ComponentsAgent patterns

Message Response

Turns the Markdown an agent writes into a readable reply. It supports GitHub tables and task lists, shows fenced code in a Code Block, drops raw HTML and images, and lets you replace links with citations or downloads.

PlaygroundEvery prop: Markdown text, streaming, and the table viewport.

Pipeline this week

Three deals moved forward and one stalled. Northwind is the largest open deal at $48,000.

  1. Northwind asked for a security review before Friday.
  2. Blue Harbor signed the order form.
  3. Fennel & Co. has not replied in 9 days.
CompanyStageValueOwnerNext stepLast touch
Northwind TradersSecurity review$48,000Priya ShahSend SOC 2 reportToday
Blue HarborClosed won$21,500Marco DiazKickoff callYesterday
Fennel & Co.Proposal$12,000Priya ShahFollow-up email9 days ago
Lumen LabsDiscovery$8,400Ada OkaforBook a demo3 days ago
Orchard SupplyProposal$16,750Marco DiazPricing call2 days ago
Kestrel HealthDiscovery$30,000Ada OkaforIntro meeting5 days ago

I can draft the follow-up to Fennel & Co. if you want.

Usage

An assistant replyTSX
import { MessageResponse } from "@tulipfarm/sprout/message-response";

<MessageResponse streaming={message.status === "streaming"}>
  {message.text}
</MessageResponse>

children is the Markdown string. There are no variants or sizes: the reply uses the body text size and fills its column. Long words and URLs wrap, so it never pushes a chat column wider.

Markdown

Headings, emphasis, strikethrough, nested and numbered lists, task lists, quotes, rules, aligned tables, autolinks, and fenced code. A fence with a language gets syntax colors; a fence without one shows astext. Raw HTML is dropped and images are not shown.

Every Markdown featureHeadings, emphasis, lists, task lists, quotes, rules, aligned tables, fences, and links.

Launch checklist

Before Friday

Content

Owners

Ship the spring catalog with new photos, and skip the old banner. Ask @robin for the final copy.

  • Photos
    • Hero shot
    • Six product shots
  • Copy review
  • Price check
  1. Freeze the catalog
  2. Publish to the store
  3. Email the list
  • Draft the announcement
  • Schedule the email

Customers saw the old prices for two hours last launch. Check them twice.


ItemStatusPrice
Tulip bulbs, mixedReady$14.00
Watering can, 5 LWaiting for photo$22.50
TypeScript
export const launch = { date: "2026-03-20", channel: "email" } as const;
Plain text
Plain fence: no language, shown as text.

Details live at https://example.com/spring-launch and in the brief.

HTML tags are dropped; their words stay.

Streaming

Set streaming while tokens arrive. A pulsing caret follows the text and the reply is marked busy. An empty reply that is streaming shows only the caret, which is the waiting state.

StreamingA caret follows the text and the region is busy until the reply ends.

Here is a partially streamed response for the Meadow beds.

TaskStatus
Plant bulbsReady

Table viewport

Cap tables when the reply shows row cardsTSX
import { MessageTableViewport } from "@tulipfarm/sprout/message-response";

<MessageTableViewport bounded={rowViews.length > 0}>
  <MessageResponse>{text}</MessageResponse>
</MessageTableViewport>

Every table scrolls in its own focusable region. Inside abounded viewport a table stops at 12rem and scrolls both ways with a sticky header, so row cards below it stay in view. Without one, tables grow to their full height and scroll sideways only when their columns cannot fit.

With row viewsbounded: tables stop at 12rem and scroll both ways.

I found 12 leads that match “opened a pricing email this week”.

CompanyStageValue
Northwind USDiscovery$8.0k
Blue Harbor USProposal$11.5k
Fennel & Co. USSecurity review$15.0k
Lumen Labs USClosed won$18.5k
Orchard Supply USDiscovery$22.0k
Kestrel Health USProposal$25.5k
Northwind EUSecurity review$29.0k
Blue Harbor EUClosed won$32.5k
Fennel & Co. EUDiscovery$36.0k
Lumen Labs EUProposal$39.5k
Orchard Supply EUSecurity review$43.0k
Kestrel Health EUClosed won$46.5k

The full rows are open in the canvas.

Leads12 rows · open in canvas
Without row viewsNot bounded: tables grow to full height.

Pipeline this week

Three deals moved forward and one stalled. Northwind is the largest open deal at $48,000.

  1. Northwind asked for a security review before Friday.
  2. Blue Harbor signed the order form.
  3. Fennel & Co. has not replied in 9 days.
CompanyStageValueOwnerNext stepLast touch
Northwind TradersSecurity review$48,000Priya ShahSend SOC 2 reportToday
Blue HarborClosed won$21,500Marco DiazKickoff callYesterday
Fennel & Co.Proposal$12,000Priya ShahFollow-up email9 days ago
Lumen LabsDiscovery$8,400Ada OkaforBook a demo3 days ago
Orchard SupplyProposal$16,750Marco DiazPricing call2 days ago
Kestrel HealthDiscovery$30,000Ada OkaforIntro meeting5 days ago

I can draft the follow-up to Fennel & Co. if you want.

Custom links

Citations and workspace downloadsTSX
<MessageResponse
  renderLink={(href, label) => {
    const citation = citationId(href);
    if (citation) return <Citation id={citation}>{label}</Citation>;
    if (isWorkspaceDownload(href)) return <DownloadLink path={href}>{label}</DownloadLink>;
    return <a href={href} target="_blank" rel="noopener noreferrer">{label}</a>;
  }}
>
  {text}
</MessageResponse>

Without renderLink, links open in a new tab withnoopener. With it, every link goes through your function, so return a plain link for the ones you do not handle. The examples use sample adapters: citations check access, then show the passage, a hidden notice, or a retry; downloads report progress, success, or failure.

Custom linksrenderLink turns citations into passages and workspace files into downloads.

I compared the two exports in meadow/storefront.

  • The refund rule matches .
  • The shipping rule cites that you can no longer open.
  • Check before you change it.

Download or the older . The diff is on GitHub.

Agent task resultThe latest agent update, then the full history.

Result

Done. has 1,284 rows. I skipped 3 test orders.

  1. You

    Export last month’s orders from meadow/storefront as CSV.

  2. Agent

    Done. has 1,284 rows. I skipped 3 test orders.

    2 explicit source references. Access is checked on each read.

Markdown files

Format Markdown in the file viewerTSX
<FileRenderersProvider
  renderers={{ markdown: ({ text }) => <MessageResponse>{text}</MessageResponse> }}
>
  <FileViewer file={file} open={open} onOpenChange={setOpen} />
</FileRenderersProvider>

Markdown fileFileViewer's markdown renderer, with Show source and Show formatted.

Opening file

Preparing the document viewer.

In a panel

Put a reply in a bordered panel when it sits among other details, such as a chat handoff.

Compaction handoffA written handoff in a bordered panel, with its hidden and failed states.

Handoff

Goal: reconcile March orders with the warehouse export.

  • 1,284 orders matched; 3 are missing from the warehouse.
  • Missing: SO-1042, SO-1077, SO-1103.

Next: ask Robin whether the three were cancelled.

Instructions: Keep order numbers and the open question.

Accessibility

Headings, lists, quotes, and tables keep their native roles. Each table scroll region is named “Response table” and takes focus, so keyboard users can scroll it with the arrow keys. The reply setsaria-busy while it streams and the caret is hidden from screen readers. Links and table regions show a focus ring. The caret stops pulsing with reduced motion and uses the system text color in forced colors mode.