Fix mobile "phone size" Restore buttons doing nothing (#7749)

This commit is contained in:
Brennan Benson
2026-07-07 15:31:34 -07:00
committed by GitHub
parent 1b66de0426
commit c4db3ca51d
2 changed files with 367 additions and 37 deletions
+269 -4
View File
@@ -74,13 +74,21 @@ const store = {
})
}
function createRuntime() {
const runtime = new OrcaRuntimeService(store)
// Why (#7588): the held-modal repro needs indefinite hold (null) while the
// legacy tests rely on the finite 5s default. Wrap getSettings per-test so a
// caller can pick the hold without mutating the shared stub.
function createRuntime(mobileAutoRestoreFitMs: number | null = 5_000) {
const effectiveStore = {
...store,
getSettings: () => ({ ...store.getSettings(), mobileAutoRestoreFitMs })
}
const runtime = new OrcaRuntimeService(effectiveStore)
const ptySizes = new Map<string, { cols: number; rows: number }>([
['pty-1', { cols: 150, rows: 40 }]
])
const resizes: { ptyId: string; cols: number; rows: number }[] = []
const driverEvents: { ptyId: string; driver: { kind: string; clientId?: string } }[] = []
const fitOverrideEvents: { ptyId: string; mode: string; cols: number; rows: number }[] = []
let resizeSucceeds = true
runtime.setPtyController({
@@ -107,7 +115,9 @@ function createRuntime() {
focusTerminal: vi.fn(),
closeTerminal: vi.fn(),
sleepWorktree: vi.fn(),
terminalFitOverrideChanged: vi.fn(),
terminalFitOverrideChanged: (ptyId, mode, cols, rows) => {
fitOverrideEvents.push({ ptyId, mode, cols, rows })
},
terminalDriverChanged: (ptyId, driver) => {
driverEvents.push({ ptyId, driver: { ...driver } })
}
@@ -118,6 +128,7 @@ function createRuntime() {
ptySizes,
resizes,
driverEvents,
fitOverrideEvents,
setResizeSucceeds: (next: boolean) => {
resizeSucceeds = next
}
@@ -314,11 +325,12 @@ describe('mobile presence lock — multi-mobile semantics', () => {
})
it('updateMobileViewport re-fits PTY without flipping the driver', async () => {
const { runtime, ptySizes, driverEvents } = createRuntime()
const { runtime, ptySizes, driverEvents, fitOverrideEvents } = createRuntime()
await runtime.handleMobileSubscribe('pty-1', 'phone-A', { cols: 49, rows: 38 })
expect(ptySizes.get('pty-1')).toEqual({ cols: 49, rows: 38 })
expect(runtime.getDriver('pty-1')).toEqual({ kind: 'mobile', clientId: 'phone-A' })
const before = driverEvents.length
const fitEventsBefore = fitOverrideEvents.length
// Keyboard opens — viewport shrinks.
await expect(
@@ -330,6 +342,10 @@ describe('mobile presence lock — multi-mobile semantics', () => {
// Why: a viewport update may re-emit driver to refresh listener
// wiring, but it must never go through `idle` (no banner flash).
expect(driverEvents.slice(before).every((e) => e.driver.kind === 'mobile')).toBe(true)
// Why: phone→phone dim ticks (keyboard show/hide) are the hottest layout
// path and must not wake the renderer's fit-override listeners — the
// emit gate opens only when layout kind or override presence changes.
expect(fitOverrideEvents.length).toBe(fitEventsBefore)
})
it('updateMobileViewport late-binds a viewport-less mobile subscriber', async () => {
@@ -467,3 +483,252 @@ describe('mobile presence lock — multi-mobile semantics', () => {
expect(ptySizes.get('pty-1')).toEqual({ cols: 150, rows: 40 })
})
})
// Why (#7588): drive the runtime into the reported held-modal state — a phone
// fit that an indefinite hold left behind, followed by a null-viewport
// resubscribe (app update / WebView reload) that re-registers an active
// subscriber with wasResizedToPhone=false while the override is still held.
// This is the state where the desktop "Your phone left this at phone size"
// modal's Restore buttons used to silently no-op.
async function reachHeldModalWithNullViewportResubscribe(
runtime: OrcaRuntimeService
): Promise<void> {
await runtime.handleMobileSubscribe('pty-1', 'phone-A', { cols: 45, rows: 20 })
runtime.handleMobileUnsubscribe('pty-1', 'phone-A')
await vi.advanceTimersByTimeAsync(300)
// Production RPC passes `params.viewport` straight through, so a client
// that hasn't measured yet arrives here as `undefined`.
await runtime.handleMobileSubscribe('pty-1', 'phone-A', undefined)
}
describe('mobile presence lock — issue #7588 held-modal restore convergence', () => {
beforeEach(() => vi.useFakeTimers())
afterEach(() => vi.useRealTimers())
// Scenario 1: the reported repro end-to-end. Restore must converge and
// notify BOTH the renderer notifier and a runtime listener (paired), since
// remote/web viewers ride the listener channel.
it('reclaim after a null-viewport resubscribe restores dims, clears override, notifies both channels', async () => {
const { runtime, ptySizes, fitOverrideEvents } = createRuntime(null)
const listenerEvents: { mode: string; cols: number; rows: number }[] = []
runtime.subscribeToFitOverrideChanges('pty-1', (e) => listenerEvents.push(e))
await reachHeldModalWithNullViewportResubscribe(runtime)
// Held state: driver idle, override still present, phone-sized PTY.
expect(runtime.getDriver('pty-1')).toEqual({ kind: 'idle' })
expect(runtime.getTerminalFitOverride('pty-1')).not.toBeNull()
expect(ptySizes.get('pty-1')).toEqual({ cols: 45, rows: 20 })
const notifierBefore = fitOverrideEvents.length
const listenerBefore = listenerEvents.length
const restored = await runtime.reclaimTerminalForDesktop('pty-1')
expect(restored).toBe(true)
expect(ptySizes.get('pty-1')).toEqual({ cols: 150, rows: 40 })
expect(runtime.getTerminalFitOverride('pty-1')).toBeNull()
expect(fitOverrideEvents.slice(notifierBefore).some((e) => e.mode === 'desktop-fit')).toBe(true)
expect(listenerEvents.slice(listenerBefore).some((e) => e.mode === 'desktop-fit')).toBe(true)
})
// Scenario 2: a second click after success is an idempotent no-op. The
// persistent null-viewport subscriber keeps reclaim in the active-subscriber
// branch; assert only "returns true, no new PTY resize, no new fit-override
// event" — a benign mobile-facing mode-change notify is acceptable.
it('second Restore click after success returns true with no new resize or fit-override event', async () => {
const { runtime, resizes, fitOverrideEvents } = createRuntime(null)
const listenerEvents: { mode: string; cols: number; rows: number }[] = []
runtime.subscribeToFitOverrideChanges('pty-1', (e) => listenerEvents.push(e))
await reachHeldModalWithNullViewportResubscribe(runtime)
expect(await runtime.reclaimTerminalForDesktop('pty-1')).toBe(true)
const resizeCount = resizes.length
const notifierCount = fitOverrideEvents.length
const listenerCount = listenerEvents.length
expect(await runtime.reclaimTerminalForDesktop('pty-1')).toBe(true)
expect(resizes.length).toBe(resizeCount)
expect(fitOverrideEvents.length).toBe(notifierCount)
expect(listenerEvents.length).toBe(listenerCount)
})
// Scenario 3: the existing driving take-back is unregressed — a phone that is
// actively driving (wasResizedToPhone=true) still flips to desktop, clears
// the override, and notifies both channels.
it('driving take-back still converges: driver → desktop, override cleared, both channels notified', async () => {
const { runtime, ptySizes, fitOverrideEvents } = createRuntime()
const listenerEvents: { mode: string; cols: number; rows: number }[] = []
runtime.subscribeToFitOverrideChanges('pty-1', (e) => listenerEvents.push(e))
await runtime.handleMobileSubscribe('pty-1', 'phone-A', { cols: 45, rows: 20 })
expect(runtime.getDriver('pty-1').kind).toBe('mobile')
expect(runtime.getTerminalFitOverride('pty-1')).not.toBeNull()
const restored = await runtime.reclaimTerminalForDesktop('pty-1')
expect(restored).toBe(true)
expect(runtime.getDriver('pty-1')).toEqual({ kind: 'desktop' })
expect(runtime.getTerminalFitOverride('pty-1')).toBeNull()
expect(ptySizes.get('pty-1')).toEqual({ cols: 150, rows: 40 })
expect(fitOverrideEvents.some((e) => e.mode === 'desktop-fit')).toBe(true)
expect(listenerEvents.some((e) => e.mode === 'desktop-fit')).toBe(true)
})
// Scenario 4: a failing resize during a HELD (no-subscriber) restore returns
// false and keeps the override — no lying, no phantom desktop-fit event.
it('held restore with a failing resize returns false and keeps the override', async () => {
const { runtime, fitOverrideEvents, setResizeSucceeds } = createRuntime(null)
const listenerEvents: { mode: string; cols: number; rows: number }[] = []
runtime.subscribeToFitOverrideChanges('pty-1', (e) => listenerEvents.push(e))
await runtime.handleMobileSubscribe('pty-1', 'phone-A', { cols: 45, rows: 20 })
// Last leaver under indefinite hold → override held, no active subscriber.
runtime.handleMobileUnsubscribe('pty-1', 'phone-A')
await vi.advanceTimersByTimeAsync(300)
expect(runtime.isMobileSubscriberActive('pty-1')).toBe(false)
expect(runtime.getTerminalFitOverride('pty-1')).not.toBeNull()
const notifierBefore = fitOverrideEvents.length
const listenerBefore = listenerEvents.length
setResizeSucceeds(false)
const restored = await runtime.reclaimTerminalForDesktop('pty-1')
expect(restored).toBe(false)
expect(runtime.getTerminalFitOverride('pty-1')).not.toBeNull()
expect(fitOverrideEvents.slice(notifierBefore).some((e) => e.mode === 'desktop-fit')).toBe(
false
)
expect(listenerEvents.slice(listenerBefore).some((e) => e.mode === 'desktop-fit')).toBe(false)
})
// Scenario 5: a failing resize during an ACTIVE-SUBSCRIBER take-back returns
// false, keeps the override, leaves the driver on its mobile lock, and
// restores the prior display mode ('auto') — the fix #3 P1 correction.
it('active-subscriber take-back with a failing resize returns false and preserves the mobile lock', async () => {
const { runtime, fitOverrideEvents, setResizeSucceeds } = createRuntime()
const listenerEvents: { mode: string; cols: number; rows: number }[] = []
runtime.subscribeToFitOverrideChanges('pty-1', (e) => listenerEvents.push(e))
await runtime.handleMobileSubscribe('pty-1', 'phone-A', { cols: 45, rows: 20 })
expect(runtime.getDriver('pty-1')).toEqual({ kind: 'mobile', clientId: 'phone-A' })
const notifierBefore = fitOverrideEvents.length
const listenerBefore = listenerEvents.length
setResizeSucceeds(false)
const restored = await runtime.reclaimTerminalForDesktop('pty-1')
expect(restored).toBe(false)
expect(runtime.getTerminalFitOverride('pty-1')).not.toBeNull()
// Driver stays mobile (lock retained) and mode is not left lying at 'desktop'.
expect(runtime.getDriver('pty-1')).toEqual({ kind: 'mobile', clientId: 'phone-A' })
expect(runtime.getMobileDisplayMode('pty-1')).toBe('auto')
expect(fitOverrideEvents.slice(notifierBefore).some((e) => e.mode === 'desktop-fit')).toBe(
false
)
expect(listenerEvents.slice(listenerBefore).some((e) => e.mode === 'desktop-fit')).toBe(false)
})
// Scenario 5b: after a FAILED active-subscriber take-back, wasResizedToPhone
// must be re-armed so a later unsubscribe under a finite auto-restore setting
// still schedules its timer and eventually clears the override. Without the
// re-arm the flag would be stuck false and the phone-fit would strand.
it('failed take-back re-arms wasResizedToPhone so a later unsubscribe still auto-restores', async () => {
// Finite auto-restore (5s default from the rig store).
const { runtime, ptySizes, setResizeSucceeds } = createRuntime()
await runtime.handleMobileSubscribe('pty-1', 'phone-A', { cols: 45, rows: 20 })
expect(ptySizes.get('pty-1')).toEqual({ cols: 45, rows: 20 })
// Take-back fails → false, flag re-armed, mode rolled back to 'auto'.
setResizeSucceeds(false)
expect(await runtime.reclaimTerminalForDesktop('pty-1')).toBe(false)
expect(runtime.getTerminalFitOverride('pty-1')).not.toBeNull()
// Phone then leaves the terminal. Resize works again for the auto-restore.
setResizeSucceeds(true)
runtime.handleMobileUnsubscribe('pty-1', 'phone-A')
// Soft-leave grace, then the finite auto-restore timer.
await vi.advanceTimersByTimeAsync(300)
await vi.advanceTimersByTimeAsync(5_000)
// The scheduled auto-restore fired: override cleared, PTY back to desktop.
expect(runtime.getTerminalFitOverride('pty-1')).toBeNull()
expect(ptySizes.get('pty-1')).toEqual({ cols: 150, rows: 40 })
})
// Scenario 6: a phone-initiated setDisplayMode('desktop') against a stale
// held override converges through the shared applyMobileDisplayMode seam.
it('phone-initiated setDisplayMode(desktop) against a stale held override converges', async () => {
const { runtime, ptySizes, fitOverrideEvents } = createRuntime(null)
const listenerEvents: { mode: string; cols: number; rows: number }[] = []
runtime.subscribeToFitOverrideChanges('pty-1', (e) => listenerEvents.push(e))
await reachHeldModalWithNullViewportResubscribe(runtime)
expect(runtime.getTerminalFitOverride('pty-1')).not.toBeNull()
const notifierBefore = fitOverrideEvents.length
const listenerBefore = listenerEvents.length
runtime.setMobileDisplayMode('pty-1', 'desktop')
const converged = await runtime.applyMobileDisplayMode('pty-1')
expect(converged).toBe(true)
expect(runtime.getTerminalFitOverride('pty-1')).toBeNull()
expect(ptySizes.get('pty-1')).toEqual({ cols: 150, rows: 40 })
expect(fitOverrideEvents.slice(notifierBefore).some((e) => e.mode === 'desktop-fit')).toBe(true)
expect(listenerEvents.slice(listenerBefore).some((e) => e.mode === 'desktop-fit')).toBe(true)
})
// Scenario 7 (white-box): a held override whose `layouts` entry is gone is
// unreachable via public APIs (onPtyExit deletes both in lockstep), so seed
// it directly. Reclaim must still delete the override and emit a paired
// desktop-fit 0×0 rather than stranding the modal on the next hydrate.
it('orphan cleanup: reclaim on a held override with no layout entry converges', async () => {
const { runtime, fitOverrideEvents } = createRuntime(null)
const listenerEvents: { mode: string; cols: number; rows: number }[] = []
runtime.subscribeToFitOverrideChanges('pty-1', (e) => listenerEvents.push(e))
const internal = runtime as unknown as {
terminalFitOverrides: Map<
string,
{
mode: string
cols: number
rows: number
previousCols: number | null
previousRows: number | null
updatedAt: number
clientId: string
}
>
}
internal.terminalFitOverrides.set('pty-1', {
mode: 'mobile-fit',
cols: 45,
rows: 20,
previousCols: 150,
previousRows: 40,
updatedAt: Date.now(),
clientId: 'phone-A'
})
expect(runtime.getTerminalFitOverride('pty-1')).not.toBeNull()
const restored = await runtime.reclaimTerminalForDesktop('pty-1')
expect(restored).toBe(true)
expect(runtime.getTerminalFitOverride('pty-1')).toBeNull()
expect(fitOverrideEvents.find((e) => e.mode === 'desktop-fit')).toEqual({
ptyId: 'pty-1',
mode: 'desktop-fit',
cols: 0,
rows: 0
})
expect(listenerEvents.find((e) => e.mode === 'desktop-fit')).toEqual({
mode: 'desktop-fit',
cols: 0,
rows: 0
})
})
})
+98 -33
View File
@@ -7285,13 +7285,30 @@ export class OrcaRuntimeService {
// the existing 'resized' event reaches the phone.
// 2. Held with no mobile subscriber (post-indefinite-hold): no inner
// subscriber to notify; resolve restore target and enqueueLayout
// directly. applyLayout is the SOLE writer of terminalFitOverrides;
// the held branch must not duplicate that mutation. See
// docs/mobile-fit-hold.md.
// directly. applyLayout is the SOLE writer of terminalFitOverrides on
// the normal path, so the held branch defers the mutation to it — the
// one exception is the dead-pty orphan cleanup below, which mirrors
// onPtyExit's sanctioned delete. See docs/mobile-fit-hold.md.
//
// Returns `true` only when the pty ends converged (no fit-override held);
// `false` when a restore was attempted but the resize failed (#7588), or
// when there was nothing to reclaim. On a failed-restore `false`, driver/mode
// are left unchanged so a driving phone keeps its lock and the modal
// truthfully stays — the user can retry.
async reclaimTerminalForDesktop(ptyId: string): Promise<boolean> {
if (this.isMobileSubscriberActive(ptyId)) {
// Why (#7588): capture the prior mode so a failed restore can roll the
// routing-only 'desktop' write back — driver/mode transitions must be
// gated on convergence, not committed before we know the resize took.
const priorMode = this.getMobileDisplayMode(ptyId)
this.setMobileDisplayMode(ptyId, 'desktop')
await this.applyMobileDisplayMode(ptyId)
const converged = await this.applyMobileDisplayMode(ptyId)
if (!converged) {
// Resize failed — override still held. Leave the driver on its mobile
// lock and undo the mode write so nothing is left lying at 'desktop'.
this.setMobileDisplayMode(ptyId, priorMode)
return false
}
this.setDriver(ptyId, { kind: 'desktop' })
// Why: a desktop-initiated reclaim is "I'm taking over right now",
// not a sticky preference. The next mobile subscribe (e.g. user
@@ -7315,15 +7332,32 @@ export class OrcaRuntimeService {
const renderer = this.lastRendererSizes.get(ptyId)
const cols = renderer?.cols ?? heldOverride.previousCols ?? fallback.cols
const rows = renderer?.rows ?? heldOverride.previousRows ?? fallback.rows
await this.enqueueLayout(ptyId, { kind: 'desktop', cols, rows })
this.setDriver(ptyId, { kind: 'desktop' })
// Why: a desktop-initiated reclaim is "I'm taking over right now",
// not a sticky preference. Reset to auto so the next mobile subscribe
// re-enters phone-fit. (Held-PTY branch may not have an entry, but
// calling setMobileDisplayMode('auto') is a no-op deletion in that
// case — safe and idempotent.)
this.setMobileDisplayMode(ptyId, 'auto')
return true
const result = await this.enqueueLayout(ptyId, { kind: 'desktop', cols, rows })
if (result.ok) {
this.setDriver(ptyId, { kind: 'desktop' })
// Why: a desktop-initiated reclaim is "I'm taking over right now",
// not a sticky preference. Reset to auto so the next mobile subscribe
// re-enters phone-fit. (Held-PTY branch may not have an entry, but
// calling setMobileDisplayMode('auto') is a no-op deletion in that
// case — safe and idempotent.)
this.setMobileDisplayMode(ptyId, 'auto')
return true
}
// Why (#7588): the layout entry is gone (pty exited) but the override is
// still held — unreachable via public APIs today (onPtyExit deletes both
// in lockstep), but reporting success while stranding the override would
// re-show the modal on the next hydrate. Run the same cleanup onPtyExit
// does (delete override + paired desktop-fit 0×0) so the renderer
// converges; nothing to resize on a dead pty.
if (result.reason === 'pty-exited' && this.terminalFitOverrides.has(ptyId)) {
this.terminalFitOverrides.delete(ptyId)
this.notifier?.terminalFitOverrideChanged(ptyId, 'desktop-fit', 0, 0)
this.notifyFitOverrideListeners(ptyId, 'desktop-fit', 0, 0)
return true
}
// resize-failed: the override remains held; report the truth so the
// modal correctly stays and the user can retry. Driver/mode untouched.
return false
}
return false
}
@@ -7439,7 +7473,8 @@ export class OrcaRuntimeService {
//
// applyLayout is the SOLE writer of:
// - this.layouts
// - this.terminalFitOverrides
// - this.terminalFitOverrides (except the sanctioned dead-pty cleanups in
// onPtyExit and reclaimTerminalForDesktop's orphan branch, which delete)
// - this.ptyController.resize (i.e. the actual PTY dims)
//
// Every trigger that wants to change PTY dims or flip mode goes through
@@ -7634,11 +7669,17 @@ export class OrcaRuntimeService {
this.resizeHeadlessTerminal(ptyId, target.cols, target.rows)
}
// Why: emit fit-override-changed only when the *mode* flips. Layouts
// can change dims without flipping mode (keyboard show/hide while
// phone), and waking the renderer on every viewport tick is wasteful
// churn.
if (modeChanged) {
// Why: emit fit-override-changed when the *mode* flips. Layouts can
// change dims without flipping mode (keyboard show/hide while phone),
// and waking the renderer on every viewport tick is wasteful churn.
// Defense-in-depth (#7588): also emit when the override's presence
// changed even without a kind flip. applyLayout is the sole writer and
// keeps override presence in lockstep with layout kind, so overrideChanged
// ≡ modeChanged in every reachable state today; the extra clause fires
// only if that invariant is ever violated, repairing the renderer instead
// of stranding the held modal.
const overrideChanged = (prevFitOverride != null) !== (target.kind === 'phone')
if (modeChanged || overrideChanged) {
// Why: phone→desktop arms the renderer-cascade suppress window
// before the collateral safeFit IPCs arrive. See "Renderer cascade
// suppression".
@@ -8036,7 +8077,13 @@ export class OrcaRuntimeService {
// Multi-mobile: the most recent mobile actor's viewport drives the active
// phone-fit dims. The earliest-by-subscribe-time subscriber's
// previousCols/Rows drive the desktop-restore target.
async applyMobileDisplayMode(ptyId: string): Promise<void> {
//
// Returns the post-condition "no fit-override remains held" (#7588): `true`
// when it cleared a held override OR nothing was held to begin with, `false`
// only when a restore was attempted and the resize failed (override rolled
// back, still held). reclaimTerminalForDesktop gates its driver/mode
// transitions on this; other callers ignore it.
async applyMobileDisplayMode(ptyId: string): Promise<boolean> {
const mode = this.getMobileDisplayMode(ptyId)
const inner = this.mobileSubscribers.get(ptyId)
const subscriber = inner ? this.pickMostRecentActor(inner) : null
@@ -8045,25 +8092,39 @@ export class OrcaRuntimeService {
if (mode === 'desktop') {
// Reset wasResizedToPhone on every fitted subscriber so a future
// toggle back to auto re-issues the resize. applyLayout owns the
// actual PTY resize + override delete + renderer notify.
let anyWasResized = false
if (inner) {
for (const sub of inner.values()) {
if (sub.wasResizedToPhone) {
anyWasResized = true
sub.wasResizedToPhone = false
}
}
// actual PTY resize + override delete + renderer notify. Track which
// subscribers we cleared so a failed resize can re-arm them.
const clearedFitSubscribers = inner
? [...inner.values()].filter((sub) => sub.wasResizedToPhone)
: []
for (const sub of clearedFitSubscribers) {
sub.wasResizedToPhone = false
}
if (anyWasResized) {
const anyWasResized = clearedFitSubscribers.length > 0
// Why (#7588): also restore when a fit-override is still held but no
// subscriber carries wasResizedToPhone — e.g. a null-viewport resubscribe
// after an indefinite hold resets the flag yet leaves the override,
// stranding the desktop "phone size" modal. Reuse resolveDesktopRestoreTarget
// (the same resolver the anyWasResized branch uses) so the two adjacent
// restore paths can never resolve to different dims for the same state.
if (anyWasResized || this.terminalFitOverrides.has(ptyId)) {
const restore = this.resolveDesktopRestoreTarget(ptyId)
await this.enqueueLayout(ptyId, {
const result = await this.enqueueLayout(ptyId, {
kind: 'desktop',
cols: restore.cols,
rows: restore.rows
})
// Why (#7588): a failed resize rolls the override back (still held), so
// re-arm the flags we cleared. Otherwise a later unsubscribe under a
// finite mobileAutoRestoreFitMs would see wasResizedToPhone=false, skip
// scheduling its auto-restore timer, and strand the held phone-fit.
if (!result.ok) {
for (const sub of clearedFitSubscribers) {
sub.wasResizedToPhone = true
}
}
} else {
// No subscriber was fitted — emit a mode-change resize event so
// Nothing was fitted or held — emit a mode-change resize event so
// the mobile client still learns the toggle landed.
const size = this.getTerminalSize(ptyId)
this.notifyTerminalResize(ptyId, {
@@ -8082,7 +8143,10 @@ export class OrcaRuntimeService {
const viewport = subscriberRecord.viewport
if (viewport) {
await this.handleMobileSubscribe(ptyId, subscriberRecord.clientId, viewport)
return
// After a phone-fit an override IS held, so this reports false. The
// auto branch is never reached from reclaim (it sets 'desktop'
// first); computed here only to keep the post-condition uniform.
return !this.terminalFitOverrides.has(ptyId)
}
}
// Why: always emit the mode change even when no resize occurred — the
@@ -8098,6 +8162,7 @@ export class OrcaRuntimeService {
seq: this.layouts.get(ptyId)?.seq
})
}
return !this.terminalFitOverrides.has(ptyId)
}
// Why: called after a desktop renderer path has successfully resized the