Archilyzer · Source

archilyzer

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

commit d260e6c1036b1e734e3f1a16aa44441565eef05d
parent 5f287409597064ca7d4f74f838f5eaafb2fc3255
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Mon, 28 Sep 2026 00:29:16 -0400

Merge r10/mark-ring — release 10 slice MR: on the dark base the Found-line mark's tile gets a 1 px ring in its own dim (site #6b5d47, Archilyzer #586977), outside the tile, following its corners; light and sepia unchanged

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

Diffstat:
Acommon/components/BrandMark.test.ts | 57+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcommon/components/BrandMark.tsx | 25++++++++++++++++++++++---
Meditor/CHANGELOG.md | 3+++
Meditor/e2e/branding.spec.ts | 31+++++++++++++++++++++++++++++++
Mexport/CHANGELOG.md | 3+++
Mexport/e2e-hub/brand.spec.ts | 53++++++++++++++++++++++++++++++++++++++++++++++++++++-
Mexport/e2e/brand.spec.ts | 59++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Mhomepage/CHANGELOG.md | 1+
Mhomepage/e2e/brand.spec.ts | 53++++++++++++++++++++++++++++++++++++++++++++++++++++-
Mplans/brand-and-themes.md | 7+++++++
Mplans/release-10.md | 74++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
11 files changed, 360 insertions(+), 6 deletions(-)

