@growthub/cli 0.13.4 → 0.13.5

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 (23) hide show
  1. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/metadata-graph/route.js +184 -0
  2. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/sandbox-run/route.js +25 -2
  3. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/AgentSwarmPanel.jsx +326 -0
  4. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/OrchestrationGraphEmptyCanvas.jsx +6 -0
  5. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/OrchestrationNodeConfigPanel.jsx +88 -1
  6. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/OrchestrationRunTracePanel.jsx +41 -0
  7. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/WorkspaceGraphInspectorPanel.jsx +226 -0
  8. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/globals.css +16 -0
  9. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workflows/WorkflowSurface.jsx +49 -4
  10. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workspace-builder.jsx +14 -1
  11. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-agent-swarm.js +923 -0
  12. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-graph-runner.js +14 -1
  13. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-graph.js +216 -5
  14. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-run-console.js +28 -0
  15. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-run-inputs.js +43 -0
  16. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-run-trace.js +3 -1
  17. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/sandbox-agent-auth.js +36 -0
  18. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-chart-values.js +53 -0
  19. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-metadata-graph.js +646 -0
  20. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-metadata-selectors.js +249 -0
  21. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-metadata-store.js +1186 -0
  22. package/assets/worker-kits/growthub-custom-workspace-starter-v1/kit.json +5 -0
  23. package/package.json +1 -1
@@ -215,6 +215,43 @@ async function copyToClipboard(text) {
215
215
  }
216
216
  }
217
217
 
