# Session Sidebar Implementation Plan > **Execution:** Use the subagent-driven-development workflow to implement this plan. **Goal:** Add a collapsible left-side sidebar to the terminal view showing a live session list with snapshot previews, usable as a session switcher without leaving the active terminal. **Architecture:** Pure frontend — no backend changes. Sidebar consumes session data already fetched by the poll loop. CSS-only animations (width transition on wide, transform slide on narrow). Responsive at 960px: side-by-side on wide screens, fixed overlay with click-away on narrow. **Tech stack:** Vanilla JS, CSS custom properties, Node test runner (`node:test`), no build step. **Design doc:** `muxplex/docs/plans/2026-03-27-session-sidebar-design.md` --- ## Conventions - **Working directory for git:** `/home/bkrabach/dev/web-tmux` - **Working directory for tests:** `/home/bkrabach/dev/web-tmux/muxplex` - **Test command:** `node --test frontend/tests/test_app.mjs` - **Test framework:** `node:test` + `node:assert/strict` (no Jest/Vitest) - **Test file:** `muxplex/frontend/tests/test_app.mjs` — all sidebar tests go here - **Import pattern:** CJS shim — `const app = require(join(__dirname, '..', 'app.js'))` - **DOM stubs:** injected onto `globalThis.document` before require - **App state naming:** `_camelCase` with leading underscore (`_viewingSession`, `_viewMode`) - **Helper `$`:** `document.getElementById` alias — use throughout app.js - **Helper `on`:** `element.addEventListener` alias — use for all event binding - **Exports:** app.js has a `module.exports` block at lines 887–935 — add all new public functions there - **Commits:** conventional format, atomic per task, run from `/home/bkrabach/dev/web-tmux` --- ## Task 1: HTML structure — sidebar markup, toggle button, view-body wrapper **Files:** - Modify: `muxplex/frontend/index.html` ### Step 1: Add sidebar toggle button to expanded-header In `muxplex/frontend/index.html`, find lines 31–35 (the `expanded-header`). Insert the sidebar toggle button after `#back-btn` and before `#expanded-session-name`: Replace: ```html
``` With: ```html
``` ### Step 2: Wrap terminal-container in view-body and add sidebar Replace line 36: ```html
``` With: ```html
``` **Important:** `#reconnect-overlay` (line 37) must remain OUTSIDE `.view-body` — it stays as a sibling of `.view-body` inside `#view-expanded`. ### Step 3: Verify the final structure The `#view-expanded` section should now look like: ```html ``` ### Step 4: Commit ```bash cd /home/bkrabach/dev/web-tmux git add muxplex/frontend/index.html git commit -m "feat: add sidebar markup, toggle button, and view-body wrapper to expanded view" ``` --- ## Task 2: CSS — view-body flex layout and --bg-surface variable **Files:** - Modify: `muxplex/frontend/style.css` ### Step 1: Add --bg-surface CSS variable In `muxplex/frontend/style.css`, find the `:root` block (line 3). Add `--bg-surface` after line 10 (`--bg-tile-hover`): Replace: ```css --bg-tile-hover: #1A1F2B; /* --color-bg-surface */ ``` With: ```css --bg-tile-hover: #1A1F2B; /* --color-bg-surface */ --bg-surface: #1A1F2B; /* sidebar hover & active card background */ ``` ### Step 2: Add view-body layout rules Find the `.terminal-container` rule (lines 367–372). Insert `.view-body` rules immediately BEFORE it: Insert before `.terminal-container {`: ```css /* View body: flex-row region below the expanded header */ .view-body { display: flex; flex-direction: row; flex: 1; min-height: 0; overflow: hidden; } ``` ### Step 3: Add flex properties to .terminal-container The existing `.terminal-container` rule (lines 367–372) already has `flex: 1` and `overflow: hidden`. Verify it contains: ```css .terminal-container { flex: 1; overflow: hidden; background: #000; padding: 0 4px; } ``` Add `min-width: 0;` to prevent flex overflow: ```css .terminal-container { flex: 1; min-width: 0; overflow: hidden; background: #000; padding: 0 4px; } ``` ### Step 4: Commit ```bash cd /home/bkrabach/dev/web-tmux git add muxplex/frontend/style.css git commit -m "feat: add view-body flex layout and --bg-surface variable" ``` --- ## Task 3: CSS — sidebar container and collapse animation **Files:** - Modify: `muxplex/frontend/style.css` ### Step 1: Add sidebar container styles In `muxplex/frontend/style.css`, insert the following block immediately after the `.view-body` rules added in Task 2 (before `.terminal-container`): ```css /* ============================================================ Session sidebar ============================================================ */ /* Sidebar container — wide screens (>=960px): side-by-side */ .session-sidebar { width: 200px; min-width: 200px; background: var(--bg-secondary); border-right: 1px solid var(--border-subtle); display: flex; flex-direction: column; overflow: hidden; flex-shrink: 0; transition: width 0.25s ease, min-width 0.25s ease; } .session-sidebar.sidebar--collapsed { width: 0; min-width: 0; } /* Sidebar header */ .sidebar-header { display: flex; align-items: center; justify-content: space-between; padding: 8px 12px; border-bottom: 1px solid var(--border-subtle); flex-shrink: 0; } .sidebar-title { font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-muted); } .sidebar-list { flex: 1; overflow-y: auto; overflow-x: hidden; } .sidebar-collapse-btn { background: none; border: none; color: var(--text-muted); cursor: pointer; font-size: 18px; padding: 2px 6px; line-height: 1; border-radius: 4px; } .sidebar-collapse-btn:hover { color: var(--text); } /* Sidebar toggle in expanded header */ .sidebar-toggle-btn { background: none; border: 1px solid var(--border); border-radius: 4px; color: var(--text-muted); font-size: 14px; width: 36px; height: 36px; cursor: pointer; display: flex; align-items: center; justify-content: center; margin-right: 8px; flex-shrink: 0; } .sidebar-toggle-btn:hover { border-color: var(--accent); color: var(--text); } ``` ### Step 2: Commit ```bash cd /home/bkrabach/dev/web-tmux git add muxplex/frontend/style.css git commit -m "feat: add sidebar container styles and collapse animation" ``` --- ## Task 4: CSS — sidebar session card styles **Files:** - Modify: `muxplex/frontend/style.css` ### Step 1: Add session card styles In `muxplex/frontend/style.css`, append the following immediately after the sidebar toggle button rules (after `.sidebar-toggle-btn:hover`), still inside the "Session sidebar" section: ```css /* Sidebar session cards */ .sidebar-item { height: 120px; background: var(--bg-secondary); border-bottom: 1px solid var(--border-subtle); cursor: pointer; overflow: hidden; display: flex; flex-direction: column; position: relative; } .sidebar-item:hover { background: var(--bg-surface); } .sidebar-item--active { background: var(--bg-surface); border-left: 3px solid var(--accent); } .sidebar-item-header { display: flex; align-items: center; justify-content: space-between; padding: 8px 8px 4px; flex-shrink: 0; height: 32px; gap: 4px; } .sidebar-item-name { font-size: 12px; font-weight: 600; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; flex: 1; min-width: 0; } .sidebar-item-body { flex: 1; position: relative; overflow: hidden; } .sidebar-item-body pre { position: absolute; bottom: 0; left: 0; right: 0; font-family: 'JetBrains Mono', 'Fira Code', monospace; font-size: 10px; line-height: 1.4; color: var(--text-muted); white-space: pre; overflow: hidden; margin: 0; padding: 0 8px 6px; } .sidebar-empty { padding: 16px 12px; color: var(--text-muted); font-size: 12px; text-align: center; } ``` ### Step 2: Commit ```bash cd /home/bkrabach/dev/web-tmux git add muxplex/frontend/style.css git commit -m "feat: add sidebar session card styles" ``` --- ## Task 5: CSS — responsive overlay at <960px **Files:** - Modify: `muxplex/frontend/style.css` ### Step 1: Add responsive media query In `muxplex/frontend/style.css`, add the following at the very end of the file (after the `.app-wordmark img` rule, which is the last rule currently at line 762): ```css /* ============================================================ Responsive: sidebar becomes overlay at <960px ============================================================ */ @media (max-width: 959px) { .session-sidebar { position: fixed; left: 0; top: 0; height: 100%; z-index: 200; width: 240px; min-width: 240px; transition: transform 0.25s ease; transform: translateX(0); box-shadow: 2px 0 16px rgba(0, 0, 0, 0.5); } .session-sidebar.sidebar--collapsed { width: 240px; min-width: 240px; transform: translateX(-100%); } /* Hide internal chevron in overlay mode — header button is the only toggle */ .sidebar-collapse-btn { display: none; } } ``` ### Step 2: Add sidebar to reduced-motion overrides Find the existing `@media (prefers-reduced-motion: reduce)` block (around line 726). Add the sidebar transition override inside it: After ` .toast { animation: none; }` and before the closing `}`, add: ```css .session-sidebar { transition: none; } ``` ### Step 3: Commit ```bash cd /home/bkrabach/dev/web-tmux git add muxplex/frontend/style.css git commit -m "feat: add responsive sidebar overlay at <960px and reduced-motion support" ``` --- ## Task 6: `buildSidebarHTML` — session card HTML builder (TDD) **Files:** - Modify: `muxplex/frontend/app.js` - Modify: `muxplex/frontend/tests/test_app.mjs` ### Step 1: Write the failing tests In `muxplex/frontend/tests/test_app.mjs`, add the following test group at the very end of the file (after the last `closeBottomSheet` test, around line 1648): ```js // ─── buildSidebarHTML ──────────────────────────────────────────────────── test('buildSidebarHTML marks active session with sidebar-item--active class', () => { const session = { name: 'work', snapshot: '', bell: null }; const html = app.buildSidebarHTML(session, 'work'); assert.ok(html.includes('sidebar-item--active'), 'active class missing'); }); test('buildSidebarHTML does NOT mark inactive session with sidebar-item--active', () => { const session = { name: 'work', snapshot: '', bell: null }; const html = app.buildSidebarHTML(session, 'other'); assert.ok(!html.includes('sidebar-item--active'), 'active class should not be present'); }); test('buildSidebarHTML renders bell badge when unseen_count > 0', () => { const session = { name: 'work', snapshot: '', bell: { unseen_count: 3 } }; const html = app.buildSidebarHTML(session, null); assert.ok(html.includes('tile-bell'), 'bell badge missing'); assert.ok(html.includes('>3<'), 'bell count not rendered'); }); test('buildSidebarHTML omits bell badge when unseen_count is 0', () => { const session = { name: 'work', snapshot: '', bell: { unseen_count: 0 } }; const html = app.buildSidebarHTML(session, null); assert.ok(!html.includes('tile-bell'), 'bell badge should be absent'); }); test('buildSidebarHTML HTML-escapes the session name', () => { const session = { name: '', snapshot: '', bell: null }; const html = app.buildSidebarHTML(session, null); assert.ok(!html.includes('