Subsystem 4b: Smart Alarms (#alarms channel, pair/validate, @everyone + team-chat relay on trigger)#23
Merged
Merged
Conversation
…ve/LastTriggeredUtc; UpdateStateAsync)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…catalog) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fix 1: Update stale XML comment on AlarmLocalizationCatalog.Default to reference the actual per-slice AlarmLocalizer constructor instead of non-existent shared ILocalizer type. Fix 2: Extract IAlarmLocalizer interface (matching ISwitchLocalizer pattern) with Get(key, culture) and Get(key, culture, params args) methods. Make AlarmLocalizer implement it with <inheritdoc /> on public methods. Update AlarmEmbedRenderer ctor to accept IAlarmLocalizer instead of concrete AlarmLocalizer. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…oordinator Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ert prompt replacement Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ay, unreachable) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Wire the smart-alarms slice live: AlarmsHostedService drives three consume loops (AlarmPairedEvent → coordinator, SmartDeviceTriggeredEvent → relay, ConnectionStatusChangedEvent → relay); AddAlarms registers all slice singletons and the hosted service; Host references the Alarms project and calls AddAlarms() after AddSwitches(). AlarmRegistrationTests covers both descriptor presence and captive-dependency-free resolution. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Whitespace-only line-wrap reformats produced by dotnet jb cleanupcode --profile=ReformatAndReorder. No semantic changes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…e-fetch Add an IAlarmRefresher.RefreshAsync(SmartAlarm, ...) overload that renders an already-loaded alarm, skipping the per-alarm store.GetAsync. The id-based method now fetches-then-delegates via a shared RenderAndPostAsync helper. HandleConnectionStatusAsync passes the alarms it already listed instead of re-querying each by id. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds Smart Alarms end-to-end, the second slice of subsystem 4 (smart devices), on the socket-trigger model established by the smart-device refactor (#22): alarms work exactly like smart switches — primed on connect, reacting to the in-game
SmartDeviceTriggeredEvent(the entity id is the switch-vs-alarm discriminant). The FCM alarm push is not used for attribution (it's a server-level "poke", not per-alarm).Flow: pair an alarm in-game → FCM
OnSmartAlarmPairing→ user-validated "Add it?" prompt in a per-server#alarmschannel → on Accept, a persistent per-alarm embed. On (re)connect the supervisor primes each managed alarm (registering the socket's interest). When the alarm goes active in-game, its embed updates (state + "last triggered N ago") and — per the alarm's toggles — pings@everyonein#alarmsand/or relays a🚨 {name} triggeredline to in-game team chat. Controls: @everyone toggle, relay-to-team-chat toggle, Rename. Validated/opt-in; any guild member operates them; unreachable shown inline when the server isn't connected.What's included
SmartAlarmentity (socket-state:LastIsActive+LastTriggeredUtc),IAlarmStore/AlarmStore(UpdateStateAsync, toggles, idempotentAddAsync), oneSmartAlarmsmigration.PairingNotification.EntityKindthreadsPairedEntityKind;PairingHandlerroutes switch→SwitchPairedEvent(unchanged) / alarm→AlarmPairedEvent; FCM source subscribesOnSmartAlarmPairing.ConnectionSupervisor.PrimeDevicesAsyncalso primes alarms (via an extractedPrimeEntityIdsAsyncDRY helper) — the only Connections edit; the genericSmartDeviceTriggeredEventseam is unchanged.#alarmschannel + EN/FRchannel.alarms.name+AlarmChannelLocator.Features.Alarmsproject:AlarmEmbedRenderer,AlarmPairingCoordinator,IAlarmRefresher/AlarmRefresher,AlarmStateRelay(active-edge ping/relay,ExistsAsyncfilter, unreachable on disconnect), per-sliceAlarmLocalizer+IAlarmChannelPoster/DiscordAlarmChannelPoster, thinAlarmComponentModule+ rename modal,AlarmsHostedService,AddAlarmsDI + Host wiring.Design notes
SmartDeviceTriggeredEventis consumed by bothSwitchStateRelay(filters to switch ids) andAlarmStateRelay(filters to alarm ids); the event bus fans out per-subscriber, and per-store unique(GuildId, ServerId, EntityId)indexes make switch↔alarm cross-talk structurally impossible.EntityKinddefault keeps the switch path identical).Verification
-warnaserror); jbReformatAndReorderclean.SmartAlarmsmigration, no other drift.🤖 Generated with Claude Code