// c51 (layout) — UI: page chrome + small response/format helpers shared // across every domain. Bigger per-domain body builders live next to this // file as `c51_render_.ts` (projects, reports). Layout exports // `escape`, `renderPage`, `renderNotFound`, `htmlResponse`, `errorPage`, // `phaseSpan`, `relativeTime`, plus the `Section` + `PageOptions` types. // Per the SAMA convention, lower layers don't import from this one. import { marked } from "marked"; import type { Phase } from "./a31_commits.ts"; const STYLE_CSS = "./public/style.css"; const css = await Bun.file(STYLE_CSS).text(); export type Section = "home" | "games" | "guides" | "blog" | "agents" | "leaderboard" | "sama" | "goals" | "contributing"; export interface PageOptions { title: string; // Provide either bodyMarkdown (parsed by marked) or bodyHtml // (passed through as-is). bodyHtml is what the docs layout uses // when it has already done its own marked.parse and wrapped the // result in sidebar/content/anchor-rail chrome. bodyMarkdown?: string; bodyHtml?: string; description?: string; ogPath?: string; active?: Section; noindex?: boolean; jsonLd?: Record; bodyClass?: string; // Skip the top nav bar (tdd.md · games · guides · sama · blog · agents // · leaderboard). Used by the /GIT views which have their own // breadcrumb chrome and don't need the site-wide nav competing for // space at the top of the page. hideNav?: boolean; } const SITE_DESCRIPTION = "SAMA — the architectural standard for AI-agent codebases. Sorted, Architecture, Modeled, Atomic. Four pillars, one CI verifier."; export const escape = (s: string): string => s.replace(/&/g, "&").replace(/"/g, """).replace(//g, ">"); const navLink = (href: string, label: string, active: boolean): string => { const cls = active ? ' class="nav-active"' : ""; return `${label}`; }; const nav = (active?: Section): string => ``; export const renderPage = async (opts: PageOptions): Promise => { const body = opts.bodyHtml ?? await marked.parse(opts.bodyMarkdown ?? "", { gfm: true, breaks: false }); const description = opts.description ?? SITE_DESCRIPTION; const bodyClassAttr = opts.bodyClass ? ` class="${escape(opts.bodyClass)}"` : ""; const ogPath = opts.ogPath ?? "https://tdd.md"; const robots = opts.noindex ? `\n` : ""; const jsonLd = opts.jsonLd ? `\n` : ""; return ` ${robots} ${escape(opts.title)} ${jsonLd} ${opts.hideNav ? "" : nav(opts.active)}
${body}
`; }; export const renderNotFound = async (path: string): Promise => renderPage({ title: "404 — tdd.md", bodyMarkdown: `# 404\n\n> No such path: \`${path}\`\n\nTry [home](/), [games](/games), [agents](/agents), or [leaderboard](/leaderboard).`, noindex: true, }); // --------------------------------------------------------------------- // Small response/formatting helpers used by c21 handlers + domain renders. // --------------------------------------------------------------------- export const htmlResponse = (html: string, status = 200): Response => new Response(html, { status, headers: { "Content-Type": "text/html; charset=utf-8" } }); export const errorPage = async (message: string, status = 400): Promise => { const html = await renderPage({ title: "error — tdd.md", bodyMarkdown: `# error\n\n> ${message}\n\n[← back](/agents/register)`, active: "agents", }); return htmlResponse(html, status); }; export const phaseSpan = (p: Phase): string => { const cls = p === "red" ? "red" : p === "green" ? "green" : p === "refactor" ? "blue" : "muted"; return `${p}`; }; export const relativeTime = (iso: string): string => { const ms = Date.now() - new Date(iso).getTime(); if (ms < 60_000) return `${Math.max(0, Math.floor(ms / 1000))}s ago`; if (ms < 3_600_000) return `${Math.floor(ms / 60_000)}m ago`; if (ms < 86_400_000) return `${Math.floor(ms / 3_600_000)}h ago`; return `${Math.floor(ms / 86_400_000)}d ago`; };