Files
mangaBookmark/PRODUCT.md
T
sulthan b0bf6fe770 feat: register any guild member as a Reader (#27)
Guild membership is now the whole gate: discordCallback checks membership
(and DISCORD_REQUIRED_ROLE when set), then Store.EnsureReader creates the
Reader on first sight and returns the same row on every later login. The
refusal returns before EnsureReader, so nothing is created as a side
effect of being turned away. OWNER_DISCORD_ID keeps seeding the owner, but
only as the administrator — it no longer gates sign-in.

The cutover grace path is gone with it: API_TOKEN, API_TOKEN_GRACE_UNTIL
and the legacy branch in httpmw.ResolveReader are deleted, so a credential
authenticates exactly one Reader or nothing. That also lets
userscript.Handler drop the re-derivation — the resolved path segment is
already the credential to substitute.

New surfaces: an empty library offers both install links instead of
describing a filter (listView.Fresh, which also hides the action key it has
nothing to name), and the owner alone gets a Readers panel with
POST /readers/{id}/revoke (404 for anyone else) to sign a Reader out
everywhere.

Isolation is asserted from both directions rather than by counting one
Reader's rows, and the shared-series invariant is pinned: two Readers on
one series produce one series row, two independent progresses, one poll
per due cycle, and one Reader's delete leaves the other's bookmark and the
poll intact.
2026-08-08 20:01:42 +07:00

5.0 KiB

Product

Platform

web

Users

Members of one private Discord guild, each with their own library. Accounts exist and are created by signing in — there is no signup form, no invite code and no approval step: any member of the configured guild becomes a Reader on their first Discord login. The person running the deployment is the owner, seeded at startup, and the only Reader with an administrative capability (revoking another Reader's sessions).

Reading happens on asurascans.com, demonicscans.org, comix.to and kagane.to for manga and novelfull.com and lightnovelworld.net for novels, primarily via Bromite on mobile, with checks and corrections from a desktop browser. The web UI is the cross-device view into progress the userscripts capture while reading.

Product Purpose

Tracks read-progress ("last chapter read") per series across sites that each have their own separate localStorage. A Go backend unifies bookmarks into one store; the web UI is a Discord-gated browser view of one Reader's own bookmarks, for reviewing, favouriting, correcting, shelving or removing them, and jumping back into a series to continue reading. A background poller refreshes each series' latest-published-chapter so the list can flag "NEW" without the Reader visiting the site.

Positioning

Not a public reading tracker or social app — a private, self-hosted sync layer for one Discord community, purpose-built for a fixed set of scraped sites. Multi-Reader, not multi-tenant: libraries are isolated, but the deployment belongs to one group and its membership is the whole access model.

Operating Context

  • Primary reading device: Bromite (mobile Chromium), where a userscript captures progress automatically. Each Reader installs their own copy, rendered with their own credential.
  • Web UI is a secondary surface: checking list state, correcting a wrong chapter number, removing dead bookmarks, jumping to "continue reading."
  • Cover art and titles come from the source sites' og:image/og:title — real content, not placeholders. They are facts about the series, so they are shared between Readers who track it; progress is not.
  • List order is driven by updated_at, which moves only on real reading progress (not favouriting, not a newly detected chapter) — a UI constraint the design must not break.

Capabilities and Constraints

  • Two libraries (manga, novels) with lifecycle tabs: All / Updated / Favourites / Archived / Finished. Search-filter by title (client-side, filter.js).
  • Card actions: continue (opens source site), toggle favourite, manual chapter override, archive, finish, remove — each move out of the list confirm-gated.
  • "Continue reading" horizontal strip for series with an unread chapter.
  • A Reader with no bookmarks at all sees a deliberate empty library offering both userscript install links, not an error and not a blank page.
  • Isolation is the load-bearing invariant: two Readers cannot see or change each other's bookmarks. A series both track is one shared row polled once, with independent progress on each side.
  • The owner can revoke a specific Reader's sessions; nothing else in the UI differs by Reader.
  • htmx-driven partial updates, no client-side framework or build step — templates are Go html/template, go:embed-ed.
  • Mobile-first is a hard functional constraint (primary device is a phone), not just a starting breakpoint.

Brand Commitments

  • Name: BookmarkManager.
  • Dark-first is binding: dark-by-default / light-follows-system-preference must be preserved as a design constraint, not just a starting default, because reading happens at night.

Evidence on Hand

  • Live templates/CSS at backend/internal/web/templates/*.html, backend/internal/web/static/style.css, governed by the Cinder design system (docs/design-system.md).
  • No logo beyond the wordmark, no screenshots, no marketing copy; none should be fabricated.

Product Principles

  • Dark-first, night-reading-optimized — never regress to a light-default or high-glare surface.
  • Mobile is the primary target; desktop is an enhancement, not the design center.
  • Progress data integrity over visual flourish: updated_at/list-ordering behavior is a correctness constraint the UI must respect, not decorate over.
  • A leak between Readers fails silently and looks like working software — isolation is asserted from both directions, never inferred from counting one Reader's rows.
  • No roles, no admin console, no org chrome: one owner capability and otherwise every Reader's view is the same.
  • Prefer native platform affordances (system dark/light, native touch targets) over custom widgetry — this is a lean self-hosted tool, not a product to demo.

Accessibility & Inclusion

Explicit personal requirement: optimized for low-light/night reading — minimize glare and eye strain (true dark surfaces, restrained brightness/contrast on accents, no jarring pure-white flashes), beyond generic touch-target/contrast compliance.