# Discovery notes — git-diff-panel implementation Single source of truth for project context. Specialists: read this first; do NOT re-grep what is here. Source spec: `../feature-specification.md` (+ `decision-log.md` D1–D18, `team-findings.md` F1–F21). No `feature-technical-notes.md` (no load-bearing mechanic qualified at spec time). ## Tech stack - pnpm monorepo. `apps/web` (React 18 + Vite SPA), `apps/server` (BooChat — Fastify + postgres, native inference, **read-only** file/git tools), `apps/coder` (BooCoder — host systemd service, port 9502, write-capable, runs git writes today), `apps/booterm`. TS strict, NodeNext (`.js` import suffixes) on server + coder. `@boocode/contracts` package single-sources WS frames + provider/message types. - Tests: vitest ^3. server `pnpm -C apps/server test`; coder `pnpm -C apps/coder test` (`globals:false` — import describe/it/expect). Include glob `src/**/__tests__/**/*.test.ts`. No web test harness. DB-integration tests opt-in via `DATABASE_URL` + `describe.runIf`. - Deploy by surface: apps/coder change → `sudo systemctl restart boocoder`; apps/web|server change → `docker compose up --build -d boocode` (rebuilds web+server from working tree; web HMR live on dev only). - Shiki `^1.29.2` already in apps/web (`CodeBlock.tsx`, `FileViewerOverlay.tsx`); `lang:'diff'` is valid — the path of least resistance for rendering a unified diff. No react-diff-viewer / diff2html installed. ## ADRs / coding standards - No `docs/adr/`. Decisions live in `boocode_roadmap.md` (Decisions log) + per-app `CLAUDE.md` (auto-loaded when editing that subtree) + `openspec/changes/archived/`. - Coding standards: `docs/coding-standards/` (canonical), surfaced via `.claude/rules/coding-standards/` path-scoped indexes. Cross-cutting conventions in root `CLAUDE.md`. ## Code touch points ### Git data sources today (read) - `apps/server/src/services/git_meta.ts:44` `getGitMeta(rootPath)` — runs `runGit([...argv], rootPath)` (SAFE: discrete argv, no shell), returns `{branch,is_dirty,ahead,behind}` only (NO diff text), 30s cache. This is the read-side precedent for the new read route and the F2 argv-safety bar. Uses `rev-list --left-right --count HEAD...@{upstream}` (the upstream-resolution precedent for D2/D13's base). - `apps/server/src/routes/projects.ts:426` `GET /api/projects/:id/git` — returns the GitMeta shape (no diff). `api.projects.git(id)` (`apps/web/src/api/client.ts:154`), polled 30s by `useProjectGit`. The new read route slots beside this. - `apps/coder/src/services/worktrees.ts:46` `diffWorktree(worktreePath, projectPath, {baseRef})` — produces a real unified `git diff ...` but via `hostExec(shell string)` + `shellEscape`, and commits with per-invocation `-c user.email=boocoder@local -c user.name=BooCoder`. **Caution:** this is the SHELL pattern F2 warns against; the new git-write ops should follow `git_meta.ts`'s argv `runGit`, not this. But it IS the precedent that the coder/host can run git writes and inject identity per-invocation. - `pending_changes.diff` is unified only for external-agent edits; native BooCode edits store `{old,new}` JSON. The new Git panel is project-repo git state, complementary to the pending-changes panel (spec Coordinations). ### The "file browser" host (apps/web) - `apps/web/src/components/RightRail.tsx` — the right-side file panel (NOT a workspace pane; the legacy `file_browser` pane kind is dead). Header at ~:209 renders a static "Files" label + 2 icon buttons; desktop `w-64`, mobile drawer `w-[85vw] max-w-sm` via `useRightRailDrawer`. Already applies `max-md:min-h-[44px]` to header buttons (the 44px convention for D18). Fetches tree via `api.projects.files/listDir/viewFile`. **The Files / Git tab (D1, D16) is added to THIS header** — and must fit one line (toolbar-fit rule, D18). `open_file_in_browser` sessionEvent already opens the panel programmatically. - Rendered in `App.tsx:~89` for every `/session/:id` (so the panel — and D8's Git tab — appears in all session types). `Session.tsx:~397` mobile `FolderTree` toggle button (the dirty-indicator host for D17). ### Refresh-trigger plumbing (F20, D10) - `message_complete` WS frame = "agent turn complete" trigger. Coder pending-changes refresh precedent: `usePendingChanges` in `CoderPane.tsx:~786` refetches on message-complete. Pending-apply/discard has no named frame — driven by the `refresh()` callback. Adding a new event/frame requires the CLAUDE.md parity steps: a new WS frame → BOTH server/contracts `WsFrameSchema` AND web `WsFrame` (`apps/web/src/api/types.ts`); a new sessionEvent → a `case` in `useSidebar.ts` `applyEvent`. ### Security surfaces - `apps/server/src/services/path_guard.ts` `resolveProjectRoot(project.path)` — derives + scopes project paths from the DB project row, never from the request (the F2 "server-derived root + relative-arg validation" precedent). `secret_guard.ts` deny-list applies to the assistant's read tools (not the user's git panel — spec D8). HTML artifacts run in a sandboxed iframe with `connect-src 'none'` (BOOCHAT.md) — the evidence behind F1 (an artifact cannot POST to the new write endpoints). No app-layer auth (Authelia at the proxy; `'default'` user key). - BooCoder proxy: apps/server forwards `/api/coder/*` to apps/coder (`coder-proxy.ts`) — the route by which a web client reaches coder (host) endpoints. ## Recent activity / precedent - HEAD ~v2.7.11. Pure-helper + TDD precedent: `backends/turn-guard.ts`, `lifecycle-decisions.ts`, `mistake-tracker.ts` (pure module + unit test, then wire). The diff-parse / base-resolution / mode-decision logic should follow it (testable pure helpers). - Sibling backlog plan at `docs/plans/post-review-backlog/` is the format precedent for the plan files. ## Enumerated gaps / open implementation questions for the team 1. **THE architecture decision (F18 / JD-005):** which service owns the new git operations? Options: (a) all in apps/server (read + write) — but apps/server is "read-only" by posture (the git-write would be a new write surface there); (b) read in apps/server (consistent with git_meta), write in apps/coder (the write-capable host service, already runs git writes) via the `/api/coder/*` proxy; (c) all in apps/coder. Note apps/coder runs on the host (can git-write to the project path); apps/server runs in Docker. The diff panel appears in ALL sessions incl. plain BooChat — does that constrain which service answers? software-architect to settle. 2. No HTTP route returns a full working-tree git diff today — a new read route is needed regardless. 3. The diff-parse + per-file expand/collapse + staged/unstaged grouping + Shiki `lang:'diff'` rendering is net-new UI in `RightRail.tsx`; no unified-diff renderer exists yet. 4. F2 argv-safety: the new write path must use discrete-argv git (like `git_meta.runGit`), NOT the `hostExec(shell)` pattern `worktrees.ts` uses — concrete bar for the security/structural recommendation. 5. Committer identity (D6/D12/F3): server-derived. Precedents differ — `worktrees.ts` injects `-c user.email=boocoder@local`; `project_bootstrap.ts` hardcodes `samkintop@gmail.com`. The plan must pick the source for a USER commit (host git config vs a configured value) — not request-supplied. 6. Base resolution (D2/D13): `@{upstream}` precedent exists in git_meta; default-branch fallback unspecified in code (needs `git symbolic-ref refs/remotes/origin/HEAD` or `rev-parse --abbrev-ref origin/HEAD`).