Skip to content

Dashboard

The Workstacean dashboard is a static Astro + Preact site at dashboard/ that replaces the legacy event viewer. It is the primary operational UI for the GOAP world engine, covering system health, world state, live events, goals, and project CI.

FieldValue
Sourcedashboard/ (standalone package.json)
FrameworkAstro 6.x, static output
IslandsPreact (@astrojs/preact) hydrated with client:load
ThemeGitHub dark (#0d1117 / #161b22 / #30363d)
Build outputdashboard/dist/
Served bylib/plugins/event-viewer.ts on port 8080
API baseSame origin — plugin proxies /api/* and /ws to WORKSTACEAN_HTTP_PORT

All pages live under dashboard/src/pages/. Each one renders a DashboardLayout wrapper with a sidebar, a WebSocket status indicator in the header, and the page-specific component hydrated as a Preact island.

RouteComponentPollsNotes
/OverviewGrid30 sHealth cards for services, agents, CI, PRs, flow efficiency, security, HITL pending
/world-stateWorldStateVieweron demandLive world-state snapshot with domain cards + JSON tree
/eventsEventStreamWebSocketReal-time bus event feed with filter, tabs, sticky header
/goalsGoalStatus + OutcomesTable30 sDeclarative goal pass/fail + GOAP action dispatch history
/projectsProjectsView60 sPer-project CI health bars + PR pipeline badges

The sidebar nav is declared in dashboard/src/layouts/DashboardLayout.astro. The header WebSocket dot connects to /ws for a live/disconnected indicator, independent of the page-level polling.

dashboard/src/lib/api.ts is the single source of truth for all HTTP calls. It provides:

  • Envelope unwrap{ success, data } responses are auto-unwrapped so callers always see the inner payload.
  • In-memory cache with per-endpoint TTLsgetCiHealth, getPrPipeline, etc. return cached values until their TTL expires.
  • Force refresh — every getter accepts a force: boolean argument that bypasses the cache.
  • peek<T>(path) — synchronous stale read used by components to seed state instantly on page revisits, so navigating away and back never shows a “Loading…” flash.
  • Typed response interfaces — every endpoint has an exported type *Response so components get strict shapes.

Cache TTLs (defined in api.ts):

EndpointTTL
/api/world-state, /api/outcomes15 s
/api/services, /api/agent-health, /api/flow-metrics30 s
/api/security-summary60 s
/api/pr-pipeline2 min
/api/ci-health5 min
/api/branch-drift10 min

Pages set their own POLL_INTERVAL_MS on top of this; most pass force: true on the interval tick so they always refresh rather than hitting the cache.

  1. Add a component under dashboard/src/components/ — Preact, .tsx, no default export constraints beyond being a valid component.
  2. Add an .astro page under dashboard/src/pages/ that imports the component and renders it inside <DashboardLayout>:
    ---
    import DashboardLayout from "../layouts/DashboardLayout.astro";
    import MyThing from "../components/MyThing.tsx";
    ---
    <DashboardLayout title="My Thing" activePage="my-thing">
    <MyThing client:load />
    </DashboardLayout>
  3. Add a nav entry to navItems in DashboardLayout.astro. Use the same id string as your activePage prop.
  4. If the component needs new data, add a getter + response type to dashboard/src/lib/api.ts — do not call fetch() directly from components; cache seeding and envelope unwrap live in the api module.
Terminal window
# Development
cd dashboard
bun install
bun run dev # Astro dev server, hot reload, port 4321
# Production build
bun run build # outputs dashboard/dist/
# Serve via main app
cd ..
bun run start # event-viewer plugin picks up dashboard/dist automatically
open http://localhost:8080

The Docker image builds the dashboard as a dedicated stage between install and release, so production containers ship the compiled assets. See the Dockerfile for the exact stage layout.

VariableEffect
DISABLE_EVENT_VIEWERAny non-empty value disables the plugin — the dashboard will not be served.
WORKSTACEAN_HTTP_PORTMain HTTP port that /api/* is proxied to (default 3000).

The dashboard itself has no env vars — it’s a static build. All runtime configuration lives on the server side.

  • HTTP API — every endpoint the dashboard consumes
  • World Engine — what the world-state viewer is showing
  • Bus topics — events streamed to /events