@growthub/cli 0.13.5 → 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 (35) 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/data-model/components/DataModelShell.jsx +161 -50
  11. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/NangoConnectionPanel.jsx +496 -0
  12. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/globals.css +104 -17
  13. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/settings/integrations/nango/page.jsx +167 -0
  14. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/settings/integrations/page.jsx +1 -0
  15. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workflows/WorkflowSurface.jsx +18 -7
  16. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workspace-builder.jsx +17 -9
  17. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workspace-rail.jsx +16 -14
  18. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/env.js +7 -0
  19. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/nango/index.js +38 -0
  20. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/nango/nango-adapter.js +552 -0
  21. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/nango/nango-config-loader.js +202 -0
  22. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/nango/nango-schema.js +303 -0
  23. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/resolver-loader.js +49 -10
  24. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/source-resolver-registry.js +1 -1
  25. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/templates/nango.js +49 -0
  26. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/templates/template-registry.js +4 -2
  27. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-graph.js +2 -2
  28. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-data-model.js +2 -1
  29. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-schema.js +102 -3
  30. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/package.json +1 -0
  31. package/assets/worker-kits/growthub-custom-workspace-starter-v1/bundles/growthub-custom-workspace-starter-v1.json +1 -0
  32. package/assets/worker-kits/growthub-custom-workspace-starter-v1/kit.json +2 -0
  33. package/assets/worker-kits/growthub-custom-workspace-starter-v1/templates/seeded-configs/project-management.config.json +276 -0
  34. package/dist/index.js +127 -44
  35. package/package.json +1 -1
