Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion apps/desktop/electron.vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,11 @@ export default defineConfig({
},
},
resolve: {
alias: {},
alias: {
// @xterm/headless 6.0.0 has a packaging bug: `module` field points to
// non-existent `lib/xterm.mjs`. Force Vite to use the CJS entry instead.
"@xterm/headless": "@xterm/headless/lib-headless/xterm-headless.js",
},
},
},

Expand Down
22 changes: 10 additions & 12 deletions apps/desktop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -96,18 +96,16 @@
"@trpc/server": "^11.7.1",
"@types/express": "^5.0.5",
"@vercel/blob": "^2.0.0",
"@xterm/addon-canvas": "^0.7.0",
"@xterm/addon-clipboard": "^0.1.0",
"@xterm/addon-fit": "^0.10.0",
"@xterm/addon-image": "^0.8.0",
"@xterm/addon-ligatures": "^0.9.0",
"@xterm/addon-search": "^0.15.0",
"@xterm/addon-serialize": "^0.13.0",
"@xterm/addon-unicode11": "^0.8.0",
"@xterm/addon-web-links": "^0.11.0",
"@xterm/addon-webgl": "^0.18.0",
"@xterm/headless": "^5.5.0",
"@xterm/xterm": "^5.5.0",
"@xterm/addon-clipboard": "0.3.0-beta.109",
"@xterm/addon-fit": "0.12.0-beta.109",
"@xterm/addon-image": "0.10.0-beta.109",
"@xterm/addon-ligatures": "0.11.0-beta.109",
"@xterm/addon-search": "0.17.0-beta.109",
"@xterm/addon-serialize": "0.15.0-beta.109",
"@xterm/addon-unicode11": "0.10.0-beta.109",
"@xterm/addon-webgl": "0.20.0-beta.109",
"@xterm/headless": "6.1.0-beta.109",
"@xterm/xterm": "6.1.0-beta.109",
"better-auth": "1.4.17",
"better-sqlite3": "12.6.2",
"bindings": "^1.5.0",
Expand Down
15 changes: 5 additions & 10 deletions apps/desktop/src/renderer/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -167,19 +167,14 @@
min-height: 100vh;
}

