Skip to content

Add a new dashboard panel

You are adding a new flexlayout panel to one or more workspaces.

  • The frontend dev server running so you can render the new panel quickly (cd frontend && npm run dev)

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:

Terminal window
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:

  1. Add to a workspace in layoutModels.ts. Either add a tab to an existing model or include it in a new layout.
  2. Flesh out the component at frontend/src/components/<PanelName>Panel.tsx. Use useAppSelector for store reads, useChannelIn if it should listen to a workspace channel, useChannelOut if it should broadcast to one. Adjust the channel cap, trading-style restriction, and permissions in panelRegistry.ts if the defaults (cap 1, all read roles) are wrong.
  3. 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.

If you need to understand what the generator does, or are working without it, the manual surface is:

  1. Create the component at frontend/src/components/<PanelName>Panel.tsx.
  2. 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
  3. Wire the renderer in panelComponents.ts via registerPanel, and add the description to the duplicate map in ComponentPicker.tsx.
  4. Add to a workspace in layoutModels.ts.
  5. Add a vitest at frontend/src/components/__tests__/<PanelName>Panel.test.tsx.
Terminal window
# Component tests
cd 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.ts

The 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).

  • Forgetting the panel-id type unionPanelId is a string-literal union derived from PANEL_IDS. If you add a panel but forget to update the registry, every consumer using PanelId will 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.ts before merging or the docs site will show the old screenshot. CI does this automatically on main.