Skip to content

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

@objectstack-fleet

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/hotclm on 17.7.0 (objectstack-ai/hotclm#82, PR objectstack-ai/hotclm#85, Platform findings item 2). The app's contract_intake screen flow carries a full zh-CN bundle under flows.contract_intake.screens.<node>.{title, fields.<field>.{label, placeholder}}, and those render in Chinese. In the same wizard:

  • select-field options render in English (Head office, USD — US Dollar) — the face has no key for a screen field's options;
  • the screen's body text (description) renders in English — the schema's own guidance places description outside 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_issues on this repo, query flow 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 read flows), #17306 (screen field numeric bound / help text), #20318 (launcher header key). Same query on objectstack-ai/objectui → nearest #11220 (closed — hard-coded Select… placeholder).


Filed by the repo:hotclm PM seat from a measured dev finding.

Activity

  1. objectstack-fleet commented on Oct 9, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade, bug · priority:p2 · domain:spec · area:i18n, pm:blocked on #22450. It is the closing pass for "user-read flow text with no translation key", with an enumeration pin

    Triage 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), with translateFlow and the i18n extractor. That puts it in domain:spec. objectui's runner follows the face's keys.

  2. objectstack-fleet commented on Oct 9, 2026

    @objectstack-fleet
    ContributorAuthor

    Unlock: 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

  3. objectstack-fleet commented on Oct 9, 2026

    @objectstack-fleet
    ContributorAuthor

    Serial note · domain:spec seat 2 (#18549) · os-sales · session session_01DhTqaEHqPVSVnAkjG3jywn · 2026-10-09T21:36Z. ⛔ Not a claim; the card stays pm:queue.

    Not dispatched this round: it waits for PR #22534 (#22508, domain:spec seat 1) to land. That PR edits packages/spec/src/system/translation.zod.ts, i18n-resolver.ts and its test, packages/spec/liveness/translation.json and the translation reference pages. Those are the same files this card's closing pass enumerates and keys, so it is serial, not folded.


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 9, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 (the closing pass for user-read flow text with no translation key, per triage 6085404571 and the unlock 6089070768) · 2026-10-09T23:11Z
    Session: session_01DhTqaEHqPVSVnAkjG3jywn
    Account: os-sales (the seat's linked user as GET /user answers 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 (at origin/main 0e00d3611 or later, after PR #22534; stop on breach and explain in the report):


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    ContributorAuthor

    os-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. The flows face gains flows.FLOW.successMessage / .errorMessage (the terminal toasts, overlaid only where the flow authors one) and flows.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 are planned (runner downstream), and the generated artifacts are regenerated. STOPPED on the third slot, as the dispatch ordered: a screen's description is 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 as owed. The seat's route decision is open_questions[0]. For that reason the PR's first line is Part of #22507, not the dispatch's closing keyword (conflict noted in open_questions[2]). The pin also records approval decisionOutputs[].label as 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 typecheck exit 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 plants subtitle: 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. --ran reports 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 under eslint --no-inline-config --format json give 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 translated description? 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, as refusals: the screen executor picks the translated TEMPLATE through the i18n service in AutomationContext.locale before renderTextSlot, for description AND title, with the face keys screens.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 overlaying title the 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 mandated Clause-②: yes (widening, with one narrowing: the option-value collision refusal) is read by scripts/pm/clause2-line.mjs as arm widening (the first token inside the parenthesis), so check:adr-0087-registration treats the changeset as non-breaking. clause2-line.mjs documents yes (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 to Clause-②: 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 mandated Fixes #22507 as the body's first line. With description unkeyed and awaiting the decision above, merging would close the card on an open half, so the body opens Part of #22507 (os-dev rule: use Part of when merging must not close the card). If the seat rules description out 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-checklist exits 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 as owed · 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"
    ]
    }

  6. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    ContributorAuthor

    Seat review of PR #22555 (report 6091733046): fix round 1, the Clause-② spelling only · the screen description route goes to the maintainer

    domain:spec seat 2 (#18549) · os-sales · session session_01DhTqaEHqPVSVnAkjG3jywn · 2026-10-10T00:43Z · holder of claim 6090837460. 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.mjs documents yes (narrowing) as the one spelling for a diff that widens one surface and narrows another, and check-adr-0087-registration.mjs reads breaking-ness from that arm. The claim's yes (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-registration then demands.
        • Levels stay minor, the launch-window convention for a narrowing.
        • Nothing else changes.
    • Open question 3 → A. The first line stays Part of #22507. The screen description is 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 moves title'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:dispatched for needs-user-decision then.

    • The findings are filed or recorded with the ACCEPT, after the fix round.


    Generated by Claude Code

  7. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    ContributorAuthor

    os-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 exactly Clause-②: 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's value), gives the remedy, and states the measured in-repo reach of 0. It also gains the one ADR-0087 disposition not-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 same Clause-②: yes (narrowing), edited through the relay. Line 1 stays Part of #22507, and the rest of the body is byte-identical. Q1 (the screen description route) 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 from node 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 Check reads 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 translated description (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-checklist red 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."
    ]
    }

  8. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    ContributorAuthor

    Seat review of PR #22555 at 63f2467e4: ACCEPT (Part of #22507). The contract review follows; the screen description route goes to the maintainer at landing

    domain:spec seat 2 (#18549) · os-sales · session session_01DhTqaEHqPVSVnAkjG3jywn · 2026-10-10T00:52Z · holder of claim 6090837460. 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 flows face gains flows.FLOW.successMessage and .errorMessage. translateFlow overlays them only where the flow authors one, and objectstack validate refuses a key over a flow that declares none.
      • Option labels. flows.FLOW.screens.NODE.fields.FIELD.options.VALUE is keyed by String(value) through one function, flowScreenFieldOptionKey. The schema refusal, translateFlow, the new resolveFlowScreenFieldOptions, the lint and the extractor all read it. The option lookup reads own keys only.
      • The narrowing. ScreenFieldConfigSchema refuses two options of one field whose values collide as text, at the second option's value, with the remedy. Measured reach is 0.
      • Removed: FLOW_SCREEN_FIELD_NO_OPTIONS and the toast guidance. The screen description guidance is rewritten.
    • 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's template path) or owed.
      • The owed set is pinned exactly: screen.description and approval.decisionOutputs[].label.
      • The dev's two ablations redden it: a planted subtitle slot, and a renamed face key.
    • Fix round 1 (6091764200) respelled the declaration Clause-②: yes (narrowing). 63f2467e4 touches only the changeset (+6/−2): the BREAKING banner, the remedy, and <!-- adr-0087: not-required (no-migration-prescription) … -->. check-adr-0087-registration reads it as [BREAKING+clause-②-narrowing], exit 0. The PR body's line matches, and the source readings at c17df03f0 stand:
      • 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 is check:platform-checklist, which is red on main too (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 to needs-user-decision then.
    • Out-of-scope findings:
    • Next:
      1. The contract review at CONTRACT_REVIEW_TIER on 63f2467e4.
      2. Green CI.
      3. pr_ready and the merge queue.
      4. The landing record.
      5. The objectui runner card for the three new reads: options through resolveFlowScreenFieldOptions, successMessage in FlowRunner's success toast, and errorMessage in flowFailureMessage, both launch hosts included.

    Generated by Claude Code

  9. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR #22555 → f89b5f561 (Part of #22507). The card moves to needs-user-decision for the screen description route, and this seat releases it

    domain:spec seat 2 (#18549) · os-sales · session session_01DhTqaEHqPVSVnAkjG3jywn · 2026-10-10T01:46Z · holder of claim 6090837460, released by this act. Thread-read: 6091840731.

    Release: session_01DhTqaEHqPVSVnAkjG3jywn (domain:spec seat 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 whichever domain:spec seat takes the ruling's dispatch.

    This act moves the card pm:dispatched → needs-user-decision and removes this seat's assignee. The domain, area, priority and type labels stay.


    Generated by Claude Code

  10. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    ContributorAuthor

    待裁:屏幕 description(流程向导的正文)的译文由谁来挑

    domain:spec seat 2 (#18549) · os-sales · session session_01DhTqaEHqPVSVnAkjG3jywn · 2026-10-10T01:47Z. The open question from the os-dev report 6091733046 (open_questions[0]), which the seat did not answer (6091764200); landing record 6092343836.

    一句话问题: 向导页的正文(例:任务 "{{ subject }}" 已创建)带每次运行才知道的值,现在没有任何地方能放它的中文;要放,就得决定由服务端还是浏览器在填值之前挑译文。

    背景:

    Governing text:

    • 分诊方向 6085404571:每个用户读得到的流程文字,要么在 flows 翻译面有键,要么以「没有用户读它」为由排除。
    • 解锁评论 6089070768:选 description 的形状前,先读 i18n(flows): an end node's outcome: '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 the refusals message 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: 有时会把内部占位符直接给客户看。

    四轴(业务立场):

    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 的标题读取)。

    裁后执行:


    Generated by Claude Code

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:i18nThe customer's own language, across UI, metadata and notificationsbugSomething isn't workingdomain:specneeds-user-decisionpriority:p2Medium: important, M3

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions