OOKNET                             [ /  search the index  ]  
──────────────────────────────────────────────────────────────────────────────────────
══════════════════════════════════════════════════════════════════════════════════════
OOKNET   [ /  search  ]  
────────────────────────────────────────────────
════════════════════════════════════════════════
 
HASH      6b9265c18b2e
DATE      2026-07-18
SUBJECT   web: git pages grow branches, per-file diffs, file history, and the site's
          own markdown renderer
FILES     18 CHANGED
HASH      6b9265c18b2e
DATE      2026-07-18
SUBJECT   web: git pages grow branches, per-file
          diffs, file history, and the site's
          own markdown renderer
FILES     18 CHANGED
 

diff --git a/.gitignore b/.gitignore
index e43b0f9..f7beecb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,4 @@
 .DS_Store
+
+# machine-local planning docs
+.internal/

diff --git a/.gitignore b/.gitignore
index e43b0f9..f7beecb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,4 @@
 .DS_Store
+
+# machine-local planning docs
+.internal/
 

diff --git a/ooknet-design/package-lock.json b/ooknet-design/package-lock.json
index d58ec8a..4f60302 100644
--- a/ooknet-design/package-lock.json
+++ b/ooknet-design/package-lock.json
@@ -11,7 +11,11 @@
         "@fontsource/cascadia-mono": "^5.2.3",
         "@fontsource/ibm-plex-mono": "^5.2.7",
         "astro": "^6.1.9",
-        "marked": "^18.0.6"
+        "rehype-stringify": "^10.0.1",
+        "remark-gfm": "^4.0.1",
+        "remark-parse": "^11.0.0",
+        "remark-rehype": "^11.1.2",
+        "unified": "^11.0.5"
       },
       "devDependencies": {
         "sass": "^1.99.0",
@@ -3292,18 +3296,6 @@
         "url": "https://github.com/sponsors/wooorm"
       }
     },
-    "node_modules/marked": {
-      "version": "18.0.6",
-      "resolved": "https://registry.npmjs.org/marked/-/marked-18.0.6.tgz",
-      "integrity": "sha512-MrV5puXBfuiy6wl6DLaq3BtIJQAJToAd5zt/ZKhRfGRAuFPALE7/4Y
7jnxRQoEgK/pBgurGqLyAuRgZ2xOjr6w==",
-      "license": "MIT",
-      "bin": {
-        "marked": "bin/marked.js"
-      },
-      "engines": {
-        "node": ">= 20"
-      }
-    },
     "node_modules/mdast-util-definitions": {
       "version": "6.0.0",
       "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util
-definitions-6.0.0.tgz",

diff --git a/ooknet-design/package-lock.json
 b/ooknet-design/package-lock.json
index d58ec8a..4f60302 100644
--- a/ooknet-design/package-lock.json
+++ b/ooknet-design/package-lock.json
@@ -11,7 +11,11 @@
         "@fontsource/cascadia-mono": "^5.2.
3",
         "@fontsource/ibm-plex-mono": "^5.2.
7",
         "astro": "^6.1.9",
-        "marked": "^18.0.6"
+        "rehype-stringify": "^10.0.1",
+        "remark-gfm": "^4.0.1",
+        "remark-parse": "^11.0.0",
+        "remark-rehype": "^11.1.2",
+        "unified": "^11.0.5"
       },
       "devDependencies": {
         "sass": "^1.99.0",
@@ -3292,18 +3296,6 @@
         "url": "https://github.com/sponsors
/wooorm"
       }
     },
