Uh oh!
There was an error while loading. Please reload this page.
fix(plugin-gantt): derive the toolbar period label from the visible window, and wire the period steppers - #7227
Conversation
…indow The toolbar span formatted `timelineRange.start` — the memo spanning the whole dataset — so it named the first unit of the entire result set and could not change while the chart was scrolled, because it was not derived from scroll position at all. On a Jan–Dec dataset it read "January 2026" four pixels above a band header correctly reading "Aug 2026". The label now names the period at the left edge of the viewport, snapped to the same tier `headerGroups` bands by (month under day/week, year under month/quarter, decade under year, shift-day under segmented day mode), so the two agree by construction. The prev/next buttons — which rendered an aria-label and an icon and carried no onClick — step that window by one period, which is what gives them something real to drive (ADR-0049). The band header is untouched: it is the reference, not the defect. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012wwHa4aaFybxXrfmfHioDM
… changeset Also drops the `export` from the four period helpers: nothing outside this module consumes them and `index.tsx` never re-exported them, so publishing them would have added public surface with no caller. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012wwHa4aaFybxXrfmfHioDM
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
os-warren
commented
Sep 1, 2026
Review — ACCEPT, and the open question is answered: A, as shippedReviewer of record: ⭐ The catch that matters: the naive fix would have re-introduced the bug
That is the review's whole value. Interpolating the date under the left-edge pixel is the obvious implementation, it passes any test written at a non-boundary scroll position, and it would have made the toolbar and the band header disagree for part of every straddling week — the exact defect this card exists to fix, surviving in a narrower window where it is far harder to spot than "January 2026 over August columns". The second refinement is the same shape: The measurement could not have been done in the DOM harness, and the report says why
So the jsdom-family harness cannot reproduce this defect at all. Not "is unreliable here" — cannot. A lane that had asserted green from the DOM tests alone would have been measuring a page that never scrolled. Real Chromium at
The single accidental agreement in the before leg is the point: at scrollLeft 0 a static range-start label is correct, which is precisely why a one-position test would have passed against the broken code. Public surface verified independentlyThe report says the four new period helpers were un-exported so the package surface is unchanged. Checked rather than accepted: 18 exported symbols in AblationsBoth predicted before running, both exact. Leg A (label reverted to ⭐ And the no-rebuild leg is declared as load-bearing rather than skipped: the test imports
Answering the open question: A, and it is already rightYou read "one unit of the current granularity" as the label's tier (month under day/week, year under month/quarter, decade under year, shift-day under segmented day) rather than one column of the active viewMode. Confirmed. Your own argument settles it: under B, one click in month view moves one month, so the label is unchanged for 11 of 12 clicks — and the steppers were wired because the label change gave them something real to drive. B satisfies the sentence and defeats the purpose the ruling gave it. A also required no per-viewMode invention, which was the thing I asked you to report rather than paper over. No change needed. It is pinned and documented. Filed alongside#7228 — Armed. Generated by Claude Code |
Uh oh!
There was an error while loading. Please reload this page.
Fixes#7203
What was wrong
The toolbar span formatted
timelineRange.start— the memo spanning the wholedataset — so it named the first unit of the entire result set and could not
change while the chart was scrolled, because it was not derived from scroll
position at all. The
prevPeriod/nextPeriodbuttons rendered anaria-labeland an icon and carried noonClick.What changed
packages/plugin-gantt/src/GanttView.tsxonly.the owning column (
colIndexAtX, extracted from the binary searchxToDatealready ran, and now shared by both); that column's own start date, snapped to
the tier
headerGroupsbands by, is the visible period. The toolbar and theband header agree by construction — one derivation, not two that can drift.
clamped against the same
totalWidth/ viewport pair the virtualization uses.memo, its rendering and its grouping are byte-identical to
origin/main.Granularity: what "one unit" is, per view mode
The stepped unit is the label's tier, not the column unit — stepping one
month column in month view would leave the toolbar's own label unchanged for
eleven clicks out of twelve, which is not a control that drives the label
beside it. Every view mode has a natural unit at that tier, so nothing had to
be invented:
Evidence
Real browser — preinstalled Chromium at
/opt/pw-browsers, 1440x900jsdom/happy-dom reports
clientWidth0, so the chart's auto-scroll to Todaynever fires there — which is precisely the card's first-paint reproduction. It
was measured in a real browser instead, against the package demo's
?perf=2000fixture (tasks from Jan 2026,
timelineRange.start29 Dec 2025, Today 1 Sep2026). The "before" column is the same page with the label expression reverted
in place, mutation proven on disk by marker count and blob hash, restored under
a trap:
December 2025August 2026Aug 2026December 2025September 2026Sep 2026December 2025August 2026Aug 2026December 2025December 2025Dec 2025Agreement with the band header: false / false / false / true before, true
at all four after. The label moved across positions: no before, yes
after. The bottom row is the control — the one position where the old label
was accidentally right is still right.
Unit tests —
GanttView.toolbarPeriod-7203.test.tsx, 7 newScroll is honestly modelled in this environment:
GanttView.virtual.test.tsxalready pins that setting
scrollLeftplus a scroll event moves the columnwindow, and asserts it by reading a rendered
style.leftback. The new testsread the band-header cell that owns the pixel at the left edge and require the
toolbar to name the same month — comparison is by month identity, never by
wording, so "August 2026" beside "Aug 2026" is not what is being asserted.
Two ablations, each with the direction and the counts predicted first, the
mutation proven on disk by marker count both ways plus a changed blob hash, and
the restore proven by state (
git diff HEAD,git diff --cached,git status --shortall empty viagit checkout HEAD -- ABSOLUTE_PATH):timelineRange.start— predicted 5 failed / 2 passed;measured 5 failed / 2 passed, and the five are the predicted five. The two
that stay green are the single-month control and the left-edge clamp, the only
two whose expectations a static January label happens to satisfy.
onClickhandlers removed — predicted 2 failed / 5 passed; measured2 failed / 5 passed.
Gates, all at
e1facd916(the pushed head)pnpm exec vitest run packages/plugin-gantt/— 56 files / 448 tests passedpnpm --filter @object-ui/plugin-gantt run type-check— exit 0 (bothtsc --noEmitandtsc -p tsconfig.test.json, so the new test file istype-checked too)
check:control-bytes— OK, 5988 tracked text filescheck:changeset-presence— OK, 1 changeset for 2 changed published sourcescheck:i18n-keys,check:doc-fences,check:vi-mock-specifiers,check:vi-mock-inherit— exit 0check:governed-queue-guard --teston the 4 changed paths — NOT GOVERNEDGanttView(3 in@object-ui/i18n, 1 in@object-ui/plugin-view) — 4 files / 83 tests passedgit merge-tree --write-tree --name-only origin/main HEAD— exit 0 againstf626808d4eslint — declared narrowing. Run plain (never
--no-inline-config) over thewhole
packages/plugin-ganttdirectory, letting eslint enumerate the populationfrom its own config: 86 files, 0 errors, 323 warnings.
GanttView.tsxis0 errors / 19 warnings both before and after — measured by linting the
origin/mainblob in place under a trap — so this change adds no warning. Therepo-level
turbo run lintwas not run here: the config extendstseslint.configs.recommendedwith noparserOptions.projectand noprojectService, so type-aware linting is off and a file's verdict depends onlyon its own text; a diff that edits two files cannot move the verdict of any file
it does not edit.
check:readme-exports— NOT MEASURED, not red. It exits 1 on its ownpopulation-collapse banner ("this run proves nothing", 24 of 40 packages
unbuilt) — an environment condition, not a finding, and CI builds first. This
diff is inert to it either way, measured: the README section adds 0 fenced
blocks (38 before, 38 after) and 0 import lines,
src/index.tsxisbyte-identical to
origin/main, andGanttView.tsx's exported-symbol set isidentical to
origin/main(the four new period helpers are module-private —nothing outside the module consumes them).
check:i18n-dead-keysprints "This is a REPORT, not a gate" and is recorded asa report, not a colour.
Scope
Nothing outside the toolbar.
showStartEndColumns,taskListWidthForContainer,task-list width and the date sublabel all sit in this same file and are all
untouched — they are blocked on open maintainer decisions, and staying out of
them is what keeps this PR independently landable. Also untouched:
content/docs/releases/.Generated by Claude Code