DanCode redesign: drop tmux, two-process backend with disk-persisted scrollback and layouts #2

Description

@DanielGGordon

Problem

DanCode currently uses tmux as the backend for persistent shell sessions, and that abstraction is the source of most of the day-to-day pain. The terminfo translation layer (screen-256color) causes formatting glitches; tmux's own scrollback buffer fights with xterm.js's scrollback causing scroll issues; the reconcile-on-startup logic races against tmux-resurrect and silently drops sessions; and the dependency on tmux + tmux-resurrect + tmux-continuum makes the system fragile and hard to reason about. tmux is also the wrong abstraction for the actual use case — what matters is files, Claude Code conversations, and a clean per-terminal experience, none of which need a multiplexer.

Solution

Replace the tmux backend with a two-process node-pty architecture: a long-lived dancode-shellhost daemon owns the PTYs and persists scrollback to disk, while the existing dancode-server handles HTTP, WebSockets, files, and projects. The two communicate over a UNIX socket. This makes ordinary server restarts (npm run dev reloads, crashes) fully invisible to the user, because the shellhost keeps running. Pi reboots still kill PTYs (unavoidable), but on the next open we re-spawn shells at the same cwd, replay the on-disk scrollback for context, and resume the matching Claude Code conversation by session id. A CodeMirror-based editor replaces the current text view. Per-project layout (which terminals existed, which files were open, splits) is serialized to disk and restored on project open.

Requirements

Core features

  • A separate dancode-shellhost process owns all PTYs; the main DanCode server connects to it over a UNIX socket and proxies I/O to/from browsers.
  • Shellhost runs as a systemd --user service with auto-restart, independent of the DanCode web server.
  • Restarting the DanCode web server (e.g. npm run dev reload, crash) MUST NOT kill running shells. The web server reconnects to existing PTYs on startup.
  • Restarting the shellhost (only) MUST recover all currently-known terminals by reading their persisted metadata.
  • After a Pi reboot, opening a project re-spawns each known terminal at its saved cwd, with its saved startup command, and the prior scrollback visible.
  • A "background" mode is available per-terminal that wraps the running command in systemd-run --user --scope so it truly survives a daemon restart (opt-in, for long jobs like builds/training).
  • All current functionality preserved: project list, create/rename/delete project, file browser, view/edit files, split panes, tabs within a project, mobile UI, auth (bcrypt + TOTP + 30-day session).

Terminal behavior

  • Each terminal is a node-pty process spawned with TERM=xterm-256color (no multiplexer in between).
  • Per-terminal scrollback log appended to ~/.dancode/terminals/<id>/scrollback.log as the PTY produces output. File is rotated at 1MB; only the most recent rotation is kept (so disk usage per terminal is bounded ~2MB worst case during rotation).
  • On socket reconnect (browser refresh, network blip), the last N KB of scrollback log is replayed to the client before live output resumes.
  • After PTY death + respawn (e.g. post-reboot), the scrollback log from the prior session is replayed as a banner ("--- prior session ended at ---") and then live output starts.
  • Resize events propagate to the PTY immediately.
  • Paste of plain text fires exactly once. Paste of image data triggers upload to project (current image-paste behavior preserved).
  • No bracketed-paste double-emit, no duplicated input, no terminfo translation glitches.

Project & layout persistence

  • Each project stores a layout.json at ~/.dancode/projects/<slug>/layout.json describing:
    • Ordered list of terminals: id, cwd (relative to project root), startup command (if any), claude-session-id (if any), background-mode flag.
    • Ordered list of open files: path (relative to project root), pane index, scroll position.
    • Split/tab structure of the workspace.
  • Layout is updated atomically on every meaningful change (terminal created/closed/moved, file opened/closed, split changed).
  • Opening a project reads layout.json and reconstructs the workspace. Missing files are surfaced as a warning, not a crash.
  • Renaming a project updates the slug AND moves the on-disk project directory (closes [[project_rename_bug]]).

