commit 4f868150e753f77773c2494d6dc1ed8b3b791a9d
parent bd0565c54944b3ff6c5c42220176b9cf8cecb67a
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date: Wed, 30 Sep 2026 09:07:52 -0400
homepage: /source/ gains a History block — the commit count and head from the manifest, links to the log, the refs and the Atom feed — shown only when the manifest has a history and its log page is here; the e2e reads a fixture publish (E2E_SOURCE_PUBLIC_DIR, never in a production build) to show the page with and without one, and source.spec walks the real pages: the site's line, the theme by stored choice and by the OS, a commit, the Files index into the raw tree, the feed untouched
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Diffstat:
10 files changed, 316 insertions(+), 12 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -145,6 +145,8 @@ yarn-error.log*
/homepage/e2e/.e2e-settings.json*
# the e2e's empty sites directory (no homepage.json; homepage/playwright.config.ts)
/homepage/e2e/.e2e-sites/
+# the e2e's fixture publish for /source/ (homepage/e2e/fixture-source.ts)
+/homepage/e2e/.e2e-source/
# site config
/settings.json
diff --git a/ENVIRONMENT.md b/ENVIRONMENT.md
@@ -190,5 +190,6 @@ Read only by a test harness, a fake binary or a test-mode branch. Never set one
| `E2E_RETRIES` | `0` | Retries per shard (`--retries N` wins); 0 keeps a sharded run comparable to a serial one. | scripts/run-sharded-e2e.mjs |
| `E2E_IMAGE` | `yt-dlp-transcript-browser-e2e` | The sharded e2e run's image tag. | scripts/run-sharded-e2e.mjs |
| `E2E_SKIP_BUILD` | off | `1` reuses the sharded e2e image instead of rebuilding it (`--no-build`). | scripts/run-sharded-e2e.mjs |
+| `E2E_SOURCE_PUBLIC_DIR` | unset (the page reads `homepage/public`) | The fixture publish the homepage's e2e dev server reads the `/source/` page from while it holds a manifest; set by `homepage/playwright.config.ts`, written and removed by `homepage/e2e/source-history.spec.ts`, ignored by a production build. | homepage/app/lib/source.ts |
| `E2E_EXPECT_SOURCE` | off (both states pass) | `1` makes the homepage suite's `/source/` specs fail on the empty state; a gate that ran `archilyzer source publish` first sets it. | homepage/e2e/source.spec.ts |
| `E2E_HOMEPAGE_SUMMARY_FILE` | `homepage/public/homepage-summary.json` | The synthetic summary the homepage's e2e dev server reads; set by `homepage/playwright.config.ts`, ignored by a production build. | homepage/app/lib/summary.ts |
diff --git a/common/lib/envVars.ts b/common/lib/envVars.ts
@@ -189,6 +189,7 @@ const DECLARED: EnvVarDecl[] = [
{ name: "E2E_RETRIES", audience: "test", default: "`0`", readBy: "scripts/run-sharded-e2e.mjs", doc: "Retries per shard (`--retries N` wins); 0 keeps a sharded run comparable to a serial one." },
{ name: "E2E_IMAGE", audience: "test", default: "`yt-dlp-transcript-browser-e2e`", readBy: "scripts/run-sharded-e2e.mjs", doc: "The sharded e2e run's image tag." },
{ name: "E2E_SKIP_BUILD", audience: "test", default: "off", readBy: "scripts/run-sharded-e2e.mjs", doc: "`1` reuses the sharded e2e image instead of rebuilding it (`--no-build`)." },
+ { name: "E2E_SOURCE_PUBLIC_DIR", audience: "test", default: "unset (the page reads `homepage/public`)", readBy: "homepage/app/lib/source.ts", doc: "The fixture publish the homepage's e2e dev server reads the `/source/` page from while it holds a manifest; set by `homepage/playwright.config.ts`, written and removed by `homepage/e2e/source-history.spec.ts`, ignored by a production build." },
{ name: "E2E_EXPECT_SOURCE", audience: "test", default: "off (both states pass)", readBy: "homepage/e2e/source.spec.ts", doc: "`1` makes the homepage suite's `/source/` specs fail on the empty state; a gate that ran `archilyzer source publish` first sets it." },
{ name: "E2E_HOMEPAGE_SUMMARY_FILE", audience: "test", default: "`homepage/public/homepage-summary.json`", readBy: "homepage/app/lib/summary.ts", doc: "The synthetic summary the homepage's e2e dev server reads; set by `homepage/playwright.config.ts`, ignored by a production build." },
];
diff --git a/homepage/app/lib/source.test.ts b/homepage/app/lib/source.test.ts
@@ -3,7 +3,7 @@ import assert from "node:assert/strict";
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import os from "node:os";
import path from "node:path";
-import { loadSourceManifest } from "./source";
+import { loadSourceManifest, sourcePublicDir } from "./source";
// Run with:
// pnpm --filter homepage test
@@ -34,7 +34,7 @@ const manifest = {
};
let n = 0;
-function pub(opts: { manifest?: unknown; refs?: boolean; tarball?: boolean }): string {
+function pub(opts: { manifest?: unknown; refs?: boolean; tarball?: boolean; log?: boolean }): string {
const p = path.join(TMP, `p${n++}`);
mkdirSync(path.join(p, "source", "archilyzer.git", "info"), { recursive: true });
mkdirSync(path.join(p, "downloads"), { recursive: true });
@@ -46,6 +46,10 @@ function pub(opts: { manifest?: unknown; refs?: boolean; tarball?: boolean }): s
}
if (opts.refs !== false) writeFileSync(path.join(p, "source", "archilyzer.git", "info", "refs"), "x\trefs/heads/main\n");
if (opts.tarball !== false) writeFileSync(path.join(p, "downloads", "archilyzer-source.tar.gz"), "t");
+ if (opts.log) {
+ mkdirSync(path.join(p, "source", "git"), { recursive: true });
+ writeFileSync(path.join(p, "source", "git", "log.html"), "<html></html>");
+ }
return p;
}
@@ -69,3 +73,35 @@ test("malformed, wrong-version or orphaned manifests are the empty state, never
assert.equal(loadSourceManifest(dir), null, what);
}
});
+
+// Release 15 slice SG: the history block is believed only beside its log page.
+const history = {
+ href: "/source/git/log.html",
+ commits: 3,
+ head: "2".repeat(40),
+ files: 9,
+ bytes: 1234,
+ sha256: "4".repeat(64),
+ tool: "stagit (sha256 0123456789ab)",
+};
+
+test("a history block beside its log page is believed; without the page the manifest comes back without it", () => {
+ assert.deepEqual(loadSourceManifest(pub({ manifest: { ...manifest, history }, log: true }))?.history, history);
+ const orphan = loadSourceManifest(pub({ manifest: { ...manifest, history } }));
+ assert.equal(orphan?.mirrorHead, "2".repeat(40), "the rest of the manifest stands");
+ assert.ok(orphan && !("history" in orphan), "no History links for pages that are not here");
+ assert.equal(loadSourceManifest(pub({ manifest, log: true }))?.history, undefined, "no block, no History");
+ // A malformed block is a manifest nobody believes (parseSourceManifest).
+ assert.equal(loadSourceManifest(pub({ manifest: { ...manifest, history: { ...history, commits: "3" } }, log: true })), null);
+});
+
+test("the e2e's fixture publish is read only outside a production build, and only when it holds a manifest", () => {
+ const fixture = pub({ manifest, log: true });
+ const empty = path.join(TMP, "empty-fixture");
+ mkdirSync(empty, { recursive: true });
+ const PUBLIC = "/checkout/homepage/public";
+ assert.equal(sourcePublicDir({ NODE_ENV: "development", E2E_SOURCE_PUBLIC_DIR: fixture }, PUBLIC), fixture);
+ assert.equal(sourcePublicDir({ NODE_ENV: "production", E2E_SOURCE_PUBLIC_DIR: fixture }, PUBLIC), PUBLIC);
+ assert.equal(sourcePublicDir({ NODE_ENV: "development", E2E_SOURCE_PUBLIC_DIR: empty }, PUBLIC), PUBLIC, "no manifest: public/");
+ assert.equal(sourcePublicDir({ NODE_ENV: "development" }, PUBLIC), PUBLIC);
+});
diff --git a/homepage/app/lib/source.ts b/homepage/app/lib/source.ts
@@ -1,6 +1,7 @@
import fs from "node:fs";
import path from "node:path";
import {
+ HISTORY_DIR,
MIRROR_DIR,
TARBALL_HREF,
parseSourceManifest,
@@ -15,27 +16,61 @@ import {
//
// Believed only when the files it describes are here too (the snapshot.ts
// rule): a manifest beside a missing mirror or tarball would advertise a
-// clone that fails and a download that 404s. A malformed or wrong-version
-// manifest is null too (parseSourceManifest checks every number the page
-// reads), so a bad file is the empty state, never a crash in `next build`.
-// `pub` is the test's seam.
+// clone that fails and a download that 404s. The same for the history block
+// (release 15 slice SG): without its log page beside it, the manifest is
+// returned WITHOUT it, and the page shows no History links. A malformed or
+// wrong-version manifest is null too (parseSourceManifest checks every number
+// the page reads), so a bad file is the empty state, never a crash in `next
+// build`. `pub` is the test's seam.
//
// The default is a DIRECTORY join on `process.cwd()`, which Turbopack would
// trace as every file under `public/` (the source mirror among them, and it
// grows with every publish): the opt-out keeps it a plain run-time path
// (plans/FACTS.md, "A path joined from `process.cwd()` …").
export function loadSourceManifest(
- pub: string = path.join(/* turbopackIgnore: true */ process.cwd(), "public"),
+ pub: string = sourcePublicDir(
+ process.env,
+ path.join(/* turbopackIgnore: true */ process.cwd(), "public"),
+ ),
): SourceManifest | null {
try {
const manifest = parseSourceManifest(
- JSON.parse(fs.readFileSync(path.join(pub, "source", "manifest.json"), "utf8")),
+ JSON.parse(fs.readFileSync(path.join(/* turbopackIgnore: true */ pub, "source", "manifest.json"), "utf8")),
);
if (!manifest) return null;
- if (!fs.statSync(path.join(pub, "source", MIRROR_DIR, "info", "refs")).isFile()) return null;
- if (!fs.statSync(path.join(pub, TARBALL_HREF.replace(/^\//, ""))).isFile()) return null;
+ if (!fs.statSync(path.join(/* turbopackIgnore: true */ pub, "source", MIRROR_DIR, "info", "refs")).isFile()) return null;
+ if (!fs.statSync(path.join(/* turbopackIgnore: true */ pub, TARBALL_HREF.replace(/^\//, ""))).isFile()) return null;
+ if (manifest.history && !isFile(path.join(/* turbopackIgnore: true */ pub, "source", HISTORY_DIR, "log.html"))) {
+ const without: SourceManifest = { ...manifest };
+ delete without.history;
+ return without;
+ }
return manifest;
} catch {
return null;
}
}
+
+function isFile(p: string): boolean {
+ try {
+ return fs.statSync(p).isFile();
+ } catch {
+ return false;
+ }
+}
+
+// The directory loadSourceManifest reads: `public/`, or the one
+// E2E_SOURCE_PUBLIC_DIR names when it holds a manifest — but ONLY outside a
+// production build. The homepage e2e's `next dev` points it at a fixture
+// publish its spec writes and removes (e2e/fixture-source.ts), so the page's
+// states with and without a history are exercised whatever this checkout has
+// published; with the fixture gone, the page reads `public/` again. `next
+// build` runs with NODE_ENV=production and ignores it (the summary.ts rule).
+export function sourcePublicDir(
+ env: Readonly<Record<string, string | undefined>>,
+ publicDir: string,
+): string {
+ const fixture = env.NODE_ENV !== "production" ? env.E2E_SOURCE_PUBLIC_DIR : undefined;
+ if (fixture && isFile(path.join(/* turbopackIgnore: true */ fixture, "source", "manifest.json"))) return fixture;
+ return publicDir;
+}
diff --git a/homepage/app/source/page.tsx b/homepage/app/source/page.tsx
@@ -1,6 +1,12 @@
import Link from "next/link";
import type { Metadata } from "next";
-import { CLONE_URL, TREE_HREF } from "yt-dlp-transcript-common/lib/sourceManifest";
+import {
+ CLONE_URL,
+ HISTORY_ATOM_HREF,
+ HISTORY_LOG_HREF,
+ HISTORY_REFS_HREF,
+ TREE_HREF,
+} from "yt-dlp-transcript-common/lib/sourceManifest";
import { PageShell, PageHeading } from "../components/PageShell";
import { Fact } from "../components/Fact";
import { loadSourceManifest } from "../lib/source";
@@ -19,7 +25,8 @@ const linkClass =
// and the tarball are build artefacts, so this page has two states and the
// manifest decides which. A manifest is believed only when the files it
// describes are here too (lib/source.ts): no clone command or link for a file
-// that isn't there.
+// that isn't there. The History block likewise: only when the manifest has a
+// history (stagit rendered it) and its log page is here.
export default function SourcePage() {
const manifest = loadSourceManifest();
const date = manifest
@@ -79,6 +86,36 @@ export default function SourcePage() {
</Fact>
</div>
+ {manifest.history ? (
+ <div data-testid="source-history" className="doc-measure flex flex-col gap-2">
+ <p className="label-machine">History</p>
+ <p className="text-sm leading-[1.7] text-[var(--muted-foreground)]">
+ Every commit of main with its diff, as static pages:{" "}
+ <span data-testid="source-history-commits" className="tabular">
+ {manifest.history.commits.toLocaleString("en-US")}
+ </span>{" "}
+ commit{manifest.history.commits === 1 ? "" : "s"}, the newest{" "}
+ <span data-testid="source-history-head" className="tabular break-all">
+ {manifest.history.head}
+ </span>
+ .
+ </p>
+ <p className="text-sm text-[var(--muted-foreground)]">
+ <a data-testid="source-history-log" href={HISTORY_LOG_HREF} className={linkClass}>
+ Log
+ </a>
+ {" · "}
+ <a data-testid="source-history-refs" href={HISTORY_REFS_HREF} className={linkClass}>
+ Refs
+ </a>
+ {" · "}
+ <a data-testid="source-history-atom" href={HISTORY_ATOM_HREF} className={linkClass}>
+ Atom feed
+ </a>
+ </p>
+ </div>
+ ) : null}
+
<div className="doc-measure flex flex-col gap-3">
<p>
<a data-testid="source-tree-link" href={TREE_HREF} className={`text-sm ${linkClass}`}>
diff --git a/homepage/e2e/fixture-source.ts b/homepage/e2e/fixture-source.ts
@@ -0,0 +1,67 @@
+import fs from "node:fs";
+import path from "node:path";
+
+// A FIXTURE PUBLISH for the /source/ page's History block (release 15 slice
+// SG). The page reads the directory E2E_SOURCE_PUBLIC_DIR names instead of
+// public/ while it holds a manifest (app/lib/source.ts sourcePublicDir,
+// honoured only outside a production build), so a spec can show the page with
+// a history and without one, whatever this checkout has published. The spec
+// writes it, and removes it after each test: with it gone, the page — and
+// every other spec — reads public/ again. playwright.config.ts clears it
+// before the server starts, in case a killed run left one.
+//
+// Only the files the loader requires: the manifest, the mirror's info/refs,
+// the tarball, and — with a history — the log page. Nothing here is served:
+// the dev server serves public/, so the History links are checked by their
+// hrefs, and the real pages by source.spec.ts.
+
+export const FIXTURE_SOURCE_NAME = ".e2e-source";
+
+export const FIXTURE_HISTORY = {
+ href: "/source/git/log.html",
+ commits: 1234,
+ head: "c".repeat(40),
+ files: 1240,
+ bytes: 1_000_000,
+ sha256: "d".repeat(64),
+ tool: "stagit (sha256 0123456789ab)",
+};
+
+export const FIXTURE_MIRROR_HEAD = "c".repeat(40);
+
+export function writeFixtureSource(dir: string, o: { history: boolean }): void {
+ clearFixtureSource(dir);
+ fs.mkdirSync(path.join(dir, "source", "archilyzer.git", "info"), { recursive: true });
+ fs.mkdirSync(path.join(dir, "downloads"), { recursive: true });
+ fs.writeFileSync(path.join(dir, "source", "archilyzer.git", "info", "refs"), `${FIXTURE_MIRROR_HEAD}\trefs/heads/main\n`);
+ fs.writeFileSync(path.join(dir, "downloads", "archilyzer-source.tar.gz"), "fixture");
+ if (o.history) {
+ fs.mkdirSync(path.join(dir, "source", "git"), { recursive: true });
+ fs.writeFileSync(path.join(dir, "source", "git", "log.html"), "<html></html>\n");
+ }
+ const manifest = {
+ version: 1,
+ generatedAt: "2026-09-30T12:00:00.000Z",
+ branch: "main",
+ sourceCommit: "b".repeat(40),
+ mirrorHead: FIXTURE_MIRROR_HEAD,
+ subject: "a fixture publish",
+ files: 10,
+ bytes: 100,
+ mirror: { files: 9, bytes: 80, packs: 1 },
+ tree: { files: 5, dirs: 2, bytes: 20 },
+ tarball: { href: "/downloads/archilyzer-source.tar.gz", bytes: 7, sha256: "e".repeat(64) },
+ cloneUrl: "https://archilyzer.pages.dev/source/archilyzer.git",
+ treeHref: "/source/tree/",
+ audit: { objects: 3, commits: 1, gitleaks: "skipped" },
+ tools: { git: "2.55.0", filterRepo: "fixture" },
+ ...(o.history ? { history: FIXTURE_HISTORY } : {}),
+ };
+ // Written last, as the publish writes it: the page reads the fixture only
+ // once a manifest is there.
+ fs.writeFileSync(path.join(dir, "source", "manifest.json"), JSON.stringify(manifest, null, 2));
+}
+
+export function clearFixtureSource(dir: string): void {
+ fs.rmSync(dir, { recursive: true, force: true });
+}
diff --git a/homepage/e2e/source-history.spec.ts b/homepage/e2e/source-history.spec.ts
@@ -0,0 +1,45 @@
+import path from "node:path";
+import { test, expect } from "@playwright/test";
+import {
+ FIXTURE_HISTORY,
+ FIXTURE_SOURCE_NAME,
+ clearFixtureSource,
+ writeFixtureSource,
+} from "./fixture-source";
+
+// The /source/ page's History block (release 15 slice SG), in both states,
+// from a FIXTURE publish (e2e/fixture-source.ts): the dev server reads the
+// page's manifest from E2E_SOURCE_PUBLIC_DIR while it holds one
+// (playwright.config.ts, app/lib/source.ts). Each test writes it and removes
+// it, so every other spec reads public/. The history PAGES themselves — what
+// stagit rendered and the publish rewrote — are source.spec.ts's, over the
+// checkout's real publish.
+const FIXTURE = path.resolve(process.cwd(), "e2e", FIXTURE_SOURCE_NAME);
+
+test.describe.configure({ mode: "serial" });
+test.afterEach(() => clearFixtureSource(FIXTURE));
+
+test("with a history in the manifest: the History block names the commits and the head, and links the log, the refs and the feed", async ({ page }) => {
+ writeFixtureSource(FIXTURE, { history: true });
+ await page.goto("/source/");
+ await expect(page.getByTestId("source-mirror-head")).toHaveText(FIXTURE_HISTORY.head);
+ const block = page.getByTestId("source-history");
+ await expect(block).toBeVisible();
+ await expect(block).toContainText("Every commit of main with its diff, as static pages");
+ await expect(page.getByTestId("source-history-commits")).toHaveText("1,234");
+ await expect(page.getByTestId("source-history-head")).toHaveText(FIXTURE_HISTORY.head);
+ await expect(page.getByTestId("source-history-log")).toHaveAttribute("href", "/source/git/log.html");
+ await expect(page.getByTestId("source-history-refs")).toHaveAttribute("href", "/source/git/refs.html");
+ await expect(page.getByTestId("source-history-atom")).toHaveAttribute("href", "/source/git/atom.xml");
+ await expect(page.getByTestId("source-history-log")).toHaveText("Log");
+ await expect(page.getByTestId("source-history-atom")).toHaveText("Atom feed");
+});
+
+test("without a history in the manifest: no History block, and the rest of the page as before", async ({ page }) => {
+ writeFixtureSource(FIXTURE, { history: false });
+ await page.goto("/source/");
+ await expect(page.getByTestId("source-clone")).toBeVisible();
+ await expect(page.getByTestId("source-tree-link")).toBeVisible();
+ await expect(page.getByTestId("source-history")).toHaveCount(0);
+ await expect(page.getByRole("link", { name: "Atom feed" })).toHaveCount(0);
+});
diff --git a/homepage/e2e/source.spec.ts b/homepage/e2e/source.spec.ts
@@ -106,6 +106,73 @@ test("the raw tree: an index per directory, encoded hrefs that resolve, brackets
expect(font.status()).toBe(200);
});
+// The history pages (release 15 slice SG): stagit's rendering, rewritten by
+// the publish. Run when the checkout's publish has one (a machine with
+// stagit); without, the page must show no History block.
+test("the history pages: the log, a commit, the Files index into the raw tree; the site's line, its theme by stored choice and by the OS; the feed untouched", async ({ page, browser, baseURL }) => {
+ if (!(await published(page))) {
+ await expect(page.getByTestId("source-history")).toHaveCount(0);
+ return;
+ }
+ const manifest = await (await page.request.get("/source/manifest.json")).json();
+ if (!manifest.history) {
+ await expect(page.getByTestId("source-history")).toHaveCount(0);
+ return;
+ }
+ await expect(page.getByTestId("source-history-head")).toHaveText(manifest.history.head);
+ const shown = (await page.getByTestId("source-history-commits").textContent())!.replace(/\D/g, "");
+ expect(shown).toBe(String(manifest.history.commits));
+
+ // The log, with the one line the publish adds, linking back here, and the
+ // homepage's base (dark) for a reader who has stored none.
+ await page.getByTestId("source-history-log").click();
+ await expect(page).toHaveURL(/\/source\/git\/log\.html$/);
+ const back = page.getByRole("link", { name: "Archilyzer · Source" });
+ await expect(back).toHaveAttribute("href", "/source/");
+ await expect(page.locator("html")).toHaveAttribute("data-base", "dark");
+ await expect(page.locator("html")).toHaveAttribute("data-theme-ready", "1");
+ const bg = () => page.evaluate(() => getComputedStyle(document.body).backgroundColor);
+ expect(await bg()).toBe("rgb(12, 10, 8)");
+ // The stored choice is the homepage's (one origin, one key).
+ await page.evaluate(() => localStorage.setItem("ytdlp-tb:base", "light"));
+ await page.reload();
+ await expect(page.locator("html")).toHaveAttribute("data-base", "light");
+ expect(await bg()).toBe("rgb(243, 246, 247)");
+
+ // The newest commit's page resolves from the log, and its diff's file links
+ // go to the raw tree (no per-file pages are published).
+ const first = page.locator("#log a[href^='commit/']").first();
+ await expect(first).toHaveAttribute("href", `commit/${manifest.history.head}.html`);
+ const commit = await page.request.get(`/source/git/commit/${manifest.history.head}.html`);
+ expect(commit.status()).toBe(200);
+ const commitHtml = await commit.text();
+ expect(commitHtml).toContain('<a href="/source/">Archilyzer · Source</a>');
+ expect(commitHtml).not.toMatch(/href="(?:\.\.\/)*file\//);
+ // Files is an index into the tree: its first link is a raw file, served.
+ await page.goto("/source/git/files.html");
+ const href = await page.locator("#files a").first().getAttribute("href");
+ expect(href).toMatch(/^\.\.\/tree\//);
+ const target = new URL(href!, `${baseURL}/source/git/files.html`).pathname;
+ expect((await page.request.get(target)).status()).toBe(200);
+ expect((await page.request.get("/source/git/refs.html")).status()).toBe(200);
+ // The feeds are not pages: nothing is injected into them.
+ const atom = await (await page.request.get("/source/git/atom.xml")).text();
+ expect(atom).toContain("<feed");
+ expect(atom).toContain(`https://archilyzer.pages.dev/source/git/commit/${manifest.history.head}.html`);
+ expect(atom).not.toContain("archilyzer-source");
+ expect(atom).not.toContain("<script");
+
+ // Without the script (no JS), the OS decides: light, and dark.
+ for (const [scheme, rgb] of [["light", "rgb(243, 246, 247)"], ["dark", "rgb(12, 10, 8)"]] as const) {
+ const ctx = await browser.newContext({ javaScriptEnabled: false, colorScheme: scheme });
+ const p = await ctx.newPage();
+ await p.goto(`${baseURL}/source/git/log.html`);
+ expect(await p.locator("html").getAttribute("data-base"), "the script did not run").toBeNull();
+ expect(await p.evaluate(() => getComputedStyle(document.body).backgroundColor), `no JS, a ${scheme} OS`).toBe(rgb);
+ await ctx.close();
+ }
+});
+
test("the Downloads page points at the mirror for the history", async ({ page }) => {
await page.goto("/downloads/");
await expect(page.getByRole("link", { name: "read-only git mirror" })).toHaveAttribute("href", "/source/");
diff --git a/homepage/playwright.config.ts b/homepage/playwright.config.ts
@@ -11,6 +11,7 @@ import {
FIXTURE_SOCIAL_TRIO,
writeFixtureSettings,
} from "./e2e/fixture-social";
+import { FIXTURE_SOURCE_NAME, clearFixtureSource } from "./e2e/fixture-source";
// Homepage e2e. Runs against `next dev` (default mode) so it reflects uncommitted
// source. Kill any stale dev server on the port between runs.
@@ -38,6 +39,12 @@ import {
// `archilyzer source publish`): e2e/source.spec.ts
// FAILS on the /source/ page's empty state instead
// of accepting it
+// E2E_SOURCE_PUBLIC_DIR set below on the dev server: the directory the
+// /source/ page reads the publish from WHILE it
+// holds a manifest (app/lib/source.ts, ignored by
+// a production build). e2e/source-history.spec.ts
+// writes a fixture publish there and removes it;
+// empty, the page reads public/
const PORT = portFor("HOMEPAGE_E2E_PORT");
const baseURL = `http://localhost:${PORT}`;
@@ -56,6 +63,11 @@ writeFixtureSettings(FIXTURE_SETTINGS, FIXTURE_SOCIAL_TRIO);
const FIXTURE_SITES_DIR = path.resolve(process.cwd(), "e2e", ".e2e-sites");
fs.mkdirSync(FIXTURE_SITES_DIR, { recursive: true });
+// The fixture publish (e2e/fixture-source.ts): none until a spec writes one,
+// so a killed run's leftover never stands in for public/.
+const FIXTURE_SOURCE_DIR = path.resolve(process.cwd(), "e2e", FIXTURE_SOURCE_NAME);
+clearFixtureSource(FIXTURE_SOURCE_DIR);
+
export default defineConfig({
testDir: "./e2e",
timeout: 30_000,
@@ -73,6 +85,7 @@ export default defineConfig({
E2E_HOMEPAGE_SUMMARY_FILE: FIXTURE_SUMMARY,
SETTINGS_FILE: FIXTURE_SETTINGS,
SITES_DIR: FIXTURE_SITES_DIR,
+ E2E_SOURCE_PUBLIC_DIR: FIXTURE_SOURCE_DIR,
},
},
use: {