fix(ui): draw the frozen-column seam once per plane, not once per cell - #577
Merged
Conversation
blove
enabled auto-merge (squash)
September 4, 2026 15:42
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
The seam started and stopped once per row. It was a `box-shadow` on every pinned cell, and a per-cell shadow cannot tile into a continuous edge: the blur has to be held inside the cell (spread <= -blur/2) or neighbouring shadows overlap and double into a dark band at each row boundary — so it faded to nothing at every boundary instead, and read as a dashed line rather than the edge of a frozen pane. This file had the trade documented and had picked the fading half. Draw it once per plane instead. A gradient has no falloff along its own axis, so one box spanning the sticky header row and one spanning the scroll content are each uniform for their full height, and the two meet exactly at the header's lower edge with nothing between them. Both sides come from the two pseudo-elements of each plane, which is why those planes are flex here: two in-flow boxes in a block container stack VERTICALLY, which would put the right-hand seam below the content instead of beside the left one. Rows are absolutely positioned, so making their container a flex box moves nothing that is already drawn. The surface publishes the only thing CSS cannot work out — where each edge falls — as `--pretable-pinned-left-edge` / `--pretable-pinned-right-edge`, gated by `data-pretable-pinned-left` / `-right` so a side with nothing pinned draws nothing rather than a seam down the grid's own border. Both are viewport-x and the right-hand one goes through the same `getPinnedRightEdge` the right-pinned CELLS use: a right-pinned cell is stuck by a computed `left`, never a `right` inset, so a seam anchored the other way would clamp differently from the columns it marks. Before the scrollport is measured the right edge is withheld, exactly as those cells go unpinned. One regression on the way, caught by the smoke suite rather than by me: the header row's pinned cells are IN FLOW (sticky, not absolute), so a seam that consumed 8px of that line shoved the frozen columns 8px clear of the grid's left edge. The negative end margin gives the space back; the box is placed by `left` regardless of where the flow put it. Guards: the seam guard now matches on the rule carrying each side's own gradient rather than the shared block — reading the shared block let a side lose a whole plane with the guard none the wiser, which the mutation run caught. Five mutations fail it (header plane dropped, right side anchored by `right`, planes back to block, seam returning as a per-cell shadow, seam made a pointer target); three more fail the new React tests (attributes dropped, right edge published unmeasured, edges published with nothing pinned). The two edge properties are registered as RUNTIME_VARS, and a typo'd one still fails the token contract. Verified in a browser on a production build: hero grid scrolled sideways, the seam is one unbroken gradient from the top of the header through every row; the showcase's right-pinned column mirrors it at the group's leading edge (912px against the cell's 920px); a grouped grid crosses its group bands without a notch; and both pinned header and body cells sit flush at offset 0. 136 Playwright specs pass across smoke, popover-scroll, header-keyboard, keyboard-a11y, grouping and range-selection. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
blove
force-pushed
the
blove/pinned-seam-continuous
branch
from
September 4, 2026 16:05
abfff83 to
364157a
Compare
The bench suite's `a focused pinned cell keeps its seam` was written when the seam WAS the cell's box-shadow, and asserted the cell still computed `rgb(1,2,3) 8px 0px 8px -8px` under focus. Moving the seam onto the plane made that assertion false — correctly, and I missed it: I ran the website's Playwright suites and the package unit tests, but never `pnpm bench:e2e`, which is its own project with its own tests. CI found it. The invariant it protects is still worth a browser: the plane paints its seam AND the focused cell draws its ring, at the same time, from one stylesheet. What changed is that the collision it was written against — box-shadow being winner-takes-all on one element — is now impossible by construction, since the two live on different elements. So the test asserts the pair directly: the cell has the outline ring and an EMPTY shadow slot (a ring must never take it back), and the plane's ::after paints the seam colour at the published edge while that cell holds focus. Mutation-checked: with the plane's gradient stopped, it fails. The full bench suite passes locally (28 specs), which is the step that should have preceded the first push. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Contributor
Vercel preview readyPreview: https://pretable-o20mryyjf-cacheplane.vercel.app Updated automatically by the |
This was referenced Sep 4, 2026
Merged
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.
You were right that it still starts and stops. Landing the seam on the header closed the header-sized gap, but the seam also fades out at every row boundary — and that is structural, not a missing rule.
Why a per-cell shadow can never be continuous
The seam was
box-shadow: 8px 0 8px -8px var(--pretable-seam-color)on each pinned cell. Spread and blur leave no third option:The stylesheet documented that trade and had picked the fading half. A 4× crop of the shipped seam, one 16px-wide strip crossing four rows, is the first image below.
Draw it once per plane
A gradient has no falloff along its own axis, so one box spanning a whole plane is uniform top to bottom. There are two planes — the sticky header row and the scroll content — and they meet exactly at the header's lower edge with nothing between them.
Each plane draws both sides from its own two pseudo-elements, which is why those planes are flex here: two in-flow boxes in a block container stack vertically, which would put the right-hand seam below the content instead of beside the left one. Rows are absolutely positioned, so making their container a flex box moves nothing already drawn.
The surface publishes the only thing CSS cannot work out — where each edge falls:
--pretable-pinned-left-edge--pretable-pinned-right-edgegetPinnedRightEdge(viewportWidth, pinnedRightWidth)— the same call the right-pinned cells makedata-pretable-pinned-left/-rightBoth edges are viewport-x and both seams are anchored by
left, because a right-pinned cell is stuck by a computedleftand never arightinset — a seam anchored the other way would clamp differently from the columns it marks when the content is narrower than the viewport. Before the scrollport is measured the right edge is withheld, exactly as those cells go unpinned.--pretable-seam-colorstill colours it; it is now the stop of a gradient rather than a shadow's colour, which is the same reason the token holds a colour and not a whole shadow.One regression on the way, caught by the suite rather than by me
The header row's pinned cells are in flow — sticky, not absolute — so a seam consuming 8px of that line shoved the frozen columns 8px clear of the grid's left edge.
smoke.spec.tsfailed on the header being offinnerLeftwhile the body cell was fine. A negative end margin gives the space back; the box is placed byleftregardless of where the flow put it. Both header and body pinned cells now measure offset 0.Guards
The seam guard was rewritten and tightened after its own mutation run: matching any rule mentioning the side let the shared block satisfy it, so a side could lose a whole plane unnoticed. It now matches on the rule carrying that side's own gradient.
Three more on the React side — attributes dropped, right edge published before measurement, edges published with nothing pinned — each fail the new
right-pin-surfacetests. The two edge properties are registered asRUNTIME_VARSin the token contract, and a typo'd one still fails it.Verification
pnpm format,lint,typecheck,api:check,testall pass from the repo root.In a browser on a production build:
left: 912pxagainst the cell'sleft: 920px, gradientto left.136 Playwright specs pass across
smoke,grid-header-popover-scroll,grid-header-keyboard,grid-keyboard-a11y,groupingandrange-selection.🤖 Generated with Claude Code