Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
1ee6afe
docs: refresh the README and wiki with newly recorded media
paramcodes Oct 17, 2026
a1d0d87
docs: refresh wiki with concept guides and current-feature accuracy
paramcodes Oct 17, 2026
0b01366
feat(core): persist library and likes via repo seam
paramcodes Oct 17, 2026
cf324d6
feat(player): phase 11 volume, shuffle, repeat, and shortcuts
paramcodes Oct 17, 2026
c6ca1bb
feat(search): phase 12 catalog search, tabs, and detail routes
paramcodes Oct 17, 2026
a1abc95
feat(views): phase 13 expanded now-playing, mobile sheet, and lyrics
paramcodes Oct 17, 2026
a28fb83
merge: Phase 11 player controls
paramcodes Oct 17, 2026
78b85fa
feat(search): merge Phase 12 search and discovery
paramcodes Oct 17, 2026
0d8743d
feat(views): merge Phase 13 now-playing, mobile sheet, and lyrics
paramcodes Oct 17, 2026
9ebedef
docs(roadmap): mark Phases 11, 12, and 13 as complete
paramcodes Oct 17, 2026
55b63e4
docs: document player controls, catalog search, and now-playing views
paramcodes Oct 18, 2026
1181195
docs(readme): refresh media artifacts and showcase all features
paramcodes Oct 18, 2026
1d49feb
feat(seo): restore opengraph and twitter image endpoints
paramcodes Oct 18, 2026
e700046
merge: reconcile origin/main with post-mvp features
paramcodes Oct 18, 2026
85d84a7
fix(pages): clean up client-split artifacts from merge
paramcodes Oct 18, 2026
9f01dff
docs(verify): merge PR 11 verification map updates
paramcodes Oct 18, 2026
ff8eb2a
chore: remove disallowed comments across src for CI check
paramcodes Oct 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,4 @@ todo.md

# agent worktrees
.worktrees/
playwright.tmp.config.ts
51 changes: 27 additions & 24 deletions .opencode/skills/verify-sunao/features/player-bar.md
Original file line number Diff line number Diff line change
@@ -1,35 +1,38 @@
# Player bar

The persistent bottom bar: current song, play/pause, previous/next, seek, speed, full screen, and like.
The bottom player bar shows the current song, play/pause, prev/next, seek, speed, shuffle, repeat, volume, and like.
It starts **idle** ("Nothing playing", transport disabled) until a track is played.

## Sub-features
- `player-playpause` — toggles playback; shows a spinner while loading.
- `player-skip` — previous / next track; auto-advance when a track ends.
- `player-seek` — scrubber bound to the audio element's real duration.
- `player-speed` — 0.5x, 1x, 1.25x, 1.5x, 2x.
- `player-like` — session-only liked toggle with a toast.
- `player-idle` shows "Nothing playing" with disabled transport before any playback.
- `player-playpause` toggles playback.
- `player-seek` drags the desktop scrubber.
- `player-mobile-seek` drags the thin bar at the top of the bar on mobile.
- `player-speed` changes playback rate.
- `player-shuffle` context-aware queue shuffle.
- `player-repeat` 3-state repeat mode (off/all/one).
- `player-volume` slider and mute toggle.
- `player-like` toggles liked state.

## How to get to it (user POV)
- Open `http://localhost:3000/`. The bar is pinned to the bottom of every in-app screen.
- Open `http://localhost:3000/`. The bar is at the bottom, idle.

## Driving it with Playwright
Preconditions
- Dev server healthy, viewport >= `md` for the seek bar and speed control.
- Audio output is irrelevant: assert DOM/ARIA state, never sound. A headless `audio.play()` may be rejected, but the store's `isPlaying` still flips.
Preconditions:
- sunao dev server healthy at `http://localhost:3000`.

