|
| 1 | +# File Tree Virtualization Design |
| 2 | + |
| 3 | +## Context |
| 4 | + |
| 5 | +`components/FileTree.tsx` currently renders the full expanded tree recursively. For large repositories this creates thousands of mounted DOM nodes, repeated descendant counting, and expensive re-renders for expand/collapse, keyboard navigation, and selection changes. The first performance milestone is to virtualize only the file tree without changing parsing, search, or export behavior. |
| 6 | + |
| 7 | +## Goals |
| 8 | + |
| 9 | +- Keep the existing tree data model (`FileNode[]`) and `ProcessedFiles` shape unchanged. |
| 10 | +- Reduce mounted tree row count to a viewport-sized window instead of the full expanded tree. |
| 11 | +- Preserve current interactions: |
| 12 | + - expand/collapse |
| 13 | + - keyboard navigation |
| 14 | + - file selection |
| 15 | + - directory double click |
| 16 | + - file actions (copy path, exclude/include, delete) |
| 17 | + - selected and focused row styling |
| 18 | +- Keep the mobile and desktop layouts working with the current `MainContent.tsx` split-panel structure. |
| 19 | + |
| 20 | +## Non-Goals |
| 21 | + |
| 22 | +- No Web Worker work in this milestone. |
| 23 | +- No ZIP parsing changes in this milestone. |
| 24 | +- No search algorithm changes in this milestone. |
| 25 | +- No changes to `ProcessedFiles.fileContents`, `treeData`, or `structureString` synchronization rules. |
| 26 | + |
| 27 | +## Chosen Approach |
| 28 | + |
| 29 | +Use a virtualized flat list for the visible tree rows while keeping the source tree nested. |
| 30 | + |
| 31 | +### Why this approach |
| 32 | + |
| 33 | +- It isolates the change to the rendering layer. |
| 34 | +- It avoids risky changes to the repository data model. |
| 35 | +- It works with the current expand/collapse state by deriving a flat visible-row list from `nodes` and `expandedPaths`. |
| 36 | +- It gives an immediate DOM-count reduction even before any Worker work lands. |
| 37 | + |
| 38 | +### Library choice |
| 39 | + |
| 40 | +Prefer `react-virtuoso`. |
| 41 | + |
| 42 | +Reasoning: |
| 43 | + |
| 44 | +- The current tree rows can expand in height when hover actions appear, so a variable-height-friendly list is safer than a fixed-row abstraction. |
| 45 | +- It supports container-based virtualization cleanly for the existing scrollable panel. |
| 46 | +- It reduces the amount of manual measuring code we would need to maintain. |
| 47 | + |
| 48 | +## Architecture |
| 49 | + |
| 50 | +### 1. Flatten visible rows |
| 51 | + |
| 52 | +Add a small tree-flattening helper inside `components/FileTree.tsx` or a nearby helper module. |
| 53 | + |
| 54 | +Each flat row will contain: |
| 55 | + |
| 56 | +- `node: FileNode` |
| 57 | +- `level: number` |
| 58 | +- `isOpen: boolean` |
| 59 | +- `isSelected: boolean` |
| 60 | +- `isFocused: boolean` |
| 61 | + |
| 62 | +The flat list is recalculated from: |
| 63 | + |
| 64 | +- `nodes` |
| 65 | +- `expandedPaths` |
| 66 | +- `selectedFilePath` |
| 67 | +- `focusedPath` |
| 68 | + |
| 69 | +Only directory descendants under expanded paths are included. |
| 70 | + |
| 71 | +### 2. Replace recursive render with row renderer |
| 72 | + |
| 73 | +Split the current recursive `FileTreeNode` rendering into: |
| 74 | + |
| 75 | +- a reusable row component that renders one node row |
| 76 | +- a virtualized list wrapper that renders the visible flat rows |
| 77 | + |
| 78 | +Directory expansion state remains in `expandedPaths`. |
| 79 | + |
| 80 | +### 3. Preserve keyboard navigation semantics |
| 81 | + |
| 82 | +Keyboard navigation continues to operate on the visible path order, but the visible path list will now come from the flattened rows instead of a separate recursive walk. |
| 83 | + |
| 84 | +On focus movement, the virtualized list should scroll the focused row into view. |
| 85 | + |
| 86 | +### 4. Preserve action behavior |
| 87 | + |
| 88 | +Row actions continue to call the existing callbacks: |
| 89 | + |
| 90 | +- `onFileSelect` |
| 91 | +- `onDeleteFile` |
| 92 | +- `onCopyPath` |
| 93 | +- `onToggleExclude` |
| 94 | +- `onDirDoubleClick` |
| 95 | + |
| 96 | +No business logic changes are required in `useInteraction.ts` or `useAppLogic.ts`. |
| 97 | + |
| 98 | +## Files Expected To Change |
| 99 | + |
| 100 | +- Modify `components/FileTree.tsx` |
| 101 | +- Modify `package.json` |
| 102 | +- Modify `package-lock.json` |
| 103 | +- Add `components/FileTree.test.tsx` |
| 104 | + |
| 105 | +## Risks And Mitigations |
| 106 | + |
| 107 | +### Risk: flattening breaks expand/collapse or selection behavior |
| 108 | + |
| 109 | +Mitigation: |
| 110 | + |
| 111 | +- keep the original `expandedPaths` state shape |
| 112 | +- derive rows from existing `FileNode.path` |
| 113 | +- cover expand/collapse and selection behavior with component tests |
| 114 | + |
| 115 | +### Risk: keyboard navigation loses parity |
| 116 | + |
| 117 | +Mitigation: |
| 118 | + |
| 119 | +- derive visible order from the flattened rows |
| 120 | +- add tests for arrow navigation and Enter behavior |
| 121 | + |
| 122 | +### Risk: variable-height rows produce jitter |
| 123 | + |
| 124 | +Mitigation: |
| 125 | + |
| 126 | +- use a virtualization library that supports dynamic row sizing |
| 127 | +- keep row structure close to the current DOM and CSS |
| 128 | + |
| 129 | +## Testing Strategy |
| 130 | + |
| 131 | +Add component tests focused on behavior instead of implementation details: |
| 132 | + |
| 133 | +1. renders only a bounded number of visible rows for a large expanded tree |
| 134 | +2. collapses a directory and removes descendants from the visible row set |
| 135 | +3. supports keyboard navigation across visible rows and Enter activation |
| 136 | +4. preserves file action buttons for processed files |
| 137 | + |
| 138 | +Manual verification: |
| 139 | + |
| 140 | +- load a generated tree with thousands of files |
| 141 | +- confirm tree scrolling stays responsive |
| 142 | +- confirm DOM row count stays near viewport scale rather than total node count |
| 143 | +- confirm mobile and desktop tree panels still render and scroll correctly |
| 144 | + |
| 145 | +## Rollout Notes |
| 146 | + |
| 147 | +This milestone intentionally stops at render virtualization. If it lands cleanly, the next stage can address main-thread parsing and search with Workers without mixing concerns in one refactor. |
0 commit comments