@cat-factory/app 0.78.0 → 0.79.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.
@@ -0,0 +1,62 @@
1
+ <script setup lang="ts">
2
+ // Shared renderer for a gate's failing-check list (`gateFailingCheckSchema[]`): each check
3
+ // links to its GitHub run when a URL is known, with its conclusion. Used by BOTH the CI
4
+ // gate's precheck panel and each per-attempt "handed to the fixer" list in GateResultView,
5
+ // so the link + conclusion-fallback logic lives in one place (they had drifted — the
6
+ // per-attempt copy silently dropped the GitHub link).
7
+ import type { GateFailingCheck } from '~/types/execution'
8
+
9
+ defineProps<{
10
+ checks: GateFailingCheck[]
11
+ // Compact layout for the per-attempt timeline; the fuller card layout is the default
12
+ // (the precheck panel).
13
+ dense?: boolean
14
+ }>()
15
+
16
+ const { t } = useI18n()
17
+ </script>
18
+
19
+ <template>
20
+ <ul :class="dense ? 'space-y-0.5' : 'space-y-1'">
21
+ <li
22
+ v-for="(c, i) in checks"
23
+ :key="`${c.name}-${i}`"
24
+ class="flex items-center"
25
+ :class="
26
+ dense ? 'gap-1.5' : 'gap-2 rounded-md border border-slate-800 bg-slate-950/40 px-3 py-1.5'
27
+ "
28
+ >
29
+ <UIcon
30
+ name="i-lucide-circle-x"
31
+ class="shrink-0 text-rose-400"
32
+ :class="dense ? 'h-3 w-3' : 'h-3.5 w-3.5'"
33
+ />
34
+ <a
35
+ v-if="c.url"
36
+ :href="c.url"
37
+ target="_blank"
38
+ rel="noopener"
39
+ class="group min-w-0 flex-1 truncate text-sky-300 hover:text-sky-200 hover:underline"
40
+ :class="dense ? 'text-[12px]' : 'text-[13px]'"
41
+ :title="t('gates.ci.openOnGithub', { name: c.name })"
42
+ >
43
+ {{ c.name }}
44
+ <UIcon
45
+ name="i-lucide-external-link"
46
+ class="ms-0.5 inline h-3 w-3 opacity-60 group-hover:opacity-100"
47
+ />
48
+ </a>
49
+ <span
50
+ v-else
51
+ class="min-w-0 flex-1 truncate"
52
+ :class="dense ? 'text-[12px] text-slate-300' : 'text-[13px] text-slate-200'"
53
+ >{{ c.name }}</span
54
+ >
55
+ <span
56
+ class="shrink-0 uppercase text-rose-300"
57
+ :class="dense ? 'text-[10px]' : 'text-[11px]'"
58
+ >{{ c.conclusion ?? t('gates.ci.conclusionFallback') }}</span
59
+ >
60
+ </li>
61
+ </ul>
62
+ </template>
@@ -10,10 +10,12 @@ import { agentKindMeta } from '~/utils/catalog'
10
10
  import type { GateAttempt, GateStepState } from '~/types/execution'
11
11
  import StepRestartControl from '~/components/panels/StepRestartControl.vue'
12
12
  import StepRunMeta from '~/components/panels/StepRunMeta.vue'
13
+ import AttemptEntryHeader from '~/components/panels/AttemptEntryHeader.vue'
14
+ import GateFailingCheckList from '~/components/gates/GateFailingCheckList.vue'
13
15
 
14
16
  const board = useBoardStore()
15
17
  const execution = useExecutionStore()
16
- const { t, d } = useI18n()
18
+ const { t } = useI18n()
17
19
 
18
20
  // Synchronous window: it reads its state straight off the execution step, so there's
19
21
  // nothing to fetch on open (no `onOpen` loader).
@@ -75,10 +77,6 @@ const OUTCOME_LABELS = computed<Record<GateAttempt['outcome'], string>>(() => ({
75
77
  failed: t('gates.outcome.failed'),
76
78
  }))
77
79
 
