commit 0fd627c306a107f1bdbc620027ff517617282e60
parent ef987279bed05be673c522ab14a6b222740ba2fa
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date: Tue, 29 Sep 2026 20:47:30 -0400
sites: one checkbox, "List on the Archilyzer homepage and hub", on by default; saveSiteAction carries the opt-out into the Site it rebuilds, so a save of any other field keeps it
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Diffstat:
4 files changed, 78 insertions(+), 0 deletions(-)
diff --git a/editor/app/sites/actions.ts b/editor/app/sites/actions.ts
@@ -92,6 +92,9 @@ export async function saveSiteAction(
};
}
const siteUrl = parseSiteUrl(siteUrlRaw);
+ // Listed on the homepage and hub by default: the same opt-out idiom as
+ // archives below (an unchecked box sends no key → persisted as false).
+ const listed = formData.get("listed") === "on";
// Hub parent (per-site override of the family default) + PWA opt-in.
const hubUrlRaw = String(formData.get("hubUrl") ?? "").trim();
@@ -217,6 +220,8 @@ export async function saveSiteAction(
...(accent ? { accent } : {}),
...(cloudflareProject ? { cloudflareProject } : {}),
...(siteUrl ? { siteUrl } : {}),
+ // The Site is rebuilt from the form: a key missing here is dropped on save.
+ ...(listed ? {} : { listed: false }),
...(hubUrl ? { hubUrl } : {}),
...(pwa ? { pwa: true } : {}),
...(archives ? {} : { archives: false }),
diff --git a/editor/app/sites/components/SiteForm.tsx b/editor/app/sites/components/SiteForm.tsx
@@ -327,6 +327,22 @@ export function SiteForm({ initial, channels, allSites, isNew }: Props) {
defaultValue={initial.siteUrl ?? ""}
hint="Absolute URL this site is served at (e.g. https://jeralyzer.com). Used so other sites can link to it in their footer. Leave blank to omit this site from cross-site lists."
/>
+ <label className="flex items-center gap-2 text-sm">
+ <input
+ type="checkbox"
+ name="listed"
+ defaultChecked={initial.listed !== false}
+ className="accent-brand"
+ />
+ List on the Archilyzer homepage and hub
+ </label>
+ <p className="-mt-2 text-xs text-muted-foreground">
+ On by default. Turn off to leave this site out of the homepage (its
+ card, chart and stats), the hub (its members, search, corpus.json and
+ llms.txt) and every other site's footer, and to count its own
+ channels in none of the published totals. The site still builds and
+ deploys as before, at its own URL.
+ </p>
<Field
label="Hub URL"
name="hubUrl"
diff --git a/editor/e2e/helpers.ts b/editor/e2e/helpers.ts
@@ -152,6 +152,7 @@ export async function writeSite(
? { cloudflareProject: site.cloudflareProject }
: {}),
...(site.siteUrl ? { siteUrl: site.siteUrl } : {}),
+ ...(site.listed === false ? { listed: false } : {}),
...(site.relatedSites ? { relatedSites: site.relatedSites } : {}),
};
await writeFile(
diff --git a/editor/e2e/sites-crud.spec.ts b/editor/e2e/sites-crud.spec.ts
@@ -345,6 +345,62 @@ test("archives + per-video transcript downloads opt-outs round-trip", async ({
await expect(downloads).toBeChecked();
});
+test("the listed opt-out round-trips, and a save of another field keeps it", async ({
+ page,
+}) => {
+ await resetData("empty");
+ // An invented fixture id: no real site is named in a test.
+ await writeSite("fixture-unlisted", {
+ siteTitle: "Unlisted Fixture",
+ siteUrl: "https://fixture-unlisted.example",
+ listed: false,
+ });
+
+ type ListedSiteFile = { siteTitle?: string; listed?: boolean };
+ const file = "test-transcripts/sites/fixture-unlisted/site.json";
+ const listed = page.getByRole("checkbox", {
+ name: "List on the Archilyzer homepage and hub",
+ });
+ const save = async () => {
+ await page.getByRole("button", { name: /save site/i }).click();
+ await expect(
+ page.getByRole("status").filter({ hasText: "Saved" }),
+ ).toBeVisible();
+ };
+
+ // `false` on disk opens unticked, and a save that changes only the title
+ // keeps it: the action rebuilds the site from the form.
+ await page.goto("/sites/fixture-unlisted");
+ await expect(listed).not.toBeChecked();
+ await page.getByLabel(/site title/i).fill("Unlisted Fixture Renamed");
+ await save();
+ await expect(async () => {
+ const site = await readJson<ListedSiteFile>(file);
+ expect(site.siteTitle).toBe("Unlisted Fixture Renamed");
+ expect(site.listed).toBe(false);
+ }).toPass({ timeout: 10_000 });
+
+ // Ticking it removes the key — listed is the default.
+ await page.goto("/sites/fixture-unlisted");
+ await expect(listed).not.toBeChecked();
+ await listed.check();
+ await save();
+ await expect(async () => {
+ const site = await readJson<ListedSiteFile>(file);
+ expect("listed" in site).toBe(false);
+ }).toPass({ timeout: 10_000 });
+
+ // And unticking writes the explicit false again.
+ await page.goto("/sites/fixture-unlisted");
+ await expect(listed).toBeChecked();
+ await listed.uncheck();
+ await save();
+ await expect(async () => {
+ const site = await readJson<ListedSiteFile>(file);
+ expect(site.listed).toBe(false);
+ }).toPass({ timeout: 10_000 });
+});
+
test("the hub form's per-video transcript downloads opt-out round-trips to homepage.json", async ({
page,
}) => {