@growthub/cli 0.13.9 → 0.14.1

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 (39) hide show
  1. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/env-status/route.js +31 -0
  2. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/helper/apply/route.js +227 -5
  3. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/helper/query/route.js +1 -0
  4. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/api/workspace/sandbox-run/route.js +70 -9
  5. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/components/WorkspaceActivationPanel.jsx +17 -1
  6. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/components/WorkspaceHelperSetupModal.jsx +6 -3
  7. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/AgentSwarmPanel.jsx +61 -35
  8. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/ApiRegistryCreationCockpit.jsx +200 -0
  9. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/DataModelShell.jsx +414 -9
  10. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/HelperSidecar.jsx +339 -77
  11. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/OrchestrationGraphCanvas.jsx +81 -10
  12. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/OrchestrationNodeConfigPanel.jsx +70 -85
  13. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/ReferencePicker.jsx +2 -2
  14. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/SidecarExpandView.jsx +37 -0
  15. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/SwarmRunCockpit.jsx +625 -0
  16. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/helper-commands.js +150 -0
  17. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/globals.css +229 -9
  18. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workflows/WorkflowSurface.jsx +224 -14
  19. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/integrations/resolver-loader.js +2 -4
  20. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/sandboxes/default-local-agent-host.js +139 -4
  21. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/adapters/sandboxes/default-local-intelligence.js +4 -0
  22. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/api-registry-creation-flow.js +317 -0
  23. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/api-response-profile.js +207 -0
  24. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/creation-error-recovery.js +103 -0
  25. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/env-status.js +100 -0
  26. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-agent-swarm.js +246 -4
  27. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-graph.js +69 -0
  28. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/orchestration-run-console.js +411 -1
  29. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/sandbox-serverless-flow.js +215 -0
  30. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/server-resolver-write.js +67 -0
  31. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/serverless-upgrade.js +89 -0
  32. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-activation.js +11 -4
  33. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-data-model.js +8 -1
  34. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-helper.js +30 -1
  35. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-metadata-store.js +8 -6
  36. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-resolver-proposal.js +200 -0
  37. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-swarm-proposal.js +551 -0
  38. package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/package.json +1 -1
  39. package/package.json +1 -1
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
 
