# Enhanced File Panel — Implementation Plan

## TL;DR

> **Quick Summary**: Add side-by-side diff, hide whitespace, wrap lines, expand all files, inline diff comments, and in-browser file editing to BooCode's right-rail file panel.
>
> **Deliverables**:
> - Enhanced `GitDiffView.tsx` with toolbar (layout/whitespace/wrap/expand-all toggles)
> - Split-layout diff renderer (side-by-side)
> - `useDiffPreferences` hook (localStorage persistence)
> - Inline diff comment components + Zustand store
> - File editing mode in file tree + server write endpoint
> - Server `git diff -w` support
>
> **Estimated Effort**: Medium-Large
> **Parallel Execution**: YES — 4 waves
> **Critical Path**: Wave 1 (server) → Wave 2 (diff preferences + toolbar) → Wave 3 (split layout) → Wave 4 (comments + editing)

---

## Context

### Original Request
User wants to implement these features from Paseo into BooCode's file manager:
1. Unified diff ✅ (exists) / Side by side diff ❌
2. Hide whitespace ❌
3. Wrap long lines ❌
4. Expand all files ❌ (only per-file)
5. Refresh ✅ (exists)
6. Comments on specific diffs ❌
7. File edits (editing in the file browser) ❌

### Research Findings
- **Paseo** (`/opt/forks/paseo`): Best reference for all features. Key files: `diff-pane.tsx`, `diff-layout.ts`, `diff-rendering.ts`, `review/surface.tsx`, `review/store.ts`, `use-changes-preferences/`
- **Existing BooCode files**: `GitDiffView.tsx`, `RightRail.tsx`, `useGitDiff.ts`, `git_diff.ts`, `FileViewerOverlay.tsx`
- Key insight: None of the web references have true inline file editing in the browser — this is new ground

---

## Work Objectives

### Core Objective
Augment the existing file panel with side-by-side diff, whitespace/wrap/expand toggles, inline comments, and inline file editing.

### Definition of Done
- [x] `pnpm -C apps/web build` succeeds with no errors
- [x] `pnpm -C apps/server build` succeeds with no errors
- [ ] Side-by-side diff renders correctly (two aligned columns)
- [ ] Hide whitespace toggles and re-fetches diff
- [ ] Wrap lines toggles between pre / pre-wrap
- [ ] Expand/Collapse all toggles all file diffs
- [ ] Inline comments: click gutter → type → save → display thread
- [ ] File edit: double-click tree → edit → save → file changes on disk
- [ ] All preferences persist across page refresh

### Must Have
- Side-by-side diff view
- Hide whitespace toggle (server param)
- Wrap long lines toggle (CSS)
- Expand/Collapse all file diffs
- Inline diff comments with thread UI
- In-browser file editing with save
- Preference persistence

### Must NOT Have (Guardrails)
- No DB migration (comments are client-side)
- No new WS frames (reuse git_diff_refresh)
- No new `@boocode/contracts` types
- No multi-user comment sharing
- No git push/pull/PR operations
- No inline hunk staging

---

## Verification Strategy

### Test Decision
- **Infrastructure exists**: YES (vitest for server)
- **Automated tests**: Tests-after for new server route + `git_diff.ts` changes
- **Agent-Executed QA**: Playwright for diff interactions, curl for API endpoints

### QA Policy
Every task includes agent-executed scenarios. Evidence saved to `.omo/evidence/`.

---

## Execution Strategy

### Waves

```
Wave 1 (Server — foundation):
├── Task 1: Server: whitespace param in git_diff.ts
├── Task 2: Server: POST /api/projects/:id/write_file endpoint
├── Task 3: Server tests for whitespace + write
└── [tests + typecheck]

Wave 2 (Frontend — preferences + toolbar):
├── Task 4: useDiffPreferences hook (localStorage)
├── Task 5: GitDiffView toolbar (layout/whitespace/wrap/expand-all toggles)
├── Task 6: Wrap lines CSS + hide whitespace re-fetch
└── [pnpm build]

Wave 3 (Frontend — split layout):
├── Task 7: Diff layout utilities (buildSplitDiffRows etc.)
├── Task 8: Side-by-side renderer in GitDiffView
├── Task 9: Line number gutter + alignment
└── [pnpm build]

Wave 4 (Frontend — comments + file editing):
├── Task 10: InlineComment store (Zustand + localStorage)
├── Task 11: InlineReviewGutterCell + InlineReviewEditor
├── Task 12: InlineReviewThread (comment display)
├── Task 13: File editing mode in RightRail file tree
└── [pnpm build + full smoke test]
```

