From 3d8bf6ed79fabc839e60729a8263a347bc357a7d Mon Sep 17 00:00:00 2001 From: abose Date: Sun, 26 Apr 2026 22:27:24 +0530 Subject: [PATCH 01/12] chore: muted style for restored edit-card stats; FS API note MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a CSS rule for .ai-tool-edit-stats so the per-edit +x/-y on restored cards reads with the same muted grey as the Show diff toggle (the .ai-msg-edit-summary-scoped green/red colors don't apply inside the tool indicator, leaving the default bright text otherwise). CLAUDE.md gains a short note on which file API to reach for — Phoenix.VFS.{read,write,unlink}Async for raw app data (no size cap, recursive dir delete) versus FileSystem.getFileForPath only for files that may be opened as editor documents. --- CLAUDE.md | 9 +++++++++ src/styles/Extn-AIChatPanel.less | 11 +++++++++++ 2 files changed, 20 insertions(+) diff --git a/CLAUDE.md b/CLAUDE.md index 6a0bc2647a..f127fea18c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -23,6 +23,15 @@ - **Exception — Markdown viewer iframe** (`src-mdviewer/`): Has its own i18n system. Strings go in `src-mdviewer/src/locales/en.json` (root), not `src/nls/`. Other locale files in that folder are auto-translated by GitHub Actions. Use `t("key")` / `tp("key", { param })` from `src-mdviewer/src/core/i18n.js`. - Never compare `$(el).text()` against English strings for logic — use data attributes or CSS classes instead. +## File I/O APIs — which to use + +Phoenix has two parallel file APIs. Pick the right one for the situation: + +- **`Phoenix.VFS.readFileAsync(path, encoding)` / `Phoenix.VFS.writeFileAsync(path, content, encoding)` / `Phoenix.VFS.unlinkAsync(path)`** — for raw app data (config files, session JSONs, caches, snapshots). No size cap. `unlinkAsync` removes non-empty directories recursively. +- **`FileSystem.getFileForPath(path).read/.write/.unlink`** (and `getDirectoryForPath`) — *only* for files that may be opened as documents in the editor. Goes through the document layer (mtime tracking, dirty-buffer reconciliation). Has a 16 MB cap on reads/writes. + +If a file is purely app-internal data and never edited by the user as a document, use the VFS APIs. Mixing them on the same file leads to mtime confusion and surprise size limits. + ## Phoenix MCP (Desktop App Testing) Use `exec_js` to run JS in the Phoenix browser runtime. jQuery `$()` is global. `brackets.test.*` exposes internal modules (DocumentManager, CommandManager, ProjectManager, FileSystem, EditorManager). Always `return` a value from `exec_js` to see results. Prefer reusing an already-running Phoenix instance (`get_phoenix_status`) over launching a new one. diff --git a/src/styles/Extn-AIChatPanel.less b/src/styles/Extn-AIChatPanel.less index d3f03e162e..9f3957b08a 100644 --- a/src/styles/Extn-AIChatPanel.less +++ b/src/styles/Extn-AIChatPanel.less @@ -688,6 +688,17 @@ } } + // Per-edit line stats shown next to the Show-diff toggle on + // restored cards. Match the toggle's muted look so the right edge + // reads as a single secondary-action cluster — without this the + // +x/-y use the panel's default bright text and clash. + .ai-tool-edit-stats { + font-size: @ai-text-meta; + color: @project-panel-text-2; + opacity: 0.65; + font-family: 'SourceCodePro-Medium', 'SourceCodePro', monospace; + } + .ai-tool-diff-more { background: none; border: none; From acf7c45cfbc1bf7bf4b97634d6b22c1016851771 Mon Sep 17 00:00:00 2001 From: abose Date: Sun, 26 Apr 2026 22:34:37 +0530 Subject: [PATCH 02/12] 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 49eec234e4..948f59e10e 100644 --- a/tracking-repos.json +++ b/tracking-repos.json @@ -1,5 +1,5 @@ { "phoenixPro": { - "commitID": "d175c1fb02062cd8ad27a3f942162383c132c630" + "commitID": "21f26650f613377219524b88ed23b394e69133a2" } } From 63dfc2f01655fb20750f6571033d02b1d59bbe76 Mon Sep 17 00:00:00 2001 From: abose Date: Sun, 26 Apr 2026 22:40:50 +0530 Subject: [PATCH 03/12] chore: nudge sidebar tabs to 12px and weight active state MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 0.8rem (≈11px) tab labels read as undersized chrome against the 14px panel content and didn't align with the secondary-tier scale used elsewhere. Bumps: - font-size 0.8rem → 0.85rem (~12px) - icon size 0.78rem → 0.82rem to track - vertical padding 0.25rem → 0.3rem so the slightly larger label isn't boxed in - active tab weight 500 → 600 so the selected state doesn't rely on color alone Affects every consumer of the global SidebarTabs component (Files, AI, extension sidebar tabs) uniformly. --- src/styles/Extn-SidebarTabs.less | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/styles/Extn-SidebarTabs.less b/src/styles/Extn-SidebarTabs.less index f16716c215..634a59bccf 100644 --- a/src/styles/Extn-SidebarTabs.less +++ b/src/styles/Extn-SidebarTabs.less @@ -42,16 +42,22 @@ justify-content: center; flex: 1 1 0; gap: 0.4rem; - padding: 0.25rem 0.75rem; + // Slightly more vertical padding so the larger label doesn't feel + // boxed in; horizontal stays the same. + padding: 0.3rem 0.75rem; cursor: pointer; color: @project-panel-text-2; - font-size: 0.8rem; + // Bumped from 0.8rem (~11px) to 0.85rem (~12px). 0.8rem read as + // chrome-too-small against the panel's 14px content; 12px aligns + // with the secondary-tier scale used elsewhere in the AI panel + // and matches modern editor tab-bar conventions. + font-size: 0.85rem; font-weight: 500; border-radius: 5px; transition: color 0.15s ease, background-color 0.15s ease, box-shadow 0.15s ease; i { - font-size: 0.78rem; + font-size: 0.82rem; opacity: 0.85; } @@ -63,6 +69,10 @@ &.active { color: @project-panel-text-1; background-color: rgba(255, 255, 255, 0.09); + // Heavier weight on the active tab so the selected state + // doesn't rely on color alone — strengthens the affordance + // without changing the silhouette. + font-weight: 600; box-shadow: 0 1px 0 rgba(255, 255, 255, 0.05) inset, 0 1px 2px rgba(0, 0, 0, 0.2); From a68a617d597b6e87d9356c33259f5822b0602096 Mon Sep 17 00:00:00 2001 From: abose Date: Mon, 27 Apr 2026 10:54:11 +0530 Subject: [PATCH 04/12] feat(ai-chat): wire additionalDirectories through the SDK + chip styling Threads the new attached-folders list from sendPrompt all the way to the SDK's additionalDirectories option. Validates each entry (absolute, exists, not a duplicate of cwd) and drops invalid ones silently so a typo doesn't blow up the query. Adds the matching styles: .ai-attach-dropup mirrors the screenshot dropup, and .ai-context-chip-folder gives the new folder chip an amber-tinted border and icon so it reads as a distinct kind of context vs file/selection/live-preview chips. Plus three new strings for the popup options and folder-picker title. --- src-node/claude-code-agent.js | 21 ++++++++++++++++++--- src/nls/root/strings.js | 4 ++++ src/styles/Extn-AIChatPanel.less | 14 ++++++++++++-- 3 files changed, 34 insertions(+), 5 deletions(-) diff --git a/src-node/claude-code-agent.js b/src-node/claude-code-agent.js index 22be2d86bf..577b5af4a1 100644 --- a/src-node/claude-code-agent.js +++ b/src-node/claude-code-agent.js @@ -248,7 +248,7 @@ exports.checkAvailability = async function () { * aiProgress, aiTextStream, aiToolEdit, aiError, aiComplete */ exports.sendPrompt = async function (params) { - const { prompt, projectPath, sessionAction, model, locale, selectionContext, images, envOverrides, permissionMode } = params; + const { prompt, projectPath, sessionAction, model, locale, selectionContext, images, envOverrides, permissionMode, additionalDirectories } = params; const requestId = Date.now().toString(36) + Math.random().toString(36).slice(2, 7); // Handle session @@ -291,7 +291,7 @@ exports.sendPrompt = async function (params) { } // Run the query asynchronously — don't await here so we return requestId immediately - _runQuery(requestId, enrichedPrompt, projectPath, model, currentAbortController.signal, locale, images, envOverrides, permissionMode) + _runQuery(requestId, enrichedPrompt, projectPath, model, currentAbortController.signal, locale, images, envOverrides, permissionMode, additionalDirectories) .catch(err => { console.error("[Phoenix AI] Query error:", err); }); @@ -452,7 +452,7 @@ exports.clearClarification = async function () { /** * Internal: run a Claude SDK query and stream results back to the browser. */ -async function _runQuery(requestId, prompt, projectPath, model, signal, locale, images, envOverrides, permissionMode) { +async function _runQuery(requestId, prompt, projectPath, model, signal, locale, images, envOverrides, permissionMode, additionalDirectories) { // Sync the runtime mutable that hooks read for permission decisions — // setPermissionMode (peer) updates this same variable when the user // cycles modes mid-stream. @@ -520,8 +520,23 @@ async function _runQuery(requestId, prompt, projectPath, model, signal, locale, _hookErrorTimer = null; } + // Validate the user-attached extra directories the browser sent. + // Drop entries that aren't absolute, don't exist, or duplicate cwd. + // Returns undefined for empty results so the SDK ignores the option + // rather than seeing a literal []. Each sendPrompt rebuilds this + // list, so adding/removing in the UI takes effect on the next turn. + const _cwdForValidation = projectPath || process.cwd(); + const validatedExtraDirs = (Array.isArray(additionalDirectories) + ? additionalDirectories.filter(function (p) { + if (typeof p !== "string" || !path.isAbsolute(p)) { return false; } + if (p === _cwdForValidation) { return false; } + try { return fs.existsSync(p); } catch (e) { return false; } + }) + : []); + const queryOptions = { cwd: projectPath || process.cwd(), + additionalDirectories: validatedExtraDirs.length ? validatedExtraDirs : undefined, maxTurns: undefined, stderr: (data) => { console.log("[AI stderr]", data); diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index 9e28baf8dc..78dacaea39 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -2284,6 +2284,10 @@ define({ "AI_CHAT_IMAGE_LIMIT": "Maximum {0} images allowed", "AI_CHAT_IMAGE_REMOVE": "Remove image", "AI_CHAT_ATTACH_FILE": "Attach files", + "AI_CHAT_ATTACH_TITLE": "Attach file or folder", + "AI_CHAT_ATTACH_FILE_OPTION": "Attach a file", + "AI_CHAT_ATTACH_FOLDER": "Add folder as context", + "AI_CHAT_ATTACH_FOLDER_PICK_TITLE": "Choose folder to add as context", "AI_CHAT_SCREENSHOT_TITLE": "Take Screenshot", "AI_CHAT_SCREENSHOT_LIVE_PREVIEW": "Live Preview", "AI_CHAT_SCREENSHOT_AREA": "Select Area", diff --git a/src/styles/Extn-AIChatPanel.less b/src/styles/Extn-AIChatPanel.less index 9f3957b08a..49f60cd53f 100644 --- a/src/styles/Extn-AIChatPanel.less +++ b/src/styles/Extn-AIChatPanel.less @@ -2121,6 +2121,14 @@ .ai-context-chip-icon { color: #6b9eff; } } + // Folder context chip — extra working directories the user has + // attached to the conversation. Tinted amber/orange so it reads + // as a different *kind* of context than file/selection chips. + .ai-context-chip-folder { + border-color: rgba(232, 168, 56, 0.35); + .ai-context-chip-icon { color: #e8a838; } + } + /* ── Image preview strip (between context bar and textarea) ──────── */ .ai-chat-image-preview { display: none; @@ -2345,7 +2353,8 @@ } } - .ai-screenshot-dropup { + .ai-screenshot-dropup, + .ai-attach-dropup { position: fixed; background: @bc-ai-input-bg; border: 1px solid @bc-ai-input-border; @@ -2355,7 +2364,8 @@ z-index: 100; min-width: 200px; - .ai-screenshot-option { + .ai-screenshot-option, + .ai-attach-option { padding: 8px 12px; cursor: pointer; color: @project-panel-text-1; From 13574d7864b073996c7493ac3caff8145171a126 Mon Sep 17 00:00:00 2001 From: abose Date: Mon, 27 Apr 2026 10:55:46 +0530 Subject: [PATCH 05/12] 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 948f59e10e..8d9b90c89a 100644 --- a/tracking-repos.json +++ b/tracking-repos.json @@ -1,5 +1,5 @@ { "phoenixPro": { - "commitID": "21f26650f613377219524b88ed23b394e69133a2" + "commitID": "fe4badcc3aaede50bf3104b0ed1d62cab4987d1c" } } From a2e2ca3ead8584f8ac5642c3805fadcd46c14736 Mon Sep 17 00:00:00 2001 From: abose Date: Mon, 27 Apr 2026 11:26:23 +0530 Subject: [PATCH 06/12] feat(ai-chat): auto-allow read-only bash in Edit Mode Mirrors the Claude Code CLI's default permissions.allow set (git status / log / diff / show / remote show / branch / ls-files / rev-parse, plus generic ls / pwd / cat / head / tail / wc / which / file / stat / echo, and version probes) so the user isn't prompted for every "look around the repo" command. Anything containing shell composition characters (; && || | $(...) backticks < >) still falls through to a user prompt, so chained destructive operations can't piggy-back on a safe prefix. --- src-node/claude-code-agent.js | 60 +++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/src-node/claude-code-agent.js b/src-node/claude-code-agent.js index 577b5af4a1..5deaba6af5 100644 --- a/src-node/claude-code-agent.js +++ b/src-node/claude-code-agent.js @@ -114,6 +114,53 @@ function _isToolResponseError(toolResponse) { return false; } +// Bash commands the agent can run without prompting the user in Edit +// Mode. Mirrors the CLI's default "permissions.allow" set +// (cli.js:2925) plus a small handful of universally read-only shell +// utilities. Shell-composition characters (`;`, `&&`, `||`, backticks, +// pipes, redirection, command substitution) trip the safety belt +// below — without that check `git status; rm -rf /` would slip through +// since the prefix matches. +const _SAFE_BASH_PATTERNS = [ + // git read-only + /^git\s+status(\s|$)/, + /^git\s+log(\s|$)/, + /^git\s+diff(\s|$)/, + /^git\s+show(\s|$)/, + /^git\s+branch(\s|$)/, + /^git\s+ls-files(\s|$)/, + /^git\s+rev-parse(\s|$)/, + /^git\s+remote\s+show(\s|$)/, + /^git\s+--version$/, + // generic read-only shell + /^ls(\s|$)/, + /^pwd$/, + /^echo(\s|$)/, + /^which\s/, + /^cat(\s|$)/, + /^head(\s|$)/, + /^tail(\s|$)/, + /^wc(\s|$)/, + /^file\s/, + /^stat\s/, + // version probes + /^node\s+--version$/, + /^npm\s+--version$/, + /^yarn\s+--version$/, + /^pnpm\s+--version$/ +]; + +function _isSafeReadOnlyBash(rawCmd) { + const cmd = (rawCmd || "").trim(); + if (!cmd) { return false; } + // Reject anything that could chain a destructive op via shell + // composition: `;` `&&` `||` `|` backticks `$(...)` `<` `>`. + // The CLI's parser handles these; we keep matching simple by + // refusing to bypass the prompt if any of them are present. + if (/[;&|`$()<>]/.test(cmd)) { return false; } + return _SAFE_BASH_PATTERNS.some(function (rx) { return rx.test(cmd); }); +} + /** * Lazily import the ESM @anthropic-ai/claude-code module. */ @@ -916,6 +963,19 @@ async function _runQuery(requestId, prompt, projectPath, model, signal, locale, } // Edit Mode: ask user confirmation before running bash const command = input.tool_input.command || ""; + // Skip prompting for well-known read-only commands + // that mirror the Claude Code CLI's default safe + // patterns. Cuts down on prompt fatigue during + // typical "look around the repo" turns. + if (_isSafeReadOnlyBash(command)) { + console.log("[Phoenix AI] Auto-allowing safe bash:", command.slice(0, 80)); + return { + hookSpecificOutput: { + hookEventName: "PreToolUse", + permissionDecision: "allow" + } + }; + } console.log("[Phoenix AI] Bash confirmation requested:", command.slice(0, 80)); nodeConnector.triggerPeer("aiBashConfirm", { requestId: requestId, From 10f8882b1e33b01e40b6cc91a96d1d646b78bb63 Mon Sep 17 00:00:00 2001 From: abose Date: Mon, 27 Apr 2026 17:33:43 +0530 Subject: [PATCH 07/12] feat(ai-chat): expand safe-bash list, scoped sidebar dark overrides, plan overlay css MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Safe-bash classifier in claude-code-agent.js now splits commands on `;`, `&&`, `||` and accepts the chain if every segment matches a safe pattern, so `git status && git log -5` and `sleep 1; echo done` no longer prompt. `sleep` (numeric durations) joins the allowlist. Process substitution (`$(...)`, backticks), redirection (`<`, `>`) and pipes (`|`) still fall through to a user prompt — chained destructive ops can't piggy-back on a safe prefix. * New scoped overrides at the bottom of Extn-AIChatPanel.less neutralise Bootstrap's .btn / .btn-primary / .btn-secondary skins and add dark-theme defaults for inputs/textareas/selects inside .ai-chat-panel, so the always-dark sidebar renders identically in light and dark editor themes. * Plan-card maximize: .ai-plan-maximize-btn (muted button flush right of the header) plus .ai-plan-fullscreen-overlay (fixed, z-index 10001, dark backdrop, 960px card with the same plan chrome). Strings AI_CHAT_PLAN_MAXIMIZE and AI_CHAT_PLAN_CLOSE_FULLSCREEN. * First-time Full Auto warning strings: AI_CHAT_FULL_AUTO_WARNING_TITLE / _BODY / _PROCEED. --- src-node/claude-code-agent.js | 30 ++++--- src/nls/root/strings.js | 5 ++ src/styles/Extn-AIChatPanel.less | 147 +++++++++++++++++++++++++++++++ 3 files changed, 172 insertions(+), 10 deletions(-) diff --git a/src-node/claude-code-agent.js b/src-node/claude-code-agent.js index 5deaba6af5..3380e2e778 100644 --- a/src-node/claude-code-agent.js +++ b/src-node/claude-code-agent.js @@ -117,10 +117,10 @@ function _isToolResponseError(toolResponse) { // Bash commands the agent can run without prompting the user in Edit // Mode. Mirrors the CLI's default "permissions.allow" set // (cli.js:2925) plus a small handful of universally read-only shell -// utilities. Shell-composition characters (`;`, `&&`, `||`, backticks, -// pipes, redirection, command substitution) trip the safety belt -// below — without that check `git status; rm -rf /` would slip through -// since the prefix matches. +// utilities. The safety belt in _isSafeReadOnlyBash splits on +// `;` / `&&` / `||` and checks every segment, so chaining safe +// commands (e.g. `git status && git log`, `sleep 1; echo done`) +// works while `git status; rm -rf /` correctly falls through. const _SAFE_BASH_PATTERNS = [ // git read-only /^git\s+status(\s|$)/, @@ -143,6 +143,9 @@ const _SAFE_BASH_PATTERNS = [ /^wc(\s|$)/, /^file\s/, /^stat\s/, + // numeric-only sleep — no `sleep $(...)` since process substitution + // is rejected separately, but be explicit so `sleep $VAR` also fails. + /^sleep\s+\d+(\.\d+)?$/, // version probes /^node\s+--version$/, /^npm\s+--version$/, @@ -153,12 +156,19 @@ const _SAFE_BASH_PATTERNS = [ function _isSafeReadOnlyBash(rawCmd) { const cmd = (rawCmd || "").trim(); if (!cmd) { return false; } - // Reject anything that could chain a destructive op via shell - // composition: `;` `&&` `||` `|` backticks `$(...)` `<` `>`. - // The CLI's parser handles these; we keep matching simple by - // refusing to bypass the prompt if any of them are present. - if (/[;&|`$()<>]/.test(cmd)) { return false; } - return _SAFE_BASH_PATTERNS.some(function (rx) { return rx.test(cmd); }); + // Reject command/process substitution, redirection, and pipes — + // these can hide arbitrary commands or send output to dangerous + // places. Backticks, `$(...)`, `<`, `>`, `|`. Plain `$VAR` is + // allowed (substitution-without-command). + if (/[`<>|]|\$\(/.test(cmd)) { return false; } + // Split on `;`, `&&`, `||` and verify EVERY segment matches a safe + // pattern. Quotes around delimiters are not handled — a command + // like `echo "a; b"` will split mid-string and fail safe-check + // (which is fine: false negatives are OK, false positives are not). + const segments = cmd.split(/\s*(?:;|&&|\|\|)\s*/).filter(Boolean); + return segments.every(function (seg) { + return _SAFE_BASH_PATTERNS.some(function (rx) { return rx.test(seg); }); + }); } /** diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index 78dacaea39..d1d20f1f47 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -2223,6 +2223,9 @@ define({ "AI_CHAT_FILE_NOT_FOUND_MSG": "Could not open {0}. The file may have been moved or deleted.", "AI_CHAT_UNDO_RESTORE_WARNING_TITLE": "AI Undo & Restore", "AI_CHAT_UNDO_RESTORE_WARNING_BODY": "This will only undo changes made by the AI. Changes made outside the AI won’t be restored and may be lost. For full version history, use version control like Git.", + "AI_CHAT_FULL_AUTO_WARNING_TITLE": "Switch to Full Auto Mode?", + "AI_CHAT_FULL_AUTO_WARNING_BODY": "Full Auto mode lets the AI run any tool — Bash commands, file edits, file deletions, web fetches — without asking you first.

This is convenient for trusted scratch projects, but can be risky: a misjudged step could overwrite or delete files, run a destructive shell command, or push unintended changes. Use version control (Git) so you can recover if something goes wrong.

Only enable Full Auto in projects you trust. You can switch back to Edit Mode at any time using Shift+Tab or by clicking the mode bar.", + "AI_CHAT_FULL_AUTO_WARNING_PROCEED": "Enable Full Auto", "AI_CHAT_SHOW_DIFF": "Show diff", "AI_CHAT_HIDE_DIFF": "Hide diff", "AI_CHAT_DIFF_MORE_TITLE": "Diff options", @@ -2249,6 +2252,8 @@ define({ "AI_CHAT_TOOL_TASK_NAME": "Subagent: {0}", "AI_CHAT_TOOL_PLANNING": "Planning", "AI_CHAT_PLAN_TITLE": "Proposed Plan", + "AI_CHAT_PLAN_MAXIMIZE": "Open plan in full screen", + "AI_CHAT_PLAN_CLOSE_FULLSCREEN": "Close (Esc)", "AI_CHAT_PLAN_APPROVE": "Approve", "AI_CHAT_PLAN_REVISE": "Revise", "AI_CHAT_PLAN_FEEDBACK_PLACEHOLDER": "What would you like changed?", diff --git a/src/styles/Extn-AIChatPanel.less b/src/styles/Extn-AIChatPanel.less index 49f60cd53f..9627e43bdf 100644 --- a/src/styles/Extn-AIChatPanel.less +++ b/src/styles/Extn-AIChatPanel.less @@ -1365,6 +1365,94 @@ font-weight: 600; } +// Maximize button on the plan header — sits flush right via auto +// margin so the title and icon stay left-aligned. Same muted look as +// the diff toggle / more menu so it reads as a quiet affordance. +.ai-plan-maximize-btn { + margin-left: auto; + background: none; + border: none; + color: #6b9eff; + font-size: @ai-text-secondary; + line-height: 1; + padding: 2px 6px; + border-radius: 4px; + cursor: pointer; + opacity: 0.65; + transition: opacity 0.15s ease, background-color 0.15s ease; + + &:hover { + opacity: 1; + background-color: rgba(107, 158, 255, 0.15); + } +} + +// Fullscreen plan overlay — covers the whole window (over the editor, +// not just the AI panel). z-index above modal dialogs so a long plan +// is readable in detail without horizontal cramping. +.ai-plan-fullscreen-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: 10001; + background: rgba(0, 0, 0, 0.72); + display: flex; + align-items: center; + justify-content: center; + padding: 24px; + -webkit-user-select: text; + -moz-user-select: text; + user-select: text; + + .ai-plan-fullscreen-card { + // Same chrome as the inline plan card so the maximized view is + // a faithful magnification, not a different surface. + width: min(960px, 96vw); + max-height: 92vh; + display: flex; + flex-direction: column; + background-color: rgba(40, 44, 52, 0.98); + border: 1px solid rgba(107, 158, 255, 0.35); + border-radius: 8px; + overflow: hidden; + box-shadow: 0 8px 40px rgba(0, 0, 0, 0.6); + + .ai-plan-header { + flex-shrink: 0; + } + + .ai-plan-body { + // Override the inline card's max-height — in fullscreen we + // want the body to consume the remaining card height. + max-height: none; + flex: 1 1 auto; + overflow-y: auto; + padding: 22px 28px; + } + } + + .ai-plan-fullscreen-close { + margin-left: auto; + background: none; + border: none; + color: #6b9eff; + font-size: @ai-text-body; + line-height: 1; + padding: 2px 8px; + border-radius: 4px; + cursor: pointer; + opacity: 0.7; + transition: opacity 0.15s ease, background-color 0.15s ease; + + &:hover { + opacity: 1; + background-color: rgba(107, 158, 255, 0.15); + } + } +} + .ai-plan-body { padding: 14px 16px; font-size: @ai-text-body; @@ -2745,3 +2833,62 @@ text-align: center; } +/* ── Sidebar-is-always-dark overrides ───────────────────────────────── */ +/* The AI panel renders against a fixed dark sidebar background. Both + Bootstrap's stock .btn skins AND Phoenix's light-theme overrides of + them paint badly here in light editor themes. Neutralise the skins + inside the panel so the AI panel's own per-element rules + (.ai-plan-approve-btn, .ai-plan-revise-btn, .ai-plan-feedback-send, + etc.) own the look — and that look is identical in both themes. */ +.ai-chat-panel { + .btn, + .btn:hover, + .btn:focus, + .btn:active, + .btn-primary, + .btn-primary:hover, + .btn-primary:focus, + .btn-primary:active, + .btn-secondary, + .btn-secondary:hover, + .btn-secondary:focus, + .btn-secondary:active { + background: none; + background-color: transparent; + background-image: none; + border-color: transparent; + box-shadow: none; + text-shadow: none; + color: inherit; + } + + /* Inputs and textareas — same story. The chat textarea has its own + per-class style; this is the safety net for any other input we + drop in (plan feedback textarea today, future ones tomorrow). */ + input[type="text"], + input[type="search"], + input[type="email"], + input[type="password"], + input[type="number"], + select, + textarea { + background-color: rgba(255, 255, 255, 0.04); + color: @project-panel-text-1; + border: 1px solid rgba(255, 255, 255, 0.12); + border-radius: 4px; + box-shadow: none; + + &:focus { + background-color: rgba(255, 255, 255, 0.06); + border-color: rgba(107, 158, 255, 0.4); + outline: none; + box-shadow: none; + } + + &::placeholder { + color: @project-panel-text-2; + opacity: 0.6; + } + } +} + From 1149459704145eda60c2fc26ada8ad0a59e5675f Mon Sep 17 00:00:00 2001 From: abose Date: Mon, 27 Apr 2026 17:35:00 +0530 Subject: [PATCH 08/12] 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 8d9b90c89a..cae39cb9a2 100644 --- a/tracking-repos.json +++ b/tracking-repos.json @@ -1,5 +1,5 @@ { "phoenixPro": { - "commitID": "fe4badcc3aaede50bf3104b0ed1d62cab4987d1c" + "commitID": "bc54fbaf61378f8aa8f3139a3d8fa1dab90369b6" } } From 2bed1bcaa0b2612bd2c1a4511aa0d0e6afa63c0f Mon Sep 17 00:00:00 2001 From: abose Date: Mon, 27 Apr 2026 19:07:27 +0530 Subject: [PATCH 09/12] feat(ai-chat): onboarding iframe styles + strings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Companion to the phoenix-pro onboarding iframe wiring. * Extn-AIChatPanel.less: - .ai-tab-container gains `position: relative` so the prompt overlay (re-parented there at runtime) can use absolute inset:0 to cover tabs + panel body. - .ai-onboarding-wrap claims the same flex slot as .ai-chat-messages when shown. - .ai-history-open also hides the wrap so opening the history dropdown takes the panel body cleanly. - .ai-onboarding-prompt-overlay (top-level rule, not nested under the wrap, so the styles apply after the runtime reparent) matches Phoenix Code's panel chrome — neutral grey card on @bc-ai-sidebar-bg / @bc-ai-input-border, header strip with title + × close, full-bleed flush textarea, action row with quiet text Cancel and an icon-only send button mirroring .ai-send-btn. Explicit focus reset (border/outline/box-shadow: none) so the global textarea:focus rule can't shift the dialog when the user clicks in. - Light entrance animations (overlay fade, card pop). * strings.js: AI_CHAT_ONBOARDING_REVIEW_PROMPT ("Ready to build") and AI_CHAT_ONBOARDING_SEND. --- src/nls/root/strings.js | 2 + src/styles/Extn-AIChatPanel.less | 198 ++++++++++++++++++++++++++++++- 2 files changed, 199 insertions(+), 1 deletion(-) diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index d1d20f1f47..581c919fe9 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -2226,6 +2226,8 @@ define({ "AI_CHAT_FULL_AUTO_WARNING_TITLE": "Switch to Full Auto Mode?", "AI_CHAT_FULL_AUTO_WARNING_BODY": "Full Auto mode lets the AI run any tool — Bash commands, file edits, file deletions, web fetches — without asking you first.

This is convenient for trusted scratch projects, but can be risky: a misjudged step could overwrite or delete files, run a destructive shell command, or push unintended changes. Use version control (Git) so you can recover if something goes wrong.

Only enable Full Auto in projects you trust. You can switch back to Edit Mode at any time using Shift+Tab or by clicking the mode bar.", "AI_CHAT_FULL_AUTO_WARNING_PROCEED": "Enable Full Auto", + "AI_CHAT_ONBOARDING_REVIEW_PROMPT": "Ready to build", + "AI_CHAT_ONBOARDING_SEND": "Send", "AI_CHAT_SHOW_DIFF": "Show diff", "AI_CHAT_HIDE_DIFF": "Hide diff", "AI_CHAT_DIFF_MORE_TITLE": "Diff options", diff --git a/src/styles/Extn-AIChatPanel.less b/src/styles/Extn-AIChatPanel.less index 9627e43bdf..45dc3b8b27 100644 --- a/src/styles/Extn-AIChatPanel.less +++ b/src/styles/Extn-AIChatPanel.less @@ -40,6 +40,10 @@ flex: 1; min-height: 0; overflow: hidden; + // Positioning context for the onboarding prompt overlay, which is + // detached from its template position and re-parented here so it + // covers the Files/AI tab strip as well as the panel body. + position: relative; } .ai-chat-panel { @@ -145,7 +149,8 @@ .ai-chat-panel.ai-history-open { > .ai-chat-messages, > .ai-chat-status, - > .ai-chat-input-area { + > .ai-chat-input-area, + > .ai-onboarding-wrap { display: none !important; } } @@ -265,6 +270,179 @@ } } +/* ── Onboarding iframe (guided AI starter) ──────────────────────────── */ +/* The wrap sits where .ai-chat-messages would. When visible it claims + the same flex slot — when hidden the chat takes that slot back. */ +.ai-onboarding-wrap { + flex: 1; + min-height: 0; + min-width: 0; + position: relative; + background-color: @bc-ai-sidebar-bg; + + .ai-onboarding-frame { + width: 100%; + height: 100%; + border: 0; + display: block; + } +} + +/* Prompt confirm overlay — top-level rule (NOT nested under + .ai-onboarding-wrap) because at runtime _initOnboarding detaches + the overlay element and re-parents it to .ai-tab-container so it + covers the Files/AI tab strip in addition to the panel body. + Neutral dark-panel chrome — no blue accent. Title strip + full- + bleed textarea + bottom action row with Cancel and an icon-only + send button that mirrors .ai-send-btn's transparent quiet style. */ +.ai-onboarding-prompt-overlay { + position: absolute; + inset: 0; + z-index: 10; + background: rgba(0, 0, 0, 0.45); + display: flex; + align-items: center; + justify-content: center; + padding: 16px; + animation: ai-onboarding-overlay-fade 0.15s ease-out; + + .ai-onboarding-prompt-card { + width: 100%; + max-width: 440px; + border: 1px solid @bc-ai-input-border; + border-radius: 8px; + background: @bc-ai-sidebar-bg; + overflow: hidden; + display: flex; + flex-direction: column; + box-shadow: 0 6px 20px rgba(0, 0, 0, 0.45); + animation: ai-onboarding-card-pop 0.18s ease-out; + } + + .ai-onboarding-prompt-header { + display: flex; + align-items: center; + gap: 8px; + padding: 8px 8px 8px 14px; + border-bottom: 1px solid rgba(255, 255, 255, 0.06); + } + + .ai-onboarding-prompt-title { + flex: 1; + min-width: 0; + font-size: @ai-text-body; + font-weight: 600; + color: @project-panel-text-1; + line-height: 1.3; + } + + .ai-onboarding-prompt-close { + background: none; + border: 0; + color: @project-panel-text-2; + width: 26px; + height: 26px; + border-radius: 4px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + opacity: 0.55; + flex-shrink: 0; + transition: opacity 0.15s ease, color 0.15s ease, background-color 0.15s ease; + + i { font-size: 0.95em; } + + &:hover { + opacity: 1; + color: @project-panel-text-1; + background: rgba(255, 255, 255, 0.05); + } + } + + .ai-onboarding-prompt-text { + // display: block + width: 100% + box-sizing makes the textarea + // span the card's full inner width regardless of the rows attr + // or the browser's textarea default width. + display: block; + width: 100%; + box-sizing: border-box; + background: @bc-ai-input-bg; + border: 0; + border-radius: 0; + color: @project-panel-text-1; + font-size: @ai-text-body; + line-height: @ai-line-prose; + padding: 12px 14px; + resize: vertical; + min-height: 110px; + max-height: 260px; + outline: none; + box-shadow: none; + + // Phoenix's global textarea focus rule paints a border + shadow + // that shifts the dialog by ~2px and reads as a flicker. Force + // the no-chrome focus state so only the bg darkening signals + // focus. + &:focus, + &:focus-visible { + background: @bc-ai-input-bg-focused; + border: 0; + outline: none; + box-shadow: none; + } + } + + .ai-onboarding-prompt-actions { + display: flex; + justify-content: flex-end; + align-items: center; + gap: 4px; + padding: 6px 8px 6px 12px; + border-top: 1px solid rgba(255, 255, 255, 0.06); + } + + .ai-onboarding-prompt-cancel { + background: transparent; + border: 0; + color: @project-panel-text-2; + font-size: @ai-text-secondary; + line-height: @ai-line-compact; + padding: 6px 10px; + border-radius: 4px; + cursor: pointer; + transition: background-color 0.15s ease, color 0.15s ease; + + &:hover { + background: rgba(255, 255, 255, 0.05); + color: @project-panel-text-1; + } + } + + .ai-onboarding-prompt-send { + background: none; + border: 0; + color: @project-panel-text-2; + width: 32px; + height: 28px; + border-radius: 4px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + opacity: 0.6; + transition: opacity 0.15s ease, color 0.15s ease, background-color 0.15s ease; + + i { font-size: 0.95em; } + + &:hover { + opacity: 1; + color: @project-panel-text-1; + background: rgba(255, 255, 255, 0.05); + } + } +} + /* ── Message list ───────────────────────────────────────────────────── */ .ai-chat-messages { flex: 1; @@ -2892,3 +3070,21 @@ } } +/* Onboarding prompt overlay entrance animations. Kept at file scope so + the keyframes resolve regardless of nesting context. */ +@keyframes ai-onboarding-overlay-fade { + from { opacity: 0; } + to { opacity: 1; } +} + +@keyframes ai-onboarding-card-pop { + from { + opacity: 0; + transform: scale(0.96) translateY(6px); + } + to { + opacity: 1; + transform: scale(1) translateY(0); + } +} + From 050b5e144adbad290f2a406ba2bfc31e3e0aefa6 Mon Sep 17 00:00:00 2001 From: abose Date: Mon, 27 Apr 2026 19:10:45 +0530 Subject: [PATCH 10/12] chore(ai-chat): declare onboarding iframe theme as CSS custom properties MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Companion to the AIChatPanel.js refactor: the iframe-theme values now live as --bg-panel / --text-primary / --accent-primary / etc. on .ai-chat-panel itself. JS reads them via getComputedStyle and forwards them to the iframe, so retheming is purely editing this LESS list — no JS change required. --- src/styles/Extn-AIChatPanel.less | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/styles/Extn-AIChatPanel.less b/src/styles/Extn-AIChatPanel.less index 45dc3b8b27..1148c28efc 100644 --- a/src/styles/Extn-AIChatPanel.less +++ b/src/styles/Extn-AIChatPanel.less @@ -57,6 +57,32 @@ color: @project-panel-text-1; font-size: @ai-text-body; container-type: inline-size; + + /* Onboarding-iframe theme variables — single source of truth for + what gets forwarded to the iframe's `?configCSS=` param. + AIChatPanel.js _buildOnboardingConfigCSS reads each of these via + getComputedStyle and serialises them into a :root{...} block. + To retheme the iframe, edit only this list. The names match the + iframe's published contract (see ai-panel-onboarding README). */ + --bg-panel: @bc-ai-sidebar-bg; + --bg-card: rgba(255, 255, 255, 0.04); + --bg-card-hover: rgba(255, 255, 255, 0.07); + --bg-input: rgba(255, 255, 255, 0.04); + --bg-input-focus: rgba(255, 255, 255, 0.06); + --text-primary: @project-panel-text-1; + --text-secondary: @project-panel-text-2; + --text-muted: rgba(168, 176, 180, 0.6); + --border-subtle: rgba(255, 255, 255, 0.12); + --border-active: rgba(107, 158, 255, 0.4); + --border-focus: rgba(107, 158, 255, 0.5); + --accent-primary: #6b9eff; + --accent-primary-soft: rgba(107, 158, 255, 0.12); + --accent-primary-border: rgba(107, 158, 255, 0.3); + --font-body: 14px; + --font-secondary: 12px; + --font-meta: 11px; + --radius-md: 6px; + --radius-lg: 8px; } /* ── Header ─────────────────────────────────────────────────────────── */ From a43372d4019771dc46197571e681cc245f60ada2 Mon Sep 17 00:00:00 2001 From: abose Date: Mon, 27 Apr 2026 19:16:50 +0530 Subject: [PATCH 11/12] 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 cae39cb9a2..29f8069833 100644 --- a/tracking-repos.json +++ b/tracking-repos.json @@ -1,5 +1,5 @@ { "phoenixPro": { - "commitID": "bc54fbaf61378f8aa8f3139a3d8fa1dab90369b6" + "commitID": "cc369c5039be7b5d9104f43539ae4a1598f2616e" } } From 8452642551a02eac894b2343f70695865278f4f4 Mon Sep 17 00:00:00 2001 From: abose Date: Mon, 27 Apr 2026 20:33:05 +0530 Subject: [PATCH 12/12] 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 29f8069833..ebb6ad1b3b 100644 --- a/tracking-repos.json +++ b/tracking-repos.json @@ -1,5 +1,5 @@ { "phoenixPro": { - "commitID": "cc369c5039be7b5d9104f43539ae4a1598f2616e" + "commitID": "05c8420787657d64785b753c80b901e27ae74270" } }