diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..61e36cf --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,94 @@ +# SpiceBook + +Notebook interface for SPICE circuit simulation. + +- **Frontend**: Astro 5 SSR + React 19 islands (`frontend/`) +- **Backend**: FastAPI + ngspice (`backend/`) +- **Production**: https://spicebook.warehack.ing +- **Repo**: git.supported.systems:warehack.ing/spicebook.git + +## Development + +```bash +# Local dev (Docker) +make dev +# Frontend: http://localhost:4321 +# Backend: http://localhost:8099 + +# Local dev (no Docker) +cd backend && uv run uvicorn spicebook.main:app --host 0.0.0.0 --port 8099 --reload +cd frontend && npm run dev -- --host 0.0.0.0 --port 4322 +``` + +## Deployment to Production + +Production runs on `warehack.ing` (149.28.126.25) behind caddy-docker-proxy. + +```bash +# 1. Push changes +git push origin main + +# 2. SSH to production server +ssh -A warehack-ing@warehack.ing + +# 3. Pull and rebuild +cd ~/spicebook +git pull origin main +make prod + +# 4. Verify +docker compose -f docker-compose.yml -f docker-compose.prod.yml logs --tail=20 +``` + +### Production environment notes + +- `.env` on the production server is NOT tracked in git — edit it directly on the server when adding new env vars +- `BACKEND_INTERNAL_URL=http://backend:8000` must be set for SSR server-side fetches (frontend talks to backend over Docker network) +- `notebooks/user/` must be world-writable (chmod 777) because the backend runs as non-root user `spicebook` but the bind mount is owned by `warehack-ing` +- The `@resvg/resvg-js` native binary requires `node:20-slim` (Debian glibc) — Alpine won't work +- OG image font comes from `@fontsource/inter` in `node_modules`, not a separate font file + +### Caddy routing + +Caddy routes `/api/*`, `/health`, `/docs`, `/openapi.json` to the backend (port 8000). Everything else goes to the frontend (port 4321). No Caddy config changes needed for new frontend routes — they're caught by the frontend's SSR handler. + +## Architecture + +### SSR Data Flow + +Notebook pages fetch metadata server-side via `server-api.ts`: +- `fetchNotebookMeta(id)` calls `BACKEND_INTERNAL_URL/api/notebooks/{id}` with a 5s timeout +- Title, engine, and tags populate SEO meta tags at render time +- If the backend is unreachable, pages degrade to generic metadata + +### OG Image Pipeline + +`/og/[id].png` and `/og/default.png` are SSR API routes that: +1. Fetch notebook metadata from the backend (same internal URL) +2. Render JSX layout with Satori (SVG) +3. Rasterize to PNG with @resvg/resvg-js (native Rust NAPI binding) +4. Return with cache headers (1h client / 24h CDN for notebooks, 24h/7d for default) + +### Key Environment Variables + +| Variable | Where | Purpose | +|----------|-------|---------| +| `PUBLIC_API_URL` | Build-time (Vite) | Client-side API base URL. Empty string = same origin (prod) | +| `BACKEND_INTERNAL_URL` | Runtime (process.env) | SSR server-to-server API URL over Docker network | +| `SPICEBOOK_DOMAIN` | Docker labels | Caddy reverse proxy domain | +| `CORS_EXTRA_ORIGINS` | Backend | Additional CORS origins for embed framing | + +## Build & Test + +```bash +cd frontend +npx astro check # TypeScript type checking +npm run build # Full SSR build +``` + +## Style Guide + +- Date-based versioning (YYYY.MM.DD) +- Dark theme: slate-950 background, slate-200 text +- Icons: astro-icon with lucide set (server-rendered SVGs, zero JS) +- React components use `client:load` for interactive islands