1
0
forked from wrenn/wrenn

Added basic frontend (#1)

Reviewed-on: wrenn/sandbox#1
Co-authored-by: pptx704 <rafeed@omukk.dev>
Co-committed-by: pptx704 <rafeed@omukk.dev>
This commit is contained in:
2026-03-22 19:01:38 +00:00
committed by Rafeed M. Bhuiyan
parent 866f3ac012
commit 97292ba0bf
76 changed files with 5770 additions and 683 deletions

226
CLAUDE.md
View File

@ -12,14 +12,16 @@ All commands go through the Makefile. Never use raw `go build` or `go run`.
```bash
make build # Build all binaries → builds/
make build-cp # Control plane only
make build-cp # Control plane only (builds frontend first)
make build-agent # Host agent only
make build-envd # envd static binary (verified statically linked)
make build-frontend # SvelteKit dashboard → internal/dashboard/static/
make dev # Full local dev: infra + migrate + control plane
make dev-infra # Start PostgreSQL + Prometheus + Grafana (Docker)
make dev-down # Stop dev infra
make dev-cp # Control plane with hot reload (if air installed)
make dev-frontend # Vite dev server with HMR (port 5173)
make dev-agent # Host agent (sudo required)
make dev-envd # envd in TCP debug mode
@ -62,13 +64,13 @@ envd is a **completely independent Go module**. It is never imported by the main
### Control Plane
**Packages:** `internal/api/`, `internal/admin/`, `internal/auth/`, `internal/scheduler/`, `internal/lifecycle/`, `internal/config/`, `internal/db/`
**Packages:** `internal/api/`, `internal/dashboard/`, `internal/auth/`, `internal/scheduler/`, `internal/lifecycle/`, `internal/config/`, `internal/db/`
Startup (`cmd/control-plane/main.go`) wires: config (env vars) → pgxpool → `db.Queries` (sqlc-generated) → Connect RPC client to host agent → `api.Server`. Everything flows through constructor injection.
- **API Server** (`internal/api/server.go`): chi router with middleware. Creates handler structs (`sandboxHandler`, `execHandler`, `filesHandler`, etc.) injected with `db.Queries` and the host agent Connect RPC client. Routes under `/v1/sandboxes/*`.
- **Reconciler** (`internal/api/reconciler.go`): background goroutine (every 30s) that compares DB records against `agent.ListSandboxes()` RPC. Marks orphaned DB entries as "stopped".
- **Admin UI** at `/admin/` (htmx + basecoat + alpine.js + Go html/template, no SPA, no build step)
- **Dashboard** (SvelteKit + Tailwind + Bits UI, statically built and embedded via `go:embed`, served as catch-all at root)
- **Database**: PostgreSQL via pgx/v5. Queries generated by sqlc from `db/queries/sandboxes.sql`. Migrations in `db/migrations/` (goose, plain SQL).
- **Config** (`internal/config/config.go`): purely environment variables (`DATABASE_URL`, `CP_LISTEN_ADDR`, `CP_HOST_AGENT_ADDR`), no YAML/file config.
@ -95,6 +97,22 @@ Runs as PID 1 inside the microVM via `wrenn-init.sh` (mounts procfs/sysfs/dev, s
- **FilesystemService**: stat/list/mkdir/move/remove/watch files
- **Health**: GET `/health`
### Dashboard (Frontend)
**Directory:** `frontend/` — standalone SvelteKit app (Svelte 5, runes mode)
- **Stack**: SvelteKit + `adapter-static` + Tailwind CSS v4 + Bits UI (headless accessible components)
- **Package manager**: pnpm
- **Routing**: SvelteKit file-based routing under `frontend/src/routes/`
- **Routing layout**: `/login` and `/signup` at root, authenticated pages under `/dashboard/*` (e.g. `/dashboard/capsules`, `/dashboard/keys`)
- **Build output**: `frontend/build/` → copied to `internal/dashboard/static/` → embedded via `go:embed` into the control plane binary
- **Serving**: `internal/dashboard/dashboard.go` registers a `NotFound` catch-all SPA handler with fallback to `index.html`. API routes (`/v1/*`, `/openapi.yaml`, `/docs`) are registered first and take priority
- **Dev workflow**: `make dev-frontend` runs Vite dev server on port 5173 with HMR. API calls proxy to `http://localhost:8000`
- **Fonts**: Manrope (UI), Instrument Serif (headings), JetBrains Mono (code), Alice (brand wordmark) — all self-hosted via `@fontsource`
- **Dark mode**: class-based (`.dark` on `<html>`) with system preference detection + localStorage persistence
To add a new page: create `frontend/src/routes/your-page/+page.svelte`.
### Networking (per sandbox)
Each sandbox gets its own Linux network namespace (`ns-{idx}`). Slot index (1-based, up to 65534) determines all addressing:
@ -183,7 +201,7 @@ To add a new query: add it to the appropriate `.sql` file in `db/queries/` → `
- **TAP networking** (not vsock) for host-to-envd communication
- **Device-mapper snapshots** for rootfs CoW — shared read-only loop device per base template, per-sandbox sparse CoW file, Firecracker gets `/dev/mapper/wrenn-{id}`
- **PostgreSQL** via pgx/v5 + sqlc (type-safe query generation). Goose for migrations (plain SQL, up/down)
- **Admin UI**: htmx + Go html/template + chi router. No SPA, no React, no build step
- **Dashboard**: SvelteKit (Svelte 5, adapter-static) + Tailwind CSS v4 + Bits UI. Built to static files, embedded into the Go binary via `go:embed`, served as catch-all at root
- **Lago** for billing (external service, not in this codebase)
## Coding Conventions
@ -214,22 +232,184 @@ The main module (`go.mod`) and envd (`envd/go.mod`) are fully independent. `make
## Web UI Styling
**Wrenn brand:**
Warm earthy developer tool with crafted organic character.
**Color palette (light/dark):**
Background scale: #f8f6f1#f1eeea#e8e5e0#dedbd5 (light); #090b0a#0f1211#151918#1b201e#222826 (dark). Text hierarchy: bright #2c2a26 / body #4a4740 / dim #7a766e / faint #a09b93 (light); #e8e5df / #c8c4bc / #8a867f / #5f5c57 (dark). Sage green brand accent: #5e8c58 (light) / #89a785 (dark), with glow variant rgba(94,140,88,0.08). Borders: #e2dfd9 (light) / #262c2a (dark). Semantic status colors: amber #9e7c2e (warning/building), red #b35544 (error/failed), blue #3d7aac (info/stopped) — each with a color-dim transparent bg variant for badge backgrounds. Destructive: #b35544 light / #c27b6d dark.
**Typography:**
Four fonts. Manrope (variable, weights 300700) for all UI labels, nav, body. Instrument Serif (400) for page titles, empty-state headings, large metric values. JetBrains Mono (400/500) for code, env var keys/values, deployment IDs, commit SHAs, log viewer, URL paths. Alice for the sidebar wordmark only. Base body size 14px. Headings: h1 24px serif, h2 20px, h3 18px, h4h6 11px sans-serif uppercase wide-tracked. Metric card values 34px serif at letter-spacing: -0.08em. Section labels at 0.060.07em tracking, weight 550600.
Spacing: 4px base unit (Tailwind scale). Page content p-8 (32px). Cards p-4p-5. Sidebar nav items 7px 10px. Consistent, moderate density — functional but not cramped.
**Borders & depth:** Flat aesthetic — --shadow-sm: 0 0 #0000, no drop shadows. Depth is achieved through background color stepping (bg → bg-3 → bg-4 → bg-5), not shadows. Borders 1px solid in warm muted tones. Corner radii: cards/surfaces 12px, inputs/small buttons 68px, avatars 8px, dots 50%.
**Components:** Active sidebar nav items use a 3px left-border in sage green rather than filled backgrounds, with a sage glow bg (rgba(94,140,88,0.08)). Focus rings are double-ring: 0 0 0 2px background, 0 0 0 4px ring. Status system has four states (Live/sage, Building/amber+pulse, Failed/red, Stopped/faint) each with solid dot + transparent-bg badge pair. Buttons follow ghost → outline → filled hierarchy. Tables wrapped in rounded-xl border. Dialogs via native <dialog>. Toasts bottom-anchored.
**Animation:** Crisp 150ms transitions on all interactive elements. Sidebar width 250ms ease. Custom wrenn-pulse keyframe (2.5s ease infinite box-shadow bloom) on live/building status dots. Top-of-page loading bar (h-0.5, sage green) on navigation.
**Dark mode:** Full support. Very dark near-black-green backgrounds with warm off-white text and desaturated sage accent. Flat (no card shadows). System preference detection + localStorage persistence.
**Overall feel:** Warm, earthy, semi-flat. Avoids cold grays entirely — palette leans slightly warm/brown-tinted throughout. The serif + mono + geometric sans type stack gives a designed but unfussy developer-tool character. Organic and considered, not sterile.
### Identity
Warm, confident developer tool with industrial precision and crafted organic character. The feel is sharp and data-forward — not cold or sterile, but not soft either. Think: an engineer's favorite tool, built with care.
---
### Color Palette (Dark Mode)
**Background scale (6 steps, near-black-green):**
`#0a0c0b` (bg-0, page base) → `#0f1211` (bg-1, sidebar/topbar) → `#141817` (bg-2, cards/surfaces) → `#1a1e1c` (bg-3, hover states/elevated) → `#212624` (bg-4, inputs/avatars) → `#2a302d` (bg-5, active controls)
**Text hierarchy (5 levels):**
- Bright `#eae7e2` — page titles, metric values, active states
- Primary `#d0cdc6` — body text, nav labels, readable content
- Secondary `#9b9790` — supporting text, inactive nav, descriptions
- Tertiary `#6b6862` — labels, section headers, timestamps
- Muted `#454340` — ghost text, disabled states, grid labels
**Sage green brand accent (3 tiers + 2 glows):**
- Solid `#5e8c58` — primary accent, buttons, borders, active indicators
- Mid `#89a785` — badges, chart lines, secondary accent
- Bright `#a4c89f` — active nav text, live counts, chart dots
- Glow `rgba(94,140,88,0.07)` — active nav backgrounds, subtle highlights
- Glow Mid `rgba(94,140,88,0.14)` — live badges, status badge backgrounds
**Borders (2 levels):**
- Default `#1f2321` — card edges, dividers, sidebar borders
- Mid `#2a2f2c` — hover states, interactive borders, stronger separation
**Semantic status colors:**
- Amber `#d4a73c` — warning, building, countdown timers
- Red `#cf8172` — error, failed, destructive actions
- Blue `#5a9fd4` — info, stopped (use sparingly)
**Light mode:** (TBD — follow same warm-tinted approach. Background scale from `#f8f6f1``#dedbd5`. Text hierarchy inverts. Accent stays `#5e8c58` for solid.)
---
### Typography
Four fonts, each with a clear role:
| Font | Role | Weights | Where |
|------|------|---------|-------|
| **Manrope** (variable) | Body, UI | 400700 | All body text, nav labels, buttons, descriptions, section headers |
| **Instrument Serif** | Display, metrics | 400 | Page titles (h1), large metric values, empty-state headings only |
| **JetBrains Mono** | Code, data | 400600 | Status bar, time range buttons, search inputs, IDs, commit SHAs, countdown timers, log viewer, URL paths, code blocks |
| **Alice** | Brand wordmark | 400 | Sidebar wordmark only — never used elsewhere |
**Sizing:**
- Base body: `14px`
- Page title (h1): `24px` serif, `letter-spacing: -0.02em`
- Card metric values: `36px` serif, `letter-spacing: -0.04em`
- Chart inline metric: `30px` serif, `letter-spacing: -0.04em`
- Nav items: `13px` body, weight 500
- Section/group labels: `11px` body, uppercase, `letter-spacing: 0.06em`, weight 600
- Chart section labels: `12px` body, uppercase, `letter-spacing: 0.05em`, weight 600
- Stat cell labels: `11px` body, uppercase, `letter-spacing: 0.05em`, weight 600
- Badge text: `10px`, uppercase, `letter-spacing: 0.04em`, weight 600
- Status bar / footer links: `1112px` mono
- Table headers: `11px` body, uppercase, `letter-spacing: 0.05em`, weight 600, color muted
- Table body cells: `13px`
**Key rule:** Instrument Serif is reserved exclusively for page-level titles and large numeric values. It provides warmth and character without softness. Everything else uses Manrope (UI) or JetBrains Mono (data/code).
---
### Spacing
4px base unit (Tailwind scale). Moderate density — functional and confident, never cramped.
- Page content padding: `2428px`
- Card/surface internal padding: `1820px`
- Sidebar width: `230px`
- Sidebar nav item padding: `8px 10px`
- Sidebar brand area: `18px 16px 16px`
- Tab bar items: `10px 16px`
- Topbar: `16px 28px`
- Metric strip cell: `18px 20px`
- Chart header: `18px 20px`
- Chart canvas: `14px 20px 12px`
- Table header cells: `11px 16px`
- Table body cells: `12px 16px`
- Status bar: `6px 28px`
- Between sections (cards): `2024px` margin-bottom
---
### Borders & Depth
**Flat aesthetic — no drop shadows.** Depth comes from background color stepping (bg-0 → bg-1 → bg-2 → bg-3), not shadows. `--shadow-sm: 0 0 #0000`.
- All borders: `1px solid` in warm muted tones
- Corner radii: cards/surfaces `8px`, inputs/buttons `5px`, logo mark `6px`, avatars `5px`, dots `50%`
- Connected metric cells use shared border container with `border-left: 1px solid` between cells (no gap/grid trick) — creates the industrial panel look
- Tables wrapped in `border-radius: 8px` container with overflow hidden
---
### Components
**Sidebar navigation:**
- Active items use `3px left-border` in sage solid (`#5e8c58`) with accent glow background (`rgba(94,140,88,0.07)`)
- Active text color: accent-bright (`#a4c89f`)
- Icons at `16px`, opacity 0.5 default, 1.0 on active
- Group labels: `11px` uppercase with `0.06em` tracking, muted color
**Status chip (live indicator):**
- Rounded `8px` border, `bg-2` background, `border-mid` border
- Pulsing dot: `7px`, accent-solid fill, `box-shadow: 0 0 8px rgba(94,140,88,0.5)` with glow animation
- Count in mono at `14px` accent-bright, label in secondary text
**Live badges (inline):**
- `10px` text, uppercase, `3px` border-radius
- Background: accent-glow-mid (`rgba(94,140,88,0.14)`), text: accent mid
- Includes `5px` pulsing dot with box-shadow
**Metric strip:**
- 3-column grid, connected cells (single outer border, inner dividers)
- Hover: background steps from bg-2 to bg-3
- Value: `36px` serif, bright text
- Label: `11px` uppercase, tertiary
- Sub-metadata row with `1px` divider between items
**Chart cards:**
- `8px` border-radius, bg-2 background, default border
- Header: section label (12px uppercase) + large serif metric + live badge
- Range group: segmented buttons with `1px` borders, mono text, active state uses bg-5
- Chart area: SVG with `0.5px` grid lines in border color, `10px` mono axis labels in muted
- Data line: `1.5px` accent-solid stroke, `stroke-linejoin: round`
- Area fill: gradient from `rgba(94,140,88,0.28)` → transparent
- Data dot: accent-bright fill, `2.5px` bg-2 stroke, `4px` radius
**Buttons hierarchy:**
1. Ghost (icon-btn): transparent bg, default border, tertiary color → border-mid + secondary on hover
2. Outline: no bg, border-mid border → accent-solid border + primary text on hover
3. Tool: bg-2 background, default border → border-mid + primary on hover
4. Filled/CTA: accent-solid background, white text → lighter green on hover, subtle `translateY(-1px)` lift
**Tables:**
- Container: `8px` border-radius, border, overflow hidden
- Header: bg-3 background, `11px` uppercase muted text
- Body: default bg, `1px` border-bottom between rows
- Row hover: bg-3
**Empty states:**
- Centered, `72px` vertical padding
- Icon container: `56px` square, bg-3, border-mid border, `8px` radius
- Heading: `20px` serif, bright text
- Description: `13px` body, tertiary text
- CTA button below
**Inputs:**
- bg-2 background, default border, `5px` radius
- Mono font for search/filter inputs
- Focus: `border-color: accent-solid` (clean single ring, no double-ring)
- Placeholder: muted color
**Focus rings:** Single accent-solid border-color change on focus. Clean and minimal — no double-ring outlines.
---
### Animation
- **All interactive transitions:** `150ms ease`
- **Page load / section entrance:** `fadeUp``opacity: 0, translateY(6px)` → visible, `0.35s ease`, staggered with `6080ms` delays between elements
- **Chart data animation:** SVG `<animate>` on path `d`, polyline `points`, and circle `cy``0.50.6s` duration, `0.20.35s` begin delay, `fill: freeze`
- **Live status dot:** `glow` keyframe — `2.5s ease infinite` box-shadow bloom from `0 0 6px rgba(94,140,88,0.5)``0 0 14px rgba(94,140,88,0.2)`
- **CTA buttons:** subtle `translateY(-1px)` on hover for lift feel
---
### Dark Mode
Primary and default mode. Very dark near-black-green backgrounds (`#0a0c0b` base) with warm off-white text and desaturated sage accent. Completely flat — no card shadows anywhere. System preference detection + localStorage persistence.
---
### Overall Feel
Sharp, warm, industrial-confident. Avoids cold grays entirely — palette leans slightly warm/brown-tinted throughout. The serif display type provides organic character and warmth on titles and metrics, while Manrope handles readable UI text and JetBrains Mono anchors the data-forward, developer-tool identity. Connected metric panels, tight chart cards, and uppercase section labels create engineering density without sacrificing readability. The result is a tool that feels crafted and precise — designed by someone who uses developer tools daily.