mirror of
https://github.com/stablyai/orca.git
synced 2026-10-12 16:03:03 +00:00
Remove mobile-fit debug logging, add debug skill doc
Strip all mfLog file logging and console.log('[mobile-fit]') calls
from production code. The skill doc at skills/mobile-fit-debug/SKILL.md
documents how to temporarily re-add logging when needed.
Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
@@ -210,7 +210,6 @@ export default function SessionScreen() {
|
||||
if (terminalUnsubsRef.current.has(handle)) return
|
||||
if (subscribingHandlesRef.current.has(handle)) return
|
||||
if (!getTerminalRef(handle)) {
|
||||
console.log(`[mobile-fit] subscribeToTerminal SKIP handle=${handle} reason=no-ref`)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -319,12 +318,11 @@ export default function SessionScreen() {
|
||||
if (toggleInFlightRef.current.has(handle)) return
|
||||
const current = terminalModes.get(handle) ?? 'auto'
|
||||
const next: MobileDisplayMode = current === 'auto' || current === 'phone' ? 'desktop' : 'auto'
|
||||
console.log(`[mobile-fit] toggleDisplayMode handle=${handle} current=${current} next=${next}`)
|
||||
toggleInFlightRef.current.add(handle)
|
||||
try {
|
||||
await client.sendRequest('terminal.setDisplayMode', { terminal: handle, mode: next })
|
||||
} catch (err) {
|
||||
console.log(`[mobile-fit] toggleDisplayMode FAILED handle=${handle}`, err)
|
||||
} catch {
|
||||
// Mode change failed — server state unchanged, UI stays in sync.
|
||||
} finally {
|
||||
toggleInFlightRef.current.delete(handle)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
---
|
||||
name: mobile-fit-debug
|
||||
description: Debug mobile terminal display-mode toggle and PTY resize issues. Use when investigating why a terminal restores to the wrong width after toggling between phone and desktop mode on mobile, or when PTY sizes are corrupted across tab switches.
|
||||
---
|
||||
|
||||
# Mobile Fit Debug
|
||||
|
||||
Use this skill when debugging mobile terminal display-mode toggle issues — wrong restore width, corrupted previousCols, stale PTY sizes, or collateral safeFit cascades.
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
The mobile display-mode toggle involves **3 processes** communicating asynchronously:
|
||||
|
||||
```
|
||||
Mobile App (React Native) Server (Electron main) Desktop Renderer (Electron renderer)
|
||||
───────────────────────── ────────────────────── ────────────────────────────────────
|
||||
terminal.setDisplayMode ──RPC──► applyMobileDisplayMode
|
||||
├─ resize PTY (authoritative)
|
||||
├─ suppressResizesForMs(500)
|
||||
├─ terminalFitOverrideChanged ──IPC──► setOverrideTick (React re-render)
|
||||
│ ├─ safeFit on ALL panes
|
||||
│ └─ pty:resize IPC ──────► SUPPRESSED
|
||||
└─ notifyTerminalResize ──stream──► mobile client updates
|
||||
```
|
||||
|
||||
## Key Files
|
||||
|
||||
| File | Role |
|
||||
|------|------|
|
||||
| `src/main/runtime/orca-runtime.ts` | Server-side state: `mobileSubscribers`, `mobileDisplayModes`, `lastRendererSizes`, suppress window. Core handlers: `handleMobileSubscribe`, `handleMobileUnsubscribe`, `applyMobileDisplayMode`, `onExternalPtyResize` |
|
||||
| `src/main/ipc/pty.ts` | `pty:resize` IPC handler. Checks `isResizeSuppressed()` before processing. Calls `onExternalPtyResize` to update server state |
|
||||
| `src/main/runtime/rpc/methods/terminal.ts` | RPC methods: `terminal.subscribe` (mobile subscription lifecycle), `terminal.setDisplayMode`, `terminal.getDisplayMode` |
|
||||
| `src/renderer/src/lib/pane-manager/mobile-fit-overrides.ts` | Desktop renderer's override cache. `setFitOverride('desktop-fit')` clears the override. `notifyChange` fires `onOverrideChange` listeners |
|
||||
| `src/renderer/src/lib/pane-manager/pane-tree-ops.ts` | `safeFit()` — measures pane DOM and resizes xterm. When a mobile-fit override exists, uses override dims instead of measuring |
|
||||
| `src/renderer/src/components/terminal-pane/TerminalPane.tsx` | `onOverrideChange` handler triggers `setOverrideTick` re-render cascade. This is what causes safeFit to run on ALL panes |
|
||||
| `mobile/app/h/[hostId]/session/[worktreeId].tsx` | Mobile session screen — tab switching, `toggleDisplayMode`, terminal subscription management |
|
||||
| `src/main/runtime/mobile-subscribe-integration.test.ts` | Integration tests for the full subscribe/unsubscribe/toggle lifecycle |
|
||||
|
||||
## Adding Debug Logging
|
||||
|
||||
Production code has no debug logging. To investigate issues, temporarily add file-based logging to the key files. Here's a pattern that works well:
|
||||
|
||||
### 1. Add mfLog helper to each file you want to instrument
|
||||
|
||||
```typescript
|
||||
// In orca-runtime.ts (no prefix):
|
||||
import { appendFileSync } from 'fs'
|
||||
function mfLog(msg: string): void {
|
||||
try { appendFileSync('/tmp/mobile-fit-debug.log', `[${new Date().toISOString()}] ${msg}\n`) } catch {}
|
||||
}
|
||||
|
||||
// In pty.ts (prefix [pty-ipc]):
|
||||
function mfLog(msg: string): void {
|
||||
try { appendFileSync('/tmp/mobile-fit-debug.log', `[${new Date().toISOString()}] [pty-ipc] ${msg}\n`) } catch {}
|
||||
}
|
||||
|
||||
// In terminal.ts RPC methods (prefix [rpc]):
|
||||
function mfLog(msg: string): void {
|
||||
try { appendFileSync('/tmp/mobile-fit-debug.log', `[${new Date().toISOString()}] [rpc] ${msg}\n`) } catch {}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Useful log points
|
||||
|
||||
**orca-runtime.ts — `handleMobileSubscribe`:**
|
||||
```typescript
|
||||
mfLog(`handleMobileSubscribe ptyId=${ptyId} mode=${mode} viewport=${viewport?.cols}x${viewport?.rows}`)
|
||||
mfLog(` existing.prev=${existing?.previousCols}x${existing?.previousRows} currentSize=${currentSize?.cols}x${currentSize?.rows} rendererSize=${rendererSize?.cols}x${rendererSize?.rows}`)
|
||||
mfLog(` → previousCols=${previousCols}`)
|
||||
```
|
||||
|
||||
**orca-runtime.ts — `applyMobileDisplayMode`:**
|
||||
```typescript
|
||||
mfLog(`applyMobileDisplayMode ptyId=${ptyId} mode=${mode} sub.prev=${subscriber?.previousCols}x${subscriber?.previousRows}`)
|
||||
mfLog(` DESKTOP RESTORE: previousCols=${previousCols}`)
|
||||
```
|
||||
|
||||
**orca-runtime.ts — `onExternalPtyResize`:**
|
||||
```typescript
|
||||
mfLog(`onExternalPtyResize ptyId=${ptyId} cols=${cols} rows=${rows}`)
|
||||
```
|
||||
|
||||
**pty.ts — `pty:resize` handler:**
|
||||
```typescript
|
||||
if (runtime?.isResizeSuppressed()) {
|
||||
mfLog(`pty:resize SUPPRESSED id=${args.id} cols=${args.cols} rows=${args.rows}`)
|
||||
return
|
||||
}
|
||||
mfLog(`pty:resize id=${args.id} cols=${args.cols} rows=${args.rows}`)
|
||||
```
|
||||
|
||||
**terminal.ts — `terminal.subscribe`:**
|
||||
```typescript
|
||||
mfLog(`\n========== MOBILE SUBSCRIBE ==========`)
|
||||
mfLog(`terminal.subscribe handle=${params.terminal} ptyId=${leaf?.ptyId} client=${params.client?.type}`)
|
||||
```
|
||||
|
||||
### 3. Reading the log
|
||||
|
||||
```bash
|
||||
> /tmp/mobile-fit-debug.log # clear
|
||||
# ... reproduce issue on mobile ...
|
||||
cat /tmp/mobile-fit-debug.log # read
|
||||
```
|
||||
|
||||
### 4. Remember to remove logging before committing
|
||||
|
||||
Remove all `mfLog` functions, calls, and `appendFileSync` imports.
|
||||
|
||||
## What to Look For in Logs
|
||||
|
||||
**Wrong previousCols captured:**
|
||||
```
|
||||
handleMobileSubscribe ptyId=...@@abc123 ...
|
||||
existing.prev=null currentSize=105x72 rendererSize=214x72 <-- rendererSize is WRONG
|
||||
→ previousCols=214 <-- captured wrong value
|
||||
```
|
||||
This means `lastRendererSizes` was polluted by a collateral safeFit cascade.
|
||||
|
||||
**Suppress window too short:**
|
||||
```
|
||||
[pty-ipc] pty:resize id=...@@abc123 cols=105 rows=72 <-- NOT suppressed, arrived after window
|
||||
```
|
||||
If you see unsuppressed `pty:resize` events for terminals that weren't the one being toggled, the 500ms suppress window may be too short. Check the timestamp gap between `applyMobileDisplayMode` and the stale resize.
|
||||
|
||||
**Suppress working correctly:**
|
||||
```
|
||||
[pty-ipc] pty:resize SUPPRESSED id=...@@abc123 cols=214 rows=72
|
||||
```
|
||||
|
||||
**Collateral cascade pattern** (the root cause of most bugs):
|
||||
```
|
||||
applyMobileDisplayMode ptyId=...@@split mode=desktop
|
||||
DESKTOP RESTORE: previousCols=105
|
||||
[pty-ipc] pty:resize SUPPRESSED id=...@@split cols=105 <-- redundant, suppressed OK
|
||||
[pty-ipc] pty:resize SUPPRESSED id=...@@other cols=214 <-- collateral, suppressed OK
|
||||
```
|
||||
If the collateral line says `pty:resize id=` instead of `SUPPRESSED`, the cascade leaked through.
|
||||
|
||||
## Known Gotchas
|
||||
|
||||
### 1. The Collateral safeFit Cascade
|
||||
When `terminalFitOverrideChanged` fires for ONE pane, the desktop renderer's `setOverrideTick` React state change triggers a re-render that runs `safeFit` on ALL panes across ALL tabs. Background-tab panes get measured at wrong widths because they may not be visible or may be in different split configurations than the active tab.
|
||||
|
||||
### 2. Cascade Timing is ~360ms
|
||||
The full cascade path: server sends IPC -> desktop renderer receives -> React re-render -> requestAnimationFrame -> DOM measurement -> `pty:resize` IPC back to server. This takes ~360ms, which is why the suppress window must be >360ms (currently 500ms).
|
||||
|
||||
### 3. lastRendererSizes Persistence
|
||||
`lastRendererSizes` stores every `pty:resize` from the desktop renderer. Stale values persist across mobile subscribe/unsubscribe cycles. The `lastRendererSizes.delete(ptyId)` in desktop restore is critical — without it, a stale 214 from a previous cascade gets used as `previousCols` on the next subscribe.
|
||||
|
||||
### 4. Daemon Doesn't Hot-Reload
|
||||
The daemon process (`out/main/daemon-entry.js`) loads compiled code once at startup. Changes to `orca-runtime.ts` or `pty.ts` require:
|
||||
1. `npx electron-vite build --outDir out` (or wait for watcher)
|
||||
2. **Restart the dev server** (`pnpm dev`) — the daemon subprocess won't pick up new builds
|
||||
|
||||
Verify your code is live: `grep "your_unique_string" out/main/index.js`
|
||||
|
||||
### 5. previousCols Priority Chain
|
||||
```typescript
|
||||
previousCols = existing?.previousCols ?? rendererSize?.cols ?? currentSize?.cols
|
||||
```
|
||||
- `existing?.previousCols` — re-subscribe case (tab switch back), most trusted
|
||||
- `rendererSize?.cols` — from `lastRendererSizes`, the desktop renderer's last `pty:resize`
|
||||
- `currentSize?.cols` — from `getTerminalSize()`, the server-side PTY size
|
||||
|
||||
If `rendererSize` is polluted (e.g. 214 for a split pane), it takes priority over the correct `currentSize`. That's why clearing `lastRendererSizes` on restore is essential.
|
||||
|
||||
### 6. agent-device for Automated Testing
|
||||
Use `/opt/homebrew/bin/agent-device` to automate mobile UI testing:
|
||||
```bash
|
||||
agent-device snapshot # accessibility tree
|
||||
agent-device click @e45 # click element by ref
|
||||
agent-device screenshot --out /tmp/s.png # capture screenshot
|
||||
agent-device wait 2 # wait 2 seconds
|
||||
```
|
||||
Typical test flow: click tab -> click "Switch to desktop mode" -> wait 3s -> click "Switch to phone mode" -> switch tabs -> repeat -> read `/tmp/mobile-fit-debug.log`.
|
||||
|
||||
## Running Tests
|
||||
|
||||
```bash
|
||||
npx vitest run src/main/runtime/mobile-subscribe-integration.test.ts
|
||||
```
|
||||
@@ -5,15 +5,6 @@ boundary. Splitting it by line count would scatter tightly coupled terminal
|
||||
process behavior across files without a cleaner ownership seam. */
|
||||
import { join, delimiter } from 'path'
|
||||
import { randomUUID } from 'crypto'
|
||||
import { appendFileSync } from 'fs'
|
||||
|
||||
function mfLog(msg: string): void {
|
||||
try {
|
||||
appendFileSync('/tmp/mobile-fit-debug.log', `[${new Date().toISOString()}] [pty-ipc] ${msg}\n`)
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
import { type BrowserWindow, ipcMain, app } from 'electron'
|
||||
export { getBashShellReadyRcfileContent } from '../providers/local-pty-shell-ready'
|
||||
import type { OrcaRuntimeService } from '../runtime/orca-runtime'
|
||||
@@ -840,12 +831,8 @@ export function registerPtyHandlers(
|
||||
// of their correct split width. Suppressing ALL pty:resize during
|
||||
// this window prevents the cascade from corrupting PTY dimensions.
|
||||
if (runtime?.isResizeSuppressed()) {
|
||||
mfLog(
|
||||
`pty:resize SUPPRESSED (global window) id=${args.id} cols=${args.cols} rows=${args.rows}`
|
||||
)
|
||||
return
|
||||
}
|
||||
mfLog(`pty:resize id=${args.id} cols=${args.cols} rows=${args.rows}`)
|
||||
ptySizes.set(args.id, { cols: args.cols, rows: args.rows })
|
||||
getProviderForPty(args.id).resize(args.id, args.cols, args.rows)
|
||||
runtime?.onExternalPtyResize(args.id, args.cols, args.rows)
|
||||
|
||||
@@ -11,18 +11,6 @@ import { gitExecFileAsync, gitExecFileSync } from '../git/runner'
|
||||
import { isWslPath, parseWslPath, getWslHome } from '../wsl'
|
||||
import { randomUUID } from 'crypto'
|
||||
import { join } from 'path'
|
||||
import { appendFileSync } from 'fs'
|
||||
|
||||
const MOBILE_FIT_LOG = '/tmp/mobile-fit-debug.log'
|
||||
function mfLog(msg: string): void {
|
||||
const ts = new Date().toISOString()
|
||||
const line = `[${ts}] ${msg}\n`
|
||||
try {
|
||||
appendFileSync(MOBILE_FIT_LOG, line)
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
import { rm } from 'fs/promises'
|
||||
import { OrchestrationDb } from './orchestration/db'
|
||||
import { formatMessagesForInjection } from './orchestration/formatter'
|
||||
@@ -1106,14 +1094,7 @@ export class OrcaRuntimeService {
|
||||
viewport?: { cols: number; rows: number }
|
||||
): boolean {
|
||||
const mode = this.mobileDisplayModes.get(ptyId) ?? 'auto'
|
||||
mfLog(
|
||||
`handleMobileSubscribe ptyId=${ptyId} mode=${mode} viewport=${viewport ? `${viewport.cols}x${viewport.rows}` : 'none'}`
|
||||
)
|
||||
console.log(
|
||||
`[mobile-fit] handleMobileSubscribe ptyId=${ptyId} mode=${mode} viewport=${viewport ? `${viewport.cols}x${viewport.rows}` : 'none'}`
|
||||
)
|
||||
if (!viewport) {
|
||||
console.log('[mobile-fit] → skipped (no viewport)')
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -1136,24 +1117,11 @@ export class OrcaRuntimeService {
|
||||
const previousCols = existing?.previousCols ?? rendererSize?.cols ?? currentSize?.cols ?? null
|
||||
const previousRows = existing?.previousRows ?? rendererSize?.rows ?? currentSize?.rows ?? null
|
||||
|
||||
mfLog(
|
||||
` existing.previousCols=${existing?.previousCols ?? 'null'} existing.previousRows=${existing?.previousRows ?? 'null'} existing.wasResized=${existing?.wasResizedToPhone ?? 'N/A'}`
|
||||
)
|
||||
mfLog(
|
||||
` currentSize=${currentSize ? `${currentSize.cols}x${currentSize.rows}` : 'null'} rendererSize=${rendererSize ? `${rendererSize.cols}x${rendererSize.rows}` : 'null'}`
|
||||
)
|
||||
mfLog(` → previousCols=${previousCols} previousRows=${previousRows}`)
|
||||
mfLog(
|
||||
` allRendererSizes: ${JSON.stringify([...this.lastRendererSizes.entries()].map(([k, v]) => [k.slice(-12), `${v.cols}x${v.rows}`]))}`
|
||||
)
|
||||
|
||||
// Why: always register the subscriber so applyMobileDisplayMode can find
|
||||
// the viewport when the user later toggles from desktop to auto/phone.
|
||||
// Without this, toggling to auto after subscribing in desktop mode sees
|
||||
// hasSubscriber=false and can't perform the phone resize.
|
||||
if (mode === 'desktop') {
|
||||
mfLog(` → SKIP (desktop mode), registering with null prev`)
|
||||
console.log('[mobile-fit] → registered subscriber but skipped resize (desktop mode)')
|
||||
// Why: set previousCols/Rows to null so we don't capture a stale PTY
|
||||
// size that may not match the actual pane geometry (e.g. 214 when the
|
||||
// pane is in a split at 105). When the user later toggles to auto/phone,
|
||||
@@ -1184,14 +1152,6 @@ export class OrcaRuntimeService {
|
||||
// to a terminal that was left at phone dims (no restore on tab switch)
|
||||
// should not trigger another SIGWINCH → shell prompt redraw.
|
||||
const alreadyAtTarget = currentSize?.cols === clampedCols && currentSize?.rows === clampedRows
|
||||
const prevSource =
|
||||
existing?.previousCols != null ? 'existing' : rendererSize ? 'renderer' : 'pty'
|
||||
mfLog(
|
||||
` → ${alreadyAtTarget ? 'ALREADY AT' : 'RESIZE TO'} ${clampedCols}x${clampedRows} src=${prevSource}`
|
||||
)
|
||||
console.log(
|
||||
`[mobile-fit] → ${alreadyAtTarget ? 'already at' : 'resizing PTY to'} ${clampedCols}x${clampedRows} (prev=${previousCols}x${previousRows} src=${prevSource}) notifier=${!!this.notifier}`
|
||||
)
|
||||
if (!alreadyAtTarget) {
|
||||
this.ptyController?.resize?.(ptyId, clampedCols, clampedRows)
|
||||
this.resizeHeadlessTerminal(ptyId, clampedCols, clampedRows)
|
||||
@@ -1217,20 +1177,12 @@ export class OrcaRuntimeService {
|
||||
// intermediate terminals keep their current dims harmlessly.
|
||||
handleMobileUnsubscribe(ptyId: string, clientId: string): void {
|
||||
const subscriber = this.mobileSubscribers.get(ptyId)
|
||||
mfLog(
|
||||
`handleMobileUnsubscribe ptyId=${ptyId} hasSub=${!!subscriber} wasResized=${subscriber?.wasResizedToPhone} prev=${subscriber?.previousCols}x${subscriber?.previousRows}`
|
||||
)
|
||||
console.log(
|
||||
`[mobile-fit] handleMobileUnsubscribe ptyId=${ptyId} hasSubscriber=${!!subscriber} wasResized=${subscriber?.wasResizedToPhone}`
|
||||
)
|
||||
if (!subscriber || subscriber.clientId !== clientId) {
|
||||
mfLog(` → SKIP (no subscriber or clientId mismatch)`)
|
||||
return
|
||||
}
|
||||
|
||||
const mode = this.mobileDisplayModes.get(ptyId) ?? 'auto'
|
||||
this.mobileSubscribers.delete(ptyId)
|
||||
mfLog(` mode=${mode} deleted subscriber`)
|
||||
|
||||
if (mode === 'auto' && subscriber.wasResizedToPhone) {
|
||||
const existing = this.pendingRestoreTimers.get(ptyId)
|
||||
@@ -1239,14 +1191,11 @@ export class OrcaRuntimeService {
|
||||
}
|
||||
|
||||
const { previousCols, previousRows } = subscriber
|
||||
mfLog(` scheduling delayed restore to ${previousCols}x${previousRows} in 300ms`)
|
||||
const timer = setTimeout(() => {
|
||||
this.pendingRestoreTimers.delete(ptyId)
|
||||
if (this.mobileSubscribers.has(ptyId)) {
|
||||
mfLog(` delayed restore CANCELLED — new subscriber exists`)
|
||||
return
|
||||
}
|
||||
mfLog(` delayed restore FIRING: ptyId=${ptyId} resize to ${previousCols}x${previousRows}`)
|
||||
if (previousCols != null && previousRows != null) {
|
||||
this.ptyController?.resize?.(ptyId, previousCols, previousRows)
|
||||
this.resizeHeadlessTerminal(ptyId, previousCols, previousRows)
|
||||
@@ -1275,19 +1224,10 @@ export class OrcaRuntimeService {
|
||||
applyMobileDisplayMode(ptyId: string): void {
|
||||
const mode = this.mobileDisplayModes.get(ptyId) ?? 'auto'
|
||||
const subscriber = this.mobileSubscribers.get(ptyId)
|
||||
const rendererSize = this.lastRendererSizes.get(ptyId)
|
||||
const currentSize = this.getTerminalSize(ptyId)
|
||||
mfLog(
|
||||
`applyMobileDisplayMode ptyId=${ptyId} mode=${mode} hasSub=${!!subscriber} wasResized=${subscriber?.wasResizedToPhone} sub.prev=${subscriber?.previousCols}x${subscriber?.previousRows} rendererSize=${rendererSize ? `${rendererSize.cols}x${rendererSize.rows}` : 'null'} currentSize=${currentSize ? `${currentSize.cols}x${currentSize.rows}` : 'null'}`
|
||||
)
|
||||
console.log(
|
||||
`[mobile-fit] applyMobileDisplayMode ptyId=${ptyId} mode=${mode} hasSubscriber=${!!subscriber} wasResized=${subscriber?.wasResizedToPhone}`
|
||||
)
|
||||
|
||||
if (mode === 'desktop') {
|
||||
if (subscriber?.wasResizedToPhone) {
|
||||
const { previousCols, previousRows } = subscriber
|
||||
mfLog(` DESKTOP RESTORE: previousCols=${previousCols} previousRows=${previousRows}`)
|
||||
if (previousCols != null && previousRows != null) {
|
||||
this.ptyController?.resize?.(ptyId, previousCols, previousRows)
|
||||
this.resizeHeadlessTerminal(ptyId, previousCols, previousRows)
|
||||
@@ -1311,7 +1251,6 @@ export class OrcaRuntimeService {
|
||||
)
|
||||
}
|
||||
const size = this.getTerminalSize(ptyId)
|
||||
mfLog(` NOTIFY desktop resize: ${size?.cols}x${size?.rows}`)
|
||||
this.notifyTerminalResize(ptyId, {
|
||||
cols: size?.cols ?? 0,
|
||||
rows: size?.rows ?? 0,
|
||||
@@ -1345,23 +1284,14 @@ export class OrcaRuntimeService {
|
||||
// the actual pane geometry instead of a stale PTY size for previousCols.
|
||||
onExternalPtyResize(ptyId: string, cols: number, rows: number): void {
|
||||
this.lastRendererSizes.set(ptyId, { cols, rows })
|
||||
mfLog(`onExternalPtyResize ptyId=${ptyId} cols=${cols} rows=${rows}`)
|
||||
|
||||
const subscriber = this.mobileSubscribers.get(ptyId)
|
||||
if (!subscriber) {
|
||||
mfLog(` no subscriber, stored in lastRendererSizes only`)
|
||||
return
|
||||
}
|
||||
if (!subscriber.wasResizedToPhone) {
|
||||
mfLog(
|
||||
` updated subscriber.previousCols ${subscriber.previousCols}→${cols} previousRows ${subscriber.previousRows}→${rows}`
|
||||
)
|
||||
subscriber.previousCols = cols
|
||||
subscriber.previousRows = rows
|
||||
} else {
|
||||
mfLog(
|
||||
` SKIPPED (wasResizedToPhone=true, keeping prev=${subscriber.previousCols}x${subscriber.previousRows})`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1399,9 +1329,6 @@ export class OrcaRuntimeService {
|
||||
event: { cols: number; rows: number; displayMode: string; reason: string }
|
||||
): void {
|
||||
const listeners = this.resizeListeners.get(ptyId)
|
||||
mfLog(
|
||||
`notifyTerminalResize ptyId=${ptyId} cols=${event.cols} rows=${event.rows} mode=${event.displayMode} reason=${event.reason} listenerCount=${listeners?.size ?? 0}`
|
||||
)
|
||||
if (!listeners) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1,17 +1,8 @@
|
||||
/* oxlint-disable max-lines -- Why: terminal RPC methods are co-located for discoverability; splitting would scatter related handlers across files. */
|
||||
import { z } from 'zod'
|
||||
import { appendFileSync } from 'fs'
|
||||
import { defineMethod, defineStreamingMethod, type RpcAnyMethod } from '../core'
|
||||
import { OptionalFiniteNumber, OptionalString, requiredString } from '../schemas'
|
||||
|
||||
function mfLog(msg: string): void {
|
||||
try {
|
||||
appendFileSync('/tmp/mobile-fit-debug.log', `[${new Date().toISOString()}] [rpc] ${msg}\n`)
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
|
||||
const TerminalHandle = z.object({
|
||||
terminal: requiredString('Missing terminal handle')
|
||||
})
|
||||
@@ -247,12 +238,6 @@ export const TERMINAL_METHODS: RpcAnyMethod[] = [
|
||||
params: TerminalSetDisplayMode,
|
||||
handler: async (params, { runtime }) => {
|
||||
const leaf = runtime.resolveLeafForHandle(params.terminal)
|
||||
mfLog(
|
||||
`setDisplayMode handle=${params.terminal} mode=${params.mode} ptyId=${leaf?.ptyId ?? 'none'}`
|
||||
)
|
||||
console.log(
|
||||
`[mobile-fit] setDisplayMode handle=${params.terminal} mode=${params.mode} ptyId=${leaf?.ptyId ?? 'none'}`
|
||||
)
|
||||
if (!leaf?.ptyId) {
|
||||
throw new Error('no_connected_pty')
|
||||
}
|
||||
@@ -280,15 +265,6 @@ export const TERMINAL_METHODS: RpcAnyMethod[] = [
|
||||
handler: async (params, { runtime }, emit) => {
|
||||
let leaf = runtime.resolveLeafForHandle(params.terminal)
|
||||
const isMobile = params.client?.type === 'mobile'
|
||||
if (isMobile) {
|
||||
mfLog(`\n========== MOBILE SUBSCRIBE ==========`)
|
||||
}
|
||||
mfLog(
|
||||
`terminal.subscribe handle=${params.terminal} ptyId=${leaf?.ptyId ?? 'none'} client=${params.client?.type ?? 'none'} viewport=${params.viewport ? `${params.viewport.cols}x${params.viewport.rows}` : 'none'}`
|
||||
)
|
||||
console.log(
|
||||
`[mobile-fit] terminal.subscribe handle=${params.terminal} ptyId=${leaf?.ptyId ?? 'none'} client=${params.client?.type ?? 'none'} viewport=${params.viewport ? `${params.viewport.cols}x${params.viewport.rows}` : 'none'}`
|
||||
)
|
||||
|
||||
// Why: the left pane's PTY spawns asynchronously after the tab is created.
|
||||
// Mobile clients that subscribe before the PTY is ready would get a bare
|
||||
@@ -298,22 +274,12 @@ export const TERMINAL_METHODS: RpcAnyMethod[] = [
|
||||
try {
|
||||
const ptyId = await runtime.waitForLeafPtyId(params.terminal)
|
||||
leaf = { ptyId }
|
||||
mfLog(
|
||||
` terminal.subscribe handle=${params.terminal} ptyId resolved to ${ptyId} after wait`
|
||||
)
|
||||
console.log(
|
||||
`[mobile-fit] terminal.subscribe handle=${params.terminal} ptyId resolved to ${ptyId} after wait`
|
||||
)
|
||||
} catch {
|
||||
mfLog(` terminal.subscribe handle=${params.terminal} PTY wait timed out`)
|
||||
console.log(
|
||||
`[mobile-fit] terminal.subscribe handle=${params.terminal} PTY wait timed out, falling back`
|
||||
)
|
||||
// PTY wait timed out — fall through to scrollback-only path below
|
||||
}
|
||||
}
|
||||
|
||||
if (!leaf?.ptyId) {
|
||||
mfLog(` terminal.subscribe handle=${params.terminal} no ptyId, sending scrollback+end`)
|
||||
const read = await runtime.readTerminal(params.terminal)
|
||||
emit({
|
||||
type: 'scrollback',
|
||||
@@ -329,9 +295,6 @@ export const TERMINAL_METHODS: RpcAnyMethod[] = [
|
||||
|
||||
const ptyId = leaf.ptyId
|
||||
const clientId = params.client?.id
|
||||
mfLog(
|
||||
` terminal.subscribe resolved ptyId=${ptyId} clientId=${clientId ?? 'none'} isMobile=${isMobile}`
|
||||
)
|
||||
|
||||
// Server-side auto-fit: resize PTY to phone dims before serializing scrollback
|
||||
if (isMobile && clientId) {
|
||||
@@ -361,9 +324,6 @@ export const TERMINAL_METHODS: RpcAnyMethod[] = [
|
||||
// mobile clients. They include fresh serialized scrollback so the client
|
||||
// can reinitialize xterm without resubscribing.
|
||||
const unsubscribeResize = runtime.subscribeToTerminalResize(ptyId, async (event) => {
|
||||
mfLog(
|
||||
` terminal.subscribe RESIZE event ptyId=${ptyId} cols=${event.cols} rows=${event.rows} displayMode=${event.displayMode} reason=${event.reason}`
|
||||
)
|
||||
const fresh = await runtime.serializeTerminalBuffer(ptyId)
|
||||
emit({
|
||||
type: 'resized',
|
||||
@@ -389,9 +349,6 @@ export const TERMINAL_METHODS: RpcAnyMethod[] = [
|
||||
|
||||
const subscriptionId = params.terminal
|
||||
runtime.registerSubscriptionCleanup(subscriptionId, () => {
|
||||
mfLog(
|
||||
` terminal.subscribe CLEANUP ptyId=${ptyId} isMobile=${isMobile} clientId=${clientId ?? 'none'}`
|
||||
)
|
||||
unsubscribeData()
|
||||
unsubscribeResize()
|
||||
unsubscribeFit()
|
||||
|
||||
@@ -114,9 +114,6 @@ export default function TerminalPane({
|
||||
useEffect(
|
||||
() =>
|
||||
onOverrideChange((event) => {
|
||||
console.log(
|
||||
`[mobile-fit] desktop onOverrideChange ptyId=${event.ptyId} mode=${event.mode} cols=${event.cols} rows=${event.rows}`
|
||||
)
|
||||
setOverrideTick((n) => n + 1)
|
||||
if (event.mode === 'desktop-fit') {
|
||||
const paneIds = getPaneIdsForPty(event.ptyId)
|
||||
|
||||
@@ -789,9 +789,6 @@ export function useIpcEvents(): void {
|
||||
|
||||
unsubs.push(
|
||||
window.api.runtime.onTerminalFitOverrideChanged((event) => {
|
||||
console.log(
|
||||
`[mobile-fit] desktop received fitOverrideChanged ptyId=${event.ptyId} mode=${event.mode} cols=${event.cols} rows=${event.rows}`
|
||||
)
|
||||
setFitOverride(event.ptyId, event.mode, event.cols, event.rows)
|
||||
})
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user