Skip to content

feat: a wheel pulls the wall too, and the bar stays put on a desktop - #76

Merged
antstanley merged 1 commit into
mainfrom
polish-2
Jul 28, 2026
Merged

feat: a wheel pulls the wall too, and the bar stays put on a desktop#76
antstanley merged 1 commit into
mainfrom
polish-2

Conversation

@antstanley

Copy link
Copy Markdown
Owner

The desktop half of the pull gesture, and the bar change that made the indicator need to think about where the wall's top is.

A wheel pulls the wall

Keep scrolling up when the wall is already at its top and it opens exactly as a finger opens it: same band, same 72px threshold, same 54px shelf, same indicator, same feed.refresh(). Two things are forced by what a wheel is, and they are the only differences:

  • It reports movement, not position, so travel accumulates and each delta is scaled by 0.4. One notch of a mouse wheel is 100px in chrome; at 1:1 a stray notch at the top of the wall would arm a hundred-author fan-out. At 0.4 it takes two deliberate notches, while a trackpad's many small deltas give a pull that answers continuously.
  • It never says it is done, so stopping is the release: 140ms of quiet past the threshold lays the wall. The pill says "stop to lay again" rather than "let go", because telling somebody holding no button to let go is an instruction they cannot follow.

A wheel pull starts from rest, and that is the whole defence against momentum. A trackpad flick that reaches the top keeps delivering wheel events after the fingers have left the glass: upward, at scrollY 0, exactly the shape of a pull. What a momentum tail structurally cannot have is a gap in front of it, so a gesture may only begin after 200ms of quiet. A reader who means to pull always has that gap.

The two inputs ask "where" differently, and that is not drift: a finger grabs what is under it, so a touch pull must start inside main#wall; a wheel only points, and the cursor sits wherever it was last left, very often over the bar. A wheel is refused only over a screen with its own scroll.

The bar stays put on a desktop

From md it sticks to the top of the scroll rather than scrolling away, so the layout picker, the switcher, refresh and settings are reachable from anywhere in an endless scroll. Unchanged on a phone, where it is fixed to the bottom under a thumb. z-20 so the switcher's scrim still dims the bar its panel hangs off; the page's own colour at 80% behind a blur, so it reads as nothing at the top of a wall and veils the bricks that pass under it; no border, because a rule across an unscrolled page is a line for its own sake.

The two are one change rather than neighbours: a sticky bar moves where the top of the wall is, so the indicator now measures main#wall's own top edge when a gesture starts. Zero on a phone, the height of the bar on a desktop, and no bar height written down anywhere it could go stale.

Verification

just check (254 vitest, 159 cargo nextest, two tsc projects, four guards, lint, fmt) and just test-e2e (85 browser tests, five of them new).

Thirteen new vitest cases cover the momentum tail and the rule keeping the two releases apart: a finger resting mid-drag must not lay the wall, and a touchend cannot commit a wheel pull. The browser cases dispatch wheel events 16ms apart rather than driving page.mouse.wheel, because the timing IS what is under test: every mouse.wheel call is a round trip that puts tens to hundreds of milliseconds between events, landing on the wrong side of both timers.

🤖 Generated with Claude Code

The desktop half of the pull. Keep scrolling up when the wall is already at its
top and it opens exactly as a finger opens it: same band, same 72px threshold,
same 54px shelf, same indicator. A wheel is the same gesture through a different
input, so it is the same PullState and the same feed.refresh() at the end of it.

Two things are forced by what a wheel is, and they are the only differences. It
reports movement rather than position, so travel accumulates instead of being
measured from an origin, scaled by 0.4: one notch of a mouse wheel is 100px in
chrome, and at 1:1 a stray notch at the top of the wall would arm a
hundred-author fan-out. And it never says it is done, so stopping is the
release: 140ms of quiet past the threshold lays the wall. The pill says "stop to
lay again" rather than "let go", because telling somebody holding no button to
let go is an instruction they cannot follow.

A WHEEL PULL STARTS FROM REST, and that is the whole defence against momentum.
A trackpad flick that reaches the top keeps delivering wheel events after the
fingers have left the glass: upward, at scrollY 0, exactly the shape of a pull
and nobody asked for it. What a momentum tail structurally cannot have is a gap
in front of it, because it is the continuation of the scroll that just arrived,
so a gesture may only begin after 200ms of quiet. A reader who means to pull
always has that gap: they reach the top, the wall stops, they push again.

The two inputs ask "where" differently, which is not drift. A finger grabs what
is under it, so a touch pull must start inside main#wall. A wheel only points,
and the cursor sits wherever it was last left, very often over the bar; refusing
the gesture there would fail for a reason no reader could see. So a wheel is
refused only over a screen with its own scroll, which is the switcher's panel
and the scrim under it.

Thirteen more vitest cases, including the momentum tail and the rule that keeps
the two releases apart: a finger resting mid-drag must not lay the wall, and a
touchend cannot commit a wheel pull. Four browser cases on a desktop viewport
with hasTouch off. Those dispatch wheel events 16ms apart rather than driving
page.mouse.wheel, because the timing IS the thing under test: every mouse.wheel
call is a round trip that puts tens to hundreds of milliseconds between events,
which lands on the wrong side of both timers and reads as a reader who pushed,
stopped, and pushed again.

The bar sticks to the top from md rather than scrolling away with the wall, so
the layout picker, the switcher, refresh and settings are reachable from
anywhere in an endless scroll instead of only from the top of one. It is
unchanged on a phone, where it is fixed to the bottom under a thumb. z-20, so
the switcher's own scrim still dims the bar its panel hangs off; the page's own
colour at 80% with a blur behind it, so it reads as nothing at the top of a wall
and veils the bricks that pass under it once the reader moves; and no border,
because a rule across a page nobody has scrolled yet is a line for its own sake.

That is the same change as the one above rather than a neighbour of it: a sticky
bar moves where the top of the wall IS, and the pull indicator has to sit in the
gap the wall opens rather than over the controls. So the indicator measures
main#wall's own top edge when a gesture starts, which is zero on a phone and the
height of the bar on a desktop. One measurement, no breakpoint named twice, and
no bar height written down anywhere it could go stale.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

🧹 Preview wall removed.

@antstanley
antstanley merged commit 07c423a into main Jul 28, 2026
5 checks passed
@antstanley
antstanley deleted the polish-2 branch July 28, 2026 12:51
@github-actions github-actions Bot mentioned this pull request Jul 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant