Wake the bunting on a scroll's air, not only its speed
The wake gate divided a scroll event's distance by the time since the previous event, idle included, so one wheel turn after twenty quiet seconds read as 4 px/s and never woke the loop, though it had moved 100 px/s of air. Waking also when the air passes AIR_REST catches it; the speed gate stays for the events that carry little air.
Human
Prompt
Astra reviewed the branch: Ship after fixes (bunting-review.md). Fix all four without changing the feel. 1. (Important) bunting.js:517: a scroll after idle can fail to wake the cloth. Wake whenever the accumulated air exceeds AIR_REST, independent of the velocity gate. Add a drive: idle 20s, one 80px scroll with the row on screen, the cloth moves (red first).
From this commit's own message. The prompt ledger has no entries for it — which is what a squashed, rebased or amended commit looks like, and what every commit looks like in a repo without a ledger.
Summary
bunting.js wakes when the air exceeds AIR_REST; TestAScrollAfterIdleWakesTheBunting; its weight.
Agent notes
Red before the fix: 0.00% of the canvas moved (want 1%). Green: 2.17%.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
3 files changed,
+34
−1
hack/test-weights.txt+1 −0internal/www/landing_browser_test.go+29 −0site/static/bunting.js+4 −1
Intent
diff --git a/hack/test-weights.txt b/hack/test-weights.txt| index b5c7d48..b012699 100644 |
| --- a/hack/test-weights.txt |
| +++ b/hack/test-weights.txt |
| @@ -156,6 +156,7 @@ browser internal/gallery TestTheGallerySwitchLandsOnTheNextFrame 2 |
| browser internal/gallery TestTheGallerysOwnControlsAreExactlyTheFloor 2 |
| browser internal/gallery TestThePhoneIndexAndTheWayBack 2 |
| browser internal/gallery TestTheWayBackFromAShellDemoIsATarget 2 |
| +browser internal/www TestAScrollAfterIdleWakesTheBunting 29 |
| browser internal/www TestTheLandingPassesAxe 16 |
| browser internal/www TestAShakeMovesTheBuntingMoreThanAScroll 15 |
| browser internal/www TestTheBuntingFollowsTheScrollBack 15 |
diff --git a/internal/www/landing_browser_test.go b/internal/www/landing_browser_test.go| index 811e96f..0e271e7 100644 |
| --- a/internal/www/landing_browser_test.go |
| +++ b/internal/www/landing_browser_test.go |
| @@ -1362,6 +1362,35 @@ func TestTheBuntingNeverLeavesItsRow(t *testing.T) { |
| } |
| } |
| +// TestAScrollAfterIdleWakesTheBunting: a reader leaves the page for |
| +// twenty seconds, then turns the wheel once, 80 px, with the row on |
| +// screen. One scroll event that long after the last is a slow scroll |
| +// by its average speed, but the air it moves is real, so the cloth |
| +// must wake and lift. |
| +func TestAScrollAfterIdleWakesTheBunting(t *testing.T) { |
| + rig, _ := siteRig(t) |
| + ctx, cancel := context.WithTimeout(rig.Context(), 2*time.Minute) |
| + defer cancel() |
| + openStill(t, ctx, rig) |
| + mustRun(t, ctx, chromedp.Evaluate(`scrollTo({top: 80, behavior: "instant"}); true`, nil)) |
| + mustRun(t, ctx, chromedp.Sleep(20*time.Second)) |
| + settle(t, ctx, "the cloth after twenty idle seconds", time.Second) |
| + var peak float64 |
| + at(t, ctx, `(async () => { |
| + const m = siteMotion; |
| + m.rest(); |
| + scrollTo({top: 0, behavior: "instant"}); |
| + let peak = 0; |
| + const t0 = performance.now(); |
| + while (performance.now() - t0 < 1000) { await m.frame(); peak = Math.max(peak, m.off()); } |
| + return JSON.stringify(peak); |
| +})()`, &peak, awaitPromise) |
| + t.Logf("one 80 px scroll after twenty idle seconds moved %.2f%% of the canvas within a second", peak) |
| + if peak < 1 { |
| + t.Errorf("one 80 px scroll after twenty idle seconds moved %.2f%% of the canvas; want at least 1%%: the cloth never woke", peak) |
| + } |
| +} |
| + |
| // TestReducedMotionIgnoresScrolling: under reduced motion there is no |
| // canvas, and scrolling and shaking the page leave the still SVG |
| // exactly as it was. |
diff --git a/site/static/bunting.js b/site/static/bunting.js| index 2dc2bb8..00cd1b1 100644 |
| --- a/site/static/bunting.js |
| +++ b/site/static/bunting.js |
| @@ -514,7 +514,10 @@ |
| sy = scrollY; st = now; |
| air = Math.max(-AIR_MAX, Math.min(AIR_MAX, airAt(now) + d / AIR_TAU)); airT = now; |
| scrollV = now - scrollT < 100 ? (scrollV + v) / 2 : v; scrollT = now; |
| - if (Math.abs(v) > 50) { lastInput = now; start(); } |
| + // Wake on the air as well as the speed: v is averaged since the |
| + // last event, so one wheel turn after a long idle reads as slow |
| + // while the air it moved is not. |
| + if (Math.abs(v) > 50 || Math.abs(air) > AIR_REST) { lastInput = now; start(); } |
| }, { passive: true }); |
| new IntersectionObserver(function (es) { onScreen = es[0].isIntersecting; if (onScreen) start(); }).observe(row); |