Archilyzer · Source

archilyzer

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

commit 3dbc29034b3ae782ff432963925e35e8bfde1003
parent 3d273ea14b4d564542222c05c3d327548dfef577
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Mon, 27 Apr 2026 23:23:53 -0400

feat(editor): use site/header title from settings.json for editor layout

Layout now reads siteTitle for the document title and headerTitle (with
siteTitle fallback) for the sidebar brand, with a small "editor" sub-label
to keep editor vs export distinguishable. Drops the in-memory settings cache
so file-level edits (cypress fixtures, manual writes) are picked up on the
next request without a server restart.

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

Diffstat:
Mcommon/lib/settings.ts | 7+------
Meditor/app/layout.tsx | 28+++++++++++++++++++++-------
Aeditor/cypress/e2e/branding.cy.ts | 44++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 66 insertions(+), 13 deletions(-)

diff --git a/common/lib/settings.ts b/common/lib/settings.ts @@ -21,10 +21,7 @@ const DEFAULTS: SiteSettings = { maxTranscriptPageBytes: TRANSCRIPT_PAGE_DEFAULT_BYTES, }; -let cached: SiteSettings | null = null; - export function getSettings(): SiteSettings { - if (cached) return cached; const file = getPaths().settingsFile; let parsed: Partial<SiteSettings> = {}; try { @@ -34,8 +31,7 @@ export function getSettings(): SiteSettings { } const merged = { ...DEFAULTS, ...parsed }; merged.maxTranscriptPageBytes = clampPageBytes(merged.maxTranscriptPageBytes); - cached = merged; - return cached; + return merged; } function clampPageBytes(value: unknown): number { @@ -57,5 +53,4 @@ export async function writeSettings(next: SiteSettings): Promise<void> { const tmp = `${file}.tmp-${process.pid}`; await fs.promises.writeFile(tmp, JSON.stringify(merged, null, 2) + "\n"); await fs.promises.rename(tmp, file); - cached = null; } diff --git a/editor/app/layout.tsx b/editor/app/layout.tsx @@ -1,11 +1,16 @@ import type { Metadata } from "next"; import Link from "next/link"; +import { getSettings } from "yt-dlp-transcript-common/lib/settings"; import "./globals.css"; -export const metadata: Metadata = { - title: "Transcript Editor", - description: "Editor for yt-dlp-transcript-browser", -}; +export async function generateMetadata(): Promise<Metadata> { + const settings = getSettings(); + return { + title: settings.siteTitle, + description: + settings.siteDescription || "Editor for yt-dlp-transcript-browser", + }; +} const NAV_LINKS = [ { href: "/", label: "Dashboard" }, @@ -15,19 +20,28 @@ const NAV_LINKS = [ { href: "/settings", label: "Settings" }, ]; -export default function RootLayout({ +export default async function RootLayout({ children, }: Readonly<{ children: React.ReactNode; }>) { + const settings = getSettings(); + const headerLabel = settings.headerTitle || settings.siteTitle; return ( <html lang="en" className="h-full antialiased"> <body className="min-h-full flex bg-zinc-50 text-zinc-900 dark:bg-zinc-950 dark:text-zinc-100"> <aside className="w-56 shrink-0 border-r border-zinc-200 dark:border-zinc-800 bg-white dark:bg-zinc-900 flex flex-col"> <div className="px-4 py-4 border-b border-zinc-200 dark:border-zinc-800"> - <Link href="/" className="font-semibold tracking-tight"> - Editor + <Link + href="/" + data-testid="sidebar-brand" + className="font-semibold tracking-tight block leading-tight" + > + {headerLabel} </Link> + <div className="text-xs uppercase tracking-wide text-zinc-500 mt-0.5"> + editor + </div> </div> <nav className="flex flex-col p-2 gap-1 text-sm"> {NAV_LINKS.map((link) => ( diff --git a/editor/cypress/e2e/branding.cy.ts b/editor/cypress/e2e/branding.cy.ts @@ -0,0 +1,44 @@ +describe("Editor branding from settings.json", () => { + it("renders siteTitle in document title and headerTitle in sidebar", () => { + cy.resetData("empty"); + cy.writeSettings({ + siteTitle: "Cypress Browser", + siteDescription: "x", + headerTitle: "Cypress HQ", + homeTagline: "", + maxTranscriptPageBytes: 8388608, + }); + // Re-invalidate the in-memory settings cache after the file was rewritten. + cy.request({ url: "/api/test/invalidate-cache", failOnStatusCode: false }); + + cy.visit("/"); + cy.title().should("include", "Cypress Browser"); + cy.findByTestId("sidebar-brand").should("contain.text", "Cypress HQ"); + }); + + it("falls back to siteTitle when headerTitle is empty", () => { + cy.resetData("empty"); + cy.writeSettings({ + siteTitle: "Just A Title", + siteDescription: "", + headerTitle: "", + homeTagline: "", + maxTranscriptPageBytes: 8388608, + }); + cy.request({ url: "/api/test/invalidate-cache", failOnStatusCode: false }); + + cy.visit("/"); + cy.findByTestId("sidebar-brand").should("contain.text", "Just A Title"); + }); + + it("reflects sidebar header after saving via the settings form", () => { + cy.resetData("empty"); + cy.visit("/settings"); + cy.findByLabelText(/header title/i).clear().type("Saved Header"); + cy.findByRole("button", { name: /save settings/i }).click(); + cy.findByTestId("settings-saved", { timeout: 10_000 }); + + cy.visit("/"); + cy.findByTestId("sidebar-brand").should("contain.text", "Saved Header"); + }); +});