Archilyzer · Source

archilyzer

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

commit 03cb237c64a43ff2ad687e1074d3cc60b909f106
parent ba5ae2347fc02867852ddc27aa079bd6b16db7f5
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Tue, 29 Sep 2026 20:46:18 -0400

common: site.json `listed` — a per-site opt-out, absent = listed, only false written; one predicate, isListedSite; the footer never links an unlisted sibling

`listed` sits after `siteUrl` in the schema, SITE_FIELD_DOCS, siteFieldsSchema
and siteToDisk; SITE.md regenerated. isListedSite and
channelsOnlyOnUnlistedSites live beside the key in siteSchema.ts and are
exported from lib/site like isValidSiteId, so the pure summary builder can use
them without file I/O. resolveRelatedSites drops an unlisted sibling, even one
a featured group names; an unlisted site's own footer still lists the rest.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

Diffstat:
MSITE.md | 7+++++++
Mcommon/lib/site.ts | 7+++++--
Mcommon/lib/siteSchema.test.ts | 60+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Mcommon/lib/siteSchema.ts | 37+++++++++++++++++++++++++++++++++++++
4 files changed, 108 insertions(+), 3 deletions(-)

diff --git a/SITE.md b/SITE.md @@ -23,6 +23,7 @@ Regenerate this file with `pnpm --filter yt-dlp-transcript-common exec tsx bin/f | [`cloudflareProject`](#cloudflareproject) | absent | | [`accent`](#accent) | absent | | [`siteUrl`](#siteurl) | absent | +| [`listed`](#listed) | `true` | | [`relatedSites`](#relatedsites) | `[]` | | [`pwa`](#pwa) | `false` | | [`archives`](#archives) | `true` | @@ -157,6 +158,12 @@ Absolute public URL of this site's deployment, e.g. `https://jeralyzer.pages.dev Default: absent +## `listed` + +Whether the family lists this site. Opt-OUT: absent/true = listed, only an explicit `false` is written. An unlisted site still builds and deploys as before, and its own pages are unchanged; it is left out of the homepage (cards, chart, `/stats`), the hub (members, federated search, `/corpus.json`, `/llms.txt`), every other site's footer, and the published `channel-sites.json` and pooled `stats/`. A channel only unlisted sites expose is in none of the family's public totals; a channel a listed site also exposes is credited to the listed one. + +Default: `true` + ## `relatedSites` Pulls specific siblings to the front of the footer's cross-site list, in named groups. Siblings not named here fall into a trailing "Other sites" group. Absent/empty = one flat list of every sibling. diff --git a/common/lib/site.ts b/common/lib/site.ts @@ -13,6 +13,7 @@ import { import { socialLinksForSave } from "./socialLinks"; import { readJsonFileSync, writeJsonAtomic } from "./jsonFile-server"; import { + isListedSite, isValidSiteId, parseSite, parseSiteUrl, @@ -138,7 +139,9 @@ export type CrossSiteLink = { siteId: string; title: string; url: string }; export type CrossSiteGroup = { label?: string; sites: CrossSiteLink[] }; // Resolve the footer's cross-site list for `current` against the full pool. -// Siblings that lack a siteUrl (or are `current`) are not linkable and dropped. +// Siblings that lack a siteUrl (or are `current`) are not linkable and dropped, +// and so is an unlisted sibling (`listed: false`, isListedSite) — even one a +// featured group names. An unlisted `current` still lists its siblings. // `current.relatedSites` groups render first, in order, each filtered to known // linkable ids (unknown/used/self skipped, empty groups dropped). Every still- // unused sibling lands in a trailing remainder group — unlabeled when there @@ -149,7 +152,7 @@ export function resolveRelatedSites( ): CrossSiteGroup[] { const byId = new Map<string, CrossSiteLink>(); for (const s of all) { - if (s.siteId === current.siteId || !s.siteUrl) continue; + if (s.siteId === current.siteId || !s.siteUrl || !isListedSite(s)) continue; byId.set(s.siteId, { siteId: s.siteId, title: s.siteTitle, url: s.siteUrl }); } const used = new Set<string>(); diff --git a/common/lib/siteSchema.test.ts b/common/lib/siteSchema.test.ts @@ -9,12 +9,14 @@ import type { z } from "zod"; import { SITE_FIELD_DOCS, SITE_KEYS, + channelsOnlyOnUnlistedSites, + isListedSite, parseSite, siteFieldsSchema, siteToDisk, type Site, } from "./siteSchema"; -import { getSite, siteConfigFile, writeSite } from "./site"; +import { getSite, resolveRelatedSites, siteConfigFile, writeSite } from "./site"; import type { Paths } from "./paths"; const HERE = path.dirname(fileURLToPath(import.meta.url)); @@ -60,6 +62,7 @@ test("empty, null, [] and a number all read as the defaults, every key emitted", assert.equal(want.duplicates, true); assert.equal(want.transcriptDownloads, true); assert.equal(want.pwa, false); + assert.equal(want.listed, true); assert.deepEqual(want.relatedSites, []); assert.equal(want.socialLinks, undefined); assert.ok("socialLinks" in want); @@ -231,6 +234,7 @@ function fixtures(): Array<[string, unknown]> { channels: [{ slug: "c1", groupId: "a", order: 3 }, { slug: "c2", groupId: "q" }], cloudflareProject: "p", siteUrl: "https://s.example//", + listed: false, relatedSites: [{ siteIds: ["x", "x", "BAD"] }, { label: " ", siteIds: [] }], pwa: true, archives: false, @@ -285,6 +289,59 @@ test("writeSite throws on no groups, a default outside the groups, and an unsafe assert.equal(fs.existsSync(siteConfigFile(paths, "s")), false); }); +test("listed: absent reads listed, only an explicit false unlists, and only false is written", async () => { + for (const v of [undefined, true, 0, "false", null]) { + assert.equal(parseSite("s", { listed: v }).listed, true, String(v)); + assert.equal("listed" in siteToDisk(parseSite("s", { listed: v })), false, String(v)); + } + assert.equal(parseSite("s", { listed: false }).listed, false); + // `true` in a caller's Site is the default, so it is not written either. + assert.equal("listed" in siteToDisk({ ...parseSite("s", {}), listed: true }), false); + + // Through the real writer and reader: false survives, absent reads listed. + const paths = scratchPaths(await mkdtemp(path.join(os.tmpdir(), "site-"))); + const hidden = parseSite("s", { siteTitle: "Hidden", siteUrl: "https://h.example", listed: false }); + await writeSite(hidden, paths); + assert.equal(JSON.parse(await readFile(siteConfigFile(paths, "s"), "utf8")).listed, false); + assert.deepEqual(getSite("s", paths), hidden); + await writeSite({ ...hidden, listed: true }, paths); + const disk = JSON.parse(await readFile(siteConfigFile(paths, "s"), "utf8")); + assert.equal("listed" in disk, false); + assert.equal(getSite("s", paths).listed, true); +}); + +test("isListedSite is the key's default; channelsOnlyOnUnlistedSites keeps a shared channel with the listed site", () => { + assert.equal(isListedSite({}), true); + assert.equal(isListedSite({ listed: true }), true); + assert.equal(isListedSite({ listed: false }), false); + const sites = [ + parseSite("shown", { channels: [{ slug: "shared" }, { slug: "mine" }] }), + parseSite("hidden", { listed: false, channels: [{ slug: "shared" }, { slug: "secret" }] }), + parseSite("hidden2", { listed: false, channels: [{ slug: "secret" }, { slug: "secret2" }] }), + ]; + assert.deepEqual([...channelsOnlyOnUnlistedSites(sites)].sort(), ["secret", "secret2"]); + assert.deepEqual([...channelsOnlyOnUnlistedSites([sites[0]])], []); +}); + +test("the footer never links an unlisted sibling, and an unlisted site's own footer still lists the rest", () => { + const current = parseSite("cur", { + siteUrl: "https://cur.example", + relatedSites: [{ label: "Friends", siteIds: ["hidden", "shown"] }], + }); + const shown = parseSite("shown", { siteTitle: "Shown", siteUrl: "https://shown.example" }); + const hidden = parseSite("hidden", { siteTitle: "Hidden", siteUrl: "https://hidden.example", listed: false }); + const other = parseSite("other", { siteTitle: "Other", siteUrl: "https://other.example" }); + const ids = (groups: ReturnType<typeof resolveRelatedSites>) => + groups.flatMap((g) => g.sites.map((s) => s.siteId)); + // Named in a featured group or not, the unlisted site is not linked. + assert.deepEqual(ids(resolveRelatedSites(current, [current, shown, hidden, other])), ["shown", "other"]); + // The unlisted site is still built as before: its footer lists its siblings. + assert.deepEqual( + ids(resolveRelatedSites({ ...hidden, relatedSites: [] }, [current, shown, hidden, other])), + ["cur", "shown", "other"], + ); +}); + test("writeSite → getSite round-trips, and the file holds only non-defaults", async () => { const paths = scratchPaths(await mkdtemp(path.join(os.tmpdir(), "site-"))); const site = parseSite("s", { siteTitle: "Mine", archives: false }); @@ -295,4 +352,5 @@ test("writeSite → getSite round-trips, and the file holds only non-defaults", assert.equal("duplicates" in disk, false); assert.equal("transcriptDownloads" in disk, false); assert.equal("pwa" in disk, false); + assert.equal("listed" in disk, false); }); diff --git a/common/lib/siteSchema.ts b/common/lib/siteSchema.ts @@ -84,6 +84,7 @@ export type Site = { cloudflareProject?: string; accent?: string; siteUrl?: string; + listed?: boolean; relatedSites?: RelatedSiteGroup[]; pwa?: boolean; archives?: boolean; @@ -116,6 +117,8 @@ export const SITE_FIELD_DOCS: FieldDocs<Site> = { 'Per-site brand accent: a named accent id (`signal`, `brass`, `vermilion`, `violet`, `sakura`, `blue`, `green`) or a custom `"#rrggbb"`. It is the site\'s accent on every page; a reader does not pick one. Absent = `signal`, the family default. A custom hex is darkened or lightened per base until it reaches 4.5:1. The public `/site.json` always carries a hex: an id is published as its on-dark value. Any other spelling is dropped.', siteUrl: "Absolute public URL of this site's deployment, e.g. `https://jeralyzer.pages.dev` (trimmed, trailing slashes removed; anything not absolute http(s) is dropped). Drives the cross-site footer: a site with no siteUrl is omitted from every other site's list.", + listed: + "Whether the family lists this site. Opt-OUT: absent/true = listed, only an explicit `false` is written. An unlisted site still builds and deploys as before, and its own pages are unchanged; it is left out of the homepage (cards, chart, `/stats`), the hub (members, federated search, `/corpus.json`, `/llms.txt`), every other site's footer, and the published `channel-sites.json` and pooled `stats/`. A channel only unlisted sites expose is in none of the family's public totals; a channel a listed site also exposes is credited to the listed one.", relatedSites: "Pulls specific siblings to the front of the footer's cross-site list, in named groups. Siblings not named here fall into a trailing \"Other sites\" group. Absent/empty = one flat list of every sibling.", pwa: @@ -139,6 +142,36 @@ export function isValidSiteId(id: unknown): id is string { return typeof id === "string" && SITE_ID_RE.test(id); } +// THE ONE PREDICATE for `listed` (site.json's opt-out; absent = listed). Every +// public output that enumerates the family's sites filters through it: the +// homepage summary (lib/homepageSummary.ts), channel-sites.json and the pooled +// stats (controller/poolSummary.ts, controller/buildStats.ts), the hub's +// member list (bin/compose-hub.ts) and the footer's siblings +// (lib/site.ts resolveRelatedSites). The editor's own pages list every site. +// Here, beside the key, and exported from lib/site like isValidSiteId, so the +// pure summary builder can use it without importing file I/O. +export function isListedSite(site: Pick<Site, "listed">): boolean { + return site.listed !== false; +} + +// The channels whose content belongs to unlisted sites alone: exposed by at +// least one site, and by no listed one. No public total counts them. A channel +// a listed site also exposes is not here (it is credited to the listed site), +// and a channel no site exposes (pool-only) is not here either — the family's +// instance-wide totals have always counted it. +export function channelsOnlyOnUnlistedSites( + sites: readonly Pick<Site, "listed" | "channels">[], +): Set<string> { + const onListed = new Set<string>(); + const onUnlisted = new Set<string>(); + for (const site of sites) { + const into = isListedSite(site) ? onListed : onUnlisted; + for (const c of site.channels) into.add(c.slug); + } + for (const slug of onListed) onUnlisted.delete(slug); + return onUnlisted; +} + export const SITE_DEFAULT_TITLE = "Transcript Browser"; export const SITE_DEFAULT_DESCRIPTION = "Browse and search video transcripts"; @@ -246,6 +279,8 @@ export const siteFieldsSchema = z.object({ ).describe(d.cloudflareProject), accent: settingsField(parseAccentSetting).describe(d.accent), siteUrl: settingsField(parseSiteUrl).describe(d.siteUrl), + // Opt-out: only an explicit false unlists. Absent/true stays listed. + listed: settingsField((v): boolean => v !== false).describe(d.listed), relatedSites: settingsField(parseRelatedSites).describe(d.relatedSites), pwa: settingsField((v): boolean => v === true).describe(d.pwa), // Opt-out: only an explicit false disables. Absent/true stays on. @@ -335,6 +370,8 @@ export function siteToDisk(site: Site): Site { : {}), ...(accent ? { accent } : {}), ...(siteUrl ? { siteUrl } : {}), + // Listed is the default: only the opt-out is persisted. + ...(site.listed === false ? { listed: false } : {}), ...(relatedSites.length > 0 ? { relatedSites } : {}), ...(site.pwa ? { pwa: true } : {}), // Persist only the non-default: archives is on unless explicitly disabled.