fix(multi): resolve 26 bugs found in comprehensive codebase review
Build and Deploy / build-and-push (push) Successful in 3m40s
Build and Deploy / build-and-push (push) Successful in 3m40s
Core fixes (issues #12, #13, #22): - #12: undo now preserves score delta instead of restoring snapshot score - #13: take_from_foundation defaults to false (non-standard house rule) - #22: check_win validates full suit sequence, not just card count Engine fixes: - #8: replay keyboard input guard against non-replay state - #9: help modal scrims.is_empty() guard added - #10: settings modal scrims.is_empty() guard added - #11: sync_plugin builds payload at poll time (not task-spawn time) - #14: server replay mode case-sensitivity fix ("Classic") - #15: play_by_seed_plugin confirmed flag set to true on launch - #16: replay back-step debounce via Local<bool> + StateChangedEvent; register StateChangedEvent in ReplayOverlayPlugin (fixes 52 tests) - #17: time-attack timer ignores win-summary overlay - #18: HUD dropdown glyphs U+25BE → U+2193 (FiraMono-safe arrow) - #19: theme plugin applies immediate visual update on A→B→A switch - #20: SyncAuthError / SyncBusyOverlay split into separate entities so auth errors are visible after busy overlay is hidden - #21: handle_forfeit ordered before update_stats_on_new_game - #23: server merge uses correct avg_time_seconds and games_lost math - #24: win_summary migrated to ModalScrim pattern - #25: card_animation apply_deferred between animation systems - #26: cursor_plugin HashMap access uses .get() with fallback - #27: auto_complete mid-sequence deactivation guard - #28: feedback_anim SettleAnim ordered before FoundationFlourish - #29: achievement_plugin iterates all win events; adds scrims guard - #30: leaderboard modal scrims.is_empty() guard added - #31: server auth tmp file cleanup on rename failure - #32: sync_setup modal scrims.is_empty() guard added - #33: font_plugin uses match fallback; TokioRuntimeResource graceful current-thread fallback on runtime init failure Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -55,7 +55,7 @@ use crate::font_plugin::FontResource;
|
||||
use crate::settings_plugin::{SettingsResource, SettingsScreen, SettingsStoragePath};
|
||||
use crate::resources::TokioRuntimeResource;
|
||||
use crate::sync_plugin::SyncProviderResource;
|
||||
use crate::ui_modal::spawn_modal;
|
||||
use crate::ui_modal::{spawn_modal, ModalScrim};
|
||||
use crate::ui_theme::{
|
||||
ACCENT_PRIMARY, BG_ELEVATED, BG_ELEVATED_HI,
|
||||
BORDER_SUBTLE, HighContrastBorder, RADIUS_SM, STATE_DANGER, TEXT_DISABLED,
|
||||
@@ -208,6 +208,7 @@ impl Plugin for SyncSetupPlugin {
|
||||
fn open_sync_setup_modal(
|
||||
mut events: MessageReader<SyncConfigureRequestEvent>,
|
||||
existing: Query<(), With<SyncSetupScreen>>,
|
||||
other_modal_scrims: Query<(), (With<ModalScrim>, Without<SyncSetupScreen>)>,
|
||||
mut commands: Commands,
|
||||
mut focused: ResMut<SyncFocusedField>,
|
||||
font_res: Option<Res<FontResource>>,
|
||||
@@ -219,6 +220,9 @@ fn open_sync_setup_modal(
|
||||
if !existing.is_empty() {
|
||||
return; // Already open.
|
||||
}
|
||||
if !other_modal_scrims.is_empty() {
|
||||
return; // Another modal is already visible.
|
||||
}
|
||||
*focused = SyncFocusedField::Url;
|
||||
spawn_sync_setup_modal(&mut commands, font_res.as_deref());
|
||||
}
|
||||
@@ -354,9 +358,10 @@ fn handle_auth_button(
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear error and show busy indicator.
|
||||
for (mut text, _) in &mut error_nodes {
|
||||
text.0 = "Connecting…".to_string();
|
||||
// Clear previous error and show busy indicator.
|
||||
for (mut text, mut color) in &mut error_nodes {
|
||||
text.0 = String::new();
|
||||
color.0 = TEXT_SECONDARY;
|
||||
}
|
||||
for mut vis in &mut busy_nodes {
|
||||
*vis = Visibility::Visible;
|
||||
@@ -540,6 +545,7 @@ fn handle_logout(
|
||||
fn open_delete_confirm_modal(
|
||||
mut events: MessageReader<DeleteAccountRequestEvent>,
|
||||
existing: Query<(), With<DeleteConfirmScreen>>,
|
||||
other_modal_scrims: Query<(), (With<ModalScrim>, Without<DeleteConfirmScreen>)>,
|
||||
mut commands: Commands,
|
||||
font_res: Option<Res<FontResource>>,
|
||||
) {
|
||||
@@ -550,6 +556,9 @@ fn open_delete_confirm_modal(
|
||||
if !existing.is_empty() {
|
||||
return;
|
||||
}
|
||||
if !other_modal_scrims.is_empty() {
|
||||
return; // Another modal is already visible.
|
||||
}
|
||||
spawn_delete_confirm_modal(&mut commands, font_res.as_deref());
|
||||
}
|
||||
|
||||
@@ -675,20 +684,31 @@ fn spawn_sync_setup_modal(commands: &mut Commands, font_res: Option<&FontResourc
|
||||
font_res,
|
||||
);
|
||||
|
||||
// Error / status line.
|
||||
// Error / status line — two distinct children so visibility and
|
||||
// text can be controlled independently.
|
||||
body.spawn(Node {
|
||||
min_height: Val::Px(18.0),
|
||||
flex_direction: FlexDirection::Row,
|
||||
align_items: AlignItems::Center,
|
||||
column_gap: VAL_SPACE_2,
|
||||
..default()
|
||||
})
|
||||
.with_children(|row| {
|
||||
// Busy indicator: shown while the auth task is in flight.
|
||||
row.spawn((
|
||||
SyncAuthError,
|
||||
SyncBusyOverlay,
|
||||
Text::new(String::new()),
|
||||
Text::new("…"),
|
||||
make_font(font_res, TYPE_CAPTION),
|
||||
TextColor(TEXT_SECONDARY),
|
||||
Visibility::Hidden,
|
||||
));
|
||||
// Error / status text: always laid out, empty when idle.
|
||||
row.spawn((
|
||||
SyncAuthError,
|
||||
Text::new(String::new()),
|
||||
make_font(font_res, TYPE_CAPTION),
|
||||
TextColor(TEXT_SECONDARY),
|
||||
));
|
||||
});
|
||||
|
||||
// Tab hint — desktop only; no Tab key on Android.
|
||||
|
||||
Reference in New Issue
Block a user