From 54cdef40f1a6bf85db664e986fb96e23982379e4 Mon Sep 17 00:00:00 2001 From: Antoine BERNIER Date: Sun, 9 Aug 2026 12:33:53 +0200 Subject: [PATCH 1/3] Keep the list back until the filter has been applied MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Arriving on `?q=` or `?library=` meant watching all ~160 cards paint and then jump to the handful that match. Nothing React does can help: the site is `output: "export"`, so the list ships rendered whole in the HTML and paints before the JS that will narrow it has even loaded. Next has an answer for this — a `` boundary around a `useSearchParams` consumer, which drops that subtree from the static HTML — but it is a build-time decision, so it would cost the prerendered list for every visitor to spare the flash for the few who arrive filtered. Instead the boot script, which already reads the URL for `?nav=`, marks a filtered arrival on ; `globals.css` holds the list back until `Nav` takes the mark over, alongside the collapse one. Only filtered arrivals pay anything, and the list still ships prerendered for everyone else. `visibility`, not `display`, so the cards keep the layout the list windowing measures. That script is now a real function, serialized with `String(bootNav)` rather than written as a template literal: typed, formatted and linted like the rest of the file. It has to stay hermetic to survive stringification, so the storage key comes in as an argument. A filter now also beats a stored collapse in the script itself, matching what `Nav` already did on the client — the rail no longer paints shut and swings open on a shared filter link. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01Y9J9K5TobdJo49EU4Z1Fp4 --- apps/website/app/globals.css | 11 +++++++ apps/website/app/layout.tsx | 56 ++++++++++++++++++++++++--------- apps/website/components/Nav.tsx | 18 +++++++---- 3 files changed, 65 insertions(+), 20 deletions(-) diff --git a/apps/website/app/globals.css b/apps/website/app/globals.css index 20282f4e..d7ae7e59 100644 --- a/apps/website/app/globals.css +++ b/apps/website/app/globals.css @@ -350,3 +350,14 @@ margin-inline-start: calc(var(--sidebar-width) * -1); } } + +/* Same story for `?q=` and `?library=`, one step earlier: a static export + ships the list rendered whole, so all ~160 cards paint while the JS that + will narrow them down is still loading, and the filter lands as a visible + jump. The script marks a filtered arrival and this keeps the list back + until React has applied it -- only for those arrivals; everyone else gets + the prerendered list painted as it always was. `visibility`, not `display`, + so the cards keep the layout the list windowing measures. */ +html[data-nav-filtering] #example-list { + visibility: hidden; +} diff --git a/apps/website/app/layout.tsx b/apps/website/app/layout.tsx index 23cec686..9f933f5a 100644 --- a/apps/website/app/layout.tsx +++ b/apps/website/app/layout.tsx @@ -12,6 +12,45 @@ import { builder } from "material-theme-builder"; const inter = Inter({ subsets: ["latin"] }); const examples = getExamples(); +/** + * Two things only the client knows, both needed before the first paint, so the + * blocking script below runs this and leaves the verdict on : whether the + * rail starts collapsed, and whether this is a filtered arrival — `?q=` + * /`?library=`, whose list must not paint whole while the JS that will narrow + * it is still loading. `globals.css` acts on both marks and `Nav` takes them + * over, then drops them. A filter beats a stored collapse: a shared link has to + * be able to show what it filtered down to. + * + * A function rather than a template string, so it is typed, formatted and + * linted like everything else — `String(bootNav)` is what ends up in the page. + * The catch that comes with that: it has to stay hermetic. No imports, no + * module-level constants, nothing but its arguments, or the bundler leaves a + * dangling reference in the string. Hence the storage key coming in as one — + * `Nav` owns the other half of that contract. + */ +function bootNav(storageKey: string) { + const parts = window.location.pathname.split("/").filter(Boolean); + const examplesIndex = parts.indexOf("examples"); + const hasExampleSelected = examplesIndex !== -1 && !!parts[examplesIndex + 1]; + + const params = new URLSearchParams(window.location.search); + const nav = params.get("nav"); + const filtering = !!(params.get("q") || params.get("library")); + + const collapsed = + !hasExampleSelected || filtering + ? false + : nav === "closed" + ? true + : nav === "open" + ? false + : localStorage.getItem(storageKey) === "1"; + + const root = document.documentElement; + root.toggleAttribute("data-nav-collapsed", collapsed); + root.toggleAttribute("data-nav-filtering", filtering); +} + /** * The one hex the whole palette hangs off -- poimandres' signature mint. * Material Color Utilities derives every `--md-sys-color-*` role from it, and @@ -81,21 +120,10 @@ export default function RootLayout({ precedence="high" dangerouslySetInnerHTML={{ __html: mcuCss }} /> + {/* Blocking on purpose: `bootNav` settles what the rail looks like + before anything paints. */}