Cultivar UI
Svelte frontend for the Grove5 Cultivar appliance.
A thoughtful friend who checked in on your grow while you were busy, and wants to tell you about it over coffee. Warm, specific, calm, quietly knowledgeable. Never anxious. Never condescending.
Grove5 — Open source software, honest hardware.
What it is
The Cultivar UI runs on the QRB2210 Linux side of the Arduino UNO Q, served as static files by cultivar-daemon. It connects to the daemon via WebSocket for real-time sensor data and REST for history.
Designed for a tablet or desktop browser. The appliance serves it locally — no cloud, no accounts, no ads.
Stack
- Svelte — compiled to vanilla JS, no virtual DOM
- TypeScript — full type safety via
cultivar-types - Vite — build tooling
- D3 — sweep visualizations (radar, heatmap)
Current bundle: 242 KB JS / 73 KB gzip — lean by design.
Structure
src/
├── lib/
│ ├── charts/
│ │ ├── ColonizationCurve.svelte
│ │ └── LineChart.svelte
│ ├── reference/
│ │ ├── contaminants.ts # 15 contaminants with causes + remediation
│ │ ├── species.ts # 27 species with climate envelopes + CO2 tolerances
│ │ └── index.ts
│ ├── twin/
│ │ ├── CultivarTwin.svelte # dual-chamber physical twin
│ │ ├── MushroomBag.svelte
│ │ └── BagHeroCard.svelte
│ ├── ui/
│ │ ├── ChartPanel.svelte
│ │ ├── InfoHint.svelte
│ │ └── TopBar.svelte
│ ├── store.svelte.ts # reactive app state
│ ├── types.ts # from cultivar-types
│ └── mock.ts # mock data for development
└── routes/
├── Overview.svelte # main dashboard
├── BagDetail.svelte # per-bag detail view
├── Reference.svelte # field guide
└── Onboarding.svelte
Views
Overview
The main dashboard. Shows:
- Cultivar Rhythm — narrative summary + actionable task list
- Current Status — dual-chamber physical twin with bag states
- Environment — temp, humidity, CO2, air quality with 1h/6h/24h history
Field Guide
- 27 species with climate envelopes, CO2 tolerances, flush patterns
- 15 contaminants with visual ID, causes, remediation
Bag Detail
- Per-bag colonization progress, vision snapshots, stage history
Development
npm install
npm run dev # dev server at localhost:5173 (uses mock data)
npm run build # build to dist/ for daemon to serve
npm run preview # preview built output
Mock vs live data
mock.ts provides realistic data for UI development without a running daemon. The store switches to WebSocket automatically when VITE_DAEMON_URL is set:
# Dev against live daemon
VITE_DAEMON_URL=http://localhost:8000 npm run dev
Environment variables
| Variable | Default | Description |
|---|---|---|
VITE_DAEMON_URL |
`` (mock mode) | Daemon base URL |
VITE_WS_URL |
derived from VITE_DAEMON_URL |
WebSocket URL |
WebSocket integration
The store connects to ws://{daemon}/ws and handles typed messages:
// store.svelte.ts
ws.onmessage = (event) => {
const msg: WsMessage = JSON.parse(event.data)
switch (msg.type) {
case 'payload': updateEnvironment(msg.chamber_id, msg.data)
case 'anomaly': addAnomaly(msg.data)
case 'baseline': updateBaseline(msg.chamber_id, msg.data)
case 'heartbeat': // keepalive
}
}
Deployment
Built output at dist/ is served directly by cultivar-daemon:
npm run build
# daemon auto-serves dist/ at http://cultivar.local:8000
Design system
- Colors: charcoal (
#1C1C1C) + sage green (#7A9E7E) — two color system - Typography: ITC New Baskerville serif (headings) + system sans (body)
- Voice: warm, specific, calm — "Harvest day." not "HARVEST READY ✅"
Related repos
grove5/cultivar-daemon— data pipeline + APIgrove5/cultivar-types— shared typesgrove5/cultivar-firmware— STM32 sensor firmware
License
MIT — Grove5