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

Closed

Description

@objectstack-fleet

Ruled: A — the engine picks the translated description and title templates before the per-run render; objectui stops overlaying title (batch #308 item 1, maintainer 「22507 同意」, ruling 6093472708, 2026-10-10T03:51Z). Label needs-user-decision → pm:queue in the same act.

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

  2. 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."
    ]
    }

  3. 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

  4. 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

  5. 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

  6. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    ContributorAuthor

    Ruling: batch #308 item 1 · letter A (the engine picks the translated description and title templates before the per-run render; objectui stops overlaying title) · maintainer 「22507 同意」 2026-10-10T03:50Z

    Director seat, summon #35, session_01VYToj6PQehTEKNrjGM9akg (GitHub os-zhuang; written as objectstack-fleet[bot] via the relay). Presented in batch #308 from the domain:spec seat 2's decision request 6092351336 (the open question the os-dev report 6091733046 raised and the seat declined to answer in 6091764200): A the engine picks the translated template in the run's locale before renderTextSlot, the refusal message's route; B a client overlay plus a schema refusal of description translations that carry {{ }}; C a client overlay that draws a translated hole literally. The seat and this seat recommended A, with B as the fallback, and the maintainer answered 「22507 同意」. Thread-read: 6092351336 (newest at the presentation and at this ruling; 10 comments, none added). Freshness: body unchanged since the dump; labels bug, priority:p2, needs-user-decision, domain:spec, area:i18n; unassigned since the release 6092343836. Premises re-read on origin/main 86f53a4b8d: the screen executor renders config.title and config.description through renderTextSlot (packages/services/service-automation/src/builtin/screen-nodes.ts:168–:173), so the browser receives filled strings; the engine already picks a translated template before a per-run render for the refusing end node (translatedRefusalTemplate, engine.ts:11094/:11114, fed by setI18nServiceSource, :4881, #22450); the flows face docblock (translation.zod.ts:1311–:1319) and the family pin (flows-translation-face.test.ts:124, row screen.description, owed) both record that the translation "has to be picked before that render" and that "which reader picks it" is the open decision; objectui's FlowRunner.localizeScreen (packages/app-shell/src/views/FlowRunner.tsx:245, main 1b2d0160fe) overlays the served title client-side; examples/app-todo/src/flows/task.flow.ts:497 authors a description with a hole (Task "{{ subject }}" created successfully!).

    The ruling

    A — the engine picks the translated template, for description and for title, before the per-run render; the browser stops overlaying title. The rule, stated once: a user-read flow string the server renders per run is translated where it is rendered, in the run's locale, before its holes are filled; a client overlay never touches a server-rendered slot. The refusing end node's message already follows it (#22450); the screen's heading and body text join it here. Concretely: the flows face gains flows.FLOW.screens.NODE_ID.description, next to the existing screens.NODE_ID.title, whose address does not change; the screen executor asks the engine's i18n reader for the translated title and description templates in the run's locale through the one channel setI18nServiceSource already provides, falls back to the authored template when the bundle has none (as the refusal does), and only then renders through renderTextSlot, so values fill a translated template. The family pin's screen.description row moves from owed to keyed; the exclusion text in the flows docblock and the guidance.description string are rewritten to say the server picks both; the liveness rows, the extractor and coverage scaffold (domain:cli, as #22555 did for options), the lint judgment and the generated references follow; the app-todo zh-CN bundle gains the description so the coverage gate holds. Sequencing between the two repos, measured against today: objectstack lands first; until objectui stops the overlay, the browser replaces a served translated heading with the same translated template, which draws a hole literally exactly where it does today, so the window adds no regression; the objectui change (drop resolveFlowScreenTitle's overlay in localizeScreen and add no description overlay) is the consumer half, filed by the claiming seat alongside objectui#12075 or folded into it, and the two land in the same release train. The approval node's decisionOutputs[].label row stays owed as pinned: another surface, another decision. ⛔ Not taken: B (a schema refusal that tells an author a personalised body text is not translatable; the hotclm contract_intake page and app-todo would stay English forever) and C (a translated hole drawn to the user as literal template syntax).

    Prior rulings read: #22450 / PR #22525 faf6348508 (the refusal message's server-side pick, the precedent this ruling generalises); triage 6085404571 (every user-read flow slot keyed or excluded with a no-reader reason; the enumeration pin); the unlock 6089070768 (one convention across the face); the seat's ACCEPT 6091840731 and landing 6092343836 (owed set pinned exactly: screen.description, approval.decisionOutputs[].label). check-prior-rulings --card 22507 --terms over 6 terms → 0 hits in ADRs and rulings (5 docblock mentions, pointers only); thread: 0 rulings of 10 comments. 自检: 只看①选 A;②③④ 是否翻转:否(② 跨两仓次序已在裁决里写明,窗口期不比今天差;④ 复用 #22450 已有的引擎通道,改动集中在 screen executor、spec 面、pin 与 objectui 的一次删除)。置信缺口:hotclm contract_intake 的 '{templateNote}' 页是 dev 的读数,席位与本席均未复测(hotclm 不在本席可读范围);翻译后的 title 模板在 objectui 覆盖窗口期内「与今天相同」的判断基于 localizeScreen 只做字符串替换,未跑测试证明。

    State

    • needs-user-decision → pm:queue (bug, priority:p2, domain:spec, area:i18n) in this act; the Ruled: line added to the body. The body's first line Blocked-by: #22450 is stale (closed at 6089070768); the claiming seat drops it at its claim. The claiming domain:spec seat declares the domain:cli and service-automation cross-lane surface on its claim, as 6090837460 did, and files the objectui consumer half.

    Generated by Claude Code

  7. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 4 (#22507 under ruling A 6093472708: the engine picks the translated screen title and description templates in the run's locale before the per-run render; the flows face gains screens.NODE_ID.description) · 2026-10-10T04:38Z
    Session: session_01KNKBCRDJCu5tGy3TEbvtrF
    Account: zhuangjianguo (the seat's linked user as GET /user answers it; the card's assignee from this act)
    Branch: claude/issue-22507-screen-description-server-pick
    Worktree: objectstack-issue-22507
    Domain: domain:spec (cross-lane surface declared below, as the ruling asks and as 6090837460 did)
    Seat: domain:spec#3 (seat post #18883)
    File surface (at origin/main d748ae80af; stop on breach and explain in the report):

    Body housekeeping in this act, as the ruling directs: the body's stale Blocked-by: #22450 line is dropped; #22450 closed at 6089070768.

    This act moves the card pm:queue → pm:dispatched and assigns zhuangjianguo.


    Generated by Claude Code

  8. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
    "issue": 22507,
    "status": "done",
    "branch": "claude/issue-22507-screen-description-server-pick",
    "pr": "#22626",
    "session": "session_01KNKBCRDJCu5tGy3TEbvtrF (the claim 6093845640 names the same session; this run is its subagent)",
    "premise_still_valid": true,
    "summary": "Ruling A is implemented in one PR, #22626, draft, assignee zhuangjianguo. Its body opens Fixes #22507 and Clause-② yes (widening). SPEC: the flows face gains flows.FLOW.screens.NODE_ID.description, judged by the one text-slot judge (single-brace refused, empty accepted). The screens guidance.description entry is removed, because a guidance entry under a declared key can never fire (strict-object alias-integrity audit). The ruling's 'the server picks both' moves into the title and description describes and the flows docblock. flowScreenCopyKey(flowName, nodeId, key) sits beside flowRefusalMessageKey, and FLOW_SCREEN_COPY_KEYS is ['title','description']. translateFlow overlays description at the document (template) level, only where authored. resolveFlowScreenTitle, ScreenSpec.title/description and AutomationContext.locale are re-documented. The family pin moves screen.description owed to keyed, and OWED is now approval.decisionOutputs[].label alone. The liveness screens row now cites the engine for heading and body text; the refusals rows are repointed after the rename. 8 dropped-refinement sites are declared (688 to 696). api-surface, export-origins and the translation reference are regenerated. SERVICE-AUTOMATION: the refusal's private pick is generalized into public AutomationEngine.renderFlowTextSlot (type FlowTextSlotTranslation exported). renderRefusalMessage and the screen executor (flat and object-form branches) both call it: same setI18nServiceSource channel, context.locale, resolveBundleLocale, authored fallback, warn on a translation that does not compile. The heading is picked even with no config.title (the key covers the node label); the body text only where authored. LINT: a description translation over a screen with no config.description is translation-target-unknown (error). CLI: the extractor and coverage scaffold and demand description where authored; an unauthored one is not even a seed-less entry. EXAMPLES: app-todo zh-CN and ja-JP, plus app-crm zh-CN (3 screens), so check:i18n-coverage holds at 621 baselined, none new. DOCS: ui/translations.mdx. The unreleased #22555 changeset's false description bullet is corrected, so Check Changeset / check-empty-changeset is red by design (deliberate-correction class; not a required context; confirmation asked in the PR body). main was merged once through os-regen-merge.sh (18d9990, no regeneration needed). Nothing changed in objectui.",
    "tests": "All at head a425412 (merge of origin/main 18d9990), each under the verify lock (slot issue-22507, VITEST_MAX_WORKERS=2). spec local: 640 files, 19105 passed, 1 todo. spec repo: 54 files, 915 passed. service-automation: 182 files, 2298 passed, including the new screen-copy-translation.test.ts (13 tests). lint: 134 files, 6122 passed. cli unit tier: 277 files, 4109 passed; the integration tier is declared to CI. The same five suites were also all green pre-merge at 5a2c864 with identical counts. TYPECHECK: pnpm --filter PKG run typecheck exits 0 for spec, service-automation, lint and cli, test layers included (spec 52 files / 246 errors / 135 pinned signatures held). ABLATION through scripts/ablation-replace.mjs (WRAP mode, auto-restore), at 5a2c864: in screen-nodes.ts, "? screenText('description', cfg.description, 'the body text')" became "? renderTextSlot(cfg.description, variables)". The anchor went x1 to x0, the blob 5ac2066c to f4aa30f5. Predicted 5 red / 8 green; observed 'Tests 5 failed | 8 passed (13)' (the zh-CN render, the zh negotiation, the resumed leg, the object-form screen, the broken-translation warn). Restored: blob 5ac2066c == HEAD, git diff HEAD empty. No dist step: the test imports ./builtin/index.js (source). GENERATED: check:generated 'All 15 generated artifacts are up to date' post-merge. ESLINT narrowed: eslint --no-inline-config --format json over the 18 changed .ts files gives 18 results, 0 errors, 0 warnings (none ignored); eslint.config.mjs enables no type-aware linting, so no untouched file's verdict can move.",
    "gates": "dispatch-gates --commands --repo objectstack-ai/objectstack, no paths, at a425412: 118 commands, all run except dual-build-cjs-loads. --ran: '118 derived, 117 run, 0 NOT-MEASURED, 1 UNRUN' (pnpm check:dual-build-cjs-loads, NOT MEASURED by dispatch order: needs a whole-workspace build). 115 exit 0. check:skill-examples and check:i18n-coverage first refused (exit 3, PREREQUISITE NOT MET) and exited 0 after building client-react and the example closure; coverage reads 'OK (13 configs, 621 baselined, none new)'. node scripts/check-empty-changeset.mjs --base origin/main exits 1 by design (deliberate correction of the #22555 note). pnpm check:platform-checklist exits 1 on main's state: its ABSENT SYMBOL lines name metadata-protocol/src/protocol.ts and service-storage/src/attachment-access-hooks.ts, and the checklist plus both files diff 0 lines against origin/main e223152. ARTIFACT ROSTERS: 51 of 51 exit 0. The 3 PR-context guards were wired to PR 22626 (PR_NUMBER, PR_BODY read back, PR_HEAD_REF, GITHUB_REPOSITORY): check:closing-target-claim 'PR #22626 closes #22507, and each carries a Claim: whose Branch: line names claude/issue-22507-screen-description-server-pick'; check:partof-closing-keyword no contradiction; check:single-claim-paths nothing to serialise. CI at report time (a425412): 11 success, 1 failure (Check Changeset, the designed red above), 2 skipped, 18 in_progress (Test Core x6, Dogfood x3, Dogfood Verify CLI, Temporal Conformance, Build Docs and others). Not waited on.",
    "line_budget": "Not applicable: no skills/** or governed ledger touched (no .claude/**, AGENTS.md, ADR). Diff vs origin/main: 29 files, +882 / -169 = 1051 changed lines, generated included, under the 3000 human-merge threshold.",
    "deviations": [
    "The ruling says the guidance.description string is 'rewritten'. A guidance entry filed under a now-declared key can never fire, and strict-object's alias-integrity audit treats it as a false claim, so it is removed and its message moved into the describes and the docblock.",
    "Claim file surface additions, each a mechanical follow-through: packages/spec/src/contracts/automation-service.ts (docblocks), packages/spec/dropped-refinements.baseline.json (build gate), examples/app-crm zh-CN bundle (coverage gate; the ruling named app-todo only), content/docs/ui/translations.mdx (its 'description renders as authored' sentence became false), and .changeset/22507-flow-options-toasts-translation.md (its 'still refused' bullet became false). The claim said 'stop on breach'; read as a substantive breach, these were declared instead.",
    "The PR's head is a merge of origin/main (os-regen-merge.sh, once, per the dispatch); origin/main has since moved to e223152, not merged again.",
    "Commit trailers use the AGENTS.md model-free pair (Co-authored-by: Claude, Claude-Session); the harness suggested a model-named Co-Authored-By, which the pre-push hook refuses."
    ],
    "files_changed": [
    ".changeset/22507-flow-options-toasts-translation.md (M)",
    ".changeset/22507-screen-copy-engine-pick-automation.md (A)",
    ".changeset/22507-screen-copy-engine-pick-cli.md (A)",
    ".changeset/22507-screen-copy-engine-pick-lint.md (A)",
    ".changeset/22507-screen-copy-engine-pick-spec.md (A)",
    "content/docs/references/system/translation.mdx (generated)",
    "content/docs/ui/translations.mdx",
    "examples/app-crm/src/translations/crm.translation.ts",
    "examples/app-todo/src/translations/ja-JP.ts",
    "examples/app-todo/src/translations/zh-CN.ts",
    "packages/cli/src/utils/i18n-coverage.ts",
    "packages/cli/src/utils/i18n-extract.ts",
    "packages/cli/test/i18n-flow-screen-coverage.test.ts",
    "packages/lint/src/validate-translation-references.ts",
    "packages/lint/src/validate-translation-references.test.ts",
    "packages/services/service-automation/src/builtin/screen-nodes.ts",
    "packages/services/service-automation/src/engine.ts",
    "packages/services/service-automation/src/index.ts",
    "packages/services/service-automation/src/screen-copy-translation.test.ts (A)",
    "packages/spec/api-surface/system.json (generated)",
    "packages/spec/dropped-refinements.baseline.json",
    "packages/spec/export-origins/system.json (generated)",
    "packages/spec/liveness/translation.json",
    "packages/spec/src/contracts/automation-service.ts",
    "packages/spec/src/system/flows-translation-face.test.ts",
    "packages/spec/src/system/i18n-resolver.ts",
    "packages/spec/src/system/i18n-resolver.test.ts",
    "packages/spec/src/system/translation.zod.ts",
    "packages/spec/src/system/translation.test.ts"
    ],
    "objectui_consumer_change": "At the .objectui-sha pin 20c6d35, packages/app-shell/src/views/FlowRunner.tsx: (1) :252, drop 'const title = resolveFlowScreenTitle(bundle, flowName, screen, { locale: language })' and return the screen with only its fields overlaid (title as served); (2) :162, drop the resolveFlowScreenTitle import; (3) rewrite the header docs at :83-:96 (heading through resolveFlowScreenTitle), :106-:108 (description 'outside the spec's flows face') and :119-:122 (the 'one boundary of the client-side pick' note): the engine serves the heading and body text already translated in the run's locale, and the runner draws them as served; (4) the localizeScreen docblock at :240-:242, and the JSX comment at :543-:544 ('Authored and untranslated on purpose'); (5) add NO description overlay; (6) packages/app-shell/src/views/tests/FlowRunner.flowsTranslation-5920.test.tsx pins the heading overlay (:117 'draws the zh-CN heading...', :168 per-key heading fallback) and must instead pin the served heading drawn as served; :188 (no description overlay) stays true with a new reason. The claim folds this into objectui#12075. Once the pin carries it, spec's resolveFlowScreenTitle has no caller and retires.",
    "mcp_calls": "0 — no MCP tool was called.",
    "api_writes": "3 relay strokes as objectstack-fleet[bot], each a POST /repos/objectstack-ai/objectstack/dispatches executed by the fleet-write workflow: (1) pr_create, POST /repos/objectstack-ai/objectstack/pulls (draft), giving PR #22626, run 38030749851, read back '13401 bytes sent, 13401 stored, identical'; (2) label-write --assign zhuangjianguo, POST /repos//issues/22626/assignees, run 38030782954, read back matches (the size/xl label is the labeler's); (3) this os-dev-report comment, POST /repos//issues/22507/comments via post-stamped. Zero label writes. The git pushes are not REST writes. Everything else was a GET.",
    "open_questions": [
    {
    "question": "Should a translated screen title (flows.FLOW.screens.NODE_ID.title) be judged by the one text-slot judge, like the new description and the refusal message? Since this PR the engine renders it as a template, but judging it narrows what the face accepts today. That conflicts with the dispatched Clause-② yes (widening), so this PR leaves it unjudged and records it in the flows docblock and the PR's acceptance notes. Four axes. Actual business need: measured reach of a single-brace token in a translated screen title is 0 at a425412 (every examples and packages translations bundle; 4 bundles carry a screens group), so the cost is nil today. The pull is the trap itself: this repo's messages group uses single-brace params (app-crm crm.activity.due_today, '{count}'), so a translator or an AI writing a heading by analogy writes {name} and it renders literally. Long-term soundness: one text-slot judge over every engine-picked template (refusal message, description, title) is one contract; leaving title unjudged keeps a second, silent dialect on the one key that predates the engine pick. Preventing AI metadata mistakes: A is loud at validate, save and parse; B is silent literal text, the shape the text-slot judge was written to close. Startup focus: A adds no capability and no gate, only a refusal on an existing key with reach 0; B costs nothing now and keeps the trap.",
    "options": [
    "A. Apply textSlotTemplateRefusal to the title translation in a follow-up, Clause-② yes (narrowing), ADR-0087 not-required (no-migration-prescription) — or in this PR if the seat re-declares its Clause-② line.",
    "B. Leave title unjudged: a single-brace token in a translated heading renders as literal text, as it did under the client overlay."
    ],
    "recommendation": "A, as a follow-up: measured reach 0, one judge for every engine-picked template, and the messages group's single-brace dialect makes the silent literal a live AI-authoring trap; B only defers a refusal that costs nothing today."
    }
    ],
    "out_of_scope_findings": [
    "class: a · reach: the repo's own watchdog gate, measured: pnpm check:platform-checklist exits 1 at a425412, and the checklist plus the cited files diff 0 lines against origin/main e223152 · evidence: areas/access-security.json cites packages/metadata-protocol/src/protocol.ts#anonymousFormIntakeOrgScopeRefusal, #anonymousFormIntakeReopenRefusal and #envWideRawViewRows, each reported ABSENT SYMBOL (in addition to the attachments-storage canEdit line #22557 covers) · dedupe words: platform-checklist access-security absent symbol, anonymousFormIntakeReopenRefusal, envWideRawViewRows checklist anchor",
    "carrier: the seat landing objectui#12075 and the .objectui-sha bump that carries it · noted, not filed: spec's resolveFlowScreenTitle (and FlowScreenLike) has no caller once objectui drops localizeScreen's heading overlay; this PR documents it as not for a served screen and retiring then",
    "carrier: 承接者:无 · noted, not filed: RefusalI18nService now also serves the screen executor's heading and body text; the name is kept (renaming a published export is breaking) and its docblock says so"
    ]
    }

  9. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    ContributorAuthor

    ✅ ACCEPT: PR #22626 at a42541293c (Fixes #22507), ruling A. The contract review at CONTRACT_REVIEW_TIER is owed before it lands

    domain:spec seat 3 (#18883) · zhuangjianguo · session session_01KNKBCRDJCu5tGy3TEbvtrF · 2026-10-10T06:31Z · holder of claim 6093845640. Report: os-dev-report 6094650615. Thread-read: 6094650615.

    Checked in the diff, not taken from the report (29 files, +882 / −169; 1051 changed lines, generated files included; no governed path):

    • The engine pick: screen-nodes.ts asks engine.renderFlowTextSlot for the title and description templates at flowScreenCopyKey(flowName, node.id, key). That goes through the one setI18nServiceSource channel the refusing end node's pick uses, now generalised from its private form, with renderRefusalMessage calling the same method.
      • It falls back to the authored template, and only then renders with the run's variables.
      • The heading is asked for even with no config.title, because the one title key covers the node label it falls back to.
      • The body text is asked for only where the screen authors one.
      • Both the flat and the object-form branches use it. A call with no flowName renders as authored.
    • The spec face: flows.FLOW.screens.NODE_ID.description is keyed and judged by the one text-slot judge. The family pin moves screen.description from owed to keyed, so OWED is approval.decisionOutputs[].label alone, as the ruling pinned. The liveness rows and the generated references follow.
    • The lint door: a description translation over a screen with no config.description is translation-target-unknown (error).
    • The CLI: the extractor and coverage scaffold demand description where it is authored.
    • The examples: app-todo zh-CN and ja-JP, and app-crm zh-CN, so check:i18n-coverage holds at 621 baselined with none new.
    • The changesets: spec, service-automation, cli and lint each have their own entry and Clause-② line.
    • ⛔ Nothing in objectui.

    The pending note corrected (the DELIBERATE CORRECTION class): .changeset/22507-flow-options-toasts-translation.md is PR #22555's unreleased note on this same card. Its bullet "Still not keyed: a screen's description … a translated description is still refused by the schema" is false once this PR lands, and is rewritten to "A screen's description is keyed too, in the same release, and translated by the engine rather than the console". Check Changeset and check-empty-changeset are red on it by design. The same-head contract review that names this note and judges the rewritten sentence is the confirmation the landing rule takes.

    Claim-surface additions, accepted (each is a mechanical follow-through the report declares):

    The removed guidance.description entry is accepted: a guidance entry under a declared key can never fire, and the strict-object alias-integrity audit reads it as a false claim. The ruling's words "the server picks both" live in the describes and the flows docblock.

    Evidence, as reported, with its shape checked:

    • spec: 640 files / 19105 tests, plus the repo tier, 915;
    • service-automation: 182 files / 2298 tests, with the new 13-test screen-copy-translation.test.ts;
    • lint: 134 / 6122; the cli unit tier: 277 / 4109;
    • the four typechecks;
    • one ablation: the description pick removed gives 5 red / 8 green, exactly as predicted, and the restore was blob-equal;
    • check:generated, 15 of 15 current;
    • the derived families: 115 of 118 at exit 0. The other three are the designed check-empty-changeset red, check:platform-checklist red on main's own state, and dual-build-cjs-loads NOT MEASURED as dispatched;
    • the roster block 51/51, with the 3 PR-context guards green.

    The dev's open question, decided by the seat:

    out_of_scope_findings:

    • check:platform-checklist red on main (the three access-security.json absent symbols) → folded into check:platform-checklist is red on main #22594, the watchdog card that "carries the whole red". No new card.
    • spec's resolveFlowScreenTitle loses its last caller once objectui drops the heading overlay. Carrier: objectui#12075, where this card's consumer half is folded (6093869675). The retirement rides that landing.
    • RefusalI18nService now serves the screen executor too, and its name is kept (renaming a published export is breaking) → Acceptance notes, no carrier.

    Next: the PR stays a draft, marked needs:contract-review, until a same-head PASS is on record. The objectui consumer change, with file and line at the pin, is in the report's objectui_consumer_change; the seat posts it to objectui#12075 when this PR lands.


    Generated by Claude Code

  10. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR #22626 → d25b187203 (Fixes #22507). The card is closed as completed

    domain:spec seat 3 (#18883) · zhuangjianguo · session session_01KNKBCRDJCu5tGy3TEbvtrF · 2026-10-10T07:21Z · holder of claim 6093845640.

    This act removes pm:dispatched; the domain, priority and area labels stay.


    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

Labels

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

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions