Commit Graph
2155 Commits
Author SHA1 Message Date
vicrodh 8ac5ba92fd refactor(v2): enforce backend purity and stabilize playback runtime
cx
2026-02-16 07:45:43 -06:00
vicrodh d64e0f38d7 chore(v2): apply phase-2 legacy cut-off for auth playback queue and context surface
cx
2026-02-16 04:14:13 -06:00
vicrodh 9e126a0c59 chore(v2): apply legacy invoke cut-off markers in handler with rollback notes
cx
2026-02-16 04:08:22 -06:00
vicrodh ee5998533d refactor(v2): route library flows through shared impls and remove command-to-command calls
cx
2026-02-16 04:05:16 -06:00
vicrodh d2a13ba95e refactor(v2): internalize notification offline playlist reset and reco training flows
cx
2026-02-16 04:02:52 -06:00
vicrodh 0aecda77e5 refactor(v2): migrate remaining unknown frontend invokes to v2 commands
cx
2026-02-16 03:53:24 -06:00
vicrodh d02b8a9628 refactor(v2): eliminate legacy frontend invokes and close remaining v2 command gaps
cx
2026-02-16 03:51:01 -06:00
vicrodh 54af46fea8 refactor(v2): close missing command surface for cast, library, offline and radio
cx
2026-02-16 03:35:52 -06:00
vicrodh d4b5dedc90 chore(v2): add migration guardrails for backend purity and invoke audit
cx
2026-02-16 03:29:02 -06:00
vicrodh 432dd828c1 refactor(v2): add native settings/cache/plex command surface
cx
2026-02-16 03:28:47 -06:00
vicrodh fafa98c46a refactor(v2): remove post-cutoff backend wrapper wave and restore pure v2 surface
cx
2026-02-16 03:10:21 -06:00
vicrodh 65632f08d5 refactor(frontend): migrate Library and Offline commands to V2
Update frontend to use v2_library_* and v2_get_pending_playlists.
Progress: 138 legacy commands remaining.
2026-02-16 02:52:15 -06:00
vicrodh 8d3627b743 feat(v2): add Library and Offline V2 command wrappers
Add V2 wrappers for frequently used commands:
- Library: play_track, get_albums, get_tracks_by_ids, check_folder_accessible,
  set_album_hidden, get_scan_progress, clear_artwork_cache,
  clear_thumbnails_cache, remove_folder, set_album_artwork
- Offline: get_pending_playlists, playlist_get_all_local_track_counts
2026-02-16 02:49:31 -06:00
vicrodh e4fe4598d0 refactor(frontend): migrate playlist and settings commands to V2
Batch migrate frontend invocations to V2:
- Playlist commands (22 commands)
- Settings/preferences commands
- Cache and audio commands
- API commands (radio, genres, tags, etc.)

Progress: reduced legacy commands from ~228 to ~152
2026-02-16 02:40:50 -06:00
vicrodh 12246ea2ad feat(v2): add more playlist V2 command wrappers
Add V2 wrappers for playlist commands:
- get_settings, get_all_settings
- get_stats, get_all_stats, increment_play_count
- get_favorites, get_local_tracks, get_local_tracks_with_position
- get_custom_order, has_custom_order, get_tracks_with_local_copies
- import_preview, import_execute
2026-02-16 02:28:33 -06:00
vicrodh 1de7c7830e refactor(frontend): migrate Cast/DLNA/AirPlay commands to V2
Update all casting invocations to use v2_* commands:
- castStore: connect, disconnect, play, pause, stop, seek, volume
- CastPicker: device discovery
2026-02-16 02:25:32 -06:00
vicrodh 3ede4e2fc6 refactor(frontend): migrate Plex commands to V2
Update all Plex invocations to use v2_plex_* commands:
- SettingsView: auth, ping, cache operations
- LocalLibraryView: albums, tracks, search, hydration
- playerStore/playbackService: play_track
2026-02-16 01:47:33 -06:00
vicrodh 5dbd383bab feat(v2): add Plex V2 command wrappers
Wrap all 18 Plex commands with V2 RuntimeError handling:
- Network: ping, get_music_sections, get_section_tracks, get_track_metadata
- Auth: auth_pin_start, auth_pin_check, open_auth_url
- Cache: get_sections, save_sections, get_tracks, get_albums,
  get_album_tracks, search_tracks, save_tracks, update_track_quality,
  get_tracks_needing_hydration, clear
- Playback: play_track

Completes the Plex category of V2 migration.
2026-02-16 01:03:45 -06:00
vicrodh e565b964a5 feat(v2): add Other V2 commands
Add V2 commands for miscellaneous categories:

Audio Backends (7):
- v2_get_available_backends, v2_get_devices_for_backend
- v2_get_alsa_plugins, v2_check_alsa_utils_installed
- v2_get_default_device_name, v2_get_linux_distro
- v2_query_dac_capabilities

Audio Diagnostics (3):
- v2_get_hardware_audio_status
- v2_start_bitdepth_capture, v2_stop_bitdepth_capture

Radio (5):
- v2_create_artist_radio, v2_create_track_radio
- v2_refill_radio_queue, v2_get_queue_remaining
- v2_create_infinite_radio

Share (3):
- v2_get_qobuz_track_url/album_url/artist_url

Additional (7):
- v2_is_visualizer_enabled
- v2_get_current_queue_track, v2_peek_next_track
- v2_get_shuffle, v2_get_repeat
- v2_get_current_user_id
- v2_has_saved_credentials, v2_save_credentials

Total 25 new V2 commands.
2026-02-16 01:00:18 -06:00
vicrodh d39fe148ed feat(v2): add Cast/DLNA/AirPlay V2 commands
Add 39 V2 commands for casting:

Chromecast (14):
- v2_cast_start_discovery, v2_cast_stop_discovery
- v2_cast_get_devices, v2_cast_connect, v2_cast_disconnect
- v2_cast_get_status, v2_cast_get_position
- v2_cast_play_track, v2_cast_play_local_track
- v2_cast_play, v2_cast_pause, v2_cast_stop
- v2_cast_seek, v2_cast_set_volume

DLNA (14):
- v2_dlna_start_discovery, v2_dlna_stop_discovery
- v2_dlna_get_devices, v2_dlna_connect, v2_dlna_disconnect
- v2_dlna_get_status, v2_dlna_get_position
- v2_dlna_play_track, v2_dlna_load_media
- v2_dlna_play, v2_dlna_pause, v2_dlna_stop
- v2_dlna_seek, v2_dlna_set_volume

AirPlay (11):
- v2_airplay_start_discovery, v2_airplay_stop_discovery
- v2_airplay_get_devices, v2_airplay_connect
- v2_airplay_disconnect, v2_airplay_get_status
- v2_airplay_load_media
- v2_airplay_play, v2_airplay_pause, v2_airplay_stop
- v2_airplay_set_volume

All commands wrap existing implementations with RuntimeError.
2026-02-16 00:52:57 -06:00
vicrodh d269acc709 feat(v2): add playlist custom V2 commands
Add 10 V2 commands for playlist customization:
- v2_playlist_set_sort
- v2_playlist_set_artwork
- v2_playlist_add_local_track
- v2_playlist_remove_local_track
- v2_playlist_set_hidden
- v2_playlist_set_favorite
- v2_playlist_reorder (reorder playlists in sidebar)
- v2_playlist_init_custom_order
- v2_playlist_set_custom_order
- v2_playlist_move_track

All commands wrap existing library implementations with
proper type signatures and RuntimeError handling.
2026-02-16 00:50:57 -06:00
vicrodh a405954846 feat(v2): add V2 commands for cache management (clear cache, artist cache, vector store, offline cache, credentials) 2026-02-16 00:11:21 -06:00
vicrodh 477b032abe feat(v2): add V2 commands for settings (tray, graphics, window, developer, playback, favorites prefs, visualizer, blacklist, locale) 2026-02-16 00:07:12 -06:00
vicrodh 18e7303aaf fix: update remaining sync_cached_favorite_tracks to V2 2026-02-15 22:07:51 -06:00
vicrodh 68aec71fdf fix(v2): migrate favorites cache, session, and context stores to V2 commands
- Add v2_save_session_state and v2_load_session_state commands
- Update favoritesStore to use V2 cache commands
- Update albumFavoritesStore to use V2 cache commands
- Update artistFavoritesStore to use V2 cache commands
- Update sessionService to use V2 session commands
- Update playbackContextStore to use V2 context commands
2026-02-15 21:55:15 -06:00
vicrodh 35758d07df feat(v2): add V2 commands for playback context, session, and favorites cache
- Add ContextType::Search variant
- Add V2 playback context commands (get, set, clear, has)
- Add V2 session persistence commands (save position/volume/mode, clear)
- Add V2 favorites cache commands (get/sync/add/remove for tracks/albums/artists)
- Register all new commands in lib.rs
2026-02-15 21:41:43 -06:00
vicrodh 7337b7e026 fix(v2): match V2QueueTrack field names to frontend interface
Remove serde rename attributes that caused mismatch:
- Frontend sends 'duration_secs', backend expected 'duration'
- Frontend sends 'artwork_url', backend expected 'artwork'

Now V2QueueTrack field names match BackendQueueTrack exactly.
2026-02-15 20:36:47 -06:00
vicrodh 045cf24a61 fix(v2): unify queue architecture - all commands use CoreBridge
Previously, V2 queue commands had a dual-queue bug:
- Writes (v2_set_queue, v2_add_to_queue, etc.) went to AppState.queue (legacy)
- Reads (v2_get_queue_state) came from CoreBridge/QbzCore queue
- Result: frontend sets queue, syncs, gets empty/stale data

This commit fixes the architecture:
- All V2 queue commands now use CoreBridge methods
- Added queue methods to QbzCore: add_track, add_tracks, add_track_next,
  set_queue, remove_track, remove_upcoming_track, move_track, play_index,
  next_track, previous_track, peek_upcoming
- Added corresponding methods to CoreBridge
- Fixed v2_set_shuffle to use CoreBridge (was using legacy AppState)
- Fixed runtime requirements: v2_toggle_shuffle and v2_clear_queue now
  use RequiresUserSession like other queue commands (consistency)
- Prefetch now gets upcoming tracks from CoreBridge, not legacy queue
- Updated tests to match V2 behavior (v2_toggle_shuffle, available field)
2026-02-15 20:07:01 -06:00
vicrodh 8d26f547d1 fix(frontend): resolve all remaining TypeScript errors
- HomeView: fix OfflineCacheStatus type for getTrackOfflineCacheStatus prop
- PlaylistDetailView: fix trackToReplace null access in console.log
- SearchView: add id and isViewMore to Album/Artist interfaces
- Toast: add 'warning' type support
- playbackContextStore: add 'search' to ContextType union
- queueStore: add trackId to QueueTrack interface
- trackActions: add queueDisplayTrackNext/Later and buildQueueTrackFromDisplayTrack
- +page.svelte: fix numerous type issues including:
  - selectedAlbum closure captures for TypeScript narrowing
  - handleDisplayTrackDownload signature for DisplayTrack
  - handleSearchTrackDownload for SearchView's Track type
  - ImportSummary callback signature
  - QobuzTrack hires_streamable property usage
  - album.tracks.items instead of .data

npm run check now passes with 0 errors (138 warnings remain, mostly a11y)
2026-02-15 17:20:05 -06:00
vicrodh e7d617d46d fix(frontend): additional type error fixes
- SearchView.svelte: Fix async onMount pattern to properly return cleanup
- vite.config.js: Add ts-nocheck to suppress Node.js global errors

Reduces npm run check errors from 42 to 40.
2026-02-15 16:29:57 -06:00
vicrodh 8becf36842 fix(frontend): resolve type errors in multiple components
- miniplayerService.ts: Use PhysicalSize/PhysicalPosition classes instead of plain objects
- AudioOutputBadges.svelte: Add null checks for settings
- SettingsView.svelte: Add gapless_enabled to AudioSettings interface, fix return types
- SearchView.svelte: Fix variable declaration ordering for $derived dependencies
- LocalLibraryView.svelte: Add tick import, fix loadAlbums -> loadLibraryData, wrap onclick handler

Reduces npm run check errors from 66 to 42.
2026-02-15 16:27:53 -06:00
vicrodh 81e9279add fix(v2): proper teardown for V2 integration state
ListenBrainzV2State::clear_credentials() now resets internal client to
fresh state, not just wrapper fields.

LastFmV2State:
- Added clear_session() method that clears session_key AND pending_token
- init_with_session(None) now calls clear_session() instead of no-op

Ensures no credentials leak across sessions on logout/deactivate.

Closes Must-Fix #2 from MUST_FIX_BEFORE_RELEASE_RUNTIME_V2.md
2026-02-15 16:06:43 -06:00
vicrodh af910c4617 fix(v2): add transactional auth rollback on login failures
Added rollback_auth_state() helper that clears legacy_auth, corebridge_auth,
and emits AuthChanged event on partial login failures.

Applied to all error paths in v2_auto_login, v2_manual_login, and v2_login:
- CoreBridge auth failure now rolls back
- Session activation failure now rolls back

Ensures runtime_get_status never reports half-authenticated state.

Closes Must-Fix #1 from MUST_FIX_BEFORE_RELEASE_RUNTIME_V2.md
2026-02-15 16:06:37 -06:00
vicrodh 233d3c87ef refactor(frontend): migrate all integration calls to V2 commands
Complete frontend migration eliminating "double brain" pattern:

- bootstrap.ts: Last.fm session restore uses v2_lastfm_set_session
- playbackService.ts: All scrobbling uses V2 ListenBrainz and Last.fm
- SettingsView.svelte: All integration settings use V2 commands
  - Last.fm auth flow: v2_lastfm_get_auth_url, v2_lastfm_complete_auth
  - ListenBrainz: v2_listenbrainz_get_status, connect, disconnect
  - MusicBrainz: v2_musicbrainz_is_enabled, set_enabled
  - Audio reinit: removed legacy reinit_audio_device, V2 only

Frontend now uses ZERO legacy integration commands.
2026-02-15 15:37:16 -06:00
vicrodh 130c95dbdf feat(v2): add MusicBrainz metadata support to ListenBrainz commands
V2 ListenBrainz now_playing and scrobble commands now accept optional
MusicBrainz identifiers (recording_mbid, release_mbid, artist_mbids),
ISRC, and duration_ms parameters for richer scrobbles.

The AdditionalInfo struct is populated when any metadata is provided,
automatically including QBZ media player identification.
2026-02-15 15:37:10 -06:00
vicrodh 53927b5c6f Add missing V2 commands and deprecate legacy integration commands
New V2 commands:
- v2_lastfm_set_session: Set session key for restored sessions
- v2_listenbrainz_queue_listen: Queue listen for offline (uses legacy cache)

