@zenfg/inspector 0.1.0-beta.2 → 0.1.0-beta.4

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 +60 -26
  3. package/THEMING.md +124 -0
  4. package/dist/FrameGraphInspector.d.ts +24 -8
  5. package/dist/FrameGraphInspector.d.ts.map +1 -1
  6. package/dist/FrameGraphInspector.js +151 -65
  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 +28 -4
  13. package/dist/debugCaptureModel.d.ts.map +1 -1
  14. package/dist/debugCaptureModel.js +72 -16
  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 +18 -3
  24. package/dist/panelCytoscapeGraphRenderer.d.ts.map +1 -1
  25. package/dist/panelCytoscapeGraphRenderer.js +183 -41
  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 +167 -58
  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 +20 -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 +15 -26
  50. package/dist/panelGraphScene.d.ts.map +1 -1
  51. package/dist/panelGraphScene.js +191 -208
  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 +42 -9
  60. package/dist/panelGraphView.js.map +1 -1
  61. package/dist/panelGraphVisuals.d.ts +18 -6
  62. package/dist/panelGraphVisuals.d.ts.map +1 -1
  63. package/dist/panelGraphVisuals.js +156 -116
  64. package/dist/panelGraphVisuals.js.map +1 -1
  65. package/dist/panelInspectorView.d.ts +13 -0
  66. package/dist/panelInspectorView.d.ts.map +1 -1
  67. package/dist/panelInspectorView.js +326 -46
  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 +36 -6
  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 +18 -4
  102. package/dist/panelTypes.d.ts.map +1 -1
  103. package/dist/panelTypes.js.map +1 -1
  104. package/dist/panelVisualTheme.d.ts +176 -71
  105. package/dist/panelVisualTheme.d.ts.map +1 -1
  106. package/dist/panelVisualTheme.js +34 -36
  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 +63 -2
  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 +161 -205
  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 +145 -68
  139. package/src/capture.ts +4 -0
  140. package/src/debugCaptureModel.ts +97 -21
  141. package/src/index.ts +6 -2
  142. package/src/panelAliasAnalysis.ts +24 -70
  143. package/src/panelCytoscapeGraphRenderer.ts +177 -40
  144. package/src/panelDetailLayout.ts +155 -0
  145. package/src/panelDetailStyles.ts +56 -0
  146. package/src/panelDiagnosticsView.ts +167 -86
  147. package/src/panelDomHelpers.ts +4 -5
  148. package/src/panelGraphLayout.ts +20 -5
  149. package/src/panelGraphRenderer.ts +7 -0
  150. package/src/panelGraphScene.ts +204 -292
  151. package/src/panelGraphSearch.ts +96 -0
  152. package/src/panelGraphView.ts +41 -9
  153. package/src/panelGraphVisuals.ts +152 -118
  154. package/src/panelInspectorView.ts +315 -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 +39 -7
  162. package/src/panelTheme.ts +85 -0
  163. package/src/panelTypes.ts +16 -4
  164. package/src/panelVisualTheme.ts +38 -38
  165. package/src/panelWorkbenchHelpers.ts +62 -4
  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 +162 -205
  170. package/src/theme.ts +24 -0
  171. package/src/themeDefinitions.ts +103 -0
  172. package/src/themePalette.ts +44 -0
@@ -1,7 +1,30 @@
1
1
  import { labelNode, labelResource } from "./panelDomHelpers.js";
2
2
  import { createPanelIcon } from "./panelIcons.js";
