diff --git a/server/src/chat/transport/matrix/bot/run.rs b/server/src/chat/transport/matrix/bot/run.rs index 46df9ddf..2c9bdaf8 100644 --- a/server/src/chat/transport/matrix/bot/run.rs +++ b/server/src/chat/transport/matrix/bot/run.rs @@ -361,8 +361,23 @@ pub async fn run_bot( let notif_room_id_strings: Vec = notif_room_ids.iter().map(|r| r.to_string()).collect(); crate::service::notifications::spawn_notification_listener( Arc::clone(&transport), - move || notif_room_id_strings.clone(), + { + let notif_room_id_strings = notif_room_id_strings.clone(); + move || notif_room_id_strings.clone() + }, watcher_rx, + notif_project_root.clone(), + ); + + // Spawn the TransitionFired-driven stage-notification subscriber (story + // 995) here too — this was previously only wired for WhatsApp/Slack/Discord + // (`startup::bots::spawn_notification_listeners`), leaving Matrix + // deployments with no stage-transition chat notifications at all (story + // 1235). Spawned once here, before the sync loop below, so the task runs + // independently of Matrix sync reconnects/re-logins. + crate::service::notifications::spawn_stage_notification_subscriber( + Arc::clone(&transport), + move || notif_room_id_strings.clone(), notif_project_root, ); diff --git a/server/src/service/notifications/events.rs b/server/src/service/notifications/events.rs index a8fbbdb6..b8c9a43b 100644 --- a/server/src/service/notifications/events.rs +++ b/server/src/service/notifications/events.rs @@ -50,9 +50,9 @@ pub enum EventAction { /// Classify a [`WatcherEvent`] into the action the notification listener should take. pub fn classify(event: &WatcherEvent) -> EventAction { match event { - // Stage-change notifications are now handled by the TransitionFired subscriber - // (story 995). WorkItem events are skipped regardless of from_stage. - WatcherEvent::WorkItem { .. } => EventAction::Skip, + // Stage-change notifications are handled by the TransitionFired + // subscriber (story 995/1235), not this watcher-event path — WorkItem + // falls through to the `_` catch-all below. WatcherEvent::MergeFailure { .. } => EventAction::MergeFailure, WatcherEvent::RateLimitWarning { .. } => EventAction::RateLimitWarning, WatcherEvent::StoryBlocked { .. } => EventAction::StoryBlocked, @@ -79,30 +79,6 @@ pub fn classify(event: &WatcherEvent) -> EventAction { mod tests { use super::*; - fn work_item(from_stage: Option<&str>) -> WatcherEvent { - WatcherEvent::WorkItem { - stage: "3_qa".to_string(), - item_id: "1_story_foo".to_string(), - action: "qa".to_string(), - commit_msg: String::new(), - from_stage: from_stage.map(str::to_string), - } - } - - // Stage-change notifications moved to TransitionFired subscriber (story 995). - // All WorkItem events are now classified as Skip regardless of from_stage. - #[test] - fn work_item_with_from_stage_is_skip() { - let event = work_item(Some("2_current")); - assert_eq!(classify(&event), EventAction::Skip); - } - - #[test] - fn work_item_without_from_stage_is_skip() { - let event = work_item(None); - assert_eq!(classify(&event), EventAction::Skip); - } - #[test] fn merge_failure_is_classified_correctly() { let event = WatcherEvent::MergeFailure {