PRD: Terminal Abstraction & Frontend-Owned UX #1

Description

@DanielGGordon

Problem

DanCode's terminal experience is built around tmux as both the backend process manager and the user-facing UX layer. Tmux concepts — grouped sessions, window indices, status bars, attach commands — leak directly into the browser experience. Layout management is split between React and tmux, causing quirks with mouse handling, resize behavior, and multiplexing. Creating a "grouped connection session" per browser tab just to achieve independent window views is a workaround, not an architecture. The result is a functional but hacky experience that fights tmux's design rather than building on top of it cleanly.

Solution

Rearchitect the terminal layer so the frontend fully owns the terminal UX — layout, multiplexing, pane management, labels, and resize — while the server provides a clean terminal abstraction backed by direct PTY connections. Tmux operates invisibly as a persistence layer: each server-managed PTY is backed by a hidden tmux session so processes survive browser disconnects and server restarts, but no tmux concept is ever exposed to the client. The server maintains an output ring buffer per terminal for seamless reconnection replay. A file explorer is added as a new panel type alongside terminals. A mobile-first PWA experience provides monitoring and light interaction from a phone — swipe navigation, a system-wide shortcut bar, and a status dashboard designed for checking on running agents. As a stretch goal, sessions remain directly accessible from the host environment for power users who want to drop into a shell.

Requirements

Phase 1: Direct PTY & Frontend-Owned Layout

