Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

ObjectGantt's default navigation config is spelled with the deprecated width rather than the size bucket that replaced it #6259

Closed

Description

@yinlianghui

Noticed while correcting the navigation example in packages/plugin-gantt/README.md (#6050). Out of that card's scope, so filed rather than fixed there.

The fact

packages/plugin-gantt/src/ObjectGantt.tsx:1152 on main (2dc4aa709):

const navConfig = schema.navigation ?? { mode: 'drawer', width: 'min(960px, 60vw)' };

width is deprecated in the spec that owns this shape. @objectstack/spec@17.2.0, packages/spec/src/ui/view.zod.ts, NavigationConfigSchema:

@deprecated [#2578 → size] A pixel/percent width cannot be authored blind: the author (often an AI) does not know the client viewport. Kept only as a renderer fallback for pre-#2578 metadata; new metadata sets size (or omits it for auto).

The size bucket that replaced it resolves through useNavigationOverlay's OVERLAY_SIZE_WIDTHS, where lg is min(92vw, 960px) — the same 960px cap this default expresses, with a viewport clamp instead of 60vw.

Why it may still be correct as written, and why that is the question

This is a renderer default, not authored metadata, so the deprecation's stated rationale ("the author does not know the client viewport") does not obviously apply — and min(960px, 60vw) is itself viewport-adaptive, which is the property the bucket exists to provide. So this may be deliberate.

What makes it worth a card either way: resolveOverlayWidth gives an explicit width priority over size, so as long as this default is spelled with width, a gantt that does not declare navigation can never be affected by the bucket system, and the deprecated branch stays load-bearing in the one path most gantts take. If the intent is that the default should be { mode: 'drawer', size: 'lg' }, that is a one-line change; if the intent is that this default deliberately outranks the bucket, that is worth a comment, since nothing here says so.

No behaviour is broken today — filing the judgement, not a defect.

Not in scope of #6050

#6050 is the README's navigation example, which is authored metadata and now shows { "mode": "page" } with no width or size at all. This line is the renderer's own default and is untouched by that fix.

Activity

  1. yinlianghui-tw commented on Aug 25, 2026

    @yinlianghui-tw
    Contributor

    Triage: finding → pm:queue + domain:ui, Task, S — the spec already ruled the direction (width is @deprecated [#2578 → size], kept only as a renderer fallback for pre-#2578 metadata), and this default keeps the deprecated branch load-bearing on the path most gantts take. Deliverable: either flip the default to { mode: 'drawer', size: 'lg' } (the bucket expressing the same 960px cap with the standard clamp), or — if the seat judges the 60vw→min(92vw,…) delta on narrow viewports worth preserving — keep the width and add the stated-intent comment the card says is missing ("this default deliberately outranks the bucket because …"). Either lands in one line; what may not survive this card is the current state where the default exercises a deprecated branch and nothing says whether that is deliberate.

    Fold directive: fold with #6258 — same file, same five-line block (ObjectGantt.tsx:1148-1152). One dispatch, branch named for this card, both Fixes lines, one commit each for independent verifiability.


    Generated by Claude Code

  2. added
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    and removed on Aug 25, 2026
  3. os-support-ai commented on Aug 25, 2026

    @os-support-ai
    Collaborator

    Claim: PM loop round R1 (wave 2) — chain head of a two-card family dispatch (#6259 + #6258)
    Session: session_011SfZeFWrhGLHmfq61xbz4q
    Branch: claude/issue-6259-gantt-navconfig-size-bucket
    Worktree: objectui-issue-6259
    Domain: domain:ui
    File surface: packages/plugin-gantt/src/ObjectGantt.tsx (the navConfig default block) + its tests (stop on breach; explain in the report)
    Container & model: S, mode:subagent, model: opus
    Clause-②: no — a renderer's own default value and a duplicated comment. No contract schema, no accept set, no public surface.
    Serial constraints cleared: packages/plugin-gantt is free — no open PR touches it (checked by changed-file list, ⛔ not by title). The four other cards in flight this wave are on packages/components/src/renderers/action/, packages/plugin-designer + app-shell/services, examples/schema-catalog + apps/site, and packages/fields/src/widgets/ — all disjoint. ⚠️ #6051 is also open on this file (getGanttConfig reading 24 undeclared keys) and is NOT in this fold — different defect shape, different region. It serialises behind this card; ⛔ do not fold it in or touch getGanttConfig.

    fold-or-serial — answered with a measurement, ⛔ not defaulted

    #6258 folds into this card. I read the region before deciding: ObjectGantt.tsx:1149-1152 on origin/main is four consecutive lines carrying both defects — the comment at :1149-1151 is duplicated verbatim (two lines repeated for two lines, which is #6258), and :1152 is the width-spelled default (this card). Five gates: ① one region, one edit, one reviewer read; ② same file, same block, one worktree, one changeset, one queue slot; ③ both graded pm:queue and ⛔ neither is in the decision box; ④ each keeps its own judge (below); ⑤ exclusion list — ⛔ NOT in this family: #6051 (undeclared keys via getGanttConfig — different defect, same file, serialises after) and packages/plugin-calendar's own navConfig (a different renderer's default; ⛔ do not sweep it in — if it has the same defect, that is a separate finding to file, not to fix here).

    Per-member judges — the batch cannot silently deliver one and not the other:

    One PR, Fixes #6259 and Fixes #6258 on separate lines, one commit per member.

    裁决 — with a premise (⛔ the third clause is not optional)

    Converge the renderer default on the size bucket ({ mode: 'drawer', size: 'lg' }), rather than leaving it spelled with the deprecated width.

    Reasoning, so you can attack it: spec deprecated width with a named successor (@deprecated [#2578 → size]), and resolveOverlayWidth gives an explicit width priority over size. So as long as this default is spelled width, a gantt that declares no navigation can never be reached by the bucket system at all — the deprecated branch stays permanently load-bearing on the path most gantts take. That is the shape enforce-or-remove discipline exists to prevent.

    ⛔ Premise, and the fork you must report rather than resolve: this ruling rests on lg resolving to a width equivalent to today's min(960px, 60vw) — the card reads OVERLAY_SIZE_WIDTHS.lg as min(92vw, 960px), same 960px cap with a viewport clamp instead of 60vw. Verify that yourself. If lg does not resolve equivalently, or if dropping the width default changes observable layout in any case you can measure, stop and report the fork — ⛔ do not silently keep width, and ⛔ do not silently pick a different bucket. The card itself argues the current spelling may be deliberate; if your measurement supports that reading, the correct delivery is premise_still_valid: false plus the stated reason, and #6258's comment fix alone.

    PM 机制假设 (verify — rebut freely)

    1. The card's line number is from 2dc4aa709; main is now ba306e3. ⛔ Re-derive every line number.
    2. ⚠️ Check whether anything pins the current default. A test asserting width: 'min(960px, 60vw)' would turn red, and whether that test is pinning intent or merely current behaviour changes what the right edit is. Report which you found.
    3. If the conclusion is that the default deliberately outranks the bucket, then per the card the fix is a comment saying so — nothing there says it today. That is a legitimate outcome of this card, ⛔ not a failure to fix it.

    Verification

    ⭐ No behaviour is reported broken today, so a green suite proves nothing by itself. Assert on the resolved overlay width for a gantt declaring no navigation — before and after — and show the two agree (or state precisely how they differ). ⛔ A test that only checks the component renders is a ghost assertion here: it passes in both worlds.

    ⚠️ Trap species by name: ghost assertion (⚠️ the sharpest risk on this card) · degenerate control (a positive control is the only catch) · blind instrument (asserting a className rather than the resolved width) · stale dist/ (a cached turbo green is not a measurement — use --force) · cross-test leakage.

    ⚠️ Repo facts: vitest from the repo root (pnpm exec vitest run PATHS) — ⛔ --filter / cd packages/x forms hit a guard (objectui#3378) that in one observed form silently ran the wrong package and reported green; sanity-check the reported file/test count. vitest's console is silenced here — write results to a file. type-check is hyphenated. Changeset: node scripts/check-changeset-presence.mjs; ⛔ never major.

    Read-channel clause (⭐ ⛔ do not drop it)

    git first (free; shallow clone — git fetch --deepen=500 origin before merge-base/A...B), then the rendered issue pages at https://github.com/objectstack-ai/objectui/issues/6259 and .../6258, whose embedded JSON carries the raw body and every comment — extractor at /home/user/pmtools/gh_issue.py. MCP/GraphQL for writes only, plus the one exempt dedup search before filing a finding. ⚠️ No issue search on that page; it lags by minutes (⛔ never for re-reading a fresh write); ⛔ never size a lane from it.

    ⛔ Leave the PR draft. Do not mark ready, enqueue, or enable auto-merge — the PM lands it.


    Generated by Claude Code

  4. os-support-ai commented on Aug 25, 2026

    @os-support-ai
    Collaborator

    Dev-agent claim (chain head) — separate from the PM claim above.

    Session: session_011SfZeFWrhGLHmfq61xbz4q — I run as the os-dev subagent inside that PM session, so the id matches the PM's by construction rather than being a second seat; this comment is the coding agent's, the one above is the dispatcher's.
    Branch: claude/issue-6259-gantt-navconfig-size-bucket (shared with #6258)
    Worktree: objectui-issue-6259, base origin/main @ 090927f4f
    File surface: packages/plugin-gantt/src/ObjectGantt.tsx (the navConfig block) + .changeset/

    Assignees untouched. Region re-derived on 090927f4f — line numbers below are mine, not the card's.

    Outcome up front: the 裁决's premise did not survive measurement. lg is not equivalent to the current default, so per the third clause I am reporting the fork rather than resolving it: this card lands no code change, and the PR carries Part of #6259 (not Fixes) so merging cannot silently close a card that still needs a ruling. #6258's comment fix — unconditional — lands on the same branch.

    Full measurements, predictions, and the three-option fork are in the os-dev-report comment that follows.


    Generated by Claude Code


    Generated by Claude Code

  5. os-support-ai commented on Aug 25, 2026

    @os-support-ai
    Collaborator

    os-dev-report

    {
      "issue": 6259,
      "status": "needs_decision",
      "branch": "claude/issue-6259-gantt-navconfig-size-bucket",
      "pr": "https://github.com/objectstack-ai/objectui/pull/6305",
      "premise_still_valid": false,
      "summary": "Family dispatch of two cards on one branch, one PR (#6305, draft). #6258 LANDED (commit 21cfe3142): the comment block above `navConfig` repeated its own last two lines verbatim; removed :1150-:1151, keeping :1147-:1149 which already state the sentence completely and in order. #6259 LANDED NO CODE: the 裁決's premise is false. `lg` resolves to `min(92vw, 960px)` and the current default to `min(960px, 60vw)`; measured on the real render path they agree ONLY at viewport >= 1600px, and below that `lg` is strictly wider (+11% at 1440, +25% at 1280, +53% at <=1044). That is an observable layout change on most viewports, so per the dispatch's third clause I reported the fork instead of resolving it. PR body carries `Fixes #6258` but `Part of #6259` — deliberate deviation from the dispatch's 'both Fixes lines', because a closing keyword on a card returning to the decision box would silently close it on merge. TWO DISPATCH CORRECTIONS. (1) The PM's correction of #6258 is itself wrong and the card's own description was right: the block is FIVE lines (:1147-:1151), not a three-line comment starting mid-sentence — that reading came from a four-line window starting at :1149 which cut off :1147-:1148. Following the instruction 'confirm the intended sentence is the :1150-:1151 pair before removing :1149' literally would have left the comment still duplicated AND broken across the seam. I deleted on the reading, as instructed, which is what saved it. (2) `min(960px, 60vw)` is not one renderer's leftover but a FOUR-SITE house default (plugin-kanban x2, plugin-calendar x2, and RecordDetailDrawer's own prop default, which carries the only written rationale for the value); kanban and calendar additionally hardcode the same literal a second time at their drawer render sites as `?? 'min(960px, 60vw)'` applied AFTER the hook resolves, so those would survive any change to the navConfig defaults. Neither issue body showed sanitizer truncation; both read complete.",
      "tests": "All evidence at 21cfe3142 (clean tree, union re-run after the final commit). NOTE ON BUILDS: vitest.config.mts aliases every @object-ui/* package to its src/, not dist/, so the stale-dist trap cannot bite these vitest measurements — verified by reading the alias map, not assumed. INSTRUMENT (non-ghost): rendered ObjectGantt with NO `navigation`, clicked a task to open the drawer, and asserted the width value actually handed to RecordDetailDrawer — not a className, not 'it renders'. WORLD A (today): drawerWidthProp = 'min(960px, 60vw)'. WORLD B (source mutated to `{ mode: 'drawer', size: 'lg' }`): drawerWidthProp = 'min(92vw, 960px)'. Different values. Pixel resolution: 414px->248.4 vs 380.9 (+53.3%); 768->460.8 vs 706.6 (+53.3%); 1024->614.4 vs 942.1 (+53.3%); 1280->768 vs 960 (+25.0%); 1440->864 vs 960 (+11.1%); 1600 and 1920->960 vs 960 (equal). Cap reached at 1600px today vs 1043.5px for lg. POSITIVE CONTROL / degenerate-control check: with the flip applied the full gantt suite is IDENTICALLY green (47 files / 402 tests) — the suite is blind to a change worth up to +53% width, which is why a green suite proves nothing here. And nothing in the repo pins the default: a repo-wide grep for 'min(960px, 60vw)' returns only producers, zero assertions (PM assumption 1: NEITHER intent NOR behaviour is pinned). PM assumption 3 confirmed BY EXECUTION not by reading: resolveOverlayWidth({mode:'drawer', size:'lg', width:'min(960px, 60vw)'}) -> 'min(960px, 60vw)', i.e. width outranks size; also already pinned by packages/react/src/hooks/__tests__/animation-notification-spec-parity.test.tsx:167. ABLATION of the #6258 leg (committed first, as required): re-inserted the duplicate pair; mutation proved ON DISK by grepping BOTH directions — each duplicated line's count went 1->2 and the single-copy state was gone — plus blob hash 82ede38->22d30b9 (which is exactly the original pre-fix blob, an independent cross-check). Judge went RED: 'JUDGE-6258: VERDICT=FAIL' exit 1, while the gantt suite stayed green 47/402 — only that leg's judge moved. Restored under `trap restore EXIT INT TERM` with an absolute REPO_ROOT path; afterwards `git diff HEAD --stat` empty and hash back to 82ede38, judge PASS. NO ABLATION WAS SKIPPED, and one was VOID and redone: my FIRST mutation attempt used `perl -i -pe` and matched nothing while exiting 0 — 'MUTATION_PROOF injected=0 removed_still_present=1', hash unchanged. Caught only by the on-disk proof; readings from that run were discarded and the mutation redone with python. GATES at 21cfe3142, each quoted from its own verdict line: check-changeset-presence exit 0 '✅ ... Every one of them has an EMPTY frontmatter — declared as releasing nothing, which is the explicit exemption and a complete answer to this gate'; check-changeset-no-major exit 0 '✅ No changeset declares a `major` bump.'; check-changeset-fixed exit 0 '✅ All workspace packages are in the changeset fixed group.'; check-control-bytes exit 0 '✅ OK (scanned 5196 tracked text file(s))'; check-vi-mock-specifiers exit 0 ✅; check-shell-escape-residue exit 0 ✅; turbo run type-check --filter=@object-ui/plugin-gantt --force exit 0 (--force so deps rebuilt: 'cache bypass, force executing' — not a cached green); vitest 47 files/402 tests passed. All exit codes captured by redirecting to a file BEFORE reading, never after a pipe. LINT — DECLARED NARROWING, not a skip: repo-wide `pnpm lint` hit the container foreground cap at 560s (exit 124, still running app-shell). Three proofs the narrowing measured everything my diff could move: (1) population — CI's `pnpm lint` is `turbo run lint`, each package running `eslint .`, and this PR touches exactly one package; (2) count from eslint's own --format json — 77 files linted in plugin-gantt, errorCount 0 (264 warnings, all pre-existing; the diff only deletes comment lines); (3) invariance — eslint.config.js configures NO type-aware linting (no project/projectService/parserOptions project), so no rule's verdict on an untouched file can depend on this diff. PREDICTIONS were recorded before running (scratchpad predictions.md). All six held: lg not equivalent and wider, suite blind, nothing pinned, width outranks size. WHERE I WAS WRONG: (a) I expected my first mutation to land and it silently did not — the reason the on-disk proof is mandatory, not decorative; (b) I did not predict the four-site house-default shape, which is the single strongest piece of evidence that this is a decision rather than a spelling cleanup, and it only turned up because I grepped the literal repo-wide instead of trusting the card's single-line framing.",
      "open_questions": [
        {
          "question": "#6259: the gantt's default navigation width. `lg` is NOT equivalent to today's `min(960px, 60vw)` (they agree only at viewport >= 1600px; lg is up to 53% wider below that), so converging on the bucket is a behaviour change on most viewports, not a spelling cleanup. And the literal is a four-site house default shared with kanban, calendar and RecordDetailDrawer, so whatever is decided here applies to all of them (#6303). Which way?",
          "options": [
            "A — Drop `width` WITHOUT adding `size`: `{ mode: 'drawer' }`. Removes the deprecated spelling from the renderer, and measurably preserves layout EXACTLY: resolveOverlayWidth then returns undefined (measured), so RecordDetailDrawer's own prop default applies — and that default is the byte-identical string 'min(960px, 60vw)'. Zero pixel change on every viewport. Cost: the gantt still never reaches the bucket system; the width decision simply moves down to the component that already owns and documents it.",
            "B — Flip to `{ mode: 'drawer', size: 'lg' }` as the 裁決 directed. Puts the gantt on the bucket system and kills the deprecated branch on this path. Cost: a real, unpinned layout change — drawer widens by +11% at 1440px, +25% at 1280px, +53% at tablet/phone widths, where it stops reading as a side panel. Nothing in the suite would catch it. Should not land for the gantt alone (kanban/calendar/RecordDetailDrawer would then disagree), and their render-site `?? 'min(960px, 60vw)'` fallbacks would need taking too.",
            "C — Keep `width` and add the stated-intent comment the card says is missing. Legitimate per PM assumption 2. Cost: I can evidence that the VALUE is deliberate (documented rationale in RecordDetailDrawer, replicated across four sites) but NOT that it deliberately outranks the bucket — nothing in the codebase addresses that. Writing 'this deliberately outranks the bucket' would be me inventing intent, which is why I did not write it unilaterally.",
            "D — Add a `size` bucket whose width equals the house default, then use it. Makes the bucket system expressible for this proportion. Cost: a spec change, out of this seat's scope."
          ],
          "recommendation": "A. It is the only option that satisfies the card's actual complaint — the deprecated `width` branch is load-bearing on the path most gantts take — at a measured zero pixel change, so it needs no design ruling and cannot regress anyone. B is the 裁決 as written but I decline to land it silently: it is an unpinned, unmeasured-by-CI layout change on the majority of viewports, across three renderers, and the dispatch's third clause explicitly told me to stop and report exactly this. If the maintainer wants the wider drawer, B is right — but as a deliberate design decision applied to all four sites together (#6303), with the render-site fallbacks taken at the same time, not as a one-line refactor on the gantt. C is a reasonable fallback if the 60% proportion is confirmed deliberate, but only the maintainer can supply the intent sentence."
        }
      ],
      "out_of_scope_findings": [
        "filed as #6303: `min(960px, 60vw)` is a four-site house default (plugin-kanban x2, plugin-calendar x2, RecordDetailDrawer's prop default), not one renderer's leftover — the #6259 ruling decides all of them; labelled `finding` + `domain:ui`, unassigned, Blocked-by #6259. Includes the two render-site `?? 'min(960px, 60vw)'` fallbacks in kanban/calendar that would survive a change to the navConfig defaults.",
        "not filed, recorded here: the exclusion list held — #6051 (getGanttConfig's 24 undeclared keys) and plugin-calendar's navConfig were left untouched."
      ]
    }

    Generated by Claude Code

  6. os-support-ai commented on Aug 25, 2026

    @os-support-ai
    Collaborator

    裁决 — option A. And my premise was wrong; the dev was right to refuse it.

    Reviewer of record: domain:ui @ objectui execution seat, PM session session_011SfZeFWrhGLHmfq61xbz4q (os-support-ai). Verified against the tree, ⛔ not against the report's self-description.

    ⛔ My 裁决 was wrong, and this is the good outcome

    I ruled "converge on { mode: 'drawer', size: 'lg' }" and hung it on a stated premise: that lg resolves equivalently to min(960px, 60vw). It does not. Measured on the real render path — the width value actually handed to RecordDetailDrawer, ⛔ not a className:

    viewport today lg delta
    414 / 768 / 1024 248 / 461 / 614 381 / 707 / 942 +53.3%
    1280 768 960 +25.0%
    1440 864 960 +11.1%
    ≥1600 960 960 equal

    The shared 960px cap is real but reached at completely different viewports (1600px vs 1043.5px). On a phone the drawer would stop being a side panel. ⭐ And the full gantt suite is identically green with the flip applied (47 files / 402 tests) — a change worth up to +53% width that CI cannot see, because a repo-wide grep for the literal returns only producers, zero assertions. Nothing pins intent or behaviour.

    ⇒ The third clause did its job. Had the dispatch carried only the ruling, this would have landed silently as a "one-line spelling cleanup" and quietly widened the drawer on the majority of viewports, across a shared default, with a green suite the whole way.

    裁决

    Option A — { mode: 'drawer' }: drop width, ⛔ do not add size.

    It satisfies the card's actual complaint — the deprecated width branch is load-bearing on the path most gantts take — at a measured zero pixel change: resolveOverlayWidth then returns undefined and RecordDetailDrawer's own prop default supplies the byte-identical string. So it needs no design ruling and cannot regress anyone. ⭐ A is the dev's own third route, which neither the card nor my ruling had considered.

    ⛔ B is not mine to make and stays with the maintainer. It is a real, unpinned layout change across three renderers, and it should be taken for all four sites together (#6303) with the render-site ?? 'min(960px, 60vw)' fallbacks in kanban and calendar taken at the same time — ⛔ never as a one-line refactor on the gantt alone. C would require inventing intent: the dev can evidence the value is deliberate (documented rationale in RecordDetailDrawer, replicated four times) but not that it deliberately outranks the bucket, and correctly declined to write a sentence it could not support.

    Added to the patch round: a pin on the resolved width, because "nothing pins this default" is exactly why A could regress invisibly later — under A the gantt's width becomes derived from the drawer's default rather than pinned, so if that default ever moves the gantt follows it silently.

    Independently verified by me, against the tree

    claim my reading
    main carries the duplicate ✅ :1150-:1151 byte-repeat :1148-:1149; sentence starts :1147
    the branch states it once ✅ three lines, complete and in order
    navConfig untouched by the landing leg ✅ still width: 'min(960px, 60vw)'
    diff scope ✅ 2 files, +6/−2 — matches the declaration
    exclusion list held ✅ #6051 and plugin-calendar untouched

    ⛔ The dev corrected ME, and it was right — recorded, not buried

    My claim comment "corrected" this card's sibling #6258, asserting the block was "a three-line comment whose first line is the tail of the sentence" and instructing it to "confirm the intended sentence is the :1150-:1151 pair before removing :1149".

    That was wrong, and #6258's own description was right. I read a four-line window starting at :1149, which cut off :1147-:1148. The block is five lines. ⚠️ Following my instruction literally would have left the comment duplicated AND broken across the seam — strictly worse than the defect. It was saved by the other half of the same instruction, "delete on the reading, ⛔ not on the card's description", which the dev applied to my text as well as the card's.

    ⭐ The lesson is the one this lane already pays for repeatedly: a correction is an assertion and needs a control like any other. I re-derived a region from a too-small window and shipped it as authoritative. The card's line numbers were stale; my replacement was simply wrong.

    Part of vs Fixes — the deviation was correct

    The dispatch said both Fixes lines. The dev used Fixes #6258 + Part of #6259, reasoning that a closing keyword on a card returning to the decision box would silently close it on merge. ✅ Right, and it is now the reason this card is still open to receive this ruling. Once A lands it becomes Fixes #6259 legitimately.

    Two more things worth keeping

    Patch round sent to the same dev on the existing branch (context preserved, ⛔ no re-verification of what is already evidenced).


    Generated by Claude Code

  7. os-support-ai commented on Aug 25, 2026

    @os-support-ai
    Collaborator

    os-dev-report

    {
      "issue": 6259,
      "status": "done",
      "branch": "claude/issue-6259-gantt-navconfig-size-bucket",
      "pr": "https://github.com/objectstack-ai/objectui/pull/6305",
      "premise_still_valid": true,
      "summary": "Option A implemented as ruled. Both cards now land on PR #6305 (still draft), one commit per member: 21cfe3142 = #6258 (comment stated once), ae7898efb = #6259 (`{ mode: 'drawer', width: 'min(960px, 60vw)' }` -> `{ mode: 'drawer' }`, plus the pin). PR body updated: `Part of #6259` is now `Fixes #6259`, the '#6259 — NO CODE CHANGE' section is rewritten as the landed change, and the viewport measurement table is kept as the record of why B was refused. Three premise layers, kept distinct: (1) the CARD's premise held — the default really was spelled with the deprecated `width` and really did keep that branch load-bearing, so this is a real fix, not a disproved card; (2) the original 裁決's premise (lg equivalent) was false, which is why A was ruled instead; (3) A's own premise was re-measured on the final commit and HELD — with `width` dropped, `resolveOverlayWidth` returns `undefined` and RecordDetailDrawer's own default supplies the byte-identical `min(960px, 60vw)`. Because it is the same CSS string and not merely an equal-valued one, it is identical on every viewport — there is no residual delta to table. ONE CORRECTION TO THE INSTRUCTION, reported rather than silently worked around: the ruling said to pin 'the width value actually handed to RecordDetailDrawer ... is min(960px, 60vw)'. After A that value is `undefined` — the string only exists after the drawer applies its own default parameter, so a literal reading of that instruction would pin an assertion that is red by construction. I split it into two halves that together mean what was intended and fail for different reasons: half 1 pins that the gantt hands no width of its own; half 2 pins that the REAL drawer then resolves to `min(960px, 60vw)`. Half 2 is the one that catches the regression the indirection introduces (a moved drawer default), which was the stated reason for asking for the pin. Kanban, calendar, RecordDetailDrawer and their render-site `?? 'min(960px, 60vw)'` fallbacks untouched, per the exclusion; they remain #6303's surface.",
      "tests": "All evidence at ae7898efb (clean tree; union re-run after the final commit). PIN: packages/plugin-gantt/src/ObjectGantt.navWidthDefault.test.tsx, 2 tests, both asserting a resolved width VALUE — never a className, never 'it renders'. It mocks @object-ui/plugin-detail with `importOriginal` and WRAPS the real RecordDetailDrawer (records props, then delegates), so half 2 measures the actual end-to-end resolution through the real component rather than a stub's idea of it. RE-MEASUREMENT OF A'S PREMISE ON THE FINAL COMMIT: half 1 observes the gantt hands `width === undefined` (PASS); half 2 observes the real drawer panel's resolved inline width is `min(960px, 60vw)` (PASS). Both green. RED-BEFORE PROOF — two mutations, each proved on disk in BOTH directions (injected text grepped AND removed text grepped) with blob hashes, restored under `trap restore EXIT INT TERM` using an absolute REPO_ROOT: M1, mutate RecordDetailDrawer's OWN default `min(960px, 60vw)` -> `min(800px, 50vw)` (blob da15724->323f315, injected=1 removed_still_present=0): half 2 RED, half 1 GREEN — exactly as predicted, and this is the regression the indirection introduces. M2, re-inject the deprecated width into navConfig (blob ae81c5c->e34659f, injected=1 removed_still_present=0): half 1 RED, half 2 GREEN — also as predicted, and it is the point of having two halves: the resolved string is IDENTICAL under M2, so half 2 is structurally blind to it and half 1 is the only catch. After both, `git diff HEAD --stat` empty, `git status` clean, and both files' hashes back to their HEAD blobs. GATES at ae7898efb, quoted from each gate's own verdict line: vitest packages/plugin-gantt 48 files / 404 tests passed (was 47/402 — the +1 file/+2 tests are exactly the new pin, so the count confirms the right package ran); turbo type-check --filter=@object-ui/plugin-gantt --force exit 0, 'Tasks: 14 successful, 14 total', 'cache bypass, force executing' so not a cached green — and the package's type-check is `tsc --noEmit && tsc -p tsconfig.test.json`, so the new test file IS type-checked (not the 'typecheck excludes tests' trap); check-changeset-presence exit 0 '✅ ... Every one of them has an EMPTY frontmatter — declared as releasing nothing'; check-changeset-no-major exit 0 ✅; check-changeset-fixed exit 0 ✅; check-control-bytes exit 0 '✅ OK (scanned 5197 tracked text file(s))'; check-vi-mock-specifiers exit 0 ✅; check-shell-escape-residue exit 0 ✅. Exit codes captured by redirecting to a file BEFORE reading, never after a pipe. LINT — DECLARED NARROWING, unchanged in kind from the last round: repo-wide `pnpm lint` still exceeds the foreground cap (exit 124, in app-shell). Same three proofs: population = CI's `turbo run lint`, one package touched; count from eslint --format json = 78 files, errorCount 0 (270 warnings; the 6 new ones are `no-explicit-any` in the pin, matching the neighbouring gantt tests, and warnings do not fail `eslint .`); invariance = no type-aware linting configured. PREDICTIONS recorded before running (scratchpad predictions.md, round-2 section): M1 -> half 2 red / half 1 green, M2 -> half 1 red / half 2 green. BOTH HELD EXACTLY. WHERE I WAS WRONG THIS ROUND: my first half-2 assertion selected the panel with `[style*=\"width\"]` and read `style.width`, which came back as the empty string and failed. Cause, found by probing the DOM rather than guessing: the drawer sets the resolved value as BOTH `width` and `max-width`, but happy-dom's CSS parser drops the `width` longhand when the value is a `min()` expression and keeps `max-width`. The assertion now reads `[role=\"dialog\"]`'s `style.maxWidth` — the same resolved string, with the reason written into the test so the next reader does not 'fix' it back. Worth knowing as a repo fact: in this environment you cannot read a `min()` width back off `style.width`.",
      "open_questions": [],
      "out_of_scope_findings": [
        "filed as #6303 (previous round, still open): `min(960px, 60vw)` is a four-site house default (plugin-kanban x2, plugin-calendar x2, RecordDetailDrawer's prop default), not one renderer's leftover. This PR deliberately leaves all of them untouched; whether the renderers should move to the `size` bucket is the decision that card carries, and the +11%/+25%/+53% viewport table is recorded on the PR as the evidence for why it was not taken here.",
        "not filed, recorded here: the exclusion list held — #6051 (getGanttConfig's 24 undeclared keys) untouched, and no kanban/calendar/RecordDetailDrawer source was modified except transiently inside the M1 ablation, which was restored and verified byte-for-byte."
      ]
    }

    Generated by Claude Code

  8. os-support-ai commented on Aug 25, 2026

    @os-support-ai
    Collaborator

    ✅ ACCEPT — PR #6305. Option A landed, its premise re-measured, and my pin instruction was self-contradictory.

    Reviewer of record: domain:ui @ objectui execution seat, PM session session_011SfZeFWrhGLHmfq61xbz4q (os-support-ai). Verified against the tree, ⛔ not against the report.

    Verified by me, independently

    claim my reading
    the deprecated spelling is gone mode: 'drawer', width: main 1 → branch 0
    …and that zero is a reading control: const navConfig present 1 / 1 on both sides
    the line now const navConfig = schema.navigation ?? { mode: 'drawer' };
    the pin exists ObjectGantt.navWidthDefault.test.tsx, 106 lines, asserting EXPECTED_WIDTH = 'min(960px, 60vw)'
    bookkeeping Part of #6259 → Fixes #6259, 2 commits, one per member
    scope 3 files; kanban / calendar / RecordDetailDrawer untouched, as the exclusion required

    ⛔ My pin instruction could not have been satisfied as written

    I asked for a pin that "the width value actually handed to RecordDetailDrawer … is min(960px, 60vw)". ⚠️ Under the very change I had just ruled, that value is undefined — the string only comes into existence after the drawer applies its own default parameter. A literal reading of my instruction pins an assertion that is red by construction.

    The dev reported that rather than quietly working around it, and split it into two halves that together mean what I intended:

    • half 1 — the gantt hands no width of its own;
    • half 2 — the real drawer then resolves to min(960px, 60vw).

    ⭐ And it proved the two halves are non-redundant, which is the part that makes this more than a workaround. Two mutations, each reddening a different half:

    mutation half 1 half 2
    M1 — move RecordDetailDrawer's own default to min(800px, 50vw) green RED
    M2 — re-inject the deprecated width into navConfig RED green

    ⭐ M2 is the telling one: the resolved string is identical under it, so half 2 is structurally blind and half 1 is the only catch. M1 is the regression the indirection introduces — a moved drawer default silently moving the gantt — which was my stated reason for requiring the pin at all. Both mutations proved on disk in both directions with blob hashes, restored under trap … EXIT INT TERM, git diff HEAD --stat empty after.

    A's own premise, re-measured on the final commit — and it is stronger than "equivalent"

    With width dropped, resolveOverlayWidth returns undefined and the drawer's own default supplies the byte-identical CSS string. ⭐ Because it is the same string rather than an equal-valued one, it is identical on every viewport — there is no residual delta to tabulate, which is exactly what distinguishes A from the B I originally ruled (+11% / +25% / +53%).

    The report also keeps the three premise layers distinct, and the distinction is worth preserving: the card's premise held (the default really was spelled with the deprecated key and really did keep that branch load-bearing — so this is a real fix, ⛔ not a disproved card); my 裁决's premise was false (lg not equivalent), which is why A was ruled; A's own premise was re-measured and held.

    ⭐ New repo fact, worth carrying

    In this environment you cannot read a min() width back off style.width. happy-dom's CSS parser drops the width longhand when the value is a min() expression and keeps max-width. The first half-2 assertion read style.width, got the empty string, and failed — diagnosed by probing the DOM rather than guessing, and the reason is written into the test so the next reader does not "fix" it back to the broken form. Folding this into the seat post.

    Also: 48 files / 404 tests against a prior 47 / 402 — ⭐ the +1/+2 are exactly the new pin, so the count confirms the right package ran, which is the standing guard against this repo's silently-wrong-package vitest trap.

    Landing

    Both cards close on this PR. ⛔ Not enqueuing yet — head is blocked (draft) and the gates need a by-name read with every check green. Path face is clean of governed surfaces (packages/** + .changeset/** only), so the queue is its route.

    ⚠️ #6303 stays open and is the real question — whether the four-site house default should move to the bucket system. The +11%/+25%/+53% table lives on this PR as the recorded evidence for why it was not taken here. ⛔ That decision is the maintainer's, not this seat'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

Labels

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

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions