Skip to content

Conversation

@max-programming
Copy link
Contributor

@max-programming max-programming commented Dec 21, 2025

Examples

image image

Summary by CodeRabbit

  • New Features

    • Grouped citations support: consecutive citations render and open as a single group.
    • Improved citation labels: names resolved and formatted (truncation and "+n" handling).
    • Multi-source modal: shows multiple sources with per-source metadata and copyable details.
  • Refactor

    • Unified citation UI and trigger behavior; hosting-aware rendering path for hosted vs non-hosted contexts.

✏️ Tip: You can customize this high-level summary in your review settings.

@vercel
Copy link

vercel bot commented Dec 21, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
app-agentset-ai Ready Ready Preview, Comment Dec 28, 2025 9:15am

@coderabbitai
Copy link

coderabbitai bot commented Dec 21, 2025

Walkthrough

Adds grouped-citation support: parser groups adjacent citations; renderer exposes "citation-group" nodes; CitationButton handles single or grouped citations with hosting-aware path; CitationModal accepts multiple sources; new citation-utils resolves names and formats display text; hosting context gains useOptionalHosting.

Changes

Cohort / File(s) Summary
Citation components
apps/web/src/components/chat/citation-button.tsx, apps/web/src/components/chat/citation-modal.tsx
CitationButton now accepts CitationButtonProps supporting "data-citation" and "data-citations", branches to a hosted path via internal HostedCitationButton. CitationModal refactored to multi-source API (sources, sourceIndices, displayText); adds internal CitationItem and CitationDisplayText.
Citation utilities
apps/web/src/components/chat/citation-utils.ts
New module exporting resolveCitationName, ResolvedCitation interface, and formatCitationDisplay; includes name resolution, deduplication, truncation, and display formatting logic.
Markdown rendering
apps/web/src/components/chat/markdown.tsx
Markdown renderer adds a handler for "citation-group" nodes that reuses the CitationButton rendering path (mirrors existing "citation" handler).
Citation parsing plugin
apps/web/src/components/chat/remark-citations.ts
Adds CitationMatch and CitationGroupNode types, createCitationNode and createCitationGroupNode helpers; plugin now collects matches, groups adjacent citations into citation-group nodes, and emits mixed text/citation/citation-group sequences. Updates mdast declaration for "citation-group".
Hosting context
apps/web/src/contexts/hosting-context.tsx
Adds useOptionalHosting() hook that returns the HostingContext value without coercion/throwing, allowing null when hosting is absent.

Sequence Diagram

sequenceDiagram
    participant Parser as Remark Plugin
    participant Renderer as Markdown Renderer
    participant Button as CitationButton
    participant Hosting as HostingContext / HostedCitationButton
    participant Utils as citation-utils
    participant Modal as CitationModal

    Parser->>Parser: find citation matches
    Parser->>Parser: group adjacent matches -> citation-group nodes
    Parser->>Renderer: emit AST with citation / citation-group nodes
    Renderer->>Button: render node(s)

    alt Hosted environment (useOptionalHosting / useIsHosting true)
        Button->>Hosting: detect hosting & delegate to HostedCitationButton
        HostedCitationButton->>Utils: resolveCitationName(metadata, path)
        Utils-->>HostedCitationButton: names
        HostedCitationButton->>Utils: formatCitationDisplay(resolved)
        Utils-->>HostedCitationButton: displayText
        HostedCitationButton->>Modal: open modal with hosted sources + indices
    else Non-hosted environment
        Button->>Modal: open modal with local sources + indices
    end

    Modal->>Modal: filter/normalize sources & indices
    Modal->>Modal: render CitationItem for each source
    Modal-->>Button: show trigger with displayText
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Pay special attention to:
    • remark-citations: match grouping, position handling, and emitted node structure
    • CitationModal props change and per-source rendering (index handling, metadataPath usage)
    • HostedCitationButton delegation and resolveCitationName traversal edge cases
    • formatCitationDisplay: deduplication, truncation, and plus-count logic
    • Type updates: new interfaces (ResolvedCitation, CitationGroupNode, updated props) and mdast augmentation

Poem

🐰 Hop, I found the sources bright,

grouped them snugly, left and right.
Buttons, modals, names in play,
utils trim the words away.
A rabbit cheers — citations day! 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main changes: grouped citations functionality and enhanced citation pill styling are central to this pull request.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch enhancement/chat-grouped-citations

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 36d92bc and 35456bb.

📒 Files selected for processing (5)
  • apps/web/src/components/chat/citation-button.tsx (1 hunks)
  • apps/web/src/components/chat/citation-modal.tsx (2 hunks)
  • apps/web/src/components/chat/citation-utils.ts (1 hunks)
  • apps/web/src/components/chat/markdown.tsx (1 hunks)
  • apps/web/src/components/chat/remark-citations.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
apps/web/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/nextjs.mdc)

apps/web/**/*.{ts,tsx}: Use the App Router directory structure in Next.js projects
Mark client components explicitly with 'use client' directive
Place static content and interfaces at file end
When using useRouter, import from @bprogress/next/app to show global progress bar
Use Zod for form validation in forms
Use react-hook-form for form handling as defined in apps/web/src/components/ui/form.tsx

Files:

  • apps/web/src/components/chat/remark-citations.ts
  • apps/web/src/components/chat/markdown.tsx
  • apps/web/src/components/chat/citation-modal.tsx
  • apps/web/src/components/chat/citation-button.tsx
  • apps/web/src/components/chat/citation-utils.ts
{apps/web/**/*.ts,apps/web/**/*.tsx,packages/ui/**/*.ts,packages/ui/**/*.tsx}

📄 CodeRabbit inference engine (.cursor/rules/shadcn.mdc)

{apps/web/**/*.ts,apps/web/**/*.tsx,packages/ui/**/*.ts,packages/ui/**/*.tsx}: Import Shadcn UI components from the @agentset/ui alias (e.g., import { Button, Card } from "@agentset/ui")
Use Shadcn UI components from the packages/ui/src/components/ui directory for UI elements
Style components using the 'new-york' style variant with 'neutral' base color and CSS variables for theming as configured in components.json
The Button component supports an isLoading prop to display a loading spinner

Files:

  • apps/web/src/components/chat/remark-citations.ts
  • apps/web/src/components/chat/markdown.tsx
  • apps/web/src/components/chat/citation-modal.tsx
  • apps/web/src/components/chat/citation-button.tsx
  • apps/web/src/components/chat/citation-utils.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

Import from packages using configured aliases (e.g., @agentset/ui, @agentset/db/client) instead of relative paths to packages

Files:

  • apps/web/src/components/chat/remark-citations.ts
  • apps/web/src/components/chat/markdown.tsx
  • apps/web/src/components/chat/citation-modal.tsx
  • apps/web/src/components/chat/citation-button.tsx
  • apps/web/src/components/chat/citation-utils.ts
apps/web/**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/nextjs.mdc)

apps/web/**/*.tsx: Wrap client components in Suspense with fallback
Minimize use of 'useEffect' and 'setState' hooks
Show loading states during form submission

Files:

  • apps/web/src/components/chat/markdown.tsx
  • apps/web/src/components/chat/citation-modal.tsx
  • apps/web/src/components/chat/citation-button.tsx
🧬 Code graph analysis (3)
apps/web/src/components/chat/markdown.tsx (1)
apps/web/src/components/chat/citation-button.tsx (1)
  • CitationButton (20-80)
apps/web/src/components/chat/citation-modal.tsx (5)
packages/utils/src/string.ts (1)
  • truncate (28-31)
apps/web/src/contexts/hosting-context.tsx (2)
  • useIsHosting (29-32)
  • useHosting (23-27)
packages/ui/src/lib/utils.ts (1)
  • cn (5-7)
apps/web/src/components/chat/citation-utils.ts (1)
  • resolveCitationName (6-31)
packages/ui/src/components/ai-elements/code-block.tsx (2)
  • CodeBlock (72-131)
  • CodeBlockCopyButton (139-179)
apps/web/src/components/chat/citation-button.tsx (4)
apps/web/src/types/ai.ts (1)
  • MyUIMessage (25-25)
apps/web/src/contexts/hosting-context.tsx (2)
  • useIsHosting (29-32)
  • useHosting (23-27)
apps/web/src/components/chat/citation-modal.tsx (1)
  • CitationModal (45-100)
apps/web/src/components/chat/citation-utils.ts (2)
  • ResolvedCitation (33-37)
  • formatCitationDisplay (57-107)
🪛 Biome (2.1.2)
apps/web/src/components/chat/citation-modal.tsx

[error] 52-52: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)

apps/web/src/components/chat/citation-button.tsx

[error] 27-27: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 38-38: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)

🔇 Additional comments (8)
apps/web/src/components/chat/remark-citations.ts (2)

19-82: LGTM! Well-structured helper functions for citation node creation.

The new CitationGroupNode interface and helper functions cleanly separate concerns. The grouping logic correctly identifies adjacent citations by comparing startIndex with prevMatch.endIndex.


