Skip to content

fix(desktop): prevent xterm garbling via tRPC-first terminal sessions#3252

Merged
Kitenite merged 12 commits intomainfrom
xterm-rendering-changes-in-pane-v2
Apr 9, 2026
Merged

fix(desktop): prevent xterm garbling via tRPC-first terminal sessions#3252
Kitenite merged 12 commits intomainfrom
xterm-rendering-changes-in-pane-v2

Conversation

@Kitenite
Copy link
Copy Markdown
Collaborator

@Kitenite Kitenite commented Apr 8, 2026

Summary

  • Create terminal sessions via tRPC terminal.ensureSession before WebSocket connect
  • WebSocket URL is now stable (/terminal/{terminalId}?token=...) — no workspaceId/themeType in query params
  • Prevents spurious reconnects on workspace/tab switch that caused xterm rendering garbling via buffer replay
  • Remove unused reconnect() from terminal runtime registry
  • Revert transport connect() to simple URL string equality check
  • WS handler retains query-param fallback for v1 compatibility

Test plan

  • Open v2 workspace with multiple terminal tabs
  • Switch between tabs — no garbling
  • Switch between workspaces — no garbling
  • Verify WebSocket connect is skipped (idempotent) on re-mount
  • Verify stale effects are cancelled and don't attach

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 8, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Added a reconnect path for terminal websockets: TerminalRuntimeRegistryImpl.reconnect(terminalId, wsUrl) reuses existing runtime to update the transport connection; TerminalWSTransport.connect() compares origin + pathname (ignores query) for idempotency; TerminalPane now decouples DOM attach from URL updates and calls reconnect on URL changes.

Changes

Cohort / File(s) Summary
Registry Reconnection
apps/desktop/src/renderer/lib/terminal/terminal-runtime-registry.ts
Added public reconnect(terminalId, wsUrl) that looks up an existing runtime and calls connect(...) to re-establish the websocket using the existing transport and terminal instance without detaching the DOM.
Transport Idempotency
apps/desktop/src/renderer/lib/terminal/terminal-ws-transport.ts
connect() now treats transports as already-active when baseUrl(currentUrl) equals baseUrl(wsUrl) (compares origin + pathname, ignores query). Added baseUrl() helper and null-guard for transport.currentUrl; preserves latest query params when matching.
Terminal Pane Behavior
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/TerminalPane/TerminalPane.tsx
Introduced websocketUrlRef and used it for attach(...) to avoid remounts; changed attach/detach effect to depend only on terminalId; added an effect that calls terminalRuntimeRegistry.reconnect(terminalId, websocketUrl) when websocketUrl changes so transport updates in-place.
Docs
apps/desktop/docs/TERMINAL_WS_SESSION_MIGRATION.md
New documentation describing migration: create PTY session via tRPC before WebSocket, simplified WS URL (terminalId + token), updated reconnect and resize flow, and related renderer/server responsibilities.

Sequence Diagram(s)

sequenceDiagram
    participant TerminalPane
    participant Registry as TerminalRuntimeRegistry
    participant Transport as TerminalWS
    participant WebSocket

    Note over TerminalPane,WebSocket: Old flow (re-attach on URL change)
    TerminalPane->>Registry: detach(terminalId)
    Registry->>Transport: cleanup()
    Transport-->>WebSocket: close()
    TerminalPane->>Registry: attach(terminalId, newUrl)
    Registry->>Transport: connect(transport, terminal, newUrl)
    Transport->>WebSocket: new WebSocket(newUrl)
    WebSocket-->>Transport: open

    Note over TerminalPane,WebSocket: New flow (reconnect in-place)
    TerminalPane->>Registry: reconnect(terminalId, newUrl)
    Registry->>Transport: connect(existingTransport, existingTerminal, newUrl)
    Transport->>Transport: compare baseUrl(currentUrl) vs baseUrl(newUrl)
    alt origin+pathname match
        Transport-->>Registry: update currentUrl, return (no close)
    else different base
        Transport-->>WebSocket: close()
        Transport->>WebSocket: new WebSocket(newUrl)
        WebSocket-->>Transport: open
    end
    Registry-->>TerminalPane: done
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Poem

