Connect notification panel to notification toasts

Max Brunsfeld created

Change summary

Cargo.lock                                                        |   1 
crates/collab/src/db/queries/notifications.rs                     |  18 
crates/collab/src/rpc.rs                                          |  35 
crates/collab/src/tests/following_tests.rs                        |   2 
crates/collab_ui/Cargo.toml                                       |   4 
crates/collab_ui/src/collab_titlebar_item.rs                      |  28 
crates/collab_ui/src/collab_ui.rs                                 |   8 
crates/collab_ui/src/notification_panel.rs                        |  37 
crates/collab_ui/src/notifications.rs                             |  12 
crates/collab_ui/src/notifications/contact_notification.rs        |  15 
crates/collab_ui/src/notifications/incoming_call_notification.rs  |   0 
crates/collab_ui/src/notifications/project_shared_notification.rs |   0 
crates/notifications/src/notification_store.rs                    |  58 
crates/rpc/proto/zed.proto                                        |  51 
crates/rpc/src/proto.rs                                           | 181 
15 files changed, 272 insertions(+), 178 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -1566,6 +1566,7 @@ dependencies = [
  "project",
  "recent_projects",
  "rich_text",
+ "rpc",
  "schemars",
  "serde",
  "serde_derive",

crates/collab/src/db/queries/notifications.rs 🔗

@@ -26,11 +26,19 @@ impl Database {
         &self,
         recipient_id: UserId,
         limit: usize,
-    ) -> Result<proto::AddNotifications> {
+        before_id: Option<NotificationId>,
+    ) -> Result<Vec<proto::Notification>> {
         self.transaction(|tx| async move {
-            let mut result = proto::AddNotifications::default();
+            let mut result = Vec::new();
+            let mut condition =
+                Condition::all().add(notification::Column::RecipientId.eq(recipient_id));
+
+            if let Some(before_id) = before_id {
+                condition = condition.add(notification::Column::Id.lt(before_id));
+            }
+
             let mut rows = notification::Entity::find()
-                .filter(notification::Column::RecipientId.eq(recipient_id))
+                .filter(condition)
                 .order_by_desc(notification::Column::Id)
                 .limit(limit as u64)
                 .stream(&*tx)
@@ -40,7 +48,7 @@ impl Database {
                 let Some(kind) = self.notification_kinds_by_id.get(&row.kind) else {
                     continue;
                 };
-                result.notifications.push(proto::Notification {
+                result.push(proto::Notification {
                     id: row.id.to_proto(),
                     kind: kind.to_string(),
                     timestamp: row.created_at.assume_utc().unix_timestamp() as u64,
@@ -49,7 +57,7 @@ impl Database {
                     actor_id: row.actor_id.map(|id| id.to_proto()),
                 });
             }
-            result.notifications.reverse();
+            result.reverse();
             Ok(result)
         })
         .await

crates/collab/src/rpc.rs 🔗

@@ -70,7 +70,7 @@ pub const CLEANUP_TIMEOUT: Duration = Duration::from_secs(10);
 
 const MESSAGE_COUNT_PER_PAGE: usize = 100;
 const MAX_MESSAGE_LEN: usize = 1024;
-const INITIAL_NOTIFICATION_COUNT: usize = 30;
+const NOTIFICATION_COUNT_PER_PAGE: usize = 50;
 
 lazy_static! {
     static ref METRIC_CONNECTIONS: IntGauge =
@@ -269,6 +269,7 @@ impl Server {
             .add_request_handler(send_channel_message)
             .add_request_handler(remove_channel_message)
             .add_request_handler(get_channel_messages)
+            .add_request_handler(get_notifications)
             .add_request_handler(link_channel)
             .add_request_handler(unlink_channel)
             .add_request_handler(move_channel)
@@ -579,17 +580,15 @@ impl Server {
                 this.app_state.db.set_user_connected_once(user_id, true).await?;
             }
 
-            let (contacts, channels_for_user, channel_invites, notifications) = future::try_join4(
+            let (contacts, channels_for_user, channel_invites) = future::try_join3(
                 this.app_state.db.get_contacts(user_id),
                 this.app_state.db.get_channels_for_user(user_id),
                 this.app_state.db.get_channel_invites_for_user(user_id),
-                this.app_state.db.get_notifications(user_id, INITIAL_NOTIFICATION_COUNT)
             ).await?;
 
             {
                 let mut pool = this.connection_pool.lock();
                 pool.add_connection(connection_id, user_id, user.admin);
-                this.peer.send(connection_id, notifications)?;
                 this.peer.send(connection_id, build_initial_contacts_update(contacts, &pool))?;
                 this.peer.send(connection_id, build_initial_channels_update(
                     channels_for_user,
@@ -2099,8 +2098,8 @@ async fn request_contact(
         session.peer.send(connection_id, update.clone())?;
         session.peer.send(
             connection_id,
-            proto::AddNotifications {
-                notifications: vec![notification.clone()],
+            proto::NewNotification {
+                notification: Some(notification.clone()),
             },
         )?;
     }
@@ -2158,8 +2157,8 @@ async fn respond_to_contact_request(
             session.peer.send(connection_id, update.clone())?;
             session.peer.send(
                 connection_id,
-                proto::AddNotifications {
-                    notifications: vec![notification.clone()],
+                proto::NewNotification {
+                    notification: Some(notification.clone()),
                 },
             )?;
         }
@@ -3008,6 +3007,26 @@ async fn get_channel_messages(
     Ok(())
 }
 
+async fn get_notifications(
+    request: proto::GetNotifications,
+    response: Response<proto::GetNotifications>,
+    session: Session,
+) -> Result<()> {
+    let notifications = session
+        .db()
+        .await
+        .get_notifications(
+            session.user_id,
+            NOTIFICATION_COUNT_PER_PAGE,
+            request
+                .before_id
+                .map(|id| db::NotificationId::from_proto(id)),
+        )
+        .await?;
+    response.send(proto::GetNotificationsResponse { notifications })?;
+    Ok(())
+}
+
 async fn update_diff_base(request: proto::UpdateDiffBase, session: Session) -> Result<()> {
     let project_id = ProjectId::from_proto(request.project_id);
     let project_connection_ids = session

crates/collab/src/tests/following_tests.rs 🔗

@@ -1,6 +1,6 @@
 use crate::{rpc::RECONNECT_TIMEOUT, tests::TestServer};
 use call::ActiveCall;
-use collab_ui::project_shared_notification::ProjectSharedNotification;
+use collab_ui::notifications::project_shared_notification::ProjectSharedNotification;
 use editor::{Editor, ExcerptRange, MultiBuffer};
 use gpui::{executor::Deterministic, geometry::vector::vec2f, TestAppContext, ViewHandle};
 use live_kit_client::MacOSDisplay;

crates/collab_ui/Cargo.toml 🔗

@@ -41,7 +41,8 @@ notifications = { path = "../notifications" }
 rich_text = { path = "../rich_text" }
 picker = { path = "../picker" }
 project = { path = "../project" }
-recent_projects = {path = "../recent_projects"}
+recent_projects = { path = "../recent_projects" }
+rpc = { path = "../rpc" }
 settings = { path = "../settings" }
 feature_flags = {path = "../feature_flags"}
 theme = { path = "../theme" }
@@ -68,6 +69,7 @@ editor = { path = "../editor", features = ["test-support"] }
 gpui = { path = "../gpui", features = ["test-support"] }
 notifications = { path = "../notifications", features = ["test-support"] }
 project = { path = "../project", features = ["test-support"] }
+rpc = { path = "../rpc", features = ["test-support"] }
 settings = { path = "../settings", features = ["test-support"] }
 util = { path = "../util", features = ["test-support"] }
 workspace = { path = "../workspace", features = ["test-support"] }

crates/collab_ui/src/collab_titlebar_item.rs 🔗

@@ -1,10 +1,10 @@
 use crate::{
-    contact_notification::ContactNotification, face_pile::FacePile, toggle_deafen, toggle_mute,
-    toggle_screen_sharing, LeaveCall, ToggleDeafen, ToggleMute, ToggleScreenSharing,
+    face_pile::FacePile, toggle_deafen, toggle_mute, toggle_screen_sharing, LeaveCall,
+    ToggleDeafen, ToggleMute, ToggleScreenSharing,
 };
 use auto_update::AutoUpdateStatus;
 use call::{ActiveCall, ParticipantLocation, Room};
-use client::{proto::PeerId, Client, ContactEventKind, SignIn, SignOut, User, UserStore};
+use client::{proto::PeerId, Client, SignIn, SignOut, User, UserStore};
 use clock::ReplicaId;
 use context_menu::{ContextMenu, ContextMenuItem};
 use gpui::{
@@ -151,28 +151,6 @@ impl CollabTitlebarItem {
             this.window_activation_changed(active, cx)
         }));
         subscriptions.push(cx.observe(&user_store, |_, _, cx| cx.notify()));
-        subscriptions.push(
-            cx.subscribe(&user_store, move |this, user_store, event, cx| {
-                if let Some(workspace) = this.workspace.upgrade(cx) {
-                    workspace.update(cx, |workspace, cx| {
-                        if let client::Event::Contact { user, kind } = event {
-                            if let ContactEventKind::Requested | ContactEventKind::Accepted = kind {
-                                workspace.show_notification(user.id as usize, cx, |cx| {
-                                    cx.add_view(|cx| {
-                                        ContactNotification::new(
-                                            user.clone(),
-                                            *kind,
-                                            user_store,
-                                            cx,
-                                        )
-                                    })
-                                })
-                            }
-                        }
-                    });
-                }
-            }),
-        );
 
         Self {
             workspace: workspace.weak_handle(),

crates/collab_ui/src/collab_ui.rs 🔗

@@ -2,13 +2,10 @@ pub mod channel_view;
 pub mod chat_panel;
 pub mod collab_panel;
 mod collab_titlebar_item;
-mod contact_notification;
 mod face_pile;
-mod incoming_call_notification;
 pub mod notification_panel;
-mod notifications;
+pub mod notifications;
 mod panel_settings;
-pub mod project_shared_notification;
 mod sharing_status_indicator;
 
 use call::{report_call_event_for_room, ActiveCall, Room};
@@ -48,8 +45,7 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
     collab_titlebar_item::init(cx);
     collab_panel::init(cx);
     chat_panel::init(cx);
-    incoming_call_notification::init(&app_state, cx);
-    project_shared_notification::init(&app_state, cx);
+    notifications::init(&app_state, cx);
     sharing_status_indicator::init(cx);
 
     cx.add_global_action(toggle_screen_sharing);

crates/collab_ui/src/notification_panel.rs 🔗

@@ -1,5 +1,7 @@
 use crate::{
-    format_timestamp, is_channels_feature_enabled, render_avatar, NotificationPanelSettings,
+    format_timestamp, is_channels_feature_enabled,
+    notifications::contact_notification::ContactNotification, render_avatar,
+    NotificationPanelSettings,
 };
 use anyhow::Result;
 use channel::ChannelStore;
@@ -39,6 +41,7 @@ pub struct NotificationPanel {
     notification_list: ListState<Self>,
     pending_serialization: Task<Option<()>>,
     subscriptions: Vec<gpui::Subscription>,
+    workspace: WeakViewHandle<Workspace>,
     local_timezone: UtcOffset,
     has_focus: bool,
 }
@@ -64,6 +67,7 @@ impl NotificationPanel {
         let fs = workspace.app_state().fs.clone();
         let client = workspace.app_state().client.clone();
         let user_store = workspace.app_state().user_store.clone();
+        let workspace_handle = workspace.weak_handle();
 
         let notification_list =
             ListState::<Self>::new(0, Orientation::Top, 1000., move |this, ix, cx| {
@@ -96,6 +100,7 @@ impl NotificationPanel {
                 notification_store: NotificationStore::global(cx),
                 notification_list,
                 pending_serialization: Task::ready(None),
+                workspace: workspace_handle,
                 has_focus: false,
                 subscriptions: Vec::new(),
                 active: false,
@@ -177,7 +182,7 @@ impl NotificationPanel {
         let notification_store = self.notification_store.read(cx);
         let user_store = self.user_store.read(cx);
         let channel_store = self.channel_store.read(cx);
-        let entry = notification_store.notification_at(ix).unwrap();
+        let entry = notification_store.notification_at(ix)?;
         let now = OffsetDateTime::now_utc();
         let timestamp = entry.timestamp;
 
@@ -293,7 +298,7 @@ impl NotificationPanel {
         &mut self,
         _: ModelHandle<NotificationStore>,
         event: &NotificationEvent,
-        _: &mut ViewContext<Self>,
+        cx: &mut ViewContext<Self>,
     ) {
         match event {
             NotificationEvent::NotificationsUpdated {
@@ -301,7 +306,33 @@ impl NotificationPanel {
                 new_count,
             } => {
                 self.notification_list.splice(old_range.clone(), *new_count);
+                cx.notify();
             }
+            NotificationEvent::NewNotification { entry } => match entry.notification {
+                Notification::ContactRequest { actor_id }
+                | Notification::ContactRequestAccepted { actor_id } => {
+                    let user_store = self.user_store.clone();
+                    let Some(user) = user_store.read(cx).get_cached_user(actor_id) else {
+                        return;
+                    };
+                    self.workspace
+                        .update(cx, |workspace, cx| {
+                            workspace.show_notification(actor_id as usize, cx, |cx| {
+                                cx.add_view(|cx| {
+                                    ContactNotification::new(
+                                        user.clone(),
+                                        entry.notification.clone(),
+                                        user_store,
+                                        cx,
+                                    )
+                                })
+                            })
+                        })
+                        .ok();
+                }
+                Notification::ChannelInvitation { .. } => {}
+                Notification::ChannelMessageMention { .. } => {}
+            },
         }
     }
 }

crates/collab_ui/src/notifications.rs 🔗

@@ -2,13 +2,23 @@ use client::User;
 use gpui::{
     elements::*,
     platform::{CursorStyle, MouseButton},
-    AnyElement, Element, ViewContext,
+    AnyElement, AppContext, Element, ViewContext,
 };
 use std::sync::Arc;
+use workspace::AppState;
+
+pub mod contact_notification;
+pub mod incoming_call_notification;
+pub mod project_shared_notification;
 
 enum Dismiss {}
 enum Button {}
 
+pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
+    incoming_call_notification::init(app_state, cx);
+    project_shared_notification::init(app_state, cx);
+}
+
 pub fn render_user_notification<F, V: 'static>(
     user: Arc<User>,
     title: &'static str,

crates/collab_ui/src/contact_notification.rs → crates/collab_ui/src/notifications/contact_notification.rs 🔗

@@ -1,14 +1,13 @@
-use std::sync::Arc;
-
 use crate::notifications::render_user_notification;
 use client::{ContactEventKind, User, UserStore};
 use gpui::{elements::*, Entity, ModelHandle, View, ViewContext};
+use std::sync::Arc;
 use workspace::notifications::Notification;
 
 pub struct ContactNotification {
     user_store: ModelHandle<UserStore>,
     user: Arc<User>,
-    kind: client::ContactEventKind,
+    notification: rpc::Notification,
 }
 
 #[derive(Clone, PartialEq)]
@@ -34,8 +33,8 @@ impl View for ContactNotification {
     }
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-        match self.kind {
-            ContactEventKind::Requested => render_user_notification(
+        match self.notification {
+            rpc::Notification::ContactRequest { .. } => render_user_notification(
                 self.user.clone(),
                 "wants to add you as a contact",
                 Some("They won't be alerted if you decline."),
@@ -56,7 +55,7 @@ impl View for ContactNotification {
                 ],
                 cx,
             ),
-            ContactEventKind::Accepted => render_user_notification(
+            rpc::Notification::ContactRequestAccepted { .. } => render_user_notification(
                 self.user.clone(),
                 "accepted your contact request",
                 None,
@@ -78,7 +77,7 @@ impl Notification for ContactNotification {
 impl ContactNotification {
     pub fn new(
         user: Arc<User>,
-        kind: client::ContactEventKind,
+        notification: rpc::Notification,
         user_store: ModelHandle<UserStore>,
         cx: &mut ViewContext<Self>,
     ) -> Self {
@@ -97,7 +96,7 @@ impl ContactNotification {
 
         Self {
             user,
-            kind,
+            notification,
             user_store,
         }
     }

crates/notifications/src/notification_store.rs 🔗

@@ -2,7 +2,7 @@ use anyhow::Result;
 use channel::{ChannelMessage, ChannelMessageId, ChannelStore};
 use client::{Client, UserStore};
 use collections::HashMap;
-use gpui::{AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle};
+use gpui::{AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, Task};
 use rpc::{proto, AnyNotification, Notification, TypedEnvelope};
 use std::{ops::Range, sync::Arc};
 use sum_tree::{Bias, SumTree};
@@ -14,7 +14,7 @@ pub fn init(client: Arc<Client>, user_store: ModelHandle<UserStore>, cx: &mut Ap
 }
 
 pub struct NotificationStore {
-    _client: Arc<Client>,
+    client: Arc<Client>,
     user_store: ModelHandle<UserStore>,
     channel_messages: HashMap<u64, ChannelMessage>,
     channel_store: ModelHandle<ChannelStore>,
@@ -27,6 +27,9 @@ pub enum NotificationEvent {
         old_range: Range<usize>,
         new_count: usize,
     },
+    NewNotification {
+        entry: NotificationEntry,
+    },
 }
 
 #[derive(Debug, PartialEq, Eq, Clone)]
@@ -63,16 +66,19 @@ impl NotificationStore {
         user_store: ModelHandle<UserStore>,
         cx: &mut ModelContext<Self>,
     ) -> Self {
-        Self {
+        let this = Self {
             channel_store: ChannelStore::global(cx),
             notifications: Default::default(),
             channel_messages: Default::default(),
             _subscriptions: vec![
-                client.add_message_handler(cx.handle(), Self::handle_add_notifications)
+                client.add_message_handler(cx.handle(), Self::handle_new_notification)
             ],
             user_store,
-            _client: client,
-        }
+            client,
+        };
+
+        this.load_more_notifications(cx).detach();
+        this
     }
 
     pub fn notification_count(&self) -> usize {
@@ -93,18 +99,42 @@ impl NotificationStore {
         cursor.item()
     }
 
-    async fn handle_add_notifications(
+    pub fn load_more_notifications(&self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
+        let request = self
+            .client
+            .request(proto::GetNotifications { before_id: None });
+        cx.spawn(|this, cx| async move {
+            let response = request.await?;
+            Self::add_notifications(this, false, response.notifications, cx).await?;
+            Ok(())
+        })
+    }
+
+    async fn handle_new_notification(
         this: ModelHandle<Self>,
-        envelope: TypedEnvelope<proto::AddNotifications>,
+        envelope: TypedEnvelope<proto::NewNotification>,
         _: Arc<Client>,
+        cx: AsyncAppContext,
+    ) -> Result<()> {
+        Self::add_notifications(
+            this,
+            true,
+            envelope.payload.notification.into_iter().collect(),
+            cx,
+        )
+        .await
+    }
+
+    async fn add_notifications(
+        this: ModelHandle<Self>,
+        is_new: bool,
+        notifications: Vec<proto::Notification>,
         mut cx: AsyncAppContext,
     ) -> Result<()> {
         let mut user_ids = Vec::new();
         let mut message_ids = Vec::new();
 
-        let notifications = envelope
-            .payload
-            .notifications
+        let notifications = notifications
             .into_iter()
             .filter_map(|message| {
                 Some(NotificationEntry {
@@ -195,6 +225,12 @@ impl NotificationStore {
                     cursor.next(&());
                 }
 
+                if is_new {
+                    cx.emit(NotificationEvent::NewNotification {
+                        entry: notification.clone(),
+                    });
+                }
+
                 new_notifications.push(notification, &());
             }
 

crates/rpc/proto/zed.proto 🔗

@@ -155,25 +155,28 @@ message Envelope {
         UpdateChannelBufferCollaborators update_channel_buffer_collaborators = 128;
         RejoinChannelBuffers rejoin_channel_buffers = 129;
         RejoinChannelBuffersResponse rejoin_channel_buffers_response = 130;
-        AckBufferOperation ack_buffer_operation = 143;
-
-        JoinChannelChat join_channel_chat = 131;
-        JoinChannelChatResponse join_channel_chat_response = 132;
-        LeaveChannelChat leave_channel_chat = 133;
-        SendChannelMessage send_channel_message = 134;
-        SendChannelMessageResponse send_channel_message_response = 135;
-        ChannelMessageSent channel_message_sent = 136;
-        GetChannelMessages get_channel_messages = 137;
-        GetChannelMessagesResponse get_channel_messages_response = 138;
-        RemoveChannelMessage remove_channel_message = 139;
-        AckChannelMessage ack_channel_message = 144;
-
-        LinkChannel link_channel = 140;
-        UnlinkChannel unlink_channel = 141;
-        MoveChannel move_channel = 142;
-
-        AddNotifications add_notifications = 145;
-        GetChannelMessagesById get_channel_messages_by_id = 146; // Current max
+        AckBufferOperation ack_buffer_operation = 131;
+
+        JoinChannelChat join_channel_chat = 132;
+        JoinChannelChatResponse join_channel_chat_response = 133;
+        LeaveChannelChat leave_channel_chat = 134;
+        SendChannelMessage send_channel_message = 135;
+        SendChannelMessageResponse send_channel_message_response = 136;
+        ChannelMessageSent channel_message_sent = 137;
+        GetChannelMessages get_channel_messages = 138;
+        GetChannelMessagesResponse get_channel_messages_response = 139;
+        RemoveChannelMessage remove_channel_message = 140;
+        AckChannelMessage ack_channel_message = 141;
+        GetChannelMessagesById get_channel_messages_by_id = 142;
+
+        LinkChannel link_channel = 143;
+        UnlinkChannel unlink_channel = 144;
+        MoveChannel move_channel = 145;
+
+        NewNotification new_notification = 146;
+        GetNotifications get_notifications = 147;
+        GetNotificationsResponse get_notifications_response = 148; // Current max
+
     }
 }
 
@@ -1563,7 +1566,15 @@ message UpdateDiffBase {
     optional string diff_base = 3;
 }
 
-message AddNotifications {
+message GetNotifications {
+    optional uint64 before_id = 1;
+}
+
+message NewNotification {
+    Notification notification = 1;
+}
+
+message GetNotificationsResponse {
     repeated Notification notifications = 1;
 }
 

crates/rpc/src/proto.rs 🔗

@@ -133,7 +133,8 @@ impl fmt::Display for PeerId {
 
 messages!(
     (Ack, Foreground),
-    (AddNotifications, Foreground),
+    (AckBufferOperation, Background),
+    (AckChannelMessage, Background),
     (AddProjectCollaborator, Foreground),
     (ApplyCodeAction, Background),
     (ApplyCodeActionResponse, Background),
@@ -144,58 +145,74 @@ messages!(
     (Call, Foreground),
     (CallCanceled, Foreground),
     (CancelCall, Foreground),
+    (ChannelMessageSent, Foreground),
     (CopyProjectEntry, Foreground),
     (CreateBufferForPeer, Foreground),
     (CreateChannel, Foreground),
     (CreateChannelResponse, Foreground),
-    (ChannelMessageSent, Foreground),
     (CreateProjectEntry, Foreground),
     (CreateRoom, Foreground),
     (CreateRoomResponse, Foreground),
     (DeclineCall, Foreground),
+    (DeleteChannel, Foreground),
     (DeleteProjectEntry, Foreground),
     (Error, Foreground),
     (ExpandProjectEntry, Foreground),
+    (ExpandProjectEntryResponse, Foreground),
     (Follow, Foreground),
     (FollowResponse, Foreground),
     (FormatBuffers, Foreground),
     (FormatBuffersResponse, Foreground),
     (FuzzySearchUsers, Foreground),
-    (GetCodeActions, Background),
-    (GetCodeActionsResponse, Background),
-    (GetHover, Background),
-    (GetHoverResponse, Background),
+    (GetChannelMembers, Foreground),
+    (GetChannelMembersResponse, Foreground),
     (GetChannelMessages, Background),
-    (GetChannelMessagesResponse, Background),
     (GetChannelMessagesById, Background),
-    (SendChannelMessage, Background),
-    (SendChannelMessageResponse, Background),
+    (GetChannelMessagesResponse, Background),
+    (GetCodeActions, Background),
+    (GetCodeActionsResponse, Background),
     (GetCompletions, Background),
     (GetCompletionsResponse, Background),
     (GetDefinition, Background),
     (GetDefinitionResponse, Background),
-    (GetTypeDefinition, Background),
-    (GetTypeDefinitionResponse, Background),
     (GetDocumentHighlights, Background),
     (GetDocumentHighlightsResponse, Background),
-    (GetReferences, Background),
-    (GetReferencesResponse, Background),
+    (GetHover, Background),
+    (GetHoverResponse, Background),
+    (GetNotifications, Foreground),
+    (GetNotificationsResponse, Foreground),
+    (GetPrivateUserInfo, Foreground),
+    (GetPrivateUserInfoResponse, Foreground),
     (GetProjectSymbols, Background),
     (GetProjectSymbolsResponse, Background),
+    (GetReferences, Background),
+    (GetReferencesResponse, Background),
+    (GetTypeDefinition, Background),
+    (GetTypeDefinitionResponse, Background),
     (GetUsers, Foreground),
     (Hello, Foreground),
     (IncomingCall, Foreground),
+    (InlayHints, Background),
+    (InlayHintsResponse, Background),
     (InviteChannelMember, Foreground),
-    (UsersResponse, Foreground),
+    (JoinChannel, Foreground),
+    (JoinChannelBuffer, Foreground),
+    (JoinChannelBufferResponse, Foreground),
+    (JoinChannelChat, Foreground),
+    (JoinChannelChatResponse, Foreground),
     (JoinProject, Foreground),
     (JoinProjectResponse, Foreground),
     (JoinRoom, Foreground),
     (JoinRoomResponse, Foreground),
-    (JoinChannelChat, Foreground),
-    (JoinChannelChatResponse, Foreground),
+    (LeaveChannelBuffer, Background),
     (LeaveChannelChat, Foreground),
     (LeaveProject, Foreground),
     (LeaveRoom, Foreground),
+    (LinkChannel, Foreground),
+    (MoveChannel, Foreground),
+    (NewNotification, Foreground),
+    (OnTypeFormatting, Background),
+    (OnTypeFormattingResponse, Background),
     (OpenBufferById, Background),
     (OpenBufferByPath, Background),
     (OpenBufferForSymbol, Background),
@@ -203,58 +220,54 @@ messages!(
     (OpenBufferResponse, Background),
     (PerformRename, Background),
     (PerformRenameResponse, Background),
-    (OnTypeFormatting, Background),
-    (OnTypeFormattingResponse, Background),
-    (InlayHints, Background),
-    (InlayHintsResponse, Background),
-    (ResolveInlayHint, Background),
-    (ResolveInlayHintResponse, Background),
-    (RefreshInlayHints, Foreground),
     (Ping, Foreground),
     (PrepareRename, Background),
     (PrepareRenameResponse, Background),
-    (ExpandProjectEntryResponse, Foreground),
     (ProjectEntryResponse, Foreground),
+    (RefreshInlayHints, Foreground),
+    (RejoinChannelBuffers, Foreground),
+    (RejoinChannelBuffersResponse, Foreground),
     (RejoinRoom, Foreground),
     (RejoinRoomResponse, Foreground),
-    (RemoveContact, Foreground),
-    (RemoveChannelMember, Foreground),
-    (RemoveChannelMessage, Foreground),
     (ReloadBuffers, Foreground),
     (ReloadBuffersResponse, Foreground),
+    (RemoveChannelMember, Foreground),
+    (RemoveChannelMessage, Foreground),
+    (RemoveContact, Foreground),
     (RemoveProjectCollaborator, Foreground),
+    (RenameChannel, Foreground),
+    (RenameChannelResponse, Foreground),
     (RenameProjectEntry, Foreground),
     (RequestContact, Foreground),
-    (RespondToContactRequest, Foreground),
+    (ResolveInlayHint, Background),
+    (ResolveInlayHintResponse, Background),
     (RespondToChannelInvite, Foreground),
-    (JoinChannel, Foreground),
+    (RespondToContactRequest, Foreground),
     (RoomUpdated, Foreground),
     (SaveBuffer, Foreground),
-    (RenameChannel, Foreground),
-    (RenameChannelResponse, Foreground),
-    (SetChannelMemberAdmin, Foreground),
     (SearchProject, Background),
     (SearchProjectResponse, Background),
+    (SendChannelMessage, Background),
+    (SendChannelMessageResponse, Background),
+    (SetChannelMemberAdmin, Foreground),
     (ShareProject, Foreground),
     (ShareProjectResponse, Foreground),
     (ShowContacts, Foreground),
     (StartLanguageServer, Foreground),
     (SynchronizeBuffers, Foreground),
     (SynchronizeBuffersResponse, Foreground),
-    (RejoinChannelBuffers, Foreground),
-    (RejoinChannelBuffersResponse, Foreground),
     (Test, Foreground),
     (Unfollow, Foreground),
+    (UnlinkChannel, Foreground),
     (UnshareProject, Foreground),
     (UpdateBuffer, Foreground),
     (UpdateBufferFile, Foreground),
-    (UpdateContacts, Foreground),
-    (DeleteChannel, Foreground),
-    (MoveChannel, Foreground),
-    (LinkChannel, Foreground),
-    (UnlinkChannel, Foreground),
+    (UpdateChannelBuffer, Foreground),
+    (UpdateChannelBufferCollaborators, Foreground),
     (UpdateChannels, Foreground),
+    (UpdateContacts, Foreground),
     (UpdateDiagnosticSummary, Foreground),
+    (UpdateDiffBase, Foreground),
     (UpdateFollowers, Foreground),
     (UpdateInviteInfo, Foreground),
     (UpdateLanguageServer, Foreground),
@@ -263,18 +276,7 @@ messages!(
     (UpdateProjectCollaborator, Foreground),
     (UpdateWorktree, Foreground),
     (UpdateWorktreeSettings, Foreground),
-    (UpdateDiffBase, Foreground),
-    (GetPrivateUserInfo, Foreground),
-    (GetPrivateUserInfoResponse, Foreground),
-    (GetChannelMembers, Foreground),
-    (GetChannelMembersResponse, Foreground),
-    (JoinChannelBuffer, Foreground),
-    (JoinChannelBufferResponse, Foreground),
-    (LeaveChannelBuffer, Background),
-    (UpdateChannelBuffer, Foreground),
-    (UpdateChannelBufferCollaborators, Foreground),
-    (AckBufferOperation, Background),
-    (AckChannelMessage, Background),
+    (UsersResponse, Foreground),
 );
 
 request_messages!(
@@ -286,73 +288,74 @@ request_messages!(
     (Call, Ack),
     (CancelCall, Ack),
     (CopyProjectEntry, ProjectEntryResponse),
+    (CreateChannel, CreateChannelResponse),
     (CreateProjectEntry, ProjectEntryResponse),
     (CreateRoom, CreateRoomResponse),
-    (CreateChannel, CreateChannelResponse),
     (DeclineCall, Ack),
+    (DeleteChannel, Ack),
     (DeleteProjectEntry, ProjectEntryResponse),
     (ExpandProjectEntry, ExpandProjectEntryResponse),
     (Follow, FollowResponse),
     (FormatBuffers, FormatBuffersResponse),
+    (FuzzySearchUsers, UsersResponse),
+    (GetChannelMembers, GetChannelMembersResponse),
+    (GetChannelMessages, GetChannelMessagesResponse),
+    (GetChannelMessagesById, GetChannelMessagesResponse),
     (GetCodeActions, GetCodeActionsResponse),
-    (GetHover, GetHoverResponse),
     (GetCompletions, GetCompletionsResponse),
     (GetDefinition, GetDefinitionResponse),
-    (GetTypeDefinition, GetTypeDefinitionResponse),
     (GetDocumentHighlights, GetDocumentHighlightsResponse),
-    (GetReferences, GetReferencesResponse),
+    (GetHover, GetHoverResponse),
+    (GetNotifications, GetNotificationsResponse),
     (GetPrivateUserInfo, GetPrivateUserInfoResponse),
     (GetProjectSymbols, GetProjectSymbolsResponse),
-    (FuzzySearchUsers, UsersResponse),
+    (GetReferences, GetReferencesResponse),
+    (GetTypeDefinition, GetTypeDefinitionResponse),
     (GetUsers, UsersResponse),
+    (IncomingCall, Ack),
+    (InlayHints, InlayHintsResponse),
     (InviteChannelMember, Ack),
+    (JoinChannel, JoinRoomResponse),
+    (JoinChannelBuffer, JoinChannelBufferResponse),
+    (JoinChannelChat, JoinChannelChatResponse),
     (JoinProject, JoinProjectResponse),
     (JoinRoom, JoinRoomResponse),
-    (JoinChannelChat, JoinChannelChatResponse),
+    (LeaveChannelBuffer, Ack),
     (LeaveRoom, Ack),
-    (RejoinRoom, RejoinRoomResponse),
-    (IncomingCall, Ack),
+    (LinkChannel, Ack),
+    (MoveChannel, Ack),
+    (OnTypeFormatting, OnTypeFormattingResponse),
     (OpenBufferById, OpenBufferResponse),
     (OpenBufferByPath, OpenBufferResponse),
     (OpenBufferForSymbol, OpenBufferForSymbolResponse),
-    (Ping, Ack),
     (PerformRename, PerformRenameResponse),
+    (Ping, Ack),
     (PrepareRename, PrepareRenameResponse),
-    (OnTypeFormatting, OnTypeFormattingResponse),
-    (InlayHints, InlayHintsResponse),
-    (ResolveInlayHint, ResolveInlayHintResponse),
     (RefreshInlayHints, Ack),
+    (RejoinChannelBuffers, RejoinChannelBuffersResponse),
+    (RejoinRoom, RejoinRoomResponse),
     (ReloadBuffers, ReloadBuffersResponse),
-    (RequestContact, Ack),
     (RemoveChannelMember, Ack),
-    (RemoveContact, Ack),
-    (RespondToContactRequest, Ack),
-    (RespondToChannelInvite, Ack),
-    (SetChannelMemberAdmin, Ack),
-    (SendChannelMessage, SendChannelMessageResponse),
-    (GetChannelMessages, GetChannelMessagesResponse),
-    (GetChannelMessagesById, GetChannelMessagesResponse),
-    (GetChannelMembers, GetChannelMembersResponse),
-    (JoinChannel, JoinRoomResponse),
     (RemoveChannelMessage, Ack),
-    (DeleteChannel, Ack),
-    (RenameProjectEntry, ProjectEntryResponse),
+    (RemoveContact, Ack),
     (RenameChannel, RenameChannelResponse),
-    (LinkChannel, Ack),
-    (UnlinkChannel, Ack),
-    (MoveChannel, Ack),
+    (RenameProjectEntry, ProjectEntryResponse),
+    (RequestContact, Ack),
+    (ResolveInlayHint, ResolveInlayHintResponse),
+    (RespondToChannelInvite, Ack),
+    (RespondToContactRequest, Ack),
     (SaveBuffer, BufferSaved),
     (SearchProject, SearchProjectResponse),
+    (SendChannelMessage, SendChannelMessageResponse),
+    (SetChannelMemberAdmin, Ack),
     (ShareProject, ShareProjectResponse),
     (SynchronizeBuffers, SynchronizeBuffersResponse),
-    (RejoinChannelBuffers, RejoinChannelBuffersResponse),
     (Test, Test),
+    (UnlinkChannel, Ack),
     (UpdateBuffer, Ack),
     (UpdateParticipantLocation, Ack),
     (UpdateProject, Ack),
     (UpdateWorktree, Ack),
-    (JoinChannelBuffer, JoinChannelBufferResponse),
-    (LeaveChannelBuffer, Ack)
 );
 
 entity_messages!(
@@ -371,25 +374,25 @@ entity_messages!(
     GetCodeActions,
     GetCompletions,
     GetDefinition,
-    GetTypeDefinition,
     GetDocumentHighlights,
     GetHover,
-    GetReferences,
     GetProjectSymbols,
+    GetReferences,
+    GetTypeDefinition,
+    InlayHints,
     JoinProject,
     LeaveProject,
+    OnTypeFormatting,
     OpenBufferById,
     OpenBufferByPath,
     OpenBufferForSymbol,
     PerformRename,
-    OnTypeFormatting,
-    InlayHints,
-    ResolveInlayHint,
-    RefreshInlayHints,
     PrepareRename,
+    RefreshInlayHints,
     ReloadBuffers,
     RemoveProjectCollaborator,
     RenameProjectEntry,
+    ResolveInlayHint,
     SaveBuffer,
     SearchProject,
     StartLanguageServer,
@@ -398,19 +401,19 @@ entity_messages!(
     UpdateBuffer,
     UpdateBufferFile,
     UpdateDiagnosticSummary,
+    UpdateDiffBase,
     UpdateLanguageServer,
     UpdateProject,
     UpdateProjectCollaborator,
     UpdateWorktree,
     UpdateWorktreeSettings,
-    UpdateDiffBase
 );
 
 entity_messages!(
     channel_id,
     ChannelMessageSent,
-    UpdateChannelBuffer,
     RemoveChannelMessage,
+    UpdateChannelBuffer,
     UpdateChannelBufferCollaborators,
 );