soulsync/tests
Broque Thomas 698c21c3ce Auto-Sync Weekly Board: weekday schedules in the UI (PR 3/4)
PR 3 of the schedule-types feature — see
``memory/project_auto_sync_schedule_types.md``. Backend
``next_run_at`` + ``weekly_time`` trigger handler landed in PRs 1-2.
This PR exposes them in the Auto-Sync manager so users can finally
schedule playlists by day-of-week + time instead of only hourly
intervals.

**UI layout:**

The Auto-Sync modal grows a ``Weekly Board`` tab between
``Hourly Board`` (renamed from ``Schedule Board``) and
``Automation Pipelines``. Same sidebar (mirrored playlists grouped
by source, with filter). Main panel is 7 day columns Mon-Sun
instead of 10 hour buckets. Drag a playlist onto a day column →
creates a single-day weekly schedule at the default time
(09:00 in the browser's IANA tz from
``Intl.DateTimeFormat().resolvedOptions().timeZone``). Click any
scheduled card → opens an editor popover for time, multi-day
toggles, tz override, and unschedule.

Multi-day schedules render under every matching column (Mon-Wed-Fri
schedule appears as three cards, one per column) — matches how
users think about "this playlist runs on Mon AND Wed AND Fri".

**Mutual exclusion:** one schedule per playlist. The save path on
either tab deletes any existing schedule of the OTHER kind before
installing the new one. Backend can technically run both as two
separate automation rows, but two cards under the same playlist
would surprise users and the engine has no merge semantic for
"daily-and-hourly".

**Pure-function helpers** (testable via node:test, matching the
existing ``tests/static/test_auto_sync.mjs`` pattern):

- ``detectBrowserTimezone()`` — Intl tz with UTC fallback for
  browsers where Intl is absent.
- ``autoSyncWeeklyTrigger({time, days, tz})`` — defensive payload
  builder: garbage time → 09:00, unrecognised days dropped,
  missing tz → browser tz.
- ``autoSyncWeeklyFromTrigger(config)`` — inverse parser with
  the same defensive shape. Empty days expands to every weekday
  (matches ``next_run_at`` engine semantic). Returns null for
  non-object configs so ``buildAutoSyncScheduleState`` can route
  broken rows to automationPipelines instead of silently
  bucketing them as every-day weekly.
- ``autoSyncWeeklyLabel(parsed)`` — sorted "Mon, Wed, Fri @
  09:00" / collapses to "Daily @ HH:MM" for full-week / "Unscheduled"
  for null. Canonical Mon-Sun ordering regardless of input order.

