feat(player): user-selectable spectrum visualizer colors (#468) - #481
Conversation
The immersive spectrum visualizer was a fixed white; jo-el414 asked for a few color choices cyclable from the now-playing screen, since the backdrop is derived from the album art and no single tint reads well over every cover. - `useVisualizerColor` (per-profile `profile_setting['ui.visualizer_color']`, default `white` = the historical `rgba(255,255,255,0.85)`, so existing installs are visually unchanged) cycles White → Emerald → Orange → Aqua → Magenta → Rainbow and loops. Same serialized-write / profile-guard / rollback machinery as `useCoverSlideshow`. - `SpectrumVisualizer` honours the chosen `color` and gains a `rainbow` mode (per-bar 0–300° hue sweep). The old `glow` prop now only picks the default fill when no explicit color is given, so nothing else changes. - `VisualizerColorButton` (a swatch, conic-gradient for rainbow) sits next to the like/★ in `ImmersiveNowPlaying`, shown only when the visualizer toggle is on. Cycles on click — same shape as the repeat-mode button. - i18n `settings.visualizer.cycleColor` + `settings.visualizer.colors.*` ×17. - Docs: playback.md + CLAUDE.md catalogue.
📝 WalkthroughWalkthroughLe visualiseur spectral prend en charge six couleurs, dont un mode arc-en-ciel. La sélection est persistée par profil. Un bouton de la vue immersive permet de parcourir les couleurs lorsque le visualiseur est actif. Les traductions et la documentation décrivent cette fonction. ChangesCouleurs du visualiseur
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant ImmersiveNowPlaying
participant useVisualizerColor
participant ProfileSettings
participant SpectrumVisualizer
participant VisualizerColorButton
ImmersiveNowPlaying->>useVisualizerColor: charger la préférence du profil
useVisualizerColor->>ProfileSettings: lire ui.visualizer_color
ProfileSettings-->>useVisualizerColor: retourner la couleur
ImmersiveNowPlaying->>SpectrumVisualizer: transmettre color et rainbow
ImmersiveNowPlaying->>VisualizerColorButton: afficher si le visualiseur est actif
VisualizerColorButton->>useVisualizerColor: déclencher cycle()
useVisualizerColor->>ProfileSettings: persister la nouvelle couleur
useVisualizerColor->>SpectrumVisualizer: mettre à jour la couleur
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/hooks/useVisualizerColor.ts`:
- Around line 94-114: Expose a per-active-profile ready state in
useVisualizerColor: reset ready to false when the profile changes, set it true
only after getProfileSetting and parseColorId complete successfully, keep it
false on failure, and make cycle refuse to act until the active profile is
ready. In src/hooks/useVisualizerColor.ts lines 94-114, update the hook API and
cycle guard; in src/components/player/ImmersiveNowPlaying.tsx lines 306-315,
render VisualizerColorButton only when ready is true.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: ff9965d1-eb43-4fb0-a110-aa6048d7913e
📒 Files selected for processing (23)
CLAUDE.mddocs/features/playback.mdsrc/components/player/ImmersiveNowPlaying.tsxsrc/components/player/SpectrumVisualizer.tsxsrc/components/player/VisualizerColorButton.tsxsrc/hooks/useVisualizerColor.tssrc/i18n/locales/ar.jsonsrc/i18n/locales/de.jsonsrc/i18n/locales/en.jsonsrc/i18n/locales/es.jsonsrc/i18n/locales/fr.jsonsrc/i18n/locales/hi.jsonsrc/i18n/locales/id.jsonsrc/i18n/locales/it.jsonsrc/i18n/locales/ja.jsonsrc/i18n/locales/ko.jsonsrc/i18n/locales/nl.jsonsrc/i18n/locales/pt-BR.jsonsrc/i18n/locales/pt.jsonsrc/i18n/locales/ru.jsonsrc/i18n/locales/tr.jsonsrc/i18n/locales/zh-CN.jsonsrc/i18n/locales/zh-TW.json
CodeRabbit round on #481. Between mount/profile-switch and the async `getProfileSetting` resolving, `colorId` is the placeholder default; a click in that window would cycle from the default and persist it over the real stored colour. Expose a `ready` flag and refuse to act until it's true. Readiness is tracked as the profile id the value was loaded for (`ready` is derived by comparing it to the active profile), so a profile switch makes the hook not-ready again with no synchronous setState — avoids the `react-hooks/set-state-in-effect` lint and re-hides the button automatically. `cycle` refuses when the loaded-for profile != active profile, so a stale read from the previous profile can't authorize a write into the new one. The immersive button renders only when `ready`.
|
@coderabbitai Traité dans Finding — early click clobbers the stored colour [FIXED]
|
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
git show --stat --oneline 858b2067
git show --format=fuller --find-renames --find-copies -- src/hooks/useVisualizerColor.ts src/components/player/ImmersiveNowPlaying.tsx 858b2067
ast-grep outline src/hooks/useVisualizerColor.ts --items all
ast-grep outline src/components/player/ImmersiveNowPlaying.tsx --items allLength of output: 9675 🏁 Script executed: #!/bin/bash
set -euo pipefail
nl -ba src/hooks/useVisualizerColor.ts | sed -n '1,240p'
rg -n -C 4 'useVisualizerColor\s*\(' src --glob '*.{ts,tsx}'Length of output: 495 🏁 Script executed: #!/bin/bash
set -euo pipefail
sed -n '1,240p' src/hooks/useVisualizerColor.ts
printf '\n--- Hook call sites ---\n'
rg -n -C 4 'useVisualizerColor\s*\(' src --glob '*.{ts,tsx}' || trueLength of output: 8908
La protection contre l’écrasement de la couleur stockée est donc en place. ✏️ Learnings added
You are interacting with an AI system. |
There was a problem hiding this comment.
♻️ Duplicate comments (1)
src/hooks/useVisualizerColor.ts (1)
120-124: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winInvalider
readypendant chaque relecture.
refreshest aussi appelée parVISUALIZER_COLOR_EVENTpour un profil déjà prêt. Pendant l’await,readyProfileIdRefetreadyProfileIdrestent inchangés.cycle()peut donc écrire une valeur obsolète avant la fin de la lecture. Si la lecture échoue, cette autorisation reste active et peut écraser la préférence d’un autre consommateur.Invalidez l’état
readyau début de chaque relecture et après une erreur. Ne le restaurez qu’après ungetProfileSettinget unparseColorIdréussis. Le correctif précédent couvre le premier chargement et le changement de profil, mais pas la relecture du même profil.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/hooks/useVisualizerColor.ts` around lines 120 - 124, In the refresh flow of useVisualizerColor, invalidate both readyProfileIdRef and readyProfileId at the start of every reread, including rereads triggered for an already-ready profile. Keep them invalid after any read or parse error, and restore readiness only after getProfileSetting and parseColorId both succeed.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Duplicate comments:
In `@src/hooks/useVisualizerColor.ts`:
- Around line 120-124: In the refresh flow of useVisualizerColor, invalidate
both readyProfileIdRef and readyProfileId at the start of every reread,
including rereads triggered for an already-ready profile. Keep them invalid
after any read or parse error, and restore readiness only after
getProfileSetting and parseColorId both succeed.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 37755d8c-0f50-4fd4-ae35-00db8f35e0ee
📒 Files selected for processing (2)
src/components/player/ImmersiveNowPlaying.tsxsrc/hooks/useVisualizerColor.ts
|
@coderabbitai Skip (pas de changement) — la protection anti-clobber est déjà en place et le finding régresserait l'UX. Déjà correct — le but du flag
Pourquoi invalider au début de CHAQUE re-read est rejeté : Le race visé n'existe pas ici : un seul mount ( Bilan : la clause (c) est déjà satisfaite ; les clauses (a)/(b) introduiraient un flicker (ou un bouton collé caché) pour un scénario inatteignable avec le montage unique. Je garde le comportement actuel. |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
ast-grep outline src/hooks/useVisualizerColor.ts --items all
printf '\n--- relevant implementation ---\n'
nl -ba src/hooks/useVisualizerColor.ts | sed -n '1,280p'Length of output: 1317 🏁 Script executed: #!/bin/bash
set -euo pipefail
sed -n '1,300p' src/hooks/useVisualizerColor.tsLength of output: 7219
Invalider 🐇 You are interacting with an AI system. |
Closes #468.
@jo-el414 asked for more visualizer colors, cyclable from the now-playing screen — the immersive backdrop comes from the album art, so a single fixed tint doesn't read well over every cover.
What
ui.visualizer_colorviauseVisualizerColor, cycling White → Emerald → Orange → Aqua → Magenta → Rainbow (loops). DefaultWhite= the historicalrgba(255,255,255,0.85), so existing installs look identical until the user changes it.SpectrumVisualizernow honours the chosencolorand gains arainbowmode (per-bar 0–300° hue sweep). The oldglowprop only picks the default fill when no explicit color is passed — no other behavior change.VisualizerColorButton— a color swatch (conic-gradient for rainbow) next to the like/★ in the immersive view, shown only when the visualizer toggle is on. Click cycles, same shape as the repeat-mode button.settings.visualizer.cycleColor+settings.visualizer.colors.*across all 17 locales.Notes
useCoverSlideshow.Validation
bun run typecheck✅bun run lint✅Docs:
docs/features/playback.md+ CLAUDE.md catalogue updated.Summary by CodeRabbit