Queue Manager Architect
SKILL.md
Queue Manager Architect
You are the multiple queue system architect for Modcaster, solving the #1 most-requested Pocket Casts feature.
Your Job
Design and validate a robust multiple queue system with context-aware auto-switching and smart queue building.
Core Requirements
1. Multiple Named Queues
User Can Create:
- Unlimited custom queues (reasonable limit: 20)
- Each queue has:
- Name (emoji + text, e.g., "๐ Commute")
- Episodes (ordered list)
- Auto-switch rules (optional)
- Smart fill settings (optional)
- Current playback position
Default Queues (Pre-created):
- "Up Next" (default, always exists)
- "Commute" (auto-switch: in car)
- "Workout" (auto-switch: motion detected)
- "Sleep" (auto-switch: 10pm-6am)
2. Context-Aware Auto-Switching
Triggers:
- Location: Car detected (CarPlay connection, Bluetooth car audio)
- Motion: Walking, Running (Core Motion API)
- Time: Scheduled (e.g., Sleep queue 10pm-6am)
- Audio Output: AirPods, HomePod, Car, etc.
- Focus Mode: Work, Personal, Sleep (iOS Focus API)
Behavior:
User is playing episode from "Up Next"
โ
CarPlay connects
โ
IF "Commute" queue has auto-switch: Car = ON
โ
PAUSE current episode
SHOW notification: "Switching to Commute queue?"
[Yes] [No] [Don't ask again for Car]
โ
IF Yes:
- Save position in "Up Next"
- Switch active queue to "Commute"
- Resume playback from Commute queue position
3. Smart Queue Building
"Fill Time" Mode:
User says: "Fill my Commute queue with 45 minutes of episodes"
โ
Algorithm:
1. Get user's subscribed shows
2. Filter to unplayed episodes
3. Prioritize by:
- Oldest unlistened (season-aware)
- User's listening history (shows listened more)
- Explicit queue preferences (user favorited shows)
4. Select episodes until total duration โ 45 min (ยฑ5 min)
5. Order by show/season continuity
6. Add to Commute queue
"Continue Series" Mode:
For each subscribed show:
1. Find current season/episode position
2. Add next unlistened episode
3. Respect season boundaries (don't skip seasons)
4. Order by show priority or oldest-first
"Variety" Mode:
Select next episode from different shows
Avoid consecutive episodes from same show
Maximize diversity across categories/styles
Queue Data Model
Queue Entity
struct Queue: Identifiable, Codable {
let id: UUID
var name: String // "๐ Commute"
var episodes: [EpisodeReference] // Ordered
var currentIndex: Int // Currently playing position
var autoSwitchRules: [AutoSwitchRule]
var smartFillSettings: SmartFillSettings?
var createdAt: Date
var lastModified: Date
var isSyncedToCloud: Bool
}
struct EpisodeReference: Identifiable, Codable {
let id: UUID
let episodeGUID: String // Reference to actual episode
let feedURL: String
var addedAt: Date
var playbackPosition: TimeInterval // If partially played
}
struct AutoSwitchRule: Codable {
var trigger: SwitchTrigger
var isEnabled: Bool
enum SwitchTrigger: Codable {
case car
case motion(MotionType)
case timeRange(start: Date, end: Date)
case audioOutput(AudioOutputType)
case focusMode(FocusModeType)
}
}
struct SmartFillSettings: Codable {
var targetDuration: TimeInterval? // e.g., 45 minutes
var mode: FillMode
var priorityShows: [String] // Feed URLs
enum FillMode: String, Codable {
case fillTime
case continueSeries
case variety
}
}
CloudKit Sync Strategy
Queue Sync Record
CKRecord Type: "Queue"
Fields:
- queueID: String (UUID, indexed)
- name: String
- episodes: [String] (array of episodeGUID)
- currentIndex: Int
- autoSwitchRules: Data (encoded JSON)
- smartFillSettings: Data (encoded JSON)
- lastModified: Date (for conflict resolution)
- deviceID: String (which device last modified)
Conflict Resolution
Scenario: User adds episode to "Commute" on iPhone, also on iPad
Resolution Strategy:
1. Compare lastModified timestamps
2. IF both modified same queue in <10 seconds:
- MERGE: Union of episodes (order from most recent device)
- currentIndex: Use from most recently active device
3. ELSE:
- Last-write-wins (later timestamp)
- Notify user: "Queue updated from [device name]"
UI Components
Queue Switcher (Mini Player)
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Now Playing: "Episode Title" โ
โ From: [๐ Commute โผ] 5 episodes โ โ Tap to switch queue
โ [===โโโโโโโโ] 12:34 / 42:16 โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Tap queue name dropdown:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ โ ๐ Commute (Active) ยท 5 โ
โ ๐ Up Next ยท 8 โ
โ ๐๏ธ Workout ยท 3 โ
โ ๐ด Sleep ยท 12 โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ + Create Queue โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Queue Management Screen
Queues
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ ๐ Commute (Active Now) ยท 5 โ
โ 45 min total โ
โ Auto-Switch: Car ๐ โ
โ [Edit] [Clear] [Smart Fill] โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ ๐ Up Next ยท 8 โ
โ 2 hrs 15 min total โ
โ Default queue โ
โ [Edit] [Clear] โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ ๐๏ธ Workout ยท 3 โ
โ 90 min total โ
โ Auto-Switch: Running ๐ โ
โ [Edit] [Clear] [Smart Fill] โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
[+ Create New Queue]
Queue Editor
Edit Queue: ๐ Commute
Name: [๐ Commute ]
Emoji: [๐โผ]
Auto-Switch Rules:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ โ When connected to car โ
โ โ When in car (location) โ
โ โ Between 7-9am weekdays โ
โ [+ Add Rule] โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Smart Fill:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ โ Enabled โ
โ Target Duration: [45] minutes โ
โ Mode: [Continue Series โผ] โ
โ Priority Shows: [3 selected] โ
โ [Run Smart Fill Now] โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Episodes (5): Drag to reorder
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ โก [Art] Show A ยท S2E5 (20 min) โ
โ โก [Art] Show B ยท S1E3 (15 min) โ
โ โก [Art] Show C ยท Bonus (10 min) โ
โ [ร] [โ] [โ] โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
[Save] [Cancel]
Smart Fill Algorithm
Continue Series Fill
func smartFillContinueSeries(
queue: Queue,
targetDuration: TimeInterval,
subscriptions: [Subscription]
) -> [EpisodeReference] {
var episodes: [EpisodeReference] = []
var totalDuration: TimeInterval = 0
// Get current listening position for each show
let positions = subscriptions.map { subscription in
(subscription, getCurrentPosition(for: subscription))
}
// Sort by oldest unlistened
let sorted = positions.sorted {
$0.1.nextEpisodeDate < $1.1.nextEpisodeDate
}
for (subscription, position) in sorted {
guard totalDuration < targetDuration else { break }
// Get next episode in series
if let nextEpisode = position.nextEpisode {
episodes.append(EpisodeReference(from: nextEpisode))
totalDuration += nextEpisode.duration
}
}
return episodes
}
Fill Time Algorithm
func smartFillTime(
targetDuration: TimeInterval,
subscriptions: [Subscription],
priorityShows: [String]
) -> [EpisodeReference] {
let tolerance: TimeInterval = 300 // ยฑ5 minutes
var episodes: [EpisodeReference] = []
var totalDuration: TimeInterval = 0
// Collect all unplayed episodes
let allEpisodes = subscriptions
.flatMap { $0.unplayedEpisodes }
.sorted { $0.pubDate < $1.pubDate } // Oldest first
// Prioritize from priority shows
let prioritized = allEpisodes.sorted { ep1, ep2 in
let isPriority1 = priorityShows.contains(ep1.feedURL)
let isPriority2 = priorityShows.contains(ep2.feedURL)
if isPriority1 != isPriority2 {
return isPriority1
}
return ep1.pubDate < ep2.pubDate
}
// Greedy selection (knapsack problem)
for episode in prioritized {
if totalDuration + episode.duration <= targetDuration + tolerance {
episodes.append(EpisodeReference(from: episode))
totalDuration += episode.duration
if totalDuration >= targetDuration - tolerance {
break
}
}
}
return episodes
}
Context Detection Implementation
Car Detection
import CoreMotion
import AVFoundation
func monitorCarConnection() {
// CarPlay connection
NotificationCenter.default.addObserver(
forName: .MPCarPlayConnectionEstablished
) { _ in
handleCarConnected()
}
// Bluetooth car audio
NotificationCenter.default.addObserver(
forName: AVAudioSession.routeChangeNotification
) { notification in
if let reason = notification.userInfo?[AVAudioSessionRouteChangeReasonKey] as? UInt,
reason == AVAudioSession.RouteChangeReason.newDeviceAvailable.rawValue {
// Check if car Bluetooth
if isCarAudioDevice(currentRoute) {
handleCarConnected()
}
}
}
}
Motion Detection
let motionManager = CMMotionActivityManager()
func monitorMotion() {
guard CMMotionActivityManager.isActivityAvailable() else { return }
motionManager.startActivityUpdates(to: .main) { activity in
guard let activity = activity else { return }
if activity.running {
handleMotionDetected(.running)
} else if activity.walking {
handleMotionDetected(.walking)
}
}
}
Time-Based Switching
func scheduleTimeBased(queue: Queue, start: Date, end: Date) {
// Schedule notification at start time
let startTrigger = UNCalendarNotificationTrigger(
dateMatching: Calendar.current.dateComponents([.hour, .minute], from: start),
repeats: true
)
// Ask user if they want to switch
let content = UNMutableNotificationContent()
content.title = "Switch to \(queue.name)?"
content.body = "\(queue.episodes.count) episodes ready"
content.categoryIdentifier = "QUEUE_SWITCH"
}
Validation Checklist
Queue Functionality
- Creation: User can create unlimited queues
- Naming: Support emoji + text, unique names
- Ordering: Drag-and-drop reordering works smoothly
- Deletion: Can delete queue (with confirmation)
- Duplication: Can duplicate queue with all settings
Auto-Switching
- Trigger Detection: All context triggers detected reliably
- User Consent: Ask before auto-switching (first time)
- State Preservation: Save position in current queue before switch
- Notification: Inform user when auto-switch happens
- Disable Option: User can turn off auto-switch per queue
Smart Fill
- Accuracy: Fill time within ยฑ5 min of target
- Continuity: Respect season/series order
- Variety: No 3+ consecutive episodes from same show (variety mode)
- Performance: Fill calculation <500ms for 100 subscriptions
Sync Reliability
- Conflict-Free: Multiple device edits merge correctly
- Real-Time: Changes sync within 10 seconds
- Offline Support: Queue changes when offline, sync later
- No Data Loss: Episodes never lost during sync
Common Issues & Fixes
Issue: Queue Switching Mid-Episode
- Problem: User doesn't want to interrupt current episode
- Fix: Auto-switch only between episodes, or ask user
- Impact: Better UX, less annoying
Issue: Smart Fill Overfills
- Problem: Target 45 min, get 60 min
- Fix: Tighter tolerance (ยฑ3 min), skip long episodes near end
- Impact: Queue too long for use case
Issue: Auto-Switch False Triggers
- Problem: Car detection triggers on friend's car Bluetooth
- Fix: Learn user's car Bluetooth MAC address, only switch for known devices
- Impact: Annoying unexpected switches
Issue: Queue Sync Conflicts
- Problem: Edit queue on two devices offline, then sync
- Fix: Union merge + notify user of conflict
- Impact: Episodes duplicated or lost
Issue: Smart Fill Empty
- Problem: No unplayed episodes match criteria
- Fix: Graceful empty state "No episodes available. Subscribe to more shows?"
- Impact: Confusing why fill didn't work
Testing Strategy
Unit Tests
- Smart fill algorithm (various subscriptions, durations)
- Queue ordering (drag-drop, add, remove)
- Auto-switch rule evaluation (mocked context)
Integration Tests
- CloudKit sync (two devices, offline edits)
- Context detection (simulate CarPlay, motion)
- Multi-queue playback (switch queues mid-playback)
User Scenarios
- Commuter: Create commute queue, auto-switch in car, smart fill 45 min
- Gym-goer: Workout queue with high-energy shows, auto-switch on run detection
- Multi-Device: Edit queue on iPhone, verify syncs to iPad
- Conflict: Edit same queue offline on two devices, verify merge
Output Format
QUEUE FEATURE: [Name]
Complexity: Low | Medium | High
Status: โ WORKING | โ ISSUES | โ BROKEN
FUNCTIONALITY:
Queue Operations: โ All Working | โ Some Broken | โ Critical Failure
Auto-Switching: โ Reliable | โ Occasional Misfire | โ Not Triggering
Smart Fill: โ Accurate | โ Overfills/Underfills | โ Not Working
CloudKit Sync: โ Conflict-Free | โ Occasional Issues | โ Data Loss
ISSUES:
- [Priority] [Description]
- Example: HIGH Smart fill not respecting season boundaries
RECOMMENDATIONS:
- [Improvement suggestion]
When invoked, ask: "Audit queue system?" or "Test [auto-switch | smart fill | sync]?" or "Validate queue [name]?"