fix(terminal): address re-review findings on the pane-buffer rework

Follow-up to the PR #112 re-review (all six prior blockers were already
resolved; these are new issues the rework introduced):

- app.js: define the missing `_scheduleTerminalRepaint()` helper. It was
  called from both WebGL-fallback paths (onContextLoss + long-task trip)
  but defined nowhere, so each fallback threw `TypeError` and lost the
  post-fallback repaint, leaving a stale/blank terminal. Implemented as an
  rAF-debounced full refresh (matches the old inline `terminal.refresh`).
- app.js: clear terminal load-state on the two post-write stale-select
  early-returns (cached-buffer + rewrite branches), matching the other
  four checks. Switching away from a mid-loading tab no longer leaks a
  permanent `.tab-loading` spinner / `aria-busy=true`.
- terminal-ui.js + app.js: gate the post-resize TUI-redraw settle on an
  actual dimension change. `sendResize` now returns whether dims changed;
  a same-size tab switch sends no SIGWINCH, so the wait is skipped instead
  of charging a flat tax on every non-shell switch. Literal hoisted to
  `TUI_REDRAW_SETTLE_MS`.
- tmux-manager.ts: `resizeWindow()` uses a non-blocking `exec` instead of
  `execSync` so the interactive WS/HTTP resize path can't stall the
  Fastify event loop on a slow/hung tmux. Sole caller already fire-and-
  forgets the result; test updated to assert the async dispatch.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
arkon
2026-06-10 02:07:26 +02:00
co-authored by Claude Opus 4.8
parent 0569f68b86
commit b75181b725
5 changed files with 60 additions and 23 deletions
+6 -3
View File
@@ -9,7 +9,7 @@
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { TmuxManager, formatPaneSnapshot, parsePaneList, resolveActivePaneTarget } from '../src/tmux-manager.js';
import { execSync } from 'node:child_process';
import { execSync, exec } from 'node:child_process';
// ============================================================================
// Unit Tests (mocked)
@@ -64,6 +64,7 @@ vi.mock('node:fs/promises', async () => {
describe('TmuxManager (unit)', () => {
let manager: TmuxManager;
const mockedExecSync = vi.mocked(execSync);
const mockedExec = vi.mocked(exec);
beforeEach(() => {
vi.clearAllMocks();
@@ -131,9 +132,11 @@ describe('TmuxManager (unit)', () => {
it('resizes the tmux window when Codeman accepts a desktop resize', () => {
expect(manager.resizeWindow('codeman-abc12345', 140, 42)).toBe(true);
expect(mockedExecSync).toHaveBeenCalledWith(
// Non-blocking exec (not execSync) on the interactive resize hot path.
expect(mockedExec).toHaveBeenCalledWith(
"tmux -L 'codeman' resize-window -t 'codeman-abc12345' -x 140 -y 42",
expect.objectContaining({ stdio: 'ignore' })
expect.objectContaining({ timeout: expect.any(Number) }),
expect.any(Function)
);
});
});