Skip to content

feat(sync): position history + one-click restore#137

Closed
bndct-devops wants to merge 1 commit into
feat/command-palettefrom
feat/position-history
Closed

feat(sync): position history + one-click restore#137
bndct-devops wants to merge 1 commit into
feat/command-palettefrom
feat/position-history

Conversation

@bndct-devops

Copy link
Copy Markdown
Owner

Overnight run PR 6/12 — stacked on #136; merge in order. The one DB change in the stack (additive table).

What

The safety net under position sync: TomeSyncPosition is a single overwritten row, so one bad write — a device falsely jumping a book to 100%, a mis-tap that propagated — was unrecoverable. Now:

  • position_history table (additive; created on startup by create_all, same as every other table — no Alembic migration needed, nothing destructive). Written inside upsert_position, the one choke point every position writer already goes through (device PUT, web autosave, admin flows), only when the position moves ≥0.2% or the locator changes — the ten-page idle heartbeat can't spam it. Pruned to the newest 40 per user+book.
  • GET /books/{id}/position-history — the log plus the live position; POST /books/{id}/position-history/{hid}/restore — sets the live position back to that entry.
  • Restore deliberately overrides sticky completion: restoring below 100% un-finishes the book (status → reading, finished_at cleared). Without this the position would come back but the book would stay "read" — the exact scenario the feature exists for. Restores are themselves logged, so a restore can be undone.
  • UI: history-clock button on the book page's Reading Stats header → modal with the log ("45% · Kindle · 2h ago"), per-entry Restore, current entry marked. Stats + status pill refresh after restore.

Verification

  • 5 backend tests: heartbeat/sub-threshold noise not recorded, movement recorded + prune cap honored, endpoint ordering, false-completion recovery round-trip (jump to 100% + finished → restore 45% → status reading, finished_at cleared, device "restore"), foreign-user entry 404s.
  • Browser round-trip on dev: seeded 31→45→52→100% writes through the real service path, restored the 45% entry from the modal — new "restore · current" row appears, status pill flips to Reading (screenshots in run log).
  • Full position-related suite + build/typecheck clean.

New position_history table (additive, created by the startup create_all
like every other table): upsert_position — the single choke point all
position writers go through — appends an entry whenever the position moves
meaningfully (>=0.2% or locator change; the idle heartbeat can't spam it),
pruned to the newest 40 per user+book.

GET /books/{id}/position-history lists the log + live position;
POST .../{hid}/restore sets the live position back. Restore is an explicit
override of the sticky-completion rule: recovering from a false 100% also
un-finishes the book (status/finished_at), or the position would come back
while the book stayed "read". The restore itself is logged, so it can be
undone the same way. Devices converge on their next pull.

UI: history button on the book page's Reading Stats header opens the log
with per-entry Restore; refreshes stats + status pill after.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@bndct-devops
bndct-devops deleted the branch feat/command-palette July 19, 2026 10:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant