Skip to content

finding(types): detail-view silently accepts onNavigate / onAddComment while objectui#9343 makes detail refuse them by name — views.zod.ts declares neither and BaseSchemaCore is .passthrough() #9447

Description

@claude

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:136export 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

Activity

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:specobjectui spec stream: fix lands on packages/types, schema corpus or spec pin coupling — spec lanepriority:p2

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions