@zenfg/inspector 0.1.0-beta.3 → 0.1.0

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 (172) hide show
  1. package/GUIDE.md +182 -0
  2. package/README.md +61 -89
  3. package/THEMING.md +124 -0
  4. package/dist/FrameGraphInspector.d.ts +23 -4
  5. package/dist/FrameGraphInspector.d.ts.map +1 -1
  6. package/dist/FrameGraphInspector.js +137 -29
  7. package/dist/FrameGraphInspector.js.map +1 -1
  8. package/dist/capture.d.ts +5 -0
  9. package/dist/capture.d.ts.map +1 -0
  10. package/dist/capture.js +2 -0
  11. package/dist/capture.js.map +1 -0
  12. package/dist/debugCaptureModel.d.ts +19 -0
  13. package/dist/debugCaptureModel.d.ts.map +1 -1
  14. package/dist/debugCaptureModel.js +36 -2
  15. package/dist/debugCaptureModel.js.map +1 -1
  16. package/dist/index.d.ts +2 -0
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js.map +1 -1
  19. package/dist/panelAliasAnalysis.d.ts +1 -0
  20. package/dist/panelAliasAnalysis.d.ts.map +1 -1
  21. package/dist/panelAliasAnalysis.js +23 -54
  22. package/dist/panelAliasAnalysis.js.map +1 -1
  23. package/dist/panelCytoscapeGraphRenderer.d.ts +15 -2
  24. package/dist/panelCytoscapeGraphRenderer.d.ts.map +1 -1
  25. package/dist/panelCytoscapeGraphRenderer.js +145 -23
  26. package/dist/panelCytoscapeGraphRenderer.js.map +1 -1
  27. package/dist/panelDetailLayout.d.ts +27 -0
  28. package/dist/panelDetailLayout.d.ts.map +1 -0
  29. package/dist/panelDetailLayout.js +174 -0
  30. package/dist/panelDetailLayout.js.map +1 -0
  31. package/dist/panelDetailStyles.d.ts +2 -0
  32. package/dist/panelDetailStyles.d.ts.map +1 -0
  33. package/dist/panelDetailStyles.js +57 -0
  34. package/dist/panelDetailStyles.js.map +1 -0
  35. package/dist/panelDiagnosticsView.d.ts +10 -1
  36. package/dist/panelDiagnosticsView.d.ts.map +1 -1
  37. package/dist/panelDiagnosticsView.js +166 -57
  38. package/dist/panelDiagnosticsView.js.map +1 -1
  39. package/dist/panelDomHelpers.d.ts.map +1 -1
  40. package/dist/panelDomHelpers.js +6 -5
  41. package/dist/panelDomHelpers.js.map +1 -1
  42. package/dist/panelGraphLayout.d.ts +3 -2
  43. package/dist/panelGraphLayout.d.ts.map +1 -1
  44. package/dist/panelGraphLayout.js +6 -5
  45. package/dist/panelGraphLayout.js.map +1 -1
  46. package/dist/panelGraphRenderer.d.ts +10 -0
  47. package/dist/panelGraphRenderer.d.ts.map +1 -1
  48. package/dist/panelGraphRenderer.js.map +1 -1
  49. package/dist/panelGraphScene.d.ts +1 -0
  50. package/dist/panelGraphScene.d.ts.map +1 -1
  51. package/dist/panelGraphScene.js +69 -32
  52. package/dist/panelGraphScene.js.map +1 -1
  53. package/dist/panelGraphSearch.d.ts +16 -0
  54. package/dist/panelGraphSearch.d.ts.map +1 -0
  55. package/dist/panelGraphSearch.js +93 -0
  56. package/dist/panelGraphSearch.js.map +1 -0
  57. package/dist/panelGraphView.d.ts +82 -0
  58. package/dist/panelGraphView.d.ts.map +1 -1
  59. package/dist/panelGraphView.js +15 -4
  60. package/dist/panelGraphView.js.map +1 -1
  61. package/dist/panelGraphVisuals.d.ts +14 -6
  62. package/dist/panelGraphVisuals.d.ts.map +1 -1
  63. package/dist/panelGraphVisuals.js +126 -51
  64. package/dist/panelGraphVisuals.js.map +1 -1
  65. package/dist/panelInspectorView.d.ts +8 -0
  66. package/dist/panelInspectorView.d.ts.map +1 -1
  67. package/dist/panelInspectorView.js +242 -48
  68. package/dist/panelInspectorView.js.map +1 -1
  69. package/dist/panelListStyles.d.ts +3 -0
  70. package/dist/panelListStyles.d.ts.map +1 -0
  71. package/dist/panelListStyles.js +155 -0
  72. package/dist/panelListStyles.js.map +1 -0
  73. package/dist/panelMemoryStyles.d.ts +3 -0
  74. package/dist/panelMemoryStyles.d.ts.map +1 -0
  75. package/dist/panelMemoryStyles.js +42 -0
  76. package/dist/panelMemoryStyles.js.map +1 -0
  77. package/dist/panelMemoryView.d.ts +13 -1
  78. package/dist/panelMemoryView.d.ts.map +1 -1
  79. package/dist/panelMemoryView.js +161 -41
  80. package/dist/panelMemoryView.js.map +1 -1
  81. package/dist/panelPassesView.d.ts +15 -0
  82. package/dist/panelPassesView.d.ts.map +1 -1
  83. package/dist/panelPassesView.js +202 -72
  84. package/dist/panelPassesView.js.map +1 -1
  85. package/dist/panelRawView.d.ts +15 -0
  86. package/dist/panelRawView.d.ts.map +1 -0
  87. package/dist/panelRawView.js +105 -0
  88. package/dist/panelRawView.js.map +1 -0
  89. package/dist/panelResourcesView.d.ts +8 -0
  90. package/dist/panelResourcesView.d.ts.map +1 -1
  91. package/dist/panelResourcesView.js +62 -31
  92. package/dist/panelResourcesView.js.map +1 -1
  93. package/dist/panelSelection.d.ts +9 -1
  94. package/dist/panelSelection.d.ts.map +1 -1
  95. package/dist/panelSelection.js +34 -4
  96. package/dist/panelSelection.js.map +1 -1
  97. package/dist/panelTheme.d.ts +13 -0
  98. package/dist/panelTheme.d.ts.map +1 -0
  99. package/dist/panelTheme.js +104 -0
  100. package/dist/panelTheme.js.map +1 -0
  101. package/dist/panelTypes.d.ts +10 -1
  102. package/dist/panelTypes.d.ts.map +1 -1
  103. package/dist/panelTypes.js.map +1 -1
  104. package/dist/panelVisualTheme.d.ts +147 -50
  105. package/dist/panelVisualTheme.d.ts.map +1 -1
  106. package/dist/panelVisualTheme.js +34 -44
  107. package/dist/panelVisualTheme.js.map +1 -1
  108. package/dist/panelWorkbenchHelpers.d.ts +12 -1
  109. package/dist/panelWorkbenchHelpers.d.ts.map +1 -1
  110. package/dist/panelWorkbenchHelpers.js +62 -1
  111. package/dist/panelWorkbenchHelpers.js.map +1 -1
  112. package/dist/panelWorkbenchStyles.d.ts +2 -0
  113. package/dist/panelWorkbenchStyles.d.ts.map +1 -0
  114. package/dist/panelWorkbenchStyles.js +101 -0
  115. package/dist/panelWorkbenchStyles.js.map +1 -0
  116. package/dist/panelWorkbenchView.d.ts +16 -0
  117. package/dist/panelWorkbenchView.d.ts.map +1 -1
  118. package/dist/panelWorkbenchView.js +228 -50
  119. package/dist/panelWorkbenchView.js.map +1 -1
  120. package/dist/query.js.map +1 -1
  121. package/dist/styles.d.ts.map +1 -1
  122. package/dist/styles.js +147 -195
  123. package/dist/styles.js.map +1 -1
  124. package/dist/theme.d.ts +22 -0
  125. package/dist/theme.d.ts.map +1 -0
  126. package/dist/theme.js +10 -0
  127. package/dist/theme.js.map +1 -0
  128. package/dist/themeDefinitions.d.ts +251 -0
  129. package/dist/themeDefinitions.d.ts.map +1 -0
  130. package/dist/themeDefinitions.js +96 -0
  131. package/dist/themeDefinitions.js.map +1 -0
  132. package/dist/themePalette.d.ts +66 -0
  133. package/dist/themePalette.d.ts.map +1 -0
  134. package/dist/themePalette.js +37 -0
  135. package/dist/themePalette.js.map +1 -0
  136. package/dist/themes.css +165 -0
  137. package/package.json +44 -10
  138. package/src/FrameGraphInspector.ts +130 -31
  139. package/src/capture.ts +4 -0
  140. package/src/debugCaptureModel.ts +53 -2
  141. package/src/index.ts +6 -2
  142. package/src/panelAliasAnalysis.ts +24 -70
  143. package/src/panelCytoscapeGraphRenderer.ts +140 -23
  144. package/src/panelDetailLayout.ts +155 -0
  145. package/src/panelDetailStyles.ts +56 -0
  146. package/src/panelDiagnosticsView.ts +166 -85
  147. package/src/panelDomHelpers.ts +4 -5
  148. package/src/panelGraphLayout.ts +6 -5
  149. package/src/panelGraphRenderer.ts +7 -0
  150. package/src/panelGraphScene.ts +68 -30
  151. package/src/panelGraphSearch.ts +96 -0
  152. package/src/panelGraphView.ts +14 -4
  153. package/src/panelGraphVisuals.ts +122 -52
  154. package/src/panelInspectorView.ts +227 -48
  155. package/src/panelListStyles.ts +154 -0
  156. package/src/panelMemoryStyles.ts +41 -0
  157. package/src/panelMemoryView.ts +178 -53
  158. package/src/panelPassesView.ts +190 -137
  159. package/src/panelRawView.ts +99 -0
  160. package/src/panelResourcesView.ts +69 -56
  161. package/src/panelSelection.ts +37 -5
  162. package/src/panelTheme.ts +85 -0
  163. package/src/panelTypes.ts +7 -1
  164. package/src/panelVisualTheme.ts +38 -49
  165. package/src/panelWorkbenchHelpers.ts +60 -2
  166. package/src/panelWorkbenchStyles.ts +100 -0
  167. package/src/panelWorkbenchView.ts +201 -52
  168. package/src/query.ts +11 -11
  169. package/src/styles.ts +148 -195
  170. package/src/theme.ts +24 -0
  171. package/src/themeDefinitions.ts +103 -0
  172. package/src/themePalette.ts +44 -0
@@ -6,7 +6,9 @@ import { MemoryView } from "./panelMemoryView.js";
6
6
  import { PassesView } from "./panelPassesView.js";
7
7
  import { ResourcesView } from "./panelResourcesView.js";
8
8
  import { renderGraphView, resizeGraph } from "./panelGraphView.js";
9
- import { formatEstimatedBytes } from "./panelWorkbenchHelpers.js";
9
+ import { DetailLayout } from "./panelDetailLayout.js";
10
+ import { GraphSearch } from "./panelGraphSearch.js";
11
+ import { enableTabKeyboard, formatEstimatedBytes, formatEstimateCoverage, formatTimingCoverage } from "./panelWorkbenchHelpers.js";
10
12
  export class FrameGraphDebugWorkbench {
11
13
  graphView;
12
14
  callbacks;
@@ -17,6 +19,8 @@ export class FrameGraphDebugWorkbench {
17
19
  tabList = document.createElement('div');
18
20
  commandActions = document.createElement('div');
19
21
  commandStatus = document.createElement('span');
22
+ feedback = document.createElement('details');
23
+ feedbackTitle = document.createElement('summary');
20
24
  workspace = document.createElement('div');
21
25
  main = document.createElement('main');
22
26
  emptyHost = document.createElement('div');
@@ -27,6 +31,10 @@ export class FrameGraphDebugWorkbench {
27
31
  memory;
28
32
  diagnostics;
29
33
  inspector;
34
+ detailLayout;
35
+ graphSearch;
36
+ dirtyViews = new Set();
37
+ captureDetails = document.createElement('details');
30
38
  inspectorOpenButton = document.createElement('button');
31
39
  captureButton = document.createElement('button');
32
40
  importButton = document.createElement('button');
@@ -42,6 +50,7 @@ export class FrameGraphDebugWorkbench {
42
50
  snapshot;
43
51
  selected;
44
52
  hovered;
53
+ destroyed = false;
45
54
  constructor(graphView, callbacks, actions, options) {
46
55
  this.graphView = graphView;
47
56
  this.callbacks = callbacks;
@@ -62,7 +71,7 @@ export class FrameGraphDebugWorkbench {
62
71
  this.commandStatus.className = 'zenfg-inspector-command-status';
63
72
  this.commandStatus.setAttribute('role', 'status');
64
73
  this.commandStatus.setAttribute('aria-live', 'polite');
65
- this.workspace.className = 'zenfg-inspector-workspace inspector-open';
74
+ this.workspace.className = 'zenfg-inspector-workspace';
66
75
  this.main.className = 'zenfg-inspector-main';
67
76
  this.emptyHost.className = 'zenfg-inspector-workbench-empty';
68
77
  this.emptyHost.setAttribute('role', 'status');
@@ -74,12 +83,25 @@ export class FrameGraphDebugWorkbench {
74
83
  this.graphRoot.className = 'zenfg-inspector-view zenfg-inspector-graph-view';
75
84
  this.graphRoot.id = `${options.idPrefix}-view-graph`;
76
85
  this.graphRoot.setAttribute('role', 'tabpanel');
77
- this.graphRoot.append(this.graphView.toolbar, this.graphView.host);
86
+ const graphViewport = document.createElement('div');
87
+ graphViewport.className = 'zenfg-inspector-graph-viewport';
88
+ graphViewport.append(this.graphView.host, this.graphView.toolbar);
89
+ if (this.graphView.legend) {
90
+ const legend = document.createElement('details');
91
+ legend.className = 'zenfg-inspector-legend-details';
92
+ const title = document.createElement('summary');
93
+ title.textContent = 'Legend';
94
+ legend.append(title, this.graphView.legend);
95
+ graphViewport.append(legend);
96
+ }
97
+ this.graphRoot.append(graphViewport);
78
98
  this.passes = new PassesView(callbacks, options.idPrefix);
79
99
  this.resources = new ResourcesView(callbacks, options.idPrefix);
80
100
  this.memory = new MemoryView(callbacks, options.idPrefix);
81
101
  this.diagnostics = new DiagnosticsView(callbacks, options.idPrefix);
82
102
  this.inspector = new InspectorView(callbacks, (open) => this.handleInspectorOpenChange(open), options.idPrefix);
103
+ this.graphSearch = new GraphSearch((selection) => callbacks.onReveal?.(selection, 'graph'), `${options.idPrefix}-graph-search`);
104
+ this.graphView.toolbar.prepend(this.graphSearch.root);
83
105
  this.views.set('overview', this.overviewRoot);
84
106
  this.views.set('graph', this.graphRoot);
85
107
  this.views.set('passes', this.passes.root);
@@ -104,6 +126,7 @@ export class FrameGraphDebugWorkbench {
104
126
  this.tabButtons.set(tab, button);
105
127
  this.tabList.appendChild(button);
106
128
  }
129
+ enableTabKeyboard(this.tabList);
107
130
  this.inspectorOpenButton.type = 'button';
108
131
  this.inspectorOpenButton.hidden = true;
109
132
  this.inspectorOpenButton.className = 'zenfg-inspector-open-inspector';
@@ -154,7 +177,7 @@ export class FrameGraphDebugWorkbench {
154
177
  actions.onCopyJson();
155
178
  });
156
179
  this.exportMenu.append(this.downloadButton, this.copyButton);
157
- this.commandActions.append(this.commandStatus, this.inspectorOpenButton, this.captureButton, this.importButton, this.exportButton, this.importInput);
180
+ this.commandActions.append(this.inspectorOpenButton, this.captureButton, this.importButton, this.exportButton, this.importInput);
158
181
  if (options.branding === false)
159
182
  this.commandBar.append(this.tabList, this.commandActions, this.exportMenu);
160
183
  else
@@ -167,15 +190,15 @@ export class FrameGraphDebugWorkbench {
167
190
  if (!this.exportButton.contains(targetNode) && !this.exportMenu.contains(targetNode))
168
191
  this.setExportMenuOpen(false);
169
192
  });
170
- this.root.addEventListener('keydown', (event) => {
171
- if (event.key !== 'Escape' || this.exportMenu.hidden)
172
- return;
173
- this.setExportMenuOpen(false);
174
- this.exportButton.focus();
175
- });
193
+ this.root.addEventListener('keydown', (event) => this.handleMenuKey(event));
176
194
  this.main.append(this.emptyHost, ...this.views.values());
177
195
  this.workspace.append(this.main, this.inspector.root);
178
- this.root.append(this.commandBar, this.workspace);
196
+ this.feedback.className = 'zenfg-inspector-feedback';
197
+ this.feedback.hidden = true;
198
+ this.feedback.append(this.feedbackTitle, this.commandStatus);
199
+ this.root.append(this.commandBar, this.feedback, this.workspace);
200
+ this.detailLayout = new DetailLayout(this.workspace, this.main, this.inspector.root, this.commandBar, () => this.inspector.setOpen(false));
201
+ this.workspace.addEventListener('detail-layout-change', () => this.resizeGraph());
179
202
  this.showEmptyState('empty', 'Drop a ZenFG Snapshot here or choose Import.', 'Files are processed locally in your browser.');
180
203
  this.setSnapshotActionState({
181
204
  providerAvailable: false,
@@ -191,18 +214,14 @@ export class FrameGraphDebugWorkbench {
191
214
  this.snapshot = snapshot;
192
215
  this.selected = selected;
193
216
  this.hovered = undefined;
194
- this.renderSummary(snapshot);
195
- this.passes.setSnapshot(snapshot);
196
- this.resources.setSnapshot(snapshot);
197
- this.memory.setSnapshot(snapshot);
198
- this.diagnostics.setSnapshot(snapshot);
217
+ for (const tab of this.views.keys())
218
+ this.dirtyViews.add(tab);
219
+ this.renderDiagnosticBadge(snapshot);
220
+ this.graphSearch.setSnapshot(snapshot);
199
221
  this.inspector.setSnapshot(snapshot);
200
- this.passes.setSelection(selected);
201
- this.resources.setSelection(selected);
202
- this.memory.setSelection(selected);
203
- this.diagnostics.setSelection(selected);
204
222
  this.inspector.setSelection(selected, false);
205
223
  this.emptyHost.hidden = true;
224
+ this.ensureActiveView();
206
225
  this.updateActiveTab();
207
226
  this.updateWorkspaceState();
208
227
  if (this.activeTab === 'graph')
@@ -263,13 +282,13 @@ export class FrameGraphDebugWorkbench {
263
282
  this.commandStatus.title = state.message;
264
283
  else
265
284
  this.commandStatus.removeAttribute('title');
285
+ this.feedback.hidden = !state.message;
286
+ this.feedback.dataset.tone = state.messageTone ?? 'neutral';
287
+ this.feedbackTitle.textContent = state.message ? state.messageTone === 'error' ? 'Operation failed · show details' : 'Operation feedback · show details' : '';
266
288
  }
267
289
  setSelection(selected) {
268
290
  this.selected = selected;
269
- this.passes.setSelection(selected);
270
- this.resources.setSelection(selected);
271
- this.memory.setSelection(selected);
272
- this.diagnostics.setSelection(selected);
291
+ this.updateActiveSelection();
273
292
  this.inspector.setSelection(selected);
274
293
  if (this.activeTab === 'graph')
275
294
  this.renderGraph();
@@ -280,12 +299,22 @@ export class FrameGraphDebugWorkbench {
280
299
  this.renderGraph();
281
300
  }
282
301
  setActiveTab(tab) {
302
+ if (this.destroyed)
303
+ return;
283
304
  if (this.activeTab === tab)
284
305
  return;
306
+ if (this.activeTab === 'graph') {
307
+ this.graphView.revealOnNextRender = undefined;
308
+ this.graphView.renderer?.cancelReveal?.();
309
+ }
285
310
  this.activeTab = tab;
286
311
  this.updateActiveTab();
312
+ this.ensureActiveView();
313
+ this.updateWorkspaceState();
287
314
  if (tab === 'graph') {
288
315
  window.requestAnimationFrame(() => {
316
+ if (this.destroyed || this.activeTab !== 'graph')
317
+ return;
289
318
  resizeGraph(this.graphView);
290
319
  this.renderGraph();
291
320
  });
@@ -294,7 +323,6 @@ export class FrameGraphDebugWorkbench {
294
323
  refreshGraphStructure() {
295
324
  if (!this.snapshot)
296
325
  return;
297
- this.passes.setSnapshot(this.snapshot);
298
326
  if (this.activeTab === 'graph')
299
327
  this.renderGraph();
300
328
  }
@@ -302,6 +330,8 @@ export class FrameGraphDebugWorkbench {
302
330
  if (fit)
303
331
  this.graphView.fitOnNextRender = true;
304
332
  window.requestAnimationFrame(() => {
333
+ if (this.destroyed)
334
+ return;
305
335
  resizeGraph(this.graphView);
306
336
  if (this.activeTab === 'graph')
307
337
  this.renderGraph();
@@ -313,6 +343,7 @@ export class FrameGraphDebugWorkbench {
313
343
  button.disabled = !this.hasSnapshot;
314
344
  button.classList.toggle('active', active);
315
345
  button.setAttribute('aria-selected', active ? 'true' : 'false');
346
+ button.tabIndex = active ? 0 : -1;
316
347
  const view = this.views.get(tab);
317
348
  view.hidden = !this.hasSnapshot || !active;
318
349
  view.setAttribute('aria-labelledby', button.id);
@@ -327,9 +358,7 @@ export class FrameGraphDebugWorkbench {
327
358
  const frameGraphSegments = snapshot.executionSegments.filter((segment) => segment.kind === 'frame-graph').length;
328
359
  const opaqueIntervals = snapshot.executionSegments.length - frameGraphSegments;
329
360
  const metrics = snapshot.metrics;
330
- const coverage = metrics.timingEligibleNodeCount === 0
331
- ? 'No eligible passes'
332
- : `${metrics.timedNodeCount}/${metrics.timingEligibleNodeCount} timed`;
361
+ const coverage = formatTimingCoverage(metrics.timedNodeCount, metrics.timingEligibleNodeCount);
333
362
  const slowest = metrics.slowestNode
334
363
  ? `${labelNode(metrics.slowestNode)} · ${(metrics.slowestNode.gpuDurationMicros / 1000).toFixed(3)} ms`
335
364
  : 'Unknown';
@@ -346,34 +375,47 @@ export class FrameGraphDebugWorkbench {
346
375
  const poolRetained = snapshot.resourcePool.status === 'available'
347
376
  ? `${snapshot.resourcePool.estimatedRetainedBytes === undefined ? 'Unknown' : formatBytes(snapshot.resourcePool.estimatedRetainedBytes)} · ${snapshot.resourcePool.retainedCount} allocations`
348
377
  : 'Unavailable';
349
- this.summary.replaceChildren(this.createSummaryGroup('Capture', [
350
- ['Source', snapshot.source.label],
351
- ['Schema', `${protocol.format} v${protocol.version.major}.${protocol.version.minor}`],
352
- ['Producer', producer],
353
- ['Runtime', runtimeLabel],
354
- ['Frame', String(snapshot.frameIndex)],
355
- ...(snapshot.source.migratedFromLegacy ? [['Migration', 'Legacy V0 → V1']] : []),
356
- ['Timing', snapshot.profiling.status === 'available' ? 'Available' : snapshot.profiling.reason],
357
- ]), this.createSummaryGroup('GPU', [
358
- ['GPU Span', snapshot.profiling.status === 'available' ? `${formatGpuFrameDuration(snapshot)} ms` : 'Unknown'],
359
- ['Coverage', coverage],
360
- ['Slowest', slowest],
361
- ]), this.createSummaryGroup('Work', [
378
+ const counts = this.diagnosticCounts(snapshot);
379
+ this.summary.replaceChildren(this.createSummaryGroup('Diagnostics', [
380
+ ['Errors', String(counts.error)], ['Warnings', String(counts.warning)], ['Information', String(counts.info)],
381
+ ], () => this.setActiveTab('diagnostics')), this.createSummaryGroup('Execution timing', [
382
+ ['CPU execute', snapshot.cpuProfiling.status === 'available' ? (snapshot.cpuProfiling.executionDurationMicros / 1000).toFixed(3) + ' ms' : 'Not collected'],
383
+ ['CPU pass sum', snapshot.metrics.cpuTimedNodeCount ? (snapshot.metrics.cpuWorkDurationMicros / 1000).toFixed(3) + ' ms' : 'Not collected'],
384
+ ['CPU coverage', formatTimingCoverage(snapshot.metrics.cpuTimedNodeCount, snapshot.nodes.length)],
385
+ ['GPU span', snapshot.profiling.status === 'available' ? `${formatGpuFrameDuration(snapshot)} ms` : 'Not collected'],
386
+ ['GPU coverage', coverage],
387
+ ['Slowest GPU pass', slowest],
388
+ ], () => metrics.slowestNode ? this.callbacks.onReveal?.({ kind: 'node', id: metrics.slowestNode.id }, 'passes') : this.setActiveTab('passes')), this.createSummaryGroup('Work', [
362
389
  ['Nodes', `${snapshot.nodes.length} retained · ${snapshot.culledNodes.length} culled`],
363
390
  ['Segments', `${frameGraphSegments} FG · ${opaqueIntervals} opaque`],
364
391
  ['Groups', snapshot.availability.groups ? String(snapshot.debugGroups.length) : 'Unknown'],
365
392
  ['Recording order', snapshot.availability.recordingOrder ? 'Available' : 'Unknown'],
366
- ]), this.createSummaryGroup('Resources', [
367
- ['Logical / physical', `${snapshot.resources.length} / ${snapshot.physicalAllocations.length}`],
393
+ ], () => this.setActiveTab('passes')), this.createSummaryGroup('Resources', [
394
+ ['Logical / physical', `${snapshot.resources.length} / ${protocol.memory.allocationReport.status === 'available' ? snapshot.physicalAllocations.length : 'Unavailable'}`],
368
395
  ['Texture views', snapshot.availability.textureViews ? String(snapshot.textureViewById.size) : 'Unknown'],
369
396
  ['Access regions', snapshot.availability.accessRegions ? 'Available' : 'Unknown'],
370
- ['Transient estimate', formatEstimatedBytes(metrics.transientEstimatedByteSize)],
371
- ]), this.createSummaryGroup('Pool', [
397
+ ['Transient estimate', formatEstimateCoverage(metrics.transientEstimatedByteSize, metrics.estimatedCoverage.transient)],
398
+ ], () => this.setActiveTab('resources')), this.createSummaryGroup('Memory estimates', [
399
+ ['Physical allocations', protocol.memory.allocationReport.status === 'available' ? formatEstimateCoverage(metrics.physicalEstimatedBytes, metrics.estimatedCoverage.physical) : 'Unavailable'],
400
+ ['Alias reuse', protocol.memory.allocationReport.status === 'available' ? formatEstimatedBytes(metrics.aliasReuseBytes) : 'Unavailable'],
401
+ ], () => this.setActiveTab('memory')), this.createSummaryGroup('Pool', [
372
402
  ['Retained', poolRetained],
373
403
  ['Reuse', formatPoolHitRate(snapshot)],
404
+ ], () => this.setActiveTab('memory')));
405
+ const title = document.createElement('summary');
406
+ title.textContent = 'Capture information';
407
+ this.captureDetails.className = 'zenfg-inspector-capture-details';
408
+ this.captureDetails.replaceChildren(title, this.createSummaryGroup('Capture', [
409
+ ['Source', snapshot.source.label], ['Frame', String(snapshot.frameIndex)],
410
+ ['Captured at', protocol.capture.capturedAt ?? 'Unknown'],
411
+ ['Schema', `${protocol.format} v${protocol.version.major}.${protocol.version.minor}`],
412
+ ['Producer', producer], ['Runtime', runtimeLabel],
413
+ ...(protocol.capture.migration ? [['Migration', `${protocol.capture.migration.sourceFormat} → canonical v${protocol.version.major}.${protocol.version.minor}`]] : []),
414
+ ['Timing', snapshot.profiling.status === 'available' ? coverage : snapshot.profiling.reason],
374
415
  ]));
416
+ this.summary.append(this.captureDetails);
375
417
  }
376
- createSummaryGroup(title, rows) {
418
+ createSummaryGroup(title, rows, onNavigate) {
377
419
  const group = document.createElement('section');
378
420
  const heading = document.createElement('h2');
379
421
  heading.textContent = title;
@@ -388,11 +430,21 @@ export class FrameGraphDebugWorkbench {
388
430
  row.append(term, description);
389
431
  group.appendChild(row);
390
432
  }
433
+ if (onNavigate) {
434
+ const action = document.createElement('button');
435
+ action.type = 'button';
436
+ action.className = 'zenfg-inspector-relation-button';
437
+ action.textContent = `Explore ${title.toLowerCase()}`;
438
+ action.addEventListener('click', onNavigate);
439
+ group.append(action);
440
+ }
391
441
  return group;
392
442
  }
393
443
  setExportMenuOpen(open) {
394
444
  this.exportMenu.hidden = !open;
395
445
  this.exportButton.setAttribute('aria-expanded', open ? 'true' : 'false');
446
+ if (open)
447
+ this.downloadButton.focus();
396
448
  }
397
449
  handleInspectorOpenChange(_open) {
398
450
  this.updateWorkspaceState();
@@ -400,11 +452,137 @@ export class FrameGraphDebugWorkbench {
400
452
  this.resizeGraph(false);
401
453
  }
402
454
  updateWorkspaceState() {
403
- const inspectorOpen = this.hasSnapshot && this.inspector.isOpen;
455
+ const inspectorOpen = this.hasSnapshot && this.inspector.isOpen && this.activeTab !== 'overview';
404
456
  this.workspace.classList.toggle('has-capture', this.hasSnapshot);
405
- this.workspace.classList.toggle('inspector-open', inspectorOpen);
406
- this.inspector.root.classList.toggle('unavailable', !this.hasSnapshot);
407
- this.inspectorOpenButton.hidden = !this.hasSnapshot || inspectorOpen;
457
+ this.inspector.root.classList.toggle('unavailable', !inspectorOpen);
458
+ this.inspectorOpenButton.hidden = !this.hasSnapshot || !this.selected || inspectorOpen || this.activeTab === 'overview';
459
+ this.detailLayout.update(inspectorOpen);
460
+ }
461
+ reveal(selection, tab) {
462
+ if (this.feedbackTitle.textContent === 'Graph location unavailable') {
463
+ this.feedback.hidden = true;
464
+ this.commandStatus.replaceChildren();
465
+ this.commandStatus.hidden = true;
466
+ }
467
+ this.setActiveTab(tab);
468
+ if (tab === 'passes')
469
+ this.passes.reveal(selection);
470
+ else if (tab === 'resources')
471
+ this.resources.reveal(selection);
472
+ else if (tab === 'memory')
473
+ this.memory.reveal(selection);
474
+ else if (tab === 'diagnostics')
475
+ this.diagnostics.reveal(selection);
476
+ if (this.detailLayout.isDrawer)
477
+ this.inspector.setOpen(false);
478
+ if (tab === 'graph')
479
+ this.renderGraph();
480
+ }
481
+ navigate(tab, filter) {
482
+ this.setActiveTab(tab);
483
+ if (tab === 'passes' && filter === 'culled')
484
+ this.passes.showCulled();
485
+ if (this.detailLayout.isDrawer)
486
+ this.inspector.setOpen(false);
487
+ }
488
+ showNavigationIssue(message, selection) {
489
+ if (this.detailLayout.isDrawer)
490
+ this.inspector.setOpen(false);
491
+ this.feedback.hidden = false;
492
+ this.feedback.open = true;
493
+ this.feedback.dataset.tone = 'neutral';
494
+ this.feedbackTitle.textContent = 'Graph location unavailable';
495
+ const action = document.createElement('button');
496
+ action.type = 'button';
497
+ action.className = 'zenfg-inspector-relation-button';
498
+ const tab = selection.kind === 'resource' ? 'resources' : selection.kind === 'allocation' ? 'memory'
499
+ : selection.kind === 'root' || selection.kind === 'segment' ? 'diagnostics' : 'passes';
500
+ action.textContent = `Show in ${tab}`;
501
+ action.addEventListener('click', () => this.callbacks.onReveal?.(selection, tab));
502
+ this.commandStatus.hidden = false;
503
+ this.commandStatus.replaceChildren(document.createTextNode(`${message} `), action);
504
+ action.focus();
505
+ }
506
+ destroy() { this.destroyed = true; this.detailLayout.destroy(); }
507
+ ensureActiveView() {
508
+ const snapshot = this.snapshot;
509
+ if (!snapshot)
510
+ return;
511
+ if (this.dirtyViews.has(this.activeTab)) {
512
+ switch (this.activeTab) {
513
+ case 'overview':
514
+ this.renderSummary(snapshot);
515
+ break;
516
+ case 'passes':
517
+ this.passes.setSnapshot(snapshot);
518
+ break;
519
+ case 'resources':
520
+ this.resources.setSnapshot(snapshot);
521
+ break;
522
+ case 'memory':
523
+ this.memory.setSnapshot(snapshot);
524
+ break;
525
+ case 'diagnostics':
526
+ this.diagnostics.setSnapshot(snapshot);
527
+ break;
528
+ }
529
+ this.dirtyViews.delete(this.activeTab);
530
+ }
531
+ this.updateActiveSelection();
532
+ }
533
+ updateActiveSelection() {
534
+ switch (this.activeTab) {
535
+ case 'passes':
536
+ this.passes.setSelection(this.selected);
537
+ break;
538
+ case 'resources':
539
+ this.resources.setSelection(this.selected);
540
+ break;
541
+ case 'memory':
542
+ this.memory.setSelection(this.selected);
543
+ break;
544
+ case 'diagnostics':
545
+ this.diagnostics.setSelection(this.selected);
546
+ break;
547
+ }
548
+ }
549
+ diagnosticCounts(snapshot) {
550
+ const counts = { error: 0, warning: 0, info: 0 };
551
+ for (const entry of snapshot.protocol.diagnostics)
552
+ counts[entry.severity]++;
553
+ return counts;
554
+ }
555
+ renderDiagnosticBadge(snapshot) {
556
+ const counts = this.diagnosticCounts(snapshot);
557
+ const tab = this.tabButtons.get('diagnostics');
558
+ const label = document.createElement('span');
559
+ label.textContent = 'Diagnostics';
560
+ const badge = document.createElement('span');
561
+ badge.className = 'zenfg-inspector-diagnostic-badge';
562
+ badge.textContent = `${counts.error} / ${counts.warning}`;
563
+ badge.title = `${counts.error} errors, ${counts.warning} warnings`;
564
+ badge.setAttribute('aria-hidden', 'true');
565
+ tab.replaceChildren(label, ...(counts.error + counts.warning ? [badge] : []));
566
+ tab.title = badge.title;
567
+ }
568
+ handleMenuKey(event) {
569
+ if (this.exportMenu.hidden || event.defaultPrevented)
570
+ return;
571
+ if (event.key === 'Escape') {
572
+ event.preventDefault();
573
+ event.stopPropagation();
574
+ this.setExportMenuOpen(false);
575
+ this.exportButton.focus();
576
+ }
577
+ else if (['ArrowDown', 'ArrowUp', 'Home', 'End'].includes(event.key)) {
578
+ event.preventDefault();
579
+ event.stopPropagation();
580
+ const items = [this.downloadButton, this.copyButton].filter((button) => !button.disabled);
581
+ const index = items.indexOf(document.activeElement);
582
+ const next = event.key === 'Home' ? 0 : event.key === 'End' ? items.length - 1
583
+ : (index + (event.key === 'ArrowDown' ? 1 : -1) + items.length) % items.length;
584
+ items[next]?.focus();
585
+ }
408
586
  }
409
587
  }
410
588
  //# sourceMappingURL=panelWorkbenchView.js.map