feat(v0.3): M3 themes/font settings + M7 session dashboard

- M3: settings.ts (⚙ panel: dark/light/solarized theme + font size A-/A+),
  persisted to localStorage; terminal-session.applyTheme; TabApp.applySettings
  applies to all + new tabs
- M7: dashboard.ts (▦ overlay listing every tab with connection dot + folder +
  Claude status; click a row to focus; live re-render while open);
  TabApp.snapshot()/focusTab()
- Browser-verified: light theme renders + persists; dashboard lists tabs and
  focuses on click. 208 tests green, typechecks + build ok.
This commit is contained in:
Yaojia Wang
2026-06-18 05:40:35 +02:00
parent dcb1bf4845
commit f79f14b89d
6 changed files with 384 additions and 0 deletions

View File

@@ -17,6 +17,7 @@
*/
import { TerminalSession } from './terminal-session.js'
import { THEMES, DEFAULT_SETTINGS, type Settings } from './settings.js'
const TABS_KEY = 'web-terminal:tabs'
const ACTIVE_KEY = 'web-terminal:active'
@@ -41,6 +42,7 @@ export class TabApp {
private editingIndex = -1
private dragIndex = -1
private notifyAsked = false
private settings: Settings = DEFAULT_SETTINGS
private readonly paneHost: HTMLElement
private readonly tabBar: HTMLElement
private readonly approvalBar: HTMLDivElement
@@ -181,6 +183,7 @@ export class TabApp {
})
this.paneHost.appendChild(entry.session.el)
this.tabs.push(entry)
entry.session.applyTheme(THEMES[this.settings.theme] ?? THEMES['dark']!, this.settings.fontSize)
entry.session.connect()
return entry
}
@@ -251,6 +254,36 @@ export class TabApp {
this.tabs[this.activeIndex]?.session.send(data)
}
/** Apply theme + font settings to every terminal (M3). */
applySettings(s: Settings): void {
this.settings = s
const theme = THEMES[s.theme] ?? THEMES['dark']!
for (const t of this.tabs) t.session.applyTheme(theme, s.fontSize)
}
/** A read-only view of all tabs for the dashboard (M7). */
snapshot(): Array<{
idx: number
title: string
conn: string
claude: string
active: boolean
pending: boolean
}> {
return this.tabs.map((t, idx) => ({
idx,
title: this.displayTitle(t, idx),
conn: t.session.status,
claude: t.session.claudeStatus,
active: idx === this.activeIndex,
pending: t.session.pendingApproval,
}))
}
focusTab(idx: number): void {
this.activate(idx)
}
/** Scrollback search in the active tab (M1). */
findInActive(query: string, dir: 'next' | 'prev'): void {
const s = this.tabs[this.activeIndex]?.session