SPB Git 2.0 — The Intelligent Git Forge · Plan
Written 2026-11-01 after Phase 0 (reconnaissance) on the real repository
(spbgit 1.1.0, commit 810e402, deployed on M1M32 ~/srv/spbgit/app,
public at https://git.spboucher.ai through the MacLustr Tunnel).
1. Architecture as found
| Layer | Implementation | Notes |
|---|---|---|
| Runtime | Node ≥ 20 (25.9 on M1M32), ESM, Fastify 5, pino | PM2 spbgit-server, port 7420, loopback; TLS at the BHS64 gateway |
| Git hosting | src/git/smart-http.mjs spawns git upload-pack / receive-pack --stateless-rpc, streamed, gzip bodies |
Anonymous clone/fetch, PAT (argon2id) for push, HTTP Basic user spb |
| Repo model | src/git/repo.mjs shells out to git (refs, tree, blob, log, diff parser, blame, archive) |
maxBuffer 64 MB per call, blobs fully buffered |
| Storage | bare repos ~/srv/git/*.git (157), data/meta.json (desc/topics/pins), data/activity.jsonl, data/clones.json, data/tokens.json, data/releases/ |
filesystem is the database |
| Cache | ~/srv/spbgit/cache/repos/<repo>/<sha>/*, repos/<repo>/_repo/overview-v4.json, global/* |
post-receive hook POSTs to /internal/hooks/post-receive (loopback only) → busts repo + global |
| Rendering | Nunjucks SSR (src/web/views), hand-written CSS (tokens.css, app.css, markdown.css), vanilla JS (app.js, theme-boot.js), shiki dual-theme, markdown-it + sanitize-html, satori/resvg OG cards |
No bundler, no framework, CSP script-src 'self' (no inline JS) |
| API | /api/v1/* public reads, Bearer PAT writes, /healthz, /feed.atom, /sitemap.xml, /og/*.png, /raw/*, /archive/*, /releases/* |
JSON error envelope, rate limit 200/min/IP |
| CLI | cli/spbgit.mjs (commander) |
untouched by this plan |
| Tests | vitest: 6 unit files + 1 e2e (real git push round trip, releases) — 59/59 green at baseline; eslint, check:headers green |
no browser tests yet |
| Deploy | mld manifest M1M32:~/dispatch/apps/spbgit.json (pinned to M1M32), PM2 max_memory_restart 750M, nightly deploy/backup.sh |
ngrok removed 2026-09-10; deploy/ecosystem.config.cjs still documents a tunnel app (dead) |
Visibility model (CLAUDE.md §0): every repository is public, read-only; the only writes are owner PAT operations through the API/CLI. The web UI has no login and no authenticated action by design — "authenticated actions on a phone" therefore means: clone URLs, release downloads and the public API, all of which must work from Safari/iOS. This plan does not add a web login.
2. Feature inventory (existing — must keep working)
Home (hero, stats, pinned bento, repo grid with sparklines + client filters, activity feed, language ring, heatmap) · Repo overview (header, 6 stat tiles, language bar, tabs, branch select, clone box, zip/tar.gz, file table with last commit, README + TOC) · Tree · Blob (shiki, line anchors + ranges, wrap, copy, raw, blame, history, permalink, markdown render/source, images, PDF inline, binaries, notebooks as JSON) · Commits (grouped by day, stats, pagination, path filter) · Commit (diff with per-hunk highlighting, file nav, rename/binary) · Blame (age-coloured) · Branches (ahead/behind) · Tags/releases (assets with sha256) · Search (names/desc/topics/README) · ⌘K palette (repos + commands) · OG images, favicon, sitemap, robots, Atom · 404/500 · Smart HTTP · API v1 · releases upload · hooks · CLI.
3. Baseline measurements (2026-11-01, production, Playwright Chromium)
Mobile = 375×812, CPU ×4, ~1.6 Mb/s. Desktop = 1440×900, no throttling.
| Page | Mobile LCP | Mobile TTFB | Desktop LCP | HTML | CSS | JS | Fonts |
|---|---|---|---|---|---|---|---|
/ |
1 216 ms | 182 ms | 320 ms | 34 KB | 18.7 KB | 9 KB | 209 KB (5 files preloaded) |
/spbgit |
276 ms | 144 ms | 236 ms | 9.7 KB | cached | cached | cached |
blob src/server.mjs |
552 ms | 424 ms | 328 ms | 13 KB | |||
commit 810e402 |
1 612 ms | 1 463 ms | 1 336 ms | 121 KB |
CLS = 0 everywhere. Server timing on M1M32: / 64 ms warm, repo 58 ms,
/api/v1/repos 10 ms. 157 repos, cache 2.0 GB / 9 684 files.
Responsive audit (document.documentElement.scrollWidth at 375 px, Chromium +
WebKit): every page overflows horizontally (399 px in production, 377–383 px
locally). Causes found by bisection: footer grid 1fr track inheriting the
min-content of pre.footer-cmd (fix: minmax(0,1fr) / min-width:0), the
hero canvas positioned inset:-24px + width:calc(100%+48px), and the
repo tab strip. Baseline screenshots: docs/screenshots/baseline/.
4. Critical risks found in Phase 0
- Memory restart loop (production incident). PM2 restarted
spbgit-server2 861 times (exceeds --max-memory-restart, RSS up to 2.1 GB vs 750 MB limit). Every restart drops in-flight pushes/clones and makes the post-receive hook POST fail (hook has a 15 s timeout and swallows errors). Request mix before a restart: crawlers on/<repo>/blob/*and/raw/*of large files (phd_thesisPDFs).Repos.blob()buffers whole blobs (up to 64 MB) even when the view only needs size/type,/rawbuffers again; concurrency is unbounded. Fix in Phase 1 (reliability track): stream/raw, metadata-only blob probe, bounded git concurrency, cheaper/healthz. - Commit totals inconsistent ("all-time" 3 332 < "52 weeks" 3 346). Root
cause verified:
overview-v4.jsonis keyed by repo, not by HEAD, and is only invalidated by the hook; 8 repos were pushed while the server was restarting (risk 1) so their cachedcommitCountis stale (e.g.llm-api0 vs 5), while the heatmap is rebuilt from liverev-listwhenever any push clearsglobal/. Fix: validate overview cache against live HEAD (cheap ref stamp), derive both numbers from the same per-repo timestamp source, document metric definitions. - Unbounded logs. pino logs every request at info;
~/.pm2/logs/spbgit-server-out.logis 599 MB with no rotation. Fix: compact completion-only logging, exclude assets, documentpm2-logrotate. - Commit page cost. Per-hunk shiki highlighting on every request (1.2–1.5 s TTFB, 121 KB HTML). Fix: cache the prepared diff per sha, collapse large files.
- Mobile layout. Horizontal overflow on every page; touch targets < 44 px
(tabs 36 px,
.btn-sm28 px, copy buttons); desktop-only hover reveal on heading anchors;.hero-terminaland constellation canvas run on phones;100vhwithoutdvh; no safe-area insets; sticky header 64 px on phones. - Themes. Only dark/light; theme toggle is a 2-state button without an
"OS" mode; no
color-schememeta per theme. - Dead deploy config.
deploy/ecosystem.config.cjs+deploy/ngrok.ymlstill describe ngrok; CLAUDE.md names hostm3u96a. Production is M1M32 viamld+ MacLustr Tunnel. Documentation only — the running manifest is correct. - Fonts. 209 KB of preloaded fonts on first visit (5 files). Keep self-hosting but drop preloads for non-critical weights, subset usage.
5. Design direction (decisions)
- Identity kept: S mark, electric-blue→teal gradient, network motif (static
SVG/CSS, animated only on desktop without
prefers-reduced-motion). - Three themes:
light(soft blue-white#f4f6fb),dark(charcoal/slate#15181f),midnight(deep navy#070b16, electric blue, restrained turquoise — the successor of the v4 obsidian look). Default follows the OS (auto→ light/dark); explicit override persisted inlocalStorage(spbgit-theme=auto|light|dark|midnight), applied before first paint bytheme-boot.js, exposed in the header menu and the ⌘K palette. - Semantic tokens in
tokens.css: surfaces (--surface-0..3), foreground (--fg,--fg-muted,--fg-faint), borders, focus ring, status (--ok/--warn/--danger/--info), spacing scale 4/8 px (--space-1..10), radii, shadows, motion durations, syntax colours (shiki vars). WCAG AA checked per theme (contrast table in progress log). - Density: Linear-like. Compact sticky header (56 px desktop, 52 px phone),
compact
StatStripinstead of five hero tiles and six repo tiles, 1-column phone layouts, bottom-sheet dialogs on phones (palette, menu, branch picker). - Component library (Nunjucks macros in
src/web/views/components/+ matching CSS sections): AppShell, Header, MobileNavigation (sheet), Breadcrumbs, StatStrip, RepositoryCard, StatusChip, Tabs, SearchDialog, CodeViewer, FileTree, CommitTimeline, DiffViewer, EmptyState, LoadingSkeleton, ErrorState, Toast, ConfirmationDialog (JS, used by destructive CLI-less flows only if they ever appear; shipped as a primitive). - Typography: Inter (UI) / JetBrains Mono (code) / Bricolage Grotesque
(display, 700 + 800). Instrument Serif italic dropped (one less font).
font-variant-numeric: tabular-numson every statistic.
6. Phased milestones
| Phase | Scope | Acceptance |
|---|---|---|
| 0 Reconnaissance | this document, baseline numbers, screenshots, risks | done |
| 1 Design system + reliability P0 | tokens/themes, AppShell, header + mobile sheet, buttons/tabs/cards/chips, skeleton/empty/error, a11y foundations; streaming raw, blob probe, bounded concurrency, head-validated overview cache, compact logging, overflow test harness | tests green, no overflow at 320–1920, three themes, memory fix unit-tested |
| 2 Core screens | home, repo overview, catalog, source browser (tree + blob), clone panel, file tree sidebar on desktop | all old URLs 200, README/badges unchanged, screenshots at 9 widths |
| 3 Developer workflows | ⌘K with server-side search (repos/files/commits/branches), commits timeline, diff viewer (unified/split, wrap, cached), branch nav, analytics + metric definitions, keyboard/touch | search API tests, commit-count coherence test, Playwright mobile smoke |
| 4 Research Mode | opt-in research flag + auto-detection banner, /:repo/research: PDF, LaTeX sources, figures, tables, reproducibility checklist, outputs, version links |
no code execution, no LaTeX compile, tests on fixtures |
| 5 AI groundwork | src/ai/ provider-agnostic (Anthropic, OpenAI-compatible, local OpenAI-compatible e.g. MLX/llama.cpp), incremental file index, grounded Q&A with path@sha#Lx-Ly citations, prompt-injection wrapping, token/concurrency budgets, feature-flagged off by default |
disabled providers are absent, never simulated; tests use an explicit fake provider under SPBGIT_ENV=test |
| 6 Agent Workspace | design document only (isolation, scopes, approvals, audit) | docs/AGENT-WORKSPACE-DESIGN.md |
| 7 Hardening & release | cross-engine QA (Chromium + WebKit) at 320/360/375/390/430/768/1024/1440/1920, landscape, 200 % zoom, axe audit, perf re-measure, security regression tests, changelog, deploy + rollback runbook | all acceptance criteria below |
Deployment to M1M32 (git pull + pm2 restart) is not performed by the
implementer without explicit approval (directive: pause before production
changes).
7. Acceptance criteria (from the directive, made checkable)
npm testincludes the Smart HTTP round trip (clone/push/fetch) and passes; every pre-2.0 URL pattern is covered by a route test (home, repo, tree, blob, raw, archive, commits, commit, blame, branches, tags/releases, search, feeds, OG, API).- Playwright run at 320–430 px and desktop, in light/dark/midnight, asserts
scrollWidth === clientWidthon every core page and 44×44 px targets on primary controls. - Metric definitions documented (
docs/METRICS.md), unit test proving all-time ≥ 52-week on the same source, no placeholder data in production templates (demo fixtures only underdev/). - Search/file browsing: path traversal + ref validation tests; search index built only from repos on disk (all public).
- Markdown/diff/code: sanitizer tests (script/iframe/event handlers stripped, badges kept), diff whitespace preserved, keyboard-reachable actions.
- Automated tests + a manual Safari/iPhone checklist recorded in the progress log.
- AI/Research features behind flags;
/healthzreports feature flags. - Deliverables:
CHANGELOG.md, screenshots (docs/screenshots/), tests,docs/BENCHMARKS.md(before/after),docs/DEPLOY.md(deploy + rollback), remaining work list indocs/SPBGIT-2-PROGRESS.md.