@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
@@ -1,24 +1,29 @@
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";
5
6
  function formatAccessFacts(access) {
6
7
  return [
7
8
  access.mode, access.access,
8
- ...(access.mode === 'write' ? [access.contents, `producesValue: ${access.producesValue}`] : []),
9
+ ...(access.mode === 'write' ? [access.contents, access.producesValue ? 'produces a value' : 'does not produce a value'] : []),
9
10
  ...(access.bufferRange ? [`bytes ${access.bufferRange.offset}–${access.bufferRange.size === undefined ? 'end' : access.bufferRange.offset + access.bufferRange.size}`] : []),
10
- ...(access.textureRegion ? [JSON.stringify(access.textureRegion)] : []),
11
+ ...(access.textureRegion ? [formatTextureRegion(access.textureRegion)] : []),
11
12
  ...(access.textureViewId ? [`view ${access.textureViewId}`] : []),
12
13
  ].join(' · ');
13
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
+ }
14
21
  function formatRootRange(root) {
15
22
  if (!root.range)
16
23
  return 'Range unavailable';
17
24
  if (root.range.kind === 'buffer')
18
25
  return `bytes ${root.range.offset}–${root.range.offset + root.range.size}`;
19
- return root.range.regions.map((region) => `mip ${region.baseMipLevel}+${region.mipLevelCount} · ${region.baseDepthSlice === undefined
20
- ? `layers ${region.baseArrayLayer}+${region.arrayLayerCount}`
21
- : `depth ${region.baseDepthSlice}+${region.depthSliceCount}`} · ${region.aspect}`).join('; ');
26
+ return root.range.regions.map(formatTextureRegion).join('; ');
22
27
  }
23
28
  export class InspectorView {
24
29
  callbacks;
@@ -31,8 +36,9 @@ export class InspectorView {
31
36
  snapshot;
32
37
  selected;
33
38
  activeTab = 'summary';
34
- open = true;
39
+ open = false;
35
40
  hoveredLink;
41
+ rawCache;
36
42
  constructor(callbacks, onOpenChange, idPrefix) {
37
43
  this.callbacks = callbacks;
38
44
  this.onOpenChange = onOpenChange;
@@ -65,6 +71,7 @@ export class InspectorView {
65
71
  this.tabs.set(tab, button);
66
72
  this.tabList.appendChild(button);
67
73
  }
74
+ enableTabKeyboard(this.tabList);
68
75
  this.content.id = `${idPrefix}-inspector-panel`;
69
76
  this.content.className = 'zenfg-inspector-inspector-content';
70
77
  this.content.setAttribute('role', 'tabpanel');
@@ -73,16 +80,24 @@ export class InspectorView {
73
80
  this.render();
74
81
  }
75
82
  setSnapshot(snapshot) {
83
+ if (this.snapshot !== snapshot)
84
+ this.rawCache = undefined;
76
85
  this.snapshot = snapshot;
77
86
  this.render();
78
87
  }
79
88
  setSelection(selected, reveal = true) {
89
+ if (!selected || !this.selected || selectionKey(selected) !== selectionKey(this.selected))
90
+ this.rawCache = undefined;
80
91
  if (selected?.kind === 'resource'
81
92
  && (this.selected?.kind !== 'resource' || this.selected.id !== selected.id))
82
93
  this.activeTab = 'summary';
83
94
  this.selected = selected;
84
- if (selected && reveal)
95
+ if (!selected)
96
+ this.setOpen(false);
97
+ else if (reveal && !this.open) {
85
98
  this.setOpen(true);
99
+ return;
100
+ }
86
101
  this.render();
87
102
  }
88
103
  setOpen(open) {
@@ -92,6 +107,8 @@ export class InspectorView {
92
107
  this.clearLinkHover();
93
108
  this.open = open;
94
109
  this.updateOpenState();
110
+ if (open)
111
+ this.render();
95
112
  this.onOpenChange(open);
96
113
  }
97
114
  get isOpen() {
@@ -102,11 +119,13 @@ export class InspectorView {
102
119
  this.root.classList.toggle('open', this.open);
103
120
  }
104
121
  render() {
122
+ const restoreFocus = this.content.contains(document.activeElement);
105
123
  this.clearLinkHover();
106
124
  for (const [tab, button] of this.tabs) {
107
125
  const active = tab === this.activeTab;
108
126
  button.classList.toggle('active', active);
109
127
  button.setAttribute('aria-selected', active ? 'true' : 'false');
128
+ button.tabIndex = active ? 0 : -1;
110
129
  if (active)
111
130
  this.content.setAttribute('aria-labelledby', button.id);
112
131
  }
@@ -122,33 +141,59 @@ export class InspectorView {
122
141
  return;
123
142
  }
124
143
  this.title.textContent = this.selectionTitle(snapshot, selected);
144
+ if (!this.open)
145
+ return;
125
146
  if (this.activeTab === 'raw') {
126
- const raw = document.createElement('pre');
127
- raw.className = 'zenfg-inspector-raw-detail';
128
- raw.textContent = JSON.stringify(resolveSelectedDetail(snapshot, selected), null, 2);
129
- 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.';
130
156
  }
131
157
  else if (this.activeTab === 'relations') {
132
158
  this.content.appendChild(this.createRelations(snapshot, selected));
133
159
  }
134
160
  else {
135
- 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
+ }
136
178
  }
179
+ // Selecting a relation replaces its button. Keep keyboard focus in the pane.
180
+ if (restoreFocus)
181
+ this.tabs.get(this.activeTab)?.focus();
137
182
  }
138
183
  selectionTitle(snapshot, selection) {
139
184
  switch (selection.kind) {
140
185
  case 'node': {
141
186
  const node = snapshot.nodeById.get(selection.id);
142
- return node ? labelNode(node) : `Pass #${selection.id}`;
187
+ return node ? labelNode(node) : selection.id;
143
188
  }
144
189
  case 'group': return snapshot.groupByPathKey.get(selection.pathKey)?.label ?? 'Group';
145
190
  case 'resource': {
146
191
  const resource = snapshot.resourceById.get(selection.id);
147
- return resource ? labelResource(resource) : `Resource #${selection.id}`;
192
+ return resource ? labelResource(resource) : selection.id;
148
193
  }
149
- case 'allocation': return `Allocation #${selection.id}`;
194
+ case 'allocation': return /^allocation[:#-]/i.test(selection.id) ? selection.id : `Allocation ${selection.id}`;
150
195
  case 'root': return 'Output root';
151
- case 'culled': return `Culled node #${selection.index}`;
196
+ case 'culled': return `${snapshot.culledById.get(selection.id)?.node.label ?? selection.id} (culled)`;
152
197
  case 'segment': return `Segment #${selection.index}`;
153
198
  }
154
199
  }
@@ -159,12 +204,21 @@ export class InspectorView {
159
204
  if (!node)
160
205
  return this.summary([]);
161
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));
162
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'],
163
211
  ['Kind', node.kind],
164
- ['Order', String(node.order)],
212
+ ['Execution slot', String(node.order)],
165
213
  ['Group', groupPath(snapshot, node.debugGroupId)],
166
214
  ['Segment', segment ? `#${segment.index} ${segment.kind}` : '-'],
167
- ['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.'],
168
222
  ['Accesses', `${node.reads.length} reads · ${node.writes.length} writes`],
169
223
  ['Side effect', node.sideEffect ? 'yes' : 'no'],
170
224
  ]);
@@ -177,9 +231,12 @@ export class InspectorView {
177
231
  ['Path', group.path.join(' / ')],
178
232
  ['Retained', String(group.summary.retainedNodeCount)],
179
233
  ['Culled', String(group.summary.culledNodeCount)],
180
- ['GPU coverage', `${group.summary.timedNodeCount}/${group.summary.timingEligibleNodeCount}`],
181
- ['GPU work', `${(group.summary.gpuWorkDurationMicros / 1000).toFixed(3)} ms`],
182
- ['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'],
183
240
  ['Segments', String(group.summary.executionSegmentCount)],
184
241
  ]);
185
242
  }
