diff --git a/AGENTS.md b/AGENTS.md index fedf19f..f03f720 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -27,17 +27,43 @@ Bromite userscript (isolated world, per-site adapters, localStorage cache) ``` - **Backend** (`backend/`): stdlib `net/http` (handful of routes, no framework) + `modernc.org/sqlite` (pure Go, `CGO_ENABLED=0` -> static binary -> distroless/scratch image). Reverse proxy terminates TLS; Go service listens plain `:8080`. + Single binary, split into packages under `backend/internal/`: `store` + (Bookmark type, SQLite persistence, migrations), `latest` (background + poller, site parsers, TLS fetcher), `session` (cookie signing, login + rate limiter), `httpmw` (Auth/Gzip/CORS middleware), `api` (JSON + bookmark handlers), `userscript` (userscript-serving handler), `web` + (browser UI handler + `templates/` + `static/`, `go:embed`-ed). + `backend/main.go` is the composition root — the only place that wires + packages together into `newRouter`. Root-level `*_test.go` hold + integration tests that exercise the full router; unit tests for a + package live beside it under `internal/`. - **Single-user store.** One `bookmarks` table keyed `:` (`asura`|`demonic`). Sync **last-write-wins**. Schema + endpoint list in plan. - **Endpoints:** `GET /bookmarks`, `PUT /bookmarks/{key}` (upsert; see `updated_at` rule below), `DELETE /bookmarks/{key}`, `GET /healthz` (no auth). - **Web UI:** same binary serves password-gated browser UI on second hostname — `GET /` (list, or login page when no session), `POST /login`, `POST /logout`, `GET /static/*`, htmx fragment endpoints - under `/ui/*`. Templates/assets `go:embed`-ed, so `backend/Dockerfile` - must copy `templates/` and `static/` plus `*.go`. Sessions = stateless + under `/ui/*`. Templates + assets `go:embed`-ed under + `backend/internal/web/`, so `backend/Dockerfile` must copy the whole + `internal/` tree, not just `*.go`. Sessions = stateless HMAC cookies keyed off `API_TOKEN`; `WEB_PASSWORD` gates them, when empty web routes not registered at all. UI mutations read-modify-write through `Store.Get` + `Store.Upsert` so `updated_at` rule stays one place. See `docs/superpowers/specs/2026-07-25-web-ui-design.md`. + **Design-tool caveat:** templates link `/static/style.css` root-absolutely + (correct — served from `/`), but impeccable detector resolves + stylesheet href with `path.resolve(fileDir, href)`, drops directory + on leading `/` and silently skips file. Relative hrefs don't help + either: template's directory isn't its served path. So + `detect.mjs backend/internal/web/templates` reports **false clean** — + always pass `backend/internal/web/static` too. One finding there, + `overused-font` on "Instrument Serif", deliberate identity choice, not debt. +- **Every action that moves series out of list is confirm-gated.** + Archive, finish, remove each open own `.confirm-row` disclosure + (`toggleConfirmRow(key, kind)` in `filter.js`, `kind` ∈ + `archive|finish|remove`); restore fires instantly since it's the reversal. + Remove's row wears ember wash, two reversible ones wear `.calm` grey. + `--ember` stays reserved for new-chapter signal: busy bar and inline + error use `--mute`. - **Latest-chapter poller:** ticker goroutine in same binary re-checks each bookmarked series' newest published chapter from backend's own network access, so `latest_chapter` stays fresh when user not @@ -96,7 +122,14 @@ Bromite userscript (isolated world, per-site adapters, localStorage cache) 5. **UI** — rendered inside **Shadow DOM** root to isolate from site CSS (critical on mobile). Three tabs (All / Favourites / Archived) + row of link chips to web UI and both manga sites; `WEB_BASE` sits in CONFIG - block next to `API_BASE`. + block next to `API_BASE`. FAB is `7 × 44` edge tab whose *hit* area + widened to `28 × 72` by invisible `#hit` child; `#fab` must keep + `touch-action: none` and must **not** regain `overflow: hidden`. Since + `touch-action` resolved at gesture start, strip can't be both + browser-scrolled and script-dragged, so `makeDraggable` splits by intent: swipe + from `#hit` scrolls via `window.scrollBy`, hold of `ARM_MS` arms + reposition drag, visible sliver drags with no hold. See + `docs/superpowers/specs/2026-07-28-edge-tab-hitbox-design.md`. 6. **SPA navigation** — Asura is Astro, client-routed on comic/chapter pages: patch `history.pushState`/`replaceState` + listen `popstate`, re-run `detect()` on URL change so auto-update fires w/o reload. Demonic uses classic reloads (initial `document-idle` run suffices). ### Live URL shapes (verified 2026-07-26, may drift — re-check against live pages before trusting) @@ -135,15 +168,55 @@ Smoke test: `curl` endpoints w/ `Authorization: Bearer `; confirm `OPTION `tea` prints output as rendered boxes not plain text; PR URL lands on last line. +## Design system + +Web UI + userscript panel follow **Cinder**, rules in `docs/design-system.md` +— source of truth Claude Design project `mangaBookmark Web UI` +(`969ac210-fe02-4c01-ae1b-9a271dcc779a`). Read it before touching +`backend/internal/web/static/style.css`, `backend/internal/web/templates/*`, or userscript +`TEMPLATE`/`CSS`. Core law: **ember means new chapter only** — no other +state (busy, error, destruction) may use `--ember`; destruction gets +`--danger`. No cards/corners/shadows, one `--measure: 760px` column, tokens +only (never hardcode hex outside `:root`), both colour branches touched +together. Any move that pulls series out of list (archive/finish/remove) +must be confirm-gated via its own `.confirm-row`; only restore fires +instantly. + ## Security invariants - Auth on `/bookmarks*`: require `Authorization: Bearer `, **constant-time compare**, 401 otherwise. - CORS: reflect `Origin` only when in `ALLOWED_ORIGINS`; allow `GET,PUT,DELETE,OPTIONS` + headers `Authorization,Content-Type`; answer preflight `OPTIONS` w/ `204`. +## Comments + +Comment only if code alone can't carry info. Cost per read — must earn spot. + +Write for: +- Why not what. Tradeoffs, non-obvious decisions. +- Load-bearing detail looking incidental — say so if "simplify" breaks it. +- Non-local consequence, invisible from function alone. +- Wire format / encoding / interface contract — save callers re-deriving. +- Gotcha/workaround, with ref if exists. +- Domain/business rule not derivable from code. + +Skip: +- Restating code (no `// increment i` above `i++`). +- Trivial getter/setter/pass-through. +- Banners, dividers, `// helpers`. +- Change narration (`// fix bug`, `// as requested`, `// new impl`) — git's job. +- Commented-out code — delete. +- TODO without concrete action. + +Style: one dense comment over function beats one per line inside. Tight, no worked example unless bug subtle. Wrong comment worse than none — update/delete on change. Default fewer — sparse+high-signal beats comprehensive. + +Test: "competent reader get this from code in few sec?" Yes → skip. Needs detour through another file/spec/git-blame → write it. + ## Relevant skills `multi-stage-dockerfile` and `docker-compose-orchestration` for container work (referenced in plan). +`golang-code-style`, `golang-error-handling`, `golang-performance`, `golang-testing` for backend Go work. + ## graphify Project has knowledge graph at graphify-out/ w/ god nodes, community structure, cross-file relationships.