Per-book reading stats on the book page #25

Merged
arne merged 10 commits from stats-dashboard into main 2026-04-24 09:40:14 +02:00
Owner

Surface per-book reading stats that Nickel already pushes via PUT /v1/library/{id}/state, rendered as additional rows in the book page's .book-marginalia. Also captures three currently-dropped StatusInfo fields and starts an append-only reading-state event log for future historical views.

Summary

  • Data model: migration 007 adds times_started_reading, last_time_started_reading, last_time_finished columns to reading_state; creates reading_state_events table with one row per meaningful PUT.
  • Capture: PUT /v1/library/{id}/state now persists the three StatusInfo fields; GET echoes them back.
  • UI: /book/{uuid} shows status, time spent reading, time to finish, times opened, started reading, finished, and last read — each only when the underlying field has a value. Books without a reading_state row are unchanged.
  • Event log: populated but not queried; enables "minutes per day" / "books finished over time" style graphs without a future schema change.

Scope boundaries

  • No aggregate views (total time, streaks, charts) — deferred.
  • No invented metrics beyond what the Kobo sync API exposes.
  • No multi-device aggregation — single device assumed; GetLatestReadingState picks the most recent row across devices.
  • /v1/analytics/event bodies still accept-and-drop — reading-session telemetry channel left for a potential future investigation.

Spec + plan

  • Spec: docs/superpowers/specs/2026-04-20-stats-dashboard-design.md
  • Plan: docs/superpowers/plans/2026-04-20-stats-dashboard.md

Test plan

  • go test ./... green.
  • Visit /book/{uuid} for a book with a reading_state row — new rows render (status, time spent, time to finish, times opened, last read).
  • Visit /book/{uuid} for a book without one — marginalia unchanged from before this PR.
  • Deploy the binary to abase and trigger a sync from the Kobo; confirm reading_state now has times_started_reading populated and reading_state_events starts accumulating rows.
Surface per-book reading stats that Nickel already pushes via `PUT /v1/library/{id}/state`, rendered as additional rows in the book page's `.book-marginalia`. Also captures three currently-dropped `StatusInfo` fields and starts an append-only reading-state event log for future historical views. ## Summary - **Data model:** migration 007 adds `times_started_reading`, `last_time_started_reading`, `last_time_finished` columns to `reading_state`; creates `reading_state_events` table with one row per meaningful PUT. - **Capture:** `PUT /v1/library/{id}/state` now persists the three `StatusInfo` fields; `GET` echoes them back. - **UI:** `/book/{uuid}` shows status, time spent reading, time to finish, times opened, started reading, finished, and last read — each only when the underlying field has a value. Books without a `reading_state` row are unchanged. - **Event log:** populated but not queried; enables "minutes per day" / "books finished over time" style graphs without a future schema change. ## Scope boundaries - No aggregate views (total time, streaks, charts) — deferred. - No invented metrics beyond what the Kobo sync API exposes. - No multi-device aggregation — single device assumed; `GetLatestReadingState` picks the most recent row across devices. - `/v1/analytics/event` bodies still accept-and-drop — reading-session telemetry channel left for a potential future investigation. ## Spec + plan - Spec: `docs/superpowers/specs/2026-04-20-stats-dashboard-design.md` - Plan: `docs/superpowers/plans/2026-04-20-stats-dashboard.md` ## Test plan - [ ] `go test ./...` green. - [ ] Visit `/book/{uuid}` for a book with a `reading_state` row — new rows render (status, time spent, time to finish, times opened, last read). - [ ] Visit `/book/{uuid}` for a book without one — marginalia unchanged from before this PR. - [ ] Deploy the binary to abase and trigger a sync from the Kobo; confirm `reading_state` now has `times_started_reading` populated and `reading_state_events` starts accumulating rows.
arne added 10 commits 2026-04-20 23:25:02 +02:00
First stats sub-project: mirror Kobo's per-book info panel on the
books web UI using data the device already pushes via PUT /state.
Captures three currently-dropped StatusInfo fields, adds an
append-only event log for future historical views, renders a stats
block in the book page marginalia.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Eight bite-sized tasks covering: migration, struct extensions,
upsert merge, event-log insert, GetLatestReadingState, wire
capture/emit, view-model + helpers, handler/template wiring,
final verification.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Returns the most-recently-updated reading_state row for a book across
all devices. Uses ORDER BY updated_at DESC, rowid DESC so insertion
order is a stable tiebreaker within the same second.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Wire GetLatestReadingState into BookGet and render the new reading-state
rows (status, time spent, time to finish, times opened, started reading,
finished, last read) in the .book-marginalia dl; absent state renders nothing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
arne merged commit da77759065 into main 2026-04-24 09:40:14 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
arne/books!25
No description provided.