@growthub/cli 0.13.6 → 0.13.7

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.
@@ -152,6 +152,7 @@ async function GET(request) {
152
152
  runs: metadataStore.runs,
153
153
  outputArtifacts: metadataStore.outputArtifacts,
154
154
  workerKits: metadataStore.workerKits,
155
+ provenance: metadataStore.provenance,
155
156
  pipelineHealth: metadataStore.pipelineHealth
156
157
  },
157
158
  graph: {
@@ -0,0 +1,172 @@
1
+ "use client";
2
+
3
+ /**
4
+ * WorkspaceActivationPanel — customer activation checklist.
5
+ *
6
+ * Renders the derived activation state from
7
+ * `lib/workspace-activation.js` as a goal-first checklist with deep links
8
+ * into the existing surfaces. The panel is read-only; every step routes
9
+ * through routes the workspace already owns.
10
+ *
11
+ * Props:
12
+ * workspaceConfig — parsed growthub.config.json (governed)
13
+ * workspaceSourceRecords — parsed growthub.source-records.json (sidecar)
14
+ * metadataGraph — optional metadata graph envelope
15
+ * onOpenHelper — optional helper-thread CTA handler
16
+ * compact — render the rail-friendly compact variant
17
+ *
18
+ * Invariants:
19
+ * - No fetch. No mutation. No secret reading.
20
+ * - Reads only safe data (booleans, status strings, deep-link routes).
21
+ * - Falls back to a sensible default state if config is missing.
22
+ */
23
+
24
+ import Link from "next/link";
25
+ import {
26
+ ArrowRight,
27
+ Check,
28
+ CircleDot,
29
+ HelpCircle,
30
+ Lock,
31
+ Sparkles,
32
+ } from "lucide-react";
33
+ import { deriveWorkspaceActivationState } from "@/lib/workspace-activation";
34
+
35
+ function StatusIcon({ status }) {
36
+ if (status === "complete") return <Check size={14} aria-hidden="true" />;
37
+ if (status === "blocked") return <Lock size={14} aria-hidden="true" />;
38
+ if (status === "optional") return <Sparkles size={14} aria-hidden="true" />;
39
+ return <CircleDot size={14} aria-hidden="true" />;
40
+ }
41
+
42
+ function StatusLabel({ status }) {
43
+ if (status === "complete") return "Done";
44
+ if (status === "blocked") return "Blocked";
45
+ if (status === "optional") return "Optional";
46
+ return "Next";
47
+ }
48
+
49
+ export function WorkspaceActivationPanel({
50
+ workspaceConfig,
51
+ workspaceSourceRecords,
52
+ metadataGraph,
53
+ onOpenHelper,
54
+ compact = false,
55
+ }) {
56
+ const state = deriveWorkspaceActivationState({
57
+ workspaceConfig,
58
+ workspaceSourceRecords,
59
+ metadataGraph,
60
+ });
61
+
62
+ const stepsToRender = compact
63
+ ? state.steps.filter((step) => step.status !== "optional")
64
+ : state.steps;
65
+
66
+ return (
67
+ <section
68
+ className={
69
+ "workspace-activation-panel"
70
+ + (compact ? " is-compact" : "")
71
+ + (state.complete ? " is-complete" : "")
72
+ }
73
+ aria-label="Workspace activation"
74
+ data-template={state.template}
75
+ >
76
+ <header className="workspace-activation-head">
77
+ <div className="workspace-activation-head-text">
78
+ <p className="workspace-activation-eyebrow">{state.templateName}</p>
79
+ <h2 className="workspace-activation-headline">{state.headline}</h2>
80
+ {state.subheadline ? (
81
+ <p className="workspace-activation-subheadline">{state.subheadline}</p>
82
+ ) : null}
83
+ </div>
84
+ <div
85
+ className="workspace-activation-progress"
86
+ aria-label={`${state.completedCount} of ${state.totalCount} setup steps complete`}
87
+ >
88
+ <span className="workspace-activation-progress-value">
89
+ {state.completedCount}/{state.totalCount}
90
+ </span>
91
+ <span className="workspace-activation-progress-bar" aria-hidden="true">
92
+ <span
93
+ className="workspace-activation-progress-fill"
94
+ style={{
95
+ width: state.totalCount > 0
96
+ ? `${Math.min(100, Math.round((state.completedCount / state.totalCount) * 100))}%`
97
+ : "0%",
98
+ }}
99
+ />
100
+ </span>
101
+ </div>
102
+ </header>
103
+
104
+ <ol className="workspace-activation-steps" role="list">
105
+ {stepsToRender.map((step) => {
106
+ const isNext = state.nextStepId === step.id;
107
+ return (
108
+ <li
109
+ key={step.id}
110
+ className={
111
+ "workspace-activation-step"
112
+ + ` is-${step.status}`
113
+ + (isNext ? " is-next" : "")
114
+ }
115
+ >
116
+ <span className="workspace-activation-step-status" aria-hidden="true">
117
+ <StatusIcon status={step.status} />
118
+ </span>
119
+ <div className="workspace-activation-step-body">
120
+ <div className="workspace-activation-step-titlebar">
121
+ <h3 className="workspace-activation-step-title">{step.label}</h3>
122
+ <span className={`workspace-activation-step-badge is-${step.status}`}>
123
+ <StatusLabel status={step.status} />
124
+ </span>
125
+ </div>
126
+ <p className="workspace-activation-step-description">{step.description}</p>
127
+ {step.hint ? (
128
+ <p className="workspace-activation-step-hint">
129
+ <HelpCircle size={12} aria-hidden="true" />
130
+ <span>{step.hint}</span>
131
+ </p>
132
+ ) : null}
133
+ {step.href ? (
134
+ <Link
135
+ href={step.href}
136
+ className={
137
+ "workspace-activation-step-cta"
138
+ + (isNext ? " is-primary" : "")
139
+ }
140
+ >
141
+ <span>{step.cta || (step.status === "complete" ? "Review" : "Open")}</span>
142
+ <ArrowRight size={12} aria-hidden="true" />
143
+ </Link>
144
+ ) : null}
145
+ </div>
146
+ </li>
147
+ );
148
+ })}
149
+ </ol>
150
+
151
+ {onOpenHelper ? (
152
+ <div className="workspace-activation-helper-cta">
153
+ <button
154
+ type="button"
155
+ className="workspace-activation-helper-btn"
156
+ onClick={() => onOpenHelper({
157
+ template: state.template,
158
+ nextStepId: state.nextStepId,
159
+ })}
160
+ >
161
+ <Sparkles size={13} aria-hidden="true" />
162
+ <span>
163
+ {state.complete
164
+ ? "Ask helper to customize this workspace"
165
+ : `Ask helper to finish ${state.templateName}`}
166
+ </span>
167
+ </button>
168
+ </div>
169
+ ) : null}
170
+ </section>
171
+ );
172
+ }
@@ -1,7 +1,8 @@
1
1
  "use client";
2
2
 
3
3
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
4
- import { CheckCircle, ExternalLink, Loader2, RefreshCw, ShieldCheck, XCircle } from "lucide-react";
4
+ import Link from "next/link";
5
+ import { ArrowRight, CheckCircle, ExternalLink, Loader2, RefreshCw, ShieldCheck, XCircle } from "lucide-react";
5
6
 
6
7
  /**
7
8
  * NangoConnectionPanel — interactive sidecar for an api-registry row whose
@@ -114,7 +115,7 @@ function StatusBadge({ status, label }) {
114
115
  );
115
116
  }
116
117
 
117
- export function NangoConnectionPanel({ row, disabled, onUpdateRow }) {
118
+ export function NangoConnectionPanel({ row, disabled, onUpdateRow, templateContext }) {
118
119
  const initialProviderConfigKey = useMemo(() => deriveProviderConfigKey(row), [row]);
119
120
  const initialConnectionId = useMemo(() => deriveDefaultConnectionId(row), [row]);
120
121
  const initialStatus = useMemo(() => deriveInitialStatus(row), [row]);
@@ -491,6 +492,40 @@ export function NangoConnectionPanel({ row, disabled, onUpdateRow }) {
491
492
  </button>
492
493
  ) : null}
493
494
  </div>
495
+
496
+ {/*
497
+ * Template-aware affordances. When this row was scaffolded as part
498
+ * of a workspace template (project-management today), surface a
499
+ * "next step" hint linking back to the activation checklist or the
500
+ * seeded workflow. Other rows render no template context at all.
501
+ */}
502
+ {templateContext && (templateContext.nextHref || templateContext.backHref) ? (
503
+ <div
504
+ className={
505
+ "workspace-template-context-banner"
506
+ + ((statusKind === "connected" || persistedStatus === "connected") ? "" : " is-warn")
507
+ }
508
+ role="note"
509
+ >
510
+ <span>
511
+ {(statusKind === "connected" || persistedStatus === "connected")
512
+ ? (templateContext.nextLabel || "Next: run the seeded workflow.")
513
+ : (templateContext.pendingLabel || "Finish OAuth above, then continue setup.")}
514
+ </span>
515
+ {templateContext.backHref ? (
516
+ <Link href={templateContext.backHref} className="workspace-template-context-link">
517
+ <span>Back to setup checklist</span>
518
+ <ArrowRight size={12} aria-hidden="true" />
519
+ </Link>
520
+ ) : null}
521
+ {templateContext.nextHref ? (
522
+ <Link href={templateContext.nextHref} className="workspace-template-context-link">
523
+ <span>{templateContext.nextCta || "Open workflow"}</span>
524
+ <ArrowRight size={12} aria-hidden="true" />
525
+ </Link>
526
+ ) : null}
527
+ </div>
528
+ ) : null}
494
529
  </section>
