huskies: merge 643_story_web_ui_consumer_for_the_unified_status_broadcaster

This commit is contained in:
dave
2026-04-26 11:26:20 +00:00
parent f88bb5f486
commit 8673e563a9
13 changed files with 375 additions and 25 deletions
+2 -1
View File
@@ -9,11 +9,12 @@ use crate::service::notifications::format::stage_display_name;
use crate::service::status::StatusEvent;
/// Render a [`StatusEvent`] into a human-readable plain-text string.
#[allow(dead_code)]
///
/// This is the single formatter for all status event types. Every transport
/// (chat, Web UI, agent context) calls this function rather than duplicating
/// formatting logic.
// Used by chat/agent transports (stories 642/644); the web UI uses StatusUpdate frames instead.
#[allow(dead_code)]
pub fn format_status_event(event: &StatusEvent) -> String {
match event {
StatusEvent::StageTransition {
+3 -1
View File
@@ -26,6 +26,7 @@
pub mod format;
use chrono::{DateTime, Utc};
use serde::Serialize;
use std::sync::{
Arc,
atomic::{AtomicBool, Ordering},
@@ -44,7 +45,8 @@ const CHANNEL_CAPACITY: usize = 256;
///
/// Each variant carries enough context for [`format_status_event`] to render a
/// human-readable message without additional lookups.
#[derive(Clone, Debug)]
#[derive(Clone, Debug, Serialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum StatusEvent {
/// A work item moved between pipeline stages.
StageTransition {
+16
View File
@@ -9,6 +9,7 @@ use crate::io::onboarding;
use crate::io::watcher::WatcherEvent;
use crate::io::wizard;
use crate::log_buffer;
use crate::service::status::Subscription;
use std::sync::Arc;
use tokio::sync::{broadcast, mpsc};
@@ -116,6 +117,21 @@ pub fn subscribe_watcher(
});
}
/// Spawn a background task that forwards status broadcaster events to the client.
///
/// Each [`StatusEvent`](crate::service::status::StatusEvent) is delivered as a
/// [`WsResponse::StatusUpdate`] with the structured event fields intact, so the
/// frontend can do per-type presentation without parsing strings.
pub fn subscribe_status(tx: mpsc::UnboundedSender<WsResponse>, mut subscription: Subscription) {
tokio::spawn(async move {
while let Some(event) = subscription.recv().await {
if tx.send(WsResponse::StatusUpdate { event }).is_err() {
break;
}
}
});
}
/// Spawn a background task that forwards reconciliation events to the client.
pub fn subscribe_reconciliation(
tx: mpsc::UnboundedSender<WsResponse>,
+10
View File
@@ -8,6 +8,7 @@ use crate::http::workflow::{PipelineState, UpcomingStory};
use crate::io::watcher::WatcherEvent;
use crate::llm::chat;
use crate::llm::types::Message;
use crate::service::status::StatusEvent;
use serde::{Deserialize, Serialize};
/// WebSocket request messages sent by the client.
@@ -153,6 +154,15 @@ pub enum WsResponse {
level: String,
message: String,
},
/// A structured pipeline status event forwarded from the status broadcaster.
///
/// The structured [`StatusEvent`] fields are preserved on the wire so
/// frontend consumers can do per-type presentation without parsing strings.
/// This frame intentionally does NOT call `format_status_event` — that
/// formatter is reserved for chat transports (story 644).
StatusUpdate {
event: StatusEvent,
},
}
// ── Domain event conversions ────────────────────────────────────────────────
+1 -1
View File
@@ -20,6 +20,6 @@ pub use dispatch::{
};
pub use io::{
check_onboarding, load_initial_pipeline_state, load_recent_logs, load_wizard_state,
subscribe_logs, subscribe_reconciliation, subscribe_watcher,
subscribe_logs, subscribe_reconciliation, subscribe_status, subscribe_watcher,
};
pub use message::{WizardStepInfo, WsResponse};