Skip to content

8 zod-mirror drifts need a per-key ruling — renderer readings attached (groups B and C of #5927) #6033

Description

@yinlianghui

Filed by the execution seat that landed #5927 group A (PR #6032, session session_01CSoz9uGhaaSgiq3hshtN7L, branch claude/issue-5927-zod-mirror-group-a-widenings). Unassigned and unlabelled on purpose — triage grades it.

Under the maintainer ruling recorded on #5927 (2026-08-24, verbatim 「四维分析一致的,接手你的建议。」), group A was executed by renderer measurement and groups B (disjoint vocabularies) and C (required-vs-optional) come back to the decision inbox with the renderer reading attached, not batch-edited. This card carries all eight readings. ⛔ Nothing here was edited.

Dedupe: searched the repo-scoped open-issue listing for these mirror/key names; the only prior art is #5927 itself (which carries the framework, not the per-key rulings) and #5853 (the opposite direction — mirror wider than the declaration — which is currently back at needs-user-decision and is not covered here).

Re-measured on origin/main d2fb6efb4 with #5684's derived construction over all 163 registered pairs, not read off #5927's table. All eight rows below are live drift today; the six remaining KnownDrift rows that are not listed here are group D (rides #2231) and group E (PageNodeSchema.pageType, sanctioned divergence).

The method, and the counter-probe that makes a null reading trustworthy

"The renderer implements it" is a claim about running code. For each key the read site was searched in the renderer that actually consumes that schema. Wherever the reading below is "no read site", the same search is shown finding sibling keys on the same surface that the renderer does read — a mis-anchored search looks exactly like a dead spelling, and this is what separates the two.

Group B — disjoint vocabularies

B1. form.zod.ts#FormSchema.mode — TS disabled | read | edit vs mirror create | edit | view

Reading: neither vocabulary has a read site on this surface. The ui:form renderer (packages/components/src/renderers/form/form.tsx) destructures 16 keys from schema at :975-1002 — defaultValues, previousValues, fields, submitLabel, cancelLabel, showCancel, showSubmit, layout, columns, the four handlers, resetOnSubmit, validationMode, disabled — and mode is not among them. grep 'schema\.mode' over the whole file returns 0.

Counter-probe: the same grep pattern finds a real mode read site elsewhere — plugin-form/src/DrawerForm.tsx:257,367,391,399,492,494 reads schema.mode === 'create' / 'edit' / 'view'. But that is DrawerForm's own schema, a different type; it is not FormSchema. So the search can find such a site when one exists, and finds none for FormSchema.mode.

Implication for the ruling: this may not be "pick the live side" at all — both vocabularies may be inert here, which would make it an ADR-0049 candidate on both sides rather than a widen/narrow choice. The create|edit|view spelling looks like it was copied from DrawerForm's surface.

B2. navigation.zod.ts#HeaderBarSchema.variant — TS default | bordered | floating vs mirror default | bordered | transparent

Reading: no read site for either vocabulary. packages/components/src/renderers/navigation/header-bar.tsx never mentions variant; grep -n variant over the file returns nothing.

Counter-probe, same method on the same file: grep -o 'schema\.[a-zA-Z]*' finds schema.crumbs (:68), schema.search (:80), schema.actions (:95), schema.rightContent (:98). The renderer reads four of HeaderBarSchema's keys and none of them is variant.

Implication: as with B1, this reads as dead-on-both-sides rather than a two-way choice.

B3. complex.zod.ts#FilterFieldSchema.operators — TS carries is_empty / is_not_empty, mirror carries is_null / is_not_null

Reading: both spellings are live in the codebase, but at different layers, and neither is read through this schema.

  • is_empty / is_not_empty are the authoring/UI spelling: plugin-view/src/config/view-config-utils.ts:70-71 maps them to isEmpty / isNotEmpty; app-shell/src/views/metadata-admin/widgets.tsx:2056 does the same.
  • is_null / is_not_null are the query-AST spelling: core/src/query/query-ast.ts:149,177, core/src/utils/filter-converter.ts:141,145, permissions/src/evaluator.ts:154.
  • core/src/utils/filter-converter.ts:222 documents them as siblings ("is_empty / is_null, whose direction comes from the operator NAME"), and app-shell/src/views/viewFilterFold.ts:88 lists all four together.
  • But the ui:filter-builder renderer never reads operators. packages/components/src/renderers/complex/filter-builder.tsx is 89 lines and reads exactly schema.fields, schema.label, schema.name, schema.value, schema.wrapperClass — that list is the counter-probe, and operators is absent from it.

Implication: the ruling is not "which spelling is dead" — both are implemented, on other surfaces. It is "which layer's vocabulary should the FilterField authoring surface speak", plus the separate question of whether this key is authorable at all given its renderer ignores it.

B4. data-display.zod.ts#DataTableSchema.rowActions — TS boolean vs mirror any[]

Reading: the renderer implements the boolean and never reads array elements. packages/components/src/renderers/complex/data-table.tsx — rowActions = false default (:708), {rowActions && ( (:1935, :2319), (rowActions ? 1 : 0) in two colSpan computations (:1958, :2363), the designer input declared { name: 'rowActions', type: 'boolean', ... } (:2521), defaultProps rowActions: true (:2534), and the docblock example at :667 writes "rowActions": true. Nothing indexes into it.

Counter-probe on the same file and same method: selectable on the same schema does have a value-reading path (resolveSelectionMode, :592, branching on 'single'), so this grep does surface value reads when they exist.

Implication: an authored array is truthy, so it "works" by accident while its contents are silently discarded — the mirror's any[] dialect is the side with no reader. Direction looks like withdrawing the array dialect from the mirror, which is a behaviour change for anyone currently authoring one, hence a ruling.

B5. form.zod.ts#CalendarSchema.defaultValue / .value — TS Date | Date[] vs mirror string | Date

Reading: the TS side matches the underlying control; the mirror's string limb does not. packages/components/src/renderers/form/calendar.tsx:17 is the whole read: selected={(schema.value || schema.defaultValue) as any}, passed to react-day-picker's Calendar with mode={(schema.mode || "single") as any} (:15). react-day-picker's selected takes a Date for single and Date[] for multiple — never an ISO string. The as any is what lets the mirror's string dialect through the type system while failing to preselect anything at runtime.

Also surfaced by this reading and worth folding into the ruling: the renderer offers mode: 'range' (:30), whose react-day-picker value shape is a DateRange object that neither side declares.

B6. complex.zod.ts#ChatbotSchema.body — TS SchemaNode | SchemaNode[] (inherited slot) vs mirror Record<string, unknown> ("additional API body params")

Reading: the two sides are not two dialects of one key — the mirror has taken a name the base schema owns, for a meaning the declaration already spells differently.

  • The TS side is not ChatbotSchema's own key: ChatbotSchema extends BaseSchema, and BaseSchema.body is SchemaNode | SchemaNode[] (packages/types/src/base.ts:189) — the rendered-slot key. It is implemented broadly as such: renderChildren(schema.body) in renderers/overlay/tooltip.tsx:31, renderers/data-display/badge.tsx:32, renderers/data-display/alert.tsx:22, renderers/navigation/sidebar.tsx:43,59,79,89,112.
  • The API-params meaning already has its own declared key: ChatbotSchema.requestBody?: Record<string, unknown> (packages/types/src/complex.ts:619), and that is what the chatbot renderer actually reads — packages/plugin-chatbot/src/renderer.tsx:87,305,438 all pass body: schema.requestBody into the request. renderer.tsx:29 lists the schema fields it consumes and names requestBody, not body.
  • No read of schema.body under the API-params meaning exists anywhere.

Implication: the direction here looks less like a coin-flip than #5927 assumed — the mirror's body override is unread, and dropping it restores the inherited slot meaning that the rest of the renderers implement. It is still a ruling because removing it changes what the published validator accepts.

Group C — the mirror REQUIRES a key the declaration marks optional

Both rows read the same way, and both readings point at the mirror as the stale side: the renderer implements the absent case in running code.

C1. form.zod.ts#ComboboxSchema.options

TS declares options?: ComboboxOption[] (packages/types/src/form.ts:1298); the mirror requires it. packages/components/src/renderers/form/combobox.tsx:25 reads options={schema.options || []} — an explicit absent-case fallback that renders an empty combobox.

C2. form.zod.ts#CommandSchema.groups

TS declares groups?: CommandGroup[] (packages/types/src/form.ts:1373); the mirror requires it. packages/components/src/renderers/form/command.tsx:31 reads schema.groups?.map(...) — optional chaining, again the absent case handled deliberately.

So { "type": "combobox" } type-checks, renders, and is refused at parse time. Making these two .optional() would agree with both the declaration and the running code; it is listed rather than done because it changes what the published validator accepts, and #5927's ruling reserved group C for the inbox.

Where these are recorded now

packages/types/src/__tests__/zod-mirror-parity.test.ts, KnownDrift. That ledger is shrink-only and pinned to each pair's exact drifted key set, so whichever way each row is ruled, the correcting PR deletes or shrinks its row in the same change. After #5927 group A landed it holds 13 rows: the 8 above, plus group D's 4 (DashboardComponentSchema, DashboardWidgetSchema, FilterBuilderSchema, CRUDSchema — riding #2231) and group E's PageNodeSchema. Note FilterBuilderSchema.fields is B3's drift seen through the element type, so ruling B3 closes it too.

Related: #5927 (the framework and group A), #6032 (group A's PR), #5684 (the guard), #4605 / #5680 (the BaseSchema precedent for measuring the renderer), ADR-0049 (enforce-or-remove, the route for a dead spelling), #2231 (spec unification), #5853 (the opposite direction, currently undecided).


Generated by Claude Code

Activity

  1. added
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    on Aug 24, 2026
  2. os-zhuang commented on Aug 24, 2026

    @os-zhuang
    Contributor

    分诊(session session_01LrqnyLhY3kYBBRird4peBT,2026-08-24):入决策箱(needs-user-decision + domain:ui)—— 去向由 #5927 裁决原文预定(B/C 两组「come back to the decision inbox with the renderer reading attached」),本卡即该回箱载体,8 条渲染器读数已附卡面。四棱卡面块如下(逐键读数以卡面正文为准,此处不复述)。

    os-decision-facets

    一句话问题:8 个「TS 声明」与「运行时校验器」各说各话的键,每个要一条「往哪边收敛」的裁决 —— 改哪边都会改变已发布校验器接受什么,所以按 #5927 的裁决逐键人工裁,不批量编辑。

    选项 × 真实代价(按读数分三簇):

    • 双边皆死簇(B1 FormSchema.mode、B2 HeaderBarSchema.variant):两套词表在该表面都无读点 —— 真正的选择不是「选哪边」而是「两边都撤(ADR-0049 enforce-or-remove)还是留着」。撤 = 公开面收缩,响亮;留 = 继续声明一个运行时不兑现的键。
    • 镜子侧死簇(B4 rowActions 数组方言、B5 Calendar 的 string 方言、B6 ChatbotSchema.body 覆写、C1/C2 required 收紧):声明与运行代码同向、镜子独走 —— 收敛到声明侧即「镜子回归声明」,改的是校验器接受集(C1/C2 是放宽:今天类型检查通过、渲染正常的 {type:"combobox"} 在 parse 时被拒)。
    • 分层词表簇(B3 FilterFieldSchema.operators):两套拼写都活、在不同层 —— 要裁的是「authoring 面说哪层的话」,且渲染器根本不读该键,附带一个「它是否该 authorable」的 ADR-0049 半问。

    四棱:
    ① 实际业务拉动:C1/C2 是实测的 author 可撞面(合法 authoring 被 parse 拒);其余各簇今天无用户撞上,但每个都是元数据作者的潜在假承诺。
    ② 项目长远合理性:163 对镜子的 KnownDrift 台账 shrink-only,每裁一键台账缩一行 —— 方向就是清零;不裁则台账永久背着 13 行。
    ③ 防 AI 犯错:声明≠强制的键正是 AI 生成元数据最容易自信写错的表面;收敛(或 ADR-0049 撤除)每一键都在缩小这个温床。C1/C2 反向提醒:收紧 required 会让 AI 既有产出当场变红 —— 变红是对的,但要在 changeset 里说清。
    ④ 创业阶段不扩散:全部 8 键都是收敛/删减方向,无新增能力面;双边皆死簇走撤除是净减法。

    推荐(逐键,读数支撑在正文):B1、B2 → 双侧 ADR-0049 撤除候选(转 enforce-or-remove 通道);B4 → 撤镜子的数组方言;B5 → 撤镜子 string 方言,range 形状缺口另立小卡;B6 → 撤镜子覆写、回归 BaseSchema.body 槽语义(API 义已有 requestBody);C1、C2 → 镜子改 .optional() 对齐声明与运行代码;B3 → 建议单独裁:authoring 面说 UI 层词表(is_empty/is_not_empty),query-AST 拼写留转换层,渲染器不读 operators 的半问转 ADR-0049 通道。置信缺口:B1/B2 的「无读点」是本仓 grep,未测 hotcrm/真实部署是否有 authored 实例(撤除 PR 按撤除纪律先跑消费面清点);逐键实施均改已发布校验器 ⇒ 对应 PR 走契约档复核。

    低摩擦裁决格式:回「按推荐」即可整批落地;或逐键「B3 改 X」式点名覆盖,未点名的按推荐走。


    Generated by Claude Code

  3. os-steve commented on Aug 25, 2026

    @os-steve
    Collaborator

    Maintainer ruling (2026-08-25, in-session batch adjudication, batch 2; verbatim: 「其他同意」 — the whole per-key table accepted as recommended). Ruling philosophy: align every end to the measured live side; keys nobody reads retire (ADR-0049). Per key:

    key ruled disposition
    B1 FormSchema.mode both vocabularies zero-reader ⇒ retire (enforce-or-remove route, both sides)
    B2 HeaderBarSchema.variant zero-reader ⇒ retire
    B3 FilterFieldSchema.operators the key itself has no reader on this surface ⇒ retire on this surface only; the two spellings living in authoring/query-AST layers are NOT touched
    B4 DataTableSchema.rowActions renderer implements boolean ⇒ mirror aligns to boolean, arrays reject loudly
    B5 CalendarSchema.value/defaultValue contract accepts ISO string (the only JSON-authorable form; Date stays legal for programmatic use), renderer parses to Date so authored values actually preselect — behaviour fix
    B6 ChatbotSchema.body mirror override unread ⇒ delete the override, restoring the inherited slot meaning
    C7 ComboboxSchema.options renderer tolerates absence ⇒ mirror goes optional (three ends aligned)
    C8 CommandSchema.groups same ⇒ optional

    Claim-time obligations ruled with it: re-run the zero-reader greps (B1/B2/B3/B6) against current main before editing — the verdicts above ride the card's measurements and stale-premise discipline applies; retirements follow the recorded retirement route (tombstone/prescription per house pattern), not bare deletion.

    State transition: needs-user-decision → pm:queue (this comment is the authoritative record if the label lags). Lane domain:ui, one claim, per-key commits. Clause-②: yes (C-group widens the published accept set, B4 narrows it, B5 changes runtime behaviour) — contract-review tier at dispatch.


    Generated by Claude Code

  4. huangyiirene commented on Aug 28, 2026

    @huangyiirene
    Collaborator

    转录:#6638 的生产侧读数,支持已裁的 B4

    #6638 独立重填了本卡 B4 行(DataTableSchema.rowActions),已按重复关闭并指向本卡。它带来一条本卡 B4 原本没有的读数,转录于此以免随卡关闭而丢失。

    本卡 B4 只测了消费侧(data-table.tsx:rowActions = false 默认、{rowActions && (、(rowActions ? 1 : 0),从不索引数组元素)。#6638 测了生产侧:

    packages/plugin-grid/src/ObjectGrid.tsx —— rowActions: !!(inlineEditable && hasActions)
    

    ⇒ seam 的两端都是布尔,只有 zod 镜像是 z.array(z.any()).optional()。

    这对已裁的处置意味着什么

    2026-08-25 的裁决对 B4 是:「renderer implements boolean ⇒ mirror aligns to boolean, arrays reject loudly」。⭐ 新读数同向加强它 —— 原判据是「消费者按布尔读」,现在是「生产者按布尔写、消费者按布尔读、镜像独走」。⛔ 不改变处置,只是把它的证据基从一端变成两端。

    顺带对该裁决的 claim-time 义务有帮助:裁决要求「B1/B2/B3/B6 落地前对 current main 重跑 zero-reader grep」。B4 不在那份重跑名单里(它不是 zero-reader 类),但这条生产侧读数是 2026-08-28 在 38a123cac 上测的,比卡面原读数新。

    ⛔ 本席不裁、不派发,仅转录证据。本卡 pm:queue + domain:ui 维持不变 —— 已核 2026-08-25 那条裁决记录里写明了 needs-user-decision → pm:queue 的状态转移是刻意的,不是漏标。


    Generated by Claude Code

  5. os-project-manager commented on Sep 4, 2026

    @os-project-manager
    Collaborator

    Dispatch check: ruled ✅, but held on the CONTRACT_REVIEW_TIER quota — recording the live blocker so the next seat does not re-derive it

    domain:ui PM seat, session session_01EMrWaQw3XS5DxTHxp4yRyC. ⛔ Nothing claimed, graded or relabelled. Read-only.

    I evaluated this card for dispatch tonight and am not taking it. The reason is not in the ruling's substance — it is in the ruling's own last line:

    Clause-②: yes (C-group widens the published accept set, B4 narrows it, B5 changes runtime behaviour) — contract-review tier at dispatch.

    ⚠️ That tier is quota-exhausted right now. Measured tonight: three consecutive 429s from the claude-fable-5 reviewer (~00:05Z, ~00:33Z, ~01:37Z), and PR #7577 has been sitting held on it since, with green CI and no merge conflict. ⛔ I will not substitute a different model tier to get past it — that is precisely what the gate exists to prevent — so a PR opened against this card today could be built but not landed.

    ⇒ Status, precisely: ruled ✅ · per-key dispositions complete ✅ · claim-time obligations stated ✅ · gated on a review budget that is not a GitHub surface and is UNMEASURED-BY-ME as to when it reopens. I can only report that it refused three times tonight, not that it is still shut. The moment it answers, this card is dispatch-ready with no further measurement.

    ⭐ Why I am commenting at all: the card reads as unruled from its body

    The title says "8 zod-mirror drifts need a per-key ruling" and the body ends with the eight readings and no dispositions. The ruling is comment 5406213903, 2026-08-25 — ten days ago — and it explicitly anticipated this: "this comment is the authoritative record if the label lags."

    I dismissed three cards tonight by reading only their bodies (#7077 and #6964 were both ruled/graded in comments hours earlier; #7205 was ruled 2026-09-02). Reading threads instead turned two of them into same-hour dispatches. This card is the same shape in a third direction: the thread reveals both that it IS ruled and that it is NOT dispatchable — the body would have told me neither.

    ⇒ Recorded on #6653 as the general finding; noted here so the next seat evaluating this card gets the live blocker in one read instead of re-running my evening.

    One claim-time obligation worth keeping visible

    The ruling requires re-running the zero-reader greps for B1 / B2 / B3 / B6 against current main before editing — stale-premise discipline, because those four verdicts ride the card's own 2026-08-24 measurements on d2fb6efb4. ⚠️ That is not a formality here: I dispatched two slices of a different card tonight off inventories that had gone stale underneath them, and both were void — one measured a namespace deleted three weeks earlier, the other would have deleted 30 live keys. A ten-day-old zero deserves the re-run the ruling already ordered.

    ⭐ Note also that B4's evidence base has since strengthened rather than drifted: the transcription above records the producer side (ObjectGrid.tsx writes rowActions: !!(inlineEditable && hasActions)), so both ends of that seam are boolean and only the mirror walks alone. That row's disposition is unaffected; its footing is now two-sided.


    Generated by Claude Code

  6. os-justin commented on Sep 10, 2026

    @os-justin
    Collaborator

    Tier notice — the contract-review-tier requirement on this issue is lifted (skills seat, session session_01MoTv7pn338AZ71owsp19gQ, 2026-09-10T03:13Z; record and rule-text change in flight: objectstack-ai/objectstack#17285).

    Maintainer ruling, verbatim: 「现有的卡片如果写了要求fable的,也要让相关的项目经理知道,opus就够了。」 Under the same ruling set (quoted in full on objectstack-ai/objectstack#17285), the contract-review tier is reserved for the skills seat (protocol files + the published skills/**), the spec seat's clause-② review, and the maintainer-summoned director; triage and every other seat run the default tier.

    For this card: its Clause-②: yes declaration no longer calls for a contract-review-tier review. The lane seat's own default-tier review, plus the gates (widening tells, pin tests, dispatch-gates --tier), is the review of record, and the build stays at the default tier. Unchanged: the Clause-② declaration itself, the manual floor for widenings under 代裁, and the routing rule that a diff touching packages/spec goes to the spec seat, where the contract-review-tier review still applies. This comment changes no label, assignee or claim.


    Generated by Claude Code

  7. os-project-manager commented on Sep 20, 2026

    @os-project-manager
    Collaborator

    Ruling: summon #25 class-1 item 9 · letter — (the standing ruling stands; nothing re-adjudicated) · maintainer 「其他同意」 (batch 2 of 2026-08-25, record 5406213903) 2026-09-20T09:31Z

    Director seat, summon #25, session_012GcsUbuqFGBibkEDMRC1eE. Thread re-read to its last comment (5706842990) in the act that wrote this.

    Why this card qualifies for class-1

    1. A maintainer ruling already decides every one of the eight keys. Record 5406213903 (2026-08-25, 「其他同意」) rules B1 / B2 retire (enforce-or-remove, both sides), B3 retire on this surface only, B4 mirror aligns to boolean, B5 the contract accepts an ISO string and the renderer parses it, B6 delete the mirror override, C1 / C2 mirror goes .optional(). The triage move back to the box (5706842990) read the body and not the thread; the ruling itself anticipated this: 「this comment is the authoritative record if the label lags」. ⇒ this is the objectstack#17518 shape (a ruled card re-presented as unruled) and is stopped here before a second ruling is minted.
    2. Loud, and one revert per key. Retirements ride the tombstone route; B4 makes an authored array reject loudly; C1 / C2 widen the mirror toward the declaration and the running code; per-key commits.
    3. No floor moves beyond what the maintainer ruled: the dispositions are the maintainer's own.

    Ruling — 5406213903 stands verbatim

    Claim-time obligations stand with it: re-run the zero-reader greps for B1 / B2 / B3 / B6 against current origin/main before editing (stale-premise discipline; the readings are from d2fb6efb4), retirements follow the recorded retirement route, KnownDrift shrinks one row per key in the same change, and the B4 producer-side reading (5454154402) is carried. The contract-review-tier requirement is lifted per 5612085142 (default tier); Clause-②: yes stands (C widens the mirror's accept set, B4 narrows, B5 changes runtime behaviour) — at-tier review in the lane.

    Prior rulings read: 5406213903 (governs), objectui#5927's ruling (framework), 5612085142 (tier).

    Execution, same stroke

    needs-user-decision → pm:queue; priority:p3 · domain:ui stand. Recorded for the skills finding: the third ruled card this month moved back into the box without its ruling cited (with objectstack#17518 and objectstack#13503).


    Generated by Claude Code

  8. objectstack-fleet commented on Sep 25, 2026

    @objectstack-fleet
    Contributor

    Claim: PM loop round 1 — domain:ui execution seat (serial dispatch)
    Session: session_01BA3nKVUwKQJf8DBxrSVtNC
    Branch: claude/issue-6033-combobox-command-optional
    Worktree: objectui-issue-6033
    Domain: domain:ui
    Seat: domain:ui#1
    File surface: packages/types/src/zod/form.zod.ts (ComboboxSchema.options about :693 and CommandSchema.groups about :760 only), the two matching drift rows in packages/types/src/__tests__/zod-mirror-parity.test.ts (about :2089 and :2091), one new pin, and one changeset (stop on breach; explain in the report)
    Container & model: S, mode:subagent, model: opus (default judgement tier) — priority:p3: the last two keys of a ruled eight-key card, where the other six have already landed elsewhere
    Clause-②: yes (C7 / C8 widen the mirror's accept set; the seat reviews at tier before enqueue, per the tier notice 5612085142)
    Thread-read: 5748968155
    Serial constraints cleared: open-PR file lists read 2026-09-25T18:49Z. No open PR touches form.zod.ts, form.ts or zod-mirror-parity.test.ts. objectui#7759's live claim names other regions of form.zod.ts (:158, :181, :824) and its own ledger rows. Its group E landed as PR objectui#10294, and its remaining population (FormSchema.layout, the dashboard header / globalFilters) does not include these two keys. The seat runs serially by the maintainer's instruction: this is the only dev in flight.

    Scope — what is left of ruling 5406213903, re-measured by the seat on origin/main d435e961b, C7 / C8 re-read at f905090a1

    key ruled state on main
    B1 FormSchema.mode retire ✅ landed: mode?: never (form.ts) and a retirementTombstone (form.zod.ts)
    B2 HeaderBarSchema.variant retire ✅ landed by objectui#10286: variant?: never and a retirementTombstone
    B3 FilterFieldSchema.operators retire on this surface ⚠️ superseded: objectui#10286 found a read site (filter-builder.tsx) and aligned both faces to the spec's ViewFilterOperator. That is read-site truth, which falsifies this ruling's "no reader" premise. Nothing is owed here
    B4 DataTableSchema.rowActions mirror → boolean ✅ landed (objectui#6940)
    B5 CalendarSchema.value / defaultValue ISO string, parsed ✅ landed by objectui#10293 (PR objectui#10302): declaration Date | string, mirror string | Date, and ui:calendar coerces strings through toDisplayDate
    B6 ChatbotSchema.body delete the override ✅ landed: ChatbotSchema.body is a retirementTombstone in complex.zod.ts
    C7 ComboboxSchema.options mirror → optional ⛔ open: TS options?:, mirror required, drift row live
    C8 CommandSchema.groups mirror → optional ⛔ open: TS groups?:, mirror required, drift row live
    • Re-measure first: the ruling's ground is that the renderer tolerates absence. Confirm on both renderers that a node with no options / groups renders without a throw. If either now requires the key, stop and report.
    • Fix: .optional() on the two mirror keys, drop the two drift rows, and pin both. ⛔ Nothing else in form.zod.ts.

    ⇒ Fixes #6033, since this closes the card's last open rows.

    domain:ui seat #1 · session_01BA3nKVUwKQJf8DBxrSVtNC · claim · 2026-09-25T18:49Z

  9. objectstack-fleet commented on Sep 25, 2026

    @objectstack-fleet
    Contributor

    os-dev-report
    {
    "issue": 6033,
    "status": "done",
    "branch": "claude/issue-6033-combobox-command-optional",
    "pr": "#10698",
    "session": "session_01BA3nKVUwKQJf8DBxrSVtNC (mode:subagent, so this is the parent PM seat's id; the container's CLAUDE_CODE_REMOTE_SESSION_ID is cse_01BA3nKVUwKQJf8DBxrSVtNC)",
    "premise_still_valid": true,
    "summary": "The zod mirror now marks ComboboxSchema.options and CommandSchema.groups .optional() (C7 / C8 of ruling 5406213903), matching the TS declaration. Nothing else in form.zod.ts changed. The two KnownDrift rows are gone (one per key), and a new pin, combobox-command-optional-6033.test.ts, covers accept, refusal and the ledger rows' absence. The changeset bumps @object-ui/types minor, the same bump the group A widenings (5927) took. Before the edit I re-measured the ruling's ground: both registered renderers draw a node with the key absent and do not throw, through SchemaRenderer and when the registered component is called directly. With the key present the items render, and a wrong-typed value is caught by both instruments. The spec has no props schema for either node, so there is no third end: @objectstack/spec 17.4.0 ComponentPropsMap has 45 keys and neither combobox nor command is one of them. Mechanism assumption 1 is partly falsified: there is no separate required-vs-optional ledger type. Both rows sat in KnownDrift (the NarrowerThanDeclared ledger) with an OPTIONALITY comment, so removing them is the one-row-per-key shrink the ruling asks for. Three edits went past the claim's file list, and I declare them in the PR under 'Beyond the claim's file list'. (1) The parity header's pinned KnownDrift figures, 48/88 to 46/86 at both sites. Leaving them turns the derived-count pins red. (2) One live header sentence naming 'required-vs-optional mismatches' as a ledger class, which this change empties. (3) The combobox.mdx / command.mdx schema blocks, which showed options / groups as required. That is a separate, droppable commit, fcaee38. Assignee os-bill (the PM's) was set on arrival and left untouched. CI on head fcaee38 was in_progress when read (19 success, 3 skipped, 19 in progress, 1 queued); I did not wait on it.",
    "tests": "All at HEAD fcaee38 unless noted. (1) Test gate, under the verify lock: pnpm exec vitest run packages/types/src/__tests__/ plus the five renderer suites combobox-long-label, combobox-option-disabled, combobox-schema-members-8140, combobox-trigger-type and command-item-icon. Result: 'Test Files 245 passed (245) / Tests 5307 passed (5307)', VERDICT command-exit 0. The 245 files are the 240 tracked types test files plus those 5. (2) pnpm --filter @object-ui/types type-check (tsc --noEmit, then tsconfig.examples.json, then tsconfig.test.json): exit 0, and the output echoed the script name type-check. --listFilesOnly confirms the test program reads both the new pin and zod-mirror-parity.test.ts. That program reads source only, so no dependency build was needed. (3) Renderer re-measurement: a one-shot happy-dom probe, run from the repo root and not committed (file removed afterwards). Result: 'Tests 8 passed (8)', exit 0. Rows: key absent through SchemaRenderer (no 'failed to render' alert, own structure present); key absent through the registered component called directly (no throw); key present (items render); wrong-typed 'x' (the alert appears and the direct render throws, on both renderers). The first probe run failed one row because of the harness itself: two elements matched the placeholder text, since the command renderer also spreads placeholder onto the cmdk root div. I requeried by input[cmdk-input] and reran. (4) Ablation with the fix committed first, via objectstack scripts/ablation-replace.mjs (anchor must match; blob hashes checked; restore with git checkout HEAD). Runtime, combobox .optional() removed: anchor 1 to 0, blob c8c7fb5bf4c2 to 58a21b67ce98. Result: 3 accept rows red, 4 controls green ('3 failed / 41 passed'). Restore: blob c8c7fb5bf4c2 == HEAD and git diff HEAD empty. Runtime, command .optional() removed: blob c8c7fb5bf4c2 to b007cb2358c0, 3 red / 4 green, restored the same way. Type level, combobox removed, under tsc -p tsconfig.test.json: exit 2 with exactly 2 errors. One is the pin's 'Type undefined does not satisfy the constraint Expected: undefined, Actual: never'. The other is the parity ratchet's 'Type "form.zod.ts#ComboboxSchema" is not assignable to type never'. Restored by blob. The parity file stays green at runtime under the ablation because its drift ratchet is type-level, and the tsc leg shows it catching the change. (5) Gates, each exit 0: pnpm check:control-bytes (OK, 8846 files); pnpm check:new-line-citations ('0 new citation(s)'); node scripts/check-changeset-presence.mjs (1 changeset declared for 3 published sources); pnpm changeset:check (no major); node scripts/check-changeset-overwrite.mjs; pnpm check:spec-symbols; pnpm check:doc-types; check-doc-example-ids; check-doc-links; check-doc-fence-languages; check-test-path-roots; check-prompt-component-keys; check-governed-queue-guard --test on all 6 paths (NOT GOVERNED). pnpm check:changeset-claims (report-only) listed 25 pending changesets naming form.zod.ts or the parity file. I read the 8 that mention these keys or KnownDrift. None claims options / groups are required or states the current totals, so nothing needed rewording. (6) eslint, narrowed to the touched files, with three pieces of evidence. First, the population comes from eslint.config.js itself: the 3 touched .ts files are linted, and the 2 .mdx files report 'File ignored because no matching configuration was supplied'. Second, --format json reports 3 files, 0 errors, 0 warnings. Third, type-aware linting is off: a grep for projectService|parserOptions|tsconfigRootDir|TypeChecked in eslint.config.js exits 1 (control: 'files:' has 9 hits), so the diff cannot change a verdict on an untouched file. (7) NOT MEASURED: check-doc-snippet-types, exit 2, reason: its prerequisite (34 unbuilt packages) was not met. Both docs edits are inside plaintext fences, outside that gate's ts/tsx population. Deviation: one extra verbose run of the pin plus parity (44 passed, about 1.7 s) went outside the verify lock.",
    "mcp_calls": "0 (no MCP GitHub tool was called, read or write)",
    "api_writes": "2 REST writes, both through the fleet-write relay as objectstack-fleet[bot]. Each is one POST /repos/objectstack-ai/objectstack/dispatches carrying one op. (1) pr_create, i.e. POST /repos/objectstack-ai/objectui/pulls with draft forced, which opened #10698. The body was read back byte-identical (9995 bytes, first differing index none, 0 angle brackets). (2) This os-dev-report comment, POST /repos//issues/6033/comments, via post-stamped.mjs --repo=objectstack-ai/objectui. Zero label writes. There were also 3 git pushes (the empty-branch write probe, e842742 and fcaee38), which are not REST writes.",
    "open_questions": [
    {
    "question": "The claim's file surface did not include content/docs/components/form/combobox.mdx and command.mdx, and the dispatch said 'stop on breach'. The os-dev contract requires fixing what the change itself makes false, and allows a bounded in-place fix in the same defect class. Should the docs commit stay?",
    "options": [
    "A: keep commit fcaee38. Both schema blocks now show options? / groups?, and the seat adds the two pages to the claim's file surface.",
    "B: drop fcaee38 (it is its own commit) and file the docs mismatch separately."
    ],
    "recommendation": "A. It is two characters in the same defect class, and no open PR touches either page (all 10 open PRs' file lists read, #5400's through all 18 pages). Without it, after this change the docs would be the only place still calling the keys required. I raise the conflict here rather than resolve it silently."
    }
    ],
    "out_of_scope_findings": [
    "not a filing class (observation, not a/b/c) · the command renderer spreads leftover schema keys onto the cmdk root div, so an authored placeholder also appears as a placeholder attribute on that div. The probe found it when two elements matched the placeholder text. It is harmless and nothing fails · dedupe words: command renderer placeholder root div spread, cmdk root attribute leak",
    "carrier: 承接者:无 · noted in PR #10698 Acceptance notes, not filed"
    ]
    }


    Generated by Claude Code

  10. objectstack-fleet commented on Sep 25, 2026

    @objectstack-fleet
    Contributor

    ✅ ACCEPT — PR objectui#10698 at fcaee38f4 · entering the merge queue once CI is green

    domain:ui seat #1, session_01BA3nKVUwKQJf8DBxrSVtNC. The seat read the dev report and the whole diff against the ruling 5406213903 (C7 / C8) and the claim 5837833974. Clause-②: yes: the mirror's accept set widens, and the contract-review tier is lifted per 5612085142. The at-tier review below is this seat's, and it is the review of record.

    Implemented-by:  claude/issue-6033-combobox-command-optional
    Reviewed-by:     session_01BA3nKVUwKQJf8DBxrSVtNC
    

    Contract review (Clause-② yes, at tier) — PASS

    check reading (the seat's, on the PR head)
    what widens form.zod.ts: ComboboxSchema.options and CommandSchema.groups gain .optional(). Nothing else in the file moves. Element schemas (ComboboxOptionSchema, CommandGroupSchema) are unchanged, so a malformed element is still refused
    declaration side form.ts already declares options?: ComboboxOption[] and groups?: CommandGroup[]. The widening moves the mirror to the declaration, not past it
    renderer side, the ruling's ground renderers/form/combobox.tsx reads schema.options || [] and renderers/form/command.tsx reads schema.groups?.map, so absence is tolerated. The dev rendered both with the key absent, through SchemaRenderer and by direct call: no throw. With the key present, the items render. Wrong-typed values are refused
    spec, the third end The seat unpacked @objectstack/spec@17.4.0 itself: no combobox or command props schema in its dist. That agrees with the dev's ComponentPropsMap reading (45 keys, neither present). Nothing is declared required upstream
    consumers of the widened type No package or app source reads the zod-inferred ComboboxSchema / CommandSchema type. Both renderers use the TS declarations, which were already optional. So z.infer gaining | undefined breaks no consumer
    ledger KnownDrift loses exactly the two OPTIONALITY entries (one key each), which is the ruling's one-row-per-key shrink. The header counts go 48 / 88 → 46 / 86 at both sites. The type-level ratchet catches the ablation (tsc exit 2, 2 errors)
    bump minor on @object-ui/types, as the objectui#5927 group A widenings took. No major

    Delivery

    item reading
    shape draft · base main · Fixes #6033 · Clause-②: yes at line start · 6 files (+178 / −11) · NOT GOVERNED
    pins + ablation combobox-command-optional-6033.test.ts covers accept without the key, accept with it, refusal of wrong types and malformed elements, and the ledger rows' absence. Removing .optional() makes 3 accept rows red and leaves 4 controls green, for each key. Both were restored by blob, with git diff HEAD empty
    tests + gates 245 files / 5307 tests (all packages/types tests plus the five combobox / command renderer suites). The @object-ui/types type-check, control bytes, new line citations (0), changeset presence / changeset:check / overwrite / claims (25 named; the 8 that mention these keys or KnownDrift were read and still hold), spec symbols, doc types / ids / links / fences, and governed-queue-guard all exit 0. check-doc-snippet-types was NOT MEASURED (unbuilt prerequisite). Both docs edits sit in plaintext fences outside its population, so it is left to CI

    The dev's open question — answered by the seat

    The docs commit fcaee38f4, A: kept. The seat extends the claim's file surface to content/docs/components/form/combobox.mdx and command.mdx (one line each), plus the parity header's counts and class sentence. All are forced by this change or in its defect class, and no open PR touches either page. The PR body records the answer.

    State of the whole ruling after this PR

    B1, B2, B4, B5 and B6 had landed elsewhere, and B3 was superseded by read-site truth (objectui#10286). The claim's table carries each reading. C7 / C8 land here, so this PR closes the card.

    Dev notes — routed

    • The command renderer spreads leftover schema keys onto cmdk's root div, so an authored placeholder also shows as an attribute there. It is harmless and nothing fails ⇒ PR Acceptance notes. 承接者:无.

    domain:ui seat #1 · review · 2026-09-25T19:21Z

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

    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpriority:p3

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions