diff --git a/apps/web/src/components/GenerationPreviewStage.module.css b/apps/web/src/components/GenerationPreviewStage.module.css index 258d24549..33213a3f1 100644 --- a/apps/web/src/components/GenerationPreviewStage.module.css +++ b/apps/web/src/components/GenerationPreviewStage.module.css @@ -153,6 +153,20 @@ background: var(--bg-panel); color: var(--text-muted); font-size: 12px; + /* Steps are revealed one at a time as the agent reaches them, so each + pill slides + fades in on mount to make the progression visible. */ + animation: stepReveal 280ms cubic-bezier(0.23, 1, 0.32, 1); +} + +@keyframes stepReveal { + from { + opacity: 0; + transform: translateY(4px) scale(0.96); + } + to { + opacity: 1; + transform: translateY(0) scale(1); + } } .step[data-status='running'] { @@ -207,7 +221,8 @@ .mark[data-active='true'], .progress[data-active='true']::after, .stepDot[data-running='true'], - .lead[data-live='true'] { + .lead[data-live='true'], + .step { animation: none; } } diff --git a/apps/web/src/components/GenerationPreviewStage.tsx b/apps/web/src/components/GenerationPreviewStage.tsx index 3aa6136e2..8649917c6 100644 --- a/apps/web/src/components/GenerationPreviewStage.tsx +++ b/apps/web/src/components/GenerationPreviewStage.tsx @@ -79,7 +79,9 @@ export function GenerationPreviewStage({ model, onRetry }: Props) {
    - {model.steps.map((step) => ( + {model.steps + .filter((step) => step.status !== 'pending') + .map((step) => (
  1. {step.status === 'succeeded' ? ( diff --git a/apps/web/src/runtime/generation-preview.ts b/apps/web/src/runtime/generation-preview.ts index a55a5a01f..21b3fad03 100644 --- a/apps/web/src/runtime/generation-preview.ts +++ b/apps/web/src/runtime/generation-preview.ts @@ -172,7 +172,12 @@ export function derivePrototypeGenerationSteps(input: { let understand: GenerationStepStatus = 'running'; if (input.failed && !hasText && !hasToolUse) { understand = 'failed'; - } else if (hasText || hasStatus(['thinking', 'streaming', 'requesting', 'starting']) || hasToolUse) { + } else if (hasText || hasStatus(['thinking', 'streaming']) || hasToolUse) { + // `requesting`/`starting` only mean the request left the client — the + // model hasn't produced anything yet, so we keep "understand" in + // progress until real thinking/output/tool activity arrives. This lets + // the UI reveal the steps one at a time instead of jumping straight to + // a fully populated row. understand = 'succeeded'; } @@ -241,9 +246,9 @@ function latestActivityLabel(events: AgentEvent[]): string | null { if (event.kind === 'text' && event.text.trim() && !QUESTION_FORM_RE.test(event.text)) { return truncateActivity(event.text); } - if (event.kind === 'status' && event.detail?.trim()) { - return truncateActivity(event.detail); - } + // Intentionally skip `status` details: their payload is often an + // internal identifier (e.g. the model slug from a `requesting` event) + // rather than human-readable progress, so surfacing it reads as noise. } return null; } diff --git a/apps/web/tests/runtime/generation-preview.test.ts b/apps/web/tests/runtime/generation-preview.test.ts index 53fc9c700..a8c047247 100644 --- a/apps/web/tests/runtime/generation-preview.test.ts +++ b/apps/web/tests/runtime/generation-preview.test.ts @@ -46,6 +46,24 @@ describe('generation preview helpers', () => { ]); }); + it('keeps the understand step in progress while the request is still pending', () => { + // `requesting` only means the request left the client; nothing should + // advance past the first step until real model activity arrives, so the + // UI can reveal steps one at a time. + expect( + derivePrototypeGenerationSteps({ + events: [{ kind: 'status', label: 'requesting', detail: 'claude-opus-4-7' }], + hasArtifactHtml: false, + hasPreviewSurface: false, + failed: false, + }), + ).toEqual([ + { id: 'understand', status: 'running' }, + { id: 'generate', status: 'pending' }, + { id: 'prepare', status: 'pending' }, + ]); + }); + it('builds preview state for an active assistant run without an open preview tab', () => { const assistant: ChatMessage = { id: 'a1',