mirror of
https://github.com/PerpetualSoftware/pad.git
synced 2026-09-21 01:53:33 +00:00
8ae009fa40
* feat(admin): add Billing tab and dashboard page (TASK-828)
Surfaces the Pad Cloud billing metrics in the admin console as a new
tab between "Audit Log" and "Settings". Final piece of PLAN-825.
The page calls GET /api/v1/admin/billing-stats (TASK-827) and renders
six metric cards in a responsive auto-fit grid:
1. MRR (Stripe-derived; greyed when unavailable)
2. ARR (Stripe-derived; greyed when unavailable)
3. Active Subs (Stripe-derived; greyed when unavailable)
4. Customers/Plan (LOCAL — always real; e.g. "Free: 42 · Pro: 7")
5. New Signups 30d (LOCAL — always real)
6. Churn 30d (Stripe-derived; greyed when unavailable;
subtitle shows cancelled count)
Two banners drive the degraded-state UX:
- cloud_unreachable=true → amber warning ("sidecar unreachable, showing
local data only")
- stripe_configured=false → blue info banner explaining that Stripe
metrics will be zero until STRIPE_SECRET_KEY
is set on pad-cloud (the expected pre-launch
steady state)
Header carries a Refresh button (re-fetches without unmounting the page)
and an "Open in Stripe Dashboard ↗" external anchor (rel=noopener).
A subtle footer renders "Updated just now" or "Updated N min ago" from
the cache_age_seconds field.
The Billing tab is hidden from the layout's tab list when
adminStore.stats.cloud_mode is false — self-host operators won't see a
tab that always 404s on click. Used $derived(...) for the tabs array so
the tab list reacts to the cloud_mode flag flipping after stats load.
Svelte 5: runes throughout ($state, $derived, $props), single onMount
for the initial fetch, no combined effect-on-effect chains (CONVE-606).
Visual idiom mirrors the existing /console/admin stats-bar (.stat
cards, --bg-secondary background, --radius-lg, value/label sizing).
Validated with the svelte MCP autofixer (clean) and `npm run build`
(clean, page emitted to entries/pages/console/admin/billing).
Closes PLAN-825's UI work.
* fix(admin): add role=status / aria-live=polite to Stripe info banner
Codex round 1 LOW: the warning banner already carries role=alert because
its message is urgent (sidecar unreachable), but the "Stripe not
configured" info banner appears asynchronously after load with no live-
region semantics, so screen readers never announce that the page is in
a degraded state. Add role=status + aria-live=polite so the announcement
is non-interrupting but happens.
Pad Web UI
SvelteKit 2 + Svelte 5 frontend for Pad, compiled to static files and embedded into the Go binary.
Development
npm install
npm run dev # Dev server at localhost:5173 (proxies API to localhost:7777)
npm run build # Production build to build/
npm run check # Type checking with svelte-check
When developing, run the Go backend separately with make dev from the project root.
Building for Production
Do not build in isolation. Always use make build from the project root — this builds the web frontend, then compiles the Go binary with the build output embedded via //go:embed.
Stack
- Svelte 5 with runes (
$state,$derived,$effect) - SvelteKit 2 with
adapter-static(SPA mode) - Tiptap block editor with markdown round-trip
- svelte-dnd-action for drag-and-drop in board/list views
- SSE for real-time updates
- TypeScript throughout
Structure
src/
routes/ SvelteKit pages
+layout.svelte App shell (sidebar + main)
+page.svelte Landing/redirect
[workspace]/
+page.svelte Dashboard (collections, phases, activity)
+layout.svelte SSE connection per workspace
[collection]/
+page.svelte Collection view (board/list)
[collection]/[item]/
+page.svelte Item detail + editor
conventions/ Purpose-built conventions page
playbooks/ Purpose-built playbooks page
settings/ Workspace settings
lib/
api/client.ts HTTP API client
components/
layout/ Sidebar, navigation
editor/ Tiptap editor, raw markdown editor
fields/ FieldEditor, relation picker
items/ ItemCard, ItemDetail
collections/ BoardView, ListView
common/ StatusBadge, badges, modals
search/ CommandPalette
activity/ ActivityFeed
stores/ Svelte 5 reactive stores
workspace.svelte.ts Workspace state
collections.svelte.ts Collection + item state
ui.svelte.ts Sidebar, mobile state
types/index.ts TypeScript types and constants
app.css Global styles and design tokens