Skip to content

[Decision] A bulk action's confirmText: refuse {{…}} holes by name (A), or declare {{count}} and the action's param holes and fill them (B)? HotCRM authors them in 12 strings (from objectui#13154) #24798

Open

Description

@objectstack-fleet

Ruled: 6285948976 · letter B · 2026-10-20T07:41Z — confirmText becomes a template slot (ADR-0032 §3) with declared roots count and the action's params, two steps; renderer half objectstack-ai/objectui#13183; sibling sweep #24814; state pm:queue · domain:spec · priority:p2 (director seat, summon #45 continuation 1, batch #375 item 4).

Filing gate ②: a maintainer's decision. It is a contract-shape question. Either way it touches a shipped customer's metadata: (A) refuses what HotCRM ships, and (B) widens the spec. Both go to the decision box (triage-duties.md:39–40). The card was moved here from objectstack-ai/objectui#13154 on the objectui domain:ui seat's reading 6284554292, then turned into a decision card on the maintainer's question 「24798 为什么不是决策卡」. Filed by the triage seat (seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U. ⛔ Not a claim.

Reader who acts: the maintainer answers A or B. Then the domain:spec seat lands the spec half. Under B, objectui's domain:ui seat lands the console half.

Measured (origin/main a43405d596; objectui cbac9d7dde; hotcrm and cloud origin/main)

  • The contract. packages/spec/src/ui/bulk-action.zod.ts:403 reads confirmText: z.string().optional().describe('Confirmation text shown above the affected-record summary.'). It is plain text with no hole declared. The holes the spec does declare are named elsewhere: record:details' {{ count }} (component.zod.ts:1180–:1276, which refuses a foreign hole by name), an api action's {{page.<var>}}, and the operation messages' {{count}}.
  • The renderer. objectui plugin-grid/src/components/BulkActionDialog.tsx:412 renders def.confirmText verbatim, or, when none is authored, t('grid.bulk.confirmDefault', { count: rows.length, … }). So the platform's own default already says the count. Only authored text cannot.
  • Who authors holes (git grep for confirmText containing {{):
repo strings
hotcrm 12: src/sales/views/account.view.ts:59, :77, :85 (Update Tier, Transfer Owner, Delete) × en, zh-CN, es-ES, ja-JP. Holes {{count}}, and {{tier}} (a bulk param) on Update Tier
objectstack (examples, packages) 0
cloud 0

维护者速读

一句话问题: 批量操作的确认文案里,作者写了「将 {{count}} 个客户的等级更新为 {{tier}}」,界面原样显示 {{count}}。spec 没有给这个文案声明占位符,所以控制台不该自己替换。要定:是不许写占位符,还是正式支持。

选项 做法 客户能感知的后果 工作量
A 不许写 发布与 os lint 按名字拒绝 confirmText 里的 {{…}},提示改用默认文案(默认文案本身已显示条数) 确认句里不能写条数和所选值(「改为 Enterprise」这类信息只在上一步的表单里)。HotCRM 要改 12 条文案;先做成提示,HotCRM 改完再升级为拒绝 spec 1 个 PR(S)+ HotCRM 改文案(S)
B 正式支持(推荐) spec 声明两类占位符:{{count}}(受影响条数)与 {{<参数名>}}(本次批量操作的参数值,按显示值填);未声明的占位符在发布时按名字拒绝;objectui 按声明填 HotCRM 现有 12 条照原样生效;作者能在确认句里写清「对几条、改成什么」 spec 1 个 PR(S–M)+ objectui 1 个 PR(S–M)。先例:record:details 的 {{ count }} 声明加外来占位符拒绝(component.zod.ts:1180)

业务含义直译: A = 确认框只念固定台词,具体数字看旁边的清单;B = 确认框可以念「您要把 2 个客户改成 Enterprise」,但只许用登记过的空。

四轴(业务立场):

  • 项目长远合理性: 两年后应该是「写下的确认句说得清对几条、改成什么」。平台自己的默认文案已经在填条数,B 让作者文案和默认文案用同一个机制,并且沿用 spec 已有的「声明占位符 + 拒绝未声明者」模式,不另造规则。A 让作者文案永远比默认文案少一个能力。
  • 实际业务拉动: 有真实拉动:一个客户应用(HotCRM)在 4 种语言里写了 12 条,其中 3 条用到参数值。仓库内其他地方是 0。
  • 防 AI 犯错: 今天 AI 自然会写 {{count}},而且静默失效。A 和 B 都能消除这个陷阱:A 靠拒绝,B 靠兑现,再加上对未声明占位符的拒绝。B 和 AI 的自然写法一致,出错面更小。
  • 创业阶段不扩散: A 更小(「能力扩张默认从紧」)。B 多一个模板面,但复用既有机制,可以分两步落:先 {{count}},再参数占位符。

推荐:B,分两步落,不进 18.0。 只看「项目长远合理性」就选 B。实际拉动支持 B;「从紧」只影响规模和时机,不翻字母。
回退:A(先提示、后拒绝)。
置信缺口:

  • 云端客户自建应用里的 confirmText 只量了仓库源码,没量线上库。
  • 参数值怎么显示(选项标签、查找记录名、日期格式、多语言)需要 B 的 PR 明确;未定之前,第二步不落。

自检:只看①选 B;②③④是否翻转:否(④只影响分步与时机)。

是否提前: 两个选项都是协议变化,18.0 GA 在即。倾向不提前,在 18.x 小版本里落。

裁后执行:

  • 裁 A: domain:spec 席位一个 PR,先做成提示,objectstack-ai/hotcrm 改完 12 条后再升级为拒绝;本卡随之关闭。
  • 裁 B: domain:spec 席位声明两类占位符,并按名字拒绝未声明的;本席位在 objectui 立填充卡(第一步 {{count}},第二步参数值);HotCRM 不需要改。

os-decision-facets

  • ① 项目长远合理性:B 让作者文案和平台默认文案用同一机制(默认文案已在填条数),并沿用「声明占位符 + 拒绝未声明」的既有模式;A 让作者文案永远少一个能力。
  • ② 实际业务拉动:HotCRM 在 4 种语言里写了 12 条(3 条用到参数值);仓库内其他地方为 0;云端未测。
  • ③ 防 AI 犯错:今天静默失效;A、B 都消除陷阱,B 与 AI 的自然写法一致。
  • ④ 创业阶段不扩散:A 更小;B 复用既有机制,分两步落。

Related


Generated by Claude Code

Activity

  1. changed the title [-]spec(ui): a bulk action's `confirmText` declares no template hole, yet HotCRM authors `{{count}}` / `{{tier}}` and the console prints them raw — refuse a hole by name (a), or declare the holes (b) (from objectui#13154)[/-] [+][Decision] A bulk action's `confirmText`: refuse `{{…}}` holes by name (A), or declare `{{count}}` and the action's param holes and fill them (B)? HotCRM authors them in 12 strings (from objectui#13154)[/+] on Oct 20, 2026
  2. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    席位意见(增量 1 · 批量操作确认文案里的占位符)· director seat (summon #45 continuation 1, session_01Lg6GQ4xjqmvEzYv78d6H7i), read as batch #375 item 4, 2026-10-20T07:19Z

    On the triage seat's decision card, filed on the maintainer's question 「24798 为什么不是决策卡」 (B recommended by the filer with A as a fallback; under the maintainer's rule #24444 this delta names one option and no fallback). Opening marker #12708, comment 6285413947. Presented, not self-ruled: both letters move a published contract (bulk-action.zod.ts), the decision box's floor.

    Premises re-run in this act — objectstack origin/main a43405d596d4, objectui origin/main 91b658a75d64:

    1. packages/spec/src/ui/bulk-action.zod.ts:403: confirmText: z.string().optional(), 「Confirmation text shown above the affected-record summary」, no hole declared. Holds.
    2. The declared-hole precedent: component.zod.ts:1180–1200 — SECTION_HEADING_COUNT_HOLE is ONE exported grammar read by both the renderer that fills it and the judge that refuses a heading with a foreign hole, 「two readings of a hole is how a heading parses clean and still renders the hole as literal text」. The platform's own operation messages carry {{count}} too (system/operation-message.ts:215). Holds — the mechanism B needs already exists and is already the house pattern.
    3. objectui plugin-grid/src/components/BulkActionDialog.tsx:412 renders def.confirmText verbatim, else t('grid.bulk.confirmDefault', { count: rows.length }): the default fills the count, authored text cannot. Holds.
    4. Producers: objectstack 7 strings (showcase project.view.ts ×6, app-todo ×1), none with a hole; hotcrm 12 by the card, 3 with a param hole, read in the maintainer's own browser pass (hotcrm#2188) — hotcrm is unreachable from here, taken as the card reads it. Holds.
    5. ADRs the mechanical pass named (ADR-0110 D3, ADR-0066 D1, ADR-0070 D5) do not touch confirmation text; ADR-0011 uses the presence of confirmText as the destructive heuristic, unchanged by either letter. No ADR answers the card.

    Facets, this seat's reading against the card's: ① same direction, B: in two years every authored sentence that may carry a hole uses the one declared-hole grammar (declare the holes, fill them, refuse a foreign one by name) — the record:details heading already does; an authored confirm then says 「N 条 → 值」 as the platform's default already says N. A leaves authored text weaker than the default forever and adds a refusal for a hole the platform fills elsewhere. ② same: 12 strings in four locales in a shipped customer app, 3 with a param value; the maintainer's standing 口径 「平台相关的功能,应该尽量先改平台」 points at B (the platform fills; the app keeps its strings) over A (the app rewrites 12 strings to satisfy a refusal). ③ same, sharpened: today {{count}} reaches the screen as literal text with no signal; under B a declared hole is filled and a foreign one is refused at publish and os lint by name; under A the trap an AI author falls into (writing {{count}} because the default shows a count) is turned into a refusal rather than removed. ④ B adds no second mechanism; the only new surface is the param-hole display rule, so it lands second, after the spec PR states that rule.

    Recommend B, in two steps, not in 18.0. 口径: confirmText declares two holes — {{count}}, the affected-record count, and {{<param name>}}, the display value of the bulk action's param of that name; any other hole is refused at publish and by os lint by name, with the record:details grammar reused and exported for the renderer; objectui fills {{count}} first (the dialog already holds rows.length), and {{<param>}} only after the spec PR states the display-value rule (option label, lookup record name, date and number formatting, locale) — ⛔ no second step before that rule; hotcrm's 12 strings stand unchanged. Clause-②: yes (widening, minor), contract review at CONTRACT_REVIEW_TIER. Not taken: A — a refusal for the one hole the platform's own default already fills, paid by a customer app rewriting 12 strings.

    Workload of the letter: domain:spec · 1 PR, S–M (the declaration, the judge, the exported grammar, the ADR-0087-free widening, regenerated artifacts) · objectui domain:ui · 1 PR S (step 1) + 1 PR S–M (step 2) · hotcrm: nothing. Precedent: record:details {{ count }} (#23013).

    After the ruling: needs-user-decision → pm:queue (domain:spec, p2 as graded); the objectui half is one domain:ui card filed at ruling time with the ruled sentence quoted (ruling D on #24591: split by repository), pm:blocked on this card until the spec release is installable there.


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    席位意见(增量 2 · 维护者问:这是什么语法,符合平台 CEL 规范吗)· director seat (summon #45 continuation 1, session_01Lg6GQ4xjqmvEzYv78d6H7i), batch #375 item 4, 2026-10-20T07:30Z

    The maintainer's words in this session's chat: 「24798 是什么语法,符合平台cel的语法规范吗」. Read on objectstack origin/main a43405d596d4:

    1. {{count}} is not CEL; it is the platform's template dialect. shared/expression.zod.ts:72 declares exactly three dialects, ExpressionDialect = ['cel', 'cron', 'template']; template has a typed slot (TemplateExpressionInputSchema, a bare string being shorthand for { dialect: 'template', source }, whose refusals prescribe {{record.name}}). ADR-0032 (accepted) Decision 2: 「predicates and computed values are whole-field CEL with no delimiters; only genuine text uses templates」; Decision 3: 「One delimiter, {{ }}; single { } deleted … holes are a restricted CEL subset: field/variable paths plus a whitelisted formatter set — not arbitrary CEL logic」; and 「"One expression language" means predicates + computed values + template holes resolve to CEL semantics — not literally one syntax for everything」. The engine is @objectstack/formula template-engine.ts: {{ path }} and {{ path | formatter[:arg] }} over the same variable scope CEL uses.
    2. So HotCRM's {{count}} / {{tier}} is the platform's own hole spelling and conforms to ADR-0032 §3. What does not conform is the slot: bulk-action.zod.ts:403 types confirmText as a plain z.string(), not a template slot, so no door compiles its holes (validateExpression('template', …) never runs on it) and the renderer prints them raw. Every other person-facing text slot already made this move on the protocol-18 line: automation/flow-text-slot-template.ts types a notify node's title / message, a screen's title, an http node's url and headers as template slots, compiles their {{ }} holes at objectstack validate / compile / the publish gate, and refuses an unbound root or a single-brace token by name.
    3. This corrects delta 1's 口径 on mechanism. Not the record:details heading regex (SECTION_HEADING_COUNT_HOLE, a one-hole special case written before the text-slot conversion) — the house mechanism is the typed template slot plus the one judge the flow text slots use. Under B, confirmText becomes a TemplateExpressionInputSchema slot whose scope declares the bulk-confirm variables — count (affected records) and the action's params by name (the spec PR fixes the root spelling, e.g. {{ params.tier }} or {{ tier }}, consistent with the flow scope's roots) — an unbound root refused at publish and os lint by name, formatters from the existing whitelist ({{ count | number }}), ⛔ no CEL logic inside a hole (ADR-0032 non-goal). The console fills the slot through the same template engine the flow notifications already render with. Under A, the platform would refuse its own hole grammar in one text slot while accepting it in every other — against ADR-0032 Decision 2.

    Recommendation unchanged: B, two steps ({{count}} first; param roots once the display-value rule is stated — the template engine's formatter whitelist already defines number, date and currency rendering, so that rule is mostly the choice of option label vs stored value for picklists and lookups). Workload unchanged: domain:spec 1 PR S–M (the slot type, the scope's bound roots, the judge wiring at the three doors, regenerated artifacts; Clause-②: yes widening, minor), objectui 2 PRs, hotcrm none.

    • ① 长远:confirmText 收口到 ADR-0032 的 template 槽,与 flow 文本槽同一机制、同一判官;不另造正则。
    • ② 拉动:HotCRM 12 条写的正是平台自己的空位拼写。
    • ③ 防错:有类型的 template 槽在 validate / compile / 发布三道门按名拒绝未绑定根与单花括号,不再静默上屏。
    • ④ 不扩散:零新语法、零新引擎,只把一个文本槽改成已有的槽类型。

    Generated by Claude Code

  4. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    Ruling: batch #375 item 4 · letter B · maintainer 「同意」 2026-10-20T07:40Z

    Director seat, summon #45 continuation 1, session_01Lg6GQ4xjqmvEzYv78d6H7i (GitHub os-justin; written as objectstack-fleet[bot] through the relay, read back), on the triage seat's decision card (B recommended by the filer and by this seat's deltas 6285610982 and 6285797754). Opening marker #12708, comment 6285413947. The maintainer's words in this session's chat, in order: 「24798 是什么语法,符合平台cel的语法规范吗」 (answered: the template dialect, conforming to ADR-0032 §3 — delta 2), 「是否建议统一到 cel」 (answered: no — ADR-0032 Decision 2 keeps two shapes; the slot type, not the dialect, is what is unfinished), then 「同意」. Thread-read: 6285797754.

    What the ruling binds (letter B — confirmText becomes a template slot; two steps):

    1. ui/bulk-action.zod.ts:403 confirmText becomes a TemplateExpressionInputSchema slot (ADR-0032 Decisions 2 and 3): a bare string is shorthand for { dialect: 'template', source }; holes are {{ path }} and {{ path | formatter }} over the bulk-confirm scope, whose bound roots the spec declares — count, the affected-record count, and the action's params by name (the PR fixes the root spelling, {{ tier }} or {{ params.tier }}, consistent with the flow scope's roots); an unbound root and a single-brace token are refused by name at objectstack validate, compile and the publish gate through the one judge the flow text slots use (automation/flow-text-slot-template.ts); ⛔ no CEL logic inside a hole (an ADR-0032 non-goal); formatters from the existing whitelist.
    2. The console fills the slot through the template engine the flow notifications render with: step 1 {{count}} now; step 2 the param roots once the spec PR states the display-value rule (option label against stored value for picklists and lookups; number, date and currency via the whitelist). The renderer half is list bulk action confirmText: fill the template slot's declared holes — {{count}} first, the action's params second (the renderer half of objectstack#24798 ruling B) objectui#13183, pm:blocked on this card until the spec release is installable there (ruling D on [Decision] A card whose premise or fix lives in a repository its lane cannot reach (cloud today): who reads it, who acts, and what the lane does meanwhile #24591).
    3. HotCRM's 12 strings stand unchanged and render filled once both halves ship; a stored confirmText with no hole parses as plain text as before.
    4. The sibling, spec: type the remaining person-facing text slots as template slots — confirmText ×3, successMessage / errorMessage ×6, emptyText ×2 (ADR-0032 Decision 2 and 3; the same mechanism as #24798) #24814, types the remaining nine person-facing text slots the same way (confirmText ×3, successMessage / errorMessage ×6, emptyText ×2), serial after this card's PR — the maintainer's 「同意」 on 「统一槽」.
    5. Clause-②: yes (widening, minor); contract review at CONTRACT_REVIEW_TIER; not before 18.0 GA.
      Not taken: A — the platform would refuse its own hole grammar in one text slot while every other text slot accepts it, against ADR-0032 Decision 2; C, unifying person-facing text into CEL — put to the maintainer at his question and declined: translators move holes and do not translate expressions, CEL has no formatting, and ADR-0032 rejected CEL inside holes.

    Workload of the letter: domain:spec · 1 PR, S–M · objectui domain:ui · 2 PRs (#13183) · hotcrm: nothing. Precedent: the flow text slots' conversion on the protocol-18 line.

    Execution: needs-user-decision → pm:queue in this act (domain:spec, priority:p2 as graded); the spec seat dispatches and quotes items 1–2 in the PR body; #24814 follows serially; objectstack-ai/objectui#13183 waits on the pin.


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 17
    Session: session_01D6F9axfoPzR7t8G1J7Ytzz
    Account: baozhoutao (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-24798-bulk-confirm-template-slot
    Worktree: objectstack-issue-24798
    Domain: domain:spec
    Seat: domain:spec#3 (seat post #18883)
    File surface (at origin/main b42c621c05 or later; stop on a breach and explain it in the report):

    • packages/spec/src/ui/bulk-action.zod.ts: confirmText becomes a TemplateExpressionInputSchema slot, with declared roots count and the action's params;
    • the one template judge the flow text slots use (packages/spec/src/automation/flow-text-slot-template.ts), reused rather than forked, and wherever objectstack validate / compile / the publish gate run it;
    • their tests, generated artifacts regenerated by repo tooling, and .changeset/24798-*.md.

    The renderer half is objectstack-ai/objectui#13183, and the sibling sweep is #24814, serial after this PR.
    Container & model: M, mode:subagent, model: default (no path-derived mandate)
    Clause-②: yes (widening)
    Responsibility: n/a — ruled letter B (6285948976)
    Thread-read: 6292027850
    Serial constraints cleared: ruling 6285948976 item 5 reads "not before 18.0 GA", and @objectstack/spec@18.0.0 has shipped. The dev re-checks open PRs touching bulk-action.zod.ts and flow-text-slot-template.ts before opening its own. Under 「甲」 (#6015 6292005854), any refusal this adds to text that passes today ships as a warning in 18.x, and its refusal waits for 19.0, unless an ADR-0087 conversion carries it.

    Read on origin/main b42c621c05 at 2026-10-20T15:36Z: confirmText: z.string().optional() declares no hole.


    Generated by Claude Code

  6. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 24798,
    "status": "done",
    "branch": "claude/issue-24798-bulk-confirm-template-slot",
    "pr": "#24947",
    "session": "session_01D6F9axfoPzR7t8G1J7Ytzz",
    "premise_still_valid": true,
    "summary": "Ruling B, step 1, spec half. bulkActionDefs[].confirmText (ui/bulk-action.zod.ts) is now an ADR-0032 template slot: the TemplateExpressionInputSchema input built from the internal templateExpressionInput constructor, as the notify title/message are, so its refusal prescribes {{ count }}. A bare string is shorthand for the envelope, holeless text parses unchanged, and for 18.x a blank string still parses verbatim. The spec declares the closed scope bulkConfirmTemplateRoots(def): count, then the def's params by name, written BARE ({{ tier }}, never {{ params.tier }}). That matches the flow scope's bare roots, and ruling item 3 keeps HotCRM's {{count}}/{{tier}} unchanged; count wins over a param named count. The slot's describe states the display-value rule step 2 waits on: option label, record display name, else the entered value plus a whitelisted formatter. The one judge (automation/flow-text-slot-template.ts) gains a closed-scope entry, closedScopeTemplateRefusal, over the same token, hole and root internals; the flow verdicts are unchanged and pinned with a control. validateStackExpressions (validate, compile and the publish gate) judges each bulk confirmText with it, then compiles it with validateExpression('template'). The walk covers view list/listViews, the flattened overlay and ViewItem record, and object listViews; the gate's expression rule now declares 'view' (that pass alone on a view write). Under the jia rule every finding is a WARNING: measured on b42c621, confirmText was z.string() and every such text passed, so the refusal waits for 19.0. The changeset is Clause-2 yes (widening), minor for spec and lint. The contract review tier is CONTRACT_REVIEW_TIER; no governed surface is touched (neither Tier S nor H), and the diff is 734 lines. Closing line: the PR body uses 'Fixes #24798' (os-dev's first-line rule) where the dispatch said 'Closes'; both close. I closed rather than used Refs because the ruling splits the work by repo and objectui#13183 is Blocked-by this card, so keeping it open would deadlock. Outside the claim's listed paths, each forced by the slot change and named in the PR: a docblock line in shared/typed-expression-input.ts, the ui unknown-members ledger (confirmText.ast under EXPRESSION_AST), the parsed-value assertion in action-confirm-params-guard.test.ts, the dogfood expression-conformance ledger row, and the lint pins listing the view rules plus the declared-keys meta-test's receiver table. The worktree was cleaned up (node_modules removed, git worktree remove succeeded without --force).",
    "tests": "All at final head 2a0f011. spec: pnpm --filter @objectstack/spec test -> 'Test Files 704 passed (704)', 'Tests 21379 passed | 1 todo'. lint: pnpm --filter @objectstack/lint test -> 'Test Files 152 passed (152)', 'Tests 7009 passed'. dogfood expression-conformance.test.ts -> 8 passed (the new slot is discovered and classified). cli --project unit on 4 i18n suites -> 74 passed; the integration layer is declared to CI. typecheck spec + lint -> Done. dogfood typecheck: 12 TS7016/TS2307 errors, all on unbuilt sibling declarations (cloud-connection, cli/console), none in the touched ledger file: NOT MEASURED for that package, environment. check:generated flagged api-surface, export-origins and docs stale; fixed with --fix (5 new exports plus the confirmText rows). Derived battery: dispatch-gates --commands gives 111 families at 2a0f011; --ran reconciliation gives '110 run, 1 NOT-MEASURED, 0 UNRUN'. The NOT MEASURED one is check:dual-build-cjs-loads: exit 3, prerequisite not met (needs every package built, about 90); it belongs to CI. check:dts-closure went green after rebuilding the declarations of two untouched packages (cloud-connection, plugin-dev). eslint narrowed and proven: --print-config shows all 15 touched TS files in the linted population; eslint --no-inline-config --format json gives 15 files, 0 errors, 0 warnings; invariance holds because the config enables no type-aware linting (eslint.config.mjs:325). Ablation, done after committing: scripts/ablation-replace.mjs removed 'view' from runtimeTypes in authoring-rules.ts (anchor 1->0, blob 37ae7e87aacf -> 82d615ca2df0). The 3 gate positives in validate-expressions.bulk-confirm-text.test.ts went red ('Tests 3 failed | 11 passed') and the 3 controls stayed green. The restore leg brought the blob back to HEAD 37ae7e87aacf and git diff HEAD was empty. No dist was involved, since lint tests read src.",
    "mcp_calls": "0",
    "api_writes": "5 REST writes on GitHub. Two seat-side POST /repos/objectstack-ai/objectstack/dispatches calls to the fleet-write relay (one for the PR, one for this comment). The relay executed POST /repos/objectstack-ai/objectstack/pulls (draft PR 24947), POST /repos//issues/24947/assignees (baozhoutao), and POST /repos//issues/24798/comments (this report). Plus 9 git pushes, which are not REST. Label writes: 0, because the dispatch named no labels and skip-changeset does not apply (the PR publishes).",
    "open_questions": [
    {
    "question": "Landing order. The retyped slot changes the SERVED shape: a parsed confirmText is {dialect:'template', source} (os compile serializes result.data). objectui's bulk dialog renders def.confirmText as a React child (plugin-grid BulkActionDialog.tsx:412, with BulkActionDef.confirmText?: string hand-declared in types/objectql.ts:680, objectui origin/main 1ca759a16). A console without an envelope reader therefore breaks the confirm step of every bulk def carrying confirmText (showcase project.view.ts has 6), and no typecheck sees it. objectui#13183 is pm:blocked on this card, which is the reverse of the order this needs. When may PR 24947 land?",
    "options": [
    "A: hold the merge until objectui ships an envelope reader (string | {source}, the shape formatTitleTemplate already reads for titleFormat) and the .objectui-sha pin bump lands with or before it. Unblock #13183's first PR now against the spec branch or main. Cost: one small objectui PR moved ahead of the spec release.",
    "B: land now, accept a broken bulk-confirm dialog in the pinned console until #13183 lands. Cost: a user-visible regression on main and in any release cut in between.",
    "C: keep the parsed output a plain string (normalize envelope to source). Cost: reverses ruling item 1's 'bare string is shorthand for the envelope', which needs a new ruling."
    ],
    "recommendation": "A, because it keeps ruling item 1 as written and avoids a main-visible regression; the objectui reader is a few lines and already has a precedent (formatTitleTemplate)."
    },
    {
    "question": "os i18n extract and an envelope-form confirmText. Measured at collectExpectedEntries (packages/cli/src/utils/i18n-extract.ts), the function the extract and coverage commands call: a confirmText written as {dialect:'template', source} is recorded exactly like an absent key (no sourceValue, no inline), so the newly accepted envelope form gets no translation seed. The file is outside this claim's surface, so I did not edit it. Fix in this PR, or in the sibling?",
    "options": [
    "A: extend the claim to i18n-extract.ts and read templateSlotSource(def.confirmText) in pushBulkActionDefs, plus one pin. Cost: one more commit on PR 24947.",
    "B: carry it on #24814, which types nine more text slots that the same extractor reads. Cost: the envelope-form gap ships if this PR is released first."
    ],
    "recommendation": "A, because the gap is made reachable by this PR's own widening, and a one-line read of the same exported helper closes it."
    },
    {
    "question": "The 19.0 half under the jia rule: flip the bulk confirmText warnings (unbound root, single-brace token, hole compile error, blank) to errors, and drop the blank-string arm from BulkConfirmTextSchema. This needs a card for 19.0.",
    "options": [
    "A: the seat files one 19.0 card naming both halves (validate-expressions.ts bulkConfirmTextIssues severity; bulk-action.zod.ts blank arm), Blocked-by this card.",
    "B: fold it into #24814's 19.0 follow-up."
    ],
    "recommendation": "A, because it is one mechanical flip with its own changeset (a narrowing), and it is easier to schedule on its own."
    }
    ],
    "out_of_scope_findings": [
    "class: a · reach: public door, measured at its callee: collectExpectedEntries (packages/cli/src/utils/i18n-extract.ts, called by os i18n extract / coverage) records bulkActionDefs[].confirmText = {dialect:'template', source:'Update {{count}}?'} as [{path:[objects,crm_account,_views,all,bulkActions,update_tier,confirmText], source:'view', objectName:'crm_account'}], identical to an absent key, while the bare string gets sourceValue and inline · evidence: probe via tsx on the worktree at 2a0f011; cause: authoredEvidence reads a string or an InlineLocaleMapSchema map only · dedupe words: i18n extract envelope, confirmText template envelope seed, authoredEvidence template slot, bulkActionDefs translation",
    "carrier: #24814 (the sibling sweep typing nine more text slots) · noted, not filed: translated bulk confirmText strings in translation bundles (objects.OBJ._views.VIEW.bulkActions.DEF.confirmText) are not judged for holes; ruling item 1 binds the def's slot only"
    ]
    }

  7. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    Seat hold · domain:spec seat 3 (#18883) · baozhoutao · session session_01D6F9axfoPzR7t8G1J7Ytzz · 2026-10-20T17:25Z · ⛔ Not a claim.

    PR #24947 (ruling B, step 1, the spec half) is done and stays a draft. Landing it in the order ruling item 2 sets would break the console.

    Measured by the dev (6296915438):

    • The served shape changes. With confirmText a template slot, a parsed value is { dialect: 'template', source }, and os compile serializes result.data.
    • objectui renders it as a string. objectui plugin-grid/src/components/BulkActionDialog.tsx:412 renders def.confirmText as a React child. BulkActionDef.confirmText?: string is hand-declared in types/objectql.ts:680.
    • So a console without an envelope reader breaks the confirm step of every bulk def that carries confirmText: 6 in showcase project.view.ts, plus HotCRM's 12 strings. No typecheck sees it.

    The order conflict. Ruling 6285948976 item 2 keeps objectstack-ai/objectui#13183 pm:blocked on this card "until the spec release is installable there". That is the reverse of what this change needs.

    The seat's reading (an engineering order, not a product change):

    1. objectui's first fix(spec): refuse an unrecognized liveness status, and make the fold preserve the walk's total #13183 PR ships an envelope reader, string | { source }. The precedent is the shape formatTitleTemplate already reads for titleFormat. It needs no spec release, because objectui reads both shapes.
    2. .objectui-sha moves past it.
    3. PR feat(spec,lint): a bulk action's confirmText is a template slot over count and its params, judged at validate, compile and the publish gate #24947 lands.

    Option C, keeping the parsed output a plain string, would reverse ruling item 1 and is not taken. The order change goes to the maintainer in the seat's session chat at 2026-10-20T17:25Z. A copy goes on objectstack-ai/objectui#13183.

    Also in this PR: the i18n extractor gap the dev measured is being fixed (i18n-extract.ts, envelope-form seed). The 19.0 half (warnings → errors, the blank arm) is filed by the seat as its own card when #24947 lands.


    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:recordsBusiness objects, records, the views that show data, usable forms, searchbugSomething isn't workingdomain:specpm:dispatchedpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions