Archilyzer · Source

archilyzer

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

commit c5819828c5a08a52d18e419f10dae3dc4a100ec0
parent 06209852b27ec8008b311e4cf8a0e3fc34721d47
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Mon,  5 Oct 2026 05:22:37 -0400

hub and homepage: a cited report site is never listed (isListedSite) — no hub member, no homepage card, total or channel-sites entry, no footer link; a full site with reports is listed as before; SITE.md regenerated

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

Diffstat:
MSITE.md | 4++--
Mcommon/bin/compose-hub.test.ts | 41+++++++++++++++++++++++++++++++++++++++++
Mcommon/bin/compose-hub.ts | 3++-
Mcommon/controller/poolSummary.test.ts | 10++++++++++
Mcommon/lib/homepageSummary.test.ts | 11+++++++++++
Mcommon/lib/homepageSummary.ts | 7++++++-
Mcommon/lib/siteSchema.test.ts | 13+++++++++++++
Mcommon/lib/siteSchema.ts | 16++++++++++------
Meditor/app/sites/components/SiteForm.tsx | 2++
9 files changed, 97 insertions(+), 10 deletions(-)

diff --git a/SITE.md b/SITE.md @@ -163,7 +163,7 @@ 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. +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. A private site and a cited site (`publish: "cited"`) are never listed, whatever `listed` says. Default: `true` @@ -175,7 +175,7 @@ Default: absent ## `publish` -What this site publishes. `"full"` (the default; absent): the searchable corpus of its `channels`. `"cited"`: only the site's `reports` and the moments they cite — no search, no browse, no full transcripts, no archives; `channels` is then the pool its citations may resolve against. The cited scope is applied by the reports pipeline at build time. Only `"cited"` is written; any other value reads as `"full"`. +What this site publishes. `"full"` (the default; absent): the searchable corpus of its `channels`. `"cited"`: only the site's `reports` and the moments they cite — no search, no browse, no full transcripts, no archives; `channels` is then the pool its citations may resolve against. The cited scope is applied by the reports pipeline at build time. A cited site is not a searchable archive, so the family does not list it (as `listed: false`, whatever `listed` says): no hub membership, no homepage card or totals, no footer link from other sites. Only `"cited"` is written; any other value reads as `"full"`. Default: `"full"` diff --git a/common/bin/compose-hub.test.ts b/common/bin/compose-hub.test.ts @@ -172,6 +172,47 @@ test("an unlisted site is in none of the hub's files; a listed one is in each", } }); +// Report sites: a cited site (`publish: "cited"`) publishes reports, not a +// searchable archive — no hub member, whatever `listed` says. A full site with +// reports stays one. +test("a cited site is no hub member; a full site with reports is", async () => { + const root = mkdtempSync(path.join(tmpdir(), "compose-hub-")); + const log = console.log; + try { + const paths = fixturePaths(root); + const write = (id: string, site: Record<string, unknown>) => { + mkdirSync(path.join(paths.sitesDir, id), { recursive: true }); + writeFileSync(path.join(paths.sitesDir, id, "site.json"), JSON.stringify(site)); + }; + write("fixture-full", { + siteTitle: "Full Fixture", + siteUrl: "https://fixture-full.example", + reports: ["demo-report"], + }); + write("fixture-cited", { + siteTitle: "Cited Fixture", + siteUrl: "https://fixture-cited.example", + publish: "cited", + listed: true, + }); + console.log = () => {}; + await main({ paths }); + console.log = log; + const pool = JSON.parse( + readFileSync(path.join(paths.exportPublicDir, "hub-sites.json"), "utf8"), + ) as Array<{ siteId: string }>; + assert.deepEqual(pool.map((s) => s.siteId), ["fixture-full"]); + for (const f of ["corpus.json", "llms.txt"]) { + const text = readFileSync(path.join(paths.exportPublicDir, f), "utf8"); + assert.ok(text.includes("fixture-full.example"), `${f} lists the full site`); + assert.ok(!text.includes("fixture-cited"), `${f} names the cited site`); + } + } finally { + console.log = log; + rmSync(root, { recursive: true, force: true }); + } +}); + // Release 17 slice XP (the review's HIGH 1): a site's compose leaves its data // in public/ — a private site's X posts included — and the hub builds from // public/ next. compose-hub removes every per-site entry, through a link only diff --git a/common/bin/compose-hub.ts b/common/bin/compose-hub.ts @@ -5,7 +5,8 @@ // reads every archive cross-origin at runtime. It emits: // // public/hub-sites.json <- the built-in trusted pool (listSites with a siteUrl, -// listed — site.json `listed`, isListedSite) +// listed — site.json `listed`, isListedSite; never +// a private or a cited report site) // public/hub-summary.json <- the official instances' numbers, the homepage's // own (lib/hubSummary.ts) — OPTIONAL: skipped when // there is no index to walk diff --git a/common/controller/poolSummary.test.ts b/common/controller/poolSummary.test.ts @@ -27,6 +27,16 @@ test("channel-sites.json names listed sites only; a channel only an unlisted sit assert.ok(!JSON.stringify(channelSitesOf(sites)).includes("fixture-unlisted")); }); +// Report sites: a CITED site is never listed either — its channels are its +// citations' pool, not a published archive. +test("channel-sites.json leaves out a cited site", () => { + const sites = [ + parseSite("fixture-a", { channels: [{ slug: "shared" }] }), + parseSite("fixture-cited", { publish: "cited", channels: [{ slug: "shared" }, { slug: "own" }] }), + ]; + assert.deepEqual(channelSitesOf(sites), { shared: ["fixture-a"] }); +}); + // Release 17 slice XP: a PRIVATE site is never listed, so it is in neither; and // with X posts private an X channel a public site leaves out of its build is // not mapped to that site (buildPoolSummary passes the narrowing). diff --git a/common/lib/homepageSummary.test.ts b/common/lib/homepageSummary.test.ts @@ -305,6 +305,17 @@ test("an unlisted site is in no array and no total; a channel it shares is the l assert.equal(s.version, 6); }); +// Report sites: a cited site (`publish: "cited"`) is not a searchable archive, +// so the homepage lists it no more than an unlisted one. +test("a cited site is in no array and no total, exactly as an unlisted one", () => { + const cited = { ...site("zeta", ["q1", "a1"], "https://zeta.example"), publish: "cited" } as Site; + const channelSites = { ...CHANNEL_SITES, a1: ["alpha", "zeta"], q1: ["zeta"] }; + const own = [stat({ channelSlug: "q1", id: "q-1", uploadDate: "20251101", duration: 7200 })]; + const s = buildHomepageSummary([...STATS, ...own], channelSites, [...SITES, cited], NOW); + assert.deepEqual(s, buildHomepageSummary(STATS, CHANNEL_SITES, SITES, NOW)); + assert.ok(!JSON.stringify(s).includes("zeta")); +}); + test("an unlisted site with no siteUrl, or with every channel shared, changes nothing either", () => { const base = buildHomepageSummary(STATS, CHANNEL_SITES, SITES, NOW); // No siteUrl: never public, and its own channel is still in no total (unlike diff --git a/common/lib/homepageSummary.ts b/common/lib/homepageSummary.ts @@ -12,7 +12,8 @@ import { VIDEO_STATES, type VideoState } from "./availability"; // multi-MB whole-pool stats dataset. // // Scope: the chart "universe" is PUBLIC sites only (those with a siteUrl that -// are listed — site.json `listed`, lib/siteSchema.ts isListedSite), and every +// are listed — site.json `listed`, lib/siteSchema.ts isListedSite; never a +// private or a cited report site), and every // video is attributed to a single PRIMARY public site (the first, by sorted id, // exposing its channel) so the Site and Channel breakdowns partition the same // set and combined totals stay honest. The KPI `totals` (and `availability`) @@ -43,6 +44,10 @@ import { VIDEO_STATES, type VideoState } from "./availability"; // and a channel only unlisted sites expose is in no total — `totals` and // `availability` included. No field was added or removed; the number says the // totals' scope moved. +// +// Still v6: a cited report site (site.json `publish: "cited"`) is left out +// exactly as an unlisted one (lib/siteSchema.ts isListedSite) — the same scope +// rule, applied to a kind of site no summary had yet counted. export const HOMEPAGE_SUMMARY_VERSION = 6; // Day buckets are capped to this many trailing days so the embedded summary stays diff --git a/common/lib/siteSchema.test.ts b/common/lib/siteSchema.test.ts @@ -376,6 +376,19 @@ test("isListedSite is the key's default; channelsOnlyOnUnlistedSites keeps a sha assert.deepEqual([...channelsOnlyOnUnlistedSites([sites[0]])], []); }); +test("a cited site is never listed, whatever `listed` says; a full site with reports is listed as before", () => { + assert.equal(isListedSite({ publish: "cited" }), false); + assert.equal(isListedSite({ publish: "cited", listed: true }), false); + assert.equal(isListedSite({ publish: "full" }), true); + assert.equal(isListedSite(parseSite("full-with-reports", { reports: ["demo-report"] })), true); + const sites = [ + parseSite("shown", { channels: [{ slug: "shared" }] }), + parseSite("reports", { publish: "cited", channels: [{ slug: "shared" }, { slug: "pool-only" }] }), + ]; + // A cited site's channels are its citations' pool, not a published archive. + assert.deepEqual([...channelsOnlyOnUnlistedSites(sites)], ["pool-only"]); +}); + 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", diff --git a/common/lib/siteSchema.ts b/common/lib/siteSchema.ts @@ -184,11 +184,11 @@ export const SITE_FIELD_DOCS: FieldDocs<Site> = { 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.", + "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. A private site and a cited site (`publish: \"cited\"`) are never listed, whatever `listed` says.", audience: 'Who this site is built for. `"public"` (the default; absent) or `"private"`: the operator\'s own reading copy, built on this machine and never deployed — every deploy path (Build & deploy, Deploy, `archilyzer deploy site`, Build & deploy all, docker/publish-site.sh) refuses it before any upload, while a build without a deploy still works. A private site is never listed (as `listed: false`, whatever `listed` says), publishes no `hubUrl`, and its `/corpus.json` says `"audience": "private"`. Content kept from the public — X posts while `social.x.visibility` is `"private"` — is built only into private sites. Only `"private"` is written.', publish: - 'What this site publishes. `"full"` (the default; absent): the searchable corpus of its `channels`. `"cited"`: only the site\'s `reports` and the moments they cite — no search, no browse, no full transcripts, no archives; `channels` is then the pool its citations may resolve against. The cited scope is applied by the reports pipeline at build time. Only `"cited"` is written; any other value reads as `"full"`.', + 'What this site publishes. `"full"` (the default; absent): the searchable corpus of its `channels`. `"cited"`: only the site\'s `reports` and the moments they cite — no search, no browse, no full transcripts, no archives; `channels` is then the pool its citations may resolve against. The cited scope is applied by the reports pipeline at build time. A cited site is not a searchable archive, so the family does not list it (as `listed: false`, whatever `listed` says): no hub membership, no homepage card or totals, no footer link from other sites. Only `"cited"` is written; any other value reads as `"full"`.', reports: "The site's published reports, in display order: report ids (lowercase slugs, `[a-z0-9][a-z0-9-]*`), each a directory under `sites/<siteId>/reports/`. A report directory not named here is a draft and is not published. Invalid and repeated ids are dropped. Absent/empty = no reports.", relatedSites: @@ -224,9 +224,13 @@ export function isValidSiteId(id: unknown): id is string { // pure summary builder can use it without importing file I/O. // // A PRIVATE site (`audience: "private"`) is never listed, whatever `listed` -// says: it is never deployed, so there is nothing at its URL to list. -export function isListedSite(site: Pick<Site, "listed" | "audience">): boolean { - return site.listed !== false && !isPrivateSite(site); +// says: it is never deployed, so there is nothing at its URL to list. Nor is a +// CITED site (`publish: "cited"`): it publishes reports and the moments they +// cite, not a searchable archive, so it is no hub member (federated search +// would find no channel there), no homepage card and in no family total. A +// full site with reports is listed as before. +export function isListedSite(site: Pick<Site, "listed" | "audience" | "publish">): boolean { + return site.listed !== false && !isPrivateSite(site) && !isCitedSite(site); } // The channels whose content belongs to unlisted sites alone: exposed by at @@ -235,7 +239,7 @@ export function isListedSite(site: Pick<Site, "listed" | "audience">): boolean { // 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" | "audience" | "channels">[], + sites: readonly Pick<Site, "listed" | "audience" | "publish" | "channels">[], ): Set<string> { const onListed = new Set<string>(); const onUnlisted = new Set<string>(); diff --git a/editor/app/sites/components/SiteForm.tsx b/editor/app/sites/components/SiteForm.tsx @@ -377,6 +377,8 @@ export function SiteForm({ initial, channels, allSites, isNew }: Props) { A cited-only site publishes no search, browse, full transcripts or archives; its channels are the pool its reports&apos; citations resolve against. The reports pipeline applies this scope when the site is built. + It is never listed on the homepage or the hub: it is not a searchable + archive. </span> </label> <div className="flex flex-col gap-1 text-sm">