Skip to content

spec: a flow screen field's help text is translatable — inlineHelpText joins the flows per-field face (#17306) - #21386

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-17306-screen-field-keys-live
Oct 2, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-17306-screen-field-keys-live

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #17306
Clause-②: yes (widening)

The restart shape pre-written on the card (comment 5908590307, unlocked by 5947573357), checked against origin/main before 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-sha pin 31971ff1e28f carries it: REST compare 81778b955575...31971ff1e28f on objectui answers ahead, 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.FIELD declares inlineHelpText. Five help spellings (help, helpText, hint, tooltip, description) used to get guidance saying the face had no help key. They are now aliases onto inlineHelpText, so the .strict() refusal names the rename. options / choices / values keep their guidance. Nothing that parsed before is refused.
  • FlowScreenFieldLike gains inlineHelpText?: string. translateFlow needs no logic change, because translateScreenField spreads whatever the constant resolved.
  • liveness/translation.json (hand-kept): the flows.screens row was already live. It is re-read at the pin 31971ff1e: every objectui pointer in evidence and producer is repinned from f8a9d0fb, overlayFieldCopy and ScreenView are added as readers, and verifiedAt is 2026-10-02. The flows container's authorHint and the stale help sentence in its note are corrected. No status moved, so state-counts/ and the README count rows are unchanged (check:liveness: current, 10 planned in 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, head 0b151ee532): the screen-field paragraph that said inlineHelpText was "not translatable yet" now says it is translated under the flows face, beside label and placeholder, and links the flows row in Translations. This PR made the old sentence false; the at-tier record 5950219612 named it.
  • Changeset @objectstack/spec: minor, carrying the same Clause-②: yes (widening) line.

Every reader of the constant, followed

Reader Where What it needed
spec resolver i18n-resolver.ts#lookupFlowScreenFieldCopy / #translateScreenField Nothing. It walks the constant and spreads the result. New pin in i18n-resolver.test.ts.
translation schema the flows field node in translation.zod.ts The member. Without it .strict() would refuse the key the extractor writes. The existing "declares exactly the keys" pin now iterates three keys.
lint walk packages/lint/src/validate-translation-references.ts Nothing. It resolves flow, screen and field names and never reads copy keys.
CLI extractor and coverage packages/cli/src/utils/i18n-extract.ts#walkScreenFlows Nothing in source, because it imports the constant. Four CLI pins listed literal keys and are updated. "Scaffolds a bundle the strict schema accepts" now parses a skeleton that carries inlineHelpText.
objectui runner objectui @31971ff1e packages/app-shell/src/views/FlowRunner.tsx#overlayFieldCopy Nothing. It imports the constant (its header says the day the spec lists the key it is translated with no edit there). copy[key] is typed from the spec's TranslationData, which now carries the member.

Premise check: part 1 (the four planned liveness rows) does not exist

