Commit Graph
96 Commits
Author SHA1 Message Date
vicrodh 88af1b248b refactor(qbzd): scrobble CLI verbs use the canonical ScrobblerSettingsStore
Migrate `qbzd scrobble login|status|enable|disable` off the interim custom
`crate::scrobbler` (scrobbler.json) onto the canonical
`qbz_app::settings::scrobblers::ScrobblerSettingsStore` (SQLite at the daemon
data root) — the SAME store the scrobble-on-play engine reads and the settings
bundle export/import already carries, so credentials are consistent across the
daemon, the desktop store format, and bundle transport. Session keys / tokens are
registered with `qbz_log::register_secret` before they touch the store.
enable/disable now refuses a not-connected provider up front (`not_connected`)
and status reads `lastfm_is_authed()/active()` from the store snapshot.

This was the orphaned tail of the store migration the scrobble-on-play engine
and TUI screen already build on; committing it closes the gap.

cc
2026-07-19 22:25:41 -06:00
vicrodh bdbdf4e0a1 feat(qbzd): publish MPRIS media controls (KDE/GNOME widget + media keys)
Add a daemon MPRIS service (CONSOLE ext) via qbz-media-controls (mpris-server on
Linux, incl. the DesktopEntry that lets GNOME/KDE resolve the app icon). A KDE
Plasma media widget, a plasmoid, or hardware media keys now drive the daemon
with NO custom client code — the marquee third-party-integration surface.

Two halves in crate::mpris:
- OUTBOUND: a CoreEvent-bus subscriber pushes now-playing metadata, play/pause,
  live position, and volume into the OS controls (seeded once from live state).
- INBOUND: the qbz-media-controls callback maps MediaEvent (Play/Pause/Toggle/
  Next/Previous/Stop/Seek/SetPosition/SetVolume) onto core transport commands;
  seek/volume honor the DSD-direct guard; Next/Previous fire the full advance
  ritual off the D-Bus thread; Raise/Quit are ignored (headless).

The inbound callback holds only a Weak<AppRuntime> (upgraded per event) and the
updater task upgrades a Weak once to seed then drops it, so the integration never
pins the runtime — the #521 audio-release-before-drop(booted) ordering is
unaffected. Spawned next to the scrobbler in run(); torn down (updater abort +
handle drop) in shutdown. On by default where a session bus exists; QBZD_MPRIS in
{0,false,off,no} disables it, and spawn returns None gracefully on a headless
box with no D-Bus. 280 tests green.

cc
2026-07-19 22:24:41 -06:00
vicrodh 903acdb044 feat(qbzd): persist + drain ListenBrainz offline queue in scrobble engine
A failed `submit_listen` now writes the listen to the SHARED
`ListenBrainzCache.listen_queue` (daemon-root `cache/listenbrainz_v2.db`, the
same schema the desktop uses) instead of being lost. The scrobble task's loop
becomes a biased `select!` between the CoreEvent bus and a 120s drain interval
(the interval also fires once at task start), so a queue built up while offline
is retried oldest-first and cleared on reconnect, stopping at the first failure
and resuming on the next tick. Mirrors `qbz::scrobble::flush_listenbrainz_queue`:
the rusqlite Connection is opened inside a `spawn_blocking` per op and never held
across an await. The daemon uses its own `qbzd` cache root — it never touches the
desktop's dirs.

cc
2026-07-19 20:17:19 -06:00
vicrodh 09a46b694d feat(qbzd): add Scrobbler setup-TUI screen (Last.fm / ListenBrainz)
Add an eighth setup-TUI section (`qbzd setup`) for scrobbler auth, mirroring
the Account browser-login methodology: the App suspends the alt-screen and runs
the CLI connect flow on the plain terminal, then reloads the screen's settings
snapshot on resume. Last.fm prints an authorize URL and waits for Enter (the CLI
verb owns that); ListenBrainz prompts for a pasted user token, then hands it to
the CLI verb. Both write the canonical ScrobblerSettingsStore, so enable/disable
stays a CLI verb and the screen only shows connection state.

