commit c7d44b39d313eabcc021b53f9d469a2fbae4e3ba
parent f83fcb88aca738ebe9c75dfe98ba6a99d686b297
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date: Fri, 25 Sep 2026 19:05:04 -0400
plans: release 9 — slice C1 as shipped (the hub in the family's register; hub-summary.json; --chart-3 green)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Diffstat:
2 files changed, 139 insertions(+), 0 deletions(-)
diff --git a/editor/CHANGELOG.md b/editor/CHANGELOG.md
@@ -1,6 +1,8 @@
# Changelog
## [Unreleased]
+- **The hub looks and reads like the homepage, and its list of archives is "Official instances" with the homepage's numbers.** The hub now opens in the project's own dark look (the *Archilyzer* theme), as the homepage does; published sites keep theirs. Its front page opens on one sentence, "60,840 hours of speech from every official archive, searchable at once.", and one paragraph. **Official instances** has a card for each built-in archive: its name linking out, its description, and its channels, recordings, transcripts and hours, the same figures the homepage shows. Archives a visitor adds are listed under **Archives you added**, with their channel count and a Remove button, above the add form. The line under the cards reads "Searching 5 archives · … channels · … transcripts right now." and counts what the browser actually loaded, added archives included. The figures come from a new file the hub build writes, `hub-summary.json`, made by the same code that writes the homepage's summary. A hub built without it (an older build, or a machine with no index) shows the cards without figures. "The shelf", "Your archives" and the Member/Added badges are gone. Needs a rebuild and deploy of the hub.
+- **The Archilyzer theme's third chart colour is no longer the "gone" red.** It is now a green (`#5a9e3a` light, `#86c86a` dark). The homepage chart and cards draw the third official instance in it, and a red layer read as "recordings gone". Needs a rebuild and deploy of the homepage.
- **The yt-dlp clip command is back on sites with transcript downloads turned off.** Turning off `transcriptDownloads` (site.json, or the hub's homepage.json) hid three buttons in the transcript viewer. One of them, the yt-dlp button, only copies a `yt-dlp --download-sections` command for a marked clip to the clipboard and serves no file, so it is not a download. It now shows on every site. The switch still hides the Download menu (txt / srt / json) and Copy MD. The site and hub form labels in the editor say so. No setting changed; a site picks this up at its next build and deploy.
- **Export sites: a search restored from the last visit waits for you.** Opening a site (or `/ask`, or the hub) still loads the last query, the filters and the profile from the browser, but no longer runs the search on the first page of a visit; moving between pages after a search keeps it running, so the hub's chat still grounds in the search just done on its front page. The results show the video listing under those filters, and the bar says "Press Enter or click Search to apply", as for any unapplied edit. Search or Enter runs it; so does loading a profile. A link with a query in it (`?qt=`) still runs on arrival. Going straight to the hub's `/ask` in a new visit leaves the restored search held, and that page has no search bar: search on the hub's front page first. The restored search used to re-fetch transcript shards (up to 8 MB each) on every visit to a device that had not cached them. Needs a rebuild and deploy of every export site.
- **The project homepage is rewritten, and it can be deployed as a preview.** The front page of `archilyzer.pages.dev` now opens on one line and a chart. The chart shows the official instances' transcripts by the month each video was published, from 2009 to now, stacked by instance. It is drawn when the site is built, so no chart script loads. Under it is one strip of numbers for the official instances: hours of speech, transcripts, recordings, channels, instances, and recordings gone at the source but still here. The **Official instances** section has a card for each public site, with its channels, recordings, transcripts and hours. **What it does** is now three short paragraphs. The recent-acquisitions list, "How it works" and "What this isn't" are gone from the page. The homepage summary (`homepage/public/homepage-summary.json`) is version 5. It adds `monthly`, `official` and per-site numbers and removes nothing, so `/stats` is unchanged. `archilyzer deploy homepage --preview <branch>` deploys a preview the way `deploy hub --preview` does. Without the flag it still deploys to production (`main`).
diff --git a/plans/release-9.md b/plans/release-9.md
@@ -0,0 +1,137 @@
+# Release 9 — the hub, polished (+ fixes)
+
+`main` at `9247211e`, release 8 live 2026-09-25. The operator withheld the homepage's link to the hub
+(`HUB_LINK_ENABLED = false`) "until it is more polished". Release 9 is that polish — C1 brings the hub
+into the homepage's look and copy with the homepage's own numbers, C2 the federated-search UX — plus
+the fixes slice (`one-core/r9-fixes`). Flipping the homepage's hub link is a later step, not a slice.
+Rules: `plans/tools/implementer-rules.md`. Record file: this file.
+
+## Record
+
+### Slice C1, as shipped — the hub in the family's register (2026-09-25)
+
+Branch `one-core/c1-hub-look` off `main` `9247211e`. **The operator's decision (2026-09-25 evening):**
+the hub (`https://archilyzer-hub.pages.dev`, the export app with `INSTANCE_MODE=hub`) gets the
+homepage's look and copy, and its shelf becomes "Official instances" showing the SAME per-site numbers
+the homepage shows. Scoping: `$T/r9-hub-scope.md` (read-only pass). The federated-search UX
+(`SearchDataContext.tsx`, `SearchResults.tsx`, `SearchSessionContext.tsx`) is C2's and untouched.
+
+**Theme.** `export/app/layout.tsx` branches on `instanceMode() === "hub"` (server-only, the file is a
+server component) to `defaultTheme="archilyzer" defaultMode="dark"`, exactly `homepage/app/layout.tsx`'s,
+and the hub's browser-chrome `themeColor` is the homepage's `#151b20`. Site builds keep
+`base`/`system` and `#2563eb`; `theme.spec.ts`/`theme-family.spec.ts` (site build) stay green. The
+built hub `index.html` carries `archilyzer` in its ThemeScript; a render of the built hub reads
+`data-theme="archilyzer"` + `.dark` with no stored preference.
+
+**`--chart-3`** in `[data-theme="archilyzer"]` was the `--state-gone` hex in both modes, so the third
+official instance (Bonnellyzer, in `seriesColor` order) drew as "gone" on the homepage chart and card.
+It is now a green, lightness-stepped away from the gone red so the two stay apart under CVD simulation
+too. Chosen by the dataviz validator (OKLab ΔE ×100, Machado CVD), against the neighbours in the
+fixed order:
+
+| mode | `--chart-3` | vs `--state-gone` normal / protan / deutan | vs chart-2 | vs chart-4 | contrast on `--chart-surface` |
+|---|---|---|---|---|---|
+| light | `#5a9e3a` (was `#a8412d`) | 25.9 / 21.8 / 11.2 | 26.9 | 30.3 | 3.29:1 on `#ffffff` |
+| dark | `#86c86a` (was `#c4553f`) | 29.1 / 27.4 / 17.1 | 20.8 | 26.6 | 8.03:1 on `#1a2229` |
+
+Hues near magenta read as the gone red at normal vision (ΔE ~10–11); darker greens collapse into it for
+deutans (ΔE 2–5). The palette's pre-existing validator failures are unchanged and not this slice's:
+chroma floor (`chart-1`, `chart-5` light; `chart-1`, `chart-2` dark), the chart-1↔chart-2 normal-vision
+floor (12.4 light / 9.0 dark), and the dark lightness band. Green also sits near `--success`; a chart
+series is not chrome state, so that was accepted.
+
+**The numbers, at build time.** `compose-hub.ts` now also writes `public/hub-summary.json`
+(`lib/hubSummary.ts`, version 1): `{version, generatedAt, official, sites[]}`, each site
+`{siteId, siteTitle, siteDescription?, siteUrl, channels?, recordings?, transcripts?, hoursArchived?,
+gone?, accent?}` — `transcripts` is the homepage summary's `transcribed.total`. It is `toHubSummary()`
+of the SAME `buildHomepageSummary` call the homepage makes: the input gathering (buildStats → read the
+whole-pool pages → channel→sites map → summarise) moved out of `compose-homepage.ts` into
+`common/controller/poolSummary.ts` (`buildPoolSummary`, `readStatsPages`, `channelSitesOf`), and both
+composes call it. The hub's whole-pool stats pages go to `<exportIndexDir>/hub-stats` (staging, not
+served). **The file is optional:** no index (`paths.lmdbPath` absent) → not written and any stale copy
+removed; a failure → logged, removed, the hub build carries on. `compose-site.ts` removes it with
+`hub-sites.json`, so a site bundle never ships it. Not in `HUB_CORS_PATHS` (read same-origin);
+`builtHubProblem` unchanged. Why build time: a member's `corpus.json` carries only channel/video
+counts, so a live sum could never equal the homepage's; the hub is rebuilt alongside the sites.
+
+**Proved equal, read-only:** a scratch script (`$T/c1-verify.mts`) ran `readStatsPages` over the
+primary's `homepage/public/stats` (77,842 records), `listSites` over the primary's `sites/`, and
+`buildHomepageSummary` + `toHubSummary`, and compared with `toHubSummary` of the live
+`homepage/public/homepage-summary.json`: **MATCH** (official 5 sites / 63 channels / 75,785 recordings
+/ 49,767 transcripts / 60,840 h / 480 gone). No LMDB was opened. `compose:hub` itself was NOT run
+against the real corpus: `buildStats` writes the index's `statsByPath` sub-DB, so it is not read-only,
+and the 15 GB index cannot be copied. In the worktree it ran with no corpus: "0 built-in pool site(s)
+…; no index to summarise — hub-summary.json skipped." The first real `hub-summary.json` is written by
+the parent's `build-hub`, whose log line says how many instances it covers.
+
+**The page, top to bottom, with its final copy** (`ArchiveShelf.tsx`, `HubStats.tsx`,
+`AddArchive.tsx`, new `useHubSummary.ts`):
+1. **H1** `{official.hoursArchived} hours of speech from every official archive, searchable at once.`
+ (no summary: `Every official archive, searchable at once.`). **Paragraph:** "One search runs across
+ all of them, in your browser. Any Archilyzer archive can join it: add its address below."
+2. **`Official instances`** (h2) + "The archives I run. Anyone can run their own." + one card per
+ built-in member in `hub-sites.json` order: title linking out (`target="_blank"`, ↗), the summary's
+ description, and channels · recordings · transcripts · hours, each omitted when absent; the
+ `Installable`/`Data-only` badge. The stripe is the site's own `accent`, else `var(--brand)` (no
+ live site sets an accent today, so all five are the family teal). Figures match by `siteId`, then
+ by origin. Zero built-ins: "This hub has no official instances. Add an archive to search it."
+3. **`Archives you added`** (h2, only with externals) + the same card with the descriptor's channel
+ count only and the Remove control (`aria-label="Remove {title}"`), then "Add an archive by its
+ address. It stays in this browser until you remove it." + the form. The Add button is the
+ family's CTA (ink on ground, brand on hover), no longer a brand fill.
+4. **Live line:** "Searching N archives · N channels · N transcripts right now." — the LIVE federation,
+ added archives included; the code comment says so, the page does not.
+5. `/ask` hub branch: "…for other ways to use every archive on this hub." (was "this archive").
+ `siteRegistry.ts` ALREADY: "That archive is already on this hub." (was "on your shelf"; no spec
+ asserts it).
+
+**Dropped:** "The shelf" eyebrow, the "Your archives" H1, the Member/Added badges (the two sections
+replace them), the empty-shelf sentence. `data-testid="shelf-spine"` stays on every card in both
+sections.
+
+**Changed locators** (same commit as the copy): `getByRole("heading", { name: "Your archives" })` →
+`getByRole("heading", { level: 1, name: /searchable at once\./ })` in `e2e-hub/federation.spec.ts`,
+`e2e-hub/ask-grounding.spec.ts`, `e2e-2origin/federation.spec.ts`. `e2e-hub/federation.spec.ts`
+"adds an archive by URL and shows it on the shelf" is renamed "…shows it under Archives you added" and
+also asserts the h2 and `Remove Origin B`. Unchanged: `getByLabel("Archive URL")`, button `Add`,
+`getByTestId("shelf-spine")`, `p[role="alert"]`, `role="status"` `Added {title}.`, the `/ask` H1.
+New `e2e-hub/official-instances.spec.ts` (+2): with a stubbed `hub-summary.json` the official card
+shows its four figures, description and outbound link, the H1 carries the hours, and there is no Remove
+and no "Archives you added"; with it 404, the card renders with no `dl` and the fallback H1.
+
+| sha | what |
+|---|---|
+| `ec5f8ebe` | `export/app/layout.tsx`: the hub defaults to the archilyzer family, dark, and the homepage's theme colour; sites unchanged |
+| `6d847cdc` | `common/styles/tokens.css`: archilyzer `--chart-3` light `#5a9e3a`, dark `#86c86a` |
+| `812d1b86` | `common/lib/hubSummary.ts` (+ test, 5), `common/controller/poolSummary.ts`, `compose-hub.ts` writes `hub-summary.json` (+ `compose-hub.test.ts`, 1: no index → no file, stale removed, no index created), `compose-homepage.ts` on `buildPoolSummary`, `compose-site.ts` removes it, `publish/build.ts` doc |
+| `dc9ba0dd` | the hub page (`ArchiveShelf`, `HubStats`, `AddArchive`, `useHubSummary`, `HubHome` comment), `/ask` wording, registry ALREADY copy, the spec locators, `official-instances.spec.ts` |
+
+**Gates** (on `dc9ba0dd`): tsc clean (per commit). Common tests **1,822/1,822** (1,816 + 6). test:scripts
+**161 passed + 1 skipped**; the first run, concurrent with the export builds, had 1 failure in
+`scripts/queue-lock.test.mjs` "prints a banner naming the holder while waiting" — a timing test this
+slice does not touch; the idle re-run passed. `pnpm --filter export exec next build` (site) ok;
+`compose:hub` then `INSTANCE_MODE=hub … next build` ok (11 static routes); both with the one known
+Turbopack warning. e2e, queued: **`e2e:hub` 11 passed** (9 + 2), 36 s (after ~6 min in the queue behind
+`one-core/r9-fixes`); export `theme.spec.ts theme-family.spec.ts site-branding.spec.ts` **8 passed**,
+29 s; **`e2e:2origin`** (`TWO_ORIGIN_REBUILD=1`) **3 passed**, 44 s — the six compose-hub outputs in
+`export/public` swapped for copies before and relinked after; the primary's files were untouched
+(mtimes 18:09, `hub-sites.json` still the five-site pool). A render of the built hub with the verified
+summary and the real `hub-sites.json` (members' origins blocked) was checked at 1280 dark/light and
+390 dark. The editor build, editor unit and mcp were not run: nothing they build or test changed
+beyond `common/`, which tsc and the common tests cover. **Numbers tools: none.**
+
+**Found and left:**
+- The branch `one-core/r9-fixes` changes `buildHomepageSummary` (`c714bc14`: official.transcripts
+ counted where transcripts are placed). `hub-summary.json` is a projection of whatever it returns, so
+ after both merge the hub and the homepage still agree — as long as both are rebuilt from the same
+ commit. The MATCH above is against the pre-change function.
+- No official site sets `accent`, so every official card wears `var(--brand)`. Giving each archive its
+ accent is a `site.json` edit, not code.
+- The live line counts what loaded; while members load it climbs, and a member that fails silently
+ lowers it with no sign — C2's per-site state is the fix.
+- The H1 swaps from the fallback to the hours sentence when `hub-summary.json` arrives (client fetch,
+ one frame of text change). Embedding it at build would need the hub page to read `public/` at build;
+ left as a fetch so the e2e can stub it.
+- The chart-3 change repaints the homepage's third instance too; it needs a homepage rebuild to show.
+
+## Rollout