chrome-devtools-frontend 1.0.1603822 → 1.0.1604514

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/front_end/core/host/AidaClient.ts +39 -462
  2. package/front_end/core/host/AidaClientTypes.ts +470 -0
  3. package/front_end/core/host/AidaGcaTranslation.ts +225 -122
  4. package/front_end/core/host/GcaTypes.ts +107 -155
  5. package/front_end/core/protocol_client/DevToolsCDPConnection.ts +1 -1
  6. package/front_end/core/sdk/DOMModel.ts +24 -22
  7. package/front_end/core/sdk/EmulationModel.ts +21 -23
  8. package/front_end/core/sdk/HeapProfilerModel.ts +4 -7
  9. package/front_end/core/sdk/NetworkManager.ts +35 -28
  10. package/front_end/core/sdk/OverlayModel.ts +10 -10
  11. package/front_end/core/sdk/PreloadingModel.ts +3 -4
  12. package/front_end/core/sdk/ServiceWorkerManager.ts +2 -3
  13. package/front_end/core/sdk/SourceMapScopesInfo.ts +93 -63
  14. package/front_end/entrypoints/formatter_worker/FormatterWorker.ts +1 -3
  15. package/front_end/entrypoints/heap_snapshot_worker/AllocationProfile.ts +22 -48
  16. package/front_end/entrypoints/heap_snapshot_worker/HeapSnapshot.ts +1 -1
  17. package/front_end/generated/InspectorBackendCommands.ts +4 -0
  18. package/front_end/generated/SupportedCSSProperties.js +68 -2
  19. package/front_end/generated/protocol-mapping.d.ts +7 -0
  20. package/front_end/generated/protocol-proxy-api.d.ts +14 -0
  21. package/front_end/generated/protocol.ts +22 -0
  22. package/front_end/models/bindings/TempFile.ts +1 -4
  23. package/front_end/models/extensions/ExtensionAPI.ts +1 -1
  24. package/front_end/models/heap_snapshot_model/HeapSnapshotModel.ts +34 -47
  25. package/front_end/models/javascript_metadata/NativeFunctions.js +6 -6
  26. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +88 -19
  27. package/front_end/panels/ai_assistance/components/ChatMessage.ts +14 -20
  28. package/front_end/panels/ai_assistance/components/ChatView.ts +3 -2
  29. package/front_end/panels/ai_assistance/components/WalkthroughView.ts +35 -7
  30. package/front_end/panels/elements/ElementsTreeElement.ts +91 -13
  31. package/front_end/panels/elements/elementsTreeOutline.css +18 -0
  32. package/front_end/panels/profiler/HeapSnapshotDataGrids.ts +15 -19
  33. package/front_end/panels/profiler/HeapSnapshotGridNodes.ts +4 -2
  34. package/front_end/panels/profiler/HeapSnapshotProxy.ts +12 -16
  35. package/front_end/panels/profiler/HeapSnapshotView.ts +3 -10
  36. package/front_end/panels/profiler/ProfilesPanel.ts +5 -7
  37. package/front_end/panels/sensors/SensorsView.ts +0 -2
  38. package/front_end/panels/timeline/TimelinePanel.ts +1 -3
  39. package/front_end/panels/timeline/components/LiveMetricsView.ts +35 -63
  40. package/front_end/panels/timeline/components/liveMetricsView.css +4 -0
  41. package/front_end/third_party/chromium/README.chromium +1 -1
  42. package/package.json +1 -1
@@ -1747,10 +1747,16 @@ export const generatedProperties = [
1747
1747
  },
1748
1748
  {
1749
1749
  "inherited": false,
1750
+ "keywords": [
1751
+ "overlap-join"
1752
+ ],
1750
1753
  "name": "column-rule-edge-inset-end"
1751
1754
  },
1752
1755
  {
1753
1756
  "inherited": false,
1757
+ "keywords": [
1758
+ "overlap-join"
1759
+ ],
1754
1760
  "name": "column-rule-edge-inset-start"
1755
1761
  },
