@cat-factory/app 0.266.0 → 0.266.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.
@@ -12,6 +12,7 @@ import { agentKindMeta } from '~/utils/catalog'
12
12
  import type { JudgeFinding, JudgeStepState } from '~/types/execution'
13
13
  import ResultWindowShell from '~/components/panels/ResultWindowShell.vue'
14
14
  import CopyButton from '~/components/common/CopyButton.vue'
15
+ import MarkdownProse from '~/components/common/MarkdownProse.vue'
15
16
 
16
17
  const board = useBoardStore()
17
18
  const execution = useExecutionStore()
@@ -190,11 +191,14 @@ async function act(choice: 'proceed' | 'bounce' | 'stop') {
190
191
  {{ judge.note }}
191
192
  </p>
192
193
 
194
+ <!-- The verdict a human reads. Markdown, because the judge prompt asks for a verdict
195
+ line plus grouped bullets rather than one paragraph. -->
193
196
  <div v-if="verdict?.summary" class="relative mt-3">
194
197
  <CopyButton :text="verdict.summary" class="absolute end-1 top-1" />
195
- <p class="whitespace-pre-wrap pe-8 text-[13px] leading-relaxed text-slate-200">
196
- {{ verdict.summary }}
197
- </p>
198
+ <MarkdownProse
199
+ :text="verdict.summary"
200
+ class="pe-8 text-[13px] leading-relaxed text-slate-200"
201
+ />
198
202
  </div>
199
203
 
200
204
  <section v-if="findings.length" class="mt-4">
@@ -220,12 +224,11 @@ async function act(choice: 'proceed' | 'bounce' | 'stop') {
220
224
  finding.where
221
225
  }}</code>
222
226
  </div>
223
- <p
227
+ <MarkdownProse
224
228
  v-if="finding.detail"
225
- class="mt-1 whitespace-pre-wrap text-[12px] leading-relaxed text-slate-400"
226
- >
227
- {{ finding.detail }}
228
- </p>
229
+ :text="finding.detail"
230
+ class="mt-1 text-[12px] leading-relaxed text-slate-400"
231
+ />
229
232
  </li>
230
233
  </ul>
231
234
  </section>
@@ -6,6 +6,7 @@
6
6
  // reviewer reported at least one standard; when none were reachable the reviewer says so in its
7
7
  // summary instead (so there is nothing to show here).
8
8
  import type { FragmentAdherence } from '~/types/execution'
9
+ import MarkdownProse from '~/components/common/MarkdownProse.vue'
9
10
 
10
11
  const props = defineProps<{ items: FragmentAdherence }>()
11
12
  const { t } = useI18n()
@@ -63,12 +64,11 @@ function label(item: FragmentAdherence[number]): string {
63
64
  {{ t('panels.stepDetail.adherence.outOfTen', { value: item.rating }) }}
64
65
  </span>
65
66
  </div>
66
- <p
67
+ <MarkdownProse
67
68
  v-if="item.assessment"
68
- class="mt-1 whitespace-pre-wrap text-[12px] leading-relaxed text-slate-300"
69
- >
70
- {{ item.assessment }}
71
- </p>
69
+ :text="item.assessment"
70
+ class="mt-1 text-[12px] leading-relaxed text-slate-300"
71
+ />
72
72
  <div v-if="item.relatedFindings.length" class="mt-1.5">
73
73
  <p class="text-[10px] font-semibold uppercase tracking-wide text-slate-500">
74
74
  {{ t('panels.stepDetail.adherence.relatedFindings') }}
@@ -4,6 +4,8 @@ import type { AgentState, PipelineStep, CompanionVerdict, StepApproval } from '~
4
4
  import { subtaskIconClass } from '~/utils/pipelineRender'
5
5
  import StepModelActivity from '~/components/observability/StepModelActivity.vue'
6
6
  import StepContainerStatus from '~/components/panels/StepContainerStatus.vue'
7
+ import CopyButton from '~/components/common/CopyButton.vue'
8
+ import MarkdownProse from '~/components/common/MarkdownProse.vue'
7
9
 
8
10
  // The step's metadata card body: state/timing/model/run id, the container cold-boot
9
11
  // phase, the live subtask breakdown, the LLM observability rollup, the applied
@@ -281,22 +283,35 @@ async function copyRunId() {
281
283
  {{ latestVerdict?.passed ? '≥' : '<' }} {{ pctOf(latestVerdict!.threshold) }}
282
284
  </UBadge>
283
285
  </div>
284
- <ol class="mt-2 space-y-1.5">
285
- <li v-for="(v, i) in companionVerdicts" :key="i" class="flex items-start gap-2 text-[12px]">
286
- <span
287
- class="mt-px inline-flex h-4 shrink-0 items-center rounded px-1 font-mono text-[11px] tabular-nums"
288
- :class="
289
- v.passed ? 'bg-emerald-500/15 text-emerald-300' : 'bg-amber-500/15 text-amber-300'
290
- "
291
- >
292
- {{ i + 1 }}
293
- </span>
294
- <div class="min-w-0">
286
+ <!-- One card per correction round: the score on its own line, then the reviewer's
287
+ challenge as rendered markdown. The feedback used to trail the score inside the same
288
+ line, which turned a multi-point review into one unreadable run of text. -->
289
+ <ol class="mt-2 space-y-2">
290
+ <li
291
+ v-for="(v, i) in companionVerdicts"
292
+ :key="i"
293
+ data-testid="companion-verdict"
294
+ class="relative rounded-lg border border-slate-800 bg-slate-900/60 px-3 py-2"
295
+ >
296
+ <CopyButton v-if="v.feedback" :text="v.feedback" class="absolute end-1 top-1" />
297
+ <div class="flex items-center gap-2 text-[12px]">
298
+ <span
299
+ class="inline-flex h-4 shrink-0 items-center rounded px-1 font-mono text-[11px] tabular-nums"
300
+ :class="
301
+ v.passed ? 'bg-emerald-500/15 text-emerald-300' : 'bg-amber-500/15 text-amber-300'
302
+ "
303
+ >
304
+ {{ i + 1 }}
305
+ </span>
295
306
  <span :class="v.passed ? 'text-emerald-300' : 'text-amber-300'">
296
307
  {{ pctOf(v.rating) }} {{ v.passed ? '≥' : '<' }} {{ pctOf(v.threshold) }}
297
308
  </span>
298
- <span v-if="v.feedback" class="ms-1 text-slate-400">— {{ v.feedback }}</span>
299
309
  </div>
310
+ <MarkdownProse
311
+ v-if="v.feedback"
312
+ :text="v.feedback"
313
+ class="mt-1.5 pe-6 text-[12px] leading-relaxed text-slate-300"
314
+ />
300
315
  </li>
301
316
  </ol>
302
317
  <p v-if="companionVerdicts.length > 1" class="mt-1 text-[11px] text-slate-500">
@@ -2,6 +2,7 @@
2
2
  import type { RequirementVerdictStatus, TestReport } from '~/types/domain'
3
3
  import type { TesterStepState } from '~/types/execution'
4
4
  import { resolveVerdictMeta, type VerdictMeta } from './StepTestReport.logic'
5
+ import MarkdownProse from '~/components/common/MarkdownProse.vue'
5
6
 
6
7
  // A tester step's latest structured report (what was tested, the per-area outcomes,
7
8
  // the concerns it raised and the greenlight verdict) plus the fixer-loop phase.
@@ -57,9 +58,11 @@ function verdictMeta(status: RequirementVerdictStatus): VerdictMeta {
57
58
  }}<span v-if="phase.phase === 'fixing'"> {{ t('panels.testReport.fixing') }}</span>
58
59
  </span>
59
60
  </div>
60
- <p v-if="report.summary" class="mb-3 text-[13px] leading-relaxed text-slate-300">
61
- {{ report.summary }}
62
- </p>
61
+ <MarkdownProse
62
+ v-if="report.summary"
63
+ :text="report.summary"
64
+ class="mb-3 text-[13px] leading-relaxed text-slate-300"
65
+ />
63
66
 
64
67
  <div v-if="report.tested.length" class="mb-3">
65
68
  <div class="mb-1 text-[11px] text-slate-500">{{ t('panels.testReport.tested') }}</div>
@@ -123,7 +126,7 @@ function verdictMeta(status: RequirementVerdictStatus): VerdictMeta {
123
126
  >
124
127
  <span class="font-medium text-slate-200">{{ c.title }}</span>
125
128
  </div>
126
- <p v-if="c.detail" class="mt-1 text-slate-400">{{ c.detail }}</p>
129
+ <MarkdownProse v-if="c.detail" :text="c.detail" class="mt-1 text-slate-400" />
127
130
  </div>
128
131
  </div>
129
132
  </section>
@@ -29,6 +29,7 @@ import { activeChunkLabels, chunkReviewPercent, hasNoSlicePlan } from '~/utils/p
29
29
  import ResultWindowShell from '~/components/panels/ResultWindowShell.vue'
30
30
  import StepRunMeta from '~/components/panels/StepRunMeta.vue'
31
31
  import StepFragmentAdherence from '~/components/panels/StepFragmentAdherence.vue'
32
+ import MarkdownProse from '~/components/common/MarkdownProse.vue'
32
33
 
33
34
  const execution = useExecutionStore()
34
35
  const board = useBoardStore()
@@ -527,13 +528,13 @@ async function onDismiss(id: string): Promise<void> {
527
528
 
528
529
  <!-- The reviewer's overall assessment. Prose, so it takes the reading measure (see the
529
530
  shell's `width` prop) — this window is `full`-width. -->
530
- <p
531
+ <div
531
532
  v-if="state?.summary"
532
533
  class="mb-3 max-w-3xl rounded-md bg-slate-800/50 px-3 py-2 text-[12px] text-slate-300"
533
534
  >
534
- <span class="text-slate-500">{{ t('prReview.summaryLabel') }}</span>
535
- {{ state.summary }}
536
- </p>
535
+ <span class="mb-1 block text-slate-500">{{ t('prReview.summaryLabel') }}</span>
536
+ <MarkdownProse :text="state.summary" />
537
+ </div>
537
538
 
538
539
  <!-- Best-practice adherence: per standard folded into the reviewer's prompt, a 1..10
539
540
  rating of how well the PR adheres + the issues that standard surfaced. -->
@@ -680,12 +681,17 @@ async function onDismiss(id: string): Promise<void> {
680
681
  bucket to `full`, so these are the three paragraphs the width would
681
682
  otherwise have stretched furthest; the path/line row, the badges and the
682
683
  per-finding actions are what it is actually for. -->
683
- <p
684
- class="mt-1 max-w-3xl whitespace-pre-wrap text-[12px] text-slate-300"
684
+ <MarkdownProse
685
+ v-if="f.detail"
686
+ :text="f.detail"
687
+ class="mt-1 max-w-3xl text-[12px] text-slate-300"
685
688
  :class="isRetracted(f) ? 'line-through' : ''"
686
- >
687
- {{ f.detail }}
688
- </p>
689
+ />
690
+ <!-- The suggested fix is a VALUE a human copies (a patch line, a command, a
691
+ path), not prose, so it stays preformatted: markdown would emphasise the
692
+ `__dunder__` in an identifier, curl the quotes in a command, and drop the
693
+ indentation of anything longer than a line. Same reason the CI gate's
694
+ failure summary is left alone. -->
689
695
  <p
690
696
  v-if="f.suggestedFix"
691
697
  class="mt-1 max-w-3xl whitespace-pre-wrap rounded-md bg-slate-800/50 px-2 py-1 text-[11px] text-slate-300"
@@ -696,10 +702,10 @@ async function onDismiss(id: string): Promise<void> {
696
702
 
697
703
  <!-- The investigator's justification (why the finding holds up / was retracted),
698
704
  or the reason the challenge investigation failed. -->
699
- <p
705
+ <div
700
706
  v-if="f.challenge?.justification"
701
707
  data-testid="pr-review-finding-justification"
702
- class="mt-1.5 max-w-3xl whitespace-pre-wrap rounded-md px-2 py-1 text-[11px]"
708
+ class="mt-1.5 max-w-3xl rounded-md px-2 py-1 text-[11px]"
703
709
  :class="
704
710
  isRetracted(f)
705
711
  ? 'bg-rose-500/10 text-rose-200'
@@ -708,13 +714,16 @@ async function onDismiss(id: string): Promise<void> {
708
714
  : 'bg-sky-500/10 text-sky-200'
709
715
  "
710
716
  >
711
- <span class="font-medium">{{
717
+ <!-- A label that used to prefix its value inline now heads the block the
718
+ rendered prose became, so it needs to READ as a heading line rather than
719
+ as a stray word above a paragraph. -->
720
+ <span class="mb-1 block font-medium">{{
712
721
  isChallengeFailed(f)
713
722
  ? t('prReview.challenge.failedLabel')
714
723
  : t('prReview.challenge.verdictLabel')
715
724
  }}</span>
716
- {{ f.challenge.justification }}
717
- </p>
725
+ <MarkdownProse :text="f.challenge.justification" />
726
+ </div>
718
727
 
719
728
  <!-- Per-finding actions: Challenge + Dismiss (only while awaiting a selection). -->
720
729
  <div
@@ -20,6 +20,7 @@ import StepContainerStatus from '~/components/panels/StepContainerStatus.vue'
20
20
  import AttemptEntryHeader from '~/components/panels/AttemptEntryHeader.vue'
21
21
  import EnvironmentStatusPanel from '~/components/environments/EnvironmentStatusPanel.vue'
22
22
  import ProvisioningLogsDrawer from '~/components/provisioning/ProvisioningLogsDrawer.vue'
23
+ import MarkdownProse from '~/components/common/MarkdownProse.vue'
23
24
 
24
25
  const board = useBoardStore()
25
26
  const execution = useExecutionStore()
@@ -489,9 +490,11 @@ const GROUP_STATUS_META: Record<ScenarioGroup['status'], { icon: string; text: s
489
490
  :icon="a.outcome === 'completed' ? 'i-lucide-wrench' : 'i-lucide-circle-x'"
490
491
  :icon-class="a.outcome === 'completed' ? 'text-amber-300' : 'text-rose-400'"
491
492
  />
492
- <p v-if="a.summary" class="mt-1 max-w-3xl text-[12px] leading-snug text-slate-400">
493
- {{ a.summary }}
494
- </p>
493
+ <MarkdownProse
494
+ v-if="a.summary"
495
+ :text="a.summary"
496
+ class="mt-1 max-w-3xl text-[12px] leading-snug text-slate-400"
497
+ />
495
498
  <div v-if="a.concerns && a.concerns.length" class="mt-1.5">
496
499
  <p class="text-[11px] text-slate-500">
497
500
  {{ t('testing.fixerTimeline.addressed') }}
@@ -575,9 +578,11 @@ const GROUP_STATUS_META: Record<ScenarioGroup['status'], { icon: string; text: s
575
578
  d(new Date(vd.at), 'short')
576
579
  }}</span>
577
580
  </div>
578
- <p v-if="vd.feedback" class="mt-1 text-[12px] leading-snug text-slate-400">
579
- {{ vd.feedback }}
580
- </p>
581
+ <MarkdownProse
582
+ v-if="vd.feedback"
583
+ :text="vd.feedback"
584
+ class="mt-1 text-[12px] leading-snug text-slate-400"
585
+ />
581
586
  <div v-if="vd.gaps.length" class="mt-1.5">
582
587
  <p class="text-[11px] text-slate-500">{{ t('testing.quality.gaps') }}</p>
583
588
  <ul class="mt-1 space-y-0.5">
@@ -610,12 +615,11 @@ const GROUP_STATUS_META: Record<ScenarioGroup['status'], { icon: string; text: s
610
615
  <!-- Summary — the tester's own prose, so it takes the reading measure the shell's `full`
611
616
  width obliges (see the `width` prop). The scenario rows and log tails below keep the
612
617
  full span. -->
613
- <p
618
+ <MarkdownProse
614
619
  v-if="report.summary"
620
+ :text="report.summary"
615
621
  class="mb-4 max-w-3xl text-[13px] leading-relaxed text-slate-300"
616
- >
617
- {{ report.summary }}
618
- </p>
622
+ />
619
623
 
620
624
  <h3 class="mb-2 text-[11px] font-semibold uppercase tracking-wide text-slate-500">
621
625
  {{ t('testing.scenariosOutcomes') }}
@@ -680,9 +684,11 @@ const GROUP_STATUS_META: Record<ScenarioGroup['status'], { icon: string; text: s
680
684
  />
681
685
  <div class="min-w-0">
682
686
  <span class="text-[13px] text-slate-200">{{ o.name }}</span>
683
- <p v-if="o.detail" class="max-w-3xl text-[12px] leading-snug text-slate-400">
684
- {{ o.detail }}
685
- </p>
687
+ <MarkdownProse
688
+ v-if="o.detail"
689
+ :text="o.detail"
690
+ class="max-w-3xl text-[12px] leading-snug text-slate-400"
691
+ />
686
692
  </div>
687
693
  </div>
688
694
  <p v-if="!g.outcomes.length" class="py-0.5 text-[12px] italic text-slate-500">
@@ -710,9 +716,11 @@ const GROUP_STATUS_META: Record<ScenarioGroup['status'], { icon: string; text: s
710
716
  {{ SEVERITY_LABELS[c.severity] }}
711
717
  </span>
712
718
  </div>
713
- <p v-if="c.detail" class="max-w-3xl text-[12px] leading-snug text-slate-400">
714
- {{ c.detail }}
715
- </p>
719
+ <MarkdownProse
720
+ v-if="c.detail"
721
+ :text="c.detail"
722
+ class="max-w-3xl text-[12px] leading-snug text-slate-400"
723
+ />
716
724
  </div>
717
725
  </div>
718
726
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cat-factory/app",
3
- "version": "0.266.0",
3
+ "version": "0.266.1",
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",