Archilyzer · Source

archilyzer

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

commit c38b78cbb36aeba32b6505cf28f68e1b3dee8eb7
parent 7b56e84e53f50611726927b02a6468776ab318c5
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Sat, 26 Sep 2026 00:32:23 -0400

plans: brand + themes — slice S3 as shipped (carry-overs, the Plex Sans l/font build defect, full gates), release 10's record, FACTS, STATE; the rollout section checked against the repo

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

Diffstat:
Mplans/FACTS.md | 123+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mplans/STATE.md | 80+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Mplans/brand-and-themes.md | 196++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
Aplans/release-10.md | 196+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 545 insertions(+), 50 deletions(-)

diff --git a/plans/FACTS.md b/plans/FACTS.md @@ -6453,3 +6453,126 @@ Line numbers are `plans/FACTS.md` lines at `e172749b`, before this record's in-p - **The homepage build refuses symlinks that point outside the project**, so a worktree's `homepage/public` data (~60 MB, `homepage-summary.json` + stats pages) must be COPIED from the primary, not linked. + +## Brand + themes — slices S0–S3 (verified 2026-09-25, `main` @ `b9772e53` + S3 `brand/found-line` @ `2c76f6b2`) + +Next is 16.2.3 (`export/node_modules/next/package.json`). The plan and every slice record are in +[`brand-and-themes.md`](brand-and-themes.md) "As shipped"; the release is +[`release-10.md`](release-10.md). + +- **A force-static GET route handler is copied to its EXACT path under `output: "export"`.** + `next/dist/export/index.js:712` marks an app route handler, and `:728-736` copies its prerendered + `<route>.body` byte for byte to `out/<route>` — no `.html`, no hash. So + `export/app/icons/[file]/route.ts:9-14` (`dynamic = "force-static"`, `dynamicParams = false`, + `generateStaticParams` over `ICON_FILES`) writes `out/icons/icon-192.png` etc., and + `homepage/app/icons/[file]/route.ts:9-14` is its twin. `app/icon.tsx` cannot do this: its URLs + are always hashed. Checked on every S3 build: seven files, PNG magic `89504e47…`, IHDR + 180/192/32/512/512. Such a route must never read the request. +- **`next/og` runs under tsx**, so a unit test can render a real PNG and read its IHDR + (`common/lib/brandIcons.test.ts:129-148`, which also reads pixel (0,0) out of the inflated IDAT to + tell the variants apart). A probe script that uses top-level await must be `.mts`: tsx refuses it + in a `.ts` it compiles to CJS. +- **`/favicon.ico` can be a route handler** (`export/app/favicon.ico/route.ts:7`, + `homepage/app/favicon.ico/route.ts:6`, force-static). Next files `/favicon.ico` as a metadata + route (`next/dist/lib/metadata/is-metadata-route.js:114`, `FAVICON_REGEX`), so it is **missing + from the build's route table but IS emitted** (`out/favicon.ico`, starting `00 00 01 00 03 00`: + three PNG entries, 16/32/48). Next adds no `<link>` for it; `<head>` is exactly `ICON_METADATA` + (`common/lib/brandIconFiles.ts:35-43`). +- **Importing Next's root types into `common/` breaks common's tsc.** `import type { Metadata } + from "next"` pulls `next/index.d.ts:1` (`/// <reference types="./types/global" />`), whose + `ProcessEnv` augmentation makes `NODE_ENV` required (`next/types/global.d.ts:23`) on every + `process.env` object in common's program: 10 errors in untouched tests (S1, tsc run 1). So + `ICON_METADATA` is untyped (`brandIconFiles.ts:32-34`) and the layouts type it. `next/og` does not + carry the reference. +- **The service workers: `SHELL` is `"shell-v2"`, `VERSION` stays `"v1"`, `/icons/` is + network-first.** `export/service-worker/site-sw.js:37-40` (`sw-hub.js:28-31`); activate keeps only + `{SHELL, PAGES, META}` (`:64` / `:54`). **Never bump `VERSION` for a shell change:** `PAGES` and + `META` are `pages-${VERSION}` / `meta-${VERSION}`, so a bump deletes every reader's offline channel + downloads. `/_next/static/` is cache-first (content-hashed); `/icons/` is `networkFirst(req, + SHELL)` (`:107-108` / `:97-98`) because the icons keep their URL across an accent change. + `networkFirst` stores only an `ok` response and falls back to the cache when the fetch throws + (`site-sw.js:134-144`); `common/lib/archive/serviceWorkerRouting.test.ts` runs both workers in + `node:vm` and pins fresh-online, a non-ok response not replacing the cached icon, and the offline + fallback. **The rename's one cost:** activate also drops shell-v1's cached HTML and chunks, so right + after the update an installed app opens offline only after one more online visit. +- **The reader's theme is two localStorage keys** (`common/components/themeConfig.ts:27-36`): + `ytdlp-tb:base` (`system|light|sepia|dark`) and `ytdlp-tb:accent` (an accent id, stored ONLY + while it differs from the site's: `setAccent(siteAccent)` removes it, `ThemeProvider.tsx:178-190`; + `"custom"` is never stored). The retired `ytdlp-tb:theme` / `ytdlp-tb:mode` are read once and + deleted. **The migration** (`migrateLegacy`, `:167-181`, and the same table inside the pre-paint + script, `:207-213`) runs only when no base is stored: mode `light` → `light`, or `sepia` when the + old theme was `archive`; `dark` → `dark`; `system` → `system`; absent → nothing stored. The family + is dropped. The pre-paint script (`buildThemeScript`, `:196-226`) sets `data-base` + `.dark`, then + `data-accent` only from a valid stored id, then every `meta[name=theme-color]`, and **LAST + `data-theme-ready="1"`** — the e2e no-flash marker. After a `reload({waitUntil: "commit"})` the new + document can have no root yet, so a spec waits on `document.documentElement?.dataset.themeReady` + (S2 `e3e3fb26`). `ThemeProvider`'s `useLayoutEffect` (`:137-140`) adopts the stored choice and + re-asserts the attributes before paint, because hydration can reset them. +- **`data-base` / `data-accent` and the cascade** (`common/styles/tokens.css`). `.dark` is on + `<html>` iff the resolved base is dark; `@custom-variant dark (&:where(.dark, .dark *))` (`:39`) + keeps Tailwind's `dark:` on the class, so sepia styles as light. The light block is + `:root, html[data-base="light"]` (`:155-156`) and doubles as the no-script fallback; sepia + (`:247`) and dark (`:325`) are `html[data-base=…]`, (0,1,1), and win over `:root`'s (0,1,0). The + eight `html[data-accent=…]` rules (`:403-437`) come AFTER all three bases at the same + specificity, so the accent wins `--brand` on every base; `--brand` reads the base's `--swatch-<id>`, + so an accent is always its contrast-corrected value for the ground it is on. `--brand-mark` is the + accent's on-dark value on every base (the header mark sits on an ink tile), with a Signal default + on `:root` (`:116-122`) for the editor, which renders no `data-accent`. `--primary` is neutral ink + on every base; `--ring: var(--brand)`. `themeTokens.test.ts` parses the sheet and pins all of it, + including the swatches against `lib/brand.ts` `ACCENTS`. +- **The hub and the homepage render dark on the server; a site cannot.** + `export/app/layout.tsx:109-120` adds a literal `… dark` class and `data-base="dark"` only when + `defaultBase === "dark"` (the hub); `homepage/app/layout.tsx:67-70` always does. A site's default + is `system`, which only the browser can resolve, so a site's server HTML carries `data-accent` + (and a custom hex's inline `--accent-custom-*`) but no `data-base`, and a no-JS reader gets the + light block. `suppressHydrationWarning` is on both `<html>`s; React does not patch attributes on + hydration. +- **Chromium on Linux splits IBM Plex Sans words at 1.5x and up.** The Google-served font has no + TrueType instructions (no `fpgm`/`prep`, no glyph programs) but its `gasp` table asks for + grid-fitting at every size (`{9: 10, 65535: 15}`), so FreeType hints it and each advance is + rounded to a whole CSS pixel: at 14px the `g` of "Signal" is 9 px against a 7.41 px design width, + at every DPR (S3 probe, `$T/s3-probe2.log`; S2's "ships TrueType hinting" was inexact). At + 1x and 1.25x the hinted glyph fills the advance and reads clean; **from 1.5x (1.5, 1.75, 2, 3) it + reads "Sig nal"**. `text-rendering: geometricPrecision` turns hinting off (advance 7.41). It is + scoped `@media (min-resolution: 1.5dppx)` (`tokens.css:141-149`; built as `(min-resolution:1.5x)`) + on `html, button, input, select, textarea` — the UA sheet resets form controls to `auto`. +- **Google Fonts can answer a static-weight request with `l/font?kit=…&skey=…` URLs, and Turbopack + cannot load them.** IBM Plex Sans is a variable master on Google (`wght` 100–700; next's + `font-data.json` lists `variable`). A request for static weights is cut from it — usually a cached + `https://fonts.gstatic.com/s/ibmplexsans/v23/<instance>.woff2`, but at times through the dynamic + instancer, `https://fonts.gstatic.com/l/font?kit=…&skey=…&v=v23`. Turbopack's `next/font/google` + puts the font URL in a `?{"url":…}` import query and requires exactly one query entry, so the `&` + breaks it: `next/font/google queries have exactly one entry` → `Module not found: + '@vercel/turbopack-next/internal/font/google/font'`, and `next dev` never answers (the homepage e2e + webServer timed out at 120 s, S3, 23:30) or `next build` fails (S1, the homepage build). Twice on + 2026-09-25, both on IBM Plex Sans; 26 later static-weight requests all got `/s/` URLs. **Request + a variable family with no `weight`** (`common/styles/fonts.ts:42-46`, the reason in the comment + above it): the variable file is always a pre-built `/s/` URL. Its `wght=400` instance has the + static file's advances and outlines exactly (fontTools, 54 glyphs). IBM Plex Mono is static-only + on Google, and Archivo was already requested variable. +- **Worktree e2e and builds write through the `export/public` links into the primary.** Besides + `sw.js` (the release 9 fact above), `e2e:2origin` with `TWO_ORIGIN_REBUILD=1` runs `pnpm run + build:hub` (`export/e2e-2origin/globalSetup.ts:142`), whose `compose:hub` `writeFile`s + `hub-sites.json`, `corpus.json`, `llms.txt`, `robots.txt`, `_headers` and `sw.js` into + `paths.exportPublicDir` (`common/bin/compose-hub.ts:79-137`). `writeFile` follows a symlink, so a + worktree with no corpus would overwrite the primary's live hub pool with an empty one. Swap those + links for plain copies before the run and relink after (every brand slice did; the primary's files + kept their size and mtime). `rm` of the no-index `hub-summary.json` (`:59-62`) removes the link, + not the target. +- **`pnpm wt` port blocks are `git worktree list` order**, which is the admin dirs under + `.git/worktrees/` sorted by name (`scripts/worktree.mjs:101-109`). Adding `brand-found-line` put it + at #1 ahead of `diet-series`, which moved to #2 (reconfirms the release 9 fact above); removing + `brand-mark` and `brand-themes` will move `diet-series` again. +- **The published accent is always a hex, and an absent accent publishes no key** — a decision, not + an accident (2026-09-25, S3). `accentHex` (`common/lib/accent.ts:107`) maps an id to its on-dark + value; + `siteDescriptor.ts`, `compose-hub.ts` and `homepageSummary.ts` omit `accent` when the site sets + none, although a reader's page then shows Signal. Third-party hubs keep their own fallback for such + a site, as before; the official sites get explicit accents at rollout. + +### Anchors this release made stale + +- `:6441-6443` (Release 9 facts): "The hub defaults to the `archilyzer` theme, dark". The theme + families are gone; the hub opens on the dark base in Signal (`export/app/layout.tsx:36-44`, + `defaultBase: "dark", accent: DEFAULT_ACCENT`), server-rendered, and `--chart-3` is fixed per base + (a violet), no longer the archilyzer block's green. Historical, left in place. diff --git a/plans/STATE.md b/plans/STATE.md @@ -3,9 +3,30 @@ The working memory for the local-AI derived-corpus work. Rewritten at the end of every session, before context is cleared. See [`README.md`](README.md) for the protocol. -**Live on :3001 (2026-09-25, 19:40):** `0213f6c8` (release 9 slice F + hub C1), `BUILD_ID` -`P0VMdKX7gbdsaiS5GvorF`; umtool untouched (`a9YAe_dwhuM6DiCPzIwMD`). `main` is `eebcaa90` (+ C1b, -C2, C3 — export/homepage only, no editor restart needed). Live sites: +**Now (2026-09-26, just after midnight): the brand is merged to `main` and NOT rolled out.** `main` = +`2c76f6b2` + the S3 `plans:` record commit. It is release 10's first content, the Found-line mark and +base × accent reader themes: S0 `7c9e3bdf`, S1 `575ae1d4`, S2 `b9772e53`, then S3 (integrate: review +carry-overs, a coherence pass, the full gates, the records, the runbook) fast-forwarded. Record: +[`release-10.md`](release-10.md); the plan and the per-slice records: +[`brand-and-themes.md`](brand-and-themes.md). +- **The live :3001 editor still runs the pre-brand build**, `0213f6c8` / `BUILD_ID` + `P0VMdKX7gbdsaiS5GvorF` (umtool `a9YAe_dwhuM6DiCPzIwMD`, untouched; the brand does not touch + umtool). The five sites, the hub and the homepage still serve pre-brand builds (below). +- **Next action: the operator's rollout, per `~/reports/release-10/RUNBOOK.html`** (`make-runbook.py` + beside it; the rollout scripts in `scripts/`). In order: cut the export `[Unreleased]` notes (they + are public on every site's `/changelog`); restart :3001 on the new `main` (one editor restart, md5 + before / pre-restart / after-boot, smoke); set each site's accent + wordmark lead in the site form; + preview Anilyzer (`https://brand.anilyzer.pages.dev`) and check it on a phone; then the five sites + by name, the hub, the homepage. +- **Archilyzer Media (the YouTube channel) awaits the operator's picks** on the canvas + (https://claude.ai/artifact/UsUxwgRkP5a3m4jZXucAvG): mark M1 bone / **M2 Signal**, lockup inline / + **imprint** (recommended in bold). Then slice S4 (`brand-and-themes.md` "Proposed"); it needs + `ttf-ibm-plex` and Archivo installed as system fonts. +- Worktrees: `brand-mark` and `brand-themes` are merged and can go; `brand-found-line` once `main` + carries its tip. Removing any of them re-sorts the port blocks (`diet-series` moves up). + +**Live on :3001 — unchanged since 2026-09-25, 19:40:** `0213f6c8` (release 9 slice F + hub C1), +`BUILD_ID` `P0VMdKX7gbdsaiS5GvorF`; umtool untouched (`a9YAe_dwhuM6DiCPzIwMD`). Live sites: - https://archilyzer-hub.pages.dev — C1 + C1b + C2 (archilyzer theme, "Official Instances" with the homepage's figures, scope chips, per-archive state, "N of M archives answered", archive named on every card, bare Ko-fi footer link), deploy `6ef7f472` (20:50); @@ -13,45 +34,28 @@ C2, C3 — export/homepage only, no editor restart needed). Live sites: deploy `e09900af` (20:51); - the five official sites at release 8's slices Z + E (17:45 / 18:10 deploys); they carry no Ko-fi. -**The 2026-09-25 evening plan is complete through step C.** Next = the release 10 candidates below -(plus: `retry: false` on the hub's subs query — C2 re-read LOW), then Phase 4 slice 3. - -**Release 10 candidates (found 2026-09-25 evening, none started):** `retry: false` on the hub's subs -query (a member with no subs corpus is ready ~1 s late); `/ask` on the hub honours the scope chips -(`useHubScope().isOn` in `AskHub`); official-site accents on the hub (shared `seriesColor` fallback -for result stripes + chip dots, needs `hub-summary.json`); the hub orders its cards alphabetically, -the homepage by transcripts; `export/playwright.config.ts` must unlink `public/sw.js` before copying -(a worktree run overwrites the primary's hub SW); YouTube's soft block "try again later" classifies -`deleted`/per_video so it never backs off; the boot pass's wait on the storage pass has no timeout; -`/jobs` does not show `cancelReason`; the safeRevalidate warning is once per bundle. Then Phase 4 -slice 3 (`plans/one-core.md`), the LM chat-only tier config, the `Dockerfile.build` image refresh. - -See the [release-9 rollout record](release-9.md#rollout-2026-09-25-evening--0213f6c8-live-on-3001-third-restart-of-the-day). - -**Next (the 2026-09-25 evening plan, step C2 onward) — the operator ruled at ~19:58:** C2 goes as -proposed (all official sites by default with per-site chips, per-site loading/failed state, "N of M -archives answered", a text source label on each result card, staged loading) — implementing on -`one-core/c2-federated-search`. Ko-fi (ko-fi.com/archilyzer) is a BARE link, no copy at all (the -operator manages the pitch on Ko-fi): homepage + hub footers, sites untouched — on -`one-core/c1b-hub-copy` with the copy rulings: "Official Instances" in Title Case, the sentence under -it removed on hub AND homepage, no subtitles unless needed, the per-site descriptions dropped from -the instance cards. The Paramount Tactical filter clear was the operator's; a full download is wanted -(handling `youtube`, captions arrive with each video). Then C3 (`HUB_LINK_ENABLED = true`) in the -same homepage deploy as C1b/C2; then release 10 lows and Phase 4 slice 3. +**Release 10 candidates (found 2026-09-25 evening, none started; any may join release 10 before its +rollout):** `retry: false` on the hub's subs query (a member with no subs corpus is ready ~1 s late); +`/ask` on the hub honours the scope chips (`useHubScope().isOn` in `AskHub`); official-site accents +on the hub (shared `seriesColor` fallback for result stripes + chip dots, needs `hub-summary.json` — +setting the accents at rollout already fixes the cards' stripes); the hub orders its cards +alphabetically, the homepage by transcripts; `export/playwright.config.ts` must unlink +`public/sw.js` before copying (a worktree run overwrites the primary's hub SW); YouTube's soft block +"try again later" classifies `deleted`/per_video so it never backs off; the boot pass's wait on the +storage pass has no timeout; `/jobs` does not show `cancelReason`; the safeRevalidate warning is once +per bundle. Then Phase 4 slice 3 (`plans/one-core.md`), the LM chat-only tier config, the +`Dockerfile.build` image refresh. + +**The 2026-09-25 evening plan is complete** (steps A–C: release 9 F + C1 live on :3001; C1b, C2, C3 +on the hub and the homepage). The operator's rulings (C2's federated search as proposed; a bare Ko-fi +link on the homepage and hub footers only; the Paramount Tactical filter clear) are recorded in +`release-9.md`. Paramount Tactical downloads in full at lane pace since the operator cleared its +`Quartering` include filter (19:35). See the +[release-9 rollout record](release-9.md#rollout-2026-09-25-evening--0213f6c8-live-on-3001-third-restart-of-the-day). See the [release-8 rollout record](release-8.md#rollout-2026-09-25-late-afternoon--0e72ef73-live-on-3001-second-restart-of-the-day). -**Evening 2026-09-25 (in progress).** Homepage refresh is production (`a684a0ce`). Paramount Tactical -finished on the operator's "queue step 1": metadata-scan job `01M3DBFV07NC0Z6KWZ1BG3K9BX` scanned the -last 201 (0 errors; store 1,370 entries + 12 members-only errors of 1,473); download-missing -`01M3DD3WBVYPPXB1NCQ1HDX8BM` found 1,381 missing, 92 complete, excluded 12 members-only, and -**settled 1,369 by the include filter with no request and no sleep**; `keep-videos` matched 1 -(`NV1QqS9NOiU`, already kept). Both ops jobs read `failed` on the revalidate invariant (release 9 B1). -The include filter is still `Quartering`: clearing it (Configure) re-evaluates the 1,369 for the -auto-download lane — the operator's call. Release 9 (`one-core/r9-fixes`) and hub slice C1 -(`one-core/c1-hub-look`) are reviewed and in fixes; see `plans/release-9.md` once merged. - **Last updated:** 2026-09-25 (late afternoon). **Release 8 is live, the day's second restart.** - Release 8 (`bb3dbb4c` → `0e72ef73`) has two slices, each Opus-reviewed: SHIP AFTER FIXES, then SHIP. diff --git a/plans/brand-and-themes.md b/plans/brand-and-themes.md @@ -1,7 +1,9 @@ # Brand + themes: the Found-line mark, and base × accent -Status: APPROVED 2026-09-25 — implementing on `brand/found-line` (worktree `../brand-found-line`, -pnpm wt block #2). Slices S1 and S2 branch from S0's tip as `brand/mark` and `brand/themes`. +Status: SHIPPED to main 2026-09-25 (S0 `7c9e3bdf`, S1 `575ae1d4`, S2 `b9772e53`, S3 `2c76f6b2` + its +`plans:` record commit), not rolled out. Worked on `brand/found-line` (worktree `../brand-found-line`, +pnpm wt block #1), with S1 and S2 as `brand/mark` and `brand/themes` off S0's tip. The release is +[`release-10.md`](release-10.md); the operator's runbook is `~/reports/release-10/RUNBOOK.html`. Design canvas: https://claude.ai/artifact/UsUxwgRkP5a3m4jZXucAvG (boards *Family*, *In context*, *Themes · base × accent*, and the rejected directions A–C). Read it with Artifact @@ -1033,11 +1035,177 @@ describes the committed icons, which is S1's, so it was left. - the hub on its dark default: the parent mark, and `data-accent="signal"`; - the 390 px phone menus on sepia and dark at DPR 2, after `fonts.ready`: no glyph gaps. +### Slice S3, as shipped — integrate (2026-09-25) + +Branch `brand/found-line`, fast-forwarded to `main` at `b9772e53` (S0 + S1 + S2 merged), worktree +`../brand-found-line`, port block #1. S3 is the integrated tree: the review carry-overs +(`$T/s3-carry.md`), a coherence pass over the merged code and docs, the full gates, the records and +the operator's runbook. Nothing was rolled out. + +**Carry-overs, all done.** +- **Test pins** (S0 review). `brand.test.ts` pins the bar as a literal (`MIN_ACCENT_CONTRAST === 4.5`, + and both contrast loops compare against `4.5`), the sRGB curve at its boundary pair on white + (`#767676` 4.54 passes, `#777777` 4.48 fails — a naive gamma 2.2 would not land there), and the whole + 21-value `ACCENTS` table as literals. `accent.test.ts` pins the literal too. +- **`export/app/lib/site.ts`**: `hubSite()`'s comment re-wrapped at ~80 columns. +- **Service-worker test** (S1 re-read). `serviceWorkerRouting.test.ts`'s fake response gained `ok`, and + both workers get two cases: an online non-ok `/icons/` response is passed through but never replaces + the cached icon (and the next offline launch still gets it), and a failed fetch falls back to the + cached icon. Removing `if (res.ok)` from `site-sw.js`'s `networkFirst` turns the first red. +- **Light base status colours** (S2 left). On their own `-soft` fills over the ground and a card: + success `#1f7a4d` → **`#1c7046`** (4.18 / 4.52 → **4.74 / 5.12**), warning `#96650b` → **`#825809`** + (3.89 / 4.18 → **4.75 / 5.14**); the `-soft` rgba values follow; info (5.20 / 5.62), destructive and + gone (4.69 / 5.08) already passed. On the plain ground they read 5.60 and 5.77. `themeTokens.test`'s + text-on-soft check now runs on all three bases (30 checks). No other file used the old hexes. +- **`text-rendering: geometricPrecision` scoped to high density** — at **1.5dppx**, not the 2dppx the + review suggested. A probe of the built hub (`$T/s3-probe*.log`, `s3-probe-montage*.png`) measured the + `g` of "Signal" at 9 px against its 7.41 px design width at every DPR without the rule. At 1x and 1.25x + the hinted glyph fills the advance and the word reads clean; at 1.5x and 1.75x (150 % and 175 % + desktop scaling) it already reads "Sig nal", as at 2x and 3x. `@media (min-resolution: 1.5dppx)` + (built as `(min-resolution:1.5x)`) keeps 1x/1.25x screens hinted and fixes every density that split; + re-probed clean at 1, 1.25, 1.5, 1.75 and 2. `themeTokens.test` pins the media rule, its selector list, + and that no top-level rule sets `text-rendering`. +- **`export/playwright.config.ts`**: the `sw.js` staging comment sat above an unrelated block and said + "exactly like the committed public/icons/*". It now sits on the copy it explains and says the icons + need no staging (route handlers render them). +- **The absent-accent contract** (S0 review "consider"; parent decision): no change — recorded in + `release-10.md` and FACTS, not in code. + +**Coherence pass.** A grep of code, specs, docs and the editor UI for the retired families (`archive`, +`selenized`, `swiss`, `archilyzer` as a theme, `data-theme`), the retired fonts, `siteAccentVars`, +`FALLBACK_THEME_COLOR`, `HUB_THEME_COLOR` and `public/icons` found only legitimate uses (the +migration's legacy keys and tests, the `data-theme-ready` marker, the no-retired-family test) plus four +stale comments and one hint, fixed: `ui/alert.tsx` and `WorkersView.tsx` ("every theme family"), +`views/laneState.ts` ("four theme families × light/dark", and a `flow/tone.ts` path that is +`pipeline/tone.ts`), and the site form's accent hint ("per theme" → "on each base (light, sepia, +dark)"). `homepage/content/docs/**`, the root `*.md` and SITE.md were already right. +**Changelogs:** the three `[Unreleased]` sections now read as one release. The editor's S1 bullet +promised the header mark would follow the reader's accent "once the theme picker lands" and S0's +promised the header, icons and picker "with the rest of the brand work" — both stale; they now state +what shipped. The icons' network-first refresh and the one-visit offline gap are in the editor and +export bullets; the light status contrast and the homepage chart's stack order are named. Release 9's +bullets are untouched. + +**A build defect the gates found: IBM Plex Sans is now requested variable.** The first homepage e2e run +never started. `next dev` failed on every IBM Plex Sans `@font-face` with `next/font/google queries have +exactly one entry` → `Module not found: '@vercel/turbopack-next/internal/font/google/font'`, and the +webServer timed out at 120 s. The generated CSS (`homepage/.next/dev/static/chunks/…ibm_plex_sans…css`) +held `https://fonts.gstatic.com/l/font?kit=…&skey=…&v=v23` URLs: Google had cut the four static weights +through its dynamic instancer, and Turbopack carries the font URL in a one-entry import query that the +`&` splits. It is the error that failed S1's first homepage build too; 26 static-weight requests made +afterwards all got `/s/` files, so it is intermittent upstream, and a site build during the rollout +could fail the same way. `fonts.ts` now asks for Plex Sans with no `weight` (variable, `wght` +100–700): the variable file is always a pre-built `/s/` URL, and its `wght=400` instance has the static +file's advances and outlines exactly (fontTools, 54 glyphs; both files carry no TrueType instructions +and the same `gasp`). The UI's 400–700 are unchanged; the build now emits 12 Plex Sans faces +(`font-weight: 100 700`) instead of 48, and no `l/font` URL. The glyph probe after the change reads the +same advances at every DPR. Archivo was already requested variable; IBM Plex Mono is static-only on +Google and never goes through the instancer. While there, the `tokens.css` comment is corrected: S2 +wrote "IBM Plex Sans ships TrueType hinting", but the Google-served font has no instructions; its +`gasp` table asks for grid-fitting and FreeType hints it. + +| sha | what | +|---|---| +| `6497439d` | brand tests: the literal 4.5 bar, the sRGB boundary pair, all 21 accent values; `accent.test` the literal bar | +| `53a692ac` | `serviceWorkerRouting.test`: a non-ok and a failed online icon fetch leave the cached icon, both workers | +| `1da6a265` | light success `#1c7046` / warning `#825809` (+ soft fills); `themeTokens.test` text-on-soft on every base | +| `408120a3` | `geometricPrecision` under `@media (min-resolution: 2dppx)` + the test (superseded by `dc7ff1cf`) | +| `58e71a88` | `export/app/lib/site.ts` comment wrap; `playwright.config.ts`'s sw.js comment by its code, no committed icons | +| `a0790d39` | stale "theme family" comments (`ui/alert`, `laneState`, `WorkersView`) and the site form's accent hint | +| `25952f12` | the editor/export/homepage `[Unreleased]` brand bullets as one coherent release | +| `dc7ff1cf` | the scope starts at 1.5dppx (measured), test updated | +| `a4542721` | `fonts.ts`: IBM Plex Sans requested variable (the `l/font` build failure) | +| `2c76f6b2` | the `tokens.css` comment: why Plex hints on Linux, why the scope starts at 1.5x | +| _this_ | `plans:` this record, `release-10.md`, FACTS, STATE, the rollout section | + +**Gates** (the code tip is `2c76f6b2`). +- **tsc** clean before every code commit (four runs: `s3-tsc-1..4.log`; the changelog commit is markdown). +- **Unit and script tests** on `25952f12`: common **1,913/1,913** (1,907 + 6: brand +1, service worker + +4, tokens +1), editor unit **79/79**, `test:scripts` **162 + 1 skip**, mcp **219/219**; + `file-schemas-docs.ts --check` clean. On `2c76f6b2`: common **1,913/1,913** again (the font request + touches nothing the other three test). +- **Builds**, `export/public` seeded under sh with `sw.js` a plain copy and no dangling links, + `homepage/public` data copied from the primary: + - on `25952f12` (`s3-gates.log`): editor ok (33 s), export site ok (22 s), export hub ok (19 s), + homepage ok (13 s); + - on `2c76f6b2` (`s3-regate.log`): editor ok (43 s), export site ok (26 s), homepage ok (14 s), export + hub ok (27 s); + - every export/homepage build: `out/icons` = the seven files, PNG magic `89504e470d0a1a0a` and IHDR + 180/192/32/512/512, `favicon.ico` `00 00 01 00 03 00`; the site's `icon.svg` lit Signal `#5fa8a0`, + the hub's and homepage's the parent mark; the site `<html>` carries `data-accent="signal"` and no + `data-base` (theme-color pair `#f3f6f7` / `#0c0a08`), the hub and homepage `dark + data-base="dark" data-accent="signal"`; the built CSS has `@media (min-resolution:1.5x){html,button, + input,select,textarea{text-rendering:geometricprecision}}`. +- **e2e, the full sequence on `dc7ff1cf`** (`s3-e2e.log`, 23:22 → 00:10, no queue wait): + - export full **204 passed**, 6.4 min; + - `e2e:hub` **19 passed**, 37 s; + - `e2e:2origin` (`TWO_ORIGIN_REBUILD=1`, the seven compose outputs + `sw.js` swapped for copies, + relinked after) **3 passed**, 33 s; + - homepage full: **did not start** — the webServer timed out at 120 s on the font defect above; + - **editor full (`pnpm e2e`) 638 passed, 1 failed, 12 skipped, 38.2 min** (651 tests). The failure + is `pipeline.spec.ts:164` "channel list sync button runs the platform-queue sync", 355 ms: + `EEXIST: file already exists, mkdir '…/editor/test-transcripts/channels'` inside `resetData`'s + fixture copy (`editor/e2e/helpers.ts`), before the test touched the page. **Pre-existing, + flaky:** it is the fixture-reset race the helper's own comment describes (a server runner + re-creating a path while the tree is reset; the same class hit `channel-work.spec` and + `pipeline.spec:106` before), and the brand diff touches no helper, runner, channel list or job + code (`git diff --stat c9223978 HEAD -- editor/e2e/helpers.ts editor/app/channels common/jobs + common/controller` is empty). Rerun alone ×3 on the final tree: **3 passed**. +- **e2e re-gate on `2c76f6b2`** (`s3-regate.log`, 00:14 → 00:26, after the font fix): + - homepage full **27 passed**, 42 s; + - editor `pipeline.spec.ts:164 --repeat-each 3` **3 passed**, 39 s; + - editor `theme.spec.ts branding.spec.ts sites-crud.spec.ts` **23 passed**, 58 s; + - export full **204 passed**, 7.2 min; + - `e2e:hub` **19 passed**, 43 s; + - `e2e:2origin` (copies swapped in and relinked as above) **3 passed**, 41 s. + - The full editor suite was not re-run after `a4542721`/`2c76f6b2`: they change only which + Google Fonts URL `next/font` requests (same outlines) and a CSS comment. The suites that compile + those fonts in `next dev` (export, homepage) ran in full on the final tree. +- **The primary checkout's `export/public` was never written:** `sw.js` 10,027 B, mtime 20:47:37, and + `hub-summary.json`, `hub-sites.json`, `corpus.json`, `llms.txt`, `robots.txt`, `sitemap.xml` and + `_headers` kept their sizes and mtimes before, between and after every run. +- Numbers tools: none. + +**Visual set** (`$T/s3-*.png`, served `out/` on localhost, all looked at; embedded in the runbook). A +brass site (`$T/s3-sites/shotsite`, the fixture copy titled Jeralyzer with lead `Jer`): light + brass +(`--brand #95661a`, mark `#e3b15c`), sepia + violet (`#6a4bc4` / `#b49cf2`), dark + brass, the theme +menu open (Base + Accent, Brass tagged DEFAULT), the 390 px phone sheet on sepia at DPR 2 (no split +words); the hub on dark (`data-accent="signal"`, the parent mark, Archi|lyzer; built in the worktree, +so it read the primary's `hub-sites.json` through the link and loaded the live members); the homepage +header. They match the S2 post-merge shots and the canvas. + +**Found and left.** +- **The absent-accent contract stays as it is** (decided; `release-10.md`). +- **The release-10 lows are unstarted**, including `playwright.config.ts` copying over `public/sw.js` + without unlinking it: S3 replaced the worktree's link with a copy before every run instead. +- **The 1.5dppx threshold was measured on Linux Chromium only.** Chromium on Windows (DirectWrite) and + macOS were not probed; the rule changes nothing where text is not hinted. +- **`e2e:2origin` builds the hub twice per run**: `stageTwoOrigins()` runs at config load, and + Playwright loads the config in the runner and again in the worker (two `compose-hub … Compiled` + passes in `s3-e2e-2origin.log`). Pre-existing; it costs ~30 s. +- **Worktree state:** `export/out` holds the last shot build (the hub, reading the primary's + `hub-sites.json` through the link); `export/.2origin/` holds a hub build (~2.5 GB, gitignored); + `homepage/public` holds copied data (gitignored). The worktree's `export/public` links are intact + and `sw.js` is a copy. + +**Runbook:** `~/reports/release-10/RUNBOOK.html`, generated by `~/reports/release-10/make-runbook.py` +(the release 8/9 generator's shape; screenshots embedded as base64). It writes the rollout scripts to +`~/reports/release-10/scripts/` (`r10-build`, `r10-restart`, `r10-preview`, `r10-sites`, `r10-home`, +`r10-live-check`), release 9's procedure parameterised, logging to `~/reports/release-10/tmp/`. + ## Operator rollout (after merge) -1. Restart the live :3001 editor on the new `main`; it needs S0's form. -2. In the editor's site form (the one writer; never hand-edit `transcripts/`), set accent and - wordmark lead: +The runbook is `~/reports/release-10/RUNBOOK.html` (generated by `make-runbook.py` beside it, which +also writes the rollout scripts to `scripts/`); the release record is +[`release-10.md`](release-10.md). The commands below were checked against the repo (S3). + +0. Cut `export/CHANGELOG.md`'s `[Unreleased]`: it is rendered on every site's public `/changelog`. + `/sites` → Release notes → Cut release; untick "Commit changelog" while the primary has untracked + files (it refuses a dirty tree) and commit `export/CHANGELOG.md` by hand. +1. Restart the live :3001 editor on the new `main` (release 9's procedure: md5 before / pre-restart / + after-boot, one editor restart, smoke; umtool untouched); it needs S0's form. +2. In the editor's site form (`/sites/<id>`, the one writer; never hand-edit `transcripts/`), set + accent and wordmark lead: | Site | Accent | Wordmark lead | |---|---|---| @@ -1048,13 +1216,17 @@ describes the committed icons, which is S1's, so it was left. | bonnellyzer | blue | Bonnell | | jasolyzer | green | Jaso | -3. `archilyzer deploy site anilyzer --preview brand` (or - `pnpm ops deploy-site --json '{"siteId":"anilyzer","preview":"brand"}' --wait`). On a phone, - check the favicon and installed-PWA icon update (SW `shell-v2`), the picker, and migration - from a stored old theme. -4. Build and deploy every site: `pnpm ops build-deploy --json '{"siteIds":[…]}' --wait`; then the - hub (`pnpm ops build-hub --json '{"deploy":true}' --wait`) and the homepage - (`archilyzer deploy homepage`). +3. Preview Anilyzer: `pnpm ops build-deploy --json + '{"siteId":"anilyzer","skipArchives":true,"preview":"brand"}' --wait` → + `https://brand.anilyzer.pages.dev` (`deploy-site` / `archilyzer deploy site` ship an ALREADY-built + `export/out`, so they need a build first). Check the favicon, the picker, and the migration from a + stored old theme (on the preview origin, seed the legacy keys in the console; the real phone test + is on the production origin after step 4). The installed PWA's icon updates only on production. +4. Build and deploy every site by name: `pnpm ops build-deploy --json + '{"siteIds":["jeralyzer","anilyzer","bonnellyzer","hasanalyzer","rekietalyzer"],"skipArchives":true}' + --wait --wait-timeout 7200`; then the hub (`pnpm ops build-hub --json '{"deploy":true}' --wait`) + and the homepage (`pnpm --filter yt-dlp-transcript-common exec tsx bin/archilyzer.ts build + homepage`, then `… deploy homepage`). `pnpm ops` needs `WORKER_TOKEN` from `editor/.env`. ## Proposed — Archilyzer Media, the YouTube channel (awaiting the operator's picks) diff --git a/plans/release-10.md b/plans/release-10.md @@ -0,0 +1,196 @@ +# Release 10 — the brand: the Found-line mark, and base × accent reader themes + +`main` at `c9223978` (release 9 live on :3001 since 2026-09-25 19:40 as `0213f6c8`; C1b/C2/C3 +deployed to the hub and the homepage the same evening). Release 10's first content is the brand +the operator asked for on 2026-09-25: one mark and one wordmark for Archilyzer and every official +site, and a reader theme made of two independent choices, a **base** (System / Light / Sepia / +Dark) and an **accent** (the site's own by default, any of seven by choice). The five theme +families retire. The plan, the design decisions and the per-slice records are +[`brand-and-themes.md`](brand-and-themes.md); this file records the release as a whole. Rules: +`plans/tools/implementer-rules.md`. + +**The release-10 "lows" in `STATE.md` are still unstarted** (`retry: false` on the hub's subs +query, `/ask` honouring the hub's scope chips, the shared `shelfAccent` fallback, the hub's card +order, `playwright.config.ts` unlinking `public/sw.js` before its copy, the YouTube soft-block +classification, the boot pass's storage wait, `cancelReason` on `/jobs`, the safeRevalidate +warning). Any of them may join this release before it is rolled out. + +## Record + +### The brand, slices S0–S3 (2026-09-25) + +Four slices, each one Opus implementer and one Opus review, under the plan's dependency graph: +S0 alone, then S1 and S2 in parallel worktrees off S0's tip, then S3 on the merged tree. + +| Slice | Branch → tip | Review | Merged to `main` | +|---|---|---|---| +| **S0** palette + data | `brand/found-line` off `d8dd98b8` → `02295a94` (+ `dbf12219`, the Archilyzer Media proposal) | SHIP | `7c9e3bdf` | +| **S1** mark, icons, wordmark | `brand/mark` off `02295a94` → `03a0ce06` | SHIP (fixes `541a46e0`, `405fccd5`, `951a0ff5`, `4a7641af`; re-read SHIP) | `575ae1d4` | +| **S2** base × accent | `brand/themes` off `02295a94` → `42e84f68` (merged `main` as `6456ac6c`) | SHIP AFTER FIXES (7 fixes; re-read SHIP) | `b9772e53` | +| **S3** integrate | `brand/found-line` fast-forwarded to `b9772e53` → `2c76f6b2` + the `plans:` record | — | fast-forward | + +**Gates per slice** (full numbers in each slice's record in `brand-and-themes.md`): + +| Slice | Unit + script tests | Builds | e2e | +|---|---|---|---| +| S0 | common 1,874, editor unit 78, scripts 162 + 1 skip, mcp 219; SITE.md `--check` clean | editor, export, homepage ok | editor `sites-crud` + `branding` 16/16 (run 2; run 1's failure was the new test's own locator), export `site-branding` 7/7, `e2e:hub` 12/12 | +| S1 | common 1,881 → 1,887 (review fixes), editor unit 79, scripts 162 + 1, mcp 219 | editor, export (site, hub, a brass demo), homepage ok; icons + ICO magic checked | export full 199 + 1 (`theme-family.spec:18`, a click-before-hydration race in a spec S2 deleted; rerun 6/6), `e2e:hub` 14, homepage 26, editor 48, `e2e:2origin` 3; review re-gate 8 + 14 | +| S2 | common 1,893 → 1,907 after merging `main`, editor unit 79, scripts 162 + 1, mcp 219 | editor, export (site, hub), homepage ok | pre-merge: export full 199, hub 12, homepage 24, editor 22; post-merge: export full 203 + 1 (a ready-marker race, fixed `e3e3fb26`, then 39/39 over three repeats), hub 19, 2origin 3, homepage 27, editor 23 | +| S3 | common 1,913, editor unit 79, scripts 162 + 1, mcp 219 | editor, export (site, hub), homepage ok, twice | below: export 204, hub 19, 2origin 3, homepage 27, **editor full 638 + 1 flaky (rerun 3/3), 12 skipped** | + +**What shipped, in one place:** +- **S0 — the brand as data.** `common/lib/brand.ts` (seven named accents with a value per ground, + each ≥ 4.5:1 against its ground and its ink; the three grounds; the Found-line mark as a shape + list; `markSvg`; the subject-split wordmark). `site.json` takes `accent` as an id or a hex and a + new `wordmarkLead` (a proper prefix of `headerTitle`, else dropped). The published accent stays a + hex (`accentHex` in `/site.json`, `hub-sites.json`, the homepage summary). The editor's site form + has the swatch radio group + Custom hex and the Wordmark lead field. +- **S1 — the mark everywhere.** Every icon file (`/icons/*`, `/favicon.ico`) is rendered at build by + force-static route handlers (`next/og` PNGs, a PNG-payload ICO), lit with the site's accent; the + hub, the homepage and the editor wear the parent mark (bone on slate). Header = `BrandMark` + + `Wordmark`; the footer credit carries the parent mark; the editor gets a favicon. The manifest is + on ink. Both service workers: `SHELL = "shell-v2"`, `/icons/` network-first, `VERSION` unchanged. +- **S2 — base × accent.** `tokens.css` is three base blocks × eight accent rules (the seven + custom), + with fixed validated charts per base; Archivo / IBM Plex Sans / IBM Plex Mono; the pre-paint + script migrates the retired theme/mode keys once; `ThemeMenu` (Base + Accent), `ThemeToggle` + (System → Light → Sepia → Dark), the phone sheet's two native radio groups; the hub and homepage + render dark on the server. +- **S3 — integrate** (below). + +**The absent-accent contract: no change (decided 2026-09-25, S3).** A site with no `accent` renders +in Signal, but its published `/site.json`, `hub-sites.json` entry and homepage-summary entry still +carry **no** `accent` key, rather than Signal's hex. Publishing one would change what third-party +hubs draw for every accent-less site they already list; today they keep their own fallback, as +before the brand. The official sites get explicit accents at rollout (the settings table below), so +the question is moot for them. Revisit only if a hub asks for it. + +### Slice S3, as shipped — integrate (2026-09-25) + +The full record — every carry-over with its numbers, the coherence pass, the visual set, found and +left — is [`brand-and-themes.md`](brand-and-themes.md) "Slice S3, as shipped". In short: +- **Review carry-overs, all done:** the brand tests pin the literal 4.5 bar, the sRGB boundary pair + and all 21 accent values; the service-worker test covers a non-ok and a failed icon fetch; Light's + success and warning read ≥ 4.5:1 on their own soft fills (`#1c7046` 4.74 / 5.12, `#825809` + 4.75 / 5.14); `geometricPrecision` is scoped to **1.5dppx** (measured: words split from 1.5x, not + only 2x); two stale comments fixed; the absent-accent decision recorded above. +- **Coherence pass:** four stale "theme family" comments and the accent hint fixed; the three + `[Unreleased]` changelogs rewritten as one release (release 9's bullets untouched). +- **A build defect the gates found, fixed:** Google Fonts at times serves IBM Plex Sans's static + weights through `https://fonts.gstatic.com/l/font?kit=…&skey=…`, which Turbopack's + `next/font/google` cannot load, so `next dev` / `next build` failed intermittently (S1's first + homepage build, S3's first homepage e2e). Plex Sans is now requested variable (`wght` 100–700, + identical outlines and advances), whose file is always a pre-built `/s/` URL. + +| sha | what | +|---|---| +| `6497439d` | brand tests: the literal 4.5 bar, the sRGB boundary pair, all 21 accent values; `accent.test` the literal bar | +| `53a692ac` | `serviceWorkerRouting.test`: a non-ok and a failed online icon fetch leave the cached icon, both workers | +| `1da6a265` | light success `#1c7046` / warning `#825809` (+ soft fills); `themeTokens.test` text-on-soft on every base | +| `408120a3` | `geometricPrecision` under `@media (min-resolution: 2dppx)` + the test (superseded by `dc7ff1cf`) | +| `58e71a88` | `export/app/lib/site.ts` comment wrap; `playwright.config.ts`'s sw.js comment by its code, no committed icons | +| `a0790d39` | stale "theme family" comments (`ui/alert`, `laneState`, `WorkersView`) and the site form's accent hint | +| `25952f12` | the editor/export/homepage `[Unreleased]` brand bullets as one coherent release | +| `dc7ff1cf` | the scope starts at 1.5dppx (measured), test updated | +| `a4542721` | `fonts.ts`: IBM Plex Sans requested variable (the `l/font` build failure) | +| `2c76f6b2` | the `tokens.css` comment: why Plex hints on Linux, why the scope starts at 1.5x | +| _this_ | `plans:` this record, `release-10.md`, FACTS, STATE, the rollout section | + +**Gates** (the code tip is `2c76f6b2`). +- **tsc** clean before every code commit (four runs: `s3-tsc-1..4.log`; the changelog commit is markdown). +- **Unit and script tests** on `25952f12`: common **1,913/1,913** (1,907 + 6: brand +1, service worker + +4, tokens +1), editor unit **79/79**, `test:scripts` **162 + 1 skip**, mcp **219/219**; + `file-schemas-docs.ts --check` clean. On `2c76f6b2`: common **1,913/1,913** again (the font request + touches nothing the other three test). +- **Builds**, `export/public` seeded under sh with `sw.js` a plain copy and no dangling links, + `homepage/public` data copied from the primary: + - on `25952f12` (`s3-gates.log`): editor ok (33 s), export site ok (22 s), export hub ok (19 s), + homepage ok (13 s); + - on `2c76f6b2` (`s3-regate.log`): editor ok (43 s), export site ok (26 s), homepage ok (14 s), export + hub ok (27 s); + - every export/homepage build: `out/icons` = the seven files, PNG magic `89504e470d0a1a0a` and IHDR + 180/192/32/512/512, `favicon.ico` `00 00 01 00 03 00`; the site's `icon.svg` lit Signal `#5fa8a0`, + the hub's and homepage's the parent mark; the site `<html>` carries `data-accent="signal"` and no + `data-base` (theme-color pair `#f3f6f7` / `#0c0a08`), the hub and homepage `dark + data-base="dark" data-accent="signal"`; the built CSS has `@media (min-resolution:1.5x){html,button, + input,select,textarea{text-rendering:geometricprecision}}`. +- **e2e, the full sequence on `dc7ff1cf`** (`s3-e2e.log`, 23:22 → 00:10, no queue wait): + - export full **204 passed**, 6.4 min; + - `e2e:hub` **19 passed**, 37 s; + - `e2e:2origin` (`TWO_ORIGIN_REBUILD=1`, the seven compose outputs + `sw.js` swapped for copies, + relinked after) **3 passed**, 33 s; + - homepage full: **did not start** — the webServer timed out at 120 s on the font defect above; + - **editor full (`pnpm e2e`) 638 passed, 1 failed, 12 skipped, 38.2 min** (651 tests). The failure + is `pipeline.spec.ts:164` "channel list sync button runs the platform-queue sync", 355 ms: + `EEXIST: file already exists, mkdir '…/editor/test-transcripts/channels'` inside `resetData`'s + fixture copy (`editor/e2e/helpers.ts`), before the test touched the page. **Pre-existing, + flaky:** it is the fixture-reset race the helper's own comment describes (a server runner + re-creating a path while the tree is reset; the same class hit `channel-work.spec` and + `pipeline.spec:106` before), and the brand diff touches no helper, runner, channel list or job + code (`git diff --stat c9223978 HEAD -- editor/e2e/helpers.ts editor/app/channels common/jobs + common/controller` is empty). Rerun alone ×3 on the final tree: **3 passed**. +- **e2e re-gate on `2c76f6b2`** (`s3-regate.log`, 00:14 → 00:26, after the font fix): + - homepage full **27 passed**, 42 s; + - editor `pipeline.spec.ts:164 --repeat-each 3` **3 passed**, 39 s; + - editor `theme.spec.ts branding.spec.ts sites-crud.spec.ts` **23 passed**, 58 s; + - export full **204 passed**, 7.2 min; + - `e2e:hub` **19 passed**, 43 s; + - `e2e:2origin` (copies swapped in and relinked as above) **3 passed**, 41 s. + - The full editor suite was not re-run after `a4542721`/`2c76f6b2`: they change only which + Google Fonts URL `next/font` requests (same outlines) and a CSS comment. The suites that compile + those fonts in `next dev` (export, homepage) ran in full on the final tree. +- **The primary checkout's `export/public` was never written:** `sw.js` 10,027 B, mtime 20:47:37, and + `hub-summary.json`, `hub-sites.json`, `corpus.json`, `llms.txt`, `robots.txt`, `sitemap.xml` and + `_headers` kept their sizes and mtimes before, between and after every run. +- Numbers tools: none. + +## Rollout + +Nothing is rolled out. The live :3001 editor still runs `0213f6c8` (the pre-brand build); the five +official sites are at release 8's Z + E; the hub and homepage at release 9's C1b/C2/C3. The +operator's runbook is `~/reports/release-10/RUNBOOK.html` (generated by `make-runbook.py` beside it, +with the rollout scripts in `scripts/`). The steps: + +1. **Cut the export release notes first.** `export/CHANGELOG.md`'s `[Unreleased]` is rendered on + every site's public `/changelog` until it is cut: `/sites` → Release notes → Cut release (the + form suggests `0.8.8`). With "Commit changelog" ticked the form refuses a dirty tree, and the + primary has an untracked `settings.json.pre-priority-*`, so untick it and commit the changelog by + hand. The + editor's own notes (`/changelog`) can be cut the same way; they are not public. +2. **Restart the live :3001 editor on the new `main`** (release 9's procedure): check `/jobs` for an + operator-started job; md5 `before`; the editor build into the live `.next` (umtool untouched, not + rebuilt or restarted); md5 `pre-restart`; ONE editor restart; md5 `after-boot` — the three must + match; the smoke (`SMOKE_FAIL=0`, or every flag explained). The editor itself changes look: the + parent mark and a favicon, Archivo/Plex, and the operator's stored theme migrates once. +3. **Set each site's accent and wordmark lead** in the site form (`/sites/<id>`, the one writer; never + hand-edit `transcripts/`): + + | Site | Accent | Wordmark lead | + |---|---|---| + | jeralyzer | brass | Jer | + | rekietalyzer | vermilion | Rekieta | + | hasanalyzer | violet | Hasan | + | anilyzer | sakura | Ani | + | bonnellyzer | blue | Bonnell | + | jasolyzer | green | Jaso | + + Six `site.json` files change, and nothing else (an md5 sweep shows it). +4. **Preview Anilyzer**: `pnpm ops build-deploy --json + '{"siteId":"anilyzer","skipArchives":true,"preview":"brand"}' --wait` → + `https://brand.anilyzer.pages.dev`. On a phone and the desktop: the favicon (Sakura lit line), + the picker (Base + Accent, Sakura tagged default), and the migration — the preview is its own + origin with no stored theme, so seed the legacy keys in the console (`ytdlp-tb:theme=archive`, + `ytdlp-tb:mode=light`) and expect Sepia. The real phone test is on production: pick an old theme + on `anilyzer.pages.dev` before step 5, expect Sepia after it; an installed app's icon updates only + from production (SW `shell-v2`, `/icons/` network-first). +5. **Every official site**: `pnpm ops build-deploy --json + '{"siteIds":["jeralyzer","anilyzer","bonnellyzer","hasanalyzer","rekietalyzer"],"skipArchives":true}' + --wait --wait-timeout 7200` (by name, never `all`). +6. **The hub**: `pnpm ops build-hub --json '{"deploy":true}' --wait`. **The homepage**: + `pnpm --filter yt-dlp-transcript-common exec tsx bin/archilyzer.ts build homepage`, then + `… deploy homepage`. +7. Live checks per surface, and the record of this rollout under this heading. + +**What readers will notice:** a stored theme migrates once (light stays light, Archive light becomes +Sepia, dark stays dark, system stays system; the family is dropped). After the service-worker update +an installed app opens offline only after one more online visit (`shell-v2` drops the old shell); +offline channel downloads are kept.