Commit f4acfc9
Add virtualized flat-list TreeView component and preview sandbox (#2972)
This PR introduces a new frontend TreeView widget intended for
VSCode-style explorer use cases, without backend wiring yet. It provides
a reusable, backend-agnostic API with virtualization, flat visible-row
projection, and preview coverage under `frontend/preview`.
- **What this adds**
- New `TreeView` component in `frontend/app/treeview/treeview.tsx`
designed around:
- flat `visibleRows` projection with `depth` (not recursive render)
- TanStack Virtual row virtualization
- responsive width constraints + horizontal/vertical scrolling
- single-selection, expand/collapse, and basic keyboard navigation
- New preview: `frontend/preview/previews/treeview.preview.tsx` with
async mocked directory loading and width controls.
- Focused tests: `frontend/app/treeview/treeview.test.ts` for
projection/sorting/synthetic-row behavior.
- **Tree model + projection behavior**
- Defines a canonical `TreeNodeData` wrapper (separate from direct
`FileInfo` coupling) with:
- `id`, `parentId`, `isDirectory`, `mimeType`, flags, `childrenStatus`,
`childrenIds`, `capInfo`
- Builds `visibleRows` from `nodesById + expandedIds` and injects
synthetic rows for:
- `loading`
- `error`
- `capped` (“Showing first N entries”)
- **Interaction model implemented**
- Click: select row
- Double-click directory (or chevron click): expand/collapse
- Double-click file: emits `onOpenFile`
- Keyboard:
- Up/Down: move visible selection
- Left: collapse selected dir or move to parent
- Right: expand selected dir or move to first child
- **Sorting + icon strategy**
- Child sorting is deterministic and stable:
- directories first
- case-insensitive label order
- id/path tie-breaker
- Icon resolution supports directory/file/error states and simple
mimetype/extension fallbacks.
- **Example usage**
```tsx
<TreeView
rootIds={["workspace:/"]}
initialNodes={{ "workspace:/": { id: "workspace:/", isDirectory: true, childrenStatus: "unloaded" } }}
fetchDir={async (id, limit) => ({ nodes: data[id].slice(0, limit), capped: data[id].length > limit })}
maxDirEntries={120}
minWidth={100}
maxWidth={400}
height={420}
onSelectionChange={(id) => setSelection(id)}
/>
```
- **<screenshot>**
-
https://github.com/user-attachments/assets/6f8b8a2a-f9a1-454d-bf4f-1d4a97b6e123
<!-- START COPILOT CODING AGENT TIPS -->
---
💬 We'd love your input! Share your thoughts on Copilot coding agent in
our [2 minute survey](https://gh.io/copilot-coding-agent-survey).
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: sawka <2722291+sawka@users.noreply.github.com>1 parent d47329d commit f4acfc9
5 files changed
Lines changed: 696 additions & 2 deletions
File tree
- frontend
- app/treeview
- preview/previews
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
0 commit comments