From 15ba683995b46280fb04ca5a6bbcb0e551472463 Mon Sep 17 00:00:00 2001 From: MocA-Love Date: Sun, 5 Apr 2026 07:43:25 +0900 Subject: [PATCH 01/11] cherry-pick: Patch vuln (#3120) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit upstream 538f3061 — marketing/package.json脆弱性パッチ --- apps/marketing/package.json | 2 +- bun.lock | 12 +++--------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/apps/marketing/package.json b/apps/marketing/package.json index fc4221af181..53d6ea3e2c4 100644 --- a/apps/marketing/package.json +++ b/apps/marketing/package.json @@ -25,7 +25,7 @@ "import-in-the-middle": "2.0.1", "lucide-react": "^0.563.0", "next": "^16.0.10", - "next-mdx-remote": "^5.0.0", + "next-mdx-remote": "^6.0.0", "next-themes": "^0.4.6", "posthog-js": "1.310.1", "react": "19.2.0", diff --git a/bun.lock b/bun.lock index 1097a96863a..4a43a555ca8 100644 --- a/bun.lock +++ b/bun.lock @@ -425,7 +425,7 @@ "import-in-the-middle": "2.0.1", "lucide-react": "^0.563.0", "next": "^16.0.10", - "next-mdx-remote": "^5.0.0", + "next-mdx-remote": "^6.0.0", "next-themes": "^0.4.6", "posthog-js": "1.310.1", "react": "19.2.0", @@ -4684,7 +4684,7 @@ "next": ["next@16.2.1", "", { "dependencies": { "@next/env": "16.2.1", "@swc/helpers": "0.5.15", "baseline-browser-mapping": "^2.9.19", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "16.2.1", "@next/swc-darwin-x64": "16.2.1", "@next/swc-linux-arm64-gnu": "16.2.1", "@next/swc-linux-arm64-musl": "16.2.1", "@next/swc-linux-x64-gnu": "16.2.1", "@next/swc-linux-x64-musl": "16.2.1", "@next/swc-win32-arm64-msvc": "16.2.1", "@next/swc-win32-x64-msvc": "16.2.1", "sharp": "^0.34.5" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-VaChzNL7o9rbfdt60HUj8tev4m6d7iC1igAy157526+cJlXOQu5LzsBXNT+xaJnTP/k+utSX5vMv7m0G+zKH+Q=="], - "next-mdx-remote": ["next-mdx-remote@5.0.0", "", { "dependencies": { "@babel/code-frame": "^7.23.5", "@mdx-js/mdx": "^3.0.1", "@mdx-js/react": "^3.0.1", "unist-util-remove": "^3.1.0", "vfile": "^6.0.1", "vfile-matter": "^5.0.0" }, "peerDependencies": { "react": ">=16" } }, "sha512-RNNbqRpK9/dcIFZs/esQhuLA8jANqlH694yqoDBK8hkVdJUndzzGmnPHa2nyi90N4Z9VmzuSWNRpr5ItT3M7xQ=="], + "next-mdx-remote": ["next-mdx-remote@6.0.0", "", { "dependencies": { "@babel/code-frame": "^7.23.5", "@mdx-js/mdx": "^3.0.1", "@mdx-js/react": "^3.0.1", "unist-util-remove": "^4.0.0", "unist-util-visit": "^5.1.0", "vfile": "^6.0.1", "vfile-matter": "^5.0.0" }, "peerDependencies": { "react": ">=16" } }, "sha512-cJEpEZlgD6xGjB4jL8BnI8FaYdN9BzZM4NwadPe1YQr7pqoWjg9EBCMv3nXBkuHqMRfv2y33SzUsuyNh9LFAQQ=="], "next-themes": ["next-themes@0.4.6", "", { "peerDependencies": { "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc" } }, "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA=="], @@ -5678,7 +5678,7 @@ "unist-util-position-from-estree": ["unist-util-position-from-estree@2.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ=="], - "unist-util-remove": ["unist-util-remove@3.1.1", "", { "dependencies": { "@types/unist": "^2.0.0", "unist-util-is": "^5.0.0", "unist-util-visit-parents": "^5.0.0" } }, "sha512-kfCqZK5YVY5yEa89tvpl7KnBBHu2c6CzMkqHUrlOqaRgGOMp0sMvwWOVrbAtj03KhovQB7i96Gda72v/EFE0vw=="], + "unist-util-remove": ["unist-util-remove@4.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-b4gokeGId57UVRX/eVKej5gXqGlc9+trkORhFJpu9raqZkZhU0zm8Doi05+HaiBsMEIJowL+2WtQ5ItjsngPXg=="], "unist-util-remove-position": ["unist-util-remove-position@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-visit": "^5.0.0" } }, "sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q=="], @@ -6744,12 +6744,6 @@ "tunnel-rat/zustand": ["zustand@4.5.7", "", { "dependencies": { "use-sync-external-store": "^1.2.2" }, "peerDependencies": { "@types/react": ">=16.8", "immer": ">=9.0.6", "react": ">=16.8" }, "optionalPeers": ["@types/react", "immer", "react"] }, "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw=="], - "unist-util-remove/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="], - - "unist-util-remove/unist-util-is": ["unist-util-is@5.2.1", "", { "dependencies": { "@types/unist": "^2.0.0" } }, "sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw=="], - - "unist-util-remove/unist-util-visit-parents": ["unist-util-visit-parents@5.1.3", "", { "dependencies": { "@types/unist": "^2.0.0", "unist-util-is": "^5.0.0" } }, "sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg=="], - "uniwind/@tailwindcss/node": ["@tailwindcss/node@4.2.1", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.19.0", "jiti": "^2.6.1", "lightningcss": "1.31.1", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.2.1" } }, "sha512-jlx6sLk4EOwO6hHe1oCGm1Q4AN/s0rSrTTPBGPM0/RQ6Uylwq17FuU8IeJJKEjtc6K6O07zsvP+gDO6MMWo7pg=="], "uniwind/@tailwindcss/oxide": ["@tailwindcss/oxide@4.2.1", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.2.1", "@tailwindcss/oxide-darwin-arm64": "4.2.1", "@tailwindcss/oxide-darwin-x64": "4.2.1", "@tailwindcss/oxide-freebsd-x64": "4.2.1", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.1", "@tailwindcss/oxide-linux-arm64-gnu": "4.2.1", "@tailwindcss/oxide-linux-arm64-musl": "4.2.1", "@tailwindcss/oxide-linux-x64-gnu": "4.2.1", "@tailwindcss/oxide-linux-x64-musl": "4.2.1", "@tailwindcss/oxide-wasm32-wasi": "4.2.1", "@tailwindcss/oxide-win32-arm64-msvc": "4.2.1", "@tailwindcss/oxide-win32-x64-msvc": "4.2.1" } }, "sha512-yv9jeEFWnjKCI6/T3Oq50yQEOqmpmpfzG1hcZsAOaXFQPfzWprWrlHSdGPEF3WQTi8zu8ohC9Mh9J470nT5pUw=="], From b8bab3d0654a0b375653cca636ddc8360254ce49 Mon Sep 17 00:00:00 2001 From: Kiet <31864905+Kitenite@users.noreply.github.com> Date: Thu, 2 Apr 2026 16:09:01 -0700 Subject: [PATCH 02/11] fix(desktop): remove PreToolUse/PostToolUse hooks from Codex integration (#3121) These per-tool hooks add overhead without providing value for Codex. Only SessionStart, UserPromptSubmit, and Stop are needed. --- apps/desktop/docs/EXTERNAL_FILES.md | 2 +- .../agent-wrappers-claude-codex-opencode.ts | 21 +---- .../lib/agent-setup/agent-wrappers.test.ts | 85 ++++--------------- 3 files changed, 19 insertions(+), 89 deletions(-) diff --git a/apps/desktop/docs/EXTERNAL_FILES.md b/apps/desktop/docs/EXTERNAL_FILES.md index abcc47f5bb8..5cc01ad97cb 100644 --- a/apps/desktop/docs/EXTERNAL_FILES.md +++ b/apps/desktop/docs/EXTERNAL_FILES.md @@ -42,7 +42,7 @@ its hook entries into these files while preserving user-defined entries: | File | Purpose | |------|---------| | `~/.claude/settings.json` | Claude Code hook registration merge | -| `~/.codex/hooks.json` | Codex hook registration merge (`SessionStart`, `UserPromptSubmit`, `PreToolUse`, `PostToolUse`, `Stop`) | +| `~/.codex/hooks.json` | Codex hook registration merge (`SessionStart`, `UserPromptSubmit`, `Stop`) | | `~/.factory/settings.json` | Factory Droid hook registration (`UserPromptSubmit`, `Notification`, `PostToolUse`, `Stop`) | For Codex specifically, Superset now relies on native `~/.codex/hooks.json` diff --git a/apps/desktop/src/main/lib/agent-setup/agent-wrappers-claude-codex-opencode.ts b/apps/desktop/src/main/lib/agent-setup/agent-wrappers-claude-codex-opencode.ts index e3378f804f9..db889900cd5 100644 --- a/apps/desktop/src/main/lib/agent-setup/agent-wrappers-claude-codex-opencode.ts +++ b/apps/desktop/src/main/lib/agent-setup/agent-wrappers-claude-codex-opencode.ts @@ -385,12 +385,7 @@ export function getCodexGlobalHooksJsonContent( } const managedEvents: Array<{ - eventName: - | "SessionStart" - | "UserPromptSubmit" - | "PreToolUse" - | "PostToolUse" - | "Stop"; + eventName: "SessionStart" | "UserPromptSubmit" | "Stop"; definition: ClaudeHookDefinition; }> = [ { @@ -405,20 +400,6 @@ export function getCodexGlobalHooksJsonContent( hooks: [{ type: "command", command: notifyScriptPath }], }, }, - { - eventName: "PreToolUse", - definition: { - matcher: "*", - hooks: [{ type: "command", command: notifyScriptPath }], - }, - }, - { - eventName: "PostToolUse", - definition: { - matcher: "*", - hooks: [{ type: "command", command: notifyScriptPath }], - }, - }, { eventName: "Stop", definition: { diff --git a/apps/desktop/src/main/lib/agent-setup/agent-wrappers.test.ts b/apps/desktop/src/main/lib/agent-setup/agent-wrappers.test.ts index 9c968c4c7b6..9e96a3e4912 100644 --- a/apps/desktop/src/main/lib/agent-setup/agent-wrappers.test.ts +++ b/apps/desktop/src/main/lib/agent-setup/agent-wrappers.test.ts @@ -888,7 +888,7 @@ describe("agent-wrappers codex hooks.json", () => { rmSync(TEST_ROOT, { recursive: true, force: true }); }); - it("creates Codex hooks.json with prompt and tool lifecycle hooks when no file exists", () => { + it("creates Codex hooks.json with prompt and lifecycle hooks when no file exists", () => { const notifyPath = "/tmp/.superset/hooks/notify.sh"; const content = getCodexGlobalHooksJsonContent(notifyPath); expect(content).not.toBeNull(); @@ -907,8 +907,6 @@ describe("agent-wrappers codex hooks.json", () => { for (const eventName of [ "SessionStart", "UserPromptSubmit", - "PreToolUse", - "PostToolUse", "Stop", ] as const) { const hooks = parsed.hooks[eventName]; @@ -920,12 +918,8 @@ describe("agent-wrappers codex hooks.json", () => { ).toBe(true); } - expect(parsed.hooks.PreToolUse?.every((def) => def.matcher === "*")).toBe( - true, - ); - expect(parsed.hooks.PostToolUse?.every((def) => def.matcher === "*")).toBe( - true, - ); + expect(parsed.hooks.PreToolUse).toBeUndefined(); + expect(parsed.hooks.PostToolUse).toBeUndefined(); }); it("preserves user hooks when merging", () => { @@ -987,7 +981,7 @@ describe("agent-wrappers codex hooks.json", () => { const parsed = JSON.parse(content); - // Preserves user hook + // Preserves user hooks (including PreToolUse/PostToolUse which we don't manage) expect( parsed.hooks.Stop.some((def: { hooks: Array<{ command: string }> }) => def.hooks.some( @@ -1024,32 +1018,19 @@ describe("agent-wrappers codex hooks.json", () => { ), ).toBe(true); - // Adds managed hook - expect( - parsed.hooks.Stop.some((def: { hooks: Array<{ command: string }> }) => - def.hooks.some( - (hook: { command: string }) => hook.command === notifyPath, + // Adds managed hooks for SessionStart, UserPromptSubmit, Stop + for (const eventName of ["SessionStart", "UserPromptSubmit", "Stop"]) { + expect( + parsed.hooks[eventName].some( + (def: { hooks: Array<{ command: string }> }) => + def.hooks.some( + (hook: { command: string }) => hook.command === notifyPath, + ), ), - ), - ).toBe(true); + ).toBe(true); + } - // Also creates prompt + start hooks - expect( - parsed.hooks.SessionStart.some( - (def: { hooks: Array<{ command: string }> }) => - def.hooks.some( - (hook: { command: string }) => hook.command === notifyPath, - ), - ), - ).toBe(true); - expect( - parsed.hooks.UserPromptSubmit.some( - (def: { hooks: Array<{ command: string }> }) => - def.hooks.some( - (hook: { command: string }) => hook.command === notifyPath, - ), - ), - ).toBe(true); + // Does NOT inject managed hooks for PreToolUse/PostToolUse expect( parsed.hooks.PreToolUse.some( (def: { hooks: Array<{ command: string }> }) => @@ -1057,7 +1038,7 @@ describe("agent-wrappers codex hooks.json", () => { (hook: { command: string }) => hook.command === notifyPath, ), ), - ).toBe(true); + ).toBe(false); expect( parsed.hooks.PostToolUse.some( (def: { hooks: Array<{ command: string }> }) => @@ -1065,37 +1046,7 @@ describe("agent-wrappers codex hooks.json", () => { (hook: { command: string }) => hook.command === notifyPath, ), ), - ).toBe(true); - }); - - it("adds UserPromptSubmit, PreToolUse, and PostToolUse to the Codex hooks.json merge", () => { - const notifyPath = "/tmp/.superset/hooks/notify.sh"; - const content = getCodexGlobalHooksJsonContent(notifyPath); - expect(content).not.toBeNull(); - if (content === null) throw new Error("Expected content"); - - const parsed = JSON.parse(content) as { - hooks: Record< - string, - Array<{ - matcher?: string; - hooks: Array<{ type: string; command: string }>; - }> - >; - }; - - for (const eventName of [ - "UserPromptSubmit", - "PreToolUse", - "PostToolUse", - ] as const) { - expect(parsed.hooks[eventName]).toBeDefined(); - expect( - parsed.hooks[eventName]?.some((def) => - def.hooks.some((hook) => hook.command === notifyPath), - ), - ).toBe(true); - } + ).toBe(false); }); it("replaces stale Codex hook commands from old superset paths", () => { @@ -1150,8 +1101,6 @@ describe("agent-wrappers codex hooks.json", () => { for (const eventName of [ "SessionStart", "UserPromptSubmit", - "PreToolUse", - "PostToolUse", "Stop", ] as const) { const hooks = parsed.hooks[eventName]; From 58b5ba2c865361f6d1ee40e7c5020cedd8309a77 Mon Sep 17 00:00:00 2001 From: Kiet <31864905+Kitenite@users.noreply.github.com> Date: Thu, 2 Apr 2026 19:00:43 -0700 Subject: [PATCH 03/11] =?UTF-8?q?Revert=20"fix:=20solve=20#3028=20?= =?UTF-8?q?=E2=80=94=20forward=20DA1=20query=20responses=20during=20shell?= =?UTF-8?q?=20init=20(#3030)"=20(#3127)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 31945688656912a1063e989780bd7484fbfa5a47. --- .../terminal-host/session-shell-ready.test.ts | 51 ------------------- .../desktop/src/main/terminal-host/session.ts | 15 +++--- 2 files changed, 6 insertions(+), 60 deletions(-) diff --git a/apps/desktop/src/main/terminal-host/session-shell-ready.test.ts b/apps/desktop/src/main/terminal-host/session-shell-ready.test.ts index a2ad32fabbb..791ef23e10e 100644 --- a/apps/desktop/src/main/terminal-host/session-shell-ready.test.ts +++ b/apps/desktop/src/main/terminal-host/session-shell-ready.test.ts @@ -301,57 +301,6 @@ describe("Session shell-ready: kill/exit before readiness", () => { }); }); -/** Wait for the emulator write queue to drain (uses setImmediate internally). */ -function waitForEmulatorFlush(): Promise { - return new Promise((resolve) => setTimeout(resolve, 50)); -} - -describe("Session shell-ready: DA1 query response forwarding (#3028)", () => { - it("forwards headless emulator DA1 response to subprocess during pending state", async () => { - // Fish shell sends DA1 (ESC[c) at startup to detect terminal capabilities. - // The headless emulator generates a response (ESC[?1;2c) via xterm.js. - // This response MUST be forwarded to the subprocess even during the - // "pending" shell-ready state, otherwise fish waits 10s and then - // disables optional features like cursor shape and reflow detection. - const { session, proc } = createTestSession("/usr/local/bin/fish"); - spawnAndReady(session, proc); - - // Simulate PTY output containing a DA1 query from fish. - // When the headless emulator processes this, xterm.js generates - // a DA1 response via its onData callback. - sendData(proc, "\x1b[c"); - - // The emulator write queue processes via setImmediate, so we need - // to let the event loop tick for xterm to process the query. - await waitForEmulatorFlush(); - - // The emulator's DA1 response should have been forwarded to the - // subprocess (written to stdin) even though shell is still pending. - const writes = getWrittenData(proc); - - // The response should contain a DA1 reply (ESC[?...c format) - expect(writes.length).toBeGreaterThan(0); - const da1Response = writes.join(""); - // biome-ignore lint/suspicious/noControlCharactersInRegex: matching ESC in terminal protocol data - expect(da1Response).toMatch(/\x1b\[\?[\d;]+c/); - }); - - it("forwards DSR response to subprocess during pending state", async () => { - const { session, proc } = createTestSession("/bin/zsh"); - spawnAndReady(session, proc); - - // DSR (Device Status Report): ESC[5n → ESC[0n (terminal OK) - sendData(proc, "\x1b[5n"); - - await waitForEmulatorFlush(); - - const writes = getWrittenData(proc); - expect(writes.length).toBeGreaterThan(0); - const response = writes.join(""); - expect(response).toContain("\x1b[0n"); - }); -}); - describe("Session shell-ready: supported shells", () => { for (const shell of [ "/bin/zsh", diff --git a/apps/desktop/src/main/terminal-host/session.ts b/apps/desktop/src/main/terminal-host/session.ts index 87174902470..f04fe72014c 100644 --- a/apps/desktop/src/main/terminal-host/session.ts +++ b/apps/desktop/src/main/terminal-host/session.ts @@ -224,21 +224,18 @@ export class Session { // Set initial CWD this.emulator.setCwd(options.cwd); - // The headless emulator responds to terminal queries (e.g. DA1, - // DSR) when no renderer client is attached. These responses must - // be forwarded to the subprocess immediately — even during shell - // init — because shells like fish send DA1 at startup and wait - // up to 10 seconds for a reply before disabling optional features. - // Unlike renderer-generated responses (which go through write() - // and are correctly dropped during init to avoid appearing as - // typed text), headless emulator responses are written directly - // to the PTY and consumed by the shell as protocol data. + // The headless emulator responds to terminal queries (e.g. DA) + // when no renderer client is attached. During shell init we drop + // these — they'd land in the pre-ready stdin queue and appear as + // typed text like "?62;4;9;22c" once flushed. After a client + // attaches the renderer's xterm handles all terminal queries. this.emulator.onData((data) => { if ( this.attachedClients.size === 0 && this.subprocess && this.subprocessReady ) { + if (this.shellReadyState === "pending") return; this.sendWriteToSubprocess(data); } }); From d8e679a2bdec27f57aaf4a338bb93567069a2baf Mon Sep 17 00:00:00 2001 From: zombopanda Date: Fri, 3 Apr 2026 05:08:19 +0300 Subject: [PATCH 04/11] fix: forward DA1 query responses regardless of attached clients (#3054) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `emulator.onData` callback only forwarded terminal query responses (DA1, DSR) to the PTY subprocess when `attachedClients.size === 0`. When a renderer client is attached (the normal case when viewing a terminal), the headless emulator's DA1 response was silently dropped. This caused fish shell to wait 10 seconds for a DA1 response that never arrived, then print: "warning: fish could not read response to Primary Device Attribute query after waiting for 10 seconds" The previous fix in #3030 removed the `shellReadyState === "pending"` guard but left the `attachedClients.size === 0` check intact. The renderer's xterm also generates DA1 responses, but those go through `write()` which drops all escape sequences during shell init — so neither path delivered the response to fish. Remove the `attachedClients.size === 0` condition so the headless emulator always forwards query responses to the subprocess. This is safe because `sendWriteToSubprocess` writes directly to the PTY via IPC, bypassing the renderer's write path entirely. Fixes #3028 Co-authored-by: zombopanda <1810282+zombopanda@users.noreply.github.com> Co-authored-by: Kiet Ho --- .../desktop/src/main/terminal-host/session.ts | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/apps/desktop/src/main/terminal-host/session.ts b/apps/desktop/src/main/terminal-host/session.ts index f04fe72014c..6e810b9ad27 100644 --- a/apps/desktop/src/main/terminal-host/session.ts +++ b/apps/desktop/src/main/terminal-host/session.ts @@ -224,18 +224,17 @@ export class Session { // Set initial CWD this.emulator.setCwd(options.cwd); - // The headless emulator responds to terminal queries (e.g. DA) - // when no renderer client is attached. During shell init we drop - // these — they'd land in the pre-ready stdin queue and appear as - // typed text like "?62;4;9;22c" once flushed. After a client - // attaches the renderer's xterm handles all terminal queries. + // The headless emulator responds to terminal queries (e.g. DA1, + // DSR). These responses must be forwarded to the subprocess + // regardless of whether renderer clients are attached, because + // shells like fish send DA1 at startup and wait up to 10 seconds + // for a reply before disabling optional features. + // Unlike renderer-generated responses (which go through write() + // and are correctly dropped during init to avoid appearing as + // typed text), headless emulator responses are written directly + // to the PTY and consumed by the shell as protocol data. this.emulator.onData((data) => { - if ( - this.attachedClients.size === 0 && - this.subprocess && - this.subprocessReady - ) { - if (this.shellReadyState === "pending") return; + if (this.subprocess && this.subprocessReady) { this.sendWriteToSubprocess(data); } }); From 1d71e73af039ab0f2403498913df61da986ccc34 Mon Sep 17 00:00:00 2001 From: Kiet <31864905+Kitenite@users.noreply.github.com> Date: Thu, 2 Apr 2026 16:04:54 -0700 Subject: [PATCH 05/11] fix(desktop): adjust tiptap editor line-height in pane (#3097) * fix(desktop): reduce line-height in markdown renderer pane styles Tighten line-height and spacing in default and tufte markdown styles for a more compact reading experience in panes. * Lint --- .../MarkdownRenderer/styles/default/default.css | 4 ++-- .../components/MarkdownRenderer/styles/tufte/tufte.css | 9 +++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/apps/desktop/src/renderer/components/MarkdownRenderer/styles/default/default.css b/apps/desktop/src/renderer/components/MarkdownRenderer/styles/default/default.css index b24a3cde29a..8085dcc3dfe 100644 --- a/apps/desktop/src/renderer/components/MarkdownRenderer/styles/default/default.css +++ b/apps/desktop/src/renderer/components/MarkdownRenderer/styles/default/default.css @@ -48,7 +48,7 @@ .default-markdown p { margin-top: 0; margin-bottom: 1rem; - line-height: 1.7; + line-height: 1.5; } .default-markdown ul, @@ -60,7 +60,7 @@ .default-markdown li { margin-bottom: 0.25rem; - line-height: 1.6; + line-height: 1.5; } .default-markdown ul { diff --git a/apps/desktop/src/renderer/components/MarkdownRenderer/styles/tufte/tufte.css b/apps/desktop/src/renderer/components/MarkdownRenderer/styles/tufte/tufte.css index 5d21f9938a7..edff21d4fcd 100644 --- a/apps/desktop/src/renderer/components/MarkdownRenderer/styles/tufte/tufte.css +++ b/apps/desktop/src/renderer/components/MarkdownRenderer/styles/tufte/tufte.css @@ -2,7 +2,7 @@ .tufte-markdown { font-family: Georgia, "Times New Roman", serif; font-size: 1.1rem; - line-height: 1.8; + line-height: 1.6; color: var(--foreground); } @@ -84,7 +84,7 @@ /* Body text */ .tufte-markdown p { margin-top: 0; - margin-bottom: 1.4rem; + margin-bottom: 1.1rem; text-align: justify; -webkit-hyphens: auto; hyphens: auto; @@ -101,7 +101,7 @@ /* Lists */ .tufte-markdown ul, .tufte-markdown ol { - margin: 1.4rem 0; + margin: 1.1rem 0; padding-left: 1.5rem; list-style-position: outside; } @@ -115,7 +115,8 @@ } .tufte-markdown li { - margin-bottom: 0.5rem; + margin-bottom: 0.35rem; + line-height: 1.5; } /* Links */ From 245105d69acfb78cf78fb63e4b823cae5691f373 Mon Sep 17 00:00:00 2001 From: Priyank Rajai Date: Fri, 3 Apr 2026 07:41:44 +0530 Subject: [PATCH 06/11] fix(desktop): always find existing file viewer pane regardless of open mode (#3093) Decouples the reuse-existing-pane lookup from the openInNewTab flag so that an already-open file is always located first. The pane is still only reused in-place when openInNewTab is false, preventing duplicate panes when the user's file-open mode is set to "new-tab". --- apps/desktop/src/renderer/stores/tabs/store.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/apps/desktop/src/renderer/stores/tabs/store.ts b/apps/desktop/src/renderer/stores/tabs/store.ts index aaaea777863..ec84856ccca 100644 --- a/apps/desktop/src/renderer/stores/tabs/store.ts +++ b/apps/desktop/src/renderer/stores/tabs/store.ts @@ -782,9 +782,7 @@ export const useTabsStore = create()( const tabPaneIds = extractPaneIdsFromLayout(activeTab.layout); const reuseExisting = options.reuseExisting ?? "workspace"; - const canReuseExistingPane = - !options.openInNewTab && reuseExisting !== "none"; - const existingFileViewerPane = canReuseExistingPane + const existingFileViewerPane = reuseExisting !== "none" ? findReusableFileViewerPane({ workspaceId, activeTabId: activeTab.id, @@ -854,7 +852,7 @@ export const useTabsStore = create()( // If we found an unpinned (preview) file-viewer pane, reuse it // (skip reuse when explicitly requesting a new tab, e.g. cmd+click) - if (fileViewerPanes.length > 0 && canReuseExistingPane) { + if (fileViewerPanes.length > 0 && !options.openInNewTab && reuseExisting !== "none") { const paneToReuse = fileViewerPanes[0]; const existingFileViewer = paneToReuse.fileViewer; if (!existingFileViewer) { From 26bb16ef3ed83d00f15c6eced157a1e68b2ec967 Mon Sep 17 00:00:00 2001 From: Kiet <31864905+Kitenite@users.noreply.github.com> Date: Thu, 2 Apr 2026 20:04:34 -0700 Subject: [PATCH 07/11] chore(desktop): bump version to 1.4.7 (#3128) * Lint * chore(desktop): bump version to 1.4.7 --- apps/desktop/package.json | 2 +- .../desktop/src/renderer/stores/tabs/store.ts | 29 +++++++++++-------- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 88be9c665bc..881ff16dcdd 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -2,7 +2,7 @@ "name": "@superset/desktop", "productName": "Superset", "description": "The last developer tool you'll ever need", - "version": "1.4.6", + "version": "1.4.7", "main": "./dist/main/index.js", "resources": "src/resources", "repository": { diff --git a/apps/desktop/src/renderer/stores/tabs/store.ts b/apps/desktop/src/renderer/stores/tabs/store.ts index ec84856ccca..3f7539cd779 100644 --- a/apps/desktop/src/renderer/stores/tabs/store.ts +++ b/apps/desktop/src/renderer/stores/tabs/store.ts @@ -782,17 +782,18 @@ export const useTabsStore = create()( const tabPaneIds = extractPaneIdsFromLayout(activeTab.layout); const reuseExisting = options.reuseExisting ?? "workspace"; - const existingFileViewerPane = reuseExisting !== "none" - ? findReusableFileViewerPane({ - workspaceId, - activeTabId: activeTab.id, - tabs: state.tabs, - panes: state.panes, - tabHistoryStacks: state.tabHistoryStacks, - reuseExisting, - options, - }) - : null; + const existingFileViewerPane = + reuseExisting !== "none" + ? findReusableFileViewerPane({ + workspaceId, + activeTabId: activeTab.id, + tabs: state.tabs, + panes: state.panes, + tabHistoryStacks: state.tabHistoryStacks, + reuseExisting, + options, + }) + : null; if (existingFileViewerPane) { const nextPane = applyFileViewerOpenOptionsToPane( @@ -852,7 +853,11 @@ export const useTabsStore = create()( // If we found an unpinned (preview) file-viewer pane, reuse it // (skip reuse when explicitly requesting a new tab, e.g. cmd+click) - if (fileViewerPanes.length > 0 && !options.openInNewTab && reuseExisting !== "none") { + if ( + fileViewerPanes.length > 0 && + !options.openInNewTab && + reuseExisting !== "none" + ) { const paneToReuse = fileViewerPanes[0]; const existingFileViewer = paneToReuse.fileViewer; if (!existingFileViewer) { From 284e2d974e60e63c16784f52620bc4e6ee68371e Mon Sep 17 00:00:00 2001 From: Avi Peltz Date: Fri, 3 Apr 2026 09:59:41 -0700 Subject: [PATCH 08/11] =?UTF-8?q?chore(desktop):=20upgrade=20Electron=2040?= =?UTF-8?q?.2.1=20=E2=86=92=2040.8.5=20(#3150)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Patch upgrade to latest 40.x stable to fix 4 Dependabot security alerts: protocol handler injection, second-instance IPC OOB read, use-after-free in fullscreen/download callbacks, and registry key injection. --- bun.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bun.lock b/bun.lock index 4a43a555ca8..123c48bb538 100644 --- a/bun.lock +++ b/bun.lock @@ -110,7 +110,7 @@ }, "apps/desktop": { "name": "@superset/desktop", - "version": "1.4.6", + "version": "1.4.7", "dependencies": { "@ai-sdk/anthropic": "^3.0.43", "@ai-sdk/openai": "3.0.36", From 6452b619cf085b428bcdd2ac884ed27176c37ae9 Mon Sep 17 00:00:00 2001 From: Avi Peltz Date: Fri, 3 Apr 2026 12:01:01 -0700 Subject: [PATCH 09/11] feat: make GitHub integration free for all users (#3152) Remove the PostHog feature flag gate and desktop paywall from the GitHub integration so it is accessible to all plan tiers. Update billing plans to reflect GitHub as a free feature and replace GitHub with Slack in the pro features paywall preview. --- .../IntegrationsDemo/IntegrationsDemo.tsx | 16 +++++++++++----- .../src/renderer/components/Paywall/constants.ts | 2 +- .../_authenticated/settings/billing/constants.ts | 1 + .../settings/billing/plans/page.tsx | 6 +++++- .../IntegrationsSettings.tsx | 16 +++++----------- .../app/(dashboard-legacy)/integrations/page.tsx | 6 +----- 6 files changed, 24 insertions(+), 23 deletions(-) diff --git a/apps/desktop/src/renderer/components/Paywall/components/FeaturePreview/components/IntegrationsDemo/IntegrationsDemo.tsx b/apps/desktop/src/renderer/components/Paywall/components/FeaturePreview/components/IntegrationsDemo/IntegrationsDemo.tsx index 1a11b48fd32..1e4c3c26a9e 100644 --- a/apps/desktop/src/renderer/components/Paywall/components/FeaturePreview/components/IntegrationsDemo/IntegrationsDemo.tsx +++ b/apps/desktop/src/renderer/components/Paywall/components/FeaturePreview/components/IntegrationsDemo/IntegrationsDemo.tsx @@ -1,9 +1,15 @@ +import { FaSlack } from "react-icons/fa"; import { HiArrowPath, HiCheck } from "react-icons/hi2"; -import { SiGithub, SiLinear } from "react-icons/si"; +import { SiLinear } from "react-icons/si"; const SYNCED_ITEMS = [ { id: "1", type: "issue", name: "SUP-142: Fix auth flow", status: "synced" }, - { id: "2", type: "pr", name: "PR #89: Add workspace sync", status: "synced" }, + { + id: "2", + type: "message", + name: "#eng: Add workspace sync", + status: "synced", + }, { id: "3", type: "issue", @@ -45,10 +51,10 @@ export function IntegrationsDemo() {
-
- +
+
- GitHub + Slack
diff --git a/apps/desktop/src/renderer/components/Paywall/constants.ts b/apps/desktop/src/renderer/components/Paywall/constants.ts index 2dac4cebb40..0a2c4e540dd 100644 --- a/apps/desktop/src/renderer/components/Paywall/constants.ts +++ b/apps/desktop/src/renderer/components/Paywall/constants.ts @@ -41,7 +41,7 @@ export const PRO_FEATURES: ProFeature[] = [ id: "integrations", title: "Integrations", description: - "Connect Linear, GitHub, and more to sync issues and PRs directly with your workspaces.", + "Connect Linear, Slack, and more to sync issues and conversations directly with your workspaces.", icon: HiOutlinePuzzlePiece, iconColor: "text-purple-500", gradientColors: ["#7c3aed", "#6d28d9", "#4c1d95", "#1a1a2e"], diff --git a/apps/desktop/src/renderer/routes/_authenticated/settings/billing/constants.ts b/apps/desktop/src/renderer/routes/_authenticated/settings/billing/constants.ts index 9af00ffa30c..a1deb7ede1d 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/settings/billing/constants.ts +++ b/apps/desktop/src/renderer/routes/_authenticated/settings/billing/constants.ts @@ -48,6 +48,7 @@ export const PLANS: Record = { { id: "workspaces", name: "Up to 5 workspaces", included: true }, { id: "local-only", name: "Local workspaces only", included: true }, { id: "desktop-app", name: "Desktop app", included: true }, + { id: "github", name: "GitHub integration", included: true }, ], cta: { text: "Current plan", action: "current", disabled: true }, }, diff --git a/apps/desktop/src/renderer/routes/_authenticated/settings/billing/plans/page.tsx b/apps/desktop/src/renderer/routes/_authenticated/settings/billing/plans/page.tsx index e38003e5022..0cd76ee3dad 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/settings/billing/plans/page.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/settings/billing/plans/page.tsx @@ -136,7 +136,7 @@ const COMPARISON_SECTIONS: ComparisonSection[] = [ }, { label: "GitHub integration", - values: [null, true, true], + values: [true, true, true], }, { label: "Cloud workspaces", @@ -152,6 +152,10 @@ const COMPARISON_SECTIONS: ComparisonSection[] = [ label: "Linear integration", values: [null, true, true], }, + { + label: "Slack integration", + values: [null, true, true], + }, { label: "Team collaboration", values: [null, true, true], diff --git a/apps/desktop/src/renderer/routes/_authenticated/settings/integrations/components/IntegrationsSettings/IntegrationsSettings.tsx b/apps/desktop/src/renderer/routes/_authenticated/settings/integrations/components/IntegrationsSettings/IntegrationsSettings.tsx index 1ca327fdf92..6eabfde81aa 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/settings/integrations/components/IntegrationsSettings/IntegrationsSettings.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/settings/integrations/components/IntegrationsSettings/IntegrationsSettings.tsx @@ -60,9 +60,6 @@ export function IntegrationsSettings({ useState(null); const [isLoadingGithub, setIsLoadingGithub] = useState(true); - const hasGithubAccess = useFeatureFlagEnabled( - FEATURE_FLAGS.GITHUB_INTEGRATION_ACCESS, - ); const hasSlackAccess = useFeatureFlagEnabled( FEATURE_FLAGS.SLACK_INTEGRATION_ACCESS, ); @@ -71,9 +68,10 @@ export function IntegrationsSettings({ SETTING_ITEM_ID.INTEGRATIONS_LINEAR, visibleItems, ); - const showGithub = - hasGithubAccess && - isItemVisible(SETTING_ITEM_ID.INTEGRATIONS_GITHUB, visibleItems); + const showGithub = isItemVisible( + SETTING_ITEM_ID.INTEGRATIONS_GITHUB, + visibleItems, + ); const fetchGithubInstallation = useCallback(async () => { if (!activeOrganizationId) { @@ -161,11 +159,7 @@ export function IntegrationsSettings({ isConnected={isGithubConnected} connectedOrgName={githubInstallation?.accountLogin} isLoading={isLoadingGithub} - onManage={() => - gateFeature(GATED_FEATURES.INTEGRATIONS, () => - handleOpenWeb("/integrations/github"), - ) - } + onManage={() => handleOpenWeb("/integrations/github")} /> )} diff --git a/apps/web/src/app/(dashboard-legacy)/integrations/page.tsx b/apps/web/src/app/(dashboard-legacy)/integrations/page.tsx index 351d3daf5d1..981e7b0a1f4 100644 --- a/apps/web/src/app/(dashboard-legacy)/integrations/page.tsx +++ b/apps/web/src/app/(dashboard-legacy)/integrations/page.tsx @@ -38,9 +38,6 @@ const integrations: IntegrationCardProps[] = [ ]; export default function IntegrationsPage() { - const hasGithubAccess = useFeatureFlagEnabled( - FEATURE_FLAGS.GITHUB_INTEGRATION_ACCESS, - ); const hasSlackAccess = useFeatureFlagEnabled( FEATURE_FLAGS.SLACK_INTEGRATION_ACCESS, ); @@ -48,11 +45,10 @@ export default function IntegrationsPage() { const visibleIntegrations = useMemo( () => integrations.filter((i) => { - if (i.id === "github") return hasGithubAccess; if (i.id === "slack") return hasSlackAccess; return true; }), - [hasGithubAccess, hasSlackAccess], + [hasSlackAccess], ); return ( From 0cd0b54a217f0133ba4537ede0ec1409d96d0e42 Mon Sep 17 00:00:00 2001 From: Kiet <31864905+Kitenite@users.noreply.github.com> Date: Sat, 4 Apr 2026 13:51:30 -0700 Subject: [PATCH 10/11] fix(desktop): remove duplicate HTML5 backend from v2 Workspace (#3174) * Reuse dnd backend * Remove provider from v2 workspace * Lint * fix: await async closeTab in bulk close callbacks * docs(panes): document DndProvider requirement for consumers --- bun.lock | 1 - packages/panes/README.md | 19 ++++ packages/panes/package.json | 1 - .../react/components/Workspace/Workspace.tsx | 88 +++++++++---------- 4 files changed, 60 insertions(+), 49 deletions(-) diff --git a/bun.lock b/bun.lock index 123c48bb538..55e56802374 100644 --- a/bun.lock +++ b/bun.lock @@ -801,7 +801,6 @@ "@superset/ui": "workspace:*", "lucide-react": "^0.563.0", "react-dnd": "^16.0.1", - "react-dnd-html5-backend": "^16.0.1", "zustand": "^5.0.8", }, "devDependencies": { diff --git a/packages/panes/README.md b/packages/panes/README.md index dce39c6aba6..dc78379e938 100644 --- a/packages/panes/README.md +++ b/packages/panes/README.md @@ -330,6 +330,25 @@ Pin from inside a pane component (e.g. on first edit): context.actions.pin(); ``` +## Drag-and-Drop + +`Workspace` uses `react-dnd` internally for tab reordering and pane dragging but does **not** include its own `DndProvider`. You must wrap `` in a `DndProvider` yourself: + +```tsx +import { DndProvider } from "react-dnd"; +import { HTML5Backend } from "react-dnd-html5-backend"; + +function App() { + return ( + + + + ); +} +``` + +This is intentional — embedding a provider inside `Workspace` would conflict with any parent `DndProvider` in your app (the HTML5 backend cannot be instantiated twice). Keeping it external lets you share a single backend across your entire component tree. + ## Workspace Props ```ts diff --git a/packages/panes/package.json b/packages/panes/package.json index fea2ba390d5..5640d099d14 100644 --- a/packages/panes/package.json +++ b/packages/panes/package.json @@ -18,7 +18,6 @@ "@superset/ui": "workspace:*", "lucide-react": "^0.563.0", "react-dnd": "^16.0.1", - "react-dnd-html5-backend": "^16.0.1", "zustand": "^5.0.8" }, "devDependencies": { diff --git a/packages/panes/src/react/components/Workspace/Workspace.tsx b/packages/panes/src/react/components/Workspace/Workspace.tsx index dbe07c57998..9d35efeaf41 100644 --- a/packages/panes/src/react/components/Workspace/Workspace.tsx +++ b/packages/panes/src/react/components/Workspace/Workspace.tsx @@ -1,6 +1,4 @@ import { cn } from "@superset/ui/utils"; -import { DndProvider } from "react-dnd"; -import { HTML5Backend } from "react-dnd-html5-backend"; import { useStore } from "zustand"; import type { WorkspaceProps } from "../../types"; import { Tab } from "./components/Tab"; @@ -31,53 +29,49 @@ export function Workspace({ }; return ( - -
- store.getState().setActiveTab(tabId)} - onCloseTab={closeTab} - onCloseOtherTabs={(tabId) => { - for (const tab of tabs) { - if (tab.id !== tabId) closeTab(tab.id); - } - }} - onCloseAllTabs={() => { - for (const tab of tabs) { - closeTab(tab.id); - } - }} - onRenameTab={(tabId, title) => - store - .getState() - .setTabTitleOverride({ tabId, titleOverride: title }) +
+ store.getState().setActiveTab(tabId)} + onCloseTab={closeTab} + onCloseOtherTabs={async (tabId) => { + for (const tab of tabs) { + if (tab.id !== tabId) await closeTab(tab.id); } - onReorderTab={(tabId, toIndex) => - store.getState().reorderTab({ tabId, toIndex }) + }} + onCloseAllTabs={async () => { + for (const tab of tabs) { + await closeTab(tab.id); } - getTabTitle={(tab) => tab.titleOverride ?? tab.id} - renderAddTabMenu={renderAddTabMenu} - renderTabAccessory={renderTabAccessory} + }} + onRenameTab={(tabId, title) => + store.getState().setTabTitleOverride({ tabId, titleOverride: title }) + } + onReorderTab={(tabId, toIndex) => + store.getState().reorderTab({ tabId, toIndex }) + } + getTabTitle={(tab) => tab.titleOverride ?? tab.id} + renderAddTabMenu={renderAddTabMenu} + renderTabAccessory={renderTabAccessory} + /> + {activeTab ? ( + - {activeTab ? ( - - ) : ( -
- {renderEmptyState?.() ?? "No tabs open"} -
- )} -
- + ) : ( +
+ {renderEmptyState?.() ?? "No tabs open"} +
+ )} +
); } From ebef1c28cf00ff79632fd594017423dd89388284 Mon Sep 17 00:00:00 2001 From: Priyank Rajai Date: Sat, 4 Apr 2026 02:33:36 +0530 Subject: [PATCH 11/11] fix(desktop): send correct terminal dimensions after attach for TUI apps (#3154) * fix(desktop): send correct terminal dimensions after attach for TUI apps When opening a terminal via preset (Ctrl+1), the PTY was spawned with stale dimensions because fitAddon.fit() ran before the container had its final layout. Most CLIs handle the late SIGWINCH from the debounced ResizeObserver, but ink-based TUI apps like Claude Code commit to the initial width during their first render cycle, resulting in a narrow UI. After a successful createOrAttach, schedule a requestAnimationFrame to re-fit and send corrected dimensions to the PTY. The resize is only sent if dimensions actually changed, making it a no-op when the initial size was already correct. * refactor(terminal): remove stale dimension handling comments Removed outdated comments regarding terminal dimension handling after attach, as the logic has been updated to ensure correct dimensions are sent to TUI apps. This cleanup enhances code readability and maintains focus on the current implementation. * refactor(terminal): streamline terminal dimension handling logic Reintroduced the requestAnimationFrame logic for updating terminal dimensions after attach, ensuring accurate dimensions are sent to TUI apps. This change enhances the responsiveness of terminal resizing and maintains consistency in the terminal's display behavior. --- .../Terminal/hooks/useTerminalLifecycle.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalLifecycle.ts b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalLifecycle.ts index 034319ab923..c4ed578effa 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalLifecycle.ts +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalLifecycle.ts @@ -599,6 +599,20 @@ export function useTerminalLifecycle({ return; } + requestAnimationFrame(() => { + if (!isAttachActive()) return; + const prevCols = xterm.cols; + const prevRows = xterm.rows; + fitAddon.fit(); + if (xterm.cols !== prevCols || xterm.rows !== prevRows) { + resizeRef.current({ + paneId, + cols: xterm.cols, + rows: xterm.rows, + }); + } + }); + pendingInitialStateRef.current = result; maybeApplyInitialState();