# AGENTS.md ## Safety Rules - Dev branch naming: `{feat|fix|docs|chore}/{branch-name}` (e.g. `feat/file-tag-done`, `fix/tree-render`). - Before `git commit`: run `git branch --show-current`. If on `master`, do NOT commit — ask user for a branch name (suggest one based on the changes), create it, commit there. - Every user-visible change gets a changelog entry: WebUI changes (files under `frontend/`) → `docs/CHANGELOG_web.md`, everything else → `docs/CHANGELOG.md`; never duplicate an entry across both. Higher versions on top. - CHANGELOG entry format: same entry has English line then Chinese line on consecutive lines (no blank line between them); different entries are separated by a blank line. - `docs/README.md` and `docs/README_zh.md` stay content-equivalent; update them in the same change. ## Docker deployment (dev & release) - dev = `deploy/docker-compose.dev.yml`: source bind-mounted (`../backend:/app`, `../frontend:/app` with an anonymous volume on `/app/node_modules`), `target: dev` images; api runs `go mod download && dlv debug ./cmd/webui` (delve on 2345), web runs `npm run dev` (hot reload, no rebuild needed); infra ports exposed to host (postgres 5432, redis 6379); startup gated on healthchecks. - release = `deploy/docker-compose.yml`: multi-stage `*.prod` Dockerfiles (`target: runner`) with compiled binaries, no source mounts; infra ports stay on the internal network only. - Persisted state lives only in named volumes `booklib_{postgres,redis}_data` — same names in both files, so dev and release see the same data. Only `down -v` wipes them. - Books on disk: host `deploy/api/storage` is mounted as `/data/books` (`BOOKS_DIR`); uploads land there and the scanner picks them up. - Rendered configs `deploy/nginx/nginx.conf`, `deploy/nginx/conf.d/default.conf`, `deploy/redis/redis.conf` (mounted `:ro`) come from their `*.tpl` via `deploy/prepare.sh` — edit the template, rerun `prepare.sh`, restart; stale files in a container mean you forgot to rerun. Host logs under `deploy/logs/`. - Tear down with `down`, not `rm`, or the web image's anonymous `node_modules` volume gets orphaned. ## Project structure Root: `docs` (docs + changelogs), `backend` (Go webui service), `frontend` (node app, served via nginx in release), `deploy` (compose stacks, rendered configs, host logs). Actual layout is visible via `ls`; only rules not derivable from it are listed here. ### backend - `cmd/`: each subdir is exactly one `main` package; `main.go` only bootstraps (load config, wire deps, serve, graceful shutdown) — no business logic, and nothing outside `cmd` imports `cmd`. - `cmd/webui/api` is the single source of truth for the endpoint contract; the frontend aligns with it. - Handlers stay thin (bind/validate, call `internal`, map errors) — never touch the DB or implement domain rules; SQL and business rules live behind `internal` boundaries. - Business code defaults to `internal/`; `pkg/` is opt-in public surface shared with other repos — keep it small, never leak `internal` types through it. ### frontend - Talks to the backend only over the HTTP APIs in `backend/cmd/webui/api` — no direct infra access (DB, redis) from the browser app. - Build output (`dist/`) is disposable and git-ignored; only `src`/`public` are committed. - Dev runs from the source mount with `node_modules` provided by the image — install new deps inside the container and commit the lockfile. - TypeScript runs side-by-side (`frontend/package.json`): `typescript` = TS 6.0.2 JS API for tooling (typescript-eslint doesn't support TS 7 yet), `@typescript/native` = native 7.0.2 providing `tsc`. Do not re-alias `typescript` to 7.x until typescript-eslint ships TS 7 support. Plain `npm install` needs `--legacy-peer-deps` (also pinned in `frontend/.npmrc`); `npm ci` is unaffected. ### deploy - Only config templates/examples are tracked by git; rendered `*.conf` and `logs/` are git-ignored. - Service-specific host dirs (storage, config) go under `deploy/{$service_name}/`, never loose at the repo root.