feat: Auto-Playlists — unified management UI with master automation - #1019
feat: Auto-Playlists — unified management UI with master automation#1019shkarlsson wants to merge 11 commits into
Conversation
Add a new React page at /playlists for managing all personalized playlists from one place: Backend: - Add 'Auto-Playlist Refresh' system automation that runs hourly and discovers playlists with auto_refresh enabled (discover_due mode) - Add POST /api/personalized/playlist/<kind>/activate endpoint - Add PUT /api/personalized/playlist/<kind>/auto-refresh endpoint - Extend playlist API responses with auto_refresh and refresh_interval_hours fields - Add unplayed_tracks generator (JOIN artists table fix) - Add migration logic for legacy per-playlist personalized_pipeline automations Frontend: - Full React page with TanStack Router + React Query - Active Auto-Playlists section with auto-refresh toggle + frequency selector - Create New section with kind cards grouped by tag - Editable playlist names, config fields, track listing - Dark glassmorphism styling matching existing pages - Mobile responsive, keyboard accessible, focus-visible states - useReactPageShell integration, error/loading states
Backend: - Add manager.delete_playlist() to remove playlist + tracks from DB - Add api.delete_playlist() handler - Add DELETE /api/personalized/playlist/<kind> endpoint - Add recent_unheard generator: sort by created_at DESC, top N never-played tracks (replaces unplayed_tracks) - Keep unplayed_tracks registered for backward compat Frontend: - Replace auto-refresh toggle with Deactivate button (with confirm) - Add deletePlaylist API client function - Keep refresh interval selector as secondary control - Add btnDanger CSS style for deactivate button - Remove unused toggle/slider CSS classes Deactivation removes the playlist entirely (not just disabling auto-refresh). The new Recent Unheard generator sorts by recently added and takes the top N tracks with play_count = 0.
|
Updated with two changes: 1. Deactivation = Playlist Removal
2. "Recent Unheard" replaces "Unplayed Tracks"
|
Previously, tracks.created_at defaulted to CURRENT_TIMESTAMP (the SoulSync import time), ignoring the media server's addedAt field. This caused 'recently added' playlists to show tracks in import order rather than the order they were added to the library. Now: - INSERT uses the media server's addedAt for created_at - UPDATE corrects created_at when the new addedAt differs - Falls back to CURRENT_TIMESTAMP when addedAt is unavailable
…oute guard, toasts
|
hey, thanks for putting this much into it. the page genuinely looks great and you picked a real problem, managing auto-playlists deserves better than it has today. the one big thing standing in the way: this builds its own scheduler running parallel to the automation engine (per-playlist intervals in playlist config, polled by an hourly master job). all scheduling in soulsync lives in the engine, so this needs to as well. if the page drives automations the engine owns instead of its own refresh system, i'd be really happy to take it. two smaller things to catch in that rework: the migration disables automations users set up themselves, and the master job runs as profile 1 so auto-refresh never fires for anyone else. on the sync page there is a discovery playlist section but it's nothing like what you built. Replacing that but built on the automation engine would be huge |
# Conflicts: # core/automation_engine.py
|
Closing this to rework based on your feedback. The page will be rebuilt to drive automations the engine owns instead of its own parallel scheduler. Will reopen when it's ready. |
Rework auto-playlists to use the automation engine directly instead of a parallel scheduler (config_json.extra polling + discover_due master job). Backend: - Remove 'Auto-Playlist Refresh' system automation from SYSTEM_AUTOMATIONS - Remove discover_due orphan cleanup that disabled user automations - Add _migrate_auto_refresh_to_automations(): converts existing auto_refresh=True playlists to per-playlist automation rows - activate_playlist creates an automation row (owned_by='auto_playlist') - toggle_auto_refresh toggles the automation's enabled status - update_refresh_interval updates the automation's trigger_config - delete_playlist also deletes the associated automation row - Each automation carries the correct profile_id (not hardcoded 1) API: - New endpoints: activate, auto-refresh, refresh-interval, delete - _record_to_dict enriched with automation data from the engine - All handlers accept optional engine= parameter Frontend: - Types: add automation_id field - API: add updateRefreshInterval, fix toggleAutoRefresh signature - Page: intervalMutation uses updateRefreshInterval
|
Hi — reworked this based on your feedback. Here's what changed: Core change: per-playlist automation rows replace the parallel scheduler. Fixed the three issues you called out:
The existing playlists page and its UI are unchanged — same card grid, same track preview, same activate/deactivate/interval controls. The difference is entirely in what happens backend: create/toggle/delete real automations instead of storing flags in extra config. |
Backend: - C1: Move config_json cleanup inside 'if aid:' block (prevent data loss when automation creation fails) - C2: Delete playlist before automation (prevent orphaned state) - W2: Filter _find_playlist_automation by owned_by='auto_playlist' (prevent matching user-created automations) - W4: Single automation fetch in list_playlists (eliminate N+1 query) - W5: Add owned_by filter to migration duplicate check Frontend: - Remove dead toggleAutoRefresh export - Fix deletePlaylist return type to include error field - Remove unused kind prop from PlaylistCard - Remove dead kindsByKind memo
…ive clamping Backend: - _interval_to_trigger: clamp input to >= 1 - toggle_auto_refresh/update_refresh_interval: return error instead of silently creating playlist - delete_playlist: cancel_automation before delete_automation (timer leak) - migration: reuse _interval_to_trigger instead of duplicating logic - migration: hoist N+1 get_automations_by_action out of loop - web server: clamp refresh interval input to 1-168 - remove dead _discover_due_playlists code path Frontend: - ConfigField: blur-to-commit pattern (no per-keystroke API calls) - ConfigField: isPending guard on both config mutations - getConfig() reads from query cache to prevent stale closure overwrites - useEffect: guard name sync against overwriting in-progress edits - onBlur: guard against double-fire with isPending check - intervalMutation: guard against concurrent fire - select: fallback option for non-standard refresh intervals - refreshMutation: check result.error field - retry button on error state
|
hey, this rework is exactly what i asked for. i verified the big three myself: scheduling now lives entirely in the engine (real automation rows, schedule/cancel/toggle), the migration no longer touches anything users made (i ran it against a db with an auto-sync board row and it stayed untouched, and it's idempotent), and the rows carry the owner's profile_id so the engine runs them as that profile. backend logic is solid, all existing tests pass on a merge with current dev. three things before i can merge it:
couple small ones while you're in there: the old unplayed_tracks kind is still registered so users see both it and recent unheard as create cards, hide the deprecated one from the listing. and the package-lock changes are just your npm version stripping libc fields, please drop that file from the pr since package.json didn't change. fix those and i'm happy to take this. |
Summary
Adds a new React page at
/playlistsfor managing all personalized playlists from one place, with a single master automation that auto-refreshes them on configurable per-playlist schedules.Backend Changes
auto_refreshenabled inconfig.extra, checks theirrefresh_interval_hoursvslast_generated_atPOST /api/personalized/playlist/<kind>/activate— creates playlist, enables auto-refresh, runs first generationPUT /api/personalized/playlist/<kind>/auto-refresh— toggles auto-refresh or changes intervalauto_refreshandrefresh_interval_hoursfieldsunplayed_tracksgenerator (fixed JOIN onartiststable)personalized_pipelineautomationsFrontend Changes
useReactPageShellintegrationDesign Decisions
config_json.extradict used forauto_refreshandrefresh_interval_hours— no schema migration neededdiscover_dueflag in pipeline action_config — clean separation between hardcoded-kind runs and dynamic discoveryCloses #N/A