@growthub/cli 0.13.4 → 0.13.6

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 (52) hide show
  1. package/assets/worker-kits/growthub-custom-workspace-starter-v1/QUICKSTART.md +19 -0
  2. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/.env.example +8 -0
  3. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/README.md +4 -0
  4. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/integrations/nango/action/execute/route.js +60 -0
  5. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/integrations/nango/actions/route.js +50 -0
  6. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/integrations/nango/connect-session/route.js +68 -0
  7. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/integrations/nango/connection-status/route.js +56 -0
  8. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/integrations/nango/proxy/route.js +67 -0
  9. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/integrations/nango/status/route.js +50 -0
  10. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/metadata-graph/route.js +184 -0
  11. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/sandbox-run/route.js +25 -2
  12. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/AgentSwarmPanel.jsx +326 -0
  13. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/DataModelShell.jsx +161 -50
  14. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/NangoConnectionPanel.jsx +496 -0
  15. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/OrchestrationGraphEmptyCanvas.jsx +6 -0
  16. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/OrchestrationNodeConfigPanel.jsx +88 -1
  17. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/OrchestrationRunTracePanel.jsx +41 -0
  18. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/WorkspaceGraphInspectorPanel.jsx +226 -0
  19. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/globals.css +120 -17
  20. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/settings/integrations/nango/page.jsx +167 -0
  21. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/settings/integrations/page.jsx +1 -0
  22. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workflows/WorkflowSurface.jsx +67 -11
  23. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workspace-builder.jsx +31 -10
  24. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workspace-rail.jsx +16 -14
  25. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/env.js +7 -0
  26. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/nango/index.js +38 -0
  27. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/nango/nango-adapter.js +552 -0
  28. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/nango/nango-config-loader.js +202 -0
  29. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/nango/nango-schema.js +303 -0
  30. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/resolver-loader.js +49 -10
  31. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/source-resolver-registry.js +1 -1
  32. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/templates/nango.js +49 -0
  33. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/templates/template-registry.js +4 -2
  34. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-agent-swarm.js +923 -0
  35. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-graph-runner.js +14 -1
  36. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-graph.js +218 -7
  37. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-run-console.js +28 -0
  38. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-run-inputs.js +43 -0
  39. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-run-trace.js +3 -1
  40. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/sandbox-agent-auth.js +36 -0
  41. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-chart-values.js +53 -0
  42. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-data-model.js +2 -1
  43. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-metadata-graph.js +646 -0
  44. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-metadata-selectors.js +249 -0
  45. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-metadata-store.js +1186 -0
  46. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-schema.js +102 -3
  47. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/package.json +1 -0
  48. package/assets/worker-kits/growthub-custom-workspace-starter-v1/bundles/growthub-custom-workspace-starter-v1.json +1 -0
  49. package/assets/worker-kits/growthub-custom-workspace-starter-v1/kit.json +7 -0
  50. package/assets/worker-kits/growthub-custom-workspace-starter-v1/templates/seeded-configs/project-management.config.json +276 -0
  51. package/dist/index.js +127 -44
  52. package/package.json +1 -1
@@ -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,11 +47,22 @@ 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
- const graph = parseOrchestrationGraph(sandboxRow?.orchestrationGraph);
65
+ const graph = parseOrchestrationGraph(sandboxRow?.orchestrationConfig || sandboxRow?.orchestrationGraph);
53
66
  const apiNode = graph?.nodes?.find((n) => n?.type === "api-registry-call");
54
67
  const registryId = String(
55
68
  apiNode?.config?.registryId || apiNode?.config?.integrationId || sandboxRow?.schedulerRegistryId || ""
@@ -278,6 +291,10 @@ function isPassingRun(payload) {
278
291
  return payload?.ok === true && Number(payload?.exitCode ?? payload?.response?.exitCode) === 0;
279
292
  }
280
293
 
294
+ function graphHasNodes(graph) {
295
+ return Array.isArray(graph?.nodes) && graph.nodes.length > 0;
296
+ }
297
+
281
298
  function WorkflowAddStepPanel({ target, onSelect }) {
282
299
  return (
283
300
  <div className="dm-workflow-add-panel">
@@ -310,7 +327,7 @@ export default function WorkflowSurface() {
310
327
  const searchParams = useSearchParams();
311
328
  const objectId = String(searchParams.get("object") || "").trim();
312
329
  const rowId = String(searchParams.get("row") || "").trim();
313
- const fieldName = String(searchParams.get("field") || "orchestrationGraph").trim();
330
+ const fieldName = String(searchParams.get("field") || "orchestrationConfig").trim();
314
331
  const runId = String(searchParams.get("run") || "").trim();
315
332
 
316
333
  const [workspaceConfig, setWorkspaceConfig] = useState(null);
@@ -356,11 +373,14 @@ export default function WorkflowSurface() {
356
373
  );
357
374
 
358
375
  const sandboxRow = resolved.row;
359
- const effectiveFieldName = sandboxRow?.[fieldName] !== undefined
376
+ const hasGraphValue = (value) => Boolean(parseOrchestrationGraph(value));
377
+ const effectiveFieldName = hasGraphValue(sandboxRow?.[fieldName])
360
378
  ? fieldName
361
- : sandboxRow?.orchestrationConfig !== undefined
379
+ : hasGraphValue(sandboxRow?.orchestrationConfig)
362
380
  ? "orchestrationConfig"
363
- : "orchestrationGraph";
381
+ : hasGraphValue(sandboxRow?.orchestrationGraph)
382
+ ? "orchestrationGraph"
383
+ : (fieldName || "orchestrationConfig");
364
384
  const draftFieldName = effectiveFieldName === "orchestrationConfig" ? "orchestrationDraftConfig" : "orchestrationDraftGraph";
365
385
  const registryRow = useMemo(
366
386
  () => (sandboxRow && workspaceConfig ? resolveRegistryRowForSandbox(workspaceConfig, sandboxRow) : null),
@@ -373,7 +393,11 @@ export default function WorkflowSurface() {
373
393
 
374
394
  useEffect(() => {
375
395
  if (!sandboxRow) return;
376
- const parsed = parseOrchestrationGraph(sandboxRow[draftFieldName]) || parseOrchestrationGraph(sandboxRow[effectiveFieldName]);
396
+ const draftParsed = parseOrchestrationGraph(sandboxRow[draftFieldName]);
397
+ const publishedParsed = parseOrchestrationGraph(sandboxRow[effectiveFieldName])
398
+ || parseOrchestrationGraph(sandboxRow.orchestrationConfig)
399
+ || parseOrchestrationGraph(sandboxRow.orchestrationGraph);
400
+ const parsed = graphHasNodes(draftParsed) || !graphHasNodes(publishedParsed) ? draftParsed : publishedParsed;
377
401
  setOrchestrationGraph(parsed);
378
402
  setDirty(false);
379
403
  setGraphError("");
@@ -382,9 +406,10 @@ export default function WorkflowSurface() {
382
406
  const graphUiState = getOrchestrationGraphUiState(orchestrationGraph);
383
407
  const graphUnset = graphUiState === "unset";
384
408
  const graphBlankShell = graphUiState === "blank-shell";
409
+ const swarmMode = useMemo(() => isAgentSwarmGraph(orchestrationGraph), [orchestrationGraph]);
385
410
  const nextNodeId = useMemo(
386
- () => (orchestrationGraph ? getNextCanonicalNodeId(orchestrationGraph) : "input"),
387
- [orchestrationGraph]
411
+ () => (orchestrationGraph && !swarmMode ? getNextCanonicalNodeId(orchestrationGraph) : null),
412
+ [orchestrationGraph, swarmMode]
388
413
  );
389
414
 
390
415
  const selectedNode = useMemo(() => {
@@ -649,6 +674,16 @@ export default function WorkflowSurface() {
649
674
  setDirty(true);
650
675
  }
651
676
 
677
+ function startAgentSwarm() {
678
+ const graph = buildDefaultAgentSwarmGraph({
679
+ agentHost: String(sandboxRow?.agentHost || "").trim()
680
+ });
681
+ setOrchestrationGraph(graph);
682
+ setSelectedNodeId("orchestrator");
683
+ setConfigTab("swarm");
684
+ setDirty(true);
685
+ }
686
+
652
687
  function applyPastedGraph(text) {
653
688
  const parsed = parseOrchestrationGraph(text);
654
689
  if (parsed) {
@@ -736,7 +771,7 @@ export default function WorkflowSurface() {
736
771
  const publishReady = draftPassed && String(sandboxRow?.orchestrationDraftTestedConfig || "") === currentGraphSerialized && !dirty;
737
772
  const savedDraftValue = String(sandboxRow?.[draftFieldName] || "").trim();
738
773
  const draftStatus = String(sandboxRow?.orchestrationDraftStatus || "").trim();
739
- const hasSavedDraft = Boolean(savedDraftValue) && draftStatus !== "published";
774
+ const hasSavedDraft = Boolean(savedDraftValue) && draftStatus !== "published" && graphHasNodes(parseOrchestrationGraph(savedDraftValue));
740
775
  const isDraftMode = dirty || hasSavedDraft;
741
776
  const canTest = !graphUnset && !graphBlankShell && Boolean(sandboxRow) && !Boolean(graphError);
742
777
  const showDiscardDraft = isDraftMode;
@@ -871,6 +906,7 @@ export default function WorkflowSurface() {
871
906
  disabled={false}
872
907
  onStartFromRegistry={registryRow ? startFromRegistry : undefined}
873
908
  onStartBlank={startBlank}
909
+ onStartAgentSwarm={startAgentSwarm}
874
910
  onPasteGraph={applyPastedGraph}
875
911
  />
876
912
  ) : graphBlankShell ? (
@@ -932,7 +968,27 @@ export default function WorkflowSurface() {
932
968
  />
933
969
  </div>
934
970
  )}
935
- {graphUiState === "populated" && !runSetupOpen && !addTarget && selectedNode && (
971
+ {graphUiState === "populated" && !runSetupOpen && !addTarget && selectedNode && swarmMode && selectedNode?.type === "thinAdapter" && (
972
+ <div className="dm-orchestration-sidecar__config-col">
973
+ <div className="dm-workflow-panel-head">
974
+ <button type="button" className="dm-workflow-icon-btn" onClick={() => setSelectedNodeId("")} aria-label="Close side panel">
975
+ <X size={14} />
976
+ </button>
977
+ <span>Agent swarm</span>
978
+ <em>agent-swarm-v1</em>
979
+ </div>
980
+ <AgentSwarmPanel
981
+ graph={orchestrationGraph}
982
+ disabled={false}
983
+ onGraphChange={(updater) => {
984
+ setOrchestrationGraph((g) => (typeof updater === "function" ? updater(g) : updater));
985
+ setDirty(true);
986
+ }}
987
+ />
988
+ {graphError && <p className="dm-orchestration-config__error">{graphError}</p>}
989
+ </div>
990
+ )}
991
+ {graphUiState === "populated" && !runSetupOpen && !addTarget && selectedNode && !(swarmMode && selectedNode?.type === "thinAdapter") && (
936
992
  <div className="dm-orchestration-sidecar__config-col">
937
993
  <div className="dm-workflow-panel-head">
938
994
  <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";
@@ -293,7 +306,7 @@ const GRID_COLUMNS = 12;
293
306
  const GRID_ROWS = 16;
294
307
  const GRID_CELL_COUNT = GRID_COLUMNS * GRID_ROWS;
295
308
  const DEFAULT_TAB_ID = "tab-default";
296
- const COLLAPSED_GRID_COLUMNS = "264px minmax(0, 1fr)";
309
+ const COLLAPSED_GRID_COLUMNS = "var(--workspace-rail-width, 264px) minmax(0, 1fr)";
297
310
 
298
311
  function generateId(prefix) {
299
312
  if (typeof globalThis !== "undefined" && globalThis.crypto?.randomUUID) {
@@ -3915,8 +3928,9 @@ function WorkspaceBuilder({ initialConfig, initialSourceRecords, adapterConfig,
3915
3928
  const resizeDragRef = useRef(null);
3916
3929
  const moveDragRef = useRef(null);
3917
3930
  const importInputRef = useRef(null);
3918
- const addSlot = dragPreview || selectedPosition;
3919
3931
  const selectedWidgetLookupId = selectedWidgetId || pendingSelectedWidgetId;
3932
+ const addSlot = dragPreview || selectedPosition;
3933
+ const showAddWidgetSlot = dashboardDraftMode && !selectedWidgetLookupId && panelOpen && addSlot;
3920
3934
  const selectedWidget = activeWidgets.find((widget) => widget.id === selectedWidgetLookupId) || null;
3921
3935
  const availableIntegrations = useMemo(() => flattenIntegrationSettings(integrationSettings), [integrationSettings]);
3922
3936
  const dataModelTables = useMemo(
@@ -5136,6 +5150,12 @@ function WorkspaceBuilder({ initialConfig, initialSourceRecords, adapterConfig,
5136
5150
  return () => window.removeEventListener("keydown", handler);
5137
5151
  }, [addWidget, commandPaletteOpen, managementOpen, panelOpen, settingsOpen, templateGalleryOpen, workspaceView]);
5138
5152
 
5153
+ useEffect(() => {
5154
+ const openFromRail = () => setCommandPaletteOpen(true);
5155
+ window.addEventListener("growthub:open-command-palette", openFromRail);
5156
+ return () => window.removeEventListener("growthub:open-command-palette", openFromRail);
5157
+ }, []);
5158
+
5139
5159
  const builderStyle = workspaceView === "dashboards" || !panelOpen
5140
5160
  ? { gridTemplateColumns: COLLAPSED_GRID_COLUMNS }
5141
5161
  : undefined;
@@ -5304,6 +5324,7 @@ function WorkspaceBuilder({ initialConfig, initialSourceRecords, adapterConfig,
5304
5324
  <WorkspaceRail
5305
5325
  workspaceConfig={config}
5306
5326
  authority={integrationAdapter.authority}
5327
+ defaultCollapsed={workspaceView === "builder"}
5307
5328
  helperOpen={helperOpen}
5308
5329
  onOpenHelper={() => {
5309
5330
  if (helperOpen) { setHelperOpen(false); return; }
@@ -5341,11 +5362,11 @@ function WorkspaceBuilder({ initialConfig, initialSourceRecords, adapterConfig,
5341
5362
  )}
5342
5363
  />
5343
5364
 
5344
- <section className={`workspace-surface${workspaceView === "builder" ? " dm-workflow-surface workspace-dashboard-surface" : ""}`}>
5365
+ <section className={`workspace-surface${workspaceView === "builder" ? ` dm-workflow-surface workspace-dashboard-surface${dashboardDraftMode ? " is-dashboard-editing" : ""}` : ""}`}>
5345
5366
  <header className={`workspace-toolbar${workspaceView === "builder" ? " dm-workflow-toolbar" : ""}`}>
5346
5367
  <div className={workspaceView === "builder" ? "dm-workflow-titlebar" : undefined}>
5347
5368
  {workspaceView === "builder" ? <>
5348
- <span className="dm-workflow-title-muted">Dashboards</span>
5369
+ <button type="button" className="dm-workflow-breadcrumb-link" onClick={showDashboardHome}>Dashboards</button>
5349
5370
  <span className="dm-workflow-title-separator">/</span>
5350
5371
  <h1>{activeDashboard?.name || "Untitled"}</h1>
5351
5372
  <span className="dm-workflow-count">({activeWidgets.length}) · v{activeDashboard?.version || "1"} · {dashboardModeLabel}</span>
@@ -5596,7 +5617,7 @@ function WorkspaceBuilder({ initialConfig, initialSourceRecords, adapterConfig,
5596
5617
  <button type="button" onClick={duplicateTab} disabled={!dashboardDraftMode}><Copy size={15} />Duplicate Tab</button>
5597
5618
  </div>
5598
5619
  <div
5599
- className={`workspace-grid${moveDrag ? " moving-widget" : ""}`}
5620
+ className={`workspace-grid${moveDrag ? " moving-widget" : ""}${dashboardDraftMode ? " is-edit-mode" : " is-view-mode"}`}
5600
5621
  ref={gridRef}
5601
5622
  onPointerMove={updatePointerDrag}
5602
5623
  onPointerUp={(event) => {
@@ -5614,7 +5635,7 @@ function WorkspaceBuilder({ initialConfig, initialSourceRecords, adapterConfig,
5614
5635
  }}
5615
5636
  style={{ "--workspace-columns": canvas.layout.columns, "--workspace-rows": GRID_ROWS }}
5616
5637
  >
5617
- {Array.from({ length: GRID_CELL_COUNT }).map((_, index) => {
5638
+ {dashboardDraftMode ? Array.from({ length: GRID_CELL_COUNT }).map((_, index) => {
5618
5639
  const x = index % GRID_COLUMNS;
5619
5640
  const y = Math.floor(index / GRID_COLUMNS);
5620
5641
  const isOccupied = occupiedCells.has(`${x}:${y}`);
@@ -5632,15 +5653,15 @@ function WorkspaceBuilder({ initialConfig, initialSourceRecords, adapterConfig,
5632
5653
  }}
5633
5654
  type="button"
5634
5655
  />;
5635
- })}
5636
- <button className={`workspace-add-widget${dragPreview ? " selecting" : ""}`} type="button" disabled={!dashboardDraftMode} onClick={() => dashboardDraftMode && setPanelOpen(true)} style={{
5656
+ }) : null}
5657
+ {showAddWidgetSlot ? <button className={`workspace-add-widget${dragPreview ? " selecting" : ""}`} type="button" onClick={() => setPanelOpen(true)} style={{
5637
5658
  gridColumn: `${addSlot.x + 1} / span ${addSlot.w}`,
5638
5659
  gridRow: `${addSlot.y + 1} / span ${addSlot.h}`
5639
5660
  }}>
5640
5661
  <span className="workspace-widget-icon" aria-hidden="true"><span /></span>
5641
5662
  <strong>Add widget</strong>
5642
5663
  <small>Click to add your first widget</small>
5643
- </button>
5664
+ </button> : null}
5644
5665
  {activeWidgets.map((widget) => <WidgetPreview
5645
5666
  key={widget.id}
5646
5667
  branding={branding}
@@ -1488,6 +1488,7 @@ export function WorkspaceRail({
1488
1488
  onConfigChange,
1489
1489
  dashboardsSlot,
1490
1490
  dataModelSlot,
1491
+ defaultCollapsed = false,
1491
1492
  // `managementSlot` retained as accepted-but-ignored prop for backward
1492
1493
  // compatibility with callers that still pass it. The Management item
1493
1494
  // moved to the Workspace Settings → Ownership tab.
@@ -1500,7 +1501,7 @@ export function WorkspaceRail({
1500
1501
  const router = useRouter();
1501
1502
 
1502
1503
  const [activeTab, setActiveTab] = useState("home");
1503
- const [railCollapsed, setRailCollapsed] = useState(false);
1504
+ const [railCollapsed, setRailCollapsed] = useState(Boolean(defaultCollapsed));
1504
1505
  const [openMenuId, setOpenMenuId] = useState(null);
1505
1506
  const [renamingId, setRenamingId] = useState(null);
1506
1507
  const [renameDraft, setRenameDraft] = useState("");
@@ -1521,6 +1522,10 @@ export function WorkspaceRail({
1521
1522
 
1522
1523
  const threads = useMemo(() => getHelperThreadRows(workspaceConfig), [workspaceConfig]);
1523
1524
 
1525
+ useEffect(() => {
1526
+ setRailCollapsed(Boolean(defaultCollapsed));
1527
+ }, [defaultCollapsed]);
1528
+
1524
1529
  useEffect(() => {
1525
1530
  if (typeof document === "undefined") return undefined;
1526
1531
  document.body.classList.toggle("workspace-rail-collapsed", railCollapsed);
@@ -1640,6 +1645,16 @@ export function WorkspaceRail({
1640
1645
  <ChevronDown size={13} className="workspace-brand-caret" aria-hidden="true" />
1641
1646
  </button>
1642
1647
  <div className="workspace-rail-topbar-actions">
1648
+ <button
1649
+ type="button"
1650
+ className="workspace-rail-icon-btn"
1651
+ aria-label={railCollapsed ? "Expand sidebar" : "Collapse sidebar"}
1652
+ title={railCollapsed ? "Expand sidebar" : "Collapse sidebar"}
1653
+ aria-pressed={railCollapsed}
1654
+ onClick={() => setRailCollapsed((v) => !v)}
1655
+ >
1656
+ <PanelLeftClose size={13} />
1657
+ </button>
1643
1658
  <button
1644
1659
  type="button"
1645
1660
  className="workspace-rail-icon-btn"
@@ -1647,9 +1662,6 @@ export function WorkspaceRail({
1647
1662
  title="Search (⌘K)"
1648
1663
  data-rail-search=""
1649
1664
  onClick={() => {
1650
- // Surfaces with a command palette (DataModelShell) listen
1651
- // for this event and open the palette in place. Other
1652
- // surfaces are free to ignore it.
1653
1665
  if (typeof window !== "undefined") {
1654
1666
  window.dispatchEvent(new CustomEvent("growthub:open-command-palette"));
1655
1667
  }
@@ -1657,16 +1669,6 @@ export function WorkspaceRail({
1657
1669
  >
1658
1670
  <Search size={13} />
1659
1671
  </button>
1660
- <button
1661
- type="button"
1662
- className="workspace-rail-icon-btn"
1663
- aria-label={railCollapsed ? "Expand sidebar" : "Collapse sidebar"}
1664
- title={railCollapsed ? "Expand sidebar" : "Collapse sidebar"}
1665
- aria-pressed={railCollapsed}
1666
- onClick={() => setRailCollapsed((v) => !v)}
1667
- >
1668
- <PanelLeftClose size={13} />
1669
- </button>
1670
1672
  <button
1671
1673
  type="button"
1672
1674
  className="workspace-rail-icon-btn"
@@ -12,6 +12,13 @@ function readAdapterConfig() {
12
12
  userId: process.env.GROWTHUB_BRIDGE_USER_ID || void 0,
13
13
  hasAccessToken: Boolean(process.env.GROWTHUB_BRIDGE_ACCESS_TOKEN)
14
14
  },
15
+ nango: {
16
+ mode: readEnum(["NANGO_MODE"], ["cloud", "self-hosted"], "cloud"),
17
+ hostUrl: process.env.NANGO_HOST_URL || void 0,
18
+ environment: process.env.NANGO_ENVIRONMENT || "dev",
19
+ secretEnvName: "NANGO_SECRET_KEY",
20
+ hasSecretKey: Boolean(process.env.NANGO_SECRET_KEY)
21
+ },
15
22
  dataSources: {
16
23
  hasWindsorApiKey: Boolean(process.env.WINDSOR_API_KEY)
17
24
  }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Nango adapter barrel — server-side imports only.
3
+ *
4
+ * The browser must never import any module under this path. Routes consume
5
+ * this barrel; UI components hit the routes.
6
+ */
7
+
8
+ export {
9
+ DEFAULT_NANGO_SECRET_ENV,
10
+ createConnectSession,
11
+ describeNangoAdapter,
12
+ executeAction,
13
+ getConnectionSummary,
14
+ getStatus,
15
+ listActions,
16
+ pickSafeConnectionFields,
17
+ projectNangoBinding,
18
+ proxyRequest,
19
+ resolveNangoEnv
20
+ } from "./nango-adapter.js";
21
+
22
+ export {
23
+ buildNangoResolver,
24
+ registerNangoResolversFromConfig
25
+ } from "./nango-config-loader.js";
26
+
27
+ export {
28
+ validateActionExecuteRequest,
29
+ validateActionsListInput,
30
+ validateConnectSessionRequest,
31
+ validateConnectionId,
32
+ validateConnectionStatusRequest,
33
+ validateConnectionSummaryRequest,
34
+ validateHostUrl,
35
+ validateNangoMode,
36
+ validateProviderConfigKey,
37
+ validateProxyRequest
38
+ } from "./nango-schema.js";