Skip to content

Restore pane and zoom state on reconnect#57

Open
guysmoilov wants to merge 1 commit into
mainfrom
feat/issue-56-reconnect-pane-zoom
Open

Restore pane and zoom state on reconnect#57
guysmoilov wants to merge 1 commit into
mainfrom
feat/issue-56-reconnect-pane-zoom

Conversation

@guysmoilov

@guysmoilov guysmoilov commented Feb 18, 2026

Copy link
Copy Markdown
Member

Summary

  • persist and reuse control clientId across reconnects
  • track per-client reconnect state (base session, active pane, zoomed state)
  • best-effort restore pane and zoom state after reattach
  • include tmux window zoom state in protocol/parsing and update test harness

Testing

  • npm run typecheck
  • npm test

Closes #56

Implementation Details

The reconnection mechanism is built around a per-client ReconnectState structure that persists three pieces of state:

  • baseSession: The tmux session the client was last attached to
  • paneId: The specific pane that was active
  • zoomed: Whether the pane/window was in zoomed state

Client Identity and Session Reuse

  • A normalizeClientId function sanitizes incoming clientId values from the authentication message
  • When a client reconnects with an existing clientId, the server forcefully closes any previous WebSocket connection for that identity and cleans up its context, then reuses the clientId for the new connection
  • This prevents duplicate control contexts and enables true session continuity

State Tracking and Restoration

  • The server tracks state changes through control message handlers (select_pane and zoom_pane mutations), storing them in rememberReconnectState
  • On disconnect, the current baseSession is persisted so subsequent reconnects can default to it
  • The tryRestoreClientView function applies stored state after attachment, with careful error handling:
    • Attempts selectPane with the remembered paneId
    • Only if successful, checks and restores zoom state by comparing stored vs. current window zoom status
    • Silently falls back to the current view if any restoration step fails

Frontend Client ID Persistence

  • The frontend persists a client-generated or server-assigned clientId to localStorage (key: "tmux-mobile-client-id")
  • Sends this clientId in the control channel authentication payload
  • Stores the server-confirmed clientId on successful auth_ok response

Test Harness and Protocol Updates

  • The fake tmux harness now tracks per-window zoom state and updates it during zoomPane operations
  • Protocol interfaces updated to include zoomed: boolean on TmuxWindowState at both frontend and backend
  • New integration tests verify clientId preservation and pane/zoom restoration across reconnects, including best-effort fallback when the remembered pane is no longer available

@coderabbitai

coderabbitai Bot commented Feb 18, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

The PR implements client-side reconnection with persistent session state restoration. It adds per-client tracking of base sessions, active panes, and zoom states via a new reconnect state mechanism. The frontend persists clientIds in localStorage and sends them during authentication. When clients reconnect, the server restores their previous pane and zoom context. Changes span backend session management, frontend storage, protocol types, and test infrastructure.

Changes

Cohort / File(s) Summary
Backend Reconnection State Tracking
src/backend/server.ts
Introduces ReconnectState structure and reconnectStateByClientId map to persist per-client session info (baseSession, paneId, zoomed). Adds normalizeClientId for sanitization, rememberReconnectState and tryRestoreClientView for state capture/restoration. Modifies auth flow to support clientId reuse and closes previous connections. Updates control attachment and message handling to track and restore pane/zoom state.
Tmux Window Zoom Representation
src/backend/tmux/cli-executor.ts, src/backend/tmux/parser.ts, src/backend/types/protocol.ts, src/frontend/types/protocol.ts
Extends tmux window data format to include zoom state flag. CLI executor adds window_zoomed_flag to format string; parser extracts zoomed column and derives boolean; both backend and frontend protocol types add zoomed: boolean property to TmuxWindowState interface.
Frontend ClientId Persistence
src/frontend/App.tsx
Adds tmux-mobile-client-id storage key and persistedClientIdRef to cache clientId in localStorage. Propagates clientId in control channel authentication and persists received clientId on successful auth_ok.
Test Infrastructure Updates
tests/harness/fakeTmux.ts, tests/backend/parser.test.ts, tests/integration/server.test.ts
Extends fake tmux WindowNode with zoomed: boolean property; propagates through session cloning and window creation; zoomPane toggles zoom state. Parser tests updated for new column format. Integration tests extended with authControl helper to accept optional clientId and added two new reconnect scenarios: verifying clientId preservation + pane/zoom restoration, and graceful fallback when remembered pane no longer exists.

