Skip to content

feat(app): Preview Tab — Document Viewer/Editor in Side Panel - #182

Merged
jeonghun-jj-lee merged 16 commits into
local/amicodefrom
feat/162-preview-tab
Aug 11, 2026
Merged

feat(app): Preview Tab — Document Viewer/Editor in Side Panel#182
jeonghun-jj-lee merged 16 commits into
local/amicodefrom
feat/162-preview-tab

Conversation

@jeonghun-jj-lee

@jeonghun-jj-lee jeonghun-jj-lee commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Summary

Implements #162 — a Preview tab in the side panel for viewing/editing AI-modified .md files, plus a Panel Menu replacing the old "+" button.

Preview Tab

  • New "Preview" tab appears in the side panel alongside Modified Files and Context
  • Lists all .md files the AI modified in the current session, with A (added) / M (modified) badges
  • Clicking a file renders it through the existing Markdown component (full GFM: headings, code blocks, tables, LaTeX math, etc.)
  • Per-file toggle switches between Preview (rendered) and Raw (editable textarea) modes
  • Raw mode: monospace textarea, auto-save with ~1s debounce, Cmd+S/Ctrl+S for immediate flush
  • Tab only appears when at least one .md file has been AI-modified (no empty state)

Panel Menu

  • The "+" button is replaced with a dropdown menu listing available panel types: Context, Preview
  • The menu is the extensible seam for future panel types
  • Already-open panels show a checkmark indicator
  • SESSION_OPEN_FILE_TAB inline file browser is removed (the command palette Cmd+O dialog still works)

Server

  • Added POST /file/write endpoint for raw editor auto-save (path containment check + writeWithDirs)

Files Changed

New:

  • packages/app/src/components/session/session-preview-tab.tsx — main tab: file list + content view + mode toggle + raw editor
  • packages/app/src/components/session/panel-menu.tsx — dropdown menu component

Modified:

  • packages/app/src/context/layout-tabs.ts — add SESSION_PREVIEW_TAB constant
  • packages/app/src/pages/session/helpers.ts — add previewOpen signal, filter preview from sortable tabs
  • packages/app/src/pages/session/session-side-panel.tsx — wire Preview tab + PanelMenu in both v1/v2 paths; remove SESSION_OPEN_FILE_TAB inline trigger
  • packages/app/src/components/session/index.ts — export new components
  • packages/opencode/src/server/routes/instance/httpapi/groups/file.ts — add write endpoint definition
  • packages/opencode/src/server/routes/instance/httpapi/handlers/file.ts — add write handler

Testing

  • Full monorepo typecheck passes (30/30 packages)
  • 899/900 unit tests pass (1 pre-existing i18n parity failure unrelated to this PR)

Closes #162

- Add SESSION_PREVIEW_TAB constant and tab registration in layout-tabs.ts
- Add previewOpen signal and preview tab filtering in helpers.ts
- Create SessionPreviewTab component: file list with A/M badges, rendered
  markdown preview via existing Markdown component, raw editor with
  debounced auto-save and Cmd+S support
- Create PanelMenu dropdown component replacing the + button in both v1
  and v2 side panel layouts
- Wire Preview tab trigger and content in both layout paths
- Tab only shows when .md files have been AI-modified in the session
- Export new components from session/index.ts

Remaining work:
- Add /file/write server endpoint for raw editor saves
- Remove SESSION_OPEN_FILE_TAB and related dead code paths
- Test end-to-end in the running app
…rowser

Server:
- Add POST /file/write endpoint to the file API group with path
  containment check and writeWithDirs for safe file writes from the
  Preview tab's raw editor

Client (session-side-panel):
- Remove the SESSION_OPEN_FILE_TAB inline file browser trigger from both
  v1 and v2 tab lists (the spec removes the inline file opener)
- Remove openFileBrowser() function (replaced by PanelMenu)
- Remove the lazy DialogSelectFile import from the old + button
- Remove unused useDialog import and openFileKeybind memo
- Simplify the For-each-panelTabs render (no more Show branch for
  open-file tab)
- SessionFileBrowserTab still renders file content for open file tabs
  (only the 'open-file' tab trigger is removed, not file viewing)
@jeonghun-jj-lee
jeonghun-jj-lee marked this pull request as ready for review August 11, 2026 01:33
- Make Preview menu item always visible (available: () => true) so
  users can discover it even when no .md files are modified yet
- Remove nested button in PanelMenu trigger — the DropdownMenu.Trigger
  already renders as a button, so just put the Icon directly inside it