Server — TerminalManager

  • Server exposes a terminal CRUD API: create, list, get, destroy terminals
  • Each terminal is identified by a unique ID (not a tmux window index)
  • Creating a terminal spawns a real PTY process (bash or user's default shell) via node-pty
  • Terminals are associated with a project (by slug) and have a user-defined label
  • Terminal creation accepts optional command param (e.g., claude --dangerously-skip-permissions) run inside the shell
  • Terminal creation accepts optional cwd param (defaults to project path)
  • Server maintains an output ring buffer (~50KB) per terminal for reconnection replay
  • When a browser disconnects, the PTY stays alive and continues buffering output
  • When a browser reconnects to a terminal ID, the buffered output is replayed before resuming live streaming
  • resize events from the client resize the PTY directly (no tmux intermediary)
  • Terminal metadata (id, project, label, state) is persisted in ~/.dancode/terminals/ so the server can rediscover terminals after restart

Server — API

  • POST /api/terminals — create terminal (params: projectSlug, label, command?, cwd?) → returns terminal object with id
  • GET /api/terminals?project=<slug> — list terminals for a project
  • GET /api/terminals/:id — get terminal metadata
  • PATCH /api/terminals/:id — update label or other metadata
  • DELETE /api/terminals/:id — kill PTY process and remove terminal
  • WebSocket /terminal/:id — bidirectional I/O (replaces current /terminal namespace with query params)

Client — TerminalLayout (replaces PaneLayout)

  • Layout is 100% frontend-owned: no fetching tmux windows from server
  • Supports split view (side-by-side, resizable dividers) and tabbed view
  • Mobile (<768px) always uses tabbed view
  • Users can create new terminals from the UI ("+" button) — opens in project directory by default
  • Users can close/kill terminals from the UI (with confirmation)
  • Users can rename terminal labels inline
  • Users can reorder tabs via drag-and-drop
  • Users can toggle terminals visible/hidden without killing them
  • Layout state (which terminals are visible, order, split sizes, active tab) persisted in project config
  • Each terminal pane renders an xterm.js instance connected via WebSocket to /terminal/:id
  • Click-to-focus with visual indicator (accent bar) on focused pane
  • Ctrl+Scroll font sizing, Ctrl+C copy, Ctrl+V paste all preserved

Client — Project Creation (updated)

  • Remove "Adopt existing tmux session" flow entirely
  • New project creates 2 default terminals: "CLI" (shell) and "Claude" (runs claude command)
  • No hardcoded pane types — terminals are generic, labels are just strings

Migration / Cleanup

  • Remove tmux.js module (or gut it for Phase 2 reuse)
  • Remove grouped session logic from terminal.js
  • Remove tmux status dots, attach command bar, and tmux context menu items from Sidebar
  • Remove tmuxSession, showTmuxCommands from project config schema
  • Remove /api/tmux-status and /api/tmux/sessions endpoints

Phase 2: Invisible Tmux Persistence

Server — Tmux as Hidden Backend

  • Each PTY is spawned inside a hidden tmux session rather than as a bare process
  • Tmux session naming is deterministic and internal: dancode-{projectSlug}-{terminalId}
  • The server interacts with the PTY via node-pty spawning a shell inside the tmux session (NOT tmux attach)
  • Tmux is never referenced in any client-facing API response or WebSocket message
  • On server startup, the TerminalManager scans for existing dancode-* tmux sessions and reconciles with terminal metadata files — orphaned sessions are reclaimed, metadata without sessions is cleaned up
  • If a PTY handle is lost (server crash) but the tmux session survives, the server reattaches transparently on restart
  • Output ring buffer is repopulated from tmux scrollback on reattach (tmux capture-pane)

Server — Resilience

  • Terminal metadata files in ~/.dancode/terminals/ include the backing tmux session name
  • Server restart triggers full reconciliation: match metadata ↔ tmux sessions ↔ PTY handles
  • Terminals that were running before a crash appear as "reconnecting" in the UI, then resume automatically
  • Stale metadata (tmux session gone, no PTY) is cleaned up with a warning log

Stretch Goal: Direct Host Access

  • Tmux sessions use clean, human-readable names (dancode-{projectSlug}-{label})
  • A user can tmux ls on the host and see all DanCode-managed sessions
  • A user can tmux attach -t dancode-myproject-cli to directly access a terminal
  • Optional: a dancode CLI helper script that lists projects and attaches to sessions (dancode attach myproject cli)
  • Input from direct tmux attach and browser are interleaved — both see the same terminal
  • This is explicitly a power-user feature; the web UI remains the primary interface

Phase 3: File Explorer

Server — File System API

  • GET /api/files?path=<dir> — list directory contents (name, type, size, modified date), scoped to project path
  • GET /api/files/read?path=<file> — read file contents (with size limit, e.g., 1MB)
  • PUT /api/files/write — write file contents (path + content in body)
  • POST /api/files/mkdir — create directory
  • POST /api/files/rename — rename/move file or directory
  • DELETE /api/files — delete file or directory (with confirmation)
  • All paths are validated to be within the project directory (no path traversal)
  • Symlinks are followed but validated to stay within project bounds
  • Hidden files (dotfiles) shown with a toggle

Client — File Explorer Panel

  • Tree view panel that can appear alongside terminal panes (left side or as a tab)
  • Lazy-loaded: directories expand on click, fetching contents on demand
  • File icons by extension (simple icon set — folders, code files, config, images, etc.)
  • Single-click to select, double-click to... (future: open in editor; for now: copy path to clipboard or insert into focused terminal)
  • Right-click context menu: rename, delete, copy path, new file, new folder
  • Drag-and-drop files to terminal panes to insert the file path
  • Search/filter within the current directory tree
  • Respects .gitignore patterns by default (toggle to show ignored files)
  • Collapsible — can be hidden to maximize terminal space
  • File explorer state (expanded directories, scroll position) persisted per project

Integration with Terminals

  • "Open terminal here" context menu option on directories — creates a new terminal with cwd set to that directory
  • Dragging a file onto a terminal inserts the relative path

Phase 4: Mobile Experience & PWA

PWA Foundation

  • Add web app manifest (manifest.json) with app name, icons, theme color (Solarized Dark), display: standalone
  • Add service worker for offline shell (cache app assets, show "offline" state when server unreachable)
  • "Add to Home Screen" works on Android Chrome — launches full-screen, no browser chrome
  • Viewport and touch meta tags for native-feeling mobile experience
  • App icon and splash screen in Solarized Dark branding

Mobile Layout — Monitoring-First Design

  • Default mobile view is a status dashboard, not a terminal
  • Dashboard shows all projects in a card list: project name, terminal labels, activity indicator (last output timestamp, active/idle/stopped), and a preview of the last few lines of output per terminal
  • Tap a project card to expand and see its terminals
  • Tap a terminal to enter full-screen terminal view
  • Back gesture or button returns to dashboard

Terminal View — Mobile Optimized

  • Terminal takes full screen (no sidebar, no header — just the terminal + a thin top bar with back button and terminal label)
  • Read-first: keyboard is dismissed by default, terminal output is scrollable
  • Tap the terminal area or a "keyboard" button to enter input mode (soft keyboard appears)
  • Auto-dismiss keyboard after pressing Enter (configurable — some users may want it to stay)

Shortcut Bar

  • Persistent bottom bar when keyboard is active, sitting above the soft keyboard
  • System-wide shortcuts (not per-project): Ctrl+C, Ctrl+V, Ctrl+D, Tab, Up Arrow, Down Arrow, Esc
  • Scrollable horizontally if more shortcuts than screen width
  • Buttons are touch-friendly (min 44px tap targets)
  • Shortcut bar hides when keyboard is dismissed (read mode)

Swipe Navigation

  • Swipe left/right between terminals within the same project
  • Dot indicators at top show which terminal is active (like iOS page dots)
  • Swipe from left edge opens project list (drawer-style)
  • Swipe down from top of terminal to return to dashboard

Mobile-Specific Features

  • Pull-to-refresh on dashboard to update activity status
  • Long-press a project card for quick actions (kill all terminals, open CLI, open Claude)
  • Haptic feedback on shortcut button taps (where supported)
  • Font size respects system accessibility settings, with pinch-to-zoom override
  • Landscape mode: terminal uses full width, shortcut bar along the bottom

Responsive Breakpoints

  • < 480px (phone portrait): dashboard cards stack, single terminal full-screen, shortcut bar active
  • 480–768px (phone landscape / small tablet): same as above but wider terminal
  • 768–1024px (tablet): optional split view (2 terminals), shortcut bar available but optional
  • > 1024px (desktop): full desktop layout, no shortcut bar, no swipe navigation

UX / Interface (all phases)

  • Solarized Dark theme maintained throughout
  • No tmux concepts visible anywhere in the UI — no session names, attach commands, or status bars referencing tmux
  • All terminal management is presented as "terminals" not "panes" or "windows"
  • Smooth reconnection UX: terminal shows "Reconnecting..." overlay, then replays buffer and resumes
  • Connection state indicators per terminal (connected / reconnecting / disconnected)
  • Responsive: splits on desktop, tabs on mobile, file explorer collapses on narrow viewports

Data / Persistence

  • Terminal metadata: ~/.dancode/terminals/{terminalId}.json — id, projectSlug, label, tmuxSessionName, createdAt, lastActivity
  • Project config: ~/.dancode/projects/{slug}.json — updated schema drops tmux fields, adds layout object for frontend state
  • Output ring buffer: in-memory only (not persisted to disk) — repopulated from tmux scrollback on restart
  • File explorer state: persisted in project config (expanded paths, panel visibility)

Technical Decisions

  • Stack: No changes — Node.js, Express, Socket.io, React, Vite, xterm.js, Tailwind. Proven and working.
  • Architecture: New TerminalManager class server-side replaces current tmux.js + terminal.js. Owns PTY lifecycle, output buffering, and reconnection. Single responsibility: manage terminal processes.
  • Data model: Terminals become first-class entities with their own ID and metadata file, decoupled from tmux window indices. Projects reference terminals by ID, not by pane index.
  • API surface: Clean REST for terminal CRUD + WebSocket per terminal for I/O. No tmux vocabulary in any API contract. File system API for explorer with strict path validation.
  • Persistence strategy: Phase 1 uses bare PTYs (sessions lost on server restart). Phase 2 adds invisible tmux backing for full persistence. This phasing means Phase 1 is shippable without tmux complexity.
  • Output buffer: ~50KB circular buffer per terminal, in-memory. Enough to replay a screenful+ on reconnect. Not persisted — tmux scrollback is the durable copy (Phase 2).
  • Mobile: PWA (manifest + service worker), not a native app. Touch gestures via standard DOM events or a lightweight library (e.g., Hammer.js). Shortcut bar is a React component, not a native keyboard extension. Responsive breakpoints handled with Tailwind's existing breakpoint system.
  • Testing: Existing Vitest + Playwright stack. New tests for TerminalManager lifecycle, reconnection replay, file system API path validation. Playwright mobile emulation for PWA/mobile layout tests. Visual tests updated to assert on new layout.

Out of Scope

  • Code editor / Monaco integration (future phase, after file explorer proves useful)
  • Ralph UI controls (separate PRD)
  • Multi-server management
  • Multi-user / role-based access
  • Light mode / theming beyond Solarized Dark
  • Session recording / playback
  • Git integration in the UI
  • Native Android/iOS app (PWA covers mobile; native wrapper via Capacitor is a future option if push notifications or app store presence become important)
  • Offline terminal interaction (PWA caches the app shell, but terminals require server connectivity)

Open Questions

  • Shell selection: Should terminal creation auto-detect the user's default shell ($SHELL) or always use bash? Leaning toward $SHELL with bash fallback.
  • Buffer size: 50KB per terminal is a starting point. Should this be configurable per-project? Probably not worth the complexity initially.
  • File explorer size limits: What's the right max file size to read via the API? 1MB seems reasonable for text files. Binary files should be blocked or download-only.
  • Terminal limits: Should there be a max number of terminals per project? Probably not enforced, but the UI should handle 10+ terminals gracefully (tabs scroll or overflow).

🤖 Generated with Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
       blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
      }
      } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
      })();
      (function(){
      try {
      var __m = "github.com";
      var __re = new RegExp('^' + "github\\.com" + '
      
      Skip to content

      PRD: Terminal Abstraction & Frontend-Owned UX #1

      Description

      @DanielGGordon

      Problem

      DanCode's terminal experience is built around tmux as both the backend process manager and the user-facing UX layer. Tmux concepts — grouped sessions, window indices, status bars, attach commands — leak directly into the browser experience. Layout management is split between React and tmux, causing quirks with mouse handling, resize behavior, and multiplexing. Creating a "grouped connection session" per browser tab just to achieve independent window views is a workaround, not an architecture. The result is a functional but hacky experience that fights tmux's design rather than building on top of it cleanly.

      Solution

      Rearchitect the terminal layer so the frontend fully owns the terminal UX — layout, multiplexing, pane management, labels, and resize — while the server provides a clean terminal abstraction backed by direct PTY connections. Tmux operates invisibly as a persistence layer: each server-managed PTY is backed by a hidden tmux session so processes survive browser disconnects and server restarts, but no tmux concept is ever exposed to the client. The server maintains an output ring buffer per terminal for seamless reconnection replay. A file explorer is added as a new panel type alongside terminals. A mobile-first PWA experience provides monitoring and light interaction from a phone — swipe navigation, a system-wide shortcut bar, and a status dashboard designed for checking on running agents. As a stretch goal, sessions remain directly accessible from the host environment for power users who want to drop into a shell.

      Requirements

      Phase 1: Direct PTY & Frontend-Owned Layout

      Server — TerminalManager

      • Server exposes a terminal CRUD API: create, list, get, destroy terminals
      • Each terminal is identified by a unique ID (not a tmux window index)
      • Creating a terminal spawns a real PTY process (bash or user's default shell) via node-pty
      • Terminals are associated with a project (by slug) and have a user-defined label
      • Terminal creation accepts optional command param (e.g., claude --dangerously-skip-permissions) run inside the shell
      • Terminal creation accepts optional cwd param (defaults to project path)
      • Server maintains an output ring buffer (~50KB) per terminal for reconnection replay
      • When a browser disconnects, the PTY stays alive and continues buffering output
      • When a browser reconnects to a terminal ID, the buffered output is replayed before resuming live streaming
      • resize events from the client resize the PTY directly (no tmux intermediary)
      • Terminal metadata (id, project, label, state) is persisted in ~/.dancode/terminals/ so the server can rediscover terminals after restart

      Server — API

      • POST /api/terminals — create terminal (params: projectSlug, label, command?, cwd?) → returns terminal object with id
      • GET /api/terminals?project=<slug> — list terminals for a project
      • GET /api/terminals/:id — get terminal metadata
      • PATCH /api/terminals/:id — update label or other metadata
      • DELETE /api/terminals/:id — kill PTY process and remove terminal
      • WebSocket /terminal/:id — bidirectional I/O (replaces current /terminal namespace with query params)

      Client — TerminalLayout (replaces PaneLayout)

      • Layout is 100% frontend-owned: no fetching tmux windows from server
      • Supports split view (side-by-side, resizable dividers) and tabbed view
      • Mobile (<768px) always uses tabbed view
      • Users can create new terminals from the UI ("+" button) — opens in project directory by default
      • Users can close/kill terminals from the UI (with confirmation)
      • Users can rename terminal labels inline
      • Users can reorder tabs via drag-and-drop
      • Users can toggle terminals visible/hidden without killing them
      • Layout state (which terminals are visible, order, split sizes, active tab) persisted in project config
      • Each terminal pane renders an xterm.js instance connected via WebSocket to /terminal/:id
      • Click-to-focus with visual indicator (accent bar) on focused pane
      • Ctrl+Scroll font sizing, Ctrl+C copy, Ctrl+V paste all preserved

      Client — Project Creation (updated)

      • Remove "Adopt existing tmux session" flow entirely
      • New project creates 2 default terminals: "CLI" (shell) and "Claude" (runs claude command)
      • No hardcoded pane types — terminals are generic, labels are just strings

      Migration / Cleanup

      • Remove tmux.js module (or gut it for Phase 2 reuse)
      • Remove grouped session logic from terminal.js
      • Remove tmux status dots, attach command bar, and tmux context menu items from Sidebar
      • Remove tmuxSession, showTmuxCommands from project config schema
      • Remove /api/tmux-status and /api/tmux/sessions endpoints

      Phase 2: Invisible Tmux Persistence

      Server — Tmux as Hidden Backend

      • Each PTY is spawned inside a hidden tmux session rather than as a bare process
      • Tmux session naming is deterministic and internal: dancode-{projectSlug}-{terminalId}
      • The server interacts with the PTY via node-pty spawning a shell inside the tmux session (NOT tmux attach)
      • Tmux is never referenced in any client-facing API response or WebSocket message
      • On server startup, the TerminalManager scans for existing dancode-* tmux sessions and reconciles with terminal metadata files — orphaned sessions are reclaimed, metadata without sessions is cleaned up
      • If a PTY handle is lost (server crash) but the tmux session survives, the server reattaches transparently on restart
      • Output ring buffer is repopulated from tmux scrollback on reattach (tmux capture-pane)

      Server — Resilience

      • Terminal metadata files in ~/.dancode/terminals/ include the backing tmux session name
      • Server restart triggers full reconciliation: match metadata ↔ tmux sessions ↔ PTY handles
      • Terminals that were running before a crash appear as "reconnecting" in the UI, then resume automatically
      • Stale metadata (tmux session gone, no PTY) is cleaned up with a warning log

      Stretch Goal: Direct Host Access

      • Tmux sessions use clean, human-readable names (dancode-{projectSlug}-{label})
      • A user can tmux ls on the host and see all DanCode-managed sessions
      • A user can tmux attach -t dancode-myproject-cli to directly access a terminal
      • Optional: a dancode CLI helper script that lists projects and attaches to sessions (dancode attach myproject cli)
      • Input from direct tmux attach and browser are interleaved — both see the same terminal
      • This is explicitly a power-user feature; the web UI remains the primary interface

      Phase 3: File Explorer

      Server — File System API

      • GET /api/files?path=<dir> — list directory contents (name, type, size, modified date), scoped to project path
      • GET /api/files/read?path=<file> — read file contents (with size limit, e.g., 1MB)
      • PUT /api/files/write — write file contents (path + content in body)
      • POST /api/files/mkdir — create directory
      • POST /api/files/rename — rename/move file or directory
      • DELETE /api/files — delete file or directory (with confirmation)
      • All paths are validated to be within the project directory (no path traversal)
      • Symlinks are followed but validated to stay within project bounds
      • Hidden files (dotfiles) shown with a toggle

      Client — File Explorer Panel

      • Tree view panel that can appear alongside terminal panes (left side or as a tab)
      • Lazy-loaded: directories expand on click, fetching contents on demand
      • File icons by extension (simple icon set — folders, code files, config, images, etc.)
      • Single-click to select, double-click to... (future: open in editor; for now: copy path to clipboard or insert into focused terminal)
      • Right-click context menu: rename, delete, copy path, new file, new folder
      • Drag-and-drop files to terminal panes to insert the file path
      • Search/filter within the current directory tree
      • Respects .gitignore patterns by default (toggle to show ignored files)
      • Collapsible — can be hidden to maximize terminal space
      • File explorer state (expanded directories, scroll position) persisted per project

      Integration with Terminals

      • "Open terminal here" context menu option on directories — creates a new terminal with cwd set to that directory
      • Dragging a file onto a terminal inserts the relative path

      Phase 4: Mobile Experience & PWA

      PWA Foundation

      • Add web app manifest (manifest.json) with app name, icons, theme color (Solarized Dark), display: standalone
      • Add service worker for offline shell (cache app assets, show "offline" state when server unreachable)
      • "Add to Home Screen" works on Android Chrome — launches full-screen, no browser chrome
      • Viewport and touch meta tags for native-feeling mobile experience
      • App icon and splash screen in Solarized Dark branding

      Mobile Layout — Monitoring-First Design

      • Default mobile view is a status dashboard, not a terminal
      • Dashboard shows all projects in a card list: project name, terminal labels, activity indicator (last output timestamp, active/idle/stopped), and a preview of the last few lines of output per terminal
      • Tap a project card to expand and see its terminals
      • Tap a terminal to enter full-screen terminal view
      • Back gesture or button returns to dashboard

      Terminal View — Mobile Optimized

      • Terminal takes full screen (no sidebar, no header — just the terminal + a thin top bar with back button and terminal label)
      • Read-first: keyboard is dismissed by default, terminal output is scrollable
      • Tap the terminal area or a "keyboard" button to enter input mode (soft keyboard appears)
      • Auto-dismiss keyboard after pressing Enter (configurable — some users may want it to stay)

      Shortcut Bar

      • Persistent bottom bar when keyboard is active, sitting above the soft keyboard
      • System-wide shortcuts (not per-project): Ctrl+C, Ctrl+V, Ctrl+D, Tab, Up Arrow, Down Arrow, Esc
      • Scrollable horizontally if more shortcuts than screen width
      • Buttons are touch-friendly (min 44px tap targets)
      • Shortcut bar hides when keyboard is dismissed (read mode)

      Swipe Navigation

      • Swipe left/right between terminals within the same project
      • Dot indicators at top show which terminal is active (like iOS page dots)
      • Swipe from left edge opens project list (drawer-style)
      • Swipe down from top of terminal to return to dashboard

      Mobile-Specific Features

      • Pull-to-refresh on dashboard to update activity status
      • Long-press a project card for quick actions (kill all terminals, open CLI, open Claude)
      • Haptic feedback on shortcut button taps (where supported)
      • Font size respects system accessibility settings, with pinch-to-zoom override
      • Landscape mode: terminal uses full width, shortcut bar along the bottom

      Responsive Breakpoints

      • < 480px (phone portrait): dashboard cards stack, single terminal full-screen, shortcut bar active
      • 480–768px (phone landscape / small tablet): same as above but wider terminal
      • 768–1024px (tablet): optional split view (2 terminals), shortcut bar available but optional
      • > 1024px (desktop): full desktop layout, no shortcut bar, no swipe navigation

      UX / Interface (all phases)

      • Solarized Dark theme maintained throughout
      • No tmux concepts visible anywhere in the UI — no session names, attach commands, or status bars referencing tmux
      • All terminal management is presented as "terminals" not "panes" or "windows"
      • Smooth reconnection UX: terminal shows "Reconnecting..." overlay, then replays buffer and resumes
      • Connection state indicators per terminal (connected / reconnecting / disconnected)
      • Responsive: splits on desktop, tabs on mobile, file explorer collapses on narrow viewports

      Data / Persistence

      • Terminal metadata: ~/.dancode/terminals/{terminalId}.json — id, projectSlug, label, tmuxSessionName, createdAt, lastActivity
      • Project config: ~/.dancode/projects/{slug}.json — updated schema drops tmux fields, adds layout object for frontend state
      • Output ring buffer: in-memory only (not persisted to disk) — repopulated from tmux scrollback on restart
      • File explorer state: persisted in project config (expanded paths, panel visibility)

      Technical Decisions

      • Stack: No changes — Node.js, Express, Socket.io, React, Vite, xterm.js, Tailwind. Proven and working.
      • Architecture: New TerminalManager class server-side replaces current tmux.js + terminal.js. Owns PTY lifecycle, output buffering, and reconnection. Single responsibility: manage terminal processes.
      • Data model: Terminals become first-class entities with their own ID and metadata file, decoupled from tmux window indices. Projects reference terminals by ID, not by pane index.
      • API surface: Clean REST for terminal CRUD + WebSocket per terminal for I/O. No tmux vocabulary in any API contract. File system API for explorer with strict path validation.
      • Persistence strategy: Phase 1 uses bare PTYs (sessions lost on server restart). Phase 2 adds invisible tmux backing for full persistence. This phasing means Phase 1 is shippable without tmux complexity.
      • Output buffer: ~50KB circular buffer per terminal, in-memory. Enough to replay a screenful+ on reconnect. Not persisted — tmux scrollback is the durable copy (Phase 2).
      • Mobile: PWA (manifest + service worker), not a native app. Touch gestures via standard DOM events or a lightweight library (e.g., Hammer.js). Shortcut bar is a React component, not a native keyboard extension. Responsive breakpoints handled with Tailwind's existing breakpoint system.
      • Testing: Existing Vitest + Playwright stack. New tests for TerminalManager lifecycle, reconnection replay, file system API path validation. Playwright mobile emulation for PWA/mobile layout tests. Visual tests updated to assert on new layout.

      Out of Scope

      • Code editor / Monaco integration (future phase, after file explorer proves useful)
      • Ralph UI controls (separate PRD)
      • Multi-server management
      • Multi-user / role-based access
      • Light mode / theming beyond Solarized Dark
      • Session recording / playback
      • Git integration in the UI
      • Native Android/iOS app (PWA covers mobile; native wrapper via Capacitor is a future option if push notifications or app store presence become important)
      • Offline terminal interaction (PWA caches the app shell, but terminals require server connectivity)

      Open Questions

      • Shell selection: Should terminal creation auto-detect the user's default shell ($SHELL) or always use bash? Leaning toward $SHELL with bash fallback.
      • Buffer size: 50KB per terminal is a starting point. Should this be configurable per-project? Probably not worth the complexity initially.
      • File explorer size limits: What's the right max file size to read via the API? 1MB seems reasonable for text files. Binary files should be blocked or download-only.
      • Terminal limits: Should there be a max number of terminals per project? Probably not enforced, but the UI should handle 10+ terminals gracefully (tabs scroll or overflow).

      🤖 Generated with Claude Code

      Metadata

      Metadata

      Assignees

      No one assigned

        Labels

        No labels
        No labels

        Projects

        No projects

          Milestone

          No milestone

          Relationships

          None yet

          Development

          No branches or pull requests

          Issue actions

          , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
          Skip to content

          PRD: Terminal Abstraction & Frontend-Owned UX #1

          Description

          @DanielGGordon

          Problem

          DanCode's terminal experience is built around tmux as both the backend process manager and the user-facing UX layer. Tmux concepts — grouped sessions, window indices, status bars, attach commands — leak directly into the browser experience. Layout management is split between React and tmux, causing quirks with mouse handling, resize behavior, and multiplexing. Creating a "grouped connection session" per browser tab just to achieve independent window views is a workaround, not an architecture. The result is a functional but hacky experience that fights tmux's design rather than building on top of it cleanly.

          Solution

          Rearchitect the terminal layer so the frontend fully owns the terminal UX — layout, multiplexing, pane management, labels, and resize — while the server provides a clean terminal abstraction backed by direct PTY connections. Tmux operates invisibly as a persistence layer: each server-managed PTY is backed by a hidden tmux session so processes survive browser disconnects and server restarts, but no tmux concept is ever exposed to the client. The server maintains an output ring buffer per terminal for seamless reconnection replay. A file explorer is added as a new panel type alongside terminals. A mobile-first PWA experience provides monitoring and light interaction from a phone — swipe navigation, a system-wide shortcut bar, and a status dashboard designed for checking on running agents. As a stretch goal, sessions remain directly accessible from the host environment for power users who want to drop into a shell.

          Requirements

          Phase 1: Direct PTY & Frontend-Owned Layout

          Server — TerminalManager

          • Server exposes a terminal CRUD API: create, list, get, destroy terminals
          • Each terminal is identified by a unique ID (not a tmux window index)
          • Creating a terminal spawns a real PTY process (bash or user's default shell) via node-pty
          • Terminals are associated with a project (by slug) and have a user-defined label
          • Terminal creation accepts optional command param (e.g., claude --dangerously-skip-permissions) run inside the shell
          • Terminal creation accepts optional cwd param (defaults to project path)
          • Server maintains an output ring buffer (~50KB) per terminal for reconnection replay
          • When a browser disconnects, the PTY stays alive and continues buffering output
          • When a browser reconnects to a terminal ID, the buffered output is replayed before resuming live streaming
          • resize events from the client resize the PTY directly (no tmux intermediary)
          • Terminal metadata (id, project, label, state) is persisted in ~/.dancode/terminals/ so the server can rediscover terminals after restart

          Server — API

          • POST /api/terminals — create terminal (params: projectSlug, label, command?, cwd?) → returns terminal object with id
          • GET /api/terminals?project=<slug> — list terminals for a project
          • GET /api/terminals/:id — get terminal metadata
          • PATCH /api/terminals/:id — update label or other metadata
          • DELETE /api/terminals/:id — kill PTY process and remove terminal
          • WebSocket /terminal/:id — bidirectional I/O (replaces current /terminal namespace with query params)

          Client — TerminalLayout (replaces PaneLayout)

          • Layout is 100% frontend-owned: no fetching tmux windows from server
          • Supports split view (side-by-side, resizable dividers) and tabbed view
          • Mobile (<768px) always uses tabbed view
          • Users can create new terminals from the UI ("+" button) — opens in project directory by default
          • Users can close/kill terminals from the UI (with confirmation)
          • Users can rename terminal labels inline
          • Users can reorder tabs via drag-and-drop
          • Users can toggle terminals visible/hidden without killing them
          • Layout state (which terminals are visible, order, split sizes, active tab) persisted in project config
          • Each terminal pane renders an xterm.js instance connected via WebSocket to /terminal/:id
          • Click-to-focus with visual indicator (accent bar) on focused pane
          • Ctrl+Scroll font sizing, Ctrl+C copy, Ctrl+V paste all preserved

          Client — Project Creation (updated)

          • Remove "Adopt existing tmux session" flow entirely
          • New project creates 2 default terminals: "CLI" (shell) and "Claude" (runs claude command)
          • No hardcoded pane types — terminals are generic, labels are just strings

          Migration / Cleanup

          • Remove tmux.js module (or gut it for Phase 2 reuse)
          • Remove grouped session logic from terminal.js
          • Remove tmux status dots, attach command bar, and tmux context menu items from Sidebar
          • Remove tmuxSession, showTmuxCommands from project config schema
          • Remove /api/tmux-status and /api/tmux/sessions endpoints

          Phase 2: Invisible Tmux Persistence

          Server — Tmux as Hidden Backend

          • Each PTY is spawned inside a hidden tmux session rather than as a bare process
          • Tmux session naming is deterministic and internal: dancode-{projectSlug}-{terminalId}
          • The server interacts with the PTY via node-pty spawning a shell inside the tmux session (NOT tmux attach)
          • Tmux is never referenced in any client-facing API response or WebSocket message
          • On server startup, the TerminalManager scans for existing dancode-* tmux sessions and reconciles with terminal metadata files — orphaned sessions are reclaimed, metadata without sessions is cleaned up
          • If a PTY handle is lost (server crash) but the tmux session survives, the server reattaches transparently on restart
          • Output ring buffer is repopulated from tmux scrollback on reattach (tmux capture-pane)

          Server — Resilience

          • Terminal metadata files in ~/.dancode/terminals/ include the backing tmux session name
          • Server restart triggers full reconciliation: match metadata ↔ tmux sessions ↔ PTY handles
          • Terminals that were running before a crash appear as "reconnecting" in the UI, then resume automatically
          • Stale metadata (tmux session gone, no PTY) is cleaned up with a warning log

          Stretch Goal: Direct Host Access

          • Tmux sessions use clean, human-readable names (dancode-{projectSlug}-{label})
          • A user can tmux ls on the host and see all DanCode-managed sessions
          • A user can tmux attach -t dancode-myproject-cli to directly access a terminal
          • Optional: a dancode CLI helper script that lists projects and attaches to sessions (dancode attach myproject cli)
          • Input from direct tmux attach and browser are interleaved — both see the same terminal
          • This is explicitly a power-user feature; the web UI remains the primary interface

          Phase 3: File Explorer

          Server — File System API

          • GET /api/files?path=<dir> — list directory contents (name, type, size, modified date), scoped to project path
          • GET /api/files/read?path=<file> — read file contents (with size limit, e.g., 1MB)
          • PUT /api/files/write — write file contents (path + content in body)
          • POST /api/files/mkdir — create directory
          • POST /api/files/rename — rename/move file or directory
          • DELETE /api/files — delete file or directory (with confirmation)
          • All paths are validated to be within the project directory (no path traversal)
          • Symlinks are followed but validated to stay within project bounds
          • Hidden files (dotfiles) shown with a toggle

          Client — File Explorer Panel

          • Tree view panel that can appear alongside terminal panes (left side or as a tab)
          • Lazy-loaded: directories expand on click, fetching contents on demand
          • File icons by extension (simple icon set — folders, code files, config, images, etc.)
          • Single-click to select, double-click to... (future: open in editor; for now: copy path to clipboard or insert into focused terminal)
          • Right-click context menu: rename, delete, copy path, new file, new folder
          • Drag-and-drop files to terminal panes to insert the file path
          • Search/filter within the current directory tree
          • Respects .gitignore patterns by default (toggle to show ignored files)
          • Collapsible — can be hidden to maximize terminal space
          • File explorer state (expanded directories, scroll position) persisted per project

          Integration with Terminals

          • "Open terminal here" context menu option on directories — creates a new terminal with cwd set to that directory
          • Dragging a file onto a terminal inserts the relative path

          Phase 4: Mobile Experience & PWA

          PWA Foundation

          • Add web app manifest (manifest.json) with app name, icons, theme color (Solarized Dark), display: standalone
          • Add service worker for offline shell (cache app assets, show "offline" state when server unreachable)
          • "Add to Home Screen" works on Android Chrome — launches full-screen, no browser chrome
          • Viewport and touch meta tags for native-feeling mobile experience
          • App icon and splash screen in Solarized Dark branding

          Mobile Layout — Monitoring-First Design

          • Default mobile view is a status dashboard, not a terminal
          • Dashboard shows all projects in a card list: project name, terminal labels, activity indicator (last output timestamp, active/idle/stopped), and a preview of the last few lines of output per terminal
          • Tap a project card to expand and see its terminals
          • Tap a terminal to enter full-screen terminal view
          • Back gesture or button returns to dashboard

          Terminal View — Mobile Optimized

          • Terminal takes full screen (no sidebar, no header — just the terminal + a thin top bar with back button and terminal label)
          • Read-first: keyboard is dismissed by default, terminal output is scrollable
          • Tap the terminal area or a "keyboard" button to enter input mode (soft keyboard appears)
          • Auto-dismiss keyboard after pressing Enter (configurable — some users may want it to stay)

          Shortcut Bar

          • Persistent bottom bar when keyboard is active, sitting above the soft keyboard
          • System-wide shortcuts (not per-project): Ctrl+C, Ctrl+V, Ctrl+D, Tab, Up Arrow, Down Arrow, Esc
          • Scrollable horizontally if more shortcuts than screen width
          • Buttons are touch-friendly (min 44px tap targets)
          • Shortcut bar hides when keyboard is dismissed (read mode)

          Swipe Navigation

          • Swipe left/right between terminals within the same project
          • Dot indicators at top show which terminal is active (like iOS page dots)
          • Swipe from left edge opens project list (drawer-style)
          • Swipe down from top of terminal to return to dashboard

          Mobile-Specific Features

          • Pull-to-refresh on dashboard to update activity status
          • Long-press a project card for quick actions (kill all terminals, open CLI, open Claude)
          • Haptic feedback on shortcut button taps (where supported)
          • Font size respects system accessibility settings, with pinch-to-zoom override
          • Landscape mode: terminal uses full width, shortcut bar along the bottom

          Responsive Breakpoints

          • < 480px (phone portrait): dashboard cards stack, single terminal full-screen, shortcut bar active
          • 480–768px (phone landscape / small tablet): same as above but wider terminal
          • 768–1024px (tablet): optional split view (2 terminals), shortcut bar available but optional
          • > 1024px (desktop): full desktop layout, no shortcut bar, no swipe navigation

          UX / Interface (all phases)

          • Solarized Dark theme maintained throughout
          • No tmux concepts visible anywhere in the UI — no session names, attach commands, or status bars referencing tmux
          • All terminal management is presented as "terminals" not "panes" or "windows"
          • Smooth reconnection UX: terminal shows "Reconnecting..." overlay, then replays buffer and resumes
          • Connection state indicators per terminal (connected / reconnecting / disconnected)
          • Responsive: splits on desktop, tabs on mobile, file explorer collapses on narrow viewports

          Data / Persistence

          • Terminal metadata: ~/.dancode/terminals/{terminalId}.json — id, projectSlug, label, tmuxSessionName, createdAt, lastActivity
          • Project config: ~/.dancode/projects/{slug}.json — updated schema drops tmux fields, adds layout object for frontend state
          • Output ring buffer: in-memory only (not persisted to disk) — repopulated from tmux scrollback on restart
          • File explorer state: persisted in project config (expanded paths, panel visibility)

          Technical Decisions

          • Stack: No changes — Node.js, Express, Socket.io, React, Vite, xterm.js, Tailwind. Proven and working.
          • Architecture: New TerminalManager class server-side replaces current tmux.js + terminal.js. Owns PTY lifecycle, output buffering, and reconnection. Single responsibility: manage terminal processes.
          • Data model: Terminals become first-class entities with their own ID and metadata file, decoupled from tmux window indices. Projects reference terminals by ID, not by pane index.
          • API surface: Clean REST for terminal CRUD + WebSocket per terminal for I/O. No tmux vocabulary in any API contract. File system API for explorer with strict path validation.
          • Persistence strategy: Phase 1 uses bare PTYs (sessions lost on server restart). Phase 2 adds invisible tmux backing for full persistence. This phasing means Phase 1 is shippable without tmux complexity.
          • Output buffer: ~50KB circular buffer per terminal, in-memory. Enough to replay a screenful+ on reconnect. Not persisted — tmux scrollback is the durable copy (Phase 2).
          • Mobile: PWA (manifest + service worker), not a native app. Touch gestures via standard DOM events or a lightweight library (e.g., Hammer.js). Shortcut bar is a React component, not a native keyboard extension. Responsive breakpoints handled with Tailwind's existing breakpoint system.
          • Testing: Existing Vitest + Playwright stack. New tests for TerminalManager lifecycle, reconnection replay, file system API path validation. Playwright mobile emulation for PWA/mobile layout tests. Visual tests updated to assert on new layout.

          Out of Scope

          • Code editor / Monaco integration (future phase, after file explorer proves useful)
          • Ralph UI controls (separate PRD)
          • Multi-server management
          • Multi-user / role-based access
          • Light mode / theming beyond Solarized Dark
          • Session recording / playback
          • Git integration in the UI
          • Native Android/iOS app (PWA covers mobile; native wrapper via Capacitor is a future option if push notifications or app store presence become important)
          • Offline terminal interaction (PWA caches the app shell, but terminals require server connectivity)

          Open Questions

          • Shell selection: Should terminal creation auto-detect the user's default shell ($SHELL) or always use bash? Leaning toward $SHELL with bash fallback.
          • Buffer size: 50KB per terminal is a starting point. Should this be configurable per-project? Probably not worth the complexity initially.
          • File explorer size limits: What's the right max file size to read via the API? 1MB seems reasonable for text files. Binary files should be blocked or download-only.
          • Terminal limits: Should there be a max number of terminals per project? Probably not enforced, but the UI should handle 10+ terminals gracefully (tabs scroll or overflow).

          🤖 Generated with Claude Code

          Metadata

          Metadata

          Assignees

          No one assigned

            Labels

            No labels
            No labels

            Projects

            No projects

              Milestone

              No milestone

              Relationships

              None yet

              Development

              No branches or pull requests

              Issue actions

              , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
              Skip to content

              PRD: Terminal Abstraction & Frontend-Owned UX #1

              Description

              @DanielGGordon

              Problem

              DanCode's terminal experience is built around tmux as both the backend process manager and the user-facing UX layer. Tmux concepts — grouped sessions, window indices, status bars, attach commands — leak directly into the browser experience. Layout management is split between React and tmux, causing quirks with mouse handling, resize behavior, and multiplexing. Creating a "grouped connection session" per browser tab just to achieve independent window views is a workaround, not an architecture. The result is a functional but hacky experience that fights tmux's design rather than building on top of it cleanly.

              Solution

              Rearchitect the terminal layer so the frontend fully owns the terminal UX — layout, multiplexing, pane management, labels, and resize — while the server provides a clean terminal abstraction backed by direct PTY connections. Tmux operates invisibly as a persistence layer: each server-managed PTY is backed by a hidden tmux session so processes survive browser disconnects and server restarts, but no tmux concept is ever exposed to the client. The server maintains an output ring buffer per terminal for seamless reconnection replay. A file explorer is added as a new panel type alongside terminals. A mobile-first PWA experience provides monitoring and light interaction from a phone — swipe navigation, a system-wide shortcut bar, and a status dashboard designed for checking on running agents. As a stretch goal, sessions remain directly accessible from the host environment for power users who want to drop into a shell.

              Requirements

              Phase 1: Direct PTY & Frontend-Owned Layout

              Server — TerminalManager

              • Server exposes a terminal CRUD API: create, list, get, destroy terminals
              • Each terminal is identified by a unique ID (not a tmux window index)
              • Creating a terminal spawns a real PTY process (bash or user's default shell) via node-pty
              • Terminals are associated with a project (by slug) and have a user-defined label
              • Terminal creation accepts optional command param (e.g., claude --dangerously-skip-permissions) run inside the shell
              • Terminal creation accepts optional cwd param (defaults to project path)
              • Server maintains an output ring buffer (~50KB) per terminal for reconnection replay
              • When a browser disconnects, the PTY stays alive and continues buffering output
              • When a browser reconnects to a terminal ID, the buffered output is replayed before resuming live streaming
              • resize events from the client resize the PTY directly (no tmux intermediary)
              • Terminal metadata (id, project, label, state) is persisted in ~/.dancode/terminals/ so the server can rediscover terminals after restart

              Server — API

              • POST /api/terminals — create terminal (params: projectSlug, label, command?, cwd?) → returns terminal object with id
              • GET /api/terminals?project=<slug> — list terminals for a project
              • GET /api/terminals/:id — get terminal metadata
              • PATCH /api/terminals/:id — update label or other metadata
              • DELETE /api/terminals/:id — kill PTY process and remove terminal
              • WebSocket /terminal/:id — bidirectional I/O (replaces current /terminal namespace with query params)

              Client — TerminalLayout (replaces PaneLayout)

              • Layout is 100% frontend-owned: no fetching tmux windows from server
              • Supports split view (side-by-side, resizable dividers) and tabbed view
              • Mobile (<768px) always uses tabbed view
              • Users can create new terminals from the UI ("+" button) — opens in project directory by default
              • Users can close/kill terminals from the UI (with confirmation)
              • Users can rename terminal labels inline
              • Users can reorder tabs via drag-and-drop
              • Users can toggle terminals visible/hidden without killing them
              • Layout state (which terminals are visible, order, split sizes, active tab) persisted in project config
              • Each terminal pane renders an xterm.js instance connected via WebSocket to /terminal/:id
              • Click-to-focus with visual indicator (accent bar) on focused pane
              • Ctrl+Scroll font sizing, Ctrl+C copy, Ctrl+V paste all preserved

              Client — Project Creation (updated)

              • Remove "Adopt existing tmux session" flow entirely
              • New project creates 2 default terminals: "CLI" (shell) and "Claude" (runs claude command)
              • No hardcoded pane types — terminals are generic, labels are just strings

              Migration / Cleanup

              • Remove tmux.js module (or gut it for Phase 2 reuse)
              • Remove grouped session logic from terminal.js
              • Remove tmux status dots, attach command bar, and tmux context menu items from Sidebar
              • Remove tmuxSession, showTmuxCommands from project config schema
              • Remove /api/tmux-status and /api/tmux/sessions endpoints

              Phase 2: Invisible Tmux Persistence

              Server — Tmux as Hidden Backend

              • Each PTY is spawned inside a hidden tmux session rather than as a bare process
              • Tmux session naming is deterministic and internal: dancode-{projectSlug}-{terminalId}
              • The server interacts with the PTY via node-pty spawning a shell inside the tmux session (NOT tmux attach)
              • Tmux is never referenced in any client-facing API response or WebSocket message
              • On server startup, the TerminalManager scans for existing dancode-* tmux sessions and reconciles with terminal metadata files — orphaned sessions are reclaimed, metadata without sessions is cleaned up
              • If a PTY handle is lost (server crash) but the tmux session survives, the server reattaches transparently on restart
              • Output ring buffer is repopulated from tmux scrollback on reattach (tmux capture-pane)

              Server — Resilience

              • Terminal metadata files in ~/.dancode/terminals/ include the backing tmux session name
              • Server restart triggers full reconciliation: match metadata ↔ tmux sessions ↔ PTY handles
              • Terminals that were running before a crash appear as "reconnecting" in the UI, then resume automatically
              • Stale metadata (tmux session gone, no PTY) is cleaned up with a warning log

              Stretch Goal: Direct Host Access

              • Tmux sessions use clean, human-readable names (dancode-{projectSlug}-{label})
              • A user can tmux ls on the host and see all DanCode-managed sessions
              • A user can tmux attach -t dancode-myproject-cli to directly access a terminal
              • Optional: a dancode CLI helper script that lists projects and attaches to sessions (dancode attach myproject cli)
              • Input from direct tmux attach and browser are interleaved — both see the same terminal
              • This is explicitly a power-user feature; the web UI remains the primary interface

              Phase 3: File Explorer

              Server — File System API

              • GET /api/files?path=<dir> — list directory contents (name, type, size, modified date), scoped to project path
              • GET /api/files/read?path=<file> — read file contents (with size limit, e.g., 1MB)
              • PUT /api/files/write — write file contents (path + content in body)
              • POST /api/files/mkdir — create directory
              • POST /api/files/rename — rename/move file or directory
              • DELETE /api/files — delete file or directory (with confirmation)
              • All paths are validated to be within the project directory (no path traversal)
              • Symlinks are followed but validated to stay within project bounds
              • Hidden files (dotfiles) shown with a toggle

              Client — File Explorer Panel

              • Tree view panel that can appear alongside terminal panes (left side or as a tab)
              • Lazy-loaded: directories expand on click, fetching contents on demand
              • File icons by extension (simple icon set — folders, code files, config, images, etc.)
              • Single-click to select, double-click to... (future: open in editor; for now: copy path to clipboard or insert into focused terminal)
              • Right-click context menu: rename, delete, copy path, new file, new folder
              • Drag-and-drop files to terminal panes to insert the file path
              • Search/filter within the current directory tree
              • Respects .gitignore patterns by default (toggle to show ignored files)
              • Collapsible — can be hidden to maximize terminal space
              • File explorer state (expanded directories, scroll position) persisted per project

              Integration with Terminals

              • "Open terminal here" context menu option on directories — creates a new terminal with cwd set to that directory
              • Dragging a file onto a terminal inserts the relative path

              Phase 4: Mobile Experience & PWA

              PWA Foundation

              • Add web app manifest (manifest.json) with app name, icons, theme color (Solarized Dark), display: standalone
              • Add service worker for offline shell (cache app assets, show "offline" state when server unreachable)
              • "Add to Home Screen" works on Android Chrome — launches full-screen, no browser chrome
              • Viewport and touch meta tags for native-feeling mobile experience
              • App icon and splash screen in Solarized Dark branding

              Mobile Layout — Monitoring-First Design

              • Default mobile view is a status dashboard, not a terminal
              • Dashboard shows all projects in a card list: project name, terminal labels, activity indicator (last output timestamp, active/idle/stopped), and a preview of the last few lines of output per terminal
              • Tap a project card to expand and see its terminals
              • Tap a terminal to enter full-screen terminal view
              • Back gesture or button returns to dashboard

              Terminal View — Mobile Optimized

              • Terminal takes full screen (no sidebar, no header — just the terminal + a thin top bar with back button and terminal label)
              • Read-first: keyboard is dismissed by default, terminal output is scrollable
              • Tap the terminal area or a "keyboard" button to enter input mode (soft keyboard appears)
              • Auto-dismiss keyboard after pressing Enter (configurable — some users may want it to stay)

              Shortcut Bar

              • Persistent bottom bar when keyboard is active, sitting above the soft keyboard
              • System-wide shortcuts (not per-project): Ctrl+C, Ctrl+V, Ctrl+D, Tab, Up Arrow, Down Arrow, Esc
              • Scrollable horizontally if more shortcuts than screen width
              • Buttons are touch-friendly (min 44px tap targets)
              • Shortcut bar hides when keyboard is dismissed (read mode)

              Swipe Navigation

              • Swipe left/right between terminals within the same project
              • Dot indicators at top show which terminal is active (like iOS page dots)
              • Swipe from left edge opens project list (drawer-style)
              • Swipe down from top of terminal to return to dashboard

              Mobile-Specific Features

              • Pull-to-refresh on dashboard to update activity status
              • Long-press a project card for quick actions (kill all terminals, open CLI, open Claude)
              • Haptic feedback on shortcut button taps (where supported)
              • Font size respects system accessibility settings, with pinch-to-zoom override
              • Landscape mode: terminal uses full width, shortcut bar along the bottom

              Responsive Breakpoints

              • < 480px (phone portrait): dashboard cards stack, single terminal full-screen, shortcut bar active
              • 480–768px (phone landscape / small tablet): same as above but wider terminal
              • 768–1024px (tablet): optional split view (2 terminals), shortcut bar available but optional
              • > 1024px (desktop): full desktop layout, no shortcut bar, no swipe navigation

              UX / Interface (all phases)

              • Solarized Dark theme maintained throughout
              • No tmux concepts visible anywhere in the UI — no session names, attach commands, or status bars referencing tmux
              • All terminal management is presented as "terminals" not "panes" or "windows"
              • Smooth reconnection UX: terminal shows "Reconnecting..." overlay, then replays buffer and resumes
              • Connection state indicators per terminal (connected / reconnecting / disconnected)
              • Responsive: splits on desktop, tabs on mobile, file explorer collapses on narrow viewports

              Data / Persistence

              • Terminal metadata: ~/.dancode/terminals/{terminalId}.json — id, projectSlug, label, tmuxSessionName, createdAt, lastActivity
              • Project config: ~/.dancode/projects/{slug}.json — updated schema drops tmux fields, adds layout object for frontend state
              • Output ring buffer: in-memory only (not persisted to disk) — repopulated from tmux scrollback on restart
              • File explorer state: persisted in project config (expanded paths, panel visibility)

              Technical Decisions

              • Stack: No changes — Node.js, Express, Socket.io, React, Vite, xterm.js, Tailwind. Proven and working.
              • Architecture: New TerminalManager class server-side replaces current tmux.js + terminal.js. Owns PTY lifecycle, output buffering, and reconnection. Single responsibility: manage terminal processes.
              • Data model: Terminals become first-class entities with their own ID and metadata file, decoupled from tmux window indices. Projects reference terminals by ID, not by pane index.
              • API surface: Clean REST for terminal CRUD + WebSocket per terminal for I/O. No tmux vocabulary in any API contract. File system API for explorer with strict path validation.
              • Persistence strategy: Phase 1 uses bare PTYs (sessions lost on server restart). Phase 2 adds invisible tmux backing for full persistence. This phasing means Phase 1 is shippable without tmux complexity.
              • Output buffer: ~50KB circular buffer per terminal, in-memory. Enough to replay a screenful+ on reconnect. Not persisted — tmux scrollback is the durable copy (Phase 2).
              • Mobile: PWA (manifest + service worker), not a native app. Touch gestures via standard DOM events or a lightweight library (e.g., Hammer.js). Shortcut bar is a React component, not a native keyboard extension. Responsive breakpoints handled with Tailwind's existing breakpoint system.
              • Testing: Existing Vitest + Playwright stack. New tests for TerminalManager lifecycle, reconnection replay, file system API path validation. Playwright mobile emulation for PWA/mobile layout tests. Visual tests updated to assert on new layout.

              Out of Scope

              • Code editor / Monaco integration (future phase, after file explorer proves useful)
              • Ralph UI controls (separate PRD)
              • Multi-server management
              • Multi-user / role-based access
              • Light mode / theming beyond Solarized Dark
              • Session recording / playback
              • Git integration in the UI
              • Native Android/iOS app (PWA covers mobile; native wrapper via Capacitor is a future option if push notifications or app store presence become important)
              • Offline terminal interaction (PWA caches the app shell, but terminals require server connectivity)

              Open Questions

              • Shell selection: Should terminal creation auto-detect the user's default shell ($SHELL) or always use bash? Leaning toward $SHELL with bash fallback.
              • Buffer size: 50KB per terminal is a starting point. Should this be configurable per-project? Probably not worth the complexity initially.
              • File explorer size limits: What's the right max file size to read via the API? 1MB seems reasonable for text files. Binary files should be blocked or download-only.
              • Terminal limits: Should there be a max number of terminals per project? Probably not enforced, but the UI should handle 10+ terminals gracefully (tabs scroll or overflow).

              🤖 Generated with Claude Code

              Metadata

              Metadata

              Assignees

              No one assigned

                Labels

                No labels
                No labels

                Projects

                No projects

                  Milestone

                  No milestone

                  Relationships

                  None yet

                  Development

                  No branches or pull requests

                  Issue actions

                  , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
                  Skip to content

                  PRD: Terminal Abstraction & Frontend-Owned UX #1

                  Description

                  @DanielGGordon

                  Problem

                  DanCode's terminal experience is built around tmux as both the backend process manager and the user-facing UX layer. Tmux concepts — grouped sessions, window indices, status bars, attach commands — leak directly into the browser experience. Layout management is split between React and tmux, causing quirks with mouse handling, resize behavior, and multiplexing. Creating a "grouped connection session" per browser tab just to achieve independent window views is a workaround, not an architecture. The result is a functional but hacky experience that fights tmux's design rather than building on top of it cleanly.

                  Solution

                  Rearchitect the terminal layer so the frontend fully owns the terminal UX — layout, multiplexing, pane management, labels, and resize — while the server provides a clean terminal abstraction backed by direct PTY connections. Tmux operates invisibly as a persistence layer: each server-managed PTY is backed by a hidden tmux session so processes survive browser disconnects and server restarts, but no tmux concept is ever exposed to the client. The server maintains an output ring buffer per terminal for seamless reconnection replay. A file explorer is added as a new panel type alongside terminals. A mobile-first PWA experience provides monitoring and light interaction from a phone — swipe navigation, a system-wide shortcut bar, and a status dashboard designed for checking on running agents. As a stretch goal, sessions remain directly accessible from the host environment for power users who want to drop into a shell.

                  Requirements

                  Phase 1: Direct PTY & Frontend-Owned Layout

                  Server — TerminalManager

                  • Server exposes a terminal CRUD API: create, list, get, destroy terminals
                  • Each terminal is identified by a unique ID (not a tmux window index)
                  • Creating a terminal spawns a real PTY process (bash or user's default shell) via node-pty
                  • Terminals are associated with a project (by slug) and have a user-defined label
                  • Terminal creation accepts optional command param (e.g., claude --dangerously-skip-permissions) run inside the shell
                  • Terminal creation accepts optional cwd param (defaults to project path)
                  • Server maintains an output ring buffer (~50KB) per terminal for reconnection replay
                  • When a browser disconnects, the PTY stays alive and continues buffering output
                  • When a browser reconnects to a terminal ID, the buffered output is replayed before resuming live streaming
                  • resize events from the client resize the PTY directly (no tmux intermediary)
                  • Terminal metadata (id, project, label, state) is persisted in ~/.dancode/terminals/ so the server can rediscover terminals after restart

                  Server — API

                  • POST /api/terminals — create terminal (params: projectSlug, label, command?, cwd?) → returns terminal object with id
                  • GET /api/terminals?project=<slug> — list terminals for a project
                  • GET /api/terminals/:id — get terminal metadata
                  • PATCH /api/terminals/:id — update label or other metadata
                  • DELETE /api/terminals/:id — kill PTY process and remove terminal
                  • WebSocket /terminal/:id — bidirectional I/O (replaces current /terminal namespace with query params)

                  Client — TerminalLayout (replaces PaneLayout)

                  • Layout is 100% frontend-owned: no fetching tmux windows from server
                  • Supports split view (side-by-side, resizable dividers) and tabbed view
                  • Mobile (<768px) always uses tabbed view
                  • Users can create new terminals from the UI ("+" button) — opens in project directory by default
                  • Users can close/kill terminals from the UI (with confirmation)
                  • Users can rename terminal labels inline
                  • Users can reorder tabs via drag-and-drop
                  • Users can toggle terminals visible/hidden without killing them
                  • Layout state (which terminals are visible, order, split sizes, active tab) persisted in project config
                  • Each terminal pane renders an xterm.js instance connected via WebSocket to /terminal/:id
                  • Click-to-focus with visual indicator (accent bar) on focused pane
                  • Ctrl+Scroll font sizing, Ctrl+C copy, Ctrl+V paste all preserved

                  Client — Project Creation (updated)

                  • Remove "Adopt existing tmux session" flow entirely
                  • New project creates 2 default terminals: "CLI" (shell) and "Claude" (runs claude command)
                  • No hardcoded pane types — terminals are generic, labels are just strings

                  Migration / Cleanup

                  • Remove tmux.js module (or gut it for Phase 2 reuse)
                  • Remove grouped session logic from terminal.js
                  • Remove tmux status dots, attach command bar, and tmux context menu items from Sidebar
                  • Remove tmuxSession, showTmuxCommands from project config schema
                  • Remove /api/tmux-status and /api/tmux/sessions endpoints

                  Phase 2: Invisible Tmux Persistence

                  Server — Tmux as Hidden Backend

                  • Each PTY is spawned inside a hidden tmux session rather than as a bare process
                  • Tmux session naming is deterministic and internal: dancode-{projectSlug}-{terminalId}
                  • The server interacts with the PTY via node-pty spawning a shell inside the tmux session (NOT tmux attach)
                  • Tmux is never referenced in any client-facing API response or WebSocket message
                  • On server startup, the TerminalManager scans for existing dancode-* tmux sessions and reconciles with terminal metadata files — orphaned sessions are reclaimed, metadata without sessions is cleaned up
                  • If a PTY handle is lost (server crash) but the tmux session survives, the server reattaches transparently on restart
                  • Output ring buffer is repopulated from tmux scrollback on reattach (tmux capture-pane)

                  Server — Resilience

                  • Terminal metadata files in ~/.dancode/terminals/ include the backing tmux session name
                  • Server restart triggers full reconciliation: match metadata ↔ tmux sessions ↔ PTY handles
                  • Terminals that were running before a crash appear as "reconnecting" in the UI, then resume automatically
                  • Stale metadata (tmux session gone, no PTY) is cleaned up with a warning log

                  Stretch Goal: Direct Host Access

                  • Tmux sessions use clean, human-readable names (dancode-{projectSlug}-{label})
                  • A user can tmux ls on the host and see all DanCode-managed sessions
                  • A user can tmux attach -t dancode-myproject-cli to directly access a terminal
                  • Optional: a dancode CLI helper script that lists projects and attaches to sessions (dancode attach myproject cli)
                  • Input from direct tmux attach and browser are interleaved — both see the same terminal
                  • This is explicitly a power-user feature; the web UI remains the primary interface

                  Phase 3: File Explorer

                  Server — File System API

                  • GET /api/files?path=<dir> — list directory contents (name, type, size, modified date), scoped to project path
                  • GET /api/files/read?path=<file> — read file contents (with size limit, e.g., 1MB)
                  • PUT /api/files/write — write file contents (path + content in body)
                  • POST /api/files/mkdir — create directory
                  • POST /api/files/rename — rename/move file or directory
                  • DELETE /api/files — delete file or directory (with confirmation)
                  • All paths are validated to be within the project directory (no path traversal)
                  • Symlinks are followed but validated to stay within project bounds
                  • Hidden files (dotfiles) shown with a toggle

                  Client — File Explorer Panel

                  • Tree view panel that can appear alongside terminal panes (left side or as a tab)
                  • Lazy-loaded: directories expand on click, fetching contents on demand
                  • File icons by extension (simple icon set — folders, code files, config, images, etc.)
                  • Single-click to select, double-click to... (future: open in editor; for now: copy path to clipboard or insert into focused terminal)
                  • Right-click context menu: rename, delete, copy path, new file, new folder
                  • Drag-and-drop files to terminal panes to insert the file path
                  • Search/filter within the current directory tree
                  • Respects .gitignore patterns by default (toggle to show ignored files)
                  • Collapsible — can be hidden to maximize terminal space
                  • File explorer state (expanded directories, scroll position) persisted per project

                  Integration with Terminals

                  • "Open terminal here" context menu option on directories — creates a new terminal with cwd set to that directory
                  • Dragging a file onto a terminal inserts the relative path

                  Phase 4: Mobile Experience & PWA

                  PWA Foundation

                  • Add web app manifest (manifest.json) with app name, icons, theme color (Solarized Dark), display: standalone
                  • Add service worker for offline shell (cache app assets, show "offline" state when server unreachable)
                  • "Add to Home Screen" works on Android Chrome — launches full-screen, no browser chrome
                  • Viewport and touch meta tags for native-feeling mobile experience
                  • App icon and splash screen in Solarized Dark branding

                  Mobile Layout — Monitoring-First Design

                  • Default mobile view is a status dashboard, not a terminal
                  • Dashboard shows all projects in a card list: project name, terminal labels, activity indicator (last output timestamp, active/idle/stopped), and a preview of the last few lines of output per terminal
                  • Tap a project card to expand and see its terminals
                  • Tap a terminal to enter full-screen terminal view
                  • Back gesture or button returns to dashboard

                  Terminal View — Mobile Optimized

                  • Terminal takes full screen (no sidebar, no header — just the terminal + a thin top bar with back button and terminal label)
                  • Read-first: keyboard is dismissed by default, terminal output is scrollable
                  • Tap the terminal area or a "keyboard" button to enter input mode (soft keyboard appears)
                  • Auto-dismiss keyboard after pressing Enter (configurable — some users may want it to stay)

                  Shortcut Bar

                  • Persistent bottom bar when keyboard is active, sitting above the soft keyboard
                  • System-wide shortcuts (not per-project): Ctrl+C, Ctrl+V, Ctrl+D, Tab, Up Arrow, Down Arrow, Esc
                  • Scrollable horizontally if more shortcuts than screen width
                  • Buttons are touch-friendly (min 44px tap targets)
                  • Shortcut bar hides when keyboard is dismissed (read mode)

                  Swipe Navigation

                  • Swipe left/right between terminals within the same project
                  • Dot indicators at top show which terminal is active (like iOS page dots)
                  • Swipe from left edge opens project list (drawer-style)
                  • Swipe down from top of terminal to return to dashboard

                  Mobile-Specific Features

                  • Pull-to-refresh on dashboard to update activity status
                  • Long-press a project card for quick actions (kill all terminals, open CLI, open Claude)
                  • Haptic feedback on shortcut button taps (where supported)
                  • Font size respects system accessibility settings, with pinch-to-zoom override
                  • Landscape mode: terminal uses full width, shortcut bar along the bottom

                  Responsive Breakpoints

                  • < 480px (phone portrait): dashboard cards stack, single terminal full-screen, shortcut bar active
                  • 480–768px (phone landscape / small tablet): same as above but wider terminal
                  • 768–1024px (tablet): optional split view (2 terminals), shortcut bar available but optional
                  • > 1024px (desktop): full desktop layout, no shortcut bar, no swipe navigation

                  UX / Interface (all phases)

                  • Solarized Dark theme maintained throughout
                  • No tmux concepts visible anywhere in the UI — no session names, attach commands, or status bars referencing tmux
                  • All terminal management is presented as "terminals" not "panes" or "windows"
                  • Smooth reconnection UX: terminal shows "Reconnecting..." overlay, then replays buffer and resumes
                  • Connection state indicators per terminal (connected / reconnecting / disconnected)
                  • Responsive: splits on desktop, tabs on mobile, file explorer collapses on narrow viewports

                  Data / Persistence

                  • Terminal metadata: ~/.dancode/terminals/{terminalId}.json — id, projectSlug, label, tmuxSessionName, createdAt, lastActivity
                  • Project config: ~/.dancode/projects/{slug}.json — updated schema drops tmux fields, adds layout object for frontend state
                  • Output ring buffer: in-memory only (not persisted to disk) — repopulated from tmux scrollback on restart
                  • File explorer state: persisted in project config (expanded paths, panel visibility)

                  Technical Decisions

                  • Stack: No changes — Node.js, Express, Socket.io, React, Vite, xterm.js, Tailwind. Proven and working.
                  • Architecture: New TerminalManager class server-side replaces current tmux.js + terminal.js. Owns PTY lifecycle, output buffering, and reconnection. Single responsibility: manage terminal processes.
                  • Data model: Terminals become first-class entities with their own ID and metadata file, decoupled from tmux window indices. Projects reference terminals by ID, not by pane index.
                  • API surface: Clean REST for terminal CRUD + WebSocket per terminal for I/O. No tmux vocabulary in any API contract. File system API for explorer with strict path validation.
                  • Persistence strategy: Phase 1 uses bare PTYs (sessions lost on server restart). Phase 2 adds invisible tmux backing for full persistence. This phasing means Phase 1 is shippable without tmux complexity.
                  • Output buffer: ~50KB circular buffer per terminal, in-memory. Enough to replay a screenful+ on reconnect. Not persisted — tmux scrollback is the durable copy (Phase 2).
                  • Mobile: PWA (manifest + service worker), not a native app. Touch gestures via standard DOM events or a lightweight library (e.g., Hammer.js). Shortcut bar is a React component, not a native keyboard extension. Responsive breakpoints handled with Tailwind's existing breakpoint system.
                  • Testing: Existing Vitest + Playwright stack. New tests for TerminalManager lifecycle, reconnection replay, file system API path validation. Playwright mobile emulation for PWA/mobile layout tests. Visual tests updated to assert on new layout.

                  Out of Scope

                  • Code editor / Monaco integration (future phase, after file explorer proves useful)
                  • Ralph UI controls (separate PRD)
                  • Multi-server management
                  • Multi-user / role-based access
                  • Light mode / theming beyond Solarized Dark
                  • Session recording / playback
                  • Git integration in the UI
                  • Native Android/iOS app (PWA covers mobile; native wrapper via Capacitor is a future option if push notifications or app store presence become important)
                  • Offline terminal interaction (PWA caches the app shell, but terminals require server connectivity)

                  Open Questions

                  • Shell selection: Should terminal creation auto-detect the user's default shell ($SHELL) or always use bash? Leaning toward $SHELL with bash fallback.
                  • Buffer size: 50KB per terminal is a starting point. Should this be configurable per-project? Probably not worth the complexity initially.
                  • File explorer size limits: What's the right max file size to read via the API? 1MB seems reasonable for text files. Binary files should be blocked or download-only.
                  • Terminal limits: Should there be a max number of terminals per project? Probably not enforced, but the UI should handle 10+ terminals gracefully (tabs scroll or overflow).

                  🤖 Generated with Claude Code

                  Metadata

                  Metadata

                  Assignees

                  No one assigned

                    Labels

                    No labels
                    No labels

                    Projects

                    No projects

                      Milestone

                      No milestone

                      Relationships

                      None yet

                      Development

                      No branches or pull requests

                      Issue actions

                      , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
                      Skip to content

                      PRD: Terminal Abstraction & Frontend-Owned UX #1

                      Description

                      @DanielGGordon

                      Problem

                      DanCode's terminal experience is built around tmux as both the backend process manager and the user-facing UX layer. Tmux concepts — grouped sessions, window indices, status bars, attach commands — leak directly into the browser experience. Layout management is split between React and tmux, causing quirks with mouse handling, resize behavior, and multiplexing. Creating a "grouped connection session" per browser tab just to achieve independent window views is a workaround, not an architecture. The result is a functional but hacky experience that fights tmux's design rather than building on top of it cleanly.

                      Solution

                      Rearchitect the terminal layer so the frontend fully owns the terminal UX — layout, multiplexing, pane management, labels, and resize — while the server provides a clean terminal abstraction backed by direct PTY connections. Tmux operates invisibly as a persistence layer: each server-managed PTY is backed by a hidden tmux session so processes survive browser disconnects and server restarts, but no tmux concept is ever exposed to the client. The server maintains an output ring buffer per terminal for seamless reconnection replay. A file explorer is added as a new panel type alongside terminals. A mobile-first PWA experience provides monitoring and light interaction from a phone — swipe navigation, a system-wide shortcut bar, and a status dashboard designed for checking on running agents. As a stretch goal, sessions remain directly accessible from the host environment for power users who want to drop into a shell.

                      Requirements

                      Phase 1: Direct PTY & Frontend-Owned Layout

                      Server — TerminalManager

                      • Server exposes a terminal CRUD API: create, list, get, destroy terminals
                      • Each terminal is identified by a unique ID (not a tmux window index)
                      • Creating a terminal spawns a real PTY process (bash or user's default shell) via node-pty
                      • Terminals are associated with a project (by slug) and have a user-defined label
                      • Terminal creation accepts optional command param (e.g., claude --dangerously-skip-permissions) run inside the shell
                      • Terminal creation accepts optional cwd param (defaults to project path)
                      • Server maintains an output ring buffer (~50KB) per terminal for reconnection replay
                      • When a browser disconnects, the PTY stays alive and continues buffering output
                      • When a browser reconnects to a terminal ID, the buffered output is replayed before resuming live streaming
                      • resize events from the client resize the PTY directly (no tmux intermediary)
                      • Terminal metadata (id, project, label, state) is persisted in ~/.dancode/terminals/ so the server can rediscover terminals after restart

                      Server — API

                      • POST /api/terminals — create terminal (params: projectSlug, label, command?, cwd?) → returns terminal object with id
                      • GET /api/terminals?project=<slug> — list terminals for a project
                      • GET /api/terminals/:id — get terminal metadata
                      • PATCH /api/terminals/:id — update label or other metadata
                      • DELETE /api/terminals/:id — kill PTY process and remove terminal
                      • WebSocket /terminal/:id — bidirectional I/O (replaces current /terminal namespace with query params)

                      Client — TerminalLayout (replaces PaneLayout)

                      • Layout is 100% frontend-owned: no fetching tmux windows from server
                      • Supports split view (side-by-side, resizable dividers) and tabbed view
                      • Mobile (<768px) always uses tabbed view
                      • Users can create new terminals from the UI ("+" button) — opens in project directory by default
                      • Users can close/kill terminals from the UI (with confirmation)
                      • Users can rename terminal labels inline
                      • Users can reorder tabs via drag-and-drop
                      • Users can toggle terminals visible/hidden without killing them
                      • Layout state (which terminals are visible, order, split sizes, active tab) persisted in project config
                      • Each terminal pane renders an xterm.js instance connected via WebSocket to /terminal/:id
                      • Click-to-focus with visual indicator (accent bar) on focused pane
                      • Ctrl+Scroll font sizing, Ctrl+C copy, Ctrl+V paste all preserved

                      Client — Project Creation (updated)

                      • Remove "Adopt existing tmux session" flow entirely
                      • New project creates 2 default terminals: "CLI" (shell) and "Claude" (runs claude command)
                      • No hardcoded pane types — terminals are generic, labels are just strings

                      Migration / Cleanup

                      • Remove tmux.js module (or gut it for Phase 2 reuse)
                      • Remove grouped session logic from terminal.js
                      • Remove tmux status dots, attach command bar, and tmux context menu items from Sidebar
                      • Remove tmuxSession, showTmuxCommands from project config schema
                      • Remove /api/tmux-status and /api/tmux/sessions endpoints

                      Phase 2: Invisible Tmux Persistence

                      Server — Tmux as Hidden Backend

                      • Each PTY is spawned inside a hidden tmux session rather than as a bare process
                      • Tmux session naming is deterministic and internal: dancode-{projectSlug}-{terminalId}
                      • The server interacts with the PTY via node-pty spawning a shell inside the tmux session (NOT tmux attach)
                      • Tmux is never referenced in any client-facing API response or WebSocket message
                      • On server startup, the TerminalManager scans for existing dancode-* tmux sessions and reconciles with terminal metadata files — orphaned sessions are reclaimed, metadata without sessions is cleaned up
                      • If a PTY handle is lost (server crash) but the tmux session survives, the server reattaches transparently on restart
                      • Output ring buffer is repopulated from tmux scrollback on reattach (tmux capture-pane)

                      Server — Resilience

                      • Terminal metadata files in ~/.dancode/terminals/ include the backing tmux session name
                      • Server restart triggers full reconciliation: match metadata ↔ tmux sessions ↔ PTY handles
                      • Terminals that were running before a crash appear as "reconnecting" in the UI, then resume automatically
                      • Stale metadata (tmux session gone, no PTY) is cleaned up with a warning log

                      Stretch Goal: Direct Host Access

                      • Tmux sessions use clean, human-readable names (dancode-{projectSlug}-{label})
                      • A user can tmux ls on the host and see all DanCode-managed sessions
                      • A user can tmux attach -t dancode-myproject-cli to directly access a terminal
                      • Optional: a dancode CLI helper script that lists projects and attaches to sessions (dancode attach myproject cli)
                      • Input from direct tmux attach and browser are interleaved — both see the same terminal
                      • This is explicitly a power-user feature; the web UI remains the primary interface

                      Phase 3: File Explorer

                      Server — File System API

                      • GET /api/files?path=<dir> — list directory contents (name, type, size, modified date), scoped to project path
                      • GET /api/files/read?path=<file> — read file contents (with size limit, e.g., 1MB)
                      • PUT /api/files/write — write file contents (path + content in body)
                      • POST /api/files/mkdir — create directory
                      • POST /api/files/rename — rename/move file or directory
                      • DELETE /api/files — delete file or directory (with confirmation)
                      • All paths are validated to be within the project directory (no path traversal)
                      • Symlinks are followed but validated to stay within project bounds
                      • Hidden files (dotfiles) shown with a toggle

                      Client — File Explorer Panel

                      • Tree view panel that can appear alongside terminal panes (left side or as a tab)
                      • Lazy-loaded: directories expand on click, fetching contents on demand
                      • File icons by extension (simple icon set — folders, code files, config, images, etc.)
                      • Single-click to select, double-click to... (future: open in editor; for now: copy path to clipboard or insert into focused terminal)
                      • Right-click context menu: rename, delete, copy path, new file, new folder
                      • Drag-and-drop files to terminal panes to insert the file path
                      • Search/filter within the current directory tree
                      • Respects .gitignore patterns by default (toggle to show ignored files)
                      • Collapsible — can be hidden to maximize terminal space
                      • File explorer state (expanded directories, scroll position) persisted per project

                      Integration with Terminals

                      • "Open terminal here" context menu option on directories — creates a new terminal with cwd set to that directory
                      • Dragging a file onto a terminal inserts the relative path

                      Phase 4: Mobile Experience & PWA

                      PWA Foundation

                      • Add web app manifest (manifest.json) with app name, icons, theme color (Solarized Dark), display: standalone
                      • Add service worker for offline shell (cache app assets, show "offline" state when server unreachable)
                      • "Add to Home Screen" works on Android Chrome — launches full-screen, no browser chrome
                      • Viewport and touch meta tags for native-feeling mobile experience
                      • App icon and splash screen in Solarized Dark branding

                      Mobile Layout — Monitoring-First Design

                      • Default mobile view is a status dashboard, not a terminal
                      • Dashboard shows all projects in a card list: project name, terminal labels, activity indicator (last output timestamp, active/idle/stopped), and a preview of the last few lines of output per terminal
                      • Tap a project card to expand and see its terminals
                      • Tap a terminal to enter full-screen terminal view
                      • Back gesture or button returns to dashboard

                      Terminal View — Mobile Optimized

                      • Terminal takes full screen (no sidebar, no header — just the terminal + a thin top bar with back button and terminal label)
                      • Read-first: keyboard is dismissed by default, terminal output is scrollable
                      • Tap the terminal area or a "keyboard" button to enter input mode (soft keyboard appears)
                      • Auto-dismiss keyboard after pressing Enter (configurable — some users may want it to stay)

                      Shortcut Bar

                      • Persistent bottom bar when keyboard is active, sitting above the soft keyboard
                      • System-wide shortcuts (not per-project): Ctrl+C, Ctrl+V, Ctrl+D, Tab, Up Arrow, Down Arrow, Esc
                      • Scrollable horizontally if more shortcuts than screen width
                      • Buttons are touch-friendly (min 44px tap targets)
                      • Shortcut bar hides when keyboard is dismissed (read mode)

                      Swipe Navigation

                      • Swipe left/right between terminals within the same project
                      • Dot indicators at top show which terminal is active (like iOS page dots)
                      • Swipe from left edge opens project list (drawer-style)
                      • Swipe down from top of terminal to return to dashboard

                      Mobile-Specific Features

                      • Pull-to-refresh on dashboard to update activity status
                      • Long-press a project card for quick actions (kill all terminals, open CLI, open Claude)
                      • Haptic feedback on shortcut button taps (where supported)
                      • Font size respects system accessibility settings, with pinch-to-zoom override
                      • Landscape mode: terminal uses full width, shortcut bar along the bottom

                      Responsive Breakpoints

                      • < 480px (phone portrait): dashboard cards stack, single terminal full-screen, shortcut bar active
                      • 480–768px (phone landscape / small tablet): same as above but wider terminal
                      • 768–1024px (tablet): optional split view (2 terminals), shortcut bar available but optional
                      • > 1024px (desktop): full desktop layout, no shortcut bar, no swipe navigation

                      UX / Interface (all phases)

                      • Solarized Dark theme maintained throughout
                      • No tmux concepts visible anywhere in the UI — no session names, attach commands, or status bars referencing tmux
                      • All terminal management is presented as "terminals" not "panes" or "windows"
                      • Smooth reconnection UX: terminal shows "Reconnecting..." overlay, then replays buffer and resumes
                      • Connection state indicators per terminal (connected / reconnecting / disconnected)
                      • Responsive: splits on desktop, tabs on mobile, file explorer collapses on narrow viewports

                      Data / Persistence

                      • Terminal metadata: ~/.dancode/terminals/{terminalId}.json — id, projectSlug, label, tmuxSessionName, createdAt, lastActivity
                      • Project config: ~/.dancode/projects/{slug}.json — updated schema drops tmux fields, adds layout object for frontend state
                      • Output ring buffer: in-memory only (not persisted to disk) — repopulated from tmux scrollback on restart
                      • File explorer state: persisted in project config (expanded paths, panel visibility)

                      Technical Decisions

                      • Stack: No changes — Node.js, Express, Socket.io, React, Vite, xterm.js, Tailwind. Proven and working.
                      • Architecture: New TerminalManager class server-side replaces current tmux.js + terminal.js. Owns PTY lifecycle, output buffering, and reconnection. Single responsibility: manage terminal processes.
                      • Data model: Terminals become first-class entities with their own ID and metadata file, decoupled from tmux window indices. Projects reference terminals by ID, not by pane index.
                      • API surface: Clean REST for terminal CRUD + WebSocket per terminal for I/O. No tmux vocabulary in any API contract. File system API for explorer with strict path validation.
                      • Persistence strategy: Phase 1 uses bare PTYs (sessions lost on server restart). Phase 2 adds invisible tmux backing for full persistence. This phasing means Phase 1 is shippable without tmux complexity.
                      • Output buffer: ~50KB circular buffer per terminal, in-memory. Enough to replay a screenful+ on reconnect. Not persisted — tmux scrollback is the durable copy (Phase 2).
                      • Mobile: PWA (manifest + service worker), not a native app. Touch gestures via standard DOM events or a lightweight library (e.g., Hammer.js). Shortcut bar is a React component, not a native keyboard extension. Responsive breakpoints handled with Tailwind's existing breakpoint system.
                      • Testing: Existing Vitest + Playwright stack. New tests for TerminalManager lifecycle, reconnection replay, file system API path validation. Playwright mobile emulation for PWA/mobile layout tests. Visual tests updated to assert on new layout.

                      Out of Scope

                      • Code editor / Monaco integration (future phase, after file explorer proves useful)
                      • Ralph UI controls (separate PRD)
                      • Multi-server management
                      • Multi-user / role-based access
                      • Light mode / theming beyond Solarized Dark
                      • Session recording / playback
                      • Git integration in the UI
                      • Native Android/iOS app (PWA covers mobile; native wrapper via Capacitor is a future option if push notifications or app store presence become important)
                      • Offline terminal interaction (PWA caches the app shell, but terminals require server connectivity)

                      Open Questions

                      • Shell selection: Should terminal creation auto-detect the user's default shell ($SHELL) or always use bash? Leaning toward $SHELL with bash fallback.
                      • Buffer size: 50KB per terminal is a starting point. Should this be configurable per-project? Probably not worth the complexity initially.
                      • File explorer size limits: What's the right max file size to read via the API? 1MB seems reasonable for text files. Binary files should be blocked or download-only.
                      • Terminal limits: Should there be a max number of terminals per project? Probably not enforced, but the UI should handle 10+ terminals gracefully (tabs scroll or overflow).

                      🤖 Generated with Claude Code

                      Metadata

                      Metadata

                      Assignees

                      No one assigned

                        Labels

                        No labels
                        No labels

                        Projects

                        No projects

                          Milestone

                          No milestone

                          Relationships

                          None yet

                          Development

                          No branches or pull requests

                          Issue actions

                          , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
                          Skip to content

                          PRD: Terminal Abstraction & Frontend-Owned UX #1

                          Description

                          @DanielGGordon

                          Problem

                          DanCode's terminal experience is built around tmux as both the backend process manager and the user-facing UX layer. Tmux concepts — grouped sessions, window indices, status bars, attach commands — leak directly into the browser experience. Layout management is split between React and tmux, causing quirks with mouse handling, resize behavior, and multiplexing. Creating a "grouped connection session" per browser tab just to achieve independent window views is a workaround, not an architecture. The result is a functional but hacky experience that fights tmux's design rather than building on top of it cleanly.

                          Solution

                          Rearchitect the terminal layer so the frontend fully owns the terminal UX — layout, multiplexing, pane management, labels, and resize — while the server provides a clean terminal abstraction backed by direct PTY connections. Tmux operates invisibly as a persistence layer: each server-managed PTY is backed by a hidden tmux session so processes survive browser disconnects and server restarts, but no tmux concept is ever exposed to the client. The server maintains an output ring buffer per terminal for seamless reconnection replay. A file explorer is added as a new panel type alongside terminals. A mobile-first PWA experience provides monitoring and light interaction from a phone — swipe navigation, a system-wide shortcut bar, and a status dashboard designed for checking on running agents. As a stretch goal, sessions remain directly accessible from the host environment for power users who want to drop into a shell.

                          Requirements

                          Phase 1: Direct PTY & Frontend-Owned Layout

                          Server — TerminalManager

                          • Server exposes a terminal CRUD API: create, list, get, destroy terminals
                          • Each terminal is identified by a unique ID (not a tmux window index)
                          • Creating a terminal spawns a real PTY process (bash or user's default shell) via node-pty
                          • Terminals are associated with a project (by slug) and have a user-defined label
                          • Terminal creation accepts optional command param (e.g., claude --dangerously-skip-permissions) run inside the shell
                          • Terminal creation accepts optional cwd param (defaults to project path)
                          • Server maintains an output ring buffer (~50KB) per terminal for reconnection replay
                          • When a browser disconnects, the PTY stays alive and continues buffering output
                          • When a browser reconnects to a terminal ID, the buffered output is replayed before resuming live streaming
                          • resize events from the client resize the PTY directly (no tmux intermediary)
                          • Terminal metadata (id, project, label, state) is persisted in ~/.dancode/terminals/ so the server can rediscover terminals after restart

                          Server — API

                          • POST /api/terminals — create terminal (params: projectSlug, label, command?, cwd?) → returns terminal object with id
                          • GET /api/terminals?project=<slug> — list terminals for a project
                          • GET /api/terminals/:id — get terminal metadata
                          • PATCH /api/terminals/:id — update label or other metadata
                          • DELETE /api/terminals/:id — kill PTY process and remove terminal
                          • WebSocket /terminal/:id — bidirectional I/O (replaces current /terminal namespace with query params)

                          Client — TerminalLayout (replaces PaneLayout)

                          • Layout is 100% frontend-owned: no fetching tmux windows from server
                          • Supports split view (side-by-side, resizable dividers) and tabbed view
                          • Mobile (<768px) always uses tabbed view
                          • Users can create new terminals from the UI ("+" button) — opens in project directory by default
                          • Users can close/kill terminals from the UI (with confirmation)
                          • Users can rename terminal labels inline
                          • Users can reorder tabs via drag-and-drop
                          • Users can toggle terminals visible/hidden without killing them
                          • Layout state (which terminals are visible, order, split sizes, active tab) persisted in project config
                          • Each terminal pane renders an xterm.js instance connected via WebSocket to /terminal/:id
                          • Click-to-focus with visual indicator (accent bar) on focused pane
                          • Ctrl+Scroll font sizing, Ctrl+C copy, Ctrl+V paste all preserved

                          Client — Project Creation (updated)

                          • Remove "Adopt existing tmux session" flow entirely
                          • New project creates 2 default terminals: "CLI" (shell) and "Claude" (runs claude command)
                          • No hardcoded pane types — terminals are generic, labels are just strings

                          Migration / Cleanup

                          • Remove tmux.js module (or gut it for Phase 2 reuse)
                          • Remove grouped session logic from terminal.js
                          • Remove tmux status dots, attach command bar, and tmux context menu items from Sidebar
                          • Remove tmuxSession, showTmuxCommands from project config schema
                          • Remove /api/tmux-status and /api/tmux/sessions endpoints

                          Phase 2: Invisible Tmux Persistence

                          Server — Tmux as Hidden Backend

                          • Each PTY is spawned inside a hidden tmux session rather than as a bare process
                          • Tmux session naming is deterministic and internal: dancode-{projectSlug}-{terminalId}
                          • The server interacts with the PTY via node-pty spawning a shell inside the tmux session (NOT tmux attach)
                          • Tmux is never referenced in any client-facing API response or WebSocket message
                          • On server startup, the TerminalManager scans for existing dancode-* tmux sessions and reconciles with terminal metadata files — orphaned sessions are reclaimed, metadata without sessions is cleaned up
                          • If a PTY handle is lost (server crash) but the tmux session survives, the server reattaches transparently on restart
                          • Output ring buffer is repopulated from tmux scrollback on reattach (tmux capture-pane)

                          Server — Resilience

                          • Terminal metadata files in ~/.dancode/terminals/ include the backing tmux session name
                          • Server restart triggers full reconciliation: match metadata ↔ tmux sessions ↔ PTY handles
                          • Terminals that were running before a crash appear as "reconnecting" in the UI, then resume automatically
                          • Stale metadata (tmux session gone, no PTY) is cleaned up with a warning log

                          Stretch Goal: Direct Host Access

                          • Tmux sessions use clean, human-readable names (dancode-{projectSlug}-{label})
                          • A user can tmux ls on the host and see all DanCode-managed sessions
                          • A user can tmux attach -t dancode-myproject-cli to directly access a terminal
                          • Optional: a dancode CLI helper script that lists projects and attaches to sessions (dancode attach myproject cli)
                          • Input from direct tmux attach and browser are interleaved — both see the same terminal
                          • This is explicitly a power-user feature; the web UI remains the primary interface

                          Phase 3: File Explorer

                          Server — File System API

                          • GET /api/files?path=<dir> — list directory contents (name, type, size, modified date), scoped to project path
                          • GET /api/files/read?path=<file> — read file contents (with size limit, e.g., 1MB)
                          • PUT /api/files/write — write file contents (path + content in body)
                          • POST /api/files/mkdir — create directory
                          • POST /api/files/rename — rename/move file or directory
                          • DELETE /api/files — delete file or directory (with confirmation)
                          • All paths are validated to be within the project directory (no path traversal)
                          • Symlinks are followed but validated to stay within project bounds
                          • Hidden files (dotfiles) shown with a toggle

                          Client — File Explorer Panel

                          • Tree view panel that can appear alongside terminal panes (left side or as a tab)
                          • Lazy-loaded: directories expand on click, fetching contents on demand
                          • File icons by extension (simple icon set — folders, code files, config, images, etc.)
                          • Single-click to select, double-click to... (future: open in editor; for now: copy path to clipboard or insert into focused terminal)
                          • Right-click context menu: rename, delete, copy path, new file, new folder
                          • Drag-and-drop files to terminal panes to insert the file path
                          • Search/filter within the current directory tree
                          • Respects .gitignore patterns by default (toggle to show ignored files)
                          • Collapsible — can be hidden to maximize terminal space
                          • File explorer state (expanded directories, scroll position) persisted per project

                          Integration with Terminals

                          • "Open terminal here" context menu option on directories — creates a new terminal with cwd set to that directory
                          • Dragging a file onto a terminal inserts the relative path

                          Phase 4: Mobile Experience & PWA

                          PWA Foundation

                          • Add web app manifest (manifest.json) with app name, icons, theme color (Solarized Dark), display: standalone
                          • Add service worker for offline shell (cache app assets, show "offline" state when server unreachable)
                          • "Add to Home Screen" works on Android Chrome — launches full-screen, no browser chrome
                          • Viewport and touch meta tags for native-feeling mobile experience
                          • App icon and splash screen in Solarized Dark branding

                          Mobile Layout — Monitoring-First Design

                          • Default mobile view is a status dashboard, not a terminal
                          • Dashboard shows all projects in a card list: project name, terminal labels, activity indicator (last output timestamp, active/idle/stopped), and a preview of the last few lines of output per terminal
                          • Tap a project card to expand and see its terminals
                          • Tap a terminal to enter full-screen terminal view
                          • Back gesture or button returns to dashboard

                          Terminal View — Mobile Optimized

                          • Terminal takes full screen (no sidebar, no header — just the terminal + a thin top bar with back button and terminal label)
                          • Read-first: keyboard is dismissed by default, terminal output is scrollable
                          • Tap the terminal area or a "keyboard" button to enter input mode (soft keyboard appears)
                          • Auto-dismiss keyboard after pressing Enter (configurable — some users may want it to stay)

                          Shortcut Bar

                          • Persistent bottom bar when keyboard is active, sitting above the soft keyboard
                          • System-wide shortcuts (not per-project): Ctrl+C, Ctrl+V, Ctrl+D, Tab, Up Arrow, Down Arrow, Esc
                          • Scrollable horizontally if more shortcuts than screen width
                          • Buttons are touch-friendly (min 44px tap targets)
                          • Shortcut bar hides when keyboard is dismissed (read mode)

                          Swipe Navigation

                          • Swipe left/right between terminals within the same project
                          • Dot indicators at top show which terminal is active (like iOS page dots)
                          • Swipe from left edge opens project list (drawer-style)
                          • Swipe down from top of terminal to return to dashboard

                          Mobile-Specific Features

                          • Pull-to-refresh on dashboard to update activity status
                          • Long-press a project card for quick actions (kill all terminals, open CLI, open Claude)
                          • Haptic feedback on shortcut button taps (where supported)
                          • Font size respects system accessibility settings, with pinch-to-zoom override
                          • Landscape mode: terminal uses full width, shortcut bar along the bottom

                          Responsive Breakpoints

                          • < 480px (phone portrait): dashboard cards stack, single terminal full-screen, shortcut bar active
                          • 480–768px (phone landscape / small tablet): same as above but wider terminal
                          • 768–1024px (tablet): optional split view (2 terminals), shortcut bar available but optional
                          • > 1024px (desktop): full desktop layout, no shortcut bar, no swipe navigation

                          UX / Interface (all phases)

                          • Solarized Dark theme maintained throughout
                          • No tmux concepts visible anywhere in the UI — no session names, attach commands, or status bars referencing tmux
                          • All terminal management is presented as "terminals" not "panes" or "windows"
                          • Smooth reconnection UX: terminal shows "Reconnecting..." overlay, then replays buffer and resumes
                          • Connection state indicators per terminal (connected / reconnecting / disconnected)
                          • Responsive: splits on desktop, tabs on mobile, file explorer collapses on narrow viewports

                          Data / Persistence

                          • Terminal metadata: ~/.dancode/terminals/{terminalId}.json — id, projectSlug, label, tmuxSessionName, createdAt, lastActivity
                          • Project config: ~/.dancode/projects/{slug}.json — updated schema drops tmux fields, adds layout object for frontend state
                          • Output ring buffer: in-memory only (not persisted to disk) — repopulated from tmux scrollback on restart
                          • File explorer state: persisted in project config (expanded paths, panel visibility)

                          Technical Decisions

                          • Stack: No changes — Node.js, Express, Socket.io, React, Vite, xterm.js, Tailwind. Proven and working.
                          • Architecture: New TerminalManager class server-side replaces current tmux.js + terminal.js. Owns PTY lifecycle, output buffering, and reconnection. Single responsibility: manage terminal processes.
                          • Data model: Terminals become first-class entities with their own ID and metadata file, decoupled from tmux window indices. Projects reference terminals by ID, not by pane index.
                          • API surface: Clean REST for terminal CRUD + WebSocket per terminal for I/O. No tmux vocabulary in any API contract. File system API for explorer with strict path validation.
                          • Persistence strategy: Phase 1 uses bare PTYs (sessions lost on server restart). Phase 2 adds invisible tmux backing for full persistence. This phasing means Phase 1 is shippable without tmux complexity.
                          • Output buffer: ~50KB circular buffer per terminal, in-memory. Enough to replay a screenful+ on reconnect. Not persisted — tmux scrollback is the durable copy (Phase 2).
                          • Mobile: PWA (manifest + service worker), not a native app. Touch gestures via standard DOM events or a lightweight library (e.g., Hammer.js). Shortcut bar is a React component, not a native keyboard extension. Responsive breakpoints handled with Tailwind's existing breakpoint system.
                          • Testing: Existing Vitest + Playwright stack. New tests for TerminalManager lifecycle, reconnection replay, file system API path validation. Playwright mobile emulation for PWA/mobile layout tests. Visual tests updated to assert on new layout.

                          Out of Scope

                          • Code editor / Monaco integration (future phase, after file explorer proves useful)
                          • Ralph UI controls (separate PRD)
                          • Multi-server management
                          • Multi-user / role-based access
                          • Light mode / theming beyond Solarized Dark
                          • Session recording / playback
                          • Git integration in the UI
                          • Native Android/iOS app (PWA covers mobile; native wrapper via Capacitor is a future option if push notifications or app store presence become important)
                          • Offline terminal interaction (PWA caches the app shell, but terminals require server connectivity)

                          Open Questions

                          • Shell selection: Should terminal creation auto-detect the user's default shell ($SHELL) or always use bash? Leaning toward $SHELL with bash fallback.
                          • Buffer size: 50KB per terminal is a starting point. Should this be configurable per-project? Probably not worth the complexity initially.
                          • File explorer size limits: What's the right max file size to read via the API? 1MB seems reasonable for text files. Binary files should be blocked or download-only.
                          • Terminal limits: Should there be a max number of terminals per project? Probably not enforced, but the UI should handle 10+ terminals gracefully (tabs scroll or overflow).

                          🤖 Generated with Claude Code

                          Metadata

                          Metadata

                          Assignees

                          No one assigned

                            Labels

                            No labels
                            No labels

                            Projects

                            No projects

                              Milestone

                              No milestone

                              Relationships

                              None yet

                              Development

                              No branches or pull requests

                              Issue actions

                              , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
                              Skip to content

                              PRD: Terminal Abstraction & Frontend-Owned UX #1

                              Description

                              @DanielGGordon

                              Problem

                              DanCode's terminal experience is built around tmux as both the backend process manager and the user-facing UX layer. Tmux concepts — grouped sessions, window indices, status bars, attach commands — leak directly into the browser experience. Layout management is split between React and tmux, causing quirks with mouse handling, resize behavior, and multiplexing. Creating a "grouped connection session" per browser tab just to achieve independent window views is a workaround, not an architecture. The result is a functional but hacky experience that fights tmux's design rather than building on top of it cleanly.

                              Solution

                              Rearchitect the terminal layer so the frontend fully owns the terminal UX — layout, multiplexing, pane management, labels, and resize — while the server provides a clean terminal abstraction backed by direct PTY connections. Tmux operates invisibly as a persistence layer: each server-managed PTY is backed by a hidden tmux session so processes survive browser disconnects and server restarts, but no tmux concept is ever exposed to the client. The server maintains an output ring buffer per terminal for seamless reconnection replay. A file explorer is added as a new panel type alongside terminals. A mobile-first PWA experience provides monitoring and light interaction from a phone — swipe navigation, a system-wide shortcut bar, and a status dashboard designed for checking on running agents. As a stretch goal, sessions remain directly accessible from the host environment for power users who want to drop into a shell.

                              Requirements

                              Phase 1: Direct PTY & Frontend-Owned Layout

                              Server — TerminalManager

                              • Server exposes a terminal CRUD API: create, list, get, destroy terminals
                              • Each terminal is identified by a unique ID (not a tmux window index)
                              • Creating a terminal spawns a real PTY process (bash or user's default shell) via node-pty
                              • Terminals are associated with a project (by slug) and have a user-defined label
                              • Terminal creation accepts optional command param (e.g., claude --dangerously-skip-permissions) run inside the shell
                              • Terminal creation accepts optional cwd param (defaults to project path)
                              • Server maintains an output ring buffer (~50KB) per terminal for reconnection replay
                              • When a browser disconnects, the PTY stays alive and continues buffering output
                              • When a browser reconnects to a terminal ID, the buffered output is replayed before resuming live streaming
                              • resize events from the client resize the PTY directly (no tmux intermediary)
                              • Terminal metadata (id, project, label, state) is persisted in ~/.dancode/terminals/ so the server can rediscover terminals after restart

                              Server — API

                              • POST /api/terminals — create terminal (params: projectSlug, label, command?, cwd?) → returns terminal object with id
                              • GET /api/terminals?project=<slug> — list terminals for a project
                              • GET /api/terminals/:id — get terminal metadata
                              • PATCH /api/terminals/:id — update label or other metadata
                              • DELETE /api/terminals/:id — kill PTY process and remove terminal
                              • WebSocket /terminal/:id — bidirectional I/O (replaces current /terminal namespace with query params)

                              Client — TerminalLayout (replaces PaneLayout)

                              • Layout is 100% frontend-owned: no fetching tmux windows from server
                              • Supports split view (side-by-side, resizable dividers) and tabbed view
                              • Mobile (<768px) always uses tabbed view
                              • Users can create new terminals from the UI ("+" button) — opens in project directory by default
                              • Users can close/kill terminals from the UI (with confirmation)
                              • Users can rename terminal labels inline
                              • Users can reorder tabs via drag-and-drop
                              • Users can toggle terminals visible/hidden without killing them
                              • Layout state (which terminals are visible, order, split sizes, active tab) persisted in project config
                              • Each terminal pane renders an xterm.js instance connected via WebSocket to /terminal/:id
                              • Click-to-focus with visual indicator (accent bar) on focused pane
                              • Ctrl+Scroll font sizing, Ctrl+C copy, Ctrl+V paste all preserved

                              Client — Project Creation (updated)

                              • Remove "Adopt existing tmux session" flow entirely
                              • New project creates 2 default terminals: "CLI" (shell) and "Claude" (runs claude command)
                              • No hardcoded pane types — terminals are generic, labels are just strings

                              Migration / Cleanup

                              • Remove tmux.js module (or gut it for Phase 2 reuse)
                              • Remove grouped session logic from terminal.js
                              • Remove tmux status dots, attach command bar, and tmux context menu items from Sidebar
                              • Remove tmuxSession, showTmuxCommands from project config schema
                              • Remove /api/tmux-status and /api/tmux/sessions endpoints

                              Phase 2: Invisible Tmux Persistence

                              Server — Tmux as Hidden Backend

                              • Each PTY is spawned inside a hidden tmux session rather than as a bare process
                              • Tmux session naming is deterministic and internal: dancode-{projectSlug}-{terminalId}
                              • The server interacts with the PTY via node-pty spawning a shell inside the tmux session (NOT tmux attach)
                              • Tmux is never referenced in any client-facing API response or WebSocket message
                              • On server startup, the TerminalManager scans for existing dancode-* tmux sessions and reconciles with terminal metadata files — orphaned sessions are reclaimed, metadata without sessions is cleaned up
                              • If a PTY handle is lost (server crash) but the tmux session survives, the server reattaches transparently on restart
                              • Output ring buffer is repopulated from tmux scrollback on reattach (tmux capture-pane)

                              Server — Resilience

                              • Terminal metadata files in ~/.dancode/terminals/ include the backing tmux session name
                              • Server restart triggers full reconciliation: match metadata ↔ tmux sessions ↔ PTY handles
                              • Terminals that were running before a crash appear as "reconnecting" in the UI, then resume automatically
                              • Stale metadata (tmux session gone, no PTY) is cleaned up with a warning log

                              Stretch Goal: Direct Host Access

                              • Tmux sessions use clean, human-readable names (dancode-{projectSlug}-{label})
                              • A user can tmux ls on the host and see all DanCode-managed sessions
                              • A user can tmux attach -t dancode-myproject-cli to directly access a terminal
                              • Optional: a dancode CLI helper script that lists projects and attaches to sessions (dancode attach myproject cli)
                              • Input from direct tmux attach and browser are interleaved — both see the same terminal
                              • This is explicitly a power-user feature; the web UI remains the primary interface

                              Phase 3: File Explorer

                              Server — File System API

                              • GET /api/files?path=<dir> — list directory contents (name, type, size, modified date), scoped to project path
                              • GET /api/files/read?path=<file> — read file contents (with size limit, e.g., 1MB)
                              • PUT /api/files/write — write file contents (path + content in body)
                              • POST /api/files/mkdir — create directory
                              • POST /api/files/rename — rename/move file or directory
                              • DELETE /api/files — delete file or directory (with confirmation)
                              • All paths are validated to be within the project directory (no path traversal)
                              • Symlinks are followed but validated to stay within project bounds
                              • Hidden files (dotfiles) shown with a toggle

                              Client — File Explorer Panel

                              • Tree view panel that can appear alongside terminal panes (left side or as a tab)
                              • Lazy-loaded: directories expand on click, fetching contents on demand
                              • File icons by extension (simple icon set — folders, code files, config, images, etc.)
                              • Single-click to select, double-click to... (future: open in editor; for now: copy path to clipboard or insert into focused terminal)
                              • Right-click context menu: rename, delete, copy path, new file, new folder
                              • Drag-and-drop files to terminal panes to insert the file path
                              • Search/filter within the current directory tree
                              • Respects .gitignore patterns by default (toggle to show ignored files)
                              • Collapsible — can be hidden to maximize terminal space
                              • File explorer state (expanded directories, scroll position) persisted per project

                              Integration with Terminals

                              • "Open terminal here" context menu option on directories — creates a new terminal with cwd set to that directory
                              • Dragging a file onto a terminal inserts the relative path

                              Phase 4: Mobile Experience & PWA

                              PWA Foundation

                              • Add web app manifest (manifest.json) with app name, icons, theme color (Solarized Dark), display: standalone
                              • Add service worker for offline shell (cache app assets, show "offline" state when server unreachable)
                              • "Add to Home Screen" works on Android Chrome — launches full-screen, no browser chrome
                              • Viewport and touch meta tags for native-feeling mobile experience
                              • App icon and splash screen in Solarized Dark branding

                              Mobile Layout — Monitoring-First Design

                              • Default mobile view is a status dashboard, not a terminal
                              • Dashboard shows all projects in a card list: project name, terminal labels, activity indicator (last output timestamp, active/idle/stopped), and a preview of the last few lines of output per terminal
                              • Tap a project card to expand and see its terminals
                              • Tap a terminal to enter full-screen terminal view
                              • Back gesture or button returns to dashboard

                              Terminal View — Mobile Optimized

                              • Terminal takes full screen (no sidebar, no header — just the terminal + a thin top bar with back button and terminal label)
                              • Read-first: keyboard is dismissed by default, terminal output is scrollable
                              • Tap the terminal area or a "keyboard" button to enter input mode (soft keyboard appears)
                              • Auto-dismiss keyboard after pressing Enter (configurable — some users may want it to stay)

                              Shortcut Bar

                              • Persistent bottom bar when keyboard is active, sitting above the soft keyboard
                              • System-wide shortcuts (not per-project): Ctrl+C, Ctrl+V, Ctrl+D, Tab, Up Arrow, Down Arrow, Esc
                              • Scrollable horizontally if more shortcuts than screen width
                              • Buttons are touch-friendly (min 44px tap targets)
                              • Shortcut bar hides when keyboard is dismissed (read mode)

                              Swipe Navigation

                              • Swipe left/right between terminals within the same project
                              • Dot indicators at top show which terminal is active (like iOS page dots)
                              • Swipe from left edge opens project list (drawer-style)
                              • Swipe down from top of terminal to return to dashboard

                              Mobile-Specific Features

                              • Pull-to-refresh on dashboard to update activity status
                              • Long-press a project card for quick actions (kill all terminals, open CLI, open Claude)
                              • Haptic feedback on shortcut button taps (where supported)
                              • Font size respects system accessibility settings, with pinch-to-zoom override
                              • Landscape mode: terminal uses full width, shortcut bar along the bottom

                              Responsive Breakpoints

                              • < 480px (phone portrait): dashboard cards stack, single terminal full-screen, shortcut bar active
                              • 480–768px (phone landscape / small tablet): same as above but wider terminal
                              • 768–1024px (tablet): optional split view (2 terminals), shortcut bar available but optional
                              • > 1024px (desktop): full desktop layout, no shortcut bar, no swipe navigation

                              UX / Interface (all phases)

                              • Solarized Dark theme maintained throughout
                              • No tmux concepts visible anywhere in the UI — no session names, attach commands, or status bars referencing tmux
                              • All terminal management is presented as "terminals" not "panes" or "windows"
                              • Smooth reconnection UX: terminal shows "Reconnecting..." overlay, then replays buffer and resumes
                              • Connection state indicators per terminal (connected / reconnecting / disconnected)
                              • Responsive: splits on desktop, tabs on mobile, file explorer collapses on narrow viewports

                              Data / Persistence

                              • Terminal metadata: ~/.dancode/terminals/{terminalId}.json — id, projectSlug, label, tmuxSessionName, createdAt, lastActivity
                              • Project config: ~/.dancode/projects/{slug}.json — updated schema drops tmux fields, adds layout object for frontend state
                              • Output ring buffer: in-memory only (not persisted to disk) — repopulated from tmux scrollback on restart
                              • File explorer state: persisted in project config (expanded paths, panel visibility)

                              Technical Decisions

                              • Stack: No changes — Node.js, Express, Socket.io, React, Vite, xterm.js, Tailwind. Proven and working.
                              • Architecture: New TerminalManager class server-side replaces current tmux.js + terminal.js. Owns PTY lifecycle, output buffering, and reconnection. Single responsibility: manage terminal processes.
                              • Data model: Terminals become first-class entities with their own ID and metadata file, decoupled from tmux window indices. Projects reference terminals by ID, not by pane index.
                              • API surface: Clean REST for terminal CRUD + WebSocket per terminal for I/O. No tmux vocabulary in any API contract. File system API for explorer with strict path validation.
                              • Persistence strategy: Phase 1 uses bare PTYs (sessions lost on server restart). Phase 2 adds invisible tmux backing for full persistence. This phasing means Phase 1 is shippable without tmux complexity.
                              • Output buffer: ~50KB circular buffer per terminal, in-memory. Enough to replay a screenful+ on reconnect. Not persisted — tmux scrollback is the durable copy (Phase 2).
                              • Mobile: PWA (manifest + service worker), not a native app. Touch gestures via standard DOM events or a lightweight library (e.g., Hammer.js). Shortcut bar is a React component, not a native keyboard extension. Responsive breakpoints handled with Tailwind's existing breakpoint system.
                              • Testing: Existing Vitest + Playwright stack. New tests for TerminalManager lifecycle, reconnection replay, file system API path validation. Playwright mobile emulation for PWA/mobile layout tests. Visual tests updated to assert on new layout.

                              Out of Scope

                              • Code editor / Monaco integration (future phase, after file explorer proves useful)
                              • Ralph UI controls (separate PRD)
                              • Multi-server management
                              • Multi-user / role-based access
                              • Light mode / theming beyond Solarized Dark
                              • Session recording / playback
                              • Git integration in the UI
                              • Native Android/iOS app (PWA covers mobile; native wrapper via Capacitor is a future option if push notifications or app store presence become important)
                              • Offline terminal interaction (PWA caches the app shell, but terminals require server connectivity)

                              Open Questions

                              • Shell selection: Should terminal creation auto-detect the user's default shell ($SHELL) or always use bash? Leaning toward $SHELL with bash fallback.
                              • Buffer size: 50KB per terminal is a starting point. Should this be configurable per-project? Probably not worth the complexity initially.
                              • File explorer size limits: What's the right max file size to read via the API? 1MB seems reasonable for text files. Binary files should be blocked or download-only.
                              • Terminal limits: Should there be a max number of terminals per project? Probably not enforced, but the UI should handle 10+ terminals gracefully (tabs scroll or overflow).

                              🤖 Generated with Claude Code

                              Metadata

                              Metadata

                              Assignees

                              No one assigned

                                Labels

                                No labels
                                No labels

                                Projects

                                No projects

                                  Milestone

                                  No milestone

                                  Relationships

                                  None yet

                                  Development

                                  No branches or pull requests

                                  Issue actions