Skip to content

fix(components,plugin-view): forward the row-click modifier payload to the host - #9797

Merged
os-justin merged 3 commits into
mainfrom
claude/issue-9462-row-click-modifier-payload-forward
Sep 18, 2026
Merged

os-justin merged 3 commits into
mainfrom
claude/issue-9462-row-click-modifier-payload-forward

Conversation

@os-justin

Copy link
Copy Markdown
Collaborator

Fixes #9462

Clause-2: yes — and the mechanical floor is NOT the reading here. A ^\+export
count over this diff is 0, because nothing new is exported: the widening lands
on ObjectViewProps.onRowClick, a parameter on a type that
packages/plugin-view/src/index.tsx already re-exports from the package entry
(export type { ObjectViewProps } from './ObjectView';), and plugin-view is not
private with its exports["."] types condition pointing at ./dist/index.d.ts.
So a published declaration MOVES. ⛔ Do not read that 0 as a clearance.

What changed

useNavigationOverlay's handleClick has always invoked the handler it is given
with two arguments — the record, and the modifier payload (metaKey / ctrlKey /
button). objectui#9360 made the hook's option declare the payload; objectui#9460
made 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
undefined as its second argument and "open in a new tab" silently degraded to an
ordinary navigation.

  1. packages/components/src/renderers/complex/data-table.tsx, the row's own click
    handler — was schema.onRowClick(row), now schema.onRowClick(row, e).
  2. The same file's hover "open record" button — was schema.onRowClick?.(row), now
    schema.onRowClick?.(row, e). This is the interesting one: its handler had
    already bound the DOM event as e to call stopPropagation, so the payload was
    in scope on that line and still was not handed on.
  3. packages/plugin-view/src/ObjectView.tsx — handleRowClick was truncating to
    onRowClick(record), now forwards onRowClick(record, event). Its own branches
    deliberately 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 / button is
objectui#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-shell because that is the one package that already depends on
all four subjects, so the control can share the file without a new dependency
edge anywhere — and because app-shell is the real host: its own views already
spell 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 sibling
data-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 HEAD
and prove the restore by blob hash rather than by an exit code.

case on the mutated tree
RED 1 — ⌘-click a row through ObjectGrid expected undefined to be defined
RED 2 — Ctrl-click the hover "open record" button expected undefined to be defined
RED 3 — ObjectView's own onRowClick prop expected undefined to be defined
⭐ CONTROL — ⌘-click an ObjectGallery card GREEN, both before and after

The control is ObjectGallery, which already forwards
(navigation.handleClick(item, e)). It is what separates "these three hops drop
the payload" from "the harness never delivered metaKey" — without it, a broken
probe 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_SITES rather 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) — the
    published one, see the clause-2 note above.
  • The onRowClick inside ObjectViewProps.renderListView's props — that is
    handleRowClick handed verbatim to a host's custom list view.

Each second parameter is optional and spelled any, for the reason objectui#9341
measured on ObjectKanbanSchema.onCardClick: HandleClickModifiers lives in
@object-ui/react, which depends on @object-ui/types, so naming it there is a
phantom 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). ObjectDataTable in plugin-dashboard puts
schema.onRowClick ?? handleRowClick on the same data-table node, so after this
change 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 objectstack checkout.

  • turbo run type-check for @object-ui/types, @object-ui/components,
    @object-ui/plugin-view — 19/19 tasks successful (this also builds the
    dependency closure, so the .d.ts the checks read are freshly built).
  • turbo run type-check for @object-ui/react, @object-ui/app-shell —
    31/31 tasks successful.
  • turbo run lint for all five of those packages — 6/6 tasks successful,
    0 errors (the warning counts each package prints are pre-existing and
    repo-wide).
  • vitest run over packages/plugin-view/, packages/types/,
    packages/react/src/hooks/ and the new pin — 274 files / 5439 tests green.
  • vitest run over packages/components/, packages/plugin-grid/,
    packages/plugin-list/, packages/plugin-dashboard/, packages/plugin-detail/
    — 796 files / 7841 tests green. ⚠️ This one was measured BEFORE the main
    merge; the post-merge re-run below is narrowed, and CI owns the full farm.
  • Post-merge re-run at 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.
  • Gates, all exit 0 on the merged head: 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 --test over every changed path: NOT GOVERNED.

⚠️ One narrowing, declared rather than hidden: an argument-level scan for tests
that pin the row-click call's exact argument list
(toHaveBeenCalledWith / toHaveBeenNthCalledWith / toHaveBeenLastCalledWith
in any file mentioning onRowClick or handleRowClick) found exactly one, the
hook'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.tsx declares
    onRowClick?: (row: any, index: number) => void — a different contract with a
    different 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.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.
  • packages/plugin-designer/src/ObjectManager.tsx passes a one-parameter handler
    to ObjectGrid's two-parameter prop. Assignable, and it simply ignores the
    payload — 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.onRowClick in packages/types/src/objectql.ts still
    declares one parameter while the data-table channel it feeds now delivers two.
    Dedupe words: ObjectDataTableSchema, onRowClick, modifier payload,
    arity, ObjectDataTable. objectui#9357 is not addressed here and objectui#9460
    is not addressed here; this is a successor to both.

