Skip to content

feat(channels): organize and sort the sidebar - #184

Closed
tellaho wants to merge 5 commits into
mainfrom
tho/sidebar-sorting
Closed

tellaho wants to merge 5 commits into
mainfrom
tho/sidebar-sorting

Conversation

@tellaho

@tellaho tellaho commented Sep 23, 2026

Copy link
Copy Markdown
Contributor

Overview

Category: new-feature
User Impact: Users on supported browser/development hosts can star channels, organize them into sections, and independently sort each section by name or recent activity.

Problem: Sidebar organization was read-only, leaving users unable to shape a large channel list around their work or choose which activity rises to the top.

Solution: Add one exclusive Move channel chooser for Starred, saved sections and Create new, plus independent Recent/A–Z section sorting. Changes appear immediately, save quietly, and expose explicit recovery when persistence fails.

This is the combined grouping + Star + sorting replacement, not a sorting-only follow-up.

Changes

  • One row placement: Starred, a saved section, or Channels. Removing Star never restores a hidden previous group. Moves expand the destination and restore focus by channel identity.
  • Create-and-move uses shared controls. Pending saves stay quiet; failures remain outside closed menus with Retry/Dismiss, and newer intent wins.
  • Recent uses session-owned verified activity and purpose-bound background batches of at most 128 channels. Existing live routes are reused; no polling or additional subscriptions, and no roster activity query for all-A–Z.
  • Cold sidebar reveal waits at most 1.5 seconds after roster availability for preferences, metadata, initial unread repair and required recency. Slow dependencies reveal usable rows with an updating notice, without blocking conversation opening or access checks.
  • Custom section icons use the shared emoji/media owners, a subtle loading square and no literal-shortcode fallback.

Rebase integration

Preserve current main’s sidebar rows, activity/session controls, DM Hide, settings/toasts and channel creation (including an empty Channels section). Compose the move menu around the existing row trigger; use current shared Dialog/Input/IconButton APIs and the icon gateway. Activity parsing now calls parseEvents(payload, verify, signal) rather than the obsolete signature. Browser assertions synchronize on actual roster reveal and submenu focus, without sleeps or weaker viewport/keyboard assertions.

Scope and limits

45 changed files; production code is +2,277/−67 lines, excluding tests/docs. The larger test portion covers persistence and lifecycle failure paths.

Host writers verify/decrypt the fresh head, preserve unrelated entries, publish and re-read to confirm. Assignment/create is confirmed before clearing Star; only a complete move advances confirmed placement. This is not atomic multi-device persistence: whole-record writers on different hosts can race, and a partial publication may survive reload. No durable pending outbox, automatic retry or automatic cross-device sync is promised. Native-adapter parity, forum/DM assignment, general section CRUD/reorder and drag-and-drop are out of scope; hosts without the capabilities retain read-only behavior.

Validation

  • Full Vitest suite: 237 files / 2,514 tests passed on the rebased 46fc1b8 working tree with the integration fixes, before the final commit (75.66s wall time). Not presented as a separate full-suite run at the committed head.
  • Chromium + WebKit, five sidebar files (navigation-groups, navigation-sorting, navigation-group-icons, navigation-sidebar, sidebar-unread): 47/48 initially passed. The remaining failure pressed End before Base UI transferred submenu focus. After asserting the observable focus handoff, the entire grouping file passed 8/8 (29.4s); the entire 48-case set was not rerun afterward.
  • At clean 9164b3d: mandatory push hooks passed types, 144 related unit files / 1,806 tests, and all design-system checks. Hooks were not bypassed.
  • At clean 9164b3d: production-build cold/warm channel-opening regression 2/2 passed in Chromium 153.0.8010.12 and WebKit 26.6. Cold click-to-visible upper bounds were 52.9ms / 99ms (include assertion roundtrip); four warm switches were 34.7–40.7ms / 38–52ms, below the existing 100ms budget, without a head read.
pnpm exec playwright test --config tests/browser/playwright.config.mjs   channel-opening.spec.mjs --grep 'cold opening bypasses'   --project chromium-measurements --project webkit-measurements   --no-deps --workers=1

Measurements are local macOS arm64, Node 24.18.0, 1440×950 viewport, production frontend/broker with modeled upstream WS/HTTP policy—not native, real-relay or hosted-CI latency. Evidence records the clean head. Native and live multi-device verification remain deferred; hosted PR checks are separate.

Browser coverage adds 7 cases / 14 engine executions across the three new files, removes none, and updates one existing unread case. Browser-only reasons: real nested-menu/dialog focus, row relocation/scroll geometry, frame-by-frame remount presentation, image layout and paint readiness. Persistence/schema/concurrency matrices stay in unit and broker tests. Integration fail-then-pass evidence: the held-preference unread test originally scrolled an empty loading view; it now waits for usable rows and still requires the exact 1,800px position after preferences settle. The submenu race likewise failed before the focus barrier and passed in both engines afterward. These are synchronization repairs, not claims that all flakes are eliminated.

File changes

dev/relay-broker.mjs
Expose narrow, relay-scoped preference writers and bounded background activity reads through the existing host admission lanes.

dev/sidebar-create-section.test.mjs
Cover create-and-assign persistence, retry identity, invalid heads and preserved unrelated fields.

dev/sidebar-group-moves.test.mjs
Exercise ordered assignment/Star writes, partial failure and cancellation across the host boundary.

dev/sidebar-preference-writes.test.mjs
Cover fresh-head assignment writes, confirmation, validation and same-host serialization.

dev/sidebar-preferences.d.mts
Describe the narrow assignment/create intent and host mutation results.

dev/sidebar-preferences.mjs
Decode the sort coordinate and implement verified, encrypted assignment/create writes with read-back confirmation.

dev/sidebar-sort.mjs
Persist independent section sort choices while preserving unrelated raw preferences.

dev/sidebar-sort.test.mjs
Cover sort validation, no-op writes, preservation and failed confirmation.

dev/sidebar-stars.mjs
Persist explicit Star/Unstar choices against fresh verified heads, retaining tombstones.

dev/sidebar-stars.test.mjs
Cover star preservation, malformed records, capacity limits and confirmation failures.

docs/channels.md
Document ownership, interaction semantics, startup behavior and the limits of whole-record/two-record persistence.

src/bundled/channels/ChannelSidebarItem.tsx
Allow the move menu to compose around the existing activity trigger without replacing current row controls.

src/bundled/channels/Channels.module.css
Fit the shared section action button into the compact sidebar header.

src/bundled/channels/ChannelsPage.tsx
Wire exclusive moves, create-and-move, section sorting, retained failure actions and bounded sidebar reveal into the current page.

src/bundled/channels/CreateSidebarSection.module.css
Keep the dialog description on one line, truncating only the channel name when needed.

src/bundled/channels/CreateSidebarSection.tsx
Use shared dialog/form controls for naming a section and moving the selected channel, with explicit focus ownership.

src/bundled/channels/SidebarGroupIcon.module.css
Keep decorative custom icons bounded and show a subtle semantic loading fill.

src/bundled/channels/SidebarGroupIcon.test.tsx
Cover catalog/image loading, failures, blocked media and source replacement.

src/bundled/channels/SidebarGroupIcon.tsx
Resolve custom icons through the shared emoji catalog/media path; omit shortcode text and failed images.

src/bundled/channels/sidebar-sections.test.ts
Cover exclusive placement and per-section ordering while retaining main’s empty Channels creation section.

src/bundled/channels/sidebar-sections.ts
Apply independent Recent/A–Z ordering with deterministic ties without changing roster, hidden-DM or placement rules.

src/bundled/channels/useSidebarPreferences.ts
Expose session-owned write capabilities and commands to the page.

src/bundled/channels/useSidebarStartup.test.tsx
Cover bounded cold reveal, failures, warm remounts and session replacement.

src/bundled/channels/useSidebarStartup.ts
Coordinate a presentation-only reveal latch; conversation opening and access remain independent.

src/features/relay/channel-activity-session.test.ts
Verify activity demand, session lifecycle fencing and reuse of verified live events.

src/features/relay/channel-activity.test.ts
Cover batches, authoritative emptiness, last-good retention and stale/live result ordering.

src/features/relay/channel-activity.ts
Own the bounded verified recency projection and its cancellation lifecycle.

src/features/relay/contracts.ts
Expose optional activity timestamps and activity readiness in channel snapshots.

src/features/relay/live.test.ts
Guard inclusion of forum activity in the existing channel route.

src/features/relay/live.ts
Reuse the live channel route for supported forum activity kinds instead of adding subscriptions.

src/features/relay/session.ts
Compose preference writers and recency demand with the existing session lifetime and access boundaries.

src/features/relay/sidebar-moves-store.test.ts
Cover optimistic moves, partial failures, newer intent precedence and explicit retry.

src/features/relay/sidebar-preferences-store.test.ts
Extend coverage for ordered commands, refresh fencing and retained confirmed placement.

src/features/relay/sidebar-preferences-store.ts
Serialize local writes and project optimistic moves/sorts over confirmed state with session-retained failures.

src/features/relay/sidebar-preferences.test.ts
Cover sort-coordinate projection and validation alongside existing group/star formats.

src/features/relay/sidebar-preferences.ts
Define narrow write contracts and project the encrypted sort coordinate.

src/features/relay/sidebar-sorting-store.test.ts
Cover independent optimistic sort choices, rollback, retry and cancellation.

src/features/relay/transport.ts
Connect advertised host write/activity capabilities and verify activity with the current verifier API.

src/shared/design-system/icons/index.ts
Export the Phosphor sort icon through the shared icon gateway.

tests/browser/fixture.mjs
Provide deterministic preference/activity/media gates and minimal fixtures for the real application journeys.

tests/browser/navigation-group-icons.spec.mjs
Exercise actual custom-image layout and dialog text geometry in both browser engines.

tests/browser/navigation-groups.spec.mjs
Exercise menu/dialog keyboard ownership, optimistic relocation, failure recovery, reload and frame-by-frame warm return stability.

tests/browser/navigation-sorting.spec.mjs
Exercise independent sort persistence and cold reveal without blocking the conversation.

tests/browser/policy-relay.mjs
Model the new purpose-bound activity request within the existing upstream policy fixture.

tests/browser/sidebar-unread.spec.mjs
Wait for usable rows before scrolling during held preference reads, preserving the exact unread/viewport guarantees.

Reproduction Steps

  1. Open Messages on the browser/development host with the broker restarted for the expanded activity filter. Use an account with stream channels and optionally saved sections/custom section icons.
  2. Right-click a stream row, or focus it and press Shift+F10. Open Move channel, choose Starred or a saved section, and confirm there is exactly one row, the destination expands and focus follows the channel.
  3. Choose the checked destination again, or Remove from …. Confirm the row returns to Channels rather than a remembered group. Move directly from Starred into another section as well.
  4. Choose Create new…, name a section, then Create and move. Confirm the menu closes before the dialog opens, Cancel returns focus, and a confirmed create/move survives reload.
  5. Open a section’s More actions → Sort menu and choose Recent. Change a different section independently; return the first to A–Z. Confirm updates are immediate and persisted choices survive reload.
  6. With the browser fixtures holding/failing preference publication, verify immediate relocation, quiet pending state, rollback and visible Retry/Dismiss. A later move/sort must survive an older failure.
  7. Hold cold preference/activity reads: the conversation should remain usable, while the sidebar reveals either settled placement or usable rows with an updating notice after its bounded wait. Go Home and back to confirm retained scroll/placement. Verify custom icon loading resolves to the image or blank, never shortcode text.

Screenshots / Demos

Actual built Buzz application, dark theme with the fixture accent and synthetic channel/message data; these are cropped application screenshots, not mockups. Captured during the rebase-validation runs. Feature-state images below; a separate pre-change baseline was not captured.

Exclusive channel placement — keyboard-accessible Starred, saved section, create and removal destinations.

Move channel submenu with Starred, Work, Create new and removal

Create and move — shared dialog controls and the single-line channel description.

Create new section dialog with a section name and Create and move action

Independent section sorting — Recent/A–Z in the shared nested menu.

Sidebar section Sort submenu with Recent and A–Z choices

Carl added 5 commits September 23, 2026 15:29
Expose group assignment and Star/Unstar through confirmed host-owned preference writes. Preserve unrelated encrypted data, fence stale reads and cancelled sessions, and restore keyboard focus after exclusive row relocation.

Signed-off-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz>
Offer Starred and saved groups in one destination chooser. Confirm the destination assignment before clearing Star so removal returns to Channels without restoring a hidden group after reload. Retain confirmed local placement through partial failures, fence overlapping refreshes, and cover ordered writes, cancellation, retry and browser focus.

Signed-off-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz>
Keep independent section choices in confirmed encrypted preferences with optimistic rollback. Project bounded verified activity in the session, reuse existing live routes, and preserve current grouping behavior as the explicit stack base.

Signed-off-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz>
Add nested channel destinations and create-and-move with immediate placement, ordered preference writes, rollback, and explicit retry. Keep saving quiet and retain newer move and sort choices after older failures.

Bound cold sidebar reveal while retaining warm presentation. Resolve custom group icons through the shared catalog with a subtle neutral-4 loading square and no shortcode fallback. Cover persistence, cancellation, menu focus, startup, and image lifecycles.

Signed-off-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz>
Compose grouping and sorting with main's sidebar rows, shared controls and verifier API. Keep empty Channels creation available, and synchronize browser assertions with roster reveal and submenu focus without weakening viewport or keyboard guarantees.

Signed-off-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@buzz.block.builderlab.xyz>
@tellaho

tellaho commented Sep 24, 2026

Copy link
Copy Markdown
Contributor Author

Closing as superseded by #206 (sorting) and #207 (grouping/Star). This combined PR is no longer the landing prerequisite for #91 or #92. Retaining the branch and history for reference.

Posted by Carl (AI agent), at Taylor’s explicit request.

@tellaho tellaho closed this Sep 24, 2026
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