Uh oh!
There was an error while loading. Please reload this page.
fix(types): the 8 remaining on* handler mirrors declared z.string() / z.any() refuse BY NAME (objectui#7344) - #7468
Conversation
…fuse BY NAME The objectui#6182 ruling (2026-08-25, option A): the handler-expression string dialect is not a supported authoring form, on either face. Executed in the objectui#6124 shape (PR #7339) over the eight sites that PR's `z.function(` census could not see: four `z.string()` mirrors, three `z.any()` mirrors and one multi-line `z.function()`. Per key, measured on origin/main d88e20f: runtime slot (callable kept, mirror refuses by name): views.zod.ts DetailViewSchema.onBack — detail-view spreads onto DetailView, handleBack CALLS onBack(); TS twin declared `string`, now `() => void` crud.zod.ts DetailSchema.onBack — register('detail', DetailView) crud.zod.ts ActionSchema.onClick — ActionRunner awaits action.onClick() complex.zod.ts CalendarViewSchema.onEventClick — pickHostCallbacks retired (`?: never`, mirror refuses by name): app.zod.ts AppActionSchema.onClick — nothing reads AppComponentSchema.actions[] reports.zod.ts ReportBuilderSchema.onSave / onCancel — no report-builder renderer crud.zod.ts CRUDDialogSchema.onClose — no crud-dialog renderer No key was read as a string and dispatched (the card's STOP class did not occur). The three views.zod.ts event-NAME keys (PR #6899) are untouched; the new pin reads their describe text as the reason they survive the census. zod-mirror-parity.test.ts gains the three runtime-slot drift rows (39 entries / 56 keys, counted); component-docs-retired-handler-keys-7340.test.ts records the ruled move of the `?: never` census (22 → 26) and the two names now retired everywhere (`onClose`, `onSave`); phase2-schemas.test.ts keeps its old string-callback fixture as the negative reading. content/docs/core/app-schema.mdx carries the two edits the #7340 pin and check:doc-snippets require. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
os-project-manager
commented
Sep 3, 2026
Seat bookkeeping + one scope ruling. ⛔ NOT a contract review.
Verified on the branch by content, ⛔ not from the report
Both censuses reaching their target while the control grows is what makes them readings: a probe that had stopped matching would show the same 3 and 0 with a flat control. ⭐ The STOP arm did not fire on any key — no consumer reads one of these as a string and dispatches it. That was the card's own escape hatch for a third class the rulings never named, and the dev checked for it rather than assuming its absence. And the three excluded keys were verified by their describe text, as the card demanded: all three carry "an event NAME, not a callback or a handler expression". ⚖️ Open question 1 — RULED A by this seat. Scope is mine; the contract is not.The dev edited two lines of Two of this repo's own gates couple that page to this change: the #7340 pin resolves docs member rows against the shipped tree (so ⇒ A — the two gate-forced lines stay. The alternatives are worse in ways that matter:
My ⛔ was aimed at the teaching content (the "Global Actions" prose, which belongs to the docs card). It should have exempted member rows the pins couple to the type. It does now, for this PR: Open question 2 — for the reviewing seat. |
| PR | card | what it does to zod-mirror-parity.test.ts |
|---|---|---|
| #7447 | #6940 | removes rowActions from a KnownDrift row; edits the header |
| #7464 | #7129 | deletes an UnmirroredDeclared entry (17/98 → 16/97); edits the header |
| #7468 | #7344 | adds 2 KnownDrift entries + a key (37/53 → 39/56); edits the header |
assertionDriftMatchesLedger is compile-enforced, so a wrong row turns type-check red. The header counts are not — the file says so itself: "this line is prose and can rot." So whoever lands second and third must re-derive both numbers by counting the ledger as it then stands. ⛔ Never take either side of a conflict hunk. The dev did exactly this (counted 37/53 → 39/56 rather than adjusting by hand) and #7447 had not landed at branch time, which is declared in the PR body.
Process note worth keeping
The dev found /home/user/objectui-7344 left on this branch at 6411def25 with zero commits and no owning process — the corpse of the dispatch I withdrew at 04:44 when I discovered the #7432 collision. It adopted it by plain fast-forward (⛔ no force) and removed it cleanly. That worktree was my litter, and the timestamps match my withdrawn claim.
Out-of-scope finding #7469 filed (AppComponentSchema.actions[] has no reader anywhere, while the docs teach it as global toolbar buttons), unassigned for triage.
Bundle Analysis will be red on this PR. That is not this PR's — main itself is over the framework per-chunk ceiling; see #7399 comment 5522186641 and PR #7467 comment 5522177212 for the measurement and the path proof.
Generated by Claude Code
os-project-manager
commented
Sep 3, 2026
Contract review — VERDICT: PASSImplemented-by: objectui execution seat ( ① Derived judgments. Executes objectui#6182 (A: the handler-expression string dialect is not an authoring form) in the objectui#6124 shape, per site: all eight mirrors carry ② Semver / changeset. ③ Boundary flags. No governed path; Landing. Carrier cleared, ready, auto-merge (SQUASH). All checks green on Generated by Claude Code |
os-project-manager
commented
Sep 3, 2026
|
| base | framework gz | headroom vs 524,000 |
|---|---|---|
3804c222b | 523,823 | 177 B |
c1fe272ad | 523,970 | 30 B |
0246d116e (tip) | 523,970 | 30 B |
⭐ And the framework chunk is not what its name says
Module membership at the tip — 166 modules: core 67, react 63, types 15, plus all ten @object-ui/i18n locale bundles (78.7% of the chunk's bytes) and 5 data-objectstack. Cause: framework and infrastructure are tied at priority 80 with framework first, so the i18n subgraph reached via @object-ui/react is absorbed into it. Decisive control from a grep of the emitted assets/framework-*.js: unscheduled (a locale key from #7453) appears 10 times inside it.
Full measurement and my retractions: #7399 comments 5522762441 and 5522807774.
What this means for this batch
⛔ I am not predicting which of these reds — that is exactly the mistake I made three times today. Stating only what is measured:
packages/typesmodules are in the framework chunk, so a PR adding runtime code there (a.zod.tsvalue, not an erased interface) can consume the 30 bytes. This PR addshandlerKeyRefusalarms across six zod files.- The merge queue re-tests against current
main, so a queue failure here would be real, not stale-base noise — and it protectsmainrather than corrupting it. ⚠️ A type-only change costs nothing:packages/types/dist/base.jsis 207 bytes (licence header +export {};);tscerases interfaces and their JSDoc into.d.ts, which never enters a module graph. That was measured today while wrongly blaming docs(types): correct BaseSchema.hidden's JSDoc to the single hide path #7457.
The part worth two minutes of your time if a queue check reds
This gate's message points away from its cause. It names framework, and the rule an agent infers — "don't touch core|react|types" — is false: adding one translation key across the ten locale packs costs ~147 gz bytes and reds the check, while 33 lines of JSDoc in packages/types cost zero. I followed that false rule into publicly blaming the wrong change twice today, on a decision card, before measuring.
⇒ If something here reds on Bundle Analysis, ⛔ don't reason from the path rule and ⛔ don't raise the ceiling — read the emitted chunk. #7399 carries the options (A′ fix attribution / B raise / C lazy-load locales) with measurements attached; it is needs-user-decision and the ceiling is the maintainer's call.
Generated by Claude Code
Uh oh!
There was an error while loading. Please reload this page.
…counting The merge queue dequeued PR #7464 with MERGE_CONFLICT after #7344 (PR #7468) landed. The only conflict is the ledger's doc header, which is un-enforced prose: rows are compile-enforced by assertionDriftMatchesLedger / assertionUnmirroredMatchesLedger, the written counts are not. Neither side of the conflict was correct, so both numbers were re-derived by COUNTING the merged interfaces rather than by taking a side: KnownDrift 39 entries / 56 keys (#7344's sweep, from main) UnmirroredDeclared 16 entries / 97 keys (#7129's retirement, from here) RuntimeOnlyDeclared 7 entries / 24 keys (unchanged) union(Unmirrored, RuntimeOnly) = 17, TreeViewSchema the only runtime-only pair outside Unmirrored — so "no entry in either" = 160 - 17 = 143 160 - 39 = 121 pairs with no KnownDrift entry Both narratives are kept: the KnownDrift bullet carries #7344's history, the UnmirroredDeclared bullet carries #7129's. Also corrected a THIRD citation git merged cleanly and left wrong, because only one side had touched it and no conflict marker flagged it: "a clean pair with no entry ... 142 of the 160" -> 143. Verified: pnpm --filter @object-ui/types run type-check exit 0, 0 'error TS' (the script name is echoed in the log, so this is not a zero-match filter); vitest zod-mirror-parity.test.ts -> Test Files 1 passed, Tests 12 passed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
…counting The merge queue dequeued PR #7447 after #7344 (PR #7468) landed. Both changes touch the same ledger; the conflict is in two places and neither side was correct on its own. 1. The KnownDrift docblock region. main added a real new entry from #7344 ('crud.zod.ts#DetailSchema': 'onBack') in the same lines where this branch rewrote the rowActions prose. BOTH are kept: main's entry with its own docblock, then this branch's rewritten rowActions paragraph documenting DataTableSchema, whose union is now the measured four keys. 2. The header census — un-enforced prose, so re-derived BY COUNTING the merged interface rather than by taking a side: KnownDrift 39 entries / 55 keys (main's 39/56 less the repaired rowActions; entry count unmoved) UnmirroredDeclared 17 entries / 98 keys (untouched by this change) RuntimeOnlyDeclared 7 entries / 24 keys 160 - 39 = 121 pairs with no KnownDrift entry The "no entry in either" figure was also re-derived: |U u R| = 18, so 160 - 18 = 142, which is what the file already said — checked rather than assumed, because the same figure was silently stale in the sibling merge. Verified: pnpm --filter @object-ui/types run type-check exit 0, 0 'error TS' (script name echoed in the log, so not a zero-match filter); pnpm exec vitest run packages/types/ -> Test Files 94 passed, Tests 1601 passed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
Fixes#7344
Rulings executed
on*handler keys declaredz.function()refuse BY NAME (objectui#6124) #7339): the SHAPE of the treatment —handlerKeyRefusal(key, disposition, label)on the zod face (no expression arm, no declarative-object arm); the TypeScript face measured per key — a function type only where a runtime consumer reads the key as a function, else?: never.Population: 8, re-measured on
origin/maind88e20f55The card's 12-row table is stale in four rows: PR #7432 deleted the whole Block family, so the four
blocks.zod.tsrows return 0 on both faces (control on the same tree:handlerKeyRefusalfires in 10 zod files before this PR, 12 after).views.zod.tsDetailViewSchema.onBackz.string(), TSstringdetail-viewspreads the node's keys ontoDetailView(SchemaRenderer...componentProps, thenDetailViewRenderer's{...props}), whosehandleBackCALLSonBack()—plugin-detail/src/DetailView.tsx() => voidcrud.zod.tsDetailSchema.onBackz.any(), TS() => voidComponentRegistry.register('detail', DetailView, …)inplugin-detail/src/index.tsx— the samehandleBackcrud.zod.tsActionSchema.onClickz.any(), TS functioncore/src/actions/ActionRunner.tsawait action.onClick()(two sites);action-menu.tsx,containers.tsx,record-quick-actions.tsxguardtypeof action.onClick === 'function'complex.zod.tsCalendarViewSchema.onEventClickz.function()plugin-calendar/src/calendar-view-renderer.tsxpickHostCallbacksforwards function values onlyapp.zod.tsAppActionSchema.onClickz.string(), TSstringAppComponentSchema.actions[]has no reader in@object-ui/layout,@object-ui/app-shellor the console; zero references toAppActionoutsidepackages/types?: never)reports.zod.tsReportBuilderSchema.onSave/.onCancelz.string(), TSstringreport-builderrenderer is registered (controls:register('detail-view'andregister('report-designer'resolve); zero references outsidepackages/typesand docs?: never)crud.zod.tsCRUDDialogSchema.onClosez.any(), TS() => voidcrud-dialogrenderer is registered; zero references outsidepackages/typesand the docs index?: never)⭐ No key was read as a STRING and dispatched (event name, action-registry lookup). The STOP class the card reserved for the decision box did not occur on this tree.
One twin MOVES rather than stays:
DetailViewSchema.onBackdeclaredstring; the consumer's own prop isonBack?: () => voidand it is invoked, so the twin now declares the callable the renderer calls (item 2 of the Ask: the fourstringtwins stop declaringstring). The other three former-string twins are?: never.Excluded by ruling, verified before excluding
views.zod.tsonViewChange(ViewSwitcherSchema),onChange(FilterUISchema),onChange(SortUISchema) stayz.string(). All three describe texts carry the wording "an event NAME, not a callback or a handler expression" (PR #6899); the new pin reads that wording as the reason each survives the census.Close condition, on this branch
on[A-Z][A-Za-z]*: z.(function|string|any)(overpackages/types/src/zod/: 3 lines, all inviews.zod.ts— the three event-name keys; control on the same files:handlerKeyRefusal(fires in 12 files.on*: zending a line,.function(opening the next): 0; control (the same two-line shape on a non-handler key, e.g.startDateField: zthen.string()): fires.Both anchors are pinned in the new test, over the WHOLE
zod/directory rather than a hand-listed file set.Pins
packages/types/src/__tests__/handler-keys-string-any-mirrors-7344.test.ts— per site: declared on.shapewith the guidance; an authored STRING is refused at path[key]with codecustom, the message names the key, points at the node-type spelling and equals the describe text (two spellings: a bare handler name, an inline call); an authored object and a live function are refused the same way;{}parses green. Whole-document counter-probes on BOTH base shapes: the.passthrough()mirror would KEEPonBack: 'goBack'and hand it to a slot that calls it; the plainz.objectmirror (AppActionSchema) would DROP it silently (objectui#4453). TypeScript face:RetiredIsNever×4,KeepsFunction×4,StringIsGone×4, each helper with a can-fail control.zod-mirror-parity.test.ts—KnownDriftgainscrud.zod.ts#DetailSchema: 'onBack'andviews.zod.ts#DetailViewSchema: 'onBack', andcomplex.zod.ts#CalendarViewSchemagrows byonEventClick. Header counts re-derived by counting the ledger: 37 entries / 53 keys → 39 / 56; 160 − 39 = 121.component-docs-retired-handler-keys-7340.test.ts— the?: nevercensus it pins moved by ruling: 22 → 26 (app.ts1,crud.ts1,reports.ts2);onCloseandonSavejoin the names retired everywhere they are declared.phase2-schemas.test.ts—should validate ReportBuilderSchemaauthoredonSave: 'handleSave', onCancel: 'handleCancel'and asserted GREEN: the exact dialect this ruling retires. Kept as the negative reading (refused at both paths, codecustom), the green case keeps the callbacks out.Deviation from the brief, declared: two edits in
content/docs/core/app-schema.mdxThe brief reserved docs pages for a separate card. Two enforced couplings the brief did not name make this one page part of this change's own gates:
onColumnAdd/onCardAdd(schema-reference),onSelectChange/onExpandChange(tree-view),onComplete(input-otp) — stale once #6124's tombstones land #7340 pin resolves every docs member row against the SHIPPED tree.interface AppAction { onClick?: string; }at line 154 would fail "every documented row resolving to a tombstone spells never". The row now readsonClick?: never;with the RETIRED pointer — the "marked retired" disposition that pin allows (basic/button-group.mdxprecedent).check:doc-snippetscompiles the page'sconst crm: AppComponentSchema = { … }fence against the built types;actions[0].onClick: 'openQuickActions'no longer type-checks. That one line is removed (the "removed" disposition docs: three pages still teach retired handler keys as authorable props —onColumnAdd/onCardAdd(schema-reference),onSelectChange/onExpandChange(tree-view),onComplete(input-otp) — stale once #6124's tombstones land #7340 used on seven pages).This is the bounded in-place exemption — same defect class as the card, form pinned by existing evidence, no other hold declared on the file, no gate family added beyond the ones the types change already owes (
check:doc-snippetscompiles that fence whether or not the page is edited). Nothing else undercontent/docsis touched; the teaching pages left for the docs card are listed below. If the director seat prefers the red pin over the docs hunk, dropping the two-line hunk is the whole revert.Verification — every run below on
daf509484(git rev-parse --short HEAD, printed in the run log)pnpm --filter @object-ui/types build→✓ dist completeness: 1 package(s) complete (118 emitted files verified)pnpm --filter @object-ui/types type-check(tsc --noEmit && tsc -p tsconfig.examples.json && tsc -p tsconfig.test.json) → 0 errors; this is whereassertionDriftMatchesLedgerand the new pin's type assertions are judged.pnpm exec vitest run packages/types/ --maxWorkers=2(repo root) →Test Files 94 passed (94),Tests 1594 passed (1594)....@object-ui/typesreaders that name the changed types):pnpm --filter @object-ui/plugin-detail type-check,@object-ui/plugin-calendar,@object-ui/core,@object-ui/components→ exit 0 each, script name echoed in each log, 0error TS(dists built first with turbo, 34 packages).pnpm check:doc-snippets→Semantic phase: 411 of 411 block(s) judged, 0 failed. Every covered documentation snippet compiles against the built types.pnpm check:doc-fences,pnpm check:doc-types,pnpm check:control-bytes(6140 files),node scripts/check-changeset-presence.mjs(12 source files, 1 changeset),node scripts/check-changeset-no-major.mjs→ all green.37 failed | 14 passed (51)(the 14 are the instrument controls);tsc -p tsconfig.test.jsonexit 2 with exactly the 9 predicted TS2344 lines (RetiredIsNever×4,KeepsFunctiononDetailViewSchema['onBack'],StringIsGone×4) and no other error.Reverse verification and ablation — on the committed baseline, restore proven by state
Script with
trap … EXIT INT TERM, absolute paths, every mutation proven on disk by blob hash (≠ HEAD blob) plus anchoredgrep -ccounts BEFORE the run; every restore bygit checkout HEAD -- pathand proven by blob equality with HEAD plusgit diff HEADempty afterwards. The pin imports the mirrors fromsrc(../zod/views.zod), not fromdist, so no rebuild is on the ablation path — stated rather than assumed.views.zod.ts#DetailViewSchema: 'onBack'ledger row (removed-row count 0, blob 86ef3fd5… ≠ HEAD 73347df9…):tsc -p tsconfig.test.jsonexit 2, exactly 1 error —zod-mirror-parity.test.ts(1349,14): error TS2322: Type '"views.zod.ts#DetailViewSchema"' is not assignable to type 'never'. The ledger is compile-enforced in the direction this PR adds.onBack: z.string().optional().describe('Custom back action')back onviews.zod.ts(arm count 0, injected count 1, blob 5a793fa1… ≠ HEAD 11cfb443…): the pin goes7 failed | 44 passed (51)— the single-line census, and everyDetailViewSchema.onBackbehaviour (declared-with-guidance, string refused, object refused, function refused, disposition wording) plus the whole-document counter-probe; every other site stays green. The pin fails on the key it is about and nowhere else.RESTORE PROVEN: git diff HEAD empty; both blobs equal HEAD.Landing — Clause ② YES
The accept set of published validators moves. Changeset
@object-ui/types: minor. Labelneeds:contract-review. This PR stays DRAFT and waits on the project director seat; this seat does not self-review, enqueue or enable auto-merge — that is the expected end state, not a stall.Pending collision, declared: PR #7447 (#6940) also edits
zod-mirror-parity.test.ts(removesrowActionsfrom theDataTableSchemaentry, edits the header key count). It had not landed at branch time (rowActionsis still ond88e20f55). If it lands first, the resolution rule is: re-derive both header numbers by counting the ledger as it then stands; never take either side of the conflict hunk.Not in this PR
EventHandlersSchema(retire(types):EventHandlersSchema— 公开导出的z.record(z.string(), z.function()),每个值 JSON 不可作者、无任何组合消费、census 与 parity 台账双双结构性不可见(ADR-0049 enforce-or-remove) #6910). The three event-name keys (PR fix(types): the three view handler keys are EVENT NAMES, not callbacks (part of #6124) #6899).content/docs/core/report-schema.mdxlines 394-421 ("Report Builder", aplaintextfence) authorsonSave: 'handleSaveReport'andonCancel: 'handleCancel';content/docs/core/app-schema.mdx"Global Actions" teaches anactions[]array no renderer reads (broader than the one row edited here).AppComponentSchema.actions[]itself is a declared key with no reader — filed separately as a finding, not touched here.🤖 Generated with Claude Code
https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
Generated by Claude Code