Skip to content

Redesign Preferred Artists page with default suggestions and batch save - #630

Merged
LucasSantana-Dev merged 8 commits into
mainfrom
feature/preferred-artists-ux
Apr 15, 2026
Merged

LucasSantana-Dev merged 8 commits into
mainfrom
feature/preferred-artists-ux

Conversation

@LucasSantana-Dev

@LucasSantana-Dev LucasSantana-Dev commented Apr 15, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • Load suggested artists on page mount (user's Spotify top artists or popular artists fallback)
  • Display artist grid immediately without requiring search
  • Click artist to expand related artists panel with add buttons
  • Batch save button for preference changes instead of auto-save
  • Fixed sidebar active link highlighting for /music vs /music/artists paths

Backend Changes

  • Added GET /api/artists/suggestions endpoint returning suggested artists (12 max)
  • Tries user's Spotify top artists if linked, falls back to popular artist search
  • Integrates with existing spotifyLinkService for user OAuth tokens

Frontend Changes

  • PreferredArtists page now loads suggestions on mount
  • State management refactored from auto-save to batch save with unsavedChanges map
  • Save Preferences button shows count of pending changes
  • Sidebar fixed to only highlight exact /music path, not /music/artists
  • Related artists expand already implemented, now works with new flow

Tests

  • Frontend builds successfully (no TypeScript errors)
  • Manual testing: dev server running at http://localhost:5173/
  • Backend build blocked on pre-existing type errors in GuildAutomationExecutionService (unrelated)

Summary by CodeRabbit

  • New Features
    • Added personalized artist suggestions on the Preferred Artists page, pulling recommendations based on your Spotify listening history.
    • Changed preference management to batch updates with a "Save Preferences" button instead of immediate saving.

@vercel

vercel Bot commented Apr 15, 2026 •

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
lucky Ready Ready Preview, Comment Apr 15, 2026 3:52am

Request Review

@coderabbitai

coderabbitai Bot commented Apr 15, 2026 •

Copy link
Copy Markdown

Warning

Rate limit exceeded

@LucasSantana-Dev has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 48 minutes and 59 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 48 minutes and 59 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9d63252a-81dd-4c0d-b1c0-0356d65dbcd6

📥 Commits

Reviewing files that changed from the base of the PR and between 7702f61 and 845f7a9.

📒 Files selected for processing (2)
  • packages/frontend/src/components/Layout/Sidebar.tsx
  • packages/frontend/src/pages/PreferredArtists.tsx
📝 Walkthrough

Walkthrough

Added a backend route for Spotify artist suggestions with OAuth token validation and fallback to popular artists. Updated frontend to display suggestions on the preferences page and batch preference changes before saving. Added API client method and expanded test coverage for new suggestion flows.

Changes

Cohort / File(s) Summary
Backend Suggestions Route
packages/backend/src/routes/artists.ts
Added GET /api/artists/suggestions route that validates auth/config, fetches user's Spotify top artists via OAuth token, and falls back to searching popular artists to return up to 12 suggestions with duplicate handling.
Backend Route Tests
packages/backend/tests/unit/routes/artists.test.ts
Migrated from Express integration tests to direct route-handler invocation; updated auth mocking strategy; added comprehensive test suite for suggestions endpoint covering success, fallback, and error cases; updated existing search/related artist tests for new token flow.
Frontend Preferences & API
packages/frontend/src/pages/PreferredArtists.tsx, packages/frontend/src/pages/PreferredArtists.test.tsx, packages/frontend/src/services/artistsApi.ts
Added suggestions loading and display workflow; replaced immediate preference persistence with batch local state (unsavedChanges map); added "Save Preferences" button to persist all changes at once; updated tests to mock suggestions endpoint and verify new batching behavior.
Frontend Navigation
packages/frontend/src/components/Layout/Sidebar.tsx
Refactored isActive route-matching helper to use explicit intermediate boolean variables for exact and child-path matching.

Sequence Diagram

sequenceDiagram
    actor User
    participant Frontend
    participant Backend API
    participant Spotify OAuth
    participant Spotify API

    User->>Frontend: Load Preferences Page
    Frontend->>Backend API: GET /api/artists/suggestions
    Backend API->>Spotify OAuth: getValidAccessToken(user)
    Spotify OAuth-->>Backend API: access_token
    Backend API->>Spotify API: GET /v1/me/top/artists?limit=12
    Spotify API-->>Backend API: top artists (may be < 12)
    Backend API->>Spotify API: searchSpotifyArtists(popular artist queries)
    Spotify API-->>Backend API: additional artists
    Backend API-->>Frontend: up to 12 unique suggestions
    Frontend->>Frontend: Display suggestions
    
    User->>Frontend: Click Prefer/Block on artists
    Frontend->>Frontend: Store in unsavedChanges (local)
    Frontend->>Frontend: Show "Save Preferences (N)" button
    
    User->>Frontend: Click Save Preferences
    Frontend->>Backend API: POST /api/artists/savePreference (batch)
    Backend API-->>Frontend: Success
    Frontend->>Frontend: Clear unsavedChanges
    Frontend->>User: Preferences saved
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

enhancement

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes: introducing default suggestions on the Preferred Artists page and implementing batch save functionality instead of per-click auto-save.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/preferred-artists-ux

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@github-actions

github-actions Bot commented Apr 15, 2026 •

Copy link
Copy Markdown

Size Change: +179 B (+0.05%)

Total Size: 330 kB

📦 View Changed
Filename Size Change
packages/frontend/dist/assets/api-BLqG7SN5.js 0 B -3.03 kB (removed) 🏆
packages/frontend/dist/assets/api-Blto5RPu.js 3.04 kB +3.04 kB (new file) 🆕
packages/frontend/dist/assets/authStore-C1dwYeJT.js 556 B +556 B (new file) 🆕
packages/frontend/dist/assets/authStore-C4ijVvVV.js 0 B -559 B (removed) 🏆
packages/frontend/dist/assets/AutoMessages-CxAC26iZ.js 2.69 kB +2.69 kB (new file) 🆕
packages/frontend/dist/assets/AutoMessages-DbMmQklh.js 0 B -2.69 kB (removed) 🏆
packages/frontend/dist/assets/AutoMod-DC_3Fe18.js 0 B -4.09 kB (removed) 🏆
packages/frontend/dist/assets/AutoMod-IPk4FC7g.js 4.09 kB +4.09 kB (new file) 🆕
packages/frontend/dist/assets/CommandsConfig-BHEBU2Zz.js 1.48 kB +1.48 kB (new file) 🆕
packages/frontend/dist/assets/CommandsConfig-C71Z4IkF.js 0 B -1.48 kB (removed) 🏆
packages/frontend/dist/assets/Config-CAxgqG6L.js 1.74 kB +1.74 kB (new file) 🆕
packages/frontend/dist/assets/Config-DzwPg7B7.js 0 B -1.74 kB (removed) 🏆
packages/frontend/dist/assets/CustomCommands-B7S3fWE5.js 2.16 kB +2.16 kB (new file) 🆕
packages/frontend/dist/assets/CustomCommands-BJ1Oig4H.js 0 B -2.16 kB (removed) 🏆
packages/frontend/dist/assets/DashboardOverview-CgUjrOjS.js 3.43 kB +3.43 kB (new file) 🆕
packages/frontend/dist/assets/DashboardOverview-heBtBhTy.js 0 B -3.43 kB (removed) 🏆
packages/frontend/dist/assets/EmbedBuilder-BeE7w404.js 0 B -3.35 kB (removed) 🏆
packages/frontend/dist/assets/EmbedBuilder-BxVz8INr.js 3.35 kB +3.35 kB (new file) 🆕
packages/frontend/dist/assets/Features-CEU1eqSj.js 0 B -2.85 kB (removed) 🏆
packages/frontend/dist/assets/Features-CrbsOLD9.js 2.85 kB +2.85 kB (new file) 🆕
packages/frontend/dist/assets/GuildAutomation-brd_jKPZ.js 2.94 kB +2.94 kB (new file) 🆕
packages/frontend/dist/assets/GuildAutomation-C-TG8vtz.js 0 B -2.94 kB (removed) 🏆
packages/frontend/dist/assets/guildStore-CAjPv8sV.js 0 B -794 B (removed) 🏆
packages/frontend/dist/assets/guildStore-wioLYb73.js 792 B +792 B (new file) 🆕
packages/frontend/dist/assets/index-B9az-0WS.css 14.8 kB +14.8 kB (new file) 🆕
packages/frontend/dist/assets/index-CctRicia.css 0 B -14.8 kB (removed) 🏆
packages/frontend/dist/assets/index-D2n6sd3m.js 0 B -11.9 kB (removed) 🏆
packages/frontend/dist/assets/index-tQ0w2qnb.js 11.9 kB +11.9 kB (new file) 🆕
packages/frontend/dist/assets/LastFm-B07mRY72.js 1.74 kB +1.74 kB (new file) 🆕
packages/frontend/dist/assets/LastFm-DIlMaY52.js 0 B -1.74 kB (removed) 🏆
packages/frontend/dist/assets/Levels-BLW2OZYg.js 0 B -2.65 kB (removed) 🏆
packages/frontend/dist/assets/Levels-KZV2nvlm.js 2.65 kB +2.65 kB (new file) 🆕
packages/frontend/dist/assets/Login-Bh2-6TFH.js 2.54 kB +2.54 kB (new file) 🆕
packages/frontend/dist/assets/Login-DNiXyp2_.js 0 B -2.54 kB (removed) 🏆
packages/frontend/dist/assets/Lyrics-Dl-b7yFR.js 0 B -1.33 kB (removed) 🏆
packages/frontend/dist/assets/Lyrics-Gtw6yX4k.js 1.33 kB +1.33 kB (new file) 🆕
packages/frontend/dist/assets/Moderation-CyaN7lXF.js 3.86 kB +3.86 kB (new file) 🆕
packages/frontend/dist/assets/Moderation-DTml_loD.js 0 B -3.86 kB (removed) 🏆
packages/frontend/dist/assets/Music--LCKSOb6.js 0 B -6.19 kB (removed) 🏆
packages/frontend/dist/assets/Music-D1PNEUqr.js 6.19 kB +6.19 kB (new file) 🆕
packages/frontend/dist/assets/MusicConfig-87HBxyt8.js 0 B -1.62 kB (removed) 🏆
packages/frontend/dist/assets/MusicConfig-BCkOOtK9.js 1.62 kB +1.62 kB (new file) 🆕
packages/frontend/dist/assets/PreferredArtists-Cw_z1IGH.js 3.47 kB +3.47 kB (new file) 🆕
packages/frontend/dist/assets/PreferredArtists-D1owV9DW.js 0 B -3.33 kB (removed) 🏆
packages/frontend/dist/assets/ReactionRoles-BPfogqlf.js 0 B -1.9 kB (removed) 🏆
packages/frontend/dist/assets/ReactionRoles-DBwT2uY_.js 1.9 kB +1.9 kB (new file) 🆕
packages/frontend/dist/assets/ServerLogs-C8ftKvHi.js 2.9 kB +2.9 kB (new file) 🆕
packages/frontend/dist/assets/ServerLogs-F3T90IRN.js 0 B -2.91 kB (removed) 🏆
packages/frontend/dist/assets/ServerSettings-BCZJ99-s.js 4.21 kB +4.21 kB (new file) 🆕
packages/frontend/dist/assets/ServerSettings-BOaCeJ6A.js 0 B -4.21 kB (removed) 🏆
packages/frontend/dist/assets/ServersPage-CFWg2_5N.js 2.95 kB +2.95 kB (new file) 🆕
packages/frontend/dist/assets/ServersPage-CoHVlmOg.js 0 B -2.95 kB (removed) 🏆
packages/frontend/dist/assets/Spotify-1jyiUDSb.js 1.75 kB +1.75 kB (new file) 🆕
packages/frontend/dist/assets/Spotify-Dbzb3H0T.js 0 B -1.75 kB (removed) 🏆
packages/frontend/dist/assets/Starboard-DpV4ppJ5.js 2.09 kB +2.09 kB (new file) 🆕
packages/frontend/dist/assets/Starboard-Sa0w_z7k.js 0 B -2.1 kB (removed) 🏆
packages/frontend/dist/assets/TrackHistory-BdWQP0sm.js 0 B -2.18 kB (removed) 🏆
packages/frontend/dist/assets/TrackHistory-BGNzV6ha.js 2.18 kB +2.18 kB (new file) 🆕
packages/frontend/dist/assets/TwitchNotifications-BCRAXnPh.js 0 B -2.44 kB (removed) 🏆
packages/frontend/dist/assets/TwitchNotifications-CTpayncX.js 2.44 kB +2.44 kB (new file) 🆕
packages/frontend/dist/assets/useGuildSelection-CJakUuQg.js 0 B -486 B (removed) 🏆
packages/frontend/dist/assets/useGuildSelection-CL4dEo7i.js 487 B +487 B (new file) 🆕
ℹ️ View Unchanged
Filename Size
packages/frontend/dist/assets/ActionPanel-DxbYjZOV.js 400 B
packages/frontend/dist/assets/avatar-B8wmY8_z.js 1.79 kB
packages/frontend/dist/assets/badge-Di6G1o1C.js 498 B
packages/frontend/dist/assets/Button-DNJBdlDI.js 761 B
packages/frontend/dist/assets/Card-BZdEz3Em.js 456 B
packages/frontend/dist/assets/chunk-QFMPRPBF-B0_TipZB.js 14.8 kB
packages/frontend/dist/assets/dialog-nNJN7xxV.js 947 B
packages/frontend/dist/assets/dist-BYvsGl5s.js 414 B
packages/frontend/dist/assets/EmptyState-SHSbVkpR.js 430 B
packages/frontend/dist/assets/input-ChG24niz.js 463 B
packages/frontend/dist/assets/label-q_xFnvVW.js 485 B
packages/frontend/dist/assets/PrivacyPolicy-D6bsnWOd.js 1.38 kB
packages/frontend/dist/assets/rbac-BN1DzQHh.js 152 B
packages/frontend/dist/assets/rolldown-runtime-COnpUsM8.js 467 B
packages/frontend/dist/assets/scroll-area-C108baTt.js 519 B
packages/frontend/dist/assets/SectionHeader-CQJKLDod.js 383 B
packages/frontend/dist/assets/select-DphAEnHz.js 1.22 kB
packages/frontend/dist/assets/Skeleton-C5XSiOK9.js 235 B
packages/frontend/dist/assets/StatTile-BL59Oosl.js 603 B
packages/frontend/dist/assets/switch-C3IautHz.js 541 B
packages/frontend/dist/assets/TermsOfService-B5dZHvi1.js 1.37 kB
packages/frontend/dist/assets/usePageMetadata-B513X5D1.js 328 B
packages/frontend/dist/assets/utils-DhIi5ktf.js 147 B
packages/frontend/dist/assets/vendor-forms-B7m4p2yU.js 25.4 kB
packages/frontend/dist/assets/vendor-radix-CqxcePto.js 33.5 kB
packages/frontend/dist/assets/vendor-react-DRFstzzV.js 55.6 kB
packages/frontend/dist/assets/vendor-state-C5KtdxzA.js 22.2 kB
packages/frontend/dist/assets/vendor-ui-DqvmPbZd.js 64.4 kB

compressed-size-action

…tch save

- Load suggested artists on page mount (user's top Spotify artists or popular artists)
- Show artist grid without requiring search input
- Click to expand related artists panel with add buttons
- Batch save button instead of auto-save on each click
- Fixed sidebar active link highlighting for /music vs /music/artists
- PreferredArtists.test.tsx: add mockGetSuggestions and fix tests to match redesigned grid view with batch save button, detail panel, and related artists
- Sidebar.tsx: simplify isActive() logic to properly support sub-route activation for all paths including /music sub-routes

All 517 tests pass.
- Backend: 22 unit tests for artists.ts (suggestions, search, related, preferences CRUD)
  * Covers OAuth-linked top artists path, fallback to search, error cases (401, 503, 500)
  * Tests both success and error flows for all 5 endpoints
- Frontend: 14 additional tests for PreferredArtists.tsx (26 total)
  * Coverage: 93.44% statements, 82.11% branches, 90% functions
  * Covers save/error handling, loading states, related panel, unsaved changes
  * Tests duplicate prevention, genre rendering, preference state in detail panel
- API client: artistsApi.ts inherently covered via frontend integration tests

Fixes PR #630 SonarCloud coverage requirement (41.8% → >80%)
@LucasSantana-Dev
LucasSantana-Dev force-pushed the feature/preferred-artists-ux branch from b5886c6 to 7702f61 Compare April 15, 2026 03:40
@github-actions github-actions Bot added size/xl and removed size/l labels Apr 15, 2026
@coderabbitai coderabbitai Bot added the enhancement New feature or request label Apr 15, 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: 2

🧹 Nitpick comments (5)
packages/backend/tests/unit/routes/artists.test.ts (1)

105-107: Handler extraction relies on route registration order.

Extracting handlers via mock.calls[0][2], mock.calls[1][2], etc. couples tests to the order routes are registered in setupArtistsRoutes. If routes are reordered, these indices will break silently. Consider extracting handlers by matching the route path instead.

♻️ Example approach
function findHandler(mockApp: Express, method: 'get' | 'post' | 'delete', path: string) {
    const calls = (mockApp[method] as jest.Mock).mock.calls
    const call = calls.find(([p]) => p === path)
    return call?.[2]
}

// Usage:
const handler = findHandler(mockApp, 'get', '/api/artists/suggestions')
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/backend/tests/unit/routes/artists.test.ts` around lines 105 - 107,
Tests for route handlers currently grab handlers by call index (e.g.,
(mockApp.get as jest.Mock).mock.calls[0][2]), which couples them to registration
order; update the tests that call setupArtistsRoutes and read handlers from
mockApp (mockApp.get/mockApp.post/etc.) to instead search the mock calls for the
desired route path and HTTP method and return the handler (e.g., implement a
small helper used by the test to scan (mockApp.get as jest.Mock).mock.calls for
the entry whose first argument equals the route string and then use its third
element as the handler), ensuring references to setupArtistsRoutes and
mockApp.get/mockApp.post identify where to change.
packages/frontend/src/pages/PreferredArtists.tsx (2)

445-447: Silent error handling provides no user feedback.

When savePreference fails, the error is caught but users receive no indication of failure. While unsavedChanges correctly remains populated (allowing retry), consider adding a toast or error state as noted in the TODO comment.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/frontend/src/pages/PreferredArtists.tsx` around lines 445 - 447, The
catch block in the savePreference flow (inside PreferredArtists.tsx around the
savePreference call) swallows errors and provides no user feedback; update the
catch to surface failures by invoking the app's toast/notification mechanism (or
set an error state) and include the error message so users know the save failed,
while keeping unsavedChanges intact for retry; specifically modify the catch
handling that currently comments "// error handling could be improved with a
toast" to call the existing toast/snackbar helper (or setError / setToast) and
ensure any cleanup or UI state (e.g., disabling spinner) is handled
consistently.

416-442: Sequential API calls could be parallelized for better performance.

The batch save iterates through unsavedChanges and awaits each savePreference call sequentially. For users with many pending changes, this could be slow. Consider using Promise.all for parallel execution if the backend supports concurrent requests.

♻️ Parallel save approach
const savePromises = [...unsavedChanges.entries()].map(async ([key, pref]) => {
    const artist = suggestedArtists.find(a => normalizeArtistKey(a.name) === key)
    // ... build payload
    return api.artists.savePreference(payload)
})
await Promise.all(savePromises)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/frontend/src/pages/PreferredArtists.tsx` around lines 416 - 442, The
loop over unsavedChanges is calling api.artists.savePreference sequentially
which is slow; change it to build an array of promises and await them in
parallel using Promise.all. For each entry in unsavedChanges (use
unsavedChanges.entries() or [...unsavedChanges.entries()]) compute the artist
via suggestedArtists.find(a => normalizeArtistKey(a.name) === key) or fallback
to savedPreferences.get(key), construct the same payload you currently pass to
api.artists.savePreference, push the returned promise into an array, and then
await Promise.all(promises) so savePreference calls run concurrently; keep use
of normalizeArtistKey, suggestedArtists, savedPreferences, and
api.artists.savePreference intact.
packages/backend/src/routes/artists.ts (1)

60-67: Consider adding a timeout to the Spotify API fetch call.

The fetch call to Spotify's top artists endpoint has no timeout configured. If the Spotify API is slow or unresponsive, this could block the request handler indefinitely. Consider using AbortSignal.timeout() for resilience.

♻️ Suggested fix
                     const userTopRes = await fetch(
                         'https://api.spotify.com/v1/me/top/artists?limit=12&time_range=medium_term',
                         {
                             headers: {
                                 Authorization: `Bearer ${link}`,
                             },
+                            signal: AbortSignal.timeout(5000),
                         },
                     )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/backend/src/routes/artists.ts` around lines 60 - 67, The Spotify
fetch that assigns userTopRes has no timeout; wrap the request with an
AbortSignal (e.g., use AbortSignal.timeout(ms) or create an AbortController) and
pass signal to fetch so the call to
fetch('https://api.spotify.com/v1/me/top/artists...') will be aborted after a
reasonable timeout (e.g., 3-10s); update the surrounding async handler to catch
the abort/timeout error (AbortError) and return/throw an appropriate error
response instead of letting the request hang.
packages/frontend/src/pages/PreferredArtists.test.tsx (1)

512-549: Test validates API call but not error feedback to user.

The test confirms mockSavePreference is called when an error occurs, but doesn't assert any user-visible error feedback. This aligns with the implementation's TODO comment about improving error handling with a toast. Consider adding error UI assertions when that improvement is made.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/frontend/src/pages/PreferredArtists.test.tsx` around lines 512 -
549, The test "handles save preference error gracefully" currently only asserts
mockSavePreference was called; update it to also assert the user-visible error
feedback is shown after mockSavePreference rejects (e.g., check for the
toast/alert message or error UI that the component displays on failure). Locate
the test by its name and the references to mockSavePreference, renderPage, and
the Save Preferences button, then add a waitFor/assertion that the expected
error element (toast/alert text or role) appears in the DOM.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/frontend/src/components/Layout/Sidebar.tsx`:
- Around line 489-491: The current active-link logic still treats a parent like
"/music" as active for "/music/artists" because withChild uses startsWith(path +
'/'); change the logic in the Sidebar link activation to only use exact match
(remove/stop using withChild) so the function returns only exact (i.e., return
exact) and ensure any aria-current computation relies on that exact variable
instead of withChild (update uses of exact and withChild in this block
accordingly).

In `@packages/frontend/src/pages/PreferredArtists.tsx`:
- Around line 412-450: The save loop in handleSavePreferences only looks up
artist metadata in suggestedArtists and savedPreferences so preferences toggled
from searchResults get lost; modify how unsavedChanges is populated (update
handleTogglePreference) to store an object containing both preference and the
artist metadata (e.g., { pref, artist }) or include searchResults lookup, then
update handleSavePreferences to read artist metadata from unsavedChanges first
(falling back to suggestedArtists or savedPreferences only if missing) before
calling api.artists.savePreference; reference handleSavePreferences,
handleTogglePreference, unsavedChanges, suggestedArtists, savedPreferences, and
searchResults when making this change.

---

Nitpick comments:
In `@packages/backend/src/routes/artists.ts`:
- Around line 60-67: The Spotify fetch that assigns userTopRes has no timeout;
wrap the request with an AbortSignal (e.g., use AbortSignal.timeout(ms) or
create an AbortController) and pass signal to fetch so the call to
fetch('https://api.spotify.com/v1/me/top/artists...') will be aborted after a
reasonable timeout (e.g., 3-10s); update the surrounding async handler to catch
the abort/timeout error (AbortError) and return/throw an appropriate error
response instead of letting the request hang.

In `@packages/backend/tests/unit/routes/artists.test.ts`:
- Around line 105-107: Tests for route handlers currently grab handlers by call
index (e.g., (mockApp.get as jest.Mock).mock.calls[0][2]), which couples them to
registration order; update the tests that call setupArtistsRoutes and read
handlers from mockApp (mockApp.get/mockApp.post/etc.) to instead search the mock
calls for the desired route path and HTTP method and return the handler (e.g.,
implement a small helper used by the test to scan (mockApp.get as
jest.Mock).mock.calls for the entry whose first argument equals the route string
and then use its third element as the handler), ensuring references to
setupArtistsRoutes and mockApp.get/mockApp.post identify where to change.

In `@packages/frontend/src/pages/PreferredArtists.test.tsx`:
- Around line 512-549: The test "handles save preference error gracefully"
currently only asserts mockSavePreference was called; update it to also assert
the user-visible error feedback is shown after mockSavePreference rejects (e.g.,
check for the toast/alert message or error UI that the component displays on
failure). Locate the test by its name and the references to mockSavePreference,
renderPage, and the Save Preferences button, then add a waitFor/assertion that
the expected error element (toast/alert text or role) appears in the DOM.

In `@packages/frontend/src/pages/PreferredArtists.tsx`:
- Around line 445-447: The catch block in the savePreference flow (inside
PreferredArtists.tsx around the savePreference call) swallows errors and
provides no user feedback; update the catch to surface failures by invoking the
app's toast/notification mechanism (or set an error state) and include the error
message so users know the save failed, while keeping unsavedChanges intact for
retry; specifically modify the catch handling that currently comments "// error
handling could be improved with a toast" to call the existing toast/snackbar
helper (or setError / setToast) and ensure any cleanup or UI state (e.g.,
disabling spinner) is handled consistently.
- Around line 416-442: The loop over unsavedChanges is calling
api.artists.savePreference sequentially which is slow; change it to build an
array of promises and await them in parallel using Promise.all. For each entry
in unsavedChanges (use unsavedChanges.entries() or
[...unsavedChanges.entries()]) compute the artist via suggestedArtists.find(a =>
normalizeArtistKey(a.name) === key) or fallback to savedPreferences.get(key),
construct the same payload you currently pass to api.artists.savePreference,
push the returned promise into an array, and then await Promise.all(promises) so
savePreference calls run concurrently; keep use of normalizeArtistKey,
suggestedArtists, savedPreferences, and api.artists.savePreference intact.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 927d58f4-fbf4-41ec-8a68-b40676b2d113

📥 Commits

Reviewing files that changed from the base of the PR and between 1bc5b3a and 7702f61.

📒 Files selected for processing (6)
  • packages/backend/src/routes/artists.ts
  • packages/backend/tests/unit/routes/artists.test.ts
  • packages/frontend/src/components/Layout/Sidebar.tsx
  • packages/frontend/src/pages/PreferredArtists.test.tsx
  • packages/frontend/src/pages/PreferredArtists.tsx
  • packages/frontend/src/services/artistsApi.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: compressed-size
  • GitHub Check: Quality Gates
  • GitHub Check: SonarCloud Scan
🔇 Additional comments (8)
packages/frontend/src/services/artistsApi.ts (1)

26-28: LGTM!

The new getSuggestions method correctly matches the backend endpoint response shape { artists: SpotifyArtist[] } and follows the established patterns of other methods in this API client.

packages/backend/src/routes/artists.ts (1)

31-145: LGTM - well-structured suggestions endpoint.

The new endpoint handles authentication, Spotify configuration checks, user's top artists with OAuth, and fallback to popular artists search. The deduplication via Map and proper error handling with errorLog are good practices.

packages/frontend/src/pages/PreferredArtists.test.tsx (2)

16-28: LGTM - comprehensive test mock setup.

The mock for getSuggestions is properly added alongside existing API mocks, and the default return value in beforeEach (Line 92) ensures tests start with a clean state.


551-725: LGTM - thorough edge case coverage.

Good test coverage for no genres, loading states, unsaved changes removal, duplicate artists, API errors, and preference state in related artists panel. The tests follow consistent patterns and properly await async operations.

packages/backend/tests/unit/routes/artists.test.ts (2)

75-226: LGTM - comprehensive test coverage for suggestions endpoint.

Tests cover the OAuth top artists flow, fallback to popular artists search, and all error cases (401, 503, 500). The fetch spy is properly restored after each test.


228-608: LGTM - consistent test coverage for all routes.

Tests for search, related artists, and preference CRUD operations follow a consistent pattern with proper mock setup and assertions for success and error cases.

packages/frontend/src/pages/PreferredArtists.tsx (2)

612-633: LGTM - well-implemented save button with loading state.

The save button correctly shows the pending changes count, disables during save, and displays a loading spinner. Good UX pattern for batch operations.


346-349: LGTM - appropriate initial data loading.

Both preferences and suggestions are loaded on mount. The effect correctly depends on both callback references, ensuring data refreshes when the guild changes.

Comment thread packages/frontend/src/components/Layout/Sidebar.tsx
Comment thread packages/frontend/src/pages/PreferredArtists.tsx Outdated
@LucasSantana-Dev
LucasSantana-Dev enabled auto-merge (squash) April 15, 2026 03:48
@sonarqubecloud

Copy link
Copy Markdown

@LucasSantana-Dev
LucasSantana-Dev merged commit ab56340 into main Apr 15, 2026
13 checks passed
LucasSantana-Dev added a commit that referenced this pull request May 13, 2026
…ve (#630)

* fix: sidebar active link - only highlight exact /music path, not /music/artists

* feat: add /api/artists/suggestions endpoint for default artist grid

* feat: add getSuggestions method to artistsApi

* feat: redesign preferred artists page with default suggestions and batch save

- Load suggested artists on page mount (user's top Spotify artists or popular artists)
- Show artist grid without requiring search input
- Click to expand related artists panel with add buttons
- Batch save button instead of auto-save on each click
- Fixed sidebar active link highlighting for /music vs /music/artists

* fix(backend): replace any type with SpotifyArtist in artists suggestions endpoint

* fix: update PreferredArtists and Sidebar tests for redesigned UX

- PreferredArtists.test.tsx: add mockGetSuggestions and fix tests to match redesigned grid view with batch save button, detail panel, and related artists
- Sidebar.tsx: simplify isActive() logic to properly support sub-route activation for all paths including /music sub-routes

All 517 tests pass.

* test: comprehensive coverage for artists feature

- Backend: 22 unit tests for artists.ts (suggestions, search, related, preferences CRUD)
  * Covers OAuth-linked top artists path, fallback to search, error cases (401, 503, 500)
  * Tests both success and error flows for all 5 endpoints
- Frontend: 14 additional tests for PreferredArtists.tsx (26 total)
  * Coverage: 93.44% statements, 82.11% branches, 90% functions
  * Covers save/error handling, loading states, related panel, unsaved changes
  * Tests duplicate prevention, genre rendering, preference state in detail panel
- API client: artistsApi.ts inherently covered via frontend integration tests

Fixes PR #630 SonarCloud coverage requirement (41.8% → >80%)

* fix(frontend): exact /music match + preserve search-result artist metadata on save
@LucasSantana-Dev
LucasSantana-Dev deleted the feature/preferred-artists-ux branch May 23, 2026 02:21

This branch was successfully deployed

1 active deployment
Preview — 845f7a98 Deployed Apr 15, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant