orcheo-studio 0.24.4 → 0.24.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 (103) hide show
  1. package/.bumpversion.cfg +1 -1
  2. package/dist/assets/{arc-99TH-Koz.js → arc-CJePtrV4.js} +1 -1
  3. package/dist/assets/{architectureDiagram-VXUJARFQ-DugaTSY2.js → architectureDiagram-VXUJARFQ-BavAz0jp.js} +1 -1
  4. package/dist/assets/{blockDiagram-VD42YOAC-BH68wVtD.js → blockDiagram-VD42YOAC-Bx1XAydH.js} +1 -1
  5. package/dist/assets/{c4Diagram-YG6GDRKO-V6Vqtp7B.js → c4Diagram-YG6GDRKO-B2LhByin.js} +1 -1
  6. package/dist/assets/channel-BuDj-lWJ.js +1 -0
  7. package/dist/assets/{chunk-4BX2VUAB-CvbCAMNv.js → chunk-4BX2VUAB-UqiH5dLp.js} +1 -1
  8. package/dist/assets/{chunk-55IACEB6-h_T3kQh7.js → chunk-55IACEB6-ChxTr_6v.js} +1 -1
  9. package/dist/assets/{chunk-B4BG7PRW-Dm6dgbGh.js → chunk-B4BG7PRW-2ETw-eA1.js} +1 -1
  10. package/dist/assets/{chunk-DI55MBZ5-DSXFA4Fi.js → chunk-DI55MBZ5-BdxVSKej.js} +1 -1
  11. package/dist/assets/{chunk-FMBD7UC4-DJDlfjt1.js → chunk-FMBD7UC4-BJF2M5iQ.js} +1 -1
  12. package/dist/assets/{chunk-QN33PNHL-BnjUGeBL.js → chunk-QN33PNHL-DI9t3nKL.js} +1 -1
  13. package/dist/assets/{chunk-QZHKN3VN-BRudOghZ.js → chunk-QZHKN3VN-DsbUIBgB.js} +1 -1
  14. package/dist/assets/{chunk-TZMSLE5B-CpxSpvqK.js → chunk-TZMSLE5B-DzMhWfBV.js} +1 -1
  15. package/dist/assets/classDiagram-2ON5EDUG-Bc5AzWzU.js +1 -0
  16. package/dist/assets/classDiagram-v2-WZHVMYZB-Bc5AzWzU.js +1 -0
  17. package/dist/assets/clone-CtxIENQC.js +1 -0
  18. package/dist/assets/{cose-bilkent-S5V4N54A-Dl80WZ_a.js → cose-bilkent-S5V4N54A-BcKKbLWz.js} +1 -1
  19. package/dist/assets/{dagre-6UL2VRFP-vw-vkw78.js → dagre-6UL2VRFP-CERJ9KSL.js} +1 -1
  20. package/dist/assets/{diagram-PSM6KHXK-B4MT5Nvf.js → diagram-PSM6KHXK-DGl--Cyq.js} +1 -1
  21. package/dist/assets/{diagram-QEK2KX5R-CMioZnRI.js → diagram-QEK2KX5R-DbfdELCd.js} +1 -1
  22. package/dist/assets/{diagram-S2PKOQOG-PJAE8YS0.js → diagram-S2PKOQOG-CsR4YAm5.js} +1 -1
  23. package/dist/assets/{erDiagram-Q2GNP2WA-DCd-WouG.js → erDiagram-Q2GNP2WA-BlOr_gpI.js} +1 -1
  24. package/dist/assets/{flowDiagram-NV44I4VS-DS__-WT4.js → flowDiagram-NV44I4VS-BAyDXjxF.js} +1 -1
  25. package/dist/assets/{ganttDiagram-JELNMOA3-Btifh-31.js → ganttDiagram-JELNMOA3-b2ntam6s.js} +1 -1
  26. package/dist/assets/{gitGraphDiagram-V2S2FVAM-BCyjopey.js → gitGraphDiagram-V2S2FVAM-BvdeZrga.js} +1 -1
  27. package/dist/assets/{graph-6YBx8iQ0.js → graph-DMrtDwYQ.js} +1 -1
  28. package/dist/assets/{index-lkycEDHM.js → index-CaWVBOSf.js} +204 -189
  29. package/dist/assets/index-CbLxL6va.css +1 -0
  30. package/dist/assets/{infoDiagram-HS3SLOUP-CAsJ2eU1.js → infoDiagram-HS3SLOUP-CFO6LsEb.js} +1 -1
  31. package/dist/assets/{isUndefined-CcAlyXn3.js → isUndefined-DE2VOYrW.js} +1 -1
  32. package/dist/assets/{journeyDiagram-XKPGCS4Q-Bi8XsX15.js → journeyDiagram-XKPGCS4Q-Svi8cltR.js} +1 -1
  33. package/dist/assets/{kanban-definition-3W4ZIXB7-PTAFVsDn.js → kanban-definition-3W4ZIXB7-C7uds03_.js} +1 -1
  34. package/dist/assets/{layout-Do-GOFdW.js → layout-D5N_DnHw.js} +1 -1
  35. package/dist/assets/{linear-CZlgolw0.js → linear-h9-K1RBq.js} +1 -1
  36. package/dist/assets/{min-N15IYW1Y.js → min-CjtZYucR.js} +1 -1
  37. package/dist/assets/{mindmap-definition-VGOIOE7T-Dlgxv6Jo.js → mindmap-definition-VGOIOE7T-Bw80_6TW.js} +1 -1
  38. package/dist/assets/{pieDiagram-ADFJNKIX-Dr2AfIw5.js → pieDiagram-ADFJNKIX-DGukFJSP.js} +1 -1
  39. package/dist/assets/{quadrantDiagram-AYHSOK5B-B8TPuvW6.js → quadrantDiagram-AYHSOK5B-CU7GPi0P.js} +1 -1
  40. package/dist/assets/{requirementDiagram-UZGBJVZJ-CdD2oP1N.js → requirementDiagram-UZGBJVZJ-D_aCZrji.js} +1 -1
  41. package/dist/assets/{sankeyDiagram-TZEHDZUN-XEMX6l5k.js → sankeyDiagram-TZEHDZUN-Wgtprjih.js} +1 -1
  42. package/dist/assets/{sequenceDiagram-WL72ISMW-BlIKpBev.js → sequenceDiagram-WL72ISMW-BCOCCVxS.js} +1 -1
  43. package/dist/assets/{stateDiagram-FKZM4ZOC-D3KGQY6v.js → stateDiagram-FKZM4ZOC-DHVe22f9.js} +1 -1
  44. package/dist/assets/stateDiagram-v2-4FDKWEC3-Bq9eQr8D.js +1 -0
  45. package/dist/assets/{timeline-definition-IT6M3QCI-TkL4mz_f.js → timeline-definition-IT6M3QCI-Cm4CAEQk.js} +1 -1
  46. package/dist/assets/{treemap-GDKQZRPO-BDp_c1Vc.js → treemap-GDKQZRPO-DvLhDp9r.js} +1 -1
  47. package/dist/assets/{xychartDiagram-PRI3JC2R-D0dNQImL.js → xychartDiagram-PRI3JC2R-BtcJYIPe.js} +1 -1
  48. package/dist/index.html +8 -2
  49. package/index.html +6 -0
  50. package/package.json +1 -1
  51. package/src/App.tsx +48 -44
  52. package/src/design-system/ui/button.tsx +1 -1
  53. package/src/features/account/components/settings/appearance-settings-tab.tsx +1 -1
  54. package/src/features/account/pages/profile/components/profile-general-tab.tsx +1 -1
  55. package/src/features/account/pages/workspace-members.tsx +58 -62
  56. package/src/features/auth/pages/login.tsx +4 -2
  57. package/src/features/auth/pages/verify.tsx +1 -1
  58. package/src/features/chatkit/components/public-chat-error-boundary.tsx +1 -1
  59. package/src/features/chatkit/components/public-chat-widget.tsx +2 -3
  60. package/src/features/chatkit/lib/chatkit-theme.ts +3 -2
  61. package/src/features/chatkit/lib/public-chat-access.ts +13 -0
  62. package/src/features/chatkit/pages/public-chat.tsx +18 -27
  63. package/src/features/shared/components/chat-interface.types.ts +1 -5
  64. package/src/features/shared/components/top-navigation/studio-brand.tsx +1 -1
  65. package/src/features/shared/components/top-navigation/version-status.tsx +27 -31
  66. package/src/features/shared/components/workspace-bootstrap-gate.tsx +4 -4
  67. package/src/features/workflow/components/dialogs/credential-access-badge.tsx +2 -2
  68. package/src/features/workflow/components/dialogs/credential-status-badge.tsx +2 -2
  69. package/src/features/workflow/components/dialogs/credentials-table.tsx +1 -1
  70. package/src/features/workflow/components/dialogs/update-workflow-dialog.tsx +34 -78
  71. package/src/features/workflow/components/dialogs/upload-workflow-dialog.tsx +40 -136
  72. package/src/features/workflow/components/dialogs/workflow-folder-picker.tsx +178 -0
  73. package/src/features/workflow/components/panels/rjsf-cron-widget.tsx +35 -10
  74. package/src/features/workflow/components/panels/workflow-diff-dialog.tsx +5 -5
  75. package/src/features/workflow/components/panels/workflow-history-table.tsx +3 -4
  76. package/src/features/workflow/components/trace/agent-prism/SpanCard/SpanCardConnector.tsx +1 -5
  77. package/src/features/workflow/components/trace/agent-prism/shared.ts +1 -10
  78. package/src/features/workflow/components/trace/agent-prism/theme/theme.css +74 -74
  79. package/src/features/workflow/lib/workflow-execution-builders.ts +4 -4
  80. package/src/features/workflow/lib/workflow-execution.types.ts +2 -9
  81. package/src/features/workflow/lib/workflow-folder-selection.ts +239 -0
  82. package/src/features/workflow/lib/workflow-frontmatter.ts +3 -1
  83. package/src/features/workflow/lib/workflow-storage-api.ts +5 -9
  84. package/src/features/workflow/lib/workflow-storage-helpers.ts +2 -4
  85. package/src/features/workflow/lib/workflow-storage.ts +6 -2
  86. package/src/features/workflow/lib/workflow-storage.types.ts +1 -6
  87. package/src/features/workflow/lib/workflow-upload-errors.ts +6 -2
  88. package/src/features/workflow/pages/workflow/components/settings-tab-content.tsx +24 -9
  89. package/src/features/workflow/pages/workflow/components/workflow-tab-content.tsx +6 -4
  90. package/src/features/workflow/pages/workflow/hooks/controller/use-workflow-execution-controller.ts +4 -1
  91. package/src/features/workflow/pages/workflow/hooks/use-execution-updates.ts +4 -2
  92. package/src/features/workflow/pages/workflow-gallery/create-team-dialog.tsx +2 -6
  93. package/src/features/workflow/pages/workflow-gallery/workflow-card.tsx +16 -16
  94. package/src/index.css +101 -59
  95. package/src/lib/api.ts +7 -2
  96. package/src/lib/workspace-routing.ts +1 -3
  97. package/tailwind.config.js +47 -2
  98. package/dist/assets/channel-D562bZxL.js +0 -1
  99. package/dist/assets/classDiagram-2ON5EDUG-Dix_KnuM.js +0 -1
  100. package/dist/assets/classDiagram-v2-WZHVMYZB-Dix_KnuM.js +0 -1
  101. package/dist/assets/clone-0c2Tu-SH.js +0 -1
  102. package/dist/assets/index-Df4jFRhd.css +0 -1
  103. package/dist/assets/stateDiagram-v2-4FDKWEC3-Di3Nkv2n.js +0 -1
@@ -0,0 +1,239 @@
1
+ /**
2
+ * Shared logic for selecting a workflow from a folder.
3
+ *
4
+ * A workflow folder is expected to contain exactly one Python (`.py`) script
5
+ * and at most one JSON (`.json`) config file at its top level. Multiple files
6
+ * of either kind are rejected as ambiguous so callers can surface an
7
+ * easy-to-understand error. The helpers here work for both folder `<input>`
8
+ * elements (`webkitdirectory`) and folder drag-and-drop.
9
+ */
10
+
11
+ export const MAX_SCRIPT_UPLOAD_BYTES = 1024 * 1024;
12
+ export const MAX_CONFIG_UPLOAD_BYTES = 256 * 1024;
13
+
14
+ /** Error with a user-facing message describing why a folder was rejected. */
15
+ export class WorkflowFolderSelectionError extends Error {
16
+ constructor(message: string) {
17
+ super(message);
18
+ this.name = "WorkflowFolderSelectionError";
19
+ }
20
+ }
21
+
22
+ export interface WorkflowFolderSelection {
23
+ scriptName: string;
24
+ scriptContent: string;
25
+ configName: string | null;
26
+ configContent: Record<string, unknown> | null;
27
+ }
28
+
29
+ const formatUploadLimit = (bytes: number): string => {
30
+ if (bytes >= 1024 * 1024) {
31
+ return `${bytes / (1024 * 1024)} MB`;
32
+ }
33
+ return `${bytes / 1024} KB`;
34
+ };
35
+
36
+ const hasExtension = (name: string, extension: string): boolean =>
37
+ name.toLowerCase().endsWith(extension);
38
+
39
+ const formatNames = (files: File[]): string =>
40
+ files.map((file) => file.name).join(", ");
41
+
42
+ /**
43
+ * Split a list of files into the single script and optional config.
44
+ * Throws {@link WorkflowFolderSelectionError} when the folder is ambiguous or
45
+ * is missing a script.
46
+ */
47
+ export const classifyWorkflowFolderFiles = (
48
+ files: File[],
49
+ ): { script: File; config: File | null } => {
50
+ const scripts = files.filter((file) => hasExtension(file.name, ".py"));
51
+ const configs = files.filter((file) => hasExtension(file.name, ".json"));
52
+
53
+ if (scripts.length === 0) {
54
+ throw new WorkflowFolderSelectionError(
55
+ "No Python (.py) workflow script was found in the folder. Add exactly one .py file and try again.",
56
+ );
57
+ }
58
+ if (scripts.length > 1) {
59
+ throw new WorkflowFolderSelectionError(
60
+ `Found ${scripts.length} Python (.py) files (${formatNames(scripts)}). ` +
61
+ "Keep exactly one .py file in the folder so it's clear which script to use.",
62
+ );
63
+ }
64
+ if (configs.length > 1) {
65
+ throw new WorkflowFolderSelectionError(
66
+ `Found ${configs.length} JSON (.json) config files (${formatNames(configs)}). ` +
67
+ "Keep at most one .json file in the folder so it's clear which config to use.",
68
+ );
69
+ }
70
+
71
+ return { script: scripts[0], config: configs[0] ?? null };
72
+ };
73
+
74
+ const isTopLevelFile = (file: File): boolean => {
75
+ const relativePath =
76
+ (file as File & { webkitRelativePath?: string }).webkitRelativePath ?? "";
77
+ // "" (no path info), "folder/file.py" → top level; "folder/sub/file.py" → nested.
78
+ return relativePath.split("/").length <= 2;
79
+ };
80
+
81
+ /**
82
+ * Collect the top-level files from a folder `<input webkitdirectory>` FileList,
83
+ * ignoring anything inside nested subdirectories.
84
+ */
85
+ export const collectFolderInputFiles = (fileList: FileList | null): File[] => {
86
+ if (!fileList) {
87
+ return [];
88
+ }
89
+ return Array.from(fileList).filter(isTopLevelFile);
90
+ };
91
+
92
+ interface FileSystemEntryLike {
93
+ isFile: boolean;
94
+ isDirectory: boolean;
95
+ file?: (
96
+ onSuccess: (file: File) => void,
97
+ onError?: (err: unknown) => void,
98
+ ) => void;
99
+ createReader?: () => {
100
+ readEntries: (
101
+ onSuccess: (entries: FileSystemEntryLike[]) => void,
102
+ onError?: (err: unknown) => void,
103
+ ) => void;
104
+ };
105
+ }
106
+
107
+ const entryToFile = (entry: FileSystemEntryLike): Promise<File> =>
108
+ new Promise((resolve, reject) => {
109
+ if (!entry.file) {
110
+ reject(new Error("Unable to read dropped file."));
111
+ return;
112
+ }
113
+ entry.file(resolve, reject);
114
+ });
115
+
116
+ const readDirectoryEntries = (
117
+ directory: FileSystemEntryLike,
118
+ ): Promise<FileSystemEntryLike[]> =>
119
+ new Promise((resolve, reject) => {
120
+ const reader = directory.createReader?.();
121
+ if (!reader) {
122
+ resolve([]);
123
+ return;
124
+ }
125
+ const entries: FileSystemEntryLike[] = [];
126
+ const readBatch = () => {
127
+ reader.readEntries((batch) => {
128
+ // readEntries returns results in batches; an empty batch marks the end.
129
+ if (batch.length === 0) {
130
+ resolve(entries);
131
+ return;
132
+ }
133
+ entries.push(...batch);
134
+ readBatch();
135
+ }, reject);
136
+ };
137
+ readBatch();
138
+ });
139
+
140
+ /**
141
+ * Collect the top-level files from a folder drag-and-drop event. Supports
142
+ * dropping a folder (its immediate children are used) or dropping the loose
143
+ * files directly. Falls back to `dataTransfer.files` when the entry API is
144
+ * unavailable.
145
+ */
146
+ export const collectDroppedFolderFiles = async (
147
+ dataTransfer: DataTransfer,
148
+ ): Promise<File[]> => {
149
+ const items = dataTransfer.items ? Array.from(dataTransfer.items) : [];
150
+ const entries: FileSystemEntryLike[] = [];
151
+ for (const item of items) {
152
+ const entry =
153
+ typeof item.webkitGetAsEntry === "function"
154
+ ? item.webkitGetAsEntry()
155
+ : null;
156
+ if (entry) {
157
+ entries.push(entry as unknown as FileSystemEntryLike);
158
+ }
159
+ }
160
+
161
+ if (entries.length === 0) {
162
+ return Array.from(dataTransfer.files ?? []);
163
+ }
164
+
165
+ const files: File[] = [];
166
+ for (const entry of entries) {
167
+ if (entry.isFile) {
168
+ files.push(await entryToFile(entry));
169
+ } else if (entry.isDirectory) {
170
+ const children = await readDirectoryEntries(entry);
171
+ for (const child of children) {
172
+ if (child.isFile) {
173
+ files.push(await entryToFile(child));
174
+ }
175
+ }
176
+ }
177
+ }
178
+ return files;
179
+ };
180
+
181
+ const readFileText = (file: File): Promise<string> =>
182
+ new Promise((resolve, reject) => {
183
+ const reader = new FileReader();
184
+ reader.onload = () => {
185
+ const result = reader.result;
186
+ if (typeof result === "string") {
187
+ resolve(result);
188
+ } else {
189
+ reject(new Error(`Unable to read ${file.name}.`));
190
+ }
191
+ };
192
+ reader.onerror = () =>
193
+ reject(reader.error ?? new Error(`Unable to read ${file.name}.`));
194
+ reader.readAsText(file);
195
+ });
196
+
197
+ /**
198
+ * Classify, validate, and read the workflow script and optional config from a
199
+ * folder's files. Throws {@link WorkflowFolderSelectionError} with a
200
+ * user-facing message on any problem.
201
+ */
202
+ export const loadWorkflowFolderSelection = async (
203
+ files: File[],
204
+ ): Promise<WorkflowFolderSelection> => {
205
+ const { script, config } = classifyWorkflowFolderFiles(files);
206
+
207
+ if (script.size > MAX_SCRIPT_UPLOAD_BYTES) {
208
+ throw new WorkflowFolderSelectionError(
209
+ `Workflow script must be ${formatUploadLimit(MAX_SCRIPT_UPLOAD_BYTES)} or smaller.`,
210
+ );
211
+ }
212
+ const scriptContent = await readFileText(script);
213
+
214
+ let configName: string | null = null;
215
+ let configContent: Record<string, unknown> | null = null;
216
+ if (config) {
217
+ if (config.size > MAX_CONFIG_UPLOAD_BYTES) {
218
+ throw new WorkflowFolderSelectionError(
219
+ `Config file must be ${formatUploadLimit(MAX_CONFIG_UPLOAD_BYTES)} or smaller.`,
220
+ );
221
+ }
222
+ const configText = await readFileText(config);
223
+ try {
224
+ configContent = JSON.parse(configText) as Record<string, unknown>;
225
+ } catch {
226
+ throw new WorkflowFolderSelectionError(
227
+ `${config.name} is not valid JSON.`,
228
+ );
229
+ }
230
+ configName = config.name;
231
+ }
232
+
233
+ return {
234
+ scriptName: script.name,
235
+ scriptContent,
236
+ configName,
237
+ configContent,
238
+ };
239
+ };
@@ -22,7 +22,9 @@ const parseTomlString = (value: string): string | undefined => {
22
22
  if (trimmed.startsWith('"') && trimmed.endsWith('"')) {
23
23
  try {
24
24
  const parsed = JSON.parse(trimmed) as unknown;
25
- return typeof parsed === "string" && parsed.trim() ? parsed.trim() : undefined;
25
+ return typeof parsed === "string" && parsed.trim()
26
+ ? parsed.trim()
27
+ : undefined;
26
28
  } catch {
27
29
  return undefined;
28
30
  }
@@ -498,7 +498,8 @@ export const extractCronConfigFromVersionGraph = (
498
498
 
499
499
  // Matches a value that is exactly a single `{{config.configurable.<name>}}`
500
500
  // placeholder, optionally padded with whitespace inside the braces.
501
- const CONFIGURABLE_TEMPLATE = /^\{\{\s*config\.configurable\.([^}\s.]+)\s*\}\}$/;
501
+ const CONFIGURABLE_TEMPLATE =
502
+ /^\{\{\s*config\.configurable\.([^}\s.]+)\s*\}\}$/;
502
503
 
503
504
  const resolveConfigurableTemplate = (
504
505
  value: string | null | undefined,
@@ -539,16 +540,11 @@ const resolveConfigurableCronConfig = (
539
540
  resolveConfigurableTemplate(config.expression, configurable) ??
540
541
  config.expression,
541
542
  timezone: resolveConfigurableTemplate(config.timezone, configurable) as
542
- | string
543
- | undefined,
543
+ string | undefined,
544
544
  start_at: resolveConfigurableTemplate(config.start_at, configurable) as
545
- | string
546
- | null
547
- | undefined,
545
+ string | null | undefined,
548
546
  end_at: resolveConfigurableTemplate(config.end_at, configurable) as
549
- | string
550
- | null
551
- | undefined,
547
+ string | null | undefined,
552
548
  };
553
549
  };
554
550
 
@@ -281,11 +281,9 @@ const parseWorkflowMetadata = (
281
281
 
282
282
  const workflowRecord = workflowMetadata as Record<string, unknown>;
283
283
  const snapshotPayload = workflowRecord.snapshot as
284
- | WorkflowSnapshot
285
- | undefined;
284
+ WorkflowSnapshot | undefined;
286
285
  const summaryPayload = workflowRecord.summary as
287
- | WorkflowDiffResult["summary"]
288
- | undefined;
286
+ WorkflowDiffResult["summary"] | undefined;
289
287
  const messagePayload = workflowRecord.message as string | undefined;
290
288
  const workflowToGraph = (workflowRecord.workflowToGraph ??
291
289
  workflowRecord.canvasToGraph) as Record<string, string> | undefined;
@@ -399,7 +399,9 @@ export const uploadWorkflowFromFiles = async (
399
399
  });
400
400
  } catch (error) {
401
401
  const message =
402
- error instanceof Error ? error.message : "Upload failed. Please try again.";
402
+ error instanceof Error
403
+ ? error.message
404
+ : "Upload failed. Please try again.";
403
405
  const uploadError = saveWorkflowUploadError(created.id, message);
404
406
  const stored = await ensureWorkflow(created.id);
405
407
  invalidateWorkflowListCache();
@@ -453,7 +455,9 @@ export const updateWorkflowFromFiles = async (
453
455
  });
