From 8879472df7f7b8143e811617d034225ec209e0c4 Mon Sep 17 00:00:00 2001 From: abose Date: Sat, 14 Mar 2026 19:14:27 +0530 Subject: [PATCH 01/25] feat: improve AI install screen and header branding Add sparkle icon next to "Claude Code" title in chat header. Redesign CLI-not-found screen with install button linking to claude.com/product/claude-code. Require global CLI binary for availability check instead of falling back to bundled SDK. Fix text wrapping in unavailable message by overriding inherited white-space:nowrap from sidebar panel. --- src-node/claude-code-agent.js | 10 +++----- src/nls/root/strings.js | 7 ++--- src/styles/Extn-AIChatPanel.less | 44 +++++++++++++++++++++++++++++++- 3 files changed, 51 insertions(+), 10 deletions(-) diff --git a/src-node/claude-code-agent.js b/src-node/claude-code-agent.js index f9fe0cd8b..015b73708 100644 --- a/src-node/claude-code-agent.js +++ b/src-node/claude-code-agent.js @@ -119,14 +119,12 @@ function findGlobalClaudeCli() { exports.checkAvailability = async function () { try { const claudePath = findGlobalClaudeCli(); - if (claudePath) { - // Also verify the SDK can be imported - await getQueryFn(); - return { available: true, claudePath: claudePath }; + if (!claudePath) { + return { available: false, claudePath: null, error: "Claude Code CLI not found" }; } - // No global CLI found — try importing SDK anyway (it might find its own) + // Verify the SDK can be imported await getQueryFn(); - return { available: true, claudePath: null }; + return { available: true, claudePath: claudePath }; } catch (err) { return { available: false, claudePath: null, error: err.message }; } diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index b19a4a78a..93391007f 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -1829,15 +1829,16 @@ define({ "AI_UPSELL_DIALOG_MESSAGE": "You’ve discovered {0}. To proceed, you’ll need an AI subscription or credits.", // AI CHAT PANEL - "AI_CHAT_TITLE": "AI Assistant", + "AI_CHAT_TITLE": "Claude Code", "AI_CHAT_NEW_SESSION_TITLE": "Start a new conversation", "AI_CHAT_NEW_BTN": "New", "AI_CHAT_THINKING": "Thinking...", "AI_CHAT_PLACEHOLDER": "Ask Claude...", "AI_CHAT_SEND_TITLE": "Send message", "AI_CHAT_STOP_TITLE": "Stop generation (Esc)", - "AI_CHAT_CLI_NOT_FOUND": "Claude CLI Not Found", - "AI_CHAT_CLI_INSTALL_MSG": "Install the Claude CLI to use AI features:
npm install -g @anthropic-ai/claude-code

Then run claude login to authenticate.", + "AI_CHAT_CLI_NOT_FOUND": "Claude Code Not Installed", + "AI_CHAT_CLI_INSTALL_MSG": "Install Claude Code to use AI features. Claude Code is Anthropic's agentic coding tool that powers the AI features in {APP_NAME}.", + "AI_CHAT_CLI_INSTALL_BTN": "Install Claude Code", "AI_CHAT_RETRY": "Retry", "AI_CHAT_DESKTOP_ONLY": "AI features require the Phoenix desktop app. Download it to get started.", "AI_CHAT_DOWNLOAD_BTN": "Download Desktop App", diff --git a/src/styles/Extn-AIChatPanel.less b/src/styles/Extn-AIChatPanel.less index 0a30db345..fea0b4a35 100644 --- a/src/styles/Extn-AIChatPanel.less +++ b/src/styles/Extn-AIChatPanel.less @@ -45,11 +45,30 @@ .ai-chat-header { display: flex; align-items: center; - justify-content: space-between; + justify-content: center; + position: relative; padding: 10px 10px 9px 12px; border-bottom: 1px solid rgba(255, 255, 255, 0.06); flex-shrink: 0; + .ai-chat-title-group { + display: flex; + align-items: center; + } + + .ai-chat-title-icon { + display: inline-flex; + align-items: center; + margin-right: 5px; + opacity: 0.6; + color: @project-panel-text-2; + + svg { + width: 16px; + height: 16px; + } + } + .ai-chat-title { font-weight: 400; font-size: @label-font-size; @@ -58,6 +77,8 @@ } .ai-chat-header-actions { + position: absolute; + right: 10px; display: flex; align-items: center; gap: 2px; @@ -1564,6 +1585,8 @@ padding: 2rem; text-align: center; color: @project-panel-text-2; + overflow: hidden; + min-width: 0; .ai-unavailable-icon { font-size: 2rem; @@ -1583,6 +1606,7 @@ line-height: 1.5; margin-bottom: 12px; opacity: 0.6; + white-space: normal; } .ai-retry-btn { @@ -1618,6 +1642,24 @@ } } +.ai-install-screen { + .ai-install-icon { + display: inline-flex; + margin-bottom: 12px; + opacity: 0.5; + color: @project-panel-text-2; + + svg { + width: 48px; + height: 48px; + } + } + + .ai-install-btn { + margin-bottom: 8px; + } +} + /* ── AI Settings Dialog ────────────────────────────────────────────── */ .ai-settings-dialog { .ai-settings-section-label { From c4541fc715f9fb91fd3461b8fcd636d0dd26914d Mon Sep 17 00:00:00 2001 From: abose Date: Sat, 14 Mar 2026 19:25:39 +0530 Subject: [PATCH 02/25] feat: add learn more link to AI install screen Add inline "Learn more" link in the install message that opens the Claude Code product page. Style the link to match the panel theme. --- src/nls/root/strings.js | 2 +- src/styles/Extn-AIChatPanel.less | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index 93391007f..2aecacddd 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -1837,7 +1837,7 @@ define({ "AI_CHAT_SEND_TITLE": "Send message", "AI_CHAT_STOP_TITLE": "Stop generation (Esc)", "AI_CHAT_CLI_NOT_FOUND": "Claude Code Not Installed", - "AI_CHAT_CLI_INSTALL_MSG": "Install Claude Code to use AI features. Claude Code is Anthropic's agentic coding tool that powers the AI features in {APP_NAME}.", + "AI_CHAT_CLI_INSTALL_MSG": "Claude Code CLI must be installed on your system to use AI features in {APP_NAME}. Learn more", "AI_CHAT_CLI_INSTALL_BTN": "Install Claude Code", "AI_CHAT_RETRY": "Retry", "AI_CHAT_DESKTOP_ONLY": "AI features require the Phoenix desktop app. Download it to get started.", diff --git a/src/styles/Extn-AIChatPanel.less b/src/styles/Extn-AIChatPanel.less index fea0b4a35..72358e6ec 100644 --- a/src/styles/Extn-AIChatPanel.less +++ b/src/styles/Extn-AIChatPanel.less @@ -1607,6 +1607,17 @@ margin-bottom: 12px; opacity: 0.6; white-space: normal; + + .ai-learn-more-link { + color: @project-panel-text-2; + text-decoration: underline; + cursor: pointer; + opacity: 1; + + &:hover { + color: @project-panel-text-1; + } + } } .ai-retry-btn { From 03d91a35e69086755e887638130af75f8f41aa55 Mon Sep 17 00:00:00 2001 From: abose Date: Sat, 14 Mar 2026 20:04:09 +0530 Subject: [PATCH 03/25] feat: add terminal shellCommand param and install UX improvements Add options.shellCommand parameter to VIEW_TERMINAL command to open a new terminal and execute a command in it. Add installing and installing-message strings. Add learn-more link styling. --- src/extensionsIntegrated/Terminal/main.js | 17 ++++++++++++++++- src/nls/root/strings.js | 2 ++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/extensionsIntegrated/Terminal/main.js b/src/extensionsIntegrated/Terminal/main.js index e8bdd2846..923ba6b1c 100644 --- a/src/extensionsIntegrated/Terminal/main.js +++ b/src/extensionsIntegrated/Terminal/main.js @@ -547,8 +547,23 @@ define(function (require, exports, module) { * If the panel is visible and the active terminal is focused and there * are 2+ terminals, cycles to the next one. Otherwise just shows and * focuses the active terminal. + * + * @param {Object} [options] - Optional settings + * @param {string} [options.shellCommand] - A shell command to execute in a new terminal. + * When provided, always creates a fresh terminal and types the command into it. */ - async function _showTerminal() { + async function _showTerminal(options) { + if (options && options.shellCommand) { + await _createNewTerminal(); + const active = _getActiveTerminal(); + if (active && active.isAlive) { + nodeConnector.execPeer("writeTerminal", { + id: active.id, + data: options.shellCommand + "\n" + }); + } + return; + } if (terminalInstances.length === 0) { await _createNewTerminal(); return; diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index 2aecacddd..bc926c742 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -1839,6 +1839,8 @@ define({ "AI_CHAT_CLI_NOT_FOUND": "Claude Code Not Installed", "AI_CHAT_CLI_INSTALL_MSG": "Claude Code CLI must be installed on your system to use AI features in {APP_NAME}. Learn more", "AI_CHAT_CLI_INSTALL_BTN": "Install Claude Code", + "AI_CHAT_CLI_INSTALLING": "Installing…", + "AI_CHAT_CLI_INSTALLING_MSG": "Installing Claude Code, please wait. This may take a while...", "AI_CHAT_RETRY": "Retry", "AI_CHAT_DESKTOP_ONLY": "AI features require the Phoenix desktop app. Download it to get started.", "AI_CHAT_DOWNLOAD_BTN": "Download Desktop App", From e3c2f0e678873c06c636b1da4128dff3e87b208c Mon Sep 17 00:00:00 2001 From: abose Date: Sat, 14 Mar 2026 20:12:45 +0530 Subject: [PATCH 04/25] feat: add restart note string and styling for install screen --- src/nls/root/strings.js | 1 + src/styles/Extn-AIChatPanel.less | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index bc926c742..b0c8526d0 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -1841,6 +1841,7 @@ define({ "AI_CHAT_CLI_INSTALL_BTN": "Install Claude Code", "AI_CHAT_CLI_INSTALLING": "Installing…", "AI_CHAT_CLI_INSTALLING_MSG": "Installing Claude Code, please wait. This may take a while...", + "AI_CHAT_CLI_RESTART_NOTE": "Restart {APP_NAME} after installation completes.", "AI_CHAT_RETRY": "Retry", "AI_CHAT_DESKTOP_ONLY": "AI features require the Phoenix desktop app. Download it to get started.", "AI_CHAT_DOWNLOAD_BTN": "Download Desktop App", diff --git a/src/styles/Extn-AIChatPanel.less b/src/styles/Extn-AIChatPanel.less index 72358e6ec..bbccd65d2 100644 --- a/src/styles/Extn-AIChatPanel.less +++ b/src/styles/Extn-AIChatPanel.less @@ -1669,6 +1669,14 @@ .ai-install-btn { margin-bottom: 8px; } + + .ai-install-restart-note { + font-size: @sidebar-small-font-size; + color: @project-panel-text-2; + opacity: 0.6; + margin-top: 8px; + white-space: normal; + } } /* ── AI Settings Dialog ────────────────────────────────────────────── */ From 636201e6fe2d489d50809e44e0128e768d7b12e0 Mon Sep 17 00:00:00 2001 From: abose Date: Sat, 14 Mar 2026 20:20:33 +0530 Subject: [PATCH 05/25] feat: detect Claude login status and add setup strings checkAvailability now returns loggedIn flag via claude auth status. Add setup/login strings for Claude Code configuration screen. --- src-node/claude-code-agent.js | 14 +++++++++++++- src/nls/root/strings.js | 3 +++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src-node/claude-code-agent.js b/src-node/claude-code-agent.js index 015b73708..d60d92c68 100644 --- a/src-node/claude-code-agent.js +++ b/src-node/claude-code-agent.js @@ -124,7 +124,19 @@ exports.checkAvailability = async function () { } // Verify the SDK can be imported await getQueryFn(); - return { available: true, claudePath: claudePath }; + // Check if user is logged in + let loggedIn = false; + try { + const authOutput = execSync(claudePath + " auth status", { + encoding: "utf8", + timeout: 10000 + }); + const authStatus = JSON.parse(authOutput); + loggedIn = authStatus.loggedIn === true; + } catch (e) { + // auth status failed — treat as not logged in + } + return { available: true, claudePath: claudePath, loggedIn: loggedIn }; } catch (err) { return { available: false, claudePath: null, error: err.message }; } diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index b0c8526d0..d95ad5119 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -1842,6 +1842,9 @@ define({ "AI_CHAT_CLI_INSTALLING": "Installing…", "AI_CHAT_CLI_INSTALLING_MSG": "Installing Claude Code, please wait. This may take a while...", "AI_CHAT_CLI_RESTART_NOTE": "Restart {APP_NAME} after installation completes.", + "AI_CHAT_CLAUDE_LOGIN_TITLE": "Setup Claude Code", + "AI_CHAT_CLAUDE_LOGIN_MSG": "Claude Code is installed but needs to be configured. Run the setup to sign in to your Anthropic account.", + "AI_CHAT_CLAUDE_LOGIN_BTN": "Setup Claude Code", "AI_CHAT_RETRY": "Retry", "AI_CHAT_DESKTOP_ONLY": "AI features require the Phoenix desktop app. Download it to get started.", "AI_CHAT_DOWNLOAD_BTN": "Download Desktop App", From 05d4df65ee7d7a36c459d200f0d36343d7d1bf03 Mon Sep 17 00:00:00 2001 From: abose Date: Sat, 14 Mar 2026 22:19:02 +0530 Subject: [PATCH 06/25] fix: remove SDK gate from checkAvailability and add provider btn string MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove getQueryFn() call from checkAvailability — CLI presence and auth status are sufficient. Add custom provider button string and spacing fix for setup screen buttons. --- src-node/claude-code-agent.js | 2 -- src/nls/root/strings.js | 3 ++- src/styles/Extn-AIChatPanel.less | 5 +++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src-node/claude-code-agent.js b/src-node/claude-code-agent.js index d60d92c68..5458b6e3b 100644 --- a/src-node/claude-code-agent.js +++ b/src-node/claude-code-agent.js @@ -122,8 +122,6 @@ exports.checkAvailability = async function () { if (!claudePath) { return { available: false, claudePath: null, error: "Claude Code CLI not found" }; } - // Verify the SDK can be imported - await getQueryFn(); // Check if user is logged in let loggedIn = false; try { diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index d95ad5119..7f96b75ef 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -1843,8 +1843,9 @@ define({ "AI_CHAT_CLI_INSTALLING_MSG": "Installing Claude Code, please wait. This may take a while...", "AI_CHAT_CLI_RESTART_NOTE": "Restart {APP_NAME} after installation completes.", "AI_CHAT_CLAUDE_LOGIN_TITLE": "Setup Claude Code", - "AI_CHAT_CLAUDE_LOGIN_MSG": "Claude Code is installed but needs to be configured. Run the setup to sign in to your Anthropic account.", + "AI_CHAT_CLAUDE_LOGIN_MSG": "Claude Code is installed but needs to be configured.", "AI_CHAT_CLAUDE_LOGIN_BTN": "Setup Claude Code", + "AI_CHAT_ADD_PROVIDER_BTN": "Add Custom Provider", "AI_CHAT_RETRY": "Retry", "AI_CHAT_DESKTOP_ONLY": "AI features require the Phoenix desktop app. Download it to get started.", "AI_CHAT_DOWNLOAD_BTN": "Download Desktop App", diff --git a/src/styles/Extn-AIChatPanel.less b/src/styles/Extn-AIChatPanel.less index bbccd65d2..3b077af49 100644 --- a/src/styles/Extn-AIChatPanel.less +++ b/src/styles/Extn-AIChatPanel.less @@ -1666,8 +1666,9 @@ } } - .ai-install-btn { - margin-bottom: 8px; + .ai-install-btn, + .ai-claude-login-btn { + margin-bottom: 10px; } .ai-install-restart-note { From 5d029deb4c2d6605717e3e04ebff311eed11a555 Mon Sep 17 00:00:00 2001 From: abose Date: Sat, 14 Mar 2026 22:22:25 +0530 Subject: [PATCH 07/25] feat: add learn more link to Claude setup screen string --- src/nls/root/strings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index 7f96b75ef..0692ad14d 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -1843,7 +1843,7 @@ define({ "AI_CHAT_CLI_INSTALLING_MSG": "Installing Claude Code, please wait. This may take a while...", "AI_CHAT_CLI_RESTART_NOTE": "Restart {APP_NAME} after installation completes.", "AI_CHAT_CLAUDE_LOGIN_TITLE": "Setup Claude Code", - "AI_CHAT_CLAUDE_LOGIN_MSG": "Claude Code is installed but needs to be configured.", + "AI_CHAT_CLAUDE_LOGIN_MSG": "Claude Code is installed but needs to be configured. Learn more", "AI_CHAT_CLAUDE_LOGIN_BTN": "Setup Claude Code", "AI_CHAT_ADD_PROVIDER_BTN": "Add Custom Provider", "AI_CHAT_RETRY": "Retry", From 3587d89c54425ffc0d3c2256f551ddd49f4154d2 Mon Sep 17 00:00:00 2001 From: abose Date: Sat, 14 Mar 2026 22:30:33 +0530 Subject: [PATCH 08/25] fix: code block text color in AI panel for light theme Add explicit color to inline code elements using panel text color. The sidebar is always dark regardless of editor theme, so code blocks need to use panel text colors not body text. --- src/styles/Extn-AIChatPanel.less | 1 + 1 file changed, 1 insertion(+) diff --git a/src/styles/Extn-AIChatPanel.less b/src/styles/Extn-AIChatPanel.less index 3b077af49..86fd45826 100644 --- a/src/styles/Extn-AIChatPanel.less +++ b/src/styles/Extn-AIChatPanel.less @@ -439,6 +439,7 @@ code { background-color: rgba(255, 255, 255, 0.08); + color: @project-panel-text-1; padding: 1px 4px; border-radius: 3px; font-size: @sidebar-small-font-size; From abff2206519cbf4cdd24c18274b69e09e8fa0141 Mon Sep 17 00:00:00 2001 From: abose Date: Sun, 15 Mar 2026 15:06:54 +0530 Subject: [PATCH 09/25] fix: windows claude code detection working --- src-node/claude-code-agent.js | 55 +++++++++++++++++++++++++++++++++-- 1 file changed, 52 insertions(+), 3 deletions(-) diff --git a/src-node/claude-code-agent.js b/src-node/claude-code-agent.js index 5458b6e3b..ddce86626 100644 --- a/src-node/claude-code-agent.js +++ b/src-node/claude-code-agent.js @@ -27,9 +27,12 @@ */ const { execSync } = require("child_process"); +const fs = require("fs"); const path = require("path"); const { createEditorMcpServer } = require("./mcp-editor-tools"); +const isWindows = process.platform === "win32"; + const CONNECTOR_ID = "ph_ai_claude"; const CLARIFICATION_HINT = @@ -71,9 +74,45 @@ async function getQueryFn() { } /** - * Find the user's globally installed Claude CLI, skipping node_modules copies. + * Find the user's globally installed Claude CLI on Windows. */ -function findGlobalClaudeCli() { +function _findGlobalClaudeCliWin() { + const userHome = process.env.USERPROFILE || process.env.HOME || ""; + const locations = [ + path.join(userHome, ".local", "bin", "claude.exe"), + path.join(process.env.APPDATA || "", "npm", "claude.cmd"), + path.join(process.env.LOCALAPPDATA || "", "Programs", "claude", "claude.exe") + ]; + + // Try 'where claude' first to find claude in PATH + try { + const allPaths = execSync("where claude", { encoding: "utf8" }) + .trim() + .split("\r\n") + .filter(p => p && !p.includes("node_modules")); + if (allPaths.length > 0) { + console.log("[Phoenix AI] Found global Claude CLI at:", allPaths[0]); + return allPaths[0]; + } + } catch { + // where failed, try manual locations + } + + // Check common Windows locations + for (const loc of locations) { + if (loc && fs.existsSync(loc)) { + console.log("[Phoenix AI] Found global Claude CLI at:", loc); + return loc; + } + } + + return null; +} + +/** + * Find the user's globally installed Claude CLI on macOS/Linux. + */ +function _findGlobalClaudeCliLinuxMac() { const locations = [ "/usr/local/bin/claude", "/usr/bin/claude", @@ -108,10 +147,20 @@ function findGlobalClaudeCli() { } } - console.log("[Phoenix AI] Global Claude CLI not found"); return null; } +/** + * Find the user's globally installed Claude CLI, skipping node_modules copies. + */ +function findGlobalClaudeCli() { + const claudePath = isWindows ? _findGlobalClaudeCliWin() : _findGlobalClaudeCliLinuxMac(); + if (!claudePath) { + console.log("[Phoenix AI] Global Claude CLI not found"); + } + return claudePath; +} + /** * Check whether Claude CLI is available. * Called from browser via execPeer("checkAvailability"). From dbe1f701e2f2c048eb88caeb54e548f9531510f1 Mon Sep 17 00:00:00 2001 From: abose Date: Sun, 15 Mar 2026 15:24:38 +0530 Subject: [PATCH 10/25] fix: terminal shellCommand execution on Windows Wait for shell prompt before writing command instead of firing immediately after PTY spawn. Use \r (carriage return) instead of \n for command submission, matching what xterm.js sends for Enter. --- src/extensionsIntegrated/Terminal/TerminalInstance.js | 10 ++++++++++ src/extensionsIntegrated/Terminal/main.js | 4 +++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/extensionsIntegrated/Terminal/TerminalInstance.js b/src/extensionsIntegrated/Terminal/TerminalInstance.js index fa131a706..90d21e26d 100644 --- a/src/extensionsIntegrated/Terminal/TerminalInstance.js +++ b/src/extensionsIntegrated/Terminal/TerminalInstance.js @@ -108,6 +108,12 @@ define(function (require, exports, module) { this._webglAddon = null; this._lastDpr = null; + // Promise that resolves when the shell sends its first output (prompt ready) + this._firstDataResolve = null; + this.firstDataReceived = new Promise(function (resolve) { + this._firstDataResolve = resolve; + }.bind(this)); + // Bound event handlers for cleanup this._onTerminalData = this._onTerminalData.bind(this); this._onTerminalExit = this._onTerminalExit.bind(this); @@ -217,6 +223,10 @@ define(function (require, exports, module) { TerminalInstance.prototype._onTerminalData = function (_event, eventData) { if (eventData.id === this.id && this.terminal) { this.terminal.write(eventData.data); + if (this._firstDataResolve) { + this._firstDataResolve(); + this._firstDataResolve = null; + } } }; diff --git a/src/extensionsIntegrated/Terminal/main.js b/src/extensionsIntegrated/Terminal/main.js index 923ba6b1c..2a5bcd554 100644 --- a/src/extensionsIntegrated/Terminal/main.js +++ b/src/extensionsIntegrated/Terminal/main.js @@ -557,9 +557,11 @@ define(function (require, exports, module) { await _createNewTerminal(); const active = _getActiveTerminal(); if (active && active.isAlive) { + // Wait for the shell to output its prompt before sending the command. + await active.firstDataReceived; nodeConnector.execPeer("writeTerminal", { id: active.id, - data: options.shellCommand + "\n" + data: options.shellCommand + "\r" }); } return; From 827c2279d27836cfdddaff7085dc461e5bd3be33 Mon Sep 17 00:00:00 2001 From: abose Date: Sun, 15 Mar 2026 15:53:30 +0530 Subject: [PATCH 11/25] chore: ai panel show more buttons only on hover --- src/styles/Extn-AIChatPanel.less | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/styles/Extn-AIChatPanel.less b/src/styles/Extn-AIChatPanel.less index 86fd45826..fc057d2a4 100644 --- a/src/styles/Extn-AIChatPanel.less +++ b/src/styles/Extn-AIChatPanel.less @@ -82,6 +82,9 @@ display: flex; align-items: center; gap: 2px; + opacity: 0; + pointer-events: none; + transition: opacity 0.5s ease; } .ai-history-btn { @@ -122,9 +125,8 @@ height: 26px; border-radius: 3px; cursor: pointer; - opacity: 0; + opacity: 0.7; transition: opacity 0.15s ease, background-color 0.15s ease; - pointer-events: none; &:hover { opacity: 1; @@ -153,12 +155,14 @@ } } -/* Show settings gear on tab container hover */ -.ai-tab-container:hover .ai-settings-btn { - opacity: 0.7; +/* Show header actions on tab container hover */ +.ai-tab-container:hover .ai-chat-header-actions { + opacity: 1; pointer-events: auto; + transition: opacity 0.15s ease; } + /* ── Session history dropdown ──────────────────────────────────────── */ /* When history is open, hide chat content and show the dropdown instead */ .ai-chat-panel.ai-history-open { From 609cef6d14cceebee10623a9876a9d8ad12e74b5 Mon Sep 17 00:00:00 2001 From: abose Date: Sun, 15 Mar 2026 16:12:23 +0530 Subject: [PATCH 12/25] fix: ai header buttons use btn-alt-quiet style and left-align title on narrow sidebar --- src/styles/Extn-AIChatPanel.less | 56 ++++++-------------------------- 1 file changed, 10 insertions(+), 46 deletions(-) diff --git a/src/styles/Extn-AIChatPanel.less b/src/styles/Extn-AIChatPanel.less index fc057d2a4..c0def638c 100644 --- a/src/styles/Extn-AIChatPanel.less +++ b/src/styles/Extn-AIChatPanel.less @@ -39,6 +39,7 @@ background-color: @bc-sidebar-bg; color: @project-panel-text-1; font-size: @sidebar-content-font-size; + container-type: inline-size; } /* ── Header ─────────────────────────────────────────────────────────── */ @@ -87,71 +88,27 @@ transition: opacity 0.5s ease; } - .ai-history-btn { - display: flex; - align-items: center; - justify-content: center; - background: none; - border: none; - color: @project-panel-text-2; - font-size: @menu-item-font-size; - width: 26px; - height: 26px; - border-radius: 3px; - cursor: pointer; - opacity: 0.7; - transition: opacity 0.15s ease, background-color 0.15s ease; - - &:hover { - opacity: 1; - background-color: rgba(255, 255, 255, 0.06); - } - - &.active { - opacity: 1; - background-color: rgba(255, 255, 255, 0.08); - } - } - + .ai-history-btn, .ai-settings-btn { display: flex; align-items: center; justify-content: center; - background: none; - border: none; color: @project-panel-text-2; font-size: @menu-item-font-size; width: 26px; height: 26px; - border-radius: 3px; cursor: pointer; - opacity: 0.7; - transition: opacity 0.15s ease, background-color 0.15s ease; - - &:hover { - opacity: 1; - background-color: rgba(255, 255, 255, 0.06); - } } .ai-new-session-btn { display: flex; align-items: center; gap: 4px; - background: none; - border: none; color: @project-panel-text-2; font-size: @menu-item-font-size; + height: 26px; padding: 0 8px; - border-radius: 3px; cursor: pointer; - opacity: 0.7; - transition: opacity 0.15s ease, background-color 0.15s ease; - - &:hover { - opacity: 1; - background-color: rgba(255, 255, 255, 0.06); - } } } @@ -163,6 +120,13 @@ } +/* Left-align title when sidebar is narrow to free space for action buttons */ +@container (max-width: 380px) { + .ai-chat-header { + justify-content: flex-start; + } +} + /* ── Session history dropdown ──────────────────────────────────────── */ /* When history is open, hide chat content and show the dropdown instead */ .ai-chat-panel.ai-history-open { From 0dcde4589a55b53f118c406742998a152502557f Mon Sep 17 00:00:00 2001 From: abose Date: Sun, 15 Mar 2026 17:00:59 +0530 Subject: [PATCH 13/25] build: update pro deps --- src-node/package-lock.json | 1 - tracking-repos.json | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src-node/package-lock.json b/src-node/package-lock.json index ebbaa71bc..9f0b4d406 100644 --- a/src-node/package-lock.json +++ b/src-node/package-lock.json @@ -2647,7 +2647,6 @@ "version": "4.0.3", "inBundle": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, diff --git a/tracking-repos.json b/tracking-repos.json index 1cebaf842..9301b35e6 100644 --- a/tracking-repos.json +++ b/tracking-repos.json @@ -1,5 +1,5 @@ { "phoenixPro": { - "commitID": "e1b145089cb7c242689fdeec3cc138445b9928e2" + "commitID": "dc54c66e3a6de664210c743bdda4f0f796cf3c0b" } } From 2f7063860b5d24da825d0196bafeb4e96d770cc1 Mon Sep 17 00:00:00 2001 From: abose Date: Sun, 15 Mar 2026 18:05:30 +0530 Subject: [PATCH 14/25] fix: prod build fails --- gulpfile.js/index.js | 10 +++++++++- src/command/DefaultMenus.js | 4 +++- tracking-repos.json | 2 +- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/gulpfile.js/index.js b/gulpfile.js/index.js index 9eec937fa..e61dc4541 100644 --- a/gulpfile.js/index.js +++ b/gulpfile.js/index.js @@ -610,6 +610,9 @@ function containsRegExpExcludingEmpty(str) { const minifyablePaths = [ 'src/extensionsIntegrated/phoenix-pro/browser-context' ]; +const noMinifyFiles = [ + 'src/extensionsIntegrated/phoenix-pro/browser-context/control-box.js' +]; function _minifyBrowserContextFile(fileContent) { const minified = terser.minify(fileContent, { @@ -636,6 +639,9 @@ function _minifyBrowserContextFile(fileContent) { function _isMinifyablePath(filePath) { const normalizedFilePath = path.normalize(filePath); + if (noMinifyFiles.some(f => normalizedFilePath.endsWith(path.normalize(f)))) { + return false; + } return minifyablePaths.some(minifyPath => normalizedFilePath.startsWith(path.normalize(minifyPath)) ); @@ -690,7 +696,9 @@ function inlineTextRequire(file, content, srcDir, isDevBuild = true) { throw `Error inlining ${requireStatement} in ${file}: Regex: ${detectedRegEx}`+ "\nRegular expression of the form /*/ is not allowed for minification please use RegEx constructor"; } - content = content.replaceAll(requireStatement, `${JSON.stringify(textContent)}`); + // Escape $ in replacement to prevent special replacement patterns ($&, $1, etc.) + const safeReplacement = JSON.stringify(textContent).replaceAll("$", "$$$$"); + content = content.replaceAll(requireStatement, safeReplacement); } } diff --git a/src/command/DefaultMenus.js b/src/command/DefaultMenus.js index c803191e3..6361f3cb9 100644 --- a/src/command/DefaultMenus.js +++ b/src/command/DefaultMenus.js @@ -231,7 +231,9 @@ define(function (require, exports, module) { menu.addMenuItem(Commands.TOGGLE_RULERS); menu.addMenuDivider(); menu.addMenuItem(Commands.VIEW_TOGGLE_PROBLEMS); - menu.addMenuItem(Commands.VIEW_TERMINAL); + if (Phoenix.isNativeApp) { + menu.addMenuItem(Commands.VIEW_TERMINAL); + } menu.addMenuItem(Commands.VIEW_TOGGLE_INSPECTION); /* diff --git a/tracking-repos.json b/tracking-repos.json index 9301b35e6..cf9fb4dd8 100644 --- a/tracking-repos.json +++ b/tracking-repos.json @@ -1,5 +1,5 @@ { "phoenixPro": { - "commitID": "dc54c66e3a6de664210c743bdda4f0f796cf3c0b" + "commitID": "e576b046d4ab2941be70582b5acc641996f1e42d" } } From 96e511a7bca641afda44a8e6cf62c0426ae01098 Mon Sep 17 00:00:00 2001 From: abose Date: Sun, 15 Mar 2026 18:25:41 +0530 Subject: [PATCH 15/25] fix: prod build live preview edit fails --- gulpfile.js/index.js | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/gulpfile.js/index.js b/gulpfile.js/index.js index e61dc4541..e134fb1d7 100644 --- a/gulpfile.js/index.js +++ b/gulpfile.js/index.js @@ -610,13 +610,9 @@ function containsRegExpExcludingEmpty(str) { const minifyablePaths = [ 'src/extensionsIntegrated/phoenix-pro/browser-context' ]; -const noMinifyFiles = [ - 'src/extensionsIntegrated/phoenix-pro/browser-context/control-box.js' -]; - function _minifyBrowserContextFile(fileContent) { const minified = terser.minify(fileContent, { - mangle: true, + mangle: false, compress: { unused: false }, @@ -639,9 +635,6 @@ function _minifyBrowserContextFile(fileContent) { function _isMinifyablePath(filePath) { const normalizedFilePath = path.normalize(filePath); - if (noMinifyFiles.some(f => normalizedFilePath.endsWith(path.normalize(f)))) { - return false; - } return minifyablePaths.some(minifyPath => normalizedFilePath.startsWith(path.normalize(minifyPath)) ); From 31eb7ad7fde068386b8a553ca8cc733417dbc0a0 Mon Sep 17 00:00:00 2001 From: abose Date: Sun, 15 Mar 2026 19:43:38 +0530 Subject: [PATCH 16/25] build: update pro deps --- src-node/package-lock.json | 1 + src/nls/root/strings.js | 4 ++-- tracking-repos.json | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src-node/package-lock.json b/src-node/package-lock.json index 9f0b4d406..ebbaa71bc 100644 --- a/src-node/package-lock.json +++ b/src-node/package-lock.json @@ -2647,6 +2647,7 @@ "version": "4.0.3", "inBundle": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index 0692ad14d..808c76fd0 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -240,8 +240,8 @@ define({ "LIVE_DEV_IMAGE_FOLDER_DIALOG_PLACEHOLDER": "Type folder path (e.g., assets/images/)", "LIVE_DEV_IMAGE_FOLDER_DIALOG_HELP": "💡 Type folder path or leave empty to download in 'images' folder.", "LIVE_DEV_IMAGE_FOLDER_DIALOG_REMEMBER": "Don't ask again for this project", - "DEVICE_SIZE_LIMIT_TITLE": "Responsive Preview Limit Reached", - "DEVICE_SIZE_LIMIT_MESSAGE": "Free accounts get a few responsive previews per day. Upgrade to Phoenix Pro for unlimited responsive previews across all device sizes.", + "DEVICE_SIZE_LIMIT_TITLE": "Available in Phoenix Pro", + "DEVICE_SIZE_LIMIT_MESSAGE": "Phoenix Pro lets you preview your page at the screen sizes defined in your CSS.", "IMAGE_SEARCH_LIMIT_TITLE": "Image search limit reached", "IMAGE_SEARCH_LIMIT_MESSAGE": "You’ve used all {0} image searches for this month.
Start a paid Phoenix Pro plan to remove trial limits and continue searching.", "IMAGE_SEARCH_LIMIT_MESSAGE_THROTTLE": "Image search is temporarily unavailable due to high demand.
Start a paid Phoenix Pro plan to remove trial limits and continue searching.", diff --git a/tracking-repos.json b/tracking-repos.json index cf9fb4dd8..81e2f6e71 100644 --- a/tracking-repos.json +++ b/tracking-repos.json @@ -1,5 +1,5 @@ { "phoenixPro": { - "commitID": "e576b046d4ab2941be70582b5acc641996f1e42d" + "commitID": "9e7f259b105c24355cfa1930b9cb7ed1b5a686f6" } } From 667eec8334ab5dd940747916395b5257a680d6b1 Mon Sep 17 00:00:00 2001 From: abose Date: Sun, 15 Mar 2026 19:59:16 +0530 Subject: [PATCH 17/25] build: update pro deps --- tracking-repos.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tracking-repos.json b/tracking-repos.json index 81e2f6e71..30ad060d0 100644 --- a/tracking-repos.json +++ b/tracking-repos.json @@ -1,5 +1,5 @@ { "phoenixPro": { - "commitID": "9e7f259b105c24355cfa1930b9cb7ed1b5a686f6" + "commitID": "28437fa2ee1c78f7bf95b5c32237794e8bd82a24" } } From 62f9bd4e8f3195e9a654cd1c92a2432d09cd1c11 Mon Sep 17 00:00:00 2001 From: abose Date: Sun, 15 Mar 2026 20:48:20 +0530 Subject: [PATCH 18/25] build: update pro deps --- tracking-repos.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tracking-repos.json b/tracking-repos.json index 30ad060d0..50016da60 100644 --- a/tracking-repos.json +++ b/tracking-repos.json @@ -1,5 +1,5 @@ { "phoenixPro": { - "commitID": "28437fa2ee1c78f7bf95b5c32237794e8bd82a24" + "commitID": "ca8641e99f5954e259d1408d8bda62a5a16d4384" } } From 460e876b073f28f10bcd47aa46478ac7506c3428 Mon Sep 17 00:00:00 2001 From: abose Date: Sun, 15 Mar 2026 22:12:20 +0530 Subject: [PATCH 19/25] feat(ai): gracefully handle plan mode in AI chat Intercept EnterPlanMode/ExitPlanMode tools from Claude Code SDK. Capture plan content from Write to .claude/plans/, display as a styled card with Approve/Revise buttons. On approve, send follow-up prompt to proceed with implementation. On revise, abort and send user feedback as a new prompt. Includes i18n strings and plan card CSS. --- src-node/claude-code-agent.js | 115 +++++++++++++++++++- src/nls/root/strings.js | 5 + src/styles/Extn-AIChatPanel.less | 176 +++++++++++++++++++++++++++++++ 3 files changed, 295 insertions(+), 1 deletion(-) diff --git a/src-node/claude-code-agent.js b/src-node/claude-code-agent.js index ddce86626..fa33ec65b 100644 --- a/src-node/claude-code-agent.js +++ b/src-node/claude-code-agent.js @@ -57,6 +57,18 @@ const TEXT_STREAM_THROTTLE_MS = 50; // Pending question resolver — used by AskUserQuestion hook let _questionResolve = null; +// Pending plan resolver — used by ExitPlanMode stream interception +let _planResolve = null; + +// Stores rejection feedback when user rejects a plan +let _planRejectionFeedback = null; + +// Stores the last plan content written to .claude/plans/ +let _lastPlanContent = null; + +// Flag set when user approves a plan +let _planApproved = false; + // Queued clarification from the user (typed while AI is streaming) // Shape: { text: string, images: [{mediaType, base64Data}] } or null let _queuedClarification = null; @@ -257,8 +269,9 @@ exports.cancelQuery = async function () { currentAbortController = null; // Clear session so next query starts fresh instead of resuming a killed session currentSessionId = null; - // Clear any pending question + // Clear any pending question or plan _questionResolve = null; + _planResolve = null; _queuedClarification = null; return { success: true }; } @@ -277,6 +290,18 @@ exports.answerQuestion = async function (params) { return { success: true }; }; +/** + * Receive the user's response to a proposed plan. + * Called from browser via execPeer("answerPlan", {approved, feedback}). + */ +exports.answerPlan = async function (params) { + if (_planResolve) { + _planResolve(params); + _planResolve = null; + } + return { success: true }; +}; + /** * Resume a previous session by setting the session ID. * The next sendPrompt call will use queryOptions.resume with this session ID. @@ -287,6 +312,7 @@ exports.resumeSession = async function (params) { currentAbortController = null; } _questionResolve = null; + _planResolve = null; _queuedClarification = null; currentSessionId = params.sessionId; return { success: true }; @@ -396,6 +422,7 @@ async function _runQuery(requestId, prompt, projectPath, model, signal, locale, "AskUserQuestion", "Task", "TodoRead", "TodoWrite", "WebFetch", "WebSearch", + "EnterPlanMode", "ExitPlanMode", "mcp__phoenix-editor__getEditorState", "mcp__phoenix-editor__takeScreenshot", "mcp__phoenix-editor__execJsInLivePreview", @@ -551,6 +578,13 @@ async function _runQuery(requestId, prompt, projectPath, model, signal, locale, hooks: [ async (input) => { console.log("[Phoenix AI] Intercepted Write tool"); + // Capture plan content when writing to .claude/plans/ + const writePath = input.tool_input.file_path || ""; + if (writePath.includes("/.claude/plans/")) { + _lastPlanContent = input.tool_input.content || ""; + console.log("[Phoenix AI] Captured plan content:", + _lastPlanContent.length + "ch"); + } const myToolId = toolCounter; // capture before any await const edit = { file: input.tool_input.file_path, @@ -927,6 +961,45 @@ async function _runQuery(requestId, prompt, projectPath, model, signal, locale, toolId: toolCounter, toolInput: toolInput }); + + // ExitPlanMode: show plan to user and wait for approval + // Plan text comes from a prior Write to .claude/plans/ (captured in hook) + if (activeToolName === "ExitPlanMode") { + const planText = toolInput.plan || _lastPlanContent || ""; + _lastPlanContent = null; + if (planText) { + _log("ExitPlanMode plan detected (" + planText.length + "ch), sending to browser"); + nodeConnector.triggerPeer("aiPlanProposed", { + requestId: requestId, + plan: planText + }); + // Pause stream processing until user approves/rejects + const planResponse = await new Promise((resolve, reject) => { + _planResolve = resolve; + if (signal.aborted) { + _planResolve = null; + reject(new Error("Aborted")); + return; + } + const onAbort = () => { + _planResolve = null; + reject(new Error("Aborted")); + }; + signal.addEventListener("abort", onAbort, { once: true }); + }); + if (!planResponse.approved) { + _log("Plan rejected by user, aborting"); + currentAbortController.abort(); + _planRejectionFeedback = planResponse.feedback || ""; + } else { + _log("Plan approved by user, will send proceed prompt"); + _planApproved = true; + } + } else { + _log("ExitPlanMode with no plan content, skipping UI"); + } + } + activeToolName = null; activeToolIndex = null; activeToolInputJson = ""; @@ -965,6 +1038,32 @@ async function _runQuery(requestId, prompt, projectPath, model, signal, locale, _log("Complete: tools=" + toolCounter, "edits=" + editCount, "textDeltas=" + textDeltaCount, "textSent=" + textStreamSendCount); + // Check if plan was approved — send follow-up to proceed with implementation + if (_planApproved) { + _planApproved = false; + _log("Plan approved, sending proceed prompt"); + nodeConnector.triggerPeer("aiComplete", { + requestId: requestId, + sessionId: currentSessionId, + planApproved: true + }); + return; + } + + // Check if stream ended due to plan rejection (abort + break) + if (_planRejectionFeedback !== null) { + const feedback = _planRejectionFeedback; + _planRejectionFeedback = null; + _log("Plan rejected, sending revision request"); + nodeConnector.triggerPeer("aiComplete", { + requestId: requestId, + sessionId: currentSessionId, + planRejected: true, + planFeedback: feedback + }); + return; + } + // Signal completion nodeConnector.triggerPeer("aiComplete", { requestId: requestId, @@ -977,6 +1076,20 @@ async function _runQuery(requestId, prompt, projectPath, model, signal, locale, const isAbort = signal.aborted || /abort/i.test(errMsg); if (isAbort) { + // Check if this was a plan rejection — if so, send feedback as follow-up + if (_planRejectionFeedback !== null) { + const feedback = _planRejectionFeedback; + _planRejectionFeedback = null; + _log("Plan rejected, sending revision request"); + // Don't clear session — resume with feedback + nodeConnector.triggerPeer("aiComplete", { + requestId: requestId, + sessionId: currentSessionId, + planRejected: true, + planFeedback: feedback + }); + return; + } _log("Cancelled"); // Send sessionId so browser side can save partial history for later resume const cancelledSessionId = currentSessionId; diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index 808c76fd0..18d6cd7dc 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -1919,6 +1919,11 @@ define({ "AI_CHAT_TOOL_QUESTION": "Question", "AI_CHAT_TOOL_TASK": "Subagent", "AI_CHAT_TOOL_TASK_NAME": "Subagent: {0}", + "AI_CHAT_TOOL_PLANNING": "Planning", + "AI_CHAT_PLAN_TITLE": "Proposed Plan", + "AI_CHAT_PLAN_APPROVE": "Approve", + "AI_CHAT_PLAN_REVISE": "Revise", + "AI_CHAT_PLAN_FEEDBACK_PLACEHOLDER": "What would you like changed?", "AI_CHAT_QUESTION_OTHER": "Type a custom answer\u2026", "AI_CHAT_IMAGE_LIMIT": "Maximum {0} images allowed", "AI_CHAT_IMAGE_REMOVE": "Remove image", diff --git a/src/styles/Extn-AIChatPanel.less b/src/styles/Extn-AIChatPanel.less index c0def638c..721f9de7c 100644 --- a/src/styles/Extn-AIChatPanel.less +++ b/src/styles/Extn-AIChatPanel.less @@ -1076,6 +1076,182 @@ } } +/* ── Plan card (ExitPlanMode) ───────────────────────────────────────── */ +.ai-msg-plan { + margin-bottom: 8px; + border: 1px solid rgba(107, 158, 255, 0.25); + border-radius: 6px; + background-color: rgba(107, 158, 255, 0.04); + overflow: hidden; +} + +.ai-plan-header { + display: flex; + align-items: center; + gap: 6px; + padding: 8px 12px; + background-color: rgba(107, 158, 255, 0.08); + border-bottom: 1px solid rgba(107, 158, 255, 0.15); + color: #6b9eff; + font-size: @sidebar-content-font-size; + font-weight: 600; +} + +.ai-plan-body { + padding: 10px 12px; + font-size: @sidebar-content-font-size; + color: @project-panel-text-1; + line-height: 1.5; + white-space: normal; + word-wrap: break-word; + max-height: 400px; + overflow-y: auto; + + p, ul, ol, pre { + margin-bottom: 8px; + &:last-child { + margin-bottom: 0; + } + } + + code { + background: rgba(255, 255, 255, 0.06); + padding: 1px 4px; + border-radius: 3px; + font-size: 0.9em; + } + + pre { + background: rgba(0, 0, 0, 0.2); + padding: 8px; + border-radius: 4px; + overflow-x: auto; + + code { + background: none; + padding: 0; + } + } +} + +.ai-plan-actions { + display: flex; + gap: 8px; + padding: 8px 12px; + border-top: 1px solid rgba(107, 158, 255, 0.1); +} + +.ai-plan-approve-btn { + background: rgba(76, 175, 80, 0.15); + border: 1px solid rgba(76, 175, 80, 0.35); + color: #4caf50; + font-size: @sidebar-content-font-size; + padding: 5px 14px; + border-radius: 4px; + cursor: pointer; + transition: background-color 0.15s ease; + + &:hover:not(:disabled) { + background: rgba(76, 175, 80, 0.25); + } + + &.selected { + background: rgba(76, 175, 80, 0.2); + border-color: rgba(76, 175, 80, 0.5); + } + + &:disabled { + opacity: 0.5; + cursor: default; + } +} + +.ai-plan-revise-btn { + background: rgba(255, 255, 255, 0.04); + border: 1px solid rgba(255, 255, 255, 0.12); + color: @project-panel-text-2; + font-size: @sidebar-content-font-size; + padding: 5px 14px; + border-radius: 4px; + cursor: pointer; + transition: background-color 0.15s ease; + + &:hover:not(:disabled) { + background: rgba(255, 255, 255, 0.08); + } + + &.selected { + background: rgba(107, 158, 255, 0.1); + border-color: rgba(107, 158, 255, 0.3); + color: #6b9eff; + } + + &:disabled { + opacity: 0.5; + cursor: default; + } +} + +.ai-plan-feedback { + padding: 8px 12px; + border-top: 1px solid rgba(107, 158, 255, 0.1); + display: flex; + gap: 6px; + align-items: stretch; +} + +.ai-plan-feedback-input { + flex: 1; + min-width: 0; + background: rgba(255, 255, 255, 0.04); + border: 1px solid rgba(255, 255, 255, 0.1) !important; + border-radius: 4px; + color: @project-panel-text-1; + font-size: @sidebar-content-font-size; + padding: 6px 10px !important; + margin: 0 !important; + line-height: 1.4; + height: auto !important; + min-height: 32px; + max-height: 80px; + resize: vertical; + box-sizing: content-box; + outline: none !important; + box-shadow: none !important; + + &:focus { + border-color: @bc-btn-border-focused !important; + } + + &:disabled { + opacity: 0.5; + cursor: not-allowed; + } +} + +.ai-plan-feedback-send { + background: rgba(107, 158, 255, 0.15); + border: 1px solid rgba(107, 158, 255, 0.3); + color: #6b9eff; + padding: 0 10px; + border-radius: 4px; + cursor: pointer; + flex-shrink: 0; + display: flex; + align-items: center; + justify-content: center; + transition: background-color 0.15s ease; + + &:hover:not(:disabled) { + background: rgba(107, 158, 255, 0.25); + } + + &:disabled { + opacity: 0.4; + cursor: default; + } +} + /* ── Queued clarification bubble (static, above input) ─────────────── */ .ai-queued-msg { border: 1px dashed rgba(255, 255, 255, 0.15); From 81c3ecf73c52a4bcd9025ed9393c1329603c86ea Mon Sep 17 00:00:00 2001 From: abose Date: Sun, 15 Mar 2026 22:18:43 +0530 Subject: [PATCH 20/25] fix(ai): skip opening plan files in editor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plan files written to .claude/plans/ are now intercepted early in the Write hook — content is captured for the plan card UI but the file is not opened in the editor, avoiding clutter from out-of-project files. --- src-node/claude-code-agent.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src-node/claude-code-agent.js b/src-node/claude-code-agent.js index fa33ec65b..45adf9fea 100644 --- a/src-node/claude-code-agent.js +++ b/src-node/claude-code-agent.js @@ -579,11 +579,29 @@ async function _runQuery(requestId, prompt, projectPath, model, signal, locale, async (input) => { console.log("[Phoenix AI] Intercepted Write tool"); // Capture plan content when writing to .claude/plans/ + // Don't open plan files in editor — shown in plan card UI const writePath = input.tool_input.file_path || ""; if (writePath.includes("/.claude/plans/")) { _lastPlanContent = input.tool_input.content || ""; console.log("[Phoenix AI] Captured plan content:", _lastPlanContent.length + "ch"); + if (_queuedClarification) { + return { + hookSpecificOutput: { + hookEventName: "PreToolUse", + permissionDecision: "deny", + permissionDecisionReason: + "Plan file saved." + CLARIFICATION_HINT + } + }; + } + return { + hookSpecificOutput: { + hookEventName: "PreToolUse", + permissionDecision: "deny", + permissionDecisionReason: "Plan file saved." + } + }; } const myToolId = toolCounter; // capture before any await const edit = { From 195702832f33edb64c1bb960cb1c39f4f99aeddd Mon Sep 17 00:00:00 2001 From: abose Date: Sun, 15 Mar 2026 22:21:58 +0530 Subject: [PATCH 21/25] feat(ai): add verification hint to system prompt for plan mode Tell Claude about execJsInLivePreview and takeScreenshot in the system prompt so it can include meaningful verification steps when planning. --- src-node/claude-code-agent.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src-node/claude-code-agent.js b/src-node/claude-code-agent.js index 45adf9fea..4d031c4a6 100644 --- a/src-node/claude-code-agent.js +++ b/src-node/claude-code-agent.js @@ -470,6 +470,9 @@ async function _runQuery(requestId, prompt, projectPath, model, signal, locale, "controlEditor). Never use relative paths." + "\n\nWhen a tool response mentions the user has typed a clarification, immediately " + "call getUserClarification to read it and incorporate the user's feedback into your current work." + + "\n\nWhen planning, include a verification step that uses execJsInLivePreview " + + "and takeScreenshot to confirm the result visually. These tools let you run JS " + + "in the browser live preview and capture screenshots for verification." + (locale && !locale.startsWith("en") ? "\n\nThe user's display language is " + locale + ". " + "Respond in this language unless they write in a different language." From 04a3e99e36b0acd4dba8bb0a1ac7464e5d1bb86e Mon Sep 17 00:00:00 2001 From: abose Date: Sun, 15 Mar 2026 22:22:42 +0530 Subject: [PATCH 22/25] fix(ai): scope verification hint to live preview HTML work Only suggest execJsInLivePreview and takeScreenshot for verification when working with HTML/CSS/JS in live preview, not generically. --- src-node/claude-code-agent.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src-node/claude-code-agent.js b/src-node/claude-code-agent.js index 4d031c4a6..a4c3cd604 100644 --- a/src-node/claude-code-agent.js +++ b/src-node/claude-code-agent.js @@ -470,9 +470,9 @@ async function _runQuery(requestId, prompt, projectPath, model, signal, locale, "controlEditor). Never use relative paths." + "\n\nWhen a tool response mentions the user has typed a clarification, immediately " + "call getUserClarification to read it and incorporate the user's feedback into your current work." + - "\n\nWhen planning, include a verification step that uses execJsInLivePreview " + - "and takeScreenshot to confirm the result visually. These tools let you run JS " + - "in the browser live preview and capture screenshots for verification." + + "\n\nWhen planning, include a verification step. For HTML/CSS/JS work with " + + "live preview, you can use execJsInLivePreview to run JS in the browser and " + + "takeScreenshot to confirm the result visually." + (locale && !locale.startsWith("en") ? "\n\nThe user's display language is " + locale + ". " + "Respond in this language unless they write in a different language." From b98d16dcee7f8c4449c100f6c5530426ed738ebc Mon Sep 17 00:00:00 2001 From: abose Date: Sun, 15 Mar 2026 22:24:04 +0530 Subject: [PATCH 23/25] fix(ai): clarify takeScreenshot works globally, not just live preview --- src-node/claude-code-agent.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src-node/claude-code-agent.js b/src-node/claude-code-agent.js index a4c3cd604..0fd1dc072 100644 --- a/src-node/claude-code-agent.js +++ b/src-node/claude-code-agent.js @@ -470,9 +470,9 @@ async function _runQuery(requestId, prompt, projectPath, model, signal, locale, "controlEditor). Never use relative paths." + "\n\nWhen a tool response mentions the user has typed a clarification, immediately " + "call getUserClarification to read it and incorporate the user's feedback into your current work." + - "\n\nWhen planning, include a verification step. For HTML/CSS/JS work with " + - "live preview, you can use execJsInLivePreview to run JS in the browser and " + - "takeScreenshot to confirm the result visually." + + "\n\nWhen planning, include a verification step. Use takeScreenshot to visually " + + "verify the editor state at any time. For HTML/CSS/JS work with live preview, " + + "also use execJsInLivePreview to run JS in the browser and confirm behavior." + (locale && !locale.startsWith("en") ? "\n\nThe user's display language is " + locale + ". " + "Respond in this language unless they write in a different language." From 1e9df98a24596d8dd4457ac54b4b223a37e58c66 Mon Sep 17 00:00:00 2001 From: abose Date: Sun, 15 Mar 2026 22:25:05 +0530 Subject: [PATCH 24/25] fix(ai): let Claude decide if verification tools are needed --- src-node/claude-code-agent.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src-node/claude-code-agent.js b/src-node/claude-code-agent.js index 0fd1dc072..83403e9bf 100644 --- a/src-node/claude-code-agent.js +++ b/src-node/claude-code-agent.js @@ -470,9 +470,9 @@ async function _runQuery(requestId, prompt, projectPath, model, signal, locale, "controlEditor). Never use relative paths." + "\n\nWhen a tool response mentions the user has typed a clarification, immediately " + "call getUserClarification to read it and incorporate the user's feedback into your current work." + - "\n\nWhen planning, include a verification step. Use takeScreenshot to visually " + - "verify the editor state at any time. For HTML/CSS/JS work with live preview, " + - "also use execJsInLivePreview to run JS in the browser and confirm behavior." + + "\n\nWhen planning, consider if verification is needed. takeScreenshot can " + + "visually verify the editor state. For HTML/CSS/JS with live preview, " + + "execJsInLivePreview can run JS in the browser to confirm behavior." + (locale && !locale.startsWith("en") ? "\n\nThe user's display language is " + locale + ". " + "Respond in this language unless they write in a different language." From d330d646e7c92155bfba9d88dd331a1b4a702463 Mon Sep 17 00:00:00 2001 From: abose Date: Sun, 15 Mar 2026 22:26:23 +0530 Subject: [PATCH 25/25] fix(ai): mention takeScreenshot can target specific panels --- src-node/claude-code-agent.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src-node/claude-code-agent.js b/src-node/claude-code-agent.js index 83403e9bf..108bad9d1 100644 --- a/src-node/claude-code-agent.js +++ b/src-node/claude-code-agent.js @@ -471,8 +471,9 @@ async function _runQuery(requestId, prompt, projectPath, model, signal, locale, "\n\nWhen a tool response mentions the user has typed a clarification, immediately " + "call getUserClarification to read it and incorporate the user's feedback into your current work." + "\n\nWhen planning, consider if verification is needed. takeScreenshot can " + - "visually verify the editor state. For HTML/CSS/JS with live preview, " + - "execJsInLivePreview can run JS in the browser to confirm behavior." + + "capture the full editor, specific panels, the code area, or the live preview. " + + "For HTML/CSS/JS with live preview, execJsInLivePreview can run JS in the " + + "browser to confirm behavior." + (locale && !locale.startsWith("en") ? "\n\nThe user's display language is " + locale + ". " + "Respond in this language unless they write in a different language."