@cat-factory/app 0.244.0 → 0.245.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.
@@ -19,14 +19,18 @@ import type {
19
19
  OutcomeCheckKind,
20
20
  OutcomeCheckState,
21
21
  OutcomeDisposition,
22
+ OutcomeSource,
22
23
  OutcomeSpecJoin,
23
24
  OutcomeVisual,
24
25
  RequirementsGap,
26
+ SourcesGap,
25
27
  TestsGap,
26
28
  TestsVerdict,
27
29
  VisualsGap,
28
30
  } from '~/utils/runOutcome'
29
31
  import { composeRunOutcome } from '~/utils/runOutcome'
32
+ import { GAP_KEYS } from '~/components/documents/DocumentSyncState.logic'
33
+ import DocumentOriginLink from '~/components/documents/DocumentOriginLink.vue'
30
34
  import { REPRODUCTION_STATUS_KEYS } from '~/utils/reproduction'
31
35
  import type { RequirementVerdictStatus, TestConcernSeverity } from '~/types/domain'
32
36
  import type { TestEnvironment } from '@cat-factory/contracts'
@@ -37,6 +41,7 @@ import MarkdownProse from '~/components/common/MarkdownProse.vue'
37
41
  import EmptyState from '~/components/common/EmptyState.vue'
38
42
 
39
43
  const board = useBoardStore()
44
+ const documents = useDocumentsStore()
40
45
  const execution = useExecutionStore()
41
46
  const serviceSpec = useServiceSpecStore()
42
47
  const ui = useUiStore()
@@ -128,6 +133,10 @@ const TESTS_GAP_KEYS: Record<TestsGap, string> = {
128
133
  no_tester_step: 'outcome.tests.gap.no_tester_step',
129
134
  tester_not_reported: 'outcome.tests.gap.tester_not_reported',
130
135
  }
136
+ const SOURCES_GAP_KEYS: Record<SourcesGap, string> = {
137
+ run_unavailable: RUN_UNAVAILABLE_KEY,
138
+ none_linked: 'outcome.sources.gap.none_linked',
139
+ }
131
140
  const VISUALS_GAP_KEYS: Record<VisualsGap, string> = {
132
141
  run_unavailable: RUN_UNAVAILABLE_KEY,
133
142
  no_visual_step: 'outcome.visuals.gap.no_visual_step',
@@ -288,6 +297,47 @@ const checkRows = computed(() =>
288
297
  })),
289
298
  )
290
299
 
300
+ /**
301
+ * What one linked page's row says about how current the copy the agents read was.
302
+ *
303
+ * Four outcomes, and none of them may collapse into another: a named revision to check the work
304
+ * against, a copy nobody could confirm (a warning about the WORK, not about the platform), a body
305
+ * with no source to trail, and a deployment that runs no freshness check at all. The last two are
306
+ * the pair a dash would merge, and they are opposite facts about whether anything is missing.
307
+ */
308
+ function revisionLabel(freshness: OutcomeSource['freshness']): string {
309
+ if (!freshness) return t('outcome.sources.unchecked')
310
+ switch (freshness.status) {
311
+ case 'confirmed':
312
+ return t('documents.freshness.revision', { version: freshness.version })
313
+ case 'not-applicable':
314
+ return t('outcome.sources.noSource')
315
+ case 'unconfirmed':
316
+ return t(GAP_KEYS[freshness.reason])
317
+ default:
318
+ return exhaustiveFreshness(freshness)
319
+ }
320
+ }
321
+
322
+ /** Compile-time totality: a new verdict member fails the build rather than rendering a blank. */
323
+ function exhaustiveFreshness(freshness: never): string {
324
+ return String(freshness)
325
+ }
326
+
327
+ /**
328
+ * The linked pages with their labels resolved, so the exhaustive mapping above is applied once
329
+ * per row rather than on every re-render, and the template stays a list.
330
+ */
331
+ const sourceRows = computed(() => {
332
+ const sources = outcome.value?.sources
333
+ if (!sources || sources.status !== 'reported') return []
334
+ return sources.sources.map((source) => ({
335
+ ...source,
336
+ icon: documents.descriptorForOrigin(source.origin)?.icon ?? 'i-lucide-file-text',
337
+ revision: revisionLabel(source.freshness),
338
+ }))
339
+ })
340
+
291
341
  /** Drill into the full test report (this card is the summary, never a replacement for it). */
292
342
  function openTestReport() {
293
343
  if (instance.value) ui.openTestEvidence(instance.value.id)
@@ -358,6 +408,49 @@ function openTestReport() {
358
408
  </p>
359
409
  </section>
360
410
 
411
+ <!-- What the run built FROM, and which revision of it. Directly under the ask, because it
412
+ is the rest of the brief: the description is what a person wrote, this is what the
413
+ agents actually read, and every section below is a statement about work done against
414
+ it. A design that moved mid-run is the reading that changes all of them. -->
415
+ <section class="mb-5" data-testid="outcome-sources">
416
+ <h3 class="mb-1.5 text-[11px] font-semibold uppercase tracking-wide text-slate-500">
417
+ {{ t('outcome.sources.title') }}
418
+ </h3>
419
+ <div v-if="outcome.sources.status === 'reported'" class="space-y-1">
420
+ <div
421
+ v-for="source in sourceRows"
422
+ :key="`${source.origin}:${source.url || source.title}`"
423
+ class="rounded-md border border-slate-800 bg-slate-900/60 px-2 py-1.5"
424
+ data-testid="outcome-source"
425
+ >
426
+ <DocumentOriginLink
427
+ :url="source.url ?? ''"
428
+ class="flex items-center gap-1.5 text-xs text-slate-300"
429
+ hover-class="hover:text-white"
430
+ >
431
+ <UIcon :name="source.icon" class="h-3.5 w-3.5 shrink-0 text-indigo-400" />
432
+ <span class="truncate">{{ source.title }}</span>
433
+ </DocumentOriginLink>
434
+ <p class="mt-0.5 text-[11px] text-slate-500" data-testid="outcome-source-revision">
435
+ {{ source.revision }}
436
+ </p>
437
+ <!-- Stated separately from the revision above: the last revision alone says the run
438
+ ENDED current, and says nothing about the step that finished before the page
439
+ changed under it. -->
440
+ <p
441
+ v-if="source.movedDuringRun"
442
+ class="mt-0.5 text-[11px] text-amber-300"
443
+ data-testid="outcome-source-moved"
444
+ >
445
+ {{ t('outcome.sources.moved') }}
446
+ </p>
447
+ </div>
448
+ </div>
449
+ <p v-else class="text-[13px] italic leading-relaxed text-slate-500">
450
+ {{ t(SOURCES_GAP_KEYS[outcome.sources.gap]) }}
451
+ </p>
452
+ </section>
453
+
361
454
  <!-- Requirement coverage: which required behaviours were checked, and what was seen. -->
362
455
  <section class="mb-5" data-testid="outcome-requirements">
363
456
  <h3 class="mb-1.5 text-[11px] font-semibold uppercase tracking-wide text-slate-500">
@@ -27,6 +27,8 @@ export type {
27
27
  OutcomePullRequest,
28
28
  OutcomeRequirement,
29
29
  OutcomeRequirements,
30
+ OutcomeSource,
31
+ OutcomeSources,
30
32
  OutcomeSpecJoin,
31
33
  OutcomeTests,
32
34
  OutcomeVisual,
@@ -34,6 +36,7 @@ export type {
34
36
  RequirementsGap,
35
37
  RunOutcome,
36
38
  RunUnavailableGap,
39
+ SourcesGap,
37
40
  TestsGap,
38
41
  TestsVerdict,
39
42
  VisualsGap,
@@ -6126,6 +6126,15 @@
6126
6126
  "title": "Was gefordert war",
6127
6127
  "none": "Diese Aufgabe hat keine Beschreibung, es gibt hier also nichts, womit sich das Ergebnis vergleichen ließe."
6128
6128
  },
6129
+ "sources": {
6130
+ "title": "Erstellt aus",
6131
+ "unchecked": "Nicht mit der Quelle abgeglichen.",
6132
+ "noSource": "Keine Quellseite zum Abgleichen vorhanden.",
6133
+ "moved": "Diese Seite wurde während des Laufs geändert, frühere Schritte haben also eine ältere Revision gelesen.",
6134
+ "gap": {
6135
+ "none_linked": "Zu diesem Lauf gelangte keine Seite zu den Agenten, sie arbeiteten allein mit der Aufgabenbeschreibung."
6136
+ }
6137
+ },
6129
6138
  "gap": {
6130
6139
  "run_unavailable": "Der Lauf dieser Aufgabe wurde nicht geladen, deshalb können die von ihm erfassten Belege hier nicht gezeigt werden. Das ist nicht dasselbe wie ein Lauf, der nichts erfasst hat."
6131
6140
  },
@@ -5839,6 +5839,15 @@
5839
5839
  "title": "What was asked",
5840
5840
  "none": "This task carries no description, so there is nothing here to compare the result against."
5841
5841
  },
5842
+ "sources": {
5843
+ "title": "Built from",
5844
+ "unchecked": "Not checked against the source.",
5845
+ "noSource": "No source page to compare against.",
5846
+ "moved": "This page changed while the run was in flight, so earlier steps read an older revision.",
5847
+ "gap": {
5848
+ "none_linked": "No page reached this run's agents, so they worked from the task description alone."
5849
+ }
5850
+ },
5842
5851
  "gap": {
5843
5852
  "run_unavailable": "This task's run was not loaded, so none of the evidence it recorded can be shown here. That is not the same as a run that recorded nothing."
5844
5853
  },
@@ -5570,6 +5570,15 @@
5570
5570
  "title": "Qué se pidió",
5571
5571
  "none": "Esta tarea no tiene descripción, así que no hay nada con lo que comparar el resultado."
5572
5572
  },
5573
+ "sources": {
5574
+ "title": "Creado a partir de",
5575
+ "unchecked": "No se ha comprobado con la fuente.",
5576
+ "noSource": "No hay página de origen con la que comparar.",
5577
+ "moved": "Esta página cambió mientras la ejecución estaba en curso, así que los pasos anteriores leyeron una revisión más antigua.",
5578
+ "gap": {
5579
+ "none_linked": "Ninguna página llegó a los agentes de esta ejecución, así que trabajaron solo con la descripción de la tarea."
5580
+ }
5581
+ },
5573
5582
  "gap": {
5574
5583
  "run_unavailable": "La ejecución de esta tarea no se cargó, así que aquí no pueden mostrarse las pruebas que registró. No es lo mismo que una ejecución que no registró nada."
5575
5584
  },
@@ -5570,6 +5570,15 @@
5570
5570
  "title": "Ce qui était demandé",
5571
5571
  "none": "Cette tâche n'a pas de description, il n'y a donc rien à quoi comparer le résultat."
5572
5572
  },