@@ -0,0 +1,167 @@
1
+ import Link from "next/link";
2
+ import { Suspense } from "react";
3
+ import { describeNangoAdapter, getStatus } from "@/lib/adapters/integrations/nango";
4
+ import { readWorkspaceConfig } from "@/lib/workspace-config";
5
+ import { WorkspaceRail } from "../../../workspace-rail.jsx";
6
+
7
+ /**
8
+ * Nango integration settings — inspect-only panel.
9
+ *
10
+ * Reads from the EXISTING `objectType: "api-registry"` rows in
11
+ * `dataModel.objects[]`. Rows that declare `connectorKind: "nango"` are
12
+ * Nango-backed; other connectorKinds (http, mcp, chrome, tool) are shown
13
+ * here only for context — they keep going through their own resolvers.
14
+ *
15
+ * Secrets are not displayed. The Nango secret lives in env (default env-ref
16
+ * name: NANGO_SECRET_KEY); the row's `authRef` column names the env-ref.
17
+ */
18
+
19
+ function listApiRegistryObjects(workspaceConfig) {
20
+ const objects = workspaceConfig?.dataModel?.objects;
21
+ if (!Array.isArray(objects)) return [];
22
+ return objects.filter((object) => object?.objectType === "api-registry");
23
+ }
24
+
25
+ function flattenNangoRows(apiRegistryObjects) {
26
+ const rows = [];
27
+ for (const object of apiRegistryObjects) {
28
+ const objectRows = Array.isArray(object.rows) ? object.rows : [];
29
+ for (const row of objectRows) {
30
+ if (row?.connectorKind === "nango") {
31
+ rows.push({ object, row });
32
+ }
33
+ }
34
+ }
35
+ return rows;
36
+ }
37
+
38
+ function summarizeList(value) {
39
+ if (Array.isArray(value)) return value.filter(Boolean);
40
+ if (typeof value === "string" && value.trim()) {
41
+ return value.split(",").map((v) => v.trim()).filter(Boolean);
42
+ }
43
+ return [];
44
+ }
45
+
46
+ function NangoRowCard({ object, row }) {
47
+ const connectionIds = summarizeList(row.connectionIds);
48
+ const enabledActions = summarizeList(row.enabledActions);
49
+ const providerConfigKey = row.providerConfigKey || row.integrationId;
50
+ return <article className="workspace-integration-row">
51
+ <div className="workspace-provider-mark">{String(row.integrationId || "?").slice(0, 1).toUpperCase()}</div>
52
+ <div className="workspace-integration-main">
53
+ <strong>{row.integrationId}</strong>
54
+ <p>providerConfigKey: <code>{providerConfigKey || "—"}</code></p>
55
+ <div className="workspace-integration-meta">
56
+ <span>object: <code>{object.id}</code></span>
57
+ <span>{row.nangoMode || "cloud"}</span>
58
+ <span>env: {row.nangoEnvironment || "dev"}</span>
59
+ <span>{connectionIds.length} {connectionIds.length === 1 ? "connection" : "connections"}</span>
60
+ <span>{enabledActions.length} {enabledActions.length === 1 ? "action" : "actions"}</span>
61
+ {row.authRef ? <span><code>{row.authRef}</code></span> : null}
62
+ {row.endpoint ? <span>endpoint: <code>{row.endpoint}</code></span> : null}
63
+ </div>
64
+ </div>
65
+ <span className={`workspace-integration-status ${row.status || "configured"}`}>{row.status || "configured"}</span>
66
+ </article>;
67
+ }
68
+
69
+ async function NangoIntegrationsSettingsPage() {
70
+ const adapter = describeNangoAdapter();
71
+ const workspaceConfig = await readWorkspaceConfig();
72
+ const apiRegistryObjects = listApiRegistryObjects(workspaceConfig);
73
+ const nangoRows = flattenNangoRows(apiRegistryObjects);
74
+
75
+ let status;
76
+ try {
77
+ status = await getStatus();
78
+ } catch (error) {
79
+ status = { status: "disconnected", reason: error?.message || "status probe failed" };
80
+ }
81
+
82
+ return <main className="workspace-builder workspace-settings-page">
83
+ <Suspense fallback={null}>
84
+ <WorkspaceRail
85
+ workspaceConfig={workspaceConfig}
86
+ authority={adapter.authority}
87
+ managementSlot={(
88
+ <Link className="active" href="/settings/integrations">Integrations</Link>
89
+ )}
90
+ />
91
+ </Suspense>
92
+
93
+ <section className="workspace-surface">
94
+ <header className="workspace-toolbar">
95
+ <div>
96
+ <p>Workspace settings &middot; Integrations</p>
97
+ <h1>Nango</h1>
98
+ </div>
99
+ <div className="workspace-toolbar-actions">
100
+ <Link href="/settings/integrations">All integrations</Link>
101
+ <span>{adapter.id}</span>
102
+ <span>{adapter.authority}</span>
103
+ </div>
104
+ </header>
105
+
106
+ <section className="workspace-integration-summary" aria-label="Nango adapter summary">
107
+ <article>
108
+ <span>Adapter</span>
109
+ <strong>{adapter.label}</strong>
110
+ <div>
111
+ <code>{adapter.secretEnvName}</code>
112
+ <span> &middot; row-scoped via <code>connectorKind: "nango"</code></span>
113
+ </div>
114
+ </article>
115
+ <article>
116
+ <span>Mode</span>
117
+ <strong>{adapter.mode}</strong>
118
+ <div>{adapter.hostUrl ? <code>{adapter.hostUrl}</code> : <code>nango cloud</code>}</div>
119
+ </article>
120
+ <article>
121
+ <span>Status</span>
122
+ <strong>{status.status}</strong>
123
+ <div>{status.reason ? <span>{status.reason}</span> : <span>environment: <code>{status.environment || adapter.environment}</code></span>}</div>
124
+ </article>
125
+ </section>
126
+
127
+ <section className="workspace-integration-toolbar">
128
+ <div>
129
+ <strong>Nango-backed API Registry rows</strong>
130
+ <p>Rows in <code>dataModel.objects[]</code> with <code>objectType: "api-registry"</code> and <code>connectorKind: "nango"</code>. Edit <code>growthub.config.json</code> to add or remove rows — secrets stay in env, referenced by the row's <code>authRef</code>.</p>
131
+ </div>
132
+ </section>
133
+
134
+ <section className="workspace-integration-board">
135
+ {nangoRows.length === 0
136
+ ? <article className="workspace-integration-section">
137
+ <div className="workspace-integration-section-heading">
138
+ <div>
139
+ <h2>No Nango-backed rows yet</h2>
140
+ <p>Add a row to an <code>api-registry</code> object with <code>connectorKind: "nango"</code>, an <code>integrationId</code>, and an optional <code>providerConfigKey</code>. Apply the <code>nango</code> resolver template from the Data Model template picker for sensible defaults.</p>
141
+ </div>
142
+ </div>
143
+ </article>
144
+ : <article className="workspace-integration-section">
145
+ <div className="workspace-integration-section-heading">
146
+ <div>
147
+ <h2>Nango providers</h2>
148
+ <p>{nangoRows.length} row{nangoRows.length === 1 ? "" : "s"} across {apiRegistryObjects.length} API Registry object{apiRegistryObjects.length === 1 ? "" : "s"}.</p>
149
+ </div>
150
+ <span>{nangoRows.length}</span>
151
+ </div>
152
+ <div className="workspace-integration-list">
153
+ {nangoRows.map(({ object, row }, index) => <NangoRowCard
154
+ object={object}
155
+ row={row}
156
+ key={`${object.id}:${row.integrationId || index}`}
157
+ />)}
158
+ </div>
159
+ </article>}
160
+ </section>
161
+ </section>
162
+ </main>;
163
+ }
164
+
165
+ export {
166
+ NangoIntegrationsSettingsPage as default
167
+ };
@@ -77,6 +77,7 @@ async function IntegrationsSettingsPage() {
77
77
  </div>
78
78
  <div className="workspace-toolbar-actions">
79
79
  <Link href="/api/settings/integrations">API contract</Link>
80
+ <Link href="/settings/integrations/nango">Nango</Link>
80
81
  <span>{adapter.id}</span>
81
82
  <span>{adapter.authority}</span>
82
83
  </div>
@@ -62,7 +62,7 @@ const WORKFLOW_METADATA_SELECTORS = Object.freeze({
62
62
  });
63
63
 
64
64
  function resolveRegistryRowForSandbox(workspaceConfig, sandboxRow) {
65
- const graph = parseOrchestrationGraph(sandboxRow?.orchestrationGraph);
65
+ const graph = parseOrchestrationGraph(sandboxRow?.orchestrationConfig || sandboxRow?.orchestrationGraph);
66
66
  const apiNode = graph?.nodes?.find((n) => n?.type === "api-registry-call");
67
67
  const registryId = String(
68
68
  apiNode?.config?.registryId || apiNode?.config?.integrationId || sandboxRow?.schedulerRegistryId || ""
@@ -291,6 +291,10 @@ function isPassingRun(payload) {
291
291
  return payload?.ok === true && Number(payload?.exitCode ?? payload?.response?.exitCode) === 0;
292
292
  }
293
293
 
294
+ function graphHasNodes(graph) {
295
+ return Array.isArray(graph?.nodes) && graph.nodes.length > 0;
296
+ }
297
+
294
298
  function WorkflowAddStepPanel({ target, onSelect }) {
295
299
  return (
296
300
  <div className="dm-workflow-add-panel">
@@ -323,7 +327,7 @@ export default function WorkflowSurface() {
323
327
  const searchParams = useSearchParams();
324
328
  const objectId = String(searchParams.get("object") || "").trim();
325
329
  const rowId = String(searchParams.get("row") || "").trim();
326
- const fieldName = String(searchParams.get("field") || "orchestrationGraph").trim();
330
+ const fieldName = String(searchParams.get("field") || "orchestrationConfig").trim();
327
331
  const runId = String(searchParams.get("run") || "").trim();
328
332
 
329
333
  const [workspaceConfig, setWorkspaceConfig] = useState(null);
@@ -369,11 +373,14 @@ export default function WorkflowSurface() {
369
373
  );
370
374
 
371
375
  const sandboxRow = resolved.row;
372
- const effectiveFieldName = sandboxRow?.[fieldName] !== undefined
376
+ const hasGraphValue = (value) => Boolean(parseOrchestrationGraph(value));
377
+ const effectiveFieldName = hasGraphValue(sandboxRow?.[fieldName])
373
378
  ? fieldName
374
- : sandboxRow?.orchestrationConfig !== undefined
379
+ : hasGraphValue(sandboxRow?.orchestrationConfig)
375
380
  ? "orchestrationConfig"
376
- : "orchestrationGraph";
381
+ : hasGraphValue(sandboxRow?.orchestrationGraph)
382
+ ? "orchestrationGraph"
383
+ : (fieldName || "orchestrationConfig");
377
384
  const draftFieldName = effectiveFieldName === "orchestrationConfig" ? "orchestrationDraftConfig" : "orchestrationDraftGraph";
378
385
  const registryRow = useMemo(
379
386
  () => (sandboxRow && workspaceConfig ? resolveRegistryRowForSandbox(workspaceConfig, sandboxRow) : null),
@@ -386,7 +393,11 @@ export default function WorkflowSurface() {
386
393
 
387
394
  useEffect(() => {
388
395
  if (!sandboxRow) return;
389
- 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;
390
401
  setOrchestrationGraph(parsed);
391
402
  setDirty(false);
392
403
  setGraphError("");
@@ -760,7 +771,7 @@ export default function WorkflowSurface() {
760
771
  const publishReady = draftPassed && String(sandboxRow?.orchestrationDraftTestedConfig || "") === currentGraphSerialized && !dirty;
761
772
  const savedDraftValue = String(sandboxRow?.[draftFieldName] || "").trim();
762
773
  const draftStatus = String(sandboxRow?.orchestrationDraftStatus || "").trim();
763
- const hasSavedDraft = Boolean(savedDraftValue) && draftStatus !== "published";
774
+ const hasSavedDraft = Boolean(savedDraftValue) && draftStatus !== "published" && graphHasNodes(parseOrchestrationGraph(savedDraftValue));
764
775
  const isDraftMode = dirty || hasSavedDraft;
765
776
  const canTest = !graphUnset && !graphBlankShell && Boolean(sandboxRow) && !Boolean(graphError);
766
777
  const showDiscardDraft = isDraftMode;
@@ -306,7 +306,7 @@ const GRID_COLUMNS = 12;
306
306
  const GRID_ROWS = 16;
307
307
  const GRID_CELL_COUNT = GRID_COLUMNS * GRID_ROWS;
308
308
  const DEFAULT_TAB_ID = "tab-default";
309
- const COLLAPSED_GRID_COLUMNS = "264px minmax(0, 1fr)";
309
+ const COLLAPSED_GRID_COLUMNS = "var(--workspace-rail-width, 264px) minmax(0, 1fr)";
310
310
 
311
311
  function generateId(prefix) {
312
312
  if (typeof globalThis !== "undefined" && globalThis.crypto?.randomUUID) {
@@ -3928,8 +3928,9 @@ function WorkspaceBuilder({ initialConfig, initialSourceRecords, adapterConfig,
3928
3928
  const resizeDragRef = useRef(null);
3929
3929
  const moveDragRef = useRef(null);
3930
3930
  const importInputRef = useRef(null);
3931
- const addSlot = dragPreview || selectedPosition;
3932
3931
  const selectedWidgetLookupId = selectedWidgetId || pendingSelectedWidgetId;
3932
+ const addSlot = dragPreview || selectedPosition;
3933
+ const showAddWidgetSlot = dashboardDraftMode && !selectedWidgetLookupId && panelOpen && addSlot;
3933
3934
  const selectedWidget = activeWidgets.find((widget) => widget.id === selectedWidgetLookupId) || null;
3934
3935
  const availableIntegrations = useMemo(() => flattenIntegrationSettings(integrationSettings), [integrationSettings]);
3935
3936
  const dataModelTables = useMemo(
@@ -5149,6 +5150,12 @@ function WorkspaceBuilder({ initialConfig, initialSourceRecords, adapterConfig,
5149
5150
  return () => window.removeEventListener("keydown", handler);
5150
5151
  }, [addWidget, commandPaletteOpen, managementOpen, panelOpen, settingsOpen, templateGalleryOpen, workspaceView]);
5151
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
+
5152
5159
  const builderStyle = workspaceView === "dashboards" || !panelOpen
5153
5160
  ? { gridTemplateColumns: COLLAPSED_GRID_COLUMNS }
5154
5161
  : undefined;
@@ -5317,6 +5324,7 @@ function WorkspaceBuilder({ initialConfig, initialSourceRecords, adapterConfig,
5317
5324
  <WorkspaceRail
5318
5325
  workspaceConfig={config}
5319
5326
  authority={integrationAdapter.authority}
5327
+ defaultCollapsed={workspaceView === "builder"}
5320
5328
  helperOpen={helperOpen}
5321
5329
  onOpenHelper={() => {
5322
5330
  if (helperOpen) { setHelperOpen(false); return; }
@@ -5354,11 +5362,11 @@ function WorkspaceBuilder({ initialConfig, initialSourceRecords, adapterConfig,
5354
5362
  )}
5355
5363
  />
5356
5364
 
5357
- <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" : ""}` : ""}`}>
5358
5366
  <header className={`workspace-toolbar${workspaceView === "builder" ? " dm-workflow-toolbar" : ""}`}>
5359
5367
  <div className={workspaceView === "builder" ? "dm-workflow-titlebar" : undefined}>
5360
5368
  {workspaceView === "builder" ? <>
5361
- <span className="dm-workflow-title-muted">Dashboards</span>
5369
+ <button type="button" className="dm-workflow-breadcrumb-link" onClick={showDashboardHome}>Dashboards</button>
5362
5370
  <span className="dm-workflow-title-separator">/</span>
5363
5371
  <h1>{activeDashboard?.name || "Untitled"}</h1>
5364
5372
  <span className="dm-workflow-count">({activeWidgets.length}) · v{activeDashboard?.version || "1"} · {dashboardModeLabel}</span>
@@ -5609,7 +5617,7 @@ function WorkspaceBuilder({ initialConfig, initialSourceRecords, adapterConfig,
5609
5617
  <button type="button" onClick={duplicateTab} disabled={!dashboardDraftMode}><Copy size={15} />Duplicate Tab</button>
5610
5618
  </div>
5611
5619
  <div
5612
- className={`workspace-grid${moveDrag ? " moving-widget" : ""}`}
5620
+ className={`workspace-grid${moveDrag ? " moving-widget" : ""}${dashboardDraftMode ? " is-edit-mode" : " is-view-mode"}`}
5613
5621
  ref={gridRef}
5614
5622
  onPointerMove={updatePointerDrag}
5615
5623
  onPointerUp={(event) => {
@@ -5627,7 +5635,7 @@ function WorkspaceBuilder({ initialConfig, initialSourceRecords, adapterConfig,
5627
5635
  }}
5628
5636
  style={{ "--workspace-columns": canvas.layout.columns, "--workspace-rows": GRID_ROWS }}
5629
5637
  >
5630
- {Array.from({ length: GRID_CELL_COUNT }).map((_, index) => {
5638
+ {dashboardDraftMode ? Array.from({ length: GRID_CELL_COUNT }).map((_, index) => {
5631
5639
  const x = index % GRID_COLUMNS;
5632
5640
  const y = Math.floor(index / GRID_COLUMNS);
5633
5641
  const isOccupied = occupiedCells.has(`${x}:${y}`);
@@ -5645,15 +5653,15 @@ function WorkspaceBuilder({ initialConfig, initialSourceRecords, adapterConfig,
5645
5653
  }}
5646
5654
  type="button"
5647
5655
  />;
5648
- })}
5649
- <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={{
5650
5658
  gridColumn: `${addSlot.x + 1} / span ${addSlot.w}`,
5651
5659
  gridRow: `${addSlot.y + 1} / span ${addSlot.h}`
5652
5660
  }}>
5653
5661
  <span className="workspace-widget-icon" aria-hidden="true"><span /></span>
5654
5662
  <strong>Add widget</strong>
5655
5663
  <small>Click to add your first widget</small>
5656
- </button>
5664
+ </button> : null}
5657
5665
  {activeWidgets.map((widget) => <WidgetPreview
5658
5666
  key={widget.id}
5659
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";