chrome-devtools-frontend 1.0.1602543 → 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 (75) hide show
  1. package/.agents/skills/version-control/SKILL.md +17 -2
  2. package/front_end/core/host/AidaClient.ts +39 -462
  3. package/front_end/core/host/AidaClientTypes.ts +470 -0
  4. package/front_end/core/host/AidaGcaTranslation.ts +225 -122
  5. package/front_end/core/host/GcaTypes.ts +107 -155
  6. package/front_end/core/protocol_client/DevToolsCDPConnection.ts +1 -1
  7. package/front_end/core/sdk/DOMModel.ts +24 -22
  8. package/front_end/core/sdk/EmulationModel.ts +21 -23
  9. package/front_end/core/sdk/HeapProfilerModel.ts +4 -7
  10. package/front_end/core/sdk/NetworkManager.ts +35 -28
  11. package/front_end/core/sdk/OverlayModel.ts +23 -23
  12. package/front_end/core/sdk/OverlayPersistentHighlighter.ts +13 -10
  13. package/front_end/core/sdk/PreloadingModel.ts +3 -4
  14. package/front_end/core/sdk/RuntimeModel.ts +5 -5
  15. package/front_end/core/sdk/ServiceWorkerManager.ts +6 -5
  16. package/front_end/core/sdk/SourceMapScopesInfo.ts +93 -63
  17. package/front_end/core/sdk/TargetManager.ts +5 -0
  18. package/front_end/core/sdk/WebMCPModel.ts +6 -0
  19. package/front_end/entrypoints/formatter_worker/FormatterWorker.ts +1 -3
  20. package/front_end/entrypoints/heap_snapshot_worker/AllocationProfile.ts +22 -48
  21. package/front_end/entrypoints/heap_snapshot_worker/HeapSnapshot.ts +1 -1
  22. package/front_end/generated/InspectorBackendCommands.ts +7 -0
  23. package/front_end/generated/SupportedCSSProperties.js +68 -2
  24. package/front_end/generated/protocol-mapping.d.ts +15 -0
  25. package/front_end/generated/protocol-proxy-api.d.ts +24 -0
  26. package/front_end/generated/protocol.ts +79 -0
  27. package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +16 -5
  28. package/front_end/models/ai_assistance/data_formatters/LighthouseFormatter.snapshot.txt +60 -9
  29. package/front_end/models/ai_assistance/data_formatters/LighthouseFormatter.ts +71 -15
  30. package/front_end/models/bindings/TempFile.ts +1 -4
  31. package/front_end/models/extensions/ExtensionAPI.ts +1 -1
  32. package/front_end/models/heap_snapshot_model/HeapSnapshotModel.ts +34 -47
  33. package/front_end/models/javascript_metadata/NativeFunctions.js +12 -8
  34. package/front_end/models/lighthouse/LighthouseReporterTypes.ts +10 -8
  35. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +91 -21
  36. package/front_end/panels/ai_assistance/components/ChatMessage.ts +90 -32
  37. package/front_end/panels/ai_assistance/components/ChatView.ts +3 -2
  38. package/front_end/panels/ai_assistance/components/WalkthroughView.ts +65 -17
  39. package/front_end/panels/ai_assistance/components/chatMessage.css +33 -7
  40. package/front_end/panels/ai_assistance/components/walkthroughView.css +35 -5
  41. package/front_end/panels/common/ThrottlingUtils.ts +46 -0
  42. package/front_end/panels/common/common.ts +1 -0
  43. package/front_end/panels/elements/ComputedStyleWidget.ts +11 -1
  44. package/front_end/panels/elements/ElementsTreeElement.ts +91 -13
  45. package/front_end/panels/elements/StandaloneStylesContainer.ts +0 -3
  46. package/front_end/panels/elements/StylePropertiesSection.ts +0 -49
  47. package/front_end/panels/elements/StylesContainer.ts +0 -1
  48. package/front_end/panels/elements/StylesSidebarPane.ts +32 -7
  49. package/front_end/panels/elements/elementsTreeOutline.css +18 -0
  50. package/front_end/panels/mobile_throttling/NetworkThrottlingSelector.ts +14 -0
  51. package/front_end/panels/profiler/HeapSnapshotDataGrids.ts +15 -19
  52. package/front_end/panels/profiler/HeapSnapshotGridNodes.ts +4 -2
  53. package/front_end/panels/profiler/HeapSnapshotProxy.ts +12 -16
  54. package/front_end/panels/profiler/HeapSnapshotView.ts +3 -10
  55. package/front_end/panels/profiler/ProfileView.ts +0 -9
  56. package/front_end/panels/profiler/ProfilesPanel.ts +5 -7
  57. package/front_end/panels/sensors/SensorsView.ts +0 -2
  58. package/front_end/panels/settings/keybindsSettingsTab.css +25 -31
  59. package/front_end/panels/timeline/ThirdPartyTreeView.ts +8 -0
  60. package/front_end/panels/timeline/TimelinePanel.ts +15 -5
  61. package/front_end/panels/timeline/components/FieldSettingsDialog.ts +5 -9
  62. package/front_end/panels/timeline/components/LiveMetricsView.ts +36 -65
  63. package/front_end/panels/timeline/components/OriginMap.ts +176 -159
  64. package/front_end/panels/timeline/components/liveMetricsView.css +4 -0
  65. package/front_end/panels/timeline/components/originMap.css +4 -51
  66. package/front_end/panels/timeline/thirdPartyTreeView.css +6 -0
  67. package/front_end/panels/timeline/timeline-meta.ts +11 -0
  68. package/front_end/panels/timeline/utils/Helpers.ts +5 -25
  69. package/front_end/third_party/acorn/README.chromium +1 -0
  70. package/front_end/third_party/chromium/README.chromium +1 -1
  71. package/front_end/ui/components/{list → lists}/List.ts +1 -1
  72. package/front_end/ui/components/{list → lists}/list.css +0 -1
  73. package/front_end/ui/legacy/components/data_grid/DataGridElement.ts +3 -3
  74. package/package.json +1 -1
  75. /package/front_end/ui/components/{list → lists}/lists.ts +0 -0
