Answer wait: one quiet line and the sources arriving, not a five-step panel - #2437
Answer wait: one quiet line and the sources arriving, not a five-step panel#2437BigSimmo wants to merge 8 commits into
Conversation
The answer surface was quietened in #2386 and #2388 — the assistant tile off the turn, one number per claim, the safety rail's colour moved into its own icon, two panels made one. The loading state was not in that pass, so AnswerProgressStepper still owns the four to twelve seconds before every answer with a filled accent panel, a 36px icon tile, a five-circle stepper, four connecting rails, a scrolling ECG trace, a per-second elapsed counter and a Processing details disclosure. It is now the loudest element on the surface, and it is the first thing a reader sees. New study at /mockups/answer-loading-redesign, in four panels. Panel One draws the real AnswerProgressStepper — imported from answer-status.tsx rather than redrawn, so the comparison is against what ships and not against an unflattering approximation. Both densities, counter live. Six observations travel with it, of which the substantive one is the last: the evidence preview already crosses the stream boundary before the prose, trimmed and owner-scoped and consumed by the client today, and the surface currently spends that arrival on a progress bar about the fact that it arrived. Panel Two runs three directions off one shared eleven-beat clock, because a loading state cannot be judged from a still. A scrubs to any beat and pauses. Direction A is what answer-chat-perfected-v2 already drew, held for the whole wait. Direction B adds the sources arriving — each lands in the rail as it is found, carrying a dot rather than a number, because the evidence preview is retrieval-ordered while the final list is rebuilt from what the answer cites, so a number assigned during the wait can end up pointing at a different document. Direction C names each document as it is opened and is drawn to be rejected: retrieval opens far more documents than the answer cites, so a reader who watches six titles go past takes all six to be behind the answer. B is recommended. All three end on the same arrived answer and the same numbered rail, so only the wait is being compared. Panel Three is B at the 68ch desktop measure, with the copy for each beat mapped to the stage that already emits it, and a list of what is deliberately dropped. Panel Four draws the five states the current stepper renders identically — slow, nothing found, assembled without the model, stopped, reduced motion. Design scratch only. No production surface changes: answer-status.tsx still ships the stepper drawn in Panel One, and removing it is the follow-up once a direction is chosen. Verified: tsc --noEmit clean; eslint clean on all three changed files; prettier clean; check:design-system-contract passed (55 components, 76 roots, no ratchet moved); route-reachability, site-map, bundle-budget and calculator-mockup-boundary suites 62 passed (4 files); page rendered at 1440px in Chromium with zero console errors, scrubbed and screenshotted at the searching and arrived beats. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012kHT2YKCNnUrVckTaJW6ga
The intro counted the states that need their own words and then included the arrived state, which is there for comparison rather than as a sixth state. Names it as such instead of miscounting the row. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012kHT2YKCNnUrVckTaJW6ga
… panel Direction B from /mockups/answer-loading-redesign, applied to the live surface after the clinical owner chose it (2026-08-27). `AnswerProgressStepper` is gone. It was a filled accent panel carrying a 36px icon tile, a five-circle stepper with connecting rails, a scrolling ECG trace, a per-second elapsed counter and a Processing details disclosure — roughly 210px of the loudest chrome in the product, occupying the answer's own position for the four to twelve seconds before every answer, on a surface that PRs #2386 and #2388 had just spent their whole argument quietening. It narrated the orchestrator's five stages, which the reader is not operating, and it never showed a single source. `AnswerProgress` replaces it with one status line plus the arriving source rail. The rail is the substantive half. The evidence preview already crossed the stream boundary before the prose — trimmed, owner-scoped, governed, consumed by the client — and was being rendered as a SECOND full panel below the stepper, with its own icon tile, heading and three-column card grid. Two loud blocks in the answer's position, both removed when the answer arrived. It is now a horizontal rail of small cards drawn to look like the source rail the arrived answer renders, mounted inside AnswerProgress rather than beside it, so nothing is removed and nothing jumps when the answer lands. Cards carry a dot, not a number. The preview is the top slice of retrieval in retrieval order; the final list is rebuilt from what the answer cites and re-capped by trust. A number assigned during the wait can point at a different document once the answer lands, which is the precise failure the citation design exists to prevent. Pinned by a DOM test, and stated in the rail's accessible name for a reader who never sees the dot. Checked while building, not assumed: `isDeliverableVerifiedUnit` pins evidence_preview to sequence 0 and rejects a repeat, so the preview crosses once, complete — it cannot accrue card by card. And it is gated behind NEXT_PUBLIC_RAG_INCREMENTAL_EVIDENCE_PREVIEW_RENDER (#100 Phase 1), off by default, so today the line carries the accrual alone via resultCount and the rail is simply absent rather than a placeholder. Improvements beyond the mockup: - Retrieval counts passages and selection counts sources, never one noun for both. Collapsing them is how a reader believes two dozen documents are behind an answer that cites three. Pinned in tests/answer-progress.test.ts. - `fallback` says "Assembling the answer from the sources directly" while it is happening. Twenty of thirty answers in the 2026-08-18 blinded read were source_only; the wait is the honest place to set that expectation. - Processing details became "How this answer was built", shown only after a retrying/fallback/cached run. The old disclosure held the same five stages for every question, which is why nobody opened it. - The elapsed counter is gone. In its place one threshold at 10s appends "taking longer than usual" and does not tick. Nothing can be done with the number while the search is healthy; re-drawing it every second in the position the eye rests on is what made the wait the subject. - SearchProgressBanner (library/document modes) loses its filled accent band and spinning Loader2 for the same line. Fill is how this app marks a hazard. - AnswerSkeleton drops its own status line. Found in the browser, not in a test: it renders in the answer's body slot directly under AnswerProgress, so the screen showed "Writing the answer…" above "Reading your question…". There is now exactly one place that says what is happening. - The completion dot is not green. A status hue that nothing else on the element repeats is a colour-only signal, and it was redundant beside a line already reading "Answer ready in 3s". Caught by check:design-system-contract. Motion. The ECG trace, its two animation tokens, its keyframes and its compositor rules are deleted; the indicator is a 5px dot breathing on opacity. That choice is what holds the contract ui-phone-motion.spec.ts exists for, after Reduce Motion set the trace to opacity 0 and left a dead panel on a physical iPhone: a stopped dot is a complete, correct bullet, where a stopped spinner is a fragment of a circle. The animation stays in globals.css rather than a motion-safe: utility because html[data-motion="full"] must be able to opt back in over the OS, which a Tailwind media variant cannot express. tests/answer-activity-trace-css.test.ts pinned the deleted component, so it is replaced by tests/answer-progress-indicator-css.test.ts carrying the same regression forward against the dot — including an assertion that the trace stays deleted, since a partial revival is how the original defect shipped. docs/search-chrome-behaviour.md's physical-iPhone rubric named the ECG strip by class and would have sent a tester hunting for markup that no longer exists; docs/design-system/COMPONENTS.md named the component in its live-region adoption note. Both updated — the second found by check:dead-code-candidate, which is the gate doing its job. Verified: verify:cheap exit 0 — 37 gates enforced, design-system contract passed (1021 production files, no ratchet moved), 886 test files / 10733 tests passed; Playwright chromium answer-progress-ui-smoke + ui-phone-motion 9 passed, ui-universal-search 20 passed; driven in a real browser at 390px through scoping → retrieving → retrieved(24) → ranking(4 AU, 2 WA) → generating and past the 10s threshold, and again with the preview flag on to confirm six real cards render, link to /documents/<id>?page=&chunk=, and carry no numbers. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012kHT2YKCNnUrVckTaJW6ga
…ion-redesign-zdcba4 # Conflicts: # data/repo-awareness-snapshot.json
…creen
Takes the pending screens from /mockups/answer-chat-perfected-v2 ("Evidence
arrives first") and applies what they get right to the live wait. Three changes,
each a defect in what shipped in the previous commit.
**Order.** The mockup draws status line → prose placeholder → sources, because
that is where each one's finished counterpart lands. The first cut put the rail
directly under the line and left AnswerSkeleton to render below it as a sibling,
so the rail sat ABOVE the prose during the wait and BELOW it after — travelling
the height of the answer at the exact moment the reader was given something to
read. That is the whole "nothing jumps" claim, and it was wrong. AnswerProgress
now owns the wait end to end, ClinicalDashboard stops rendering AnswerSkeleton
beside it, and a Playwright test pins the geometry rather than the markup.
**Counts.** The mockup prints one number and it is the number of cards visible
beneath it. Nothing else. Measured against that rule, both counts I added fail:
`resultCount` is candidate chunks — commonly 24 where the answer cites three —
so a reader who takes "24" away has been told the wrong thing about how much
evidence is behind their answer, whatever noun sat beside it; and
`australianSourceCount` is a ratio (4 of 6) nothing on screen can confirm. The
first is gone. The second survives as the fact without the figure —
"Prioritising Australian sources" — because a Perth reader does care that local
guidance is being favoured, and the per-source origin stays where it can be
checked. `answerProgressPreviewMessage` is now the only place the wait prints a
number, and it counts the rail.
Accrual does not depend on numbers: a healthy wait still moves through four
distinct clauses in about seven seconds, which is what separates working from
stuck.
**Completion.** The mockup's third frame hands the completed state to the
answer's own provenance line and prints nothing else. Production already renders
that line ("AI-generated from N cited sources", clinical owner approved
2026-08-25), so "Answer ready in 3s" underneath it was a competing completion
statement and the last of the elapsed counter. The completed wait now renders no
visible chrome — only the screen-reader announcement, plus the "How this answer
was built" disclosure when the answer left the ordinary route.
Also from the mockup: the rail cards carry review status rather than a section
heading ("p.12 · Current"), read through `sourceStatusShortLabel` so the wait and
the arrived answer can never disagree about a document. Freshness is the fact
that decides whether a source should be trusted at all; a section heading is
orientation the reader gets anyway on opening it.
One consequence worth naming: `compactSourceSnippet` now has no call site,
because the evidence preview was the only surface printing a source snippet and
the rail prints none. The formatter is kept — it is the contract any
reintroduced snippet must go through — and its guard in
rendered-text-formatting.test.ts becomes conditional on `source.content` being
rendered at all, so it fires the moment one of these surfaces touches it again.
The unconditional raw-render half is unchanged and now also covers
`{source.content}`.
Verified: tsc clean; eslint clean; prettier clean; design-system contract passed
(1028 production files, no ratchet moved); full offline suite 895 files / 10841
tests passed; Playwright chromium answer-progress-ui-smoke 5 passed (including
the new geometry test), ui-phone-motion 5 passed, ui-universal-search 20 passed;
driven in a real browser at 390px, where the rendered tops are line 88 → prose
128 with the rail structurally next, and the line reads "Searching your
documents…" then "Writing the answer…" with no digit in either.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012kHT2YKCNnUrVckTaJW6gaYou have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Warning Review limit reachedNext included review available in 55 minutes. View limit detailsLimit details: You’ve used the included review currently available. Your 103 included PR review attempts over the past 7 days set your current allowance at 1 review per hour. Your organization has reached its usage spending cap. Adjust your spending cap in the billing tab. Review configuration: ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (3)
📒 Files selected for processing (21)
Comment |
This pull request has been ignored for the connected project Preview Branches by Supabase. |
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_cf88f30d-7f18-461a-a02f-100e6f61b101) |
Resolves two conflicts: - src/components/clinical-dashboard/answer-status.tsx — main extracted CopyButton into @/components/ui/copy-button (dropping the Clipboard, ClipboardCheck and floatingControl imports) while this branch retired AnswerProgressStepper (dropping Activity, Check, Loader2 and sourceCard). The resolution takes the union of both removals and keeps this branch's useEffect/useState additions. Confirmed by symbol scan that the only remaining Loader2 mentions are prose in comments. - data/repo-awareness-snapshot.json — generated; regenerated with npm run snapshot:repo-awareness rather than hand-merged.
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_dc58b45f-4758-44d6-8c48-2241dee9d3a4) |
Only conflict was the generated data/repo-awareness-snapshot.json, which every concurrent PR touches. Regenerated with npm run snapshot:repo-awareness rather than hand-merged. No code conflict this time.
CI triageCI failed on this PR. Automated classification of the 2 failed job(s):
Compared with main CI run #14346 (success). That run's conclusion is an aggregate and did not exercise Classification is evidence routing, not permission to ignore a failure. Exact quarantined Playwright identities remain governed by the flake ledger. |
Summary
AnswerProgressStepper. It was a filled accent panel carrying a 36px icon tile, a five-circle stepper with connecting rails, a scrolling ECG trace, a per-second elapsed counter and aProcessing detailsdisclosure — roughly 210px of the loudest chrome in the product, occupying the answer's own position for the four to twelve seconds before every answer, on a surface that PRs Answer mockup: take the symbol off the turn, drop the safety rail, and make the two panels one #2386 and Answer page: symbol off, one number per claim, no safety rail, one panel #2388 had just spent their whole argument quietening. It narrated the orchestrator's five stages, which the reader is not operating, and it never showed a single source.AnswerProgress: one status line, a prose placeholder, and the arriving source rail — in that order, because that is where each one's finished counterpart lands. Arrival swaps content in place instead of removing a panel and letting everything below it jump up.AnswerProgress, drawn to look like the source rail the arrived answer renders, because that is what it becomes.isDeliverableVerifiedUnitpinsevidence_previewto sequence 0 and rejects a repeat, so the preview crosses once and complete — it cannot accrue card by card. More importantly the preview is the top slice of retrieval in retrieval order, while the final list is rebuilt from what the answer cites and re-capped by trust, so a number assigned during the wait can point at a different document once the answer lands. Numbering is what arrival buys. Pinned by a DOM test and stated in the rail's accessible name.p.12 · Current), read through the samesourceStatusShortLabelhelper the arrived answer's rail uses, so the wait and the answer can never disagree about a document. Freshness is the fact that decides whether a source should be trusted at all.resultCountis candidate chunks — commonly 24 where the answer cites three — so a reader who takes "24" away has been told the wrong thing about how much evidence is behind their answer, whatever noun sat beside it.australianSourceCountis a ratio nothing on screen can confirm, so it survives as the fact without the figure ("Prioritising Australian sources"). The single count that is printed comes fromanswerProgressPreviewMessageand counts exactly the cards visible beneath the line.fallbackroute says so while it is happening ("Assembling the answer from the sources directly"). Twenty of thirty answers in the 2026-08-18 blinded read weresource_only; the wait is the honest place to set that expectation rather than leaving the answer to defend it.Processing detailsbecameHow this answer was built, shown only after aretrying/fallback/cachedrun. The old disclosure held the same five stages for every question, which is why nobody opened it.SearchProgressBanner(library/document modes) loses its filled accent band and spinningLoader2for the same quiet line. Fill is how this app marks a hazard, and a search in flight is not one.AnswerSkeletondrops its own status line and is no longer rendered besideAnswerProgress. Found in the browser rather than in a test: the screen showed "Writing the answer…" directly above "Reading your question…". There is now exactly one place that says what is happening.ui-phone-motion.spec.tsexists for after Reduce Motion set the trace toopacity: 0and left a dead panel on a physical iPhone. The animation stays inglobals.cssrather than amotion-safe:utility becausehtml[data-motion="full"]must be able to opt back in over the OS, which a Tailwind media variant cannot express./mockups/answer-loading-redesign(three directions, driven from one shared clock, with the states that are not the happy path). Design scratch; 404s in production.The rail degrades to nothing rather than to a placeholder: the preview unit is gated behind
NEXT_PUBLIC_RAG_INCREMENTAL_EVIDENCE_PREVIEW_RENDER(#100Phase 1) and is off by default, so today the line and the prose placeholder are the whole wait. Nothing here invents a source to fill the space.Docs updated where they named the retired markup:
docs/search-chrome-behaviour.md's physical-iPhone acceptance rubric named the ECG strip by class and would have sent a tester hunting for markup that no longer exists;docs/design-system/COMPONENTS.mdnamed the component in its live-region adoption note. The second was found bycheck:dead-code-candidate.tests/answer-activity-trace-css.test.tspinned the deleted component and is replaced bytests/answer-progress-indicator-css.test.ts, which carries the same regression forward against the dot and additionally asserts the trace stays deleted — a partial revival is how the original defect shipped.compactSourceSnippetnow has no call site, because the evidence preview was the only surface printing a source snippet and the rail prints none. The formatter is kept as the contract any reintroduced snippet must go through, and its guard inrendered-text-formatting.test.tsbecomes conditional onsource.contentbeing rendered at all. The unconditional raw-render half is unchanged and now also covers{source.content}.Verification
npm run verify:pr-localDuring development, use
npm run verify:cheapas the faster iteration gate before the final PR-local preflight.npm run verify:uiwhen UI, routing, styling, browser behavior, reduced-motion, or forced-colors behavior changedUI verification not run as the full
verify:uigate: the affected Chromium journeys were run directly instead —tests/answer-progress-ui-smoke.spec.ts5 passed (including a new geometry test pinning line → prose → rail),tests/ui-phone-motion.spec.ts5 passed,tests/ui-universal-search.spec.ts20 passed. Shared search-chrome foundations are untouched by this diff, so the broad gate was not the smallest check capable of detecting a plausible regression here. Chromium evidence does not close the physical Safari or installed-PWA acceptance gap; the updated rubric indocs/search-chrome-behaviour.mdis what covers that.npm run verify:releasebefore release or handoff confidence claimsFor retrieval, ranking, selection, chunking, source/citation rendering, or answer-contract changes,
verify:pr-localrunseval:rag:offlineautomatically. Run the offline command directly during iteration before spending a live eval.npm run eval:retrieval:quality(must stay 36/36) when retrieval, ranking, selection, chunking, or scoring behavior changed — not applicable: no retrieval, ranking, selection, chunking or scoring code is touched. This diff is presentation only.npm run eval:rag -- --limit 15+npm run eval:quality -- --rag-only— not applicable: answer generation, the synthesis prompt and answer post-processing are unchanged. The only answer-path edits are to how progress and preview sources are rendered.npm run check:production-readiness— not run. No clinical workflow, privacy, environment, Supabase, source-governance or deployment behaviour changed; the change is confined to client rendering of the loading state.npm run check:deployment-readiness— not applicable: no deployment startup, hosting or rollout behaviour changed.Full offline suite: 895 test files / 10841 tests passed, 1 skipped. Design-system contract passed (1028 production files; no ratchet moved).
tsc --noEmit,eslintandprettier --checkall clean.Risk and rollout
answer-progress-indicator-css.test.ts, the geometry test inanswer-progress-ui-smoke.spec.ts, and the retargetedui-phone-motion.spec.ts). The residual gap is physical iOS Safari and the installed PWA, which Chromium cannot close.NEXT_PUBLIC_RAG_INCREMENTAL_EVIDENCE_PREVIEW_RENDERis untouched and remains off by default, so the arriving rail is dark in production until it is deliberately enabled.src/lib/rag/, no retrieval RPC, no golden fixture, no ranking or selection module, and no eval harness file is modified. The diff is limited to client components (answer-status.tsx,answer-evidence-preview.tsx,answer-progress.ts,ClinicalDashboard.tsx,DocumentViewer.tsx),globals.css, the tailwind-merge token list, tests, docs and a design-scratch mockup.answer-progress.tsderives display copy from the already-public stage DTO and has never influenced retrieval;answer-progress-public.ts, which owns that boundary, is unchanged.Clinical Governance Preflight
Clinical KB Database(sjrfecxgysukkwxsowpy)trimSourceForClientoutput already validated byisDeliverableVerifiedUnit; this diff renders it and adds nothing to what crosses the boundary.src/app/mockups/**and*-mockups.tsx, which 404 in production, and to test fixtures.sourceStatusShortLabel, the same helper the arrived answer's rail uses, soreview_due,outdatedandunknownare shown rather than silently omitted, and the two surfaces cannot disagree.Notes
/mockups/answer-loading-redesignis kept as the record of the argument, so its Panel One is now a redraw of a component that no longer exists. Its Direction B frames still show the mockup's original conception (cards accruing one at a time, a running count during search); the shipped behaviour differs on both points for the reasons above, and reconciling the scratch page with what landed is worth a follow-up but does not affect production.answerProgressStepIndexis retained and still exported. It is a correct, tested pure function describing the public stage ordering;check:dead-code-candidaterefuses it as pinned by a committed test, which is the gate working as intended.🤖 Generated with Claude Code
https://claude.ai/code/session_012kHT2YKCNnUrVckTaJW6ga
Generated by Claude Code
Note
Low Risk
Presentation-only changes to loading/progress UI with no retrieval or answer-generation path touched; main regression risk is layout, a11y live regions, and reduced-motion visibility, which the updated tests explicitly guard.
Overview
Replaces the answer wait UI — retires
AnswerProgressStepper(accent panel, five-step stepper, ECG trace, elapsed counter, always-on processing details) in favor ofAnswerProgress: a muted status line with a breathing dot, three prose skeleton bars, and an optional horizontal sources rail wired throughClinicalDashboard/DocumentViewer.Evidence preview is folded into that wait, not a sibling panel:
AnswerEvidencePreviewbecomes a scrollable rail (up to six cards) with dots instead of numbers, page + review status, and copy rules inanswer-progress.tsso the line only shows counts the user can match to visible cards. Completion drops visible “answer ready” chrome; “How this answer was built” appears only after retry/fallback/cached runs.Motion/CSS: ECG animations and
.answer-activity-traceare removed;answer-progress-breathon.answer-progress-dotkeeps the indicator visible under reduced motion.SearchProgressBannerandAnswerSkeletonalign with the same quiet pattern (no duplicate status).Adds
/mockups/answer-loading-redesign(design record) and retargets tests (answer-progress-indicator-css, Playwright smoke/phone motion).Reviewed by Cursor Bugbot for commit 15dcd00. Configure here.
Generated by Claude Code