@@ -191,9 +248,14 @@ export class InspectorView {
191
248
  ['Kind / origin', `${resource.kind} · ${resource.origin}`],
192
249
  ['Group', groupPath(snapshot, resource.debugGroupId)],
193
250
  ['Descriptor', formatResourceDescriptor(resource)],
194
- ['Estimated', formatEstimatedBytes(resource.estimatedByteSize)],
195
- ['Lifetime', resource.lifetime ? `${resource.lifetime.firstUse}–${resource.lifetime.lastUse}` : '-'],
196
- ['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'],
197
259
  ['Usage', resource.usageFlags.join(', ') || '-'],
198
260
  ]);
199
261
  }
@@ -204,7 +266,7 @@ export class InspectorView {
204
266
  return this.summary([
205
267
  ['Kind', allocation.kind],
206
268
  ['Compatibility class', String(allocation.compatibilityClassId)],
207
- ['Estimated', formatEstimatedBytes(allocation.estimatedByteSize)],
269
+ ['Physical estimate', `${formatEstimatedBytes(allocation.estimatedByteSize)} · allocation capacity estimate`],
208
270
  ['Logical resources', String(allocation.resourceIds.length)],
209
271
  ['Alias', allocation.resourceIds.length > 1 ? `yes · ×${allocation.resourceIds.length}` : 'single'],
210
272
  ]);
@@ -213,22 +275,24 @@ export class InspectorView {
213
275
  const root = snapshot.roots.find((root) => root.key === selection.key);
214
276
  return this.summary(root ? [
215
277
  ['Reason', root.reason],
216
- ['Node', root.nodeId === undefined ? '-' : `#${root.nodeId}`],
278
+ ['Node', root.nodeId === undefined ? 'Not applicable' : this.nodeLink(snapshot, root.nodeId)],
217
279
  ['Resource', root.resource ? this.viewResourceButton(snapshot, root.resource.id) : '-'],
218
280
  ...(root.reason === 'side-effect' ? [] : [
219
- ['Range', root.range ? JSON.stringify(root.range) : 'Unavailable in Legacy capture'],
281
+ ['Range', root.range ? formatRootRange(root) : 'Unavailable in Legacy capture'],
220
282
  ['Initial contents', root.resolution ? String(root.resolution.usesInitialContents) : 'Unavailable in Legacy capture'],
221
283
  ['Producers', root.resolution ? root.resolution.producerNodeIds.join(', ') || 'None' : 'Unavailable in Legacy capture'],
222
284
  ]),
223
285
  ] : []);
224
286
  }
225
287
  case 'culled': {
226
- const culled = snapshot.culledNodes[selection.index];
288
+ const culled = snapshot.culledById.get(selection.id);
227
289
  return this.summary(culled ? [
290
+ ['Compile state', 'Culled · excluded by the compiler'],
228
291
  ['Node', labelNode(culled.node)],
229
292
  ['Kind', culled.node.kind],
230
293
  ['Group', groupPath(snapshot, culled.node.debugGroupId)],
231
294
  ['Reason', culled.reason],
295
+ ['Execution / segment / CPU / GPU', 'Not applicable · culled passes do not execute'],
232
296
  ['Accesses', `${culled.node.reads.length} reads · ${culled.node.writes.length} writes`],
233
297
  ] : []);
234
298
  }
@@ -247,6 +311,7 @@ export class InspectorView {
247
311
  createRelations(snapshot, selection) {
248
312
  const host = document.createElement('div');
249
313
  host.className = 'zenfg-inspector-inspector-relations';
314
+ host.appendChild(this.createLocationActions(snapshot, selection));
250
315
  switch (selection.kind) {
251
316
  case 'node': {
252
317
  const node = snapshot.nodeById.get(selection.id);
@@ -257,6 +322,21 @@ export class InspectorView {
257
322
  host.appendChild(this.relationGroup('Segment', [
258
323
  [`#${segment.index} ${segment.kind}`, { kind: 'segment', index: segment.index }],
259
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
+ }
260
340
  }
261
341
  break;
262
342
  }
@@ -264,7 +344,7 @@ export class InspectorView {
264
344
  const group = snapshot.groupByPathKey.get(selection.pathKey);
265
345
  if (group) {
266
346
  host.append(this.resourceRelations('Inputs', group.summary.inputResources), this.resourceRelations('Outputs', group.summary.outputResources), this.relationGroup('Output roots', group.summary.outputRoots.map((root) => [
267
- `${root.resource ? labelResource(root.resource) : '-'} · ${root.reason} · ${root.range ? JSON.stringify(root.range) : 'Range unavailable'}`,
347
+ `${root.resource ? labelResource(root.resource) : '-'} · ${root.reason} · ${formatRootRange(root)}`,
268
348
  { kind: 'root', key: root.key },
269
349
  ])));
270
350
  }
@@ -291,7 +371,7 @@ export class InspectorView {
291
371
  ])));
292
372
  if (resource?.physicalResourceId !== undefined)
293
373
  host.appendChild(this.relationGroup('Allocation', [
294
- [`#${resource.physicalResourceId}`, { kind: 'allocation', id: resource.physicalResourceId }],
374
+ [resource.physicalResourceId, { kind: 'allocation', id: resource.physicalResourceId }],
295
375
  ]));
296
376
  break;
297
377
  }
@@ -320,7 +400,7 @@ export class InspectorView {
320
400
  break;
321
401
  }
322
402
  case 'culled': {
323
- const culled = snapshot.culledNodes[selection.index];
403
+ const culled = snapshot.culledById.get(selection.id);
324
404
  if (culled)
325
405
  host.append(this.accessRelations('Reads', culled.node.reads), this.accessRelations('Writes', culled.node.writes));
326
406
  break;
@@ -335,7 +415,7 @@ export class InspectorView {
335
415
  break;
336
416
  }
337
417
  }
338
- if (host.childElementCount === 0) {
418
+ if (host.childElementCount === 1) {
339
419
  const empty = document.createElement('p');
340
420
  empty.className = 'zenfg-inspector-muted';
341
421
  empty.textContent = 'No related objects.';
@@ -362,18 +442,21 @@ export class InspectorView {
362
442
  section.appendChild(heading);
363
443
  for (const [label, selection, description] of relations) {
364
444
  const link = this.createDetailLink(label, selection);
365
- if (description === undefined) {
366
- section.appendChild(link);
367
- }
368
- else {
369
- const entry = document.createElement('div');
370
- entry.className = 'zenfg-inspector-relation-entry';
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) {
371
454
  const metadata = document.createElement('span');
372
455
  metadata.className = 'zenfg-inspector-muted';
373
456
  metadata.textContent = description;
374
- entry.append(link, metadata);
375
- section.appendChild(entry);
457
+ entry.appendChild(metadata);
376
458
  }
459
+ section.appendChild(entry);
377
460
  }
378
461
  if (relations.length === 0) {
379
462
  const empty = document.createElement('span');
@@ -430,14 +513,125 @@ export class InspectorView {
430
513
  const node = snapshot.nodeById.get(nodeId);
431
514
  if (node)
432
515
  return labelNode(node);
433
- const culled = snapshot.culledNodes.find((candidate) => candidate.node.id === nodeId);
434
- return culled ? `${labelNode(culled.node)} (culled)` : `node-${nodeId}`;
516
+ const culled = snapshot.culledById.get(nodeId);
517
+ return culled ? `${labelNode(culled.node)} (culled)` : nodeId;
435
518
  }
436
519
  accessNodeSelection(snapshot, nodeId) {
437
- if (snapshot.nodeById.has(nodeId))
438
- return { kind: 'node', id: nodeId };
439
- const index = snapshot.culledNodes.findIndex((candidate) => candidate.node.id === nodeId);
440
- 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;
441
635
  }
442
636
  }
443
637
  //# sourceMappingURL=panelInspectorView.js.map