The #52 teardown clears the session when idle/dismissed, so the
headset / lock-screen play button had nothing to resume and the user
lost their place.
- track.dart: toJson() (round-trips fromJson)
- db.dart: CachedResumeState single-row snapshot, schema 9->10 + migration
- audio_handler.dart: queuedTracks getter
- player_provider.dart: restoreQueue() — configure + setQueueFromTracks
+ seek, no play (restores PAUSED)
- resume_controller.dart: restores last snapshot paused on launch;
persists {source,index,position_ms,tracks} debounced on track
change / pause and immediately on app teardown; never clobbers the
saved snapshot when the queue is empty so a teardown stays
recoverable; restore gated on auth + no active session
- app.dart: wired into postFrame
db.g.dart regenerated by CI per project convention. Deferred fast-follow:
media-button-when-fully-stopped re-init (needs a configure()-injected
callback; tracked on #54).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Field names mirror web/src/lib/api/types.ts and the server JSON shape
(internal/api/types.go's HomePayload). Hand-written DTOs per spec
section 10 -- codegen revisits at ~30 models.
Adopts server contract over plan-body mock:
- cover_url (not cover_art_url) for artist/album refs
- duration_sec (not duration_ms) for albums and tracks
- stream_url, disc_number on TrackRef
- sort_name/album_count on ArtistRef; sort_title/year/track_count on AlbumRef
HomeData section keys match server: recently_added_albums,
rediscover_albums, rediscover_artists, most_played_tracks,
last_played_artists.