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:
94
public/dashboard.ts
Normal file
94
public/dashboard.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* public/dashboard.ts — all-sessions overview (M7).
|
||||
*
|
||||
* A ▦ toolbar button opens an overlay listing every tab with its connection
|
||||
* dot, folder name, and Claude status; click a row to focus that tab. While
|
||||
* open it re-renders every second so statuses stay live.
|
||||
*/
|
||||
|
||||
export interface TabInfo {
|
||||
idx: number
|
||||
title: string
|
||||
conn: string // SessionStatus
|
||||
claude: string // ClaudeStatus
|
||||
active: boolean
|
||||
pending: boolean
|
||||
}
|
||||
|
||||
export interface DashboardHooks {
|
||||
snapshot: () => TabInfo[]
|
||||
focus: (idx: number) => void
|
||||
}
|
||||
|
||||
function claudeText(claude: string): string {
|
||||
if (claude === 'working') return '⚙ working'
|
||||
if (claude === 'waiting') return '⏳ needs approval'
|
||||
if (claude === 'idle') return '✓ idle'
|
||||
return ''
|
||||
}
|
||||
|
||||
export function mountDashboard(toolbar: HTMLElement, hooks: DashboardHooks): void {
|
||||
const overlay = document.createElement('div')
|
||||
overlay.id = 'dashboard'
|
||||
overlay.style.display = 'none'
|
||||
|
||||
const card = document.createElement('div')
|
||||
card.className = 'dash-card'
|
||||
overlay.appendChild(card)
|
||||
document.body.appendChild(overlay)
|
||||
|
||||
let timer: ReturnType<typeof setInterval> | null = null
|
||||
|
||||
const render = (): void => {
|
||||
card.replaceChildren()
|
||||
const title = document.createElement('div')
|
||||
title.className = 'dash-title'
|
||||
title.textContent = 'Sessions'
|
||||
card.appendChild(title)
|
||||
|
||||
for (const t of hooks.snapshot()) {
|
||||
const row = document.createElement('div')
|
||||
row.className = t.active ? 'dash-row active' : 'dash-row'
|
||||
|
||||
const dot = document.createElement('span')
|
||||
dot.className = `tab-dot dot-${t.conn}`
|
||||
const name = document.createElement('span')
|
||||
name.className = 'dash-name'
|
||||
name.textContent = t.title
|
||||
const claude = document.createElement('span')
|
||||
claude.className = t.pending ? 'dash-claude pending' : 'dash-claude'
|
||||
claude.textContent = claudeText(t.claude)
|
||||
|
||||
row.append(dot, name, claude)
|
||||
row.addEventListener('click', () => {
|
||||
hooks.focus(t.idx)
|
||||
hide()
|
||||
})
|
||||
card.appendChild(row)
|
||||
}
|
||||
}
|
||||
|
||||
const open = (): void => {
|
||||
render()
|
||||
overlay.style.display = 'flex'
|
||||
timer = setInterval(render, 1000)
|
||||
}
|
||||
const hide = (): void => {
|
||||
overlay.style.display = 'none'
|
||||
if (timer !== null) {
|
||||
clearInterval(timer)
|
||||
timer = null
|
||||
}
|
||||
}
|
||||
overlay.addEventListener('click', (e) => {
|
||||
if (e.target === overlay) hide()
|
||||
})
|
||||
|
||||
const btn = document.createElement('button')
|
||||
btn.className = 'toolbtn'
|
||||
btn.textContent = '▦'
|
||||
btn.title = 'All sessions'
|
||||
btn.setAttribute('aria-label', 'All sessions')
|
||||
btn.addEventListener('click', () => (overlay.style.display === 'none' ? open() : hide()))
|
||||
toolbar.appendChild(btn)
|
||||
}
|
||||
Reference in New Issue
Block a user