feat(toast): wire ToastVariant::Warning for daily-challenge expiry

Adds the first in-engine consumer of `ToastVariant::Warning` — a 4s
amber-bordered toast that fires once per daily-challenge date when the
player is within 30 minutes of UTC midnight reset and hasn't yet
completed today's challenge.

Mirrors the v0.21.2 `ToastVariant::Error` wiring: a domain-event
message (`WarningToastEvent(String)`) crosses the plugin boundary;
`animation_plugin::handle_warning_toast` reads it and spawns the
fire-and-forget toast. Suppression is decided by a pure helper
(`compute_expiry_warning_minutes`) that's exhaustively covered by 7
unit tests + 1 in-Bevy idempotence test.

After this lands, every `ToastVariant` (Info, Warning, Error,
Celebration) has at least one real driver — closing the "is this enum
scaffolding or load-bearing?" ambiguity that's been latent since the
variant was introduced.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
funman300
2026-05-08 14:22:58 -07:00
parent 12fba2157a
commit 279e23d0af
3 changed files with 259 additions and 4 deletions
+21 -1
View File
@@ -22,7 +22,8 @@ use crate::card_plugin::CardEntity;
use crate::challenge_plugin::ChallengeAdvancedEvent;
use crate::daily_challenge_plugin::{DailyChallengeCompletedEvent, DailyGoalAnnouncementEvent};
use crate::events::{
AchievementUnlockedEvent, GameWonEvent, InfoToastEvent, MoveRejectedEvent, XpAwardedEvent,
AchievementUnlockedEvent, GameWonEvent, InfoToastEvent, MoveRejectedEvent, WarningToastEvent,
XpAwardedEvent,
};
use crate::game_plugin::GameMutation;
use crate::layout::LayoutResource;
@@ -164,6 +165,7 @@ impl Plugin for AnimationPlugin {
.add_message::<SettingsChangedEvent>()
.add_message::<InfoToastEvent>()
.add_message::<MoveRejectedEvent>()
.add_message::<WarningToastEvent>()
.add_message::<XpAwardedEvent>()
.init_resource::<EffectiveSlideDuration>()
.init_resource::<ToastQueue>()
@@ -186,6 +188,7 @@ impl Plugin for AnimationPlugin {
handle_auto_complete_toast,
handle_xp_awarded_toast,
handle_move_rejected_toast,
handle_warning_toast,
tick_toasts,
(enqueue_toasts, drive_toast_display).chain(),
)
@@ -651,6 +654,23 @@ fn handle_move_rejected_toast(
}
}
/// Spawns a 4-second amber-bordered Warning toast for every incoming
/// [`WarningToastEvent`]. First in-engine consumer of
/// [`ToastVariant::Warning`] — exercises the variant's amber accent and
/// the design-system "act soon" semantic.
///
/// Mirrors [`handle_move_rejected_toast`] but reads a generic carrier
/// event (not a domain-specific one) because Warning has multiple
/// candidate drivers and the call-site knows the message wording.
fn handle_warning_toast(
mut commands: Commands,
mut events: MessageReader<WarningToastEvent>,
) {
for ev in events.read() {
spawn_toast(&mut commands, ev.0.clone(), 4.0, ToastVariant::Warning);
}
}
/// Ticks down `ToastTimer` on each toast and despawns it when the timer expires.
///
/// Skipped while the game is paused so toast countdowns freeze along with the