106-122: Grouping algorithm looks correct.

The adjacent citation detection logic properly groups citations that are immediately consecutive (no characters between them). The algorithm preserves ordering and handles both single citations and groups appropriately.

apps/web/src/components/chat/markdown.tsx (1)

26-29: LGTM! Consistent rendering for grouped citations.

The citation-group node is correctly mapped to use the same CitationButton component, which handles both single and grouped citations via the data-citation and data-citations props.

apps/web/src/components/chat/citation-utils.ts (2)

6-31: LGTM! Robust path traversal with proper null safety.

The function handles nested path traversal well and correctly converts different primitive types to strings.


95-106: Verify the remainingCount calculation logic.

The calculation validCitations.length - Math.min(uniqueNames.length, maxNamesToShow) counts remaining citations after showing unique names. However, if there are 5 citations with 3 unique names and you show 2 names, remainingCount would be 5 - 2 = 3, which includes citations with shown names.

This seems intentional based on the comment "Remaining count = total citations - unique names we're showing", but verify this matches the expected UX (e.g., "Name1, Name2 | +3" for 5 citations with 3 unique names).

apps/web/src/components/chat/citation-button.tsx (1)

88-132: LGTM! HostedCitationButton is well-structured.

This component correctly calls all hooks at the top level before any conditional returns. The citation resolution and filtering logic is clear and properly memoized.

apps/web/src/components/chat/citation-modal.tsx (2)

17-36: Consider edge case in CitationDisplayText.

The slice on line 25 uses separatorIndex + 3 to skip " | ", but the search pattern is " | +" (4 characters). This means countPart will include the + prefix, which appears intentional for the display. Just confirming this is the expected behavior.


109-162: LGTM! CitationItem component is well-structured.

All hooks are called unconditionally at the top level. The memoization of citationName and stringifiedMetadata is appropriate, and the error handling for JSON stringify is a nice touch.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
apps/web/src/components/chat/citation-button.tsx (1)

106-113: Consider combining the duplicate filter operations.

Both validSources and validIndices iterate over the same array with the same filter condition. This could be combined into a single pass.

🔎 Proposed refactor to avoid double iteration
-  // Filter valid sources for the modal
-  const validSources = resolvedCitations
-    .filter((c) => c.source !== undefined)
-    .map((c) => c.source!);
-
-  const validIndices = resolvedCitations
-    .filter((c) => c.source !== undefined)
-    .map((c) => c.index);
+  // Filter valid sources for the modal
+  const { validSources, validIndices } = useMemo(() => {
+    const sources: Array<{ text: string; metadata?: Record<string, unknown> }> = [];
+    const indices: number[] = [];
+    for (const c of resolvedCitations) {
+      if (c.source !== undefined) {
+        sources.push(c.source);
+        indices.push(c.index);
+      }
+    }
+    return { validSources: sources, validIndices: indices };
+  }, [resolvedCitations]);
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 35456bb and 7d244c0.

📒 Files selected for processing (3)
  • apps/web/src/components/chat/citation-button.tsx (1 hunks)
  • apps/web/src/components/chat/citation-modal.tsx (3 hunks)
  • apps/web/src/contexts/hosting-context.tsx (1 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
apps/web/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/nextjs.mdc)

apps/web/**/*.{ts,tsx}: Use the App Router directory structure in Next.js projects
Mark client components explicitly with 'use client' directive
Place static content and interfaces at file end
When using useRouter, import from @bprogress/next/app to show global progress bar
Use Zod for form validation in forms
Use react-hook-form for form handling as defined in apps/web/src/components/ui/form.tsx

Files:

  • apps/web/src/components/chat/citation-button.tsx
  • apps/web/src/contexts/hosting-context.tsx
  • apps/web/src/components/chat/citation-modal.tsx
apps/web/**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/nextjs.mdc)

apps/web/**/*.tsx: Wrap client components in Suspense with fallback
Minimize use of 'useEffect' and 'setState' hooks
Show loading states during form submission

Files:

  • apps/web/src/components/chat/citation-button.tsx
  • apps/web/src/contexts/hosting-context.tsx
  • apps/web/src/components/chat/citation-modal.tsx
{apps/web/**/*.ts,apps/web/**/*.tsx,packages/ui/**/*.ts,packages/ui/**/*.tsx}

📄 CodeRabbit inference engine (.cursor/rules/shadcn.mdc)

