git-ents.gitmain
⌘K
foforge
commit d53ff40
chore: remove design scaffolding
Joseph D. Carpinelli · 1 month ago

Reviews

No reviews of this commit yet — record a verdict below.

Start a review

verdict

design_handoff_git_ents/Git Ents.dc.html @@ -1,895 +1,0 @@ -<!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&#8209;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&#8209;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: &amp;<span style="color:var(--ty)">Blob</span>) -&gt; <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>(&amp;blob.path); - html! { - pre.paper { (<span style="color:var(--fn)">highlight</span>(&amp;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="{&quot;$preview&quot;:{&quot;width&quot;:&quot;100%&quot;},&quot;defaultTab&quot;:{&quot;editor&quot;:&quot;enum&quot;,&quot;options&quot;:[&quot;overview&quot;,&quot;files&quot;,&quot;releases&quot;,&quot;hooks&quot;,&quot;issues&quot;,&quot;settings&quot;],&quot;default&quot;:&quot;overview&quot;,&quot;tsType&quot;:&quot;string&quot;},&quot;collapseTree&quot;:{&quot;editor&quot;:&quot;boolean&quot;,&quot;default&quot;:false,&quot;tsType&quot;:&quot;boolean&quot;}}"> -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,275 +1,0 @@ -# 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.