commit f27aeaca7f99265a9463e1612a9e97f3fe4114f5
parent 0fd627c306a107f1bdbc620027ff517617282e60
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date: Tue, 29 Sep 2026 20:50:29 -0400
homepage: the e2e fixture carries a seventh, unlisted site (its own two channels, and the first site's first channel shared); a spec proves the summary, `/` and `/stats/` name it nowhere and count none of its own channels
buildFixtureSummary() is exactly buildFixtureSummary(FIXTURE_SITES, []): the
unlisted site's recordings are generated last, so every listed site's numbers
are unchanged and every other spec reads the six sites as before.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Diffstat:
2 files changed, 83 insertions(+), 7 deletions(-)
diff --git a/homepage/e2e/fixture-summary.ts b/homepage/e2e/fixture-summary.ts
@@ -26,7 +26,13 @@ import type { VideoStat } from "../../common/lib/stats";
// symlog axis reaches 10K while the Recent weeks stay linear and small;
// • every site transcribes every day of the last 200, so every line is full
// and every series has a non-zero start for Indexed;
-// • uploads spread over 2019–2026, for the growth chart's months.
+// • uploads spread over 2019–2026, for the growth chart's months;
+// • a seventh site, UNLISTED (site.json `listed: false`, release 14 slice
+// HS): its own two channels transcribe every day like the rest, and it
+// also exposes the first site's first channel. The summary names it nowhere
+// and counts its own channels in no total, so `buildFixtureSummary()` is
+// exactly `buildFixtureSummary(FIXTURE_SITES, [])` — every number the specs
+// read is the six listed sites'.
export const FIXTURE_SUMMARY_NAME = ".e2e-summary.json";
@@ -42,7 +48,8 @@ export const FIXTURE_PALE_HEX = "#f4c2d7";
// site.json one (a named accent's site, the custom hex's and a site with no
// accent have one — the last ends MID-WORD, "Fix" + "ture Three", as
// "Jer" + "alyzer" does; the rest show their title plain); `daily` how many
-// recordings each channel transcribes a day.
+// recordings each channel transcribes a day; `listed` the site.json key
+// (absent = listed).
export type FixtureSite = {
siteId: string;
siteTitle: string;
@@ -50,6 +57,7 @@ export type FixtureSite = {
wordmarkLead?: string;
channels: number;
daily: number;
+ listed?: boolean;
};
export const FIXTURE_SITES: readonly FixtureSite[] = [
@@ -61,6 +69,16 @@ export const FIXTURE_SITES: readonly FixtureSite[] = [
{ siteId: "fixture-six", siteTitle: "Fixture Six", accent: "vermilion", channels: 2, daily: 3 },
];
+// The unlisted site (an invented id, as every fixture's). Not in FIXTURE_SITES,
+// which is the six the pages show.
+export const FIXTURE_UNLISTED_SITE: FixtureSite = {
+ siteId: "fixture-unlisted",
+ siteTitle: "Fixture Unlisted",
+ channels: 2,
+ daily: 6,
+ listed: false,
+};
+
const DAY = 86_400_000;
const SPIKE = { site: 0, start: Date.UTC(2026, 4, 11), days: 7, count: 12_000 };
const HISTORY_DAYS = 200;
@@ -76,7 +94,10 @@ function uploadDate(n: number): string {
return ymd(d.getTime());
}
-export function buildFixtureSummary(fixtureSites: readonly FixtureSite[] = FIXTURE_SITES) {
+export function buildFixtureSummary(
+ fixtureSites: readonly FixtureSite[] = FIXTURE_SITES,
+ unlistedSites: readonly FixtureSite[] = [FIXTURE_UNLISTED_SITE],
+) {
const stats: VideoStat[] = [];
const channelSites: Record<string, string[]> = {};
const sites: Site[] = [];
@@ -112,17 +133,20 @@ export function buildFixtureSummary(fixtureSites: readonly FixtureSite[] = FIXTU
});
};
const today = Date.UTC(2026, 8, 15);
- fixtureSites.forEach((s, i) => {
+ // A site's own channels, `<siteId>-ch<N>`, each transcribing every day of the
+ // last HISTORY_DAYS; `shared` are other sites' channels it also exposes.
+ const addSite = (s: FixtureSite, i: number, shared: string[] = []) => {
const slugs = Array.from({ length: s.channels }, (_, c) => `${s.siteId}-ch${c + 1}`);
- for (const slug of slugs) channelSites[slug] = [s.siteId];
+ for (const slug of [...slugs, ...shared]) (channelSites[slug] ??= []).push(s.siteId);
sites.push({
siteId: s.siteId,
siteTitle: s.siteTitle,
siteDescription: `${s.siteTitle}, an e2e fixture archive.`,
siteUrl: `https://${s.siteId}.example`,
- channels: slugs.map((slug) => ({ slug })),
+ channels: [...slugs, ...shared].map((slug) => ({ slug })),
...(s.accent ? { accent: s.accent } : {}),
...(s.wordmarkLead ? { wordmarkLead: s.wordmarkLead } : {}),
+ ...(s.listed === false ? { listed: false } : {}),
} as unknown as Site);
slugs.forEach((slug, c) => {
const name = `${s.siteTitle} Channel ${c + 1}`;
@@ -133,13 +157,19 @@ export function buildFixtureSummary(fixtureSites: readonly FixtureSite[] = FIXTU
for (let k = 0; k < count; k++) record(i, slug, name, day);
}
});
- });
+ };
+ fixtureSites.forEach((s, i) => addSite(s, i));
// The megaspike: one week of bulk transcription on the first site's first
// channel.
const spikeSite = fixtureSites[SPIKE.site];
for (let k = 0; k < SPIKE.count; k++) {
record(SPIKE.site, `${spikeSite.siteId}-ch1`, `${spikeSite.siteTitle} Channel 1`, SPIKE.start + (k % SPIKE.days) * DAY);
}
+ // The unlisted sites LAST, so every listed site's recordings (their numbers,
+ // dates and states) are the same with them or without.
+ unlistedSites.forEach((s, j) =>
+ addSite(s, fixtureSites.length + j, [`${spikeSite.siteId}-ch1`]),
+ );
return buildHomepageSummary(stats, channelSites, sites, FIXTURE_NOW);
}
diff --git a/homepage/e2e/unlisted-site.spec.ts b/homepage/e2e/unlisted-site.spec.ts
@@ -0,0 +1,46 @@
+import fs from "node:fs";
+import path from "node:path";
+import { test, expect } from "@playwright/test";
+import {
+ FIXTURE_SITES,
+ FIXTURE_SUMMARY_NAME,
+ FIXTURE_UNLISTED_SITE,
+ buildFixtureSummary,
+} from "./fixture-summary";
+
+// Release 14 slice HS: a site with site.json `listed: false` builds and
+// deploys, and the homepage lists it nowhere — not a card, not a chart series,
+// not a recent item — and counts the channels only it exposes in no total.
+//
+// The fixture summary (fixture-summary.ts) is built over six listed sites and
+// one unlisted one, which has two channels of its own and shares the first
+// site's first channel.
+
+const NEEDLES = [FIXTURE_UNLISTED_SITE.siteId, FIXTURE_UNLISTED_SITE.siteTitle];
+
+test("the summary the server reads is the one built without the unlisted site", () => {
+ const onDisk = JSON.parse(
+ fs.readFileSync(path.resolve(process.cwd(), "e2e", FIXTURE_SUMMARY_NAME), "utf8"),
+ );
+ // Every array and every total: what the six listed sites alone produce.
+ expect(onDisk).toEqual(JSON.parse(JSON.stringify(buildFixtureSummary(FIXTURE_SITES, []))));
+ const text = JSON.stringify(onDisk);
+ for (const needle of NEEDLES) expect(text).not.toContain(needle);
+ expect(onDisk.sites.map((s: { siteId: string }) => s.siteId)).toEqual(
+ FIXTURE_SITES.map((s) => s.siteId),
+ );
+});
+
+for (const route of ["/", "/stats/"]) {
+ test(`${route} names no unlisted site, in its HTML or on the page`, async ({ page }) => {
+ const res = await page.goto(route);
+ expect(res?.ok()).toBe(true);
+ const served = (await res?.text()) ?? "";
+ // The listed sites are there, so the check below is not vacuous.
+ for (const s of FIXTURE_SITES) expect(served).toContain(s.siteId);
+ for (const needle of NEEDLES) {
+ expect(served).not.toContain(needle);
+ expect(await page.content()).not.toContain(needle);
+ }
+ });
+}