The cutover: finished becomes a Series fact, and the Lane's poll gate is rewritten with it #157

Closed
opened 2026-08-22 12:37:10 +07:00 by sulthan · 2 comments
Owner

Part of #136.

Blocked by: none — can start immediately.

What to build

One landing that moves the poll gate from the per-Reader finished bucket to a Series-owned flag, and deletes the bucket's write paths in the same moment. After it lands, the Lane polls exactly the set it polled yesterday (the migration seeds from the buckets before flipping them), the API refuses finished like any other invalid status, no surface can create the bucket anymore, and the decision is recorded as an ADR because the migration is one-way.

Acceptance criteria

  • One migration adds the finished_at column, seeds it from the bookmarks table, then flips every finished bookmark to archived — column, seed, flip in that order in one file, with a comment saying the order is load-bearing.
  • A data test seeds a pre-migration schema, runs the migration, and asserts: a Series held only by finished bookmarks is finished; a Series held by a mix is not; every previously-finished bookmark reads archived. The test would catch the two data statements being reordered.
  • The due query polls a Series only while it is not finished; a forced Series is due regardless of finish; the finished flag joins the query's grouping; the finished-only HAVING arm is deleted from both Lane queries.
  • The eligible count excludes finished Series and deliberately admits no forced one — the asymmetry is commented, because the count is the Lane's pace divisor.
  • A Site whose whole worklist is finished records a nothing-eligible pass, not a stall.
  • Servicing a forced request never clears the finish: nothing writes the force stamp back to zero, and the finish stamp is untouched by any Poll path.
  • An archived bookmark still keeps its Series polled.
  • The store's status validation admits two values; the StatusFinished constant and the three-state doc comment are gone.
  • The API answers status: finished with the same plain 400 as any invalid value — no special message, no special case.
  • The web UI offers the finished bucket nowhere: no Finished tab, no finished badge, no finish button, no confirm row, no finished branch in the keyrow or the tab switch.
  • The ADR records finished-belonging-to-the-Series and why the migration is one-way.
  • Full backend test suite passes.
Part of #136. Blocked by: none — can start immediately. ## What to build One landing that moves the poll gate from the per-Reader finished bucket to a Series-owned flag, and deletes the bucket's write paths in the same moment. After it lands, the Lane polls exactly the set it polled yesterday (the migration seeds from the buckets before flipping them), the API refuses `finished` like any other invalid status, no surface can create the bucket anymore, and the decision is recorded as an ADR because the migration is one-way. ## Acceptance criteria - [ ] One migration adds the finished_at column, seeds it from the bookmarks table, then flips every finished bookmark to archived — column, seed, flip in that order in one file, with a comment saying the order is load-bearing. - [ ] A data test seeds a pre-migration schema, runs the migration, and asserts: a Series held only by finished bookmarks is finished; a Series held by a mix is not; every previously-finished bookmark reads archived. The test would catch the two data statements being reordered. - [ ] The due query polls a Series only while it is not finished; a forced Series is due regardless of finish; the finished flag joins the query's grouping; the finished-only HAVING arm is deleted from both Lane queries. - [ ] The eligible count excludes finished Series and deliberately admits no forced one — the asymmetry is commented, because the count is the Lane's pace divisor. - [ ] A Site whose whole worklist is finished records a nothing-eligible pass, not a stall. - [ ] Servicing a forced request never clears the finish: nothing writes the force stamp back to zero, and the finish stamp is untouched by any Poll path. - [ ] An archived bookmark still keeps its Series polled. - [ ] The store's status validation admits two values; the StatusFinished constant and the three-state doc comment are gone. - [ ] The API answers `status: finished` with the same plain 400 as any invalid value — no special message, no special case. - [ ] The web UI offers the finished bucket nowhere: no Finished tab, no finished badge, no finish button, no confirm row, no finished branch in the keyrow or the tab switch. - [ ] The ADR records finished-belonging-to-the-Series and why the migration is one-way. - [ ] Full backend test suite passes.
sulthan added the ready-for-agent label 2026-08-22 12:37:10 +07:00
Author
Owner

Landed on spec-136 as ffb7d69.

All 12 acceptance criteria met.

  • Migration 0016: ALTER TABLE series ADD finished_at, seed from the pre-flip finished buckets (mirrors the old due gate exactly: not finished while any bookmark is outside the bucket), then flip every finished bookmark to archived — column, seed, flip, load-bearing order commented in the file.
  • Data test TestMigration0016SeedsFinishedAtBeforeFlippingBucket: seeds a 0015-era schema (finished buckets, no column), runs 0016, asserts done/shared stamped now-ish and archived; mixed (one reader finished, one archived) not stamped; live and orphan untouched. Would fail with the two data statements reordered.
  • Due query reads series.finished_at; force_poll_at overrides the flag; finished_at joins the grouping; the finished-only HAVING arm is gone from both Lane queries, and the eligible count is now a single WHERE over the Series rows.
  • Eligible count excludes finished Series and deliberately admits no forced one — asymmetry commented at EligibleSeriesCount (issue #157).
  • A Site whose whole worklist is finished records a nothing-eligible pass row sleeping a full rest, not a stall (existing SkipNothingEligible path, test in latest/poller_test.go).
  • Servicing a forced request never clears the finish: nothing writes finished_at outside the migration; TestForcedSeriesSelfClearsOnFailedAttempt + TestDueForLatestCheckForcedOverridesFinishedFlag guard the force side.
  • Archived bookmarks still keep their Series polled (TestDueForLatestCheckSkipsFinishedKeepsArchived).
  • StatusFinished constant and the three-state doc comment are gone; validation admits reading | archived.
  • PUT /bookmarks/{key} answers finished with the plain invalid-status 400 (TestPutStatusValidation).
  • Web UI: tab link, badge, finish button, confirm row, and keyrow/tab-switch branches all deleted — app/card/chrome/list templates, buildListView, uiStatus; dead .finish CSS, the --moss token and filter.js branches removed; web tests rewritten to the two buckets.
  • Both userscripts: novel merge rank is now archived > reading; finished filters/comments removed; 66/66 logic tests pass.
  • ADR-0015 records finished-belonging-to-the-Series and why the migration is one-way (bucket destroyed by the flip; per-Reader history kept nowhere in the schema); CONTEXT.md glossary updated.
  • go vet ./... clean; go test ./... green (store/latest/root re-run with -count=1).

No file/Discord/SSH code touched.

Landed on `spec-136` as ffb7d69. All 12 acceptance criteria met. - Migration 0016: `ALTER TABLE series ADD finished_at`, seed from the pre-flip finished buckets (mirrors the old due gate exactly: not finished while any bookmark is outside the bucket), then flip every finished bookmark to archived — column, seed, flip, load-bearing order commented in the file. - Data test `TestMigration0016SeedsFinishedAtBeforeFlippingBucket`: seeds a 0015-era schema (finished buckets, no column), runs 0016, asserts done/shared stamped now-ish and archived; mixed (one reader finished, one archived) not stamped; live and orphan untouched. Would fail with the two data statements reordered. - Due query reads `series.finished_at`; `force_poll_at` overrides the flag; `finished_at` joins the grouping; the finished-only HAVING arm is gone from both Lane queries, and the eligible count is now a single WHERE over the Series rows. - Eligible count excludes finished Series and deliberately admits no forced one — asymmetry commented at `EligibleSeriesCount` (issue #157). - A Site whose whole worklist is finished records a `nothing-eligible` pass row sleeping a full rest, not a stall (existing `SkipNothingEligible` path, test in latest/poller_test.go). - Servicing a forced request never clears the finish: nothing writes `finished_at` outside the migration; `TestForcedSeriesSelfClearsOnFailedAttempt` + `TestDueForLatestCheckForcedOverridesFinishedFlag` guard the force side. - Archived bookmarks still keep their Series polled (`TestDueForLatestCheckSkipsFinishedKeepsArchived`). - `StatusFinished` constant and the three-state doc comment are gone; validation admits `reading | archived`. - `PUT /bookmarks/{key}` answers `finished` with the plain invalid-status 400 (`TestPutStatusValidation`). - Web UI: tab link, badge, finish button, confirm row, and keyrow/tab-switch branches all deleted — app/card/chrome/list templates, `buildListView`, `uiStatus`; dead `.finish` CSS, the `--moss` token and filter.js branches removed; web tests rewritten to the two buckets. - Both userscripts: novel merge rank is now `archived > reading`; finished filters/comments removed; 66/66 logic tests pass. - ADR-0015 records finished-belonging-to-the-Series and why the migration is one-way (bucket destroyed by the flip; per-Reader history kept nowhere in the schema); CONTEXT.md glossary updated. - `go vet ./...` clean; `go test ./...` green (store/latest/root re-run with -count=1). No file/Discord/SSH code touched.
Author
Owner

Review round (cr-spec + cr-standards on ffb7d69) — findings fixed and amended into 43ca043.

Fixed:

  • Restored .actions .play { color: var(--paper); } — collateral of the finish-rule sweep; the Continue-reading icon had dropped to idle grey.
  • Restored .confirm-row.calm background/span rules — the archive confirm was silently rendered with the remove's danger wash.
  • Restored light-branch --clay: #7c5533 — same sweep had removed it along with --moss, dropping the pencil hover below the 3:1 icon threshold.
  • Deleted dead #i-check sprite (only consumers were the removed finish badge/button).
  • Dropped stale userscript line-number refs from the store.go due-query comment.
  • ADR: removed the Store.SetFinishedAt reference (no such writer exists; the migration is the only writer) and the duplicated API paragraph.
  • Test: TestEligibleSeriesCount now forces a Series and asserts the count stays unchanged — the eligible count's deliberate non-admission of forced rows is no longer comment-only.

Verification: go vet ./... clean; go test -count=1 ./... green.

Review round (cr-spec + cr-standards on ffb7d69) — findings fixed and amended into 43ca043. Fixed: - Restored `.actions .play { color: var(--paper); }` — collateral of the finish-rule sweep; the Continue-reading icon had dropped to idle grey. - Restored `.confirm-row.calm` background/span rules — the archive confirm was silently rendered with the remove's danger wash. - Restored light-branch `--clay: #7c5533` — same sweep had removed it along with `--moss`, dropping the pencil hover below the 3:1 icon threshold. - Deleted dead `#i-check` sprite (only consumers were the removed finish badge/button). - Dropped stale userscript line-number refs from the store.go due-query comment. - ADR: removed the `Store.SetFinishedAt` reference (no such writer exists; the migration is the only writer) and the duplicated API paragraph. - Test: `TestEligibleSeriesCount` now forces a Series and asserts the count stays unchanged — the eligible count's deliberate non-admission of forced rows is no longer comment-only. Verification: `go vet ./...` clean; `go test -count=1 ./...` green.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sulthan/mangaBookmark#157