Files
muxplex/muxplex/frontend/index.html
T
Ken 815c806da4
CI / test (3.13) (push) Failing after 12m40s
CI / test (3.12) (push) Failing after 12m42s
CI / test (3.11) (push) Failing after 12m44s
fix: stabilize frontend after Lit migration, fix new-session layout, graceful reconnect UX
**Frontend stabilization:**
- Added CSS display: contents rules for custom element wrappers (session-grid, session-tile,
  sidebar-item, view-dropdown) that were defaulting to display: inline and breaking grid/flex
  layouts
- Restored 20-line snapshot previews in session-tile and sidebar-item (were silently reduced
  to 12)
- Fixed view-dropdown New View button missing data-action attribute, breaking create-view flow
- Fixed sidebar dropdown close referencing non-existent #sidebar-view-dropdown-trigger element
- Fixed sidebar renderSidebar() clear+rebuild pattern that ran every 2s poll — now diffs
  existing elements to avoid layout jank and scroll loss
- Removed dead store.js import from index.html
- Removed dead document-level .tile-options-btn delegation handler (superseded by Lit
  custom event chain)
- Updated test for removed handler

**Bug fix: new session from sidebar covers entire screen:**
- Scoped zoom animation selector from unscoped [data-session] to .session-tile[data-session]
  within #session-grid
- Added guard to skip zoom when overview grid not visible (creating from sidebar already
  in fullscreen, no grid tile to zoom from)

**UX: graceful reconnect overlay:**
- Added 1.5s grace period before showing Reconnecting... overlay
- Brief disconnections (like service restarts) now invisible to user
- Timer properly cleaned up in onopen, closeTerminal, and self-clearing callback

**New documentation:**
- Created RECOVERY_PLAN.md documenting COE, state diagrams, storyboards, data model

Generated with Amplifier <https://github.com/microsoft/amplifier>
2026-05-27 18:14:07 +00:00

