Minimized drawer -- update consumers

Part 3 of the 3 minimized drawer changes. Updates Console and AI
Assistance panels to be aware of the new minimized drawer state.

Console changes:
- console.toggle now minimizes/expands the drawer instead of
  closing it, so the drawer persists across toggle cycles.
- ConsolePanel and WrapperView save and restore the drawer's
  minimized state across show/hide transitions to prevent
  unexpected state changes when switching panels.

Tests:
- Unit tests for the updated console.toggle behavior.
- AI Assistance panel tests for visibility events when the
  drawer is minimized/expanded.
- E2E tests for drawer staying minimized across main-panel tab
  switches and for the toggle-console open/minimize/restore cycle.

Original CL:
https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/7566246

Part 1 (refactor InspectorView):
https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/7685970

Part 2 (core minimization feature):
https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/7708162

Bug: 483762280
Change-Id: I31b2ad99ebd369515a89b4071e23b2bab93a0143
Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/7737421
Reviewed-by: Danil Somsikov <dsv@chromium.org>
Commit-Queue: Guangyue Xu <guangyue.xu@microsoft.com>
Reviewed-by: Piotr Paulski <piotrpaulski@chromium.org>
diff --git a/front_end/panels/ai_assistance/AiAssistancePanel.test.ts b/front_end/panels/ai_assistance/AiAssistancePanel.test.ts
index 691f5bc..9fc3d62 100644
--- a/front_end/panels/ai_assistance/AiAssistancePanel.test.ts
+++ b/front_end/panels/ai_assistance/AiAssistancePanel.test.ts
@@ -1776,6 +1776,40 @@
           });
     }
 
