fix(plugin-gantt): the timeline column width is a flat floor, not a dead breakpoint table - #7591
Merged
Merged
Conversation
…ead breakpoint table (objectui#7228)
`columnWidthForContainer(width)` branched on the container width three ways and
returned 110 from all three arms, so its parameter was read only to be compared
against thresholds nothing acted on. The shape is not neutral: the two siblings
directly beneath it, `taskListWidthForContainer` and `showStartEndColumns`,
branch on the same 640/1024 breakpoints and really do vary, so the dead table
read as a live responsive policy — a reader reasonably concluded the gantt
narrows its columns in small embeds. It does not.
History settles what the arms were for, so this is a collapse and not a
restoration. The function was born a real curve (35/50/60 off
`window.innerWidth`), kept it through the rename to a container-derived width,
and was bumped to 44/64/80 inside objectui#1870 — then a later commit of that
same PR, titled "floor timeline columns at 110px so day/week/month stay
readable", deliberately replaced the whole curve with one floor. The comment it
added names the value's provenance ("user-specified minimum") and why flatness
costs nothing at the wide end. The branching was leftover shape, not an
unfinished table.
The arms are gone and the value all three returned is now the module constant
`BASE_COLUMN_W`, documented as the floor it is: `columnWidth` resolves to
`columnWidthOverride ?? fitColumnWidth ?? BASE_COLUMN_W` and the fit-stretch
yields a value only when it is strictly greater. That matches how objectui#7420
hoisted the task-list geometry in this file — fixed px values become named
module constants, helpers stay functions only where they vary with the
container.
Behaviour is identical at every input; the changeset carries no package bump.
Ten pins now hold it on both sides of each retired breakpoint (320/500/639/640/
800/1023/1024/1280/1920) plus one requiring a single distinct value across all
of them. Nothing pinned this before: every sibling suite that reads a column
width first forces the container to 1280 "so columnWidth=110 (deterministic)",
pinning the widest arm alone.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
Contributor
✅ 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-project-manager
marked this pull request as ready for review
September 4, 2026 03:21
os-project-manager
enabled auto-merge
September 4, 2026 03:21
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #7228
Clause-②: no
columnWidthForContaineris declared withoutexportinGanttView.tsx; the package entrypackages/plugin-gantt/src/index.tsxnever names it, and the package publishes exactly onepath (
.todist/index.d.ts). A repo-widegit grepfinds two occurrences before thischange — the definition and its single call site, both inside
GanttView.tsx. Nothingauthorable, nothing accepted or rejected, no published surface widened or narrowed. Answered
from the export graph, not from expectation.
The measurement came first, because it decides the repair
Triage refused to queue this until someone established whether 110 is right at all three
tiers (collapse the table) or whether the narrow tiers were meant to differ and the table was
never finished (restore a curve). That is history, not taste.
The function was born a real curve, and a later commit flattened it deliberately.
81781fb04getResponsiveColumnWidth(), readingwindow.innerWidtha2d7023b7columnWidthForContainer(width), container-derivedcb7e05a50(#1870)bf244f400(#7420)So the fork's second arm is reached — a curve existed and a later change flattened it — and
the deliberateness test the dispatch asks for comes back deliberate, on four independent
pieces of evidence inside the flattening commit itself:
cb7e05a50is a squashed PR, and one of its own sub-commit titles is"fix(plugin-gantt): floor timeline columns at 110px so day/week/month stay readable" —
it names the concept (floor), the value, and the reason.
("Default column width was too narrow (35/50/60). Bumped to 44/64/80 so day/week/month
columns have room to breathe"). The flattening is the next deliberate step, not a
collision of sloppy edits.
"even in narrow embeds (user-specified minimum)". Raising the narrow arms from 44/64
up to 110 is the content of the change: a minimum applied everywhere.
timeline via the fit-stretch below; manual zoom can override either way." Both mechanisms
are real and still in the file.
A deliberately flattened helper is the dispatch's Route 1. Nobody is proposing to restore a
curve here — the design call was already made and recorded; only the code's shape lied about
it. Route 1, mechanical. No responsive behaviour is invented, and none is restored.
Consistency with #7420, which just ruled on this file
That PR imposed a convention on the same breakpoint family, and this follows it rather than
inventing a third one: fixed px values become named module constants with a doc comment
tracing the value; helpers stay
somethingForContainer(width)functions only where theygenuinely vary with the container.
showStartEndColumnsstayed a function because it varies;TASK_LIST_MIN_Wwent the other way — #7420 deleted the function-localconstand hoisted itto a module constant used directly at every site.
columnWidthForContainerdoes not vary with its argument, so keeping it a function named...ForContainerwould restate the same lie in a smaller form. It becomesBASE_COLUMN_W,hoisted exactly like
TASK_LIST_MIN_W.showStartEndColumns,taskListWidthForContainer,rowHeightForContainerand the date sublabel are untouched here.Behaviour equivalence — by exhaustion, not by sampling
The old function was total over
numberand every path returned the same literal:width < 640(incl. negatives,-Infinity)110110640 <= width < 1024110110width >= 1024(incl.+Infinity)110110NaN— both comparisons false, falls to the third arm110110There is no input,
NaNincluded, at which the two disagree, so this is equivalence byexhaustion over the whole domain. Three further facts close the call-site side:
columnWidthForContainer(effectiveWidth), whose argument is a plainnumber binding — deleting the call removes no side effect.
baseColumnWidthwas a local alias holding that same constant. Its five uses now readBASE_COLUMN_Wdirectly;columnWidthstill resolves ascolumnWidthOverride ?? fitColumnWidth ?? BASE_COLUMN_W.useMemodependency. It never changed across renders (always 110), sodropping it from the array cannot change when that memo recomputes — and a module constant
is not a valid dependency in the first place.
Verification
Every reading below was taken at
1735f6150, the branch head, with a clean tree.pnpm exec vitest run packages/plugin-gantt/from the repo root (the canonical invocation this repo's guardenforces). The suite ran 63 files and the tree contains exactly 63 test files, so nothing
was silently globbed out;
origin/maincarries 62, this branch adds the 63rd.type-check: exit 0 —turbo run type-check --filter=@object-ui/plugin-gantt, 14/14tasks successful, with the dependency closure built first (
dependsOn: ["^build"]), since astale
dist/*.d.tslies in both directions. Verified it actually covered the new code ratherthan excluding it:
tsc -p tsconfig.test.json --listFilescontains bothGanttView.tsxandthe new test file.
pnpm --filter @object-ui/plugin-gantt lint→0 errors / 352 warnings. Measured before and after by restoring the pre-fix blob and
re-linting: 93 files, 2 errors, 356 warnings, identical on both sides, and
GanttView.tsxis 0 errors / 12 warnings with an identical rule multiset —
react-refresh/only-export-components5,@typescript-eslint/no-explicit-any3,react-hooks/set-state-in-effect2,react-hooks/exhaustive-deps2. (Those 2 errors arepre-existing and appear on both sides;
--no-inline-configis what surfaces them.)the file population was read from eslint's own config resolution (93 files, counted from
--format json, not guessed);packages/plugin-ganttis the only package this diff touches(the other path is a
.changeset/*.md, which eslint does not lint); andeslint.config.jsenables no type-aware linting — zero occurrences of
parserOptions,projectServiceorrecommendedTypeCheckedin a 17.5 KB config — so this diff cannot move a verdict on a file itdoes not contain.
check:control-bytes,check:vi-mock-specifiers,check:vi-mock-inherit,check:element-data-source-declaration,check:shell-escape-residue. Plus a manual control-byte sweep over the diff's own files.Reverse verification — the pair that fits a behaviour-preserving change
A red-then-green does not apply to a refactor that changes nothing. The honest pair is
equivalence and bite, and both legs were run against the committed fix, each with a
trap ... EXIT INT TERM, absolute paths, and the mutation proved on disk by blob hash plus ananchored text control before anything was measured. The pre-fix side is pinned to this
branch's recorded base commit, never to the shared
origin/mainpointer.Leg A — equivalence. The new pins were run against the pre-fix
GanttView.tsx(on-disk blob
aa4f3229b, controls: pre-fix marker present,BASE_COLUMN_Wabsent):10 passed / 10. The behaviour these pins describe is therefore identical before and after,
measured at nine container widths rather than argued.
Leg B — bite. Re-introducing exactly the curve a future reader might "restore"
(
effectiveWidth < 640 ? 90 : BASE_COLUMN_W; injected marker counted on disk, blob changed):4 failed / 10 — the three narrow-tier pins plus the single-distinct-value pin. The pins are
not vacuous.
Both restores were proved by state, not by an exit code: blob equality against the
HEADblob (
5fe7529a4) and an emptygit diff HEAD, and the restore isgit checkout HEAD -- ...rather than a bare
git checkout -- ..., which reads back from the index the mutation justpolluted. No rebuild step is needed for either leg and none is claimed: the root Vitest config
aliases every
@object-ui/*specifier to itssrc, and the suite imports./GanttViewrelatively, so nothing here resolves through a package's
dist.Test surface
packages/plugin-gantt/src/GanttView.columnWidth-7228.test.tsx— 10 pins, all DOM readings(the rendered day-column width off the header's unit cells), never computed style. Nine of them
sit on both sides of each retired breakpoint — 320 / 500 / 639 / 640 / 800 / 1023 / 1024 /
1280 / 1920 — and the tenth requires a single distinct value across all nine, so a restored
curve fails here whatever numbers it picks.
This gap was real: every sibling suite that reads a column width opens by forcing the container
to 1280 "so columnWidth=110 (deterministic)", which pins the widest arm alone. Neither
direction of drift — re-curving the width, or moving the floor — had a test that could catch it.
Not measured locally, and why
check:readme-exportsandcheck:sdui-registration-pinsboth stopped on an unmetprerequisite, not on a finding, and each says so in its own verdict: the first reports
the population COLLAPSED -- this run proves nothingwith 36 packages unbuilt, the secondprints
No console build to weigh ... This is exit 2, not a pass. Both want builds this changehas no reason to produce, and neither is reachable by this diff:
index.tsxis untouched, noComponentRegistry.registercall changes, and no export appears or disappears. Recorded as NOTMEASURED rather than as a pass or a failure. CI runs the full farm regardless.
Release impact
None, so the changeset carries empty frontmatter and no package bump — behaviour is
identical at every input; what changes is the shape of the code and its comment.
🤖 Generated with Claude Code
https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
Generated by Claude Code