fix(onboarding): delay first-run modal until splash screen despawns
Build and Deploy / build-and-push (push) Successful in 4m35s
Web E2E / web-e2e (push) Successful in 4m25s

OnboardingPlugin previously used PostStartup which fires before the
first Update tick — guaranteeing the onboarding modal and the launch
splash (MOTION_SPLASH_TOTAL_SECS = 1.6 s) overlap for the entire
splash duration. The splash sits at Z_SPLASH (the highest UI z-index),
so the two screens fought visually and the user saw a confusing frozen
composite before the splash faded out.

Fix: move spawn_if_first_run to Update and gate it on
`splashes.is_empty()` (no SplashRoot entity alive). A Local<bool>
ensures the spawn fires at most once per session. Cost: ~one frame of
latency after the splash clears, which is imperceptible.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
funman300
2026-06-02 12:59:58 -07:00
parent d45b7cb82b
commit de7ae16830
3 changed files with 35 additions and 15 deletions
+23 -3
View File
@@ -36,6 +36,7 @@ use crate::ui_theme::{
BORDER_SUBTLE, HighContrastBorder, RADIUS_SM, TEXT_PRIMARY, TYPE_BODY, TYPE_CAPTION, BORDER_SUBTLE, HighContrastBorder, RADIUS_SM, TEXT_PRIMARY, TYPE_BODY, TYPE_CAPTION,
VAL_SPACE_1, VAL_SPACE_2, VAL_SPACE_3, VAL_SPACE_1, VAL_SPACE_2, VAL_SPACE_3,
}; };
use crate::splash_plugin::SplashRoot;
use crate::ui_theme::{TEXT_SECONDARY, Z_ONBOARDING}; use crate::ui_theme::{TEXT_SECONDARY, Z_ONBOARDING};
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -153,7 +154,7 @@ pub struct OnboardingPlugin;
impl Plugin for OnboardingPlugin { impl Plugin for OnboardingPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.init_resource::<OnboardingSlideIndex>() app.init_resource::<OnboardingSlideIndex>()
.add_systems(PostStartup, spawn_if_first_run) .add_systems(Update, spawn_if_first_run)
.add_systems( .add_systems(
Update, Update,
(handle_onboarding_buttons, handle_onboarding_keyboard).chain(), (handle_onboarding_buttons, handle_onboarding_keyboard).chain(),
@@ -170,11 +171,30 @@ fn spawn_if_first_run(
settings: Option<Res<SettingsResource>>, settings: Option<Res<SettingsResource>>,
font_res: Option<Res<FontResource>>, font_res: Option<Res<FontResource>>,
mut slide_index: ResMut<OnboardingSlideIndex>, mut slide_index: ResMut<OnboardingSlideIndex>,
splashes: Query<(), With<SplashRoot>>,
existing: Query<(), With<OnboardingScreen>>,
mut spawned: Local<bool>,
) { ) {
let Some(s) = settings else { return }; if *spawned {
if s.0.first_run_complete {
return; return;
} }
// Wait until the launch splash has despawned so the two screens
// never overlap. PostStartup would fire before the first Update
// tick, guaranteeing overlap; checking here costs one frame of
// latency after the splash clears, which is imperceptible.
if !splashes.is_empty() {
return;
}
if !existing.is_empty() {
*spawned = true;
return;
}
let Some(s) = settings else { return };
if s.0.first_run_complete {
*spawned = true;
return;
}
*spawned = true;
slide_index.0 = 0; slide_index.0 = 0;
spawn_slide(&mut commands, 0, font_res.as_deref()); spawn_slide(&mut commands, 0, font_res.as_deref());
} }
+12 -12
View File
@@ -1649,62 +1649,62 @@ function __wbg_get_imports() {
return ret; return ret;
}, },
__wbindgen_cast_0000000000000001: function(arg0, arg1) { __wbindgen_cast_0000000000000001: function(arg0, arg1) {
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 114633, ret: Result(Unit), inner_ret: Some(Result(Unit)) }, mutable: true }) -> Externref`. // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 114621, ret: Result(Unit), inner_ret: Some(Result(Unit)) }, mutable: true }) -> Externref`.
const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__hf0188236128725a8); const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__hf0188236128725a8);
return ret; return ret;
}, },
__wbindgen_cast_0000000000000002: function(arg0, arg1) { __wbindgen_cast_0000000000000002: function(arg0, arg1) {
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 9777, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`. // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 9765, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h038e9392efba509b); const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h038e9392efba509b);
return ret; return ret;
}, },
__wbindgen_cast_0000000000000003: function(arg0, arg1) { __wbindgen_cast_0000000000000003: function(arg0, arg1) {
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("Array<any>"), NamedExternref("ResizeObserver")], shim_idx: 9779, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`. // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("Array<any>"), NamedExternref("ResizeObserver")], shim_idx: 9767, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__hb8334c8e03ee5ee1); const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__hb8334c8e03ee5ee1);
return ret; return ret;
}, },
__wbindgen_cast_0000000000000004: function(arg0, arg1) { __wbindgen_cast_0000000000000004: function(arg0, arg1) {
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("Array<any>")], shim_idx: 9777, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`. // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("Array<any>")], shim_idx: 9765, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h038e9392efba509b_3); const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h038e9392efba509b_3);
return ret; return ret;
}, },
__wbindgen_cast_0000000000000005: function(arg0, arg1) { __wbindgen_cast_0000000000000005: function(arg0, arg1) {
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("Event")], shim_idx: 9777, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`. // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("Event")], shim_idx: 9765, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h038e9392efba509b_4); const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h038e9392efba509b_4);
return ret; return ret;
}, },
__wbindgen_cast_0000000000000006: function(arg0, arg1) { __wbindgen_cast_0000000000000006: function(arg0, arg1) {
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("FocusEvent")], shim_idx: 9777, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`. // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("FocusEvent")], shim_idx: 9765, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h038e9392efba509b_5); const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h038e9392efba509b_5);
return ret; return ret;
}, },
__wbindgen_cast_0000000000000007: function(arg0, arg1) { __wbindgen_cast_0000000000000007: function(arg0, arg1) {
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("KeyboardEvent")], shim_idx: 9777, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`. // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("KeyboardEvent")], shim_idx: 9765, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h038e9392efba509b_6); const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h038e9392efba509b_6);
return ret; return ret;
}, },
__wbindgen_cast_0000000000000008: function(arg0, arg1) { __wbindgen_cast_0000000000000008: function(arg0, arg1) {
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("PageTransitionEvent")], shim_idx: 9777, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`. // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("PageTransitionEvent")], shim_idx: 9765, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h038e9392efba509b_7); const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h038e9392efba509b_7);
return ret; return ret;
}, },
__wbindgen_cast_0000000000000009: function(arg0, arg1) { __wbindgen_cast_0000000000000009: function(arg0, arg1) {
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("PointerEvent")], shim_idx: 9777, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`. // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("PointerEvent")], shim_idx: 9765, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h038e9392efba509b_8); const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h038e9392efba509b_8);
return ret; return ret;
}, },
__wbindgen_cast_000000000000000a: function(arg0, arg1) { __wbindgen_cast_000000000000000a: function(arg0, arg1) {
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("WheelEvent")], shim_idx: 9777, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`. // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("WheelEvent")], shim_idx: 9765, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h038e9392efba509b_9); const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h038e9392efba509b_9);
return ret; return ret;
}, },
__wbindgen_cast_000000000000000b: function(arg0, arg1) { __wbindgen_cast_000000000000000b: function(arg0, arg1) {
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Option(NamedExternref("Blob"))], shim_idx: 9787, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`. // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Option(NamedExternref("Blob"))], shim_idx: 9775, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h618c0cad9a289a93); const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h618c0cad9a289a93);
return ret; return ret;
}, },
__wbindgen_cast_000000000000000c: function(arg0, arg1) { __wbindgen_cast_000000000000000c: function(arg0, arg1) {
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [], shim_idx: 9781, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`. // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [], shim_idx: 9769, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h277d9d6b389a2871); const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h277d9d6b389a2871);
return ret; return ret;
}, },
Binary file not shown.