PR objectui#9343 makes the detail arm refuse onNavigate and onAddComment by name (objectui#6124 runtime slots). The detail-view arm, which reaches the same renderer, keeps accepting them silently. After that PR lands, the same two keys have two different fates depending on which type literal an author wrote.
Two contract reviews on objectui#9343 asked for this to get a named home — the first "before this lands or immediately after". Neither repair commit, nor the rewritten PR body, nor card objectui#7804, nor any issue carried it. This card is that home. ⛔ It is not objectui#9343's to fix: that PR is one ruled slice of objectui#7804 under batch #69, and widening it is exactly what the ruling forbids.
Measured on origin/main
The detail-view zod arm declares neither key. packages/types/src/zod/views.zod.ts:136 — export const DetailViewSchema = BaseSchema.extend({ … }). Across its members it declares exactly one handler key:
// views.zod.ts:155
onBack: handlerKeyRefusal('onBack', 'runtime-slot', 'Custom back action'),
onNavigate and onAddComment appear nowhere in that arm. The zero is negative rather than void: onBack is the lit control in the same file — 3 occurrences, one of them the declaration above — so the search reaches the right corpus in the right spelling.
Undeclared does not mean refused here, and the file says so itself. From that same file's docblock at :171:
BaseSchemaCore ends .passthrough() and the TypeScript BaseSchema closes with an any-valued index signature, so a dropped MEMBER key is KEPT, not refused
⇒ an authored onNavigate on a detail-view node parses green and the value is kept, then reaches a call site that expects a function.
The TypeScript face does declare them. packages/types/src/views.ts:773 onNavigate?: (url: string, options?: { replace?: boolean; newTab?: boolean }) => void; · :887 onAddComment?: (text: string) => void | Promise<void>; (and a differently-shaped nested onNavigate at :878). So the two faces of detail-view already disagree with each other, before objectui#9343 adds the third position.
What objectui#9343 changes. It narrows the detail arm so those two keys are refused by name with a message pointing at the node-type spelling. Nothing in it touches detail-view.
⇒ once it lands, on main:
| arm |
onNavigate / onAddComment authored in JSON |
TS face |
detail |
refused by name |
declared |
detail-view |
accepted and kept, then handed to a call site expecting a function |
declared |
Attributed, ⛔ not measured by this card
The reviewer of objectui#9343 measured that detail-view feeds the same DetailView component through DetailViewRenderer's derived bound object, which is why the gate scripts/check-handler-key-read-sites.mjs cannot see the read. ⛔ This card did not re-derive that path — whoever picks this up should, since it decides whether the fix is "mirror the refusal onto detail-view" or something narrower.
Why it is worth a card rather than an acceptance note
⛔ The «write it into some PR's acceptance notes» fallback does not hold: no open PR touches views.zod.ts's detail-view arm, and the two PRs that were near it have both been told, correctly, that it is out of their slice. A note with no carrier is how this reached a third review unowned.
⭐ It is also the live half of a pattern this repo has already ruled on twice in this same file: onBack (objectui#7344 / the objectui#6182 ruling) and related (objectui#7997, ADR-0049 enforce-or-remove) were both kept declared and unwritable rather than deleted, precisely because .passthrough() turns a deletion into a silent accept. The same reasoning applies here.
⛔ Filed bare on purpose: no domain:*, no priority:*, no type. Triage owns all three — this seat does not grade.
Generated by Claude Code
PR objectui#9343 makes the
detailarm refuseonNavigateandonAddCommentby name (objectui#6124 runtime slots). Thedetail-viewarm, which reaches the same renderer, keeps accepting them silently. After that PR lands, the same two keys have two different fates depending on whichtypeliteral an author wrote.Two contract reviews on objectui#9343 asked for this to get a named home — the first "before this lands or immediately after". Neither repair commit, nor the rewritten PR body, nor card objectui#7804, nor any issue carried it. This card is that home. ⛔ It is not objectui#9343's to fix: that PR is one ruled slice of objectui#7804 under batch #69, and widening it is exactly what the ruling forbids.
Measured on
origin/mainThe
detail-viewzod arm declares neither key.packages/types/src/zod/views.zod.ts:136—export const DetailViewSchema = BaseSchema.extend({ … }). Across its members it declares exactly one handler key:onNavigateandonAddCommentappear nowhere in that arm. The zero is negative rather than void:onBackis the lit control in the same file — 3 occurrences, one of them the declaration above — so the search reaches the right corpus in the right spelling.Undeclared does not mean refused here, and the file says so itself. From that same file's docblock at
:171:⇒ an authored
onNavigateon adetail-viewnode parses green and the value is kept, then reaches a call site that expects a function.The TypeScript face does declare them.
packages/types/src/views.ts:773onNavigate?: (url: string, options?: { replace?: boolean; newTab?: boolean }) => void;·:887onAddComment?: (text: string) => void | Promise<void>;(and a differently-shaped nestedonNavigateat:878). So the two faces ofdetail-viewalready disagree with each other, before objectui#9343 adds the third position.What objectui#9343 changes. It narrows the
detailarm so those two keys are refused by name with a message pointing at the node-type spelling. Nothing in it touchesdetail-view.⇒ once it lands, on
main:onNavigate/onAddCommentauthored in JSONdetaildetail-viewAttributed, ⛔ not measured by this card
The reviewer of objectui#9343 measured that
detail-viewfeeds the sameDetailViewcomponent throughDetailViewRenderer's derivedboundobject, which is why the gatescripts/check-handler-key-read-sites.mjscannot see the read. ⛔ This card did not re-derive that path — whoever picks this up should, since it decides whether the fix is "mirror the refusal ontodetail-view" or something narrower.Why it is worth a card rather than an acceptance note
⛔ The «write it into some PR's acceptance notes» fallback does not hold: no open PR touches
views.zod.ts'sdetail-viewarm, and the two PRs that were near it have both been told, correctly, that it is out of their slice. A note with no carrier is how this reached a third review unowned.⭐ It is also the live half of a pattern this repo has already ruled on twice in this same file:
onBack(objectui#7344 / the objectui#6182 ruling) andrelated(objectui#7997, ADR-0049 enforce-or-remove) were both kept declared and unwritable rather than deleted, precisely because.passthrough()turns a deletion into a silent accept. The same reasoning applies here.⛔ Filed bare on purpose: no
domain:*, nopriority:*, no type. Triage owns all three — this seat does not grade.Generated by Claude Code