- Show the Preview tab trigger whenever previewOpen() is true (not
  gated on hasMarkdownFiles) — the tab's internal file list handles
  the empty state message
- Simplify PanelMenu: remove unused IconButton/IconButtonV2 imports,
  use Kobalte's gutter/placement props for positioning
- Add preprocessMarkdown() that converts ```math fenced blocks to
  $$...$$ display math blocks (the Markdown component's KaTeX
  extension only matches $$ delimiters, not GitHub-flavored ```math)
- Replace the subtle single-button toggle with a visible segmented
  control: both Preview and Raw buttons shown side-by-side, the active
  mode gets a highlighted background so the current state is obvious
- Remove unused toggleMode function
Right-clicking a file in the Preview tab's file list now shows a
context menu with 'Copy full path' and 'Copy filename' options,
matching the Files Changed panel's behavior. Uses the same MenuV2
context menu pattern from session-review-file-preview-v2.
- Math wrapping: display math (.katex-display) gets overflow-x:auto so
  wide equations scroll horizontally instead of overflowing the panel
- Save status: shows 'Saving...' during the write, then 'Saved' for 2s
  after success (both debounced auto-save and Cmd+S)
- Mode toggle: replaced text buttons with an icon-only SegmentedControlV2
  (same component as the unified/split toggle in Files Changed), with
  TooltipV2 on hover showing 'Preview' or 'Raw' respectively
- Slightly smaller KaTeX font size (0.9em) to fit better in narrow panels
- SegmentedControlV2: override default 232px width with !w-auto and
  !flex-none !px-2 on items so it's compact icon-only size
- PanelMenu trigger: smaller (w-6 h-6), matching the icon button sizing
  that was there before; add px-2 to the v2 sticky wrapper for proper
  spacing within the tab bar
- Save status: inline in the header row next to the filename (shows
  'Saving...' / 'Saved' text when active)
…cator

- Panel menu wrapper: pl-3 pr-3 on both v1/v2 layouts to give clear
  separation between the last tab and the + button
- Zoom control: +/- buttons with a percentage textbox, rendered to the
  left of the preview/raw toggle (range 50%–200%, step 10%)
- Save indicator: green text for 'Saved', muted for 'Saving...'
- Reverted PanelMenu to simpler trigger (plain button with Icon)
- Use writeClipboardViaBridge (works in Amicode webview) with
  navigator.clipboard.writeText as fallback — fixes copy not working
- Zoom percentage input: w-9 → w-11 for more room
- Plus button: -ml-0.5 to sit closer to the minus button
The textarea was using value={props.content} which made it a controlled
component — every keystroke re-set the value from the signal, destroying
the browser's native undo stack. Now the initial content is set via the
ref callback and the textarea manages its own state natively. Cmd+A,
Cmd+Z, Cmd+Shift+Z, Cmd+C/V all work as expected.
…atch zoom height

- Panel menu: moved OUTSIDE Tabs.List as a sibling in the header flex
  row (both v1 and v2 layouts). This prevents any Kobalte tab-list
  CSS interaction with the dropdown trigger — the tabs render normally
  and the + button sits cleanly after them.
- Raw editor: stopPropagation on all Cmd/Ctrl keydown events so the
  app's global command handler doesn't steal Cmd+A, Cmd+Z, Cmd+Shift+Z
- Zoom control: h-6 → h-7 (28px) to match SegmentedControlV2 height
- Raw editor: use el.addEventListener('keydown', ...) directly on the
  DOM element instead of SolidJS's onKeyDown. SolidJS uses event
  delegation (handlers on document root), so stopPropagation from a
  delegated handler doesn't prevent other delegated handlers. A native
  listener fires in the capture/bubble phase on the element itself,
  before delegation, so stopPropagation actually works.
- Panel menu wrapper: pl-3 pr-4 on both layouts for more breathing room
  between the last tab and the + button
- Panel menu wrapper: pl-2 pr-1 (was pl-3 pr-4) — the pr-4 was pushing
  the button AWAY from the right edge; reducing pr moves it closer to
  the panel's right edge
- Raw editor textarea: add selection:bg-blue-500/30 so Cmd+A selection
  is visually distinguishable (the transparent bg + outline-none may
  have made native selection invisible)
@jeonghun-jj-lee
jeonghun-jj-lee merged commit 68224e6 into local/amicode Aug 11, 2026
1 of 4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Preview Tab — Document Viewer/Editor in the Side Panel

1 participant