Sequence Diagram

sequenceDiagram
    participant Client as Mobile Client
    participant Auth as Auth System
    participant Server as Server
    participant Storage as localStorage
    participant Tmux as TMUX

    autonumber

    rect rgba(100, 150, 200, 0.5)
    Note over Client,Tmux: Initial Connection
    Client->>Storage: Read persisted clientId (if exists)
    Client->>Server: Connect control WebSocket
    Client->>Auth: Send auth with clientId
    Auth->>Server: Authenticate & normalize clientId
    Server->>Tmux: Attach to base session
    Server->>Server: Store baseSession in reconnectState[clientId]
    Server->>Client: auth_ok with assigned clientId
    Client->>Storage: Persist clientId to localStorage
    end

    rect rgba(100, 200, 150, 0.5)
    Note over Client,Tmux: User Actions (Record State)
    Client->>Server: select_pane message
    Server->>Server: Remember pane in reconnectState[clientId]
    Server->>Tmux: Execute pane select
    Client->>Server: zoom_pane message
    Server->>Server: Update zoomed state in reconnectState[clientId]
    Server->>Tmux: Toggle window zoom
    end

    rect rgba(200, 150, 100, 0.5)
    Note over Client,Tmux: Reconnection Flow
    Client->>Storage: Read persisted clientId
    Client->>Server: Reconnect control WebSocket
    Client->>Auth: Send auth with same clientId
    Auth->>Server: Reuse clientId, close previous context
    Server->>Server: Lookup reconnectState[clientId]
    Server->>Tmux: Restore to remembered baseSession
    Server->>Server: Attempt restore pane & zoom from state
    Server->>Tmux: Attach to remembered pane (if exists)
    Server->>Tmux: Apply remembered zoom state
    Server->>Client: auth_ok + restored state
    Client->>Client: Reflect restored pane/zoom in UI
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed PR implements all acceptance criteria from issue #56: persists clientId, tracks reconnect state (pane/zoom), restores state on reconnect with best-effort fallback, and includes integration tests.
Out of Scope Changes check ✅ Passed All changes are directly scoped to issue #56 requirements: clientId persistence, reconnect state tracking, pane/zoom restoration, and necessary protocol/parser updates. No unrelated modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/issue-56-reconnect-pane-zoom

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

Copy link
Copy Markdown
Contributor

UI Screenshots

Auto-captured from commit ad91d7a

Screenshots are available as a build artifact.

Files captured: drawer-open.png, main-view.png

Download the ui-screenshots artifact from the Actions run to view full-resolution images.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/backend/server.ts (1)

501-506: ⚠️ Potential issue | 🟡 Minor

Potential race: fire-and-forget shutdown can kill a session the new context just attached to.

On close, shutdownControlContext is called without await (line 504). This means the mobile session kill runs asynchronously. If a client reconnects before the old shutdown completes, the new context won't find the old one in controlClients (already deleted on line 503) and may reuse the still-alive mobile session via hasMobileSession in attachControlToBaseSession. The old fire-and-forget shutdownControlContext could then kill that session from under the new context.

The window is very narrow (requires reconnection within milliseconds), but it could cause a session-destroyed error on the new connection. Awaiting the shutdown or tracking in-flight shutdowns would close it.

🧹 Nitpick comments (5)
src/backend/server.ts (2)

36-41: reconnectStateByClientId grows unboundedly — consider adding eviction.

The reconnectStateByClientId map is never pruned. Each unique clientId creates a permanent entry. While individual entries are small and the server likely serves few concurrent clients, long-running instances could accumulate stale entries. The updatedAt field is already present — a periodic sweep or LRU eviction based on it would prevent unbounded growth.

Also applies to: 136-136


361-367: Optimistic zoom toggle may briefly disagree with actual tmux state.

The new zoomed value is computed by inverting the currently tracked state rather than querying tmux after the command. If the tracked state is stale (e.g., first interaction before any snapshot), the saved value will be wrong until the next forcePublish (line 490) corrects it via updateReconnectStateFromSnapshot. This is a narrow race, but if a disconnect occurs in that window, the restore will apply the wrong zoom state.

Consider querying the actual window zoom state after zoomPane for accuracy, or accept this as a known limitation of the best-effort approach.

tests/backend/parser.test.ts (1)

14-15: Consider adding a test case for zoomed: true.

The test only covers zoomed: false (input 0). Adding a second window line with zoomed: true (input 1) would validate both branches of the zoomed === "1" check and guard against regressions.

🧪 Proposed addition
-    const windows = parseWindows("0\tbash\t1\t0\t2");
-    expect(windows[0]).toEqual({ index: 0, name: "bash", active: true, zoomed: false, paneCount: 2 });
+    const windows = parseWindows("0\tbash\t1\t0\t2\n1\tvim\t0\t1\t1");
+    expect(windows[0]).toEqual({ index: 0, name: "bash", active: true, zoomed: false, paneCount: 2 });
+    expect(windows[1]).toEqual({ index: 1, name: "vim", active: false, zoomed: true, paneCount: 1 });
tests/integration/server.test.ts (2)

279-281: Fixed-delay setTimeout synchronization is fragile under CI load.

Both tests use await new Promise(r => setTimeout(r, 40)) to wait for select_pane/zoom_pane to be processed and for the socket-close to propagate. On a loaded CI runner this window can be insufficient, making the tests intermittently flaky.

Consider awaiting a server-sent acknowledgement for select_pane/zoom_pane commands, or at minimum awaiting the close event on controlFirst instead of a fixed delay for the disconnect propagation:

♻️ Example: event-driven close wait
-      controlFirst.close();
-      await new Promise((resolve) => setTimeout(resolve, 40));
+      await new Promise<void>((resolve) => {
+        controlFirst.once("close", resolve);
+        controlFirst.close();
+      });

Also applies to: 312-314


262-331: Duplicated test setup across both new tests.

Lines 263–314 of test 1 and lines 296–314 of test 2 share an identical 20-line block (start server, connect, auth, snapshot, find pane, send select_pane + zoom_pane, disconnect). Extracting a shared helper would reduce duplication and make both tests easier to maintain.

♻️ Sketch of a shared helper
const setupAndDisconnect = async (): Promise<{ firstClientId: string; paneId: string }> => {
  const controlFirst = await openSocket(`${baseWsUrl}/ws/control`);
  const firstAuth = await authControl(controlFirst);
  const snapshot = await buildSnapshot(tmux);
  const session = snapshot.sessions.find((s) => s.name === firstAuth.attachedSession);
  const paneId = session?.windowStates[0]?.panes[0]?.id;
  expect(paneId).toBeDefined();

  controlFirst.send(JSON.stringify({ type: "select_pane", paneId }));
  controlFirst.send(JSON.stringify({ type: "zoom_pane", paneId }));
  await new Promise<void>((resolve) => { controlFirst.once("close", resolve); controlFirst.close(); });

  return { firstClientId: firstAuth.clientId, paneId: paneId as string };
};
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 84fe10e and e9cbe0c.

📒 Files selected for processing (9)
  • src/backend/server.ts
  • src/backend/tmux/cli-executor.ts
  • src/backend/tmux/parser.ts
  • src/backend/types/protocol.ts
  • src/frontend/App.tsx
  • src/frontend/types/protocol.ts
  • tests/backend/parser.test.ts
  • tests/harness/fakeTmux.ts
  • tests/integration/server.test.ts
🧰 Additional context used
📓 Path-based instructions (3)
**/types/protocol.ts

📄 CodeRabbit inference engine (AGENTS.md)