@@ -4,6 +4,8 @@
4
4
 
5
5
  import type * as LHModel from '../../lighthouse/lighthouse.js';
6
6
 
7
+ import {bytes, millis} from './UnitFormatters.js';
8
+
7
9
  /**
8
10
  * A formatter that takes a raw Lighthouse report JSON and creates a markdown
9
11
  * summary for an AI Agent.
@@ -120,37 +122,74 @@ export class LighthouseFormatter {
120
122
  }
121
123
  }
122
124
 
123
- #formatTable(headings: LHModel.ReporterTypes.TableHeadingJSON[], items: Array<Record<string, unknown>>): string {
125
+ #formatTable(headings: LHModel.ReporterTypes.TableHeadingJSON[], items: LHModel.ReporterTypes.TableItem[]): string {
124
126
  const lines: string[] = [];
125
- lines.push(`| ${headings.map(h => h.label).join(' | ')} |`);
127
+
126
128
  for (const item of items) {
127
- const row = headings.map(h => this.#formatTableValue(item[h.key] as LHModel.ReporterTypes.TableItemValue));
128
- lines.push(`| ${row.join(' | ')} |`);
129
+ const itemLines: string[] = [];
130
+ for (const heading of headings) {
131
+ const value = item[heading.key] as LHModel.ReporterTypes.TableItemValue;
132
+ const formattedValues = this.#formatTableValues(value, heading.valueType);
133
+ for (const {labelSuffix, value: v} of formattedValues) {
134
+ const baseLabel = heading.label || heading.key;
135
+ const label = labelSuffix ? `${baseLabel} ${labelSuffix}` : baseLabel;
136
+ itemLines.push(` * **${label}**: ${v}`);
137
+ }
138
+ const subItems = item.subItems;
139
+ // subItems can technically be a string (TableItemValue), but we
140
+ // only care about it here if it's a SubItemsJSON (type:
141
+ // 'subitems'), which represents a nested table of values.
142
+ if (subItems && typeof subItems === 'object' && 'type' in subItems && subItems.type === 'subitems' &&
143
+ heading.subItemsHeading) {
144
+ for (const subItem of subItems.items) {
145
+ const subValue = subItem[heading.subItemsHeading.key] as LHModel.ReporterTypes.TableItemValue;
146
+ // Skip sub-item values that are identical to the main item's value
147
+ // for the same heading to avoid redundant output (e.g. if both
148
+ // show the same "Est Savings" value).
149
+ if (subValue === value) {
150
+ continue;
151
+ }
152
+ const formattedSubValues = this.#formatTableValues(subValue, heading.subItemsHeading.valueType);
153
+ for (const {value: v} of formattedSubValues) {
154
+ itemLines.push(` * ${v}`);
155
+ }
156
+ }
157
+ }
158
+ }
159
+ if (itemLines.length > 0) {
160
+ lines.push(`- Item:`);
161
+ lines.push(...itemLines);
162
+ }
129
163
  }
130
164
  return lines.join('\n');
131
165
  }
132
166
 
133
- #formatTableValue(value: LHModel.ReporterTypes.TableItemValue|undefined): string {
167
+ #formatTableValues(value: LHModel.ReporterTypes.TableItemValue|undefined, valueType?: string): Array<{
168
+ value: string,
169
+ labelSuffix?: string,
170
+ }> {
134
171
  if (value === undefined || value === null) {
135
- return '';
172
+ return [];
136
173
  }
137
174
  if (typeof value === 'string' || typeof value === 'number') {
138
- return String(value);
175
+ return [{value: this.#formatValue(value, valueType)}];
139
176
  }
140
177
  if (typeof value === 'object' && 'type' in value) {
141
178
  switch (value.type) {
142
179
  case 'node': {
143
- let label = value.nodeLabel || value.selector || value.snippet || '(node)';
144
- if (value.selector) {
145
- label += ` (selector: ${value.selector})`;
180
+ const results = [];
181
+ const label = value.nodeLabel || value.selector || value.snippet || '(node)';
182
+ results.push({value: label});
183
+ if (value.selector && value.selector !== label) {
184
+ results.push({labelSuffix: 'selector', value: value.selector});
146
185
  }
147
186
  if (value.path) {
148
- label += ` (path: ${value.path})`;
187
+ results.push({labelSuffix: 'path', value: value.path});
149
188
  }
150
189
  if (value.explanation) {
151
- label += ` (explanation: ${value.explanation.replace(/\n/g, ' ')})`;
190
+ results.push({labelSuffix: 'explanation', value: value.explanation.replace(/\n/g, ' ')});
152
191
  }
153
- return label;
192
+ return results;
154
193
  }
155
194
  case 'source-location': {
156
195
  const parts = [];
@@ -163,10 +202,27 @@ export class LighthouseFormatter {
163
202
  if (value.column) {
164
203
  parts.push(String(value.column));
165
204
  }
166
- return parts.join(':');
205
+ return [{value: parts.join(':')}];
167
206
  }
168
207
  }
169
208
  }
170
- return '';
209
+ return [];
210
+ }
211
+
212
+ #formatValue(value: string|number, valueType?: string): string {
213
+ if (typeof value === 'string') {
214
+ return value;
215
+ }
216
+ switch (valueType) {
217
+ case 'bytes': {
218
+ return bytes(value);
219
+ }
220
+ case 'timespanMs':
221
+ case 'ms': {
222
+ return millis(value);
223
+ }
224
+ default:
225
+ return String(value);
226
+ }
171
227
  }
172
228
  }
@@ -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;
@@ -1003,7 +1003,7 @@ export const NativeFunctions = [
1003
1003
  {
1004
1004
  name: "addEventListener",
1005
1005
  signatures: [["type","listener","?options"]],
1006
- receivers: ["AbortSignal","SharedWorker","Worker","ServiceWorker","Animation","AudioBufferSourceNode","AudioContext","AudioDecoder","AudioEncoder","AudioScheduledSourceNode","AudioWorkletNode","BaseAudioContext","BroadcastChannel","CSSAnimation","CSSTransition","CanvasCaptureMediaStreamTrack","ConstantSourceNode","CookieStore","Document","Element","EventSource","FileReader","FontFaceSet","Window","HTMLElement","MathMLElement","SVGElement","HTMLAnchorElement","HTMLAreaElement","HTMLAudioElement","HTMLBRElement","HTMLBaseElement","HTMLBodyElement","HTMLButtonElement","HTMLCanvasElement","HTMLDListElement","HTMLDataElement","HTMLDataListElement","HTMLDetailsElement","HTMLDialogElement","HTMLDirectoryElement","HTMLDivElement","HTMLDocument","HTMLEmbedElement","HTMLFieldSetElement","HTMLFontElement","HTMLFormElement","HTMLFrameElement","HTMLFrameSetElement","HTMLHRElement","HTMLHeadElement","HTMLHeadingElement","HTMLHtmlElement","HTMLIFrameElement","HTMLImageElement","HTMLInputElement","HTMLLIElement","HTMLLabelElement","HTMLLegendElement","HTMLLinkElement","HTMLMapElement","HTMLMarqueeElement","HTMLMediaElement","HTMLMenuElement","HTMLMetaElement","HTMLMeterElement","HTMLModElement","HTMLOListElement","HTMLObjectElement","HTMLOptGroupElement","HTMLOptionElement","HTMLOutputElement","HTMLParagraphElement","HTMLParamElement","HTMLPictureElement","HTMLPreElement","HTMLProgressElement","HTMLQuoteElement","HTMLScriptElement","HTMLSelectElement","HTMLSlotElement","HTMLSourceElement","HTMLSpanElement","HTMLStyleElement","HTMLTableCaptionElement","HTMLTableCellElement","HTMLTableColElement","HTMLTableDataCellElement","HTMLTableElement","HTMLTableHeaderCellElement","HTMLTableRowElement","HTMLTableSectionElement","HTMLTemplateElement","HTMLTextAreaElement","HTMLTimeElement","HTMLTitleElement","HTMLTrackElement","HTMLUListElement","HTMLUnknownElement","HTMLVideoElement","IDBDatabase","IDBOpenDBRequest","IDBRequest","IDBTransaction","MIDIAccess","MIDIInput","MIDIOutput","MIDIPort","MediaDevices","MediaKeySession","MediaQueryList","MediaRecorder","MediaSource","MediaStream","MediaStreamTrack","MessageEventTarget","MessagePort","NavigationHistoryEntry","Notification","OfflineAudioContext","OffscreenCanvas","OscillatorNode","PaymentRequest","PaymentResponse","Performance","PermissionStatus","PictureInPictureWindow","RTCDTMFSender","RTCDataChannel","RTCDtlsTransport","RTCIceTransport","RTCPeerConnection","RTCSctpTransport","RemotePlayback","SVGAElement","SVGAnimateElement","SVGAnimateMotionElement","SVGAnimateTransformElement","SVGAnimationElement","SVGCircleElement","SVGClipPathElement","SVGComponentTransferFunctionElement","SVGDefsElement","SVGDescElement","SVGEllipseElement","SVGFEBlendElement","SVGFEColorMatrixElement","SVGFEComponentTransferElement","SVGFECompositeElement","SVGFEConvolveMatrixElement","SVGFEDiffuseLightingElement","SVGFEDisplacementMapElement","SVGFEDistantLightElement","SVGFEDropShadowElement","SVGFEFloodElement","SVGFEFuncAElement","SVGFEFuncBElement","SVGFEFuncGElement","SVGFEFuncRElement","SVGFEGaussianBlurElement","SVGFEImageElement","SVGFEMergeElement","SVGFEMergeNodeElement","SVGFEMorphologyElement","SVGFEOffsetElement","SVGFEPointLightElement","SVGFESpecularLightingElement","SVGFESpotLightElement","SVGFETileElement","SVGFETurbulenceElement","SVGFilterElement","SVGForeignObjectElement","SVGGElement","SVGGeometryElement","SVGGradientElement","SVGGraphicsElement","SVGImageElement","SVGLineElement","SVGLinearGradientElement","SVGMPathElement","SVGMarkerElement","SVGMaskElement","SVGMetadataElement","SVGPathElement","SVGPatternElement","SVGPolygonElement","SVGPolylineElement","SVGRadialGradientElement","SVGRectElement","SVGSVGElement","SVGScriptElement","SVGSetElement","SVGStopElement","SVGStyleElement","SVGSwitchElement","SVGSymbolElement","SVGTSpanElement","SVGTextContentElement","SVGTextElement","SVGTextPathElement","SVGTextPositioningElement","SVGTitleElement","SVGUseElement","SVGViewElement","ScreenOrientation","ScriptProcessorNode","ServiceWorkerContainer","ServiceWorkerRegistration","ShadowRoot","SourceBuffer","SourceBufferList","SpeechSynthesis","SpeechSynthesisUtterance","TextTrack","TextTrackCue","TextTrackList","VTTCue","VideoDecoder","VideoEncoder","VisualViewport","WakeLockSentinel","WebSocket","XMLDocument","XMLHttpRequest","XMLHttpRequestEventTarget","XMLHttpRequestUpload","DedicatedWorkerGlobalScope","ServiceWorkerGlobalScope","SharedWorkerGlobalScope","WorkerGlobalScope","Highlight"]
1006
+ receivers: ["AbortSignal","SharedWorker","Worker","ServiceWorker","Animation","AudioBufferSourceNode","AudioContext","AudioDecoder","AudioEncoder","AudioScheduledSourceNode","AudioWorkletNode","BaseAudioContext","BroadcastChannel","CSSAnimation","CSSTransition","CanvasCaptureMediaStreamTrack","ConstantSourceNode","CookieStore","Document","Element","EventSource","FileReader","FontFaceSet","Window","HTMLElement","MathMLElement","SVGElement","HTMLAnchorElement","HTMLAreaElement","HTMLAudioElement","HTMLBRElement","HTMLBaseElement","HTMLBodyElement","HTMLButtonElement","HTMLCanvasElement","HTMLDListElement","HTMLDataElement","HTMLDataListElement","HTMLDetailsElement","HTMLDialogElement","HTMLDirectoryElement","HTMLDivElement","HTMLDocument","HTMLEmbedElement","HTMLFieldSetElement","HTMLFontElement","HTMLFormElement","HTMLFrameElement","HTMLFrameSetElement","HTMLHRElement","HTMLHeadElement","HTMLHeadingElement","HTMLHtmlElement","HTMLIFrameElement","HTMLImageElement","HTMLInputElement","HTMLLIElement","HTMLLabelElement","HTMLLegendElement","HTMLLinkElement","HTMLMapElement","HTMLMarqueeElement","HTMLMediaElement","HTMLMenuElement","HTMLMetaElement","HTMLMeterElement","HTMLModElement","HTMLOListElement","HTMLObjectElement","HTMLOptGroupElement","HTMLOptionElement","HTMLOutputElement","HTMLParagraphElement","HTMLParamElement","HTMLPictureElement","HTMLPreElement","HTMLProgressElement","HTMLQuoteElement","HTMLScriptElement","HTMLSelectElement","HTMLSlotElement","HTMLSourceElement","HTMLSpanElement","HTMLStyleElement","HTMLTableCaptionElement","HTMLTableCellElement","HTMLTableColElement","HTMLTableDataCellElement","HTMLTableElement","HTMLTableHeaderCellElement","HTMLTableRowElement","HTMLTableSectionElement","HTMLTemplateElement","HTMLTextAreaElement","HTMLTimeElement","HTMLTitleElement","HTMLTrackElement","HTMLUListElement","HTMLUnknownElement","HTMLVideoElement","IDBDatabase","IDBOpenDBRequest","IDBRequest","IDBTransaction","MIDIAccess","MIDIInput","MIDIOutput","MIDIPort","MediaDevices","MediaKeySession","MediaQueryList","MediaRecorder","MediaSource","MediaStream","MediaStreamTrack","MessageEventTarget","MessagePort","NavigationHistoryEntry","Notification","OfflineAudioContext","OffscreenCanvas","OscillatorNode","PaymentRequest","PaymentResponse","Performance","PermissionStatus","PictureInPictureWindow","RTCDTMFSender","RTCDataChannel","RTCDtlsTransport","RTCIceTransport","RTCPeerConnection","RTCSctpTransport","RemotePlayback","SVGAElement","SVGAnimateElement","SVGAnimateMotionElement","SVGAnimateTransformElement","SVGAnimationElement","SVGCircleElement","SVGClipPathElement","SVGComponentTransferFunctionElement","SVGDefsElement","SVGDescElement","SVGEllipseElement","SVGFEBlendElement","SVGFEColorMatrixElement","SVGFEComponentTransferElement","SVGFECompositeElement","SVGFEConvolveMatrixElement","SVGFEDiffuseLightingElement","SVGFEDisplacementMapElement","SVGFEDistantLightElement","SVGFEDropShadowElement","SVGFEFloodElement","SVGFEFuncAElement","SVGFEFuncBElement","SVGFEFuncGElement","SVGFEFuncRElement","SVGFEGaussianBlurElement","SVGFEImageElement","SVGFEMergeElement","SVGFEMergeNodeElement","SVGFEMorphologyElement","SVGFEOffsetElement","SVGFEPointLightElement","SVGFESpecularLightingElement","SVGFESpotLightElement","SVGFETileElement","SVGFETurbulenceElement","SVGFilterElement","SVGForeignObjectElement","SVGGElement","SVGGeometryElement","SVGGradientElement","SVGGraphicsElement","SVGImageElement","SVGLineElement","SVGLinearGradientElement","SVGMPathElement","SVGMarkerElement","SVGMaskElement","SVGMetadataElement","SVGPathElement","SVGPatternElement","SVGPolygonElement","SVGPolylineElement","SVGRadialGradientElement","SVGRectElement","SVGSVGElement","SVGScriptElement","SVGSetElement","SVGStopElement","SVGStyleElement","SVGSwitchElement","SVGSymbolElement","SVGTSpanElement","SVGTextContentElement","SVGTextElement","SVGTextPathElement","SVGTextPositioningElement","SVGTitleElement","SVGUseElement","SVGViewElement","ScreenOrientation","ScriptProcessorNode","ServiceWorkerContainer","ServiceWorkerRegistration","ShadowRoot","SourceBuffer","SourceBufferList","SpeechSynthesis","SpeechSynthesisUtterance","TextTrack","TextTrackCue","TextTrackList","VTTCue","VideoDecoder","VideoEncoder","VisualViewport","WakeLockSentinel","WebSocket","XMLDocument","XMLHttpRequest","XMLHttpRequestEventTarget","XMLHttpRequestUpload","DedicatedWorkerGlobalScope","ServiceWorkerGlobalScope","SharedWorkerGlobalScope","WorkerGlobalScope"]
1007
1007
  },
1008
1008
  {
1009
1009
  name: "addEventListener",
@@ -1013,7 +1013,7 @@ export const NativeFunctions = [
1013
1013
  {
1014
1014
  name: "removeEventListener",
1015
1015
  signatures: [["type","listener","?options"]],
1016
- receivers: ["AbortSignal","SharedWorker","Worker","ServiceWorker","Animation","AudioBufferSourceNode","AudioContext","AudioDecoder","AudioEncoder","AudioScheduledSourceNode","AudioWorkletNode","BaseAudioContext","BroadcastChannel","CSSAnimation","CSSTransition","CanvasCaptureMediaStreamTrack","ConstantSourceNode","CookieStore","Document","Element","EventSource","FileReader","FontFaceSet","Window","HTMLElement","MathMLElement","SVGElement","HTMLAnchorElement","HTMLAreaElement","HTMLAudioElement","HTMLBRElement","HTMLBaseElement","HTMLBodyElement","HTMLButtonElement","HTMLCanvasElement","HTMLDListElement","HTMLDataElement","HTMLDataListElement","HTMLDetailsElement","HTMLDialogElement","HTMLDirectoryElement","HTMLDivElement","HTMLDocument","HTMLEmbedElement","HTMLFieldSetElement","HTMLFontElement","HTMLFormElement","HTMLFrameElement","HTMLFrameSetElement","HTMLHRElement","HTMLHeadElement","HTMLHeadingElement","HTMLHtmlElement","HTMLIFrameElement","HTMLImageElement","HTMLInputElement","HTMLLIElement","HTMLLabelElement","HTMLLegendElement","HTMLLinkElement","HTMLMapElement","HTMLMarqueeElement","HTMLMediaElement","HTMLMenuElement","HTMLMetaElement","HTMLMeterElement","HTMLModElement","HTMLOListElement","HTMLObjectElement","HTMLOptGroupElement","HTMLOptionElement","HTMLOutputElement","HTMLParagraphElement","HTMLParamElement","HTMLPictureElement","HTMLPreElement","HTMLProgressElement","HTMLQuoteElement","HTMLScriptElement","HTMLSelectElement","HTMLSlotElement","HTMLSourceElement","HTMLSpanElement","HTMLStyleElement","HTMLTableCaptionElement","HTMLTableCellElement","HTMLTableColElement","HTMLTableDataCellElement","HTMLTableElement","HTMLTableHeaderCellElement","HTMLTableRowElement","HTMLTableSectionElement","HTMLTemplateElement","HTMLTextAreaElement","HTMLTimeElement","HTMLTitleElement","HTMLTrackElement","HTMLUListElement","HTMLUnknownElement","HTMLVideoElement","IDBDatabase","IDBOpenDBRequest","IDBRequest","IDBTransaction","MIDIAccess","MIDIInput","MIDIOutput","MIDIPort","MediaDevices","MediaKeySession","MediaQueryList","MediaRecorder","MediaSource","MediaStream","MediaStreamTrack","MessageEventTarget","MessagePort","NavigationHistoryEntry","Notification","OfflineAudioContext","OffscreenCanvas","OscillatorNode","PaymentRequest","PaymentResponse","Performance","PermissionStatus","PictureInPictureWindow","RTCDTMFSender","RTCDataChannel","RTCDtlsTransport","RTCIceTransport","RTCPeerConnection","RTCSctpTransport","RemotePlayback","SVGAElement","SVGAnimateElement","SVGAnimateMotionElement","SVGAnimateTransformElement","SVGAnimationElement","SVGCircleElement","SVGClipPathElement","SVGComponentTransferFunctionElement","SVGDefsElement","SVGDescElement","SVGEllipseElement","SVGFEBlendElement","SVGFEColorMatrixElement","SVGFEComponentTransferElement","SVGFECompositeElement","SVGFEConvolveMatrixElement","SVGFEDiffuseLightingElement","SVGFEDisplacementMapElement","SVGFEDistantLightElement","SVGFEDropShadowElement","SVGFEFloodElement","SVGFEFuncAElement","SVGFEFuncBElement","SVGFEFuncGElement","SVGFEFuncRElement","SVGFEGaussianBlurElement","SVGFEImageElement","SVGFEMergeElement","SVGFEMergeNodeElement","SVGFEMorphologyElement","SVGFEOffsetElement","SVGFEPointLightElement","SVGFESpecularLightingElement","SVGFESpotLightElement","SVGFETileElement","SVGFETurbulenceElement","SVGFilterElement","SVGForeignObjectElement","SVGGElement","SVGGeometryElement","SVGGradientElement","SVGGraphicsElement","SVGImageElement","SVGLineElement","SVGLinearGradientElement","SVGMPathElement","SVGMarkerElement","SVGMaskElement","SVGMetadataElement","SVGPathElement","SVGPatternElement","SVGPolygonElement","SVGPolylineElement","SVGRadialGradientElement","SVGRectElement","SVGSVGElement","SVGScriptElement","SVGSetElement","SVGStopElement","SVGStyleElement","SVGSwitchElement","SVGSymbolElement","SVGTSpanElement","SVGTextContentElement","SVGTextElement","SVGTextPathElement","SVGTextPositioningElement","SVGTitleElement","SVGUseElement","SVGViewElement","ScreenOrientation","ScriptProcessorNode","ServiceWorkerContainer","ServiceWorkerRegistration","ShadowRoot","SourceBuffer","SourceBufferList","SpeechSynthesis","SpeechSynthesisUtterance","TextTrack","TextTrackCue","TextTrackList","VTTCue","VideoDecoder","VideoEncoder","VisualViewport","WakeLockSentinel","WebSocket","XMLDocument","XMLHttpRequest","XMLHttpRequestEventTarget","XMLHttpRequestUpload","DedicatedWorkerGlobalScope","ServiceWorkerGlobalScope","SharedWorkerGlobalScope","WorkerGlobalScope","Highlight"]
1016
+ receivers: ["AbortSignal","SharedWorker","Worker","ServiceWorker","Animation","AudioBufferSourceNode","AudioContext","AudioDecoder","AudioEncoder","AudioScheduledSourceNode","AudioWorkletNode","BaseAudioContext","BroadcastChannel","CSSAnimation","CSSTransition","CanvasCaptureMediaStreamTrack","ConstantSourceNode","CookieStore","Document","Element","EventSource","FileReader","FontFaceSet","Window","HTMLElement","MathMLElement","SVGElement","HTMLAnchorElement","HTMLAreaElement","HTMLAudioElement","HTMLBRElement","HTMLBaseElement","HTMLBodyElement","HTMLButtonElement","HTMLCanvasElement","HTMLDListElement","HTMLDataElement","HTMLDataListElement","HTMLDetailsElement","HTMLDialogElement","HTMLDirectoryElement","HTMLDivElement","HTMLDocument","HTMLEmbedElement","HTMLFieldSetElement","HTMLFontElement","HTMLFormElement","HTMLFrameElement","HTMLFrameSetElement","HTMLHRElement","HTMLHeadElement","HTMLHeadingElement","HTMLHtmlElement","HTMLIFrameElement","HTMLImageElement","HTMLInputElement","HTMLLIElement","HTMLLabelElement","HTMLLegendElement","HTMLLinkElement","HTMLMapElement","HTMLMarqueeElement","HTMLMediaElement","HTMLMenuElement","HTMLMetaElement","HTMLMeterElement","HTMLModElement","HTMLOListElement","HTMLObjectElement","HTMLOptGroupElement","HTMLOptionElement","HTMLOutputElement","HTMLParagraphElement","HTMLParamElement","HTMLPictureElement","HTMLPreElement","HTMLProgressElement","HTMLQuoteElement","HTMLScriptElement","HTMLSelectElement","HTMLSlotElement","HTMLSourceElement","HTMLSpanElement","HTMLStyleElement","HTMLTableCaptionElement","HTMLTableCellElement","HTMLTableColElement","HTMLTableDataCellElement","HTMLTableElement","HTMLTableHeaderCellElement","HTMLTableRowElement","HTMLTableSectionElement","HTMLTemplateElement","HTMLTextAreaElement","HTMLTimeElement","HTMLTitleElement","HTMLTrackElement","HTMLUListElement","HTMLUnknownElement","HTMLVideoElement","IDBDatabase","IDBOpenDBRequest","IDBRequest","IDBTransaction","MIDIAccess","MIDIInput","MIDIOutput","MIDIPort","MediaDevices","MediaKeySession","MediaQueryList","MediaRecorder","MediaSource","MediaStream","MediaStreamTrack","MessageEventTarget","MessagePort","NavigationHistoryEntry","Notification","OfflineAudioContext","OffscreenCanvas","OscillatorNode","PaymentRequest","PaymentResponse","Performance","PermissionStatus","PictureInPictureWindow","RTCDTMFSender","RTCDataChannel","RTCDtlsTransport","RTCIceTransport","RTCPeerConnection","RTCSctpTransport","RemotePlayback","SVGAElement","SVGAnimateElement","SVGAnimateMotionElement","SVGAnimateTransformElement","SVGAnimationElement","SVGCircleElement","SVGClipPathElement","SVGComponentTransferFunctionElement","SVGDefsElement","SVGDescElement","SVGEllipseElement","SVGFEBlendElement","SVGFEColorMatrixElement","SVGFEComponentTransferElement","SVGFECompositeElement","SVGFEConvolveMatrixElement","SVGFEDiffuseLightingElement","SVGFEDisplacementMapElement","SVGFEDistantLightElement","SVGFEDropShadowElement","SVGFEFloodElement","SVGFEFuncAElement","SVGFEFuncBElement","SVGFEFuncGElement","SVGFEFuncRElement","SVGFEGaussianBlurElement","SVGFEImageElement","SVGFEMergeElement","SVGFEMergeNodeElement","SVGFEMorphologyElement","SVGFEOffsetElement","SVGFEPointLightElement","SVGFESpecularLightingElement","SVGFESpotLightElement","SVGFETileElement","SVGFETurbulenceElement","SVGFilterElement","SVGForeignObjectElement","SVGGElement","SVGGeometryElement","SVGGradientElement","SVGGraphicsElement","SVGImageElement","SVGLineElement","SVGLinearGradientElement","SVGMPathElement","SVGMarkerElement","SVGMaskElement","SVGMetadataElement","SVGPathElement","SVGPatternElement","SVGPolygonElement","SVGPolylineElement","SVGRadialGradientElement","SVGRectElement","SVGSVGElement","SVGScriptElement","SVGSetElement","SVGStopElement","SVGStyleElement","SVGSwitchElement","SVGSymbolElement","SVGTSpanElement","SVGTextContentElement","SVGTextElement","SVGTextPathElement","SVGTextPositioningElement","SVGTitleElement","SVGUseElement","SVGViewElement","ScreenOrientation","ScriptProcessorNode","ServiceWorkerContainer","ServiceWorkerRegistration","ShadowRoot","SourceBuffer","SourceBufferList","SpeechSynthesis","SpeechSynthesisUtterance","TextTrack","TextTrackCue","TextTrackList","VTTCue","VideoDecoder","VideoEncoder","VisualViewport","WakeLockSentinel","WebSocket","XMLDocument","XMLHttpRequest","XMLHttpRequestEventTarget","XMLHttpRequestUpload","DedicatedWorkerGlobalScope","ServiceWorkerGlobalScope","SharedWorkerGlobalScope","WorkerGlobalScope"]
1017
1017
  },
1018
1018
  {
1019
1019
  name: "removeEventListener",
@@ -2895,6 +2895,10 @@ export const NativeFunctions = [
2895
2895
  signatures: [["?detailsPromise"]],
2896
2896
  receivers: ["PaymentRequest"]
2897
2897
  },
2898
+ {
2899
+ name: "attachInternals",
2900
+ signatures: [["?options"]]
2901
+ },
2898
2902
  {
2899
2903
  name: "showPopover",
2900
2904
  signatures: [["?options"]]
@@ -6602,16 +6606,12 @@ export const NativeFunctions = [
6602
6606
  name: "replaceWithHTMLUnsafe",
6603
6607
  signatures: [["html","?options"]]
6604
6608
  },
6605
- {
6606
- name: "streamBeforeHTMLUnsafe",
6607
- signatures: [["?options"]]
6608
- },
6609
6609
  {
6610
6610
  name: "streamBeforeHTML",
6611
6611
  signatures: [["?options"]]
6612
6612
  },
6613
6613
  {
6614
- name: "streamAfterHTMLUnsafe",
6614
+ name: "streamBeforeHTMLUnsafe",
6615
6615
  signatures: [["?options"]]
6616
6616
  },
6617
6617
  {
@@ -6619,13 +6619,17 @@ export const NativeFunctions = [
6619
6619
  signatures: [["?options"]]
6620
6620
  },
6621
6621
  {
6622
- name: "streamReplaceWithHTMLUnsafe",
6622
+ name: "streamAfterHTMLUnsafe",
6623
6623
  signatures: [["?options"]]
6624
6624
  },
6625
6625
  {
6626
6626
  name: "streamReplaceWithHTML",
6627
6627
  signatures: [["?options"]]
6628
6628
  },
6629
+ {
6630
+ name: "streamReplaceWithHTMLUnsafe",
6631
+ signatures: [["?options"]]
6632
+ },
6629
6633
  {
6630
6634
  name: "Comment",
6631
6635
  signatures: [["?data"]]
@@ -82,10 +82,17 @@ export interface ReportJSON {
82
82
  export type DetailsJSON =
83
83
  |TableDetailsJSON|OpportunityDetailsJSON|FilmstripDetailsJSON|ScreenshotDetailsJSON|DebugDataDetailsJSON;
84
84
 
85
+ export interface SubItemsJSON {
86
+ type: 'subitems';
87
+ items: Array<Record<string, TableItemValue>>;
88
+ }
89
+
90
+ export type TableItem = Record<string, TableItemValue|SubItemsJSON|undefined>;
91
+
85
92
  export interface TableDetailsJSON {
86
93
  type: 'table';
87
94
  headings: TableHeadingJSON[];
88
- items: Array<Record<string, TableItemValue>>;
95
+ items: TableItem[];
89
96
  summary?: OpportunitySummary;
90
97
  }
91
98
 
@@ -96,7 +103,7 @@ export interface TableHeadingJSON {
96
103
  itemType?: string;
97
104
  subItemsHeading?: {
98
105
  key: string,
99
- valueType: string,
106
+ valueType?: string,
100
107
  };
101
108
  }
102
109
 
@@ -109,12 +116,7 @@ export type TableItemValue =|string|number|NodeDetailsJSON|SourceLocationDetails
109
116
  export interface OpportunityDetailsJSON {
110
117
  type: 'opportunity';
111
118
  headings: TableHeadingJSON[];
112
- items: Array<{
113
- url: string,
114
- wastedBytes?: number,
115
- wastedMs?: number,
116
- totalBytes?: number, [key: string]: unknown,
117
- }>;
119
+ items: TableItem[];
118
120
  overallSavingsMs: number;
119
121
  overallSavingsBytes?: number;
120
122
  }
@@ -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.#clearWalkthrough();
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
 
@@ -1563,9 +1631,10 @@ export class AiAssistancePanel extends UI.Panel.Panel {
1563
1631
  this.#updateConversationState();
1564
1632
  }
1565
1633
 
1566
- #clearWalkthrough(): void {
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 {
@@ -1574,7 +1643,7 @@ export class AiAssistancePanel extends UI.Panel.Panel {
1574
1643
  }
1575
1644
 
1576
1645
  // Ensure we clear the walkthrough so it doesn't hold onto a chat that is about to be deleted.
1577
- this.#clearWalkthrough();
1646
+ this.#resetWalkthrough();
1578
1647
 
1579
1648
  void AiAssistanceModel.AiHistoryStorage.AiHistoryStorage.instance().deleteHistoryEntry(this.#conversation.id);
1580
1649
  this.#updateConversationState();
@@ -1599,6 +1668,7 @@ export class AiAssistancePanel extends UI.Panel.Panel {
1599
1668
 
1600
1669
  #handleNewChatRequest(): void {
1601
1670
  this.#updateConversationState();
1671
+ this.#resetWalkthrough();
1602
1672
  UI.ARIAUtils.LiveAnnouncer.alert(i18nString(UIStrings.newChatCreated));
1603
1673
 
1604
1674
  if (Annotations.AnnotationRepository.annotationsEnabled()) {