# Enhanced File Panel — Design Decisions ## D1: Diff preference system A `useDiffPreferences` hook manages three stored preferences: ```typescript interface DiffPreferences { layout: 'unified' | 'split'; // default: 'unified' wrapLines: boolean; // default: false hideWhitespace: boolean; // default: false } ``` Persisted via localStorage key `boocode.diff.preferences`. **Reference**: `/opt/forks/paseo/hooks/use-changes-preferences/storage.ts` ## D2: Side-by-side diff layout Split layout renders two columns (left = removals, right = additions) with aligned line numbers. Algorithm (adapted from Paseo `diff-layout.ts` `buildSplitDiffRows`): 1. Parse unified diff into hunks (reuse existing `splitDiffByFile`) 2. Group remove lines into pendingRemovals, add lines into pendingAdditions 3. Flush paired rows when encountering context lines 4. Unpaired lines get an empty cell on one side ## D3: Hide whitespace (server-side) `GET /api/projects/:id/git/diff` gains optional `whitespace=1` query param. When set, `git diff` appends `-w` (`--ignore-all-space`). ## D4: Wrap lines (CSS-only) When `wrapLines` is true: `white-space: pre-wrap; overflow-wrap: anywhere` replaces `white-space: pre; overflow-x: auto`. Gutter stays unwrapped. ## D5: Expand/Collapse all `allExpanded` computed as `files.every(f => expandedPaths.has(f.path))`. Toggle button adds all paths to expandedPaths or clears them. ## D6: Inline diff comments Data model: ```typescript interface DiffComment { id: string; filePath: string; side: 'old' | 'new'; lineNumber: number; body: string; createdAt: number; updatedAt: number; } ``` Zustand store with localStorage persistence, keyed by `${sessionId}:${mode}`. ## D7: File editing Double-click file in tree → fetch content via `view_file` → textarea → Save calls `POST /api/projects/:id/write_file` → triggers `git_diff_refresh`. Path validated via existing `pathGuard`. ## D8: No DB / no WS frames / no contract changes All new state is client-side (localStorage, React state, Zustand).