Repository navigation
fix(components,plugin-view): forward the row-click modifier payload to the host - #9797
Conversation
…o the host `useNavigationOverlay.handleClick` invokes the handler it is given with the record AND the modifier payload (`metaKey` / `ctrlKey` / `button`). Three hops received that payload and passed one argument on, so a host wiring Cmd/Ctrl/middle-click to `ObjectGrid`, `ListView` or `plugin-view`'s `ObjectView` always read `undefined` and "open in a new tab" degraded to an ordinary navigation. - `data-table`'s renderer forwards the DOM event from both call sites: the row's own click handler, and the hover "open record" button, whose handler had already bound the event as `e` for `stopPropagation`. - `plugin-view`'s `ObjectView` forwards it out of `handleRowClick` to its own `onRowClick` prop. The declarations that receive the payload widen with the hops: `DataTableSchema.onRowClick`, `ObjectViewProps.onRowClick`, and the `onRowClick` `ObjectView` hands a host's `renderListView`. Second parameter is optional and spelled `any`, so every existing handler stays assignable. `ObjectDataTableSchema.onRowClick` feeds the same channel and now receives the payload while still declaring one parameter; it is reported rather than widened here, and the consumer-arity ledger records it as a known gap. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012EpHzwH4wTy5sd7ibkD2yq
…-click-modifier-payload-forward
…ads them `vi.fn()` infers `Mock<Procedure | Constructable>`, which `tsc` refuses where the four subjects declare `(record, event?) => void` (TS2322). The spies now carry the host-facing shape once, as `HostRowClick`, so the pin type-checks under each package's `tsconfig.test.json`. No assertion changes. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012EpHzwH4wTy5sd7ibkD2yq
|
changeset-claim-re-read
|
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Contract reviewServed-tier: 97/97 Reviewed at head ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
Clause-② carriers cleared — provenance
⛔ Adopted verbatim, ⛔ not edited. The tier reading, and the one check the reviewer designed for me⭐ The reviewer did not assert its tier — it measured it, by the route I ran that re-tally after it exited, over the whole transcript, comparing each stamp against the value ⇒ the void condition did not trigger. Why this record exists at all, when two other authorities say it need not
⭐ One finding the review produced that no one else hadThe record's ③ carries a new reading, and it falsifies text in this PR: the inline comment at ⛔ The reviewer marked it not a re-spin cause and I am honouring that: it is outside objectui#9462's subject (hosts that DO supply a handler), it misleads no published declaration, and it changes nothing in ① or ②. I am filing it as its own card. Both carriers stripped in this same act. Generated by Claude Code |
Fixes #9462
Clause-2: yes— and the mechanical floor is NOT the reading here. A^\+exportcount over this diff is 0, because nothing new is exported: the widening lands
on
ObjectViewProps.onRowClick, a parameter on a type thatpackages/plugin-view/src/index.tsxalready re-exports from the package entry(
export type { ObjectViewProps } from './ObjectView';), andplugin-viewis notprivate with its
exports["."]types condition pointing at./dist/index.d.ts.So a published declaration MOVES. ⛔ Do not read that
0as a clearance.What changed
useNavigationOverlay'shandleClickhas always invoked the handler it is givenwith two arguments — the record, and the modifier payload (
metaKey/ctrlKey/button). objectui#9360 made the hook's option declare the payload; objectui#9460made the component props on the path declare it. Three hops in the middle received
it and passed one argument on, so a host wiring Cmd/Ctrl/middle-click read
undefinedas its second argument and "open in a new tab" silently degraded to anordinary navigation.
packages/components/src/renderers/complex/data-table.tsx, the row's own clickhandler — was
schema.onRowClick(row), nowschema.onRowClick(row, e).schema.onRowClick?.(row), nowschema.onRowClick?.(row, e). This is the interesting one: its handler hadalready bound the DOM event as
eto callstopPropagation, so the payload wasin scope on that line and still was not handed on.
packages/plugin-view/src/ObjectView.tsx—handleRowClickwas truncating toonRowClick(record), now forwardsonRowClick(record, event). Its own branchesdeliberately do not READ the event: what a modifier click should do when no host
handler is present is the hook's decision, taken before this callback runs.
⛔ The payload's contents are untouched —
metaKey/ctrlKey/buttonisobjectui#9360's declaration. This is a forward, not a redefinition.
The three reds, and the control, in one file
packages/app-shell/src/__tests__/rowClickModifierPayloadForward-9462.test.tsx.It lives in
app-shellbecause that is the one package that already depends onall four subjects, so the control can share the file without a new dependency
edge anywhere — and because
app-shellis the real host: its own views alreadyspell their row-click handlers with two parameters.
RED-FIRST, measured twice — once on the first commit and again after merging
main(the second run also proves the ablation still bites with the siblingdata-table change from objectui#9623 present). Method: revert the three source
files to the branch point, prove the mutation reached the disk by counting the
injected and the removed text in both directions, run, then restore from
HEADand prove the restore by blob hash rather than by an exit code.
ObjectGridexpected undefined to be definedexpected undefined to be definedObjectView's ownonRowClickpropexpected undefined to be definedObjectGallerycardThe control is
ObjectGallery, which already forwards(
navigation.handleClick(item, e)). It is what separates "these three hops dropthe payload" from "the harness never delivered
metaKey" — without it, a brokenprobe produces exactly the same three failures.
The consumer-arity ledger
(
packages/react/src/hooks/__tests__/navigationOverlayConsumers.onRowClickArity-9357.test.ts)went red on the same run, which is the handshake it was built for: two of its
CONTROLS were OUT on a claim about BEHAVIOUR ("this hop hands over one argument,
so widening the declaration would promise a payload that never arrives"), and
repairing the hops made those same declarations understated. They moved into
IN_SITESrather than being edited in place.Declarations widened — and the one deliberately not
Widened, because the call they receive now carries the payload:
DataTableSchema.onRowClick(packages/types/src/data-display.ts) — required,not optional: the renderer's two call sites do not compile against a
one-parameter declaration.
ObjectViewProps.onRowClick(packages/plugin-view/src/ObjectView.tsx) — thepublished one, see the clause-2 note above.
onRowClickinsideObjectViewProps.renderListView's props — that ishandleRowClickhanded verbatim to a host's custom list view.Each second parameter is optional and spelled
any, for the reason objectui#9341measured on
ObjectKanbanSchema.onCardClick:HandleClickModifierslives in@object-ui/react, which depends on@object-ui/types, so naming it there is aphantom dependency and closes a cycle — and a host that discovered the payload
from the implementation annotated it as React's mouse event, which a narrower
declaration refuses contravariantly. Source-compatible in both directions: no
existing handler is refused, and none is forced to take the second argument.
⛔ NOT widened, and reported instead:
ObjectDataTableSchema.onRowClick(
packages/types/src/objectql.ts).ObjectDataTableinplugin-dashboardputsschema.onRowClick ?? handleRowClickon the samedata-tablenode, so after thischange a host handler on that key IS invoked with the payload while the
declaration still says one parameter — a live instance of objectui#9357's class,
one hop further out. It is out of this card's pinned scope, so it stays a control
in the arity ledger with its justification rewritten to state the gap rather than
claim accuracy, and it is reported for its own card.
Verification
Every command below ran from the repo root; the heavy ones went through the shared
serialisation lock in the sibling
objectstackcheckout.turbo run type-checkfor@object-ui/types,@object-ui/components,@object-ui/plugin-view— 19/19 tasks successful (this also builds thedependency closure, so the
.d.tsthe checks read are freshly built).turbo run type-checkfor@object-ui/react,@object-ui/app-shell—31/31 tasks successful.
turbo run lintfor all five of those packages — 6/6 tasks successful,0 errors (the warning counts each package prints are pre-existing and
repo-wide).
vitest runoverpackages/plugin-view/,packages/types/,packages/react/src/hooks/and the new pin — 274 files / 5439 tests green.vitest runoverpackages/components/,packages/plugin-grid/,packages/plugin-list/,packages/plugin-dashboard/,packages/plugin-detail/— 796 files / 7841 tests green.
mainmerge; the post-merge re-run below is narrowed, and CI owns the full farm.
dea7eb802: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.check:control-bytes,check:new-line-citations(0 new citations),check:handler-key-reads,check:phantom-deps,check:unreferenced-sources,check-changeset-presence.mjs. Earlier on the same 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 --testover every changed path: NOT GOVERNED.that pin the row-click call's exact argument list
(
toHaveBeenCalledWith/toHaveBeenNthCalledWith/toHaveBeenLastCalledWithin any file mentioning
onRowClickorhandleRowClick) found exactly one, thehook's own two-argument assertion, which is unaffected. That is the failure mode
an added argument has, and it was searched for directly instead of being left to
a full-repo run.
Acceptance notes
Out-of-scope, noted and NOT filed:
packages/plugin-grid/src/VirtualGrid.tsxdeclaresonRowClick?: (row: any, index: number) => void— a different contract with adifferent second parameter, already pinned as a control in the arity ledger.
Not on this path. Successor: the arity ledger already carries it.
packages/fields/src/widgets/RecordPickerDialog.tsxdeclares a one-parameteronRowClickon its custom-table render prop. It is the picker's own contractand never reaches the
data-tableslot. Successor: none.packages/plugin-designer/src/ObjectManager.tsxpasses a one-parameter handlerto
ObjectGrid's two-parameter prop. Assignable, and it simply ignores thepayload — a choice, not a defect. Successor: none.
To file (one finding, three classes checked — class (b), a published declaration
that understates a real call):
ObjectDataTableSchema.onRowClickinpackages/types/src/objectql.tsstilldeclares one parameter while the
data-tablechannel it feeds now delivers two.Dedupe words:
ObjectDataTableSchema,onRowClick,modifier payload,arity,ObjectDataTable. objectui#9357 is not addressed here and objectui#9460is not addressed here; this is a successor to both.
Generated by Claude Code in session
session_012EpHzwH4wTy5sd7ibkD2yq.Generated by Claude Code