{apps/web/**/*.ts,apps/web/**/*.tsx,packages/ui/**/*.ts,packages/ui/**/*.tsx}: Import Shadcn UI components from the @agentset/ui alias (e.g., import { Button, Card } from "@agentset/ui")
Use Shadcn UI components from the packages/ui/src/components/ui directory for UI elements
Style components using the 'new-york' style variant with 'neutral' base color and CSS variables for theming as configured in components.json
The Button component supports an isLoading prop to display a loading spinner

Files:

  • apps/web/src/components/chat/citation-button.tsx
  • apps/web/src/contexts/hosting-context.tsx
  • apps/web/src/components/chat/citation-modal.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

Import from packages using configured aliases (e.g., @agentset/ui, @agentset/db/client) instead of relative paths to packages

Files:

  • apps/web/src/components/chat/citation-button.tsx
  • apps/web/src/contexts/hosting-context.tsx
  • apps/web/src/components/chat/citation-modal.tsx
🧠 Learnings (1)
📚 Learning: 2025-11-26T20:30:45.439Z
Learnt from: CR
Repo: agentset-ai/agentset PR: 0
File: .cursor/rules/nextjs.mdc:0-0
Timestamp: 2025-11-26T20:30:45.439Z
Learning: Applies to apps/web/**/*.tsx : Minimize use of 'useEffect' and 'setState' hooks

Applied to files:

  • apps/web/src/components/chat/citation-modal.tsx
🧬 Code graph analysis (2)
apps/web/src/components/chat/citation-button.tsx (4)
apps/web/src/types/ai.ts (1)
  • MyUIMessage (25-25)
apps/web/src/contexts/hosting-context.tsx (2)
  • useIsHosting (29-32)
  • useHosting (23-27)
apps/web/src/components/chat/citation-modal.tsx (1)
  • CitationModal (45-99)
apps/web/src/components/chat/citation-utils.ts (3)
  • ResolvedCitation (33-37)
  • resolveCitationName (6-31)
  • formatCitationDisplay (57-107)
apps/web/src/components/chat/citation-modal.tsx (4)
packages/utils/src/string.ts (1)
  • truncate (28-31)
apps/web/src/contexts/hosting-context.tsx (1)
  • useOptionalHosting (34-37)
apps/web/src/components/chat/citation-utils.ts (1)
  • resolveCitationName (6-31)
packages/ui/src/components/ai-elements/code-block.tsx (2)
  • CodeBlock (72-131)
  • CodeBlockCopyButton (139-179)
🔇 Additional comments (7)
apps/web/src/contexts/hosting-context.tsx (1)

33-37: LGTM!

The new useOptionalHosting hook provides a clean, non-throwing access pattern to the hosting context. This properly addresses the Rules of Hooks violation that was previously flagged in citation-modal.tsx by enabling unconditional hook calls.

apps/web/src/components/chat/citation-button.tsx (2)

20-42: Rules of Hooks violation has been resolved.

The hooks (useMemo at line 25 and useIsHosting at line 36) are now correctly called before the early return at line 38. This ensures hooks are called unconditionally on every render.


88-131: LGTM!

The HostedCitationButton component is properly structured with hooks at the top level. Since it's only rendered when isHosted is true (line 53), the useHosting() call at line 93 is guaranteed to succeed. The citation resolution logic with resolveCitationName and formatCitationDisplay is well-integrated.

apps/web/src/components/chat/citation-modal.tsx (4)

51-51: Rules of Hooks violation has been resolved.

Using useOptionalHosting() instead of conditionally calling useHosting() ensures the hook is called unconditionally on every render while still providing access to the hosting context when available.


17-36: LGTM!

The CitationDisplayText helper correctly parses and formats the display text, handling both single names (truncated to 50 chars) and grouped citations with the | +N pattern. The separator styling with reduced opacity provides good visual hierarchy.


108-161: Well-structured component.

CitationItem correctly uses useMemo for expensive operations (name resolution and JSON stringification) rather than useEffect/setState. The error handling in stringifiedMetadata is good defensive coding, and the conditional title tooltip for truncated names enhances UX. Based on learnings, the minimal use of state hooks here aligns with project guidelines.


85-94: Verify sources and sourceIndices arrays have matching lengths.

The component assumes these parallel arrays are of equal length. While the CitationButton callers ensure this, a mismatch would cause sourceIndices[idx] to be undefined, potentially breaking the key prop and sourceIndex prop.

If you want to add defensive validation, consider:

if (sources.length !== sourceIndices.length) {
  console.warn("CitationModal: sources and sourceIndices length mismatch");
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants