Files
Codeman/test/session-resize-arbitration.test.ts
T
arkonandClaude Opus 4.8 e77df131b8 fix(mobile): address review blockers on the touch-device change set
Review follow-ups on PR #111 (rebased onto master post-#112/#113):

Resize arbitration redesigned (review blocker 2): the previous
'cols < _ptyCols' guard froze a mobile-only session's PTY at the spawn
default — narrow phones rendered clipped and could never re-fit. The
guard now uses connection-scoped desktop sizing claims instead:
ws-routes registers a claim on a desktop-typed resize and releases it
on socket close (or when the same connection later reports a small
viewport), and Session.resize() ignores mobile/tablet resizes only
while at least one desktop connection holds a claim. A phone alone
fully controls its size (shrink, rows-only shrink, re-grow); a phone
glancing at a desktop-driven session can no longer reflow it.
mobile-handlers' keyboard open/close resize now declares its viewport
type so it participates in arbitration. Tests rewritten to cover
mobile-only shrink/rows-only/re-grow, claim/release lifecycle, multi-
claim behavior, and untyped legacy resizes; ws-routes test covers the
claim lifecycle over a real socket.

Solo/detached header restored (review blocker 3): index.html had
removed #soloSessionTitle and #soloRedockBtn, which _applySoloMode
still references — every detached window hit a null deref. Both are
back alongside the new mobile utility toggle.

Desktop leak fixed (review should-fix): .mobile-header-utility-toggle
had no rule outside the <=768px media queries, so the raw button
rendered on desktop. styles.css now hides it by default; the mobile/
tablet queries re-enable it.

Visual-regression baselines reverted to master (review should-fix):
the 18 contributor-machine PNGs are environment-specific (8 of the
behavioral tests already report environment-sensitive failures across
machines); re-baseline deliberately on the canonical machine instead.
The 24 behavioral keyboard/layout/tabs tests are kept as-is.

AGENTS.md trimmed to a pointer at CLAUDE.md (review should-fix) to
avoid drift between duplicated guidance.

Also dropped a dead getAttachmentHistoryForPersist stub (codex-branch
residue — no such method exists in src/).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 04:07:26 +02:00

113 lines
4.0 KiB
TypeScript

import { describe, expect, it, vi } from 'vitest';
import { Session } from '../src/session.js';
type ResizeableSessionInternals = {
ptyProcess: { resize: (cols: number, rows: number) => void };
_ptyCols: number;
_ptyRows: number;
};
function attachFakePty(session: Session, cols = 160, rows = 48) {
const resize = vi.fn();
const internals = session as unknown as ResizeableSessionInternals;
internals.ptyProcess = { resize };
internals._ptyCols = cols;
internals._ptyRows = rows;
return resize;
}
describe('Session resize arbitration', () => {
it('lets a mobile-only session shrink below the spawn default (no desktop connected)', () => {
const session = new Session({ workingDir: '/tmp', mode: 'shell' });
const resize = attachFakePty(session, 160, 48);
session.resize(48, 28, { viewportType: 'mobile' });
expect(resize).toHaveBeenCalledWith(48, 28);
});
it('lets a mobile-only session shrink rows only', () => {
const session = new Session({ workingDir: '/tmp', mode: 'shell' });
const resize = attachFakePty(session, 160, 48);
session.resize(160, 28, { viewportType: 'mobile' });
expect(resize).toHaveBeenCalledWith(160, 28);
});
it('lets a mobile-only session re-grow after shrinking', () => {
const session = new Session({ workingDir: '/tmp', mode: 'shell' });
const resize = attachFakePty(session, 160, 48);
session.resize(48, 28, { viewportType: 'mobile' });
session.resize(80, 36, { viewportType: 'tablet' });
expect(resize).toHaveBeenNthCalledWith(1, 48, 28);
expect(resize).toHaveBeenNthCalledWith(2, 80, 36);
});
it('ignores mobile resizes while a desktop connection holds a sizing claim', () => {
const session = new Session({ workingDir: '/tmp', mode: 'shell' });
const resize = attachFakePty(session, 160, 48);
const desktop = Symbol('desktop-conn');
session.claimDesktopSizing(desktop);
session.resize(48, 28, { viewportType: 'mobile' });
// Grow is ignored too — it would reflow the desktop view just the same.
session.resize(200, 60, { viewportType: 'tablet' });
expect(resize).not.toHaveBeenCalled();
});
it('always applies desktop resizes, claim or not', () => {
const session = new Session({ workingDir: '/tmp', mode: 'shell' });
const resize = attachFakePty(session, 160, 48);
session.claimDesktopSizing(Symbol('desktop-conn'));
session.resize(120, 40, { viewportType: 'desktop' });
expect(resize).toHaveBeenCalledWith(120, 40);
});
it('restores mobile control once the desktop claim is released', () => {
const session = new Session({ workingDir: '/tmp', mode: 'shell' });
const resize = attachFakePty(session, 160, 48);
const desktop = Symbol('desktop-conn');
session.claimDesktopSizing(desktop);
session.resize(48, 28, { viewportType: 'mobile' });
expect(resize).not.toHaveBeenCalled();
session.releaseDesktopSizing(desktop);
session.resize(48, 28, { viewportType: 'mobile' });
expect(resize).toHaveBeenCalledWith(48, 28);
});
it('keeps ignoring mobile resizes until every desktop claim is released', () => {
const session = new Session({ workingDir: '/tmp', mode: 'shell' });
const resize = attachFakePty(session, 160, 48);
const desktopA = Symbol('desktop-a');
const desktopB = Symbol('desktop-b');
session.claimDesktopSizing(desktopA);
session.claimDesktopSizing(desktopB);
session.releaseDesktopSizing(desktopA);
session.resize(48, 28, { viewportType: 'mobile' });
expect(resize).not.toHaveBeenCalled();
session.releaseDesktopSizing(desktopB);
session.resize(48, 28, { viewportType: 'mobile' });
expect(resize).toHaveBeenCalledWith(48, 28);
});
it('applies untyped (legacy/API) resizes regardless of claims', () => {
const session = new Session({ workingDir: '/tmp', mode: 'shell' });
const resize = attachFakePty(session, 160, 48);
session.claimDesktopSizing(Symbol('desktop-conn'));
session.resize(100, 30);
expect(resize).toHaveBeenCalledWith(100, 30);
});
});