The dispatch's mechanism assumption 1 was that some ledger holds planned rows for the screen-field keys min, max, inlineHelpText and reference. Measured at base 9360df4138:

  • No ledger under packages/spec/liveness/ has a row for any of the four. git grep -n inlineHelpText -- packages/spec/liveness hits only field.json (the OBJECT field's row) and translation.json.
  • flow.json stops at nodes.config, which is z.record(z.string(), z.unknown()) (packages/spec/src/automation/flow.zod.ts), so the gate's walk cannot reach a node config key.
  • Probe. children: { min: … } was added under flow.json's nodes.config, then check:liveness was run. It exited 1 with ✗ 1 UNCLASSIFIED … flow/nodes.config (declared children but property is not a container). The file was restored with git checkout HEAD --, and its blob hash equals HEAD's. A row for these keys cannot be added, so there is nothing to flip.
  • None of the four keys' .describe() carries a planned / experimental marker 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#ScreenFieldInput puts the native min / max on the numeric input. ScreenView.tsx#screenFieldBoundViolations is the submit-time comparison (inclusive, present finite number, hidden fields skipped). FlowRunner.tsx#FlowRunner refuses the submit through it and names the field.
  • inlineHelpText: ScreenView.tsx#ScreenView draws it under the control, and the control names it in aria-describedby.
  • reference: ScreenView.tsx#ScreenFieldInput renders the shared LookupField widget over field.reference on a type: 'lookup' field.

Tests (head a7f3557b11, after merging origin/main at 3937ad2f32)

  • 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, and check: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. The integration layer 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 a dist rebuilt after the merge.
  • Reverse verification (one-off, not committed). A scratch module was compiled against the rebuilt @objectstack/spec .d.ts. It assigned 'inlineHelpText' to FlowScreenFieldCopyKey, { inlineHelpText } to the TranslationData flows field node, and 'help' to FlowScreenFieldCopyKey. Result: exactly one error, on the 'help' line (TS2322 … not assignable to type '"label" | "placeholder" | "inlineHelpText"'). The module was deleted and git status is clean.

Gates (union run on a7f3557b11)

node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands derived 109 commands with no paths passed. All 109 ran, plus check:i18n, check:i18n-coverage and check:i18n-stale-fill, measured for the dispatch's coverage question. 112 runs, all exit 0. --ran reconciliation: 109 derived, 109 run, 0 NOT-MEASURED, 0 UNRUN.

  • The first local run of three gates refused with exit 3, PREREQUISITE NOT MET (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.
  • Translation coverage (dispatch assumption 4): check:i18n-coverage OK (13 configs, 621 baselined, none new). check:i18n OK (9 packages in sync). No example app or platform bundle authors inlineHelpText on a flow screen field. The only hit in examples/ is an object field, app-showcase contact.object.ts. The CLI's whole flows.* demand is also still held back by the flows row's authorWarn, which waits on the flow-label reader.
  • Lint, narrowed. Population read from eslint itself: all 6 changed .ts files report isPathIgnored: false. The changed .md, .mdx and .json files are outside the config's **/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs} files. eslint --no-inline-config --format json on the 6 files: 6 files, 0 errors, 0 warnings on a7f3557b11. Invariance: eslint.config.mjs enables no type-aware linting (no parserOptions.project, no typed rules, as its own comment states), so this diff cannot move a verdict on any untouched file. The repo-wide pnpm lint is CI's run.

NOT MEASURED

  • Console Pin Gate (objectui at the pin built against this spec): NOT MEASURED locally, because building objectui does not fit this container's foreground budget. CI does not run it either: the console path filter in ci.yml excludes packages/spec/** by design, so the check is skipped on this PR. What stands is the static reading at the pin, which the at-tier record 5950219612 verified independently: FlowRunner.tsx imports the constant, overlayFieldCopy indexes copy[key] with the widened key, ScreenView.tsx draws inlineHelpText under the control, and scripts/build-console.sh bundles this tree's spec into the console.

Acceptance notes

  • The four screen-field keys have no liveness-ledger seat at all, because nodes.config is opaque to the walk (probe above). Their declared-vs-read reconciliation lives in service-automation's builtin-node-form-zod-ledger.test.ts and screen-input-contract.test.ts, not in liveness/. 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 to live …") still describes the flow-label half correctly and is unchanged.
  • origin/main moved again after the merge (d78bd011ea, 11905a4f8b: CI-filter parity and os generate). Neither touches this diff's files. The re-derivation printed the same 109 commands.

Review round 1

  • At-tier contract review PASS on a7f3557b11 (comment 5950219612). Its ③ escalated one sentence this PR made false, in content/docs/automation/flows.mdx. Patch round 2 (0b151ee532, +5 / -3 in that file alone) corrects it. No code moved.
  • The same record judged the five help spellings REFUSED with a rename to inlineHelpText (no second spelling admitted), minor / Clause-②: yes (widening) right, and part 1's falsification verified by reading the ledger and check-liveness's source.

Generated by Claude Code

claude added 3 commits October 2, 2026 08:17
…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>
@github-actions github-actions Bot added the size/m label Oct 2, 2026
@github-actions github-actions Bot added documentation Improvements or additions to documentation protocol:system tests tooling labels Oct 2, 2026
@github-actions

github-actions Bot commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec, touching 6 documentable anchor(s). ⚠️ 1 changed file(s) yielded no anchor (packages/spec/liveness/translation.json), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

5 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/automation/flows.mdx (via inlineHelpText (symbol, a field of interface FlowScreenFieldLike), inlineHelpText (literal, a string literal in FLOW_SCREEN_FIELD_COPY_KEYS; a string literal in appTranslationDataShape))
  • content/docs/data-modeling/field-types.mdx (via inlineHelpText (symbol, a field of interface FlowScreenFieldLike), inlineHelpText (literal, a string literal in FLOW_SCREEN_FIELD_COPY_KEYS; a string literal in appTranslationDataShape))
  • content/docs/data-modeling/fields.mdx (via inlineHelpText (symbol, a field of interface FlowScreenFieldLike), inlineHelpText (literal, a string literal in FLOW_SCREEN_FIELD_COPY_KEYS; a string literal in appTranslationDataShape))
  • content/docs/getting-started/quick-reference.mdx (via inlineHelpText (symbol, a field of interface FlowScreenFieldLike), inlineHelpText (literal, a string literal in FLOW_SCREEN_FIELD_COPY_KEYS; a string literal in appTranslationDataShape))
  • content/docs/ui/translations.mdx (via inlineHelpText (symbol, a field of interface FlowScreenFieldLike), inlineHelpText (literal, a string literal in FLOW_SCREEN_FIELD_COPY_KEYS; a string literal in appTranslationDataShape))

⛔ 1 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v17/17-5.mdx (via inlineHelpText (symbol, a field of interface FlowScreenFieldLike), inlineHelpText (literal, a string literal in FLOW_SCREEN_FIELD_COPY_KEYS; a string literal in appTranslationDataShape))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/spec/liveness/translation.json) — pages documenting those are invisible to this run
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 138 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 51550933dbc5c7cbd89349a6beb5145363fc881d → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 13e0d10f068dd7f0f59ebea8db8ebcded4da783d — the merge of head 0b151ee532ad9db8625dde2a12cec6fa4d4d4bd5 into base 51550933dbc5c7cbd89349a6beb5145363fc881d, 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 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

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

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 51550933dbc5c7cbd89349a6beb5145363fc881d → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: a7f3557b119385c771d010576f2e82a8cc756cb9
Local-runs: none
Rendered-at: 2026-10-02T10:19Z

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 5643444726 A and 5651909056 A-prime with clarification 5652004815, restart shape 5908590307, unlock 5947573357, claim 5947914088, seat-2 pointer 5947942746, dev report 5949733717), #7646's ruling 5253154523 and report 5253892795, the PR body and file list, the net diff origin/main...head (merge base 3937ad2f32), the head's check-runs over REST, and objectui at the .objectui-sha pin read with git show PIN:PATH. Nothing was built, tested, or re-run. Head identity: refs/review/17306 fetched from the branch equals the head sha above.

