fix(v0.4): full-screen per device — latest-writer-wins PTY sizing

A shared PTY can only be one size; min-sizing clamped the shared session to the
SMALLEST viewer, so a wide desktop got letterboxed when an iPad was also attached
(iPad looked full-screen, desktop did not).

Switch to latest-writer-wins: the device that most recently fit/focused drives the
PTY size, so whichever device you're actively using is full-screen.
- session.ts: setClientDims resizes the PTY directly (drop min across clients);
  attach/detach/blur never resize (the active device keeps its size)
- frontend: TerminalSession.refit() force-resends dims; window 'focus' /
  visibilitychange re-assert the active tab's size, so switching devices reclaims
  full-screen
- tests updated for latest-wins (incl. join/hidden/blur don't resize)

Verified (two ws clients): 200x50 → iPad 100x40 → desktop refit 200x50 → iPad blur
keeps 200x50. 225 tests green.
This commit is contained in:
Yaojia Wang
2026-06-19 11:16:59 +02:00
parent d782ec8488
commit edbfc62f7f
5 changed files with 87 additions and 70 deletions

View File

@@ -45,6 +45,14 @@ app.applySettings(settings)
// Key bar (mobile + desktop) sends to whichever tab is active.
mountKeybar((data) => app.sendToActive(data))
// Multi-device: when this device regains focus, re-assert the active tab's size
// so a shared session snaps to THIS screen (latest-writer-wins) — full-screen on
// whichever device you're currently using.
window.addEventListener('focus', () => app.refitActive())
document.addEventListener('visibilitychange', () => {
if (!document.hidden) app.refitActive()
})
// Toolbar utilities.
mountSearch(toolbar, {
find: (query, dir) => app.findInActive(query, dir),

View File

@@ -337,6 +337,12 @@ export class TabApp {
this.tabs[this.activeIndex]?.session.send(data)
}
/** Re-assert the active tab's size (latest-writer-wins) when this device
* regains focus, so a shared session snaps back to this screen's size. */
refitActive(): void {
this.tabs[this.activeIndex]?.session.refit()
}
/** Apply theme + font settings to every terminal (M3). */
applySettings(s: Settings): void {
this.settings = s

View File

@@ -349,6 +349,19 @@ export class TerminalSession {
})
}
/**
* Re-fit and FORCE-resend dims (latest-writer-wins): when this device regains
* focus, re-assert its size so the shared PTY snaps to this screen even if
* another device had resized it. No-op while hidden.
*/
refit(): void {
if (this.el.style.display === 'none') return
this.lastCols = -1 // force sendResize to fire even if our dims are unchanged
this.lastRows = -1
const dims = this.safefit()
if (dims !== null) this.sendResize(dims.cols, dims.rows)
}
hide(): void {
this.el.style.display = 'none'
// v0.4: withdraw our size vote so a backgrounded mirror doesn't clamp the