- **Initial state.** Scoped to `[data-slot="player-bar"]`, the bar shows `Come Together` / `The Beatles` and the primary button is named `Play` (loaded but paused). There is no empty-bar state.
- **Start playback.** Click a song row in `main`; the bar's text contains the title and artist and the primary button becomes `Pause`.
- **Skip.** `Previous track` and `Next track` are enabled; `Next track` moves the bar to the next queued song.
- **Seek.** `getByRole("slider", { name: "Seek" })` (scoped to the bar): focus it and press `ArrowRight`; `aria-valuenow` increases and the elapsed label moves off `0:00`.
- **Speed.** Hover or focus the `1x` button; the menu (plain buttons in the same group) becomes visible. Click `1.5x`; the trigger label becomes `1.5x`.
- **Like.** The heart's name is `Unlike` when the current song is liked, `Like` otherwise. Click it; the name flips and a sonner toast appears (`Saved to Liked Songs`, or `Removed from Liked Songs` with an `Undo` action).
- **Keyboard.** Press `Space` (with focus outside an input); the play/pause name flips.
- **Proof.** Screenshots to `artifacts/verify-player-bar/`: bar in the playing state, after liking.
- **Idle.** On a fresh `/`, the bar shows "Nothing playing" and the Play button is `disabled`.
Pressing Space must not change that.
- **Start playing.** Click a song row (e.g. "Come Together") → the button becomes enabled Pause.
- **Play/pause.** Click the play button. The icon switches to pause.
- **Seek.** Drag the desktop range input. Position updates.
- **Mobile seek.** At a mobile viewport, drag `[data-slot="mobile-seek"] [data-slot="slider-thumb"]`;
`aria-valuenow` increases.
- **Speed.** Use `[data-slot="player-bar"] div.group button` first, then dispatch a click on the rate.
- **Like.** Click the heart. It fills (appears only once a song is active).

## Gotchas
- Audio is a real 30-second iTunes preview fetched per title + artist, cached, falling back to the bundled `public/audio/sample.mp3` sample. It is not a synthesized tone. Do not assert sound.
- Songs 1, 3, and 5 are pre-liked, and the bar's song on first load is id 1, so the heart starts as `Unlike`. Assert whichever flip you actually observe.
- The buffering spinner only shows when `isBuffering && isPlaying`, delayed 200ms; do not assert its presence.
- The bar always renders a song (it falls back to the first song), so it never shows a "Nothing playing" placeholder.
- `like` is in-memory: a reload resets it, and it is not connected to the `Liked Songs` playlist.
- Multiple buttons share the accessible name `Play`, so every query in this recipe must be scoped to `[data-slot="player-bar"]`.
- Audio is a 30-second iTunes preview; position advances with real playback.
- Seek uses the audio element's currentTime, and the total time is the preview's duration (~0:30),
not the song metadata duration.
- The bar is idle until `currentSongId` is set; there is no default track.
54 changes: 54 additions & 0 deletions PERF.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Performance Ledger

Baseline and result for each performance experiment, kept or reverted.
Method: Lighthouse (`--only-categories=performance`, mobile) against a
production build (`next build && next start`) on `http://localhost:3000`.
Throttle: simulated slow 4G, 4x CPU. Machine noise is ±0.2s LCP / ±300ms TBT,
so a delta smaller than that is not a win.

## Baseline (before this pass)

| Route | Score | LCP | CLS | TBT |
|---|---|---|---|---|
| `/` | 0.43 | 5.5s | 0.156 | 2110ms |
| `/search` | 0.55 | 2.2s | 0.250 | 4370ms |
| `/library` | 0.52 | 4.2s | 0.129 | 2520ms |

## Kept