Scrobbler is appended LAST in SCREENS so no existing section index or number-key
jump shifts; the number-key jump range widens 1-7 -> 1-8. The D7 six-screen cap
was already lifted for FB4's Wizard; this is the second owner-sanctioned break.

cc
2026-07-19 20:13:39 -06:00
vicrodh 1c5c44b904 feat(qbzd): scrobble-on-play engine — Last.fm / ListenBrainz (CONSOLE)
The daemon now scrobbles what it plays. A CoreEvent-bus subscriber
(scrobble_engine, spawned in run() next to queue-persist):
- on TrackStarted -> "now playing" to each active provider (LastFm
  update_now_playing / ListenBrainz submit_playing_now);
- scrobbles ONCE when the position crosses the threshold from
  qbz_app::scrobble_timing::scrobble_delay_secs (Last.fm's half-or-4-min rule)
  -> LastFm scrobble / ListenBrainz submit_listen, best-effort + logged;
- re-reads the canonical ScrobblerSettingsStore on each track, so `qbzd scrobble
  login/enable/disable` take effect on the next track with no reload signal;
- holds no Arc<AppRuntime>; aborted on shutdown alongside queue-persist.

Album is dropped when empty / "Unknown Album". Pure helpers (due, album_opt)
are unit-tested. The ListenBrainz offline queue (ListenBrainzCache) is a
follow-up — this slice submits best-effort.

Verified: cargo test -p qbzd --bin qbzd -> 278 passed, 0 failed.

cc
2026-07-19 19:22:50 -06:00
vicrodh 17de908225 feat(qbzd): scrobbling auth foundation — Last.fm / ListenBrainz (CONSOLE)
The connect flows, same methodology as `qbzd login`:
- `scrobble login lastfm`: LastFmClient::get_token prints an authorize URL, the
  user approves on last.fm, get_session exchanges it for a session key.
- `scrobble login listenbrainz --token <T>`: ListenBrainzClient::set_token
  validates a pasted user token (like `login --token`).
- `scrobble status` · `scrobble enable|disable <provider>`.

Credentials land in the CANONICAL scrobbler store
(qbz_app::settings::scrobblers::ScrobblerSettingsStore — SQLite at the daemon
data root), the SAME store the desktop uses and the one the settings bundle
export/import already carries, so scrobbler creds travel with `settings export`.
Secrets registered with qbz_log; a running daemon is nudged to reload.

- Cargo.toml: qbz-integrations path dep (already in-tree via qbz-core).
- cli/scrobble.rs: the verbs. main.rs: Scrobble subcommand group.

Verified: cargo test -p qbzd --bin qbzd -> 276 passed, 0 failed.

NOTE: lands CREDENTIALS + auth only. Scrobble-on-play (a daemon task on the
CoreEvent bus: update_now_playing on TrackStarted, scrobble past
scrobble_delay_secs, ListenBrainz submit + offline queue) is the next slice —
it wires the playback driver. The setup-TUI Scrobbler screen is also pending.

cc
2026-07-19 19:11:43 -06:00
vicrodh 620af9d75d feat(qbzd): add lyrics verb + GET /api/lyrics (CONSOLE)
- qbz-core: thin QbzCore::get_lyrics wrapper over QobuzClient::get_lyrics
  (additive; lets qbzd avoid the client() escape hatch).
- api/lyrics.rs: GET /api/lyrics?id=<TRACK_ID|current>; flattens the tagged
  QobuzLyricsDocument union to {track_id, synced, lines:[{time_ms?, text}]}.
  No-lyrics = 200 with empty lines, never 404. Auth-gated.
- cli/lyrics.rs: `qbzd lyrics [TRACK_ID] [--synced] [--json]`; --synced prefixes
  each line with its [mm:ss.cc] LRC timestamp.
- api/mod.rs: P1_ROUTES 24->25; route + test. main.rs: Lyrics subcommand.

Verified: cargo test -p qbzd --bin qbzd -> 276 passed, 0 failed.

cc
2026-07-19 19:04:14 -06:00
vicrodh 7fc292de27 feat(qbzd): add playlist recommendations — POST /api/reco/playlist (CONSOLE)
Enables QBZ's Suggested-Songs engine headlessly and Slint-free. The
artist-vector store is a CACHE the engine builds on demand from MusicBrainz +
Qobuz — no listening history needed, so the personalized (history-based)
recommendations stay out of scope (they would need play-recording plumbing).

- Cargo.toml: qbz-reco path dep (already in-tree via qbz-core; direct dep only
  exposes the ArtistVectorStore name).
- daemon.rs: open ArtistVectorStore::open_at(daemon-data-root) after core init
  and set_artist_vectors — mirrors qbz/src/auth.rs, session-independent,
  best-effort (suggestions still work un-cached if the open fails).
- api/reco.rs: POST /api/reco/playlist {playlist_id} (server seeds the engine
  with the playlist's distinct artists + excludes its tracks) | {artists,
  exclude} | {limit}. generate_playlist_suggestions; auth-gated. Documents the
  single-thread cost (MusicBrainz-resolve is rate-limited).
- cli/reco.rs: `qbzd reco playlist <ID> [--limit --ids --json]`; --ids emits
  suggested track_ids (pipe into `queue add -` / `playlist add`).
- api/mod.rs: P1_ROUTES 23->24; route + test. main.rs: Reco subcommand group.

Verified: cargo test -p qbzd --bin qbzd -> 274 passed, 0 failed.

cc
2026-07-19 16:56:49 -06:00
vicrodh 1a50f4c1b1 feat(qbzd): add discover — Discover rails, no recommendations (CONSOLE)
GET /api/discover?section= exposes the editorial Discover rails so a third
party can replicate the QBZ Discover page WITHOUT the personalized
recommendation engine (per-user reco/play-count stores the daemon never opens):
  index · most-streamed (Qobuz "most played") · new-releases · press-awards ·
  qobuzissims · album-of-the-week · ideal-discography · playlists[?tag] · tags ·
  release-watch[?release_type=artists|labels|awards] · featured[?type]. Optional
  ?genre=ID,ID scopes a section by genre.
- api/discover.rs: one route over get_discover_index / get_discover_albums /
  get_discover_playlists / get_playlist_tags / get_release_watch /
  get_featured_albums; typed serde shapes verbatim; auth-gated.
- cli/discover.rs: `qbzd discover [SECTION] [--genre --tag --release-type
  --type --limit --ids --json]`.
- api/mod.rs: P1_ROUTES 22->23; route + test. main.rs: Discover subcommand.

Verified: cargo test -p qbzd --bin qbzd -> 274 passed, 0 failed (with the
playlist-CRUD commit below it).

cc
2026-07-19 16:56:49 -06:00
vicrodh df4f29a50e feat(qbzd): add playlist CRUD — create/edit/rm/add/remove (CONSOLE P2)
- api/playlist.rs: POST /api/playlist/create, /update, /delete, /tracks/add,
  /tracks/remove. tracks/remove takes PLAIN track ids and the daemon resolves
  them to per-playlist playlist_track_id ROW ids (the row-id trap) via
  get_playlist before remove_tracks_from_playlist — clients never touch row ids.
- cli/playlist.rs: `playlist create/edit/rm/add/remove` (rm requires --yes; add
  reads `-` from stdin; edit --public/--private mutually exclusive).
- api/mod.rs: P1_ROUTES 17->22; routes + test. main.rs: PlaylistCmd variants.

Pending: `cargo check -p qbzd` — box at 7GB with a qbz_ui build running;
compile deferred to avoid an OOM freeze (verified next in a memory window).

cc
2026-07-19 16:32:29 -06:00
vicrodh 4437133c49 feat(qbzd): add art verb — current-track cover art (CONSOLE)
`qbzd art` prints the now-playing track's artwork_url (already carried on
GET /api/now-playing — an unauthenticated Qobuz CDN URL); `qbzd art --save
PATH` downloads it (notification icons, terminal viewers). A pure-ish client
verb: no new route (reads the shipped now-playing). A daemon-served
GET /api/artwork/current (302) is deferred until a daemon-only client needs it.

