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:
226
CLAUDE.md
226
CLAUDE.md
@ -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 300–700) 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, h4–h6 11px sans-serif uppercase wide-tracked. Metric card values 34px serif at letter-spacing: -0.08em. Section labels at 0.06–0.07em tracking, weight 550–600.
|
||||
Spacing: 4px base unit (Tailwind scale). Page content p-8 (32px). Cards p-4–p-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 6–8px, 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 | 400–700 | 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 | 400–600 | 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: `11–12px` 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: `24–28px`
|
||||
- Card/surface internal padding: `18–20px`
|
||||
- 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): `20–24px` 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 `60–80ms` delays between elements
|
||||
- **Chart data animation:** SVG `<animate>` on path `d`, polyline `points`, and circle `cy` — `0.5–0.6s` duration, `0.2–0.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.
|
||||
Reference in New Issue
Block a user