refactor: overhaul RecentSongs store architecture and improve Docker dev scripts (#87)#88
refactor: overhaul RecentSongs store architecture and improve Docker dev scripts (#87)#88tomast1337 wants to merge 2 commits intodevelopfrom
Conversation
…nd MinIO services, and update npm scripts for improved Docker management.
There was a problem hiding this comment.
Pull request overview
This PR refactors the frontend RecentSongs state management to use a per-tree Context-backed Zustand store, aiming to fix hydration flashes and pagination desync/race issues (issue #87), and also updates the local Docker Compose workflow to support compose up --wait with MinIO initialization.
Changes:
- Replaced the global
RecentSongsZustand store with a Context-per-tree store, adding explicit actions for pagination, category selection, and category fetching. - Updated the browse home page components to consume the new
recentItemsfeed model (song/ad/loading) and to explicitly trigger category loading. - Added Docker Compose healthchecks and new root scripts to support
docker compose up --waitplus a one-shot MinIO bucket/CORS initializer profile.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| package.json | Adds Docker helper scripts for compose up/down/reset and MinIO init. |
| docker-compose.yml | Adds healthchecks and introduces a profile-based one-shot mc init service. |
| apps/frontend/src/modules/browse/components/client/context/RecentSongs.context.tsx | Replaces singleton store with Context store; introduces feed item model + abort-based concurrency. |
| apps/frontend/src/modules/browse/components/client/context/HomePage.context.tsx | Simplifies provider composition; removes legacy combined hook. |
| apps/frontend/src/modules/browse/components/client/CategoryButton.tsx | Switches to selector-based store access and explicitly fetches categories. |
| apps/frontend/src/modules/browse/components/HomePageComponent.tsx | Renders the new recentItems feed model and removes implicit loader hooks. |
| apps/frontend/src/app/(content)/page.tsx | Adjusts initial recent-song fetch limit. |
Comments suppressed due to low confidence (1)
docker-compose.yml:85
- The
mcservice definesentrypoint: ['/bin/sh','-c']but thecommandstring also starts with-c '...'. That results in the container running/bin/sh -c "-c '...'", which typically tries to execute a command literally named-cand will fail, preventing MinIO bucket/CORS initialization. Remove the extra leading-cfromcommand(or change the entrypoint to just/bin/sh) so the init script actually runs.
entrypoint: ['/bin/sh', '-c']
depends_on:
minio:
condition: service_healthy
environment:
- MINIO_ROOT_USER=minioadmin
- MINIO_ROOT_PASSWORD=minioadmin
command: >
-c '
while ! mc alias set minio http://minio:9000 minioadmin minioadmin; do
echo "Waiting for MinIO to be available..."
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
… pagination - Migrate singleton Zustand store to React Context to allow synchronous SSR hydration and prevent initial render flashes. - Fix pagination desynchronization by incrementing the page state only after a successful network response. - Remove redundant ID-based concurrency checks, relying strictly on AbortController for deterministic network cancellation. - Delete implicit `useRecentSongsCategoriesLoader` side-effect hook in favor of explicit component-level data fetching. Fixes #87
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const didLoad = await fetchRecentSongs(nextPage); | ||
| if (!didLoad) { | ||
| set((state) => ({ | ||
| recentItems: state.recentItems.filter( | ||
| (item) => item.type !== 'loading', | ||
| ), | ||
| })); |
There was a problem hiding this comment.
increasePageRecent treats any fetchRecentSongs failure the same, but fetchRecentSongs returns false both for real errors and for intentional aborts. If a user changes category while a “load more” request is in-flight, the aborted increasePageRecent path can run this cleanup and strip the new category’s loading placeholders (and potentially cause a brief empty/flash state). Consider distinguishing aborted vs failed (e.g., return a status enum or throw on abort) and/or only removing the specific placeholders appended by this call (track count/requestId and validate selectedCategory/page before mutating state).
Copilot is making a valid architectural point. Mixing infrastructure changes (Docker
This PR primarily restructures the
RecentSongsstate management to prioritize predictable data flow and resolve structural bugs related to React hydration and concurrent network requests. It moves away from a global singleton store to a Context-per-tree architecture to eliminate race conditions at the root.Additionally, this PR includes developer experience (DX) improvements for the local Docker environment.
Closes #87.
Key Changes
Frontend Architecture (RecentSongs):
create()store with aRecentSongsContext. The store is now instantiated per-tree and hydrated synchronously withinitialRecentSongs. This eliminates theuseEffectinitialization flash and hydration mismatches.pagecounter would eagerly increment before the fetch completed. The page state is now treated strictly: it only updates upon a successful network response.AbortController, seamlessly short-circuiting stale requests.useRecentSongsCategoriesLoaderauto-loader hook. Category fetching is now an explicit action invoked by consuming components.Infrastructure & DX:
package.jsonscripts (docker:up,docker:down,docker:reset,docker:reset:fresh) to streamline local environment setup.docker:minio-initdirectly into the startup sequence to ensure storage buckets are provisioned automatically.