Mischief

50 / Agent UI

Source Card

One retrieved passage: where it came from, what it said, and how well it matched.

Installation

Copy the source into your project, or keep it behind a package.

npx shadcn@latest add Tinkerers-Labs/mischief-ui/source-card
import { SourceCard } from "mischief-ui/source-card"
Also installs
  • lucide-react

Or paste it in yourself. The source imports the shared cn helper from @/lib/utils, so point that at your own copy.

registry/default/source-card/source-card.tsx
import * as React from "react"import { ExternalLink } from "lucide-react" import { cn } from "@/lib/utils" export type SourceCardProps = Omit<  React.HTMLAttributes<HTMLElement>,  "title" | "children"> & {  title: React.ReactNode  /** Makes the title a link. Opens in a new tab. */  url?: string  /** The retrieved passage. */  snippet?: React.ReactNode

Usage

export function Sources() {
  return results.map((result, index) => (
    <SourceCard
      key={result.id}
      index={index + 1}
      title={result.title}
      url={result.url}
      snippet={result.text}
      score={result.score}
    />
  ))
}

About that percentage

score is a fraction from 0 to 1, clamped, and shown as a percentage. What it means is entirely your retriever's business: a cosine similarity, a reranker's output, and a BM25 score are three different quantities, and none of them is a probability that the answer is correct.

So show it only where the reader can act on it. A number that always reads between 80 and 90 percent teaches nobody anything, and a confident-looking percentage attached to a bad passage is worse than no number at all. Leave score out and the bar disappears.

Making a source checkable

The snippet should be the passage the claim actually rests on, not the opening of the document. The whole value of showing sources is that someone can check the claim in a second, and a first paragraph that happens to sit above the relevant text does not let them.

Where there is no url -- an internal document, a chunk from your own store -- the card still works and simply stops being a link. Give it a source in that case, since the host it would otherwise fall back to does not exist.

<SourceCard
  index={1}
  title="Refund policy, section 4"
  source="Support handbook"
  snippet="Refunds are issued to the original payment method within ten working days."
/>

API

titleReactNodeThe heading. Becomes a link when a url is given.
urlstringOpens in a new tab, with the host shown underneath.
snippetReactNodeThe retrieved passage.
sourceReactNodeWhere it came from. Falls back to the host of the url.
indexnumberPosition in the result list, shown as a marker.
scorenumberRelevance from 0 to 1, shown as a bar and a percentage.
icon, footerReactNode, ReactNodeA leading mark, and a row beneath the passage.

Accessibility

Each card is an article with a real heading, so a list of them can be navigated by heading. Links say they open in a new tab and carry rel=noreferrer noopener. The relevance bar is decoration with the percentage written beside it, so the score never depends on seeing the bar. A malformed url degrades to no host rather than throwing.