5573
+ "sources": {
5574
+ "title": "Construit à partir de",
5575
+ "unchecked": "Non vérifié auprès de la source.",
5576
+ "noSource": "Aucune page source à laquelle comparer.",
5577
+ "moved": "Cette page a changé pendant l'exécution : les étapes antérieures ont donc lu une révision plus ancienne.",
5578
+ "gap": {
5579
+ "none_linked": "Aucune page n'est parvenue aux agents de cette exécution, ils ont donc travaillé uniquement à partir de la description de la tâche."
5580
+ }
5581
+ },
5573
5582
  "gap": {
5574
5583
  "run_unavailable": "L'exécution de cette tâche n'a pas été chargée : les éléments qu'elle a enregistrés ne peuvent donc pas être affichés ici. Ce n'est pas la même chose qu'une exécution qui n'a rien enregistré."
5575
5584
  },
@@ -5570,6 +5570,15 @@
5570
5570
  "title": "מה התבקש",
5571
5571
  "none": "למשימה הזאת אין תיאור, ולכן אין מול מה להשוות את התוצאה."
5572
5572
  },
5573
+ "sources": {
5574
+ "title": "נבנה מתוך",
5575
+ "unchecked": "לא נבדק מול המקור.",
5576
+ "noSource": "אין עמוד מקור להשוואה.",
5577
+ "moved": "העמוד הזה השתנה בזמן שההרצה רצה, ולכן שלבים מוקדמים קראו גרסה ישנה יותר.",
5578
+ "gap": {
5579
+ "none_linked": "לא הגיע אף עמוד לסוכנים של הרצה זו, ולכן הם עבדו לפי תיאור המשימה בלבד."
5580
+ }
5581
+ },
5573
5582
  "gap": {
5574
5583
  "run_unavailable": "ההרצה של המשימה הזאת לא נטענה, ולכן הראיות שהיא תיעדה לא מוצגות כאן. זה לא אותו דבר כמו הרצה שלא תיעדה כלום."
5575
5584
  },
@@ -6126,6 +6126,15 @@
6126
6126
  "title": "Che cosa era stato chiesto",
6127
6127
  "none": "Questa attività non ha una descrizione, quindi non c'è nulla con cui confrontare l'esito."
6128
6128
  },
6129
+ "sources": {
6130
+ "title": "Costruito a partire da",
6131
+ "unchecked": "Non verificato con la sorgente.",
6132
+ "noSource": "Nessuna pagina di origine con cui confrontare.",
6133
+ "moved": "Questa pagina è cambiata mentre l'esecuzione era in corso, quindi i passi precedenti hanno letto una revisione più vecchia.",
6134
+ "gap": {
6135
+ "none_linked": "Nessuna pagina è arrivata agli agenti di questa esecuzione, che hanno lavorato solo sulla descrizione dell'attività."
6136
+ }
6137
+ },
6129
6138
  "gap": {
6130
6139
  "run_unavailable": "L'esecuzione di questa attività non è stata caricata, quindi le prove che ha registrato non possono essere mostrate qui. Non è la stessa cosa di un'esecuzione che non ha registrato nulla."
6131
6140
  },
