Skip to content

feat(spec): FLOW_SCREEN_COPY_KEYS resolver family for screen-flow copy (#7646 recommendation B) - #11484

Merged
os-steve merged 2 commits into
mainfrom
claude/issue-11287-flow-screen-copy-keys
Aug 24, 2026
Merged

feat(spec): FLOW_SCREEN_COPY_KEYS resolver family for screen-flow copy (#7646 recommendation B)#11484
os-steve merged 2 commits into
mainfrom
claude/issue-11287-flow-screen-copy-keys

Conversation

@os-steve

Copy link
Copy Markdown
Collaborator

Fixes#11287

Clause-② / needs:contract-review: this diff widens @objectstack/spec's public surface (a new exported resolver family in packages/spec/src/system/i18n-resolver.ts). The PR stays draft, held for the PM seat's contract review — the landing decision belongs to that review, not to this branch going green.

The measurement (why this exists)

TranslationData.flows — the screen-flow copy vocabulary #7646 declared and PR #7763 shipped — had no reader anywhere: the issue's probe showed i18n.t("flows.lead_conversion.screens.screen_1.title", "zh-CN") resolving to 转化详情 from the real zh-CN bundle while the real wizard rendered "Conversion Details" in every locale. The carrier and the tree already work; only the resolver family was missing (deliberately deferred by #7763 because another change was in flight on i18n-resolver.ts, since landed).

What the family covers (the spec half only)

New exports in packages/spec/src/system/i18n-resolver.ts, mirroring the page family's conventions (PAGE_COMPONENT_COPY_KEYS / translatePage):

  • translateFlow(flow, bundle, opts) — metadata-document overlay: the flow's own label (key flows.{flow_name}.label), and for every type: 'screen' node with an id, the screen heading (flows.{flow_name}.screens.{node_id}.title) and per-field copy (…fields.{field_name}.label / .placeholder). The translated title is written to config.title even when the author relied on the node-label fallback — the executor builds the wire title as config.title ?? node.label, so one key covers whichever the runner draws (the schema's own design note). Input never mutated; untouched nodes/fields keep reference identity.
  • resolveFlowScreenTitle(bundle, flowName, screen, opts) — the piecemeal half for a caller already holding a ScreenSpec (nodeId addressing — the identifier the client is guaranteed to hold, since it correlates a resume to its pause point).
  • FLOW_SCREEN_COPY_KEYS (['title']) and FLOW_SCREEN_FIELD_COPY_KEYS (['label', 'placeholder']) — the one list the overlay and the CLI's future skeleton extractor both import; pinned against TranslationDataSchema in translation.test.ts so neither can drift.
  • Supporting shapes: FlowLike, FlowNodeLike, FlowScreenLike, FlowScreenFieldLike, FlowScreenCopyKey, FlowScreenFieldCopyKey. Complete TSDoc throughout, matching the module's conventions.

Deliberately not covered, measured against the declared vocabulary: screen description is guidance-refused by the schema (outside the recorded #7646 ruling's enumeration), so the resolver does not read it — the dispatch's measured-surfaces list names it, and per instructions I stopped on that key and reported instead of inventing (see the report's open question). Field help/options and runner chrome (Cancel/Submit/toast) likewise stay out, per the schema face and the #7646 maintainer ruling.

Fallback semantics (the sibling families' order, not a new one)

Lookup per key across locale then fallbackChain (default ['en']), key by key — a partially translated locale still falls back per key, exactly like lookupPageComponentCopy. When no locale answers, the authored source string stands (helpers never throw). Off-spec bundle entries carrying keys the schema refuses (description, help) are ignored, never overlaid — pinned by tests, mirroring translatePage's retired-submitLabel negative pin.

Why the ledger rows stay planned

This PR is resolver-only: no shipped runner reads the group yet, so flows / label / screens liveness rows keep planned + authorWarn (untouched here; check:liveness green). translateFlow is deliberately not registered in translateMetadataDocument's dispatch table — that registration reaches the REST metadata boundary by itself (TRANSLATABLE_METADATA_TYPES drives @objectstack/rest), which would stand up a shipped reader while the rows say planned. The absence is pinned by a test so adding it is a deliberate act on the runner card. The runner application (item 2) and the CLI coverage bucket (item 3) are follow-up cards the PM files at ACCEPT; the ledger flip rides the runner card. The lint-walk defect that silences authorWarn is tracked separately as #11288 and is untouched here.

Verification (union re-run at final commit f89b869a7f)

  • pnpm --filter @objectstack/spec testTest Files 419 passed (419) / Tests 11176 passed (11176) at f89b869a7f (verdict line os-verify-lock: VERDICT command-exit 0).
  • pnpm --filter @objectstack/spec typecheck → clean; test layer check:test-typecheck: OK — 55 file(s) / 263 error(s) held in test-typecheck-debt.json (pre-existing shrink-only debt, unchanged).
  • pnpm --filter @objectstack/spec check:generated✓ All 14 generated artifacts are up to date. at f89b869a7f (after gen:export-origins + gen:api-surface for the new exports — both regenerated artifacts committed).
  • Reverse verification (fix committed first, mutation via git restore --source=origin/main, trap-guarded restore): with the family reverted, the two pinned test files went red — 11 failed (translateFlow is not a function × the 10 new resolver tests, plus the schema↔list pin) — and restore returned them green. Mutation proven on disk by marker grep (0 with the family removed, 4 restored); the tests resolve ./i18n-resolver relatively from src, no dist in the path, so neither leg needed a rebuild.
  • Derived local gates (node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack, derived at fa3cde3c8a, re-checked content-identical at f89b869a7f): all runnable gates green — check:merge-driver, check:objectui-changeset, check:published-files, check:slot-lookup, check:spec-parsed-alias, check:test-source-alias, check:type-source-resolution, check:changeset-gate-self-tests, check:nul-bytes, check:cross-package-test-inputs, check:query-options-erasure, check:engine-double-contract, check:where-matcher, check:type-check-coverage, check:adr-0087-registration, check-changeset-no-major, check-empty-changeset, check-ci-filter-parity, check-plugin-teardown-shape, check:empty-state, check:variant-docs, check:liveness, check:strictness-ledger, check:doc-formula-expressions (after building its @objectstack/formula dep), docs-audit/check-affected-docs (exit 0). Declared not run locally: check:type-check-debt and check-dev-prereqs require the full 67-package workspace closure built (fresh worktree; whole-workspace build is CI's run — the spec-package half of the type-check ratchet, check:test-typecheck, ran green above); check:react-declaration-parity needs objectui's sdui.manifest.json, which this repo cannot produce (on-demand gate by ruling).
  • Changeset: minor for @objectstack/spec (additive surface widening; not breaking — check:adr-0087-registration green with no marker required).

Generated by Claude Code

#7646 recommendation B)
translateFlow / resolveFlowScreenTitle + the FLOW_SCREEN_COPY_KEYS /
FLOW_SCREEN_FIELD_COPY_KEYS shared key lists in system/i18n-resolver.ts,
mirroring the page family's conventions. Deliberately NOT registered in
METADATA_DOCUMENT_TRANSLATORS: that table reaches the REST boundary by
itself and would stand up a shipped reader while the flows liveness rows
are planned; the runner wiring and the ledger flip ride the downstream
cards. Off-spec bundle keys the schema refuses (description, help) are
ignored, never overlaid — pinned. export-origins regenerated.
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

This PR changes 1 package(s): @objectstack/spec, touching 17 documentable anchor(s).

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

  • content/docs/automation/approvals.mdx(via nodeId (symbol))
  • content/docs/automation/flows.mdx(via nodeId (symbol))
  • content/docs/kernel/cluster.mdx(via nodeId (symbol))

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

  • content/docs/releases/v17.mdx(via nodeId (symbol))

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
  • 2 changed file(s) yielded no anchor (packages/spec/api-surface/system.json, packages/spec/export-origins/system.json) — pages documenting those are invisible to this run
  • 4 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 a58eac3e274f3b85d6ca27d7447d43759756d661packageMentionDocs.

Which tree this was computed on

This run read content/docs from a9b15b5179573c910e4ead8a9b19c032bd4c29ce — the merge of head f89b869a7fbdc4ad524aefcdc428d3c470784099 into base a58eac3e274f3b85d6ca27d7447d43759756d661, 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 a9b15b5179573c910e4ead8a9b19c032bd4c29ce && git checkout a9b15b5179573c910e4ead8a9b19c032bd4c29ce
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin a58eac3e274f3b85d6ca27d7447d43759756d661 f89b869a7fbdc4ad524aefcdc428d3c470784099 && git checkout -B drift-repro a58eac3e274f3b85d6ca27d7447d43759756d661 && git merge --no-ff f89b869a7fbdc4ad524aefcdc428d3c470784099
node scripts/docs-audit/affected-docs.mjs --json a58eac3e274f3b85d6ca27d7447d43759756d661

⚠️ 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 a58eac3e274f3b85d6ca27d7447d43759756d661 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

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

Labels

documentationImprovements or additions to documentationprotocol:systemsize/lteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

i18n: nothing reads TranslationData.flows — a screen flow still renders its authored English in every locale (the runner half #7646 deferred)

2 participants

@os-steve@claude