Keep protocol types in sync between backend (src/backend/types/protocol.ts) and frontend (src/frontend/types/protocol.ts)

Files:

  • src/frontend/types/protocol.ts
  • src/backend/types/protocol.ts
**/server.ts

📄 CodeRabbit inference engine (AGENTS.md)

Preserve independent auth gating on both /ws/control and /ws/terminal WebSocket endpoints

Files:

  • src/backend/server.ts
**/tmux/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Use execFile with argument arrays in tmux command execution to prevent shell injection attacks

Files:

  • src/backend/tmux/cli-executor.ts
  • src/backend/tmux/parser.ts
🧠 Learnings (3)
📚 Learning: 2026-02-14T22:15:23.148Z
Learnt from: CR
Repo: DagsHub/tmux-mobile PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-14T22:15:23.148Z
Learning: Applies to **/server.ts : Preserve independent auth gating on both `/ws/control` and `/ws/terminal` WebSocket endpoints

Applied to files:

  • src/backend/server.ts
  • tests/integration/server.test.ts
  • src/frontend/App.tsx
📚 Learning: 2026-02-14T22:15:23.148Z
Learnt from: CR
Repo: DagsHub/tmux-mobile PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-14T22:15:23.148Z
Learning: Applies to **/tmux/**/*.ts : Use `execFile` with argument arrays in tmux command execution to prevent shell injection attacks

Applied to files:

  • src/backend/tmux/cli-executor.ts
📚 Learning: 2026-02-14T22:15:23.148Z
Learnt from: CR
Repo: DagsHub/tmux-mobile PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-14T22:15:23.148Z
Learning: Applies to **/pty/**/*.ts : Shell-quote session names in PTY spawn paths in node-pty-adapter.ts

Applied to files:

  • src/backend/tmux/cli-executor.ts
🧬 Code graph analysis (3)
tests/backend/parser.test.ts (1)
src/backend/tmux/parser.ts (1)
  • parseWindows (23-37)
src/backend/server.ts (4)
src/backend/state/state-monitor.ts (1)
  • TmuxStateMonitor (5-48)
src/backend/types/protocol.ts (1)
  • TmuxStateSnapshot (43-46)
src/frontend/types/protocol.ts (1)
  • TmuxStateSnapshot (29-32)
src/backend/util/random.ts (1)
  • randomToken (3-4)
tests/integration/server.test.ts (2)
tests/harness/ws.ts (2)
  • openSocket (34-38)
  • waitForMessage (9-32)
src/backend/tmux/types.ts (1)
  • buildSnapshot (27-47)
🔇 Additional comments (8)
src/backend/types/protocol.ts (1)

34-34: LGTM!

The zoomed: boolean addition to TmuxWindowState is clean. The field is correctly positioned and aligns with the frontend counterpart in src/frontend/types/protocol.ts (line 20).

src/frontend/types/protocol.ts (1)

20-20: LGTM!

The zoomed: boolean field is in sync with the backend TmuxWindowState definition. As per coding guidelines, "Keep protocol types in sync between backend (src/backend/types/protocol.ts) and frontend (src/frontend/types/protocol.ts)".

src/backend/tmux/cli-executor.ts (1)

10-11: LGTM!

The #{window_zoomed_flag} is correctly inserted between #{window_active} and #{window_panes}, matching the parser's destructuring order [index, name, active, zoomed, panes]. All tmux commands continue to use execFile with argument arrays. Based on learnings: "Use execFile with argument arrays in tmux command execution to prevent shell injection attacks".

src/frontend/App.tsx (1)

25-25: LGTM!

The clientId persistence flow is well-structured:

  • Stored in localStorage for cross-session persistence, read into a ref at startup.
  • Sent as undefined (not empty string) when absent via || undefined on line 273.
  • Updated from the server's auth_ok response, correctly deferring to the server as the source of truth for the canonical clientId.
  • Both control and terminal WebSocket endpoints continue to send independent auth messages.

Also applies to: 50-50, 268-275, 290-291

src/backend/tmux/parser.ts (1)

29-34: LGTM!

