From d5bd43aca3cb033016ce21083ef913776b41f519 Mon Sep 17 00:00:00 2001 From: vicrodh <103399283+vicrodh@users.noreply.github.com> Date: Wed, 10 Jun 2026 09:04:27 -0600 Subject: [PATCH] fix(slint): sign-in flow no longer lifts the offline session up front login_via_system_browser's start-of-flow offline_session pre-clear (added when the gate still blocked login) unlocked an empty shell while the browser OAuth was pending. The pre-clear is gone: the auth endpoints are gate-exempt since c207f232, runtime activation is purely local, and the one remaining gated dependency - the COLD bundle fetch in try_init_api - is handled by the new ensure_api_initialized helper, which lifts the flag only around that init and restores it immediately (no-op on the normal warm-token path). The flag now ends up false exclusively on a completed login; restore_saved_session goes through the same helper, fixing a latent cold-bundle recovery failure. Handler wiring rides the next commit. cc --- crates/qbz-slint/src/auth.rs | 67 +++++++++++++++++++++++++----------- 1 file changed, 47 insertions(+), 20 deletions(-) diff --git a/crates/qbz-slint/src/auth.rs b/crates/qbz-slint/src/auth.rs index 063386602..540f046c1 100644 --- a/crates/qbz-slint/src/auth.rs +++ b/crates/qbz-slint/src/auth.rs @@ -33,23 +33,16 @@ where { let core = runtime.core(); - // Belt-and-suspenders: signing in is an explicit intent to reach Qobuz. - // If a session-scoped offline session is still active (e.g. invoked from - // the login screen after a logout that raced the engine reset), end it so - // the still-gated parts of the pipeline (cold bundle init, post-login - // activation) work. The persisted `induced` preference is NOT touched — - // a successful login reloads it via init_for_user and the app correctly - // lands back in induced offline. - let offline_engine = crate::offline_mode::engine(); - if offline_engine.status().offline_session { - log::info!("[qbz-slint] sign-in requested with an offline session active — ending it"); - offline_engine.set_offline_session(false); - } - - // Ensure the Qobuz client is initialized (bundle tokens). - if !core.is_api_initialized().await { - core.try_init_api().await.map_err(|e| e.to_string())?; - } + // NOTE: no offline_session pre-clear here. The sign-in endpoints are + // EXEMPT from the offline gate (qbz-qobuz raw-client auth methods since + // c207f232), so a live offline session never blocks the login itself — + // and session activation (`runtime.activate`) is purely local. The old + // upfront clear ended the offline session the moment the attempt + // STARTED, which unlocked the shell (empty Discover/Library) while the + // browser OAuth was still pending. The flag now drops on SUCCESS only + // (below); the one still-gated dependency — a cold bundle-token init — + // is scoped inside ensure_api_initialized. + ensure_api_initialized(core).await?; let app_id = { let client_lock = core.client(); @@ -134,6 +127,40 @@ where }) } +/// Make sure the Qobuz client holds bundle tokens before a sign-in call. +/// +/// The sign-in POSTs are gate-exempt, but `try_init_api`'s cold bundle +/// fetch is a network SERVICE request and stays gated on purpose. When an +/// unauthenticated offline session holds the gate closed (sign-in from the +/// offline shell's badge flyout / recovery path), lift the session flag +/// ONLY around this init and put it back immediately after, success or +/// failure — the offline session must end exclusively on a COMPLETED +/// login (the callers' success paths), never as a side effect of merely +/// starting an attempt. No-op in the normal case: an offline session boots +/// from cached bundle tokens, so the client is already initialized and the +/// flag is never touched. +async fn ensure_api_initialized(core: &qbz_core::QbzCore) -> Result<(), String> +where + A: FrontendAdapter + Send + Sync + 'static, +{ + if core.is_api_initialized().await { + return Ok(()); + } + let offline_engine = crate::offline_mode::engine(); + let lifted = offline_engine.status().offline_session; + if lifted { + log::info!( + "[qbz-slint] cold bundle init with an offline session active — lifting the flag for the init only" + ); + offline_engine.set_offline_session(false); + } + let result = core.try_init_api().await.map_err(|e| e.to_string()); + if lifted { + offline_engine.set_offline_session(true); + } + result +} + /// True only for an EXPLICIT auth rejection from Qobuz: a 401 on the token /// login (`AuthenticationError`) or an ineligible-account verdict. Network /// failures, the offline gate, 5xx, rate limiting, parse errors and unknown @@ -172,9 +199,9 @@ where }; let core = runtime.core(); - if !core.is_api_initialized().await { - core.try_init_api().await.map_err(|e| e.to_string())?; - } + // Same scoped handling of the gated cold bundle init as the browser + // flow (no-op when the client already holds tokens). + ensure_api_initialized(core).await?; match core.login_with_token(&token).await { Ok(session) => {