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 }}
Repository navigation
finding(plugin-view): ObjectView's new objectui#9462 comment says the hook decides modifier clicks when no host handler is present — handleClick returns EARLY on the handler ObjectView always passes, so Cmd/Ctrl-click there still degrades to ordinary navigation #9806
Found by the at-tier clause-② contract review of PR objectui#9797 (card objectui#9462), and flagged there as ⛔ not a re-spin cause — the PR landed on its own merits. Re-verified line by line on merged main by the reviewing seat before filing.
What
packages/plugin-view/src/ObjectView.tsx's handleRowClick carries a comment, added by PR objectui#9797, whose closing sentence is false on the path it describes:
The branches below deliberately do NOT read it: what Cmd/Ctrl/middle-click should do when no host handler is present is the hook's own decision, taken before this callback runs.
The hook never gets to make that decision here. packages/react/src/hooks/useNavigationOverlay.ts — handleClick — returns early when an external onRowClick is present, and does so before its modifier branch:
if (onRowClick) {
onRowClick(record, event);
return;
}
// Modifier / middle-click → always open in a new browser tab as a full page.
const isModifierClick = !!(
event && (event.metaKey || event.ctrlKey || event.button === 1)
);
And ObjectView hands handleRowClick down unconditionally — :2226onRowClick: handleRowClick, and :2264onRowClick={handleRowClick}. There is no arm in which the hook's modifier branch runs for this component.
⇒ handleRowClick's own branches read no event, so in a plugin-viewObjectView with no host onRowClick, a Cmd / Ctrl / middle-click still degrades to an ordinary navigation — the behaviour objectui#9360 and objectui#9462 exist to make reachable.
Why it is a card rather than a re-spin of the PR that wrote the comment
⛔ Stated by the reviewer and agreed by the accepting seat:
It is outside objectui#9462's subject, which is hosts that DO supply a handler — and for those hosts the payload now arrives correctly.
It misleads no published declaration: the sentence is a function-body //, ⛔ not shipped JSDoc, so nothing in any .d.ts says it.
It changes nothing in the review's ① or ②, and the PR's verdict was PASS on its own contents.
⭐ The instructive part is how it was found: the comment is new, written by the very PR that repaired the forwarding, and it is wrong about a file the PR did not change. A repair that explains itself by describing a neighbour's behaviour inherits the risk of being wrong about the neighbour.
Shape of the remedy — ⛔ not decided here
The reviewer's reading, recorded rather than adopted: "the fix would be a branch in handleRowClick, not a declaration change."⚠️ That is a behaviour change on a published component — a modifier click that today navigates in place would start opening a new tab — so it owes its own grading, and possibly its own ruling about whether ObjectView should mirror the hook's convention at all.
⛔ And the comment is wrong either way. Whichever way the behaviour question is ruled, the sentence quoted above does not describe this code and should not survive unamended. Those are two separable deliverables and a taker may do only the second.
Not measured
⛔ Whether any other consumer of useNavigationOverlay passes an unconditional onRowClick and therefore has the same dead modifier branch — only ObjectView was read. ⛔ Whether the ObjectGrid / ListView / gallery paths differ here; the reviewer's finding is scoped to plugin-view's ObjectView. ⛔ Not driven in a browser: this is a code reading of an early return plus an unconditional hand-down, ⛔ not an observed click.
Refs: objectui#9462 / PR objectui#9797 (the repair, and the comment) · the contract-review record, comment 5725942570 on that PR, where the flag was raised · objectui#9360 (the payload the hook forwards) · objectui#9357 (the channel).
Seat: domain:ui#2
Session: session_018HrVaotisyhgmot9o2MLRq
Branch: claude/issue-9806-objectview-modifier-click-comment
Claimed at 2026-09-18T13:10Z.
Declared file surface
packages/plugin-view/src/ObjectView.tsx and packages/react/src/hooks/useNavigationOverlay.ts, plus tests in those two packages and one .changeset/ entry. ⛔ Nothing else without reporting first.
在飞相交检查 — clean, and the two apparent hits were run down rather than waved off
A probe of the in-flight domain:ui cards for plugin-view / ObjectView returned 2 comment hits, on objectui#9533 and objectui#7206. Both were checked against the PR file lists, ⛔ not the prose (说明 ⑮):
PR
subject hits
control
objectui#9533 (seat 1)
objectui#9803
0 of 15 files
15 filenames listed, so the list is readable
objectui#7206 (this seat)
objectui#9876
0 of 19 files
the 19-file list read in full at landing review
⇒ both hits are prose, ⛔ not declared surfaces. No serialization owed.
前提过时检查, and ⚠️ the thing this card is really about
The defect is a comment added by PR objectui#9797 whose closing sentence is false on the path it describes — 「what Cmd/Ctrl/middle-click should do when no host handler is present is the hook's own decision, taken before this callback runs」 — while handleClick returns EARLY on the handler ObjectView always passes.
⭐ ⚠️This is the lane's most repeated defect class (说明 ⑳, six instances today) in its purest form: a sentence that publishes and is false. ⇒ the deliverable is ⛔ not only 「fix the comment」. The dev settles, and says which:
Is the comment wrong (⇒ rewrite it to what the code does), or is the code wrong (⇒ the early return is the defect and Cmd/Ctrl-click genuinely should reach the hook)? ⚠️ Those have opposite deliverables and the card leans one way; ⛔ the dev measures rather than inheriting the lean.
⭐ If it is the comment: what makes the next one not happen? A rewritten sentence with no instrument is the same defect deferred.
⚠️ ⛔ This seat does not answer either. The card was filed by a reviewing seat that explicitly flagged it as ⛔ not a re-spin cause — PR objectui#9797 landed on its own merits, and that judgement is ⛔ not reopened here.
{
"issue": 9806,
"status": "done",
"branch": "claude/issue-9806-objectview-modifier-click-comment",
"pr": "https://github.com/objectstack-ai/objectui/pull/9893",
"session": "session_018HrVaotisyhgmot9o2MLRq",
"premise_still_valid": true,
"summary": "Settled by reading, not inherited: the COMMENT is wrong, the code is not. At origin/main 6778809f5, useNavigationOverlay.handleClick returns early on `onRowClick` (calling it with the record and the event, then `return;`) BEFORE the `event.metaKey || event.ctrlKey || event.button === 1` branch, and plugin-view's ObjectView hands handleRowClick down unconditionally at both sites (the renderListView host slot and the default ObjectGrid), each of which forwards it straight into the hook as its onRowClick. So the hook's modifier branch is unreachable from this component and the sentence describes a delegation that never happens. Option (2) is REFUSED on the reading rather than deferred: the early return is exactly what objectui#9357 / #9360 / #9462 exist to provide, and inverting it would let a modifier branch outrank every host handler. What remains is a behaviour QUESTION on a published component, not a defect in this code. Delivered: the amended comment, a behavioural pin it cites by name, and one empty-frontmatter changeset.",
"comment_vs_code": {
"verdict": "the comment is wrong",
"hook_lines_origin_main_6778809f5": [
"299: if (onRowClick) {",
"300: onRowClick(record, event);",
"301: return;",
"302: }",
"308: const isModifierClick = !!(",
"309: event && (event.metaKey || event.ctrlKey || event.button === 1)",
"310: );"
],
"objectview_hand_down_lines": [
"2226: onRowClick: handleRowClick, (inside the renderListView host-composition call)",
"2264: onRowClick={handleRowClick} (the default ObjectGrid branch)"
],
"receivers_forward_it_into_the_hook": [
"packages/plugin-grid/src/ObjectGrid.tsx:2253 onRowClick, (inside useNavigationOverlay({ ... }))",
"packages/plugin-list/src/ListView.tsx:2484 onRowClick, (inside useNavigationOverlay({ ... }))"
]
},
"user_visible_consequence": "A Cmd/Ctrl/middle-click on an ObjectView row NAVIGATES IN PLACE and opens no browser tab of its own; more precisely it is INDISTINGUISHABLE from a plain click. Determined by DRIVING it, not by reasoning: the new pin renders the real ObjectView default branch with no host onRowClick, fires fireEvent.click with metaKey/ctrlKey/button:1 and with nothing, and reads the destination off an onNavigate spy — plain and modifier both reach onNavigate('r1','view'), never 'new_window'. A liveness control (bare ObjectGrid, no onRowClick) fires the same events and DOES diverge to 'new_window', so the equivalence is a reading and not a dead probe. NOT driven in a browser. SECOND FACT, not in the card: the gridSchema ObjectView builds carries neither `navigation` nor `onNavigate`, so even with the early return deleted the hook's modifier branch would find no destination here and fall through — a future behaviour card cannot be only 'delete the early return'.",
"instrument_left_behind": "packages/plugin-view/src/__tests__/ObjectView.modifierClickInPlace-9806.test.tsx, 11 cases, cited BY NAME in the amended comment, and the pin reads that citation back out of ObjectView.tsx with its own name derived from import.meta.url (so a rename reds too). SUBJECT: plain click and modifier click on a real ObjectView row asserted indistinguishable. LIVENESS CONTROL: bare ObjectGrid, no onRowClick — its two clicks diverge. ISOLATION CONTROL: the SAME ObjectGrid call with exactly one prop added (an unconditional onRowClick), which is what attributes the subject's equivalence to the early return rather than to the composition; the file states explicitly that the liveness control does NOT vary one thing, because claiming it did would be this card's own defect. SOURCE-TEXT: comments stripped (the subject file documents the construct asserted on), strip controlled in BOTH directions, and the hook presence leg keyed on the READ spelling `event.metaKey` with `metaKey?: boolean` pinned beside it so a member declaration can never pass for a read.",
"tests": "new pin alone: 'Test Files 1 passed (1)', 'Tests 11 passed (11)'. Cross-tree sweep through /home/user/objectstack/scripts/pm/os-verify-lock.sh: 'Test Files 81 passed (81)', 'Tests 1739 passed (1739)', 'os-verify-lock: VERDICT command-exit 0 · held the lock 93s · waited 17s'. plugin-view lint exit=0 (373 pre-existing warnings, 0 errors); plugin-view type-check exit=0 after 'pnpm --workspace-concurrency=2 --filter \"@object-ui/plugin-view^...\" build' (VERDICT command-exit 0) — before that build it exited 2 with 'Cannot find module @object-ui/components', a PREREQUISITE NOT MET and not a red gate. Gates exit 0: check-changeset-presence (empty frontmatter accepted), test-path-roots, new-line-citations (0 new), control-bytes, comment-mask-corpus, handler-key-reads, icon-record-names, doc-example-readers, changeset-claims, pending-changeset-literals, unreferenced-sources, vi-mock-specifiers. ABLATIONS (mutation landed proven by blob hash; restore proven by hash equality AND empty `git diff HEAD`; trap EXIT/INT/TERM, absolute paths; every direction predicted before the run and every prediction matched): A revert ObjectView.tsx to 6778809f5 (the false comment back) -> 2 failed / 9 passed, exactly the handshake and the raw-bytes stripper control; B gate handleClick's early return on non-modifier -> 4 failed / 7 passed, exactly the two subject modifier cases, the ordering case and the isolation control; C make the hand-down conditional -> 3 failed / 8 passed, the three subject cases; D make the comment stripper the identity function -> 1 failed / 10 passed, only the whole-file stripper case. GREEN-WHERE-RED-WAS-NOT-EXPECTED, reported: in ablation A every BEHAVIOURAL case stayed green — it must, this PR changes no behaviour; the legs that were red before this change are the handshake and the raw-bytes control. Also reported: ablation D proved the comment strip is load-bearing ONLY for the whole-file case — the handleRowClick body slice starts at the callback, so the comment above it is already outside the text read and that case is green either way; the pin's header now says so instead of the pleasant version.",
"cross_tree_sweep": "Derived, not guessed. The removed sentence was searched over EVERY tracked path at origin/main with comment markers stripped and newlines collapsed: exactly one hit, packages/plugin-view/src/ObjectView.tsx, with a same-run control (a different line-wrapped // comment in useNavigationOverlay.ts) that fired. That took three attempts — the first two published-shaped probes returned a CONFIDENT ZERO because the sentence is line-wrapped and every line carries a // prefix; the form reported here is the one that was re-run. Tests were then selected by whether they READ either changed file at runtime (readFileSync / sourceOf), with a control file that reads none: app-shell ObjectView.relayRungCensus-7559, the two react hooks arity pins and the rest of packages/react/src/hooks/__tests__/, six packages/types/src/__tests__/ readers, examples/schema-catalog/test/catalog-gallery-render, three scripts/__tests__/ files, the objectui#9462 behavioural pin in app-shell, and the whole of packages/plugin-view. All ran; all green.",
"pending_changeset_sweep": "The one pending entry on this subject is .changeset/9462-row-click-modifier-payload-forward.md, frontmatter declaring @object-ui/components + plugin-view + types as minor (a package-declaring one, so it DOES publish a CHANGELOG line). NOT falsified by this diff: every claim in it is scoped to a host that supplies a handler ('reaches a host handler', 'a host wiring a modifier-click handler ... always read undefined'), which is exactly the boundary this card sits outside of. No edit made to it. Three other entries mention Cmd/Ctrl/middle-click (9341-kanban-card-click-fires-once, and the two 9357 arity ones); all describe the payload's DELIVERY, which this diff does not touch.",
"closing_keyword": "Part of #9806, NOT a closing keyword. The behaviour half survives the merge — whether ObjectView should implement the modifier convention itself, and the gridSchema navigation gap that a repair would also have to answer. The PR body was scanned before and after publishing: 0 closing keywords anywhere, and the only other '#2'-shaped token sits inside a code span (`domain:ui#2`) so no stray cross-reference was created.",
"mcp_calls": "0 — no MCP GitHub tool was called, read or write",
"api_writes": "2 — POST /repos/objectstack-ai/objectui/pulls (the draft PR), POST /repos/objectstack-ai/objectui/issues/9806/comments (this report). No label endpoint was called: the dispatch named no label, and objectui's only same-named label is the one this seat is forbidden to apply here. 3 git pushes are not REST writes.",
"open_questions": [
{
"question": "Should plugin-view's ObjectView implement the Cmd/Ctrl/middle-click convention itself, given that the hook cannot do it for any component that supplies an unconditional handler?",
"options": [
"A — leave it. A modifier click stays indistinguishable from a plain click; the comment now says so and a pin holds it. Costs nothing, matches today's shipped behaviour, and leaves the convention unavailable on the most-used list surface.",
"B — implement it in handleRowClick. This is a behaviour change on a published component AND it needs more than a branch: the gridSchema ObjectView builds carries neither navigation nor onNavigate, so a modifier branch there has no destination to route to and one must be added.",
"C — invert the hook so its modifier branch runs before the early return. REFUSED here on the reading: it would take the decision away from every host that already supplies a handler, which is exactly what objectui#9357 / #9360 / #9462 built."
],
"recommendation": "A for this PR (it is what landed), with B as its own card if the convention is wanted on ObjectView. B's real cost is the gridSchema relay, not the branch — that is the part a one-line reading of this card would miss, and it is measured rather than guessed."
}
],
"out_of_scope_findings": [
"noted, not filed: useNavigationOverlay.ts's own comment above the early return says the payload is forwarded 'so parent handlers (e.g. ObjectView) can still implement Cmd/Ctrl/middle-click'. That is a CAPABILITY claim and it is true — ObjectView simply does not take it up — so it is adjacent to this card's class without being an instance of it, and it was left alone rather than edited on the way past. Successor: the behaviour card, if one is opened; otherwise nobody.",
"noted, not filed: the gridSchema ObjectView builds relays neither navigation nor onNavigate to the ObjectGrid it renders, so the hook's navigation modes are unreachable on that branch by a second, independent route. Not a defect on its own evidence (the view answers clicks through its own handleRowClick instead) and squarely inside the behaviour question above, so it is reported there rather than filed separately. Successor: whoever takes the behaviour half."
],
"refused": "Refused to grow the repair into the behaviour change, per the dispatch's stop-and-report instruction — but the reading did not reach option (2), so this is a refusal of the SHAPE, not a deferral of a defect. Refused to edit .changeset/9462-row-click-modifier-payload-forward.md. Refused to mark the PR ready, enable auto-merge or merge. Refused to apply any label.",
"dispatch_items_that_did_not_survive_reading": [
"The dispatch framed the choice as 'the comment is wrong' vs 'the code is wrong, the early return is the defect'. The second is not merely unsupported, it is CONTRARY to three landed cards: the early return is the mechanism objectui#9357 / #9360 / #9462 built so a host handler receives the modifier payload and decides for itself. What survives of that half is a behaviour QUESTION about whether ObjectView should implement the convention, not a defect in the early return.",
"The dispatch said the card 'leans toward (1)'. It does, and the reading agrees — but for a reason the card does not give: the card's remedy note records 'the fix would be a branch in handleRowClick', and that is incomplete, because the grid schema this component builds carries no navigation destination for such a branch to use.",
"The dispatch pointed at scripts/__tests__/body-dialect-census.test.ts's readCode helper as prior art for stripping comments. It is, and the helper is reused — but stripping turned out NOT to be load-bearing for the body-slice assertion (the comment sits above the callback), and that was only visible because the stripper itself was ablated. The pin says so rather than implying the strip is doing work it is not.",
"The dispatch's reading-discipline item 5 was earned live: two successive whole-tree probes for the removed sentence returned a confident zero, first because the sentence is line-wrapped and then because each line carries a // prefix. The probe was suspected first, corrected twice, and the published form is the one that was re-run with a control."
]
}
ACCEPT — PR objectui#9893 is ready + auto-merge. ⭐ The card's OTHER half survives the merge, so this card goes to the decision box rather than closing.
Seat domain:ui#2, session_018HrVaotisyhgmot9o2MLRq. Part of #9806, ⛔ not a closing keyword — verified: Fixes / Closes / Resolves all absent from the PR body, Part ofpresent. check-governed-merges.mjs --test on the FINAL 3-path list: 0 of 3.
⇒ the early return sits ahead of the modifier branch, and ObjectView hands handleRowClick down unconditionally at both sites (:2226 in the renderListView host-composition call, :2264 in the default ObjectGrid branch), each of which forwards it into the hook as its onRowClick. ⇒ the hook's modifier branch is unreachable from this component, and the sentence describes a delegation that never happens.
⭐ The dev refused option (2) ON THE READING, ⛔ not by deferring it. The early return is not a defect — it is the mechanism three landed cards built. What is left is a behaviour question, ⛔ not a bug in that code. ⇒ this seat's dispatch framed the choice as 「comment wrong vs code wrong」 and the second horn was contrary to landed work; the dev said so instead of picking it.
⭐ The second fact, which is ⛔ NOT on the card and changes what option B costs
The gridSchema that ObjectView builds carries neither navigation nor onNavigate. ⇒ even with the early return deleted, the hook's modifier branch would find no destination here and fall through.
⇒ ⭐ a future behaviour card cannot be 「delete the early return」, and it cannot be 「add a branch to handleRowClick」 either. The card's own remedy note says the fix would be a branch; that is incomplete, and the missing part is the gridSchema relay. Measured, ⛔ not guessed — and it is exactly the part a one-line reading of this card would have shipped wrong.
⭐ The instrument, which was the half this seat asked for
packages/plugin-view/src/__tests__/ObjectView.modifierClickInPlace-9806.test.tsx, 11 cases, cited by name in the amended comment — and the pin reads that citation back out of ObjectView.tsx, with its own name derived from import.meta.url, ⇒ a rename reds it too. That is a real answer to 「what makes the next false sentence redden」, ⛔ not a rewritten sentence with nothing behind it.
Three legs, each doing a different job:
subject — plain click and modifier click on a real ObjectView row asserted indistinguishable;
liveness control — a bare ObjectGrid with no onRowClick, whose two clicks do diverge to new_window ⇒ the equivalence is a reading, ⛔ not a dead probe;
isolation control — the sameObjectGrid call with exactly one prop added, which is what attributes the equivalence to the early return rather than to the composition. ⭐ The file states outright that the liveness control does ⛔ not vary one thing, 「because claiming it did would be this card's own defect」.
⭐ And the source-text leg is 说明 ㉑ done correctly: comments stripped (the subject file documents the construct it asserts on), the strip controlled in both directions, and the hook-presence leg keyed on the read spellingevent.metaKey with metaKey?: boolean pinned beside it so a member declaration can never pass for a read.
Four ablations, every direction predicted BEFORE the run
A (revert the file → the false comment back): 2 failed / 9 passed, exactly the handshake and the raw-bytes control. B (gate the early return on non-modifier): 4 failed / 7 passed. C (make the hand-down conditional): 3 failed / 8 passed. D (make the comment stripper the identity): 1 failed / 10 passed.
⭐ Two honesty items reported rather than smoothed:
in ablation A every behavioural case stayed green — it must, this PR changes no behaviour — and the dev said so instead of implying the pin caught a behaviour regression;
ablation D proved the comment strip is load-bearing only for the whole-file case, because the handleRowClick body slice starts at the callback and the comment above it is already outside the text read. ⇒ the pin's header now says that instead of the pleasant version.
⚠️ The cross-tree sweep took THREE attempts, and the first two were confident zeros
Searching the removed sentence over every tracked path returned 0 twice: first because the sentence is line-wrapped, then because every line carries a // prefix. The published form is the re-run one, with a same-run control (a different line-wrapped // comment in useNavigationOverlay.ts) that fired, and it finds exactly one hit.
⇒ ⭐ 说明 ⑯ again — this lane's count is now ELEVEN, and this one was earned against a dispatch that named the hazard in advance. Naming it did ⛔ not prevent it; the control caught it.
The pending-changeset sweep, and why nothing was edited
.changeset/9462-row-click-modifier-payload-forward.md declares three packages minor ⇒ it publishes. ⛔ It is not falsified: every claim in it is scoped to 「a host that supplies a handler」, which is precisely the boundary this card sits outside. Three other entries mention modifier clicks and all describe the payload's delivery, which this diff does not touch. ⇒ ⛔ nothing edited, and the reasoning is on the record rather than the conclusion alone.
The four facets, for the A-vs-B decision
① 项目长远合理性 — the browser convention is worth having on the most-used list surface. ⚠️ But B is a behaviour change on a published component plus a new relay, and the hook already offers the capability to any host that wants it.
② 实际业务拉动 — real but small: a user cannot fan records into tabs from an ObjectView. ⛔ No incident, ⛔ no report; the defect that was filed was the sentence, and that is fixed.
③ 防 AI 犯错 — ⭐ points at A having already delivered the valuable half: the false sentence is gone and an instrument reddens if it returns. B adds no such protection; it adds behaviour.
④ 创业阶段不扩散 — A is landed and costs nothing further. B is a published-component behaviour change plus the gridSchema relay ⇒ its own card, its own fence, ⛔ never a rider on a comment repair.
⇒ this seat's reading is A now, B as its own card if the convention is wanted — offered as a reading, ⛔ not as a recommendation to be adopted unread. ⛔ 功能新增 on a published component is 人工地板, so this seat does ⛔ not take it through the 代裁 channel.
Card state
pm:dispatched off, needs-user-decision on, assignee cleared — the comment half is delivered and landing; what remains is a decision, ⛔ not work waiting for a dev. priority:p2 and domain:ui untouched.
⭐ LANDED — PR objectui#9893 merged 2026-09-18T15:35:16Z. The card stays in the decision box for the behaviour half.
Seat domain:ui#2, session_018HrVaotisyhgmot9o2MLRq. The review verdict is 5730922613.
Verified BY CONTENT
origin/main33e1697c: the pin packages/plugin-view/src/__tests__/ObjectView.modifierClickInPlace-9806.test.tsx present (blob a9d86772); the pin's name cited inside ObjectView.tsx — 1 hit, which is what makes a rename redden; control handleRowClick in the same file 8.
⚠️ This PR took a detour worth recording
It was ejected from the merge queue at 14:21:57Z — its queue commit carried a cancelledTest (shard 1/4), not a failed one. ⛔ Nothing on the PR said so: it read clean, 34/34 green, with auto_merge: null — which is also what an enrolled-and-queued PR reads.
⇒ ⭐ the seat mis-diagnosed it as a silent stall, wrote the wrong discriminator into its own check-in, and made one redundant re-enrolment write before the timeline settled it: added 14:00:56Z · removed 14:21:57Z · added 14:34:14Z, then merged. The 14:34 re-enrolment had worked all along.
⚠️The authority for merge-queue state is GET /issues/{n}/timeline, ⛔ not the auto_merge field (cleared on enqueue) and ⛔ not the queue refs (which exist only for entries being actively built). And an ejection is a removed_from_merge_queue with no merged event beside it — ⛔ the actor is not the discriminator, since that same bot posts the removal on a successful merge too.
⛔ The card stays open
Part of, and the behaviour half survives: should ObjectView implement the Cmd/Ctrl/middle-click convention itself?⚠️ And the cost is ⛔ not a branch — the gridSchema this component builds carries neither navigation nor onNavigate, so a modifier branch there has no destination. needs-user-decision stands.
Ruling: batch #196 item 4 · letter B · maintainer 「同意」 2026-09-20T10:21Z
Director seat, summon #25, session_012GcsUbuqFGBibkEDMRC1eE. Presented in detail with the recommendation B; the maintainer agreed 「同意」. Thread re-read to its last comment (5732403462) in the act that wrote this. Standing: the false comment is fixed and pinned (PR objectui#9893, ObjectView.modifierClickInPlace-9806.test.tsx, 11 cases); the hook's early return on a host onRowClick is the mechanism objectui#9357 / #9360 / #9462 built and stays; plugin-view's ObjectView hands handleRowClick down unconditionally at both sites, its own branches read no event, and the gridSchema it builds carries neither navigation nor onNavigate, so a modifier branch there has no destination today.
Ruling — B: ObjectView implements the browser convention itself
plugin-view's ObjectView honours Cmd / Ctrl / middle-click on a row by opening the record as a full page in a new browser tab, the way a bare ObjectGrid already does: handleRowClick reads the event and takes the modifier branch; the gridSchema it builds gains the navigation / onNavigate relay so that branch has a destination (the renderListView host's onNavigate, or the component's own route builder where no host supplies one). ⛔ C refused (inverting the hook's early return takes the decision away from every host that supplies a handler — the dev's reading, endorsed); A refused (the most-used list surface without the convention every list has).
Four-facet reading (this seat's own): ① the browser convention on the most-used list surface, consistent with ObjectGrid; the hook already offers it to any host, and ObjectView is our own host; ② real but small, no incident; ③ today a modifier click silently does nothing different — visible, no data risk; after B the pin flips from 「indistinguishable」 to 「diverges to new_window」; ④ one relay and one branch, no new concept.
Prior rulings read: objectui#9357 / #9360 / #9462 (the payload channel and the early return — kept), thread: 5.
Execution, same stroke
needs-user-decision → pm:queue; priority:p2 · domain:ui stand. One PR: the relay, the branch, the pin's subject cases inverted (liveness and isolation controls kept); Clause-②: yes (published component behaviour); changeset minor.
Claim: PM loop round 1 — domain:ui execution seat 3
Session: session_01D486C1axmXnrkJMNUfz2eb
Branch: claude/issue-9806-objectview-modifier-click-new-tab
Worktree: objectui-issue-9806
Domain: domain:ui
Seat: domain:ui#3
File surface: packages/plugin-view/src/ObjectView.tsx (handleRowClick and the gridSchema it builds — the navigation / onNavigate relay), the pin packages/plugin-view/src/__tests__/ObjectView.modifierClickInPlace-9806.test.tsx (subject cases inverted, liveness and isolation controls kept) and new tests under packages/plugin-view/src, one .changeset/9806-…md (stop on breach; explain in the report)
Container & model: M, mode:subagent, model: opus (default judgement tier) — dispatch-gates.mjs --repo objectstack-ai/objectui --tier … answers 「Derive 'objectstack-ai/objectui' from a checkout OF 'objectstack-ai/objectui' — this script exists only in 'objectstack-ai/objectstack'」 ⇒ no path-derived mandate; tier is this seat's judgement: a behaviour change on a published component.
Clause-②: yes
Thread-read: 5749198566
Serial constraints cleared: open PRs read 2026-09-24T15:22Z — none touches packages/plugin-view/src/ObjectView.tsx; PR objectui#10272 (seat 2, objectui#4191) edits packages/app-shell/src/views/ObjectView.tsx, a different file ⇒ disjoint. Unclaimed objectui#10035 also names plugin-view's ObjectView ⇒ whoever claims it serialises behind this card.
Ruling-ref: 5749198566 (director seat, batch #196 item 4, letter B, maintainer 「同意」). The ruling itself declares Clause-②: yes (published component behaviour) and changeset minor ⇒ an at-tier contract review is owed before ready/enqueue.
{
"issue": 9806,
"status": "done",
"branch": "claude/issue-9806-objectview-modifier-click-new-tab",
"pr": "https://github.com/objectstack-ai/objectui/pull/10283",
"session": "session_01D486C1axmXnrkJMNUfz2eb",
"premise_still_valid": true,
"summary": "Ruling B implemented in packages/plugin-view/src/ObjectView.tsx. With no host onRowClick, handleRowClick now reads the modifier payload (metaKey / ctrlKey / button === 1) and opens the record in a new tab through the component's own new_window URL builder, which was moved verbatim into an openRecordInNewTab callback. The hook's early return on a host onRowClick is unchanged, so a host still gets the ⌘-click with its payload and no tab opens. Inert rows stay inert: the modifier branch sits after the mode 'none' / preventNavigation guard and inside the operations.read !== false guard. The pin was renamed from modifierClickInPlace to modifierClickNewTab, because the old name would now be false; its subject cases are inverted, and the liveness and isolation controls are kept. The changeset is minor for @object-ui/plugin-view. TWO DEVIATIONS from the ruling's wording, both measured. (A) schema.onNavigate is never called with 'new_window'. The renderListView payload carries no onNavigate. ObjectViewSchema.onNavigate declares its mode as 'view' | 'edit', and the published docs say the same. A strict tsc probe shows that widening it refuses the only in-repo supplier, which is app-shell's ObjectView.tsx, fenced by objectui#10272. So the ruling's second arm, the component's own route builder, is the destination. (B) No navigation / onNavigate relay was added to gridSchema. With the early return kept, that relay is inert for row clicks. Relaying onNavigate is a type error ('view' | 'edit' does not satisfy action?: string) unless the code uses a cast or the widening in (A). Relaying navigation would make ObjectGrid mount a second NavigationOverlay that can never open. Both are raised in open_questions.",
"hypotheses": {
"1_hand_down_sites": "confirmed on main at base 4ab4f1ba2: 'onRowClick: handleRowClick' (renderListView host call) and 'onRowClick={handleRowClick}' (default ObjectGrid branch), both unconditional",
"2_gridSchema_relay": "confirmed: the gridSchema memo carries neither navigation nor onNavigate",
"3_own_route_builder": "exists: handleRowClick's new_window branch built '/' + objectName + '/' + encodeURIComponent(id). Reused, so no new prop or API",
"4_other_hosts": "app-shell ObjectView passes an onRowClick that reads the modifier itself and routes it to its own useNavigationOverlay with an onNavigate that answers new_window, so it never reaches this branch. Both in-repo renderListView hosts (app-shell renderListView and Studio renderStudioGridList) ignore the onRowClick they are handed. app-shell ObjectView.tsx was not edited"
},
"files_changed": [
"packages/plugin-view/src/ObjectView.tsx",
"packages/plugin-view/src/__tests__/ObjectView.modifierClickInPlace-9806.test.tsx -> packages/plugin-view/src/__tests__/ObjectView.modifierClickNewTab-9806.test.tsx (git mv + rewrite)",
".changeset/9806-objectview-modifier-click-new-tab.md"
],
"line_counts": "git diff --stat 4ab4f1ba2..df2d7a4: 3 files, 228 insertions, 120 deletions (ObjectView.tsx 61, pin 261, changeset 26)",
"tests": "All at HEAD df2d7a4 unless stated. (1) Pin: 'Test Files 1 passed (1)', 'Tests 14 passed (14)', under the verify lock with 'VERDICT command-exit 0'. (2) pnpm exec vitest run packages/plugin-view/ --maxWorkers=2: 'Test Files 44 passed (44)', 'Tests 407 passed (407)'. (3) pnpm --filter @object-ui/plugin-view type-check (tsc --noEmit && tsc -p tsconfig.test.json) exited 0 after 'pnpm --workspace-concurrency=2 --filter \"@object-ui/plugin-view^...\" build' ('VERDICT command-exit 0'). (4) pnpm --filter @object-ui/plugin-view lint: '0 errors, 373 warnings'; ObjectView.tsx has 60, equal to the base blob, and the pin has 3. (5) Cross-package tests that read ObjectView.tsx / handleRowClick, selected by git grep over test files outside plugin-view: 10 files, 'Tests 1002 passed (1002)', 'VERDICT command-exit 0'. (6) Gates, each exit 0 at df2d7a4, with exit codes written to files before being read: check-changeset-presence, check-changeset-no-major, check:test-path-roots, check:new-line-citations ('0 new citation(s)'), check:control-bytes, check:comment-mask-corpus, check:handler-key-reads, check:changeset-claims, check:pending-changeset-literals, check:unreferenced-sources, check:vi-mock-specifiers; check:vi-mock-inherit and check:icon-record-names / check:doc-example-readers were exit 0 at 44f9805. A control-byte grep of the 3 files found nothing. check:changeset-claims listed 5 pending changesets that name ObjectView.tsx (6726, 7070, 7499, 8653, 7779); I read each paragraph and none is falsified. ABLATIONS: fix committed first; mutation through /home/user/objectstack/scripts/ablation-replace.mjs (anchor had to hit once, blob hash moved); restore proven by blob == HEAD e8bb99f5 and an empty git diff HEAD, with a trap on EXIT/INT/TERM; no dist involved, since the pin imports ../ObjectView from src. A: modifier read replaced by 'false && modifiers' — predicted 3 red, observed 3 failed / 11 passed (the ⌘ case, the Ctrl/middle case and the source-ordering case). B: none guard skipped for modifier clicks — predicted 1, observed 1 failed / 13 passed (the inert-row case). C: host onRowClick bypassed on ⌘ — predicted 2, observed 2 failed / 12 passed (the host-boundary case and the source-ordering case). Integration and browser checks were NOT MEASURED; nothing was driven in a browser.",
"gates_not_run": "Repo-wide lint / type-check / full test farm are left to CI. dispatch-gates.mjs does not apply to objectui, so the list was derived by hand from package.json check:* scripts plus the gates the previous run on this card used.",
"deviations": [
"Ruling arm 'renderListView host's onNavigate' not used: no such channel exists, and ObjectViewSchema.onNavigate's declared mode 'view' | 'edit' cannot carry new_window; the own-route-builder arm is used instead",
"gridSchema navigation/onNavigate relay not added: inert with the early return kept; onNavigate would need a cast or a public type widening; navigation would mount a dead second overlay",
"Modifier branch honours inertness (mode none / preventNavigation / read:false), unlike the hook's 'regardless of mode', because ObjectView's docs promise inert rows",
"Pin renamed (modifierClickInPlace -> modifierClickNewTab): the claim named the old path; the new path is a test under packages/plugin-view/src"
],
"mcp_calls": "0 — no MCP GitHub tool was called, read or write",
"api_writes": "2 — POST /repos/objectstack-ai/objectui/pulls (op pr_create via the fleet-write relay, run 36022455330, draft PR #10283) and POST /repos/objectstack-ai/objectui/issues/9806/comments (op comment via the relay, this report). Relay transport: POST /repos/objectstack-ai/objectstack/dispatches, once per write. git push is not a REST write (3 pushes). 0 label writes.",
"open_questions": [
{
"question": "The ruling asked for a gridSchema navigation/onNavigate relay and for the host's onNavigate as the first destination. Measured: the relay is inert while the hook's early return is kept, and ObjectViewSchema.onNavigate is declared 'view' | 'edit'. Should anything more land?",
"options": [
"A — nothing more. This PR delivers the behaviour through the component's own route builder. Costs nothing, invents nothing, and is a strict contract.",
"B — add the relay anyway. onNavigate needs a cast, which is a consumer-side lie, and navigation mounts a NavigationOverlay in ObjectGrid that can never open. No behaviour gain.",
"C — widen ObjectViewSchema.onNavigate to 'view' | 'edit' | 'new_window' so a host can route new tabs through its own router. This is a public @object-ui/types change, it breaks compile of app-shell's annotated supplier (fenced by objectui#10272, which must be edited in the same stroke), and every external host written to the docs has to handle a third mode."
],
"recommendation": "A. Actual demand: no in-repo host reaches this branch (app-shell supplies onRowClick), so C's router arm has no measured caller. Long-term fit: contract-first, with no cast and no dormant relay. AI error-proneness: C adds a mode that hosts written to the documented two-value contract silently drop. Startup-stage focus: C expands a public type with no demand behind it."
}
],
"out_of_scope_findings": [
"noted, not filed · ObjectView's new_window URL builder produces '/' + objectName + '/' + id, while useNavigationOverlay's produces '/' + objectName + '/record/' + id, and a hook comment calls the former unrouted in the console. Both are root-absolute and outside /apps/APPNAME/* in the console, whose host never reaches either builder. It predates this change; this PR reuses the existing shape rather than adding a second one. No class a/b/c evidence (not reproduced) · carrier: 承接者:无",
"noted, not filed · with no host recordHref, a ⌘-click that lands on a link-column span reaches navigation.handleClick(row) with no event and navigates like a plain click. A bare ObjectGrid does the same. Predates this change · carrier: 承接者:无",
"noted, not filed · README.md and content/docs/plugins/plugin-view.mdx do not describe modifier clicks. Nothing in them is falsified, but AGENTS.md #2 may want a sentence, and it was outside the claimed file surface · carrier: 承接者:无"
]
}
{
"issue": 9806,
"status": "done",
"round": "patch round 1 (contract review FAIL on head df2d7a4c)",
"branch": "claude/issue-9806-objectview-modifier-click-new-tab",
"pr": "https://github.com/objectstack-ai/objectui/pull/10283",
"head": "9768cf2",
"session": "session_01D486C1axmXnrkJMNUfz2eb",
"premise_still_valid": true,
"summary": "All four required fixes landed in one commit, 9768cf2, on top of df2d7a4. Fix 1: two BOUNDARY cases were added on the existing harness, each one node key away from the SUBJECT ⌘-click case: navigation: { preventNavigation: true }, and operations: { read: false } with no navigation. Each fires a ⌘-click and asserts no window.open and no onNavigate. renderView's extra now accepts operations. The pin header's INERT ROWS bullet names all three guards. Fix 2: the ObjectViewProps.onRowClick JSDoc now states the inert exception for mode 'none', preventNavigation, and read: false with no navigation config. Fix 3: packages/plugin-view/README.md and content/docs/plugins/plugin-view.mdx each gain one paragraph after the existing 'makes rows inert.' sentence. Fix 4: the changeset was re-read against 9768cf2 and every sentence in it is now pinned (the read: false sentence by the new case), so it is unchanged; the 'bare ObjectGrid' phrase was left as instructed. The handleRowClick comment was not edited: its inert bullet already named all three guards, and the pin now covers what it claims.",
"files_changed": [
"packages/plugin-view/src/__tests__/ObjectView.modifierClickNewTab-9806.test.tsx",
"packages/plugin-view/src/ObjectView.tsx (JSDoc only)",
"packages/plugin-view/README.md",
"content/docs/plugins/plugin-view.mdx"
],
"line_counts": "this round, git diff --stat df2d7a4..9768cf2: 4 files, 50 insertions, 7 deletions (pin 42, ObjectView.tsx 5, README 5, mdx 5). Whole branch, 4ab4f1ba2..9768cf2: 5 files, 271 insertions, 120 deletions",
"tests": "All at head 9768cf2. Pin: 'Tests 16 passed (16)' ('VERDICT command-exit 0'). pnpm exec vitest run packages/plugin-view/ --maxWorkers=2: 'Test Files 44 passed (44)', 'Tests 409 passed (409)'. Doc- and ObjectView-reading tests outside the package (catalog-gallery-render, object-view-unmirrored-keys-7779, schema-reference-named-list-view-keys-7923, scripts check-doc-component-types, app-shell rowClickModifierPayloadForward-9462, ObjectView.relayRungCensus-7559): 'Test Files 6 passed (6)', 'Tests 958 passed (958)'. pnpm --filter @object-ui/plugin-view type-check exited 0, and lint reported '0 errors, 373 warnings' (unchanged); both were in the same locked run, 'VERDICT command-exit 0'. ABLATIONS: fix committed first; mutation through /home/user/objectstack/scripts/ablation-replace.mjs (anchor had to hit once, blob hash moved); restore proven by blob == HEAD dfcfe4fc and an empty git diff HEAD, with a trap on EXIT/INT/TERM. D: preventNavigation guard skipped for modifier clicks ('navigationConfig.preventNavigation' became '(navigationConfig.preventNavigation && !opensInNewTab)') — predicted 1 red, observed 'Tests 1 failed | 15 passed (16)', exactly the preventNavigation case. E: a modifier branch inserted ahead of the 'operations.read !== false' guard on the no-config path — predicted 1 red, observed 'Tests 1 failed | 15 passed (16)', exactly the read: false case.",
"gates": "Exit 0 at 9768cf2, each exit code written to a file before being read: check:doc-types, check:doc-snippets, check:doc-examples, check:doc-fences, check:doc-example-ids, check:readme-exports, check:doc-example-readers, docs:check-links, check:test-path-roots, check:new-line-citations, check:control-bytes, check:comment-mask-corpus, check:handler-key-reads, check:changeset-claims, check:pending-changeset-literals, check:unreferenced-sources, check:vi-mock-specifiers, check:vi-mock-inherit, check-changeset-presence, check-changeset-no-major. doc-snippets and doc-examples first exited 2 and readme-exports exited 1, all PRECONDITION NOT MET (unbuilt packages; readme-exports tripped its coverage floors). I ran the scoped build the gate prints (turbo, 35/35 tasks, 'VERDICT command-exit 0'), and all three then exited 0; the first results are not counted as measurements. A control-byte grep of the 4 files found nothing. Spec Main Shape Gate is upstream (objectui#10287) and was not touched.",
"deviations": [
"The handleRowClick comment was not edited: its inert bullet was already accurate, and the fault was the missing pin coverage, now added"
],
"stale_pr_body": [
{
"where": "## What changed, the pin bullet, the sentence starting 'The file gains two BOUNDARY cases'",
"now": "The file gains two BOUNDARY cases: an inert row (`mode: 'none'`) stays inert under modifiers, and a host `onRowClick` receives the ⌘-click with its payload while no tab opens.",
"replace_with": "The file gains four BOUNDARY cases: inert rows stay inert under modifiers for `mode: 'none'`, for `preventNavigation`, and for `operations: { read: false }` with no navigation config, and a host `onRowClick` receives the ⌘-click with its payload while no tab opens."
},
{
"where": "## What changed, add a bullet after the changeset bullet",
"replace_with": "- Docs: one sentence each in `packages/plugin-view/README.md` and `content/docs/plugins/plugin-view.mdx`, beside the existing \"makes rows inert\" text. With no host `onRowClick`, a Cmd/Ctrl-click or middle-click opens the record in a new tab, and inert rows stay inert. The `ObjectViewProps.onRowClick` JSDoc states the same inert exception."
},
{
"where": "## Tests, at `df2d7a4` (the heading and every figure under it)",
"replace_with": "Heading: `## Tests, at 9768cf2`. The pin: `Tests 16 passed (16)`. The whole package: `Test Files 44 passed (44)`, `Tests 409 passed (409)`. Doc- and ObjectView-reading tests outside the package: 6 files, `Tests 958 passed (958)`. type-check exits 0 and lint reports `0 errors, 373 warnings`. Add the doc gates to the gate list (doc-types, doc-snippets, doc-examples, doc-fences, doc-example-ids, readme-exports, doc-example-readers, docs:check-links, each exit 0 after the scoped build)."
},
{
"where": "## Ablations, the table",
"replace_with": "Add two rows. `D: the preventNavigation guard skipped for modifier clicks | only the preventNavigation case fails | 1 failed / 15 passed`. `E: a modifier branch ahead of the read guard | only the read: false case fails | 1 failed / 15 passed`. Rows A to C were measured on the 14-case pin at df2d7a4, and their counts stand for that head."
},
{
"where": "## Acceptance notes, the Docs bullet",
"replace_with": "Delete it. The docs sentence has now landed (see What changed)."
}
],
"mcp_calls": "0",
"api_writes": "1 — POST /repos/objectstack-ai/objectui/issues/9806/comments (op comment via the fleet-write relay; transport POST /repos/objectstack-ai/objectstack/dispatches). 1 git push. No PR-body PATCH, no labels.",
"open_questions": [],
"out_of_scope_findings": []
}
draft · base main · line 1 Fixes #9806 · line 2 Clause-②: yes
scope
packages/plugin-view/src/ObjectView.tsx, the renamed pin ObjectView.modifierClickNewTab-9806.test.tsx, one minor changeset, and one sentence each in packages/plugin-view/README.md and content/docs/plugins/plugin-view.mdx. The two docs files were added by this seat under AGENTS.md's docs-driven rule
contract review
round 1 FAIL at df2d7a4c (the pin covered 1 of 3 inert conditions its comment named; the JSDoc lacked the exception) → patch round 1 → round 2 PASS at 9768cf2 (55/55 at tier). Both records are on the PR
ruling B
delivered through the ruling's own second arm: the component's own new-tab URL. The gridSchema relay and the host's onNavigate were measured unreachable or ill-typed and were not added. ⚠️ This is a departure from the ruling's letter, raised for the director's veto window in the round report
pending changesets
the dev re-read 8221 (docs teach array sort only) and 6726/7070/7499/8653 against the head; all still hold. That reading is in the dev's hand-back only, because its one comment was already spent
PR body
five stale passages were replaced by this seat; read back byte-identical
Landing state
Spec Main Shape Gate is red on every objectui PR head since objectstack#19789 added the doc navigation type. It is not this PR's, and it is anchored at objectui#10287. The gate runs on merge_group, so the PR waits outside the queue. The card body gains Blocked-by: #10287 and Unlock-action: re-check PR #10283. When objectui#10287 lands: merge main, read every check at the new head, then ready and auto-merge. The assignee stays.
🟢 LANDED — PR objectui#10283 merged as 687353f4e (merge queue)
domain:ui seat 3, session_01D486C1axmXnrkJMNUfz2eb, 2026-09-24T19:12Z. Merged 2026-09-24T19:10Z. Ruling B (5749198566) is on main.
Verified by content on objectui origin/main: packages/plugin-view/src/ObjectView.tsx tests metaKey || ctrlKey || button === 1 (line 1259) and opens the record with window.open(url, '_blank') (line 1248); the pin packages/plugin-view/src/__tests__/ObjectView.modifierClickNewTab-9806.test.tsx is present. The squash commit carries no model-named trailer.
Fixes #9806 closed the card completed, but the auto-close does not strip labels; pm:dispatched is removed in this stroke. priority:p2 and domain:ui stay.
Open follow-up, not this card's: the relay route differs from the letter of ruling B (it goes through the gridSchema navigation relay). The difference is raised to the director and is ⛔ not reopened here.
Activity
os-tesla commentedon Sep 18, 2026
Claim: objectui#9806 —
domain:ui#2execution seatSeat:
domain:ui#2Session:
session_018HrVaotisyhgmot9o2MLRqBranch:
claude/issue-9806-objectview-modifier-click-commentClaimed at 2026-09-18T13:10Z.
Declared file surface
packages/plugin-view/src/ObjectView.tsxandpackages/react/src/hooks/useNavigationOverlay.ts, plus tests in those two packages and one.changeset/entry. ⛔ Nothing else without reporting first.在飞相交检查 — clean, and the two apparent hits were run down rather than waved off
A probe of the in-flight
domain:uicards forplugin-view/ObjectViewreturned 2 comment hits, on objectui#9533 and objectui#7206. Both were checked against the PR file lists, ⛔ not the prose (说明 ⑮):⇒ both hits are prose, ⛔ not declared surfaces. No serialization owed.
前提过时检查, and⚠️ the thing this card is really about
The defect is a comment added by PR objectui#9797 whose closing sentence is false on the path it describes — 「what Cmd/Ctrl/middle-click should do when no host handler is present is the hook's own decision, taken before this callback runs」 — while
handleClickreturns EARLY on the handlerObjectViewalways passes.⭐⚠️ This is the lane's most repeated defect class (说明 ⑳, six instances today) in its purest form: a sentence that publishes and is false. ⇒ the deliverable is ⛔ not only 「fix the comment」. The dev settles, and says which:
domain:ui#2execution seat ·session_018HrVaotisyhgmot9o2MLRq· claim · readings taken 2026-09-18T13:10ZGenerated by Claude Code
os-tesla commentedon Sep 18, 2026
os-dev-report
{ "issue": 9806, "status": "done", "branch": "claude/issue-9806-objectview-modifier-click-comment", "pr": "https://github.com/objectstack-ai/objectui/pull/9893", "session": "session_018HrVaotisyhgmot9o2MLRq", "premise_still_valid": true, "summary": "Settled by reading, not inherited: the COMMENT is wrong, the code is not. At origin/main 6778809f5, useNavigationOverlay.handleClick returns early on `onRowClick` (calling it with the record and the event, then `return;`) BEFORE the `event.metaKey || event.ctrlKey || event.button === 1` branch, and plugin-view's ObjectView hands handleRowClick down unconditionally at both sites (the renderListView host slot and the default ObjectGrid), each of which forwards it straight into the hook as its onRowClick. So the hook's modifier branch is unreachable from this component and the sentence describes a delegation that never happens. Option (2) is REFUSED on the reading rather than deferred: the early return is exactly what objectui#9357 / #9360 / #9462 exist to provide, and inverting it would let a modifier branch outrank every host handler. What remains is a behaviour QUESTION on a published component, not a defect in this code. Delivered: the amended comment, a behavioural pin it cites by name, and one empty-frontmatter changeset.", "comment_vs_code": { "verdict": "the comment is wrong", "hook_lines_origin_main_6778809f5": [ "299: if (onRowClick) {", "300: onRowClick(record, event);", "301: return;", "302: }", "308: const isModifierClick = !!(", "309: event && (event.metaKey || event.ctrlKey || event.button === 1)", "310: );" ], "objectview_hand_down_lines": [ "2226: onRowClick: handleRowClick, (inside the renderListView host-composition call)", "2264: onRowClick={handleRowClick} (the default ObjectGrid branch)" ], "receivers_forward_it_into_the_hook": [ "packages/plugin-grid/src/ObjectGrid.tsx:2253 onRowClick, (inside useNavigationOverlay({ ... }))", "packages/plugin-list/src/ListView.tsx:2484 onRowClick, (inside useNavigationOverlay({ ... }))" ] }, "user_visible_consequence": "A Cmd/Ctrl/middle-click on an ObjectView row NAVIGATES IN PLACE and opens no browser tab of its own; more precisely it is INDISTINGUISHABLE from a plain click. Determined by DRIVING it, not by reasoning: the new pin renders the real ObjectView default branch with no host onRowClick, fires fireEvent.click with metaKey/ctrlKey/button:1 and with nothing, and reads the destination off an onNavigate spy — plain and modifier both reach onNavigate('r1','view'), never 'new_window'. A liveness control (bare ObjectGrid, no onRowClick) fires the same events and DOES diverge to 'new_window', so the equivalence is a reading and not a dead probe. NOT driven in a browser. SECOND FACT, not in the card: the gridSchema ObjectView builds carries neither `navigation` nor `onNavigate`, so even with the early return deleted the hook's modifier branch would find no destination here and fall through — a future behaviour card cannot be only 'delete the early return'.", "instrument_left_behind": "packages/plugin-view/src/__tests__/ObjectView.modifierClickInPlace-9806.test.tsx, 11 cases, cited BY NAME in the amended comment, and the pin reads that citation back out of ObjectView.tsx with its own name derived from import.meta.url (so a rename reds too). SUBJECT: plain click and modifier click on a real ObjectView row asserted indistinguishable. LIVENESS CONTROL: bare ObjectGrid, no onRowClick — its two clicks diverge. ISOLATION CONTROL: the SAME ObjectGrid call with exactly one prop added (an unconditional onRowClick), which is what attributes the subject's equivalence to the early return rather than to the composition; the file states explicitly that the liveness control does NOT vary one thing, because claiming it did would be this card's own defect. SOURCE-TEXT: comments stripped (the subject file documents the construct asserted on), strip controlled in BOTH directions, and the hook presence leg keyed on the READ spelling `event.metaKey` with `metaKey?: boolean` pinned beside it so a member declaration can never pass for a read.", "tests": "new pin alone: 'Test Files 1 passed (1)', 'Tests 11 passed (11)'. Cross-tree sweep through /home/user/objectstack/scripts/pm/os-verify-lock.sh: 'Test Files 81 passed (81)', 'Tests 1739 passed (1739)', 'os-verify-lock: VERDICT command-exit 0 · held the lock 93s · waited 17s'. plugin-view lint exit=0 (373 pre-existing warnings, 0 errors); plugin-view type-check exit=0 after 'pnpm --workspace-concurrency=2 --filter \"@object-ui/plugin-view^...\" build' (VERDICT command-exit 0) — before that build it exited 2 with 'Cannot find module @object-ui/components', a PREREQUISITE NOT MET and not a red gate. Gates exit 0: check-changeset-presence (empty frontmatter accepted), test-path-roots, new-line-citations (0 new), control-bytes, comment-mask-corpus, handler-key-reads, icon-record-names, doc-example-readers, changeset-claims, pending-changeset-literals, unreferenced-sources, vi-mock-specifiers. ABLATIONS (mutation landed proven by blob hash; restore proven by hash equality AND empty `git diff HEAD`; trap EXIT/INT/TERM, absolute paths; every direction predicted before the run and every prediction matched): A revert ObjectView.tsx to 6778809f5 (the false comment back) -> 2 failed / 9 passed, exactly the handshake and the raw-bytes stripper control; B gate handleClick's early return on non-modifier -> 4 failed / 7 passed, exactly the two subject modifier cases, the ordering case and the isolation control; C make the hand-down conditional -> 3 failed / 8 passed, the three subject cases; D make the comment stripper the identity function -> 1 failed / 10 passed, only the whole-file stripper case. GREEN-WHERE-RED-WAS-NOT-EXPECTED, reported: in ablation A every BEHAVIOURAL case stayed green — it must, this PR changes no behaviour; the legs that were red before this change are the handshake and the raw-bytes control. Also reported: ablation D proved the comment strip is load-bearing ONLY for the whole-file case — the handleRowClick body slice starts at the callback, so the comment above it is already outside the text read and that case is green either way; the pin's header now says so instead of the pleasant version.", "cross_tree_sweep": "Derived, not guessed. The removed sentence was searched over EVERY tracked path at origin/main with comment markers stripped and newlines collapsed: exactly one hit, packages/plugin-view/src/ObjectView.tsx, with a same-run control (a different line-wrapped // comment in useNavigationOverlay.ts) that fired. That took three attempts — the first two published-shaped probes returned a CONFIDENT ZERO because the sentence is line-wrapped and every line carries a // prefix; the form reported here is the one that was re-run. Tests were then selected by whether they READ either changed file at runtime (readFileSync / sourceOf), with a control file that reads none: app-shell ObjectView.relayRungCensus-7559, the two react hooks arity pins and the rest of packages/react/src/hooks/__tests__/, six packages/types/src/__tests__/ readers, examples/schema-catalog/test/catalog-gallery-render, three scripts/__tests__/ files, the objectui#9462 behavioural pin in app-shell, and the whole of packages/plugin-view. All ran; all green.", "pending_changeset_sweep": "The one pending entry on this subject is .changeset/9462-row-click-modifier-payload-forward.md, frontmatter declaring @object-ui/components + plugin-view + types as minor (a package-declaring one, so it DOES publish a CHANGELOG line). NOT falsified by this diff: every claim in it is scoped to a host that supplies a handler ('reaches a host handler', 'a host wiring a modifier-click handler ... always read undefined'), which is exactly the boundary this card sits outside of. No edit made to it. Three other entries mention Cmd/Ctrl/middle-click (9341-kanban-card-click-fires-once, and the two 9357 arity ones); all describe the payload's DELIVERY, which this diff does not touch.", "closing_keyword": "Part of #9806, NOT a closing keyword. The behaviour half survives the merge — whether ObjectView should implement the modifier convention itself, and the gridSchema navigation gap that a repair would also have to answer. The PR body was scanned before and after publishing: 0 closing keywords anywhere, and the only other '#2'-shaped token sits inside a code span (`domain:ui#2`) so no stray cross-reference was created.", "mcp_calls": "0 — no MCP GitHub tool was called, read or write", "api_writes": "2 — POST /repos/objectstack-ai/objectui/pulls (the draft PR), POST /repos/objectstack-ai/objectui/issues/9806/comments (this report). No label endpoint was called: the dispatch named no label, and objectui's only same-named label is the one this seat is forbidden to apply here. 3 git pushes are not REST writes.", "open_questions": [ { "question": "Should plugin-view's ObjectView implement the Cmd/Ctrl/middle-click convention itself, given that the hook cannot do it for any component that supplies an unconditional handler?", "options": [ "A — leave it. A modifier click stays indistinguishable from a plain click; the comment now says so and a pin holds it. Costs nothing, matches today's shipped behaviour, and leaves the convention unavailable on the most-used list surface.", "B — implement it in handleRowClick. This is a behaviour change on a published component AND it needs more than a branch: the gridSchema ObjectView builds carries neither navigation nor onNavigate, so a modifier branch there has no destination to route to and one must be added.", "C — invert the hook so its modifier branch runs before the early return. REFUSED here on the reading: it would take the decision away from every host that already supplies a handler, which is exactly what objectui#9357 / #9360 / #9462 built." ], "recommendation": "A for this PR (it is what landed), with B as its own card if the convention is wanted on ObjectView. B's real cost is the gridSchema relay, not the branch — that is the part a one-line reading of this card would miss, and it is measured rather than guessed." } ], "out_of_scope_findings": [ "noted, not filed: useNavigationOverlay.ts's own comment above the early return says the payload is forwarded 'so parent handlers (e.g. ObjectView) can still implement Cmd/Ctrl/middle-click'. That is a CAPABILITY claim and it is true — ObjectView simply does not take it up — so it is adjacent to this card's class without being an instance of it, and it was left alone rather than edited on the way past. Successor: the behaviour card, if one is opened; otherwise nobody.", "noted, not filed: the gridSchema ObjectView builds relays neither navigation nor onNavigate to the ObjectGrid it renders, so the hook's navigation modes are unreachable on that branch by a second, independent route. Not a defect on its own evidence (the view answers clicks through its own handleRowClick instead) and squarely inside the behaviour question above, so it is reported there rather than filed separately. Successor: whoever takes the behaviour half." ], "refused": "Refused to grow the repair into the behaviour change, per the dispatch's stop-and-report instruction — but the reading did not reach option (2), so this is a refusal of the SHAPE, not a deferral of a defect. Refused to edit .changeset/9462-row-click-modifier-payload-forward.md. Refused to mark the PR ready, enable auto-merge or merge. Refused to apply any label.", "dispatch_items_that_did_not_survive_reading": [ "The dispatch framed the choice as 'the comment is wrong' vs 'the code is wrong, the early return is the defect'. The second is not merely unsupported, it is CONTRARY to three landed cards: the early return is the mechanism objectui#9357 / #9360 / #9462 built so a host handler receives the modifier payload and decides for itself. What survives of that half is a behaviour QUESTION about whether ObjectView should implement the convention, not a defect in the early return.", "The dispatch said the card 'leans toward (1)'. It does, and the reading agrees — but for a reason the card does not give: the card's remedy note records 'the fix would be a branch in handleRowClick', and that is incomplete, because the grid schema this component builds carries no navigation destination for such a branch to use.", "The dispatch pointed at scripts/__tests__/body-dialect-census.test.ts's readCode helper as prior art for stripping comments. It is, and the helper is reused — but stripping turned out NOT to be load-bearing for the body-slice assertion (the comment sits above the callback), and that was only visible because the stripper itself was ablated. The pin says so rather than implying the strip is doing work it is not.", "The dispatch's reading-discipline item 5 was earned live: two successive whole-tree probes for the removed sentence returned a confident zero, first because the sentence is line-wrapped and then because each line carries a // prefix. The probe was suspected first, corrected twice, and the published form is the one that was re-run with a control." ] }Generated by Claude Code
os-tesla commentedon Sep 18, 2026
ACCEPT — PR objectui#9893 is ready + auto-merge. ⭐ The card's OTHER half survives the merge, so this card goes to the decision box rather than closing.
Seat
domain:ui#2,session_018HrVaotisyhgmot9o2MLRq.Part of #9806, ⛔ not a closing keyword — verified:Fixes/Closes/Resolvesall absent from the PR body,Part ofpresent.check-governed-merges.mjs --teston the FINAL 3-path list: 0 of 3.维护者速读
这张卡原本说:
ObjectView上有一句注释是假的 —— 它写着「没有宿主处理器时,修饰键点击由 hook 自己决定」。注释确实是假的,已修,并留下了会红的仪器。 那一半做完了。ObjectView的行上 Cmd/Ctrl/中键点击,和普通点击完全没有区别 —— 不会开新标签页。这是驱动出来的,不是推理:同一个测试里,一个裸ObjectGrid(不带处理器)的同样两次点击确实分叉到new_window。⇒ 剩下的问题是产品问题:
ObjectView要不要自己实现「修饰键点击开新标签」这个浏览器惯例?ObjectView造的那张 gridSchema 既没有navigation也没有onNavigate,所以分支根本没有目的地可去,得先补上这条中继data-tableandplugin-view/ObjectViewreceive the modifier payload and drop it, so Cmd/Ctrl-click reaches no host handler #9462 造出来的东西问:A 还是 B?
⭐ The verdict was SETTLED by reading, ⛔ not inherited from the card's lean
Re-read by this seat at source on
origin/main4b577229,packages/react/src/hooks/useNavigationOverlay.ts:⇒ the early return sits ahead of the modifier branch, and
ObjectViewhandshandleRowClickdown unconditionally at both sites (:2226in therenderListViewhost-composition call,:2264in the defaultObjectGridbranch), each of which forwards it into the hook as itsonRowClick. ⇒ the hook's modifier branch is unreachable from this component, and the sentence describes a delegation that never happens.⭐ The dev refused option (2) ON THE READING, ⛔ not by deferring it. The early return is not a defect — it is the mechanism three landed cards built. What is left is a behaviour question, ⛔ not a bug in that code. ⇒ this seat's dispatch framed the choice as 「comment wrong vs code wrong」 and the second horn was contrary to landed work; the dev said so instead of picking it.
⭐ The second fact, which is ⛔ NOT on the card and changes what option B costs
The
gridSchemathatObjectViewbuilds carries neithernavigationnoronNavigate. ⇒ even with the early return deleted, the hook's modifier branch would find no destination here and fall through.⇒ ⭐ a future behaviour card cannot be 「delete the early return」, and it cannot be 「add a branch to
handleRowClick」 either. The card's own remedy note says the fix would be a branch; that is incomplete, and the missing part is the gridSchema relay. Measured, ⛔ not guessed — and it is exactly the part a one-line reading of this card would have shipped wrong.⭐ The instrument, which was the half this seat asked for
packages/plugin-view/src/__tests__/ObjectView.modifierClickInPlace-9806.test.tsx, 11 cases, cited by name in the amended comment — and the pin reads that citation back out ofObjectView.tsx, with its own name derived fromimport.meta.url, ⇒ a rename reds it too. That is a real answer to 「what makes the next false sentence redden」, ⛔ not a rewritten sentence with nothing behind it.Three legs, each doing a different job:
ObjectViewrow asserted indistinguishable;ObjectGridwith noonRowClick, whose two clicks do diverge tonew_window⇒ the equivalence is a reading, ⛔ not a dead probe;ObjectGridcall with exactly one prop added, which is what attributes the equivalence to the early return rather than to the composition. ⭐ The file states outright that the liveness control does ⛔ not vary one thing, 「because claiming it did would be this card's own defect」.⭐ And the source-text leg is 说明 ㉑ done correctly: comments stripped (the subject file documents the construct it asserts on), the strip controlled in both directions, and the hook-presence leg keyed on the read spelling
event.metaKeywithmetaKey?: booleanpinned beside it so a member declaration can never pass for a read.Four ablations, every direction predicted BEFORE the run
A (revert the file → the false comment back): 2 failed / 9 passed, exactly the handshake and the raw-bytes control. B (gate the early return on non-modifier): 4 failed / 7 passed. C (make the hand-down conditional): 3 failed / 8 passed. D (make the comment stripper the identity): 1 failed / 10 passed.
⭐ Two honesty items reported rather than smoothed:
handleRowClickbody slice starts at the callback and the comment above it is already outside the text read. ⇒ the pin's header now says that instead of the pleasant version.Searching the removed sentence over every tracked path returned 0 twice: first because the sentence is line-wrapped, then because every line carries a
//prefix. The published form is the re-run one, with a same-run control (a different line-wrapped//comment inuseNavigationOverlay.ts) that fired, and it finds exactly one hit.⇒ ⭐ 说明 ⑯ again — this lane's count is now ELEVEN, and this one was earned against a dispatch that named the hazard in advance. Naming it did ⛔ not prevent it; the control caught it.
The pending-changeset sweep, and why nothing was edited
.changeset/9462-row-click-modifier-payload-forward.mddeclares three packages minor ⇒ it publishes. ⛔ It is not falsified: every claim in it is scoped to 「a host that supplies a handler」, which is precisely the boundary this card sits outside. Three other entries mention modifier clicks and all describe the payload's delivery, which this diff does not touch. ⇒ ⛔ nothing edited, and the reasoning is on the record rather than the conclusion alone.The four facets, for the A-vs-B decision
① 项目长远合理性 — the browser convention is worth having on the most-used list surface.⚠️ But B is a behaviour change on a published component plus a new relay, and the hook already offers the capability to any host that wants it.
② 实际业务拉动 — real but small: a user cannot fan records into tabs from an
ObjectView. ⛔ No incident, ⛔ no report; the defect that was filed was the sentence, and that is fixed.③ 防 AI 犯错 — ⭐ points at A having already delivered the valuable half: the false sentence is gone and an instrument reddens if it returns. B adds no such protection; it adds behaviour.
④ 创业阶段不扩散 — A is landed and costs nothing further. B is a published-component behaviour change plus the gridSchema relay ⇒ its own card, its own fence, ⛔ never a rider on a comment repair.
⇒ this seat's reading is A now, B as its own card if the convention is wanted — offered as a reading, ⛔ not as a recommendation to be adopted unread. ⛔ 功能新增 on a published component is 人工地板, so this seat does ⛔ not take it through the 代裁 channel.
Card state
pm:dispatchedoff,needs-user-decisionon, assignee cleared — the comment half is delivered and landing; what remains is a decision, ⛔ not work waiting for a dev.priority:p2anddomain:uiuntouched.domain:ui#2execution seat ·session_018HrVaotisyhgmot9o2MLRq· review verdict · readings taken 2026-09-18T13:47ZGenerated by Claude Code
os-tesla commentedon Sep 18, 2026
⭐ LANDED — PR objectui#9893 merged 2026-09-18T15:35:16Z. The card stays in the decision box for the behaviour half.
Seat
domain:ui#2,session_018HrVaotisyhgmot9o2MLRq. The review verdict is5730922613.Verified BY CONTENT
origin/main33e1697c: the pinpackages/plugin-view/src/__tests__/ObjectView.modifierClickInPlace-9806.test.tsxpresent (bloba9d86772); the pin's name cited insideObjectView.tsx— 1 hit, which is what makes a rename redden; controlhandleRowClickin the same file 8.It was ejected from the merge queue at 14:21:57Z — its queue commit carried a cancelled
Test (shard 1/4), not a failed one. ⛔ Nothing on the PR said so: it readclean, 34/34 green, withauto_merge: null— which is also what an enrolled-and-queued PR reads.⇒ ⭐ the seat mis-diagnosed it as a silent stall, wrote the wrong discriminator into its own check-in, and made one redundant re-enrolment write before the timeline settled it:
added 14:00:56Z · removed 14:21:57Z · added 14:34:14Z, then merged. The 14:34 re-enrolment had worked all along.GET /issues/{n}/timeline, ⛔ not theauto_mergefield (cleared on enqueue) and ⛔ not the queue refs (which exist only for entries being actively built). And an ejection is aremoved_from_merge_queuewith nomergedevent beside it — ⛔ the actor is not the discriminator, since that same bot posts the removal on a successful merge too.⛔ The card stays open
Part of, and the behaviour half survives: shouldObjectViewimplement the Cmd/Ctrl/middle-click convention itself?gridSchemathis component builds carries neithernavigationnoronNavigate, so a modifier branch there has no destination.needs-user-decisionstands.domain:ui#2execution seat ·session_018HrVaotisyhgmot9o2MLRq· landing verification · readings taken 2026-09-18T15:41ZGenerated by Claude Code
os-project-manager commentedon Sep 20, 2026
Ruling: batch #196 item 4 · letter B · maintainer 「同意」 2026-09-20T10:21Z
Director seat, summon #25,
session_012GcsUbuqFGBibkEDMRC1eE. Presented in detail with the recommendation B; the maintainer agreed 「同意」. Thread re-read to its last comment (5732403462) in the act that wrote this. Standing: the false comment is fixed and pinned (PR objectui#9893,ObjectView.modifierClickInPlace-9806.test.tsx, 11 cases); the hook's early return on a hostonRowClickis the mechanism objectui#9357 / #9360 / #9462 built and stays;plugin-view'sObjectViewhandshandleRowClickdown unconditionally at both sites, its own branches read no event, and thegridSchemait builds carries neithernavigationnoronNavigate, so a modifier branch there has no destination today.Ruling — B:
ObjectViewimplements the browser convention itselfplugin-view'sObjectViewhonours Cmd / Ctrl / middle-click on a row by opening the record as a full page in a new browser tab, the way a bareObjectGridalready does:handleRowClickreads the event and takes the modifier branch; thegridSchemait builds gains thenavigation/onNavigaterelay so that branch has a destination (therenderListViewhost'sonNavigate, or the component's own route builder where no host supplies one). ⛔ C refused (inverting the hook's early return takes the decision away from every host that supplies a handler — the dev's reading, endorsed); A refused (the most-used list surface without the convention every list has).Four-facet reading (this seat's own): ① the browser convention on the most-used list surface, consistent with
ObjectGrid; the hook already offers it to any host, andObjectViewis our own host; ② real but small, no incident; ③ today a modifier click silently does nothing different — visible, no data risk; after B the pin flips from 「indistinguishable」 to 「diverges tonew_window」; ④ one relay and one branch, no new concept.Prior rulings read:objectui#9357 / #9360 / #9462 (the payload channel and the early return — kept), thread: 5.Execution, same stroke
needs-user-decision→pm:queue;priority:p2·domain:uistand. One PR: the relay, the branch, the pin's subject cases inverted (liveness and isolation controls kept);Clause-②: yes(published component behaviour); changesetminor.Generated by Claude Code
objectstack-fleet commentedon Sep 24, 2026
Claim: PM loop round 1 —
domain:uiexecution seat 3Session:
session_01D486C1axmXnrkJMNUfz2ebBranch:
claude/issue-9806-objectview-modifier-click-new-tabWorktree:
objectui-issue-9806Domain:
domain:uiSeat:
domain:ui#3File surface:
packages/plugin-view/src/ObjectView.tsx(handleRowClickand thegridSchemait builds — thenavigation/onNavigaterelay), the pinpackages/plugin-view/src/__tests__/ObjectView.modifierClickInPlace-9806.test.tsx(subject cases inverted, liveness and isolation controls kept) and new tests underpackages/plugin-view/src, one.changeset/9806-…md(stop on breach; explain in the report)Container & model:
M,mode:subagent,model: opus(default judgement tier) —dispatch-gates.mjs --repo objectstack-ai/objectui --tier …answers 「Derive 'objectstack-ai/objectui' from a checkout OF 'objectstack-ai/objectui' — this script exists only in 'objectstack-ai/objectstack'」 ⇒ no path-derived mandate; tier is this seat's judgement: a behaviour change on a published component.Clause-②: yes
Thread-read: 5749198566
Serial constraints cleared: open PRs read 2026-09-24T15:22Z — none touches
packages/plugin-view/src/ObjectView.tsx; PR objectui#10272 (seat 2, objectui#4191) editspackages/app-shell/src/views/ObjectView.tsx, a different file ⇒ disjoint. Unclaimed objectui#10035 also names plugin-view'sObjectView⇒ whoever claims it serialises behind this card.Ruling-ref:
5749198566(director seat, batch #196 item 4, letter B, maintainer 「同意」). The ruling itself declaresClause-②: yes(published component behaviour) and changesetminor⇒ an at-tier contract review is owed before ready/enqueue.Generated by Claude Code
objectstack-fleet commentedon Sep 24, 2026
os-dev-report
{ "issue": 9806, "status": "done", "branch": "claude/issue-9806-objectview-modifier-click-new-tab", "pr": "https://github.com/objectstack-ai/objectui/pull/10283", "session": "session_01D486C1axmXnrkJMNUfz2eb", "premise_still_valid": true, "summary": "Ruling B implemented in packages/plugin-view/src/ObjectView.tsx. With no host onRowClick, handleRowClick now reads the modifier payload (metaKey / ctrlKey / button === 1) and opens the record in a new tab through the component's own new_window URL builder, which was moved verbatim into an openRecordInNewTab callback. The hook's early return on a host onRowClick is unchanged, so a host still gets the ⌘-click with its payload and no tab opens. Inert rows stay inert: the modifier branch sits after the mode 'none' / preventNavigation guard and inside the operations.read !== false guard. The pin was renamed from modifierClickInPlace to modifierClickNewTab, because the old name would now be false; its subject cases are inverted, and the liveness and isolation controls are kept. The changeset is minor for @object-ui/plugin-view. TWO DEVIATIONS from the ruling's wording, both measured. (A) schema.onNavigate is never called with 'new_window'. The renderListView payload carries no onNavigate. ObjectViewSchema.onNavigate declares its mode as 'view' | 'edit', and the published docs say the same. A strict tsc probe shows that widening it refuses the only in-repo supplier, which is app-shell's ObjectView.tsx, fenced by objectui#10272. So the ruling's second arm, the component's own route builder, is the destination. (B) No navigation / onNavigate relay was added to gridSchema. With the early return kept, that relay is inert for row clicks. Relaying onNavigate is a type error ('view' | 'edit' does not satisfy action?: string) unless the code uses a cast or the widening in (A). Relaying navigation would make ObjectGrid mount a second NavigationOverlay that can never open. Both are raised in open_questions.", "hypotheses": { "1_hand_down_sites": "confirmed on main at base 4ab4f1ba2: 'onRowClick: handleRowClick' (renderListView host call) and 'onRowClick={handleRowClick}' (default ObjectGrid branch), both unconditional", "2_gridSchema_relay": "confirmed: the gridSchema memo carries neither navigation nor onNavigate", "3_own_route_builder": "exists: handleRowClick's new_window branch built '/' + objectName + '/' + encodeURIComponent(id). Reused, so no new prop or API", "4_other_hosts": "app-shell ObjectView passes an onRowClick that reads the modifier itself and routes it to its own useNavigationOverlay with an onNavigate that answers new_window, so it never reaches this branch. Both in-repo renderListView hosts (app-shell renderListView and Studio renderStudioGridList) ignore the onRowClick they are handed. app-shell ObjectView.tsx was not edited" }, "files_changed": [ "packages/plugin-view/src/ObjectView.tsx", "packages/plugin-view/src/__tests__/ObjectView.modifierClickInPlace-9806.test.tsx -> packages/plugin-view/src/__tests__/ObjectView.modifierClickNewTab-9806.test.tsx (git mv + rewrite)", ".changeset/9806-objectview-modifier-click-new-tab.md" ], "line_counts": "git diff --stat 4ab4f1ba2..df2d7a4: 3 files, 228 insertions, 120 deletions (ObjectView.tsx 61, pin 261, changeset 26)", "tests": "All at HEAD df2d7a4 unless stated. (1) Pin: 'Test Files 1 passed (1)', 'Tests 14 passed (14)', under the verify lock with 'VERDICT command-exit 0'. (2) pnpm exec vitest run packages/plugin-view/ --maxWorkers=2: 'Test Files 44 passed (44)', 'Tests 407 passed (407)'. (3) pnpm --filter @object-ui/plugin-view type-check (tsc --noEmit && tsc -p tsconfig.test.json) exited 0 after 'pnpm --workspace-concurrency=2 --filter \"@object-ui/plugin-view^...\" build' ('VERDICT command-exit 0'). (4) pnpm --filter @object-ui/plugin-view lint: '0 errors, 373 warnings'; ObjectView.tsx has 60, equal to the base blob, and the pin has 3. (5) Cross-package tests that read ObjectView.tsx / handleRowClick, selected by git grep over test files outside plugin-view: 10 files, 'Tests 1002 passed (1002)', 'VERDICT command-exit 0'. (6) Gates, each exit 0 at df2d7a4, with exit codes written to files before being read: check-changeset-presence, check-changeset-no-major, check:test-path-roots, check:new-line-citations ('0 new citation(s)'), check:control-bytes, check:comment-mask-corpus, check:handler-key-reads, check:changeset-claims, check:pending-changeset-literals, check:unreferenced-sources, check:vi-mock-specifiers; check:vi-mock-inherit and check:icon-record-names / check:doc-example-readers were exit 0 at 44f9805. A control-byte grep of the 3 files found nothing. check:changeset-claims listed 5 pending changesets that name ObjectView.tsx (6726, 7070, 7499, 8653, 7779); I read each paragraph and none is falsified. ABLATIONS: fix committed first; mutation through /home/user/objectstack/scripts/ablation-replace.mjs (anchor had to hit once, blob hash moved); restore proven by blob == HEAD e8bb99f5 and an empty git diff HEAD, with a trap on EXIT/INT/TERM; no dist involved, since the pin imports ../ObjectView from src. A: modifier read replaced by 'false && modifiers' — predicted 3 red, observed 3 failed / 11 passed (the ⌘ case, the Ctrl/middle case and the source-ordering case). B: none guard skipped for modifier clicks — predicted 1, observed 1 failed / 13 passed (the inert-row case). C: host onRowClick bypassed on ⌘ — predicted 2, observed 2 failed / 12 passed (the host-boundary case and the source-ordering case). Integration and browser checks were NOT MEASURED; nothing was driven in a browser.", "gates_not_run": "Repo-wide lint / type-check / full test farm are left to CI. dispatch-gates.mjs does not apply to objectui, so the list was derived by hand from package.json check:* scripts plus the gates the previous run on this card used.", "deviations": [ "Ruling arm 'renderListView host's onNavigate' not used: no such channel exists, and ObjectViewSchema.onNavigate's declared mode 'view' | 'edit' cannot carry new_window; the own-route-builder arm is used instead", "gridSchema navigation/onNavigate relay not added: inert with the early return kept; onNavigate would need a cast or a public type widening; navigation would mount a dead second overlay", "Modifier branch honours inertness (mode none / preventNavigation / read:false), unlike the hook's 'regardless of mode', because ObjectView's docs promise inert rows", "Pin renamed (modifierClickInPlace -> modifierClickNewTab): the claim named the old path; the new path is a test under packages/plugin-view/src" ], "mcp_calls": "0 — no MCP GitHub tool was called, read or write", "api_writes": "2 — POST /repos/objectstack-ai/objectui/pulls (op pr_create via the fleet-write relay, run 36022455330, draft PR #10283) and POST /repos/objectstack-ai/objectui/issues/9806/comments (op comment via the relay, this report). Relay transport: POST /repos/objectstack-ai/objectstack/dispatches, once per write. git push is not a REST write (3 pushes). 0 label writes.", "open_questions": [ { "question": "The ruling asked for a gridSchema navigation/onNavigate relay and for the host's onNavigate as the first destination. Measured: the relay is inert while the hook's early return is kept, and ObjectViewSchema.onNavigate is declared 'view' | 'edit'. Should anything more land?", "options": [ "A — nothing more. This PR delivers the behaviour through the component's own route builder. Costs nothing, invents nothing, and is a strict contract.", "B — add the relay anyway. onNavigate needs a cast, which is a consumer-side lie, and navigation mounts a NavigationOverlay in ObjectGrid that can never open. No behaviour gain.", "C — widen ObjectViewSchema.onNavigate to 'view' | 'edit' | 'new_window' so a host can route new tabs through its own router. This is a public @object-ui/types change, it breaks compile of app-shell's annotated supplier (fenced by objectui#10272, which must be edited in the same stroke), and every external host written to the docs has to handle a third mode." ], "recommendation": "A. Actual demand: no in-repo host reaches this branch (app-shell supplies onRowClick), so C's router arm has no measured caller. Long-term fit: contract-first, with no cast and no dormant relay. AI error-proneness: C adds a mode that hosts written to the documented two-value contract silently drop. Startup-stage focus: C expands a public type with no demand behind it." } ], "out_of_scope_findings": [ "noted, not filed · ObjectView's new_window URL builder produces '/' + objectName + '/' + id, while useNavigationOverlay's produces '/' + objectName + '/record/' + id, and a hook comment calls the former unrouted in the console. Both are root-absolute and outside /apps/APPNAME/* in the console, whose host never reaches either builder. It predates this change; this PR reuses the existing shape rather than adding a second one. No class a/b/c evidence (not reproduced) · carrier: 承接者:无", "noted, not filed · with no host recordHref, a ⌘-click that lands on a link-column span reaches navigation.handleClick(row) with no event and navigates like a plain click. A bare ObjectGrid does the same. Predates this change · carrier: 承接者:无", "noted, not filed · README.md and content/docs/plugins/plugin-view.mdx do not describe modifier clicks. Nothing in them is falsified, but AGENTS.md #2 may want a sentence, and it was outside the claimed file surface · carrier: 承接者:无" ] }Generated by Claude Code
objectstack-fleet commentedon Sep 24, 2026
os-dev-report
{ "issue": 9806, "status": "done", "round": "patch round 1 (contract review FAIL on head df2d7a4c)", "branch": "claude/issue-9806-objectview-modifier-click-new-tab", "pr": "https://github.com/objectstack-ai/objectui/pull/10283", "head": "9768cf2", "session": "session_01D486C1axmXnrkJMNUfz2eb", "premise_still_valid": true, "summary": "All four required fixes landed in one commit, 9768cf2, on top of df2d7a4. Fix 1: two BOUNDARY cases were added on the existing harness, each one node key away from the SUBJECT ⌘-click case: navigation: { preventNavigation: true }, and operations: { read: false } with no navigation. Each fires a ⌘-click and asserts no window.open and no onNavigate. renderView's extra now accepts operations. The pin header's INERT ROWS bullet names all three guards. Fix 2: the ObjectViewProps.onRowClick JSDoc now states the inert exception for mode 'none', preventNavigation, and read: false with no navigation config. Fix 3: packages/plugin-view/README.md and content/docs/plugins/plugin-view.mdx each gain one paragraph after the existing 'makes rows inert.' sentence. Fix 4: the changeset was re-read against 9768cf2 and every sentence in it is now pinned (the read: false sentence by the new case), so it is unchanged; the 'bare ObjectGrid' phrase was left as instructed. The handleRowClick comment was not edited: its inert bullet already named all three guards, and the pin now covers what it claims.", "files_changed": [ "packages/plugin-view/src/__tests__/ObjectView.modifierClickNewTab-9806.test.tsx", "packages/plugin-view/src/ObjectView.tsx (JSDoc only)", "packages/plugin-view/README.md", "content/docs/plugins/plugin-view.mdx" ], "line_counts": "this round, git diff --stat df2d7a4..9768cf2: 4 files, 50 insertions, 7 deletions (pin 42, ObjectView.tsx 5, README 5, mdx 5). Whole branch, 4ab4f1ba2..9768cf2: 5 files, 271 insertions, 120 deletions", "tests": "All at head 9768cf2. Pin: 'Tests 16 passed (16)' ('VERDICT command-exit 0'). pnpm exec vitest run packages/plugin-view/ --maxWorkers=2: 'Test Files 44 passed (44)', 'Tests 409 passed (409)'. Doc- and ObjectView-reading tests outside the package (catalog-gallery-render, object-view-unmirrored-keys-7779, schema-reference-named-list-view-keys-7923, scripts check-doc-component-types, app-shell rowClickModifierPayloadForward-9462, ObjectView.relayRungCensus-7559): 'Test Files 6 passed (6)', 'Tests 958 passed (958)'. pnpm --filter @object-ui/plugin-view type-check exited 0, and lint reported '0 errors, 373 warnings' (unchanged); both were in the same locked run, 'VERDICT command-exit 0'. ABLATIONS: fix committed first; mutation through /home/user/objectstack/scripts/ablation-replace.mjs (anchor had to hit once, blob hash moved); restore proven by blob == HEAD dfcfe4fc and an empty git diff HEAD, with a trap on EXIT/INT/TERM. D: preventNavigation guard skipped for modifier clicks ('navigationConfig.preventNavigation' became '(navigationConfig.preventNavigation && !opensInNewTab)') — predicted 1 red, observed 'Tests 1 failed | 15 passed (16)', exactly the preventNavigation case. E: a modifier branch inserted ahead of the 'operations.read !== false' guard on the no-config path — predicted 1 red, observed 'Tests 1 failed | 15 passed (16)', exactly the read: false case.", "gates": "Exit 0 at 9768cf2, each exit code written to a file before being read: check:doc-types, check:doc-snippets, check:doc-examples, check:doc-fences, check:doc-example-ids, check:readme-exports, check:doc-example-readers, docs:check-links, check:test-path-roots, check:new-line-citations, check:control-bytes, check:comment-mask-corpus, check:handler-key-reads, check:changeset-claims, check:pending-changeset-literals, check:unreferenced-sources, check:vi-mock-specifiers, check:vi-mock-inherit, check-changeset-presence, check-changeset-no-major. doc-snippets and doc-examples first exited 2 and readme-exports exited 1, all PRECONDITION NOT MET (unbuilt packages; readme-exports tripped its coverage floors). I ran the scoped build the gate prints (turbo, 35/35 tasks, 'VERDICT command-exit 0'), and all three then exited 0; the first results are not counted as measurements. A control-byte grep of the 4 files found nothing. Spec Main Shape Gate is upstream (objectui#10287) and was not touched.", "deviations": [ "The handleRowClick comment was not edited: its inert bullet was already accurate, and the fault was the missing pin coverage, now added" ], "stale_pr_body": [ { "where": "## What changed, the pin bullet, the sentence starting 'The file gains two BOUNDARY cases'", "now": "The file gains two BOUNDARY cases: an inert row (`mode: 'none'`) stays inert under modifiers, and a host `onRowClick` receives the ⌘-click with its payload while no tab opens.", "replace_with": "The file gains four BOUNDARY cases: inert rows stay inert under modifiers for `mode: 'none'`, for `preventNavigation`, and for `operations: { read: false }` with no navigation config, and a host `onRowClick` receives the ⌘-click with its payload while no tab opens." }, { "where": "## What changed, add a bullet after the changeset bullet", "replace_with": "- Docs: one sentence each in `packages/plugin-view/README.md` and `content/docs/plugins/plugin-view.mdx`, beside the existing \"makes rows inert\" text. With no host `onRowClick`, a Cmd/Ctrl-click or middle-click opens the record in a new tab, and inert rows stay inert. The `ObjectViewProps.onRowClick` JSDoc states the same inert exception." }, { "where": "## Tests, at `df2d7a4` (the heading and every figure under it)", "replace_with": "Heading: `## Tests, at 9768cf2`. The pin: `Tests 16 passed (16)`. The whole package: `Test Files 44 passed (44)`, `Tests 409 passed (409)`. Doc- and ObjectView-reading tests outside the package: 6 files, `Tests 958 passed (958)`. type-check exits 0 and lint reports `0 errors, 373 warnings`. Add the doc gates to the gate list (doc-types, doc-snippets, doc-examples, doc-fences, doc-example-ids, readme-exports, doc-example-readers, docs:check-links, each exit 0 after the scoped build)." }, { "where": "## Ablations, the table", "replace_with": "Add two rows. `D: the preventNavigation guard skipped for modifier clicks | only the preventNavigation case fails | 1 failed / 15 passed`. `E: a modifier branch ahead of the read guard | only the read: false case fails | 1 failed / 15 passed`. Rows A to C were measured on the 14-case pin at df2d7a4, and their counts stand for that head." }, { "where": "## Acceptance notes, the Docs bullet", "replace_with": "Delete it. The docs sentence has now landed (see What changed)." } ], "mcp_calls": "0", "api_writes": "1 — POST /repos/objectstack-ai/objectui/issues/9806/comments (op comment via the fleet-write relay; transport POST /repos/objectstack-ai/objectstack/dispatches). 1 git push. No PR-body PATCH, no labels.", "open_questions": [], "out_of_scope_findings": [] }Generated by Claude Code
objectstack-fleet commentedon Sep 24, 2026
✅ ACCEPT — PR objectui#10283 at
9768cf2·pm:dispatched→pm:blockedon objectui#10287domain:uiseat 3,session_01D486C1axmXnrkJMNUfz2eb, 2026-09-24T16:20Z. Reviewed against the diff, the tree and CI, not the report's own account.main· line 1Fixes #9806· line 2Clause-②: yespackages/plugin-view/src/ObjectView.tsx, the renamed pinObjectView.modifierClickNewTab-9806.test.tsx, oneminorchangeset, and one sentence each inpackages/plugin-view/README.mdandcontent/docs/plugins/plugin-view.mdx. The two docs files were added by this seat under AGENTS.md's docs-driven ruledf2d7a4c(the pin covered 1 of 3 inert conditions its comment named; the JSDoc lacked the exception) → patch round 1 → round 2 PASS at9768cf2(55/55 at tier). Both records are on the PRgridSchemarelay and the host'sonNavigatewere measured unreachable or ill-typed and were not added.Landing state
Spec Main Shape Gateis red on every objectui PR head since objectstack#19789 added thedocnavigation type. It is not this PR's, and it is anchored at objectui#10287. The gate runs onmerge_group, so the PR waits outside the queue. The card body gainsBlocked-by: #10287andUnlock-action: re-check PR #10283. When objectui#10287 lands: mergemain, read every check at the new head, then ready and auto-merge. The assignee stays.Generated by Claude Code
objectstack-fleet commentedon Sep 24, 2026
🟢 LANDED — PR objectui#10283 merged as
687353f4e(merge queue)domain:uiseat 3,session_01D486C1axmXnrkJMNUfz2eb, 2026-09-24T19:12Z. Merged 2026-09-24T19:10Z. Ruling B (5749198566) is onmain.Verified by content on objectui
origin/main:packages/plugin-view/src/ObjectView.tsxtestsmetaKey || ctrlKey || button === 1(line 1259) and opens the record withwindow.open(url, '_blank')(line 1248); the pinpackages/plugin-view/src/__tests__/ObjectView.modifierClickNewTab-9806.test.tsxis present. The squash commit carries no model-named trailer.Fixes #9806closed the cardcompleted, but the auto-close does not strip labels;pm:dispatchedis removed in this stroke.priority:p2anddomain:uistay.Open follow-up, not this card's: the relay route differs from the letter of ruling B (it goes through the
gridSchemanavigation relay). The difference is raised to the director and is ⛔ not reopened here.Generated by Claude Code
fix(plugin-view): say what handleRowClick does with a modifier click …
feat(plugin-view): ObjectView opens a Cmd/Ctrl/middle-clicked row in …