commit 7b603d8cf8dbb6d0a64c1e976f630d849e3967ed
parent 2907e08946920fb94b916d7d366a250c65208fc7
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date: Fri, 25 Sep 2026 21:30:59 -0400
editor: the parent mark beside adminTitle, and app/icon.svg as the favicon (brand S1)
The sidebar puts the mark before the title block; adminTitle is free text,
so it is not split, and the mark sits outside the title link. app/icon.svg is
markSvg(ICON_PALETTES.archilyzer), held to it byte for byte by
app/lib/brandIcon.test.ts; the editor had no favicon before. branding.spec
checks the mark, the link's name and the linked icon.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Diffstat:
4 files changed, 67 insertions(+), 9 deletions(-)
diff --git a/editor/app/icon.svg b/editor/app/icon.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><rect width="512" height="512" rx="112" fill="#151b20"/><rect x="112" y="128" width="288" height="44" rx="22" fill="#3f4c56"/><polygon points="112,210 172,234 112,258" fill="#e7edf1"/><rect x="188" y="212" width="212" height="44" rx="22" fill="#e7edf1"/><rect x="112" y="296" width="232" height="44" rx="22" fill="#3f4c56"/><rect x="112" y="380" width="152" height="44" rx="22" fill="#3f4c56"/></svg>
diff --git a/editor/app/layout.tsx b/editor/app/layout.tsx
@@ -12,6 +12,8 @@ import { ThemeScript } from "yt-dlp-transcript-common/components/ThemeScript";
import { ThemeProvider } from "yt-dlp-transcript-common/components/ThemeProvider";
import { ThemeToggle } from "yt-dlp-transcript-common/components/ThemeToggle";
import { ThemeMenu } from "yt-dlp-transcript-common/components/ThemeMenu";
+import { BrandMark } from "yt-dlp-transcript-common/components/BrandMark";
+import { ICON_PALETTES } from "yt-dlp-transcript-common/lib/brand";
import { AppFrame } from "./components/AppFrame";
import { AutoRefresh } from "./components/AutoRefresh";
import { CommandPalette } from "./components/CommandPalette";
@@ -131,15 +133,23 @@ export default async function RootLayout({
sidebar={
<aside className="md:w-56 md:shrink-0 md:sticky md:top-0 md:self-start md:h-screen md:overflow-y-auto border-b md:border-b-0 md:border-r border-border bg-card flex flex-col">
<div className="px-3 py-2.5 md:py-3 md:border-b md:border-border flex items-start justify-between gap-2">
- <div className="min-w-0">
- <Link
- href="/"
- className="font-display text-base font-semibold tracking-tight block leading-tight"
- >
- {headerLabel}
- </Link>
- <div className="font-mono text-[10px] uppercase tracking-[0.18em] text-muted-foreground mt-0.5">
- editor
+ <div className="min-w-0 flex items-center gap-2.5">
+ {/* The parent mark, beside the operator's title — which is
+ NOT split: adminTitle is free text, not a wordmark. */}
+ <BrandMark
+ palette={ICON_PALETTES.archilyzer}
+ className="size-8 shrink-0"
+ />
+ <div className="min-w-0">
+ <Link
+ href="/"
+ className="font-display text-base font-semibold tracking-tight block leading-tight"
+ >
+ {headerLabel}
+ </Link>
+ <div className="font-mono text-[10px] uppercase tracking-[0.18em] text-muted-foreground mt-0.5">
+ editor
+ </div>
</div>
</div>
<div className="flex items-center gap-1.5 shrink-0">
diff --git a/editor/app/lib/brandIcon.test.ts b/editor/app/lib/brandIcon.test.ts
@@ -0,0 +1,18 @@
+import { test } from "node:test";
+import assert from "node:assert/strict";
+import { readFileSync } from "node:fs";
+import { fileURLToPath } from "node:url";
+import { ICON_PALETTES, markSvg } from "yt-dlp-transcript-common/lib/brand";
+
+// editor/app/icon.svg is the editor's favicon (Next's app/icon.svg file
+// convention links it from every page). It is a static file because the
+// editor's icon never changes per install — it is the parent mark — but it
+// must stay the mark: markSvg is the geometry, this file a copy of it.
+// Regenerate with markSvg(ICON_PALETTES.archilyzer) if this fails.
+test("app/icon.svg is markSvg(ICON_PALETTES.archilyzer), byte for byte", () => {
+ const file = fileURLToPath(new URL("../icon.svg", import.meta.url));
+ assert.equal(
+ readFileSync(file, "utf8").trimEnd(),
+ markSvg(ICON_PALETTES.archilyzer),
+ );
+});
diff --git a/editor/e2e/branding.spec.ts b/editor/e2e/branding.spec.ts
@@ -35,3 +35,32 @@ test("reflects sidebar header after saving via the settings form", async ({
page.locator("aside").getByRole("link", { name: "Saved Admin" }),
).toBeVisible();
});
+
+test("the sidebar carries the parent mark beside adminTitle, and the favicon is the mark", async ({
+ page,
+ request,
+}) => {
+ await writeSettings({
+ adminTitle: "Cypress HQ",
+ maxTranscriptPageBytes: 8388608,
+ sleepBetweenDownloadsSeconds: 0,
+ });
+ await page.goto("/");
+ const aside = page.locator("aside");
+ // Decorative, and NOT inside the title link: its name stays adminTitle.
+ const mark = aside.locator("svg[data-brand-mark]");
+ await expect(mark).toHaveCount(1);
+ await expect(mark).toHaveAttribute("aria-hidden", "true");
+ await expect(mark).toBeVisible();
+ await expect(
+ aside.getByRole("link", { name: "Cypress HQ", exact: true }),
+ ).toBeVisible();
+ // app/icon.svg (Next's file convention) is linked from <head>.
+ const href = await page
+ .locator('head link[rel="icon"][type="image/svg+xml"]')
+ .getAttribute("href");
+ expect(href).toMatch(/^\/icon\.svg/);
+ const svg = await (await request.get(href as string)).text();
+ expect(svg).toContain('fill="#151b20"');
+ expect(svg).toContain('fill="#e7edf1"');
+});