@cat-factory/app 0.7.2 → 0.7.4
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/app/components/board/nodes/TaskPipelineMini.vue +37 -1
- package/app/components/gates/GateResultView.vue +23 -50
- package/app/components/observability/StepModelActivity.vue +49 -0
- package/app/components/panels/ObservabilityPanel.vue +1 -1
- package/app/components/panels/StepMetadataCard.vue +4 -16
- package/app/components/panels/StepRunMeta.vue +105 -0
- package/app/components/testing/TestReportWindow.vue +17 -8
- package/package.json +1 -1
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import type { AgentState } from '~/types/domain'
|
|
3
3
|
import { agentKindMeta } from '~/utils/catalog'
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
subtaskIconClass,
|
|
6
|
+
isFailedStep,
|
|
7
|
+
FAILED_STEP_META,
|
|
8
|
+
gateCompanionFor,
|
|
9
|
+
COMPANION_STATE_META,
|
|
10
|
+
} from '~/utils/pipelineRender'
|
|
5
11
|
import { lodAtLeast } from '~/composables/useSemanticZoom'
|
|
6
12
|
|
|
7
13
|
// Spatial drill-down inside a task card: at the `steps` zoom band the task's
|
|
@@ -24,6 +30,13 @@ const steps = computed(() => instance.value?.steps ?? [])
|
|
|
24
30
|
// `working`) must stop spinning, matching the failure card the task card shows.
|
|
25
31
|
const runFailed = computed(() => instance.value?.status === 'failed')
|
|
26
32
|
|
|
33
|
+
// The conditionally-run companion (if any) each step drives — the polling gates'
|
|
34
|
+
// helper (ci → ci-fixer, conflicts → conflict-resolver) or the Tester's fixer — with
|
|
35
|
+
// its possible/running/completed/skipped state. The board drill-down shows it the same
|
|
36
|
+
// way the inspector + focus pipeline do, so a gate working its helper reads as active
|
|
37
|
+
// (spinning "Running") rather than a frozen subtask list.
|
|
38
|
+
const companionByStep = computed(() => steps.value.map((s) => gateCompanionFor(s, runFailed.value)))
|
|
39
|
+
|
|
27
40
|
// Expand the pipeline list only when zoomed in far enough AND the board driver
|
|
28
41
|
// permits this card — on-screen, and the centre-most of any cards that would
|
|
29
42
|
// otherwise overlap (see useTaskExpansion) — so deep-zoom expansions don't pile up.
|
|
@@ -132,6 +145,29 @@ const ITEM_ICON: Record<string, string> = {
|
|
|
132
145
|
/>
|
|
133
146
|
</div>
|
|
134
147
|
|
|
148
|
+
<!-- conditionally-run companion (the gate's ci-fixer / conflict-resolver, or the
|
|
149
|
+
Tester's fixer): a compact running/ran/skipped line, so a gate that's working
|
|
150
|
+
its helper reads as actively fixing rather than a frozen subtask list. -->
|
|
151
|
+
<div v-if="companionByStep[i]" class="mt-1 flex items-center gap-1 text-[9px]">
|
|
152
|
+
<UIcon
|
|
153
|
+
:name="agentKindMeta(companionByStep[i]!.kind).icon"
|
|
154
|
+
class="h-2.5 w-2.5 shrink-0"
|
|
155
|
+
:class="[
|
|
156
|
+
COMPANION_STATE_META[companionByStep[i]!.state].text,
|
|
157
|
+
companionByStep[i]!.state === 'running' ? 'animate-spin' : '',
|
|
158
|
+
]"
|
|
159
|
+
/>
|
|
160
|
+
<span class="truncate text-slate-400">
|
|
161
|
+
{{ agentKindMeta(companionByStep[i]!.kind).label }}
|
|
162
|
+
</span>
|
|
163
|
+
<span
|
|
164
|
+
class="ml-auto shrink-0"
|
|
165
|
+
:class="COMPANION_STATE_META[companionByStep[i]!.state].text"
|
|
166
|
+
>
|
|
167
|
+
{{ COMPANION_STATE_META[companionByStep[i]!.state].label }}
|
|
168
|
+
</span>
|
|
169
|
+
</div>
|
|
170
|
+
|
|
135
171
|
<!-- deepest band: the actual todo list (done / in-progress / pending) -->
|
|
136
172
|
<ul v-if="showItems && s.subtasks?.items?.length" class="mt-1 space-y-0.5">
|
|
137
173
|
<li
|
|
@@ -9,10 +9,10 @@ import { computed } from 'vue'
|
|
|
9
9
|
import { agentKindMeta } from '~/utils/catalog'
|
|
10
10
|
import type { GateStepState } from '~/types/execution'
|
|
11
11
|
import StepRestartControl from '~/components/panels/StepRestartControl.vue'
|
|
12
|
+
import StepRunMeta from '~/components/panels/StepRunMeta.vue'
|
|
12
13
|
|
|
13
14
|
const board = useBoardStore()
|
|
14
15
|
const execution = useExecutionStore()
|
|
15
|
-
const ui = useUiStore()
|
|
16
16
|
|
|
17
17
|
// Synchronous window: it reads its state straight off the execution step, so there's
|
|
18
18
|
// nothing to fetch on open (no `onOpen` loader).
|
|
@@ -44,15 +44,6 @@ function formatClock(ms?: number | null): string | null {
|
|
|
44
44
|
return ms ? new Date(ms).toLocaleString() : null
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
// The run id (execution instance id) this gate belongs to — copyable for support /
|
|
48
|
-
// log lookups, and a jump into the run's observability view.
|
|
49
|
-
async function copyRunId() {
|
|
50
|
-
if (instanceId.value) await navigator.clipboard?.writeText(instanceId.value)
|
|
51
|
-
}
|
|
52
|
-
function openObservability() {
|
|
53
|
-
if (instanceId.value) ui.openObservability(instanceId.value)
|
|
54
|
-
}
|
|
55
|
-
|
|
56
47
|
/**
|
|
57
48
|
* The display status — a roll-up of the persisted gate state + the run's status, so the
|
|
58
49
|
* window reads as a conclusion rather than raw fields:
|
|
@@ -258,19 +249,16 @@ const conflictVerdict = computed(() => {
|
|
|
258
249
|
>
|
|
259
250
|
{{ gate.lastFailureSummary }}
|
|
260
251
|
</p>
|
|
261
|
-
<
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
>.</template
|
|
272
|
-
>
|
|
273
|
-
</p>
|
|
252
|
+
<a
|
|
253
|
+
v-if="prUrl"
|
|
254
|
+
:href="prUrl"
|
|
255
|
+
target="_blank"
|
|
256
|
+
rel="noopener"
|
|
257
|
+
class="mt-2 inline-flex items-center gap-1 text-[12px] text-sky-300 hover:text-sky-200 hover:underline"
|
|
258
|
+
>
|
|
259
|
+
View pull request on GitHub
|
|
260
|
+
<UIcon name="i-lucide-external-link" class="h-3 w-3" />
|
|
261
|
+
</a>
|
|
274
262
|
</template>
|
|
275
263
|
|
|
276
264
|
<!-- Attempt history (both gates): what each helper run did and how it ended. -->
|
|
@@ -312,7 +300,7 @@ const conflictVerdict = computed(() => {
|
|
|
312
300
|
|
|
313
301
|
<!-- Sidebar: gate state -->
|
|
314
302
|
<aside
|
|
315
|
-
class="hidden w-
|
|
303
|
+
class="hidden w-60 shrink-0 flex-col gap-4 border-l border-slate-800 bg-slate-900/50 px-4 py-4 lg:flex"
|
|
316
304
|
>
|
|
317
305
|
<div v-if="gate">
|
|
318
306
|
<h4 class="mb-2 text-[11px] font-semibold uppercase tracking-wide text-slate-500">
|
|
@@ -348,32 +336,17 @@ const conflictVerdict = computed(() => {
|
|
|
348
336
|
<p class="font-mono text-[12px] text-slate-300">{{ shortSha }}</p>
|
|
349
337
|
</div>
|
|
350
338
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
<p
|
|
363
|
-
class="cursor-pointer break-all font-mono text-[12px] text-slate-400 hover:text-slate-200"
|
|
364
|
-
:title="`${instanceId} — click to copy`"
|
|
365
|
-
@click="copyRunId"
|
|
366
|
-
>
|
|
367
|
-
{{ instanceId }}
|
|
368
|
-
</p>
|
|
369
|
-
<button
|
|
370
|
-
class="mt-1 inline-flex items-center gap-1 text-[11px] text-sky-300 hover:text-sky-200"
|
|
371
|
-
@click="openObservability"
|
|
372
|
-
>
|
|
373
|
-
<UIcon name="i-lucide-activity" class="h-3 w-3" />
|
|
374
|
-
Open observability
|
|
375
|
-
</button>
|
|
376
|
-
</div>
|
|
339
|
+
<!-- Shared run metadata + embedded observability (model, run id, timing,
|
|
340
|
+
model-activity rollup) — identical to the agent step detail. -->
|
|
341
|
+
<StepRunMeta
|
|
342
|
+
v-if="step"
|
|
343
|
+
:step="step"
|
|
344
|
+
:instance-id="instanceId ?? undefined"
|
|
345
|
+
:step-number="stepIndex === null ? undefined : stepIndex + 1"
|
|
346
|
+
:total-steps="instance?.steps.length"
|
|
347
|
+
:run-failed="instance?.status === 'failed'"
|
|
348
|
+
:failure-at="instance?.failure?.occurredAt"
|
|
349
|
+
/>
|
|
377
350
|
|
|
378
351
|
<p class="mt-auto text-[10px] leading-relaxed text-slate-600">
|
|
379
352
|
A gate runs a programmatic precheck and only spins up the
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
import type { StepMetrics } from '~/types/execution'
|
|
4
|
+
import StepMetricsBar from '~/components/observability/StepMetricsBar.vue'
|
|
5
|
+
|
|
6
|
+
// The shared "Model activity" block: the LLM observability rollup (StepMetricsBar) under
|
|
7
|
+
// a labelled header with a "View all calls →" link into the full per-call panel. Used by
|
|
8
|
+
// every step surface that shows a single step's metrics (the step metadata card, the
|
|
9
|
+
// gate / tester result windows) so the embedded-observability treatment can't drift.
|
|
10
|
+
// The "View all calls →" link opens the run-level panel, so it appears for any step that
|
|
11
|
+
// belongs to a run — including a gate, whose programmatic precheck records no per-step
|
|
12
|
+
// calls (the bar is omitted, but the link still reaches the helper agents' calls). Renders
|
|
13
|
+
// nothing only when there's neither a run to inspect nor any recorded calls.
|
|
14
|
+
const props = defineProps<{
|
|
15
|
+
metrics?: StepMetrics | null
|
|
16
|
+
/** The run whose per-call panel the header link / bar click opens. */
|
|
17
|
+
instanceId?: string
|
|
18
|
+
}>()
|
|
19
|
+
|
|
20
|
+
const ui = useUiStore()
|
|
21
|
+
const hasCalls = computed(() => !!props.metrics && props.metrics.calls > 0)
|
|
22
|
+
|
|
23
|
+
function openObservability() {
|
|
24
|
+
if (props.instanceId) ui.openObservability(props.instanceId)
|
|
25
|
+
}
|
|
26
|
+
</script>
|
|
27
|
+
|
|
28
|
+
<template>
|
|
29
|
+
<div v-if="instanceId || hasCalls">
|
|
30
|
+
<div class="mb-1 flex items-center justify-between">
|
|
31
|
+
<span class="text-[11px] font-semibold uppercase tracking-wide text-slate-500">
|
|
32
|
+
Model activity
|
|
33
|
+
</span>
|
|
34
|
+
<button
|
|
35
|
+
v-if="instanceId"
|
|
36
|
+
class="text-[11px] text-sky-400 hover:text-sky-300"
|
|
37
|
+
@click="openObservability"
|
|
38
|
+
>
|
|
39
|
+
View all calls →
|
|
40
|
+
</button>
|
|
41
|
+
</div>
|
|
42
|
+
<StepMetricsBar
|
|
43
|
+
v-if="hasCalls && metrics"
|
|
44
|
+
:metrics="metrics"
|
|
45
|
+
clickable
|
|
46
|
+
@inspect="openObservability"
|
|
47
|
+
/>
|
|
48
|
+
</div>
|
|
49
|
+
</template>
|
|
@@ -109,7 +109,7 @@ function exportJson() {
|
|
|
109
109
|
<Transition name="obs-fade">
|
|
110
110
|
<div
|
|
111
111
|
v-if="open"
|
|
112
|
-
class="fixed inset-0 z-
|
|
112
|
+
class="fixed inset-0 z-[60] flex flex-col bg-slate-950/96 backdrop-blur-sm"
|
|
113
113
|
role="dialog"
|
|
114
114
|
aria-modal="true"
|
|
115
115
|
>
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { computed } from 'vue'
|
|
3
3
|
import type { AgentState, PipelineStep, CompanionVerdict } from '~/types/execution'
|
|
4
4
|
import { subtaskIconClass } from '~/utils/pipelineRender'
|
|
5
|
-
import
|
|
5
|
+
import StepModelActivity from '~/components/observability/StepModelActivity.vue'
|
|
6
6
|
|
|
7
7
|
// The step's metadata card body: state/timing/model/run id, the container cold-boot
|
|
8
8
|
// phase, the live subtask breakdown, the LLM observability rollup, the applied
|
|
@@ -20,7 +20,6 @@ const props = defineProps<{
|
|
|
20
20
|
latestVerdict: CompanionVerdict | null
|
|
21
21
|
}>()
|
|
22
22
|
|
|
23
|
-
const ui = useUiStore()
|
|
24
23
|
const models = useModelsStore()
|
|
25
24
|
|
|
26
25
|
const STATE_META: Record<AgentState, { label: string; color: string }> = {
|
|
@@ -56,10 +55,6 @@ async function copyRunId() {
|
|
|
56
55
|
const id = props.step.runId ?? props.instanceId
|
|
57
56
|
if (id) await navigator.clipboard?.writeText(id)
|
|
58
57
|
}
|
|
59
|
-
|
|
60
|
-
function openObservability() {
|
|
61
|
-
if (props.instanceId) ui.openObservability(props.instanceId)
|
|
62
|
-
}
|
|
63
58
|
</script>
|
|
64
59
|
|
|
65
60
|
<template>
|
|
@@ -169,16 +164,9 @@ function openObservability() {
|
|
|
169
164
|
</div>
|
|
170
165
|
|
|
171
166
|
<!-- LLM observability rollup (tokens, output-limit headroom,
|
|
172
|
-
transport-vs-execution); click to open the full per-call panel
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
<span class="text-[11px] uppercase tracking-wide text-slate-500"> Model activity </span>
|
|
176
|
-
<button class="text-[11px] text-sky-400 hover:text-sky-300" @click="openObservability">
|
|
177
|
-
View all calls →
|
|
178
|
-
</button>
|
|
179
|
-
</div>
|
|
180
|
-
<StepMetricsBar :metrics="step.metrics" clickable @inspect="openObservability" />
|
|
181
|
-
</div>
|
|
167
|
+
transport-vs-execution); click to open the full per-call panel. Self-gates: the
|
|
168
|
+
"View all calls →" link shows for any run, the metrics bar only when calls exist. -->
|
|
169
|
+
<StepModelActivity class="mt-4" :metrics="step.metrics" :instance-id="instanceId" />
|
|
182
170
|
|
|
183
171
|
<!-- standards (prompt fragments) folded into this step -->
|
|
184
172
|
<div v-if="step.selectedFragmentIds && step.selectedFragmentIds.length" class="mt-4">
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
import type { PipelineStep } from '~/types/execution'
|
|
4
|
+
import { useStepTimer } from '~/composables/useStepTimer'
|
|
5
|
+
import StepModelActivity from '~/components/observability/StepModelActivity.vue'
|
|
6
|
+
|
|
7
|
+
// Shared run-metadata + observability block for the step-backed result windows
|
|
8
|
+
// (the CI/conflicts gate, the tester report). It carries the facts every step has in
|
|
9
|
+
// common — step position, live duration, model, run id, and the LLM model-activity
|
|
10
|
+
// rollup — so each window keeps only its own bespoke detail (the gate's verdict, the
|
|
11
|
+
// tester's scenarios) and the universal "which run is this / how did the model do"
|
|
12
|
+
// facts read the same everywhere. Laid out as a stack of labelled fields to drop into
|
|
13
|
+
// a window's sidebar; the canonical full-width version lives in StepMetadataCard.
|
|
14
|
+
const props = defineProps<{
|
|
15
|
+
step: PipelineStep
|
|
16
|
+
/** The enclosing run; copyable + opens the per-call observability panel. */
|
|
17
|
+
instanceId?: string
|
|
18
|
+
/** 1-based position in the pipeline, shown as "N of M" when both are given. */
|
|
19
|
+
stepNumber?: number
|
|
20
|
+
totalSteps?: number
|
|
21
|
+
/** The run failed: freezes the clock and reports a mid-flight step honestly. */
|
|
22
|
+
runFailed?: boolean
|
|
23
|
+
/** Epoch ms the run failed, so the frozen duration is the failure time. */
|
|
24
|
+
failureAt?: number | null
|
|
25
|
+
}>()
|
|
26
|
+
|
|
27
|
+
const models = useModelsStore()
|
|
28
|
+
|
|
29
|
+
const { isRunning, durationLabel } = useStepTimer({
|
|
30
|
+
step: () => props.step,
|
|
31
|
+
runFailed: () => props.runFailed ?? false,
|
|
32
|
+
failureAt: () => props.failureAt,
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
const modelLabel = computed(() => (props.step.model ? models.labelForRef(props.step.model) : null))
|
|
36
|
+
const runId = computed(() => props.step.runId ?? props.instanceId ?? null)
|
|
37
|
+
|
|
38
|
+
function formatClock(ms?: number | null): string | null {
|
|
39
|
+
return ms ? new Date(ms).toLocaleString() : null
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function copyRunId() {
|
|
43
|
+
if (runId.value) await navigator.clipboard?.writeText(runId.value)
|
|
44
|
+
}
|
|
45
|
+
</script>
|
|
46
|
+
|
|
47
|
+
<template>
|
|
48
|
+
<div class="flex flex-col gap-4">
|
|
49
|
+
<div v-if="stepNumber && totalSteps">
|
|
50
|
+
<h4 class="mb-1 text-[11px] font-semibold uppercase tracking-wide text-slate-500">Step</h4>
|
|
51
|
+
<p class="text-[12px] text-slate-300">{{ stepNumber }} of {{ totalSteps }}</p>
|
|
52
|
+
</div>
|
|
53
|
+
|
|
54
|
+
<div v-if="durationLabel">
|
|
55
|
+
<h4 class="mb-1 text-[11px] font-semibold uppercase tracking-wide text-slate-500">
|
|
56
|
+
Duration
|
|
57
|
+
</h4>
|
|
58
|
+
<p class="flex items-center gap-1.5 text-[12px] tabular-nums text-slate-300">
|
|
59
|
+
<UIcon
|
|
60
|
+
v-if="isRunning"
|
|
61
|
+
name="i-lucide-loader-circle"
|
|
62
|
+
class="h-3 w-3 animate-spin text-indigo-400"
|
|
63
|
+
/>
|
|
64
|
+
{{ durationLabel }}
|
|
65
|
+
<span v-if="isRunning" class="text-[11px] text-slate-500">elapsed</span>
|
|
66
|
+
</p>
|
|
67
|
+
</div>
|
|
68
|
+
|
|
69
|
+
<div v-if="formatClock(step.startedAt)">
|
|
70
|
+
<h4 class="mb-1 text-[11px] font-semibold uppercase tracking-wide text-slate-500">Started</h4>
|
|
71
|
+
<p class="text-[12px] text-slate-300">{{ formatClock(step.startedAt) }}</p>
|
|
72
|
+
</div>
|
|
73
|
+
|
|
74
|
+
<div v-if="formatClock(step.finishedAt)">
|
|
75
|
+
<h4 class="mb-1 text-[11px] font-semibold uppercase tracking-wide text-slate-500">
|
|
76
|
+
Finished
|
|
77
|
+
</h4>
|
|
78
|
+
<p class="text-[12px] text-slate-300">{{ formatClock(step.finishedAt) }}</p>
|
|
79
|
+
</div>
|
|
80
|
+
|
|
81
|
+
<div v-if="step.model">
|
|
82
|
+
<h4 class="mb-1 text-[11px] font-semibold uppercase tracking-wide text-slate-500">Model</h4>
|
|
83
|
+
<p class="break-all text-[12px] text-slate-300" :title="step.model">
|
|
84
|
+
{{ modelLabel ?? step.model }}
|
|
85
|
+
</p>
|
|
86
|
+
</div>
|
|
87
|
+
|
|
88
|
+
<div v-if="runId">
|
|
89
|
+
<h4 class="mb-1 text-[11px] font-semibold uppercase tracking-wide text-slate-500">Run</h4>
|
|
90
|
+
<p
|
|
91
|
+
class="cursor-pointer break-all font-mono text-[12px] text-slate-400 hover:text-slate-200"
|
|
92
|
+
:title="`${runId} — click to copy`"
|
|
93
|
+
@click="copyRunId"
|
|
94
|
+
>
|
|
95
|
+
{{ runId }}
|
|
96
|
+
</p>
|
|
97
|
+
</div>
|
|
98
|
+
|
|
99
|
+
<!-- The model-activity rollup, embedded inline. The "View all calls →" link opens the
|
|
100
|
+
run's observability panel even when this step recorded no calls (e.g. a gate that
|
|
101
|
+
passed its precheck with no helper spun up), so every window reaches it the same
|
|
102
|
+
way; the metrics bar shows only when the step itself made calls. -->
|
|
103
|
+
<StepModelActivity :metrics="step.metrics" :instance-id="instanceId" />
|
|
104
|
+
</div>
|
|
105
|
+
</template>
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
// `spec/features/*.feature` files would need a spec endpoint (a future enhancement).
|
|
13
13
|
import type { TestConcern, TestOutcome, TestReport } from '~/types/domain'
|
|
14
14
|
import StepRestartControl from '~/components/panels/StepRestartControl.vue'
|
|
15
|
+
import StepRunMeta from '~/components/panels/StepRunMeta.vue'
|
|
15
16
|
|
|
16
17
|
const board = useBoardStore()
|
|
17
18
|
const execution = useExecutionStore()
|
|
@@ -21,9 +22,12 @@ const execution = useExecutionStore()
|
|
|
21
22
|
const { open, blockId, instanceId, stepIndex, close } = useResultView('tester')
|
|
22
23
|
const block = computed(() => (blockId.value ? board.getBlock(blockId.value) : undefined))
|
|
23
24
|
|
|
25
|
+
const instance = computed(() =>
|
|
26
|
+
instanceId.value === null ? null : (execution.getInstance(instanceId.value) ?? null),
|
|
27
|
+
)
|
|
24
28
|
const step = computed(() => {
|
|
25
|
-
if (
|
|
26
|
-
return
|
|
29
|
+
if (instance.value === null || stepIndex.value === null) return null
|
|
30
|
+
return instance.value.steps[stepIndex.value] ?? null
|
|
27
31
|
})
|
|
28
32
|
const report = computed<TestReport | null>(() => step.value?.test?.lastReport ?? null)
|
|
29
33
|
const testState = computed(() => step.value?.test ?? null)
|
|
@@ -385,12 +389,17 @@ const GROUP_STATUS_META: Record<ScenarioGroup['status'], { icon: string; text: s
|
|
|
385
389
|
<p class="text-[12px] capitalize text-slate-300">{{ report.environment }}</p>
|
|
386
390
|
</div>
|
|
387
391
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
392
|
+
<!-- Shared run metadata + embedded observability (model, run id, timing,
|
|
393
|
+
model-activity rollup) — identical to the gate and agent step detail. -->
|
|
394
|
+
<StepRunMeta
|
|
395
|
+
v-if="step"
|
|
396
|
+
:step="step"
|
|
397
|
+
:instance-id="instanceId ?? undefined"
|
|
398
|
+
:step-number="stepIndex === null ? undefined : stepIndex + 1"
|
|
399
|
+
:total-steps="instance?.steps.length"
|
|
400
|
+
:run-failed="instance?.status === 'failed'"
|
|
401
|
+
:failure-at="instance?.failure?.occurredAt"
|
|
402
|
+
/>
|
|
394
403
|
|
|
395
404
|
<p class="mt-auto text-[10px] leading-relaxed text-slate-600">
|
|
396
405
|
Scenarios are the areas the Tester chose to exercise (its spec acceptance scenarios).
|