Skip to content

fix: unwatch toggle, fresh-seed regenerate, swap alternate cleanup, guide polish - #50

Merged
shottah merged 6 commits into
mainfrom
fix/platform-polish
Jul 17, 2026
Merged

fix: unwatch toggle, fresh-seed regenerate, swap alternate cleanup, guide polish#50
shottah merged 6 commits into
mainfrom
fix/platform-polish

Conversation

@shottah

@shottah shottah commented Jul 17, 2026

Copy link
Copy Markdown
Owner

Four user-approved improvement groups, no tracking issue.

  • Real unwatch: DELETE /v1/guides/{id}/items/{itemID}/watched with conditional rollback (pointer rolls back only if this mark's advance is still the latest; status watched->rotation and watched_at cleared whenever the title sits on the watched shelf). The guide watched buttons (hover cluster + menu chip) are now true aria-pressed toggles with Watched/Unwatched toasts.
  • Regenerate reshuffles: mints a fresh seed per regenerate and persists it to guides.seed in the same transaction; pinned/edited/watched/past keeps still bind.
  • Swap cleanup: swapping an alternate into the plan deletes that title's alternates for the whole date (pinned as intended semantics by test), with a self-deletion guard for direct API callers.
  • Mechanical: epLabel deduped into lib/guide, orphaned CalendarSlot.sub dropped, ProviderChip empty-name fallback, posterTint cache orchestration deduped (exports byte-stable), GenerateBar explicit empty-days guard, tz-proof epoch assertion (the long-red store test is green; full suite green in both DB modes).

Verification: per-task reviews with two fix cycles (a real stuck-status bug in the coupled unwatch conditional was caught by empirical refutation and decoupled), plus a final whole-branch review with live wire smoke on an isolated emulator user: unwatch round-trips (shelf/pointer/watched_at), three regenerates yielding distinct persisted seeds and a proven reshuffle under tie-breaks, and a date-wide alternate clear on swap.

Ledgered follow-up: PATCHing an alternate row directly can create duplicate state (no server-side is_plan gate; unreachable via UI); ticket-worthy hardening.

Both dev servers currently run this branch for acceptance.

https://claude.ai/code/session_01VJLReS68zKnpPZgGrbfdLa

shottah added 6 commits July 17, 2026 08:47
Adds DELETE /v1/guides/{id}/items/{itemID}/watched, backed by
store.UnmarkItemWatched which mirrors MarkItemWatched in reverse: the
user_titles pointer rolls back only if it still sits exactly where this
mark's advance left it (a later mark that pushed it further is left
untouched), and status/watched_at only revert if this mark auto-completed
the title. Idempotent on an already-unwatched item.

Claude-Session: https://claude.ai/code/session_01VJLReS68zKnpPZgGrbfdLa
UnmarkItemWatched's series path wrapped both the pointer rollback and
the user_titles status/watched_at rollback in one pointer-equality
guard. Two scenarios broke it: re-ingest changing title_seasons counts
between mark and unmark makes the recomputed nextPointer no longer
match the stored pointer, and a finale completed via a different guide
item also fails the guard for an earlier item's unmark. Either way the
guard failing left status stuck on 'watched' forever. Split into two
independent statements: pointer rollback keeps the equality guard,
status/watched_at rollback fires solely off status='watched', matching
the movie path's existing independence.

Claude-Session: https://claude.ai/code/session_01VJLReS68zKnpPZgGrbfdLa
UpdateGuideItem's swap-path DELETE clears all is_plan=false alternates
for the swapped-in title on the same date, not just the swapped slot.
This is the intended semantic (a title in tonight's plan stops being
offered as tonight's alternate anywhere), so document it with a
comment and a test that seeds the same title as an alternate at two
different start_min slots on one date to prove the scope is genuinely
date-wide.

Claude-Session: https://claude.ai/code/session_01VJLReS68zKnpPZgGrbfdLa
- useGuideItemMutations: watchedM issues DELETE/POST based on
  item.watched, with matching Watched/Unwatched toasts; ItemMenu's
  Watched chip now reflects pressed state and toggles its label.
- GenerateBar: track the days input as number | "" so an empty field
  stays visually blank and is explicitly rejected, instead of relying
  on 0 coincidentally failing the MIN_DAYS bound.
- Export epLabel from lib/guide.ts; delete the duplicate
  app/guide/epLabel.ts and retarget its tests into lib/guide.test.ts.
- Drop the orphaned CalendarSlot.sub field and its guide.test.ts
  assertions (CalendarColumn.sub and BoardCell.sub are unrelated and
  unchanged).
- ProviderChip: standalone variant with an empty name falls back to
  the text path instead of emitting role="img" aria-label="".
- posterTint: dedupe the cache/in-flight/fallback orchestration shared
  by posterHue and logoPlate into one parameterized resolveCached/
  peekCached pair; all exported signatures and posterTint.test.ts's
  36 tests are unmodified (60 across the whole web suite).

Claude-Session: https://claude.ai/code/session_01VJLReS68zKnpPZgGrbfdLa
@shottah
shottah merged commit dd6370b into main Jul 17, 2026
2 checks passed
@shottah
shottah deleted the fix/platform-polish branch July 17, 2026 18:18
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