Spec #136: finished becomes a Series fact, Lane gate rewritten on it (#157)

finished_at lands on series (0016 seeds it from the pre-flip finished
bookmarks, then flips those bookmarks to archived), the poll gate and
eligible count read the flag instead of a per-Reader vote, PUT rejects
the finished status like any unknown value, and the web UI drops the
Finished tab, badge and finish button. Novel merge rank is archived >
reading. Per-Reader disagreement (one Reader keeps a finished Series in
reading forever) is what the flag repairs; the cutover keeps polling
state unchanged for every Series.
This commit is contained in:
2026-08-22 13:02:09 +07:00
parent faa80c41ea
commit 77e3f710ec
21 changed files with 423 additions and 189 deletions
+9 -5
View File
@@ -200,12 +200,16 @@ stored** and clients must adopt that response rather than their own payload.
### Lifecycle buckets — `status` on each bookmark
`reading` | `archived` | `finished`, orthogonal to `favorite`. Archived and
finished appear only in their own tab, never in All, Updated, Favourites or the
recent strip. The poller keeps checking archived series and skips finished ones.
`reading` | `archived`, orthogonal to `favorite`. Archived rows appear only
in their own tab, never in All, Updated, Favourites or the recent strip. The
poller keeps checking archived series; a finished Series (issue #157) is a
`series.finished_at` fact the Lane gate reads, with every bookmark on it
archived.
- `finished` is settable only from the web UI; `PUT /bookmarks/{key}` rejects
it with 400.
- `PUT /bookmarks/{key}` accepts only the two values; anything else —
`finished` included — is a plain 400, and the web UI's own status control
validates the same way. The 0016 migration is the only writer of the flag
today; the undo is writing 0.
- **An empty incoming status means "keep the stored one"**, and it is resolved
on the `VALUES` side of `Store.Upsert`, not in the conflict clause:
`excluded.*` is the post-evaluation row, so a default applied there would