The destructuring now correctly includes zoomed at the expected position, matching the WINDOW_FMT column order in cli-executor.ts. The zoomed === "1" conversion is consistent with the existing active === "1" pattern.

tests/harness/fakeTmux.ts (1)

232-236: LGTM!

The zoomPane toggle correctly mimics tmux's resize-pane -Z behavior (toggle), and zoomed is properly initialized to false in buildDefaultSession and newWindow, propagated in listWindows and createGroupedSession.

tests/integration/server.test.ts (2)

30-47: LGTM — clientId propagation in authControl helper is correct.

JSON.stringify omits undefined fields, so existing callers that pass no clientId are unaffected.


286-288: No race condition exists. In attachControlToBaseSession (server.ts lines 272-273), the restore operations (selectPane, zoomPane) are awaited before sending the attached message. The code flow is:

  1. await tryRestoreClientView(context) completes (all tmux calls recorded)
  2. sendJson(context.socket, { type: "attached" }) sends
  3. Test receives attached and asserts on tmux.calls

The selectPane and zoomPane calls are guaranteed to be in tmux.calls when assertions execute. The assertions are correctly written and do not race.

Likely an incorrect or invalid review comment.

Comment thread src/backend/server.ts
Comment on lines +88 to +97
const normalizeClientId = (value: unknown): string | undefined => {
if (typeof value !== "string") {
return undefined;
}
const trimmed = value.trim();
if (!trimmed || trimmed.length > 128) {
return undefined;
}
return trimmed;
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

rg -n "buildMobileSessionName" --type=ts -C2
echo "---"
rg -n "MOBILE_SESSION_PREFIX" --type=ts -C2

Repository: DagsHub/tmux-mobile

Length of output: 1426


🏁 Script executed:

rg -n "clientId" --type=ts -C2 | head -80

Repository: DagsHub/tmux-mobile

Length of output: 5617


🏁 Script executed:

rg -n "normalizeClientId\|context\.clientId\s*=" --type=ts -C3

Repository: DagsHub/tmux-mobile

Length of output: 45


🏁 Script executed:

rg -n "normalizeClientId" --type=ts -C3
echo "---"
rg -n "context\.clientId\s*=" --type=ts -C3

Repository: DagsHub/tmux-mobile

Length of output: 1861


🏁 Script executed:

rg -n "randomToken" --type=ts -C5

Repository: DagsHub/tmux-mobile

Length of output: 4259


🏁 Script executed:

rg -n "mobileSession\|buildMobileSessionName" --type=ts -C5 | head -100

Repository: DagsHub/tmux-mobile

Length of output: 45


🏁 Script executed:

rg -n "const mobileSession\|mobileSession\s*=" --type=ts -C5

Repository: DagsHub/tmux-mobile

Length of output: 45


🏁 Script executed:

rg -n "mobileSession" --type=ts -C3

Repository: DagsHub/tmux-mobile

Length of output: 1651


🏁 Script executed:

cat -n src/backend/server.ts | sed -n '440,475p'

Repository: DagsHub/tmux-mobile

Length of output: 1839


🏁 Script executed:

cat -n src/backend/server.ts | sed -n '515,545p'
echo "---"
rg -n "createGroupedSession\|killSession\|attachToSession" --type=ts -C2

Repository: DagsHub/tmux-mobile

Length of output: 1295


🏁 Script executed:

fd "tmux" --type=f --extension=ts | grep -E "(tmux|gateway)" | head -20

Repository: DagsHub/tmux-mobile

Length of output: 92


🏁 Script executed:

find . -name "*.ts" -type f | xargs grep -l "createGroupedSession\|killSession" | head -10

Repository: DagsHub/tmux-mobile

Length of output: 244


🏁 Script executed:

cat -n src/backend/tmux/types.ts | head -50
echo "---"
rg -n "createGroupedSession" src/backend/tmux/cli-executor.ts -C5

Repository: DagsHub/tmux-mobile

Length of output: 2530


🏁 Script executed:

web_search
tmux session name character restrictions limitations

Repository: DagsHub/tmux-mobile

Length of output: 156


🌐 Web query:

tmux session name character restrictions . : separators

💡 Result:

In tmux, : and . are not “just characters” in targets—they’re syntax separators:

  • A target is parsed as session:window.pane (colon separates session/window, period separates window/pane). [1]
  • The man page describes the same parsing: target-window is session:window, and target-pane may add .pane. [2]

Practical impact on session-name characters

  • Avoid : in session names (and . in window names) if you want to reliably address them by name, because tmux will treat those characters as structural separators when you use -t. [1][2]
  • If you need arbitrary characters in names (including separators), the robust workaround is to target by IDs instead of names (session IDs look like $1, window IDs @1, pane IDs %1). [1][2]
  • Spaces are allowed in names, but then you must quote the target in your shell (otherwise it splits into multiple arguments). (This is shell parsing, not tmux.) [2]

Recommendation

Use session names like proj_api or proj-api (no :), and rely on session:window.pane only for addressing, not naming. [1][2]

Sources: [1] tmux wiki “Advanced Use / Command targets”; [2] tmux(1) manual (target syntax).


normalizeClientId allows characters that are invalid in tmux session targets.

The clientId is used in buildMobileSessionName to form a tmux session name (tmux-mobile-client-<clientId>), which is then passed to tmux commands via the -t (target) flag. Tmux interprets : and . as syntax separators in targets (session:window.pane), so if a clientId contains these characters, tmux will misparse the target, causing commands to fail or target the wrong session/window/pane.

While the server generates clientIds using base64url (safe characters), clients can provide their own clientId in the auth message. Restrict normalizeClientId to [a-zA-Z0-9_-] to prevent unsafe characters.

Comment on lines +289 to +292
} finally {
controlFirst.close();
controlSecond?.close();
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

controlFirst.close() is called twice — once inside the try block and again in the finally block.

This applies to both new tests (lines 280 + 290, and lines 313 + 328). controlFirst is unconditionally closed in the try block before reconnect, so the finally block should only guard controlSecond.

🔧 Proposed fix
-    } finally {
-      controlFirst.close();
-      controlSecond?.close();
-    }
+    } finally {
+      controlSecond?.close();
+    }