@@ -5570,6 +5570,15 @@
5570
5570
  "title": "依頼内容",
5571
5571
  "none": "このタスクには説明がないため、結果と照らし合わせるものがありません。"
5572
5572
  },
5573
+ "sources": {
5574
+ "title": "参照した資料",
5575
+ "unchecked": "ソースとの照合は行われていません。",
5576
+ "noSource": "比較できるソースページがありません。",
5577
+ "moved": "このページは実行中に変更されたため、前半のステップは古いリビジョンを読んでいます。",
5578
+ "gap": {
5579
+ "none_linked": "この実行のエージェントにはページが渡されなかったため、タスクの説明だけを頼りに作業しました。"
5580
+ }
5581
+ },
5573
5582
  "gap": {
5574
5583
  "run_unavailable": "このタスクの実行が読み込まれていないため、そこに記録された根拠をここには表示できません。何も記録しなかった実行とは別のことです。"
5575
5584
  },
@@ -5570,6 +5570,15 @@
5570
5570
  "title": "O co proszono",
5571
5571
  "none": "To zadanie nie ma opisu, więc nie ma do czego porównać wyniku."
5572
5572
  },
5573
+ "sources": {
5574
+ "title": "Zbudowano na podstawie",
5575
+ "unchecked": "Nie sprawdzono ze źródłem.",
5576
+ "noSource": "Brak strony źródłowej do porównania.",
5577
+ "moved": "Ta strona zmieniła się w trakcie uruchomienia, więc wcześniejsze kroki czytały starszą wersję.",
5578
+ "gap": {
5579
+ "none_linked": "Do agentów tego przebiegu nie trafiła żadna strona, więc pracowali wyłącznie na opisie zadania."
5580
+ }
5581
+ },
5573
5582
  "gap": {
5574
5583
  "run_unavailable": "Przebieg tego zadania nie został wczytany, więc zapisane w nim dowody nie są tutaj pokazane. To co innego niż przebieg, który niczego nie zapisał."
5575
5584
  },
@@ -5570,6 +5570,15 @@
5570
5570
  "title": "Ne istenmişti",
5571
5571
  "none": "Bu görevin açıklaması yok, dolayısıyla sonucu karşılaştıracak bir şey de yok."
5572
5572
  },
5573
+ "sources": {
5574
+ "title": "Şuradan üretildi",
5575
+ "unchecked": "Kaynakla karşılaştırılmadı.",
5576
+ "noSource": "Karşılaştırılacak bir kaynak sayfa yok.",
5577
+ "moved": "Bu sayfa çalışma sürerken değişti, bu yüzden önceki adımlar daha eski bir sürümü okudu.",
5578
+ "gap": {
5579
+ "none_linked": "Bu çalıştırmanın ajanlarına hiçbir sayfa ulaşmadı, bu yüzden yalnızca görev açıklamasıyla çalıştılar."
5580
+ }
5581
+ },
5573
5582
  "gap": {
5574
5583
  "run_unavailable": "Bu görevin çalışması yüklenmediği için kaydettiği kanıtlar burada gösterilemiyor. Bu, hiçbir şey kaydetmemiş bir çalışmayla aynı şey değildir."
5575
5584
  },
@@ -5570,6 +5570,15 @@
5570
5570
  "title": "Про що просили",
5571
5571
  "none": "У цього завдання немає опису, тож немає з чим порівняти результат."
5572
5572
  },
5573
+ "sources": {
5574
+ "title": "Створено на основі",
5575
+ "unchecked": "Не звірено з джерелом.",
5576
+ "noSource": "Немає сторінки джерела для звіряння.",
5577
+ "moved": "Ця сторінка змінилася під час виконання, тож попередні кроки читали давнішу редакцію.",
5578
+ "gap": {
5579
+ "none_linked": "До агентів цього запуску не потрапила жодна сторінка, тож вони працювали лише з опису завдання."
5580
+ }
5581
+ },
5573
5582
  "gap": {
5574
5583
  "run_unavailable": "Запуск цього завдання не завантажено, тому зібрані ним докази тут не показані. Це не те саме, що запуск, який нічого не зібрав."
5575
5584
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cat-factory/app",
3
- "version": "0.244.0",
3
+ "version": "0.245.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",
@@ -40,7 +40,7 @@
40
40
  "valibot": "^1.4.2",
41
41
  "vue": "3.5.40",
42
42
  "wretch": "^3.0.9",
43
- "@cat-factory/contracts": "0.266.0"
43
+ "@cat-factory/contracts": "0.267.0"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@toad-contracts/testing": "0.3.2",