Archilyzer · Source

archilyzer

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

commit 8322925460d57f913c3e2f673ba78ba77db4ce6c
parent a6c632b024dbaf415a5c6753cf068a2834656ac0
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Mon, 28 Sep 2026 22:09:23 -0400

plans: r14/two-grounds-headers as shipped — the final review's Lows, the chart's gap, T1, H1/H2; the plan as built; STATE; FACTS' guard entry

release-14.md: the slice table; "Branch r14/two-grounds-headers" (rulings, the
commit table, the Lows with the capped build measurements, the chart's gap with
the thin-band numbers and the departures left for the operator), "Slice T1, as
shipped" (what was deleted, the migration, the counts), "Slice H1/H2, as
shipped" (the header, the lg breakpoint, the narrow-width approach and its
measured widths per title), the gates, found and left, and the decisions the
operator could overturn; a line under HP for ruling 10.
export-header-first-search.md: the rulings; H3 folded into H1; H1, H2 and T1
marked built with where they differ. export-responsive-redesign.md: the old
header contract marked superseded. FACTS: the guard's new name and scope.
homepage/CHANGELOG.md: the Firefox tab-stop bullet.

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

Diffstat:
Mhomepage/CHANGELOG.md | 1+
Mplans/FACTS.md | 26++++++++++++++------------
Mplans/STATE.md | 48++++++++++++++++++++++--------------------------
Mplans/export-header-first-search.md | 47++++++++++++++++++++++++++++++++++++-----------
Mplans/export-responsive-redesign.md | 4++++
Mplans/release-14.md | 318++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
6 files changed, 392 insertions(+), 52 deletions(-)

