Skip to content

benchmark groups start collapsed, load charts when a group opens - #130

Draft
lwwmanning wants to merge 21 commits into
mainfrom
claude/bench-landing
Draft

lwwmanning wants to merge 21 commits into
mainfrom
claude/bench-landing

Conversation

@lwwmanning

@lwwmanning lwwmanning commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

Benchmark groups on the landing page start collapsed. Each group shows a one-line result from its summary, for example "DuckDB 2.5× faster". If Vortex is slower, the text says "N× slower". A group loads its charts only when it opens. The URL fragment lists the open groups, and a link with that fragment opens them. Reload and Back open the same groups and go back to the same scroll position.

These values are from a copy of the production database, with the CPU 4× slower:

  • Landing page HTML: 83.6 → 27.1 KB (gzip)
  • Charts that start at page load: 791 → 0
  • Total Blocking Time: 22 → 0 ms
  • Time until the groups show: 413 → 182 ms
  • First Contentful Paint: 124 → 184 ms, because the old page first showed a loading message
  • Expand All: 574 ms of blocking time (500 ms before)

The APIs do not change. The landing page screenshots change, as expected. With JavaScript off, the page shows only a loading message, as before.

🤖 Generated with Claude Code

@vercel

vercel Bot commented Oct 29, 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 29, 2026 7:33pm UTC

Request Review

Base automatically changed from claude/bench-cleanup to main October 29, 2026 07:20
@lwwmanning lwwmanning changed the title Fold bench's landing page and load charts when a group opens benchmark groups start collapsed, load charts when a group opens Oct 29, 2026
@lwwmanning
lwwmanning marked this pull request as ready for review October 29, 2026 07:23
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 29, 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-29T07:26:27.339130Z e149958 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.

Comment thread bench/web/components/GroupSection.test.tsx Fixed
Comment thread bench/web/components/GroupSection.test.tsx Fixed
@github-actions

github-actions Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

Bench preview for 511c3166d5f6c2f08bda8c608a771c519c18e6cc: https://benchmarks-website-gnuhbl7ro-vortex-data.vercel.app

lwwmanning and others added 17 commits October 29, 2026 15:30
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Will Manning <[email protected]>
Each group's summary row now ends with one line that says how Vortex
compares with Parquet, so a folded group still says something. A pure
function, groupHeadline in lib/headline.ts, reads the summary the group
already carries. It adds no query and changes no summary math.

A slower result reads in words ("4.0× slower"), not only through color.
Tone colors wait for the visual redesign. When the needed series are
missing, unmeasured or degenerate, the row shows no headline rather than
a misleading number.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Will Manning <[email protected]>
Each group's summary card and a new client GroupBody now render inside
the group's <details>. GroupBody server-renders as an empty div and
mounts the group toolbar and the chart grid only while the disclosure is
open; closing unmounts them and aborts the group's bundle. A folded
landing page therefore ships no chart markup and hydrates no chart
island. The loading machinery in lib/chart-store does not change.