**Tests:** 26 new node:test cases across ``detectBrowserTimezone``
x1, ``autoSyncWeeklyTrigger`` x6, ``autoSyncWeeklyFromTrigger`` x6,
``autoSyncWeeklyLabel`` x5, and ``buildAutoSyncScheduleState``
weekly bucketing x5 (covering owned weekly_time → weeklySchedules,
hourly stays in playlistSchedules, non-owned falls through to
automationPipelines, legacy-named auto-sync rows still recognised,
garbage trigger_config falls through). All 62 node:test cases pass;
261 across the automation pytest suite still green (zero regression
on PRs 1-2's plumbing). Python wrapper at
``tests/test_auto_sync_js.py`` shells out cleanly.

**CSS** (themed to the existing Auto-Sync gradient + accent
variables):
- 7-column grid for the weekly board, narrower than the 10
  hour-bucket layout.
- Editor popover with backdrop-blur, accent-tinted save / delete
  buttons, hover states that pick up the user's accent color.
- ``scheduled-elsewhere`` state for playlists with an hourly
  schedule visible on the weekly board (dashed border + opacity)
  so the user knows a drop will replace, not stack.

**WHATS_NEW entry** under 2.6.3 unreleased — first user-visible
slice of the schedule-types feature.

PR 4 (Monthly UI tab) deferred until weekly proves wanted.
2026-05-27 12:39:56 -07:00
..
artists Direct ID lookup in Enhance Quality, like Download Discography 2026-05-06 12:05:41 -07:00
automation Wire automation engine through next_run_at + register monthly_time (PR 2/4) 2026-05-27 12:03:41 -07:00
database Preserve Navidrome album cover art 2026-05-25 19:51:38 -07:00
discovery Branch cleanup: lift manual-match helpers, fix length-pref ordering, profile-scope view toggle 2026-05-27 07:43:21 -07:00
downloads Wishlist modal: surface most-advanced live phase, not least-complete 2026-05-26 22:35:53 -07:00
enrichment Honor manually-matched source IDs in per-source enrichment workers 2026-05-06 19:00:53 -07:00
imports Fix album-bundle downloads landing every track as track 1 2026-05-26 21:04:27 -07:00
library Test missing-track existing file imports 2026-05-17 14:18:17 -07:00
matching AcoustID + quarantine modal: three bug fixes (closes #607, closes #608) 2026-05-15 08:55:06 -07:00
media_server Preserve Navidrome album cover art 2026-05-25 19:51:38 -07:00
metadata Branch cleanup: lift manual-match helpers, fix length-pref ordering, profile-scope view toggle 2026-05-27 07:43:21 -07:00
playlists Expose mirrored playlist source-ref health 2026-05-24 19:32:39 -07:00
search feat(import): show MusicBrainz variants 2026-05-24 21:29:22 +03:00
static Auto-Sync Weekly Board: weekday schedules in the UI (PR 3/4) 2026-05-27 12:39:56 -07:00
stats
streaming
sync Address Cin review: extract helpers, indexed pool fetch, tidy nits 2026-05-24 23:33:55 -07:00
text Address Cin review: extract helpers, indexed pool fetch, tidy nits 2026-05-24 23:33:55 -07:00
tools test(amazon): update search_albums test for derived-from-tracks behavior 2026-05-19 18:24:27 -07:00
watchlist Complete MusicBrainz watchlist source parity 2026-05-18 19:19:25 -07:00
webui Fix missing album placeholder asset path 2026-05-22 08:34:42 -07:00
wishlist Wishlist: reify run id + gate cycle toggle on last-sibling completion 2026-05-26 21:50:42 -07:00
conftest.py
test_acoustid_scanner.py AcoustID scanner: multi-candidate match + duration guard + multi-value retag 2026-05-14 14:09:38 -07:00
test_acoustid_skip_logic.py
test_acoustid_version_mismatch.py Reject AcoustID matches whose version disagrees with the expected track 2026-05-07 13:25:30 -07:00
test_album_bundle.py Address kettui-flagged items on usenet poll fix (#706) 2026-05-27 10:05:37 -07:00
test_album_bundle_dispatch.py Add Soulseek album bundle downloads 2026-05-23 15:08:21 -07:00
test_album_completeness_job.py
test_album_mbid_consistency.py
test_archive_pipeline.py feat(downloads): add archive_pipeline module for torrent/usenet downloads 2026-05-20 17:05:00 -07:00
test_artist_top_tracks_clients.py Add download buttons + bulk action to artist top-tracks sidebar 2026-05-07 15:44:47 -07:00
test_audiodb_worker_stuck_track.py AudioDB worker: stop infinite loop on direct-ID lookup failure (#553) 2026-05-11 14:18:55 -07:00
test_auth_usage_throttle.py
test_auto_sync_js.py Add node:test contract for auto-sync.js helpers 2026-05-25 00:01:34 -07:00
test_config_save_retry.py
test_content_filter_regex.py
test_create_finding_dedup_counter.py Repair job card badge — show pending count, not last-scan count 2026-05-07 08:28:17 -07:00
test_dashboard_events.py
test_database_io_resilience.py Handle transient SQLite IO during maintenance 2026-05-21 17:50:30 -07:00
test_debug_info_services.py Fix Copy Debug Info music_source + surface missing services 2026-05-12 16:47:55 -07:00
test_discogs_collection_source.py Discogs: strip artist disambiguation suffixes at every name surface (#634) 2026-05-27 09:05:47 -07:00
test_discogs_track_count.py
test_discover_section_controller_js.py Discover controller — Cin pre-review polish 2026-05-07 20:35:10 -07:00
test_download_orchestrator_soundcloud.py
test_download_plugin_conformance.py Add torrent usenet PR notes and test updates 2026-05-21 15:28:48 -07:00
test_downloads_pagination.py
test_duplicate_detector_mount_paths.py
test_duplicate_detector_slskd_dedup.py
test_enrichment_services.py
test_featured_artist_completion.py
test_hifi_instance_methods.py Fix "no such table: hifi_instances" via defensive lazy-create 2026-05-06 17:49:44 -07:00
test_hls_parsing.py
test_image_cache.py feat(webui): cache artwork images on disk 2026-05-20 10:43:47 -07:00
test_import_album_match_endpoint.py Lift auto-import matching to testable helper + pin contracts 2026-05-09 09:13:23 -07:00
test_import_singles_parallel.py
test_integrity_failure_marks_task_failed.py
test_lb_series_detect.py Roll LB Weekly / Top series into single rolling mirrors (Phase 1c.2.1) 2026-05-26 15:49:49 -07:00
test_library_disk_usage.py Repair stale media schema during refresh 2026-05-21 17:41:54 -07:00
test_library_reorganize.py Rewrite Library Reorganize job to delegate to per-album planner 2026-05-06 21:18:20 -07:00
test_library_reorganize_orchestrator.py Drop flaky log-assertion in watchdog test, keep behavioural assertion 2026-05-10 00:01:03 -07:00
test_library_tag_payload.py Add _build_library_tag_db_data helper 2026-05-25 18:48:45 -07:00
test_library_track_identity.py
test_lidarr_download_client.py
test_listening_stats_batch_queries.py
test_lyrics_reembed_from_sidecar.py Retag now re-embeds LYRICS tag instead of leaving it empty 2026-05-14 15:52:05 -07:00
test_manual_library_match.py Honor manual library matches across source labels 2026-05-18 16:32:38 -07:00
test_manual_pick_no_auto_retry.py Add torrent and usenet release staging support 2026-05-21 14:22:21 -07:00
test_manual_search_endpoint.py Manual picks: stream results, don't auto-retry, fix stuck-at-0% 2026-05-08 15:12:58 -07:00
test_missing_cover_art.py
test_orphan_file_detector.py Fix Picard albumartist orphan false positives 2026-05-22 08:43:57 -07:00
test_personalized_api.py Personalized playlists (3/N): standardized API endpoints 2026-05-15 17:15:25 -07:00
test_personalized_generators_curated.py Personalized playlists (2/N): all 8 generators wired through manager 2026-05-15 17:02:08 -07:00
test_personalized_generators_singletons.py Personalized playlists (2/N): all 8 generators wired through manager 2026-05-15 17:02:08 -07:00
test_personalized_generators_variants.py Personalized playlists (2/N): all 8 generators wired through manager 2026-05-15 17:02:08 -07:00
test_personalized_manager.py Personalized pipeline: auto-refresh stale snapshots after watchlist scan 2026-05-15 20:53:03 -07:00
test_personalized_playlists_id_gate.py Discover: sharpen track selection (diversity, source-aware popularity, library dedup, SQL genre) 2026-05-08 08:49:22 -07:00
test_playlist_sources_adapters.py Fold provider-matching into PlaylistSource contract (Phase 1b) 2026-05-26 13:07:01 -07:00
test_provenance_id_persistence.py
test_prowlarr_client.py test: cover Prowlarr + torrent + usenet adapters 2026-05-20 15:43:18 -07:00
test_qobuz_credential_sync.py
test_qobuz_playlists.py Fetch all Qobuz favorite tracks for discovery 2026-05-24 01:17:38 -07:00
test_reorganize_db_methods.py
test_reorganize_orphan_format_handling.py Reorganize: move orphan-format siblings alongside the canonical 2026-05-10 19:58:31 -07:00
test_reorganize_queue.py
test_reorganize_runner.py
test_reorganize_tag_source.py Reorganize: optional embedded-tag mode (closes #592) 2026-05-15 07:56:18 -07:00
test_reorganize_unknown_artist_hint.py Reorganize: hint at Unknown Artist Fixer for placeholder-metadata rows 2026-05-10 20:16:28 -07:00
test_repair_worker_album_fill.py fix(album-completeness): block cross-artist auto-fill 2026-05-21 16:49:12 -07:00
test_repair_worker_unresolvable_folder_error.py Album Completeness: surface diagnostic when resolver can't find album folder 2026-05-12 14:04:15 -07:00
test_replaygain_summary_parse.py
test_request_cleanup_timer.py
test_runtime_state.py
test_script_split_integrity.py Add node:test contract for auto-sync.js helpers 2026-05-25 00:01:34 -07:00
test_server_playlist_append_mode.py Server playlist sync: append mode (preserve user-added tracks) 2026-05-10 22:52:11 -07:00
test_socketio_cors.py
test_soulseek_search_throttle.py Add min delay between slskd searches (Bell Canada anti-abuse fix) 2026-05-12 19:11:12 -07:00
test_soundcloud_client.py
test_spa_deep_linking.py feat: artist-detail deep linking — /artist-detail/:source/:id 2026-05-18 13:07:54 -07:00
test_staging_album_provenance.py refactor(staging): inject batch-field accessor instead of importing runtime_state 2026-05-20 20:43:35 -07:00
test_status_engine_fallback.py Manual picks: stream results, don't auto-retry, fix stuck-at-0% 2026-05-08 15:12:58 -07:00
test_sync_discovery_scan_events.py
test_tag_writer_multi_artist.py AcoustID scanner: multi-candidate match + duration guard + multi-value retag 2026-05-14 14:09:38 -07:00
test_tidal_album_tracks.py Your Albums: selectable wishlist modal + Tidal album resolution 2026-05-11 12:36:16 -07:00
test_tidal_auth_instructions.py
test_tidal_auth_redirect_uri.py
test_tidal_collection_tracks.py Tidal: surface Favorite Tracks as virtual playlist (issue #502) 2026-05-10 21:36:22 -07:00
test_tidal_favorite_albums_artists.py Tidal: rewire favorite albums + artists to V2 user-collection endpoints 2026-05-11 10:03:27 -07:00
test_tidal_qualifier_filter.py Fix MTV Unplugged & live-album false-quarantine pipeline 2026-05-14 12:14:31 -07:00
test_tidal_search_shortening.py
test_tool_progress_events.py
test_torrent_client_adapters.py test: cover Prowlarr + torrent + usenet adapters 2026-05-20 15:43:18 -07:00
test_torrent_usenet_plugins.py Fix Usenet settings reload without restart 2026-05-22 08:28:56 -07:00
test_track_artist_matching.py
test_track_number_repair.py
test_track_search_single_query.py
test_unknown_artist_fixer.py fix(repair): rewire Unknown Artist Fixer deferred imports (#646) 2026-05-19 22:09:15 -07:00
test_usenet_client_adapters.py Address kettui-flagged items on usenet poll fix (#706) 2026-05-27 10:05:37 -07:00
test_watchlist_album_match.py
test_watchlist_bulk_add.py Complete MusicBrainz watchlist source parity 2026-05-18 19:19:25 -07:00
test_watchlist_scanner_scan.py
test_websocket_client_transport.py
test_websocket_infrastructure.py
test_wishlist_pagination.py
test_worker_existing_id_marks_matched.py
test_worker_utils_album_track_count.py Repair stale media schema during refresh 2026-05-21 17:41:54 -07:00
test_youtube_ffmpeg_no_eager_download.py ffmpeg gate also covers is_available — fixes the actual leak path 2026-05-08 15:46:31 -07:00
test_youtube_search_dash_query.py Escape dash-leading YouTube search queries 2026-05-24 08:54:47 -07:00