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
+8 -8
View File
@@ -471,9 +471,10 @@
}
// Only an explicit archive/restore has an opinion about the bucket. Every
// other write omits `status`, so the server keeps the stored one — otherwise
// a cached value would resend "finished" (which the API rejects with 400) or
// silently un-archive a series archived on another device.
// other write omits `status`, so the server keeps the stored one —
// otherwise a cached value would resend a stale status (the API rejects
// unknown values with 400) or silently un-archive a series archived on
// another device.
async function apiPut(key, obj, { sendStatus = false } = {}) {
const body = Object.assign({}, obj);
if (!sendStatus) delete body.status;
@@ -888,8 +889,8 @@
}
// Archive parks a series: it leaves All and Favourites but the server keeps
// polling it for new chapters. "finished" is deliberately not reachable from
// here — the API rejects that value, it is a web-UI decision.
// polling it for new chapters. finished is not a bucket the script can
// reach — it is a fact about the Series now (issue #157).
async function toggleArchive(key) {
const existing = state.byKey[key];
if (!existing) return;
@@ -966,7 +967,6 @@
const now = Date.now();
const due = state.list
.filter((b) => b.site === site && b.series_url)
.filter((b) => statusOf(b) !== "finished")
.filter((b) => now - (checked[b.key] || 0) >= LATEST_CHECK_THROTTLE_MS)
.sort((a, b) => (checked[a.key] || 0) - (checked[b.key] || 0))
.slice(0, LATEST_CHECK_BATCH);
@@ -1335,8 +1335,8 @@
}
// Tabs narrow what is drawn; state.list always holds every bookmark.
// Archived rows are hidden from All and Favourites, and finished ones —
// which only the web UI can set — are hidden from every tab.
// Archived rows are hidden from All and Favourites; a Series that is
// finished is the web UI's business and arrives archived (issue #157).
for (const [id, tab] of [["tabAll", "all"], ["tabFav", "favorites"], ["tabArc", "archived"]]) {
root.getElementById(id).classList.toggle("active", activeTab === tab);
}