Skip to content

Decouple and complete the resumable Last.fm import pipeline #41

Description

@rianjs

Outcome

Decouple and complete the resumable Last.fm import pipeline as a follow-up to #39 and #40. Keep the core deterministic, keep Spotify traffic behind the shared client/request gate, and make source download independently resumable before any Spotify-derived work.

Acceptance criteria

  • Keep user.getRecentTracks at the documented maximum of 200 rows per page. Capture one fixed historyTo as the recorded pre-scrobbling cutoff.
  • Add an exportable account-bound lastfmScrobblingProfile {username, startedAt}. The first successful connection/enable records it; toggling the same username preserves it; a different username replaces it; legacy records missing it are generically backfilled on read/write. Later live proof on this workstation seeds Unix 1786804381; this ticket must not mutate app data during implementation or review.
  • Replace importer session V1 with V2 and quarantine V1. Download requires only Last.fm; defer Spotify account binding until the first page match. One metadata probe discovers the total, then download fetches the oldest page toward page 1.
  • Atomically cache parsed raw pages under a snapshot-specific machine cache, write the manifest only after each page write, and enforce 100 MiB safety ceilings. Do not aggregate until every page is present; then read, sort timestamp-ascending, aggregate once, atomically enter review, and best-effort delete the raw cache.
  • Retry transient Last.fm failures with the existing capped backoff. Relaunching the importer resumes the exact missing page.
  • Remove automatic global Spotify matching and the normal matching phase. Add an aggregating phase plus downloadedPages/totalPages.
  • When a visible review batch is opened, lazily match it through the shared Spotify client/request gate, serialize duplicate requests, cache the result, and make revisits issue no API call. Defer Spotify identity resolution/binding to the first match; a later mismatch suspends Spotify-derived work. Do not add adjacent prefetch.
  • Make Accept All the explicit bulk exception: sequentially match/cache all remaining batches, show exact entity counts, and apply only after confirmation.
  • Update UI states and the owning architecture documentation. Do not add a dependency or generic importer abstraction.
  • Quarantine/restart any incomplete V1 cache because the changed cutoff changes page boundaries.
  • Add regression coverage for cutoff/order, complete-before-aggregate, interruption/reload/orphan/oversize/corruption, zero Spotify during source work, one-batch lazy matching/cache/revisit, account binding/mismatch, settings export/restore, UI states, and Accept All. Run the full documented checks.
  • Later live proof: back up app data, seed 1786804381, run a real import, verify resume and that no Spotify request occurs before a visible batch, and do not apply/import any content.
  • Delivery uses TDD, one independent Terra-high coverage review, and a live CR review at <pr-url> with inline replies, up to three passes while feedback remains high-value. CI is green; merge and clean up the branch/worktree.
  • Put durable architecture decisions in the architecture docs and delete the temporary implementation plan after merge.

Scope guard

No product implementation, documentation edits, tests, commits, pushes, PR, native app launch, or connected Spotify/Last.fm mutations are part of the planning gate.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions