Skip to content

Fix/audio state across page#29

Open
aman-wednesdaysol wants to merge 11 commits into
wednesday-solutions:masterfrom
aman-wednesdaysol:fix/audio-state-across-page
Open

Fix/audio state across page#29
aman-wednesdaysol wants to merge 11 commits into
wednesday-solutions:masterfrom
aman-wednesdaysol:fix/audio-state-across-page

Conversation

@aman-wednesdaysol

@aman-wednesdaysol aman-wednesdaysol commented Feb 23, 2026

Copy link
Copy Markdown

Summary by CodeRabbit

  • New Features

    • Audio player with playback controls, progress slider, and volume adjustment
    • Song search functionality with live results
    • Liked songs library with add/remove capability
    • Track detail pages displaying song information
    • User authentication with login and signup
    • Dark/light theme switcher
    • Music visualization component
    • Enhanced navigation and UI controls
  • Documentation

    • Added code governance rules and design guidelines

@coderabbitai

coderabbitai Bot commented Feb 23, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Introduces a complete music player application with authentication, playback controls, library management, theme switching, and API integration. Adds 100+ new files spanning components, containers, services, utilities, styled elements, Redux state management, sagas, and comprehensive test coverage.

Changes

Cohort / File(s) Summary
Authentication System
app/containers/Auth/*, pages/login.js, pages/signup.js, app/services/authApi.js, app/utils/authStorage.js, app/utils/withAuth.js
Login and signup flows with Redux integration, token persistence, and protected routing via HOC wrapper.
Audio Playback
app/components/AudioPlayer/*, app/components/AudioPlayer/tests/*
Custom hook-based audio player component with playback controls, progress tracking, and volume management. Includes singleton audio instance, setup hook, and comprehensive tests.
Music Container & Search
app/containers/Music/*, app/containers/Music/tests/*
Main music page with search, song list display, playback control, and like/unlike functionality. Redux-connected with debounced search, navigation, and toggle hooks.
Library Management
app/containers/Library/*, app/containers/Library/tests/*
Liked songs library container with fetch, like/unlike actions, and playlist display. Redux reducer, saga, and selector tests included.
Track Detail
app/containers/TrackDetail/*, app/containers/TrackDetail/tests/*
Individual track detail page with metadata display and playback integration. Includes fetching saga and state management tests.
UI Components
app/components/ArtworkPlayButton/*, app/components/BackButton/*, app/components/HeartButton/*, app/components/LogoutButton/*, app/components/NavLink/*, app/components/SearchBar/*, app/components/SongList/*, app/components/ThemeToggle/*, app/components/TrackInfo/*, app/components/MusicVisual/*
Reusable presentational components for artwork display, navigation, song interactions, search, and visual branding. All include PropTypes validation and test suites.
Styling System
app/components/styled/*
Emotion-based styled components for UI elements (buttons, forms, player bar, layouts, colors). Consistent theming and responsive design across application.
Theme Management
app/contexts/ThemeContext.js, app/contexts/tests/ThemeContext.test.js, app/themes/palettes.js, app/utils/themeStorage.js
Dark/light theme support with localStorage persistence, CSS variable generation, and context-based theming system.
API & Services
app/services/musicApi.js, app/services/libraryApi.js, app/utils/apiUtils.js
Music and library API clients with rewrite configuration for backend routing. Extended API utilities for auth headers and request/response transformation.
Redux State Management
app/reducers.js, combined with Auth/Music/Library/TrackDetail reducer imports
Central reducer composition integrating authentication, music, library, and track detail state slices.
Utilities & Helpers
app/utils/formatDuration.js, app/utils/tests/*
Duration formatting and comprehensive utility tests for auth storage, theme storage, and authentication HOC.
Global Configuration
pages/_app.js, pages/_document.js, pages/index.js, pages/library.js, pages/track/[trackId].js, app/global-styles.js, next.config.js, environments/.*
App initialization with theme provider setup, font loading, API rewrites, auth token setup, and global styling. Protected pages wrapped with withAuth HOC. Environment variables for analytics.
Documentation & Governance
CLAUDE.md
Code rules and aesthetic guidelines for frontend development.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant LoginPage
    participant Redux
    participant AuthSaga
    participant AuthAPI
    participant TokenStorage
    participant Router

    User->>LoginPage: Enter email/password
    LoginPage->>Redux: Dispatch requestLogin(email, password)
    Redux->>AuthSaga: REQUEST_LOGIN action
    AuthSaga->>AuthAPI: Call loginUser(credentials)
    AuthAPI-->>AuthSaga: Response with accessToken
    AuthSaga->>Redux: Dispatch successAuth(data)
    Redux->>Redux: Store user data & token
    AuthSaga->>TokenStorage: setStoredToken(accessToken)
    AuthSaga->>AuthAPI: setAuthHeader('auth', token)
    AuthSaga->>Router: Push to '/'
    Router-->>User: Navigate to Music page
Loading
sequenceDiagram
    participant User
    participant MusicPage
    participant SongList
    participant AudioPlayer
    participant useAudioPlayer
    participant AudioSingleton
    participant Redux

    User->>MusicPage: Mount page
    MusicPage->>Redux: Fetch songs & library
    Redux-->>MusicPage: Songs loaded
    MusicPage->>SongList: Render with songs
    User->>SongList: Click play on song
    SongList->>AudioPlayer: Set currentSong via prop
    AudioPlayer->>useAudioPlayer: song changed
    useAudioPlayer->>AudioSingleton: getAudioInstance()
    AudioSingleton-->>useAudioPlayer: Audio element
    useAudioPlayer->>useAudioPlayer: Set src & volume
    useAudioPlayer->>AudioSingleton: audio.play()
    AudioSingleton-->>useAudioPlayer: Playing
    useAudioPlayer->>Redux: Dispatch setIsPlaying(true)
    Redux-->>MusicPage: Update UI state
    MusicPage-->>User: Show pause icon
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Poem

🐰 Whiskers twitch with joy so bright,
Music plays from dawn to night,
Auth flows dance, themes toggle free,
A player built for you and me!
🎵 Hop along this code so grand,
The finest player in the land! 🎶

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'Fix/audio state across page' is vague and does not clearly summarize the main changes. While it references audio state management, the PR introduces numerous new components, pages, services, and Redux architecture for a complete music player application far beyond audio state fixes. Consider a more descriptive title like 'Add music player and authentication system with audio state management' or 'Implement complete music app with auth, player, and library features' to better reflect the extensive scope of changes.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

@gemini-code-assist

Copy link
Copy Markdown

Summary of Changes

Hello @aman-wednesdaysol, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a comprehensive music streaming application structure, including core UI components, state management for music playback and user authentication, and API integrations. It establishes a robust foundation for a feature-rich music platform, focusing on user experience and maintainability.

Highlights

  • Core UI Components: Introduced a suite of new React components including ArtworkPlayButton, AudioPlayer, SongList, SearchBar, HeartButton, BackButton, LogoutButton, NavLink, ThemeToggle, MusicVisual, and TrackInfo to build the music application's user interface.
  • State Management & API Integration: Implemented Redux Sagas for managing authentication (login/signup), music search, and user library (liking/unliking songs), along with dedicated reducers and selectors for each domain. New API services (authApi, musicApi, libraryApi) were created to interact with backend endpoints.
  • Global Audio Playback: Established a global audio singleton (audioSingleton.js) and custom hooks (useAudioPlayer, useAudioSetup) to ensure persistent audio state and seamless playback across different pages of the application.
  • Theming & Authentication: Added a global ThemeContext for dynamic dark/light mode switching with localStorage persistence. Implemented an authentication flow with LoginForm and SignupForm containers, token storage, and a withAuth Higher-Order Component (HOC) for protecting routes.
  • Styled Components & Global Styles: Utilized Emotion's styled components for all new UI elements, ensuring a consistent and modern aesthetic. Global styles were updated to incorporate new font families and CSS variables for theming.
  • Routing & Pages: Introduced new Next.js pages for /login, /signup, /library, and dynamic /track/[trackId] routes, replacing the previous ReposPage with the new Music container as the homepage.
  • Testing: Added comprehensive unit tests for all new components, custom hooks, Redux reducers, sagas, selectors, and utility functions, ensuring robust and reliable functionality.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • CLAUDE.md
    • Added code rules and frontend aesthetics guidelines.
  • app/components/ArtworkPlayButton/index.js
    • Added a React component for displaying album artwork with a play/pause overlay.
  • app/components/ArtworkPlayButton/tests/index.test.js
    • Added tests for the ArtworkPlayButton component's rendering and click behavior.
  • app/components/AudioPlayer/audioSingleton.js
    • Added a singleton function to provide a single HTML Audio instance across the application.
  • app/components/AudioPlayer/constants.js
    • Added a constant for the default volume of the audio player.
  • app/components/AudioPlayer/index.js
    • Added the main AudioPlayer component with playback controls, progress, and volume sliders.
  • app/components/AudioPlayer/tests/index.test.js
    • Added tests for the AudioPlayer component's rendering and functionality.
  • app/components/AudioPlayer/tests/useAudioPlayer.test.js
    • Added tests for the useAudioPlayer hook's playback, volume, and seek functionalities.
  • app/components/AudioPlayer/useAudioPlayer.js
    • Added a custom hook to manage audio playback state and controls.
  • app/components/AudioPlayer/useAudioSetup.js
    • Added a custom hook to set up and manage audio element event listeners.
  • app/components/BackButton/index.js
    • Added a reusable BackButton component for navigation.
  • app/components/BackButton/tests/index.test.js
    • Added tests for the BackButton component's rendering and click handling.
  • app/components/HeartButton/index.js
    • Added a reusable HeartButton component for liking and unliking songs.
  • app/components/HeartButton/tests/index.test.js
    • Added tests for the HeartButton component's state and click behavior.
  • app/components/LogoutButton/index.js
    • Added a LogoutButton component to clear authentication and redirect.
  • app/components/LogoutButton/tests/index.test.js
    • Added tests for the LogoutButton component's logout functionality.
  • app/components/MusicVisual/constants.js
    • Added constants defining properties for equalizer bars, floating notes, and glow rings.
  • app/components/MusicVisual/index.js
    • Added a MusicVisual component to display animated music-themed background elements.
  • app/components/MusicVisual/tests/snapshots/index.test.js.snap
    • Added snapshot for the MusicVisual component.
  • app/components/MusicVisual/tests/index.test.js
    • Added tests for the MusicVisual component's rendering.
  • app/components/NavLink/index.js
    • Added a NavLink component for styled navigation links.
  • app/components/NavLink/tests/index.test.js
    • Added tests for the NavLink component's rendering and href.
  • app/components/SearchBar/index.js
    • Added a SearchBar component for user input to search for music.
  • app/components/SearchBar/tests/snapshots/index.test.js.snap
    • Added snapshot for the SearchBar component.
  • app/components/SearchBar/tests/index.test.js
    • Added tests for the SearchBar component's input and change handling.
  • app/components/SongList/index.js
    • Added a SongList component to display a collection of songs with play and like actions.
  • app/components/SongList/tests/index.test.js
    • Added tests for the SongList component's rendering, navigation, and interaction.
  • app/components/ThemeToggle/index.js
    • Added a ThemeToggle component to switch between dark and light themes.
  • app/components/ThemeToggle/tests/index.test.js
    • Added tests for the ThemeToggle component's theme switching and persistence.
  • app/components/TrackInfo/index.js
    • Added a TrackInfo component to display detailed information about a music track.
  • app/components/TrackInfo/tests/index.test.js
    • Added tests for the TrackInfo component's rendering of track details.
  • app/components/styled/artworkPlayButton.js
    • Added styled components for the ArtworkPlayButton, including animations.
  • app/components/styled/authForm.js
    • Added styled components for authentication forms, including inputs, buttons, and text.
  • app/components/styled/authLayout.js
    • Added styled components for the authentication page layout, including visual and form panels.
  • app/components/styled/backButton.js
    • Added styled components for the BackButton.
  • app/components/styled/colors.js
    • Added color palette constants for consistent theming.
  • app/components/styled/heartButton.js
    • Added styled components for the HeartButton, including a pop animation.
  • app/components/styled/logoutButton.js
    • Added styled components for the LogoutButton.
  • app/components/styled/musicPage.js
    • Added styled components for general music page layout, headers, and loading states.
  • app/components/styled/musicSearch.js
    • Added styled components for the SearchBar.
  • app/components/styled/musicVisual.js
    • Added styled components for the MusicVisual component, including animations for vinyl, glow rings, equalizer, and floating notes.
  • app/components/styled/navLink.js
    • Added styled components for navigation links and groups.
  • app/components/styled/playerBar.js
    • Added styled components for the AudioPlayer, including controls and sliders.
  • app/components/styled/songList.js
    • Added styled components for the SongList, including card styling and glow animation.
  • app/components/styled/themeToggle.js
    • Added styled components for the ThemeToggle button.
  • app/components/styled/trackInfo.js
    • Added styled components for the TrackInfo component, including artwork, metadata, and tags.
  • app/containers/Auth/LoginForm.js
    • Added a LoginForm component for user authentication.
  • app/containers/Auth/SignupForm.js
    • Added a SignupForm component for user registration.
  • app/containers/Auth/reducer.js
    • Added Redux reducer for managing authentication state.
  • app/containers/Auth/saga.js
    • Added Redux Saga to handle login and signup API calls and token persistence.
  • app/containers/Auth/selectors.js
    • Added Redux selectors to access authentication state.
  • app/containers/Auth/tests/LoginForm.test.js
    • Added tests for the LoginForm component's input and submission.
  • app/containers/Auth/tests/SignupForm.test.js
    • Added tests for the SignupForm component's input and submission.
  • app/containers/Auth/tests/snapshots/LoginForm.test.js.snap
    • Added snapshot for the LoginForm component.
  • app/containers/Auth/tests/snapshots/SignupForm.test.js.snap
    • Added snapshot for the SignupForm component.
  • app/containers/Auth/tests/reducer.test.js
    • Added tests for the auth reducer's state transitions.
  • app/containers/Auth/tests/saga.test.js
    • Added tests for the auth saga's login and signup flows.
  • app/containers/Auth/tests/selectors.test.js
    • Added tests for the auth selectors.
  • app/containers/Library/constants.js
    • Added constants for the library module's Redux actions and state.
  • app/containers/Library/index.js
    • Added the Library container to display and manage liked songs.
  • app/containers/Library/reducer.js
    • Added Redux reducer for managing the user's liked songs library.
  • app/containers/Library/saga.js
    • Added Redux Saga to handle fetching, liking, and unliking songs in the library.
  • app/containers/Library/selectors.js
    • Added Redux selectors to access library state.
  • app/containers/Library/tests/index.test.js
    • Added tests for the Library container's rendering and interactions.
  • app/containers/Library/tests/reducer.test.js
    • Added tests for the library reducer's state management.
  • app/containers/Library/tests/saga.test.js
    • Added tests for the library saga's API interactions.
  • app/containers/Library/tests/selectors.test.js
    • Added tests for the library selectors.
  • app/containers/Music/constants.js
    • Added constants for the music module's Redux actions, state, and search debounce.
  • app/containers/Music/index.js
    • Added the Music container for searching and playing songs.
  • app/containers/Music/reducer.js
    • Added Redux reducer for managing music search results and current playback.
  • app/containers/Music/saga.js
    • Added Redux Saga to handle music search API calls.
  • app/containers/Music/selectors.js
    • Added Redux selectors to access music search and playback state.
  • app/containers/Music/tests/index.test.js
    • Added tests for the Music container's rendering and search functionality.
  • app/containers/Music/tests/reducer.test.js
    • Added tests for the music reducer's state updates.
  • app/containers/Music/tests/saga.test.js
    • Added tests for the music saga's search API calls.
  • app/containers/Music/tests/selectors.test.js
    • Added tests for the music selectors.
  • app/containers/Music/tests/usePlayToggle.test.js
    • Added tests for the usePlayToggle hook's play/pause logic.
  • app/containers/Music/tests/usePlaybackNav.test.js
    • Added tests for the usePlaybackNav hook's next/previous song logic.
  • app/containers/Music/tests/useToggleLike.test.js
    • Added tests for the useToggleLike hook's like/unlike logic.
  • app/containers/Music/usePlayToggle.js
    • Added a custom hook to manage play/pause actions for the current song.
  • app/containers/Music/usePlaybackNav.js
    • Added a custom hook to handle navigation between songs in a list.
  • app/containers/Music/useToggleLike.js
    • Added a custom hook to manage liking and unliking songs.
  • app/containers/Repos/tests/snapshots/index.test.js.snap
    • Updated snapshot for the Repos component to reflect search input type change.
  • app/containers/TrackDetail/constants.js
    • Added constants for the track detail module's Redux actions and state.
  • app/containers/TrackDetail/index.js
    • Added the TrackDetail container to display detailed information for a specific track.
  • app/containers/TrackDetail/reducer.js
    • Added Redux reducer for managing track detail state.
  • app/containers/TrackDetail/saga.js
    • Added Redux Saga to handle fetching track details from the API.
  • app/containers/TrackDetail/selectors.js
    • Added Redux selectors to access track detail state.
  • app/containers/TrackDetail/tests/index.test.js
    • Added tests for the TrackDetail container's rendering and data fetching.
  • app/containers/TrackDetail/tests/reducer.test.js
    • Added tests for the track detail reducer's state updates.
  • app/containers/TrackDetail/tests/saga.test.js
    • Added tests for the track detail saga's API calls.
  • app/containers/TrackDetail/tests/selectors.test.js
    • Added tests for the track detail selectors.
  • app/contexts/ThemeContext.js
    • Added a React context for global theme management, including persistence.
  • app/contexts/tests/ThemeContext.test.js
    • Added tests for the ThemeContext's theme toggling and persistence.
  • app/global-styles.js
    • Updated global CSS to use new font families and CSS variables for theming.
  • app/reducers.js
    • Integrated new Redux reducers (auth, music, library, trackDetail) into the root reducer.
  • app/services/authApi.js
    • Added API client and functions for user login and signup.
  • app/services/libraryApi.js
    • Added API client and functions for fetching, liking, and unliking songs in the user's library.
  • app/services/musicApi.js
    • Added API client and functions for searching songs and fetching track details.
  • app/services/tests/libraryApi.test.js
    • Added tests for the library API service functions.
  • app/themes/palettes.js
    • Added dark and light color palettes and a utility to convert them to CSS variables.
  • app/utils/apiUtils.js
    • Modified API utility to support multiple API clients, set authentication headers, and conditionally skip request transformations.
  • app/utils/authStorage.js
    • Added utility functions for managing authentication tokens in local storage.
  • app/utils/formatDuration.js
    • Added a utility function to format duration from milliseconds to M:SS.
  • app/utils/tests/authStorage.test.js
    • Added tests for the authStorage utility functions.
  • app/utils/tests/formatDuration.test.js
    • Added tests for the formatDuration utility function.
  • app/utils/tests/themeStorage.test.js
    • Added tests for the themeStorage utility functions.
  • app/utils/tests/withAuth.test.js
    • Added tests for the withAuth Higher-Order Component.
  • app/utils/themeStorage.js
    • Added utility functions for managing theme preferences in local storage.
  • app/utils/withAuth.js
    • Added a Higher-Order Component (HOC) to protect routes by checking for an authentication token.
  • environments/.env.development
    • Updated development environment variables to include PostHog API keys.
  • environments/.env.production
    • Updated production environment variables to include PostHog API keys.
  • environments/.env.qa
    • Updated QA environment variables to include PostHog API keys.
  • next.config.js
    • Configured Next.js to handle API rewrites for music and authentication services and updated webpack alias.
  • package.json
    • Updated lint-staged configuration to include the -u flag for Jest.
  • pages/_app.js
    • Modified the main application component to include a global ThemeProvider and initialize authentication headers.
  • pages/_document.js
    • Modified the document component to preload Google Fonts for new UI typography.
  • pages/index.js
    • Replaced the previous ReposPage with the new Music container and applied authentication protection.
  • pages/library.js
    • Added a new page for the user's music library, protected by authentication.
  • pages/login.js
    • Added a new login page, integrating the LoginForm, MusicVisual, and ThemeToggle components.
  • pages/signup.js
    • Added a new signup page, integrating the SignupForm, MusicVisual, and ThemeToggle components.
  • pages/track/[trackId].js
    • Added a new dynamic route for displaying detailed track information, protected by authentication.
Activity
  • The pull request introduces a significant amount of new code, primarily focused on establishing a music application.
  • Many new components, hooks, Redux modules (reducers, sagas, selectors), and utility functions have been added.
  • Comprehensive unit tests have been written for most new functionalities, ensuring code quality and reliability.
  • Configuration files like next.config.js and environment variables have been updated to support new features and API integrations.
  • Global styling and theming infrastructure have been set up.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request introduces a comprehensive audio player feature with persistent state, authentication, and library management. However, it contains a critical security vulnerability due to hardcoded API endpoints pointing to "http://localhost:9000" in both the API utility and Next.js configuration. This transmits sensitive data over unencrypted HTTP and will cause production failures. Additionally, there are critical issues regarding Server-Side Rendering (SSR) compatibility for the audio instance, potential stale closures in event listeners, and several new styled-component files exceeding the 100-line limit defined in CLAUDE.md.


export const getAudioInstance = () => {
if (!audioInstance) {
audioInstance = new Audio();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

critical

The Audio constructor is a browser-only API. In a Next.js environment, this code will execute on the server during pre-rendering and throw a ReferenceError: Audio is not defined. You should add a check for the window object before instantiation.

Suggested change
audioInstance = new Audio();
audioInstance = typeof window !== 'undefined' ? new Audio() : null;

Comment on lines +7 to +10
const [isPlaying, setIsPlaying] = useState(!audio.paused);
const [currentTime, setCurrentTime] = useState(audio.currentTime || 0);
const [duration, setDuration] = useState(audio.duration || 0);
const [volume, setVolumeState] = useState(audio.volume);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

These state initializers will throw an error during Server-Side Rendering (SSR) because the audio instance will be null (if the singleton is fixed) or Audio will be undefined. Use optional chaining and provide safe default values for the server environment.

Suggested change
const [isPlaying, setIsPlaying] = useState(!audio.paused);
const [currentTime, setCurrentTime] = useState(audio.currentTime || 0);
const [duration, setDuration] = useState(audio.duration || 0);
const [volume, setVolumeState] = useState(audio.volume);
const [isPlaying, setIsPlaying] = useState(audio?.paused === false);
const [currentTime, setCurrentTime] = useState(audio?.currentTime || 0);
const [duration, setDuration] = useState(audio?.duration || 0);
const [volume, setVolumeState] = useState(audio?.volume || 0);

Comment on lines +12 to +30
useEffect(() => {
const onTime = () => setCurrentTime(audio.currentTime);
const onLoaded = () => setDuration(audio.duration);
const onEnded = () => {
setIsPlaying(false);
onPlayStateChange?.(false);
onSongEnd?.();
};

audio.addEventListener('timeupdate', onTime);
audio.addEventListener('loadedmetadata', onLoaded);
audio.addEventListener('ended', onEnded);

return () => {
audio.removeEventListener('timeupdate', onTime);
audio.removeEventListener('loadedmetadata', onLoaded);
audio.removeEventListener('ended', onEnded);
};
}, []);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

The event listeners capture onPlayStateChange and onSongEnd from the initial render due to the empty dependency array []. If these callbacks change in the parent component (e.g., due to a search result update), the onEnded listener will execute stale versions of the functions. Use a useRef to store the latest callbacks and access them within the listeners.

Comment thread app/utils/apiUtils.js
case 'github':
apiClients[type] = createApiClientWithTransForm(process.env.NEXT_PUBLIC_GITHUB_URL);
case 'auth':
apiClients[type] = createApiClientWithTransForm('http://localhost:9000');

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

security-medium medium

The 'auth' API client's base URL is hardcoded to http://localhost:9000. This is a critical security risk as it's used for sensitive operations (e.g., login/signup) and transmits passwords over unencrypted HTTP, making them vulnerable to interception. Hardcoding localhost will also cause the application to fail in production and may lead to Mixed Content errors. It is strongly recommended to use environment variables for API endpoints and ensure production environments use HTTPS.

Suggested change
apiClients[type] = createApiClientWithTransForm('http://localhost:9000');
apiClients[type] = createApiClientWithTransForm(process.env.NEXT_PUBLIC_AUTH_URL || 'http://localhost:9000');

Comment thread next.config.js
Comment on lines +31 to +33
{ source: '/music/:path*', destination: 'http://localhost:9000/music/:path*' },
{ source: '/login', destination: 'http://localhost:9000/login' },
{ source: '/signup', destination: 'http://localhost:9000/signup' }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

security-medium medium

The Next.js configuration contains hardcoded rewrites that proxy requests to 'http://localhost:9000'. This includes endpoints for music resources, login, and signup. Proxying sensitive requests to an insecure HTTP endpoint on 'localhost' is a security risk in production and a configuration flaw that prevents proper deployment. It is recommended to configure proxy destinations using environment variables and ensure production backends are accessed over HTTPS.

Comment thread app/utils/apiUtils.js

export const createApiClientWithTransForm = (baseURL) => {
export const setAuthHeader = (type, token) => {
const client = apiClients[type];

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The setAuthHeader function directly accesses apiClients[type]. If the client hasn't been initialized yet via generateApiClient, this function will do nothing. It's safer to call generateApiClient inside this function to ensure the client exists before setting the header.

Suggested change
const client = apiClients[type];
const client = generateApiClient(type);

@@ -0,0 +1,118 @@
import styled from '@emotion/styled';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

This file is 118 lines long, which violates the project rule defined in CLAUDE.md (Max file length: 100 lines). Please consider breaking this file into smaller logical units, such as separating input-related styles from button or card styles.

References
  1. Max file length: 100 lines — break into smaller files if exceeded (link)

@@ -0,0 +1,131 @@
import styled from '@emotion/styled';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

This file is 131 lines long, exceeding the 100-line limit specified in the CLAUDE.md code rules. Consider extracting keyframes or specific visual component groups (like Equalizer or FloatingNotes) into separate files.

References
  1. Max file length: 100 lines — break into smaller files if exceeded (link)

@@ -0,0 +1,122 @@
import styled from '@emotion/styled';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

This file is 122 lines long, violating the 100-line limit rule in CLAUDE.md. Consider splitting the player container and control styles from the slider and volume group styles.

References
  1. Max file length: 100 lines — break into smaller files if exceeded (link)

@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: 16

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

🟡 Minor comments (23)
environments/.env.qa-3-3 (1)

3-3: ⚠️ Potential issue | 🟡 Minor

Missing trailing newline

dotenv-linter flags this file as missing a blank line at the end, which is a POSIX/Unix convention that some tooling depends on.

✏️ Proposed fix
 NEXT_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
+
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@environments/.env.qa` at line 3, Add a POSIX trailing newline to this
environment file by ensuring the final line with
NEXT_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com ends with a newline character;
update the file that contains the NEXT_PUBLIC_POSTHOG_HOST entry so the file
terminates with a blank line (newline at EOF) to satisfy dotenv-linter and POSIX
conventions.
app/components/ArtworkPlayButton/tests/index.test.js-37-46 (1)

37-46: ⚠️ Potential issue | 🟡 Minor

Stop-propagation test never asserts that the parent click was suppressed.

parentClick is declared and registered but its call count is never asserted. Without expect(parentClick).not.toHaveBeenCalled(), the "stop propagation" half of the test name is untested.

   fireEvent.click(wrapper)
   expect(defaultProps.onClick).toHaveBeenCalledTimes(1)
+  expect(parentClick).not.toHaveBeenCalled()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/components/ArtworkPlayButton/tests/index.test.js` around lines 37 - 46,
The test for ArtworkPlayButton doesn't assert that event propagation was
stopped: after registering parentClick on wrapper.parentElement and firing
fireEvent.click(wrapper), add an assertion like
expect(parentClick).not.toHaveBeenCalled() so the "stop propagation" behavior is
validated; update the test that references wrapper, parentClick, defaultProps,
getByTestId, and ArtworkPlayButton to include this negative assertion after the
existing expect(defaultProps.onClick) check.
app/utils/formatDuration.js-1-6 (1)

1-6: ⚠️ Potential issue | 🟡 Minor

Add a guard for null/undefined and consider hours support.

Math.floor(undefined / 1000) yields NaN, producing the string "NaN:NaN". With no upstream null-check, passing undefined (e.g., before a track's metadata loads) will silently render a broken duration in the UI.

Additionally, values ≥ 3,600,000 ms will render as "60:00", "61:05", etc. instead of "1:00:00". Depending on whether the app plays podcasts or long mixes, this may be a latent bug worth addressing now.

🛡️ Proposed fix
 export const formatDuration = (ms) => {
+  if (ms == null || isNaN(ms) || ms < 0) return '0:00';
   const totalSeconds = Math.floor(ms / 1000);
-  const minutes = Math.floor(totalSeconds / 60);
+  const hours = Math.floor(totalSeconds / 3600);
+  const minutes = Math.floor((totalSeconds % 3600) / 60);
   const seconds = totalSeconds % 60;
-  return `${minutes}:${seconds.toString().padStart(2, '0')}`;
+  return hours > 0
+    ? `${hours}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`
+    : `${minutes}:${seconds.toString().padStart(2, '0')}`;
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/utils/formatDuration.js` around lines 1 - 6, formatDuration currently
crashes or returns "NaN:NaN" when ms is null/undefined and doesn't handle
durations >= 1 hour; update the formatDuration function to first guard: if ms is
null/undefined or not a finite number coerce to 0 (e.g., const totalSeconds =
Math.max(0, Math.floor(Number(ms) / 1000))), then compute hours =
Math.floor(totalSeconds/3600), minutes = Math.floor((totalSeconds%3600)/60),
seconds = totalSeconds%60, and return "H:MM:SS" when hours>0 (hours no pad,
minutes and seconds padStart(2,'0')) otherwise return "M:SS" with minutes and
seconds padded as needed; keep function name formatDuration unchanged.
app/components/ArtworkPlayButton/tests/index.test.js-24-35 (1)

24-35: ⚠️ Potential issue | 🟡 Minor

Icon-specific tests don't verify which icon is actually rendered.

Both "show PlayCircleFilled" and "show PauseCircleFilled" tests only assert that the overlay container exists — they would pass even if the isActive && isPlaying icon-switching logic were completely broken. To make these tests meaningful, assert on something that differentiates the two states, such as an aria-label or data-testid on each icon.

💡 Suggested approach

Add distinct data-testid attributes in ArtworkPlayButton/index.js:

- {isActive && isPlaying ? <PauseCircleFilled /> : <PlayCircleFilled />}
+ {isActive && isPlaying
+   ? <PauseCircleFilled data-testid="pause-icon" />
+   : <PlayCircleFilled data-testid="play-icon" />}

Then update the tests:

 it('should show PlayCircleFilled when not active', () => {
   const { getByTestId } = renderProvider(<ArtworkPlayButton {...defaultProps} />)
-  expect(getByTestId('play-overlay')).toBeTruthy()
+  expect(getByTestId('play-icon')).toBeTruthy()
+  expect(() => getByTestId('pause-icon')).toThrow()
 })

 it('should show PauseCircleFilled when active and playing', () => {
   const props = { ...defaultProps, isActive: true, isPlaying: true }
   const { getByTestId } = renderProvider(<ArtworkPlayButton {...props} />)
-  expect(getByTestId('play-overlay')).toBeTruthy()
+  expect(getByTestId('pause-icon')).toBeTruthy()
+  expect(() => getByTestId('play-icon')).toThrow()
 })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/components/ArtworkPlayButton/tests/index.test.js` around lines 24 - 35,
Tests for ArtworkPlayButton only assert the overlay exists and do not verify
which icon is rendered; update the component ArtworkPlayButton (the render logic
that chooses PlayCircleFilled vs PauseCircleFilled) to add distinct identifiers
(e.g., data-testid or aria-label on the PlayCircleFilled and PauseCircleFilled
icons) and then change the two tests in
app/components/ArtworkPlayButton/tests/index.test.js to assert the specific icon
identifier (e.g., expect(getByTestId('play-icon')) and
expect(getByTestId('pause-icon')) respectively) so each test validates the
icon-switching behavior when isActive and isPlaying change.
app/components/SearchBar/tests/index.test.js-8-8 (1)

8-8: ⚠️ Potential issue | 🟡 Minor

loading prop is not part of SearchBar's API — test is misleading.

The SearchBar component only accepts value and onChange. The loading prop in defaultProps (Line 8) is silently ignored, so the test at lines 32–36 asserts that an input that is never disabled is not disabled — it passes vacuously and documents no real behavior.

Either remove the loading prop from the test, or implement disabled={loading} in the component and update the test assertion to reflect the intended behavior.

🛠️ Option A — Remove `loading` from tests (if no disable behavior is intended)
-  const defaultProps = { value: '', onChange: mockChange, loading: false }
+  const defaultProps = { value: '', onChange: mockChange }
 
-  it('should not be disabled when loading', () => {
-    const props = { ...defaultProps, loading: true }
-    const { getByTestId } = renderProvider(<SearchBar {...props} />)
-    expect(getByTestId('music-search-input')).not.toBeDisabled()
-  })
🛠️ Option B — Add `disabled` prop support to the component and test it properly

In app/components/SearchBar/index.js:

-const SearchBar = ({ value, onChange }) => (
+const SearchBar = ({ value, onChange, loading }) => (
   <SearchContainer>
     <SearchInput
       data-testid="music-search-input"
       type="text"
       placeholder="Search songs, artists..."
       value={value}
+      disabled={loading}
       onChange={(e) => onChange(e.target.value)}
     />
   </SearchContainer>
 );

Then the test becomes meaningful:

-  it('should not be disabled when loading', () => {
+  it('should be disabled when loading', () => {
     const props = { ...defaultProps, loading: true }
     const { getByTestId } = renderProvider(<SearchBar {...props} />)
-    expect(getByTestId('music-search-input')).not.toBeDisabled()
+    expect(getByTestId('music-search-input')).toBeDisabled()
   })

Also applies to: 32-36

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

In `@app/components/SearchBar/tests/index.test.js` at line 8, The test's
defaultProps includes a non-existent loading prop so assertions about disabled
state are meaningless; either remove loading from defaultProps and delete the
disabled-related assertion in the SearchBar tests, or add disabled support to
the SearchBar component by wiring a disabled={loading} (or prop name you choose)
onto the input element and update the tests to pass loading and assert the input
is disabled when true; locate the test defaultProps and the SearchBar component
(component function/class and its input element) to apply the chosen fix and
ensure prop types/prop forwarding are updated accordingly.
CLAUDE.md-13-33 (1)

13-33: ⚠️ Potential issue | 🟡 Minor

DISTILLED_AESTHETICS_PROMPT = """...""" is a raw Python/AI artifact in a Markdown file.

This block renders as literal text with stray """ delimiters, breaking the document. Either remove it, convert it into a proper Markdown section with headings and bullet points, or fence it in a code block if the intent is to expose it as a reusable prompt literal.

📝 Minimal fix — wrap in a fenced code block
-DISTILLED_AESTHETICS_PROMPT = """
-<frontend_aesthetics>
-...
-</frontend_aesthetics>
-"""
+```python
+DISTILLED_AESTHETICS_PROMPT = """
+<frontend_aesthetics>
+...
+</frontend_aesthetics>
+"""
+```
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@CLAUDE.md` around lines 13 - 33, The Markdown contains a raw Python/AI
literal DISTILLED_AESTHETICS_PROMPT = """...""" which renders incorrectly; fix
by converting that block into a proper Markdown fenced code block or a formatted
Markdown section: replace the raw triple-quoted Python string with a fenced code
block (```python ... ```) or transform the contents into headings, paragraphs,
and bullet lists so the stray """ delimiters are removed and the prompt renders
correctly; target the DISTILLED_AESTHETICS_PROMPT block in CLAUDE.md when making
this change.
app/components/SearchBar/index.js-5-20 (1)

5-20: ⚠️ Potential issue | 🟡 Minor

Add missing loading prop to match test expectations and parent container usage.

The test file and parent container (app/containers/Music/index.js) both pass a loading prop to SearchBar, but the component doesn't accept it. This creates a prop contract mismatch. While the test currently passes because the input is never disabled (matching the assertion .not.toBeDisabled()), the component should accept the prop it receives.

Proposed fix
-const SearchBar = ({ value, onChange }) => (
+const SearchBar = ({ value, onChange, loading }) => (
   <SearchContainer>
     <SearchInput
       data-testid="music-search-input"
       type="text"
       placeholder="Search songs, artists..."
       value={value}
       onChange={(e) => onChange(e.target.value)}
     />
   </SearchContainer>
 );

 SearchBar.propTypes = {
   value: PropTypes.string,
-  onChange: PropTypes.func.isRequired
+  onChange: PropTypes.func.isRequired,
+  loading: PropTypes.bool
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/components/SearchBar/index.js` around lines 5 - 20, SearchBar currently
ignores the loading prop passed from its parent; update the component signature
to accept loading (e.g., change ({ value, onChange }) to ({ value, onChange,
loading })) and pass it to the input as disabled={loading} on the SearchInput
element so parent/tests can control disabled state, then add loading:
PropTypes.bool to SearchBar.propTypes (and optionally a defaultProp) to keep the
prop contract consistent; reference SearchBar, SearchInput, SearchContainer, and
SearchBar.propTypes when making this change.
app/components/ArtworkPlayButton/index.js-12-18 (1)

12-18: ⚠️ Potential issue | 🟡 Minor

ArtworkWrapper is a non-interactive <div> — keyboard inaccessible.

Clicking via mouse works, but keyboard users and assistive technologies cannot activate this control. Add role, tabIndex, and a keyboard handler:

♿ Proposed fix
-<ArtworkWrapper onClick={handleClick} data-testid="artwork-play-btn">
+<ArtworkWrapper
+  onClick={handleClick}
+  onKeyDown={(e) => (e.key === 'Enter' || e.key === ' ') && handleClick(e)}
+  role="button"
+  tabIndex={0}
+  aria-label={isPlaying && isActive ? 'Pause' : 'Play'}
+  data-testid="artwork-play-btn"
+>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/components/ArtworkPlayButton/index.js` around lines 12 - 18,
ArtworkWrapper is a non-interactive div so keyboard users can't activate the
control; make it keyboard-accessible by adding role="button", tabIndex={0}, and
an onKeyDown handler that calls the same handlerClick when Enter or Space is
pressed (preserve existing onClick={handleClick} and data-testid). Update the
ArtworkWrapper element in the ArtworkPlayButton component to include these
attributes and ensure the onKeyDown logic normalizes Space/Enter activation to
invoke handleClick (mirror the click behavior and keep PlayOverlay/isActive
rendering unchanged).
app/components/LogoutButton/index.js-7-10 (1)

7-10: ⚠️ Potential issue | 🟡 Minor

Use Router.replace instead of Router.push on logout.

Router.push('/login') adds /login to the history stack, allowing the user to press "Back" and land on the now-protected page (which withAuth will then redirect away from, causing a visible flash/loop). withAuth.js correctly uses router.replace('/login') for the same reason — replace avoids adding the protected page to history. Logout should follow the same pattern.

🐛 Proposed fix
 const handleLogout = () => {
   clearStoredToken();
-  Router.push('/login');
+  Router.replace('/login');
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/components/LogoutButton/index.js` around lines 7 - 10, The logout handler
currently uses Router.push('/login') which leaves the protected page in browser
history; update the handleLogout function to call Router.replace('/login') after
clearStoredToken() so the login navigation replaces the current history entry
(match the behavior in withAuth.js) and prevents a back-button flash/redirect
loop.
app/utils/authStorage.js-12-17 (1)

12-17: ⚠️ Potential issue | 🟡 Minor

setStoredToken(null) stores the string "null" — causes a false-positive auth check.

localStorage.setItem(TOKEN_KEY, null) coerces null to the string "null", which is truthy. getStoredToken() would then return "null", and withAuth.js would treat this as a valid token, granting access to protected pages.

🐛 Proposed guard
 export const setStoredToken = (token) => {
   if (!isBrowser()) {
     return;
   }
+  if (token == null) {
+    return;
+  }
   localStorage.setItem(TOKEN_KEY, token);
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/utils/authStorage.js` around lines 12 - 17, setStoredToken currently
calls localStorage.setItem(TOKEN_KEY, token) which coerces null/undefined to the
string "null"/"undefined" and leads to false-positive auth; update
setStoredToken to first check isBrowser(), then if token is null or undefined
(or an empty string if you prefer) call localStorage.removeItem(TOKEN_KEY)
instead of setItem, otherwise call setItem with the token; also ensure
getStoredToken and withAuth.js treat absent key as unauthenticated (reference
functions setStoredToken, getStoredToken and withAuth.js).
app/themes/palettes.js-14-20 (1)

14-20: ⚠️ Potential issue | 🟡 Minor

darkPalette.placeholder is near-invisible on the dark background.

placeholder: '#070708' is essentially pure black. The dark theme's primary bg is #0d0d0d — placing near-black placeholder text on a near-black surface fails contrast requirements (WCAG AA requires ≥ 4.5:1 for normal text). This is only legible when the placeholder appears inside the light inputBg: '#d2d2db', but that inputBg is also unusually light for a dark theme.

Compare to lightPalette.placeholder: '#b0b0c0', which follows the conventional pattern of muted text. The dark palette values for inputBg and placeholder appear to be swapped or incorrectly set.

🐛 Proposed fix
 export const darkPalette = {
   ...shared,
   bg: '#0d0d0d',
   cardBg: '#161622',
-  inputBg: '#d2d2db',
+  inputBg: '#2a2a3e',
   border: '#000000',
   text: '#ffffff',
   muted: '#d0d0d0',
   label: '#9ca0b0',
-  placeholder: '#070708',
+  placeholder: '#6c6c80',
   surface: '#1a1a2e'
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/themes/palettes.js` around lines 14 - 20, The darkPalette currently uses
near-black placeholder and a very light inputBg (symbols:
darkPalette.placeholder and darkPalette.inputBg), which breaks contrast and
looks swapped compared to lightPalette.placeholder; swap or replace these values
so darkPalette.placeholder becomes a muted light-gray (similar to
lightPalette.placeholder but lighter, e.g., a mid-gray) and darkPalette.inputBg
becomes a dark/charcoal tone matching darkPalette.surface/bg, ensuring WCAG AA
contrast (adjust darkPalette.placeholder to a value that meets ≥4.5:1 against
darkPalette.inputBg); update the values in app/themes/palettes.js for
darkPalette.placeholder and darkPalette.inputBg accordingly.
app/components/styled/musicVisual.js-67-110 (1)

67-110: ⚠️ Potential issue | 🟡 Minor

Custom props (size, duration, delay, left, bottom) leak to the DOM on GlowRing, EqualizerBar, and FloatingNote.

Same issue as noted in songList.js — Emotion forwards all props to the underlying div. Each of these components will generate React console warnings for unrecognized DOM attributes. Apply shouldForwardProp to filter them.

🛡️ Example fix for GlowRing
-export const GlowRing = styled.div`
+const dynamicProps = new Set(['size', 'duration', 'delay', 'left', 'bottom']);
+
+export const GlowRing = styled('div', {
+  shouldForwardProp: (prop) => !dynamicProps.has(prop)
+})`

Apply the same pattern to EqualizerBar and FloatingNote.

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

In `@app/components/styled/musicVisual.js` around lines 67 - 110, The styled
components GlowRing, EqualizerBar and FloatingNote are leaking custom props
(size, duration, delay, left, bottom) to the DOM; update each styled declaration
to use Emotion's shouldForwardProp to filter these props out (e.g. styled('div',
{ shouldForwardProp: (prop) =>
!['size','duration','delay','left','bottom'].includes(prop) })) so only valid
HTML attributes are forwarded; apply the same pattern for EqualizerBar (filter
duration, delay) and FloatingNote (filter size, duration, delay, left, bottom)
to remove React console warnings.
app/containers/Library/reducer.js-67-70 (1)

67-70: ⚠️ Potential issue | 🟡 Minor

Like/unlike failure handlers reuse handleFetchFailure, which calls stopLoading — this can prematurely clear the loading flag set by an in-flight fetch.

FAILURE_LIKE_SONG and FAILURE_UNLIKE_SONG map to handleFetchFailure, which calls stopLoading(draft). Since REQUEST_LIKE_SONG / REQUEST_UNLIKE_SONG never call startLoading, the stopLoading here is a no-op under normal conditions. However, if a like/unlike failure arrives while a REQUEST_FETCH_LIBRARY is in progress, it will reset loading to false prematurely.

Consider a dedicated failure handler for like/unlike that only sets the error without touching the loading flag.

🛡️ Proposed fix
+const handleLikeUnlikeFailure = (draft, action) => {
+  setError(draft, action);
+};
+
 const handlers = {
   [libraryTypes.REQUEST_FETCH_LIBRARY]: handleFetchRequest,
   [libraryTypes.SUCCESS_FETCH_LIBRARY]: handleFetchSuccess,
   [libraryTypes.FAILURE_FETCH_LIBRARY]: handleFetchFailure,
   [libraryTypes.SUCCESS_LIKE_SONG]: handleLikeSuccess,
-  [libraryTypes.FAILURE_LIKE_SONG]: handleFetchFailure,
+  [libraryTypes.FAILURE_LIKE_SONG]: handleLikeUnlikeFailure,
   [libraryTypes.SUCCESS_UNLIKE_SONG]: handleUnlikeSuccess,
-  [libraryTypes.FAILURE_UNLIKE_SONG]: handleFetchFailure,
+  [libraryTypes.FAILURE_UNLIKE_SONG]: handleLikeUnlikeFailure,
   [libraryTypes.CLEAR_LIBRARY]: () => initialState
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/containers/Library/reducer.js` around lines 67 - 70, The failure handlers
for like/unlike reuse handleFetchFailure which calls stopLoading and can
prematurely clear the global loading flag; create a new handler (e.g.,
handleLikeUnlikeFailure) that only sets the error on the draft without calling
stopLoading, and replace the mappings for libraryTypes.FAILURE_LIKE_SONG and
libraryTypes.FAILURE_UNLIKE_SONG to use this new handler instead of
handleFetchFailure so like/unlike failures don't alter the fetch loading state.
app/components/styled/songList.js-16-31 (1)

16-31: ⚠️ Potential issue | 🟡 Minor

isActive prop leaks to the DOM, causing React warnings.

Emotion's styled forwards all props to the underlying DOM element by default. Since isActive is not a valid HTML attribute, React will log a warning. Use shouldForwardProp to filter it out.

🛡️ Example fix using shouldForwardProp
-export const SongCard = styled.div`
+export const SongCard = styled('div', {
+  shouldForwardProp: (prop) => prop !== 'isActive'
+})`
   display: flex;
   ...
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/components/styled/songList.js` around lines 16 - 31, The SongCard styled
component forwards the custom prop isActive to the DOM causing React warnings;
update the declaration of SongCard to use emotion's shouldForwardProp to filter
out isActive (so only valid HTML attributes are forwarded) while keeping the
same styling logic (background, border-left, animation using glowPulse and
colors from C). Locate SongCard in app/components/styled/songList.js and modify
the styled.div creation to add shouldForwardProp that returns false for
'isActive' (leave other props untouched) so the component behavior and styles
remain unchanged but the prop no longer leaks to the DOM.
app/components/TrackInfo/index.js-25-25 (1)

25-25: ⚠️ Potential issue | 🟡 Minor

Genre tag renders unconditionally, unlike all other tag fields.

Line 25's genre <Tag> has no guard, so if track.genre is undefined or an empty string, an empty tag still renders. This is inconsistent with the guarded durationMs, releaseDate, and trackPrice tags below it.

🐛 Proposed fix
-      {<Tag data-testid="tag-genre">{track.genre}</Tag>}
+      {track.genre && <Tag data-testid="tag-genre">{track.genre}</Tag>}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/components/TrackInfo/index.js` at line 25, The genre Tag in TrackInfo
(the JSX line that renders <Tag data-testid="tag-genre">{track.genre}</Tag>) is
rendered unconditionally and can produce an empty tag when track.genre is
undefined or empty; update the render so it only returns the Tag when
track.genre is a non-empty value (match the existing guards used for durationMs,
releaseDate, and trackPrice) — e.g., wrap the Tag render in a conditional that
checks track.genre truthiness before rendering.
app/components/styled/playerBar.js-60-79 (1)

60-79: ⚠️ Potential issue | 🟡 Minor

Dead background: none declaration — overridden by the later dynamic background on line 72.

Within the same CSS rule block the later declaration always wins, so background: none (line 61) is never applied. Remove it; the conditional on line 72 already covers the non-primary case with transparent.

🐛 Proposed fix
 export const ControlButton = styled.button`
-  background: none;
   border: none;
   color: ${C.text};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/components/styled/playerBar.js` around lines 60 - 79, The ControlButton
styled component contains a redundant "background: none" that is always
overridden by the later dynamic background rule; remove the initial "background:
none" declaration so the conditional background (p.primary ?
linear-gradient(...) : 'transparent') in ControlButton controls all cases and
avoid dead CSS. Ensure you only delete that redundant line and keep the dynamic
background, preserving other styles and the hover/active transforms.
app/components/styled/authForm.js-55-57 (1)

55-57: ⚠️ Potential issue | 🟡 Minor

Hardcoded #3d3d54 bypasses the design-token system and will break on theme switches.

The color is not present in the C token map from colors.js. Either add a dedicated token (e.g., C.borderHover) or reuse an existing one like C.border with a fallback.

🐛 Proposed fix (example using a new token)

In app/components/styled/colors.js:

 export const C = {
   ...
   border: 'var(--musica-border)',
+  borderHover: 'var(--musica-borderHover)',
   ...
 };

Then in authForm.js:

   &:hover:not(:focus) {
-    border-color: `#3d3d54`;
+    border-color: ${C.borderHover};
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/components/styled/authForm.js` around lines 55 - 57, Replace the
hardcoded color in the authForm hover rule with a design token: update the
styles in authForm.js (the &:hover:not(:focus) rule) to reference a token from
the colors export (e.g., C.borderHover or fallback to C.border) instead of
"#3d3d54"; if C.borderHover does not exist, add a new token named borderHover to
the colors.js C map and use that token in the authForm styled component so theme
switching works consistently.
app/components/TrackInfo/index.js-28-32 (1)

28-32: ⚠️ Potential issue | 🟡 Minor

track.trackPrice && (...) will render 0 as a raw text node when the price is zero.

If a track is free (trackPrice: 0), the expression short-circuits and React renders the numeric 0 as a text node rather than nothing. Guard against this with an explicit null-check instead.

🐛 Proposed fix
-      {track.trackPrice && (
+      {track.trackPrice != null && (
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/components/TrackInfo/index.js` around lines 28 - 32, The conditional
rendering currently uses "track.trackPrice && (...)" which treats 0 as falsy and
renders a raw "0"; update the guard in TrackInfo (the JSX that renders <Tag
data-testid="tag-price">) to explicitly check for null/undefined (e.g., use
track.trackPrice != null) so a price of 0 will render correctly while still
skipping when trackPrice is null/undefined.
app/containers/Auth/tests/reducer.test.js-45-53 (1)

45-53: ⚠️ Potential issue | 🟡 Minor

FAILURE_AUTH test relies on a hardcoded default error value rather than explicitly passing it.

The setError utility (in app/utils/reducer.js) defaults the error to 'something_went_wrong' when action[PAYLOAD.ERROR] is undefined. The test dispatches { type: authTypes.FAILURE_AUTH } without providing the error field, making it pass only because of this internal default. For better test clarity and explicit dependencies, pass the error in the action:

Suggested fix
-  expect(authReducer(state, { type: authTypes.FAILURE_AUTH })).toEqual(
+  expect(authReducer(state, { type: authTypes.FAILURE_AUTH, [PAYLOAD.ERROR]: 'something_went_wrong' })).toEqual(
     expectedResult
   )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/containers/Auth/tests/reducer.test.js` around lines 45 - 53, The test for
FAILURE_AUTH currently relies on reducer defaulting the error via setError;
update the spec to dispatch the error explicitly by passing
action[PAYLOAD.ERROR] when calling authReducer so it no longer depends on the
internal default; specifically modify the test that calls authReducer(state, {
type: authTypes.FAILURE_AUTH }) to include the error field (match the
expectedResult where [PAYLOAD.ERROR] is 'something_went_wrong') so the test
asserts behavior of authReducer/authTypes.FAILURE_AUTH directly rather than
relying on setError's default.
app/containers/Music/saga.js-13-15 (1)

13-15: ⚠️ Potential issue | 🟡 Minor

response.data is null for network/timeout errors — error context is lost.

With apisauce, when a request fails due to network issues or timeouts, response.ok is false but response.data is null; the actual problem string is in response.problem. Dispatching null as the error leaves the UI with no actionable message.

🛠️ Proposed fix
- yield put(failureSearchSongs(response.data));
+ yield put(failureSearchSongs(response.data ?? response.problem));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/containers/Music/saga.js` around lines 13 - 15, The failure branch is
dispatching response.data which can be null on network/timeouts; update the
error passed to failureSearchSongs to use response.data if present else fall
back to response.problem (or a composed message including response.problem) so
UI gets actionable error context; change the dispatch in the saga where yield
put(failureSearchSongs(response.data)) to pass response.data || response.problem
(or a descriptive string/object) and ensure the saga importing/using
failureSearchSongs handles that shape.
app/services/musicApi.js-7-7 (1)

7-7: ⚠️ Potential issue | 🟡 Minor

fetchTrackDetails silently constructs a malformed URL when trackId is undefined or null.

/music/resources/songs/undefined will result in a 404 or unexpected data. Callers in the saga extract trackId via action[TRACK_DETAIL_PAYLOAD.TRACK_ID] with no prior guard, so a misconfigured dispatch will reach here undetected.

🛡️ Proposed guard
-export const fetchTrackDetails = (trackId) => musicApi.get(`/music/resources/songs/${trackId}`);
+export const fetchTrackDetails = (trackId) => {
+  if (!trackId) return Promise.resolve({ ok: false, data: null, problem: 'INVALID_TRACKID' });
+  return musicApi.get(`/music/resources/songs/${trackId}`);
+};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/services/musicApi.js` at line 7, fetchTrackDetails currently builds a URL
even when trackId is undefined/null, producing e.g.
/music/resources/songs/undefined; update fetchTrackDetails to validate the
trackId argument (in function fetchTrackDetails) before calling musicApi.get: if
trackId is missing or not a valid non-empty string/number, either throw a clear
Error or return a rejected Promise (so callers/sagas can handle it) and include
a descriptive message; ensure you call musicApi.get only when trackId is valid
to avoid malformed requests.
app/containers/TrackDetail/index.js-32-36 (1)

32-36: ⚠️ Potential issue | 🟡 Minor

Guard against non-numeric trackId values.

Number(trackId) silently produces NaN for non-numeric query strings (e.g., /track/abc). The if (trackId) guard prevents the undefined case but not invalid strings.

🛡️ Proposed defensive check
   useEffect(() => {
-    if (trackId) {
-      dispatchFetchTrack(Number(trackId));
+    const id = Number(trackId);
+    if (trackId && !Number.isNaN(id)) {
+      dispatchFetchTrack(id);
     }
   }, [trackId]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/containers/TrackDetail/index.js` around lines 32 - 36, The useEffect
currently calls dispatchFetchTrack(Number(trackId)) which turns non-numeric IDs
into NaN; update the effect to validate trackId before dispatching:
parse/convert trackId (e.g., Number or parseInt) and check with
Number.isFinite/!isNaN or a /^\d+$/ test, return early if invalid, and only call
dispatchFetchTrack with a valid numeric id (reference the useEffect closure and
the dispatchFetchTrack call in TrackDetail/index.js).
app/containers/Music/index.js-45-48 (1)

45-48: ⚠️ Potential issue | 🟡 Minor

Debounced function is never cancelled on unmount.

If the component unmounts within the debounce window, the pending dispatchSearch will still fire. Use a useEffect cleanup to cancel the debounced function.

Proposed fix
  const debouncedSearch = useCallback(
    debounce((term) => dispatchSearch(term), SEARCH_DEBOUNCE_MS),
    []
  );
+
+ useEffect(() => {
+   return () => debouncedSearch.cancel();
+ }, [debouncedSearch]);
+
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/containers/Music/index.js` around lines 45 - 48, The debounced function
debouncedSearch created with debounce(...) can fire after unmount; add a
useEffect that returns a cleanup which calls debouncedSearch.cancel() to prevent
the pending dispatchSearch from running on unmount. Locate the debouncedSearch
created with useCallback/debounce (and the SEARCH_DEBOUNCE_MS constant and
dispatchSearch reference) and add a cleanup useEffect (importing useEffect if
needed) that calls debouncedSearch.cancel() when the component unmounts. Ensure
the effect depends on debouncedSearch so the correct instance is cancelled.

Comment on lines +5 to +10
export const getAudioInstance = () => {
if (!audioInstance) {
audioInstance = new Audio();
audioInstance.volume = DEFAULT_VOLUME;
}
return audioInstance;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# Find the audioSingleton.js file and examine its contents
find . -name "audioSingleton.js" -type f 2>/dev/null | head -5

Repository: wednesday-solutions/nextjs-template

Length of output: 125


🏁 Script executed:

# Search for all calls to getAudioInstance to see the usage patterns
rg -n "getAudioInstance" --type js --type jsx --type ts --type tsx -A 3 -B 3 2>/dev/null | head -100

Repository: wednesday-solutions/nextjs-template

Length of output: 61


🏁 Script executed:

# Check if this is a Next.js project
ls -la | grep -E "next.config|package.json" && head -30 package.json 2>/dev/null

Repository: wednesday-solutions/nextjs-template

Length of output: 1411


🏁 Script executed:

# Read the audioSingleton.js file
cat -n ./app/components/AudioPlayer/audioSingleton.js

Repository: wednesday-solutions/nextjs-template

Length of output: 400


🏁 Script executed:

# Search more broadly for getAudioInstance, audioInstance, or AudioPlayer imports
rg "getAudioInstance|audioInstance|audioSingleton" --type js --type jsx --type ts --type tsx 2>/dev/null

Repository: wednesday-solutions/nextjs-template

Length of output: 61


🏁 Script executed:

# Check what's in the AudioPlayer directory
ls -la ./app/components/AudioPlayer/

Repository: wednesday-solutions/nextjs-template

Length of output: 573


🏁 Script executed:

# Check useAudioSetup.js
cat -n ./app/components/AudioPlayer/useAudioSetup.js

Repository: wednesday-solutions/nextjs-template

Length of output: 1520


🏁 Script executed:

# Check useAudioPlayer.js
cat -n ./app/components/AudioPlayer/useAudioPlayer.js

Repository: wednesday-solutions/nextjs-template

Length of output: 1983


🏁 Script executed:

# Check the main index.js component
cat -n ./app/components/AudioPlayer/index.js

Repository: wednesday-solutions/nextjs-template

Length of output: 3369


🏁 Script executed:

# Search for dynamic imports or 'use client' directives
rg "dynamic|'use client'|\"use client\"|ssr: false" app/components/AudioPlayer --type js --type jsx

Repository: wednesday-solutions/nextjs-template

Length of output: 106


🏁 Script executed:

# Check if AudioPlayer is imported dynamically anywhere
rg "AudioPlayer|audioSingleton" --type js --type jsx -B 2 | head -50

Repository: wednesday-solutions/nextjs-template

Length of output: 106


🏁 Script executed:

# Search for dynamic imports and 'use client' with correct file types
rg "dynamic|use client|ssr:" app/components/AudioPlayer --type js -B 2

Repository: wednesday-solutions/nextjs-template

Length of output: 61


🏁 Script executed:

# Broader search for AudioPlayer imports in the codebase
rg "AudioPlayer|from.*audioSingleton" --type js -B 1 -A 1

Repository: wednesday-solutions/nextjs-template

Length of output: 8269


getAudioInstance() triggers ReferenceError: Audio is not defined during SSR — the proposed fix is incomplete.

Audio is a browser-only Web API. Next.js renders components server-side, and getAudioInstance() is called at render time (line 5 of useAudioSetup), outside any useEffect. This means AudioPlayer will execute on the server whenever rendered in TrackDetail, Library, or Music pages.

The proposed fix (if (typeof window === 'undefined') return null;) prevents the immediate error but introduces a secondary failure: useAudioSetup immediately accesses properties on the result without null checks (lines 7–10: audio.paused, audio.currentTime, audio.duration, audio.volume), causing Cannot read property 'paused' of null.

A complete fix requires one of these approaches:

  1. Move the call inside useEffect in useAudioSetup — this defers initialization to client-side only.
  2. Guard the function AND add null handling — return null from getAudioInstance() and guard all property accesses in useAudioSetup (and any other callers).
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/components/AudioPlayer/audioSingleton.js` around lines 5 - 10,
getAudioInstance currently calls the browser-only Audio constructor during
render causing SSR crashes; move the client-only initialization into a useEffect
inside useAudioSetup so getAudioInstance (and the Audio constructor) is only
invoked on the client, or if you prefer to keep a synchronous getter, make
getAudioInstance return null when window is undefined and update useAudioSetup
to guard all accesses to audio.paused/currentTime/duration/volume (and any other
callers) against null; reference getAudioInstance, useAudioSetup, audioInstance
and the Audio constructor when making the change.

Comment on lines +21 to +25
audioRef.current.src = song.previewUrl;
audioRef.current.play().catch(() => {});
setIsPlaying(true);
onPlayStateChange?.(true);
}, [song?.trackId]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

play() rejection silently leaves isPlaying stuck at true.

audioRef.current.play().catch(() => {}) is async. The subsequent setIsPlaying(true) and onPlayStateChange?.(true) fire synchronously regardless of whether play() resolves or rejects. Under browser autoplay restrictions (Chrome/Safari require a prior user gesture), play() will reject and the UI will show a "pause" state while no audio plays.

🐛 Proposed fix — move state updates into .then()
-    audioRef.current.src = song.previewUrl;
-    audioRef.current.play().catch(() => {});
-    setIsPlaying(true);
-    onPlayStateChange?.(true);
+    audioRef.current.src = song.previewUrl;
+    audioRef.current.play()
+      .then(() => {
+        setIsPlaying(true);
+        onPlayStateChange?.(true);
+      })
+      .catch(() => {
+        setIsPlaying(false);
+        onPlayStateChange?.(false);
+      });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
audioRef.current.src = song.previewUrl;
audioRef.current.play().catch(() => {});
setIsPlaying(true);
onPlayStateChange?.(true);
}, [song?.trackId]);
audioRef.current.src = song.previewUrl;
audioRef.current.play()
.then(() => {
setIsPlaying(true);
onPlayStateChange?.(true);
})
.catch(() => {
setIsPlaying(false);
onPlayStateChange?.(false);
});
}, [song?.trackId]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/components/AudioPlayer/useAudioPlayer.js` around lines 21 - 25, The
play() promise is handled incorrectly causing isPlaying to be set true even if
playback is blocked; change the logic in the start/play handler that uses
audioRef.current.play() so you call setIsPlaying(true) and
onPlayStateChange(true) inside the play() .then() callback (and only after
audioRef.current.play() resolves), and handle .catch() to set
setIsPlaying(false) and call onPlayStateChange(false) (optionally logging the
error); ensure you still guard audioRef.current exists before calling play() and
clear any previous state on rejection.

Comment on lines +32 to +36
const next = !isPlaying;
isPlaying ? audioRef.current.pause() : audioRef.current.play().catch(() => {});

setIsPlaying(next);
onPlayStateChange?.(next);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Same unconditional state update issue in togglePlay.

When transitioning from paused → playing, setIsPlaying(next) and onPlayStateChange?.(next) are called synchronously before the play() Promise settles. If play() rejects, state is still set to true.

🐛 Proposed fix
-    const next = !isPlaying;
-    isPlaying ? audioRef.current.pause() : audioRef.current.play().catch(() => {});
-
-    setIsPlaying(next);
-    onPlayStateChange?.(next);
+    if (isPlaying) {
+      audioRef.current.pause();
+      setIsPlaying(false);
+      onPlayStateChange?.(false);
+    } else {
+      audioRef.current.play()
+        .then(() => {
+          setIsPlaying(true);
+          onPlayStateChange?.(true);
+        })
+        .catch(() => {});
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const next = !isPlaying;
isPlaying ? audioRef.current.pause() : audioRef.current.play().catch(() => {});
setIsPlaying(next);
onPlayStateChange?.(next);
if (isPlaying) {
audioRef.current.pause();
setIsPlaying(false);
onPlayStateChange?.(false);
} else {
audioRef.current.play()
.then(() => {
setIsPlaying(true);
onPlayStateChange?.(true);
})
.catch(() => {});
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/components/AudioPlayer/useAudioPlayer.js` around lines 32 - 36, The
togglePlay function updates isPlaying and calls onPlayStateChange immediately
before the audioRef.current.play() Promise resolves, risking a true state when
play() rejects; change togglePlay to call audioRef.current.play() and only
setIsPlaying(next) and invoke onPlayStateChange(next) inside the play() Promise
resolution (or after awaiting it), and conversely perform pause() and state
update synchronously for pauses, while catching and handling play() rejection to
leave state unchanged and optionally log or surface the error; update references
in togglePlay to use audioRef, setIsPlaying, and onPlayStateChange accordingly.

Comment on lines +12 to +30
useEffect(() => {
const onTime = () => setCurrentTime(audio.currentTime);
const onLoaded = () => setDuration(audio.duration);
const onEnded = () => {
setIsPlaying(false);
onPlayStateChange?.(false);
onSongEnd?.();
};

audio.addEventListener('timeupdate', onTime);
audio.addEventListener('loadedmetadata', onLoaded);
audio.addEventListener('ended', onEnded);

return () => {
audio.removeEventListener('timeupdate', onTime);
audio.removeEventListener('loadedmetadata', onLoaded);
audio.removeEventListener('ended', onEnded);
};
}, []);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Stale closure: onSongEnd and onPlayStateChange are never refreshed after mount.

The useEffect runs once ([] deps) and the onEnded closure captures the onSongEnd/onPlayStateChange values present at mount. If the parent re-renders with new callback references, the handler will still invoke the original stale functions.

Use refs to keep callbacks current without re-registering listeners:

🔒 Proposed fix
+  const onSongEndRef = useRef(onSongEnd);
+  const onPlayStateChangeRef = useRef(onPlayStateChange);
+  useEffect(() => {
+    onSongEndRef.current = onSongEnd;
+    onPlayStateChangeRef.current = onPlayStateChange;
+  });

   useEffect(() => {
     const onEnded = () => {
       setIsPlaying(false);
-      onPlayStateChange?.(false);
-      onSongEnd?.();
+      onPlayStateChangeRef.current?.(false);
+      onSongEndRef.current?.();
     };
     // ...
   }, []);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/components/AudioPlayer/useAudioSetup.js` around lines 12 - 30, The effect
registers audio event handlers once but onEnded captures stale onSongEnd and
onPlayStateChange; fix by creating refs (e.g., onSongEndRef,
onPlayStateChangeRef) and update them inside the hook when props change, then
have onEnded/readers call the current ref values instead of the closed-over
props; keep the event listener registration in useEffect with empty deps but
reference ref.current inside onTime/onLoaded/onEnded so callbacks stay
up-to-date without re-attaching listeners.

Comment on lines +17 to +27
export function* handleLogin(action) {
const { email, password } = action;
const response = yield call(loginUser, { email, password });
if (response.ok) {
yield put(successAuth(response.data));
persistToken(response.data);
Router.push('/');
} else {
yield put(failureAuth(response.data));
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Missing try/catch — network errors will crash the saga.

If loginUser throws (network timeout, DNS failure, etc.), the saga generator will propagate the exception unhandled. No failureAuth will be dispatched, leaving the UI in a perpetual loading state. The same applies to handleSignup.

🐛 Proposed fix for handleLogin (apply similarly to handleSignup)
 export function* handleLogin(action) {
+  try {
     const { email, password } = action;
     const response = yield call(loginUser, { email, password });
     if (response.ok) {
       yield put(successAuth(response.data));
       persistToken(response.data);
       Router.push('/');
     } else {
-      yield put(failureAuth(response.data));
+      yield put(failureAuth(response.data?.message || 'Login failed'));
     }
+  } catch (err) {
+    yield put(failureAuth(err.message || 'An unexpected error occurred'));
+  }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/containers/Auth/saga.js` around lines 17 - 27, handleLogin (and similarly
handleSignup) must be wrapped in a try/catch so thrown errors from the call to
loginUser don't crash the saga; update handleLogin to try { const response =
yield call(loginUser,...); if (response.ok) { yield
put(successAuth(response.data)); persistToken(response.data); Router.push('/');
} else { yield put(failureAuth(response.data)); } } catch (err) { yield
put(failureAuth(err)); } — ensure you reference the generator function names
handleLogin and handleSignup, call(loginUser) / call(signupUser), and dispatch
failureAuth with the caught error so the UI stops loading on network failures.

Comment on lines +2 to +3
NEXT_PUBLIC_POSTHOG_KEY=phc_s5dpMW7DvBDzwFpo0dWrj59N2iFi6aj59yn4bW8gmP8
NEXT_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com No newline at end of file

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Same live PostHog key exposed in production env file

Same issues as .env.qa — the production key is hardcoded in git history and is identical to the development and QA keys. The production key is the highest-risk instance: once it's in git history it can be read by anyone with repo access. Rotate the key from the PostHog dashboard and replace the committed value with a placeholder injected via CI/CD secrets.

🧰 Tools
🪛 dotenv-linter (4.0.0)

[warning] 3-3: [EndingBlankLine] No blank line at the end of the file

(EndingBlankLine)


[warning] 3-3: [UnorderedKey] The NEXT_PUBLIC_POSTHOG_HOST key should go before the NEXT_PUBLIC_POSTHOG_KEY key

(UnorderedKey)

🪛 Gitleaks (8.30.0)

[high] 2-2: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

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

In `@environments/.env.production` around lines 2 - 3, The committed production
PostHog key (NEXT_PUBLIC_POSTHOG_KEY) must be rotated and removed from the repo;
generate a new key in the PostHog dashboard, revoke the exposed key, and replace
the hardcoded value in environments/.env.production with a CI-injected
placeholder (e.g., NEXT_PUBLIC_POSTHOG_KEY=${POSTHOG_KEY}) so the real secret
comes from pipeline/secret store; ensure NEXT_PUBLIC_POSTHOG_HOST remains
configurable but not paired with an exposed secret, and update CI/CD to inject
POSTHOG_KEY from your secret manager before deployment.

Comment thread environments/.env.qa
Comment on lines +2 to +3
NEXT_PUBLIC_POSTHOG_KEY=phc_s5dpMW7DvBDzwFpo0dWrj59N2iFi6aj59yn4bW8gmP8
NEXT_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com No newline at end of file

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Real PostHog key committed to version control and identical across all environments

Two compounding problems here:

  1. Git history exposure: Committing a live phc_ key directly embeds it in git history permanently — even if replaced later, the key remains accessible via git log. This is why Gitleaks flags it as HIGH severity. The committed files should use placeholder values (e.g., NEXT_PUBLIC_POSTHOG_KEY=), with real values injected at build/deploy time via CI/CD secrets.

  2. No environment isolation: The identical key phc_s5dpMW7DvBDzwFpo0dWrj59N2iFi6aj59yn4bW8gmP8 appears in .env.development, .env.qa, and .env.production. This means development and QA test events land in the same PostHog project as production user data, polluting your analytics. Each environment should have its own PostHog project key.

🔐 Suggested approach
-NEXT_PUBLIC_POSTHOG_KEY=phc_s5dpMW7DvBDzwFpo0dWrj59N2iFi6aj59yn4bW8gmP8
-NEXT_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
+NEXT_PUBLIC_POSTHOG_KEY=
+NEXT_PUBLIC_POSTHOG_HOST=

Inject the actual per-environment values via CI/CD secrets (e.g., GitHub Actions secrets, Vercel environment variables). Consider also rotating the currently exposed key from the PostHog dashboard.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
NEXT_PUBLIC_POSTHOG_KEY=phc_s5dpMW7DvBDzwFpo0dWrj59N2iFi6aj59yn4bW8gmP8
NEXT_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
NEXT_PUBLIC_POSTHOG_KEY=
NEXT_PUBLIC_POSTHOG_HOST=
🧰 Tools
🪛 dotenv-linter (4.0.0)

[warning] 3-3: [EndingBlankLine] No blank line at the end of the file

(EndingBlankLine)


[warning] 3-3: [UnorderedKey] The NEXT_PUBLIC_POSTHOG_HOST key should go before the NEXT_PUBLIC_POSTHOG_KEY key

(UnorderedKey)

🪛 Gitleaks (8.30.0)

[high] 2-2: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

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

In `@environments/.env.qa` around lines 2 - 3, Replace the committed real PostHog
key in the env files by removing the live value for NEXT_PUBLIC_POSTHOG_KEY and
substituting a placeholder (e.g., NEXT_PUBLIC_POSTHOG_KEY=) in all environment
files so secrets are not stored in git; configure your CI/CD (or hosting
platform) to inject the real per-environment keys via environment variables or
secret management and ensure NEXT_PUBLIC_POSTHOG_HOST remains configurable per
environment if needed; rotate the exposed phc_ key immediately in the PostHog
dashboard and update secrets so each environment (development, QA, production)
uses its own PostHog project key to avoid cross-environment data mixing.

Comment thread next.config.js
Comment on lines +31 to +33
{ source: '/music/:path*', destination: 'http://localhost:9000/music/:path*' },
{ source: '/login', destination: 'http://localhost:9000/login' },
{ source: '/signup', destination: 'http://localhost:9000/signup' }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Hardcoded localhost:9000 will break in every non-local environment.

All three rewrite destinations reference http://localhost:9000, making this configuration environment-specific. It must be driven by an environment variable so staging and production deployments can point to the correct backend.

🛠️ Proposed fix
+    async rewrites() {
+      const apiBase = process.env.API_BASE_URL || 'http://localhost:9000';
       return [
-        { source: '/music/:path*', destination: 'http://localhost:9000/music/:path*' },
-        { source: '/login', destination: 'http://localhost:9000/login' },
-        { source: '/signup', destination: 'http://localhost:9000/signup' }
+        { source: '/music/:path*', destination: `${apiBase}/music/:path*` },
+        { source: '/login', destination: `${apiBase}/login` },
+        { source: '/signup', destination: `${apiBase}/signup` }
       ];
     },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@next.config.js` around lines 31 - 33, The rewrite destinations in
next.config.js currently hardcode 'http://localhost:9000' for the routes (the
rewrite entries for '/music/:path*', '/login', '/signup'), which will fail
outside local env; change the destinations to build from an environment variable
(e.g. process.env.BACKEND_URL or NEXT_PUBLIC_BACKEND_URL) with a sensible
fallback, and use that variable when constructing the destination strings for
the rewrite objects so staging/production can point to the correct backend;
update the rewrite entries that reference 'http://localhost:9000' to use the env
var (ensuring the value includes scheme and port) and document that the variable
must be set in deployment envs.

Comment thread next.config.js
Comment on lines +32 to +33
{ source: '/login', destination: 'http://localhost:9000/login' },
{ source: '/signup', destination: 'http://localhost:9000/signup' }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check whether Next.js page files exist at /login and /signup
fd -t f 'login\.(js|jsx|ts|tsx)' pages/
fd -t f 'signup\.(js|jsx|ts|tsx)' pages/

Repository: wednesday-solutions/nextjs-template

Length of output: 109


🏁 Script executed:

cat -n next.config.js

Repository: wednesday-solutions/nextjs-template

Length of output: 2055


The /login and /signup rewrites are dead code and will be shadowed by existing page files.

Pages pages/login.js and pages/signup.js exist in the repository. Since rewrites() returns a plain array (treated as "afterFiles"), Next.js checks the filesystem first. Requests to /login and /signup will be served by the matching page files, and these rewrite rules will never be consulted.

Either use the beforeFiles configuration to prioritize rewrites over pages, remove the page files if proxying is the intent, or move login/signup logic to API routes (pages/api/login.js, pages/api/signup.js) if these are API endpoints.

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

In `@next.config.js` around lines 32 - 33, The rewrite entries for { source:
'/login', destination: 'http://localhost:9000/login' } and { source: '/signup',
destination: 'http://localhost:9000/signup' } are dead because Next.js serves
pages/login.js and pages/signup.js first; update rewrites() to place these rules
in the beforeFiles phase so they take priority (i.e. move those two objects into
a beforeFiles array returned by rewrites()), or alternatively remove the page
files pages/login.js and pages/signup.js if you intend to proxy those paths, or
convert the endpoints to API routes (pages/api/login.js and pages/api/signup.js)
if they are backend handlers. Ensure you modify the rewrites() function in
next.config.js and keep the unique source/destination objects unchanged when
moving them to beforeFiles.

Comment thread package.json
"npm run lint:eslint:fix",
"git add --force",
"jest --findRelatedTests --passWithNoTests $STAGED_FILES"
"jest --findRelatedTests -u --passWithNoTests $STAGED_FILES"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

-u (--updateSnapshot) in a pre-commit hook silently masks regressions

The -u flag instructs Jest to automatically overwrite all mismatched snapshots. Running this unconditionally on every commit means:

  • Any accidental rendering regression is silently committed as the new "expected" output instead of failing the hook and alerting the developer.
  • Developers receive no signal that snapshots changed — the hook rewrites and stages them transparently.
  • The entire value of snapshot testing (catching unintentional UI diffs) is negated at the point where it matters most.

The flag is appropriate when a developer intentionally updates a component and wants to refresh its snapshot (jest -u run manually), not as a blanket pre-commit behaviour.

🛠️ Proposed fix
-      "jest --findRelatedTests -u --passWithNoTests $STAGED_FILES"
+      "jest --findRelatedTests --passWithNoTests $STAGED_FILES"

If the motivation was to avoid pre-commit failures for newly created snapshot files, the correct workflow is to commit the initial snapshot file explicitly after the first local test run, rather than auto-updating on every commit.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"jest --findRelatedTests -u --passWithNoTests $STAGED_FILES"
"jest --findRelatedTests --passWithNoTests $STAGED_FILES"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@package.json` at line 34, The pre-commit Jest command currently includes the
auto-update flag "-u" ("jest --findRelatedTests -u --passWithNoTests
$STAGED_FILES") which silently overwrites failing snapshots; remove the "-u"
flag so the hook runs "jest --findRelatedTests --passWithNoTests $STAGED_FILES"
and fails on snapshot mismatches, and instruct developers to run "jest -u"
manually when they intentionally want to update snapshots (or commit initial
snapshot files after the first local test run) to avoid masking regressions.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant