From 60b7c5d2339924c9de2ea5babf59152f1bbff413 Mon Sep 17 00:00:00 2001 From: abose Date: Tue, 24 Feb 2026 10:50:01 +0530 Subject: [PATCH 1/5] fix(ai): prevent UI flicker on entitlement refresh and enforce absolute paths Track current entitlement state so _checkEntitlementAndInit skips re-rendering when the state hasn't changed (e.g. background token refresh). Also add system prompt instruction to always use full absolute paths for file operations. --- src-node/claude-code-agent.js | 2 ++ src/core-ai/AIChatPanel.js | 32 ++++++++++++++++++++++++++------ 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/src-node/claude-code-agent.js b/src-node/claude-code-agent.js index d863e1201..921c286a0 100644 --- a/src-node/claude-code-agent.js +++ b/src-node/claude-code-agent.js @@ -363,6 +363,8 @@ async function _runQuery(requestId, prompt, projectPath, model, signal, locale, "multiple Edit calls to make targeted changes rather than rewriting the entire " + "file with Write. This is critical because Write replaces the entire file content " + "which is slow and loses undo history." + + "\n\nAlways use full absolute paths for all file operations (Read, Edit, Write, " + + "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." + (locale && !locale.startsWith("en") diff --git a/src/core-ai/AIChatPanel.js b/src/core-ai/AIChatPanel.js index b6a6a616d..1d542c8ad 100644 --- a/src/core-ai/AIChatPanel.js +++ b/src/core-ai/AIChatPanel.js @@ -45,6 +45,7 @@ define(function (require, exports, module) { const _KernalModeTrust = window.KernalModeTrust; let _nodeConnector = null; + let _currentEntitlementState = null; // "chat" | "login" | "upsell" | "adminDisabled" | null let _isStreaming = false; let _queuedMessage = null; // text queued by user while AI is streaming let _currentRequestId = null; @@ -240,15 +241,20 @@ define(function (require, exports, module) { * upsell screen if no AI plan, or proceeds to CLI availability check if entitled. */ function _checkEntitlementAndInit() { - _removeCurrentPanel(); const EntitlementsManager = _KernalModeTrust && _KernalModeTrust.EntitlementsManager; if (!EntitlementsManager) { // No entitlement system (test env or dev) — skip straight to CLI check - _checkAvailability(); + if (_currentEntitlementState !== "chat") { + _removeCurrentPanel(); + _checkAvailability(); + } return; } if (!EntitlementsManager.isLoggedIn()) { - _renderLoginUI(); + if (_currentEntitlementState !== "login") { + _removeCurrentPanel(); + _renderLoginUI(); + } return; } // TODO: Switch to EntitlementsManager.getAIEntitlement() once AI entitlement is @@ -267,12 +273,21 @@ define(function (require, exports, module) { // }); EntitlementsManager.getLiveEditEntitlement().then(function (entitlement) { if (entitlement.activated) { - _checkAvailability(); + if (_currentEntitlementState !== "chat") { + _removeCurrentPanel(); + _checkAvailability(); + } } else { - _renderUpsellUI(entitlement); + if (_currentEntitlementState !== "upsell") { + _removeCurrentPanel(); + _renderUpsellUI(entitlement); + } } }).catch(function () { - _checkAvailability(); // fallback on error + if (_currentEntitlementState !== "chat") { + _removeCurrentPanel(); + _checkAvailability(); // fallback on error + } }); } @@ -280,6 +295,7 @@ define(function (require, exports, module) { * Render the login prompt UI (user not signed in). */ function _renderLoginUI() { + _currentEntitlementState = "login"; const html = '
' + '
' + @@ -302,6 +318,7 @@ define(function (require, exports, module) { * Render the upsell UI (user logged in but no AI plan). */ function _renderUpsellUI(entitlement) { + _currentEntitlementState = "upsell"; const html = '
' + '
' + @@ -325,6 +342,7 @@ define(function (require, exports, module) { * Render the admin-disabled UI (AI turned off by system administrator). */ function _renderAdminDisabledUI() { + _currentEntitlementState = "adminDisabled"; const html = '
' + '
' + @@ -359,6 +377,7 @@ define(function (require, exports, module) { * Render the full chat UI. */ function _renderChatUI() { + _currentEntitlementState = "chat"; $panel = $(PANEL_HTML); $messages = $panel.find(".ai-chat-messages"); $status = $panel.find(".ai-chat-status"); @@ -567,6 +586,7 @@ define(function (require, exports, module) { * Render the unavailable UI (CLI not found). */ function _renderUnavailableUI(error) { + _currentEntitlementState = "chat"; const $unavailable = $(UNAVAILABLE_HTML); $unavailable.find(".ai-retry-btn").on("click", function () { _checkAvailability(); From b22153f199c857cfe932c84fa8a4f5e50cd4dd77 Mon Sep 17 00:00:00 2001 From: abose Date: Tue, 24 Feb 2026 13:45:00 +0530 Subject: [PATCH 2/5] fix(ai): use CMD_ADD_TO_WORKINGSET_AND_OPEN for controlEditor open and add [AI Control] log prefix CMD_OPEN didn't reliably pin files to the working set, causing the AI's open-file tool to report success while the file wasn't actually visible. --- src/core-ai/aiPhoenixConnectors.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/core-ai/aiPhoenixConnectors.js b/src/core-ai/aiPhoenixConnectors.js index d29430f19..494a16e51 100644 --- a/src/core-ai/aiPhoenixConnectors.js +++ b/src/core-ai/aiPhoenixConnectors.js @@ -618,18 +618,18 @@ define(function (require, exports, module) { function controlEditor(params) { const deferred = new $.Deferred(); const vfsPath = SnapshotStore.realToVfsPath(params.filePath); - console.log("controlEditor:", params.operation, params.filePath, "-> vfs:", vfsPath); + console.log("[AI Control] controlEditor:", params.operation, params.filePath, "-> vfs:", vfsPath); function _resolve(result) { if (!result.success) { - console.error("controlEditor failed:", params.operation, vfsPath, result.error); + console.error("[AI Control] controlEditor failed:", params.operation, vfsPath, result.error); } deferred.resolve(result); } switch (params.operation) { case "open": - CommandManager.execute(Commands.CMD_OPEN, { fullPath: vfsPath }) + CommandManager.execute(Commands.CMD_ADD_TO_WORKINGSET_AND_OPEN, { fullPath: vfsPath }) .done(function () { _resolve({ success: true }); }) .fail(function (err) { _resolve({ success: false, error: String(err) }); }); break; From 5d19bea01c4e04d9e026d727b6bbc36e20d12bdc Mon Sep 17 00:00:00 2001 From: abose Date: Tue, 24 Feb 2026 13:56:04 +0530 Subject: [PATCH 3/5] feat(ai): add isDirty flag to working set and improve error logging Working set entries now return { path, isDirty } so the AI can see unsaved files. Added [AI Control] prefixed console.error to screenshot, edit, and live preview failure paths for better debuggability. --- src-node/mcp-editor-tools.js | 2 +- src/core-ai/aiPhoenixConnectors.js | 20 +++++++++++++++++--- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src-node/mcp-editor-tools.js b/src-node/mcp-editor-tools.js index 962456d68..d04b698ae 100644 --- a/src-node/mcp-editor-tools.js +++ b/src-node/mcp-editor-tools.js @@ -61,7 +61,7 @@ function createEditorMcpServer(sdkModule, nodeConnector, clarificationAccessors) const hasClarification = clarificationAccessors && clarificationAccessors.hasClarification; const getEditorStateTool = sdkModule.tool( "getEditorState", - "Get the current Phoenix editor state: active file, working set (open files), live preview file, " + + "Get the current Phoenix editor state: active file, working set (open files with isDirty flag), live preview file, " + "cursor/selection info (current line text with surrounding context, or selected text), " + "and the currently selected element in the live preview (tag, selector, text preview) if any. " + "The live preview selected element may differ from the editor cursor — use execJsInLivePreview to inspect it further. " + diff --git a/src/core-ai/aiPhoenixConnectors.js b/src/core-ai/aiPhoenixConnectors.js index 494a16e51..bc89b8a56 100644 --- a/src/core-ai/aiPhoenixConnectors.js +++ b/src/core-ai/aiPhoenixConnectors.js @@ -213,7 +213,9 @@ define(function (require, exports, module) { if (p.startsWith("/tauri/")) { p = p.replace("/tauri", ""); } - return p; + const doc = DocumentManager.getOpenDocumentForPath(file.fullPath); + const isDirty = doc ? doc.isDirty : false; + return { path: p, isDirty: isDirty }; }); let livePreviewFile = null; @@ -338,6 +340,7 @@ define(function (require, exports, module) { deferred.resolve({ base64: base64 }); }) .catch(function (err) { + console.error("[AI Control] Screenshot failed:", err); deferred.resolve({ base64: null, error: err.message || String(err) }); }); return deferred.promise(); @@ -350,6 +353,14 @@ define(function (require, exports, module) { return deferred.promise(); } + function _onResult(result) { + if (result.base64) { + console.log("[AI Control] Screenshot taken:", params.selector || "full window", + params.purePreview ? "(pure preview)" : ""); + } + deferred.resolve(result); + } + if (params.purePreview) { const previousMode = LiveDevMain.getCurrentMode(); LiveDevMain.setMode(LivePreviewConstants.LIVE_PREVIEW_MODE); @@ -358,12 +369,12 @@ define(function (require, exports, module) { _captureScreenshot(params.selector) .done(function (result) { LiveDevMain.setMode(previousMode); - deferred.resolve(result); + _onResult(result); }); }, 150); } else { _captureScreenshot(params.selector) - .done(function (result) { deferred.resolve(result); }); + .done(function (result) { _onResult(result); }); } return deferred.promise(); @@ -490,6 +501,7 @@ define(function (require, exports, module) { }); }) .fail(function (err) { + console.error("[AI Control] applyEditToBuffer failed:", err); deferred.resolve({ applied: false, error: err.message || String(err) }); }); return deferred.promise(); @@ -544,6 +556,7 @@ define(function (require, exports, module) { }) .fail(function (err) { _onExecJsDone(); + console.error("[AI Control] execJsInLivePreview failed:", err); deferred.resolve({ error: (err && err.message) || String(err) || "evaluate() failed" }); }); } @@ -581,6 +594,7 @@ define(function (require, exports, module) { if (settled) { return; } cleanup(); _onExecJsDone(); + console.error("[AI Control] Timed out waiting for live preview connection (30s)"); deferred.resolve({ error: "Timed out waiting for live preview connection (30s)" }); }, TIMEOUT); From da938d75b8d2c3da350f8b92272037831b865f15 Mon Sep 17 00:00:00 2001 From: abose Date: Tue, 24 Feb 2026 14:12:48 +0530 Subject: [PATCH 4/5] fix: mac puc rendering issues --- 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 ef56a4c16..8e047ce81 100644 --- a/src/styles/Extn-AIChatPanel.less +++ b/src/styles/Extn-AIChatPanel.less @@ -475,6 +475,7 @@ margin-bottom: 3px; border-radius: 4px; background-color: rgba(255, 255, 255, 0.025); + border: 1px solid rgba(255, 255, 255, 0.04); border-left: 2px solid var(--tool-color, @project-panel-text-2); .ai-tool-header { From dc527ae31f0c21a33e104d80948f1fd1eaef8a46 Mon Sep 17 00:00:00 2001 From: abose Date: Tue, 24 Feb 2026 14:17:56 +0530 Subject: [PATCH 5/5] fix: mac should be able to select ai panel text --- src/styles/Extn-AIChatPanel.less | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/styles/Extn-AIChatPanel.less b/src/styles/Extn-AIChatPanel.less index 8e047ce81..915bd0c60 100644 --- a/src/styles/Extn-AIChatPanel.less +++ b/src/styles/Extn-AIChatPanel.less @@ -243,6 +243,9 @@ padding: 8px 10px; min-height: 0; min-width: 0; + -webkit-user-select: text; + user-select: text; + cursor: default; } /* ── Individual messages ────────────────────────────────────────────── */ @@ -265,6 +268,7 @@ word-wrap: break-word; overflow-wrap: anywhere; min-width: 0; + -webkit-user-select: text; user-select: text; cursor: text; }