Commit graph

7 commits

Author SHA1 Message Date
1a035ad0a6 Phase 1: tabbed workspaces
Each tab is an independent tile tree; PTYs in non-active tabs keep
running (render-all-panes with visibility:hidden on inactive layers
so xterm.js's fit() still sees valid dimensions and the existing
per-pane resize dedupe absorbs no-op SIGWINCHes).

workspace.json shape goes from a bare TreeNode to
`{ version: 2, workspaces: [{ id, name, tree }] }` with a legacy v1
auto-wrap migration (the old single tree becomes one tab named
"Default").

App.tsx wraps the old single-tree state in workspace-aware state
but keeps `tree` / `setTree` / `activeLeafId` / `setActiveLeafId` as
identity-stable derived wrappers (reading currentWorkspaceId from a
ref), so the bulk of App.tsx stays unchanged.

XtermPane's initial term.focus() now checks `visibility !== "hidden"`
on the container so a pane mounting inside a hidden tab on app boot
doesn't yank focus away from the active tab. The focus poller is
scoped to the active workspace layer for the same reason.

Shortcuts: Ctrl+T new tab, Ctrl+Shift+T close current (window.confirm
when there are live panes), Ctrl+PageDown/PageUp navigate, Ctrl+1..9
switch to tab N. README + help overlay auto-generated from
shortcuts.ts.

79/79 vitest pass (7 new envelope-migration cases). tsc -b clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 18:43:32 +01:00
6068522ee3 Add per-leaf mcpAllow flag for MCP visibility gating (default-deny) 2026-05-25 21:22:15 +01:00
5085326cb1 Replace drag-promote gesture with Ctrl+Shift+P keyboard shortcut 2026-05-25 20:58:43 +01:00
150e5f09cb Promote nested pane to full row/column by dragging gutter past sibling 2026-05-25 20:24:47 +01:00
872fb0e80e Add SSH connections: saved hosts manager and hierarchical shell picker 2026-05-25 19:47:37 +01:00
aab36afce4 Per-pane and global terminal zoom via keyboard
Each leaf now carries an optional fontSizeOffset, persisted in
workspace.json alongside everything else. Ctrl+= / Ctrl+- / Ctrl+0
adjust the active pane; adding Shift escalates to every pane (the
mirror of the broadcast Shift+Alt convention, with shift alone since
the keys are otherwise unused). Bindings match on e.code so layouts
that don't have "=" / "-" / "0" in the same spot still work.

XtermPane gained a fontSize prop. A secondary effect reacts to changes:
set term.options.fontSize, fit() to recompute cols/rows for the new
cell size, refresh(), then resizePane so bash redraws the prompt at
the right width. No remount, so PTY + scrollback survive zoom changes.

The new tree helpers (resolveFontSize / adjustFontSize /
adjustAllFontSizes) are metadata-only — they don't swap leaf ids, so
nothing respawns. reshapeToPreset also carries the offset across when
splicing existing leaves into a new layout. 12 new vitest cases pin
those invariants plus the clamp and reset-to-default behaviour.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 22:48:35 +01:00
b1412287be Add vitest + 43 unit tests for tree.ts
Setup:
- vitest 2.x devDep; pnpm test / pnpm test:watch scripts.
- vite.config.ts test: block (node env, src/**/*.test.ts) via vitest/config.

Coverage in tree.test.ts:
- newLeaf / newSplit (defaults + provided props).
- replaceById (root/nested/no-match, immutability + sibling reuse).
- splitLeaf (orientation, inheritance, no-op on missing id, nested).
- closeLeaf (root -> null, sibling collapse, nested removal, no-op).
- findLeaf / leafCount / walkLeaves (order).
- changeDistro pins the invariant that it MUST swap the leaf id
  ({#key} remounts XtermPane → kills+respawns PTY).
- changeLabel / toggleBroadcast pin the inverse invariant: id MUST
  remain stable (metadata-only mutations).
- All 5 presets: shape, distro propagation, fresh ids per call.
- serialize/deserialize roundtrip + invalid-input rejection.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 13:28:02 +01:00