Search result cards rendered inline, then rescued from the collapsible wrapper

feat-004 makes every `legal_search` and `web_search` result visible as a clickable card in the assistant message regardless of whether Olava synthesizes anything useful. bug-003, 45 minutes later, fixes the UX failure where those cards landed inside the collapsible "Completed in N steps" wrapper and were invisible by default.

searchchat-ui

The backend change adds a ReferenceResult type and threads it through runToolCalls. Both the legal and web search dispatch cases now push one ReferenceResult per result and emit a reference_added SSE event live as results arrive. Those events get pushed into the assistant message's events[] array, so reloading the chat keeps all cards visible.

On the frontend, a new ReferenceBlock component renders title, source label, date, snippet, and a blue dot connector, with a hover state and new-tab navigation on click. The AssistantMessage.tsx event grouper dispatches reference_added events inline between content blocks.

The bug: the grouper treated reference_added as pre-response chatter and accumulated them into PreResponseWrapper alongside other tool events. That wrapper collapses to "Completed in N steps" by default. When Olava under-synthesizes - which it does on multi-source research queries (the motivating test case had Olava emit \n\n three times alongside 11 web references) - the source cards were invisible until the user expanded the disclosure. The fix extends the EventGroup discriminated union with a "reference" kind that breaks out of the pre-wrapper exactly like a "content" event does.

The comment in the diff is worth noting: feat-005's research orchestrator later reverses this - reference cards go back inside the wrapper in research mode because synthesis is reliably non-empty there. If you're importing both features, do them together.

So what Worth importing as a unit if your fork uses small models that occasionally produce empty synthesis. The principle - show every source even when the model goes quiet - is sound, and the `ReferenceBlock` component is self-contained. The bug-003 fix is tightly coupled to how feat-005 later reverses the grouping behavior, so import both or neither if you're taking the full research orchestrator.

View this fork on GitHub →

Spotted something wrong? Or know the PR text has fresher detail than the writeup above?

Commits in this thread

2 commits from nwhitehouse/mike, oldest first. Source extracted verbatim from the harvested git log.

SHA Subject Author Date
e7f59072 [feat-004] References inline display Nick Whitehouse 2026-05-04 ↗ GitHub
commit body
Surfaces every legal_search and web_search result as a clickable card in
the assistant message, decoupling source-link visibility from Olava's
synthesis. Even when the model under-summarises (which it does because
it's a small reasoning model), the user sees every source and can click
through.

Backend
- chatTools.ts: new ReferenceResult type, accumulated by runToolCalls,
  returned alongside docsRead/docsCreated/etc. legal_search and
  web_search dispatch cases push one ReferenceResult per result and
  emit a "reference_added" SSE event for each (live streaming so cards
  appear as soon as fetched).
- runLLMStream's runTools callback iterates references and pushes
  reference_added events into the assistant message's events array, so
  reloading a chat keeps the cards visible.
- Backend AssistantEvent union extended with reference_added shape.

Frontend
- types.ts: AssistantEvent union extended to mirror the backend shape.
- useAssistantChat.ts: handles reference_added SSE events live.
- AssistantMessage.tsx: new ReferenceBlock card component (title +
  source label + date + snippet, blue dot connector, hover state,
  opens URL in new tab). Dispatch case interleaves cards
  chronologically with content and other tool events.

Same card style works for both legal and web sources - feat-005's
multi-pass orchestrator will produce many of these per turn and they'll
all render with one component.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3785d7f2 [bug-003] Render references outside the collapsible step wrapper Nick Whitehouse 2026-05-04 ↗ GitHub
commit body
feat-004's reference_added events were getting grouped into the
PreResponseWrapper alongside other tool chatter, which collapses to
"Completed in N steps" by default. References were invisible until the
user expanded the disclosure - which is exactly backwards: references
ARE the answer when Olava under-synthesises (which it does on
multi-source research queries; see Lawyer Tech Misuse Case test where
Olava emitted only "\n\n" three times alongside 11 web references).

Fix: extend the EventGroup discriminated union with a "reference" kind
that breaks the pre-wrapper just like content does. Reference cards
now render inline at top level, never hidden behind a disclosure.

Verified against the chat that motivated the fix - same 11 references
will now render as cards instead of collapsing into "Completed in 5
steps" wrappers. Synthesis quality (Olava emitting empty content) is a
separate problem addressed by feat-005.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

Capture this thread into my fork

Download a single Markdown prompt that tells Claude how to port every commit above into your working tree — adapting paths and structure to match your repo. Run it via claude -p < capture-thread-123.md from inside the repo you want the changes in.

⬇ Download capture-thread-123.md