▍ humdrum codex / custard
license AGPL-3.0

custard — web code forge over Soft Serve

62366fb34f1ee8d458104784f3f4ba5f0bef9429
humdrum-tiv <45084903+humdrum-tiv@users.noreply.github.com> · 2026-06-17 19:50

custard — web code forge over Soft Serve

A read-only, public-facing web UI over a self-hosted Soft Serve git server:
repo browser, syntax-highlighted blobs, commit log + per-file diffs, branches
and tags, and a GitHub-style issues view backed by in-repo Backlog.md tasks.
Themed on the shared app-kit token system with a Bubble Tea / Charm aesthetic
(7 themes incl. e-ink). Pure Go (go-git, templ, chroma, goldmark).

Repo visibility honors Soft Serve's private/hidden flags — only public repos
are served. Deployed behind Caddy at https://git.kortum.world.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

29 files changed

.gitignore +0 −17
@@ -1,17 +0,0 @@
-/custard
-/repos/
-*.out
-
-# Proprietary fonts — present on disk for build-time go:embed, never committed.
-web/static/fonts/
-
-# Agent / assistant instruction files — kept local, never published (this repo is public).
-CLAUDE.md
-AGENTS.md
-GEMINI.md
-.cursor/
-.cursorrules
-.windsurfrules
-.aider*
-.claude/
-.github/copilot-instructions.md
PLAN.md +0 −110
@@ -1,110 +0,0 @@
-# custard — build plan
-
-A custom, public-facing web code forge over my self-hosted **Soft Serve** git server.
-Own design — not a skinned cgit/gitweb. Reads bare repos directly; renders my own
-templates. Aesthetic = **Bubble Tea / Charm terminal vibe** + **_shared-app-kit** tokens
-(Flexoki / Uchu / Humdrum themes, Awke / Untitled Sans / Name Mono fonts).
-
-> Soft Serve is the git host (SSH push/TUI, read-only HTTP clone, webhooks). custard is a
-> separate read-only Go web app pointed at the same bare repos on disk. It never writes git.
-
-## Decisions (locked)
-
-- **Stack:** pure Go (1.26). `go-git/v5` reads bare repos directly off disk — no shelling to
-  `git`. `templ` for typed templates, `chroma` for syntax highlight, `goldmark` for markdown,
-  `gopkg.in/yaml.v3` for backlog frontmatter. Router = stdlib `net/http` (Go 1.22+
-  method+pattern mux) — no web framework.
-- **Render:** dynamic server. Each request reads the bare repo live → template → HTML. Always
-  fresh; leaves room for private-repo auth later. **Caddy** in front for auto-TLS.
-- **Hosting:** all-on-droplet. `Caddy → custard` (reads `./repos/*.git`); Caddy reverse-proxies
-  `/git/<repo>.git/*` → Soft Serve HTTP (`:23232`) for clone, and serves `/dl/*` tarballs.
-- **Tokens are plain CSS custom properties** → port `tokens.css` + fonts straight in; no
-  Tailwind/Next needed. Theme via `data-theme` on `<html>` + cookie (app-kit pattern), default
-  `flexoki`.
-- **jj:** Soft Serve is git-only; jj repos colocate a `.git`, so go-git reads them unchanged.
-  Optionally show a "jj" badge when `.jj/` is present. No special handling required.
-
-## Layout
-
-```
-custard/
-├─ cmd/custard/main.go        flags/env, http.Server, graceful shutdown
-├─ internal/
-│  ├─ config/                 REPOS_PATH, SOFT_SERVE_HTTP, LISTEN_ADDR, BASE_URL
-│  ├─ gitread/                go-git wrappers: list, refs, tree, blob, log, commit, archive
-│  ├─ backlog/                parse backlog/tasks/*.md → Task{frontmatter + md body}
-│  ├─ render/                 chroma highlight, goldmark md, byte/lang detection
-│  └─ server/                 handlers, routes, middleware, error pages
-├─ web/
-│  ├─ templates/*.templ       layout, list, repo, tree, blob, log, commit, refs, issues, issue
-│  └─ static/
-│     ├─ tokens.css           ported from _shared-app-kit (Next bits stripped)
-│     ├─ custard.css          app chrome — terminal/Charm look on the token system
-│     ├─ theme.js             data-theme cookie toggle (7 modes)
-│     └─ fonts/               Awke, Untitled Sans, Name Mono (.woff2)
-├─ Caddyfile
-├─ deploy/                    systemd unit + release.sh
-├─ go.mod  PLAN.md  CLAUDE.md
-```
-
-## URL scheme (cgit-inspired)
-
-| Route | View |
-|---|---|
-| `/` | repo list (public, non-hidden) — name, description, last commit |
-| `/r/<repo>` | repo home: README render + branch/tag/commit summary |
-| `/r/<repo>/tree/<ref>/<path>` | directory browse |
-| `/r/<repo>/blob/<ref>/<path>` | file view — chroma highlight, or goldmark for `.md` |
-| `/r/<repo>/raw/<ref>/<path>` | raw bytes |
-| `/r/<repo>/log/<ref>` | commit log (paged) |
-| `/r/<repo>/commit/<sha>` | commit diff |
-| `/r/<repo>/refs` | branches + tags |
-| `/r/<repo>/issues` | **backlog tasks, GitHub-issues style** — filter by status/label |
-| `/r/<repo>/issues/<id>` | single task: rendered body + acceptance criteria |
-| `/dl/<repo>/<file>.tar.gz` | release tarball *(phase 4)* |
-| `/git/<repo>.git/*` | clone — Caddy reverse-proxy → Soft Serve HTTP *(deploy)* |
-
-## Backlog → issues view (the GitHub-issues surface)
-
-Issues already travel in-repo at `backlog/tasks/*.md` (YAML frontmatter + Markdown body), so
-**no separate data source** — go-git reads them from the chosen ref like any other file.
-
-- `internal/backlog` reads `backlog/tasks/`, splits frontmatter (`id, title, status, labels,
-  assignee, dependencies, created_date, updated_date, ordinal`) from the md body, parses with
-  `yaml.v3`, renders body via goldmark.
-- Issues list: group/filter by `status` (To Do / In Progress / Done), color chips from
-  `labels` (feature/bug/…) mapped onto theme color tokens. Sort by `ordinal`.
-- Issue detail: title, status badge, labels, dates, dependencies, then rendered body
-  (description + acceptance criteria checklist).
-- Read-only (a public forge view). No create/edit — that stays in the `backlog` CLI + git push.
-- Repos with no `backlog/tasks/` dir simply hide the Issues tab.
-- **Nice loop:** custard's own backlog renders inside custard once deployed.
-
-## Phases
-
-1. **Read layer + core views** — go-git wrappers + handlers for list, repo, tree, blob, log,
-   commit, refs. Unstyled HTML. Point at a local clone of `./repos/*.git` for dev.
-2. **Issues view** — `internal/backlog` parser + `/issues` list and detail.
-3. **Styling pass** — port `tokens.css` + fonts, build `custard.css` (terminal/Charm chrome),
-   theme switcher (7 modes, cookie, no-flash SSR), polish all views.
-4. **Homebrew tap + release pipeline** — `git archive` tag → `name-X.Y.Z.tar.gz` in `/dl` +
-   sha256; `homebrew-tap` repo in Soft Serve; GoReleaser emits formula, ~10-line script bumps
-   `url`/`sha256`/`version` and pushes the tap. Source-build formula (no GitHub, no bottles).
-5. **Deploy** — Caddyfile (auto-TLS), systemd unit, reverse-proxy `/git/*` → Soft Serve
-   `:23232`, serve `/dl`. Run on droplet next to Soft Serve's data dir.
-
-## Dev commands (target)
-
-```bash
-go install github.com/a-h/templ/cmd/templ@latest   # one-time: templ generator
-templ generate                                      # .templ → _templ.go
-go run ./cmd/custard --repos ./repos --addr :8080   # local dev
-go build -o custard ./cmd/custard                    # release binary
-```
-
-## Open / later
-
-- Private repos: dynamic server can gate later (read Soft Serve auth model); public-only first.
-- Search across blobs/commits — defer.
-- Atom feeds per repo (cgit has them) — cheap add later.
-- Self-hosting = I own uptime/TLS; keep `/dl` + Caddy reliable so `brew install` never breaks.
README.md +0 −30
@@ -1,30 +0,0 @@
-# custard
-
-A custom, public-facing **web code forge** over a self-hosted
-[Soft Serve](https://github.com/charmbracelet/soft-serve) git server. custard reads the
-same bare repos Soft Serve hosts — directly off disk via go-git — and renders its own
-templates. It never writes git; pushing and admin stay in Soft Serve over SSH.
-
-The look is the [Bubble Tea / Charm](https://charm.sh) terminal aesthetic layered on the
-_shared-app-kit token system (Flexoki / Uchu / Humdrum themes, Awke / Untitled Sans /
-Name Mono type).
-
-## Features
-
-- Repo list, tree, blob (syntax-highlighted), raw, commit log, commit diffs, branches & tags
-- **Issues** — renders each repo's in-repo `backlog/tasks/*.md` ([Backlog.md](https://backlog.md))
-  GitHub-style, grouped by the repo's own configured statuses; first label = type badge
-- 7 themes via a cookie-backed switcher (flexoki / uchu / humdrum, light + dark, plus e-ink),
-  flash-free server-side render
-- Syntax + diff highlighting (chroma), Markdown (goldmark), all colored through theme tokens
-
-## Develop
-
-```bash
-go install github.com/a-h/templ/cmd/templ@latest   # one-time
-templ generate                                      # after editing *.templ
-go run ./cmd/custard --repos ./repos --addr :8080
-go test ./...
-```
-
-See `PLAN.md` for the phased build plan.
backlog/config.yml +0 −17
@@ -1,17 +0,0 @@
-project_name: "custard"
-default_status: "🟦 Backlog"
-statuses: ["🟦 Backlog", "🟢 In progress", "🚧 Paused", "🏁 Done"]
-labels: []
-date_format: yyyy-mm-dd
-max_column_width: 20
-default_editor: "micro"
-auto_open_browser: true
-default_port: 6420
-remote_operations: false
-auto_commit: false
-filesystem_only: false
-zero_padded_ids: 3
-bypass_git_hooks: false
-check_active_branches: true
-active_branch_days: 30
-task_prefix: "TASK"
- → Phase-1-go-git-read-layer-core-repo-views.md +0 −27
@@ -1,29 +0,0 @@
----
-id: TASK-001
-title: 'Phase 1: go-git read layer + core repo views'
-status: "\U0001F3C1 Done"
-assignee: []
-created_date: '2026-06-17 23:44'
-updated_date: '2026-06-18 00:24'
-labels:
-  - feature
-dependencies: []
-priority: high
-ordinal: 1000
----
-
-## Description
-
-<!-- SECTION:DESCRIPTION:BEGIN -->
-Build internal/gitread (the only package touching go-git) plus internal/server handlers and templ templates for the core read views. Dynamic server: each request reads bare repos live off REPOS_PATH. Point at a local ./repos/*.git for dev. Unstyled HTML is fine here; styling is phase 3.
-<!-- SECTION:DESCRIPTION:END -->
-
-## Acceptance Criteria
-<!-- AC:BEGIN -->
-- [x] #1 go.mod initialized, deps added (go-git/v5, templ, chroma, goldmark, yaml.v3)
-- [x] #2 cmd/custard/main.go: flags/env (--repos, --addr), http.Server with graceful shutdown
-- [x] #3 internal/config resolves REPOS_PATH, SOFT_SERVE_HTTP, LISTEN_ADDR, BASE_URL
-- [x] #4 internal/gitread wraps go-git: list repos, refs, tree-at-ref, blob bytes, commit, log page
-- [x] #5 Routes + templ views: / (repo list), /r/<repo>, tree, blob, raw, log, commit, refs
-- [x] #6 stdlib net/http method+pattern mux; handlers never open repos directly
-<!-- AC:END -->
- → Phase-2-backlog-issues-view-GitHub-style.md +0 −26
@@ -1,28 +0,0 @@
----
-id: TASK-002
-title: 'Phase 2: backlog issues view (GitHub-style)'
-status: "\U0001F3C1 Done"
-assignee: []
-created_date: '2026-06-17 23:44'
-updated_date: '2026-06-18 00:24'
-labels:
-  - feature
-dependencies: []
-priority: high
-ordinal: 2000
----
-
-## Description
-
-<!-- SECTION:DESCRIPTION:BEGIN -->
-Render the repo's own backlog/tasks/*.md as a read-only GitHub-issues-style surface. go-git reads the files in-repo at the chosen ref; no separate store. Editing stays in backlog CLI + push.
-<!-- SECTION:DESCRIPTION:END -->
-
-## Acceptance Criteria
-<!-- AC:BEGIN -->
-- [x] #1 internal/backlog reads backlog/tasks/ via gitread, splits YAML frontmatter from md body (yaml.v3)
-- [x] #2 /r/<repo>/issues: list grouped/filtered by status, label chips mapped to theme colors, sorted by ordinal
-- [x] #3 /r/<repo>/issues/<id>: title, status badge, labels, dates, dependencies, goldmark-rendered body + AC
-- [x] #4 Repos with no backlog/tasks/ dir hide the Issues tab
-- [x] #5 Read-only: no create/edit in the web UI
-<!-- AC:END -->
- → Phase-3-styling-pass-Charm-vibe-app-kit-tokens.md +0 −31
@@ -1,33 +0,0 @@
----
-id: TASK-003
-title: 'Phase 3: styling pass (Charm vibe + app-kit tokens)'
-status: "\U0001F3C1 Done"
-assignee: []
-created_date: '2026-06-17 23:44'
-updated_date: '2026-06-18 01:30'
-labels:
-  - feature
-dependencies: []
-priority: medium
-ordinal: 3000
----
-
-## Description
-
-<!-- SECTION:DESCRIPTION:BEGIN -->
-Port the app-kit token system and fonts, build custard.css with the Bubble Tea/Charm terminal look, and add the theme switcher. Apply across all views from phases 1-2.
-<!-- SECTION:DESCRIPTION:END -->
-
-## Acceptance Criteria
-<!-- AC:BEGIN -->
-- [x] #1 web/static/tokens.css ported from _shared-app-kit (Tailwind/Next wiring stripped)
-- [x] #2 Fonts in web/static/fonts: Awke, Untitled Sans, Name Mono (.woff2)
-- [x] #3 custard.css: terminal/Charm chrome built entirely on tokens; no hard-coded colors
-- [x] #4 Theme switcher: 7 data-theme modes, cookie persistence, server reads cookie for no-flash SSR; default flexoki
-- [x] #5 All views (list/repo/tree/blob/log/commit/refs/issues) styled and legible incl eink mode
-- [x] #6 Commit diffs syntax-highlighted (chroma diff lexer); per-file split + add/del line coloring
-- [x] #7 Issues + issue detail fully themed: status columns, type badge, label chips on tokens
-- [x] #8 Markdown (READMEs, issue bodies) styled via tokens; evaluate Glamour/Glow CSS as reference
-- [x] #9 Chroma highlight theme wired to data-theme (light/dark/eink) instead of fixed github style
-- [x] #10 Full token-level code highlighting styled via tokens: keyword/name/variable/function/string/number/comment/operator/builtin/punctuation mapped to color families (covers shell commands+vars), light/dark/eink
-<!-- AC:END -->
- → Phase-4-Homebrew-tap-release-pipeline.md +0 −25
@@ -1,27 +0,0 @@
----
-id: TASK-004
-title: 'Phase 4: Homebrew tap + release pipeline'
-status: "\U0001F7E6 Backlog"
-assignee: []
-created_date: '2026-06-17 23:44'
-updated_date: '2026-06-18 00:24'
-labels:
-  - feature
-dependencies: []
-priority: low
-ordinal: 4000
----
-
-## Description
-
-<!-- SECTION:DESCRIPTION:BEGIN -->
-Self-hosted Homebrew distribution with no GitHub. git archive a tag to a source tarball + sha256 served at /dl, push a generated formula to a homebrew-tap repo in Soft Serve.
-<!-- SECTION:DESCRIPTION:END -->
-
-## Acceptance Criteria
-<!-- AC:BEGIN -->
-- [ ] #1 Release script: git archive <tag> -> name-X.Y.Z.tar.gz in /dl, compute sha256
-- [ ] #2 homebrew-tap repo in Soft Serve; source-build formula (depends_on go => build, no bottles)
-- [ ] #3 GoReleaser emits formula; script bumps url/sha256/version and pushes the tap repo
-- [ ] #4 brew tap <you>/tap https://<you>/git/homebrew-tap.git then brew install works end to end
-<!-- AC:END -->
- → Phase-5-deploy-on-droplet-Caddy-systemd.md +0 −24
@@ -1,26 +0,0 @@
----
-id: TASK-005
-title: 'Phase 5: deploy on droplet (Caddy + systemd)'
-status: "\U0001F7E6 Backlog"
-assignee: []
-created_date: '2026-06-17 23:44'
-updated_date: '2026-06-18 00:24'
-labels:
-  - feature
-dependencies: []
-priority: low
-ordinal: 5000
----
-
-## Description
-
-<!-- SECTION:DESCRIPTION:BEGIN -->
-Run custard on the droplet next to Soft Serve's data dir. Caddy fronts it for auto-TLS and proxies clone + tarball paths.
-<!-- SECTION:DESCRIPTION:END -->
-
-## Acceptance Criteria
-<!-- AC:BEGIN -->
-- [ ] #1 Caddyfile: auto-TLS; reverse-proxy /git/<repo>.git/* -> Soft Serve HTTP :23232; serve /dl/*
-- [ ] #2 systemd unit for custard with REPOS_PATH pointed at Soft Serve repos dir
-- [ ] #3 Public read of all non-hidden repos verified live; git clone via /git/* works
-<!-- AC:END -->
cmd/custard/main.go +0 −49
@@ -1,49 +0,0 @@
-// Command custard is a read-only web code forge over bare git repositories.
-package main
-
-import (
-	"context"
-	"errors"
-	"log"
-	"net/http"
-	"os"
-	"os/signal"
-	"syscall"
-	"time"
-
-	"git.kortum.world/custard/internal/config"
-	"git.kortum.world/custard/internal/server"
-)
-
-func main() {
-	cfg := config.Load()
-	srv, err := server.New(cfg)
-	if err != nil {
-		log.Fatalf("init: %v", err)
-	}
-
-	httpSrv := &http.Server{
-		Addr:              cfg.ListenAddr,
-		Handler:           srv.Handler(),
-		ReadHeaderTimeout: 10 * time.Second,
-	}
-
-	// Serve until an interrupt, then drain in flight requests.
-	go func() {
-		log.Printf("custard listening on %s (repos: %s)", cfg.ListenAddr, cfg.ReposPath)
-		if err := httpSrv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
-			log.Fatalf("server error: %v", err)
-		}
-	}()
-
-	stop := make(chan os.Signal, 1)
-	signal.Notify(stop, os.Interrupt, syscall.SIGTERM)
-	<-stop
-
-	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
-	defer cancel()
-	if err := httpSrv.Shutdown(ctx); err != nil {
-		log.Printf("shutdown error: %v", err)
-	}
-	log.Println("custard stopped")
-}
deploy/Caddyfile +0 −20
@@ -1,20 +0,0 @@
-# custard — Caddy reverse proxy with automatic TLS.
-# Web UI on 443; /git/* proxied to Soft Serve's HTTP backend for read-only clone.
-git.kortum.world {
-	encode gzip zstd
-
-	# Read-only HTTPS git clone → Soft Serve HTTP (handle_path strips /git):
-	#   git clone https://git.kortum.world/git/<repo>.git
-	handle_path /git/* {
-		reverse_proxy 127.0.0.1:23232
-	}
-
-	# Release tarballs (populated by the phase-4 Homebrew pipeline).
-	handle_path /dl/* {
-		root * /var/lib/custard/dl
-		file_server browse
-	}
-
-	# Everything else → the custard web app.
-	reverse_proxy 127.0.0.1:8080
-}
deploy/custard.service +0 −28
@@ -1,28 +0,0 @@
-[Unit]
-Description=custard — web code forge over Soft Serve
-After=network-online.target soft-serve.service
-Wants=network-online.target
-
-[Service]
-# Runs as the soft-serve user so it can read the bare repos (read-only).
-User=soft-serve
-Group=soft-serve
-ExecStart=/usr/local/bin/custard \
-  --repos /var/lib/soft-serve/repos \
-  --addr 127.0.0.1:8080 \
-  --base-url https://git.kortum.world \
-  --soft-serve-http https://git.kortum.world/git \
-  --soft-serve-db /var/lib/soft-serve/soft-serve.db
-Restart=on-failure
-RestartSec=2
-
-# Hardening. ProtectSystem=full (not strict) keeps /var writable so SQLite can
-# manage the db's -wal/-shm sidecars; the repos tree stays read-only.
-NoNewPrivileges=true
-ProtectSystem=full
-ProtectHome=true
-PrivateTmp=true
-ReadOnlyPaths=/var/lib/soft-serve/repos
-
-[Install]
-WantedBy=multi-user.target
deploy/deploy.sh +0 −54
@@ -1,54 +0,0 @@
-#!/usr/bin/env bash
-#
-# deploy.sh — build custard for linux/amd64 and deploy it to the droplet behind
-# Caddy + systemd, next to Soft Serve. Idempotent; safe to re-run for updates.
-#
-# Usage: deploy/deploy.sh <user@host>   (or set CUSTARD_DEPLOY_REMOTE)
-#
-set -euo pipefail
-
-REMOTE="${1:-${CUSTARD_DEPLOY_REMOTE:-}}"
-if [ -z "$REMOTE" ]; then
-	echo "usage: deploy/deploy.sh <user@host>   (or set CUSTARD_DEPLOY_REMOTE)" >&2
-	exit 1
-fi
-REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
-cd "$REPO_ROOT"
-
-echo "==> generating templates + building linux/amd64 binary"
-templ generate
-GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o /tmp/custard-linux ./cmd/custard
-
-echo "==> uploading binary + unit + Caddyfile to $REMOTE"
-scp /tmp/custard-linux "$REMOTE:/usr/local/bin/custard.new"
-scp deploy/custard.service "$REMOTE:/etc/systemd/system/custard.service"
-scp deploy/Caddyfile "$REMOTE:/etc/caddy/Caddyfile.custard"
-
-echo "==> installing/configuring on remote"
-ssh "$REMOTE" 'bash -seu' <<'REMOTE_EOF'
-# Install Caddy if missing (official apt repo).
-if ! command -v caddy >/dev/null 2>&1; then
-  apt-get install -y debian-keyring debian-archive-keyring apt-transport-https curl
-  curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
-  curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' > /etc/apt/sources.list.d/caddy-stable.list
-  apt-get update && apt-get install -y caddy
-fi
-
-# Use our Caddyfile.
-mv /etc/caddy/Caddyfile.custard /etc/caddy/Caddyfile
-
-# Swap binary atomically + restart.
-mv /usr/local/bin/custard.new /usr/local/bin/custard
-chmod +x /usr/local/bin/custard
-
-systemctl daemon-reload
-systemctl enable --now custard
-systemctl restart custard
-systemctl reload caddy || systemctl restart caddy
-
-sleep 1
-echo "--- custard ---"; systemctl --no-pager --lines=5 status custard | tail -6
-echo "--- local probe ---"; curl -s -o /dev/null -w 'custard 127.0.0.1:8080 -> %{http_code}\n' http://127.0.0.1:8080/ || true
-REMOTE_EOF
-
-echo "==> done. https://git.kortum.world"
go.mod +0 −45
@@ -1,45 +0,0 @@
-module git.kortum.world/custard
-
-go 1.26.4
-
-require (
-	github.com/a-h/templ v0.3.1020
-	github.com/alecthomas/chroma/v2 v2.27.0
-	github.com/go-git/go-git/v5 v5.19.1
-	github.com/yuin/goldmark v1.8.2
-	github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
-	gopkg.in/yaml.v3 v3.0.1
-	modernc.org/sqlite v1.52.0
-)
-
-require (
-	dario.cat/mergo v1.0.0 // indirect
-	github.com/Microsoft/go-winio v0.6.2 // indirect
-	github.com/ProtonMail/go-crypto v1.1.6 // indirect
-	github.com/cloudflare/circl v1.6.3 // indirect
-	github.com/cyphar/filepath-securejoin v0.6.1 // indirect
-	github.com/dlclark/regexp2/v2 v2.2.1 // indirect
-	github.com/dustin/go-humanize v1.0.1 // indirect
-	github.com/emirpasic/gods v1.18.1 // indirect
-	github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
-	github.com/go-git/go-billy/v5 v5.9.0 // indirect
-	github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
-	github.com/google/uuid v1.6.0 // indirect
-	github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
-	github.com/kevinburke/ssh_config v1.2.0 // indirect
-	github.com/klauspost/cpuid/v2 v2.3.0 // indirect
-	github.com/mattn/go-isatty v0.0.20 // indirect
-	github.com/ncruces/go-strftime v1.0.0 // indirect
-	github.com/pjbgf/sha1cd v0.6.0 // indirect
-	github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
-	github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
-	github.com/skeema/knownhosts v1.3.1 // indirect
-	github.com/xanzy/ssh-agent v0.3.3 // indirect
-	golang.org/x/crypto v0.50.0 // indirect
-	golang.org/x/net v0.53.0 // indirect
-	golang.org/x/sys v0.43.0 // indirect
-	gopkg.in/warnings.v0 v0.1.2 // indirect
-	modernc.org/libc v1.72.3 // indirect
-	modernc.org/mathutil v1.7.1 // indirect
-	modernc.org/memory v1.11.0 // indirect
-)
go.sum +0 −176
@@ -1,176 +0,0 @@
-dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
-dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
-github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
-github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
-github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
-github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw=
-github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
-github.com/a-h/templ v0.3.1020 h1:ypAT/L5ySWEnZ6Zft/5yfoWXYYkhFNvEFOeeqecg4tw=
-github.com/a-h/templ v0.3.1020/go.mod h1:A2DlK61v+K+NRoGnhmYbNYVmtYHcFO5/AisMvBdDxTM=
-github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
-github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
-github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs=
-github.com/alecthomas/chroma/v2 v2.27.0 h1:FodwmyOBgJULFYmDqibcp9pvfDLWdtPRh9v/r5BXYZs=
-github.com/alecthomas/chroma/v2 v2.27.0/go.mod h1:NjJ3ciIgrqBNeIkWZ4e46nseoLDslxU1LmfCoL+wcY8=
-github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
-github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs=
-github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
-github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
-github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
-github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
-github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
-github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=
-github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
-github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE=
-github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc=
-github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
-github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
-github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
-github.com/dlclark/regexp2/v2 v2.2.1 h1:mf4KkFUj0gJuarK8P+LgiS+Lit7m9N1yAwEfPbee7R0=
-github.com/dlclark/regexp2/v2 v2.2.1/go.mod h1:avUrQvPaLz2DrFNHJF0taWAFFX2C1GMSSoeiqFjcBmU=
-github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
-github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
-github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o=
-github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
-github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
-github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
-github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
-github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
-github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
-github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
-github.com/go-git/go-billy/v5 v5.9.0 h1:jItGXszUDRtR/AlferWPTMN4j38BQ88XnXKbilmmBPA=
-github.com/go-git/go-billy/v5 v5.9.0/go.mod h1:jCnQMLj9eUgGU7+ludSTYoZL/GGmii14RxKFj7ROgHw=
-github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
-github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
-github.com/go-git/go-git/v5 v5.19.1 h1:nX27AnaU43/K5bKktKwgBmR9lawoYVe1Ckg0rgzzN00=
-github.com/go-git/go-git/v5 v5.19.1/go.mod h1:Pb1v0c7/g8aGQJwx9Us09W85yGoyvSwuhEGMH7zjDKQ=
-github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
-github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
-github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
-github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
-github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
-github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
-github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
-github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
-github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
-github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
-github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
-github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
-github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
-github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
-github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
-github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
-github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
-github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
-github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
-github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
-github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
-github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
-github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
-github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
-github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
-github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
-github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
-github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
-github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
-github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
-github.com/pjbgf/sha1cd v0.6.0 h1:3WJ8Wz8gvDz29quX1OcEmkAlUg9diU4GxJHqs0/XiwU=
-github.com/pjbgf/sha1cd v0.6.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM=
-github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
-github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
-github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
-github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
-github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
-github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
-github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
-github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
-github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
-github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
-github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8=
-github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY=
-github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
-github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
-github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
-github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
-github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
-github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
-github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
-github.com/yuin/goldmark v1.8.2 h1:kEGpgqJXdgbkhcOgBxkC0X0PmoPG1ZyoZ117rDVp4zE=
-github.com/yuin/goldmark v1.8.2/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
-github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc h1:+IAOyRda+RLrxa1WC7umKOZRsGq4QrFFMYApOeHzQwQ=
-github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc/go.mod h1:ovIvrum6DQJA4QsJSovrkC4saKHQVs7TvcaeO8AIl5I=
-golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
-golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
-golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM=
-golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f/go.mod h1:J1xhfL/vlindoeF/aINzNzt2Bket5bjo9sdOYzOsU80=
-golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
-golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
-golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
-golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
-golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
-golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
-golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
-golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
-golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
-golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY=
-golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY=
-golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
-golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
-golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
-golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
-gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
-gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
-gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
-gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
-gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
-gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
-gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-modernc.org/cc/v4 v4.28.2 h1:3tQ0lf2ADtoby2EtSP+J7IE2SHwEJdP8ioR59wx7XpY=
-modernc.org/cc/v4 v4.28.2/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI=
-modernc.org/ccgo/v4 v4.34.0 h1:yRLPFZieg532OT4rp4JFNIVcquwalMX26G95WQDqwCQ=
-modernc.org/ccgo/v4 v4.34.0/go.mod h1:AS5WYMyBakQ+fhsHhtP8mWB82KTGPkNNJDGfGQCe0/A=
-modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM=
-modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU=
-modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
-modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
-modernc.org/gc/v3 v3.1.2 h1:ZtDCnhonXSZexk/AYsegNRV1lJGgaNZJuKjJSWKyEqo=
-modernc.org/gc/v3 v3.1.2/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
-modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
-modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
-modernc.org/libc v1.72.3 h1:ZnDF4tXn4NBXFutMMQC4vtbTFSXhhKzR73fv0beZEAU=
-modernc.org/libc v1.72.3/go.mod h1:dn0dZNnnn1clLyvRxLxYExxiKRZIRENOfqQ8XEeg4Qs=
-modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
-modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
-modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
-modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
-modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg=
-modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
-modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
-modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
-modernc.org/sqlite v1.52.0 h1:p4dhYh2tXZCiyaqHwRVJDjIGKWyXayiQpThxgDzJaxo=
-modernc.org/sqlite v1.52.0/go.mod h1:tcNzv5p84E0skkmJn038y+hWJbLQXQqEnQfeh5r2JLM=
-modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
-modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
-modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
-modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
internal/backlog/backlog.go +0 −196
@@ -1,196 +0,0 @@
-// Package backlog reads a repository's in-repo Backlog.md tasks
-// (backlog/tasks/*.md) into Task values for the GitHub-issues-style views. The
-// task files are the only data source — there is no separate issue store, and
-// these views are read-only (create/edit happens through the backlog CLI).
-package backlog
-
-import (
-	"sort"
-	"strings"
-
-	"git.kortum.world/custard/internal/gitread"
-	"gopkg.in/yaml.v3"
-)
-
-// Dir is the in-repo path Backlog.md stores tasks under.
-const Dir = "backlog/tasks"
-
-// configPath is the in-repo Backlog.md config file.
-const configPath = "backlog/config.yml"
-
-// DefaultStatuses is the fallback status order when a repo has no config.yml.
-var DefaultStatuses = []string{"To Do", "In Progress", "Done"}
-
-// Config is the subset of backlog/config.yml that custard cares about: the
-// status set (which drives issue column order) and the default status.
-type Config struct {
-	Statuses      []string `yaml:"statuses"`
-	DefaultStatus string   `yaml:"default_status"`
-}
-
-// ReadConfig parses the repo's backlog/config.yml at ref. It always returns a
-// usable Config: missing or unparseable config falls back to DefaultStatuses.
-func ReadConfig(s *gitread.Store, repo, ref string) Config {
-	cfg := Config{Statuses: DefaultStatuses, DefaultStatus: DefaultStatuses[0]}
-	data, err := s.Blob(repo, ref, configPath)
-	if err != nil {
-		return cfg
-	}
-	var parsed Config
-	if err := yaml.Unmarshal(data, &parsed); err != nil {
-		return cfg
-	}
-	if len(parsed.Statuses) > 0 {
-		cfg.Statuses = parsed.Statuses
-	}
-	if parsed.DefaultStatus != "" {
-		cfg.DefaultStatus = parsed.DefaultStatus
-	}
-	return cfg
-}
-
-// Task is one backlog task: parsed frontmatter plus the Markdown body.
-type Task struct {
-	ID       string   `yaml:"id"`
-	Title    string   `yaml:"title"`
-	Status   string   `yaml:"status"`
-	Priority string   `yaml:"priority"`
-	Labels   []string `yaml:"labels"`
-	Assignee []string `yaml:"assignee"`
-	Deps     []string `yaml:"dependencies"`
-	Ordinal  int      `yaml:"ordinal"`
-	Created  string   `yaml:"created_date"`
-	Updated  string   `yaml:"updated_date"`
-
-	Body string `yaml:"-"` // Markdown after the frontmatter
-	Slug string `yaml:"-"` // source filename without extension
-}
-
-// Key is the lowercased task id used in URLs (e.g. "task-001").
-func (t Task) Key() string { return strings.ToLower(t.ID) }
-
-// Types is the canonical label vocabulary for a task's primary type.
-var Types = []string{"feature", "bug", "chore", "docs", "refactor"}
-
-// Type is the task's primary type: its first label, lowercased ("" if none).
-func (t Task) Type() string {
-	if len(t.Labels) == 0 {
-		return ""
-	}
-	return strings.ToLower(t.Labels[0])
-}
-
-// OtherLabels are the labels after the primary type.
-func (t Task) OtherLabels() []string {
-	if len(t.Labels) <= 1 {
-		return nil
-	}
-	return t.Labels[1:]
-}
-
-// Has reports whether the repo carries any backlog tasks at ref.
-func Has(s *gitread.Store, repo, ref string) bool {
-	entries, err := s.Tree(repo, ref, Dir)
-	if err != nil {
-		return false
-	}
-	for _, e := range entries {
-		if !e.IsDir && strings.HasSuffix(e.Name, ".md") {
-			return true
-		}
-	}
-	return false
-}
-
-// List reads and parses every task at ref, sorted by ordinal then id.
-func List(s *gitread.Store, repo, ref string) ([]Task, error) {
-	entries, err := s.Tree(repo, ref, Dir)
-	if err != nil {
-		return nil, err
-	}
-	var tasks []Task
-	for _, e := range entries {
-		if e.IsDir || !strings.HasSuffix(e.Name, ".md") {
-			continue
-		}
-		data, err := s.Blob(repo, ref, e.Path)
-		if err != nil {
-			continue
-		}
-		t, err := ParseTask(strings.TrimSuffix(e.Name, ".md"), data)
-		if err != nil {
-			continue
-		}
-		tasks = append(tasks, t)
-	}
-	sort.Slice(tasks, func(i, j int) bool {
-		if tasks[i].Ordinal != tasks[j].Ordinal {
-			return tasks[i].Ordinal < tasks[j].Ordinal
-		}
-		return tasks[i].ID < tasks[j].ID
-	})
-	return tasks, nil
-}
-
-// Get returns the task whose Key matches id (case-insensitive), or ok=false.
-func Get(s *gitread.Store, repo, ref, id string) (Task, bool) {
-	tasks, err := List(s, repo, ref)
-	if err != nil {
-		return Task{}, false
-	}
-	id = strings.ToLower(id)
-	for _, t := range tasks {
-		if t.Key() == id {
-			return t, true
-		}
-	}
-	return Task{}, false
-}
-
-// ParseTask splits YAML frontmatter from the Markdown body and unmarshals it.
-// The id falls back to the slug when frontmatter omits it.
-func ParseTask(slug string, raw []byte) (Task, error) {
-	fm, body := splitFrontmatter(raw)
-	var t Task
-	if len(fm) > 0 {
-		if err := yaml.Unmarshal(fm, &t); err != nil {
-			return Task{}, err
-		}
-	}
-	t.Slug = slug
-	t.Body = strings.TrimSpace(string(body))
-	if t.ID == "" {
-		t.ID = slug
-	}
-	if t.Title == "" {
-		t.Title = slug
-	}
-	if t.Status == "" {
-		t.Status = "To Do"
-	}
-	return t, nil
-}
-
-// splitFrontmatter separates a leading `---`-delimited YAML block from the rest.
-// If no frontmatter is present, fm is nil and body is the whole input.
-func splitFrontmatter(raw []byte) (fm, body []byte) {
-	s := string(raw)
-	if !strings.HasPrefix(s, "---") {
-		return nil, raw
-	}
-	nl := strings.IndexByte(s, '\n')
-	if nl < 0 {
-		return nil, raw
-	}
-	rest := s[nl+1:]
-	end := strings.Index(rest, "\n---")
-	if end < 0 {
-		return nil, raw
-	}
-	fm = []byte(rest[:end])
-	after := rest[end+1:] // begins at the closing "---"
-	if nl2 := strings.IndexByte(after, '\n'); nl2 >= 0 {
-		body = []byte(after[nl2+1:])
-	}
-	return fm, body
-}
internal/backlog/backlog_test.go +0 −63
@@ -1,64 +0,0 @@
-package backlog
-
-import "testing"
-
-func TestParse(t *testing.T) {
-	raw := []byte(`---
-id: TASK-007
-title: Wire the issues view
-status: In Progress
-priority: high
-labels:
-  - feature
-  - ui
-dependencies:
-  - TASK-001
-ordinal: 7000
-created_date: '2026-06-17 16:08'
-updated_date: '2026-06-17 16:21'
----
-Render backlog tasks as issues.
-
-## Acceptance Criteria
-- [ ] list view
-- [x] detail view
-`)
-	got, err := ParseTask("task-007 - wire-the-issues-view", raw)
-	if err != nil {
-		t.Fatalf("ParseTask: %v", err)
-	}
-	if got.ID != "TASK-007" {
-		t.Errorf("ID = %q, want TASK-007", got.ID)
-	}
-	if got.Key() != "task-007" {
-		t.Errorf("Key = %q, want task-007", got.Key())
-	}
-	if got.Status != "In Progress" {
-		t.Errorf("Status = %q", got.Status)
-	}
-	if got.Ordinal != 7000 {
-		t.Errorf("Ordinal = %d, want 7000", got.Ordinal)
-	}
-	if len(got.Labels) != 2 || got.Labels[0] != "feature" {
-		t.Errorf("Labels = %v", got.Labels)
-	}
-	if len(got.Deps) != 1 || got.Deps[0] != "TASK-001" {
-		t.Errorf("Deps = %v", got.Deps)
-	}
-	if got.Body == "" || got.Body[0] == '-' {
-		t.Errorf("Body not separated from frontmatter: %q", got.Body)
-	}
-}
-
-func TestParseNoFrontmatter(t *testing.T) {
-	got, err := ParseTask("loose-note", []byte("just a body, no frontmatter"))
-	if err != nil {
-		t.Fatalf("ParseTask: %v", err)
-	}
-	if got.ID != "loose-note" || got.Title != "loose-note" {
-		t.Errorf("fallbacks not applied: id=%q title=%q", got.ID, got.Title)
-	}
-	if got.Status != "To Do" {
-		t.Errorf("default status = %q, want To Do", got.Status)
-	}
-}
internal/config/config.go +0 −35
@@ -1,35 +0,0 @@
-// Package config resolves runtime configuration from flags and environment.
-// Flags win over env; env wins over defaults.
-package config
-
-import (
-	"flag"
-	"os"
-)
-
-type Config struct {
-	ReposPath     string // directory holding bare *.git repos
-	ListenAddr    string // host:port to listen on
-	BaseURL       string // public base URL (for absolute links, feeds)
-	SoftServeHTTP string // Soft Serve HTTP clone base, reverse-proxied at /git in prod
-	SoftServeDB   string // path to soft-serve.db; when set, only public repos are served
-}
-
-// Load parses flags (with env fallbacks) and returns the config.
-func Load() Config {
-	var c Config
-	flag.StringVar(&c.ReposPath, "repos", env("REPOS_PATH", "./repos"), "directory of bare *.git repositories")
-	flag.StringVar(&c.ListenAddr, "addr", env("LISTEN_ADDR", ":8080"), "listen address")
-	flag.StringVar(&c.BaseURL, "base-url", env("BASE_URL", "http://localhost:8080"), "public base URL")
-	flag.StringVar(&c.SoftServeHTTP, "soft-serve-http", env("SOFT_SERVE_HTTP", "http://git.kortum.world:23232"), "Soft Serve HTTP clone base")
-	flag.StringVar(&c.SoftServeDB, "soft-serve-db", env("SOFT_SERVE_DB", ""), "path to soft-serve.db; when set, only public (non-private, non-hidden) repos are served")
-	flag.Parse()
-	return c
-}
-
-func env(key, def string) string {
-	if v := os.Getenv(key); v != "" {
-		return v
-	}
-	return def
-}
internal/gitread/gitread.go +0 −455
@@ -1,455 +0,0 @@
-// Package gitread is the only package that touches go-git. It reads bare
-// repositories off disk and returns plain domain values; HTTP handlers ask it
-// for data and never open repositories themselves.
-package gitread
-
-import (
-	"database/sql"
-	"errors"
-	"fmt"
-	"io/fs"
-	"os"
-	"path/filepath"
-	"sort"
-	"strings"
-	"time"
-
-	"github.com/go-git/go-git/v5"
-	"github.com/go-git/go-git/v5/plumbing"
-	"github.com/go-git/go-git/v5/plumbing/object"
-	_ "modernc.org/sqlite"
-)
-
-// ErrNotFound is returned when a repo, ref, or path does not exist.
-var ErrNotFound = errors.New("not found")
-
-// Store reads bare repositories from a root directory. When db is non-nil
-// (Soft Serve's database), only public repos are visible — private and hidden
-// repos are treated as if they do not exist.
-type Store struct {
-	root string
-	db   *sql.DB
-}
-
-func New(root string) *Store { return &Store{root: root} }
-
-// WithDB attaches Soft Serve's sqlite database for visibility + metadata. The
-// db is opened read-concurrent (Soft Serve owns writes); only SELECTs are run.
-// A failure to open is returned so the caller can refuse to serve rather than
-// silently exposing private repos.
-func (s *Store) WithDB(path string) error {
-	db, err := sql.Open("sqlite", "file:"+path+"?_pragma=busy_timeout(5000)&_pragma=foreign_keys(0)")
-	if err != nil {
-		return err
-	}
-	if err := db.Ping(); err != nil {
-		return err
-	}
-	s.db = db
-	return nil
-}
-
-// publicMeta returns description/project for a public repo, or ok=false if the
-// repo is private, hidden, or unknown. Always ok=true when no db is attached.
-func (s *Store) publicMeta(name string) (desc, project string, ok bool) {
-	if s.db == nil {
-		return "", "", true
-	}
-	row := s.db.QueryRow(
-		`SELECT coalesce(description,''), coalesce(project_name,'')
-		   FROM repos WHERE name = ? AND private = 0 AND hidden = 0`, name)
-	if err := row.Scan(&desc, &project); err != nil {
-		return "", "", false
-	}
-	return desc, project, true
-}
-
-// visible reports whether a repo may be served (public, or no db gating).
-func (s *Store) visible(name string) bool {
-	_, _, ok := s.publicMeta(name)
-	return ok
-}
-
-// Repo is a single repository summary for the list view.
-type Repo struct {
-	Name        string
-	Description string
-	Last        *Commit
-}
-
-// Commit is commit metadata. Subject is the first line of Message.
-type Commit struct {
-	Hash    string
-	Short   string
-	Author  string
-	Email   string
-	When    time.Time
-	Message string
-	Subject string
-}
-
-// Ref is a named branch or tag.
-type Ref struct {
-	Name string
-	Hash string
-}
-
-// Refs groups a repository's branches and tags.
-type Refs struct {
-	Branches []Ref
-	Tags     []Ref
-}
-
-// Entry is a single tree entry (file or directory).
-type Entry struct {
-	Name  string
-	Path  string
-	IsDir bool
-	Mode  string
-	Size  int64
-}
-
-// CommitDetail is a commit plus its diff against its first parent.
-type CommitDetail struct {
-	Commit  Commit
-	Parents []string
-	Diff    string
-}
-
-// List returns the servable repos, sorted by name. With a db attached it lists
-// only public repos (using db descriptions); otherwise every bare repo on disk.
-func (s *Store) List() ([]Repo, error) {
-	if s.db != nil {
-		return s.listFromDB()
-	}
-	entries, err := os.ReadDir(s.root)
-	if err != nil {
-		return nil, err
-	}
-	var repos []Repo
-	for _, e := range entries {
-		if !e.IsDir() || !strings.HasSuffix(e.Name(), ".git") {
-			continue
-		}
-		name := strings.TrimSuffix(e.Name(), ".git")
-		repos = append(repos, s.summary(name, s.description(e.Name())))
-	}
-	sort.Slice(repos, func(i, j int) bool { return repos[i].Name < repos[j].Name })
-	return repos, nil
-}
-
-// listFromDB lists public repos recorded in Soft Serve's db that also exist on
-// disk, preferring the db description (falling back to the git description file).
-func (s *Store) listFromDB() ([]Repo, error) {
-	rows, err := s.db.Query(
-		`SELECT name, coalesce(description,'') FROM repos
-		   WHERE private = 0 AND hidden = 0 ORDER BY name`)
-	if err != nil {
-		return nil, err
-	}
-	defer rows.Close()
-	var repos []Repo
-	for rows.Next() {
-		var name, desc string
-		if err := rows.Scan(&name, &desc); err != nil {
-			return nil, err
-		}
-		if info, err := os.Stat(s.dir(name)); err != nil || !info.IsDir() {
-			continue // recorded but no bare repo on disk
-		}
-		if desc == "" {
-			desc = s.description(name + ".git")
-		}
-		repos = append(repos, s.summary(name, desc))
-	}
-	return repos, rows.Err()
-}
-
-// summary builds a list entry with the repo's HEAD commit.
-func (s *Store) summary(name, desc string) Repo {
-	r := Repo{Name: name, Description: desc}
-	if repo, err := s.open(name); err == nil {
-		if c, err := headCommit(repo); err == nil {
-			r.Last = c
-		}
-	}
-	return r
-}
-
-// Exists reports whether a repo is present and visible (public, when db-gated).
-func (s *Store) Exists(name string) bool {
-	if !s.visible(name) {
-		return false
-	}
-	info, err := os.Stat(s.dir(name))
-	return err == nil && info.IsDir()
-}
-
-// Refs returns the branches and tags of a repo.
-func (s *Store) Refs(name string) (*Refs, error) {
-	repo, err := s.open(name)
-	if err != nil {
-		return nil, err
-	}
-	out := &Refs{}
-	bs, err := repo.Branches()
-	if err == nil {
-		_ = bs.ForEach(func(r *plumbing.Reference) error {
-			out.Branches = append(out.Branches, Ref{Name: r.Name().Short(), Hash: r.Hash().String()})
-			return nil
-		})
-	}
-	ts, err := repo.Tags()
-	if err == nil {
-		_ = ts.ForEach(func(r *plumbing.Reference) error {
-			out.Tags = append(out.Tags, Ref{Name: r.Name().Short(), Hash: r.Hash().String()})
-			return nil
-		})
-	}
-	sort.Slice(out.Branches, func(i, j int) bool { return out.Branches[i].Name < out.Branches[j].Name })
-	sort.Slice(out.Tags, func(i, j int) bool { return out.Tags[i].Name < out.Tags[j].Name })
-	return out, nil
-}
-
-// DefaultBranch returns the short name of HEAD's target branch.
-func (s *Store) DefaultBranch(name string) (string, error) {
-	repo, err := s.open(name)
-	if err != nil {
-		return "", err
-	}
-	head, err := repo.Head()
-	if err != nil {
-		return "", ErrNotFound
-	}
-	return head.Name().Short(), nil
-}
-
-// Tree lists the entries at path under ref. An empty path is the root.
-func (s *Store) Tree(name, ref, path string) ([]Entry, error) {
-	repo, err := s.open(name)
-	if err != nil {
-		return nil, err
-	}
-	tree, err := s.treeAt(repo, ref)
-	if err != nil {
-		return nil, err
-	}
-	path = strings.Trim(path, "/")
-	if path != "" {
-		tree, err = tree.Tree(path)
-		if err != nil {
-			return nil, ErrNotFound
-		}
-	}
-	var out []Entry
-	for _, e := range tree.Entries {
-		isDir := e.Mode == 0o040000
-		entry := Entry{
-			Name:  e.Name,
-			Path:  strings.TrimPrefix(path+"/"+e.Name, "/"),
-			IsDir: isDir,
-			Mode:  e.Mode.String(),
-		}
-		if !isDir {
-			if blob, err := repo.BlobObject(e.Hash); err == nil {
-				entry.Size = blob.Size
-			}
-		}
-		out = append(out, entry)
-	}
-	sort.Slice(out, func(i, j int) bool {
-		if out[i].IsDir != out[j].IsDir {
-			return out[i].IsDir // directories first
-		}
-		return out[i].Name < out[j].Name
-	})
-	return out, nil
-}
-
-// Blob returns the raw bytes of the file at path under ref.
-func (s *Store) Blob(name, ref, path string) ([]byte, error) {
-	repo, err := s.open(name)
-	if err != nil {
-		return nil, err
-	}
-	tree, err := s.treeAt(repo, ref)
-	if err != nil {
-		return nil, err
-	}
-	f, err := tree.File(strings.Trim(path, "/"))
-	if err != nil {
-		return nil, ErrNotFound
-	}
-	r, err := f.Blob.Reader()
-	if err != nil {
-		return nil, err
-	}
-	defer r.Close()
-	buf := make([]byte, f.Size)
-	if _, err := readFull(r, buf); err != nil {
-		return nil, err
-	}
-	return buf, nil
-}
-
-// Log returns up to limit commits reachable from ref, newest first.
-func (s *Store) Log(name, ref string, limit int) ([]Commit, error) {
-	repo, err := s.open(name)
-	if err != nil {
-		return nil, err
-	}
-	h, err := s.resolve(repo, ref)
-	if err != nil {
-		return nil, err
-	}
-	iter, err := repo.Log(&git.LogOptions{From: *h})
-	if err != nil {
-		return nil, err
-	}
-	defer iter.Close()
-	var out []Commit
-	for len(out) < limit {
-		c, err := iter.Next()
-		if err != nil {
-			break
-		}
-		out = append(out, toCommit(c))
-	}
-	return out, nil
-}
-
-// Commit returns a single commit with its diff against the first parent.
-func (s *Store) Commit(name, rev string) (*CommitDetail, error) {
-	repo, err := s.open(name)
-	if err != nil {
-		return nil, err
-	}
-	h, err := s.resolve(repo, rev)
-	if err != nil {
-		return nil, err
-	}
-	c, err := repo.CommitObject(*h)
-	if err != nil {
-		return nil, ErrNotFound
-	}
-	d := &CommitDetail{Commit: toCommit(c)}
-	for _, p := range c.ParentHashes {
-		d.Parents = append(d.Parents, p.String())
-	}
-	if c.NumParents() > 0 {
-		parent, err := c.Parent(0)
-		if err == nil {
-			if patch, err := parent.Patch(c); err == nil {
-				d.Diff = patch.String()
-			}
-		}
-	} else {
-		if patch, err := c.Patch(nil); err == nil {
-			d.Diff = patch.String()
-		}
-	}
-	return d, nil
-}
-
-// --- internals ---
-
-func (s *Store) dir(name string) string {
-	return filepath.Join(s.root, name+".git")
-}
-
-func (s *Store) open(name string) (*git.Repository, error) {
-	if !s.visible(name) {
-		return nil, ErrNotFound // private/hidden/unknown — never serve
-	}
-	repo, err := git.PlainOpen(s.dir(name))
-	if errors.Is(err, git.ErrRepositoryNotExists) || errors.Is(err, fs.ErrNotExist) {
-		return nil, ErrNotFound
-	}
-	return repo, err
-}
-
-// description reads the git `description` file, ignoring the default placeholder.
-func (s *Store) description(dirName string) string {
-	b, err := os.ReadFile(filepath.Join(s.root, dirName, "description"))
-	if err != nil {
-		return ""
-	}
-	d := strings.TrimSpace(string(b))
-	if strings.HasPrefix(d, "Unnamed repository") {
-		return ""
-	}
-	return d
-}
-
-// resolve turns a ref name, short hash, or HEAD into a commit hash.
-func (s *Store) resolve(repo *git.Repository, ref string) (*plumbing.Hash, error) {
-	if ref == "" {
-		ref = "HEAD"
-	}
-	h, err := repo.ResolveRevision(plumbing.Revision(ref))
-	if err != nil {
-		return nil, ErrNotFound
-	}
-	return h, nil
-}
-
-func (s *Store) treeAt(repo *git.Repository, ref string) (*object.Tree, error) {
-	h, err := s.resolve(repo, ref)
-	if err != nil {
-		return nil, err
-	}
-	c, err := repo.CommitObject(*h)
-	if err != nil {
-		return nil, ErrNotFound
-	}
-	return c.Tree()
-}
-
-func headCommit(repo *git.Repository) (*Commit, error) {
-	head, err := repo.Head()
-	if err != nil {
-		return nil, err
-	}
-	c, err := repo.CommitObject(head.Hash())
-	if err != nil {
-		return nil, err
-	}
-	out := toCommit(c)
-	return &out, nil
-}
-
-func toCommit(c *object.Commit) Commit {
-	msg := strings.TrimRight(c.Message, "\n")
-	subject := msg
-	if i := strings.IndexByte(subject, '\n'); i >= 0 {
-		subject = subject[:i]
-	}
-	return Commit{
-		Hash:    c.Hash.String(),
-		Short:   c.Hash.String()[:8],
-		Author:  c.Author.Name,
-		Email:   c.Author.Email,
-		When:    c.Author.When,
-		Message: msg,
-		Subject: subject,
-	}
-}
-
-// readFull fills buf, tolerating short reads, and erroring on a size mismatch.
-func readFull(r interface{ Read([]byte) (int, error) }, buf []byte) (int, error) {
-	total := 0
-	for total < len(buf) {
-		n, err := r.Read(buf[total:])
-		total += n
-		if err != nil {
-			if total == len(buf) {
-				return total, nil
-			}
-			return total, err
-		}
-	}
-	if total != len(buf) {
-		return total, fmt.Errorf("short read: got %d want %d", total, len(buf))
-	}
-	return total, nil
-}
internal/render/render.go +0 −269
@@ -1,269 +0,0 @@
-// Package render turns raw repository bytes into HTML: syntax-highlighted code
-// via chroma and rendered Markdown via goldmark. It holds no git knowledge.
-package render
-
-import (
-	"bytes"
-	"html/template"
-	"path"
-	"strings"
-	"unicode/utf8"
-
-	"github.com/alecthomas/chroma/v2"
-	"github.com/alecthomas/chroma/v2/formatters/html"
-	"github.com/alecthomas/chroma/v2/lexers"
-	"github.com/alecthomas/chroma/v2/styles"
-	"github.com/yuin/goldmark"
-	highlighting "github.com/yuin/goldmark-highlighting/v2"
-	"github.com/yuin/goldmark/extension"
-	"gopkg.in/yaml.v3"
-)
-
-// FMPair is one ordered key/value from a Markdown file's YAML frontmatter.
-type FMPair struct {
-	Key   string
-	Value string
-}
-
-// SplitFrontmatter separates a leading `---`-delimited YAML block from the body.
-// No frontmatter → fm is nil and body is the whole input.
-func SplitFrontmatter(raw []byte) (fm, body []byte) {
-	s := string(raw)
-	if !strings.HasPrefix(s, "---") {
-		return nil, raw
-	}
-	nl := strings.IndexByte(s, '\n')
-	if nl < 0 {
-		return nil, raw
-	}
-	rest := s[nl+1:]
-	end := strings.Index(rest, "\n---")
-	if end < 0 {
-		return nil, raw
-	}
-	fm = []byte(rest[:end])
-	after := rest[end+1:] // starts at closing "---"
-	if nl2 := strings.IndexByte(after, '\n'); nl2 >= 0 {
-		body = []byte(after[nl2+1:])
-	}
-	return fm, body
-}
-
-// ParseFrontmatter parses a YAML frontmatter block into ordered key/value pairs
-// for display. Sequence values are comma-joined; nested maps are flattened to a
-// compact YAML string. Returns nil on empty or invalid input.
-func ParseFrontmatter(fm []byte) []FMPair {
-	if len(bytes.TrimSpace(fm)) == 0 {
-		return nil
-	}
-	var doc yaml.Node
-	if err := yaml.Unmarshal(fm, &doc); err != nil || len(doc.Content) == 0 {
-		return nil
-	}
-	root := doc.Content[0]
-	if root.Kind != yaml.MappingNode {
-		return nil
-	}
-	var pairs []FMPair
-	for i := 0; i+1 < len(root.Content); i += 2 {
-		pairs = append(pairs, FMPair{
-			Key:   root.Content[i].Value,
-			Value: nodeString(root.Content[i+1]),
-		})
-	}
-	return pairs
-}
-
-func nodeString(n *yaml.Node) string {
-	switch n.Kind {
-	case yaml.ScalarNode:
-		return n.Value
-	case yaml.SequenceNode:
-		parts := make([]string, 0, len(n.Content))
-		for _, c := range n.Content {
-			parts = append(parts, nodeString(c))
-		}
-		return strings.Join(parts, ", ")
-	default:
-		var b bytes.Buffer
-		enc := yaml.NewEncoder(&b)
-		_ = enc.Encode(n)
-		_ = enc.Close()
-		return strings.TrimSpace(b.String())
-	}
-}
-
-// md renders GFM and highlights fenced code blocks with chroma classes, so
-// code in READMEs/issue bodies is colored by the same theme tokens as blobs.
-var md = goldmark.New(goldmark.WithExtensions(
-	extension.GFM,
-	highlighting.NewHighlighting(
-		highlighting.WithFormatOptions(html.WithClasses(true)),
-	),
-))
-
-// chroma formatters emit classes (not inline styles) so themes drive colors.
-// formatter is for source files (with a line-number gutter); diffFormatter omits
-// line numbers, which would fight a patch's own +/- columns.
-var formatter = html.New(html.WithClasses(true), html.WithLineNumbers(true), html.LineNumbersInTable(true))
-var diffFormatter = html.New(html.WithClasses(true))
-
-// classStyle is irrelevant to class-based output but required by the API.
-var classStyle = styles.Get("github")
-
-// Markdown renders GitHub-flavored Markdown to HTML.
-func Markdown(src []byte) (template.HTML, error) {
-	var buf bytes.Buffer
-	if err := md.Convert(src, &buf); err != nil {
-		return "", err
-	}
-	return template.HTML(buf.String()), nil // #nosec G203 -- trusted repo content, rendered by goldmark
-}
-
-// Highlight returns syntax-highlighted HTML for code, choosing a lexer by
-// filename then content. Binary content yields ok=false so callers can show a
-// "binary file" notice instead.
-func Highlight(filename string, code []byte) (out template.HTML, ok bool) {
-	if isBinary(code) {
-		return "", false
-	}
-	lexer := lexers.Match(filename)
-	if lexer == nil {
-		lexer = lexers.Analyse(string(code))
-	}
-	if lexer == nil {
-		lexer = lexers.Fallback
-	}
-	lexer = chroma.Coalesce(lexer)
-
-	iterator, err := lexer.Tokenise(nil, string(code))
-	if err != nil {
-		return "", false
-	}
-	var buf bytes.Buffer
-	if err := formatter.Format(&buf, classStyle, iterator); err != nil {
-		return "", false
-	}
-	return template.HTML(buf.String()), true // #nosec G203 -- escaped by chroma formatter
-}
-
-// FileDiff is one file's slice of a commit diff: its name, highlighted hunks,
-// and add/delete line counts.
-type FileDiff struct {
-	Name    string
-	HTML    template.HTML
-	Added   int
-	Deleted int
-	Binary  bool
-}
-
-// SplitDiff breaks a unified diff into per-file sections. Each section's git
-// metadata (diff --git / index / ---/+++ lines) is dropped — the filename is
-// surfaced separately — and the hunks are highlighted on their own.
-func SplitDiff(diff string) []FileDiff {
-	if strings.TrimSpace(diff) == "" {
-		return nil
-	}
-	lines := strings.Split(diff, "\n")
-	var files []FileDiff
-	var cur []string
-	flush := func() {
-		if len(cur) == 0 {
-			return
-		}
-		files = append(files, buildFileDiff(cur))
-		cur = nil
-	}
-	for _, ln := range lines {
-		if strings.HasPrefix(ln, "diff --git ") {
-			flush()
-		}
-		cur = append(cur, ln)
-	}
-	flush()
-	return files
-}
-
-func buildFileDiff(block []string) FileDiff {
-	fd := FileDiff{Name: diffName(block)}
-	hunk := -1
-	for i, ln := range block {
-		switch {
-		case strings.HasPrefix(ln, "@@") && hunk < 0:
-			hunk = i
-		case strings.HasPrefix(ln, "Binary files "), strings.HasPrefix(ln, "GIT binary patch"):
-			fd.Binary = true
-		case strings.HasPrefix(ln, "+") && !strings.HasPrefix(ln, "+++"):
-			fd.Added++
-		case strings.HasPrefix(ln, "-") && !strings.HasPrefix(ln, "---"):
-			fd.Deleted++
-		}
-	}
-	if hunk >= 0 {
-		fd.HTML = HighlightDiff(strings.Join(block[hunk:], "\n"))
-	}
-	return fd
-}
-
-// diffName extracts a display name from a file block's git header, formatting
-// renames as "old → new".
-func diffName(block []string) string {
-	for _, ln := range block {
-		if strings.HasPrefix(ln, "diff --git ") {
-			fields := strings.Fields(ln)
-			if len(fields) >= 4 {
-				a := strings.TrimPrefix(fields[len(fields)-2], "a/")
-				b := strings.TrimPrefix(fields[len(fields)-1], "b/")
-				if a != b {
-					return a + " → " + b
-				}
-				return b
-			}
-		}
-	}
-	for _, ln := range block {
-		if strings.HasPrefix(ln, "+++ b/") {
-			return strings.TrimPrefix(ln, "+++ b/")
-		}
-	}
-	return "diff"
-}
-
-// HighlightDiff renders a unified diff with chroma's diff lexer (class-based:
-// .gi inserts, .gd deletes, .gu/.gh hunk headers). Falls back to escaped text.
-func HighlightDiff(diff string) template.HTML {
-	lexer := lexers.Get("diff")
-	if lexer == nil {
-		return template.HTML("<pre class=\"chroma\">" + template.HTMLEscapeString(diff) + "</pre>") // #nosec G203 -- escaped
-	}
-	iterator, err := lexer.Tokenise(nil, diff)
-	if err != nil {
-		return template.HTML("<pre class=\"chroma\">" + template.HTMLEscapeString(diff) + "</pre>") // #nosec G203 -- escaped
-	}
-	var buf bytes.Buffer
-	if err := diffFormatter.Format(&buf, classStyle, iterator); err != nil {
-		return template.HTML("<pre class=\"chroma\">" + template.HTMLEscapeString(diff) + "</pre>") // #nosec G203 -- escaped
-	}
-	return template.HTML(buf.String()) // #nosec G203 -- escaped by chroma formatter
-}
-
-// IsMarkdown reports whether a filename should be rendered as Markdown.
-func IsMarkdown(name string) bool {
-	switch strings.ToLower(path.Ext(name)) {
-	case ".md", ".markdown", ".mdown":
-		return true
-	}
-	return false
-}
-
-// isBinary uses a NUL-byte heuristic over the first chunk, like git.
-func isBinary(b []byte) bool {
-	const sniff = 8000
-	if len(b) > sniff {
-		b = b[:sniff]
-	}
-	if bytes.IndexByte(b, 0) >= 0 {
-		return true
-	}
-	return !utf8.Valid(b)
-}
internal/server/server.go +0 −371
@@ -1,371 +0,0 @@
-// Package server wires HTTP routes to gitread reads, render helpers, and templ
-// views. Handlers never open repositories directly — they go through gitread.
-package server
-
-import (
-	"fmt"
-	"html/template"
-	"io/fs"
-	"net/http"
-	"strings"
-
-	"git.kortum.world/custard/internal/backlog"
-	"git.kortum.world/custard/internal/config"
-	"git.kortum.world/custard/internal/gitread"
-	"git.kortum.world/custard/internal/render"
-	"git.kortum.world/custard/web"
-	"git.kortum.world/custard/web/templates"
-	"github.com/a-h/templ"
-)
-
-const logLimit = 100
-
-// Server holds dependencies shared across handlers.
-type Server struct {
-	cfg   config.Config
-	store *gitread.Store
-}
-
-// New builds the server. When cfg.SoftServeDB is set it attaches Soft Serve's
-// database for repo-visibility gating; a failure to open is fatal (returned)
-// rather than silently falling back to exposing every repo.
-func New(cfg config.Config) (*Server, error) {
-	store := gitread.New(cfg.ReposPath)
-	if cfg.SoftServeDB != "" {
-		if err := store.WithDB(cfg.SoftServeDB); err != nil {
-			return nil, fmt.Errorf("open soft-serve db %q: %w", cfg.SoftServeDB, err)
-		}
-	}
-	return &Server{cfg: cfg, store: store}, nil
-}
-
-// Handler builds the route table. Tree/blob/raw register twice so the ref-only
-// form (no path) and the path form both match the Go 1.22 pattern mux.
-func (s *Server) Handler() http.Handler {
-	mux := http.NewServeMux()
-	mux.HandleFunc("GET /{$}", s.handleIndex)
-	mux.HandleFunc("GET /r/{repo}", s.handleRepo)
-	mux.HandleFunc("GET /r/{repo}/files", s.handleFiles)
-	mux.HandleFunc("GET /r/{repo}/readme", s.handleReadme)
-	mux.HandleFunc("GET /r/{repo}/refs", s.handleRefs)
-	mux.HandleFunc("GET /r/{repo}/log/{ref}", s.handleLog)
-	mux.HandleFunc("GET /r/{repo}/commit/{sha}", s.handleCommit)
-	mux.HandleFunc("GET /r/{repo}/tree/{ref}", s.handleTree)
-	mux.HandleFunc("GET /r/{repo}/tree/{ref}/{path...}", s.handleTree)
-	mux.HandleFunc("GET /r/{repo}/blob/{ref}/{path...}", s.handleBlob)
-	mux.HandleFunc("GET /r/{repo}/raw/{ref}/{path...}", s.handleRaw)
-	mux.HandleFunc("GET /r/{repo}/issues", s.handleIssues)
-	mux.HandleFunc("GET /r/{repo}/issues/{id}", s.handleIssue)
-
-	staticFS, _ := fs.Sub(web.Static, "static")
-	mux.Handle("GET /static/", http.StripPrefix("/static/", http.FileServerFS(staticFS)))
-	return mux
-}
-
-func (s *Server) handleIndex(w http.ResponseWriter, r *http.Request) {
-	repos, err := s.store.List()
-	if err != nil {
-		s.fail(w, r, http.StatusInternalServerError, "could not list repositories")
-		return
-	}
-	page := templates.IndexPage{Meta: templates.Meta{Theme: readTheme(r)}, Repos: repos}
-	s.render(w, r, templates.Index(page))
-}
-
-// handleRepo is the repo landing: a repo with a README opens to it; otherwise
-// it shows the file tree.
-func (s *Server) handleRepo(w http.ResponseWriter, r *http.Request) {
-	name := r.PathValue("repo")
-	if !s.store.Exists(name) {
-		s.notFound(w, r)
-		return
-	}
-	branch, err := s.store.DefaultBranch(name)
-	if err != nil {
-		// Empty repository: show a stub page rather than failing.
-		s.render(w, r, templates.Repo(templates.RepoPage{Meta: templates.Meta{Repo: name, Tab: "code", Theme: readTheme(r)}}))
-		return
-	}
-	if md, ok := s.readme(name, branch); ok {
-		page := templates.ReadmePage{Meta: s.meta(r, name, branch, "readme"), Readme: md}
-		s.render(w, r, templates.Readme(page))
-		return
-	}
-	s.renderFiles(w, r, name, branch)
-}
-
-// handleFiles is the file-tree (code) view, the destination of the code tab.
-func (s *Server) handleFiles(w http.ResponseWriter, r *http.Request) {
-	name := r.PathValue("repo")
-	branch, err := s.store.DefaultBranch(name)
-	if err != nil {
-		s.notFound(w, r)
-		return
-	}
-	s.renderFiles(w, r, name, branch)
-}
-
-func (s *Server) renderFiles(w http.ResponseWriter, r *http.Request, name, branch string) {
-	refs, _ := s.store.Refs(name)
-	entries, _ := s.store.Tree(name, branch, "")
-	page := templates.RepoPage{
-		Meta:          s.meta(r, name, branch, "code"),
-		DefaultBranch: branch,
-		Entries:       entries,
-		Last:          s.lastCommit(name, branch),
-	}
-	if refs != nil {
-		page.Branches = len(refs.Branches)
-		page.Tags = len(refs.Tags)
-	}
-	s.render(w, r, templates.Repo(page))
-}
-
-func (s *Server) handleReadme(w http.ResponseWriter, r *http.Request) {
-	name := r.PathValue("repo")
-	branch, err := s.store.DefaultBranch(name)
-	if err != nil {
-		s.notFound(w, r)
-		return
-	}
-	md, ok := s.readme(name, branch)
-	if !ok {
-		s.notFound(w, r)
-		return
-	}
-	page := templates.ReadmePage{Meta: s.meta(r, name, branch, "readme"), Readme: md}
-	s.render(w, r, templates.Readme(page))
-}
-
-func (s *Server) handleTree(w http.ResponseWriter, r *http.Request) {
-	name, ref, path := r.PathValue("repo"), r.PathValue("ref"), r.PathValue("path")
-	entries, err := s.store.Tree(name, ref, path)
-	if err != nil {
-		s.notFound(w, r)
-		return
-	}
-	page := templates.TreePage{
-		Meta:    s.meta(r, name, ref, "code"),
-		Path:    strings.Trim(path, "/"),
-		Crumbs:  templates.BuildCrumbs(path),
-		Entries: entries,
-	}
-	s.render(w, r, templates.Tree(page))
-}
-
-func (s *Server) handleBlob(w http.ResponseWriter, r *http.Request) {
-	name, ref, path := r.PathValue("repo"), r.PathValue("ref"), r.PathValue("path")
-	data, err := s.store.Blob(name, ref, path)
-	if err != nil {
-		s.notFound(w, r)
-		return
-	}
-	page := templates.BlobPage{
-		Meta:   s.meta(r, name, ref, "code"),
-		Path:   strings.Trim(path, "/"),
-		Crumbs: templates.BuildCrumbs(path),
-		Size:   int64(len(data)),
-	}
-	switch {
-	case render.IsMarkdown(path):
-		fm, body := render.SplitFrontmatter(data)
-		md, err := render.Markdown(body)
-		if err == nil {
-			page.IsMarkdown = true
-			page.Markdown = md
-			page.Frontmatter = render.ParseFrontmatter(fm)
-			break
-		}
-		fallthrough
-	default:
-		code, ok := render.Highlight(path, data)
-		if ok {
-			page.Code = code
-		} else {
-			page.IsBinary = true
-		}
-	}
-	s.render(w, r, templates.Blob(page))
-}
-
-func (s *Server) handleRaw(w http.ResponseWriter, r *http.Request) {
-	name, ref, path := r.PathValue("repo"), r.PathValue("ref"), r.PathValue("path")
-	data, err := s.store.Blob(name, ref, path)
-	if err != nil {
-		s.notFound(w, r)
-		return
-	}
-	w.Header().Set("Content-Type", "text/plain; charset=utf-8")
-	w.Header().Set("X-Content-Type-Options", "nosniff")
-	_, _ = w.Write(data)
-}
-
-func (s *Server) handleLog(w http.ResponseWriter, r *http.Request) {
-	name, ref := r.PathValue("repo"), r.PathValue("ref")
-	commits, err := s.store.Log(name, ref, logLimit)
-	if err != nil {
-		s.notFound(w, r)
-		return
-	}
-	page := templates.LogPage{Meta: s.meta(r, name, ref, "log"), Commits: commits}
-	s.render(w, r, templates.Log(page))
-}
-
-func (s *Server) handleCommit(w http.ResponseWriter, r *http.Request) {
-	name, sha := r.PathValue("repo"), r.PathValue("sha")
-	detail, err := s.store.Commit(name, sha)
-	if err != nil {
-		s.notFound(w, r)
-		return
-	}
-	page := templates.CommitPage{
-		Meta:   s.meta(r, name, "", "log"),
-		Detail: detail,
-		Files:  render.SplitDiff(detail.Diff),
-	}
-	s.render(w, r, templates.Commit(page))
-}
-
-func (s *Server) handleRefs(w http.ResponseWriter, r *http.Request) {
-	name := r.PathValue("repo")
-	refs, err := s.store.Refs(name)
-	if err != nil {
-		s.notFound(w, r)
-		return
-	}
-	page := templates.RefsPage{Meta: s.meta(r, name, "", "refs"), Refs: refs}
-	s.render(w, r, templates.Refs(page))
-}
-
-func (s *Server) handleIssues(w http.ResponseWriter, r *http.Request) {
-	name := r.PathValue("repo")
-	ref, err := s.store.DefaultBranch(name)
-	if err != nil {
-		s.notFound(w, r)
-		return
-	}
-	tasks, err := backlog.List(s.store, name, ref)
-	if err != nil {
-		s.notFound(w, r)
-		return
-	}
-	cfg := backlog.ReadConfig(s.store, name, ref)
-	page := templates.IssuesPage{
-		Meta:   s.meta(r, name, ref, "issues"),
-		Groups: templates.GroupByStatus(tasks, cfg.Statuses),
-		Total:  len(tasks),
-	}
-	s.render(w, r, templates.Issues(page))
-}
-
-func (s *Server) handleIssue(w http.ResponseWriter, r *http.Request) {
-	name, id := r.PathValue("repo"), r.PathValue("id")
-	ref, err := s.store.DefaultBranch(name)
-	if err != nil {
-		s.notFound(w, r)
-		return
-	}
-	task, ok := backlog.Get(s.store, name, ref, id)
-	if !ok {
-		s.notFound(w, r)
-		return
-	}
-	page := templates.IssuePage{Meta: s.meta(r, name, ref, "issues"), Task: task}
-	if task.Body != "" {
-		if body, err := render.Markdown([]byte(task.Body)); err == nil {
-			page.Body = body
-		}
-	}
-	s.render(w, r, templates.Issue(page))
-}
-
-// --- helpers ---
-
-func (s *Server) lastCommit(name, ref string) *gitread.Commit {
-	commits, err := s.store.Log(name, ref, 1)
-	if err != nil || len(commits) == 0 {
-		return nil
-	}
-	return &commits[0]
-}
-
-// readme finds and renders a root README, trying common filenames.
-func (s *Server) readme(name, ref string) (template.HTML, bool) {
-	for _, fn := range readmeFiles {
-		data, err := s.store.Blob(name, ref, fn)
-		if err != nil {
-			continue
-		}
-		html, err := render.Markdown(data)
-		if err != nil {
-			continue
-		}
-		return html, true
-	}
-	return "", false
-}
-
-// meta builds shared page chrome: active tab, issues-tab visibility, theme,
-// and (in a repo context) the read-only HTTP clone URL for the footer.
-func (s *Server) meta(r *http.Request, name, ref, tab string) templates.Meta {
-	m := templates.Meta{Repo: name, Ref: ref, Tab: tab, HasIssues: s.hasIssues(name, ref), HasReadme: s.hasReadme(name, ref), Theme: readTheme(r)}
-	if name != "" && s.cfg.SoftServeHTTP != "" {
-		m.CloneURL = strings.TrimRight(s.cfg.SoftServeHTTP, "/") + "/" + name + ".git"
-	}
-	return m
-}
-
-// readTheme resolves the data-theme from the "theme" cookie, defaulting safely.
-func readTheme(r *http.Request) string {
-	if c, err := r.Cookie("theme"); err == nil {
-		return templates.ValidTheme(c.Value)
-	}
-	return templates.DefaultTheme
-}
-
-// hasIssues reports whether the repo carries backlog tasks (drives the tab).
-// An empty ref falls back to the default branch.
-func (s *Server) hasIssues(name, ref string) bool {
-	if ref == "" {
-		b, err := s.store.DefaultBranch(name)
-		if err != nil {
-			return false
-		}
-		ref = b
-	}
-	return backlog.Has(s.store, name, ref)
-}
-
-// readmeFiles are the root README names tried, in order.
-var readmeFiles = []string{"README.md", "readme.md", "README.markdown", "README"}
-
-// hasReadme reports whether the repo has a root README (drives the tab).
-func (s *Server) hasReadme(name, ref string) bool {
-	if ref == "" {
-		b, err := s.store.DefaultBranch(name)
-		if err != nil {
-			return false
-		}
-		ref = b
-	}
-	for _, fn := range readmeFiles {
-		if _, err := s.store.Blob(name, ref, fn); err == nil {
-			return true
-		}
-	}
-	return false
-}
-
-func (s *Server) render(w http.ResponseWriter, r *http.Request, c templ.Component) {
-	w.Header().Set("Content-Type", "text/html; charset=utf-8")
-	_ = c.Render(r.Context(), w)
-}
-
-func (s *Server) notFound(w http.ResponseWriter, r *http.Request) {
-	s.fail(w, r, http.StatusNotFound, "not found")
-}
-
-func (s *Server) fail(w http.ResponseWriter, r *http.Request, code int, msg string) {
-	w.WriteHeader(code)
-	_ = templates.Error(templates.Meta{Theme: readTheme(r)}, code, msg).Render(r.Context(), w)
-}
scripts/migrate-backlog-statuses.sh +0 −58
@@ -1,58 +0,0 @@
-#!/usr/bin/env bash
-#
-# migrate-backlog-statuses.sh <repo-path> [--dry-run]
-#
-# Migrate a Backlog.md repo to the unified status vocabulary:
-#   🟦 Backlog · 🟢 In progress · 🚧 Paused · 🏁 Done
-#
-# Updates backlog/config.yml (statuses + default_status) and rewrites each task's
-# status via the backlog CLI using the mapping below. Idempotent: tasks already
-# on the new vocab are left alone.
-#
-# This is the pilot rollout helper — run it per repo for the later full rollout.
-# Claude project docs (Humdrum/Claude/Projects/*.md) are migrated separately:
-#   - frontmatter `status:` → one of the four (judgement: 🟡→🟢, 🔴→🚧, etc.)
-#   - any ```base``` filters referencing "Done" → "🏁 Done"
-#
-set -euo pipefail
-
-repo="${1:?usage: migrate-backlog-statuses.sh <repo-path> [--dry-run]}"
-dry=""; [ "${2:-}" = "--dry-run" ] && dry=1
-cd "$repo"
-[ -f backlog/config.yml ] || { echo "no backlog/config.yml in $repo — skipping"; exit 0; }
-
-NEW_STATUSES='["🟦 Backlog", "🟢 In progress", "🚧 Paused", "🏁 Done"]'
-
-map_status() {
-  case "$1" in
-    "To Do"|"Todo"|"Backlog")              echo "🟦 Backlog" ;;
-    "In Progress"|"In progress"|"Doing")   echo "🟢 In progress" ;;
-    "Paused"|"Blocked"|"On Hold"|"Hold")   echo "🚧 Paused" ;;
-    "Done"|"Shipped"|"Complete"|"Completed") echo "🏁 Done" ;;
-    *) echo "" ;;  # already new vocab, or unknown — leave it
-  esac
-}
-
-echo "== $repo =="
-
-# config: statuses array (CLI refuses arrays, so edit the file) + default_status
-if [ -z "$dry" ]; then
-  tmp=$(mktemp)
-  awk -v repl="statuses: $NEW_STATUSES" '/^statuses:/{print repl; next} {print}' backlog/config.yml > "$tmp" && mv "$tmp" backlog/config.yml
-  backlog config set defaultStatus "🟦 Backlog" >/dev/null 2>&1 || true
-fi
-echo "  config: statuses + default_status → new vocab"
-
-# tasks: map current status → new
-shopt -s nullglob
-for f in backlog/tasks/*.md; do
-  id=$(awk -F': ' '/^id:/{print $2; exit}' "$f")
-  cur=$(awk '/^status:/{sub(/^status:[ ]*/,""); gsub(/^["'\'']|["'\'']$/,""); print; exit}' "$f")
-  new=$(map_status "$cur")
-  [ -z "$new" ] && continue
-  [ "$new" = "$cur" ] && continue
-  echo "  $id: $cur → $new"
-  [ -z "$dry" ] && backlog task edit "$id" -s "$new" --plain >/dev/null 2>&1
-done
-
-echo "  done"
web/embed.go +0 −8
@@ -1,8 +0,0 @@
-// Package web embeds custard's static assets (CSS, JS, fonts) so the compiled
-// binary is self-contained and needs no files on disk at the deploy target.
-package web
-
-import "embed"
-
-//go:embed static
-var Static embed.FS
web/static/custard.css +0 −318
@@ -1,318 +0,0 @@
-/*
- * custard.css — terminal / Charm-flavored chrome built entirely on the
- * _shared-app-kit token system (tokens.css). No hard-coded colors: every value
- * reads a theme token, so all 7 data-theme modes (incl. eink) work for free.
- */
-
-* { box-sizing: border-box; }
-
-body {
-  margin: 0;
-  padding: 0 var(--space-4) var(--space-16);
-  max-width: 1100px;
-  margin-inline: auto;
-  font-family: var(--font-body);
-  font-size: var(--text-base);
-  line-height: var(--leading-normal);
-  background: var(--bg);
-  color: var(--text);
-}
-
-a { color: var(--accent); text-decoration: none; }
-a:hover { text-decoration: underline; }
-code, pre, kbd, samp { font-family: var(--font-mono); font-size: var(--text-sm); }
-.muted { color: var(--text-muted); }
-.empty { color: var(--text-faint); font-style: italic; padding: var(--space-2) 0; }
-.num { text-align: right; color: var(--text-muted); }
-h1 { font-size: var(--text-2xl); margin: var(--space-4) 0 var(--space-3); }
-h2 { font-size: var(--text-lg); }
-
-/* ─── header ───────────────────────────────────────────────────────── */
-.site-head {
-  display: flex; align-items: center; justify-content: space-between; gap: var(--space-4);
-  padding: var(--space-3) 0;
-  border-bottom: 1px solid var(--border);
-  margin-bottom: var(--space-4);
-}
-.site-head-left { display: flex; align-items: baseline; gap: var(--space-2); min-width: 0; }
-.brand {
-  font-family: var(--font-mono); font-weight: 700; font-size: var(--text-lg);
-  color: var(--accent); letter-spacing: -0.02em;
-}
-.brand:hover { text-decoration: none; }
-.repo-name { font-family: var(--font-mono); color: var(--text); }
-.sep { color: var(--text-faint); }
-.theme-select {
-  font-family: var(--font-mono); font-size: var(--text-xs);
-  color: var(--text); background: var(--bg-card);
-  border: 1px solid var(--border-strong); border-radius: var(--radius-sm);
-  padding: var(--space-1) var(--space-2);
-}
-
-/* ─── repo tabs ────────────────────────────────────────────────────── */
-.repo-tabs {
-  display: flex; gap: var(--space-1);
-  font-family: var(--font-mono); font-size: var(--text-sm);
-  border-bottom: 1px solid var(--border);
-  margin-bottom: var(--space-4);
-}
-.repo-tabs a {
-  color: var(--text-muted); padding: var(--space-2) var(--space-3);
-  border-bottom: 2px solid transparent; margin-bottom: -1px;
-}
-.repo-tabs a:hover { color: var(--text); text-decoration: none; background: var(--bg-hover); }
-.repo-tabs a.active { color: var(--text); border-bottom-color: var(--accent); }
-
-/* ─── repo list (index) ────────────────────────────────────────────── */
-.repo-list { list-style: none; padding: 0; margin: 0; border: 1px solid var(--border); border-radius: var(--radius); }
-.repo-list li {
-  padding: var(--space-3) var(--space-4);
-  border-bottom: 1px solid var(--border);
-  display: grid; grid-template-columns: minmax(0,1fr); gap: var(--space-1);
-}
-.repo-list li:last-child { border-bottom: 0; }
-.repo-list li:hover { background: var(--bg-hover); }
-.repo-link { font-family: var(--font-mono); font-weight: 500; font-size: var(--text-base); }
-.repo-desc { color: var(--text-muted); }
-.repo-meta { color: var(--text-faint); font-size: var(--text-sm); }
-
-/* ─── repo home ────────────────────────────────────────────────────── */
-.repo-summary {
-  display: flex; flex-wrap: wrap; gap: var(--space-4); align-items: center;
-  font-family: var(--font-mono); font-size: var(--text-sm); color: var(--text-muted);
-  padding: var(--space-3); border: 1px solid var(--border); border-radius: var(--radius);
-  margin-bottom: var(--space-4);
-}
-.repo-summary code { color: var(--text); }
-.last-commit { font-size: var(--text-sm); color: var(--text); margin: var(--space-3) 0; }
-
-/* ─── tree & breadcrumbs ───────────────────────────────────────────── */
-.crumbs { font-family: var(--font-mono); font-size: var(--text-sm); margin: var(--space-3) 0; }
-.crumbs .sep { margin: 0 var(--space-1); }
-table.tree { width: 100%; border-collapse: collapse; font-family: var(--font-mono); font-size: var(--text-sm); border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; }
-table.tree td { padding: var(--space-2) var(--space-3); border-bottom: 1px solid var(--border); }
-table.tree tr:last-child td { border-bottom: 0; }
-table.tree tr:hover td { background: var(--bg-hover); }
-
-/* ─── blob bar ─────────────────────────────────────────────────────── */
-.blob-bar {
-  display: flex; gap: var(--space-3); align-items: center; justify-content: space-between;
-  font-family: var(--font-mono); font-size: var(--text-sm);
-  padding: var(--space-2) var(--space-3);
-  background: var(--bg-card); border: 1px solid var(--border); border-bottom: 0;
-  border-radius: var(--radius) var(--radius) 0 0;
-}
-
-/* ─── log ──────────────────────────────────────────────────────────── */
-.log { list-style: none; padding: 0; margin: 0; }
-.log li {
-  display: flex; align-items: baseline; flex-wrap: wrap; gap: var(--space-3);
-  padding: var(--space-2) 0; border-bottom: 1px solid var(--border); font-size: var(--text-sm);
-}
-.commit-id {
-  font-family: var(--font-mono); color: var(--accent);
-  background: var(--bg-active); padding: 0 var(--space-2); border-radius: var(--radius-sm);
-  flex: none;
-}
-.commit-title { color: var(--text); }
-.last-commit { display: flex; align-items: baseline; flex-wrap: wrap; gap: var(--space-3); font-size: var(--text-sm); margin: var(--space-3) 0; }
-
-.refs { list-style: none; padding: 0; }
-.refs li { display: flex; gap: var(--space-3); padding: var(--space-2) 0; border-bottom: 1px solid var(--border); font-family: var(--font-mono); font-size: var(--text-sm); }
-
-/* ─── commit ───────────────────────────────────────────────────────── */
-.commit-subject { font-family: var(--font-mono); }
-.commit-body { background: var(--bg-card); padding: var(--space-3); border-radius: var(--radius-sm); white-space: pre-wrap; }
-
-/* ─── per-file diffs (commit view) ─────────────────────────────────── */
-.diff-summary { font-family: var(--font-mono); font-size: var(--text-sm); margin: var(--space-4) 0 var(--space-2); }
-.filediff { margin: var(--space-3) 0; border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; }
-.filediff-head {
-  display: flex; align-items: center; justify-content: space-between; gap: var(--space-3);
-  padding: var(--space-2) var(--space-3);
-  background: var(--bg-card); border-bottom: 1px solid var(--border);
-  font-family: var(--font-mono); font-size: var(--text-sm);
-}
-.filediff-name { color: var(--accent); font-weight: 600; word-break: break-all; }
-.diffstat { flex: none; font-family: var(--font-mono); font-size: var(--text-xs); }
-.diffstat .add { color: var(--green-600); }
-.diffstat .del { color: var(--red-600); }
-[data-theme$="-dark"] .diffstat .add { color: var(--green-300); }
-[data-theme$="-dark"] .diffstat .del { color: var(--red-300); }
-.filediff .code.diff { border: 0; border-radius: 0; }
-.filediff .empty { padding: var(--space-3); margin: 0; }
-
-/* ─── code blocks (chroma) ─────────────────────────────────────────── */
-.code {
-  border: 1px solid var(--border); border-radius: 0 0 var(--radius) var(--radius);
-  overflow-x: auto; background: var(--bg-card);
-}
-.code.diff { border-radius: var(--radius); }
-.chroma { margin: 0; padding: var(--space-3); background: transparent; font-size: var(--text-sm); line-height: var(--leading-normal); }
-.chroma .lntable { border-collapse: collapse; width: 100%; }
-.chroma .lntd { vertical-align: top; padding: 0; }
-.chroma .lnt, .chroma .ln { color: var(--text-faint); padding-right: var(--space-3); user-select: none; }
-
-/* token colors — all resolve from theme color families */
-.chroma .c, .chroma .ch, .chroma .cm, .chroma .c1, .chroma .cs, .chroma .cp, .chroma .cpf { color: var(--text-faint); font-style: italic; }
-.chroma .k, .chroma .kc, .chroma .kd, .chroma .kn, .chroma .kp, .chroma .kr, .chroma .kt { color: var(--purple); }
-.chroma .o, .chroma .ow { color: var(--pink); }
-.chroma .p, .chroma .pi { color: var(--text-muted); }
-.chroma .s, .chroma .sa, .chroma .sb, .chroma .sc, .chroma .dl, .chroma .sd, .chroma .s2, .chroma .se, .chroma .sh, .chroma .si, .chroma .sx, .chroma .sr, .chroma .s1, .chroma .ss { color: var(--green); }
-.chroma .m, .chroma .mb, .chroma .mf, .chroma .mh, .chroma .mi, .chroma .il, .chroma .mo { color: var(--orange); }
-.chroma .nf, .chroma .fm { color: var(--blue); }
-.chroma .nb, .chroma .bp { color: var(--cyan); }
-.chroma .nc, .chroma .nn, .chroma .no { color: var(--yellow); }
-.chroma .na, .chroma .nv, .chroma .vc, .chroma .vg, .chroma .vi { color: var(--cyan); }
-.chroma .nt { color: var(--red); }
-.chroma .nd, .chroma .ni, .chroma .ne, .chroma .nl, .chroma .py, .chroma .nx, .chroma .n { color: var(--text); }
-.chroma .err { color: var(--red); }
-
-/* diff (chroma diff lexer) */
-.chroma .gi { color: var(--green-600); background: color-mix(in oklab, var(--green) 16%, var(--bg)); display: block; }
-.chroma .gd { color: var(--red-600);   background: color-mix(in oklab, var(--red) 16%, var(--bg));   display: block; }
-.chroma .gu, .chroma .gh { color: var(--blue); font-weight: 700; display: block; }
-.chroma .gp { color: var(--text-faint); }
-
-/* ─── markdown (README, issue bodies) ──────────────────────────────── */
-.markdown { line-height: var(--leading-relax); }
-.markdown.readme, .markdown.issue-body {
-  border: 1px solid var(--border); border-radius: var(--radius);
-  padding: var(--space-6); margin-top: var(--space-4); background: var(--bg-card);
-}
-.markdown h1, .markdown h2, .markdown h3, .markdown h4, .markdown h5, .markdown h6 { margin: var(--space-6) 0 var(--space-3); }
-/* h1 — Awke bold inside a solid pink block (Soft Serve title style) */
-.markdown h1 {
-  font-family: var(--font-display); font-weight: 700;
-  display: inline-block;
-  background: var(--pink); color: var(--bg);
-  padding: var(--space-1) var(--space-4);
-  border-radius: var(--radius-sm);
-}
-/* h2 — Awke regular; rest walk the palette in the body face */
-.markdown h2 { font-family: var(--font-display); font-weight: 400; color: var(--blue); border-bottom: 1px solid var(--blue-200); padding-bottom: var(--space-2); }
-.markdown h3 { font-family: var(--font-body); color: var(--cyan); }
-.markdown h4 { font-family: var(--font-body); color: var(--purple); }
-.markdown h5, .markdown h6 { font-family: var(--font-body); color: var(--green); }
-.markdown p, .markdown ul, .markdown ol { margin: var(--space-3) 0; }
-.markdown a { color: var(--accent); text-decoration: underline; text-decoration-color: var(--accent-subtle-border); }
-.markdown strong { color: var(--orange); font-weight: 700; }
-.markdown em { color: var(--cyan); font-style: italic; }
-.markdown del { color: var(--text-faint); }
-.markdown code { color: var(--green-600); background: var(--bg-active); padding: 0.1em 0.35em; border-radius: var(--radius-sm); }
-[data-theme$="-dark"] .markdown code { color: var(--green-300); }
-.markdown pre { background: var(--bg); border: 1px solid var(--border); border-left: 3px solid var(--accent); border-radius: var(--radius); padding: var(--space-3); overflow-x: auto; }
-.markdown pre code { color: inherit; background: transparent; padding: 0; }
-.markdown pre.chroma, .markdown .chroma pre { background: transparent; border: 0; padding: 0; }
-.markdown blockquote { margin: var(--space-3) 0; padding: var(--space-1) var(--space-4); border-left: 3px solid var(--purple); color: var(--text-muted); background: var(--accent-subtle); border-radius: 0 var(--radius-sm) var(--radius-sm) 0; }
-.markdown hr { border: 0; border-top: 1px solid var(--border-strong); margin: var(--space-6) 0; }
-.markdown ul li::marker { color: var(--accent); }
-.markdown ol li::marker { color: var(--accent); }
-.markdown table { border-collapse: collapse; }
-.markdown th, .markdown td { border: 1px solid var(--border); padding: var(--space-2) var(--space-3); }
-.markdown th { background: var(--bg-active); color: var(--text-accent); }
-.markdown a code { color: inherit; }
-
-/* ─── issues ───────────────────────────────────────────────────────── */
-.issue-group { margin: var(--space-5) 0; }
-.issue-group h2.status { display: flex; align-items: baseline; gap: var(--space-2); }
-.issue-list { list-style: none; padding: 0; margin: var(--space-2) 0; border: 1px solid var(--border); border-radius: var(--radius); }
-.issue-list li { display: flex; align-items: baseline; gap: var(--space-2); padding: var(--space-2) var(--space-3); border-bottom: 1px solid var(--border); }
-.issue-list li:last-child { border-bottom: 0; }
-.issue-list li:hover { background: var(--bg-hover); }
-.issue-link { display: flex; align-items: baseline; gap: var(--space-2); min-width: 0; }
-.issue-id { font-family: var(--font-mono); font-size: var(--text-xs); color: var(--text-faint); }
-.issue-title { color: var(--text); }
-.issue-meta { display: flex; flex-wrap: wrap; gap: var(--space-2); align-items: baseline; margin: var(--space-3) 0; }
-.issue-dates { font-size: var(--text-sm); }
-.issue-detail-title { font-family: var(--font-mono); }
-
-/* chips, badges, status — color via families, shape via tokens */
-.chip, .badge {
-  font-family: var(--font-mono); font-size: var(--text-xs); line-height: 1.6;
-  padding: 0 var(--space-2); border-radius: var(--radius-sm);
-  border: 1px solid currentColor;
-}
-.badge { font-weight: 700; text-transform: lowercase; }
-.status {
-  font-family: var(--font-mono); font-size: var(--text-sm);
-  padding-left: var(--space-2); border-left: 3px solid currentColor;
-}
-.chip--red,    .badge--red    { color: var(--red-600); }
-.chip--orange, .badge--orange { color: var(--orange-600); }
-.chip--yellow, .badge--yellow { color: var(--yellow-600); }
-.chip--green,  .badge--green  { color: var(--green-600); }
-.chip--cyan,   .badge--cyan   { color: var(--cyan-600); }
-.chip--blue,   .badge--blue   { color: var(--blue-600); }
-.chip--purple, .badge--purple { color: var(--purple-600); }
-.chip--pink,   .badge--pink   { color: var(--pink-600); }
-.chip--accent, .badge--accent { color: var(--accent); }
-
-.status--backlog     { color: var(--blue); }
-.status--in-progress { color: var(--green); }
-.status--paused      { color: var(--orange); }
-.status--done        { color: var(--text-muted); }
-.status--other       { color: var(--text-muted); }
-
-/* dark themes: the -600 levels go too dark on a dark surface — use the lighter
-   -300 level for chips/badges so tags stay legible (e.g. humdrum-dark feature). */
-[data-theme$="-dark"] .chip--red,    [data-theme$="-dark"] .badge--red    { color: var(--red-300); }
-[data-theme$="-dark"] .chip--orange, [data-theme$="-dark"] .badge--orange { color: var(--orange-300); }
-[data-theme$="-dark"] .chip--yellow, [data-theme$="-dark"] .badge--yellow { color: var(--yellow-300); }
-[data-theme$="-dark"] .chip--green,  [data-theme$="-dark"] .badge--green  { color: var(--green-300); }
-[data-theme$="-dark"] .chip--cyan,   [data-theme$="-dark"] .badge--cyan   { color: var(--cyan-300); }
-[data-theme$="-dark"] .chip--blue,   [data-theme$="-dark"] .badge--blue   { color: var(--blue-300); }
-[data-theme$="-dark"] .chip--purple, [data-theme$="-dark"] .badge--purple { color: var(--purple-300); }
-[data-theme$="-dark"] .chip--pink,   [data-theme$="-dark"] .badge--pink   { color: var(--pink-300); }
-
-/* priority pills */
-.prio {
-  font-family: var(--font-mono); font-size: var(--text-xs); line-height: 1.6;
-  padding: 0 var(--space-2); border-radius: var(--radius-sm);
-  border: 1px solid currentColor;
-}
-.prio--high   { color: var(--red-600); }
-.prio--medium { color: var(--orange-600); }
-.prio--low    { color: var(--blue-600); }
-[data-theme$="-dark"] .prio--high   { color: var(--red-300); }
-[data-theme$="-dark"] .prio--medium { color: var(--orange-300); }
-[data-theme$="-dark"] .prio--low    { color: var(--blue-300); }
-
-/* paths & filenames — Charm-flavored: dirs accent+bold, files plain, ext muted */
-.tree-dir  { color: var(--blue); font-weight: 600; }
-.tree-file { color: var(--text); }
-.tree-file:hover, .tree-dir:hover { color: var(--accent); }
-.crumbs a { color: var(--blue); }
-.crumbs a:last-child { color: var(--accent); font-weight: 600; }
-
-/* "words in blocks" — filled pills for refs/branches */
-.ref-pill {
-  font-family: var(--font-mono); font-size: var(--text-xs);
-  background: var(--accent-subtle); color: var(--text-accent);
-  border: 1px solid var(--accent-subtle-border); border-radius: var(--radius-sm);
-  padding: 0 var(--space-2);
-}
-.section-label { font-family: var(--font-mono); color: var(--accent); font-size: var(--text-base); margin: var(--space-6) 0 var(--space-2); }
-.readme-section { margin-top: var(--space-6); }
-
-/* frontmatter block (markdown files opened in code view) */
-.frontmatter {
-  margin: 0; border: 1px solid var(--border); border-radius: var(--radius);
-  background: var(--bg-card); padding: var(--space-2) var(--space-3); font-family: var(--font-mono); font-size: var(--text-sm);
-}
-.fm-row { display: grid; grid-template-columns: minmax(8rem, max-content) 1fr; gap: var(--space-3); padding: var(--space-1) 0; border-bottom: 1px solid var(--border); }
-.fm-row:last-child { border-bottom: 0; }
-.fm-row dt { color: var(--purple); }
-.fm-row dd { margin: 0; color: var(--text); }
-
-/* ─── footer ───────────────────────────────────────────────────────── */
-.site-foot {
-  margin-top: var(--space-12);
-  display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-4); align-items: baseline;
-  padding: var(--space-3) var(--space-4);
-  background: var(--bg-card); border: 1px solid var(--border-strong); border-radius: var(--radius);
-  color: var(--text-muted); font-family: var(--font-mono); font-size: var(--text-xs);
-}
-.foot-brand { color: var(--accent); font-weight: 700; }
-.foot-item { color: var(--text-muted); }
-.foot-clone code { color: var(--text); background: var(--bg-active); padding: 0 var(--space-2); border-radius: var(--radius-sm); }
web/static/theme.js +0 −42
@@ -1,42 +0,0 @@
-// theme.js — live side of the theme switcher. The picker chooses a palette
-// FAMILY (flexoki/uchu/humdrum/eink); light vs dark follows the OS. An inline
-// <head> script already resolved the first paint; this handles switching and
-// reacting when the OS color scheme flips.
-(function () {
-  "use strict";
-  var FAMILIES = ["flexoki", "uchu", "humdrum", "eink"];
-
-  function family() {
-    var m = document.cookie.match(/(?:^|; )theme=([^;]+)/);
-    var f = m ? decodeURIComponent(m[1]) : "flexoki";
-    return FAMILIES.indexOf(f) < 0 ? "flexoki" : f;
-  }
-
-  function systemDark() {
-    return window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches;
-  }
-
-  function resolve(f) {
-    return f === "eink" ? "eink" : (systemDark() ? f + "-dark" : f);
-  }
-
-  function apply(f) {
-    document.documentElement.setAttribute("data-theme", resolve(f));
-    document.cookie = "theme=" + f + "; Path=/; Max-Age=31536000; SameSite=Lax";
-    try { localStorage.setItem("theme", f); } catch (e) {}
-  }
-
-  document.addEventListener("DOMContentLoaded", function () {
-    var sel = document.getElementById("theme-select");
-    if (sel) sel.addEventListener("change", function () { apply(sel.value); });
-  });
-
-  if (window.matchMedia) {
-    var mq = window.matchMedia("(prefers-color-scheme: dark)");
-    var onChange = function () {
-      document.documentElement.setAttribute("data-theme", resolve(family()));
-    };
-    if (mq.addEventListener) mq.addEventListener("change", onChange);
-    else if (mq.addListener) mq.addListener(onChange);
-  }
-})();
web/static/tokens.css +0 −638
@@ -1,638 +0,0 @@
-/*
- * tokens.css — single source-of-truth for theme + type tokens.
- * Imported by scaffold/app/globals.css and STYLE_GUIDE.html.
- *
- * Themes (7 modes via data-theme):
- *   flexoki | flexoki-dark | uchu | uchu-dark | humdrum | humdrum-dark | eink
- *
- * Color levels (per family — red, orange, yellow, green, cyan, blue, purple, pink):
- *   --{color}-200 — lightest accent, hairline borders, soft fills
- *   --{color}-300 — soft accent, hover backgrounds
- *   --{color}-400 — DEFAULT (also exposed as --{color} alias)
- *   --{color}-500 — pressed/active
- *   --{color}-600 — strong text on light, prominent fills
- *
- * Sources:
- *   Flexoki — github.com/kepano/flexoki (kepano)
- *   Uchu    — github.com/Passw/NeverCease-uchu, uchu.style
- *   Humdrum — custom; blue base #0f80ea per spec, OKLCH-derived levels
- */
-
-/* ─── Font face declarations ───────────────────────────────────────── */
-
-/* Awke — display. Three weights. */
-@font-face {
-  font-family: "Awke";
-  src: url("/static/fonts/Awke-Thin.woff2") format("woff2");
-  font-weight: 100;
-  font-style: normal;
-  font-display: swap;
-}
-@font-face {
-  font-family: "Awke";
-  src: url("/static/fonts/Awke-Regular.woff2") format("woff2");
-  font-weight: 400;
-  font-style: normal;
-  font-display: swap;
-}
-@font-face {
-  font-family: "Awke";
-  src: url("/static/fonts/Awke-Bold.woff2") format("woff2");
-  font-weight: 700;
-  font-style: normal;
-  font-display: swap;
-}
-
-/* Untitled Sans — body. Light/Regular/Medium/Bold/Black + italics. */
-@font-face {
-  font-family: "Untitled Sans";
-  src: url("/static/fonts/untitled-sans-light.woff2") format("woff2");
-  font-weight: 300; font-style: normal; font-display: swap;
-}
-@font-face {
-  font-family: "Untitled Sans";
-  src: url("/static/fonts/untitled-sans-light-italic.woff2") format("woff2");
-  font-weight: 300; font-style: italic; font-display: swap;
-}
-@font-face {
-  font-family: "Untitled Sans";
-  src: url("/static/fonts/untitled-sans-regular.woff2") format("woff2");
-  font-weight: 400; font-style: normal; font-display: swap;
-}
-@font-face {
-  font-family: "Untitled Sans";
-  src: url("/static/fonts/untitled-sans-regular-italic.woff2") format("woff2");
-  font-weight: 400; font-style: italic; font-display: swap;
-}
-@font-face {
-  font-family: "Untitled Sans";
-  src: url("/static/fonts/untitled-sans-medium.woff2") format("woff2");
-  font-weight: 500; font-style: normal; font-display: swap;
-}
-@font-face {
-  font-family: "Untitled Sans";
-  src: url("/static/fonts/untitled-sans-medium-italic.woff2") format("woff2");
-  font-weight: 500; font-style: italic; font-display: swap;
-}
-@font-face {
-  font-family: "Untitled Sans";
-  src: url("/static/fonts/untitled-sans-bold.woff2") format("woff2");
-  font-weight: 700; font-style: normal; font-display: swap;
-}
-@font-face {
-  font-family: "Untitled Sans";
-  src: url("/static/fonts/untitled-sans-bold-italic.woff2") format("woff2");
-  font-weight: 700; font-style: italic; font-display: swap;
-}
-@font-face {
-  font-family: "Untitled Sans";
-  src: url("/static/fonts/untitled-sans-black.woff2") format("woff2");
-  font-weight: 900; font-style: normal; font-display: swap;
-}
-@font-face {
-  font-family: "Untitled Sans";
-  src: url("/static/fonts/untitled-sans-black-italic.woff2") format("woff2");
-  font-weight: 900; font-style: italic; font-display: swap;
-}
-
-/* Name Mono — variable font preferred, static fallbacks. */
-@font-face {
-  font-family: "Name Mono";
-  src: url("/static/fonts/ATNameMonoVariable-Regular.woff2") format("woff2-variations"),
-       url("/static/fonts/ATNameMono-Regular.woff2") format("woff2");
-  font-weight: 100 900; font-style: normal; font-display: swap;
-}
-@font-face {
-  font-family: "Name Mono";
-  src: url("/static/fonts/ATNameMonoVariable-RegularItalic.woff2") format("woff2-variations"),
-       url("/static/fonts/ATNameMono-Italic.woff2") format("woff2");
-  font-weight: 100 900; font-style: italic; font-display: swap;
-}
-
-/* ─── Global tokens (shared across all themes) ─────────────────────── */
-
-:root {
-  --font-display: "Awke", "Untitled Sans", sans-serif;
-  --font-body:    "Untitled Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
-  --font-mono:    "Name Mono", ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;
-
-  --text-xs:   0.75rem;
-  --text-sm:   0.875rem;
-  --text-base: 1rem;
-  --text-lg:   1.125rem;
-  --text-xl:   1.25rem;
-  --text-2xl:  1.5rem;
-  --text-3xl:  1.875rem;
-  --text-4xl:  2.25rem;
-  --text-5xl:  3rem;
-  --leading-tight:  1.2;
-  --leading-normal: 1.5;
-  --leading-relax:  1.7;
-
-  --space-1:  0.25rem;
-  --space-2:  0.5rem;
-  --space-3:  0.75rem;
-  --space-4:  1rem;
-  --space-5:  1.25rem;
-  --space-6:  1.5rem;
-  --space-8:  2rem;
-  --space-10: 2.5rem;
-  --space-12: 3rem;
-  --space-16: 4rem;
-
-  --radius-sm: 4px;
-  --radius:    8px;
-  --radius-lg: 12px;
-  --radius-xl: 16px;
-
-  --tap-min: 44px;
-
-  /*
-   * Derived semantic tokens — resolve per-theme via the active --accent + --bg.
-   * color-mix evaluates at use-site, so the same :root declaration works for
-   * every theme without repetition.
-   */
-  --accent-subtle:        color-mix(in oklab, var(--accent) 12%, var(--bg));
-  --accent-subtle-border: var(--accent);
-  --focus-ring:           var(--accent);
-}
-
-/* ═══════════════════════════════════════════════════════════════════
-   FLEXOKI — kepano canonical (github.com/kepano/flexoki)
-   ═══════════════════════════════════════════════════════════════════ */
-
-:root,
-[data-theme="flexoki"] {
-  /* Surfaces */
-  --bg:            #FFFCF0;  /* paper */
-  --bg-card:       #F2F0E5;  /* 50 */
-  --bg-elevated:   #F2F0E5;
-  --bg-hover:      rgba(0, 0, 0, 0.04);
-  --bg-active:     rgba(0, 0, 0, 0.08);
-  --border:        #E6E4D9;  /* 100 */
-  --border-strong: #CECDC3;  /* 200 */
-  --text:          #100F0F;  /* black */
-  --text-muted:    #6F6E69;  /* 600 */
-  --text-faint:    #B7B5AC;  /* 300 */
-  --text-accent:   #100F0F;
-  --text-on-accent:#FFFCF0;
-  --selection:     #DDF1E4;  /* cyan-50 */
-  --accent:        #24837B;  /* cyan-600 */
-
-  /* Red */
-  --red-200: #F89A8A; --red-300: #E8705F; --red-400: #D14D41; --red-500: #C03E35; --red-600: #AF3029;
-  --red: var(--red-400);
-  /* Orange */
-  --orange-200: #F9AE77; --orange-300: #EC8B49; --orange-400: #DA702C; --orange-500: #CB6120; --orange-600: #BC5215;
-  --orange: var(--orange-400);
-  /* Yellow */
-  --yellow-200: #ECCB60; --yellow-300: #DFB431; --yellow-400: #D0A215; --yellow-500: #BE9207; --yellow-600: #AD8301;
-  --yellow: var(--yellow-400);
-  /* Green */
-  --green-200: #BEC97E; --green-300: #A0AF54; --green-400: #879A39; --green-500: #768D21; --green-600: #66800B;
-  --green: var(--green-400);
-  /* Cyan */
-  --cyan-200: #87D3C3; --cyan-300: #5ABDAC; --cyan-400: #3AA99F; --cyan-500: #2F968D; --cyan-600: #24837B;
-  --cyan: var(--cyan-400);
-  /* Blue */
-  --blue-200: #92BFDB; --blue-300: #66A0C8; --blue-400: #4385BE; --blue-500: #3171B2; --blue-600: #205EA6;
-  --blue: var(--blue-400);
-  /* Purple */
-  --purple-200: #C4B9E0; --purple-300: #A699D0; --purple-400: #8B7EC8; --purple-500: #735EB5; --purple-600: #5E409D;
-  --purple: var(--purple-400);
-  /* Pink (Flexoki magenta) */
-  --pink-200: #F4A4C2; --pink-300: #E47DA8; --pink-400: #CE5D97; --pink-500: #B74583; --pink-600: #A02F6F;
-  --pink: var(--pink-400);
-}
-
-[data-theme="flexoki-dark"] {
-  --bg:            #100F0F;  /* black */
-  --bg-card:       #1C1B1A;  /* 950 */
-  --bg-elevated:   #282726;  /* 900 */
-  --bg-hover:      rgba(255, 255, 255, 0.05);
-  --bg-active:     rgba(255, 255, 255, 0.1);
-  --border:        #282726;  /* 900 */
-  --border-strong: #403E3C;  /* 800 */
-  --text:          #CECDC3;  /* 200 */
-  --text-muted:    #878580;  /* 500 */
-  --text-faint:    #575653;  /* 700 */
-  --text-accent:   #CECDC3;
-  --text-on-accent:#100F0F;
-  --selection:     #122F2C;  /* cyan-900 */
-  --accent:        #3AA99F;  /* cyan-400 */
-
-  --red-200: #F89A8A; --red-300: #E8705F; --red-400: #D14D41; --red-500: #C03E35; --red-600: #AF3029;
-  --red: var(--red-400);
-  --orange-200: #F9AE77; --orange-300: #EC8B49; --orange-400: #DA702C; --orange-500: #CB6120; --orange-600: #BC5215;
-  --orange: var(--orange-400);
-  --yellow-200: #ECCB60; --yellow-300: #DFB431; --yellow-400: #D0A215; --yellow-500: #BE9207; --yellow-600: #AD8301;
-  --yellow: var(--yellow-400);
-  --green-200: #BEC97E; --green-300: #A0AF54; --green-400: #879A39; --green-500: #768D21; --green-600: #66800B;
-  --green: var(--green-400);
-  --cyan-200: #87D3C3; --cyan-300: #5ABDAC; --cyan-400: #3AA99F; --cyan-500: #2F968D; --cyan-600: #24837B;
-  --cyan: var(--cyan-400);
-  --blue-200: #92BFDB; --blue-300: #66A0C8; --blue-400: #4385BE; --blue-500: #3171B2; --blue-600: #205EA6;
-  --blue: var(--blue-400);
-  --purple-200: #C4B9E0; --purple-300: #A699D0; --purple-400: #8B7EC8; --purple-500: #735EB5; --purple-600: #5E409D;
-  --purple: var(--purple-400);
-  --pink-200: #F4A4C2; --pink-300: #E47DA8; --pink-400: #CE5D97; --pink-500: #B74583; --pink-600: #A02F6F;
-  --pink: var(--pink-400);
-}
-
-/* ═══════════════════════════════════════════════════════════════════
-   UCHU — canonical OKLCH (github.com/Passw/NeverCease-uchu, uchu.style)
-   Levels 2-6 of canonical 1-9 scale → mapped to 200-600.
-   ═══════════════════════════════════════════════════════════════════ */
-
-[data-theme="uchu"] {
-  --bg:            oklch(99.4% 0 0);                /* yang */
-  --bg-card:       oklch(95.57% 0.003 286.35);      /* gray-1 */
-  --bg-elevated:   oklch(99.4% 0 0);
-  --bg-hover:      rgba(0, 0, 0, 0.035);
-  --bg-active:     rgba(0, 0, 0, 0.07);
-  --border:        oklch(91.87% 0.003 264.54);      /* yin-1 */
-  --border-strong: oklch(84.61% 0.004 286.31);      /* yin-2 */
-  --text:          oklch(14.38% 0.007 256.88);      /* yin */
-  --text-muted:    oklch(43.87% 0.005 271.3);       /* yin-7 */
-  --text-faint:    oklch(69.17% 0.004 247.88);      /* yin-4 */
-  --text-accent:   oklch(14.38% 0.007 256.88);
-  --text-on-accent:oklch(99.4% 0 0);
-  --selection:     oklch(89.1% 0.046 305.24);       /* purple-1 */
-  --accent:        oklch(58.47% 0.181 302.06);      /* purple-4 */
-
-  --red-200:    oklch(78.78% 0.109 4.54);
-  --red-300:    oklch(69.86% 0.162 7.82);
-  --red-400:    oklch(62.73% 0.209 12.37);
-  --red-500:    oklch(58.63% 0.231 19.6);
-  --red-600:    oklch(54.41% 0.214 19.06);
-  --red: var(--red-400);
-
-  --orange-200: oklch(88.37% 0.0726 55.8);
-  --orange-300: oklch(83.56% 0.1075 56.49);
-  --orange-400: oklch(78.75% 0.1416 54.33);
-  --orange-500: oklch(74.61% 0.171 51.56);
-  --orange-600: oklch(69.33% 0.157 52.18);
-  --orange: var(--orange-400);
-
-  --yellow-200: oklch(95% 0.07 92.39);
-  --yellow-300: oklch(92.76% 0.098 92.58);
-  --yellow-400: oklch(90.92% 0.125 92.56);
-  --yellow-500: oklch(89% 0.146 91.5);
-  --yellow-600: oklch(82.39% 0.133 91.5);
-  --yellow: var(--yellow-400);
-
-  --green-200:  oklch(88.77% 0.096 147.71);
-  --green-300:  oklch(83.74% 0.139 146.57);
-  --green-400:  oklch(79.33% 0.179 145.62);
-  --green-500:  oklch(75.23% 0.209 144.64);
-  --green-600:  oklch(70.03% 0.194 144.71);
-  --green: var(--green-400);
-
-  /* Uchu has no canonical "cyan" — derive from blue+green hue middle. */
-  --cyan-200:   oklch(85% 0.07 200);
-  --cyan-300:   oklch(78% 0.10 200);
-  --cyan-400:   oklch(70% 0.13 200);
-  --cyan-500:   oklch(63% 0.14 200);
-  --cyan-600:   oklch(55% 0.13 200);
-  --cyan: var(--cyan-400);
-
-  --blue-200:   oklch(80.17% 0.091 258.88);
-  --blue-300:   oklch(70.94% 0.136 258.06);
-  --blue-400:   oklch(62.39% 0.181 258.33);
-  --blue-500:   oklch(54.87% 0.222 260.33);
-  --blue-600:   oklch(51.15% 0.204 260.17);
-  --blue: var(--blue-400);
-
-  --purple-200: oklch(78.68% 0.091 305);
-  --purple-300: oklch(68.5% 0.136 303.78);
-  --purple-400: oklch(58.47% 0.181 302.06);
-  --purple-500: oklch(49.39% 0.215 298.31);
-  --purple-600: oklch(46.11% 0.198 298.4);
-  --purple: var(--purple-400);
-
-  --pink-200:   oklch(92.14% 0.046 352.31);
-  --pink-300:   oklch(88.9% 0.066 354.39);
-  --pink-400:   oklch(85.43% 0.09 354.1);
-  --pink-500:   oklch(82.23% 0.112 355.33);
-  --pink-600:   oklch(76.37% 0.101 355.37);
-  --pink: var(--pink-400);
-}
-
-[data-theme="uchu-dark"] {
-  --bg:            oklch(14.38% 0.007 256.88);      /* yin */
-  --bg-card:       oklch(25.11% 0.006 258.36);      /* yin-9 */
-  --bg-elevated:   oklch(35.02% 0.005 236.66);      /* yin-8 */
-  --bg-hover:      rgba(255, 255, 255, 0.05);
-  --bg-active:     rgba(255, 255, 255, 0.1);
-  --border:        oklch(35.02% 0.005 236.66);      /* yin-8 */
-  --border-strong: oklch(43.87% 0.005 271.3);       /* yin-7 */
-  --text:          oklch(91.87% 0.003 264.54);      /* yin-1 */
-  --text-muted:    oklch(69.17% 0.004 247.88);      /* yin-4 */
-  --text-faint:    oklch(52.79% 0.005 271.32);      /* yin-6 */
-  --text-accent:   oklch(91.87% 0.003 264.54);
-  --text-on-accent:oklch(14.38% 0.007 256.88);
-  --selection:     oklch(36.01% 0.145 298.35);      /* purple-9 */
-  --accent:        oklch(68.5% 0.136 303.78);       /* purple-3 */
-
-  --red-200:    oklch(78.78% 0.109 4.54);
-  --red-300:    oklch(69.86% 0.162 7.82);
-  --red-400:    oklch(62.73% 0.209 12.37);
-  --red-500:    oklch(58.63% 0.231 19.6);
-  --red-600:    oklch(54.41% 0.214 19.06);
-  --red: var(--red-400);
-  --orange-200: oklch(88.37% 0.0726 55.8);
-  --orange-300: oklch(83.56% 0.1075 56.49);
-  --orange-400: oklch(78.75% 0.1416 54.33);
-  --orange-500: oklch(74.61% 0.171 51.56);
-  --orange-600: oklch(69.33% 0.157 52.18);
-  --orange: var(--orange-400);
-  --yellow-200: oklch(95% 0.07 92.39);
-  --yellow-300: oklch(92.76% 0.098 92.58);
-  --yellow-400: oklch(90.92% 0.125 92.56);
-  --yellow-500: oklch(89% 0.146 91.5);
-  --yellow-600: oklch(82.39% 0.133 91.5);
-  --yellow: var(--yellow-400);
-  --green-200:  oklch(88.77% 0.096 147.71);
-  --green-300:  oklch(83.74% 0.139 146.57);
-  --green-400:  oklch(79.33% 0.179 145.62);
-  --green-500:  oklch(75.23% 0.209 144.64);
-  --green-600:  oklch(70.03% 0.194 144.71);
-  --green: var(--green-400);
-  --cyan-200:   oklch(85% 0.07 200);
-  --cyan-300:   oklch(78% 0.10 200);
-  --cyan-400:   oklch(70% 0.13 200);
-  --cyan-500:   oklch(63% 0.14 200);
-  --cyan-600:   oklch(55% 0.13 200);
-  --cyan: var(--cyan-400);
-  --blue-200:   oklch(80.17% 0.091 258.88);
-  --blue-300:   oklch(70.94% 0.136 258.06);
-  --blue-400:   oklch(62.39% 0.181 258.33);
-  --blue-500:   oklch(54.87% 0.222 260.33);
-  --blue-600:   oklch(51.15% 0.204 260.17);
-  --blue: var(--blue-400);
-  --purple-200: oklch(78.68% 0.091 305);
-  --purple-300: oklch(68.5% 0.136 303.78);
-  --purple-400: oklch(58.47% 0.181 302.06);
-  --purple-500: oklch(49.39% 0.215 298.31);
-  --purple-600: oklch(46.11% 0.198 298.4);
-  --purple: var(--purple-400);
-  --pink-200:   oklch(92.14% 0.046 352.31);
-  --pink-300:   oklch(88.9% 0.066 354.39);
-  --pink-400:   oklch(85.43% 0.09 354.1);
-  --pink-500:   oklch(82.23% 0.112 355.33);
-  --pink-600:   oklch(76.37% 0.101 355.37);
-  --pink: var(--pink-400);
-}
-
-/* ═══════════════════════════════════════════════════════════════════
-   HUMDRUM — warm neutrals, vivid blue accent (#0f80ea ≈ oklch 60% 0.21 246).
-   All accent families derived in OKLCH around 400 = base.
-   ═══════════════════════════════════════════════════════════════════ */
-
-[data-theme="humdrum"] {
-  --bg:            #F5F3EE;
-  --bg-card:       #FFFFFF;
-  --bg-elevated:   #FFFFFF;
-  --bg-hover:      rgba(0, 0, 0, 0.035);
-  --bg-active:     rgba(0, 0, 0, 0.07);
-  --border:        #E2DFD7;
-  --border-strong: #C3BFB3;
-  --text:          #2A2825;
-  --text-muted:    #6D6A63;
-  --text-faint:    #ADA99F;
-  --text-accent:   #2A2825;
-  --text-on-accent:#FFFFFF;
-  --selection:     oklch(0.85 0.07 253);            /* blue-200 tint */
-  --accent:        #0F80EA;                         /* blue-400, kept as hex */
-
-  /*
-   * Color levels — every family anchored at oklch(0.60 0.18 H).
-   * Blue 400 = oklch(0.60 0.18 253) ≈ #0F80EA (actual conversion).
-   * All other families share L=0.60 C=0.18 at 400, only hue changes.
-   *
-   * Step pattern (L, C multiplier):
-   *   200: L 0.82, C × 0.45  (= 0.081)
-   *   300: L 0.72, C × 0.75  (= 0.135)
-   *   400: L 0.60, C × 1.00  (= 0.180)  base
-   *   500: L 0.53, C × 0.95  (= 0.171)
-   *   600: L 0.45, C × 0.85  (= 0.153)
-   */
-
-  /* Blue (primary) — base = #0F80EA */
-  --blue-200:   oklch(0.82 0.081 253);
-  --blue-300:   oklch(0.72 0.135 253);
-  --blue-400:   oklch(0.60 0.180 253);
-  --blue-500:   oklch(0.53 0.171 253);
-  --blue-600:   oklch(0.45 0.153 253);
-  --blue: var(--blue-400);
-
-  --red-200:    oklch(0.82 0.081 22);
-  --red-300:    oklch(0.72 0.135 22);
-  --red-400:    oklch(0.60 0.180 22);
-  --red-500:    oklch(0.53 0.171 22);
-  --red-600:    oklch(0.45 0.153 22);
-  --red: var(--red-400);
-
-  --orange-200: oklch(0.82 0.081 50);
-  --orange-300: oklch(0.72 0.135 50);
-  --orange-400: oklch(0.60 0.180 50);
-  --orange-500: oklch(0.53 0.171 50);
-  --orange-600: oklch(0.45 0.153 50);
-  --orange: var(--orange-400);
-
-  --yellow-200: oklch(0.82 0.081 85);
-  --yellow-300: oklch(0.72 0.135 85);
-  --yellow-400: oklch(0.60 0.180 85);
-  --yellow-500: oklch(0.53 0.171 85);
-  --yellow-600: oklch(0.45 0.153 85);
-  --yellow: var(--yellow-400);
-
-  --green-200:  oklch(0.82 0.081 140);
-  --green-300:  oklch(0.72 0.135 140);
-  --green-400:  oklch(0.60 0.180 140);
-  --green-500:  oklch(0.53 0.171 140);
-  --green-600:  oklch(0.45 0.153 140);
-  --green: var(--green-400);
-
-  --cyan-200:   oklch(0.82 0.081 205);
-  --cyan-300:   oklch(0.72 0.135 205);
-  --cyan-400:   oklch(0.60 0.180 205);
-  --cyan-500:   oklch(0.53 0.171 205);
-  --cyan-600:   oklch(0.45 0.153 205);
-  --cyan: var(--cyan-400);
-
-  --purple-200: oklch(0.82 0.081 295);
-  --purple-300: oklch(0.72 0.135 295);
-  --purple-400: oklch(0.60 0.180 295);
-  --purple-500: oklch(0.53 0.171 295);
-  --purple-600: oklch(0.45 0.153 295);
-  --purple: var(--purple-400);
-
-  --pink-200:   oklch(0.82 0.081 350);
-  --pink-300:   oklch(0.72 0.135 350);
-  --pink-400:   oklch(0.60 0.180 350);
-  --pink-500:   oklch(0.53 0.171 350);
-  --pink-600:   oklch(0.45 0.153 350);
-  --pink: var(--pink-400);
-}
-
-[data-theme="humdrum-dark"] {
-  --bg:            #1F1D1A;
-  --bg-card:       #282622;
-  --bg-elevated:   #32302C;
-  --bg-hover:      rgba(255, 255, 255, 0.04);
-  --bg-active:     rgba(255, 255, 255, 0.08);
-  --border:        #32302C;
-  --border-strong: #4A4740;
-  --text:          #E8E5DD;
-  --text-muted:    #A8A49B;
-  --text-faint:    #6D6A63;
-  --text-accent:   #E8E5DD;
-  --text-on-accent:#FFFFFF;
-  --selection:     oklch(0.35 0.13 253);            /* dark blue tint */
-  --accent:        #0F80EA;                         /* same as light, kept hex */
-
-  --blue-200:   oklch(0.82 0.081 253);
-  --blue-300:   oklch(0.72 0.135 253);
-  --blue-400:   oklch(0.60 0.180 253);
-  --blue-500:   oklch(0.53 0.171 253);
-  --blue-600:   oklch(0.45 0.153 253);
-  --blue: var(--blue-400);
-
-  --red-200:    oklch(0.82 0.081 22);
-  --red-300:    oklch(0.72 0.135 22);
-  --red-400:    oklch(0.60 0.180 22);
-  --red-500:    oklch(0.53 0.171 22);
-  --red-600:    oklch(0.45 0.153 22);
-  --red: var(--red-400);
-
-  --orange-200: oklch(0.82 0.081 50);
-  --orange-300: oklch(0.72 0.135 50);
-  --orange-400: oklch(0.60 0.180 50);
-  --orange-500: oklch(0.53 0.171 50);
-  --orange-600: oklch(0.45 0.153 50);
-  --orange: var(--orange-400);
-
-  --yellow-200: oklch(0.82 0.081 85);
-  --yellow-300: oklch(0.72 0.135 85);
-  --yellow-400: oklch(0.60 0.180 85);
-  --yellow-500: oklch(0.53 0.171 85);
-  --yellow-600: oklch(0.45 0.153 85);
-  --yellow: var(--yellow-400);
-
-  --green-200:  oklch(0.82 0.081 140);
-  --green-300:  oklch(0.72 0.135 140);
-  --green-400:  oklch(0.60 0.180 140);
-  --green-500:  oklch(0.53 0.171 140);
-  --green-600:  oklch(0.45 0.153 140);
-  --green: var(--green-400);
-
-  --cyan-200:   oklch(0.82 0.081 205);
-  --cyan-300:   oklch(0.72 0.135 205);
-  --cyan-400:   oklch(0.60 0.180 205);
-  --cyan-500:   oklch(0.53 0.171 205);
-  --cyan-600:   oklch(0.45 0.153 205);
-  --cyan: var(--cyan-400);
-
-  --purple-200: oklch(0.82 0.081 295);
-  --purple-300: oklch(0.72 0.135 295);
-  --purple-400: oklch(0.60 0.180 295);
-  --purple-500: oklch(0.53 0.171 295);
-  --purple-600: oklch(0.45 0.153 295);
-  --purple: var(--purple-400);
-
-  --pink-200:   oklch(0.82 0.081 350);
-  --pink-300:   oklch(0.72 0.135 350);
-  --pink-400:   oklch(0.60 0.180 350);
-  --pink-500:   oklch(0.53 0.171 350);
-  --pink-600:   oklch(0.45 0.153 350);
-  --pink: var(--pink-400);
-}
-
-/* ═══════════════════════════════════════════════════════════════════
-   E-INK — pure monochrome, no motion. All accents collapse to black.
-   ═══════════════════════════════════════════════════════════════════ */
-
-[data-theme="eink"] {
-  --bg:            #FFFFFF;
-  --bg-card:       #FFFFFF;
-  --bg-elevated:   #FFFFFF;
-  --bg-hover:      #000000;
-  --bg-active:     #000000;
-  --border:        #000000;
-  --border-strong: #000000;
-  --text:          #000000;
-  --text-muted:    #000000;
-  --text-faint:    #000000;
-  --text-accent:   #000000;
-  --text-on-accent:#FFFFFF;
-  --selection:     #000000;
-  --accent:        #000000;
-
-  --red-200: #000; --red-300: #000; --red-400: #000; --red-500: #000; --red-600: #000;
-  --red: #000;
-  --orange-200: #000; --orange-300: #000; --orange-400: #000; --orange-500: #000; --orange-600: #000;
-  --orange: #000;
-  --yellow-200: #000; --yellow-300: #000; --yellow-400: #000; --yellow-500: #000; --yellow-600: #000;
-  --yellow: #000;
-  --green-200: #000; --green-300: #000; --green-400: #000; --green-500: #000; --green-600: #000;
-  --green: #000;
-  --cyan-200: #000; --cyan-300: #000; --cyan-400: #000; --cyan-500: #000; --cyan-600: #000;
-  --cyan: #000;
-  --blue-200: #000; --blue-300: #000; --blue-400: #000; --blue-500: #000; --blue-600: #000;
-  --blue: #000;
-  --purple-200: #000; --purple-300: #000; --purple-400: #000; --purple-500: #000; --purple-600: #000;
-  --purple: #000;
-  --pink-200: #000; --pink-300: #000; --pink-400: #000; --pink-500: #000; --pink-600: #000;
-  --pink: #000;
-}
-
-[data-theme="eink"],
-[data-theme="eink"] * {
-  transition: none !important;
-  animation: none !important;
-  box-shadow: none !important;
-  font-weight: 600;
-}
-
-[data-theme="eink"] strong,
-[data-theme="eink"] b,
-[data-theme="eink"] h1,
-[data-theme="eink"] h2,
-[data-theme="eink"] h3 {
-  font-weight: 800;
-}
-
-[data-theme="eink"] button,
-[data-theme="eink"] .card,
-[data-theme="eink"] .nav,
-[data-theme="eink"] .btn,
-[data-theme="eink"] .dialog {
-  border: 2px solid #000 !important;
-  border-radius: 0 !important;
-}
-
-[data-theme="eink"] .btn-primary {
-  background: #000 !important;
-  color: #fff !important;
-}
-
-[data-theme="eink"] .highlight {
-  background: transparent !important;
-  border-bottom: 4px solid #000 !important;
-  border-radius: 0 !important;
-}
-
-/* ─── Base body wiring ─────────────────────────────────────────────── */
-
-html, body {
-  background: var(--bg);
-  color: var(--text);
-  font-family: var(--font-body);
-  font-size: var(--text-base);
-  line-height: var(--leading-normal);
-}
-
-h1, h2, h3 { font-family: var(--font-display); line-height: var(--leading-tight); }
-code, pre, kbd, samp { font-family: var(--font-mono); }
-::selection { background: var(--selection); }
web/templates/templates.templ +0 −459
@@ -1,459 +0,0 @@
-package templates
-
-import (
-	"fmt"
-	"strconv"
-	"strings"
-)
-
-// Layout is the shared page shell. Styling is intentionally minimal in phase 1;
-// the design pass (tokens + Charm chrome) lands in phase 3.
-templ Layout(m Meta) {
-	<!DOCTYPE html>
-	<html lang="en" data-theme={ themeOr(m.Theme) }>
-		<head>
-			<meta charset="utf-8"/>
-			<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"/>
-			<title>{ pageTitle(m) }</title>
-			@templ.Raw(themeResolveScript)
-			<link rel="stylesheet" href="/static/tokens.css"/>
-			<link rel="stylesheet" href="/static/custard.css"/>
-			<script src="/static/theme.js" defer></script>
-		</head>
-		<body>
-			<header class="site-head">
-				<div class="site-head-left">
-					<a href="/" class="brand">▍ humdrum codex</a>
-					if m.Repo != "" {
-						<span class="sep">/</span>
-						<a href={ templ.SafeURL("/r/" + m.Repo) } class="repo-name">{ m.Repo }</a>
-					}
-				</div>
-				@themePicker(m)
-			</header>
-			if m.Repo != "" {
-				@repoTabs(m)
-			}
-			<main class="content">
-				{ children... }
-			</main>
-			<footer class="site-foot">
-				<span class="foot-brand">▍ served by custard</span>
-				if m.Repo != "" {
-					<span class="foot-item">{ m.Repo }</span>
-					if m.Ref != "" {
-						<span class="foot-item ref-pill">{ m.Ref }</span>
-					}
-					if m.CloneURL != "" {
-						<span class="foot-item foot-clone">clone <code>git clone { m.CloneURL }</code></span>
-					}
-				}
-			</footer>
-		</body>
-	</html>
-}
-
-templ themePicker(m Meta) {
-	<select id="theme-select" aria-label="Theme" class="theme-select">
-		for _, t := range Families {
-			if t == themeOr(m.Theme) {
-				<option value={ t } selected>{ themeLabel(t) }</option>
-			} else {
-				<option value={ t }>{ themeLabel(t) }</option>
-			}
-		}
-	</select>
-}
-
-// themeResolveScript runs synchronously in <head> before paint: it reads the
-// family cookie and the OS color scheme, then sets the concrete data-theme
-// (family or family-dark; eink is fixed). This is the flash-free path for the
-// system-driven light/dark axis the server can't see.
-const themeResolveScript = `<script>(function(){try{` +
-	`var m=document.cookie.match(/(?:^|; )theme=([^;]+)/);` +
-	`var f=m?decodeURIComponent(m[1]):'flexoki';` +
-	`if(['flexoki','uchu','humdrum','eink'].indexOf(f)<0)f='flexoki';` +
-	`var d=window.matchMedia&&window.matchMedia('(prefers-color-scheme: dark)').matches;` +
-	`document.documentElement.setAttribute('data-theme',f==='eink'?'eink':(d?f+'-dark':f));` +
-	`}catch(e){}})();</script>`
-
-templ repoTabs(m Meta) {
-	<nav class="repo-tabs">
-		if m.HasReadme {
-			<a href={ templ.SafeURL("/r/" + m.Repo + "/readme") } class={ templ.KV("active", m.Tab == "readme") }>readme</a>
-		}
-		<a href={ templ.SafeURL("/r/" + m.Repo + "/files") } class={ templ.KV("active", m.Tab == "code") }>code</a>
-		<a href={ templ.SafeURL("/r/" + m.Repo + "/log/" + refOr(m.Ref)) } class={ templ.KV("active", m.Tab == "log") }>log</a>
-		<a href={ templ.SafeURL("/r/" + m.Repo + "/refs") } class={ templ.KV("active", m.Tab == "refs") }>refs</a>
-		if m.HasIssues {
-			<a href={ templ.SafeURL("/r/" + m.Repo + "/issues") } class={ templ.KV("active", m.Tab == "issues") }>issues</a>
-		}
-	</nav>
-}
-
-templ Index(p IndexPage) {
-	@Layout(p.Meta) {
-		<h1>Repositories</h1>
-		if len(p.Repos) == 0 {
-			<p class="empty">No repositories found.</p>
-		}
-		<ul class="repo-list">
-			for _, r := range p.Repos {
-				<li>
-					<a class="repo-link" href={ templ.SafeURL("/r/" + r.Name) }>{ r.Name }</a>
-					if r.Description != "" {
-						<span class="repo-desc">{ r.Description }</span>
-					}
-					if r.Last != nil {
-						<span class="repo-meta">{ r.Last.Subject } · { FmtTime(r.Last.When) }</span>
-					}
-				</li>
-			}
-		</ul>
-	}
-}
-
-templ Repo(p RepoPage) {
-	@Layout(p.Meta) {
-		<div class="repo-summary">
-			<span class="ref-pill">{ p.DefaultBranch }</span>
-			<span>{ strconv.Itoa(p.Branches) } branches</span>
-			<span>{ strconv.Itoa(p.Tags) } tags</span>
-		</div>
-		if p.Last != nil {
-			<p class="last-commit">
-				<a href={ templ.SafeURL("/r/" + p.Meta.Repo + "/commit/" + p.Last.Hash) } class="commit-id">{ p.Last.Short }</a>
-				<span class="commit-title">{ p.Last.Subject }</span>
-				<span class="muted">— { p.Last.Author }, { FmtTime(p.Last.When) }</span>
-			</p>
-		}
-		if len(p.Entries) > 0 {
-			<table class="tree">
-				<tbody>
-					for _, e := range p.Entries {
-						<tr>
-							<td>
-								if e.IsDir {
-									<a class="tree-dir" href={ templ.SafeURL("/r/" + p.Meta.Repo + "/tree/" + p.DefaultBranch + "/" + e.Path) }>{ e.Name }/</a>
-								} else {
-									<a class="tree-file" href={ templ.SafeURL("/r/" + p.Meta.Repo + "/blob/" + p.DefaultBranch + "/" + e.Path) }>{ e.Name }</a>
-								}
-							</td>
-							<td class="num">
-								if !e.IsDir {
-									{ HumanSize(e.Size) }
-								}
-							</td>
-						</tr>
-					}
-				</tbody>
-			</table>
-		}
-	}
-}
-
-templ Readme(p ReadmePage) {
-	@Layout(p.Meta) {
-		<article class="markdown readme">
-			@templ.Raw(p.Readme)
-		</article>
-	}
-}
-
-templ Tree(p TreePage) {
-	@Layout(p.Meta) {
-		@crumbs(p.Meta, p.Crumbs)
-		<table class="tree">
-			<tbody>
-				if p.Path != "" {
-					<tr><td colspan="2"><a href={ templ.SafeURL(parentTreeURL(p.Meta, p.Path)) }>..</a></td></tr>
-				}
-				for _, e := range p.Entries {
-					<tr>
-						<td>
-							if e.IsDir {
-								<a class="tree-dir" href={ templ.SafeURL("/r/" + p.Meta.Repo + "/tree/" + p.Meta.Ref + "/" + e.Path) }>{ e.Name }/</a>
-							} else {
-								<a class="tree-file" href={ templ.SafeURL("/r/" + p.Meta.Repo + "/blob/" + p.Meta.Ref + "/" + e.Path) }>{ e.Name }</a>
-							}
-						</td>
-						<td class="num">
-							if !e.IsDir {
-								{ HumanSize(e.Size) }
-							}
-						</td>
-					</tr>
-				}
-			</tbody>
-		</table>
-	}
-}
-
-templ Blob(p BlobPage) {
-	@Layout(p.Meta) {
-		@crumbs(p.Meta, p.Crumbs)
-		<div class="blob-bar">
-			<span class="muted">{ HumanSize(p.Size) }</span>
-			<a href={ templ.SafeURL("/r/" + p.Meta.Repo + "/raw/" + p.Meta.Ref + "/" + p.Path) }>raw</a>
-		</div>
-		if p.IsBinary {
-			<p class="empty">Binary file not shown.</p>
-		} else if p.IsMarkdown {
-			if len(p.Frontmatter) > 0 {
-				<dl class="frontmatter">
-					for _, kv := range p.Frontmatter {
-						<div class="fm-row">
-							<dt>{ kv.Key }</dt>
-							<dd>{ kv.Value }</dd>
-						</div>
-					}
-				</dl>
-			}
-			<article class="markdown">
-				@templ.Raw(p.Markdown)
-			</article>
-		} else {
-			<div class="code">
-				@templ.Raw(p.Code)
-			</div>
-		}
-	}
-}
-
-templ Log(p LogPage) {
-	@Layout(p.Meta) {
-		<h1>Commits</h1>
-		<ul class="log">
-			for _, c := range p.Commits {
-				<li>
-					<a class="commit-id" href={ templ.SafeURL("/r/" + p.Meta.Repo + "/commit/" + c.Hash) }>{ c.Short }</a>
-					<span class="commit-title">{ c.Subject }</span>
-					<span class="muted">— { c.Author }, { FmtTime(c.When) }</span>
-				</li>
-			}
-		</ul>
-	}
-}
-
-templ Commit(p CommitPage) {
-	@Layout(p.Meta) {
-		<h1 class="commit-subject">{ p.Detail.Commit.Subject }</h1>
-		<p class="muted">
-			<code>{ p.Detail.Commit.Hash }</code><br/>
-			{ p.Detail.Commit.Author } &lt;{ p.Detail.Commit.Email }&gt; · { FmtTime(p.Detail.Commit.When) }
-		</p>
-		for _, parent := range p.Detail.Parents {
-			<p class="muted">parent <a href={ templ.SafeURL("/r/" + p.Meta.Repo + "/commit/" + parent) }><code>{ ShortHash(parent) }</code></a></p>
-		}
-		if p.Detail.Commit.Message != p.Detail.Commit.Subject {
-			<pre class="commit-body">{ p.Detail.Commit.Message }</pre>
-		}
-		if len(p.Files) == 0 {
-			<p class="empty">No changes.</p>
-		}
-		<p class="muted diff-summary">{ strconv.Itoa(len(p.Files)) } files changed</p>
-		for _, f := range p.Files {
-			<div class="filediff">
-				<div class="filediff-head">
-					<span class="filediff-name">{ f.Name }</span>
-					<span class="diffstat">
-						<span class="add">+{ strconv.Itoa(f.Added) }</span>
-						<span class="del">−{ strconv.Itoa(f.Deleted) }</span>
-					</span>
-				</div>
-				if f.Binary {
-					<p class="empty">Binary file.</p>
-				} else if f.HTML != "" {
-					<div class="code diff">
-						@templ.Raw(f.HTML)
-					</div>
-				} else {
-					<p class="empty">No textual changes.</p>
-				}
-			</div>
-		}
-	}
-}
-
-templ Refs(p RefsPage) {
-	@Layout(p.Meta) {
-		<h1>Branches</h1>
-		<ul class="refs">
-			for _, b := range p.Refs.Branches {
-				<li>
-					<a href={ templ.SafeURL("/r/" + p.Meta.Repo + "/tree/" + b.Name + "/") }>{ b.Name }</a>
-					<span class="muted"><code>{ ShortHash(b.Hash) }</code></span>
-				</li>
-			}
-		</ul>
-		<h1>Tags</h1>
-		if len(p.Refs.Tags) == 0 {
-			<p class="empty">No tags.</p>
-		}
-		<ul class="refs">
-			for _, t := range p.Refs.Tags {
-				<li>
-					<a href={ templ.SafeURL("/r/" + p.Meta.Repo + "/log/" + t.Name) }>{ t.Name }</a>
-					<span class="muted"><code>{ ShortHash(t.Hash) }</code></span>
-				</li>
-			}
-		</ul>
-	}
-}
-
-templ Issues(p IssuesPage) {
-	@Layout(p.Meta) {
-		<h1>Issues <span class="muted">({ strconv.Itoa(p.Total) })</span></h1>
-		if p.Total == 0 {
-			<p class="empty">No issues.</p>
-		}
-		for _, g := range p.Groups {
-			<section class="issue-group">
-				<h2 class={ "status", "status--" + StatusKind(g.Status) }>{ g.Status } <span class="muted">{ strconv.Itoa(len(g.Tasks)) }</span></h2>
-				<ul class="issue-list">
-					for _, t := range g.Tasks {
-						<li>
-							if t.Type() != "" {
-								<span class={ "badge", "badge--" + LabelColor(t.Type()) }>{ t.Type() }</span>
-							}
-							<a class="issue-link" href={ templ.SafeURL("/r/" + p.Meta.Repo + "/issues/" + t.Key()) }>
-								<span class="issue-id">{ t.ID }</span>
-								<span class="issue-title">{ t.Title }</span>
-							</a>
-							if PriorityClass(t.Priority) != "" {
-								<span class={ "prio", "prio--" + PriorityClass(t.Priority) }>{ t.Priority }</span>
-							}
-							for _, l := range t.OtherLabels() {
-								<span class={ "chip", "chip--" + LabelColor(l) }>{ l }</span>
-							}
-						</li>
-					}
-				</ul>
-			</section>
-		}
-	}
-}
-
-templ Issue(p IssuePage) {
-	@Layout(p.Meta) {
-		<p class="muted"><a href={ templ.SafeURL("/r/" + p.Meta.Repo + "/issues") }>← issues</a></p>
-		<h1 class="issue-detail-title">
-			<span class="issue-id">{ p.Task.ID }</span>
-			{ p.Task.Title }
-		</h1>
-		<div class="issue-meta">
-			<span class={ "status", "status--" + StatusKind(p.Task.Status) }>{ p.Task.Status }</span>
-			if p.Task.Type() != "" {
-				<span class={ "badge", "badge--" + LabelColor(p.Task.Type()) }>{ p.Task.Type() }</span>
-			}
-			if PriorityClass(p.Task.Priority) != "" {
-				<span class={ "prio", "prio--" + PriorityClass(p.Task.Priority) }>priority: { p.Task.Priority }</span>
-			} else if p.Task.Priority != "" {
-				<span class="muted">priority: { p.Task.Priority }</span>
-			}
-			for _, l := range p.Task.OtherLabels() {
-				<span class={ "chip", "chip--" + LabelColor(l) }>{ l }</span>
-			}
-		</div>
-		if p.Task.Created != "" || p.Task.Updated != "" {
-			<p class="muted issue-dates">
-				if p.Task.Created != "" {
-					<span>created { p.Task.Created }</span>
-				}
-				if p.Task.Updated != "" {
-					<span>· updated { p.Task.Updated }</span>
-				}
-			</p>
-		}
-		if len(p.Task.Deps) > 0 {
-			<p class="muted">depends on: { strings.Join(p.Task.Deps, ", ") }</p>
-		}
-		if p.Body != "" {
-			<article class="markdown issue-body">
-				@templ.Raw(p.Body)
-			</article>
-		}
-	}
-}
-
-templ Error(m Meta, code int, msg string) {
-	@Layout(m) {
-		<h1>{ strconv.Itoa(code) }</h1>
-		<p>{ msg }</p>
-		<p><a href="/">← back to repositories</a></p>
-	}
-}
-
-templ crumbs(m Meta, cs []Crumb) {
-	<nav class="crumbs">
-		<a href={ templ.SafeURL("/r/" + m.Repo + "/tree/" + m.Ref + "/") }>{ m.Repo }</a>
-		for _, c := range cs {
-			<span class="sep">/</span>
-			<a href={ templ.SafeURL("/r/" + m.Repo + "/tree/" + m.Ref + "/" + c.Path) }>{ c.Name }</a>
-		}
-	</nav>
-}
-
-// pageTitle builds the <title> from page meta.
-func pageTitle(m Meta) string {
-	if m.Title != "" {
-		return m.Title
-	}
-	if m.Repo != "" {
-		return m.Repo + " · humdrum codex"
-	}
-	return "humdrum codex"
-}
-
-func refOr(ref string) string {
-	if ref == "" {
-		return "HEAD"
-	}
-	return ref
-}
-
-func themeOr(t string) string {
-	if t == "" {
-		return DefaultTheme
-	}
-	return t
-}
-
-// themeLabel renders a human-friendly name for the theme picker.
-func themeLabel(t string) string {
-	switch t {
-	case "flexoki":
-		return "Flexoki"
-	case "flexoki-dark":
-		return "Flexoki Dark"
-	case "uchu":
-		return "Uchu"
-	case "uchu-dark":
-		return "Uchu Dark"
-	case "humdrum":
-		return "Humdrum"
-	case "humdrum-dark":
-		return "Humdrum Dark"
-	case "eink":
-		return "E-ink"
-	}
-	return t
-}
-
-// parentTreeURL returns the tree URL one directory up from path.
-func parentTreeURL(m Meta, path string) string {
-	parent := ""
-	if i := lastSlash(path); i >= 0 {
-		parent = path[:i]
-	}
-	return fmt.Sprintf("/r/%s/tree/%s/%s", m.Repo, m.Ref, parent)
-}
-
-func lastSlash(s string) int {
-	for i := len(s) - 1; i >= 0; i-- {
-		if s[i] == '/' {
-			return i
-		}
-	}
-	return -1
-}
web/templates/templates_templ.go +0 −2709
@@ -1,2709 +0,0 @@
-// Code generated by templ - DO NOT EDIT.
-
-// templ: version: v0.3.1020
-package templates
-
-//lint:file-ignore SA4006 This context is only used if a nested component is present.
-
-import "github.com/a-h/templ"
-import templruntime "github.com/a-h/templ/runtime"
-
-import (
-	"fmt"
-	"strconv"
-	"strings"
-)
-
-// Layout is the shared page shell. Styling is intentionally minimal in phase 1;
-// the design pass (tokens + Charm chrome) lands in phase 3.
-func Layout(m Meta) templ.Component {
-	return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
-		templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
-		if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
-			return templ_7745c5c3_CtxErr
-		}
-		templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
-		if !templ_7745c5c3_IsBuffer {
-			defer func() {
-				templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
-				if templ_7745c5c3_Err == nil {
-					templ_7745c5c3_Err = templ_7745c5c3_BufErr
-				}
-			}()
-		}
-		ctx = templ.InitializeContext(ctx)
-		templ_7745c5c3_Var1 := templ.GetChildren(ctx)
-		if templ_7745c5c3_Var1 == nil {
-			templ_7745c5c3_Var1 = templ.NopComponent
-		}
-		ctx = templ.ClearChildren(ctx)
-		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<!doctype html><html lang=\"en\" data-theme=\"")
-		if templ_7745c5c3_Err != nil {
-			return templ_7745c5c3_Err
-		}
-		var templ_7745c5c3_Var2 string
-		templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.ResolveAttributeValue(themeOr(m.Theme))
-		if templ_7745c5c3_Err != nil {
-			return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 13, Col: 46}
-		}
-		_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var2)
-		if templ_7745c5c3_Err != nil {
-			return templ_7745c5c3_Err
-		}
-		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1, viewport-fit=cover\"><title>")
-		if templ_7745c5c3_Err != nil {
-			return templ_7745c5c3_Err
-		}
-		var templ_7745c5c3_Var3 string
-		templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(pageTitle(m))
-		if templ_7745c5c3_Err != nil {
-			return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 17, Col: 24}
-		}
-		_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
-		if templ_7745c5c3_Err != nil {
-			return templ_7745c5c3_Err
-		}
-		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</title>")
-		if templ_7745c5c3_Err != nil {
-			return templ_7745c5c3_Err
-		}
-		templ_7745c5c3_Err = templ.Raw(themeResolveScript).Render(ctx, templ_7745c5c3_Buffer)
-		if templ_7745c5c3_Err != nil {
-			return templ_7745c5c3_Err
-		}
-		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<link rel=\"stylesheet\" href=\"/static/tokens.css\"><link rel=\"stylesheet\" href=\"/static/custard.css\"><script src=\"/static/theme.js\" defer></script></head><body><header class=\"site-head\"><div class=\"site-head-left\"><a href=\"/\" class=\"brand\">▍ humdrum codex</a> ")
-		if templ_7745c5c3_Err != nil {
-			return templ_7745c5c3_Err
-		}
-		if m.Repo != "" {
-			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<span class=\"sep\">/</span> <a href=\"")
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			var templ_7745c5c3_Var4 templ.SafeURL
-			templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL("/r/" + m.Repo))
-			if templ_7745c5c3_Err != nil {
-				return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 29, Col: 45}
-			}
-			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "\" class=\"repo-name\">")
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			var templ_7745c5c3_Var5 string
-			templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(m.Repo)
-			if templ_7745c5c3_Err != nil {
-				return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 29, Col: 74}
-			}
-			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "</a>")
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-		}
-		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "</div>")
-		if templ_7745c5c3_Err != nil {
-			return templ_7745c5c3_Err
-		}
-		templ_7745c5c3_Err = themePicker(m).Render(ctx, templ_7745c5c3_Buffer)
-		if templ_7745c5c3_Err != nil {
-			return templ_7745c5c3_Err
-		}
-		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "</header>")
-		if templ_7745c5c3_Err != nil {
-			return templ_7745c5c3_Err
-		}
-		if m.Repo != "" {
-			templ_7745c5c3_Err = repoTabs(m).Render(ctx, templ_7745c5c3_Buffer)
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-		}
-		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<main class=\"content\">")
-		if templ_7745c5c3_Err != nil {
-			return templ_7745c5c3_Err
-		}
-		templ_7745c5c3_Err = templ_7745c5c3_Var1.Render(ctx, templ_7745c5c3_Buffer)
-		if templ_7745c5c3_Err != nil {
-			return templ_7745c5c3_Err
-		}
-		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "</main><footer class=\"site-foot\"><span class=\"foot-brand\">▍ served by custard</span> ")
-		if templ_7745c5c3_Err != nil {
-			return templ_7745c5c3_Err
-		}
-		if m.Repo != "" {
-			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "<span class=\"foot-item\">")
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			var templ_7745c5c3_Var6 string
-			templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(m.Repo)
-			if templ_7745c5c3_Err != nil {
-				return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 43, Col: 37}
-			}
-			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "</span> ")
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			if m.Ref != "" {
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "<span class=\"foot-item ref-pill\">")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				var templ_7745c5c3_Var7 string
-				templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(m.Ref)
-				if templ_7745c5c3_Err != nil {
-					return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 45, Col: 46}
-				}
-				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "</span>")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-			}
-			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, " ")
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			if m.CloneURL != "" {
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "<span class=\"foot-item foot-clone\">clone <code>git clone ")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				var templ_7745c5c3_Var8 string
-				templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(m.CloneURL)
-				if templ_7745c5c3_Err != nil {
-					return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 48, Col: 75}
-				}
-				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "</code></span>")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-			}
-		}
-		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "</footer></body></html>")
-		if templ_7745c5c3_Err != nil {
-			return templ_7745c5c3_Err
-		}
-		return nil
-	})
-}
-
-func themePicker(m Meta) templ.Component {
-	return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
-		templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
-		if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
-			return templ_7745c5c3_CtxErr
-		}
-		templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
-		if !templ_7745c5c3_IsBuffer {
-			defer func() {
-				templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
-				if templ_7745c5c3_Err == nil {
-					templ_7745c5c3_Err = templ_7745c5c3_BufErr
-				}
-			}()
-		}
-		ctx = templ.InitializeContext(ctx)
-		templ_7745c5c3_Var9 := templ.GetChildren(ctx)
-		if templ_7745c5c3_Var9 == nil {
-			templ_7745c5c3_Var9 = templ.NopComponent
-		}
-		ctx = templ.ClearChildren(ctx)
-		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "<select id=\"theme-select\" aria-label=\"Theme\" class=\"theme-select\">")
-		if templ_7745c5c3_Err != nil {
-			return templ_7745c5c3_Err
-		}
-		for _, t := range Families {
-			if t == themeOr(m.Theme) {
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "<option value=\"")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				var templ_7745c5c3_Var10 string
-				templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.ResolveAttributeValue(t)
-				if templ_7745c5c3_Err != nil {
-					return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 60, Col: 21}
-				}
-				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var10)
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "\" selected>")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				var templ_7745c5c3_Var11 string
-				templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(themeLabel(t))
-				if templ_7745c5c3_Err != nil {
-					return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 60, Col: 48}
-				}
-				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "</option>")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-			} else {
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "<option value=\"")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				var templ_7745c5c3_Var12 string
-				templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.ResolveAttributeValue(t)
-				if templ_7745c5c3_Err != nil {
-					return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 62, Col: 21}
-				}
-				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var12)
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "\">")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				var templ_7745c5c3_Var13 string
-				templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(themeLabel(t))
-				if templ_7745c5c3_Err != nil {
-					return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 62, Col: 39}
-				}
-				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "</option>")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-			}
-		}
-		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "</select>")
-		if templ_7745c5c3_Err != nil {
-			return templ_7745c5c3_Err
-		}
-		return nil
-	})
-}
-
-// themeResolveScript runs synchronously in <head> before paint: it reads the
-// family cookie and the OS color scheme, then sets the concrete data-theme
-// (family or family-dark; eink is fixed). This is the flash-free path for the
-// system-driven light/dark axis the server can't see.
-const themeResolveScript = `<script>(function(){try{` +
-	`var m=document.cookie.match(/(?:^|; )theme=([^;]+)/);` +
-	`var f=m?decodeURIComponent(m[1]):'flexoki';` +
-	`if(['flexoki','uchu','humdrum','eink'].indexOf(f)<0)f='flexoki';` +
-	`var d=window.matchMedia&&window.matchMedia('(prefers-color-scheme: dark)').matches;` +
-	`document.documentElement.setAttribute('data-theme',f==='eink'?'eink':(d?f+'-dark':f));` +
-	`}catch(e){}})();</script>`
-
-func repoTabs(m Meta) templ.Component {
-	return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
-		templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
-		if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
-			return templ_7745c5c3_CtxErr
-		}
-		templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
-		if !templ_7745c5c3_IsBuffer {
-			defer func() {
-				templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
-				if templ_7745c5c3_Err == nil {
-					templ_7745c5c3_Err = templ_7745c5c3_BufErr
-				}
-			}()
-		}
-		ctx = templ.InitializeContext(ctx)
-		templ_7745c5c3_Var14 := templ.GetChildren(ctx)
-		if templ_7745c5c3_Var14 == nil {
-			templ_7745c5c3_Var14 = templ.NopComponent
-		}
-		ctx = templ.ClearChildren(ctx)
-		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "<nav class=\"repo-tabs\">")
-		if templ_7745c5c3_Err != nil {
-			return templ_7745c5c3_Err
-		}
-		if m.HasReadme {
-			var templ_7745c5c3_Var15 = []any{templ.KV("active", m.Tab == "readme")}
-			templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var15...)
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "<a href=\"")
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			var templ_7745c5c3_Var16 templ.SafeURL
-			templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL("/r/" + m.Repo + "/readme"))
-			if templ_7745c5c3_Err != nil {
-				return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 83, Col: 54}
-			}
-			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16))
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "\" class=\"")
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			var templ_7745c5c3_Var17 string
-			templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var15).String())
-			if templ_7745c5c3_Err != nil {
-				return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 1, Col: 0}
-			}
-			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var17)
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "\">readme</a> ")
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-		}
-		var templ_7745c5c3_Var18 = []any{templ.KV("active", m.Tab == "code")}
-		templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var18...)
-		if templ_7745c5c3_Err != nil {
-			return templ_7745c5c3_Err
-		}
-		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "<a href=\"")
-		if templ_7745c5c3_Err != nil {
-			return templ_7745c5c3_Err
-		}
-		var templ_7745c5c3_Var19 templ.SafeURL
-		templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL("/r/" + m.Repo + "/files"))
-		if templ_7745c5c3_Err != nil {
-			return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 85, Col: 52}
-		}
-		_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19))
-		if templ_7745c5c3_Err != nil {
-			return templ_7745c5c3_Err
-		}
-		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "\" class=\"")
-		if templ_7745c5c3_Err != nil {
-			return templ_7745c5c3_Err
-		}
-		var templ_7745c5c3_Var20 string
-		templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var18).String())
-		if templ_7745c5c3_Err != nil {
-			return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 1, Col: 0}
-		}
-		_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var20)
-		if templ_7745c5c3_Err != nil {
-			return templ_7745c5c3_Err
-		}
-		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "\">code</a> ")
-		if templ_7745c5c3_Err != nil {
-			return templ_7745c5c3_Err
-		}
-		var templ_7745c5c3_Var21 = []any{templ.KV("active", m.Tab == "log")}
-		templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var21...)
-		if templ_7745c5c3_Err != nil {
-			return templ_7745c5c3_Err
-		}
-		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "<a href=\"")
-		if templ_7745c5c3_Err != nil {
-			return templ_7745c5c3_Err
-		}
-		var templ_7745c5c3_Var22 templ.SafeURL
-		templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL("/r/" + m.Repo + "/log/" + refOr(m.Ref)))
-		if templ_7745c5c3_Err != nil {
-			return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 86, Col: 66}
-		}
-		_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22))
-		if templ_7745c5c3_Err != nil {
-			return templ_7745c5c3_Err
-		}
-		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "\" class=\"")
-		if templ_7745c5c3_Err != nil {
-			return templ_7745c5c3_Err
-		}
-		var templ_7745c5c3_Var23 string
-		templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var21).String())
-		if templ_7745c5c3_Err != nil {
-			return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 1, Col: 0}
-		}
-		_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var23)
-		if templ_7745c5c3_Err != nil {
-			return templ_7745c5c3_Err
-		}
-		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "\">log</a> ")
-		if templ_7745c5c3_Err != nil {
-			return templ_7745c5c3_Err
-		}
-		var templ_7745c5c3_Var24 = []any{templ.KV("active", m.Tab == "refs")}
-		templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var24...)
-		if templ_7745c5c3_Err != nil {
-			return templ_7745c5c3_Err
-		}
-		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "<a href=\"")
-		if templ_7745c5c3_Err != nil {
-			return templ_7745c5c3_Err
-		}
-		var templ_7745c5c3_Var25 templ.SafeURL
-		templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL("/r/" + m.Repo + "/refs"))
-		if templ_7745c5c3_Err != nil {
-			return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 87, Col: 51}
-		}
-		_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25))
-		if templ_7745c5c3_Err != nil {
-			return templ_7745c5c3_Err
-		}
-		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "\" class=\"")
-		if templ_7745c5c3_Err != nil {
-			return templ_7745c5c3_Err
-		}
-		var templ_7745c5c3_Var26 string
-		templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var24).String())
-		if templ_7745c5c3_Err != nil {
-			return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 1, Col: 0}
-		}
-		_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var26)
-		if templ_7745c5c3_Err != nil {
-			return templ_7745c5c3_Err
-		}
-		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "\">refs</a> ")
-		if templ_7745c5c3_Err != nil {
-			return templ_7745c5c3_Err
-		}
-		if m.HasIssues {
-			var templ_7745c5c3_Var27 = []any{templ.KV("active", m.Tab == "issues")}
-			templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var27...)
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "<a href=\"")
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			var templ_7745c5c3_Var28 templ.SafeURL
-			templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL("/r/" + m.Repo + "/issues"))
-			if templ_7745c5c3_Err != nil {
-				return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 89, Col: 54}
-			}
-			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28))
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "\" class=\"")
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			var templ_7745c5c3_Var29 string
-			templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var27).String())
-			if templ_7745c5c3_Err != nil {
-				return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 1, Col: 0}
-			}
-			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var29)
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "\">issues</a>")
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-		}
-		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "</nav>")
-		if templ_7745c5c3_Err != nil {
-			return templ_7745c5c3_Err
-		}
-		return nil
-	})
-}
-
-func Index(p IndexPage) templ.Component {
-	return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
-		templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
-		if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
-			return templ_7745c5c3_CtxErr
-		}
-		templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
-		if !templ_7745c5c3_IsBuffer {
-			defer func() {
-				templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
-				if templ_7745c5c3_Err == nil {
-					templ_7745c5c3_Err = templ_7745c5c3_BufErr
-				}
-			}()
-		}
-		ctx = templ.InitializeContext(ctx)
-		templ_7745c5c3_Var30 := templ.GetChildren(ctx)
-		if templ_7745c5c3_Var30 == nil {
-			templ_7745c5c3_Var30 = templ.NopComponent
-		}
-		ctx = templ.ClearChildren(ctx)
-		templ_7745c5c3_Var31 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
-			templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
-			templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
-			if !templ_7745c5c3_IsBuffer {
-				defer func() {
-					templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
-					if templ_7745c5c3_Err == nil {
-						templ_7745c5c3_Err = templ_7745c5c3_BufErr
-					}
-				}()
-			}
-			ctx = templ.InitializeContext(ctx)
-			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, "<h1>Repositories</h1>")
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			if len(p.Repos) == 0 {
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, "<p class=\"empty\">No repositories found.</p>")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-			}
-			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, " <ul class=\"repo-list\">")
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			for _, r := range p.Repos {
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "<li><a class=\"repo-link\" href=\"")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				var templ_7745c5c3_Var32 templ.SafeURL
-				templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL("/r/" + r.Name))
-				if templ_7745c5c3_Err != nil {
-					return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 103, Col: 62}
-				}
-				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var32))
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, "\">")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				var templ_7745c5c3_Var33 string
-				templ_7745c5c3_Var33, templ_7745c5c3_Err = templ.JoinStringErrs(r.Name)
-				if templ_7745c5c3_Err != nil {
-					return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 103, Col: 73}
-				}
-				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var33))
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, "</a> ")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				if r.Description != "" {
-					templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "<span class=\"repo-desc\">")
-					if templ_7745c5c3_Err != nil {
-						return templ_7745c5c3_Err
-					}
-					var templ_7745c5c3_Var34 string
-					templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.JoinStringErrs(r.Description)
-					if templ_7745c5c3_Err != nil {
-						return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 105, Col: 45}
-					}
-					_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var34))
-					if templ_7745c5c3_Err != nil {
-						return templ_7745c5c3_Err
-					}
-					templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "</span> ")
-					if templ_7745c5c3_Err != nil {
-						return templ_7745c5c3_Err
-					}
-				}
-				if r.Last != nil {
-					templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, "<span class=\"repo-meta\">")
-					if templ_7745c5c3_Err != nil {
-						return templ_7745c5c3_Err
-					}
-					var templ_7745c5c3_Var35 string
-					templ_7745c5c3_Var35, templ_7745c5c3_Err = templ.JoinStringErrs(r.Last.Subject)
-					if templ_7745c5c3_Err != nil {
-						return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 108, Col: 46}
-					}
-					_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var35))
-					if templ_7745c5c3_Err != nil {
-						return templ_7745c5c3_Err
-					}
-					templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, " · ")
-					if templ_7745c5c3_Err != nil {
-						return templ_7745c5c3_Err
-					}
-					var templ_7745c5c3_Var36 string
-					templ_7745c5c3_Var36, templ_7745c5c3_Err = templ.JoinStringErrs(FmtTime(r.Last.When))
-					if templ_7745c5c3_Err != nil {
-						return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 108, Col: 74}
-					}
-					_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var36))
-					if templ_7745c5c3_Err != nil {
-						return templ_7745c5c3_Err
-					}
-					templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, "</span>")
-					if templ_7745c5c3_Err != nil {
-						return templ_7745c5c3_Err
-					}
-				}
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, "</li>")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-			}
-			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 57, "</ul>")
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			return nil
-		})
-		templ_7745c5c3_Err = Layout(p.Meta).Render(templ.WithChildren(ctx, templ_7745c5c3_Var31), templ_7745c5c3_Buffer)
-		if templ_7745c5c3_Err != nil {
-			return templ_7745c5c3_Err
-		}
-		return nil
-	})
-}
-
-func Repo(p RepoPage) templ.Component {
-	return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
-		templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
-		if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
-			return templ_7745c5c3_CtxErr
-		}
-		templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
-		if !templ_7745c5c3_IsBuffer {
-			defer func() {
-				templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
-				if templ_7745c5c3_Err == nil {
-					templ_7745c5c3_Err = templ_7745c5c3_BufErr
-				}
-			}()
-		}
-		ctx = templ.InitializeContext(ctx)
-		templ_7745c5c3_Var37 := templ.GetChildren(ctx)
-		if templ_7745c5c3_Var37 == nil {
-			templ_7745c5c3_Var37 = templ.NopComponent
-		}
-		ctx = templ.ClearChildren(ctx)
-		templ_7745c5c3_Var38 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
-			templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
-			templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
-			if !templ_7745c5c3_IsBuffer {
-				defer func() {
-					templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
-					if templ_7745c5c3_Err == nil {
-						templ_7745c5c3_Err = templ_7745c5c3_BufErr
-					}
-				}()
-			}
-			ctx = templ.InitializeContext(ctx)
-			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, "<div class=\"repo-summary\"><span class=\"ref-pill\">")
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			var templ_7745c5c3_Var39 string
-			templ_7745c5c3_Var39, templ_7745c5c3_Err = templ.JoinStringErrs(p.DefaultBranch)
-			if templ_7745c5c3_Err != nil {
-				return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 119, Col: 43}
-			}
-			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var39))
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, "</span> <span>")
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			var templ_7745c5c3_Var40 string
-			templ_7745c5c3_Var40, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(p.Branches))
-			if templ_7745c5c3_Err != nil {
-				return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 120, Col: 35}
-			}
-			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var40))
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 60, " branches</span> <span>")
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			var templ_7745c5c3_Var41 string
-			templ_7745c5c3_Var41, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(p.Tags))
-			if templ_7745c5c3_Err != nil {
-				return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 121, Col: 31}
-			}
-			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var41))
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 61, " tags</span></div>")
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			if p.Last != nil {
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 62, "<p class=\"last-commit\"><a href=\"")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				var templ_7745c5c3_Var42 templ.SafeURL
-				templ_7745c5c3_Var42, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL("/r/" + p.Meta.Repo + "/commit/" + p.Last.Hash))
-				if templ_7745c5c3_Err != nil {
-					return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 125, Col: 75}
-				}
-				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var42))
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 63, "\" class=\"commit-id\">")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				var templ_7745c5c3_Var43 string
-				templ_7745c5c3_Var43, templ_7745c5c3_Err = templ.JoinStringErrs(p.Last.Short)
-				if templ_7745c5c3_Err != nil {
-					return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 125, Col: 110}
-				}
-				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var43))
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 64, "</a> <span class=\"commit-title\">")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				var templ_7745c5c3_Var44 string
-				templ_7745c5c3_Var44, templ_7745c5c3_Err = templ.JoinStringErrs(p.Last.Subject)
-				if templ_7745c5c3_Err != nil {
-					return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 126, Col: 47}
-				}
-				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var44))
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 65, "</span> <span class=\"muted\">— ")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				var templ_7745c5c3_Var45 string
-				templ_7745c5c3_Var45, templ_7745c5c3_Err = templ.JoinStringErrs(p.Last.Author)
-				if templ_7745c5c3_Err != nil {
-					return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 127, Col: 43}
-				}
-				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var45))
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 66, ", ")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				var templ_7745c5c3_Var46 string
-				templ_7745c5c3_Var46, templ_7745c5c3_Err = templ.JoinStringErrs(FmtTime(p.Last.When))
-				if templ_7745c5c3_Err != nil {
-					return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 127, Col: 69}
-				}
-				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var46))
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 67, "</span></p>")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-			}
-			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 68, " ")
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			if len(p.Entries) > 0 {
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 69, "<table class=\"tree\"><tbody>")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				for _, e := range p.Entries {
-					templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 70, "<tr><td>")
-					if templ_7745c5c3_Err != nil {
-						return templ_7745c5c3_Err
-					}
-					if e.IsDir {
-						templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 71, "<a class=\"tree-dir\" href=\"")
-						if templ_7745c5c3_Err != nil {
-							return templ_7745c5c3_Err
-						}
-						var templ_7745c5c3_Var47 templ.SafeURL
-						templ_7745c5c3_Var47, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL("/r/" + p.Meta.Repo + "/tree/" + p.DefaultBranch + "/" + e.Path))
-						if templ_7745c5c3_Err != nil {
-							return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 137, Col: 114}
-						}
-						_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var47))
-						if templ_7745c5c3_Err != nil {
-							return templ_7745c5c3_Err
-						}
-						templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 72, "\">")
-						if templ_7745c5c3_Err != nil {
-							return templ_7745c5c3_Err
-						}
-						var templ_7745c5c3_Var48 string
-						templ_7745c5c3_Var48, templ_7745c5c3_Err = templ.JoinStringErrs(e.Name)
-						if templ_7745c5c3_Err != nil {
-							return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 137, Col: 125}
-						}
-						_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var48))
-						if templ_7745c5c3_Err != nil {
-							return templ_7745c5c3_Err
-						}
-						templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 73, "/</a>")
-						if templ_7745c5c3_Err != nil {
-							return templ_7745c5c3_Err
-						}
-					} else {
-						templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 74, "<a class=\"tree-file\" href=\"")
-						if templ_7745c5c3_Err != nil {
-							return templ_7745c5c3_Err
-						}
-						var templ_7745c5c3_Var49 templ.SafeURL
-						templ_7745c5c3_Var49, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL("/r/" + p.Meta.Repo + "/blob/" + p.DefaultBranch + "/" + e.Path))
-						if templ_7745c5c3_Err != nil {
-							return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 139, Col: 115}
-						}
-						_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var49))
-						if templ_7745c5c3_Err != nil {
-							return templ_7745c5c3_Err
-						}
-						templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 75, "\">")
-						if templ_7745c5c3_Err != nil {
-							return templ_7745c5c3_Err
-						}
-						var templ_7745c5c3_Var50 string
-						templ_7745c5c3_Var50, templ_7745c5c3_Err = templ.JoinStringErrs(e.Name)
-						if templ_7745c5c3_Err != nil {
-							return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 139, Col: 126}
-						}
-						_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var50))
-						if templ_7745c5c3_Err != nil {
-							return templ_7745c5c3_Err
-						}
-						templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 76, "</a>")
-						if templ_7745c5c3_Err != nil {
-							return templ_7745c5c3_Err
-						}
-					}
-					templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 77, "</td><td class=\"num\">")
-					if templ_7745c5c3_Err != nil {
-						return templ_7745c5c3_Err
-					}
-					if !e.IsDir {
-						var templ_7745c5c3_Var51 string
-						templ_7745c5c3_Var51, templ_7745c5c3_Err = templ.JoinStringErrs(HumanSize(e.Size))
-						if templ_7745c5c3_Err != nil {
-							return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 144, Col: 28}
-						}
-						_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var51))
-						if templ_7745c5c3_Err != nil {
-							return templ_7745c5c3_Err
-						}
-					}
-					templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 78, "</td></tr>")
-					if templ_7745c5c3_Err != nil {
-						return templ_7745c5c3_Err
-					}
-				}
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 79, "</tbody></table>")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-			}
-			return nil
-		})
-		templ_7745c5c3_Err = Layout(p.Meta).Render(templ.WithChildren(ctx, templ_7745c5c3_Var38), templ_7745c5c3_Buffer)
-		if templ_7745c5c3_Err != nil {
-			return templ_7745c5c3_Err
-		}
-		return nil
-	})
-}
-
-func Readme(p ReadmePage) templ.Component {
-	return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
-		templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
-		if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
-			return templ_7745c5c3_CtxErr
-		}
-		templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
-		if !templ_7745c5c3_IsBuffer {
-			defer func() {
-				templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
-				if templ_7745c5c3_Err == nil {
-					templ_7745c5c3_Err = templ_7745c5c3_BufErr
-				}
-			}()
-		}
-		ctx = templ.InitializeContext(ctx)
-		templ_7745c5c3_Var52 := templ.GetChildren(ctx)
-		if templ_7745c5c3_Var52 == nil {
-			templ_7745c5c3_Var52 = templ.NopComponent
-		}
-		ctx = templ.ClearChildren(ctx)
-		templ_7745c5c3_Var53 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
-			templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
-			templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
-			if !templ_7745c5c3_IsBuffer {
-				defer func() {
-					templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
-					if templ_7745c5c3_Err == nil {
-						templ_7745c5c3_Err = templ_7745c5c3_BufErr
-					}
-				}()
-			}
-			ctx = templ.InitializeContext(ctx)
-			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 80, "<article class=\"markdown readme\">")
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			templ_7745c5c3_Err = templ.Raw(p.Readme).Render(ctx, templ_7745c5c3_Buffer)
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 81, "</article>")
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			return nil
-		})
-		templ_7745c5c3_Err = Layout(p.Meta).Render(templ.WithChildren(ctx, templ_7745c5c3_Var53), templ_7745c5c3_Buffer)
-		if templ_7745c5c3_Err != nil {
-			return templ_7745c5c3_Err
-		}
-		return nil
-	})
-}
-
-func Tree(p TreePage) templ.Component {
-	return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
-		templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
-		if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
-			return templ_7745c5c3_CtxErr
-		}
-		templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
-		if !templ_7745c5c3_IsBuffer {
-			defer func() {
-				templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
-				if templ_7745c5c3_Err == nil {
-					templ_7745c5c3_Err = templ_7745c5c3_BufErr
-				}
-			}()
-		}
-		ctx = templ.InitializeContext(ctx)
-		templ_7745c5c3_Var54 := templ.GetChildren(ctx)
-		if templ_7745c5c3_Var54 == nil {
-			templ_7745c5c3_Var54 = templ.NopComponent
-		}
-		ctx = templ.ClearChildren(ctx)
-		templ_7745c5c3_Var55 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
-			templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
-			templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
-			if !templ_7745c5c3_IsBuffer {
-				defer func() {
-					templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
-					if templ_7745c5c3_Err == nil {
-						templ_7745c5c3_Err = templ_7745c5c3_BufErr
-					}
-				}()
-			}
-			ctx = templ.InitializeContext(ctx)
-			templ_7745c5c3_Err = crumbs(p.Meta, p.Crumbs).Render(ctx, templ_7745c5c3_Buffer)
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 82, " <table class=\"tree\"><tbody>")
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			if p.Path != "" {
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 83, "<tr><td colspan=\"2\"><a href=\"")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				var templ_7745c5c3_Var56 templ.SafeURL
-				templ_7745c5c3_Var56, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(parentTreeURL(p.Meta, p.Path)))
-				if templ_7745c5c3_Err != nil {
-					return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 169, Col: 79}
-				}
-				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var56))
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 84, "\">..</a></td></tr>")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-			}
-			for _, e := range p.Entries {
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 85, "<tr><td>")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				if e.IsDir {
-					templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 86, "<a class=\"tree-dir\" href=\"")
-					if templ_7745c5c3_Err != nil {
-						return templ_7745c5c3_Err
-					}
-					var templ_7745c5c3_Var57 templ.SafeURL
-					templ_7745c5c3_Var57, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL("/r/" + p.Meta.Repo + "/tree/" + p.Meta.Ref + "/" + e.Path))
-					if templ_7745c5c3_Err != nil {
-						return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 175, Col: 108}
-					}
-					_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var57))
-					if templ_7745c5c3_Err != nil {
-						return templ_7745c5c3_Err
-					}
-					templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 87, "\">")
-					if templ_7745c5c3_Err != nil {
-						return templ_7745c5c3_Err
-					}
-					var templ_7745c5c3_Var58 string
-					templ_7745c5c3_Var58, templ_7745c5c3_Err = templ.JoinStringErrs(e.Name)
-					if templ_7745c5c3_Err != nil {
-						return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 175, Col: 119}
-					}
-					_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var58))
-					if templ_7745c5c3_Err != nil {
-						return templ_7745c5c3_Err
-					}
-					templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 88, "/</a>")
-					if templ_7745c5c3_Err != nil {
-						return templ_7745c5c3_Err
-					}
-				} else {
-					templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 89, "<a class=\"tree-file\" href=\"")
-					if templ_7745c5c3_Err != nil {
-						return templ_7745c5c3_Err
-					}
-					var templ_7745c5c3_Var59 templ.SafeURL
-					templ_7745c5c3_Var59, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL("/r/" + p.Meta.Repo + "/blob/" + p.Meta.Ref + "/" + e.Path))
-					if templ_7745c5c3_Err != nil {
-						return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 177, Col: 109}
-					}
-					_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var59))
-					if templ_7745c5c3_Err != nil {
-						return templ_7745c5c3_Err
-					}
-					templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 90, "\">")
-					if templ_7745c5c3_Err != nil {
-						return templ_7745c5c3_Err
-					}
-					var templ_7745c5c3_Var60 string
-					templ_7745c5c3_Var60, templ_7745c5c3_Err = templ.JoinStringErrs(e.Name)
-					if templ_7745c5c3_Err != nil {
-						return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 177, Col: 120}
-					}
-					_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var60))
-					if templ_7745c5c3_Err != nil {
-						return templ_7745c5c3_Err
-					}
-					templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 91, "</a>")
-					if templ_7745c5c3_Err != nil {
-						return templ_7745c5c3_Err
-					}
-				}
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 92, "</td><td class=\"num\">")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				if !e.IsDir {
-					var templ_7745c5c3_Var61 string
-					templ_7745c5c3_Var61, templ_7745c5c3_Err = templ.JoinStringErrs(HumanSize(e.Size))
-					if templ_7745c5c3_Err != nil {
-						return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 182, Col: 27}
-					}
-					_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var61))
-					if templ_7745c5c3_Err != nil {
-						return templ_7745c5c3_Err
-					}
-				}
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 93, "</td></tr>")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-			}
-			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 94, "</tbody></table>")
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			return nil
-		})
-		templ_7745c5c3_Err = Layout(p.Meta).Render(templ.WithChildren(ctx, templ_7745c5c3_Var55), templ_7745c5c3_Buffer)
-		if templ_7745c5c3_Err != nil {
-			return templ_7745c5c3_Err
-		}
-		return nil
-	})
-}
-
-func Blob(p BlobPage) templ.Component {
-	return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
-		templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
-		if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
-			return templ_7745c5c3_CtxErr
-		}
-		templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
-		if !templ_7745c5c3_IsBuffer {
-			defer func() {
-				templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
-				if templ_7745c5c3_Err == nil {
-					templ_7745c5c3_Err = templ_7745c5c3_BufErr
-				}
-			}()
-		}
-		ctx = templ.InitializeContext(ctx)
-		templ_7745c5c3_Var62 := templ.GetChildren(ctx)
-		if templ_7745c5c3_Var62 == nil {
-			templ_7745c5c3_Var62 = templ.NopComponent
-		}
-		ctx = templ.ClearChildren(ctx)
-		templ_7745c5c3_Var63 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
-			templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
-			templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
-			if !templ_7745c5c3_IsBuffer {
-				defer func() {
-					templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
-					if templ_7745c5c3_Err == nil {
-						templ_7745c5c3_Err = templ_7745c5c3_BufErr
-					}
-				}()
-			}
-			ctx = templ.InitializeContext(ctx)
-			templ_7745c5c3_Err = crumbs(p.Meta, p.Crumbs).Render(ctx, templ_7745c5c3_Buffer)
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 95, " <div class=\"blob-bar\"><span class=\"muted\">")
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			var templ_7745c5c3_Var64 string
-			templ_7745c5c3_Var64, templ_7745c5c3_Err = templ.JoinStringErrs(HumanSize(p.Size))
-			if templ_7745c5c3_Err != nil {
-				return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 196, Col: 42}
-			}
-			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var64))
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 96, "</span> <a href=\"")
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			var templ_7745c5c3_Var65 templ.SafeURL
-			templ_7745c5c3_Var65, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL("/r/" + p.Meta.Repo + "/raw/" + p.Meta.Ref + "/" + p.Path))
-			if templ_7745c5c3_Err != nil {
-				return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 197, Col: 85}
-			}
-			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var65))
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 97, "\">raw</a></div>")
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			if p.IsBinary {
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 98, "<p class=\"empty\">Binary file not shown.</p>")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-			} else if p.IsMarkdown {
-				if len(p.Frontmatter) > 0 {
-					templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 99, "<dl class=\"frontmatter\">")
-					if templ_7745c5c3_Err != nil {
-						return templ_7745c5c3_Err
-					}
-					for _, kv := range p.Frontmatter {
-						templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 100, "<div class=\"fm-row\"><dt>")
-						if templ_7745c5c3_Err != nil {
-							return templ_7745c5c3_Err
-						}
-						var templ_7745c5c3_Var66 string
-						templ_7745c5c3_Var66, templ_7745c5c3_Err = templ.JoinStringErrs(kv.Key)
-						if templ_7745c5c3_Err != nil {
-							return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 206, Col: 19}
-						}
-						_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var66))
-						if templ_7745c5c3_Err != nil {
-							return templ_7745c5c3_Err
-						}
-						templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 101, "</dt><dd>")
-						if templ_7745c5c3_Err != nil {
-							return templ_7745c5c3_Err
-						}
-						var templ_7745c5c3_Var67 string
-						templ_7745c5c3_Var67, templ_7745c5c3_Err = templ.JoinStringErrs(kv.Value)
-						if templ_7745c5c3_Err != nil {
-							return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 207, Col: 21}
-						}
-						_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var67))
-						if templ_7745c5c3_Err != nil {
-							return templ_7745c5c3_Err
-						}
-						templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 102, "</dd></div>")
-						if templ_7745c5c3_Err != nil {
-							return templ_7745c5c3_Err
-						}
-					}
-					templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 103, "</dl>")
-					if templ_7745c5c3_Err != nil {
-						return templ_7745c5c3_Err
-					}
-				}
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 104, " <article class=\"markdown\">")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				templ_7745c5c3_Err = templ.Raw(p.Markdown).Render(ctx, templ_7745c5c3_Buffer)
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 105, "</article>")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-			} else {
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 106, "<div class=\"code\">")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				templ_7745c5c3_Err = templ.Raw(p.Code).Render(ctx, templ_7745c5c3_Buffer)
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 107, "</div>")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-			}
-			return nil
-		})
-		templ_7745c5c3_Err = Layout(p.Meta).Render(templ.WithChildren(ctx, templ_7745c5c3_Var63), templ_7745c5c3_Buffer)
-		if templ_7745c5c3_Err != nil {
-			return templ_7745c5c3_Err
-		}
-		return nil
-	})
-}
-
-func Log(p LogPage) templ.Component {
-	return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
-		templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
-		if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
-			return templ_7745c5c3_CtxErr
-		}
-		templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
-		if !templ_7745c5c3_IsBuffer {
-			defer func() {
-				templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
-				if templ_7745c5c3_Err == nil {
-					templ_7745c5c3_Err = templ_7745c5c3_BufErr
-				}
-			}()
-		}
-		ctx = templ.InitializeContext(ctx)
-		templ_7745c5c3_Var68 := templ.GetChildren(ctx)
-		if templ_7745c5c3_Var68 == nil {
-			templ_7745c5c3_Var68 = templ.NopComponent
-		}
-		ctx = templ.ClearChildren(ctx)
-		templ_7745c5c3_Var69 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
-			templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
-			templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
-			if !templ_7745c5c3_IsBuffer {
-				defer func() {
-					templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
-					if templ_7745c5c3_Err == nil {
-						templ_7745c5c3_Err = templ_7745c5c3_BufErr
-					}
-				}()
-			}
-			ctx = templ.InitializeContext(ctx)
-			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 108, "<h1>Commits</h1><ul class=\"log\">")
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			for _, c := range p.Commits {
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 109, "<li><a class=\"commit-id\" href=\"")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				var templ_7745c5c3_Var70 templ.SafeURL
-				templ_7745c5c3_Var70, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL("/r/" + p.Meta.Repo + "/commit/" + c.Hash))
-				if templ_7745c5c3_Err != nil {
-					return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 229, Col: 89}
-				}
-				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var70))
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 110, "\">")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				var templ_7745c5c3_Var71 string
-				templ_7745c5c3_Var71, templ_7745c5c3_Err = templ.JoinStringErrs(c.Short)
-				if templ_7745c5c3_Err != nil {
-					return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 229, Col: 101}
-				}
-				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var71))
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 111, "</a> <span class=\"commit-title\">")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				var templ_7745c5c3_Var72 string
-				templ_7745c5c3_Var72, templ_7745c5c3_Err = templ.JoinStringErrs(c.Subject)
-				if templ_7745c5c3_Err != nil {
-					return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 230, Col: 43}
-				}
-				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var72))
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 112, "</span> <span class=\"muted\">— ")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				var templ_7745c5c3_Var73 string
-				templ_7745c5c3_Var73, templ_7745c5c3_Err = templ.JoinStringErrs(c.Author)
-				if templ_7745c5c3_Err != nil {
-					return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 231, Col: 39}
-				}
-				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var73))
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 113, ", ")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				var templ_7745c5c3_Var74 string
-				templ_7745c5c3_Var74, templ_7745c5c3_Err = templ.JoinStringErrs(FmtTime(c.When))
-				if templ_7745c5c3_Err != nil {
-					return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 231, Col: 60}
-				}
-				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var74))
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 114, "</span></li>")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-			}
-			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 115, "</ul>")
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			return nil
-		})
-		templ_7745c5c3_Err = Layout(p.Meta).Render(templ.WithChildren(ctx, templ_7745c5c3_Var69), templ_7745c5c3_Buffer)
-		if templ_7745c5c3_Err != nil {
-			return templ_7745c5c3_Err
-		}
-		return nil
-	})
-}
-
-func Commit(p CommitPage) templ.Component {
-	return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
-		templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
-		if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
-			return templ_7745c5c3_CtxErr
-		}
-		templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
-		if !templ_7745c5c3_IsBuffer {
-			defer func() {
-				templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
-				if templ_7745c5c3_Err == nil {
-					templ_7745c5c3_Err = templ_7745c5c3_BufErr
-				}
-			}()
-		}
-		ctx = templ.InitializeContext(ctx)
-		templ_7745c5c3_Var75 := templ.GetChildren(ctx)
-		if templ_7745c5c3_Var75 == nil {
-			templ_7745c5c3_Var75 = templ.NopComponent
-		}
-		ctx = templ.ClearChildren(ctx)
-		templ_7745c5c3_Var76 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
-			templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
-			templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
-			if !templ_7745c5c3_IsBuffer {
-				defer func() {
-					templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
-					if templ_7745c5c3_Err == nil {
-						templ_7745c5c3_Err = templ_7745c5c3_BufErr
-					}
-				}()
-			}
-			ctx = templ.InitializeContext(ctx)
-			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 116, "<h1 class=\"commit-subject\">")
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			var templ_7745c5c3_Var77 string
-			templ_7745c5c3_Var77, templ_7745c5c3_Err = templ.JoinStringErrs(p.Detail.Commit.Subject)
-			if templ_7745c5c3_Err != nil {
-				return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 240, Col: 54}
-			}
-			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var77))
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 117, "</h1><p class=\"muted\"><code>")
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			var templ_7745c5c3_Var78 string
-			templ_7745c5c3_Var78, templ_7745c5c3_Err = templ.JoinStringErrs(p.Detail.Commit.Hash)
-			if templ_7745c5c3_Err != nil {
-				return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 242, Col: 31}
-			}
-			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var78))
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 118, "</code><br>")
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			var templ_7745c5c3_Var79 string
-			templ_7745c5c3_Var79, templ_7745c5c3_Err = templ.JoinStringErrs(p.Detail.Commit.Author)
-			if templ_7745c5c3_Err != nil {
-				return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 243, Col: 27}
-			}
-			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var79))
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 119, " &lt;")
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			var templ_7745c5c3_Var80 string
-			templ_7745c5c3_Var80, templ_7745c5c3_Err = templ.JoinStringErrs(p.Detail.Commit.Email)
-			if templ_7745c5c3_Err != nil {
-				return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 243, Col: 57}
-			}
-			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var80))
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 120, "&gt; · ")
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			var templ_7745c5c3_Var81 string
-			templ_7745c5c3_Var81, templ_7745c5c3_Err = templ.JoinStringErrs(FmtTime(p.Detail.Commit.When))
-			if templ_7745c5c3_Err != nil {
-				return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 243, Col: 98}
-			}
-			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var81))
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 121, "</p>")
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			for _, parent := range p.Detail.Parents {
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 122, "<p class=\"muted\">parent <a href=\"")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				var templ_7745c5c3_Var82 templ.SafeURL
-				templ_7745c5c3_Var82, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL("/r/" + p.Meta.Repo + "/commit/" + parent))
-				if templ_7745c5c3_Err != nil {
-					return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 246, Col: 93}
-				}
-				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var82))
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 123, "\"><code>")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				var templ_7745c5c3_Var83 string
-				templ_7745c5c3_Var83, templ_7745c5c3_Err = templ.JoinStringErrs(ShortHash(parent))
-				if templ_7745c5c3_Err != nil {
-					return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 246, Col: 121}
-				}
-				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var83))
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 124, "</code></a></p>")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-			}
-			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 125, " ")
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			if p.Detail.Commit.Message != p.Detail.Commit.Subject {
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 126, "<pre class=\"commit-body\">")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				var templ_7745c5c3_Var84 string
-				templ_7745c5c3_Var84, templ_7745c5c3_Err = templ.JoinStringErrs(p.Detail.Commit.Message)
-				if templ_7745c5c3_Err != nil {
-					return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 249, Col: 53}
-				}
-				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var84))
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 127, "</pre>")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-			}
-			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 128, " ")
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			if len(p.Files) == 0 {
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 129, "<p class=\"empty\">No changes.</p>")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-			}
-			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 130, " <p class=\"muted diff-summary\">")
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			var templ_7745c5c3_Var85 string
-			templ_7745c5c3_Var85, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(len(p.Files)))
-			if templ_7745c5c3_Err != nil {
-				return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 254, Col: 60}
-			}
-			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var85))
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 131, " files changed</p>")
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			for _, f := range p.Files {
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 132, "<div class=\"filediff\"><div class=\"filediff-head\"><span class=\"filediff-name\">")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				var templ_7745c5c3_Var86 string
-				templ_7745c5c3_Var86, templ_7745c5c3_Err = templ.JoinStringErrs(f.Name)
-				if templ_7745c5c3_Err != nil {
-					return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 258, Col: 41}
-				}
-				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var86))
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 133, "</span> <span class=\"diffstat\"><span class=\"add\">+")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				var templ_7745c5c3_Var87 string
-				templ_7745c5c3_Var87, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(f.Added))
-				if templ_7745c5c3_Err != nil {
-					return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 260, Col: 48}
-				}
-				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var87))
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 134, "</span> <span class=\"del\">−")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				var templ_7745c5c3_Var88 string
-				templ_7745c5c3_Var88, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(f.Deleted))
-				if templ_7745c5c3_Err != nil {
-					return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 261, Col: 52}
-				}
-				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var88))
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 135, "</span></span></div>")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				if f.Binary {
-					templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 136, "<p class=\"empty\">Binary file.</p>")
-					if templ_7745c5c3_Err != nil {
-						return templ_7745c5c3_Err
-					}
-				} else if f.HTML != "" {
-					templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 137, "<div class=\"code diff\">")
-					if templ_7745c5c3_Err != nil {
-						return templ_7745c5c3_Err
-					}
-					templ_7745c5c3_Err = templ.Raw(f.HTML).Render(ctx, templ_7745c5c3_Buffer)
-					if templ_7745c5c3_Err != nil {
-						return templ_7745c5c3_Err
-					}
-					templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 138, "</div>")
-					if templ_7745c5c3_Err != nil {
-						return templ_7745c5c3_Err
-					}
-				} else {
-					templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 139, "<p class=\"empty\">No textual changes.</p>")
-					if templ_7745c5c3_Err != nil {
-						return templ_7745c5c3_Err
-					}
-				}
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 140, "</div>")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-			}
-			return nil
-		})
-		templ_7745c5c3_Err = Layout(p.Meta).Render(templ.WithChildren(ctx, templ_7745c5c3_Var76), templ_7745c5c3_Buffer)
-		if templ_7745c5c3_Err != nil {
-			return templ_7745c5c3_Err
-		}
-		return nil
-	})
-}
-
-func Refs(p RefsPage) templ.Component {
-	return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
-		templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
-		if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
-			return templ_7745c5c3_CtxErr
-		}
-		templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
-		if !templ_7745c5c3_IsBuffer {
-			defer func() {
-				templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
-				if templ_7745c5c3_Err == nil {
-					templ_7745c5c3_Err = templ_7745c5c3_BufErr
-				}
-			}()
-		}
-		ctx = templ.InitializeContext(ctx)
-		templ_7745c5c3_Var89 := templ.GetChildren(ctx)
-		if templ_7745c5c3_Var89 == nil {
-			templ_7745c5c3_Var89 = templ.NopComponent
-		}
-		ctx = templ.ClearChildren(ctx)
-		templ_7745c5c3_Var90 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
-			templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
-			templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
-			if !templ_7745c5c3_IsBuffer {
-				defer func() {
-					templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
-					if templ_7745c5c3_Err == nil {
-						templ_7745c5c3_Err = templ_7745c5c3_BufErr
-					}
-				}()
-			}
-			ctx = templ.InitializeContext(ctx)
-			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 141, "<h1>Branches</h1><ul class=\"refs\">")
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			for _, b := range p.Refs.Branches {
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 142, "<li><a href=\"")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				var templ_7745c5c3_Var91 templ.SafeURL
-				templ_7745c5c3_Var91, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL("/r/" + p.Meta.Repo + "/tree/" + b.Name + "/"))
-				if templ_7745c5c3_Err != nil {
-					return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 284, Col: 75}
-				}
-				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var91))
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 143, "\">")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				var templ_7745c5c3_Var92 string
-				templ_7745c5c3_Var92, templ_7745c5c3_Err = templ.JoinStringErrs(b.Name)
-				if templ_7745c5c3_Err != nil {
-					return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 284, Col: 86}
-				}
-				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var92))
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 144, "</a> <span class=\"muted\"><code>")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				var templ_7745c5c3_Var93 string
-				templ_7745c5c3_Var93, templ_7745c5c3_Err = templ.JoinStringErrs(ShortHash(b.Hash))
-				if templ_7745c5c3_Err != nil {
-					return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 285, Col: 50}
-				}
-				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var93))
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 145, "</code></span></li>")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-			}
-			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 146, "</ul><h1>Tags</h1>")
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			if len(p.Refs.Tags) == 0 {
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 147, "<p class=\"empty\">No tags.</p>")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-			}
-			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 148, " <ul class=\"refs\">")
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			for _, t := range p.Refs.Tags {
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 149, "<li><a href=\"")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				var templ_7745c5c3_Var94 templ.SafeURL
-				templ_7745c5c3_Var94, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL("/r/" + p.Meta.Repo + "/log/" + t.Name))
-				if templ_7745c5c3_Err != nil {
-					return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 296, Col: 68}
-				}
-				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var94))
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 150, "\">")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				var templ_7745c5c3_Var95 string
-				templ_7745c5c3_Var95, templ_7745c5c3_Err = templ.JoinStringErrs(t.Name)
-				if templ_7745c5c3_Err != nil {
-					return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 296, Col: 79}
-				}
-				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var95))
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 151, "</a> <span class=\"muted\"><code>")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				var templ_7745c5c3_Var96 string
-				templ_7745c5c3_Var96, templ_7745c5c3_Err = templ.JoinStringErrs(ShortHash(t.Hash))
-				if templ_7745c5c3_Err != nil {
-					return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 297, Col: 50}
-				}
-				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var96))
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 152, "</code></span></li>")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-			}
-			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 153, "</ul>")
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			return nil
-		})
-		templ_7745c5c3_Err = Layout(p.Meta).Render(templ.WithChildren(ctx, templ_7745c5c3_Var90), templ_7745c5c3_Buffer)
-		if templ_7745c5c3_Err != nil {
-			return templ_7745c5c3_Err
-		}
-		return nil
-	})
-}
-
-func Issues(p IssuesPage) templ.Component {
-	return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
-		templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
-		if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
-			return templ_7745c5c3_CtxErr
-		}
-		templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
-		if !templ_7745c5c3_IsBuffer {
-			defer func() {
-				templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
-				if templ_7745c5c3_Err == nil {
-					templ_7745c5c3_Err = templ_7745c5c3_BufErr
-				}
-			}()
-		}
-		ctx = templ.InitializeContext(ctx)
-		templ_7745c5c3_Var97 := templ.GetChildren(ctx)
-		if templ_7745c5c3_Var97 == nil {
-			templ_7745c5c3_Var97 = templ.NopComponent
-		}
-		ctx = templ.ClearChildren(ctx)
-		templ_7745c5c3_Var98 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
-			templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
-			templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
-			if !templ_7745c5c3_IsBuffer {
-				defer func() {
-					templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
-					if templ_7745c5c3_Err == nil {
-						templ_7745c5c3_Err = templ_7745c5c3_BufErr
-					}
-				}()
-			}
-			ctx = templ.InitializeContext(ctx)
-			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 154, "<h1>Issues <span class=\"muted\">(")
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			var templ_7745c5c3_Var99 string
-			templ_7745c5c3_Var99, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(p.Total))
-			if templ_7745c5c3_Err != nil {
-				return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 306, Col: 57}
-			}
-			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var99))
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 155, ")</span></h1>")
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			if p.Total == 0 {
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 156, "<p class=\"empty\">No issues.</p>")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-			}
-			for _, g := range p.Groups {
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 157, "<section class=\"issue-group\">")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				var templ_7745c5c3_Var100 = []any{"status", "status--" + StatusKind(g.Status)}
-				templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var100...)
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 158, "<h2 class=\"")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				var templ_7745c5c3_Var101 string
-				templ_7745c5c3_Var101, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var100).String())
-				if templ_7745c5c3_Err != nil {
-					return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 1, Col: 0}
-				}
-				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var101)
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 159, "\">")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				var templ_7745c5c3_Var102 string
-				templ_7745c5c3_Var102, templ_7745c5c3_Err = templ.JoinStringErrs(g.Status)
-				if templ_7745c5c3_Err != nil {
-					return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 312, Col: 72}
-				}
-				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var102))
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 160, " <span class=\"muted\">")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				var templ_7745c5c3_Var103 string
-				templ_7745c5c3_Var103, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(len(g.Tasks)))
-				if templ_7745c5c3_Err != nil {
-					return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 312, Col: 123}
-				}
-				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var103))
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 161, "</span></h2><ul class=\"issue-list\">")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				for _, t := range g.Tasks {
-					templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 162, "<li>")
-					if templ_7745c5c3_Err != nil {
-						return templ_7745c5c3_Err
-					}
-					if t.Type() != "" {
-						var templ_7745c5c3_Var104 = []any{"badge", "badge--" + LabelColor(t.Type())}
-						templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var104...)
-						if templ_7745c5c3_Err != nil {
-							return templ_7745c5c3_Err
-						}
-						templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 163, "<span class=\"")
-						if templ_7745c5c3_Err != nil {
-							return templ_7745c5c3_Err
-						}
-						var templ_7745c5c3_Var105 string
-						templ_7745c5c3_Var105, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var104).String())
-						if templ_7745c5c3_Err != nil {
-							return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 1, Col: 0}
-						}
-						_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var105)
-						if templ_7745c5c3_Err != nil {
-							return templ_7745c5c3_Err
-						}
-						templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 164, "\">")
-						if templ_7745c5c3_Err != nil {
-							return templ_7745c5c3_Err
-						}
-						var templ_7745c5c3_Var106 string
-						templ_7745c5c3_Var106, templ_7745c5c3_Err = templ.JoinStringErrs(t.Type())
-						if templ_7745c5c3_Err != nil {
-							return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 317, Col: 76}
-						}
-						_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var106))
-						if templ_7745c5c3_Err != nil {
-							return templ_7745c5c3_Err
-						}
-						templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 165, "</span> ")
-						if templ_7745c5c3_Err != nil {
-							return templ_7745c5c3_Err
-						}
-					}
-					templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 166, "<a class=\"issue-link\" href=\"")
-					if templ_7745c5c3_Err != nil {
-						return templ_7745c5c3_Err
-					}
-					var templ_7745c5c3_Var107 templ.SafeURL
-					templ_7745c5c3_Var107, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL("/r/" + p.Meta.Repo + "/issues/" + t.Key()))
-					if templ_7745c5c3_Err != nil {
-						return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 319, Col: 93}
-					}
-					_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var107))
-					if templ_7745c5c3_Err != nil {
-						return templ_7745c5c3_Err
-					}
-					templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 167, "\"><span class=\"issue-id\">")
-					if templ_7745c5c3_Err != nil {
-						return templ_7745c5c3_Err
-					}
-					var templ_7745c5c3_Var108 string
-					templ_7745c5c3_Var108, templ_7745c5c3_Err = templ.JoinStringErrs(t.ID)
-					if templ_7745c5c3_Err != nil {
-						return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 320, Col: 37}
-					}
-					_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var108))
-					if templ_7745c5c3_Err != nil {
-						return templ_7745c5c3_Err
-					}
-					templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 168, "</span> <span class=\"issue-title\">")
-					if templ_7745c5c3_Err != nil {
-						return templ_7745c5c3_Err
-					}
-					var templ_7745c5c3_Var109 string
-					templ_7745c5c3_Var109, templ_7745c5c3_Err = templ.JoinStringErrs(t.Title)
-					if templ_7745c5c3_Err != nil {
-						return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 321, Col: 43}
-					}
-					_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var109))
-					if templ_7745c5c3_Err != nil {
-						return templ_7745c5c3_Err
-					}
-					templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 169, "</span></a> ")
-					if templ_7745c5c3_Err != nil {
-						return templ_7745c5c3_Err
-					}
-					if PriorityClass(t.Priority) != "" {
-						var templ_7745c5c3_Var110 = []any{"prio", "prio--" + PriorityClass(t.Priority)}
-						templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var110...)
-						if templ_7745c5c3_Err != nil {
-							return templ_7745c5c3_Err
-						}
-						templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 170, "<span class=\"")
-						if templ_7745c5c3_Err != nil {
-							return templ_7745c5c3_Err
-						}
-						var templ_7745c5c3_Var111 string
-						templ_7745c5c3_Var111, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var110).String())
-						if templ_7745c5c3_Err != nil {
-							return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 1, Col: 0}
-						}
-						_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var111)
-						if templ_7745c5c3_Err != nil {
-							return templ_7745c5c3_Err
-						}
-						templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 171, "\">")
-						if templ_7745c5c3_Err != nil {
-							return templ_7745c5c3_Err
-						}
-						var templ_7745c5c3_Var112 string
-						templ_7745c5c3_Var112, templ_7745c5c3_Err = templ.JoinStringErrs(t.Priority)
-						if templ_7745c5c3_Err != nil {
-							return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 324, Col: 81}
-						}
-						_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var112))
-						if templ_7745c5c3_Err != nil {
-							return templ_7745c5c3_Err
-						}
-						templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 172, "</span> ")
-						if templ_7745c5c3_Err != nil {
-							return templ_7745c5c3_Err
-						}
-					}
-					for _, l := range t.OtherLabels() {
-						var templ_7745c5c3_Var113 = []any{"chip", "chip--" + LabelColor(l)}
-						templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var113...)
-						if templ_7745c5c3_Err != nil {
-							return templ_7745c5c3_Err
-						}
-						templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 173, "<span class=\"")
-						if templ_7745c5c3_Err != nil {
-							return templ_7745c5c3_Err
-						}
-						var templ_7745c5c3_Var114 string
-						templ_7745c5c3_Var114, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var113).String())
-						if templ_7745c5c3_Err != nil {
-							return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 1, Col: 0}
-						}
-						_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var114)
-						if templ_7745c5c3_Err != nil {
-							return templ_7745c5c3_Err
-						}
-						templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 174, "\">")
-						if templ_7745c5c3_Err != nil {
-							return templ_7745c5c3_Err
-						}
-						var templ_7745c5c3_Var115 string
-						templ_7745c5c3_Var115, templ_7745c5c3_Err = templ.JoinStringErrs(l)
-						if templ_7745c5c3_Err != nil {
-							return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 327, Col: 60}
-						}
-						_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var115))
-						if templ_7745c5c3_Err != nil {
-							return templ_7745c5c3_Err
-						}
-						templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 175, "</span>")
-						if templ_7745c5c3_Err != nil {
-							return templ_7745c5c3_Err
-						}
-					}
-					templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 176, "</li>")
-					if templ_7745c5c3_Err != nil {
-						return templ_7745c5c3_Err
-					}
-				}
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 177, "</ul></section>")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-			}
-			return nil
-		})
-		templ_7745c5c3_Err = Layout(p.Meta).Render(templ.WithChildren(ctx, templ_7745c5c3_Var98), templ_7745c5c3_Buffer)
-		if templ_7745c5c3_Err != nil {
-			return templ_7745c5c3_Err
-		}
-		return nil
-	})
-}
-
-func Issue(p IssuePage) templ.Component {
-	return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
-		templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
-		if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
-			return templ_7745c5c3_CtxErr
-		}
-		templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
-		if !templ_7745c5c3_IsBuffer {
-			defer func() {
-				templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
-				if templ_7745c5c3_Err == nil {
-					templ_7745c5c3_Err = templ_7745c5c3_BufErr
-				}
-			}()
-		}
-		ctx = templ.InitializeContext(ctx)
-		templ_7745c5c3_Var116 := templ.GetChildren(ctx)
-		if templ_7745c5c3_Var116 == nil {
-			templ_7745c5c3_Var116 = templ.NopComponent
-		}
-		ctx = templ.ClearChildren(ctx)
-		templ_7745c5c3_Var117 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
-			templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
-			templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
-			if !templ_7745c5c3_IsBuffer {
-				defer func() {
-					templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
-					if templ_7745c5c3_Err == nil {
-						templ_7745c5c3_Err = templ_7745c5c3_BufErr
-					}
-				}()
-			}
-			ctx = templ.InitializeContext(ctx)
-			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 178, "<p class=\"muted\"><a href=\"")
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			var templ_7745c5c3_Var118 templ.SafeURL
-			templ_7745c5c3_Var118, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL("/r/" + p.Meta.Repo + "/issues"))
-			if templ_7745c5c3_Err != nil {
-				return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 339, Col: 75}
-			}
-			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var118))
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 179, "\">← issues</a></p><h1 class=\"issue-detail-title\"><span class=\"issue-id\">")
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			var templ_7745c5c3_Var119 string
-			templ_7745c5c3_Var119, templ_7745c5c3_Err = templ.JoinStringErrs(p.Task.ID)
-			if templ_7745c5c3_Err != nil {
-				return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 341, Col: 37}
-			}
-			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var119))
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 180, "</span> ")
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			var templ_7745c5c3_Var120 string
-			templ_7745c5c3_Var120, templ_7745c5c3_Err = templ.JoinStringErrs(p.Task.Title)
-			if templ_7745c5c3_Err != nil {
-				return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 342, Col: 17}
-			}
-			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var120))
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 181, "</h1><div class=\"issue-meta\">")
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			var templ_7745c5c3_Var121 = []any{"status", "status--" + StatusKind(p.Task.Status)}
-			templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var121...)
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 182, "<span class=\"")
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			var templ_7745c5c3_Var122 string
-			templ_7745c5c3_Var122, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var121).String())
-			if templ_7745c5c3_Err != nil {
-				return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 1, Col: 0}
-			}
-			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var122)
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 183, "\">")
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			var templ_7745c5c3_Var123 string
-			templ_7745c5c3_Var123, templ_7745c5c3_Err = templ.JoinStringErrs(p.Task.Status)
-			if templ_7745c5c3_Err != nil {
-				return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 345, Col: 83}
-			}
-			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var123))
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 184, "</span> ")
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			if p.Task.Type() != "" {
-				var templ_7745c5c3_Var124 = []any{"badge", "badge--" + LabelColor(p.Task.Type())}
-				templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var124...)
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 185, "<span class=\"")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				var templ_7745c5c3_Var125 string
-				templ_7745c5c3_Var125, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var124).String())
-				if templ_7745c5c3_Err != nil {
-					return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 1, Col: 0}
-				}
-				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var125)
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 186, "\">")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				var templ_7745c5c3_Var126 string
-				templ_7745c5c3_Var126, templ_7745c5c3_Err = templ.JoinStringErrs(p.Task.Type())
-				if templ_7745c5c3_Err != nil {
-					return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 347, Col: 82}
-				}
-				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var126))
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 187, "</span> ")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-			}
-			if PriorityClass(p.Task.Priority) != "" {
-				var templ_7745c5c3_Var127 = []any{"prio", "prio--" + PriorityClass(p.Task.Priority)}
-				templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var127...)
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 188, "<span class=\"")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				var templ_7745c5c3_Var128 string
-				templ_7745c5c3_Var128, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var127).String())
-				if templ_7745c5c3_Err != nil {
-					return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 1, Col: 0}
-				}
-				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var128)
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 189, "\">priority: ")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				var templ_7745c5c3_Var129 string
-				templ_7745c5c3_Var129, templ_7745c5c3_Err = templ.JoinStringErrs(p.Task.Priority)
-				if templ_7745c5c3_Err != nil {
-					return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 350, Col: 97}
-				}
-				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var129))
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 190, "</span> ")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-			} else if p.Task.Priority != "" {
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 191, "<span class=\"muted\">priority: ")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				var templ_7745c5c3_Var130 string
-				templ_7745c5c3_Var130, templ_7745c5c3_Err = templ.JoinStringErrs(p.Task.Priority)
-				if templ_7745c5c3_Err != nil {
-					return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 352, Col: 51}
-				}
-				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var130))
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 192, "</span> ")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-			}
-			for _, l := range p.Task.OtherLabels() {
-				var templ_7745c5c3_Var131 = []any{"chip", "chip--" + LabelColor(l)}
-				templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var131...)
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 193, "<span class=\"")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				var templ_7745c5c3_Var132 string
-				templ_7745c5c3_Var132, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var131).String())
-				if templ_7745c5c3_Err != nil {
-					return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 1, Col: 0}
-				}
-				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var132)
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 194, "\">")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				var templ_7745c5c3_Var133 string
-				templ_7745c5c3_Var133, templ_7745c5c3_Err = templ.JoinStringErrs(l)
-				if templ_7745c5c3_Err != nil {
-					return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 355, Col: 56}
-				}
-				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var133))
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 195, "</span>")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-			}
-			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 196, "</div>")
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			if p.Task.Created != "" || p.Task.Updated != "" {
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 197, "<p class=\"muted issue-dates\">")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				if p.Task.Created != "" {
-					templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 198, "<span>created ")
-					if templ_7745c5c3_Err != nil {
-						return templ_7745c5c3_Err
-					}
-					var templ_7745c5c3_Var134 string
-					templ_7745c5c3_Var134, templ_7745c5c3_Err = templ.JoinStringErrs(p.Task.Created)
-					if templ_7745c5c3_Err != nil {
-						return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 361, Col: 35}
-					}
-					_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var134))
-					if templ_7745c5c3_Err != nil {
-						return templ_7745c5c3_Err
-					}
-					templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 199, "</span> ")
-					if templ_7745c5c3_Err != nil {
-						return templ_7745c5c3_Err
-					}
-				}
-				if p.Task.Updated != "" {
-					templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 200, "<span>· updated ")
-					if templ_7745c5c3_Err != nil {
-						return templ_7745c5c3_Err
-					}
-					var templ_7745c5c3_Var135 string
-					templ_7745c5c3_Var135, templ_7745c5c3_Err = templ.JoinStringErrs(p.Task.Updated)
-					if templ_7745c5c3_Err != nil {
-						return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 364, Col: 38}
-					}
-					_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var135))
-					if templ_7745c5c3_Err != nil {
-						return templ_7745c5c3_Err
-					}
-					templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 201, "</span>")
-					if templ_7745c5c3_Err != nil {
-						return templ_7745c5c3_Err
-					}
-				}
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 202, "</p>")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-			}
-			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 203, " ")
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			if len(p.Task.Deps) > 0 {
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 204, "<p class=\"muted\">depends on: ")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				var templ_7745c5c3_Var136 string
-				templ_7745c5c3_Var136, templ_7745c5c3_Err = templ.JoinStringErrs(strings.Join(p.Task.Deps, ", "))
-				if templ_7745c5c3_Err != nil {
-					return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 369, Col: 65}
-				}
-				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var136))
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 205, "</p>")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-			}
-			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 206, " ")
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			if p.Body != "" {
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 207, "<article class=\"markdown issue-body\">")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				templ_7745c5c3_Err = templ.Raw(p.Body).Render(ctx, templ_7745c5c3_Buffer)
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 208, "</article>")
-				if templ_7745c5c3_Err != nil {
-					return templ_7745c5c3_Err
-				}
-			}
-			return nil
-		})
-		templ_7745c5c3_Err = Layout(p.Meta).Render(templ.WithChildren(ctx, templ_7745c5c3_Var117), templ_7745c5c3_Buffer)
-		if templ_7745c5c3_Err != nil {
-			return templ_7745c5c3_Err
-		}
-		return nil
-	})
-}
-
-func Error(m Meta, code int, msg string) templ.Component {
-	return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
-		templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
-		if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
-			return templ_7745c5c3_CtxErr
-		}
-		templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
-		if !templ_7745c5c3_IsBuffer {
-			defer func() {
-				templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
-				if templ_7745c5c3_Err == nil {
-					templ_7745c5c3_Err = templ_7745c5c3_BufErr
-				}
-			}()
-		}
-		ctx = templ.InitializeContext(ctx)
-		templ_7745c5c3_Var137 := templ.GetChildren(ctx)
-		if templ_7745c5c3_Var137 == nil {
-			templ_7745c5c3_Var137 = templ.NopComponent
-		}
-		ctx = templ.ClearChildren(ctx)
-		templ_7745c5c3_Var138 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
-			templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
-			templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
-			if !templ_7745c5c3_IsBuffer {
-				defer func() {
-					templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
-					if templ_7745c5c3_Err == nil {
-						templ_7745c5c3_Err = templ_7745c5c3_BufErr
-					}
-				}()
-			}
-			ctx = templ.InitializeContext(ctx)
-			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 209, "<h1>")
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			var templ_7745c5c3_Var139 string
-			templ_7745c5c3_Var139, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(code))
-			if templ_7745c5c3_Err != nil {
-				return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 381, Col: 26}
-			}
-			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var139))
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 210, "</h1><p>")
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			var templ_7745c5c3_Var140 string
-			templ_7745c5c3_Var140, templ_7745c5c3_Err = templ.JoinStringErrs(msg)
-			if templ_7745c5c3_Err != nil {
-				return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 382, Col: 10}
-			}
-			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var140))
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 211, "</p><p><a href=\"/\">← back to repositories</a></p>")
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			return nil
-		})
-		templ_7745c5c3_Err = Layout(m).Render(templ.WithChildren(ctx, templ_7745c5c3_Var138), templ_7745c5c3_Buffer)
-		if templ_7745c5c3_Err != nil {
-			return templ_7745c5c3_Err
-		}
-		return nil
-	})
-}
-
-func crumbs(m Meta, cs []Crumb) templ.Component {
-	return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
-		templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
-		if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
-			return templ_7745c5c3_CtxErr
-		}
-		templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
-		if !templ_7745c5c3_IsBuffer {
-			defer func() {
-				templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
-				if templ_7745c5c3_Err == nil {
-					templ_7745c5c3_Err = templ_7745c5c3_BufErr
-				}
-			}()
-		}
-		ctx = templ.InitializeContext(ctx)
-		templ_7745c5c3_Var141 := templ.GetChildren(ctx)
-		if templ_7745c5c3_Var141 == nil {
-			templ_7745c5c3_Var141 = templ.NopComponent
-		}
-		ctx = templ.ClearChildren(ctx)
-		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 212, "<nav class=\"crumbs\"><a href=\"")
-		if templ_7745c5c3_Err != nil {
-			return templ_7745c5c3_Err
-		}
-		var templ_7745c5c3_Var142 templ.SafeURL
-		templ_7745c5c3_Var142, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL("/r/" + m.Repo + "/tree/" + m.Ref + "/"))
-		if templ_7745c5c3_Err != nil {
-			return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 389, Col: 66}
-		}
-		_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var142))
-		if templ_7745c5c3_Err != nil {
-			return templ_7745c5c3_Err
-		}
-		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 213, "\">")
-		if templ_7745c5c3_Err != nil {
-			return templ_7745c5c3_Err
-		}
-		var templ_7745c5c3_Var143 string
-		templ_7745c5c3_Var143, templ_7745c5c3_Err = templ.JoinStringErrs(m.Repo)
-		if templ_7745c5c3_Err != nil {
-			return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 389, Col: 77}
-		}
-		_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var143))
-		if templ_7745c5c3_Err != nil {
-			return templ_7745c5c3_Err
-		}
-		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 214, "</a> ")
-		if templ_7745c5c3_Err != nil {
-			return templ_7745c5c3_Err
-		}
-		for _, c := range cs {
-			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 215, "<span class=\"sep\">/</span> <a href=\"")
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			var templ_7745c5c3_Var144 templ.SafeURL
-			templ_7745c5c3_Var144, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL("/r/" + m.Repo + "/tree/" + m.Ref + "/" + c.Path))
-			if templ_7745c5c3_Err != nil {
-				return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 392, Col: 76}
-			}
-			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var144))
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 216, "\">")
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			var templ_7745c5c3_Var145 string
-			templ_7745c5c3_Var145, templ_7745c5c3_Err = templ.JoinStringErrs(c.Name)
-			if templ_7745c5c3_Err != nil {
-				return templ.Error{Err: templ_7745c5c3_Err, FileName: `web/templates/templates.templ`, Line: 392, Col: 87}
-			}
-			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var145))
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 217, "</a>")
-			if templ_7745c5c3_Err != nil {
-				return templ_7745c5c3_Err
-			}
-		}
-		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 218, "</nav>")
-		if templ_7745c5c3_Err != nil {
-			return templ_7745c5c3_Err
-		}
-		return nil
-	})
-}
-
-// pageTitle builds the <title> from page meta.
-func pageTitle(m Meta) string {
-	if m.Title != "" {
-		return m.Title
-	}
-	if m.Repo != "" {
-		return m.Repo + " · humdrum codex"
-	}
-	return "humdrum codex"
-}
-
-func refOr(ref string) string {
-	if ref == "" {
-		return "HEAD"
-	}
-	return ref
-}
-
-func themeOr(t string) string {
-	if t == "" {
-		return DefaultTheme
-	}
-	return t
-}
-
-// themeLabel renders a human-friendly name for the theme picker.
-func themeLabel(t string) string {
-	switch t {
-	case "flexoki":
-		return "Flexoki"
-	case "flexoki-dark":
-		return "Flexoki Dark"
-	case "uchu":
-		return "Uchu"
-	case "uchu-dark":
-		return "Uchu Dark"
-	case "humdrum":
-		return "Humdrum"
-	case "humdrum-dark":
-		return "Humdrum Dark"
-	case "eink":
-		return "E-ink"
-	}
-	return t
-}
-
-// parentTreeURL returns the tree URL one directory up from path.
-func parentTreeURL(m Meta, path string) string {
-	parent := ""
-	if i := lastSlash(path); i >= 0 {
-		parent = path[:i]
-	}
-	return fmt.Sprintf("/r/%s/tree/%s/%s", m.Repo, m.Ref, parent)
-}
-
-func lastSlash(s string) int {
-	for i := len(s) - 1; i >= 0; i-- {
-		if s[i] == '/' {
-			return i
-		}
-	}
-	return -1
-}
-
-var _ = templruntime.GeneratedTemplate
web/templates/view.go +0 −285
@@ -1,285 +0,0 @@
-// Package templates holds the templ components and their view-models. View-models
-// are plain structs assembled by the server layer; templ files render them.
-package templates
-
-import (
-	"fmt"
-	"html/template"
-	"strings"
-	"time"
-
-	"git.kortum.world/custard/internal/backlog"
-	"git.kortum.world/custard/internal/gitread"
-	"git.kortum.world/custard/internal/render"
-)
-
-// Meta is the shared chrome data every page needs.
-type Meta struct {
-	Title     string
-	Repo      string // current repo name, "" on the index
-	Ref       string // current ref, when relevant
-	HasIssues bool   // show the issues tab only when backlog tasks exist
-	HasReadme bool   // show the readme tab only when a README exists
-	Theme     string // active data-theme, resolved from cookie (default flexoki)
-	Tab       string // active repo tab: code | readme | log | refs | issues
-	CloneURL  string // read-only HTTP clone URL, shown in the footer bar
-}
-
-// Families is what the picker offers: a palette choice only. Light vs dark is
-// resolved from the OS (prefers-color-scheme), not chosen here. E-ink is its own
-// fixed mode. The cookie stores one of these; the client resolves the actual
-// data-theme (e.g. flexoki → flexoki-dark) before paint.
-var Families = []string{"flexoki", "uchu", "humdrum", "eink"}
-
-// DefaultTheme (family) applies when no valid cookie is present.
-const DefaultTheme = "flexoki"
-
-// ValidTheme returns t if it is a known family, else DefaultTheme.
-func ValidTheme(t string) string {
-	for _, k := range Families {
-		if k == t {
-			return t
-		}
-	}
-	return DefaultTheme
-}
-
-// Crumb is one breadcrumb segment with the cumulative path up to it.
-type Crumb struct {
-	Name string
-	Path string
-}
-
-// IndexPage lists all repositories.
-type IndexPage struct {
-	Meta  Meta
-	Repos []gitread.Repo
-}
-
-// RepoPage is the repo home (code tab): the root file tree plus a ref summary.
-// The README lives on its own tab, not here.
-type RepoPage struct {
-	Meta          Meta
-	DefaultBranch string
-	Entries       []gitread.Entry
-	Last          *gitread.Commit
-	Branches      int
-	Tags          int
-}
-
-// ReadmePage renders a repo's README on its own tab.
-type ReadmePage struct {
-	Meta   Meta
-	Readme template.HTML
-}
-
-// TreePage browses a directory at a ref.
-type TreePage struct {
-	Meta    Meta
-	Path    string
-	Crumbs  []Crumb
-	Entries []gitread.Entry
-}
-
-// BlobPage shows one file.
-type BlobPage struct {
-	Meta        Meta
-	Path        string
-	Crumbs      []Crumb
-	Size        int64
-	IsMarkdown  bool
-	Markdown    template.HTML
-	Frontmatter []render.FMPair // YAML frontmatter of a Markdown file, if any
-	Code        template.HTML
-	IsBinary    bool
-}
-
-// LogPage is a commit list for a ref.
-type LogPage struct {
-	Meta    Meta
-	Commits []gitread.Commit
-}
-
-// CommitPage shows a single commit and its diff, split per file.
-type CommitPage struct {
-	Meta   Meta
-	Detail *gitread.CommitDetail
-	Files  []render.FileDiff
-}
-
-// RefsPage lists branches and tags.
-type RefsPage struct {
-	Meta Meta
-	Refs *gitread.Refs
-}
-
-// IssueGroup is a status bucket of tasks in the issues list.
-type IssueGroup struct {
-	Status string
-	Tasks  []backlog.Task
-}
-
-// IssuesPage is the GitHub-issues-style list, grouped by status.
-type IssuesPage struct {
-	Meta   Meta
-	Groups []IssueGroup
-	Total  int
-}
-
-// IssuePage is a single task with its rendered Markdown body.
-type IssuePage struct {
-	Meta Meta
-	Task backlog.Task
-	Body template.HTML
-}
-
-// GroupByStatus buckets tasks following the repo's configured status order,
-// appending any statuses not in that order at the end. Empty buckets are
-// dropped. An empty order falls back to the Backlog.md defaults.
-func GroupByStatus(tasks []backlog.Task, order []string) []IssueGroup {
-	if len(order) == 0 {
-		order = backlog.DefaultStatuses
-	}
-	known := make(map[string]bool, len(order))
-	for _, s := range order {
-		known[s] = true
-	}
-	byStatus := map[string][]backlog.Task{}
-	var extra []string
-	for _, t := range tasks {
-		if _, seen := byStatus[t.Status]; !seen && !known[t.Status] {
-			extra = append(extra, t.Status)
-		}
-		byStatus[t.Status] = append(byStatus[t.Status], t)
-	}
-	var groups []IssueGroup
-	for _, s := range append(append([]string{}, order...), extra...) {
-		if ts := byStatus[s]; len(ts) > 0 {
-			groups = append(groups, IssueGroup{Status: s, Tasks: ts})
-		}
-	}
-	return groups
-}
-
-// LabelColor maps a label to a theme color-family name (phase-3 tokens key off
-// these via .chip--<color>). Unknown labels get the neutral accent.
-func LabelColor(label string) string {
-	switch strings.ToLower(label) {
-	case "bug":
-		return "red"
-	case "feature":
-		return "green"
-	case "enhancement", "ui":
-		return "blue"
-	case "docs", "documentation":
-		return "cyan"
-	case "chore", "refactor":
-		return "purple"
-	case "question":
-		return "yellow"
-	default:
-		return "accent"
-	}
-}
-
-// PriorityClass maps a priority to a css-class-safe level (high/medium/low),
-// or "" when absent so the template can skip the pill.
-func PriorityClass(p string) string {
-	switch strings.ToLower(strings.TrimSpace(p)) {
-	case "high", "critical", "urgent":
-		return "high"
-	case "medium", "med", "normal":
-		return "medium"
-	case "low", "minor":
-		return "low"
-	default:
-		return ""
-	}
-}
-
-// StatusKind maps an arbitrary status label (emoji and all) to a stable
-// css-class-safe kind, so themes can style columns regardless of the exact
-// wording a repo uses. Unrecognized statuses fall back to an alnum slug.
-func StatusKind(status string) string {
-	s := strings.ToLower(status)
-	switch {
-	case strings.Contains(s, "progress"):
-		return "in-progress"
-	case strings.Contains(s, "done"), strings.Contains(s, "ship"), strings.Contains(s, "complete"):
-		return "done"
-	case strings.Contains(s, "paus"), strings.Contains(s, "block"), strings.Contains(s, "hold"):
-		return "paused"
-	case strings.Contains(s, "backlog"), strings.Contains(s, "to do"), strings.Contains(s, "todo"):
-		return "backlog"
-	}
-	return slugify(s)
-}
-
-// slugify reduces a string to lowercase alphanumerics joined by single dashes.
-func slugify(s string) string {
-	var b strings.Builder
-	dash := false
-	for _, r := range strings.ToLower(s) {
-		switch {
-		case r >= 'a' && r <= 'z', r >= '0' && r <= '9':
-			if dash && b.Len() > 0 {
-				b.WriteByte('-')
-			}
-			b.WriteRune(r)
-			dash = false
-		default:
-			dash = true
-		}
-	}
-	if b.Len() == 0 {
-		return "other"
-	}
-	return b.String()
-}
-
-// HumanSize formats a byte count as a short human-readable string.
-func HumanSize(n int64) string {
-	const unit = 1024
-	if n < unit {
-		return fmt.Sprintf("%d B", n)
-	}
-	div, exp := int64(unit), 0
-	for m := n / unit; m >= unit; m /= unit {
-		div *= unit
-		exp++
-	}
-	return fmt.Sprintf("%.1f %cB", float64(n)/float64(div), "KMGT"[exp])
-}
-
-// ShortHash returns the first 8 characters of a hash, or the whole thing.
-func ShortHash(h string) string {
-	if len(h) >= 8 {
-		return h[:8]
-	}
-	return h
-}
-
-// FmtTime renders a timestamp in a compact, stable form.
-func FmtTime(t time.Time) string {
-	return t.Format("2006-01-02 15:04")
-}
-
-// BuildCrumbs splits a "/"-separated path into cumulative breadcrumbs.
-func BuildCrumbs(path string) []Crumb {
-	path = strings.Trim(path, "/")
-	if path == "" {
-		return nil
-	}
-	parts := strings.Split(path, "/")
-	crumbs := make([]Crumb, 0, len(parts))
-	acc := ""
-	for _, p := range parts {
-		if acc == "" {
-			acc = p
-		} else {
-			acc = acc + "/" + p
-		}
-		crumbs = append(crumbs, Crumb{Name: p, Path: acc})
-	}
-	return crumbs
-}