Archilyzer · Source

archilyzer

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

commit c50648aa92ca02b0ba5120c41050646dd9368a15
parent ab18ea575333255b40f201bb37aac46c25d5e467
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Tue, 29 Sep 2026 00:13:35 -0400

plans: r14/two-grounds-headers after its review — each finding to its commit, the narrow-header ruling's record and measured table, the Rollout; the plan; STATE

release-14.md: the Review (H1, M1, M2, L1–L10 each to a sha or "left, why";
the growth chart's gaps after M1 on the published summary, 0 covered, and
that they now read as dashes; the nine decisions as ruled; the gates run once
at ab18ea57); "The narrow header, as ruled" (the ruling, `featured`'s new
meaning, 520 px and why, the smallest width at which each real title shows
in full with one marked link and with none, both pointers; Hasanalyzer and
Rekietalyzer miss 360 px under touch by 7 and 8 px, with the fixes for the
operator to pick); "## Rollout" (the editor, the homepage with #instances
before any site, the hub, then the six sites, with the verified commands and
live checks). The plan and STATE follow.

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

Diffstat:
Mplans/STATE.md | 21++++++++++++++++++---
Mplans/export-header-first-search.md | 9+++++++--
Mplans/release-14.md | 236+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
3 files changed, 255 insertions(+), 11 deletions(-)

