Commit Graph

3 Commits

Author SHA1 Message Date
bvandeusen 27f123f7d9 fix(server,flutter): sync wire format + systemVariant column (#357)
Two fixes in one commit because they're entangled — the systemVariant
work would have been theater otherwise.

## The wire-format bug

/api/library/sync was emitting PascalCase JSON for artist / album /
track / playlist upserts (raw json.Marshal of sqlc-generated structs
with no JSON tags — sqlc.yaml: emit_json_tags=false). Flutter's
sync_controller _*FromJson reads snake_case keys, so all metadata
sync rows landed in drift with empty strings / zero ints.

The like_track / like_album / like_artist / playlist_track entities
work because they're hand-built `map[string]string` payloads with
snake_case keys — they sidestepped the bug. The 4 raw-marshal
entities did not.

Existing sync test caught zero of this — it asserts on len(upserts)
not field shape.

Fix: server-side view structs in library_sync_views.go with proper
JSON tags + pgtype-flattening (UUID → 8-4-4-4-12 hex string,
Date → "2006-01-02"). Mirrors the playlistRowView pattern from
/api/playlists. New library_sync_views_test.go pins the wire keys
so future field-name drift breaks loud.

## systemVariant column (closes #357 plan C v1 limitation)

playlistSyncView now carries `system_variant` server → wire.

Flutter drift schema bumped from 1 → 2 with onUpgrade adding the
`systemVariant TEXT NULL` column to cached_playlists. Cursor reset
to 0 in the migration so existing rows refresh with the new field
on the next sync.

playlistsListProvider now filters locally by systemVariant:
- kind='user'   → systemVariant IS NULL  (the add-to-playlist sheet's intent)
- kind='system' → systemVariant IS NOT NULL
- kind='all'    → no filter

Closes the documented v1 limitation where the add-to-playlist sheet
showed system playlists alongside user-created ones.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 18:47:30 -04:00
bvandeusen 0d171490c3 fix(flutter): analyze errors after Plan B push
Cleared 5 errors + 1 warning + 1 info from CI flutter analyze on 5114a81:

- prefetcher.dart: Riverpod listener callbacks used (_, _) for two
  placeholder params — Dart 3 enforces unique names. Changed to (_, __).
- prefetcher.dart: Riverpod 3's AsyncValue exposes `.value` (nullable)
  not `.valueOrNull`. Three call sites updated.
- sync_controller.dart: doc comment had `?since=<cursor>` → analyzer
  warned about unintended HTML. Wrapped in backticks with `{cursor}`.
- sync_controller.dart: delete loop over heterogeneous Table list
  inferred as List<Table>; drift's delete() expects TableInfo. Unrolled
  to explicit per-table deletes.
- audio_cache_manager_test.dart: db.into(...).insertAll([...]) doesn't
  exist on InsertStatement — only insert/insertOnConflictUpdate. Used
  db.batch((b) => b.insertAll(table, [...])) instead, in two test cases.
- audio_handler.dart: LockCachingAudioSource is marked experimental in
  just_audio. Added // ignore: experimental_member_use — operator
  acknowledged the experimental status during brainstorming and we're
  the project; CI's --fatal-infos would otherwise gate.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 10:18:38 -04:00
bvandeusen ab58f3ffcb feat(flutter/cache): SyncController — token-based delta sync
Drives /api/library/sync against drift:
  - reads cursor from SyncMetadata (default 0 = full snapshot)
  - 204 → just bump lastSyncAt, return zeroes
  - 410 → wipe all cached entities + retry from cursor=0
  - 200 → apply upserts + deletes per entity type, advance cursor

Handles all 8 entity types (artist/album/track + like_track/like_album/
like_artist + playlist/playlist_track) for both upsert and delete paths.
Composite-key entities use the "<a>:<b>" string format the server emits.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 23:18:15 -04:00