feat(desktop): add user-defined channel sections to sidebar#789
Merged
Conversation
c99021f to
bfbdbad
Compare
Collaborator
|
@wpfleger96 will this work across devices as well? Like will the mobile app have access to these same groups to display? |
Collaborator
Author
|
@wesbillman no, as-is the PR will save channel sections to I'm gonna add Nostr event syncing so that sections will be shared between devices in a stacked PR so this one doesn't get too chonky |
Collaborator
Author
Collaborator
Author
881b951 to
f081fbf
Compare
Collaborator
Author
Collaborator
Author
All stream channels appear in a single flat list which becomes hard to navigate as channel count grows. Channel sections let users create custom named groups (e.g., "Starred", "Work") to organize channels in the sidebar, similar to Slack's sidebar sections. Sections are purely cosmetic and client-side -- stored in localStorage keyed by pubkey, with no backend changes. Users manage sections via right-click context menus: create, rename, delete, reorder (move up/down), and assign/unassign channels. Each section is independently collapsible. Channels not assigned to a section remain in the default "Channels" group.
Export storageKey from channelSectionsStorage to eliminate duplicate key definitions that could diverge during refactors. Fix createSection to return null on write failure instead of a phantom section, preventing stale channel assignments. Clean up collapsedSections state when a section is deleted. Freeze DEFAULT_STORE to guard against accidental mutation.
Extract shared parseChannelSectionPayload validator and stripOrphanedAssignments helper to channelSectionsStorage.ts. Deduplicate Create/Rename dialogs into shared SectionNameDialog base. Add per-section mark-all-read button to CustomChannelSection.
markChannelRead bailed when lastMessageAt was null because it lacked the latestByChannelRef fallback that markChannelUnread already had. This made the per-section "mark all as read" button and the individual channel "mark as read" context menu action silently do nothing. Also clear forcedContexts immediately in markContextRead so the forced-unread flag from "mark unread" is removed when the user re-opens the channel.
…ssignment Channels can be dragged between sections or back to the ungrouped list, and sections can be dragged to reorder. Uses @dnd-kit pointer events (no conflict with existing file drop prevention). DnD primitives extracted to SidebarDnd.tsx; existing context-menu and move up/down keyboard alternatives remain as accessibility fallbacks.
closestCenter collision detection required dragging to the vertical midpoint of a tall channel list before the drop target activated. Switch to pointerWithin so the drop fires as soon as the pointer enters the droppable rect, and move the droppable wrappers up to cover the entire section (header + content) instead of just the channel list.
f081fbf to
e9bca97
Compare
…s merge Main merged #809 (⌘⇧N shortcut) which added 23 lines to AppSidebar.tsx for the controlled create-channel dialog. Bump override from 810 to 830.
pointerWithin was resolving over.id to the nested DroppableSectionBody
("section-drop:X") instead of the SortableSectionShell ("X"), causing
sectionIds.indexOf to return -1 and silently skip the reorder. Read
sectionId from over.data.current instead, which both registrations set.
Collaborator
Author
wesbillman
approved these changes
Jun 1, 2026
tlongwell-block
pushed a commit
that referenced
this pull request
Jun 1, 2026
* origin/main: chore(release): release version 0.3.6 (#811) feat(mobile): add channel sections with relay sync (#800) feat(desktop): sync channel sections across devices via Nostr (#792) feat(media): support arbitrary file types with download cards (#810) feat(desktop): add user-defined channel sections to sidebar (#789) Signed-off-by: tlongwell-block <109685178+tlongwell-block@users.noreply.github.com> # Conflicts: # desktop/src-tauri/src/commands/mod.rs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.








Adds Slack-style sidebar sections so users can organize stream channels into custom named groups — the flat channel list gets unwieldy as the channel count grows.
Users create sections via right-click on any stream channel ("Move to section → New section…") and manage them via right-click on the section header (rename, delete). Sections and channels are both drag-and-droppable: drag a channel onto a section to assign it, drag section headers to reorder them. The context menu retains move up/down and "Move to section" as keyboard-accessible alternatives. Each section collapses independently. Channels not assigned to a section remain in the default "Channels" group below all custom sections. Each section header includes a "mark all as read" button.
channelSectionsStorage.ts— pure localStorage read/write keyed bysprout-channel-sections.v1:{pubkey}withparseChannelSectionPayloadtype-guard validation,stripOrphanedAssignmentscleanup, and a safe default; sections and channel assignments stored separately for O(1) lookupsuseChannelSections.ts— hook owning all CRUD mutations (create, rename, delete, assign/unassign,reorderSections) with cross-tab sync viaStorageEventChannelSectionDialogs.tsx—SectionNameDialogbase with thinCreateSectionDialog/RenameSectionDialogwrappers using existingDialog/InputprimitivesCustomChannelSection.tsx—ChannelGroupSection,SectionHeaderActions,CustomChannelSection, context menu items, and "Move to section" submenu;AppSidebar.tsxupdated to drive section state and render custom sections above the default groupSidebarDnd.tsx— all@dnd-kitprimitives extracted here:SidebarDndContext(wrapsDndContext+SortableContext+DragOverlay),SortableSectionShell,DraggableChannelRow,DroppableSectionBody,DroppableUngroupedBody,DragOverlayChannel,DragOverlaySection; usespointerWithincollision detection to align drop zones with visual section boundarieschannelSectionsHelpers.ts— pureswapSectionOrderfunction extracted for testabilityuseUnreadChannels.tswheremarkChannelReadsilently failed whenchannel.lastMessageAtwas null (missinglatestByChannelReffallback thatmarkChannelUnreadalready had), and clearsforcedContextsimmediately inreadStateManager.ts:markContextReadto prevent a 5s async window where relay merges are blockedStacked: cross-device sync added in #792