294 lines
20 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta name="theme-color" content="#0D1117" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<link rel="manifest" href="/manifest.json" />
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32.png" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
<link rel="stylesheet" href="/vendor/xterm.css" />
<link rel="stylesheet" href="/style.css" />
<title>muxplex</title>
</head>
<body>
<!-- ── Overview view ──────────────────────────────────────────────────── -->
<div id="view-overview" class="view view--active">
<header class="app-header">
<h1 class="app-wordmark"><img src="/wordmark-on-dark.svg" alt="muxplex" height="24" /></h1>
<view-dropdown id="view-dropdown" variant="header" active-view="all"></view-dropdown>
<div class="header-actions">
<button id="new-session-btn" class="header-btn" aria-label="New session"><svg viewBox="0 0 16 16" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2"><path d="M8 3v10M3 8h10"/></svg></button>
<button id="view-mode-btn" class="header-btn" aria-label="Toggle view mode" title="View: auto"><svg viewBox="0 0 16 16" width="14" height="14" fill="currentColor"><rect x="1" y="1" width="6" height="6" rx="1"/><rect x="9" y="1" width="6" height="6" rx="1"/><rect x="1" y="9" width="6" height="6" rx="1"/><rect x="9" y="9" width="6" height="6" rx="1"/></svg></button>
<button id="settings-btn" class="header-btn" aria-label="Settings"><svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="8" cy="8" r="2.5"/><path d="M8 1v2M8 13v2M1 8h2M13 8h2M2.9 2.9l1.4 1.4M11.7 11.7l1.4 1.4M2.9 13.1l1.4-1.4M11.7 4.3l1.4-1.4"/></svg></button>
<connection-status id="connection-status" level="ok"></connection-status>
</div>
</header>
<search-filter id="filter-bar"></search-filter>
<session-grid id="session-grid" class="session-grid" role="list"></session-grid>
<div id="empty-state" class="empty-state hidden">No active tmux sessions</div>
</div>
<!-- ── Expanded (terminal) view ──────────────────────────────────────── -->
<div id="view-expanded" class="view hidden">
<header class="expanded-header">
<button id="back-btn" class="back-btn" aria-label="Back"><svg viewBox="0 0 16 16" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M10 3L5 8l5 5"/></svg></button>
<button id="sidebar-toggle-btn" class="sidebar-toggle-btn" aria-label="Toggle session list"><svg viewBox="0 0 16 16" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M2 4h12M2 8h12M2 12h12"/></svg></button>
<span id="expanded-session-name" class="expanded-session-name"></span>
<button id="settings-btn-expanded" class="header-btn" aria-label="Settings"><svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="8" cy="8" r="2.5"/><path d="M8 1v2M8 13v2M1 8h2M13 8h2M2.9 2.9l1.4 1.4M11.7 11.7l1.4 1.4M2.9 13.1l1.4-1.4M11.7 4.3l1.4-1.4"/></svg></button>
</header>
<div class="view-body">
<div id="session-sidebar" class="session-sidebar">
<div class="sidebar-header">
<view-dropdown id="sidebar-view-dropdown" variant="sidebar" active-view="all"></view-dropdown>
<button id="sidebar-collapse-btn" class="sidebar-collapse-btn" aria-label="Collapse session list"><svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M10 3L5 8l5 5"/></svg></button>
</div>
<div id="sidebar-list" class="sidebar-list"></div>
<div class="sidebar-footer">
<button id="sidebar-new-session-btn" class="sidebar-new-btn">+ New</button>
</div>
</div>
<div class="terminal-wrapper">
<div id="terminal-search-bar" class="terminal-search-bar hidden">
<input id="terminal-search-input" type="text" class="terminal-search-input" placeholder="Find..." />
<span id="terminal-search-count" class="terminal-search-count"></span>
<button id="terminal-search-prev" class="terminal-search-btn" title="Previous (Shift+Enter)"><svg viewBox="0 0 16 16" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 10l4-4 4 4"/></svg></button>
<button id="terminal-search-next" class="terminal-search-btn" title="Next (Enter)"><svg viewBox="0 0 16 16" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 6l4 4 4-4"/></svg></button>
<button id="terminal-search-close" class="terminal-search-btn" title="Close (Escape)"><svg viewBox="0 0 16 16" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M4 4l8 8M12 4l-8 8"/></svg></button>
</div>
<div id="terminal-container" class="terminal-container"></div>
<div id="mobile-toolbar" class="mobile-toolbar hidden">
<div class="mobile-toolbar__scroll">
<button class="mobile-toolbar__key" data-key="Escape">Esc</button>
<button class="mobile-toolbar__key" data-key="Tab">Tab</button>
<button class="mobile-toolbar__key mobile-toolbar__key--modifier" data-modifier="ctrl">Ctrl</button>
<button class="mobile-toolbar__key mobile-toolbar__key--modifier" data-modifier="alt">Alt</button>
<button class="mobile-toolbar__key" data-key="ArrowUp"><svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2"><path d="M8 12V4M4 7l4-4 4 4"/></svg></button>
<button class="mobile-toolbar__key" data-key="ArrowDown"><svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2"><path d="M8 4v8M4 9l4 4 4-4"/></svg></button>
<button class="mobile-toolbar__key" data-key="ArrowLeft"><svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 8H4M7 4L3 8l4 4"/></svg></button>
<button class="mobile-toolbar__key" data-key="ArrowRight"><svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 8h8M9 4l4 4-4 4"/></svg></button>
<button class="mobile-toolbar__key" data-input="|">|</button>
<button class="mobile-toolbar__key" data-input="~">~</button>
<button class="mobile-toolbar__key" data-input="/">/</button>
<button class="mobile-toolbar__key" data-input="-">-</button>
<button class="mobile-toolbar__key" data-input="`">`</button>
</div>
</div>
</div>
</div>
<div id="reconnect-overlay" class="reconnect-overlay hidden" aria-live="polite">Reconnecting&hellip;</div>
</div>
<!-- ── Bottom sheet (session switcher) ────────────────────────────────── -->
<bottom-sheet-switcher id="bottom-sheet" class="bottom-sheet hidden" role="dialog" aria-modal="true" aria-label="Switch session"></bottom-sheet-switcher>
<!-- —— Manage View panel ———————————————————————————————————————————————————————————————————————————————————— -->
<div id="manage-view-panel" class="manage-view-panel hidden" role="dialog" aria-modal="true" aria-label="Manage view">
<div class="manage-view-panel__backdrop" id="manage-view-backdrop"></div>
<div class="manage-view-panel__content">
<div class="manage-view-panel__header">
<div class="manage-view-panel__name-row">
<h2 id="manage-view-name" class="manage-view-panel__name">View Name</h2>
</div>
<p id="manage-view-summary" class="manage-view-panel__summary"></p>
</div>
<div id="manage-view-list" class="manage-view-panel__list"></div>
<div class="manage-view-panel__footer">
<button id="manage-view-close" class="manage-view-panel__close-btn">Close</button>
</div>
</div>
</div>
<!-- ── Session pill (persistent overlay button) ────────────────────────── -->
<session-pill id="session-pill"></session-pill>
<!-- ── Mobile FAB (new session) ──────────────────────────────────────────── -->
<button id="new-session-fab" class="new-session-fab" aria-label="New session"><svg viewBox="0 0 16 16" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2"><path d="M8 3v10M3 8h10"/></svg></button>
<!-- ── Hover preview ──────────────────────────────────────────────── -->
<hover-preview id="hover-preview"></hover-preview>
<!-- ── Toast notification ──────────────────────────────────────────────── -->
<toast-notification id="toast"></toast-notification>
<!-- ── Settings ──────────────────────────────────────────────────────────── -->
<div id="settings-backdrop" class="settings-backdrop hidden"></div>
<dialog id="settings-dialog" class="settings-dialog">
<div class="settings-dialog-header">
<h2 class="settings-dialog-title">Settings</h2>
<button id="settings-close-btn" class="settings-close-btn" aria-label="Close settings"><svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M4 4l8 8M12 4l-8 8"/></svg></button>
</div>
<div class="settings-layout">
<nav class="settings-tabs">
<button class="settings-tab settings-tab--active" data-tab="display">Display</button>
<button class="settings-tab" data-tab="sessions">Sessions</button>
<button class="settings-tab" data-tab="views">Views</button>
<button class="settings-tab" data-tab="new-session">Commands</button>
<button class="settings-tab" data-tab="devices">Multi-Device</button>
</nav>
<div class="settings-content">
<div class="settings-panel" data-tab="display">
<div class="settings-field">
<label class="settings-label" for="setting-device-name">Device Name</label>
<input type="text" id="setting-device-name" class="settings-input" placeholder="This device" />
</div>
<div class="settings-field">
<label class="settings-label" for="setting-font-size">Font Size</label>
<select id="setting-font-size" class="settings-select">
<option value="11">11</option>
<option value="12">12</option>
<option value="13">13</option>
<option value="14" selected>14</option>
<option value="16">16</option>
<option value="18">18</option>
</select>
</div>
<div class="settings-field">
<label class="settings-label" for="setting-grid-columns">Grid Columns</label>
<select id="setting-grid-columns" class="settings-select">
<option value="auto" selected>Auto</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
</select>
</div>
<div class="settings-field">
<label class="settings-label" for="setting-hover-delay">Hover Delay</label>
<select id="setting-hover-delay" class="settings-select">
<option value="0">Off</option>
<option value="1000">1000ms</option>
<option value="1500" selected>1500ms</option>
<option value="2000">2000ms</option>
<option value="3000">3000ms</option>
</select>
</div>
<div class="settings-field">
<label class="settings-label" for="setting-show-hover-preview">Show hover preview</label>
<input type="checkbox" id="setting-show-hover-preview" class="settings-checkbox" checked />
</div>
<div class="settings-field">
<label class="settings-label" for="setting-show-device-badges">Show device badges</label>
<input type="checkbox" id="setting-show-device-badges" class="settings-checkbox" checked />
</div>
<div class="settings-field">
<label class="settings-label" for="setting-activity-indicator">Activity indicator</label>
<select id="setting-activity-indicator" class="settings-select">
<option value="both">Dot + Glow</option>
<option value="glow">Glow only</option>
<option value="dot">Dot only</option>
<option value="none">None</option>
</select>
</div>
</div>
<div class="settings-panel hidden" data-tab="sessions">
<div class="settings-field">
<label class="settings-label" for="setting-default-session">Default Session</label>
<select id="setting-default-session" class="settings-select">
<option value="">(none)</option>
</select>
</div>
<div class="settings-field">
<label class="settings-label" for="setting-sort-order">Sort Order</label>
<select id="setting-sort-order" class="settings-select">
<option value="manual">Manual</option>
<option value="alphabetical">Alphabetical</option>
<option value="recent">Recent</option>
</select>
</div>
<div class="settings-field">
<label class="settings-label" for="setting-window-size-largest">Auto-set window size (largest)</label>
<input type="checkbox" id="setting-window-size-largest" class="settings-checkbox" />
</div>
<div class="settings-field">
<label class="settings-label" for="setting-auto-open">Auto-open created sessions</label>
<input type="checkbox" id="setting-auto-open" class="settings-checkbox" checked />
</div>
<div class="settings-field">
<label class="settings-label" for="setting-bell-sound">Bell Sound</label>
<input type="checkbox" id="setting-bell-sound" class="settings-checkbox" />
</div>
<div class="settings-field">
<label class="settings-label">Desktop Notifications</label>
<div class="settings-notification-status">
<span id="notification-status-text" class="settings-status-text">Not requested</span>
<button id="notification-request-btn" class="settings-action-btn">Request Permission</button>
</div>
</div>
</div>
<div class="settings-panel hidden" data-tab="views">
<div id="views-settings-list" class="views-settings-list"></div>
<p class="settings-helper" id="views-settings-empty" style="display:none">No user-created views yet. Use the dropdown in the header to create one.</p>
</div>
<div class="settings-panel hidden" data-tab="new-session">
<div class="settings-field settings-field--column">
<label class="settings-label" for="setting-template">Command template</label>
<textarea id="setting-template" class="settings-textarea" rows="3" placeholder="tmux new-session -d -s {name}"></textarea>
<span class="settings-helper">{name} is replaced with the session name</span>
<button id="setting-template-reset" class="settings-action-btn">Reset to default</button>
</div>
<div class="settings-field settings-field--column">
<label class="settings-label" for="setting-delete-template">Kill session command</label>
<textarea id="setting-delete-template" class="settings-textarea" rows="3" placeholder="tmux kill-session -t {name}"></textarea>
<span class="settings-helper">{name} is replaced with the session name</span>
<button id="setting-delete-template-reset" class="settings-action-btn">Reset to default</button>
</div>
</div>
<div class="settings-panel hidden" data-tab="devices">
<div class="settings-field">
<label class="settings-label" for="setting-multi-device-enabled">Enable multi-device support</label>
<input type="checkbox" id="setting-multi-device-enabled" class="settings-checkbox" />
</div>
<div id="multi-device-fields">
<div class="settings-field">
<label class="settings-label" for="setting-view-mode">View Mode</label>
<select id="setting-view-mode" class="settings-select">
<option value="flat">Flat</option>
<option value="grouped">Grouped</option>
</select>
</div>
<div class="settings-field settings-field--column">
<label class="settings-label">Federation Key</label>
<div class="settings-federation-key">
<span id="federation-key-display" class="settings-key-display">&#8226;&#8226;&#8226;&#8226;&#8226;&#8226;&#8226;&#8226;</span>
<button id="federation-generate-btn" class="settings-action-btn">Generate Key</button>
</div>
<span class="settings-helper">Share this key with remote instances to allow federation access</span>
</div>
<div class="settings-field settings-field--column">
<label class="settings-label">Remote Instances</label>
<div id="setting-remote-instances" class="settings-remote-list"></div>
<button id="add-remote-instance-btn" class="settings-action-btn">+ Add Remote</button>
</div>
</div>
</div>
</div>
</div>
</dialog>
<!-- ── Scripts ──────────────────────────────────────────────────────────── -->
<script src="/vendor/xterm.js"></script>
<script src="/vendor/xterm-addon-fit.js"></script>
<script src="/vendor/xterm-addon-web-links.js"></script>
<script src="/vendor/xterm-addon-search.js"></script>
<script src="/vendor/addon-image.js"></script>
<script src="/app.js" defer></script>
<script src="/terminal.js" defer></script>
<script type="module">
import '/components/toast-notification.js';
import '/components/connection-status.js';
import '/components/session-pill.js';
import '/components/session-tile.js';
import '/components/sidebar-item.js';
import '/components/hover-preview.js';
import '/components/bottom-sheet-switcher.js';
import '/components/session-grid.js';
import '/components/view-dropdown.js';
import '/components/search-filter.js';
</script>
</body>
</html>