Archilyzer · Source

archilyzer

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

commit 67ace2d330967dd2ec7b6ec34df37ee3a39db1a4
parent 189bb8a3ed05cbfb446d29a0ac3a6d9e146d4757
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Fri, 25 Sep 2026 16:18:11 -0400

homepage: marketing e2e pins the chart, Official instances and the build date against the summary on disk

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

Diffstat:
Mhomepage/e2e/marketing.spec.ts | 97+++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------
1 file changed, 65 insertions(+), 32 deletions(-)

diff --git a/homepage/e2e/marketing.spec.ts b/homepage/e2e/marketing.spec.ts @@ -1,10 +1,12 @@ +import fs from "node:fs"; +import path from "node:path"; import { test, expect } from "@playwright/test"; // The home page's job is to say what Archilyzer is and offer the download. -// Everything asserted here is data-INDEPENDENT by construction: no count, no -// site name, no headline number. The numeric bands are gated on corpus data, so -// pinning one would make this suite fail on a source-only build — which is -// exactly the build this page has to work on. +// Nothing asserted here names a count, a site or a headline number. The numeric +// parts are gated on corpus data, so the specs that touch them read the summary +// on disk and branch on it — a source-only build is the build this page has to +// work on. test.beforeEach(async ({ page }) => { await page.goto("/"); @@ -23,7 +25,7 @@ test("says what it is and offers the source", async ({ page }) => { }); // The hub is a separate app on its own Pages project, linked from the hero -// when homepage.json names its URL. Data-dependent like the rail below, so the +// when homepage.json names its URL. Data-dependent like the numbers below, so the // assertion is conditional the same way: absent is a legal state (a // source-only build has no hub), but PRESENT means an absolute link off-site — // never a relative path into this site, which has no search. @@ -34,19 +36,6 @@ test("the hub link, when there is one, leaves for the hub", async ({ page }) => await expect(hub).toHaveAttribute("href", /^https?:\/\/[^/]/); }); -test("the trust block states what it isn't", async ({ page }) => { - // The cheapest, highest-value block on the page — every claim in it is - // simply true, and each one pre-empts a wrong assumption. - for (const claim of [ - "Not a hosted service", - "No public repository", - "You supply the tools", - "Transcription is slow", - ]) { - await expect(page.getByText(claim, { exact: true })).toBeVisible(); - } -}); - test("every nav destination resolves", async ({ page }) => { // The nav must never point at a 404, including on a build with no corpus // data — which is why /stats/ always exists and degrades in place. @@ -61,22 +50,66 @@ test("every nav destination resolves", async ({ page }) => { } }); -test("the rail never invents a recording", async ({ page }) => { - // Absent data must produce an empty state, not filler. An archiving tool - // that fabricates transcript rows on its own marketing page has undercut - // itself before a visitor has read a sentence. - const rail = page.getByText("Recent acquisitions"); - const empty = page.getByText(/shipped without corpus data/i); - const hasRail = await rail.isVisible().catch(() => false); - if (!hasRail) { - await expect(empty).toBeVisible(); +// The numeric parts of the page are gated on the build-time summary, so each +// assertion reads the summary on disk and expects exactly what it implies: the +// part present with data, absent without. A source-only build (no summary) is +// a legal state and must render none of them rather than zeroes. +function readSummary(): { + monthly?: unknown[]; + sites?: unknown[]; + official?: { sites: number }; + generatedAt?: string; +} | null { + try { + const file = path.resolve( + test.info().project.testDir, + "..", + "public", + "homepage-summary.json", + ); + return JSON.parse(fs.readFileSync(file, "utf8")); + } catch { + return null; + } +} + +test("the growth chart renders from the summary, or not at all", async ({ + page, +}) => { + const summary = readSummary(); + const chart = page.getByRole("img", { name: /transcripts by the month/i }); + if (!summary?.monthly?.length || !summary.sites?.length) { + await expect(chart).toHaveCount(0); + return; + } + await expect(chart).toBeVisible(); + await expect( + page.getByText(/all official instances\.$/i), + ).toBeVisible(); +}); + +test("official instances link out, and the numbers carry their build date", async ({ + page, +}) => { + const summary = readSummary(); + const heading = page.getByRole("heading", { name: "Official instances" }); + if (!summary?.sites?.length) { + await expect(heading).toHaveCount(0); return; } - // With data: every row carries a real date in the log's own format. - const times = page.locator("time[datetime]"); - await expect(times.first()).toBeVisible(); - for (const text of await times.allTextContents()) { - expect(text.trim()).toMatch(/^\d{4}-\d{2}-\d{2}$/); + await expect(heading).toBeVisible(); + const section = page.locator("section").filter({ has: heading }); + const links = section.getByRole("link"); + expect(await links.count()).toBeGreaterThanOrEqual(1); + for (const href of await links.evaluateAll((els) => + els.map((e) => e.getAttribute("href") ?? ""), + )) { + expect(href).toMatch(/^https?:\/\/[^/]/); + } + if (summary.official && summary.official.sites > 0) { + const time = page.locator("time[datetime]"); + await expect(time).toHaveCount(1); + expect(await time.getAttribute("datetime")).toMatch(/^\d{4}-\d{2}-\d{2}$/); } });