454
456
  } catch (error) {
455
457
  const message =
456
- error instanceof Error ? error.message : "Update failed. Please try again.";
458
+ error instanceof Error
459
+ ? error.message
460
+ : "Update failed. Please try again.";
457
461
  saveWorkflowUploadError(workflowId, message);
458
462
  invalidateWorkflowListCache();
459
463
  invalidateWorkflowCache(workflowId);
@@ -161,12 +161,7 @@ export interface WorkflowListenerHealth {
161
161
  last_event_at?: string | null;
162
162
  last_error?: string | null;
163
163
  runtime_status:
164
- | "starting"
165
- | "healthy"
166
- | "backoff"
167
- | "stopped"
168
- | "error"
169
- | "unknown";
164
+ "starting" | "healthy" | "backoff" | "stopped" | "error" | "unknown";
170
165
  runtime_detail?: string | null;
171
166
  last_polled_at?: string | null;
172
167
  consecutive_failures: number;
@@ -7,7 +7,8 @@ export interface WorkflowUploadError {
7
7
 
8
8
  const STORAGE_PREFIX = "orcheo.workflowUploadError.";
9
9
 
10
- const storageKey = (workflowId: string): string => `${STORAGE_PREFIX}${workflowId}`;
10
+ const storageKey = (workflowId: string): string =>
11
+ `${STORAGE_PREFIX}${workflowId}`;
11
12
 
12
13
  const canUseSessionStorage = (): boolean =>
13
14
  typeof window !== "undefined" && Boolean(window.sessionStorage);
@@ -26,7 +27,10 @@ export const saveWorkflowUploadError = (
26
27
  }
27
28
 
28
29
  try {
29
- window.sessionStorage.setItem(storageKey(workflowId), JSON.stringify(error));
30
+ window.sessionStorage.setItem(
31
+ storageKey(workflowId),
32
+ JSON.stringify(error),
33
+ );
30
34
  } catch {
31
35
  // Upload failure rendering should not depend on storage availability.
32
36
  }
@@ -61,15 +61,27 @@ const runtimeStatusBadge = (
61
61
  ) => {
62
62
  switch (status) {
63
63
  case "healthy":
64
- return { label: "Healthy", className: "bg-emerald-600 text-white" };
64
+ return {
65
+ label: "Healthy",
66
+ className: "bg-success text-success-foreground",
67
+ };
65
68
  case "starting":
66
- return { label: "Starting", className: "bg-sky-600 text-white" };
69
+ return { label: "Starting", className: "bg-info text-info-foreground" };
67
70
  case "backoff":
68
- return { label: "Backoff", className: "bg-amber-500 text-black" };
71
+ return {
72
+ label: "Backoff",
73
+ className: "bg-warning text-warning-foreground",
74
+ };
69
75
  case "error":
70
- return { label: "Runtime Error", className: "bg-rose-600 text-white" };
76
+ return {
77
+ label: "Runtime Error",
78
+ className: "bg-destructive text-destructive-foreground",
79
+ };
71
80
  case "stopped":
72
- return { label: "Stopped", className: "bg-slate-500 text-white" };
81
+ return {
82
+ label: "Stopped",
83
+ className: "bg-muted-foreground text-background",
84
+ };
73
85
  default:
74
86
  return { label: "Unknown", className: "bg-muted text-muted-foreground" };
75
87
  }
@@ -274,13 +286,13 @@ export function SettingsTabContent({
274
286
  </AlertDescription>
275
287
  </Alert>
276
288
  ) : isListenersLoading ? (
277
- <Card>
289
+ <Card className="border-border/70 bg-muted/40 shadow-none">
278
290
  <CardContent className="pt-6 text-sm text-muted-foreground">
279
291
  Loading listener health and control state...
280
292
  </CardContent>
281
293
  </Card>
282
294
  ) : listeners.length === 0 ? (
283
- <Card>
295
+ <Card className="border-border/70 bg-muted/40 shadow-none">
284
296
  <CardHeader>
285
297
  <CardTitle>No listeners detected</CardTitle>
286
298
  <CardDescription>
@@ -320,7 +332,10 @@ export function SettingsTabContent({
320
332
  listener.status === "paused" || listener.status === "blocked";
321
333
 
322
334
  return (
323
- <Card key={listener.subscription_id}>
335
+ <Card
336
+ key={listener.subscription_id}
337
+ className="border-border/70 bg-muted/40 shadow-none"
338
+ >
324
339
  <CardHeader className="gap-3 sm:flex-row sm:items-start sm:justify-between">
325
340
  <div className="space-y-2">
326
341
  <div className="flex flex-wrap items-center gap-2">
@@ -394,7 +409,7 @@ export function SettingsTabContent({
394
409
 
395
410
  {(listener.runtime_detail || listener.last_error) && (
396
411
  <div className="sm:col-span-2 xl:col-span-4">
397
- <div className="rounded-lg border border-amber-300/50 bg-amber-50/70 px-3 py-2 text-amber-950 dark:border-amber-700/50 dark:bg-amber-950/30 dark:text-amber-100">
412
+ <div className="rounded-lg border border-warning/40 bg-warning-muted/70 px-3 py-2 text-warning-muted-foreground">
398
413
  <div className="mb-1 flex items-center gap-2 text-xs font-semibold uppercase tracking-[0.16em]">
399
414
  <Zap className="h-3.5 w-3.5" />
400
415
  Runtime detail
@@ -239,7 +239,7 @@ const resolveRunResultBanner = (
239
239
  return {
240
240
  Icon: CheckCircle2,
241
241
  className:
242
- "border-emerald-500/40 bg-emerald-50 text-emerald-700 dark:bg-emerald-950/40 dark:text-emerald-300",
242
+ "border-success/40 bg-success-muted text-success-muted-foreground",
243
243
  message: "Workflow run succeeded. See the full trace on the Trace tab.",
244
244
  };
245
245
  case "failed":
@@ -252,7 +252,7 @@ const resolveRunResultBanner = (
252
252
  return {
253
253
  Icon: AlertTriangle,
254
254
  className:
255
- "border-amber-500/40 bg-amber-50 text-amber-700 dark:bg-amber-950/40 dark:text-amber-300",
255
+ "border-warning/40 bg-warning-muted text-warning-muted-foreground",
256
256
  message:
257
257
  "Workflow run finished with partial results. Check the Trace tab for details.",
258
258
  };
@@ -261,7 +261,7 @@ const resolveRunResultBanner = (
261
261
  return {
262
262
  Icon: AlertTriangle,
263
263
  className:
264
- "border-amber-500/40 bg-amber-50 text-amber-700 dark:bg-amber-950/40 dark:text-amber-300",
264
+ "border-warning/40 bg-warning-muted text-warning-muted-foreground",
265
265
  message:
266
266
  "Workflow run ended without a final status. Check the Trace tab for the latest status.",
267
267
  };
@@ -752,7 +752,9 @@ export function WorkflowTabContent({
752
752
  void handleScheduleToggle(checked)
753
753
  }
754
754
  disabled={
755
- isSchedulePending || !canToggleSchedule || uploadBlocksWorkflow
755
+ isSchedulePending ||
756
+ !canToggleSchedule ||
757
+ uploadBlocksWorkflow
756
758
  }
757
759
  />
758
760
  </div>
@@ -92,7 +92,10 @@ export function useWorkflowExecutionController(
92
92
 
93
93
  const graphToWorkflow = latestVersionRecord?.graphToWorkflow ?? {};
94
94
  const executionId = generateRandomId("run");
95
- const executionRecord = createExecutionRecord(executionId, graphToWorkflow);
95
+ const executionRecord = createExecutionRecord(
96
+ executionId,
97
+ graphToWorkflow,
98
+ );
96
99
 
97
100
  if (core.websocketRef.current) {
98
101
  core.websocketRef.current.close();
@@ -48,8 +48,10 @@ export function useExecutionUpdates({
48
48
  );
49
49
 
50
50
  const describePayload = useCallback(
51
- (payload: Record<string, unknown>, graphToWorkflow: Record<string, string>) =>
52
- describePayloadRaw(payload, graphToWorkflow, (id) => id),
51
+ (
52
+ payload: Record<string, unknown>,
53
+ graphToWorkflow: Record<string, string>,
54
+ ) => describePayloadRaw(payload, graphToWorkflow, (id) => id),
53
55
  [],
54
56
  );
55
57
 
@@ -70,9 +70,7 @@ export const CreateTeamDialog = ({
70
70
  await onCreate(trimmedName, trimmedSlug);
71
71
  onOpenChange(false);
72
72
  } catch (err) {
73
- setError(
74
- err instanceof Error ? err.message : "Failed to create team.",
75
- );
73
+ setError(err instanceof Error ? err.message : "Failed to create team.");
76
74
  } finally {
77
75
  setIsSubmitting(false);
78
76
  }
@@ -118,9 +116,7 @@ export const CreateTeamDialog = ({
118
116
  underscores only.
119
117
  </p>
120
118
  </div>
121
- {error ? (
122
- <p className="text-sm text-destructive">{error}</p>
123
- ) : null}
119
+ {error ? <p className="text-sm text-destructive">{error}</p> : null}
124
120
  </div>
125
121
 
126
122
  <DialogFooter>
@@ -213,7 +213,7 @@ export const WorkflowCard = ({
213
213
  <>
214
214
  <Card
215
215
  className={cn(
216
- "flex w-full flex-col overflow-hidden border-border/70 bg-card text-card-foreground shadow-[0_16px_48px_rgba(15,23,42,0.08)] transition-transform duration-200 hover:-translate-y-1 dark:shadow-[0_16px_48px_rgba(0,0,0,0.45)] text-[20px] md:text-[14px]",
216
+ "flex w-full flex-col overflow-hidden border-border/70 bg-card text-card-foreground shadow-[0_16px_48px_rgba(40,30,16,0.1)] transition-transform duration-200 hover:-translate-y-1 dark:shadow-[0_16px_48px_rgba(0,0,0,0.45)] text-[20px] md:text-[14px]",
217
217
  WORKFLOW_GALLERY_CARD_ASPECT_CLASSNAME,
218
218
  isClickable && "cursor-pointer",
219
219
  )}
@@ -223,7 +223,7 @@ export const WorkflowCard = ({
223
223
  role={isClickable ? "button" : undefined}
224
224
  tabIndex={isClickable ? 0 : undefined}
225
225
  >
226
- <CardHeader className="relative flex h-12 items-center justify-center overflow-hidden border-b border-border/70 bg-gradient-to-r from-slate-950 via-slate-900 to-slate-800 px-4 py-0 text-center text-slate-50 dark:border-border/60 dark:from-slate-100 dark:via-slate-50 dark:to-slate-200 dark:text-slate-950">
226
+ <CardHeader className="relative flex h-12 items-center justify-center overflow-hidden border-b border-border/70 bg-gradient-to-r from-stone-950 via-stone-900 to-stone-800 px-4 py-0 text-center text-stone-50 dark:border-border/60 dark:from-stone-100 dark:via-stone-50 dark:to-stone-200 dark:text-stone-950">
227
227
  <div
228
228
  className="absolute inset-0 opacity-25 dark:opacity-40"
229
229
  aria-hidden="true"
@@ -235,7 +235,7 @@ export const WorkflowCard = ({
235
235
  />
236
236
 
237
237
  <div className="relative z-10 flex items-center gap-2">
238
- <span className="font-mono text-[10px] uppercase tracking-[0.22em] text-white/70 dark:text-slate-700">
238
+ <span className="font-mono text-[10px] uppercase tracking-[0.22em] text-white/70 dark:text-stone-700">
239
239
  {headerLabel}
240
240
  </span>
241
241
  </div>
@@ -253,7 +253,7 @@ export const WorkflowCard = ({
253
253
  <Button
254
254
  variant="ghost"
255
255
  size="icon"
256
- className="absolute right-2 top-2 z-10 h-7 w-7 text-white/75 hover:bg-white/10 hover:text-white dark:text-slate-700 dark:hover:bg-slate-950/10 dark:hover:text-slate-950"
256
+ className="absolute right-2 top-2 z-10 h-7 w-7 text-white/75 hover:bg-white/10 hover:text-white dark:text-stone-700 dark:hover:bg-stone-950/10 dark:hover:text-stone-950"
257
257
  onClick={stopPropagation}
258
258
  onPointerDown={stopPropagation}
259
259
  aria-label="Workflow actions"
@@ -290,7 +290,7 @@ export const WorkflowCard = ({
290
290
  <>
291
291
  <DropdownMenuSeparator />
292
292
  <DropdownMenuItem
293
- className="text-red-600"
293
+ className="text-destructive"
294
294
  onSelect={(event) => {
295
295
  event.preventDefault();
296
296
  event.stopPropagation();
@@ -314,31 +314,31 @@ export const WorkflowCard = ({
314
314
  className={cn(
315
315
  "absolute left-0 top-0 flex items-center gap-1 rounded-md border px-2 py-1 shadow-sm",
316
316
  uploadError
317
- ? "border-red-500/40 bg-red-50 text-red-700 dark:bg-red-950/40 dark:text-red-300"
318
- : "border-border/70 bg-muted/60 text-muted-foreground dark:bg-slate-950/20",
317
+ ? "border-destructive/40 bg-destructive-muted text-destructive-muted-foreground"
318
+ : "border-border/70 bg-muted/60 text-muted-foreground dark:bg-background/30",
319
319
  )}
320
320
  >
321
321
  <span
322
322
  className={cn(
323
323
  "h-1.5 w-1.5 rounded-full",
324
324
  uploadError
325
- ? "bg-red-600 shadow-[0_0_6px_rgba(220,38,38,0.45)] dark:bg-red-300"
326
- : "bg-slate-800 shadow-[0_0_6px_rgba(15,23,42,0.32)] dark:bg-slate-200 dark:shadow-[0_0_6px_rgba(226,232,240,0.28)]",
325
+ ? "bg-destructive shadow-[0_0_6px_hsl(var(--destructive)/0.5)]"
326
+ : "bg-foreground shadow-[0_0_6px_hsl(var(--foreground)/0.35)]",
327
327
  )}
328
328
  />
329
329
  <span
330
330
  className={cn(
331
331
  "font-mono text-[9px] font-bold uppercase tracking-[0.1em]",
332
332
  uploadError
333
- ? "text-red-700 dark:text-red-300"
334
- : "text-slate-700 dark:text-slate-300",
333
+ ? "text-destructive-muted-foreground"
334
+ : "text-muted-foreground",
335
335
  )}
336
336
  >
337
337
  {uploadError ? "Error" : "AI"}
338
338
  </span>
339
339
  </div>
340
340
 
341
- <div className="flex h-[7em] w-[7em] items-center justify-center overflow-hidden rounded-full border-4 border-slate-900 bg-slate-100 shadow-[0_4px_16px_rgba(15,23,42,0.18)] dark:border-slate-100 dark:bg-slate-800 dark:shadow-[0_4px_16px_rgba(0,0,0,0.35)]">
341
+ <div className="flex h-[7em] w-[7em] items-center justify-center overflow-hidden rounded-full border-4 border-primary bg-muted shadow-[0_4px_16px_rgba(248,120,37,0.24)] dark:shadow-[0_4px_16px_rgba(0,0,0,0.35)]">
342
342
  <img
343
343
  src={avatarUrl}
344
344
  alt={workflow.name}
@@ -354,7 +354,7 @@ export const WorkflowCard = ({
354
354
  <Button
355
355
  variant="outline"
356
356
  size="icon"
357
- className="absolute right-10 top-0 h-8 w-8 border-emerald-500/40 bg-emerald-50 text-emerald-700 hover:bg-emerald-100 dark:bg-emerald-950/40 dark:text-emerald-300 dark:hover:bg-emerald-900/60"
357
+ className="absolute right-10 top-0 h-8 w-8 border-success/40 bg-success-muted text-success-muted-foreground hover:bg-success/20"
358
358
  aria-label={`Update ${workflow.name} to candidate version ${candidateUpdate.latestVersion}`}
359
359
  data-card-action="true"
360
360
  onClick={(event) => {
@@ -376,7 +376,7 @@ export const WorkflowCard = ({
376
376
  <p>{candidateUpdate.latestSummary}</p>
377
377
  ) : null}
378
378
  {candidateUpdate.firstMigration ? (
379
- <p className="text-amber-600 dark:text-amber-300">
379
+ <p className="text-warning-muted-foreground">
380
380
  {candidateUpdate.firstMigration}
381
381
  </p>
382
382
  ) : null}
@@ -490,7 +490,7 @@ export const WorkflowCard = ({
490
490
  </DialogHeader>
491
491
 
492
492
  {candidateUpdate.isMajor ? (
493
- <div className="rounded-md border border-amber-500/50 bg-amber-50 px-3 py-2 text-sm text-amber-900 dark:bg-amber-950/30 dark:text-amber-200">
493
+ <div className="rounded-md border border-warning/50 bg-warning-muted px-3 py-2 text-sm text-warning-muted-foreground">
494
494
  This is a major candidate update. Review migration notes before
495
495
  applying it to this colleague.
496
496
  </div>
@@ -508,7 +508,7 @@ export const WorkflowCard = ({
508
508
  </div>
509
509
  <p className="mt-1 text-muted-foreground">{note.summary}</p>
510
510
  {note.migration ? (
511
- <p className="mt-2 text-amber-700 dark:text-amber-300">
511
+ <p className="mt-2 text-warning-muted-foreground">
512
512
  {note.migration}
513
513
  </p>
514
514
  ) : null}