diff --git a/plans/STATE.md b/plans/STATE.md @@ -190,9 +190,24 @@ changed at integration. Nothing was deployed, cut, pushed or restarted; :3001 st group; an Archilyzer link to the homepage's new `#instances` replaces the sites dropdown and the hub link; Changelog is in the footer; the wordmark's text drops exactly when it does not fit, for any title; the inline nav starts at `lg`. - - **Owed before a production deploy:** the review of this branch, then the parent's merge. The - Archilyzer link lands on the list only once the homepage with `#instances` is deployed. The - export sites need a rebuild and deploy each. + - **Reviewed SHIP AFTER FIXES; the fixes are in:** + - stacked areas in the shared charts keep their coloured edge, with the surface gap on + stacked bars only (H1); + - the growth chart measures thickness at right angles before it takes a gap, so no band is + covered; the gaps now read as dashes, and folding the small sites into "Other" or small + multiples is the operator's call (M1); + - the hub URL texts say what the setting does now (M2); + - the Lows (L1, L2, L5, L6, L9, L10); L3 and L7 are left, and L4 needed nothing. + - **Owed:** the parent's merge, then the rollout in `release-14.md` ("## Rollout"): the editor, + then the homepage (with `#instances`) before any site, then the hub, then the six sites. + - **The narrow header, as ruled after the review, is built** (`447ded9a`). Below 520 px every + header shows the name and only the links marked **Keep in header on small screens** (none + marked → none); from 520 px it shows every link, up to four. At 360 px under touch with one + marked link every real title shows in full except Hasanalyzer (7 px short) and Rekietalyzer + (8 px short). The fix is the operator's to pick: the bar's gap from 12 to 8 px below 520 px + would do it (`release-14.md`). + - **The operator's settings:** mark the link to keep in the header; the parent does this when + the branch lands. - **Next candidates:** one-core Phase 5 (projects join the core, `plans/one-core.md`); the Diagnostics cards keeping their retry log (O3's found-and-left); `ChartView.tsx`'s five-slot cycle reaching `--chart-6` (O2); O5's two wording lows in `svg-faces.mjs` / the README (kerning is not diff --git a/plans/export-header-first-search.md b/plans/export-header-first-search.md @@ -49,6 +49,10 @@ main...<branch>` is empty for the search and header components on all five). link to the Archilyzer home's Official Instances (not on the hub); the header's Hub link goes; Changelog joins the footer after Use with AI; narrow widths behave as the homepage's; the slide-out menu keeps the nav; the editor's header keeps the toggle, with no accent picker. +- 2026-09-28 (after the branch's review): on narrow screens a site's header keeps the site's NAME + and shows only the link(s) marked for the header; the other social links are in the footer. + `featured` now means "keep in the header on small screens" (below 520 px only those; from + 520 px every link up to four, those kept first). Built on `r14/two-grounds-headers`. - Standing: **no copy** beside any social link: icons with accessible names only. ## Decisions and assumptions @@ -235,8 +239,9 @@ constant), `homepage/CHANGELOG.md`. As built, where it differs from the steps below: the inline nav and the Archilyzer link start at `lg`, not `md` (at 768 px they, a long title and four icons did not fit); the Archilyzer link is in the slide-out menu below `lg`; the wordmark's text drops by a pure-CSS wrap in a one-line clipped -box, exact for any title, with no measured threshold; the footer's `-mx` inset is the shared -row's. `header.spec.ts` covers 280–430 px with a short and a long title, 1–4 links, both pointers. +box, exact for any title, with no measured threshold, its width reserved from Archivo's own metrics +so the decision is the same before and after the web font loads (review L2); from `lg` the brand +shrinks first (L1); the footer's row wraps (L6); the footer's `-mx` inset is the shared row's. `header.spec.ts` covers 280–430 px with a short and a long title, 1–4 links, both pointers. Owns `export/app/components/{Header,MobileMenu,Footer,SiblingSwitcher}.tsx`, `export/e2e/{helpers.ts,site-branding.spec.ts,brand.spec.ts,responsive.spec.ts}`, a NEW diff --git a/plans/release-14.md b/plans/release-14.md @@ -19,7 +19,7 @@ slice HP added to it on the operator's ruling of the same day. Rules: | Slice | Branch | What | Owns | |---|---|---|---| | HP | `homepage/social-visible` | The homepage's social row and one theme toggle in the header at every width, the wordmark's text dropped first on a very small screen and the row scrolling only as the last resort, one shared `SocialLinks` component, larger keys with a focus ring; Changelog in the footer only; the instance cards' names as the site's wordmark; the social icon checked by an allowlist on save and at render; a sized SVG with no viewBox gets one; `featured` ("Show in header") on a social link | `common/components/{SocialLinks,SocialScroll,ThemeRadios,ThemeToggle,ThemeScript,ThemeProvider,Wordmark}.tsx` + `themeConfig.ts`, `common/bin/doctor.ts`, the growth chart, `common/lib/{socialSvg,socialLinks}.ts` + tests, `common/lib/settingsSchema.ts` (the social-link type, parser, docs; the normalizer moved to `socialSvg.ts`), `common/lib/normalizeSocialSvg.test.ts`, `common/lib/{settings,site,homepage}.ts` (the save errors), `common/lib/{homepageSummary,siteColor}.ts`, `homepage/app/components/{Header,Footer,ArchiveCards}.tsx`, `homepage/app/lib/{nav,summary}.ts`, `homepage/app/not-found.tsx`, `homepage/e2e/**` (the fixtures, `helpers.ts`, the new and the rewritten specs), `homepage/playwright.config.ts`, `export/app/components/{MobileMenu,Footer}.tsx` (the `ThemeRadios` swap; the footer's read path), `editor/app/components/SocialLinksField.tsx` + `socialLinksJson{,.test}.ts`, `editor/app/{settings,sites}/actions.ts` (the save errors), `editor/e2e/settings.spec.ts`, `SETTINGS.md`, `SITE.md` | -| Lows, chart gap, T1, H1 (H3 folded in), H2 | `r14/two-grounds-headers` | The final review's Lows; the charts' surface gap; two grounds and each site in its own accent; the export and hub headers carry the social row and the toggle as one group, with an Archilyzer link to the homepage's `#instances` in place of the sites dropdown and the hub link; Changelog to the footer | `common/components/{ThemeProvider,ThemeScript,ThemeToggle,SocialScroll}.tsx` + `themeConfig.ts` (and the deleted `ThemeMenu`, `ThemeRadios`), `common/components/charts/{ChartView,CrossSiteChart,surfaceGap}`, `common/styles/tokens.css`, `common/lib/{brand,accent,siteColor,paths,project,socialSvg,siteSchema,settingsSchema}.ts` + tests, `scripts/next-build-trace.test.mjs`, `export/app/components/{Header,MobileMenu,Footer}.tsx` (and the deleted `SiblingSwitcher`), `export/app/{layout.tsx,globals.css,changelog/page.tsx,lib/brand.ts}`, `export/e2e{,-hub}/**` (the theme, header and branding specs), `export/playwright.config.ts`, `editor/app/{layout.tsx,globals.css,sites/components/SiteForm.tsx}`, `editor/e2e/theme.spec.ts`, `homepage/app/{page.tsx,layout.tsx,globals.css,lib/*,changelog/page.tsx,components/{Header,ArchiveGrowthChart,ArchiveCards}.tsx}`, `homepage/e2e/**`, `homepage/content/docs/operate.md`, `SETTINGS.md`, `SITE.md` | +| Lows, chart gap, T1, H1 (H3 folded in), H2 | `r14/two-grounds-headers` | The final review's Lows; the charts' surface gap; two grounds and each site in its own accent; the export and hub headers carry the social row and the toggle as one group, with an Archilyzer link to the homepage's `#instances` in place of the sites dropdown and the hub link; Changelog to the footer; after its review, the narrow header keeps the name and shows only the marked links (every header) | `common/components/{ThemeProvider,ThemeScript,ThemeToggle,SocialScroll}.tsx` + `themeConfig.ts` (and the deleted `ThemeMenu`, `ThemeRadios`), `common/components/charts/{ChartView,CrossSiteChart,surfaceGap}`, `common/styles/tokens.css`, `common/lib/{brand,accent,siteColor,paths,project,socialSvg,siteSchema,settingsSchema}.ts` + tests, `scripts/next-build-trace.test.mjs`, `export/app/components/{Header,MobileMenu,Footer}.tsx` (and the deleted `SiblingSwitcher`), `export/app/{layout.tsx,globals.css,changelog/page.tsx,lib/brand.ts}`, `export/e2e{,-hub}/**` (the theme, header and branding specs), `export/playwright.config.ts`, `editor/app/{layout.tsx,globals.css,sites/components/SiteForm.tsx}`, `editor/e2e/theme.spec.ts`, `homepage/app/{page.tsx,layout.tsx,globals.css,lib/*,changelog/page.tsx,components/{Header,ArchiveGrowthChart,ArchiveCards}.tsx}`, `homepage/e2e/**`, `homepage/content/docs/operate.md`, `SETTINGS.md`, `SITE.md` | | S1 | per the plan | per the plan | per the plan | **Order:** HP → `r14/two-grounds-headers` → S1. The shared files are the three changelogs' @@ -470,6 +470,8 @@ homepage e2e 3340, editor test 3311), one Opus implementer. Scratch files `t-*` text drops first and the scroll box is the last resort; the slide-out menu keeps the nav; the editor's header keeps the toggle, with no accent picker and nothing else restyled. 4. (The final review's Lows, parent.) F1, F3, F4, F5 and F8 fixed; F2, F6 and F7 left. +5. (After the review, operator.) On narrow screens a site's header keeps the site's NAME and shows + only the link(s) marked for the header; the other social links are in the footer. | sha | what | |---|---| @@ -537,9 +539,10 @@ homepage e2e 3340, editor test 3311), one Opus implementer. Scratch files `t-*` - no gap at the stack's top (the surface is already there); - one set of gaps per plot height (200, 260, 300 px), each in a `<g>` shown by its class: a gap is drawn only where both bands are at least 3 px tall at that height, so a band that gives - one keeps at least 1 px of its colour and a thinner band touches its neighbour instead; + one keeps at least 1 px of its colour and a thinner band touches its neighbour instead + (after the review, measured at right angles to the edge: M1 in "Review" below); - a run under three months is dropped. -- **Measured on the published summary:** +- **Measured on the published summary** (the first, vertical rule; after M1 see "Review"): | Plot height | Smallest band that gives a gap keeps | Smallest band with no gap | Month boundaries parted / touching | |---|---|---|---| @@ -550,8 +553,8 @@ homepage e2e 3340, editor test 3311), one Opus implementer. Scratch files `t-*` - **The shared charts** (`common/components/charts/surfaceGap.ts`, `--chart-gap` in `tokens.css`: the chart surface, `Canvas` in forced colours): - stacked bars get a 2 px stroke per segment; - - stacked areas get a 4 px stroke along the band's top edge, half under the band above, so - 2 px show; + - stacked areas got a 4 px stroke along the band's top edge, half under the band above, so + 2 px showed — withdrawn after the review (H1): they keep their series-coloured edge; - single-series charts, lines and side-by-side bars are unchanged. - This covers `ChartView` (the export's charts) and `CrossSiteChart` (the homepage's `/stats` customize view). @@ -634,7 +637,9 @@ homepage e2e 3340, editor test 3311), one Opus implementer. Scratch files `t-*` - At 768 px the nav, the Archilyzer link and a long title with four icons did not fit, so the wordmark hid and an icon scrolled while the nav was still inline. - With the nav in the menu below `lg`, the icons never scroll at 768. -- **Narrow widths, for any title** — the approach, and why: +- **Narrow widths, for any title** — the approach, and why (superseded in part by ruling 5: + below 520 px the header now shows only the marked links, and the wrap and the scroll box are + last resorts; see "The narrow header, as ruled"): - The wordmark's text sits in a one-line box (`h-7`, `overflow-hidden`, `flex-wrap`) behind a zero-width strut. The text is one flex item: when it does not fit beside the strut, it wraps to the second line, which is clipped. @@ -763,4 +768,223 @@ homepage e2e 3340, editor test 3311), one Opus implementer. Scratch files `t-*` | `RETIRED_BASE = "sepia"` stays in `themeConfig.ts` for the migration | spell it indirectly so the grep reads zero | | The export footer's dot shows only after a downloads link | leave the dot unconditional, as before | +#### Review (verdict SHIP AFTER FIXES; `$T/t-review.md`) + +| Finding | Fix | +|---|---| +| H1: the stacked-area surface gap erased small values and cut peaks (every site's multi-series area charts, `/stats`) | `92dc4084`: stacked areas keep `main`'s series-coloured edge (ChartView 1 px, CrossSiteChart 1.5 px); `BAR_GAP` stays on stacked bars. `charts.spec` reads the chart back from a screenshot (`common/testing/chartPixels.ts`): at 390 and 1280 px the stack's topmost painted row at each month is within 1 px of the value scale's y for the true total, and each series shows pixels of its own fill; with the old gap the tops land 2–4 px low | +| M1: the growth chart's thin-band rule was vertical, so on steep edges the gap covered whole bands | `f455d7da`: `homepage/app/lib/growthGaps.ts` (pure): thickness at right angles to the edge (min vertical height × cos θ) at the narrowest plot of each height (240, 592, 976 px); a gap only where both bands keep ≥ 1 px after every gap on them. `growthGaps.test.ts` checks every drawn segment independently: 0 covered band-segments on the fixture, a slivers-and-spikes stand-in and a one-month spike (two of the four fail under the old rule). `growth-chart.spec`: only the drawn height's set shows; the peak's painted top is within 1 px of the true total; every site with data shows its colour | +| M2: four texts still promised the Hub link | `706f62bf`: Settings' Family hub URL and a site's Hub URL hints, `homepageUrl`'s and `hubUrl`'s docs: the value is published as `hubUrl` in the site's `/site.json` and `/corpus.json` so the hub can tell member sites; no page links to it. `SETTINGS.md`, `SITE.md` regenerated; labels and field names unchanged | +| L1: from 1024 to 1046 px a very long title dropped its text and scrolled the icons at once | `2a5a81c0`: `lg:shrink-[999]` on the brand link; `header.spec` sweeps 1024–1050 px under touch with a 27-character title (scrolls without the fix) | +| L2: a title could show in the fallback face and vanish when Archivo loaded | `2a5a81c0`: the wordmark's width is reserved from Archivo's metrics at its two instances (`common/lib/wordmarkMetrics.ts`, generated by `common/bin/gen-wordmark-metrics.py` from the vendored font; `lib/wordmarkWidth.ts`, +3 %: the served font renders 0.2–1.7 % wider than the file's advances). `header.spec` blocks the font file and compares the title's visibility at 360, 375, 390, 412 and 430 px: the same before and after Archivo loads (the short title differs without the reservation) | +| L3: the provider and the script disagree when storage reads but refuses writes | left: reachable only with a stored retired value that can no longer be rewritten | +| L4: `common/components/ui/` was not untouched | nothing to change: `alert.tsx`, `badge.tsx` and `sonner.tsx` have comment-only edits naming the bases; the palette, `dialog.tsx` and `command.tsx` are untouched | +| L5: stale text | `689a835f`: `export/globals.css`, `export/manifest.ts`, `brand.test.ts`; the export changelog's T1 bullet no longer names a Base list; the homepage changelog's e2e bullet | +| L6: the footer's social row did not wrap | `2a5a81c0`: `flex-wrap` on the footer placement; eight links under touch at 320 px, export and homepage specs | +| L7: the guard's blind spots (`fs.promises.*`, `accessSync`/`open`/`readlink`, a relative literal to fs, a cwd default parameter, a class method as the source; false positives on a string naming `process.cwd()`) | left: none of these shapes is in the covered code today | +| L8: the rollout order was implied | this commit: "## Rollout" below | +| L9: 200 % text | `2a5a81c0`: the header itself never overflows at 200 % text (tested at 320 and 360 px, both pointers). The 43 px the review measured at 320 px are the search page's own controls (a label and the view toggle), not the header. At 320 px under touch the mark, the 88 px toggle and the 72 px menu button leave the social row's box no room: the phone header is the operator's to rule on | +| L10: named-accent coverage | `689a835f`: `theme-accent.spec` rewrites the fixture to a named accent (violet): its value on Light and on Dark, a stored pick not applied and left in place | + +**The growth chart's gaps after M1**, on the published summary: + +| Plot height | Gap segments | Runs | Covered band-segments | +|---|---|---|---| +| 200 px | 25 | 5 | 0 | +| 260 px | 64 | 11 | 0 | +| 300 px | 96 | 14 | 0 | + +**The gaps now read as dashes.** On a phone the chart has two short dashes, both along +Jeralyzer's top. At 1280 px they are stretches along Jeralyzer's and Anilyzer's tops, with +short pieces elsewhere. The rest of the bands touch. This is the underlying issue the operator +has been told about: four sliver bands beside two large ones. Folding the small sites into +"Other", or small multiples, is the operator's call and is not made here. +Screenshots: `~/reports/release-14/shots/chart2/growth-{390,1280}-{light,dark}{,-2019-2022}.png`. + +**Screenshots:** +- `chart2/site-stacked-{area,bar}-{390,1280}-{light,dark}.png`: a site's charts. The fixture has + one video a month, so its stacked bars have no touching segments. +- `chart2/stats-stacked-{area,bar}-{390,1280}-{light,dark}.png`: `/stats` on the published + summary. The area has its coloured edges back, and the bars show the 2 px gaps between + segments. + +**The nine decisions, as ruled:** +1. The nav and the Archilyzer link are inline from `lg` → keep, with L1. +2. The Archilyzer link is in the slide-out menu below `lg` → keep. +3. The link is small and muted, with the text "Archilyzer" → keep. +4. The pure-CSS wrap → keep, with L1 and L2. +5. The export footer takes the shared 36 px keys → keep, with L6. +6. The growth-chart gap is skipped under 3 px, and runs under three months are dropped → keep + the rule, measured at right angles (M1). +7. The surface gap over translucent stacked areas → do not keep (H1). +8. `RETIRED_BASE = "sepia"` stays → keep. +9. The footer's dot only after a downloads link → keep. + +| sha | what | +|---|---| +| `92dc4084` | H1 | +| `f455d7da` | M1 | +| `706f62bf` | M2 | +| `2a5a81c0` | L1, L2, L6, L9 | +| `689a835f` | L5, L10 | +| `447ded9a` | the narrow-header ruling (below) | +| _this_ | `plans:` the Review, the ruling's record, the Rollout, the plan, STATE | + +**Gates**, run once after the review fixes and the ruling (at `447ded9a`; logs `$T/t-g3-*.log`): + +- **tsc** was clean before every commit, and at the tip (37 s). +- **Unit:** + + | Suite | Result | + |---|---| + | common | 2,210/2,210 | + | editor unit | 87/87 | + | homepage unit | 12/12 | + | `test:scripts` | 191 passed, 1 skipped | + +- **Docs:** the three `--check` commands exit **0**. +- **Builds**, each capped at 5 GB with no swap, one at a time: + + | Build | Time | Max RSS | + |---|---|---| + | homepage | 16 s | 800 MB | + | export site | 25 s | 995 MB | + | export hub | 23 s | 1,021 MB | + | editor | 43 s | 1,650 MB | + +- **e2e**, each detached and queued: + + | Suite | Passed | Failed | Time | + |---|---|---|---| + | homepage, full | 90 | 0 | 2.1 min | + | export, full | 235 | 0 | 10.9 min | + | hub, full | 35 | 0 | 1.3 min | + | editor (`settings`, `sites-crud`, `theme`) | 32 | 0 | 1.3 min | + +- **An earlier gate run** at `689a835f` (the review fixes alone) was stopped when the ruling + arrived, to run the gates once for both. Before it stopped, it had passed: tsc; common + 2,208/2,208; editor unit 87/87; homepage unit 12/12; `test:scripts` 191 + 1 skipped; the docs + checks; all four builds; and the homepage e2e, 83/83. + +#### The narrow header, as ruled (2026-09-28) + +**The ruling** (operator): on narrow screens a site's header keeps the site's NAME and shows only +the link(s) marked for the header; the other social links are in the footer. + +**`featured`, new meaning** (`common/lib/socialLinks.ts` `headerSocialLinks(links, width)`): +- **Wide:** every link, up to four. With more configured, the `featured` ones are kept first, then + the last of the rest, shown in configured order. +- **Narrow:** only the `featured` ones, up to four (the last four when more are marked). With none + marked, the narrow header shows no social link: the name wins, and the footer has them all. +- **The footer** always shows every link. +- The key is still `featured`, stored only when true. The editor's checkbox is **Keep in header + on small screens**, with the hint "On small screens the header shows only these; the rest stay + in the footer." `SETTINGS.md` and `SITE.md` are regenerated. + +**Layout** (the homepage, every site and the hub, one behaviour): +- Both rows are rendered. CSS shows the narrow one below **520 px** and the wide one from 520 px; + the other is `display: none`, so exactly one is focusable and in the accessibility tree. +- Each copy scopes its icons' ids. +- The narrow bar is mark + name · the marked link(s) · toggle, then the menu button on the export. +- The wordmark's text drop (the export's reserved-width wrap, the homepage's container + thresholds, now keyed by the narrow row's count) and the scroll box stay as last resorts. + +**Why 520 px:** every link (up to four, 44 px touch keys), the toggle, the menu button and the +longest real title (Rekietalyzer, Hasanalyzer) fit the export's bar from about 500 px wide under +touch. At 519 px the narrow row shows and at 520 px the wide one, and the name shows at both +(measured on Rekietalyzer and Hasanalyzer, both pointers, 519–1280 px). The homepage's bar needs +about 424 px. + +**The smallest viewport width at which the full name shows** (a dev server, four links +configured): + +| Title | One marked link: mouse / touch | None marked: mouse / touch | +|---|---|---| +| Jeralyzer | 310 / 326 | 274 / 282 | +| Anilyzer | 296 / 312 | 260 / 268 | +| Bonnellyzer | 344 / **360** | 308 / 316 | +| Hasanalyzer | 351 / **367** | 315 / 323 | +| Rekietalyzer | 352 / **368** | 316 / 324 | +| Jasolyzer | 316 / 332 | 280 / 288 | +| Archilyzer (homepage) | 276 / 292 | 240 / 248 | +| Archilyzer (hub) | 321 / 337 | 285 / 293 | + +**The target** (every title in full at 360 px, one marked link, touch) is met by all but two: +- **Hasanalyzer misses by 7 px.** +- **Rekietalyzer misses by 8 px.** + +Not changed; the fixes, for the operator to pick: +1. **The bar's gap from 12 to 8 px below 520 px.** There are two gaps (name → row, toggle → + menu), so it saves 8 px and both titles meet 360. Type is untouched. +2. **The mark–name gap from 10 to 6 px** saves 4 px. Alone it is not enough. +3. **The wordmark from 1.35 to 1.25 rem below 520 px** saves 10–11 px on these titles. This + shrinks the type, so it is listed, not taken. +4. **The reserved width's margin from 3 % to 1 %** saves 3 px. This is risky: the served font + renders up to 1.7 % wider. + +**Tests:** +- `socialLinks.test.ts` covers both widths: none marked, one, several, more than four with and + without marks, and order kept. +- The homepage (`social.spec`), a site with a short title and "Bonnellyzer" (`header.spec`), and + the hub (`official-instances.spec`) are checked at 360 and 390 px, both pointers: + - the full name shows; + - only the marked link is in the header, in the accessibility tree too; + - with none marked, the header has no row and the name shows; + - from 520 px every link shows; + - the footer has every link at every width; + - nothing scrolls the page sideways from 280 to 1400 px; + - focus goes brand → marked link → toggle, then menu on a site. +- The e2e fixtures mark their last link. + +**Screenshots:** `~/reports/release-14/shots/h2/{jeralyzer,anilyzer,bonnellyzer,hasanalyzer,rekietalyzer,jasolyzer,homepage}-{320,360,390,768,1280}-{light,dark}.png`. +These use four synthetic icons, the last marked, with a mouse pointer. At 360 px under touch, +Hasanalyzer and Rekietalyzer drop their text (see the table). + ## Rollout + +Release 14 is slice HP (merged, `bfa1ff3c`) and `r14/two-grounds-headers` (after the parent's +merge). Every command below is typed **from the primary checkout's root**. There is no +`archilyzer` on PATH, so it is `pnpm archilyzer …`. The command forms are the ones verified in +`plans/stats-cache-key.md`'s rollout. + +**Preconditions.** +1. `main` carries `r14/two-grounds-headers`. +2. **The homepage build runs release 12's source publish.** So the homepage waits on release 12's + rollout step 0: the denylist is complete and `pnpm archilyzer source publish --check` is clean. +3. **No other index, stats or site build is running.** `/jobs` shows none running or queued. +4. **If the stats-cache-key rollout has not run yet, run it first.** Its steps 5–7 build the + homepage, the hub and the sites in the same order as below, and then cover this release's too. + +**The order, and why.** The homepage goes first, because every site's header links to +`https://archilyzer.pages.dev/#instances` and that anchor exists only in the new homepage. The +hub goes before the sites, because in basic mode they share `export/out`. The six sites go last. + +1. **The editor**, for its own header (no theme menu; the toggle cycles System, Light and Dark): + `pnpm --filter editor build`, then restart :3001 the way it is normally run. +2. **The homepage.** Use exactly ONE of: + - `pnpm archilyzer build homepage && pnpm archilyzer deploy homepage`; + - `pnpm ops build-homepage --json '{"deploy":true}' --wait`. +3. **The hub, before the sites.** Build it, check its log, and only then deploy it. Use exactly + ONE pair: + - `pnpm archilyzer build hub`, then `pnpm archilyzer deploy hub`; + - `pnpm ops build-hub --wait`, then `pnpm ops deploy-hub --wait`. + + **Between the two,** the build's `compose-hub: …` line must end with `hub-summary.json covers + N official instance(s)`, where N is the number of public sites. If it says `hub-summary.json + skipped: …`, stop and fix what it names. +4. **The six sites:** `pnpm ops build-deploy --json '{"all":true}' --wait`. + +**Live checks.** +- `https://archilyzer.pages.dev/#instances` lands on Official Instances, below the header. +- One site at 390 px wide: + - the header shows the mark, the social icons and the theme toggle as one group, then the menu + button; + - the menu holds the nav and "Archilyzer". +- At 1280 px wide, the header's **Archilyzer** link goes to the homepage's Official Instances, in + the same tab. There is no Sites menu and no Hub link. +- **Changelog** is in the footer, after Use with AI, and not in the header. +- **A stored Sepia renders Light.** In a site's console, run + `localStorage.setItem("ytdlp-tb:base","sepia")` and reload. `<html data-base>` reads `light`, + and `localStorage.getItem("ytdlp-tb:base")` now reads `"light"`. +- The homepage's growth chart has no slash in the page colour through any band. `/stats` in + Area mode has coloured top lines.