# Bookmark list ordering, latest-chapter display, and favorites Date: 2026-07-25 Status: Approved by user, pending implementation plan ## Context Three requested additions to the Bromite userscript's bookmark panel (`userscript/manga-bookmark.user.js`) plus one incidental bug found while verifying feasibility live: 1. Bookmark list should show the most-recently-read manga first. 2. Show not just the last chapter *read*, but the latest chapter *available* for that manga, if feasible. 3. A favorites mechanism: a second list/tab showing only favorited manga, without removing favorited manga from the normal list. ## 1. Reorder by latest read (already implemented) `reindex()` in `manga-bookmark.user.js` has sorted `state.list` by `(b.updated_at || 0)` descending since the very first commit (`f58d113`). `updated_at` is set whenever progress is recorded (`bookmarkCurrent`, `updateToCurrentChapter`, `setChapterManual`). No code change needed here — confirmed by decision: only an actual progress advance should reorder the list (not simply opening/re-reading an old chapter). The only risk to this existing behavior is introduced by features 2 and 3 below, since both add new fields synced through the same PUT endpoint that currently (per existing CLAUDE.md) has the server set `updated_at` unconditionally on every upsert. See section 4. ## 2. Latest available chapter ### Feasibility (verified live via Playwright, 2026-07-25) - **Chapter reader pages only expose immediate neighbors.** On `asurascans.com/comics/dungeon-odyssey-f886a8af/chapter/160`, the DOM contains only chapters 159–161 (prev/next nav) — not the full list. - **Series pages list every chapter, newest first by default.** On `asurascans.com/comics/dungeon-odyssey-f886a8af`, the series page's chapter list panel contains one `Chapter N ...` per chapter (verified: 162 links for a 162-chapter series, first is Chapter 162). This is therefore the only reliable place to learn the true latest chapter number. - Demonic's series page (`demonicscans.org/manga/Dungeon-Odyssey`) similarly lists every chapter via `chaptered.php?manga=&chapter=` links, interleaved with a "first chapter" quick-jump link. Taking `max()` of all parsed chapter numbers (rather than assuming list order) is used for robustness on this site. **Conclusion: latest-available-chapter data can only be refreshed when the user visits a bookmarked series' series page** (not on every chapter open). This is a real, disclosed limitation — not solved by polling, since the backend cannot fetch these sites itself (Cloudflare blocks server-side fetch, per existing CLAUDE.md constraint) and there is no reader-page source for it. ### Behavior - New adapter capability: on `detect()` returning `type: "series"` for an already-bookmarked series (`state.byKey[key]` exists), scan the page for chapter links per the site-specific pattern below and compute the max chapter number + its display label. - Asura: `a[href*="/chapter/"]` where the href matches `/chapter/([\d.]+)$/` and the link text matches `/Chapter\s+[\d.]+/` (excludes the unrelated "First Chapter" quick-jump button, which lacks that text pattern). - Demonic: all `a[href]` matching `/chaptered\.php\?manga=\d+&chapter=([\d.]+)/`; take the max parsed chapter number across all matches (list order is not assumed reliable). - If the computed max differs from the bookmark's stored `latest_chapter_num`, silently update `latest_chapter` (label) and `latest_chapter_num` via the API — but this update must **not** change `updated_at` / list order (see section 4). - Display: each list item's subtitle line becomes e.g. `"Read: Chapter 12 · Latest: Chapter 15"` when latest is known and differs from last-read; otherwise unchanged (`"Chapter 12 · asura"` as today). Plain inline text, no separate badge/count UI. ## 3. Favorites - New field `favorite: bool` on the bookmark record, synced through the existing PUT endpoint (chosen over local-only storage so favorites persist across devices/reinstalls, consistent with how the rest of the data syncs). - Each list item gets a star toggle (☆ / ★) that flips `favorite` and PUTs the updated bookmark. Toggling **must not** change `updated_at` / list order (see section 4). - The panel gains two tabs above the bookmark list: **All** and **★ Favorites**. Both apply the same sort (section 1). Switching tabs is local UI state (not persisted) defaulting to "All". A favorited manga continues to appear in "All" — tabs only filter which array is rendered, favoriting never removes the bookmark from `state.list`. ## 4. Backend change required: conditional `updated_at` Current CLAUDE.md / `store.go` behavior: `PUT /bookmarks/{key}` always sets `updated_at` server-side on every upsert. Once latest-chapter-capture and favorite-toggle both PUT through that same endpoint, this would reorder the list on every series-page visit or star click — contradicting the "reorder only on progress advance" decision from section 1. **Change:** `Store.Upsert` sets `updated_at = now()` only when: - the bookmark is new (no existing row for that key), or - `last_chapter_num` in the incoming payload differs from the currently stored value. Otherwise the existing stored `updated_at` is preserved, even though other fields (`favorite`, `latest_chapter`, `latest_chapter_num`, cover, title, etc.) are still updated. This centralizes "what counts as a progress advance" as a single authoritative rule in the backend, applied consistently regardless of which device/browser performed the write. This is a deliberate deviation from the current CLAUDE.md wording ("server sets `updated_at`" unconditionally) and needs the doc updated to match. ## 5. Asura redirect bug (bundled into this work) Verified live: `https://asuracomic.net/comics/dungeon-odyssey-f886a8af` returns an HTTP **301** with `Location: https://asurascans.com/` (root, no path) — a Cloudflare-edge redirect that discards the path *before any JS on asuracomic.net executes*. This contradicts the existing code comment ("asuracomic.net currently 301s to asurascans.com", implying path preservation) and means any deep link on `asuracomic.net` currently lands the user on the asurascans.com homepage with page type `"other"` — bookmark/progress detection silently does nothing. There is no client-side fix: the userscript's `@match` for `asuracomic.net/*` never gets a chance to run for these URLs, since the redirect happens at the edge before the browser has a document to inject into. **Fix is documentation-only**: correct the misleading comment in `asura.matches()`/adapter notes and README to state that `asuracomic.net` deep links are currently broken, and the user should navigate via `asurascans.com` links directly. No behavior change to ship. ## Data model summary `Bookmark` (backend `store.go`) gains: | field | type | notes | |---|---|---| | `favorite` | `bool` | default `false` | | `latest_chapter` | `string` | display label, e.g. `"Chapter 162"`; empty if never captured | | `latest_chapter_num` | `*float64` | nullable; null if never captured | SQLite migration: additive `ALTER TABLE bookmarks ADD COLUMN ...` for each, guarded against "duplicate column" errors so it's safe to run against the already-deployed database. ## Testing - Go: table-driven tests for `Store.Upsert`'s conditional `updated_at` logic (new bookmark, unchanged progress, changed progress, favorite-only change, latest-chapter-only change). - Userscript: manual on-device verification (per existing project convention — no JS test harness in this repo). Verify: - list reorders only when a chapter is actually advanced, not on plain re-visit or favorite toggle. - latest-chapter capture fires on series-page visits and displays correctly for both sites. - favorite toggle persists across a panel close/reopen and a `refresh()` (i.e. round-trips through the backend correctly). - favorited manga still appears in "All" tab.