lib/group-bodies.ts seeds a group's URL series overrides once per page
load, so a reopen keeps the user's changes, and lets later code wait
until the open groups' bodies have mounted and their bundles settled.
chartPriority names the page-wide load priority the islands already
used.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Will Manning <[email protected]>
The landing page's fragment now lists the open groups' anchors in page
order (#tpc-h-nvme-sf-1,clickbench). An inline script after the last
group opens them before the first paint. LandingState writes the
fragment on every toggle with replaceState, opens the listed groups
after a client navigation or a hashchange, and scrolls to the first
one once its body mounts. Expand All and Collapse All write once.

The group nav adds its group to the open set. A permalink copies a
link to its one group and no longer rewrites the address bar; it
shows "copied" only after the clipboard accepts the write. Old
single-anchor and opaque-slug links still open their group.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Will Manning <[email protected]>
groupBodiesReady kept a closed group noted, so after an open and a
close its `mounted` resolved at once and a waiter could keep the old,
aborted bundle. GroupBody now withdraws its note when it closes or
unmounts (noteGroupBodyClosed, from the noting effect's cleanup), so a
wait that starts later holds for the next mount and that mount's
bundle. A wait that already saw the body keeps its bundle. The cleanup
never aborts, so a StrictMode replay stays safe.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Will Manning <[email protected]>
The pre-paint script compared every fragment token with every group
section, reading `data-group-slug` for each miss, so a crafted
multi-megabyte fragment could stall the first paint. It now builds an
anchor lookup and a slug lookup once (first section per key, keys
prefixed so `__proto__` or `constructor` never reach Object.prototype),
skips repeated decoded tokens, and opens what it finds. Behavior is
unchanged: the parity test against the lib/open-groups helpers still
passes, now with prototype-named tokens too. New tests load 100k-token
fragments and bound the script's DOM calls by the number of sections.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Will Manning <[email protected]>
An open group's cards mount only after hydration, so the browser's own
scroll restoration lands against a shorter page. The landing page now
sets history.scrollRestoration to 'manual' while mounted (and puts the
old mode back on unmount) and restores the scroll itself
(lib/scroll-restore.ts):

- On pagehide, and when a chart link is clicked, it saves to
  sessionStorage, keyed by path and fragment, the first chart card in
  view below the sticky header (or else the first group section) and
  its offset from the viewport top. Storage that is blocked or full
  never throws.
- On mount, planScroll chooses. A reload or Back/Forward with a saved
  position restores it: once the open bodies mount, and again one
  frame after their bundles settle. Wheel, touch, key or pointer input
  stops it. If the element is gone, it falls back to the fragment
  scroll. A fresh visit, or nothing saved, scrolls to the first listed
  group as before.

A hashchange now also rewrites the fragment to the whole open set (a
group already open fires no toggle) and ends a pending restore.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Will Manning <[email protected]>
navigationKind read the document's navigation entry, but the chart
page's logo link renders `/` inside the chart page's document. After a
reload or a Back to that chart page, the landing page saw 'reload' or
'back_forward' and restored an old position on what was a fresh visit.
An entry now counts only when its address has the current path;
another path, no address or one that does not parse reads as
'navigate'.

Leaving the page before a restore applies no longer overwrites the good
save with the top of the page. restoreScroll takes an optional
onPendingEnd, called once when its first pass applies, it falls back,
or it stops first. Until then pagehide saves the position being
restored (saveScroll takes an optional position).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Will Manning <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Will Manning <[email protected]>
The islands now mount into an open group and unmount on close, so
teardown runs through effect cleanup and destroy(), not the toggle event.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Will Manning <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Will Manning <[email protected]>
Safari throws on a document's 101st history.replaceState call in 10
seconds. Each group toggle made two calls: ours, then Next.js's own
re-sync, which is not caught. About 50 fast toggles replaced the
landing page with Next.js's error page.

A toggle after 300 ms of quiet still writes at once. Inside that time
the write waits until the toggles stop, so a burst is two writes and
writes stay at least 300 ms apart. A hashchange drops a pending write
before it reads the new fragment, and unmount drops it too. A chart
link click makes a pending write before it saves the scroll. pagehide
saves the scroll under the current URL, which a reload loads, then
makes the write and saves again under the new fragment for Back.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Will Manning <[email protected]>
A query or random-access headline now says "(partial)" when it leaves
out an engine, or a hot or cold side, that ran Parquet but not Vortex.
Before, that result was dropped without a word. An engine that ran
only Vortex is still left out silently.

A size reads as a multiple outside half to twice Parquet's: "2.0×
larger" once the printed percentage would reach 100 (from 1.995), and
"2.0× smaller" from a ratio of 0.5 down. No size reads "100% larger"
or "100% smaller" any more.

Tests now also cover random access with no Parquet run, an unknown
engine's label and order, a vector-search tie and NaN score, and the
9.95× and 20× display boundaries. None of the 23 groups on the local
production dump changes its wording.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Will Manning <[email protected]>
Before hydration, a reader can already open or close groups, and scroll.
LandingState overrode both:

- On mount it reopened every group the fragment lists, so a group the
  reader closed before hydration opened again. A group opened before
  hydration reached the fragment only with the next toggle.
- A Reload or Back restore moved a reader who had already scrolled.

The pre-paint script now leaves a marker on its document, after it opens
the groups. One-shot passive capture listeners (wheel, touchstart,
keydown, pointerdown) set its input flag. A scroll does not set it, so
the browser's jump to a single-anchor fragment is not input.
LandingState takes the marker on its first mount, once per document. It
then opens nothing, writes the fragment once from what is open, and
skips a restore when the flag is set, as input after mount would. A
client navigation to / finds no marker and opens the groups from the
fragment, as before.

Also:

- Save the scroll position when the tab goes hidden too, through the
  pagehide path. A mobile browser can discard a hidden tab without a
  pagehide.
- A scroll that no click asked for (a fresh visit, a restore's fallback,
  a new fragment) now jumps. Only the group nav scrolls smoothly.
- Space fragment writes on performance.now(), not Date.now(). A wall
  clock that jumps forward let two writes come closer than 300 ms.
- Drop a pending fragment write when the path changed since it was
  scheduled, as when the fragment changed.
- Remove the wall-clock limit from the bootstrap's linear-work test. The
  DOM-call counts are the bound.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Will Manning <[email protected]>
A group with no charts mounts no chart island, so it has no bundle to
wait for. Its body now requests nothing on open, and the scroll
restorer's wait for it resolves at once.

Rename a GroupBody test that claimed to pin the once-per-page-load seed
of the URL's series overrides. The body stays mounted across a close, so
the test passes without that guard; lib/group-bodies.test.ts pins it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Will Manning <[email protected]>
With JavaScript off, / shows only the loading card: the groups stream
inside the Suspense boundary's hidden segment, which only React's inline
script reveals. This predates the folded page. Comments in GroupSection,
SummaryCard and GroupPermalink claimed behavior "with or without
JavaScript". They now say what holds: before hydration, with JavaScript
on. page.tsx says why its <noscript> hint never shows in a browser. The
hint stays where it is: outside the boundary it would need a wrapper and
would sit outside the page layout, above the header or below the footer.

performance.md's folded-page section now:

- names OpenGroupsScript and the pre-hydration marker;
- says why first contentful paint rose 124 -> 184 ms: the previous build
  first paints its loading card, which the new page no longer needs;
- says what the reader sees on Reload and Back (1-2 frames of the page
  top) and where the 1.4-1.8 layout-instability score comes from;
- gives the conditions: local dump, no network throttling, CPU 4x,
  median of 5 runs (Expand All: 3), one-off scripts, and the previous
  build (the claude/bench-cleanup head, b4caf7c);
- says Expand All mounts its islands in the toggle tasks after the
  click.

Chart.tsx's header adds GroupBody's mount at hydration for a group that
is already open. Header.tsx no longer says Expand All writes the
fragment once; writes are scheduled and coalesced.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Will Manning <[email protected]>
Input before hydration means the reader has taken over the scroll. The
pre-paint script's input flag stopped only the Reload and Back restore.
On a fresh visit to a link that lists groups, the page still jumped to
the first listed group after hydration, away from where the reader had
scrolled.

planScroll now returns "none" whenever the flag is set: no restore, no
fallback, and no fragment scroll. A scroll alone is still not input, so
the browser's jump to a single-anchor fragment changes nothing.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Will Manning <[email protected]>
lwwmanning and others added 4 commits October 29, 2026 15:30
The design and plan documents for the folded landing page were working
notes. The code, bench/docs/architecture/performance.md and the PR record
what they decided.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Will Manning <[email protected]>
The first run of LandingState's effect may rewrite the fragment, which
keys the saved position and orders the listed groups. Keep that run's
reads in a ref so the replay in development restores the same position.
Name a click as input before hydration in the comments that list it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Will Manning <[email protected]>
CodeQL flags the tag-stripping regex as incomplete HTML sanitization.
The tests now take the summary row's textContent instead.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Will Manning <[email protected]>
A collapsed group showed only its header row. The summary card now sits
after the group's <details>, inside its section, so it shows whether the
group is open or not. The toolbar and charts still mount only when the
group opens. The card was already in the HTML, so the page ships the same
bytes. Comments and docs now say "collapsed", not "folded".

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Will Manning <[email protected]>

This branch was successfully deployed

2 active deployments
bench-preview — 511c3166 Deployed Oct 29, 2026 by lwwmanning via Build & deploy to Vercel #71
Preview — 511c3166 Deployed Oct 29, 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.

2 participants