diff --git a/common/components/BrandMark.test.ts b/common/components/BrandMark.test.ts @@ -0,0 +1,57 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; +import * as React from "react"; +import { renderToStaticMarkup } from "react-dom/server"; +import { BRAND_MARK_RING_CLASS, BrandMark } from "./BrandMark"; +import { ICON_PALETTES, MARK_GROUND_RX, MARK_VIEWBOX, childIconPalette, markSvg } from "../lib/brand"; + +// The ring on dark (release 10, slice MR): a 1px box-shadow ring outside the +// tile in the palette's own dim, drawn only under `.dark`, following the +// ground's corner. The browser half — the computed box-shadow per base — is +// held by the export, hub and homepage brand.spec e2e. + +// common's tsconfig has `jsx: "preserve"` (Next compiles it), so tsx falls +// back to the classic transform — `React.createElement` on a free `React`. +(globalThis as { React?: typeof React }).React = React; +const { createElement } = React; +const render = (el: Parameters<typeof renderToStaticMarkup>[0]) => renderToStaticMarkup(el); + +test("the ring's corner is the ground's: MARK_GROUND_RX of MARK_VIEWBOX", () => { + const pct = (MARK_GROUND_RX / MARK_VIEWBOX) * 100; // 21.875 + assert.equal(pct, 21.875); + assert.ok(BRAND_MARK_RING_CLASS.split(" ").includes(`rounded-[${pct}%]`)); + // The ring is the dark variant only, 1px, spread with no blur or offset, + // in the palette's ring colour; light and sepia get no shadow class at all. + const shadows = BRAND_MARK_RING_CLASS.split(" ").filter((c) => c.includes("shadow")); + assert.deepEqual(shadows, ["dark:shadow-[0_0_0_1px_var(--mark-ring)]"]); + // The svg must not clip the ground a second time at that corner. + assert.ok(BRAND_MARK_RING_CLASS.split(" ").includes("overflow-visible")); +}); + +test("each palette rings in its own dim, set as --mark-ring on the svg", () => { + const parent = render(createElement(BrandMark, { palette: ICON_PALETTES.archilyzer })); + assert.match(parent, /style="--mark-ring:#586977"/); + const child = render( + createElement(BrandMark, { + palette: { ...ICON_PALETTES.child, lit: "var(--brand-mark, var(--brand))" }, + }), + ); + assert.match(child, /style="--mark-ring:#6b5d47"/); +}); + +test("the caller's classes follow the ring's, and its style can still override the ring", () => { + const html = render( + createElement(BrandMark, { + palette: childIconPalette("#e3b15c"), + className: "size-7 shrink-0", + style: { "--mark-ring": "#123456" } as Record<string, string>, + }), + ); + assert.ok(html.includes(`class="${BRAND_MARK_RING_CLASS} size-7 shrink-0"`), html); + assert.match(html, /style="--mark-ring:#123456"/); +}); + +test("the ring is in-page only: the icon files carry no ring", () => { + const svg = markSvg(ICON_PALETTES.archilyzer); + assert.doesNotMatch(svg, /stroke|shadow|mark-ring/); +}); diff --git a/common/components/BrandMark.tsx b/common/components/BrandMark.tsx @@ -6,12 +6,31 @@ import { MARK, MARK_VIEWBOX, type MarkTone } from "../lib/brand"; // this is not lib/brand.ts's hex-only IconPalette. export type BrandMarkPalette = Readonly<Record<MarkTone, string>>; +// THE RING ON DARK. On the dark base the tile has no edge: a site's ink tile +// is the ink page itself (#0c0a08 on #0c0a08), and the parent's slate barely +// clears it. So on dark — `.dark` on <html>, tokens.css's `@custom-variant +// dark` — the tile gets a 1px ring OUTSIDE it, in the palette's own dim +// (`--mark-ring`, set from `palette.dim` below), so the ring reads as part of +// the mark. Light and sepia are untouched. +// - A box-shadow, so it takes no layout: the mark's size and the header's +// alignment are the same on every base. +// - `rounded-[21.875%]` is the ground's corner (MARK_GROUND_RX / MARK_VIEWBOX, +// 112 of 512; BrandMark.test.ts holds the two equal), so the ring follows +// the tile's own curve. +// - `overflow-visible`: an <svg> clips its content by default, and with a +// border-radius that clip would be a second anti-aliased edge laid over +// the ground's own rounded corner. +// Complete literal class strings — Tailwind v4 only sees whole literals. +export const BRAND_MARK_RING_CLASS = + "rounded-[21.875%] overflow-visible dark:shadow-[0_0_0_1px_var(--mark-ring)]"; + // The Found-line mark, inline — drawn from the same MARK shape list as // markSvg (the icon files), so the two cannot drift. Decorative: the text // beside it names the link. Colours go through `style`, not a `fill="var()"` // attribute, because a presentation attribute does not resolve custom // properties everywhere a style declaration does. Size it with `className` -// (e.g. `size-7`) or `size`. +// (e.g. `size-7`) or `size`. Every use draws the tile, so every use rings on +// dark; the icon files (markSvg) never do. export function BrandMark({ palette, size, @@ -30,8 +49,8 @@ export function BrandMark({ height={size} aria-hidden="true" focusable="false" - className={className} - style={style} + className={className ? `${BRAND_MARK_RING_CLASS} ${className}` : BRAND_MARK_RING_CLASS} + style={{ "--mark-ring": palette.dim, ...style } as CSSProperties} data-brand-mark="" > {MARK.map((s) => diff --git a/editor/CHANGELOG.md b/editor/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog +## [Unreleased] +- **On the Dark ground the sidebar's Archilyzer mark has a thin outline.** Its slate tile now has a 1-pixel ring just outside it, following its rounded corners, in the colour of the mark's unlit lines, so the tile's edge shows against the dark page. Light and Sepia are unchanged, and so is the favicon. + ## [0.9.3] - 2026-09-27 - **The lines inside the Archilyzer mark are easier to see.** The three unlit lines of the sidebar mark and the favicon now read at 3:1 against the slate tile instead of 2:1. The Archilyzer Media mark in umtool's report videos and the YouTube channel's picture, watermark and banner use the same lighter slate. diff --git a/editor/e2e/branding.spec.ts b/editor/e2e/branding.spec.ts @@ -64,3 +64,34 @@ test("the sidebar carries the parent mark beside adminTitle, and the favicon is expect(svg).toContain('fill="#151b20"'); expect(svg).toContain('fill="#e7edf1"'); }); + +// The ring on dark (release 10, slice MR): on the dark base the sidebar mark's +// slate tile gets a 1px ring outside it in the slate's dim (#586977), a +// box-shadow following the ground's corner; on light, none. +test("on dark the sidebar mark's tile has a 1px ring in its dim; on light none", async ({ + page, +}) => { + await page.goto("/"); + const mark = page.locator("aside svg[data-brand-mark]"); + const read = () => + mark.evaluate((svg) => { + const cs = getComputedStyle(svg); + const layers = cs.boxShadow === "none" ? [] : cs.boxShadow.split(/,(?![^(]*\))/); + return { + dark: document.documentElement.classList.contains("dark"), + ring: layers.map((l) => l.trim()).filter((l) => !l.startsWith("rgba(0, 0, 0, 0)")), + radius: cs.borderTopLeftRadius, + }; + }); + for (const base of ["dark", "light"] as const) { + await page.evaluate((b) => localStorage.setItem("ytdlp-tb:base", b), base); + await page.reload(); + await expect + .poll(read, { message: base }) + .toEqual({ + dark: base === "dark", + ring: base === "dark" ? ["rgb(88, 105, 119) 0px 0px 0px 1px"] : [], + radius: "21.875%", + }); + } +}); diff --git a/export/CHANGELOG.md b/export/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog +## [Unreleased] +- **On the Dark ground the mark's tile has a thin outline.** The header mark's ink tile is the colour of the Dark page, so only its lines showed. On Dark the tile now has a 1-pixel ring just outside it, following its rounded corners, in the colour of the mark's unlit lines. The small Archilyzer mark by the footer credit and the hub's mark get the same ring in their own slate's unlit colour. Light and Sepia are unchanged, and so are the icons. Needs a rebuild and deploy of every export site and the hub. + ## [0.9.3] - 2026-09-27 - **The lines inside the mark are easier to see.** The three unlit lines of the header mark and the site's icons were a dark brown barely lighter than their ink tile, so on the Dark ground the mark read as a single lit line. They are now a lighter warm grey that reads at 3:1 against the tile; the lit line is unchanged. The hub's mark, bone on slate, gets the same fix. Needs a rebuild and deploy of every export site and the hub. diff --git a/export/e2e-hub/brand.spec.ts b/export/e2e-hub/brand.spec.ts @@ -1,4 +1,4 @@ -import { expect, test, type Route } from "@playwright/test"; +import { expect, test, type Locator, type Page, type Route } from "@playwright/test"; import { BASE_GROUNDS, ICON_PALETTES, markSvg } from "../../common/lib/brand"; // The hub is the family's own tool, so it wears the PARENT mark — bone on @@ -48,3 +48,54 @@ test("the hub header is the parent mark + Archi|lyzer", async ({ page }) => { ); expect(fills).toEqual(["rgb(21, 27, 32)", "rgb(88, 105, 119)", "rgb(231, 237, 241)"]); }); + +// The ring on dark (release 10, slice MR): on the dark base the tile has no +// edge, so every mark's tile gets a 1px ring outside it in its palette's dim, +// following the ground's corner (rx 112 of 512). A box-shadow, so the mark's +// box is the same on every base; light and sepia draw none. +const BASE_KEY = "ytdlp-tb:base"; + +async function onBase(page: Page, base: "light" | "sepia" | "dark") { + await page.evaluate(([k, b]) => localStorage.setItem(k, b), [BASE_KEY, base]); + await page.reload({ waitUntil: "commit" }); + await page.waitForFunction(() => document.documentElement?.dataset.themeReady === "1"); + expect(await page.evaluate(() => document.documentElement.classList.contains("dark"))).toBe( + base === "dark", + ); +} + +// The mark's visible box-shadow layers (Tailwind's composite lists four +// transparent ones before the ring), its corner, and its laid-out size. +async function markRing(mark: Locator) { + return mark.evaluate((svg) => { + const cs = getComputedStyle(svg); + const r = svg.getBoundingClientRect(); + const layers = cs.boxShadow === "none" ? [] : cs.boxShadow.split(/,(?![^(]*\))/); + return { + ring: layers.map((l) => l.trim()).filter((l) => !l.startsWith("rgba(0, 0, 0, 0)")), + radius: cs.borderTopLeftRadius, + size: [r.width, r.height], + }; + }); +} + +test("on dark the hub's header and footer marks have a 1px ring in the slate's dim; on light and sepia none", async ({ + page, +}) => { + await page.route("**/hub-sites.json", (r) => fulfillJson(r, [])); + await page.route("**/hub-summary.json", (r) => r.fulfill({ status: 404, body: "" })); + await page.goto("/"); + const header = page.getByRole("banner").locator("svg[data-brand-mark]"); + const footer = page.locator("footer svg[data-brand-mark]"); + + await onBase(page, "dark"); + const ring = ["rgb(88, 105, 119) 0px 0px 0px 1px"]; // ICON_PALETTES.archilyzer.dim, #586977 + expect(await markRing(header)).toEqual({ ring, radius: "21.875%", size: [28, 28] }); + expect(await markRing(footer)).toEqual({ ring, radius: "21.875%", size: [16, 16] }); + + for (const base of ["light", "sepia"] as const) { + await onBase(page, base); + expect(await markRing(header), base).toEqual({ ring: [], radius: "21.875%", size: [28, 28] }); + expect(await markRing(footer), base).toEqual({ ring: [], radius: "21.875%", size: [16, 16] }); + } +}); diff --git a/export/e2e/brand.spec.ts b/export/e2e/brand.spec.ts @@ -1,4 +1,4 @@ -import { test, expect, type Locator } from "@playwright/test"; +import { test, expect, type Locator, type Page } from "@playwright/test"; import { ICON_PALETTES, childIconPalette, markSvg } from "../../common/lib/brand"; import { resolveAccent } from "../../common/lib/accent"; import { installRoutes } from "./helpers"; @@ -72,6 +72,63 @@ test("the header mark is the ink tile, its lit line following the reader's accen expect(f.lit).not.toBe(f.dim); }); +// The ring on dark (release 10, slice MR): on the dark base a site's ink tile +// IS the page, so every mark's tile gets a 1px ring outside it in its own +// palette's dim, following the ground's corner (rx 112 of 512). A box-shadow, +// so the mark's box is the same on every base; light and sepia draw none. +const BASE_KEY = "ytdlp-tb:base"; + +async function onBase(page: Page, base: "light" | "sepia" | "dark") { + await page.evaluate(([k, b]) => localStorage.setItem(k, b), [BASE_KEY, base]); + await page.reload({ waitUntil: "commit" }); + await page.waitForFunction(() => document.documentElement?.dataset.themeReady === "1"); + expect(await page.evaluate(() => document.documentElement.classList.contains("dark"))).toBe( + base === "dark", + ); +} + +// The mark's visible box-shadow layers (Tailwind's composite lists four +// transparent ones before the ring), its corner, and its laid-out size. +async function markRing(mark: Locator) { + return mark.evaluate((svg) => { + const cs = getComputedStyle(svg); + const r = svg.getBoundingClientRect(); + const layers = cs.boxShadow === "none" ? [] : cs.boxShadow.split(/,(?![^(]*\))/); + return { + ring: layers.map((l) => l.trim()).filter((l) => !l.startsWith("rgba(0, 0, 0, 0)")), + radius: cs.borderTopLeftRadius, + size: [r.width, r.height], + }; + }); +} + +test("on dark the header and footer marks' tiles have a 1px ring in their dim; on light and sepia none", async ({ + page, +}) => { + await installRoutes(page); + await page.goto("/"); + const header = page.getByRole("banner").locator("svg[data-brand-mark]"); + const footer = page.locator("footer svg[data-brand-mark]"); + + await onBase(page, "dark"); + expect(await markRing(header)).toEqual({ + ring: ["rgb(107, 93, 71) 0px 0px 0px 1px"], // ICON_PALETTES.child.dim, #6b5d47 + radius: "21.875%", + size: [28, 28], + }); + expect(await markRing(footer)).toEqual({ + ring: ["rgb(88, 105, 119) 0px 0px 0px 1px"], // ICON_PALETTES.archilyzer.dim, #586977 + radius: "21.875%", + size: [16, 16], + }); + + for (const base of ["light", "sepia"] as const) { + await onBase(page, base); + expect(await markRing(header), base).toEqual({ ring: [], radius: "21.875%", size: [28, 28] }); + expect(await markRing(footer), base).toEqual({ ring: [], radius: "21.875%", size: [16, 16] }); + } +}); + test("the footer carries the parent mark before the credit, outside the link", async ({ page, }) => { diff --git a/homepage/CHANGELOG.md b/homepage/CHANGELOG.md @@ -2,6 +2,7 @@ ## [Unreleased] +- **On the Dark ground the header mark's tile has a thin outline.** Its slate tile now has a 1-pixel ring just outside it, following its rounded corners, in the colour of the mark's unlit lines, so the tile's edge shows against the dark page. Light and Sepia are unchanged, and so are the icons. - **The lines inside the Archilyzer mark are easier to see.** The header mark's and the icons' three unlit lines now read at 3:1 against the slate tile instead of 2:1. - **The homepage opens on the Dark ground in Signal, even with JavaScript off, and a reader can pick another ground or accent.** The theme menu has **Base** (System, Light, Sepia, Dark) and **Accent** (the seven named diff --git a/homepage/e2e/brand.spec.ts b/homepage/e2e/brand.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "@playwright/test"; +import { expect, test, type Locator, type Page } from "@playwright/test"; import { ICON_PALETTES, markSvg } from "../../common/lib/brand"; // The project site wears the PARENT mark (plans/brand-and-themes.md, slice @@ -36,3 +36,54 @@ test("the icon set is rendered from the parent mark", async ({ request }) => { const ico = await request.get("/favicon.ico"); expect([...(await ico.body()).subarray(0, 4)]).toEqual([0, 0, 1, 0]); }); + +// The ring on dark (release 10, slice MR): on the dark base the tile has no +// edge, so every mark's tile gets a 1px ring outside it in its palette's dim, +// following the ground's corner (rx 112 of 512). A box-shadow, so the mark's +// box is the same on every base; light and sepia draw none. +const BASE_KEY = "ytdlp-tb:base"; + +async function onBase(page: Page, base: "light" | "sepia" | "dark") { + await page.evaluate(([k, b]) => localStorage.setItem(k, b), [BASE_KEY, base]); + await page.reload({ waitUntil: "commit" }); + await page.waitForFunction(() => document.documentElement?.dataset.themeReady === "1"); + expect(await page.evaluate(() => document.documentElement.classList.contains("dark"))).toBe( + base === "dark", + ); +} + +// The mark's visible box-shadow layers (Tailwind's composite lists four +// transparent ones before the ring), its corner, and its laid-out size. +async function markRing(mark: Locator) { + return mark.evaluate((svg) => { + const cs = getComputedStyle(svg); + const r = svg.getBoundingClientRect(); + const layers = cs.boxShadow === "none" ? [] : cs.boxShadow.split(/,(?![^(]*\))/); + return { + ring: layers.map((l) => l.trim()).filter((l) => !l.startsWith("rgba(0, 0, 0, 0)")), + radius: cs.borderTopLeftRadius, + size: [r.width, r.height], + }; + }); +} + +test("on dark the header mark has a 1px ring in the slate's dim; on light and sepia none", async ({ + page, +}) => { + await page.goto("/"); + const mark = page + .getByRole("link", { name: "Archilyzer home", exact: true }) + .locator("svg[data-brand-mark]"); + + await onBase(page, "dark"); + expect(await markRing(mark)).toEqual({ + ring: ["rgb(88, 105, 119) 0px 0px 0px 1px"], // ICON_PALETTES.archilyzer.dim, #586977 + radius: "21.875%", + size: [28, 28], + }); + + for (const base of ["light", "sepia"] as const) { + await onBase(page, base); + expect(await markRing(mark), base).toEqual({ ring: [], radius: "21.875%", size: [28, 28] }); + } +}); diff --git a/plans/brand-and-themes.md b/plans/brand-and-themes.md @@ -62,6 +62,13 @@ saturation and sit just over 3:1 (3.09, 3.06), because the two rules multiply to the darkest lit values leave little room: Vermilion is 2.28:1 over the child dim, and Signal on the Media slate 2.05:1. +**On the dark base the in-page mark's tile has a 1px ring** (2026-09-28, release 10, slice MR; +`release-10.md`): the operator asked for "a thin outline to the tile on dark", because the child's +ink tile is the dark page itself and the slate is 1.14:1 on it. `BrandMark` draws it outside the +tile, following its corner (`border-radius` 21.875% = rx 112 of 512), in the palette's own dim +(`#6b5d47`, `#586977`: 3.09:1 and 3.48:1 on the ink page), as a box-shadow under `.dark` only, so no +layout moves and light and sepia are unchanged. The icon files carry no ring. + Icon variants: - `any`: `rx=112`. - `maskable`: full-bleed ground, the mark scaled 0.8 about (256,256). diff --git a/plans/release-10.md b/plans/release-10.md @@ -2076,6 +2076,80 @@ also had adjacent in the other order. No chart change is owed. rebuilt for the swap. The regenerated YouTube picture, watermark and banner are the operator's to upload in Studio (`INDEX.html` has the steps). +### Slice MR, as shipped — mark ring on dark (2026-09-28) + +The operator's ask (2026-09-28): **"Add a thin outline to the tile on dark."** Slice MC left it as +found: on the dark base a site's ink tile is the ink page itself (`#0c0a08` on `#0c0a08`) and the +parent's slate `#151b20` is 1.14:1 on it, so the mark read as four lines with no tile. Branch +`r10/mark-ring` off `main` `6db8fcda`, worktree `/home/user/Projects/r10-mark-ring`, one Opus +implementer. Scratch files `mr-*` in the job's `tmp/brand`. + +**What shipped.** `BrandMark` (`common/components/BrandMark.tsx`) draws a **1 CSS px ring outside +the tile, only when `.dark` is on `<html>`** (tokens.css's `@custom-variant dark`), in the palette's +own **dim**: `--mark-ring` is set from `palette.dim` in the svg's `style`, so a site mark rings in +`#6b5d47` and the parent mark in `#586977`, and the ring reads as part of the mark. The classes are +one literal, `BRAND_MARK_RING_CLASS`: `rounded-[21.875%] overflow-visible +dark:shadow-[0_0_0_1px_var(--mark-ring)]`. +- A **box-shadow**, so it takes no layout: the mark is 28 × 28 (16 × 16 in a footer) on every base, + and the header does not move. +- `rounded-[21.875%]` is the ground's `rx` (112 of 512; the unit test holds the two equal), so the + ring follows the tile's corner. **`overflow-visible` is needed**: an `<svg>` clips its content by + default, and with a radius that clip is a second anti-aliased edge over the ground's own corner + (at 2×, 70 px of the site header and 84 px of the homepage's differed from main by up to 7 levels; + without the clip, 2 px by ≤ 2). +- Light and sepia render **pixel-identical to main** (0 px differ, site Brass and Blue, hub and + homepage, on both). +- Every `BrandMark` use draws the tile, so all five get the ring: the export header, the footer + credit, the hub header (and its footer), the homepage header, the editor sidebar. The icon files + (`markSvg`: favicons, app icons, the OG image, the YouTube assets) are untouched. + +**The variant, and why.** Screenshotted at 2× on dark from this branch's builds, the site (Brass) and +parent marks (`mr-variants-header.png`, `mr-variants-zoom.png`): the full dim outside (chosen); the +same at 60 % (`#453c2e` on ink, **1.82:1**, and `#3a434b` 1.96:1): it reads as a smudge rather than +an edge at 1 px; and a ring 1 px inside the edge (an `outline` at offset −1px): it eats a pixel of the +tile, so the tile looks smaller on dark than on light and the ring crowds the lines. The full dim is +**3.09:1** on the ink page (child) and **3.48:1** (parent; 3.06 on its own slate), the same WCAG +1.4.11 floor the dims meet, and at 1 px against the lines' ~2.4 px it reads as the tile's edge +without competing with them; the lit line stays the found line. Crisp at DPR 1, 2 and 3 +(`mr-dpr.png`: the straight edges are one, two and three full device pixels). + +| sha | what | +|---|---| +| `c5fc5bb3` | `common:` the ring — `BRAND_MARK_RING_CLASS`, `--mark-ring` from `palette.dim`; `BrandMark.test.ts` (4 tests) | +| `eb680a9c` | `e2e:` the export, hub and homepage `brand.spec`s and the editor's `branding.spec` read the computed box-shadow on dark, light and sepia | +| `14fd9f62` | `changelogs:` a new `[Unreleased]` above `[0.9.3]` in export and editor; a bullet in the homepage's | +| _this_ | `plans:` this record; a dated note in `brand-and-themes.md`'s mark section | + +**Gates** (logs `mr-*.log`): +- **tsc** clean (64 s) on the code tree of `eb680a9c`; the later commits are Markdown only. +- **common 2,038/2,038** (2,034 + 4), editor unit **85/85**, `test:scripts` **174 + 1 skipped**, mcp + **269/269**, homepage unit **2/2**. +- **They bite:** with the shadow class removed, 1 of the 4 unit tests fails, the export `brand.spec` + 1 failed / 5 passed and the homepage's 1 failed / 2 passed (`ring: []` where the dim was expected). +- **Builds:** editor ok (43 s), export site ok (29 s), export hub ok (26 s; `out/ask` present), + homepage ok (14 s). Each built stylesheet carries + `.dark\:shadow-\[0_0_0_1px_var\(--mark-ring\)\]:where(.dark,.dark *)` and + `.rounded-\[21\.875\%\]`; the site's `index.html` sets `--mark-ring:#6b5d47` (header) and + `#586977` (footer), the hub's `#586977` twice, the homepage's once. The primary's `export/public` + byte-identical before and after the builds and the e2e. +- **e2e** (no queue wait, nothing failed, so no re-runs): export `brand` + `site-branding` + `theme` + + `theme-accent` **20 passed** (55 s); `e2e:hub` **26 passed** (63 s); homepage full **21 passed, 8 + skipped** (39 s; no summary on disk in a fresh worktree, see below), then with the primary's + `homepage/public` data copied in **29 passed** (49 s); editor `branding.spec` **4 passed** (31 s). +- **Visual** (`mr-compare.png`): the site header in Brass and Blue, the hub and the homepage, on dark + and light at 2×, before (main's `<svg>` re-created: no radius, the default clip, no shadow) and + after; the footer credit on dark (site and hub); the two marks on dark at 3×. + +**Found and left** +- **A fresh worktree's homepage e2e skips 8 specs** (`stats.spec` ×7, `instance-colours.spec`) unless + `homepage/public`'s gitignored data (`homepage-summary.json`, `channel-sites.json`, `stats/`, + `downloads/`) is copied from the primary: the specs skip on "no summary on disk", so the run is + green but short. Copied here (not linked, so nothing can write through). +- **The ring is a box-shadow, which forced-colours mode (Windows High Contrast) removes**; the mark + then looks as it does on `main`. Not asked, not tested. +- **The live sites carry none of this until rebuilt:** every export site and the hub, the homepage, + and the editor on its next restart. + ## Rollout Nothing is rolled out, except that **Jeralyzer is already on the brand, in Signal** (a build-deploy