From 20a500a7f3fa272326418312e3b57107ee96f0c4 Mon Sep 17 00:00:00 2001 From: Can Celik Date: Sun, 23 Aug 2026 02:01:24 +0300 Subject: fix(api): start lifecycle subscriptions from live events (#3134) refs #1270 --- src/api/server.rs | 29 +++++--- src/api/subscriptions.rs | 187 ++++++++++++++++++++++------------------------- src/api/wait.rs | 10 ++- 3 files changed, 114 insertions(+), 112 deletions(-) (limited to 'src/api') diff --git a/src/api/server.rs b/src/api/server.rs index 3af8051f..3c42d699 100644 --- a/src/api/server.rs +++ b/src/api/server.rs @@ -691,21 +691,28 @@ fn stream_subscriptions( event_hub: &EventHub, running: &Arc, ) -> std::io::Result<()> { + let event_start_sequence = event_hub.current_sequence(); let mut subscriptions = Vec::with_capacity(params.subscriptions.len()); for (index, subscription) in params.subscriptions.into_iter().enumerate() { - let active = - match ActiveSubscription::new(subscription, &request_id, index, api_tx, event_hub) { - Ok(active) => active, - Err(response) => { - if let Err(err) = write_json_line(&mut stream, &response) { - if is_connection_closed_error(&err) { - return Ok(()); - } - return Err(err); + let active = match ActiveSubscription::new( + subscription, + &request_id, + index, + api_tx, + event_hub, + event_start_sequence, + ) { + Ok(active) => active, + Err(response) => { + if let Err(err) = write_json_line(&mut stream, &response) { + if is_connection_closed_error(&err) { + return Ok(()); } - return Ok(()); + return Err(err); } - }; + return Ok(()); + } + }; subscriptions.push(active); } diff --git a/src/api/subscriptions.rs b/src/api/subscriptions.rs index 8d2f84ec..0888250e 100644 --- a/src/api/subscriptions.rs +++ b/src/api/subscriptions.rs @@ -1,9 +1,9 @@ use regex::Regex; use crate::api::schema::{ - ErrorBody, ErrorResponse, Method, PaneAgentStatusChangedEvent, PaneOutputMatchedEvent, - PaneScrollChangedEvent, PaneScrollInfo, Request, Subscription, SubscriptionEventData, - SubscriptionEventEnvelope, SubscriptionEventKind, + ErrorBody, ErrorResponse, EventKind, Method, PaneAgentStatusChangedEvent, + PaneOutputMatchedEvent, PaneScrollChangedEvent, PaneScrollInfo, Request, Subscription, + SubscriptionEventData, SubscriptionEventEnvelope, SubscriptionEventKind, }; use crate::api::server::{dispatch_to_app_with_timeout, APP_RESPONSE_TIMEOUT}; use crate::api::{ApiRequestSender, EventHub}; @@ -110,104 +110,54 @@ impl ActiveSubscription { index: usize, api_tx: &ApiRequestSender, event_hub: &EventHub, + event_start_sequence: u64, ) -> Result { + let event_subscription = |event_kind| { + Self::Event(ActiveEventSubscription { + event_kind, + last_sequence: event_start_sequence, + }) + }; + match subscription { - Subscription::WorkspaceCreated {} => Ok(Self::Event(ActiveEventSubscription { - event_kind: crate::api::schema::EventKind::WorkspaceCreated, - last_sequence: 0, - })), - Subscription::WorkspaceUpdated {} => Ok(Self::Event(ActiveEventSubscription { - event_kind: crate::api::schema::EventKind::WorkspaceUpdated, - last_sequence: 0, - })), - Subscription::WorkspaceMetadataUpdated {} => Ok(Self::Event(ActiveEventSubscription { - event_kind: crate::api::schema::EventKind::WorkspaceMetadataUpdated, - last_sequence: 0, - })), - Subscription::WorkspaceRenamed {} => Ok(Self::Event(ActiveEventSubscription { - event_kind: crate::api::schema::EventKind::WorkspaceRenamed, - last_sequence: 0, - })), - Subscription::WorkspaceMoved {} => Ok(Self::Event(ActiveEventSubscription { - event_kind: crate::api::schema::EventKind::WorkspaceMoved, - last_sequence: 0, - })), - Subscription::WorkspaceReordered {} => Ok(Self::Event(ActiveEventSubscription { - event_kind: crate::api::schema::EventKind::WorkspaceReordered, - last_sequence: 0, - })), - Subscription::WorkspaceClosed {} => Ok(Self::Event(ActiveEventSubscription { - event_kind: crate::api::schema::EventKind::WorkspaceClosed, - last_sequence: 0, - })), - Subscription::WorkspaceFocused {} => Ok(Self::Event(ActiveEventSubscription { - event_kind: crate::api::schema::EventKind::WorkspaceFocused, - last_sequence: 0, - })), - Subscription::WorktreeCreated {} => Ok(Self::Event(ActiveEventSubscription { - event_kind: crate::api::schema::EventKind::WorktreeCreated, - last_sequence: 0, - })), - Subscription::WorktreeOpened {} => Ok(Self::Event(ActiveEventSubscription { - event_kind: crate::api::schema::EventKind::WorktreeOpened, - last_sequence: 0, - })), - Subscription::WorktreeRemoved {} => Ok(Self::Event(ActiveEventSubscription { - event_kind: crate::api::schema::EventKind::WorktreeRemoved, - last_sequence: 0, - })), - Subscription::TabCreated {} => Ok(Self::Event(ActiveEventSubscription { - event_kind: crate::api::schema::EventKind::TabCreated, - last_sequence: 0, - })), - Subscription::TabClosed {} => Ok(Self::Event(ActiveEventSubscription { - event_kind: crate::api::schema::EventKind::TabClosed, - last_sequence: 0, - })), - Subscription::TabFocused {} => Ok(Self::Event(ActiveEventSubscription { - event_kind: crate::api::schema::EventKind::TabFocused, - last_sequence: 0, - })), - Subscription::TabRenamed {} => Ok(Self::Event(ActiveEventSubscription { - event_kind: crate::api::schema::EventKind::TabRenamed, - last_sequence: 0, - })), - Subscription::TabMoved {} => Ok(Self::Event(ActiveEventSubscription { - event_kind: crate::api::schema::EventKind::TabMoved, - last_sequence: 0, - })), - Subscription::PaneCreated {} => Ok(Self::Event(ActiveEventSubscription { - event_kind: crate::api::schema::EventKind::PaneCreated, - last_sequence: 0, - })), - Subscription::PaneClosed {} => Ok(Self::Event(ActiveEventSubscription { - event_kind: crate::api::schema::EventKind::PaneClosed, - last_sequence: 0, - })), - Subscription::PaneUpdated {} => Ok(Self::Event(ActiveEventSubscription { - event_kind: crate::api::schema::EventKind::PaneUpdated, - last_sequence: 0, - })), - Subscription::PaneFocused {} => Ok(Self::Event(ActiveEventSubscription { - event_kind: crate::api::schema::EventKind::PaneFocused, - last_sequence: 0, - })), - Subscription::PaneMoved {} => Ok(Self::Event(ActiveEventSubscription { - event_kind: crate::api::schema::EventKind::PaneMoved, - last_sequence: 0, - })), - Subscription::PaneExited {} => Ok(Self::Event(ActiveEventSubscription { - event_kind: crate::api::schema::EventKind::PaneExited, - last_sequence: 0, - })), - Subscription::PaneAgentDetected {} => Ok(Self::Event(ActiveEventSubscription { - event_kind: crate::api::schema::EventKind::PaneAgentDetected, - last_sequence: 0, - })), - Subscription::LayoutUpdated {} => Ok(Self::Event(ActiveEventSubscription { - event_kind: crate::api::schema::EventKind::LayoutUpdated, - last_sequence: 0, - })), + Subscription::WorkspaceCreated {} => { + Ok(event_subscription(EventKind::WorkspaceCreated)) + } + Subscription::WorkspaceUpdated {} => { + Ok(event_subscription(EventKind::WorkspaceUpdated)) + } + Subscription::WorkspaceMetadataUpdated {} => { + Ok(event_subscription(EventKind::WorkspaceMetadataUpdated)) + } + Subscription::WorkspaceRenamed {} => { + Ok(event_subscription(EventKind::WorkspaceRenamed)) + } + Subscription::WorkspaceMoved {} => Ok(event_subscription(EventKind::WorkspaceMoved)), + Subscription::WorkspaceReordered {} => { + Ok(event_subscription(EventKind::WorkspaceReordered)) + } + Subscription::WorkspaceClosed {} => Ok(event_subscription(EventKind::WorkspaceClosed)), + Subscription::WorkspaceFocused {} => { + Ok(event_subscription(EventKind::WorkspaceFocused)) + } + Subscription::WorktreeCreated {} => Ok(event_subscription(EventKind::WorktreeCreated)), + Subscription::WorktreeOpened {} => Ok(event_subscription(EventKind::WorktreeOpened)), + Subscription::WorktreeRemoved {} => Ok(event_subscription(EventKind::WorktreeRemoved)), + Subscription::TabCreated {} => Ok(event_subscription(EventKind::TabCreated)), + Subscription::TabClosed {} => Ok(event_subscription(EventKind::TabClosed)), + Subscription::TabFocused {} => Ok(event_subscription(EventKind::TabFocused)), + Subscription::TabRenamed {} => Ok(event_subscription(EventKind::TabRenamed)), + Subscription::TabMoved {} => Ok(event_subscription(EventKind::TabMoved)), + Subscription::PaneCreated {} => Ok(event_subscription(EventKind::PaneCreated)), + Subscription::PaneClosed {} => Ok(event_subscription(EventKind::PaneClosed)), + Subscription::PaneUpdated {} => Ok(event_subscription(EventKind::PaneUpdated)), + Subscription::PaneFocused {} => Ok(event_subscription(EventKind::PaneFocused)), + Subscription::PaneMoved {} => Ok(event_subscription(EventKind::PaneMoved)), + Subscription::PaneExited {} => Ok(event_subscription(EventKind::PaneExited)), + Subscription::PaneAgentDetected {} => { + Ok(event_subscription(EventKind::PaneAgentDetected)) + } + Subscription::LayoutUpdated {} => Ok(event_subscription(EventKind::LayoutUpdated)), Subscription::PaneOutputMatched { pane_id, source, @@ -652,6 +602,15 @@ mod tests { } } + fn workspace_focused_event(workspace_id: &str) -> EventEnvelope { + EventEnvelope { + event: EventKind::WorkspaceFocused, + data: EventData::WorkspaceFocused { + workspace_id: workspace_id.into(), + }, + } + } + fn pane_info_with_scroll(scroll: Option) -> PaneInfo { PaneInfo { pane_id: "pane_1".into(), @@ -676,6 +635,35 @@ mod tests { } } + #[test] + fn lifecycle_subscription_skips_history_but_keeps_setup_window_events() { + let event_hub = EventHub::default(); + event_hub.push(workspace_focused_event("before_subscription")); + let event_start_sequence = event_hub.current_sequence(); + event_hub.push(workspace_focused_event("during_setup")); + + let (api_tx, _api_rx) = tokio::sync::mpsc::unbounded_channel(); + let mut subscription = ActiveSubscription::new( + Subscription::WorkspaceFocused {}, + "test", + 0, + &api_tx, + &event_hub, + event_start_sequence, + ) + .expect("workspace focus subscription"); + + let setup_event = subscription + .poll(&api_tx, &event_hub) + .expect("setup-window event"); + assert_eq!(setup_event["data"]["workspace_id"], "during_setup"); + assert!(subscription.poll(&api_tx, &event_hub).is_none()); + + event_hub.push(workspace_focused_event("after_setup")); + let live_event = subscription.poll(&api_tx, &event_hub).expect("live event"); + assert_eq!(live_event["data"]["workspace_id"], "after_setup"); + } + #[test] fn workspace_metadata_subscription_uses_dedicated_event_kind() { let event_hub = EventHub::default(); @@ -686,6 +674,7 @@ mod tests { 0, &api_tx, &event_hub, + event_hub.current_sequence(), ) .expect("workspace metadata subscription"); diff --git a/src/api/wait.rs b/src/api/wait.rs index 7da9b7a9..0c47fbff 100644 --- a/src/api/wait.rs +++ b/src/api/wait.rs @@ -674,8 +674,14 @@ pub(super) fn wait_for_event( Ok(subscription) => subscription, Err(response) => return Ok(Some(serde_json::to_string(&response).unwrap())), }; - let mut active = match ActiveSubscription::new(subscription, &request_id, 0, api_tx, event_hub) - { + let mut active = match ActiveSubscription::new( + subscription, + &request_id, + 0, + api_tx, + event_hub, + event_hub.current_sequence(), + ) { Ok(active) => active, Err(response) => return Ok(Some(serde_json::to_string(&response).unwrap())), }; -- cgit v1.2.3