Critical Path: T1 → T2 → T4 → T5 → T7 → T8 → T10 → T11 → T12 → T13

---

## TODOs

- [x] 1. **Server: Add `ignoreWhitespace` param to git diff**

  **What to do**:
  - In `apps/server/src/services/git_diff.ts`, add `ignoreWhitespace?: boolean` to the `getGitDiff` function signature
  - When `ignoreWhitespace` is true, append `'-w'` to the git diff argv call in `getGitDiff` (the main diff command, not name-status)
  - Update `GET /api/projects/:id/git/diff` route in `routes/projects.ts` to accept optional query param `whitespace=1`
  - The param should be optional (backward compatible) — default false

  **Files to modify**:
  - `apps/server/src/services/git_diff.ts` — update `getGitDiff()` to accept and use `ignoreWhitespace`
  - `apps/server/src/routes/projects.ts` — add `whitespace` query param

  **References**:
  - Paseo: `useCheckoutDiffQuery({ ignoreWhitespace })` passes to server → `git diff -w`
  - Existing `git_diff.ts:36-48` `runGit` function — argv pattern to follow

  **QA Scenarios**:
  ```
  Scenario: Diff with whitespace changes respects ignoreWhitespace param
    Tool: Bash (curl)
    Preconditions: A file exists with whitespace-only changes (extra spaces)
    Steps:
      1. GET /api/projects/:id/git/diff ⇒ verify diff_body includes whitespace changes
      2. GET /api/projects/:id/git/diff?whitespace=1 ⇒ verify diff_body excludes whitespace-only changes
    Expected: With whitespace=1, files that only had whitespace changes show as unchanged
    Evidence: .omo/evidence/task-1-whitespace.txt
  ```

- [x] 2. **Server: Add POST /api/projects/:id/write_file endpoint**

  **What to do**:
  - Add `POST /api/projects/:id/write_file` route in `routes/projects.ts`
  - Accept `{ path: string, content: string }` body
  - Validate path via existing `pathGuard` helper (same as git discard)
  - Write file content atomically: write to `.tmp` then `rename` the file
  - Return `{ ok: boolean }` on success
  - Reuse the safe file-write pattern from `services/file_ops.ts`

  **Files to modify**:
  - `apps/server/src/routes/projects.ts` — add POST route
  - `apps/web/src/api/client.ts` — add `writeFile` method
  - `apps/web/src/api/types.ts` — add write types if needed

  **References**:
  - `apps/server/src/services/file_ops.ts` — existing file operations pattern
  - `apps/server/src/routes/projects.ts:544-592` — git write routes (same security pattern)
  - `apps/server/src/services/path_guard.ts` — path validation

  **QA Scenarios**:
  ```
  Scenario: Write file content and verify on disk
    Tool: Bash (curl)
    Preconditions: A project exists with a writable path
    Steps:
      1. POST /api/projects/:id/write_file { path: "test.txt", content: "hello" }
      2. GET /api/projects/:id/view_file?path=test.txt
    Expected: Status 200, view_file returns "hello"
    Evidence: .omo/evidence/task-2-write.txt
  ```