Verified: cargo test -p qbzd --bin qbzd -> 269 passed, 0 failed.

cc
2026-07-19 16:28:03 -06:00
vicrodh 9ab41fbe8c feat(qbzd): add fav (list/add/remove) + playlist (list/show) (CONSOLE P1)
- api/fav.rs: GET /api/favorites (paged read), POST /api/favorites/add|remove.
  The contract is SINGULAR (track|album|artist) everywhere; the server
  pluralizes for get_favorites, hiding the plural-read/singular-write trap.
  `add {current:true}` favorites the now-playing track.
- api/playlist.rs: GET /api/playlists (collection), GET /api/playlist?id=
  (one playlist, full track list). Reads only in this slice; CRUD deferred.
- cli/fav.rs + cli/playlist.rs reuse browse's generic render/collect_ids
  (made pub(crate)); playlist list has its own id/name/count renderer.
- api/mod.rs: P1_ROUTES 12->17 (+favorites x3, playlists, playlist); routes +
  test. main.rs: Fav/Playlist subcommand groups.

Verified: cargo test -p qbzd --bin qbzd -> 269 passed, 0 failed.

cc
2026-07-19 16:28:03 -06:00
vicrodh ecc47b6d23 feat(qbzd): add shuffle/repeat + queue move/jump/stop-after (CONSOLE)
Completes transport + queue parity from the CLI.

