Fixes#368.
Reopening a Spotify playlist from "recent access" showed no tracks,
while the first view (right after pasting the URL) worked fine.
Root cause traced across two layers:
1. Go: ExtURLHandleResult (the parsed shape of an extension's handleUrl()
return value) never captured a top-level `id` for the handled
resource. Track/album/artist results carry their own id inside their
nested metadata, but a plain playlist result has no such object, so
its id was silently dropped everywhere from the goja parser through
to the JSON the Dart side receives.
2. Dart: TrackState had nowhere to put that id even if it existed
(only playlistName), so recording a "recent" playlist access stored
the playlist's *name* as if it were its id. Reopening it later fed
that name into PlaylistScreen's provider-guessing logic
(legacyProviderIdFromResourceId, which only recognizes legacy
"provider:id" prefixes), which naturally failed and fell through to
a hardcoded Deezer metadata fetch using a Spotify playlist's name as
the resource id - guaranteed to return nothing.
Fix, matching the "generic API, not per-provider checks" architecture
in CONTRIBUTING.md:
- go_backend/extension_provider_wrapper.go: add ExtURLHandleResult.ID.
- go_backend/extension_goja_convert.go: parse it from the handler's
return value.
- go_backend/exports_extensions.go: surface it in the JSON response.
- lib/providers/track_provider.dart: add TrackState.playlistId,
populated from the response's `id` field for playlist results.
- lib/screens/home_tab.dart: record the real playlist id (falling back
to the name only if a provider never supplies one) and pass both the
id and the already-known provider id forward to PlaylistScreen.
- lib/screens/playlist_screen.dart: PlaylistScreen gains a
metadataProviderId param that takes priority over guessing from the
id's shape.
- lib/screens/home_tab_recent.dart: pass the recent-access entry's
stored providerId through when reopening a playlist.
- lib/utils/provider_resource_ids.dart: extract the
known-id-vs-guessed-id preference into a small, directly testable
resolvePreferredMetadataProviderId helper.
Note: this fixes the common case (an extension already reported its
own id as the source provider for the URL it handled). If a provider
never supplies an id for its handleUrl() playlist result, playlistId
stays null and behavior is unchanged from before this fix - no
regression, just not a complete fix for that narrower case, since
that would require changes in extension-side JS code outside this
repo.
Tests:
- go_backend/extension_goja_convert_url_handle_test.go: the new ID
field round-trips from a handler's return value, and stays empty
when the handler doesn't supply one.
- test/provider_resource_ids_test.dart: resolvePreferredMetadataProviderId
prefers a known provider id, falls back to the legacy-prefix guess,
treats a blank known id as unknown, and returns null for the
unprefixed-id-with-no-known-provider case from #368 itself.
Verification: go build/vet/test and flutter analyze/test all green.
Remove the never-called checkAvailability chain end to end (Dart bridge +
provider, Android/iOS MethodChannel handlers, Go exports) and stop restoring
its persistent lookup cache on every cold start; the legacy prefs key is
cleared on next cache reset.
Delete dead Go: romaji.go (test-only), the no-op pre-warm chain
(PreWarmTrackCache/PreWarmCache/…), retired Download stubs, and a batch of
exports with no Dart/Kotlin/Swift/Go caller. Kept GetTrackCacheSize/
ClearTrackIDCache (Settings cache screen), SetAllowedDownloadDirs and
Clear/GetItemProgress (live test seams).
Dedup: single updateFlacVorbis + replaceFlacPictures helper behind the six
FLAC tag writers; merge byte-identical extractCommentFrame/extractLyricsFrame
into extractLangTextFrame.
go build/vet + 237 go tests, flutter analyze + tests green.
marshalJSONString collapses 62 identical marshal-and-return blocks in the
gomobile exports; jsError/jsSuccess collapse 135 goja extension-response
maps; the two MusicBrainz fetchers share one request+retry helper; the
extension-HTTP default User-Agent block and fileExists get single homes.
No exported signature changes.
gofmt -r rewrite of all 506 interface{} occurrences to the any alias
(identical semantics), drop unused spec constants into comments, rename
unused parameters to _, convert three if-else chains to tagged
switches, and use slices.ContainsFunc for the private-IP check. No
behavior change; the whole package is now gofmt-clean.
A pending verification challenge lived in a process-global map with no
timestamp, so a challenge created while the app was backgrounded (and
never completed) was served again verbatim on the next attempt - sending
the user to an already-expired CAPTCHA page that needed a second request.
Stamp PendingAuthRequest with CreatedAt at every writer and make
ensureExtensionPendingAuthRequest discard entries older than 5 minutes,
starting a fresh signed-session verification instead.
Pure mechanical move within package gobackend — no signature or behavior
change, so the gomobile binding surface is identical. exports.go (4,141
lines) now holds only availability/dir/duplicate/filename exports; the
rest moved to exports_{musicbrainz,download,reenrich,metadata,lyrics,
deezer,extensions,store,library}.go along existing responsibility
boundaries.