Fix xterm clipping; move mic + Jump to Current into status bar #7

Merged
jknapp merged 2 commits from terminal-layout-statusbar into main 2026-06-29 15:23:35 +00:00
Owner

Summary

Terminal layout fixes for the xterm pane, plus relocating two floating overlay controls into the status bar.

Changes

  • Fix right/bottom clipping of the terminal grid. The padding was applied directly to the element term.open() mounts into, which xterm's FitAddon also measures — so the computed grid overhung the padding and the rightmost column / bottom row got clipped (visible as text sliced mid-character at the window edge). Padding now lives on a wrapper <div>; the xterm host fills that wrapper's content box with no padding of its own.
  • Move the STT mic into the status bar (far right). Previously one useSTT instance per TerminalView rendered a floating bottom-left button. Now a single useSTT bound to the active session lives in App, and the mic renders inline in StatusBar. Ctrl+Shift+M is routed through the store (sttToggle) so the shortcut still toggles the active session's mic.
  • Move "Jump to Current" into the status bar. The active TerminalView surfaces its scroll state (terminalAtBottom) and scroll action (scrollActiveToBottom) via the store; the status bar shows the control only when the active terminal is scrolled up.
  • Tighten terminal padding (8px 12px 48px 16px4px 8px 4px 8px) now that nothing floats over the terminal, so it claims as much area as possible.

Testing

  • npm run build (tsc + vite) — clean
  • npx vitest run — 33 passed (the 7 reported errors are pre-existing, from ProjectCard.test.tsx's listen mock, unrelated to this change)
  • npx tauri build — compiled and produced the .deb/.rpm bundles successfully

🤖 Generated with Claude Code

## Summary Terminal layout fixes for the xterm pane, plus relocating two floating overlay controls into the status bar. ### Changes - **Fix right/bottom clipping of the terminal grid.** The padding was applied directly to the element `term.open()` mounts into, which xterm's `FitAddon` also measures — so the computed grid overhung the padding and the rightmost column / bottom row got clipped (visible as text sliced mid-character at the window edge). Padding now lives on a wrapper `<div>`; the xterm host fills that wrapper's content box with no padding of its own. - **Move the STT mic into the status bar** (far right). Previously one `useSTT` instance per `TerminalView` rendered a floating bottom-left button. Now a single `useSTT` bound to the **active session** lives in `App`, and the mic renders inline in `StatusBar`. `Ctrl+Shift+M` is routed through the store (`sttToggle`) so the shortcut still toggles the active session's mic. - **Move "Jump to Current" into the status bar.** The active `TerminalView` surfaces its scroll state (`terminalAtBottom`) and scroll action (`scrollActiveToBottom`) via the store; the status bar shows the control only when the active terminal is scrolled up. - **Tighten terminal padding** (`8px 12px 48px 16px` → `4px 8px 4px 8px`) now that nothing floats over the terminal, so it claims as much area as possible. ### Testing - `npm run build` (tsc + vite) — clean - `npx vitest run` — 33 passed (the 7 reported errors are pre-existing, from `ProjectCard.test.tsx`'s `listen` mock, unrelated to this change) - `npx tauri build` — compiled and produced the `.deb`/`.rpm` bundles successfully 🤖 Generated with [Claude Code](https://claude.com/claude-code)
jknapp added 1 commit 2026-06-29 02:26:25 +00:00
Fix xterm clipping; move mic + Jump to Current into status bar
Build App / compute-version (pull_request) Successful in 3s
Build App / build-macos (pull_request) Successful in 2m19s
Build App / build-linux (pull_request) Successful in 5m3s
Build App / build-windows (pull_request) Successful in 7m34s
Build App / create-tag (pull_request) Has been skipped
Build App / sync-to-github (pull_request) Has been skipped
3d2d979197
Terminal layout fixes for the xterm pane:

- Stop the terminal grid from clipping its rightmost column / bottom
  row. The padding was on the element xterm mounts into, which the
  FitAddon measures; the grid overhang got clipped. Padding now lives on
  a wrapper and the xterm host fills it with no padding.
- Move the STT mic from a floating bottom-left overlay into the status
  bar (far right). A single useSTT instance bound to the active session
  now lives in App; Ctrl+Shift+M routes through the store.
- Move "Jump to Current" from a floating terminal overlay into the
  status bar. The active TerminalView surfaces its scroll state and
  scroll action via the store.
- Tighten terminal padding (was 8/12/48/16) now that nothing floats over
  it, so the terminal claims as much area as possible.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Author
Owner

Automated code review (Claude agent)

Verdict: Largely correct. The zustand-function-storage pattern is used correctly, hook deps are sound, and background terminals cannot clobber the active one. One genuine behavioral regression around mid-recording tab switches, plus minor items.

Findings

1. [Major] STT transcript goes to whatever tab is active at stop time, not where recording startedApp.tsx + hooks/useSTT.ts
A single useSTT(activeSessionId ?? "", sendInput) lives in App. The recording lifecycle does not reset when activeSessionId changes. Start recording in session A, switch to B, stop → stopRecording closes over the current sessionId (B) and injects the transcript into terminal B. Regression vs. the old per-terminal instance.
Fix: capture the target session at recording start (a recordingSessionIdRef set in startRecording, used by stopRecording instead of the live sessionId). Keeps a single mic while pinning the transcript to the originating terminal.

2. [Minor] Stale scrollActiveToBottom/terminalAtBottom after the active terminal unmountsTerminalView.tsx, store defaults
The registration effect only writes when active (good), but nothing clears the store on close. After closing the last session, scrollActiveToBottom still points at the disposed terminal's handler (termRef.current is never reset to null in cleanup). Masked in practice because StatusBar gates both controls behind activeSessionId. A reset in the unmount path would be more robust.

3. [Nit] Padding comment doesn't match the valueTerminalView.tsx
Comment says "right side leaves a little room beside the scrollbar," but padding is symmetric 4px 8px 4px 8px.

Confirmed correct

  • Zustand function storage: set({ sttToggle: fn }) passes an object → shallow-merged and stored verbatim, not misinterpreted as a state updater. Correct.
  • setSttToggle effect deps [stt.toggle, setSttToggle] are right; stt.toggle identity changes with STT state so the store always holds the latest. Read via getState() at call time — no stale closure, no render loop.
  • Multi-terminal isolation: inactive terminals early-return, so background output never writes scroll state. On tab switch the activating effect wins regardless of order. Correct.
  • Dangling references: useSTT/SttButton fully removed from TerminalView; no leftover imports.
  • xterm padding fix: host containerRef now has no padding; padding moved to wrapper. FitAddon + ResizeObserver measure the host, so the grid no longer overhangs. Correct.
  • No-active-session guards: StatusBar hides both controls when !activeSessionId; store defaults are safe no-ops.

🤖 Generated with Claude Code

## Automated code review (Claude agent) **Verdict:** Largely correct. The zustand-function-storage pattern is used correctly, hook deps are sound, and background terminals cannot clobber the active one. One genuine behavioral regression around mid-recording tab switches, plus minor items. ### Findings **1. [Major] STT transcript goes to whatever tab is active at *stop* time, not where recording started** — `App.tsx` + `hooks/useSTT.ts` A single `useSTT(activeSessionId ?? "", sendInput)` lives in `App`. The recording lifecycle does not reset when `activeSessionId` changes. Start recording in session A, switch to B, stop → `stopRecording` closes over the current `sessionId` (B) and injects the transcript into terminal B. Regression vs. the old per-terminal instance. _Fix:_ capture the target session at recording *start* (a `recordingSessionIdRef` set in `startRecording`, used by `stopRecording` instead of the live `sessionId`). Keeps a single mic while pinning the transcript to the originating terminal. **2. [Minor] Stale `scrollActiveToBottom`/`terminalAtBottom` after the active terminal unmounts** — `TerminalView.tsx`, store defaults The registration effect only writes when `active` (good), but nothing clears the store on close. After closing the last session, `scrollActiveToBottom` still points at the disposed terminal's handler (`termRef.current` is never reset to null in cleanup). Masked in practice because `StatusBar` gates both controls behind `activeSessionId`. A reset in the unmount path would be more robust. **3. [Nit] Padding comment doesn't match the value** — `TerminalView.tsx` Comment says "right side leaves a little room beside the scrollbar," but padding is symmetric `4px 8px 4px 8px`. ### Confirmed correct - **Zustand function storage:** `set({ sttToggle: fn })` passes an object → shallow-merged and stored verbatim, not misinterpreted as a state updater. Correct. - **`setSttToggle` effect deps** `[stt.toggle, setSttToggle]` are right; `stt.toggle` identity changes with STT state so the store always holds the latest. Read via `getState()` at call time — no stale closure, no render loop. - **Multi-terminal isolation:** inactive terminals early-return, so background output never writes scroll state. On tab switch the activating effect wins regardless of order. Correct. - **Dangling references:** `useSTT`/`SttButton` fully removed from `TerminalView`; no leftover imports. - **xterm padding fix:** host `containerRef` now has no padding; padding moved to wrapper. `FitAddon` + `ResizeObserver` measure the host, so the grid no longer overhangs. Correct. - **No-active-session guards:** `StatusBar` hides both controls when `!activeSessionId`; store defaults are safe no-ops. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
jknapp added 1 commit 2026-06-29 02:33:31 +00:00
Address review: pin STT transcript, clear stale scroll state
Build App / compute-version (pull_request) Successful in 3s
Build App / build-macos (pull_request) Successful in 2m15s
Build App / build-windows (pull_request) Successful in 3m49s
Build App / build-linux (pull_request) Successful in 4m48s
Build App / create-tag (pull_request) Has been skipped
Build App / sync-to-github (pull_request) Has been skipped
da7b7b9bd5
Follow-up to PR review on terminal-layout-statusbar:

- [Major] Pin STT transcripts to the originating terminal. The single
  useSTT instance is bound to the live active session, which can change
  mid-recording. Capture the session id at recording start in a ref and
  inject the transcript there instead of the live sessionId, so text
  always lands in the terminal where recording began.
- [Minor] Clear the status-bar scroll state when the active terminal
  unmounts, and null out termRef on dispose, so scrollActiveToBottom
  can't point at a disposed terminal. Tab switches don't unmount, so
  this only fires when the active session is actually closed.
- [Nit] Fix the terminal padding comment to match the symmetric value.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Author
Owner

Review follow-up (commit da7b7b9)

All three findings addressed:

  • [Major] STT transcript routingstartRecording now captures the active session id into recordingSessionIdRef; stopRecording injects the transcript via that ref instead of the live sessionId. Switching tabs mid-recording no longer misroutes the transcript — it lands in the terminal where recording began. (startRecording deps gained sessionId; stopRecording no longer depends on it.)
  • [Minor] Stale scroll statetermRef.current is now nulled on dispose, and an unmount effect clears terminalAtBottom/scrollActiveToBottom when the active terminal is closed (guarded by an activeRef, so tab switches — which don't unmount — are unaffected and the newly-active terminal's effect re-writes fresh values after).
  • [Nit] Padding comment — reworded to match the symmetric 4px 8px gutter.

Verified: tsc clean, vitest 33 passed, vite build clean.

## Review follow-up (commit da7b7b9) All three findings addressed: - **[Major] STT transcript routing** — `startRecording` now captures the active session id into `recordingSessionIdRef`; `stopRecording` injects the transcript via that ref instead of the live `sessionId`. Switching tabs mid-recording no longer misroutes the transcript — it lands in the terminal where recording began. (`startRecording` deps gained `sessionId`; `stopRecording` no longer depends on it.) - **[Minor] Stale scroll state** — `termRef.current` is now nulled on dispose, and an unmount effect clears `terminalAtBottom`/`scrollActiveToBottom` when the *active* terminal is closed (guarded by an `activeRef`, so tab switches — which don't unmount — are unaffected and the newly-active terminal's effect re-writes fresh values after). - **[Nit] Padding comment** — reworded to match the symmetric `4px 8px` gutter. Verified: `tsc` clean, `vitest` 33 passed, `vite build` clean.
jknapp merged commit 1716fb5c82 into main 2026-06-29 15:23:35 +00:00
jknapp deleted branch terminal-layout-statusbar 2026-06-29 15:23:38 +00:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: CyberCoveLLC/Triple-C#7