Skip to content

Add backup/restore, library sort, and PWA auto-update features - #41

Open
EnesYilmazcode wants to merge 9 commits into
mainfrom
claude/phone-app-auto-update-xgckp7
Open

Add backup/restore, library sort, and PWA auto-update features#41
EnesYilmazcode wants to merge 9 commits into
mainfrom
claude/phone-app-auto-update-xgckp7

Conversation

@EnesYilmazcode

Copy link
Copy Markdown
Owner

Summary

This PR adds three major features to Melody: backup/restore functionality for playlists and library metadata, persistent sort preferences in the Library view, and improved PWA auto-update detection for iOS. It also includes a hidden build readout accessible via long-press on the tab bar for debugging deployment state.

Key Changes

Backup & Restore

  • Added exportBackup() and importBackup() functions to serialize/merge catalog and playlist data as JSON
  • Backup exports include schema version and timestamp; restore validates schema and coerces fields to prevent crashes from hand-edited or foreign backups
  • Restored tracks initially have no audio bytes; re-importing the same files reattaches them via the existing dedup match in addLocalTrack
  • Playlists merge by name (appending unseen track IDs); tracks merge by ID with user state combined (play count, stars, last played)
  • Added Export/Restore buttons in PlaylistsView with file picker and toast feedback

Library Sorting

  • Added three sort modes: Recent (default, by dateAdded desc), Most Played (by playCount), and A–Z (alphabetical)
  • Sort preference persists to localStorage with graceful fallback for private-mode Safari
  • Refactored LibraryView to wrap filter and sort controls in a flex row that wraps on narrow screens
  • Enhanced empty state with centered layout and NotesGlyph icon when library is empty

PWA Auto-Update & Build Readout

  • Implemented registerServiceWorker() in new src/lib/pwa.js that explicitly checks for updates on a 1-hour interval and whenever the app returns to foreground (critical for iOS where PWA is frozen/resumed rather than reloaded)
  • Added hidden VersionPeek component (long-press tab bar) showing commit hash and build timestamp, auto-hides after 5 seconds
  • Build info injected at compile time via vite.config.js using git commit hash and timestamp
  • Added Toast component for transient status messages (import summaries, restore results) positioned above the dock

Supporting Changes

  • Updated summarize() in format.js to use "songs" terminology and added formatTotalDuration() for human-readable duration formatting (e.g., "2 hr 51 min")
  • Enhanced PlaylistCard to display a 2×2 artwork mosaic and track count alongside playlist name
  • Modified ImportButton to filter non-audio files from multi-select (handles a-Shell folder sweeps with .json/.txt sidecars) and surface results via app-wide toast
  • Added UIProvider toast state management for centralized status messaging
  • Removed inline .importnote CSS in favor of app-wide toast pattern
  • Updated README with PWA auto-update and data safety documentation

Workflow Infrastructure

  • Added .claude/workflows/ship.js: a multi-phase pipeline for shipping code changes (research fleet → sequential implementers → slop-check fleet → fresh review → fix loop)
  • Added .claude/commands/ship.md documentation for the ship workflow

Notable Implementation Details

  • Backup restore uses a Dexie transaction to atomically merge tracks and playlists, preventing partial imports on failure
  • Dedup logic in addLocalTrack now distinguishes between restored rows (no blob) and duplicates (blob exists), allowing re-import to reattach bytes without counting as a duplicate
  • Sort state uses a pure lazy initializer to avoid StrictMode double-render side effects
  • Toast and VersionPeek auto-dismiss with configurable timeouts; clicking either dismisses immediately
  • Service worker update check explicitly called on visibility change to work around iOS PWA freeze/resume behavior
  • All new UI elements (backup buttons, toast, version peek) use existing design tokens and follow the repo's idiom for comment density and naming

https://claude.ai/code/session_01NBLkqNYmSgVYiTwUDcdogL

claude added 9 commits July 28, 2026 19:10
The app is configured with registerType:'autoUpdate', but on iOS a
standalone PWA is frozen and resumed rather than reloaded, so Safari's
built-in update check rarely fires and the app looks stale until it's
removed and re-added from the home screen. That workaround is also
risky: imported songs/playlists live in IndexedDB, which iOS can drop
when the home-screen icon is deleted.

- Register the service worker via virtual:pwa-register and call
  registration.update() on a timer and on every foreground resume, so a
  resumed iOS app picks up new builds without a reinstall.
- Request persistent storage at startup (not just on import) so the
  library is far less likely to be evicted while the app sits unused.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NBLkqNYmSgVYiTwUDcdogL
Long-press anywhere on the tab bar to peek at which deploy is running
(short commit hash + build timestamp, stamped at build time via a Vite
define). The pill auto-hides after 5s and never appears in normal use,
so the UI stays clean while still making it possible to confirm that
the PWA's in-place auto-update actually landed a new build.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NBLkqNYmSgVYiTwUDcdogL
Research fleet -> sequential implementers -> slop-check fleet -> fresh
review -> fix/re-verify loop, parameterized by a task batch. Improves
the shared team format: batch tasks with shared research and one
combined review, self-contained reviewer, and repo-idiom-beats-house-
style as the standing rule.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NBLkqNYmSgVYiTwUDcdogL
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NBLkqNYmSgVYiTwUDcdogL
…ring and an always-on summary

Filter the picked list down to audio files (by MIME type or the accept-list
extensions) before the quota pre-flight and the remaining counter, since iOS
Select-All sweeps in the a-Shell folder's .json sidecars and the accept attr
is advisory only. Count added tracks explicitly (the out-of-space break exits
early, so deriving it from lengths would be wrong) and always set a summary
notice after a non-empty pick: "N added", skipped/failed parts when present,
and "No new songs" when nothing changed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NBLkqNYmSgVYiTwUDcdogL
Adds exportBackup/importBackup to db.js: a backup is the catalog +
playlists as plain JSON (no audio bytes, no lyrics). Restore merges
tracks by id keeping live catalog metadata and combining user state,
and merges playlists by name with fresh auto-increment ids. Restored
rows keep their original id and srcType so re-importing the same audio
file hits addLocalTrack's dedup match, which now reattaches the bytes
to the existing row when its blob is missing, keeping playlists and
stars intact (issue #40).

The Playlists tab gains quiet Export backup / Restore pills with an
inline importnote status, and the README documents updating and data
safety, replacing the now-unsafe remove-and-re-add-the-icon advice.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NBLkqNYmSgVYiTwUDcdogL
UI polish pass over issue #39:

- format.js gains formatTotalDuration ('2 hr 51 min' / '48 min' / '42 sec',
  '' for empty) so views can append durations to song counts; summarize and
  formatTime are untouched.
- Library gets a Recent / Most played / A-Z sort segmented control persisted
  in localStorage (melody:librarySort, try/catch for private-mode Safari,
  pure lazy initializer for StrictMode), a song-count + duration meta line
  under the title, and a centered empty state with an icon and a second
  ImportButton CTA (only mounted while the library is empty, so the test
  harnesses' strict file-input locator still resolves on seeded dev).
- Playlist cards render a 2x2 artwork mosaic (Artwork's gradient fallback
  covers empty cells) plus a resolved song count and total duration,
  filtering dangling track ids the same way PlaylistDetail does.
- New toast in UIProvider/App replaces both inline importnote renderings
  (import summaries and backup-restore status) with a transient pill
  absolutely positioned inside the dock, keyed by id so repeat messages
  restart the 4s timer; the unused .importnote css rule is removed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NBLkqNYmSgVYiTwUDcdogL
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NBLkqNYmSgVYiTwUDcdogL
- summarize() now says "songs" and delegates to formatTotalDuration;
  Library header, playlist cards, and playlist detail all share it, so
  the count/duration vocabulary can't drift again
- formatTotalDuration: drop the unreachable NaN check and round to
  minutes before splitting off hours, so 3599 s renders "1 hr", not
  "60 min"
- importBackup: reject backups with a schema newer than 3; drop the
  dead starred default that the spread always overwrote
- ImportButton: picking only sidecar files now says "No audio files in
  that selection" instead of claiming the songs were already imported
- PlaylistsView: export failures surface a toast instead of vanishing

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NBLkqNYmSgVYiTwUDcdogL
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.

2 participants