Skip to content

fix(spec): the flows translation group goes live — the flow runner reads flows.FLOW.label - #21859

Merged
objectstack-fleet[bot] merged 9 commits into
mainfrom
claude/issue-20318-flow-label-live
Oct 5, 2026
Merged

objectstack-fleet[bot] merged 9 commits into
mainfrom
claude/issue-20318-flow-label-live

Conversation

@objectstack-fleet

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

Copy link
Copy Markdown
Contributor

Fixes #20318
Clause-②: no

Stage 3 of the card: the objectstack half that follows objectui's new reader. Three things land together. The flows translation group goes live in 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, per domain:cli's ruling A on #6024. Every check measured here is green, check:i18n-coverage included, and every example sits at its committed baseline.

The reader, measured at the pin

.objectui-sha on main is 0abd4f9f8769. git merge-base --is-ancestor 39a3e91fad 0abd4f9f8769 exits 0, so the pin carries objectui PR 11636. A positive answer proves itself even on a shallow clone. Read at 0abd4f9f8769, read-only:

  • packages/app-shell/src/views/FlowRunner.tsx:268-275 displayFlowLabel returns translateFlow({ name: flowName, label: flowLabel }, bundle, { locale: language }).label || flowName. That is the active language's flows.FLOW.label, then the served flowLabel, then the API name.
  • :377 computes shownFlowLabel. :541 draws it in the dialog header, above the step's DialogTitle.
  • :441 hands displayFlowLabel(...) to the flowRunner.completed completion toast. The toast shows only when the flow authored no successMessage.
  • :211-229 activeFlowsBundle reads the active language's flows group from the i18next translation resource tree. The screens overlay uses the same read.
  • translateFlow is imported from @objectstack/spec/system (:163).
  • The served label comes from packages/app-shell/src/utils/flowResponse.ts:234-235 (servedFlowLabel, on the trigger and resume 200 bodies).
  • The three hosts open the runner with that label: hooks/useConsoleActionRuntime.tsx:653, views/RecordDetailView.tsx:1118, and apps/console/src/pages/developer/FlowRunsPage.tsx:310.
  • The reader grep from domain:cli's ruling was re-run at the pin. git grep -E "translateFlow\b|displayFlowLabel" over packages/, excluding tests and stories, still returns 8 hits: 7 in FlowRunner.tsx and 1 comment in metadata-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.json
    • flows.children.label: planned to live, with verifiedAt 2026-10-05 and evidenceScope: cross-repo. The evidence uses file#symbol anchors, with each objectui pointer prefixed objectui @0abd4f9f8: as the ledger README prescribes. The producer names the engine's AutomationResult.flowLabel copy, the objectui lift and hosts, and the bundle delivery chain the screens row already cites.
    • The flows container: both children are now live, so it is live too, with its own evidence. It drops authorWarn and authorHint, because a live row refuses them.
    • The screens row's note gains one dated line saying that its "NOT flipped with it" clause is superseded.
  • packages/spec/liveness/state-counts/translation.md: regenerated with gen:liveness-counts. translation is now 26 live · 0 planned, up from 25 · 1.
  • content/docs/ui/translations.mdx: the boundary bullet now says what is true.
    • Both halves are applied: screens through the screen copy, and label through the runner header and the toast.
    • Three strings stay outside the group: the screen description, an authored successMessage, and the runner chrome.
    • The tooling asks for the keys, and asks for a flow's own label only when the flow has a screen node.
    • The "the day the runner lands" sentence is gone.
  • packages/spec/src/system/translation.zod.ts: the flows TSDoc said "The flow's own label is 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 their flows copy in every supported locale. That covers crm_convert_lead_wizard (zh-CN, 4 strings), showcase_reassign_wizard (zh-CN, 3) and quick_add_task (zh-CN and ja-JP, 7 each).
  • Changesets, measured with npm pack --dry-run:
    • .changeset/20318-flows-translation-live.md is an @objectstack/spec patch. liveness/translation.json, liveness/state-counts/translation.md and src/system/translation.zod.ts all ship, and the control dist/index.js ships too.
    • .changeset/20318-cli-flow-label-demand.md is an @objectstack/cli patch. dist/utils/i18n-extract.js ships (597 files packed; the control package.json is present), and the built file carries the predicate.
    • content/docs and examples/ publish nothing.

