Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

[Decision] Is an authored handler EXPRESSION a supported dialect? 56 mirrors say function, 11 say string, and both appear in files that mirror the other #6182

Closed

Description

@yinlianghui-tw

Blocked-by: #7344

Escalated by the domain:devx @ objectui execution seat (#5748), PM session session_019b5UBNMtTzKbVtZZGvFuxe, from #6152's measurement. ⛔ This cannot be read off the code — it is a product question about the authoring contract, and the seat is not guessing it.

RULED 2026-08-25 (maintainer, batch adjudication, verbatim 「同意」): Option A — the handler-expression string dialect is NOT a supported authoring form. The authorable form for actions is the declarative ActionDef object; runtime callbacks are the programmatic face. The question below is therefore answered; what remains is the sweep, which is #6124's (the residual left after PR #7339 is #7344's — see the 2026-09-02 comment). This card closes when that sweep lands and a re-verification shows the 11 mirrors no longer carry the bare z.string() expression dialect — re-run the rg at the bottom of this card on the merged ref.

The measurement

A census of every on* key mirrored in packages/types/src/zod/ found 73 mirrored handler keys in two incompatible dialects:

dialect count
z.function() 56
z.string() — a handler expression 11 (onClick, onInstall, onPreview, onSave, onCancel, onBack, onViewChange, onChange)
z.any() 3
named schema 3

⚠️ Both dialects appear in files that also mirror the other, so this is not a clean per-file or per-surface split that could be inferred. Two answers coexist in one codebase with nothing declaring which is intended.

Why it needed a person

The two dialects make incompatible promises to an author:

  • z.function() cannot be satisfied by any serialized authored document. A JSON or YAML schema can never produce a function, so the key is declared-but-unauthorable — it exists for a runtime caller, not for a metadata author.
  • z.string() says the opposite: an author may write a handler as an expression, and something resolves it.

⭐ objectui#4453 already narrowed one such key to typeof === 'function', so an authored string handler is silently DROPPED (packages/plugin-calendar/src/calendar-view-renderer.tsx:322-331). The ruling confirms that direction: the 11 z.string() mirrors are advertising a capability the runtime discards — the "declared but not enforced" shape this whole family exists to close, pointing the other way.

What it decided

⭐ It changes the remedy for a lane already in flight. #6152 ruled that 23 callback-shaped declared-but-unmirrored keys are runtime-only and non-authorable (reclassified, not mirrored). Under the ruling as issued:

⚠️ #5155's standing ruling places rejection at the zod/publish boundary. That makes these mirrors the authoritative statement of what an author may write — which is exactly why two contradictory statements in that layer mattered more than they would in prose.

What is NOT being asked

⛔ Not asking anyone to pick a favourite. Not proposing a sweep here — the sweep is #6124's (residual: #7344), where the 11 mirrors get the same per-key treatment (renderer consumes a declarative object form ⇒ union with that form; otherwise runtime-only retirement). ⛔ #6152's option A is not blocked by this and should land on its own.

Reproduce (also the close condition — run it on the merged ref)

rg -n 'on[A-Z][A-Za-z]*:\s*z\.(function|string|any)' packages/types/src/zod/

Refs: #6124 / PR #7339 (the function-site sweep, landed 2026-09-02) · #7344 (the residual sweep this card is blocked on) · #6152 (where it was measured, and the lane it changes) · #6058 / PR #6149 (the ledger that surfaced it) · #4453 (an authored string handler is dropped) · #5155 (rejection lives at the zod/publish boundary) · #6122 / PR #6142 (no renderer reads 16 schema-level event slots).

Activity

  1. added
    domain:devxobjectui devx stream: fix lands on .github/, scripts/ or release pipeline — devx lane cross-repo
    on Aug 24, 2026
  2. os-zhuang commented on Aug 25, 2026

    @os-zhuang
    Contributor

    Triage(决策箱勤务):补标准四棱卡面块 —— 卡内测量完整,此块为收件箱标准件。

    ① 项目长远合理性:#5155 已裁「拒收活在 zod/publish 边界」,所以镜像层就是作者契约的权威声明 —— 两种方言并存等于契约自相矛盾;裁定其一(或把「分面共存」写成工具可读的规则)都收敛,不裁则这一族每张后续卡都要从 56:11 的票数里重新猜。
    ② 实际业务拉动:11 个 z.string() 镜像今天在广告一个 runtime 丢弃的能力 —— #4453 实测:作者写的 handler 表达式被 typeof === 'function' 静默 drop。写了白写、无声无息,正是这一族要关的洞。
    ③ 防 AI 写代码犯错:z.function() 对任何序列化文档不可满足(声明即不可作者化);若意图是表达式方言,56 个 function 镜像又挡住合法作者。现状同时教 AI 两套矛盾拼法 —— 哪个方向都比「不裁」强。
    ④ 创业阶段不扩散需求:裁 function-only 是收缩(11 个镜像走退役路线);裁表达式方言是大新面(#6152 的 23 键各要加表达式位 + 解析执行机制)。从紧默认前者,除非有真实作者场景拉动。
    推荐:function-only(11 个 z.string() 镜像按退役路线处理,#6152 的 23 键维持 runtime-only 重分类);「分面共存」仅当维护者明确要 authored-handler 能力,且须成文为工具可读规则。
    置信缺口:out-of-tree corpus 里是否已有作者在写 handler 表达式(未测);spec 侧对应键的方言(若有)未在本席核对。

    <!-- os-decision-facets -->


    Generated by Claude Code

  3. os-steve commented on Aug 25, 2026

    @os-steve
    Collaborator

    Maintainer ruling (2026-08-25, batch adjudication, batch 4; verbatim: 「同意」): Option A — the handler-expression string dialect is NOT a supported authoring form. The contract, coherent with the same-day #6124-A ruling: the authorable form for actions is the declarative ActionDef object; runtime callbacks are the programmatic face (function arm / runtime-only). Consequences ruled with it:

    State transition: needs-user-decision → pm:blocked with Blocked-by: #6124 — this card closes when the sweep lands and a re-verification shows the 11 mirrors no longer carry the bare z.string() expression dialect (re-run the card's own rg on the merged ref). Relabeled domain:ui (the close-condition surface is packages/types). Scope-extension note posted on #6124.


    Generated by Claude Code

  4. added
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    and removed
    domain:devxobjectui devx stream: fix lands on .github/, scripts/ or release pipeline — devx lane cross-repo
    on Aug 25, 2026
  5. os-sales commented on Aug 28, 2026

    @os-sales
    Collaborator

    Half-state repaired: Blocked-by: #6124 lifted into the body

    The 2026-08-25 maintainer ruling set this card's state transition (needs-user-decision → pm:blocked with Blocked-by: #6124) in a comment only. The label moved; the machine-readable line never landed. That makes the card invisible to the reverse index the unlock scan runs on, so it could not be put back when #6124's sweep lands — one of 17 such cards measured across this lane (#6653).

    Repaired, with no substance changed:

    • Blocked-by: #6124 as the first line.
    • The ruling quoted at the top, so a reader does not have to reach the second comment to learn the question is answered — the body still opened with "this cannot be read off the code / the seat is not guessing it", which now reads as though the decision were outstanding.
    • The consequences the ruling fixed are folded into the two sections that previously listed them as open alternatives, and the rg at the bottom is labelled as what it now is: the card's close condition, to be run on the merged ref.

    Verified while repairing: #6124 is open, pm:queue, domain:ui, unassigned — so the block is live and correctly held. It is also a dispatchable card in this lane with its direction already ruled, which this seat has noted as a refill candidate.


    Generated by Claude Code

  6. os-litant commented on Sep 2, 2026

    @os-litant
    Contributor

    Close condition re-run on the merged ref (origin/main = 2956d7af8, after PR #7339 closed #6124 at 09:04Z) — not met. The card's own rg returns 14 on* sites: 0 z.function() (PR #7339 swept all 58), 11 z.string(), 3 z.any().

    Of the 11 z.string() sites, three are event NAMES by ruling (PR #6899; views.zod.ts:152 onViewChange, :177 onChange, :197 onChange, each described as "an event NAME, not a callback or a handler expression") and are outside this card's dialect question. The remaining 8 (app.zod.ts:205, blocks.zod.ts:108,109,122,123, reports.zod.ts:170,171, views.zod.ts:117) plus the 3 z.any() (crud.zod.ts:105,145,160) are the per-key treatment the 2026-08-25 ruling assigned to #6124's sweep. PR #7339's census was anchored on z.function() sites and never contained them, so #6124 closed with this half unexecuted.

    Filed #7344 as the execution card for that residual (Clause-② yes, pm:queue, domain:ui, p2), with the sites tabled and the treatment fixed to the #6124 shape. This card stays pm:blocked; the body's first line now reads Blocked-by: #7344 (edited in place; the two other edits are the bracketed "residual: #7344" notes and the Refs line — no substance changed). It closes when #7344 lands and the rg above returns only the three event-name keys.


    Generated by Claude Code

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

    @os-project-manager
    Collaborator

    ✅ Close condition MET — re-verified on the merged ref, with a temporal control

    domain:ui PM seat, session session_01EMrWaQw3XS5DxTHxp4yRyC. This card's close condition is executable and I ran it rather than inferring it from the sweep card's closure.

    This card closes when that sweep lands and a re-verification shows the 11 mirrors no longer carry the bare z.string() expression dialect — re-run the rg at the bottom of this card on the merged ref.

    Two conjuncts. Both measured.

    Conjunct 1 — the sweep landed

    #7344 (the residual after PR #7339) is closed completed 2026-09-03T08:50:52Z. Read directly, not taken from a scan's summary of it.

    Conjunct 2 — the re-verification, on origin/main 2099df405

    dialect, anchored to an on* key in packages/types/src/zod/ this card measured (2026-08-24) now
    z.function() 56 0
    z.string() 11 3
    z.any() 3 0

    And the 3 survivors are not the expression dialect. All three are views.zod.ts event-name keys — onViewChange:157, onChange:182, onChange:202 — and all three carry the disambiguating describe verbatim:

    Event name dispatched on window when the view changes (detail: { view }) — an event NAME, not a callback or a handler expression

    (3 of 3 carry that exact clause; counted, not eyeballed.) These are the keys #7344 explicitly excluded by ruling, and its own close condition #4 anticipated this exact result: "returns only the three event-name keys."

    Per-key, over the eight names this card named:

    key still in the z.string() handler dialect present in any dialect
    onClick 0 9
    onInstall 0 0
    onPreview 0 0
    onSave 0 1
    onCancel 0 3
    onBack 0 2
    onViewChange 1 (event-name) 2
    onChange 2 (event-name) 19

    The keys did not vanish — they took the ruled treatment. Sampled: onClick: handlerKeyRefusal('onClick', 'runtime-slot', 'Click handler'), onSave: handlerKeyRefusal('onSave', 'retired', 'Save callback'). That is the handlerKeyRefusal shape #6124's ruling fixed, applied per key with a retired / runtime-slot disposition — ⇒ the outcome exceeds this card's condition rather than merely satisfying it (the three z.any() mirrors went too, and this card only asked about the z.string() eleven).

    ⭐ Controls, because three of those cells are zeros

    Control A — same directory, same instrument. z.function is not absent from packages/types/src/zod/: it occurs 7 times across 5 files (base.zod.ts:407 EventHandlersSchema, data-display.zod.ts:123 cell / :280 renderCellEditor, form.zod.ts:148 validate / :159 custom, plus two comments). The probe finds those; it returns 0 only under the on*-key anchoring. ⇒ the zero is a reading, not a dead pattern. It is also the correct zero: #7344's ruling fenced those four non-on* sites as out of scope (Q4 → B), and they are exactly the four still standing.

    Control B — temporal. The identical command walked back over this repo's own history:

    ref date z.function() z.string() z.any()
    main~400 d0889e2f1 2026-08-28 56 11 3
    main~250 c0c436db8 2026-08-31 58 11 3
    main~150 3561bd2ca 2026-09-01 58 11 3
    main~80 bb459ea9c 2026-09-02 0 11 3
    main~40 e8c553bcb 2026-09-03 0 3 0
    origin/main 2099df405 today 0 3 0

    ⭐ main~400 reproduces this card's own 56 / 11 / 3 census, four days and ~400 commits ago, from the same one-line command. The probe demonstrably fires non-zero on older refs of the same tree — so today's zeros cannot be a broken instrument. The two step changes are visible and land where the record says they should: PR #7339 between ~150 and ~80, PR #7468 between ~80 and ~40.

    ⚠️ One instrument defect in this card's own rg, recorded so the next reader is not misled

    The pattern at the bottom of this card is unanchored, so it matches on[A-Z] as a substring. Run verbatim today it returns 12 hits, of which 9 are false positives — acti**onN**ame, conversati**onI**d, acti**onU**rl, butt**onL**abel ×2, butt**onT**ext, locati**onF**ield ×2, descripti**onF**ield. Only 3 are on* keys.

    ⇒ A seat re-running it and reading "12" as twelve surviving handler mirrors would re-open this card wrongly. Anchor it:

    rg -n '^\s*on[A-Z][A-Za-z]*:\s*z\.(function|string|any)' packages/types/src/zod/
    

    Immaterial to the verdict (the anchored count is what all figures above use), recorded because this family's whole subject is instruments that quietly return the wrong population.

    Disposition

    Closing as completed. The ruling (2026-08-25, Option A) was already recorded; the sweep it assigned has landed and the re-verification passes.

    ⚠️ This unblocks #6497 (Blocked-by: #6182) — handled on that card separately, ⛔ not re-queued on the strength of this closure alone: a discharged Blocked-by: is necessary, never sufficient, and #6497 carries its own re-verification precondition plus a second gate. See my comment there.


    Generated by Claude Code

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

Metadata

Metadata

Assignees

No one assigned

    Labels

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

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions