Repository navigation
[finding] a gantt view's authored view-level navigation reaches nothing — ListView forwards it neither as a prop nor on the view schema, so ObjectGantt always falls back to its own mode: 'drawer' default #7334
Description
Activity
- addeddomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatobjectui ui stream: fix lands on the published library or apps — objectui execution seat
on Sep 5, 2026 分诊 —
domain:ui/priority:p2/pm:queue/finding锚定 (anchoring):最便宜的修法落在
packages/plugin-list/src/ListView.tsx⇒domain:ui。在
origin/maina472b07上复核 —— 三条读数全部成立读数 1(gantt 自带抽屉):
packages/plugin-gantt/src/ObjectGantt.tsx:1511 const navConfig = schema.navigation ?? { mode: 'drawer' }; packages/plugin-gantt/src/ObjectGantt.tsx:1512 const navIsOverlay = navConfig.mode === 'drawer' || navConfig.mode === 'modal' || navConfig.mode === 'split' || navConfig.mode === 'popover'; packages/plugin-gantt/src/ObjectGantt.tsx:1514 navigation: navConfig,?? { mode: 'drawer' }就是那个兜底。⭐ 顺带一条卡片没写、但对定级有用的::1512说明组件认得四种 mode(drawer/modal/split/popover),所以这不是"组件只会抽屉",而是"组件会四种、但永远只收到默认那一种"。读数 3(授权配置也到不了)—— 卡片说这是新的那部分,我逐字复核:
packages/plugin-list/src/ListView.tsx:2331 const baseProps = { objectName · fields · filter · sort · className showSearch · onRowClick · rowHeight …baseProps的键没有navigation。正控制,同一次读:onRowClick: navigation.handleClick命中 1 处 —— 也就是说ListView手里确实有navigation(它自己在用),只是没往下传。这把卡片的论证钉死了:不是"宿主没有这个信息",是"宿主有但不转发"。定级理由
priority:p2(比同批的 #7333 高一级,理由是可达性):- [finding]
ObjectGantt.reload's host-datashort-circuit fires on an EMPTY array, so the day the wrapper forwards host props an api-provider gantt paints an empty chart instead of its endpoint's tree #7333 今天不可达(有钉子证明宿主 prop 进不了rest);本卡今天就在发生 —— 任何 authorednavigation: { mode: 'page' }的 gantt 视图,现在就开抽屉。 - 这是已声明键被静默丢弃:作者写了明确指令,两条可能承载它的路径都是空的,没有任何诊断。
- 卡片对"为什么其他视图类型没这个毛病"的解释是对的、也是反证:它们消费
onRowClick,于是由宿主的 overlay 决定 —— 只有 gantt 因为自带抽屉而"看起来正常",这正是它躲过发现的原因。
不上 p1:兜底是良性的(抽屉照样打开、记录照样可达),没有数据损坏、没有错误数据。且卡片诚实标注未经浏览器验证,我采信静态证据但不把它升级成实测。
⭐ 为什么是
pm:queue而不是pm:blocked卡片说"哪一层该修没定,且第二种修法与 #7210 half 2 纠缠"。这句只对一半成立,而这一半的区分决定了状态:
路线 落点 是否被 #7210 half 2 挡住 A. 在 gantt 视图 schema 路上转发 navigation—— 即baseProps或viewComponentSchema的case 'gantt'分支补一个navigation键ListView.tsx一处⛔ 不挡。组件已经读 schema.navigation(:1511),这条路今天就通B. 把宿主 props 转发给图表 wrapper 挡住 —— 那是 #7210 half 2 的天花板决策,且会顺带引爆 #7333 的空数组短路 路线 A 现在可做、独立可交付、且与 B 的裁决无关(B 若最终落地,A 也不会白做:A 修的是 schema 路,B 修的是 prop 路,两者本就该一致)。所以进队列。
定型:Bug,不踩 manual floor
navigation是已存在的授权键,组件已经在读;补一处转发 ⇒ 恢复 declared = enforced。⛔ 不新增任何作者可写的键。⛔ 给执行席的边界
- 做路线 A,⛔ 不要顺手做 B。 B 是 A gantt lens fetches its rows twice — once paged (which feeds the footer) and once unbounded (which feeds the chart), so the footer misdescribes the chart and the real fetch has no ceiling #7210 half 2 的范围,且会撞上 [finding]
ObjectGantt.reload's host-datashort-circuit fires on an EMPTY array, so the day the wrapper forwards host props an api-provider gantt paints an empty chart instead of its endpoint's tree #7333 记录的[]短路(ObjectGantt.tsx:710与:818两处)。 - 加一个断言 mode 的钉子。 这个 bug 的形态是"抽屉照样开",所以任何只断言"点击后有东西打开"的测试都抓不住它 —— 必须断言
navConfig.mode等于作者写的值。⛔ 不要写expect(drawer).toBeVisible()就收工。 ⚠️ 转发时注意viewComponentSchema的case 'gantt'分支目前只加data/startDateField/endDateField/progressField/dependenciesField/titleField与两个 gantt 配置包 —— 决定把navigation加在baseProps(所有视图共享)还是只加在 gantt 分支时,请说明理由:加在baseProps会让其他视图类型同时收到一个它们已经通过onRowClick解决的东西,可能产生两个真相源。- 修完回 finding(plugin-gantt):
ObjectGanttRendererforwards no host prop butschema— the one view wrapper that does not, soListView'sdata/onRowClicknever arrive and a live comment says the opposite #7222 的 "Consequence 3 — SUSPECTED, not measured" 销账。
⛔ 分诊席不认领、不派单、不写码、不裁定 #7210 half 2。
Generated by Claude Code
- [finding]
Claim: branch
claude/issue-7334-gantt-navigation-forwardClause-②: yes
domain:uiPM seat,session_01UzHd6hDYatoDn17BuwKxnZ, R16. The assignee and this comment are set by the PM on the dev's behalf — the dev inherits both, posts no second claim, and never writes the assignee field.⚠️ Whyyeswhen the repair looks like pure plumbing. Triage's 定型 says this adds no authorable key, and I agree that is the likely outcome. But if making the forwarded key legible turns out to neednavigationdeclared on the gantt registration's inputs, that is an accept-set widening. The lane rule is 「拿不准 ⇒ 按yes」 and anomay not be revised on delivery — so this is declaredyesdeliberately, and the in-seat review is cheap if the change never touches a declaration. ⛔ Hang the carriers on both sides with the draft PR and report the--pairexit code, per the standing contract; ⛔ do not let this comment's prose talk you out of that.The direction is already ruled — ⛔ do not re-derive it
Triage's comment
5548470070is load-bearing and I am not restating it. Read it in full. The one thing it settles that matters most: route A is NOT blocked by objectui#7210 half 2, and route B is. You are doing A only.route lands blocked? A — forward navigationon the gantt view-schema pathListView.tsx, one place⛔ not blocked — ObjectGanttalready readsschema.navigationB — forward host props to the chart wrapper blocked by objectui#7210 half 2, and it detonates objectui#7333's []short-circuitRe-measured on
origin/main689953abefore dispatch, ⛔ not inheritedTriage measured at
a472b07on 2026-09-05 — six days stale. All three readings hold, with a lit control:ObjectGantt.tsx:1492 const navConfig = schema.navigation ?? { mode: 'drawer' }; ObjectGantt.tsx:1493 navIsOverlay = mode === drawer|modal|split|popover ListView.tsx baseProps keys, printed in full: objectName · fields · filter · sort · className · showSearch · onRowClick · rowHeight · hideRowHeightToggle ^ no `navigation:` key control: the same probe finds `onRowClick:` -> 1 hit, so it can see a key that is there⚠️ Triage's line numbers are stale even though its claims are not — the fallback moved:1511to:1492. Re-derive every line number; ⛔ cite none of ours as fact.⚠️ One trap I walked into while checking this, so you do not: greppingbasePropsfor the wordnavigationreturns a false positive —onRowClick: navigation.handleClickcontains it as a value. The defect is the absence of anavigation:key. Match the key, and keep a positive control in the same run.ZONE 1 — what to build
Forward the authored
navigationdown the gantt view-schema path soObjectGanttreceives what the author wrote instead of falling back. ⭐ The component already understands four modes (drawer/modal/split/popover) — this is not "the component only knows drawers", it is "the component knows four and is only ever handed the default".⚠️ Triage left you one real judgement call, and it wants a stated reason, not a coin flip:baseProps(shared by every view type) or thecase 'gantt'branch only. Putting it inbasePropshandsnavigationto view types that already resolve this throughonRowClick, which risks two sources of truth for one question. Say which you chose and why — that reasoning is part of the deliverable.ZONE 2 — fences
- ⛔ Route A only. Do not forward host props to the chart. That is objectui#7210 half 2's scope and it collides with objectui#7333's
[]short-circuit (ObjectGantt.tsxaround:710and:818). - ⛔ Add no new authorable key.
navigationexists and is already read; this restores declared = enforced. - ⛔ Do not rule on objectui#7210 half 2, and do not wake objectui#7333.
ZONE 3 — stop conditions, stated as the harm each guards
⭐ Written this way on purpose — a stop condition naming a location instead of a harm cost this lane a round earlier today, and the very next dispatch proved the fix works. If your measurement contradicts a location I gave but the harm is untouched, keep going and say so in the report.
- Stop if the repair cannot be done without forwarding host props to the chart. The harm is silently doing route B: taking a ceiling decision that is not this card's and detonating a short-circuit another card is tracking.
- Stop if forwarding
navigationturns out to change behaviour for a non-gantt view type. The harm is a second source of truth for one question — the exact thing thebaseProps-vs-branch choice exists to avoid. Report which view types you measured. - Report, don't stop, if the component's mode handling is wider or narrower than the four modes at
:1493.
ZONE 4 — acceptance
⭐ Triage's sharpest instruction, and the one I will check first in review: the pin MUST assert
navConfig.modeequals the authored value. This defect's whole shape is "a drawer opens anyway" — so any test asserting only that something opened is blind to it by construction. ⛔expect(drawer).toBeVisible()is not acceptance here.- An authored
navigation: { mode: 'page' }on a gantt view → the mode the component resolves ispage, ⛔ notdrawer. - At least one non-
draweroverlay mode too (modalorsplit), so the fix is not special-cased to one value. - A gantt view authoring no
navigation→ stilldrawer. (Regression guard: the fallback must survive.) - Ablate the forwarding and show the mode assertions go red; restore and prove the tree clean by state (
git diff HEADempty), ⛔ never by an exit code.
ZONE 5 — bookkeeping owed
Triage asks that objectui#7222's "Consequence 3 — SUSPECTED, not measured" be settled when this lands. Name it in your report; ⛔ do not edit objectui#7222 yourself.
Generated by Claude Code
- ⛔ Route A only. Do not forward host props to the chart. That is objectui#7210 half 2's scope and it collides with objectui#7333's
os-dev-report
{ "issue": 7334, "status": "done", "branch": "claude/issue-7334-gantt-navigation-forward", "pr": "https://github.com/objectstack-ai/objectui/pull/9115", "premise_still_valid": true, "summary": "Route A only. ListView's `case 'gantt'` branch of `viewComponentSchema` now forwards the authored view-level `navigation` onto the gantt node, conditionally, so `ObjectGantt` receives what the author wrote instead of falling back to its own `schema.navigation ?? { mode: 'drawer' }`. All three of the card's readings re-measured and HOLD on origin/main 689953a (triage's line numbers were stale as warned; the fallback sits at ObjectGantt.tsx:1492 and the four-mode classification at :1493 today, and I cite by symbol rather than by line in the code itself because objectui#8875's gate forbids new cross-file line addresses). The judgement call went to the BRANCH, not `baseProps`, and it is a measurement: seven other child views (grid, gallery, kanban, calendar, map, timeline, tree) read `schema.navigation` for themselves AND all receive `onRowClick: navigation.handleClick` from `baseProps`, which `useNavigationOverlay` gives full priority over any `navigation` handed in - so on `baseProps` six of them would take the key only to have it outranked (this card's own defect relocated) and ObjectCalendar would genuinely change behaviour, since it mirrors gantt's `navIsOverlay ? undefined : onRowClick` and would stop suppressing the host handler for an authored non-overlay mode. Gantt is the only branch where forwarding settles the question rather than splitting it, because its wrapper forwards no host props at all. No host prop is forwarded to the chart; no new authorable key; no ruling taken on objectui#7210 half 2; objectui#7333 untouched. Assignee was already set to os-tesla by the PM and I never wrote it. Clause-2 carriers hung on both sides; `--pair 9115` exit 0.", "tests": "All at repo root, exit codes captured by redirecting to a file, never after a pipe; heavy runs serialized through ../objectstack/scripts/pm/os-verify-lock.sh. (1) `pnpm exec vitest run packages/plugin-list/ packages/plugin-gantt/` exit 0 - `Test Files 142 passed (142)`, `Tests 1428 passed (1428)`. (2) `pnpm --filter @object-ui/plugin-list --filter @object-ui/plugin-gantt run type-check` exit 0; each runs `tsc --noEmit` AND `tsc -p tsconfig.test.json`, and `--listFiles` returns 1 hit for each new test file in those programs, so the tests really are type-checked and this is not a NOT-MEASURED. It failed first with TS2307 on unbuilt workspace `.d.ts`; the dependency-closure build `turbo run build --filter '@object-ui/plugin-list^...' --filter '@object-ui/plugin-gantt^...'` (exit 0, 13/13 tasks) is what made it a real reading. (3) Two new pins, 17 tests. plugin-list/src/__tests__/ListView.ganttNavigationForward-7334.test.tsx (11) asserts the forwarded MODE VALUE - authored `page` arrives as `page` and not `drawer`, authored `modal` as `modal`, the whole block (mode+view+size) verbatim, no authored navigation gives an ABSENT key (regression guard, the `drawer` default survives), a positive control first, plus five cross-view cases pinning that grid/kanban/calendar/tree/timeline get NO navigation key while each is asserted to be receiving `onRowClick` in the same breath. plugin-gantt/src/ObjectGantt.navigationModeResolution-7334.test.tsx (6) asserts the RESOLVED `navConfig.mode` on the real component through the registry, instrumented by a pass-through spy on its own `useNavigationOverlay` call so the value read is the component's own navConfig and not a second copy of its `??`: `page` resolves `page` with isOverlay false, `modal` and `split` resolve themselves with isOverlay true, absent resolves `drawer`, and all seven spec modes are classified. The second file exists because the four overlay modes are NOT separable from the DOM in ObjectGantt - all four render the one RecordDetailDrawer - so `expect(drawer).toBeVisible()` could not have told `modal` from `drawer` even in principle. (4) ABLATION, direction predicted before running then observed. Mutation proven ON DISK before reading any result: anchor `...(schema.navigation ? { navigation: schema.navigation } : {}),` counted 1 before and 0 after, worktree blob 853fe7f then a14929a, numstat 1 add / 2 del. Ablated plugin-list pin exit 1, `Tests 4 failed | 7 passed (11)`, with `AssertionError: expected undefined to be 'page'`, `expected undefined to be 'modal'`, `expected undefined to deeply equal { mode: 'split', ...(2) }`; the regression guard and all five cross-view cases STAYED GREEN, as predicted. Ablated plugin-gantt resolution pin exit 0 and ablated sibling control ListView.gantt-binding-7070 exit 0 - both stay green, which is the asymmetry that makes the two files fail for different reasons. The script carried `trap restore EXIT INT TERM` with an absolute REPO_ROOT and restored with `git checkout HEAD -- PATH` (never a bare `git checkout --`, which would take the mutation back out of the index). RESTORED BY STATE, not by an exit code: `git diff HEAD` empty, `git status` empty, and `git hash-object` on the file back to the HEAD blob hash 853fe7f exactly. No dist hop was involved: the plugin-list pin imports `../ListView` by relative source path, so the ablation reached the test without a rebuild. (5) Gates: check:vi-mock-specifiers, check:vi-mock-inherit, check:vi-mock-override-shape, check:control-bytes, check:new-line-citations (0 new citations), check-changeset-presence, check-changeset-no-major - exit 0 each, judged on the gate's own printed verdict line. A direct control-byte scan of the four changed paths matched nothing. check-governed-queue-guard --test on all four paths: NOT GOVERNED. (6) Lint, with all three pieces of evidence rather than a narrowing claim: the FULL tree was linted, `eslint . --no-inline-config --format json`, population 4795 files read from eslint's own config and file count read from the json output - so no narrowing was declared and none is needed. My three changed files carry 0 ERRORS each (warnings only, `no-explicit-any` of the kind every sibling test in these packages carries). The repo's own per-package gate `pnpm --filter ... run lint` also exits 0 for both packages (0 errors; 505 and 372 pre-existing warnings). NOT MEASURED: nothing was driven in a browser - this card, like the finding it came from, is settled statically. CI convergence on PR 9115 is the PM's read, not mine.", "mcp_calls": "0 - zero MCP GitHub calls for the whole run. The repo-scoped REST probe came back 200 at the top of the round, so every read and write went through REST (issue body, comments, dedup listing, issue creation, PR creation, labels) and every history question went through git. The dispatch's false-zero warning about MCP `search_issues` therefore never applied to this run.", "open_questions": [], "out_of_scope_findings": [ "filed as #9113: three of the four overlay `navigation.mode` values collapse into the drawer - ObjectGantt, ObjectKanban and ObjectCalendar each compute the identical four-mode predicate and then render RecordDetailDrawer (a Sheet) with no mode prop, so authored `modal` / `split` / `popover` all render as `drawer`, while ObjectGrid and ObjectTree honour all four through the shared NavigationOverlay (Sheet / Dialog / ResizablePanelGroup / popover). Newly observable on gantt because of this PR; class (c) - declared values silently narrowed, and narrowed inconsistently across sibling surfaces.", "filed as #9114: `page` is the one mode that becomes reachable and inert on a gantt. useNavigationOverlay's `page` branch needs an `onNavigate` and returns with no fallback without one; ObjectGantt supplies none (only ObjectGrid and ObjectCalendar do, of the eight view renderers) and ObjectGanttRenderer drops the host `onRowClick`. `new_window` still works through the window.open fallback and `none` is inert by design, so `page` alone moves from 'wrong thing happens' to 'nothing happens'. Remedy space named on the card (ObjectGantt could supply its own onNavigate without touching any host prop); no ruling taken on objectui#7210 half 2.", "noted, not filed: ObjectGantt's `navIsOverlay` and the spec enum are measurably out of step in WIDTH but correctly so - the component classifies the four overlay modes and leaves `page` / `new_window` / `none` to the hook, which handles all seven. This is the ZONE 3 'report, do not stop' item and it is not a defect on its own; the two ways it bites are the two cards above. Successor: whoever picks up #9113 or #9114.", "noted, not filed: `ObjectGallery` lives in plugin-list and reads `schema.navigation` itself, so it is an eighth reader of the key even though its branch is next door to the one changed. It is unaffected here (it also receives `onRowClick` unconditionally) and is listed only so the seven-plus-one count in the PR body is not read as an omission. Successor: any PR that revisits the baseProps-vs-branch question." ], "bookkeeping_7222_consequence_3": "SETTLED STATICALLY, and the suspicion was half right in an instructive way - reported here as asked; objectui#7222 is CLOSED and I did ⛔ not edit it. Its Consequence 3 read: 'If a gantt view's authored navigation config (drawer / modal / page) is meant to be honoured through that callback, it is not being honoured - but ObjectGantt owns a record drawer of its own, so it may simply not need it. Unverified.' Measured answer: BOTH halves are true, and they are true of different things. The authored navigation was indeed not honoured - but the dropped `onRowClick` was NOT the reason, so the second half ('it may simply not need it') is correct: the callback was never the carrier. ObjectGantt reads the config off its OWN view schema and was never handed one, because ListView put the key on no node at all. That is the gap this card measured and this PR closes on the schema side. Status now: the SCHEMA carrier is live; the PROP carrier stays dead and remains objectui#7210 half 2's scope. Still not browser-driven - the question 'someone should drive a gantt row click in a browser' is answered at the component level by ObjectGantt.navigationModeResolution-7334.test.tsx, not in a browser." }
Generated by Claude Code
os-dev-report
{ "issue": 7334, "status": "done", "branch": "claude/issue-7334-gantt-navigation-forward", "pr": "https://github.com/objectstack-ai/objectui/pull/9115", "premise_still_valid": true, "summary": "TWO LEGS, both on this card. Leg 1 (plugin-list, first round): ListView's `case 'gantt'` branch now forwards the authored view-level `navigation` onto the gantt node, conditionally, so ObjectGantt receives what the author wrote instead of its `schema.navigation ?? { mode: 'drawer' }` fallback. The branch-not-baseProps choice is a measurement: seven other child views read `schema.navigation` for themselves AND all receive `onRowClick: navigation.handleClick` from baseProps, which useNavigationOverlay gives full priority over any `navigation` handed in - so on baseProps six would take the key only to have it outranked and ObjectCalendar would genuinely change behaviour. Leg 2 (plugin-gantt, this round, added on seat review): ObjectGantt now supplies its own `onNavigate`, because leg 1 alone made `mode: 'page'` reachable and INERT - the hook's `page` branch calls `onNavigate` and returns with no fallback, the component supplied none, and ObjectGanttRenderer hands it no host `onRowClick` either, so both carriers were empty at once and an authored `page` went from 'a drawer opens, which is wrong' to 'the click does nothing'. The destination is the record-page href the component already computes for the drawer's full-page link, so the two cannot diverge; same-tab navigation uses the history plus popstate pair matching DashboardRenderer and PageHeader; `new_window` now opens that app-prefixed href instead of the hook's unprefixed fallback. After both legs no authored value is worse than before and two are better (`page`, `new_window`); `drawer`/unauthored and `none` are unchanged. ⛔ Still route A throughout: no host prop is forwarded to the chart, no `{...props}` is spread, objectui#7210 half 2 is untouched and unruled, objectui#7333 is not woken, no new authorable key. objectui#9113 left filed and out of scope as the PM directed. `origin/main` was MERGED in (never rebased) after a `git merge-tree --write-tree` conflict probe came back clean; it brought only app-shell and scripts changes, nothing in either package this PR touches.", "tests": "All at repo root; exit codes captured by redirecting to a file, never after a pipe; heavy runs serialized through ../objectstack/scripts/pm/os-verify-lock.sh. Suite and lint figures are from the FINAL head 066c43e (after the origin/main merge), not from an earlier commit. (1) `pnpm exec vitest run packages/plugin-list/ packages/plugin-gantt/` exit 0 - `Test Files 143 passed (143)`, `Tests 1435 passed (1435)`. (2) `pnpm --filter @object-ui/plugin-list --filter @object-ui/plugin-gantt run type-check` exit 0; each runs `tsc --noEmit` AND `tsc -p tsconfig.test.json`, and `--listFiles` returns 1 hit for EACH of the three new test files, so they really are type-checked. The dependency-closure build `turbo run build --filter '@object-ui/plugin-list^...' --filter '@object-ui/plugin-gantt^...'` (exit 0, 13/13) is what makes that a real reading rather than TS2307 noise on unbuilt workspace `.d.ts`. (3) THREE pins, 24 tests, each failing for a different reason. ListView.ganttNavigationForward-7334 (11, plugin-list) asserts the forwarded MODE VALUE plus five cross-view cases that no other view type starts receiving the key, each asserted to be receiving `onRowClick` in the same breath so the negative reading is a measurement. ObjectGantt.navigationModeResolution-7334 (6) asserts the RESOLVED navConfig.mode on the real component through the registry via a pass-through spy on its own useNavigationOverlay call. ObjectGantt.navigationPageSink-7334 (7, NEW with leg 2) asserts the navigation ARGUMENTS: a real task click under an authored `page` gives `pushState({}, '', '/console/duly_task/record/1')` exactly once, a popstate dispatched, and location.pathname moved - `deriveRecordPageHref` is deliberately NOT mocked and the harness parks the document on /console/duly_task/views/all first, so the /console prefix can only come from a genuine derivation. Plus `new_window` asserting the prefixed href and '_blank', two overlay controls that must NOT navigate, the unauthored regression guard, and `none` staying inert. (4) TWO ABLATIONS, direction predicted before running then observed, each with an EXIT/INT/TERM trap on an absolute repo root and `git checkout HEAD -- PATH` restore (never a bare `git checkout --`, which takes the mutation back out of the index). LEG 1 - delete the forwarding line; mutation proven on disk first (anchor 1 then 0; blob 853fe7f then a14929a): plugin-list pin exit 1, `Tests 4 failed | 7 passed (11)`, `expected undefined to be 'page'` / `'modal'` / `expected undefined to deeply equal { mode: 'split', ...(2) }`, while the regression guard, all five cross-view cases, the resolution pin (exit 0) and the sibling control ListView.gantt-binding-7070 (exit 0) stayed green. LEG 2 - delete `onNavigate: navigateToRecord` from the hook call; mutation proven on disk first (anchor 1 then 0; blob 21bb86c then b96f82c): the sink pin exit 1, `Tests 3 failed | 4 passed (7)`, `expected 'undefined' to be 'function'`, `expected \"pushState\" to be called 1 times, but got 0 times`, and - exactly as predicted - `new_window` red on its ARGUMENTS rather than on the call, received `/duly_task/record/1` against expected `/console/duly_task/record/1`, because the hook's own window.open fallback still fires with no app prefix to work from. Both overlay controls, the regression guard and `none` stayed green, and both leg-1 pins stayed green (exit 0 each). RESTORED BY STATE both times, never by an exit code: `git diff HEAD` empty, `git status` empty, `git hash-object` back to the HEAD blob hash exactly (853fe7f, 21bb86c). Re-run from the restored state: 3 files, 24 tests, all green. (5) Gates, judged on each gate's own printed verdict line: check:vi-mock-specifiers, check:vi-mock-inherit, check:vi-mock-override-shape, check:control-bytes, check:new-line-citations (0 new citations), check-changeset-presence (5 source files of 2 released packages, 1 changeset now declaring both), check-changeset-no-major - exit 0 each. A direct control-byte scan of all six changed paths matched nothing. check-governed-queue-guard --test on all six paths: NOT GOVERNED. (6) check:changeset-claims - a gate that landed on main DURING this round and arrived with the merge. Exit 0, report-only, and the read it asks for was DONE rather than skipped: it names 9 pending changesets whose bodies mention ListView.tsx or ObjectGantt.tsx; all nine paragraphs were read and none is falsified. The two gantt ones speak about getGanttConfig's declared key set and getDataConfig's record-source ladder (neither touched; navigateToRecord only READS the already-resolved `resource`); 7070 and 7499 say the gantt branch invents no date axis and floors no non-axis key (a key was added, no floor restored); 7773 / 7780 / 8990 / list-user-actions-collision-5398 are kanban groupBy and userActions, untouched; listview-comment-pair-4559's 'no runtime behaviour changes' is a claim about ITS OWN change (objectui#4559/#4966), not a standing claim about the file, so a separate CHANGELOG entry describing a behaviour change does not contradict it. (7) Lint, all three evidence pieces and no narrowing claim needed because the FULL tree was linted: `eslint . --no-inline-config --format json`, population 4796 files read from eslint's own config, counts read from the json output. All FIVE changed files carry 0 ERRORS (warnings only). Tree-wide errors 95 across 79 files - the same reading as before leg 2, so this branch adds none. The repo's own per-package gate `pnpm --filter ... run lint` exits 0 for both packages (0 errors; 505 and 382 pre-existing warnings). NOT MEASURED: nothing was driven in a browser; this card is settled statically, as the finding it came from was. CI convergence on PR 9115 is the PM's read, not mine.", "clause2": "`--pair 9115` (PM_SWEEP_REPO=objectstack-ai/objectui, exit captured by redirect, not after a pipe) FINAL EXIT 0. Worth reading, because it was 4 first and the reason is a real protocol fact rather than a flake: adding a `Closes` line for #9114 made this PR deliver TWO cards, and the checker's C1 row then reported a split - the gate hung on the PR and on #7334 but not on #9114. The gate is a DUAL carrier (maintainer 2026-08-22, both sides hung), and the second carrier is the only machine-readable evidence that a gate was CLEARED rather than STRIPPED, so the split was closed by hanging `needs:contract-review` on #9114 too, through the additive REST endpoint, with a read-back. Its C2-SIBLING row then resolved to a pass: #9114 carries no `Claim:` comment because a card that closes when its sibling's PR lands is never separately dispatched, and the declaration governing this PR's contract increment is readable on the sibling card #7334. Carriers now: PR #9115 yes, card #7334 yes, card #9114 yes - all read back.", "mcp_calls": "0 - zero MCP GitHub calls across both rounds. The repo-scoped REST probe came back 200 at the top of the round, so every read and write went through REST (issue and comment reads, dedup listing, issue creation, PR creation and PATCH, labels on three carriers) and every history question went through git. The dispatch's false-zero warning about MCP `search_issues` never applied to this run.", "open_questions": [], "out_of_scope_findings": [ "filed as #9114, and now CLOSED BY THIS PR rather than left as a follow-up: `page` was the one mode that leg 1 made reachable and inert. Leg 2 is the remedy the card itself named (ObjectGantt supplies its own onNavigate, touching no host prop). The card stays as the written record of the measurement; the PR body carries a `Closes` line naming #9114, and that is the only closing keyword anywhere in it.", "filed as #9113, LEFT FILED and out of scope on the PM's explicit direction: three of the four overlay `navigation.mode` values collapse into the drawer - ObjectGantt, ObjectKanban and ObjectCalendar each compute the identical four-mode predicate and then render RecordDetailDrawer (a Sheet) with no mode prop, so authored `modal` / `split` / `popover` all render as `drawer`, while ObjectGrid and ObjectTree honour all four through the shared NavigationOverlay (Sheet / Dialog / ResizablePanelGroup / popover). Pre-existing, outcome unchanged by this PR in either leg, and repairing it means touching ObjectKanban and ObjectCalendar too.", "noted, not filed: `GanttView`'s own `onTaskClick` prop type takes a GanttTask and returns void - it declares no event parameter at all - so useNavigationOverlay's documented Cmd/Ctrl/middle-click 'open in a new tab' convention is structurally unreachable on a gantt. Measured because leg 2 touches exactly that code path: the sink the convention needs now exists, but the event never arrives to trigger it, so this PR changes nothing here in either direction. Successor: whoever picks up #9113, which is the other half of 'this component's click handling is narrower than the shared vocabulary'.", "noted, not filed: `ObjectGallery` lives in plugin-list and reads `schema.navigation` itself, so it is an eighth reader of the key even though its branch is next door to the one changed. Unaffected here (it also receives `onRowClick` unconditionally) and listed only so the seven-plus-one count in the PR body is not read as an omission. Successor: any PR that revisits the baseProps-vs-branch question." ], "pm_correction": "ONE READING IN THE REVIEW IS WRONG, and the harm it names is untouched - reported per ZONE 3's own rule rather than silently worked around. The review's lit control was `control ObjectCalendar.tsx onNavigate occurrences: 4`, offered as the model to copy. Those four occurrences are a DIFFERENT symbol: ObjectCalendar's `onNavigate` is its own component prop for CALENDAR DATE navigation (its declaration reads as taking a Date and returning void) and it is never handed to useNavigationOverlay - the calendar's hook call passes navigation, objectName and onRowClick only, exactly like the gantt's did. Of the eight view renderers, the only one that hands the hook an `onNavigate` is ObjectGrid, and it reads it off `schema.onNavigate`, a NON-AUTHOR programmatic-caller channel that the maintainer ruling of 2026-08-19 on objectui#5234 deliberately keeps unpublished - so copying that spelling into ObjectGantt would have left the dead click exactly as it was, because ListView forwards no such key and nothing else sets it. The repair therefore had to be a real in-component sink rather than a copied prop read, which is what landed. The HARM the review named - `page` reachable and inert, a silent dead click - was measured correct in every other respect and is what leg 2 fixes.", "bookkeeping_7222_consequence_3": "SETTLED STATICALLY, unchanged from the first report and accepted in the seat review; objectui#7222 is CLOSED and I did ⛔ not edit it. Its Consequence 3 read: 'If a gantt view's authored navigation config (drawer / modal / page) is meant to be honoured through that callback, it is not being honoured - but ObjectGantt owns a record drawer of its own, so it may simply not need it. Unverified.' Both halves are true, of different things: the authored navigation was indeed not honoured, and the dropped `onRowClick` was genuinely NOT the reason - the callback was never the carrier, because the component reads the config off its OWN view schema and was never handed one. Leg 1 makes the schema carrier live. ⭐ Leg 2 adds the half that first report could not yet state: the dropped `onRowClick` is not needed for the overlay modes, and for `page` it is not needed either, because the component can own its destination without any host prop. The PROP carrier stays dead and remains objectui#7210 half 2's scope. Still not browser-driven - answered at component level by the two plugin-gantt pins, not by a real click in a browser." }
Generated by Claude Code
- addedpm:retriageQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatchQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatchand removed
on Sep 11, 2026 Landed — route A complete, both legs. ⛔ Routed to triage for the retirement call, ⛔ not closed by this seat.
domain:uiPM seat,session_01UzHd6hDYatoDn17BuwKxnZ, R16.PR objectui#9115 landed as
2c878be, verified BY CONTENT onorigin/main, ⛔ not by sha and ⛔ not by an ancestor test:subject navigateToRecord -> 2 (packages/plugin-gantt/src/ObjectGantt.tsx) control navIsOverlay -> 2 (same file, pre-existing)State cleaned by hand:
pm:dispatchedstripped, assignee cleared.⚠️ Neither was stripped by the merge — objectui#9115 saysPart offor this card, deliberately. Thirty-five for thirty-five in this lane.Contract review PASS in 同形 shape at objectui#9115 comment
5630163403(judged head066c43e); all three carriers cleared in one stroke;--pair 9115exit 0. Sibling card objectui#9114 auto-closed via itsClosesline.What landed, in two legs
authored on a gantt before after drawer/ unauthoreddrawer drawer modal/split/popoverdrawer drawer — ⚠️ still narrowed, tracked at objectui#9113pagedrawer — wrong, but the record opened ⭐ navigates to the record page new_windowdrawer new tab at the app-prefixed href nonedrawer inert, by design ⭐ Leg 2 exists because leg 1 alone made it worse. Forwarding the key made
mode: 'page'reachable and inert —useNavigationOverlay'spagebranch returns with no fallback when handed noonNavigate, and the wrapper drops the hostonRowClick, which is this card's own premise. Both carriers were empty at once, so an authoredpagewent from wrong thing happens to nothing happens. The seat held the PR on that and the dev added a real in-component sink; the destination reuses the href the drawer's own full-page link already computes, so the two cannot diverge.⚠️ The seat's fence had a hole and it is worth recording: the dispatch said stop if forwarding changes behaviour for a non-gantt view type — which the dev correctly steered around by choosing thecase 'gantt'branch overbaseProps— but never said stop if the gantt itself gets worse for any authored value. ⇒ fence the subject, not only the neighbours.Why this is
pm:retriageand notpm:queueThe card's own scope — "a gantt view's authored
navigationreaches nothing" — is discharged. What remains belongs elsewhere:- ⛔ Route B (the wrapper forwarding host props) is objectui#7210 half 2's scope, untouched and unruled here.
- ⛔ objectui#9113 —
modal/split/popoverall collapsing into the drawer across gantt, kanban and calendar — is pre-existing, unchanged by this PR in either leg, and repairing it means touchingObjectKanbanandObjectCalendartoo.
⇒ this card should not be picked up as dispatchable work again, and an execution seat should not close it either. Triage decides.
objectui#7222's Consequence 3 — settled statically, ⛔ objectui#7222 not edited
Its open question read: "If a gantt view's authored navigation config is meant to be honoured through that callback, it is not being honoured — but ObjectGantt owns a record drawer of its own, so it may simply not need it. Unverified."
⭐ Both halves true, of different things. The authored navigation genuinely was not honoured — and the dropped
onRowClickwas genuinely not the reason, because the callback was never the carrier: the component reads the config off its own view schema and was never handed one. Leg 1 makes the schema carrier live; leg 2 shows the droppedonRowClickis not needed even forpage, since the component can own its destination without any host prop. The prop carrier stays dead, and stays objectui#7210 half 2's.
Generated by Claude Code
Measured statically while implementing objectui#7222. Filed unassigned. This sharpens objectui#7222's "Consequence 3 — SUSPECTED, not measured", which asked whether a gantt's authored navigation is honoured; it is not browser-verified here either, but the two code paths that would have to carry it are both measurably empty.
Measured — three reads,
origin/mainat 1e7fe0aObjectGanttowns a record drawer of its own.packages/plugin-gantt/src/ObjectGantt.tsximportsRecordDetailDrawerfrom@object-ui/plugin-detailand computesconst navConfig = schema.navigation ?? { mode: 'drawer' };. So a task click opens something regardless of any host callback — which is why objectui#7222 was right not to treat the droppedonRowClickas a defect on its own.The host callback never arrives.
ListView'sbasePropscarriesonRowClick: navigation.handleClick, andObjectGanttRendererdestructures({ schema })only, so it is dropped one layer up (objectui#7222; pinned inplugin-gantt/src/ObjectGantt.hostDataProp-7210.test.tsx).The authored config never arrives either — this is the new part.
ObjectGanttreadsschema.navigation, so the schema route would still work even with every prop dropped. It is empty:basePropsinListView.tsxdeclares nonavigationkey (objectName,fields,filter,sort,className,showSearch,onRowClick,rowHeight,hideRowHeightToggle, plus the column-state callback), and thecase 'gantt'branch ofviewComponentSchemaadds onlydata,startDateField,endDateField,progressField,dependenciesField,titleFieldand the two gantt config bags.schema.navigationis consumed byListViewitself (useNavigationOverlay) and forwarded no further.Net: a list view authored
navigation: { mode: 'page' }(ormodal, orsplit) renders a gantt that opens a drawer. The other view types do not have this problem for the opposite reason — they consumeonRowClick, so the host's overlay decides.What is NOT claimed
Not driven in a browser. Nothing here says which layer should own the fix: forwarding
navigationon the gantt view schema and forwarding host props to the chart are two different repairs, and the second is entangled with the open ceiling decision on objectui#7210 half 2. Recording the measurement so the next person does not have to re-derive it.Refs: objectui#7222 (Consequence 3, where this was flagged as unverified), objectui#7210.
Generated by Claude Code