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-node/claude-code-agent.js b/src-node/claude-code-agent.js
index 22be2d86bf..3380e2e778 100644
--- a/src-node/claude-code-agent.js
+++ b/src-node/claude-code-agent.js
@@ -114,6 +114,63 @@ 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. 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|$)/,
+ /^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/,
+ // 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$/,
+ /^yarn\s+--version$/,
+ /^pnpm\s+--version$/
+];
+
+function _isSafeReadOnlyBash(rawCmd) {
+ const cmd = (rawCmd || "").trim();
+ if (!cmd) { return false; }
+ // 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); });
+ });
+}
+
/**
* Lazily import the ESM @anthropic-ai/claude-code module.
*/
@@ -248,7 +305,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 +348,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 +509,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 +577,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);
@@ -901,6 +973,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,
diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js
index 9e28baf8dc..581c919fe9 100644
--- a/src/nls/root/strings.js
+++ b/src/nls/root/strings.js
@@ -2223,6 +2223,11 @@ 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_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",
@@ -2249,6 +2254,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?",
@@ -2284,6 +2291,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 d3f03e162e..1148c28efc 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 {
@@ -53,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 ─────────────────────────────────────────────────────────── */
@@ -145,7 +175,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 +296,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;
@@ -688,6 +892,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;
@@ -1354,6 +1569,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;
@@ -2110,6 +2413,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;
@@ -2334,7 +2645,8 @@
}
}
- .ai-screenshot-dropup {
+ .ai-screenshot-dropup,
+ .ai-attach-dropup {
position: fixed;
background: @bc-ai-input-bg;
border: 1px solid @bc-ai-input-border;
@@ -2344,7 +2656,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;
@@ -2724,3 +3037,80 @@
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;
+ }
+ }
+}
+
+/* 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);
+ }
+}
+
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);
diff --git a/tracking-repos.json b/tracking-repos.json
index 49eec234e4..ebb6ad1b3b 100644
--- a/tracking-repos.json
+++ b/tracking-repos.json
@@ -1,5 +1,5 @@
{
"phoenixPro": {
- "commitID": "d175c1fb02062cd8ad27a3f942162383c132c630"
+ "commitID": "05c8420787657d64785b753c80b901e27ae74270"
}
}