The typography kit for rendered agent markdown

The package ships no markdown renderer. You render markdown with whatever library you already use, wrap the output in Prose, and every heading, paragraph, list, link, code span, quote and table inside it picks up the token scale. One wrapper, and rich agent output looks like it belongs.

Body and headings

Body text sits on the paragraph scale and headings step down the heading tiers, so hierarchy is carried by the tokens rather than by anything Prose invents. Margins are trimmed at the edges, so the block slots into a bubble or card with no stray spacing.

Migrating the Lumen widgets

The lumen-panel package splits its widgets into two entry points, so the first step is deciding which side of the line each one sits on. Anything that touches the network moves to lumen-panel/live; everything else stays put.

What changes for consumers

Most imports keep working. The three exceptions are the widgets that used to bundle their own polling logic, which is now opt-in rather than automatic:

  • TickerStrip takes a refresh prop instead of polling on mount
  • QueueDepth reads from the shared feed context
  • UplinkBadge is now purely presentational

The full mapping is in the migration table below, and each entry links to the widget it replaces.

Code

Inline code and code blocks both use the code font. The pre block here is the plain fallback that scrolls sideways instead of wrapping; reach for CodeBlock when standalone code deserves a header and a copy button.

The feed context wires up in one place, at the top of the panel tree:

import { FeedProvider } from 'lumen-panel/live';

export function Dashboard() {
  return (
    <FeedProvider interval={5000}>
      <QueueDepth />
    </FeedProvider>
  );
}

Every widget below the provider stays in step with the same schedule, so QueueDepth and TickerStrip never fight for the connection.

Quotes and tables

Blockquotes take the same quiet left rail as a reasoning trace, and tables get collapsed borders with a tinted header row. Both come straight out of a markdown renderer with no extra classes.

The changelog puts the reasoning plainly:

Widgets that fetch on their own schedule fight each other for the connection. One provider, one schedule, and every widget below it stays in step.

WidgetEntry pointNotes
TickerStriplivePass a refresh interval
QueueDepthliveReads the feed context
UplinkBadgecorePresentational only

Everything not listed here is unchanged and imports from the package root as before.

Small size

The small size drops body text to the small paragraph scale for dense chat contexts, while headings keep their tiers. Use it inside message bubbles where the default scale would feel oversized.

Done. I renamed the three lumen-panel widgets and updated every import. Two things worth knowing:

  1. TickerStrip no longer polls on mount, so pass refresh where you need live data
  2. UplinkBadge lost its network code entirely and is safe in server components

The build is green and nothing else changed.