feat(rotation): per-device image-selection preferences
CI / test (push) Has been cancelled

Adds two settings exposed in the PWA frame-settings sheet:

- rotationMode (enum: random | least_recently_shown | oldest_upload |
  newest_upload). Default oldest_upload preserves the legacy
  hard-coded sort, so existing devices behave identically until the
  user changes it.
- prioritizeNeverShown (bool). When set, the candidate set is narrowed
  to never-shown images first (if any exist) before the mode runs —
  useful for "burn through new uploads before re-shuffling the catalog."

RotationService pipeline:
  1. Pull approved/ready pool.
  2. Drop the last `uniquenessWindow` served (existing).
  3. If prioritizeNeverShown AND any candidates have never been served,
     narrow to those.
  4. Apply the selection mode.

Backend: enum, entity columns + accessors, migration, serializer,
PATCH validator. Frontend: types, stores, settings sheet section
(dropdown + checkbox), test fixtures, save-flow test.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-07 16:37:14 -04:00
parent ba9625d45d
commit cf6623de67
26 changed files with 320 additions and 31 deletions
@@ -30,6 +30,8 @@ const makeDevice = (overrides: Partial<Device> = {}): Device => ({
wakeTimes: [],
timezone: 'America/Chicago',
uniquenessWindow: 30,
rotationMode: 'oldest_upload',
prioritizeNeverShown: false,
linkedAt: '2026-01-01T00:00:00Z',
lastSeenAt: null,
nextPollExpectedAt: null,