- [x] 3. **Frontend: useDiffPreferences hook**

  **What to do**:
  - Create `apps/web/src/hooks/useDiffPreferences.ts`
  - Define `DiffPreferences` interface: `{ layout: 'unified'|'split', wrapLines: boolean, hideWhitespace: boolean }`
  - Default: `{ layout: 'unified', wrapLines: false, hideWhitespace: false }`
  - Read/write to localStorage key `boocode.diff.preferences`
  - Return `{ preferences, updatePreferences, resetPreferences }`
  - Zod-validate on read for forward compatibility

  **Files to create/modify**:
  - Create `apps/web/src/hooks/useDiffPreferences.ts`

  **References**:
  - `/opt/forks/paseo/packages/app/src/hooks/use-changes-preferences/storage.ts` — exact pattern
  - `apps/web/src/hooks/useProjectGit.ts` — hooks pattern in BooCode

  **QA Scenarios**:
  ```
  Scenario: Preferences persist across page refresh
    Tool: Playwright
    Preconditions: Page loaded
    Steps:
      1. Call updatePreferences({ layout: 'split' })
      2. Read localStorage.getItem('boocode.diff.preferences')
      3. Reload page, read preferences again
    Expected: layout is 'split' after reload
    Evidence: .omo/evidence/task-3-prefs.txt
  ```

- [x] 4. **Frontend: GitDiffView toolbar with all toggles**

  **What to do**:
  - Add a toolbar row inside `GitDiffView.tsx` between the mode selector and file list
  - Controls (left to right):
    - **Layout toggle**: two-segment button (Unified | Split) — uses `AlignJustify` / `Columns2` icons
    - **Hide whitespace**: toggle button — `Pilcrow` icon, active state highlights
    - **Wrap lines**: toggle button — `WrapText` icon  
    - **Expand/Collapse all**: toggle button — `ListChevronsUpDown` / `ListChevronsDownUp` icons
    - **Refresh**: existing button (already present)
  - Wire each toggle to the `useDiffPreferences` hook
  - Expand all state: compute `allExpanded = files.every(f => expandedPaths.has(f.path))`
  - Pass expand state as a new prop or local state

  **Files to modify**:
  - `apps/web/src/components/GitDiffView.tsx` — add toolbar section, expand-all logic

  **References**:
  - Paseo `diff-pane.tsx:1114-1273` — `DiffLayoutToggleGroup`, `DiffWhitespaceToggle`, `DiffFilesToolbar`
  - openchamber `DiffViewToggle.tsx` — simple toggle pattern
  - happy `InlineFileDiff.tsx:196-219` — `DiffStyleToggle` segment control

  **QA Scenarios**:
  ```
  Scenario: All toolbar controls render and toggle
    Tool: Playwright
    Preconditions: Git tab active with changed files
    Steps:
      1. Verify layout toggle shows "Unified" / "Split" buttons
      2. Click "Split" — verify visual change
      3. Click "Wrap" — verify wrap toggle
      4. Click "Expand all" — verify all files expand
      5. Click "Collapse all" — verify all files collapse
    Expected: Each toggle works and updates state
    Evidence: .omo/evidence/task-4-toolbar.png
  ```

- [x] 5. **Frontend: Diff layout utilities + side-by-side renderer**

  **What to do**:
  - Create `apps/web/src/utils/diff-layout.ts` with pure functions:
    - `buildNumberedDiffHunks(diffBody: string): NumberedDiffHunk[]` — parse diff text into hunks with old/new line numbers
    - `buildUnifiedDiffLines(file): UnifiedDiffDisplayLine[]` — existing behavior
    - `buildSplitDiffRows(file): SplitDiffRow[]` — pair removals/additions into left/right rows
  - Create `apps/web/src/components/DiffSplitView.tsx` — the side-by-side renderer:
    - Two columns (left = deletions, right = additions) with a thin divider
    - Each column has its own gutter (line numbers) + code content
    - Use Shiki `codeToHtml(language)` for syntax highlighting per side
    - Handle empty cells (unpaired lines render as blank)
  - In `GitDiffView.tsx`, when `layout === 'split'`, render `DiffSplitView` instead of the unified diff body

  **Files to create/modify**:
  - Create `apps/web/src/utils/diff-layout.ts`
  - Create `apps/web/src/components/DiffSplitView.tsx`
  - Modify `apps/web/src/components/GitDiffView.tsx` — add layout branching

  **References**:
  - `/opt/forks/paseo/packages/app/src/utils/diff-layout.ts` — full algorithm
  - `/opt/forks/paseo/packages/app/src/git/diff-pane.tsx:968-989` — split layout rendering
  - existing `git_diff.ts` `splitDiffByFile` — already splits unified diff per file

  **QA Scenarios**:
  ```
  Scenario: Side-by-side diff renders correctly
    Tool: Playwright
    Preconditions: Git tab active, files with changes
    Steps:
      1. Click "Split" layout toggle
      2. Verify two columns appear with a divider
      3. Verify deleted lines are on left side (red background)
      4. Verify added lines are on right side (green background)
      5. Verify context lines appear on both sides, aligned
    Expected: Layout matches Paseo's split diff
    Evidence: .omo/evidence/task-5-splitdiff.png
  ```