3
- import { useMemo, useState } from "react";
3
+ import { useCallback, useMemo, useRef, useState } from "react";
4
4
  import {
5
5
  ArrowDownToLine,
6
6
  Bot,
@@ -53,6 +53,15 @@ const CONNECTOR_OPTIONS = [
53
53
  { id: "preview", label: "Preview output" }
54
54
  ];
55
55
 
56
+ const MIN_ZOOM = 0.45;
57
+ const MAX_ZOOM = 1.4;
58
+ const NODE_BLOCK_HEIGHT = 98;
59
+ const FIT_VIEW_PADDING = 128;
60
+
61
+ function clampZoom(value) {
62
+ return Math.min(MAX_ZOOM, Math.max(MIN_ZOOM, Number(value.toFixed(2))));
63
+ }
64
+
56
65
  function nodeSubtitle(node) {
57
66
  const config = node?.config || {};
58
67
  if (node?.subtitle) return String(node.subtitle);
@@ -111,8 +120,61 @@ export function OrchestrationGraphCanvas({
111
120
  const [internalSelected, setInternalSelected] = useState(null);
112
121
  const [connectorPopover, setConnectorPopover] = useState(null);
113
122
  const [zoom, setZoom] = useState(1);
123
+ const [pan, setPan] = useState({ x: 0, y: 0 });
124
+ const canvasRef = useRef(null);
125
+ const dragRef = useRef(null);
114
126
  const activeId = selectedNodeId ?? internalSelected;
115
127
 
128
+ function edgeBetween(fromId, toId) {
129
+ return edges.find((e) => String(e.from) === fromId && String(e.to) === toId);
130
+ }
131
+
132
+ const zoomBy = useCallback((delta) => {
133
+ setZoom((value) => clampZoom(value + delta));
134
+ }, []);
135
+
136
+ const fitView = useCallback(() => {
137
+ const rect = canvasRef.current?.getBoundingClientRect();
138
+ const availableHeight = Math.max(240, (rect?.height || 720) - FIT_VIEW_PADDING);
139
+ const graphHeight = Math.max(NODE_BLOCK_HEIGHT, nodes.length * NODE_BLOCK_HEIGHT);
140
+ const nextZoom = clampZoom(Math.min(1, availableHeight / graphHeight));
141
+ setZoom(nextZoom);
142
+ setPan({ x: 0, y: 0 });
143
+ }, [nodes.length]);
144
+
145
+ const handleWheel = useCallback((event) => {
146
+ event.preventDefault();
147
+ const direction = event.deltaY > 0 ? -0.08 : 0.08;
148
+ zoomBy(direction);
149
+ }, [zoomBy]);
150
+
151
+ const handlePointerDown = useCallback((event) => {
152
+ if (event.button !== 0) return;
153
+ if (event.target.closest("button, .dm-orchestration-node, .dm-orchestration-connector__popover")) return;
154
+ dragRef.current = {
155
+ pointerId: event.pointerId,
156
+ startX: event.clientX,
157
+ startY: event.clientY,
158
+ originX: pan.x,
159
+ originY: pan.y
160
+ };
161
+ event.currentTarget.setPointerCapture?.(event.pointerId);
162
+ }, [pan.x, pan.y]);
163
+
164
+ const handlePointerMove = useCallback((event) => {
165
+ const drag = dragRef.current;
166
+ if (!drag || drag.pointerId !== event.pointerId) return;
167
+ event.preventDefault();
168
+ setPan({
169
+ x: drag.originX + event.clientX - drag.startX,
170
+ y: drag.originY + event.clientY - drag.startY
171
+ });
172
+ }, []);
173
+
174
+ const endDrag = useCallback((event) => {
175
+ if (dragRef.current?.pointerId === event.pointerId) dragRef.current = null;
176
+ }, []);
177
+
116
178
  if (!nodes.length) {
117
179
  return (
118
180
  <div className="dm-orchestration-canvas dm-orchestration-canvas--empty">
@@ -121,12 +183,18 @@ export function OrchestrationGraphCanvas({
121
183
  );
122
184
  }
123
185
 
124
- function edgeBetween(fromId, toId) {
125
- return edges.find((e) => String(e.from) === fromId && String(e.to) === toId);
126
- }
127
-
128
186
  return (
129
- <div className="dm-orchestration-canvas" aria-label="Orchestration graph field editor">
187
+ <div
188
+ ref={canvasRef}
189
+ className="dm-orchestration-canvas"
190
+ aria-label="Orchestration graph field editor"
191
+ onWheel={handleWheel}
192
+ onPointerDown={handlePointerDown}
193
+ onPointerMove={handlePointerMove}
194
+ onPointerUp={endDrag}
195
+ onPointerCancel={endDrag}
196
+ onPointerLeave={endDrag}
197
+ >
130
198
  <span className={`dm-orchestration-canvas__badge is-${String(statusLabel || "draft").toLowerCase()}`}>{statusLabel}</span>
131
199
  <div className="dm-orchestration-floating-tools" aria-label="Canvas tools">
132
200
  <button type="button" title="Add node" aria-label="Add node" onClick={() => onConnectorAction?.({ action: "add-step", from: String(nodes[nodes.length - 1]?.id || ""), to: "" })}>
@@ -135,17 +203,20 @@ export function OrchestrationGraphCanvas({
135
203
  <button type="button" title="Tidy workflow" aria-label="Tidy workflow">
136
204
  <Settings size={14} />
137
205
  </button>
138
- <button type="button" title="Zoom in" aria-label="Zoom in" onClick={() => setZoom((value) => Math.min(1.4, Number((value + 0.1).toFixed(2))))}>
206
+ <button type="button" title="Zoom in" aria-label="Zoom in" onClick={() => zoomBy(0.1)}>
139
207
  <ZoomIn size={14} />
140
208
  </button>
141
- <button type="button" title="Zoom out" aria-label="Zoom out" onClick={() => setZoom((value) => Math.max(0.7, Number((value - 0.1).toFixed(2))))}>
209
+ <button type="button" title="Zoom out" aria-label="Zoom out" onClick={() => zoomBy(-0.1)}>
142
210
  <ZoomOut size={14} />
143
211
  </button>
144
- <button type="button" title="Reset zoom" aria-label="Reset zoom" onClick={() => setZoom(1)}>
212
+ <button type="button" title="Fit view" aria-label="Fit view" onClick={fitView}>
145
213
  <Maximize2 size={14} />
146
214
  </button>
147
215
  </div>
148
- <div className="dm-orchestration-canvas__viewport" style={{ transform: `scale(${zoom})` }}>
216
+ <div
217
+ className="dm-orchestration-canvas__viewport"
218
+ style={{ transform: `translate3d(${pan.x}px, ${pan.y}px, 0) scale(${zoom})` }}
219
+ >
149
220
  {nodes.map((node, index) => {
150
221
  const id = String(node.id || "");
151
222
  const isSelected = activeId === id;
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  import { useMemo, useState } from "react";
4
+ import { Check } from "lucide-react";
4
5
  import {
5
6
  detectFieldIdsFromLastResponse,
6
7
  FILTER_CONJUNCTIONS,
@@ -28,6 +29,23 @@ const EMPTY_AGENT_AUTH_PATCH = {
28
29
  agentAuthLastLoginUrl: ""
29
30
  };
30
31
 
32
+ function WorkflowCheckbox({ checked, disabled, onChange, children, title }) {
33
+ return (
34
+ <label className="dm-orchestration-config__field dm-orchestration-config__field-inline dm-workflow-check" title={title}>
35
+ <input
36
+ type="checkbox"
37
+ checked={checked}
38
+ disabled={disabled}
39
+ onChange={(event) => onChange?.(event.target.checked)}
40
+ />
41
+ <span className="dm-workflow-check__box" aria-hidden="true">
42
+ {checked ? <Check size={13} strokeWidth={2.4} /> : null}
43
+ </span>
44
+ <span>{children}</span>
45
+ </label>
46
+ );
47
+ }
48
+
31
49
  function getAgentHostOptions() {
32
50
  return Object.entries(HOST_AUTH_CATALOG || {}).map(([slug, host]) => ({
33
51
  value: slug,
@@ -696,24 +714,20 @@ export function OrchestrationNodeConfigPanel({
696
714
  Latest registry test: {registryRow.status}
697
715
  </span>
698
716
  )}
699
- <label className="dm-orchestration-config__field dm-orchestration-config__field-inline">
700
- <input
701
- type="checkbox"
702
- checked={config.writeLastResponse !== false}
703
- disabled={disabled}
704
- onChange={(e) => patchConfig({ writeLastResponse: e.target.checked })}
705
- />
706
- <span>Write lastResponse on success</span>
707
- </label>
708
- <label className="dm-orchestration-config__field dm-orchestration-config__field-inline">
709
- <input
710
- type="checkbox"
711
- checked={config.writeSourceRecord !== false}
712
- disabled={disabled}
713
- onChange={(e) => patchConfig({ writeSourceRecord: e.target.checked })}
714
- />
715
- <span>Write source record history</span>
716
- </label>
717
+ <WorkflowCheckbox
718
+ checked={config.writeLastResponse !== false}
719
+ disabled={disabled}
720
+ onChange={(checked) => patchConfig({ writeLastResponse: checked })}
721
+ >
722
+ Write lastResponse on success
723
+ </WorkflowCheckbox>
724
+ <WorkflowCheckbox
725
+ checked={config.writeSourceRecord !== false}
726
+ disabled={disabled}
727
+ onChange={(checked) => patchConfig({ writeSourceRecord: checked })}
728
+ >
729
+ Write source record history
730
+ </WorkflowCheckbox>
717
731
  <label className="dm-orchestration-config__field">
718
732
  <span>Success HTTP codes</span>
719
733
  <input
@@ -882,15 +896,13 @@ export function OrchestrationNodeConfigPanel({
882
896
  )}
883
897
  </div>
884
898
  )}
885
- <label className="dm-orchestration-config__field dm-orchestration-config__field-inline">
886
- <input
887
- type="checkbox"
888
- checked={config.confirmationRequired === true}
889
- disabled={disabled || config.destructive === true}
890
- onChange={(e) => patchConfig({ confirmationRequired: e.target.checked })}
891
- />
892
- <span>Require confirmation before destructive or version-changing execution</span>
893
- </label>
899
+ <WorkflowCheckbox
900
+ checked={config.confirmationRequired === true}
901
+ disabled={disabled || config.destructive === true}
902
+ onChange={(checked) => patchConfig({ confirmationRequired: checked })}
903
+ >
904
+ Require confirmation before destructive or version-changing execution
905
+ </WorkflowCheckbox>
894
906
  <p className="dm-orchestration-config__hint">
895
907
  Data actions bind only to this workspace data model. Execution resolves the latest object schema at run time.
896
908
  </p>
@@ -960,27 +972,21 @@ export function OrchestrationNodeConfigPanel({
960
972
  ))}
961
973
  </select>
962
974
  </label>
963
- <label className="dm-orchestration-config__field dm-orchestration-config__field-inline">
964
- <input
965
- type="checkbox"
966
- checked={config.required !== false}
967
- disabled={disabled}
968
- onChange={(e) => patchConfig({ required: e.target.checked })}
969
- />
970
- <span>Required</span>
971
- </label>
972
- <label
973
- className="dm-orchestration-config__field dm-orchestration-config__field-inline"
975
+ <WorkflowCheckbox
976
+ checked={config.required !== false}
977
+ disabled={disabled}
978
+ onChange={(checked) => patchConfig({ required: checked })}
979
+ >
980
+ Required
981
+ </WorkflowCheckbox>
982
+ <WorkflowCheckbox
983
+ checked={config.networkAccess === true}
984
+ disabled={disabled}
974
985
  title="Network is granted only when both this and the row's networkAllow are on."
986
+ onChange={(checked) => patchConfig({ networkAccess: checked })}
975
987
  >
976
- <input
977
- type="checkbox"
978
- checked={config.networkAccess === true}
979
- disabled={disabled}
980
- onChange={(e) => patchConfig({ networkAccess: e.target.checked })}
981
- />
982
- <span>Network</span>
983
- </label>
988
+ Network
989
+ </WorkflowCheckbox>
984
990
  </div>
985
991
  )}
986
992
 
@@ -1013,33 +1019,15 @@ export function OrchestrationNodeConfigPanel({
1013
1019
  </label>
1014
1020
  <div className="dm-orchestration-config__section">
1015
1021
  <span>Permissions</span>
1016
- <label className="dm-orchestration-config__field dm-orchestration-config__field-inline">
1017
- <input
1018
- type="checkbox"
1019
- checked={config.canReadWorkspace !== false}
1020
- disabled={disabled}
1021
- onChange={(e) => patchConfig({ canReadWorkspace: e.target.checked })}
1022
- />
1023
- <span>Read workspace data</span>
1024
- </label>
1025
- <label className="dm-orchestration-config__field dm-orchestration-config__field-inline">
1026
- <input
1027
- type="checkbox"
1028
- checked={config.canWriteDraft === true}
1029
- disabled={disabled}
1030
- onChange={(e) => patchConfig({ canWriteDraft: e.target.checked })}
1031
- />
1032
- <span>Write draft changes only</span>
1033
- </label>
1034
- <label className="dm-orchestration-config__field dm-orchestration-config__field-inline">
1035
- <input
1036
- type="checkbox"
1037
- checked={config.networkAccess === true}
1038
- disabled={disabled}
1039
- onChange={(e) => patchConfig({ networkAccess: e.target.checked })}
1040
- />
1041
- <span>Allow network access</span>
1042
- </label>
1022
+ <WorkflowCheckbox checked={config.canReadWorkspace !== false} disabled={disabled} onChange={(checked) => patchConfig({ canReadWorkspace: checked })}>
1023
+ Read workspace data
1024
+ </WorkflowCheckbox>
1025
+ <WorkflowCheckbox checked={config.canWriteDraft === true} disabled={disabled} onChange={(checked) => patchConfig({ canWriteDraft: checked })}>
1026
+ Write draft changes only
1027
+ </WorkflowCheckbox>
1028
+ <WorkflowCheckbox checked={config.networkAccess === true} disabled={disabled} onChange={(checked) => patchConfig({ networkAccess: checked })}>
1029
+ Allow network access
1030
+ </WorkflowCheckbox>
1043
1031
  </div>
1044
1032
  <KeyValueRows
1045
1033
  label="Output fields"
@@ -1176,10 +1164,9 @@ export function OrchestrationNodeConfigPanel({
1176
1164
  <span>Message</span>
1177
1165
  <textarea rows={6} value={config.message || ""} disabled={disabled} onChange={(e) => patchConfig({ message: e.target.value })} />
1178
1166
  </label>
1179
- <label className="dm-orchestration-config__field dm-orchestration-config__field-inline">
1180
- <input type="checkbox" checked={config.requireApproval !== false} disabled={disabled} onChange={(e) => patchConfig({ requireApproval: e.target.checked })} />
1181
- <span>Require approval before sending</span>
1182
- </label>
1167
+ <WorkflowCheckbox checked={config.requireApproval !== false} disabled={disabled} onChange={(checked) => patchConfig({ requireApproval: checked })}>
1168
+ Require approval before sending
1169
+ </WorkflowCheckbox>
1183
1170
  </>
1184
1171
  )}
1185
1172
  {config.action === "code-function" && (
@@ -1216,10 +1203,9 @@ export function OrchestrationNodeConfigPanel({
1216
1203
  valuePlaceholder="Field type or help text"
1217
1204
  onChange={(fields) => patchConfig({ fields })}
1218
1205
  />
1219
- <label className="dm-orchestration-config__field dm-orchestration-config__field-inline">
1220
- <input type="checkbox" checked={config.required !== false} disabled={disabled} onChange={(e) => patchConfig({ required: e.target.checked })} />
1221
- <span>Require response before continuing</span>
1222
- </label>
1206
+ <WorkflowCheckbox checked={config.required !== false} disabled={disabled} onChange={(checked) => patchConfig({ required: checked })}>
1207
+ Require response before continuing
1208
+ </WorkflowCheckbox>
1223
1209
  </div>
1224
1210
  )}
1225
1211
 
@@ -1233,10 +1219,9 @@ export function OrchestrationNodeConfigPanel({
1233
1219
  <span>Sample response</span>
1234
1220
  <textarea rows={5} value={config.sampleResponse || ""} disabled={disabled} onChange={(e) => patchConfig({ sampleResponse: e.target.value })} />
1235
1221
  </label>
1236
- <label className="dm-orchestration-config__field dm-orchestration-config__field-inline">
1237
- <input type="checkbox" checked={config.blockPublishOnFailure !== false} disabled={disabled} onChange={(e) => patchConfig({ blockPublishOnFailure: e.target.checked })} />
1238
- <span>Block Publish unless this step passes</span>
1239
- </label>
1222
+ <WorkflowCheckbox checked={config.blockPublishOnFailure !== false} disabled={disabled} onChange={(checked) => patchConfig({ blockPublishOnFailure: checked })}>
1223
+ Block Publish unless this step passes
1224
+ </WorkflowCheckbox>
1240
1225
  </div>
1241
1226
  )}
1242
1227
 
@@ -202,9 +202,9 @@ export function ReferencePicker({
202
202
  <div className="dm-reference-picker">
203
203
  {error && <p className="dm-field-error" style={{ fontSize: 11 }}>{error}</p>}
204
204
  {showRepair && (
205
- <p className="dm-validation-banner" style={{ fontSize: 11, marginBottom: 6 }}>
205
+ <p className="dm-reference-picker-warning">
206
206
  <AlertTriangle size={12} aria-hidden />
207
- <span>Selected reference is missing or filtered out. Pick a new row or adjust API Registry status.</span>
207
+ <span>Missing reference. Pick a row or test the API Registry.</span>
208
208
  </p>
209
209
  )}
210
210
  <SearchableSelect
@@ -0,0 +1,37 @@
1
+ "use client";
2
+
3
+ /**
4
+ * SidecarExpandView — full-width takeover WITHIN the existing helper sidecar
5
+ * system (SWARM_RUN_CONTRACT_V1, Phase 5).
6
+ *
7
+ * Not a modal and not a route: the parent HelperSidecar widens its own aside
8
+ * while this view is active and renders this wrapper inside the same body.
9
+ * Back returns to the prior sidecar view; Esc collapses (handled by the
10
+ * parent so it composes with the sidecar's existing Esc-to-close); the
11
+ * sidecar close button keeps closing the whole sidecar.
12
+ *
13
+ * Reuses the existing sidecar header/body grammar — no new modal stack, no
14
+ * new visual language.
15
+ */
16
+
17
+ import { ArrowLeft } from "lucide-react";
18
+
19
+ export function SidecarExpandView({ title, onBack, children }) {
20
+ return (
21
+ <div className="dm-swarm-expand" data-sidecar-expand="">
22
+ <div className="dm-sidecar-header dm-swarm-expand-head">
23
+ <button
24
+ type="button"
25
+ className="dm-sidecar-icon-btn"
26
+ onClick={onBack}
27
+ aria-label="Back"
28
+ title="Back (Esc)"
29
+ >
30
+ <ArrowLeft size={14} />
31
+ </button>
32
+ <span className="dm-sidecar-title">{title}</span>
33
+ </div>
34
+ <div className="dm-swarm-expand-body">{children}</div>
35
+ </div>
36
+ );
37
+ }