Repository navigation
fix(spec): serve a field's translated help on description, never on an undeclared help - #21956
Conversation
… an undeclared `help` translateObject overlaid the bundle's `objects.OBJECT.fields.FIELD.help` entry onto a `help` key FieldSchema does not declare. The entry is the translation of the field's `description` (the i18n extractor writes it from that key), so it is now served there, under ADR-0029 D9.2a: the catalog applies only while the served description equals the packaged field's (valueOverridesPackagedBase), and with no packaged base it applies. ObjectFieldLike drops its `help` member. Claude-Session: https://claude.ai/code/session_01T9u38rswFp5Rw8DswRUReJ Co-authored-by: Claude <noreply@anthropic.com>
…tion Claude-Session: https://claude.ai/code/session_01T9u38rswFp5Rw8DswRUReJ Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift Check2 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run. What this run could not see
Coarse fallback — 138 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 1562b97cc7f3ebadae0712bb31964613a5fa3a83 && git checkout 1562b97cc7f3ebadae0712bb31964613a5fa3a83
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 01e0f71ad8518ed208dc95eb7bb6bf2b2fc2fa05 3ad2f22bed534424e1af81d7d96c38eab84afd0d && git checkout -B drift-repro 01e0f71ad8518ed208dc95eb7bb6bf2b2fc2fa05 && git merge --no-ff 3ad2f22bed534424e1af81d7d96c38eab84afd0d
node scripts/docs-audit/affected-docs.mjs --json 01e0f71ad8518ed208dc95eb7bb6bf2b2fc2fa05 |
Fixes #21948
Clause-②: no
What changed
translateObjectused to put a bundle'sobjects.OBJECT.fields.FIELD.helpentry on ahelpkey thatFieldSchemadoes not declare. The served field then failedFieldSchemawithunrecognized_keys. A consumer that reads only declared keys rendered the Englishdescription, and the console logged one ingestion warning per such field.field.help ?? field.description(packages/cli/src/utils/i18n-extract.ts:1223).FieldSchemarefuseshelp, so on a spec-valid field the only source isdescription. The nine bundle source trees author 0inlineHelpTextand 0 fieldhelp. The nineenbundles carry 523helpentries.translateField(packages/spec/src/system/i18n-resolver.ts:3054) now serves the translated help ondescriptionand writes nohelpkey.inlineHelpTextis not touched.valueOverridesPackagedBasepredicate. There is no second comparison. The catalog applies only while the served field'sdescriptionequals the packaged field's./meta/objectread — and neither do extension scalar overrides #8284 established).packagedPart's convention. The new finderpackagedObjectField(:1434) reads both field-map shapes.labelis unchanged and stays a flatcatalog ?? document, as its existing pin scopes it.ObjectFieldLike(:2453) drops itshelp?: stringmember. This is safe:[key: string]: anyindex signature still accepts and types ahelpkey, so a caller that writes or reads one still compiles (probe below);packages/spec/api-surface/system.jsonrecords onlyObjectFieldLike (interface), not its members, andcheck:api-surfacestays green;service-analytics, which readsoptionsonly) still typechecks.translateObjectdocblock no longer says it translates a field'shelp. A new section states the target key and the D9.2a precedence..changeset/21948-spec-field-help-served-on-description.md:@objectstack/specpatch.Clause-② — measured
nocheck:authorable-surface,check:api-surface,check:export-origins, andcheck:generated(15 artifacts up to date).dist/system/index.d.ts, exit 0:const legacy: ObjectFieldLike = { name: 'x', help: 'legacy' }compiles, and so does readinglegacy.help;@ts-expect-erroronlabel: 42is consumed);const proof: number = ({} as ObjectFieldLike).helpcompiles, which shows the probe read the rebuilt declaration..d.tswith BASE'shelp?: stringput back gives exit 2, with exactly one error: TS2322 on theproofline. Writing and readinghelpcompile under both declarations.ObjectFieldLike['help']is now read through the index signature (any) instead ofstring | undefined. That is stated here for the at-tier review.Measured
All at HEAD
3ad2f22bedunless stated.Corpus at the resolver seam (scratch script, not committed). Every object in
packages/platform-objects/scripts/i18n-extract.config.ts(48 objects, 617 fields), with that package's real bundles, using each object as its own packaged base:helpkeyshelprefused byFieldSchemasys_userfields withhelpdescriptionequal to the bundle entrysys_user.two_factor_enabled.description=该用户是否已启用双因素认证。由 better-auth 的 \twoFactor` 插件维护。`.en, 0 descriptions change from source, because the en entries repeat the source.GET /api/v1/meta/object/sys_user). The spec change invalidates the showcase build closure: 62 of 63 turbo tasks miss the cache. The pins and the corpus run read the sametranslateMetadataDocumentdispatch the REST read calls.Pins (
packages/spec/src/system/i18n-resolver.test.ts:3552, 9 cases):help, has the translation ondescription, and parses with nounrecognized_keys;descriptionandinlineHelpTextgets the translation ondescription, andinlineHelpTextis untouched;descriptionis kept in zh-CN and en, through the type dispatch, while the undiverged sibling is translated;undefined,null, or omitted) the catalog applies;descriptionis filled by the catalog;No existing pin asserted a served field
help. I searched every test outside the bundle suites, so none had to move.Ablation, run on the committed head. Each leg used
scripts/ablation-replace.mjsin WRAP mode plus a trap. Predictions were written down before the run. Both legs were restored, with the restored blob equal to HEAD (a8eade91) andgit diff HEADempty:next.help, neverdescription)Tests 7 failed | 2 passedtrue)Tests 3 failed | 6 passedexpected 'Whether two-factor authentication is …' to be '该用户是否已启用双因素认证。…'.expected '该用户是否已启用双因素认证。…' to be 'Edited by the tenant.'.Tests
@objectstack/spec, full local project:Test Files 619 passed,Tests 18480 passed | 1 todo.pnpm --filter @objectstack/spec typecheck: exit 0. Its test layer compiles undertsconfig.test.jsonwith the identity-pinned debt held.@objectstack/service-analytics:typecheckexit 0. The three dimension/label suites that calltranslateObject:Tests 53 passed..tsfiles: 0 errors and 0 warnings. That run is not the repo-wide lint, which is CI's.Gates.
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --randerived 85 families: 84 ran with exit 0 and 1 is NOT MEASURED.pnpm check:dual-build-cjs-loads, which exits 3 withPREREQUISITE NOT METbecause 78 packages have nodist/.requireentries of@objectstack/spec'sexportsload from the rebuiltdist, andsystem.translateObjectis a function.pnpm check:lean-entry-closurefirst answered exit 3 (objectql/coreunbuilt). Afterturbo run build --filter=@objectstack/objectql...it answered exit 0, and that is the recorded code.Ships:
npm pack --dry-runof@objectstack/speclistsdist/system/index.js,.mjsand.d.ts. Both runtime files carrypackagedObjectFieldand nonext.help = translatedHelp.Base: origin/main moved to
a3bd157730(#21947, test titles underpackages/spec/src/ui/only). None of this PR's files is touched, so it is not merged.Known readers in objectui
objectui at
.objectui-sha0abd4f9f, read-only. No objectui file is edited here.packages/plugin-form/src/ObjectForm.tsx:1033andpackages/plugin-form/src/sectionFields.ts:311(field.help || field.description), andpackages/app-shell/src/utils/resolveActionParams.ts:714(param.helpText ?? field.help ?? field.description).description, so they render the same translated text, and theirhelparm is now dead. Retiring those arms is objectui's job.packages/core/src/utils/reference-keys.ts:362, reached through theno-declared-twinarm (:415) ofcanonicalizeRetiredFieldKeys. It fires for any undeclared key on a served field def, so it goes quiet once nohelpis served.Acceptance notes
None of these is addressed in this PR.
saveFieldswrite-back. NOT MEASURED; this is a read-only inference.packages/app-shell/src/services/MetadataService.ts:898to:939carries per-field server keys from the translated read back into the object PUT.helpis not in objectui'sRETIRED_FIELD_KEYS(packages/types/src/internal/retired-field-keys.ts). So a field save on a platform object with bundlehelpentries should have sent a keyFieldSchemarefuses.field.help ??arm (packages/cli/src/utils/i18n-extract.ts:1223) reads a keyFieldSchemarefuses, so it is dead on every spec-valid field. Carrier:domain:cli; none in flight.placeholderis never overlaid.FieldTranslationSchemadeclaresplaceholder, and the extractor emitsobjects.OBJECT.fields.FIELD.placeholder(:1224), buttranslateFieldnever overlays it. The nine shipped bundles carry 0 field-level placeholder entries (the 4 per locale are action params), so it is dormant. Carrier: none.inlineHelpTexthas no translation path. The extractor never reads it, so an authored one is never offered for translation. The nine bundle sources author 0 of them. Carrier: none.content/docs/protocol/kernel/i18n-standard.mdx:166lists a field'slabel/help/placeholderas display labels, butFieldSchemadeclares nohelp. Carrier: none.Generated by Claude Code