feat(artist): spotify-style hero backdrop on the artist page (#482) - #483
Conversation
The artist page shipped a flat surface with a circular avatar; the data pipeline only ever carried the square artist photo. This adds a full-bleed hero behind the header with two tiers. Backend - theaudiodb: parse strArtistFanart(+2/3/4) / strArtistWideThumb / strArtistBanner. artist_bio becomes artist_info and returns bio AND fanart_url from one lookup; it returns Some on any name match even with neither, so a "looked, nothing there" outcome is cacheable. - new app migration: metadata_artist gains background_url / background_hash / background_fetched_at. The last one is the "we already looked" marker -- without it a NULL hash cannot be told apart from "never queried", and every fanart-less artist would re-hit a rate-limited API on each page visit. Stamped only when the API was reached, so a transport error retries instead of caching for 30 days. - enrich_artist_deezer calls TheAudioDB independently of metadata.bio_source: Last.fm has no equivalent image, so gating on it would leave every Last.fm user with no hero at all. One request serves both consumers. The fanart is cached full-res (no _1x/_2x tier). - get_artist_detail returns it too, so the hero paints on first frame. Frontend - ArtistHeroBackdrop: real fanart nearly crisp, square photo heavily blurred (SkinAmbientBackdrop's colour-field treatment), nothing at all otherwise. Dark scrim + white header copy in EVERY theme, and the bottom edge fades through a CSS mask rather than a hard-coded colour stop so it dissolves into any theme x skin. - ArtistDetailView breaks out of main's p-8 via -mx-8 -mt-8. The enrichment response only ever sets the fanart, so an empty refresh (offline, API down) never blanks a hero the cache produced. - per-profile toggle ui.artist_hero, default ON -- a baseline visual, not extra motion. prefers-reduced-motion skips the cross-fade only. - i18n propagated to all 17 locales. Claude-Session: https://claude.ai/code/session_01N9WXrurarkoiT2jgimvyj1
|
No actionable comments were generated in the recent review. π βΉοΈ Recent review infoβοΈ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: π Files selected for processing (1)
π WalkthroughWalkthroughLβintΓ©gration rΓ©cupΓ¨re le fanart TheAudioDB, le tΓ©lΓ©charge en pleine rΓ©solution, le met en cache et lβexpose Γ la vue artiste. La vue affiche un hero configurable par profil, avec repli sur la photo carrΓ©e et prise en charge de la rΓ©duction des mouvements. ChangesPipeline TheAudioDB et cache
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant VueArtiste
participant EnrichissementDeezer
participant TheAudioDB
participant CacheImages
VueArtiste->>EnrichissementDeezer: demander lβenrichissement artiste
EnrichissementDeezer->>TheAudioDB: appeler artist_info
TheAudioDB-->>EnrichissementDeezer: retourner biographie et fanart_url
EnrichissementDeezer->>CacheImages: tΓ©lΓ©charger et mettre en cache le fanart
CacheImages-->>EnrichissementDeezer: retourner background_path
EnrichissementDeezer-->>VueArtiste: retourner background_url et background_path
Possibly related PRs
π₯ Pre-merge checks | β 5β Passed checks (5 passed)
β¨ Finishing Touchesπ Generate docstrings
π§ͺ Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
π€ 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 `@docs/features/ui.md`:
- Around line 97-101: Correct the fallback count in the βTwo tiers, in
precedence orderβ heading of the artist backdrop documentation to match the
three listed outcomes, including the βNothingβ state; use βThree tiersβ or
explicitly distinguish the no-image state from the two image tiers.
In `@src-tauri/crates/app/src/commands/deezer.rs`:
- Around line 567-578: Update metadata_artwork::download_and_cache, after
checking all its Rust call sites, to support skipping thumbnail generation and
use that no-thumbnail path for the full-resolution background download in the
Deezer enrichment flow. Preserve thumbnail generation for existing callers that
require _1x/_2x assets, and continue resolving background_path from the cached
full-resolution file.
In `@src/components/common/ArtistHeroBackdrop.tsx`:
- Around line 59-70: SΓ©curisez la valeur de src avant son utilisation dans la
propriΓ©tΓ© backgroundImage du style inline de ArtistHeroBackdrop, en Γ©chappant
les guillemets, parenthΓ¨ses et caractΓ¨res dβΓ©chappement afin quβune URL de
mΓ©tadonnΓ©e ne puisse pas rompre url() ni injecter du CSS. Utilisez
systΓ©matiquement cette valeur encodΓ©e lors de lβinterpolation.
In `@src/components/views/ArtistDetailView.tsx`:
- Around line 97-101: Expose a loading/resolution state from useArtistHero and
use it in ArtistDetailView so the hero remains hidden until the profile
preference has been loaded. Ensure heroSrc is not rendered while the preference
is unresolved, preventing the initial default true value from causing a flash;
preserve the existing disabled behavior once ui.artist_hero resolves to false.
πͺ 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: 5ac2330b-a67e-45b8-af09-3f4ddf0fd851
π Files selected for processing (31)
CLAUDE.mddocs/features/library.mddocs/features/ui.mdsrc-tauri/crates/app/src/commands/browse.rssrc-tauri/crates/app/src/commands/deezer.rssrc-tauri/crates/core/src/metadata/theaudiodb.rssrc-tauri/migrations/app/20260802120000_metadata_artist_background.sqlsrc/app.csssrc/components/common/ArtistHeroBackdrop.tsxsrc/components/views/ArtistDetailView.tsxsrc/components/views/SettingsView.tsxsrc/components/views/settings/ArtistHeroCard.tsxsrc/hooks/useArtistHero.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.jsonsrc/lib/tauri/detail.ts
- docs: the "two tiers" list has three entries (the no-image case is
not an image tier) -- say so.
- metadata_artwork: download_and_cache always queued the _1x/_2x job,
so the full-res-only fanart generated tiers nothing reads. Split out
download_and_cache_full_res; the three existing callers (album cover,
artist picture, similar-artist picture) keep the thumbnails they need.
- ArtistHeroBackdrop: escape the url("...") interpolation. Unlike the
local paths other backdrops paint, a hero source can be a remote URL
straight out of TheAudioDB -- third-party data that must not be able
to close the string and inject CSS.
- useArtistHero: expose `resolved` and gate the hero on it. The
preference defaults to ON and is read asynchronously, so the previous
code flashed a hero at users who had turned it off. Resolved is set
even when the read fails, otherwise the hero would never appear.
Claude-Session: https://claude.ai/code/session_01N9WXrurarkoiT2jgimvyj1
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and canβt be posted inline due to platform limitations.
β οΈ Outside diff range comments (1)
src/hooks/useArtistHero.ts (1)
42-70: π― Functional Correctness | π‘ Minor | β‘ Quick winLiez
resolvedau profil actif.Γ la Line 55, un changement de profil conserve
resolved === truedepuis le profil prΓ©cΓ©dent.ArtistDetailViewpeut donc afficher le hero avec lβancienne valeur avant la lecture deui.artist_herodu nouveau profil.Stockez lβidentifiant du profil dont la lecture est terminΓ©e. DΓ©rivez
resolveden le comparant ΓactiveProfile?.id. Le rendu masquera alors le hero immΓ©diatement pendant chaque changement de 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/useArtistHero.ts` around lines 42 - 70, Update the resolved state in the useArtistHero hook to track the profile ID whose setting read completed, rather than retaining a profile-independent boolean. Record the active profile ID after the read succeeds or fails, and derive resolved by comparing that recorded ID with activeProfile?.id so profile changes immediately remain unresolved until the new read finishes.
π€ 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-tauri/crates/core/src/artwork/metadata.rs`:
- Around line 83-91: Ajoutez une abstraction dβΓ©tat hors ligne indΓ©pendante de
crates/app et utilisez-la dans download_and_cache_inner. Faites retourner None
immΓ©diatement lorsque le mode hors ligne est actif, avant de crΓ©er le client
reqwest ou dβeffectuer toute requΓͺte HTTP; conservez ce contrΓ΄le commun aux
chemins download_and_cache_full_res et download_and_cache.
---
Outside diff comments:
In `@src/hooks/useArtistHero.ts`:
- Around line 42-70: Update the resolved state in the useArtistHero hook to
track the profile ID whose setting read completed, rather than retaining a
profile-independent boolean. Record the active profile ID after the read
succeeds or fails, and derive resolved by comparing that recorded ID with
activeProfile?.id so profile changes immediately remain unresolved until the new
read finishes.
πͺ 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: fc4f1ec9-997c-477e-90d3-ea1b96ef2553
π Files selected for processing (6)
docs/features/ui.mdsrc-tauri/crates/app/src/commands/deezer.rssrc-tauri/crates/core/src/artwork/metadata.rssrc/components/common/ArtistHeroBackdrop.tsxsrc/components/views/ArtistDetailView.tsxsrc/hooks/useArtistHero.ts
A bare "did we read once" boolean stayed true across a profile switch, so the previous profile's value painted until the new read landed. Stamp the profile the read completed for and derive `resolved` by comparing it with the active id β a switch re-renders with a different id, the stamp stops matching and the hero goes back to hidden for free, with no set-state-in-effect dance. Claude-Session: https://claude.ai/code/session_01N9WXrurarkoiT2jgimvyj1
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/useArtistHero.ts`:
- Around line 23-26: In the [activeProfileId] effect, reset enabledRef.current,
confirmedEnabledRef.current, setEnabledState, and readProfileId to
undefined/default values before calling refresh; keep this reset outside refresh
because refresh also handles ARTIST_HERO_EVENT. Update the resolved
documentation and failure handling so a failed profile read uses DEFAULT_ENABLED
rather than retaining the previous profileβs preference, including write
rollback behavior.
πͺ 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: d1264915-7c2c-46fd-90d2-3119acfb5e40
π Files selected for processing (1)
src/hooks/useArtistHero.ts
`resolved` going false on a switch hid the hero during the read, but a read that *failed* still stamped the new profile and then exposed the outgoing profile's value -- which the doc comment claimed could never happen. Clear enabled + both refs to the default before the read, in the effect rather than in `refresh`: that function also serves ARTIST_HERO_EVENT, where resetting would flash the default on every same-profile broadcast, and a failed same-profile refresh is better off keeping what it had. Claude-Session: https://claude.ai/code/session_01N9WXrurarkoiT2jgimvyj1
There was a problem hiding this comment.
Caution
Some comments are outside the diff and canβt be posted inline due to platform limitations.
β οΈ Outside diff range comments (1)
src/hooks/useArtistHero.ts (1)
82-95: π― Functional Correctness | π Major | β‘ Quick winEmpΓͺchez une lecture obsolΓ¨te dβΓ©craser une Γ©criture optimiste.
Une lecture
refreshlancΓ©e avantsetEnabled(false)peut se terminer aprΓ¨s lβΓ©criture et aprΓ¨s leARTIST_HERO_EVENT. Chaque lecture applique actuellement son rΓ©sultat sans contrΓ΄ler son ordre.Dans ce cas, une ancienne valeur
"true"peut remplacerenabledetconfirmedEnabledRef.currentalors que la prΓ©fΓ©rence persistΓ©e vaut"false". Invalidez les lectures en cours lors dβune Γ©criture et appliquez un rΓ©sultat seulement sβil correspond Γ la derniΓ¨re lecture et Γ la derniΓ¨re Γ©criture pertinentes.Also applies to: 106-122
π€ 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/useArtistHero.ts` around lines 82 - 95, Update the refresh flow in useArtistHero so each read is associated with a generation/token, and apply parsed results only when that read is still the latest relevant read and no subsequent optimistic write has occurred. Invalidate or advance the read generation when setEnabled(false) or any preference write starts, and use the same guards before updating enabledRef, confirmedEnabledRef, setEnabledState, and setReadProfileId.
π€ 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.
Outside diff comments:
In `@src/hooks/useArtistHero.ts`:
- Around line 82-95: Update the refresh flow in useArtistHero so each read is
associated with a generation/token, and apply parsed results only when that read
is still the latest relevant read and no subsequent optimistic write has
occurred. Invalidate or advance the read generation when setEnabled(false) or
any preference write starts, and use the same guards before updating enabledRef,
confirmedEnabledRef, setEnabledState, and setReadProfileId.
βΉοΈ Review info
βοΈ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: d8c4c1ca-8043-4038-8246-7434613682cc
π Files selected for processing (1)
src/hooks/useArtistHero.ts
A read started at mount (or by ARTIST_HERO_EVENT) applied its result unconditionally, so a toggle clicked while it was in flight got overwritten by the pre-click value -- transient, since the write's own broadcast re-reads afterwards, but visible as the checkbox snapping back. Tag each read with a token that every read and every write bumps, and apply the parsed value only while that token still holds. The profile stamp in `finally` deliberately stays unconditional: the profile HAS been read, and gating it would pin `resolved` false forever if the racing write then failed. Claude-Session: https://claude.ai/code/session_01N9WXrurarkoiT2jgimvyj1
There was a problem hiding this comment.
Caution
Some comments are outside the diff and canβt be posted inline due to platform limitations.
β οΈ Outside diff range comments (2)
src/hooks/useArtistHero.ts (2)
131-143: ποΈ Data Integrity & Integration | π Major | β‘ Quick winNotifier les autres instances aprΓ¨s le rollback.
Si plusieurs instances de
useArtistHerosont montΓ©es, une premiΓ¨re Γ©criture peut rΓ©ussir puis une Γ©criture plus rΓ©cente Γ©chouer. La premiΓ¨re Γ©criture met Γ jourconfirmedEnabledRef.current, mais son Γ©vΓ©nement est supprimΓ© parseq !== writeSeqRef.current. Lecatchde la seconde Γ©criture restaure seulement lβinstance courante et nβΓ©met aucun Γ©vΓ©nement. Les autres instances gardent alors une valeur obsolΓ¨te.AprΓ¨s
setEnabledState(rollback), diffusezARTIST_HERO_EVENT, en conservant les contrΓ΄les de profil et de sΓ©quence.Correction minimale
const rollback = confirmedEnabledRef.current; enabledRef.current = rollback; setEnabledState(rollback); + window.dispatchEvent(new CustomEvent(ARTIST_HERO_EVENT));π€ 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/useArtistHero.ts` around lines 131 - 143, Dans le catch de lβΓ©criture de useArtistHero, aprΓ¨s setEnabledState(rollback), Γ©mettez ARTIST_HERO_EVENT afin de notifier les autres instances. Conservez les vΓ©rifications activeProfileIdRef.current !== profileId et seq !== writeSeqRef.current avant le rollback et la diffusion.
77-86: π― Functional Correctness | π Major | β‘ Quick winRΓ©initialiser
readProfileIdlors de chaque changement de profil.Lorsque lβutilisateur revient rapidement du profil A au profil B, puis au profil A,
readProfileIdpeut encore contenir A.resolveddevient alors vrai avant la nouvelle lecture, alors queenabledcontient encore la valeur par dΓ©faut appliquΓ©e pour B. Le hero peut afficher la mauvaise prΓ©fΓ©rence de A. Si la lecture Γ©choue, cet Γ©tat reste actif.RΓ©initialisez
readProfileIdΓundefinedavec les autres Γ©tats avantrefresh.Correction minimale
enabledRef.current = DEFAULT_ENABLED; confirmedEnabledRef.current = DEFAULT_ENABLED; + // eslint-disable-next-line react-hooks/set-state-in-effect + setReadProfileId(undefined); confirmedEnabledRef.current = DEFAULT_ENABLED;Also applies to: 149-150
π€ 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/useArtistHero.ts` around lines 77 - 86, Reset readProfileId to undefined alongside enabledRef, confirmedEnabledRef, and setEnabledState when the profile changes, before refresh runs. Update the profile-change reset logic in useArtistHero so resolved cannot reuse the previous profileβs read marker, while preserving the existing optimistic enabled behavior.
π€ 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.
Outside diff comments:
In `@src/hooks/useArtistHero.ts`:
- Around line 131-143: Dans le catch de lβΓ©criture de useArtistHero, aprΓ¨s
setEnabledState(rollback), Γ©mettez ARTIST_HERO_EVENT afin de notifier les autres
instances. Conservez les vΓ©rifications activeProfileIdRef.current !== profileId
et seq !== writeSeqRef.current avant le rollback et la diffusion.
- Around line 77-86: Reset readProfileId to undefined alongside enabledRef,
confirmedEnabledRef, and setEnabledState when the profile changes, before
refresh runs. Update the profile-change reset logic in useArtistHero so resolved
cannot reuse the previous profileβs read marker, while preserving the existing
optimistic enabled behavior.
βΉοΈ Review info
βοΈ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 7067c0ce-0546-4785-8260-fda80b0dc001
π Files selected for processing (1)
src/hooks/useArtistHero.ts
|
Note Docstrings generation - SUCCESS |
Docstrings generation was requested by @InstaZDLL. * #483 (comment) The following files were modified: * `src-tauri/crates/app/src/commands/browse.rs` * `src-tauri/crates/app/src/commands/deezer.rs` * `src-tauri/crates/core/src/artwork/metadata.rs` * `src-tauri/crates/core/src/metadata/theaudiodb.rs` * `src/components/common/ArtistHeroBackdrop.tsx` * `src/components/views/ArtistDetailView.tsx` * `src/components/views/SettingsView.tsx` * `src/components/views/settings/ArtistHeroCard.tsx` * `src/hooks/useArtistHero.ts` * `src/lib/tauri/detail.ts`
Deriving `resolved` from the id comparison alone was not enough: switch A -> B -> A faster than B's read completes and the stamp still says A, so `resolved` goes true while `enabled` is sitting at the default B's reset put there -- the very flash the stamp exists to prevent. Reset it with the rest of the outgoing profile's state. Claude-Session: https://claude.ai/code/session_01N9WXrurarkoiT2jgimvyj1
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and canβt be posted inline due to platform limitations.
β οΈ Outside diff range comments (3)
src/hooks/useArtistHero.ts (3)
77-105: π― Functional Correctness | π Major | ποΈ Heavy liftNe rendez pas
resolvedvrai avec un état de lecture obsolète.
finallyexΓ©cutesetReadProfileId(activeProfileId)mΓͺme lorsqueseq !== readSeqRef.current. Une lecture plus ancienne peut donc rendreresolvedvrai sans avoir appliquΓ© sa valeur.De plus,
readProfileIdnβest pas invalidΓ© quand le profil change. AprΓ¨sA β B β A, lβancien marqueur deArendresolvedvrai pendant la nouvelle lecture deA.enabledpeut alors contenir un Γ©tat par dΓ©faut non confirmΓ©.RΓ©initialisez
readProfileIdΓundefinedavec les autres Γ©tats de changement de profil. Renseignez-le uniquement pour la lecture courante. GΓ©rez explicitement le repli lorsquβune Γ©criture invalide la lecture.Also applies to: 149-150
π€ 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/useArtistHero.ts` around lines 77 - 105, Update the profile-change handling in useArtistHero to reset readProfileId to undefined alongside enabledRef, confirmedEnabledRef, and setEnabledState. In refresh, only setReadProfileId(activeProfileId) for the current read sequence, never when cancelled or seq !== readSeqRef.current, and explicitly preserve the default fallback when a write invalidates the read so resolved cannot become true from stale data.
131-143: ποΈ Data Integrity & Integration | π Major | β‘ Quick winΓmettez
ARTIST_HERO_EVENTaprΓ¨s le rollback.Si lβΓ©criture
seq=1rΓ©ussit et queseq=2Γ©choue, aucun Γ©vΓ©nement nβest Γ©mis. Lβinstance qui a lancΓ© les Γ©critures revient Γ la valeur confirmΓ©e, mais les autres instances peuvent conserver lβancienne valeur. Ajoutez lβΓ©mission aprΓ¨s le rollback et testez deux consommateurs montΓ©s simultanΓ©ment.π€ 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/useArtistHero.ts` around lines 131 - 143, Update the error path in the write handling around the ARTIST_HERO_EVENT dispatch so it emits ARTIST_HERO_EVENT after restoring the confirmed value and updating state. Preserve the existing profile and sequence guards, and add coverage for two simultaneously mounted consumers where a later write fails after an earlier write succeeds.
121-127: ποΈ Data Integrity & Integration | π‘ Minor | β‘ Quick winAssociez lβΓ©criture au profil capturΓ©.
set_profile_settingutilise le pool actif au moment oΓΉ la commande acquiert son lease. Un changement de profil peut donc survenir aprΓ¨s le garde JavaScript, mais avant cette acquisition, et Γ©crire la prΓ©fΓ©rence dans le nouveau profil. PassezprofileIdΓ la commande et validez-le sous le mΓͺme verrou que lβacquisition du pool.π€ 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/useArtistHero.ts` around lines 121 - 127, Associate the queued write in the enabled-setting flow with the captured profileId by passing it into the underlying set_profile_setting command. Ensure the command validates the captured profile ID while holding the same lock used to acquire the active pool, preventing a profile switch between the JavaScript guard and lease acquisition from writing to the wrong profile.
π€ 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/useArtistHero.ts`:
- Around line 55-59: Add a real read/write barrier in the refresh flow around
readSeqRef, refresh, and setProfileSetting: prevent a read triggered by
ARTIST_HERO_EVENT from applying while the current write is still pending, either
by awaiting that write or validating a pending-write token before updating
enabled and confirmedEnabledRef. After the write completes, reread the persisted
value and apply that result, while preserving existing overlapping-read
invalidation.
---
Outside diff comments:
In `@src/hooks/useArtistHero.ts`:
- Around line 77-105: Update the profile-change handling in useArtistHero to
reset readProfileId to undefined alongside enabledRef, confirmedEnabledRef, and
setEnabledState. In refresh, only setReadProfileId(activeProfileId) for the
current read sequence, never when cancelled or seq !== readSeqRef.current, and
explicitly preserve the default fallback when a write invalidates the read so
resolved cannot become true from stale data.
- Around line 131-143: Update the error path in the write handling around the
ARTIST_HERO_EVENT dispatch so it emits ARTIST_HERO_EVENT after restoring the
confirmed value and updating state. Preserve the existing profile and sequence
guards, and add coverage for two simultaneously mounted consumers where a later
write fails after an earlier write succeeds.
- Around line 121-127: Associate the queued write in the enabled-setting flow
with the captured profileId by passing it into the underlying
set_profile_setting command. Ensure the command validates the captured profile
ID while holding the same lock used to acquire the active pool, preventing a
profile switch between the JavaScript guard and lease acquisition from writing
to the wrong profile.
πͺ Autofix
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: f31f3771-1543-47d5-88f2-8a9ae67b3834
π Files selected for processing (1)
src/hooks/useArtistHero.ts
|
@coderabbitai Two findings from this review are real but deliberately out of scope for this PR β tracked in #485:
Everything else from the review is applied on this branch. The remaining suggestions I did not take, with reasons:
|
|
Tip For best results, initiate chat on the files or code changes.
I agree that the I also agree that the duplicated preference-hook behavior needs one shared The stated I will treat these items as deferred to βοΈ Learnings added
You are interacting with an AI system. |
Closes #482.
Gives the artist detail page a full-bleed hero behind the header, Spotify-style, instead of today's flat surface with a circular avatar. Both tiers ship here: the real wide fanart pipeline and the blurred-photo fallback.
Backdrop precedence
real wide fanart (TheAudioDB) β blurred square photo (Deezer / local
artist.jpg) β flat headerA crisp wide fanart gives the real look but not every artist has one; the blurred square photo is always available and works offline, so it's the universal fallback.
Backend
theaudiodb.rsβArtistPayloadnow parsesstrArtistFanart(+2/3/4),strArtistWideThumb,strArtistBanner.artist_biobecomesartist_infoand returns bio andfanart_urlfrom a single lookup, picking widest-and-cleanest first (logo banner last, its baked-in text can clash with the header copy). It returnsSomefor any name match even with neither bio nor fanart, so the caller can cache the "looked, nothing there" outcome.20260802120000_metadata_artist_background.sqlβbackground_url+background_hash+background_fetched_at. That last column is the "we already looked" marker: without it a NULL hash is indistinguishable from "never queried", and every fanart-less artist would re-hit a rate-limited API on each page visit. It's stamped only when the API was reached (match or not) and left NULL on a transport error, so a network blip retries instead of caching "no fanart" for the row's whole 30-day TTL. Pre-existing rows have NULL there and backfill once on their next refresh.enrich_artist_deezerβ resolves the fanart independently ofmetadata.bio_source(the issue's open question): Last.fm has no equivalent image, so gating on the bio source would leave every Last.fm user with no hero at all. One TheAudioDB request serves both consumers β the bio half is used only when TheAudioDB is the selected source, which matters on their shared rate-limited free key. Downloaded through the usualmetadata_artwork::download_and_cacheand kept full-res (no_1x/_2xβ downscaling a full-bleed banner only softens it). Offline mode short-circuits before all of it.get_artist_detailreturnsbackground_url/background_pathtoo, so the hero paints on the first frame for an already-enriched artist instead of waiting for the enrichment round-trip.Frontend
ArtistHeroBackdropβ fanart shown nearly crisp (blur(2px), just enough to keep JPEG artefacts from crawling under the white copy); the square photo gets the heavy colour-field treatmentSkinAmbientBackdropuses (blur(56px) saturate(190%),scale(1.35)) because a 1:1 image stretched across a banner is unreadable otherwise.ArtistDetailViewbreaks out of<main>'sp-8with-mx-8 -mt-8so the image reaches the column edges (and shrinks with the column when a right panel opens). The enrichment response only ever sets the fanart β an empty refresh (offline, TheAudioDB down) must not blank a hero the cache already produced.ui.artist_hero, per-profile, default ON (a baseline visual, not extra motion), Settings β Appearance; write machinery mirrorsuseCoverSlideshow.prefers-reduced-motionskips the cross-fade only β the image itself is static.Docs
docs/features/ui.md(new Artist hero section),docs/features/library.md(Wide artist fanart (hero) β the backend pipeline), CLAUDE.md (UI + Integrations catalogues).Checks
bun run typecheckβ Β·bun run lintβcargo check --workspace --all-targetsβ Β·cargo clippyβ no new warning (the 7 reported are pre-existing inscanner/extract.rs+browse.rs:462)cargo test -p waveflow-coreβ 102/102 β , including 3 newtheaudiodbtests covering the fanart fallback ordermigrations/app/chain + the exact INSERT the code issues) βNot verified by me: the visual result in the running app (crisp fanart vs blurred fallback, legibility across the 5 skins Γ 14 themes). Worth a look before merge.
https://claude.ai/code/session_01N9WXrurarkoiT2jgimvyj1
Summary by CodeRabbit
Nouvelles fonctionnalitΓ©s
AmΓ©liorations
Documentation