| Change | Baseline → Result | Why |
|---|---|---|
| `queue-panel` aside gets `h-full` | CLS 0.156 → 0.022 | Lighthouse layout-shift audit named `aside[data-slot=queue-panel]`: the panel resized after `useSongs()` resolved, pushing the page content down. `h-full` pins it to the row so late-arriving queue data cannot reflow the shell. |
| `/search` renders a loading block instead of `EmptyState` | CLS 0.250 → 0.021 | Empty results and "still loading" both rendered a short block, then the real list pushed it down. `isLoading` now reserves height. |
| `fetchAlbums` groups songs by album into a `Map` | O(albums × songs) → O(songs) | No user-visible number on a 4-album seed; kept because it is free and the table is unbounded by design. |
| `listPlaylists` groups `playlist_songs` by `playlist_id` | O(playlists × links) → O(links) | Same reasoning; the N+1 shape is wrong regardless of current dataset size. |
| `getPlaylist` filters and orders `playlist_songs` server-side | full table scan → indexed `eq` + `order` | Reuses the `(playlist_id, song_id)` primary key. |
| `selectAll` gains `limit` / `orderBy` / `ascending` | unbounded → capped + ordered | Every list endpoint previously did `select("*")` with no bound. Default cap 1000. |

## Reverted

| Idea | Baseline → Result | Why |
|---|---|---|
| `priority` on the first 4 album images (`i < 4`) | LCP 4.4s → 4.4s, CLS 0 → 0 | LCP is the `<h1>`, not an album image. Preloading 4 images only competes for bandwidth. Reverted to `priority={i === 0}`. |
| Rewrite `SeekBar` to update via ref + rAF | not attempted | The `position` subscription is already contained by the `SeekBar` component boundary; only that subtree re-renders, not `PlayerBar`. Lighthouse shows no long tasks while playing, so there was no measurement to justify the rewrite. |

## After this pass

| Route | Score | LCP | CLS | TBT |
|---|---|---|---|---|
| `/` | 0.63 | 4.4s | 0.000 | 780ms |
| `/search` | 0.62 | 4.0s | 0.021 | 1430ms |

CLS is now inside the "good" band (≤ 0.1) on both routes.

## Open, not yet attempted

- **LCP 4.4s is still poor.** The LCP element is the `<h1>Good evening</h1>`, which
is server-rendered and present at FCP (1.3s). The gap is the client-side shell
booting behind it. Fixing this means reducing initial JS (137 KiB of unused JS
flagged) or moving the header into a server component. Needs a trace, not a guess.
- **`fetchPreviewUrl`** (`src/lib/itunes.ts`) caches in an unbounded module `Map`
and fetches from iTunes on the client. Worth a TTL and an LRU bound.
- **Index on `playlist_songs(playlist_id)`** was *not* added: the composite
primary key `(playlist_id, song_id)` already leads with `playlist_id`, so a new
index would be dead weight on every write.
80 changes: 46 additions & 34 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# sunao

A Spotify-like music player. Red theme, dark UI. Built with Next.js 16, React 19, Tailwind v4, shadcn/ui on Base UI, Supabase, Zustand, and TanStack Query.
A Spotify-like music player. Red theme, dark UI. Runs with or without a Supabase backend. Built with Next.js 16, React 19, Tailwind v4, shadcn/ui on Base UI, Supabase, Zustand, and TanStack Query.

**Live demo:** https://sunao-omega.vercel.app

Expand All @@ -14,66 +14,77 @@ Full-resolution video: [MP4](artifacts/readme/demo.mp4) · [WebM](artifacts/read
|---|---|
| ![Home](artifacts/readme/home.png) | ![Search](artifacts/readme/search.png) |

| Expanded Now Playing | Synchronized Lyrics |
|---|---|
| ![Now Playing](artifacts/readme/now-playing.png) | ![Lyrics](artifacts/readme/lyrics.png) |

| Artist Detail | Album Detail |
|---|---|
| ![Artist Detail](artifacts/readme/artist.png) | ![Album Detail](artifacts/readme/album.png) |

| Library | Playlist |
|---|---|
| ![Library](artifacts/readme/library.png) | ![Playlist](artifacts/readme/playlist.png) |

| Now playing | Collapsed queue |
| Player bar & controls | Collapsed queue |
|---|---|
| ![Now playing](artifacts/readme/playing.png) | ![Collapsed queue](artifacts/readme/queue-collapsed.png) |
| ![Player controls](artifacts/readme/playing.png) | ![Collapsed queue](artifacts/readme/queue-collapsed.png) |

| Sign in | Sign up |
| Mobile home | Mobile now playing |
|---|---|
| ![Sign in](artifacts/readme/login.png) | ![Sign up](artifacts/readme/signup.png) |
| ![Mobile home](artifacts/readme/mobile-home.png) | ![Mobile now playing](artifacts/readme/mobile-now-playing.png) |

| Mobile home | Mobile queue |
| Mobile queue | Sign in |
|---|---|
| ![Mobile home](artifacts/readme/mobile-home.png) | ![Mobile queue](artifacts/readme/mobile-queue.png) |
| ![Mobile queue](artifacts/readme/mobile-queue.png) | ![Sign in](artifacts/readme/login.png) |

Captured from a production build; regenerate with [scripts/capture-readme.mjs](scripts/capture-readme.mjs).

## Features

Playback
- Play / pause, previous / next, and auto-advance when a track ends
- Volume control: responsive slider, mute toggle button, and keyboard volume shortcuts (`↑`/`↓`), persisted in `localStorage`
- Shuffle mode: context-aware queue randomization preserving the active song at index 0 and cleanly restoring original queue on toggle
- Repeat modes: 3-state toggle (`off` / `all` / `one`) wired into audio `ended` event
- Comprehensive keyboard shortcuts: `Space` (play/pause), `Shift+N` / `J` (next), `Shift+P` / `K` (prev), `L` (like/unlike current track), `↑`/`↓` (volume up/down), input-guarded against text entry
- Seek scrubber bound to the audio element's real duration, with position and total labels
- Playback speed: 0.5×, 1×, 1.25×, 1.5×, 2×
- Like / unlike the current track (session-only, in memory; not persisted to Supabase)
- Like / unlike the current track — persisted (a versioned `localStorage` library document without a backend; a `liked_songs` table in Supabase mode), with optimistic updates and an Undo toast on removal
- Buffering spinner, debounced 200ms so it does not flash on fast loads
- Audio: 30-second previews from the iTunes Search API (no key), cached per title + artist, falling back to a bundled sample tone
- Album artwork from the iTunes CDN, served through `next/image`

Browse
- Home: album grid (click an album to play it) plus the full song list
- Search: filter songs by title or artist, with an empty state
- Library: list playlists and create a new one (writes to Supabase, or to a local store without a backend)
- Home: album grid, full song list, "Recently Played" history, and "Made For You" dynamic recommendation pool
- Search: live iTunes Search API catalog querying (debounced 300ms with abort cancellation and local fallback), category tabs (`All`, `Songs`, `Albums`, `Artists`, `Playlists`), and persisted recent searches
- Detail routes: Artist pages (`/artist/[id]`) with header, top songs, and discography; Album pages (`/album/[id]`) with artwork, tracklist, and "Play all"
- Library: list playlists and create a new one (writes to Supabase when configured, to an on-device store otherwise — both persist across reloads)
- Playlist detail: cover, song count, "Play all", remove a song, empty state

Now playing and queue
Now playing, queue, and lyrics
- Now-playing card and a windowed (virtualized) queue; click any row to jump to it
- Expanded Now-Playing view: full-screen modal panel (`NowPlayingPanel`) replacing native Fullscreen API button, featuring large 420px artwork, full transport controls, seek bar, queue drawer sheet, and lyrics toggle
- Synchronized lyrics: scrolling `LyricsPanel` tracking playback time, with smooth auto-centering and click-to-seek
- Collapsible queue panel at `lg` and up: narrows to a 40px rail instead of unmounting, so the toggle stays reachable and scroll position survives; the collapsed content is `inert`
- Mobile bottom-sheet player: touch-gestured bottom sheet (`MobileSheetPlayer`) with swipe-up to expand from player bar and swipe-down to dismiss (velocity-aware, `inert` when closed)
- Mobile queue in a bottom sheet

Auth (Supabase, optional)
- Email and password sign in / sign up, plus an `/auth/callback` route for confirmation links
- `src/proxy.ts` refreshes the session and protects `/library` and `/playlist/*`
- With the Supabase env vars unset the app runs without a backend: static seed data, in-memory playlists, and no auth
- With the Supabase env vars unset the app runs without a backend: static seed data, an on-device library store (`localStorage`), and no auth

Mobile
- Bottom nav (Home / Search / Library / Queue) below `lg`, replacing the hidden sidebar
- Compact player bar with a 2px progress line; the seek bar and speed control appear from `md` up
- Safe-area padding on the bottom nav

Discoverability
- Per-route metadata from the server components: title, description, canonical URL, Open Graph, and Twitter cards
- `robots.txt`, a `sitemap.xml` generated from the catalogue, and generated OG/Twitter share images
- `MusicRecording` structured data on playlist pages; library, login, and signup are `noindex`

Polish
- Loading skeletons for the home route; empty states for search and playlists
- Accessibility: ARIA labels and states on player controls, tooltips with keyboard hints, Space toggles play/pause, focus-visible ring, `inert` collapsed panels
- Loading skeletons for the home route; empty states for search and playlists, with an action slot when there is an obvious next step
- Accessibility: a skip-to-content link, ARIA labels and states on player controls, tooltips with keyboard hints, Space toggles play/pause, focus-visible ring, `inert` collapsed panels
- Motion: shared easing tokens, pointer-gated hover, press feedback, staggered album entrance, heart pop, artwork pulse, and reduced-motion support
- Performance: `optimizePackageImports` for lucide-react and react-query
- Performance: search filtering deferred with `useDeferredValue`, `content-visibility` on song rows, DNS preconnects for the iTunes API, `optimizePackageImports` for lucide-react, Base UI, and react-query

## Tech stack

Expand All @@ -83,7 +94,6 @@ See [docs/stack.md](docs/stack.md) for the full list with rationale. Highlights:
- TypeScript (strict)
- Tailwind CSS v4 and shadcn/ui primitives on Base UI
- Zustand (player state) and TanStack Query + Virtual (server state and the queue)
- Vercel Analytics and Speed Insights for page views and Web Vitals
- Supabase (Postgres, Auth, RLS) and Zod (boundary validation)
- Vitest and React Testing Library, Playwright (E2E)
- Biome (lint and format), Husky and lint-staged
Expand All @@ -94,12 +104,13 @@ Prerequisites: Node 20+, pnpm 12+.

```bash
pnpm install
cp .env.example .env.local # optional; the app runs without Supabase
pnpm dev
```

Open http://localhost:3000.

Optional: create `.env.local` with the variables below for Supabase data and auth.
Environment variables (optional, for Supabase data and auth):

```
NEXT_PUBLIC_SUPABASE_URL=
Expand Down Expand Up @@ -131,37 +142,35 @@ node scripts/capture-readme.mjs # CAPTURE_BASE=http://localhost:3000, CAPTURE_

It writes the screenshots, `demo.webm`, and the `demo.mp4` / `demo.gif` derived from it. Requires `ffmpeg`.

`node scripts/check-seo.mjs [url]` audits a running server for per-route metadata, `robots.txt`, `sitemap.xml`, and share images.

## Project structure

```
src/
app/ App Router routes: (app)/ shell group, login, signup, auth/callback;
server pages export metadata and render a *-client.tsx; robots.ts,
sitemap.ts, opengraph-image.tsx, twitter-image.tsx
app/ routes (App Router): (app)/ shell group, about, login, signup, auth/callback, robots, sitemap
components/
ui/ shadcn primitives (button, dialog, sheet, slider, tooltip, kbd, skeleton, empty-state)
layout/ app shell (sidebar, player bar, queue panel, mobile nav)
features/ feature components (playlist form)
hooks/ use-audio, use-delayed-flag
stores/ Zustand (player)
lib/ utils, query client, Supabase clients, schemas, queries, iTunes, site metadata, data
lib/ utils, query client, Supabase clients, schemas, queries, iTunes, data, library (repo seam)
types/ shared types
docs/ wiki (stack, architecture, design system, workflows, roadmap)
docs/ wiki (concepts/, stack, architecture, design system, workflows, roadmap)
e2e/ Playwright specs
supabase/ migrations + seed
scripts/ check-comments.mjs, check-seo.mjs, capture-readme.mjs
scripts/ check-comments.mjs, capture-readme.mjs
```

## Docs

- [docs/README.md](docs/README.md) — wiki index
- [docs/concepts/](docs/concepts/) — the patterns: state ownership, the repository seam, server/client split, the audio engine, SEO & AI discovery
- [docs/stack.md](docs/stack.md) — what we use and why
- [docs/architecture.md](docs/architecture.md) — app structure and data flow
- [docs/design-system.md](docs/design-system.md) — tokens, components, motion
- [docs/workflows.md](docs/workflows.md) — dev commands and PR flow
- [docs/roadmap.md](docs/roadmap.md) — the multi-phase plan
- [docs/roadmap-post-mvp.md](docs/roadmap-post-mvp.md) — the post-MVP phases (persistence, likes, discovery)

## Contributing

Expand All @@ -178,6 +187,9 @@ Notes for contributors
## Status

Phases 0–6 are complete and deployed to https://sunao-omega.vercel.app. Work after the
original plan (real iTunes artwork and previews, motion polish, queue virtualization, the
collapsible queue panel, and SEO/discoverability) shipped on top of it. See
[docs/roadmap.md](docs/roadmap.md).
original plan shipped on top of it: real iTunes artwork and previews, motion polish, queue
virtualization, the collapsible queue panel, the SEO/discoverability surface, and post-MVP
phases 7–8 (correctness fixes + the persistence foundation — likes and playlists now
survive a reload without a backend, as machine-checked in `e2e/persistence.spec.ts`).
Phase 9 ("Liked Songs" as a live projection of likes) is the open remainder of bug A.
See [docs/roadmap.md](docs/roadmap.md) and [docs/roadmap-post-mvp.md](docs/roadmap-post-mvp.md).
Binary file added artifacts/full-journey/01-home.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added artifacts/full-journey/02-playing-album.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added artifacts/full-journey/03-like-toast.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added artifacts/full-journey/04-search.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added artifacts/full-journey/05-library.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added artifacts/full-journey/06-playlist.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added artifacts/full-journey/07-signup.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added artifacts/full-journey/08-flicker-end.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added artifacts/full-journey/09-mobile-queue.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added artifacts/full-journey/flicker.webm
Binary file not shown.
Binary file added artifacts/full-journey/journey-desktop.webm
Binary file not shown.
Binary file added artifacts/full-journey/mobile.webm
Binary file not shown.
Binary file added artifacts/full-journey/perf.webm
Binary file not shown.
Binary file added artifacts/persistence/01-liked-after-reload.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added artifacts/queue-toggle/01-open.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added artifacts/queue-toggle/02-collapsed.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added artifacts/queue-toggle/03-restored.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added artifacts/readme/album.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added artifacts/readme/artist.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified artifacts/readme/demo.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified artifacts/readme/demo.mp4
Binary file not shown.
Binary file modified artifacts/readme/demo.webm
Binary file not shown.
Binary file modified artifacts/readme/home.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified artifacts/readme/library.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified artifacts/readme/login.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added artifacts/readme/lyrics.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified artifacts/readme/mobile-home.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added artifacts/readme/mobile-now-playing.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified artifacts/readme/mobile-queue.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added artifacts/readme/now-playing.png
Binary file modified artifacts/readme/playing.png
Binary file modified artifacts/readme/playlist.png
Binary file modified artifacts/readme/queue-collapsed.png
Binary file modified artifacts/readme/search.png
Binary file modified artifacts/readme/signup.png
Binary file added artifacts/verify-auth/login-submit.png
Binary file added artifacts/verify-auth/login.png
Binary file added artifacts/verify-auth/signup.png
Binary file added artifacts/verify-data-layer/library.png
Binary file added artifacts/verify-data-layer/playlist-detail.png
Loading
Loading