Generated by Claude Code in session session_012EpHzwH4wTy5sd7ibkD2yq.


Generated by Claude Code

…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
…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
@github-actions

Copy link
Copy Markdown
Contributor

changeset-claim-re-read

⚠️ 26 pending changeset(s) describe a file this change touches

Their bodies publish verbatim into the CHANGELOG at the next release, so this is a request to re-read them against your diff — addressed here because you are the one seat that can answer it without re-deriving anything.

⛔ Nothing here blocks, and nothing here is a verdict on your change. This gate exits 0, is not a required context, and judges name resolution, never meaning: it asked whether a pending body names a file you touched. "Is this sentence still true?" is the one question it will not answer, and the one you are being asked to answer.

.changeset/4919-retire-mobile-overrides.md

  • names data-display.ts → packages/types/src/data-display.ts — edited by this change

    Removal rather than a ?: never tombstone follows this package's own discriminator. A tombstone exists to steer authors to a named live replacement — crud.ts confirm → confirmText (objectui#4314), data-display.ts hoverable / striped → data-table (objectui#5474) — or to keep a key loud that the docs had actively taught as working. Neither applies: there is no replacement key to steer to, no documentation ever described the surface, and there is no successor spelling. That is the same zero-pull, no-successor shape as the retired AccordionItem.icon (objectui#4652) and ToggleGroupItem.icon (objectui#4632), both of which were removed outright rather than tombstoned.

.changeset/5453-retire-grid-column-wrap-forward.md

  • names data-table.tsx → packages/components/src/renderers/complex/data-table.tsx — edited by this change

    generateColumns() copied a per-column wrap onto every column object handed to data-table, and packages/components' data-table.tsx never read it. Measured on the current ref rather than inherited from the card, with comments stripped so prose mentions cannot be counted as reads: a column-level wrap scores 0, against accessorKey 34, align 5, header 4, className 4, width 8 and fitContent 2 in the same query shape. Those sibling counts are the positive control — the search style does find the keys that are genuinely consumed, so the zero is a measurement and not a mis-aimed grep. The raw string wrap does occur in that file; every occurrence is flex-wrap, whitespace-nowrap, or a variable named wrapper.

.changeset/5853-tablecolumn-type-canonical-union.md

  • names data-table.tsx → packages/components/src/renderers/complex/data-table.tsx — edited by this change

    @object-ui/types exports TABLE_COLUMN_TYPES (the canonical tuple — the single declaration the zod mirror builds its enum from, so the two cannot drift), the TableColumnType union, and normalizeTableColumnType() for producers. The as any cast in data-table.tsx is deleted and the read is typed, so re-introducing an undeclared spelling is a tsc error rather than a silent widening.

.changeset/5926-empty-action-visible-when.md

  • names data-table.tsx → packages/components/src/renderers/complex/data-table.tsx — edited by this change

    The fix is a route, not a new check: no visibleWhen test was added to data-table.tsx. A local check on this slot would have been a fourth evaluator, which is the drift page:tabs' item-level predicate already records on this card. The slot now mounts its authored node exactly the way the empty renderer's action slot always has.

.changeset/5942-retire-mobile-component-config.md

  • names data-display.ts → packages/types/src/data-display.ts — edited by this change

    Removed outright rather than kept as a ?: never tombstone, on this package's own discriminator: a tombstone steers authors to a named live replacement key (crud.ts confirm to confirmText; data-display.ts hoverable/striped to data-table), or keeps loud a key the docs taught as working. Neither applies — the whole interface goes, so there is no surviving object to hang a never key on, and no documentation ever described it (skills/objectui/guides/mobile.md teaches the hooks, never this type). Same zero-pull, no-successor shape as MobileOverrides (objectui#4919) and AccordionItem.icon / ToggleGroupItem.icon.

.changeset/5993-button-shared-icon-resolver.md

.changeset/6175-column-state-persistence.md

  • names data-table.tsx → packages/components/src/renderers/complex/data-table.tsx — edited by this change

    • @object-ui/components — DataTableSchema has declared onColumnResize?: (columnKey, width) => void all along, and data-table.tsx invoked it nowhere: the resize drag updated the table's local columnWidths state and stopped there. It now reports the settled width once, at mouseup. Once, deliberately — the host turns this callback into a write to shared view config, so a per-mousemove callback would be a write storm. - @object-ui/plugin-grid — ObjectGrid emitted onColumnReorder (singular) while the renderer invokes the near-duplicate onColumnsReorder (with the s), a different declared key with a different signature. The producer now emits the spelling the renderer actually invokes, mapping the reported TableColumn[] to the accessorKey order columnState stores.

.changeset/6349-types-internal-name-collisions-batch-1.md

  • names data-display.ts → packages/types/src/data-display.ts — edited by this change

    BreadcrumbItem / BreadcrumbSchema — re-pointed, because one copy was stale. Both were declared in data-display.ts and in navigation.ts. The data-display pair was not a second dialect but a strict SUBSET: no key declared differently on either side, and missing BreadcrumbItem.icon / onClick / siblings and BreadcrumbSchema.maxItems. Everything that reads a breadcrumb was already on the navigation declaration — registry.ts maps the 'breadcrumb' component type to it, src/index.ts re-exports it under the bare names, zod/navigation.zod.ts mirrors it (icon, onClick, siblings, maxItems included), the ui:breadcrumb renderer consumes it, and the component's own documentation page documents icon and maxItems. data-display.ts now re-exports the one authority.

.changeset/6373-datatable-emit-boundary.md

  • names data-table.tsx → packages/components/src/renderers/complex/data-table.tsx — edited by this change

    The measured read set of the consumer (data-table.tsx, comments stripped) contains none of the six, so all six retire from the emit rather than being declared — declaring a key nothing reads is the same declared != enforced defect facing the other way. Rendering is unchanged because none of those keys was the live path for its own value: the FieldMeta the cell closure captures is what this widget's type-aware rendering has always read, and it is untouched. Authored spellings still pass through, so a column the author wrote as { format: '$0,0' } keeps its format exactly as before.

.changeset/6424-tablecolumn-declares-headericon.md

  • names data-table.tsx → packages/components/src/renderers/complex/data-table.tsx — edited by this change

    Scope of the enforcement claim, measured: the declaration, the parse road, and the renderer's behaviour now agree; the renderer's internal column reads remain any-mediated (the col: any normalization in data-table.tsx) — a standing instrument gap, not closed here.

.changeset/6726-find-envelope-records-arms.md

  • names plugin-view/src/ObjectView.tsx → packages/plugin-view/src/ObjectView.tsx — edited by this change

    | module | what it does | | --- | --- | | components/src/hooks/related-count-store.ts | related-list tab badge count | | components/src/renderers/basic/data-list.tsx | element:repeater rows | | components/src/renderers/basic/elements.tsx | element:number client-side aggregate | | components/src/renderers/basic/record-picker.tsx | element:record_picker options | | plugin-detail/src/renderers/record-activity.tsx | record:activity self-fetch | | plugin-detail/src/renderers/record-history.tsx | record:history self-fetch | | plugin-view/src/ObjectView.tsx | non-grid (kanban / calendar / gallery / timeline) fetch |

.changeset/6881-retire-data-table-toolbar.md

  • names data-display.ts → packages/types/src/data-display.ts — edited by this change

    What was measured. The key was declared on both published faces — data-display.ts (toolbar?: SchemaNode[], "Table toolbar actions/content") and the Zod mirror (SchemaNode | SchemaNode[]) — documented, mirrored, and read by NOTHING: data-table.tsx, the registered renderer for type: 'data-table', contains the word only in two prose comments and never reads schema.toolbar. The sibling emptyAction slot on the same interface IS mounted through SchemaRenderer, so the census zero is a reading, not a blind query. An author who wrote a toolbar got a green document and a blank result, with no signal anywhere that said so — the declared-vs-enforced failure mode that is worst for AI-authored metadata, which has nothing but the declaration to go on.

  • names data-table.tsx → packages/components/src/renderers/complex/data-table.tsx — edited by this change

    What was measured. The key was declared on both published faces — data-display.ts (toolbar?: SchemaNode[], "Table toolbar actions/content") and the Zod mirror (SchemaNode | SchemaNode[]) — documented, mirrored, and read by NOTHING: data-table.tsx, the registered renderer for type: 'data-table', contains the word only in two prose comments and never reads schema.toolbar. The sibling emptyAction slot on the same interface IS mounted through SchemaRenderer, so the census zero is a reading, not a blind query. An author who wrote a toolbar got a green document and a blank result, with no signal anywhere that said so — the declared-vs-enforced failure mode that is worst for AI-authored metadata, which has nothing but the declaration to go on.

.changeset/6882-datatable-declare-render-cell-editor-and-cell-classname.md

  • names data-table.tsx → packages/components/src/renderers/complex/data-table.tsx — edited by this change

    data-table has read both keys on its production path all along — renderCellEditor through a (schema as any) cast, cellClassName by destructuring it into the class of its three utility cells (the selection checkbox, the row number, the row actions). Neither was declared, so authoring either one was unchecked: a misspelling produced no error and no widget, and no editor completion offered them. DataTableSchema now declares both, and the cast in data-table.tsx is gone rather than replaced.

.changeset/6909-fieldeditwidget-dom-pass-through.md

  • names data-table.tsx → packages/components/src/renderers/complex/data-table.tsx — edited by this change

    Also corrects a comment in @object-ui/components' data-table.tsx that this change falsifies. It justified the injected editor's document-level pointerdown listener partly with "FieldEditWidget forwards autoFocus and nothing else out of the DOM block, so a host handler could not reach the control through it even if one were passed" — no longer true. The listener is still load-bearing for the other half of that reason, which is untouched: the renderCellEditor context object has nowhere to put an onBlur in the first place. Comment only; no behaviour change in that package.

