Skip to content

feat(spec): give TranslationBundle a flows surface for screen-flow wizard copy (#7646) - #7763

Merged
os-zhuang merged 4 commits into
mainfrom
claude/issue-7646-translationbundle-flows-surface
Aug 12, 2026
Merged

feat(spec): give TranslationBundle a flows surface for screen-flow wizard copy (#7646)#7763
os-zhuang merged 4 commits into
mainfrom
claude/issue-7646-translationbundle-flows-surface

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes #7646

A type: 'screen' flow is a wizard the user reads — a heading, a list of labelled inputs — and the translation bundle had no group for any of it. Not a drifted key: no key. The bundle's surfaces were objects, apps, dashboards, pages, settings and metadata forms, so a translator had nowhere to put a screen title or a screen field label, and the strict shapes (correctly) refused whatever group they invented. HotCRM finished all four locales and retired its i18n exemption ledger, and its lead_conversion wizard still rendered "Conversion Details / Create Opportunity? / Opportunity Name" in English on a zh-CN console.

The ruling this implements

Recorded on #7646 (2026-08-11), quoted verbatim from the issue comment:

Ruling: direction accepted. TranslationBundle gains a flows surface — per-flow label; per-screen title; per-field label/placeholder/help — applied by the screen-flow runner the same way object/field labels already are. Runner chrome (Cancel/Submit) stays in the console's own message catalog, NOT the bundle. Contract-first split: this card is the spec surface; the objectui runner-application card and the HotCRM wiring are downstream, filed by their consuming seats once this lands.

The new group

Added to the shared translationDataShape(), so it lands on both doors by construction — the file-authored bundle (TranslationDataSchema) and the translation metadata item (TranslationItemSchema). That shared-shape placement is the thing #3778's item-only guard got wrong and #4001 fixed; a group added to one door only is the asymmetry this file keeps paying to remove.

flows.< flow_name >.label
flows.< flow_name >.screens.< node_id >.title
flows.< flow_name >.screens.< node_id >.fields.< field_name >.label
flows.< flow_name >.screens.< node_id >.fields.< field_name >.placeholder

Key addressing — measured, with evidence

The PM assumption to falsify here was "which identifiers does the runner actually resolve against". Each level's key is an identifier a consumer already holds at render time, not a new naming scheme:

level key declared at reaches the client as
flow Flow.name (machine name, ^[a-z_][a-z0-9_]*$) packages/spec/src/automation/flow.zod.ts:614 the console launches the run by name
screen FlowNode.id packages/spec/src/automation/flow.zod.ts:263 ScreenSpec.nodeId, packages/spec/src/contracts/automation-service.ts:138
field ScreenFieldConfig.name packages/spec/src/automation/builtin-node-config.zod.ts:382 ScreenFieldSpec.name, packages/spec/src/contracts/automation-service.ts:105

nodeId rather than a screen index or the node label: it is what correlates a resume back to its pause point, so it is the one screen identifier guaranteed stable and present client-side. An index renumbers whenever a step is inserted, and the node label is itself display copy. A translation surface addressed by keys nothing produces is the declared-but-unresolvable trap this lane exists to close, so the addressing is pinned by a test that builds a real FlowSchema with a screen node and derives every bundle key from the parsed flow.

Screen title overlays config.title; a screen that declares none shows its node label (ScreenConfigSchema.title — "falls back to the node label"), so this one key covers whichever of the two the runner ends up drawing. Same one-string-one-spelling rule pages.< name >.title follows over label.

Per-field key face — measured against the screen-field schema

The ruling's list was label / placeholder / help. Two of the three are real; the third is not, and is refused rather than declared:

Runner chrome is not here

Cancel/Submit are the console's own words in every app; putting them in a per-app bundle would ask every app to re-translate the platform. No key for them on any of the three levels, and a test pins that.

Family conventions mirrored

  • Every key optional, at every level — the family's partial-locale semantics, for the reason ObjectTranslationDataSchema.label states: partial translation is the normal state, and requiring completeness fills bundles with restated source strings that mask real coverage gaps. One locale of a bundle may carry flows while another does not.
  • strictObject at all three levels with surface / history / aliases / guidance, so an unknown key is refused by name instead of stripped.
  • The label/title trap alias one level down (a flow's headline is label, a screen's is title) — the same table dashboards.widgets and pages.components carry.
  • Top level: flowflows is offered as the rename it is; screens is guidance, not an alias, because screen copy nests under the flow that owns it — the content moves, it is not re-spelled. That is the app/apps distinction TRANSLATION_KEY_GUIDANCE was built to draw.

Liveness — planned, not live

No shipped screen-flow runner reads this group yet, so the new flows row in packages/spec/liveness/translation.json is planned with authorWarn + authorHint, drilled one level over label / screens. Precedent: app.navigation.runAction, the contract-first half of the SDUI deep-link promotion, which is planned for the same reason and flips to live when its objectui consumer lands. planned rather than dead is load-bearing — dead means declared with no consumer and no plan, and this group was ruled into the vocabulary specifically so the runner half could be built against it.

Scope boundary held

The resolver-side helper — a FLOW_SCREEN_COPY_KEYS sibling of PAGE_COMPONENT_COPY_KEYS, which lives in packages/spec/src/system/i18n-resolver.ts — is deliberately not in this change: #7634 is in flight on that file, and this card is the schema surface. Recorded as a follow-up rather than collided with. Likewise packages/lint/src/validate-translation-references.ts (which skips unrecognised top-level namespaces, so flows is inert there rather than broken) and the CLI extract/coverage utilities.

Changeset

@objectstack/spec: minor. The accepted authoring surface widens — a bundle previously rejected for carrying flows now parses — which is an additive vocabulary change, not a fix to existing behaviour and not a break.

Verification

Reverse-verified via a patch file (never git stash): predicted direction plain red, since the surface is new and its pins address it directly. Removing the schema hunk turned 11 of the block's tests red — the fully-populated parse, the partial-locale parse, the addressing pin, the alias/guidance pins, the JSON-Schema describe pin and the item-door pin — and restoring it returned 110/110.

pnpm --filter @objectstack/spec test          → 378 files / 9947 tests passed
pnpm --filter @objectstack/spec typecheck     → clean (test layer: pre-existing shrink-only debt unchanged)
pnpm --filter @objectstack/spec check:liveness → translation 21 classified (live 19, planned 2); every container inheritance declared
pnpm --filter @objectstack/spec check:generated → All 13 generated artifacts are up to date
pnpm --filter @objectstack/spec check:authorable-surface → 1590 schemas, green
pnpm check:merge-driver / check:adr-anchors / check:spec-parsed-alias / check:nul-bytes → all exit 0
pnpm check:i18n → OK (9 packages, all bundles in sync, no undeclared authoring keys)

Generated closure committed: authorable-surface/system.json, liveness/state-counts.md, content/docs/references/system/translation.mdx, the strictness-ledger counts.


Generated by Claude Code

…wizard copy (#7646)

A `type: 'screen'` flow is a wizard the user reads — a heading and a list of
labelled inputs — and the translation bundle had no group for any of it. Not a
drifted key: no key. A translator had nowhere to put a screen title or a screen
field label, and the strict shapes refused whatever group they invented, so a
fully localized app still rendered its wizard in the source language.

Adds `flows` to the shared translation shape, so it lands on both doors (the
file-authored bundle and the `translation` metadata item) by construction:

  flows.<flow_name>.label
  flows.<flow_name>.screens.<node_id>.title
  flows.<flow_name>.screens.<node_id>.fields.<field_name>.{label,placeholder}

Addressing is measured against what the runner already holds — `Flow.name`,
`FlowNode.id` (forwarded verbatim as `ScreenSpec.nodeId`) and
`ScreenFieldConfig.name` (forwarded as `ScreenFieldSpec.name`) — rather than
invented, so the surface cannot be keyed by names nothing produces.

The per-field key face is measured against `ScreenFieldConfigSchema`: `label`
and `placeholder` are declared, `help` is not (the screen field has no
help-shaped key, so it would validate and never render — the ADR-0078 shape
#6080 kept out of the page-component face). `help` and `options` ride the
unknown-key guidance instead. Runner chrome (Cancel/Submit) stays in the
console's own message catalog, per the maintainer ruling on #7646.

Spec half of a contract-first split: the `flows` liveness row is `planned` with
an author warning, and flips to `live` when the objectui runner half lands.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HXyTbnqdXNR7cccv5jioAs
@vercel

vercel Bot commented Aug 11, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 12, 2026 6:54am

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec.

106 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/agents.mdx (via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx (via @objectstack/spec)
  • content/docs/ai/skills.mdx (via @objectstack/spec)
  • content/docs/api/client-sdk.mdx (via @objectstack/spec)
  • content/docs/api/environment-routing.mdx (via @objectstack/spec)
  • content/docs/api/error-catalog.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-client.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx (via @objectstack/spec)
  • content/docs/api/index.mdx (via @objectstack/spec)
  • content/docs/automation/approvals.mdx (via @objectstack/spec)
  • content/docs/automation/connectors.mdx (via @objectstack/spec)
  • content/docs/automation/flows.mdx (via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx (via packages/spec)
  • content/docs/automation/hooks.mdx (via @objectstack/spec)
  • content/docs/automation/index.mdx (via @objectstack/spec)
  • content/docs/automation/webhooks.mdx (via @objectstack/spec)
  • content/docs/automation/workflows.mdx (via @objectstack/spec)
  • content/docs/concepts/architecture.mdx (via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx (via packages/spec)
  • content/docs/concepts/index.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx (via packages/spec)
  • content/docs/concepts/north-star.mdx (via @objectstack/spec)
  • content/docs/data-modeling/analytics.mdx (via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx (via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx (via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx (via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx (via @objectstack/spec)
  • content/docs/data-modeling/index.mdx (via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx (via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx (via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx (via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx (via @objectstack/spec)
  • content/docs/deployment/cli.mdx (via @objectstack/spec)
  • content/docs/deployment/tenancy-modes.mdx (via @objectstack/spec)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx (via @objectstack/spec)
  • content/docs/getting-started/build-with-claude-code.mdx (via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx (via @objectstack/spec)
  • content/docs/getting-started/examples.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx (via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/spec)
  • content/docs/kernel/cluster.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx (via @objectstack/spec)
  • content/docs/kernel/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/data-service.mdx (via @objectstack/spec)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/examples.mdx (via @objectstack/spec)
  • content/docs/kernel/runtime-services/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx (via @objectstack/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx (via @objectstack/spec)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/spec)
  • content/docs/kernel/services.mdx (via @objectstack/spec)
  • content/docs/permissions/authorization.mdx (via @objectstack/spec)
  • content/docs/permissions/permission-sets.mdx (via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx (via @objectstack/spec)
  • content/docs/permissions/positions.mdx (via @objectstack/spec)
  • content/docs/permissions/rls.mdx (via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx (via @objectstack/spec)
  • content/docs/permissions/system-context.mdx (via packages/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx (via @objectstack/spec)
  • content/docs/plugins/development.mdx (via @objectstack/spec)
  • content/docs/plugins/index.mdx (via @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx (via @objectstack/spec)
  • content/docs/protocol/diagram.mdx (via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/http-protocol.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/query-syntax.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx (via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx (via @objectstack/spec)
  • content/docs/ui/actions.mdx (via @objectstack/spec)
  • content/docs/ui/apps.mdx (via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx (via @objectstack/spec)
  • content/docs/ui/dashboards.mdx (via @objectstack/spec)
  • content/docs/ui/field-grouping-and-order.mdx (via @objectstack/spec)
  • content/docs/ui/forms.mdx (via @objectstack/spec)
  • content/docs/ui/index.mdx (via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx (via @objectstack/spec)
  • content/docs/ui/setup-app.mdx (via @objectstack/spec)
  • content/docs/ui/translations.mdx (via @objectstack/spec)
  • content/docs/ui/views.mdx (via @objectstack/spec)

7 release-owned page(s) also reference the affected code. These are read-only:

  • content/docs/releases/implementation-status.mdx (via @objectstack/spec)
  • content/docs/releases/index.mdx (via @objectstack/spec)
  • content/docs/releases/v12.mdx (via @objectstack/spec)
  • content/docs/releases/v13.mdx (via @objectstack/spec)
  • content/docs/releases/v16.mdx (via @objectstack/spec)
  • content/docs/releases/v17.mdx (via @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/spec)

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.

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@github-actions github-actions Bot added documentation Improvements or additions to documentation protocol:system tests tooling labels Aug 11, 2026
…undary (#7646)

The bundle's own reference page enumerates every translatable surface and keeps
an honest-limits list; adding a group without touching it leaves the page
describing a bundle that no longer exists.

Adds the `flows` row to "What you can translate" and a boundary bullet saying
what is true today: the group is declared and addressed the way the runner
resolves it, no shipped runner reads it yet (the liveness row is `planned` and
the compile lint warns), a screen field has no help text to translate, and
runner chrome lives in the console's message catalog.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HXyTbnqdXNR7cccv5jioAs

@os-zhuang os-zhuang left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PM step-7 review (spec lane, session_01JY2Q5Xto1u8YHADgrZDTnk) — ACCEPT. (Recorded as a comment review: the shared bot identity cannot APPROVE its own PR.)

Direction matches the maintainer-accepted ruling on #7646 (2026-08-11, seat #6017 triage sweep): the bundle gains a flows group, and runner chrome (Cancel/Submit) stays in the console's own message catalog rather than per-app bundles.

What carried the review:

  • Addressing is the runner's ownFlow.name / FlowNode.id (client's ScreenSpec.nodeId) / ScreenFieldConfig.name, each verified against the declaring schema rather than mirrored from the report. The declared-but-unresolvable trap is tested, not just argued.
  • help correctly excluded: ScreenFieldConfigSchema has nothing help-shaped, so it rides guidance instead of becoming a slot that validates and never renders — consistent with #6080/ADR-0078. The options exclusion (unconstrained options[].value can't anchor a value-keyed map) is likewise measured.
  • Both doors get the group by construction (shared translationDataShape), and the metadata-item door is pinned in tests (#3778's lesson).
  • Liveness honesty: planned + authorWarn rather than live — with per-child rows because the two halves resolve through different consumers and can flip on different days.
  • Hot-file discipline: deliberately leaving the FLOW_SCREEN_COPY_KEYS resolver helper out because #7634 was in flight on i18n-resolver.ts was the right call; that helper travels with the objectui runner card when the group flips to live.

Landing note (PM-handled, no dev action): this PR regenerates content/docs/references/**, an os-regen routed tree, so it lands via the one-at-a-time relay — it's queued behind #7758 and #7759. I'll run its sync lap and flip when its slot arrives.


Generated by Claude Code

claude added 2 commits August 12, 2026 06:28
Wholesale regen (gen:docs, gen:strictness-ledger, gen:liveness-counts) to
re-materialize artifacts that drifted from the commits main picked up since
this branch was cut. check:generated: 13/13 green.
@os-zhuang
os-zhuang marked this pull request as ready for review August 12, 2026 07:11
@os-zhuang
os-zhuang added this pull request to the merge queue Aug 12, 2026
Merged via the queue into main with commit fa5758e Aug 12, 2026
27 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-7646-translationbundle-flows-surface branch August 12, 2026 07:28
os-zhuang pushed a commit that referenced this pull request Aug 12, 2026
…main merge

Wholesale regen (gen:docs, gen:schema's authorable-surface projection,
gen:liveness-counts) to re-materialize artifacts that drifted from commits
main picked up since this branch's last merge — the `internal` field key
(#7728) and the `flows` translation surface's planned entries (#7763).
check:generated: 13/13 green; check:liveness: green.
huangyiirene pushed a commit that referenced this pull request Aug 12, 2026
Rebase round for PR #7851 (#7521) after the domain:spec os-regen landing
relay tail landed on main (#7758, #7759, #7763, #7782, #7813, #7892).

Every `merge=os-regen` generated path is taken from origin/main WHOLESALE
(`git checkout origin/main -- <paths>`, the 12 entries .gitattributes names),
so this commit is a known-good base: the `os-regen` merge driver exits 0
without conflict markers while silently dropping one side, so a clean merge is
indistinguishable from a dropped baseline. Regeneration lands as a SEPARATE
commit on top of this one, and asserts the relay PRs' entries survived.

Committed with --no-verify deliberately: the os-regen pre-commit hook wants the
artifacts regenerated into this same commit, which would mix "what main brought"
with "what my change produces" into one indistinguishable blob. The very next
commit regenerates them, so the pushed tip is current.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018tmmVmCkr4QtvGKMcn5s96
huangyiirene pushed a commit that referenced this pull request Aug 12, 2026
…ee (#7521)

Step 4 of the os-regen rebase, on top of the committed merge (cbea40d) whose
generated baselines came from origin/main wholesale.

`check:generated` proved exactly 2 of 13 artifacts stale — api-surface/ and
export-origins/, both this PR's own — and the other 11 current, so main's
baselines carried across the merge intact. Regenerated only those two.

The diff is 4 pure insertions and zero removals: the four exports
managed-api-affordance.ts adds. The relay tail's entries are asserted present
in the regenerated tree rather than assumed — the os-regen driver drops a side
silently, so a clean merge is not evidence:

  #7763  TranslationBundle `flows`   — content/docs/references/system/translation.mdx:161
  #7813  Field.number `useGrouping`  — packages/spec/authorable-surface/data.json
  #7892  GlobalFilterSchema `object` — content/docs/references/ui/dashboard.mdx
  #7782  InvitationStatus `canceled` — content/docs/references/identity/organization.mdx

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018tmmVmCkr4QtvGKMcn5s96
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.

i18n: TranslationBundle has no surface for screen-flow UI — wizard screens render source-language only, even in a fully localized app

2 participants