🐰 I nudged the socket, kept the pane,

Reused old threads to skip the strain.
A ref held fast while URLs moved near,
The DOM stayed put — the hops were clear.
🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title mentions preventing xterm garbling via tRPC-first terminal sessions, which aligns with the main objective of preventing spurious reconnects and rendering corruption described in the PR.
Description check ✅ Passed The PR description covers the main changes, rationale, and test plan. However, it is missing a formal link to related issues and does not use the template structure with Type of Change checkboxes.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch xterm-rendering-changes-in-pane-v2

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.

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 2 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/desktop/src/renderer/lib/terminal/terminal-addons.ts">

<violation number="1" location="apps/desktop/src/renderer/lib/terminal/terminal-addons.ts:60">
P2: Do not silently swallow `clearTextureAtlas` failures; log the error so reattach rendering issues remain diagnosable.

(Based on your team's feedback about avoiding silent catch blocks that hide runtime failures.) [FEEDBACK_USED]</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread apps/desktop/src/renderer/lib/terminal/terminal-addons.ts
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/desktop/src/renderer/lib/terminal/terminal-addons.ts`:
- Line 21: Update the JSDoc for the function loadAddons to reflect that it now
returns an AddonHandle instead of a simple cleanup function: locate the JSDoc
immediately above loadAddons(terminal: XTerm) and replace the description and
`@returns` tag to describe the AddonHandle type and its responsibilities (what
methods/properties callers can expect) rather than saying it returns a cleanup
function; ensure the text references the AddonHandle symbol and accurately
documents any cleanup method name on that handle if applicable.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6f01cac7-faa5-4923-a15c-c13db118739f

📥 Commits

Reviewing files that changed from the base of the PR and between ed7fb56 and f243b47.

📒 Files selected for processing (2)
  • apps/desktop/src/renderer/lib/terminal/terminal-addons.ts
  • apps/desktop/src/renderer/lib/terminal/terminal-runtime.ts

Comment thread apps/desktop/src/renderer/lib/terminal/terminal-addons.ts Outdated
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 8, 2026

Greptile encountered an error while reviewing this PR. Please reach out to support@greptile.com for assistance.

Kitenite added 2 commits April 8, 2026 13:09
Two fixes for terminal rendering corruption when switching workspaces:

1. TerminalPane: split attach/detach effect (deps: [terminalId] only)
   from reconnect effect (deps: [terminalId, websocketUrl]). This
   prevents DOM wrapper removal on workspace switches where only the
   URL query params change, preserving the WebGL context.

2. terminal-ws-transport: compare origin+pathname only in connect()
   idempotency check. Query params (workspaceId, token) change during
   workspace switches without meaning the terminal session changed.
   Full URL comparison caused spurious socket reconnects that triggered
   replay messages and garbled the terminal buffer.
@Kitenite Kitenite force-pushed the xterm-rendering-changes-in-pane-v2 branch from f243b47 to 42b1207 Compare April 8, 2026 20:25
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/desktop/src/renderer/lib/terminal/terminal-ws-transport.ts`:
- Around line 106-111: The early return in the isActive check prevents updating
transport.currentUrl when only the query portion of wsUrl changes (e.g.,
refreshed token from useWorkspaceWsUrl()), so scheduleReconnect() later retries
the old URL; modify the logic to still update transport.currentUrl to the new
wsUrl whenever baseUrl(transport.currentUrl) === baseUrl(wsUrl) but the full
wsUrl differs (or introduce a separate reconnectTarget field) before returning
so future scheduleReconnect() uses the refreshed URL; touch symbols:
transport.currentUrl, baseUrl(...), wsUrl, isActive, scheduleReconnect(),
useWorkspaceWsUrl().
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0f8538e9-d8aa-4d47-8e34-bfaf2b11cd0f

📥 Commits

Reviewing files that changed from the base of the PR and between f243b47 and 42b1207.

📒 Files selected for processing (3)
  • apps/desktop/src/renderer/lib/terminal/terminal-runtime-registry.ts
  • apps/desktop/src/renderer/lib/terminal/terminal-ws-transport.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/TerminalPane/TerminalPane.tsx

Comment thread apps/desktop/src/renderer/lib/terminal/terminal-ws-transport.ts Outdated
Kitenite added 3 commits April 8, 2026 13:40
…econnect

When connect() skips reconnection (same base URL), still update
transport.currentUrl to the new full URL so that scheduleReconnect
uses the latest token/query params if the socket later drops.
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 2 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/desktop/src/renderer/lib/terminal/terminal-ws-transport.ts">

<violation number="1" location="apps/desktop/src/renderer/lib/terminal/terminal-ws-transport.ts:110">
P1: `currentUrl` is no longer updated on same-endpoint active connections, so auto-reconnect may use stale query/token values.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread apps/desktop/src/renderer/lib/terminal/terminal-ws-transport.ts Outdated
Create session via tRPC first, then connect WebSocket as a data pipe.
Eliminates query param instability that causes rendering garbling.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
apps/desktop/docs/TERMINAL_WS_SESSION_MIGRATION.md (2)

3-6: Clarify the relationship between the current PR and this migration plan.

The statement "PR #3252 works around this" may confuse readers since this document is part of PR #3252. Consider adding a sentence explicitly stating that this document describes a future migration plan, and that the current PR implements a temporary workaround (the websocketUrlRef, separate reconnect effect, and clearTextureAtlas call) that will be replaced by the tRPC-based approach described here.

📝 Suggested clarification
 ## Problem
 
-WebSocket URL includes `workspaceId`, `themeType` as query params. When workspace context changes, the URL changes, causing spurious reconnects that garble rendering. PR `#3252` works around this.
+WebSocket URL includes `workspaceId`, `themeType` as query params. When workspace context changes, the URL changes, causing spurious reconnects that garble rendering. 
+
+**Current state:** PR `#3252` implements a workaround by keeping TerminalPane mounted and reconnecting only the transport layer.
+
+**Future fix (this doc):** Migrate to tRPC-based session creation to eliminate query-param instability entirely.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/docs/TERMINAL_WS_SESSION_MIGRATION.md` around lines 3 - 6,
Update the migration doc to explicitly state that it outlines a future migration
plan and that PR `#3252` contains a temporary workaround: add one clear sentence
near the top saying this document describes the intended tRPC-based migration
while the current PR implements a stopgap using websocketUrlRef, a separate
reconnect effect, and a clearTextureAtlas call, and that those temporary pieces
will be removed when the tRPC approach is implemented.

13-19: Add language identifier to the code block.

The fenced code block should specify a language for proper syntax highlighting and rendering.

📝 Proposed fix
 ### Flow
 
-```
+```text
 1. Client generates terminalId (crypto.randomUUID) — already done in pane data
 2. tRPC: terminal.createSession({ terminalId, workspaceId, themeType, cols, rows })
    → host-service creates PTY, returns { status, scrollback? }
 3. WebSocket: ws://host/terminal/{terminalId}?token=Z
    → attaches to existing session, forwards data

</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against the current code and only fix it if needed.

In @apps/desktop/docs/TERMINAL_WS_SESSION_MIGRATION.md around lines 13 - 19, The
fenced code block that begins with "1. Client generates terminalId
(crypto.randomUUID) — already done in pane data" lacks a language identifier;
fix it by adding a language hint after the opening (for exampletext) to
the block containing the three numbered steps so the renderer applies proper
syntax highlighting.


</details>

</blockquote></details>

</blockquote></details>

<details>
<summary>🤖 Prompt for all review comments with AI agents</summary>

Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In @apps/desktop/docs/TERMINAL_WS_SESSION_MIGRATION.md:

  • Around line 3-6: Update the migration doc to explicitly state that it outlines
    a future migration plan and that PR #3252 contains a temporary workaround: add
    one clear sentence near the top saying this document describes the intended
    tRPC-based migration while the current PR implements a stopgap using
    websocketUrlRef, a separate reconnect effect, and a clearTextureAtlas call, and
    that those temporary pieces will be removed when the tRPC approach is
    implemented.
  • Around line 13-19: The fenced code block that begins with "1. Client generates
    terminalId (crypto.randomUUID) — already done in pane data" lacks a language
    identifier; fix it by adding a language hint after the opening ``` (for example
applies proper syntax highlighting.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 970b91b5-7ec8-45ec-bb4e-d1c884bc3ca3

📥 Commits

Reviewing files that changed from the base of the PR and between fc07f9d and 4d3ffbe.

📒 Files selected for processing (1)
  • apps/desktop/docs/TERMINAL_WS_SESSION_MIGRATION.md

…nect

Move terminal session creation from WebSocket query params to a dedicated
tRPC `terminal.ensureSession` mutation. This makes the WebSocket URL
stable across workspace switches (no workspaceId/themeType in query
params), preventing spurious reconnects that caused xterm rendering
garbling via buffer replay.

- Add `terminal.ensureSession` tRPC mutation in host-service
- TerminalPane calls ensureSession before attaching WebSocket
- Revert transport `connect()` to simple URL string equality
- Remove unused `reconnect()` from terminal runtime registry
- WS handler retains query-param fallback for v1 compatibility
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 7 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/host-service/src/trpc/router/terminal/terminal.ts">

<violation number="1" location="packages/host-service/src/trpc/router/terminal/terminal.ts:15">
P3: `cols` and `rows` are added to the tRPC input but never used, which creates misleading dead API surface.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread packages/host-service/src/trpc/router/terminal/terminal.ts Outdated
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 9, 2026

🚀 Preview Deployment

🔗 Preview Links

Service Status Link
Neon Database (Neon) View Branch
Fly.io Electric (Fly.io) View App
Vercel API (Vercel) Open Preview
Vercel Web (Vercel) Open Preview
Vercel Marketing (Vercel) Open Preview
Vercel Admin (Vercel) Open Preview
Vercel Docs (Vercel) Open Preview

Preview updates automatically with new commits

@Kitenite Kitenite changed the title fix(desktop): clear WebGL texture atlas on terminal pane reattach fix(desktop): prevent xterm garbling via tRPC-first terminal sessions Apr 9, 2026
@Kitenite Kitenite merged commit 54e8a1c into main Apr 9, 2026
14 checks passed
@Kitenite Kitenite deleted the xterm-rendering-changes-in-pane-v2 branch April 9, 2026 00:47
MocA-Love added a commit to MocA-Love/superset that referenced this pull request Apr 10, 2026
…perset-sh#3252)

Cherry-picked from upstream 54e8a1c (fix(desktop): prevent xterm garbling
via tRPC-first terminal sessions). Only the terminal-related portion was
taken; the quit lifecycle changes (setSkipQuitConfirmation / quitApp in
main/index.ts and auto-updater.ts) were deliberately excluded because they
collide with fork's independent requestQuit(mode: QuitMode) / GitHub API
based auto-updater implementation.

Changes:
- Add terminal.ensureSession tRPC mutation in host-service
- TerminalPane calls ensureSession before attaching WebSocket
- WebSocket URL becomes stable (no workspaceId/themeType query params),
  preventing spurious reconnects that caused xterm rendering garbling on
  workspace switches
- host-service WS handler retains query-param fallback for backwards
  compatibility with v1 callers

Fork-specific decisions:
- main/index.ts and auto-updater.ts intentionally left untouched
- setSkipQuitConfirmation() helper NOT added to fork (fork uses
  prepareQuit('stop') instead)
hyrness added a commit to hyrness/superset that referenced this pull request Apr 15, 2026
…context loss

After the ensureSession change (superset-sh#3252), terminals can receive data
(including wide/CJK characters) via the WebSocket before the WebGL
renderer initializes (deferred to rAF). When WebGL takes over, the
pre-rendered cells are not correctly re-rendered, causing intermittent
garbling that fixes on mouse selection.

Two fixes:
1. Call terminal.refresh() after loading the WebGL addon to force a
   full repaint of all buffered cells with the WebGL renderer.
2. Set suggestedRendererType = "dom" in the onContextLoss handler so
   that subsequent terminals in the same session skip WebGL after a
   GPU context loss.

Fixes superset-sh#3406
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.

1 participant