Extending the Dashboard
The dashboard's own panels aren't special-cased — they're registered through a public plugin contract, @stepbook/plugin-api. The same contract is open to you: you can contribute your own canvas views and bottom-drawer panels.
Status
The plugin contract (@stepbook/plugin-api) is stable and is what the built-in panels are built on. What's not yet wired is loading third-party plugins from your project config — today the active plugin set is compiled in. So you can author against this contract and the types are firm, but dropping a plugin into a running dashboard via config is a planned next step, not a turnkey feature yet. This page documents the contract so you can build against it now.
The contract
Install (or reference) the types-only package:
import type {
StepbookPlugin,
CanvasViewContribution,
AddonPanelContribution,
AddonTabBadge,
} from '@stepbook/plugin-api'A plugin is a bundle of contributions with a stable id:
interface StepbookPlugin {
id: string // stable id, e.g. 'my-org/coverage' — used for dedup
name?: string // human-readable, shown in dev tools / settings
addonPanels?: AddonPanelContribution[]
canvasViews?: CanvasViewContribution[]
Provider?: ComponentType<{ children: ReactNode }> // optional plugin-owned state
}There are two surfaces you can contribute to — the same two the dashboard itself uses (see The Dashboard):
Canvas views (per step)
A view in the main area that follows the selected step:
interface CanvasViewContribution {
key: string // becomes the route value, e.g. 'my-org/coverage'
label: string // tab label
Component: ComponentType // propless — reads dashboard context for its data
order?: number // lower renders first; builtins use 10–60, use 100+
}Addon panels (whole run)
A bottom-drawer tab that reflects the run as a whole, optionally with a live badge on its tab:
interface AddonPanelContribution {
key: string // builtins are bare ('checks'); namespace yours ('my-org/x')
label: string
Component: ComponentType // propless
useBadge?: () => AddonTabBadge | undefined // optional live tab badge
order?: number // builtins use 10/20/30; default 100
}
interface AddonTabBadge {
text: string
tone?: 'fail' | 'warn' | 'default'
}Components are propless
This is the key design rule: a contribution's Component takes no props. It reads everything it needs from the dashboard's React context — the selected step, the current run, the step's output, and so on. That keeps contributions decoupled from the shell's internal wiring; you subscribe to the context you care about and render.
If your panel needs its own state that should live above the app (a shared store, a client to some service), provide it via the plugin's optional Provider, which the dashboard composes into the context tree.
A minimal example
A plugin that adds one addon panel showing a coverage summary, with a badge when coverage is below target:
import type { StepbookPlugin, AddonTabBadge } from '@stepbook/plugin-api'
function CoveragePanel() {
// Read what you need from dashboard context (selected run, outputs, …),
// compute, and render. No props.
return <div>/* coverage view */</div>
}
function useCoverageBadge(): AddonTabBadge | undefined {
const pct = /* derive from context */ 82
return pct < 90 ? { text: `${pct}%`, tone: 'warn' } : undefined
}
export const coveragePlugin: StepbookPlugin = {
id: 'my-org/coverage',
name: 'Coverage',
addonPanels: [
{
key: 'my-org/coverage',
label: 'Coverage',
Component: CoveragePanel,
useBadge: useCoverageBadge,
order: 100, // after the builtins
},
],
}How the builtins use it
The dashboard's own panels are a single StepbookPlugin (id: 'stepbook/builtin'). Its canvasViews are Input, Output, Code, Compare, Stability, and Query (orders 10–60); its addonPanels are Checks, History, and Telemetry (orders 10–30). That's the whole reason the contract is trustworthy — the first-party UI is built on exactly the API you'd use.
Conventions
- Namespace your keys and ids (
my-org/thing) so they don't collide with builtins or other plugins. Builtin keys are bare (checks,output). - Use
order≥ 100 so your contributions render after the builtins by default. - Keep components propless and read from context.