SPB Git

spb/spbgit

Public MIT Pinned

SPB Git — the platform hosting itself

12.8 KB

# 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

  1. Memory restart loop (production incident). PM2 restarted spbgit-server 2 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_thesis PDFs). Repos.blob() buffers whole blobs (up to 64 MB) even when the view only needs size/type, /raw buffers again; concurrency is unbounded. Fix in Phase 1 (reliability track): stream /raw, metadata-only blob probe, bounded git concurrency, cheaper /healthz.
  2. Commit totals inconsistent ("all-time" 3 332 < "52 weeks" 3 346). Root cause verified: overview-v4.json is 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 cached commitCount is stale (e.g. llm-api 0 vs 5), while the heatmap is rebuilt from live rev-list whenever any push clears global/. Fix: validate overview cache against live HEAD (cheap ref stamp), derive both numbers from the same per-repo timestamp source, document metric definitions.
  3. Unbounded logs. pino logs every request at info; ~/.pm2/logs/spbgit-server-out.log is 599 MB with no rotation. Fix: compact completion-only logging, exclude assets, document pm2-logrotate.
  4. 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.
  5. Mobile layout. Horizontal overflow on every page; touch targets < 44 px (tabs 36 px, .btn-sm 28 px, copy buttons); desktop-only hover reveal on heading anchors; .hero-terminal and constellation canvas run on phones; 100vh without dvh; no safe-area insets; sticky header 64 px on phones.
  6. Themes. Only dark/light; theme toggle is a 2-state button without an "OS" mode; no color-scheme meta per theme.
  7. Dead deploy config. deploy/ecosystem.config.cjs + deploy/ngrok.yml still describe ngrok; CLAUDE.md names host m3u96a. Production is M1M32 via mld + MacLustr Tunnel. Documentation only — the running manifest is correct.
  8. 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 in localStorage (spbgit-theme = auto|light|dark|midnight), applied before first paint by theme-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 StatStrip instead 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-nums on 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)

  1. npm test includes 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).
  2. Playwright run at 320–430 px and desktop, in light/dark/midnight, asserts scrollWidth === clientWidth on every core page and 44×44 px targets on primary controls.
  3. 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 under dev/).
  4. Search/file browsing: path traversal + ref validation tests; search index built only from repos on disk (all public).
  5. Markdown/diff/code: sanitizer tests (script/iframe/event handlers stripped, badges kept), diff whitespace preserved, keyboard-reachable actions.
  6. Automated tests + a manual Safari/iPhone checklist recorded in the progress log.
  7. AI/Research features behind flags; /healthz reports feature flags.
  8. Deliverables: CHANGELOG.md, screenshots (docs/screenshots/), tests, docs/BENCHMARKS.md (before/after), docs/DEPLOY.md (deploy + rollback), remaining work list in docs/SPBGIT-2-PROGRESS.md.
Files · main
Menu

Theme