Skip to content

feat(player): user-selectable spectrum visualizer colors (#468) - #481

Merged
InstaZDLL merged 2 commits into
mainfrom
feat/468-visualizer-colors
Aug 2, 2026
Merged

feat(player): user-selectable spectrum visualizer colors (#468)#481
InstaZDLL merged 2 commits into
mainfrom
feat/468-visualizer-colors

Conversation

@InstaZDLL

@InstaZDLL InstaZDLL commented Aug 2, 2026

Copy link
Copy Markdown
Owner

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

  • New per-profile preference ui.visualizer_color via useVisualizerColor, cycling White → Emerald → Orange → Aqua → Magenta → Rainbow (loops). Default White = the historical rgba(255,255,255,0.85), so existing installs look identical until the user changes it.
  • SpectrumVisualizer now honours the chosen color and gains a rainbow mode (per-bar 0–300° hue sweep). The old glow prop 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.
  • i18n settings.visualizer.cycleColor + settings.visualizer.colors.* across all 17 locales.

Notes

  • Frontend-only; the visualizer is immersive-only, so that's where the control lives.
  • Write machinery (serialized writes, profile-switch guards, rollback) mirrors useCoverSlideshow.

Validation

  • bun run typecheck
  • bun run lint
  • No JS unit tests in this project (per convention).

Docs: docs/features/playback.md + CLAUDE.md catalogue updated.

Summary by CodeRabbit

  • Nouvelles fonctionnalités
    • Personnalisez la couleur du visualiseur audio parmi six options : blanc, émeraude, orange, aqua, magenta et arc-en-ciel.
    • Changez rapidement la couleur depuis le lecteur lorsque le visualiseur est actif.
    • Votre préférence est conservée par profil et restaurée lors de vos prochaines sessions.
  • Documentation
    • La personnalisation des couleurs est désormais documentée.
  • Accessibilité et traductions
    • Les nouveaux libellés sont disponibles dans les langues prises en charge.

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.
@InstaZDLL InstaZDLL added scope: frontend React/Vite frontend (src/) scope: i18n Translations (src/i18n/) scope: docs Docs, README, assets type: feat New feature size: l 200-500 lines labels Aug 2, 2026
@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Le 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.

Changes

Couleurs du visualiseur

Layer / File(s) Summary
Préférence de couleur persistée
src/hooks/useVisualizerColor.ts
useVisualizerColor définit les six valeurs, charge la préférence par profil, synchronise les changements externes et restaure la dernière valeur confirmée après une erreur d’écriture.
Rendu et contrôle de couleur
src/components/player/SpectrumVisualizer.tsx, src/components/player/VisualizerColorButton.tsx
SpectrumVisualizer applique une couleur fixe ou une teinte HSL par barre. VisualizerColorButton affiche l’action traduite et déclenche le cycle des couleurs.
Intégration immersive et contenu associé
src/components/player/ImmersiveNowPlaying.tsx, src/i18n/locales/*.json, docs/features/playback.md, CLAUDE.md
ImmersiveNowPlaying charge l’activation du visualiseur, transmet la préférence au rendu et affiche le bouton conditionnellement. Les traductions et la documentation ajoutent les six options et la clé de persistance.

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
Loading

Possibly related PRs

  • InstaZDLL/WaveFlow#327 : utilise aussi une préférence persistée par profil et un hook synchronisé par événements.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed Le titre décrit clairement l’ajout de couleurs sélectionnables au visualiseur spectral et respecte le format Conventional Commits.
Description check ✅ Passed La description couvre le résumé, les changements, la validation, la documentation et l’issue liée, malgré l’absence du checklist du modèle.
Linked Issues check ✅ Passed Les objectifs de l’issue #468 sont couverts : couleurs cyclables, bouton adjacent au bouton J’aime, mode arc-en-ciel et retour à la couleur par défaut.
Out of Scope Changes check ✅ Passed Les changements restent dans le périmètre : visualiseur immersif, préférence par profil, traductions et documentation associée.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/468-visualizer-colors

Comment @coderabbitai help to get the list of available commands.

@InstaZDLL InstaZDLL self-assigned this Aug 2, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 70cd206 and 904b652.

📒 Files selected for processing (23)
  • CLAUDE.md
  • docs/features/playback.md
  • src/components/player/ImmersiveNowPlaying.tsx
  • src/components/player/SpectrumVisualizer.tsx
  • src/components/player/VisualizerColorButton.tsx
  • src/hooks/useVisualizerColor.ts
  • src/i18n/locales/ar.json
  • src/i18n/locales/de.json
  • src/i18n/locales/en.json
  • src/i18n/locales/es.json
  • src/i18n/locales/fr.json
  • src/i18n/locales/hi.json
  • src/i18n/locales/id.json
  • src/i18n/locales/it.json
  • src/i18n/locales/ja.json
  • src/i18n/locales/ko.json
  • src/i18n/locales/nl.json
  • src/i18n/locales/pt-BR.json
  • src/i18n/locales/pt.json
  • src/i18n/locales/ru.json
  • src/i18n/locales/tr.json
  • src/i18n/locales/zh-CN.json
  • src/i18n/locales/zh-TW.json

Comment thread src/hooks/useVisualizerColor.ts
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`.
@InstaZDLL

Copy link
Copy Markdown
Owner Author

@coderabbitai Traité dans 858b2067.

Finding — early click clobbers the stored colour [FIXED]
Réel : entre le montage (ou un switch de profil) et la résolution de getProfileSetting, colorId est le défaut placeholder ; un clic dans cette fenêtre faisait cycler depuis le défaut et écrasait la couleur stockée.

useVisualizerColor expose maintenant ready, et cycle no-op tant que ce n'est pas prêt. Une nuance vs la suggestion : au lieu d'un booléen remis à false synchronement dans l'effet (ce que le lint react-hooks/set-state-in-effect interdit ici, et qui ferait clignoter le bouton à chaque re-lecture déclenchée par l'event après une écriture), la readiness est trackée comme l'id du profil pour lequel la valeur a été chargée :

  • ready est dérivé (readyProfileId === activeProfile.id) → un switch de profil rend le hook not-ready automatiquement, sans setState synchrone, et le bouton se re-cache tout seul.
  • cycle refuse quand readyProfileIdRef !== activeProfileIdRef → une lecture périmée du profil précédent ne peut pas autoriser une écriture dans le nouveau (fail-closed).
  • Le bouton immersif ne rend que visualizerOn && ready.

bun run typecheck ✅ · bun run lint ✅.

@InstaZDLL InstaZDLL added size: xl > 500 lines and removed size: l 200-500 lines labels Aug 2, 2026
@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown

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 all

Length 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}' || true

Length of output: 8908


@InstaZDLL Le correctif dans 858b2067 traite bien le finding.

ready devient faux lors d’un changement de profil par dérivation. cycle refuse toute écriture tant que la valeur chargée ne correspond pas au profil actif. Le bouton reste masqué jusqu’au chargement de la préférence du profil actif.

La protection contre l’écrasement de la couleur stockée est donc en place.


✏️ Learnings added
Learnt from: InstaZDLL
URL: https://github.com/InstaZDLL/WaveFlow/pull/481

Timestamp: 2026-08-02T17:17:36.555Z
Learning: Dans `src/hooks/useVisualizerColor.ts`, la disponibilité de la préférence `ui.visualizer_color` doit être suivie par l’identifiant du profil chargé, et non par un booléen réinitialisé dans un effet. `ready` est dérivé de `readyProfileId === activeProfile.id`. `cycle` doit refuser les écritures si `readyProfileIdRef.current` ne correspond pas à `activeProfileIdRef.current`, afin qu’une valeur placeholder ou une lecture obsolète ne remplace pas la préférence d’un profil.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

You are interacting with an AI system.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
src/hooks/useVisualizerColor.ts (1)

120-124: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Invalider ready pendant chaque relecture.

refresh est aussi appelée par VISUALIZER_COLOR_EVENT pour un profil déjà prêt. Pendant l’await, readyProfileIdRef et readyProfileId restent 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 ready au début de chaque relecture et après une erreur. Ne le restaurez qu’après un getProfileSetting et un parseColorId ré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

📥 Commits

Reviewing files that changed from the base of the PR and between 904b652 and 858b206.

📒 Files selected for processing (2)
  • src/components/player/ImmersiveNowPlaying.tsx
  • src/hooks/useVisualizerColor.ts

@InstaZDLL
InstaZDLL merged commit b5ef338 into main Aug 2, 2026
13 checks passed
@InstaZDLL
InstaZDLL deleted the feat/468-visualizer-colors branch August 2, 2026 17:28
@InstaZDLL

Copy link
Copy Markdown
Owner Author

@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 ready est d'empêcher un clic précoce d'écraser la couleur stockée :

  • ready est dérivé (readyProfileId === activeProfile.id), donc un switch de profil le rend false instantanément.
  • Une lecture initiale échouée laisse ready à false (jamais mis à true hors du chemin succès après parseColorId).
  • cycle refuse tant que readyProfileIdRef !== activeProfileIdRef → fail-closed sur le profil actif.

Pourquoi invalider au début de CHAQUE re-read est rejeté : VISUALIZER_COLOR_EVENT est redispatché après chaque écriture réussie (setColorId). Invalider readyProfileId en tête de refresh ferait donc osciller ready false→true à chaque changement de couleur → flicker du bouton (hide puis re-show) sur le chemin nominal. Une invalidation seulement dans le catch a un défaut symétrique : après un échec transitoire de re-read post-write, le bouton se cache → plus d'event possible → reste caché jusqu'au remount.

Le race visé n'existe pas ici : un seul mount (ImmersiveNowPlaying), l'event n'est émis que par le post-write de ce hook, et colorId est déjà posé optimistiquement avant le dispatch — un re-read de confirmation qui échoue ne rend donc rien incohérent (et cycler depuis « ce que l'utilisateur voit » reste correct). parseColorId ne peut pas échouer (défaut sur entrée inconnue/null), donc « restaurer seulement si parse réussit » est déjà vrai.

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.

@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown

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.ts

Length of output: 7219


@InstaZDLL Vous avez raison. Je retire le finding.

ready reste false après un échec de lecture initiale. cycle refuse aussi toute écriture si la valeur chargée ne correspond pas au profil actif. Après une écriture, colorId est déjà mis à jour avant le VISUALIZER_COLOR_EVENT.

Invalider readyProfileId lors de chaque refresh masquerait le bouton après chaque changement de couleur. Cela introduirait le flicker décrit sans corriger une course applicable ici.

🐇

You are interacting with an AI system.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

scope: docs Docs, README, assets scope: frontend React/Vite frontend (src/) scope: i18n Translations (src/i18n/) size: xl > 500 lines type: feat New feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: more colors options for Audio visualizer .

1 participant