From ac11c3d60641ead881b15aa4c41c5b279f1fbf47 Mon Sep 17 00:00:00 2001
From: Claude
Date: Tue, 9 Jun 2026 18:51:56 +0000
Subject: [PATCH] feat: add roulette spinner auto-select to the results page
Adds a 'Spin for me' option over the 3-5 result cards: the highlight
travels across the picks with a decelerating 2-4s step sequence
(spin-plan.ts), lands on a random book, and offers accept/respin.
Accepting reuses the existing selection tracking and confirmation
screen. Falls back to an instant reveal under prefers-reduced-motion,
and puts the previously unused book-spin pulse to work on the
highlighted card. Completes TODO task 12.
https://claude.ai/code/session_01T3Bf7bT36EKqybyL4X2uKQ
---
TODO.md | 44 +++++++------
.../app/features/result/result.component.html | 66 ++++++++++++++++++-
.../app/features/result/result.component.ts | 55 +++++++++++++++-
.../src/app/features/result/spin-plan.spec.ts | 56 ++++++++++++++++
frontend/src/app/features/result/spin-plan.ts | 35 ++++++++++
goodreads_tbr_recommendation_spec.md | 2 +-
6 files changed, 233 insertions(+), 25 deletions(-)
create mode 100644 frontend/src/app/features/result/spin-plan.spec.ts
create mode 100644 frontend/src/app/features/result/spin-plan.ts
diff --git a/TODO.md b/TODO.md
index 67260a0..9211a50 100644
--- a/TODO.md
+++ b/TODO.md
@@ -10,27 +10,6 @@
> Known future work, not yet planned.
-### 12. Roulette / spinner selection
-
-Depends on: 11
-
-> Deferred. Manual selection (tap a book → detail modal → confirm) is implemented and
-> covers the "select a book" outcome. The spec's roulette spinner is an optional,
-> additive way to pick between the 3–5 results and has not been built. A `book-spin`
-> CSS keyframe exists in `styles.css` but is currently unused.
-
-#### Tasks
-
-- Implement roulette spinner component (2–4s decelerate easing) over the 3–5 results
-- Respect `prefers-reduced-motion` (instant reveal fallback)
-- Allow respin and accept
-- Wire to the existing result reveal
-
-#### Done when
-
-- Spinner resolves to a single book with correct motion behaviour
-- Respin and accept work on mobile
-
### 16. Book status: mark in progress / completed
Depends on: 11
@@ -143,6 +122,29 @@ Depends on: 1
> Merged and verified.
+### 12. Roulette / spinner selection
+
+Depends on: 11
+
+> Implemented as an optional "Spin for me" control on the results page rather than a
+> separate spinner screen. The highlight travels across the existing result cards with
+> decelerating steps (~2–3s, within the brand's 2–4s spinner window, via
+> `spin-plan.ts`), lands on a random pick, and reuses the unused `book-spin` pulse.
+> Manual selection remains the primary path.
+
+#### Tasks
+
+- [x] Roulette spin over the 3–5 results (2–4s decelerating step sequence)
+- [x] Respect `prefers-reduced-motion` (instant reveal fallback)
+- [x] Respin and accept from the spin result panel
+- [x] Wired to the existing result reveal and accept/confirmation flow
+- [x] Unit tests for the spin schedule (`spin-plan.spec.ts`)
+
+#### Done when
+
+- [x] Spinner resolves to a single book with correct motion behaviour
+- [x] Respin and accept work on mobile
+
### 19. Quiz flow improvements
Depends on: 11
diff --git a/frontend/src/app/features/result/result.component.html b/frontend/src/app/features/result/result.component.html
index 762efdf..18d7faf 100644
--- a/frontend/src/app/features/result/result.component.html
+++ b/frontend/src/app/features/result/result.component.html
@@ -72,6 +72,63 @@
+
+ @if (picks().length > 1) {
+
+ @if (!spunBook()) {
+
+ }
+
+ @if (spunBook() && !spinning()) {
+
+
+
+ The spinner chose
+
+
+ {{ spunBook()!.title }}
+
+ @if (spunBook()!.author) {
+
+ {{ spunBook()!.author }}
+
+ }
+
+
+
+
+
+
+ }
+
+
+ }
+
@for (book of picks(); track book.id; let i = $index) {
@@ -79,7 +136,14 @@