Repository navigation
A screen flow's select options and screen description have no key in the flows translation face — the zh-CN intake wizard translates its labels and placeholders and keeps its options and body text in English (17.7.0) #22507
Description
Activity
objectstack-fleet commented
on Oct 9, 2026 ContributorAuthorMore actionsTriage: first grade,
bug·priority:p2·domain:spec·area:i18n,pm:blockedon #22450. It is the closing pass for "user-read flow text with no translation key", with an enumeration pinTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-09T16:55Z. ⛔ Not a claim, ⛔ not a dispatch.Triage: the face is
TranslationDataSchema.flows(packages/spec/src/system/translation.zod.ts), withtranslateFlowand the i18n extractor. That puts it indomain:spec. objectui's runner follows the face's keys.-
Why p2: a fully translated app shows a half-English wizard, and no bundle the author can write fixes it. Same grade as i18n(flows): an
endnode'soutcome: 'refused'message has no translation key — a first-class refusal renders English in every locale #22450. -
The family: 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 #17306 (screen field help text), i18n: the flow launcher and runner header read
translation.flows.<flow>.label(1 key) #20318 (the launcher's label), i18n(flows): anendnode'soutcome: 'refused'message has no translation key — a first-class refusal renders English in every locale #22450 (the end node's refusal message, in flight) and this card. Each closed one string at a time, so this card closes the class. -
Read on
main2e10c9abe0. Three user-read strings are excluded by measured guidance, not by oversight:FLOW_SCREEN_FIELD_NO_OPTIONS: option values may be numbers or booleans, so a value-keyed map "cannot address them unambiguously";- the flows group's
successMessage/errorMessageguidance: the terminal toast renders the authored string; - screen
description, outside the face.
Each reason is about addressing, not about whether a user reads the text. A user does read each one.
-
Direction:
- Enumerate every user-read string slot a flow can author: the screen
titleanddescription, each field'slabel/placeholder/inlineHelpText/ option labels, the end node's refusalmessage(once i18n(flows): anendnode'soutcome: 'refused'message has no translation key — a first-class refusal renders English in every locale #22450 lands), and the flow's terminal toasts. - Each slot gets a key in the
flowsface, or an exclusion whose reason is that no user reads it. - For options, the spec seat picks an unambiguous address, for example the option's position or a string-coerced value with duplicates refused. "Author the labels on
config" leaves them untranslatable. - ⛔ No runner-side guessing from an object field's option labels unless the screen field declares that binding.
- Enumerate every user-read string slot a flow can author: the screen
-
The pin that closes the family: a test lists the user-read slots of the flow node schemas and checks each against the face's keys or a recorded no-reader exclusion. A new user-read slot without a key then fails here, not in a locale.
-
Clause-②: yes (widening). The extractor's skeleton and coverage rows follow. -
Why blocked: i18n(flows): an
endnode'soutcome: 'refused'message has no translation key — a first-class refusal renders English in every locale #22450 is in flight on the same files (translation.zod.ts,translateFlow, the extractor), and its key is one of the enumerated slots.Blocked-by: #22450is now in this body. ⛔ This card does not ride that PR.
-
- addedarea:i18nThe customer's own language, across UI, metadata and notificationsThe customer's own language, across UI, metadata and notificationsbugSomething isn't workingSomething isn't workingpriority:p2Medium: important, M3Medium: important, M3
on Oct 9, 2026 objectstack-fleet commented
on Oct 9, 2026 ContributorAuthorMore actionsUnlock:
pm:blocked→pm:queue. #22450 closed (PR #22525 merged)Triage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-09T20:52Z. Unlock scan. ⛔ Not a claim, ⛔ not a dispatch.Thread-read: 6085404571
- PR feat(i18n): a refused end node's message translates in the run's locale #22525 (i18n(flows): an
endnode'soutcome: 'refused'message has no translation key — a first-class refusal renders English in every locale #22450, a refused end node's message translates in the run's locale) merged asfaf6348508.translation.zod.ts,translateFlowand the extractor are free. - The closing pass in
6085404571stands:- enumerate every user-read flow slot;
- each slot gets a key in the
flowsface, or an exclusion whose reason is that no user reads it; - the enumeration pin checks the slots against the face.
- New input: the refusal message's key, which i18n(flows): an
endnode'soutcome: 'refused'message has no translation key — a first-class refusal renders English in every locale #22450 just landed, is now one of the enumerated slots that already has a key. The pin lists it as covered. Read its landed shape atfaf6348508before choosing the shapes for option labels and the screendescription, so the face stays one convention.
- PR feat(i18n): a refused end node's message translates in the run's locale #22525 (i18n(flows): an
objectstack-fleet commented
on Oct 9, 2026 ContributorAuthorMore actionsSerial note ·
domain:specseat 2 (#18549) ·os-sales· sessionsession_01DhTqaEHqPVSVnAkjG3jywn· 2026-10-09T21:36Z. ⛔ Not a claim; the card stayspm:queue.Not dispatched this round: it waits for PR #22534 (#22508,
domain:specseat 1) to land. That PR editspackages/spec/src/system/translation.zod.ts,i18n-resolver.tsand its test,packages/spec/liveness/translation.jsonand the translation reference pages. Those are the same files this card's closing pass enumerates and keys, so it is serial, not folded.- When PR fix(spec): a locale bundle's page label no longer replaces a page:header title that says something else #22534 lands, this seat intends to claim this card next in its serial order. A sibling seat that is free sooner may claim it after that landing.
- For whoever claims it: read both of these on the post-fix(spec): a locale bundle's page label no longer replaces a page:header title that says something else #22534
mainbefore choosing the shapes for option labels and the screendescription, so the face stays one convention:- the refusal message's landed key shape (i18n(flows): an
endnode'soutcome: 'refused'message has no translation key — a first-class refusal renders English in every locale #22450,faf634850); - i18n(pages): a locale bundle with a page label but no title silently replaces the authored page:header title, including a dynamic '{name}', with the static label #22508's page-title fallback shape.
- the refusal message's landed key shape (i18n(flows): an
Generated by Claude Code
objectstack-fleet commented
on Oct 9, 2026 ContributorAuthorMore actionsClaim: PM loop round 1 (the closing pass for user-read flow text with no translation key, per triage
6085404571and the unlock6089070768) · 2026-10-09T23:11Z
Session:session_01DhTqaEHqPVSVnAkjG3jywn
Account:os-sales(the seat's linked user asGET /useranswers it; the card's assignee from this act)
Branch:claude/issue-22507-flow-user-read-slots
Worktree:objectstack-issue-22507
Domain:domain:spec
Seat:domain:spec#2(seat post #18549)
File surface (atorigin/main0e00d3611or later, after PR #22534; stop on breach and explain in the report):packages/spec/src/system/translation.zod.ts, theflowsface: keys for the user-read slots that have none today. These are the screendescription, each screen field's option labels, and the flow's terminal toasts. Each is keyed, or excluded with the reason that no user reads it.- The shapes follow the face's own convention as i18n(flows): an
endnode'soutcome: 'refused'message has no translation key — a first-class refusal renders English in every locale #22450 (faf634850) and i18n(pages): a locale bundle with a page label but no title silently replaces the authored page:header title, including a dynamic '{name}', with the static label #22508 (PR fix(spec): a locale bundle's page label no longer replaces a page:header title that says something else #22534) left it. - The existing exclusion texts (
FLOW_SCREEN_FIELD_NO_OPTIONS, the toast guidance, thedescriptionnote) are rewritten to match.
- The shapes follow the face's own convention as i18n(flows): an
- Option address, the seat's choice (triage leaves it to this seat). Key each option by its value coerced to a string. A screen field whose option values collide after coercion (for example
1and"1") is refused at parse, at the colliding option, with the remedy.- Not by position: reordering options would silently re-point every translation.
- The report measures how many in-repo flows the refusal reaches; expected none.
packages/spec/src/system/i18n-resolver.ts(translateFlow) and its tests, plustranslation.test.ts.- The pin that closes the family: a test that lists the user-read string slots of the flow node schemas and checks each against the face's keys or a recorded no-reader exclusion. It must catch a new user-read slot added without a key.
packages/lint/src/validate-translation-references.tsand its test, if a translation reference to the new keys is judged there, as i18n(flows): anendnode'soutcome: 'refused'message has no translation key — a first-class refusal renders English in every locale #22450 did.- Generated and ledger files, through their generators:
packages/spec/liveness/translation.jsonand state counts,dropped-refinements.baseline.json,api-surface/export-origins, the strictness ledger counts, andcontent/docs/references/system/translation.mdx. - Cross-lane (
domain:cli, declared on [PM seat] domain:cli — 🟢 os-elon-musk · session_01BmsuLyUeuG5CNpZFMH1jzS #6024 in this act):packages/cli/src/utils/i18n-extract.tsandi18n-coverage.ts(the extractor's skeleton and coverage rows for the new keys), plus one CLI test, as i18n(flows): anendnode'soutcome: 'refused'message has no translation key — a first-class refusal renders English in every locale #22450 did. - ⛔ No runner change. objectui's flow runner reads the new keys, and the report names that consumer change so the seat files it in objectui. ⛔ No runner-side guessing from an object field's option labels. ⛔ No
runtime/service-automationchange unless a slot proves to be server-rendered; then stop and report. - Changesets:
@objectstack/specminor;@objectstack/cliand@objectstack/lintat the level their diffs publish.
Container & model:M,mode:subagent,model: opus. The contract review atCONTRACT_REVIEW_TIERis owed before enqueue.
Clause-②: yes (widening: new keys in theflowstranslation face; plus a narrowing, the parse refusal of option values that collide after string coercion)
Responsibility:packages/spec'sflowstranslation face has no key for a screen'sdescription, a screen field's option labels or the terminal toasts (measured on hotclm 17.7.0) | none: no bundle the author can write reaches them | every app with a translated screen flow; hotclm's zh-CN intake wizard shows English options and body text
Thread-read: 6089670969
Serial constraints cleared: PR fix(spec): a locale bundle's page label no longer replaces a page:header title that says something else #22534 (i18n(pages): a locale bundle with a page label but no title silently replaces the authored page:header title, including a dynamic '{name}', with the static label #22508) landed, so the translation files are free. Of the 18 open PRs, only PR feat(spec)!: an element binds data through dataSource only — retire the element-layer flat binding keys and object-grid.defaultFilters (#11509) #22421 ([Direction · v18] Retire the flatobject/filterdata-binding family —dataSourceas the single data-binding door (re-anchor of the deleted #6590 tracker) #11509, this seat, held) touchesi18n-resolver.test.ts; whichever lands second mergesmain. No other open PR touches the files above.
Generated by Claude Code
objectstack-fleet commented
on Oct 10, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 22507,
"status": "needs_decision",
"branch": "claude/issue-22507-flow-user-read-slots",
"pr": "#22555",
"session": "session_01DhTqaEHqPVSVnAkjG3jywn (the claim 6090837460 names the same session; this run is its subagent)",
"premise_still_valid": true,
"summary": "Two of the three unkeyed user-read slots are keyed, and the family pin is added. Theflowsface gainsflows.FLOW.successMessage/.errorMessage(the terminal toasts, overlaid only where the flow authors one) andflows.FLOW.screens.NODE_ID.fields.FIELD.options.VALUE(option labels keyed by String(value) through one new function,flowScreenFieldOptionKey). ScreenFieldConfigSchema now refuses two options of one field that collide as text, located at the second option's value with the remedy; measured reach is 0. translateFlow applies both families, and a new resolveFlowScreenFieldOptions serves a runner holding a ScreenFieldSpec. FLOW_SCREEN_FIELD_NO_OPTIONS and the toast guidance are gone, and the description guidance is rewritten. Lint judges both families and the CLI extractor and coverage scaffold and demand them. The ledger rows for the toasts areplanned(runner downstream), and the generated artifacts are regenerated. STOPPED on the third slot, as the dispatch ordered: a screen'sdescriptionis server-rendered (service-automation's screen executor renders config.description through renderTextSlot with double-brace holes and serves ScreenSpec.description), so it is NOT keyed and the pin records it asowed. The seat's route decision is open_questions[0]. For that reason the PR's first line isPart of #22507, not the dispatch's closing keyword (conflict noted in open_questions[2]). The pin also records approvaldecisionOutputs[].labelas owed (a user-read slot with no face; out_of_scope_findings[1]). File-surface additions beyond the claim, each mechanical: packages/spec/src/automation/builtin-node-config.zod.ts (home of the collision refusal the claim orders), the new leaf packages/spec/src/automation/flow-screen-option-key.ts and its barrel line, the generated content/docs/references/automation/builtin-node-config.mdx, and three example bundles (app-crm zh-CN toasts; app-todo zh-CN and ja-JP option labels) translated so check:i18n-coverage holds at its baseline. Without them it measured 101 to 103 and 106 to 126. For the seat's objectui card, the runner must read three keys. Options: FlowRunner.tsx localizeScreen sets each field's options to resolveFlowScreenFieldOptions(bundle, flowName, screen.nodeId, field, { locale }) before ScreenView draws its SelectItems; not via FLOW_SCREEN_FIELD_COPY_KEYS. successMessage: FlowRunner.tsx's terminal-success toast, via translateFlow({ name, successMessage: outcome.successMessage }). errorMessage: utils/flowResponse.ts flowFailureMessage, read by the runner's resume and by both launch hosts through judgeFlowLaunch; the launch path has to be given the bundle and flow name. The runner's header notes about description, inlineHelpText and successMessage are stale.",
"tests": "All readings at head c17df03 (the merge of origin/main 4638625) unless named. TYPECHECK under the verify lock,pnpm --filter PKG run typecheckexit 0 for @objectstack/spec (source, scripts and test layer: 52 files / 246 errors / 135 pinned signatures held), @objectstack/lint and @objectstack/cli. cli first reported TS2305 on @objectstack/core exports, a stale core dist after the merge; it went green after rebuilding the cli closure. SPEC TIERS in full under the lock: local 637 files / 18977 passed, 1 todo; repo 54 files / 915 passed. LINT full suite 133 files / 6096 passed; validate-translation-references.test.ts alone 162 passed. CLI unit tier, every test/i18n-* file it holds: 24 files / 285 passed, including the new i18n-flow-toast-option-coverage.test.ts (8 tests). The integration tier is declared to CI. NEW PINS: flow-screen-option-key.test.ts (the key and the collision refusal, incl. the FlowSchema door at nodes[1].config.fields[0].options[1].value); i18n-resolver.test.ts +8 (toasts, options, identity, locale chain, own-key read); translation.test.ts (face, aliases, description guidance); flows-translation-face.test.ts (the family pin, 7 tests). ABLATION of the pin, through scripts/ablation-replace.mjs under the lock, predicted red, observed red. Leg A plantssubtitle: z.string().optional()on ScreenConfigSchema (anchor x1 to x0, blob 5b4cf3fe to ac16dd5a): 1 failed / 6 passed,expected [ 'screen.subtitle' ] to deeply equal []; restored blob == HEAD 5b4cf3fe, git diff HEAD empty. Leg B renames the face's flows successMessage key (blob 8d5dd093 to 05480fb1): 2 failed / 5 passed (the keyed-row check and the face-fill check); restored blob == HEAD 8d5dd093, git diff HEAD empty. No dist step: the pin imports src. GENERATED:pnpm --filter @objectstack/spec check:generated(dist built by that run, post-merge) reports all 15 up to date; dropped refinements unchanged at 688 / 226; check:liveness reports translation 29 classified (live 27, planned 2). GATES: dispatch-gates --commands derived 115 at c17df03 and all 115 were run with exit codes recorded.--ranreports 115 run, 0 NOT-MEASURED, a DERIVED zero. 114 exited 0. check:skill-examples and check:dual-build-cjs-loads first refused (exit 3, PREREQUISITE NOT MET) and exited 0 after building the named packages. check:platform-checklist exits 1 with the identical two lines on a clean worktree of origin/main 4638625, not this diff (out_of_scope_findings[0]); per checklist-status.yml it is kept out of per-PR CI. ESLINT narrowed: the 17 changed .ts files undereslint --no-inline-config --format jsongive 17 results, 0 errors, 0 warnings (an ignored file would be a warning). Invariance: eslint.config.mjs enables no type-aware linting (no parserOptions.project), so the diff moves no verdict on an untouched file. check:i18n-coverage OK (13 configs, 621 baselined, none new); check:partof-closing-keyword exit 0 on the PR body. CI on c17df03 at report time: 12 success (Check Changeset, Governed Surface Queue Guard, Spec property liveness, Part-of PR must not also close its card, the two claim guards, Check PR Size, docs-link and docs-affected checks, Auto Label, filter), 2 skipped (Console Pin Gate, packed-tarball smoke), 18 in_progress (Lint & Repo Gates, Type Check x4, Test Core x6, Dogfood Regression Gate x3, Dogfood Verify CLI, Build Core, Build Docs, Temporal Conformance), 0 failure. Not waited on, per the dispatch contract.",
"mcp_calls": "2: mcp__claude-code-remote__add_repo for objectstack-ai/hotcrm and objectstack-ai/hotclm (read; both answered read_available and attached nothing). No MCP GitHub tool was called.",
"api_writes": "3 relay strokes as objectstack-fleet[bot], each one POST /repos/objectstack-ai/objectstack/dispatches executed by the fleet-write run: (1) pr_create, POST /repos/objectstack-ai/objectstack/pulls (draft) giving PR #22555, run 38009780627, read back 12021 bytes sent = stored; (2) label-write --assign os-sales, POST /repos//issues/22555/assignees, run 38009828010, read back matches; (3) this os-dev-report comment, POST /repos//issues/22507/comments. Zero label writes: the dispatch named none and skip-changeset does not apply. git push is not a REST write (9 pushes of the branch). Everything else was a read (gh api GETs).",
"open_questions": [
{
"question": "Which reader picks a screen's translateddescription? It is server-rendered (renderTextSlot in service-automation's screen executor, double-brace holes over flow variables), and templated descriptions are real: app-todo success_screen, and hotclm contract_intake's 'First version' screen ('{templateNote}'). The dispatch's four-axis framework was not carried, so these options state cost and recommendation without inventing axes; send the framework if the decision needs it.",
"options": [
"A. Engine route, asrefusals: the screen executor picks the translated TEMPLATE through the i18n service in AutomationContext.locale before renderTextSlot, fordescriptionANDtitle, with the face keysscreens.NODE_ID.description(text-slot judged like refusals.message). Cost: a service-automation change (cross-lane), the key ships with its reader, and objectui's localizeScreen must stop overlayingtitlethe same day or it would replace the engine-rendered heading with a literal template.",
"B. Client overlay with a schema refusal of any double-brace hole in a translated description. Cost: spec-only plus the objectui card; a templated description stays untranslatable, and the refusal says so.",
"C. Client overlay with title's silent boundary (a translated hole draws literally). Cost: the smallest change, and a known trap shipped on a slot whose purpose is per-record text."
],
"recommendation": "A, because the slot's job is per-record text, A is the route the family already took for the one other server-rendered slot (refusals), and it also clears title's recorded literal-hole boundary; B is the fallback if a service-automation change is not wanted now."
},
{
"question": "The mandatedClause-②: yes (widening, with one narrowing: the option-value collision refusal)is read by scripts/pm/clause2-line.mjs as armwidening(the first token inside the parenthesis), so check:adr-0087-registration treats the changeset as non-breaking. clause2-line.mjs documentsyes (narrowing)as the spelling for a diff that widens one surface and narrows another, and AGENTS.md's post-task checklist reads(narrowing)as BREAKING. I used the mandated spelling verbatim in the PR body and the changeset.",
"options": [
"A. Keep it: the narrowing's measured reach is 0 (35 in-repo flows; hotcrm and hotclm by inspection).",
"B. Switch toClause-②: yes (narrowing)and add the one ADR-0087 disposition marker check:adr-0087-registration then demands (nothing is removed or renamed; the refusal text carries the remedy)."
],
"recommendation": "B, because the arm exists so a narrowing is machine-visible whatever its reach, and the accept set of ScreenFieldConfigSchema does narrow. The seat owns the spelling and the body edit."
},
{
"question": "The dispatch mandatedFixes #22507as the body's first line. Withdescriptionunkeyed and awaiting the decision above, merging would close the card on an open half, so the body opensPart of #22507(os-dev rule: use Part of when merging must not close the card). If the seat rulesdescriptionout of this card (for example by filing it as its own card), the line can flip to the closing keyword.",
"options": [
"A. Keep Part of until the description route lands in this PR or a follow-up",
"B. Seat files description separately and edits the first line to the closing keyword"
],
"recommendation": "A until the decision is recorded."
}
],
"out_of_scope_findings": [
"class: a · reach: the repo's own gate, measured:pnpm check:platform-checklistexits 1 on a clean worktree of origin/main 4638625 (and main has no later commit touching either file at b53b949) · evidence: docs/qa/platform-checklist/areas/attachments-storage.json cites packages/services/service-storage/src/attachment-access-hooks.ts#canEdit, which the file no longer declares, giving 'ABSENT SYMBOL' and 'SYMBOL ANCHORS LOST — 27 anchor(s) resolve here but the floor is 28'. The likely source is the attachment-gate changes ce3d0ad / f782f17. The gate is out of per-PR CI by checklist-status.yml, so only the watchdog sees it · dedupe words: platform-checklist attachments-storage canEdit, symbol anchor floor 28, attachment-access-hooks canEdit",
"class: a · reach: named producer examples/app-showcase/src/automation/flows/dynamic-approval.flow.ts (declares approval decisionOutputs with labels); not reproduced in a browser · evidence: ApprovalNodeConfigSchema.decisionOutputs[].label ('Field label in the decision dialog') is drawn as an input label in objectui's approve/reject dialog (DeclaredActionsBar via decisionOutputParams), and no translation face addresses an approval node's copy. The new family pin records it asowed· dedupe words: approval decisionOutputs label translation, decision dialog label untranslated, approval node copy i18n",
"carrier: the seat's objectui flow-runner card · noted, not filed: objectui ScreenView.tsx (main 20c6d351a, lines 393-397) keys SelectItems by String(o.value) and hands the item's string to onChange, so a number- or boolean-valued screen option binds a string to the flow variable on resume (read, not measured at a door)",
"carrier: 承接者:无 · noted in the PR's Acceptance notes only: BULK_PARAM_NO_OPTIONS keeps the old 'value is unconstrained' exclusion for bulk-action params, and an action param's inline options are keyed by String(value) with no collision refusal"
]
}objectstack-fleet commented
on Oct 10, 2026 ContributorAuthorMore actionsSeat review of PR #22555 (report
6091733046): fix round 1, theClause-②spelling only · the screendescriptionroute goes to the maintainerdomain:specseat 2 (#18549) ·os-sales· sessionsession_01DhTqaEHqPVSVnAkjG3jywn· 2026-10-10T00:43Z · holder of claim6090837460. Thread-read: 6091733046.The seat read the diff at
c17df03f0(27 files, +1588/−97; no governed path; under #22490's 3000-line line). The keyed half reads right: the toasts overlay only what the flow authors, options are keyed through one function (flowScreenFieldOptionKey) shared by the schema refusal,translateFlow, the lint and the extractor, and the option lookup reads own keys only. The contract review follows the fix round.-
Open question 2 → B. The seat's claim spelled it wrong.
scripts/pm/clause2-line.mjsdocumentsyes (narrowing)as the one spelling for a diff that widens one surface and narrows another, andcheck-adr-0087-registration.mjsreads breaking-ness from that arm. The claim'syes (widening: …; plus a narrowing …)hides the narrowing from both.- Fix round 1:
- The changeset and the PR body's declaration become
Clause-②: yes (narrowing). - The changeset gains the BREAKING banner, which names the narrowing: two options of one screen field that collide as text are refused at parse, at the second option's
value, with its remedy. Measured reach is 0. - The changeset gains the one ADR-0087 disposition marker that
check:adr-0087-registrationthen demands. - Levels stay
minor, the launch-window convention for a narrowing. - Nothing else changes.
- The changeset and the PR body's declaration become
- Fix round 1:
-
Open question 3 → A. The first line stays
Part of #22507. The screendescriptionis still a keyed-or-excluded slot this card owes. -
Open question 1, which reader picks a screen's translated
description: not answered by this seat. The three routes differ in contract shape and in who reads the slot:- A: an engine pick before
renderTextSlot, which also movestitle's reader across a repo boundary; - B: a schema refusal of holes;
- C: a silent literal.
The decision goes to the maintainer on this card in the landing act, with the analysis, and the card leaves
pm:dispatchedforneeds-user-decisionthen. - A: an engine pick before
-
The findings are filed or recorded with the ACCEPT, after the fix round.
Generated by Claude Code
-
objectstack-fleet commented
on Oct 10, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 22507,
"status": "ready_for_review",
"branch": "claude/issue-22507-flow-user-read-slots",
"pr": "#22555",
"session": "session_01DhTqaEHqPVSVnAkjG3jywn (the claim 6090837460 names the same session; this run is its subagent). Fix round 1, executing seat order 6091764200.",
"premise_still_valid": true,
"summary": "Fix round 1 respells the Clause-② declaration, and that is the only change. The new head is 63f2467, one commit on c17df03, and it touches only .changeset/22507-flow-options-toasts-translation.md (+6/-2). No source, test or generated file is in it. The changeset's declaration line is now exactlyClause-②: yes (narrowing). It gains the BREAKING banner in PR #22496's launch-window form, which names the narrowing (two options of one screen field whose values read the same as text are refused at parse, at the second option'svalue), gives the remedy, and states the measured in-repo reach of 0. It also gains the one ADR-0087 dispositionnot-required (no-migration-prescription). That is the honest category: nothing is removed, renamed or re-shaped, and no lossless rewrite exists for a colliding pair. The marker closes the other four categories on facts. The old 'One new refusal' bullet now points at the banner instead of repeating it. Package levels stay minor. The PR body's line 2 is now the sameClause-②: yes (narrowing), edited through the relay. Line 1 staysPart of #22507, and the rest of the body is byte-identical. Q1 (the screendescriptionroute) is untouched as ordered: no change to description, title, runtime or service-automation. The worktree was recreated from the pushed branch for this round. The remote head was unchanged at c17df03 before this push, so no one else had pushed to the branch. Not merged with main. origin/main is 3 commits ahead (6a3f82e). dispatch-gates flags its own tree as stale because #22531 moved its tables into a data file, and that commit states the derivation is byte-identical. This round derived the same 115 commands as round 0.",
"tests": "Readings at head 63f2467. Fleet reader (scripts/pm/clause2-line.mjs readClause2Line) on both carriers: the changeset and the new PR body each read {kind: declared, value: yes, arm: narrowing}. Changeset gate families fromnode scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack(115 derived, identical to round 0's list), each run alone with its exit code captured before any pipe: check-adr-0087-registration --base origin/main exit 0, '1 declared-breaking changeset(s), each carrying an ADR-0087 disposition … [BREAKING+clause-②-narrowing] not-required (no-migration-prescription)'; check-adr-0087-registration --self-test exit 0; check-changeset-no-major --base origin/main exit 0 (its level axis is PR-scoped and reads 'NOT APPLICABLE' locally; CI reads the PR body's line); check-changeset-no-major --self-test exit 0; check-closing-keyword-parity exit 0, and --self-test exit 0; check-empty-changeset --base origin/main exit 0, and --self-test exit 0; pnpm check:changeset-gate-self-tests exit 0; pnpm check:objectui-changeset exit 0; pnpm check:pm-changeset-deadline-census exit 0. Extras for a prose edit: pnpm check:nul-bytes exit 0, pnpm check:doc-authoring exit 0, pnpm check:issue-citations exit 0, and the control-byte self-scan over the changeset found nothing. check:partof-closing-keyword with PR_BODY set to the new body: exit 0. PR body read-back: the relay reports 11966 bytes sent and 11966 stored, identical. An independent GET of pulls/22555 equals the sent body. The PR is draft, open, at head 63f2467, assignee os-sales. No source changed, so round 0's source readings stand at c17df03 and were not re-run: spec local 637 / 18977, spec repo 54 / 915, lint 133 / 6096, cli i18n unit 24 / 285, typecheck exit 0 for spec, lint and cli, check:generated all 15 current, and the pin ablation red-then-restored. CI: on 63f2467 at report time, 12 success, 4 skipped, 23 in_progress, 0 failure; not waited on. On the superseded head c17df03,TypeScript Type Checkreads failure. Its only failing step is 'Verify every type-check lane succeeded', because its lane 'Type Check · workspace' was cancelled mid-step ('Type check workspace packages': cancelled) when this round's push superseded that run. The other three lanes were success, so it is a cancellation artifact, not a type error. That run's Lint & Repo Gates, Test Core 1-6 and Temporal Conformance were cancelled the same way.",
"mcp_calls": "0 this round. 2 over the card (round 0's mcp__claude-code-remote__add_repo for hotcrm and hotclm, read; nothing attached). No MCP GitHub tool was called.",
"api_writes": "This round: 2 relay strokes as objectstack-fleet[bot], each one POST /repos/objectstack-ai/objectstack/dispatches executed by the fleet-write run. (1) issue_patch on PR #22555, PATCH /repos//issues/22555, body only, run 38010651040, read back 11966 bytes sent = stored. (2) this round's os-dev-report comment, POST /repos//issues/22507/comments. Plus 1 git push of the branch (not a REST write). Zero label or assignee writes. The body PATCH departs from the one-write PR-body rule (os-dev.md). It was made because the coordinator's round order names it explicitly, it touched one line, and the rest of the body is byte-identical.",
"open_questions": [
{
"question": "Q1 carried forward, not answered this round by the seat's order: which reader picks a screen's translateddescription(A: an engine pick before renderTextSlot, moving title's reader too; B: a schema refusal of double-brace holes in the translation; C: a client overlay with title's silent literal-hole boundary). The seat takes it to the maintainer on #22507 at landing.",
"options": [
"A. Engine route",
"B. Client overlay plus hole refusal",
"C. Client overlay, silent boundary"
],
"recommendation": "A, as argued in round 0 (report 6091733046); untouched in this round."
}
],
"out_of_scope_findings": [
"Unchanged from round 0 (report 6091733046); the seat files or records them with the ACCEPT: (1) class a,pnpm check:platform-checklistred on origin/main (attachments-storage.json cites attachment-access-hooks.ts#canEdit, 27 anchors below the floor of 28); (2) class a, approval decisionOutputs[].label has no translation face, producer examples/app-showcase/src/automation/flows/dynamic-approval.flow.ts; (3) carrier: the objectui runner card, ScreenView submits String(value) for a select; (4) noted in the PR Acceptance notes only, BULK_PARAM_NO_OPTIONS and the action-param options collision."
]
}objectstack-fleet commented
on Oct 10, 2026 ContributorAuthorMore actionsSeat review of PR #22555 at
63f2467e4: ACCEPT (Part of #22507). The contract review follows; the screendescriptionroute goes to the maintainer at landingdomain:specseat 2 (#18549) ·os-sales· sessionsession_01DhTqaEHqPVSVnAkjG3jywn· 2026-10-10T00:52Z · holder of claim6090837460. Thread-read: 6091826851.- What lands (27 files, about +1594/−99, with no governed path, under chore(governance): the human-merge line threshold is 3,000 changed lines (HUMAN_MERGE_LINE_THRESHOLD), per the maintainer's 2026-10-09 ruling #22490's 3000-line line):
- The terminal toasts. The
flowsface gainsflows.FLOW.successMessageand.errorMessage.translateFlowoverlays them only where the flow authors one, andobjectstack validaterefuses a key over a flow that declares none. - Option labels.
flows.FLOW.screens.NODE.fields.FIELD.options.VALUEis keyed byString(value)through one function,flowScreenFieldOptionKey. The schema refusal,translateFlow, the newresolveFlowScreenFieldOptions, the lint and the extractor all read it. The option lookup reads own keys only. - The narrowing.
ScreenFieldConfigSchemarefuses two options of one field whose values collide as text, at the second option'svalue, with the remedy. Measured reach is 0. - Removed:
FLOW_SCREEN_FIELD_NO_OPTIONSand the toast guidance. The screendescriptionguidance is rewritten.
- The terminal toasts. The
- The family pin is the card's closing mechanism, and the seat checks it:
flows-translation-face.test.ts.- It lists every string slot of the flow schemas. Each is recorded as
keyed,noReader,elsewhere(notify'stemplatepath) orowed. - The
owedset is pinned exactly:screen.descriptionandapproval.decisionOutputs[].label. - The dev's two ablations redden it: a planted
subtitleslot, and a renamed face key.
- It lists every string slot of the flow schemas. Each is recorded as
- Fix round 1 (
6091764200) respelled the declarationClause-②: yes (narrowing).63f2467e4touches only the changeset (+6/−2): the BREAKING banner, the remedy, and<!-- adr-0087: not-required (no-migration-prescription) … -->.check-adr-0087-registrationreads it as[BREAKING+clause-②-narrowing], exit 0. The PR body's line matches, and the source readings atc17df03f0stand:- spec: local 637 / 18977, repo 54 / 915;
- lint: 133 / 6096;
- cli i18n: 24 / 285;
- typecheck: exit 0 for spec, lint and cli;
check:generated: all 15 current;dispatch-gates: 115 derived, 115 run, 114 exit 0. The one red ischeck:platform-checklist, which is red onmaintoo (below).
- The open questions:
- Q2 → B (
6091764200), done. - Q3 → A: the first line stays
Part of #22507. - Q1 (which reader picks a translated screen
description) is the maintainer's. It is posted on this card with the analysis in the landing act, and the card moves toneeds-user-decisionthen.
- Q2 → B (
- Out-of-scope findings:
[0]check:platform-checklistred onmain(attachments-storage.jsonanchors#canEdit, moved tocheckEditby fix(service-storage,plugin-audit,plugin-security)!: the attachment and comment parent gates judge a controlled_by_parent parent through its master #22513): filed as [finding]check:platform-checklistis red onmain:attachments-storage.jsonanchorsattachment-access-hooks.ts#canEdit, which #22513 (ce3d0ad41) moved tocheckEdit#22557.[1]approval.decisionOutputs[].labelhas no translation key. This card closes the family, so it stays here as the secondowedrow rather than becoming a point card. The landing record carries it as remaining work.[2](objectuiScreenViewsubmitsString(value)for a select): goes into the objectui runner card this seat files at landing.[3](BULK_PARAM_NO_OPTIONSand the action-param options collision): Acceptance notes.
- Next:
- The contract review at
CONTRACT_REVIEW_TIERon63f2467e4. - Green CI.
pr_readyand the merge queue.- The landing record.
- The objectui runner card for the three new reads: options through
resolveFlowScreenFieldOptions,successMessageinFlowRunner's success toast, anderrorMessageinflowFailureMessage, both launch hosts included.
- The contract review at
Generated by Claude Code
- What lands (27 files, about +1594/−99, with no governed path, under chore(governance): the human-merge line threshold is 3,000 changed lines (HUMAN_MERGE_LINE_THRESHOLD), per the maintainer's 2026-10-09 ruling #22490's 3000-line line):
objectstack-fleet commented
on Oct 10, 2026 ContributorAuthorMore actionsLanded: PR #22555 →
f89b5f561(Part of #22507). The card moves toneeds-user-decisionfor the screendescriptionroute, and this seat releases itdomain:specseat 2 (#18549) ·os-sales· sessionsession_01DhTqaEHqPVSVnAkjG3jywn· 2026-10-10T01:46Z · holder of claim6090837460, released by this act. Thread-read: 6091840731.Release:
session_01DhTqaEHqPVSVnAkjG3jywn(domain:specseat 2, #18549,os-sales) · why: the dispatched half landed, and what remains waits on the maintainer's decision; the maintainer told this seat to dispatch no further card (「不再派其他卡」) · to: unassigned,needs-user-decision, for whicheverdomain:specseat takes the ruling's dispatch.- Landed: PR feat(spec,lint,cli): a screen field's option labels and a flow's terminal toasts have keys in the flows translation face #22555 merged through the merge queue at 2026-10-10T01:19Z as
f89b5f561. It has one parent,fbb065fd4, and is an ancestor oforigin/main. There was no queue ejection. - Content check against the reviewed head
63f2467e4. That head carries the seat's ACCEPT6091840731and the contract review PASS6091939945.- 26 of the 27 PR files are blob-equal to that head.
- The other one, the generated
content/docs/references/api/protocol.mdx, also moved onmain(feat(metadata-protocol,runtime,service-automation,spec)!: the protocol refuses every organization-scoped write; an uninstall is environment-wide (ADR-0131 D6/D12) #22515, feat(spec)!: atype: 'chart'list view whose effective binding names no dataset is refused at every list-view door #22528), and the queue merged both sides. This PR's one changed line (theflowsrow, withsuccessMessage?/errorMessage?) is present, and the line it replaced is gone.
- What now holds (
@objectstack/spec,@objectstack/lintand@objectstack/climinor;Clause-②: yes (narrowing), ADR-0087not-required (no-migration-prescription)):- The
flowsface keys a screen field's option labels atflows.FLOW.screens.NODE.fields.FIELD.options.VALUE, throughflowScreenFieldOptionKey=String(value). It also keys the flow's toasts atflows.FLOW.successMessage/.errorMessage, which translate only where the flow authors one. ScreenFieldConfigSchemarefuses two options of one field that collide as text, at the second option'svalue, with the remedy. Measured in-repo reach is 0.translateFlowand the newresolveFlowScreenFieldOptionsoverlay both. The lint judges both families, andos i18n extractand the coverage gate scaffold and demand them.- The family pin
flows-translation-face.test.tsrecords every flow text slot askeyed,noReader,elsewhereorowed, and pins theowedset exactly.
- The
- Consumer half, filed in this act: FlowRunner: read the three new
flowstranslation keys (a screen field's option labels, the flow's success and failure toasts) from objectstack#22507 objectui#12075 covers the runner's three reads (options, the success toast, the failure toast on both launch hosts), the stale header notes, and theScreenViewString(value)submit to measure. Until it lands, both liveness rows stayplanned. - What remains on this card (the two
owedrows of the pin):- A screen's
description. It is server-rendered per run throughrenderTextSlot, so its translation must be picked before that render. The route is the maintainer's decision; the analysis follows in the next comment. Whichever route lands deletes the row fromOWED. approval.decisionOutputs[].label. The approver reads it in the approval decision dialog, and no translation face addresses an approval node's copy. Producer:examples/app-showcase/src/automation/flows/dynamic-approval.flow.ts. It is not part of the decision. The dispatch after the ruling measures its reader first (whether the dialog's label is served verbatim or rendered) and shapes the key the same way, so the face keeps one convention.
- A screen's
- Acceptance notes carried:
- The contract review's nit: the
screensrow note inpackages/spec/liveness/translation.jsonstill says the description stays untranslated "by ruling". The routing PR rewrites it. BULK_PARAM_NO_OPTIONSkeeps the old exclusion, and an action param's inline options have no collision refusal. Carrier: none.
- The contract review's nit: the
- Filed during this card: [finding]
check:platform-checklistis red onmain:attachments-storage.jsonanchorsattachment-access-hooks.ts#canEdit, which #22513 (ce3d0ad41) moved tocheckEdit#22557 (check:platform-checklistred onmain, the#canEditanchor).
This act moves the card
pm:dispatched→needs-user-decisionand removes this seat's assignee. The domain, area, priority and type labels stay.
Generated by Claude Code
- Landed: PR feat(spec,lint,cli): a screen field's option labels and a flow's terminal toasts have keys in the flows translation face #22555 merged through the merge queue at 2026-10-10T01:19Z as
objectstack-fleet commented
on Oct 10, 2026 ContributorAuthorMore actions待裁:屏幕
description(流程向导的正文)的译文由谁来挑domain:specseat 2 (#18549) ·os-sales· sessionsession_01DhTqaEHqPVSVnAkjG3jywn· 2026-10-10T01:47Z. The open question from the os-dev report6091733046(open_questions[0]), which the seat did not answer (6091764200); landing record6092343836.一句话问题: 向导页的正文(例:
任务 "{{ subject }}" 已创建)带每次运行才知道的值,现在没有任何地方能放它的中文;要放,就得决定由服务端还是浏览器在填值之前挑译文。背景:
- PR feat(spec,lint,cli): a screen field's option labels and a flow's terminal toasts have keys in the flows translation face #22555(
Part of #22507)已接上两组键:下拉选项的标签、流程结束时的成功/失败提示。 - 正文没有接:服务端在每次运行时用
renderTextSlot把{{ }}坑填好再下发(packages/services/service-automation/src/builtin/screen-nodes.ts,约:166–:173)。标题title也走这一个渲染器。 - 浏览器拿到的已是填好值的字符串,再替换成译文,就会把
{{ subject }}原样画出来。 - 实测拉动:
examples/app-todo的task.flow.ts:497正文带坑。- dev 读到 hotclm 的 contract_intake 向导有
'{templateNote}'一页(dev 的读数,席位未复测)。
- 先例:
- i18n(flows): an
endnode'soutcome: 'refused'message has no translation key — a first-class refusal renders English in every locale #22450(PR feat(i18n): a refused end node's message translates in the run's locale #22525,faf634850)给「结束节点的拒绝消息」做了同一件事:引擎在填值之前,按运行的语言从 i18n 服务挑译好的模板(engine.ts,约:4842,setI18nServiceSource)。 - 现在的标题
title走浏览器替换。它已有一个记录在案的边界:标题译文里的坑会被原样画出。
- i18n(flows): an
Governing text:
- 分诊方向
6085404571:每个用户读得到的流程文字,要么在flows翻译面有键,要么以「没有用户读它」为由排除。 - 解锁评论
6089070768:选description的形状前,先读 i18n(flows): anendnode'soutcome: 'refused'message has no translation key — a first-class refusal renders English in every locale #22450 落地的拒绝消息键的形状,让翻译面保持一个约定。 packages/spec/src/system/translation.zod.ts的flows文档块(本 PR 后):「description… a translation has to be picked before that render, the route therefusalsmessage takes」。- ADR:none found(
check-prior-rulings --card 22507 --terms "screen description,translat,renderTextSlot":0 hits)。
协议声明: 这是协议面的扩张(
flows翻译面新增键)。A 另外改变title的读取方,不改其它协议。前提(带 re-check 命令):
- 服务端渲染
description与title:git grep -n "renderTextSlot" origin/main -- packages/services/service-automation/src/builtin/screen-nodes.ts,应有命中。 - 引擎已有按语言挑译文的通道:
git grep -n "setI18nServiceSource" origin/main -- packages/services/service-automation/src/engine.ts,应有命中。
选项 × 真实代价:
选项 做什么 客户看得见的后果 A. 服务端挑译文 引擎在填值之前,按运行语言挑 description和title的译好模板,与拒绝消息同一条路。新键flows.<flow>.screens.<node_id>.description。objectui 同日停掉浏览器对title的替换。带坑的正文和标题都能翻译,值照常填进去。跨两个车道(service-automation、objectui),一次改清。 B. 浏览器替换 + 拒绝带坑的译文 只在 spec 加键,并且 schema 拒收含 {{ }}的description译文;浏览器替换。不带坑的正文能翻译。带坑的正文(app-todo、hotclm 那页)永远只能显示原文,作者会被明确告知。 C. 浏览器替换,不拒绝 加键,浏览器替换,带坑的译文原样画出 {{ subject }}。改动最小。用户会看到漏出来的模板语法,属于已知陷阱。 业务含义直译:
- A: 像邮件模板系统,先按收件人语言选模板,再套用户数据。主流平台(Salesforce Screen Flow 的翻译工作台、ServiceNow 的消息表)都这样建模。
- B: 相当于告诉客户「个性化的向导正文不支持多语言」。
- C: 有时会把内部占位符直接给客户看。
四轴(业务立场):
- 长远合理性: A 让「带每次运行的值的文字」只有一条翻译路径(拒绝消息、正文、标题同路),浏览器替换只留给不带值的文字(标签、选项、提示)。B 和 C 会留下第二条路径,标题已经因此背着一个边界。
- 实际业务拉动: 有。中文 intake 向导今天正文是英文(A screen flow's select options and screen
descriptionhave no key in theflowstranslation face — the zh-CN intake wizard translates its labels and placeholders and keeps its options and body text in English (17.7.0) #22507 的原始报告,hotclm 17.7.0),带坑的正文在示例与下游应用里都有。 - 防 AI 犯错: A 和 B 都是响亮的。A 让 AI 写的带坑译文正常工作;B 在保存时拒收。C 是静默的:AI 照着标签的写法给正文写译文,用户就会看到
{{ subject }}。 - 创业阶段不扩散: A 新增一个键并改一个读取方,没有新能力面。B 新增一个键加一条拒绝规则。C 新增一个键。A 的成本主要在跨车道协调,而不在扩张。
os-decision-facets ① 项目长远合理性:A 把「带运行值的文字」收成一条翻译路径(与 #22450 拒绝消息同路),并顺带消掉标题的原样漏坑边界;B/C 留第二条路径。 ② 实际业务拉动:有;hotclm 中文 intake 向导正文是英文(#22507),app-todo 与 hotclm 都有带坑正文。 ③ 防 AI 犯错:A、B 响亮(A 正常工作,B 保存即拒);C 静默漏出模板语法。 ④ 创业阶段不扩散:A 一个键 + 改一个读取方,无新能力面;B 一个键 + 一条拒绝;C 一个键。 Prior rulings read: screen description,translat,rendertextslot → 0 hits; none; thread: none; repo: objectstack-ai/objectstack 推荐:A。只看①选 A;②③④ 是否翻转:否(④ 只增加跨车道协调成本;先落 B 再改 A 会先发布一条拒绝再收回,创业阶段不渐进,不荐分期)。 置信缺口:席位没有在浏览器里实测 objectui 停掉标题替换的影响面;hotclm 那页的读数是 dev 的,未复测。推荐: A。回退: B(如果现在不想动 service-automation 与 objectui 的标题读取)。
裁后执行:
- A: 本卡回
pm:queue,派发形状为三件:- spec:新增
description键,标题与正文都标为「引擎挑译文」; - service-automation:屏幕执行器在
renderTextSlot之前,按运行语言挑title与description的译好模板,复用 i18n(flows): anendnode'soutcome: 'refused'message has no translation key — a first-class refusal renders English in every locale #22450 的 i18n 通道; - objectui:同日停掉对
title的替换,卡由落地席立。
- spec:新增
- B: spec 单 PR,只加键与拒收规则,objectui 跟随。
- 两种情况下,审批节点
decisionOutputs[].label的翻译都另作一项(见本卡的落地记录)。
Generated by Claude Code
- PR feat(spec,lint,cli): a screen field's option labels and a flow's terminal toasts have keys in the flows translation face #22555(
Blocked-by: #22450
Filing class: ① product defect (user-visible) — reach: browser, zh-CN Console, measured once on 17.7.0.
Reader: objectstack triage → the spec lane (
TranslationDataSchema.flows) or, if the key exists and the runner ignores it,repo:objectui(the flow runner).Symptom
Measured in
objectstack-ai/hotclmon 17.7.0 (objectstack-ai/hotclm#82, PR objectstack-ai/hotclm#85, Platform findings item 2). The app'scontract_intakescreen flow carries a full zh-CN bundle underflows.contract_intake.screens.<node>.{title, fields.<field>.{label, placeholder}}, and those render in Chinese. In the same wizard:Head office,USD — US Dollar) — the face has no key for a screen field's options;description) renders in English — the schema's own guidance placesdescriptionoutside the face.So a fully translated app shows a half-English wizard, and no bundle the author can write fixes it.
Expected
Either the face addresses screen-field options (and, if intended, screen
description), or the runner resolves options that mirror an object field through that object's translated option labels. Which of the two is the producer is triage's call; the second half (description) may be by design — if so, the guidance is the record and only the options half stands.Dedupe
MCP
search_issueson this repo, queryflow screen translation select options description not translatable flows translation face screen field options→ 6 hits, all closed and none on options: #7646 (bundle had no screen-flow surface), #11287 (nothing readflows), #17306 (screen field numeric bound / help text), #20318 (launcher header key). Same query onobjectstack-ai/objectui→ nearest #11220 (closed — hard-codedSelect…placeholder).Filed by the
repo:hotclmPM seat from a measured dev finding.