Files
yellowjacket/.planning/plans/completed/006-play-history.md
T
2026-05-01 11:52:50 -04:00

1.8 KiB

006 — Play History & Play Count

Tracks every natural track completion. Powers smart-playlist rules like "most played", "never played", "not played in 30 days", and exposes play count as an optional track-list column.

Shipped: 2026-03-22 · Slices: S01-S03 (M003 in GSD)

What landed

  • S01 — Schema, migration, recording. Migration 10 adds play_history table and denormalized play_count + last_played columns on audio_files; track_metadata VIEW recreated to include them. recordPlay() pipeline: insert history row → increment count → update timestamp → emit event. OnPlaybackFinished hook unlocks the player mutex before the DB write (avoids SQLite deadlock).
  • S02 — Smart playlist integration. play_count (numeric) and days_since_played (computed) added to the rule-engine field whitelist. Explicit NULL handling for never-played tracks in days_since_played conditions.
  • S03 — UI. Optional play-count column in track lists; PlayCount / LastPlayed added to TypeScript models; editor dropdown entries for the new fields.

Key decisions retained

  • Natural finish only — no percentage threshold, no skip counting. (If you scrub past the end, that doesn't count.)
  • Denormalized play_count/last_played on audio_files — the alternative was per-query aggregation against play_history; the cost wasn't worth it.
  • days_since_played is a computed field, not raw timestamp comparison in the rule. Lets the rule engine speak the user's vocabulary.
  • "Recently played" is a smart playlist, not a dedicated view. Reuses the existing rule engine instead of adding a parallel query path.
  • Mutex unlock before recordPlay() is mandatory — locking around the DB write under MaxOpenConns(1) deadlocks.