- api/playback.rs: POST /api/playback/shuffle {on|off|toggle} and
  /api/playback/repeat {off|all|one} (set_shuffle / toggle_shuffle /
  set_repeat_mode; no auth — queue-mode toggles, state surfaces in now/status).
- api/queue.rs: POST /api/queue/move {from,to} (move_track); /api/queue/jump
  {index} (play_index + the shipped start ritual, auth-gated — a click-to-play
  row, never a bare cursor move); /api/queue/stop-after {track_id|current|off}
  (set_stop_after / clear_stop_after).
- cli/mode.rs: `shuffle`, `repeat`. cli/queue.rs: `queue move/jump/stop-after`
  (1-based positions translated to 0-based at the boundary, like `queue remove`).
- api/mod.rs: P1_ROUTES 7->12; routes + test. main.rs: subcommands.

Verified: cargo test -p qbzd --bin qbzd -> 264 passed, 0 failed.

cc
2026-07-19 15:36:28 -06:00
vicrodh d819d94ab5 feat(qbzd): add radio (seed-and-go) + resolve (CONSOLE)
- api/radio.rs: POST /api/radio {artist_id|track_id|album_id} — qobuz mode
  (get_radio_artist/track/album, zero local state). Reuses the new shared
  play::start_resolved for the queue-replace + cold-start ritual (protected
  audio untouched). Smart local-pool mode deferred (needs RadioDb::open_at
  plumbing through QbzCore — a shared-core change).
- api/play.rs: extract pub(crate) start_resolved (shared by play + radio).
- cli/radio.rs: `qbzd radio artist:ID | track:ID | album:ID`.
- cli/resolve.rs: `qbzd resolve <URL>` -> kind:ID token; pure client, no
  daemon and no network (qbz_qobuz::link_resolver).
- api/mod.rs: P1_ROUTES 6->7 (+POST /api/radio); route + test. main.rs:
  Radio + Resolve subcommands.

Verified: cargo test -p qbzd --bin qbzd -> 264 passed, 0 failed.

cc
2026-07-19 15:06:38 -06:00
vicrodh ba2f626208 feat(qbzd): add browse reads — album/artist/similar/suggest (CONSOLE P2)
qbzd can now BROWSE the catalog from the CLI: open an album, an artist page /
top-tracks / releases grid, find similar artists or albums, and pull For-You
suggestions — reads that feed the composition pipeline (`--ids | queue add -`).

