mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-09-30 12:39:42 +02:00
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>
This commit is contained in:
+27
-1
@@ -2052,17 +2052,43 @@ export class Session extends EventEmitter {
|
||||
private _ptyCols = 120;
|
||||
private _ptyRows = 40;
|
||||
|
||||
/**
|
||||
* Live WebSocket connections that have announced a desktop viewport for this
|
||||
* session. While at least one is registered, small-viewport (mobile/tablet)
|
||||
* resizes are ignored so a phone glancing at the session can't reflow the
|
||||
* PTY under an active desktop view. Claims are connection-scoped: ws-routes
|
||||
* registers them on a desktop-typed resize and releases them on socket
|
||||
* close, so a mobile-only session (no desktop connected) keeps full control
|
||||
* of its own size — including narrowing below the spawn default.
|
||||
*/
|
||||
private _desktopSizeClaims = new Set<symbol>();
|
||||
|
||||
/** Register a live desktop sizing claim (see _desktopSizeClaims). */
|
||||
claimDesktopSizing(token: symbol): void {
|
||||
this._desktopSizeClaims.add(token);
|
||||
}
|
||||
|
||||
/** Release a desktop sizing claim when its connection goes away. */
|
||||
releaseDesktopSizing(token: symbol): void {
|
||||
this._desktopSizeClaims.delete(token);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resizes the PTY terminal dimensions.
|
||||
* Skips the resize if dimensions haven't changed to avoid triggering
|
||||
* unnecessary Ink full-screen redraws (visible flicker on tab switch).
|
||||
*
|
||||
* Arbitration: while a desktop connection holds a sizing claim, resizes from
|
||||
* small viewports (mobile/tablet) are ignored entirely — shrink AND grow
|
||||
* would both reflow the desktop view. Without a desktop connected, small
|
||||
* viewports control the PTY size freely.
|
||||
*
|
||||
* @param cols - Number of columns (width in characters)
|
||||
* @param rows - Number of rows (height in lines)
|
||||
*/
|
||||
resize(cols: number, rows: number, options: { viewportType?: ResizeViewportType } = {}): void {
|
||||
const isSmallViewport = options.viewportType === 'mobile' || options.viewportType === 'tablet';
|
||||
if (isSmallViewport && cols < this._ptyCols) {
|
||||
if (isSmallViewport && this._desktopSizeClaims.size > 0) {
|
||||
return;
|
||||
}
|
||||
if (this.ptyProcess && (cols !== this._ptyCols || rows !== this._ptyRows)) {
|
||||
|
||||
@@ -77,7 +77,11 @@
|
||||
<div class="session-tabs" id="sessionTabs" role="tablist" aria-label="Session tabs">
|
||||
</div>
|
||||
|
||||
<!-- Detached single-session window title (shown only in solo mode) -->
|
||||
<div class="solo-session-title" id="soloSessionTitle" style="display: none;" aria-live="polite"></div>
|
||||
|
||||
<div class="header-right mobile-collapsed" id="headerRight">
|
||||
<button class="btn-icon-header btn-solo-redock" id="soloRedockBtn" style="display: none;" onclick="window.close()" title="Re-dock to dashboard (close window)" aria-label="Re-dock session to dashboard">⊞</button>
|
||||
<button class="tunnel-indicator" id="tunnelIndicator" style="display: none;" onclick="app.toggleTunnelPanel()" title="Cloudflare Tunnel" aria-label="Tunnel status">
|
||||
<span class="tunnel-dot"></span>
|
||||
</button>
|
||||
|
||||
@@ -434,10 +434,13 @@ const KeyboardHandler = {
|
||||
const cols = Math.max(dims.cols, 40);
|
||||
const rows = Math.max(dims.rows, 10);
|
||||
app._lastResizeDims = { cols, rows };
|
||||
// Declare the viewport type so resize arbitration can ignore this
|
||||
// while a desktop connection is sizing the same session.
|
||||
const viewportType = MobileDetection.getDeviceType ? MobileDetection.getDeviceType() : 'mobile';
|
||||
fetch(`/api/sessions/${app.activeSessionId}/resize`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ cols, rows }),
|
||||
body: JSON.stringify({ cols, rows, viewportType }),
|
||||
}).catch(() => {});
|
||||
}
|
||||
} catch {}
|
||||
|
||||
@@ -190,6 +190,12 @@ body {
|
||||
filter: brightness(1.1);
|
||||
}
|
||||
|
||||
/* Mobile-only header utility toggle — hidden by default; the mobile/tablet
|
||||
media queries in mobile.css (max-width: 768px) re-enable it as flex. */
|
||||
.mobile-header-utility-toggle {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Session Tabs */
|
||||
.session-tabs {
|
||||
display: flex;
|
||||
|
||||
@@ -109,6 +109,12 @@ export function registerWsRoutes(app: FastifyInstance, ctx: SessionPort, getHost
|
||||
socket.send(`{"t":"o","d":${JSON.stringify(DEC_2026_START + data + DEC_2026_END)}}`);
|
||||
};
|
||||
|
||||
// Per-connection desktop sizing claim — registered on the first
|
||||
// desktop-typed resize and released on socket close, so Session.resize()
|
||||
// can ignore small-viewport resizes only while a desktop is actually
|
||||
// connected (see Session._desktopSizeClaims).
|
||||
const sizingToken = Symbol('ws-desktop-sizing');
|
||||
|
||||
// Attach message handler synchronously BEFORE any async work
|
||||
// (@fastify/websocket requirement to avoid dropped messages).
|
||||
socket.on('message', (raw) => {
|
||||
@@ -127,6 +133,13 @@ export function registerWsRoutes(app: FastifyInstance, ctx: SessionPort, getHost
|
||||
msg.r <= 200
|
||||
) {
|
||||
const viewportType = msg.v === 'mobile' || msg.v === 'tablet' || msg.v === 'desktop' ? msg.v : undefined;
|
||||
if (viewportType === 'desktop') {
|
||||
session.claimDesktopSizing(sizingToken);
|
||||
} else if (viewportType) {
|
||||
// The connection's viewport can change (e.g. browser window
|
||||
// narrowed past the tablet breakpoint) — drop a stale claim.
|
||||
session.releaseDesktopSizing(sizingToken);
|
||||
}
|
||||
if (viewportType) {
|
||||
session.resize(msg.c, msg.r, { viewportType });
|
||||
} else {
|
||||
@@ -210,6 +223,7 @@ export function registerWsRoutes(app: FastifyInstance, ctx: SessionPort, getHost
|
||||
session.off('clearTerminal', onClearTerminal);
|
||||
session.off('needsRefresh', onNeedsRefresh);
|
||||
session.off('exit', onSessionExit);
|
||||
session.releaseDesktopSizing(sizingToken);
|
||||
|
||||
// Decrement per-session connection count
|
||||
const count = sessionWsCount.get(id) ?? 1;
|
||||
|
||||
Reference in New Issue
Block a user