@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,12 +6,17 @@ import type {
6
6
  } from './debugCaptureModel.ts';
7
7
  import { labelNode, labelResource } from './panelDomHelpers.ts';
8
8
  import { createPanelIcon } from './panelIcons.ts';
9
- import { resolveSelectedDetail } from './panelSelection.ts';
10
- import type { InspectorTab, Selection } from './panelTypes.ts';
9
+ import { RawDetailView } from './panelRawView.ts';
10
+ import { resolveNodeSelection, resolveSelectedCanonicalDetail } from './panelSelection.ts';
11
+ import type { InspectorTab, Selection, WorkbenchTab } from './panelTypes.ts';
11
12
  import {
12
13
  createRelationButton,
13
14
  formatEstimatedBytes,
14
15
  formatResourceDescriptor,
16
+ formatTimingCoverage,
17
+ enableTabKeyboard,
18
+ writeClipboardText,
19
+ selectionKey,
15
20
  groupPath,
16
21
  resourceLabel,
17
22
  type WorkbenchCallbacks,
@@ -22,21 +27,24 @@ type DetailRelation = readonly [label: string, selection: Selection, description
22
27
  function formatAccessFacts(access: FrameGraphDebugAccess): string {
23
28
  return [
24
29
  access.mode, access.access,
25
- ...(access.mode === 'write' ? [access.contents, `producesValue: ${access.producesValue}`] : []),
30
+ ...(access.mode === 'write' ? [access.contents, access.producesValue ? 'produces a value' : 'does not produce a value'] : []),
26
31
  ...(access.bufferRange ? [`bytes ${access.bufferRange.offset}–${access.bufferRange.size === undefined ? 'end' : access.bufferRange.offset + access.bufferRange.size}`] : []),
27
- ...(access.textureRegion ? [JSON.stringify(access.textureRegion)] : []),
32
+ ...(access.textureRegion ? [formatTextureRegion(access.textureRegion)] : []),
28
33
  ...(access.textureViewId ? [`view ${access.textureViewId}`] : []),
29
34
  ].join(' · ');
30
35
  }
31
36
 
37
+ function formatTextureRegion(region: NonNullable<FrameGraphDebugAccess['textureRegion']>): string {
38
+ const end = (start: number | undefined, count: number | undefined) => start === undefined ? 'unknown' : count === undefined ? `${start}–end` : `${start}–${start + count - 1}`;
39
+ return `mip ${end(region.baseMipLevel, region.mipLevelCount)} · ${region.baseDepthSlice === undefined
40
+ ? `layers ${end(region.baseArrayLayer, region.arrayLayerCount)}`
41
+ : `depth ${end(region.baseDepthSlice, region.depthSliceCount)}`} · aspect ${region.aspect}`;
42
+ }
43
+
32
44
  function formatRootRange(root: FrameGraphDebugRoot): string {
33
45
  if (!root.range) return 'Range unavailable';
34
46
  if (root.range.kind === 'buffer') return `bytes ${root.range.offset}–${root.range.offset + root.range.size}`;
35
- return root.range.regions.map((region) =>
36
- `mip ${region.baseMipLevel}+${region.mipLevelCount} · ${region.baseDepthSlice === undefined
37
- ? `layers ${region.baseArrayLayer}+${region.arrayLayerCount}`
38
- : `depth ${region.baseDepthSlice}+${region.depthSliceCount}`} · ${region.aspect}`,
39
- ).join('; ');
47
+ return root.range.regions.map(formatTextureRegion).join('; ');
40
48
  }
41
49
 
42
50
  export class InspectorView {
@@ -48,8 +56,9 @@ export class InspectorView {
48
56
  private snapshot: FrameGraphDebugViewModel | undefined;
49
57
  private selected: Selection | undefined;
50
58
  private activeTab: InspectorTab = 'summary';
51
- private open = true;
59
+ private open = false;
52
60
  private hoveredLink: HTMLButtonElement | undefined;
61
+ private rawCache: { readonly snapshot: FrameGraphDebugViewModel; readonly key: string; readonly view: RawDetailView } | undefined;
53
62
 
54
63
  constructor(
55
64
  private readonly callbacks: WorkbenchCallbacks,
@@ -86,6 +95,7 @@ export class InspectorView {
86
95
  this.tabs.set(tab, button);
87
96
  this.tabList.appendChild(button);
88
97
  }
98
+ enableTabKeyboard(this.tabList);
89
99
  this.content.id = `${idPrefix}-inspector-panel`;
90
100
  this.content.className = 'zenfg-inspector-inspector-content';
91
101
  this.content.setAttribute('role', 'tabpanel');
@@ -95,15 +105,21 @@ export class InspectorView {
95
105
  }
96
106
 
97
107
  setSnapshot(snapshot: FrameGraphDebugViewModel): void {
108
+ if (this.snapshot !== snapshot) this.rawCache = undefined;
98
109
  this.snapshot = snapshot;
99
110
  this.render();
100
111
  }
101
112
 
102
113
  setSelection(selected: Selection | undefined, reveal = true): void {
114
+ if (!selected || !this.selected || selectionKey(selected) !== selectionKey(this.selected)) this.rawCache = undefined;
103
115
  if (selected?.kind === 'resource'
104
116
  && (this.selected?.kind !== 'resource' || this.selected.id !== selected.id)) this.activeTab = 'summary';
105
117
  this.selected = selected;
106
- if (selected && reveal) this.setOpen(true);
118
+ if (!selected) this.setOpen(false);
119
+ else if (reveal && !this.open) {
120
+ this.setOpen(true);
121
+ return;
122
+ }
107
123
  this.render();
108
124
  }
109
125
 
@@ -112,6 +128,7 @@ export class InspectorView {
112
128
  if (!open) this.clearLinkHover();
113
129
  this.open = open;
114
130
  this.updateOpenState();
131
+ if (open) this.render();
115
132
  this.onOpenChange(open);
116
133
  }
117
134
 
@@ -125,11 +142,13 @@ export class InspectorView {
125
142
  }
126
143
 
127
144
  private render(): void {
145
+ const restoreFocus = this.content.contains(document.activeElement);
128
146
  this.clearLinkHover();
129
147
  for (const [tab, button] of this.tabs) {
130
148
  const active = tab === this.activeTab;
131
149
  button.classList.toggle('active', active);
132
150
  button.setAttribute('aria-selected', active ? 'true' : 'false');
151
+ button.tabIndex = active ? 0 : -1;
133
152
  if (active) this.content.setAttribute('aria-labelledby', button.id);
134
153
  }
135
154
  const snapshot = this.snapshot;
@@ -144,32 +163,52 @@ export class InspectorView {
144
163
  return;
145
164
  }
146
165
  this.title.textContent = this.selectionTitle(snapshot, selected);
166
+ if (!this.open) return;
147
167
  if (this.activeTab === 'raw') {
148
- const raw = document.createElement('pre');
149
- raw.className = 'zenfg-inspector-raw-detail';
150
- raw.textContent = JSON.stringify(resolveSelectedDetail(snapshot, selected), null, 2);
151
- this.content.appendChild(raw);
168
+ const key = selectionKey(selected);
169
+ if (this.rawCache?.snapshot !== snapshot || this.rawCache.key !== key) {
170
+ const detail = resolveSelectedCanonicalDetail(snapshot, selected);
171
+ this.rawCache = detail ? { snapshot, key, view: new RawDetailView(detail, Boolean(snapshot.protocol.capture.migration)) } : undefined;
172
+ }
173
+ if (this.rawCache) this.content.appendChild(this.rawCache.view.root);
174
+ else this.content.textContent = 'Canonical object unavailable in this capture.';
152
175
  } else if (this.activeTab === 'relations') {
153
176
  this.content.appendChild(this.createRelations(snapshot, selected));
154
177
  } else {
155
- this.content.appendChild(this.createSummary(snapshot, selected));
178
+ this.content.append(this.createLocationActions(snapshot, selected), this.createSummary(snapshot, selected));
179
+ const identity = this.selectionId(snapshot, selected);
180
+ if (identity) this.content.appendChild(this.summary([['ID', this.copyId(identity)]]));
181
+ const diagnostics = this.createDiagnostics(snapshot, selected);
182
+ if (diagnostics) this.content.appendChild(diagnostics);
183
+ if (selected.kind === 'node' || selected.kind === 'culled') {
184
+ const node = snapshot.nodeById.get(selected.id) ?? snapshot.culledById.get(selected.id)?.node;
185
+ if (node) {
186
+ const accesses = document.createElement('details');
187
+ const heading = document.createElement('summary');
188
+ heading.textContent = `Access facts · ${node.reads.length + node.writes.length}`;
189
+ accesses.append(heading, this.accessRelations('Reads', node.reads), this.accessRelations('Writes', node.writes));
190
+ this.content.appendChild(accesses);
191
+ }
192
+ }
156
193
  }
194
+ // Selecting a relation replaces its button. Keep keyboard focus in the pane.
195
+ if (restoreFocus) this.tabs.get(this.activeTab)?.focus();
157
196
  }
158
197
 
159
198
  private selectionTitle(snapshot: FrameGraphDebugViewModel, selection: Selection): string {
160
199
  switch (selection.kind) {
161
200
  case 'node': {
162
201
  const node = snapshot.nodeById.get(selection.id);
163
- return node ? labelNode(node) : `Pass #${selection.id}`;
202
+ return node ? labelNode(node) : selection.id;
164
203
  }
165
204
  case 'group': return snapshot.groupByPathKey.get(selection.pathKey)?.label ?? 'Group';
166
205
  case 'resource': {
167
206
  const resource = snapshot.resourceById.get(selection.id);
168
- return resource ? labelResource(resource) : `Resource #${selection.id}`;
207
+ return resource ? labelResource(resource) : selection.id;
169
208
  }
170
- case 'allocation': return `Allocation #${selection.id}`;
209
+ case 'allocation': return /^allocation[:#-]/i.test(selection.id) ? selection.id : `Allocation ${selection.id}`;
171
210
  case 'root': return 'Output root';
172
- case 'culled': return `Culled node #${selection.index}`;
211
+ case 'culled': return `${snapshot.culledById.get(selection.id)?.node.label ?? selection.id} (culled)`;
173
212
  case 'segment': return `Segment #${selection.index}`;
174
213
  }
175
214
  }
@@ -180,12 +219,21 @@ export class InspectorView {
180
219
  const node = snapshot.nodeById.get(selection.id);
181
220
  if (!node) return this.summary([]);
182
221
  const segment = snapshot.segmentByNodeId.get(node.id);
222
+ const roots = snapshot.roots.filter((root) => root.nodeId === node.id || root.resolution?.producerNodeIds.includes(node.id));
183
223
  return this.summary([
224
+ ['Compile state', 'Retained · compiler included this pass; execution success is not implied'],
225
+ ['Retention', roots.length ? roots.map((root) => root.reason).join(', ') : 'No direct output root recorded; inspect dependencies in Relations'],
184
226
  ['Kind', node.kind],
185
- ['Order', String(node.order)],
227
+ ['Execution slot', String(node.order)],
186
228
  ['Group', groupPath(snapshot, node.debugGroupId)],
187
229
  ['Segment', segment ? `#${segment.index} ${segment.kind}` : '-'],
188
- ['GPU', node.kind === 'external-submission' ? 'Opaque' : node.gpuDurationMicros === undefined ? 'Not timed' : `${(node.gpuDurationMicros / 1000).toFixed(3)} ms`],
230
+ ['CPU duration', node.cpuDurationMicros === undefined ? 'Not collected' : (node.cpuDurationMicros / 1000).toFixed(3) + ' ms'],
231
+ ['CPU scope', 'Synchronous elapsed time; zero may reflect clock precision.' + (node.kind === 'external-submission' ? ' Includes synchronous external submission.' : '')],
232
+ ['GPU duration', node.kind === 'external-submission' ? 'Opaque · external work is not measured'
233
+ : node.kind !== 'render' && node.kind !== 'compute' ? 'Not applicable · this pass kind is not timing eligible'
234
+ : node.gpuDurationMicros === undefined ? `Not collected${snapshot.profiling.status === 'unavailable' ? ` · ${snapshot.profiling.reason}` : ''}`
235
+ : `${(node.gpuDurationMicros / 1000).toFixed(3)} ms`],
236
+ ['GPU scope', 'GPU timestamp interval for render/compute passes; excludes external work. Zero may reflect timestamp precision.'],
189
237
  ['Accesses', `${node.reads.length} reads · ${node.writes.length} writes`],
190
238
  ['Side effect', node.sideEffect ? 'yes' : 'no'],
191
239
  ]);
@@ -197,9 +245,12 @@ export class InspectorView {
197
245
  ['Path', group.path.join(' / ')],
198
246
  ['Retained', String(group.summary.retainedNodeCount)],
199
247
  ['Culled', String(group.summary.culledNodeCount)],
200
- ['GPU coverage', `${group.summary.timedNodeCount}/${group.summary.timingEligibleNodeCount}`],
201
- ['GPU work', `${(group.summary.gpuWorkDurationMicros / 1000).toFixed(3)} ms`],
202
- ['Allocations', String(group.summary.physicalAllocationCount)],
248
+ ['CPU coverage', formatTimingCoverage(group.summary.cpuTimedNodeCount, group.summary.retainedNodeCount)],
249
+ ['CPU pass sum', group.summary.cpuTimedNodeCount ? (group.summary.cpuWorkDurationMicros / 1000).toFixed(3) + ' ms' : 'Not collected'],
250
+ ['GPU coverage', formatTimingCoverage(group.summary.timedNodeCount, group.summary.timingEligibleNodeCount)],
251
+ ['GPU pass sum', group.summary.timingEligibleNodeCount === 0 ? 'Not applicable' : group.summary.timedNodeCount === 0 ? 'Not collected' : (group.summary.gpuWorkDurationMicros / 1000).toFixed(3) + ' ms'],
252
+ ['Opaque passes', `${group.summary.externalSubmissionCount} · excluded from GPU pass sum`],
253
+ ['Allocations', snapshot.protocol.memory.allocationReport.status === 'available' ? String(group.summary.physicalAllocationCount) : 'Unknown · allocation report unavailable'],
203
254
  ['Segments', String(group.summary.executionSegmentCount)],
204
255
  ]);
205
256
  }
@@ -210,9 +261,14 @@ export class InspectorView {
210
261
  ['Kind / origin', `${resource.kind} · ${resource.origin}`],
211
262
  ['Group', groupPath(snapshot, resource.debugGroupId)],
212
263
  ['Descriptor', formatResourceDescriptor(resource)],
213
- ['Estimated', formatEstimatedBytes(resource.estimatedByteSize)],
214
- ['Lifetime', resource.lifetime ? `${resource.lifetime.firstUse}–${resource.lifetime.lastUse}` : '-'],
215
- ['Allocation', resource.physicalResourceId === undefined ? '-' : `#${resource.physicalResourceId}`],
264
+ ['Resource estimate', `${formatEstimatedBytes(resource.estimatedByteSize)} · logical descriptor estimate`],
265
+ ['Lifetime', resource.lifetime ? `execution slots ${resource.lifetime.firstUse}–${resource.lifetime.lastUse} (inclusive)` : 'Unavailable'],
266
+ ['Allocation', resource.physicalResourceId !== undefined
267
+ ? this.createDetailLink(resource.physicalResourceId, { kind: 'allocation', id: resource.physicalResourceId })
268
+ : resource.origin !== 'transient' ? 'Not applicable · externally owned resource'
269
+ : snapshot.protocol.memory.allocationReport.status === 'unavailable' ? 'Unavailable · allocation report not captured'
270
+ : 'Unallocated'],
271
+ ['Initial contents', resource.initialContents ?? 'Unknown'],
216
272
  ['Usage', resource.usageFlags.join(', ') || '-'],
217
273
  ]);
218
274
  }
@@ -222,7 +278,7 @@ export class InspectorView {
222
278
  return this.summary([
223
279
  ['Kind', allocation.kind],
224
280
  ['Compatibility class', String(allocation.compatibilityClassId)],
225
- ['Estimated', formatEstimatedBytes(allocation.estimatedByteSize)],
281
+ ['Physical estimate', `${formatEstimatedBytes(allocation.estimatedByteSize)} · allocation capacity estimate`],
226
282
  ['Logical resources', String(allocation.resourceIds.length)],
227
283
  ['Alias', allocation.resourceIds.length > 1 ? `yes · ×${allocation.resourceIds.length}` : 'single'],
228
284
  ]);
@@ -231,22 +287,24 @@ export class InspectorView {
231
287
  const root = snapshot.roots.find((root) => root.key === selection.key);
232
288
  return this.summary(root ? [
233
289
  ['Reason', root.reason],
234
- ['Node', root.nodeId === undefined ? '-' : `#${root.nodeId}`],
290
+ ['Node', root.nodeId === undefined ? 'Not applicable' : this.nodeLink(snapshot, root.nodeId)],
235
291
  ['Resource', root.resource ? this.viewResourceButton(snapshot, root.resource.id) : '-'],
236
292
  ...(root.reason === 'side-effect' ? [] : [
237
- ['Range', root.range ? JSON.stringify(root.range) : 'Unavailable in Legacy capture'],
293
+ ['Range', root.range ? formatRootRange(root) : 'Unavailable in Legacy capture'],
238
294
  ['Initial contents', root.resolution ? String(root.resolution.usesInitialContents) : 'Unavailable in Legacy capture'],
239
295
  ['Producers', root.resolution ? root.resolution.producerNodeIds.join(', ') || 'None' : 'Unavailable in Legacy capture'],
240
296
  ] as [string, string][]),
241
297
  ] : []);
242
298
  }
243
299
  case 'culled': {
244
- const culled = snapshot.culledNodes[selection.index];
300
+ const culled = snapshot.culledById.get(selection.id);
245
301
  return this.summary(culled ? [
302
+ ['Compile state', 'Culled · excluded by the compiler'],
246
303
  ['Node', labelNode(culled.node)],
247
304
  ['Kind', culled.node.kind],
248
305
  ['Group', groupPath(snapshot, culled.node.debugGroupId)],
249
306
  ['Reason', culled.reason],
307
+ ['Execution / segment / CPU / GPU', 'Not applicable · culled passes do not execute'],
250
308
  ['Accesses', `${culled.node.reads.length} reads · ${culled.node.writes.length} writes`],
251
309
  ] : []);
252
310
  }
@@ -266,6 +324,7 @@ export class InspectorView {
266
324
  private createRelations(snapshot: FrameGraphDebugViewModel, selection: Selection): HTMLElement {
267
325
  const host = document.createElement('div');
268
326
  host.className = 'zenfg-inspector-inspector-relations';
327
+ host.appendChild(this.createLocationActions(snapshot, selection));
269
328
  switch (selection.kind) {
270
329
  case 'node': {
271
330
  const node = snapshot.nodeById.get(selection.id);
@@ -278,6 +337,18 @@ export class InspectorView {
278
337
  if (segment) host.appendChild(this.relationGroup('Segment', [
279
338
  [`#${segment.index} ${segment.kind}`, { kind: 'segment', index: segment.index }],
280
339
  ]));
340
+ for (const kind of ['value', 'ordering'] as const) {
341
+ const relations: DetailRelation[] = [];
342
+ for (const edge of snapshot.edges) {
343
+ if (edge.kind !== kind || (edge.fromNodeId !== node.id && edge.toNodeId !== node.id)) continue;
344
+ const upstream = edge.toNodeId === node.id;
345
+ const otherId = upstream ? edge.fromNodeId : edge.toNodeId;
346
+ const other = resolveNodeSelection(snapshot, otherId);
347
+ if (other) relations.push([this.accessNodeLabel(snapshot, otherId), other,
348
+ `${upstream ? 'Upstream' : 'Downstream'} · ${resourceLabel(snapshot, edge.resource.id)} · ${kind} dependency`]);
349
+ }
350
+ if (relations.length) host.appendChild(this.relationGroup(kind === 'value' ? 'Value dependencies' : 'Ordering dependencies', relations));
351
+ }
281
352
  }
282
353
  break;
283
354
  }
@@ -288,7 +359,7 @@ export class InspectorView {
288
359
  this.resourceRelations('Inputs', group.summary.inputResources),
289
360
  this.resourceRelations('Outputs', group.summary.outputResources),
290
361
  this.relationGroup('Output roots', group.summary.outputRoots.map((root) => [
291
- `${root.resource ? labelResource(root.resource) : '-'} · ${root.reason} · ${root.range ? JSON.stringify(root.range) : 'Range unavailable'}`,
362
+ `${root.resource ? labelResource(root.resource) : '-'} · ${root.reason} · ${formatRootRange(root)}`,
292
363
  { kind: 'root', key: root.key },
293
364
  ])),
294
365
  );
@@ -313,7 +384,7 @@ export class InspectorView {
313
384
  `${root.reason} · ${formatRootRange(root)}`, { kind: 'root', key: root.key },
314
385
  ])));
315
386
  if (resource?.physicalResourceId !== undefined) host.appendChild(this.relationGroup('Allocation', [
316
- [`#${resource.physicalResourceId}`, { kind: 'allocation', id: resource.physicalResourceId }],
387
+ [resource.physicalResourceId, { kind: 'allocation', id: resource.physicalResourceId }],
317
388
  ]));
318
389
  break;
319
390
  }
@@ -337,7 +408,7 @@ export class InspectorView {
337
408
  break;
338
409
  }
339
410
  case 'culled': {
340
- const culled = snapshot.culledNodes[selection.index];
411
+ const culled = snapshot.culledById.get(selection.id);
341
412
  if (culled) host.append(
342
413
  this.accessRelations('Reads', culled.node.reads),
343
414
  this.accessRelations('Writes', culled.node.writes),
@@ -353,7 +424,7 @@ export class InspectorView {
353
424
  break;
354
425
  }
355
426
  }
356
- if (host.childElementCount === 0) {
427
+ if (host.childElementCount === 1) {
357
428
  const empty = document.createElement('p');
358
429
  empty.className = 'zenfg-inspector-muted';
359
430
  empty.textContent = 'No related objects.';
@@ -383,17 +454,20 @@ export class InspectorView {
383
454
  section.appendChild(heading);
384
455
  for (const [label, selection, description] of relations) {
385
456
  const link = this.createDetailLink(label, selection);
386
- if (description === undefined) {
387
- section.appendChild(link);
388
- } else {
389
- const entry = document.createElement('div');
390
- entry.className = 'zenfg-inspector-relation-entry';
457
+ const entry = document.createElement('div');
458
+ entry.className = 'zenfg-inspector-relation-entry';
459
+ const links = document.createElement('div');
460
+ links.className = 'zenfg-inspector-relation-links';
461
+ links.appendChild(link);
462
+ if (this.callbacks.onReveal) links.appendChild(this.revealButton(selection, this.primaryPage(selection)));
463
+ entry.appendChild(links);
464
+ if (description !== undefined) {
391
465
  const metadata = document.createElement('span');
392
466
  metadata.className = 'zenfg-inspector-muted';
393
467
  metadata.textContent = description;
394
- entry.append(link, metadata);
395
- section.appendChild(entry);
468
+ entry.appendChild(metadata);
396
469
  }
470
+ section.appendChild(entry);
397
471
  }
398
472
  if (relations.length === 0) {
399
473
  const empty = document.createElement('span');
@@ -453,13 +527,118 @@ export class InspectorView {
453
527
  private accessNodeLabel(snapshot: FrameGraphDebugViewModel, nodeId: string): string {
454
528
  const node = snapshot.nodeById.get(nodeId);
455
529
  if (node) return labelNode(node);
456
- const culled = snapshot.culledNodes.find((candidate) => candidate.node.id === nodeId);
457
- return culled ? `${labelNode(culled.node)} (culled)` : `node-${nodeId}`;
530
+ const culled = snapshot.culledById.get(nodeId);
531
+ return culled ? `${labelNode(culled.node)} (culled)` : nodeId;
458
532
  }
459
533
 
460
534
  private accessNodeSelection(snapshot: FrameGraphDebugViewModel, nodeId: string): Selection | undefined {
461
- if (snapshot.nodeById.has(nodeId)) return { kind: 'node', id: nodeId };
462
- const index = snapshot.culledNodes.findIndex((candidate) => candidate.node.id === nodeId);
463
- return index < 0 ? undefined : { kind: 'culled', index };
535
+ return resolveNodeSelection(snapshot, nodeId);
536
+ }
537
+
538
+ private nodeLink(snapshot: FrameGraphDebugViewModel, id: string): string | HTMLButtonElement {
539
+ const selected = resolveNodeSelection(snapshot, id);
540
+ return selected ? this.createDetailLink(this.accessNodeLabel(snapshot, id), selected) : id;
541
+ }
542
+
543
+ private primaryPage(selection: Selection): WorkbenchTab {
544
+ switch (selection.kind) {
545
+ case 'node': case 'culled': case 'group': return 'passes';
546
+ case 'resource': return 'resources';
547
+ case 'allocation': return 'memory';
548
+ case 'root': case 'segment': return 'diagnostics';
549
+ }
550
+ }
551
+
552
+ private revealButton(selection: Selection, page: WorkbenchTab): HTMLButtonElement {
553
+ const button = document.createElement('button');
554
+ button.type = 'button';
555
+ button.className = 'zenfg-inspector-inline-action';
556
+ button.textContent = `Locate in ${page[0].toUpperCase()}${page.slice(1)}`;
557
+ button.addEventListener('click', () => {
558
+ this.clearLinkHover();
559
+ this.callbacks.onReveal?.(selection, page);
560
+ });
561
+ return button;
562
+ }
563
+
564
+ private createLocationActions(snapshot: FrameGraphDebugViewModel, selection: Selection): HTMLElement {
565
+ const actions = document.createElement('div');
566
+ actions.className = 'zenfg-inspector-detail-actions';
567
+ if (!this.callbacks.onReveal) return actions;
568
+ actions.appendChild(this.revealButton(selection, this.primaryPage(selection)));
569
+ if (selection.kind === 'node' || selection.kind === 'group' || selection.kind === 'resource' || selection.kind === 'root') {
570
+ actions.appendChild(this.revealButton(selection, 'graph'));
571
+ }
572
+ if (selection.kind === 'resource' && snapshot.resourceById.get(selection.id)?.origin === 'transient') actions.appendChild(this.revealButton(selection, 'memory'));
573
+ if (selection.kind === 'culled') {
574
+ const note = document.createElement('span');
575
+ note.className = 'zenfg-inspector-muted';
576
+ note.textContent = 'Culled passes are absent from Graph. Locate them in Passes.';
577
+ actions.appendChild(note);
578
+ }
579
+ return actions;
580
+ }
581
+
582
+ private selectionId(snapshot: FrameGraphDebugViewModel, selection: Selection): string | undefined {
583
+ if ('id' in selection) return selection.id;
584
+ if (selection.kind === 'group') return snapshot.groupByPathKey.get(selection.pathKey)?.id;
585
+ if (selection.kind === 'segment') return snapshot.segmentByIndex.get(selection.index)?.id;
586
+ return undefined;
587
+ }
588
+
589
+ private copyId(id: string): HTMLElement {
590
+ const host = document.createElement('span');
591
+ host.className = 'zenfg-inspector-copy-id';
592
+ const code = document.createElement('code');
593
+ code.textContent = id;
594
+ const button = document.createElement('button');
595
+ button.type = 'button';
596
+ button.textContent = 'Copy ID';
597
+ button.setAttribute('aria-label', 'Copy full object ID');
598
+ button.addEventListener('click', () => {
599
+ void writeClipboardText(id).then(() => { button.textContent = 'Copied'; }, () => { button.textContent = 'Copy failed'; });
600
+ });
601
+ host.append(code, button);
602
+ return host;
603
+ }
604
+
605
+ private createDiagnostics(snapshot: FrameGraphDebugViewModel, selection: Selection): HTMLElement | undefined {
606
+ let diagnostics: FrameGraphDebugViewModel['protocol']['diagnostics'] = [];
607
+ if (selection.kind === 'node' || selection.kind === 'culled') diagnostics = snapshot.diagnosticsByNodeId.get(selection.id) ?? [];
608
+ else if (selection.kind === 'resource') diagnostics = snapshot.diagnosticsByResourceId.get(selection.id) ?? [];
609
+ else if (selection.kind === 'allocation') {
610
+ const resourceIds = new Set(snapshot.allocationById.get(selection.id)?.resourceIds);
611
+ diagnostics = snapshot.protocol.diagnostics.filter((diagnostic) => diagnostic.resourceId !== undefined && resourceIds.has(diagnostic.resourceId));
612
+ } else if (selection.kind === 'group') {
613
+ const groupId = snapshot.groupByPathKey.get(selection.pathKey)?.id;
614
+ const groups = new Set(snapshot.debugGroups.filter((group) => groupId !== undefined && group.ancestorIds.includes(groupId)).map((group) => group.id));
615
+ diagnostics = snapshot.protocol.diagnostics.filter((diagnostic) => {
616
+ const nodeGroup = diagnostic.nodeId ? snapshot.canonicalNodeById.get(diagnostic.nodeId)?.groupId : undefined;
617
+ const resourceGroup = diagnostic.resourceId ? snapshot.resourceById.get(diagnostic.resourceId)?.debugGroupId : undefined;
618
+ return (nodeGroup !== undefined && groups.has(nodeGroup)) || (resourceGroup !== undefined && groups.has(resourceGroup));
619
+ });
620
+ }
621
+ if (!diagnostics.length) return undefined;
622
+ const section = document.createElement('section');
623
+ section.className = 'zenfg-inspector-detail-diagnostics';
624
+ const heading = document.createElement('h3');
625
+ heading.textContent = `Related diagnostics ${diagnostics.length}`;
626
+ section.appendChild(heading);
627
+ for (const diagnostic of diagnostics) {
628
+ const entry = document.createElement('article');
629
+ entry.dataset.severity = diagnostic.severity;
630
+ const label = document.createElement('strong');
631
+ label.textContent = `${diagnostic.severity} · ${diagnostic.code}`;
632
+ const message = document.createElement('p');
633
+ message.textContent = diagnostic.message;
634
+ entry.append(label, message);
635
+ if (diagnostic.nodeId) {
636
+ const node = resolveNodeSelection(snapshot, diagnostic.nodeId);
637
+ if (node) entry.appendChild(this.createDetailLink(this.accessNodeLabel(snapshot, diagnostic.nodeId), node));
638
+ }
639
+ if (diagnostic.resourceId) entry.appendChild(this.viewResourceButton(snapshot, diagnostic.resourceId));
640
+ section.appendChild(entry);
641
+ }
642
+ return section;
464
643
  }
465
644
  }
@@ -0,0 +1,154 @@
1
+ /** Content-width layout and message styles for the diagnostic workbench lists. */
2
+ export const PANEL_LIST_CSS = `
3
+ .zenfg-inspector-view-toolbar[hidden],
4
+ .zenfg-inspector-subview[hidden],
5
+ .zenfg-inspector-list-context[hidden] { display: none; }
6
+ .zenfg-inspector-result-count {
7
+ color: var(--fgd-muted);
8
+ white-space: nowrap;
9
+ font: var(--fgd-font-size-small)/1.4 var(--fgd-font-ui);
10
+ }
11
+ .zenfg-inspector-list-context {
12
+ flex: 0 0 auto;
13
+ margin: 0;
14
+ padding: 7px 10px;
15
+ border-bottom: 1px solid var(--fgd-border-subtle);
16
+ color: var(--fgd-muted);
17
+ font: var(--fgd-font-size-small)/1.5 var(--fgd-font-ui);
18
+ }
19
+ .zenfg-inspector-view-toolbar button {
20
+ min-height: var(--fgd-control-height);
21
+ padding: var(--fgd-space-1) var(--fgd-space-2);
22
+ border: 1px solid var(--fgd-border);
23
+ border-radius: var(--fgd-radius-sm);
24
+ background: var(--fgd-panel);
25
+ color: var(--fgd-text-secondary);
26
+ font: var(--fgd-font-size)/1.3 var(--fgd-font-ui);
27
+ cursor: pointer;
28
+ }
29
+ .zenfg-inspector-view-toolbar button:hover { background: var(--fgd-surface-hover); }
30
+ .zenfg-inspector-view-toolbar input,
31
+ .zenfg-inspector-view-toolbar select { font-size: var(--fgd-font-size); }
32
+ .zenfg-inspector-passes-view .zenfg-inspector-workbench-table,
33
+ .zenfg-inspector-resources-view .zenfg-inspector-workbench-table {
34
+ min-width: 580px;
35
+ border-collapse: separate;
36
+ border-spacing: 0;
37
+ font: var(--fgd-font-size-small)/1.5 var(--fgd-font-ui);
38
+ }
39
+ .zenfg-inspector-passes-view .zenfg-inspector-workbench-table [data-column='numeric'],
40
+ .zenfg-inspector-resources-view .zenfg-inspector-workbench-table [data-column='numeric'] {
41
+ font-family: var(--fgd-font-mono);
42
+ }
43
+ .zenfg-inspector-passes-view .zenfg-inspector-workbench-table th:first-child,
44
+ .zenfg-inspector-resources-view .zenfg-inspector-workbench-table th:first-child,
45
+ .zenfg-inspector-passes-view .zenfg-inspector-workbench-table td:first-child,
46
+ .zenfg-inspector-resources-view .zenfg-inspector-workbench-table td:first-child {
47
+ position: sticky;
48
+ left: 0;
49
+ max-width: 290px;
50
+ background: var(--fgd-canvas);
51
+ z-index: 1;
52
+ }
53
+ .zenfg-inspector-passes-view .zenfg-inspector-workbench-table th:first-child,
54
+ .zenfg-inspector-resources-view .zenfg-inspector-workbench-table th:first-child {
55
+ z-index: 3;
56
+ background: var(--fgd-panel);
57
+ }
58
+ .zenfg-inspector-passes-view .zenfg-inspector-workbench-table tr.selected td:first-child,
59
+ .zenfg-inspector-resources-view .zenfg-inspector-workbench-table tr.selected td:first-child {
60
+ background: color-mix(in srgb, var(--fgd-accent) 12%, var(--fgd-canvas));
61
+ }
62
+ .zenfg-inspector-passes-view .zenfg-inspector-workbench-table tbody tr:hover td:first-child,
63
+ .zenfg-inspector-resources-view .zenfg-inspector-workbench-table tbody tr:hover td:first-child {
64
+ background: var(--fgd-surface-hover);
65
+ }
66
+ .zenfg-inspector-passes-view .zenfg-inspector-group-toggle { margin-right: 5px; }
67
+ .zenfg-inspector-diagnostics-view { display: flex; flex-direction: column; }
68
+ .zenfg-inspector-diagnostics-view .zenfg-inspector-diagnostics-scroller {
69
+ flex: 1 1 auto;
70
+ min-height: 0;
71
+ height: auto;
72
+ }
73
+ .zenfg-inspector-diagnostics-sections {
74
+ display: flex;
75
+ flex-direction: column;
76
+ gap: 14px;
77
+ padding: 14px;
78
+ min-width: 0;
79
+ }
80
+ .zenfg-inspector-diagnostic-messages > h2 { margin: 0; font: 600 14px/1.5 var(--fgd-font-ui); }
81
+ .zenfg-inspector-diagnostic-messages > p,
82
+ .zenfg-inspector-diagnostic-section > p {
83
+ margin: 5px 0 10px;
84
+ color: var(--fgd-muted);
85
+ font: var(--fgd-font-size-small)/1.5 var(--fgd-font-ui);
86
+ }
87
+ .zenfg-inspector-diagnostic-message {
88
+ --diagnostic-color: var(--fgd-accent);
89
+ margin-top: 9px;
90
+ padding: 10px var(--fgd-space-3);
91
+ border-left: 3px solid var(--diagnostic-color);
92
+ background: var(--fgd-surface);
93
+ overflow-wrap: anywhere;
94
+ }
95
+ .zenfg-inspector-diagnostic-message[data-severity='error'] { --diagnostic-color: var(--fgd-danger); }
96
+ .zenfg-inspector-diagnostic-message[data-severity='warning'] { --diagnostic-color: var(--fgd-warning); }
97
+ .zenfg-inspector-diagnostic-message h3 {
98
+ display: flex;
99
+ flex-wrap: wrap;
100
+ gap: var(--fgd-space-2);
101
+ margin: 0;
102
+ font: 600 var(--fgd-font-size-small)/1.5 var(--fgd-font-ui);
103
+ }
104
+ .zenfg-inspector-diagnostic-severity { color: var(--diagnostic-color); text-transform: capitalize; }
105
+ .zenfg-inspector-diagnostic-message code { font: var(--fgd-font-size-small)/1.5 var(--fgd-font-mono); }
106
+ .zenfg-inspector-diagnostic-message p { margin: 6px 0; white-space: pre-wrap; font: var(--fgd-font-size)/1.55 var(--fgd-font-ui); }
107
+ .zenfg-inspector-diagnostic-links {
108
+ display: flex;
109
+ align-items: baseline;
110
+ flex-wrap: wrap;
111
+ gap: 5px var(--fgd-space-3);
112
+ min-width: 0;
113
+ margin-top: var(--fgd-space-1);
114
+ }
115
+ .zenfg-inspector-diagnostic-links .zenfg-inspector-relation-button {
116
+ min-width: 0;
117
+ max-width: 100%;
118
+ white-space: normal;
119
+ overflow-wrap: anywhere;
120
+ text-align: left;
121
+ font: var(--fgd-font-size)/1.5 var(--fgd-font-ui);
122
+ }
123
+ .zenfg-inspector-diagnostic-reveal { color: var(--fgd-muted); }
124
+ .zenfg-inspector-diagnostic-section {
125
+ min-width: 0;
126
+ padding: 10px 0 0;
127
+ border-top: 1px solid var(--fgd-border-subtle);
128
+ overflow-wrap: anywhere;
129
+ }
130
+ .zenfg-inspector-diagnostic-section > summary {
131
+ padding: 3px 0;
132
+ font: 600 var(--fgd-font-size)/1.5 var(--fgd-font-ui);
133
+ cursor: pointer;
134
+ }
135
+ .zenfg-inspector-diagnostic-section > .zenfg-inspector-diagnostic-section { margin: var(--fgd-space-2) 0 0 var(--fgd-space-3); }
136
+ .zenfg-inspector-diagnostic-section .zenfg-inspector-diagnostic-list { overflow: visible; padding: var(--fgd-space-1) 0; }
137
+ .zenfg-inspector-diagnostic-section ul { padding-left: 20px; }
138
+ .zenfg-inspector-diagnostic-section > .zenfg-inspector-relation-button { margin: 5px 0; }
139
+ .zenfg-inspector-diagnostic-links .selected { background: var(--fgd-accent-soft); }
140
+ .zenfg-inspector-diagnostic-section summary:focus-visible,
141
+ .zenfg-inspector-view-toolbar button:focus-visible { outline: 2px solid var(--fgd-accent); outline-offset: 2px; }
142
+ @container zenfg-inspector-main (max-width: 640px) {
143
+ .zenfg-inspector-passes-view .zenfg-inspector-workbench-table,
144
+ .zenfg-inspector-resources-view .zenfg-inspector-workbench-table { min-width: 420px; }
145
+ .zenfg-inspector-passes-view [id$='pass-list-panel'] tr > :nth-child(3),
146
+ .zenfg-inspector-passes-view [id$='pass-list-panel'] tr > :nth-child(6),
147
+ .zenfg-inspector-passes-view [id$='group-list-panel'] tr > :nth-child(5),
148
+ .zenfg-inspector-passes-view [id$='group-list-panel'] tr > :nth-child(6),
149
+ .zenfg-inspector-resources-view tr > :nth-child(4),
150
+ .zenfg-inspector-resources-view tr > :nth-child(5) { display: none; }
151
+ .zenfg-inspector-passes-view .zenfg-inspector-workbench-table td:first-child,
152
+ .zenfg-inspector-resources-view .zenfg-inspector-workbench-table td:first-child { max-width: 190px; }
153
+ }
154
+ `;