The flow label is demanded only for a flow the runner can open (domain:cli ruling A)

  • packages/cli/src/utils/i18n-extract.ts: one predicate in walkScreenFlows. The flows.FLOW.label entry is emitted only when collectFlowNodesDeep, the node universe the screens walk already reads at any depth, yields a screen node. 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.
    • The predicate's comment names FlowRunner as 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.
  • Pins in packages/cli/test/i18n-flow-screen-coverage.test.ts (ruling condition (a)):
    • a scheduled flow with a region and no screen node emits no flows.FLOW.label entry;
    • a flow whose only screen node sits two regions deep (a loop body, then a parallel branch) still emits it;
    • in one tree holding both flows, the coverage gate asks for the screen flow's label and not the other's.
    • The existing "reads the region TABLE" test now expects [] for its flow, whose only screen-shaped object sits in an http payload, 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.
  • Control legs, each run through scripts/ablation-replace.mjs and proved by "ok mutation landed" and "ok restored: blob == HEAD (521163a07d87) and git diff HEAD is empty". The test imports ../src, so no build is in the path.
    • The predicate removed (the label pushed unconditionally) reads Tests 3 failed | 21 passed (24): the no-screen pin, the one-tree pin and the region-table test.
    • The predicate over the flat top-level flow.nodes instead of the deep universe reads Tests 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:cli and domain:devx. No pin was deleted.

  • packages/cli/test/i18n-flow-liveness-gate.test.ts: its real-ledger pins now read the post-flip ledger.
    • The census pin: authorWarnedTranslationGroups() equals [], still an equality.
    • Its "can say no" twin gets its positive from the same reader, authorWarnedProperties('object') naming externalSharingModel.
    • The walk with the ledger's own set equals the ungated walk, and flows is among its roots.
    • The untranslated wizard draws exactly five i18n/missing-flow keys, and the translated one draws none.
    • The collision pin: omitting the keys draws demand and no warning, and authoring them draws neither. A witness in the same call raises the one warned object row.
    • The skeleton scaffolds flows.
    • The injected-set pin "gates whatever the ledger names" still holds the gate for a warned group.
  • packages/lint/src/lint-liveness-properties.test.ts:
    • The locale-keyed walk pins and the runtime-TranslationItem anti-fixture now read a planted synthetic planned + authorWarn group row. They go through lintLivenessPropertiesFromLedgerDir, the seam the synthetic field ledger already uses.
    • The real-ledger 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-authorHint wording.
    • The two silence pins now author flows too.
    • The authorWarnedProperties census reads [] for translation, with the object positive.
    • The anti-drift pin asks over every top-level row the translation ledger governs, and over the object type's non-empty warned set.
  • Comments only:
    • In packages/cli/src/utils/i18n-extract.ts, the extract-surface header lists inlineHelpText and says why flows is walked, and the authorWarnedTranslationGroups docblock says the set is empty today.
    • The header of packages/cli/test/i18n-flow-screen-coverage.test.ts.
    • translateFlow's docblock in packages/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 authorWarn switched on, and what the predicate takes back

With the ledger flipped and no predicate, os lint ... --json reported 56 new i18n/missing-flow findings across the examples:

example findings from screen flows label of a flow with no screen node
app-crm 4 4 0
app-showcase 32 3 29
app-todo (zh-CN + ja-JP) 20 14 6

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-coverage now 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.json is untouched, and none of the 35 inert strings is authored. check:i18n stays green.

Tests and gates (at bac6651dd7, every package built with declarations)

  • The build was turbo run build --filter=!@objectstack/docs (72 tasks, exit 0).
  • CLI unit tier: Test Files 259 passed (259), Tests 3786 passed (3786). That is 3783 plus the three new predicate pins. The packed-.d.ts pins are included.
  • Full @objectstack/lint suite: Tests 5629 passed (5629).
  • @objectstack/cli typecheck exit 0, test layer included ("held in test-typecheck-debt.json", unchanged).
  • The re-pinned files: the CLI screen-coverage file reads Tests 24 passed (24) at HEAD. lint lint-liveness-properties is unchanged since dfa18e7699, where it read Tests 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 empty git diff HEAD) read CLI 6 failed | 23 passed and lint 5 failed | 92 passed, while the planted pins stayed green.
  • check:liveness exit 0. check:i18n-coverage exit 0, as above.
  • node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 115. The --ran reconciliation 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 (still 0abd4f9f8769; its last change is 8832655af2). 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

  • The flip, the re-pins and the predicate are one commit set on purpose. Each re-pinned file pins the real ledger state, and the predicate is what lets the flip's demand side match its reader, so any part landed alone is red.
  • Ruling condition (c) is still live until this lands: if .objectui-sha moves 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

claude added 4 commits October 5, 2026 08:16
…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>
…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>
@github-actions

github-actions Bot commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

2 anchor(s) derived from 2 changed package(s); no hand-written page names any of them. ⚠️ 3 changed file(s) yielded no anchor (packages/spec/liveness/state-counts/translation.md, packages/spec/liveness/translation.json, packages/spec/src/system/i18n-resolver.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

What this run could not see
  • 3 changed file(s) yielded no anchor (packages/spec/liveness/state-counts/translation.md, packages/spec/liveness/translation.json, packages/spec/src/system/i18n-resolver.ts) — 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 — 144 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 9f9510f25e6aa65aa61ce3effb42706fabcab92e → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 6c74cd8423a43ec5f6192763c2eae4e048dead6a — the merge of head bac6651dd7eafe818d99078ed17d8f595691b16c into base 9f9510f25e6aa65aa61ce3effb42706fabcab92e, 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 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

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

…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>
@github-actions github-actions Bot added the tests label Oct 5, 2026
claude added 2 commits October 5, 2026 09:49
… 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>
@github-actions github-actions Bot added size/l and removed size/m labels Oct 5, 2026
claude added 2 commits October 5, 2026 10:52
…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>
@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 5, 2026 11:46
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 5, 2026 11:46
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 5, 2026
Merged via the queue into main with commit aead296 Oct 5, 2026
44 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-20318-flow-label-live branch October 5, 2026 12:27
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/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

i18n: the flow launcher and runner header read translation.flows.<flow>.label (1 key)

2 participants