Repository navigation
fix(spec): the flows translation group goes live — the flow runner reads flows.FLOW.label - #21859
Conversation
…r reads flows.FLOW.label The objectui runner reads the translated flow label at the .objectui-sha pin 0abd4f9f8 (objectui 39a3e91fa), so flows.children.label moves planned -> live and the flows container, both children now live, flips with it and drops authorWarn and authorHint. state-counts/translation.md regenerated. Claude-Session: https://claude.ai/code/session_01T9u38rswFp5Rw8DswRUReJ Co-authored-by: Claude <noreply@anthropic.com>
…alves translations.mdx's boundary bullet and the flows TSDoc in translation.zod.ts said the flow's own label was read by nothing; the runner now names the flow by flows.FLOW.label in its header and completion toast. Claude-Session: https://claude.ai/code/session_01T9u38rswFp5Rw8DswRUReJ Co-authored-by: Claude <noreply@anthropic.com>
…roup going live Claude-Session: https://claude.ai/code/session_01T9u38rswFp5Rw8DswRUReJ Co-authored-by: Claude <noreply@anthropic.com>
…at the group is read crm_convert_lead_wizard, showcase_reassign_wizard and quick_add_task gain their flows.FLOW.label and screen copy in every supported locale, the 21 strings the coverage gate demands for flows that can open the runner. Claude-Session: https://claude.ai/code/session_01T9u38rswFp5Rw8DswRUReJ Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift Check2 anchor(s) derived from 2 changed package(s); no hand-written page names any of them. What this run could not see
Coarse fallback — 144 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 6c74cd8423a43ec5f6192763c2eae4e048dead6a && git checkout 6c74cd8423a43ec5f6192763c2eae4e048dead6a
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 9f9510f25e6aa65aa61ce3effb42706fabcab92e bac6651dd7eafe818d99078ed17d8f595691b16c && git checkout -B drift-repro 9f9510f25e6aa65aa61ce3effb42706fabcab92e && git merge --no-ff bac6651dd7eafe818d99078ed17d8f595691b16c
node scripts/docs-audit/affected-docs.mjs --json 9f9510f25e6aa65aa61ce3effb42706fabcab92e |
…the live flows row Comments only. The authorWarnedTranslationGroups docblock and the extract surface header, the flow-screen coverage test header and translateFlow's docblock said the flow label was unread and the row planned. Claude-Session: https://claude.ai/code/session_01T9u38rswFp5Rw8DswRUReJ Co-authored-by: Claude <noreply@anthropic.com>
… warned group The shipped translation ledger warns on no group now that flows is live, so the locale-keyed walk pins and the planned-with-authorHint verdict read a planted synthetic group row through lintLivenessPropertiesFromLedgerDir. The shipped flows row is pinned silent, and the authorWarnedProperties anti-drift pin asks over every governed translation group and over the object type's warned set, so it cannot pass by iterating nothing. Claude-Session: https://claude.ai/code/session_01T9u38rswFp5Rw8DswRUReJ Co-authored-by: Claude <noreply@anthropic.com>
The census pin is now the empty set, with a positive from the same reader; the gating, demand, collision and skeleton pins assert what the live flows row produces. The injected-set pin still holds the gate for a warned group. Claude-Session: https://claude.ai/code/session_01T9u38rswFp5Rw8DswRUReJ Co-authored-by: Claude <noreply@anthropic.com>
…ner can open walkScreenFlows emits the flow-label entry only when the flow has a screen node at any depth of the node universe its screens walk reads. The one reader of the key, the console's FlowRunner, opens only on a run paused at a screen, so a scheduled, record-triggered or API flow with no screen owed a string no surface shows. Pinned both ways: no screen node, no label entry; a screen nested two regions deep still owes it. Claude-Session: https://claude.ai/code/session_01T9u38rswFp5Rw8DswRUReJ Co-authored-by: Claude <noreply@anthropic.com>
…demand The translations guide and the spec changeset say the flow's own label is asked for only when the flow has a screen node. Claude-Session: https://claude.ai/code/session_01T9u38rswFp5Rw8DswRUReJ Co-authored-by: Claude <noreply@anthropic.com>
Fixes #20318
Clause-②: no
Stage 3 of the card: the objectstack half that follows objectui's new reader. Three things land together. The
flowstranslation group goeslivein the liveness ledger, in both halves. The tests that pinned the old ledger state move with it. And the CLI's coverage walker demands a flow's own label only where the runner can show it, perdomain:cli's ruling A on #6024. Every check measured here is green,check:i18n-coverageincluded, and every example sits at its committed baseline.The reader, measured at the pin
.objectui-shaonmainis0abd4f9f8769.git merge-base --is-ancestor 39a3e91fad 0abd4f9f8769exits 0, so the pin carries objectui PR 11636. A positive answer proves itself even on a shallow clone. Read at0abd4f9f8769, read-only:packages/app-shell/src/views/FlowRunner.tsx:268-275displayFlowLabelreturnstranslateFlow({ name: flowName, label: flowLabel }, bundle, { locale: language }).label || flowName. That is the active language'sflows.FLOW.label, then the servedflowLabel, then the API name.:377computesshownFlowLabel.:541draws it in the dialog header, above the step'sDialogTitle.:441handsdisplayFlowLabel(...)to theflowRunner.completedcompletion toast. The toast shows only when the flow authored nosuccessMessage.:211-229activeFlowsBundlereads the active language'sflowsgroup from the i18nexttranslationresource tree. Thescreensoverlay uses the same read.translateFlowis imported from@objectstack/spec/system(:163).packages/app-shell/src/utils/flowResponse.ts:234-235(servedFlowLabel, on the trigger and resume 200 bodies).hooks/useConsoleActionRuntime.tsx:653,views/RecordDetailView.tsx:1118, andapps/console/src/pages/developer/FlowRunsPage.tsx:310.domain:cli's ruling was re-run at the pin.git grep -E "translateFlow\b|displayFlowLabel"overpackages/, excluding tests and stories, still returns 8 hits: 7 inFlowRunner.tsxand 1 comment inmetadata-admin/inspectors/flow-nested-selection.ts. No reader of a non-screen flow's translated label exists, and the pin has not moved.What changed
packages/spec/liveness/translation.jsonflows.children.label:plannedtolive, withverifiedAt 2026-10-05andevidenceScope: cross-repo. The evidence usesfile#symbolanchors, with each objectui pointer prefixedobjectui @0abd4f9f8:as the ledger README prescribes. Theproducernames the engine'sAutomationResult.flowLabelcopy, the objectui lift and hosts, and the bundle delivery chain thescreensrow already cites.flowscontainer: both children are nowlive, so it islivetoo, with its own evidence. It dropsauthorWarnandauthorHint, because aliverow refuses them.screensrow's note gains one dated line saying that its "NOT flipped with it" clause is superseded.packages/spec/liveness/state-counts/translation.md: regenerated withgen:liveness-counts.translationis now26 live · 0 planned, up from25 · 1.content/docs/ui/translations.mdx: the boundary bullet now says what is true.screensthrough the screen copy, andlabelthrough the runner header and the toast.description, an authoredsuccessMessage, and the runner chrome.labelonly when the flow has a screen node.packages/spec/src/system/translation.zod.ts: theflowsTSDoc said "The flow's ownlabelis read by nothing yet". It now names the reader and the pin. This is the JSDoc only. No.describe()string moved, so nothing generated was built from it.examples/: the three screen wizards get theirflowscopy in every supported locale. That coverscrm_convert_lead_wizard(zh-CN, 4 strings),showcase_reassign_wizard(zh-CN, 3) andquick_add_task(zh-CN and ja-JP, 7 each).npm pack --dry-run:.changeset/20318-flows-translation-live.mdis an@objectstack/specpatch.liveness/translation.json,liveness/state-counts/translation.mdandsrc/system/translation.zod.tsall ship, and the controldist/index.jsships too..changeset/20318-cli-flow-label-demand.mdis an@objectstack/clipatch.dist/utils/i18n-extract.jsships (597 files packed; the controlpackage.jsonis present), and the built file carries the predicate.content/docsandexamples/publish nothing.The flow label is demanded only for a flow the runner can open (
domain:cliruling A)packages/cli/src/utils/i18n-extract.ts: one predicate inwalkScreenFlows. Theflows.FLOW.labelentry is emitted only whencollectFlowNodesDeep, the node universe the screens walk already reads at any depth, yields ascreennode. Nothing else in the walker moves: the node list is computed one statement earlier, so the label entry keeps its place ahead of the screen entries.FlowRunneras the reader it mirrors (ruling condition (b)). It also says the predicate widens in the same change as any future reader of a non-screen flow's label.packages/cli/test/i18n-flow-screen-coverage.test.ts(ruling condition (a)):flows.FLOW.labelentry;[]for its flow, whose only screen-shaped object sits in anhttppayload, so the flow has no screen node and owes no label. It gains a lit control: the same flow with one real screen emits the label and that screen, and still not the payload object.scripts/ablation-replace.mjsand proved by "ok mutation landed" and "ok restored: blob == HEAD (521163a07d87) andgit diff HEADis empty". The test imports../src, so no build is in the path.Tests 3 failed | 21 passed (24): the no-screen pin, the one-tree pin and the region-table test.flow.nodesinstead of the deep universe readsTests 2 failed | 22 passed (24): the region-nested pin and the one-tree pin. The region-nested pin cannot go red by removing the predicate, since an unconditional label satisfies it, so this second leg is the one that proves it.The tests that pinned the old ledger move with the flip
These are test and comment edits, with cross-lane declarations to
domain:clianddomain:devx. No pin was deleted.packages/cli/test/i18n-flow-liveness-gate.test.ts: its real-ledger pins now read the post-flip ledger.authorWarnedTranslationGroups()equals[], still an equality.authorWarnedProperties('object')namingexternalSharingModel.flowsis among its roots.i18n/missing-flowkeys, and the translated one draws none.flows.packages/lint/src/lint-liveness-properties.test.ts:TranslationItemanti-fixture now read a planted syntheticplanned+authorWarngroup row. They go throughlintLivenessPropertiesFromLedgerDir, the seam the syntheticfieldledger already uses.translation.flows (planned)case splits into a silence pin on the shipped row (beside a witness that still warns) and a planted twin that holds the planned-with-authorHintwording.flowstoo.authorWarnedPropertiescensus reads[]for translation, with theobjectpositive.objecttype's non-empty warned set.packages/cli/src/utils/i18n-extract.ts, the extract-surface header listsinlineHelpTextand says whyflowsis walked, and theauthorWarnedTranslationGroupsdocblock says the set is empty today.packages/cli/test/i18n-flow-screen-coverage.test.ts.translateFlow's docblock inpackages/spec/src/system/i18n-resolver.ts.No new id: no new or rewritten test title, test string, comment headline or runtime string carries a tracker id as its citation. The new pins, the predicate's comment and the CLI changeset sentence cite behaviour and the reader, not a tracker number.
What dropping
authorWarnswitched on, and what the predicate takes backWith the ledger flipped and no predicate,
os lint ... --jsonreported 56 newi18n/missing-flowfindings across the examples:labelof a flow with no screen nodeapp-crmapp-showcaseapp-todo(zh-CN + ja-JP)The 21 screen-flow strings are translated in this PR. The 35 label demands for flows that never open the runner are what the predicate removes.
check:i18n-coveragenow reads "OK (13 config(s), 621 baselined untranslated string(s), none new)". The ratchet is two-sided, so crm, showcase and todo sit exactly at their committed 101, 414 and 106.scripts/i18n-coverage-baseline.jsonis untouched, and none of the 35 inert strings is authored.check:i18nstays green.Tests and gates (at
bac6651dd7, every package built with declarations)turbo run build --filter=!@objectstack/docs(72 tasks, exit 0).Test Files 259 passed (259),Tests 3786 passed (3786). That is 3783 plus the three new predicate pins. The packed-.d.tspins are included.@objectstack/lintsuite:Tests 5629 passed (5629).@objectstack/clitypecheck exit 0, test layer included ("held in test-typecheck-debt.json", unchanged).Tests 24 passed (24)at HEAD. lintlint-liveness-propertiesis unchanged sincedfa18e7699, where it readTests 97 passed (97), and it is inside the 5629 above. The flip's control leg on the re-pinned real-ledger pins (pre-flip ledger restored by a trap, proved by blob hash and an emptygit diff HEAD) read CLI6 failed | 23 passedand lint5 failed | 92 passed, while the planted pins stayed green.check:livenessexit 0.check:i18n-coverageexit 0, as above.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 115. The--ranreconciliation reads "115 derived famil(ies) accounted for — 115 run, 0 NOT-MEASURED", and all 115 exited 0.origin/main(9f9510f25e) touches none of these 15 files and does not move.objectui-sha(still0abd4f9f8769; its last change is8832655af2). Not merged, not rebased. The one derived-from file main changed,scripts/engine-double-contract.pinned.json, gained an objectql test pin unrelated to this diff.Acceptance notes
.objectui-shamoves first, re-run the reader grep on the new pin, and stop if a reader of a non-screen flow's translated label has appeared.Generated by Claude Code