From 3760bc889f45263c31002c11788557f30c9231e6 Mon Sep 17 00:00:00 2001 From: Will Manning Date: Wed, 28 Oct 2026 07:18:16 +0100 Subject: [PATCH 01/73] Apply the Vortex design system's skin to bench - Load vortex-skin.css after globals.css: the Blue accent, warm greys, 6px cards and 4px controls. It sets only visual properties, and app/vortex-skin.test.ts holds it to that. - Load Geist and Geist Mono with Funnel Display in one font request. - Replace the Lucide icons with Material Symbols Sharp, in one shared icons module. The GitHub mark stays. - Write the Compression Size headings as words, without emoji. Co-Authored-By: Claude Opus 5.5 Signed-off-by: Will Manning --- bench/web/app/layout.tsx | 21 ++-- bench/web/app/vortex-skin.css | 101 ++++++++++++++++++ bench/web/app/vortex-skin.test.ts | 69 ++++++++++++ bench/web/components/FilterBar.tsx | 21 +--- bench/web/components/GroupDocsLink.tsx | 24 +---- bench/web/components/GroupNav.tsx | 26 +---- bench/web/components/GroupPermalink.tsx | 42 +------- bench/web/components/GroupToolbar.tsx | 2 +- bench/web/components/Header.tsx | 124 +++------------------- bench/web/components/SummaryCard.test.tsx | 7 +- bench/web/components/SummaryCard.tsx | 6 +- bench/web/components/icons.test.tsx | 52 +++++++++ bench/web/components/icons.tsx | 80 ++++++++++++++ bench/web/lib/fonts.test.ts | 32 ++++++ bench/web/lib/fonts.ts | 10 ++ 15 files changed, 380 insertions(+), 237 deletions(-) create mode 100644 bench/web/app/vortex-skin.css create mode 100644 bench/web/app/vortex-skin.test.ts create mode 100644 bench/web/components/icons.test.tsx create mode 100644 bench/web/components/icons.tsx create mode 100644 bench/web/lib/fonts.test.ts create mode 100644 bench/web/lib/fonts.ts diff --git a/bench/web/app/layout.tsx b/bench/web/app/layout.tsx index 9543d34..0274925 100644 --- a/bench/web/app/layout.tsx +++ b/bench/web/app/layout.tsx @@ -4,7 +4,11 @@ import type { Metadata, Viewport } from 'next'; import type { ReactNode } from 'react'; +import { FONTS_CSS } from '@/lib/fonts'; + import './globals.css'; +// The Vortex design system's skin: colours, corners and Geist over globals.css. +import './vortex-skin.css'; export const metadata: Metadata = { title: 'Vortex Benchmarks', @@ -39,21 +43,18 @@ export const viewport: Viewport = { // silently keeping the default prefers-color-scheme theme. const THEME_BOOTSTRAP = `(function(){try{var t=localStorage.getItem("bench-theme");if(t==="light"||t==="dark"){document.documentElement.dataset.theme=t;}}catch(e){}})();`; -const FUNNEL_DISPLAY_CSS = - 'https://fonts.googleapis.com/css2?family=Funnel+Display:wght@400;500;600;700&display=swap'; - -// This is a hack to load Funnel font without blocking page paint. The URL is -// written out here rather than interpolated from FUNNEL_DISPLAY_CSS, so the -// inline script is a constant and never builds code from a value. Keep the two -// in step. +// This is a hack to load the web fonts without blocking page paint. The URL is +// written out here rather than interpolated from FONTS_CSS, so the inline +// script is a constant and never builds code from a value. lib/fonts.test.ts +// fails if the two drift apart. const FONT_BOOTSTRAP = '(function(){var l=document.createElement("link");l.rel="stylesheet";' + - 'l.href="https://fonts.googleapis.com/css2?family=Funnel+Display:wght@400;500;600;700&display=swap";' + + 'l.href="https://fonts.googleapis.com/css2?family=Funnel+Display:wght@400;500;600;700&family=Geist:wght@400;500;600&family=Geist+Mono:wght@400;500;600&display=swap";' + 'document.head.appendChild(l);})();'; /** * Root layout: the ``/`` shell plus the global stylesheet, the - * pre-paint theme bootstrap, and the heading web font. + * pre-paint theme bootstrap, and the web fonts. */ export default function RootLayout({ children }: { children: ReactNode }) { return ( @@ -64,7 +65,7 @@ export default function RootLayout({ children }: { children: ReactNode }) { `, + it('renders the constant scripts as they are, before the first group and after the last', () => { + expect( + renderToStaticMarkup( + +
+ , + ), + ).toBe( + `` + + '
' + + ``, ); }); - it('is ES5 wrapped in a try, so no browser fails to parse it and no error escapes', () => { - expect(OPEN_GROUPS_BOOTSTRAP).toMatch(/^\(function\(\)\{try\{.*\}catch\(e\)\{\}\}\)\(\);$/); - expect(OPEN_GROUPS_BOOTSTRAP).not.toMatch(/=>|`|\blet\b|\bconst\b|\bclass\b/); - }); + it.each([ + ['bootstrap', OPEN_GROUPS_BOOTSTRAP], + ['finish step', OPEN_GROUPS_FINISH], + ])( + 'keeps the %s ES5 wrapped in a try, so no browser fails to parse it and no error escapes', + (_, script) => { + expect(script).toMatch(/^\(function\(\)\{try\{.*\}catch\(e\)\{\}\}\)\(\);$/); + expect(script).not.toMatch(/=>|`|\blet\b|\bconst\b|\bclass\b/); + }, + ); it('opens the listed groups while the page parses', () => { const { doc, errors } = load('#clickbench,tpc-h-nvme-sf-1'); @@ -215,4 +232,115 @@ describe('OpenGroupsScript', () => { expect(errors).toEqual([]); }); }); + + // On a slow connection the browser paints the first groups before the rest + // have arrived, so each listed group must open as soon as it is parsed. The + // observer runs at the end of the parser task that inserted the group, + // before the browser can paint it; `settle` lets it run. + describe('while the page is still arriving', () => { + const SECTION_HTML = SECTIONS.map((section) => renderToStaticMarkup(section)); + + /** A page whose `
` holds only the bootstrap, as a browser has it + * before the first group arrives. `append` adds markup to the end of + * `
`, as the parser does, and `finish` runs the finish step. */ + function stream(hash: string) { + const virtualConsole = new VirtualConsole(); + const errors: unknown[] = []; + virtualConsole.on('jsdomError', (e) => errors.push(e)); + const dom = new JSDOM( + `
`, + { url: `https://bench.vortex.dev/${hash}`, runScripts: 'dangerously', virtualConsole }, + ); + const doc = dom.window.document; + const main = doc.querySelector('main'); + if (main === null) { + throw new Error('no main'); + } + return { + window: dom.window, + doc, + main, + errors, + append: (html: string) => main.insertAdjacentHTML('beforeend', html), + settle: () => new Promise((resolve) => setTimeout(resolve, 0)), + finish: () => { + const script = doc.createElement('script'); + script.textContent = OPEN_GROUPS_FINISH; + main.appendChild(script); + }, + }; + } + + it('opens a listed group as soon as it is parsed, before the later groups arrive', async () => { + const page = stream('#random-access'); + page.append(SECTION_HTML[0]!); + await page.settle(); + expect(openAnchors(page.doc)).toEqual([]); + + page.append(SECTION_HTML[1]!); + await page.settle(); + expect(openAnchors(page.doc)).toEqual(['random-access']); + // The marker waits for the finish step. + expect((page.doc as unknown as Record).__benchOpenGroups).toBeUndefined(); + + page.append(SECTION_HTML[2]!); + page.finish(); + expect(openAnchors(page.doc)).toEqual(['random-access']); + expect(takeOpenGroupsBootstrap(page.doc)).toEqual({ input: false }); + expect(page.errors).toEqual([]); + }); + + it('opens a listed group whose disclosure arrives after its section', async () => { + const page = stream('#random-access'); + const section = page.doc.createElement('section'); + section.id = 'random-access'; + section.setAttribute('data-group-slug', 'random_access.eyJ'); + page.main.appendChild(section); + await page.settle(); + + const details = page.doc.createElement('details'); + details.className = 'group-disclosure'; + section.appendChild(details); + await page.settle(); + expect(details.open).toBe(true); + }); + + it('keeps closed a listed group the reader closed before the last group arrived', async () => { + const page = stream('#tpc-h-nvme-sf-1,clickbench'); + page.append(SECTION_HTML[0]!); + await page.settle(); + expect(openAnchors(page.doc)).toEqual(['tpc-h-nvme-sf-1']); + + const first = page.doc.querySelector('details.group-disclosure'); + if (first === null) { + throw new Error('no disclosure'); + } + first.open = false; + page.append(SECTION_HTML[1]! + SECTION_HTML[2]!); + await page.settle(); + page.finish(); + expect(openAnchors(page.doc)).toEqual(['clickbench']); + }); + + it('opens a group the fragment lists by its slug only at the end, when no group can still claim it as an anchor', async () => { + const page = stream(`#${encodeURIComponent('random_access.eyJ')}`); + for (const html of SECTION_HTML) { + page.append(html); + await page.settle(); + } + expect(openAnchors(page.doc)).toEqual([]); + + page.finish(); + expect(openAnchors(page.doc)).toEqual(['random-access']); + }); + + it('records input from before the last group arrived', () => { + const page = stream('#clickbench'); + page.append(SECTION_HTML[0]!); + page.window.dispatchEvent(new page.window.Event('keydown')); + page.append(SECTION_HTML[1]! + SECTION_HTML[2]!); + page.finish(); + expect(takeOpenGroupsBootstrap(page.doc)).toEqual({ input: true }); + }); + }); }); diff --git a/bench/web/components/OpenGroupsScript.tsx b/bench/web/components/OpenGroupsScript.tsx index cf9ee0e..f5da2ce 100644 --- a/bench/web/components/OpenGroupsScript.tsx +++ b/bench/web/components/OpenGroupsScript.tsx @@ -1,6 +1,8 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: Copyright the Vortex contributors +import type { ReactNode } from 'react'; + /** * The pre-paint bootstrap for the landing page's open groups: it opens the * groups the URL fragment lists before the browser first paints them, so a @@ -9,112 +11,184 @@ * the open groups' bodies mount, `LandingState` restores it by element * (`lib/scroll-restore.ts`). * + * The groups are visible HTML as they arrive, and on a slow connection the + * browser paints the first ones before the rest have arrived. So the script + * runs before the first group, and opens each listed group as soon as the + * parser has inserted it: a `MutationObserver` callback runs at the end of the + * parser task that inserted the nodes, before the browser can paint them. + * [`OPEN_GROUPS_FINISH`], after the last group, ends the work. + * * It is a constant ES5 string with nothing interpolated into it. The only * data it reads is `location.hash` (its input listeners, below, record only - * that input happened). It first builds two lookups from the group - * sections, each keeping the first section per key: the section `id` (the - * readable anchor) and its `data-group-slug` (the opaque slug the first - * permalinks carried). Keys carry a `:` prefix, so a token such as - * `__proto__` or `constructor` never reads an `Object.prototype` member. It - * then splits the fragment on `,`, decodes each token in its own `try` (a - * malformed token is skipped), skips empty and repeated tokens, and looks the - * token up as an anchor and then, failing that, as a slug. It never builds a - * selector or markup from the fragment. For a match it sets `open` on that - * section's `details.group-disclosure`. This is exactly what - * `openGroupsFromHash` in `lib/open-groups.ts` does after hydration; - * `OpenGroupsScript.test.tsx` pins the two together. The work is linear in - * the fragment's length, and the DOM work is bounded by the number of - * sections, so a crafted multi-megabyte fragment cannot stall the first - * paint. + * that input happened). It splits the fragment on `,`, decodes each token in + * its own `try` (a malformed token is skipped), and keeps each nonempty token + * once, with a `:` prefix, so a token such as `__proto__` or `constructor` + * never reads an `Object.prototype` member. It never builds a selector or + * markup from the fragment. + * + * Then it visits the group sections in page order, each once, as they + * arrive. A section whose `id` (the readable anchor) the fragment lists gets + * `open` on its `details.group-disclosure`, if it is the first section with + * that `id`; a visit that finds no disclosure yet waits for the next + * mutation. It also keeps the first section per `data-group-slug` (the + * opaque slug the first permalinks carried). At the end, the finish step + * visits any section not yet visited, then opens, for each listed token that + * names no section's anchor, the first section with that slug. A slug token + * waits for the end because a later section could still carry it as an + * anchor. This is exactly what `openGroupsFromHash` in `lib/open-groups.ts` + * does after hydration; `OpenGroupsScript.test.tsx` pins the two together. + * The work is linear in the fragment's length, and the DOM work is bounded by + * the number of sections, so a crafted multi-megabyte fragment cannot stall + * the first paint. * - * Last, it leaves a marker on its document for [`LandingState`] + * Last, the finish step leaves a marker on its document for [`LandingState`] * (`takeOpenGroupsBootstrap` in `lib/open-groups.ts`): * `document.__benchOpenGroups`, an object whose `input` turns true at the * reader's first `wheel`, `touchstart`, `keydown` or `pointerdown` (one-shot - * passive capture listeners on the window; `stop` removes them). After hydration, - * `LandingState` takes the marker on its first mount. It then keeps the open - * state as the reader left it (a group opened or closed before hydration - * stays so, and the fragment is written from it), and when `input` is true - * it does not scroll the page at all: no restore, no scroll to the fragment's - * first group. A scroll is not input: the browser's own jump - * to a single-anchor fragment fires one. The marker is written only after - * the groups are open, and the outer `try` keeps any failure from reaching the - * page: without the marker, the groups simply open after hydration instead. + * passive capture listeners on the window, added before the first group; + * `stop` removes them). After hydration, `LandingState` takes the marker on + * its first mount. It then keeps the open state as the reader left it (a + * group opened or closed before hydration stays so, even while the page was + * still arriving, and the fragment is written from it), and when `input` is + * true it does not scroll the page at all: no restore, no scroll to the + * fragment's first group. A scroll is not input: the browser's own jump to a + * single-anchor fragment fires one. The marker is written only after the + * groups are open, and each script's outer `try` keeps any failure from + * reaching the page: without the marker, the groups simply open after + * hydration instead. * * Kept as one line so the shipped bytes are what this file shows. Readable: * * var t = location.hash.slice(1).split(','), - * s = document.querySelectorAll('section[data-group-slug][id]'), - * a = {}, b = {}, u = {}, i, k, m, d, g, h, + * w = {}, p = [], a = {}, b = {}, j = 0, i, k, o, g, h, v, * n = ['wheel', 'touchstart', 'keydown', 'pointerdown']; - * for (i = 0; i < s.length; i++) { - * k = ':' + s[i].id; - * if (!a[k]) a[k] = s[i]; - * k = ':' + s[i].getAttribute('data-group-slug'); - * if (!b[k]) b[k] = s[i]; - * } * for (i = 0; i < t.length; i++) { * try { k = decodeURIComponent(t[i]); } catch (e) { continue; } * if (!k) continue; * k = ':' + k; - * if (u[k]) continue; - * u[k] = 1; - * m = a[k] || b[k]; - * d = m && m.querySelector('details.group-disclosure'); - * if (d) d.open = true; + * if (w[k]) continue; + * w[k] = 1; + * p.push(k); * } - * g = document.__benchOpenGroups = { + * // Visit the sections not yet visited; `z` is the finish step. + * v = function (z) { + * var l = document.querySelectorAll('section[data-group-slug][id]'), s, d, c; + * for (; j < l.length; j++) { + * s = l[j]; + * c = ':' + s.id; + * if (w[c] && !a[c]) { + * d = s.querySelector('details.group-disclosure'); + * if (!d && !z) return; + * if (d) d.open = true; + * } + * a[c] = 1; + * c = ':' + s.getAttribute('data-group-slug'); + * if (!b[c]) b[c] = s; + * } + * }; + * o = new MutationObserver(function () { try { v(0); } catch (e) {} }); + * o.observe(document, { childList: true, subtree: true }); + * g = { * input: false, * stop: function () { - * for (var j = 0; j < n.length; j++) removeEventListener(n[j], h, true); + * for (var m = 0; m < n.length; m++) removeEventListener(n[m], h, true); * } * }; * h = function () { g.input = true; g.stop(); }; * for (i = 0; i < n.length; i++) { * addEventListener(n[i], h, { capture: true, passive: true }); * } + * document.__benchOpenGroupsEnd = function () { + * var m, d; + * document.__benchOpenGroupsEnd = null; + * o.disconnect(); + * v(1); + * for (m = 0; m < p.length; m++) { + * k = p[m]; + * if (!a[k] && b[k]) { + * d = b[k].querySelector('details.group-disclosure'); + * if (d) d.open = true; + * } + * } + * document.__benchOpenGroups = g; + * }; */ export const OPEN_GROUPS_BOOTSTRAP = '(function(){try{' + 'var t=location.hash.slice(1).split(","),' + - 's=document.querySelectorAll("section[data-group-slug][id]"),' + - 'a={},b={},u={},i,k,m,d,g,h,' + + 'w={},p=[],a={},b={},j=0,i,k,o,g,h,v,' + 'n=["wheel","touchstart","keydown","pointerdown"];' + - 'for(i=0;i`, so it runs - * as soon as the groups' markup has parsed, in whatever streamed chunk - * carries them, and before they paint. The script adds `open` to a - * server-rendered `
`, which is why `GroupSection`'s disclosure - * carries `suppressHydrationWarning`. + * The finish step of [`OPEN_GROUPS_BOOTSTRAP`], after the last group: it + * visits the sections the observer has not reached, opens the groups listed by + * slug, stops the observer and leaves the marker. When the bootstrap failed it + * finds no finish step, and its `try` swallows that: no marker is left. + */ +export const OPEN_GROUPS_FINISH = '(function(){try{document.__benchOpenGroupsEnd()}catch(e){}})();'; + +/** + * Wraps the landing page's group sections in [`OPEN_GROUPS_BOOTSTRAP`], as an + * inline script before the first section, and [`OPEN_GROUPS_FINISH`], as one + * after the last, inside `
`. The bootstrap runs before any group has + * arrived and opens each listed group before it paints, in whatever streamed + * chunk carries it; the finish step runs as soon as the last group's markup + * has parsed. The scripts add `open` to a server-rendered `
`, which + * is why `GroupSection`'s disclosure carries `suppressHydrationWarning`. * * React never runs a script it renders on the client, so a client navigation - * to `/` skips this and leaves no marker; `LandingState` opens the groups + * to `/` skips both and leaves no marker; `LandingState` opens the groups * from the fragment then. */ -export function OpenGroupsScript() { +export function OpenGroupsScript({ children }: { children?: ReactNode }) { return ( -