- [x] 6. **Frontend: Inline comment store + Zustand**

  **What to do**:
  - Create `apps/web/src/stores/useDiffCommentStore.ts`
  - Define `DiffComment` interface: `{ id, filePath, side, lineNumber, body, createdAt, updatedAt }`
  - Create Zustand store with:
    - `commentsByKey: Map<string, DiffComment[]>` keyed by `${sessionId}:${mode}:${filePath}`
    - `addComment(key, comment)` / `updateComment(key, id, body)` / `deleteComment(key, id)`
    - `loadComments(key)` — load from localStorage
    - `persist()` — subscribe to store changes, write to localStorage key `boocode.diff.comments.[key]`
  - Export `useDiffCommentStore`

  **Files to create**:
  - Create `apps/web/src/stores/useDiffCommentStore.ts`

  **References**:
  - `/opt/forks/paseo/packages/app/src/review/store.ts` — zustand store for comments
  - `/opt/forks/paseo/packages/app/src/review/state.ts` — CRUD operations

  **QA Scenarios**:
  ```
  Scenario: Comments persist across page refresh
    Tool: Playwright
    Preconditions: Diff panel open with changes
    Steps:
      1. Add comment on a diff line
      2. Verify comment thread appears
      3. Reload page
      4. Navigate to same diff
    Expected: Comment thread still visible after reload
    Evidence: .omo/evidence/task-6-comment-store.txt
  ```

- [x] 7. **Frontend: InlineReviewGutterCell + InlineReviewEditor**

  **What to do**:
  - Create `apps/web/src/components/InlineReviewGutterCell.tsx`:
    - Replaces the plain line-number display in diff rows
    - Shows line number + "+" icon on hover (to start a comment)
    - Uses `ReviewableDiffTarget { filePath, side, lineNumber }` for tracking
  - Create `apps/web/src/components/InlineReviewEditor.tsx`:
    - Textarea with placeholder "Add comment..."
    - Save (Ctrl+Enter) / Cancel (Escape) buttons
    - Animates in below the target line
  - Integrate into `GitDiffView.tsx` — gutter cells render in the diff line view
  - Wire to `useDiffCommentStore`

  **Files to create/modify**:
  - Create `apps/web/src/components/InlineReviewGutterCell.tsx`
  - Create `apps/web/src/components/InlineReviewEditor.tsx`
  - Modify `apps/web/src/components/GitDiffView.tsx` — integrate gutter cells

  **References**:
  - Paseo `review/surface.tsx:245-309` — `DiffGutterCell` + `InlineReviewGutterCell`
  - Paseo `InlineReviewEditor` pattern

  **QA Scenarios**:
  ```
  Scenario: Create inline comment on diff line
    Tool: Playwright
    Preconditions: Git tab, file expanded
    Steps:
      1. Hover over a gutter cell
      2. Click "+" button
      3. Type comment text
      4. Click Save (or Ctrl+Enter)
    Expected: Comment thread appears below the line
    Evidence: .omo/evidence/task-7-comment-create.png
  ```

- [x] 8. **Frontend: InlineReviewThread component**

  **What to do**:
  - Create `apps/web/src/components/InlineReviewThread.tsx`:
    - Renders below a diff line when comments exist for that target
    - Each comment shown as a card: avatar placeholder, body, timestamp, edit/delete actions
    - Collapsed state shows comment count badge
    - Expanded state shows full thread
  - Integrate into `GitDiffView.tsx` below diff line rows

  **Files to create/modify**:
  - Create `apps/web/src/components/InlineReviewThread.tsx`
  - Modify `apps/web/src/components/GitDiffView.tsx` — render thread below lines

  **Reference**:
  - Paseo `review/surface.tsx:537-573` — `InlineReviewThreadContent`

  **QA Scenarios**:
  ```
  Scenario: Comment thread displays and supports edit/delete
    Tool: Playwright
    Preconditions: Comments exist on a diff line
    Steps:
      1. Expand comment thread
      2. Verify comment body is visible with timestamp
      3. Click edit → modify text → save
      4. Click delete → verify comment removed
    Expected: Full CRUD works on comments
    Evidence: .omo/evidence/task-8-thread.png
  ```