/* Ensure xterm terminal fills container height */
.xterm {
/* xterm 6.0.0+ requires explicit sizing due to VS Code scrollbar integration */
.xterm,
.xterm .xterm-scrollable-element,
.xterm .xterm-viewport,
.xterm .xterm-screen {
height: 100%;
width: 100%;
}

.xterm-screen {
height: 100%;
width: 100%;
}

/* Style links in terminal (like iTerm) */
/* xterm uses underline-5 (dashed) for links on hover */
.xterm .xterm-rows a {
color: inherit;
cursor: pointer;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,11 @@ export function ScrollToBottomButton({ terminal }: ScrollToBottomButtonProps) {
checkScrollPosition();

const writeDisposable = terminal.onWriteParsed(checkScrollPosition);
const viewport = terminal.element?.querySelector(".xterm-viewport");

if (viewport) {
viewport.addEventListener("scroll", checkScrollPosition);
}
const scrollDisposable = terminal.onScroll(checkScrollPosition);

return () => {
writeDisposable.dispose();
viewport?.removeEventListener("scroll", checkScrollPosition);
scrollDisposable.dispose();
};
}, [terminal, checkScrollPosition]);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,6 @@ export const TERMINAL_OPTIONS: ITerminalOptions = {
macOptionIsMeta: false,
cursorStyle: "block",
cursorInactiveStyle: "outline",
fastScrollModifier: "alt",
fastScrollSensitivity: 5,
screenReaderMode: false,
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { toast } from "@superset/ui/sonner";
import { CanvasAddon } from "@xterm/addon-canvas";
import { ClipboardAddon } from "@xterm/addon-clipboard";
import { FitAddon } from "@xterm/addon-fit";
import { ImageAddon } from "@xterm/addon-image";
Expand Down Expand Up @@ -61,33 +60,48 @@ export function getDefaultTerminalBg(): string {

/**
* Load GPU-accelerated renderer with automatic fallback.
* Tries WebGL first, falls back to Canvas if WebGL fails.
* Tries WebGL first, falls back to DOM if WebGL fails.
* This follows VS Code's approach: WebGL → DOM (canvas addon removed in xterm.js 6.0).
*/
export type TerminalRenderer = {
kind: "webgl" | "canvas" | "dom";
kind: "webgl" | "dom";
dispose: () => void;
clearTextureAtlas?: () => void;
};

type PreferredRenderer = TerminalRenderer["kind"] | "auto";

// Track WebGL failures globally to avoid repeated initialization attempts (VS Code pattern)
let suggestedRendererType: TerminalRenderer["kind"] | undefined;

function getPreferredRenderer(): PreferredRenderer {
// If WebGL previously failed, don't try again
if (suggestedRendererType === "dom") {
return "dom";
}

try {
const stored = localStorage.getItem("terminal-renderer");
if (stored === "webgl" || stored === "canvas" || stored === "dom") {
if (stored === "webgl" || stored === "dom") {
return stored;
}
if (stored === "canvas") {
// Canvas renderer was removed in xterm.js 6.0; fall back to DOM.
try {
localStorage.setItem("terminal-renderer", "dom");
} catch {
// ignore storage errors
}
return "dom";
}
} catch {
// ignore
}

// Default: avoid xterm-webgl on macOS. We've seen repeated corruption/glitching
// when terminals are hidden/shown or switched between panes.
return navigator.userAgent.includes("Macintosh") ? "canvas" : "webgl";
return "auto";
}

function loadRenderer(xterm: XTerm): TerminalRenderer {
let renderer: WebglAddon | CanvasAddon | null = null;
let webglAddon: WebglAddon | null = null;
let kind: TerminalRenderer["kind"] = "dom";

Expand All @@ -97,54 +111,35 @@ function loadRenderer(xterm: XTerm): TerminalRenderer {
return { kind: "dom", dispose: () => {}, clearTextureAtlas: undefined };
}

const tryLoadCanvas = () => {
try {
renderer = new CanvasAddon();
xterm.loadAddon(renderer);
kind = "canvas";
} catch {
// Canvas fallback failed, use default renderer
}
};

if (preferred === "canvas") {
tryLoadCanvas();
return {
kind,
dispose: () => renderer?.dispose(),
clearTextureAtlas: undefined,
};
}

try {
webglAddon = new WebglAddon();

webglAddon.onContextLoss(() => {
console.warn(
"[Terminal] WebGL context lost, falling back to DOM renderer",
);
webglAddon?.dispose();
webglAddon = null;
try {
renderer = new CanvasAddon();
xterm.loadAddon(renderer);
kind = "canvas";
// Force refresh after context loss recovery
xterm.refresh(0, xterm.rows - 1);
} catch {
// Canvas fallback failed, use default renderer
renderer = null;
kind = "dom";
}
kind = "dom";
// Force refresh after context loss
xterm.refresh(0, xterm.rows - 1);
});

xterm.loadAddon(webglAddon);
renderer = webglAddon;
kind = "webgl";
} catch {
tryLoadCanvas();
} catch (e) {
console.warn(
"[Terminal] WebGL could not be loaded, falling back to DOM renderer",
e,
);
suggestedRendererType = "dom";
webglAddon = null;
kind = "dom";
}

return {
kind,
dispose: () => renderer?.dispose(),
dispose: () => webglAddon?.dispose(),
clearTextureAtlas: webglAddon
? () => {
try {
Expand Down Expand Up @@ -216,11 +211,11 @@ export function createTerminalInstance(

// Defer GPU renderer loading to next animation frame.
// xterm.open() schedules a setTimeout for Viewport.syncScrollArea which expects
// the renderer to be ready. Loading WebGL/Canvas immediately after open() can
// cause a race condition where the setTimeout fires during addon initialization,
// when _renderer is temporarily undefined (old renderer disposed, new not yet set).
// the renderer to be ready. Loading WebGL immediately after open() can cause a
// race condition where the setTimeout fires during addon initialization, when
// _renderer is temporarily undefined (old renderer disposed, new not yet set).
// Deferring to rAF ensures xterm's internal setTimeout completes first with the
// default DOM renderer, then we safely swap to WebGL/Canvas.
// default DOM renderer, then we safely swap to WebGL.
rafId = requestAnimationFrame(() => {
rafId = null;
if (isDisposed) return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,6 @@ export function shellEscapePaths(paths: string[]): string {
return quote(paths);
}

export function scrollToBottom(
terminal: Terminal,
behavior: ScrollBehavior = "instant",
): void {
const viewport = terminal.element?.querySelector(".xterm-viewport");
if (viewport) {
viewport.scrollTo({
top: viewport.scrollHeight,
behavior,
});
} else {
terminal.scrollToBottom();
}
export function scrollToBottom(terminal: Terminal): void {
terminal.scrollToBottom();
}
Original file line number Diff line number Diff line change
@@ -1,10 +1 @@
import type { FileTreeNode } from "shared/file-tree-types";

export type OnFileOpen = (node: FileTreeNode) => void;

export type NewItemMode = "file" | "folder" | null;

export interface TreeActionResult {
success: boolean;
error?: string;
}
56 changes: 22 additions & 34 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading