Skip to content

feat(desktop): add sidebar categories and manual channel ordering - #2947

Open
NYTEMODEONLY wants to merge 8 commits into
block:mainfrom
NYTEMODEONLY:fizz/sidebar-categories-manual-order
Open

feat(desktop): add sidebar categories and manual channel ordering#2947
NYTEMODEONLY wants to merge 8 commits into
block:mainfrom
NYTEMODEONLY:fizz/sidebar-categories-manual-order

Conversation

@NYTEMODEONLY

@NYTEMODEONLY NYTEMODEONLY commented Jul 26, 2026

Copy link
Copy Markdown

Summary

  • promote custom channel sections into first-class sidebar categories
  • make Manual the default for stream groups that have no saved sort preference, while preserving explicit A-Z and Recent choices
  • keep custom categories and the built-in uncategorized Channels block in one persisted sortable lane
  • support pointer, touch, keyboard, and Move up / Move down movement across the Channels boundary
  • persist exact channel order in a separate encrypted channel-manual-order NIP-78 document
  • preserve pending local category edits during startup/reconnect hydration
  • keep draggable sidebar rows paintable in WKWebView
  • right-align category and channel drag grips so labels remain flush-left; keep grips hidden at rest and reveal them on hover, keyboard focus, or active drag, with a persistent affordance on non-hover devices
  • restore focus to the live Channels actions trigger after closing the category dialog

Why

Buzz already supported encrypted custom sections and section reordering, but category creation was buried in a channel context menu and exact channel order was not persisted. This extends the existing model instead of adding a competing category subsystem, while making the sidebar behave like a coherent manually ordered lane.

Behavior

  • Create categories from the Channels actions menu.
  • Rename, reorder, and safely delete categories.
  • Deleting a category returns its channels to the uncategorized Channels group.
  • Empty categories remain valid pointer and keyboard drop targets.
  • Categories can move above or below the built-in Channels block.
  • Starred, Forums, and Direct Messages remain fixed system lanes.
  • Manual channel ordering supports same-category and cross-category insertion.
  • Keyboard drag supports cancellation, truthful position announcements, and persistence after reload.
  • Category labels and carets remain the collapse target; a dedicated right-side grip owns dragging.
  • Drag grips stay mounted and accessible but are visually hidden at rest.
  • Hovering a draggable row reveals only that row's grip; keyboard focus and active drag keep it visible.
  • Non-hover/touch devices retain a persistent move affordance.

Persistence and compatibility

  • Category membership remains in the existing encrypted channel-sections v1 document.
  • The unified lane stores an optional normalized channelsBlockIndex; missing or malformed values preserve the legacy custom-categories-then-Channels layout.
  • Index-only changes participate in NIP-78 publication and deduplication.
  • Deleting a category adjusts the unified lane so the Channels block does not move accidentally; creating a category appends it without shifting the existing Channels position.
  • Exact channel order uses a separate encrypted channel-manual-order document.
  • Unset stream groups use an implicit deterministic visible-ID fallback; no mount-time seed or publish occurs. The order becomes explicit on the first reorder or explicit Manual selection.
  • Stores are scoped by identity and normalized relay.
  • Explicit pending local category edits stay authoritative while startup/reconnect hydration is in flight. An idle cold-start remote snapshot still wins when no local edit is pending.

Regression fixes retained

Startup sync race

Creating a category before the initial NIP-78 fetch completed could show it briefly, then silently remove it. The pending-local guard prevents an older fetched snapshot from overwriting the edit or cancelling its debounced publish while retaining normal timestamp/event-ID conflict handling for idle remote updates.

Invisible rows during drag

A draggable row in WKWebView could remain in the DOM and accessibility tree but stop painting when content-visibility: auto was combined with dnd-kit transforms. Sortable channel rows now remain paintable while non-draggable rows retain the optimization.

Focus restoration

Current Block main intentionally skips generic menu focus restoration for New Message so its recipient input keeps focus. Category creation now preserves the live Channels actions trigger separately, allowing Escape/close to restore focus after the sortable lane remounts without regressing New Message.

Validation

Refreshed upstream branch at 00a615f54f0797c11731ddefe593eeeb1387a560, rebased onto Block main at 9752b816a95e88751b9c7e4d3343fe3210de5d43:

  • pnpm --dir desktop typecheck: passed
  • pnpm --dir desktop check: passed across 1,703 files and all policy guards
  • full desktop unit suite: 3,815 / 3,815 passed
  • E2E production build: passed
  • channel-categories-manual-order.spec.ts: 8 / 8 passed with retries disabled
  • targeted custom-section drag under content virtualization: 1 / 1 passed with retries disabled
  • no lockfile change

The focused browser suite covers category create/duplicate rejection, rename/delete and focus restoration, manual channel reorder plus reload, keyboard channel movement, empty-category insertion, Move up/down across Channels, pointer category-block movement, and keyboard category-block movement plus reload.

Scope

Desktop only. This branch contains no mobile implementation, NYTEMODE branding, updater/distribution changes, installer changes, or identity/profile changes.

Co-authored-by: nytemodeonly <contact@nytemode.com>
Signed-off-by: nytemodeonly <contact@nytemode.com>
Co-authored-by: nytemodeonly <contact@nytemode.com>
Signed-off-by: nytemodeonly <contact@nytemode.com>
Co-authored-by: nytemodeonly <contact@nytemode.com>
Signed-off-by: nytemodeonly <contact@nytemode.com>
Co-authored-by: nytemodeonly <contact@nytemode.com>
Signed-off-by: nytemodeonly <contact@nytemode.com>
…l default

Make stream groups default to implicit Manual (unset vs explicit A–Z),
put categories and the Channels block in one unified reorderable lane
with optional channelsBlockIndex NIP-78 field, quiet block grips, and
keyboard/menu/pointer paths. Cover delete-index, first-reorder Manual
enable, non-integer index rejection, and E2E keyboard block move.

Co-authored-by: nytemodeonly <contact@nytemode.com>
Signed-off-by: nytemodeonly <contact@nytemode.com>
Re-query the live section-actions trigger on dialog close so Escape after
duplicate-name rejection still focuses the trigger even if the movable
Channels shell remounted under the unified block order. Open create via
the next frame after pinning trigger focus so Radix's focus stack captures
the correct node.

Co-authored-by: nytemodeonly <contact@nytemode.com>
Signed-off-by: nytemodeonly <contact@nytemode.com>
Signed-off-by: nytemodeonly <contact@nytemode.com>
Signed-off-by: nytemodeonly <contact@nytemode.com>
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.

1 participant