feat(flutter): per-item rendering infrastructure (Slice A)
Plumbing for the per-item rendering pass — no UI changes yet, just the layers the home/playlist/liked migrations will sit on. * CachedHomeIndex drift table (schema 5→6) — section/position → entity-id rows, populated by the upcoming /api/home/index endpoint. * HydrationQueue (concurrency=4, in-flight dedup) — bounded request pump that takes (entityType, entityId) and persists the result to the right cached_<entity> table. Albums + artists wired today; tracks deferred until /api/tracks/:id exists. * Per-entity tile providers (albumTileProvider, artistTileProvider, trackTileProvider as StreamProvider.family) — watch drift, enqueue hydration on miss, yield AsyncValue<EntityRef?>. * Skeleton widgets (album/artist/track) matched to the real card dimensions with a 1.2s shimmer sweep using FabledSword tokens. No shimmer-package dep — single AnimationController per surface. See docs/superpowers/specs/2026-05-13-per-item-rendering-design.md for the full architecture rationale.
This commit is contained in:
Vendored
+30
-1
@@ -142,6 +142,27 @@ class CachedHistorySnapshot extends Table {
|
||||
Set<Column> get primaryKey => {id};
|
||||
}
|
||||
|
||||
/// Section→position→entity-id index for the home screen, populated
|
||||
/// from the per-item discovery endpoint `/api/home/index`. Each row
|
||||
/// pins one tile slot to an entity; the actual entity data lives in
|
||||
/// cached_albums / cached_artists / cached_tracks. The home screen
|
||||
/// reads this table to know the layout, then hydrates each tile
|
||||
/// against the entity tables (per-item rendering). Schema 6+.
|
||||
class CachedHomeIndex extends Table {
|
||||
/// One of: 'recently_added_albums', 'rediscover_albums',
|
||||
/// 'rediscover_artists', 'most_played_tracks', 'last_played_artists'.
|
||||
/// Mirrors the keys /api/home and /api/home/index emit.
|
||||
TextColumn get section => text()();
|
||||
IntColumn get position => integer()();
|
||||
/// One of: 'album', 'artist', 'track'. Used to dispatch hydration
|
||||
/// to the right per-entity endpoint.
|
||||
TextColumn get entityType => text()();
|
||||
TextColumn get entityId => text()();
|
||||
DateTimeColumn get fetchedAt => dateTime().withDefault(currentDateAndTime)();
|
||||
@override
|
||||
Set<Column> get primaryKey => {section, position};
|
||||
}
|
||||
|
||||
/// Caller-scoped quarantine ("hidden") rows. Mirrors the wire shape of
|
||||
/// /api/quarantine/mine — fully denormalized (track + album + artist
|
||||
/// fields inline) because the Hidden tab renders straight from this row
|
||||
@@ -179,12 +200,13 @@ enum CacheSource { manual, autoLiked, autoPlaylist, autoPrefetch, incidental }
|
||||
CachedHomeSnapshot,
|
||||
CachedHistorySnapshot,
|
||||
CachedQuarantineMine,
|
||||
CachedHomeIndex,
|
||||
])
|
||||
class AppDb extends _$AppDb {
|
||||
AppDb([QueryExecutor? e]) : super(e ?? driftDatabase(name: 'minstrel_cache'));
|
||||
|
||||
@override
|
||||
int get schemaVersion => 5;
|
||||
int get schemaVersion => 6;
|
||||
|
||||
@override
|
||||
MigrationStrategy get migration => MigrationStrategy(
|
||||
@@ -218,6 +240,13 @@ class AppDb extends _$AppDb {
|
||||
// populates. Optimistic flag/unflag also writes here.
|
||||
await m.createTable(cachedQuarantineMine);
|
||||
}
|
||||
if (from < 6) {
|
||||
// Schema 6: cached_home_index for per-item rendering.
|
||||
// Empty on upgrade; first /api/home/index fetch populates.
|
||||
// cached_home_snapshot stays in place for now — older
|
||||
// builds still read from it as a fallback path.
|
||||
await m.createTable(cachedHomeIndex);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user