3
- import { resolveSelectedDetail } from "./panelSelection.js";
4
- import { createRelationButton, formatEstimatedBytes, formatResourceDescriptor, groupPath, resourceLabel, } from "./panelWorkbenchHelpers.js";
3
+ import { RawDetailView } from "./panelRawView.js";
4
+ import { resolveNodeSelection, resolveSelectedCanonicalDetail } from "./panelSelection.js";
5
+ import { createRelationButton, formatEstimatedBytes, formatResourceDescriptor, formatTimingCoverage, enableTabKeyboard, writeClipboardText, selectionKey, groupPath, resourceLabel, } from "./panelWorkbenchHelpers.js";
6
+ function formatAccessFacts(access) {
7
+ return [
8
+ access.mode, access.access,
9
+ ...(access.mode === 'write' ? [access.contents, access.producesValue ? 'produces a value' : 'does not produce a value'] : []),
10
+ ...(access.bufferRange ? [`bytes ${access.bufferRange.offset}–${access.bufferRange.size === undefined ? 'end' : access.bufferRange.offset + access.bufferRange.size}`] : []),
11
+ ...(access.textureRegion ? [formatTextureRegion(access.textureRegion)] : []),
12
+ ...(access.textureViewId ? [`view ${access.textureViewId}`] : []),
13
+ ].join(' · ');
14
+ }
15
+ function formatTextureRegion(region) {
16
+ const end = (start, count) => start === undefined ? 'unknown' : count === undefined ? `${start}–end` : `${start}–${start + count - 1}`;
17
+ return `mip ${end(region.baseMipLevel, region.mipLevelCount)} · ${region.baseDepthSlice === undefined
18
+ ? `layers ${end(region.baseArrayLayer, region.arrayLayerCount)}`
19
+ : `depth ${end(region.baseDepthSlice, region.depthSliceCount)}`} · aspect ${region.aspect}`;
20
+ }
21
+ function formatRootRange(root) {
22
+ if (!root.range)
23
+ return 'Range unavailable';
24
+ if (root.range.kind === 'buffer')
25
+ return `bytes ${root.range.offset}–${root.range.offset + root.range.size}`;
26
+ return root.range.regions.map(formatTextureRegion).join('; ');
27
+ }
5
28
  export class InspectorView {
6
29
  callbacks;
7
30
  onOpenChange;
@@ -13,7 +36,9 @@ export class InspectorView {
13
36
  snapshot;
14
37
  selected;
15
38
  activeTab = 'summary';
16
- open = true;
39
+ open = false;
40
+ hoveredLink;
41
+ rawCache;
17
42
  constructor(callbacks, onOpenChange, idPrefix) {
18
43
  this.callbacks = callbacks;
19
44
  this.onOpenChange = onOpenChange;
@@ -46,6 +71,7 @@ export class InspectorView {
46
71
  this.tabs.set(tab, button);
47
72
  this.tabList.appendChild(button);
48
73
  }
74
+ enableTabKeyboard(this.tabList);
49
75
  this.content.id = `${idPrefix}-inspector-panel`;
50
76
  this.content.className = 'zenfg-inspector-inspector-content';
51
77
  this.content.setAttribute('role', 'tabpanel');
@@ -54,20 +80,35 @@ export class InspectorView {
54
80
  this.render();
55
81
  }
56
82
  setSnapshot(snapshot) {
83
+ if (this.snapshot !== snapshot)
84
+ this.rawCache = undefined;
57
85
  this.snapshot = snapshot;
58
86
  this.render();
59
87
  }
60
88
  setSelection(selected, reveal = true) {
89
+ if (!selected || !this.selected || selectionKey(selected) !== selectionKey(this.selected))
90
+ this.rawCache = undefined;
91
+ if (selected?.kind === 'resource'
92
+ && (this.selected?.kind !== 'resource' || this.selected.id !== selected.id))
93
+ this.activeTab = 'summary';
61
94
  this.selected = selected;
62
- if (selected && reveal)
95
+ if (!selected)
96
+ this.setOpen(false);
97
+ else if (reveal && !this.open) {
63
98
  this.setOpen(true);
99
+ return;
100
+ }
64
101
  this.render();
65
102
  }
66
103
  setOpen(open) {
67
104
  if (this.open === open)
68
105
  return;
106
+ if (!open)
107
+ this.clearLinkHover();
69
108
  this.open = open;
70
109
  this.updateOpenState();
110
+ if (open)
111
+ this.render();
71
112
  this.onOpenChange(open);
72
113
  }
73
114
  get isOpen() {
@@ -78,10 +119,13 @@ export class InspectorView {
78
119
  this.root.classList.toggle('open', this.open);
79
120
  }
80
121
  render() {
122
+ const restoreFocus = this.content.contains(document.activeElement);
123
+ this.clearLinkHover();
81
124
  for (const [tab, button] of this.tabs) {
82
125
  const active = tab === this.activeTab;
83
126
  button.classList.toggle('active', active);
84
127
  button.setAttribute('aria-selected', active ? 'true' : 'false');
128
+ button.tabIndex = active ? 0 : -1;
85
129
  if (active)
86
130
  this.content.setAttribute('aria-labelledby', button.id);
87
131
  }
@@ -97,33 +141,59 @@ export class InspectorView {
97
141
  return;
98
142
  }
99
143
  this.title.textContent = this.selectionTitle(snapshot, selected);
144
+ if (!this.open)
145
+ return;
100
146
  if (this.activeTab === 'raw') {
101
- const raw = document.createElement('pre');
102
- raw.className = 'zenfg-inspector-raw-detail';
103
- raw.textContent = JSON.stringify(resolveSelectedDetail(snapshot, selected), null, 2);
104
- this.content.appendChild(raw);
147
+ const key = selectionKey(selected);
148
+ if (this.rawCache?.snapshot !== snapshot || this.rawCache.key !== key) {
149
+ const detail = resolveSelectedCanonicalDetail(snapshot, selected);
150
+ this.rawCache = detail ? { snapshot, key, view: new RawDetailView(detail, Boolean(snapshot.protocol.capture.migration)) } : undefined;
151
+ }
152
+ if (this.rawCache)
153
+ this.content.appendChild(this.rawCache.view.root);
154
+ else
155
+ this.content.textContent = 'Canonical object unavailable in this capture.';
105
156
  }
106
157
  else if (this.activeTab === 'relations') {
107
158
  this.content.appendChild(this.createRelations(snapshot, selected));
108
159
  }
109
160
  else {
110
- this.content.appendChild(this.createSummary(snapshot, selected));
161
+ this.content.append(this.createLocationActions(snapshot, selected), this.createSummary(snapshot, selected));
162
+ const identity = this.selectionId(snapshot, selected);
163
+ if (identity)
164
+ this.content.appendChild(this.summary([['ID', this.copyId(identity)]]));
165
+ const diagnostics = this.createDiagnostics(snapshot, selected);
166
+ if (diagnostics)
167
+ this.content.appendChild(diagnostics);
168
+ if (selected.kind === 'node' || selected.kind === 'culled') {
169
+ const node = snapshot.nodeById.get(selected.id) ?? snapshot.culledById.get(selected.id)?.node;
170
+ if (node) {
171
+ const accesses = document.createElement('details');
172
+ const heading = document.createElement('summary');
173
+ heading.textContent = `Access facts · ${node.reads.length + node.writes.length}`;
174
+ accesses.append(heading, this.accessRelations('Reads', node.reads), this.accessRelations('Writes', node.writes));
175
+ this.content.appendChild(accesses);
176
+ }
177
+ }
111
178
  }
179
+ // Selecting a relation replaces its button. Keep keyboard focus in the pane.
180
+ if (restoreFocus)
181
+ this.tabs.get(this.activeTab)?.focus();
112
182
  }
113
183
  selectionTitle(snapshot, selection) {
114
184
  switch (selection.kind) {
115
185
  case 'node': {
116
186
  const node = snapshot.nodeById.get(selection.id);
117
- return node ? labelNode(node) : `Pass #${selection.id}`;
187
+ return node ? labelNode(node) : selection.id;
118
188
  }
119
189
  case 'group': return snapshot.groupByPathKey.get(selection.pathKey)?.label ?? 'Group';
120
190
  case 'resource': {
121
191
  const resource = snapshot.resourceById.get(selection.id);
122
- return resource ? labelResource(resource) : `Resource #${selection.id}`;
192
+ return resource ? labelResource(resource) : selection.id;
123
193
  }
124
- case 'allocation': return `Allocation #${selection.id}`;
125
- case 'root': return `Retention root #${selection.index}`;
126
- case 'culled': return `Culled node #${selection.index}`;
194
+ case 'allocation': return /^allocation[:#-]/i.test(selection.id) ? selection.id : `Allocation ${selection.id}`;
195
+ case 'root': return 'Output root';
196
+ case 'culled': return `${snapshot.culledById.get(selection.id)?.node.label ?? selection.id} (culled)`;
127
197
  case 'segment': return `Segment #${selection.index}`;
128
198
  }
129
199
  }
@@ -134,12 +204,21 @@ export class InspectorView {
134
204
  if (!node)
135
205
  return this.summary([]);
136
206
  const segment = snapshot.segmentByNodeId.get(node.id);
207
+ const roots = snapshot.roots.filter((root) => root.nodeId === node.id || root.resolution?.producerNodeIds.includes(node.id));
137
208
  return this.summary([
209
+ ['Compile state', 'Retained · compiler included this pass; execution success is not implied'],
210
+ ['Retention', roots.length ? roots.map((root) => root.reason).join(', ') : 'No direct output root recorded; inspect dependencies in Relations'],
138
211
  ['Kind', node.kind],
139
- ['Order', String(node.order)],
212
+ ['Execution slot', String(node.order)],
140
213
  ['Group', groupPath(snapshot, node.debugGroupId)],
141
214
  ['Segment', segment ? `#${segment.index} ${segment.kind}` : '-'],
142
- ['GPU', node.kind === 'external-submission' ? 'Opaque' : node.gpuDurationMicros === undefined ? 'Not timed' : `${(node.gpuDurationMicros / 1000).toFixed(3)} ms`],
215
+ ['CPU duration', node.cpuDurationMicros === undefined ? 'Not collected' : (node.cpuDurationMicros / 1000).toFixed(3) + ' ms'],
216
+ ['CPU scope', 'Synchronous elapsed time; zero may reflect clock precision.' + (node.kind === 'external-submission' ? ' Includes synchronous external submission.' : '')],
217
+ ['GPU duration', node.kind === 'external-submission' ? 'Opaque · external work is not measured'
218
+ : node.kind !== 'render' && node.kind !== 'compute' ? 'Not applicable · this pass kind is not timing eligible'
219
+ : node.gpuDurationMicros === undefined ? `Not collected${snapshot.profiling.status === 'unavailable' ? ` · ${snapshot.profiling.reason}` : ''}`
220
+ : `${(node.gpuDurationMicros / 1000).toFixed(3)} ms`],
221
+ ['GPU scope', 'GPU timestamp interval for render/compute passes; excludes external work. Zero may reflect timestamp precision.'],
143
222
  ['Accesses', `${node.reads.length} reads · ${node.writes.length} writes`],
144
223
  ['Side effect', node.sideEffect ? 'yes' : 'no'],
145
224
  ]);
@@ -152,9 +231,12 @@ export class InspectorView {
152
231
  ['Path', group.path.join(' / ')],
153
232
  ['Retained', String(group.summary.retainedNodeCount)],
154
233
  ['Culled', String(group.summary.culledNodeCount)],
155
- ['GPU coverage', `${group.summary.timedNodeCount}/${group.summary.timingEligibleNodeCount}`],
156
- ['GPU work', `${(group.summary.gpuWorkDurationMicros / 1000).toFixed(3)} ms`],
157
- ['Allocations', String(group.summary.physicalAllocationCount)],
234
+ ['CPU coverage', formatTimingCoverage(group.summary.cpuTimedNodeCount, group.summary.retainedNodeCount)],
235
+ ['CPU pass sum', group.summary.cpuTimedNodeCount ? (group.summary.cpuWorkDurationMicros / 1000).toFixed(3) + ' ms' : 'Not collected'],
236
+ ['GPU coverage', formatTimingCoverage(group.summary.timedNodeCount, group.summary.timingEligibleNodeCount)],
237
+ ['GPU pass sum', group.summary.timingEligibleNodeCount === 0 ? 'Not applicable' : group.summary.timedNodeCount === 0 ? 'Not collected' : (group.summary.gpuWorkDurationMicros / 1000).toFixed(3) + ' ms'],
238
+ ['Opaque passes', `${group.summary.externalSubmissionCount} · excluded from GPU pass sum`],
239
+ ['Allocations', snapshot.protocol.memory.allocationReport.status === 'available' ? String(group.summary.physicalAllocationCount) : 'Unknown · allocation report unavailable'],
158
240
  ['Segments', String(group.summary.executionSegmentCount)],
159
241
  ]);
160
242
  }
@@ -166,9 +248,14 @@ export class InspectorView {
166
248
  ['Kind / origin', `${resource.kind} · ${resource.origin}`],
167
249
  ['Group', groupPath(snapshot, resource.debugGroupId)],
168
250
  ['Descriptor', formatResourceDescriptor(resource)],
169
- ['Estimated', formatEstimatedBytes(resource.estimatedByteSize)],
170
- ['Lifetime', resource.lifetime ? `${resource.lifetime.firstUse}–${resource.lifetime.lastUse}` : '-'],
171
- ['Allocation', resource.physicalResourceId === undefined ? '-' : `#${resource.physicalResourceId}`],
251
+ ['Resource estimate', `${formatEstimatedBytes(resource.estimatedByteSize)} · logical descriptor estimate`],
252
+ ['Lifetime', resource.lifetime ? `execution slots ${resource.lifetime.firstUse}–${resource.lifetime.lastUse} (inclusive)` : 'Unavailable'],
253
+ ['Allocation', resource.physicalResourceId !== undefined
254
+ ? this.createDetailLink(resource.physicalResourceId, { kind: 'allocation', id: resource.physicalResourceId })
255
+ : resource.origin !== 'transient' ? 'Not applicable · externally owned resource'
256
+ : snapshot.protocol.memory.allocationReport.status === 'unavailable' ? 'Unavailable · allocation report not captured'
257
+ : 'Unallocated'],
258
+ ['Initial contents', resource.initialContents ?? 'Unknown'],
172
259
  ['Usage', resource.usageFlags.join(', ') || '-'],
173
260
  ]);
174
261
  }
@@ -179,26 +266,33 @@ export class InspectorView {
179
266
  return this.summary([
180
267
  ['Kind', allocation.kind],
181
268
  ['Compatibility class', String(allocation.compatibilityClassId)],
182
- ['Estimated', formatEstimatedBytes(allocation.estimatedByteSize)],
269
+ ['Physical estimate', `${formatEstimatedBytes(allocation.estimatedByteSize)} · allocation capacity estimate`],
183
270
  ['Logical resources', String(allocation.resourceIds.length)],
184
271
  ['Alias', allocation.resourceIds.length > 1 ? `yes · ×${allocation.resourceIds.length}` : 'single'],
185
272
  ]);
186
273
  }
187
274
  case 'root': {
188
- const root = snapshot.roots[selection.index];
275
+ const root = snapshot.roots.find((root) => root.key === selection.key);
189
276
  return this.summary(root ? [
190
277
  ['Reason', root.reason],
191
- ['Node', root.nodeId === undefined ? '-' : `#${root.nodeId}`],
192
- ['Resource', root.resource ? labelResource(root.resource) : '-'],
278
+ ['Node', root.nodeId === undefined ? 'Not applicable' : this.nodeLink(snapshot, root.nodeId)],
279
+ ['Resource', root.resource ? this.viewResourceButton(snapshot, root.resource.id) : '-'],
280
+ ...(root.reason === 'side-effect' ? [] : [
281
+ ['Range', root.range ? formatRootRange(root) : 'Unavailable in Legacy capture'],
282
+ ['Initial contents', root.resolution ? String(root.resolution.usesInitialContents) : 'Unavailable in Legacy capture'],
283
+ ['Producers', root.resolution ? root.resolution.producerNodeIds.join(', ') || 'None' : 'Unavailable in Legacy capture'],
284
+ ]),
193
285
  ] : []);
194
286
  }
195
287
  case 'culled': {
196
- const culled = snapshot.culledNodes[selection.index];
288
+ const culled = snapshot.culledById.get(selection.id);
197
289
  return this.summary(culled ? [
290
+ ['Compile state', 'Culled · excluded by the compiler'],
198
291
  ['Node', labelNode(culled.node)],
199
292
  ['Kind', culled.node.kind],
200
293
  ['Group', groupPath(snapshot, culled.node.debugGroupId)],
201
294
  ['Reason', culled.reason],
295
+ ['Execution / segment / CPU / GPU', 'Not applicable · culled passes do not execute'],
202
296
  ['Accesses', `${culled.node.reads.length} reads · ${culled.node.writes.length} writes`],
203
297
  ] : []);
204
298
  }
@@ -217,6 +311,7 @@ export class InspectorView {
217
311
  createRelations(snapshot, selection) {
218
312
  const host = document.createElement('div');
219
313
  host.className = 'zenfg-inspector-inspector-relations';
314
+ host.appendChild(this.createLocationActions(snapshot, selection));
220
315
  switch (selection.kind) {
221
316
  case 'node': {
222
317
  const node = snapshot.nodeById.get(selection.id);
@@ -227,13 +322,31 @@ export class InspectorView {
227
322
  host.appendChild(this.relationGroup('Segment', [
228
323
  [`#${segment.index} ${segment.kind}`, { kind: 'segment', index: segment.index }],
229
324
  ]));
325
+ for (const kind of ['value', 'ordering']) {
326
+ const relations = [];
327
+ for (const edge of snapshot.edges) {
328
+ if (edge.kind !== kind || (edge.fromNodeId !== node.id && edge.toNodeId !== node.id))
329
+ continue;
330
+ const upstream = edge.toNodeId === node.id;
331
+ const otherId = upstream ? edge.fromNodeId : edge.toNodeId;
332
+ const other = resolveNodeSelection(snapshot, otherId);
333
+ if (other)
334
+ relations.push([this.accessNodeLabel(snapshot, otherId), other,
335
+ `${upstream ? 'Upstream' : 'Downstream'} · ${resourceLabel(snapshot, edge.resource.id)} · ${kind} dependency`]);
336
+ }
337
+ if (relations.length)
338
+ host.appendChild(this.relationGroup(kind === 'value' ? 'Value dependencies' : 'Ordering dependencies', relations));
339
+ }
230
340
  }
231
341
  break;
232
342
  }
233
343
  case 'group': {
234
344
  const group = snapshot.groupByPathKey.get(selection.pathKey);
235
345
  if (group) {
236
- host.append(this.resourceRelations('Inputs', group.summary.inputResources), this.resourceRelations('Outputs', group.summary.outputResources));
346
+ host.append(this.resourceRelations('Inputs', group.summary.inputResources), this.resourceRelations('Outputs', group.summary.outputResources), this.relationGroup('Output roots', group.summary.outputRoots.map((root) => [
347
+ `${root.resource ? labelResource(root.resource) : '-'} · ${root.reason} · ${formatRootRange(root)}`,
348
+ { kind: 'root', key: root.key },
349
+ ])));
237
350
  }
238
351
  break;
239
352
  }
@@ -245,14 +358,20 @@ export class InspectorView {
245
358
  const nodeSelection = this.accessNodeSelection(snapshot, access.nodeId);
246
359
  if (nodeSelection)
247
360
  passRelations.push([
248
- `${access.mode} · ${this.accessNodeLabel(snapshot, access.nodeId)} · ${access.access}`,
361
+ this.accessNodeLabel(snapshot, access.nodeId),
249
362
  nodeSelection,
363
+ formatAccessFacts(access),
250
364
  ]);
251
365
  }
252
366
  host.appendChild(this.relationGroup('Pass accesses', passRelations));
367
+ const roots = snapshot.roots.filter((root) => root.resource?.id === selection.id);
368
+ if (roots.length)
369
+ host.appendChild(this.relationGroup('Output roots', roots.map((root) => [
370
+ `${root.reason} · ${formatRootRange(root)}`, { kind: 'root', key: root.key },
371
+ ])));
253
372
  if (resource?.physicalResourceId !== undefined)
254
373
  host.appendChild(this.relationGroup('Allocation', [
255
- [`#${resource.physicalResourceId}`, { kind: 'allocation', id: resource.physicalResourceId }],
374
+ [resource.physicalResourceId, { kind: 'allocation', id: resource.physicalResourceId }],
256
375
  ]));
257
376
  break;
258
377
  }
@@ -265,19 +384,23 @@ export class InspectorView {
265
384
  break;
266
385
  }
267
386
  case 'root': {
268
- const root = snapshot.roots[selection.index];
387
+ const root = snapshot.roots.find((root) => root.key === selection.key);
269
388
  const relations = [];
270
389
  if (root?.nodeId !== undefined && snapshot.nodeById.has(root.nodeId))
271
390
  relations.push([
272
391
  this.accessNodeLabel(snapshot, root.nodeId), { kind: 'node', id: root.nodeId },
273
392
  ]);
274
393
  if (root?.resource)
275
- relations.push([labelResource(root.resource), { kind: 'resource', id: root.resource.id }]);
276
- host.appendChild(this.relationGroup('Retained object', relations));
394
+ host.appendChild(this.relationGroup('Resource', [[labelResource(root.resource), { kind: 'resource', id: root.resource.id }]]));
395
+ if (root?.resource && root.resolution?.usesInitialContents)
396
+ relations.push([`Initial contents · ${labelResource(root.resource)}`, { kind: 'resource', id: root.resource.id }]);
397
+ for (const id of root?.resolution?.producerNodeIds ?? [])
398
+ relations.push([this.accessNodeLabel(snapshot, id), { kind: 'node', id }]);
399
+ host.appendChild(this.relationGroup('Output sources', relations));
277
400
  break;
278
401
  }
279
402
  case 'culled': {
280
- const culled = snapshot.culledNodes[selection.index];
403
+ const culled = snapshot.culledById.get(selection.id);
281
404
  if (culled)
282
405
  host.append(this.accessRelations('Reads', culled.node.reads), this.accessRelations('Writes', culled.node.writes));
283
406
  break;
@@ -292,7 +415,7 @@ export class InspectorView {
292
415
  break;
293
416
  }
294
417
  }
295
- if (host.childElementCount === 0) {
418
+ if (host.childElementCount === 1) {
296
419
  const empty = document.createElement('p');
297
420
  empty.className = 'zenfg-inspector-muted';
298
421
  empty.textContent = 'No related objects.';
@@ -302,8 +425,9 @@ export class InspectorView {
302
425
  }
303
426
  accessRelations(title, accesses) {
304
427
  return this.relationGroup(title, accesses.map((access) => [
305
- `${labelResource(access.resource)} · ${access.access}${access.mode === 'write' ? ` · ${access.contents}` : ''}`,
428
+ labelResource(access.resource),
306
429
  { kind: 'resource', id: access.resource.id },
430
+ formatAccessFacts(access),
307
431
  ]));
308
432
  }
309
433
  resourceRelations(title, resources) {
@@ -316,8 +440,23 @@ export class InspectorView {
316
440
  const heading = document.createElement('h3');
317
441
  heading.textContent = `${title} ${relations.length}`;
318
442
  section.appendChild(heading);
319
- for (const [label, selection] of relations) {
320
- section.appendChild(createRelationButton(label, selection, this.callbacks.onSelect));
443
+ for (const [label, selection, description] of relations) {
444
+ const link = this.createDetailLink(label, selection);
445
+ const entry = document.createElement('div');
446
+ entry.className = 'zenfg-inspector-relation-entry';
447
+ const links = document.createElement('div');
448
+ links.className = 'zenfg-inspector-relation-links';
449
+ links.appendChild(link);
450
+ if (this.callbacks.onReveal)
451
+ links.appendChild(this.revealButton(selection, this.primaryPage(selection)));
452
+ entry.appendChild(links);
453
+ if (description !== undefined) {
454
+ const metadata = document.createElement('span');
455
+ metadata.className = 'zenfg-inspector-muted';
456
+ metadata.textContent = description;
457
+ entry.appendChild(metadata);
458
+ }
459
+ section.appendChild(entry);
321
460
  }
322
461
  if (relations.length === 0) {
323
462
  const empty = document.createElement('span');
@@ -327,6 +466,31 @@ export class InspectorView {
327
466
  }
328
467
  return section;
329
468
  }
469
+ selectRelated(selection) {
470
+ this.clearLinkHover();
471
+ this.callbacks.onSelect(selection);
472
+ }
473
+ viewResourceButton(snapshot, resourceId) {
474
+ return this.createDetailLink(`View resource · ${resourceLabel(snapshot, resourceId)}`, { kind: 'resource', id: resourceId });
475
+ }
476
+ createDetailLink(label, selection) {
477
+ const link = createRelationButton(label, selection, (target) => this.selectRelated(target));
478
+ link.addEventListener('mouseenter', () => {
479
+ this.hoveredLink = link;
480
+ this.callbacks.onHover(selection);
481
+ });
482
+ link.addEventListener('mouseleave', () => {
483
+ if (this.hoveredLink === link)
484
+ this.clearLinkHover();
485
+ });
486
+ return link;
487
+ }
488
+ clearLinkHover() {
489
+ if (!this.hoveredLink)
490
+ return;
491
+ this.hoveredLink = undefined;
492
+ this.callbacks.onHover(undefined);
493
+ }
330
494
  summary(rows) {
331
495
  const summary = document.createElement('dl');
332
496
  summary.className = 'zenfg-inspector-inspector-summary';
@@ -334,8 +498,13 @@ export class InspectorView {
334
498
  const term = document.createElement('dt');
335
499
  term.textContent = label;
336
500
  const description = document.createElement('dd');
337
- description.textContent = value;
338
- description.title = value;
501
+ if (typeof value === 'string') {
502
+ description.textContent = value;
503
+ description.title = value;
504
+ }
505
+ else {
506
+ description.appendChild(value);
507
+ }
339
508
  summary.append(term, description);
340
509
  }
341
510
  return summary;
@@ -344,14 +513,125 @@ export class InspectorView {
344
513
  const node = snapshot.nodeById.get(nodeId);
345
514
  if (node)
346
515
  return labelNode(node);
347
- const culled = snapshot.culledNodes.find((candidate) => candidate.node.id === nodeId);
348
- return culled ? `${labelNode(culled.node)} (culled)` : `node-${nodeId}`;
516
+ const culled = snapshot.culledById.get(nodeId);
517
+ return culled ? `${labelNode(culled.node)} (culled)` : nodeId;
349
518
  }
350
519
  accessNodeSelection(snapshot, nodeId) {
351
- if (snapshot.nodeById.has(nodeId))
352
- return { kind: 'node', id: nodeId };
353
- const index = snapshot.culledNodes.findIndex((candidate) => candidate.node.id === nodeId);
354
- return index < 0 ? undefined : { kind: 'culled', index };
520
+ return resolveNodeSelection(snapshot, nodeId);
521
+ }
522
+ nodeLink(snapshot, id) {
523
+ const selected = resolveNodeSelection(snapshot, id);
524
+ return selected ? this.createDetailLink(this.accessNodeLabel(snapshot, id), selected) : id;
525
+ }
526
+ primaryPage(selection) {
527
+ switch (selection.kind) {
528
+ case 'node':
529
+ case 'culled':
530
+ case 'group': return 'passes';
531
+ case 'resource': return 'resources';
532
+ case 'allocation': return 'memory';
533
+ case 'root':
534
+ case 'segment': return 'diagnostics';
535
+ }
536
+ }
537
+ revealButton(selection, page) {
538
+ const button = document.createElement('button');
539
+ button.type = 'button';
540
+ button.className = 'zenfg-inspector-inline-action';
541
+ button.textContent = `Locate in ${page[0].toUpperCase()}${page.slice(1)}`;
542
+ button.addEventListener('click', () => {
543
+ this.clearLinkHover();
544
+ this.callbacks.onReveal?.(selection, page);
545
+ });
546
+ return button;
547
+ }
548
+ createLocationActions(snapshot, selection) {
549
+ const actions = document.createElement('div');
550
+ actions.className = 'zenfg-inspector-detail-actions';
551
+ if (!this.callbacks.onReveal)
552
+ return actions;
553
+ actions.appendChild(this.revealButton(selection, this.primaryPage(selection)));
554
+ if (selection.kind === 'node' || selection.kind === 'group' || selection.kind === 'resource' || selection.kind === 'root') {
555
+ actions.appendChild(this.revealButton(selection, 'graph'));
556
+ }
557
+ if (selection.kind === 'resource' && snapshot.resourceById.get(selection.id)?.origin === 'transient')
558
+ actions.appendChild(this.revealButton(selection, 'memory'));
559
+ if (selection.kind === 'culled') {
560
+ const note = document.createElement('span');
561
+ note.className = 'zenfg-inspector-muted';
562
+ note.textContent = 'Culled passes are absent from Graph. Locate them in Passes.';
563
+ actions.appendChild(note);
564
+ }
565
+ return actions;
566
+ }
567
+ selectionId(snapshot, selection) {
568
+ if ('id' in selection)
569
+ return selection.id;
570
+ if (selection.kind === 'group')
571
+ return snapshot.groupByPathKey.get(selection.pathKey)?.id;
572
+ if (selection.kind === 'segment')
573
+ return snapshot.segmentByIndex.get(selection.index)?.id;
574
+ return undefined;
575
+ }
576
+ copyId(id) {
577
+ const host = document.createElement('span');
578
+ host.className = 'zenfg-inspector-copy-id';
579
+ const code = document.createElement('code');
580
+ code.textContent = id;
581
+ const button = document.createElement('button');
582
+ button.type = 'button';
583
+ button.textContent = 'Copy ID';
584
+ button.setAttribute('aria-label', 'Copy full object ID');
585
+ button.addEventListener('click', () => {
586
+ void writeClipboardText(id).then(() => { button.textContent = 'Copied'; }, () => { button.textContent = 'Copy failed'; });
587
+ });
588
+ host.append(code, button);
589
+ return host;
590
+ }
591
+ createDiagnostics(snapshot, selection) {
592
+ let diagnostics = [];
593
+ if (selection.kind === 'node' || selection.kind === 'culled')
594
+ diagnostics = snapshot.diagnosticsByNodeId.get(selection.id) ?? [];
595
+ else if (selection.kind === 'resource')
596
+ diagnostics = snapshot.diagnosticsByResourceId.get(selection.id) ?? [];
597
+ else if (selection.kind === 'allocation') {
598
+ const resourceIds = new Set(snapshot.allocationById.get(selection.id)?.resourceIds);
599
+ diagnostics = snapshot.protocol.diagnostics.filter((diagnostic) => diagnostic.resourceId !== undefined && resourceIds.has(diagnostic.resourceId));
600
+ }
601
+ else if (selection.kind === 'group') {
602
+ const groupId = snapshot.groupByPathKey.get(selection.pathKey)?.id;
603
+ const groups = new Set(snapshot.debugGroups.filter((group) => groupId !== undefined && group.ancestorIds.includes(groupId)).map((group) => group.id));
604
+ diagnostics = snapshot.protocol.diagnostics.filter((diagnostic) => {
605
+ const nodeGroup = diagnostic.nodeId ? snapshot.canonicalNodeById.get(diagnostic.nodeId)?.groupId : undefined;
606
+ const resourceGroup = diagnostic.resourceId ? snapshot.resourceById.get(diagnostic.resourceId)?.debugGroupId : undefined;
607
+ return (nodeGroup !== undefined && groups.has(nodeGroup)) || (resourceGroup !== undefined && groups.has(resourceGroup));
608
+ });
609
+ }
610
+ if (!diagnostics.length)
611
+ return undefined;
612
+ const section = document.createElement('section');
613
+ section.className = 'zenfg-inspector-detail-diagnostics';
614
+ const heading = document.createElement('h3');
615
+ heading.textContent = `Related diagnostics ${diagnostics.length}`;
616
+ section.appendChild(heading);
617
+ for (const diagnostic of diagnostics) {
618
+ const entry = document.createElement('article');
619
+ entry.dataset.severity = diagnostic.severity;
620
+ const label = document.createElement('strong');
621
+ label.textContent = `${diagnostic.severity} · ${diagnostic.code}`;
622
+ const message = document.createElement('p');
623
+ message.textContent = diagnostic.message;
624
+ entry.append(label, message);
625
+ if (diagnostic.nodeId) {
626
+ const node = resolveNodeSelection(snapshot, diagnostic.nodeId);
627
+ if (node)
628
+ entry.appendChild(this.createDetailLink(this.accessNodeLabel(snapshot, diagnostic.nodeId), node));
629
+ }
630
+ if (diagnostic.resourceId)
631
+ entry.appendChild(this.viewResourceButton(snapshot, diagnostic.resourceId));
632
+ section.appendChild(entry);
633
+ }
634
+ return section;
355
635
  }
356
636
  }
357
637
  //# sourceMappingURL=panelInspectorView.js.map