+    it('should update the AI Assistance visibility status when the drawer visibility changes', async () => {
+      updateHostConfig({
+        devToolsFreestyler: {
+          enabled: true,
+        },
+      });
+
+      viewManagerIsViewVisibleStub.callsFake(viewName => viewName === 'elements');
+      const {view} = await createAiAssistancePanel();
+
+      assert(view.input.state === AiAssistancePanel.ViewState.CHAT_VIEW);
+      assert.strictEqual(
+          view.input.props.conversationType, AiAssistanceModel.AiHistoryStorage.ConversationType.STYLING);
+
+      viewManagerIsViewVisibleStub.returns(false);
+      UI.ViewManager.ViewManager.instance().dispatchEventToListeners(UI.ViewManager.Events.VIEW_VISIBILITY_CHANGED, {
+        location: 'drawer',
+        revealedViewId: undefined,
+        hiddenViewId: 'elements',
+      });
+      let nextInput = await view.nextInput;
+      assert(nextInput.state === AiAssistancePanel.ViewState.EXPLORE_VIEW);
+
+      viewManagerIsViewVisibleStub.callsFake(viewName => viewName === 'elements');
+      UI.ViewManager.ViewManager.instance().dispatchEventToListeners(UI.ViewManager.Events.VIEW_VISIBILITY_CHANGED, {
+        location: 'drawer',
+        revealedViewId: 'elements',
+        hiddenViewId: undefined,
+      });
+      nextInput = await view.nextInput;
+      assert(nextInput.state === AiAssistancePanel.ViewState.CHAT_VIEW);
+      assert.strictEqual(nextInput.props.conversationType, AiAssistanceModel.AiHistoryStorage.ConversationType.STYLING);
+    });
+
     it('should refresh its state when moved', async () => {
       updateHostConfig({
         devToolsFreestyler: {
diff --git a/front_end/panels/console/ConsolePanel.ts b/front_end/panels/console/ConsolePanel.ts
index e8cf620..364793d0a7 100644
--- a/front_end/panels/console/ConsolePanel.ts
+++ b/front_end/panels/console/ConsolePanel.ts
@@ -41,6 +41,7 @@
 
 export class ConsolePanel extends UI.Panel.Panel {
   private readonly view: ConsoleView;
+  #drawerWasMinimized = false;
   constructor() {
     super('console');
     this.view = ConsoleView.instance();
@@ -64,9 +65,11 @@
 
   override wasShown(): void {
     super.wasShown();
+    const inspectorView = UI.InspectorView.InspectorView.instance();
+    this.#drawerWasMinimized = inspectorView.isDrawerMinimized();
     const wrapper = wrapperViewInstance;
     if (wrapper?.isShowing()) {
-      UI.InspectorView.InspectorView.instance().setDrawerMinimized(true);
+      inspectorView.setDrawerMinimized(true);
     }
     this.view.show(this.element);
     ConsolePanel.updateContextFlavor();
@@ -74,12 +77,17 @@
 
   override willHide(): void {
     super.willHide();
+    const inspectorView = UI.InspectorView.InspectorView.instance();
     // The minimized drawer has 0 height, and showing Console inside may set
     // Console's scrollTop to 0. Unminimize before calling show to avoid this.
-    UI.InspectorView.InspectorView.instance().setDrawerMinimized(false);
+    // Restore the previous minimized state afterwards.
+    inspectorView.setDrawerMinimized(false);
     if (wrapperViewInstance) {
       wrapperViewInstance.showViewInWrapper();
     }
+    if (this.#drawerWasMinimized) {
+      inspectorView.setDrawerMinimized(true);
+    }
     ConsolePanel.updateContextFlavor();
   }
 
@@ -117,7 +125,6 @@
 
   override willHide(): void {
     super.willHide();
-    UI.InspectorView.InspectorView.instance().setDrawerMinimized(false);
     ConsolePanel.updateContextFlavor();
   }
 
diff --git a/front_end/panels/console/ConsoleView.test.ts b/front_end/panels/console/ConsoleView.test.ts
index cd53ad5..99ec516 100644
--- a/front_end/panels/console/ConsoleView.test.ts
+++ b/front_end/panels/console/ConsoleView.test.ts
@@ -38,6 +38,100 @@
     consoleView.detach();
   });
 
+  it('expands a minimized drawer when toggling console', () => {
+    const inspectorView = UI.InspectorView.InspectorView.instance({forceNew: true});
+    const drawerVisibleStub = sinon.stub(inspectorView, 'drawerVisible').returns(true);
+    const isDrawerMinimizedStub = sinon.stub(inspectorView, 'isDrawerMinimized').returns(true);
+    const setDrawerMinimizedStub = sinon.stub(inspectorView, 'setDrawerMinimized');
+    const hasFocusStub = sinon.stub(consoleView, 'hasFocus').returns(false);
+    const bringToFrontStub = sinon.stub(Host.InspectorFrontendHost.InspectorFrontendHostInstance, 'bringToFront');
+    const showStub = sinon.stub(Common.Console.Console.instance(), 'show');
+    const focusPromptStub = sinon.stub(consoleView, 'focusPrompt');
+
+    const delegate = new Console.ConsoleView.ActionDelegate();
+    assert.isTrue(delegate.handleAction({} as UI.Context.Context, 'console.toggle'));
+
+    sinon.assert.calledOnceWithExactly(setDrawerMinimizedStub, false);
+    sinon.assert.calledOnce(showStub);
+    sinon.assert.calledOnce(focusPromptStub);
+    sinon.assert.calledOnce(bringToFrontStub);
+
+    drawerVisibleStub.restore();
+    isDrawerMinimizedStub.restore();
+    setDrawerMinimizedStub.restore();
+    hasFocusStub.restore();
+    bringToFrontStub.restore();
+    showStub.restore();
+    focusPromptStub.restore();
+    UI.InspectorView.InspectorView.removeInstance();
+  });
+
+  it('minimizes drawer when console is already shown and focused in expanded drawer', () => {
+    const inspectorView = UI.InspectorView.InspectorView.instance({forceNew: true});
+    const drawerVisibleStub = sinon.stub(inspectorView, 'drawerVisible').returns(true);
+    const isDrawerMinimizedStub = sinon.stub(inspectorView, 'isDrawerMinimized').returns(false);
+    const setDrawerMinimizedStub = sinon.stub(inspectorView, 'setDrawerMinimized');
+    const minimizeDrawerStub = sinon.stub(inspectorView, 'minimizeDrawer');
+    const isShowingStub = sinon.stub(consoleView, 'isShowing').returns(true);
+    const hasFocusStub = sinon.stub(consoleView, 'hasFocus').returns(true);
+    const bringToFrontStub = sinon.stub(Host.InspectorFrontendHost.InspectorFrontendHostInstance, 'bringToFront');
+    const showStub = sinon.stub(Common.Console.Console.instance(), 'show');
+    const focusPromptStub = sinon.stub(consoleView, 'focusPrompt');
+
+    const delegate = new Console.ConsoleView.ActionDelegate();
+    assert.isTrue(delegate.handleAction({} as UI.Context.Context, 'console.toggle'));
+
+    sinon.assert.notCalled(setDrawerMinimizedStub);
+    sinon.assert.calledOnce(minimizeDrawerStub);
+    sinon.assert.notCalled(showStub);
+    sinon.assert.notCalled(focusPromptStub);
+    sinon.assert.notCalled(bringToFrontStub);
+
+    drawerVisibleStub.restore();
+    isDrawerMinimizedStub.restore();
+    setDrawerMinimizedStub.restore();
+    minimizeDrawerStub.restore();
+    isShowingStub.restore();
+    hasFocusStub.restore();
+    bringToFrontStub.restore();
+    showStub.restore();
+    focusPromptStub.restore();
+    UI.InspectorView.InspectorView.removeInstance();
+  });
+
+  it('focuses console prompt when drawer is expanded but console is not focused', () => {
+    const inspectorView = UI.InspectorView.InspectorView.instance({forceNew: true});
+    const drawerVisibleStub = sinon.stub(inspectorView, 'drawerVisible').returns(true);
+    const isDrawerMinimizedStub = sinon.stub(inspectorView, 'isDrawerMinimized').returns(false);
+    const setDrawerMinimizedStub = sinon.stub(inspectorView, 'setDrawerMinimized');
+    const minimizeDrawerStub = sinon.stub(inspectorView, 'minimizeDrawer');
+    const isShowingStub = sinon.stub(consoleView, 'isShowing').returns(true);
+    const hasFocusStub = sinon.stub(consoleView, 'hasFocus').returns(false);
+    const bringToFrontStub = sinon.stub(Host.InspectorFrontendHost.InspectorFrontendHostInstance, 'bringToFront');
+    const showStub = sinon.stub(Common.Console.Console.instance(), 'show');
+    const focusPromptStub = sinon.stub(consoleView, 'focusPrompt');
+
+    const delegate = new Console.ConsoleView.ActionDelegate();
+    assert.isTrue(delegate.handleAction({} as UI.Context.Context, 'console.toggle'));
+
+    sinon.assert.notCalled(minimizeDrawerStub);
+    sinon.assert.notCalled(setDrawerMinimizedStub);
+    sinon.assert.calledOnce(bringToFrontStub);
+    sinon.assert.calledOnce(showStub);
+    sinon.assert.calledOnce(focusPromptStub);
+
+    drawerVisibleStub.restore();
+    isDrawerMinimizedStub.restore();
+    setDrawerMinimizedStub.restore();
+    minimizeDrawerStub.restore();
+    isShowingStub.restore();
+    hasFocusStub.restore();
+    bringToFrontStub.restore();
+    showStub.restore();
+    focusPromptStub.restore();
+    UI.InspectorView.InspectorView.removeInstance();
+  });
+
   it('adds a title to every checkbox label in the settings view', async () => {
     const consoleSettingsCheckboxes =
         consoleView.element.querySelector('devtools-toolbar')!.querySelectorAll('devtools-checkbox');
diff --git a/front_end/panels/console/ConsoleView.ts b/front_end/panels/console/ConsoleView.ts
index 593c7e8..6fa14c9 100644
--- a/front_end/panels/console/ConsoleView.ts
+++ b/front_end/panels/console/ConsoleView.ts
@@ -1959,15 +1959,22 @@
 export class ActionDelegate implements UI.ActionRegistration.ActionDelegate {
   handleAction(_context: UI.Context.Context, actionId: string): boolean {
     switch (actionId) {
-      case 'console.toggle':
-        if (ConsoleView.instance().hasFocus() && UI.InspectorView.InspectorView.instance().drawerVisible()) {
-          UI.InspectorView.InspectorView.instance().closeDrawer();
+      case 'console.toggle': {
+        const inspectorView = UI.InspectorView.InspectorView.instance();
+        const consoleView = ConsoleView.instance();
+        if (inspectorView.drawerVisible() && !inspectorView.isDrawerMinimized() && consoleView.isShowing() &&
+            consoleView.hasFocus()) {
+          inspectorView.minimizeDrawer();
           return true;
         }
+        if (inspectorView.drawerVisible() && inspectorView.isDrawerMinimized()) {
+          inspectorView.setDrawerMinimized(false);
+        }
         Host.InspectorFrontendHost.InspectorFrontendHostInstance.bringToFront();
         Common.Console.Console.instance().show();
-        ConsoleView.instance().focusPrompt();
+        consoleView.focusPrompt();
         return true;
+      }
       case 'console.clear':
         ConsoleView.instance().clearConsole();
         return true;
diff --git a/test/e2e/inspector/BUILD.gn b/test/e2e/inspector/BUILD.gn
index a4158b6..c308207 100644
--- a/test/e2e/inspector/BUILD.gn
+++ b/test/e2e/inspector/BUILD.gn
@@ -6,5 +6,8 @@
 
 ts_e2e_library("inspector") {
   sources = [ "drawer.test.ts" ]
-  deps = [ "../../shared" ]
+  deps = [
+    "../../shared",
+    "../helpers",
+  ]
 }
diff --git a/test/e2e/inspector/drawer.test.ts b/test/e2e/inspector/drawer.test.ts
index 374bd0d..766dd41 100644
--- a/test/e2e/inspector/drawer.test.ts
+++ b/test/e2e/inspector/drawer.test.ts
@@ -4,6 +4,7 @@
 
 import {assert} from 'chai';
 
+import {runCommandWithQuickOpen} from '../helpers/quick_open-helpers.js';
 import type {DevToolsPage} from '../shared/frontend-helper.js';
 
 const MINIMIZE_BUTTON_SELECTOR = '[aria-label="Minimize drawer"]';
@@ -431,6 +432,42 @@
     assert.exists(expandButton, 'Drawer should remain minimized after toggling orientation back and forth');
   });
 
+  it('drawer stays minimized when switching main panel tabs', async ({devToolsPage}) => {
+    // Show the drawer
+    await devToolsPage.pressKey('Escape');
+    await devToolsPage.waitFor(DRAWER_SELECTOR);
+
+    // Minimize the drawer
+    await devToolsPage.click(MINIMIZE_BUTTON_SELECTOR);
+    await devToolsPage.waitFor(EXPAND_BUTTON_SELECTOR);
+
+    // Click Console tab in the main tab bar
+    await devToolsPage.click('#tab-console');
+    // Switch to Elements tab
+    await devToolsPage.click('#tab-elements');
+
+    // The drawer should still be minimized (expand button visible, not minimize button)
+    const expandButton = await devToolsPage.waitFor(EXPAND_BUTTON_SELECTOR);
+    assert.exists(expandButton, 'Drawer should remain minimized after switching main panel tabs');
+  });
+
+  it('toggle console command opens, minimizes, and restores the drawer', async ({devToolsPage}) => {
+    await devToolsPage.click('#tab-elements');
+    await devToolsPage.waitForNone(DRAWER_SELECTOR);
+
+    await runCommandWithQuickOpen('Toggle console', devToolsPage);
+    await devToolsPage.waitFor(DRAWER_SELECTOR);
+    await devToolsPage.waitFor(MINIMIZE_BUTTON_SELECTOR);
+
+    await runCommandWithQuickOpen('Toggle console', devToolsPage);
+    await devToolsPage.waitFor(DRAWER_SELECTOR);
+    await devToolsPage.waitFor(EXPAND_BUTTON_SELECTOR);
+
+    await runCommandWithQuickOpen('Toggle console', devToolsPage);
+    await devToolsPage.waitFor(DRAWER_SELECTOR);
+    await devToolsPage.waitFor(MINIMIZE_BUTTON_SELECTOR);
+  });
+
   it('clicking drawer console tab minimizes expanded drawer when main console is active', async ({devToolsPage}) => {
     // Move Elements to drawer so drawer has at least two tabs and Console tab is clickable.
     await clickOnContextMenuItemFromTab('#tab-elements', MOVE_TO_DRAWER_SELECTOR, devToolsPage);