New @boocode/ion package (v0.0.1) for inference optimization network. .codesight/ wiki artifacts for codebase documentation. .omo/ work plans for openspec cleanup and enhanced file panel.
19 KiB
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.tsxwith toolbar (layout/whitespace/wrap/expand-all toggles)- Split-layout diff renderer (side-by-side)
useDiffPreferenceshook (localStorage persistence)- Inline diff comment components + Zustand store
- File editing mode in file tree + server write endpoint
- Server
git diff -wsupportEstimated 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:
- Unified diff ✅ (exists) / Side by side diff ❌
- Hide whitespace ❌
- Wrap long lines ❌
- Expand all files ❌ (only per-file)
- Refresh ✅ (exists)
- Comments on specific diffs ❌
- 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
pnpm -C apps/web buildsucceeds with no errorspnpm -C apps/server buildsucceeds 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/contractstypes - 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.tschanges - 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
-
1. Server: Add
ignoreWhitespaceparam to git diffWhat to do:
- In
apps/server/src/services/git_diff.ts, addignoreWhitespace?: booleanto thegetGitDifffunction signature - When
ignoreWhitespaceis true, append'-w'to the git diff argv call ingetGitDiff(the main diff command, not name-status) - Update
GET /api/projects/:id/git/diffroute inroutes/projects.tsto accept optional query paramwhitespace=1 - The param should be optional (backward compatible) — default false
Files to modify:
apps/server/src/services/git_diff.ts— updategetGitDiff()to accept and useignoreWhitespaceapps/server/src/routes/projects.ts— addwhitespacequery param
References:
- Paseo:
useCheckoutDiffQuery({ ignoreWhitespace })passes to server →git diff -w - Existing
git_diff.ts:36-48runGitfunction — 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 - In
-
2. Server: Add POST /api/projects/:id/write_file endpoint
What to do:
- Add
POST /api/projects/:id/write_fileroute inroutes/projects.ts - Accept
{ path: string, content: string }body - Validate path via existing
pathGuardhelper (same as git discard) - Write file content atomically: write to
.tmpthenrenamethe 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 routeapps/web/src/api/client.ts— addwriteFilemethodapps/web/src/api/types.ts— add write types if needed
References:
apps/server/src/services/file_ops.ts— existing file operations patternapps/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 - Add
-
3. Frontend: useDiffPreferences hook
What to do:
- Create
apps/web/src/hooks/useDiffPreferences.ts - Define
DiffPreferencesinterface:{ 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 patternapps/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 - Create
-
4. Frontend: GitDiffView toolbar with all toggles
What to do:
- Add a toolbar row inside
GitDiffView.tsxbetween the mode selector and file list - Controls (left to right):
- Layout toggle: two-segment button (Unified | Split) — uses
AlignJustify/Columns2icons - Hide whitespace: toggle button —
Pilcrowicon, active state highlights - Wrap lines: toggle button —
WrapTexticon - Expand/Collapse all: toggle button —
ListChevronsUpDown/ListChevronsDownUpicons - Refresh: existing button (already present)
- Layout toggle: two-segment button (Unified | Split) — uses
- Wire each toggle to the
useDiffPreferenceshook - 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—DiffStyleTogglesegment 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 - Add a toolbar row inside
-
5. Frontend: Diff layout utilities + side-by-side renderer
What to do:
- Create
apps/web/src/utils/diff-layout.tswith pure functions:buildNumberedDiffHunks(diffBody: string): NumberedDiffHunk[]— parse diff text into hunks with old/new line numbersbuildUnifiedDiffLines(file): UnifiedDiffDisplayLine[]— existing behaviorbuildSplitDiffRows(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, whenlayout === 'split', renderDiffSplitViewinstead 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.tssplitDiffByFile— 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 - Create
-
6. Frontend: Inline comment store + Zustand
What to do:
- Create
apps/web/src/stores/useDiffCommentStore.ts - Define
DiffCommentinterface:{ 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 localStoragepersist()— subscribe to store changes, write to localStorage keyboocode.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 - Create
-
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
InlineReviewEditorpattern
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 - Create
-
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.tsxbelow 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 - Create
-
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 UIapps/web/src/api/client.ts— addwriteFilemethod (from Task 2)apps/web/src/components/TreeLevel.tsx(inline in RightRail) — accept edit mode props
References:
- Existing
RightRail.tsx:170-175openFilefunction — 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 - In
Final Verification
-
F1. Plan Compliance Audit —
oracleVerify all Must Have features are implemented, Must NOT Have are absent. Output: VERDICT -
F2. Code Quality —
unspecified-highRunpnpm -C apps/web build,pnpm -C apps/server build, check foras any/@ts-ignore/console.log. Output: VERDICT -
F3. Real Manual QA —
unspecified-high+playwrightExecute all QA scenarios from every task, capture evidence. Output: Scenarios [N/N pass] -
F4. Scope Fidelity —
deepVerify 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
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