- [x] 9. **Frontend: File editing in the file tree**

  **What to do**:
  - In `RightRail.tsx`, add a file edit mode:
    - Double-click a file in the tree (or context menu "Edit") enters edit mode
    - The file row transforms: file name becomes a monospace textarea pre-filled with file content (fetched via existing `api.projects.viewFile`)
    - The row shows Save / Cancel buttons
    - Save: calls `api.projects.writeFile(projectId, path, content)` — the new endpoint from Task 2
    - Cancel: reverts to the original content and exits edit mode
    - After save: re-fetch the file tree + emit `git_diff_refresh`
  - Only one file editable at a time (close any existing editor before opening new)
  - Visual indicator (highlighted row) when in edit mode

  **Files to modify**:
  - `apps/web/src/components/RightRail.tsx` — add edit mode state, edit UI
  - `apps/web/src/api/client.ts` — add `writeFile` method (from Task 2)
  - `apps/web/src/components/TreeLevel.tsx` (inline in RightRail) — accept edit mode props

  **References**:
  - Existing `RightRail.tsx:170-175` `openFile` function — pattern for file interaction
  - Existing `FileViewerOverlay.tsx` — Shiki highlighting reference
  - Paseo `file-explorer-pane.tsx` — context menu actions pattern

  **QA Scenarios**:
  ```
  Scenario: Edit file in file tree and save
    Tool: Playwright
    Preconditions: Project with a text file
    Steps:
      1. Double-click a file in the file tree
      2. Verify file enters edit mode (textarea replaces filename)
      3. Modify content
      4. Ctrl+Enter to save
      5. Verify success indicator
    Expected: File content updated on disk, tree refreshes
    Evidence: .omo/evidence/task-9-edit-save.png

  Scenario: Cancel file edit reverts changes
    Tool: Playwright
    Preconditions: File in edit mode
    Steps:
      1. Modify content in textarea
      2. Click Cancel / press Escape
      3. Re-open file
    Expected: Original content preserved, edit mode exited
    Evidence: .omo/evidence/task-9-edit-cancel.txt
  ```

---

## Final Verification

- [ ] F1. **Plan Compliance Audit** — `oracle`
  Verify all Must Have features are implemented, Must NOT Have are absent.
  Output: VERDICT

- [ ] F2. **Code Quality** — `unspecified-high`
  Run `pnpm -C apps/web build`, `pnpm -C apps/server build`, check for `as any`/`@ts-ignore`/console.log.
  Output: VERDICT

- [ ] F3. **Real Manual QA** — `unspecified-high` + `playwright`
  Execute all QA scenarios from every task, capture evidence.
  Output: Scenarios [N/N pass]

- [ ] F4. **Scope Fidelity** — `deep`
  Verify spec matches implementation, no scope creep.
  Output: Tasks [N/N compliant]

---

## Commit Strategy

- **1**: `feat(server): add whitespace param to git diff + write_file endpoint`
- **2**: `feat(web): diff preferences hook, toolbar toggles, split layout`
- **3**: `feat(web): inline diff comments with zustand store`
- **4**: `feat(web): in-browser file editing in file tree`

---

## Success Criteria

### Verification Commands
```bash
pnpm -C apps/web build     # Must pass
pnpm -C apps/server build  # Must pass
```

### Final Checklist
- [ ] Side-by-side diff renders correctly
- [ ] Hide whitespace re-fetches with `-w`
- [ ] Wrap lines toggles CSS
- [ ] Expand/Collapse all toggles
- [ ] Inline comments: create, read, update, delete
- [ ] File editing: read, modify, save, cancel
- [ ] All preferences survive page reload