diff --git a/homepage/CHANGELOG.md b/homepage/CHANGELOG.md @@ -10,6 +10,7 @@ - **Each Official Instances card names its site with the site's wordmark.** The first part of the name is set heavy in the site's own accent and the rest light, as the site's own header sets it (Jer·alyzer, Hasan·alyzer, …), at the card title's size; the accent is fitted to the ground in force and reads above 4:1 on the card on Light and Dark. A site with no configured lead, or a summary built before this, shows its title plain as before. `homepage-summary.json` gains an optional `wordmarkLead` per site (still version 5). - **The growth chart's bands are separated by a 2 px gap in the ground's colour.** The gap runs along each band's upper edge where another band sits on it, the same 2 px at every width (it was a 1.25 px line in the page colour); where a band is too thin to give its share and keep a pixel of its own colour at the size the chart is drawn, the two bands touch instead, so no band disappears. In high-contrast mode the gap is the system's background colour. The gridlines are unchanged. The `/stats` charts' stacked areas and stacked bars are separated by the same 2 px gap in the chart panel's colour. - **Larger social links, with a focus ring.** Each icon, in the header and the footer, is a 36 px target around its 20 px glyph (44 px on a touch screen), in the muted text colour and the text colour on hover; the footer's were 20 px, in the faint colour, with no ring. Keyboard focus draws a 2 px ring in the accent, and in high-contrast mode the browser's own focus outline. An icon of two or more colours keeps its colours, and every icon paints inside its own box. A stored icon that fails the check a save runs is shown as its label (at most 10rem, with an ellipsis) instead. +- **Tab no longer stops on the header's scrolling boxes in Firefox.** When the social icons' box or the nav's rule under the header overflows (a screen under about 300 px, or a large text size), Firefox made it a tab stop with no name of its own; the icons and links inside are the stops now, and each scrolls into view as it takes focus. - **The e2e no longer reads the checkout's `settings.json` or `homepage.json`.** Its dev server reads `e2e/.e2e-settings.json` (`SETTINGS_FILE`), written by `e2e/fixture-social.ts`: three synthetic icons (a gradient with an outline, one colour, and a two-colour disc pasted with only its size), put through the same check a save runs; and `SITES_DIR` points at an empty directory. `e2e/social.spec.ts` covers the header and footer rows (at every width, with 1, 3 and 4 links, both pointers; the scroll fallback; hostile stored icons that must neither run nor fetch), `e2e/toggle.spec.ts` the theme toggle and the pinned accent, `e2e/svg-vectors.spec.ts` that every accepted icon stays inside its `<svg>` in a real parse, `e2e/growth-chart.spec.ts` the chart's lines, and `e2e/instance-wordmark.spec.ts` the cards' names; specs change the ground through one helper, `chooseTheme` (`e2e/helpers.ts`). - **A site's card counts every transcript, and never shows 0 channels while it serves recordings.** A transcript that arrived after its video was first indexed, or a video with YouTube captions alone, could be left out of the family's numbers: one site served 1,889 recordings and its card said 0 transcripts, 0 channels and 0 hours. Such transcripts are counted now — in the card, the family totals and the archive-growth chart — and one with no transcription date is left off only what is placed by that date: the charts by transcription date, "this month" and the recent list. The official-instance figures on the hub move with them. - **The source is on the site, with its history: `/source/`.** A new **Source** page (and nav entry) gives `git clone https://archilyzer.pages.dev/source/archilyzer.git`, a read-only mirror of the main branch regenerated with every deploy, with its head, the private commit it reflects, a link to browse every file raw at `/source/tree/`, and the tarball with its size and sha256. Commit ids differ from the private repository's, because machine paths are scrubbed on the way out, and the page says so. A build without a published source says "No source published in this build." instead of offering a clone. The Downloads tarball is now regenerated by every build (its commit is the mirror's), and the page points at the mirror for history. The docs that said there is no public repository (*Install*, the FAQ, *What is Archilyzer*) now say how to clone. Below `md` the header's nav drops to its own row, as it did below `sm`, because five labels no longer fit beside the wordmark. `_headers` serves the raw tree as plain text. diff --git a/plans/FACTS.md b/plans/FACTS.md @@ -7352,24 +7352,26 @@ Slices Q (`4855f70b`) and R (`ffdeb2cd`): [`release-12.md`](release-12.md), the a synthetic home inside the project, full of out-of-root symlinks under `reports/`, `.local/share/archilyzer/song` and `.cache/`, succeeded. So `~/reports` and the XDG song path are safe as `path.join(os.homedir(), …)`. -- **The guard is `scripts/umtool-build-trace.test.mjs`** (in `test:scripts`). It scans umtool's - app, components, lib, `report-to-video/*.mjs` and `song/paths.mjs`, per module. A path or fs call - carrying a value derived in that file from `process.cwd()`, `import.meta.url|dirname|filename` or - `__dirname` must open with the opt-out. +- **The guard is `scripts/next-build-trace.test.mjs`** (in `test:scripts`; it was + `umtool-build-trace.test.mjs` until release 14 widened it). It scans umtool's app, components, + lib, `report-to-video/*.mjs` and `song/paths.mjs`, and, since release 14 (F8), `homepage/app`, + `export/app`, `editor/app`, `editor/lib`, `editor/instrumentation.ts` and every `common/` module + but `bin/`, per module. A path or fs call carrying a value derived in that file from + `process.cwd()`, `import.meta.url|dirname|filename`, `__dirname`, or a call to a function + declared in the file whose body carries one, must open with the opt-out. - It is static and per module, as Turbopack's value analysis is: an imported binding is opaque to it. - With the slice Q `paths.mjs` it fails on the defect's line. - **The build gate** is run with the corpus visible and under a memory cap (the command is in `plans/tools/implementer-rules.md`). Linking `<primary>/transcripts` into a worktree is for a BUILD only. Remove the link afterwards: never run an app, an index or a fixture builder through it. -- **The other apps are safe by accident, not by rule:** - - `common/lib/paths.ts`' `findMonorepoRoot()` falls back to `process.cwd()` (the app's own - directory, which has no `transcripts/`). An editor build with the corpus present is 39 s today. - A fallback that evaluated to the repo root would make `path.join(monorepoRoot, "transcripts")` - this same bug. - - `homepage/app/lib/source.ts` joins `process.cwd()` + `public`, which holds the source mirror. - The homepage builds in about 20 s today. - - Neither has a guard. +- **The other apps were safe by accident; since release 14 (F8) they are by rule:** + - `common/lib/paths.ts` builds every path on the repo root through one opted-out `under()`, and + `findMonorepoRoot()`'s walk and its `process.cwd()` fallback are opted out. + - `homepage/app/lib/source.ts`' directory join on `public` (the source mirror) and the homepage's + and export's other cwd joins carry the opt-out. + - The guard covers them. A homepage build with the published source measured the same with and + without it (`release-14.md`, "The final review's Lows"). ## The stats cache key (verified 2026-09-28, branch `fix/stats-cache-key`) diff --git a/plans/STATE.md b/plans/STATE.md @@ -171,32 +171,28 @@ changed at integration. Nothing was deployed, cut, pushed or restarted; :3001 st 8. Optional: rebuild + restart umtool on :3050. Its app changed only in `lib/tools.mjs` (the shared tool probe) and two test-only variable names; O5's render changes already reach it from disk (the live :3050 spawns `report-to-video/*`), and an unbranded render is byte-identical. -- **Release 14 (2026-09-28): slice HP built, not merged** — the social icons in the export header, - one theme toggle, the Sites dropdown replaced by a link to the Archilyzer home, and a clear screen - until the first Search (`plans/export-header-first-search.md`; slices HP, H3, H1, H2, S1, T1 - planned, and S2 as a candidate). It waits on nothing: a link the operator adds is an entry in - `settings.json` `socialLinks`, and the earlier tip-link branch is parked, not merged. **HP** - (`homepage/social-visible`, record in `release-14.md`): - - puts the social row and one theme toggle (`ThemeToggle variant="bare"`) in the homepage's - header at every width, through the shared `common/components/SocialLinks.tsx`; no icon is - hidden by width. On a very small screen the wordmark's text drops first (a container query per - link count and pointer), and the row scrolls in `SocialScroll.tsx`, end first, only when even - the mark does not leave room; - - pins the homepage's accent (`pinAccent`). The Options dialog was built and then deleted by - ruling; `ThemeRadios` stays for the export's menu; - - moves Changelog to the footer and names each Official Instances card with the site's wordmark - (the lead tinted in its accent; `wordmarkLead` in the summary); - - draws the growth chart's separators in the ground's foreground; - - checks a social icon by an allowlist (`common/lib/socialSvg.ts`) on a new or edited icon and at - render. An unchanged stored icon never blocks a save, and `archilyzer doctor` names the failing - ones; - - gives a sized SVG with no viewBox its viewBox, and adds `featured` ("Show in header") to a - social link. - - Reviewed SHIP AFTER FIXES, then re-reviewed SHIP AFTER FIXES. Both sets of fixes are in, and - `main` (`10cefd15`) is merged in. The history was rewritten once, so no vendor file entered it. - The next review covers everything after `afc642fd`. **T1** ("two grounds", Sepia and the accent - picker removed) is planned and unassigned. Merge order: HP → H3 → H1+H2 → S1. +- **Release 14 (2026-09-28): HP merged (`bfa1ff3c`, final review SHIP); T1 and H1/H2 built on + `r14/two-grounds-headers`, not merged** — `plans/export-header-first-search.md`, record in + `release-14.md`. S1 (a clear screen until the first Search) is next, and S2 is a candidate. + - **HP** (merged): the homepage's header carries the social row and one theme toggle at every + width, through the shared `SocialLinks`; its wordmark's text drops first on a very small + screen; Changelog is in the footer; the cards wear the site's wordmark; a social icon is + checked by an allowlist on save and at render; `featured` on a social link. + - **`r14/two-grounds-headers`** (built, `0f358ee7` + the records): + - the final review's Lows (F1, F3, F4, F5, F8); + - F8 opts every cwd-derived path op in the three Next apps out of Turbopack's tracing, with + the guard widened to `scripts/next-build-trace.test.mjs`; + - the charts' foreground separators are withdrawn for a 2 px gap in the surface's colour, + the shared charts included; + - **T1:** two grounds, Light and Dark, in every app; a stored Sepia reads as Light before + paint and is rewritten; each site wears its own accent, and the accent picker is gone; + - **H1/H2:** every site's header and the hub's carry the social row and the toggle as one + 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. - **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 @@ -39,6 +39,16 @@ main...<branch>` is empty for the search and header components on all five). - 2026-09-28: the options menu is dropped for now in favour of a three-way toggle (slice HP on the homepage; slice H2 in the export). A later slice drops the Sepia base in every app and removes the accent picker from the export and editor headers (slice T1, below). +- 2026-09-28, on the charts: the chart follows standard practice for light and dark grounds; the + foreground-coloured separator lines are withdrawn (a 2 px gap in the surface's colour). +- 2026-09-28 (T1): Sepia is dropped in every app, the editor included; a reader who chose Sepia + gets Light; readers no longer pick an accent — each site shows its own, and stored accent choices + are ignored. +- 2026-09-28 (H1/H2): every site's header and the hub's carry the social row with the cycling + toggle as the last item of the same group, the homepage's; the Sites dropdown becomes a text + 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. - Standing: **no copy** beside any social link: icons with accessible names only. ## Decisions and assumptions @@ -63,17 +73,20 @@ ASSUMED by the planner (2026-09-28) — each is one line to reverse, and the ope ## Dependency graph ``` -HP (homepage social row, toggle, cards + schema, BUILT) ──► H1 ──► H2 ──► T1 -H3 (homepage anchor) ── independent; H1's link needs it DEPLOYED to land on the list +HP (homepage social row, toggle, cards + schema, BUILT, merged) + ──► T1 ──► H1 (with H3 folded in) ──► H2 BUILT on r14/two-grounds-headers S1 (clear screen) ── independent of H*; touches no header file S2 (defer summaries) ── after S1, only on the operator's word -T1 (two grounds) ── after HP merges, before the production deploy ``` -H1 then H2 are stacked on one branch (both rewrite `Header.tsx` and `MobileMenu.tsx`), branched -after HP merges: H1 adopts HP's `SocialLinks` component. S1 and H3 run in parallel with them on -their own branches. Merge order: HP → H3 → H1+H2 → S1, and T1 after HP and before the production -deploy. HP and H3 share only `homepage/CHANGELOG.md` (`[Unreleased]`). +As built (2026-09-28): T1, H1 and H2 are one branch, `r14/two-grounds-headers`, off `main` +`c6b8fc70`, in that order, after the final review's Lows and the chart's gap; H3 is folded into +H1 (the homepage's anchor ships with the link to it). The record is `release-14.md`, "Branch +`r14/two-grounds-headers`". + +As planned: H1 then H2 stacked on one branch (both rewrite `Header.tsx` and `MobileMenu.tsx`), +branched after HP merges, with H3 and S1 on their own branches, T1 after HP and before the +production deploy. As built: the one branch above; S1 is next, on its own. ## Verified facts the implementer must not re-derive @@ -206,7 +219,7 @@ shipped". What it built, so H1 does not re-derive it: (`.growth-sep`; `CanvasText` in forced colours), no longer the page background. Homepage-only: the shared charts have no such separator. -## Slice H3 — the homepage's instances anchor (branch `r14/home-anchor`) +## Slice H3 — the homepage's instances anchor (FOLDED INTO H1, built) Owns `homepage/app/page.tsx`, `homepage/e2e/marketing.spec.ts`, `common/lib/project.ts` (one constant), `homepage/CHANGELOG.md`. @@ -217,7 +230,13 @@ constant), `homepage/CHANGELOG.md`. link lands on the top of the page; that is accepted and said in the record. 4. Gates: tsc, homepage unit, homepage e2e, `archilyzer build homepage --no-source`. -## Slice H1 — the header carries the social row (branch `r14/header`, after HP merges) +## Slice H1 — the header carries the social row (BUILT on `r14/two-grounds-headers`) + +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. 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 @@ -258,7 +277,10 @@ Owns `export/app/components/{Header,MobileMenu,Footer,SiblingSwitcher}.tsx`, `related-sites.spec.ts`; hub e2e `official-instances.spec.ts`; screenshots of the header at 360 / 390 / 768 / 1280 px on Light, Sepia and Dark to `~/reports/release-14/shots/`. -## Slice H2 — one theme toggle (same branch, stacked on H1) +## Slice H2 — one theme toggle (BUILT on `r14/two-grounds-headers`) + +As built: T1 went first, so the slide-out menu lost its Base radios here with `ThemeRadios` +(deleted), rather than keeping them until T1. Owns `export/app/components/{Header,MobileMenu}.tsx`, `export/e2e/{theme,theme-accent,responsive, brand}.spec.ts`, `export/CHANGELOG.md`. The editor keeps its own header controls until T1. @@ -278,7 +300,10 @@ brand}.spec.ts`, `export/CHANGELOG.md`. The editor keeps its own header controls untouched. 4. Gates: as H1, plus the homepage e2e `toggle.spec.ts` and `theme.spec.ts`. -## Slice T1 — two grounds (planned; after HP merges, before the production deploy) +## Slice T1 — two grounds (BUILT on `r14/two-grounds-headers`, before H1) + +As built: the retired value is spelled once, `RETIRED_BASE` in `themeConfig.ts`; `ThemeMenu` and +the `pinAccent` option are deleted, and `ThemeRadios` kept the base alone until H2 deleted it. Owns `common/components/theme*` (`themeConfig.ts`, `ThemeProvider.tsx`, `ThemeScript.tsx`, `ThemeToggle.tsx`, `ThemeMenu.tsx`, `ThemeRadios.tsx`), `common/styles/tokens.css`, the three apps' diff --git a/plans/export-responsive-redesign.md b/plans/export-responsive-redesign.md @@ -258,6 +258,10 @@ in `globals.css:3` means `@utility`s defined there are usable from `common/`. - `export/app/components/Header.tsx:43`: `h-14 … flex-wrap` → `min-h-14 flex-nowrap`. Nav `:51-77` → `hidden md:flex` + add "Ask AI" → `/ask/`. Right cluster `:79-99`: SiblingSwitcher, Hub `<a>`, Changelog, ThemeMenu → `hidden md:…`; `ThemeToggle` stays visible at every width (theme.spec `/switch to/i`; theme-family.spec "Choose theme" is served by the inline ThemeMenu at 1440). - New client `export/app/components/MobileMenu.tsx`: props `{ links:{href,label}[]; sites: SwitcherGroup[] (type from SiblingSwitcher.tsx:18-19); hubUrl?: string }`. `<Sheet>` + `<SheetTrigger asChild><Button variant="ghost" size="icon-sm" aria-label="Open menu" className="md:hidden">`, `<SheetContent side="right">` with a `<SheetTitle>` (Radix warns without one); every `<Link>` wrapped in `<SheetClose asChild>` (Header lives in the root layout and never remounts on client nav). Theme lists via `useTheme()` (`ThemeProvider.tsx:35-38`) + `THEME_FAMILIES`/`THEME_MODES` from `themeConfig` as native radio groups — do not nest `ThemeMenu`'s DropdownMenu in the dialog. Offline link gated like `Footer.tsx:56` (`site.pwa`). - `export/app/components/Footer.tsx:29` → `flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between pb-safe`; `:82` drop `whitespace-nowrap`. Leave the anchor naming (`"Built with"` outside the `<a>`) and `nav aria-label="Related sites"` untouched. +- **Superseded, release 14 (H1/H2, `r14/two-grounds-headers`):** the header's right cluster is now + the social row and the theme toggle as one group at every width; the SiblingSwitcher, the Hub + link, Changelog and ThemeMenu are gone from it (Changelog is in the footer); the inline nav and + an Archilyzer link start at `lg`; MobileMenu holds only the nav and that link. ### Slice 4 — Results (M, 1 spec edit) - `common/components/SearchResults.tsx` card header `:470-538`: `flex items-stretch` → `flex flex-col sm:flex-row`; title `:504` `truncate` → `line-clamp-2 sm:line-clamp-none sm:truncate`; meta `:521-526` → `basis-full sm:basis-auto`; Ask `:528-537` `border-l` → `max-sm:border-t`. Preserve `data-result-slug`/`data-card-header` (`:460-461`), `data-card-open` (`:487`), checkbox aria-label (`:480`). diff --git a/plans/release-14.md b/plans/release-14.md @@ -19,10 +19,11 @@ 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` | -| H3, H1, H2, S1 | per the plan | per the plan | per the plan | +| 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` | +| S1 | per the plan | per the plan | per the plan | -**Order:** HP → H3 → H1+H2 → S1. The shared files are `editor/CHANGELOG.md`, -`homepage/CHANGELOG.md` (`[Unreleased]`) and this record. +**Order:** HP → `r14/two-grounds-headers` → S1. The shared files are the three changelogs' +`[Unreleased]` sections and this record. ## Record @@ -451,4 +452,315 @@ choosing, kept, and said beside the checkbox. The touch rule was kept, then supe - `export/CHANGELOG.md` `[Unreleased]` (created by `fix/stats-cache-key`): an icon that fails the check is shown as its label, bounded, and every icon paints inside its box. +### Branch `r14/two-grounds-headers` — the final review's Lows, the chart's gap, T1 and H1/H2 (2026-09-28) + +Branch `r14/two-grounds-headers` off `main` `c6b8fc70` (slice HP merged), worktree +`~/Projects/homepage-social-visible` (block #3: export dev 3300, export e2e 3320, hub e2e 3341, +homepage e2e 3340, editor test 3311), one Opus implementer. Scratch files `t-*` in the job's +`tmp`. The rulings, all 2026-09-28: +1. (The chart, operator.) The chart follows standard practice for light and dark grounds; the + foreground-coloured separator lines are withdrawn. +2. (T1, operator.) Sepia is dropped in every app, the editor included; a reader who chose Sepia + gets Light; readers no longer pick an accent — each site shows its own, and stored accent + choices are ignored. +3. (H1/H2, operator.) Every site's header and the hub's carry the social row with the cycling + toggle as the last item of the same group, the homepage's; the Sites dropdown is replaced by a + text link to the Archilyzer home's Official Instances (omitted on the hub); the header's Hub + link is removed; Changelog joins the footer after Use with AI; on narrow widths the wordmark's + 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. + +| sha | what | +|---|---| +| `d2b040fc` | `homepage:` the social row's scroll box and the compact nav are not tab stops of their own (F1); the toggle's no-flash test records every accent change (F5) | +| `ae5d535c` | `common:` the how-to-export hint only on a drawing program's leftovers (F3); stale text: `MobileMenu`'s comment, the older homepage changelog bullet, `featured`'s doc (F4) | +| `ec64011f` | `common, homepage, export, scripts:` every cwd-derived path op the three Next apps bundle opts out of Turbopack's tracing; the guard, renamed `scripts/next-build-trace.test.mjs`, covers them (F8) | +| `80228398` | `homepage, common:` chart bands are parted by a 2 px gap in the surface's colour; the foreground separators are withdrawn (ruling 1) | +| `3d0d1a46` | `common, export, editor, homepage:` two grounds, Light and Dark; each site wears its own accent (T1, ruling 2) | +| `46bd173c` | `homepage, common:` `id="instances"` on Official Instances and `INSTANCES_URL` (H1; the planned H3 folded in) | +| `04a1cfac` | `export:` the header's group, the Archilyzer link, no sites menu or hub link, Changelog in the footer, the narrow header; `header.spec.ts` (H1, H2) | +| `0f358ee7` | `export:` the footer's dot shows only after a downloads link; the social row keeps the shared row's inset | +| _this_ | `plans:` this record; the plan (H3 folded into H1, T1 and H1/H2 as built); STATE; FACTS' guard entry; the changelogs | + +#### The final review's Lows + +- **F1:** `SocialScroll`'s box has `tabIndex={-1}`, and so has the homepage's compact nav, which + overflows below 320 px. Firefox 146 (the installed build, by its path) made both a tab stop of + their own when they overflow. At 220 px with four links, without the fix Tab went home → the box + → the icons; with it, home → the icons → the toggle → the nav's links. The suite is Chromium + (Playwright's own Firefox revision is not installed), so it asserts the attribute. +- **F3:** the hint ("export it with presentation attributes rather than a style block (in + Inkscape, save as Plain SVG)") follows a `style`, `<metadata>` and an element or attribute in an + editor's namespace. An `<a>`, `<image>`, `<title>` with markup, `<foreignObject>` or `src` gets + the reason alone. There is a unit test both ways. +- **F4:** `MobileMenu`'s comment, the older homepage changelog bullet that let readers pick an + accent (reworded to the end state), and `featured`'s doc, which said "a narrow header shows + fewer" (`SETTINGS.md` and `SITE.md` regenerated). +- **F5:** the stored-accent test installs a MutationObserver from an init script and records every + value `data-accent` holds; a flash between two samples fails it. +- **F8:** + - `/* turbopackIgnore: true */` is on the homepage's `source.ts` directory join and on the + `docs.ts`, `snapshot.ts`, `summary.ts` and both changelog pages' cwd joins. + - In `common/lib/paths.ts`, every join on a path built from the repo root goes through one + opted-out `under()`, and `findMonorepoRoot()`'s walk and fallback are opted out. + `getPaths()` returns the same 58 values as before (diffed). + - The guard is `scripts/next-build-trace.test.mjs`, in `test:scripts`, 6 tests. + - It scans umtool as before, and adds `homepage/app`, `export/app`, `editor/app`, + `editor/lib`, `editor/instrumentation.ts` and every `common/` module but `bin/` (over 500 + modules). + - A function declared in the file whose body carries a source is a source. + - Brackets inside a regex literal no longer end a call. + - On `main`'s files it lists 57 findings. With `main`'s `source.ts` put back, it fails on + `source.ts:23`. + - **The homepage build, capped, with and without the published source** (`homepage/public/source`, + 2,893 files, 70 MB), a clean `.next` each time: + + | Build | Time | Max RSS | + |---|---|---| + | with the source | 14.55 s, 15.15 s | 807,332 KB, 801,948 KB | + | without it | 15.11 s | 797,744 KB | + | `main`'s join, with the source | 13.99 s | 837,144 KB | + + No measurable difference: the opt-out is by rule, not a measured fix today. +- **Left:** F2 (an animation of `style` or `class` is not held to the style allowlist; the key's + clip contains it). F6 (the form round-trip trims a stored SVG, so a hand-edited icon with + surrounding whitespace is re-checked on a form save). F7 (at 390 px the chart's thinnest upper + strata; see the gap below, which leaves a thin band its colour). + +#### The chart's surface gap (ruling 1) + +- **The growth chart** (`ArchiveGrowthChart.tsx`, `.growth-gap` in `homepage/app/globals.css`): + - a 2 px gap along each band's upper edge, in `var(--background)` (the chart sits on the page + ground, as the e2e checks); non-scaling, round joins; `Canvas` in forced colours; + - drawn centred after every fill, so each neighbour gives 1 px; + - 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; + - a run under three months is dropped. +- **Measured on the published summary:** + + | Plot height | Smallest band that gives a gap keeps | Smallest band with no gap | Month boundaries parted / touching | + |---|---|---|---| + | 200 px (a phone, 390 px) | 1.00 px | 0.10 px | 182 / 277 | + | 300 px (1280 px) | 1.00 px | 0.15 px | 302 / 157 | + + At 390 px the 2016–2020 bands are 0.1–5.5 px tall. +- **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; + - 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). +- **Screenshots:** `~/reports/release-14/shots/chart/gap2-{390,1280}-{light,sepia,dark}{,-thin}.png` + (taken before T1 removed Sepia). +- **Departures for the operator to rule on, not changed:** + - `ChartView` and `CrossSiteChart` fill stacked areas translucent (0.2 and 0.25); the standard + is opaque fills parted by the gap. + - `CrossSiteChart`, `LeaderboardChart` and `MomentumChart` draw dashed gridlines + (`strokeDasharray="3 3"`); the standard is solid hairlines. + - `ChartCard`'s surface is `--card`, equal to `--chart-surface` on both bases today. + +### Slice T1, as shipped — two grounds (2026-09-28) + +- **Two grounds.** + - `THEME_BASES` is System, Light, Dark. + - `nextBase` walks `THEME_BASES` (system → light → dark → system). + - `ResolvedBase` is light | dark; `isThemeBase` takes the three. +- **Deleted:** + - the Sepia block in `common/styles/tokens.css` and its `--base-sepia` flag; + - `onSepia` in every accent, `BASE_GROUNDS.sepia`, `ACCENT_INK.sepia`, the fitted + `--accent-custom-sepia`; + - the BookOpen icon; + - Sepia from every spec, fixture and comment; + - `ThemeMenu.tsx` (nothing renders it); + - `accentOptions` and `isThemeAccent`; + - the `pinAccent` option (it is the only behaviour now). +- **Kept:** + - `ACCENT_KEY`, documented as a pick nothing reads or deletes. + - `ThemeRadios`, base-only in this commit; H2 deleted it with its caller. +- **Migration, no flash:** + - A stored `ytdlp-tb:base` of the retired value (`RETIRED_BASE` in `themeConfig.ts`, the one + place its name is spelled) is `light` in the pre-paint script and in `ThemeProvider`, and is + rewritten to `light` once. + - The legacy `archive` + `light` pair maps to `light`. + - Any other unknown value falls back to the app's default, as before. + - Unit tests cover the whole matrix (the retired value, `archive` + `light`, garbage, a storage + that will not take the write). + - Each app has an e2e for a stored retired base: an init-script MutationObserver sees no ground + but the server's and Light, `data-theme-ready` is set, and storage reads `light`. +- **The accent is the site's:** + - `ThemeScript` and `ThemeProvider` never read `ytdlp-tb:accent` and never remove it. + - The export's layout and header, and the editor's header, have no accent picker; the + slide-out menu's accent list is gone. + - The site form's accent control is config and is unchanged; its hint no longer says a reader + can pick another. + - `theme-accent.spec.ts` keeps the site's own accent (fitted per base, no picker), a stored + accent ignored with no flash and left in place, and the hint in the site's accent on each + base. The four-bases menu, "pick Violet" and "pick the site's colour again" went with the + picker. +- **Docs:** `operate.md` (no theme menu; a light or dark ground with the header's toggle), + `siteSchema`'s accent text (`SITE.md` regenerated), the site form's hint. +- **Counts** (`git grep -i sepia -- . ':!plans/' ':!*CHANGELOG.md'`): 47 files, 177 lines at the + branch's start (with HP merged). After T1: one line, `RETIRED_BASE = "sepia"`, which the + migration needs in order to recognise a stored value. The changelogs' `[Unreleased]` sections + name it nowhere; released entries keep 9 lines. + +### Slice H1/H2, as shipped — the export and editor headers (2026-09-28) + +- **The export header** (every site and the hub): + - From `lg` (1024 px): brand · nav · **Archilyzer** · the group. + - Below `lg`: brand · the group · the menu trigger. + - The group is the homepage's: `SocialLinks` (header, at most four: `featured`, else the last + four) in `SocialScroll`, then `ThemeToggle variant="bare"`, 36 px keys (44 px under a coarse + pointer) touching. + - The footer's row is `SocialLinks` (footer) too: all the links, the same keys, the text colour + on hover where it was the accent. +- **The Archilyzer link** replaces `SiblingSwitcher` (deleted): + - visible text "Archilyzer", accessible name "Archilyzer — official instances"; + - `INSTANCES_URL`, same tab; + - not on the hub. + - The homepage's Official Instances section has `id="instances"`, with `scroll-mt` equal to the + sticky header's height. The planned H3 is folded in here, with a homepage e2e for + `/#instances`. +- **The header's Hub link** is gone from the bar and the menu; `hubUrl` and `resolveHubUrl` still + parse. **Changelog** is in the footer after Use with AI, and not in the bar or the menu. +- **The slide-out menu** holds only the nav, plus the Archilyzer link on a site. It is kept: + five-plus nav links do not fit a phone's bar. `ThemeRadios` is deleted. +- **The nav's breakpoint moved from `md` to `lg`:** + - 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: + - 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. + - So the text drops exactly when it does not fit, measured by the browser in the site's own + font at the reader's text size. There is no per-site threshold and no build-time estimate. + Titles differ in length, and a threshold written down would be wrong for some title or some + text size. + - The text stays in the DOM, so the link's name stays the title. + - Below `lg` the brand link takes the bar's free space (basis 0, grow 1, minimum the mark), so + the group gives way only once the link is the mark alone. The row's box then scrolls, end + first, as on the homepage. +- **Measured** on a dev server (synthetic titles, the viewport width): + + | Title | Links | Full wordmark from (mouse / touch) | Row scrolls below (mouse / touch) | + |---|---|---|---| + | "Shortlyzer" | 1 | 321 / 337 | never ≥ 240 | + | | 2 | 357 / 381 | never / 251 | + | | 3 | 393 / 425 | 263 / 295 | + | | 4 | 429 / 469 | 299 / 339 | + | "Longestfixturealyzer" | 1 | 449 / 465 | never ≥ 240 | + | | 2 | 485 / 509 | never / 251 | + | | 3 | 521 / 553 | 263 / 295 | + | | 4 | 557 / 597 | 299 / 339 | + + The page never scrolls sideways from 240 to 1023 px. The row scrolls at 320 px only with four + links under touch (by 19 px): the export bar also carries the menu trigger, which the + homepage's does not. +- **The editor's header:** + - `ThemeToggle` (its default variant, the editor's existing chrome) cycles System, Light and + Dark. + - `ThemeMenu` is gone. + - Nothing else is restyled. + - There is no social row. +- **Tests:** + - `header.spec.ts`, new: + - a short and a long title × 1–4 links × 280–430 px × mouse and touch, checking that the page + and the header never overflow, every link shows, the text drops before the row scrolls, and + the mark, the toggle and the link's name hold; + - a short title in full at 390 px, a long one giving way; + - key sizes (36 and 44 px) and touching boxes; + - the focus ring; + - the Archilyzer link's href, name and text; no sites button, hub link or "Choose theme"; + - Changelog in the footer after Use with AI and not in the header; + - six links and two featured; + - a hostile icon in `socialLinks` as its label, running nothing and requesting no other + origin. + - The fixture site is rewritten per test and restored. It carries its original in + `_e2ePristine`, which `playwright.config.ts` restores if a run dies. + - `responsive.spec` covers the menu's contents; `site-branding` scopes its footer link; + `official-instances` (hub) checks the hub's header has no Archilyzer link, sites menu or theme + menu. +- **Screenshots** (`~/reports/release-14/shots/h1/`, synthetic icons, six configured so the header + shows four): + - `{short,long,hub}-header-{320,360,390,768,1280}-{light,dark}.png`; + - `{short,long,hub}-footer-{…}.png`; + - `editor-header-{1280,390}-{light,dark}.png`. + +#### Gates (at `0f358ee7`; logs `$T/t-g-*.log`) + +- **tsc** was clean before every commit, and at the tip (49 s). +- **Unit:** + + | Suite | Result | + |---|---| + | common | 2,204/2,204 | + | editor unit | 87/87 | + | homepage unit | 8/8 | + | `test:scripts` | 191 passed, 1 skipped (192) | + | mcp | 271/271 | + +- **Docs:** `settings example --check`, `docs files --check` and `docs env --check` all exit **0**. +- **Builds**, each capped at 5 GB with no swap, one at a time, from a clean `.next` (time, max RSS): + + | Build | Time | Max RSS | + |---|---|---| + | homepage (fixture icons) | 17 s | 796 MB | + | export, site (the worktree's default) | 25 s | 982 MB | + | export, the fixture site | 29 s | 990 MB | + | export, hub | 26 s | 1,004 MB | + | editor, with the corpus visible | 44 s | 1,645 MB | + | umtool, with the corpus visible | 19 s | 784 MB | + + For the editor and umtool builds the primary's `transcripts/` was linked in, and the worktree's + own set aside. Both were put back after the builds; nothing ran through the link. +- **e2e**, each detached and queued: + + | Suite | Passed | Failed | Time | + |---|---|---|---| + | homepage, full | 77 | 0 | 1.8 min | + | export, full | 220 | 0 | 8.6 min | + | hub, full | 34 | 0 | 1.1 min | + | editor: `theme`, `sites-crud`, `settings`, `export-search` (the specs on the theme controls, the site form's accent hint, the social-links field, and the export's header and footer as the editor's export server shows them) | 51 | 0 | 1.7 min | + +- **Along the way:** + - the chart commit: tsc; homepage unit 8/8; a capped homepage build 16 s; homepage e2e 75/75 + (2.2 min); export `charts.spec` 9/9; + - T1: homepage theme/toggle/brand/instance specs 21, export theme/branding specs 30, hub 8, + editor 20; + - H1/H2: export header/responsive/branding specs 43 (one assertion order fixed), hub 9, + homepage `marketing` 11. +- **Numbers tool:** none. + +#### Found and left + +- **The export footer's `-mx` inset** keeps the shared row's value; on a wide screen the last + key's box reaches 8 px into the container's padding. +- **At 320 px with four links under touch** the export header's row scrolls by 19 px. This is the + ruled last resort; the menu trigger is what the homepage does not have. +- **`resolveHubUrl`** has no caller in the export app now. It still parses, as ruled. +- **Playwright's own Firefox and WebKit are not installed** at this Playwright's revisions; F1 was + checked by hand in the installed Firefox 146. +- **The chart departures** above (translucent stacked areas, dashed gridlines) are for the + operator. + +#### Decisions the operator could overturn + +| What I assumed | The alternative | +|---|---| +| The export's inline nav and the Archilyzer link start at `lg` (1024 px), with the menu below | keep `md` and let the wordmark hide and an icon scroll at 768–1023 px | +| The Archilyzer link is in the slide-out menu below `lg` | leave it out of the menu, so a phone reaches it only by the footer's "Built with Archilyzer" | +| The link is styled as the old Changelog link (small, muted), with the text "Archilyzer" | mono uppercase like the old Hub link, or the nav's style | +| The wordmark's text hides by a pure-CSS wrap, exact for any title | a per-site threshold computed at build time from the title's estimated width | +| The export footer's icons take the shared keys (36 px, hover to the text colour) | keep the footer's 20 px accent-hover icons | +| The chart's gap is skipped where either band is under 3 px at the drawn height, and runs under three months are dropped | a gap on every boundary, erasing the thinnest bands | +| Stacked areas in the shared charts get the surface gap over their translucent fills | leave their series-coloured top lines until the fill opacity is ruled on | +| `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 | + ## Rollout