Apply the same change to both new tests.

Also applies to: 327-330

Comment on lines +322 to +326
const maybeError = await Promise.race([
waitForMessage<{ type: string; message?: string }>(controlSecond, (msg) => msg.type === "error"),
new Promise<null>((resolve) => setTimeout(() => resolve(null), 80))
]);
expect(maybeError).toBeNull();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Abandoned waitForMessage promise in Promise.race can produce an unhandled rejection ~3 seconds later.

When the 80ms branch wins the race, waitForMessage's internal 3000ms setTimeout is still live. It will later call reject(new Error("Timed out...")) on a promise that has no .catch() handler, triggering an unhandled rejection. In Vitest, this can fail a subsequently running test or emit an error ~3s after this test completes.

Use waitForMessage's built-in timeoutMs parameter and catch the timeout rejection directly — this eliminates the dangling promise entirely:

🔧 Proposed fix
-      const maybeError = await Promise.race([
-        waitForMessage<{ type: string; message?: string }>(controlSecond, (msg) => msg.type === "error"),
-        new Promise<null>((resolve) => setTimeout(() => resolve(null), 80))
-      ]);
+      const maybeError = await waitForMessage<{ type: string; message?: string }>(
+        controlSecond,
+        (msg) => msg.type === "error",
+        80
+      ).catch(() => null);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const maybeError = await Promise.race([
waitForMessage<{ type: string; message?: string }>(controlSecond, (msg) => msg.type === "error"),
new Promise<null>((resolve) => setTimeout(() => resolve(null), 80))
]);
expect(maybeError).toBeNull();
const maybeError = await waitForMessage<{ type: string; message?: string }>(
controlSecond,
(msg) => msg.type === "error",
80
).catch(() => null);
expect(maybeError).toBeNull();

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Reconnect should restore previous pane and zoom state

1 participant