Add a new dashboard panel
When to use
Section titled “When to use”You are adding a new flexlayout panel to one or more workspaces.
Inputs needed
Section titled “Inputs needed”- The frontend dev server running so you can render the new panel quickly (
cd frontend && npm run dev)
Scaffold first
Section titled “Scaffold first”Adding a panel touches several coupling points that must stay in sync: a component, a test,
and four Record<PanelId> maps in panelRegistry.ts plus the registerPanel call in
panelComponents.ts and the duplicate description map in ComponentPicker.tsx. Missing one
fails the typecheck at best and silently drops the panel at worst. The generator patches
all of them:
cd frontend && deno task new:panel <panel-id> "<Title>" --desc "<description>"<panel-id> is kebab-case. The generator is idempotent, so re-running with the same id is a
no-op and safe to retry. It creates the component and its test, and patches every
PanelId-keyed map. It does not place the panel into a workspace layout
(layoutModels.ts), because that is a design choice rather than boilerplate.
After scaffolding:
- Add to a workspace in
layoutModels.ts. Either add atabto an existing model or include it in a new layout. - Flesh out the component at
frontend/src/components/<PanelName>Panel.tsx. UseuseAppSelectorfor store reads,useChannelInif it should listen to a workspace channel,useChannelOutif it should broadcast to one. Adjust the channel cap, trading-style restriction, and permissions inpanelRegistry.tsif the defaults (cap 1, all read roles) are wrong. - Flesh out the test at
frontend/src/components/__tests__/<PanelName>Panel.test.tsx. Beyond the generated header/placeholder assertions, cover: it renders with a populated store, it handles empty data gracefully, it dispatches the right actions on user interaction.
Doing it by hand
Section titled “Doing it by hand”If you need to understand what the generator does, or are working without it, the manual surface is:
- Create the component at
frontend/src/components/<PanelName>Panel.tsx. - Register the panel id in
panelRegistry.ts:- Add the id to
PANEL_IDS - Add the title to
PANEL_TITLES - Add the description to
PANEL_DESCRIPTIONS - Add the channel cap to
PANEL_CHANNEL_CAPS(default 1) - Add the permissions to
PANEL_PERMISSIONS - Optionally restrict by trading style via
PANEL_TRADING_STYLES
- Add the id to
- Wire the renderer in
panelComponents.tsviaregisterPanel, and add the description to the duplicate map inComponentPicker.tsx. - Add to a workspace in
layoutModels.ts. - Add a vitest at
frontend/src/components/__tests__/<PanelName>Panel.test.tsx.
Verification
Section titled “Verification”# Component testscd frontend && ./node_modules/.bin/vitest run src/components/__tests__/<PanelName>Panel
# Type-check everything (catches missing PANEL_TITLES entries)./node_modules/.bin/tsc --noEmit --skipLibCheck
# Visual: panel-walkthrough screenshot test renders every panel via the# panel picker and ships the screenshot to docs/panel-walkthrough/cd frontend && npx playwright test tests/panel-walkthrough.spec.tsThe Panel Reference page in the Astro docs picks up the new panel automatically on the next site build (generate-panel-reference.mjs reads panel-walkthrough/report.json).
Common pitfalls
Section titled “Common pitfalls”- Forgetting the panel-id type union —
PanelIdis a string-literal union derived fromPANEL_IDS. If you add a panel but forget to update the registry, every consumer usingPanelIdwill silently miss it. - Channel-cap default — panels default to channel cap 1 (one instance per workspace). If users should be able to add multiple instances, set the cap explicitly.
- Test-state shape — many tests preload Redux state; if your panel reads a slice that test fixtures haven't populated, you'll see
Cannot read properties of undefined. Either add the field to the fixture or default it in the panel. - Stale screenshots — if you edit the panel, re-run
panel-walkthrough.spec.tsbefore merging or the docs site will show the old screenshot. CI does this automatically on main.