Architecture
Bromite userscript (isolated world, per-site adapters, localStorage cache)
-- fetch() HTTPS --> reverse proxy (TLS + CORS) --> Go net/http --> SQLite (volume)
Why this shape
Bromite runs Chromium's native userscript engine, not Tampermonkey. That
rules out GM_* APIs entirely:
- No
GM_setValue/GM_getValue→ pagelocalStorageinstead. - No
GM_registerMenuCommand→ on-page UI (floating button + slide-in panel) instead of a browser menu. - No
GM_xmlhttpRequestfor cross-origin → plainfetch(), which only works against a CORS-enabled backend.
Manga sites are https://, so the backend must be HTTPS or the browser
blocks the fetch() as mixed content.
Asura and Demonic are separate origins with separate localStorage — a
shared remote store (the backend) is the only way to unify bookmarks across
them. Cloud sync is required, not a nice-to-have.
The userscript runs in an isolated world, so the embedded API token is invisible to the site's own JS.
Components
- Backend (
backend/) — stdlibnet/http, no framework,modernc.org/sqlite(pure Go,CGO_ENABLED=0→ static binary → distroless image). Listens plain:8080; the reverse proxy terminates TLS. - Store — one
bookmarkstable, keyed<site>:<series_id>. Single-user. Sync is last-write-wins. - Web UI — same binary, second hostname, password-gated. See Web-UI.
- Latest-chapter poller — background goroutine that re-checks each
bookmarked series' newest chapter on its own schedule, independent of the
userscript. Details in Backend-API and
plans/2026-07-26-server-latest-chapter-polling.md. - Userscript — one file, per-site adapters, Shadow DOM UI. See Userscript.
The updated_at rule
updated_at drives list order, so it must move only on real reading
progress — not on favouriting, not on the poller learning a new chapter.
The server applies its own timestamp when a row is new or last_chapter_num
changed; otherwise it keeps the stored value. PUT returns the row as
stored, and every client (userscript, web UI) adopts that response instead
of its own payload. This is the single place that rule lives — see
plans/2026-07-25-bookmark-list-favorites-design.md §4.
Known accepted limitation
The poller's read-modify-write (Store.Get + Store.Upsert) is not wrapped
in a transaction. A userscript PUT that lands between the poller's read and
write can be overwritten by the poller's stale re-read — reverting progress
and reordering the list. Accepted for a single-user deployment; not scheduled
to be fixed.
Skills used for infra work
multi-stage-dockerfile and docker-compose-orchestration (see repo
CLAUDE.md) cover the container patterns behind backend/Dockerfile and the
compose files.