From 55c043a7cf84513dc0ab79249ef25a228ac6302a Mon Sep 17 00:00:00 2001 From: abose Date: Sat, 24 Oct 2026 14:14:12 +0530 Subject: [PATCH 1/5] fix(ai): track login processes and restore home demos Allow VIEW_TERMINAL to run an explicit process profile in a fresh tab and report its exit code once. Report cancellation before killing the process, retain its output after exit, and exclude process tabs from saved shell names and project restarts. This lets Claude sign-in recovery distinguish a completed login from stored but expired credentials. Keep the Home demo controls visible over an existing conversation and add the localized login-launch error message. Pin Phoenix Pro to 19028ba4921d3ef0abb1891486ea80eb54d9a1be for the matching auth refresh, duplicate-login protection, and separate-chat demo behavior. Add registered terminal integration coverage for successful and failed process exits, cancellation, invalid startup, persistence, and restart. Terminal passes 39/39 and AI Chat CLI passes 56/56 on Windows, macOS, and Linux. Targeted ESLint, whitespace checks, and LESS compilation pass. Real browser-login confirmation and hosted CI remain pending. --- src/extensionsIntegrated/Terminal/main.js | 114 ++++++++++++-- src/nls/root/strings.js | 1 + src/styles/Extn-AIChatPanel.less | 9 +- test/spec/Terminal-integ-test.js | 181 ++++++++++++++++++++++ tracking-repos.json | 2 +- 5 files changed, 282 insertions(+), 25 deletions(-) diff --git a/src/extensionsIntegrated/Terminal/main.js b/src/extensionsIntegrated/Terminal/main.js index ea7bae6480..fd6249a6e1 100644 --- a/src/extensionsIntegrated/Terminal/main.js +++ b/src/extensionsIntegrated/Terminal/main.js @@ -329,9 +329,11 @@ define(function (require, exports, module) { * @param {Object} shell Shell profile to use. * @param {string} [cwdOverride] Directory to use instead of the project root. * @param {string} [projectPath] Owning project when restarting during a project switch. + * @param {Object} [processRun] Set when `shell` is a single process run without a shell + * (see _runProcessInNewTerminal): its env and the exit report the caller asked for. * @return {Promise} The new terminal, if a shell is available. */ - async function _createNewTerminalWithShell(shell, cwdOverride, projectPath) { + async function _createNewTerminalWithShell(shell, cwdOverride, projectPath, processRun) { if (!shell) { console.error("Terminal: No shell available"); Metrics.countEvent(Metrics.EVENT_TYPE.TERMINAL, "new", "noShell"); @@ -339,7 +341,7 @@ define(function (require, exports, module) { } // Metric: a new terminal session was created, keyed by shell family. Metrics.countEvent(Metrics.EVENT_TYPE.TERMINAL, "new", - _shellMetricLabel(shell.name)); + processRun ? "process" : _shellMetricLabel(shell.name)); // Get cwd: use override if provided, otherwise fall back to project root const projectRoot = ProjectManager.getProjectRoot(); @@ -356,6 +358,7 @@ define(function (require, exports, module) { const instance = new TerminalInstance(nodeConnector, shell, cwd); // Project ownership is independent of directories the user visits in the shell. instance.projectPath = projectPath || (projectRoot ? projectRoot.fullPath : null); + instance.processRun = processRun || null; // Set up callbacks instance.onTitleChanged = _onTerminalTitleChanged; @@ -393,10 +396,59 @@ define(function (require, exports, module) { try { instance.fitAddon.fit(); } catch (e) { /* not ready */ } // Spawn PTY process - await instance.spawn(); + const spawned = await instance.spawn(processRun ? processRun.env : undefined); + if (processRun) { + processRun.started = spawned; + if (!spawned) { + _reportProcessExit(processRun, null); + } + } return instance; } + /** + * Tell whoever started a process tab how it ended, once. + * @param {?Object} processRun The tab's run, or null for a shell tab. + * @param {?number} exitCode The process exit code, or null when it never ran or its tab was closed first. + */ + function _reportProcessExit(processRun, exitCode) { + if (!processRun || processRun.reported) { + return; + } + processRun.reported = true; + if (typeof processRun.onProcessExit === "function") { + try { + processRun.onProcessExit(exitCode); + } catch (err) { + console.error("Terminal: onProcessExit handler failed:", err); + } + } + } + + /** + * Run one executable as a new tab's own process, with no shell around it, so the + * caller learns how it ended. The tab stays open after the exit to show its output. + * @param {{path: string, args: (Array|undefined), name: (string|undefined), + * env: (Object|undefined)}} spec What to run; env is merged into the inherited environment. + * @param {function(?number)} [onProcessExit] Called once with the exit code, or null when + * the process could not start or its tab was closed before it exited. + * @return {Promise} The new tab, or null when the process did not start. + */ + async function _runProcessInNewTerminal(spec, onProcessExit) { + const processRun = {env: spec.env, onProcessExit: onProcessExit, started: false, reported: false}; + if (!spec.path || _restartingTerminals) { + _reportProcessExit(processRun, null); + return null; + } + const profile = { + name: spec.name || spec.path.split(/[\\/]/).pop(), + path: spec.path, + args: spec.args || [] + }; + const instance = await _createNewTerminalWithShell(profile, undefined, undefined, processRun); + return instance && processRun.started ? {terminalId: instance.id} : null; + } + /** Remove the project-switch notice without changing any terminal session. */ function _hideProjectBanner() { if ($contentArea) { @@ -491,12 +543,14 @@ define(function (require, exports, module) { // A restart replaces every instance, and the new ones are issued // fresh ids, so a name looked up by id would not find them. Carry // the names across with the shells: the tabs are the same tabs to - // the user, only pointed at the new project. - const carried = terminalInstances.map(inst => ({ + // the user, only pointed at the new project. A tab running a single + // process (no shell) ends with the restart rather than running again. + const shellTabs = terminalInstances.filter(inst => !inst.processRun); + const carried = shellTabs.map(inst => ({ profile: inst.shellProfile, customName: inst.customName })); - const activeIndex = terminalInstances.findIndex(inst => inst.id === activeTerminalId); + const activeIndex = shellTabs.findIndex(inst => inst.id === activeTerminalId); await _disposeAllAsync(); activeTerminalId = null; _updateFlyout(); @@ -511,6 +565,10 @@ define(function (require, exports, module) { if (replacements[activeIndex]) { _activateTerminal(replacements[activeIndex].id); } + if (!replacements.length) { + _hideProjectBanner(); + panel.hide(); + } // Keep a notice for a newer project selected during the restart. if (_projectPath !== path) { _showProjectBanner(); @@ -579,6 +637,8 @@ define(function (require, exports, module) { } } + // Disposing stops exit events, so a process still running reports as closed. + _reportProcessExit(instance.processRun, null); instance.dispose(); terminalInstances.splice(idx, 1); delete processInfo[id]; @@ -748,7 +808,9 @@ define(function (require, exports, module) { * for it to come back. */ function _saveTabNames() { + // A process tab would come back as a plain shell, so its name is not kept. StateManager.set(STATE_TAB_NAMES, terminalInstances + .filter(function (inst) { return !inst.processRun; }) .map(function (inst) { return inst.customName; }) .filter(Boolean)); } @@ -892,6 +954,10 @@ define(function (require, exports, module) { */ function _onTerminalProcessExit(id, exitCode) { delete processInfo[id]; + const instance = terminalInstances.find(inst => inst.id === id); + if (instance) { + _reportProcessExit(instance.processRun, exitCode); + } _updateFlyout(); // Metric: terminal process exited on its own (e.g. user typed // "exit"). Distinct from "user" close above, which records the @@ -901,16 +967,6 @@ define(function (require, exports, module) { exitCode === 0 ? "ok" : "err"); } - /** - * Show the terminal panel. Creates a new terminal if none exist. - * 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. - */ /** * Open the tabs the user named last time, all of them, in the order they * were in. @@ -944,7 +1000,28 @@ define(function (require, exports, module) { } } + /** + * Show the terminal panel. Creates a new terminal if none exist. + * 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. + * @param {{path: string, args: (Array|undefined), name: (string|undefined), + * env: (Object|undefined)}} [options.process] - Run this executable as a new tab's own + * process instead of a shell (takes precedence over shellCommand). The tab is not saved + * or restored, and a project restart closes it instead of running it again. + * @param {function(?number)} [options.onProcessExit] - With `process`: called once with + * its exit code, or null if it could not start or its tab was closed before it exited. + * @return {Promise} With `process`: the new tab, or null if the + * process did not start. Otherwise undefined. + */ async function _showTerminal(options) { + if (options && options.process) { + return _runProcessInNewTerminal(options.process, options.onProcessExit); + } if (options && options.shellCommand) { await _createNewTerminal(); const active = _getActiveTerminal(); @@ -1120,6 +1197,7 @@ define(function (require, exports, module) { */ function _disposeAll() { for (const inst of terminalInstances) { + _reportProcessExit(inst.processRun, null); inst.dispose(); } terminalInstances = []; @@ -1133,6 +1211,10 @@ define(function (require, exports, module) { * and acknowledged by the Node side. */ async function _disposeAllAsync() { + // Report first, so a killed process can never be read as having finished on its own. + for (const inst of terminalInstances) { + _reportProcessExit(inst.processRun, null); + } const killPromises = terminalInstances .filter(function (inst) { return inst.isAlive && !inst._disposed; }) .map(function (inst) { diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index 246f6cbb32..ef66278590 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -3041,6 +3041,7 @@ define({ "AI_CHAT_AUTH_ERROR_NOTICE": "Claude Code is signed out or your login has expired.", "AI_CHAT_AUTH_ERROR_BTN": "Sign in to Claude", "AI_CHAT_AUTH_ERROR_HINT": "Finish signing in on the page that opens in your browser, then send your message again.", + "AI_CHAT_AUTH_LOGIN_FAILED": "Could not start Claude sign-in. Check your Claude Code installation and try again.", "AI_CHAT_MODEL_DEFAULT": "Default", "AI_CHAT_MODEL_DEFAULT_DESC_CURRENT": "Currently {0}", "AI_CHAT_MODEL_DEFAULT_DESC": "Recommended — uses your Claude Code model setting", diff --git a/src/styles/Extn-AIChatPanel.less b/src/styles/Extn-AIChatPanel.less index c8a1501056..6e056610b7 100644 --- a/src/styles/Extn-AIChatPanel.less +++ b/src/styles/Extn-AIChatPanel.less @@ -1450,19 +1450,12 @@ } /* Home over a chat that has started: the chat's card leads back to it, - with the same arrow as the CLIs. The Surprise Me demo would play into - that chat, so it is not offered. */ + with the same arrow as the CLIs. Demos start a separate chat. */ .ai-start-row[data-mode="chat"] .ai-start-go { display: none; } &.ai-start-chat-running { - // The compact layout shows these with more specific rules. - .ai-start-surprise, - .ai-start-footer.ai-start-demo { - display: none !important; - } - .ai-start-row[data-mode="chat"] { .ai-start-check { display: none; diff --git a/test/spec/Terminal-integ-test.js b/test/spec/Terminal-integ-test.js index 1e64b1a1c0..f2f8f39191 100644 --- a/test/spec/Terminal-integ-test.js +++ b/test/spec/Terminal-integ-test.js @@ -229,6 +229,28 @@ define(function (require, exports, module) { return nativePath; } + /** + * A VIEW_TERMINAL `process` spec that runs `script` directly through the + * system's own interpreter, so its exit is the tab's exit. + * @param {string} script Command line for sh -c or cmd.exe /c. + * @return {{path: string, args: Array, name: string}} + */ + function scriptProcess(script) { + if (IS_WINDOWS) { + const ShellProfiles = testWindow.brackets.getModule("extensionsIntegrated/Terminal/ShellProfiles"); + const cmd = ShellProfiles.getShells().find(function (shell) { + return /cmd\.exe$/i.test(shell.path); + }); + return {path: cmd.path, args: ["/c", script], name: "Test process"}; + } + return {path: "/bin/sh", args: ["-c", script], name: "Test process"}; + } + + /** A process spec that keeps running until its tab is closed. */ + function longRunningProcess() { + return scriptProcess(IS_WINDOWS ? "ping -n 120 127.0.0.1" : "sleep 120"); + } + // --- Tests --- describe("Panel basics", function () { @@ -1056,6 +1078,44 @@ define(function (require, exports, module) { expect(testWindow.$(".terminal-project-banner").length).toBe(0); }, 30000); + it("closes a process tab instead of running it again when the terminals restart", async function () { + const shellTab = await openReadyTerminal(); + const exits = []; + await __PR.execCommand(__PR.Commands.VIEW_TERMINAL, { + process: longRunningProcess(), + onProcessExit: function (exitCode) { + exits.push(exitCode); + } + }); + const processTab = termModule._getActiveTerminal(); + await awaitsFor(function () { + return processTab.isAlive; + }, "the process to be running", 10000); + + await SpecRunnerUtils.loadProjectInTestWindow(secondProjectPath); + const path = getNativeProjectPath(); + testWindow.$(".terminal-project-restart").click(); + // the running process is a reason to ask first + await awaitsFor(function () { + return isDialogOpen() || processTab._disposed; + }, "the restart to ask or proceed", 10000); + if (isDialogOpen()) { + __PR.clickDialogButtonID(__PR.Dialogs.DIALOG_BTN_OK); + await __PR.waitForModalDialogClosed(); + } + await awaitsFor(function () { + const active = termModule._getActiveTerminal(); + return getTerminalCount() === 1 && active && active.isAlive && active.id !== shellTab.id; + }, "only the shell tab to restart", 15000); + + expect(processTab._disposed).toBeTrue(); + expect(exits).toEqual([null]); + const replacement = termModule._getActiveTerminal(); + expect(replacement.processRun).toBeNull(); + expect(replacement.shellProfile).toEqual(shellTab.shellProfile); + expect(replacement.cwd).toBe(path); + }, 30000); + it("confirms active processes and leaves sessions untouched when canceled", async function () { const instance = await openReadyTerminal(); const connectorSpy = reportActiveProcess(); @@ -1399,5 +1459,126 @@ define(function (require, exports, module) { }, "terminal panel to close", 5000); }); }); + + describe("Processes run without a shell", function () { + const STATE_TAB_NAMES = "terminal.tabNames"; + let termModule, StateManager, savedNames; + + beforeAll(function () { + termModule = testWindow.brackets.getModule("extensionsIntegrated/Terminal/main"); + StateManager = testWindow.brackets.getModule("preferences/StateManager"); + savedNames = StateManager.get(STATE_TAB_NAMES); + }); + + afterEach(async function () { + if (isDialogOpen()) { + __PR.clickDialogButtonID(__PR.Dialogs.DIALOG_BTN_CANCEL); + await __PR.waitForModalDialogClosed(); + } + await termModule._disposeAll(); + WorkspaceManager.getPanelForID(PANEL_ID).hide(); + }); + + afterAll(function () { + StateManager.set(STATE_TAB_NAMES, savedNames); + }); + + /** Read everything the tab has printed, including the exit line. */ + function bufferText(instance) { + const buffer = instance.terminal.buffer.active; + let text = ""; + for (let i = 0; i < buffer.length; i++) { + text += buffer.getLine(i).translateToString(); + } + return text; + } + + /** Start a tab running a single process and collect what it reports. */ + async function runProcess(spec) { + const exits = []; + const result = await __PR.execCommand(__PR.Commands.VIEW_TERMINAL, { + process: spec, + onProcessExit: function (exitCode) { + exits.push(exitCode); + } + }); + return {result, exits}; + } + + /** Close a tab, confirming the running-process prompt if one appears. */ + async function closeTab(instance) { + testWindow.$('.terminal-flyout-item[data-terminal-id="' + instance.id + '"] .terminal-flyout-close') + .click(); + await awaitsFor(function () { + return isDialogOpen() || instance._disposed; + }, "the close to ask or finish", 10000); + if (isDialogOpen()) { + __PR.clickDialogButtonID(__PR.Dialogs.DIALOG_BTN_OK); + await __PR.waitForModalDialogClosed(); + } + await awaitsFor(function () { + return instance._disposed; + }, "tab to close", 10000); + } + + it("reports a clean exit and keeps the tab with its output", async function () { + const {result, exits} = await runProcess(scriptProcess("exit 0")); + expect(result && result.terminalId).toBeTruthy(); + await awaitsFor(function () { + return exits.length === 1; + }, "the exit to be reported", 10000); + expect(exits).toEqual([0]); + + const instance = termModule._getActiveTerminal(); + expect(instance.id).toBe(result.terminalId); + expect(instance.isAlive).toBeFalse(); + expect(getTerminalCount()).toBe(1); + await awaitsFor(function () { + return bufferText(instance).includes("[Process exited with code 0]"); + }, "the exit line to be shown", 5000); + }); + + it("reports a failing exit code", async function () { + const {exits} = await runProcess(scriptProcess("exit 3")); + await awaitsFor(function () { + return exits.length === 1; + }, "the exit to be reported", 10000); + expect(exits).toEqual([3]); + }); + + it("reports null once when the tab is closed before the process exits", async function () { + const {result, exits} = await runProcess(longRunningProcess()); + const instance = termModule._getActiveTerminal(); + expect(instance.id).toBe(result.terminalId); + await awaitsFor(function () { + return instance.isAlive; + }, "the process to be running", 10000); + + await closeTab(instance); + expect(exits).toEqual([null]); + }); + + it("reports null and returns null when the process cannot start", async function () { + const {result, exits} = await runProcess({path: "", args: []}); + expect(result).toBeNull(); + expect(exits).toEqual([null]); + }); + + it("does not keep a process tab's name for the next session", async function () { + StateManager.set(STATE_TAB_NAMES, []); + await runProcess(longRunningProcess()); + // name it, the way the pencil does + testWindow.$(".terminal-flyout-item.active .terminal-flyout-edit").click(); + const $field = testWindow.$(".terminal-flyout-rename"); + $field.val("login"); + $field.trigger(testWindow.$.Event("keydown", {key: "Enter"})); + await awaitsFor(function () { + return testWindow.$(".terminal-flyout-item.active .terminal-flyout-title").text() === "login"; + }, "tab to take the typed name", 5000); + + // it would come back as a plain shell, so it is not one of the tabs to restore + expect(StateManager.get(STATE_TAB_NAMES)).toEqual([]); + }); + }); }); }); diff --git a/tracking-repos.json b/tracking-repos.json index 6e3878ec2a..e929599033 100644 --- a/tracking-repos.json +++ b/tracking-repos.json @@ -1,5 +1,5 @@ { "phoenixPro": { - "commitID": "7490a7fe9ec83db4dd13069d6bec64cb8866562c" + "commitID": "19028ba4921d3ef0abb1891486ea80eb54d9a1be" } } From bb5ef9bd5f18986b6c689e4dda33d84e05983f52 Mon Sep 17 00:00:00 2001 From: abose Date: Sat, 24 Oct 2026 14:29:46 +0530 Subject: [PATCH 2/5] chore(deps): pin Pro upgrade carousel ordering Pin Phoenix Pro to 35793ccb64a2e20a6d679cc68733f0bc3ca98cbe. App-upgrade trial and paid-Pro what's-new dialogs now lead with the AI feature; first-time users keep the current Live Preview first flow. The Pro change was manually verified in native Electron, including slide content, captions, arrows, dots, and auto-advance. No test suites were run for this presentational follow-up, as requested. --- tracking-repos.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tracking-repos.json b/tracking-repos.json index e929599033..d5cb478e59 100644 --- a/tracking-repos.json +++ b/tracking-repos.json @@ -1,5 +1,5 @@ { "phoenixPro": { - "commitID": "19028ba4921d3ef0abb1891486ea80eb54d9a1be" + "commitID": "35793ccb64a2e20a6d679cc68733f0bc3ca98cbe" } } From 32d3bdcc724a7afee53371bf329396d9fef9f8db Mon Sep 17 00:00:00 2001 From: abose Date: Sat, 24 Oct 2026 14:57:09 +0530 Subject: [PATCH 3/5] fix(pro): refresh AI promotion copy and carousel pin Use the approved AI slide heading, "Your AI Agents, Now Visual in Phoenix Pro", and describe bringing an existing Claude Code or ChatGPT subscription into Phoenix Code to build and refine websites visually. Keep the text in the existing English localization keys, without the feather emoji. Pin Phoenix Pro to 07c789f0cbab5ed9de9cf91b68b37d61719cb4e7 so upgrade and paid-Pro dialogs present AI, Layers, Live Preview, then Markdown. First-time users retain the existing slide order. The final wording and carousel were reviewed in the connected Linux debug app. Targeted ESLint and whitespace checks pass. No test suites were added or run for this presentational follow-up, as requested. --- src/nls/root/strings.js | 4 ++-- tracking-repos.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index ef66278590..bdeaef1344 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -2549,8 +2549,8 @@ define({ "PROMO_PRO_WHATS_NEW_MESSAGE": "Thanks for supporting {0}. Here’s what’s new in this update:", "PROMO_CARD_LIVE_PREVIEW": "Edit and style in Live Preview", "PROMO_CARD_LIVE_PREVIEW_MESSAGE": "Change text, colors and fonts, rearrange elements, and try different screen sizes. Your code updates as you go.", - "PROMO_CARD_AI": "Build and refine with AI", - "PROMO_CARD_AI_MESSAGE": "Turn an idea into a page, then select an element or a piece of code and ask AI to make changes.", + "PROMO_CARD_AI": "Your AI Agents, Now Visual in Phoenix Pro", + "PROMO_CARD_AI_MESSAGE": "Bring your existing Claude Code or ChatGPT subscription into Phoenix Code, and unlock a whole new way to build and refine websites visually.", "PROMO_CARD_LAYERS": "Shape your page with Layers", "PROMO_CARD_LAYERS_MESSAGE": "Explore your page structure, insert elements, and edit classes and styles. See every change in Live Preview.", "PROMO_CARD_MARKDOWN": "Write Markdown like a document", diff --git a/tracking-repos.json b/tracking-repos.json index d5cb478e59..c25a6aa1e7 100644 --- a/tracking-repos.json +++ b/tracking-repos.json @@ -1,5 +1,5 @@ { "phoenixPro": { - "commitID": "35793ccb64a2e20a6d679cc68733f0bc3ca98cbe" + "commitID": "07c789f0cbab5ed9de9cf91b68b37d61719cb4e7" } } From 1211f5b4cbc9c92453e756f30be361e2feb49216 Mon Sep 17 00:00:00 2001 From: abose Date: Sat, 24 Oct 2026 15:48:00 +0530 Subject: [PATCH 4/5] 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 c25a6aa1e7..3b2cc5726d 100644 --- a/tracking-repos.json +++ b/tracking-repos.json @@ -1,5 +1,5 @@ { "phoenixPro": { - "commitID": "07c789f0cbab5ed9de9cf91b68b37d61719cb4e7" + "commitID": "1183806e12e1ff7f3152fb617087a99f84a12006" } } From 6112f14bd4171973aadfb233aeccd9351392b547 Mon Sep 17 00:00:00 2001 From: abose Date: Sat, 24 Oct 2026 15:58:34 +0530 Subject: [PATCH 5/5] fix(ai): remove the expanded Surprise Me divider Hide the close button's left border when the Surprise Me card is expanded. Keep the original divider in the first-time card and preserve button dimensions and behavior. Verified both layouts in the connected Linux app and compiled the LESS stylesheet successfully. No test suites were added or run for this CSS-only change. --- 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 6e056610b7..b0385486ed 100644 --- a/src/styles/Extn-AIChatPanel.less +++ b/src/styles/Extn-AIChatPanel.less @@ -1293,6 +1293,10 @@ } .ai-start-surprise-open { + .ai-start-surprise-dismiss { + border-left: 0; + } + .ai-start-surprise-choice { display: block; animation: ai-start-choice-open 0.22s ease-out;