Skip to content

Refactor queueNeedsBuild Bool into explicit transport sync state machine #102

@joshhbk

Description

@joshhbk

Problem

queueNeedsBuild currently encodes multiple transport-sync meanings behind a single Boolean:

  • pending sync at natural boundary
  • pending sync on next play/resume
  • sync in progress / retry in progress
  • degraded or recovered but not fully disarmed

That ambiguity is making boundary-swap behavior brittle and has already produced subtle state bugs.

Current failure modes

  1. pendingSkip can re-arm boundary swap even after queueNeedsBuild was cleared, allowing a later hard pause when no sync is needed.
  2. Deferred sync can be stranded after in-app pause + external resume because re-arming is tied to add-time playing state rather than explicit sync intent.
  3. Polling/arming/disarming logic is distributed across many call sites, which makes transitions hard to reason about and test.

Proposal

Replace queueNeedsBuild: Bool with an explicit transport sync state enum in domain state.

Example shape:

enum TransportSyncState: Equatable, Sendable {
    case inSync
    case pendingOnPlay
    case pendingAtBoundary
    case syncingAtBoundary
    case retryPending(strategy: SyncStrategy, attempts: Int)
}

enum SyncStrategy: Equatable, Sendable {
    case onPlay
    case atBoundary
}

Behavior goals

  • Add while playing => .pendingAtBoundary
  • Add while paused/stopped/no queue => .pendingOnPlay
  • Boundary poller runs only in .pendingAtBoundary + .playing
  • Skip/pause/remove/algorithm-change transitions become explicit and local
  • Retry success/failure transitions cannot leave stale armed states

Incremental migration plan

  1. Add transportSyncState to QueueEngineState with compatibility shim:
    • keep queueNeedsBuild as computed bridge during migration
  2. Migrate reducer intents (addSong, addSongsWithRebuild, play, pause, syncDeferredTransport, resyncActiveAddTransport)
  3. Migrate ShufflePlayer boundary-swap orchestration to consume enum states
  4. Remove Boolean once all call sites and tests are migrated
  5. Add transition tests:
    • paused add does not boundary-arm
    • external resume re-arms when pending-on-play
    • retry success clears armed/pendingSkip paths

Acceptance criteria

  • No path can trigger boundary pause/swap when sync state is inSync
  • No deferred sync intent can be stranded across pause/resume or external playback controls
  • Boundary polling is only active when required by state
  • Existing queue/transport invariant tests remain green; new transition tests added

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions