spec: a flow screen field's help text is translatable — inlineHelpText joins the flows per-field face (#17306) - #21386
Conversation
…lpText on the flows face) FLOW_SCREEN_FIELD_COPY_KEYS gains inlineHelpText, and the flows translation schema's screen-field node declares it. The help spellings move from guidance to aliases onto it. The flows ledger rows are re-read at the .objectui-sha pin 31971ff1e. Claude-Session: https://claude.ai/code/session_01UtnxvdiN376GF3sgXwAw4d Co-authored-by: Claude <noreply@anthropic.com>
…alker now emits The walker imports FLOW_SCREEN_FIELD_COPY_KEYS, so it emits the new key with no source edit. The fixture authors a help line on one field, so the key-face pin compares the whole spec list. Claude-Session: https://claude.ai/code/session_01UtnxvdiN376GF3sgXwAw4d Co-authored-by: Claude <noreply@anthropic.com>
…reen-field-keys-live
📓 Docs Drift CheckThis PR changes 1 package(s): 5 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 1 release-owned page(s) also name something this change touched. These are read-only:
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 13e0d10f068dd7f0f59ebea8db8ebcded4da783d && git checkout 13e0d10f068dd7f0f59ebea8db8ebcded4da783d
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 51550933dbc5c7cbd89349a6beb5145363fc881d 0b151ee532ad9db8625dde2a12cec6fa4d4d4bd5 && git checkout -B drift-repro 51550933dbc5c7cbd89349a6beb5145363fc881d && git merge --no-ff 0b151ee532ad9db8625dde2a12cec6fa4d4d4bd5
node scripts/docs-audit/affected-docs.mjs --json 51550933dbc5c7cbd89349a6beb5145363fc881d
|
Contract reviewServed-tier: Isolated contract review of PR #21386 (card #17306) at the head above. Read-only shape held: the card body and every comment on it (rulings ① Derived judgmentsEvery accept-set and public-surface change the diff implies, each judged:
② Semver level
③ Boundary flagsEach deviation and the out-of-scope entry in dev report
Check-runs on the head, read over REST at 2026-10-02T10:18Z after every run had concluded: 35 check-runs, 33 Implemented-by: VERDICT: PASS |
…lows.mdx says so The flows translation face carries inlineHelpText since this branch's first commit, so the sentence saying it renders in the authored language until the face grows a key was false. It now names the key and points at the flows row in the Translations page. Claude-Session: https://claude.ai/code/session_01UtnxvdiN376GF3sgXwAw4d Co-authored-by: Claude <noreply@anthropic.com>
Contract reviewServed-tier: Isolated contract re-review of PR #21386 (card #17306) at the head above, narrowed to the delta since the previous record (comment ① Derived judgments
② Semver levelUnchanged since the previous record: the delta touches no published package, and ③ Boundary flags
Check-runs on the head, read over REST at 2026-10-02T10:44Z after every run had concluded (the last one at 10:43:47Z): 42 check-runs, 38 Implemented-by: VERDICT: PASS |
Fixes #17306
Clause-②: yes (widening)
The restart shape pre-written on the card (comment 5908590307, unlocked by 5947573357), checked against
origin/mainbefore any edit. Part 2 lands. Part 1's premise was falsified by measurement, and nothing was flipped (see Premise check below).The rulings this executes are A (5643444726) and A′ (5651909056): the screen-field keys ship together with their rendering (「声明即强制」). The rendering is objectui#9248 → objectui
81778b955, and this repo's.objectui-shapin31971ff1e28fcarries it: RESTcompare 81778b955575...31971ff1e28fon objectui answersahead,ahead_by 100,behind_by 0.What changes
FLOW_SCREEN_FIELD_COPY_KEYS(@objectstack/spec/system) is now['label', 'placeholder', 'inlineHelpText'], so a flow screen field's help text gets a per-field translation key. The key is the screen field's own spelling (ScreenFieldConfig.inlineHelpText, which is also the object field's spelling). Both overlays write the translation back onto that same key.TranslationDataSchema:flows.FLOW.screens.NODE_ID.fields.FIELDdeclaresinlineHelpText. Five help spellings (help,helpText,hint,tooltip,description) used to get guidance saying the face had no help key. They are now aliases ontoinlineHelpText, so the.strict()refusal names the rename.options/choices/valueskeep their guidance. Nothing that parsed before is refused.FlowScreenFieldLikegainsinlineHelpText?: string.translateFlowneeds no logic change, becausetranslateScreenFieldspreads whatever the constant resolved.liveness/translation.json(hand-kept): theflows.screensrow was alreadylive. It is re-read at the pin31971ff1e: every objectui pointer inevidenceandproduceris repinned fromf8a9d0fb,overlayFieldCopyandScreenVieware added as readers, andverifiedAtis 2026-10-02. Theflowscontainer'sauthorHintand the stale help sentence in its note are corrected. No status moved, sostate-counts/and the README count rows are unchanged (check:liveness: current, 10plannedin total, as before).content/docs/ui/translations.mdx: the flows row now lists.inlineHelpText. The boundary note no longer says that a screen field has no help text, or that no runner reads the group.content/docs/automation/flows.mdx(patch round 2, head0b151ee532): the screen-field paragraph that saidinlineHelpTextwas "not translatable yet" now says it is translated under the flows face, besidelabelandplaceholder, and links the flows row in Translations. This PR made the old sentence false; the at-tier record5950219612named it.@objectstack/spec: minor, carrying the sameClause-②: yes (widening)line.Every reader of the constant, followed
i18n-resolver.ts#lookupFlowScreenFieldCopy/#translateScreenFieldi18n-resolver.test.ts.flowsfield node intranslation.zod.ts.strict()would refuse the key the extractor writes. The existing "declares exactly the keys" pin now iterates three keys.packages/lint/src/validate-translation-references.tspackages/cli/src/utils/i18n-extract.ts#walkScreenFlowsinlineHelpText.packages/app-shell/src/views/FlowRunner.tsx#overlayFieldCopycopy[key]is typed from the spec'sTranslationData, which now carries the member.Premise check: part 1 (the four
plannedliveness rows) does not existThe dispatch's mechanism assumption 1 was that some ledger holds
plannedrows for the screen-field keysmin,max,inlineHelpTextandreference. Measured at base9360df4138:packages/spec/liveness/has a row for any of the four.git grep -n inlineHelpText -- packages/spec/livenesshits onlyfield.json(the OBJECT field's row) andtranslation.json.flow.jsonstops atnodes.config, which isz.record(z.string(), z.unknown())(packages/spec/src/automation/flow.zod.ts), so the gate's walk cannot reach a node config key.children: { min: … }was added underflow.json'snodes.config, thencheck:livenesswas run. It exited 1 with✗ 1 UNCLASSIFIED … flow/nodes.config (declared children but property is not a container). The file was restored withgit checkout HEAD --, and its blob hash equals HEAD's. A row for these keys cannot be added, so there is nothing to flip..describe()carries aplanned/experimentalmarker either.The rendering half the flip was meant to record is cited below instead. All four readers exist at the pin, so no key is held back.
objectui readers at the pin (
31971ff1e)min/max:ScreenView.tsx#ScreenFieldInputputs the nativemin/maxon the numeric input.ScreenView.tsx#screenFieldBoundViolationsis the submit-time comparison (inclusive, present finite number, hidden fields skipped).FlowRunner.tsx#FlowRunnerrefuses the submit through it and names the field.inlineHelpText:ScreenView.tsx#ScreenViewdraws it under the control, and the control names it inaria-describedby.reference:ScreenView.tsx#ScreenFieldInputrenders the sharedLookupFieldwidget overfield.referenceon atype: 'lookup'field.Tests (head
a7f3557b11, after mergingorigin/mainat3937ad2f32)pnpm --filter @objectstack/spec exec vitest run --project local --maxWorkers=2: 598 files, 17531 passed, 1 todo.pnpm --filter @objectstack/spec typecheck: exit 0 (tsc, scripts, andcheck:test-typecheck: 52 files, 246 errors, 135 pinned signatures held).pnpm --filter @objectstack/cli exec vitest run --project unit --maxWorkers=2: 245 files, 3465 passed. Theintegrationlayer is declared to CI: this diff touches no spawn entry and no integration file.pnpm --filter @objectstack/cli typecheck: exit 0.pnpm --filter @objectstack/spec check:generated: all 15 generated artifacts up to date, against adistrebuilt after the merge.@objectstack/spec.d.ts. It assigned'inlineHelpText'toFlowScreenFieldCopyKey,{ inlineHelpText }to theTranslationDataflows field node, and'help'toFlowScreenFieldCopyKey. Result: exactly one error, on the'help'line (TS2322 … not assignable to type '"label" | "placeholder" | "inlineHelpText"'). The module was deleted andgit statusis clean.Gates (union run on
a7f3557b11)node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commandsderived 109 commands with no paths passed. All 109 ran, pluscheck:i18n,check:i18n-coverageandcheck:i18n-stale-fill, measured for the dispatch's coverage question. 112 runs, all exit 0.--ranreconciliation:109 derived, 109 run, 0 NOT-MEASURED, 0 UNRUN.check:skill-examples,check:dual-build-cjs-loads,check:i18n-walk-parity). They were re-run after the build and are green on the final head.check:i18n-coverageOK (13 configs, 621 baselined, none new).check:i18nOK (9 packages in sync). No example app or platform bundle authorsinlineHelpTexton a flow screen field. The only hit inexamples/is an object field,app-showcasecontact.object.ts. The CLI's wholeflows.*demand is also still held back by theflowsrow'sauthorWarn, which waits on the flow-label reader..tsfiles reportisPathIgnored: false. The changed.md,.mdxand.jsonfiles are outside the config's**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}files.eslint --no-inline-config --format jsonon the 6 files: 6 files, 0 errors, 0 warnings ona7f3557b11. Invariance:eslint.config.mjsenables no type-aware linting (noparserOptions.project, no typed rules, as its own comment states), so this diff cannot move a verdict on any untouched file. The repo-widepnpm lintis CI's run.NOT MEASURED
consolepath filter inci.ymlexcludespackages/spec/**by design, so the check isskippedon this PR. What stands is the static reading at the pin, which the at-tier record5950219612verified independently:FlowRunner.tsximports the constant,overlayFieldCopyindexescopy[key]with the widened key,ScreenView.tsxdrawsinlineHelpTextunder the control, andscripts/build-console.shbundles this tree's spec into the console.Acceptance notes
nodes.configis opaque to the walk (probe above). Their declared-vs-read reconciliation lives inservice-automation'sbuiltin-node-form-zod-ledger.test.tsandscreen-input-contract.test.ts, not inliveness/. Noted, not filed: this is the design of the ledger's one-drill-level boundary, not a defect.content/docs/ui/translations.mdx's next paragraph ("The day the runner lands and the row flips tolive…") still describes the flow-label half correctly and is unchanged.origin/mainmoved again after the merge (d78bd011ea,11905a4f8b: CI-filter parity andos generate). Neither touches this diff's files. The re-derivation printed the same 109 commands.Review round 1
a7f3557b11(comment5950219612). Its ③ escalated one sentence this PR made false, incontent/docs/automation/flows.mdx. Patch round 2 (0b151ee532, +5 / -3 in that file alone) corrects it. No code moved.inlineHelpText(no second spelling admitted),minor/Clause-②: yes (widening)right, and part 1's falsification verified by reading the ledger andcheck-liveness's source.Generated by Claude Code