.changeset/6951-tree-view-data-retired.md

  • names data-display.ts → packages/types/src/data-display.ts — edited by this change

    Two published faces, one retirement — and why a tombstone, not a deletion. The TypeScript interface TreeViewSchema (@object-ui/types, data-display.ts) declares data?: never; the Zod mirror TreeViewSchema (@object-ui/types/zod, data-display.zod.ts) declares data as a retirementTombstone(). BaseSchema already declares data?: any (z.any().optional() on the mirror), so DELETING the member would not have refused the key — it would have ADMITTED it, unvalidated, through the base member, and the renderer would have drawn an empty tree. The tombstone on the extended schema shadows the base member on both faces; the pin measures the base accepting the very document the extended schema refuses.

.changeset/6972-markdown-inert-keys-retired.md

  • names data-display.ts → packages/types/src/data-display.ts — edited by this change

    What was measured, on this branch's base. sanitize was declared ?: boolean with @default true on both published faces — data-display.ts and the Zod mirror — documented, and read by NOTHING. Worse than an ordinary inert key, it implied a switch that does not exist: sanitization is unconditional. rehypePlugins in plugin-markdown/src/MarkdownImpl.tsx is a module-level const array whose last link is [rehypeSanitize, sanitizeSchema], handed to ReactMarkdown as-is — no ternary, no if, no runtime assembly. MarkdownRenderer forwards exactly content and className, and MarkdownImplProps accepts only those two. A repo-wide grep for schema.sanitize over packages/ and apps/ returns nothing, against a control of 20 .tsx files reading schema.content in the same query shape, so the zero is a reading, not a blind query. An author writing sanitize: false believed they turned XSS filtering off; one writing sanitize: true believed they turned it on. Neither was true.

.changeset/7070-no-invented-gantt-date-fields.md

  • names plugin-view/src/ObjectView.tsx → packages/plugin-view/src/ObjectView.tsx — edited by this change

    • app-shell/src/views/ObjectView.tsx — the console object page. The inline branch becomes ganttViewOptions, the sibling of calendarViewOptions and timelineViewOptions: the declared block spread whole, title floored at 'name', no date field invented. - plugin-list/src/ListView.tsx — the render branch AND the capability gate. - plugin-view/src/ObjectView.tsx — generateViewSchema, the authored object-view element route, which bypasses ListView entirely.

.changeset/7499-gantt-non-axis-floors-omitted.md

  • names plugin-view/src/ObjectView.tsx → packages/plugin-view/src/ObjectView.tsx — edited by this change

    • plugin-list/src/ListView.tsx — the object-gantt render branch. - plugin-view/src/ObjectView.tsx — generateViewSchema, the authored object-view element route, which bypasses ListView entirely.

.changeset/7722-wrapper-class-five-more.md

  • names data-display.ts → packages/types/src/data-display.ts — edited by this change

    Each of renderers/form/switch.tsx, textarea.tsx, date-picker.tsx, select.tsx and renderers/data-display/list.tsx reads schema.wrapperClass onto its wrapper element, and neither the TypeScript interface (form.ts, data-display.ts) nor the zod mirror (zod/form.zod.ts, zod/data-display.zod.ts) declared the key. The reads compiled through BaseSchema's index signature (objectui#5155) and the values parsed through .passthrough(), admitted unexamined. The same key, on the same class of read, is declared on CheckboxSchema (objectui#6938), FileUploadSchema and FilterBuilderSchema (objectui#6150); these five were left out only because their doc pages never listed it.

