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 togithub-light-high-contrast/github-dark-defaultso 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
/rawwith 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.mdandCLAUDE.mdupdated 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)
- Deploy to M1M32 (
docs/DEPLOY.md) and re-run the production measurements (LCP/INP on real iPhone, restart count after 24 h). - Per-file lazy loading of diffs over the API (today: page budget +
?all=1). - True dependency graph (lockfile resolution, cross-repository edges).
- Embedding-based retrieval behind the same provider abstraction (requires a credentialed embeddings endpoint; BM25 stays the default).
- Agent Workspace preconditions (scoped tokens + pre-receive hook).
- Playwright tests in the Node test runner (today Python Playwright scripts are the browser QA; vitest covers server/API).
deploy/ecosystem.config.cjs/deploy/setup-m3u96a.shstill describe the pre-mld (ngrok) deployment; production is driven by themldmanifest. 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.