1756
1762
  {
@@ -1785,10 +1791,16 @@ export const generatedProperties = [
1785
1791
  },
1786
1792
  {
1787
1793
  "inherited": false,
1794
+ "keywords": [
1795
+ "overlap-join"
1796
+ ],
1788
1797
  "name": "column-rule-interior-inset-end"
1789
1798
  },
1790
1799
  {
1791
1800
  "inherited": false,
1801
+ "keywords": [
1802
+ "overlap-join"
1803
+ ],
1792
1804
  "name": "column-rule-interior-inset-start"
1793
1805
  },
1794
1806
  {
@@ -2805,7 +2817,8 @@ export const generatedProperties = [
2805
2817
  "keywords": [
2806
2818
  "normal",
2807
2819
  "running",
2808
- "paused"
2820
+ "paused",
2821
+ "stopped"
2809
2822
  ],
2810
2823
  "name": "image-animation"
2811
2824
  },
@@ -3814,10 +3827,16 @@ export const generatedProperties = [
3814
3827
  },
3815
3828
  {
3816
3829
  "inherited": false,
3830
+ "keywords": [
3831
+ "overlap-join"
3832
+ ],
3817
3833
  "name": "row-rule-edge-inset-end"
3818
3834
  },
3819
3835
  {
3820
3836
  "inherited": false,
3837
+ "keywords": [
3838
+ "overlap-join"
3839
+ ],
3821
3840
  "name": "row-rule-edge-inset-start"
3822
3841
  },
3823
3842
  {
@@ -3852,10 +3871,16 @@ export const generatedProperties = [
3852
3871
  },
3853
3872
  {
3854
3873
  "inherited": false,
3874
+ "keywords": [
3875
+ "overlap-join"
3876
+ ],
3855
3877
  "name": "row-rule-interior-inset-end"
3856
3878
  },
3857
3879
  {
3858
3880
  "inherited": false,
3881
+ "keywords": [
3882
+ "overlap-join"
3883
+ ],
3859
3884
  "name": "row-rule-interior-inset-start"
3860
3885
  },
3861
3886
  {
@@ -5598,6 +5623,26 @@ export const generatedPropertyValues = {
5598
5623
  "currentcolor"
5599
5624
  ]
5600
5625
  },
5626
+ "column-rule-edge-inset-end": {
5627
+ "values": [
5628
+ "overlap-join"
5629
+ ]
5630
+ },
5631
+ "column-rule-edge-inset-start": {
5632
+ "values": [
5633
+ "overlap-join"
5634
+ ]
5635
+ },
5636
+ "column-rule-interior-inset-end": {
5637
+ "values": [
5638
+ "overlap-join"
5639
+ ]
5640
+ },
5641
+ "column-rule-interior-inset-start": {
5642
+ "values": [
5643
+ "overlap-join"
5644
+ ]
5645
+ },
5601
5646
  "column-rule-style": {
5602
5647
  "values": [
5603
5648
  "none",
@@ -6220,7 +6265,8 @@ export const generatedPropertyValues = {
6220
6265
  "values": [
6221
6266
  "normal",
6222
6267
  "running",
6223
- "paused"
6268
+ "paused",
6269
+ "stopped"
6224
6270
  ]
6225
6271
  },
6226
6272
  "image-rendering": {
@@ -6737,6 +6783,26 @@ export const generatedPropertyValues = {
6737
6783
  "currentcolor"
6738
6784
  ]
6739
6785
  },
6786
+ "row-rule-edge-inset-end": {
6787
+ "values": [
6788
+ "overlap-join"
6789
+ ]
6790
+ },
6791
+ "row-rule-edge-inset-start": {
6792
+ "values": [
6793
+ "overlap-join"
6794
+ ]
6795
+ },
6796
+ "row-rule-interior-inset-end": {
6797
+ "values": [
6798
+ "overlap-join"
6799
+ ]
6800
+ },
6801
+ "row-rule-interior-inset-start": {
6802
+ "values": [
6803
+ "overlap-join"
6804
+ ]
6805
+ },
6740
6806
  "row-rule-style": {
6741
6807
  "values": [
6742
6808
  "none",
@@ -1892,6 +1892,13 @@ export namespace ProtocolMapping {
1892
1892
  paramsType: [Protocol.Cast.StopCastingRequest];
1893
1893
  returnType: void;
1894
1894
  };
1895
+ /**
1896
+ * Returns all entries in the CrashReportContext across all frames in the page.
1897
+ */
1898
+ 'CrashReportContext.getEntries': {
1899
+ paramsType: [];
1900
+ returnType: Protocol.CrashReportContext.GetEntriesResponse;
1901
+ };
1895
1902
  /**
1896
1903
  * Collects class names for the node with given id and all of it's child nodes.
1897
1904
  */
@@ -38,6 +38,8 @@ declare namespace ProtocolProxyApi {
38
38
 
39
39
  Cast: CastApi;
40
40
 
41
+ CrashReportContext: CrashReportContextApi;
42
+
41
43
  DOM: DOMApi;
42
44
 
43
45
  DOMDebugger: DOMDebuggerApi;
@@ -149,6 +151,8 @@ declare namespace ProtocolProxyApi {
149
151
 
150
152
  Cast: CastDispatcher;
151
153
 
154
+ CrashReportContext: CrashReportContextDispatcher;
155
+
152
156
  DOM: DOMDispatcher;
153
157
 
154
158
  DOMDebugger: DOMDebuggerDispatcher;
@@ -1046,6 +1050,16 @@ declare namespace ProtocolProxyApi {
1046
1050
 
1047
1051
  }
1048
1052
 
1053
+ export interface CrashReportContextApi {
1054
+ /**
1055
+ * Returns all entries in the CrashReportContext across all frames in the page.
1056
+ */
1057
+ invoke_getEntries(): Promise<Protocol.CrashReportContext.GetEntriesResponse>;
1058
+
1059
+ }
1060
+ export interface CrashReportContextDispatcher {
1061
+ }
1062
+
1049
1063
  export interface DOMApi {
1050
1064
  /**
1051
1065
  * Collects class names for the node with given id and all of it's child nodes.
@@ -4525,6 +4525,28 @@ export namespace Cast {
4525
4525
  }
4526
4526
  }
4527
4527
 
4528
+ /**
4529
+ * This domain exposes the current state of the CrashReportContext API.
4530
+ */
4531
+ export namespace CrashReportContext {
4532
+
4533
+ /**
4534
+ * Key-value pair in CrashReportContext.
4535
+ */
4536
+ export interface CrashReportContextEntry {
4537
+ key: string;
4538
+ value: string;
4539
+ /**
4540
+ * The ID of the frame where the key-value pair was set.
4541
+ */
4542
+ frameId: Page.FrameId;
4543
+ }
4544
+
4545
+ export interface GetEntriesResponse extends ProtocolResponseWithError {
4546
+ entries: CrashReportContextEntry[];
4547
+ }
4548
+ }
4549
+
4528
4550
  /**
4529
4551
  * This domain exposes DOM read/write operations. Each DOM Node is represented with its mirror object
4530
4552
  * that has an `id`. This `id` can be used to get additional information on the Node, resolve it into
@@ -7,10 +7,7 @@ import * as Common from '../../core/common/common.js';
7
7
  import {ChunkedFileReader, type ChunkedReader} from './FileUtils.js';
8
8
 
9
9
  export class TempFile {
10
- #lastBlob: Blob|null;
11
- constructor() {
12
- this.#lastBlob = null;
13
- }
10
+ #lastBlob: Blob|null = null;
14
11
 
15
12
  write(pieces: Array<string|Blob>): void {
16
13
  if (this.#lastBlob) {
@@ -616,7 +616,7 @@ self.injectedExtensionAPI = function(
616
616
  entries[i].__proto__ = new (Constructor(Request))(entries[i]._requestId as number);
617
617
  delete entries[i]._requestId;
618
618
  }
619
- callback?.(result as Object);
619
+ callback?.(result);
620
620
  }
621
621
  extensionServer.sendRequest({command: PrivateAPI.Commands.GetHAR}, callback && callbackWrapper);
622
622
  },
@@ -71,13 +71,19 @@ export class Node {
71
71
  retainedSize: number;
72
72
  selfSize: number;
73
73
  type: string;
74
- canBeQueried: boolean;
75
- detachedDOMTreeNode: boolean;
76
- isAddedNotRemoved: boolean|null;
77
- ignored: boolean;
74
+ canBeQueried = false;
75
+ detachedDOMTreeNode = false;
76
+ isAddedNotRemoved: boolean|null = null;
77
+ ignored = false;
78
78
  constructor(
79
- id: number, name: string, distance: number, nodeIndex: number, retainedSize: number, selfSize: number,
80
- type: string) {
79
+ id: number,
80
+ name: string,
81
+ distance: number,
82
+ nodeIndex: number,
83
+ retainedSize: number,
84
+ selfSize: number,
85
+ type: string,
86
+ ) {
81
87
  this.id = id;
82
88
  this.name = name;
83
89
  this.distance = distance;
@@ -85,11 +91,6 @@ export class Node {
85
91
  this.retainedSize = retainedSize;
86
92
  this.selfSize = selfSize;
87
93
  this.type = type;
88
-
89
- this.canBeQueried = false;
90
- this.detachedDOMTreeNode = false;
91
- this.isAddedNotRemoved = null;
92
- this.ignored = false;
93
94
  }
94
95
  }
95
96
 
@@ -98,13 +99,12 @@ export class Edge {
98
99
  node: Node;
99
100
  type: string;
100
101
  edgeIndex: number;
101
- isAddedNotRemoved: boolean|null;
102
+ isAddedNotRemoved: boolean|null = null;
102
103
  constructor(name: string, node: Node, type: string, edgeIndex: number) {
103
104
  this.name = name;
104
105
  this.node = node;
105
106
  this.type = type;
106
107
  this.edgeIndex = edgeIndex;
107
- this.isAddedNotRemoved = null;
108
108
  }
109
109
  }
110
110
 
@@ -132,37 +132,19 @@ export class AggregateForDiff {
132
132
 
133
133
  export class Diff {
134
134
  name: string;
135
- addedCount: number;
136
- removedCount: number;
137
- addedSize: number;
138
- removedSize: number;
139
- deletedIndexes: number[];
140
- addedIndexes: number[];
135
+ addedCount = 0;
136
+ removedCount = 0;
137
+ addedSize = 0;
138
+ removedSize = 0;
139
+ deletedIndexes: number[] = [];
140
+ addedIndexes: number[] = [];
141
141
  countDelta!: number;
142
142
  sizeDelta!: number;
143
143
  constructor(name: string) {
144
144
  this.name = name;
145
- this.addedCount = 0;
146
- this.removedCount = 0;
147
- this.addedSize = 0;
148
- this.removedSize = 0;
149
- this.deletedIndexes = [];
150
- this.addedIndexes = [];
151
145
  }
152
146
  }
153
147
 
154
- export class DiffForClass {
155
- name!: string;
156
- addedCount!: number;
157
- removedCount!: number;
158
- addedSize!: number;
159
- removedSize!: number;
160
- deletedIndexes!: number[];
161
- addedIndexes!: number[];
162
- countDelta!: number;
163
- sizeDelta!: number;
164
- }
165
-
166
148
  export class ComparatorConfig {
167
149
  fieldName1: string;
168
150
  ascending1: boolean;
@@ -176,16 +158,16 @@ export class ComparatorConfig {
176
158
  }
177
159
  }
178
160
 
179
- export class WorkerCommand {
180
- callId!: number;
181
- disposition!: string;
182
- objectId!: number;
183
- newObjectId!: number;
184
- methodName!: string;
161
+ export interface WorkerCommand {
162
+ callId: number;
163
+ disposition: string;
164
+ objectId: number;
165
+ newObjectId: number;
166
+ methodName: string;
185
167
  // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration
186
168
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
187
- methodArguments!: any[];
188
- source!: string;
169
+ methodArguments: any[];
170
+ source: string;
189
171
  }
190
172
 
191
173
  export class ItemsRange {
@@ -244,8 +226,13 @@ export class SearchConfig {
244
226
  shouldJump: boolean;
245
227
  jumpBackward: boolean;
246
228
  constructor(
247
- query: string, caseSensitive: boolean, wholeWord: boolean, isRegex: boolean, shouldJump: boolean,
248
- jumpBackward: boolean) {
229
+ query: string,
230
+ caseSensitive: boolean,
231
+ wholeWord: boolean,
232
+ isRegex: boolean,
233
+ shouldJump: boolean,
234
+ jumpBackward: boolean,
235
+ ) {
249
236
  this.query = query;
250
237
  this.caseSensitive = caseSensitive;
251
238
  this.wholeWord = wholeWord;
@@ -6606,16 +6606,12 @@ export const NativeFunctions = [
6606
6606
  name: "replaceWithHTMLUnsafe",
6607
6607
  signatures: [["html","?options"]]
6608
6608
  },
6609
- {
6610
- name: "streamBeforeHTMLUnsafe",
6611
- signatures: [["?options"]]
6612
- },
6613
6609
  {
6614
6610
  name: "streamBeforeHTML",
6615
6611
  signatures: [["?options"]]
6616
6612
  },
6617
6613
  {
6618
- name: "streamAfterHTMLUnsafe",
6614
+ name: "streamBeforeHTMLUnsafe",
6619
6615
  signatures: [["?options"]]
6620
6616
  },
6621
6617
  {
@@ -6623,13 +6619,17 @@ export const NativeFunctions = [
6623
6619
  signatures: [["?options"]]
6624
6620
  },
6625
6621
  {
6626
- name: "streamReplaceWithHTMLUnsafe",
6622
+ name: "streamAfterHTMLUnsafe",
6627
6623
  signatures: [["?options"]]
6628
6624
  },
6629
6625
  {
6630
6626
  name: "streamReplaceWithHTML",
6631
6627
  signatures: [["?options"]]
6632
6628
  },
6629
+ {
6630
+ name: "streamReplaceWithHTMLUnsafe",
6631
+ signatures: [["?options"]]
6632
+ },
6633
6633
  {
6634
6634
  name: "Comment",
6635
6635
  signatures: [["?data"]]
@@ -383,7 +383,9 @@ interface ToolbarViewInput {
383
383
  walkthrough: {
384
384
  isExpanded: boolean,
385
385
  isInlined: boolean,
386
- onToggle: (isOpen: boolean) => void,
386
+ onToggle: (isOpen: boolean, message: ModelChatMessage) => void,
387
+ activeSidebarMessage: ModelChatMessage|null,
388
+ inlineExpandedMessages: ModelChatMessage[],
387
389
  };
388
390
  }
389
391
 
@@ -521,7 +523,7 @@ function defaultView(input: ViewInput, output: PanelViewOutput, target: HTMLElem
521
523
  let walkthroughIsForLastMessage = false;
522
524
  if(input.state === ViewState.CHAT_VIEW) {
523
525
  const lastMessage = input.props.messages.at(-1);
524
- if(lastMessage && input.props.walkthrough.activeMessage === lastMessage) {
526
+ if(lastMessage && input.props.walkthrough.activeSidebarMessage === lastMessage) {
525
527
  walkthroughIsForLastMessage = true;
526
528
  }
527
529
  }
@@ -542,7 +544,7 @@ function defaultView(input: ViewInput, output: PanelViewOutput, target: HTMLElem
542
544
  <div slot="sidebar" class="sidebar-view">
543
545
  ${shouldShowWalkthrough ? html`
544
546
  <devtools-widget ${widget(WalkthroughView, {
545
- message: input.props.walkthrough.activeMessage,
547
+ message: input.props.walkthrough.activeSidebarMessage,
546
548
  isLoading: input.props.isLoading && walkthroughIsForLastMessage,
547
549
  markdownRenderer: input.props.markdownRenderer,
548
550
  onToggle: input.props.walkthrough.onToggle,
@@ -610,10 +612,15 @@ function createPerformanceTraceContext(focus: AiAssistanceModel.AIContext.AgentF
610
612
 
611
613
  /**
612
614
  * State relating to the visibility of the Walkthrough.
613
- * Note that we have to track the active message and the visibility as distinct
614
- * state, because you can toggle the walkthrough via the sidebar, in which case
615
- * we need to make it visible/hidden but keep track of the active message for
616
- * when the user expands it again.
615
+ *
616
+ * We track both an `activeSidebarMessage` and a list of `inlineExpandedMessages` because:
617
+ * 1. In Narrow (inline) mode, multiple walkthroughs can be expanded at once,
618
+ * so we need to track them all to render them correctly in the chat.
619
+ * 2. In Wide (sidebar) mode, only one walkthrough can be visible at a time.
620
+ * The `activeSidebarMessage` tracks which one is shown in the sidebar.
621
+ * 3. When transitioning from Narrow to Wide, we use the last message in
622
+ * `inlineExpandedMessages` to determine which one should stay expanded
623
+ * in the sidebar.
617
624
  */
618
625
  interface WalkthroughState {
619
626
  /**
@@ -625,11 +632,15 @@ interface WalkthroughState {
625
632
  */
626
633
  isExpanded: boolean;
627
634
  /**
628
- * The message that the walkthrough is showing all the steps for. A
629
- * conversation can have many Model messages (1 per each user prompt) so we
630
- * need to track which one we are showing for the walkthrough.
635
+ * The message that the walkthrough is showing all the steps for. In Wide mode,
636
+ * this is the message shown in the sidebar. In Narrow mode, it tracks the
637
+ * most recently interacted message.
631
638
  */
632
- activeMessage: ModelChatMessage|null;
639
+ activeSidebarMessage: ModelChatMessage|null;
640
+ /**
641
+ * Tracks which messages are expanded in inline mode.
642
+ */
643
+ inlineExpandedMessages: ModelChatMessage[];
633
644
  }
634
645
 
635
646
  let panelInstance: AiAssistancePanel;
@@ -667,7 +678,8 @@ export class AiAssistancePanel extends UI.Panel.Panel {
667
678
  #walkthrough: WalkthroughState = {
668
679
  isInlined: false,
669
680
  isExpanded: false,
670
- activeMessage: null,
681
+ activeSidebarMessage: null,
682
+ inlineExpandedMessages: [],
671
683
  };
672
684
 
673
685
  constructor(private view: View = defaultView, {aidaClient, aidaAvailability}: {
@@ -708,6 +720,8 @@ export class AiAssistancePanel extends UI.Panel.Panel {
708
720
  isExpanded: this.#walkthrough.isExpanded,
709
721
  isInlined: this.#walkthrough.isInlined,
710
722
  onToggle: this.#toggleWalkthrough.bind(this),
723
+ activeSidebarMessage: this.#walkthrough.activeSidebarMessage,
724
+ inlineExpandedMessages: this.#walkthrough.inlineExpandedMessages,
711
725
  }
712
726
  };
713
727
  }
@@ -796,7 +810,8 @@ export class AiAssistancePanel extends UI.Panel.Panel {
796
810
  onOpen: this.#openWalkthrough.bind(this),
797
811
  isExpanded: this.#walkthrough.isExpanded,
798
812
  isInlined: this.#walkthrough.isInlined,
799
- activeMessage: this.#walkthrough.activeMessage,
813
+ activeSidebarMessage: this.#walkthrough.activeSidebarMessage,
814
+ inlineExpandedMessages: this.#walkthrough.inlineExpandedMessages,
800
815
  },
801
816
  }
802
817
  };
@@ -820,20 +835,73 @@ export class AiAssistancePanel extends UI.Panel.Panel {
820
835
  if (isNarrow === this.#walkthrough.isInlined) {
821
836
  return;
822
837
  }
823
- // If the UI changed, we reset the visibility of the AI Walkthrough.
824
- this.#resetWalkthrough();
838
+
825
839
  this.#walkthrough.isInlined = isNarrow;
840
+
841
+ if (!this.#walkthrough.isExpanded) {
842
+ // If nothing was expanded, we just ensure the state is clean.
843
+ this.#walkthrough.activeSidebarMessage = null;
844
+ this.#walkthrough.inlineExpandedMessages = [];
845
+ this.requestUpdate();
846
+ return;
847
+ }
848
+
849
+ if (isNarrow) {
850
+ // Wide -> Inline: the walkthrough that was open stays expanded
851
+ this.#walkthrough.inlineExpandedMessages =
852
+ this.#walkthrough.activeSidebarMessage ? [this.#walkthrough.activeSidebarMessage] : [];
853
+ } else {
854
+ // Inline -> Wide: the last walkthrough that the user opened stays expanded
855
+ this.#walkthrough.activeSidebarMessage = this.#walkthrough.inlineExpandedMessages.at(-1) ?? null;
856
+ }
857
+
826
858
  this.requestUpdate();
827
859
  }
828
860
 
829
861
  #openWalkthrough(message: ModelChatMessage): void {
830
- this.#walkthrough.activeMessage = message;
862
+ if (!this.#walkthrough.inlineExpandedMessages.includes(message)) {
863
+ this.#walkthrough.inlineExpandedMessages.push(message);
864
+ }
865
+ this.#walkthrough.activeSidebarMessage = message;
831
866
  this.#walkthrough.isExpanded = true;
832
867
  this.requestUpdate();
833
868
  }
834
869
 
835
- #toggleWalkthrough(isOpen: boolean): void {
836
- this.#walkthrough.isExpanded = isOpen;
870
+ /**
871
+ * Toggles the expanded state of a walkthrough.
872
+ *
873
+ * In Wide (sidebar) mode:
874
+ * - Opening a message's walkthrough shows the sidebar for that message.
875
+ * - Closing the sidebar hides the walkthrough for the currently active message.
876
+ *
877
+ * In Narrow (inline) mode:
878
+ * - Any number of walkthroughs can be open at once.
879
+ * - Opening/closing a message's walkthrough only affects that message's inline display.
880
+ */
881
+ #toggleWalkthrough(isOpen: boolean, message: ModelChatMessage): void {
882
+ if (isOpen) { // If we are opening a walkthrough, ensure it's in our list of expanded messages.
883
+ this.#openWalkthrough(message);
884
+ return;
885
+ }
886
+
887
+ // If we are closing a walkthrough, remove it from the list of expanded messages.
888
+ this.#walkthrough.inlineExpandedMessages = this.#walkthrough.inlineExpandedMessages.filter(m => m !== message);
889
+
890
+ if (this.#walkthrough.isInlined) {
891
+ // In Narrow mode, the global expanded state tracks if at least one walkthrough is open.
892
+ this.#walkthrough.isExpanded = this.#walkthrough.inlineExpandedMessages.length > 0;
893
+ // If the message we just closed was the active one, we pick a new active message
894
+ // from the remaining open ones (if any). This ensures that if the user
895
+ // re-opens the sidebar later, it shows the most recently opened walkthrough.
896
+ if (this.#walkthrough.activeSidebarMessage === message) {
897
+ this.#walkthrough.activeSidebarMessage = this.#walkthrough.inlineExpandedMessages.at(-1) ?? null;
898
+ }
899
+ } else {
900
+ // In Wide mode, closing the sidebar means we are no longer expanded globally.
901
+ this.#walkthrough.isExpanded = false;
902
+ this.#walkthrough.activeSidebarMessage = null;
903
+ }
904
+
837
905
  this.requestUpdate();
838
906
  }
839
907
 
@@ -1565,7 +1633,8 @@ export class AiAssistancePanel extends UI.Panel.Panel {
1565
1633
 
1566
1634
  #resetWalkthrough(): void {
1567
1635
  this.#walkthrough.isExpanded = false;
1568
- this.#walkthrough.activeMessage = null;
1636
+ this.#walkthrough.activeSidebarMessage = null;
1637
+ this.#walkthrough.inlineExpandedMessages = [];
1569
1638
  }
1570
1639
 
1571
1640
  #onDeleteClicked(): void {