- api/browse.rs: GET /api/album (get_album + optional get_album_suggest);
  GET /api/artist?view=page|top|albums (get_artist_page / get_artist_tracks /
  get_releases_grid); GET /api/similar?artist=|album= (get_similar_artists /
  get_album_suggest); GET /api/suggest?seed= (get_dynamic_suggest — seeds from
  the current queue when omitted). Typed serde shapes verbatim; auth-gated;
  blacklist fail-open.
- cli/browse.rs: `album`, `artist`, `similar`, `suggest`; a generic items/tracks
  walk drives the human list and --ids; `--seed -` reads ids from stdin.
- api/mod.rs: P1_ROUTES 2->6 (+album/artist/similar/suggest); routes + test.
  main.rs: four subcommands.

Verified: cargo test -p qbzd --bin qbzd -> 262 passed, 0 failed.

cc
2026-07-19 15:02:19 -06:00
vicrodh f2f65b0fbf feat(qbzd): add play <content> + POST /api/play (CONSOLE P1 row 23)
qbzd can now START playback of a track / album / playlist / artist / Qobuz
URL from the CLI — the receiver becomes a source. Server-side materialization
and the shipped cold-start ritual; the protected audio path is untouched.

- api/play.rs: resolve selector (a URL via link_resolver wins over id fields),
  fetch tracks (get_album / get_playlist / get_artist_tracks / get_tracks_batch),
  map via the now-shared queue::track_to_queue_track, stamp album/artist/
  playlist provenance, then set_queue -> play_track_resolved -> save_session_now
  (never a bare cursor move). Auth-gated; 404 on unknown id / empty resolution;
  503 on start failure.
- cli/play.rs: `qbzd play [album:ID|track:ID|artist:ID|playlist:ID|URL|N]`;
  bare `play` delegates to transport::play (one resume implementation). Local
  exit 2 on a malformed selector.
- api/queue.rs: track_to_queue_track -> pub(crate); api/playback.rs:
  resolve_quality -> pub(crate) (both now shared with play).
- api/mod.rs: P1_ROUTES += POST /api/play; route + test. main.rs: Play gains
  an optional content arg (bare = resume, unchanged).

Verified: cargo test -p qbzd --bin qbzd -> 256 passed, 0 failed.

cc
2026-07-19 14:54:56 -06:00
vicrodh 23d6cc71ec feat(qbzd): add search verb + GET /api/search (CONSOLE P1 row 19)
First slice of Proposal A (CONSOLE): qbzd can originate discovery — search
Qobuz by name from the CLI, not only receive via QConnect or take track
ids by hand.

- api/search.rs: GET /api/search?q=&type=&limit=&offset=. Typed searches
  return SearchResultsPage<T> verbatim under a per-category key; type=all
  fans out to the four typed searches. Auth-gated (needs_auth); blacklist
  fail-open by design (documented GUI-parity delta).
- api/mod.rs: new P1_ROUTES table + p1_route_table test — the shipped-caller
  HARD RULE (§3.1.4) applied to the content-verb door; route registered.
- cli/search.rs: `qbzd search <q> [--type all|albums|tracks|artists|
  playlists] [--limit N] [--offset N] [--ids] [--json]`. --ids emits ids
  one-per-line (pipes into `queue add -`); frozen exit-code table.

Verified: `cargo check -p qbzd` clean (no warnings); `cargo test -p qbzd
--bin qbzd` → 248 passed, 0 failed (10 new + 238 existing). New files are
standard-rustfmt; existing files carry only the minimal logical edits.

cc
2026-07-19 14:20:25 -06:00
vicrodh 075582920a fix(qobuz): 403 circuit breaker + prefetch backoff; header tooltips
Issue #637: a Qobuz outage + forced re-login left a user's account
transiently 403'd, and the no-backoff prefetch retry storm escalated it
into a per-IP edge/WAF block (the "error decoding response body" lines are
that non-JSON WAF body hitting a bare .json()). The bundle bump b019->b034
is exonerated: extraction yields byte-identical app_id/secret/private_key.

- ForbiddenBreaker: after repeated 403s the hot streaming/favorites paths
  short-circuit without touching the network, with an exponential cooldown
  and a half-open probe; success resets it.
- get_stream_url: typed 401/403 + body preview; abort the quality fallback
  loop on 403/401/breaker-open instead of firing all five qualities.
- CMAF session/start + file/url: feed the breaker, map 403.
- get_favorites + qws/createToken: check status before .json() so a 403
  surfaces as a 403, not a misleading "error decoding response body".
- Prefetch: per-track failure cooldown (negative cache) so a failed track
  is not re-hammered every queue tick.
- Audible path: a 403/back-off no longer counts as an "unavailable" skip;
  stop cleanly with a toast.
- QBZ_FORCE_403 test hook to smoke the back-off path on a healthy account.
- Header action-button tooltips for AlbumView/ArtistView/PlaylistView,
  translated across all 8 locales.

cc
2026-07-19 10:01:37 -06:00
vicrodh b4bff581ae feat(qbzd): wide sidebar, wrapped descriptions and fixed control column in setup TUI
cc
2026-07-15 12:15:32 -06:00
vicrodh e6cb5114a9 fix(qbzd): posture voice on the TUI network screen and pinned boot note
cc
2026-07-15 11:41:29 -06:00
vicrodh e6cc082669 feat(qbzd): default control-plane and login binds to 0.0.0.0 for LAN-first posture
cc
2026-07-15 11:32:44 -06:00
vicrodh 2e9e6a35ac fix(qbzd): honest OSC 52 copy semantics, copy-all file artifact and wizard polish
cc
2026-07-15 11:09:17 -06:00
vicrodh 90412328be feat(qbzd): hifi wizard section with copyable config blocks in setup TUI
cc
2026-07-15 10:49:38 -06:00
vicrodh ff1dbf7425 feat(qbzd): frames layout for setup TUI — sidebar nav, content frame, header and breadcrumb
cc
2026-07-15 10:10:09 -06:00
vicrodh 927b5aa762 fix(qbzd): footer state mapping test, layout parity and help-tint precision in themed TUI
cc
2026-07-15 08:47:15 -06:00
vicrodh 74dcc60b18 feat(qbzd): themed setup TUI with section borders and accent palette
cc
2026-07-15 08:37:56 -06:00
vicrodh 6b444f9748 fix(qbzd): family-aware bind fallback, URL-first output and IP-validated callback host
cc
2026-07-15 08:17:39 -06:00
vicrodh 7799017c69 feat(qbzd): auto-detect LAN callback host from SSH sessions for login
cc
2026-07-15 08:08:49 -06:00
vicrodh d658a501ad fix(qbzd): store I/O failures in settings set exit 1 not 2
cc
2026-07-14 22:04:11 -06:00
vicrodh 722a0207ec fix(qbzd): latch network.online from real network-class outcomes
cc
2026-07-14 21:59:50 -06:00
vicrodh 47081b7666 fix(qbzd): drop qconnect control handle before device release in shutdown
cc
2026-07-14 21:54:55 -06:00
vicrodh 39eb12fc45 feat(packaging): ship qbzd binary and user unit in deb/rpm and tarball
cc
2026-07-14 21:02:34 -06:00
vicrodh 80a20f4322 fix(qbzd): land setup on menu when credentials exist, use multiplication sign in size warning
cc
2026-07-14 20:56:22 -06:00
vicrodh a1fbbafc79 feat(qbzd): ratatui setup TUI, six screens with explicit-save semantics
Implements owner requirement 2 (03-setup-tui.md): `qbzd setup`, a six-screen
raspi-config-grade configurator over the daemon's real stores — zero new
persistence. ratatui 0.30 (IV5: unifies on the lock's unicode-width 0.2.2;
0.29 forbidden). A synchronous event loop on a blocking thread; disk/HTTP only
at screen entry, `r`, save and the immediate actions, on workers with a spinner
(no I/O on keystrokes). Non-tty exits 2 with the verbatim copy; alt-screen +
panic-hook restore.

