Restructures the canvas so the spreadsheet-feel works correctly with
the seeder's 14 stages: horizontal scroll moves the rows AND the
TimeAxis together; vertical scroll moves the rows but keeps TimeAxis
pinned; both panes intersect at a fixed corner cell. Diagonal trackpad
scroll behaves naturally because there's only one scroll container.
DOM restructure (E2 — sticky resolves to its nearest scroll ancestor;
fixed by giving sticky elements the right scroll-container parent
instead of patching with absolute positioning):
.tt-page__canvas position: relative; overflow: auto
└ .tt-page__layout display: grid; grid-template-columns: 200px auto;
inline-size: max-content
├ .tt-page__corner sticky top:0 left:0 z=3
├ .tt-page__axis sticky top:0 z=2 (full 1872px wide, no clip)
└ for each stage:
├ .tt-page__header-cell sticky left:0 z=2
│ └ <StageHeaderCell :row-height-px="row.rowHeightPx">
└ .tt-page__row-cell normal z=1 (height = same value)
└ <StageRow>
Z-index ladder (E1) is documented in the page CSS:
corner=3, axis row=2, header rail=2, row content=1, blocks=auto.
Popover + AddPerformanceDialog stay above via Teleport-to-body.
Drops the broken pre-stabilization layout:
- `grid-template: "corner axis" 28px "stages rows" 1fr / 200px 1fr`
that put ALL stage headers in ONE grid cell (cause of "lanes too tall"
via headers stretching to 100% of the 570px cell)
- nested `overflow: auto` on `.tt-page__rows` (cause of horizontal-scroll
desync — only the rows pane scrolled, axis stayed put)
- `overflow: hidden` on `.tt-page__axis` (E4 — clipped axis ticks beyond
the 1fr cell width)
- `<GridBg :total-height="0" />` which was a no-op anyway; gridlines now
render directly on each `.tt-page__row-cell` background
`inline-size: max-content` on the layout grid forces it wider than the
canvas viewport, so `overflow: auto` on the canvas actually fires a
horizontal scrollbar. Without this, the `auto` second column shrinks to
viewport and nothing overflows.
The page now passes `:row-height-px` to StageHeaderCell (B2 seam, now
load-bearing). Both header and row cell get the same explicit blockSize
inline so the freeze panes align pixel-for-pixel under whatever
laneCount each stage resolves to.
Visual scroll/alignment proof is deferred to TEST-VISUAL-001 — jsdom
cannot verify position:sticky behavior, scrollbar visibility, or pixel
alignment of the freeze panes. This is a known limitation, not a test
gap. B4 covers the structural assertions jsdom CAN verify.
All 389 existing tests still pass; production build smoke clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>