Files
boocode/.omo/plans/enhanced-file-panel.md
indifferentketchup 02063072ab chore: add ion package, codesight wiki, work plans, ascli config
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.
2026-06-07 22:16:45 +00:00

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.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

  • pnpm -C apps/web build succeeds with no errors
  • 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

  • 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
    
  • 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
    
  • 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
    
  • 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-1273DiffLayoutToggleGroup, DiffWhitespaceToggle, DiffFilesToolbar
    • openchamber DiffViewToggle.tsx — simple toggle pattern
    • happy InlineFileDiff.tsx:196-219DiffStyleToggle 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
    
  • 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
    
  • 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
    
  • 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-309DiffGutterCell + 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
    
  • 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-573InlineReviewThreadContent

    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
    
  • 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 Auditoracle Verify all Must Have features are implemented, Must NOT Have are absent. Output: VERDICT

  • F2. Code Qualityunspecified-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 QAunspecified-high + playwright Execute all QA scenarios from every task, capture evidence. Output: Scenarios [N/N pass]

  • F4. Scope Fidelitydeep 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

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