# Community sidebar When a community is opened from the sidebar, the sidebar's content becomes the community's own channel/member browser. Going back restores the normal tabs. ``` +------------------+--------------------------------+ | [<-] community | | | banner | | | v Channels 3 | messages | | # general | | | # random | | | v Admins 1 | | | @owner | | | v Members 2 +--------------------------------+ | @alice | [ composer ] | | @bob | | +------------------+--------------------------------+ ``` ## Current state * `community_ui::CommunityPanel` renders its own 220px left column (channels + members) next to the timeline. * `Sidebar` renders the Recents/Chats/Communities tabs, and `Sidebar::open_community` records the community as recent and asks `CommunityRegistry` to emit `CommunityEvent::Open`, which `Workspace` turns into a center dock panel. * `Community` already folds channels (`state().channels`), members, the control roster (`control().roles`), the community icon, and the banner (`metadata.banner`). ## Plan ### 1. Backend: community crate `crates/community/src/community.rs` * Add `CommunityEvent::Close(CommunityId)` and `CommunityEvent::Channel(CommunityId, ChannelId)`. * Add `active: Option` to `Community`, with: * `pub fn active_channel(&self) -> Option` — the selected channel, defaulting to the first one. * `pub fn set_active_channel(&mut self, ChannelId, &mut Context)` — stores it and emits `Channel`. * Add `banner: Option` resolved from `metadata.banner` the same way the icon already is, exposed as `pub fn banner(&self) -> Option`. `crates/community/src/lib.rs` * Add `CommunityRegistry::emit_close(&mut self, CommunityId, &mut Window, &mut Context)`, mirroring `emit_community`, emitting `Close`. `crates/community/src/sync.rs` * Rename `resolve_icon` to `resolve_image` (it takes any `ImageRef`). ### 2. `community_ui`: panel becomes timeline-only * Drop the internal channel/member column and its helpers. * Resolve the shown channel from `Community::active_channel` on every load and reload, and subscribe to `CommunityEvent::Channel` to follow sidebar clicks (deferred, because the community is being updated when it emits). ### 3. `workspace`: sidebar renders the community `crates/workspace/src/sidebar/mod.rs` * New state: `community: Option>` and three section flags. * `open_community` also sets `self.community`; new `close_community` clears it and asks the registry to emit `Close`. * `render` picks between the existing tabs and `render_community`. * `render_user` keeps the account button as usual and adds a back button on the right when a community is open. * `render_community` pins the optional banner image and scrolls everything below it: collapsible Channels, Admins (members where `control().roles.is_staff`), and Members sections. Every row is a `TreeRow` — the component the sidebar's own lists use — so height, padding, typography, hover, and selection match, and each row is wrapped `flex_shrink_0` so the list scrolls instead of squashing. * Observe the registry so member/roster changes re-render the lists. `crates/workspace/src/lib.rs` * Remember the opened `CommunityPanel` so `CommunityEvent::Close` can close it: activate it, focus the group, then dispatch `ClosePanel`. ### 4. `community`: a list refresh keeps entities `CommunityRegistry::track` used to rebuild every community, so an open panel and a browsing sidebar went stale on any list signal — and `sync::load` merges join material into a loaded `CommunityState`, so the state really can change. `track` now keeps the entity for an id it still tracks and hands it the new state through `Community::adopt`, which also cancels a fold in flight that would otherwise write the pre-adoption state back. Observers move to a `HashMap` so the survivors keep theirs, and the ids the list dropped lose their observer and their synced key. ## Out of scope Moderation, invites, community management, channel icons, unread badges, member search, and persisting the selected channel across restarts.