SPB Git

spb/spbgit

Public MIT Pinned

SPB Git — the platform hosting itself

12.5 KB

# SPB Git 2.0 — Progress log

# Phase 0 — Reconnaissance (2026-11-01) ✅

Files added: docs/SPBGIT-2-PLAN.md, docs/SPBGIT-2-PROGRESS.md, docs/screenshots/baseline/*.png (home, repo, blob, commits, commit, search × 375/1440 × dark/light).

Method: read every source/template/CSS/test file, the deploy manifest on M1M32, PM2 state and logs; measured production with Playwright (Chromium) and WebKit; ran the existing test suite on a laptop mirror with 9 bare repos cloned from production plus a copy of meta.json/activity.jsonl/clones.json (dev/, git-ignored).

Baseline: tests 59/59, eslint clean, headers clean. Web vitals and server timings in the plan (§3). Production restarts: 2 861 (memory). Stale overview caches: 8 repos (countryatlas, hilmacorp-ca, ka-apify-actors, llm-api, maclustr-agentd, wp19_uqo, wp20_uqo, wp21_uqo).

Trade-offs: work happens on a laptop mirror (~/Desktop/Cluster/projets/spbgit, origin = spbgit on M1M32) because the production checkout is the live app; the node is updated only by git pull + pm2 restart after approval.

Open issues carried into Phase 1: risks 1–8 of the plan.

# Phase 1 — Design system + reliability P0 (2026-11-01) ✅

Files changed: src/web/assets/css/tokens.css (rewritten: semantic tokens, three themes, type/spacing/motion scales), src/web/assets/css/app.css (rewritten, mobile-first, 13 sections), src/web/assets/js/theme-boot.js (auto/light/dark/midnight before first paint), src/web/assets/js/app.js (rewritten: dialogs/sheets, theme, palette, toast, confirm, clipboard, file tree, code/diff helpers, TOC), src/web/views/layout.njk (AppShell), src/web/views/components/ui.njk (new: icon, chip, statStrip, tabs, breadcrumbs, refSelect, emptyState, errorState, skeleton, copyButton), src/lib/semaphore.mjs (new), src/git/repo.mjs (git semaphore, blobInfo, blobStream, blob({maxBytes}), refStamp, allPaths, grepCommits), src/lib/overview.mjs (stamp-validated cache overview-v5.json, clones read once, research detection), src/stats/activity.mjs (stamp-validated calendar with allTime), src/server.mjs (compact completion-only logging via LogController, memoized /healthz with memory/git/feature fields), src/config.mjs (AI flags, off by default).

Trade-offs: Instrument Serif dropped (one font less, 1 preload less); the constellation canvas and typewriter are gone (static terminal card on desktop only) — decorative motion is now limited to a slow mesh drift on ≥768 px without prefers-reduced-motion. git subprocesses are capped at 12 concurrent (SPBGIT_GIT_CONCURRENCY); crawlers now queue instead of growing RSS. Native <dialog> is used for the palette, menu, clone panel and file tree (bottom sheets on phones, modals on desktop) — requires Safari ≥ 15.4.

Tests: test/unit/semaphore.test.mjs (2), test/unit/routes-helpers.test.mjs (8: split rows, day grouping, research signals, sparkline, query terms, stamps), e2e extended (+6: scoped search API, tree API + traversal, 3 MB binary streamed from /raw with exact bytes + metadata-only blob view, missed-hook detection with coherent totals, split/history/blame, healthz flags). 74/74 green, eslint + headers clean.

# Phase 2 — Core screens (2026-11-01) ✅

Files changed: src/web/views/{home,repo,tree,blob,commits,commit,blame,branches,tags,search,error}.njk (all rewritten on the component library), partials/{repo-header,repo-card,file-table}.njk (rewritten), partials/{clone-panel,tree-sheet}.njk (new), views/metrics.njk (new /about/metrics), src/web/routes.mjs (rewritten: streamed /raw, metadata-only blob probe, cached highlighted blobs/markdown/blame, cached prepared diff per sha commit-v2.json, split view, large diffs not loaded by default, /about/metrics, scoped search page), src/api/v1.mjs (/api/v1/search, /api/v1/repos/:name/tree, richer /api/v1/stats, research flag on PATCH), src/lib/search.mjs (rewritten: files, refs, commits, stamp-validated index search-index-v2.json).

Responsive QA (scripts/qa/screens.py, Playwright Chromium, 13 pages × 320/360/375/390/430/768/1024/1440/1920, light/dark/midnight): 0 horizontal overflow, 0 essential control under 44×44 px on touch viewports (was: every page overflowing at 375 px). Screenshots: docs/screenshots/v2/ (375 + 1440, midnight + light; full run archived outside the repo).

Open issues carried: commit pages ship every file's rows in one document (3 MB uncompressed for the 60-file v4 commit; ~120 KB over the gzip edge) — per-file lazy loading is listed as remaining work; WebKit run and 200 % zoom are done in Phase 7.

# Phase 3 — Developer workflows (2026-11-01) ✅

Files changed: src/web/assets/js/app.js (⌘K: scoped server search with debounce, recent queries, keyboard/touch navigation, repo-context commands), src/lib/search.mjs + src/api/v1.mjs (files/commits/branches, index status), src/web/views/commit.njk + src/web/routes.mjs (unified/split, wrap, page line budget with deferred files, cached prepared diff), src/insights/insights.mjs

  • src/web/views/insights.njk (per-repository analytics: 52-week calendar, contributors, hottest paths, declared dependencies from manifests — "dependency graph where data supports it" is delivered as a dependency list with registry links; a true graph needs lockfile resolution and is listed as remaining work), src/web/views/metrics.njk (/about/metrics).

Metric coherence: test/e2e/roundtrip.test.mjs disables the hook, pushes a commit and asserts stats.commits and stats.commitsLast52Weeks both move by exactly one and that all-time ≥ 52-week. Root cause and definitions are documented on /about/metrics and in the plan (§4.2).

Tests: unit routes-helpers, research-insights; e2e search/tree/split.

# Phase 4 — Research Mode (2026-11-01) ✅

Files: src/research/profile.mjs (pure classifier + LaTeX metadata parser, no compilation), src/web/views/research.njk, route /:repo/research, research flag on PATCH /api/v1/repos/:name, auto-detection in lib/overview.mjs (researchDetected) with a banner on the repository page. The page reads at most 256 KB of each LaTeX candidate and never executes anything; PDFs are rendered by the browser from /raw. Verified on wp20_uqo (31 PDFs, 2 main documents, 127 figures).

# Phase 5 — AI groundwork (2026-11-01) ✅ (opt-in, disabled by default)

Files: src/ai/index.mjs (chunking, per-blob cache, BM25), src/ai/ask.mjs (service, budget, concurrency, prompt-injection wrapping, citation verification), src/ai/providers/{anthropic,openai-compatible,fake}.mjs, src/config.mjs (SPBGIT_AI_*), API routes (/api/v1/ai/status, POST /api/v1/repos/:name/ask, POST /api/v1/repos/:name/commits/:sha/summary, 12 req/min/IP), ask card on the repository page and "Summarize" on commits (rendered only when enabled). Anthropic provider uses the official SDK (claude-opus-5-5 default, server-side refusal fallbacks, refusal stop reason surfaced as an error). No provider is simulated: without configuration the routes 404 and the UI shows nothing; the fake provider is refused outside SPBGIT_ENV=test (unit-tested).

Not done (needs credentials/infra): embeddings — retrieval is lexical (BM25) by design so that nothing leaves the server until a question is asked.

# Phase 6 — Agent Workspace (2026-11-01) ✅ design only

docs/AGENT-WORKSPACE-DESIGN.md: scoped job tokens + pre-receive hook, sandboxed runner on a separate node, approval gates, audit log, acceptance criteria. No code shipped — the preconditions (token scopes, runner) do not exist yet.

# Phase 7 — Hardening & release (2026-11-01) ✅ (deployment pending approval)

  • Cross-engine QA: scripts/qa/screens.py — Chromium 13 pages × 320/360/375/390/430/768/1024/1440/1920 × light/dark/midnight: 0 overflow, 0 small target; WebKit 375/1440: 0; 200 % zoom: 0; landscape 812×375: 0.
  • Accessibility: scripts/qa/axe.py (WCAG 2.x A/AA + best-practice) — from 142 violation groups on the first 2.0 build to 0 on home, repo, commits, branches, search, metrics, 404 after fixes (list semantics for the stat strip, keyboard-focusable scroll regions, contrast tokens, underlined inline links, role="img" on the pin). Code pages: shiki themes switched to github-light-high-contrast / github-dark-default so every syntax token meets AA, and gutters on changed lines use the full foreground. Final run (docs/screenshots/v2/axe-report.json): 0 violations on 9 pages × 3 themes × 375/1440.
  • Performance: docs/BENCHMARKS.md (in-process brotli/gzip added; commit page 3.0 MB → 55 KB wire, 679 ms → 44 ms server).
  • Security regression tests: CSP + frame + nosniff headers, no inline scripts, repo HTML served as text from /raw with a sandbox CSP, hostile README (onerror/script/javascript:) neutralized while badges survive, loopback-only hook with repo validation, traversal on blob/raw/tree/search.
  • Docs: CHANGELOG.md, docs/DEPLOY.md (deploy + rollback), README.md and CLAUDE.md updated for 2.0.

# Manual Safari / iPhone checklist (to run on production after deploy)

Check Expected
Open https://git.spboucher.ai on iPhone Safari, portrait header 52 px, no horizontal scroll, stat strip swipes, theme follows iOS appearance
Switch Light/Dark/Midnight from the menu sheet, reload choice persists; status bar colour matches
Repository → Clone bottom sheet; URL selectable; Copy shows a toast; ZIP downloads
Repository → Files tree sheet; folders expand lazily; current file highlighted
Blob wrap on by default; line-number tap sets #L hash; Permalink copies a sha URL
Commit deferred files load with "Load diff"; wrap toggle; split hidden on phone
⌘K (search icon) keyboard opens without zoom (16 px input), scopes scroll, results open on tap
Rotate to landscape no overflow, header stays reachable
Dynamic Type at the largest accessibility size no clipped controls
VoiceOver skip link, landmarks, dialog labels, list semantics of the stat strip

# Remaining work (not done, in priority order)

  1. Deploy to M1M32 (docs/DEPLOY.md) and re-run the production measurements (LCP/INP on real iPhone, restart count after 24 h).
  2. Per-file lazy loading of diffs over the API (today: page budget + ?all=1).
  3. True dependency graph (lockfile resolution, cross-repository edges).
  4. Embedding-based retrieval behind the same provider abstraction (requires a credentialed embeddings endpoint; BM25 stays the default).
  5. Agent Workspace preconditions (scoped tokens + pre-receive hook).
  6. Playwright tests in the Node test runner (today Python Playwright scripts are the browser QA; vitest covers server/API).
  7. deploy/ecosystem.config.cjs / deploy/setup-m3u96a.sh still describe the pre-mld (ngrok) deployment; production is driven by the mld manifest. Documentation debt only.

# Deployment — 2026-11-01 03:10 ✅

Pushed 2b8daed to spbgit and deployed on M1M32 (git pull --ff-only, npm ci --omit=dev, fonts, pm2 restart spbgit-server). First home request rebuilt 160 overviews in 15.6 s, then 28 ms. Public checks through the tunnel: /healthz 2.0.0 (RSS 358 MB, git 0/12), home 121 ms / 33 KB br, repo 275 ms, blob 535 ms, commit 1.4 s first hit then cached, /api/v1/search 71 ms, /api/v1/stats commits 3 379 = last-52-weeks 3 379 (coherent), raw file keeps Content-Length, Smart HTTP advertisement OK, responsive QA on production 375/1440 × light/midnight: 0 failures. Mobile vitals (throttled): home LCP 1 592 ms, repo 184 ms, blob 320 ms, commit 192 ms, commits 376 ms, CLS 0. Also installed pm2-logrotate (50 MB × 7, compressed), flushed the 599 MB log, removed 163 obsolete 1.1.0 cache files. AI remains disabled (features.ai: false). Restart counter baseline after deploy: 1 108 — check again after 24 h.

Files · main
Menu

Theme