Detailed changes
@@ -43,6 +43,7 @@ pub type ChannelData = (Channel, ChannelPath);
pub struct Channel {
pub id: ChannelId,
pub name: String,
+ pub has_changed: bool,
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Serialize, Deserialize)]
@@ -207,6 +208,13 @@ impl ChannelStore {
)
}
+ pub fn has_channel_buffer_changed(&self, channel_id: ChannelId) -> Option<bool> {
+ self.channel_index
+ .by_id()
+ .get(&channel_id)
+ .map(|channel| channel.has_changed)
+ }
+
pub fn open_channel_chat(
&mut self,
channel_id: ChannelId,
@@ -779,6 +787,7 @@ impl ChannelStore {
Arc::new(Channel {
id: channel.id,
name: channel.name,
+ has_changed: false,
}),
),
}
@@ -787,7 +796,8 @@ impl ChannelStore {
let channels_changed = !payload.channels.is_empty()
|| !payload.delete_channels.is_empty()
|| !payload.insert_edge.is_empty()
- || !payload.delete_edge.is_empty();
+ || !payload.delete_edge.is_empty()
+ || !payload.notes_changed.is_empty();
if channels_changed {
if !payload.delete_channels.is_empty() {
@@ -814,6 +824,10 @@ impl ChannelStore {
index.insert(channel)
}
+ for id_changed in payload.notes_changed {
+ index.has_changed(id_changed);
+ }
+
for edge in payload.insert_edge {
index.insert_edge(edge.channel_id, edge.parent_id);
}
@@ -76,6 +76,12 @@ impl<'a> ChannelPathsInsertGuard<'a> {
}
}
+ pub fn has_changed(&mut self, channel_id: ChannelId) {
+ if let Some(channel) = self.channels_by_id.get_mut(&channel_id) {
+ Arc::make_mut(channel).has_changed = true;
+ }
+ }
+
pub fn insert(&mut self, channel_proto: proto::Channel) {
if let Some(existing_channel) = self.channels_by_id.get_mut(&channel_proto.id) {
Arc::make_mut(existing_channel).name = channel_proto.name;
@@ -85,6 +91,7 @@ impl<'a> ChannelPathsInsertGuard<'a> {
Arc::new(Channel {
id: channel_proto.id,
name: channel_proto.name,
+ has_changed: false,
}),
);
self.insert_root(channel_proto.id);
@@ -291,12 +291,12 @@ CREATE INDEX "index_user_features_on_user_id" ON "user_features" ("user_id");
CREATE INDEX "index_user_features_on_feature_id" ON "user_features" ("feature_id");
-CREATE TABLE "observed_channel_note_edits" (
+CREATE TABLE "observed_buffer_edits" (
"user_id" INTEGER NOT NULL REFERENCES users (id) ON DELETE CASCADE,
- "channel_id" INTEGER NOT NULL REFERENCES channels (id) ON DELETE CASCADE,
+ "buffer_id" INTEGER NOT NULL REFERENCES buffers (id) ON DELETE CASCADE,
"epoch" INTEGER NOT NULL,
"lamport_timestamp" INTEGER NOT NULL,
- PRIMARY KEY (user_id, channel_id)
+ PRIMARY KEY (user_id, buffer_id)
);
-CREATE UNIQUE INDEX "index_observed_notes_user_and_channel_id" ON "observed_channel_note_edits" ("user_id", "channel_id");
+CREATE UNIQUE INDEX "index_observed_buffers_user_and_buffer_id" ON "observed_buffer_edits" ("user_id", "buffer_id");
@@ -1,9 +1,9 @@
-CREATE TABLE "observed_channel_note_edits" (
+CREATE TABLE "observed_buffer_edits" (
"user_id" INTEGER NOT NULL REFERENCES users (id) ON DELETE CASCADE,
- "channel_id" INTEGER NOT NULL REFERENCES channels (id) ON DELETE CASCADE,
+ "buffer_id" INTEGER NOT NULL REFERENCES buffers (id) ON DELETE CASCADE,
"epoch" INTEGER NOT NULL,
"lamport_timestamp" INTEGER NOT NULL,
- PRIMARY KEY (user_id, channel_id)
+ PRIMARY KEY (user_id, buffer_id)
);
-CREATE UNIQUE INDEX "index_observed_notes_user_and_channel_id" ON "observed_channel_note_edits" ("user_id", "channel_id");
+CREATE UNIQUE INDEX "index_observed_buffer_user_and_buffer_id" ON "observed_buffer_edits" ("user_id", "buffer_id");
@@ -436,6 +436,7 @@ pub struct Channel {
pub struct ChannelsForUser {
pub channels: ChannelGraph,
pub channel_participants: HashMap<ChannelId, Vec<UserId>>,
+ pub channels_with_changed_notes: HashSet<ChannelId>,
pub channels_with_admin_privileges: HashSet<ChannelId>,
}
@@ -80,20 +80,20 @@ impl Database {
// Save the last observed operation
if let Some(max_operation) = max_operation {
- observed_note_edits::Entity::insert(observed_note_edits::ActiveModel {
+ observed_buffer_edits::Entity::insert(observed_buffer_edits::ActiveModel {
user_id: ActiveValue::Set(user_id),
- channel_id: ActiveValue::Set(channel_id),
+ buffer_id: ActiveValue::Set(buffer.id),
epoch: ActiveValue::Set(max_operation.0),
lamport_timestamp: ActiveValue::Set(max_operation.1),
})
.on_conflict(
OnConflict::columns([
- observed_note_edits::Column::UserId,
- observed_note_edits::Column::ChannelId,
+ observed_buffer_edits::Column::UserId,
+ observed_buffer_edits::Column::BufferId,
])
.update_columns([
- observed_note_edits::Column::Epoch,
- observed_note_edits::Column::LamportTimestamp,
+ observed_buffer_edits::Column::Epoch,
+ observed_buffer_edits::Column::LamportTimestamp,
])
.to_owned(),
)
@@ -110,20 +110,20 @@ impl Database {
.map(|model| (model.epoch, model.lamport_timestamp));
if let Some(buffer_max) = buffer_max {
- observed_note_edits::Entity::insert(observed_note_edits::ActiveModel {
+ observed_buffer_edits::Entity::insert(observed_buffer_edits::ActiveModel {
user_id: ActiveValue::Set(user_id),
- channel_id: ActiveValue::Set(channel_id),
+ buffer_id: ActiveValue::Set(buffer.id),
epoch: ActiveValue::Set(buffer_max.0),
lamport_timestamp: ActiveValue::Set(buffer_max.1),
})
.on_conflict(
OnConflict::columns([
- observed_note_edits::Column::UserId,
- observed_note_edits::Column::ChannelId,
+ observed_buffer_edits::Column::UserId,
+ observed_buffer_edits::Column::BufferId,
])
.update_columns([
- observed_note_edits::Column::Epoch,
- observed_note_edits::Column::LamportTimestamp,
+ observed_buffer_edits::Column::Epoch,
+ observed_buffer_edits::Column::LamportTimestamp,
])
.to_owned(),
)
@@ -463,7 +463,7 @@ impl Database {
channel_id: ChannelId,
user: UserId,
operations: &[proto::Operation],
- ) -> Result<Vec<ConnectionId>> {
+ ) -> Result<(Vec<ConnectionId>, Vec<UserId>)> {
self.transaction(move |tx| async move {
self.check_user_is_channel_member(channel_id, user, &*tx)
.await?;
@@ -483,10 +483,23 @@ impl Database {
.filter_map(|op| operation_to_storage(op, &buffer, serialization_version))
.collect::<Vec<_>>();
+ let mut channel_members;
+
if !operations.is_empty() {
// get current channel participants and save the max operation above
- self.save_max_operation_for_collaborators(operations.as_slice(), channel_id, &*tx)
+ self.save_max_operation_for_collaborators(
+ operations.as_slice(),
+ channel_id,
+ buffer.id,
+ &*tx,
+ )
+ .await?;
+
+ channel_members = self.get_channel_members_internal(channel_id, &*tx).await?;
+ let collaborators = self
+ .get_channel_buffer_collaborators_internal(channel_id, &*tx)
.await?;
+ channel_members.retain(|member| !collaborators.contains(member));
buffer_operation::Entity::insert_many(operations)
.on_conflict(
@@ -501,6 +514,8 @@ impl Database {
)
.exec(&*tx)
.await?;
+ } else {
+ channel_members = Vec::new();
}
let mut connections = Vec::new();
@@ -519,7 +534,7 @@ impl Database {
});
}
- Ok(connections)
+ Ok((connections, channel_members))
})
.await
}
@@ -528,6 +543,7 @@ impl Database {
&self,
operations: &[buffer_operation::ActiveModel],
channel_id: ChannelId,
+ buffer_id: BufferId,
tx: &DatabaseTransaction,
) -> Result<()> {
let max_operation = operations
@@ -553,22 +569,22 @@ impl Database {
.get_channel_buffer_collaborators_internal(channel_id, tx)
.await?;
- observed_note_edits::Entity::insert_many(users.iter().map(|id| {
- observed_note_edits::ActiveModel {
+ observed_buffer_edits::Entity::insert_many(users.iter().map(|id| {
+ observed_buffer_edits::ActiveModel {
user_id: ActiveValue::Set(*id),
- channel_id: ActiveValue::Set(channel_id),
+ buffer_id: ActiveValue::Set(buffer_id),
epoch: max_operation.0.clone(),
lamport_timestamp: ActiveValue::Set(*max_operation.1.as_ref()),
}
}))
.on_conflict(
OnConflict::columns([
- observed_note_edits::Column::UserId,
- observed_note_edits::Column::ChannelId,
+ observed_buffer_edits::Column::UserId,
+ observed_buffer_edits::Column::BufferId,
])
.update_columns([
- observed_note_edits::Column::Epoch,
- observed_note_edits::Column::LamportTimestamp,
+ observed_buffer_edits::Column::Epoch,
+ observed_buffer_edits::Column::LamportTimestamp,
])
.to_owned(),
)
@@ -699,54 +715,75 @@ impl Database {
Ok(())
}
- pub async fn has_buffer_changed(&self, user_id: UserId, channel_id: ChannelId) -> Result<bool> {
- self.transaction(|tx| async move {
- let user_max = observed_note_edits::Entity::find()
- .filter(observed_note_edits::Column::UserId.eq(user_id))
- .filter(observed_note_edits::Column::ChannelId.eq(channel_id))
- .one(&*tx)
- .await?
- .map(|model| (model.epoch, model.lamport_timestamp));
+ #[cfg(test)]
+ pub async fn test_has_note_changed(
+ &self,
+ user_id: UserId,
+ channel_id: ChannelId,
+ ) -> Result<bool> {
+ self.transaction(|tx| async move { self.has_note_changed(user_id, channel_id, &*tx).await })
+ .await
+ }
- let channel_buffer = channel::Model {
- id: channel_id,
- ..Default::default()
- }
- .find_related(buffer::Entity)
+ pub async fn has_note_changed(
+ &self,
+ user_id: UserId,
+ channel_id: ChannelId,
+ tx: &DatabaseTransaction,
+ ) -> Result<bool> {
+ let Some(buffer_id) = channel::Model {
+ id: channel_id,
+ ..Default::default()
+ }
+ .find_related(buffer::Entity)
+ .one(&*tx)
+ .await?
+ .map(|buffer| buffer.id) else {
+ return Ok(false);
+ };
+
+ let user_max = observed_buffer_edits::Entity::find()
+ .filter(observed_buffer_edits::Column::UserId.eq(user_id))
+ .filter(observed_buffer_edits::Column::BufferId.eq(buffer_id))
.one(&*tx)
- .await?;
+ .await?
+ .map(|model| (model.epoch, model.lamport_timestamp));
- let Some(channel_buffer) = channel_buffer else {
- return Ok(false);
- };
+ let channel_buffer = channel::Model {
+ id: channel_id,
+ ..Default::default()
+ }
+ .find_related(buffer::Entity)
+ .one(&*tx)
+ .await?;
- let mut channel_max = buffer_operation::Entity::find()
+ let Some(channel_buffer) = channel_buffer else {
+ return Ok(false);
+ };
+
+ let mut channel_max = buffer_operation::Entity::find()
+ .filter(buffer_operation::Column::BufferId.eq(channel_buffer.id))
+ .filter(buffer_operation::Column::Epoch.eq(channel_buffer.epoch))
+ .order_by(buffer_operation::Column::Epoch, Desc)
+ .order_by(buffer_operation::Column::LamportTimestamp, Desc)
+ .one(&*tx)
+ .await?
+ .map(|model| (model.epoch, model.lamport_timestamp));
+
+ // If there are no edits in this epoch
+ if channel_max.is_none() {
+ // check if this user observed the last edit of the previous epoch
+ channel_max = buffer_operation::Entity::find()
.filter(buffer_operation::Column::BufferId.eq(channel_buffer.id))
- .filter(buffer_operation::Column::Epoch.eq(channel_buffer.epoch))
+ .filter(buffer_operation::Column::Epoch.eq(channel_buffer.epoch.saturating_sub(1)))
.order_by(buffer_operation::Column::Epoch, Desc)
.order_by(buffer_operation::Column::LamportTimestamp, Desc)
.one(&*tx)
.await?
.map(|model| (model.epoch, model.lamport_timestamp));
+ }
- // If there are no edits in this epoch
- if channel_max.is_none() {
- // check if this user observed the last edit of the previous epoch
- channel_max = buffer_operation::Entity::find()
- .filter(buffer_operation::Column::BufferId.eq(channel_buffer.id))
- .filter(
- buffer_operation::Column::Epoch.eq(channel_buffer.epoch.saturating_sub(1)),
- )
- .order_by(buffer_operation::Column::Epoch, Desc)
- .order_by(buffer_operation::Column::LamportTimestamp, Desc)
- .one(&*tx)
- .await?
- .map(|model| (model.epoch, model.lamport_timestamp));
- }
-
- Ok(user_max != channel_max)
- })
- .await
+ Ok(user_max != channel_max)
}
}
@@ -391,7 +391,8 @@ impl Database {
.all(&*tx)
.await?;
- self.get_user_channels(channel_memberships, &tx).await
+ self.get_user_channels(user_id, channel_memberships, &tx)
+ .await
})
.await
}
@@ -414,13 +415,15 @@ impl Database {
.all(&*tx)
.await?;
- self.get_user_channels(channel_membership, &tx).await
+ self.get_user_channels(user_id, channel_membership, &tx)
+ .await
})
.await
}
pub async fn get_user_channels(
&self,
+ user_id: UserId,
channel_memberships: Vec<channel_member::Model>,
tx: &DatabaseTransaction,
) -> Result<ChannelsForUser> {
@@ -460,10 +463,18 @@ impl Database {
}
}
+ let mut channels_with_changed_notes = HashSet::default();
+ for channel in graph.channels.iter() {
+ if self.has_note_changed(user_id, channel.id, tx).await? {
+ channels_with_changed_notes.insert(channel.id);
+ }
+ }
+
Ok(ChannelsForUser {
channels: graph,
channel_participants,
channels_with_admin_privileges,
+ channels_with_changed_notes,
})
}
@@ -12,7 +12,7 @@ pub mod contact;
pub mod feature_flag;
pub mod follower;
pub mod language_server;
-pub mod observed_note_edits;
+pub mod observed_buffer_edits;
pub mod project;
pub mod project_collaborator;
pub mod room;
@@ -1,12 +1,12 @@
-use crate::db::{ChannelId, UserId};
+use crate::db::{BufferId, UserId};
use sea_orm::entity::prelude::*;
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
-#[sea_orm(table_name = "observed_channel_note_edits")]
+#[sea_orm(table_name = "observed_buffer_edits")]
pub struct Model {
#[sea_orm(primary_key)]
pub user_id: UserId,
- pub channel_id: ChannelId,
+ pub buffer_id: BufferId,
pub epoch: i32,
pub lamport_timestamp: i32,
}
@@ -14,11 +14,11 @@ pub struct Model {
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(
- belongs_to = "super::channel::Entity",
- from = "Column::ChannelId",
- to = "super::channel::Column::Id"
+ belongs_to = "super::buffer::Entity",
+ from = "Column::BufferId",
+ to = "super::buffer::Column::Id"
)]
- Channel,
+ Buffer,
#[sea_orm(
belongs_to = "super::user::Entity",
from = "Column::UserId",
@@ -27,9 +27,9 @@ pub enum Relation {
User,
}
-impl Related<super::channel::Entity> for Entity {
+impl Related<super::buffer::Entity> for Entity {
fn to() -> RelationDef {
- Relation::Channel.def()
+ Relation::Buffer.def()
}
}
@@ -220,7 +220,7 @@ async fn test_channel_buffers_diffs(db: &Database) {
};
// Zero test: A should not register as changed on an unitialized channel buffer
- assert!(!db.has_buffer_changed(a_id, zed_id).await.unwrap());
+ assert!(!db.test_has_note_changed(a_id, zed_id).await.unwrap());
let _ = db
.join_channel_buffer(zed_id, a_id, connection_id_a)
@@ -228,7 +228,7 @@ async fn test_channel_buffers_diffs(db: &Database) {
.unwrap();
// Zero test: A should register as changed on an empty channel buffer
- assert!(!db.has_buffer_changed(a_id, zed_id).await.unwrap());
+ assert!(!db.test_has_note_changed(a_id, zed_id).await.unwrap());
let mut buffer_a = Buffer::new(0, 0, "".to_string());
let mut operations = Vec::new();
@@ -245,15 +245,16 @@ async fn test_channel_buffers_diffs(db: &Database) {
.unwrap();
// Smoke test: Does B register as changed, A as unchanged?
- assert!(db.has_buffer_changed(b_id, zed_id).await.unwrap());
- assert!(!db.has_buffer_changed(a_id, zed_id).await.unwrap());
+ assert!(db.test_has_note_changed(b_id, zed_id).await.unwrap());
+
+ assert!(!db.test_has_note_changed(a_id, zed_id).await.unwrap());
db.leave_channel_buffer(zed_id, connection_id_a)
.await
.unwrap();
// Snapshotting from leaving the channel buffer should not have a diff
- assert!(!db.has_buffer_changed(a_id, zed_id).await.unwrap());
+ assert!(!db.test_has_note_changed(a_id, zed_id).await.unwrap());
let _ = db
.join_channel_buffer(zed_id, b_id, connection_id_b)
@@ -261,13 +262,13 @@ async fn test_channel_buffers_diffs(db: &Database) {
.unwrap();
// B has opened the channel buffer, so we shouldn't have any diff
- assert!(!db.has_buffer_changed(b_id, zed_id).await.unwrap());
+ assert!(!db.test_has_note_changed(b_id, zed_id).await.unwrap());
db.leave_channel_buffer(zed_id, connection_id_b)
.await
.unwrap();
// Since B just opened and closed the buffer without editing, neither should have a diff
- assert!(!db.has_buffer_changed(a_id, zed_id).await.unwrap());
- assert!(!db.has_buffer_changed(b_id, zed_id).await.unwrap());
+ assert!(!db.test_has_note_changed(a_id, zed_id).await.unwrap());
+ assert!(!db.test_has_note_changed(b_id, zed_id).await.unwrap());
}
@@ -2691,7 +2691,7 @@ async fn update_channel_buffer(
let db = session.db().await;
let channel_id = ChannelId::from_proto(request.channel_id);
- let collaborators = db
+ let (collaborators, non_collaborators) = db
.update_channel_buffer(channel_id, session.user_id, &request.operations)
.await?;
@@ -2704,6 +2704,25 @@ async fn update_channel_buffer(
},
&session.peer,
);
+
+ let pool = &*session.connection_pool().await;
+
+ broadcast(
+ None,
+ non_collaborators
+ .iter()
+ .flat_map(|user_id| pool.user_connection_ids(*user_id)),
+ |peer_id| {
+ session.peer.send(
+ peer_id.into(),
+ proto::UpdateChannels {
+ notes_changed: vec![channel_id.to_proto()],
+ ..Default::default()
+ },
+ )
+ },
+ );
+
Ok(())
}
@@ -2986,6 +3005,12 @@ fn build_initial_channels_update(
});
}
+ update.notes_changed = channels
+ .channels_with_changed_notes
+ .iter()
+ .map(|channel_id| channel_id.to_proto())
+ .collect();
+
update.insert_edge = channels.channels.edges;
for (channel_id, participants) in channels.channel_participants {
@@ -410,10 +410,7 @@ async fn test_channel_buffer_disconnect(
channel_buffer_a.update(cx_a, |buffer, _| {
assert_eq!(
buffer.channel().as_ref(),
- &Channel {
- id: channel_id,
- name: "the-channel".to_string()
- }
+ &channel(channel_id, "the-channel")
);
assert!(!buffer.is_connected());
});
@@ -438,15 +435,20 @@ async fn test_channel_buffer_disconnect(
channel_buffer_b.update(cx_b, |buffer, _| {
assert_eq!(
buffer.channel().as_ref(),
- &Channel {
- id: channel_id,
- name: "the-channel".to_string()
- }
+ &channel(channel_id, "the-channel")
);
assert!(!buffer.is_connected());
});
}
+fn channel(id: u64, name: &'static str) -> Channel {
+ Channel {
+ id,
+ name: name.to_string(),
+ has_changed: false,
+ }
+}
+
#[gpui::test]
async fn test_rejoin_channel_buffer(
deterministic: Arc<Deterministic>,
@@ -627,6 +629,7 @@ async fn test_following_to_channel_notes_without_a_shared_project(
let mut server = TestServer::start(&deterministic).await;
let client_a = server.create_client(cx_a, "user_a").await;
let client_b = server.create_client(cx_b, "user_b").await;
+
let client_c = server.create_client(cx_c, "user_c").await;
cx_a.update(editor::init);
@@ -757,6 +760,50 @@ async fn test_following_to_channel_notes_without_a_shared_project(
});
}
+#[gpui::test]
+async fn test_channel_buffer_changes(
+ deterministic: Arc<Deterministic>,
+ cx_a: &mut TestAppContext,
+ cx_b: &mut TestAppContext,
+) {
+ deterministic.forbid_parking();
+ let mut server = TestServer::start(&deterministic).await;
+ let client_a = server.create_client(cx_a, "user_a").await;
+ let client_b = server.create_client(cx_b, "user_b").await;
+
+ let channel_id = server
+ .make_channel(
+ "the-channel",
+ None,
+ (&client_a, cx_a),
+ &mut [(&client_b, cx_b)],
+ )
+ .await;
+
+ let channel_buffer_a = client_a
+ .channel_store()
+ .update(cx_a, |store, cx| store.open_channel_buffer(channel_id, cx))
+ .await
+ .unwrap();
+
+ channel_buffer_a.update(cx_a, |buffer, cx| {
+ buffer.buffer().update(cx, |buffer, cx| {
+ buffer.edit([(0..0, "1")], None, cx);
+ })
+ });
+ deterministic.run_until_parked();
+
+ let has_buffer_changed = cx_b.read(|cx| {
+ client_b
+ .channel_store()
+ .read(cx)
+ .has_channel_buffer_changed(channel_id)
+ .unwrap()
+ });
+
+ assert!(has_buffer_changed);
+}
+
#[track_caller]
fn assert_collaborators(collaborators: &HashMap<PeerId, Collaborator>, ids: &[Option<UserId>]) {
let mut user_ids = collaborators
@@ -1816,12 +1816,19 @@ impl CollabPanel {
.left(),
)
.with_child(
- Label::new(channel.name.clone(), theme.channel_name.text.clone())
- .contained()
- .with_style(theme.channel_name.container)
- .aligned()
- .left()
- .flex(1., true),
+ Label::new(
+ channel.name.clone(),
+ theme
+ .channel_name
+ .in_state(channel.has_changed)
+ .text
+ .clone(),
+ )
+ .contained()
+ .with_style(theme.channel_name.container)
+ .aligned()
+ .left()
+ .flex(1., true),
)
.with_child(
MouseEventHandler::new::<ChannelCall, _>(ix, cx, move |_, cx| {
@@ -955,6 +955,7 @@ message UpdateChannels {
repeated uint64 remove_channel_invitations = 6;
repeated ChannelParticipants channel_participants = 7;
repeated ChannelPermission channel_permissions = 8;
+ repeated uint64 notes_changed = 9;
}
message ChannelEdge {
@@ -251,7 +251,7 @@ pub struct CollabPanel {
pub leave_call: Interactive<ContainedText>,
pub contact_row: Toggleable<Interactive<ContainerStyle>>,
pub channel_row: Toggleable<Interactive<ContainerStyle>>,
- pub channel_name: ContainedText,
+ pub channel_name: Toggleable<ContainedText>,
pub row_height: f32,
pub project_row: Toggleable<Interactive<ProjectRow>>,
pub tree_branch: Toggleable<Interactive<TreeBranch>>,
@@ -267,10 +267,18 @@ export default function contacts_panel(): any {
}),
channel_row: item_row,
channel_name: {
- ...text(layer, "sans", { size: "sm" }),
- margin: {
- left: CHANNEL_SPACING,
+ active: {
+ ...text(layer, "sans", { size: "sm", weight: "bold" }),
+ margin: {
+ left: CHANNEL_SPACING,
+ },
},
+ inactive: {
+ ...text(layer, "sans", { size: "sm" }),
+ margin: {
+ left: CHANNEL_SPACING,
+ },
+ }
},
list_empty_label_container: {
margin: {