Skip to content

feat(ui): chat trace state and streaming plumbing (stacked on #331) - #332

Closed
Manushpm8 wants to merge 7 commits into
NVIDIA-AI-Blueprints:developfrom
Manushpm8:feat/ui-chat-plumbing
Closed

feat(ui): chat trace state and streaming plumbing (stacked on #331)#332
Manushpm8 wants to merge 7 commits into
NVIDIA-AI-Blueprints:developfrom
Manushpm8:feat/ui-chat-plumbing

Conversation

@Manushpm8

@Manushpm8 Manushpm8 commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator

Stacked on #331. This branch builds on #331, so until that merges the "Files changed" view here also includes #331's files. Review this PR by its own commit (feat(ui): add chat trace fields, folded-step parser, and sidebar state) or the Commits tab.

What this does

PR 2 of the UI-only chat port. Adds the chat state and streaming plumbing that the visual PR will read. No user-visible change and no predictive/analytical/warehouse features.

  • chat types: optional ThinkingStep fields (completedAt, status, argSummary), ChatMessage.selectedModel, and a plan_approval prompt type.
  • intermediate-step-parser: generic folded reasoning / reflection / explanation sentinels and predicates, splitPayload / extractFoldedOutput, and label lookup via the shared tool-label engine.
  • deep-research-trace (new): replays a stored deep run into the same ThinkingStep stream so a reloaded report shows an identical trace.
  • store + use-websocket-chat: populate the new step fields as the run streams, fail steps on error, and thread the selected model through send.
  • layout store: a sidebar collapse model added alongside the existing panel API, a prompt draft, a selected-model slot, and data-source display helpers.

Preserved (no regression)

Per-user MCP OAuth, WebSocket token rotation, artifact/file handling, and HITL are all untouched. Every existing store/hook/parser test still passes.

Testing

Full frontend suite green (1479 tests, +59 new), including the pre-existing token-rotation and OAuth specs. type-check and lint clean.

Screenshots

This PR is streaming/state plumbing with no UI of its own; it drives the live phased thinking trace (durations, status, response timer) that #333's components render:

Phased thinking trace driven by the streaming plumbing

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Enhanced chat rendering with KaTeX math, Mermaid diagrams from fenced blocks, and answer-mode inline citations with tooltips.
    • Added per-turn model selection (plus a new plan-approval prompt type) and clearer “thinking step” trace folding.
    • Introduced reusable UI primitives for cards, copy-to-clipboard, collapsible sections, and rich sources (list/strip/cards).
  • Bug Fixes

    • Improved thinking-step success/error handling and reduced-motion behavior for trace status indicators.
  • Tests

    • Expanded Vitest coverage across chat tracing, intermediate-step parsing, Markdown/citations, and new UI/source utilities.

@copy-pr-bot

copy-pr-bot Bot commented Jul 14, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

This PR adds model-aware chat sending, richer thinking-step and deep-research trace handling, source-aware Markdown rendering with math and Mermaid support, new research/layout UI primitives, shared source parsing utilities, and frontend styling and utility dependencies.

Changes

Chat flow and research traces

Layer / File(s) Summary
Chat state, model propagation, and trace processing
frontends/ui/src/adapters/api/websocket-client.ts, frontends/ui/src/features/chat/*
Selected models flow through WebSocket sends and persisted messages; thinking steps gain lifecycle metadata, folded payload handling, failure transitions, and deep-research hydration/conversion.
Chat and parser validation
frontends/ui/src/features/chat/**/*.spec.*
Tests cover parser behavior, trace conversion, model persistence, lifecycle transitions, and deep-research hydration.

Answer rendering and sources

Layer / File(s) Summary
Source contracts and preprocessing
frontends/ui/src/shared/components/Sources/*, frontends/ui/src/features/layout/data-sources.*
Source references are normalized, classified, labeled, parsed from trailing reference blocks, and transformed without modifying fenced code.
Markdown and Mermaid rendering
frontends/ui/src/shared/components/MarkdownRenderer/*, frontends/ui/src/shared/components/Mermaid/*
Answer mode adds citations, KaTeX math, Mermaid routing, ordered-list preservation, answer-specific typography, interactive diagrams, and rendering fallbacks.
Source presentation
frontends/ui/src/shared/components/Sources/*, frontends/ui/src/shared/components/Surface/Card.*
Source cards, lists, strips, icons, and citation tooltips expose source metadata and links.

Layout and UI foundations

Layer / File(s) Summary
Layout and research state
frontends/ui/src/features/layout/*, frontends/ui/src/shared/components/research/*
Layout state tracks sidebar collapse, prompt drafts, and selected models; research helpers and components render normalized labels, statuses, tools, and arguments.
Shared primitives and utilities
frontends/ui/src/shared/components/{Actions,CollapsibleBlock}/*, frontends/ui/src/shared/lib/*, frontends/ui/src/app/globals.css, frontends/ui/package.json
Clipboard, collapsible, class-name, humanization, motion, typography, status, tool-row, KaTeX styling, and supporting dependencies are added.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant MarkdownRenderer
  participant SourceParser
  participant Citation
  participant MermaidBlock
  participant NATWebSocketClient
  User->>NATWebSocketClient: submit query with selected model
  NATWebSocketClient-->>User: intermediate trace updates
  User->>SourceParser: process answer references
  SourceParser->>MarkdownRenderer: provide body and SourceRef entries
  MarkdownRenderer->>Citation: render inline citation markers
  MarkdownRenderer->>MermaidBlock: render Mermaid fenced blocks
  MermaidBlock-->>MarkdownRenderer: SVG or code fallback
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is informative, but it omits required template sections like the DCO sign-off, validation checklist, reviewer start point, and related issues. Add the missing template sections, especially the exact DCO sign-off, validation checklist, reviewer start point, and related issues.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title follows Conventional Commits style and accurately summarizes the chat trace/state plumbing changes.
Docstring Coverage ✅ Passed Docstring coverage is 80.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 Biome (2.5.3)
frontends/ui/src/app/globals.css

File contains syntax errors that prevent linting: Line 155: Tailwind-specific syntax is disabled.; Line 168: Tailwind-specific syntax is disabled.; Line 172: Tailwind-specific syntax is disabled.; Line 176: Tailwind-specific syntax is disabled.; Line 180: Tailwind-specific syntax is disabled.; Line 184: Tailwind-specific syntax is disabled.; Line 188: Tailwind-specific syntax is disabled.; Line 192: Tailwind-specific syntax is disabled.; Line 196: Tailwind-specific syntax is disabled.; Line 200: Tailwind-specific syntax is disabled.; Line 208: Tailwind-specific syntax is disabled.; Line 212: Tailwind-specific syntax is disabled.; Line 216: Tailwind-specific syntax is disabled.; Line 220: Tailwind-specific syntax is disabled.; Line 225: Tailwind-specific syntax is disabled.; Line 231: Tailwind-specific syntax is disabled.; Line 235: Tailwind-specific syntax is disabled.; Line 239: Tailwind-specific syntax is disabled.; Line 247: Tailwind-specific syntax is disabled.; Line 251: Tailwind-specific syntax is disabled.; Line 255: Tailwind-specific syntax is disabled.; Line 259: Tailwind-specific syntax is disabled.; Line 263: Tailwind-specific syntax is disabled.; Line 267: Tailwind-specific syntax is disabled.; Line 271: Tailwind-specific syntax is disabled.; Line 275: Tailwind-specific syntax is disabled.; Line 279: Tailwind-specific syntax is disabled.; Line 283: Tailwind-specific syntax is disabled.


Comment @coderabbitai help to get the list of available commands.

@Manushpm8
Manushpm8 marked this pull request as ready for review July 14, 2026 14:15
@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 19

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
frontends/ui/src/features/chat/store.ts (1)

1281-1400: 📐 Maintainability & Code Quality | 🔵 Trivial

failThinkingStep duplicates completeThinkingStep's update logic.

Both actions repeat the same "map ephemeral thinkingSteps → find step → rebuild persisted message.thinkingSteps → set()" sequence, differing only in the literal status ('success' vs 'error'). This pattern is now duplicated across completeThinkingStep, failThinkingStep, appendToThinkingStep, and updateThinkingStepByFunctionName. Consider extracting a shared updateThinkingStepFields(stepId, patch) helper that both ephemeral and persisted updates route through, taking the status/completedAt patch as a parameter.

♻️ Sketch of a shared helper
+        const patchThinkingStep = (
+          stepId: string,
+          patch: Partial<Pick<ThinkingStep, 'isComplete' | 'completedAt' | 'status'>>
+        ) => {
+          const { currentConversation, conversations, thinkingSteps, activeThinkingStepId } = get()
+          const apply = (s: ThinkingStep) => (s.id === stepId ? { ...s, ...patch } : s)
+          const updatedThinkingSteps = thinkingSteps.map(apply)
+          const step = thinkingSteps.find((s) => s.id === stepId)
+          let updatedConversation = currentConversation
+          let updatedConversations = conversations
+          if (step && currentConversation) {
+            const updatedMessages = currentConversation.messages.map((msg) =>
+              msg.id === step.userMessageId && msg.thinkingSteps
+                ? { ...msg, thinkingSteps: msg.thinkingSteps.map(apply) }
+                : msg
+            )
+            updatedConversation = { ...currentConversation, messages: updatedMessages, updatedAt: new Date() }
+            updatedConversations = updateConversationInList(conversations, updatedConversation)
+          }
+          set(
+            {
+              thinkingSteps: updatedThinkingSteps,
+              activeThinkingStepId: activeThinkingStepId === stepId ? null : activeThinkingStepId,
+              currentConversation: updatedConversation,
+              conversations: updatedConversations,
+            },
+            false,
+            'patchThinkingStep'
+          )
+        }

Then completeThinkingStep/failThinkingStep become one-liners calling patchThinkingStep(stepId, { isComplete: true, completedAt: ..., status: ... }).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontends/ui/src/features/chat/store.ts` around lines 1281 - 1400, Extract
the duplicated thinking-step update flow from completeThinkingStep,
failThinkingStep, appendToThinkingStep, and updateThinkingStepByFunctionName
into a shared updateThinkingStepFields or equivalent helper. Have it apply the
supplied patch consistently to both ephemeral thinkingSteps and the matching
persisted message.thinkingSteps, while preserving each action’s success/error
status behavior and existing conversation/set updates.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@frontends/ui/src/app/globals.css`:
- Around line 360-368: Rename the dotBreathe keyframe to a kebab-case name and
update both references to it in the animation declarations, preserving the
existing animation behavior and styling.

In `@frontends/ui/src/features/chat/lib/deep-research-trace.ts`:
- Around line 32-50: Update collapseRepeats so repeated tools steps are compared
with the most recent tools entry rather than only out[out.length - 1]. Skip
agents and other non-tools steps while tracking that prior tool, preserve the
earlier id when merging, and retain all non-tool fold steps in their original
order.

In `@frontends/ui/src/features/chat/lib/intermediate-step-parser.ts`:
- Around line 181-190: Extract the inline model-name hyphen capitalization logic
into a named helper such as capitalizeModelSegment, preserving acronym
uppercasing and the original casing of each word’s tail for tokens like 30B and
A3B. Add a one-line comment documenting this intentional case-preservation
difference from titleCaseWords, then call the helper from the existing
model-name formatting flow.

In `@frontends/ui/src/shared/components/Actions/CopyButton.tsx`:
- Around line 22-33: Update CopyButton’s handleCopy timeout management so only
the latest reset can change copied state: track the pending timeout across
renders, clear any existing timeout before scheduling a new one, and clear the
pending timeout on unmount. Preserve the current copy success and failure
behavior.

In `@frontends/ui/src/shared/components/CollapsibleBlock/CollapsibleBlock.tsx`:
- Around line 1-90: Add a CollapsibleBlock.spec.tsx test suite for the shared
CollapsibleBlock component, covering the controlled open state and onOpenChange
toggle behavior, plus the collapsible={false} static path where the body remains
rendered without a toggle. Follow existing UI component test patterns.
- Around line 60-73: Update CollapsibleBlock so the toggle button and disclosed
body share a generated unique identifier: use React’s useId, assign the
resulting id to the collapsible content region, and set aria-controls on the
button to that id while preserving the existing aria-expanded behavior. Apply
the association only to the collapsible path as appropriate.

In `@frontends/ui/src/shared/components/MarkdownRenderer/Citation.tsx`:
- Around line 32-71: Update the citation trigger and tooltip in the Citation
component so the anchor references the rendered tooltip through aria-describedby
whenever the tooltip is present. Assign a stable unique id to the tooltip, apply
it to the role="tooltip" element, and ensure the reference is omitted when no
source or tooltip is rendered.

In `@frontends/ui/src/shared/components/MarkdownRenderer/MarkdownRenderer.tsx`:
- Around line 9-18: Update MarkdownRenderer to lazy-load rehype-katex and its
KaTeX stylesheet only when math rendering is needed, removing their module-scope
imports while preserving existing math output and Mermaid’s lazy-loading
pattern. Use the renderer’s existing math/answer handling symbols to trigger the
deferred loading.
- Around line 267-283: Memoize the remarkPlugins and rehypePlugins arrays in
MarkdownRenderer, keyed by hasCitations where needed, and pass those stable
references to ReactMarkdown instead of creating literal arrays inline. Keep the
existing plugin contents and citation-dependent behavior unchanged.
- Around line 279-280: Update the rehypePlugins configuration in
MarkdownRenderer so rehypeKatex runs before rehypeCitations, while retaining the
current conditional citation behavior. Add a regression test covering LaTeX with
bracketed arguments such as \sqrt[3]{x} alongside [n]-style text, verifying both
math and citation rendering remain correct.

In
`@frontends/ui/src/shared/components/MarkdownRenderer/rehype-citations.spec.ts`:
- Around line 20-42: Add a regression test in the rehypeCitations suite that
builds a paragraph containing a code element with “[1]”, runs runPlugin, and
asserts the code child remains unchanged without a cite element. Keep the
existing marker-processing tests intact and verify only code-element text is
excluded from citation transformation.

In `@frontends/ui/src/shared/components/MarkdownRenderer/rehype-citations.ts`:
- Around line 17-44: Update the text-node visitor in the citation transformation
to return without rewriting nodes whose direct parent is a code element.
Preserve the existing citation replacement behavior for all other text nodes,
including normal parent and index validation.

In `@frontends/ui/src/shared/components/Mermaid/MermaidBlock.tsx`:
- Around line 68-74: Harden the Mermaid rendering path before its
dangerouslySetInnerHTML assignment by sanitizing the generated SVG with
DOMPurify, while retaining Mermaid’s securityLevel: 'strict' configuration.
Apply this to the SVG injection flow in the Mermaid rendering component,
including the additional affected block, and inject only the sanitized output.
- Around line 183-227: Update the fullscreen container in MermaidBlock to expose
modal semantics with role="dialog" and aria-modal="true" while fullscreen is
active, and manage focus by moving it to the “Exit fullscreen” button when
entering fullscreen. Keep the existing Escape close behavior and restore focus
appropriately when leaving fullscreen.

In `@frontends/ui/src/shared/components/Sources/parse-references.ts`:
- Around line 215-267: Extract the repeated bullet, heading, and ordered-list
detection from tabularizeEntityLines into a local isMarkupLine(line) helper,
then use it for both the initial guard and the entity-run loop condition.
Preserve the existing markup patterns and tabularization behavior.
- Around line 52-61: Update the returned SourceRef object in the reference
parsing flow to preserve the URL for bare web references: when kind is 'web' and
the reference is not a file/document source, set url to ref. Leave document
handling and existing label/kind behavior unchanged.

In `@frontends/ui/src/shared/components/Sources/SourceStrip.tsx`:
- Around line 38-42: External links lack an accessible indication that they open
in a new tab. In SourceStrip.tsx at lines 38-42, add visually hidden text or
update the link aria-label to announce this behavior; make the equivalent change
to the domain link in SourceList.tsx at lines 42-49.
- Around line 74-83: Add an aria-expanded attribute to the expander button in
the hasMore && !expanded block, binding it to the expanded state managed by
setExpanded. Preserve the existing click behavior and label.

In `@frontends/ui/src/shared/lib/motion.spec.tsx`:
- Around line 11-32: Update the AppMotionConfig tests to mock or instrument
MotionConfig and assert its reducedMotion prop: use 'never' when
useReducedMotion returns false and 'always' when it returns true. Keep the
existing child-rendering assertions, and ensure both motion modes verify the
configuration behavior.

---

Outside diff comments:
In `@frontends/ui/src/features/chat/store.ts`:
- Around line 1281-1400: Extract the duplicated thinking-step update flow from
completeThinkingStep, failThinkingStep, appendToThinkingStep, and
updateThinkingStepByFunctionName into a shared updateThinkingStepFields or
equivalent helper. Have it apply the supplied patch consistently to both
ephemeral thinkingSteps and the matching persisted message.thinkingSteps, while
preserving each action’s success/error status behavior and existing
conversation/set updates.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 53539a58-184a-4b02-abe4-f2d831255478

📥 Commits

Reviewing files that changed from the base of the PR and between 34b35f5 and 49bd80c.

⛔ Files ignored due to path filters (1)
  • frontends/ui/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (52)
  • frontends/ui/package.json
  • frontends/ui/src/adapters/api/websocket-client.ts
  • frontends/ui/src/app/globals.css
  • frontends/ui/src/features/chat/hooks/use-websocket-chat.ts
  • frontends/ui/src/features/chat/lib/deep-research-trace.spec.ts
  • frontends/ui/src/features/chat/lib/deep-research-trace.ts
  • frontends/ui/src/features/chat/lib/intermediate-step-parser.spec.ts
  • frontends/ui/src/features/chat/lib/intermediate-step-parser.ts
  • frontends/ui/src/features/chat/store.spec.ts
  • frontends/ui/src/features/chat/store.ts
  • frontends/ui/src/features/chat/types.ts
  • frontends/ui/src/features/layout/data-sources.spec.ts
  • frontends/ui/src/features/layout/data-sources.ts
  • frontends/ui/src/features/layout/store.spec.ts
  • frontends/ui/src/features/layout/store.ts
  • frontends/ui/src/features/layout/types.ts
  • frontends/ui/src/shared/components/Actions/CopyButton.spec.tsx
  • frontends/ui/src/shared/components/Actions/CopyButton.tsx
  • frontends/ui/src/shared/components/CollapsibleBlock/CollapsibleBlock.tsx
  • frontends/ui/src/shared/components/CollapsibleBlock/index.ts
  • frontends/ui/src/shared/components/MarkdownRenderer/Citation.spec.tsx
  • frontends/ui/src/shared/components/MarkdownRenderer/Citation.tsx
  • frontends/ui/src/shared/components/MarkdownRenderer/MarkdownRenderer.spec.tsx
  • frontends/ui/src/shared/components/MarkdownRenderer/MarkdownRenderer.tsx
  • frontends/ui/src/shared/components/MarkdownRenderer/rehype-citations.spec.ts
  • frontends/ui/src/shared/components/MarkdownRenderer/rehype-citations.ts
  • frontends/ui/src/shared/components/MarkdownRenderer/types.ts
  • frontends/ui/src/shared/components/Mermaid/MermaidBlock.spec.tsx
  • frontends/ui/src/shared/components/Mermaid/MermaidBlock.tsx
  • frontends/ui/src/shared/components/Mermaid/index.ts
  • frontends/ui/src/shared/components/Sources/SourceKindIcon.tsx
  • frontends/ui/src/shared/components/Sources/SourceList.tsx
  • frontends/ui/src/shared/components/Sources/SourceStrip.spec.tsx
  • frontends/ui/src/shared/components/Sources/SourceStrip.tsx
  • frontends/ui/src/shared/components/Sources/parse-references.spec.ts
  • frontends/ui/src/shared/components/Sources/parse-references.ts
  • frontends/ui/src/shared/components/Sources/source-utils.spec.ts
  • frontends/ui/src/shared/components/Sources/source-utils.ts
  • frontends/ui/src/shared/components/Sources/types.ts
  • frontends/ui/src/shared/components/Surface/Card.spec.tsx
  • frontends/ui/src/shared/components/Surface/Card.tsx
  • frontends/ui/src/shared/components/research/StatusDot.tsx
  • frontends/ui/src/shared/components/research/ToolCallRow.tsx
  • frontends/ui/src/shared/components/research/index.ts
  • frontends/ui/src/shared/components/research/research-labels.spec.ts
  • frontends/ui/src/shared/components/research/research-labels.ts
  • frontends/ui/src/shared/lib/cn.spec.ts
  • frontends/ui/src/shared/lib/cn.ts
  • frontends/ui/src/shared/lib/humanize.spec.ts
  • frontends/ui/src/shared/lib/humanize.ts
  • frontends/ui/src/shared/lib/motion.spec.tsx
  • frontends/ui/src/shared/lib/motion.tsx
📜 Review details
🧰 Additional context used
📓 Path-based instructions (3)
frontends/ui/**/*.{js,ts,jsx,tsx,vue}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Run npm lint, type-check, and build validation for UI changes in frontends/ui

Files:

  • frontends/ui/src/shared/lib/humanize.spec.ts
  • frontends/ui/src/shared/components/Sources/SourceKindIcon.tsx
  • frontends/ui/src/shared/lib/motion.spec.tsx
  • frontends/ui/src/shared/components/Sources/SourceStrip.tsx
  • frontends/ui/src/shared/components/Sources/SourceStrip.spec.tsx
  • frontends/ui/src/shared/lib/cn.ts
  • frontends/ui/src/shared/components/Mermaid/index.ts
  • frontends/ui/src/shared/components/Sources/types.ts
  • frontends/ui/src/shared/components/Sources/SourceList.tsx
  • frontends/ui/src/shared/lib/motion.tsx
  • frontends/ui/src/shared/components/CollapsibleBlock/CollapsibleBlock.tsx
  • frontends/ui/src/shared/components/Actions/CopyButton.spec.tsx
  • frontends/ui/src/shared/components/MarkdownRenderer/Citation.tsx
  • frontends/ui/src/shared/components/CollapsibleBlock/index.ts
  • frontends/ui/src/shared/components/research/StatusDot.tsx
  • frontends/ui/src/shared/components/MarkdownRenderer/rehype-citations.ts
  • frontends/ui/src/adapters/api/websocket-client.ts
  • frontends/ui/src/shared/components/Surface/Card.tsx
  • frontends/ui/src/shared/components/MarkdownRenderer/types.ts
  • frontends/ui/src/features/layout/data-sources.spec.ts
  • frontends/ui/src/shared/components/Sources/source-utils.spec.ts
  • frontends/ui/src/shared/components/research/research-labels.spec.ts
  • frontends/ui/src/shared/components/research/index.ts
  • frontends/ui/src/shared/components/Actions/CopyButton.tsx
  • frontends/ui/src/shared/components/Mermaid/MermaidBlock.spec.tsx
  • frontends/ui/src/shared/lib/humanize.ts
  • frontends/ui/src/shared/components/MarkdownRenderer/rehype-citations.spec.ts
  • frontends/ui/src/shared/components/Sources/source-utils.ts
  • frontends/ui/src/shared/components/Surface/Card.spec.tsx
  • frontends/ui/src/features/layout/store.spec.ts
  • frontends/ui/src/shared/components/MarkdownRenderer/MarkdownRenderer.spec.tsx
  • frontends/ui/src/shared/lib/cn.spec.ts
  • frontends/ui/src/shared/components/research/ToolCallRow.tsx
  • frontends/ui/src/shared/components/Sources/parse-references.spec.ts
  • frontends/ui/src/shared/components/MarkdownRenderer/Citation.spec.tsx
  • frontends/ui/src/features/chat/lib/deep-research-trace.ts
  • frontends/ui/src/features/chat/lib/deep-research-trace.spec.ts
  • frontends/ui/src/features/layout/data-sources.ts
  • frontends/ui/src/shared/components/Mermaid/MermaidBlock.tsx
  • frontends/ui/src/shared/components/research/research-labels.ts
  • frontends/ui/src/features/chat/lib/intermediate-step-parser.spec.ts
  • frontends/ui/src/features/chat/store.spec.ts
  • frontends/ui/src/shared/components/Sources/parse-references.ts
  • frontends/ui/src/features/chat/types.ts
  • frontends/ui/src/features/layout/store.ts
  • frontends/ui/src/features/layout/types.ts
  • frontends/ui/src/features/chat/store.ts
  • frontends/ui/src/features/chat/hooks/use-websocket-chat.ts
  • frontends/ui/src/shared/components/MarkdownRenderer/MarkdownRenderer.tsx
  • frontends/ui/src/features/chat/lib/intermediate-step-parser.ts
frontends/ui/**/*.{ts,tsx,jsx,js}

📄 CodeRabbit inference engine (AGENTS.md)

frontends/ui/**/*.{ts,tsx,jsx,js}: The UI is built with Next.js / React / TypeScript / Tailwind with KUI components; reuse existing KUI components and visual patterns rather than introducing new ones
Validate UI-affecting changes with npm run lint, npm run type-check, and npm run test:ci, and include a screenshot for visible changes

Files:

  • frontends/ui/src/shared/lib/humanize.spec.ts
  • frontends/ui/src/shared/components/Sources/SourceKindIcon.tsx
  • frontends/ui/src/shared/lib/motion.spec.tsx
  • frontends/ui/src/shared/components/Sources/SourceStrip.tsx
  • frontends/ui/src/shared/components/Sources/SourceStrip.spec.tsx
  • frontends/ui/src/shared/lib/cn.ts
  • frontends/ui/src/shared/components/Mermaid/index.ts
  • frontends/ui/src/shared/components/Sources/types.ts
  • frontends/ui/src/shared/components/Sources/SourceList.tsx
  • frontends/ui/src/shared/lib/motion.tsx
  • frontends/ui/src/shared/components/CollapsibleBlock/CollapsibleBlock.tsx
  • frontends/ui/src/shared/components/Actions/CopyButton.spec.tsx
  • frontends/ui/src/shared/components/MarkdownRenderer/Citation.tsx
  • frontends/ui/src/shared/components/CollapsibleBlock/index.ts
  • frontends/ui/src/shared/components/research/StatusDot.tsx
  • frontends/ui/src/shared/components/MarkdownRenderer/rehype-citations.ts
  • frontends/ui/src/adapters/api/websocket-client.ts
  • frontends/ui/src/shared/components/Surface/Card.tsx
  • frontends/ui/src/shared/components/MarkdownRenderer/types.ts
  • frontends/ui/src/features/layout/data-sources.spec.ts
  • frontends/ui/src/shared/components/Sources/source-utils.spec.ts
  • frontends/ui/src/shared/components/research/research-labels.spec.ts
  • frontends/ui/src/shared/components/research/index.ts
  • frontends/ui/src/shared/components/Actions/CopyButton.tsx
  • frontends/ui/src/shared/components/Mermaid/MermaidBlock.spec.tsx
  • frontends/ui/src/shared/lib/humanize.ts
  • frontends/ui/src/shared/components/MarkdownRenderer/rehype-citations.spec.ts
  • frontends/ui/src/shared/components/Sources/source-utils.ts
  • frontends/ui/src/shared/components/Surface/Card.spec.tsx
  • frontends/ui/src/features/layout/store.spec.ts
  • frontends/ui/src/shared/components/MarkdownRenderer/MarkdownRenderer.spec.tsx
  • frontends/ui/src/shared/lib/cn.spec.ts
  • frontends/ui/src/shared/components/research/ToolCallRow.tsx
  • frontends/ui/src/shared/components/Sources/parse-references.spec.ts
  • frontends/ui/src/shared/components/MarkdownRenderer/Citation.spec.tsx
  • frontends/ui/src/features/chat/lib/deep-research-trace.ts
  • frontends/ui/src/features/chat/lib/deep-research-trace.spec.ts
  • frontends/ui/src/features/layout/data-sources.ts
  • frontends/ui/src/shared/components/Mermaid/MermaidBlock.tsx
  • frontends/ui/src/shared/components/research/research-labels.ts
  • frontends/ui/src/features/chat/lib/intermediate-step-parser.spec.ts
  • frontends/ui/src/features/chat/store.spec.ts
  • frontends/ui/src/shared/components/Sources/parse-references.ts
  • frontends/ui/src/features/chat/types.ts
  • frontends/ui/src/features/layout/store.ts
  • frontends/ui/src/features/layout/types.ts
  • frontends/ui/src/features/chat/store.ts
  • frontends/ui/src/features/chat/hooks/use-websocket-chat.ts
  • frontends/ui/src/shared/components/MarkdownRenderer/MarkdownRenderer.tsx
  • frontends/ui/src/features/chat/lib/intermediate-step-parser.ts
frontends/ui/**/*

⚙️ CodeRabbit configuration file

frontends/ui/**/*: Review UI changes for strict TypeScript behavior, API contract alignment, auth/session handling, accessible controls,
resilient loading and error states, and report/chat state consistency. Prefer existing UI patterns and require tests
for changed user-visible workflows.

Files:

  • frontends/ui/src/shared/lib/humanize.spec.ts
  • frontends/ui/src/shared/components/Sources/SourceKindIcon.tsx
  • frontends/ui/src/shared/lib/motion.spec.tsx
  • frontends/ui/src/shared/components/Sources/SourceStrip.tsx
  • frontends/ui/src/shared/components/Sources/SourceStrip.spec.tsx
  • frontends/ui/src/shared/lib/cn.ts
  • frontends/ui/src/shared/components/Mermaid/index.ts
  • frontends/ui/src/shared/components/Sources/types.ts
  • frontends/ui/src/shared/components/Sources/SourceList.tsx
  • frontends/ui/src/shared/lib/motion.tsx
  • frontends/ui/src/shared/components/CollapsibleBlock/CollapsibleBlock.tsx
  • frontends/ui/src/shared/components/Actions/CopyButton.spec.tsx
  • frontends/ui/src/shared/components/MarkdownRenderer/Citation.tsx
  • frontends/ui/src/shared/components/CollapsibleBlock/index.ts
  • frontends/ui/src/shared/components/research/StatusDot.tsx
  • frontends/ui/src/shared/components/MarkdownRenderer/rehype-citations.ts
  • frontends/ui/src/adapters/api/websocket-client.ts
  • frontends/ui/src/shared/components/Surface/Card.tsx
  • frontends/ui/src/shared/components/MarkdownRenderer/types.ts
  • frontends/ui/src/features/layout/data-sources.spec.ts
  • frontends/ui/src/shared/components/Sources/source-utils.spec.ts
  • frontends/ui/src/shared/components/research/research-labels.spec.ts
  • frontends/ui/src/shared/components/research/index.ts
  • frontends/ui/src/shared/components/Actions/CopyButton.tsx
  • frontends/ui/package.json
  • frontends/ui/src/shared/components/Mermaid/MermaidBlock.spec.tsx
  • frontends/ui/src/shared/lib/humanize.ts
  • frontends/ui/src/shared/components/MarkdownRenderer/rehype-citations.spec.ts
  • frontends/ui/src/shared/components/Sources/source-utils.ts
  • frontends/ui/src/shared/components/Surface/Card.spec.tsx
  • frontends/ui/src/features/layout/store.spec.ts
  • frontends/ui/src/shared/components/MarkdownRenderer/MarkdownRenderer.spec.tsx
  • frontends/ui/src/shared/lib/cn.spec.ts
  • frontends/ui/src/shared/components/research/ToolCallRow.tsx
  • frontends/ui/src/shared/components/Sources/parse-references.spec.ts
  • frontends/ui/src/shared/components/MarkdownRenderer/Citation.spec.tsx
  • frontends/ui/src/features/chat/lib/deep-research-trace.ts
  • frontends/ui/src/features/chat/lib/deep-research-trace.spec.ts
  • frontends/ui/src/features/layout/data-sources.ts
  • frontends/ui/src/shared/components/Mermaid/MermaidBlock.tsx
  • frontends/ui/src/shared/components/research/research-labels.ts
  • frontends/ui/src/features/chat/lib/intermediate-step-parser.spec.ts
  • frontends/ui/src/features/chat/store.spec.ts
  • frontends/ui/src/shared/components/Sources/parse-references.ts
  • frontends/ui/src/features/chat/types.ts
  • frontends/ui/src/features/layout/store.ts
  • frontends/ui/src/features/layout/types.ts
  • frontends/ui/src/features/chat/store.ts
  • frontends/ui/src/features/chat/hooks/use-websocket-chat.ts
  • frontends/ui/src/app/globals.css
  • frontends/ui/src/shared/components/MarkdownRenderer/MarkdownRenderer.tsx
  • frontends/ui/src/features/chat/lib/intermediate-step-parser.ts
🪛 ast-grep (0.44.1)
frontends/ui/src/shared/components/Mermaid/MermaidBlock.tsx

[warning] 251-251: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation

(react-unsafe-html-injection)

frontends/ui/src/shared/components/research/research-labels.ts

[warning] 167-167: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(["']${escapedKey}["']\\s*:\\s*"((?:\\\\.|[^"\\\\])*)")
Note: [CWE-1333] Inefficient Regular Expression Complexity

(regexp-from-variable)


[warning] 172-172: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(["']${escapedKey}["']\\s*:\\s*'((?:\\\\.|[^'\\\\])*)')
Note: [CWE-1333] Inefficient Regular Expression Complexity

(regexp-from-variable)

🪛 OpenGrep (1.25.0)
frontends/ui/src/shared/components/MarkdownRenderer/rehype-citations.ts

[ERROR] 24-24: Dynamic command passed to child_process.exec/execSync. Use child_process.execFile or spawn with an argument array instead.

(coderabbit.command-injection.exec-js)

frontends/ui/src/shared/components/Mermaid/MermaidBlock.tsx

[WARNING] 243-253: dangerouslySetInnerHTML with dynamic content can lead to XSS. Sanitize the input with a library like DOMPurify before rendering.

(coderabbit.xss.react-dangerously-set-innerhtml)

🪛 Stylelint (17.14.0)
frontends/ui/src/app/globals.css

[error] 360-360: Expected keyframe name "dotBreathe" to be kebab-case (keyframes-name-pattern)

(keyframes-name-pattern)

🔇 Additional comments (55)
frontends/ui/src/shared/lib/cn.ts (1)

4-12: LGTM!

frontends/ui/src/shared/lib/cn.spec.ts (1)

4-20: LGTM!

frontends/ui/src/shared/lib/humanize.ts (1)

4-26: LGTM!

frontends/ui/src/shared/lib/humanize.spec.ts (1)

4-27: LGTM!

frontends/ui/src/shared/lib/motion.tsx (1)

14-17: 🎯 Functional Correctness

No change needed useReducedMotion() is intentionally false on the first render for SSR consistency, so AppMotionConfig doesn’t introduce a reduced-motion regression.

			> Likely an incorrect or invalid review comment.
frontends/ui/src/shared/components/Surface/Card.tsx (2)

1-40: LGTM!


31-35: 📐 Maintainability & Code Quality

Provide the required UI validation evidence.

The supplied context reports lint, type-check, and test success, but not the required frontend build validation or screenshot for the rendered SourceCard styling. Please include evidence for npm run lint, npm run type-check, npm run test:ci, the UI build, and a screenshot of the affected card.

Sources: Coding guidelines, Path instructions

frontends/ui/src/shared/components/Surface/Card.spec.tsx (1)

1-45: LGTM!

frontends/ui/src/features/chat/lib/intermediate-step-parser.ts (5)

98-106: LGTM!


126-149: LGTM!


192-202: LGTM!


213-229: LGTM!


231-237: 🩺 Stability & Availability

Folded text may blank on partial frames. extractFoldedOutput() returns '' when a folded step has **Function Input:** but no **Function Output:**, and use-websocket-chat.ts applies it to every structured intermediate update. If the backend can emit those frames before completion, preserve the existing text until the output arrives.

frontends/ui/src/features/chat/lib/intermediate-step-parser.spec.ts (1)

1-119: LGTM!

frontends/ui/src/features/chat/store.spec.ts (3)

586-605: LGTM!


1279-1355: LGTM!


2285-2347: LGTM!

frontends/ui/src/shared/components/Sources/types.ts (1)

1-22: LGTM!

frontends/ui/src/shared/components/Sources/source-utils.ts (1)

1-58: LGTM!

frontends/ui/src/features/layout/data-sources.ts (1)

11-124: LGTM!

frontends/ui/src/shared/components/Sources/source-utils.spec.ts (1)

1-58: LGTM!

frontends/ui/src/features/layout/data-sources.spec.ts (1)

1-45: LGTM!

frontends/ui/src/shared/components/Sources/parse-references.spec.ts (1)

1-204: LGTM!

frontends/ui/src/shared/components/Sources/parse-references.ts (1)

12-13: 🎯 Functional Correctness

No change needed here. splitReferences is meant to parse only the backend-baked structured block, while stripTrailingReferences strips any trailing Sources/References section without parsing it. The broader matcher there is intentional, so widening REFERENCES_BLOCK_RE is not required.

			> Likely an incorrect or invalid review comment.
frontends/ui/package.json (2)

27-32: LGTM!

Also applies to: 34-34, 37-43


33-36: 🗄️ Data Integrity & Integration

React 18 is compatible with Next 16 here
next@16.2.6 accepts react/react-dom ^18.2.0, and the lockfile already resolves to React 18.3.1, so this pin is not a build/install blocker.

			> Likely an incorrect or invalid review comment.
frontends/ui/src/app/globals.css (1)

315-359: LGTM!

Also applies to: 369-383, 386-394, 397-434, 436-441

frontends/ui/src/shared/components/Actions/CopyButton.spec.tsx (1)

1-36: LGTM!

frontends/ui/src/shared/components/CollapsibleBlock/index.ts (1)

1-5: LGTM!

frontends/ui/src/features/layout/types.ts (1)

28-34: LGTM!

Also applies to: 53-56, 73-76, 91-94

frontends/ui/src/features/layout/store.ts (2)

25-26: LGTM!

Also applies to: 36-37, 58-99


126-131: 🎯 Functional Correctness

No action — promptDraft isn’t part of the chat session lifecycle. It lives in frontends/ui/src/features/layout/store.ts and isn’t reset by createConversation or startNewSessionDraft, so this doesn’t indicate a conversation-to-conversation leak.

			> Likely an incorrect or invalid review comment.
frontends/ui/src/features/layout/store.spec.ts (2)

21-22: LGTM!

Also applies to: 32-33, 188-204


139-186: 🎯 Functional Correctness | ⚡ Quick win

Missing test: restoring via openRightPanel switch, not just closeRightPanel.

openRightPanel's !collapsesSidebar && state.sessionsAutoCollapsed branch (restoring the sidebar when switching directly to a non-collapsing panel, e.g. from 'research' to 'settings', without going through closeRightPanel) isn't covered here.

✅ Suggested additional test
     test('does not auto-restore a sidebar the user collapsed manually', () => {
       useLayoutStore.setState({ sessionsCollapsed: true, sessionsAutoCollapsed: false, rightPanel: 'research' })

       useLayoutStore.getState().closeRightPanel()

       expect(useLayoutStore.getState().sessionsCollapsed).toBe(true)
     })
+
+    test('switching to a non-collapsing panel restores an auto-collapsed sidebar', () => {
+      useLayoutStore.setState({ sessionsCollapsed: false, rightPanel: null })
+      useLayoutStore.getState().openRightPanel('research')
+
+      useLayoutStore.getState().openRightPanel('settings')
+
+      expect(useLayoutStore.getState().rightPanel).toBe('settings')
+      expect(useLayoutStore.getState().sessionsCollapsed).toBe(false)
+      expect(useLayoutStore.getState().sessionsAutoCollapsed).toBe(false)
+    })
   })
frontends/ui/src/shared/components/research/research-labels.ts (1)

1-245: LGTM!

frontends/ui/src/shared/components/research/research-labels.spec.ts (1)

1-113: LGTM!

frontends/ui/src/shared/components/research/StatusDot.tsx (1)

1-20: LGTM!

frontends/ui/src/shared/components/research/ToolCallRow.tsx (1)

1-51: LGTM!

frontends/ui/src/shared/components/research/index.ts (1)

1-19: LGTM!

frontends/ui/src/shared/components/MarkdownRenderer/types.ts (1)

4-5: LGTM!

Also applies to: 20-27

frontends/ui/src/shared/components/MarkdownRenderer/MarkdownRenderer.tsx (1)

63-65: Answer-mode citation/paragraph/list rendering looks correct.

cite resolution, isAnswer-gated p/li styling, and the ol start forwarding fix are all sound and match the added test coverage.

Also applies to: 146-177

frontends/ui/src/shared/components/MarkdownRenderer/MarkdownRenderer.spec.tsx (1)

352-429: LGTM!

frontends/ui/src/shared/components/Mermaid/MermaidBlock.tsx (1)

40-166: Zoom/pan/theme-tracking logic is solid.

Fit-to-width, wheel-to-cursor zoom with scroll anchoring, pointer-capture-based drag, and the nv-dark MutationObserver are all well-implemented and match the test expectations in MermaidBlock.spec.tsx.

frontends/ui/src/shared/components/Mermaid/index.ts (1)

4-4: LGTM!

frontends/ui/src/shared/components/Mermaid/MermaidBlock.spec.tsx (1)

1-113: LGTM!

frontends/ui/src/shared/components/Sources/SourceKindIcon.tsx (1)

17-33: LGTM!

frontends/ui/src/shared/components/Sources/SourceStrip.spec.tsx (1)

19-44: LGTM!

frontends/ui/src/shared/components/MarkdownRenderer/Citation.spec.tsx (1)

20-59: LGTM!

frontends/ui/src/shared/components/Sources/SourceStrip.tsx (1)

22-26: 🎯 Functional Correctness

tone={source.kind} is supported
CardTone already includes both web and doc, so this prop value matches the component contract.

			> Likely an incorrect or invalid review comment.
frontends/ui/src/features/chat/types.ts (1)

64-64: LGTM!

Also applies to: 185-186, 219-226, 470-470, 502-503, 642-643

frontends/ui/src/features/chat/store.ts (2)

750-782: LGTM!


2406-2442: 🗄️ Data Integrity & Integration

hydrateDeepResearchFromMessage is test-only; this fallback does not affect runtime chat state. The production load path already scopes deep-research jobs through deepResearchOwnerConversationId/currentConversation, so there’s no cross-conversation overwrite here.

			> Likely an incorrect or invalid review comment.
frontends/ui/src/adapters/api/websocket-client.ts (1)

228-240: LGTM!

frontends/ui/src/features/chat/hooks/use-websocket-chat.ts (1)

58-62: LGTM!

Also applies to: 109-109, 419-419, 595-609, 844-860, 875-876, 1102-1102, 1224-1224, 1250-1250

frontends/ui/src/features/chat/lib/deep-research-trace.spec.ts (1)

1-151: LGTM!

Comment thread frontends/ui/src/app/globals.css Outdated
Comment thread frontends/ui/src/features/chat/lib/deep-research-trace.ts
Comment thread frontends/ui/src/features/chat/lib/intermediate-step-parser.ts
Comment thread frontends/ui/src/shared/components/Actions/CopyButton.tsx
Comment thread frontends/ui/src/shared/components/Sources/parse-references.ts
Comment thread frontends/ui/src/shared/components/Sources/parse-references.ts
Comment thread frontends/ui/src/shared/components/Sources/SourceStrip.tsx
Comment thread frontends/ui/src/shared/components/Sources/SourceStrip.tsx Outdated
Comment thread frontends/ui/src/shared/lib/motion.spec.tsx
@Manushpm8
Manushpm8 force-pushed the feat/ui-chat-plumbing branch from 49bd80c to 6abdf1a Compare July 14, 2026 17:15

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
frontends/ui/src/shared/components/MarkdownRenderer/Citation.tsx (1)

33-43: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Avoid dead tab stops for missing citations.

When src is absent, this expression sets tabIndex={0}, but the component returns the chip without any tooltip or action. Unresolved citation markers therefore become focusable but inert. Restrict this to src && !src.url, or render unresolved markers non-focusable, and add a regression test.

As per path instructions, “Review UI changes for strict TypeScript behavior, API contract alignment, auth/session handling, accessible controls, resilient loading and error states, and report/chat state consistency.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontends/ui/src/shared/components/MarkdownRenderer/Citation.tsx` around
lines 33 - 43, Update the Citation component’s anchor tabIndex logic so
unresolved citations without src are not focusable; only citations with a valid
src but no URL should receive a keyboard tab stop, while URL citations retain
current behavior. Add a regression test covering the missing-src case and verify
strict TypeScript and accessibility behavior.

Source: Path instructions

frontends/ui/src/shared/components/Sources/source-utils.ts (1)

43-58: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add regression coverage for the changed source and citation transformations.

The adjacent specs cover helper happy paths and normal paragraph citations, but not these changed branches:

  • frontends/ui/src/shared/components/Sources/source-utils.ts#L43-L58: test that HTTP/HTTPS URLs are retained and non-web references omit url.
  • frontends/ui/src/shared/components/MarkdownRenderer/rehype-citations.ts#L17-L20: test that [1] remains literal text inside inline/fenced code while normal text still creates <cite> elements.

As per path instructions, changed user-visible workflows require tests.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontends/ui/src/shared/components/Sources/source-utils.ts` around lines 43 -
58, Add regression tests for mapCitationSource in
frontends/ui/src/shared/components/Sources/source-utils.ts: verify HTTP/HTTPS
sources retain url while non-web references leave url undefined. Also add tests
for the citation transformation in
frontends/ui/src/shared/components/MarkdownRenderer/rehype-citations.ts,
confirming [1] remains literal inside inline and fenced code while normal text
still produces cite elements.

Source: Path instructions

frontends/ui/src/features/chat/hooks/use-websocket-chat.ts (1)

849-876: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Treat terminal NAT statuses as terminal even without a “Function Complete” name.

Line 856 only finalizes an existing step when parseFunctionName() sets isComplete. A frame with status === 'error' or 'complete' and a non-completion name remains active; a newly created error step similarly stores isComplete: false.

Derive one terminal flag from both signals and invoke failThinkingStep/completeThinkingStep for newly created terminal steps as well. Add coverage for an error-status frame whose name is not Function Complete.

As per path instructions, review UI changes for “resilient loading and error states” and “report/chat state consistency.”
As per coding guidelines, validate with npm run lint, npm run type-check, and npm run test:ci, and include a screenshot for visible changes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontends/ui/src/features/chat/hooks/use-websocket-chat.ts` around lines 849
- 876, Derive a terminal flag in the websocket frame handling flow from either
isComplete or status === 'error'/'complete', then use it for existing-step
finalization and the created step’s isComplete value. Ensure newly created
terminal steps immediately invoke failThinkingStep or completeThinkingStep after
addThinkingStep, while non-terminal steps retain the running behavior. Add
coverage for an error-status frame with a non-“Function Complete” function name.

Sources: Coding guidelines, Path instructions

frontends/ui/src/shared/components/Sources/SourceStrip.tsx (1)

74-83: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Keep the disclosure control mounted while expanded.

Because the button is rendered only under !expanded, it disappears immediately after activation. Consequently, assistive technology never observes aria-expanded="true" and focus is removed from the activated control. Keep a stable toggle, associate it with the source container via aria-controls, and support collapse or explicitly move focus to the revealed content.

As per path instructions, review UI changes for “accessible controls.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontends/ui/src/shared/components/Sources/SourceStrip.tsx` around lines 74 -
83, Update the disclosure control in SourceStrip so it remains mounted in both
collapsed and expanded states, with aria-expanded reflecting expanded and
aria-controls referencing the source container. Change its action and label to
support collapsing after expansion, and ensure focus remains on the stable
toggle or is intentionally moved to the revealed content.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@frontends/ui/src/features/chat/lib/intermediate-step-parser.ts`:
- Around line 99-104: Update the name handling in the intermediate-step
classification logic before the tool/function prefix checks: trim surrounding
whitespace and normalize case, then use that normalized value to exclude
Function and Tool: prefixes before slash-based or model-name classification. Add
coverage in intermediate-step-parser.spec.ts for lowercase tool:web/search and
whitespace-prefixed Tool: web/search inputs, ensuring both are identified as
tools rather than LLMs.

In `@frontends/ui/src/features/chat/store.ts`:
- Around line 2408-2419: Update findInConversation in the deep-research message
lookup to search messages in reverse chronological order, selecting the latest
matching snapshot for the job rather than the earliest. Preserve the existing
job ID and trace-state predicates, and keep the current-conversation precedence
over ownConversations.

In `@frontends/ui/src/features/layout/store.ts`:
- Around line 36-37: Update the authentication transition handling to reset the
layout store’s promptDraft and selectedModel alongside the existing chat-state
reset during logout and account switches. Add coverage for user switching that
verifies both composer fields return to their initial values.

In `@frontends/ui/src/features/layout/types.ts`:
- Around line 91-94: Update the setSelectedModel contract in the relevant layout
types to accept string | undefined, allowing callers to clear the override and
restore backend-default behavior. Update its implementation and add a test
covering setting a model, then resetting it with undefined.

In
`@frontends/ui/src/shared/components/CollapsibleBlock/CollapsibleBlock.spec.tsx`:
- Around line 19-24: Add coverage in the CollapsibleBlock tests for the closed
state’s aria-controls attribute, verifying it references the controlled body
element, and update the static/non-collapsible test to render open={false} while
asserting the body remains visible. Preserve the existing assertions for the
collapsible closed behavior.

In `@frontends/ui/src/shared/components/CollapsibleBlock/CollapsibleBlock.tsx`:
- Line 69: The CollapsibleBlock component must keep the element referenced by
the trigger’s aria-controls mounted when collapsed. Preserve a stable wrapper
carrying id={regionId} in the open/closed render path, and apply the collapse
animation or conditional content rendering inside that wrapper rather than
removing the target element.

In `@frontends/ui/src/shared/components/Sources/SourceList.spec.tsx`:
- Around line 37-42: Update the equality-branch fixture in the “renders a
clickable link for a web source even when the label equals the domain” test to
use the normalized domain returned by prettyDomain(url), such as “nvidia.com”,
so it exercises the no-domain branch rather than showDomain.

---

Outside diff comments:
In `@frontends/ui/src/features/chat/hooks/use-websocket-chat.ts`:
- Around line 849-876: Derive a terminal flag in the websocket frame handling
flow from either isComplete or status === 'error'/'complete', then use it for
existing-step finalization and the created step’s isComplete value. Ensure newly
created terminal steps immediately invoke failThinkingStep or
completeThinkingStep after addThinkingStep, while non-terminal steps retain the
running behavior. Add coverage for an error-status frame with a non-“Function
Complete” function name.

In `@frontends/ui/src/shared/components/MarkdownRenderer/Citation.tsx`:
- Around line 33-43: Update the Citation component’s anchor tabIndex logic so
unresolved citations without src are not focusable; only citations with a valid
src but no URL should receive a keyboard tab stop, while URL citations retain
current behavior. Add a regression test covering the missing-src case and verify
strict TypeScript and accessibility behavior.

In `@frontends/ui/src/shared/components/Sources/source-utils.ts`:
- Around line 43-58: Add regression tests for mapCitationSource in
frontends/ui/src/shared/components/Sources/source-utils.ts: verify HTTP/HTTPS
sources retain url while non-web references leave url undefined. Also add tests
for the citation transformation in
frontends/ui/src/shared/components/MarkdownRenderer/rehype-citations.ts,
confirming [1] remains literal inside inline and fenced code while normal text
still produces cite elements.

In `@frontends/ui/src/shared/components/Sources/SourceStrip.tsx`:
- Around line 74-83: Update the disclosure control in SourceStrip so it remains
mounted in both collapsed and expanded states, with aria-expanded reflecting
expanded and aria-controls referencing the source container. Change its action
and label to support collapsing after expansion, and ensure focus remains on the
stable toggle or is intentionally moved to the revealed content.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 35198851-8741-4820-982f-d671aa154fe5

📥 Commits

Reviewing files that changed from the base of the PR and between 49bd80c and 6abdf1a.

📒 Files selected for processing (23)
  • frontends/ui/src/adapters/api/websocket-client.ts
  • frontends/ui/src/features/chat/hooks/use-websocket-chat.ts
  • frontends/ui/src/features/chat/lib/deep-research-trace.spec.ts
  • frontends/ui/src/features/chat/lib/deep-research-trace.ts
  • frontends/ui/src/features/chat/lib/intermediate-step-parser.spec.ts
  • frontends/ui/src/features/chat/lib/intermediate-step-parser.ts
  • frontends/ui/src/features/chat/store.spec.ts
  • frontends/ui/src/features/chat/store.ts
  • frontends/ui/src/features/chat/types.ts
  • frontends/ui/src/features/layout/data-sources.spec.ts
  • frontends/ui/src/features/layout/data-sources.ts
  • frontends/ui/src/features/layout/store.spec.ts
  • frontends/ui/src/features/layout/store.ts
  • frontends/ui/src/features/layout/types.ts
  • frontends/ui/src/shared/components/Actions/CopyButton.tsx
  • frontends/ui/src/shared/components/CollapsibleBlock/CollapsibleBlock.spec.tsx
  • frontends/ui/src/shared/components/CollapsibleBlock/CollapsibleBlock.tsx
  • frontends/ui/src/shared/components/MarkdownRenderer/Citation.tsx
  • frontends/ui/src/shared/components/MarkdownRenderer/rehype-citations.ts
  • frontends/ui/src/shared/components/Sources/SourceList.spec.tsx
  • frontends/ui/src/shared/components/Sources/SourceList.tsx
  • frontends/ui/src/shared/components/Sources/SourceStrip.tsx
  • frontends/ui/src/shared/components/Sources/source-utils.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (3)
frontends/ui/**/*.{js,ts,jsx,tsx,vue}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Run npm lint, type-check, and build validation for UI changes in frontends/ui

Files:

  • frontends/ui/src/features/layout/data-sources.spec.ts
  • frontends/ui/src/shared/components/MarkdownRenderer/rehype-citations.ts
  • frontends/ui/src/shared/components/MarkdownRenderer/Citation.tsx
  • frontends/ui/src/features/chat/lib/intermediate-step-parser.spec.ts
  • frontends/ui/src/shared/components/Sources/SourceStrip.tsx
  • frontends/ui/src/shared/components/Sources/SourceList.tsx
  • frontends/ui/src/shared/components/Sources/SourceList.spec.tsx
  • frontends/ui/src/adapters/api/websocket-client.ts
  • frontends/ui/src/features/chat/lib/deep-research-trace.spec.ts
  • frontends/ui/src/shared/components/Actions/CopyButton.tsx
  • frontends/ui/src/features/layout/store.spec.ts
  • frontends/ui/src/features/layout/store.ts
  • frontends/ui/src/shared/components/CollapsibleBlock/CollapsibleBlock.tsx
  • frontends/ui/src/features/chat/types.ts
  • frontends/ui/src/shared/components/CollapsibleBlock/CollapsibleBlock.spec.tsx
  • frontends/ui/src/shared/components/Sources/source-utils.ts
  • frontends/ui/src/features/chat/store.spec.ts
  • frontends/ui/src/features/chat/lib/deep-research-trace.ts
  • frontends/ui/src/features/layout/data-sources.ts
  • frontends/ui/src/features/chat/store.ts
  • frontends/ui/src/features/layout/types.ts
  • frontends/ui/src/features/chat/hooks/use-websocket-chat.ts
  • frontends/ui/src/features/chat/lib/intermediate-step-parser.ts
frontends/ui/**/*.{ts,tsx,jsx,js}

📄 CodeRabbit inference engine (AGENTS.md)

frontends/ui/**/*.{ts,tsx,jsx,js}: The UI is built with Next.js / React / TypeScript / Tailwind with KUI components; reuse existing KUI components and visual patterns rather than introducing new ones
Validate UI-affecting changes with npm run lint, npm run type-check, and npm run test:ci, and include a screenshot for visible changes

Files:

  • frontends/ui/src/features/layout/data-sources.spec.ts
  • frontends/ui/src/shared/components/MarkdownRenderer/rehype-citations.ts
  • frontends/ui/src/shared/components/MarkdownRenderer/Citation.tsx
  • frontends/ui/src/features/chat/lib/intermediate-step-parser.spec.ts
  • frontends/ui/src/shared/components/Sources/SourceStrip.tsx
  • frontends/ui/src/shared/components/Sources/SourceList.tsx
  • frontends/ui/src/shared/components/Sources/SourceList.spec.tsx
  • frontends/ui/src/adapters/api/websocket-client.ts
  • frontends/ui/src/features/chat/lib/deep-research-trace.spec.ts
  • frontends/ui/src/shared/components/Actions/CopyButton.tsx
  • frontends/ui/src/features/layout/store.spec.ts
  • frontends/ui/src/features/layout/store.ts
  • frontends/ui/src/shared/components/CollapsibleBlock/CollapsibleBlock.tsx
  • frontends/ui/src/features/chat/types.ts
  • frontends/ui/src/shared/components/CollapsibleBlock/CollapsibleBlock.spec.tsx
  • frontends/ui/src/shared/components/Sources/source-utils.ts
  • frontends/ui/src/features/chat/store.spec.ts
  • frontends/ui/src/features/chat/lib/deep-research-trace.ts
  • frontends/ui/src/features/layout/data-sources.ts
  • frontends/ui/src/features/chat/store.ts
  • frontends/ui/src/features/layout/types.ts
  • frontends/ui/src/features/chat/hooks/use-websocket-chat.ts
  • frontends/ui/src/features/chat/lib/intermediate-step-parser.ts
frontends/ui/**/*

⚙️ CodeRabbit configuration file

frontends/ui/**/*: Review UI changes for strict TypeScript behavior, API contract alignment, auth/session handling, accessible controls,
resilient loading and error states, and report/chat state consistency. Prefer existing UI patterns and require tests
for changed user-visible workflows.

Files:

  • frontends/ui/src/features/layout/data-sources.spec.ts
  • frontends/ui/src/shared/components/MarkdownRenderer/rehype-citations.ts
  • frontends/ui/src/shared/components/MarkdownRenderer/Citation.tsx
  • frontends/ui/src/features/chat/lib/intermediate-step-parser.spec.ts
  • frontends/ui/src/shared/components/Sources/SourceStrip.tsx
  • frontends/ui/src/shared/components/Sources/SourceList.tsx
  • frontends/ui/src/shared/components/Sources/SourceList.spec.tsx
  • frontends/ui/src/adapters/api/websocket-client.ts
  • frontends/ui/src/features/chat/lib/deep-research-trace.spec.ts
  • frontends/ui/src/shared/components/Actions/CopyButton.tsx
  • frontends/ui/src/features/layout/store.spec.ts
  • frontends/ui/src/features/layout/store.ts
  • frontends/ui/src/shared/components/CollapsibleBlock/CollapsibleBlock.tsx
  • frontends/ui/src/features/chat/types.ts
  • frontends/ui/src/shared/components/CollapsibleBlock/CollapsibleBlock.spec.tsx
  • frontends/ui/src/shared/components/Sources/source-utils.ts
  • frontends/ui/src/features/chat/store.spec.ts
  • frontends/ui/src/features/chat/lib/deep-research-trace.ts
  • frontends/ui/src/features/layout/data-sources.ts
  • frontends/ui/src/features/chat/store.ts
  • frontends/ui/src/features/layout/types.ts
  • frontends/ui/src/features/chat/hooks/use-websocket-chat.ts
  • frontends/ui/src/features/chat/lib/intermediate-step-parser.ts
🔇 Additional comments (23)
frontends/ui/src/features/layout/store.ts (1)

58-99: 📐 Maintainability & Code Quality

Provide the required UI validation evidence.

Please attach results for npm run lint, npm run type-check, npm run test:ci, and npm run build for this UI-state change.

As per coding guidelines, “Run npm lint, type-check, and build validation for UI changes in frontends/ui.”

Source: Coding guidelines

frontends/ui/src/shared/components/MarkdownRenderer/Citation.tsx (2)

33-43: 🎯 Functional Correctness

Associate the tooltip with its citation trigger.

The previous accessibility finding remains unresolved: the role="tooltip" element is not referenced by the citation anchor, so assistive technologies receive only the short aria-label, not the tooltip’s title/snippet.

As per path instructions, “Review UI changes for strict TypeScript behavior, API contract alignment, auth/session handling, accessible controls, resilient loading and error states, and report/chat state consistency.”

Also applies to: 56-58

Source: Path instructions


26-72: 📐 Maintainability & Code Quality

Run the UI checks and attach citation-state screenshots. Execute npm run lint, npm run type-check, npm run test:ci, and npm run build in frontends/ui, and include screenshots covering the citation hover/focus states.

frontends/ui/src/features/layout/data-sources.ts (1)

11-12: LGTM!

Also applies to: 47-75, 77-109, 111-124

frontends/ui/src/features/layout/data-sources.spec.ts (1)

1-44: LGTM!

frontends/ui/src/adapters/api/websocket-client.ts (1)

231-239: LGTM!

frontends/ui/src/features/chat/hooks/use-websocket-chat.ts (1)

58-62: LGTM!

Also applies to: 109-109, 419-419, 595-609, 1102-1102, 1224-1224, 1250-1250

frontends/ui/src/features/chat/lib/intermediate-step-parser.spec.ts (1)

1-118: LGTM!

frontends/ui/src/features/chat/lib/deep-research-trace.ts (2)

32-50: The previously reported explanation-tool retry collapse issue remains.

The inserted explanation step still prevents repeated explanation-tool calls from being adjacent, so collapseRepeats cannot merge them.


1-30: LGTM!

Also applies to: 52-136

frontends/ui/src/features/chat/lib/deep-research-trace.spec.ts (2)

104-115: Covered by the existing collapseRepeats finding in deep-research-trace.ts.


1-103: LGTM!

Also applies to: 117-160

frontends/ui/src/features/chat/store.spec.ts (2)

2285-2347: Covered by the latest-snapshot hydration finding in store.ts.


586-605: LGTM!

Also applies to: 1279-1355

frontends/ui/src/features/chat/store.ts (2)

750-781: LGTM!


1281-1400: LGTM!

frontends/ui/src/features/chat/types.ts (1)

64-64: LGTM!

Also applies to: 185-226, 470-470, 502-503, 642-643

frontends/ui/src/shared/components/Actions/CopyButton.tsx (1)

6-6: LGTM!

Also applies to: 24-42

frontends/ui/src/shared/components/CollapsibleBlock/CollapsibleBlock.tsx (1)

17-17: LGTM!

Also applies to: 21-35, 46-46

frontends/ui/src/shared/components/CollapsibleBlock/CollapsibleBlock.spec.tsx (2)

1-53: 📐 Maintainability & Code Quality

Provide the required UI validation evidence.

Please confirm npm run lint, npm run type-check, npm run test:ci, and build validation for frontends/ui, and attach a screenshot for these visible UI changes.

As per coding guidelines, UI changes require lint, type-check, build validation, tests, and screenshots for visible changes.

Sources: Coding guidelines, Path instructions


1-18: LGTM!

Also applies to: 26-42

frontends/ui/src/shared/components/Sources/SourceList.tsx (2)

40-60: Announce external navigation to assistive technology.

Both links open a new tab but expose no accessible hint. Add visually hidden “opens in a new tab” text to both anchors and cover the behavior in SourceList.spec.tsx. This remains the same unresolved accessibility issue from the prior review.

As per path instructions, review UI changes for “accessible controls.”

Source: Path instructions


22-68: 📐 Maintainability & Code Quality

Run the frontend checks and attach a screenshot for the source list update.

Comment thread frontends/ui/src/features/chat/lib/intermediate-step-parser.ts Outdated
Comment thread frontends/ui/src/features/chat/store.ts
Comment thread frontends/ui/src/features/layout/store.ts
Comment thread frontends/ui/src/features/layout/types.ts Outdated
Comment thread frontends/ui/src/shared/components/Sources/SourceList.spec.tsx
@KyleZheng1284 KyleZheng1284 added enhancement New feature or request UI/UX UI and UX related fixes READY FOR REVIEW labels Jul 15, 2026
@Manushpm8
Manushpm8 force-pushed the feat/ui-chat-plumbing branch from 6abdf1a to f2d8a3e Compare July 16, 2026 03:17
@Manushpm8
Manushpm8 requested a review from a team July 16, 2026 03:17

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
frontends/ui/src/shared/components/CollapsibleBlock/CollapsibleBlock.tsx (1)

66-72: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Avoid rendering an enabled inert toggle.

When collapsible defaults to true and onOpenChange is omitted, the component renders a button whose click handler cannot change open. Require the callback for collapsible usage, or render a static/disabled header when no callback is supplied.

As per path instructions, “Review UI changes for ... accessible controls.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontends/ui/src/shared/components/CollapsibleBlock/CollapsibleBlock.tsx`
around lines 66 - 72, Update CollapsibleBlock’s collapsible rendering around the
onOpenChange handler so it does not expose an enabled toggle when the callback
is absent. Require onOpenChange before rendering the interactive button,
otherwise render a static or disabled header while preserving the existing
collapsible behavior when the callback is provided.

Source: Path instructions

frontends/ui/src/shared/components/Mermaid/MermaidBlock.tsx (1)

135-143: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Preserve native scrolling at zoom limits.

preventDefault() runs before the clamp check. At the minimum or maximum scale, next === prev, but the wheel event has already been canceled, so users cannot scroll normally over the diagram. Move preventDefault() after the boundary check.

Proposed fix
     const onWheel = (e: WheelEvent): void => {
-      e.preventDefault()
+      const prev = scaleRef.current
+      const next = clamp(prev * Math.exp(-e.deltaY * 0.0015))
+      if (next === prev) return
+
+      e.preventDefault()
       const rect = el.getBoundingClientRect()
       const cx = e.clientX - rect.left
       const cy = e.clientY - rect.top
-      const prev = scaleRef.current
-      const next = clamp(prev * Math.exp(-e.deltaY * 0.0015))
-      if (next === prev) return
       anchorRef.current = { ratio: next / prev, contentX: el.scrollLeft + cx, contentY: el.scrollTop + cy, cx, cy }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontends/ui/src/shared/components/Mermaid/MermaidBlock.tsx` around lines 135
- 143, Move e.preventDefault() in onWheel to after the next === prev early
return, so wheel events at the zoom limits retain native scrolling while zooming
events are still canceled.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@frontends/ui/src/features/chat/lib/deep-research-trace.ts`:
- Around line 35-38: Update the sameCall helper to stop comparing the lossy
argSummary display value; use canonical raw inputs or an explicit retry identity
to distinguish calls with different arguments while preserving the existing
top-level and function-name checks. Add coverage proving distinct inputs with
identical or undefined summaries remain separate, and verify report/chat state
remains consistent.

In `@frontends/ui/src/features/chat/store.ts`:
- Around line 2420-2423: Update the preferred conversation lookup near
ownConversations so it only calls findInConversation when currentUserId is
present and currentConversation.userId matches currentUserId; otherwise return
false. Keep the existing user-filtered ownConversations fallback unchanged.

In `@frontends/ui/src/shared/components/Sources/SourceStrip.tsx`:
- Around line 62-64: Guard previewCount in the SourceStrip component before the
hasMore, visible, and hiddenCount calculations, normalizing it to a positive
integer or enforcing that contract so previewCount - 1 is always valid. Preserve
consistent expanded/collapsed state and add an edge-case test covering
previewCount values at or below zero.

---

Outside diff comments:
In `@frontends/ui/src/shared/components/CollapsibleBlock/CollapsibleBlock.tsx`:
- Around line 66-72: Update CollapsibleBlock’s collapsible rendering around the
onOpenChange handler so it does not expose an enabled toggle when the callback
is absent. Require onOpenChange before rendering the interactive button,
otherwise render a static or disabled header while preserving the existing
collapsible behavior when the callback is provided.

In `@frontends/ui/src/shared/components/Mermaid/MermaidBlock.tsx`:
- Around line 135-143: Move e.preventDefault() in onWheel to after the next ===
prev early return, so wheel events at the zoom limits retain native scrolling
while zooming events are still canceled.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 1cda19ba-f6a6-474a-881a-58c2e18f8cd7

📥 Commits

Reviewing files that changed from the base of the PR and between 6abdf1a and f2d8a3e.

📒 Files selected for processing (22)
  • frontends/ui/src/adapters/api/websocket-client.ts
  • frontends/ui/src/app/globals.css
  • frontends/ui/src/features/chat/hooks/use-websocket-chat.ts
  • frontends/ui/src/features/chat/lib/deep-research-trace.spec.ts
  • frontends/ui/src/features/chat/lib/deep-research-trace.ts
  • frontends/ui/src/features/chat/lib/intermediate-step-parser.spec.ts
  • frontends/ui/src/features/chat/lib/intermediate-step-parser.ts
  • frontends/ui/src/features/chat/store.spec.ts
  • frontends/ui/src/features/chat/store.ts
  • frontends/ui/src/features/chat/types.ts
  • frontends/ui/src/features/layout/data-sources.spec.ts
  • frontends/ui/src/features/layout/data-sources.ts
  • frontends/ui/src/features/layout/store.spec.ts
  • frontends/ui/src/features/layout/store.ts
  • frontends/ui/src/features/layout/types.ts
  • frontends/ui/src/shared/components/CollapsibleBlock/CollapsibleBlock.tsx
  • frontends/ui/src/shared/components/Mermaid/MermaidBlock.spec.tsx
  • frontends/ui/src/shared/components/Mermaid/MermaidBlock.tsx
  • frontends/ui/src/shared/components/Sources/SourceStrip.spec.tsx
  • frontends/ui/src/shared/components/Sources/SourceStrip.tsx
  • frontends/ui/src/shared/components/Sources/parse-references.spec.ts
  • frontends/ui/src/shared/lib/motion.spec.tsx
📜 Review details
🧰 Additional context used
📓 Path-based instructions (3)
frontends/ui/**/*.{js,ts,jsx,tsx,vue}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Run npm lint, type-check, and build validation for UI changes in frontends/ui

Files:

  • frontends/ui/src/shared/components/Sources/SourceStrip.spec.tsx
  • frontends/ui/src/shared/lib/motion.spec.tsx
  • frontends/ui/src/shared/components/Sources/SourceStrip.tsx
  • frontends/ui/src/features/layout/data-sources.spec.ts
  • frontends/ui/src/adapters/api/websocket-client.ts
  • frontends/ui/src/shared/components/Sources/parse-references.spec.ts
  • frontends/ui/src/features/chat/lib/intermediate-step-parser.spec.ts
  • frontends/ui/src/features/layout/store.spec.ts
  • frontends/ui/src/features/chat/lib/deep-research-trace.spec.ts
  • frontends/ui/src/shared/components/Mermaid/MermaidBlock.spec.tsx
  • frontends/ui/src/shared/components/CollapsibleBlock/CollapsibleBlock.tsx
  • frontends/ui/src/features/layout/data-sources.ts
  • frontends/ui/src/features/chat/store.spec.ts
  • frontends/ui/src/features/layout/store.ts
  • frontends/ui/src/shared/components/Mermaid/MermaidBlock.tsx
  • frontends/ui/src/features/chat/store.ts
  • frontends/ui/src/features/chat/types.ts
  • frontends/ui/src/features/chat/lib/deep-research-trace.ts
  • frontends/ui/src/features/chat/hooks/use-websocket-chat.ts
  • frontends/ui/src/features/chat/lib/intermediate-step-parser.ts
  • frontends/ui/src/features/layout/types.ts
frontends/ui/**/*.{ts,tsx,jsx,js}

📄 CodeRabbit inference engine (AGENTS.md)

frontends/ui/**/*.{ts,tsx,jsx,js}: The UI is built with Next.js / React / TypeScript / Tailwind with KUI components; reuse existing KUI components and visual patterns rather than introducing new ones
Validate UI-affecting changes with npm run lint, npm run type-check, and npm run test:ci, and include a screenshot for visible changes

Files:

  • frontends/ui/src/shared/components/Sources/SourceStrip.spec.tsx
  • frontends/ui/src/shared/lib/motion.spec.tsx
  • frontends/ui/src/shared/components/Sources/SourceStrip.tsx
  • frontends/ui/src/features/layout/data-sources.spec.ts
  • frontends/ui/src/adapters/api/websocket-client.ts
  • frontends/ui/src/shared/components/Sources/parse-references.spec.ts
  • frontends/ui/src/features/chat/lib/intermediate-step-parser.spec.ts
  • frontends/ui/src/features/layout/store.spec.ts
  • frontends/ui/src/features/chat/lib/deep-research-trace.spec.ts
  • frontends/ui/src/shared/components/Mermaid/MermaidBlock.spec.tsx
  • frontends/ui/src/shared/components/CollapsibleBlock/CollapsibleBlock.tsx
  • frontends/ui/src/features/layout/data-sources.ts
  • frontends/ui/src/features/chat/store.spec.ts
  • frontends/ui/src/features/layout/store.ts
  • frontends/ui/src/shared/components/Mermaid/MermaidBlock.tsx
  • frontends/ui/src/features/chat/store.ts
  • frontends/ui/src/features/chat/types.ts
  • frontends/ui/src/features/chat/lib/deep-research-trace.ts
  • frontends/ui/src/features/chat/hooks/use-websocket-chat.ts
  • frontends/ui/src/features/chat/lib/intermediate-step-parser.ts
  • frontends/ui/src/features/layout/types.ts
frontends/ui/**/*

⚙️ CodeRabbit configuration file

frontends/ui/**/*: Review UI changes for strict TypeScript behavior, API contract alignment, auth/session handling, accessible controls,
resilient loading and error states, and report/chat state consistency. Prefer existing UI patterns and require tests
for changed user-visible workflows.

Files:

  • frontends/ui/src/shared/components/Sources/SourceStrip.spec.tsx
  • frontends/ui/src/shared/lib/motion.spec.tsx
  • frontends/ui/src/shared/components/Sources/SourceStrip.tsx
  • frontends/ui/src/features/layout/data-sources.spec.ts
  • frontends/ui/src/adapters/api/websocket-client.ts
  • frontends/ui/src/shared/components/Sources/parse-references.spec.ts
  • frontends/ui/src/features/chat/lib/intermediate-step-parser.spec.ts
  • frontends/ui/src/features/layout/store.spec.ts
  • frontends/ui/src/features/chat/lib/deep-research-trace.spec.ts
  • frontends/ui/src/app/globals.css
  • frontends/ui/src/shared/components/Mermaid/MermaidBlock.spec.tsx
  • frontends/ui/src/shared/components/CollapsibleBlock/CollapsibleBlock.tsx
  • frontends/ui/src/features/layout/data-sources.ts
  • frontends/ui/src/features/chat/store.spec.ts
  • frontends/ui/src/features/layout/store.ts
  • frontends/ui/src/shared/components/Mermaid/MermaidBlock.tsx
  • frontends/ui/src/features/chat/store.ts
  • frontends/ui/src/features/chat/types.ts
  • frontends/ui/src/features/chat/lib/deep-research-trace.ts
  • frontends/ui/src/features/chat/hooks/use-websocket-chat.ts
  • frontends/ui/src/features/chat/lib/intermediate-step-parser.ts
  • frontends/ui/src/features/layout/types.ts
🔇 Additional comments (25)
frontends/ui/src/shared/components/CollapsibleBlock/CollapsibleBlock.tsx (1)

71-83: Keep the aria-controls target mounted while collapsed.

The trigger always references regionId, but the element with that ID is removed when a collapsible block closes. Keep a stable wrapper with id={regionId} and animate or conditionally render its contents inside it. This repeats the unresolved prior review finding.

As per path instructions, “Review UI changes for ... accessible controls.”

Source: Path instructions

frontends/ui/src/app/globals.css (1)

360-360: LGTM!

Also applies to: 384-384, 395-395, 437-439

frontends/ui/src/shared/lib/motion.spec.tsx (1)

4-45: LGTM!

frontends/ui/src/shared/components/Mermaid/MermaidBlock.tsx (3)

183-187: Fullscreen focus management remains incomplete.

The new dialog semantics do not move focus into the fullscreen overlay or restore it on exit, leaving keyboard users focused outside the active modal. This repeats the unresolved prior finding; add focus capture/restoration and a regression test.

Source: Path instructions


40-134: LGTM!

Also applies to: 144-179


194-228: 📐 Maintainability & Code Quality

Provide the required frontend validation evidence.

Run npm run lint, npm run type-check, npm run test:ci, and build validation for frontends/ui. The supplied context reports lint, type-check, and tests, but not build output.

As per coding guidelines, frontend UI changes require lint, type-check, test, and build validation.

Source: Coding guidelines

frontends/ui/src/features/layout/data-sources.ts (1)

11-12: LGTM!

Also applies to: 47-75, 77-109, 111-124

frontends/ui/src/shared/components/Sources/parse-references.spec.ts (1)

1-13: LGTM!

Also applies to: 14-43, 45-70, 72-98, 100-121, 123-168, 170-203

frontends/ui/src/features/layout/data-sources.spec.ts (1)

1-44: LGTM!

frontends/ui/src/shared/components/Mermaid/MermaidBlock.spec.tsx (1)

84-85: LGTM!

Also applies to: 89-89

frontends/ui/src/shared/components/Sources/SourceStrip.tsx (1)

58-61: LGTM!

Also applies to: 66-89

frontends/ui/src/shared/components/Sources/SourceStrip.spec.tsx (1)

40-52: LGTM!

frontends/ui/src/adapters/api/websocket-client.ts (1)

231-239: 📐 Maintainability & Code Quality

Confirm the required UI build validation.

The PR reports lint, type-check, and tests, but not the required build check. Please confirm npm run build passes in frontends/ui.

As per coding guidelines, “Run npm lint, type-check, and build validation for UI changes in frontends/ui.” <coding_guidelines>

Source: Coding guidelines

frontends/ui/src/features/chat/hooks/use-websocket-chat.ts (1)

58-62: LGTM!

Also applies to: 103-110, 419-419, 595-609, 844-860, 875-876, 1102-1102, 1224-1224, 1250-1250

frontends/ui/src/features/chat/lib/intermediate-step-parser.ts (1)

14-15: LGTM!

Also applies to: 99-106, 127-150, 182-237

frontends/ui/src/features/chat/lib/intermediate-step-parser.spec.ts (1)

1-126: LGTM!

frontends/ui/src/features/layout/types.ts (1)

28-34: LGTM!

Also applies to: 53-56, 73-76, 91-96

frontends/ui/src/features/layout/store.ts (1)

25-26: LGTM!

Also applies to: 36-37, 58-99, 126-134

frontends/ui/src/features/chat/lib/deep-research-trace.ts (2)

4-34: LGTM!

Also applies to: 39-150


1-2: 📐 Maintainability & Code Quality

Run the UI build check. lint, type-check, and test:ci don’t cover the Next.js production build; add npm --prefix frontends/ui run build to the required UI validation.

frontends/ui/src/features/chat/store.ts (1)

453-464: LGTM!

Also applies to: 759-785, 1290-1318, 1346-1404, 2410-2419, 2424-2447

frontends/ui/src/features/chat/types.ts (1)

64-64: LGTM!

Also applies to: 185-186, 219-226, 470-470, 502-503, 642-643

frontends/ui/src/features/chat/lib/deep-research-trace.spec.ts (1)

1-192: LGTM!

frontends/ui/src/features/chat/store.spec.ts (1)

14-14: LGTM!

Also applies to: 45-45, 184-207, 612-631, 1305-1381, 2311-2373

frontends/ui/src/features/layout/store.spec.ts (1)

21-22: LGTM!

Also applies to: 32-33, 139-221

Comment thread frontends/ui/src/features/chat/lib/deep-research-trace.ts
Comment thread frontends/ui/src/features/chat/store.ts
Comment thread frontends/ui/src/shared/components/Sources/SourceStrip.tsx
Manushpm8 and others added 7 commits July 26, 2026 12:08
Foundational frontend utilities for the staged chat UI/UX work:
- cn(): clsx + tailwind-merge className composition so conflicting Tailwind
  utilities resolve deterministically.
- titleCaseWords()/ACRONYMS: humanize snake_case and `__`-separated identifiers
  (tool and function names) into display labels, upper-casing common acronyms.
- AppMotionConfig: a Motion provider that honors prefers-reduced-motion.

Adds clsx, tailwind-merge, and motion. Covered 100% by unit tests; no UI
behavior changes yet.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Manush Maheshwari <manushm@nvidia.com>
Adds the UI-only shared component library the chat redesign builds on:
- Sources/*: source chips, list, and reference parsing (web + doc kinds).
- research/*: StatusDot, ToolCallRow, and a generic tool-label engine for the
  live research trace.
- Surface/Card, Actions/CopyButton, CollapsibleBlock, Mermaid.
- MarkdownRenderer upgrade: an answer variant, superscript citations with hover
  popovers, KaTeX math, and Mermaid diagrams. Prod's artifact-URL handling is
  preserved verbatim; chart fences fall through unchanged.

Adds mermaid, katex, rehype-katex, remark-math, unist-util-visit. No
warehouse/predictive features are ported. Covered by unit and render tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Manush Maheshwari <manushm@nvidia.com>
- rehype-citations: skip [n] markers inside code/pre so code samples like
  arr[1] are not turned into citations.
- CopyButton: track the reset timeout in a ref, clearing it on re-click and
  unmount.
- SourceList: always render a clickable link for URL-bearing sources, including
  when the label equals the domain.
- source-utils: reuse the computed kind instead of re-testing the URL regex.
- CollapsibleBlock: wire aria-controls to the disclosed region id.
- SourceStrip: expose aria-expanded on the "+N more" button.
- Citation: keep URL-less citations keyboard-focusable.
- Add CollapsibleBlock and SourceList unit tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Manush Maheshwari <manushm@nvidia.com>
Signed-off-by: Manush Maheshwari <manushm@nvidia.com>
Additive state and streaming plumbing that a later UI PR consumes; no visual
change and no prod capability removed.

- chat types: optional ThinkingStep fields (completedAt, status, argSummary),
  ChatMessage.selectedModel, and a plan_approval prompt type.
- intermediate-step-parser: generic folded reasoning/reflection/explanation
  sentinels and predicates, splitPayload/extractFoldedOutput, and label lookup
  via the shared tool-label engine.
- deep-research-trace: replays a stored deep run into the same ThinkingStep
  stream so reloaded reports show an identical trace.
- store + use-websocket-chat: populate the new step fields as the run streams,
  fail steps on error, and thread the selected model through send.
- layout store: a sidebar collapse model added alongside the existing panel
  API, a prompt draft, a selected-model slot, and data-source display helpers.

Per-user auth, WebSocket token rotation, artifact handling, and HITL are
preserved unchanged. No predictive/analytical/warehouse plumbing is ported.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Manush Maheshwari <manushm@nvidia.com>
- deep-research-trace: preserve tool calls whose referenced agent is missing
  (dangling agentId), not just calls with no agent, and add a regression test.
- store: scope deep-research hydration to the current user's conversations.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Manush Maheshwari <manushm@nvidia.com>
Signed-off-by: Manush Maheshwari <manushm@nvidia.com>
@Manushpm8
Manushpm8 force-pushed the feat/ui-chat-plumbing branch from f2d8a3e to 88959b9 Compare July 26, 2026 19:17

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

♻️ Duplicate comments (4)
frontends/ui/src/shared/components/Sources/parse-references.ts (2)

37-61: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Bare web references still lose their url (previously flagged, appears unfixed).

For a reference line with no Title - URL dash separator (e.g. - [3] https://example.com/article), TRAILING_URL_RE doesn't match, so parsing falls to the bottom branch (lines 52-61). There, kind is correctly inferred as 'web', but the returned SourceRef never sets url. Downstream (SourceList, SourceStrip, Citation) will render this as plain unlinked text even though it's a valid, clickable web source. A prior review already flagged this exact gap and it's marked "Addressed," but the code shown here still lacks the fix.

🐛 Proposed fix
   const ref = rest.trim()
   const kind = inferSourceKind(ref)
   const isFile = FILE_PAGE_RE.test(ref) || kind === 'doc'
   return {
     id: `ref-${n}`,
     title: ref || `Source ${n}`,
+    url: kind === 'web' && !isFile ? ref : undefined,
     kind: isFile ? 'doc' : kind,
     label: isFile ? ref || sourceLabel(ref, 'doc') : sourceLabel(ref, kind),
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontends/ui/src/shared/components/Sources/parse-references.ts` around lines
37 - 61, Update the bare-reference fallback in parseReferenceLine so web
references retain their URL by setting SourceRef.url to the parsed ref when it
is a valid web reference. Preserve the existing document handling and labels,
while ensuring downstream consumers receive a clickable URL for inputs such as
https://example.com/article.

215-267: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Duplicated markup-detection regex.

Same duplication previously flagged (bullet/heading/ordered-list checks in the initial guard vs. the while-loop condition). No fix applied in the shown code.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontends/ui/src/shared/components/Sources/parse-references.ts` around lines
215 - 267, The markup-detection regexes are duplicated in tabularizeEntityLines,
both in the initial isMarkup guard and the entity-run loop. Extract the shared
bullet/heading/ordered-list check into a local helper or constant and reuse it
in both places, preserving the current detection behavior.
frontends/ui/src/features/chat/store.ts (1)

2410-2426: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Ownership check on currentConversation still missing — unresolved from a prior review.

findInConversation(currentConversation) is tried before the user-filtered ownConversations list without verifying currentConversation.userId === currentUserId, and there's no !currentUserId early-return. A stale or rehydrated currentConversation belonging to a different user would let this action hydrate that user's deep-research trace data into the current session.

As per path instructions, review UI changes for auth/session handling.

🔒 Proposed fix
         hydrateDeepResearchFromMessage: (jobId: string): boolean => {
           const { currentConversation, conversations, currentUserId } = get()
+          if (!currentUserId) return false
           const findInConversation = (conv?: Conversation | null): ChatMessage | undefined =>
             [...(conv?.messages ?? [])].reverse().find(
               (m) =>
                 m.deepResearchJobId === jobId &&
                 ((m.deepResearchToolCalls?.length ?? 0) > 0 ||
                   (m.deepResearchAgents?.length ?? 0) > 0)
             )

           const ownConversations = conversations.filter((c) => c.userId === currentUserId)
+          const ownedCurrentConversation =
+            currentConversation?.userId === currentUserId ? currentConversation : null
           const message =
-            findInConversation(currentConversation) ??
+            findInConversation(ownedCurrentConversation) ??
             ownConversations.map(findInConversation).find(Boolean)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontends/ui/src/features/chat/store.ts` around lines 2410 - 2426, Update
hydrateDeepResearchFromMessage to return false when currentUserId is absent, and
only call findInConversation(currentConversation) when
currentConversation.userId matches currentUserId; retain the existing
ownConversations filtering for fallback lookup so hydration never uses another
user’s conversation.

Source: Path instructions

frontends/ui/src/features/chat/lib/deep-research-trace.ts (1)

35-38: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

sameCall still dedupes on the lossy argSummary, not canonical input — unresolved from a prior review.

argSummary is a truncated/best-effort string (truncate, or getToolArgSummary which returns undefined for dict-like/unparseable inputs). Two tool calls with genuinely different raw input that both produce undefined or identically-truncated argSummary will satisfy sameCall and get silently merged in collapseRepeats, dropping a real trace row.

Compare against the raw toolCall.input (or an explicit retry identity) before it's summarized/truncated, not the display string.

As per path instructions, review UI changes for report/chat state consistency.

🐛 Proposed direction
-  const sameCall = (a: ThinkingStep, b: ThinkingStep): boolean =>
-    a.isTopLevel === b.isTopLevel &&
-    a.functionName === b.functionName &&
-    (a.argSummary ?? '') === (b.argSummary ?? '')
+  // Compare raw inputs (captured before summarization) rather than the
+  // lossy, possibly-truncated/undefined display summary.
+  const sameCall = (a: ThinkingStep, b: ThinkingStep): boolean =>
+    a.isTopLevel === b.isTopLevel &&
+    a.functionName === b.functionName &&
+    canonicalInputOf(a) === canonicalInputOf(b)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontends/ui/src/features/chat/lib/deep-research-trace.ts` around lines 35 -
38, Update sameCall to compare each tool call’s canonical raw toolCall.input, or
an explicit retry identity, instead of the lossy argSummary display value.
Preserve the existing isTopLevel and functionName checks, and ensure
collapseRepeats only merges calls with identical raw inputs so undefined or
truncated summaries cannot deduplicate distinct trace rows.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@frontends/ui/src/adapters/api/websocket-client.ts`:
- Around line 228-240: The sendMessage API has uniform JSON construction, so
document selectedModel in its JSDoc and consolidate the growing optional
positional parameters into a single options object while preserving payload
field behavior; update frontends/ui/src/adapters/api/websocket-client.ts lines
228-240 accordingly. In
frontends/ui/src/features/chat/hooks/use-websocket-chat.ts lines 594-609,
replace the 3-way sendOutgoingPayload branching with one unconditional
sendMessage call using the available values.

In `@frontends/ui/src/features/chat/lib/deep-research-trace.ts`:
- Line 28: Update isExplanationTool to recognize only tool names that represent
explanation tools, replacing the unrestricted substring match with an anchored
or otherwise precise name pattern. Preserve case-insensitive matching while
preventing names such as unexplained_variance_tool from being classified as
explanation tools.

In `@frontends/ui/src/shared/components/Actions/CopyButton.spec.tsx`:
- Around line 4-35: The CopyButton tests lack coverage for reset-timer lifecycle
behavior. Extend the CopyButton suite with focused fake-timer tests verifying a
newer successful copy cancels the previous reset timer and unmounting clears any
pending timer; enable and restore fake timers per test, and use the existing
clipboard mock and CopyButton interactions.

In `@frontends/ui/src/shared/components/Mermaid/MermaidBlock.tsx`:
- Around line 99-121: Update the MermaidBlock resize handling around fit and
scrollRef so the diagram refits whenever its container dimensions change,
including browser resizing while not fullscreen. Observe the container with an
appropriate resize listener or ResizeObserver, invoke fit on resize, and clean
up the subscription when the component unmounts; preserve the existing
fullscreen and Escape-key behavior.

In `@frontends/ui/src/shared/components/research/StatusDot.tsx`:
- Around line 15-20: The StatusDot state is currently hidden from assistive
technology without a text alternative. In
frontends/ui/src/shared/components/research/StatusDot.tsx:15-20, expose a
human-readable state label for each NodeState while preserving the visual dot;
frontends/ui/src/shared/components/research/ToolCallRow.tsx:1-51 requires no
direct change because the fix is contained in StatusDot.

---

Duplicate comments:
In `@frontends/ui/src/features/chat/lib/deep-research-trace.ts`:
- Around line 35-38: Update sameCall to compare each tool call’s canonical raw
toolCall.input, or an explicit retry identity, instead of the lossy argSummary
display value. Preserve the existing isTopLevel and functionName checks, and
ensure collapseRepeats only merges calls with identical raw inputs so undefined
or truncated summaries cannot deduplicate distinct trace rows.

In `@frontends/ui/src/features/chat/store.ts`:
- Around line 2410-2426: Update hydrateDeepResearchFromMessage to return false
when currentUserId is absent, and only call
findInConversation(currentConversation) when currentConversation.userId matches
currentUserId; retain the existing ownConversations filtering for fallback
lookup so hydration never uses another user’s conversation.

In `@frontends/ui/src/shared/components/Sources/parse-references.ts`:
- Around line 37-61: Update the bare-reference fallback in parseReferenceLine so
web references retain their URL by setting SourceRef.url to the parsed ref when
it is a valid web reference. Preserve the existing document handling and labels,
while ensuring downstream consumers receive a clickable URL for inputs such as
https://example.com/article.
- Around line 215-267: The markup-detection regexes are duplicated in
tabularizeEntityLines, both in the initial isMarkup guard and the entity-run
loop. Extract the shared bullet/heading/ordered-list check into a local helper
or constant and reuse it in both places, preserving the current detection
behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 8d480511-4af7-4a22-bd0c-17c070de202c

📥 Commits

Reviewing files that changed from the base of the PR and between f2d8a3e and 88959b9.

⛔ Files ignored due to path filters (1)
  • frontends/ui/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (54)
  • frontends/ui/package.json
  • frontends/ui/src/adapters/api/websocket-client.ts
  • frontends/ui/src/app/globals.css
  • frontends/ui/src/features/chat/hooks/use-websocket-chat.ts
  • frontends/ui/src/features/chat/lib/deep-research-trace.spec.ts
  • frontends/ui/src/features/chat/lib/deep-research-trace.ts
  • frontends/ui/src/features/chat/lib/intermediate-step-parser.spec.ts
  • frontends/ui/src/features/chat/lib/intermediate-step-parser.ts
  • frontends/ui/src/features/chat/store.spec.ts
  • frontends/ui/src/features/chat/store.ts
  • frontends/ui/src/features/chat/types.ts
  • frontends/ui/src/features/layout/data-sources.spec.ts
  • frontends/ui/src/features/layout/data-sources.ts
  • frontends/ui/src/features/layout/store.spec.ts
  • frontends/ui/src/features/layout/store.ts
  • frontends/ui/src/features/layout/types.ts
  • frontends/ui/src/shared/components/Actions/CopyButton.spec.tsx
  • frontends/ui/src/shared/components/Actions/CopyButton.tsx
  • frontends/ui/src/shared/components/CollapsibleBlock/CollapsibleBlock.spec.tsx
  • frontends/ui/src/shared/components/CollapsibleBlock/CollapsibleBlock.tsx
  • frontends/ui/src/shared/components/CollapsibleBlock/index.ts
  • frontends/ui/src/shared/components/MarkdownRenderer/Citation.spec.tsx
  • frontends/ui/src/shared/components/MarkdownRenderer/Citation.tsx
  • frontends/ui/src/shared/components/MarkdownRenderer/MarkdownRenderer.spec.tsx
  • frontends/ui/src/shared/components/MarkdownRenderer/MarkdownRenderer.tsx
  • frontends/ui/src/shared/components/MarkdownRenderer/rehype-citations.spec.ts
  • frontends/ui/src/shared/components/MarkdownRenderer/rehype-citations.ts
  • frontends/ui/src/shared/components/MarkdownRenderer/types.ts
  • frontends/ui/src/shared/components/Mermaid/MermaidBlock.spec.tsx
  • frontends/ui/src/shared/components/Mermaid/MermaidBlock.tsx
  • frontends/ui/src/shared/components/Mermaid/index.ts
  • frontends/ui/src/shared/components/Sources/SourceKindIcon.tsx
  • frontends/ui/src/shared/components/Sources/SourceList.spec.tsx
  • frontends/ui/src/shared/components/Sources/SourceList.tsx
  • frontends/ui/src/shared/components/Sources/SourceStrip.spec.tsx
  • frontends/ui/src/shared/components/Sources/SourceStrip.tsx
  • frontends/ui/src/shared/components/Sources/parse-references.spec.ts
  • frontends/ui/src/shared/components/Sources/parse-references.ts
  • frontends/ui/src/shared/components/Sources/source-utils.spec.ts
  • frontends/ui/src/shared/components/Sources/source-utils.ts
  • frontends/ui/src/shared/components/Sources/types.ts
  • frontends/ui/src/shared/components/Surface/Card.spec.tsx
  • frontends/ui/src/shared/components/Surface/Card.tsx
  • frontends/ui/src/shared/components/research/StatusDot.tsx
  • frontends/ui/src/shared/components/research/ToolCallRow.tsx
  • frontends/ui/src/shared/components/research/index.ts
  • frontends/ui/src/shared/components/research/research-labels.spec.ts
  • frontends/ui/src/shared/components/research/research-labels.ts
  • frontends/ui/src/shared/lib/cn.spec.ts
  • frontends/ui/src/shared/lib/cn.ts
  • frontends/ui/src/shared/lib/humanize.spec.ts
  • frontends/ui/src/shared/lib/humanize.ts
  • frontends/ui/src/shared/lib/motion.spec.tsx
  • frontends/ui/src/shared/lib/motion.tsx
📜 Review details
🧰 Additional context used
📓 Path-based instructions (4)
frontends/ui/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

frontends/ui/**/*.{ts,tsx}: Use the existing Next.js, React, TypeScript, Tailwind, and KUI patterns; reuse KUI components instead of introducing new equivalent UI components.
Access the backend through the proxy and BACKEND_URL, and preserve authentication-aware UI states.
Validate UI-affecting changes with npm run lint, npm run type-check, and npm run test:ci; include a screenshot for visible changes.

Files:

  • frontends/ui/src/shared/lib/cn.spec.ts
  • frontends/ui/src/shared/lib/cn.ts
  • frontends/ui/src/shared/components/CollapsibleBlock/index.ts
  • frontends/ui/src/shared/lib/humanize.spec.ts
  • frontends/ui/src/shared/components/Sources/SourceList.spec.tsx
  • frontends/ui/src/shared/lib/motion.tsx
  • frontends/ui/src/shared/components/CollapsibleBlock/CollapsibleBlock.spec.tsx
  • frontends/ui/src/shared/components/Sources/SourceStrip.spec.tsx
  • frontends/ui/src/shared/components/Mermaid/index.ts
  • frontends/ui/src/shared/components/MarkdownRenderer/rehype-citations.ts
  • frontends/ui/src/shared/lib/motion.spec.tsx
  • frontends/ui/src/shared/components/research/ToolCallRow.tsx
  • frontends/ui/src/shared/components/MarkdownRenderer/types.ts
  • frontends/ui/src/shared/components/research/research-labels.spec.ts
  • frontends/ui/src/shared/components/MarkdownRenderer/rehype-citations.spec.ts
  • frontends/ui/src/shared/components/Sources/types.ts
  • frontends/ui/src/shared/components/Sources/SourceKindIcon.tsx
  • frontends/ui/src/features/layout/data-sources.spec.ts
  • frontends/ui/src/shared/components/CollapsibleBlock/CollapsibleBlock.tsx
  • frontends/ui/src/adapters/api/websocket-client.ts
  • frontends/ui/src/shared/components/Surface/Card.tsx
  • frontends/ui/src/shared/components/Actions/CopyButton.spec.tsx
  • frontends/ui/src/shared/components/research/index.ts
  • frontends/ui/src/features/chat/lib/deep-research-trace.ts
  • frontends/ui/src/shared/components/Sources/SourceStrip.tsx
  • frontends/ui/src/shared/lib/humanize.ts
  • frontends/ui/src/features/chat/lib/deep-research-trace.spec.ts
  • frontends/ui/src/shared/components/research/StatusDot.tsx
  • frontends/ui/src/shared/components/Mermaid/MermaidBlock.spec.tsx
  • frontends/ui/src/shared/components/MarkdownRenderer/MarkdownRenderer.spec.tsx
  • frontends/ui/src/shared/components/Actions/CopyButton.tsx
  • frontends/ui/src/features/layout/types.ts
  • frontends/ui/src/features/layout/store.ts
  • frontends/ui/src/features/layout/data-sources.ts
  • frontends/ui/src/shared/components/MarkdownRenderer/Citation.spec.tsx
  • frontends/ui/src/shared/components/MarkdownRenderer/Citation.tsx
  • frontends/ui/src/shared/components/Surface/Card.spec.tsx
  • frontends/ui/src/features/chat/lib/intermediate-step-parser.spec.ts
  • frontends/ui/src/shared/components/Sources/parse-references.spec.ts
  • frontends/ui/src/features/layout/store.spec.ts
  • frontends/ui/src/features/chat/store.ts
  • frontends/ui/src/shared/components/Mermaid/MermaidBlock.tsx
  • frontends/ui/src/shared/components/Sources/source-utils.ts
  • frontends/ui/src/features/chat/types.ts
  • frontends/ui/src/shared/components/Sources/source-utils.spec.ts
  • frontends/ui/src/features/chat/store.spec.ts
  • frontends/ui/src/shared/components/Sources/SourceList.tsx
  • frontends/ui/src/features/chat/hooks/use-websocket-chat.ts
  • frontends/ui/src/shared/components/research/research-labels.ts
  • frontends/ui/src/shared/components/Sources/parse-references.ts
  • frontends/ui/src/features/chat/lib/intermediate-step-parser.ts
  • frontends/ui/src/shared/components/MarkdownRenderer/MarkdownRenderer.tsx
**/*

📄 CodeRabbit inference engine (AGENTS.md)

**/*: Keep changes inside this repository, avoid editing adjacent repositories, and scope changes to the smallest relevant independent package, especially under sources/.
Run the narrowest relevant validation command first and broaden to the full suite only when a change crosses shared boundaries.
Keep pull requests scoped, exclude unrelated files and generated artifacts, never include secrets, and provide validation commands and results.

**/*: Do not include secrets, credentials, private hostnames, internal-only logs, customer data, or generated local artifacts.
Add or update tests for behavior changes.

Files:

  • frontends/ui/src/shared/lib/cn.spec.ts
  • frontends/ui/src/shared/lib/cn.ts
  • frontends/ui/src/shared/components/CollapsibleBlock/index.ts
  • frontends/ui/src/shared/lib/humanize.spec.ts
  • frontends/ui/src/shared/components/Sources/SourceList.spec.tsx
  • frontends/ui/src/shared/lib/motion.tsx
  • frontends/ui/src/shared/components/CollapsibleBlock/CollapsibleBlock.spec.tsx
  • frontends/ui/src/shared/components/Sources/SourceStrip.spec.tsx
  • frontends/ui/src/shared/components/Mermaid/index.ts
  • frontends/ui/src/shared/components/MarkdownRenderer/rehype-citations.ts
  • frontends/ui/src/shared/lib/motion.spec.tsx
  • frontends/ui/src/shared/components/research/ToolCallRow.tsx
  • frontends/ui/src/shared/components/MarkdownRenderer/types.ts
  • frontends/ui/src/shared/components/research/research-labels.spec.ts
  • frontends/ui/src/shared/components/MarkdownRenderer/rehype-citations.spec.ts
  • frontends/ui/src/shared/components/Sources/types.ts
  • frontends/ui/src/shared/components/Sources/SourceKindIcon.tsx
  • frontends/ui/src/features/layout/data-sources.spec.ts
  • frontends/ui/src/shared/components/CollapsibleBlock/CollapsibleBlock.tsx
  • frontends/ui/src/adapters/api/websocket-client.ts
  • frontends/ui/src/shared/components/Surface/Card.tsx
  • frontends/ui/src/shared/components/Actions/CopyButton.spec.tsx
  • frontends/ui/src/shared/components/research/index.ts
  • frontends/ui/src/features/chat/lib/deep-research-trace.ts
  • frontends/ui/src/shared/components/Sources/SourceStrip.tsx
  • frontends/ui/src/shared/lib/humanize.ts
  • frontends/ui/src/features/chat/lib/deep-research-trace.spec.ts
  • frontends/ui/src/shared/components/research/StatusDot.tsx
  • frontends/ui/src/shared/components/Mermaid/MermaidBlock.spec.tsx
  • frontends/ui/src/shared/components/MarkdownRenderer/MarkdownRenderer.spec.tsx
  • frontends/ui/package.json
  • frontends/ui/src/shared/components/Actions/CopyButton.tsx
  • frontends/ui/src/features/layout/types.ts
  • frontends/ui/src/features/layout/store.ts
  • frontends/ui/src/features/layout/data-sources.ts
  • frontends/ui/src/shared/components/MarkdownRenderer/Citation.spec.tsx
  • frontends/ui/src/app/globals.css
  • frontends/ui/src/shared/components/MarkdownRenderer/Citation.tsx
  • frontends/ui/src/shared/components/Surface/Card.spec.tsx
  • frontends/ui/src/features/chat/lib/intermediate-step-parser.spec.ts
  • frontends/ui/src/shared/components/Sources/parse-references.spec.ts
  • frontends/ui/src/features/layout/store.spec.ts
  • frontends/ui/src/features/chat/store.ts
  • frontends/ui/src/shared/components/Mermaid/MermaidBlock.tsx
  • frontends/ui/src/shared/components/Sources/source-utils.ts
  • frontends/ui/src/features/chat/types.ts
  • frontends/ui/src/shared/components/Sources/source-utils.spec.ts
  • frontends/ui/src/features/chat/store.spec.ts
  • frontends/ui/src/shared/components/Sources/SourceList.tsx
  • frontends/ui/src/features/chat/hooks/use-websocket-chat.ts
  • frontends/ui/src/shared/components/research/research-labels.ts
  • frontends/ui/src/shared/components/Sources/parse-references.ts
  • frontends/ui/src/features/chat/lib/intermediate-step-parser.ts
  • frontends/ui/src/shared/components/MarkdownRenderer/MarkdownRenderer.tsx
frontends/ui/**/*.{js,jsx,ts,tsx,css,scss}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

For UI changes, run npm ci, npm run lint, npm run type-check, npm run test:ci, and npm run build from frontends/ui.

Files:

  • frontends/ui/src/shared/lib/cn.spec.ts
  • frontends/ui/src/shared/lib/cn.ts
  • frontends/ui/src/shared/components/CollapsibleBlock/index.ts
  • frontends/ui/src/shared/lib/humanize.spec.ts
  • frontends/ui/src/shared/components/Sources/SourceList.spec.tsx
  • frontends/ui/src/shared/lib/motion.tsx
  • frontends/ui/src/shared/components/CollapsibleBlock/CollapsibleBlock.spec.tsx
  • frontends/ui/src/shared/components/Sources/SourceStrip.spec.tsx
  • frontends/ui/src/shared/components/Mermaid/index.ts
  • frontends/ui/src/shared/components/MarkdownRenderer/rehype-citations.ts
  • frontends/ui/src/shared/lib/motion.spec.tsx
  • frontends/ui/src/shared/components/research/ToolCallRow.tsx
  • frontends/ui/src/shared/components/MarkdownRenderer/types.ts
  • frontends/ui/src/shared/components/research/research-labels.spec.ts
  • frontends/ui/src/shared/components/MarkdownRenderer/rehype-citations.spec.ts
  • frontends/ui/src/shared/components/Sources/types.ts
  • frontends/ui/src/shared/components/Sources/SourceKindIcon.tsx
  • frontends/ui/src/features/layout/data-sources.spec.ts
  • frontends/ui/src/shared/components/CollapsibleBlock/CollapsibleBlock.tsx
  • frontends/ui/src/adapters/api/websocket-client.ts
  • frontends/ui/src/shared/components/Surface/Card.tsx
  • frontends/ui/src/shared/components/Actions/CopyButton.spec.tsx
  • frontends/ui/src/shared/components/research/index.ts
  • frontends/ui/src/features/chat/lib/deep-research-trace.ts
  • frontends/ui/src/shared/components/Sources/SourceStrip.tsx
  • frontends/ui/src/shared/lib/humanize.ts
  • frontends/ui/src/features/chat/lib/deep-research-trace.spec.ts
  • frontends/ui/src/shared/components/research/StatusDot.tsx
  • frontends/ui/src/shared/components/Mermaid/MermaidBlock.spec.tsx
  • frontends/ui/src/shared/components/MarkdownRenderer/MarkdownRenderer.spec.tsx
  • frontends/ui/src/shared/components/Actions/CopyButton.tsx
  • frontends/ui/src/features/layout/types.ts
  • frontends/ui/src/features/layout/store.ts
  • frontends/ui/src/features/layout/data-sources.ts
  • frontends/ui/src/shared/components/MarkdownRenderer/Citation.spec.tsx
  • frontends/ui/src/app/globals.css
  • frontends/ui/src/shared/components/MarkdownRenderer/Citation.tsx
  • frontends/ui/src/shared/components/Surface/Card.spec.tsx
  • frontends/ui/src/features/chat/lib/intermediate-step-parser.spec.ts
  • frontends/ui/src/shared/components/Sources/parse-references.spec.ts
  • frontends/ui/src/features/layout/store.spec.ts
  • frontends/ui/src/features/chat/store.ts
  • frontends/ui/src/shared/components/Mermaid/MermaidBlock.tsx
  • frontends/ui/src/shared/components/Sources/source-utils.ts
  • frontends/ui/src/features/chat/types.ts
  • frontends/ui/src/shared/components/Sources/source-utils.spec.ts
  • frontends/ui/src/features/chat/store.spec.ts
  • frontends/ui/src/shared/components/Sources/SourceList.tsx
  • frontends/ui/src/features/chat/hooks/use-websocket-chat.ts
  • frontends/ui/src/shared/components/research/research-labels.ts
  • frontends/ui/src/shared/components/Sources/parse-references.ts
  • frontends/ui/src/features/chat/lib/intermediate-step-parser.ts
  • frontends/ui/src/shared/components/MarkdownRenderer/MarkdownRenderer.tsx
frontends/ui/**/*

⚙️ CodeRabbit configuration file

frontends/ui/**/*: Review UI changes for strict TypeScript behavior, API contract alignment, auth/session handling, accessible controls,
resilient loading and error states, and report/chat state consistency. Prefer existing UI patterns and require tests
for changed user-visible workflows.

Files:

  • frontends/ui/src/shared/lib/cn.spec.ts
  • frontends/ui/src/shared/lib/cn.ts
  • frontends/ui/src/shared/components/CollapsibleBlock/index.ts
  • frontends/ui/src/shared/lib/humanize.spec.ts
  • frontends/ui/src/shared/components/Sources/SourceList.spec.tsx
  • frontends/ui/src/shared/lib/motion.tsx
  • frontends/ui/src/shared/components/CollapsibleBlock/CollapsibleBlock.spec.tsx
  • frontends/ui/src/shared/components/Sources/SourceStrip.spec.tsx
  • frontends/ui/src/shared/components/Mermaid/index.ts
  • frontends/ui/src/shared/components/MarkdownRenderer/rehype-citations.ts
  • frontends/ui/src/shared/lib/motion.spec.tsx
  • frontends/ui/src/shared/components/research/ToolCallRow.tsx
  • frontends/ui/src/shared/components/MarkdownRenderer/types.ts
  • frontends/ui/src/shared/components/research/research-labels.spec.ts
  • frontends/ui/src/shared/components/MarkdownRenderer/rehype-citations.spec.ts
  • frontends/ui/src/shared/components/Sources/types.ts
  • frontends/ui/src/shared/components/Sources/SourceKindIcon.tsx
  • frontends/ui/src/features/layout/data-sources.spec.ts
  • frontends/ui/src/shared/components/CollapsibleBlock/CollapsibleBlock.tsx
  • frontends/ui/src/adapters/api/websocket-client.ts
  • frontends/ui/src/shared/components/Surface/Card.tsx
  • frontends/ui/src/shared/components/Actions/CopyButton.spec.tsx
  • frontends/ui/src/shared/components/research/index.ts
  • frontends/ui/src/features/chat/lib/deep-research-trace.ts
  • frontends/ui/src/shared/components/Sources/SourceStrip.tsx
  • frontends/ui/src/shared/lib/humanize.ts
  • frontends/ui/src/features/chat/lib/deep-research-trace.spec.ts
  • frontends/ui/src/shared/components/research/StatusDot.tsx
  • frontends/ui/src/shared/components/Mermaid/MermaidBlock.spec.tsx
  • frontends/ui/src/shared/components/MarkdownRenderer/MarkdownRenderer.spec.tsx
  • frontends/ui/package.json
  • frontends/ui/src/shared/components/Actions/CopyButton.tsx
  • frontends/ui/src/features/layout/types.ts
  • frontends/ui/src/features/layout/store.ts
  • frontends/ui/src/features/layout/data-sources.ts
  • frontends/ui/src/shared/components/MarkdownRenderer/Citation.spec.tsx
  • frontends/ui/src/app/globals.css
  • frontends/ui/src/shared/components/MarkdownRenderer/Citation.tsx
  • frontends/ui/src/shared/components/Surface/Card.spec.tsx
  • frontends/ui/src/features/chat/lib/intermediate-step-parser.spec.ts
  • frontends/ui/src/shared/components/Sources/parse-references.spec.ts
  • frontends/ui/src/features/layout/store.spec.ts
  • frontends/ui/src/features/chat/store.ts
  • frontends/ui/src/shared/components/Mermaid/MermaidBlock.tsx
  • frontends/ui/src/shared/components/Sources/source-utils.ts
  • frontends/ui/src/features/chat/types.ts
  • frontends/ui/src/shared/components/Sources/source-utils.spec.ts
  • frontends/ui/src/features/chat/store.spec.ts
  • frontends/ui/src/shared/components/Sources/SourceList.tsx
  • frontends/ui/src/features/chat/hooks/use-websocket-chat.ts
  • frontends/ui/src/shared/components/research/research-labels.ts
  • frontends/ui/src/shared/components/Sources/parse-references.ts
  • frontends/ui/src/features/chat/lib/intermediate-step-parser.ts
  • frontends/ui/src/shared/components/MarkdownRenderer/MarkdownRenderer.tsx
🪛 ast-grep (0.44.1)
frontends/ui/src/shared/components/Mermaid/MermaidBlock.tsx

[warning] 254-254: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation

(react-unsafe-html-injection)

frontends/ui/src/shared/components/research/research-labels.ts

[warning] 167-167: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(["']${escapedKey}["']\\s*:\\s*"((?:\\\\.|[^"\\\\])*)")
Note: [CWE-1333] Inefficient Regular Expression Complexity

(regexp-from-variable)


[warning] 172-172: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(["']${escapedKey}["']\\s*:\\s*'((?:\\\\.|[^'\\\\])*)')
Note: [CWE-1333] Inefficient Regular Expression Complexity

(regexp-from-variable)

🪛 OpenGrep (1.25.0)
frontends/ui/src/shared/components/MarkdownRenderer/rehype-citations.ts

[ERROR] 26-26: Dynamic command passed to child_process.exec/execSync. Use child_process.execFile or spawn with an argument array instead.

(coderabbit.command-injection.exec-js)

frontends/ui/src/shared/components/Mermaid/MermaidBlock.tsx

[WARNING] 246-256: dangerouslySetInnerHTML with dynamic content can lead to XSS. Sanitize the input with a library like DOMPurify before rendering.

(coderabbit.xss.react-dangerously-set-innerhtml)

🔇 Additional comments (54)
frontends/ui/package.json (1)

27-43: 🩺 Stability & Availability

Verify the Next.js 16 runtime and peer contract.

The manifest declares next ^16.2.6, but the supplied context does not show the repository’s Node.js engine, CI image, or resolved peer dependencies. Next.js 16 documents Node.js 20.9+ as the minimum; verify the toolchain and that this Next.js version resolves correctly with the declared React 18.2.0 pair. (nextjs.org)

Run npm view next@16.2.6 peerDependencies engines and inspect the repository’s CI/container declarations before merging.

frontends/ui/src/app/globals.css (1)

315-685: LGTM!

frontends/ui/src/shared/components/Actions/CopyButton.tsx (1)

1-55: LGTM!

frontends/ui/src/shared/lib/humanize.ts (1)

1-26: LGTM!

frontends/ui/src/shared/lib/motion.spec.tsx (1)

1-47: LGTM!

frontends/ui/src/shared/lib/motion.tsx (1)

14-17: 🎯 Functional Correctness

No change needed for reduced-motion startup. useReducedMotion() is intentionally initialized to false to keep server and first client render identical; the post-mount update is an accepted trade-off here.

			> Likely an incorrect or invalid review comment.
frontends/ui/src/shared/components/CollapsibleBlock/CollapsibleBlock.tsx (1)

80-92: aria-controls still points to an unmounted node when collapsed.

The button always exposes aria-controls={regionId}, but the matching element is removed when open is false. Keep a stable wrapper carrying id={regionId} and animate or conditionally render its contents inside it.

As per path instructions, UI changes must be reviewed for accessible controls.

Source: Path instructions

frontends/ui/src/shared/components/CollapsibleBlock/CollapsibleBlock.spec.tsx (2)

19-24: Cover the remaining disclosure branches.

The closed-state test does not verify that aria-controls resolves, and the static test uses open shorthand instead of proving that collapsible={false} remains visible with open={false}.

As per path instructions, changed user-visible workflows require tests.

Also applies to: 44-51

Source: Path instructions


26-42: LGTM!

frontends/ui/src/shared/components/CollapsibleBlock/index.ts (1)

4-4: LGTM!

frontends/ui/src/shared/lib/cn.spec.ts (1)

7-20: LGTM!

frontends/ui/src/shared/lib/cn.ts (1)

1-13: LGTM!

frontends/ui/src/shared/lib/humanize.spec.ts (1)

7-27: LGTM!

frontends/ui/src/shared/components/Sources/SourceList.tsx (1)

40-61: 🩺 Stability & Availability | ⚡ Quick win

External links open in a new tab without an accessible hint.

Same issue as previously flagged, not yet addressed here. See consolidated comment for both this file and SourceStrip.tsx.

Source: Path instructions

frontends/ui/src/shared/components/Sources/SourceList.spec.tsx (1)

37-42: 📐 Maintainability & Code Quality | 💤 Low value

Fixture doesn't exercise the no-domain-link branch (still open).

www.nvidia.com/newsprettyDomain(url) (nvidia.com), so showDomain stays true here — same gap as previously flagged.

✅ Proposed fix
-    render(<SourceList sources={[{ ...webSource, label: 'www.nvidia.com/news' }]} />)
+    render(<SourceList sources={[{ ...webSource, label: 'nvidia.com' }]} />)
frontends/ui/src/shared/components/Sources/SourceStrip.tsx (3)

62-64: 🎯 Functional Correctness | ⚡ Quick win

previewCount still unguarded before subtracting 1 (previously flagged).

previewCount <= 0 or non-integer values still flow straight into slice(0, previewCount - 1) / hiddenCount without normalization, unlike the earlier suggested fix.


38-42: 🩺 Stability & Availability | ⚡ Quick win

External links open in a new tab without an accessible hint.

Same unresolved issue as previously flagged for both this file and SourceList.tsx. See consolidated comment.

Source: Path instructions


21-26: 🎯 Functional Correctness

No issue: Card already supports web and doc tones. tone={source.kind} matches CardTone, so there’s no type or contract mismatch here.

			> Likely an incorrect or invalid review comment.
frontends/ui/src/shared/components/Sources/types.ts (1)

1-22: LGTM!

frontends/ui/src/shared/components/Sources/source-utils.ts (1)

10-58: LGTM!

frontends/ui/src/features/layout/data-sources.ts (1)

47-124: LGTM!

frontends/ui/src/shared/components/Sources/source-utils.spec.ts (1)

1-58: LGTM!

frontends/ui/src/features/layout/data-sources.spec.ts (1)

1-45: LGTM!

frontends/ui/src/shared/components/Sources/SourceKindIcon.tsx (1)

17-33: LGTM!

frontends/ui/src/shared/components/MarkdownRenderer/Citation.spec.tsx (1)

1-59: LGTM!

frontends/ui/src/shared/components/Sources/SourceStrip.spec.tsx (1)

1-58: LGTM!

frontends/ui/src/shared/components/MarkdownRenderer/MarkdownRenderer.tsx (3)

9-11: KaTeX still eagerly loaded for every render.

Same as flagged previously — rehype-katex and its CSS remain statically imported at module scope instead of lazy-loaded like Mermaid.


278-294: Plugin arrays still recreated on every render.

remarkPlugins/rehypePlugins remain inline literals unmemoized, unlike components. Same concern as flagged previously.


290-291: rehypeCitations still runs before rehypeKatex.

Same functional-correctness concern as previously flagged: bracketed LaTeX arguments (e.g. \sqrt[3]{x}) can still be misinterpreted as citation markers before KaTeX processes them. No regression test for mixed math+citation content has been added either.

frontends/ui/src/shared/components/MarkdownRenderer/Citation.tsx (1)

32-72: Tooltip still not linked via aria-describedby.

Same accessibility gap as previously flagged — screen reader users focusing the citation mark won't get the tooltip title/snippet announced.

frontends/ui/src/shared/components/MarkdownRenderer/rehype-citations.spec.ts (1)

20-42: Missing regression test for the code/pre skip behavior.

Now that rehype-citations.ts skips text under code/pre, add a case asserting a <code> child containing [1] stays unchanged, as previously suggested.

frontends/ui/src/shared/components/Mermaid/MermaidBlock.tsx (1)

246-256: SVG injection still unsanitized beyond securityLevel: 'strict'.

Same defense-in-depth concern as previously flagged — diagram source can be model/user-influenced, and strict mode alone is the only mitigation before dangerouslySetInnerHTML.

frontends/ui/src/shared/components/Sources/parse-references.spec.ts (1)

1-203: LGTM!

frontends/ui/src/shared/components/MarkdownRenderer/types.ts (1)

4-5: LGTM!

Also applies to: 20-27

frontends/ui/src/shared/components/MarkdownRenderer/rehype-citations.ts (1)

15-48: LGTM!

frontends/ui/src/shared/components/MarkdownRenderer/MarkdownRenderer.spec.tsx (1)

352-429: LGTM!

frontends/ui/src/shared/components/Mermaid/index.ts (1)

1-5: LGTM!

frontends/ui/src/shared/components/Mermaid/MermaidBlock.spec.tsx (1)

1-116: LGTM!

frontends/ui/src/shared/components/Surface/Card.spec.tsx (1)

1-46: LGTM!

frontends/ui/src/shared/components/Surface/Card.tsx (1)

1-41: LGTM!

frontends/ui/src/features/chat/hooks/use-websocket-chat.ts (1)

844-880: LGTM!

Also applies to: 1224-1224, 1250-1250

frontends/ui/src/features/chat/store.ts (1)

453-464: LGTM!

Also applies to: 754-786, 1285-1404

frontends/ui/src/features/chat/types.ts (1)

64-64: LGTM!

Also applies to: 185-187, 219-231, 470-470, 502-503, 642-643

frontends/ui/src/features/chat/lib/deep-research-trace.spec.ts (1)

1-193: LGTM!

frontends/ui/src/features/chat/store.spec.ts (1)

14-45: LGTM!

Also applies to: 184-207, 612-631, 1305-1381, 2311-2373

frontends/ui/src/features/chat/lib/intermediate-step-parser.ts (1)

14-15: LGTM!

Also applies to: 99-107, 127-150, 182-238

frontends/ui/src/features/layout/store.ts (1)

25-26: LGTM!

Also applies to: 36-37, 58-99, 126-134

frontends/ui/src/features/layout/store.spec.ts (1)

21-22: LGTM!

Also applies to: 32-33, 139-222

frontends/ui/src/features/layout/types.ts (2)

91-96: Prior setSelectedModel clearing issue resolved.

setSelectedModel now accepts string | undefined, addressing the earlier flagged gap around restoring backend-default behavior.


28-34: LGTM!

Also applies to: 53-56, 73-76

frontends/ui/src/features/chat/lib/intermediate-step-parser.spec.ts (1)

1-125: LGTM!

frontends/ui/src/shared/components/research/research-labels.ts (1)

1-245: LGTM!

frontends/ui/src/shared/components/research/index.ts (1)

1-19: LGTM!

frontends/ui/src/shared/components/research/research-labels.spec.ts (1)

1-113: LGTM!

Comment on lines 228 to 240
sendMessage = (
content: string,
enabledDataSources?: string[],
activeReportJobId?: string
activeReportJobId?: string,
selectedModel?: string
): string | null => {
// Format the text content as JSON with query and data_sources
const textContent = JSON.stringify({
query: content,
data_sources: enabledDataSources ?? [],
...(activeReportJobId ? { active_report_job_id: activeReportJobId } : {}),
...(selectedModel ? { model: selectedModel } : {}),
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Unnecessary call-shape branching around sendMessage's already-uniform JSON construction.

sendMessage's body spreads activeReportJobId/selectedModel into the JSON payload conditionally regardless of how many arguments are passed — a 2-arg, 3-arg, and 4-arg call with the same underlying values produce identical wire output. The downstream 3-way branch in sendOutgoingPayload (justified by a comment about "preserving call shapes" for the backend) is therefore dead complexity, and the growing list of optional positional string params (now 4) makes future mis-ordering easier.

  • frontends/ui/src/adapters/api/websocket-client.ts#L228-L240: document the new selectedModel param in the JSDoc, and consider consolidating the growing optional positional args into a single options object to prevent future mis-ordering.
  • frontends/ui/src/features/chat/hooks/use-websocket-chat.ts#L594-L609: collapse the 3-way branch into a single unconditional call, since sendMessage already treats undefined activeReportJobId/selectedModel as absent.
♻️ Proposed simplification
-        if (payload.selectedModel) {
-          outboundId = client.sendMessage(
-            payload.content,
-            payload.dataSources,
-            payload.activeReportJobId,
-            payload.selectedModel
-          )
-        } else if (payload.activeReportJobId) {
-          outboundId = client.sendMessage(payload.content, payload.dataSources, payload.activeReportJobId)
-        } else {
-          outboundId = client.sendMessage(payload.content, payload.dataSources)
-        }
+        outboundId = client.sendMessage(
+          payload.content,
+          payload.dataSources,
+          payload.activeReportJobId,
+          payload.selectedModel
+        )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
sendMessage = (
content: string,
enabledDataSources?: string[],
activeReportJobId?: string
activeReportJobId?: string,
selectedModel?: string
): string | null => {
// Format the text content as JSON with query and data_sources
const textContent = JSON.stringify({
query: content,
data_sources: enabledDataSources ?? [],
...(activeReportJobId ? { active_report_job_id: activeReportJobId } : {}),
...(selectedModel ? { model: selectedModel } : {}),
})
if (payload.kind === 'message') {
outboundId = client.sendMessage(
payload.content,
payload.dataSources,
payload.activeReportJobId,
payload.selectedModel
)
📍 Affects 2 files
  • frontends/ui/src/adapters/api/websocket-client.ts#L228-L240 (this comment)
  • frontends/ui/src/features/chat/hooks/use-websocket-chat.ts#L594-L609
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontends/ui/src/adapters/api/websocket-client.ts` around lines 228 - 240,
The sendMessage API has uniform JSON construction, so document selectedModel in
its JSDoc and consolidate the growing optional positional parameters into a
single options object while preserving payload field behavior; update
frontends/ui/src/adapters/api/websocket-client.ts lines 228-240 accordingly. In
frontends/ui/src/features/chat/hooks/use-websocket-chat.ts lines 594-609,
replace the 3-way sendOutgoingPayload branching with one unconditional
sendMessage call using the available values.

const truncate = (text: string, max = 120): string =>
text.length > max ? `${text.slice(0, max - 1).trimEnd()}…` : text

const isExplanationTool = (name: string): boolean => /explain/i.test(name)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

isExplanationTool's open substring match risks false positives.

/explain/i.test(name) matches any tool name containing "explain" as a substring anywhere (e.g. a hypothetical unexplained_variance_tool), not just explanation-purpose tools, incorrectly folding its output into an explanation block.

🔧 Proposed fix
-const isExplanationTool = (name: string): boolean => /explain/i.test(name)
+const isExplanationTool = (name: string): boolean => /^explain/i.test(name)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const isExplanationTool = (name: string): boolean => /explain/i.test(name)
const isExplanationTool = (name: string): boolean => /^explain/i.test(name)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontends/ui/src/features/chat/lib/deep-research-trace.ts` at line 28, Update
isExplanationTool to recognize only tool names that represent explanation tools,
replacing the unrestricted substring match with an anchored or otherwise precise
name pattern. Preserve case-insensitive matching while preventing names such as
unexplained_variance_tool from being classified as explanation tools.

Comment on lines +4 to +35
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
import { beforeEach, describe, expect, test, vi } from 'vitest'
import { CopyButton } from './CopyButton'

const writeText = vi.fn().mockResolvedValue(undefined)

describe('CopyButton', () => {
beforeEach(() => {
writeText.mockClear()
Object.defineProperty(navigator, 'clipboard', { value: { writeText }, configurable: true })
})

test('copies the provided text and confirms with a checkmark', async () => {
render(<CopyButton text="There are 11,463 users." />)
fireEvent.click(screen.getByRole('button', { name: 'Copy' }))
expect(writeText).toHaveBeenCalledWith('There are 11,463 users.')
await waitFor(() => expect(screen.getByRole('button', { name: 'Copied' })).toBeInTheDocument())
})

test('renders a custom label', () => {
render(<CopyButton text="x" label="Copy answer" />)
expect(screen.getByRole('button', { name: 'Copy answer' })).toBeInTheDocument()
})

test('does not throw when the clipboard API rejects', async () => {
writeText.mockRejectedValueOnce(new Error('blocked'))
render(<CopyButton text="x" />)
fireEvent.click(screen.getByRole('button', { name: 'Copy' }))
await waitFor(() => expect(writeText).toHaveBeenCalled())
expect(screen.getByRole('button', { name: 'Copy' })).toBeInTheDocument()
})
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Cover the timer lifecycle in the component tests.

The suite covers successful and failed copies, but not the behavior added around the reset timer: a newer successful click must cancel the older timer, and unmount must clear the pending timer. Add focused fake-timer tests for both cases.

As per coding guidelines, “Add or update tests for behavior changes.” As per path instructions, changed user-visible workflows require tests.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontends/ui/src/shared/components/Actions/CopyButton.spec.tsx` around lines
4 - 35, The CopyButton tests lack coverage for reset-timer lifecycle behavior.
Extend the CopyButton suite with focused fake-timer tests verifying a newer
successful copy cancels the previous reset timer and unmounting clears any
pending timer; enable and restore fake timers per test, and use the existing
clipboard mock and CopyButton interactions.

Sources: Coding guidelines, Path instructions

Comment on lines +99 to +121
const fit = useCallback(() => {
const el = scrollRef.current
if (!el || !base) return
const avail = el.clientWidth - 32
if (avail > 0) setScale(clamp(avail / base.w))
}, [base])

useEffect(() => {
fit()
}, [fit])

useEffect(() => {
fit()
}, [fullscreen, fit])

useEffect(() => {
if (!fullscreen) return
const onKey = (e: KeyboardEvent): void => {
if (e.key === 'Escape') setFullscreen(false)
}
window.addEventListener('keydown', onKey)
return () => window.removeEventListener('keydown', onKey)
}, [fullscreen])

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Diagram doesn't refit on window resize outside of fullscreen toggling.

fit() only re-runs on mount and on fullscreen change (Lines 106-112); resizing the browser window while viewing a non-fullscreen diagram leaves the scale stale until some other state change triggers a re-render.

♻️ Proposed fix: refit on container resize
   useEffect(() => {
     fit()
   }, [fit])

   useEffect(() => {
     fit()
   }, [fullscreen, fit])
+
+  useEffect(() => {
+    const el = scrollRef.current
+    if (!el) return
+    const observer = new ResizeObserver(() => fit())
+    observer.observe(el)
+    return () => observer.disconnect()
+  }, [fit])
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const fit = useCallback(() => {
const el = scrollRef.current
if (!el || !base) return
const avail = el.clientWidth - 32
if (avail > 0) setScale(clamp(avail / base.w))
}, [base])
useEffect(() => {
fit()
}, [fit])
useEffect(() => {
fit()
}, [fullscreen, fit])
useEffect(() => {
if (!fullscreen) return
const onKey = (e: KeyboardEvent): void => {
if (e.key === 'Escape') setFullscreen(false)
}
window.addEventListener('keydown', onKey)
return () => window.removeEventListener('keydown', onKey)
}, [fullscreen])
const fit = useCallback(() => {
const el = scrollRef.current
if (!el || !base) return
const avail = el.clientWidth - 32
if (avail > 0) setScale(clamp(avail / base.w))
}, [base])
useEffect(() => {
fit()
}, [fit])
useEffect(() => {
fit()
}, [fullscreen, fit])
useEffect(() => {
const el = scrollRef.current
if (!el) return
const observer = new ResizeObserver(() => fit())
observer.observe(el)
return () => observer.disconnect()
}, [fit])
useEffect(() => {
if (!fullscreen) return
const onKey = (e: KeyboardEvent): void => {
if (e.key === 'Escape') setFullscreen(false)
}
window.addEventListener('keydown', onKey)
return () => window.removeEventListener('keydown', onKey)
}, [fullscreen])
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontends/ui/src/shared/components/Mermaid/MermaidBlock.tsx` around lines 99
- 121, Update the MermaidBlock resize handling around fit and scrollRef so the
diagram refits whenever its container dimensions change, including browser
resizing while not fullscreen. Observe the container with an appropriate resize
listener or ResizeObserver, invoke fit on resize, and clean up the subscription
when the component unmounts; preserve the existing fullscreen and Escape-key
behavior.

Comment on lines +15 to +20
export const StatusDot: FC<{ state: NodeState; size?: 'sm' | 'xs' }> = ({ state, size = 'sm' }) => (
<span
className={cn('status-dot', `status-dot-${state}`, size === 'xs' && 'status-dot-xs')}
aria-hidden="true"
/>
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Node lifecycle state is color-only across the shared trace primitives. StatusDot hides its state span from assistive tech (aria-hidden="true") with no text alternative, and its only consumer renders a label that doesn't change with status, so screen reader users get no indication of running/done/error/interrupted anywhere in the trace UI. Per path instructions, UI changes should be reviewed for accessible controls.

  • frontends/ui/src/shared/components/research/StatusDot.tsx#L15-L20: add a visually-hidden text node or aria-label mapping state to a human-readable word (e.g. "Running", "Done", "Error").
  • frontends/ui/src/shared/components/research/ToolCallRow.tsx#L1-L51: once StatusDot exposes a textual state, no row-level change is needed — the fix is fully contained in StatusDot; if a bespoke per-row summary is preferred instead, pass status through to an aria-label on the row wrapper.
📍 Affects 2 files
  • frontends/ui/src/shared/components/research/StatusDot.tsx#L15-L20 (this comment)
  • frontends/ui/src/shared/components/research/ToolCallRow.tsx#L1-L51
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontends/ui/src/shared/components/research/StatusDot.tsx` around lines 15 -
20, The StatusDot state is currently hidden from assistive technology without a
text alternative. In
frontends/ui/src/shared/components/research/StatusDot.tsx:15-20, expose a
human-readable state label for each NodeState while preserving the visual dot;
frontends/ui/src/shared/components/research/ToolCallRow.tsx:1-51 requires no
direct change because the fix is contained in StatusDot.

Source: Path instructions

@Manushpm8

Copy link
Copy Markdown
Collaborator Author

Consolidating the chat-UI stack into #333. #333 was stacked on this PR and already contains all of this chat trace-state and streaming plumbing cumulatively, with every review comment here addressed there. Closing this so the chat UI merges as a single PR (#333). Nothing is lost. 🙏

@Manushpm8 Manushpm8 closed this Jul 27, 2026
cdgamarose-nv added a commit that referenced this pull request Aug 19, 2026
* feat(ui): add shared className, humanize, and motion utilities

Foundational frontend utilities for the staged chat UI/UX work:
- cn(): clsx + tailwind-merge className composition so conflicting Tailwind
  utilities resolve deterministically.
- titleCaseWords()/ACRONYMS: humanize snake_case and `__`-separated identifiers
  (tool and function names) into display labels, upper-casing common acronyms.
- AppMotionConfig: a Motion provider that honors prefers-reduced-motion.

Adds clsx, tailwind-merge, and motion. Covered 100% by unit tests; no UI
behavior changes yet.

Signed-off-by: Manush Murali <manushm@nvidia.com>

* feat(ui): add shared source, research-trace, and markdown primitives

Adds the UI-only shared component library the chat redesign builds on:
- Sources/*: source chips, list, and reference parsing (web + doc kinds).
- research/*: StatusDot, ToolCallRow, and a generic tool-label engine for the
  live research trace.
- Surface/Card, Actions/CopyButton, CollapsibleBlock, Mermaid.
- MarkdownRenderer upgrade: an answer variant, superscript citations with hover
  popovers, KaTeX math, and Mermaid diagrams. Prod's artifact-URL handling is
  preserved verbatim; chart fences fall through unchanged.

Adds mermaid, katex, rehype-katex, remark-math, unist-util-visit. No
warehouse/predictive features are ported. Covered by unit and render tests.

Signed-off-by: Manush Murali <manushm@nvidia.com>

* fix(ui): address shared-primitive review feedback

- rehype-citations: skip [n] markers inside code/pre so code samples like
  arr[1] are not turned into citations.
- CopyButton: track the reset timeout in a ref, clearing it on re-click and
  unmount.
- SourceList: always render a clickable link for URL-bearing sources, including
  when the label equals the domain.
- source-utils: reuse the computed kind instead of re-testing the URL regex.
- CollapsibleBlock: wire aria-controls to the disclosed region id.
- SourceStrip: expose aria-expanded on the "+N more" button.
- Citation: keep URL-less citations keyboard-focusable.
- Add CollapsibleBlock and SourceList unit tests.

Signed-off-by: Manush Murali <manushm@nvidia.com>

* fix(ui): address shared-primitive review comments

Signed-off-by: Manush Murali <manushm@nvidia.com>

* feat(ui): add chat trace fields, folded-step parser, and sidebar state

Additive state and streaming plumbing that a later UI PR consumes; no visual
change and no prod capability removed.

- chat types: optional ThinkingStep fields (completedAt, status, argSummary),
  ChatMessage.selectedModel, and a plan_approval prompt type.
- intermediate-step-parser: generic folded reasoning/reflection/explanation
  sentinels and predicates, splitPayload/extractFoldedOutput, and label lookup
  via the shared tool-label engine.
- deep-research-trace: replays a stored deep run into the same ThinkingStep
  stream so reloaded reports show an identical trace.
- store + use-websocket-chat: populate the new step fields as the run streams,
  fail steps on error, and thread the selected model through send.
- layout store: a sidebar collapse model added alongside the existing panel
  API, a prompt draft, a selected-model slot, and data-source display helpers.

Per-user auth, WebSocket token rotation, artifact handling, and HITL are
preserved unchanged. No predictive/analytical/warehouse plumbing is ported.

Signed-off-by: Manush Murali <manushm@nvidia.com>

* fix(ui): address chat-state review feedback

- deep-research-trace: preserve tool calls whose referenced agent is missing
  (dangling agentId), not just calls with no agent, and add a regression test.
- store: scope deep-research hydration to the current user's conversations.

Signed-off-by: Manush Murali <manushm@nvidia.com>

* fix(ui): address chat-plumbing review comments

Signed-off-by: Manush Murali <manushm@nvidia.com>

* feat(ui): re-skin the chat experience (composer, trace, panels)

Brings the chat UI to the premium layout: a live phased "thinking" trace
(status dots, durations, tool-call rows, a "using" source chip), answers with
superscript citations and a styled source list, an interactive clarifier, a
restyled composer with a stop control, a collapsible sessions rail, and
push-style data-source and research panels.

- chat: ChatThinking (phased live trace), AgentResponse (citations + sources +
  copy + answer reveal), AgentPrompt (interactive clarifier with plan approval),
  UserMessage and banner polish.
- layout: MainLayout push panels, ChatArea turn grouping + run lane + pinned
  autoscroll, AppBar theme toggle + active data-sources state, InputArea composer
  restyle + stop, SessionsPanel collapsible rail, DataSourcesPanel push re-skin,
  and the research-panel tabs and cards.
- app: mount the reduced-motion Motion provider at the root.

Per-user OAuth, human-in-the-loop navigation blocking, and rich artifact/file
handling are preserved. The model picker is omitted (prod exposes no selectable
models). No predictive/analytical/warehouse UI is ported. Full suite green.

Signed-off-by: Manush Murali <manushm@nvidia.com>

* fix(ui): address chat-look review feedback

- AgentPrompt: do not show "Plan approved" when a responded prompt has no
  response (neutral state).
- AgentResponse: surface report-load failures inline with role="alert" and a
  Retry button instead of only a title tooltip.
- AgentCard: keep distinct tool calls with unrecognized inputs separate, and
  let a terminal state replace a stale running row.
- AgentsTab: count rendered groups (agents plus orphan tool calls), not just
  the agent list.
- ReportTab: bound the expanded generated-files list with a scroll area.
- SessionsPanel: return focus to the search trigger when the search closes.

Signed-off-by: Manush Murali <manushm@nvidia.com>

* feat(ui): restore aiq-rfm surface palette and base font

The re-skin dropped aiq-rfm's custom surface/text/border palette and the base
NVIDIA Sans font-family, so surfaces fell back to KUI defaults. Restore the
light and dark token overrides and the html/body font-family + smoothing so
the theme matches aiq-rfm.

Signed-off-by: Manush Murali <manushm@nvidia.com>

* feat(ui): remove model icon and name from response summary

There is no model selector in the composer, so drop the model favicon + name
badge from the response summary bar (and the local favicon helper). The model
data field is left in place, only the display is removed.

Signed-off-by: Manush Murali <manushm@nvidia.com>

* fix(ui): address chat-look review comments

Signed-off-by: Manush Murali <manushm@nvidia.com>

* fix(ui): address chat-look review comments (round 2)

Signed-off-by: Manush Murali <manushm@nvidia.com>

* fix(ui): address chat-look review (dynamic thinking-trace sources, preserve markdown citations, scope Stop to current session)

Signed-off-by: Manush Murali <manushm@nvidia.com>

* fix(ui): correct citation index resolution, restored-turn duration, and review defects

Address confirmed defects from two adversarial reviews of the chat UI:

- Citation misattribution: carry the real [N] marker on each SourceRef and
  resolve chips/cards by that index instead of array position, so gapped or
  non-1-based numbering no longer maps to the wrong source (splitReferences,
  mapCitationSource, Citation, SourceList, SourceStrip).
- Restored-turn duration: derive the response end time from the last step's
  completedAt/timestamp when a turn is inactive with no responseCompletedAt,
  suppressing the bogus mount-relative "Total response time" (ChatThinking).
- Inline deep-research steps: stop filtering isDeepResearch out of the
  ephemeral path so live and restored traces match (getThinkingStepsForMessage).
- Inline HITL controls: gate a prompt's live actions on its interaction id
  matching the current pendingInteraction, so an older card cannot answer the
  newer interaction (AgentPrompt).
- CollapsibleBlock: only set aria-controls when the region is mounted.
- Mermaid fullscreen dialog: move focus into the dialog on open and restore it
  to the trigger on close.
- Card error tone: use the KUI --border-color-feedback-danger token.
- rehype-citations: do not treat a [n] preceded by a word character as a
  citation (prose like results[1]).
- Remove dead code: extractSchemaTables and hydrateDeepResearchFromMessage.
- Fix the vacuous .research-chart selector in the MarkdownRenderer spec.

Signed-off-by: Manush Murali <manushm@nvidia.com>

* fix(ui): derive research progress from workflow events

Signed-off-by: Kyle Zheng <kyzheng@nvidia.com>

* fix(ui): resolve citations by report numbering and stop rewriting valid Markdown (#333 review)

Fix 1 (ReportTab citations pointing to the wrong source): the report panel
built its inline-citation source list and the SourceStrip from the
discovery-order deepResearchCitations events, so marker [N] in the finished
report resolved to the Nth discovered source rather than the source the report
assigned to [N]. When the report reorders sources ([1]=B, [2]=A) the linked
target was wrong. Make the verified final report the single source of truth:
parse the report's own [N] references (splitReferences already carries the real
[N] as SourceRef.index) and resolve each marker by that index. Discovery-order
citation events are used only as a best-effort fallback when the report has no
structured references block, mapped in discovery order (keyed by marker number)
rather than by filtered array position, which is what previously shifted the
numbering. This mirrors the AgentResponse answer path.

Fix 3 (nestBulletsUnderOrderedItems / renumberOrderedLists rewriting valid
Markdown): removed both from the render path and deleted them. nestBullets
never cleared its in-ordered state on a blank line, so
`1. First\n2. Second\n\n- Separate topic` was re-indented and rendered as a
child of item 2, changing the answer's meaning. renumberOrderedLists is the same
class of bug and is unnecessary: a contiguous ordered list is already renumbered
by the browser's <ol>, while a blank-line-separated second list is an
intentionally separate list that renumber would merge. Removing both stops the
renderer from guessing at Markdown intent (tabularizeEntityLines and
splitReferences are unchanged). Chose removal over scoping because nothing
depends on either for a real malformed-output case.

Signed-off-by: Manush Murali <manushm@nvidia.com>

* fix(ui): correlate deep-research workers by id and record real job completion time (#333 review)

Deep research can run several workers that all share the name
`researcher-agent`. The streaming hook tracked live trace rows by that
shared name (activeStepIdsRef keyed by agent/tool/LLM name), so two
concurrent workers collided: the second start overwrote the first's map
entry, and a finish then completed the wrong row or left the other row
permanently running.

The backend already emits a unique agent run id (LangChain run_id) on
workflow.start/end and the owning agent_id on tool/LLM events, and the
store rows are already id-keyed. This extracts the correlation out of the
compressed callbacks into a named, directly-testable module
(createResearchCorrelator with onWorkflowStart/End, onLLMStart/Chunk/End,
onToolStart/End, and replay adopt helpers). Workers are correlated by
their unique agent id; tool and LLM runs are scoped to the owning agent id
(not the shared name), with a same-name fallback only when an end event
genuinely omits the id. The adapter now forwards agent_id (and the model
name) on llm.start/llm.end so LLM runs correlate by id too.

Also records a real completion timestamp for deep research. The hidden
tracking message is created at job start; its status was patched on the
terminal event but its timestamp was not, so ChatArea read the start
timestamp as the completion time and a multi-minute job showed a few
seconds. The terminal handlers now set responseCompletedAt, and
ChatArea/ChatThinking read that for the duration, composing with the
existing restored-turn fallback.

Tests: new deep-research-correlation spec proves two concurrent
researcher-agent workers stay independent, finishing A does not affect B,
and a worker with no finish stays consistent; ChatThinking and
use-deep-research specs cover the multi-minute duration and terminal-only
completion timestamp.

Signed-off-by: Manush Murali <manushm@nvidia.com>

* chore(ui): upgrade mermaid to 11.16.1 for security advisories (#333 review)

Bumps mermaid from 11.16.0 to 11.16.1 to pick up fixes for five npm audit advisories (CSS injection outside the diagram and browser denial-of-service via infinite rendering loops). Agent-generated Mermaid is rendered into the page and is untrusted input; MermaidBlock already renders with securityLevel: 'strict'.

Signed-off-by: Manush Murali <manushm@nvidia.com>

* fix(ui): panel/cursor/tooltip UX polish from review (#333)

Addresses review feedback on the chat UI:
- Sessions sidebar menu button now toggles open and close: the expanded-state hamburger collapses the panel (replacing the separate chevron), matching the collapsed-state expand button.
- cursor-pointer applied to the full interactive area of native buttons that previously showed the default cursor (sessions expand/collapse, clear-search, and the report Generated files toggle); only .nv-button gets a pointer from KUI, native buttons do not.
- Research panel content now has a left border (border-l) when open, consistent with the data-sources and sessions panels.
- Session titles in the sidebar expose a native title tooltip so truncated titles are readable in full.

Signed-off-by: Manush Murali <manushm@nvidia.com>

* fix(ui): show step output not the prompt input, so trace stops leaking prior responses (#333 review)

Signed-off-by: Manush Murali <manushm@nvidia.com>

* fix(ui): stop rendering agent prompt input as the step arg summary (#333 review)

Signed-off-by: Manush Murali <manushm@nvidia.com>

* fix(ui): id-key deep-research replay and job-load correlation, de-binary the correlator (#333 review)

Reuse createResearchCorrelator on the reconnect replay buffer (use-deep-research) and the full-job-load replay (use-load-job-data) so concurrent same-named workers, tools, and LLM runs are paired by agentId instead of a name-keyed LIFO stack. This stops one worker's tool output or thinking trace from being attached to another when two researchers run the same tool or model. The replay buffer now shares one correlator across the buffer and live phases, removing the name-based adoption bridge. Also replace the literal NUL KEY_SEPARATOR with the \u0000 escape so Git tracks the file as text.

Signed-off-by: Manush Murali <manushm@nvidia.com>

* fix(ui): restore compact source list in the report (#333 review)

Signed-off-by: Manush Murali <manushm@nvidia.com>

* Fix reference URL parsing for colon-delimited sources

writer.j2 emits web sources in the canonical form `[N] Title: URL`, but
TRAILING_URL_RE only matched a dash separator. Colon-delimited lines fell
through to the text branch and inferSourceKind saw the whole "Title: URL"
string, classifying them as documents with no url, so their source cards
had no working link.

Make the title/URL separator optional and accept a colon (and a bare
space), so a trailing http(s) URL is always extracted and classified as a
web source. Add regression coverage for the canonical colon form.

Signed-off-by: Manush Murali <manushm@nvidia.com>

* Replace deprecated model id in getDisplayName test

The default-model-profiles guard (test_deprecated_model_and_endpoint_references_are_absent)
forbids the deprecated `openai/gpt-oss-120b` string anywhere in the repo, and the
getDisplayName spec used it as a fixture, so Pytest and Coverage failed.

Swap it for the current `openai/gpt-5.6-sol`, which exercises the same behavior
(GPT upper-casing plus alpha-segment title-casing).

Signed-off-by: Manush Murali <manushm@nvidia.com>

---------

Signed-off-by: Manush Murali <manushm@nvidia.com>
Signed-off-by: Kyle Zheng <kyzheng@nvidia.com>
Co-authored-by: Kyle Zheng <126034466+KyleZheng1284@users.noreply.github.com>
Co-authored-by: Kyle Zheng <kyzheng@nvidia.com>
Co-authored-by: Chantal D Gama Rose <cdgamarose@nvidia.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request READY FOR REVIEW UI/UX UI and UX related fixes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants