Merge #161: Glossary and stale Reader-count note catch-up

This commit is contained in:
2026-08-22 17:14:23 +07:00
4 changed files with 30 additions and 20 deletions
+10
View File
@@ -155,6 +155,16 @@ Series (see `series.finished_at`), owned by the owner and stamped once, and ever
on a finished Series is archived. on a finished Series is archived.
_Avoid_: state, status (as a domain word), list _Avoid_: state, status (as a domain word), list
**Finished Series**:
A Series the owner has marked finished, stamped once in `series.finished_at`
(epoch ms, zero means not finished). The owner is its only writer — no
adapter, no Reader, no Poll can set it — and a Forced Poll reads a finished
Series once for that pass and never clears the flag. It is a fact about the
Series, not a Bookmark bucket: every Bookmark on a finished Series is
archived, the Lane stops polling it (the due gate reads `finished_at = 0`),
and Readers see a label and nothing more.
_Avoid_: completed, done, dropped, shelved (that is Archived), ended
**Favourite**: **Favourite**:
A reader's manual pin on a Bookmark. Orthogonal to the Lifecycle bucket, and never a A reader's manual pin on a Bookmark. Orthogonal to the Lifecycle bucket, and never a
reason to reorder the list. reason to reorder the list.
+2 -2
View File
@@ -29,8 +29,8 @@ Not a public reading tracker or social app — a private, self-hosted sync layer
## Capabilities and Constraints ## Capabilities and Constraints
- Two libraries (manga, novels) with lifecycle tabs: All / Updated / Favourites / Archived / Finished. Search-filter by title (client-side, `filter.js`). - Two libraries (manga, novels) with lifecycle tabs: All / Updated / Favourites / Archived. 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. - Card actions: continue (opens source site), toggle favourite, manual chapter override, archive, remove — each move out of the list confirm-gated.
- "Continue reading" horizontal strip for series with an unread chapter. - "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. - 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. - 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.
+4 -4
View File
@@ -218,10 +218,10 @@ desktop for faster testing — install the same file unchanged.
keeps checking it for new chapters, so it is worth coming back to. Archiving keeps checking it for new chapters, so it is worth coming back to. Archiving
does not touch read progress, and reading an archived series leaves it does not touch read progress, and reading an archived series leaves it
archived. archived.
- **Finished**: series you have completed live in a **Finished** tab in the web - **Finished**: the owner marks a Series finished from its detail page; the
UI only. It is set there and nowhere else — the API rejects the value — and backend stops polling it, and every Reader sees a read-only label. It is a
finished series are hidden from every userscript tab and are no longer polled fact about the Series, not a Reader's bucket: old `Finished` bookmarks were
for new chapters. folded into Archived in the cutover, so there is no Finished tab.
- Bookmarks made on Asura appear when the panel is opened on Demonic, and vice - Bookmarks made on Asura appear when the panel is opened on Demonic, and vice
versa — the backend is the shared store. versa — the backend is the shared store.
+14 -14
View File
@@ -2,7 +2,7 @@
Source of truth: the Claude Design project **BookmarkManager Web UI** Source of truth: the Claude Design project **BookmarkManager Web UI**
(`969ac210-fe02-4c01-ae1b-9a271dcc779a`, `index.html` + siblings (`969ac210-fe02-4c01-ae1b-9a271dcc779a`, `index.html` + siblings
`archived.html`/`fav.html`/`finished.html`/`new.html`/`login.html`/`mobile.html`, `archived.html`/`fav.html`/`new.html`/`login.html`/`mobile.html`,
`style.css`, `filter.js`). This file records the rules that got implemented so `style.css`, `filter.js`). This file records the rules that got implemented so
a future agent can extend the UI without re-reading the design. a future agent can extend the UI without re-reading the design.
@@ -48,7 +48,7 @@ Defined once in `backend/internal/web/static/style.css` `:root`, mirrored in the
| --- | --- | --- | --- | | --- | --- | --- | --- |
| `--ink` | `#100f0e` | `#f7f4ef` | page | | `--ink` | `#100f0e` | `#f7f4ef` | page |
| `--ash` | `#161413` | `#efeae3` | recessed panel (chapter form) | | `--ash` | `#161413` | `#efeae3` | recessed panel (chapter form) |
| `--dim` | `#0d0c0b` | `#f1ede7` | archived / finished row background | | `--dim` | `#0d0c0b` | `#f1ede7` | archived row background |
| `--rule` | `#221f1d` | `#e0dad2` | hairline between sheets, button borders | | `--rule` | `#221f1d` | `#e0dad2` | hairline between sheets, button borders |
| `--rule-soft` | `#1a1817` | `#e8e3dc` | the measure's own side edges | | `--rule-soft` | `#1a1817` | `#e8e3dc` | the measure's own side edges |
| `--field-line` | `#2c2926` | `#d4cdc4` | input borders, ghost-button underline | | `--field-line` | `#2c2926` | `#d4cdc4` | input borders, ghost-button underline |
@@ -70,7 +70,7 @@ Defined once in `backend/internal/web/static/style.css` `:root`, mirrored in the
| `--danger-soft` | `#e2aaa1` | `#7c2c22` | text on danger wash | | `--danger-soft` | `#e2aaa1` | `#7c2c22` | text on danger wash |
| `--brass` | `#b8912f` | `#8a681c` | favourite — a cooler second metal | | `--brass` | `#b8912f` | `#8a681c` | favourite — a cooler second metal |
| `--slate` | `#7fa0c0` | `#3f6689` | archive accent | | `--slate` | `#7fa0c0` | `#3f6689` | archive accent |
| `--moss` | `#7fae86` | `#3d6c46` | finished accent | | `--moss` | `#7fae86` | `#3d6c46` | finished Series label |
| `--clay` | `#b5906f` | `#7c5533` | set-chapter accent | | `--clay` | `#b5906f` | `#7c5533` | set-chapter accent |
| `--trash` | `#977671` | `#8c6558` | remove, at rest — icons need 3:1, not 4.5:1 | | `--trash` | `#977671` | `#8c6558` | remove, at rest — icons need 3:1, not 4.5:1 |
| `--patina` | `#5fb3a6` | `#1f6f66` | admin page only — a Poll Lane needing attention, a Reader whose reports are blocked | | `--patina` | `#5fb3a6` | `#1f6f66` | admin page only — a Poll Lane needing attention, a Reader whose reports are blocked |
@@ -164,9 +164,9 @@ rather than scaling the artwork down.
**Action key** (`.keyrow`): one permanent line under the tabs naming what **Action key** (`.keyrow`): one permanent line under the tabs naming what
every icon in `.actions` does — Read / Fav / Chapter / Archive / Done / every icon in `.actions` does — Read / Fav / Chapter / Archive / Done /
Delete — so the icon strip on a card is never a guess. The key follows the tab, Delete — so the icon strip on a card is never a guess. The key follows the
not the row: Archive becomes Restore under Archived and Finished, and Finished tab, not the row: under Archived, Archive becomes Restore. On a phone each
drops Done. On a phone each pair stacks icon-over-word pair stacks icon-over-word
(`flex-direction: column`) so the word gets the full cell width; ≥720px it lays (`flex-direction: column`) so the word gets the full cell width; ≥720px it lays
out icon-beside-word at the same wording. `.pair.brass` and `.pair.trash` out icon-beside-word at the same wording. `.pair.brass` and `.pair.trash`
carry their icon's resting accent so the key itself teaches the colour carry their icon's resting accent so the key itself teaches the colour
@@ -179,7 +179,7 @@ article.card[.is-new|.is-dim]#card-<key>[data-title]
.row .row
a.cover[tabindex="-1" aria-hidden] img | span.monogram, + span.foot-rule[.brass] a.cover[tabindex="-1" aria-hidden] img | span.monogram, + span.foot-rule[.brass]
.body .title-line (h3.title + svg.fav-mark) , p.meta .body .title-line (h3.title + svg.fav-mark) , p.meta
.actions play, favourite, chapter | lifecycle: archive/restore, finish, remove .actions play, favourite, chapter | lifecycle: archive/restore, remove
form.chapter-form[hidden] .hint + .field(input + Save) + .hint (latest known) form.chapter-form[hidden] .hint + .field(input + Save) + .hint (latest known)
.confirm-row[.calm][hidden] × one per lifecycle action, span + (go/danger-solid, Cancel) .confirm-row[.calm][hidden] × one per lifecycle action, span + (go/danger-solid, Cancel)
p.error-inline[hidden] p.error-inline[hidden]
@@ -188,7 +188,7 @@ article.card[.is-new|.is-dim]#card-<key>[data-title]
Rules that are easy to break: Rules that are easy to break:
- `.is-new` only when `Status == reading && HasNewChapter`; `.is-dim` for - `.is-new` only when `Status == reading && HasNewChapter`; `.is-dim` for
`archived` and `finished`. Both are set on the `<article>` — every heat and `archived`. Both are set on the `<article>` — every heat and
dim rule is a descendant selector off those two classes, so a new sub-element dim rule is a descendant selector off those two classes, so a new sub-element
inherits the state for free. inherits the state for free.
- `.actions` is `flex: 1 0 100%` inside `.row`, which is what makes it a - `.actions` is `flex: 1 0 100%` inside `.row`, which is what makes it a
@@ -196,19 +196,19 @@ Rules that are easy to break:
the row at ≥720px. Cells are 46px tall on phone (thumb target) and divided by the row at ≥720px. Cells are 46px tall on phone (thumb target) and divided by
`border-right: 1px var(--rule)`, last child none. `border-right: 1px var(--rule)`, last child none.
- Three clusters by consequence, in this order: navigate (`.play`) | organize - Three clusters by consequence, in this order: navigate (`.play`) | organize
(`.fav`, `.pencil`) | lifecycle (`.box`/`.restore`, `.finish`, `.remove`, (`.fav`, `.pencil`) | lifecycle (`.box`/`.restore`, `.remove`,
each carrying the `.lifecycle` class). Lifecycle cells sit on a recessed each carrying the `.lifecycle` class). Lifecycle cells sit on a recessed
`--ash` ground so the thumb reads "this one moves the series" before it `--ash` ground so the thumb reads "this one moves the series" before it
reads which icon it landed on; ≥720px they separate by a 10px gap instead of reads which icon it landed on; ≥720px they separate by a 10px gap instead of
the phone's inset hairline. the phone's inset hairline.
- Every lifecycle button that moves a series out of the list is - Every lifecycle button that moves a series out of the list is
**confirm-gated**: it opens its own `.confirm-row` (`archive`, `finish`, **confirm-gated**: it opens its own `.confirm-row` (`archive`,
`remove` — `toggleConfirmRow(key, kind)` in `filter.js`). Archive and finish `remove` — `toggleConfirmRow(key, kind)` in `filter.js`). Archive asks in
ask in `.calm` grey since they're reversible; remove alone gets the `.calm` grey since it's reversible; remove alone gets the
`--danger-wash` treatment and names the series in its question. Restore `--danger-wash` treatment and names the series in its question. Restore
fires instantly — no confirm — because it's the reversal. fires instantly — no confirm — because it's the reversal.
- Per-action hover/press accent: `.fav` → `--brass`, `.pencil` → `--clay`, - Per-action hover/press accent: `.fav` → `--brass`, `.pencil` → `--clay`,
`.box` → `--slate`, `.finish` → `--moss`. `.play` stays paper/ember (ember `.box` → `--slate`. `.play` stays paper/ember (ember
only when `.is-new`). `.remove` stays `--trash` at rest, `--danger` on only when `.is-new`). `.remove` stays `--trash` at rest, `--danger` on
hover. Desktop cell borders follow the same accent on hover hover. Desktop cell borders follow the same accent on hover
(`border-color: currentColor`); the two coloured *resting* states (`border-color: currentColor`); the two coloured *resting* states
@@ -286,7 +286,7 @@ No transforms on hover, no scale, no easing curves beyond `ease-out`/`linear`.
never reuse `--ember` or `--danger` for anything but their one meaning. never reuse `--ember` or `--danger` for anything but their one meaning.
3. If it is per-series, hang it off `.is-new` / `.is-dim` rather than adding a 3. If it is per-series, hang it off `.is-new` / `.is-dim` rather than adding a
third state class. third state class.
4. If it removes a series from the current view (archive/finish/remove-shaped), 4. If it removes a series from the current view (archive/remove-shaped),
it is confirm-gated via its own `.confirm-row` — no exceptions, restore is it is confirm-gated via its own `.confirm-row` — no exceptions, restore is
the only instant action because it's the one that's reversible by nature. the only instant action because it's the one that's reversible by nature.
5. Icon → `templates/icons.html`; nothing inlines SVG paths. Brand mark stays 5. Icon → `templates/icons.html`; nothing inlines SVG paths. Brand mark stays