feat(v0.2): multi-tab terminal + titles + Claude Code shortcut bar
Frontend feature (backend unchanged — manager was already multi-session): - Multi-tab: each tab = independent WS + Terminal + session (terminal-session.ts, tabs.ts); tab bar with +, ×, middle-click close; persisted to localStorage (migrates v0.1 single-session key) - Tab titles: auto from xterm onTitleChange (OSC 0/2), double-click to rename inline (manual wins over auto); ellipsis + tooltip - Shortcut key bar now shown on ALL devices (clickable buttons trigger shortcuts), reordered for Claude Code (Esc prominent, ⇧Tab, ↑↓, ⏎, ^C, Tab, ←→, /); responsive sizing via @media (pointer:coarse) for touch - Verified in browser: rename, two independent sessions, keybar on desktop + narrow viewport. 191 tests green; both typechecks + esbuild build pass.
This commit is contained in:
@@ -11,7 +11,7 @@
|
||||
*/
|
||||
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { KEY_MAP } from '../public/keybar.js'
|
||||
import { KEY_MAP, KEYBAR_BUTTONS } from '../public/keybar.js'
|
||||
|
||||
describe('keybar', () => {
|
||||
describe('KEY_MAP — pure byte mappings', () => {
|
||||
@@ -73,7 +73,12 @@ describe('keybar', () => {
|
||||
expect(KEY_MAP.tab.charCodeAt(0)).toBe(0x09)
|
||||
})
|
||||
|
||||
it('all 9 keys are defined', () => {
|
||||
it('slash maps to / (Claude Code slash-command launcher)', () => {
|
||||
expect(KEY_MAP.slash).toBe('/')
|
||||
expect(KEY_MAP.slash).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('all 10 keys are defined', () => {
|
||||
const keys = Object.keys(KEY_MAP)
|
||||
expect(keys).toEqual([
|
||||
'esc',
|
||||
@@ -85,13 +90,14 @@ describe('keybar', () => {
|
||||
'enter',
|
||||
'ctrlC',
|
||||
'tab',
|
||||
'slash',
|
||||
])
|
||||
})
|
||||
|
||||
it('all mapped bytes are unique strings', () => {
|
||||
const bytes = Object.values(KEY_MAP)
|
||||
expect(bytes).toHaveLength(9)
|
||||
expect(new Set(bytes).size).toBe(9) // all unique
|
||||
expect(bytes).toHaveLength(10)
|
||||
expect(new Set(bytes).size).toBe(10) // all unique
|
||||
})
|
||||
|
||||
it('ANSI arrow sequences use correct format', () => {
|
||||
@@ -103,4 +109,24 @@ describe('keybar', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('KEYBAR_BUTTONS — layout', () => {
|
||||
it('every button references a valid KEY_MAP key', () => {
|
||||
for (const b of KEYBAR_BUTTONS) {
|
||||
expect(KEY_MAP[b.keyName]).toBeDefined()
|
||||
}
|
||||
})
|
||||
|
||||
it('Esc is first and marked primary (most-used interrupt)', () => {
|
||||
expect(KEYBAR_BUTTONS[0]?.keyName).toBe('esc')
|
||||
expect(KEYBAR_BUTTONS[0]?.primary).toBe(true)
|
||||
})
|
||||
|
||||
it('exposes all Claude Code keys as buttons', () => {
|
||||
const keys = KEYBAR_BUTTONS.map((b) => b.keyName)
|
||||
expect(keys).toContain('shiftTab')
|
||||
expect(keys).toContain('slash')
|
||||
expect(keys).toContain('arrowUp')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user