Skip to content

feat(desktop): add user-defined channel sections to sidebar#789

Merged
wpfleger96 merged 8 commits into
mainfrom
worktree-wpfleger+channel-sections
Jun 1, 2026
Merged

feat(desktop): add user-defined channel sections to sidebar#789
wpfleger96 merged 8 commits into
mainfrom
worktree-wpfleger+channel-sections

Conversation

@wpfleger96

@wpfleger96 wpfleger96 commented May 29, 2026

Copy link
Copy Markdown
Collaborator

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 by sprout-channel-sections.v1:{pubkey} with parseChannelSectionPayload type-guard validation, stripOrphanedAssignments cleanup, and a safe default; sections and channel assignments stored separately for O(1) lookups
  • useChannelSections.ts — hook owning all CRUD mutations (create, rename, delete, assign/unassign, reorderSections) with cross-tab sync via StorageEvent
  • ChannelSectionDialogs.tsxSectionNameDialog base with thin CreateSectionDialog/RenameSectionDialog wrappers using existing Dialog/Input primitives
  • CustomChannelSection.tsxChannelGroupSection, SectionHeaderActions, CustomChannelSection, context menu items, and "Move to section" submenu; AppSidebar.tsx updated to drive section state and render custom sections above the default group
  • SidebarDnd.tsx — all @dnd-kit primitives extracted here: SidebarDndContext (wraps DndContext + SortableContext + DragOverlay), SortableSectionShell, DraggableChannelRow, DroppableSectionBody, DroppableUngroupedBody, DragOverlayChannel, DragOverlaySection; uses pointerWithin collision detection to align drop zones with visual section boundaries
  • channelSectionsHelpers.ts — pure swapSectionOrder function extracted for testability
  • Fixes a pre-existing bug in useUnreadChannels.ts where markChannelRead silently failed when channel.lastMessageAt was null (missing latestByChannelRef fallback that markChannelUnread already had), and clears forcedContexts immediately in readStateManager.ts:markContextRead to prevent a 5s async window where relay merges are blocked

Stacked: cross-device sync added in #792

@wpfleger96 wpfleger96 force-pushed the worktree-wpfleger+channel-sections branch from c99021f to bfbdbad Compare May 29, 2026 16:19
@wesbillman

Copy link
Copy Markdown
Collaborator

@wpfleger96 will this work across devices as well? Like will the mobile app have access to these same groups to display?

@wpfleger96

Copy link
Copy Markdown
Collaborator Author

@wesbillman no, as-is the PR will save channel sections to localStorage on each device

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

@wpfleger96

Copy link
Copy Markdown
Collaborator Author

Channel section creation:

  1. image
  2. image
  3. image

@wpfleger96

Copy link
Copy Markdown
Collaborator Author

multiple sections:
image

section context menu:
image

@wpfleger96

Copy link
Copy Markdown
Collaborator Author

Click-and-drag channels in/out of sections:

sprout_click_and_drag_channel_section

@wpfleger96

Copy link
Copy Markdown
Collaborator Author

deleting a section returns its channels back to default Channels

image

@wpfleger96 wpfleger96 marked this pull request as ready for review May 29, 2026 22:47
@wpfleger96 wpfleger96 requested a review from a team as a code owner May 29, 2026 22:47
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.
@wpfleger96 wpfleger96 force-pushed the worktree-wpfleger+channel-sections branch from f081fbf to e9bca97 Compare June 1, 2026 16:21
…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.
@wpfleger96

Copy link
Copy Markdown
Collaborator Author

Click-and-drag section order
sprout_click_drag_sections

@wpfleger96 wpfleger96 enabled auto-merge (squash) June 1, 2026 18:59
@wpfleger96 wpfleger96 merged commit 247ac52 into main Jun 1, 2026
15 checks passed
@wpfleger96 wpfleger96 deleted the worktree-wpfleger+channel-sections branch June 1, 2026 19:42
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants