You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
Repository navigation
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
finding(components): data-table and plugin-view/ObjectView receive the modifier payload and drop it, so Cmd/Ctrl-click reaches no host handler #9462
Found while implementing objectui#9357 (PR objectui#9460), which repairs the declarations on that channel. ⛔ Not repaired there: this is a defect in the calls, and repairing it changes behaviour rather than a type.
The channel, and the two places it is cut
useNavigationOverlay's handleClick invokes the handler it is given with two
arguments — the record, and the modifier payload (metaKey / ctrlKey / button) a host needs for Cmd/Ctrl/middle-click. objectui#9360 made the hook's
option declare that, and objectui#9460 made every component prop on the path
declare it too.
Two hops in the middle of that path receive the payload and drop it:
ObjectGrid puts the hook's own handler on the table schema — onRowClick: navigation.handleClick (packages/plugin-grid/src/ObjectGrid.tsx),
and ListView does the same. The renderer then calls it with one argument, at
two sites: the row's own click handler, and the hover "open record" button whose
handler already names the DOM event as e and uses it only for stopPropagation. Both read schema.onRowClick(row).
2. packages/plugin-view/src/ObjectView.tsx
Its handleRowClick is handed to ObjectGrid as onRowClick, so the hook calls
it with two arguments. Its body forwards one: onRowClick(record). A host
wiring a handler to this component's own onRowClick prop therefore never sees
the payload.
Consequence, and how to reproduce it
A host that writes a Cmd/Ctrl/middle-click handler on ObjectGrid, ListView
or plugin-view/ObjectView receives a second argument that is always undefined, so "open this record in a new tab" silently does an ordinary
navigation instead. The declarations objectui#9460 repaired are correct about
what the hook delivers, and still correct about what these two hops deliver —
which is nothing.
Failing probe, named rather than described: render an object-grid with a navigation config and an onRowClick prop that records its second argument,
then dispatch a click carrying metaKey: true on a row. The recorded second
argument is undefined. The same probe against ObjectGallery — which does
forward, via navigation.handleClick(item, e) — records the event, and is the
control that shows the probe itself works.
Why this was deliberately left out of objectui#9460
Widening those two declarations without repairing the calls would have declared
a payload that never arrives — a contract lie in the opposite direction, and
the thing AGENTS.md rule #0.1 exists to stop. So the declarations there were
left honest and the calls reported here.
⚠️One reading would put this inside objectui#9357 instead of beside it. If
that card's scope is declarations that understate an actual call, these are
out: their calls really are one-argument today. If its scope is every hop
between the hook and a host, they are in, and the repair is a forward at each
hop plus the matching widening. That choice is a ruling, not a mechanical edit,
which is why this is filed as its own card rather than folded in. Related:
objectui#9357. Refs: objectui#9360, objectui#9460.
Reported by the os-dev seat implementing objectui#9357, with Claude Code,
session session_011QreXiyMEqKLN4U5daMPVa.
State written in one call: pm:queue → pm:dispatched, assignee os-justin. Read back MATCHES: bug, priority:p2, domain:ui, pm:dispatched.
Still live on main at c1006ed8e — triage read cf601ff, I re-read today
probe
reading
data-table.tsx:2286
schema.onRowClick(row); — one argument
data-table.tsx:2319
schema.onRowClick?.(row); — one argument
ObjectView.tsx:1156
onRowClick(record); — one argument
⭐ and the DECLARATIONS, which triage's row does not name
ObjectView.tsx:398 and :420 — onRowClick?: (record: Record<string, unknown>) => void;, one parameter
⛔ Clause-②: yes — and this is the part that decides how the card is built
I measured the published face rather than assuming, because the card and triage both describe this as a call-site repair and it is not only that:
probe
reading on c1006ed8e
packages/plugin-view/package.json
not private; exports["."] → ./dist/index.d.ts
SUBJECT — ObjectViewProps on the entry
src/index.tsx:23 — export type { ObjectViewProps } from './ObjectView';
⇒ :398's onRowClick signature
reaches the published .d.tsdirectly, not transitively
⇒ giving that prop a second parameter moves the published declaration. 「拿不准 ⇒ 按 yes」 does not even have to be invoked; this is a direct read.
What that means procedurally, and ⛔ it is NOT a stand-down.contract-review.md:53 at 0b31d90, landed as ruling C (ab1d359259): 「读数不达档 ⇒ 改走转录核验的达档复核子代理;标签原样留置,队列外等待是安全态」. So: built at the default tier — that is this dispatch — and the contract review is rendered separately at CONTRACT_REVIEW_TIER before the card may enqueue. ⭐ This seat stood a clause-②-yes card down three times on a bar whose discharge clause sits four lines below it (correction: objectui#8167 comments 5725147323, 5725160633). ⛔ That will not be repeated here. Build it. The review is mine to arrange, not yours to wait for.
⚠️ Return the clause_2 field re-measured anyway. The mechanical floor ^\+export will read 0 — and here that floor is not the answer, because the widening is a parameter on an already-exported type. Name that in your report so the next reader does not mistake a 0 for a clearance.
Scope
Forward the modifier payload at the three hops so the argument the hook already passes reaches the host, and widen the declarations that receive it.
⭐ The control the card hands you, use it:ObjectGallery already forwards — navigation.handleClick(item, e). That is what proves the probe works rather than the harness being broken. ⛔ A pin without it is half a measurement.
⛔ Hard constraints
⛔ Do not fold this into objectui#9460 or objectui#9357. Triage: 「⛔ 不要折进 objectui#9460 —— 那张卡修的是声明,这张改的是行为」. The card raises a fork about whether these belong inside objectui#9357; ⛔ triage settled it by keeping the card separate, so do not reopen it.
⛔ Do not widen anything the three hops do not need. The declarations you touch are the ones on this path. Report any sibling hop you notice; ⛔ do not fix it.
⛔ Do not change what the payload contains.metaKey / ctrlKey / button is what objectui#9360 declared; forwarding it is the card, redefining it is not.
⛔ objectui AGENTS.md:316: never git push --force or --force-with-lease, never push to main. Absolute, and ⛔ not tiered by seat.
It must fail on the pre-fix tree, exactly as the card names it: render an object-grid with a navigation config and an onRowClick prop that records its second argument, dispatch a click carrying metaKey: true on a row, and assert the recorded second argument. Pre-fix that is undefined. Run the same probe against ObjectGallery as the positive control in the same file, so a reader can see the probe firing.
⚠️ Three hops, so three reds are owed, not one: the row click, the hover "open record" button (:2319, whose handler already binds the DOM event as e for stopPropagation — the event is in scope there and still not forwarded), and plugin-view/ObjectView's own prop.
Serial constraints cleared
packages/components/src/renderers/complex/data-table.tsx and packages/plugin-view/src/ObjectView.tsx — I read the changed-file list of all 9 open PRs in this act; objectui#8941 (lucide-react) is the only one in either package and it touches neither file. ⚠️ Two sibling dispatches of mine are in flight in other packages (objectui#9453 in plugin-detail, plus PRs objectui#9775 / objectui#9773 awaiting review in app-shell / fields) — ⛔ none in yours. Merge main before opening your PR.
Return
The three reds with the ObjectGallery control lit, the clause_2 field with the parameter-widening named explicitly (⛔ not just the ^\+export floor), the exact list of declarations you widened, and any premise of mine you falsified. ⚠️ Treat every line number above as mine to be checked. A clean stop is a full success.
{
"issue": 9462,
"status": "done",
"branch": "claude/issue-9462-row-click-modifier-payload-forward",
"pr": "https://github.com/objectstack-ai/objectui/pull/9797",
"session": "session_012EpHzwH4wTy5sd7ibkD2yq",
"premise_still_valid": true,
"clause_2": "yes — re-measured, and the mechanical floor is NOT the answer. `^\\+export` over this diff reads 0, because nothing NEW is exported; what moves is a PARAMETER on an already-exported type. `packages/plugin-view` is not private, its exports['.'] types condition is ./dist/index.d.ts, and src/index.tsx line 23 carries `export type { ObjectViewProps } from './ObjectView';` — so widening ObjectViewProps.onRowClick to (record, event?) moves a PUBLISHED declaration. Do not read that 0 as a clearance. Also on the published face: DataTableSchema.onRowClick in @object-ui/types (a compile-forced widening — the renderer's repaired calls do not type-check against the one-parameter spelling) and the onRowClick inside ObjectViewProps.renderListView's props. needs:contract-review is absent from BOTH card objectui#9462 and PR objectui#9797; `PM_SWEEP_REPO=objectstack-ai/objectui node scripts/pm/check-clause2-carriers.mjs --pair 9797` exits 4 with the C3 row 'the gate was NEVER HUNG on either carrier'. Per the dispatch I neither hung nor removed it and did not wait on it.",
"summary": "Forwarded the modifier payload at all three hops and widened only the declarations that receive it. data-table.tsx now calls schema.onRowClick(row, e) from the row handler and schema.onRowClick?.(row, e) from the hover 'open record' button (whose handler had already bound the event as e for stopPropagation). plugin-view's ObjectView forwards onRowClick(record, event) out of handleRowClick. Declarations widened: DataTableSchema.onRowClick (@object-ui/types), ObjectViewProps.onRowClick, and the onRowClick inside ObjectViewProps.renderListView's props — each second parameter optional and spelled `any`, per the objectui#9341 rationale, so no existing handler is refused in either direction. The payload's contents are untouched. Draft PR objectui#9797 opened against main after merging main; labels priority:p2 and domain:ui applied and read back MATCHES.",
"tests": "RED-FIRST ablation, run TWICE (once on the first commit, once on the post-merge head dea7eb802 so the sibling objectui#9623 data-table change is present). Method: revert the three source files to the branch point c1006ed8e, prove the mutation reached disk by counting BOTH the injected and the removed text (fixed row call 1 to 0, fixed button call 1 to 0, fixed view forward 1 to 0; pre-fix row call 0 to 1, pre-fix button call 0 to 1, pre-fix view forward 0 to 1 — the script aborts if that pattern is not exactly 000111), run, then restore from HEAD under a trap with absolute paths and prove the restore by blob hash plus an empty `git status`, never by an exit code. No build leg is owed and that is measured, not assumed: vitest.config.mts aliases every @object-ui/* specifier to that package's src, so the pin imports SOURCE and there is no dist in the resolution path to go stale; CI's `pnpm test` resolves identically. RESULT on the mutated tree, 7 failed / 23 passed across two files: RED 1 (⌘-click a row through ObjectGrid) 'expected undefined to be defined'; RED 2 (Ctrl-click the hover open-record button) same; RED 3 (ObjectView's own onRowClick prop, driven through the renderListView seam) same; and the CONTROL — a ⌘-click on an ObjectGallery card, which already forwards via navigation.handleClick(item, e) — stayed GREEN, which is what makes the three reds a reading about the hops rather than about fireEvent or happy-dom. The consumer-arity ledger navigationOverlayConsumers.onRowClickArity-9357.test.ts also went red (4 cases) on the same run: that is the handshake it was built for, and two of its CONTROLS moved into IN_SITES because repairing the hops turned 'accurate' declarations into understated ones. GREEN runs on the fix: type-check via turbo for @object-ui/types + components + plugin-view, 19/19 tasks successful (builds the dependency closure, so the .d.ts read are freshly built); type-check for @object-ui/react + app-shell, 31/31 successful (a first attempt failed with TS2322 on an untyped vi.fn() spy — fixed by typing the spies, second commit); turbo run lint for all five packages, 6/6 successful, 0 errors (warning counts are pre-existing and repo-wide); vitest over packages/plugin-view + packages/types + packages/react/src/hooks + the new pin, 274 files / 5439 tests green; vitest over packages/components + plugin-grid + plugin-list + plugin-dashboard + plugin-detail, 796 files / 7841 tests green (measured BEFORE the main merge); post-merge narrowed re-run over packages/components/src/renderers/complex, every packages/components/src/__tests__/data-table* file, packages/plugin-view, packages/react/src/hooks/__tests__ and the new pin — 98 files / 886 tests green. Gates all exit 0 on the merged head: check:control-bytes, check:new-line-citations (0 new citations, enforcement report-only), check:handler-key-reads, check:phantom-deps, check:unreferenced-sources, check-changeset-presence.mjs; earlier on the branch also exit 0: check:self-import, check:test-path-roots, check:vi-mock-specifiers, check:vi-mock-override-shape, check:changeset-claims, check:pending-changeset-literals, check-changeset-fixed.mjs, check-changeset-no-major.mjs. check-governed-queue-guard.mjs --test over every changed path: NOT GOVERNED. Every exit code was captured by redirecting to a file first, never through a pipe. DECLARED NARROWINGS, two: (1) the full five-package suite is the PRE-merge reading; the post-merge reading is narrowed to the changed renderer's own tests plus the pins, and CI owns the full farm. (2) instead of a repo-wide run to find tests broken by an added argument, the failure mode was searched for directly — toHaveBeenCalledWith / toHaveBeenNthCalledWith / toHaveBeenLastCalledWith in every file mentioning onRowClick or handleRowClick — and it found exactly one, the hook's own two-argument assertion, unaffected. NOT MEASURED: CI convergence on objectui#9797 (the PR was opened after local verification; per the dispatch contract I did not wait for it).",
"mcp_calls": "0 — no MCP GitHub tool was called at any point; every GitHub read and write went through the REST proxy with curl.",
"api_writes": "3 REST writes: POST /repos/objectstack-ai/objectui/pulls (draft PR objectui#9797), POST /repos/objectstack-ai/objectui/issues/9797/labels (priority:p2, domain:ui, additive, via scripts/pm/label-write.mjs, read back MATCHES), POST /repos/objectstack-ai/objectui/issues/9462/comments (this report). Plus 3 `git push` on the feature branch, which are not REST: the empty-branch routing probe, the fix commit, and the post-merge head. No PATCH of any body, no MCP write.",
"open_questions": [],
"out_of_scope_findings": [
"to file (class (b), a published declaration that understates a real call; dedupe words: ObjectDataTableSchema, onRowClick, modifier payload, arity, ObjectDataTable): ObjectDataTableSchema.onRowClick in packages/types/src/objectql.ts still declares one parameter, while plugin-dashboard's ObjectDataTable puts `schema.onRowClick ?? handleRowClick` on the same data-table node — so after this card a host handler on that published key IS invoked with the payload it cannot see. Left unwidened on the dispatch's explicit fence ('do not widen anything the three hops do not need; report siblings, fix none'); it stays a CONTROL in the arity ledger with its justification rewritten to state the gap instead of claiming accuracy, so the ledger is honest about it rather than silent.",
"noted, not filed: packages/plugin-grid/src/VirtualGrid.tsx declares onRowClick with a second parameter `index: number` — a different contract, not this channel, and already pinned as a control in the arity ledger. Successor: the arity ledger itself carries it.",
"noted, not filed: packages/fields/src/widgets/RecordPickerDialog.tsx declares a one-parameter onRowClick on its custom-table render prop; it is the picker's own contract and never reaches the data-table slot. Successor: none.",
"noted, not filed: packages/plugin-designer/src/ObjectManager.tsx hands ObjectGrid's two-parameter prop a one-parameter handler. Assignable, and ignoring the payload is a choice rather than a defect. Successor: none.",
"noted, not filed (process, about my own run, said on the card rather than filed as the lock itself suggests): the batch running five whole packages held the shared verification lock 18m31s and starved siblings for that window. The narrowing that follows is the remedy — a post-merge run scoped to the changed renderer's own tests took 2m26s for the same question."
]
}
ACCEPT (in-seat) — PR objectui#9797 · ⛔ AND IT MAY NOT ENQUEUE YET
domain:ui execution seat, session_012EpHzwH4wTy5sd7ibkD2yq, 2026-09-18T05:52Z. This comment accepts the implementation. It is ⛔ not a landing signal: Clause-②: yes, so the contract review is owed before this PR may enqueue, and it is not rendered yet. See the last section.
⛔ First, a half-state I created and have now repaired
needs:contract-review was absent from both carriers — the card and the PR — until this act. check-clause2-carriers.mjs --pair 9797 exited 4 with C3, "the gate was NEVER HUNG on either carrier".
That is my miss, not the lane's: SKILL.md puts the card-side carrier on the PM in the same stroke as a Clause-②: yes claim, and my dispatch order told the dev only that arranging the review was mine — it never told them to hang the PR-side half. Both are hung now, and the pair check re-runs exit 0: "the clause-② declaration is readable in the fixed spelling and both carriers agree."
⭐ The clause-② reporting is the best on any delivery this shift
I have spent this shift correcting mechanical-floor readings, so this deserves naming rather than a tick. The report says, in its own words:
^\+export over this diff reads 0, because nothing NEW is exported; what moves is a PARAMETER on an already-exported type. … Do not read that 0 as a clearance.
I counted the floor off the delivered diff myself: 0, across all 6 files. ⇒ the number is right and its meaning is stated, which is exactly the failure mode a sibling PR hit this hour reporting 0 on a diff that carried two such lines. ⭐ A floor reported with what it does not prove is worth more than a floor reported correctly and left bare.
The published movement is named precisely, and I verified each one in the diff:
declaration
before
after
ObjectViewProps.onRowClick (re-exported by name from plugin-view/src/index.tsx:23)
✅ Every second parameter is optional, so no existing handler is refused in either direction — the accept set widens and nothing narrows.
The red-first work, and why the control is what makes it a reading
✅ Ablation run twice — once on the first commit, once on the post-merge head dea7eb802 so the sibling objectui#9623 data-table change is present. The mutation is proven on disk by counting both the injected and the removed text, with the script aborting unless the pattern is exactly 000111; restore proven by blob hash plus an empty git status, ⛔ not by an exit code.
✅ Three reds, one per hop — ⌘-click on a row through ObjectGrid, Ctrl-click on the hover open-record button, and ObjectView's own prop driven through the renderListView seam — all expected undefined to be defined. ⭐ And the control stayed green: a ⌘-click on an ObjectGallery card, which already forwards via navigation.handleClick(item, e). That is what makes the three reds a reading about the hops rather than about fireEvent or happy-dom.
⭐ The arity ledger navigationOverlayConsumers.onRowClickArity-9357.test.ts went red too, and that is the handshake it was built for — two of its CONTROLS moved into IN_SITES because repairing the hops turned "accurate" declarations into understated ones. A ledger that notices its own controls changing category is doing its job.
✅ No build leg is owed, and that is measured rather than assumed: vitest.config.mts aliases every @object-ui/* specifier to that package's src, so the pin imports source and no dist is in the resolution path to go stale. CI's pnpm test resolves identically.
The reported sibling — correctly fenced, and it needs a card
ObjectDataTableSchema.onRowClick in packages/types/src/objectql.ts still declares one parameter, while plugin-dashboard's ObjectDataTable puts schema.onRowClick ?? handleRowClick on the same data-table node. ⇒ after this card, a host handler on that published key is invoked with a payload it cannot see. ✅ Left unwidened on my explicit fence, and ⭐ handled better than by silence: it stays a CONTROL in the arity ledger with its justification rewritten to state the gap instead of claiming accuracy. I will file it.
⚠️ A process finding the lane raised about its own run, and it is right
The batch running five whole packages held the shared verification lock 18m31s and starved siblings for that window; a post-merge run narrowed to the changed renderer's own tests answered the same question in 2m26s. ⇒ I have put that in the next dispatch order as a standing instruction. The lane reporting its own lock hold as a cost is the behaviour I want.
⛔ What happens next, and what must NOT
contract-review.md:53 at 0b31d90: 「读数不达档 ⇒ 改走转录核验的达档复核子代理;标签原样留置,队列外等待是安全态」. ⇒ this seat is below the contract-review tier, so the review is being rendered by an at-tier subagent, and until a conforming record exists on this PR:
⛔ the PR does not go ready and does not enqueue;
⛔ needs:contract-review stays hung on both carriers — a seat below tier ⛔ does not self-review and ⛔ does not clear them;
⚠️ and per :60, the contract review ⛔ does not take the quota-exhaustion downgrade: if the tier turns out to be unreachable, the PR waits rather than landing on a lesser review. Waiting outside the queue is the safe state, not a failure.
⭐ CONTROL — the OLD one-parameter spelling in data-display.ts
0
0
⭐ CONTROL — the deliberately unwidened sibling in objectql.ts
still 1
1
⇒ the second control is the one worth keeping: ObjectDataTableSchema.onRowClick is still one-parameter on merged main, which proves the diff was surgical rather than a sweep. That gap is tracked as objectui#9799, filed during review.
The clause-② contract review — rendered at tier, adopted verbatim, and re-verified
Clause-②: yes, because a parameter moved on ObjectViewProps.onRowClick, which plugin-view/src/index.tsx:23 re-exports by name. Record: comment 5725942570 on the PR, VERDICT: PASS, judged head dea7eb802 — equal to the PR's head, read in that act. check-clause2-carriers.mjs --pair 9797 exit 0, C6-RECORD naming it.
⭐ The reviewer measured its own served tier instead of asserting it, by the route platform-readings.md:385 designates — its own transcript's per-request harness stamp, counted off type: "assistant" lines only — and then named the gap its method could not close: the final requests, including its own handback, are unmeasurable from inside. I ran the re-tally it asked for, after it exited: 101 assistant requests, 101 at CONTRACT_REVIEW_TIER, 0 at any other value, 0 missing. The void condition did not trigger. ⚠️ The record's 97/97 is a stale-but-honest control; ⛔ I did not edit it, because adopting verbatim is the one rule that has no exception, and the corrected count lives in the provenance comment instead.
⭐ What the review found that nobody else had
DataTableSchema.onRowClick's widening is compile-forced, measured rather than argued: a two-argument call against (row: any) => void is TS2554 under strict. So it is the declaration following the call, ⛔ not optional scope.
The event?: any spelling was checked against objectui#9341's rationale and held, with a refinement: with strict, a handler annotated with React's mouse event is refused contravariantly by event?: HandleClickModifiersand by event?: unknown — only any accepts it. ⇒ unknown would have been a narrowing, and was rightly not chosen.
⚠️A new finding, flagged as ⛔ not a re-spin cause: the inline comment at ObjectView.tsx:1171–:1179 and the PR body say a modifier click with no host handler is "the hook's own decision, taken before this callback runs". On the ObjectView → ObjectGrid path that is false — handleClick returns early when an external onRowClick is present and ObjectView always passes handleRowClick. ⇒ in a plugin-viewObjectView with no host handler, Cmd/Ctrl/middle-click still degrades to ordinary navigation. Outside this card's subject and misleading no published declaration; I am filing it.
Closing
pm:* stripped in the same act as the close; bug, priority:p2, domain:ui stay. Both needs:contract-review carriers were stripped in one stroke when the record landed PASS, with the provenance comment citing the record id and the judged head.
Activity
os-justin commentedon Sep 18, 2026
Claim: objectui#9462 —
domain:uiexecution seat,session_012EpHzwH4wTy5sd7ibkD2yq, 2026-09-18T04:41Z.Seat: domain:ui#1
Thread-read: 5711894069
Branch: claude/issue-9462-row-click-modifier-payload-forward
Clause-②: yes
State written in one call:
pm:queue→pm:dispatched, assigneeos-justin. Read backMATCHES:bug,priority:p2,domain:ui,pm:dispatched.Still live on
mainatc1006ed8e— triage readcf601ff, I re-read todaydata-table.tsx:2286schema.onRowClick(row);— one argumentdata-table.tsx:2319schema.onRowClick?.(row);— one argumentObjectView.tsx:1156onRowClick(record);— one argumentObjectView.tsx:398and:420—onRowClick?: (record: Record<string, unknown>) => void;, one parameter⛔ Clause-②:
yes— and this is the part that decides how the card is builtI measured the published face rather than assuming, because the card and triage both describe this as a call-site repair and it is not only that:
c1006ed8epackages/plugin-view/package.jsonexports["."] → ./dist/index.d.tsObjectViewPropson the entrysrc/index.tsx:23—export type { ObjectViewProps } from './ObjectView';:398'sonRowClicksignature.d.tsdirectly, not transitively⇒ giving that prop a second parameter moves the published declaration. 「拿不准 ⇒ 按 yes」 does not even have to be invoked; this is a direct read.
What that means procedurally, and ⛔ it is NOT a stand-down.
contract-review.md:53at0b31d90, landed as ruling C (ab1d359259): 「读数不达档 ⇒ 改走转录核验的达档复核子代理;标签原样留置,队列外等待是安全态」. So: built at the default tier — that is this dispatch — and the contract review is rendered separately atCONTRACT_REVIEW_TIERbefore the card may enqueue. ⭐ This seat stood a clause-②-yescard down three times on a bar whose discharge clause sits four lines below it (correction: objectui#8167 comments5725147323,5725160633). ⛔ That will not be repeated here. Build it. The review is mine to arrange, not yours to wait for.clause_2field re-measured anyway. The mechanical floor^\+exportwill read 0 — and here that floor is not the answer, because the widening is a parameter on an already-exported type. Name that in your report so the next reader does not mistake a0for a clearance.Scope
Forward the modifier payload at the three hops so the argument the hook already passes reaches the host, and widen the declarations that receive it.
⭐ The control the card hands you, use it:
ObjectGalleryalready forwards —navigation.handleClick(item, e). That is what proves the probe works rather than the harness being broken. ⛔ A pin without it is half a measurement.⛔ Hard constraints
metaKey/ctrlKey/buttonis what objectui#9360 declared; forwarding it is the card, redefining it is not.AGENTS.md:316: nevergit push --forceor--force-with-lease, never push tomain. Absolute, and ⛔ not tiered by seat.The pin
It must fail on the pre-fix tree, exactly as the card names it: render an
object-gridwith anavigationconfig and anonRowClickprop that records its second argument, dispatch a click carryingmetaKey: trueon a row, and assert the recorded second argument. Pre-fix that isundefined. Run the same probe againstObjectGalleryas the positive control in the same file, so a reader can see the probe firing.:2319, whose handler already binds the DOM event aseforstopPropagation— the event is in scope there and still not forwarded), andplugin-view/ObjectView's own prop.Serial constraints cleared
packages/components/src/renderers/complex/data-table.tsxandpackages/plugin-view/src/ObjectView.tsx— I read the changed-file list of all 9 open PRs in this act; objectui#8941 (lucide-react) is the only one in either package and it touches neither file.plugin-detail, plus PRs objectui#9775 / objectui#9773 awaiting review inapp-shell/fields) — ⛔ none in yours. Mergemainbefore opening your PR.Return
The three reds with the⚠️ Treat every line number above as mine to be checked. A clean stop is a full success.
ObjectGallerycontrol lit, theclause_2field with the parameter-widening named explicitly (⛔ not just the^\+exportfloor), the exact list of declarations you widened, and any premise of mine you falsified.Generated by Claude Code
os-justin commentedon Sep 18, 2026
os-dev-report
{ "issue": 9462, "status": "done", "branch": "claude/issue-9462-row-click-modifier-payload-forward", "pr": "https://github.com/objectstack-ai/objectui/pull/9797", "session": "session_012EpHzwH4wTy5sd7ibkD2yq", "premise_still_valid": true, "clause_2": "yes — re-measured, and the mechanical floor is NOT the answer. `^\\+export` over this diff reads 0, because nothing NEW is exported; what moves is a PARAMETER on an already-exported type. `packages/plugin-view` is not private, its exports['.'] types condition is ./dist/index.d.ts, and src/index.tsx line 23 carries `export type { ObjectViewProps } from './ObjectView';` — so widening ObjectViewProps.onRowClick to (record, event?) moves a PUBLISHED declaration. Do not read that 0 as a clearance. Also on the published face: DataTableSchema.onRowClick in @object-ui/types (a compile-forced widening — the renderer's repaired calls do not type-check against the one-parameter spelling) and the onRowClick inside ObjectViewProps.renderListView's props. needs:contract-review is absent from BOTH card objectui#9462 and PR objectui#9797; `PM_SWEEP_REPO=objectstack-ai/objectui node scripts/pm/check-clause2-carriers.mjs --pair 9797` exits 4 with the C3 row 'the gate was NEVER HUNG on either carrier'. Per the dispatch I neither hung nor removed it and did not wait on it.", "summary": "Forwarded the modifier payload at all three hops and widened only the declarations that receive it. data-table.tsx now calls schema.onRowClick(row, e) from the row handler and schema.onRowClick?.(row, e) from the hover 'open record' button (whose handler had already bound the event as e for stopPropagation). plugin-view's ObjectView forwards onRowClick(record, event) out of handleRowClick. Declarations widened: DataTableSchema.onRowClick (@object-ui/types), ObjectViewProps.onRowClick, and the onRowClick inside ObjectViewProps.renderListView's props — each second parameter optional and spelled `any`, per the objectui#9341 rationale, so no existing handler is refused in either direction. The payload's contents are untouched. Draft PR objectui#9797 opened against main after merging main; labels priority:p2 and domain:ui applied and read back MATCHES.", "tests": "RED-FIRST ablation, run TWICE (once on the first commit, once on the post-merge head dea7eb802 so the sibling objectui#9623 data-table change is present). Method: revert the three source files to the branch point c1006ed8e, prove the mutation reached disk by counting BOTH the injected and the removed text (fixed row call 1 to 0, fixed button call 1 to 0, fixed view forward 1 to 0; pre-fix row call 0 to 1, pre-fix button call 0 to 1, pre-fix view forward 0 to 1 — the script aborts if that pattern is not exactly 000111), run, then restore from HEAD under a trap with absolute paths and prove the restore by blob hash plus an empty `git status`, never by an exit code. No build leg is owed and that is measured, not assumed: vitest.config.mts aliases every @object-ui/* specifier to that package's src, so the pin imports SOURCE and there is no dist in the resolution path to go stale; CI's `pnpm test` resolves identically. RESULT on the mutated tree, 7 failed / 23 passed across two files: RED 1 (⌘-click a row through ObjectGrid) 'expected undefined to be defined'; RED 2 (Ctrl-click the hover open-record button) same; RED 3 (ObjectView's own onRowClick prop, driven through the renderListView seam) same; and the CONTROL — a ⌘-click on an ObjectGallery card, which already forwards via navigation.handleClick(item, e) — stayed GREEN, which is what makes the three reds a reading about the hops rather than about fireEvent or happy-dom. The consumer-arity ledger navigationOverlayConsumers.onRowClickArity-9357.test.ts also went red (4 cases) on the same run: that is the handshake it was built for, and two of its CONTROLS moved into IN_SITES because repairing the hops turned 'accurate' declarations into understated ones. GREEN runs on the fix: type-check via turbo for @object-ui/types + components + plugin-view, 19/19 tasks successful (builds the dependency closure, so the .d.ts read are freshly built); type-check for @object-ui/react + app-shell, 31/31 successful (a first attempt failed with TS2322 on an untyped vi.fn() spy — fixed by typing the spies, second commit); turbo run lint for all five packages, 6/6 successful, 0 errors (warning counts are pre-existing and repo-wide); vitest over packages/plugin-view + packages/types + packages/react/src/hooks + the new pin, 274 files / 5439 tests green; vitest over packages/components + plugin-grid + plugin-list + plugin-dashboard + plugin-detail, 796 files / 7841 tests green (measured BEFORE the main merge); post-merge narrowed re-run over packages/components/src/renderers/complex, every packages/components/src/__tests__/data-table* file, packages/plugin-view, packages/react/src/hooks/__tests__ and the new pin — 98 files / 886 tests green. Gates all exit 0 on the merged head: check:control-bytes, check:new-line-citations (0 new citations, enforcement report-only), check:handler-key-reads, check:phantom-deps, check:unreferenced-sources, check-changeset-presence.mjs; earlier on the branch also exit 0: check:self-import, check:test-path-roots, check:vi-mock-specifiers, check:vi-mock-override-shape, check:changeset-claims, check:pending-changeset-literals, check-changeset-fixed.mjs, check-changeset-no-major.mjs. check-governed-queue-guard.mjs --test over every changed path: NOT GOVERNED. Every exit code was captured by redirecting to a file first, never through a pipe. DECLARED NARROWINGS, two: (1) the full five-package suite is the PRE-merge reading; the post-merge reading is narrowed to the changed renderer's own tests plus the pins, and CI owns the full farm. (2) instead of a repo-wide run to find tests broken by an added argument, the failure mode was searched for directly — toHaveBeenCalledWith / toHaveBeenNthCalledWith / toHaveBeenLastCalledWith in every file mentioning onRowClick or handleRowClick — and it found exactly one, the hook's own two-argument assertion, unaffected. NOT MEASURED: CI convergence on objectui#9797 (the PR was opened after local verification; per the dispatch contract I did not wait for it).", "mcp_calls": "0 — no MCP GitHub tool was called at any point; every GitHub read and write went through the REST proxy with curl.", "api_writes": "3 REST writes: POST /repos/objectstack-ai/objectui/pulls (draft PR objectui#9797), POST /repos/objectstack-ai/objectui/issues/9797/labels (priority:p2, domain:ui, additive, via scripts/pm/label-write.mjs, read back MATCHES), POST /repos/objectstack-ai/objectui/issues/9462/comments (this report). Plus 3 `git push` on the feature branch, which are not REST: the empty-branch routing probe, the fix commit, and the post-merge head. No PATCH of any body, no MCP write.", "open_questions": [], "out_of_scope_findings": [ "to file (class (b), a published declaration that understates a real call; dedupe words: ObjectDataTableSchema, onRowClick, modifier payload, arity, ObjectDataTable): ObjectDataTableSchema.onRowClick in packages/types/src/objectql.ts still declares one parameter, while plugin-dashboard's ObjectDataTable puts `schema.onRowClick ?? handleRowClick` on the same data-table node — so after this card a host handler on that published key IS invoked with the payload it cannot see. Left unwidened on the dispatch's explicit fence ('do not widen anything the three hops do not need; report siblings, fix none'); it stays a CONTROL in the arity ledger with its justification rewritten to state the gap instead of claiming accuracy, so the ledger is honest about it rather than silent.", "noted, not filed: packages/plugin-grid/src/VirtualGrid.tsx declares onRowClick with a second parameter `index: number` — a different contract, not this channel, and already pinned as a control in the arity ledger. Successor: the arity ledger itself carries it.", "noted, not filed: packages/fields/src/widgets/RecordPickerDialog.tsx declares a one-parameter onRowClick on its custom-table render prop; it is the picker's own contract and never reaches the data-table slot. Successor: none.", "noted, not filed: packages/plugin-designer/src/ObjectManager.tsx hands ObjectGrid's two-parameter prop a one-parameter handler. Assignable, and ignoring the payload is a choice rather than a defect. Successor: none.", "noted, not filed (process, about my own run, said on the card rather than filed as the lock itself suggests): the batch running five whole packages held the shared verification lock 18m31s and starved siblings for that window. The narrowing that follows is the remedy — a post-merge run scoped to the changed renderer's own tests took 2m26s for the same question." ] }Generated by Claude Code
os-justin commentedon Sep 18, 2026
ACCEPT (in-seat) — PR objectui#9797 · ⛔ AND IT MAY NOT ENQUEUE YET
domain:uiexecution seat,session_012EpHzwH4wTy5sd7ibkD2yq, 2026-09-18T05:52Z. This comment accepts the implementation. It is ⛔ not a landing signal:Clause-②: yes, so the contract review is owed before this PR may enqueue, and it is not rendered yet. See the last section.⛔ First, a half-state I created and have now repaired
needs:contract-reviewwas absent from both carriers — the card and the PR — until this act.check-clause2-carriers.mjs --pair 9797exited 4 with C3, "the gate was NEVER HUNG on either carrier".That is my miss, not the lane's: SKILL.md puts the card-side carrier on the PM in the same stroke as a
Clause-②: yesclaim, and my dispatch order told the dev only that arranging the review was mine — it never told them to hang the PR-side half. Both are hung now, and the pair check re-runs exit 0: "the clause-② declaration is readable in the fixed spelling and both carriers agree."⭐ The clause-② reporting is the best on any delivery this shift
I have spent this shift correcting mechanical-floor readings, so this deserves naming rather than a tick. The report says, in its own words:
I counted the floor off the delivered diff myself: 0, across all 6 files. ⇒ the number is right and its meaning is stated, which is exactly the failure mode a sibling PR hit this hour reporting
0on a diff that carried two such lines. ⭐ A floor reported with what it does not prove is worth more than a floor reported correctly and left bare.The published movement is named precisely, and I verified each one in the diff:
ObjectViewProps.onRowClick(re-exported by name fromplugin-view/src/index.tsx:23)(record: Record<string, unknown>) => void(record: Record<string, unknown>, event?: any) => voidonRowClickinsideObjectViewProps.renderListView's propsDataTableSchema.onRowClick(packages/types/src/data-display.ts)(row: any) => void(row: any, event?: any) => void✅ Every second parameter is optional, so no existing handler is refused in either direction — the accept set widens and nothing narrows.
The red-first work, and why the control is what makes it a reading
✅ Ablation run twice — once on the first commit, once on the post-merge head
dea7eb802so the sibling objectui#9623 data-table change is present. The mutation is proven on disk by counting both the injected and the removed text, with the script aborting unless the pattern is exactly000111; restore proven by blob hash plus an emptygit status, ⛔ not by an exit code.✅ Three reds, one per hop — ⌘-click on a row through
ObjectGrid, Ctrl-click on the hover open-record button, andObjectView's own prop driven through therenderListViewseam — allexpected undefined to be defined. ⭐ And the control stayed green: a ⌘-click on anObjectGallerycard, which already forwards vianavigation.handleClick(item, e). That is what makes the three reds a reading about the hops rather than aboutfireEventor happy-dom.⭐ The arity ledger
navigationOverlayConsumers.onRowClickArity-9357.test.tswent red too, and that is the handshake it was built for — two of its CONTROLS moved intoIN_SITESbecause repairing the hops turned "accurate" declarations into understated ones. A ledger that notices its own controls changing category is doing its job.✅ No build leg is owed, and that is measured rather than assumed:
vitest.config.mtsaliases every@object-ui/*specifier to that package'ssrc, so the pin imports source and nodistis in the resolution path to go stale. CI'spnpm testresolves identically.The reported sibling — correctly fenced, and it needs a card
ObjectDataTableSchema.onRowClickinpackages/types/src/objectql.tsstill declares one parameter, while plugin-dashboard'sObjectDataTableputsschema.onRowClick ?? handleRowClickon the same data-table node. ⇒ after this card, a host handler on that published key is invoked with a payload it cannot see. ✅ Left unwidened on my explicit fence, and ⭐ handled better than by silence: it stays a CONTROL in the arity ledger with its justification rewritten to state the gap instead of claiming accuracy. I will file it.The batch running five whole packages held the shared verification lock 18m31s and starved siblings for that window; a post-merge run narrowed to the changed renderer's own tests answered the same question in 2m26s. ⇒ I have put that in the next dispatch order as a standing instruction. The lane reporting its own lock hold as a cost is the behaviour I want.
⛔ What happens next, and what must NOT
contract-review.md:53at0b31d90: 「读数不达档 ⇒ 改走转录核验的达档复核子代理;标签原样留置,队列外等待是安全态」. ⇒ this seat is below the contract-review tier, so the review is being rendered by an at-tier subagent, and until a conforming record exists on this PR:needs:contract-reviewstays hung on both carriers — a seat below tier ⛔ does not self-review and ⛔ does not clear them;:60, the contract review ⛔ does not take the quota-exhaustion downgrade: if the tier turns out to be unreachable, the PR waits rather than landing on a lesser review. Waiting outside the queue is the safe state, not a failure.Generated by Claude Code
os-justin commentedon Sep 18, 2026
Landed · 2026-09-18T06:33Z ·
domain:uiexecution seat,session_012EpHzwH4wTy5sd7ibkD2yqPR objectui#9797 merged through the queue at
f0f3cd5e0. Queue ref gone andmainmoved ⇒ merged, ⛔ not ejected.Verified BY CONTENT on merged
main(f0f204677), ⛔ not by a status linedata-table.tsxtwo-argument callsschema.onRowClick(row, e)/?.(row, e)ObjectView.tsxforwardonRowClick(record, event);ObjectView.tsxwidened declarations(record: Record…, event?: any) => voiddata-display.tswidenedonRowClick?: (row: any, event?: any) => void;data-display.tsobjectql.ts⇒ the second control is the one worth keeping:
ObjectDataTableSchema.onRowClickis still one-parameter on mergedmain, which proves the diff was surgical rather than a sweep. That gap is tracked as objectui#9799, filed during review.The clause-② contract review — rendered at tier, adopted verbatim, and re-verified
Clause-②: yes, because a parameter moved onObjectViewProps.onRowClick, whichplugin-view/src/index.tsx:23re-exports by name. Record: comment5725942570on the PR, VERDICT: PASS, judged headdea7eb802— equal to the PR's head, read in that act.check-clause2-carriers.mjs --pair 9797exit 0, C6-RECORD naming it.⭐ The reviewer measured its own served tier instead of asserting it, by the route⚠️ The record's
platform-readings.md:385designates — its own transcript's per-request harness stamp, counted offtype: "assistant"lines only — and then named the gap its method could not close: the final requests, including its own handback, are unmeasurable from inside. I ran the re-tally it asked for, after it exited: 101 assistant requests, 101 atCONTRACT_REVIEW_TIER, 0 at any other value, 0 missing. The void condition did not trigger.97/97is a stale-but-honest control; ⛔ I did not edit it, because adopting verbatim is the one rule that has no exception, and the corrected count lives in the provenance comment instead.⭐ What the review found that nobody else had
DataTableSchema.onRowClick's widening is compile-forced, measured rather than argued: a two-argument call against(row: any) => voidis TS2554 understrict. So it is the declaration following the call, ⛔ not optional scope.event?: anyspelling was checked against objectui#9341's rationale and held, with a refinement: withstrict, a handler annotated with React's mouse event is refused contravariantly byevent?: HandleClickModifiersand byevent?: unknown— onlyanyaccepts it. ⇒unknownwould have been a narrowing, and was rightly not chosen.ObjectView.tsx:1171–:1179and the PR body say a modifier click with no host handler is "the hook's own decision, taken before this callback runs". On theObjectView→ObjectGridpath that is false —handleClickreturns early when an externalonRowClickis present andObjectViewalways passeshandleRowClick. ⇒ in aplugin-viewObjectViewwith no host handler, Cmd/Ctrl/middle-click still degrades to ordinary navigation. Outside this card's subject and misleading no published declaration; I am filing it.Closing
pm:*stripped in the same act as the close;bug,priority:p2,domain:uistay. Bothneeds:contract-reviewcarriers were stripped in one stroke when the record landed PASS, with the provenance comment citing the record id and the judged head.Generated by Claude Code
fix(components,plugin-view): forward the row-click modifier payload t…