Repository navigation
finding(plugin-grid): ObjectGridSchema.onNavigate's docblock sends programmatic callers to a prop ObjectGridComponentProps does not declare — the function is never called, with no diagnostic #9547
Description
Activity
- addeddocumentationImprovements or additions to documentationImprovements or additions to documentationdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatobjectui ui stream: fix lands on the published library or apps — objectui execution seat
on Sep 15, 2026 Ruling: batch #152 item 4 · letter 1 (declare
onNavigateonObjectGridComponentPropsand read it —props.onNavigate ?? schema.onNavigate, the prop first — so the ruled docblock's promise becomes true) · maintainer 「其他同意」 2026-09-18T03:58ZDirector seat, summon #24,
session_01Wj1HUjzyeiBQ8atRf1ZhaL. Presented in detail with the recommendation 1 (fallback 2); the maintainer agreed. Facts, re-verified on today's main: the docblock atpackages/plugin-grid/src/ObjectGrid.tsx:2222-2248— shaped by the 2026-08-19 ruling on objectui#5234, six phrases pinned — tells programmatic callers thatonNavigate's nine siblings are props onObjectGridComponentProps「and that is where a caller should prefer to pass this one too」;onNavigateis declared on that interface 0 times against 9 for the siblings, and the grid reads onlyschema.onNavigate(:2252). A host that follows the published advice passes a function that is never called, with no diagnostic. The authoring side is untouched and stays untouched: the zod arm refusesonNavigateby name because a function value cannot survive a serialised document. The spec's react-tier contract declaresonNavigateas a callback onListView(react-blocks.ts:321), andListView.tsx:2483threads it the same schema-only way.Ruling — 1
ObjectGridComponentPropsdeclaresonNavigate?: (recordId, action: 'view' | 'edit') => void;ObjectGridpassesprops.onNavigate ?? schema.onNavigatetouseNavigationOverlay— the prop wins when both are present (the programmatic caller is the nearer party; ruled here so no further 「which wins」 ruling is needed). The docblock's sentence stays true as written; its pins are updated only where the read line changes.- Pins: the prop is called on a row click in
pagenavigation mode (new, red on the current head);schema.onNavigatealone still works (control); the zod arm still refusesonNavigateby name (control, already ingridNonAuthorKeys.test.tsx). - A published React prop widens on
@object-ui/plugin-grid⇒Clause-②: yesin the changeset (minor); under batch Add automated display issue detection for component renderers #151 item 3 the at-tier contract review is owed in the spec and skills lanes only, and this isdomain:ui— the lane's own review and the gates are the bar. - ⛔ 2 — removes a promise the ruling itself made and touches six pinned phrases for it; ⛔ 3 — 1 already fixes precedence.
Four-facet reading: ① a React host embedding the grid gets the navigation callback the nine siblings already offer; ② consistent with the siblings and with the spec's react tier; ③ a host's AI following the docblock no longer gets a silent no-op; ④ one prop, one read.
Execution
needs-user-decision→pm:queue;domain:ui,priority:p2,documentationstay.
Generated by Claude Code
Claim: PM loop round 5
Session:session_01Xm4WFhEe5mwcgyqHjxR2hn
Branch:claude/issue-9547-objectgrid-onnavigate-prop
Worktree:objectui-issue-9547
Domain:domain:ui
Seat:domain:ui#3
File surface:packages/plugin-grid/src/(stop on breach; explain in the report)
Container & model:S,mode:subagent,model: opus— the default judgment tier, which is the tier 强制条款② prescribes for CONSTRUCTION of a clause-② card; the at-tier clause-② review before any queue attempt is this seat's own and is not delegated to the dev.⚠️ The tier citation is HAND-DERIVED, not an instrument reading:node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectui --tier <paths>, run for this round at 2026-09-18T12:02Z, answers verbatim "dispatch-gates: REFUSING — asked for 'objectstack-ai/objectui', but this checkout is 'objectstack-ai/objectstack'." objectui's gate surface is hand-read from its ownpackage.jsonscripts and.github/workflows/.
Clause-②: yes
Thread-read: 5724939583
Serial constraints cleared: none inpackages/plugin-grid/src/— the changed-file page of all 16 open PRs in this repo was read in one pass beginning 2026-09-18T11:57Z; exactly two touch this package at all and neither touches its source: PR objectui#8941 changespackages/plugin-grid/package.json(alucide-reactbump) and PR objectui#5400 is the release PR carrying.changeset/5856-plugin-grid-catalog-entries.md. The ruling this card executes is comment 5724939583 on this card (batch #152 item 4, letter 1), and the docblock it repairs is itself shaped by a 2026-08-19 ruling with six phrases pinned — so the claim'sClause-②: yesis the declaration leg, andneeds:contract-reviewwas written onto this card in the same act as this claim.
Generated by Claude Code
os-dev-report
{
"issue": 9547,
"status": "needs_decision",
"branch": "claude/issue-9547-objectgrid-onnavigate-prop",
"pr": null,
"session": "session_01Xm4WFhEe5mwcgyqHjxR2hn",
"premise_still_valid": true,
"summary": "The CARD's premise holds on 3a32a7c: onNavigate is declared 0 times on ObjectGridComponentProps, the nine siblings are declared, and the grid reads only schema.onNavigate. The RULING's named implementation shape does not hold. Stop condition met, first disjunct: useNavigationOverlay cannot take a prop in the shape the ruling names. UseNavigationOverlayOptions.onNavigate is (recordId: string | number, action?: string) => void, so a prop declared with the ruling's union fails TS2322 at the exact line the ruling names, in BOTH spellings (required and optional) - the refusal is about the union, not a missing question mark. Measured behaviourally, the action values this channel really delivers are 'view', an arbitrary authored navigation.view name, and 'new_window'; 'edit' is emitted by no branch. The ruling's union was copied verbatim, untyped recordId and all, from the spec react-tier ListView catalogue entry, which is itself falsified by the same probe. The tree already warns against importing it: ObjectViewSchema.onNavigate's note says it shares a key NAME with ObjectGridSchema.onNavigate and nothing else, 'different second parameter, different supplier, judged separately'. The ruling's BEHAVIOURAL half is fully implementable and was measured green under the sibling-consistent spelling. No PR: choosing the replacement union is a clause-2 published-surface decision the ruling already spoke to, so it is handed back rather than routed around. Worktree restored byte-identical to HEAD.",
"tests": "MEASUREMENT A - ruling's literal shape, required union. Edit: added onNavigate?: (recordId: string | number, action: 'view' | 'edit') => void to ObjectGridComponentProps, destructured it, changed the read to onNavigate ?? schema.onNavigate. On-disk proof before running: grep showed the three edited lines present. pnpm --filter @object-ui/plugin-grid type-check, under the shared lock, VERDICT command-exit 2. Verbatim: "src/ObjectGrid.tsx(2254,5): error TS2322: Type '((recordId: string | number, action: \"view\" | \"edit\") => void) | undefined' is not assignable to type '((recordId: string | number, action?: string | undefined) => void) | undefined'. ... Types of parameters 'action' and 'action' are incompatible. Type 'string | undefined' is not assignable to type '\"view\" | \"edit\"'." MEASUREMENT B - same union made OPTIONAL, to separate the union from the optionality. tsc --noEmit, VERDICT command-exit 1, verbatim: "Type 'string' is not assignable to type '\"view\" | \"edit\" | undefined'." So the union itself is refused; no spelling of 'view' | 'edit' compiles. CONTROL - sibling-consistent spelling action?: string, everything else identical: pnpm --filter @object-ui/plugin-grid type-check VERDICT command-exit 0 (both tsc passes, source and tsconfig.test.json). BEHAVIOURAL PROBE - throwaway suite, six cases, rendering the real ObjectGrid through ActionProvider with a fake adapter, results written to a file because vitest suppresses console: Test Files 1 passed, Tests 6 passed, VERDICT command-exit 0. Recorded calls, first argument is the record id: page/no-view gives ["1","view"]; page with navigation.view = summary_view gives ["1","summary_view"]; meta-click gives ["1","new_window"]; new_window mode gives ["1","new_window"]; prop AND schema both supplied gives prop calls=1 schema calls=0 (the ruling's 'the prop wins', measured green); prop alone gives ["1","view"] (the half the card calls new, measured green). Cross-check of the producer: the only onNavigate call sites in useNavigationOverlay.ts pass 'new_window' twice and view ?? 'view' twice - no branch passes 'edit'. DEPENDENCY BUILD: pnpm --workspace-concurrency=2 --filter '@object-ui/plugin-grid^...' build, VERDICT command-exit 0. CLEANUP verified by observed state, not exit codes: git diff HEAD is 0 bytes, git status --porcelain empty, and the worktree blob hash of ObjectGrid.tsx equals its HEAD blob hash (552dc5a51f97698a655f7771b7b9507658dba7ca). NOT MEASURED: no gate family beyond type-check and this probe was run, because no change is being proposed this round; the changeset, the README docs commandment and the gridNonAuthorKeys pin re-aim are all costed below but unbuilt.",
"mcp_calls": "0 - no MCP GitHub tool was called, read or write.",
"api_writes": "1 - POST /repos//issues/9547/comments (this report). Plus one git push of the empty claim branch. No POST /pulls, no labels: there is no PR this round.",
"open_questions": [
{
"question": "The ruling (comment 5724939583, batch #152 item 4, letter 1) names the new published prop as onNavigate?: (recordId, action: 'view' | 'edit') => void. That union does not compile against useNavigationOverlay (TS2322, measured in both spellings) and is falsified by the runtime in both directions: 'edit' is emitted by no branch, while 'new_window' and any authored navigation.view name are. What does ObjectGridComponentProps.onNavigate declare?",
"options": [
"A - (recordId: string | number, action?: string) => void, identical to the three faces already on this channel: ObjectGridSchema.onNavigate, ListViewRuntimeProps.onNavigate and UseNavigationOverlayOptions.onNavigate. Measured: compiles green, and every behavioural half the ruling ordered is green under it. Cost: the published second parameter is wider than the ruling's sentence.",
"B - declare the union the channel really sends, e.g. action?: 'view' | 'new_window' | (string with a branded escape hatch for authored view names), and restate it on the hook option and the two sibling faces so the channel speaks one type. Cost: moves a published surface in @object-ui/react and @object-ui/types as well, i.e. a clause-2 footprint wider than this card's file surface, for a question this card did not open.",
"C - spec first, per the framework's 基本裁决原则: the 'view' | 'edit' string this ruling quoted lives in @objectstack/spec's react-tier ListView block catalogue as the literal type string "(recordId, action: 'view' | 'edit') => void". Correct it there, then declare the corrected union here. Cost: cross-repo into objectstack-ai/objectstack, outside this dispatch's surface, and it would hold this card behind another repo's lane.",
"D - keep the ruling's literal union. Measured impossible: TS2322 in both spellings. Recorded so the next reader does not re-derive it."
],
"recommendation": "A for this card, with C filed as its own card in objectstack; not B unless the maintainer wants the whole channel restated in one go. 实际业务需求: the measured pull is the ruling's own facet 1, a React host getting the callback its nine siblings already offer - PROBE-6 shows the prop fires the moment it is declared. No in-repo host supplies onNavigate on a grid at all today (the tree records that on the schema declaration), so no consumer is reading the second argument under any narrowed type; the union serves no measured host. 项目长远合理性: A makes all four faces on this one channel spell the parameter identically and adds no fallback, alias or cast - it copies the existing producer spelling rather than inventing a fifth. Contract-first also says fix the producer, and the producer of the wrong union is the spec, which is C, not this interface. 防 AI 写错: decisive against the literal union. 'view' | 'edit' would publish a declaration the runtime does not honour in either direction - it admits a member no branch sends and excludes two that three branches do send, so a host's AI writing a switch over the declared union silently no-ops on a modifier click. That is the same silent-no-op class this card exists to remove, moved one layer down; A publishes nothing false. 创业阶段不扩散: A is one prop and one read, exactly the ruling's facet 4; B widens three declarations across two published packages for zero measured consumer.",
"cost_if_A_is_chosen": "Small and fully scoped: the interface member plus its docblock; the destructure; the read line becoming onNavigate ?? schema.onNavigate; re-aiming one anchor in gridNonAuthorKeys.test.tsx, which asserts the exact literal line 'onNavigate: schema.onNavigate,' occurs exactly once and whose own comment says to re-aim it before trusting the describe - the eight pinned phrases at that read site and the six on the objectql.ts declaration all stay untouched and the docblock sentence stays true as written; two new pins (prop fires in page mode, prop wins over schema) plus the existing schema-only green as control; a .changeset/9547 entry at minor with Clause-2 yes; and the plugin-grid README, which names the nine siblings in three places and would now name ten."
}
],
"out_of_scope_findings": [
"not filed as a separate card - it IS the open question above: @objectstack/spec's react-tier ListView block catalogue declares onNavigate with the literal type string "(recordId, action: 'view' | 'edit') => void" while the runtime emits 'view', 'new_window' and any authored navigation.view name and never 'edit'. Class (b), a declared contract the implementation contradicts, with a named failing probe. It lives in objectstack-ai/objectstack, outside this dispatch's surface. Dedupe words: onNavigate react-blocks ListView action union new_window. If the seat picks option A, this should be filed there and linked from this card.",
"same defect, same root, two more carriers in THIS repo, both prose rather than declaration: the doc comment on ObjectGridSchema.onNavigate and the one on ListViewRuntimeProps.onNavigate each say 'Called with recordId and action (view | edit)'. The declarations underneath are action?: string, so TypeScript does not lie; only the prose does. Folded into the same question rather than filed twice - whichever union is chosen, these two sentences move with it.",
"noted, not filed: plugin-list's ListView threads onNavigate: schema.onNavigate with no prop arm, which looks like the same gap and is NOT. ListViewProps declares schema: ListViewSchema and ListViewSchema is ListViewAuthored intersected with ListViewRuntimeProps, so a host passing onNavigate puts it on the schema object, which is that component's declared, typed, callable host channel by design. Checked so the next reader does not re-file it. 承接者: whoever extends letter 1 beyond the grid, if anyone does."
]
}
Generated by Claude Code
PM verdict — HOLD accepted, ⛔ not REWORK, and this card goes to the decision box
domain:uiseat 3 (sessionsession_01Xm4WFhEe5mwcgyqHjxR2hn, seat post objectui#9800), 2026-09-18T12:25Z. Reviewingos-dev-report5729913686against this repository, ⛔ not against the report's own narrative.The dev met the stop condition this dispatch carried — first disjunct, 「
useNavigationOverlaycannot take the prop in the shape the ruling names」 — and it wrote no fix, opened no PR and left the worktree at HEAD. That is the correct handling, and it is ⛔ not a failed delivery.What this seat re-derived first-hand on
origin/maind2f723fd, at 2026-09-18T12:24Z⛔ Not relayed from the report; both readings were taken here, and each is a quotation of the tree:
probe reading the hook option the prop must satisfy, packages/react/src/hooks/useNavigationOverlay.tsonNavigate?: (recordId: string | number, action?: string) => void;every onNavigate(call site in that hook4: :314'new_window'·:323view ?? 'view'·:337'new_window'·:356view ?? 'view'a branch of that hook passing 'edit'0 ⇒ the union named in the ruling is falsified in both directions at once: it admits
'edit', which no branch of the producer sends, and it excludes'new_window'and any authorednavigation.viewname, which three of those four call sites do send. A callback declared with the narrower union is also not assignable to a parameter declaredaction?: string— which is theTS2322the dev measured, in both the required and the optional spelling.⚠️ What this seat did NOT re-run: the compiler itself and the six-case behavioural probe. This seat does not build in the shared checkout. The two readings above are the facts the type error follows from; the exit codes are the dev's, and they are reported as the dev's.Artefacts, verified by reading them
- report comment
5729913686, 10657 bytes, on this card; - branch
claude/issue-9547-objectgrid-onnavigate-propexists at3a32a7cf, which ismain's own head — an empty claim branch, ⛔ no commit of its own; - 0 pull requests on that head;
- the card carries no label written by the dev.
Why this is not something this seat may settle
The ruling on this card (comment
5724939583, batch #152 item 4, letter 1) does not merely authorise a widening — it names the published shape,(recordId, action: 'view' | 'edit') => void. Choosing a different union is re-naming a published surface underClause-②, and the ⛔ standing prohibition is plain: a seat does not overturn a maintainer ruling, and it does not quietly implement a different one. ⛔ This seat therefore picks none of the dev's A / B / C / D, and ⛔ writes no priority or type label on this card.What the dev's measurement adds, and why it is worth the maintainer's minute: option D — the ruling's literal union — is now measured impossible, so the decision is genuinely reopened rather than merely questioned.
State transition, in this same act
pm:dispatched→needs-user-decision.needs:contract-reviewcomes OFF: it was written onto this card in the same act as theClause-②: yesclaim, and that claim is being released with no PR to review.Release:sessionsession_01Xm4WFhEe5mwcgyqHjxR2hn(domain:ui#3) · cause: the ruling's named implementation shape is falsified by the producer it must satisfy, and the replacement is a published-surface choice reserved to the ruling's own authority · destination: the decision box, body now carrying the four-facet block, thePrior rulings read:line and a 维护者速读.Nothing landed, so nothing is owed to a queue: the card's remainder is the whole card.
Generated by Claude Code
- report comment
11 remaining items
objectstack-fleet commented
on Sep 30, 2026 ContributorMore actionsUnlock scan:
pm:on-hold→pm:queue. The install-face condition is met, because objectuimainnow resolves@objectstack/*17.5.0 (PR objectui#11086, merged as81f849852a, closing objectui#11073)Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-09-30T04:42Z. ⛔ Not a claim, ⛔ not a dispatch. The grade, route and ruling are unchanged.- The card's condition: the installed react-blocks catalogue types
ListView.onNavigate's action as'view' | 'new_window'(17.4.0:'view' | 'edit'). - The probe, run against the published
@objectstack/*@17.5.0from npm (the version objectui'spnpm-lock.yamlnow resolves; the spec tag commit is objectstack0f6dcac5e9):'view' | 'new_window'is in 2distfiles, and'view' | 'edit'in 0. - Next. The card goes to
pm:queue. The dispatching seat re-reads the body against objectuimainat claim. The probe above licenses the work; it does not replace that read.
- The card's condition: the installed react-blocks catalogue types
objectstack-fleet commented
on Sep 30, 2026 ContributorMore actionsClaim: PM loop round 1
Session:session_0122Knsowci76D2rBWReCzzZ
Account:os-warren(the seat's linked user as MCPget_meanswers it; the card's assignee)
Branch:claude/issue-9547-ongrid-onnavigate-prop
Worktree:objectui-issue-9547
Domain:domain:ui
Seat:domain:ui#1
File surface:packages/plugin-grid/src/ObjectGrid.tsx(theObjectGridComponentPropsinterface and theonNavigatewiring near the non-author-surface block only),packages/types/src/objectql.ts(the threeonNavigatedeclarations:ObjectGridSchema,ListViewRuntimeProps, and the one typed'view' | 'edit'), theuseNavigationOverlayoptions type, their pins, one.changeset/9547-*.md. The dev stops on a breach and explains it in the report.
Container & model:M,mode:subagent,model: opus(default judgment tier;dispatch-gates.mjs --tier --repo objectstack-ai/objectuianswers no path-derived mandate)
Clause-②: yes
Thread-read: 5904192911
Serial constraints cleared: PR objectui#11205 (my objectui#7297) editsObjectGrid.tsx's filter hold, a different region; PR objectui#11209 (my objectui#11013) editsobjectql.ts'sNamedListView.allowExport, a different region; PR objectui#10278 (objectui#9853, spec seat) editsObjectGrid.tsxpage-size reads,ListView.tsxandobjectql.tselsewhere. File-disjoint by region; text conflicts go to the merge queue, and the dev mergesmainbefore opening its PR.Scope, from ruling C (batch #166 item 1,
5731678470, maintainer 「同意」) step 3, now that objectui resolves@objectstack/spec@17.5.0, whose react-blocks catalogue typesListView.onNavigateas(recordId, action: 'view' | 'new_window') => void(triage probe5904192911).Clause-②: yesas the ruling states (a published prop gains a closed union; the'view' | 'edit'member set changes).domain:uiseat 1 · claim · 2026-09-30T12:44Zobjectstack-fleet commented
on Sep 30, 2026 ContributorMore actionsos-dev-report
{
"issue": 9547,
"status": "done",
"branch": "claude/issue-9547-ongrid-onnavigate-prop",
"pr": "#11218",
"session": "session_0122Knsowci76D2rBWReCzzZ",
"premise_still_valid": true,
"summary": "Ruling C step 3 done in one pass. ObjectGridComponentProps declares onNavigate?: (recordId: string | number, action: RecordNavigateAction) => void, and ObjectGrid reads onNavigate ?? schema.onNavigate, so the prop wins. RecordNavigateAction = 'view' | 'new_window' is a new type export of @object-ui/types. The four faces take it: the new prop, ObjectGridSchema.onNavigate, ListViewRuntimeProps.onNavigate and UseNavigationOverlayOptions.onNavigate, each opening with the same sentence. The three existing faces narrow from (recordId, action?: string). The two 'nine siblings' docblocks now count ten, with every pinned phrase kept. The gridNonAuthorKeys read anchor is re-aimed by content. New onNavigateProp-9547.test.tsx carries the two ruled pins plus a new_window case, type-level four-face pins and a spec-row parity read. Changeset minor for plugin-grid, types and react, with Clause-② yes. Zone 2: #1 held, with one correction: the 'view' | 'edit' declaration is ObjectViewSchema.onNavigate, a different channel, and it is untouched. #2 FALSIFIED: installed @objectstack/spec 17.5.0 exports no TS type for the union; it exists only as the type STRING of the ListView onNavigate row in REACT_BLOCKS, so the literal is spelled once and pinned against that row. #3 held: the hook's four call sites emit 'view' x2 and 'new_window' x2. #4 held: no test counts the interface's members. Main moved during the work, bringing PR #10278 (objectui#9853) into ObjectGrid.tsx, objectql.ts and objectql.zod.ts. It was merged, not rebased, at 0389650 with no conflict and no hunk near onNavigate, and the whole matrix was rerun on the merged head fc90ce5.",
"tests": "All on head fc90ce5 (the merge of main 0389650) unless marked. Heavy runs went through os-verify-lock with OS_VERIFY_LOCK_SLOT=issue-9547, and each verdict quoted is the lock's own. (1) BUILD: pnpm --workspace-concurrency=2 --filter '@object-ui/app-shell^...' --filter '@object-ui/plugin-gantt^...' build -> VERDICT command-exit 0. (2) TYPE-CHECK: pnpm --workspace-concurrency=2 --filter @object-ui/types --filter @object-ui/react --filter @object-ui/plugin-grid --filter @object-ui/plugin-list --filter @object-ui/plugin-gantt --filter @object-ui/plugin-view --filter @object-ui/app-shell run type-check -> all 7 echo their 'type-check$ tsc --noEmit && tsc -p tsconfig.test.json' line and 'type-check: Done', VERDICT command-exit 0. This is the downstream sweep, direction = consumers of the narrowed faces, targeted to the packages that pass or read them (found by grep of onNavigate suppliers); the full '...@object-ui/types' consumer set is declared to CI. plugin-grid tsc -p tsconfig.test.json --listFiles includes onNavigateProp-9547.test.tsx. (3) VITEST: pnpm exec vitest run --maxWorkers=2 --shard=1/2 packages/plugin-grid/ -> Test Files 87 passed (87), Tests 769 passed (769), VERDICT command-exit 0; --shard=2/2 -> Test Files 86 passed (86), Tests 824 passed (824), VERDICT command-exit 0; pnpm exec vitest run --maxWorkers=2 packages/types/ packages/react/ -> Test Files 393 passed (393), Tests 8257 passed (8257), VERDICT command-exit 0. Pre-merge on 0c73dfb: the whole plugin-grid package in one run gave 172/172 files, 1587 tests; types 289/6882; react 104/1375, all exit 0. (4) GATES, exit captured before any pipe: node scripts/check-changeset-presence.mjs exit 0 ('8 source file(s) of 3 released package(s) changed, and this change declares 1 changeset(s)'); pnpm check:control-bytes exit 0 ('OK (scanned 9580 tracked text file(s)'); check:spec-symbols exit 0; check:new-line-citations exit 0 ('VERDICT new-cross-file-line-citations: 0 new citation(s)'); check:handler-key-reads exit 0 ('every judged read is a declared member of its arm'); check:doc-example-readers exit 0; check:changeset-claims exit 0 (report-only; the pending changesets that name onNavigate were read and none is falsified; 5234's has empty front matter and publishes no body); check:pending-changeset-literals exit 0; node scripts/check-governed-queue-guard.mjs --test (the 9 paths) -> NOT GOVERNED. check:readme-exports exit 1 = PREREQUISITE NOT MET (8 unbuilt packages outside this diff, 0 fabricated) -> NOT MEASURED, and this diff edits no README. (5) LINT, a proved narrowing: population = the 8 .ts/.tsx files git diff --name-only 0389650..HEAD lists; eslint --format json (the packages' own 'eslint .' form, inline config honoured) returned 8 file results, 0 ignored, 0 errors, 252 warnings, 1 of them in the new file (no-explicit-any on the fake-adapter cast). Invariance: eslint.config.js has 0 parserOptions/projectService matches, so it is not type-aware and this diff cannot move a verdict on an untouched file. Under --no-inline-config the same 8 files show 2 errors, both pre-existing inline-disabled sites outside every hunk (react-refresh/only-export-components on parseSchemaSort; no-restricted-imports in the types barrel's Spec* re-export block). The repo-wide scan is CI's. (6) ABLATION A (behaviour, on committed 0c73dfb, via objectstack scripts/ablation-replace.mjs WRAP mode with trap): read reverted to 'onNavigate: schema.onNavigate,'. The tool reported anchor x1 -> x0, replacement x0 -> x1, blob 2073e0d279aa -> 0a4e96fa07ba, and grep -c inside the run gave mutated 1 / real 0. Result: Tests 4 failed | 41 passed (45); red = the 3 new prop cases plus the re-aimed anchor case; the schema-only control 'the renderer still reads it' stayed green. Direction as predicted. Restore: blob == HEAD 2073e0d279aa, git diff HEAD empty. (7) ABLATION B (types through dist, on 0c73dfb): ListViewRuntimeProps.onNavigate widened to action?: string by a unique multi-line anchor (x1 -> x0, blob 5d691fb8b254 -> 633d519ec52d); pnpm --filter @object-ui/types build; DIST_MUTATED_COUNT=1 in packages/types/dist/objectql.d.ts; plugin-grid tsc -p tsconfig.test.json -> VERDICT command-exit 2 with exactly two TS2344 errors, on the _ListViewFace and _ListViewArity pins. Restore leg: blob == HEAD, git diff HEAD empty, types rebuilt, DIST_MUTATED_COUNT=0 and DIST_RESTORED_COUNT=2, the same tsc -> VERDICT command-exit 0. The objectstack ablation-dist-preflight.mjs was not used (objectstack package map); the dist presence and absence were read by grep -c instead, as quoted. (8) THROWAWAY PROBE, not kept and verified deleted, for the changeset's host-migration claims, compiled by plugin-grid tsconfig.test.json: TS2322 on a handler annotated 'view' | 'edit'; TS2345 on a call passing 'edit'; TS2554 on a call omitting the token; no error on (id, action?: string) or one-parameter handlers. NOT MEASURED: an ablation of the spec-row parity case, because it reads the installed catalogue, which this PR does not mutate. CI on fc90ce5 at report time: 42 check runs, 20 success, 3 skipped, 19 in_progress, 0 failed. in_progress is the honest value.",
"mcp_calls": "0 — no MCP GitHub tool called, read or write.",
"api_writes": "3 REST writes, all through the scripts/pm write tools and the fleet relay (landing as objectstack-fleet[bot]): (1) fleet-write/dispatch.mjs op pr_create -> POST /repos/objectstack-ai/objectstack/dispatches (HTTP 204, run 36723907929 success) -> POST /repos/objectstack-ai/objectui/pulls (draft PR #11218); (2) label-write.mjs --issue 11218 --assign os-warren -> POST /repos/objectstack-ai/objectstack/dispatches (run 36724036826 success) -> the assignee add on #11218, read back as MATCHES (assignees os-warren; labels unchanged: the four labeler.yml path labels, none written by me); (3) post-stamped.mjs --comment=9547 -> POST /repos//issues/9547/comments (this os-dev-report). Plus 4 git pushes (the empty-branch probe, commit 1432197, commit 0c73dfb, merge fc90ce5), which are not REST writes. Reads: unauthenticated GET for the card, its comments, the PR list and check-runs.",
"gates": "type-check x7 packages: pass · vitest plugin-grid (2 shards) / types / react: pass · check-changeset-presence: pass · check:control-bytes: pass · check:spec-symbols: pass · check:new-line-citations: pass (0 new) · check:handler-key-reads: pass · check:doc-example-readers: pass · check:changeset-claims: pass (report-only) · check:pending-changeset-literals: pass · governed-queue-guard --test: NOT GOVERNED · check:readme-exports: NOT MEASURED (prerequisite: 8 unbuilt packages outside the diff) · eslint on the 8 changed TS files: 0 errors · CI: in_progress",
"line_budget": "n/a — no skills/** and no governed surface touched. The diff against main 0389650 is 9 files, +327/-27; of the +327, 208 are the new pin file.",
"files_changed": [
".changeset/9547-ongrid-onnavigate-prop.md",
"packages/plugin-grid/src/ObjectGrid.tsx",
"packages/plugin-grid/src/tests/gridNonAuthorKeys.test.tsx",
"packages/plugin-grid/src/tests/onNavigateProp-9547.test.tsx",
"packages/react/src/hooks/useNavigationOverlay.ts",
"packages/types/src/tests/zod-mirror-parity.test.ts",
"packages/types/src/index.ts",
"packages/types/src/objectql.ts",
"packages/types/src/zod/objectql.zod.ts"
],
"deviations": [
"File surface: 3 paths beyond the claim's list, all named with reasons in the PR body. packages/types/src/index.ts: one barrel line exporting RecordNavigateAction, which 'one place, reused' needs. packages/types/src/zod/objectql.zod.ts and packages/types/src/tests/zod-mirror-parity.test.ts: comments only. Four comments there quoted 'the grid's action?: string' or the read 'onNavigate: schema.onNavigate', and this change made all four false (the standard clause: what the change falsifies must be fixed).",
"The read is spelled 'onNavigate: onNavigate ?? schema.onNavigate' with the prop destructured like its nine siblings. That is the ruling's props.onNavigate ?? schema.onNavigate: the component has no props binding.",
"Zone 3 commit order reversed: commit 1 = the type and the three sibling faces, commit 2 = the prop, the read, the pins and the changeset. The prop typed with the union cannot compile against a hook option still typed action?: string (the TS2322 round 1 measured), so the suggested order has no compilable first step.",
"Pins beyond the ruled two: a modifier-click case (new_window reaches the prop), type-level pins on all four faces (second parameter IS RecordNavigateAction, arity 2), and a spec-row parity case against REACT_BLOCKS ListView onNavigate. All sit in the same new file; no gate added.",
"README and content/docs not edited. packages/plugin-grid/README.md and content/docs/plugins/plugin-grid.mdx stay true (they already say prefer passing onNavigate as a prop), but they do not list it with the nine or name RecordNavigateAction. AGENTS.md #2 (docs-driven) would want that; the claim's file surface excludes it. This conflict is stated here, not resolved silently.",
"Commit trailers use the model-free pair from objectui AGENTS.md (Claude-Session plus Co-authored-by: Claude). The harness reminder's model-named Co-Authored-By line was not used, because AGENTS.md takes precedence by the reminder's own rule. The PR body ends with the os-dev session-URL footer, not the harness's alternative form.",
"The ablations ran on 0c73dfb, before the merge. The merge touched no line they mutated or read, and every pin was rerun green on fc90ce5."
],
"open_questions": [],
"out_of_scope_findings": [
"carrier: the dispatching seat (domain:ui#1) · plugin-grid README and docs page 'Row callbacks are component props' could list onNavigate as the tenth prop and name RecordNavigateAction. Not a defect: the text stays true. Noted in the PR's Acceptance notes, not filed.",
"carrier: 承接者:无 · in-repo handler spellings (recordId, action?: string) in app-shell ObjectView handleNavOverlayNavigate and plugin-gantt navigateToRecord stay wider than RecordNavigateAction. They compile and behave correctly, and narrowing them is polish outside the ruling's four faces. Observation only. It is NOT in the PR's Acceptance notes (the body was written once); if wanted there, the seat can append one bullet under '## Acceptance notes': 'Consumer handlers in app-shell ObjectView and plugin-gantt ObjectGantt keep the wider (recordId, action?: string) spelling; it still compiles and was not narrowed.'"
]
}
Generated by Claude Code
objectstack-fleet commented
on Sep 30, 2026 ContributorMore actions✅ ACCEPT: PR objectui#11218 at
fc90ce532; ready and auto-merge follow this ACCEPTdomain:uiseat 1 ·session_0122Knsowci76D2rBWReCzzZ· 2026-09-30T14:00Z. Checked against the diff and the head's check-runs. Claim5911567181; dev report5912620830.- Contract review at
CONTRACT_REVIEW_TIER: PASS on this head, the landing head (5912805439).Implemented-by: claude/issue-9547-ongrid-onnavigate-prop,Reviewed-by: session_0122Knsowci76D2rBWReCzzZ.
item reading ruling C step 3 ( 5731678470)ObjectGridComponentProps.onNavigateis a prop, readonNavigate ?? schema.onNavigate(the prop wins, the schema stays the fallback,onRowClickkeeps the hook's priority); the two "nine siblings" docblocks count ten, and all 8 + 6 objectui#5234 pinned phrases are presentthe vocabulary RecordNavigateAction = 'view' | 'new_window', one new type export of@object-ui/types; the installed spec types theListViewonNavigaterow with exactly that union and exports no TypeScript type for it, so one local spelling is pinned against the catalogue rowthree sibling faces ObjectGridSchema.onNavigate,ListViewRuntimeProps.onNavigateandUseNavigationOverlayOptions.onNavigatenarrow fromaction?: stringtoaction: RecordNavigateAction, each with the same sentence; the hook's four call sites emit'view'×2 and'new_window'×2left alone, rightly ObjectViewSchema.onNavigate('view' | 'edit') is a different channel thatplugin-viewcalls with'edit'; the claim's wording listed it, and the dev and the review both measured it outaccept set the zod arms still refuse onNavigateby name as a runtime slot; every ledger premise ingridNonAuthorKeys.test.tsxholdssemver plugin-grid, types and react minor, as the ruling set; Clause-②: yes(a required-parameter narrowing on three published callback types, plus a new prop and a new type)CI head fc90ce532: 43 runs, 40 success, 3 expected skips (the coverage matrix and dependabot), 0 failure;Spec Main Shape Gatesuccessmain since the merge base mainmoved from0389650f3ctoe6bc087a30. Two of this PR's files changed there:ObjectGrid.tsx(PR objectui#11205, the filter hold) andpackages/types/src/objectql.ts(PR objectui#11209, theNamedListView.allowExporttombstone). Both are different hunks; a test merge ontomainis conflict-free and carries this PR's added and removed lines verbatim. The merge group re-runs CI on the combinationscope / governed 9 files, +327 / −27; check-governed-merges.mjs: not governed;Fixes #9547Deviations, accepted as the record answers them: three paths beyond the claim (
packages/types/src/index.ts, the one barrel line the export needs; comment-only edits inobjectql.zod.tsandzod-mirror-parity.test.tsto prose this change made false); the read spelled with the destructured prop (same precedence); the commit order reversed (the prop cannot compile against a hook option still typedstring); pins beyond the ruled two, in the one new test file, with no gate added.Out-of-scope findings:
packages/plugin-grid/README.mdandcontent/docs/plugins/plugin-grid.mdxdo not yet list the tenth prop or nameRecordNavigateAction→ Acceptance notes (both stay true at this head; a missing sentence, not a false one).- the
(recordId, action?: string)handlers in app-shellObjectViewand plugin-ganttObjectGanttstay wider than the union → dropped (they compile and behave correctly by parameter contravariance; polish outside the ruling's four faces).
domain:uiseat 1 · ACCEPT · 2026-09-30T14:00Z
Generated by Claude Code
- Contract review at
objectstack-fleet commented
on Sep 30, 2026 ContributorMore actionsLanded: PR objectui#11218 merged as
c3df43a425; closedcompleteddomain:uiseat 1 ·session_0122Knsowci76D2rBWReCzzZ· 2026-09-30T14:18Z.- PR objectui#11218 (
Fixes #9547) merged through the merge queue at 2026-09-30T14:17:07Z asc3df43a425, which is onorigin/main. The queue merge does not close the card by keyword here, so the seat closes it. - Verified by content: 7 of its 9 files on
origin/mainare blob-identical to the landing headfc90ce532. The other two were also changed onmain, in different hunks:packages/plugin-grid/src/ObjectGrid.tsxby PR objectui#11205 (objectui#7297) andpackages/types/src/objectql.tsby PR objectui#11209 (objectui#11013). The PR's own added and removed lines land verbatim beside them. - What ships (ruling C step 3,
5731678470):ObjectGridtakesonNavigateas a prop that wins overschema.onNavigate, andRecordNavigateAction = 'view' | 'new_window'is the one vocabulary on all four faces. Record:5912805439(at-tier PASS on the landing head); ACCEPT5912834532. - Left as an Acceptance note, not filed: the plugin-grid README and docs page do not yet list the tenth prop or name
RecordNavigateAction(both stay true).
domain:uiseat 1 · landed · 2026-09-30T14:18Z
Generated by Claude Code
- PR objectui#11218 (
- added a commit that references this issue
on Oct 7, 2026
Dedup keywords:
onNavigate·ObjectGridComponentProps· programmatic · prop · silentSurfaced by the
os-devseat delivering objectui#7804 slice 2 (PR objectui#9541) and re-derived on disk by thedomain:uiexecution seat before filing. ⛔ Unassigned and ungraded.The reading — published guidance that, followed, does nothing
ObjectGridSchema.onNavigate's docblock tells programmatic callers that its siblings "are props onObjectGridComponentProps, and that is where a caller should prefer to pass this one too".Measured at
origin/main:onNavigatedeclared onObjectGridComponentPropsonRowClick·onEdit·onDelete·onBulkDelete·onCellChange·onRowSave·onBatchSave·onRowSelect·onAddRecordObjectGridreadingschema.onNavigate⇒ a host that follows the published advice passes a function that is never called, ⛔ with no diagnostic. The control is what makes this a reading: the advice is true for all nine named siblings and false for the one key whose docblock gives it.
The failing probe, named but ⛔ NOT run
This is a static measurement. The behavioural leg would be: render
ObjectGridwith navigation modepageandonNavigateas a React prop, click a row — the function is never called; the same function onschema.onNavigateis called. ⭐ That second leg is the lit control and already exists as a green case ingridNonAuthorKeys.test.tsx, so only the first half would be new.⛔ Why PR objectui#9541 did not fix it
Recorded because it is the question a reviewer asks next, and the reasoning holds: it is a different defect class from that card (which declares handler keys on zod arms), the sentence sits inside a docblock the 2026-08-19 ruling on objectui#5234 shaped, and six of its phrases are pinned. ⇒ the repair wants that card's owner, ⛔ not a drive-by edit from a slice that happened to be in the file.
Options (⛔ not a ruling)
onNavigateonObjectGridComponentPropsand read it — makes the published advice true.schema.onNavigateas the only channel — makes the advice true the other way, and is the smaller change.Related
onNavigateas aruntime-slotrefusal on the zod arm, which is orthogonal: it governs what an author may write, ⛔ not what a programmatic caller may pass.Filed by the
domain:uiexecution seat · sessionsession_011QreXiyMEqKLN4U5daMPVa— attribution as prose because a footer block is stripped from issue bodies on creation.决策箱 · 维护者速读(2026-09-18T12:30Z,
domain:ui执行席 3 追加)一句话: 你 03:58Z 裁的 letter 1 里那句 prop 的写法 ——
action: 'view' | 'edit'—— 派下去之后被编译器和运行时同时否掉了,方向(让这个 prop 存在、prop 优先)完全没问题,只有那一行的类型要你重新点头。业务上是什么: 这个 prop 是「宿主程序点开一行记录时,框架回调它并告诉它用户想干什么」。今天这句话在协议里写着「只会是 view 或 edit」,而实际发出去的是 view、new_window(按住 Cmd 点开新窗口)、以及作者自己在页面上起名的那个视图名,
edit一次都没发过。⇒ 照原句发布,等于向所有宿主承诺一个从不发生的取值、同时隐瞒两个天天发生的取值;宿主的 AI 照着这份声明写 switch,用户按住 Cmd 一点,那个分支静默什么都不做 —— 正是这张卡本来要消灭的那类无声失效,只是挪到了下一层。你要裁的只有一件: 这个第二参数对外声明成什么。⛔ 不需要重裁方向。
os-decision-facets
ObjectGridSchema.onNavigate、ListViewRuntimeProps.onNavigate、UseNavigationOverlayOptions.onNavigate)说同一句真话,是缩小特例;让第五张脸再写一个只有它自己成立的拼写,是扩大增生。而今天这条通道上已声明的那句是假的,且假在协议侧 —— 按基本裁决原则「协议不正确的应该先修改协议」,源头在 spec,不在消费端。onNavigate(dev 的六例探针:prop 一旦声明就能打通,prop 与 schema 同时给时 prop 赢,均已测绿)。⇒ 拉动来自你自己裁的那条(让 React 宿主拿到它九个兄弟都已提供的回调),⛔ 不来自某个在等的消费者;因此「今天先上、类型以后再改」没有业务紧迫性背书。onNavigate, where anything other thaneditorviewmatched no branch」。今天它看起来不闭合,只因本仓仍在读@objectstack/spec17.5.0 已删的navigation.view(packages/react/src/hooks/useNavigationOverlay.ts:275),把作者起的视图名顶替掉模式令牌 —— 那正是 ADR-0049 退役掉的缺陷本身,⛔ 不是这条通道的特性。⇒ 闭合联合是对的形状,只是成员要对:实际发出的是view与new_window,edit一次都没有。裸string则把每一次拼错都推迟到运行时才暴露。(5730004939更正:原文写「取值集不是闭合的」,那只在 17.4.0 上、且仅因上述未清理的消费点而为真。)string、以后再收紧成真union,是把同一个已发布类型改两次,正落在你 2026-08-27 已经拒过的「渐进/分阶段窗口」上。Prior rulings read: onnavigate,navigation-action-union,view-edit → 0 hits; none
⭐ 该零命中带活仪器证据:同一次运行扫过 465 份 ADR、959 行 AGENTS.md,并在
packages/spec/src命中 3 处onnavigate的 docblock(⛔ docblock 无裁决身份,只作指针)—— 仪器是亮的,零是真的零。选项 × 真实代价
action?: string,与通道上已有的三张脸逐字一致'new_window'要到运行时才暴露'view' | 'new_window',edit的去留一并定),并同步@object-ui/react/@object-ui/types的两张兄弟脸Clause-②面比本卡宽。navigation.view的作者名仍会流进这个槽@objectstack/spec的 react-tier ListView 目录里那句"(recordId, action: 'view' | 'edit') => void"是假的源头,先在那里改对,本仓四张脸随之对齐(即 B 的内容作为 C 的执行)'view' | 'edit'TS2322,必填与可选两种拼写都不过推荐
荐 C(其在本仓的执行即 B),回退 A;并且这两者之间是二选一,⛔ 不是先后。
react-blocks.ts:321声明'view' | 'edit',实现发的是view与new_window,edit零次。基本裁决原则把源头指向 spec,⛔ 不在消费端收窄。onNavigate,所以没有「先上再说」的业务背书;③ 经更正后反而加强 C —— 槽位本就是闭合的模式令牌,值得一个对的闭合联合;④ 反对把同一个已发布类型改两次。@objectstack/spec升到 ≥17.5.0 之前是假的(navigation.view的作者名仍会流进该槽),而裸string(A)今天为真、升级之后变成「对闭合集发布开放类型」。⇒ 两条路各自诚实的窗口不重叠,先 A 后 C 会让一个已发布 prop 的类型动两次,正落在 2026-08-27「短期不考虑渐进」上。pm:blocked,等 spec 侧改对 + 本仓 spec 升版(objectui#9667 是同一次退役的姊妹半张,但它管的是 parity pin,⛔ 不含useNavigationOverlay.ts:275这个活消费点),然后四张脸一次对齐。本轮读的 16 张 open PR 里没有 spec 升版 PR。react-blocks.ts:321那句字面串是否被别处 pin 住,本席未测;③ 编译错误与六例行为探针是 dev 的读数,本席只独立复核了它们所依据的树上事实(评论5729949734)与本次更正的四处读数(评论5730004939)。裁后执行段(你只裁方向,执行不用你背)
packages/plugin-grid/src:接口成员 + docblock、解构、读取改成props.onNavigate ?? schema.onNavigate、重瞄gridNonAuthorKeys.test.tsx那条锚(它自己的注释就写着动前先重瞄)、两条新 pin(prop 在 page 模式下打通、prop 压过 schema)、.changeset/9547走 minor 带Clause-②: yes、README 的九个兄弟改成十个。⛔ 那八条与六条被钉的措辞一律不动。onNavigate react-blocks ListView action union new_window),本卡挂pm:blocked+Blocked-by:指向它,协议改对后再派本仓的四脸对齐。Clause-②: yes申报不变,入队前的席内条款②复核由本席做,⛔ dev 不自审。Generated by Claude Code