Archilyzer · Source

archilyzer

Archilyzer
git clone https://archilyzer.pages.dev/source/archilyzer.git
Log | Files | Refs | README | LICENSE

commit 2d11f2ae4e12fc340277e1f0afa12bd9b50ade8a
parent ba8d2c971791b08053675c01371963f5cca74d84
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Fri, 25 Sep 2026 22:30:32 -0400

homepage, export: the hub and the homepage render their dark base on the server (brand S2 review)

The homepage's <html> and, in hub mode only, the export app's now carry `.dark` and `data-base="dark"` in the server markup, so a reader with no JS (or before the pre-paint script) gets the site's own base instead of the light :root fallback. A site build is unchanged: its system base cannot be resolved on the server. A reader who stored Light still gets light before paint (the script) and after hydration (ThemeProvider re-asserts the STORED base; React does not patch <html>'s attributes).

homepage theme.spec: the light reader's reload is re-checked after load, and a JS-disabled test asserts data-base="dark", .dark, data-accent="signal", no data-theme-ready (the script never ran), --background #0c0a08 and the body's background.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

Diffstat:
Mexport/app/layout.tsx | 13+++++++++++--
Mhomepage/app/layout.tsx | 8+++++++-
Mhomepage/e2e/theme.spec.ts | 27+++++++++++++++++++++++++++
3 files changed, 45 insertions(+), 3 deletions(-)

diff --git a/export/app/layout.tsx b/export/app/layout.tsx @@ -104,13 +104,22 @@ export default async function RootLayout({ // The accent is baked onto <html> at prerender, so it is right on first // paint before any script: `data-accent` selects the tokens.css rule, and a // custom hex adds its fitted per-base values inline. A reader's stored pick - // replaces `data-accent` pre-paint (ThemeScript). + // replaces `data-accent` pre-paint (ThemeScript). The hub's dark base is + // server-rendered too (`.dark` + data-base), as on the homepage; a site's + // "system" base cannot be resolved on the server, so it renders none and + // the pre-paint script sets it. const theme = themeDefaults(); + const serverDark = theme.defaultBase === "dark"; return ( <html lang="en" suppressHydrationWarning - className={`${fontVars} h-full antialiased`} + className={ + serverDark + ? `${fontVars} h-full antialiased dark` + : `${fontVars} h-full antialiased` + } + data-base={serverDark ? "dark" : undefined} data-accent={theme.accent} style={(theme.accentVars as React.CSSProperties | null) ?? undefined} > diff --git a/homepage/app/layout.tsx b/homepage/app/layout.tsx @@ -62,10 +62,16 @@ export default function RootLayout({ children: React.ReactNode; }>) { return ( + // Dark is rendered on the server too (`.dark` + data-base), so a reader + // with no JS, or before the pre-paint script, gets this site's own base + // rather than the light `:root` fallback. ThemeScript then applies a + // reader's stored base before paint, and ThemeProvider re-asserts it after + // hydration (which does not patch <html>'s attributes). <html lang="en" suppressHydrationWarning - className={`${fontVars} h-full antialiased`} + className={`${fontVars} h-full antialiased dark`} + data-base="dark" data-accent={DEFAULT_ACCENT} > <body className="min-h-full flex flex-col bg-[var(--background)] text-[var(--foreground)] font-sans selection:bg-[var(--brand-soft)] selection:text-[var(--foreground)]"> diff --git a/homepage/e2e/theme.spec.ts b/homepage/e2e/theme.spec.ts @@ -64,6 +64,33 @@ test("dark + Signal by default; the base toggle persists with no FOUC", async ({ expect(s.stored).toBe("light"); expect(s.base).toBe("light"); expect(s.dark).toBe(false); + + // <html> is server-rendered dark now; hydration must not put it back. + await page.waitForLoadState("load"); + await expect.poll(() => htmlState(page)).toMatchObject({ + base: "light", + dark: false, + stored: "light", + brand: ACCENTS.signal.onLight, + }); +}); + +test.describe("with JavaScript off", () => { + test.use({ javaScriptEnabled: false }); + + test("the server renders the dark base, so no script is needed for it", async ({ + page, + }) => { + await page.goto("/"); + const html = page.locator("html"); + await expect(html).toHaveAttribute("data-base", "dark"); + await expect(html).toHaveClass(/(^|\s)dark(\s|$)/); + await expect(html).toHaveAttribute("data-accent", "signal"); + // The pre-paint script never ran: this is the server's markup. + await expect(html).not.toHaveAttribute("data-theme-ready", /.*/); + await expect(html).toHaveCSS("--background", BASE_GROUNDS.dark); + await expect(page.locator("body")).toHaveCSS("background-color", "rgb(12, 10, 8)"); + }); }); test("every base declares a complete palette, and the grounds differ", async ({