Repository navigation
benchmark groups start collapsed, load charts when a group opens - #130
Draft
lwwmanning wants to merge 59 commits into
Draft
lwwmanning wants to merge 59 commits into
lwwmanning wants to merge 59 commits into
Conversation
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Will Manning <will@willmanning.io>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Will Manning <will@willmanning.io>
bench/scripts/parity/run.sh serves a base git ref and the working tree side by side with `next start`, against one Postgres restored from a production dump, and compares them. compare-api.ts requests every group and chart route from both servers and compares status, content-type, cache-control and body, printing the route and first differing JSON path with both values. screenshots.spec.ts compares the landing page and five chart pages pixel by pixel at desktop and phone widths, and Playwright keeps the diff images. The database runs from a local Postgres 16 data directory, since the integration suite's container is not required. Group-cache fills run one at a time because they share a database-wide advisory lock, and each server starts with an empty Data Cache. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Will Manning <will@willmanning.io>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Will Manning <will@willmanning.io>
Every URL the API check requests comes from the base's own /api/groups, so a 404, a redirect or an empty body on either side now fails with "nothing was compared for this route", even when both servers agree on it. Slugs are percent-encoded, and the summary line tallies the statuses and body bytes that were compared. run.sh deletes the whole .next/cache of each side before it starts a server, so a Next.js version that keeps Data Cache payloads somewhere other than fetch-cache cannot serve payloads made by older code. The README and config comment now say what the screenshot comparison does: Playwright's comparator ignores pixels it classes as anti-aliasing, and any other differing pixel, or a layout shift of 1px or more, fails. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Will Manning <will@willmanning.io>
GitHub's dependency graph does not read bun.lock, so dependency-review sees none of the site's transitive packages. scripts/dependency-snapshot.ts turns the lock into a snapshot for the Dependency Submission API, with one manifest per workspace. A package is runtime when a workspace's dependencies or optionalDependencies reach it, and development otherwise. Optional peers add no edge: Next.js lists Playwright as one, and following it would mark Playwright runtime. Every package in the current lock is in the snapshot, and every open high advisory in `bun audit` is development scope. The dependency-snapshot workflow runs it on pushes to main and on pull requests from this repository, with contents: write on that job only and no bun install. dependency-review now retries for up to 300 seconds while the snapshot arrives. fail-on-scopes stays at the action's default, runtime. CLAUDE.md layer 5 says so. The tests use node:test, so the root tsc needs no bun types. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Will Manning <will@willmanning.io>
The submission script now waits 5 seconds and tries once more after a network error or a 5xx answer. Any other status, and a second failure, still exits 1. The wait can be set with SNAPSHOT_RETRY_DELAY_MS, which the tests use. The token is never logged. The workflow groups a push to main by commit, so a burst of pushes cannot cancel or replace a pending run and leave a commit without a snapshot. Pull requests keep one group per ref and cancel the older run. New tests run the script against a local mock of the API: 503 then 201, 503 twice, 422 with no retry, and a refused connection. Another test covers a workspace whose path (bench/web) differs from its package name (benchmarks-web), with a workspace-keyed copy of a package, a hoisted copy, and a decoy keyed by path. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Will Manning <will@willmanning.io>
bench/web joins the root Bun workspace. One bun.lock replaces bench/web/pnpm-lock.yaml, and bench/web/pnpm-workspace.yaml goes: - The zod override (^4.6.7) moves to the root package.json. It also moves Lighthouse CI's chromium-bidi to zod 4, as decided, and clears the site's accepted zod advisory. The sharp override was already there. The postcss override is not carried over: Next.js 16.4.1 depends on postcss 8.5.23, which is patched, so until bench moves to Next.js 16 its next 15.5.28 brings postcss 8.4.31. CLAUDE.md lists those advisories under "bench advisories". - undici 7.30.1 is still inside the 14-day window, so bunfig.toml exempts undici until 2026-10-29 14:18 UTC, with the reason from the pnpm file. Every other version pnpm had locked is past the window. - The lockfile keeps pnpm's versions for every package only bench uses. The rest are the site's copies, which Bun dedupes onto. The site's resolution changes only in zod 3.25.76 -> 4.6.7. bunfig.toml sets linker = "isolated", Bun's default for a workspace. A hoisted tree hands bench's typescript-eslint the site's TypeScript 7, which it rejects. publicHoistPattern puts ESLint and Prettier packages in the root node_modules, where bench's ESLint config finds its plugins, as pnpm's default did. next.config.js traces from the repository root, where the packages now live. Every route traces the same packages at the same versions as the pnpm build on main. The bench-checks action, bench-ci.yml and bench-deploy.yml install with `bun install --frozen-lockfile` at the root and run bench's scripts with `bun run --cwd bench/web`. bench/web/vercel.json sets the same install command for `vercel build`, so the deploy does not depend on the project's settings. bench-deploy.yml also runs on changes to the root package.json, bun.lock and bunfig.toml. changed-paths counts the workspace's root files for both apps. CLAUDE.md and bench's docs use the Bun commands. trustedDependencies stays ["esbuild", "sharp"]. pnpm also let unrs-resolver build; its postinstall only checks for the native binding, which Bun installs as an optional dependency. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Will Manning <will@willmanning.io>
bench moves to next 16.4.1 and react/react-dom ^19.3.1, the site's
versions, so both apps now share one copy. Next.js 16.4.1 depends on
postcss 8.5.23, which clears the four postcss advisories that next
15.5.28's postcss 8.4.31 carried; no postcss override is needed.
- POST /api/revalidate calls revalidateTag(BENCH_DATA_TAG, { expire: 0 }).
That is the documented equivalent of the old one-argument call; 'max'
would serve stale data once after an ingest. A new test pins it.
- Other bumps, each the newest version at least 14 days old: pg and
@types/pg 8.23.1, jsdom 30.1.2, vitest 5.0.4 (clears the two
@vitest/mocker advisories), eslint-config-next 16.4.1, @types/node
^24.19.2, @types/react(-dom) ^19.3.0, @vercel/functions ^3.10.1
(already locked). chart.js stays at 4.5.1.
- jsdom 30 uses undici 8. The newest 8.x past the window, 8.12.1, has
four high advisories fixed in 8.12.2, so the existing undici exemption
now covers 8.12.2 as well; the lock pins 8.12.2, and the exemption
list's removal time moves to 2026-10-29 15:06 UTC.
- The lock keeps @types/pg on the shared @types/node 24.19.2, as the
workspace move did.
- eslint-config-next 16 ships flat config, so eslint.config.mjs imports
it directly instead of through FlatCompat. Two new React Compiler rules
from eslint-plugin-react-hooks 7 are off, so the upgrade needs no code
change.
- next build (Turbopack, now the default) set tsconfig's jsx to
react-jsx and added .next/dev/types; committed so builds stay clean.
- Docs: bench/web/README.md, the architecture pages that describe
revalidateTag, the postcss.config.mjs and vitest.config.ts comments,
bunfig.toml, and CLAUDE.md (toolchain, audit counts 17 -> 11, the
bench advisories list).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Will Manning <will@willmanning.io>
These two files were outside Prettier's glob and ran past 100 columns. Biome's check now covers them, so this is the one-time mechanical reformat, kept apart from the Biome switch. No logic changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Will Manning <will@willmanning.io>
The root biome.json now covers bench. A bench/** override keeps its Prettier style (single quotes, trailing commas, 100 columns) and turns useExhaustiveDependencies on, in place of ESLint's react-hooks rule. It turns off five rules that fire on deliberate markup and CSS, and import sorting. bench/AGENTS.md gives the reasons, because biome.json cannot hold comments. Lint fixes keep behavior: a block around a switch case, an explicit type on one let, braces on void forEach callbacks, an optional chain, a template string, an import type. Two ESLint disable comments for exhaustive-deps become biome-ignore, and a third effect gets one. Stale ESLint disable comments are gone. Remove eslint, eslint-config-next, @eslint/eslintrc and prettier with their config files. Their plugins were the only reason for publicHoistPattern in bunfig.toml, so it goes too; a clean install, the suite and both builds pass without it. Fix the stale Next.js, TypeScript and ESLint statements in bunfig.toml and CLAUDE.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Will Manning <will@willmanning.io>
bench's format and lint steps covered only bench/web, and ci.yml skips the root check:ci on bench-only changes, so bench/scripts went unchecked until the next site PR. One step, bun run biome ci bench, replaces both: it uses the workspace's pinned Biome with the root config and covers web, scripts and the parity harness. The job name does not change. Fix two notes in bench/AGENTS.md: organizeImports would rewrite 27 files (25 in bench/web, 2 in bench/scripts), and the site config also turns off noExplicitAny, so bench's old no-explicit-any check is gone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Will Manning <will@willmanning.io>
Move bench/web to typescript ^7.0.2, as at the root, so both apps run the Go-based compiler. The tsconfig options the two apps shared now live in a root tsconfig.base.json (strict, noEmit, esModuleInterop, module, moduleResolution, resolveJsonModule, isolatedModules, skipLibCheck, allowJs, incremental, noUnusedLocals, noUnusedParameters, lib, jsx and the next plugin). Each tsconfig.json extends it and keeps its own target, paths and extra flags: the site its noUncheckedIndexedAccess and exactOptionalPropertyTypes, bench its noFallthroughCasesInSwitch and noUncheckedSideEffectImports. `tsc --showConfig` gives the same effective options for both apps as before. TypeScript 7 reports no errors in bench under the existing flags, so no source changes. bench-deploy.yml now also runs when tsconfig.base.json changes, since bench's build extends it. bunfig.toml and CLAUDE.md no longer say bench keeps its own TypeScript 5.9. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Will Manning <will@willmanning.io>
The bench override in biome.json turned four a11y rules off for all of bench. New UI would then skip them unseen. Turn them back on and put a biome-ignore comment with its reason at each of the seven existing places. The markup does not change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Will Manning <will@willmanning.io>
…heck The parity check compared only the read API and light-theme screenshots. It now also requests the landing page and the five chart pages the screenshots use, and compares their status, content-type, cache-control and title. It probes an unknown chart slug (404) and a malformed slug (400 from the API, 404 from the page), and accepts only those expected statuses. Every other URL must still answer 200. The screenshots run in the dark theme too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Will Manning <will@willmanning.io>
bunfig.toml gave a reason for the isolated linker that no longer holds, now that both apps share TypeScript and ESLint is gone. State the reason that does hold. vitest.config.ts said Next.js owns tsconfig's jsx setting; it now comes from tsconfig.base.json. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Will Manning <will@willmanning.io>
bench-checks lost its dependency cache when it moved from pnpm to Bun. Restore it with the same cache step as ci.yml. Run the parity check's own tests there too, by explicit path, so its planning and comparison logic is checked on every bench change. Correct a comment that said Biome checks bench's imports; import sorting is off for bench. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Will Manning <will@willmanning.io>
Dependency review passed without bun.lock's packages when the snapshot job failed, was skipped for a fork, or finished after the action's retry window. A guard now runs before the action. For a pull request from this repository, it waits up to 10 minutes for the snapshot job on the head commit and fails unless that job succeeded. For a fork, which gets no snapshot, it fails when the pull request changes a bun.lock. The job gains checks: read, and its checkout no longer keeps the token. The snapshot job now checks out the pull request's head commit, so the graph it files under that commit holds the head's own bun.lock. CLAUDE.md describes the guard, says that development-scope advisories (bench's included) no longer block merge and show in bun audit, and gives the real fallback for the zod override. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Will Manning <will@willmanning.io>
Give the publish times behind the statement that next 16.4.1 and react 19.3.1 were past the cooldown when bench moved to them, and say how the "13 days old" for the site differs. Rewrite the vite note, which still described pnpm. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Will Manning <will@willmanning.io>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Will Manning <will@willmanning.io>
The database suites needed Docker, so they skipped on a Mac without it. Set BENCH_TEST_DATABASE_URL to a Postgres server and each suite creates a throwaway bench_test_* database there, applies the migrations, and drops the database when it ends. A Postgres advisory lock serializes the first applies, because the migrations create cluster-wide roles. Without the URL, the suites use testcontainers as before. When CI=true and neither Docker nor the URL is available, the suites now throw instead of skipping, so a broken runner cannot pass with them off. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Will Manning <will@willmanning.io>
Production signs in with a static BENCH_DB_PASSWORD for the read-only bench_read role, which has no rds_iam grant (migration 005). The IAM path in lib/db.ts never ran there. getPool() now requires BENCH_DB_PASSWORD through requireEnv, so a missing or empty value fails at first use with a clear message. passwordProvider, the region setting and the rds-signer import are gone, and the pool gets the password as a string. DbConfig stops being exported. Remove @aws-sdk/rds-signer. bun.lock loses only the signer and its transitive packages; nothing else is re-resolved. The README and the deploy runbook now list BENCH_DB_PASSWORD as required and drop BENCH_DB_REGION. BENCH_DB_REGION on the benchmarks-website Vercel project is now unused. Deleting it is a settings change for Will, so this commit leaves it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Will Manning <will@willmanning.io>
collectGroupCharts called chartPayload once per chart, two queries each: about 200 round trips for a 99-chart TPC-DS group. groupChartPayloads builds the same payloads from one statement for the last n commits and one for every chart's rows, a LATERAL join per chart. A chart's x-axis is the last min(n, total) commits of that shared window, so the JSON is byte-identical. chartPayload is unchanged and is the tests' oracle. On one connection the brief's SQL lost to the old 7-wide fan-out, so: - The commit count is an aggregate lateral plus a width_bucket binary search. A scalar subquery was flattened and ran once per output row. - A query chart's first row is a skip scan of idx_query_measurements_summary. MIN read ~27K heap rows per chart. - The compression families split charts by NULL variant, so each pin is sargable. IS NOT DISTINCT FROM planned a semi join per fact row. - Rows come in slices of 25 charts, one after another. One result for TPC-DS at ?n=all peaked at 1.6x the old memory. On the 2026-10-28 dump every group and window matches the old path. At n=100, warm: TPC-DS (NVMe) SF=1 1587 -> 321 ms, Clickbench 865 -> 143 ms, Compression 70 -> 120 ms. At ?n=all, TPC-DS takes 5.3 s against 4.9 s, with peak memory at most 1.34x the old. QUERY_CONCURRENCY now bounds only the summaries in collectGroups. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Will Manning <will@willmanning.io>
groupChartPayloads ran the commit window and the first rows slice on two pooled connections, and later slices after them, each in its own READ COMMITTED snapshot. An ingest landing between them could give payloads the per-chart path never could: a commit counted in total_commits but missing from commits[], or a short chart losing its own first commit. Now the window and every rows slice run one after another on one client, in a REPEATABLE READ, READ ONLY transaction, rolled back on error, with the client always released. The clamp that patched one direction of the skew is gone. A group still runs two statements for up to 25 charts; a TPC-DS group (99 charts) runs five. The group fill now holds one more pool connection, after the summaries finish. New window-edge tests pin byte-equality with chartPayload for a random-access group, a vector-search group with a 0.1 threshold, and a compression-size group with a non-NULL variant block, with per-chart windows. The statement-count tests now read the pooled client. performance.md states the ?n=all memory as measured: under 1.5x, 1.2 to 1.8 GB against 1.3 GB. On the 2026-10-28 dump every group still matches the old path at n=100 and n=50. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Will Manning <will@willmanning.io>
Move lib/chart-store.ts into lib/chart-store/: queues, bundles, global-filter and group-store, with index.ts re-exporting each module so '@/lib/chart-store' keeps its export surface. The code moves verbatim. group-store reads the global filter through getGlobalFilterSnapshot() instead of the module variable. Add a pin test for the chart island before it is split: under StrictMode, one wheel event pans once, and unmount removes the listener. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Will Manning <will@willmanning.io>
Move the ChartController class and its helpers out of components/Chart.tsx into components/chart/: types, datasets, tooltip, config, card-dom, pan-zoom and controller. Chart.tsx keeps the 'use client' boundary, the architecture notes, ChartIslandProps and the Chart component. The code moves verbatim. The moved private methods (the slider, badge and window-chip renderers, wheel pan and the range strip) take the state, elements and abort signal as parameters, and the controller keeps a delegator with the same name for each. maybeConstruct gets the Chart.js config from lineChartConfig(). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Will Manning <will@willmanning.io>
PALETTE, colorFor, assignStableColors and labelForCommit in lib/chart-format.ts, and commitWindowUrlValue in lib/window.ts, have no importer outside their own tests. Delete them and their six tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Will Manning <will@willmanning.io>
Move noUncheckedIndexedAccess and exactOptionalPropertyTypes from the site's tsconfig.json into tsconfig.base.json, so bench inherits them. The site's effective config does not change. TypeScript 7 reported 162 errors in bench: 41 in app code, 121 in tests. The fixes change types only: - Optional properties that receive undefined get `| undefined`. - An index access gets `!` only where the index is in range by construction. commitDateLabel gets an explicit undefined check that returns '' as before. - Chart.js's types do not let a line chart or its datasets widen to the generic types under exactOptionalPropertyTypes, so three casts at that boundary bridge them. - Tests use `!`, or `?.` where Biome forbids `!` after `?.`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Will Manning <will@willmanning.io>
lib/chart-store.ts is now lib/chart-store/, and the Chart.js wiring left components/Chart.tsx for components/chart/. Fix the links and file references in AGENTS.md, performance.md (two links were broken) and design-decisions.md, and the chart-format.ts header. Two moved comments named the wrong place: config.ts now names pan-zoom.ts for the wheel listener, and card-dom.ts names renderDownsampleBadge. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Will Manning <will@willmanning.io>
payloads[i]! in collectGroupCharts and values[vi]! in normalizeChartPayload removed null from the type as well as undefined. A chart with no rows is null, so the null check that follows looked dead to the compiler. Cast to the element type instead; this removes only undefined. Also state the parallel-length precondition of lttbIndices, pin the undefined-month and undefined-day paths of commitDateLabel with a test, and correct CLAUDE.md: the two strict flags now live in tsconfig.base.json. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Will Manning <will@willmanning.io>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Will Manning <will@willmanning.io>
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 <noreply@anthropic.com> Signed-off-by: Will Manning <will@willmanning.io>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Will Manning <will@willmanning.io>
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 <noreply@anthropic.com> Signed-off-by: Will Manning <will@willmanning.io>
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 <noreply@anthropic.com> Signed-off-by: Will Manning <will@willmanning.io>
The design and plan documents for the toolchain move and the cleanup were working notes for those changes. The code, the architecture docs and CLAUDE.md now record what they decided. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Will Manning <will@willmanning.io>
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 <noreply@anthropic.com> Signed-off-by: Will Manning <will@willmanning.io>
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 <noreply@anthropic.com> Signed-off-by: Will Manning <will@willmanning.io>
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 <noreply@anthropic.com> Signed-off-by: Will Manning <will@willmanning.io>
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 <noreply@anthropic.com> Signed-off-by: Will Manning <will@willmanning.io>
Brings in main's merge of the toolchain PR and the removal of the working plans. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Will Manning <will@willmanning.io>
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 <noreply@anthropic.com> Signed-off-by: Will Manning <will@willmanning.io>
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
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 <noreply@anthropic.com> Signed-off-by: Will Manning <will@willmanning.io>
lwwmanning
marked this pull request as ready for review
October 29, 2026 07:23
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
lwwmanning
had a problem deploying
to
bench-preview
October 29, 2026 07:24 — with
GitHub Actions
Error
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 <noreply@anthropic.com> Signed-off-by: Will Manning <will@willmanning.io>
|
Bench preview for |
lwwmanning
marked this pull request as draft
October 29, 2026 07:30
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 <noreply@anthropic.com> Signed-off-by: Will Manning <will@willmanning.io>
This branch was successfully deployed
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.
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:
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