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

View File

@@ -675,6 +675,94 @@ body::after {
}
.tab-content.active { display: flex; flex-direction: column; }
/* ─── Primary surface tabs (Terminal | Chat) ──────────────────── */
.primary-tabs {
display: flex;
border-bottom: 1px solid var(--border);
background: #0f0f0f;
padding: 0 8px;
flex-shrink: 0;
}
.primary-tab {
background: transparent;
border: none;
color: #71717a;
padding: 8px 14px;
font-size: 12px;
font-family: 'JetBrains Mono', monospace;
cursor: pointer;
border-bottom: 2px solid transparent;
margin-bottom: -1px;
}
.primary-tab:hover { color: #e5e5e5; }
.primary-tab.active {
color: #e5e5e5;
border-bottom-color: #f59e0b;
}
/* ─── Terminal Tab ────────────────────────────────────────────── */
#tab-terminal {
background: #0a0a0a;
padding: 0;
}
.terminal-bootstrap {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
color: #71717a;
padding: 24px;
}
.terminal-bootstrap-icon {
font-size: 32px;
color: #f59e0b;
margin-bottom: 8px;
}
.terminal-bootstrap p { margin: 4px 0; }
.terminal-install-card {
margin: 24px;
padding: 16px;
border: 1px solid #27272a;
border-radius: 6px;
text-align: center;
}
.terminal-install-card a { color: #f59e0b; }
.install-retry-btn {
margin-top: 12px;
padding: 6px 14px;
background: #f59e0b;
color: #0a0a0a;
border: none;
border-radius: 4px;
font-family: inherit;
font-size: 12px;
cursor: pointer;
}
.install-retry-btn:hover { opacity: 0.9; }
.terminal-mount {
flex: 1;
width: 100%;
background: #0a0a0a;
padding: 8px;
box-sizing: border-box;
}
.terminal-mount .xterm,
.terminal-mount .xterm .xterm-viewport,
.terminal-mount .xterm .xterm-screen {
height: 100% !important;
}
.terminal-ended {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: #71717a;
padding: 24px;
}
/* ─── Activity Feed ───────────────────────────────────── */
#activity-feed { flex: 1; }