End-to-end pilot of the per-item architecture. Home now reads from
the new homeIndexProvider (drift-first over CachedHomeIndex with
/api/home/index discovery + SWR), then each tile is a small
ConsumerWidget watching its own albumTileProvider/artistTileProvider/
trackTileProvider. Tiles render a matched-dimension skeleton while
their entity is still hydrating, and swap in the real card once
drift emits the populated row.
Track hydration is wired up — /api/tracks/:id already existed so
the queue's case 'track' just calls api.getTrack(id) and persists.
The visible behavior:
* Cold visit: small /api/home/index round-trip (IDs only, ~10×
smaller than /api/home), then sections appear shaped with
skeleton tiles; each tile materializes as the hydration queue
drains. No more "30s blank → everything pops in at once."
* Warm visit: drift index emits instantly, drift entity rows emit
instantly, no network. Page paints fully in the first frame.
* Mid-state: scrolling through a partially-hydrated section sees
real cards next to skeleton cards. Layout doesn't shift because
skeletons match real-card dimensions exactly.
CachedHomeSnapshot (and the legacy homeProvider) stay in place but
unconsumed by Flutter — left in for now so revert is cheap if the
new path needs reworking. Cleanup follow-up in a later slice.
Old /api/home endpoint untouched, so the web client keeps working
unchanged.
LibraryApi wraps GET /api/home, /api/artists/{id}(/tracks),
/api/albums/{id}. dioProvider builds an authenticated dio (token
resolver reads session_token from secure storage on every request).
homeProvider, artistProvider(id), albumProvider(id) sit on top.