78
- function formatClock(ms?: number | null): string | null {
79
- return ms ? d(new Date(ms), 'long') : null
80
- }
81
-
82
80
  /**
83
81
  * The display status — a roll-up of the persisted gate state + the run's status, so the
84
82
  * window reads as a conclusion rather than raw fields:
@@ -308,35 +306,7 @@ const conflictVerdict = computed(() => {
308
306
  <h3 class="mb-2 text-[11px] font-semibold uppercase tracking-wide text-slate-500">
309
307
  {{ t('gates.ci.failingChecks') }}
310
308
  </h3>
311
- <ul v-if="failingChecks.length" class="space-y-1">
312
- <li
313
- v-for="(c, i) in failingChecks"
314
- :key="`${c.name}-${i}`"
315
- class="flex items-center gap-2 rounded-md border border-slate-800 bg-slate-950/40 px-3 py-1.5"
316
- >
317
- <UIcon name="i-lucide-circle-x" class="h-3.5 w-3.5 shrink-0 text-rose-400" />
318
- <a
319
- v-if="c.url"
320
- :href="c.url"
321
- target="_blank"
322
- rel="noopener"
323
- class="group min-w-0 flex-1 truncate text-[13px] text-sky-300 hover:text-sky-200 hover:underline"
324
- :title="t('gates.ci.openOnGithub', { name: c.name })"
325
- >
326
- {{ c.name }}
327
- <UIcon
328
- name="i-lucide-external-link"
329
- class="ms-0.5 inline h-3 w-3 opacity-60 group-hover:opacity-100"
330
- />
331
- </a>
332
- <span v-else class="min-w-0 flex-1 truncate text-[13px] text-slate-200">{{
333
- c.name
334
- }}</span>
335
- <span class="shrink-0 text-[11px] uppercase text-rose-300">
336
- {{ c.conclusion ?? t('gates.ci.conclusionFallback') }}
337
- </span>
338
- </li>
339
- </ul>
309
+ <GateFailingCheckList v-if="failingChecks.length" :checks="failingChecks" />
340
310
  <p v-else class="text-[13px] leading-relaxed text-slate-300">
341
311
  {{ gate.lastFailureSummary || t('gates.ci.failureFallback') }}
342
312
  </p>
@@ -389,26 +359,47 @@ const conflictVerdict = computed(() => {
389
359
  :key="a.attempt"
390
360
  class="rounded-md border border-slate-800 bg-slate-950/40 px-3 py-2"
391
361
  >
392
- <div class="flex items-center gap-2">
393
- <span class="text-[12px] font-semibold text-slate-200">{{
394
- t('gates.attempt', { number: a.attempt })
395
- }}</span>
396
- <UBadge
397
- :color="a.outcome === 'failed' ? 'error' : 'neutral'"
398
- variant="subtle"
399
- size="sm"
400
- >{{ OUTCOME_LABELS[a.outcome] }}</UBadge
362
+ <AttemptEntryHeader
363
+ :label="t('gates.attempt', { number: a.attempt })"
364
+ :outcome="a.outcome"
365
+ :outcome-label="OUTCOME_LABELS[a.outcome]"
366
+ :at="a.at"
367
+ date-format="long"
368
+ />
369
+ <!-- What this round was asked to fix: the instructions the gate handed the
370
+ helper (the failing-check summary / conflict reason / review comments),
371
+ plus the structured red checks for the CI gate. -->
372
+ <div
373
+ v-if="a.instructions || (a.failingChecks && a.failingChecks.length)"
374
+ class="mt-1.5"
375
+ >
376
+ <p class="text-[11px] text-slate-500">
377
+ {{ t('gates.attemptInstructions', { helper: helperMeta.label }) }}
378
+ </p>
379
+ <GateFailingCheckList
380
+ v-if="a.failingChecks && a.failingChecks.length"
381
+ class="mt-1"
382
+ :checks="a.failingChecks"
383
+ dense
384
+ />
385
+ <p
386
+ v-else-if="a.instructions"
387
+ class="mt-1 whitespace-pre-wrap text-[12px] leading-relaxed text-slate-300"
401
388
  >
402
- <span v-if="formatClock(a.at)" class="ms-auto text-[11px] text-slate-500">{{
403
- formatClock(a.at)
404
- }}</span>
389
+ {{ a.instructions }}
390
+ </p>
405
391
  </div>
406
- <p
407
- v-if="a.summary"
408
- class="mt-1 whitespace-pre-wrap text-[12px] leading-relaxed text-slate-400"
409
- >
410
- {{ a.summary }}
411
- </p>
392
+ <!-- The helper's own report of what it did / what remains. -->
393
+ <template v-if="a.summary">
394
+ <p class="mt-1.5 text-[11px] text-slate-500">
395
+ {{ t('gates.attemptReport', { helper: helperMeta.label }) }}
396
+ </p>
397
+ <p
398
+ class="mt-1 whitespace-pre-wrap text-[12px] leading-relaxed text-slate-400"
399
+ >
400
+ {{ a.summary }}
401
+ </p>
402
+ </template>
412
403
  </li>
413
404
  </ol>
414
405
  </section>
@@ -0,0 +1,36 @@
1
+ <script setup lang="ts">
2
+ // Shared header row for an attempt-timeline entry. The polling-gate helper attempts
3
+ // (GateResultView) and the Tester's fixer rounds (TestReportWindow) render the same chrome —
4
+ // a leading label, an outcome badge, and a timestamp — differing only in the optional icon,
5
+ // the resolved label strings, and the date format. The per-attempt body stays the caller's.
6
+ defineProps<{
7
+ label: string
8
+ outcome: 'completed' | 'failed'
9
+ outcomeLabel: string
10
+ at?: number | null
11
+ dateFormat?: 'short' | 'long'
12
+ icon?: string
13
+ iconClass?: string
14
+ }>()
15
+
16
+ const { d } = useI18n()
17
+
18
+ function formatClock(ms: number | null | undefined, fmt: 'short' | 'long'): string | null {
19
+ return ms ? d(new Date(ms), fmt) : null
20
+ }
21
+ </script>
22
+
23
+ <template>
24
+ <div class="flex items-center gap-2">
25
+ <UIcon v-if="icon" :name="icon" class="h-3.5 w-3.5 shrink-0" :class="iconClass" />
26
+ <span class="text-[13px] font-medium text-slate-200">{{ label }}</span>
27
+ <UBadge :color="outcome === 'failed' ? 'error' : 'neutral'" variant="subtle" size="sm">{{
28
+ outcomeLabel
29
+ }}</UBadge>
30
+ <span
31
+ v-if="formatClock(at, dateFormat ?? 'short')"
32
+ class="ms-auto text-[11px] text-slate-500"
33
+ >{{ formatClock(at, dateFormat ?? 'short') }}</span
34
+ >
35
+ </div>
36
+ </template>
@@ -18,6 +18,7 @@ import ArtifactLightbox from '~/components/media/ArtifactLightbox.vue'
18
18
  import StepRestartControl from '~/components/panels/StepRestartControl.vue'
19
19
  import StepRunMeta from '~/components/panels/StepRunMeta.vue'
20
20
  import StepContainerStatus from '~/components/panels/StepContainerStatus.vue'
21
+ import AttemptEntryHeader from '~/components/panels/AttemptEntryHeader.vue'
21
22
  import EnvironmentStatusPanel from '~/components/environments/EnvironmentStatusPanel.vue'
22
23
  import ProvisioningLogsDrawer from '~/components/provisioning/ProvisioningLogsDrawer.vue'
23
24
 
@@ -507,30 +508,18 @@ const GROUP_STATUS_META: Record<ScenarioGroup['status'], { icon: string; text: s
507
508
  data-testid="tester-fixer-attempt"
508
509
  class="rounded-lg border border-slate-800 bg-slate-900/60 px-3 py-2"
509
510
  >
510
- <div class="flex items-center gap-2">
511
- <UIcon
512
- :name="a.outcome === 'completed' ? 'i-lucide-wrench' : 'i-lucide-circle-x'"
513
- class="h-3.5 w-3.5 shrink-0"
514
- :class="a.outcome === 'completed' ? 'text-amber-300' : 'text-rose-400'"
515
- />
516
- <span class="text-[13px] font-medium text-slate-200">
517
- {{ t('testing.fixerTimeline.attempt', { n: a.attempt }) }}
518
- </span>
519
- <UBadge
520
- :color="a.outcome === 'completed' ? 'neutral' : 'error'"
521
- variant="subtle"
522
- size="sm"
523
- >
524
- {{
525
- a.outcome === 'completed'
526
- ? t('testing.fixerTimeline.completed')
527
- : t('testing.fixerTimeline.failed')
528
- }}
529
- </UBadge>
530
- <span class="ms-auto text-[11px] text-slate-500">{{
531
- d(new Date(a.at), 'short')
532
- }}</span>
533
- </div>
511
+ <AttemptEntryHeader
512
+ :label="t('testing.fixerTimeline.attempt', { n: a.attempt })"
513
+ :outcome="a.outcome"
514
+ :outcome-label="
515
+ a.outcome === 'completed'
516
+ ? t('testing.fixerTimeline.completed')
517
+ : t('testing.fixerTimeline.failed')
518
+ "
519
+ :at="a.at"
520
+ :icon="a.outcome === 'completed' ? 'i-lucide-wrench' : 'i-lucide-circle-x'"
521
+ :icon-class="a.outcome === 'completed' ? 'text-amber-300' : 'text-rose-400'"
522
+ />
534
523
  <p v-if="a.summary" class="mt-1 text-[12px] leading-snug text-slate-400">
535
524
  {{ a.summary }}
536
525
  </p>
@@ -2981,6 +2981,8 @@
2981
2981
  },
2982
2982
  "attemptsHeading": "{helper} attempts",
2983
2983
  "attempt": "Attempt {number}",
2984
+ "attemptInstructions": "Handed to {helper}",
2985
+ "attemptReport": "{helper} report",
2984
2986
  "outcome": {
2985
2987
  "completed": "completed",
2986
2988
  "failed": "failed"
@@ -2885,6 +2885,8 @@
2885
2885
  },
2886
2886
  "attemptsHeading": "Intentos de {helper}",
2887
2887
  "attempt": "Intento {number}",
2888
+ "attemptInstructions": "Entregado a {helper}",
2889
+ "attemptReport": "Informe de {helper}",
2888
2890
  "outcome": {
2889
2891
  "completed": "completado",
2890
2892
  "failed": "fallido"
@@ -2885,6 +2885,8 @@
2885
2885
  },
2886
2886
  "attemptsHeading": "Tentatives du {helper}",
2887
2887
  "attempt": "Tentative {number}",
2888
+ "attemptInstructions": "Transmis à {helper}",
2889
+ "attemptReport": "Rapport de {helper}",
2888
2890
  "outcome": {
2889
2891
  "completed": "terminée",
2890
2892
  "failed": "échouée"
@@ -2896,6 +2896,8 @@
2896
2896
  },
2897
2897
  "attemptsHeading": "ניסיונות {helper}",
2898
2898
  "attempt": "ניסיון {number}",
2899
+ "attemptInstructions": "נמסר ל־{helper}",
2900
+ "attemptReport": "דוח {helper}",
2899
2901
  "outcome": {
2900
2902
  "completed": "הושלם",
2901
2903
  "failed": "נכשל"
@@ -2898,6 +2898,8 @@
2898
2898
  },
2899
2899
  "attemptsHeading": "{helper} の試行",
2900
2900
  "attempt": "試行 {number}",
2901
+ "attemptInstructions": "{helper} への指示",
2902
+ "attemptReport": "{helper} のレポート",
2901
2903
  "outcome": {
2902
2904
  "completed": "完了",
2903
2905
  "failed": "失敗"
@@ -2885,6 +2885,8 @@
2885
2885
  },
2886
2886
  "attemptsHeading": "Próby: {helper}",
2887
2887
  "attempt": "Próba {number}",
2888
+ "attemptInstructions": "Przekazano do: {helper}",
2889
+ "attemptReport": "Raport: {helper}",
2888
2890
  "outcome": {
2889
2891
  "completed": "ukończono",
2890
2892
  "failed": "niepowodzenie"
@@ -2898,6 +2898,8 @@
2898
2898
  },
2899
2899
  "attemptsHeading": "{helper} denemeleri",
2900
2900
  "attempt": "Deneme {number}",
2901
+ "attemptInstructions": "{helper}'a iletildi",
2902
+ "attemptReport": "{helper} raporu",
2901
2903
  "outcome": {
2902
2904
  "completed": "tamamlandı",
2903
2905
  "failed": "başarısız oldu"
@@ -2885,6 +2885,8 @@
2885
2885
  },
2886
2886
  "attemptsHeading": "Спроби: {helper}",
2887
2887
  "attempt": "Спроба {number}",
2888
+ "attemptInstructions": "Передано: {helper}",
2889
+ "attemptReport": "Звіт: {helper}",
2888
2890
  "outcome": {
2889
2891
  "completed": "завершено",
2890
2892
  "failed": "помилка"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cat-factory/app",
3
- "version": "0.78.0",
3
+ "version": "0.79.0",
4
4
  "description": "Reusable Nuxt layer for the Agent Architecture Board SPA (components, stores, composables, pages). Consume it from a thin deployment app via `extends: ['@cat-factory/app']` and point it at your backend with NUXT_PUBLIC_API_BASE. See deploy/frontend for an example.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -34,7 +34,7 @@
34
34
  "valibot": "^1.4.2",
35
35
  "vue": "^3.5.39",
36
36
  "wretch": "^3.0.9",
37
- "@cat-factory/contracts": "0.84.0"
37
+ "@cat-factory/contracts": "0.85.0"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@toad-contracts/testing": "0.3.2",