Plans007 2026 02 20 Dashboard Service
Set Up @services/dashboard
Overview
Build the @services/dashboard TanStack Start web application. Server functions route aggregated mart queries to DuckDB and individual record lookups to LibSQL (via @packages/db Drizzle). All 8 routes from the architecture spec are implemented with shadcn/ui + Tailwind. A period selector allows filtering dashboard views to any loaded month.
Result: bun run dev --filter @services/dashboard serves a live dashboard at localhost:15002 reading real mart data from atlas.db and operational records from atlas-ops.db.
Goals
- Scaffold
@services/dashboardas a TanStack Start workspace package - Implement root layout with sidebar navigation across all 8 routes
- Implement all 5 aggregated views backed by DuckDB mart queries (overview, revenue, orders, programs, schools, marketing)
- Implement 2 individual record views backed by LibSQL Drizzle queries (student profile, organization detail)
- Register dashboard at port
15002inNETWORK.yml - Verify:
bun run dev --filter @services/dashboardserves all routes with real data
Non-Goals
- Deploying to Cloudflare Pages or any remote host (local dev only in this plan)
- Authentication or access control
- Component reuse with
@services/present— deferred (see architecture.md Open Questions) - Dark/light mode theming (can be added later)
- Mobile responsiveness (desktop-first)
- Real-time data updates / websockets
- Data entry or editing (read-only dashboard)
Phases
- Phase 1: Package scaffold (package.json, tsconfig, vite, app.config, components.json, NETWORK.yml)
- Phase 2: Root layout + data client helpers
- Phase 3: Aggregated views — DuckDB routes (overview, revenue, orders, programs, schools, marketing)
- Phase 4: Individual record views — LibSQL routes (student profile, organization detail)
- Phase 5: Integration — bun install, NETWORK.yml, type-check, dev verification
Success
-
@services/dashboardscaffolded with all config files -
NETWORK.ymlupdated with dashboard at port15002 -
bun run test:type --filter @services/dashboardpasses -
bun run dev --filter @services/dashboardstarts without errors - All 8 routes accessible at
localhost:15002with real data - Revenue route shows
mart_revenuedata with a period selector - Student profile route shows a real person record from
atlas-ops.db
Requirements
- Plan 003 completed: all mart tables in
atlas.db - Plan 005 completed:
atlas-ops.dbschema in place (may be unpopulated — student/org views can show empty state) @packages/dbavailable as workspace dependency- shadcn/ui components available (add via
bunx shadcn@latest addor catalog) - TanStack Start, TanStack Router, TanStack Query in workspace catalogs
- Vite +
@cloudflare/vite-plugin(for future deploy compatibility, but local dev only in this plan) @repo/typescript/tanstacktsconfig preset@repo/lint/baseESLint config
Context
Why This Approach
- TanStack Start for consistency with
@services/planand the planned architecture - Server functions (not a separate API) — each route fetches its own data via
createServerFn; no REST layer needed - DuckDB for aggregated views: mart queries are fast analytical reads; no caching needed for local use
- LibSQL + Drizzle for individual records: typed queries, FK joins, row-level access
- shadcn/ui + Tailwind: matches the architecture spec; headless, composable components
- Period selector as a shared UI element: all DuckDB routes accept a
periodparam that defaults to the latest available period in the mart
Key Constraints
- DuckDB
duckdb-asyncnode client is used in server functions only (not importable in client code) - LibSQL client from
@packages/dbused in server functions only - All server function data fetching must be
use serverannotated for TanStack Start SSR compatibility - Route
_app.tsxis the shared layout wrapper; sidebar nav links to all 8 routes routeTree.gen.tsmust be seeded manually for initial type-check; regenerated onvite dev- Dashboard port:
15002(ions uses15xxxprefix; plan service is15001) - School progress route renders the year × organization matrix — same pivot as the Format layer's
SchoolProgressSectiontype
Edge Cases
- Empty mart (no data loaded): all aggregated views show empty state (no crash)
- Empty
atlas-ops.db(schema only, no data): individual record views show "No records found" - Student or organization ID not found: 404 handled gracefully
- DuckDB file
atlas.dbnot present: server function throws with a clear error
Tradeoffs
- No real-time updates: dashboard reads from static files; acceptable for the monthly reporting use case
- No pagination on school progress or channel marketing tables: marts are small enough for full-page loads
- shadcn/ui pre-built components: faster to implement than custom; some design constraints
Skills
plan— plan file format and conventions
Boundaries
- Always: Update Progress.md after each task completion
- Always: Server functions for all data fetching (no client-side DB access)
- Always: Use
catalog:references in package.json for all deps - Always: Use
workspace:*for@repo/*and@packages/*references - Ask first: Any route not in the architecture spec
- Ask first: Any deviation from TanStack Start file-based routing conventions
- Never: Write to DuckDB or LibSQL from the dashboard (read-only)
- Never: Expose raw SQL to the client
Questions
- Port? → 15002
- Data sources? → DuckDB for aggregated, LibSQL for individual records
- UI library? → shadcn/ui + Tailwind
- Should the period selector default to the latest available month in the mart, or require explicit selection?
- Should the overview route show a per-unit summary card or a combined entity total?
- Should
/ordersand/programsbe separate routes or combined (both read mart_program_progress)?