Repository navigation
Redesign Preview tab: Finder-style project browser + CodeMirror editor #912
Description
Important
Problem — The Preview tab currently shows only markdown files touched in the current session, lists them as a flat list, and uses a raw <textarea> for editing. This limits its usefulness to a narrow session-scoped view and provides a poor editing experience compared to the CodeMirror-powered Files Changed tab.
Approach — Redesign the Preview tab as a Finder-style project file browser scoped to renderable files (markdown, images, PDFs). Replace the flat file list with drill-down directory navigation, add a persistent search bar and breadcrumb path with a filetree dropdown, and replace the textarea editor with a CodeMirror instance that shares its core setup with the Files Changed tab. Sessions without a project degrade gracefully to the current session-touched-files behavior.
Approaches considered
- Monolithic rewrite — rewrite
session-preview-tab.tsxas one large component with all logic inline. Rejected: the file would exceed 800 lines, making it hard to test and maintain. - Generic file browser abstraction — build a reusable
<FileBrowser>component consumed by Preview with a filter function. Rejected: over-engineering with only one consumer; the abstraction boundary is speculative.
Scope — in: Preview tab navigation, file discovery, CodeMirror editor, image/PDF viewing, search, breadcrumb, no-project fallback, preprocessMarkdown consolidation · out: Files Changed tab behavior, diff views, non-renderable file types, server-side API changes
Assumptions — The existing file tree server API provides sufficient data to enumerate project files (lazy per-directory via file.tree.children, recursively walked on tab mount for the dropdown and search). The buildFileTreeV2Model static-mode pipeline handles the filtered file set at adequate performance (dozens to low hundreds of renderable files). The CodeMirror packages already in session-ui's dependencies cover the editor needs.
Acceptance Criteria
- Preview tab shows all renderable files in the session's project (not just session-touched files)
- Renderable file types:
.md/.markdown,.png/.jpg/.jpeg/.gif/.svg/.webp/.ico/.bmp,.pdf(.mdxis out of scope for this issue — add later if needed) - Directory view renders folder and file rows; clicking a folder drills into it (Finder-style navigation)
- Only renderable files and their ancestor folders appear in the tree; empty folders are hidden
- Search bar is always visible at the top; typing replaces directory view with a flat substring-matched list of all renderable files; clearing restores the directory view
- Breadcrumb path bar shows the current directory; each segment is individually clickable for direct navigation
- A dropdown trigger on the breadcrumb opens a
FileTreeV2(static mode) overlay showing the full filtered filetree for quick-jumping - Markdown files render in Preview mode (existing
<Markdown>component) or Edit mode (CodeMirror editor) via aSegmentedControlV2toggle (renames the existing"raw"mode to"edit") - CodeMirror editor supports debounced autosave (1s), Cmd/Ctrl+S immediate save, and a circular save indicator next to the filename
- Navigating away from a file with a pending autosave flushes the debounce timer (immediate save, not canceled)
- Images render centered and scaled to fit with zoom controls (50%–200%)
- PDFs render via
<embed>with zoom controls - Images and PDFs are view-only — the Preview/Edit mode toggle is hidden for non-markdown types
- Navigation state (current path, selected file, per-file mode/scroll/unsaved content) persists across tab switches
- Sessions without a project fall back to showing session-touched renderable files in a flat list; search bar and breadcrumb are hidden
- The existing clipboard bridge (
global-clipboard.ts) works with the new CodeMirror editor viadata-amc-clipboard="codemirror" -
preprocessMarkdownis extracted into a shared utility (e.g.markdown-utils.ts) consumed by both preview components and the test suite, consolidating the three existing copies
Testing Decisions
Extend the existing packages/extension/test/markdown_preview.test.ts suite for file-type detection logic and update it to import the consolidated preprocessMarkdown from the new shared utility (fixing the current drift where the test defines its own copy with different behavior). Add unit tests for the new navigation state machine (directory drill-down, breadcrumb navigation, search filter transitions, no-project fallback). The editor-core.ts extraction should be verified by confirming the Files Changed tab's behavior is unchanged (existing test coverage).
Key Decisions
Finder-style navigation, not a sidebar tree. The directory view IS the main content area of the tab. Clicking a folder replaces the view with that folder's contents. This matches the user's mental model of a file explorer and avoids the sidebar layout complexity that Files Changed has permanently disabled in the fork.
Static FileTreeV2 mode for the dropdown. The filtered renderable file set is small enough (dozens to low hundreds) to build the full tree model client-side via buildFileTreeV2Model. This avoids the complexity of intercepting live-mode expand/collapse to filter mid-stream.
File enumeration strategy. For the Finder-style directory view, use the lazy file.tree.children API one directory at a time — no pre-enumeration needed; each drill-down fetches one level and filters client-side. For the dropdown and search (which need the full renderable file set), recursively walk the tree on tab mount, filter to renderable extensions, and cache the result. Refresh on tab focus.
editor-core.ts extraction from editable-diff-view-core.ts. The general-purpose CodeMirror setup functions are factored into a shared module with their existing signatures preserved. Both the diff view and the new preview editor import from it. The diff view's call sites do not change — this is a pure move, not an adapter layer.
Graceful no-project degradation. When a session has no project, the tab falls back to the current behavior: session-touched renderable files from the touched-files endpoint in a flat list. The search bar and breadcrumb are hidden since there's no project tree to navigate.
Data Contracts
PreviewTabState — the navigation state owned by session-preview-tab.tsx:
currentPath: string— current directory relative to project rootselectedFile: string | null— file being viewed, or null for directory viewsearchQuery: string— empty for directory view, non-empty for flat search resultsfileStates: Record<string, { mode: "preview" | "edit", scrollPosition: number, unsavedContent: string | null }>— per-file state persisted across navigation (usesRecordfor SolidJS store reactivity, notMap)
editor-core.ts exports — pure functions extracted with their existing signatures:
baseExtensions(opts: { theme: Extension, language?: LanguageSupport | null }): Extension[]editableExtensions(opts: { readOnly: boolean, onChange?: (content: string) => void }): Extension[]loadLanguage(ext: string): Promise<LanguageSupport | null>— takes a bare file extension (e.g."md"), not a filenamebuildThemeExtension(mode: "light" | "dark"): ExtensionbuildSyntaxHighlightStyle(): Extension
Constraints & Invariants
- The
editable-diff-view-core.tsrefactor must not change any behavior of the Files Changed tab — it is a pure extraction with signatures preserved, not a rewrite - The filtered file tree must never show directories that contain no renderable descendants
- The CodeMirror editor must work with the existing VS Code webview clipboard bridge —
data-amc-clipboard="codemirror"is required on the editor container - Zoom controls must apply consistently across all file types and all applicable view modes
Prior Art
session-preview-tab.tsx— the current Preview tab (rewrite target)editable-diff-view-core.ts+editable-diff-view.tsx— the CodeMirror diff editor (extraction source)file-tree-v2.tsx+file-tree-v2-model.ts— the virtualized file tree (reused in static mode for the dropdown)session-file-list-v2.tsx— the flat search-results list (pattern for search behavior)review-panel-v2.tsx— the Files Changed file-name picker dropdown (pattern for the breadcrumb dropdown)global-clipboard.ts— the CM6 clipboard bridge for VS Code webviewsession-review-file-preview-v2.tsx— contains a duplicatepreprocessMarkdownthat should be consolidated
Notes
Component file map
| File | Role |
|---|---|
session-ui/.../editor-core.ts |
NEW — extracted CM6 setup (theme, extensions, language loading) |
session-ui/.../markdown-utils.ts |
NEW — consolidated preprocessMarkdown + file-type helpers |
app/.../session/preview-editor.tsx |
NEW — plain EditorView wrapper, debounced save, Cmd+S |
app/.../session/preview-directory-view.tsx |
NEW — Finder-style folder listing with file/folder rows |
app/.../session/preview-file-view.tsx |
NEW — routes by file type: Markdown/image/PDF/CM editor |
app/.../session/preview-breadcrumb.tsx |
NEW — clickable path segments + filetree dropdown trigger |
app/.../session/session-preview-tab.tsx |
REWRITE — thin orchestrator (state, search, routing) |
Visual layout
Directory view:
┌─────────────────────────────────────────┐
│ 🔍 Search files... │
├─────────────────────────────────────────┤
│ 📁 src › components › session │
├─────────────────────────────────────────┤
│ 📁 __tests__ │
│ 📄 README.md M │
│ 🖼️ diagram.png │
└─────────────────────────────────────────┘
File view (markdown):
┌─────────────────────────────────────────┐
│ 🔍 Search files... │
├─────────────────────────────────────────┤
│ ← 🔴 README.md src › components │
│ [👁 Preview | ✏️ Edit] │
│ [100% - +] │
├─────────────────────────────────────────┤
│ # Session Component │
│ This component handles... │
└─────────────────────────────────────────┘
Activity
jeonghun-jj-lee commentedon Sep 9, 2026
Superseded by #931 (Preview as sidebar companion). The Finder-based design is replaced by the reactive companion model — sidebar drives file selection, Preview is a pure viewer.