@revenexx/studio-integrations 1.1.0 → 1.1.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,42 @@
1
1
  # @revenexx/studio-integrations
2
2
 
3
+ ## 1.1.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 77ea454: A workflow's Runs tab no longer puts the operator back on the newest page while
8
+ they are paging. The tab's own refresh used to consult the page that last arrived
9
+ rather than the page that was asked for, so a refresh falling due between "show me
10
+ the older runs" and those runs arriving won the race and undid the click. It now
11
+ leaves a pending page alone, and a refresh that was already on its way no longer
12
+ draws over the page the operator asked for.
13
+ - ddb95eb: The `{ }` button beside a node setting is no longer announced as its own glyph.
14
+ It now carries a name saying what it does and what it opens ("Insert expression —
15
+ available data for this setting", and the German equivalent), reports whether the
16
+ list of available data is open, and points at that list while it is there.
17
+
18
+ It can also be operated from the keyboard at all now: it acted on a mouse press
19
+ alone, so Enter and Space did nothing, and Escape while it had focus closed the whole
20
+ node configuration dialog instead of the list. Nothing changes for a pointer.
21
+
22
+ - ec8f025: A run row no longer offers actions the platform will refuse. A run that has been
23
+ accepted but not started yet has no execution to act on, and the API answers a
24
+ cancel, a terminate or a resume for it with a 409 — so the row now withholds all
25
+ three, the way the run's own record already does, instead of turning the click into
26
+ an error toast.
27
+
28
+ Two German words on the same menu are fixed with it: the row's "Retry" and the
29
+ record's were shipped untranslated, and the entry that opens a run's replay said
30
+ "Wiederholen", which reads as an offer to run it again. They now read "Erneut
31
+ ausführen" and "Wiedergabe" — the word the replay surface itself already uses.
32
+
33
+ - 0de6228: A run's outcome is no longer cut off or drawn over the workflow name beside it. The
34
+ Status column in both lists of runs — the Runs page and the dashboard's latest runs —
35
+ was a fixed width measured against the English words, so the German
36
+ "fehlgeschlagen" ran out of its cell; the dashboard's own column was narrower still,
37
+ and even "erfolgreich" overflowed it. Both now share one track that keeps a floor and
38
+ grows to the widest word on the page, in whatever language the studio is read.
39
+
3
40
  ## 1.1.0
4
41
 
5
42
  ### Minor Changes
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@revenexx/studio-integrations",
3
3
  "configKey": "studioIntegrations",
4
- "version": "1.1.0",
4
+ "version": "1.1.1",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "unknown"
@@ -1,5 +1,5 @@
1
1
  <script setup>
2
- import { computed, inject, onBeforeUnmount, onMounted, ref, watch } from "vue";
2
+ import { computed, inject, onBeforeUnmount, onMounted, ref, useId, watch } from "vue";
3
3
  import { Editor, EditorContent } from "@tiptap/vue-3";
4
4
  import { Node } from "@tiptap/core";
5
5
  import Document from "@tiptap/extension-document";
@@ -158,10 +158,16 @@ const filteredSections = computed(() => {
158
158
  });
159
159
  const open = ref(false);
160
160
  const wrapRef = ref(null);
161
+ const triggerRef = ref(null);
162
+ const listOpen = computed(() => open.value && hasSuggestions.value);
163
+ const pickerId = useId();
161
164
  function toggle() {
162
165
  if (!open.value && !hasSuggestions.value) return;
163
166
  open.value = !open.value;
164
167
  }
168
+ function onTriggerClick(e) {
169
+ if (e.detail === 0) toggle();
170
+ }
165
171
  function onFocusOut(e) {
166
172
  if (wrapRef.value && e.relatedTarget instanceof globalThis.Node && wrapRef.value.contains(e.relatedTarget)) return;
167
173
  open.value = false;
@@ -173,6 +179,13 @@ function onKeydown(e) {
173
179
  open.value = false;
174
180
  }
175
181
  }
182
+ function onPickerKeydown(e) {
183
+ if (e.key !== "Escape" || !open.value) return;
184
+ e.stopPropagation();
185
+ e.preventDefault();
186
+ open.value = false;
187
+ triggerRef.value?.focus();
188
+ }
176
189
  function onDocMousedown(e) {
177
190
  if (wrapRef.value && !wrapRef.value.contains(e.target)) open.value = false;
178
191
  }
@@ -188,7 +201,11 @@ function insert(expr) {
188
201
  </script>
189
202
 
190
203
  <template>
191
- <div ref="wrapRef" class="relative" @focusout="onFocusOut">
204
+ <!-- `keydown` on the widget rather than on the editor alone: with focus on the
205
+ trigger, Escape used to reach the configuration lightbox and close the whole
206
+ dialog. The editor keeps its own handler, which stops the event before it
207
+ arrives here. -->
208
+ <div ref="wrapRef" class="relative" @focusout="onFocusOut" @keydown="onKeydown">
192
209
  <div class="flex items-center gap-1">
193
210
  <div
194
211
  class="expr-editor relative h-8 min-w-0 flex-1 overflow-hidden rounded-md border bg-white px-2 text-sm transition focus-within:outline-2 focus-within:-outline-offset-1 focus-within:outline-ring"
@@ -200,12 +217,22 @@ function insert(expr) {
200
217
  >{{ placeholder }}</span>
201
218
  <EditorContent :editor="editor" class="h-full" @keydown="onKeydown" />
202
219
  </div>
220
+ <!-- The name says what the button does AND what it opens, because the glyph
221
+ says neither; `aria-expanded` says whether the list is there, and
222
+ `aria-controls` only while it is — an id pointing at nothing is worse
223
+ than no link. See the docblock on `listOpen` for why this is not the
224
+ shared popover. -->
203
225
  <button
226
+ ref="triggerRef"
204
227
  type="button"
205
228
  :title="t('integration.nodeConfig.expression.insertTitle')"
229
+ :aria-label="t('integration.nodeConfig.expression.insertAria')"
230
+ :aria-expanded="listOpen"
231
+ :aria-controls="listOpen ? pickerId : void 0"
206
232
  class="flex h-8 shrink-0 items-center rounded-md border border-black/[0.08] bg-white px-1.5 font-mono text-sm text-muted-foreground transition hover:text-foreground hover:bg-muted/30"
207
- :class="open ? 'ring-2 ring-emerald-500/40' : ''"
233
+ :class="listOpen ? 'ring-2 ring-emerald-500/40' : ''"
208
234
  @mousedown.prevent="toggle"
235
+ @click="onTriggerClick"
209
236
  >
210
237
  {{ triggerLabel }}
211
238
  </button>
@@ -224,8 +251,10 @@ function insert(expr) {
224
251
  here and in the panel beside the form — and a popup nothing can address
225
252
  is a popup nothing can test either. -->
226
253
  <div
227
- v-if="open && hasSuggestions"
254
+ v-if="listOpen"
255
+ :id="pickerId"
228
256
  role="group"
257
+ @keydown="onPickerKeydown"
229
258
  :aria-label="t('integration.nodeConfig.expression.picker.aria')"
230
259
  class="absolute right-0 top-full z-50 mt-1 flex max-h-72 w-80 flex-col rounded-md border border-black/[0.08] bg-white text-sm shadow-lg"
231
260
  >
@@ -23,9 +23,10 @@ function viewDetails() {
23
23
  function viewReplay() {
24
24
  void router.push({ path: `/integrations/workflows/${props.run.workflowId}`, query: { run: props.run.id, view: "replay" } });
25
25
  }
26
- const isActive = computed(() => props.run.status === "running" || props.run.status === "queued");
26
+ const hasExecution = computed(() => !!props.run.temporalRunId);
27
+ const isActive = computed(() => (props.run.status === "running" || props.run.status === "queued") && hasExecution.value);
27
28
  const isTerminal = computed(() => ["succeeded", "failed", "cancelled"].includes(props.run.status));
28
- const canResume = computed(() => props.run.status === "failed");
29
+ const canResume = computed(() => props.run.status === "failed" && hasExecution.value);
29
30
  const hasAny = computed(() => isActive.value || isTerminal.value);
30
31
  const busy = ref(false);
31
32
  function actionLabel(a) {
@@ -1,7 +1,7 @@
1
1
  <script setup>
2
2
  import { computed, onMounted, onUnmounted, ref } from "vue";
3
3
  import { Badge } from "@revenexx/studio";
4
- import { RUN_STATUS_WORDS, normalisedRunStatus, runStatusClass, runStatusLabel } from "../../utils/statusStyle";
4
+ import { RUN_STATUS_COLUMN_WIDTH, RUN_STATUS_WORDS, normalisedRunStatus, runStatusClass, runStatusLabel } from "../../utils/statusStyle";
5
5
  import { TRIGGER_TYPE_WORDS, triggerTypeLabel } from "../../data/integrationNodeCatalog";
6
6
  const props = defineProps({
7
7
  runs: { type: Array, required: true },
@@ -70,7 +70,11 @@ const columns = computed(() => [
70
70
  // Sorted and searched by the word on screen, not by the raw field: a column
71
71
  // with no `searchGetter` is searched through its `sortGetter`, so a German
72
72
  // reader typing the status they can see has to find the row that shows it.
73
- { key: "status", label: t("integration.runs.table.column.status"), width: "100px", sortable: true, sortGetter: (r) => runStatusLabel(r.status) },
73
+ // One floor for both lists of runs, this one and the dashboard's, rather than a
74
+ // number guessed twice — see `RUN_STATUS_COLUMN_WIDTH` for how it was chosen and
75
+ // why the track cannot be elastic. At the 100px it used to be, the German
76
+ // "fehlgeschlagen" ran out of its cell and under the workflow name (PO-362).
77
+ { key: "status", label: t("integration.runs.table.column.status"), width: RUN_STATUS_COLUMN_WIDTH, sortable: true, sortGetter: (r) => runStatusLabel(r.status) },
74
78
  ...props.showWorkflow ? [{ key: "workflow", label: t("integration.runs.table.column.workflow"), sortable: true, sortGetter: (r) => r.workflowName.toLowerCase(), searchGetter: (r) => r.workflowName }] : [],
75
79
  // Fixed floor wide enough for a full UUID, flexible above it — header and
76
80
  // body rows are separate grids, so a content-sized track (max-content)
@@ -99,7 +103,15 @@ const columns = computed(() => [
99
103
  @row-click="(r) => openRun(r)"
100
104
  >
101
105
  <template #cell-status="{ row: run }">
102
- <Badge variant="outline" :class="['border text-xs font-medium uppercase', runStatusClass(run.status)]">
106
+ <!-- `max-w-full` is the net, not the fix: a word wider than the column above is
107
+ then clipped inside its own cell instead of being drawn over the one beside
108
+ it, and the `title` keeps it readable. A hard clip, not an ellipsis — the
109
+ badge is an inline-flex box, where `text-overflow` does not apply. -->
110
+ <Badge
111
+ variant="outline"
112
+ :title="runStatusLabel(run.status)"
113
+ :class="['border text-xs font-medium uppercase max-w-full', runStatusClass(run.status)]"
114
+ >
103
115
  {{ runStatusLabel(run.status) }}
104
116
  </Badge>
105
117
  </template>
@@ -520,6 +520,7 @@
520
520
  "integration.nodeConfig.dynamicSchema.resolving": "Felder werden geladen…",
521
521
  "integration.nodeConfig.expression.badge.title": "Akzeptiert ${{ … }}-Ausdrücke",
522
522
  "integration.nodeConfig.expression.insertTitle": "Ausdruck einfügen",
523
+ "integration.nodeConfig.expression.insertAria": "Ausdruck einfügen — verfügbare Daten für diese Einstellung",
523
524
  "integration.nodeConfig.expression.picker.aria": "Verfügbare Daten für diese Einstellung",
524
525
  "integration.nodeConfig.expression.picker.empty": "Nichts entspricht „{query}“.",
525
526
  "integration.nodeConfig.expression.picker.filterAria": "Verfügbare Daten filtern",
@@ -829,7 +830,7 @@
829
830
  "integration.runs.action.back": "Zurück",
830
831
  "integration.runs.action.cancel": "Run stoppen",
831
832
  "integration.runs.action.resume": "Fortsetzen",
832
- "integration.runs.action.retry": "Retry",
833
+ "integration.runs.action.retry": "Erneut ausführen",
833
834
  "integration.runs.action.terminate": "Terminieren",
834
835
  "integration.runs.confirm.cancel": "Diesen Run stoppen?",
835
836
  "integration.runs.confirm.resume": "Diesen Run fortsetzen?",
@@ -889,9 +890,9 @@
889
890
  "integration.runs.list.header.title": "Runs",
890
891
  "integration.runs.menu.aria.actions": "Aktionen für Run #{id}",
891
892
  "integration.runs.menu.cancel": "Run stoppen",
892
- "integration.runs.menu.replay": "Wiederholen",
893
+ "integration.runs.menu.replay": "Wiedergabe",
893
894
  "integration.runs.menu.resume": "Fortsetzen",
894
- "integration.runs.menu.retry": "Retry",
895
+ "integration.runs.menu.retry": "Erneut ausführen",
895
896
  "integration.runs.menu.terminate": "Terminieren",
896
897
  "integration.runs.menu.viewDetails": "Details anzeigen",
897
898
  "integration.runs.notice.requested": "{action} angefordert.",
@@ -520,6 +520,7 @@
520
520
  "integration.nodeConfig.dynamicSchema.resolving": "Resolving fields…",
521
521
  "integration.nodeConfig.expression.badge.title": "Accepts ${{ … }} expressions",
522
522
  "integration.nodeConfig.expression.insertTitle": "Insert expression",
523
+ "integration.nodeConfig.expression.insertAria": "Insert expression — available data for this setting",
523
524
  "integration.nodeConfig.expression.picker.aria": "Available data for this setting",
524
525
  "integration.nodeConfig.expression.picker.empty": "Nothing matches “{query}”.",
525
526
  "integration.nodeConfig.expression.picker.filterAria": "Filter available data",
@@ -6,7 +6,8 @@ import WorkflowCreateDialog from "../../components/Integrations/WorkflowCreateDi
6
6
  import { Pulse as Activity, DangerTriangle as AlertTriangle, ArrowRight, Box as Boxes, ClockCircle as Clock, Database, Layers, AddCircle as Plus, Radio, MagicStick as Sparkles, LinkRound as Webhook, Programming as Workflow } from "#solar-icons";
7
7
  import { Badge, Button } from "@revenexx/studio";
8
8
  import { servedPaths } from "../../utils/servedPaths";
9
- import { runStatusClass, runStatusLabel } from "../../utils/statusStyle";
9
+ import { RUN_STATUS_COLUMN_WIDTH, runStatusClass, runStatusLabel } from "../../utils/statusStyle";
10
+ const LATEST_RUN_COLUMNS = `${RUN_STATUS_COLUMN_WIDTH} minmax(0,1fr) 100px 70px`;
10
11
  const t = useIntegrationsT();
11
12
  useHead({ title: () => t("integration.dashboard.head.title") });
12
13
  const { workflows, activeCount: activeWorkflows, loading: workflowsLoading, load: loadWorkflows } = useIntegrationsWorkflows();
@@ -177,9 +178,18 @@ function surfaceCards() {
177
178
  v-for="r in latestPerWorkflow"
178
179
  :key="r.id"
179
180
  :to="{ path: `/integrations/workflows/${r.workflowId}`, query: { run: r.id } }"
180
- class="grid grid-cols-[90px_minmax(0,1fr)_100px_70px] items-center gap-2 border-b border-black/[0.04] px-3 py-2 text-sm last:border-b-0 hover:bg-muted/30"
181
+ class="grid items-center gap-2 border-b border-black/[0.04] px-3 py-2 text-sm last:border-b-0 hover:bg-muted/30"
182
+ :style="{ gridTemplateColumns: LATEST_RUN_COLUMNS }"
181
183
  >
182
- <Badge variant="outline" :class="['border text-xs font-medium uppercase', runStatusClass(r.status)]">
184
+ <!-- The runs list's own track, not a second guess: this panel was the
185
+ narrower of the two at 90px, so three German words ran out of it,
186
+ "erfolgreich" included (PO-362). `max-w-full` is the net behind it,
187
+ and it clips rather than ellipsing — see the runs list. -->
188
+ <Badge
189
+ variant="outline"
190
+ :title="runStatusLabel(r.status)"
191
+ :class="['border text-xs font-medium uppercase max-w-full', runStatusClass(r.status)]"
192
+ >
183
193
  {{ runStatusLabel(r.status) }}
184
194
  </Badge>
185
195
  <div class="truncate">
@@ -197,7 +207,8 @@ function surfaceCards() {
197
207
  <div
198
208
  v-for="n in 6"
199
209
  :key="`sk-run-${n}`"
200
- class="grid grid-cols-[90px_minmax(0,1fr)_100px_70px] items-center gap-2 border-b border-black/[0.04] px-3 py-2.5 last:border-b-0"
210
+ class="grid items-center gap-2 border-b border-black/[0.04] px-3 py-2.5 last:border-b-0"
211
+ :style="{ gridTemplateColumns: LATEST_RUN_COLUMNS }"
201
212
  >
202
213
  <div class="h-5 w-16 rounded bg-black/[0.07] animate-pulse" />
203
214
  <div class="space-y-1.5">
@@ -397,6 +397,9 @@ const runsError = ref(null);
397
397
  const runsPage = ref(1);
398
398
  const runsTotal = ref(0);
399
399
  const runsLastPage = ref(1);
400
+ const runsWanted = ref(1);
401
+ let runsSeq = 0;
402
+ let runsLoadingSeq = 0;
400
403
  const RUNS_POLL_MS = 3e3;
401
404
  const { delivering: realtimeDelivering, runTick, lastRunEvent, buildTick, lastBuildEvent } = useIntegrationsRealtime();
402
405
  const runsBudget = createPollBudget({ intervalMs: RUNS_POLL_MS, liveIntervalMs: SAFETY_POLL_MS });
@@ -410,12 +413,14 @@ function stopRunsPoll() {
410
413
  function startRunsPoll() {
411
414
  stopRunsPoll();
412
415
  runsPoll = setInterval(() => {
413
- if (runsPage.value === 1 && runsBudget.due(realtimeDelivering.value)) void loadRuns(1, { silent: true });
416
+ if (!runsLoading.value && runsWanted.value === 1 && runsBudget.due(realtimeDelivering.value)) {
417
+ void loadRuns(1, { silent: true });
418
+ }
414
419
  }, RUNS_POLL_MS);
415
420
  }
416
421
  onUnmounted(stopRunsPoll);
417
422
  watch(runTick, () => {
418
- if (tab.value !== "runs" || runsPage.value !== 1) return;
423
+ if (tab.value !== "runs" || runsWanted.value !== 1 || runsLoading.value) return;
419
424
  const eventWorkflow = workflowIdOf(lastRunEvent.value);
420
425
  if (eventWorkflow && eventWorkflow !== String(workflow.value?.id ?? "")) return;
421
426
  void loadRuns(1, { silent: true });
@@ -426,10 +431,15 @@ watch(buildTick, () => {
426
431
  if (eventWorkflow && eventWorkflow !== String(workflow.value.id)) return;
427
432
  void get(workflow.value.id);
428
433
  });
429
- async function loadRuns(page = runsPage.value, opts = {}) {
434
+ async function loadRuns(page = runsWanted.value, opts = {}) {
430
435
  if (!workflow.value) return;
436
+ runsWanted.value = page;
437
+ const seq = ++runsSeq;
431
438
  runsBudget.spend();
432
- if (!opts.silent) runsLoading.value = true;
439
+ if (!opts.silent) {
440
+ runsLoading.value = true;
441
+ runsLoadingSeq = seq;
442
+ }
433
443
  runsError.value = null;
434
444
  try {
435
445
  const res = await loadForWorkflow(workflow.value.id, workflow.value.name, {
@@ -437,19 +447,23 @@ async function loadRuns(page = runsPage.value, opts = {}) {
437
447
  page,
438
448
  perPage: RUNS_PER_PAGE
439
449
  });
450
+ if (seq !== runsSeq) return;
440
451
  wfRuns.value = res.runs;
441
452
  runsTotal.value = res.total;
442
453
  runsLastPage.value = res.lastPage;
443
454
  runsPage.value = res.page;
455
+ runsWanted.value = res.page;
444
456
  } catch (e) {
457
+ if (seq !== runsSeq) return;
445
458
  runsError.value = e.message || t("integration.editor.runs.error.load");
459
+ runsWanted.value = runsPage.value;
446
460
  } finally {
447
- if (!opts.silent) runsLoading.value = false;
461
+ if (!opts.silent && seq === runsLoadingSeq) runsLoading.value = false;
448
462
  }
449
463
  }
450
464
  function goRunsPage(p) {
451
465
  const next = Math.min(Math.max(1, p), runsLastPage.value);
452
- if (next !== runsPage.value) void loadRuns(next);
466
+ if (next !== runsWanted.value) void loadRuns(next);
453
467
  }
454
468
  watch(tab, (t2) => {
455
469
  if (t2 === "runs") {
@@ -912,10 +926,10 @@ function formatDateTime(iso) {
912
926
  <div v-if="runsLastPage > 1 || runsTotal > wfRuns.length" class="flex items-center justify-between text-sm text-muted-foreground">
913
927
  <span class="tabular-nums">{{ t("integration.editor.runs.pagination.page", { page: runsPage, lastPage: runsLastPage }) }} · {{ t("integration.editor.runs.pagination.count", runsTotal) }}</span>
914
928
  <div class="flex gap-2">
915
- <Button variant="outline" size="sm" class="h-7 text-sm" :disabled="runsPage <= 1 || runsLoading" @click="goRunsPage(runsPage - 1)">
929
+ <Button variant="outline" size="sm" class="h-7 text-sm" :disabled="runsWanted <= 1 || runsLoading" @click="goRunsPage(runsWanted - 1)">
916
930
  {{ t("integration.editor.runs.pagination.newer") }}
917
931
  </Button>
918
- <Button variant="outline" size="sm" class="h-7 text-sm" :disabled="runsPage >= runsLastPage || runsLoading" @click="goRunsPage(runsPage + 1)">
932
+ <Button variant="outline" size="sm" class="h-7 text-sm" :disabled="runsWanted >= runsLastPage || runsLoading" @click="goRunsPage(runsWanted + 1)">
919
933
  {{ t("integration.editor.runs.pagination.older") }}
920
934
  </Button>
921
935
  </div>
@@ -23,6 +23,30 @@ export declare function toneClass(tone: StatusTone): string;
23
23
  * order. It used to keep its own copy of the list, so a sixth word added here
24
24
  * would have been labelled everywhere and silently missing from that menu. */
25
25
  export declare const RUN_STATUS_WORDS: readonly ["running", "succeeded", "failed", "cancelled", "queued"];
26
+ /**
27
+ * How wide a list draws its Status column, as a grid track.
28
+ *
29
+ * A fixed width, and it has to be one: the shared table gives the header row, every
30
+ * body row and every skeleton row a grid of their own, so a content-sized track
31
+ * (`max-content`, `auto`, `fit-content`) is resolved per row and a row holding a
32
+ * longer word would step out of line with the header and with the rows around it.
33
+ * The `id` column below says the same thing, and this constant was `minmax(136px,
34
+ * max-content)` for one commit because that warning was read too late.
35
+ *
36
+ * So the number is the promise, and it is a floor rather than a measurement. The
37
+ * column stood at 100px, which fitted every English word with room to spare and let
38
+ * the German "fehlgeschlagen" — 126px as a rendered badge on the machine this was
39
+ * measured on — run out of its cell and under the workflow name beside it (PO-362).
40
+ * 160px is that word plus a quarter, because the rendered width is not ours to know:
41
+ * a font that failed to load, a browser zoom or a reader's own minimum font size all
42
+ * move it, and one of those is why a first attempt at 136 was still reported cut.
43
+ *
44
+ * What happens when it is still not enough, and why that is survivable: the badge
45
+ * carries `max-w-full`, so it is clipped inside its own cell rather than drawn over
46
+ * the column beside it, and the whole word stays readable in its `title`. Raising
47
+ * this number is the fix at that point — not making the track elastic.
48
+ */
49
+ export declare const RUN_STATUS_COLUMN_WIDTH = "160px";
26
50
  /**
27
51
  * Raw API/Temporal run vocabulary → the cockpit's run-status labels, so a run
28
52
  * never reads "completed" in one surface and "succeeded" in another. This IS
@@ -77,6 +77,7 @@ export function toneClass(tone) {
77
77
  return TONE_CLASS[tone];
78
78
  }
79
79
  export const RUN_STATUS_WORDS = ["running", "succeeded", "failed", "cancelled", "queued"];
80
+ export const RUN_STATUS_COLUMN_WIDTH = "160px";
80
81
  const RUN_STATUS_WORD_SET = new Set(RUN_STATUS_WORDS);
81
82
  export function normalisedRunStatus(status) {
82
83
  if (!status) return "";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@revenexx/studio-integrations",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "type": "module",
5
5
  "description": "Integration Studio (core: workflows, node editor, runs, schedules, credentials, secrets, audit) as a standalone, publishable Nuxt module.",
6
6
  "repository": {