feat(extension): Terminal as default sidebar tab

Adds a primary tab bar (Terminal | Chat) above the existing tab-content
panes. Terminal is the default-active tab; clicking Chat returns to the
existing claude -p one-shot flow which is preserved verbatim.

manifest.json: adds ws://127.0.0.1:*/ to host_permissions so MV3 doesn't
block the WebSocket upgrade.

sidepanel.html: new primary-tabs nav, new #tab-terminal pane with a
"Press any key to start Claude Code" bootstrap card, claude-not-found
install card, xterm mount point, and "session ended" restart UI. Loads
xterm.js + xterm-addon-fit + sidepanel-terminal.js. tab-chat is no
longer the .active default.

sidepanel.js: new activePrimaryPaneId() helper that reads which primary
tab is selected. Debug-close paths now route back to whichever primary
pane is active (was hardcoded to tab-chat). Primary-tab click handler
toggles .active classes and aria-selected. window.gstackServerPort and
window.gstackAuthToken exposed so sidepanel-terminal.js can build the
/pty-session POST and the WS URL.

sidepanel-terminal.js (new): xterm.js lifecycle. Lazy-spawn — first
keystroke fires POST /pty-session, then opens
ws://127.0.0.1:<terminalPort>/ws. Origin + cookie are set automatically
by the browser. Resize observer sends {type:"resize"} text frames.
ResizeObserver, tab-switch hooks, restart button, install-card retry.
On WS close shows "Session ended, click to restart" — no auto-reconnect
(codex outside-voice flagged that as session-burning).

sidepanel.css: primary-tabs bar + Terminal pane styling (full-height
xterm container, install card, ended state).
This commit is contained in:
Garry Tan
2026-04-25 12:34:12 -07:00
parent 07d4d36edf
commit 1923e1972f
5 changed files with 493 additions and 6 deletions
+35 -2
View File
@@ -3,6 +3,7 @@
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="sidepanel.css">
<link rel="stylesheet" href="lib/xterm.css">
</head>
<body>
<!-- Security shield — reflects ~/.gstack/security/session-state.json status.
@@ -59,8 +60,37 @@
<!-- Browser tab bar -->
<div class="browser-tabs" id="browser-tabs" style="display:none"></div>
<!-- Chat Tab (default, full height) -->
<main id="tab-chat" class="tab-content active">
<!-- Primary surface tabs: Terminal (default) | Chat. Activity / Refs /
Inspector still exist as a separate debug-tabs strip below. The
Terminal tab is default-active per /plan-eng-review Issue 1B
(subsequently informed by codex's spawn-waste finding: PTY only
spawns when the user types, so default-active is cheap). -->
<nav class="primary-tabs" id="primary-tabs" role="tablist">
<button class="primary-tab active" role="tab" data-pane="terminal" aria-selected="true">Terminal</button>
<button class="primary-tab" role="tab" data-pane="chat" aria-selected="false">Chat</button>
</nav>
<!-- Terminal Tab (default-active) -->
<main id="tab-terminal" class="tab-content active" role="tabpanel" aria-label="Terminal">
<div class="terminal-bootstrap" id="terminal-bootstrap">
<div class="terminal-bootstrap-icon"></div>
<p id="terminal-bootstrap-status">Press any key to start Claude Code.</p>
<p class="muted" id="terminal-bootstrap-hint">Real PTY. Real terminal. Real claude.</p>
</div>
<div class="terminal-install-card" id="terminal-install-card" style="display:none">
<p><strong>Claude Code not found</strong></p>
<p class="muted">Install: <a href="https://docs.anthropic.com/en/docs/claude-code" target="_blank">docs.anthropic.com/en/docs/claude-code</a></p>
<button class="install-retry-btn" id="terminal-install-retry">I installed it &mdash; try again</button>
</div>
<div class="terminal-mount" id="terminal-mount" style="display:none"></div>
<div class="terminal-ended" id="terminal-ended" style="display:none">
<p>Session ended.</p>
<button class="install-retry-btn" id="terminal-restart">Start a new session</button>
</div>
</main>
<!-- Chat Tab (the existing claude -p one-shot chat path; preserved verbatim) -->
<main id="tab-chat" class="tab-content" role="tabpanel" aria-label="Chat">
<div class="chat-messages" id="chat-messages">
<div class="chat-loading" id="chat-loading">
<div class="chat-loading-spinner"></div>
@@ -215,6 +245,9 @@
<button class="tab close-debug" id="close-debug" title="Close debug">&times;</button>
</nav>
<script src="lib/xterm.js"></script>
<script src="lib/xterm-addon-fit.js"></script>
<script src="sidepanel.js"></script>
<script src="sidepanel-terminal.js"></script>
</body>
</html>