① Derived judgments

Every accept-set and public-surface change the diff implies, each judged:

  1. FLOW_SCREEN_FIELD_COPY_KEYS becomes ['label', 'placeholder', 'inlineHelpText'] (packages/spec/src/system/i18n-resolver.ts:3847, exported on @objectstack/spec/system, listed in api-surface/system.json:244). RIGHT, and declared means enforced holds: the key is read by the shipped console at the pin. The chain, read at objectui 31971ff1e28f (the .objectui-sha on main at the merge base): packages/app-shell/src/views/FlowRunner.tsx:142 imports the constant from @objectstack/spec/system; #overlayFieldCopy (:208 to :216) walks it over each ScreenFieldSpec and writes copy[key] back onto the same key when it is a non-empty string; #localizeScreen (:224 to :240) maps every field through it from bundle[language]?.flows?.[flowName]?.screens?.[screen.nodeId]?.fields; FlowRunner builds shown from it at :329 and hands screen={shown} to ScreenView at :519 to :520; ScreenView.tsx:299 derives helpId from f.inlineHelpText and :319 draws it under the control as a paragraph the control names in aria-describedby. The constant reaches that bundle because scripts/build-console.sh:291 sets SPEC_PKG to this tree's packages/spec and :317 exports OBJECTSTACK_SPEC_DIST="$SPEC_PKG", which objectui's apps/console/vite.config.ts:756 resolves. FlowScreenFieldTranslation in FlowRunner.tsx:164 to :166 is derived from the spec's TranslationData, so copy['inlineHelpText'] is typed by this very schema change; no objectui edit is needed, as the dev says. The server side agrees: packages/services/service-automation/src/builtin/screen-nodes.ts:257 forwards inlineHelpText into ScreenFieldSpec (contracts/automation-service.ts:228), so the authored fallback is on the wire.
  2. TranslationDataSchema: flows.FLOW.screens.NODE_ID.fields.FIELD accepts inlineHelpText (translation.zod.ts:1302). RIGHT. The accept set widens by exactly this one key on exactly this one node; the .strict() shape is otherwise unchanged. translation.test.ts:1261 to :1290 ("declares exactly the keys") iterates the constant, so the schema face and the key list are pinned equal with no literal restated.
  3. The five help spellings (help, helpText, hint, tooltip, description) are REFUSED, not accepted. Settled from the helper, not the prose: strictObject's aliases option feeds only strictUnknownKeyError's suggestion (shared/strict-object.ts:339 to :356; shared/suggestions.zod.ts:355 to :405), there is no preprocess or transform anywhere on that path, and the shape stays .strict() (strict-object.ts:541). The new pin in translation.test.ts asserts code === 'unrecognized_keys' for each spelling and a message containing the rename to inlineHelpText. So "aliases onto inlineHelpText" means a refusal that names the right key; no second spelling is admitted, the accept set is unchanged, and the author sees a rename where the old text said the face had no help key. Consequence: the startup-stage rule (pm-dispatch SKILL.md: no dual-spelling grace, staged only on named external evidence) and Prime Directive Add comprehensive test suite for Zod schema validation #12's conversion-layer requirement are not engaged, because nothing is tolerated. RIGHT.
  4. description as the fifth refusal pointer. The dev's cited precedent is the bulk-param face at translation.zod.ts:326, description: 'help', on a face whose authoring schema (BulkActionParamSchema, ui/bulk-action.zod.ts:247) declares help and no description. The shape is the same here: ScreenFieldConfigSchema declares inlineHelpText and no description (the screen NODE has one, the field does not), and FieldSchema.description is "Tooltip/Help text" (data/field.zod.ts:1116), so the meaning is measured, not guessed. The precedent applies. One asymmetry to know about, not a defect: the authoring ScreenFieldConfigSchema aliases only the four (builtin-node-config.zod.ts:601) and refuses description with the bare key list, while the translation face refuses it with the rename. Both refuse. RIGHT as written; narrowing to four would also be defensible.
  5. FlowScreenFieldLike gains inlineHelpText?: string (exported interface, api-surface/system.json:270). RIGHT. The docblock's new claim that the executor forwards inlineHelpText verbatim is true (screen-nodes.ts:257).
  6. Resolver logic unchanged. lookupFlowScreenFieldCopy (:3892) iterates the constant down the locale chain and translateScreenField (:4067) spreads the result; no branch was added. The new i18n-resolver.test.ts case covers overlay, per-key fallback, and no invention. RIGHT.
  7. Every reader of the constant is updated or follows it. Repo-wide at the head: spec resolver :3904 (follows); translation.test.ts:1188, :1272, :1281, :1290 (iterate it); CLI extractor packages/cli/src/utils/i18n-extract.ts:133 and :1855 (imports it, no source change); CLI tests: four literal key lists updated in i18n-flow-screen-coverage.test.ts and i18n-flow-liveness-gate.test.ts, and :301 compares the whole constant; lint walk packages/lint/src/validate-translation-references.ts:1738 to :1760 resolves field NAMES only (dev's claim verified); api-surface and export-origins list the symbol name only, so no regeneration is owed. No other restated list exists in this repo or in objectui at the pin. RIGHT.
  8. Liveness ledger translation.json. The flows.screens row stays live; verifiedAt moves to 2026-10-02 and every objectui pointer is repinned from f8a9d0fb to 31971ff1e. Checked: git show 3937ad2f32:.objectui-sha is 31971ff1e28f89cfc45f0c19bc5b05e443f28b79; REST compare 81778b955575...31971ff1e28f on objectui answers ahead, ahead_by 100, behind_by 0, so the pin carries objectui#9248's merge. Each new or repinned pointer exists at the pin and says what the row says: FlowRunner.tsx#localizeScreen :224, #overlayFieldCopy :208, #activeFlowsBundle :190, #FlowRunner :486 and :519; ScreenView.tsx#ScreenView :299 and :319; producers apps/console/src/loadLanguage.ts:16 and :34, packages/i18n/src/utils/spec-translations.ts:152, packages/i18n/src/provider.tsx:624 and :717. The container's authorHint and both notes are corrected without moving a status; state-counts/ is untouched and the Spec property liveness check is green on the head. RIGHT.
  9. content/docs/ui/translations.mdx. Both corrected sentences are true after the diff (the runner reads screens; the field face carries inlineHelpText). The rest of the boundary note stays true: the flows container is still planned with authorWarn for the unread label, so "the tooling does not ask you for these keys" and "the day the runner lands and the row flips to live" still describe the flow-label half correctly. RIGHT for this page; see ③ for a sibling page.
  10. No ScreenFieldConfigSchema accept-set change. git diff origin/main...head -- packages/spec/src/automation/ is empty. RIGHT; the claim's prohibition is honoured.

② Semver level

@objectstack/spec: minor, Clause-②: yes (widening). RIGHT. What publishes: the Zod face TranslationDataSchema accepts one new key; the exported tuple FLOW_SCREEN_FIELD_COPY_KEYS and the union FlowScreenFieldCopyKey gain one member; the exported interface FlowScreenFieldLike gains one optional member. All three are widenings of a published surface, none removes or renames anything, so yes with (widening) and at least minor is the correct declaration; the PR body carries the same line. One note on the interface: the new member narrows the index-signature slot [key: string]: unknown to string | undefined for that one key, so an input object with a non-string inlineHelpText that compiled before no longer does. Every producer of that shape already types it as a string (ScreenFieldConfigSchema, ScreenFieldSpec), and label and placeholder took the same step earlier, so this is not a break in practice and minor stands. The changeset prose was checked sentence by sentence against the diff and the pin: the console draws the text under the control (true at ScreenView.tsx:319), translateFlow overlays it (true), os i18n extract scaffolds it (true: the extractor imports the constant and the CLI pin now expects the key in the skeleton), objectui's FlowRunner overlays it (true), the five spellings are still refused with the rename (true), options keeps its guidance (true), nothing that parsed before is refused now (true: only an addition to the accept set). No other published package's surface moves: packages/cli/src and packages/lint/src are untouched and the four CLI files in the diff are tests. Check Changeset is green on the head.

③ Boundary flags

Each deviation and the out-of-scope entry in dev report 5949733717, answered:

  • Part 1 of the restart shape (flip four planned rows to live) was not executed. Verified by reading, not running: no ledger under packages/spec/liveness/ carries a row for a flow screen field's min, max, inlineHelpText or reference (the only hits are the object field's rows in field.json and the datasource rows); flow.json drills props.nodes.children.config to { status: live, evidence: engine.ts } with no children; flow.zod.ts:591 declares config: z.record(z.string(), z.unknown()); and check-liveness.mts childShape (:662 to :669) returns null for a record of unknown, which drillChildren reports at :1055 as "declared children but property is not a container". The dev's probe result is what the source predicts, so the premise was false and nothing could be flipped. Are rulings A and A-prime honoured without it? Yes: the ruling's condition is that the keys ship together with their rendering. The keys are on main (PR spec: a flow screen field can express a numeric bound, help text and a lookup target #17913), the rendering is objectui#9248 merged as 81778b955, the pin at this head's merge base carries it, and build-console.sh injects this tree's spec into that console. The PR body's per-key citations were checked at the pin and are true: min and max go to the native input at ScreenView.tsx:421 to :422 and to the submit-time comparison screenFieldBoundViolations at :210 to :211, which FlowRunner.tsx:426 refuses on; inlineHelpText at :299 and :319; reference renders LookupField at :371 to :378. The ledger simply has no seat for it, so the citation in the PR body plus the repinned flows.screens row is the honest record. What remains owed, in this repo: one sentence. content/docs/automation/flows.mdx:491 to :493 still reads "Not translatable yet: the flows translation bundle carries label and placeholder per field, so inlineHelpText renders in the authored language until that face grows a key for it." This diff makes that false and does not correct it; the PR's own Docs Drift Check (comment 5949695390) named that page. content/docs/** is a prose face and not a contract-review face, so this does not move the verdict; it is escalated to the dispatching seat for ACCEPT, to be fixed before landing. Outside this repo, ruling A item 4 and A-prime item 3 (the hotcrm relay) remain the director seat's close-out act, not this PR's.
  • Anchor moved from :3822 to :3840. Verified: :3822 at the claim's base 5fd4855a9a, :3840 at 9360df4138 and at origin/main; the constant now sits at :3847 on the head because its docblock grew. Disjoint from the action-lookup range either way. Accepted.
  • translations.mdx is beyond the claim's text. Accepted: the diff made the page's sentence false, and the claim's "pins and generated reference page that follow it" is the same intent. The generated reference page did not move because content/docs/references/system/ renders no field-node copy keys (no "Translated screen field" text there on the head), and Build Docs and Check Documentation Links are green.
  • The description alias choice. Judged in ① item 4: a refusal pointer with the bulk-param precedent and FieldSchema.description's meaning behind it, accept set unchanged. Kept.
  • Harness attribution. Both non-merge commits carry the session-URL trailer and a co-author trailer that names no model; no model identifier lands in the tree or on the board. Consistent with AGENTS.md. Not a contract matter.
  • Local-run noise. Not a contract matter; the head's check-runs are the gate verdicts this record reads.
  • Out-of-scope entry (ledger boundary). The observation is correct: a flow node's config is a z.record of unknown, so the ledger's walk cannot reach a screen field key, and the declared-vs-read reconciliation for those keys lives in service-automation's builtin-node-form-zod-ledger.test.ts and screen-input-contract.test.ts. Against the finding classes (triage-duties: (a) reproducible defect, (b) breach of a declared contract, (c) a metadata-authoring trap): nothing fails, no published contract text promises a ledger row below the one-drill-level boundary the ledger's own type note states, and no author is misled by the gap. It is acceptable as an Acceptance note, not a finding. Nothing is filed by this record.
  • main moved past the merge. Verified: six commits after the merge base 3937ad2f32 (ee75aae1a8, db0cf2231b, 7b21af80cb, 43e928dd4c, 11905a4f8b, d78bd011ea); git log 3937ad2f32..origin/main over the nine changed paths is empty. No re-merge is owed.
  • One flag the dev did not raise: Console Pin Gate is skipped on this head. The console path filter (.github/workflows/ci.yml:281 to :289) deliberately excludes packages/spec/** as bundled content, so "CI owns it" does not hold for this head: no objectui build against this spec ran. The static reading at the pin is therefore what stands, and it was checked independently here: objectui imports the constant and derives its translation types from the spec (① item 1), restates no key list, and ScreenFieldSpec already carries the member. Noted; not a defect.

Check-runs on the head, read over REST at 2026-10-02T10:18Z after every run had concluded: 35 check-runs, 33 completed/success, 2 completed/skipped (Console Pin Gate, by the path filter described above; Packed-tarball smoke (opt-in)), 0 failed, 0 still running. The derived gate families this record relies on are all in the green set: Spec property liveness, Check Changeset, Lint & Repo Gates (which carries the check:generated reconciliation), the four Type Check jobs, all six Test Core shards, Build Core, Build Docs, Check Documentation Links, Governed Surface Queue Guard, the three Dogfood Regression Gate shards, Dogfood Verify CLI and Temporal Conformance. Their conclusions are the gate verdicts; nothing was re-run here, and no running gate is reported as passed.

Implemented-by: claude/issue-17306-screen-field-keys-live
Reviewed-by: session_01UtnxvdiN376GF3sgXwAw4d

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>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 0b151ee532ad9db8625dde2a12cec6fa4d4d4bd5
Local-runs: none
Rendered-at: 2026-10-02T10:46Z

Isolated contract re-review of PR #21386 (card #17306) at the head above, narrowed to the delta since the previous record (comment 5950219612, PASS on head a7f3557, written here without a code span so this record names one head). Read-only shape held: the card body and every comment on it (rulings 5643444726 A and 5651909056 A-prime with clarification 5652004815, claim 5947914088, seat-2 pointer 5947942746, dev reports 5949733717 and 5950389823), the previous record and the PR's Docs Drift Check 5949695390, the PR body and file list, the net diff origin/main...head (merge base 3937ad2f32), the delta diff from a7f3557 to the head, the head's check-runs over REST, and objectui at the .objectui-sha pin read with git show PIN:PATH. Nothing was built, tested or re-run. Head identity: refs/review/17306 fetched from the branch equals the head sha above, and its parent is a7f3557.

① Derived judgments

  1. The delta is exactly one docs hunk. The diff from a7f3557 to this head touches content/docs/automation/flows.mdx alone: one hunk, +5 / -3, replacing the paragraph that opened "Not translatable yet" (lines 492 to 494 on the previous head) with the paragraph that opens "Translatable:" (lines 492 to 496 on this head). One commit, parent a7f3557, so nothing was dropped or rebased. No line under packages/, .changeset/ or content/docs/ui/ moved, so the previous record's ① items 1 to 10 stand on byte-identical code; the pieces the new paragraph leans on were re-read at this head anyway (items 3 to 6). RIGHT.
  2. main has not moved under the diff. The merge base is still 3937ad2f32; origin/main is 51550933db, seven commits past it (d78bd011ea, 11905a4f8b, 43e928dd4c, 7b21af80cb, db0cf2231b, ee75aae1a8, 51550933db), and git log 3937ad2f32..origin/main over the ten changed paths is empty. The .objectui-sha pin is 31971ff1e28f on the head and on origin/main. No re-merge is owed. RIGHT.
  3. The key path is true. The paragraph names flows.FLOW.screens.NODE_ID.fields.FIELD.inlineHelpText (the file spells the three placeholders in the docs' own angle-bracket convention, the same one translations.mdx:83 uses). translation.zod.ts at the head: flows is a record of strict objects at :1247, screens a record at :1263, fields a record at :1283, and inlineHelpText: z.string().optional() sits on that field node at :1302. TRUE.
  4. "beside label and placeholder" is true. FLOW_SCREEN_FIELD_COPY_KEYS at i18n-resolver.ts:3847 is ['label', 'placeholder', 'inlineHelpText'], and the "declares exactly the keys" pin in translation.test.ts holds the field node to that constant (previous record ① item 2). TRUE.
  5. "the console's flow runner overlays it in the active locale" is true. objectui at the pin 31971ff1e28f: FlowRunner.tsx:142 to :146 imports the constant from @objectstack/spec/system; FlowRunner takes language from useObjectTranslation() at :300; activeFlowsBundle (:190 to :204) reads getResourceBundle(language, 'translation') and keys the bundle by that language with no fallback chain; localizeScreen (:224 to :238) maps each field through overlayFieldCopy (:208 to :216), which walks the imported constant and writes copy[key] back onto the same key when it is a non-empty string; shown is built at :329 and handed to ScreenView as screen={shown} at :519 to :520; ScreenView.tsx:299 derives helpId from f.inlineHelpText and :319 draws it under the control, which names it in aria-describedby. The constant the runner walks is the three-key one in the console this repo builds, because scripts/build-console.sh bundles this tree's spec (previous record ① item 1, unchanged). TRUE, with the ordinary reading that a console built against a spec release older than this changeset walks the two-key constant until it takes the release; the page ships in the same release as the key, so the sentence and the behaviour land together.
  6. The link resolves to the flows row. [Translations](/docs/ui/translations#what-you-can-translate): content/docs/ui/translations.mdx has ## What you can translate at :62 and its next ## at :148; the flows row, the table row naming flows.FLOW.label, .screens.NODE_ID.title, .fields.FIELD.label, .placeholder and .inlineHelpText, is at :83, inside that section. scripts/check-doc-anchors.mjs slugs a heading with github-slugger (its header, :53 to :61), which yields what-you-can-translate; the route form /docs/ui/translations#... is the one two existing fragment links already use (i18n-standard.mdx:161 and :219). Check Documentation Links is green on the head, and check:doc-anchors rides in Lint & Repo Gates (conclusion below). TRUE.
  7. The rest of the page agrees with the new paragraph. The refusal sentence at flows.mdx:461 to :462 (helpText, help, hint and tooltip named onto inlineHelpText) is about the authoring schema ScreenFieldConfigSchema, is unchanged, and is still true (previous record ① item 4). No sentence in flows.mdx now says the opposite of the new paragraph. RIGHT.

② Semver level

Unchanged since the previous record: the delta touches no published package, and .changeset/17306-flow-screen-field-help-text-translation.md is byte-identical to the one judged there: @objectstack/spec: minor, Clause-②: yes (widening), and the PR body carries the same line. What ships is still exactly the three widenings (one accepted key on the flows field node, one member on the exported tuple FLOW_SCREEN_FIELD_COPY_KEYS and its union, one optional member on FlowScreenFieldLike), none removing or renaming anything. The changeset's sentences were re-read against this head and each is still true, including "the console's screen dialog draws that text under the control, so a translated help line now renders in the active locale", which is the same claim as the new paragraph and holds on the same chain (① item 5). Check Changeset is green on the head, on both of its runs. RIGHT.

③ 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 completed/success, 4 completed/skipped, 0 failed, 0 cancelled, 0 still running. The four skips: Console Pin Gate (the console path filter, as above), Packed-tarball smoke (opt-in), and a second run each of Auto Label and Check PR Size started at 10:30Z and skipped by its own job condition, while the first run of each on this head is success. The derived gate families this record relies on are all in the green set: Spec property liveness, Check Changeset (both runs), Lint & Repo Gates (lint.yml:179, the job that runs pnpm check:doc-anchors at :382, so the new link's fragment was checked on this head), the four Type Check jobs and the TypeScript Type Check rollup, all six Test Core shards and the Test Core rollup, Build Core, Build Docs, Check Documentation Links, Governed Surface Queue Guard, Flag docs affected by code changes, the three Dogfood Regression Gate shards and their rollup, Dogfood Verify CLI and Temporal Conformance. Their conclusions are the gate verdicts; nothing was re-run here, and no running gate is reported as passed.

Implemented-by: claude/issue-17306-screen-field-keys-live
Reviewed-by: session_01UtnxvdiN376GF3sgXwAw4d

VERDICT: PASS

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 2, 2026 10:51
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 2, 2026 10:51
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 2, 2026
Merged via the queue into main with commit ecb6ca0 Oct 2, 2026
44 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-17306-screen-field-keys-live branch October 2, 2026 11:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation protocol:system size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

spec: a flow screen field cannot express a numeric bound, help text, or a lookup target — three intents that degrade into prose in the reference app

2 participants