Skip to content

feat(mobile): sync per-group channel sorting - #4231

Merged
brow merged 9 commits into
mainfrom
tho/mobile-channel-sort-parity
Aug 4, 2026
Merged

feat(mobile): sync per-group channel sorting#4231
brow merged 9 commits into
mainfrom
tho/mobile-channel-sort-parity

Conversation

@tellaho

@tellaho tellaho commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Category: improvement
User Impact: Mobile users can sort each channel group by recent activity or A–Z, with their choices synchronized with desktop.
Problem: Desktop supports persistent per-group channel sorting, but mobile shows the same groups without equivalent controls or shared preferences. The earlier mobile attempt coupled sorting to unsafe dirty-state behavior that could overwrite newer cross-client changes.
Solution: Add mobile sorting controls and encrypted NIP-78 synchronization using the existing desktop channel-sort contract, while retaining ordinary whole-blob last-write-wins behavior. Local state is scoped by identity and normalized relay, startup closes fetch/subscription gaps, and both clients use the same deterministic ordering rules.

File changes

desktop/src/features/sidebar/lib/channelSortPreference.test.mjs
Updates ordering coverage for the deterministic, cross-client A–Z comparison rule.

desktop/src/features/sidebar/lib/channelSortPreference.ts
Aligns desktop channel-name collation with mobile so synchronized preferences produce the same visible order.

mobile/lib/features/channels/channel_sort/channel_sort_manager.dart
Adds encrypted relay synchronization with safe startup gap handling, clock checks, and ordinary last-write-wins conflicts.

mobile/lib/features/channels/channel_sort/channel_sort_provider.dart
Scopes sort state to the active identity and community lifecycle.

mobile/lib/features/channels/channel_sort/channel_sort_storage.dart
Defines the desktop-compatible payload, relay-scoped cache and migration, cleanup, and shared ordering behavior.

mobile/lib/features/channels/channels_page.dart
Connects sort state to the channel page.

mobile/lib/features/channels/channels_page/body.dart
Applies each selected order to Starred, custom groups, Channels, and DMs.

mobile/lib/features/channels/channels_page/sections.dart
Adds checked Recent and A–Z actions using the existing anchored-popover UI.

mobile/test/features/channels/channel_sort/channel_sort_manager_test.dart
Covers payload adoption, encrypted publication, conflicts, timestamps, retries, and cleanup.

mobile/test/features/channels/channel_sort/channel_sort_storage_test.dart
Covers parsing, relay isolation, migration, cleanup, and ordering modes.

mobile/test/features/channels/channels_page_test.dart
Verifies the group controls expose both choices.

Reproduction steps

  1. Open the mobile channel list with populated built-in and custom groups.
  2. Open a group menu and choose Sort: Recent; confirm active channels move to the top.
  3. Choose Sort: A–Z; confirm deterministic alphabetical ordering returns.
  4. Repeat for Starred, a custom group, Channels, and DMs.
  5. Open desktop with the same identity and community and confirm each synchronized preference.
  6. Switch communities and confirm cached preferences do not bleed across relays.

Screenshots

Approved live custom-section flow with research kept offscreen.

Recent selected A–Z result A–Z selected
live custom section with Recent selected live custom section sorted A–Z live custom section with A–Z selected

Validation

  • Mobile flutter analyze — clean
  • Focused mobile sort and channel-page suites — 37/37 passed
  • Desktop full suite — 3906/3906 passed
  • Mobile full suite — 1034 passed, 1 skipped, 1 unrelated baseline failure reproduced at ac4fa13b8

@tellaho

tellaho commented Aug 2, 2026

Copy link
Copy Markdown
Contributor Author

@codex

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ce19f0d0bd

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread mobile/lib/features/channels/channel_sort/channel_sort_manager.dart Outdated
Comment thread mobile/lib/features/channels/channel_sort/channel_sort_manager.dart Outdated
@tellaho
tellaho marked this pull request as ready for review August 2, 2026 03:37
@tellaho
tellaho requested a review from a team as a code owner August 2, 2026 03:37
@tellaho
tellaho force-pushed the tho/mobile-channel-sort-parity branch from a05e767 to ae2186c Compare August 3, 2026 16:29
npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w and others added 5 commits August 3, 2026 09:29
Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
@tellaho
tellaho force-pushed the tho/mobile-channel-sort-parity branch from ae2186c to 91b4058 Compare August 3, 2026 16:30
npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w and others added 2 commits August 3, 2026 11:03
Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
@brow

brow commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Thanks for the quick turnaround. The two issues from the last round (an older relay blob erasing a local offline edit, and pending edits dropped on provider rebuild) are both genuinely fixed, and the durable pending-flag recovery is a nicer mechanism than a dispose-time flush since it survives process death. The new test in channel_sort_manager_test.dart is a real detector for it.

