Repository navigation
Put the way back where it belongs, and keep it on screen - #43
Merged
Merged
Conversation
An operator could not stow the database list. Three things were wrong, and each one hid the next. **The word.** The control said `Databases`, which is what the list holds, not what the control moves - the databases are all still there afterwards, still searched across, with one of them still selected. What folds is the list. The README had this right and the button did not: "**Databases** in the header stows **the rail**" names the object, and the object was never on the button. `title` and `aria-label` now say "Hide the database list" / "Show the database list", and the icon carries the rest. **The place.** It sat beside `Settings` and `+ Add database`, which act on data and on configuration; this one only changes the layout, so a third noun in that row read as a third way to open something. It now sits in the shell's own left margin, level with **All databases**, against the window edge - beside the thing it moves rather than beside things it has nothing to do with. `left: -1.5rem` is `main`'s own padding, so the row beside it keeps every pixel it had: measured at 1280, the map still goes 529 -> 751 when the list is stowed, exactly as before. **The reach.** Nothing was pinned, and this layout accepts the page scrolling - `.split`'s floor comment documents it as the degradation rather than a failure. Measured with the list showing, a 600px window scrolls 203px, a 520px one with Settings open 394px, a 440px one 363px, and the control went to top -89, -169 and -249: off screen, not hit-testable, and there is no second way to fold. The pinning is where this got interesting, and three attempts were wrong first. `position: sticky` cannot state it. A sticky box is clamped to its containing block and every box in this shell is capped at `100dvh` by the load-bearing `height` on `main`, so it holds for about a viewport of spill and then rides away with the page. That is not a tuning error: measured with the shell made its own scroll container instead of the page, to see whether that was the cause, the control lands at the same 38 / 30 / -10 / -34 either way. The app's own default 1280x840 window reproduces it after two presses of View > Zoom In, which Electron ships by default. Placing it from script in every state was worse. It put the resting position in the hands of a dependency list, and the list was wrong immediately: measured, the icon sat at 68 while its anchor was at 113, 225 and 192. A `ResizeObserver` added to cover that earned nothing - identical numbers with and without it, because `main` is a fixed height and `document.body` never resizes. Both are gone. The third was capturing the escaped x. It was the one non-reactive quantity in an otherwise reactive design: read once at the escape and never corrected, so a horizontal scroll at that moment pinned the control off the window and it did not come back. This shell does overflow sideways below about a 380px viewport and the window has no minimum, so it is a drag away. Measured at 360x500: scroll right 21px, scroll down to escape, scroll back left, and the control stayed at x -21 with nothing reachable at its centre. There is no captured x now - fixed positioning does not move with a scroll, and `left: 0` is where that column already is. So the control rests in the flow, in the margin, and CSS aligns it with the row in every state that does not scroll - Settings open, the add form open, an error line up. Measured across those: 113/113, 225/225, 113/113, 192/192. Script decides one thing only, the state CSS cannot express: when the row's own top passes above the pinned header, the control leaves the flow and holds at that line. The header measures itself rather than being a constant, because it wraps on a narrow window - 53px down to a 490px viewport, 69px from 480px down. Checked by clicking, at the eight sizes that defeated the sticky version: 1280x840, 1280x600, 1280x520 with Settings, 1280x460 with Settings, 1280x440, 640x500 with Settings, and the default window at zoom 2x and 3x. Scrolled to the end in each (203 to 997px of scroll), the control holds and a raw mouse click at its centre folds the rail. With the escape disabled, seven of the eight go off screen and the same click does nothing. The guard runs at three sizes, one with Settings open and one with the list stowed, and both halves of that were learned the hard way. The first version ran at 600 alone - the one size where the sticky control still held - so it was green while the bug it existed to prevent was live at 520 and 440. The second ran every case with the list showing, which is the state where this control is one of several ways to the same place; stowed, it is the only way back, and a change that killed the escape only in that state passed all three sizes. The stowed case now clicks, at the control's own centre from the scrolled position, and the list has to return. It asserts reachability rather than visibility: `toBeVisible()` passes for an element below the fold, which is the trap the map pane's guard 150 lines above was written for. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
An operator could not stow the database list. Three things were wrong, and each
one hid the next.
The word. The control said
Databases, which is what the list holds, notwhat the control moves - the databases are all still there afterwards, still
searched across, with one of them still selected. What folds is the list. The
README had this right and the button did not: "Databases in the header stows
the rail" names the object, and the object was never on the button.
titleand
aria-labelnow say "Hide the database list" / "Show the database list", andthe icon carries the rest.
The place. It sat beside
Settingsand+ Add database, which act on dataand on configuration; this one only changes the layout, so a third noun in that
row read as a third way to open something. It now sits in the shell's own left
margin, level with All databases, against the window edge - beside the thing
it moves rather than beside things it has nothing to do with.
left: -1.5remismain's own padding, so the row beside it keeps every pixel it had: measured at1280, the map still goes 529 -> 751 when the list is stowed, exactly as before.
The reach. Nothing was pinned, and this layout accepts the page scrolling -
.split's floor comment documents it as the degradation rather than a failure.Measured with the list showing, a 600px window scrolls 203px, a 520px one with
Settings open 394px, a 440px one 363px, and the control went to top -89, -169 and
-249: off screen, not hit-testable, and there is no second way to fold.
The pinning is where this got interesting, and three attempts were wrong first.
position: stickycannot state it. A sticky box is clamped to its containingblock and every box in this shell is capped at
100dvhby the load-bearingheightonmain, so it holds for about a viewport of spill and then rides awaywith the page. That is not a tuning error: measured with the shell made its own
scroll container instead of the page, to see whether that was the cause, the
control lands at the same 38 / 30 / -10 / -34 either way. The app's own default
1280x840 window reproduces it after two presses of View > Zoom In, which Electron
ships by default.
Placing it from script in every state was worse. It put the resting position in
the hands of a dependency list, and the list was wrong immediately: measured, the
icon sat at 68 while its anchor was at 113, 225 and 192. A
ResizeObserveraddedto cover that earned nothing - identical numbers with and without it, because
mainis a fixed height anddocument.bodynever resizes. Both are gone.The third was capturing the escaped x. It was the one non-reactive quantity in an
otherwise reactive design: read once at the escape and never corrected, so a
horizontal scroll at that moment pinned the control off the window and it did not
come back. This shell does overflow sideways below about a 380px viewport and the
window has no minimum, so it is a drag away. Measured at 360x500: scroll right
21px, scroll down to escape, scroll back left, and the control stayed at x -21
with nothing reachable at its centre. There is no captured x now - fixed
positioning does not move with a scroll, and
left: 0is where that columnalready is.
So the control rests in the flow, in the margin, and CSS aligns it with the row
in every state that does not scroll - Settings open, the add form open, an error
line up. Measured across those: 113/113, 225/225, 113/113, 192/192. Script decides
one thing only, the state CSS cannot express: when the row's own top passes above
the pinned header, the control leaves the flow and holds at that line. The header
measures itself rather than being a constant, because it wraps on a narrow window
Checked by clicking, at the eight sizes that defeated the sticky version:
1280x840, 1280x600, 1280x520 with Settings, 1280x460 with Settings, 1280x440,
640x500 with Settings, and the default window at zoom 2x and 3x. Scrolled to the
end in each (203 to 997px of scroll), the control holds and a raw mouse click at
its centre folds the rail. With the escape disabled, seven of the eight go off
screen and the same click does nothing.
The guard runs at three sizes, one with Settings open and one with the list
stowed, and both halves of that were learned the hard way. The first version ran
at 600 alone - the one size where the sticky control still held - so it was green
while the bug it existed to prevent was live at 520 and 440. The second ran every
case with the list showing, which is the state where this control is one of
several ways to the same place; stowed, it is the only way back, and a change that
killed the escape only in that state passed all three sizes. The stowed case now
clicks, at the control's own centre from the scrolled position, and the list has
to return. It asserts reachability rather than visibility:
toBeVisible()passesfor an element below the fold, which is the trap the map pane's guard 150 lines
above was written for.
Verification
typecheck/lint/test:unit(347) /test:e2e(9) green afterpnpm build.macos-e2eruns only on push to main, so that surface is verified after merge, not here.Adversarial review
One round before push (READ-ONLY, review-only, design-challenge list forced). Verdict needs-attention; it found a real bug, and everything it flagged as a defect is fixed in this commit:
left: 0.escaped = !railFolded && …) — the guard goes red on reachability.53 at 460 / 68 at 400) was read with the add form open, which shortens its own button and takes a line out of the header; re-measured with it closed, 53px down to 490 and 69px from 480. The472pxspill bound is exceeded at zoom 1. "Held at the header's underside" is only true while the header is itself pinned. All three corrected.Design-challenge candidates: 6, all blocked, none applied — they are the operator's calls, recorded here rather than acted on:
minWidthon the window and retire the sideways-overflow class entirely.aria-controlsalongsidearia-expanded, and drop one oftitle/aria-label(some readers announce both).The review also attacked and cleared: no
$effectloop, exact non-oscillating escape boundary (1px either side, both directions), correct behaviour through resize and zoom while escaped, no occlusion at any width or zoom, andelementFromPointfaithfully predicting real clicks. It confirmed 529→751, the resting-alignment invariant, −89/−169/−249, 203/394/363, and the two-press zoom repro.