diff --git a/.gitea/workflows/release.yaml b/.gitea/workflows/release.yaml index 33ca2c2..2bd0654 100644 --- a/.gitea/workflows/release.yaml +++ b/.gitea/workflows/release.yaml @@ -172,6 +172,14 @@ jobs: SENTRY_ORG: libnovel SENTRY_PROJECT: ui + - name: Create GlitchTip release + run: glitchtip-cli releases new ${{ gitea.ref_name }} + env: + SENTRY_URL: https://errors.libnovel.cc/ + SENTRY_AUTH_TOKEN: ${{ secrets.GLITCHTIP_AUTH_TOKEN }} + SENTRY_ORG: libnovel + SENTRY_PROJECT: ui + - name: Upload source maps to GlitchTip run: glitchtip-cli sourcemaps upload ./build --release ${{ gitea.ref_name }} env: @@ -180,6 +188,14 @@ jobs: SENTRY_ORG: libnovel SENTRY_PROJECT: ui + - name: Finalize GlitchTip release + run: glitchtip-cli releases finalize ${{ gitea.ref_name }} + env: + SENTRY_URL: https://errors.libnovel.cc/ + SENTRY_AUTH_TOKEN: ${{ secrets.GLITCHTIP_AUTH_TOKEN }} + SENTRY_ORG: libnovel + SENTRY_PROJECT: ui + # ── docker: ui ──────────────────────────────────────────────────────────────── docker-ui: name: Docker / ui diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..a33c7f4 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,150 @@ +# 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`