One new issue in the fix itself is blocking: a silent unsynced state with a positive-feedback loop. setSortModeFor stamps max(currentUnixSeconds(), _syncState.updatedAt + 1) and persists that stamp, while _publish silently returns (no retry, no log) when the derived created_at exceeds now + 300. The stamp is a monotonic counter that only a successful publish resets. The realistic trigger is clock skew: a device fast by more than 300 seconds wedges after a single edit, and then both directions fail at once: mobile never publishes, and the pending gate rejects correctly-stamped edits from other clients until wall clock catches up. Editing while wedged extends the wedge, and it persists across restarts. Note the local 300s guard is also stricter than the relay's own 900s window.

On the fix: we tried the obvious change (stamp plain currentUnixSeconds(), drop the +1) and it makes a subtler case worse. A remote blob arriving in the same wall-clock second as a pending local edit falls through the gate's strict <, and without the +1 the local edit is erased on every edit rather than only the first. The +1 is accidentally load-bearing there, and the committed suite is green with or without it. So a complete fix needs two halves:

  1. Stop _publish advancing the sync cursor off its own publication (desktop already works this way); that is what inflates the counter.
  2. Keep a strictly-greater guard for the pending case (e.g. <= on the gate, or track pending identity separately from its timestamp), and clamp/reschedule or at least log on the drift return instead of dropping the edit silently. Some existing assertions will go red with this change because they pin the accidental protection; worth looking at which ones rather than treating the redness as disqualifying.

npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w and others added 2 commits August 3, 2026 17:06
Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
@brow
brow merged commit b42b093 into main Aug 4, 2026
27 checks passed
@brow
brow deleted the tho/mobile-channel-sort-parity branch August 4, 2026 01:02
@brow

brow commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Thanks for the two follow-up commits, a66df858d and 9a643130b. The separated pendingUpdatedAt plus the volatile publish cursor closes the wedge and the cross-client clobber cleanly, and the three new regression tests pin the behavior that mattered most. Merged as b42b09361.

A few non-blocking observations from the review, recorded for whenever this area is next touched. None of these is a defect in what landed, and none needs action now.

1. The drift-branch reschedule is the highest-value untested line. channel_sort_manager.dart:274-280 is the only thing that heals a wedged publish: deleting the _schedulePublish() there leaves the focused suite fully green. If one test gets added here, this is the one worth having.

2. The clamp at :147-149 has no detector. Removing the outer min(..., now + _maxClockDriftSeconds) and restoring an unbounded max(now, pendingUpdatedAt + 1) also leaves the suite green, even though the clamp is doing real work: with it, the pending stamp saturates at 300 seconds regardless of edit volume; without it, it grows without bound (roughly 400, 1000, and 5000 edits produce leads of 399, 999, and 4999), which is the state that used to reject correctly-stamped remote blobs. Worth noting that a wall-clock toggle test races the second boundary and will be flaky. Seeding pendingUpdatedAt at now + 300 directly, or injecting the clock, discriminates reliably.

3. The legacy conjunct is uncovered. In channel_sort_storage.dart:132, dropping the hasPendingLocalChanges && half of isLegacyPending keeps the suite green.

4. The publish retry has no backoff. _schedulePublish re-arms a fixed _publishDelay timer, so a persistently-rejected publish retries on that interval indefinitely, at the 2 second production default, with only a debugPrint. Bounded and self-recovering in practice, but it is an unbounded-duration fetch loop with no visible signal if it ever stops recovering.

5. _maxClockDriftSeconds = 300 is stricter than the relay. The relay accepts +/-900 seconds (MAX_TIMESTAMP_DRIFT_SECS, crates/buzz-relay/src/handlers/ingest.rs:1859). Unchanged by this PR and possibly deliberate, but the client currently declines to publish some events the relay would accept.

One reachability note, since it came up in review and we corrected ourselves twice on it: an inflated non-pending cursor is bounded to exactly +300 seconds, reachable by adopting a fast-clock peer's blob rather than by local edits, and it self-heals once wall clock catches up. :219 uses >, so a blob at exactly now + 300 is accepted and persisted, which is the one reachable variant, and the reschedule in item 1 is what recovers it.

tellaho pushed a commit that referenced this pull request Aug 4, 2026
…er-snapshots

* origin/main:
  feat(mobile): sync per-group channel sorting (#4231)
  feat(mobile): add channel scroll navigation (#4239)
  feat(desktop): redesign the Huddle experience (#4281)
  feat(mobile): bring channel menus to desktop parity (#3940)

Signed-off-by: npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w <52a228d6edf316ec6812ac3c9fc0d696ab59fc7954d77e7be31eedcddf91335b@buzz.block.builderlab.xyz>

# Conflicts:
#	desktop/src-tauri/src/lib.rs
tellaho added a commit that referenced this pull request Aug 4, 2026
…onfig

* origin/main:
  feat(mobile): sync per-group channel sorting (#4231)
  feat(mobile): add channel scroll navigation (#4239)
  feat(desktop): redesign the Huddle experience (#4281)
  feat(mobile): bring channel menus to desktop parity (#3940)
  feat(agents): model-tuning parity in global Agent Defaults editor (#4578)

Signed-off-by: npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w <52a228d6edf316ec6812ac3c9fc0d696ab59fc7954d77e7be31eedcddf91335b@buzz.block.builderlab.xyz>
Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
tellaho added a commit that referenced this pull request Aug 4, 2026
…theme-config

* origin/main: (81 commits)
  feat(mobile): sync per-group channel sorting (#4231)
  feat(mobile): add channel scroll navigation (#4239)
  feat(desktop): redesign the Huddle experience (#4281)
  feat(mobile): bring channel menus to desktop parity (#3940)
  feat(agents): model-tuning parity in global Agent Defaults editor (#4578)
  Polish Share Compute settings (#3735)
  fix(reactions): wrap long popover names (#3834)
  fix(desktop): clarify inherited agent parallelism (#4010)
  feat(desktop): make onboarding model defaults skippable (#3968)
  ci: add guarded desktop release cache prewarm (#4575)
  fix(mobile): recover stale relay sessions (#4372)
  chore(release): release Buzz Desktop version 0.5.4 (#4562)
  test(mobile): assert follow boundary semantics (#4559)
  docs(release): align desktop handoff instructions (#3988)
  fix: report agent usage per provider round, not once per turn (#4545)
  fix(desktop): harden Windows installs against Defender block and orphaned Node (#4382)
  feat(desktop): improve channel template discovery (#4549)
  fix(desktop): save key backups to authorized path (#4022)
  Add channel activity hover menu (#3935)
  feat(desktop): show saved Run on settings when editing an agent (#4539)
  ...

Signed-off-by: npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w <52a228d6edf316ec6812ac3c9fc0d696ab59fc7954d77e7be31eedcddf91335b@buzz.block.builderlab.xyz>
Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
joahg added a commit to joahg/buzz-dev-mode that referenced this pull request Aug 4, 2026
…-style

* origin/main: (25 commits)
  Polish mobile inbox and media flows (block#4512)
  feat: ship Buzz Term (block#4347)
  feat(mobile): sync per-group channel sorting (block#4231)
  feat(mobile): add channel scroll navigation (block#4239)
  feat(desktop): redesign the Huddle experience (block#4281)
  feat(mobile): bring channel menus to desktop parity (block#3940)
  feat(agents): model-tuning parity in global Agent Defaults editor (block#4578)
  Polish Share Compute settings (block#3735)
  fix(reactions): wrap long popover names (block#3834)
  fix(desktop): clarify inherited agent parallelism (block#4010)
  feat(desktop): make onboarding model defaults skippable (block#3968)
  ci: add guarded desktop release cache prewarm (block#4575)
  fix(mobile): recover stale relay sessions (block#4372)
  chore(release): release Buzz Desktop version 0.5.4 (block#4562)
  test(mobile): assert follow boundary semantics (block#4559)
  docs(release): align desktop handoff instructions (block#3988)
  fix: report agent usage per provider round, not once per turn (block#4545)
  fix(desktop): harden Windows installs against Defender block and orphaned Node (block#4382)
  feat(desktop): improve channel template discovery (block#4549)
  fix(desktop): save key backups to authorized path (block#4022)
  ...

Amp-Thread-ID: https://ampcode.com/threads/T-019fccee-fa22-7145-95bc-a3aa0c10a174
Co-authored-by: Amp <amp@ampcode.com>
Signed-off-by: Joah Gerstenberg <joah@squareup.com>

# Conflicts:
#	desktop/package.json
#	desktop/src-tauri/src/commands/agent_config.rs
#	desktop/src/app/AppShell.tsx
#	desktop/src/features/channels/hooks.ts
#	desktop/src/features/channels/unreadReadMarker.test.mjs
#	desktop/src/features/channels/useUnreadChannels.ts
#	desktop/src/features/sidebar/ui/CommunityRail.tsx
#	desktop/src/main.tsx
#	pnpm-lock.yaml
wpfleger96 pushed a commit that referenced this pull request Aug 4, 2026
…-phase2-integration

* origin/main: (23 commits)
  Refine community invite links (#4734)
  feat(desktop): persist sidebar observed-unread across webview reload (#3976)
  feat(desktop): surface config diff in restart-required badge (#3637)
  Polish sidebar unread hierarchy (#4573)
  fix(desktop): show cached display names on startup (#3317)
  docs(acp): explain per-channel session model in base prompt (#4729)
  docs(nip-am): normative amendment — cache SHOULD/MUST + pricingIdentity + consumer cost guidance (#4632)
  Remove blur from Welcome composer guidance (#4691)
  Refine desktop timeline activity presentation (#4582)
  Defer desktop media uploads until send (#4522)
  fix(desktop): stop clipping focus ring on channel intro action cards (#2392) (#4374)
  Polish mobile inbox and media flows (#4512)
  feat: ship Buzz Term (#4347)
  feat(mobile): sync per-group channel sorting (#4231)
  feat(mobile): add channel scroll navigation (#4239)
  feat(desktop): redesign the Huddle experience (#4281)
  feat(mobile): bring channel menus to desktop parity (#3940)
  feat(agents): model-tuning parity in global Agent Defaults editor (#4578)
  Polish Share Compute settings (#3735)
  fix(reactions): wrap long popover names (#3834)
  ...
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