-    "node_modules/marked": {
-      "version": "18.0.6",
-      "resolved": "https://registry.npmjs.o
rg/marked/-/marked-18.0.6.tgz",
-      "integrity": "sha512-MrV5puXBfuiy6wl6
DLaq3BtIJQAJToAd5zt/ZKhRfGRAuFPALE7/4Y7jnxRQ
oEgK/pBgurGqLyAuRgZ2xOjr6w==",
-      "license": "MIT",
-      "bin": {
-        "marked": "bin/marked.js"
-      },
-      "engines": {
-        "node": ">= 20"
-      }
-    },
     "node_modules/mdast-util-definitions": 
{
       "version": "6.0.0",
       "resolved": "https://registry.npmjs.o
rg/mdast-util-definitions/-/mdast-util-defin
itions-6.0.0.tgz",
 

diff --git a/ooknet-design/package.json b/ooknet-design/package.json
index 112a66e..3dcd473 100644
--- a/ooknet-design/package.json
+++ b/ooknet-design/package.json
@@ -16,7 +16,11 @@
     "@fontsource/cascadia-mono": "^5.2.3",
     "@fontsource/ibm-plex-mono": "^5.2.7",
     "astro": "^6.1.9",
-    "marked": "^18.0.6"
+    "rehype-stringify": "^10.0.1",
+    "remark-gfm": "^4.0.1",
+    "remark-parse": "^11.0.0",
+    "remark-rehype": "^11.1.2",
+    "unified": "^11.0.5"
   },
   "devDependencies": {
     "sass": "^1.99.0",

diff --git a/ooknet-design/package.json b/oo
knet-design/package.json
index 112a66e..3dcd473 100644
--- a/ooknet-design/package.json
+++ b/ooknet-design/package.json
@@ -16,7 +16,11 @@
     "@fontsource/cascadia-mono": "^5.2.3",
     "@fontsource/ibm-plex-mono": "^5.2.7",
     "astro": "^6.1.9",
-    "marked": "^18.0.6"
+    "rehype-stringify": "^10.0.1",
+    "remark-gfm": "^4.0.1",
+    "remark-parse": "^11.0.0",
+    "remark-rehype": "^11.1.2",
+    "unified": "^11.0.5"
   },
   "devDependencies": {
     "sass": "^1.99.0",
 

diff --git a/ooknet-design/src/components/CodeFrame/CodeFrame.astro b/ooknet-desig
n/src/components/CodeFrame/CodeFrame.astro
new file mode 100644
index 0000000..4c67580
--- /dev/null
+++ b/ooknet-design/src/components/CodeFrame/CodeFrame.astro
@@ -0,0 +1,46 @@
+---
+// Framed code block for content outside markdown - blobs and per-file
+// commit diffs. Same markup as the pipeline's code fences, so Prose
+// styles and the copy handler apply unchanged.
+import { G } from "../../lib/ascii";
+import { FRAME_W, MOBILE_FRAME_W } from "../../lib/config";
+import { layoutDiff } from "../Diff/Diff";
+import { frameLabel, type Props } from "./CodeFrame";
+import "../Prose/Prose.scss";
+
+const { code, label, diff = false } = Astro.props as Props;
+const raw = code.replace(/\n+$/, "");
+
+const esc = (s: string) =>
+  s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
+const frame = (s: string) =>
+  `<span class="code-frame" aria-hidden="true">${esc(s)}</span>`;
+
+function renderCode(w: number): string {
+  const lbl = frameLabel(label, w);
+  const parts: string[] = [
+    frame(G.TL + G.H + ` ${lbl} ` + G.H.repeat(Math.max(0, w - 12 - lbl.length - 
2))),
+    '<button type="button" class="code-copy" aria-label="Copy code">[ COPY ]</but
ton>',
+    frame(G.H + G.TR),
+    "\n",
+  ];
+  for (const r of layoutDiff(code, w, diff)) {
+    parts.push(frame(G.V + " "));
+    parts.push(r.cls ? `<span class="${r.cls}">${esc(r.text)}</span>` : esc(r.tex
t));
+    parts.push(frame(" ".repeat(r.pad) + " " + G.V));
+    parts.push("\n");
+  }
+  parts.push(frame(G.BL + G.H.repeat(w - 2) + G.BR));
+  return `<code>${parts.join("")}</code>`;
+}
+
+const variants = [
+  { cls: "frame-wide", w: FRAME_W },
+  { cls: "frame-narrow", w: MOBILE_FRAME_W },
+];
+---
+<div class="code-frames prose">
+  {variants.map(({ cls, w }) => (
+    <pre class={"ascii code-block " + cls} data-raw-code={raw} set:html={renderCo
de(w)} />
+  ))}
+</div>

diff --git a/ooknet-design/src/components/Co
deFrame/CodeFrame.astro b/ooknet-design/src/
components/CodeFrame/CodeFrame.astro
new file mode 100644
index 0000000..4c67580
--- /dev/null
+++ b/ooknet-design/src/components/CodeFrame
/CodeFrame.astro
@@ -0,0 +1,46 @@
+---
+// Framed code block for content outside ma
rkdown - blobs and per-file
+// commit diffs. Same markup as the pipelin
e's code fences, so Prose
+// styles and the copy handler apply unchan
ged.
+import { G } from "../../lib/ascii";
+import { FRAME_W, MOBILE_FRAME_W } from "..
/../lib/config";
+import { layoutDiff } from "../Diff/Diff";
+import { frameLabel, type Props } from "./C
odeFrame";
+import "../Prose/Prose.scss";
+
+const { code, label, diff = false } = Astro
.props as Props;
+const raw = code.replace(/\n+$/, "");
+
+const esc = (s: string) =>
+  s.replace(/&/g, "&amp;").replace(/</g, "&
lt;").replace(/>/g, "&gt;");
+const frame = (s: string) =>
+  `<span class="code-frame" aria-hidden="tr
ue">${esc(s)}</span>`;
+
+function renderCode(w: number): string {
+  const lbl = frameLabel(label, w);
+  const parts: string[] = [
+    frame(G.TL + G.H + ` ${lbl} ` + G.H.rep
eat(Math.max(0, w - 12 - lbl.length - 2))),
+    '<button type="button" class="code-copy
" aria-label="Copy code">[ COPY ]</button>',
+    frame(G.H + G.TR),
+    "\n",
+  ];
+  for (const r of layoutDiff(code, w, diff)
) {
+    parts.push(frame(G.V + " "));
+    parts.push(r.cls ? `<span class="${r.cl
s}">${esc(r.text)}</span>` : esc(r.text));
+    parts.push(frame(" ".repeat(r.pad) + " 
" + G.V));
+    parts.push("\n");
+  }
+  parts.push(frame(G.BL + G.H.repeat(w - 2)
 + G.BR));
+  return `<code>${parts.join("")}</code>`;
+}
+
+const variants = [
+  { cls: "frame-wide", w: FRAME_W },
+  { cls: "frame-narrow", w: MOBILE_FRAME_W 
},
+];
+---
+<div class="code-frames prose">
+  {variants.map(({ cls, w }) => (
+    <pre class={"ascii code-block " + cls} 
data-raw-code={raw} set:html={renderCode(w)}
 />
+  ))}
+</div>
 

diff --git a/ooknet-design/src/components/CodeFrame/CodeFrame.test.ts b/ooknet-des
ign/src/components/CodeFrame/CodeFrame.test.ts
new file mode 100644
index 0000000..d954e53
--- /dev/null
+++ b/ooknet-design/src/components/CodeFrame/CodeFrame.test.ts
@@ -0,0 +1,26 @@
+import { describe, expect, it } from "vitest";
+import { frameLabel } from "./CodeFrame";
+import { layoutDiff } from "../Diff/Diff";
+
+describe("frameLabel", () => {
+  it("passes short labels through", () => {
+    expect(frameLabel("TS", 48)).toBe("TS");
+  });
+  it("keeps the tail of long paths", () => {
+    const long = "ooknet-design/src/components/ArticleBacklinks/ArticleBacklinks.
astro";
+    const label = frameLabel(long, 48);
+    expect(label.startsWith("...")).toBe(true);
+    expect(label.endsWith("ArticleBacklinks.astro")).toBe(true);
+  });
+});
+
+describe("layoutDiff classify flag", () => {
+  it("skips diff classes when off", () => {
+    const rows = layoutDiff("+added\n-removed", 48, false);
+    expect(rows.every((r) => r.cls === null)).toBe(true);
+  });
+  it("keeps them when on", () => {
+    const rows = layoutDiff("+added\n-removed", 48, true);
+    expect(rows.map((r) => r.cls)).toEqual(["diff-add", "diff-del"]);
+  });
+});

diff --git a/ooknet-design/src/components/Co
deFrame/CodeFrame.test.ts b/ooknet-design/sr
c/components/CodeFrame/CodeFrame.test.ts
new file mode 100644
index 0000000..d954e53
--- /dev/null
+++ b/ooknet-design/src/components/CodeFrame
/CodeFrame.test.ts
@@ -0,0 +1,26 @@
+import { describe, expect, it } from "vites
t";
+import { frameLabel } from "./CodeFrame";
+import { layoutDiff } from "../Diff/Diff";
+
+describe("frameLabel", () => {
+  it("passes short labels through", () => {
+    expect(frameLabel("TS", 48)).toBe("TS")
;
+  });
+  it("keeps the tail of long paths", () => 
{
+    const long = "ooknet-design/src/compone
nts/ArticleBacklinks/ArticleBacklinks.astro"
;
+    const label = frameLabel(long, 48);
+    expect(label.startsWith("...")).toBe(tr
ue);
+    expect(label.endsWith("ArticleBacklinks
.astro")).toBe(true);
+  });
+});
+
+describe("layoutDiff classify flag", () => 
{
+  it("skips diff classes when off", () => {
+    const rows = layoutDiff("+added\n-remov
ed", 48, false);
+    expect(rows.every((r) => r.cls === null
)).toBe(true);
+  });
+  it("keeps them when on", () => {
+    const rows = layoutDiff("+added\n-remov
ed", 48, true);
+    expect(rows.map((r) => r.cls)).toEqual(
["diff-add", "diff-del"]);
+  });
+});
 

diff --git a/ooknet-design/src/components/CodeFrame/CodeFrame.ts b/ooknet-design/s
rc/components/CodeFrame/CodeFrame.ts
new file mode 100644
index 0000000..e2e850f
--- /dev/null
+++ b/ooknet-design/src/components/CodeFrame/CodeFrame.ts
@@ -0,0 +1,18 @@
+// Shared math for the framed code block: label seat and width checks.
+import { checkGrid, len } from "../../lib/ascii";
+
+export interface Props {
+  code: string;
+  /** Frame label: a language tag or a file path. */
+  label: string;
+  /** Apply diff line colors. */
+  diff?: boolean;
+}
+
+/** Label truncated so the top border always seats " LABEL " plus the
+ *  copy button at the narrow frame. */
+export function frameLabel(label: string, width: number): string {
+  const max = width - 16;
+  const cut = len(label) > max ? "..." + [...label].slice(-(max - 3)).join("") : 
label;
+  return checkGrid(cut, max, "code frame label");
+}

diff --git a/ooknet-design/src/components/Co
deFrame/CodeFrame.ts b/ooknet-design/src/com
ponents/CodeFrame/CodeFrame.ts
new file mode 100644
index 0000000..e2e850f
--- /dev/null
+++ b/ooknet-design/src/components/CodeFrame
/CodeFrame.ts
@@ -0,0 +1,18 @@
+// Shared math for the framed code block: l
abel seat and width checks.
+import { checkGrid, len } from "../../lib/a
scii";
+
+export interface Props {
+  code: string;
+  /** Frame label: a language tag or a file
 path. */
+  label: string;
+  /** Apply diff line colors. */
+  diff?: boolean;
+}
+
+/** Label truncated so the top border alway
s seats " LABEL " plus the
+ *  copy button at the narrow frame. */
+export function frameLabel(label: string, w
idth: number): string {
+  const max = width - 16;
+  const cut = len(label) > max ? "..." + [.
..label].slice(-(max - 3)).join("") : label;
+  return checkGrid(cut, max, "code frame la
bel");
+}
 

diff --git a/ooknet-design/src/components/Diff/Diff.ts b/ooknet-design/src/compone
nts/Diff/Diff.ts
index 8161722..7c7684e 100644
--- a/ooknet-design/src/components/Diff/Diff.ts
+++ b/ooknet-design/src/components/Diff/Diff.ts
@@ -14,13 +14,13 @@ export interface DiffRow {
   pad: number;
 }
 
-export function layoutDiff(code: string, width: number): DiffRow[] {
+export function layoutDiff(code: string, width: number, classify = true): DiffRow
[] {
   const inner = width - 4;
   return code
     .replace(/\n+$/, "")
     .split("\n")
     .flatMap((line) => {
-      const cls = diffLineClass(line);
+      const cls = classify ? diffLineClass(line) : null;
       const cps = [...line];
       const chunks = cps.length <= inner
         ? [line]

diff --git a/ooknet-design/src/components/Di
ff/Diff.ts b/ooknet-design/src/components/Di
ff/Diff.ts
index 8161722..7c7684e 100644
--- a/ooknet-design/src/components/Diff/Diff
.ts
+++ b/ooknet-design/src/components/Diff/Diff
.ts
@@ -14,13 +14,13 @@ export interface DiffRow
 {
   pad: number;
 }
 
-export function layoutDiff(code: string, wi
dth: number): DiffRow[] {
+export function layoutDiff(code: string, wi
dth: number, classify = true): DiffRow[] {
   const inner = width - 4;
   return code
     .replace(/\n+$/, "")
     .split("\n")
     .flatMap((line) => {
-      const cls = diffLineClass(line);
+      const cls = classify ? diffLineClass(
line) : null;
       const cps = [...line];
       const chunks = cps.length <= inner
         ? [line]
 

diff --git a/ooknet-design/src/lib/git.test.ts b/ooknet-design/src/lib/git.test.ts
index fed2655..8672894 100644
--- a/ooknet-design/src/lib/git.test.ts
+++ b/ooknet-design/src/lib/git.test.ts
@@ -1,5 +1,5 @@
 import { describe, expect, it } from "vitest";
-import { filesTree, repoAvailable, repoInfo } from "./git";
+import { branches, langOf, repoAvailable, repoInfo } from "./git";
 import { toGrid } from "./ascii";
 
 describe("toGrid", () => {
@@ -17,17 +17,12 @@ describe("toGrid", () => {
   });
 });
 
-describe("filesTree", () => {
-  const tree = filesTree(["flake.nix", "src/lib/ascii.ts", "src/pages/index.astro
"], "/git/x/blob/");
-
-  it("branches directories and links files", () => {
-    expect(tree.map((n) => n.label)).toEqual(["src/", "flake.nix"]);
-    const src = tree[0].children!;
-    expect(src.map((n) => n.label)).toEqual(["lib/", "pages/"]);
-    expect(src[0].children![0]).toEqual({
-      label: "ascii.ts",
-      href: "/git/x/blob/src/lib/ascii.ts/",
-    });
+describe("langOf", () => {
+  it("maps extensions to frame labels", () => {
+    expect(langOf("src/lib/ascii.ts")).toBe("TS");
+    expect(langOf("flake.nix")).toBe("NIX");
+    expect(langOf("mod.rs")).toBe("RS");
+    expect(langOf("Makefile")).toBe("TXT");
   });
 });
 
@@ -37,6 +32,7 @@ describe("repoInfo", () => {
     const info = repoInfo("..");
     expect(info.commits).toBeGreaterThan(30);
     expect(info.branch).toBe("master");
+    expect(branches("..")[0]).toBe("master");
     expect(info.lastDate).toMatch(/^\d{4}-\d{2}-\d{2}$/);
   });
 });

diff --git a/ooknet-design/src/lib/git.test.
ts b/ooknet-design/src/lib/git.test.ts
index fed2655..8672894 100644
--- a/ooknet-design/src/lib/git.test.ts
+++ b/ooknet-design/src/lib/git.test.ts
@@ -1,5 +1,5 @@
 import { describe, expect, it } from "vites
t";
-import { filesTree, repoAvailable, repoInfo
 } from "./git";
+import { branches, langOf, repoAvailable, r
epoInfo } from "./git";
 import { toGrid } from "./ascii";
 
 describe("toGrid", () => {
@@ -17,17 +17,12 @@ describe("toGrid", () =>
 {
   });
 });
 
-describe("filesTree", () => {
-  const tree = filesTree(["flake.nix", "src
/lib/ascii.ts", "src/pages/index.astro"], "/
git/x/blob/");
-
-  it("branches directories and links files"
, () => {
-    expect(tree.map((n) => n.label)).toEqua
l(["src/", "flake.nix"]);
-    const src = tree[0].children!;
-    expect(src.map((n) => n.label)).toEqual
(["lib/", "pages/"]);
-    expect(src[0].children![0]).toEqual({
-      label: "ascii.ts",
-      href: "/git/x/blob/src/lib/ascii.ts/"
,
-    });
+describe("langOf", () => {
+  it("maps extensions to frame labels", () 
=> {
+    expect(langOf("src/lib/ascii.ts")).toBe
("TS");
+    expect(langOf("flake.nix")).toBe("NIX")
;
+    expect(langOf("mod.rs")).toBe("RS");
+    expect(langOf("Makefile")).toBe("TXT");
   });
 });
 
@@ -37,6 +32,7 @@ describe("repoInfo", () =>
 {
     const info = repoInfo("..");
     expect(info.commits).toBeGreaterThan(30
);
     expect(info.branch).toBe("master");
+    expect(branches("..")[0]).toBe("master"
);
     expect(info.lastDate).toMatch(/^\d{4}-\
d{2}-\d{2}$/);
   });
 });
 

diff --git a/ooknet-design/src/lib/git.ts b/ooknet-design/src/lib/git.ts
index 489be48..7e388d7 100644
--- a/ooknet-design/src/lib/git.ts
+++ b/ooknet-design/src/lib/git.ts
@@ -2,11 +2,11 @@
 // from real repositories at build time - stagit-style, but rendered
 // through the site's own components. Anything that can land in a
 // frame passes toGrid() so history written before the og-ascii rule
-// can't fail the build.
+// can't fail the build. Every reader takes a ref so branch pages are
+// first-class; urls follow the github shape (tree/<branch>/<path>).
 import { execFileSync } from "node:child_process";
 import { existsSync } from "node:fs";
 import { toGrid } from "./ascii";
-import type { TreeNode } from "../components/Tree/Tree";
 import type { TimelineEntry } from "../components/Timeline/Timeline";
 
 export interface GitRepo {
@@ -30,60 +30,70 @@ const git = (path: string, args: string[]): string =>
 
 export const repoAvailable = (r: GitRepo): boolean => existsSync(r.path);
 
+/** Local branches, default branch first. */
+export function branches(path: string): string[] {
+  const head = defaultBranch(path);
+  const all = git(path, ["for-each-ref", "refs/heads", "--format=%(refname:short)
"])
+    .split("\n")
+    .filter(Boolean);
+  return [head, ...all.filter((b) => b !== head)];
+}
+
+export const defaultBranch = (path: string): string =>
+  git(path, ["symbolic-ref", "--short", "HEAD"]).trim();
+
 export interface RepoInfo {
   branch: string;
   commits: number;
+  lastHash: string;
   lastDate: string;
   lastSubject: string;
 }
 
-export function repoInfo(path: string): RepoInfo {
-  const [lastDate, ...subject] = git(path, ["log", "-1", "--format=%as|%s"])
+export function repoInfo(path: string, ref?: string): RepoInfo {
+  const branch = ref ?? defaultBranch(path);
+  const [lastHash, lastDate, ...subject] = git(path, ["log", "-1", "--format=%h|%
as|%s", branch])
     .trim()
     .split("|");
   return {
-    branch: git(path, ["symbolic-ref", "--short", "HEAD"]).trim(),
-    commits: parseInt(git(path, ["rev-list", "--count", "HEAD"]).trim(), 10),
+    branch,
+    commits: parseInt(git(path, ["rev-list", "--count", branch]).trim(), 10),
+    lastHash,
     lastDate,
     lastSubject: toGrid(subject.join("|")),
   };
 }
 
-export function repoFiles(path: string): string[] {
-  return git(path, ["ls-tree", "-r", "--name-only", "HEAD"])
+export function repoFiles(path: string, ref = "HEAD"): string[] {
+  return git(path, ["ls-tree", "-r", "--name-only", ref])
     .split("\n")
     .filter(Boolean);
 }
 
-/** File list as a Tree: directories branch, files link to blob pages. */
-export function filesTree(files: string[], base: string): TreeNode[] {
-  type Branch = { dirs: Map<string, Branch>; files: string[] };
-  const root: Branch = { dirs: new Map(), files: [] };
-  for (const file of files) {
-    const segments = file.split("/");
-    let branch = root;
-    for (const seg of segments.slice(0, -1)) {
-      if (!branch.dirs.has(seg)) branch.dirs.set(seg, { dirs: new Map(), files: [
] });
-      branch = branch.dirs.get(seg)!;
-    }
-    branch.files.push(file);
-  }
-  const toNodes = (b: Branch): TreeNode[] => [
-    ...[...b.dirs.entries()]
-      .sort(([a], [c]) => a.localeCompare(c))
-      .map(([seg, child]) => ({ label: `${toGrid(seg)}/`, children: toNodes(child
) })),
-    ...b.files
-      .sort()
-      .map((f) => ({
-        label: toGrid(f.split("/").pop()!),
-        href: `${base}${f}/`,
-      })),
-  ];
-  return toNodes(root);
-}
-
-export function repoLog(path: string, limit: number, base: string): TimelineEntry
[] {
-  return git(path, ["log", `-${limit}`, "--format=%h|%as|%s"])
+export function repoLog(path: string, limit: number, base: string, ref = "HEAD"):
 TimelineEntry[] {
+  return git(path, ["log", `-${limit}`, "--format=%h|%as|%s", ref])
+    .split("\n")
+    .filter(Boolean)
+    .map((line) => {
+      const [hash, date, ...subject] = line.split("|");
+      return {
+        date,
+        title: toGrid(hash),
+        desc: toGrid(subject.join("|")),
+        href: `${base}commit/${hash}/`,
+      };
+    });
+}
+
+/** Commits touching one path - the file history page. */
+export function pathLog(
+  path: string,
+  file: string,
+  limit: number,
+  base: string,
+  ref = "HEAD",
+): TimelineEntry[] {
+  return git(path, ["log", `-${limit}`, "--format=%h|%as|%s", ref, "--", file])
     .split("\n")
     .filter(Boolean)
     .map((line) => {
@@ -109,8 +119,8 @@ export interface DirEntry {
 
 /** Immediate children of a directory, github-style: dirs first, each
  *  with the last commit that touched it. */
-export function dirEntries(path: string, dir = ""): DirEntry[] {
-  const spec = dir ? `HEAD:${dir}` : "HEAD";
+export function dirEntries(path: string, dir = "", ref = "HEAD"): DirEntry[] {
+  const spec = dir ? `${ref}:${dir}` : ref;
   const rows = git(path, ["ls-tree", spec])
     .split("\n")
     .filter(Boolean)
@@ -124,7 +134,7 @@ export function dirEntries(path: string, dir = ""): DirEntry[]
 {
     .sort((a, b) => (a.kind === b.kind ? a.name.localeCompare(b.name) : a.kind ==
= "tree" ? -1 : 1))
     .map(({ name, kind }) => {
       const full = dir ? `${dir}/${name}` : name;
-      const [hash, date, ...subject] = git(path, ["log", "-1", "--format=%h|%as|%
s", "HEAD", "--", full])
+      const [hash, date, ...subject] = git(path, ["log", "-1", "--format=%h|%as|%
s", ref, "--", full])
         .trim()
         .split("|");
       return { name: toGrid(name), path: full, kind, hash, date, subject: toGrid(
subject.join("|")) };
@@ -143,11 +153,11 @@ export function repoDirs(files: string[]): string[] {
   return [...dirs].sort();
 }
 
-/** Repo README content at HEAD, if present. */
-export function readReadme(path: string): string | null {
+/** Repo README content, if present. */
+export function readReadme(path: string, ref = "HEAD"): string | null {
   for (const name of ["README.md", "readme.md", "README"]) {
     try {
-      return git(path, ["show", `HEAD:${name}`]);
+      return git(path, ["show", `${ref}:${name}`]);
     } catch {
       continue;
     }
@@ -162,8 +172,8 @@ export interface Blob {
   lines: number;
 }
 
-export function readBlob(path: string, file: string): Blob {
-  const raw = execFileSync("git", ["-C", path, "show", `HEAD:${file}`], {
+export function readBlob(path: string, file: string, ref = "HEAD"): Blob {
+  const raw = execFileSync("git", ["-C", path, "show", `${ref}:${file}`], {
     maxBuffer: 32 * 1024 * 1024,
   });
   if (raw.subarray(0, 8000).includes(0)) {
@@ -179,23 +189,47 @@ export function readBlob(path: string, file: string): Blob {
   };
 }
 
+export interface CommitFile {
+  path: string;
+  diff: string;
+}
+
 export interface Commit {
   hash: string;
   date: string;
   subject: string;
   body: string;
-  diff: string;
+  files: CommitFile[];
 }
 
+/** One commit with its patch split per file, github-style. */
 export function readCommit(path: string, hash: string): Commit {
   const meta = git(path, ["show", "-s", "--format=%H|%as|%s|%b", hash]).trim();
   const [full, date, subject, ...body] = meta.split("|");
-  const diff = git(path, ["show", "--stat", "--patch", "--format=", hash]);
+  const patch = git(path, ["show", "--patch", "--format=", hash]);
+  const files: CommitFile[] = [];
+  for (const chunk of patch.split(/^(?=diff --git )/m)) {
+    if (!chunk.trim()) continue;
+    const m = chunk.match(/^diff --git a\/.+? b\/(.+)$/m);
+    const file = m ? m[1] : "?";
+    const trimmed =
+      chunk.length > MAX_BLOB ? chunk.slice(0, MAX_BLOB) + "\n[diff truncated]" :
 chunk;
+    files.push({ path: file, diff: trimmed });
+  }
   return {
     hash: full,
     date,
     subject: toGrid(subject),
     body: toGrid(body.join("|").trim()),
-    diff: diff.length > MAX_BLOB ? diff.slice(0, MAX_BLOB) + "\n[diff truncated]"
 : diff,
+    files,
   };
 }
+
+/** Frame label for a file: the uppercased extension, same convention
+ *  as the pipeline's code fences (label = uppercase of the token you
+ *  have). No language table to maintain. */
+export function langOf(file: string): string {
+  const name = file.split("/").pop() ?? file;
+  const ext = name.includes(".") ? name.split(".").pop()! : "";
+  return ext ? ext.toUpperCase().slice(0, 8) : "TXT";
+}

diff --git a/ooknet-design/src/lib/git.ts b/
ooknet-design/src/lib/git.ts
index 489be48..7e388d7 100644
--- a/ooknet-design/src/lib/git.ts
+++ b/ooknet-design/src/lib/git.ts
@@ -2,11 +2,11 @@
 // from real repositories at build time - s
tagit-style, but rendered
 // through the site's own components. Anyth
ing that can land in a
 // frame passes toGrid() so history written
 before the og-ascii rule
-// can't fail the build.
+// can't fail the build. Every reader takes
 a ref so branch pages are
+// first-class; urls follow the github shap
e (tree/<branch>/<path>).
 import { execFileSync } from "node:child_pr
ocess";
 import { existsSync } from "node:fs";
 import { toGrid } from "./ascii";
-import type { TreeNode } from "../component
s/Tree/Tree";
 import type { TimelineEntry } from "../comp
onents/Timeline/Timeline";
 
 export interface GitRepo {
@@ -30,60 +30,70 @@ const git = (path: strin
g, args: string[]): string =>
 
 export const repoAvailable = (r: GitRepo): 
boolean => existsSync(r.path);
 
+/** Local branches, default branch first. *
/
+export function branches(path: string): str
ing[] {
+  const head = defaultBranch(path);
+  const all = git(path, ["for-each-ref", "r
efs/heads", "--format=%(refname:short)"])
+    .split("\n")
+    .filter(Boolean);
+  return [head, ...all.filter((b) => b !== 
head)];
+}
+
+export const defaultBranch = (path: string)
: string =>
+  git(path, ["symbolic-ref", "--short", "HE
AD"]).trim();
+
 export interface RepoInfo {
   branch: string;
   commits: number;
+  lastHash: string;
   lastDate: string;
   lastSubject: string;
 }
 
-export function repoInfo(path: string): Rep
oInfo {
-  const [lastDate, ...subject] = git(path, 
["log", "-1", "--format=%as|%s"])
+export function repoInfo(path: string, ref?
: string): RepoInfo {
+  const branch = ref ?? defaultBranch(path)
;
+  const [lastHash, lastDate, ...subject] = 
git(path, ["log", "-1", "--format=%h|%as|%s"
, branch])
     .trim()
     .split("|");
   return {
-    branch: git(path, ["symbolic-ref", "--s
hort", "HEAD"]).trim(),
-    commits: parseInt(git(path, ["rev-list"
, "--count", "HEAD"]).trim(), 10),
+    branch,
+    commits: parseInt(git(path, ["rev-list"
, "--count", branch]).trim(), 10),
+    lastHash,
     lastDate,
     lastSubject: toGrid(subject.join("|")),
   };
 }
 
-export function repoFiles(path: string): st
ring[] {
-  return git(path, ["ls-tree", "-r", "--nam
e-only", "HEAD"])
+export function repoFiles(path: string, ref
 = "HEAD"): string[] {
+  return git(path, ["ls-tree", "-r", "--nam
e-only", ref])
     .split("\n")
     .filter(Boolean);
 }
 
-/** File list as a Tree: directories branch
, files link to blob pages. */
-export function filesTree(files: string[], 
base: string): TreeNode[] {
-  type Branch = { dirs: Map<string, Branch>
; files: string[] };
-  const root: Branch = { dirs: new Map(), f
iles: [] };
-  for (const file of files) {
-    const segments = file.split("/");
-    let branch = root;
-    for (const seg of segments.slice(0, -1)
) {
-      if (!branch.dirs.has(seg)) branch.dir
s.set(seg, { dirs: new Map(), files: [] });
-      branch = branch.dirs.get(seg)!;
-    }
-    branch.files.push(file);
-  }
-  const toNodes = (b: Branch): TreeNode[] =
> [
-    ...[...b.dirs.entries()]
-      .sort(([a], [c]) => a.localeCompare(c
))
-      .map(([seg, child]) => ({ label: `${t
oGrid(seg)}/`, children: toNodes(child) })),
-    ...b.files
-      .sort()
-      .map((f) => ({
-        label: toGrid(f.split("/").pop()!),
-        href: `${base}${f}/`,
-      })),
-  ];
-  return toNodes(root);
-}
-
-export function repoLog(path: string, limit
: number, base: string): TimelineEntry[] {
-  return git(path, ["log", `-${limit}`, "--
format=%h|%as|%s"])
+export function repoLog(path: string, limit
: number, base: string, ref = "HEAD"): Timel
ineEntry[] {
+  return git(path, ["log", `-${limit}`, "--
format=%h|%as|%s", ref])
+    .split("\n")
+    .filter(Boolean)
+    .map((line) => {
+      const [hash, date, ...subject] = line
.split("|");
+      return {
+        date,
+        title: toGrid(hash),
+        desc: toGrid(subject.join("|")),
+        href: `${base}commit/${hash}/`,
+      };
+    });
+}
+
+/** Commits touching one path - the file hi
story page. */
+export function pathLog(
+  path: string,
+  file: string,
+  limit: number,
+  base: string,
+  ref = "HEAD",
+): TimelineEntry[] {
+  return git(path, ["log", `-${limit}`, "--
format=%h|%as|%s", ref, "--", file])
     .split("\n")
     .filter(Boolean)
     .map((line) => {
@@ -109,8 +119,8 @@ export interface DirEntr
y {
 
 /** Immediate children of a directory, gith
ub-style: dirs first, each
  *  with the last commit that touched it. *
/
-export function dirEntries(path: string, di
r = ""): DirEntry[] {
-  const spec = dir ? `HEAD:${dir}` : "HEAD"
;
+export function dirEntries(path: string, di
r = "", ref = "HEAD"): DirEntry[] {
+  const spec = dir ? `${ref}:${dir}` : ref;
   const rows = git(path, ["ls-tree", spec])
     .split("\n")
     .filter(Boolean)
@@ -124,7 +134,7 @@ export function dirEntri
es(path: string, dir = ""): DirEntry[] {
     .sort((a, b) => (a.kind === b.kind ? a.
name.localeCompare(b.name) : a.kind === "tre
e" ? -1 : 1))
     .map(({ name, kind }) => {
       const full = dir ? `${dir}/${name}` :
 name;
-      const [hash, date, ...subject] = git(
path, ["log", "-1", "--format=%h|%as|%s", "H
EAD", "--", full])
+      const [hash, date, ...subject] = git(
path, ["log", "-1", "--format=%h|%as|%s", re
f, "--", full])
         .trim()
         .split("|");
       return { name: toGrid(name), path: fu
ll, kind, hash, date, subject: toGrid(subjec
t.join("|")) };
@@ -143,11 +153,11 @@ export function repoDi
rs(files: string[]): string[] {
   return [...dirs].sort();
 }
 
-/** Repo README content at HEAD, if present
. */
-export function readReadme(path: string): s
tring | null {
+/** Repo README content, if present. */
+export function readReadme(path: string, re
f = "HEAD"): string | null {
   for (const name of ["README.md", "readme.
md", "README"]) {
     try {
-      return git(path, ["show", `HEAD:${nam
e}`]);
+      return git(path, ["show", `${ref}:${n
ame}`]);
     } catch {
       continue;
     }
@@ -162,8 +172,8 @@ export interface Blob {
   lines: number;
 }
 
-export function readBlob(path: string, file
: string): Blob {
-  const raw = execFileSync("git", ["-C", pa
th, "show", `HEAD:${file}`], {
+export function readBlob(path: string, file
: string, ref = "HEAD"): Blob {
+  const raw = execFileSync("git", ["-C", pa
th, "show", `${ref}:${file}`], {
     maxBuffer: 32 * 1024 * 1024,
   });
   if (raw.subarray(0, 8000).includes(0)) {
@@ -179,23 +189,47 @@ export function readBl
ob(path: string, file: string): Blob {
   };
 }
 
+export interface CommitFile {
+  path: string;
+  diff: string;
+}
+
 export interface Commit {
   hash: string;
   date: string;
   subject: string;
   body: string;
-  diff: string;
+  files: CommitFile[];
 }
 
+/** One commit with its patch split per fil
e, github-style. */
 export function readCommit(path: string, ha
sh: string): Commit {
   const meta = git(path, ["show", "-s", "--
format=%H|%as|%s|%b", hash]).trim();
   const [full, date, subject, ...body] = me
ta.split("|");
-  const diff = git(path, ["show", "--stat",
 "--patch", "--format=", hash]);
+  const patch = git(path, ["show", "--patch
", "--format=", hash]);
+  const files: CommitFile[] = [];
+  for (const chunk of patch.split(/^(?=diff
 --git )/m)) {
+    if (!chunk.trim()) continue;
+    const m = chunk.match(/^diff --git a\/.
+? b\/(.+)$/m);
+    const file = m ? m[1] : "?";
+    const trimmed =
+      chunk.length > MAX_BLOB ? chunk.slice
(0, MAX_BLOB) + "\n[diff truncated]" : chunk
;
+    files.push({ path: file, diff: trimmed 
});
+  }
   return {
     hash: full,
     date,
     subject: toGrid(subject),
     body: toGrid(body.join("|").trim()),
-    diff: diff.length > MAX_BLOB ? diff.sli
ce(0, MAX_BLOB) + "\n[diff truncated]" : dif
f,
+    files,
   };
 }
+
+/** Frame label for a file: the uppercased 
extension, same convention
+ *  as the pipeline's code fences (label = 
uppercase of the token you
+ *  have). No language table to maintain. *
/
+export function langOf(file: string): strin
g {
+  const name = file.split("/").pop() ?? fil
e;
+  const ext = name.includes(".") ? name.spl
it(".").pop()! : "";
+  return ext ? ext.toUpperCase().slice(0, 8
) : "TXT";
+}
 

diff --git a/ooknet-design/src/lib/markdown.ts b/ooknet-design/src/lib/markdown.ts
new file mode 100644
index 0000000..8853e06
--- /dev/null
+++ b/ooknet-design/src/lib/markdown.ts
@@ -0,0 +1,23 @@
+// Standalone markdown rendering for strings that live outside the
+// content collections (repo READMEs). Same shape as the astro
+// pipeline: gfm, then rehype-ascii for head rules, code frames, and
+// ascii tables. Input goes through toGrid first so foreign text can't
+// fail the grid checks the pipeline runs.
+import { unified } from "unified";
+import remarkParse from "remark-parse";
+import remarkGfm from "remark-gfm";
+import remarkRehype from "remark-rehype";
+import rehypeStringify from "rehype-stringify";
+import { rehypeAscii } from "./rehype-ascii";
+import { toGrid } from "./ascii";
+
+const pipeline = unified()
+  .use(remarkParse)
+  .use(remarkGfm)
+  .use(remarkRehype)
+  .use(rehypeAscii)
+  .use(rehypeStringify);
+
+export function renderMarkdown(md: string): string {
+  return String(pipeline.processSync(toGrid(md)));
+}

diff --git a/ooknet-design/src/lib/markdown.
ts b/ooknet-design/src/lib/markdown.ts
new file mode 100644
index 0000000..8853e06
--- /dev/null
+++ b/ooknet-design/src/lib/markdown.ts
@@ -0,0 +1,23 @@
+// Standalone markdown rendering for string
s that live outside the
+// content collections (repo READMEs). Same
 shape as the astro
+// pipeline: gfm, then rehype-ascii for hea
d rules, code frames, and
+// ascii tables. Input goes through toGrid 
first so foreign text can't
+// fail the grid checks the pipeline runs.
+import { unified } from "unified";
+import remarkParse from "remark-parse";
+import remarkGfm from "remark-gfm";
+import remarkRehype from "remark-rehype";
+import rehypeStringify from "rehype-stringi
fy";
+import { rehypeAscii } from "./rehype-ascii
";
+import { toGrid } from "./ascii";
+
+const pipeline = unified()
+  .use(remarkParse)
+  .use(remarkGfm)
+  .use(remarkRehype)
+  .use(rehypeAscii)
+  .use(rehypeStringify);
+
+export function renderMarkdown(md: string):
 string {
+  return String(pipeline.processSync(toGrid
(md)));
+}
 

diff --git a/ooknet-design/src/pages/git/[repo]/blob/[...path].astro b/ooknet-desi
gn/src/pages/git/[repo]/blob/[...rest].astro
similarity index 63%
rename from ooknet-design/src/pages/git/[repo]/blob/[...path].astro
rename to ooknet-design/src/pages/git/[repo]/blob/[...rest].astro
index 6f33a4e..2c3a758 100644
--- a/ooknet-design/src/pages/git/[repo]/blob/[...path].astro
+++ b/ooknet-design/src/pages/git/[repo]/blob/[...rest].astro
@@ -4,27 +4,28 @@ import TopHeader from "../../../../components/TopHeader/TopHeade
r.astro";
 import ArticleHeader from "../../../../components/ArticleHeader/ArticleHeader.ast
ro";
 import Pre from "../../../../components/Pre/Pre.astro";
 import KeyValue from "../../../../components/KeyValue/KeyValue.astro";
-import Diff from "../../../../components/Diff/Diff.astro";
+import CodeFrame from "../../../../components/CodeFrame/CodeFrame.astro";
 import Button from "../../../../components/Button/Button.astro";
 import ArticleFooter from "../../../../components/ArticleFooter/ArticleFooter.ast
ro";
 import { toGrid } from "../../../../lib/ascii";
 import { REPOS } from "../../../../git.config";
-import { repoAvailable, repoFiles, readBlob, type GitRepo } from "../../../../lib
/git";
+import { repoAvailable, branches, repoFiles, readBlob, langOf, type GitRepo } fro
m "../../../../lib/git";
 
 export function getStaticPaths() {
   return REPOS.filter(repoAvailable).flatMap((r) =>
-    repoFiles(r.path).map((file) => ({
-      params: { repo: r.slug, path: file },
-      props: { repo: r, file },
-    })),
+    branches(r.path).flatMap((branch) =>
+      repoFiles(r.path, branch).map((file) => ({
+        params: { repo: r.slug, rest: `${branch}/${file}` },
+        props: { repo: r, branch, file },
+      })),
+    ),
   );
 }
 
-const { repo, file } = Astro.props as { repo: GitRepo; file: string };
-const blob = readBlob(repo.path, file);
+const { repo, branch, file } = Astro.props as { repo: GitRepo; branch: string; fi
le: string };
+const blob = readBlob(repo.path, file, branch);
 const name = file.split("/").pop()!;
-// paths are one long "word" - chunk them so KeyValue stays on-grid at
-// the narrow frame (48 - keyW 8 = 40 cells)
+const dir = file.includes("/") ? file.slice(0, file.lastIndexOf("/")) : "";
 const chunk = (s: string, n: number) => {
   const cps = [...s];
   return Array.from({ length: Math.ceil(cps.length / n) }, (_, i) =>
@@ -39,14 +40,16 @@ const pathRows = chunk(toGrid(file), 38);
   <KeyValue entries={[
     ["FILE", pathRows[0]],
     ...pathRows.slice(1).map((c) => ["", c] as [string, string]),
+    ["BRANCH", branch],
     ["LINES", blob.binary ? "BINARY" : `${blob.lines}`],
   ]} />
   <Pre>{" "}</Pre>
+  <Pre><Button label="HISTORY" href={`/git/${repo.slug}/history/${branch}/${file}
/`} />{"  "}<Button label="UP" href={dir ? `/git/${repo.slug}/tree/${branch}/${dir
}/` : `/git/${repo.slug}/`} /></Pre>
+  <Pre>{" "}</Pre>
   {blob.binary
     ? <Pre>*** BINARY FILE - NOT RENDERED ***</Pre>
-    : <Diff code={toGrid(blob.text)} />}
+    : <CodeFrame code={toGrid(blob.text)} label={langOf(file)} />}
   {blob.truncated && <Pre>*** TRUNCATED AT 200 KIB ***</Pre>}
   <Pre>{" "}</Pre>
-  <Pre><Button label="BACK TO REPO" href={`/git/${repo.slug}/`} /></Pre>
   <ArticleFooter />
 </ArticleLayout>

diff --git a/ooknet-design/src/pages/git/[re
po]/blob/[...path].astro b/ooknet-design/src
/pages/git/[repo]/blob/[...rest].astro
similarity index 63%
rename from ooknet-design/src/pages/git/[rep
o]/blob/[...path].astro
rename to ooknet-design/src/pages/git/[repo]
/blob/[...rest].astro
index 6f33a4e..2c3a758 100644
--- a/ooknet-design/src/pages/git/[repo]/blo
b/[...path].astro
+++ b/ooknet-design/src/pages/git/[repo]/blo
b/[...rest].astro
@@ -4,27 +4,28 @@ import TopHeader from "../
../../../components/TopHeader/TopHeader.astr
o";
 import ArticleHeader from "../../../../comp
onents/ArticleHeader/ArticleHeader.astro";
 import Pre from "../../../../components/Pre
/Pre.astro";
 import KeyValue from "../../../../component
s/KeyValue/KeyValue.astro";
-import Diff from "../../../../components/Di
ff/Diff.astro";
+import CodeFrame from "../../../../componen
ts/CodeFrame/CodeFrame.astro";
 import Button from "../../../../components/
Button/Button.astro";
 import ArticleFooter from "../../../../comp
onents/ArticleFooter/ArticleFooter.astro";
 import { toGrid } from "../../../../lib/asc
ii";
 import { REPOS } from "../../../../git.conf
ig";
-import { repoAvailable, repoFiles, readBlob
, type GitRepo } from "../../../../lib/git";
+import { repoAvailable, branches, repoFiles
, readBlob, langOf, type GitRepo } from "../
../../../lib/git";
 
 export function getStaticPaths() {
   return REPOS.filter(repoAvailable).flatMa
p((r) =>
-    repoFiles(r.path).map((file) => ({
-      params: { repo: r.slug, path: file },
-      props: { repo: r, file },
-    })),
+    branches(r.path).flatMap((branch) =>
+      repoFiles(r.path, branch).map((file) 
=> ({
+        params: { repo: r.slug, rest: `${br
anch}/${file}` },
+        props: { repo: r, branch, file },
+      })),
+    ),
   );
 }
 
-const { repo, file } = Astro.props as { rep
o: GitRepo; file: string };
-const blob = readBlob(repo.path, file);
+const { repo, branch, file } = Astro.props 
as { repo: GitRepo; branch: string; file: st
ring };
+const blob = readBlob(repo.path, file, bran
ch);
 const name = file.split("/").pop()!;
-// paths are one long "word" - chunk them s
o KeyValue stays on-grid at
-// the narrow frame (48 - keyW 8 = 40 cells
)
+const dir = file.includes("/") ? file.slice
(0, file.lastIndexOf("/")) : "";
 const chunk = (s: string, n: number) => {
   const cps = [...s];
   return Array.from({ length: Math.ceil(cps
.length / n) }, (_, i) =>
@@ -39,14 +40,16 @@ const pathRows = chunk(t
oGrid(file), 38);
   <KeyValue entries={[
     ["FILE", pathRows[0]],
     ...pathRows.slice(1).map((c) => ["", c]
 as [string, string]),
+    ["BRANCH", branch],
     ["LINES", blob.binary ? "BINARY" : `${b
lob.lines}`],
   ]} />
   <Pre>{" "}</Pre>
+  <Pre><Button label="HISTORY" href={`/git/
${repo.slug}/history/${branch}/${file}/`} />
{"  "}<Button label="UP" href={dir ? `/git/$
{repo.slug}/tree/${branch}/${dir}/` : `/git/
${repo.slug}/`} /></Pre>
+  <Pre>{" "}</Pre>
   {blob.binary
     ? <Pre>*** BINARY FILE - NOT RENDERED *
**</Pre>
-    : <Diff code={toGrid(blob.text)} />}
+    : <CodeFrame code={toGrid(blob.text)} l
abel={langOf(file)} />}
   {blob.truncated && <Pre>*** TRUNCATED AT 
200 KIB ***</Pre>}
   <Pre>{" "}</Pre>
-  <Pre><Button label="BACK TO REPO" href={`
/git/${repo.slug}/`} /></Pre>
   <ArticleFooter />
 </ArticleLayout>
 

diff --git a/ooknet-design/src/pages/git/[repo]/commit/[hash].astro b/ooknet-desig
n/src/pages/git/[repo]/commit/[hash].astro
index 391cea9..a262295 100644
--- a/ooknet-design/src/pages/git/[repo]/commit/[hash].astro
+++ b/ooknet-design/src/pages/git/[repo]/commit/[hash].astro
@@ -4,12 +4,12 @@ import TopHeader from "../../../../components/TopHeader/TopHeade
r.astro";
 import ArticleHeader from "../../../../components/ArticleHeader/ArticleHeader.ast
ro";
 import Pre from "../../../../components/Pre/Pre.astro";
 import KeyValue from "../../../../components/KeyValue/KeyValue.astro";
-import Diff from "../../../../components/Diff/Diff.astro";
+import CodeFrame from "../../../../components/CodeFrame/CodeFrame.astro";
 import Button from "../../../../components/Button/Button.astro";
 import ArticleFooter from "../../../../components/ArticleFooter/ArticleFooter.ast
ro";
 import { toGrid } from "../../../../lib/ascii";
 import { REPOS } from "../../../../git.config";
-import { repoAvailable, repoLog, readCommit, type GitRepo } from "../../../../lib
/git";
+import { repoAvailable, defaultBranch, repoLog, readCommit, type GitRepo } from "
../../../../lib/git";
 
 export function getStaticPaths() {
   return REPOS.filter(repoAvailable).flatMap((r) =>
@@ -23,6 +23,7 @@ export function getStaticPaths() {
 const { repo } = Astro.props as { repo: GitRepo };
 const { hash } = Astro.params;
 const commit = readCommit(repo.path, hash!);
+const branch = defaultBranch(repo.path);
 ---
 <ArticleLayout title={`GIT - ${repo.name} @ ${hash}`} description={commit.subject
}>
   <TopHeader />
@@ -32,10 +33,15 @@ const commit = readCommit(repo.path, hash!);
     ["HASH", commit.hash.slice(0, 12)],
     ["DATE", commit.date],
     ["SUBJECT", commit.subject],
+    ["FILES", `${commit.files.length} CHANGED`],
   ]} />
   <Pre>{" "}</Pre>
-  <Diff code={toGrid(commit.diff)} />
-  <Pre>{" "}</Pre>
-  <Pre><Button label="BACK TO LOG" href={`/git/${repo.slug}/log/`} /></Pre>
+  {commit.files.map((f) => (
+    <Fragment>
+      <CodeFrame code={toGrid(f.diff)} label={toGrid(f.path)} diff />
+      <Pre>{" "}</Pre>
+    </Fragment>
+  ))}
+  <Pre><Button label="BACK TO LOG" href={`/git/${repo.slug}/log/${branch}/`} /></
Pre>
   <ArticleFooter />
 </ArticleLayout>

diff --git a/ooknet-design/src/pages/git/[re
po]/commit/[hash].astro b/ooknet-design/src/
pages/git/[repo]/commit/[hash].astro
index 391cea9..a262295 100644
--- a/ooknet-design/src/pages/git/[repo]/com
mit/[hash].astro
+++ b/ooknet-design/src/pages/git/[repo]/com
mit/[hash].astro
@@ -4,12 +4,12 @@ import TopHeader from "../
../../../components/TopHeader/TopHeader.astr
o";
 import ArticleHeader from "../../../../comp
onents/ArticleHeader/ArticleHeader.astro";
 import Pre from "../../../../components/Pre
/Pre.astro";
 import KeyValue from "../../../../component
s/KeyValue/KeyValue.astro";
-import Diff from "../../../../components/Di
ff/Diff.astro";
+import CodeFrame from "../../../../componen
ts/CodeFrame/CodeFrame.astro";
 import Button from "../../../../components/
Button/Button.astro";
 import ArticleFooter from "../../../../comp
onents/ArticleFooter/ArticleFooter.astro";
 import { toGrid } from "../../../../lib/asc
ii";
 import { REPOS } from "../../../../git.conf
ig";
-import { repoAvailable, repoLog, readCommit
, type GitRepo } from "../../../../lib/git";
+import { repoAvailable, defaultBranch, repo
Log, readCommit, type GitRepo } from "../../
../../lib/git";
 
 export function getStaticPaths() {
   return REPOS.filter(repoAvailable).flatMa
p((r) =>
@@ -23,6 +23,7 @@ export function getStaticP
aths() {
 const { repo } = Astro.props as { repo: Git
Repo };
 const { hash } = Astro.params;
 const commit = readCommit(repo.path, hash!)
;
+const branch = defaultBranch(repo.path);
 ---
 <ArticleLayout title={`GIT - ${repo.name} @
 ${hash}`} description={commit.subject}>
   <TopHeader />
@@ -32,10 +33,15 @@ const commit = readCommi
t(repo.path, hash!);
     ["HASH", commit.hash.slice(0, 12)],
     ["DATE", commit.date],
     ["SUBJECT", commit.subject],
+    ["FILES", `${commit.files.length} CHANG
ED`],
   ]} />
   <Pre>{" "}</Pre>
-  <Diff code={toGrid(commit.diff)} />
-  <Pre>{" "}</Pre>
-  <Pre><Button label="BACK TO LOG" href={`/
git/${repo.slug}/log/`} /></Pre>
+  {commit.files.map((f) => (
+    <Fragment>
+      <CodeFrame code={toGrid(f.diff)} labe
l={toGrid(f.path)} diff />
+      <Pre>{" "}</Pre>
+    </Fragment>
+  ))}
+  <Pre><Button label="BACK TO LOG" href={`/
git/${repo.slug}/log/${branch}/`} /></Pre>
   <ArticleFooter />
 </ArticleLayout>
 

diff --git a/ooknet-design/src/pages/git/[repo]/history/[...rest].astro b/ooknet-d
esign/src/pages/git/[repo]/history/[...rest].astro
new file mode 100644
index 0000000..b303792
--- /dev/null
+++ b/ooknet-design/src/pages/git/[repo]/history/[...rest].astro
@@ -0,0 +1,48 @@
+---
+import ArticleLayout from "../../../../layouts/ArticleLayout/ArticleLayout.astro"
;
+import TopHeader from "../../../../components/TopHeader/TopHeader.astro";
+import ArticleHeader from "../../../../components/ArticleHeader/ArticleHeader.ast
ro";
+import Pre from "../../../../components/Pre/Pre.astro";
+import KeyValue from "../../../../components/KeyValue/KeyValue.astro";
+import Timeline from "../../../../components/Timeline/Timeline.astro";
+import Button from "../../../../components/Button/Button.astro";
+import ArticleFooter from "../../../../components/ArticleFooter/ArticleFooter.ast
ro";
+import { toGrid } from "../../../../lib/ascii";
+import { REPOS } from "../../../../git.config";
+import { repoAvailable, branches, repoFiles, pathLog, type GitRepo } from "../../
../../lib/git";
+
+export function getStaticPaths() {
+  return REPOS.filter(repoAvailable).flatMap((r) =>
+    branches(r.path).flatMap((branch) =>
+      repoFiles(r.path, branch).map((file) => ({
+        params: { repo: r.slug, rest: `${branch}/${file}` },
+        props: { repo: r, branch, file },
+      })),
+    ),
+  );
+}
+
+const { repo, branch, file } = Astro.props as { repo: GitRepo; branch: string; fi
le: string };
+const log = pathLog(repo.path, file, 200, `/git/${repo.slug}/`, branch);
+const chunk = (s: string, n: number) => {
+  const cps = [...s];
+  return Array.from({ length: Math.ceil(cps.length / n) }, (_, i) =>
+    cps.slice(i * n, (i + 1) * n).join(""));
+};
+const pathRows = chunk(toGrid(file), 38);
+---
+<ArticleLayout title={`GIT - ${repo.name} - history of ${file}`} description={`Co
mmits touching ${file} in ${repo.name}.`}>
+  <TopHeader />
+  <ArticleHeader left="OOKNET GIT" right={`HISTORY @ ${branch.toUpperCase()}`} />
+  <Pre>{" "}</Pre>
+  <KeyValue entries={[
+    ["FILE", pathRows[0]],
+    ...pathRows.slice(1).map((c) => ["", c] as [string, string]),
+    ["COMMITS", `${log.length}`],
+  ]} />
+  <Pre>{" "}</Pre>
+  <Timeline entries={log} />
+  <Pre>{" "}</Pre>
+  <Pre><Button label="BACK TO FILE" href={`/git/${repo.slug}/blob/${branch}/${fil
e}/`} /></Pre>
+  <ArticleFooter />
+</ArticleLayout>

diff --git a/ooknet-design/src/pages/git/[re
po]/history/[...rest].astro b/ooknet-design/
src/pages/git/[repo]/history/[...rest].astro
new file mode 100644
index 0000000..b303792
--- /dev/null
+++ b/ooknet-design/src/pages/git/[repo]/his
tory/[...rest].astro
@@ -0,0 +1,48 @@
+---
+import ArticleLayout from "../../../../layo
uts/ArticleLayout/ArticleLayout.astro";
+import TopHeader from "../../../../componen
ts/TopHeader/TopHeader.astro";
+import ArticleHeader from "../../../../comp
onents/ArticleHeader/ArticleHeader.astro";
+import Pre from "../../../../components/Pre
/Pre.astro";
+import KeyValue from "../../../../component
s/KeyValue/KeyValue.astro";
+import Timeline from "../../../../component
s/Timeline/Timeline.astro";
+import Button from "../../../../components/
Button/Button.astro";
+import ArticleFooter from "../../../../comp
onents/ArticleFooter/ArticleFooter.astro";
+import { toGrid } from "../../../../lib/asc
ii";
+import { REPOS } from "../../../../git.conf
ig";
+import { repoAvailable, branches, repoFiles
, pathLog, type GitRepo } from "../../../../
lib/git";
+
+export function getStaticPaths() {
+  return REPOS.filter(repoAvailable).flatMa
p((r) =>
+    branches(r.path).flatMap((branch) =>
+      repoFiles(r.path, branch).map((file) 
=> ({
+        params: { repo: r.slug, rest: `${br
anch}/${file}` },
+        props: { repo: r, branch, file },
+      })),
+    ),
+  );
+}
+
+const { repo, branch, file } = Astro.props 
as { repo: GitRepo; branch: string; file: st
ring };
+const log = pathLog(repo.path, file, 200, `
/git/${repo.slug}/`, branch);
+const chunk = (s: string, n: number) => {
+  const cps = [...s];
+  return Array.from({ length: Math.ceil(cps
.length / n) }, (_, i) =>
+    cps.slice(i * n, (i + 1) * n).join(""))
;
+};
+const pathRows = chunk(toGrid(file), 38);
+---
+<ArticleLayout title={`GIT - ${repo.name} -
 history of ${file}`} description={`Commits 
touching ${file} in ${repo.name}.`}>
+  <TopHeader />
+  <ArticleHeader left="OOKNET GIT" right={`
HISTORY @ ${branch.toUpperCase()}`} />
+  <Pre>{" "}</Pre>
+  <KeyValue entries={[
+    ["FILE", pathRows[0]],
+    ...pathRows.slice(1).map((c) => ["", c]
 as [string, string]),
+    ["COMMITS", `${log.length}`],
+  ]} />
+  <Pre>{" "}</Pre>
+  <Timeline entries={log} />
+  <Pre>{" "}</Pre>
+  <Pre><Button label="BACK TO FILE" href={`
/git/${repo.slug}/blob/${branch}/${file}/`} 
/></Pre>
+  <ArticleFooter />
+</ArticleLayout>
 

diff --git a/ooknet-design/src/pages/git/[repo]/index.astro b/ooknet-design/src/pa
ges/git/[repo]/index.astro
index bb1118f..469f7e3 100644
--- a/ooknet-design/src/pages/git/[repo]/index.astro
+++ b/ooknet-design/src/pages/git/[repo]/index.astro
@@ -1,19 +1,20 @@
 ---
-import { marked } from "marked";
 import ArticleLayout from "../../../layouts/ArticleLayout/ArticleLayout.astro";
 import TopHeader from "../../../components/TopHeader/TopHeader.astro";
 import ArticleHeader from "../../../components/ArticleHeader/ArticleHeader.astro"
;
 import Frame from "../../../components/Frame/Frame.astro";
 import Pre from "../../../components/Pre/Pre.astro";
-import KeyValue from "../../../components/KeyValue/KeyValue.astro";
+import ArticleMeta from "../../../components/ArticleMeta/ArticleMeta.astro";
+import Menu from "../../../components/Menu/Menu.astro";
 import GitListing from "../../../components/GitListing/GitListing.astro";
 import Timeline from "../../../components/Timeline/Timeline.astro";
 import Button from "../../../components/Button/Button.astro";
 import Prose from "../../../components/Prose/Prose.astro";
 import ArticleFooter from "../../../components/ArticleFooter/ArticleFooter.astro"
;
 import { row2, ruleD } from "../../../lib/ascii";
+import { renderMarkdown } from "../../../lib/markdown";
 import { REPOS } from "../../../git.config";
-import { repoAvailable, repoInfo, dirEntries, repoLog, readReadme, type GitRepo }
 from "../../../lib/git";
+import { repoAvailable, repoInfo, branches, dirEntries, repoLog, readReadme, type
 GitRepo } from "../../../lib/git";
 
 export function getStaticPaths() {
   return REPOS.filter(repoAvailable).map((r) => ({
@@ -24,10 +25,12 @@ export function getStaticPaths() {
 
 const { repo } = Astro.props as { repo: GitRepo };
 const info = repoInfo(repo.path);
-const entries = dirEntries(repo.path);
-const log = repoLog(repo.path, 5, `/git/${repo.slug}/`);
-const readme = readReadme(repo.path);
-const readmeHtml = readme ? marked.parse(readme) : null;
+const branch = info.branch;
+const allBranches = branches(repo.path);
+const entries = dirEntries(repo.path, "", branch);
+const log = repoLog(repo.path, 5, `/git/${repo.slug}/`, branch);
+const readme = readReadme(repo.path, branch);
+const readmeHtml = readme ? renderMarkdown(readme) : null;
 
 const section = (title: string, right: string) => (w: number) =>
   [row2(title, right, w), ruleD(w)].join("\n");
@@ -36,30 +39,37 @@ const section = (title: string, right: string) => (w: number) 
=>
   <TopHeader />
   <ArticleHeader left="OOKNET GIT" right={`REPO: ${repo.name.toUpperCase()}`} dat
e={info.lastDate} />
   <Pre>{" "}</Pre>
-  <KeyValue entries={[
-    ["BRANCH", info.branch],
-    ["COMMITS", `${info.commits}`],
-    ...(repo.clone ? [["CLONE", repo.clone.replace(/^https?:\/\//, "")] as [strin
g, string]] : []),
+  <ArticleMeta entries={[
+    { key: "COMMITS", value: `${info.commits}`, href: `/git/${repo.slug}/log/${br
anch}/` },
+    { key: "RECENT", value: `${info.lastHash} ${info.lastSubject}`, href: `/git/$
{repo.slug}/commit/${info.lastHash}/` },
+    ...(repo.clone ? [{ key: "CLONE", value: repo.clone.replace(/^https?:\/\//, "
") }] : []),
   ]} />
   <Pre>{" "}</Pre>
 
-  <Frame build={section("FILES", `HEAD @ ${info.branch}`)} />
+  <Frame build={section("FILES", `${entries.length} ENTRIES`)} />
   <Pre>{" "}</Pre>
-  <GitListing entries={entries} treeBase={`/git/${repo.slug}/tree/`} blobBase={`/
git/${repo.slug}/blob/`} commitBase={`/git/${repo.slug}/commit/`} />
+  <Pre><Menu label={`BRANCH: ${branch}`} items={allBranches.map((b) => ({
+    label: b,
+    href: b === branch ? `/git/${repo.slug}/` : `/git/${repo.slug}/tree/${b}/`,
+  }))} /></Pre>
   <Pre>{" "}</Pre>
-
-  <Frame build={section("LOG", "most recent first")} />
-  <Pre>{" "}</Pre>
-  <Timeline entries={log} />
-  <Pre><Button label="FULL LOG" href={`/git/${repo.slug}/log/`} /></Pre>
+  <GitListing entries={entries}
+    treeBase={`/git/${repo.slug}/tree/${branch}/`}
+    blobBase={`/git/${repo.slug}/blob/${branch}/`}
+    commitBase={`/git/${repo.slug}/commit/`} />
   <Pre>{" "}</Pre>
 
   {readmeHtml && (
     <Fragment>
-      <Frame build={section("README", "rendered from HEAD")} />
+      <Frame build={section("README", `rendered @ ${branch}`)} />
       <Prose><div set:html={readmeHtml} /></Prose>
       <Pre>{" "}</Pre>
     </Fragment>
   )}
+  <Frame build={section("LOG", "most recent first")} />
+  <Pre>{" "}</Pre>
+  <Timeline entries={log} />
+  <Pre><Button label="FULL LOG" href={`/git/${repo.slug}/log/${branch}/`} /></Pre
>
+  <Pre>{" "}</Pre>
   <ArticleFooter />
 </ArticleLayout>

diff --git a/ooknet-design/src/pages/git/[re
po]/index.astro b/ooknet-design/src/pages/gi
t/[repo]/index.astro
index bb1118f..469f7e3 100644
--- a/ooknet-design/src/pages/git/[repo]/ind
ex.astro
+++ b/ooknet-design/src/pages/git/[repo]/ind
ex.astro
@@ -1,19 +1,20 @@
 ---
-import { marked } from "marked";
 import ArticleLayout from "../../../layouts
/ArticleLayout/ArticleLayout.astro";
 import TopHeader from "../../../components/
TopHeader/TopHeader.astro";
 import ArticleHeader from "../../../compone
nts/ArticleHeader/ArticleHeader.astro";
 import Frame from "../../../components/Fram
e/Frame.astro";
 import Pre from "../../../components/Pre/Pr
e.astro";
-import KeyValue from "../../../components/K
eyValue/KeyValue.astro";
+import ArticleMeta from "../../../component
s/ArticleMeta/ArticleMeta.astro";
+import Menu from "../../../components/Menu/
Menu.astro";
 import GitListing from "../../../components
/GitListing/GitListing.astro";
 import Timeline from "../../../components/T
imeline/Timeline.astro";
 import Button from "../../../components/But
ton/Button.astro";
 import Prose from "../../../components/Pros
e/Prose.astro";
 import ArticleFooter from "../../../compone
nts/ArticleFooter/ArticleFooter.astro";
 import { row2, ruleD } from "../../../lib/a
scii";
+import { renderMarkdown } from "../../../li
b/markdown";
 import { REPOS } from "../../../git.config"
;
-import { repoAvailable, repoInfo, dirEntrie
s, repoLog, readReadme, type GitRepo } from 
"../../../lib/git";
+import { repoAvailable, repoInfo, branches,
 dirEntries, repoLog, readReadme, type GitRe
po } from "../../../lib/git";
 
 export function getStaticPaths() {
   return REPOS.filter(repoAvailable).map((r
) => ({
@@ -24,10 +25,12 @@ export function getStati
cPaths() {
 
 const { repo } = Astro.props as { repo: Git
Repo };
 const info = repoInfo(repo.path);
-const entries = dirEntries(repo.path);
-const log = repoLog(repo.path, 5, `/git/${r
epo.slug}/`);
-const readme = readReadme(repo.path);
-const readmeHtml = readme ? marked.parse(re
adme) : null;
+const branch = info.branch;
+const allBranches = branches(repo.path);
+const entries = dirEntries(repo.path, "", b
ranch);
+const log = repoLog(repo.path, 5, `/git/${r
epo.slug}/`, branch);
+const readme = readReadme(repo.path, branch
);
+const readmeHtml = readme ? renderMarkdown(
readme) : null;
 
 const section = (title: string, right: stri
ng) => (w: number) =>
   [row2(title, right, w), ruleD(w)].join("\
n");
@@ -36,30 +39,37 @@ const section = (title: 
string, right: string) => (w: number) =>
   <TopHeader />
   <ArticleHeader left="OOKNET GIT" right={`
REPO: ${repo.name.toUpperCase()}`} date={inf
o.lastDate} />
   <Pre>{" "}</Pre>
-  <KeyValue entries={[
-    ["BRANCH", info.branch],
-    ["COMMITS", `${info.commits}`],
-    ...(repo.clone ? [["CLONE", repo.clone.
replace(/^https?:\/\//, "")] as [string, str
ing]] : []),
+  <ArticleMeta entries={[
+    { key: "COMMITS", value: `${info.commit
s}`, href: `/git/${repo.slug}/log/${branch}/
` },
+    { key: "RECENT", value: `${info.lastHas
h} ${info.lastSubject}`, href: `/git/${repo.
slug}/commit/${info.lastHash}/` },
+    ...(repo.clone ? [{ key: "CLONE", value
: repo.clone.replace(/^https?:\/\//, "") }] 
: []),
   ]} />
   <Pre>{" "}</Pre>
 
-  <Frame build={section("FILES", `HEAD @ ${
info.branch}`)} />
+  <Frame build={section("FILES", `${entries
.length} ENTRIES`)} />
   <Pre>{" "}</Pre>
-  <GitListing entries={entries} treeBase={`
/git/${repo.slug}/tree/`} blobBase={`/git/${
repo.slug}/blob/`} commitBase={`/git/${repo.
slug}/commit/`} />
+  <Pre><Menu label={`BRANCH: ${branch}`} it
ems={allBranches.map((b) => ({
+    label: b,
+    href: b === branch ? `/git/${repo.slug}
/` : `/git/${repo.slug}/tree/${b}/`,
+  }))} /></Pre>
   <Pre>{" "}</Pre>
-
-  <Frame build={section("LOG", "most recent
 first")} />
-  <Pre>{" "}</Pre>
-  <Timeline entries={log} />
-  <Pre><Button label="FULL LOG" href={`/git
/${repo.slug}/log/`} /></Pre>
+  <GitListing entries={entries}
+    treeBase={`/git/${repo.slug}/tree/${bra
nch}/`}
+    blobBase={`/git/${repo.slug}/blob/${bra
nch}/`}
+    commitBase={`/git/${repo.slug}/commit/`
} />
   <Pre>{" "}</Pre>
 
   {readmeHtml && (
     <Fragment>
-      <Frame build={section("README", "rend
ered from HEAD")} />
+      <Frame build={section("README", `rend
ered @ ${branch}`)} />
       <Prose><div set:html={readmeHtml} /><
/Prose>
       <Pre>{" "}</Pre>
     </Fragment>
   )}
+  <Frame build={section("LOG", "most recent
 first")} />
+  <Pre>{" "}</Pre>
+  <Timeline entries={log} />
+  <Pre><Button label="FULL LOG" href={`/git
/${repo.slug}/log/${branch}/`} /></Pre>
+  <Pre>{" "}</Pre>
   <ArticleFooter />
 </ArticleLayout>
 

diff --git a/ooknet-design/src/pages/git/[repo]/log.astro b/ooknet-design/src/page
s/git/[repo]/log.astro
deleted file mode 100644
index 337ed4a..0000000
--- a/ooknet-design/src/pages/git/[repo]/log.astro
+++ /dev/null
@@ -1,31 +0,0 @@
----
-import ArticleLayout from "../../../layouts/ArticleLayout/ArticleLayout.astro";
-import TopHeader from "../../../components/TopHeader/TopHeader.astro";
-import ArticleHeader from "../../../components/ArticleHeader/ArticleHeader.astro"
;
-import Pre from "../../../components/Pre/Pre.astro";
-import Timeline from "../../../components/Timeline/Timeline.astro";
-import Button from "../../../components/Button/Button.astro";
-import ArticleFooter from "../../../components/ArticleFooter/ArticleFooter.astro"
;
-import { REPOS } from "../../../git.config";
-import { repoAvailable, repoInfo, repoLog, type GitRepo } from "../../../lib/git"
;
-
-export function getStaticPaths() {
-  return REPOS.filter(repoAvailable).map((r) => ({
-    params: { repo: r.slug },
-    props: { repo: r },
-  }));
-}
-
-const { repo } = Astro.props as { repo: GitRepo };
-const info = repoInfo(repo.path);
-const log = repoLog(repo.path, 200, `/git/${repo.slug}/`);
----
-<ArticleLayout title={`GIT - ${repo.name} - log`} description={`Commit log for ${
repo.name}.`}>
-  <TopHeader />
-  <ArticleHeader left="OOKNET GIT" right={`LOG: ${repo.name.toUpperCase()}`} date
={info.lastDate} />
-  <Pre>{" "}</Pre>
-  <Timeline entries={log} />
-  <Pre>{" "}</Pre>
-  <Pre><Button label="BACK TO REPO" href={`/git/${repo.slug}/`} /></Pre>
-  <ArticleFooter />
-</ArticleLayout>

diff --git a/ooknet-design/src/pages/git/[re
po]/log.astro b/ooknet-design/src/pages/git/
[repo]/log.astro
deleted file mode 100644
index 337ed4a..0000000
--- a/ooknet-design/src/pages/git/[repo]/log
.astro
+++ /dev/null
@@ -1,31 +0,0 @@
----
-import ArticleLayout from "../../../layouts
/ArticleLayout/ArticleLayout.astro";
-import TopHeader from "../../../components/
TopHeader/TopHeader.astro";
-import ArticleHeader from "../../../compone
nts/ArticleHeader/ArticleHeader.astro";
-import Pre from "../../../components/Pre/Pr
e.astro";
-import Timeline from "../../../components/T
imeline/Timeline.astro";
-import Button from "../../../components/But
ton/Button.astro";
-import ArticleFooter from "../../../compone
nts/ArticleFooter/ArticleFooter.astro";
-import { REPOS } from "../../../git.config"
;
-import { repoAvailable, repoInfo, repoLog, 
type GitRepo } from "../../../lib/git";
-
-export function getStaticPaths() {
-  return REPOS.filter(repoAvailable).map((r
) => ({
-    params: { repo: r.slug },
-    props: { repo: r },
-  }));
-}
-
-const { repo } = Astro.props as { repo: Git
Repo };
-const info = repoInfo(repo.path);
-const log = repoLog(repo.path, 200, `/git/$
{repo.slug}/`);
----
-<ArticleLayout title={`GIT - ${repo.name} -
 log`} description={`Commit log for ${repo.n
ame}.`}>
-  <TopHeader />
-  <ArticleHeader left="OOKNET GIT" right={`
LOG: ${repo.name.toUpperCase()}`} date={info
.lastDate} />
-  <Pre>{" "}</Pre>
-  <Timeline entries={log} />
-  <Pre>{" "}</Pre>
-  <Pre><Button label="BACK TO REPO" href={`
/git/${repo.slug}/`} /></Pre>
-  <ArticleFooter />
-</ArticleLayout>
 

diff --git a/ooknet-design/src/pages/git/[repo]/log/[...branch].astro b/ooknet-des
ign/src/pages/git/[repo]/log/[...branch].astro
new file mode 100644
index 0000000..9453807
--- /dev/null
+++ b/ooknet-design/src/pages/git/[repo]/log/[...branch].astro
@@ -0,0 +1,35 @@
+---
+import ArticleLayout from "../../../../layouts/ArticleLayout/ArticleLayout.astro"
;
+import TopHeader from "../../../../components/TopHeader/TopHeader.astro";
+import ArticleHeader from "../../../../components/ArticleHeader/ArticleHeader.ast
ro";
+import Pre from "../../../../components/Pre/Pre.astro";
+import Timeline from "../../../../components/Timeline/Timeline.astro";
+import Button from "../../../../components/Button/Button.astro";
+import ArticleFooter from "../../../../components/ArticleFooter/ArticleFooter.ast
ro";
+import { REPOS } from "../../../../git.config";
+import { repoAvailable, branches, defaultBranch, repoInfo, repoLog, type GitRepo 
} from "../../../../lib/git";
+
+export function getStaticPaths() {
+  return REPOS.filter(repoAvailable).flatMap((r) =>
+    branches(r.path).map((branch) => ({
+      params: { repo: r.slug, branch },
+      props: { repo: r, branch },
+    })),
+  );
+}
+
+const { repo, branch } = Astro.props as { repo: GitRepo; branch: string };
+const info = repoInfo(repo.path, branch);
+const log = repoLog(repo.path, 200, `/git/${repo.slug}/`, branch);
+const isDefault = branch === defaultBranch(repo.path);
+const backHref = isDefault ? `/git/${repo.slug}/` : `/git/${repo.slug}/tree/${bra
nch}/`;
+---
+<ArticleLayout title={`GIT - ${repo.name} - log @ ${branch}`} description={`Commi
t log for ${repo.name} @ ${branch}.`}>
+  <TopHeader />
+  <ArticleHeader left="OOKNET GIT" right={`LOG: ${repo.name.toUpperCase()} @ ${br
anch.toUpperCase()}`} date={info.lastDate} />
+  <Pre>{" "}</Pre>
+  <Timeline entries={log} />
+  <Pre>{" "}</Pre>
+  <Pre><Button label="BACK TO REPO" href={backHref} /></Pre>
+  <ArticleFooter />
+</ArticleLayout>

diff --git a/ooknet-design/src/pages/git/[re
po]/log/[...branch].astro b/ooknet-design/sr
c/pages/git/[repo]/log/[...branch].astro
new file mode 100644
index 0000000..9453807
--- /dev/null
+++ b/ooknet-design/src/pages/git/[repo]/log
/[...branch].astro
@@ -0,0 +1,35 @@
+---
+import ArticleLayout from "../../../../layo
uts/ArticleLayout/ArticleLayout.astro";
+import TopHeader from "../../../../componen
ts/TopHeader/TopHeader.astro";
+import ArticleHeader from "../../../../comp
onents/ArticleHeader/ArticleHeader.astro";
+import Pre from "../../../../components/Pre
/Pre.astro";
+import Timeline from "../../../../component
s/Timeline/Timeline.astro";
+import Button from "../../../../components/
Button/Button.astro";
+import ArticleFooter from "../../../../comp
onents/ArticleFooter/ArticleFooter.astro";
+import { REPOS } from "../../../../git.conf
ig";
+import { repoAvailable, branches, defaultBr
anch, repoInfo, repoLog, type GitRepo } from
 "../../../../lib/git";
+
+export function getStaticPaths() {
+  return REPOS.filter(repoAvailable).flatMa
p((r) =>
+    branches(r.path).map((branch) => ({
+      params: { repo: r.slug, branch },
+      props: { repo: r, branch },
+    })),
+  );
+}
+
+const { repo, branch } = Astro.props as { r
epo: GitRepo; branch: string };
+const info = repoInfo(repo.path, branch);
+const log = repoLog(repo.path, 200, `/git/$
{repo.slug}/`, branch);
+const isDefault = branch === defaultBranch(
repo.path);
+const backHref = isDefault ? `/git/${repo.s
lug}/` : `/git/${repo.slug}/tree/${branch}/`
;
+---
+<ArticleLayout title={`GIT - ${repo.name} -
 log @ ${branch}`} description={`Commit log 
for ${repo.name} @ ${branch}.`}>
+  <TopHeader />
+  <ArticleHeader left="OOKNET GIT" right={`
LOG: ${repo.name.toUpperCase()} @ ${branch.t
oUpperCase()}`} date={info.lastDate} />
+  <Pre>{" "}</Pre>
+  <Timeline entries={log} />
+  <Pre>{" "}</Pre>
+  <Pre><Button label="BACK TO REPO" href={b
ackHref} /></Pre>
+  <ArticleFooter />
+</ArticleLayout>
 

diff --git a/ooknet-design/src/pages/git/[repo]/tree/[...dir].astro b/ooknet-desig
n/src/pages/git/[repo]/tree/[...dir].astro
deleted file mode 100644
index 5ef4a03..0000000
--- a/ooknet-design/src/pages/git/[repo]/tree/[...dir].astro
+++ /dev/null
@@ -1,42 +0,0 @@
----
-import ArticleLayout from "../../../../layouts/ArticleLayout/ArticleLayout.astro"
;
-import TopHeader from "../../../../components/TopHeader/TopHeader.astro";
-import ArticleHeader from "../../../../components/ArticleHeader/ArticleHeader.ast
ro";
-import Frame from "../../../../components/Frame/Frame.astro";
-import Pre from "../../../../components/Pre/Pre.astro";
-import GitListing from "../../../../components/GitListing/GitListing.astro";
-import Button from "../../../../components/Button/Button.astro";
-import ArticleFooter from "../../../../components/ArticleFooter/ArticleFooter.ast
ro";
-import { row2, ruleD, toGrid } from "../../../../lib/ascii";
-import { REPOS } from "../../../../git.config";
-import { repoAvailable, repoFiles, repoDirs, dirEntries, type GitRepo } from "../
../../../lib/git";
-
-export function getStaticPaths() {
-  return REPOS.filter(repoAvailable).flatMap((r) =>
-    repoDirs(repoFiles(r.path)).map((dir) => ({
-      params: { repo: r.slug, dir },
-      props: { repo: r, dir },
-    })),
-  );
-}
-
-const { repo, dir } = Astro.props as { repo: GitRepo; dir: string };
-const entries = dirEntries(repo.path, dir);
-const parent = dir.includes("/") ? dir.slice(0, dir.lastIndexOf("/")) : "";
-const upHref = parent ? `/git/${repo.slug}/tree/${parent}/` : `/git/${repo.slug}/
`;
-
-const section = (w: number) =>
-  [row2("FILES", `/${toGrid(dir)}`.length > 40 ? "..." + `/${toGrid(dir)}`.slice(
-37) : `/${toGrid(dir)}`, w), ruleD(w)].join("\n");
----
-<ArticleLayout title={`GIT - ${repo.name} - ${dir}`} description={`${dir} in ${re
po.name}.`}>
-  <TopHeader />
-  <ArticleHeader left="OOKNET GIT" right={`REPO: ${repo.name.toUpperCase()}`} />
-  <Pre>{" "}</Pre>
-  <Frame build={section} />
-  <Pre>{" "}</Pre>
-  <Pre><Button label="UP" href={upHref} /></Pre>
-  <Pre>{" "}</Pre>
-  <GitListing entries={entries} treeBase={`/git/${repo.slug}/tree/`} blobBase={`/
git/${repo.slug}/blob/`} commitBase={`/git/${repo.slug}/commit/`} />
-  <Pre>{" "}</Pre>
-  <ArticleFooter />
-</ArticleLayout>

diff --git a/ooknet-design/src/pages/git/[re
po]/tree/[...dir].astro b/ooknet-design/src/
pages/git/[repo]/tree/[...dir].astro
deleted file mode 100644
index 5ef4a03..0000000
--- a/ooknet-design/src/pages/git/[repo]/tre
e/[...dir].astro
+++ /dev/null
@@ -1,42 +0,0 @@
----
-import ArticleLayout from "../../../../layo
uts/ArticleLayout/ArticleLayout.astro";
-import TopHeader from "../../../../componen
ts/TopHeader/TopHeader.astro";
-import ArticleHeader from "../../../../comp
onents/ArticleHeader/ArticleHeader.astro";
-import Frame from "../../../../components/F
rame/Frame.astro";
-import Pre from "../../../../components/Pre
/Pre.astro";
-import GitListing from "../../../../compone
nts/GitListing/GitListing.astro";
-import Button from "../../../../components/
Button/Button.astro";
-import ArticleFooter from "../../../../comp
onents/ArticleFooter/ArticleFooter.astro";
-import { row2, ruleD, toGrid } from "../../
../../lib/ascii";
-import { REPOS } from "../../../../git.conf
ig";
-import { repoAvailable, repoFiles, repoDirs
, dirEntries, type GitRepo } from "../../../
../lib/git";
-
-export function getStaticPaths() {
-  return REPOS.filter(repoAvailable).flatMa
p((r) =>
-    repoDirs(repoFiles(r.path)).map((dir) =
> ({
-      params: { repo: r.slug, dir },
-      props: { repo: r, dir },
-    })),
-  );
-}
-
-const { repo, dir } = Astro.props as { repo
: GitRepo; dir: string };
-const entries = dirEntries(repo.path, dir);
-const parent = dir.includes("/") ? dir.slic
e(0, dir.lastIndexOf("/")) : "";
-const upHref = parent ? `/git/${repo.slug}/
tree/${parent}/` : `/git/${repo.slug}/`;
-
-const section = (w: number) =>
-  [row2("FILES", `/${toGrid(dir)}`.length >
 40 ? "..." + `/${toGrid(dir)}`.slice(-37) :
 `/${toGrid(dir)}`, w), ruleD(w)].join("\n")
;
----
-<ArticleLayout title={`GIT - ${repo.name} -
 ${dir}`} description={`${dir} in ${repo.nam
e}.`}>
-  <TopHeader />
-  <ArticleHeader left="OOKNET GIT" right={`
REPO: ${repo.name.toUpperCase()}`} />
-  <Pre>{" "}</Pre>
-  <Frame build={section} />
-  <Pre>{" "}</Pre>
-  <Pre><Button label="UP" href={upHref} /><
/Pre>
-  <Pre>{" "}</Pre>
-  <GitListing entries={entries} treeBase={`
/git/${repo.slug}/tree/`} blobBase={`/git/${
repo.slug}/blob/`} commitBase={`/git/${repo.
slug}/commit/`} />
-  <Pre>{" "}</Pre>
-  <ArticleFooter />
-</ArticleLayout>
 

diff --git a/ooknet-design/src/pages/git/[repo]/tree/[...rest].astro b/ooknet-desi
gn/src/pages/git/[repo]/tree/[...rest].astro
new file mode 100644
index 0000000..570a4ff
--- /dev/null
+++ b/ooknet-design/src/pages/git/[repo]/tree/[...rest].astro
@@ -0,0 +1,60 @@
+---
+// Github-shaped: tree/<branch> is the branch root, tree/<branch>/<dir>
+// a directory within it. Branch names and paths both may contain
+// slashes; getStaticPaths enumerates every valid combination so there
+// is no ambiguity to parse.
+import ArticleLayout from "../../../../layouts/ArticleLayout/ArticleLayout.astro"
;
+import TopHeader from "../../../../components/TopHeader/TopHeader.astro";
+import ArticleHeader from "../../../../components/ArticleHeader/ArticleHeader.ast
ro";
+import Frame from "../../../../components/Frame/Frame.astro";
+import Pre from "../../../../components/Pre/Pre.astro";
+import GitListing from "../../../../components/GitListing/GitListing.astro";
+import Button from "../../../../components/Button/Button.astro";
+import ArticleFooter from "../../../../components/ArticleFooter/ArticleFooter.ast
ro";
+import { row2, ruleD, toGrid } from "../../../../lib/ascii";
+import { REPOS } from "../../../../git.config";
+import { repoAvailable, branches, defaultBranch, repoFiles, repoDirs, dirEntries,
 type GitRepo } from "../../../../lib/git";
+
+export function getStaticPaths() {
+  return REPOS.filter(repoAvailable).flatMap((r) =>
+    branches(r.path).flatMap((branch) => [
+      { params: { repo: r.slug, rest: branch }, props: { repo: r, branch, dir: ""
 } },
+      ...repoDirs(repoFiles(r.path, branch)).map((dir) => ({
+        params: { repo: r.slug, rest: `${branch}/${dir}` },
+        props: { repo: r, branch, dir },
+      })),
+    ]),
+  );
+}
+
+const { repo, branch, dir } = Astro.props as { repo: GitRepo; branch: string; dir
: string };
+const entries = dirEntries(repo.path, dir, branch);
+const isDefault = branch === defaultBranch(repo.path);
+const parent = dir.includes("/") ? dir.slice(0, dir.lastIndexOf("/")) : "";
+const upHref = dir
+  ? parent
+    ? `/git/${repo.slug}/tree/${branch}/${parent}/`
+    : isDefault
+      ? `/git/${repo.slug}/`
+      : `/git/${repo.slug}/tree/${branch}/`
+  : `/git/${repo.slug}/`;
+
+const where = `/${toGrid(dir)}`;
+const section = (w: number) =>
+  [row2(`FILES @ ${branch}`, where.length > 30 ? "..." + where.slice(-27) : where
, w), ruleD(w)].join("\n");
+---
+<ArticleLayout title={`GIT - ${repo.name} - ${dir || branch}`} description={`${di
r || branch} in ${repo.name}.`}>
+  <TopHeader />
+  <ArticleHeader left="OOKNET GIT" right={`REPO: ${repo.name.toUpperCase()}`} />
+  <Pre>{" "}</Pre>
+  <Frame build={section} />
+  <Pre>{" "}</Pre>
+  <Pre><Button label="UP" href={upHref} />{"  "}<Button label="LOG" href={`/git/$
{repo.slug}/log/${branch}/`} /></Pre>
+  <Pre>{" "}</Pre>
+  <GitListing entries={entries}
+    treeBase={`/git/${repo.slug}/tree/${branch}/`}
+    blobBase={`/git/${repo.slug}/blob/${branch}/`}
+    commitBase={`/git/${repo.slug}/commit/`} />
+  <Pre>{" "}</Pre>
+  <ArticleFooter />
+</ArticleLayout>

diff --git a/ooknet-design/src/pages/git/[re
po]/tree/[...rest].astro b/ooknet-design/src
/pages/git/[repo]/tree/[...rest].astro
new file mode 100644
index 0000000..570a4ff
--- /dev/null
+++ b/ooknet-design/src/pages/git/[repo]/tre
e/[...rest].astro
@@ -0,0 +1,60 @@
+---
+// Github-shaped: tree/<branch> is the bran
ch root, tree/<branch>/<dir>
+// a directory within it. Branch names and 
paths both may contain
+// slashes; getStaticPaths enumerates every
 valid combination so there
+// is no ambiguity to parse.
+import ArticleLayout from "../../../../layo
uts/ArticleLayout/ArticleLayout.astro";
+import TopHeader from "../../../../componen
ts/TopHeader/TopHeader.astro";
+import ArticleHeader from "../../../../comp
onents/ArticleHeader/ArticleHeader.astro";
+import Frame from "../../../../components/F
rame/Frame.astro";
+import Pre from "../../../../components/Pre
/Pre.astro";
+import GitListing from "../../../../compone
nts/GitListing/GitListing.astro";
+import Button from "../../../../components/
Button/Button.astro";
+import ArticleFooter from "../../../../comp
onents/ArticleFooter/ArticleFooter.astro";
+import { row2, ruleD, toGrid } from "../../
../../lib/ascii";
+import { REPOS } from "../../../../git.conf
ig";
+import { repoAvailable, branches, defaultBr
anch, repoFiles, repoDirs, dirEntries, type 
GitRepo } from "../../../../lib/git";
+
+export function getStaticPaths() {
+  return REPOS.filter(repoAvailable).flatMa
p((r) =>
+    branches(r.path).flatMap((branch) => [
+      { params: { repo: r.slug, rest: branc
h }, props: { repo: r, branch, dir: "" } },
+      ...repoDirs(repoFiles(r.path, branch)
).map((dir) => ({
+        params: { repo: r.slug, rest: `${br
anch}/${dir}` },
+        props: { repo: r, branch, dir },
+      })),
+    ]),
+  );
+}
+
+const { repo, branch, dir } = Astro.props a
s { repo: GitRepo; branch: string; dir: stri
ng };
+const entries = dirEntries(repo.path, dir, 
branch);
+const isDefault = branch === defaultBranch(
repo.path);
+const parent = dir.includes("/") ? dir.slic
e(0, dir.lastIndexOf("/")) : "";
+const upHref = dir
+  ? parent
+    ? `/git/${repo.slug}/tree/${branch}/${p
arent}/`
+    : isDefault
+      ? `/git/${repo.slug}/`
+      : `/git/${repo.slug}/tree/${branch}/`
+  : `/git/${repo.slug}/`;
+
+const where = `/${toGrid(dir)}`;
+const section = (w: number) =>
+  [row2(`FILES @ ${branch}`, where.length >
 30 ? "..." + where.slice(-27) : where, w), 
ruleD(w)].join("\n");
+---
+<ArticleLayout title={`GIT - ${repo.name} -
 ${dir || branch}`} description={`${dir || b
ranch} in ${repo.name}.`}>
+  <TopHeader />
+  <ArticleHeader left="OOKNET GIT" right={`
REPO: ${repo.name.toUpperCase()}`} />
+  <Pre>{" "}</Pre>
+  <Frame build={section} />
+  <Pre>{" "}</Pre>
+  <Pre><Button label="UP" href={upHref} />{
"  "}<Button label="LOG" href={`/git/${repo.
slug}/log/${branch}/`} /></Pre>
+  <Pre>{" "}</Pre>
+  <GitListing entries={entries}
+    treeBase={`/git/${repo.slug}/tree/${bra
nch}/`}
+    blobBase={`/git/${repo.slug}/blob/${bra
nch}/`}
+    commitBase={`/git/${repo.slug}/commit/`
} />
+  <Pre>{" "}</Pre>
+  <ArticleFooter />
+</ArticleLayout>
 
[ BACK TO LOG ]
──────────────────────────────────────────────────────────────────────────────────────
OOKNET
────────────────────────────────────────────────
OOKNET