Deprecated legacy commands (with #[deprecated] markers):
- ListenBrainz: get_status, is_enabled, set_enabled, connect, disconnect,
  now_playing, scrobble, queue_listen
- Last.fm: is_authenticated, get_auth_url, authenticate, set_session,
  disconnect, scrobble, now_playing
- MusicBrainz: is_enabled, set_enabled
- Audio: reinit_audio_device

All deprecated commands point to their V2 replacements in the note.
Frontend migration to V2 commands is next.
2026-02-15 15:29:59 -06:00
vicrodh 943f886637 Add V2 integration states to session lifecycle
Integrates ListenBrainzV2State, MusicBrainzV2State, and LastFmV2State
from qbz-integrations crate into the session activation/deactivation
lifecycle.

In activate_session:
- After legacy integrations init (which loads from SQLite), sync
  credentials to V2 states
- ListenBrainz V2: gets token, user_name, enabled from legacy client
- MusicBrainz V2: gets enabled state (use_proxy defaults to true)
- LastFm V2: initialized to clean state (no legacy equivalent)

In deactivate_session:
- Clear V2 state credentials to prevent cross-user leaks
- Reset states to defaults

This ensures V2 commands (using qbz-integrations crate) have access
to the same credentials as legacy commands, enabling a smooth
migration path.
2026-02-15 15:06:59 -06:00
vicrodh 856ceb796d Enforce ToS gate in backend for all login entrypoints
Add centralized require_tos_accepted() helper function that checks
ToS acceptance before any authentication attempt. This prevents
non-UI clients (TUI/CLI/tests) from bypassing the ToS gate.

Affected commands:
- v2_auto_login: Returns tos_not_accepted error code
- v2_manual_login: Returns tos_not_accepted error code
- v2_login: Returns RuntimeError::Internal

The ToS check was already in runtime_bootstrap, but these commands
could be called directly. Now the gate is enforced at the backend
level regardless of entry point.

Also updates obsolete comment about frontend handling ToS.
2026-02-15 15:02:15 -06:00
vicrodh d0fb30a5b1 fix(auth): enforce ToS gate and session activation severity parity
Fixes login consistency issues:

1. ToS gate enforcement:
   - LegalSettingsState now initialized GLOBALLY at app startup (not per-session)
   - runtime_bootstrap checks ToS BEFORE attempting auto-login
   - If ToS not accepted, skip auto-login and return to login screen

2. Session activation severity parity:
   - runtime_bootstrap now treats activate_session failure as FATAL
   - On failure: rollback auth state, set degraded, return error
   - Matches behavior of v2_auto_login/v2_manual_login

3. LegalSettings is now truly global:
   - Removed from session_lifecycle activate/deactivate
   - ToS acceptance persists across sessions
   - Can be checked before user is logged in

Acceptance criteria:
- ToS false + credentials -> no auto-login
- ToS true + credentials + activation OK -> session restored
- ToS true + credentials + activation fails -> error, no session
2026-02-15 14:55:14 -06:00
vicrodh 63994c0a20 refactor(cast): extract qbz-cast crate
Extract casting functionality (Chromecast, AirPlay, DLNA) to standalone
qbz-cast crate. This crate is Tauri-agnostic and can be used by TUI/CLI
clients.

- Move device connection, discovery, threading, and media server to crate
- Keep only Tauri commands in src-tauri/src/cast/
- Thin re-exports in src-tauri for backwards compatibility
- Remove duplicate dependencies from src-tauri Cargo.toml
2026-02-15 14:51:06 -06:00
vicrodh deece54bcb fix: eliminate double-brain session lifecycle (legacy vs V2)
Backend:
- v2_auto_login/v2_manual_login now FAIL if activate_session fails
  (was: log and continue with success:true, causing "logged in but not ready")
- Session activation is now REQUIRED, not optional

Frontend:
- Remove duplicate activate_user_session call from handleLoginSuccess
  (V2 login already activates session internally)
- Remove duplicate deactivate_user_session call from handleLogout
  (v2_logout already deactivates session internally)
- LoginView now uses runtime_bootstrap as SINGLE SOURCE OF TRUTH
  (was: v2_init_client + legacy is_logged_in/get_user_info checks)
- PlaylistDetailView uses runtime_get_status for user_id
  (was: legacy get_current_user_id)

This eliminates the "double-brain" where frontend used both legacy
and V2 paths, causing state divergence and intermittent failures.
2026-02-15 14:39:44 -06:00
vicrodh 59e3141aa6 fix: address 7 code review findings for V2 architecture
HIGH priority fixes:
- Enable WAL mode in qbz-integrations caches (ADR-002)
- v2_auto_login/v2_manual_login now call activate_session()
- RequiresUserSession allows offline sessions (only checks session_activated)

MEDIUM priority fixes:
- Consolidate lifecycle: legacy commands delegate to session_lifecycle
- ListenBrainz teardown now calls disconnect() to clear tokens
- Add SessionActivationFailed degraded reason and emit event on failure
2026-02-15 12:21:17 -06:00
vicrodh d6e4b3349b feat(qbz-library): extract local library management to standalone crate
Create qbz-library crate with:
- LibraryScanner: directory scanning for audio files
- MetadataExtractor: audio metadata extraction via lofty
- LibraryDatabase: SQLite persistence with WAL mode
- CueParser: CUE sheet parsing for single-file albums
- Thumbnails: artwork extraction and thumbnail generation
- Tag sidecar: album metadata override files

src-tauri re-exports from qbz-library for backwards compatibility.
Enables TUI/headless clients to manage local libraries without Tauri.
2026-02-15 12:12:52 -06:00
vicrodh 384459d4d7 feat(qbz-cache): extract audio caching to standalone crate
Create qbz-cache crate with:
- AudioCache: L1 in-memory LRU cache (~300MB)
- PlaybackCache: L2 disk-based cache (~500MB)
- LRU eviction with spillover from L1 to L2

src-tauri now re-exports from qbz-cache for backwards compatibility.
This enables TUI/headless clients to use caching without Tauri.
2026-02-15 11:27:38 -06:00
vicrodh a66d5a2af3 refactor(v2): use RuntimeError in remaining V2 commands
Convert all remaining V2 commands from Result<T, String> to Result<T, RuntimeError>:
- runtime_get_status, v2_is_logged_in
- v2_login, v2_logout, v2_activate_offline_session
- v2_play_track, v2_get_discover_albums
- Search commands: v2_search_albums, v2_search_tracks, v2_search_artists
- Catalog commands: v2_get_album, v2_get_track, v2_get_artist
- Favorites commands: v2_get_favorites, v2_add_favorite, v2_remove_favorite
- Playlist commands: full suite including CRUD operations
- Extended catalog: v2_get_tracks_batch, v2_get_genres, v2_get_discover_*
- Artist commands: v2_get_artist_page, v2_get_similar_artists, v2_get_artist_with_albums
- v2_get_label

Only v2_auto_login/v2_manual_login retain String (intentional - use V2LoginResponse error fields)
and download_audio (internal utility wrapped by callers).
2026-02-15 11:23:16 -06:00
vicrodh 8a6050f2f3 refactor(v2): use RuntimeError in queue and playback commands
Convert queue commands (16) and playback commands (10) from String
errors to typed RuntimeError:
- Queue: get_queue_state, set_repeat_mode, toggle_shuffle, etc.
- Playback: pause, resume, stop, seek, volume, get_state, etc.

Runtime checks now use direct ? propagation instead of map_err.
Bridge method errors wrapped with RuntimeError::Internal.
2026-02-15 11:15:13 -06:00
vicrodh 0c50c67a61 feat(v2): add integrations V2 commands using qbz-integrations crate
Wire qbz-integrations to Tauri via new V2 commands:

ListenBrainz V2:
- v2_listenbrainz_get_status, is_enabled, set_enabled
- v2_listenbrainz_connect, disconnect
- v2_listenbrainz_now_playing, scrobble

MusicBrainz V2:
- v2_musicbrainz_is_enabled, set_enabled
- v2_musicbrainz_resolve_track, resolve_artist

Last.fm V2:
- v2_lastfm_get_auth_url, complete_auth
- v2_lastfm_is_authenticated, disconnect
- v2_lastfm_now_playing, scrobble

New module integrations_v2.rs provides Tauri state wrappers for the
Tauri-independent clients from qbz-integrations crate.
2026-02-15 11:11:35 -06:00
vicrodh 6310e9b216 chore: add qbz-integrations crate dependency
Wire qbz-integrations to src-tauri Cargo.toml. The crate provides
Tauri-independent implementations of Last.fm, ListenBrainz, and
MusicBrainz integrations.

Next step: Create V2 commands that use the new crate, then gradually
deprecate the existing in-tree implementations.
2026-02-15 11:05:33 -06:00
vicrodh fbb7f3c226 refactor(v2): use RuntimeError in audio settings commands
Replace String errors with typed RuntimeError variants across all 17
audio settings V2 commands for consistent error handling:
- Lock errors: RuntimeError::Internal
- Session not active: RuntimeError::UserSessionNotActivated
- Store errors: RuntimeError::Internal

This aligns with the runtime contract (ADR_RUNTIME_SESSION_CONTRACT.md)
for typed errors that clients can pattern match.
2026-02-15 10:58:09 -06:00
vicrodh e70c577181 refactor: centralize session lifecycle, make runtime_bootstrap autocontenido
Addresses multiple review findings for V2 architecture:

1. Create session_lifecycle.rs module:
   - activate_session(): Full session activation callable from anywhere
   - deactivate_session(): Full teardown with proper runtime state cleanup
   - activate_offline_session(): Minimal session for offline/local-only use

2. Make runtime_bootstrap autocontenido:
   - Now activates session after successful auth (Step 4)
   - No longer delegates to frontend for session activation

3. Fix v2_login to be canonical:
   - Performs full flow: legacy auth -> corebridge auth -> session activation
   - Updates all runtime state flags

4. Fix v2_logout to be canonical:
   - Calls deactivate_session (teardown stores + clear runtime state)
   - Proper cleanup of legacy and corebridge auth

5. Fix deactivate_session:
   - Now clears session_activated flag (was missing)
   - Now clears corebridge_auth flag (was missing)
   - Emits UserSessionDeactivated event

6. Add v2_set_repeat_mode runtime check:
   - Now requires RequiresUserSession like other queue mutations

7. Fix LoginView early session activation:
   - Removed premature activate_user_session call before auto-login confirmed
   - ToS check uses localStorage fallback if no session

8. Fix offline mode:
   - Add v2_activate_offline_session command
   - Offline mode now activates minimal session so queue commands work
2026-02-15 10:53:49 -06:00