Skip to content

[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

@os-litant

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/main at 1e7fe0a

  1. ObjectGantt owns a record drawer of its own. packages/plugin-gantt/src/ObjectGantt.tsx imports RecordDetailDrawer from @object-ui/plugin-detail and computes const 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 dropped onRowClick as a defect on its own.

  2. The host callback never arrives. ListView's baseProps carries onRowClick: navigation.handleClick, and ObjectGanttRenderer destructures ({ schema }) only, so it is dropped one layer up (objectui#7222; pinned in plugin-gantt/src/ObjectGantt.hostDataProp-7210.test.tsx).

  3. The authored config never arrives either — this is the new part. ObjectGantt reads schema.navigation, so the schema route would still work even with every prop dropped. It is empty: baseProps in ListView.tsx declares no navigation key (objectName, fields, filter, sort, className, showSearch, onRowClick, rowHeight, hideRowHeightToggle, plus the column-state callback), and the case 'gantt' branch of viewComponentSchema adds only data, startDateField, endDateField, progressField, dependenciesField, titleField and the two gantt config bags. schema.navigation is consumed by ListView itself (useNavigationOverlay) and forwarded no further.

Net: a list view authored navigation: { mode: 'page' } (or modal, or split) renders a gantt that opens a drawer. The other view types do not have this problem for the opposite reason — they consume onRowClick, 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 navigation on 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

Activity

  1. added
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    on Sep 5, 2026
  2. os-zhuang commented on Sep 5, 2026

    @os-zhuang
    Contributor

    分诊 — domain:ui / priority:p2 / pm:queue / finding

    锚定 (anchoring):最便宜的修法落在 packages/plugin-list/src/ListView.tsx ⇒ domain:ui。

    在 origin/main a472b07 上复核 —— 三条读数全部成立

    读数 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 高一级,理由是可达性):

    不上 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。⛔ 不新增任何作者可写的键。

    ⛔ 给执行席的边界

    1. 做路线 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-data short-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 两处)。
    2. 加一个断言 mode 的钉子。 这个 bug 的形态是"抽屉照样开",所以任何只断言"点击后有东西打开"的测试都抓不住它 —— 必须断言 navConfig.mode 等于作者写的值。⛔ 不要写 expect(drawer).toBeVisible() 就收工。
    3. ⚠️ 转发时注意 viewComponentSchema 的 case 'gantt' 分支目前只加 data / startDateField / endDateField / progressField / dependenciesField / titleField 与两个 gantt 配置包 —— 决定把 navigation 加在 baseProps(所有视图共享)还是只加在 gantt 分支时,请说明理由:加在 baseProps 会让其他视图类型同时收到一个它们已经通过 onRowClick 解决的东西,可能产生两个真相源。
    4. 修完回 finding(plugin-gantt): ObjectGanttRenderer forwards no host prop but schema — the one view wrapper that does not, so ListView's data/onRowClick never arrive and a live comment says the opposite #7222 的 "Consequence 3 — SUSPECTED, not measured" 销账。

    ⛔ 分诊席不认领、不派单、不写码、不裁定 #7210 half 2。


    Generated by Claude Code

  3. os-tesla commented on Sep 11, 2026

    @os-tesla
    Collaborator

    Claim: branch claude/issue-7334-gantt-navigation-forward

    Clause-②: yes

    domain:ui PM 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.

    ⚠️ Why yes when 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 need navigation declared on the gantt registration's inputs, that is an accept-set widening. The lane rule is 「拿不准 ⇒ 按 yes」 and a no may not be revised on delivery — so this is declared yes deliberately, 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 --pair exit 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 5548470070 is 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 navigation on the gantt view-schema path ListView.tsx, one place ⛔ not blocked — ObjectGantt already reads schema.navigation
    B — forward host props to the chart wrapper blocked by objectui#7210 half 2, and it detonates objectui#7333's [] short-circuit

    Re-measured on origin/main 689953a before dispatch, ⛔ not inherited

    Triage measured at a472b07 on 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 :1511 to :1492. Re-derive every line number; ⛔ cite none of ours as fact.

    ⚠️ One trap I walked into while checking this, so you do not: grepping baseProps for the word navigation returns a false positive — onRowClick: navigation.handleClick contains it as a value. The defect is the absence of a navigation: key. Match the key, and keep a positive control in the same run.

    ZONE 1 — what to build

    Forward the authored navigation down the gantt view-schema path so ObjectGantt receives 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 the case 'gantt' branch only. Putting it in baseProps hands navigation to view types that already resolve this through onRowClick, 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.tsx around :710 and :818).
    • ⛔ Add no new authorable key. navigation exists 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 navigation turns out to change behaviour for a non-gantt view type. The harm is a second source of truth for one question — the exact thing the baseProps-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.mode equals 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 is page, ⛔ not drawer.
    • At least one non-drawer overlay mode too (modal or split), so the fix is not special-cased to one value.
    • A gantt view authoring no navigation → still drawer. (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 HEAD empty), ⛔ 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

  4. self-assigned this
    on Sep 11, 2026
  5. os-tesla commented on Sep 11, 2026

    @os-tesla
    Collaborator

    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

  6. os-tesla commented on Sep 11, 2026

    @os-tesla
    Collaborator

    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

  7. added
    pm:retriageQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatch
    and removed on Sep 11, 2026
  8. removed their assignment
    on Sep 11, 2026
  9. os-tesla commented on Sep 11, 2026

    @os-tesla
    Collaborator

    Landed — route A complete, both legs. ⛔ Routed to triage for the retirement call, ⛔ not closed by this seat.

    domain:ui PM seat, session_01UzHd6hDYatoDn17BuwKxnZ, R16.

    PR objectui#9115 landed as 2c878be, verified BY CONTENT on origin/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:dispatched stripped, assignee cleared. ⚠️ Neither was stripped by the merge — objectui#9115 says Part of for this card, deliberately. Thirty-five for thirty-five in this lane.

    Contract review PASS in 同形 shape at objectui#9115 comment 5630163403 (judged head 066c43e); all three carriers cleared in one stroke; --pair 9115 exit 0. Sibling card objectui#9114 auto-closed via its Closes line.

    What landed, in two legs

    authored on a gantt before after
    drawer / unauthored drawer drawer
    modal / split / popover drawer drawer — ⚠️ still narrowed, tracked at objectui#9113
    page drawer — wrong, but the record opened ⭐ navigates to the record page
    new_window drawer new tab at the app-prefixed href
    none drawer inert, by design

    ⭐ Leg 2 exists because leg 1 alone made it worse. Forwarding the key made mode: 'page' reachable and inert — useNavigationOverlay's page branch returns with no fallback when handed no onNavigate, and the wrapper drops the host onRowClick, which is this card's own premise. Both carriers were empty at once, so an authored page went 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 the case 'gantt' branch over baseProps — 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:retriage and not pm:queue

    The card's own scope — "a gantt view's authored navigation reaches 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 / popover all collapsing into the drawer across gantt, kanban and calendar — is pre-existing, unchanged by this PR in either leg, and repairing it means touching ObjectKanban and ObjectCalendar too.

    ⇒ 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 onRowClick was 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 dropped onRowClick is not needed even for page, 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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpriority:p2

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions