Skip to content

bench: fix false errors in Safari, filter URL writes, and scroll on reload - #150

Merged
lwwmanning merged 10 commits into
mainfrom
claude/bench-fixes
Oct 30, 2026
Merged

lwwmanning merged 10 commits into
mainfrom
claude/bench-fixes

Conversation

@lwwmanning

@lwwmanning lwwmanning commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

Fix five small bench faults found during the landing page work.

  • Safari showed false errors after Back from a chart page. A fetch that leaving the page cancels is no longer an error.
  • Safari showed an error page after about 50 filter clicks. Filter URL writes now share the landing page's write limit: 150 clicks make 4 writes.
  • With no group open, Reload and Back showed the page top for a moment. The browser now restores the scroll itself.
  • The landing page waited only 150 ms for cached data before it showed the loading view. In production, 1 of 12 cached reads took longer. It now waits 300 ms.
  • bench/web pins Bun as its package manager, so pnpm cannot install there.

Parity against main: 881 responses and 28 screenshots, no change.

🤖 Generated with Claude Code

lwwmanning and others added 8 commits October 30, 2026 10:08
pnpm refuses to install in a project that pins another package
manager, so a pnpm command run in bench/web now fails instead of
installing around the 7-day cooldown. The pin matches the root's.

Renovate reads packageManager as its own dep type. Without a rule,
bench's copy would take the bench- branch prefix and a PR of its own,
so the two pins could drift. A rule after bench's groups both in one
branch. A test fails when the pins differ or the rule is missing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Will Manning <will@willmanning.io>
After /api/revalidate marks the group caches stale, the next read
returns the stale value and Next refreshes the entry in the
background. If another instance holds the entry's fill lock, the
refresh failed at once with GroupCacheFillInProgressError, Next logged
it as an error, and the entry stayed stale until a later read started
another refresh.

A fill now learns from the read it runs in (an AsyncLocalStorage set by
retryGroupCacheFill) whether that read has already returned. If so, it
is a background refresh: on contention it releases its connection and
fill slot, backs off on the read retry schedule (about 15 seconds in
all), tries the lock again, and fills only once it holds the lock. A
fill that a read still waits for (a miss) fails fast as before, so the
read retries the Data Cache without holding a connection.

Unit tests cover the wait, the free slot, the bound and the unchanged
foreground path. A Postgres test holds the lock from a second session.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Will Manning <will@willmanning.io>
Leaving a page cancels its in-flight fetches. In WebKit each one
rejects with TypeError "Load failed" as the navigation starts, before
pagehide, the same error as a real network failure. So on Back from a
chart page the hover-dwell ?n=all fetch logged "bench: full history
fetch failed" and put the chip in its error state, and on Back from the
landing page an open group's bundle fetch showed every card's error.
No unhandled rejection came from bench's code: each path caught the
error, but reported it as real. Next's own ?_rsc prefetch rejects the
same way and Next catches it.

lib/page-leave.ts holds a TypeError, or an AbortError that no
controller of ours raised (Firefox), for one second. If pagehide fires,
the failure is dropped, and reported only if the page comes back from
the back/forward cache. Otherwise it reports as before. HTTP errors,
timeouts and our own aborts are unchanged. It listens only to pagehide
and pageshow: a beforeunload listener would keep Firefox from caching
the page.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Will Manning <will@willmanning.io>
Each chip click called history.replaceState at once. Safari refuses
more than 100 calls in 10 seconds, and Next.js adds one to each of
ours, so about 50 fast chip clicks could replace the page with Next's
error page, as group toggles did before they were coalesced.

