# LibNovel v2 — Agent Context This file is the root-level knowledge base for LLM coding agents (OpenCode, Claude, Cursor, Copilot, etc.). Sub-directories have their own `AGENTS.md` with deeper context (e.g. `ios/AGENTS.md`). --- ## Stack | Layer | Technology | |---|---| | UI | SvelteKit 2 + Svelte 5, TypeScript, TailwindCSS | | Backend / Runner | Go (single repo, two binaries: `backend`, `runner`) | | iOS app | SwiftUI, iOS 17+, Swift 5.9+ | | Database | PocketBase (SQLite) + MinIO (object storage) | | Search | Meilisearch | | Queue | Asynq over Redis (local) / Valkey (prod) | | Scraping | Novelfire scraper in `backend/novelfire/` | --- ## Repository Layout ``` . ├── .gitea/workflows/ # CI/CD — Gitea Actions (NOT .github/) ├── .opencode/ # OpenCode agent config (memory, skills) ├── backend/ # Go backend + runner (single module) ├── caddy/ # Caddy reverse proxy Dockerfile ├── homelab/ # Homelab docker-compose + observability stack ├── ios/ # SwiftUI iOS app (see ios/AGENTS.md) ├── scripts/ # Utility scripts ├── ui/ # SvelteKit UI ├── docker-compose.yml # Prod compose (all services) ├── AGENTS.md # This file └── opencode.json # OpenCode config ``` --- ## CI/CD — Gitea Actions - Workflows live in `.gitea/workflows/` — **not** `.github/workflows/` - Self-hosted Gitea instance; use `gitea.ref_name` / `gitea.sha` (not `github.*`) - Two workflows: - `ci.yaml` — runs on every push to `main` (test + type-check) - `release.yaml` — runs on `v*` tags (build Docker images, upload source maps, create Gitea release) - Secrets: `DOCKER_USER`, `DOCKER_TOKEN`, `GITEA_TOKEN`, `GLITCHTIP_AUTH_TOKEN` ### Releasing a new version ```bash git tag v2.5.X -m "Short title\n\nOptional longer body" git push origin v2.5.X ``` CI will build all Docker images, upload source maps to GlitchTip, and create a Gitea release automatically. --- ## GlitchTip Error Tracking - Instance: `https://errors.libnovel.cc/` - Org: `libnovel` - Projects: `ui` (id/1), `backend` (id/2), `runner` (id/3) - Tool: `glitchtip-cli` v0.1.0 ### Per-service DSNs (stored in Doppler) | Service | Doppler key | GlitchTip project | |---|---|---| | UI (SvelteKit) | `PUBLIC_GLITCHTIP_DSN` | ui (1) | | Backend (Go) | `GLITCHTIP_DSN_BACKEND` | backend (2) | | Runner (Go) | `GLITCHTIP_DSN_RUNNER` | runner (3) | ### Source map upload flow (release.yaml) The correct order is **critical** — uploading before `releases new` results in 0 files shown in GlitchTip UI: ``` glitchtip-cli sourcemaps inject ./build # inject debug IDs glitchtip-cli releases new # MUST come before upload glitchtip-cli sourcemaps upload ./build \ --release # associate files with release glitchtip-cli releases finalize # mark release complete ``` --- ## Infrastructure | Environment | Host | Path | Doppler config | |---|---|---|---| | Prod | `165.22.70.138` | `/opt/libnovel/` | `prd` | | Homelab runner | `192.168.0.109` | `/opt/libnovel-runner/` | `prd_homelab` | ### Docker Compose — always use Doppler ```bash # Prod doppler run --project libnovel --config prd -- docker compose # Homelab full-stack (runs from .bak file on server) doppler run --project libnovel --config prd_homelab -- docker compose -f homelab/docker-compose.yml.bak # Homelab runner only doppler run --project libnovel --config prd_homelab -- docker compose -f homelab/runner/docker-compose.yml ``` - Prod runner has `profiles: [runner]` — `docker compose up -d` will NOT accidentally start it - When deploying, always sync `docker-compose.yml` to the server before running `up -d` --- ## Observability | Tool | Purpose | |---|---| | GlitchTip | Error tracking (UI + backend + runner) | | Grafana Faro | RUM / Web Vitals (collector at `faro.libnovel.cc/collect`) | | OpenTelemetry | Distributed tracing (OTLP → collector → Tempo) | | Grafana | Dashboards at `/admin/grafana` | Grafana dashboards: `homelab/otel/grafana/provisioning/dashboards/` --- ## Go Backend - Primary files: `orchestrator.go`, `server/handlers_*.go`, `novelfire/scraper.go`, `storage/hybrid.go`, `storage/pocketbase.go` - Store interface: `store.go` — never touch MinIO/PocketBase clients directly outside `storage/` - Two binaries built from the same module: `backend` (HTTP API) and `runner` (Asynq worker) --- ## SvelteKit UI - Source: `ui/src/` - i18n: Paraglide — translation files in `ui/messages/*.json` (5 locales) - Auth debug bypass: `GET /api/auth/debug-login?token=&username=&next=` --- ## iOS App Full context in `ios/AGENTS.md`. Quick notes: - SwiftUI, iOS 17+, `@Observable` for new types - Download key separator: `::` (not `-`) - Voice fallback: book override → global default → `"af_bella"` - Offline pattern: `NetworkMonitor` env object + `OfflineBanner` + `ErrorAlertModifier`