Skip to content

feat(spec): retire the component-translation submitLabel copy key (#10926, ADR-0049) - #11438

Merged
os-steve merged 1 commit into
mainfrom
claude/issue-10926-retire-submit-label
Aug 23, 2026
Merged

feat(spec): retire the component-translation submitLabel copy key (#10926, ADR-0049)#11438
os-steve merged 1 commit into
mainfrom
claude/issue-10926-retire-submit-label

Conversation

@os-steve

Copy link
Copy Markdown
Collaborator

Fixes#10926

Implements the maintainer ruling recorded on the card (2026-08-22, 「接受所有」 — Option A): retire submitLabel from the component-translation face. The element:form retirement referenced throughout is the already-landed #9249 work and is not touched here; #11250 (dist-freshness message misattribution, hit during verification) is already tracked and remains open.

Ruling preconditions, measured first

  • Conversion coverage: YES. The translation face is conversion-covered — mapCollection(stack, 'translations', …) with the translation-validation-messages-removed precedent (protocol 17), and stored rows replay the chain via applyConversionsToStoredItem. So the ADR-0087 conversion is owed, and written.
  • Stored-bundle exposure: 0. Repo-wide sweep (examples/, skills/, templates, platform packages, all translation bundles/fixtures): zero writers of the translation-face submitLabel outside packages/spec itself. The only non-spec hit is service-automation's config-unknown-keys.test.ts, which uses the string as an arbitrary unknown key on a screen-node config — a different surface, unaffected. The conversion is therefore protective for customer-stored bundles, with no in-repo rewrites needed.
  • CLI sharing is real:packages/cli/src/utils/i18n-extract.ts imports PAGE_COMPONENT_COPY_KEYS from @objectstack/spec/system (one constant, both sides) — extractor and resolver narrow together; no CLI code change needed.

Retirement route: strict-delete + guidance (not a tombstone)

The face is a strictObject — per the retirement playbook's fork, the strict route applies: the key leaves the shape and a guidance entry carries the prescription (retiredKey() is the non-strict route). This face's own precedent is validationMessages (#4667): strict-delete + guidance + conversion, and — deliberately — no RETIRED_KEYS_BY_MAJOR entry: gate (b) registers keys that are tombstoned in the walked authorable surface, and this key has no authorable-surface row (the per-component copy shape is an anonymous nested record value; only system/TranslationData:pages is walked). Same disposition here.

The four sites

  1. translation.zod.ts — measured-table row replaced with the retirement note; submit: 'submitLabel' alias dropped; submitLabel key deleted; guidance tombstones added for both submitLabel (with the house os migrate meta --from 17 sentence) and the submit alias spelling (no migrate sentence on purpose — submit never parsed, so the tool lists no edit for it).
  2. i18n-resolver.tsPAGE_COMPONENT_COPY_KEYS drops the slot (six → five).
  3. ADR-0087: D2 conversion translation-component-submit-label-removed (protocol 18, retiredFromLoadPath: true) walking both authored shapes — locale-keyed bundle entries and bare item/data entries (the shape stored translation rows replay through); fixture covers both doors, expectedNotices: 2. Wired into the step-18 chain (conversionIds + rationale sentence). No overlap with parked fix(spec): reject an authored deleteBehavior 'set_null' on a master_detail at parse time; log the engine coercion loudly #11406 (that PR carries a semantic entry + regenerated migrations/registry.ts generated regions; this lands in the hand-edited CONVERSIONS registry + hand-edited conversionIds).
  4. Pins flipped, not deleted: translation.test.ts now refuses submitLabel asserting the prescription text, and refuses submit asserting the alias story; the derived schema↔list agreement pin self-adjusts. i18n-resolver.test.ts keeps the bespoke hotcrm:quick_form node and pins the NEGATIVE — an off-spec bundle entry carrying the retired key is ignored, not overlaid (the resolver is schema-independent by design; stored rows predating the retirement reach it via the raw sync path).

Changeset: minor (launch-window convention, matching the element:form precedent) with the adr-0087 registered translation-component-submit-label-removed marker, FROM → TO mapping and the one-line fix.

Ratchet visibility — zero deltas, and that is the expected reading

All four ratchets (api-surface/, authorable-surface/, json-schema.manifest/, api-surface-signatures) are byte-identical, verified by check:generated (14/14 artifacts current) at 04ea21ec55 after a full spec rebuild. Route-consistent, not suspicious: the key lives on an anonymous nested record-value shape no walker names as a def (repo-wide grep: the only submitLabel in any generated artifact is ui/ElementFormProps from #9249), and spec-changes.json / the upgrade guide project released majors only (protocolVersion: 17.0.0element-form-removed, protocol 18, is equally absent). The conversion reaches consumers when 18 is cut.

Verification (all at 04ea21ec55, spec dist rebuilt first)

  • pnpm --filter @objectstack/spec test: 419 files / 11151 tests passed (includes flipped pins, conversion fixture replay + chain replay, alias-integrity, migrate-sentence pin).
  • pnpm --filter @objectstack/spec typecheck: green (tsc + scripts + test-typecheck ledger OK).
  • pnpm --filter @objectstack/spec check:generated: "All 14 generated artifacts are up to date."
  • Derived gate families (node scripts/pm/dispatch-gates.mjs, answer asserted for this repo at 04ea21ec55): all 22 path-derived families PASS (changeset gates incl. check-adr-0087-registration and check-changeset-no-major, cross-package-test-inputs, merge-driver, spec-parsed-alias, liveness/empty-state/strictness-ledger/variant-docs, skill-examples/refs/docs, ci-filter-parity, plugin-teardown, affected-docs, doc-formula-expressions) plus convention-triggered ones (query-options-erasure, type-check-coverage, engine-double-contract, where-matcher, nul-bytes). One environment-shaped local red, declared: check-dev-prereqs fails only on 11 unrelated workspace packages having no local dist (workspace-build completeness; this diff cannot move it, CI builds first).
  • Consumer direction (downstream): pnpm --filter @objectstack/cli typecheck green after building the CLI closure; the nine CLI i18n test files re-run explicitly: 9 files / 106 tests passed. check:i18n: "check-i18n-bundles: OK (9 package(s))". Dogfood expression-conformance: 3 passed.
  • Reverse verification (rebuilt-.d.ts proof): a probe file in packages/cli/src assigning the literal 'submitLabel' to PageComponentCopyKey was confirmed on disk (grep count 1), then failed the CLI's tsc exactly as required — error TS2322: Type '"submitLabel"' is not assignable to type '"title" | "description" | "label" | "placeholder" | "emptyText"' — then removed; the same tsc is green without it. Observed direction: red, as predicted.
  • One full-suite CLI run also surfaced a pre-existing infra false-red worth naming: serve-node-env-production-default.e2e.test.ts fails on any worktree where @objectstack/cli itself is unbuilt ("command serve not found"); after pnpm --filter @objectstack/cli build it passes (3/3). Not related to this diff.

Clause-② note: needs:contract-review is hung card-side; this PR stays draft pending contract review per the dispatch contract.


Generated by Claude Code

…0926, ADR-0049)
Option A per the maintainer ruling on #10926 (2026-08-22): drop the key,
the submit alias and the PAGE_COMPONENT_COPY_KEYS slot; strict-delete
route with guidance tombstones (the face is strictObject); D2 conversion
translation-component-submit-label-removed wired into the step-18 chain;
pins flipped, not deleted.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T9cDbY2NBiVJWYx3BpWfH2
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

12 anchor(s) derived from 1 changed package(s); no hand-written page names any of them. ✅

What this run could not see
  • 5 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 45 of 222 client-bound route-ledger rows — the other 177 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run: node scripts/docs-audit/affected-docs.mjs --bridge-coverage

Coarse fallback — 126 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json d0e3a885b890e89d6d47ea5c70ae15b0a4a5d139packageMentionDocs.

Which tree this was computed on

This run read content/docs from aafee4d99731a02b8c0546023d5e6ead89f9d2b7 — the merge of head 04ea21ec55a0de0194199db9de456017ea66f291 into base d0e3a885b890e89d6d47ea5c70ae15b0a4a5d139, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin aafee4d99731a02b8c0546023d5e6ead89f9d2b7 && git checkout aafee4d99731a02b8c0546023d5e6ead89f9d2b7
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin d0e3a885b890e89d6d47ea5c70ae15b0a4a5d139 04ea21ec55a0de0194199db9de456017ea66f291 && git checkout -B drift-repro d0e3a885b890e89d6d47ea5c70ae15b0a4a5d139 && git merge --no-ff 04ea21ec55a0de0194199db9de456017ea66f291
node scripts/docs-audit/affected-docs.mjs --json d0e3a885b890e89d6d47ea5c70ae15b0a4a5d139

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationprotocol:systemsize/mteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

i18n: component-translation submitLabel copy key lost its only declared carrier when element:form retired (#9249) — decide retire vs re-anchor

2 participants

@os-steve@claude