lib/url-writes.ts generalizes the open groups' scheduler: the fragment
and the filter query are named parts of one per-window schedule. A
write applies every pending part in one replaceState call, at once
after a quiet spell, otherwise once a burst stops, and never closer
than 300 ms to the last write. The fragment part is still dropped when
the fragment changes under it; a path change drops every part.
open-groups.ts keeps its API on top of it. The filter bar also makes a
pending write on pagehide; on the landing page LandingState's flushes
before a chart link and on pagehide now cover the filter too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Will Manning <will@willmanning.io>
The logo is a Link to /. On / it soft-navigated to /, which dropped the
fragment that lists the open groups (and the filter query) while the
groups stayed open, so the URL no longer matched the page.

On / a plain click, or Enter on the focused logo, now scrolls to the
top without navigating, smoothly unless the visitor prefers reduced
motion. A click with a modifier key or another button is left to the
browser, and on every other page the logo navigates home as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Will Manning <will@willmanning.io>
LandingState set history.scrollRestoration to 'manual' on mount even
with every group closed. So a Reload or Back of the collapsed list
showed the page top for a frame or two and then restored after
hydration, though the collapsed list is as tall before hydration as
after and the browser could restore it itself.

The page now keeps 'auto' while no group is open and switches to
'manual' on the first open and back on the last close (toggles and
hashchange). The browser uses the mode the history entry had when the
page was left, which the page reads on arrival: with no group open and
'auto', it skips its own restore. With groups open, or 'manual' on
arrival (a group opened just before a reload whose fragment write had
not landed), the element save and restore run as before.

In Chromium and WebKit (Playwright) with no group open, Back now lands
on the first frame; Reload does in Chromium, and in WebKit after its
own 1-2 frames. With two groups open nothing changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Will Manning <will@willmanning.io>
Review found that a refresh which waits for the lock then fills again,
so after an ingest several instances repeat the same query one after
another. Within one deployment the holder's fill already refreshes the
shared entry, so the losers' failures are only log noise. Keep failing
fast and document the noise instead.

This reverts commit 1d5226f.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Will Manning <will@willmanning.io>
A chip change still waiting to be written was dropped when a same-origin
link navigated within 300 ms, so Back showed the URL without it. The
filter bar now writes it on the link's click, as it does on pagehide.

Drop the Renovate rule-order test: the pin-equality test is the one
that guards the pnpm refusal. Document the expected lock errors after
an ingest in read-path.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Will Manning <will@willmanning.io>
@vercel

vercel Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
vortex Ready Ready Preview Oct 30, 2026 3:35pm UTC

Request Review

On Vercel a Data Cache hit is a network read. On 2026-10-30, 1 of 12
cache-busted production renders of / missed the 150 ms wait and streamed
the loading view, a copy the CDN keeps for five minutes. 300 ms gives a
hit room; a miss still shows the loading view early.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Will Manning <will@willmanning.io>
@github-actions

github-actions Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

Bench preview for 1b001511461b2161fea5c2a9d627116a15643743: https://benchmarks-website-nhsb4otlb-vortex-data.vercel.app

@github-actions

github-actions Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

🔦 Lighthouse audit

URL Perf A11y Best practices SEO Report
/ 🟢 100 🟢 100 🟢 96 🟢 100 report
/blog 🟢 100 🟢 100 🟢 96 🟢 100 report
/blog/btrblocks-compressor 🟢 100 🟢 100 🟢 96 🟢 100 report

Run 41082070418 · 2026-10-30 15:38 UTC

Signed-off-by: Will Manning <will@willmanning.io>

# Conflicts:
#	bench/web/components/Header.tsx
@lwwmanning
lwwmanning marked this pull request as ready for review October 30, 2026 15:35
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-30T15:37:33.626901Z 1b00151 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@lwwmanning
lwwmanning merged commit aa9e8a3 into main Oct 30, 2026
21 checks passed
@lwwmanning
lwwmanning deleted the claude/bench-fixes branch October 30, 2026 15:45

This branch was successfully deployed

2 active deployments
bench-preview — 1b001511 Deployed Oct 30, 2026 by lwwmanning via Bench: deploy #162
Preview — 1b001511 Deployed Oct 30, 2026 by vercel[bot]
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