218
+ function formatRewardScore(value) {
219
+ const n = Number(value);
220
+ if (!Number.isFinite(n)) return "—";
221
+ return n.toFixed(2);
222
+ }
223
+
224
+ function SwarmSection({ swarm }) {
225
+ if (!swarm || typeof swarm !== "object") return null;
226
+ const tasks = Array.isArray(swarm.tasks) ? swarm.tasks : [];
227
+ if (tasks.length === 0 && !swarm.orchestrator?.plan && !swarm.synthesis?.answer) return null;
228
+ const completed = tasks.filter((t) => t?.status === "completed").length;
229
+ const score = swarm.reward ? formatRewardScore(swarm.reward.score) : "—";
230
+ const kind = swarm.reward?.kind || "structural-v1";
231
+ const synthesis = swarm.synthesis || null;
232
+ return (
233
+ <section className="dm-run-console__section">
234
+ <h3>Swarm</h3>
235
+ <p className="dm-swarm-summary__line">
236
+ <span><strong>{completed}/{tasks.length}</strong></span>
237
+ <span>score <strong>{score}</strong></span>
238
+ <span className="dm-swarm-summary__kind" title={swarm.reward?.note || ""}>{kind}</span>
239
+ </p>
240
+ {synthesis?.answer ? (
241
+ <details className="dm-swarm-phase" open>
242
+ <summary>
243
+ synthesizer
244
+ {synthesis.parsedOutcomeScore != null
245
+ ? ` · ${Number(synthesis.parsedOutcomeScore).toFixed(2)}`
246
+ : ""}
247
+ </summary>
248
+ <pre>{synthesis.answer}</pre>
249
+ </details>
250
+ ) : null}
251
+ </section>
252
+ );
253
+ }
254
+
218
255
  function InputsSection({ payload }) {
219
256
  const runInputs = payload?.runInputs;
220
257
  const summary = payload?.inputSummary;
@@ -477,6 +514,9 @@ export function OrchestrationRunTracePanel({
477
514
  const payload = activeConsoleRecord?.payload || {};
478
515
  const output = activeConsoleRecord?.output || {};
479
516
  const context = activeConsoleRecord?.context || {};
517
+ const swarmPayload = activeConsoleRecord?.swarm
518
+ || (activeRawRecord && activeRawRecord.swarm)
519
+ || null;
480
520
 
481
521
  return (
482
522
  <section className="dm-run-console" aria-label="Live runs console">
@@ -710,6 +750,7 @@ export function OrchestrationRunTracePanel({
710
750
  <CodeBlock label="Command" body={payload.command} />
711
751
  <CodeBlock label="Instructions" body={payload.instructions} />
712
752
  </section>
753
+ <SwarmSection swarm={swarmPayload} />
713
754
  <InputsSection payload={payload} />
714
755
  </div>
715
756
  )}
@@ -0,0 +1,226 @@
1
+ "use client";
2
+
3
+ /**
4
+ * Growthub Workspace Metadata Graph V1 — read-only inspector panel.
5
+ *
6
+ * Surfaces a selected metadata node's dependencies + dependents + warnings.
7
+ *
8
+ * Mounting status (V1):
9
+ * This component SHIPS with the worker kit but is NOT yet mounted by the
10
+ * existing builder / workflow / data-model surfaces. Mounting is
11
+ * intentionally deferred to a follow-up PR so the V1 scope stays focused
12
+ * on the typed projection + GET route. Operators that want the inspector
13
+ * now can import it directly:
14
+ *
15
+ * import { WorkspaceGraphInspectorPanel } from "@/app/data-model/components/WorkspaceGraphInspectorPanel.jsx";
16
+ *
17
+ * Intended future entry points:
18
+ *
19
+ * - Widget sidecar: View dependencies
20
+ * - Workflow sidecar: View dependencies
21
+ * - Run console: View graph lineage
22
+ * - Data Model row sidecar: View dependents
23
+ * - Workspace Settings: Metadata Graph
24
+ *
25
+ * V1 invariants:
26
+ * - Read-only. No edits. No deletes.
27
+ * - No fetch of provider data — only `GET /api/workspace/metadata-graph`.
28
+ * - No secrets rendered.
29
+ */
30
+
31
+ import { useEffect, useMemo, useState } from "react";
32
+ import { findDependencies, findDependents } from "@/lib/workspace-metadata-graph";
33
+
34
+ const RELATION_LABELS = {
35
+ containsWidget: "contains widget",
36
+ bindsToObject: "binds to object",
37
+ usesField: "uses field",
38
+ filteredByField: "filtered by field",
39
+ sortedByField: "sorted by field",
40
+ backedBySourceRecord: "backed by source record",
41
+ scopedToEntity: "scoped to entity",
42
+ containsNode: "contains node",
43
+ usesSandbox: "uses sandbox",
44
+ readsObject: "reads object",
45
+ writesObject: "writes object",
46
+ requiresRunInput: "requires run input",
47
+ callsIntegration: "calls integration",
48
+ usesAgentHost: "uses agent host",
49
+ executedWorkflow: "executed workflow",
50
+ executedSandbox: "executed sandbox",
51
+ usedAgentHost: "used agent host",
52
+ producedArtifact: "produced artifact",
53
+ belongsToIntegration: "belongs to integration",
54
+ boundToIntegration: "bound to integration"
55
+ };
56
+
57
+ const NODE_TYPE_LABELS = {
58
+ dashboard: "Dashboard",
59
+ widget: "Widget",
60
+ dataModelObject: "Data Model object",
61
+ field: "Field",
62
+ view: "View",
63
+ workflow: "Workflow",
64
+ workflowNode: "Workflow node",
65
+ runInput: "Run input",
66
+ sandbox: "Sandbox",
67
+ agentHost: "Agent host",
68
+ integration: "Integration",
69
+ integrationEntity: "Integration entity",
70
+ sourceRecord: "Source record",
71
+ run: "Run",
72
+ outputArtifact: "Output artifact"
73
+ };
74
+
75
+ function relationLabel(relation) {
76
+ return RELATION_LABELS[relation] || relation;
77
+ }
78
+
79
+ function nodeTypeLabel(type) {
80
+ return NODE_TYPE_LABELS[type] || type;
81
+ }
82
+
83
+ export function WorkspaceGraphInspectorPanel({ selectedNodeId, onSelectNode }) {
84
+ const [graph, setGraph] = useState(null);
85
+ const [loading, setLoading] = useState(false);
86
+ const [error, setError] = useState(null);
87
+ const [internalSelectedId, setInternalSelectedId] = useState(selectedNodeId || "");
88
+
89
+ useEffect(() => {
90
+ let canceled = false;
91
+ setLoading(true);
92
+ setError(null);
93
+ fetch("/api/workspace/metadata-graph")
94
+ .then((response) => {
95
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
96
+ return response.json();
97
+ })
98
+ .then((envelope) => {
99
+ if (canceled) return;
100
+ setGraph(envelope?.graph || { nodes: [], edges: [] });
101
+ })
102
+ .catch((err) => {
103
+ if (canceled) return;
104
+ setError(err?.message || "Failed to load metadata graph");
105
+ })
106
+ .finally(() => {
107
+ if (!canceled) setLoading(false);
108
+ });
109
+ return () => { canceled = true; };
110
+ }, []);
111
+
112
+ useEffect(() => {
113
+ if (selectedNodeId && selectedNodeId !== internalSelectedId) {
114
+ setInternalSelectedId(selectedNodeId);
115
+ }
116
+ }, [selectedNodeId, internalSelectedId]);
117
+
118
+ const nodes = graph?.nodes || [];
119
+ const selected = useMemo(
120
+ () => nodes.find((node) => node.id === internalSelectedId) || null,
121
+ [nodes, internalSelectedId]
122
+ );
123
+
124
+ const dependencies = useMemo(
125
+ () => (graph && selected ? findDependencies(graph, selected.id) : []),
126
+ [graph, selected]
127
+ );
128
+ const dependents = useMemo(
129
+ () => (graph && selected ? findDependents(graph, selected.id) : []),
130
+ [graph, selected]
131
+ );
132
+
133
+ const handleSelect = (nodeId) => {
134
+ setInternalSelectedId(nodeId);
135
+ if (typeof onSelectNode === "function") onSelectNode(nodeId);
136
+ };
137
+
138
+ return (
139
+ <div className="workspace-graph-inspector" aria-label="Workspace metadata graph inspector">
140
+ <header className="workspace-graph-inspector-header">
141
+ <h3>Workspace metadata graph</h3>
142
+ <p className="workspace-graph-inspector-subtitle">
143
+ Read-only projection of dashboards, widgets, workflows, sandboxes, and runs.
144
+ </p>
145
+ </header>
146
+
147
+ {loading && <p className="workspace-graph-inspector-loading">Loading metadata graph…</p>}
148
+ {error && (
149
+ <p className="workspace-graph-inspector-error" role="alert">
150
+ Could not load metadata graph: {error}
151
+ </p>
152
+ )}
153
+
154
+ <div className="workspace-graph-inspector-body">
155
+ <aside className="workspace-graph-inspector-list">
156
+ <h4>Nodes ({nodes.length})</h4>
157
+ <ul>
158
+ {nodes.map((node) => (
159
+ <li key={node.id}>
160
+ <button
161
+ type="button"
162
+ className={`workspace-graph-inspector-node-button${node.id === internalSelectedId ? " is-selected" : ""}`}
163
+ onClick={() => handleSelect(node.id)}
164
+ >
165
+ <span className="workspace-graph-inspector-node-type">{nodeTypeLabel(node.type)}</span>
166
+ <span className="workspace-graph-inspector-node-label">{node.label || node.id}</span>
167
+ </button>
168
+ </li>
169
+ ))}
170
+ </ul>
171
+ </aside>
172
+
173
+ <section className="workspace-graph-inspector-detail">
174
+ {!selected && <p className="workspace-graph-inspector-empty">Select a node to view dependencies.</p>}
175
+ {selected && (
176
+ <>
177
+ <h4>{nodeTypeLabel(selected.type)} · {selected.label}</h4>
178
+ <dl className="workspace-graph-inspector-summary">
179
+ {Object.entries(selected.summary || {}).map(([key, value]) => (
180
+ <div key={key}>
181
+ <dt>{key}</dt>
182
+ <dd>{Array.isArray(value) ? value.join(", ") || "—" : (value === null || value === "" ? "—" : String(value))}</dd>
183
+ </div>
184
+ ))}
185
+ </dl>
186
+
187
+ <section className="workspace-graph-inspector-edges">
188
+ <h5>Depends on ({dependencies.length})</h5>
189
+ <ul>
190
+ {dependencies.length === 0 && <li className="workspace-graph-inspector-empty">No dependencies.</li>}
191
+ {dependencies.map(({ node, relation }) => (
192
+ <li key={`dep::${node.id}::${relation}`}>
193
+ <button type="button" onClick={() => handleSelect(node.id)}>
194
+ <span className="workspace-graph-inspector-relation">{relationLabel(relation)}</span>
195
+ <span className="workspace-graph-inspector-node-type">{nodeTypeLabel(node.type)}</span>
196
+ <span className="workspace-graph-inspector-node-label">{node.label || node.id}</span>
197
+ </button>
198
+ </li>
199
+ ))}
200
+ </ul>
201
+ </section>
202
+
203
+ <section className="workspace-graph-inspector-edges">
204
+ <h5>Used by ({dependents.length})</h5>
205
+ <ul>
206
+ {dependents.length === 0 && <li className="workspace-graph-inspector-empty">Nothing depends on this node yet.</li>}
207
+ {dependents.map(({ node, relation }) => (
208
+ <li key={`from::${node.id}::${relation}`}>
209
+ <button type="button" onClick={() => handleSelect(node.id)}>
210
+ <span className="workspace-graph-inspector-relation">{relationLabel(relation)}</span>
211
+ <span className="workspace-graph-inspector-node-type">{nodeTypeLabel(node.type)}</span>
212
+ <span className="workspace-graph-inspector-node-label">{node.label || node.id}</span>
213
+ </button>
214
+ </li>
215
+ ))}
216
+ </ul>
217
+ </section>
218
+ </>
219
+ )}
220
+ </section>
221
+ </div>
222
+ </div>
223
+ );
224
+ }
225
+
226
+ export default WorkspaceGraphInspectorPanel;
@@ -8398,3 +8398,19 @@ body.workspace-rail-collapsed .workspace-builder.dm-workflow-page {
8398
8398
  .dm-run-setup__actions { display: flex; gap: 8px; justify-content: flex-end; flex-wrap: wrap; }
8399
8399
  .dm-run-setup__actions .dm-btn-outline,
8400
8400
  .dm-run-setup__actions .dm-workflow-chip-btn { display: inline-flex; align-items: center; gap: 6px; }
8401
+
8402
+ /* Agent swarm — sidecar editor (re-uses dm-orchestration-config tokens) */
8403
+ .dm-agent-swarm-panel__row { display: flex; align-items: center; gap: 6px; }
8404
+ .dm-agent-swarm-panel__row > span:first-child { flex: 1; font-size: 11px; font-weight: 600; color: #6b7280; text-transform: uppercase; letter-spacing: 0.04em; }
8405
+ .dm-agent-swarm-panel__row .dm-btn-outline { display: inline-flex; align-items: center; gap: 4px; font-size: 11px; padding: 3px 7px; }
8406
+ .dm-agent-swarm-panel__subagent { border: 1px solid #edf0f3; border-radius: 4px; padding: 8px 10px; display: flex; flex-direction: column; gap: 6px; }
8407
+ .dm-agent-swarm-panel__role { flex: 1; border: 1px solid #d1d5db; border-radius: 4px; padding: 4px 6px; font-size: 12px; color: #111827; background: #fff; }
8408
+ .dm-agent-swarm-panel__weights { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 6px; }
8409
+
8410
+ /* Agent swarm — condensed run trace summary */
8411
+ .dm-swarm-summary__line { display: flex; flex-wrap: wrap; gap: 6px 12px; font-size: 11px; color: #6b7280; align-items: center; margin: 0 0 6px; }
8412
+ .dm-swarm-summary__line strong { color: #111827; font-weight: 600; }
8413
+ .dm-swarm-summary__kind { font-size: 10px; text-transform: uppercase; letter-spacing: 0.04em; color: #6b7280; border: 1px solid #e5e7eb; border-radius: 999px; padding: 1px 6px; }
8414
+ .dm-swarm-phase { border: 1px solid #edf0f3; border-radius: 4px; padding: 6px 8px; font-size: 11px; }
8415
+ .dm-swarm-phase summary { cursor: pointer; color: #6b7280; font-weight: 500; }
8416
+ .dm-swarm-phase pre { margin: 6px 0 0; font-size: 11px; white-space: pre-wrap; color: #111827; max-height: 240px; overflow: auto; }
@@ -31,9 +31,11 @@ import { findSandboxRowByWorkflowRef } from "@/lib/nav-workflows";
31
31
  import {
32
32
  addCanonicalNodeToGraph,
33
33
  buildBlankOrchestrationGraphShell,
34
+ buildDefaultAgentSwarmGraph,
34
35
  buildDefaultOrchestrationGraphFromRegistry,
35
36
  getNextCanonicalNodeId,
36
37
  getOrchestrationGraphUiState,
38
+ isAgentSwarmGraph,
37
39
  parseOrchestrationGraph,
38
40
  redactSecretsFromText,
39
41
  serializeOrchestrationGraph,
@@ -45,8 +47,19 @@ import { OrchestrationGraphCanvas } from "../data-model/components/Orchestration
45
47
  import { OrchestrationGraphEmptyCanvas } from "../data-model/components/OrchestrationGraphEmptyCanvas.jsx";
46
48
  import { OrchestrationNodeConfigPanel } from "../data-model/components/OrchestrationNodeConfigPanel.jsx";
47
49
  import { OrchestrationRunTracePanel } from "../data-model/components/OrchestrationRunTracePanel.jsx";
50
+ import { AgentSwarmPanel } from "../data-model/components/AgentSwarmPanel.jsx";
48
51
  import { RunSetupPanel } from "./RunSetupPanel.jsx";
49
- import { discoverRunInputSchema } from "@/lib/orchestration-run-inputs";
52
+ import { describeRunInputMetadataItems, discoverRunInputSchema } from "@/lib/orchestration-run-inputs";
53
+ import { selectWorkflowNodeInputSchema } from "@/lib/workspace-metadata-selectors";
54
+
55
+ // Workspace Metadata Graph V1 — read-only dependency metadata for workflow
56
+ // sidecars. The runtime path (sandbox-run, publish, draft/live) is
57
+ // unchanged; this only exposes typed dependency descriptors so the sidecar
58
+ // can render "this node requires N inputs from M source nodes".
59
+ const WORKFLOW_METADATA_SELECTORS = Object.freeze({
60
+ describeRunInputMetadataItems,
61
+ selectWorkflowNodeInputSchema
62
+ });
50
63
 
51
64
  function resolveRegistryRowForSandbox(workspaceConfig, sandboxRow) {
52
65
  const graph = parseOrchestrationGraph(sandboxRow?.orchestrationGraph);
@@ -382,9 +395,10 @@ export default function WorkflowSurface() {
382
395
  const graphUiState = getOrchestrationGraphUiState(orchestrationGraph);
383
396
  const graphUnset = graphUiState === "unset";
384
397
  const graphBlankShell = graphUiState === "blank-shell";
398
+ const swarmMode = useMemo(() => isAgentSwarmGraph(orchestrationGraph), [orchestrationGraph]);
385
399
  const nextNodeId = useMemo(
386
- () => (orchestrationGraph ? getNextCanonicalNodeId(orchestrationGraph) : "input"),
387
- [orchestrationGraph]
400
+ () => (orchestrationGraph && !swarmMode ? getNextCanonicalNodeId(orchestrationGraph) : null),
401
+ [orchestrationGraph, swarmMode]
388
402
  );
389
403
 
390
404
  const selectedNode = useMemo(() => {
@@ -649,6 +663,16 @@ export default function WorkflowSurface() {
649
663
  setDirty(true);
650
664
  }
651
665
 
666
+ function startAgentSwarm() {
667
+ const graph = buildDefaultAgentSwarmGraph({
668
+ agentHost: String(sandboxRow?.agentHost || "").trim()
669
+ });
670
+ setOrchestrationGraph(graph);
671
+ setSelectedNodeId("orchestrator");
672
+ setConfigTab("swarm");
673
+ setDirty(true);
674
+ }
675
+
652
676
  function applyPastedGraph(text) {
653
677
  const parsed = parseOrchestrationGraph(text);
654
678
  if (parsed) {
@@ -871,6 +895,7 @@ export default function WorkflowSurface() {
871
895
  disabled={false}
872
896
  onStartFromRegistry={registryRow ? startFromRegistry : undefined}
873
897
  onStartBlank={startBlank}
898
+ onStartAgentSwarm={startAgentSwarm}
874
899
  onPasteGraph={applyPastedGraph}
875
900
  />
876
901
  ) : graphBlankShell ? (
@@ -932,7 +957,27 @@ export default function WorkflowSurface() {
932
957
  />
933
958
  </div>
934
959
  )}
935
- {graphUiState === "populated" && !runSetupOpen && !addTarget && selectedNode && (
960
+ {graphUiState === "populated" && !runSetupOpen && !addTarget && selectedNode && swarmMode && selectedNode?.type === "thinAdapter" && (
961
+ <div className="dm-orchestration-sidecar__config-col">
962
+ <div className="dm-workflow-panel-head">
963
+ <button type="button" className="dm-workflow-icon-btn" onClick={() => setSelectedNodeId("")} aria-label="Close side panel">
964
+ <X size={14} />
965
+ </button>
966
+ <span>Agent swarm</span>
967
+ <em>agent-swarm-v1</em>
968
+ </div>
969
+ <AgentSwarmPanel
970
+ graph={orchestrationGraph}
971
+ disabled={false}
972
+ onGraphChange={(updater) => {
973
+ setOrchestrationGraph((g) => (typeof updater === "function" ? updater(g) : updater));
974
+ setDirty(true);
975
+ }}
976
+ />
977
+ {graphError && <p className="dm-orchestration-config__error">{graphError}</p>}
978
+ </div>
979
+ )}
980
+ {graphUiState === "populated" && !runSetupOpen && !addTarget && selectedNode && !(swarmMode && selectedNode?.type === "thinAdapter") && (
936
981
  <div className="dm-orchestration-sidecar__config-col">
937
982
  <div className="dm-workflow-panel-head">
938
983
  <button type="button" className="dm-workflow-icon-btn" onClick={() => setSelectedNodeId("")} aria-label="Close side panel">
@@ -81,11 +81,24 @@ import { governedWorkspaceIntegrationCatalog } from "@/lib/domain/integrations";
81
81
  import { OBJECT_TYPE_PRESETS, listWorkspaceDataModelTables } from "@/lib/workspace-data-model";
82
82
  import {
83
83
  computeChartProjectionDebug,
84
- computeChartValuesFromRows
84
+ computeChartValuesFromRows,
85
+ deriveWidgetDependencyContract
85
86
  } from "@/lib/workspace-chart-values";
87
+ import { selectObjectFilterableFields, selectObjectSortableFields } from "@/lib/workspace-metadata-selectors";
86
88
  import { HelperSidecar } from "./data-model/components/HelperSidecar.jsx";
87
89
  import { WorkspaceRail } from "./workspace-rail.jsx";
88
90
 
91
+ // Workspace Metadata Graph V1 — typed dependency contracts.
92
+ // Used by sidecar dependency summaries; the existing chart hydration path
93
+ // continues to compute values via `computeChartValuesFromRows`. These
94
+ // selectors only describe the widget's typed contract — they never mutate
95
+ // config or trigger network calls.
96
+ const WORKSPACE_METADATA_SELECTORS = Object.freeze({
97
+ deriveWidgetDependencyContract,
98
+ selectObjectFilterableFields,
99
+ selectObjectSortableFields
100
+ });
101
+
89
102
  const DEFAULT_CHART_TYPE = "bar-vertical";
90
103
  const DEFAULT_FILTER_OP = "and";
91
104
  const DEFAULT_FILTER_OPERATOR = "contains";