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:
TickerStriptakes arefreshprop instead of polling on mountQueueDepthreads from the shared feed contextUplinkBadgeis 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.
| Widget | Entry point | Notes |
|---|---|---|
| TickerStrip | live | Pass a refresh interval |
| QueueDepth | live | Reads the feed context |
| UplinkBadge | core | Presentational 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:
- TickerStrip no longer polls on mount, so pass
refreshwhere you need live data - UplinkBadge lost its network code entirely and is safe in server components
The build is green and nothing else changed.