495
530
  );
496
531
  }
@@ -559,7 +559,10 @@ body.workspace-rail-collapsed .workspace-builder.dm-workflow-page {
559
559
  padding: 16px;
560
560
  display: flex;
561
561
  flex-direction: column;
562
- overflow: hidden;
562
+ overflow-y: auto;
563
+ overflow-x: hidden;
564
+ scrollbar-gutter: stable;
565
+ overscroll-behavior: contain;
563
566
  }
564
567
  .workspace-toolbar {
565
568
  min-height: 48px;
@@ -639,6 +642,54 @@ body.workspace-rail-collapsed .workspace-builder.dm-workflow-page {
639
642
  .workspace-tabs > button svg {
640
643
  flex: 0 0 auto;
641
644
  }
645
+ .workspace-toolbar-actions > .workspace-finish-setup-control {
646
+ gap: 0;
647
+ padding: 0;
648
+ overflow: hidden;
649
+ }
650
+ .workspace-finish-setup-trigger,
651
+ .workspace-finish-setup-dismiss {
652
+ min-height: 28px;
653
+ border: 0;
654
+ background: transparent;
655
+ color: inherit;
656
+ display: inline-flex;
657
+ align-items: center;
658
+ justify-content: center;
659
+ gap: 6px;
660
+ padding: 0 10px;
661
+ cursor: pointer;
662
+ }
663
+ .workspace-finish-setup-trigger:hover,
664
+ .workspace-finish-setup-dismiss:hover {
665
+ background: #fafafa;
666
+ }
667
+ .workspace-finish-setup-control.is-complete {
668
+ border-color: #bbf7d0;
669
+ color: #047857;
670
+ background: #f0fdf4;
671
+ }
672
+ .workspace-finish-setup-control.is-complete .workspace-finish-setup-trigger:hover,
673
+ .workspace-finish-setup-control.is-complete .workspace-finish-setup-dismiss:hover {
674
+ background: #dcfce7;
675
+ }
676
+ .workspace-finish-setup-dismiss {
677
+ width: 30px;
678
+ padding: 0;
679
+ border-left: 1px solid #bbf7d0;
680
+ }
681
+ .workspace-finish-setup-dismiss-x {
682
+ display: none;
683
+ }
684
+ .workspace-finish-setup-dismiss:hover {
685
+ color: #b91c1c;
686
+ }
687
+ .workspace-finish-setup-dismiss:hover .workspace-finish-setup-dismiss-check {
688
+ display: none;
689
+ }
690
+ .workspace-finish-setup-dismiss:hover .workspace-finish-setup-dismiss-x {
691
+ display: block;
692
+ }
642
693
  .workspace-table,
643
694
  .workspace-canvas {
644
695
  border: 1px solid #e7e7e7;
@@ -4120,6 +4171,68 @@ body.workspace-rail-collapsed .workspace-builder.dm-workflow-page {
4120
4171
  .workspace-chart-type-tabs button.active em {
4121
4172
  color: #075985;
4122
4173
  }
4174
+ .workspace-table-view-tabs {
4175
+ grid-template-columns: repeat(4, 1fr);
4176
+ }
4177
+ .workspace-table-transform-preview {
4178
+ display: grid;
4179
+ gap: 8px;
4180
+ min-height: 88px;
4181
+ margin-top: 10px;
4182
+ color: #4b5563;
4183
+ font-size: 11px;
4184
+ }
4185
+ .workspace-table-transform-preview.is-board,
4186
+ .workspace-table-transform-preview.is-calendar {
4187
+ grid-template-columns: repeat(3, minmax(0, 1fr));
4188
+ }
4189
+ .workspace-table-transform-preview.is-calendar {
4190
+ grid-template-columns: repeat(5, minmax(0, 1fr));
4191
+ }
4192
+ .workspace-table-transform-preview section {
4193
+ min-width: 0;
4194
+ border: 1px solid #eeeeee;
4195
+ border-radius: 5px;
4196
+ background: #fafafa;
4197
+ padding: 6px;
4198
+ }
4199
+ .workspace-table-transform-preview strong,
4200
+ .workspace-table-transform-preview span,
4201
+ .workspace-table-transform-preview em {
4202
+ display: block;
4203
+ min-width: 0;
4204
+ overflow: hidden;
4205
+ text-overflow: ellipsis;
4206
+ white-space: nowrap;
4207
+ }
4208
+ .workspace-table-transform-preview strong {
4209
+ color: #111827;
4210
+ font-size: 11px;
4211
+ }
4212
+ .workspace-table-transform-preview span {
4213
+ margin-top: 5px;
4214
+ border-radius: 4px;
4215
+ background: #fff;
4216
+ padding: 5px 6px;
4217
+ }
4218
+ .workspace-table-transform-preview div {
4219
+ display: grid;
4220
+ grid-template-columns: minmax(64px, 1fr) 2fr minmax(32px, auto);
4221
+ align-items: center;
4222
+ gap: 8px;
4223
+ }
4224
+ .workspace-table-transform-preview i {
4225
+ display: block;
4226
+ width: var(--width, 42%);
4227
+ height: 9px;
4228
+ margin-left: var(--offset, 0);
4229
+ border-radius: 999px;
4230
+ background: #93c5fd;
4231
+ }
4232
+ .workspace-table-transform-preview em {
4233
+ color: #9ca3af;
4234
+ font-style: normal;
4235
+ }
4123
4236
  .workspace-axis-range {
4124
4237
  display: grid;
4125
4238
  grid-template-columns: 1fr 1fr;
@@ -8501,3 +8614,59 @@ body.workspace-rail-collapsed .workspace-builder.dm-workflow-page {
8501
8614
  .dm-swarm-phase { border: 1px solid #edf0f3; border-radius: 4px; padding: 6px 8px; font-size: 11px; }
8502
8615
  .dm-swarm-phase summary { cursor: pointer; color: #6b7280; font-weight: 500; }
8503
8616
  .dm-swarm-phase pre { margin: 6px 0 0; font-size: 11px; white-space: pre-wrap; color: #111827; max-height: 240px; overflow: auto; }
8617
+
8618
+ /* Customer Activation Layer V1 — workspace activation panel */
8619
+ .workspace-activation-panel { border: 1px solid #e5e7eb; border-radius: 10px; background: #ffffff; padding: 18px 20px; margin: 0 0 20px; display: flex; flex-direction: column; gap: 16px; }
8620
+ .workspace-activation-panel.is-complete { background: linear-gradient(180deg, #f0fdf4 0%, #ffffff 80%); border-color: #bbf7d0; }
8621
+ .workspace-activation-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 16px; flex-wrap: wrap; }
8622
+ .workspace-activation-head-text { display: flex; flex-direction: column; gap: 4px; flex: 1 1 320px; min-width: 0; }
8623
+ .workspace-activation-eyebrow { margin: 0; font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em; color: #6b7280; font-weight: 600; }
8624
+ .workspace-activation-headline { margin: 0; font-size: 18px; font-weight: 600; color: #111827; line-height: 1.3; }
8625
+ .workspace-activation-subheadline { margin: 0; font-size: 13px; color: #4b5563; }
8626
+ .workspace-activation-progress { display: flex; flex-direction: column; align-items: flex-end; gap: 4px; min-width: 120px; }
8627
+ .workspace-activation-progress-value { font-size: 11px; font-weight: 600; color: #475569; }
8628
+ .workspace-activation-progress-bar { display: block; width: 120px; height: 6px; background: #f1f5f9; border-radius: 999px; overflow: hidden; }
8629
+ .workspace-activation-progress-fill { display: block; height: 100%; background: #3b82f6; transition: width 200ms ease; }
8630
+ .workspace-activation-panel.is-complete .workspace-activation-progress-fill { background: #16a34a; }
8631
+ .workspace-activation-steps { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 8px; }
8632
+ .workspace-activation-step { display: flex; align-items: flex-start; gap: 12px; padding: 12px 14px; border: 1px solid #edf0f3; border-radius: 8px; background: #fbfcfd; transition: border-color 120ms ease, background 120ms ease; }
8633
+ .workspace-activation-step.is-next { border-color: #3b82f6; background: #eff6ff; }
8634
+ .workspace-activation-step.is-complete { background: #f0fdf4; border-color: #bbf7d0; }
8635
+ .workspace-activation-step.is-blocked { background: #fffbeb; border-color: #fde68a; }
8636
+ .workspace-activation-step.is-optional { opacity: 0.85; }
8637
+ .workspace-activation-step-status { display: inline-flex; align-items: center; justify-content: center; width: 22px; height: 22px; border-radius: 999px; background: #ffffff; border: 1px solid #cbd5f5; color: #3b82f6; flex-shrink: 0; }
8638
+ .workspace-activation-step.is-complete .workspace-activation-step-status { background: #16a34a; border-color: #16a34a; color: #ffffff; }
8639
+ .workspace-activation-step.is-blocked .workspace-activation-step-status { background: #fef3c7; border-color: #f59e0b; color: #b45309; }
8640
+ .workspace-activation-step.is-optional .workspace-activation-step-status { background: #f5f3ff; border-color: #c4b5fd; color: #7c3aed; }
8641
+ .workspace-activation-step-body { display: flex; flex-direction: column; gap: 4px; flex: 1; min-width: 0; }
8642
+ .workspace-activation-step-titlebar { display: flex; align-items: center; justify-content: space-between; gap: 8px; flex-wrap: wrap; }
8643
+ .workspace-activation-step-title { margin: 0; font-size: 13px; font-weight: 600; color: #111827; }
8644
+ .workspace-activation-step-badge { font-size: 10px; text-transform: uppercase; letter-spacing: 0.04em; padding: 1px 6px; border-radius: 999px; background: #eff6ff; color: #1d4ed8; border: 1px solid #bfdbfe; }
8645
+ .workspace-activation-step-badge.is-complete { background: #ecfdf5; color: #047857; border-color: #a7f3d0; }
8646
+ .workspace-activation-step-badge.is-blocked { background: #fef3c7; color: #b45309; border-color: #fde68a; }
8647
+ .workspace-activation-step-badge.is-optional { background: #f5f3ff; color: #6d28d9; border-color: #ddd6fe; }
8648
+ .workspace-activation-step-description { margin: 0; font-size: 12px; color: #475569; line-height: 1.5; }
8649
+ .workspace-activation-step-hint { display: inline-flex; align-items: flex-start; gap: 6px; margin: 0; font-size: 11px; color: #6b7280; line-height: 1.45; }
8650
+ .workspace-activation-step-hint > svg { margin-top: 2px; flex-shrink: 0; }
8651
+ .workspace-activation-step-cta { display: inline-flex; align-items: center; gap: 4px; align-self: flex-start; font-size: 12px; color: #1d4ed8; text-decoration: none; padding: 4px 0; }
8652
+ .workspace-activation-step-cta:hover { text-decoration: underline; }
8653
+ .workspace-activation-step-cta.is-primary { background: #3b82f6; color: #ffffff; border-radius: 6px; padding: 4px 10px; }
8654
+ .workspace-activation-step-cta.is-primary:hover { background: #2563eb; text-decoration: none; }
8655
+ .workspace-activation-helper-cta { display: flex; justify-content: flex-end; }
8656
+ .workspace-activation-helper-btn { display: inline-flex; align-items: center; gap: 6px; background: transparent; border: 1px dashed #c7d2fe; color: #4338ca; font-size: 12px; padding: 6px 10px; border-radius: 6px; cursor: pointer; }
8657
+ .workspace-activation-helper-btn:hover { background: #eef2ff; }
8658
+
8659
+ /* Compact rail variant — slim activation chip in WorkspaceRail Home */
8660
+ .workspace-activation-panel.is-compact { padding: 12px 14px; margin: 0 0 12px; gap: 10px; border-radius: 8px; }
8661
+ .workspace-activation-panel.is-compact .workspace-activation-head { gap: 8px; }
8662
+ .workspace-activation-panel.is-compact .workspace-activation-headline { font-size: 13px; }
8663
+ .workspace-activation-panel.is-compact .workspace-activation-subheadline { font-size: 11px; }
8664
+ .workspace-activation-panel.is-compact .workspace-activation-step { padding: 8px 10px; gap: 8px; }
8665
+ .workspace-activation-panel.is-compact .workspace-activation-step-title { font-size: 12px; }
8666
+ .workspace-activation-panel.is-compact .workspace-activation-step-description { font-size: 11px; }
8667
+ .workspace-activation-panel.is-compact .workspace-activation-progress-bar { width: 80px; }
8668
+
8669
+ /* Template-aware context banner — workflow + nango affordance */
8670
+ .workspace-template-context-banner { display: flex; align-items: center; gap: 10px; padding: 8px 12px; border-radius: 6px; background: #eff6ff; border: 1px solid #bfdbfe; color: #1e40af; font-size: 12px; margin: 0 0 12px; }
8671
+ .workspace-template-context-banner.is-warn { background: #fffbeb; border-color: #fde68a; color: #92400e; }
8672
+ .workspace-template-context-banner .workspace-template-context-link { color: inherit; text-decoration: underline; font-weight: 600; margin-left: auto; }
@@ -51,6 +51,7 @@ import { AgentSwarmPanel } from "../data-model/components/AgentSwarmPanel.jsx";
51
51
  import { RunSetupPanel } from "./RunSetupPanel.jsx";
52
52
  import { describeRunInputMetadataItems, discoverRunInputSchema } from "@/lib/orchestration-run-inputs";
53
53
  import { selectWorkflowNodeInputSchema } from "@/lib/workspace-metadata-selectors";
54
+ import { deriveProvenance, hasConnectionId } from "@/lib/workspace-activation";
54
55
 
55
56
  // Workspace Metadata Graph V1 — read-only dependency metadata for workflow
56
57
  // sidecars. The runtime path (sandbox-run, publish, draft/live) is
@@ -387,6 +388,33 @@ export default function WorkflowSurface() {
387
388
  [workspaceConfig, sandboxRow]
388
389
  );
389
390
 
391
+ // Template-aware activation banner. When the active workflow belongs to
392
+ // a seeded template (project-management today) and its provider row
393
+ // doesn't have a connectionId yet, surface a back-link to the API
394
+ // Registry / Nango panel so the user can finish setup without hunting
395
+ // through Data Model surfaces.
396
+ const templateBanner = useMemo(() => {
397
+ if (!workspaceConfig) return null;
398
+ const provenance = deriveProvenance(workspaceConfig);
399
+ if (provenance.template !== "project-management") return null;
400
+ if (!registryRow) return null;
401
+ const ready = hasConnectionId(registryRow);
402
+ const apiRegistryObjectId = String(
403
+ (Array.isArray(workspaceConfig?.dataModel?.objects)
404
+ ? workspaceConfig.dataModel.objects.find((o) => o?.objectType === "api-registry")?.id
405
+ : "") || ""
406
+ );
407
+ const apiRegistryRowId = String(registryRow.integrationId || "").trim();
408
+ const backHref = apiRegistryObjectId && apiRegistryRowId
409
+ ? `/data-model?object=${encodeURIComponent(apiRegistryObjectId)}&row=${encodeURIComponent(apiRegistryRowId)}`
410
+ : "/data-model";
411
+ return {
412
+ ready,
413
+ backHref,
414
+ providerConfigKey: String(registryRow.providerConfigKey || registryRow.integrationId || "").trim(),
415
+ };
416
+ }, [workspaceConfig, registryRow]);
417
+
390
418
  useEffect(() => {
391
419
  setSidecarMode(runId ? "trace" : "graph");
392
420
  }, [runId]);
@@ -876,6 +904,22 @@ export default function WorkflowSurface() {
876
904
  </div>
877
905
  </header>
878
906
 
907
+ {templateBanner ? (
908
+ <div
909
+ className={"workspace-template-context-banner" + (templateBanner.ready ? "" : " is-warn")}
910
+ role="note"
911
+ >
912
+ <span>
913
+ {templateBanner.ready
914
+ ? `Provider connected via Nango${templateBanner.providerConfigKey ? ` (${templateBanner.providerConfigKey})` : ""}. Ready to run.`
915
+ : "Connect your provider through Nango before running this workflow."}
916
+ </span>
917
+ <Link href={templateBanner.backHref} className="workspace-template-context-link">
918
+ <span>{templateBanner.ready ? "Manage connection" : "Open Nango panel"}</span>
919
+ </Link>
920
+ </div>
921
+ ) : null}
922
+
879
923
  {loading ? (
880
924
  <p className="dm-workflow-empty">Loading workflow…</p>
881
925
  ) : error ? (