.changeset/7804-data-table-handler-key-arms.md

  • names renderers/complex/data-table.tsx → packages/components/src/renderers/complex/data-table.tsx — edited by this change

    BaseSchema is .passthrough(), so a key no arm declares is not refused — it stops being judged and the value is KEPT. All seven were in that state while renderers/complex/data-table.tsx read and INVOKED each one, so an authored { "type": "data-table", "onRowClick": { "action": "toast" } } parsed GREEN and that action object was handed to a call site expecting a function. Each key is now a named refusal on the mirror (handlerKeyRefusal, the objectui#6124 shape), and the arm's message says why JSON cannot author it and what to write instead.

.changeset/8331-data-table-empty-action-primitive-node.md

  • names packages/types/src/data-display.ts → packages/types/src/data-display.ts — edited by this change

    Behaviour change on a published surface, deliberately. DataTableSchema.emptyAction is declared SchemaNode on both published faces — packages/types/src/data-display.ts and its Zod twin in packages/types/src/zod/data-display.zod.ts — and SchemaNode is BaseSchema | string | number | boolean | null | undefined. The empty-state render path additionally required typeof … === 'object', so an authored emptyAction: 'Create the first record' rendered nothing and reported nothing: declared wider than enforced, failing in the direction that loses the author's content without a diagnostic. A node that renders nothing today therefore starts rendering.

.changeset/8653-listview-title-retired-rowactiondefs-pinned.md

  • names packages/plugin-view/src/ObjectView.tsx → packages/plugin-view/src/ObjectView.tsx — edited by this change

    title — retired. ListView resolved its export filename through schema.label || (schema as any).title. @objectstack/spec/ui's ListViewSchema refuses title by name (unrecognized_keys: ['title']) while ObjectGridPropsSchema accepts it; packages/types mirrors the platform contract rather than ruling over it, so declaring title on ListViewSchema would have made this repo accept what the platform save gate rejects. That asymmetry is also why objectui#6639 could take the declare branch for ObjectGridSchema.title one package over and this site could not. A parse-based census of apps/ examples/ content/ and packages/ found zero list-view nodes authoring title, so the retirement costs no author a filename. Over that same corpus the instrument reports three object-grid nodes carrying the key: two authored ones, both in content/docs/api/schema-reference.md, plus one that is not authored at all — packages/plugin-view/src/ObjectView.tsx composes title: schema.table?.title onto a grid node it builds, so it is a producer writing the key rather than an author declaring it. ObjectGrid's own title reads are untouched — they remain declared, ruled and read.

.changeset/9256-list-timeline-content-channels.md

  • names packages/types/src/data-display.ts → packages/types/src/data-display.ts — edited by this change

    These two were held out of the previous family-D slice for a SERIAL constraint on packages/types/src/data-display.ts and never for a verdict. Readership was re-derived for both rather than inherited: a TypeScript compiler-API sweep files every .body / .children read under the declared type of its receiver and answers zero for ListSchema and TimelineSchema while its live controls fire. timeline's bare-key owner is any-typed, so it was attributed directly as well — packages/plugin-timeline contains no channel read of any kind.

.changeset/dashboard-stable-empty-rows-4629.md

  • names data-table.tsx → packages/components/src/renderers/complex/data-table.tsx — edited by this change

    Nothing rendered wrong before or after; this is wasted work in the empty window, plus the live react-hooks/exhaustive-deps warning the conditional raised. It is the same module-scope frozen empty data-table.tsx adopted for its own EMPTY_ROWS (objectui#4618), applied to the provider: 'object' siblings.

.changeset/object-view-unmirrored-keys-7779.md

  • names packages/plugin-view/src/ObjectView.tsx → packages/plugin-view/src/ObjectView.tsx — edited by this change

    What was measured. Every reading was taken on the object-view node renderer (packages/plugin-view/src/ObjectView.tsx, registered by plugin-view/src/index.tsx) with schema.objectName / schema.layout as the positive controls of the same schema.KEY query, so each zero is a reading; the repo-wide census of viewTabBar finds the key in no source file outside @object-ui/types (two doc tables listed it as authorable and are corrected here). The spec side was read through the installed pin (@objectstack/spec@17.2.0, ui entry, 117 exported object schemas walked; control keys objectName / columns / navigation / listViews hit): the three spec-modelled keys are optional slots on ListViewSchema and ObjectListViewSchema; the six local keys have no spec slot anywhere.

.changeset/table-renderer-declared-column-contract-5350.md

  • names packages/types/src/data-display.ts → packages/types/src/data-display.ts — edited by this change

    renderers/complex/table.tsx resolved a heading as col.header || col.label and a cell as row[col.accessorKey || col.name]. Neither label nor name is declared on TableColumn, which declares header and accessorKey — both required (packages/types/src/data-display.ts). This was the fourth site of the column-alias family, after data-table, ObjectDataTable and ObjectGrid (objectui#5350).

Read the paragraph, not the line: both false halves of the objectui#8617 claim sat in one paragraph, and correcting either alone would have left it asserting the same wrong thing.

If a claim did go false, correct the body. That is precedented and prose-only, frontmatter untouched; check-changeset-overwrite.mjs will report the correction as its own case 2 ("correcting a declaration on purpose … legitimate"), which is the intended shape — one gate asks for the read, the other records the write.

Not covered, stated so nobody reads this as more: a born-false claim that spells no line address at all (objectui#9495 coordinated one by ORDINAL — "a grep finds that member first" — and deciding that means reading what the sentence means), a claim spelled as a symbol or a package rather than a backticked file name, and a file named ambiguously.

Compared the checked-out tree with 3e4f6324f (merge-base with origin/main): 5 file(s) changed outside .changeset/, read against 1141 pending declaration(s) that publish a body (1668 pending in total). · run

@os-justin os-justin added domain:ui objectui ui stream: fix lands on the published library or apps — objectui execution seat priority:p2 labels Sep 18, 2026 — with Claude
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 329 chunks) 3049.3 KB 3104.5 KB
Main entry chunk (gzip) 145.7 KB 350 KB
Entry file index-wHxSXIZZ.js —
Status PASS —

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

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.69KB 6.21KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 545.99KB 130.71KB
core (index.js) 8.94KB 3.59KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 215.98KB 59.97KB
fields (index.js) 249.15KB 62.87KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.22KB 2.26KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 32.15KB 10.49KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.83KB 10.95KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.26KB 1.99KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.52KB 4.88KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.39KB 3.10KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 14.81KB 3.63KB
plugin-calendar (index.js) 49.92KB 14.22KB
plugin-charts (index.js) 71.49KB 19.99KB
plugin-chatbot (index.js) 195.34KB 46.51KB
plugin-dashboard (index.js) 131.44KB 34.65KB
plugin-designer (index.js) 215.94KB 44.33KB
plugin-detail (index.js) 253.28KB 65.87KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 136.71KB 34.16KB
plugin-gantt (index.js) 167.62KB 41.26KB
plugin-grid (index.js) 212.61KB 57.90KB
plugin-kanban (index.js) 48.10KB 14.94KB
plugin-list (index.js) 112.74KB 27.70KB
plugin-map (index.js) 21.48KB 6.99KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.41KB 11.93KB
plugin-timeline (index.js) 30.07KB 8.74KB
plugin-tree (index.js) 10.58KB 3.72KB
plugin-view (index.js) 85.05KB 21.01KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 104.82KB 34.67KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.66KB 2.50KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 14.82KB 4.99KB
types (ai.js) 4.11KB 2.06KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 4.73KB 2.28KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 14.04KB 5.36KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

Copy link
Copy Markdown
Collaborator Author

Contract review

Served-tier: 97/97 CONTRACT_REVIEW_TIER
Head-sha: dea7eb80251a66666a0fa30fc68b388bad5ae7e5

Reviewed at head dea7eb802 of claude/issue-9462-row-click-modifier-payload-forward (PR objectui#9797, card objectui#9462), merge-base 3e4f6324f with main, 6 files, +377/−21. Every reading below was re-derived from the diff, the tree at head, and the cards it cites, ⛔ not from the dispatch order or the accepting seat's comment. Spelling note: generic type arguments are written TYPEARG (for example Record TYPEARG) because the body sanitizer eats angle-bracketed spans, code spans included; arrows are written →.

① Derived judgments

  1. ObjectViewProps.onRowClick (packages/plugin-view/src/ObjectView.tsx:410) — widened, and it is a PUBLISHED face. onRowClick?: (record: Record TYPEARG) → void became onRowClick?: (record: Record TYPEARG, event?: any) → void. packages/plugin-view/package.json at head carries no private, exports["."].types → ./dist/index.d.ts, and src/index.tsx:23 reads export type { ObjectViewProps } from './ObjectView';, so the member reaches the entry .d.ts by name. Measured, not inferred: an isolated declaration-only emit of ObjectView.tsx at base and at head (global tsc 6.0.2, single file, --noResolve; this box has no workspace node_modules, so the vite-plugin-dts build itself could not be run) differs on exactly two non-comment lines, this member and item 2. Accept set on the PROP side: unchanged for every handler that compiled before — a one-parameter handler is assignable to the widened spelling and a two-parameter handler written against the widened spelling is assignable back to the old one (both directions proven with a --strict probe whose @ts-expect-error rows all consumed). Runtime face: a host handler now receives a second argument where it always received undefined. Judgment: right — the payload it now receives is the one useNavigationOverlay.handleClick has always forwarded (useNavigationOverlay.ts:299-300), and handleRowClick (ObjectView.tsx:1180-1183) now forwards it instead of truncating.

  2. The onRowClick inside ObjectViewProps.renderListView's props (ObjectView.tsx:438) — widened, same face. Same before/after as item 1. This one has a CALL-side accept-set change as well: a host's custom list view may now invoke props.onRowClick?.(record, e) with two arguments, which was TS2554 against the old spelling. That is what RED 3 in the new pin exercises, and the value handed there is handleRowClick verbatim (ObjectView.tsx:2226). Judgment: right, and load-bearing rather than decorative.

  3. DataTableSchema.onRowClick (packages/types/src/data-display.ts:1400) — widened, published from @object-ui/types (exports["."] and ./data-display, both dist/*.d.ts). onRowClick?: (row: any) → void became onRowClick?: (row: any, event?: any) → void. Isolated emit of data-display.ts at base vs head differs on exactly this one non-comment line. Compile-forced, measured: the renderer's two repaired calls (data-table.tsx:2320 schema.onRowClick(row, e) and :2360 schema.onRowClick?.(row, e)) do not type-check against the one-parameter spelling — a two-argument call against (row: any) → void is TS2554 under strict, proven in the same probe. So the widening is not optional scope; it is the declaration following the call the card exists to repair. Judgment: right.

  4. The three runtime forwards (data-table.tsx:2320, :2360, ObjectView.tsx:1182) — each passes the DOM event the handler already had in scope; nothing re-shapes the first argument; the payload's shape is still objectui#9360's HandleClickModifiers (metaKey / ctrlKey / button, useNavigationOverlay.ts:176-182), which a React mouse event satisfies structurally. ObjectGrid's own keyboard/onActivate paths still call navigation.handleClick(row) with no event (ObjectGrid.tsx:2714, 2732, 2930, 2942, 4949) — untouched and correct, since they carry no modifier. Judgment: right.

  5. The event?: any spelling — the objectui#9341 rationale checked, and it holds, with one refinement. The rationale (PR objectui#9356 body, ruling comment on objectui#9341) has two legs. (a) In @object-ui/types the named type is unreachable: HandleClickModifiers is declared in @object-ui/react (useNavigationOverlay.ts:176), and @object-ui/react's manifest depends on @object-ui/types, so naming it in types is a phantom dependency closing a cycle; BaseSchema.onClick/onChange/onSubmit (base.ts:1141-1143) already use event?: any. Holds for item 3. (b) A host that discovered the payload from the implementation annotated it as React's mouse event; a declaration naming HandleClickModifiers refuses that handler contravariantly. Measured here: with strict, a handler (r, ev: MouseEventLike) and even the optional spelling (r, ev?: MouseEventLike) are both refused by event?: HandleClickModifiers AND by event?: unknown; only any accepts them. Holds for items 1 and 2, where the named type IS reachable (plugin-view depends on @object-ui/react) — so there the reason is leg (b) alone plus family consistency (ObjectGrid.tsx:547, ListView.tsx:282, RelatedList.tsx:137, ObjectGridSchema, ObjectKanbanSchema.onCardClick, ObjectGallerySchema all spell event?: any), and the shipped JSDoc says exactly that. AGENTS.md Fix documentation deployment for www.objectui.org #6 (no any) is overridden by that measured refusal, as it was in objectui#9460's review of thirteen sibling faces. Judgment: right; unknown would have been a narrowing of the accept set, and it was not chosen.

  6. Nothing narrowed, nothing refused that was accepted before — searched, not assumed. No exact-identity pin exists on DataTableSchema['onRowClick'] or ObjectViewProps['onRowClick'] at head (the only reader is a one-way KeepsFunction in handler-keys-json-refusal-6124.test.ts:772, which still holds). Parameters reads on this name occur only inside the two objectui#9357 pins' locally reproduced spellings. Exact-argument-list assertions in files naming onRowClick/handleRowClick: one, the hook's own toHaveBeenCalledWith(record, event), unaffected. The zod twin refuses onRowClick by name and is untouched. The arity ledger's two former controls for these declarations moved to IN_SITES with hop regexes that match text present at head (schema.onRowClick(row, e) in the JSDoc at data-display.ts:1382; onRowClick(record, event) after export interface ObjectViewProps; onRowClick: handleRowClick after the renderListView opener). Judgment: no narrowing.

  7. ObjectDataTableSchema.onRowClick (packages/types/src/objectql.ts:4530) left at one parameter — coherent, and correctly declared as a gap rather than hidden. ObjectDataTable.tsx:1043 puts schema.onRowClick ?? (recordDrillEnabled ? handleRowClick : undefined) on the same data-table node, so after this PR a host handler on that key IS invoked with two arguments while its declaration says one — the ledger's own IN criterion (navigationOverlayConsumers.onRowClickArity-9357.test.ts:18-24) would place it IN. It is not self-contradictory in the type system (the one-parameter value is assignable into the widened slot, no compile or runtime break), it is the same understated class the family has been retiring one card at a time, and this PR names it in the changeset's "Not in this change", rewrites the ledger control to say "known gap", and successor card objectui#9799 was filed at 05:53Z. Two exact-identity pins hold that key at one parameter today (ObjectDataTable.schemaAnchor-6576.test.ts:56, widget-schema-anchors-6576.test.ts:93), so widening it is genuinely its own published-surface move with its own clause-② declaration, not a two-token rider. Judgment: coherent with this PR's contract story; see ③ for what the successor must carry.

② Semver level

minor for @object-ui/components, @object-ui/plugin-view, @object-ui/types — correct, and consistent with the changeset. Pure-semver reading: an optional parameter added to two published callback declarations plus a runtime behaviour change on a published callback (a second argument now arrives) is additive → minor; nothing is removed or refused. Repo reading: AGENTS.md:265 forbids major in changesets (objectui's own breaking changes are also minor, enforced by check-changeset-no-major.mjs), so minor is the ceiling and the right floor here. Precedent: the same class of widening shipped as minor in .changeset/9341-kanban-card-click-fires-once.md, 9357-navigation-overlay-onrowclick-arity.md and 9357-navigation-overlay-consumer-arity.md. components moves no declaration (its diff is two call sites plus comments) but does change published runtime behaviour, so minor rather than patch is the defensible choice. The changeset body names the three widened declarations, the published entry that moves, the both-directions compatibility, and the excluded sibling — it agrees with the diff.

③ Boundary flags

  • Dev flag — ObjectDataTableSchema.onRowClick unwidened (PR body "To file"). Answered in ①.7: accepted as out of scope, coherent, successor objectui#9799 exists. ⚠️ The successor must move the two exact-identity Equal pins named above and promote the ledger control to IN_SITES; it carries its own Clause-②: yes.
  • Dev flags — "Out-of-scope, noted and NOT filed": VirtualGrid.onRowClick (second parameter index: number, a different contract, already a ledger control) — agreed; RecordPickerDialog one-parameter onRowClick (the picker's own contract, never reaches the data-table slot) — agreed; ObjectManager.tsx passing a one-parameter handler to ObjectGrid's two-parameter prop (assignable, ignores the payload by choice) — agreed.
  • Reviewer flag (new, ⛔ 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." That is false on the ObjectView → ObjectGrid path: handleClick returns early when an external onRowClick is present (useNavigationOverlay.ts:299-302) and ObjectView always passes handleRowClick, so the hook's modifier branch (:307-315) never runs there, and handleRowClick's own branches read no event. Consequence: in a plugin-view ObjectView with NO host onRowClick, Cmd/Ctrl/middle-click still degrades to an ordinary navigation. That is outside objectui#9462 (its subject is hosts that DO supply a handler), it misleads no published declaration (the comment is a function-body //, not shipped JSDoc), and it changes nothing in ①/②. It should become its own card; the fix would be a branch in handleRowClick, not a declaration change.
  • .d.ts measurement scope: movement was measured by isolated single-file declaration emit at base and head, not by the workspace build (no node_modules on this box); three non-comment declaration lines move, exactly the three named, and @object-ui/components emits no declaration change.
  • any versus AGENTS.md Fix documentation deployment for www.objectui.org #6: resolved by the measured contravariant refusal in ①.5 and the family precedent; a future convergence on a named payload type is a separate ruling (objectui#9357's open question), not this card's.
  • Independence pair: the PR and the dev report were produced in the dispatching seat's own session; per the rulebook a mode:subagent dev is identified by its branch, in-seat review of such a build is by design, and this record was rendered by an isolated review subagent and adopted by that seat. Not SELF-REVIEW by the machine-read pair.
  • CI at head when read (06:05Z): 36 check-runs — 33 success, 3 skipped, 0 failing, 0 in progress. The landing check ③ re-reads this; it is recorded here as a reading, not a clearance.
  • Carriers: --pair 9797 read exit 0 (both needs:contract-review carriers hung, claim readable) before this record existed; no earlier record or void on this head.

Implemented-by: claude/issue-9462-row-click-modifier-payload-forward
Reviewed-by: session_012EpHzwH4wTy5sd7ibkD2yq

VERDICT: PASS


Generated by Claude Code

Copy link
Copy Markdown
Collaborator Author

Clause-② carriers cleared — provenance

domain:ui execution seat, session_012EpHzwH4wTy5sd7ibkD2yq, 2026-09-18T06:10Z.

field value
review record comment 5725942570 on PR objectui#9797
judged head dea7eb80251a66666a0fa30fc68b388bad5ae7e5 — ✅ equal to the PR's head, read in this act
verdict PASS
gate check-clause2-carriers.mjs --pair 9797 → exit 0, C6-RECORD naming that comment and that head

⛔ Adopted verbatim, ⛔ not edited. contract-review.md allows the parent session only to adopt a record word for word or void it entirely — 「⛔ 永不改写润色」 — so the record was posted through the raw REST comments endpoint rather than this seat's stamping tool, because that tool substitutes tokens and a substitution is an edit. The stored body is byte-identical to what the reviewer returned plus the platform's own appended footer; all nine required 同形 markers read back present.

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 platform-readings.md:385 designates: grepping its own transcript for the harness-written per-request stamp, counted off type: "assistant" lines only (a raw grep over-counts, because printed tool results contain the same literal). It reported 97/97 and then named the gap its own method could not close: "the one or two requests after that tally (this handback itself) are unmeasured by construction; the seat can re-tally the same file after I exit … and should void the record if any later stamp differs."

I ran that re-tally after it exited, over the whole transcript, comparing each stamp against the value dispatch-gates.mjs:10730 declares for CONTRACT_REVIEW_TIER:

assistant requests: 101
  stamped at CONTRACT_REVIEW_TIER : 101
  stamped at any OTHER value      : 0
  missing a stamp                 : 0
=> RE-TALLY VERDICT: ALL AT TIER

⇒ the void condition did not trigger. ⚠️ The record's 97/97 is therefore a stale but honest control — true when written, and 101/101 over the transcript's whole life. ⛔ I did not edit it to say so, because editing it is the one thing I may not do; the corrected count lives here, in my own act, where it belongs.

Why this record exists at all, when two other authorities say it need not

⚠️ Recorded so a later reader does not mistake it for over-ceremony. contract-review.md:53 (ruling C, ab1d359259) says a below-tier seat discharges clause ② by an at-tier subagent; dispatch-gates.mjs:11949–:11952 and the maintainer-approved ruling on objectui#9533 (batch #152 item 5, citing batch #151 item 3) both say no at-tier review is owed outside the spec and skills lanes. They conflict. objectstack#18885 owns the conflict — I added this third datum to it rather than resolving it — and this seat took the stricter of the two, per SKILL.md 〈优先级〉's 「两条细则冲突 ⇒ 按更严的一条行动并立卡」.

⭐ One finding the review produced that no one else had

The record's ③ carries a new reading, and it falsifies text in this PR: 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 (useNavigationOverlay.ts:299–:302) and ObjectView always passes handleRowClick, so the hook's modifier branch never runs there. ⇒ in a plugin-view ObjectView with no host onRowClick, Cmd/Ctrl/middle-click still degrades to an ordinary navigation.

⛔ 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

@os-justin
os-justin marked this pull request as ready for review September 18, 2026 06:10
@os-justin
os-justin added this pull request to the merge queue Sep 18, 2026
Merged via the queue into main with commit f0f3cd5 Sep 18, 2026
38 checks passed
@os-justin
os-justin deleted the claude/issue-9462-row-click-modifier-payload-forward branch September 18, 2026 06:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

domain:ui objectui ui stream: fix lands on the published library or apps — objectui execution seat package: app-shell package: components package: react package: types plugin priority:p2 tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

finding(components): data-table and plugin-view/ObjectView receive the modifier payload and drop it, so Cmd/Ctrl-click reaches no host handler

2 participants