diff --git a/CONTEXT.md b/CONTEXT.md index bd54514..e602e8c 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -155,6 +155,16 @@ Series (see `series.finished_at`), owned by the owner and stamped once, and ever on a finished Series is archived. _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**: A reader's manual pin on a Bookmark. Orthogonal to the Lifecycle bucket, and never a reason to reorder the list. diff --git a/PRODUCT.md b/PRODUCT.md index 1c9e02b..a29f69c 100644 --- a/PRODUCT.md +++ b/PRODUCT.md @@ -29,8 +29,8 @@ Not a public reading tracker or social app — a private, self-hosted sync layer ## 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. +- 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, 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. diff --git a/README.md b/README.md index 11e096b..a1b3624 100644 --- a/README.md +++ b/README.md @@ -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 does not touch read progress, and reading an archived series leaves it archived. -- **Finished**: series you have completed live in a **Finished** tab in the web - UI only. It is set there and nowhere else — the API rejects the value — and - finished series are hidden from every userscript tab and are no longer polled - for new chapters. +- **Finished**: the owner marks a Series finished from its detail page; the + backend stops polling it, and every Reader sees a read-only label. It is a + fact about the Series, not a Reader's bucket: old `Finished` bookmarks were + 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 versa — the backend is the shared store. diff --git a/docs/design-system.md b/docs/design-system.md index e060c91..1b254f0 100644 --- a/docs/design-system.md +++ b/docs/design-system.md @@ -2,7 +2,7 @@ Source of truth: the Claude Design project **BookmarkManager Web UI** (`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 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 | | `--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-soft` | `#1a1817` | `#e8e3dc` | the measure's own side edges | | `--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 | | `--brass` | `#b8912f` | `#8a681c` | favourite — a cooler second metal | | `--slate` | `#7fa0c0` | `#3f6689` | archive accent | -| `--moss` | `#7fae86` | `#3d6c46` | finished accent | +| `--moss` | `#7fae86` | `#3d6c46` | finished Series label | | `--clay` | `#b5906f` | `#7c5533` | set-chapter accent | | `--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 | @@ -164,9 +164,9 @@ rather than scaling the artwork down. **Action key** (`.keyrow`): one permanent line under the tabs naming what 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, -not the row: Archive becomes Restore under Archived and Finished, and Finished -drops Done. On a phone each pair stacks icon-over-word +Delete — so the icon strip on a card is never a guess. The key follows the +tab, not the row: under Archived, Archive becomes Restore. On a phone each +pair stacks icon-over-word (`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` carry their icon's resting accent so the key itself teaches the colour @@ -179,7 +179,7 @@ article.card[.is-new|.is-dim]#card-[data-title] .row a.cover[tabindex="-1" aria-hidden] img | span.monogram, + span.foot-rule[.brass] .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) .confirm-row[.calm][hidden] × one per lifecycle action, span + (go/danger-solid, Cancel) p.error-inline[hidden] @@ -188,7 +188,7 @@ article.card[.is-new|.is-dim]#card-[data-title] Rules that are easy to break: - `.is-new` only when `Status == reading && HasNewChapter`; `.is-dim` for - `archived` and `finished`. Both are set on the `
` — every heat and + `archived`. Both are set on the `
` — every heat and dim rule is a descendant selector off those two classes, so a new sub-element inherits the state for free. - `.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 `border-right: 1px var(--rule)`, last child none. - 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 `--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 the phone's inset hairline. - Every lifecycle button that moves a series out of the list is - **confirm-gated**: it opens its own `.confirm-row` (`archive`, `finish`, - `remove` — `toggleConfirmRow(key, kind)` in `filter.js`). Archive and finish - ask in `.calm` grey since they're reversible; remove alone gets the + **confirm-gated**: it opens its own `.confirm-row` (`archive`, + `remove` — `toggleConfirmRow(key, kind)` in `filter.js`). Archive asks in + `.calm` grey since it's reversible; remove alone gets the `--danger-wash` treatment and names the series in its question. Restore fires instantly — no confirm — because it's the reversal. - 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 hover. Desktop cell borders follow the same accent on hover (`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. 3. If it is per-series, hang it off `.is-new` / `.is-dim` rather than adding a 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 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