Screens: Account (T5 login engine; inline masked token paste, suspend-and-run
browser login, logout confirm), Audio (the §3.2.3 constraint matrix + 7
cascades verified 1:1 vs the desktop code, device picker re-deriving the
§3.2.2 grouping/BP-badge rules, DSD guard), Playback (ask->fallback rule,
infinite-radio read-only), QConnect (startup/name/volume_mode, live preview),
Network (whole-file qbzd.toml rewrite preserving every key), Import/Export
(pure renderer of the T12 bundle plan; interactive device re-pick; default-off
auth gate). Saves reuse T11 write_one + reload/status classification (§4.3);
dirty marker + Save/Discard/Stay. All user-facing strings centralized.

cc
2026-07-14 20:42:42 -06:00
vicrodh 8021393925 fix(qbzd): mask secret values in import summary and distinguish reload refusal
cc
2026-07-14 19:58:31 -06:00
vicrodh 0b7cd6c5e3 feat(qbz-app): settings bundle engine with importer-side classification
Export/import CLI on qbzd; requirements 4 and 5 complete with zero desktop builds.

cc
2026-07-14 19:44:01 -06:00
vicrodh a864aa8d55 feat(qbzd): settings reload endpoint and settings/qconnect/config CLI verbs
Route table complete: 17 P0 routes, CI-counted.

cc
2026-07-14 19:02:10 -06:00
vicrodh 6fa0428127 fix(qbzd): pin player volume to 100 percent at connect in locked volume mode
cc
2026-07-14 18:27:43 -06:00
vicrodh adccb98e76 feat(qbzd): report tick, daemon device identity, volume modes and queue persistence
cc
2026-07-14 18:14:52 -06:00
vicrodh 196f167873 feat(qbzd): copy QConnect glue with TODO(converge) markers and daemon trait impls
Real-volume join report and reconnect credential re-resolution are daemon-copy
fixes; desktop call sites untouched (OD2).

cc
2026-07-14 17:47:33 -06:00
vicrodh 05e2216acc fix(qbzd): strict queue payload parsing, history in queue response, honest race comment
cc
2026-07-14 17:19:44 -06:00
vicrodh 92c49e648b feat(qbzd): queue routes and CLI verbs with server-side track materialization
cc
2026-07-14 17:05:15 -06:00
vicrodh 393a6e01a7 fix(qbzd): serialize volume canonically in JSON responses
cc
2026-07-14 16:49:20 -06:00
vicrodh e700084f0b feat(qbzd): playback routes and transport CLI verbs with full advance ritual
cc
2026-07-14 16:35:21 -06:00
vicrodh dd63b18ae9 fix(qbzd): treat empty [server] token as unset
cc
2026-07-14 16:10:34 -06:00
vicrodh 286cb51957 feat(qbzd): tiny_http control plane, Origin shield + opt-in token, ping/info/status and CLI diagnostics
cc
2026-07-14 15:59:17 -06:00
vicrodh 9597573a61 fix(qbzd): bind login nonce into the redirect path instead of relying on state echo
cc
2026-07-14 15:26:56 -06:00
vicrodh 66c9acfa86 feat(qbzd): login via one-shot nonce-bound listener, paste and token paths
Port the desktop system-browser OAuth (crates/qbz/src/auth.rs) into the daemon
CLI with three daemon-specific changes: the one-shot listener binds an ephemeral
port (never the control-API port, D6), a CSPRNG state nonce binds the callback,
and the command only validates + persists the token into the daemon root then
best-effort nudges a running daemon. Adds validate_token / login_browser /
login_paste / login_with_token_arg / nudge_reload plus logout; no email+password
surface anywhere. cli/copy.rs carries the verbatim 02 §2.2 timeout, success and
logout strings.

cc
2026-07-14 15:13:07 -06:00
vicrodh 297bbad326 feat(qbz-app): headless playback driver with pure plan_tick and thin IO shell
cc
2026-07-14 14:09:22 -06:00
vicrodh 4d0f52138a feat(qbzd): headless runtime bring-up with daemon-root stores and credential restore
Adds additive _at credential fns (crypto/format untouched), DaemonAdapter
broadcast bus, numbered boot per 01 §8.1 incl. the #521 shutdown pair.

cc
2026-07-14 13:40:49 -06:00