Claude Code awareness

  • DanCode detects when a terminal is running claude (by spawned-command introspection on the PTY's foreground process, or by scanning ~/.claude/projects/<project-slug>/ for an active session).
  • When a Claude session is detected, the terminal's layout.json entry records the claude-session-id.
  • On respawn after Pi reboot, the terminal's startup command is claude --resume <session-id> rather than a bare shell, so the prior conversation is restored.
  • A "Resume last Claude session" UI affordance on the terminal pane offers manual resume.
  • No native chat UI — Claude still renders inside the terminal.

File editor

  • Replace the current view/edit with CodeMirror 6.
  • Syntax highlighting for: JavaScript/TypeScript, JSX/TSX, Python, JSON, Markdown, YAML, Bash, HTML, CSS. (Extensible — add more languages via @codemirror/lang-*.)
  • Standard editor affordances: find/replace, undo/redo, multi-cursor, line numbers.
  • Save: explicit (Ctrl+S) and on-blur. Server validates path is inside the project root before writing.
  • No LSP, no diagnostics, no completions (out of scope).

Migration from tmux

  • A one-shot bin/dancode-migrate-from-tmux script that:
    • For each existing dancode-* tmux session, captures the current pane contents via tmux capture-pane -p -S - into the new scrollback log format.
    • Writes a meta.json per terminal with cwd extracted from tmux display -t <session> -p '#{pane_current_path}'.
    • Builds an initial layout.json per project from the captured terminals.
    • Kills the tmux sessions on success.
    • Removes the legacy ~/.dancode/terminals/*.json files.
  • Migration is idempotent and safe to run multiple times.
  • After migration, the codebase removes all tmux-related code (tmuxHasSession, session-name builders, reconcile retry logic, etc.) and removes tmux from documented system dependencies.

Wire protocol (shellhost ↔ server)

  • UNIX socket at ~/.dancode/shellhost.sock.
  • Length-prefixed JSON or msgpack frames. (Decide during implementation.)
  • Commands: spawn, attach, detach, write, resize, kill, list, inspect.
  • Events from shellhost: output, exit, respawn-needed.
  • All commands carry a request id; responses correlate. Disconnect from the server side does not kill PTYs — only an explicit kill does.

AI integration / self-modification

  • DanCode itself remains AI-editable via Claude Code running in a DanCode terminal (eat-your-own-dogfood). No new in-app AI surface is needed beyond what's in scope for Claude awareness above.

UX / interface

  • No visible UI changes for the steady-state user beyond:
    • Fewer formatting bugs in terminals.
    • Smooth scrolling that doesn't fight tmux.
    • A small "Resume Claude" button on terminals where Claude was last running.
    • Layout actually restores on project open.
  • Existing mobile UI, shortcut bar, image paste, and drag-and-drop file upload preserved.

Data / persistence

  • ~/.dancode/projects/<slug>/layout.json — per-project layout state.
  • ~/.dancode/terminals/<id>/meta.json — per-terminal metadata (project slug, cwd, command, claude session id, background flag, last-active timestamp).
  • ~/.dancode/terminals/<id>/scrollback.log — append-only output stream, rotated at 1MB.
  • ~/.dancode/credentials.json — unchanged (bcrypt hash + TOTP secret).
  • ~/.dancode/sessions.json — unchanged (server-side 30-day session tokens).
  • ~/.dancode/shellhost.sock — runtime socket; not data.

Testing

  • Vitest workspaces (server, client) — already in place.
  • New unit tests for:
    • Scrollback writer (append, rotate, replay correctness).
    • Layout serializer (round-trip, atomic write, partial-corruption recovery).
    • Shellhost wire-protocol framing.
    • Shellhost: spawn, attach, detach, kill, output streaming.
  • New integration tests for:
    • Server-restart scenario: spawn terminal → kill+restart server process → attach → verify alive.
    • Shellhost-restart scenario: spawn → kill shellhost → restart → meta survives → respawn.
    • Migration script against a fixture set of tmux sessions (using a disposable tmux server in CI).
  • Existing 230/232 server tests should pass at the same baseline (or better, with the reconcile race tests removed).
  • Manual smoke test plan: paste single + double + image, large output scrollback, splits, tabs, project rename, Claude resume, simulated reboot via systemctl --user restart dancode-shellhost.

Technical decisions

  • Stack: Node 20+, Express 5, Socket.IO, node-pty, CodeMirror 6, Vitest. Same as today minus tmux + tmux-resurrect + tmux-continuum. Add systemd --user unit file for the shellhost.
  • Architecture: Two processes — dancode-server (web/HTTP/WS, file ops, auth, projects) and dancode-shellhost (PTY ownership, scrollback writers). UNIX-socket RPC between them. Browser only talks to dancode-server; never to shellhost directly.
  • Data model:
    • Project: slug, name, root path, created/updated timestamps.
    • Terminal: id (uuid), project slug, cwd, startup command, claude-session-id, background flag, last-active.
    • Layout: project slug, terminals (ordered), open files (ordered), splits/tabs structure.
    • Session: token, user, created, expires (auth — unchanged).
  • API surface (HTTP, unchanged shape, semantics shift):
    • GET /api/projects, POST /api/projects, PATCH /api/projects/:slug, DELETE /api/projects/:slug.
    • GET /api/projects/:slug/layout, PUT /api/projects/:slug/layout.
    • GET /api/projects/:slug/files, GET /api/projects/:slug/files/*, PUT /api/projects/:slug/files/*.
    • POST /api/projects/:slug/upload (image paste — unchanged).
    • GET /api/terminals?projectSlug=, POST /api/terminals, DELETE /api/terminals/:id.
    • WebSocket /terminal/:id proxies to shellhost.
    • Auth endpoints unchanged.
  • API surface (UNIX socket, new): command/response with request ids, see Wire protocol above.
  • Testing: Vitest unit + integration. Shellhost has its own test file. Migration script tested against fixture tmux sessions.
  • Process model in dev: npm run dev starts dancode-server and dancode-shellhost concurrently (replacing the current concurrently config). On the Pi, shellhost lives in systemd --user; web server lives in either dev or systemd.

Out of scope

  • LSP, IntelliSense, completions, diagnostics.
  • Native Claude Code chat UI (Claude still runs in the terminal).
  • Drag-to-rearrange layout, saved layout presets.
  • Multi-user — DanCode remains single-user.
  • True process survival across Pi power loss (impossible). Re-spawn-with-resume is the offered guarantee.
  • Remote / cloud deployment. DanCode stays self-hosted on the Pi.
  • Migration from anything other than the current tmux-based DanCode.

Open questions

  • Wire-protocol encoding: length-prefixed JSON vs. msgpack vs. CBOR. Decide during planning based on size / Pi CPU profile.
  • Scrollback log format: raw bytes vs. timestamped chunks. Decide during planning — timestamps cost ~10% disk but enable "scrollback at time X" UX later.
  • Whether systemd-run --user --scope is sufficient for "background mode" or if we need a small supervisor inside shellhost.
  • Handling of the existing 10 known terminals during migration — should the migration script run automatically on first server start with the new code, or be invoked manually? Probably manual, with a one-liner in the upgrade docs.

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

      DanCode redesign: drop tmux, two-process backend with disk-persisted scrollback and layouts #2

      Description

      @DanielGGordon

      Problem

      DanCode currently uses tmux as the backend for persistent shell sessions, and that abstraction is the source of most of the day-to-day pain. The terminfo translation layer (screen-256color) causes formatting glitches; tmux's own scrollback buffer fights with xterm.js's scrollback causing scroll issues; the reconcile-on-startup logic races against tmux-resurrect and silently drops sessions; and the dependency on tmux + tmux-resurrect + tmux-continuum makes the system fragile and hard to reason about. tmux is also the wrong abstraction for the actual use case — what matters is files, Claude Code conversations, and a clean per-terminal experience, none of which need a multiplexer.

      Solution

      Replace the tmux backend with a two-process node-pty architecture: a long-lived dancode-shellhost daemon owns the PTYs and persists scrollback to disk, while the existing dancode-server handles HTTP, WebSockets, files, and projects. The two communicate over a UNIX socket. This makes ordinary server restarts (npm run dev reloads, crashes) fully invisible to the user, because the shellhost keeps running. Pi reboots still kill PTYs (unavoidable), but on the next open we re-spawn shells at the same cwd, replay the on-disk scrollback for context, and resume the matching Claude Code conversation by session id. A CodeMirror-based editor replaces the current text view. Per-project layout (which terminals existed, which files were open, splits) is serialized to disk and restored on project open.

      Requirements

      Core features

      • A separate dancode-shellhost process owns all PTYs; the main DanCode server connects to it over a UNIX socket and proxies I/O to/from browsers.
      • Shellhost runs as a systemd --user service with auto-restart, independent of the DanCode web server.
      • Restarting the DanCode web server (e.g. npm run dev reload, crash) MUST NOT kill running shells. The web server reconnects to existing PTYs on startup.
      • Restarting the shellhost (only) MUST recover all currently-known terminals by reading their persisted metadata.
      • After a Pi reboot, opening a project re-spawns each known terminal at its saved cwd, with its saved startup command, and the prior scrollback visible.
      • A "background" mode is available per-terminal that wraps the running command in systemd-run --user --scope so it truly survives a daemon restart (opt-in, for long jobs like builds/training).
      • All current functionality preserved: project list, create/rename/delete project, file browser, view/edit files, split panes, tabs within a project, mobile UI, auth (bcrypt + TOTP + 30-day session).

      Terminal behavior

      • Each terminal is a node-pty process spawned with TERM=xterm-256color (no multiplexer in between).
      • Per-terminal scrollback log appended to ~/.dancode/terminals/<id>/scrollback.log as the PTY produces output. File is rotated at 1MB; only the most recent rotation is kept (so disk usage per terminal is bounded ~2MB worst case during rotation).
      • On socket reconnect (browser refresh, network blip), the last N KB of scrollback log is replayed to the client before live output resumes.
      • After PTY death + respawn (e.g. post-reboot), the scrollback log from the prior session is replayed as a banner ("--- prior session ended at ---") and then live output starts.
      • Resize events propagate to the PTY immediately.
      • Paste of plain text fires exactly once. Paste of image data triggers upload to project (current image-paste behavior preserved).
      • No bracketed-paste double-emit, no duplicated input, no terminfo translation glitches.

      Project & layout persistence

      • Each project stores a layout.json at ~/.dancode/projects/<slug>/layout.json describing:
        • Ordered list of terminals: id, cwd (relative to project root), startup command (if any), claude-session-id (if any), background-mode flag.
        • Ordered list of open files: path (relative to project root), pane index, scroll position.
        • Split/tab structure of the workspace.
      • Layout is updated atomically on every meaningful change (terminal created/closed/moved, file opened/closed, split changed).
      • Opening a project reads layout.json and reconstructs the workspace. Missing files are surfaced as a warning, not a crash.
      • Renaming a project updates the slug AND moves the on-disk project directory (closes [[project_rename_bug]]).

      Claude Code awareness

      • DanCode detects when a terminal is running claude (by spawned-command introspection on the PTY's foreground process, or by scanning ~/.claude/projects/<project-slug>/ for an active session).
      • When a Claude session is detected, the terminal's layout.json entry records the claude-session-id.
      • On respawn after Pi reboot, the terminal's startup command is claude --resume <session-id> rather than a bare shell, so the prior conversation is restored.
      • A "Resume last Claude session" UI affordance on the terminal pane offers manual resume.
      • No native chat UI — Claude still renders inside the terminal.

      File editor

      • Replace the current view/edit with CodeMirror 6.
      • Syntax highlighting for: JavaScript/TypeScript, JSX/TSX, Python, JSON, Markdown, YAML, Bash, HTML, CSS. (Extensible — add more languages via @codemirror/lang-*.)
      • Standard editor affordances: find/replace, undo/redo, multi-cursor, line numbers.
      • Save: explicit (Ctrl+S) and on-blur. Server validates path is inside the project root before writing.
      • No LSP, no diagnostics, no completions (out of scope).

      Migration from tmux

      • A one-shot bin/dancode-migrate-from-tmux script that:
        • For each existing dancode-* tmux session, captures the current pane contents via tmux capture-pane -p -S - into the new scrollback log format.
        • Writes a meta.json per terminal with cwd extracted from tmux display -t <session> -p '#{pane_current_path}'.
        • Builds an initial layout.json per project from the captured terminals.
        • Kills the tmux sessions on success.
        • Removes the legacy ~/.dancode/terminals/*.json files.
      • Migration is idempotent and safe to run multiple times.
      • After migration, the codebase removes all tmux-related code (tmuxHasSession, session-name builders, reconcile retry logic, etc.) and removes tmux from documented system dependencies.

      Wire protocol (shellhost ↔ server)

      • UNIX socket at ~/.dancode/shellhost.sock.
      • Length-prefixed JSON or msgpack frames. (Decide during implementation.)
      • Commands: spawn, attach, detach, write, resize, kill, list, inspect.
      • Events from shellhost: output, exit, respawn-needed.
      • All commands carry a request id; responses correlate. Disconnect from the server side does not kill PTYs — only an explicit kill does.

      AI integration / self-modification

      • DanCode itself remains AI-editable via Claude Code running in a DanCode terminal (eat-your-own-dogfood). No new in-app AI surface is needed beyond what's in scope for Claude awareness above.

      UX / interface

      • No visible UI changes for the steady-state user beyond:
        • Fewer formatting bugs in terminals.
        • Smooth scrolling that doesn't fight tmux.
        • A small "Resume Claude" button on terminals where Claude was last running.
        • Layout actually restores on project open.
      • Existing mobile UI, shortcut bar, image paste, and drag-and-drop file upload preserved.

      Data / persistence

      • ~/.dancode/projects/<slug>/layout.json — per-project layout state.
      • ~/.dancode/terminals/<id>/meta.json — per-terminal metadata (project slug, cwd, command, claude session id, background flag, last-active timestamp).
      • ~/.dancode/terminals/<id>/scrollback.log — append-only output stream, rotated at 1MB.
      • ~/.dancode/credentials.json — unchanged (bcrypt hash + TOTP secret).
      • ~/.dancode/sessions.json — unchanged (server-side 30-day session tokens).
      • ~/.dancode/shellhost.sock — runtime socket; not data.

      Testing

      • Vitest workspaces (server, client) — already in place.
      • New unit tests for:
        • Scrollback writer (append, rotate, replay correctness).
        • Layout serializer (round-trip, atomic write, partial-corruption recovery).
        • Shellhost wire-protocol framing.
        • Shellhost: spawn, attach, detach, kill, output streaming.
      • New integration tests for:
        • Server-restart scenario: spawn terminal → kill+restart server process → attach → verify alive.
        • Shellhost-restart scenario: spawn → kill shellhost → restart → meta survives → respawn.
        • Migration script against a fixture set of tmux sessions (using a disposable tmux server in CI).
      • Existing 230/232 server tests should pass at the same baseline (or better, with the reconcile race tests removed).
      • Manual smoke test plan: paste single + double + image, large output scrollback, splits, tabs, project rename, Claude resume, simulated reboot via systemctl --user restart dancode-shellhost.

      Technical decisions

      • Stack: Node 20+, Express 5, Socket.IO, node-pty, CodeMirror 6, Vitest. Same as today minus tmux + tmux-resurrect + tmux-continuum. Add systemd --user unit file for the shellhost.
      • Architecture: Two processes — dancode-server (web/HTTP/WS, file ops, auth, projects) and dancode-shellhost (PTY ownership, scrollback writers). UNIX-socket RPC between them. Browser only talks to dancode-server; never to shellhost directly.
      • Data model:
        • Project: slug, name, root path, created/updated timestamps.
        • Terminal: id (uuid), project slug, cwd, startup command, claude-session-id, background flag, last-active.
        • Layout: project slug, terminals (ordered), open files (ordered), splits/tabs structure.
        • Session: token, user, created, expires (auth — unchanged).
      • API surface (HTTP, unchanged shape, semantics shift):
        • GET /api/projects, POST /api/projects, PATCH /api/projects/:slug, DELETE /api/projects/:slug.
        • GET /api/projects/:slug/layout, PUT /api/projects/:slug/layout.
        • GET /api/projects/:slug/files, GET /api/projects/:slug/files/*, PUT /api/projects/:slug/files/*.
        • POST /api/projects/:slug/upload (image paste — unchanged).
        • GET /api/terminals?projectSlug=, POST /api/terminals, DELETE /api/terminals/:id.
        • WebSocket /terminal/:id proxies to shellhost.
        • Auth endpoints unchanged.
      • API surface (UNIX socket, new): command/response with request ids, see Wire protocol above.
      • Testing: Vitest unit + integration. Shellhost has its own test file. Migration script tested against fixture tmux sessions.
      • Process model in dev: npm run dev starts dancode-server and dancode-shellhost concurrently (replacing the current concurrently config). On the Pi, shellhost lives in systemd --user; web server lives in either dev or systemd.

      Out of scope

      • LSP, IntelliSense, completions, diagnostics.
      • Native Claude Code chat UI (Claude still runs in the terminal).
      • Drag-to-rearrange layout, saved layout presets.
      • Multi-user — DanCode remains single-user.
      • True process survival across Pi power loss (impossible). Re-spawn-with-resume is the offered guarantee.
      • Remote / cloud deployment. DanCode stays self-hosted on the Pi.
      • Migration from anything other than the current tmux-based DanCode.

      Open questions

      • Wire-protocol encoding: length-prefixed JSON vs. msgpack vs. CBOR. Decide during planning based on size / Pi CPU profile.
      • Scrollback log format: raw bytes vs. timestamped chunks. Decide during planning — timestamps cost ~10% disk but enable "scrollback at time X" UX later.
      • Whether systemd-run --user --scope is sufficient for "background mode" or if we need a small supervisor inside shellhost.
      • Handling of the existing 10 known terminals during migration — should the migration script run automatically on first server start with the new code, or be invoked manually? Probably manual, with a one-liner in the upgrade docs.

      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

          DanCode redesign: drop tmux, two-process backend with disk-persisted scrollback and layouts #2

          Description

          @DanielGGordon

          Problem

          DanCode currently uses tmux as the backend for persistent shell sessions, and that abstraction is the source of most of the day-to-day pain. The terminfo translation layer (screen-256color) causes formatting glitches; tmux's own scrollback buffer fights with xterm.js's scrollback causing scroll issues; the reconcile-on-startup logic races against tmux-resurrect and silently drops sessions; and the dependency on tmux + tmux-resurrect + tmux-continuum makes the system fragile and hard to reason about. tmux is also the wrong abstraction for the actual use case — what matters is files, Claude Code conversations, and a clean per-terminal experience, none of which need a multiplexer.

          Solution

          Replace the tmux backend with a two-process node-pty architecture: a long-lived dancode-shellhost daemon owns the PTYs and persists scrollback to disk, while the existing dancode-server handles HTTP, WebSockets, files, and projects. The two communicate over a UNIX socket. This makes ordinary server restarts (npm run dev reloads, crashes) fully invisible to the user, because the shellhost keeps running. Pi reboots still kill PTYs (unavoidable), but on the next open we re-spawn shells at the same cwd, replay the on-disk scrollback for context, and resume the matching Claude Code conversation by session id. A CodeMirror-based editor replaces the current text view. Per-project layout (which terminals existed, which files were open, splits) is serialized to disk and restored on project open.

          Requirements

          Core features

          • A separate dancode-shellhost process owns all PTYs; the main DanCode server connects to it over a UNIX socket and proxies I/O to/from browsers.
          • Shellhost runs as a systemd --user service with auto-restart, independent of the DanCode web server.
          • Restarting the DanCode web server (e.g. npm run dev reload, crash) MUST NOT kill running shells. The web server reconnects to existing PTYs on startup.
          • Restarting the shellhost (only) MUST recover all currently-known terminals by reading their persisted metadata.
          • After a Pi reboot, opening a project re-spawns each known terminal at its saved cwd, with its saved startup command, and the prior scrollback visible.
          • A "background" mode is available per-terminal that wraps the running command in systemd-run --user --scope so it truly survives a daemon restart (opt-in, for long jobs like builds/training).
          • All current functionality preserved: project list, create/rename/delete project, file browser, view/edit files, split panes, tabs within a project, mobile UI, auth (bcrypt + TOTP + 30-day session).

          Terminal behavior

          • Each terminal is a node-pty process spawned with TERM=xterm-256color (no multiplexer in between).
          • Per-terminal scrollback log appended to ~/.dancode/terminals/<id>/scrollback.log as the PTY produces output. File is rotated at 1MB; only the most recent rotation is kept (so disk usage per terminal is bounded ~2MB worst case during rotation).
          • On socket reconnect (browser refresh, network blip), the last N KB of scrollback log is replayed to the client before live output resumes.
          • After PTY death + respawn (e.g. post-reboot), the scrollback log from the prior session is replayed as a banner ("--- prior session ended at ---") and then live output starts.
          • Resize events propagate to the PTY immediately.
          • Paste of plain text fires exactly once. Paste of image data triggers upload to project (current image-paste behavior preserved).
          • No bracketed-paste double-emit, no duplicated input, no terminfo translation glitches.

          Project & layout persistence

          • Each project stores a layout.json at ~/.dancode/projects/<slug>/layout.json describing:
            • Ordered list of terminals: id, cwd (relative to project root), startup command (if any), claude-session-id (if any), background-mode flag.
            • Ordered list of open files: path (relative to project root), pane index, scroll position.
            • Split/tab structure of the workspace.
          • Layout is updated atomically on every meaningful change (terminal created/closed/moved, file opened/closed, split changed).
          • Opening a project reads layout.json and reconstructs the workspace. Missing files are surfaced as a warning, not a crash.
          • Renaming a project updates the slug AND moves the on-disk project directory (closes [[project_rename_bug]]).

          Claude Code awareness

          • DanCode detects when a terminal is running claude (by spawned-command introspection on the PTY's foreground process, or by scanning ~/.claude/projects/<project-slug>/ for an active session).
          • When a Claude session is detected, the terminal's layout.json entry records the claude-session-id.
          • On respawn after Pi reboot, the terminal's startup command is claude --resume <session-id> rather than a bare shell, so the prior conversation is restored.
          • A "Resume last Claude session" UI affordance on the terminal pane offers manual resume.
          • No native chat UI — Claude still renders inside the terminal.

          File editor

          • Replace the current view/edit with CodeMirror 6.
          • Syntax highlighting for: JavaScript/TypeScript, JSX/TSX, Python, JSON, Markdown, YAML, Bash, HTML, CSS. (Extensible — add more languages via @codemirror/lang-*.)
          • Standard editor affordances: find/replace, undo/redo, multi-cursor, line numbers.
          • Save: explicit (Ctrl+S) and on-blur. Server validates path is inside the project root before writing.
          • No LSP, no diagnostics, no completions (out of scope).

          Migration from tmux

          • A one-shot bin/dancode-migrate-from-tmux script that:
            • For each existing dancode-* tmux session, captures the current pane contents via tmux capture-pane -p -S - into the new scrollback log format.
            • Writes a meta.json per terminal with cwd extracted from tmux display -t <session> -p '#{pane_current_path}'.
            • Builds an initial layout.json per project from the captured terminals.
            • Kills the tmux sessions on success.
            • Removes the legacy ~/.dancode/terminals/*.json files.
          • Migration is idempotent and safe to run multiple times.
          • After migration, the codebase removes all tmux-related code (tmuxHasSession, session-name builders, reconcile retry logic, etc.) and removes tmux from documented system dependencies.

          Wire protocol (shellhost ↔ server)

          • UNIX socket at ~/.dancode/shellhost.sock.
          • Length-prefixed JSON or msgpack frames. (Decide during implementation.)
          • Commands: spawn, attach, detach, write, resize, kill, list, inspect.
          • Events from shellhost: output, exit, respawn-needed.
          • All commands carry a request id; responses correlate. Disconnect from the server side does not kill PTYs — only an explicit kill does.

          AI integration / self-modification

          • DanCode itself remains AI-editable via Claude Code running in a DanCode terminal (eat-your-own-dogfood). No new in-app AI surface is needed beyond what's in scope for Claude awareness above.

          UX / interface

          • No visible UI changes for the steady-state user beyond:
            • Fewer formatting bugs in terminals.
            • Smooth scrolling that doesn't fight tmux.
            • A small "Resume Claude" button on terminals where Claude was last running.
            • Layout actually restores on project open.
          • Existing mobile UI, shortcut bar, image paste, and drag-and-drop file upload preserved.

          Data / persistence

          • ~/.dancode/projects/<slug>/layout.json — per-project layout state.
          • ~/.dancode/terminals/<id>/meta.json — per-terminal metadata (project slug, cwd, command, claude session id, background flag, last-active timestamp).
          • ~/.dancode/terminals/<id>/scrollback.log — append-only output stream, rotated at 1MB.
          • ~/.dancode/credentials.json — unchanged (bcrypt hash + TOTP secret).
          • ~/.dancode/sessions.json — unchanged (server-side 30-day session tokens).
          • ~/.dancode/shellhost.sock — runtime socket; not data.

          Testing

          • Vitest workspaces (server, client) — already in place.
          • New unit tests for:
            • Scrollback writer (append, rotate, replay correctness).
            • Layout serializer (round-trip, atomic write, partial-corruption recovery).
            • Shellhost wire-protocol framing.
            • Shellhost: spawn, attach, detach, kill, output streaming.
          • New integration tests for:
            • Server-restart scenario: spawn terminal → kill+restart server process → attach → verify alive.
            • Shellhost-restart scenario: spawn → kill shellhost → restart → meta survives → respawn.
            • Migration script against a fixture set of tmux sessions (using a disposable tmux server in CI).
          • Existing 230/232 server tests should pass at the same baseline (or better, with the reconcile race tests removed).
          • Manual smoke test plan: paste single + double + image, large output scrollback, splits, tabs, project rename, Claude resume, simulated reboot via systemctl --user restart dancode-shellhost.

          Technical decisions

          • Stack: Node 20+, Express 5, Socket.IO, node-pty, CodeMirror 6, Vitest. Same as today minus tmux + tmux-resurrect + tmux-continuum. Add systemd --user unit file for the shellhost.
          • Architecture: Two processes — dancode-server (web/HTTP/WS, file ops, auth, projects) and dancode-shellhost (PTY ownership, scrollback writers). UNIX-socket RPC between them. Browser only talks to dancode-server; never to shellhost directly.
          • Data model:
            • Project: slug, name, root path, created/updated timestamps.
            • Terminal: id (uuid), project slug, cwd, startup command, claude-session-id, background flag, last-active.
            • Layout: project slug, terminals (ordered), open files (ordered), splits/tabs structure.
            • Session: token, user, created, expires (auth — unchanged).
          • API surface (HTTP, unchanged shape, semantics shift):
            • GET /api/projects, POST /api/projects, PATCH /api/projects/:slug, DELETE /api/projects/:slug.
            • GET /api/projects/:slug/layout, PUT /api/projects/:slug/layout.
            • GET /api/projects/:slug/files, GET /api/projects/:slug/files/*, PUT /api/projects/:slug/files/*.
            • POST /api/projects/:slug/upload (image paste — unchanged).
            • GET /api/terminals?projectSlug=, POST /api/terminals, DELETE /api/terminals/:id.
            • WebSocket /terminal/:id proxies to shellhost.
            • Auth endpoints unchanged.
          • API surface (UNIX socket, new): command/response with request ids, see Wire protocol above.
          • Testing: Vitest unit + integration. Shellhost has its own test file. Migration script tested against fixture tmux sessions.
          • Process model in dev: npm run dev starts dancode-server and dancode-shellhost concurrently (replacing the current concurrently config). On the Pi, shellhost lives in systemd --user; web server lives in either dev or systemd.

          Out of scope

          • LSP, IntelliSense, completions, diagnostics.
          • Native Claude Code chat UI (Claude still runs in the terminal).
          • Drag-to-rearrange layout, saved layout presets.
          • Multi-user — DanCode remains single-user.
          • True process survival across Pi power loss (impossible). Re-spawn-with-resume is the offered guarantee.
          • Remote / cloud deployment. DanCode stays self-hosted on the Pi.
          • Migration from anything other than the current tmux-based DanCode.

          Open questions

          • Wire-protocol encoding: length-prefixed JSON vs. msgpack vs. CBOR. Decide during planning based on size / Pi CPU profile.
          • Scrollback log format: raw bytes vs. timestamped chunks. Decide during planning — timestamps cost ~10% disk but enable "scrollback at time X" UX later.
          • Whether systemd-run --user --scope is sufficient for "background mode" or if we need a small supervisor inside shellhost.
          • Handling of the existing 10 known terminals during migration — should the migration script run automatically on first server start with the new code, or be invoked manually? Probably manual, with a one-liner in the upgrade docs.

          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

              DanCode redesign: drop tmux, two-process backend with disk-persisted scrollback and layouts #2

              Description

              @DanielGGordon

              Problem

              DanCode currently uses tmux as the backend for persistent shell sessions, and that abstraction is the source of most of the day-to-day pain. The terminfo translation layer (screen-256color) causes formatting glitches; tmux's own scrollback buffer fights with xterm.js's scrollback causing scroll issues; the reconcile-on-startup logic races against tmux-resurrect and silently drops sessions; and the dependency on tmux + tmux-resurrect + tmux-continuum makes the system fragile and hard to reason about. tmux is also the wrong abstraction for the actual use case — what matters is files, Claude Code conversations, and a clean per-terminal experience, none of which need a multiplexer.

              Solution

              Replace the tmux backend with a two-process node-pty architecture: a long-lived dancode-shellhost daemon owns the PTYs and persists scrollback to disk, while the existing dancode-server handles HTTP, WebSockets, files, and projects. The two communicate over a UNIX socket. This makes ordinary server restarts (npm run dev reloads, crashes) fully invisible to the user, because the shellhost keeps running. Pi reboots still kill PTYs (unavoidable), but on the next open we re-spawn shells at the same cwd, replay the on-disk scrollback for context, and resume the matching Claude Code conversation by session id. A CodeMirror-based editor replaces the current text view. Per-project layout (which terminals existed, which files were open, splits) is serialized to disk and restored on project open.

              Requirements

              Core features

              • A separate dancode-shellhost process owns all PTYs; the main DanCode server connects to it over a UNIX socket and proxies I/O to/from browsers.
              • Shellhost runs as a systemd --user service with auto-restart, independent of the DanCode web server.
              • Restarting the DanCode web server (e.g. npm run dev reload, crash) MUST NOT kill running shells. The web server reconnects to existing PTYs on startup.
              • Restarting the shellhost (only) MUST recover all currently-known terminals by reading their persisted metadata.
              • After a Pi reboot, opening a project re-spawns each known terminal at its saved cwd, with its saved startup command, and the prior scrollback visible.
              • A "background" mode is available per-terminal that wraps the running command in systemd-run --user --scope so it truly survives a daemon restart (opt-in, for long jobs like builds/training).
              • All current functionality preserved: project list, create/rename/delete project, file browser, view/edit files, split panes, tabs within a project, mobile UI, auth (bcrypt + TOTP + 30-day session).

              Terminal behavior

              • Each terminal is a node-pty process spawned with TERM=xterm-256color (no multiplexer in between).
              • Per-terminal scrollback log appended to ~/.dancode/terminals/<id>/scrollback.log as the PTY produces output. File is rotated at 1MB; only the most recent rotation is kept (so disk usage per terminal is bounded ~2MB worst case during rotation).
              • On socket reconnect (browser refresh, network blip), the last N KB of scrollback log is replayed to the client before live output resumes.
              • After PTY death + respawn (e.g. post-reboot), the scrollback log from the prior session is replayed as a banner ("--- prior session ended at ---") and then live output starts.
              • Resize events propagate to the PTY immediately.
              • Paste of plain text fires exactly once. Paste of image data triggers upload to project (current image-paste behavior preserved).
              • No bracketed-paste double-emit, no duplicated input, no terminfo translation glitches.

              Project & layout persistence

              • Each project stores a layout.json at ~/.dancode/projects/<slug>/layout.json describing:
                • Ordered list of terminals: id, cwd (relative to project root), startup command (if any), claude-session-id (if any), background-mode flag.
                • Ordered list of open files: path (relative to project root), pane index, scroll position.
                • Split/tab structure of the workspace.
              • Layout is updated atomically on every meaningful change (terminal created/closed/moved, file opened/closed, split changed).
              • Opening a project reads layout.json and reconstructs the workspace. Missing files are surfaced as a warning, not a crash.
              • Renaming a project updates the slug AND moves the on-disk project directory (closes [[project_rename_bug]]).

              Claude Code awareness

              • DanCode detects when a terminal is running claude (by spawned-command introspection on the PTY's foreground process, or by scanning ~/.claude/projects/<project-slug>/ for an active session).
              • When a Claude session is detected, the terminal's layout.json entry records the claude-session-id.
              • On respawn after Pi reboot, the terminal's startup command is claude --resume <session-id> rather than a bare shell, so the prior conversation is restored.
              • A "Resume last Claude session" UI affordance on the terminal pane offers manual resume.
              • No native chat UI — Claude still renders inside the terminal.

              File editor

              • Replace the current view/edit with CodeMirror 6.
              • Syntax highlighting for: JavaScript/TypeScript, JSX/TSX, Python, JSON, Markdown, YAML, Bash, HTML, CSS. (Extensible — add more languages via @codemirror/lang-*.)
              • Standard editor affordances: find/replace, undo/redo, multi-cursor, line numbers.
              • Save: explicit (Ctrl+S) and on-blur. Server validates path is inside the project root before writing.
              • No LSP, no diagnostics, no completions (out of scope).

              Migration from tmux

              • A one-shot bin/dancode-migrate-from-tmux script that:
                • For each existing dancode-* tmux session, captures the current pane contents via tmux capture-pane -p -S - into the new scrollback log format.
                • Writes a meta.json per terminal with cwd extracted from tmux display -t <session> -p '#{pane_current_path}'.
                • Builds an initial layout.json per project from the captured terminals.
                • Kills the tmux sessions on success.
                • Removes the legacy ~/.dancode/terminals/*.json files.
              • Migration is idempotent and safe to run multiple times.
              • After migration, the codebase removes all tmux-related code (tmuxHasSession, session-name builders, reconcile retry logic, etc.) and removes tmux from documented system dependencies.

              Wire protocol (shellhost ↔ server)

              • UNIX socket at ~/.dancode/shellhost.sock.
              • Length-prefixed JSON or msgpack frames. (Decide during implementation.)
              • Commands: spawn, attach, detach, write, resize, kill, list, inspect.
              • Events from shellhost: output, exit, respawn-needed.
              • All commands carry a request id; responses correlate. Disconnect from the server side does not kill PTYs — only an explicit kill does.

              AI integration / self-modification

              • DanCode itself remains AI-editable via Claude Code running in a DanCode terminal (eat-your-own-dogfood). No new in-app AI surface is needed beyond what's in scope for Claude awareness above.

              UX / interface

              • No visible UI changes for the steady-state user beyond:
                • Fewer formatting bugs in terminals.
                • Smooth scrolling that doesn't fight tmux.
                • A small "Resume Claude" button on terminals where Claude was last running.
                • Layout actually restores on project open.
              • Existing mobile UI, shortcut bar, image paste, and drag-and-drop file upload preserved.

              Data / persistence

              • ~/.dancode/projects/<slug>/layout.json — per-project layout state.
              • ~/.dancode/terminals/<id>/meta.json — per-terminal metadata (project slug, cwd, command, claude session id, background flag, last-active timestamp).
              • ~/.dancode/terminals/<id>/scrollback.log — append-only output stream, rotated at 1MB.
              • ~/.dancode/credentials.json — unchanged (bcrypt hash + TOTP secret).
              • ~/.dancode/sessions.json — unchanged (server-side 30-day session tokens).
              • ~/.dancode/shellhost.sock — runtime socket; not data.

              Testing

              • Vitest workspaces (server, client) — already in place.
              • New unit tests for:
                • Scrollback writer (append, rotate, replay correctness).
                • Layout serializer (round-trip, atomic write, partial-corruption recovery).
                • Shellhost wire-protocol framing.
                • Shellhost: spawn, attach, detach, kill, output streaming.
              • New integration tests for:
                • Server-restart scenario: spawn terminal → kill+restart server process → attach → verify alive.
                • Shellhost-restart scenario: spawn → kill shellhost → restart → meta survives → respawn.
                • Migration script against a fixture set of tmux sessions (using a disposable tmux server in CI).
              • Existing 230/232 server tests should pass at the same baseline (or better, with the reconcile race tests removed).
              • Manual smoke test plan: paste single + double + image, large output scrollback, splits, tabs, project rename, Claude resume, simulated reboot via systemctl --user restart dancode-shellhost.

              Technical decisions

              • Stack: Node 20+, Express 5, Socket.IO, node-pty, CodeMirror 6, Vitest. Same as today minus tmux + tmux-resurrect + tmux-continuum. Add systemd --user unit file for the shellhost.
              • Architecture: Two processes — dancode-server (web/HTTP/WS, file ops, auth, projects) and dancode-shellhost (PTY ownership, scrollback writers). UNIX-socket RPC between them. Browser only talks to dancode-server; never to shellhost directly.
              • Data model:
                • Project: slug, name, root path, created/updated timestamps.
                • Terminal: id (uuid), project slug, cwd, startup command, claude-session-id, background flag, last-active.
                • Layout: project slug, terminals (ordered), open files (ordered), splits/tabs structure.
                • Session: token, user, created, expires (auth — unchanged).
              • API surface (HTTP, unchanged shape, semantics shift):
                • GET /api/projects, POST /api/projects, PATCH /api/projects/:slug, DELETE /api/projects/:slug.
                • GET /api/projects/:slug/layout, PUT /api/projects/:slug/layout.
                • GET /api/projects/:slug/files, GET /api/projects/:slug/files/*, PUT /api/projects/:slug/files/*.
                • POST /api/projects/:slug/upload (image paste — unchanged).
                • GET /api/terminals?projectSlug=, POST /api/terminals, DELETE /api/terminals/:id.
                • WebSocket /terminal/:id proxies to shellhost.
                • Auth endpoints unchanged.
              • API surface (UNIX socket, new): command/response with request ids, see Wire protocol above.
              • Testing: Vitest unit + integration. Shellhost has its own test file. Migration script tested against fixture tmux sessions.
              • Process model in dev: npm run dev starts dancode-server and dancode-shellhost concurrently (replacing the current concurrently config). On the Pi, shellhost lives in systemd --user; web server lives in either dev or systemd.

              Out of scope

              • LSP, IntelliSense, completions, diagnostics.
              • Native Claude Code chat UI (Claude still runs in the terminal).
              • Drag-to-rearrange layout, saved layout presets.
              • Multi-user — DanCode remains single-user.
              • True process survival across Pi power loss (impossible). Re-spawn-with-resume is the offered guarantee.
              • Remote / cloud deployment. DanCode stays self-hosted on the Pi.
              • Migration from anything other than the current tmux-based DanCode.

              Open questions

              • Wire-protocol encoding: length-prefixed JSON vs. msgpack vs. CBOR. Decide during planning based on size / Pi CPU profile.
              • Scrollback log format: raw bytes vs. timestamped chunks. Decide during planning — timestamps cost ~10% disk but enable "scrollback at time X" UX later.
              • Whether systemd-run --user --scope is sufficient for "background mode" or if we need a small supervisor inside shellhost.
              • Handling of the existing 10 known terminals during migration — should the migration script run automatically on first server start with the new code, or be invoked manually? Probably manual, with a one-liner in the upgrade docs.

              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

                  DanCode redesign: drop tmux, two-process backend with disk-persisted scrollback and layouts #2

                  Description

                  @DanielGGordon

                  Problem

                  DanCode currently uses tmux as the backend for persistent shell sessions, and that abstraction is the source of most of the day-to-day pain. The terminfo translation layer (screen-256color) causes formatting glitches; tmux's own scrollback buffer fights with xterm.js's scrollback causing scroll issues; the reconcile-on-startup logic races against tmux-resurrect and silently drops sessions; and the dependency on tmux + tmux-resurrect + tmux-continuum makes the system fragile and hard to reason about. tmux is also the wrong abstraction for the actual use case — what matters is files, Claude Code conversations, and a clean per-terminal experience, none of which need a multiplexer.

                  Solution

                  Replace the tmux backend with a two-process node-pty architecture: a long-lived dancode-shellhost daemon owns the PTYs and persists scrollback to disk, while the existing dancode-server handles HTTP, WebSockets, files, and projects. The two communicate over a UNIX socket. This makes ordinary server restarts (npm run dev reloads, crashes) fully invisible to the user, because the shellhost keeps running. Pi reboots still kill PTYs (unavoidable), but on the next open we re-spawn shells at the same cwd, replay the on-disk scrollback for context, and resume the matching Claude Code conversation by session id. A CodeMirror-based editor replaces the current text view. Per-project layout (which terminals existed, which files were open, splits) is serialized to disk and restored on project open.

                  Requirements

                  Core features

                  • A separate dancode-shellhost process owns all PTYs; the main DanCode server connects to it over a UNIX socket and proxies I/O to/from browsers.
                  • Shellhost runs as a systemd --user service with auto-restart, independent of the DanCode web server.
                  • Restarting the DanCode web server (e.g. npm run dev reload, crash) MUST NOT kill running shells. The web server reconnects to existing PTYs on startup.
                  • Restarting the shellhost (only) MUST recover all currently-known terminals by reading their persisted metadata.
                  • After a Pi reboot, opening a project re-spawns each known terminal at its saved cwd, with its saved startup command, and the prior scrollback visible.
                  • A "background" mode is available per-terminal that wraps the running command in systemd-run --user --scope so it truly survives a daemon restart (opt-in, for long jobs like builds/training).
                  • All current functionality preserved: project list, create/rename/delete project, file browser, view/edit files, split panes, tabs within a project, mobile UI, auth (bcrypt + TOTP + 30-day session).

                  Terminal behavior

                  • Each terminal is a node-pty process spawned with TERM=xterm-256color (no multiplexer in between).
                  • Per-terminal scrollback log appended to ~/.dancode/terminals/<id>/scrollback.log as the PTY produces output. File is rotated at 1MB; only the most recent rotation is kept (so disk usage per terminal is bounded ~2MB worst case during rotation).
                  • On socket reconnect (browser refresh, network blip), the last N KB of scrollback log is replayed to the client before live output resumes.
                  • After PTY death + respawn (e.g. post-reboot), the scrollback log from the prior session is replayed as a banner ("--- prior session ended at ---") and then live output starts.
                  • Resize events propagate to the PTY immediately.
                  • Paste of plain text fires exactly once. Paste of image data triggers upload to project (current image-paste behavior preserved).
                  • No bracketed-paste double-emit, no duplicated input, no terminfo translation glitches.

                  Project & layout persistence

                  • Each project stores a layout.json at ~/.dancode/projects/<slug>/layout.json describing:
                    • Ordered list of terminals: id, cwd (relative to project root), startup command (if any), claude-session-id (if any), background-mode flag.
                    • Ordered list of open files: path (relative to project root), pane index, scroll position.
                    • Split/tab structure of the workspace.
                  • Layout is updated atomically on every meaningful change (terminal created/closed/moved, file opened/closed, split changed).
                  • Opening a project reads layout.json and reconstructs the workspace. Missing files are surfaced as a warning, not a crash.
                  • Renaming a project updates the slug AND moves the on-disk project directory (closes [[project_rename_bug]]).

                  Claude Code awareness

                  • DanCode detects when a terminal is running claude (by spawned-command introspection on the PTY's foreground process, or by scanning ~/.claude/projects/<project-slug>/ for an active session).
                  • When a Claude session is detected, the terminal's layout.json entry records the claude-session-id.
                  • On respawn after Pi reboot, the terminal's startup command is claude --resume <session-id> rather than a bare shell, so the prior conversation is restored.
                  • A "Resume last Claude session" UI affordance on the terminal pane offers manual resume.
                  • No native chat UI — Claude still renders inside the terminal.

                  File editor

                  • Replace the current view/edit with CodeMirror 6.
                  • Syntax highlighting for: JavaScript/TypeScript, JSX/TSX, Python, JSON, Markdown, YAML, Bash, HTML, CSS. (Extensible — add more languages via @codemirror/lang-*.)
                  • Standard editor affordances: find/replace, undo/redo, multi-cursor, line numbers.
                  • Save: explicit (Ctrl+S) and on-blur. Server validates path is inside the project root before writing.
                  • No LSP, no diagnostics, no completions (out of scope).

                  Migration from tmux

                  • A one-shot bin/dancode-migrate-from-tmux script that:
                    • For each existing dancode-* tmux session, captures the current pane contents via tmux capture-pane -p -S - into the new scrollback log format.
                    • Writes a meta.json per terminal with cwd extracted from tmux display -t <session> -p '#{pane_current_path}'.
                    • Builds an initial layout.json per project from the captured terminals.
                    • Kills the tmux sessions on success.
                    • Removes the legacy ~/.dancode/terminals/*.json files.
                  • Migration is idempotent and safe to run multiple times.
                  • After migration, the codebase removes all tmux-related code (tmuxHasSession, session-name builders, reconcile retry logic, etc.) and removes tmux from documented system dependencies.

                  Wire protocol (shellhost ↔ server)

                  • UNIX socket at ~/.dancode/shellhost.sock.
                  • Length-prefixed JSON or msgpack frames. (Decide during implementation.)
                  • Commands: spawn, attach, detach, write, resize, kill, list, inspect.
                  • Events from shellhost: output, exit, respawn-needed.
                  • All commands carry a request id; responses correlate. Disconnect from the server side does not kill PTYs — only an explicit kill does.

                  AI integration / self-modification

                  • DanCode itself remains AI-editable via Claude Code running in a DanCode terminal (eat-your-own-dogfood). No new in-app AI surface is needed beyond what's in scope for Claude awareness above.

                  UX / interface

                  • No visible UI changes for the steady-state user beyond:
                    • Fewer formatting bugs in terminals.
                    • Smooth scrolling that doesn't fight tmux.
                    • A small "Resume Claude" button on terminals where Claude was last running.
                    • Layout actually restores on project open.
                  • Existing mobile UI, shortcut bar, image paste, and drag-and-drop file upload preserved.

                  Data / persistence

                  • ~/.dancode/projects/<slug>/layout.json — per-project layout state.
                  • ~/.dancode/terminals/<id>/meta.json — per-terminal metadata (project slug, cwd, command, claude session id, background flag, last-active timestamp).
                  • ~/.dancode/terminals/<id>/scrollback.log — append-only output stream, rotated at 1MB.
                  • ~/.dancode/credentials.json — unchanged (bcrypt hash + TOTP secret).
                  • ~/.dancode/sessions.json — unchanged (server-side 30-day session tokens).
                  • ~/.dancode/shellhost.sock — runtime socket; not data.

                  Testing

                  • Vitest workspaces (server, client) — already in place.
                  • New unit tests for:
                    • Scrollback writer (append, rotate, replay correctness).
                    • Layout serializer (round-trip, atomic write, partial-corruption recovery).
                    • Shellhost wire-protocol framing.
                    • Shellhost: spawn, attach, detach, kill, output streaming.
                  • New integration tests for:
                    • Server-restart scenario: spawn terminal → kill+restart server process → attach → verify alive.
                    • Shellhost-restart scenario: spawn → kill shellhost → restart → meta survives → respawn.
                    • Migration script against a fixture set of tmux sessions (using a disposable tmux server in CI).
                  • Existing 230/232 server tests should pass at the same baseline (or better, with the reconcile race tests removed).
                  • Manual smoke test plan: paste single + double + image, large output scrollback, splits, tabs, project rename, Claude resume, simulated reboot via systemctl --user restart dancode-shellhost.

                  Technical decisions

                  • Stack: Node 20+, Express 5, Socket.IO, node-pty, CodeMirror 6, Vitest. Same as today minus tmux + tmux-resurrect + tmux-continuum. Add systemd --user unit file for the shellhost.
                  • Architecture: Two processes — dancode-server (web/HTTP/WS, file ops, auth, projects) and dancode-shellhost (PTY ownership, scrollback writers). UNIX-socket RPC between them. Browser only talks to dancode-server; never to shellhost directly.
                  • Data model:
                    • Project: slug, name, root path, created/updated timestamps.
                    • Terminal: id (uuid), project slug, cwd, startup command, claude-session-id, background flag, last-active.
                    • Layout: project slug, terminals (ordered), open files (ordered), splits/tabs structure.
                    • Session: token, user, created, expires (auth — unchanged).
                  • API surface (HTTP, unchanged shape, semantics shift):
                    • GET /api/projects, POST /api/projects, PATCH /api/projects/:slug, DELETE /api/projects/:slug.
                    • GET /api/projects/:slug/layout, PUT /api/projects/:slug/layout.
                    • GET /api/projects/:slug/files, GET /api/projects/:slug/files/*, PUT /api/projects/:slug/files/*.
                    • POST /api/projects/:slug/upload (image paste — unchanged).
                    • GET /api/terminals?projectSlug=, POST /api/terminals, DELETE /api/terminals/:id.
                    • WebSocket /terminal/:id proxies to shellhost.
                    • Auth endpoints unchanged.
                  • API surface (UNIX socket, new): command/response with request ids, see Wire protocol above.
                  • Testing: Vitest unit + integration. Shellhost has its own test file. Migration script tested against fixture tmux sessions.
                  • Process model in dev: npm run dev starts dancode-server and dancode-shellhost concurrently (replacing the current concurrently config). On the Pi, shellhost lives in systemd --user; web server lives in either dev or systemd.

                  Out of scope

                  • LSP, IntelliSense, completions, diagnostics.
                  • Native Claude Code chat UI (Claude still runs in the terminal).
                  • Drag-to-rearrange layout, saved layout presets.
                  • Multi-user — DanCode remains single-user.
                  • True process survival across Pi power loss (impossible). Re-spawn-with-resume is the offered guarantee.
                  • Remote / cloud deployment. DanCode stays self-hosted on the Pi.
                  • Migration from anything other than the current tmux-based DanCode.

                  Open questions

                  • Wire-protocol encoding: length-prefixed JSON vs. msgpack vs. CBOR. Decide during planning based on size / Pi CPU profile.
                  • Scrollback log format: raw bytes vs. timestamped chunks. Decide during planning — timestamps cost ~10% disk but enable "scrollback at time X" UX later.
                  • Whether systemd-run --user --scope is sufficient for "background mode" or if we need a small supervisor inside shellhost.
                  • Handling of the existing 10 known terminals during migration — should the migration script run automatically on first server start with the new code, or be invoked manually? Probably manual, with a one-liner in the upgrade docs.

                  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

                      DanCode redesign: drop tmux, two-process backend with disk-persisted scrollback and layouts #2

                      Description

                      @DanielGGordon

                      Problem

                      DanCode currently uses tmux as the backend for persistent shell sessions, and that abstraction is the source of most of the day-to-day pain. The terminfo translation layer (screen-256color) causes formatting glitches; tmux's own scrollback buffer fights with xterm.js's scrollback causing scroll issues; the reconcile-on-startup logic races against tmux-resurrect and silently drops sessions; and the dependency on tmux + tmux-resurrect + tmux-continuum makes the system fragile and hard to reason about. tmux is also the wrong abstraction for the actual use case — what matters is files, Claude Code conversations, and a clean per-terminal experience, none of which need a multiplexer.

                      Solution

                      Replace the tmux backend with a two-process node-pty architecture: a long-lived dancode-shellhost daemon owns the PTYs and persists scrollback to disk, while the existing dancode-server handles HTTP, WebSockets, files, and projects. The two communicate over a UNIX socket. This makes ordinary server restarts (npm run dev reloads, crashes) fully invisible to the user, because the shellhost keeps running. Pi reboots still kill PTYs (unavoidable), but on the next open we re-spawn shells at the same cwd, replay the on-disk scrollback for context, and resume the matching Claude Code conversation by session id. A CodeMirror-based editor replaces the current text view. Per-project layout (which terminals existed, which files were open, splits) is serialized to disk and restored on project open.

                      Requirements

                      Core features

                      • A separate dancode-shellhost process owns all PTYs; the main DanCode server connects to it over a UNIX socket and proxies I/O to/from browsers.
                      • Shellhost runs as a systemd --user service with auto-restart, independent of the DanCode web server.
                      • Restarting the DanCode web server (e.g. npm run dev reload, crash) MUST NOT kill running shells. The web server reconnects to existing PTYs on startup.
                      • Restarting the shellhost (only) MUST recover all currently-known terminals by reading their persisted metadata.
                      • After a Pi reboot, opening a project re-spawns each known terminal at its saved cwd, with its saved startup command, and the prior scrollback visible.
                      • A "background" mode is available per-terminal that wraps the running command in systemd-run --user --scope so it truly survives a daemon restart (opt-in, for long jobs like builds/training).
                      • All current functionality preserved: project list, create/rename/delete project, file browser, view/edit files, split panes, tabs within a project, mobile UI, auth (bcrypt + TOTP + 30-day session).

                      Terminal behavior

                      • Each terminal is a node-pty process spawned with TERM=xterm-256color (no multiplexer in between).
                      • Per-terminal scrollback log appended to ~/.dancode/terminals/<id>/scrollback.log as the PTY produces output. File is rotated at 1MB; only the most recent rotation is kept (so disk usage per terminal is bounded ~2MB worst case during rotation).
                      • On socket reconnect (browser refresh, network blip), the last N KB of scrollback log is replayed to the client before live output resumes.
                      • After PTY death + respawn (e.g. post-reboot), the scrollback log from the prior session is replayed as a banner ("--- prior session ended at ---") and then live output starts.
                      • Resize events propagate to the PTY immediately.
                      • Paste of plain text fires exactly once. Paste of image data triggers upload to project (current image-paste behavior preserved).
                      • No bracketed-paste double-emit, no duplicated input, no terminfo translation glitches.

                      Project & layout persistence

                      • Each project stores a layout.json at ~/.dancode/projects/<slug>/layout.json describing:
                        • Ordered list of terminals: id, cwd (relative to project root), startup command (if any), claude-session-id (if any), background-mode flag.
                        • Ordered list of open files: path (relative to project root), pane index, scroll position.
                        • Split/tab structure of the workspace.
                      • Layout is updated atomically on every meaningful change (terminal created/closed/moved, file opened/closed, split changed).
                      • Opening a project reads layout.json and reconstructs the workspace. Missing files are surfaced as a warning, not a crash.
                      • Renaming a project updates the slug AND moves the on-disk project directory (closes [[project_rename_bug]]).

                      Claude Code awareness

                      • DanCode detects when a terminal is running claude (by spawned-command introspection on the PTY's foreground process, or by scanning ~/.claude/projects/<project-slug>/ for an active session).
                      • When a Claude session is detected, the terminal's layout.json entry records the claude-session-id.
                      • On respawn after Pi reboot, the terminal's startup command is claude --resume <session-id> rather than a bare shell, so the prior conversation is restored.
                      • A "Resume last Claude session" UI affordance on the terminal pane offers manual resume.
                      • No native chat UI — Claude still renders inside the terminal.

                      File editor

                      • Replace the current view/edit with CodeMirror 6.
                      • Syntax highlighting for: JavaScript/TypeScript, JSX/TSX, Python, JSON, Markdown, YAML, Bash, HTML, CSS. (Extensible — add more languages via @codemirror/lang-*.)
                      • Standard editor affordances: find/replace, undo/redo, multi-cursor, line numbers.
                      • Save: explicit (Ctrl+S) and on-blur. Server validates path is inside the project root before writing.
                      • No LSP, no diagnostics, no completions (out of scope).

                      Migration from tmux

                      • A one-shot bin/dancode-migrate-from-tmux script that:
                        • For each existing dancode-* tmux session, captures the current pane contents via tmux capture-pane -p -S - into the new scrollback log format.
                        • Writes a meta.json per terminal with cwd extracted from tmux display -t <session> -p '#{pane_current_path}'.
                        • Builds an initial layout.json per project from the captured terminals.
                        • Kills the tmux sessions on success.
                        • Removes the legacy ~/.dancode/terminals/*.json files.
                      • Migration is idempotent and safe to run multiple times.
                      • After migration, the codebase removes all tmux-related code (tmuxHasSession, session-name builders, reconcile retry logic, etc.) and removes tmux from documented system dependencies.

                      Wire protocol (shellhost ↔ server)

                      • UNIX socket at ~/.dancode/shellhost.sock.
                      • Length-prefixed JSON or msgpack frames. (Decide during implementation.)
                      • Commands: spawn, attach, detach, write, resize, kill, list, inspect.
                      • Events from shellhost: output, exit, respawn-needed.
                      • All commands carry a request id; responses correlate. Disconnect from the server side does not kill PTYs — only an explicit kill does.

                      AI integration / self-modification

                      • DanCode itself remains AI-editable via Claude Code running in a DanCode terminal (eat-your-own-dogfood). No new in-app AI surface is needed beyond what's in scope for Claude awareness above.

                      UX / interface

                      • No visible UI changes for the steady-state user beyond:
                        • Fewer formatting bugs in terminals.
                        • Smooth scrolling that doesn't fight tmux.
                        • A small "Resume Claude" button on terminals where Claude was last running.
                        • Layout actually restores on project open.
                      • Existing mobile UI, shortcut bar, image paste, and drag-and-drop file upload preserved.

                      Data / persistence

                      • ~/.dancode/projects/<slug>/layout.json — per-project layout state.
                      • ~/.dancode/terminals/<id>/meta.json — per-terminal metadata (project slug, cwd, command, claude session id, background flag, last-active timestamp).
                      • ~/.dancode/terminals/<id>/scrollback.log — append-only output stream, rotated at 1MB.
                      • ~/.dancode/credentials.json — unchanged (bcrypt hash + TOTP secret).
                      • ~/.dancode/sessions.json — unchanged (server-side 30-day session tokens).
                      • ~/.dancode/shellhost.sock — runtime socket; not data.

                      Testing

                      • Vitest workspaces (server, client) — already in place.
                      • New unit tests for:
                        • Scrollback writer (append, rotate, replay correctness).
                        • Layout serializer (round-trip, atomic write, partial-corruption recovery).
                        • Shellhost wire-protocol framing.
                        • Shellhost: spawn, attach, detach, kill, output streaming.
                      • New integration tests for:
                        • Server-restart scenario: spawn terminal → kill+restart server process → attach → verify alive.
                        • Shellhost-restart scenario: spawn → kill shellhost → restart → meta survives → respawn.
                        • Migration script against a fixture set of tmux sessions (using a disposable tmux server in CI).
                      • Existing 230/232 server tests should pass at the same baseline (or better, with the reconcile race tests removed).
                      • Manual smoke test plan: paste single + double + image, large output scrollback, splits, tabs, project rename, Claude resume, simulated reboot via systemctl --user restart dancode-shellhost.

                      Technical decisions

                      • Stack: Node 20+, Express 5, Socket.IO, node-pty, CodeMirror 6, Vitest. Same as today minus tmux + tmux-resurrect + tmux-continuum. Add systemd --user unit file for the shellhost.
                      • Architecture: Two processes — dancode-server (web/HTTP/WS, file ops, auth, projects) and dancode-shellhost (PTY ownership, scrollback writers). UNIX-socket RPC between them. Browser only talks to dancode-server; never to shellhost directly.
                      • Data model:
                        • Project: slug, name, root path, created/updated timestamps.
                        • Terminal: id (uuid), project slug, cwd, startup command, claude-session-id, background flag, last-active.
                        • Layout: project slug, terminals (ordered), open files (ordered), splits/tabs structure.
                        • Session: token, user, created, expires (auth — unchanged).
                      • API surface (HTTP, unchanged shape, semantics shift):
                        • GET /api/projects, POST /api/projects, PATCH /api/projects/:slug, DELETE /api/projects/:slug.
                        • GET /api/projects/:slug/layout, PUT /api/projects/:slug/layout.
                        • GET /api/projects/:slug/files, GET /api/projects/:slug/files/*, PUT /api/projects/:slug/files/*.
                        • POST /api/projects/:slug/upload (image paste — unchanged).
                        • GET /api/terminals?projectSlug=, POST /api/terminals, DELETE /api/terminals/:id.
                        • WebSocket /terminal/:id proxies to shellhost.
                        • Auth endpoints unchanged.
                      • API surface (UNIX socket, new): command/response with request ids, see Wire protocol above.
                      • Testing: Vitest unit + integration. Shellhost has its own test file. Migration script tested against fixture tmux sessions.
                      • Process model in dev: npm run dev starts dancode-server and dancode-shellhost concurrently (replacing the current concurrently config). On the Pi, shellhost lives in systemd --user; web server lives in either dev or systemd.

                      Out of scope

                      • LSP, IntelliSense, completions, diagnostics.
                      • Native Claude Code chat UI (Claude still runs in the terminal).
                      • Drag-to-rearrange layout, saved layout presets.
                      • Multi-user — DanCode remains single-user.
                      • True process survival across Pi power loss (impossible). Re-spawn-with-resume is the offered guarantee.
                      • Remote / cloud deployment. DanCode stays self-hosted on the Pi.
                      • Migration from anything other than the current tmux-based DanCode.

                      Open questions

                      • Wire-protocol encoding: length-prefixed JSON vs. msgpack vs. CBOR. Decide during planning based on size / Pi CPU profile.
                      • Scrollback log format: raw bytes vs. timestamped chunks. Decide during planning — timestamps cost ~10% disk but enable "scrollback at time X" UX later.
                      • Whether systemd-run --user --scope is sufficient for "background mode" or if we need a small supervisor inside shellhost.
                      • Handling of the existing 10 known terminals during migration — should the migration script run automatically on first server start with the new code, or be invoked manually? Probably manual, with a one-liner in the upgrade docs.

                      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

                          DanCode redesign: drop tmux, two-process backend with disk-persisted scrollback and layouts #2

                          Description

                          @DanielGGordon

                          Problem

                          DanCode currently uses tmux as the backend for persistent shell sessions, and that abstraction is the source of most of the day-to-day pain. The terminfo translation layer (screen-256color) causes formatting glitches; tmux's own scrollback buffer fights with xterm.js's scrollback causing scroll issues; the reconcile-on-startup logic races against tmux-resurrect and silently drops sessions; and the dependency on tmux + tmux-resurrect + tmux-continuum makes the system fragile and hard to reason about. tmux is also the wrong abstraction for the actual use case — what matters is files, Claude Code conversations, and a clean per-terminal experience, none of which need a multiplexer.

                          Solution

                          Replace the tmux backend with a two-process node-pty architecture: a long-lived dancode-shellhost daemon owns the PTYs and persists scrollback to disk, while the existing dancode-server handles HTTP, WebSockets, files, and projects. The two communicate over a UNIX socket. This makes ordinary server restarts (npm run dev reloads, crashes) fully invisible to the user, because the shellhost keeps running. Pi reboots still kill PTYs (unavoidable), but on the next open we re-spawn shells at the same cwd, replay the on-disk scrollback for context, and resume the matching Claude Code conversation by session id. A CodeMirror-based editor replaces the current text view. Per-project layout (which terminals existed, which files were open, splits) is serialized to disk and restored on project open.

                          Requirements

                          Core features

                          • A separate dancode-shellhost process owns all PTYs; the main DanCode server connects to it over a UNIX socket and proxies I/O to/from browsers.
                          • Shellhost runs as a systemd --user service with auto-restart, independent of the DanCode web server.
                          • Restarting the DanCode web server (e.g. npm run dev reload, crash) MUST NOT kill running shells. The web server reconnects to existing PTYs on startup.
                          • Restarting the shellhost (only) MUST recover all currently-known terminals by reading their persisted metadata.
                          • After a Pi reboot, opening a project re-spawns each known terminal at its saved cwd, with its saved startup command, and the prior scrollback visible.
                          • A "background" mode is available per-terminal that wraps the running command in systemd-run --user --scope so it truly survives a daemon restart (opt-in, for long jobs like builds/training).
                          • All current functionality preserved: project list, create/rename/delete project, file browser, view/edit files, split panes, tabs within a project, mobile UI, auth (bcrypt + TOTP + 30-day session).

                          Terminal behavior

                          • Each terminal is a node-pty process spawned with TERM=xterm-256color (no multiplexer in between).
                          • Per-terminal scrollback log appended to ~/.dancode/terminals/<id>/scrollback.log as the PTY produces output. File is rotated at 1MB; only the most recent rotation is kept (so disk usage per terminal is bounded ~2MB worst case during rotation).
                          • On socket reconnect (browser refresh, network blip), the last N KB of scrollback log is replayed to the client before live output resumes.
                          • After PTY death + respawn (e.g. post-reboot), the scrollback log from the prior session is replayed as a banner ("--- prior session ended at ---") and then live output starts.
                          • Resize events propagate to the PTY immediately.
                          • Paste of plain text fires exactly once. Paste of image data triggers upload to project (current image-paste behavior preserved).
                          • No bracketed-paste double-emit, no duplicated input, no terminfo translation glitches.

                          Project & layout persistence

                          • Each project stores a layout.json at ~/.dancode/projects/<slug>/layout.json describing:
                            • Ordered list of terminals: id, cwd (relative to project root), startup command (if any), claude-session-id (if any), background-mode flag.
                            • Ordered list of open files: path (relative to project root), pane index, scroll position.
                            • Split/tab structure of the workspace.
                          • Layout is updated atomically on every meaningful change (terminal created/closed/moved, file opened/closed, split changed).
                          • Opening a project reads layout.json and reconstructs the workspace. Missing files are surfaced as a warning, not a crash.
                          • Renaming a project updates the slug AND moves the on-disk project directory (closes [[project_rename_bug]]).

                          Claude Code awareness

                          • DanCode detects when a terminal is running claude (by spawned-command introspection on the PTY's foreground process, or by scanning ~/.claude/projects/<project-slug>/ for an active session).
                          • When a Claude session is detected, the terminal's layout.json entry records the claude-session-id.
                          • On respawn after Pi reboot, the terminal's startup command is claude --resume <session-id> rather than a bare shell, so the prior conversation is restored.
                          • A "Resume last Claude session" UI affordance on the terminal pane offers manual resume.
                          • No native chat UI — Claude still renders inside the terminal.

                          File editor

                          • Replace the current view/edit with CodeMirror 6.
                          • Syntax highlighting for: JavaScript/TypeScript, JSX/TSX, Python, JSON, Markdown, YAML, Bash, HTML, CSS. (Extensible — add more languages via @codemirror/lang-*.)
                          • Standard editor affordances: find/replace, undo/redo, multi-cursor, line numbers.
                          • Save: explicit (Ctrl+S) and on-blur. Server validates path is inside the project root before writing.
                          • No LSP, no diagnostics, no completions (out of scope).

                          Migration from tmux

                          • A one-shot bin/dancode-migrate-from-tmux script that:
                            • For each existing dancode-* tmux session, captures the current pane contents via tmux capture-pane -p -S - into the new scrollback log format.
                            • Writes a meta.json per terminal with cwd extracted from tmux display -t <session> -p '#{pane_current_path}'.
                            • Builds an initial layout.json per project from the captured terminals.
                            • Kills the tmux sessions on success.
                            • Removes the legacy ~/.dancode/terminals/*.json files.
                          • Migration is idempotent and safe to run multiple times.
                          • After migration, the codebase removes all tmux-related code (tmuxHasSession, session-name builders, reconcile retry logic, etc.) and removes tmux from documented system dependencies.

                          Wire protocol (shellhost ↔ server)

                          • UNIX socket at ~/.dancode/shellhost.sock.
                          • Length-prefixed JSON or msgpack frames. (Decide during implementation.)
                          • Commands: spawn, attach, detach, write, resize, kill, list, inspect.
                          • Events from shellhost: output, exit, respawn-needed.
                          • All commands carry a request id; responses correlate. Disconnect from the server side does not kill PTYs — only an explicit kill does.

                          AI integration / self-modification

                          • DanCode itself remains AI-editable via Claude Code running in a DanCode terminal (eat-your-own-dogfood). No new in-app AI surface is needed beyond what's in scope for Claude awareness above.

                          UX / interface

                          • No visible UI changes for the steady-state user beyond:
                            • Fewer formatting bugs in terminals.
                            • Smooth scrolling that doesn't fight tmux.
                            • A small "Resume Claude" button on terminals where Claude was last running.
                            • Layout actually restores on project open.
                          • Existing mobile UI, shortcut bar, image paste, and drag-and-drop file upload preserved.

                          Data / persistence

                          • ~/.dancode/projects/<slug>/layout.json — per-project layout state.
                          • ~/.dancode/terminals/<id>/meta.json — per-terminal metadata (project slug, cwd, command, claude session id, background flag, last-active timestamp).
                          • ~/.dancode/terminals/<id>/scrollback.log — append-only output stream, rotated at 1MB.
                          • ~/.dancode/credentials.json — unchanged (bcrypt hash + TOTP secret).
                          • ~/.dancode/sessions.json — unchanged (server-side 30-day session tokens).
                          • ~/.dancode/shellhost.sock — runtime socket; not data.

                          Testing

                          • Vitest workspaces (server, client) — already in place.
                          • New unit tests for:
                            • Scrollback writer (append, rotate, replay correctness).
                            • Layout serializer (round-trip, atomic write, partial-corruption recovery).
                            • Shellhost wire-protocol framing.
                            • Shellhost: spawn, attach, detach, kill, output streaming.
                          • New integration tests for:
                            • Server-restart scenario: spawn terminal → kill+restart server process → attach → verify alive.
                            • Shellhost-restart scenario: spawn → kill shellhost → restart → meta survives → respawn.
                            • Migration script against a fixture set of tmux sessions (using a disposable tmux server in CI).
                          • Existing 230/232 server tests should pass at the same baseline (or better, with the reconcile race tests removed).
                          • Manual smoke test plan: paste single + double + image, large output scrollback, splits, tabs, project rename, Claude resume, simulated reboot via systemctl --user restart dancode-shellhost.

                          Technical decisions

                          • Stack: Node 20+, Express 5, Socket.IO, node-pty, CodeMirror 6, Vitest. Same as today minus tmux + tmux-resurrect + tmux-continuum. Add systemd --user unit file for the shellhost.
                          • Architecture: Two processes — dancode-server (web/HTTP/WS, file ops, auth, projects) and dancode-shellhost (PTY ownership, scrollback writers). UNIX-socket RPC between them. Browser only talks to dancode-server; never to shellhost directly.
                          • Data model:
                            • Project: slug, name, root path, created/updated timestamps.
                            • Terminal: id (uuid), project slug, cwd, startup command, claude-session-id, background flag, last-active.
                            • Layout: project slug, terminals (ordered), open files (ordered), splits/tabs structure.
                            • Session: token, user, created, expires (auth — unchanged).
                          • API surface (HTTP, unchanged shape, semantics shift):
                            • GET /api/projects, POST /api/projects, PATCH /api/projects/:slug, DELETE /api/projects/:slug.
                            • GET /api/projects/:slug/layout, PUT /api/projects/:slug/layout.
                            • GET /api/projects/:slug/files, GET /api/projects/:slug/files/*, PUT /api/projects/:slug/files/*.
                            • POST /api/projects/:slug/upload (image paste — unchanged).
                            • GET /api/terminals?projectSlug=, POST /api/terminals, DELETE /api/terminals/:id.
                            • WebSocket /terminal/:id proxies to shellhost.
                            • Auth endpoints unchanged.
                          • API surface (UNIX socket, new): command/response with request ids, see Wire protocol above.
                          • Testing: Vitest unit + integration. Shellhost has its own test file. Migration script tested against fixture tmux sessions.
                          • Process model in dev: npm run dev starts dancode-server and dancode-shellhost concurrently (replacing the current concurrently config). On the Pi, shellhost lives in systemd --user; web server lives in either dev or systemd.

                          Out of scope

                          • LSP, IntelliSense, completions, diagnostics.
                          • Native Claude Code chat UI (Claude still runs in the terminal).
                          • Drag-to-rearrange layout, saved layout presets.
                          • Multi-user — DanCode remains single-user.
                          • True process survival across Pi power loss (impossible). Re-spawn-with-resume is the offered guarantee.
                          • Remote / cloud deployment. DanCode stays self-hosted on the Pi.
                          • Migration from anything other than the current tmux-based DanCode.

                          Open questions

                          • Wire-protocol encoding: length-prefixed JSON vs. msgpack vs. CBOR. Decide during planning based on size / Pi CPU profile.
                          • Scrollback log format: raw bytes vs. timestamped chunks. Decide during planning — timestamps cost ~10% disk but enable "scrollback at time X" UX later.
                          • Whether systemd-run --user --scope is sufficient for "background mode" or if we need a small supervisor inside shellhost.
                          • Handling of the existing 10 known terminals during migration — should the migration script run automatically on first server start with the new code, or be invoked manually? Probably manual, with a one-liner in the upgrade docs.

                          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

                              DanCode redesign: drop tmux, two-process backend with disk-persisted scrollback and layouts #2

                              Description

                              @DanielGGordon

                              Problem

                              DanCode currently uses tmux as the backend for persistent shell sessions, and that abstraction is the source of most of the day-to-day pain. The terminfo translation layer (screen-256color) causes formatting glitches; tmux's own scrollback buffer fights with xterm.js's scrollback causing scroll issues; the reconcile-on-startup logic races against tmux-resurrect and silently drops sessions; and the dependency on tmux + tmux-resurrect + tmux-continuum makes the system fragile and hard to reason about. tmux is also the wrong abstraction for the actual use case — what matters is files, Claude Code conversations, and a clean per-terminal experience, none of which need a multiplexer.

                              Solution

                              Replace the tmux backend with a two-process node-pty architecture: a long-lived dancode-shellhost daemon owns the PTYs and persists scrollback to disk, while the existing dancode-server handles HTTP, WebSockets, files, and projects. The two communicate over a UNIX socket. This makes ordinary server restarts (npm run dev reloads, crashes) fully invisible to the user, because the shellhost keeps running. Pi reboots still kill PTYs (unavoidable), but on the next open we re-spawn shells at the same cwd, replay the on-disk scrollback for context, and resume the matching Claude Code conversation by session id. A CodeMirror-based editor replaces the current text view. Per-project layout (which terminals existed, which files were open, splits) is serialized to disk and restored on project open.

                              Requirements

                              Core features

                              • A separate dancode-shellhost process owns all PTYs; the main DanCode server connects to it over a UNIX socket and proxies I/O to/from browsers.
                              • Shellhost runs as a systemd --user service with auto-restart, independent of the DanCode web server.
                              • Restarting the DanCode web server (e.g. npm run dev reload, crash) MUST NOT kill running shells. The web server reconnects to existing PTYs on startup.
                              • Restarting the shellhost (only) MUST recover all currently-known terminals by reading their persisted metadata.
                              • After a Pi reboot, opening a project re-spawns each known terminal at its saved cwd, with its saved startup command, and the prior scrollback visible.
                              • A "background" mode is available per-terminal that wraps the running command in systemd-run --user --scope so it truly survives a daemon restart (opt-in, for long jobs like builds/training).
                              • All current functionality preserved: project list, create/rename/delete project, file browser, view/edit files, split panes, tabs within a project, mobile UI, auth (bcrypt + TOTP + 30-day session).

                              Terminal behavior

                              • Each terminal is a node-pty process spawned with TERM=xterm-256color (no multiplexer in between).
                              • Per-terminal scrollback log appended to ~/.dancode/terminals/<id>/scrollback.log as the PTY produces output. File is rotated at 1MB; only the most recent rotation is kept (so disk usage per terminal is bounded ~2MB worst case during rotation).
                              • On socket reconnect (browser refresh, network blip), the last N KB of scrollback log is replayed to the client before live output resumes.
                              • After PTY death + respawn (e.g. post-reboot), the scrollback log from the prior session is replayed as a banner ("--- prior session ended at ---") and then live output starts.
                              • Resize events propagate to the PTY immediately.
                              • Paste of plain text fires exactly once. Paste of image data triggers upload to project (current image-paste behavior preserved).
                              • No bracketed-paste double-emit, no duplicated input, no terminfo translation glitches.

                              Project & layout persistence

                              • Each project stores a layout.json at ~/.dancode/projects/<slug>/layout.json describing:
                                • Ordered list of terminals: id, cwd (relative to project root), startup command (if any), claude-session-id (if any), background-mode flag.
                                • Ordered list of open files: path (relative to project root), pane index, scroll position.
                                • Split/tab structure of the workspace.
                              • Layout is updated atomically on every meaningful change (terminal created/closed/moved, file opened/closed, split changed).
                              • Opening a project reads layout.json and reconstructs the workspace. Missing files are surfaced as a warning, not a crash.
                              • Renaming a project updates the slug AND moves the on-disk project directory (closes [[project_rename_bug]]).

                              Claude Code awareness

                              • DanCode detects when a terminal is running claude (by spawned-command introspection on the PTY's foreground process, or by scanning ~/.claude/projects/<project-slug>/ for an active session).
                              • When a Claude session is detected, the terminal's layout.json entry records the claude-session-id.
                              • On respawn after Pi reboot, the terminal's startup command is claude --resume <session-id> rather than a bare shell, so the prior conversation is restored.
                              • A "Resume last Claude session" UI affordance on the terminal pane offers manual resume.
                              • No native chat UI — Claude still renders inside the terminal.

                              File editor

                              • Replace the current view/edit with CodeMirror 6.
                              • Syntax highlighting for: JavaScript/TypeScript, JSX/TSX, Python, JSON, Markdown, YAML, Bash, HTML, CSS. (Extensible — add more languages via @codemirror/lang-*.)
                              • Standard editor affordances: find/replace, undo/redo, multi-cursor, line numbers.
                              • Save: explicit (Ctrl+S) and on-blur. Server validates path is inside the project root before writing.
                              • No LSP, no diagnostics, no completions (out of scope).

                              Migration from tmux

                              • A one-shot bin/dancode-migrate-from-tmux script that:
                                • For each existing dancode-* tmux session, captures the current pane contents via tmux capture-pane -p -S - into the new scrollback log format.
                                • Writes a meta.json per terminal with cwd extracted from tmux display -t <session> -p '#{pane_current_path}'.
                                • Builds an initial layout.json per project from the captured terminals.
                                • Kills the tmux sessions on success.
                                • Removes the legacy ~/.dancode/terminals/*.json files.
                              • Migration is idempotent and safe to run multiple times.
                              • After migration, the codebase removes all tmux-related code (tmuxHasSession, session-name builders, reconcile retry logic, etc.) and removes tmux from documented system dependencies.

                              Wire protocol (shellhost ↔ server)

                              • UNIX socket at ~/.dancode/shellhost.sock.
                              • Length-prefixed JSON or msgpack frames. (Decide during implementation.)
                              • Commands: spawn, attach, detach, write, resize, kill, list, inspect.
                              • Events from shellhost: output, exit, respawn-needed.
                              • All commands carry a request id; responses correlate. Disconnect from the server side does not kill PTYs — only an explicit kill does.

                              AI integration / self-modification

                              • DanCode itself remains AI-editable via Claude Code running in a DanCode terminal (eat-your-own-dogfood). No new in-app AI surface is needed beyond what's in scope for Claude awareness above.

                              UX / interface

                              • No visible UI changes for the steady-state user beyond:
                                • Fewer formatting bugs in terminals.
                                • Smooth scrolling that doesn't fight tmux.
                                • A small "Resume Claude" button on terminals where Claude was last running.
                                • Layout actually restores on project open.
                              • Existing mobile UI, shortcut bar, image paste, and drag-and-drop file upload preserved.

                              Data / persistence

                              • ~/.dancode/projects/<slug>/layout.json — per-project layout state.
                              • ~/.dancode/terminals/<id>/meta.json — per-terminal metadata (project slug, cwd, command, claude session id, background flag, last-active timestamp).
                              • ~/.dancode/terminals/<id>/scrollback.log — append-only output stream, rotated at 1MB.
                              • ~/.dancode/credentials.json — unchanged (bcrypt hash + TOTP secret).
                              • ~/.dancode/sessions.json — unchanged (server-side 30-day session tokens).
                              • ~/.dancode/shellhost.sock — runtime socket; not data.

                              Testing

                              • Vitest workspaces (server, client) — already in place.
                              • New unit tests for:
                                • Scrollback writer (append, rotate, replay correctness).
                                • Layout serializer (round-trip, atomic write, partial-corruption recovery).
                                • Shellhost wire-protocol framing.
                                • Shellhost: spawn, attach, detach, kill, output streaming.
                              • New integration tests for:
                                • Server-restart scenario: spawn terminal → kill+restart server process → attach → verify alive.
                                • Shellhost-restart scenario: spawn → kill shellhost → restart → meta survives → respawn.
                                • Migration script against a fixture set of tmux sessions (using a disposable tmux server in CI).
                              • Existing 230/232 server tests should pass at the same baseline (or better, with the reconcile race tests removed).
                              • Manual smoke test plan: paste single + double + image, large output scrollback, splits, tabs, project rename, Claude resume, simulated reboot via systemctl --user restart dancode-shellhost.

                              Technical decisions

                              • Stack: Node 20+, Express 5, Socket.IO, node-pty, CodeMirror 6, Vitest. Same as today minus tmux + tmux-resurrect + tmux-continuum. Add systemd --user unit file for the shellhost.
                              • Architecture: Two processes — dancode-server (web/HTTP/WS, file ops, auth, projects) and dancode-shellhost (PTY ownership, scrollback writers). UNIX-socket RPC between them. Browser only talks to dancode-server; never to shellhost directly.
                              • Data model:
                                • Project: slug, name, root path, created/updated timestamps.
                                • Terminal: id (uuid), project slug, cwd, startup command, claude-session-id, background flag, last-active.
                                • Layout: project slug, terminals (ordered), open files (ordered), splits/tabs structure.
                                • Session: token, user, created, expires (auth — unchanged).
                              • API surface (HTTP, unchanged shape, semantics shift):
                                • GET /api/projects, POST /api/projects, PATCH /api/projects/:slug, DELETE /api/projects/:slug.
                                • GET /api/projects/:slug/layout, PUT /api/projects/:slug/layout.
                                • GET /api/projects/:slug/files, GET /api/projects/:slug/files/*, PUT /api/projects/:slug/files/*.
                                • POST /api/projects/:slug/upload (image paste — unchanged).
                                • GET /api/terminals?projectSlug=, POST /api/terminals, DELETE /api/terminals/:id.
                                • WebSocket /terminal/:id proxies to shellhost.
                                • Auth endpoints unchanged.
                              • API surface (UNIX socket, new): command/response with request ids, see Wire protocol above.
                              • Testing: Vitest unit + integration. Shellhost has its own test file. Migration script tested against fixture tmux sessions.
                              • Process model in dev: npm run dev starts dancode-server and dancode-shellhost concurrently (replacing the current concurrently config). On the Pi, shellhost lives in systemd --user; web server lives in either dev or systemd.

                              Out of scope

                              • LSP, IntelliSense, completions, diagnostics.
                              • Native Claude Code chat UI (Claude still runs in the terminal).
                              • Drag-to-rearrange layout, saved layout presets.
                              • Multi-user — DanCode remains single-user.
                              • True process survival across Pi power loss (impossible). Re-spawn-with-resume is the offered guarantee.
                              • Remote / cloud deployment. DanCode stays self-hosted on the Pi.
                              • Migration from anything other than the current tmux-based DanCode.

                              Open questions

                              • Wire-protocol encoding: length-prefixed JSON vs. msgpack vs. CBOR. Decide during planning based on size / Pi CPU profile.
                              • Scrollback log format: raw bytes vs. timestamped chunks. Decide during planning — timestamps cost ~10% disk but enable "scrollback at time X" UX later.
                              • Whether systemd-run --user --scope is sufficient for "background mode" or if we need a small supervisor inside shellhost.
                              • Handling of the existing 10 known terminals during migration — should the migration script run automatically on first server start with the new code, or be invoked manually? Probably manual, with a one-liner in the upgrade docs.

                              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