Tech Stack
Runtime & Tooling
| Tool | Version | Role |
|---|---|---|
| Bun | latest | Package manager, runtime, test runner |
| Vite | 6 | Frontend build + dev server |
| TypeScript | ~5.8 | Full-stack type safety |
Use bun everywhere, not npm or yarn.
Frontend
Core
- React 18: UI library
- react-router-dom v6: client-side routing
- TanStack Query (v5): data fetching, caching, background refetch
- motion (v12): animation
Styling
- Tailwind CSS v4: CSS-first config via
src/index.css(notailwind.config.js) - shadcn/ui: component library (style:
new-york, base:neutral, CSS vars enabled) - Radix UI: primitives backing shadcn components (via
radix-uipackage)
Design Tokens
The accent token is --brand / --brand-foreground. Use only:
bg-brand text-brand border-brand ring-brandNever hardcode violet-500 or the primary token for accent UI.
| Mode | Value |
|---|---|
| Dark | oklch(0.72 0.22 290) |
| Light | oklch(0.44 0.22 290) |
Utilities
clsx+tailwind-merge, compose viacn()from@/lib/cnclass-variance-authority, variant-based component APIslucide-react, icon library (do not add others)dnd-kit, drag-and-drop (@dnd-kit/core,@dnd-kit/sortable,@dnd-kit/utilities)sonner, toastsreact-markdown+remark-gfm/remark-math/rehype-katex+katex, message renderingreact-syntax-highlighter, code blocks
Maps, avatars & in-browser AI
- maplibre-gl + pmtiles, offline vector maps
- @dicebear/core + @dicebear/collection, companion avatars
- onnxruntime-web, in-browser OpenWakeWord (WASM);
scripts/copy-ort.mjscopies the runtime assets onpredev/prebuild - openmoji, emoji assets (copied via
scripts/copy-openmoji.mjs)
Music engine (offline)
- tonal, @tonejs/midi, spessasynth_core / spessasynth_lib, client-rendered podcast stinger/intro music from a soundfont
Backend
HTTP / Framework
- Hono: lightweight, Bun-native HTTP framework
- Server-Sent Events (SSE): LLM token streaming, image progress, boot repair
Database
- SQLite via Bun’s built-in
bun:sqlite+ Drizzle ORM (drizzle-orm, dialectbun-sqlite) - PostgreSQL optional override via
DATABASE_URLenv var - Schema in
backend/src/db/schema.ts; inlinerunMigrations()is authoritative (do not rely ondrizzle-kit generate)
Auth & Security
- Argon2id: PIN hashing via
Bun.password.hash()(no extra dep) - Pepper:
PIN_PEPPER_SECRETenv var; falls back to a generated value inapp_settings - HttpOnly cookie named
session: not JWT in localStorage (XSS-safe); only the token’s SHA-256 hash is stored
Backend libraries
- hono: HTTP framework
- pino / pino-pretty: logging
- @huggingface/transformers (3.8.x): Whisper STT inside the voice sidecar
- kokoro-js: Kokoro TTS in the voice sidecar
- @openzim/libzim: ZIM archive reads (alongside
kiwix-serve) - osm-pbf-parser, geotiff: maps build/geocoding helpers
- chrono-node: natural-language date parsing
- obscenity: content filtering
AI Integration
- All AI calls proxied through backend, keys/URLs never reach the browser
- Ollama: chat, routing, embeddings, vision
- ComfyUI (Python, headless, default port 8188), image generation via workflow JSON
- Voice sidecar (Node worker spawned from
backend/scripts/voice-server.ts), Kokoro TTS + Whisper STT in-process
AI Stack
| Capability | Engine | Notes |
|---|---|---|
| Chat LLM | Ollama (admin-selectable) | Catalog offers mannix/llama3.1-8b-abliterated and huihui_ai/gemma-4-abliterated (12B, built-in vision); code default is llama3.1:8b |
| Routing (T1) | all-minilm (embed) | Cosine intent match, router index cached |
| Routing (T2) | granite4.1:3b | Kept warm; extracts tool args when T1 is uncertain |
| Embeddings (memory) | nomic-embed-text via Ollama | Memory/friendship semantic recall |
| Vision | Ollama VLM | Built-in if chat model is vision-capable, else gemma3:4b |
| TTS | Kokoro-82M (kokoro-js) | ONNX in the voice sidecar, sentence-chunked streaming |
| STT | Whisper (whisper-tiny.en) | In the voice sidecar via @huggingface/transformers (not browser WASM) |
| Wakeword | OpenWakeWord | onnxruntime-web WASM, in-browser |
| Image gen | ComfyUI + Juggernaut XL Ragnarok | SDXL checkpoint; LoRA, face-ID, video, bg-remove add-ons |
Build Verification
After any frontend change:
cd frontend && bun run build # tsc -b && vite buildAfter any backend change:
bun build --target=bun backend/src/index.tsBoth must exit 0 before a change is considered done. tsc --noEmit passes but Vite’s transform catches additional JSX/TSX errors, never rely on tsc alone.