77 * the data: this reads the validated contracts payload, and the demo card stays
88 * where it is as the reviewed reference.
99 *
10+ * The card shows NO spinner of its own, not even while the investigation is
11+ * unfinished. The transcript has exactly one live progress element for the whole
12+ * turn and the card's `progress` phrase is one of the labels it wears (see
13+ * `progress-line.ts`); a row inside the card would be a second spinner, and one
14+ * that restarts its animation on every revision of the card.
15+ *
1016 * An investigation is the one *progressive* block: its `id` is the
1117 * investigationId and its `revision` climbs, so re-emitting it replaces this card
1218 * rather than stacking a second one (see `view-blocks.ts`).
@@ -31,15 +37,14 @@ import type {
3137import { useState } from "react" ;
3238import { Button } from "~/components/primitives/Buttons" ;
3339import { Callout } from "~/components/primitives/Callout" ;
34- import { AgentSpinner } from "~/components/primitives/Spinner" ;
3540import {
3641 CategoryBadge ,
3742 ConfidenceBadge ,
3843 EVIDENCE_ROW_CLASS ,
3944 SeverityBadge ,
4045 VerdictBadge ,
4146} from "./agent-badges" ;
42- import { ChatActionsRow , ChatPendingTool } from "./chat-layout" ;
47+ import { ChatActionsRow } from "./chat-layout" ;
4348import type { ResolvedUri } from "./ReportView" ;
4449
4550const SEVERITY_LABELS : Record < InvestigationSeverity , string > = {
@@ -114,11 +119,13 @@ function HypothesisRow({
114119} ) {
115120 return (
116121 < li className = "space-y-3 border-l-2 border-grid-bright pl-4" >
122+ { /* The "Testing" badge is the whole signal — no spinner beside it. The
123+ transcript's one progress line already says the agent is working, and a
124+ canvas here would restart on every revision of the card. */ }
117125 < div className = "flex flex-wrap items-center gap-2" >
118126 < VerdictBadge verdict = { hypothesis . verdict } >
119127 { VERDICT_LABELS [ hypothesis . verdict ] }
120128 </ VerdictBadge >
121- { hypothesis . verdict === "testing" ? < AgentSpinner size = { 12 } /> : null }
122129 </ div >
123130 < p className = "text-sm text-text-bright" > { hypothesis . statement } </ p >
124131 { hypothesis . finding ? < p className = "text-xs text-text-dimmed" > { hypothesis . finding } </ p > : null }
@@ -191,110 +198,102 @@ export function InvestigationCard({
191198} ) {
192199 const [ expanded , setExpanded ] = useState ( defaultExpanded ) ;
193200 const investigation = block . investigation ;
194- const inProgress = investigation . outcome === "in_progress" ;
195201 const concluded = investigation . outcome === "concluded" ;
196202
197203 return (
198- < div className = "space-y-2" >
199- < div className = "overflow-hidden rounded-lg border border-border-bright bg-background-dimmed" >
200- < div className = "space-y-1.5 border-b border-grid-bright bg-background-bright px-4 py-3" >
201- < div className = "flex flex-wrap items-center gap-2" >
202- < span className = "text-xs font-medium text-text-dimmed" > Investigation</ span >
203- < SeverityBadge severity = { investigation . severity } >
204- { SEVERITY_LABELS [ investigation . severity ] }
205- </ SeverityBadge >
206- < ConfidenceBadge confidence = { investigation . confidence } />
207- </ div >
208- { /* Its own truncating line — the badge row's right corner can't hold a
209- run id reliably at panel width (same rule as RunDiagnosisCard). */ }
210- { investigation . runId ? (
211- < div className = "truncate font-mono text-xs text-text-dimmed" > { investigation . runId } </ div >
212- ) : null }
204+ < div className = "overflow-hidden rounded-lg border border-border-bright bg-background-dimmed" >
205+ < div className = "space-y-1.5 border-b border-grid-bright bg-background-bright px-4 py-3" >
206+ < div className = "flex flex-wrap items-center gap-2" >
207+ < span className = "text-xs font-medium text-text-dimmed" > Investigation</ span >
208+ < SeverityBadge severity = { investigation . severity } >
209+ { SEVERITY_LABELS [ investigation . severity ] }
210+ </ SeverityBadge >
211+ < ConfidenceBadge confidence = { investigation . confidence } />
213212 </ div >
213+ { /* Its own truncating line — the badge row's right corner can't hold a
214+ run id reliably at panel width (same rule as RunDiagnosisCard). */ }
215+ { investigation . runId ? (
216+ < div className = "truncate font-mono text-xs text-text-dimmed" > { investigation . runId } </ div >
217+ ) : null }
218+ </ div >
214219
215- < div className = "space-y-5 px-4 py-4" >
216- < p className = "text-sm font-medium text-text-bright" > { investigation . title } </ p >
220+ < div className = "space-y-5 px-4 py-4" >
221+ < p className = "text-sm font-medium text-text-bright" > { investigation . title } </ p >
217222
218- < Section title = { concluded ? "What happened" : "What we know" } >
219- < p className = "text-sm text-text-dimmed" > { investigation . headline } </ p >
220- </ Section >
223+ < Section title = { concluded ? "What happened" : "What we know" } >
224+ < p className = "text-sm text-text-dimmed" > { investigation . headline } </ p >
225+ </ Section >
221226
222- { /* A fix is only ever shown for a concluded investigation; an
227+ { /* A fix is only ever shown for a concluded investigation; an
223228 inconclusive one gets "What to check next" instead. The schema
224229 enforces the exclusivity, so this can't render both. */ }
225- { concluded && investigation . remediation ? (
226- < Section title = "How to fix" >
227- < p className = "text-sm text-text-dimmed" > { investigation . remediation } </ p >
228- </ Section >
229- ) : null }
230+ { concluded && investigation . remediation ? (
231+ < Section title = "How to fix" >
232+ < p className = "text-sm text-text-dimmed" > { investigation . remediation } </ p >
233+ </ Section >
234+ ) : null }
230235
231- { investigation . checkNext && investigation . checkNext . length > 0 ? (
232- < Section title = "What to check next" >
233- < ol className = "list-decimal space-y-2 pl-5" >
234- { investigation . checkNext . map ( ( item , i ) => (
235- < li key = { i } className = "text-sm text-text-dimmed" >
236- { item }
237- </ li >
238- ) ) }
239- </ ol >
240- </ Section >
241- ) : null }
236+ { investigation . checkNext && investigation . checkNext . length > 0 ? (
237+ < Section title = "What to check next" >
238+ < ol className = "list-decimal space-y-2 pl-5" >
239+ { investigation . checkNext . map ( ( item , i ) => (
240+ < li key = { i } className = "text-sm text-text-dimmed" >
241+ { item }
242+ </ li >
243+ ) ) }
244+ </ ol >
245+ </ Section >
246+ ) : null }
242247
243- { investigation . caveat ? (
244- < Callout variant = "warning" > { investigation . caveat . message } </ Callout >
245- ) : null }
248+ { investigation . caveat ? (
249+ < Callout variant = "warning" > { investigation . caveat . message } </ Callout >
250+ ) : null }
246251
247- < div className = "space-y-4 border-t border-grid-bright pt-4" >
248- < Button
249- variant = "minimal/small"
250- onClick = { ( ) => setExpanded ( ( v ) => ! v ) }
251- LeadingIcon = { expanded ? ChevronDownIcon : ChevronRightIcon }
252- aria-expanded = { expanded }
253- >
254- < span className = "flex items-center gap-1.5 text-xs text-text-dimmed" >
255- { expanded ? "Hide how I worked this out" : "How I worked this out" }
256- < span className = "text-text-faint" >
257- ({ investigation . hypotheses . length } hypothes
258- { investigation . hypotheses . length === 1 ? "is" : "es" } )
259- </ span >
252+ < div className = "space-y-4 border-t border-grid-bright pt-4" >
253+ < Button
254+ variant = "minimal/small"
255+ onClick = { ( ) => setExpanded ( ( v ) => ! v ) }
256+ LeadingIcon = { expanded ? ChevronDownIcon : ChevronRightIcon }
257+ aria-expanded = { expanded }
258+ >
259+ < span className = "flex items-center gap-1.5 text-xs text-text-dimmed" >
260+ { expanded ? "Hide how I worked this out" : "How I worked this out" }
261+ < span className = "text-text-faint" >
262+ ({ investigation . hypotheses . length } hypothes
263+ { investigation . hypotheses . length === 1 ? "is" : "es" } )
260264 </ span >
261- </ Button >
265+ </ span >
266+ </ Button >
267+
268+ { expanded ? (
269+ < div className = "space-y-5 pt-1" >
270+ < Section title = "Hypotheses" >
271+ < ul className = "space-y-5" >
272+ { investigation . hypotheses . map ( ( hypothesis ) => (
273+ < HypothesisRow
274+ key = { hypothesis . id }
275+ hypothesis = { hypothesis }
276+ resolveUri = { resolveUri }
277+ />
278+ ) ) }
279+ </ ul >
280+ </ Section >
262281
263- { expanded ? (
264- < div className = "space-y-5 pt-1" >
265- < Section title = "Hypotheses" >
266- < ul className = "space-y-5" >
267- { investigation . hypotheses . map ( ( hypothesis ) => (
268- < HypothesisRow
269- key = { hypothesis . id }
270- hypothesis = { hypothesis }
271- resolveUri = { resolveUri }
272- />
282+ { investigation . evidence . length > 0 ? (
283+ < Section title = "Evidence" >
284+ < ul className = "space-y-3" >
285+ { investigation . evidence . map ( ( evidence , i ) => (
286+ < EvidenceItem key = { i } evidence = { evidence } resolveUri = { resolveUri } />
273287 ) ) }
274288 </ ul >
275289 </ Section >
276-
277- { investigation . evidence . length > 0 ? (
278- < Section title = "Evidence" >
279- < ul className = "space-y-3" >
280- { investigation . evidence . map ( ( evidence , i ) => (
281- < EvidenceItem key = { i } evidence = { evidence } resolveUri = { resolveUri } />
282- ) ) }
283- </ ul >
284- </ Section >
285- ) : null }
286- </ div >
287- ) : null }
288- </ div >
289-
290- < InvestigationActions actions = { block . capabilities ?. actions ?? [ ] } onIntent = { onIntent } />
290+ ) : null }
291+ </ div >
292+ ) : null }
291293 </ div >
294+
295+ < InvestigationActions actions = { block . capabilities ?. actions ?? [ ] } onIntent = { onIntent } />
292296 </ div >
293- { /* Progress lives outside the card, on the left — the same line the chat
294- uses for in-flight tools — the same pill, so the transcript never
295- shows two spinner styles at once. It carries the transcript's
296- alignment itself, lining up with the card above it. */ }
297- { inProgress ? < ChatPendingTool label = { investigation . progress ?? "Working…" } /> : null }
298297 </ div >
299298 ) ;
300299}
0 commit comments