docs(spec): stop the translation @example blocks teaching an unresolvable messages id - #18553
Conversation
…able messages id `messages` is a flat `Record<string, string>` while `t()` resolves a key by walking its dot path, so an id that merely contains a dot is unreachable. Both docblock `@example` blocks on `TranslationDataSchema` and `TranslationItemSchema` demonstrated exactly such an id (`'common.save'`); they now author `commonSave` and state the rule. Claude-Session: https://claude.ai/code/session_01JbZnqu8bt6YqfJsr9vaFb3 Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
What this run could not see
Coarse fallback — 136 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 257f69203a722e1d3658b979de884a52f555098c && git checkout 257f69203a722e1d3658b979de884a52f555098c
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 79a046f8cdf085d95200826ee9bb2fa6584bc3d5 9620486d10051794636f8f10e66cc1c4f7e2ef21 && git checkout -B drift-repro 79a046f8cdf085d95200826ee9bb2fa6584bc3d5 && git merge --no-ff 9620486d10051794636f8f10e66cc1c4f7e2ef21
node scripts/docs-audit/affected-docs.mjs --json 79a046f8cdf085d95200826ee9bb2fa6584bc3d5
|
Fixes #18190
Clause-②: no
The two docblock
@exampleblocks onTranslationDataSchemaandTranslationItemSchemademonstrated amessagesid that is writable, schema-valid and resolves to nothing. They now author the single-segment spelling, and both docblocks state the rule.Scope — the doorway half only
The card names two independent halves and says in terms that one does not imply the other. This PR takes half 1 (fix the doorway) and deliberately leaves half 2 (narrow the schema) alone: a single-segment constraint on
messageskeys narrows a published accept set, which the card itself marks as aClause-②: yesshape owing the contract-review path. Nothing here narrows, retires or refuses anything — the accept set is byte-identical tomain.The mechanism, measured first-hand at BOTH implementations
The card's load-bearing leg was relayed, not measured by this seat, and it demanded both
t()implementations. Triage had located one. Both are now read and run:t()packages/core/src/fallbacks/memory-i18n.tsresolveKey()at:106—key.split('.'), segment-by-segment:152, and again on the declared-fallback legpackages/services/service-i18n/src/file-i18n-adapter.tsresolveKey()at:27— same body:142, throughresolveFromLocale()at:277They agree. Same split, same walk, same
undefinedat the first missing hop, same "return the key itself" on a miss. So the bigger finding the card warned about — one resolving a dotted id while the other does not — does not exist. Nothing is absorbed into this PR on that account.messagesis declared a flat record of string to string (translation.zod.ts:670), so a dotted id is one key that merely contains a dot.t('messages.common.save', …)therefore looks for a nestedcommonobject undermessages, finds a string or nothing at the first hop, and returns the key string.Acceptance item 2 — verified by running it, not by eye
A one-time measurement drove the exact authored payload of each repaired
@examplethrough both realt()implementations, loaded through their published entry points (builtdist/), via the real authored-translation path that strips the item envelope (readAuthoredTranslationLayer's destructure) and hands the groups toreplaceAuthoredTranslations:Leg 0 is the part that keeps this PR honest about its scope: the old payload still parses. Nothing is refused that was accepted before.
The old id is the control that fires: it returns the key string at both implementations, which is what "resolves to nothing" looks like from a caller. The new id returns the translation at both.
What is NOT owed
content/docs/references/**regeneration. Those pages are generated from.describe()text (property tables) and carry no@exampleblock — verified bycheck:generated, which reports the docs artifact up to date on this diff. No.describe()string moved.Acceptance notes (out of scope, not filed)
packages/spec/src/conversions/registry.ts(thetranslation-validation-messages-removedfixture) andpackages/qa/downstream-contract/src/additional-domains.fixtures.tsboth carry'common.save'as inert fixture data. Neither is a doorway: conversion fixtures do not render into the upgrade guide or any docs page (checked), and the key's role in both is "the neighbour that survives the strip". Noted, not filed. Successor: whoever next edits those fixtures.packages/spec/src/system/translation.test.tspins'common.save'as a key the record accepts. That is a true statement about the schema and this PR does not change it, so the fixtures stay. Successor: the card that decides half 2, which is the PR that would make those assertions false.packages/lint/src/lint-liveness-properties.test.tscarries the same fixture key. Out of bounds for this card (held by lint:translation-target-unknownreadsapps[].navigationonly, so every locale key for a CONTRIBUTED navigation item (navigationContributions) is a false positive whose advice deletes a translation the runtime honours #18203) and inert there for the same reason. Noted, not filed.@exampleids resolve would need a test outsidepackages/specreading spec source — a cross-package test input with its own gate discipline. Noted, not filed; successor: none today.Filed separately (3-class finding, in the report for the seat to file)
examples/app-todo/src/translations/{en,zh-CN,ja-JP}.tsauthor twelve dottedmessagesids each ('common.save','common.cancel', …). That is the same class as this card — authorable, schema-valid, resolves to nothing — but in a shipped example app rather than a docblock, so it is a separate surface and a separate card. Not folded in.Tests
All figures below were taken at
9620486d10, this branch's final commit;origin/mainhas not moved since the branch point (79a046f8cd), so no merge is owed.node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commandsderived 78 families from the actual change set (2 paths). All 78 were run and reconciled with--ran, each line carrying its exit code: "78 derived famil(ies) accounted for — 78 run, 0 NOT-MEASURED (a DERIVED zero — all 78 recorded an exit code and none of them is 3)".pnpm check:i18n-walk-parity— the family this card's surface makes load-bearing: exit 0, "11 declared group(s), 8 walked, 3 exempted — every declared group has an extractor face".pnpm --filter @objectstack/spec check:generated— exit 0, "All 15 generated artifacts are up to date",check:docs(content/docs/references) among them. This is the evidence that no generated page is owed.pnpm --filter @objectstack/spec typecheck && pnpm --filter @objectstack/spec test— exit 0; 483 test files, 13773 tests passed.eslint . --no-inline-config— run in full rather than narrowed: 6803 files linted, 0 errors, 0 warnings, exit 0.pnpm check:cross-package-test-inputs. It flagspackages/cli/test/init-created-files-summary.e2e.test.tsdescendingpackages/spec/dist/, a radius no declared glob reaches. Controlled: the gate enumerates the real filesystem (readdirSync), so withpackages/spec/dist/moved aside it exits 0 ("OK: 29 package(s) read outside themselves, all declared") and with it present it exits 1 — on the identical source tree. The trigger is that spec was built locally, which every local gate run requires; CI runs this gate on an unbuilt checkout and therefore never sees it. Neither file is in this diff. Filed as a finding in the report on class (c): a dottedmessagesid is authorable, schema-valid and resolves to nothing — and TranslationItemSchema's own @example teaches it, at two sites #18190, ⛔ not repaired here.PREREQUISITE NOT MET(exit 3, explicitly "nothing was measured") and one self-test refused on a shallow clone. All three were re-run after building the missing packages and unshallowing, and all three then exited 0; the reconciliation above reflects the re-runs.Generated by Claude Code
Generated by Claude Code