style: add design scaffolding
commit
5aa890dstyle: add design scaffolding
Assisted-by: Claude-Design:claude-opus-4-8
Reviews
No reviews of this commit yet — record a verdict below.
Start a review
design_handoff_git_ents/Git Ents.dc.html
@@ -1,0 +1,895 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<script src="./support.js"></script>
+</head>
+<body>
+<x-dc>
+<helmet>
+<link rel="preconnect" href="https://fonts.googleapis.com">
+<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+<link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700&family=Lora:wght@500;600;700&family=IBM+Plex+Mono:wght@400;500;600&display=swap" rel="stylesheet">
+<style>
+:root{
+ --color-bg:#faf8f4; --color-surface:#ffffff; --color-text:#2a2518; --color-text-muted:#8a7e6a;
+ --color-link:#b07d10; --color-link-hover:#96690a; --color-border:#ede9de; --color-code-bg:#f5f3eb;
+ --color-accent:#b07d10; --color-accent-subtle:#b07d100f;
+ --shadow-sm:0 1px 3px #0000000d; --shadow-md:0 4px 16px #0000000f;
+ --radius-sm:10px; --radius-pill:100px;
+ --kw:#9d0006; --fn:#427b58; --ty:#b57614; --str:#79740e; --num:#8f3f71; --cmt:#9c8f74; --prop:#076678;
+ --diff-add:#4e9a0622; --diff-rem:#cc241d22;
+ --font-body:"DM Sans",system-ui,sans-serif; --font-display:"Lora",Georgia,serif; --font-mono:"IBM Plex Mono",ui-monospace,Menlo,monospace;
+}
+@media (prefers-color-scheme: dark){:root{
+ --color-bg:#171510; --color-surface:#211f17; --color-text:#ede8d8; --color-text-muted:#a89e88;
+ --color-link:#d4a030; --color-link-hover:#e4b850; --color-border:#383324; --color-code-bg:#211f17;
+ --color-accent:#d4a030; --color-accent-subtle:#d4a03012;
+ --shadow-sm:0 1px 3px #00000040; --shadow-md:0 4px 16px #0000004d;
+ --kw:#fb4934; --fn:#8ec07c; --ty:#fabd2f; --str:#b8bb26; --num:#d3869b; --cmt:#928374; --prop:#83a598;
+ --diff-add:#b8bb2620; --diff-rem:#fb493420;
+}}
+*{box-sizing:border-box}
+html,body{margin:0;padding:0}
+body{background:var(--color-bg);color:var(--color-text);font-family:var(--font-body);font-size:17px;line-height:1.7;-webkit-font-smoothing:antialiased;background-image:radial-gradient(58rem 30rem at 50% -10rem, var(--color-accent-subtle), transparent 72%);background-repeat:no-repeat;background-attachment:fixed}
+a{color:inherit;text-decoration:none}
+::selection{background:var(--color-accent-subtle)}
+input,textarea,select,button{font-family:inherit}
+@keyframes spin{to{transform:rotate(360deg)}}
+@keyframes fadein{from{opacity:0;transform:translateY(5px)}to{opacity:1;transform:none}}
+::-webkit-scrollbar{height:9px;width:9px}
+::-webkit-scrollbar-thumb{background:var(--color-border);border-radius:100px}
+::-webkit-scrollbar-track{background:transparent}
+</style>
+</helmet>
+
+<div style="min-height:100vh">
+
+ <!-- ============ STICKY NAV ============ -->
+ <header style="position:sticky;top:0;z-index:50;backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);background:color-mix(in srgb, var(--color-bg) 82%, transparent);border-bottom:1px solid var(--color-border)">
+ <div style="max-width:78rem;margin:0 auto;padding:0 1.5rem;height:58px;display:flex;align-items:center;gap:18px">
+ <a href="#" style="display:flex;align-items:center;gap:9px;font-family:var(--font-mono);font-weight:600;font-size:.95rem;letter-spacing:-.01em;white-space:nowrap;flex:none" style-hover="{ color:'var(--color-accent)' }">
+ <svg width="19" height="19" viewBox="0 0 16 16" fill="currentColor" style="color:var(--color-accent)"><path d="M8 1L2 4.2v4.3c0 3.4 2.5 5.6 6 6.5 3.5-.9 6-3.1 6-6.5V4.2L8 1zm0 1.5l4.5 2.4v3.6c0 2.5-1.8 4.2-4.5 5-2.7-.8-4.5-2.5-4.5-5V4.9L8 2.5z"/><circle cx="8" cy="7" r="1.6"/></svg>
+ git-ents
+ </a>
+ <div style="flex:1;max-width:24rem;position:relative;display:flex;align-items:center">
+ <svg width="14" height="14" viewBox="0 0 16 16" fill="var(--color-text-muted)" style="position:absolute;left:11px;pointer-events:none"><path d="M10.7 9.6a5 5 0 10-1.1 1.1l3 3 1.1-1.1-3-3zM2.5 6.5a3.5 3.5 0 117 0 3.5 3.5 0 01-7 0z"/></svg>
+ <input placeholder="Jump to file or symbol" style="width:100%;padding:7px 11px 7px 32px;background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-sm);font-size:.82rem;color:var(--color-text);outline:none" style-focus="{ borderColor:'var(--color-accent)' }"/>
+ </div>
+ <nav style="display:flex;align-items:center;gap:18px;font-size:.85rem;color:var(--color-text-muted)">
+ <a href="#" style-hover="{ color:'var(--color-accent)' }">Explore</a>
+ <a href="#" style-hover="{ color:'var(--color-accent)' }">Docs</a>
+ <div style="width:30px;height:30px;border-radius:100px;background:var(--color-accent-subtle);border:1px solid var(--color-border);display:flex;align-items:center;justify-content:center;font-family:var(--font-mono);font-size:.72rem;font-weight:600;color:var(--color-accent)">el</div>
+ </nav>
+ </div>
+ </header>
+
+ <!-- ============ REPO HEADER ============ -->
+ <div style="max-width:78rem;margin:0 auto;padding:0 1.5rem">
+ <div style="padding:26px 0 0">
+ <div style="display:flex;align-items:flex-start;justify-content:space-between;gap:20px;flex-wrap:wrap">
+ <div>
+ <div style="display:flex;align-items:center;gap:9px;font-family:var(--font-mono);font-size:1.18rem;letter-spacing:-.01em">
+ <svg width="18" height="18" viewBox="0 0 16 16" fill="var(--color-text-muted)"><path d="M2 3.5C2 2.7 2.7 2 3.5 2h3l1.5 1.5h4.5c.8 0 1.5.7 1.5 1.5v7c0 .8-.7 1.5-1.5 1.5h-9C2.7 13.5 2 12.8 2 12V3.5z"/></svg>
+ <a href="#" style="color:var(--color-text-muted)" style-hover="{ color:'var(--color-accent)' }">git-ents</a>
+ <span style="color:var(--color-border)">/</span>
+ <a href="#" style="font-weight:600;color:var(--color-accent)">git-ents</a>
+ <span style="display:inline-flex;align-items:center;gap:5px;margin-left:4px;padding:2px 10px;font-size:.7rem;font-weight:500;color:var(--color-accent);background:var(--color-accent-subtle);border:1px solid var(--color-accent);border-radius:var(--radius-pill)">
+ <svg width="11" height="11" viewBox="0 0 16 16" fill="currentColor"><path d="M11.5 3.5a2 2 0 10-2.7 1.9v1.3c0 .6-.5 1.1-1.1 1.1H5.6a2.5 2.5 0 00-1.1.3V5.4a2 2 0 10-1.5 0v5.2a2 2 0 101.6 0V9.5c0-.6.5-1 1-1h2.1A2.6 2.6 0 0010.3 6V5.4a2 2 0 001.2-1.9zM3.75 3.5a.75.75 0 110 1.5.75.75 0 010-1.5zm0 9a.75.75 0 110-1.5.75.75 0 010 1.5zm5.75-9a.75.75 0 110 1.5.75.75 0 010-1.5z"/></svg>
+ main
+ </span>
+ <span style="padding:2px 10px;font-size:.7rem;color:var(--color-text-muted);background:var(--color-code-bg);border:1px solid var(--color-border);border-radius:var(--radius-pill)">Public</span>
+ </div>
+ <p style="margin:11px 0 0;max-width:40rem;color:var(--color-text);font-size:.98rem">A warm, editorial Git forge — serve your repositories as paper-grain HTML. Readable, fast, and quiet, with no client-side JavaScript required.</p>
+ <div style="display:flex;flex-wrap:wrap;gap:7px;margin-top:13px">
+ <span style="padding:3px 11px;font-family:var(--font-mono);font-size:.72rem;color:var(--color-link);background:var(--color-accent-subtle);border-radius:var(--radius-pill)">rust</span>
+ <span style="padding:3px 11px;font-family:var(--font-mono);font-size:.72rem;color:var(--color-link);background:var(--color-accent-subtle);border-radius:var(--radius-pill)">git-forge</span>
+ <span style="padding:3px 11px;font-family:var(--font-mono);font-size:.72rem;color:var(--color-link);background:var(--color-accent-subtle);border-radius:var(--radius-pill)">self-hosted</span>
+ <span style="padding:3px 11px;font-family:var(--font-mono);font-size:.72rem;color:var(--color-link);background:var(--color-accent-subtle);border-radius:var(--radius-pill)">html-first</span>
+ </div>
+ </div>
+ <div style="display:flex;gap:9px">
+ <button style="display:flex;align-items:center;gap:7px;padding:7px 13px;font-size:.8rem;font-weight:500;color:var(--color-text);background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-sm);cursor:pointer;box-shadow:var(--shadow-sm)" style-hover="{ borderColor:'var(--color-accent)' }">
+ <svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor"><path d="M8 9.5a1.5 1.5 0 100-3 1.5 1.5 0 000 3z"/><path d="M8 2C4.5 2 1.7 4.4 1 7.9c.7 3.5 3.5 5.9 7 5.9s6.3-2.4 7-5.9C14.3 4.4 11.5 2 8 2zm0 10c-2.6 0-4.9-1.7-5.6-4.1C3.1 5.4 5.4 3.7 8 3.7s4.9 1.7 5.6 4.2C12.9 10.3 10.6 12 8 12z"/></svg>
+ Watch
+ </button>
+ <button style="display:flex;align-items:center;gap:7px;padding:7px 13px;font-size:.8rem;font-weight:500;color:var(--color-text);background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-sm);cursor:pointer;box-shadow:var(--shadow-sm)" style-hover="{ borderColor:'var(--color-accent)' }">
+ <svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor"><path d="M8 1.5l1.9 3.9 4.3.6-3.1 3 .7 4.3L8 11.3 4.2 13.3l.7-4.3L1.8 6l4.3-.6L8 1.5z"/></svg>
+ Star <span style="font-family:var(--font-mono);color:var(--color-text-muted)">128</span>
+ </button>
+ </div>
+ </div>
+
+ <!-- TAB BAR -->
+ <div style="display:flex;gap:2px;margin-top:22px;border-bottom:1px solid var(--color-border)">
+ <sc-for list="{{ tabs }}" as="t" hint-placeholder-count="6">
+ <button onClick="{{ t.onClick }}" style="{{ t.style }}" style-hover="{ color:'var(--color-text)' }">
+ <span>{{ t.label }}</span>
+ <sc-if value="{{ t.showCount }}"><span style="padding:1px 8px;font-family:var(--font-mono);font-size:.68rem;color:var(--color-text-muted);background:var(--color-code-bg);border:1px solid var(--color-border);border-radius:var(--radius-pill)">{{ t.count }}</span></sc-if>
+ <sc-if value="{{ t.dot }}"><span style="{{ t.dotStyle }}"></span></sc-if>
+ <sc-if value="{{ t.active }}"><span style="position:absolute;left:0;right:0;bottom:-1px;height:2px;background:var(--color-accent);border-radius:2px"></span></sc-if>
+ </button>
+ </sc-for>
+ </div>
+ </div>
+
+ <!-- ============ TAB CONTENT ============ -->
+ <main style="padding:30px 0 80px">
+
+ <!-- ===== OVERVIEW ===== -->
+ <sc-if value="{{ isOverview }}" hint-placeholder-val="{{ true }}">
+ <div style="display:grid;grid-template-columns:1fr 19rem;gap:34px;align-items:start">
+ <!-- README CARD -->
+ <article style="background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-sm);box-shadow:var(--shadow-sm);overflow:hidden">
+ <div style="display:flex;align-items:center;gap:8px;padding:11px 18px;background:var(--color-code-bg);border-bottom:1px solid var(--color-border);font-family:var(--font-mono);font-size:.72rem;letter-spacing:.06em;text-transform:uppercase;color:var(--color-text-muted)">
+ <svg width="13" height="13" viewBox="0 0 16 16" fill="currentColor"><path d="M3 1.5h6L13 5v9.5H3V1.5zm5.5 1H4v11h8V5.7L8.5 2.5z"/></svg>
+ README.md
+ </div>
+ <div style="padding:40px 48px 52px;max-width:44rem">
+ <h1 style="font-family:var(--font-display);font-weight:700;font-size:2.4rem;letter-spacing:-.02em;margin:0 0 6px;line-height:1.15">git‑ents</h1>
+ <p style="font-family:var(--font-display);font-style:italic;font-size:1.18rem;color:var(--color-text-muted);margin:0 0 22px">An editorial Git forge for people who love to read their code.</p>
+ <div style="display:flex;flex-wrap:wrap;gap:7px;margin-bottom:30px">
+ <span style="display:inline-flex;align-items:center;gap:5px;padding:3px 11px;font-family:var(--font-mono);font-size:.7rem;color:var(--fn);background:var(--color-code-bg);border:1px solid var(--color-border);border-radius:var(--radius-pill)"><span style="width:7px;height:7px;border-radius:100px;background:var(--fn)"></span>build passing</span>
+ <span style="padding:3px 11px;font-family:var(--font-mono);font-size:.7rem;color:var(--ty);background:var(--color-code-bg);border:1px solid var(--color-border);border-radius:var(--radius-pill)">v0.7.0</span>
+ <span style="padding:3px 11px;font-family:var(--font-mono);font-size:.7rem;color:var(--prop);background:var(--color-code-bg);border:1px solid var(--color-border);border-radius:var(--radius-pill)">MIT</span>
+ <span style="padding:3px 11px;font-family:var(--font-mono);font-size:.7rem;color:var(--color-text-muted);background:var(--color-code-bg);border:1px solid var(--color-border);border-radius:var(--radius-pill)">rust 1.78+</span>
+ </div>
+ <p style="margin:0 0 26px">git-ents renders a repository as quiet, server-side HTML — a README that reads like a page, a file tree you can wander, releases as a changelog, and CI defined entirely with the Git hooks you already know. The whole UI is paper-grain and works without a single byte of client JavaScript.</p>
+
+ <h2 style="font-family:var(--font-display);font-weight:600;font-size:1.4rem;letter-spacing:-.01em;margin:34px 0 4px;position:relative">Why git‑ents</h2>
+ <div style="width:3rem;height:2px;background:var(--color-accent);margin-bottom:16px"></div>
+ <ul style="margin:0 0 12px;padding-left:1.2rem">
+ <li style="margin-bottom:7px"><strong>Readable by default.</strong> Lora display type, generous measure, warm paper palette — light and dark are equals.</li>
+ <li style="margin-bottom:7px"><strong>Hooks, not pipelines.</strong> CI is just <code style="font-family:var(--font-mono);font-size:.85em;background:var(--color-code-bg);padding:1px 5px;border-radius:5px">pre-receive</code> and <code style="font-family:var(--font-mono);font-size:.85em;background:var(--color-code-bg);padding:1px 5px;border-radius:5px">post-receive</code> scripts, with run history and logs.</li>
+ <li style="margin-bottom:7px"><strong>No JavaScript.</strong> Every page is fully server-rendered; the browser just paints.</li>
+ <li><strong>One binary.</strong> Point it at a directory of bare repos and serve.</li>
+ </ul>
+
+ <h2 style="font-family:var(--font-display);font-weight:600;font-size:1.4rem;letter-spacing:-.01em;margin:34px 0 4px">Install</h2>
+ <div style="width:3rem;height:2px;background:var(--color-accent);margin-bottom:16px"></div>
+ <pre style="margin:0 0 20px;padding:15px 18px;background:var(--color-code-bg);border:1px solid var(--color-border);border-radius:var(--radius-sm);overflow-x:auto;font-family:var(--font-mono);font-size:.82rem;line-height:1.7;color:var(--color-text)"><span style="color:var(--color-text-muted)">$</span> cargo install git-ents
+<span style="color:var(--color-text-muted)">$</span> git-ents serve ./repos <span style="color:var(--cmt)"># http://localhost:8420</span></pre>
+
+ <h2 style="font-family:var(--font-display);font-weight:600;font-size:1.4rem;letter-spacing:-.01em;margin:34px 0 4px">A taste of the renderer</h2>
+ <div style="width:3rem;height:2px;background:var(--color-accent);margin-bottom:16px"></div>
+ <pre style="margin:0 0 8px;padding:16px 18px;background:var(--color-code-bg);border:1px solid var(--color-border);border-radius:var(--radius-sm);overflow-x:auto;font-family:var(--font-mono);font-size:.82rem;line-height:1.6"><span style="color:var(--cmt)">// render any blob as warm, highlighted HTML</span>
+<span style="color:var(--kw)">pub fn</span> <span style="color:var(--fn)">render_blob</span>(blob: &<span style="color:var(--ty)">Blob</span>) -> <span style="color:var(--ty)">Markup</span> {
+ <span style="color:var(--kw)">let</span> lang = <span style="color:var(--ty)">Language</span>::<span style="color:var(--fn)">guess</span>(&blob.path);
+ html! {
+ pre.paper { (<span style="color:var(--fn)">highlight</span>(&blob.text, lang)) }
+ }
+}</pre>
+
+ <h2 style="font-family:var(--font-display);font-weight:600;font-size:1.4rem;letter-spacing:-.01em;margin:34px 0 4px">License</h2>
+ <div style="width:3rem;height:2px;background:var(--color-accent);margin-bottom:16px"></div>
+ <p style="margin:0">Released under the MIT License. Grow something with it.</p>
+ </div>
+ </article>
+
+ <!-- ASIDE -->
+ <aside style="display:flex;flex-direction:column;gap:18px;position:sticky;top:78px">
+ <!-- Clone -->
+ <div style="background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-sm);box-shadow:var(--shadow-sm);overflow:hidden">
+ <div style="padding:10px 15px;font-family:var(--font-mono);font-size:.7rem;letter-spacing:.06em;text-transform:uppercase;color:var(--color-text-muted);border-bottom:1px solid var(--color-border)">Clone</div>
+ <div style="padding:14px 15px">
+ <div style="display:flex;gap:7px">
+ <input readonly value="git.ents.dev/git-ents/git-ents.git" style="flex:1;min-width:0;padding:7px 10px;font-family:var(--font-mono);font-size:.74rem;color:var(--color-text);background:var(--color-code-bg);border:1px solid var(--color-border);border-radius:8px;outline:none"/>
+ <button onClick="{{ onClone }}" title="Copy clone URL" style="flex:none;width:34px;display:flex;align-items:center;justify-content:center;background:var(--color-surface);border:1px solid var(--color-border);border-radius:8px;cursor:pointer;color:var(--color-text-muted)" style-hover="{ borderColor:'var(--color-accent)', color:'var(--color-accent)' }">
+ <sc-if value="{{ cloneCopied }}" hint-placeholder-val="{{ false }}"><svg width="14" height="14" viewBox="0 0 16 16" fill="var(--fn)"><path d="M13.5 4.5l-7 7L3 8l1-1 2.5 2.5 6-6 1 1z"/></svg></sc-if>
+ <sc-if value="{{ notCloneCopied }}" hint-placeholder-val="{{ true }}"><svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor"><path d="M4 2h6.5L13 4.5V11H4V2zm1 1v7h7V5.2L9.8 3H5z"/><path d="M3 5H2v9h8v-1H3V5z"/></svg></sc-if>
+ </button>
+ </div>
+ </div>
+ </div>
+ <!-- About -->
+ <div style="background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-sm);box-shadow:var(--shadow-sm)">
+ <div style="padding:10px 15px;font-family:var(--font-mono);font-size:.7rem;letter-spacing:.06em;text-transform:uppercase;color:var(--color-text-muted);border-bottom:1px solid var(--color-border)">About</div>
+ <div style="padding:14px 15px;font-size:.84rem;color:var(--color-text-muted);display:flex;flex-direction:column;gap:9px">
+ <div style="display:flex;align-items:center;gap:8px"><svg width="14" height="14" viewBox="0 0 16 16" fill="var(--ty)"><circle cx="8" cy="8" r="6"/></svg><span>Rust · MIT licensed</span></div>
+ <div style="display:flex;align-items:center;gap:8px"><svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor"><path d="M8 1.5l1.9 3.9 4.3.6-3.1 3 .7 4.3L8 11.3 4.2 13.3l.7-4.3L1.8 6l4.3-.6L8 1.5z"/></svg><span>128 stars · 9 forks</span></div>
+ <div style="display:flex;align-items:center;gap:8px"><svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor"><path d="M8 3.5V8l3 1.8.5-.8L9 7.5V3.5H8z"/><path d="M8 1.5A6.5 6.5 0 108 14.5 6.5 6.5 0 008 1.5zm0 1.5a5 5 0 110 10 5 5 0 010-10z"/></svg><span>Updated 3 hours ago</span></div>
+ </div>
+ </div>
+ <!-- Releases preview -->
+ <div style="background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-sm);box-shadow:var(--shadow-sm)">
+ <div style="display:flex;align-items:center;justify-content:space-between;padding:10px 15px;font-family:var(--font-mono);font-size:.7rem;letter-spacing:.06em;text-transform:uppercase;color:var(--color-text-muted);border-bottom:1px solid var(--color-border)"><span>Releases</span><span style="color:var(--color-accent)">4</span></div>
+ <div style="padding:14px 15px">
+ <div style="display:flex;align-items:center;gap:8px;margin-bottom:3px"><svg width="14" height="14" viewBox="0 0 16 16" fill="var(--color-accent)"><path d="M1.5 8L8 1.5h5.5V7L7 13.5 1.5 8zm10-4a1 1 0 100 2 1 1 0 000-2z"/></svg><span style="font-family:var(--font-mono);font-size:.82rem;font-weight:600;color:var(--color-accent)">v0.7.0</span><span style="padding:1px 7px;font-size:.62rem;color:var(--fn);background:var(--color-code-bg);border:1px solid var(--color-border);border-radius:var(--radius-pill)">Latest</span></div>
+ <p style="margin:0;font-size:.78rem;color:var(--color-text-muted)">Paper Trail — released 9 days ago</p>
+ </div>
+ </div>
+ <!-- Languages -->
+ <div style="background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-sm);box-shadow:var(--shadow-sm)">
+ <div style="padding:10px 15px;font-family:var(--font-mono);font-size:.7rem;letter-spacing:.06em;text-transform:uppercase;color:var(--color-text-muted);border-bottom:1px solid var(--color-border)">Languages</div>
+ <div style="padding:14px 15px">
+ <div style="display:flex;height:7px;border-radius:100px;overflow:hidden;margin-bottom:11px"><div style="width:86%;background:var(--ty)"></div><div style="width:9%;background:var(--fn)"></div><div style="width:5%;background:var(--prop)"></div></div>
+ <div style="display:flex;flex-wrap:wrap;gap:11px;font-size:.76rem;color:var(--color-text-muted)">
+ <span style="display:flex;align-items:center;gap:5px"><span style="width:8px;height:8px;border-radius:100px;background:var(--ty)"></span>Rust 86%</span>
+ <span style="display:flex;align-items:center;gap:5px"><span style="width:8px;height:8px;border-radius:100px;background:var(--fn)"></span>HTML 9%</span>
+ <span style="display:flex;align-items:center;gap:5px"><span style="width:8px;height:8px;border-radius:100px;background:var(--prop)"></span>CSS 5%</span>
+ </div>
+ </div>
+ </div>
+ </aside>
+ </div>
+ </sc-if>
+
+ <!-- ===== FILES ===== -->
+ <sc-if value="{{ isFiles }}" hint-placeholder-val="{{ false }}">
+ <div style="display:grid;grid-template-columns:17rem 1fr;gap:0;background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-sm);box-shadow:var(--shadow-sm);overflow:hidden;min-height:30rem">
+ <!-- TREE -->
+ <div style="border-right:1px solid var(--color-border);background:var(--color-bg)">
+ <div style="display:flex;align-items:center;gap:7px;padding:11px 15px;border-bottom:1px solid var(--color-border);font-family:var(--font-mono);font-size:.7rem;letter-spacing:.06em;text-transform:uppercase;color:var(--color-text-muted)">
+ <svg width="12" height="12" viewBox="0 0 16 16" fill="currentColor"><path d="M11.5 3.5a2 2 0 10-2.7 1.9v1.3c0 .6-.5 1.1-1.1 1.1H5.6a2.5 2.5 0 00-1.1.3V5.4a2 2 0 10-1.5 0v5.2a2 2 0 101.6 0V9.5c0-.6.5-1 1-1h2.1A2.6 2.6 0 0010.3 6V5.4a2 2 0 001.2-1.9z"/></svg>
+ main
+ </div>
+ <div style="padding:7px 6px">
+ <sc-for list="{{ tree }}" as="n1" hint-placeholder-count="7">
+ <button onClick="{{ n1.onClick }}" style="{{ n1.style }}" style-hover="{ background:'var(--color-code-bg)' }">
+ <sc-if value="{{ n1.isDir }}" hint-placeholder-val="{{ true }}"><span style="{{ n1.chevStyle }}"><svg width="11" height="11" viewBox="0 0 16 16" fill="currentColor"><path d="M6 4l5 4-5 4V4z"/></svg></span><svg width="14" height="14" viewBox="0 0 16 16" fill="var(--color-accent)"><path d="M1.5 4c0-.6.4-1 1-1h3l1.2 1.2H13c.6 0 1 .4 1 1V12c0 .6-.4 1-1 1H2.5c-.6 0-1-.4-1-1V4z"/></svg></sc-if>
+ <sc-if value="{{ n1.isFile }}" hint-placeholder-val="{{ false }}"><span style="width:11px;flex:none"></span><svg width="13" height="13" viewBox="0 0 16 16" fill="var(--color-text-muted)"><path d="M3 1.5h6L13 5v9.5H3V1.5zm5.5 1H4v11h8V5.7L8.5 2.5z"/></svg></sc-if>
+ <span style="overflow:hidden;text-overflow:ellipsis;white-space:nowrap">{{ n1.name }}</span>
+ </button>
+ <sc-if value="{{ n1.open }}" hint-placeholder-val="{{ false }}">
+ <sc-for list="{{ n1.kids }}" as="n2" hint-placeholder-count="3">
+ <button onClick="{{ n2.onClick }}" style="{{ n2.style }}" style-hover="{ background:'var(--color-code-bg)' }">
+ <sc-if value="{{ n2.isDir }}" hint-placeholder-val="{{ true }}"><span style="{{ n2.chevStyle }}"><svg width="11" height="11" viewBox="0 0 16 16" fill="currentColor"><path d="M6 4l5 4-5 4V4z"/></svg></span><svg width="14" height="14" viewBox="0 0 16 16" fill="var(--color-accent)"><path d="M1.5 4c0-.6.4-1 1-1h3l1.2 1.2H13c.6 0 1 .4 1 1V12c0 .6-.4 1-1 1H2.5c-.6 0-1-.4-1-1V4z"/></svg></sc-if>
+ <sc-if value="{{ n2.isFile }}" hint-placeholder-val="{{ false }}"><span style="width:11px;flex:none"></span><svg width="13" height="13" viewBox="0 0 16 16" fill="var(--color-text-muted)"><path d="M3 1.5h6L13 5v9.5H3V1.5zm5.5 1H4v11h8V5.7L8.5 2.5z"/></svg></sc-if>
+ <span style="overflow:hidden;text-overflow:ellipsis;white-space:nowrap">{{ n2.name }}</span>
+ </button>
+ <sc-if value="{{ n2.open }}" hint-placeholder-val="{{ false }}">
+ <sc-for list="{{ n2.kids }}" as="n3" hint-placeholder-count="3">
+ <button onClick="{{ n3.onClick }}" style="{{ n3.style }}" style-hover="{ background:'var(--color-code-bg)' }">
+ <span style="width:11px;flex:none"></span><svg width="13" height="13" viewBox="0 0 16 16" fill="var(--color-text-muted)"><path d="M3 1.5h6L13 5v9.5H3V1.5zm5.5 1H4v11h8V5.7L8.5 2.5z"/></svg>
+ <span style="overflow:hidden;text-overflow:ellipsis;white-space:nowrap">{{ n3.name }}</span>
+ </button>
+ </sc-for>
+ </sc-if>
+ </sc-for>
+ </sc-if>
+ </sc-for>
+ </div>
+ </div>
+ <!-- BLOB -->
+ <div style="min-width:0;display:flex;flex-direction:column">
+ <div style="display:flex;align-items:center;justify-content:space-between;gap:12px;padding:10px 16px;background:var(--color-code-bg);border-bottom:1px solid var(--color-border)">
+ <div style="font-family:var(--font-mono);font-size:.78rem;color:var(--color-text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap">{{ currentPath }}</div>
+ <div style="display:flex;align-items:center;gap:14px;flex:none">
+ <span style="font-family:var(--font-mono);font-size:.7rem;color:var(--color-text-muted)">{{ lineCount }} lines · {{ currentSize }}</span>
+ <button onClick="{{ onCopyFile }}" style="display:flex;align-items:center;gap:5px;padding:4px 10px;font-size:.72rem;color:var(--color-text-muted);background:var(--color-surface);border:1px solid var(--color-border);border-radius:7px;cursor:pointer" style-hover="{ borderColor:'var(--color-accent)', color:'var(--color-accent)' }">
+ <sc-if value="{{ fileCopied }}" hint-placeholder-val="{{ false }}"><svg width="12" height="12" viewBox="0 0 16 16" fill="var(--fn)"><path d="M13.5 4.5l-7 7L3 8l1-1 2.5 2.5 6-6 1 1z"/></svg>Copied</sc-if>
+ <sc-if value="{{ notFileCopied }}" hint-placeholder-val="{{ true }}"><svg width="12" height="12" viewBox="0 0 16 16" fill="currentColor"><path d="M4 2h6.5L13 4.5V11H4V2zm1 1v7h7V5.2L9.8 3H5z"/><path d="M3 5H2v9h8v-1H3V5z"/></svg>Copy</sc-if>
+ </button>
+ </div>
+ </div>
+ <div style="display:grid;grid-template-columns:auto 1fr;overflow:auto;flex:1">
+ <div style="padding:14px 0;background:var(--color-code-bg);border-right:1px solid var(--color-border);text-align:right;user-select:none">
+ <sc-for list="{{ lines }}" as="ln" hint-placeholder-count="12">
+ <div style="padding:0 14px;font-family:var(--font-mono);font-size:.78rem;line-height:1.55;color:var(--color-text-muted);opacity:.6">{{ ln.n }}</div>
+ </sc-for>
+ </div>
+ <div style="padding:14px 0;overflow-x:auto">
+ <sc-for list="{{ lines }}" as="ln" hint-placeholder-count="12">
+ <div style="padding:0 18px;font-family:var(--font-mono);font-size:.78rem;line-height:1.55;white-space:pre;color:var(--color-text);min-height:1.21em">{{ ln.t }}</div>
+ </sc-for>
+ </div>
+ </div>
+ </div>
+ </div>
+ </sc-if>
+
+ <!-- ===== RELEASES ===== -->
+ <sc-if value="{{ isReleases }}" hint-placeholder-val="{{ false }}">
+ <div style="margin-bottom:24px">
+ <h1 style="font-family:var(--font-display);font-weight:700;font-size:1.5rem;letter-spacing:-.01em;margin:0 0 4px">Releases</h1>
+ <div style="width:3rem;height:2px;background:var(--color-accent)"></div>
+ </div>
+ <div style="position:relative;padding-left:30px">
+ <div style="position:absolute;left:5px;top:6px;bottom:6px;width:2px;background:var(--color-border)"></div>
+ <sc-for list="{{ releases }}" as="r" hint-placeholder-count="4">
+ <div style="position:relative;margin-bottom:30px">
+ <span style="{{ r.dotStyle }}"></span>
+ <div style="background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-sm);box-shadow:var(--shadow-sm);overflow:hidden">
+ <div style="display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;padding:14px 20px;border-bottom:1px solid var(--color-border)">
+ <div style="display:flex;align-items:center;gap:11px">
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="var(--color-accent)"><path d="M1.5 8L8 1.5h5.5V7L7 13.5 1.5 8zm10-4a1 1 0 100 2 1 1 0 000-2z"/></svg>
+ <span style="font-family:var(--font-mono);font-size:1rem;font-weight:600;color:var(--color-accent)">{{ r.tag }}</span>
+ <span style="font-family:var(--font-display);font-size:1.05rem;color:var(--color-text)">{{ r.name }}</span>
+ <sc-if value="{{ r.latest }}" hint-placeholder-val="{{ false }}"><span style="padding:2px 9px;font-size:.64rem;font-weight:500;color:var(--fn);background:var(--color-code-bg);border:1px solid var(--color-border);border-radius:var(--radius-pill)">Latest</span></sc-if>
+ </div>
+ <span style="font-size:.78rem;color:var(--color-text-muted)">{{ r.date }}</span>
+ </div>
+ <div style="padding:18px 20px">
+ <sc-for list="{{ r.sections }}" as="s" hint-placeholder-count="2">
+ <div style="margin-bottom:13px">
+ <div style="font-family:var(--font-mono);font-size:.7rem;letter-spacing:.06em;text-transform:uppercase;color:{{ s.color }};margin-bottom:5px">{{ s.label }}</div>
+ <ul style="margin:0;padding-left:1.1rem">
+ <sc-for list="{{ s.items }}" as="it" hint-placeholder-count="2">
+ <li style="font-size:.88rem;margin-bottom:3px">{{ it }}</li>
+ </sc-for>
+ </ul>
+ </div>
+ </sc-for>
+ <sc-if value="{{ r.hasDiff }}" hint-placeholder-val="{{ false }}">
+ <div style="margin:6px 0 13px;border:1px solid var(--color-border);border-radius:8px;overflow:hidden;font-family:var(--font-mono);font-size:.76rem;line-height:1.6">
+ <div style="padding:3px 14px;background:var(--diff-add);color:var(--color-text)">+ render release notes with the Lora display face</div>
+ <div style="padding:3px 14px;background:var(--diff-rem);color:var(--color-text)">- render release notes with the body sans</div>
+ </div>
+ </sc-if>
+ <div style="display:flex;align-items:center;gap:14px;flex-wrap:wrap;margin-top:14px;padding-top:13px;border-top:1px solid var(--color-border)">
+ <sc-for list="{{ r.assets }}" as="a" hint-placeholder-count="2">
+ <a href="#" style="display:flex;align-items:center;gap:6px;font-family:var(--font-mono);font-size:.74rem;color:var(--color-link)" style-hover="{ color:'var(--color-link-hover)' }">
+ <svg width="13" height="13" viewBox="0 0 16 16" fill="currentColor"><path d="M8 1.5v7.8l2.5-2.5 1 1L8 12.3 4.5 8.8l1-1L8 9.3V1.5H8zM3 13h10v1.2H3V13z"/></svg>
+ {{ a.name }} <span style="color:var(--color-text-muted)">{{ a.size }}</span>
+ </a>
+ </sc-for>
+ <span style="display:flex;align-items:center;gap:6px;margin-left:auto;font-family:var(--font-mono);font-size:.72rem;color:var(--color-text-muted)">
+ <svg width="12" height="12" viewBox="0 0 16 16" fill="currentColor"><circle cx="5" cy="8" r="2"/><circle cx="11" cy="8" r="2" fill="none" stroke="currentColor" stroke-width="1.4"/></svg>
+ {{ r.sha }}
+ </span>
+ </div>
+ </div>
+ </div>
+ </div>
+ </sc-for>
+ </div>
+ </sc-if>
+
+ <!-- ===== HOOKS ===== -->
+ <sc-if value="{{ isHooks }}" hint-placeholder-val="{{ false }}">
+ <div style="margin-bottom:22px">
+ <h1 style="font-family:var(--font-display);font-weight:700;font-size:1.5rem;letter-spacing:-.01em;margin:0 0 4px">Hooks</h1>
+ <div style="width:3rem;height:2px;background:var(--color-accent)"></div>
+ <p style="margin:12px 0 0;max-width:38rem;font-size:.9rem;color:var(--color-text-muted)">Continuous integration defined as plain Git hooks. Each push runs the stages in <code style="font-family:var(--font-mono);font-size:.85em;background:var(--color-code-bg);padding:1px 5px;border-radius:5px">.gitents/hooks.toml</code> — no YAML, no marketplace.</p>
+ </div>
+ <div style="display:grid;grid-template-columns:20rem 1fr;gap:20px;align-items:start">
+ <!-- run list -->
+ <div style="background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-sm);box-shadow:var(--shadow-sm);overflow:hidden">
+ <div style="padding:10px 15px;font-family:var(--font-mono);font-size:.7rem;letter-spacing:.06em;text-transform:uppercase;color:var(--color-text-muted);border-bottom:1px solid var(--color-border)">Recent runs</div>
+ <sc-for list="{{ runs }}" as="run" hint-placeholder-count="4">
+ <button onClick="{{ run.onClick }}" style="{{ run.style }}" style-hover="{ background:'var(--color-code-bg)' }">
+ <span style="{{ run.iconWrap }}">
+ <sc-if value="{{ run.isRunning }}" hint-placeholder-val="{{ false }}"><svg width="13" height="13" viewBox="0 0 16 16" fill="none" stroke="var(--ty)" stroke-width="1.6" style="animation:spin 1s linear infinite"><path d="M8 2a6 6 0 106 6" stroke-linecap="round"/></svg></sc-if>
+ <sc-if value="{{ run.isPassed }}" hint-placeholder-val="{{ true }}"><svg width="13" height="13" viewBox="0 0 16 16" fill="var(--fn)"><path d="M8 1.5a6.5 6.5 0 100 13 6.5 6.5 0 000-13zM7 10.4L4.3 7.7l1-1L7 8.4l3.7-3.7 1 1L7 10.4z"/></svg></sc-if>
+ <sc-if value="{{ run.isFailed }}" hint-placeholder-val="{{ false }}"><svg width="13" height="13" viewBox="0 0 16 16" fill="var(--kw)"><path d="M8 1.5a6.5 6.5 0 100 13 6.5 6.5 0 000-13zm2.5 8.4l-1 1L8 9l-1.5 1.5-1-1L7 8 5.5 6.5l1-1L8 7l1.5-1.5 1 1L9 8l1.5 1.5z"/></svg></sc-if>
+ </span>
+ <span style="min-width:0;flex:1;text-align:left">
+ <span style="display:block;font-size:.82rem;color:var(--color-text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap">{{ run.subject }}</span>
+ <span style="display:block;font-family:var(--font-mono);font-size:.68rem;color:var(--color-text-muted);margin-top:1px">#{{ run.id }} · {{ run.branch }} · {{ run.trigger }}</span>
+ </span>
+ <span style="flex:none;font-family:var(--font-mono);font-size:.66rem;color:var(--color-text-muted);text-align:right">{{ run.age }}</span>
+ </button>
+ </sc-for>
+ </div>
+ <!-- run detail -->
+ <div style="display:flex;flex-direction:column;gap:18px;min-width:0">
+ <div style="background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-sm);box-shadow:var(--shadow-sm);overflow:hidden">
+ <div style="display:flex;align-items:center;gap:11px;flex-wrap:wrap;padding:15px 20px;border-bottom:1px solid var(--color-border)">
+ <span style="{{ cur.iconWrap }}">
+ <sc-if value="{{ cur.isRunning }}" hint-placeholder-val="{{ false }}"><svg width="15" height="15" viewBox="0 0 16 16" fill="none" stroke="var(--ty)" stroke-width="1.6" style="animation:spin 1s linear infinite"><path d="M8 2a6 6 0 106 6" stroke-linecap="round"/></svg></sc-if>
+ <sc-if value="{{ cur.isPassed }}" hint-placeholder-val="{{ true }}"><svg width="15" height="15" viewBox="0 0 16 16" fill="var(--fn)"><path d="M8 1.5a6.5 6.5 0 100 13 6.5 6.5 0 000-13zM7 10.4L4.3 7.7l1-1L7 8.4l3.7-3.7 1 1L7 10.4z"/></svg></sc-if>
+ <sc-if value="{{ cur.isFailed }}" hint-placeholder-val="{{ false }}"><svg width="15" height="15" viewBox="0 0 16 16" fill="var(--kw)"><path d="M8 1.5a6.5 6.5 0 100 13 6.5 6.5 0 000-13zm2.5 8.4l-1 1L8 9l-1.5 1.5-1-1L7 8 5.5 6.5l1-1L8 7l1.5-1.5 1 1L9 8l1.5 1.5z"/></svg></sc-if>
+ </span>
+ <span style="font-family:var(--font-display);font-size:1.1rem;color:var(--color-text)">{{ cur.subject }}</span>
+ <span style="font-family:var(--font-mono);font-size:.72rem;color:var(--color-text-muted);margin-left:auto">{{ cur.sha }} · {{ cur.duration }}</span>
+ </div>
+ <!-- stages -->
+ <div style="display:flex;flex-wrap:wrap;gap:8px;padding:15px 20px;border-bottom:1px solid var(--color-border)">
+ <sc-for list="{{ cur.stages }}" as="st" hint-placeholder-count="4">
+ <div style="display:flex;align-items:center;gap:7px;padding:6px 12px;background:var(--color-code-bg);border:1px solid var(--color-border);border-radius:var(--radius-pill)">
+ <span style="width:8px;height:8px;border-radius:100px;background:{{ st.color }}"></span>
+ <span style="font-family:var(--font-mono);font-size:.74rem;color:var(--color-text)">{{ st.name }}</span>
+ <span style="font-family:var(--font-mono);font-size:.68rem;color:var(--color-text-muted)">{{ st.duration }}</span>
+ </div>
+ </sc-for>
+ </div>
+ <!-- log -->
+ <div style="background:var(--color-code-bg);padding:14px 0;overflow-x:auto">
+ <sc-for list="{{ cur.log }}" as="lg" hint-placeholder-count="6">
+ <div style="display:flex;gap:14px;padding:0 18px;font-family:var(--font-mono);font-size:.76rem;line-height:1.7;white-space:pre">
+ <span style="color:var(--color-text-muted);opacity:.5;user-select:none;min-width:1.4rem;text-align:right">{{ lg.n }}</span>
+ <span style="color:{{ lg.color }}">{{ lg.text }}</span>
+ </div>
+ </sc-for>
+ </div>
+ </div>
+ <!-- config -->
+ <div style="background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-sm);box-shadow:var(--shadow-sm);overflow:hidden">
+ <div style="display:flex;align-items:center;gap:8px;padding:10px 16px;background:var(--color-code-bg);border-bottom:1px solid var(--color-border);font-family:var(--font-mono);font-size:.72rem;letter-spacing:.06em;text-transform:uppercase;color:var(--color-text-muted)">
+ <svg width="12" height="12" viewBox="0 0 16 16" fill="currentColor"><path d="M3 1.5h6L13 5v9.5H3V1.5zm5.5 1H4v11h8V5.7L8.5 2.5z"/></svg>.gitents/hooks.toml
+ </div>
+ <div style="padding:14px 0;overflow-x:auto">
+ <sc-for list="{{ hooksToml }}" as="hl" hint-placeholder-count="10">
+ <div style="padding:0 18px;font-family:var(--font-mono);font-size:.78rem;line-height:1.6;white-space:pre;color:{{ hl.color }};min-height:1.25em">{{ hl.t }}</div>
+ </sc-for>
+ </div>
+ </div>
+ </div>
+ </div>
+ </sc-if>
+
+ <!-- ===== ISSUES ===== -->
+ <sc-if value="{{ isIssues }}" hint-placeholder-val="{{ false }}">
+ <div style="display:flex;align-items:flex-end;justify-content:space-between;gap:16px;flex-wrap:wrap;margin-bottom:18px">
+ <div>
+ <h1 style="font-family:var(--font-display);font-weight:700;font-size:1.5rem;letter-spacing:-.01em;margin:0 0 4px;white-space:nowrap">Bug reports</h1>
+ <div style="width:3rem;height:2px;background:var(--color-accent)"></div>
+ </div>
+ <button style="display:flex;align-items:center;gap:7px;padding:8px 15px;font-size:.82rem;font-weight:600;color:var(--color-bg);background:var(--color-accent);border:none;border-radius:var(--radius-sm);cursor:pointer" style-hover="{ background:'var(--color-link-hover)' }">
+ <svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor"><path d="M7.25 3.5h1.5v3.75H12.5v1.5H8.75V12.5h-1.5V8.75H3.5v-1.5h3.75V3.5z"/></svg>New issue
+ </button>
+ </div>
+ <div style="display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin-bottom:16px">
+ <div style="flex:1;min-width:14rem;position:relative;display:flex;align-items:center">
+ <svg width="14" height="14" viewBox="0 0 16 16" fill="var(--color-text-muted)" style="position:absolute;left:12px;pointer-events:none"><path d="M10.7 9.6a5 5 0 10-1.1 1.1l3 3 1.1-1.1-3-3zM2.5 6.5a3.5 3.5 0 117 0 3.5 3.5 0 01-7 0z"/></svg>
+ <input value="{{ iq }}" onInput="{{ onSearch }}" placeholder="Search bug reports" style="width:100%;padding:8px 12px 8px 34px;background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-sm);font-size:.84rem;color:var(--color-text);outline:none" style-focus="{ borderColor:'var(--color-accent)' }"/>
+ </div>
+ <div style="display:flex;gap:6px">
+ <sc-for list="{{ labelChips }}" as="lc" hint-placeholder-count="4">
+ <button onClick="{{ lc.onClick }}" style="{{ lc.style }}">{{ lc.label }}</button>
+ </sc-for>
+ </div>
+ </div>
+ <div style="background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-sm);box-shadow:var(--shadow-sm);overflow:hidden">
+ <div style="display:flex;gap:20px;padding:11px 18px;border-bottom:1px solid var(--color-border);background:var(--color-code-bg)">
+ <sc-for list="{{ issueTabs }}" as="ft" hint-placeholder-count="2">
+ <button onClick="{{ ft.onClick }}" style="{{ ft.style }}">
+ <sc-if value="{{ ft.isOpen }}" hint-placeholder-val="{{ true }}"><svg width="13" height="13" viewBox="0 0 16 16" fill="currentColor"><path d="M8 1.5a6.5 6.5 0 100 13 6.5 6.5 0 000-13zm0 1.5a5 5 0 110 10 5 5 0 010-10zm0 2.5a2.5 2.5 0 100 5 2.5 2.5 0 000-5z"/></svg></sc-if>
+ <sc-if value="{{ ft.isClosed }}" hint-placeholder-val="{{ false }}"><svg width="13" height="13" viewBox="0 0 16 16" fill="currentColor"><path d="M8 1.5a6.5 6.5 0 100 13 6.5 6.5 0 000-13zM7 10.4L4.3 7.7l1-1L7 8.4l3.7-3.7 1 1L7 10.4z"/></svg></sc-if>
+ {{ ft.label }} <span style="color:var(--color-text-muted)">{{ ft.count }}</span>
+ </button>
+ </sc-for>
+ </div>
+ <sc-for list="{{ issues }}" as="iss" hint-placeholder-count="4">
+ <div style="display:flex;align-items:flex-start;gap:12px;padding:14px 18px;border-bottom:1px solid var(--color-border)" style-hover="{ background:'var(--color-code-bg)' }">
+ <span style="flex:none;margin-top:2px;color:{{ iss.statusColor }}">
+ <sc-if value="{{ iss.isOpen }}" hint-placeholder-val="{{ true }}"><svg width="15" height="15" viewBox="0 0 16 16" fill="currentColor"><path d="M8 1.5a6.5 6.5 0 100 13 6.5 6.5 0 000-13zm0 1.5a5 5 0 110 10 5 5 0 010-10zm0 2.5a2.5 2.5 0 100 5 2.5 2.5 0 000-5z"/></svg></sc-if>
+ <sc-if value="{{ iss.isClosed }}" hint-placeholder-val="{{ false }}"><svg width="15" height="15" viewBox="0 0 16 16" fill="currentColor"><path d="M8 1.5a6.5 6.5 0 100 13 6.5 6.5 0 000-13zM7 10.4L4.3 7.7l1-1L7 8.4l3.7-3.7 1 1L7 10.4z"/></svg></sc-if>
+ </span>
+ <div style="min-width:0;flex:1">
+ <div style="display:flex;align-items:center;gap:9px;flex-wrap:wrap">
+ <a href="#" style="font-size:.96rem;font-weight:500;color:var(--color-text)" style-hover="{ color:'var(--color-accent)' }">{{ iss.title }}</a>
+ <sc-for list="{{ iss.labels }}" as="lb" hint-placeholder-count="1">
+ <span style="padding:1px 9px;font-family:var(--font-mono);font-size:.66rem;color:{{ lb.color }};background:var(--color-code-bg);border:1px solid var(--color-border);border-radius:var(--radius-pill)">{{ lb.name }}</span>
+ </sc-for>
+ </div>
+ <div style="font-size:.76rem;color:var(--color-text-muted);margin-top:3px">#{{ iss.num }} {{ iss.meta }}</div>
+ </div>
+ <sc-if value="{{ iss.hasComments }}" hint-placeholder-val="{{ false }}">
+ <span style="flex:none;display:flex;align-items:center;gap:5px;font-size:.76rem;color:var(--color-text-muted)">
+ <svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor"><path d="M2 3c0-.6.4-1 1-1h10c.6 0 1 .4 1 1v7c0 .6-.4 1-1 1H6l-3 2.5V11H3c-.6 0-1-.4-1-1V3z"/></svg>{{ iss.comments }}
+ </span>
+ </sc-if>
+ </div>
+ </sc-for>
+ <sc-if value="{{ noIssues }}" hint-placeholder-val="{{ false }}">
+ <div style="padding:40px;text-align:center;color:var(--color-text-muted);font-size:.88rem">No bug reports match these filters.</div>
+ </sc-if>
+ </div>
+ </sc-if>
+
+ <!-- ===== SETTINGS ===== -->
+ <sc-if value="{{ isSettings }}" hint-placeholder-val="{{ false }}">
+ <div style="max-width:46rem">
+ <div style="margin-bottom:22px">
+ <h1 style="font-family:var(--font-display);font-weight:700;font-size:1.5rem;letter-spacing:-.01em;margin:0 0 4px">Repository settings</h1>
+ <div style="width:3rem;height:2px;background:var(--color-accent)"></div>
+ </div>
+ <!-- General -->
+ <div style="background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-sm);box-shadow:var(--shadow-sm);margin-bottom:20px">
+ <div style="padding:11px 18px;font-family:var(--font-mono);font-size:.7rem;letter-spacing:.06em;text-transform:uppercase;color:var(--color-text-muted);border-bottom:1px solid var(--color-border)">General</div>
+ <div style="padding:20px 18px;display:flex;flex-direction:column;gap:17px">
+ <label style="display:block">
+ <span style="display:block;font-size:.8rem;font-weight:500;margin-bottom:6px">Repository name</span>
+ <input value="git-ents" style="width:100%;padding:9px 12px;font-family:var(--font-mono);font-size:.85rem;color:var(--color-text);background:var(--color-bg);border:1px solid var(--color-border);border-radius:8px;outline:none" style-focus="{ borderColor:'var(--color-accent)' }"/>
+ </label>
+ <label style="display:block">
+ <span style="display:block;font-size:.8rem;font-weight:500;margin-bottom:6px">Description</span>
+ <textarea rows="2" style="width:100%;padding:9px 12px;font-size:.85rem;color:var(--color-text);background:var(--color-bg);border:1px solid var(--color-border);border-radius:8px;outline:none;resize:vertical;font-family:var(--font-body)" style-focus="{ borderColor:'var(--color-accent)' }">A warm, editorial Git forge — serve your repositories as paper-grain HTML.</textarea>
+ </label>
+ <label style="display:block">
+ <span style="display:block;font-size:.8rem;font-weight:500;margin-bottom:6px">Default branch</span>
+ <select style="width:11rem;padding:9px 12px;font-family:var(--font-mono);font-size:.85rem;color:var(--color-text);background:var(--color-bg);border:1px solid var(--color-border);border-radius:8px;outline:none">
+ <option>main</option><option>develop</option><option>trunk</option>
+ </select>
+ </label>
+ </div>
+ </div>
+ <!-- Features -->
+ <div style="background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-sm);box-shadow:var(--shadow-sm);margin-bottom:20px">
+ <div style="padding:11px 18px;font-family:var(--font-mono);font-size:.7rem;letter-spacing:.06em;text-transform:uppercase;color:var(--color-text-muted);border-bottom:1px solid var(--color-border)">Features</div>
+ <div>
+ <sc-for list="{{ features }}" as="f" hint-placeholder-count="4">
+ <div style="display:flex;align-items:center;justify-content:space-between;gap:14px;padding:14px 18px;border-bottom:1px solid var(--color-border)">
+ <div>
+ <div style="font-size:.9rem;font-weight:500">{{ f.label }}</div>
+ <div style="font-size:.76rem;color:var(--color-text-muted)">{{ f.desc }}</div>
+ </div>
+ <button onClick="{{ f.onClick }}" style="{{ f.trackStyle }}"><span style="{{ f.knobStyle }}"></span></button>
+ </div>
+ </sc-for>
+ </div>
+ </div>
+ <!-- Visibility -->
+ <div style="background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-sm);box-shadow:var(--shadow-sm);margin-bottom:20px">
+ <div style="padding:11px 18px;font-family:var(--font-mono);font-size:.7rem;letter-spacing:.06em;text-transform:uppercase;color:var(--color-text-muted);border-bottom:1px solid var(--color-border)">Visibility</div>
+ <div style="padding:14px 18px;display:flex;flex-direction:column;gap:10px">
+ <sc-for list="{{ visOptions }}" as="v" hint-placeholder-count="2">
+ <button onClick="{{ v.onClick }}" style="{{ v.style }}">
+ <span style="{{ v.radioStyle }}"><sc-if value="{{ v.on }}" hint-placeholder-val="{{ true }}"><span style="width:8px;height:8px;border-radius:100px;background:var(--color-accent)"></span></sc-if></span>
+ <span style="text-align:left"><span style="display:block;font-size:.86rem;font-weight:500;color:var(--color-text)">{{ v.label }}</span><span style="display:block;font-size:.76rem;color:var(--color-text-muted)">{{ v.desc }}</span></span>
+ </button>
+ </sc-for>
+ </div>
+ </div>
+ <!-- Danger zone -->
+ <div style="background:var(--color-surface);border:1px solid var(--kw);border-radius:var(--radius-sm);box-shadow:var(--shadow-sm);overflow:hidden">
+ <div style="padding:11px 18px;font-family:var(--font-mono);font-size:.7rem;letter-spacing:.06em;text-transform:uppercase;color:var(--kw);border-bottom:1px solid var(--color-border)">Danger zone</div>
+ <div style="padding:6px 18px">
+ <div style="display:flex;align-items:center;justify-content:space-between;gap:14px;padding:14px 0;border-bottom:1px solid var(--color-border)">
+ <div><div style="font-size:.88rem;font-weight:500">Archive this repository</div><div style="font-size:.76rem;color:var(--color-text-muted)">Mark read-only. Hooks stop running.</div></div>
+ <button style="padding:6px 14px;font-size:.78rem;font-weight:500;color:var(--kw);background:transparent;border:1px solid var(--kw);border-radius:8px;cursor:pointer" style-hover="{ background:'var(--diff-rem)' }">Archive</button>
+ </div>
+ <div style="display:flex;align-items:center;justify-content:space-between;gap:14px;padding:14px 0">
+ <div><div style="font-size:.88rem;font-weight:500">Delete this repository</div><div style="font-size:.76rem;color:var(--color-text-muted)">This cannot be undone.</div></div>
+ <button style="padding:6px 14px;font-size:.78rem;font-weight:600;color:#fff;background:var(--kw);border:1px solid var(--kw);border-radius:8px;cursor:pointer" style-hover="{ opacity:'.9' }">Delete</button>
+ </div>
+ </div>
+ </div>
+ </div>
+ </sc-if>
+
+ </main>
+ </div>
+
+ <!-- ============ FOOTER ============ -->
+ <footer style="border-top:1px solid var(--color-border);margin-top:20px">
+ <div style="max-width:78rem;margin:0 auto;padding:24px 1.5rem;text-align:center;font-family:var(--font-mono);font-size:.72rem;color:var(--color-text-muted)">
+ git-ents · served as paper-grain HTML · no JavaScript required
+ </div>
+ </footer>
+</div>
+</x-dc>
+<script type="text/x-dc" data-dc-script data-props="{"$preview":{"width":"100%"},"defaultTab":{"editor":"enum","options":["overview","files","releases","hooks","issues","settings"],"default":"overview","tsType":"string"},"collapseTree":{"editor":"boolean","default":false,"tsType":"boolean"}}">
+class Component extends DCLogic {
+ state = {
+ tab: 'overview',
+ exp: { 'src': true, 'src/forge': true, 'src/hooks': false, 'templates': false, 'static': false, '.gitents': true },
+ file: 'Cargo.toml',
+ run: 142,
+ itab: 'open',
+ ilabel: 'all',
+ iq: '',
+ copied: '',
+ features: { issues: true, releases: true, hooks: true, wiki: false },
+ visibility: 'public',
+ };
+
+ constructor(props) {
+ super(props);
+ const valid = ['overview', 'files', 'releases', 'hooks', 'issues', 'settings'];
+ if (props && valid.includes(props.defaultTab)) this.state.tab = props.defaultTab;
+ if (props && props.collapseTree) this.state.exp = { 'src': false, 'src/forge': false, 'src/hooks': false, 'templates': false, 'static': false, '.gitents': false };
+ }
+
+ componentWillUnmount() { clearTimeout(this._t); }
+
+ copy(text, key) {
+ try { if (navigator.clipboard) navigator.clipboard.writeText(text); } catch (e) {}
+ this.setState({ copied: key });
+ clearTimeout(this._t);
+ this._t = setTimeout(() => this.setState(s => (s.copied === key ? { copied: '' } : null)), 1400);
+ }
+
+ renderVals() {
+ const accent = 'var(--color-accent)', muted = 'var(--color-text-muted)', text = 'var(--color-text)';
+ const tab = this.state.tab;
+
+ // ---------- TABS ----------
+ const tabBtn = (active) => ({
+ position: 'relative', display: 'flex', alignItems: 'center', gap: '8px',
+ padding: '10px 14px', background: 'none', border: 'none', cursor: 'pointer',
+ fontSize: '.88rem', fontWeight: active ? 600 : 500,
+ color: active ? text : muted, fontFamily: 'var(--font-body)',
+ });
+ const tabDefs = [
+ { id: 'overview', label: 'Overview' },
+ { id: 'files', label: 'Files' },
+ { id: 'releases', label: 'Releases', count: 4 },
+ { id: 'hooks', label: 'Hooks', dot: true },
+ { id: 'issues', label: 'Issues', count: 3 },
+ { id: 'settings', label: 'Settings' },
+ ];
+ const tabs = tabDefs.map(t => ({
+ label: t.label, active: tab === t.id,
+ showCount: t.count != null, count: t.count,
+ dot: !!t.dot,
+ dotStyle: { width: '7px', height: '7px', borderRadius: '100px', background: 'var(--fn)' },
+ onClick: () => this.setState({ tab: t.id }),
+ style: tabBtn(tab === t.id),
+ }));
+
+ // ---------- FILE TREE ----------
+ const T = [
+ { t: 'dir', name: '.gitents', path: '.gitents', children: [
+ { t: 'file', name: 'hooks.toml', path: '.gitents/hooks.toml' },
+ ]},
+ { t: 'dir', name: 'src', path: 'src', children: [
+ { t: 'file', name: 'main.rs', path: 'src/main.rs' },
+ { t: 'dir', name: 'forge', path: 'src/forge', children: [
+ { t: 'file', name: 'mod.rs', path: 'src/forge/mod.rs' },
+ { t: 'file', name: 'repo.rs', path: 'src/forge/repo.rs' },
+ { t: 'file', name: 'render.rs', path: 'src/forge/render.rs' },
+ { t: 'file', name: 'blob.rs', path: 'src/forge/blob.rs' },
+ ]},
+ { t: 'dir', name: 'hooks', path: 'src/hooks', children: [
+ { t: 'file', name: 'mod.rs', path: 'src/hooks/mod.rs' },
+ { t: 'file', name: 'runner.rs', path: 'src/hooks/runner.rs' },
+ ]},
+ ]},
+ { t: 'dir', name: 'templates', path: 'templates', children: [
+ { t: 'file', name: 'repo.html', path: 'templates/repo.html' },
+ { t: 'file', name: 'blob.html', path: 'templates/blob.html' },
+ ]},
+ { t: 'dir', name: 'static', path: 'static', children: [
+ { t: 'file', name: 'paper.css', path: 'static/paper.css' },
+ ]},
+ { t: 'file', name: 'Cargo.toml', path: 'Cargo.toml' },
+ { t: 'file', name: 'LICENSE', path: 'LICENSE' },
+ { t: 'file', name: 'README.md', path: 'README.md' },
+ ];
+ const rowBase = (pad, active) => ({
+ display: 'flex', alignItems: 'center', gap: '7px', width: '100%',
+ padding: '4px 8px', paddingLeft: pad + 'px', border: 'none',
+ background: active ? 'var(--color-accent-subtle)' : 'transparent',
+ color: active ? accent : text, cursor: 'pointer', textAlign: 'left',
+ fontFamily: 'var(--font-mono)', fontSize: '.79rem', borderRadius: '7px',
+ fontWeight: active ? 600 : 400,
+ });
+ const dec = (n, depth) => {
+ const pad = 8 + depth * 15;
+ if (n.t === 'dir') {
+ const open = !!this.state.exp[n.path];
+ return {
+ key: n.path, name: n.name, isDir: true, isFile: false, open,
+ chevStyle: { display: 'inline-flex', transition: 'transform .15s', transform: open ? 'rotate(90deg)' : 'none', color: muted, flex: 'none' },
+ onClick: () => this.setState(s => ({ exp: { ...s.exp, [n.path]: !s.exp[n.path] } })),
+ style: rowBase(pad, false),
+ kids: (n.children || []).map(c => dec(c, depth + 1)),
+ };
+ }
+ const active = this.state.file === n.path;
+ return {
+ key: n.path, name: n.name, isDir: false, isFile: true, open: false, active,
+ onClick: () => this.setState({ file: n.path }),
+ style: rowBase(pad, active), kids: [],
+ };
+ };
+ const tree = T.map(n => dec(n, 0));
+
+ // ---------- FILE CONTENTS ----------
+ const FILES = {
+ 'Cargo.toml': '[package]\nname = "git-ents"\nversion = "0.7.0"\nedition = "2021"\nlicense = "MIT"\ndescription = "A warm, editorial Git forge."\nrepository = "https://git.ents.dev/git-ents/git-ents"\n\n[dependencies]\naxum = "0.7"\nmaud = "0.26"\ngix = "0.63"\nsyntect = "5.2"\ntoml = "0.8"\npulldown-cmark = "0.11"',
+ 'README.md': '# git-ents\n\nAn editorial Git forge for people who love to read their code.\n\ngit-ents renders a repository as quiet, server-side HTML. The whole UI is\npaper-grain and works without a single byte of client JavaScript.\n\n## Why git-ents\n\n- Readable by default.\n- Hooks, not pipelines.\n- No JavaScript.\n- One binary.\n\n## Install\n\n cargo install git-ents\n git-ents serve ./repos\n\n## License\n\nMIT.',
+ 'LICENSE': 'MIT License\n\nCopyright (c) 2026 The Git Ents\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the "Software"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so.',
+ 'src/main.rs': 'use axum::Router;\n\nmod forge;\nmod hooks;\n\n#[tokio::main]\nasync fn main() {\n let app = Router::new()\n .merge(forge::routes())\n .merge(hooks::routes());\n\n let addr = "0.0.0.0:8420";\n println!("git-ents serving on {addr}");\n axum::serve(listen(addr).await, app).await.unwrap();\n}',
+ 'src/forge/mod.rs': 'use axum::{Router, routing::get};\n\nmod repo;\nmod render;\nmod blob;\n\npub fn routes() -> Router {\n Router::new()\n .route("/:owner/:repo", get(repo::overview))\n .route("/:owner/:repo/tree/*path", get(blob::tree))\n}',
+ 'src/forge/repo.rs': 'use crate::render::Markup;\n\npub struct Repo {\n pub owner: String,\n pub name: String,\n pub head: String,\n}\n\nimpl Repo {\n pub fn open(path: &str) -> Result<Self, Error> {\n let git = gix::open(path)?;\n Ok(Self::from(git))\n }\n}',
+ 'src/forge/render.rs': '// turn a markdown README into warm, paper-grain HTML\nuse pulldown_cmark::{Parser, html};\n\npub fn readme(src: &str) -> String {\n let parser = Parser::new(src);\n let mut out = String::new();\n html::push_html(&mut out, parser);\n out\n}',
+ 'src/forge/blob.rs': 'use syntect::easy::HighlightLines;\n\npub fn highlight(text: &str, lang: Language) -> Markup {\n let mut hl = HighlightLines::new(lang.syntax(), &THEME);\n for line in text.lines() {\n let spans = hl.highlight_line(line, &SET).unwrap();\n emit(spans);\n }\n}',
+ 'src/hooks/mod.rs': 'mod runner;\n\npub use runner::Runner;\n\n// CI is just pre-receive + post-receive scripts\npub fn routes() -> axum::Router {\n axum::Router::new()\n .route("/:owner/:repo/hooks", get(list_runs))\n}',
+ 'src/hooks/runner.rs': 'use std::process::Command;\n\npub struct Runner {\n pub stages: Vec<Stage>,\n}\n\nimpl Runner {\n pub fn run(&self, push: &Push) -> RunResult {\n for stage in &self.stages {\n let ok = stage.exec(push)?;\n if !ok { return RunResult::Failed; }\n }\n RunResult::Passed\n }\n}',
+ 'templates/repo.html': '<article class="readme">\n {{ readme | safe }}\n</article>\n<aside class="meta">\n <section class="clone">{{ clone_url }}</section>\n</aside>',
+ 'templates/blob.html': '<div class="blob">\n <div class="gutter">{{ line_numbers }}</div>\n <pre class="code">{{ highlighted }}</pre>\n</div>',
+ 'static/paper.css': ':root {\n --paper: #faf8f4;\n --ink: #2a2518;\n}\nbody {\n background: var(--paper);\n color: var(--ink);\n font-family: "DM Sans", sans-serif;\n}\n.blob { font-family: "IBM Plex Mono", monospace; }',
+ };
+ const SIZES = {
+ 'Cargo.toml': '412 B', 'README.md': '688 B', 'LICENSE': '1.1 KB',
+ 'src/main.rs': '386 B', 'src/forge/mod.rs': '298 B', 'src/forge/repo.rs': '274 B',
+ 'src/forge/render.rs': '256 B', 'src/forge/blob.rs': '288 B',
+ 'src/hooks/mod.rs': '241 B', 'src/hooks/runner.rs': '352 B',
+ 'templates/repo.html': '178 B', 'templates/blob.html': '162 B', 'static/paper.css': '224 B',
+ };
+ const content = FILES[this.state.file] || '';
+ const lines = content.split('\n').map((t, i) => ({ n: i + 1, t: t === '' ? ' ' : t }));
+
+ // ---------- RELEASES ----------
+ const SEC = { added: 'var(--fn)', changed: 'var(--ty)', fixed: 'var(--prop)' };
+ const SECLABEL = { added: 'Added', changed: 'Changed', fixed: 'Fixed' };
+ const relDefs = [
+ { tag: 'v0.7.0', name: 'Paper Trail', date: 'June 12, 2026', sha: 'a1f3c9d', latest: true, hasDiff: true,
+ notes: { added: ['Releases timeline rail with the accent dot', 'Per-blob ETag caching'], changed: ['Render release notes with the Lora display face'] },
+ assets: [{ name: 'git-ents-0.7.0-x86_64-linux.tar.gz', size: '4.2 MB' }, { name: 'git-ents-0.7.0-aarch64-macos.tar.gz', size: '4.0 MB' }] },
+ { tag: 'v0.6.1', name: '', date: 'May 28, 2026', sha: '7c20e8b', latest: false, hasDiff: false,
+ notes: { fixed: ['Blob gutter alignment for 5-digit line numbers', 'Dark-mode flash on first paint'] },
+ assets: [{ name: 'git-ents-0.6.1-x86_64-linux.tar.gz', size: '4.1 MB' }] },
+ { tag: 'v0.6.0', name: 'Hairline', date: 'May 3, 2026', sha: '3e9a112', latest: false, hasDiff: false,
+ notes: { added: ['Two-pane file explorer'], changed: ['Hairline borders replace heavy dividers everywhere'] },
+ assets: [{ name: 'git-ents-0.6.0-x86_64-linux.tar.gz', size: '4.0 MB' }] },
+ { tag: 'v0.5.0', name: 'First Light', date: 'April 10, 2026', sha: 'bd44f0a', latest: false, hasDiff: false,
+ notes: { added: ['Auto dark mode via prefers-color-scheme', 'Gruvbox-warm syntax theme'] },
+ assets: [{ name: 'git-ents-0.5.0-x86_64-linux.tar.gz', size: '3.8 MB' }] },
+ ];
+ const releases = relDefs.map(r => ({
+ tag: r.tag, name: r.name, date: r.date, sha: r.sha, latest: r.latest, hasDiff: r.hasDiff,
+ assets: r.assets,
+ dotStyle: {
+ position: 'absolute', left: '-30px', top: '16px', width: '12px', height: '12px',
+ borderRadius: '100px', background: r.latest ? accent : 'var(--color-surface)',
+ border: '2px solid ' + (r.latest ? accent : 'var(--color-border)'), boxShadow: '0 0 0 4px var(--color-bg)',
+ },
+ sections: Object.keys(r.notes).map(k => ({ label: SECLABEL[k], color: SEC[k], items: r.notes[k] })),
+ }));
+
+ // ---------- HOOKS ----------
+ const LOGCOL = { cmd: 'var(--prop)', ok: 'var(--fn)', err: 'var(--kw)', warn: 'var(--ty)', info: muted };
+ const runDefs = [
+ { id: 142, status: 'passed', subject: 'Render release notes with the Lora display face', sha: 'a1f3c9d', branch: 'main', trigger: 'push', duration: '1m 42s', age: '3h ago',
+ stages: [['lint', 'passed', '6s'], ['build', 'passed', '48s'], ['test', 'passed', '41s'], ['deploy', 'passed', '7s']],
+ log: [['cmd', '$ cargo fmt --check && cargo clippy -- -D warnings'], ['ok', 'formatting clean · 0 warnings'], ['cmd', '$ cargo build --release'], ['info', ' Compiling git-ents v0.7.0'], ['ok', ' Finished release [optimized] in 48.2s'], ['cmd', '$ cargo test --all'], ['ok', 'test result: ok. 86 passed; 0 failed'], ['cmd', '$ ./deploy.sh'], ['ok', 'deployed git.ents.dev @ a1f3c9d']] },
+ { id: 141, status: 'failed', subject: 'Fix blob gutter alignment for 5-digit line numbers', sha: '7c20e8b', branch: 'pr/gutter', trigger: 'pre-receive', duration: '2m 05s', age: '5h ago',
+ stages: [['lint', 'passed', '6s'], ['build', 'passed', '47s'], ['test', 'failed', '1m 12s'], ['deploy', 'skipped', '—']],
+ log: [['cmd', '$ cargo test --all'], ['info', 'running 86 tests'], ['err', 'FAILED: blob::gutter::aligns_five_digits'], ['err', ' left: " 1024", right: "1024 "'], ['err', 'test result: FAILED. 85 passed; 1 failed'], ['warn', 'deploy skipped — tests did not pass']] },
+ { id: 140, status: 'passed', subject: 'Bump gix to 0.63, syntect to 5.2', sha: '3e9a112', branch: 'main', trigger: 'push', duration: '1m 31s', age: 'yesterday',
+ stages: [['lint', 'passed', '5s'], ['build', 'passed', '52s'], ['test', 'passed', '34s'], ['deploy', 'passed', '6s']],
+ log: [['cmd', '$ cargo build --release'], ['ok', ' Finished release [optimized] in 52.0s'], ['cmd', '$ cargo test --all'], ['ok', 'test result: ok. 86 passed; 0 failed']] },
+ { id: 139, status: 'running', subject: 'Cache rendered markdown per blob oid', sha: 'f02b8c1', branch: 'main', trigger: 'push', duration: '0m 38s', age: 'just now',
+ stages: [['lint', 'passed', '6s'], ['build', 'running', '32s'], ['test', 'pending', '—'], ['deploy', 'pending', '—']],
+ log: [['cmd', '$ cargo fmt --check && cargo clippy -- -D warnings'], ['ok', 'formatting clean · 0 warnings'], ['cmd', '$ cargo build --release'], ['info', ' Compiling git-ents v0.7.0 ...']] },
+ ];
+ const stColor = (s) => s === 'passed' ? 'var(--fn)' : s === 'failed' ? 'var(--kw)' : s === 'running' ? 'var(--ty)' : 'var(--color-text-muted)';
+ const runIconWrap = { flex: 'none', width: '18px', display: 'flex', alignItems: 'center', justifyContent: 'center' };
+ const runs = runDefs.map(r => ({
+ id: r.id, subject: r.subject, branch: r.branch, trigger: r.trigger, age: r.age,
+ isRunning: r.status === 'running', isPassed: r.status === 'passed', isFailed: r.status === 'failed',
+ iconWrap: runIconWrap,
+ onClick: () => this.setState({ run: r.id }),
+ style: {
+ display: 'flex', alignItems: 'center', gap: '10px', width: '100%', padding: '12px 15px',
+ border: 'none', borderBottom: '1px solid var(--color-border)', cursor: 'pointer',
+ background: this.state.run === r.id ? 'var(--color-accent-subtle)' : 'transparent',
+ borderLeft: '2px solid ' + (this.state.run === r.id ? accent : 'transparent'),
+ },
+ }));
+ const curDef = runDefs.find(r => r.id === this.state.run) || runDefs[0];
+ const cur = {
+ subject: curDef.subject, sha: curDef.sha, duration: curDef.duration,
+ isRunning: curDef.status === 'running', isPassed: curDef.status === 'passed', isFailed: curDef.status === 'failed',
+ iconWrap: { flex: 'none', width: '20px', display: 'flex', alignItems: 'center', justifyContent: 'center' },
+ stages: curDef.stages.map(s => ({ name: s[0], color: stColor(s[1]), duration: s[2] })),
+ log: curDef.log.map((l, i) => ({ n: i + 1, color: LOGCOL[l[0]], text: l[1] })),
+ };
+ const HT = [
+ ['# .gitents/hooks — CI defined as Git hooks', 'var(--cmt)'],
+ ['', text],
+ ['[pre-receive.lint]', 'var(--prop)'],
+ ['run = "cargo fmt --check && cargo clippy -- -D warnings"', text],
+ ['', text],
+ ['[post-receive.build]', 'var(--prop)'],
+ ['run = "cargo build --release"', text],
+ ['', text],
+ ['[post-receive.test]', 'var(--prop)'],
+ ['run = "cargo test --all"', text],
+ ['needs = ["build"]', text],
+ ['', text],
+ ['[post-receive.deploy]', 'var(--prop)'],
+ ['run = "./deploy.sh"', text],
+ ['needs = ["test"]', text],
+ ['when = "branch == \'main\'"', text],
+ ];
+ const hooksToml = HT.map(l => ({ t: l[0] === '' ? ' ' : l[0], color: l[1] }));
+
+ // ---------- ISSUES ----------
+ const LBLCOL = { bug: 'var(--kw)', enhancement: 'var(--fn)', question: 'var(--prop)', docs: 'var(--ty)' };
+ const issueDefs = [
+ { num: 58, title: 'Blob gutter misaligns on files past 9,999 lines', state: 'open', labels: ['bug'], meta: 'opened 2 days ago by mira', comments: 4 },
+ { num: 57, title: 'Add ETag caching for rendered README blobs', state: 'open', labels: ['enhancement'], meta: 'opened 4 days ago by elm', comments: 2 },
+ { num: 55, title: 'How do I configure a post-receive deploy step?', state: 'open', labels: ['question'], meta: 'opened 6 days ago by toml-fan', comments: 7 },
+ { num: 54, title: 'Dark mode flashes white on first paint', state: 'closed', labels: ['bug'], meta: 'closed 9 days ago by elm', comments: 3 },
+ { num: 52, title: 'Timeline rail for the releases page', state: 'closed', labels: ['enhancement'], meta: 'closed 12 days ago by mira', comments: 1 },
+ { num: 49, title: 'Document the hooks.toml schema', state: 'closed', labels: ['docs'], meta: 'closed 18 days ago by elm', comments: 0 },
+ ];
+ const openCount = issueDefs.filter(i => i.state === 'open').length;
+ const closedCount = issueDefs.filter(i => i.state === 'closed').length;
+ let filtered = issueDefs.filter(i => i.state === this.state.itab);
+ if (this.state.ilabel !== 'all') filtered = filtered.filter(i => i.labels.includes(this.state.ilabel));
+ if (this.state.iq.trim()) { const q = this.state.iq.toLowerCase(); filtered = filtered.filter(i => i.title.toLowerCase().includes(q)); }
+ const issues = filtered.map(i => ({
+ num: i.num, title: i.title, meta: i.meta, comments: i.comments, hasComments: i.comments > 0,
+ isOpen: i.state === 'open', isClosed: i.state === 'closed',
+ statusColor: i.state === 'open' ? 'var(--fn)' : 'var(--color-text-muted)',
+ labels: i.labels.map(l => ({ name: l, color: LBLCOL[l] || muted })),
+ }));
+ const itabBtn = (active) => ({
+ display: 'flex', alignItems: 'center', gap: '7px', background: 'none', border: 'none',
+ cursor: 'pointer', fontSize: '.84rem', fontWeight: active ? 600 : 500,
+ color: active ? text : muted, fontFamily: 'var(--font-body)', padding: 0,
+ });
+ const issueTabs = [
+ { id: 'open', label: 'Open', count: openCount },
+ { id: 'closed', label: 'Closed', count: closedCount },
+ ].map(f => ({
+ label: f.label, count: f.count, isOpen: f.id === 'open', isClosed: f.id === 'closed',
+ onClick: () => this.setState({ itab: f.id }), style: itabBtn(this.state.itab === f.id),
+ }));
+ const chipStyle = (active) => ({
+ padding: '5px 13px', fontSize: '.76rem', fontWeight: 500, cursor: 'pointer',
+ borderRadius: 'var(--radius-pill)', fontFamily: 'var(--font-body)',
+ color: active ? 'var(--color-bg)' : muted,
+ background: active ? accent : 'var(--color-surface)',
+ border: '1px solid ' + (active ? accent : 'var(--color-border)'),
+ });
+ const labelChips = [
+ { id: 'all', label: 'All' }, { id: 'bug', label: 'bug' },
+ { id: 'enhancement', label: 'enhancement' }, { id: 'question', label: 'question' },
+ ].map(c => ({ label: c.label, onClick: () => this.setState({ ilabel: c.id }), style: chipStyle(this.state.ilabel === c.id) }));
+
+ // ---------- SETTINGS ----------
+ const featDefs = [
+ { key: 'issues', label: 'Bug reports', desc: 'Let collaborators file and track issues.' },
+ { key: 'releases', label: 'Releases', desc: 'Publish tagged releases with assets.' },
+ { key: 'hooks', label: 'Hooks (CI)', desc: 'Run pre-receive and post-receive stages on push.' },
+ { key: 'wiki', label: 'Wiki', desc: 'A companion docs space, rendered as paper.' },
+ ];
+ const features = featDefs.map(f => {
+ const on = this.state.features[f.key];
+ return {
+ label: f.label, desc: f.desc,
+ onClick: () => this.setState(s => ({ features: { ...s.features, [f.key]: !s.features[f.key] } })),
+ trackStyle: {
+ flex: 'none', width: '42px', height: '24px', borderRadius: '100px', cursor: 'pointer',
+ border: 'none', padding: 0, position: 'relative', transition: 'background .18s',
+ background: on ? accent : 'var(--color-border)',
+ },
+ knobStyle: {
+ position: 'absolute', top: '3px', left: on ? '21px' : '3px', width: '18px', height: '18px',
+ borderRadius: '100px', background: '#fff', transition: 'left .18s', boxShadow: '0 1px 2px #00000033',
+ },
+ };
+ });
+ const visBtn = (active) => ({
+ display: 'flex', alignItems: 'center', gap: '11px', width: '100%', padding: '12px 14px',
+ background: active ? 'var(--color-accent-subtle)' : 'var(--color-bg)', cursor: 'pointer',
+ border: '1px solid ' + (active ? accent : 'var(--color-border)'), borderRadius: '8px', textAlign: 'left',
+ });
+ const visOptions = [
+ { id: 'public', label: 'Public', desc: 'Anyone can read; the README and tree are served openly.' },
+ { id: 'private', label: 'Private', desc: 'Only invited collaborators can clone or browse.' },
+ ].map(v => ({
+ label: v.label, desc: v.desc, on: this.state.visibility === v.id,
+ onClick: () => this.setState({ visibility: v.id }), style: visBtn(this.state.visibility === v.id),
+ radioStyle: {
+ flex: 'none', width: '16px', height: '16px', borderRadius: '100px',
+ border: '2px solid ' + (this.state.visibility === v.id ? accent : 'var(--color-border)'),
+ display: 'flex', alignItems: 'center', justifyContent: 'center',
+ },
+ }));
+
+ return {
+ tabs,
+ isOverview: tab === 'overview', isFiles: tab === 'files', isReleases: tab === 'releases',
+ isHooks: tab === 'hooks', isIssues: tab === 'issues', isSettings: tab === 'settings',
+ // overview
+ onClone: () => this.copy('git.ents.dev/git-ents/git-ents.git', 'clone'),
+ cloneCopied: this.state.copied === 'clone', notCloneCopied: this.state.copied !== 'clone',
+ // files
+ tree, lines, currentPath: this.state.file, lineCount: lines.length, currentSize: SIZES[this.state.file] || '—',
+ onCopyFile: () => this.copy(content, 'file'),
+ fileCopied: this.state.copied === 'file', notFileCopied: this.state.copied !== 'file',
+ // releases
+ releases,
+ // hooks
+ runs, cur, hooksToml,
+ // issues
+ issues, issueTabs, labelChips, iq: this.state.iq,
+ onSearch: (e) => this.setState({ iq: e.target.value }),
+ noIssues: issues.length === 0,
+ // settings
+ features, visOptions,
+ };
+ }
+}
+</script>
+</body>
+</html>
design_handoff_git_ents/README.md
@@ -1,0 +1,275 @@
+# Handoff: Git Ents — repository web UI
+
+## Overview
+
+A web presence for a self-hosted Git forge ("git-ents").
+It is a single repository page that deliberately centers the things a code editor is *bad* at: a large, editorial rendered README; a wander-able file tree; releases as a changelog; CI defined as Git hooks with run logs; bug reports; and repository configuration.
+
+The visual language is "Warm Ledger" — technical minimalism with warmth: a cream-and-gold paper palette, serif display type (Lora) over a geometric sans (DM Sans), monospace (IBM Plex Mono) for anything code-like, hairline borders, barely-there shadows, and a signature 3rem gold accent-underline under section headers.
+Light and dark are equals and switch automatically with the OS setting.
+
+## About the Design Files
+
+The file in this bundle (`Git Ents.dc.html`) is a **design reference created in HTML** — a working prototype showing intended look and behavior, **not production code to copy directly**.
+It is authored as a "Design Component" (a streaming single-file format) and will not run standalone in a normal app.
+
+The task is to **recreate this design in your target codebase** using its established framework and patterns (React, Vue, Svelte, server-rendered templates, etc.).
+If no front-end environment exists yet, pick the most appropriate one for the project.
+Note: the prototype itself is client-rendered, but the product it depicts is explicitly **server-rendered, no client JS** — if you are building the real forge, prefer a server-side HTML approach (the mock's interactivity, e.g. tab switching, would become separate page routes).
+
+## Fidelity
+
+**High-fidelity.**
+Final colors, typography, spacing, and interactions.
+Recreate the UI faithfully using your codebase's libraries and patterns.
+Exact hex values, font sizes, and measurements are documented below and in the source file.
+
+---
+
+## Design Tokens
+
+All styling reads from CSS custom properties.
+Light is the base; dark overrides under `@media (prefers-color-scheme: dark)`.
+**Never hardcode hex in components — read from these.**
+
+### Color
+
+| Token | Light | Dark | Used for |
+|---|---|---|---|
+| `--color-bg` | `#faf8f4` | `#171510` | page background |
+| `--color-surface` | `#ffffff` | `#211f17` | cards, raised panels |
+| `--color-text` | `#2a2518` | `#ede8d8` | body text |
+| `--color-text-muted` | `#8a7e6a` | `#a89e88` | secondary text, metadata |
+| `--color-link` | `#b07d10` | `#d4a030` | links |
+| `--color-link-hover` | `#96690a` | `#e4b850` | link hover |
+| `--color-border` | `#ede9de` | `#383324` | hairlines, card borders |
+| `--color-code-bg` | `#f5f3eb` | `#211f17` | code, card headers, gutters |
+| `--color-accent` | `#b07d10` | `#d4a030` | gold accent: rules, active states |
+| `--color-accent-subtle` | `#b07d100f` | `#d4a03012` | accent tint backgrounds |
+
+Shadows: `--shadow-sm` = `0 1px 3px #0000000d` (light) / `0 1px 3px #00000040` (dark); `--shadow-md` = `0 4px 16px #0000000f` / `0 4px 16px #0000004d`.
+
+Page background has a faint top-center radial glow in the accent color: `radial-gradient(58rem 30rem at 50% -10rem, var(--color-accent-subtle), transparent 72%)`, `background-attachment: fixed`.
+
+### Syntax / categorical palette (Gruvbox-warm) — light / dark
+
+| Token | Light | Dark | Role |
+|---|---|---|---|
+| `--kw` | `#9d0006` | `#fb4934` | keyword / error / danger |
+| `--fn` | `#427b58` | `#8ec07c` | function / success / "passed" |
+| `--ty` | `#b57614` | `#fabd2f` | type / "running" / Rust lang dot |
+| `--str` | `#79740e` | `#b8bb26` | string |
+| `--num` | `#8f3f71` | `#d3869b` | number / constant |
+| `--cmt` | `#9c8f74` | `#928374` | comment |
+| `--prop` | `#076678` | `#83a598` | property / link / TOML section headers |
+
+Diff tints: `--diff-add` = `#4e9a0622` / `#b8bb2620`; `--diff-rem` = `#cc241d22` / `#fb493420`.
+
+### Typography
+
+Loaded from Google Fonts.
+
+- `--font-body`: `"DM Sans", system-ui, sans-serif` — weights 400–700.
+ UI + body.
+- `--font-display`: `"Lora", Georgia, serif` — weights 500–700, `letter-spacing: -.01em`.
+ Headings (the signature move).
+- `--font-mono`: `"IBM Plex Mono", ui-monospace, Menlo, monospace` — weights 400–600.
+ Code, repo/file names, labels, logos, metadata.
+
+Base: `font-size: 17px`, `line-height: 1.7`, antialiased.
+Page section headings ~`1.5rem` Lora 700.
+README h1 `2.4rem` Lora 700.
+Uppercase mono micro-labels at `.7rem` with `.06em` letter-spacing for card headers and eyebrows.
+
+### Shape & spacing
+
+`--radius-sm: 10px` (cards, inputs, buttons); list rows / small controls use `7–8px`; `--radius-pill: 100px` (badges, branch tags, toggles).
+App content max-width `78rem`, side padding `1.5rem`.
+README reading column capped ~`44rem`.
+Generous vertical rhythm; sections separated by hairlines + the accent underline, not heavy dividers.
+
+---
+
+## Global Chrome
+
+### Sticky nav (height 58px)
+
+- `position: sticky; top: 0; z-index: 50`.
+ Background `color-mix(in srgb, var(--color-bg) 82%, transparent)` with `backdrop-filter: blur(10px)`.
+ Bottom hairline.
+- Left: mono wordmark "git-ents" with a small tree/shield glyph in accent; hover → accent; `white-space: nowrap`.
+- Center: search input (`max-width: 24rem`), placeholder "Jump to file or symbol", leading search icon, focus border → accent. (Decorative in mock.)
+- Right: "Explore", "Docs" muted links (hover → accent), and a 30px circular avatar chip showing initials ("el") in accent on `--color-accent-subtle`.
+
+### Repo header band
+
+- Path line (mono, `1.18rem`): folder icon · `git-ents` (muted link) · `/` · `git-ents` (accent, weight 600) · **branch pill** (`main`, accent text on `--color-accent-subtle`, thin accent border, pill, with branch icon) · **`Public` pill** (muted on `--color-code-bg`).
+- Description paragraph (`.98rem`, max ~40rem).
+- Topic chips: `rust`, `git-forge`, `self-hosted`, `html-first` — mono `.72rem`, link color on `--color-accent-subtle`, pill.
+- Right side: `Watch` and `Star 128` buttons — surface bg, hairline border, `--shadow-sm`, hover border → accent, leading icons.
+
+### Tab bar
+
+Row of buttons over a `1px` bottom border.
+Each tab: `10px 14px`, `.88rem`.
+Active tab = `--color-text` + weight 600 + a full-width **2px accent underline** pinned to `bottom: -1px`.
+Idle = `--color-text-muted`, hover → `--color-text`.
+Tabs: `Overview`, `Files`, `Releases` (count pill "4"), `Hooks` (green status dot), `Issues` (count pill "3"), `Settings`.
+Count pills are mono `.68rem`, muted, on `--color-code-bg` with hairline border.
+
+### Footer
+
+Top hairline, centered mono `.72rem` muted: "git-ents · served as paper-grain HTML · no JavaScript required".
+
+---
+
+## Screens / Views
+
+### 1. Overview (default tab)
+
+**Purpose:** read the project at a glance — the README is the star.
+**Layout:** CSS grid, `grid-template-columns: 1fr 19rem`, gap `34px`, `align-items: start`.
+
+- **README card** (left): surface, hairline border, `--radius-sm`, `--shadow-sm`, overflow hidden.
+ - Header strip: `--color-code-bg`, bottom hairline, mono uppercase `.72rem` muted label "README.md" with a file icon.
+ - Body: padding `40px 48px 52px`, `max-width: 44rem`.
+ - `h1` "git-ents" — Lora 700, `2.4rem`, `letter-spacing: -.02em`.
+ - Italic Lora subtitle in muted (`1.18rem`).
+ - Badge row: pills (build passing — with `--fn` dot, `v0.7.0` in `--ty`, `MIT` in `--prop`, `rust 1.78+` muted), mono `.7rem` on `--color-code-bg` with hairline.
+ - Body paragraphs; `h2` section headers (Lora 600, `1.4rem`) each followed by a **3rem × 2px accent underline**.
+ - Bulleted lists; inline `code` on `--color-code-bg`, `5px` radius.
+ - Two code blocks on `--color-code-bg` with hairline + `--radius-sm`: an install block (shell, `$` prompts muted, comments in `--cmt`) and a syntax-highlighted Rust snippet using the syntax palette (`--kw` keywords, `--fn` functions, `--ty` types, `--cmt` comments).
+- **Aside** (right, `position: sticky; top: 78px`, column, gap `18px`): four cards, each with a mono uppercase header strip + hairline:
+ - **Clone**: readonly mono input (`git.ents.dev/git-ents/git-ents.git`) on `--color-code-bg` + 34px copy button; on copy → green check for 1.4s.
+ - **About**: Rust·MIT (with `--ty` dot), stars/forks, "Updated 3 hours ago".
+ - **Releases**: count "4" in accent; latest `v0.7.0` row with tag icon + "Latest" pill + "Paper Trail — released 9 days ago".
+ - **Languages**: thin stacked bar (Rust 86% `--ty` / HTML 9% `--fn` / CSS 5% `--prop`) + legend with color dots.
+
+### 2. Files
+
+**Purpose:** browse the repo tree and read files.
+**Layout:** grid `17rem 1fr` inside one bordered, shadowed, rounded card; min-height `30rem`; overflow hidden.
+
+- **Left tree pane:** `--color-bg`, right hairline.
+ Header strip = mono "main" with branch icon.
+ - Rows: mono `.79rem`, `padding: 4px 8px`, indent `8 + depth*15` px.
+ Folders show a chevron (rotates 90° when open, `.15s`) + accent folder icon; files show a spacer + muted file icon.
+ Hover bg → `--color-code-bg`.
+ **Selected file**: bg `--color-accent-subtle`, text → accent, weight 600.
+ - Tree data: `.gitents/hooks.toml`; `src/{main.rs, forge/{mod,repo,render,blob}.rs, hooks/{mod,runner}.rs}`; `templates/{repo,blob}.html`; `static/paper.css`; `Cargo.toml`; `LICENSE`; `README.md`.
+ Default-expanded: `.gitents`, `src`, `src/forge`.
+ Default selection: `Cargo.toml`.
+- **Right blob pane:** column.
+ - Header: `--color-code-bg`, bottom hairline.
+ Left = mono current path.
+ Right = mono meta ("N lines · SIZE") + a Copy button (→ green "Copied" check for 1.4s).
+ - Body: grid `auto 1fr`, scrollable.
+ Left = line-number gutter on `--color-code-bg`, right hairline, mono `.78rem`, muted at `opacity .6`, right-aligned.
+ Right = code lines, mono `.78rem`, `line-height: 1.55`, `white-space: pre`, horizontal scroll. (Mock renders plain mono per file; real impl should syntax-highlight server-side.)
+
+### 3. Releases
+
+**Purpose:** read the changelog.
+**Layout:** Page header "Releases" (Lora `1.5rem`) + 3rem accent underline.
+Then a **timeline rail**: container `padding-left: 30px` with a `2px` vertical line at `left: 5px` in `--color-border`.
+
+- Each release: a **dot** absolutely positioned at `left: -30px; top: 16px`, 12px, pill, with a `4px` ring in `--color-bg` (`box-shadow`).
+ Latest = filled accent; others = surface fill with border ring.
+- Release card (surface, hairline, rounded, shadow):
+ - Header row (bottom hairline): tag icon (accent) + mono tag (`v0.7.0`, accent, weight 600) + Lora name ("Paper Trail") + optional "Latest" pill (`--fn`); right-aligned date (muted).
+ - Body: note sections grouped **Added / Changed / Fixed**, each with a mono uppercase eyebrow colored `--fn` / `--ty` / `--prop` respectively, then a bullet list (`.88rem`).
+ The latest release also shows a 2-line **diff** block (added line on `--diff-add`, removed line on `--diff-rem`, mono `.76rem`).
+ - Footer (top hairline): asset download links (mono `.74rem`, link color, download icon, size in muted) + right-aligned mono sha with a commit glyph.
+- Four releases: `v0.7.0 Paper Trail` (latest, June 12 2026), `v0.6.1` (May 28), `v0.6.0 Hairline` (May 3), `v0.5.0 First Light` (Apr 10).
+
+### 4. Hooks
+
+**Purpose:** CI defined as Git hooks — view runs and logs.
+**Layout:** Page header "Hooks" + underline + intro paragraph (notes CI is plain `pre-receive`/`post-receive`, config in `.gitents/hooks.toml`).
+Then grid `20rem 1fr`, gap `20px`, `align-items: start`.
+
+- **Run list** (left card): header "Recent runs".
+ Each run is a button row (bottom hairline): a status icon (passed = `--fn` filled check-circle; failed = `--kw` x-circle; running = `--ty` spinner, `@keyframes spin` 1s linear) + subject (truncated) + mono submeta "#id · branch · trigger" + right-aligned age.
+ **Selected run**: bg `--color-accent-subtle` + `2px` left accent border.
+ Hover → `--color-code-bg`.
+- **Run detail** (right column, gap 18px):
+ - Detail card: header (status icon + Lora subject + right-aligned mono "sha · duration").
+ **Stages** row (bottom hairline): pills on `--color-code-bg` each = a status dot (`--fn`/`--kw`/`--ty`/muted) + mono stage name + mono duration (`lint`, `build`, `test`, `deploy`).
+ **Log viewer**: `--color-code-bg`, mono `.76rem`, `line-height: 1.7`, `white-space: pre`, horizontal scroll.
+ Each line = muted line number + text colored by level: cmd→`--prop`, ok→`--fn`, err→`--kw`, warn→`--ty`, info→muted.
+ - Config card: mono header "`.gitents/hooks.toml`" on `--color-code-bg`; body renders the TOML with section headers (`[pre-receive.lint]` etc.) in `--prop`, comments in `--cmt`, rest in text color.
+- Four runs (#142 passed, #141 failed, #140 passed, #139 running).
+ Default selection: #142.
+
+### 5. Issues ("Bug reports")
+
+**Purpose:** file and track bugs.
+**Layout:** Header row: "Bug reports" (Lora `1.5rem`, `white-space: nowrap`) + underline on the left; "New issue" button on the right (solid accent bg, `--color-bg` text, weight 600, plus icon, hover → `--color-link-hover`).
+
+- **Filter row:** search input (flex-grow, leading icon, `onInput` filters by title, focus border → accent) + **label chips** (`All`, `bug`, `enhancement`, `question`).
+ Active chip = solid accent bg + `--color-bg` text; idle = surface + muted + hairline.
+ Pill shape, `.76rem`.
+- **List card:** header strip on `--color-code-bg` with **Open / Closed** sub-tabs (each = status icon + label + count; active = text + weight 600).
+ Each issue row (bottom hairline, hover → `--color-code-bg`): status icon (open = `--fn` open-issue dot-circle; closed = muted check-circle) + title link (hover → accent) + label pills (mono `.66rem`, colored per label: bug→`--kw`, enhancement→`--fn`, question→`--prop`, docs→`--ty`, on `--color-code-bg` + hairline) + mono submeta "#num opened/closed … by author" + right-aligned comment count with speech icon (hidden if 0).
+ Empty state: centered muted "No bug reports match these filters."
+- Six issues (3 open, 3 closed) covering bug/enhancement/question/docs labels.
+
+### 6. Settings ("Repository settings")
+
+**Purpose:** configure the repo.
+**Layout:** `max-width: 46rem`.
+Page header + underline.
+Four stacked cards, each with a mono uppercase header strip + hairline.
+
+- **General:** labeled fields — repo name (mono input), description (textarea, body font, vertical resize), default branch (`select`: main/develop/trunk).
+ Inputs: `--color-bg` bg, hairline, `8px` radius, focus border → accent.
+- **Features:** four rows (Bug reports, Releases, Hooks (CI), Wiki), each = label + description + a **toggle switch** (42×24 track, pill; on = accent bg + knob at `left: 21px`; off = `--color-border` + knob at `left: 3px`; 18px white knob, `.18s` transition).
+ Defaults: issues/releases/hooks on, wiki off.
+- **Visibility:** two radio rows (Public / Private) — each a button with a 16px radio (filled accent dot when selected), title + description; selected row = `--color-accent-subtle` bg + accent border.
+ Default: Public.
+- **Danger zone:** card with a `--kw` border + `--kw` header.
+ Two rows: "Archive this repository" (outline `--kw` button, hover bg `--diff-rem`) and "Delete this repository" (solid `--kw` button, white text).
+
+---
+
+## Interactions & Behavior
+
+- **Tabs:** clicking a tab swaps the content region; active underline + weight change.
+ In a server-rendered build these become separate routes/pages.
+- **File tree:** folder rows toggle expand/collapse (chevron rotates `.15s`); file rows select and load the blob into the right pane.
+- **Run list:** selecting a run swaps the detail card (stages + log + the run's own logs).
+ Running runs show an infinite spinner.
+- **Issue filters:** Open/Closed sub-tabs, label chips, and the search box compose (AND) to filter the list live; empty result → empty-state message.
+- **Copy buttons** (clone URL, blob): write to clipboard, then show a green check / "Copied" for ~1.4s before reverting.
+- **Settings toggles & radios:** flip local state with animated knob / radio fill.
+- **Hover motion** is small (≤3px) and quick (`.15s–.22s`); hover states lean on the accent and the subtle tint, rarely on shadow.
+ List-row hover tints to `--color-code-bg`.
+- **Dark mode:** automatic via `prefers-color-scheme`; every token has a dark value — no manual toggle.
+
+## State Management
+
+Local UI state only (no data fetching in the mock):
+
+- `tab` — active top-level tab.
+- `exp` — map of expanded folder paths (file tree).
+- `file` — selected file path (blob view).
+- `run` — selected CI run id.
+- `itab` (open|closed), `ilabel` (all|bug|enhancement|question), `iq` (search string) — issue filters.
+- `copied` — transient key marking which copy button is showing its confirmation.
+- `features` — map of feature toggles; `visibility` — public|private.
+
+In a real forge most of this maps to **server routes + query params** (tab/path/run/ filters in the URL) since the product is server-rendered with no client JS; only the copy-to-clipboard affordance strictly needs a sprinkle of JS.
+
+## Assets
+
+- **Fonts:** DM Sans, Lora, IBM Plex Mono (Google Fonts).
+- **Icons:** inline 16px line icons in the Octicon idiom, `fill: currentColor` so they inherit accent/muted from context.
+ No emoji in chrome.
+ Recreate with your icon set (e.g. Octicons / Lucide) at equivalent weights.
+- No raster images; the avatar is an initials chip.
+
+## Files
+
+- `Git Ents.dc.html` — the full hi-fi prototype (all six tabs, all interactions).
+ Open it in a browser to inspect exact markup, inline styles, and the logic that computes the lists/states.
+ All design tokens are defined in its `:root` block (and the dark `@media` override) near the top of the file.