Skip to content

v0.2.0 — Layouts, broadcast, and a perf pass

Latest

Choose a tag to compare

@tdody tdody released this 07 Jun 00:56
· 17 commits to main since this release
d64d7df

Switchboard v0.2 — new layouts and filter primitives for the kanban, broadcast in the command palette, full-text search across pane scrollback, session templates, the bulk of an external security audit closed out, and a batch of perf work.

Highlights

New layouts & filter ergonomics

  • Grid view — every pane on screen at once, grouped by session.
  • List view — dense single-column rundown.
  • Layout suggestion chip — header hint nudges you toward the layout that fits your current pane count.
  • Pinned windows — pin button in the card foot promotes a card to the top of its column with an accent border and header badge, and keeps it visible even when the active filter would normally hide it. Persisted in localStorage by pane id.
  • Saved filter presets — chip-bar in the subhead saves the current (status, kind, query) filter under a name and lets you recall or delete it in one click. Persisted in localStorage.

Command palette: broadcast mode

  • Open the palette with multiple target panes and it switches to broadcast mode: amber pill in the header, one chip per target with an inline X to prune before sending, footer reads target: N panes. Submit iterates POST /api/send per remaining target; per-target errors are swallowed so one bad pane doesn't abort the rest.
  • NeedsStrip gains a Broadcast button (visible when there are ≥2 pending panes) that opens the palette pre-loaded with the pending list as targets.

Session templates

  • services/templates.py defines Template / TemplateWindow models with two built-ins (web-project, agent-grid) and loads user templates from ~/.switchboard/templates/*.json.
  • ${VAR} substitution across session, window name / cwd / cmd; the modal collects values per template, the backend substitutes at instantiation time, and unknown vars pass through unchanged.
  • GET /api/templates returns lightweight summaries; POST /api/templates/instantiate creates the session and starts each window's command.

Pane history search (⌘⇧F)

  • GET /api/search?q=<term>&lines=500 fans the per-pane capture + grep out via asyncio.gather(asyncio.to_thread(...)). Case-insensitive substring match on ANSI-stripped lines; returns pane id, session, window name + index, 1-based line number, and 3 lines of context per match. Capped at 50 matches per pane and 5,000 lines per pane; empty query returns 400.
  • New <SearchModal> opened via ⌘⇧F / Ctrl+Shift+F (also from inside inputs). Live search-as-you-type with a 200 ms debounce and an AbortController on every keystroke.

Cards that do more

  • Per-kind quick actions — agent cards expose y / n / Ctrl-C; shell cards expose clear.
  • Live activity timers — Xm ago spans tick at 1Hz via a dedicated AgoSpan leaf component.
  • Needs-strip dismiss persistence — dismissals stored as a per-pane id set in sessionStorage, so they survive reloads in the same tab.

Security audit

A full external audit landed; every finding is now closed:

  • 5 high-severity auth findings fixed.
  • 9 medium-severity findings fixed.
  • 6 low-severity findings closed out.

Performance

  • Backend
    • parse_pane output cached keyed by (lines-hash, cwd).
    • pane.capture_pane() cached under modal-open polling so the modal and the kanban poll can share the capture.
    • libtmux calls in the WS recv loop offloaded via asyncio.to_thread.
    • Branch cache TTL 2→5s, PR cache TTL 30→60s.
  • Frontend
    • /api/state response deduped so unchanged windows keep their object references.
    • WindowCard memo upgraded with prUrl + a content-aware preview check.
    • xterm stream rewriter gets a fast-path for chunks with no truecolor background.
    • OSC 4 theme packets precomputed at module load.
    • Terminal-modal fit() debounced via rAF instead of an 80 ms setTimeout.
    • Cached pane preview painted at modal mount; replaced on the first snapshot.
    • New useSetting(key) selector hook lets the terminal modal subscribe to a single settings key instead of the whole object.
    • Favicon and title effects merged into one.

Bug fixes

  • .rename-modal now has the same chrome as the other modals (panel background, border, radius, drop shadow, rise animation). It was previously transparent — invisible in dark mode by accident, and unreadable in light mode.
  • Removed the hardcoded #282c34 inline background on .term-body so the theme-aware CSS rule (added in the original theme work) wins. Fixes the light/high-contrast terminal modal where the body sandwiched the xterm viewport between light chrome and a dark padding band.
  • Size button now scales modal height in lockstep with width.
  • CI flake in test_open.py fixed by neutralizing the prewarm-scrape daemon in conftest.

Docs

  • Wordmark banner + install/python/license badges replace the centered logo + heading on the README.
  • New Contributing and Reporting a security issue sections; tightened Layout.

Requirements

Unchanged from v0.1.0:

  • Backend: Python ≥ 3.11, tmux, uv (FastAPI + pydantic-settings).
  • Frontend: Node ≥ 20 (Vite + React 19, xterm.js).
  • Optional: an editor on the IDE allowlist for path-click open, gh for PR / CI chip enrichment.

Upgrading from v0.1.0

git fetch --tags
git checkout v0.2.0
cd backend  && uv sync && cd ..
cd frontend && npm install && cd ..
./scripts/dev.sh

No config migrations. Pins and saved filter presets persist in localStorage and start empty; needs-strip dismissals live in sessionStorage and clear on tab close.

Full changelog: v0.1.0...v0.2.0