@revenexx/studio-integrations 0.5.1 → 0.7.0

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 (171) hide show
  1. package/CHANGELOG.md +428 -0
  2. package/README.md +48 -4
  3. package/dist/module.d.mts +17 -1
  4. package/dist/module.json +1 -1
  5. package/dist/module.mjs +10 -2
  6. package/dist/runtime/components/Integrations/CredentialCreateDialog.vue +43 -41
  7. package/dist/runtime/components/Integrations/CredentialDetailDialog.vue +123 -50
  8. package/dist/runtime/components/Integrations/CredentialRevenexxSource.vue +108 -71
  9. package/dist/runtime/components/Integrations/CredentialTypePicker.vue +12 -15
  10. package/dist/runtime/components/Integrations/DeadLettersPanel.vue +18 -17
  11. package/dist/runtime/components/Integrations/EventTypeCombobox.d.vue.ts +1 -0
  12. package/dist/runtime/components/Integrations/EventTypeCombobox.vue +7 -4
  13. package/dist/runtime/components/Integrations/EventTypeCombobox.vue.d.ts +1 -0
  14. package/dist/runtime/components/Integrations/ExpressionInput.d.vue.ts +4 -0
  15. package/dist/runtime/components/Integrations/ExpressionInput.vue +67 -37
  16. package/dist/runtime/components/Integrations/ExpressionInput.vue.d.ts +4 -0
  17. package/dist/runtime/components/Integrations/GroupFrameView.vue +30 -20
  18. package/dist/runtime/components/Integrations/Layout.vue +48 -4
  19. package/dist/runtime/components/Integrations/NodeConfigDynamicField.vue +23 -23
  20. package/dist/runtime/components/Integrations/NodeConfigDynamicSchema.vue +11 -13
  21. package/dist/runtime/components/Integrations/NodeConfigField.d.vue.ts +2 -0
  22. package/dist/runtime/components/Integrations/NodeConfigField.vue +187 -93
  23. package/dist/runtime/components/Integrations/NodeConfigField.vue.d.ts +2 -0
  24. package/dist/runtime/components/Integrations/NodeInspector.d.vue.ts +6 -0
  25. package/dist/runtime/components/Integrations/NodeInspector.vue +1157 -376
  26. package/dist/runtime/components/Integrations/NodeInspector.vue.d.ts +6 -0
  27. package/dist/runtime/components/Integrations/NodeLogo.d.vue.ts +12 -0
  28. package/dist/runtime/components/Integrations/NodeLogo.vue +2 -30
  29. package/dist/runtime/components/Integrations/NodeLogo.vue.d.ts +12 -0
  30. package/dist/runtime/components/Integrations/NodeMark.d.vue.ts +15 -0
  31. package/dist/runtime/components/Integrations/NodeMark.vue +79 -0
  32. package/dist/runtime/components/Integrations/NodeMark.vue.d.ts +15 -0
  33. package/dist/runtime/components/Integrations/NodeOutputSchema.d.vue.ts +21 -0
  34. package/dist/runtime/components/Integrations/NodeOutputSchema.vue +43 -0
  35. package/dist/runtime/components/Integrations/NodeOutputSchema.vue.d.ts +21 -0
  36. package/dist/runtime/components/Integrations/NodePalette.vue +122 -55
  37. package/dist/runtime/components/Integrations/QueryBuilder.vue +46 -38
  38. package/dist/runtime/components/Integrations/RunActionsMenu.vue +38 -12
  39. package/dist/runtime/components/Integrations/RunDetailPanel.vue +114 -62
  40. package/dist/runtime/components/Integrations/RunPayloadInput.vue +27 -13
  41. package/dist/runtime/components/Integrations/RunResponseModal.vue +11 -10
  42. package/dist/runtime/components/Integrations/RunsTable.d.vue.ts +0 -1
  43. package/dist/runtime/components/Integrations/RunsTable.vue +38 -16
  44. package/dist/runtime/components/Integrations/RunsTable.vue.d.ts +0 -1
  45. package/dist/runtime/components/Integrations/SftpAccountImportDialog.vue +8 -7
  46. package/dist/runtime/components/Integrations/Sidebar.vue +14 -10
  47. package/dist/runtime/components/Integrations/StickyNoteView.d.vue.ts +14 -4
  48. package/dist/runtime/components/Integrations/StickyNoteView.vue +38 -23
  49. package/dist/runtime/components/Integrations/StickyNoteView.vue.d.ts +14 -4
  50. package/dist/runtime/components/Integrations/TemplateCard.d.vue.ts +18 -0
  51. package/dist/runtime/components/Integrations/TemplateCard.vue +119 -0
  52. package/dist/runtime/components/Integrations/TemplateCard.vue.d.ts +18 -0
  53. package/dist/runtime/components/Integrations/TemplateFlowPreview.d.vue.ts +2 -2
  54. package/dist/runtime/components/Integrations/TemplateFlowPreview.vue +16 -18
  55. package/dist/runtime/components/Integrations/TemplateFlowPreview.vue.d.ts +2 -2
  56. package/dist/runtime/components/Integrations/WorkflowContextMenu.vue +34 -33
  57. package/dist/runtime/components/Integrations/WorkflowCreateDialog.vue +52 -67
  58. package/dist/runtime/components/Integrations/WorkflowDuplicateDialog.vue +8 -7
  59. package/dist/runtime/components/Integrations/WorkflowEdgeView.d.vue.ts +18 -4
  60. package/dist/runtime/components/Integrations/WorkflowEdgeView.vue +29 -11
  61. package/dist/runtime/components/Integrations/WorkflowEdgeView.vue.d.ts +18 -4
  62. package/dist/runtime/components/Integrations/WorkflowEditor.d.vue.ts +20 -1
  63. package/dist/runtime/components/Integrations/WorkflowEditor.vue +240 -116
  64. package/dist/runtime/components/Integrations/WorkflowEditor.vue.d.ts +20 -1
  65. package/dist/runtime/components/Integrations/WorkflowHistoryPanel.vue +28 -43
  66. package/dist/runtime/components/Integrations/WorkflowIconPicker.d.vue.ts +19 -1
  67. package/dist/runtime/components/Integrations/WorkflowIconPicker.vue +17 -7
  68. package/dist/runtime/components/Integrations/WorkflowIconPicker.vue.d.ts +19 -1
  69. package/dist/runtime/components/Integrations/WorkflowNodeView.d.vue.ts +4 -0
  70. package/dist/runtime/components/Integrations/WorkflowNodeView.vue +198 -56
  71. package/dist/runtime/components/Integrations/WorkflowNodeView.vue.d.ts +4 -0
  72. package/dist/runtime/components/Integrations/WorkflowRunReplayPanel.vue +56 -59
  73. package/dist/runtime/components/Integrations/WorkflowSettingsDialog.vue +28 -21
  74. package/dist/runtime/components/Integrations/WorkflowTagsInput.d.vue.ts +1 -0
  75. package/dist/runtime/components/Integrations/WorkflowTagsInput.vue +15 -9
  76. package/dist/runtime/components/Integrations/WorkflowTagsInput.vue.d.ts +1 -0
  77. package/dist/runtime/components/Integrations/WorkflowTriggerView.vue +15 -10
  78. package/dist/runtime/components/Integrations/WorkflowVariablesDialog.vue +50 -36
  79. package/dist/runtime/components/Integrations/WorkflowsTable.vue +126 -48
  80. package/dist/runtime/composables/integrationsApi.js +4 -2
  81. package/dist/runtime/composables/useCredentialTypes.js +8 -6
  82. package/dist/runtime/composables/useIntegrationsAudit.js +4 -3
  83. package/dist/runtime/composables/useIntegrationsCredentials.js +10 -8
  84. package/dist/runtime/composables/useIntegrationsDeadLetters.js +7 -6
  85. package/dist/runtime/composables/useIntegrationsFocus.d.ts +12 -0
  86. package/dist/runtime/composables/useIntegrationsLocale.d.ts +26 -0
  87. package/dist/runtime/composables/useIntegrationsLocale.js +8 -0
  88. package/dist/runtime/composables/useIntegrationsNodes.js +7 -5
  89. package/dist/runtime/composables/useIntegrationsRealtime.d.ts +149 -0
  90. package/dist/runtime/composables/useIntegrationsRealtime.js +80 -0
  91. package/dist/runtime/composables/useIntegrationsRevisions.js +9 -7
  92. package/dist/runtime/composables/useIntegrationsRuns.d.ts +1 -1
  93. package/dist/runtime/composables/useIntegrationsRuns.js +14 -7
  94. package/dist/runtime/composables/useIntegrationsSchedules.d.ts +4 -0
  95. package/dist/runtime/composables/useIntegrationsSchedules.js +10 -3
  96. package/dist/runtime/composables/useIntegrationsSecrets.js +14 -9
  97. package/dist/runtime/composables/useIntegrationsT.d.ts +35 -0
  98. package/dist/runtime/composables/useIntegrationsT.js +9 -0
  99. package/dist/runtime/composables/useIntegrationsTemplates.d.ts +3 -3
  100. package/dist/runtime/composables/useIntegrationsTemplates.js +28 -16
  101. package/dist/runtime/composables/useIntegrationsTriggers.js +10 -8
  102. package/dist/runtime/composables/useIntegrationsWorkflows.js +13 -7
  103. package/dist/runtime/composables/useLoadGate.d.ts +17 -0
  104. package/dist/runtime/composables/useLoadGate.js +8 -0
  105. package/dist/runtime/composables/useNodeConfigResolve.js +2 -1
  106. package/dist/runtime/data/branchReorder.d.ts +37 -0
  107. package/dist/runtime/data/branchReorder.js +71 -0
  108. package/dist/runtime/data/handleId.d.ts +23 -0
  109. package/dist/runtime/data/handleId.js +6 -0
  110. package/dist/runtime/data/integrationNodeCatalog.d.ts +41 -0
  111. package/dist/runtime/data/integrationNodeCatalog.js +14 -0
  112. package/dist/runtime/data/integrationTemplates.d.ts +48 -0
  113. package/dist/runtime/data/integrationTemplates.js +21 -0
  114. package/dist/runtime/data/integrationWorkflowSamples.js +1 -1
  115. package/dist/runtime/data/nodeIcons.d.ts +2 -0
  116. package/dist/runtime/data/nodeIcons.js +73 -0
  117. package/dist/runtime/data/nodeInputData.d.ts +16 -4
  118. package/dist/runtime/data/nodeInputData.js +4 -0
  119. package/dist/runtime/data/payloadForm.d.ts +9 -2
  120. package/dist/runtime/data/payloadForm.js +29 -4
  121. package/dist/runtime/data/portTone.d.ts +17 -0
  122. package/dist/runtime/data/portTone.js +7 -0
  123. package/dist/runtime/data/webhookAuth.d.ts +18 -0
  124. package/dist/runtime/data/webhookAuth.js +19 -0
  125. package/dist/runtime/data/workflowIconCatalog.js +4 -6
  126. package/dist/runtime/i18n/messages/de.json +1207 -0
  127. package/dist/runtime/i18n/messages/en.json +1207 -0
  128. package/dist/runtime/i18n/translate.d.ts +83 -0
  129. package/dist/runtime/i18n/translate.js +41 -0
  130. package/dist/runtime/pages/integrations/audit.vue +140 -37
  131. package/dist/runtime/pages/integrations/credentials/oauth-complete.vue +9 -6
  132. package/dist/runtime/pages/integrations/credentials.vue +180 -64
  133. package/dist/runtime/pages/integrations/index.vue +91 -92
  134. package/dist/runtime/pages/integrations/runs.vue +33 -21
  135. package/dist/runtime/pages/integrations/schedules.vue +88 -23
  136. package/dist/runtime/pages/integrations/secrets.vue +57 -35
  137. package/dist/runtime/pages/integrations/templates/[slug].vue +119 -88
  138. package/dist/runtime/pages/integrations/templates/index.vue +82 -103
  139. package/dist/runtime/pages/integrations/workflows/[id].vue +344 -91
  140. package/dist/runtime/pages/integrations/workflows/index.vue +23 -5
  141. package/dist/runtime/plugins/locale.d.ts +10 -0
  142. package/dist/runtime/plugins/locale.js +6 -0
  143. package/dist/runtime/plugins/realtime.client.d.ts +32 -0
  144. package/dist/runtime/plugins/realtime.client.js +4 -0
  145. package/dist/runtime/types/integrations.d.ts +65 -6
  146. package/dist/runtime/utils/apiError.d.ts +47 -0
  147. package/dist/runtime/utils/apiError.js +32 -0
  148. package/dist/runtime/utils/cockpitSources.d.ts +153 -0
  149. package/dist/runtime/utils/cockpitSources.js +14 -0
  150. package/dist/runtime/utils/credentialState.d.ts +24 -1
  151. package/dist/runtime/utils/credentialState.js +26 -11
  152. package/dist/runtime/utils/loadGate.d.ts +61 -0
  153. package/dist/runtime/utils/loadGate.js +22 -0
  154. package/dist/runtime/utils/localize.d.ts +46 -4
  155. package/dist/runtime/utils/localize.js +34 -7
  156. package/dist/runtime/utils/notify.d.ts +59 -0
  157. package/dist/runtime/utils/notify.js +21 -0
  158. package/dist/runtime/utils/pollBudget.d.ts +72 -0
  159. package/dist/runtime/utils/pollBudget.js +17 -0
  160. package/dist/runtime/utils/revenexxCredentialSource.d.ts +26 -0
  161. package/dist/runtime/utils/revenexxCredentialSource.js +8 -0
  162. package/dist/runtime/utils/schedule.d.ts +116 -0
  163. package/dist/runtime/utils/schedule.js +219 -0
  164. package/dist/runtime/utils/statusStyle.d.ts +26 -2
  165. package/dist/runtime/utils/statusStyle.js +9 -1
  166. package/dist/runtime/utils/validationFindings.d.ts +101 -0
  167. package/dist/runtime/utils/validationFindings.js +54 -0
  168. package/dist/runtime/utils/whenIdle.d.ts +42 -0
  169. package/dist/runtime/utils/whenIdle.js +12 -0
  170. package/dist/types.d.mts +2 -6
  171. package/package.json +10 -3
@@ -8,8 +8,27 @@ type __VLS_Props = {
8
8
  runBusy?: boolean;
9
9
  /** The run is queued behind a bundle build — busy, but worth saying why. */
10
10
  runWaiting?: boolean;
11
+ /**
12
+ * One of the page's own lightboxes is open above the editor (settings,
13
+ * duplicate, run payload, the sync response, the deactivation prompt). The
14
+ * canvas stands its keyboard shortcuts down while one is up — see
15
+ * `canvasHasKeyboard` below. A new page-level dialog belongs in here too.
16
+ */
17
+ overlayOpen?: boolean;
11
18
  };
12
- declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
19
+ /**
20
+ * Show a node the user was sent to from outside the canvas — the validation
21
+ * banner's per-node line (PO-209). Centres the card and opens its settings,
22
+ * which is where the fields at fault are.
23
+ *
24
+ * A node inside a collapsed group has no card on the canvas: centring would pan
25
+ * to empty space, so it only opens the settings. The reverse can't happen — an
26
+ * id that is on the canvas is in the blob by construction.
27
+ */
28
+ declare function revealNode(id: string): void;
29
+ declare const _default: import("vue").DefineComponent<__VLS_Props, {
30
+ revealNode: typeof revealNode;
31
+ }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
13
32
  change: (blob: WorkflowBlob) => any;
14
33
  run: () => any;
15
34
  toggleActive: () => any;
@@ -2,7 +2,6 @@
2
2
  import { computed, markRaw, nextTick, onMounted, provide, ref, watch } from "vue";
3
3
  import {
4
4
  MarkerType,
5
- PanOnScrollMode,
6
5
  VueFlow,
7
6
  useVueFlow
8
7
  } from "@vue-flow/core";
@@ -25,6 +24,7 @@ const props = defineProps({
25
24
  });
26
25
  const emit = defineEmits(["restored"]);
27
26
  const revisionsApi = useIntegrationsRevisions();
27
+ const t = useIntegrationsT();
28
28
  const entries = ref([]);
29
29
  const page = ref(1);
30
30
  const lastPage = ref(1);
@@ -65,24 +65,9 @@ function onResolvedOutputs(nodeId, outputs) {
65
65
  const previewNodes = ref([]);
66
66
  const previewEdges = ref([]);
67
67
  const preview = useVueFlow("workflow-history-preview");
68
- provide("workflow-node-actions", { duplicate: () => {
69
- }, remove: () => {
70
- } });
68
+ provide("workflow-node-actions", null);
71
69
  function edgeColorByKind(kind) {
72
- switch (kind) {
73
- case "success":
74
- return "#10b981";
75
- case "error":
76
- return "#f43f5e";
77
- case "branch-true":
78
- return "#10b981";
79
- case "branch-false":
80
- return "#f59e0b";
81
- case "branch":
82
- return "#8b5cf6";
83
- default:
84
- return "#94a3b8";
85
- }
70
+ return kind === "error" ? "#cbd5e1" : "#94a3b8";
86
71
  }
87
72
  const GROUP_PAD = 24;
88
73
  const GROUP_LABEL_ROOM = 26;
@@ -126,7 +111,7 @@ function buildPreview(blob) {
126
111
  const nodePos = (id) => {
127
112
  const wn = blob.nodes.find((n) => n.id === id);
128
113
  if (wn) return { x: wn.position?.x ?? 0, y: wn.position?.y ?? 0 };
129
- const ti = blob.triggers.findIndex((t) => t.id === id);
114
+ const ti = blob.triggers.findIndex((t2) => t2.id === id);
130
115
  if (ti >= 0) return triggerPos(id, ti);
131
116
  return { x: 0, y: 0 };
132
117
  };
@@ -140,6 +125,7 @@ function buildPreview(blob) {
140
125
  if (output === "true") return "branch-true";
141
126
  if (output === "false") return "branch-false";
142
127
  if (output === "error") return "error";
128
+ if (output === "done") return "success";
143
129
  const outs = [...manifest.outputs ?? [], ...dynamicOutputs.value[sourceNodeId] ?? []];
144
130
  const portDef = outs.find((o) => o.name === output);
145
131
  if (portDef) {
@@ -164,16 +150,16 @@ function buildPreview(blob) {
164
150
  position: { x: s.position.x, y: s.position.y },
165
151
  zIndex: -100 + i,
166
152
  draggable: false,
167
- data: { sticky: s, onUpdate: () => {
168
- }, onRemove: () => {
169
- } }
153
+ // No callbacks: that absence is what makes the note read-only — it then
154
+ // draws no toolbar, no resize handle and does not open for typing (PO-284).
155
+ data: { sticky: s }
170
156
  })),
171
- ...blob.triggers.map((t, i) => ({
172
- id: t.id,
157
+ ...blob.triggers.map((t2, i) => ({
158
+ id: t2.id,
173
159
  type: "trigger",
174
- position: triggerPos(t.id, i),
160
+ position: triggerPos(t2.id, i),
175
161
  draggable: false,
176
- data: t
162
+ data: t2
177
163
  })),
178
164
  ...blob.nodes.map((n) => ({
179
165
  id: n.id,
@@ -187,7 +173,7 @@ function buildPreview(blob) {
187
173
  const kind = edgeKind(e.from.nodeId, e.from.output);
188
174
  const wn = blob.nodes.find((n) => n.id === e.from.nodeId);
189
175
  const manifest = wn ? findManifest(wn.nodeSlug, wn.nodeVersion) : null;
190
- const isBranchPort = (manifest?.outputs ?? []).some((o) => o.sourceFromConfig) || ["true", "false", "error"].includes(e.from.output);
176
+ const isBranchPort = (manifest?.outputs ?? []).some((o) => o.sourceFromConfig) || ["true", "false"].includes(e.from.output);
191
177
  const srcSide = pickSourceSide(e.from.nodeId, e.to.nodeId);
192
178
  return {
193
179
  id: e.id,
@@ -233,23 +219,23 @@ async function select(revision) {
233
219
  buildPreview(detail.blob);
234
220
  }
235
221
  } catch (e) {
236
- toast.error(e?.message ?? "Could not load the revision.");
222
+ toast.error(e?.message ?? t("integration.history.toast.loadRevisionFailed"));
237
223
  } finally {
238
224
  detailLoading.value = false;
239
225
  }
240
226
  }
241
227
  async function restoreRevision(rev) {
242
- if (!confirm(`Restore revision ${rev.revision}? The current version is archived first, then the workflow is rebuilt from this snapshot.`)) return;
228
+ if (!confirm(t("integration.history.confirm.restore", { revision: rev.revision }))) return;
243
229
  restoring.value = rev.revision;
244
230
  try {
245
231
  await revisionsApi.restore(props.workflow.id, rev.revision);
246
- toast.success(`Revision ${rev.revision} restored`);
232
+ toast.success(t("integration.history.toast.restored", { revision: rev.revision }));
247
233
  detailCache.clear();
248
234
  emit("restored");
249
235
  await loadList(true);
250
236
  void select(null);
251
237
  } catch (e) {
252
- toast.error(e?.message ?? "Could not restore the revision.");
238
+ toast.error(e?.message ?? t("integration.history.toast.restoreFailed"));
253
239
  } finally {
254
240
  restoring.value = null;
255
241
  }
@@ -274,7 +260,7 @@ const nodeTypes = {
274
260
  const edgeTypes = {
275
261
  flow: markRaw(WorkflowEdgeView)
276
262
  };
277
- const previewTitle = computed(() => selected.value === null ? `Current version \xB7 revision ${props.workflow.revision}` : `Revision ${selected.value} (read-only)`);
263
+ const previewTitle = computed(() => selected.value === null ? t("integration.history.preview.title.current", { revision: props.workflow.revision }) : t("integration.history.preview.title.revision", { revision: selected.value }));
278
264
  onMounted(() => {
279
265
  void loadList(true);
280
266
  void select(null);
@@ -296,7 +282,6 @@ onMounted(() => {
296
282
  :nodes-connectable="false"
297
283
  :elements-selectable="false"
298
284
  :edges-updatable="false"
299
- :pan-on-scroll-mode="PanOnScrollMode.Free"
300
285
  :delete-key-code="null"
301
286
  :min-zoom="0.25"
302
287
  :max-zoom="2"
@@ -327,9 +312,9 @@ onMounted(() => {
327
312
  <!-- Revision list -->
328
313
  <aside class="flex w-80 shrink-0 flex-col border-l border-black/[0.06] bg-white">
329
314
  <div class="border-b border-black/[0.06] px-3 py-2">
330
- <div class="text-sm font-semibold">Version history</div>
315
+ <div class="text-sm font-semibold">{{ t("integration.history.heading.title") }}</div>
331
316
  <p class="text-sm text-muted-foreground">
332
- Every save archives the previous revision; the newest one is what runs while the workflow is active.
317
+ {{ t("integration.history.heading.description") }}
333
318
  </p>
334
319
  </div>
335
320
 
@@ -343,17 +328,17 @@ onMounted(() => {
343
328
  >
344
329
  <div class="flex items-center justify-between gap-2">
345
330
  <span class="text-sm font-medium tabular-nums">{{ formatDateTime(workflow.updated_at) }}</span>
346
- <Badge variant="outline" class="text-xs font-medium uppercase border-emerald-500/30 text-emerald-700">Current</Badge>
331
+ <Badge variant="outline" class="text-xs font-medium uppercase border-emerald-500/30 text-emerald-700">{{ t("integration.history.badge.current") }}</Badge>
347
332
  <Badge
348
333
  variant="outline"
349
334
  class="text-xs font-medium uppercase"
350
335
  :class="workflow.active ? 'border-emerald-500/30 bg-emerald-500/10 text-emerald-700' : 'border-black/[0.08] text-muted-foreground'"
351
- :title="workflow.active ? 'Triggers are armed; this revision is what runs.' : 'Workflow is paused; activating runs this revision.'"
336
+ :title="workflow.active ? t('integration.history.badge.activeHint') : t('integration.history.badge.pausedHint')"
352
337
  >
353
- {{ workflow.active ? "Active" : "Paused" }}
338
+ {{ workflow.active ? t("integration.history.badge.active") : t("integration.history.badge.paused") }}
354
339
  </Badge>
355
340
  </div>
356
- <div class="mt-0.5 text-sm text-muted-foreground">Revision {{ workflow.revision }}</div>
341
+ <div class="mt-0.5 text-sm text-muted-foreground">{{ t("integration.history.list.revisionLabel", { revision: workflow.revision }) }}</div>
357
342
  </button>
358
343
 
359
344
  <!-- Archived revisions -->
@@ -367,7 +352,7 @@ onMounted(() => {
367
352
  <div class="flex items-center justify-between gap-2">
368
353
  <span class="text-sm font-medium tabular-nums">{{ formatDateTime(rev.archived_at) }}</span>
369
354
  </div>
370
- <div class="mt-0.5 text-sm text-muted-foreground">Revision {{ rev.revision }}</div>
355
+ <div class="mt-0.5 text-sm text-muted-foreground">{{ t("integration.history.list.revisionLabel", { revision: rev.revision }) }}</div>
371
356
  </button>
372
357
  <div class="absolute right-2 top-1/2 -translate-y-1/2 opacity-0 transition group-hover:opacity-100">
373
358
  <Button
@@ -378,13 +363,13 @@ onMounted(() => {
378
363
  @click.stop="restoreRevision(rev)"
379
364
  >
380
365
  <Loader2 v-if="restoring === rev.revision" :size="11" class="mr-1 animate-spin" />
381
- Restore
366
+ {{ t("integration.history.action.restore") }}
382
367
  </Button>
383
368
  </div>
384
369
  </div>
385
370
 
386
371
  <p v-if="!entries.length && !listLoading" class="px-2 py-6 text-center text-sm text-muted-foreground">
387
- No archived revisions yet — save a change to create the first snapshot.
372
+ {{ t("integration.history.state.empty") }}
388
373
  </p>
389
374
  <p v-if="revisionsApi.error.value" class="px-2 py-2 text-sm text-rose-600">
390
375
  {{ revisionsApi.error.value }}
@@ -409,7 +394,7 @@ onMounted(() => {
409
394
  class="w-full text-sm"
410
395
  @click="loadMore"
411
396
  >
412
- Load older revisions
397
+ {{ t("integration.history.action.loadMore") }}
413
398
  </Button>
414
399
  </div>
415
400
  </aside>
@@ -1,5 +1,23 @@
1
1
  import type { WorkflowIcon } from '../../types/integrations.js';
2
- type __VLS_PublicProps = {
2
+ /**
3
+ * The caller names the picker by pointing at its own `<Label>`'s id — not with
4
+ * `<Label for>`, and not with an `id` of ours on the trigger.
5
+ *
6
+ * An `id` on the trigger would be a quiet regression: `PopoverTrigger` puts
7
+ * reka-ui's generated `triggerId` there, `PopoverContentImpl` points the opened
8
+ * dialog's `aria-labelledby` at exactly that id, and reka's `Slot` merges with
9
+ * the *child's* props winning — so ours would replace it and leave the popover
10
+ * naming an element that no longer exists.
11
+ *
12
+ * `aria-labelledby` also lists the id of the span holding the current choice, so
13
+ * the name stays "Icon Business Central" rather than dropping the value the way
14
+ * a plain label would. Attribute fallthrough is no help here either: the root
15
+ * this component renders is the popover, not the button.
16
+ */
17
+ type __VLS_Props = {
18
+ triggerLabelledby?: string;
19
+ };
20
+ type __VLS_PublicProps = __VLS_Props & {
3
21
  modelValue?: WorkflowIcon | null;
4
22
  };
5
23
  declare const _default: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
@@ -1,10 +1,18 @@
1
1
  <script setup>
2
- import { computed, ref } from "vue";
2
+ import { computed, ref, useId } from "vue";
3
3
  import { AltArrowDown as ChevronDown } from "#solar-icons";
4
4
  import { Popover, PopoverContent, PopoverTrigger } from "@revenexx/studio";
5
5
  import { WORKFLOW_SOLAR_ICONS, imageIdFromSrc, nodeLogoOptions } from "../../data/workflowIconCatalog";
6
6
  import WorkflowIconView from "./WorkflowIconView.vue";
7
7
  const model = defineModel({ type: [Object, null], ...{ default: null } });
8
+ const props = defineProps({
9
+ triggerLabelledby: { type: String, required: false }
10
+ });
11
+ const t = useIntegrationsT();
12
+ const valueId = useId();
13
+ const triggerLabelledby = computed(
14
+ () => props.triggerLabelledby ? `${props.triggerLabelledby} ${valueId}` : void 0
15
+ );
8
16
  const open = ref(false);
9
17
  const logos = computed(() => nodeLogoOptions());
10
18
  function pick(icon) {
@@ -19,10 +27,10 @@ function isImage(id) {
19
27
  return (imageIdFromSrc(model.value.src) ?? model.value.src) === id;
20
28
  }
21
29
  const selectedLabel = computed(() => {
22
- if (!model.value) return "Default icon";
23
- if (model.value.kind === "solar") return model.value.name ?? "Icon";
30
+ if (!model.value) return t("integration.workflows.iconPicker.label.default");
31
+ if (model.value.kind === "solar") return model.value.name ?? t("integration.workflows.iconPicker.label.iconFallback");
24
32
  const id = imageIdFromSrc(model.value.src) ?? model.value.src;
25
- return logos.value.find((l) => l.id === id)?.label ?? "Image";
33
+ return logos.value.find((l) => l.id === id)?.label ?? t("integration.workflows.iconPicker.label.imageFallback");
26
34
  });
27
35
  </script>
28
36
 
@@ -30,13 +38,14 @@ const selectedLabel = computed(() => {
30
38
  <Popover v-model:open="open">
31
39
  <PopoverTrigger as-child>
32
40
  <button
41
+ :aria-labelledby="triggerLabelledby"
33
42
  type="button"
34
43
  class="flex h-9 w-full items-center gap-2 rounded-md border border-black/[0.08] bg-white px-2.5 text-left text-sm transition hover:border-black/20"
35
44
  >
36
45
  <span class="flex size-6 shrink-0 items-center justify-center rounded-md bg-slate-100 text-slate-500">
37
46
  <WorkflowIconView :icon="model" :size="14" />
38
47
  </span>
39
- <span class="min-w-0 flex-1 truncate" :class="model ? '' : 'text-muted-foreground'">
48
+ <span :id="valueId" class="min-w-0 flex-1 truncate" :class="model ? '' : 'text-muted-foreground'">
40
49
  {{ selectedLabel }}
41
50
  </span>
42
51
  <ChevronDown :size="14" class="shrink-0 text-muted-foreground" />
@@ -49,7 +58,8 @@ const selectedLabel = computed(() => {
49
58
  type="button"
50
59
  class="flex size-8 items-center justify-center rounded-md border text-sm text-muted-foreground transition hover:bg-muted/50"
51
60
  :class="!model ? 'border-emerald-500/40 bg-emerald-500/10' : 'border-transparent'"
52
- title="No icon (default glyph)"
61
+ :title="t('integration.workflows.iconPicker.action.none')"
62
+ :aria-label="t('integration.workflows.iconPicker.action.none')"
53
63
  @click="pick(null)"
54
64
  >
55
65
  —
@@ -68,7 +78,7 @@ const selectedLabel = computed(() => {
68
78
  </div>
69
79
 
70
80
  <template v-if="logos.length">
71
- <div class="text-mono text-muted-foreground">Integrations</div>
81
+ <div class="text-mono text-muted-foreground">{{ t("integration.workflows.iconPicker.section.integrations") }}</div>
72
82
  <div class="flex flex-wrap gap-1">
73
83
  <button
74
84
  v-for="l in logos"
@@ -1,5 +1,23 @@
1
1
  import type { WorkflowIcon } from '../../types/integrations.js';
2
- type __VLS_PublicProps = {
2
+ /**
3
+ * The caller names the picker by pointing at its own `<Label>`'s id — not with
4
+ * `<Label for>`, and not with an `id` of ours on the trigger.
5
+ *
6
+ * An `id` on the trigger would be a quiet regression: `PopoverTrigger` puts
7
+ * reka-ui's generated `triggerId` there, `PopoverContentImpl` points the opened
8
+ * dialog's `aria-labelledby` at exactly that id, and reka's `Slot` merges with
9
+ * the *child's* props winning — so ours would replace it and leave the popover
10
+ * naming an element that no longer exists.
11
+ *
12
+ * `aria-labelledby` also lists the id of the span holding the current choice, so
13
+ * the name stays "Icon Business Central" rather than dropping the value the way
14
+ * a plain label would. Attribute fallthrough is no help here either: the root
15
+ * this component renders is the popover, not the button.
16
+ */
17
+ type __VLS_Props = {
18
+ triggerLabelledby?: string;
19
+ };
20
+ type __VLS_PublicProps = __VLS_Props & {
3
21
  modelValue?: WorkflowIcon | null;
4
22
  };
5
23
  declare const _default: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
@@ -5,6 +5,10 @@ interface NodeData extends WorkflowNode {
5
5
  * editor without persisting to the blob. When present and non-empty, these
6
6
  * drive the rendered handles instead of the static manifest computation. */
7
7
  __resolvedOutputs?: NodeManifestOutput[];
8
+ /** Config keys the backend reported as incomplete at the last save (PO-209),
9
+ * injected by the editor the same non-persisting way. Read-only canvases
10
+ * (run replay, history preview) never pass them, so nothing is marked there. */
11
+ __invalidFields?: string[];
8
12
  }
9
13
  type __VLS_Props = {
10
14
  id: string;