Skip to content

sync channel sort preferences across clients#1556

Merged
tellaho merged 1 commit into
mainfrom
tho/channel-sort-cross-client
Jul 6, 2026
Merged

sync channel sort preferences across clients#1556
tellaho merged 1 commit into
mainfrom
tho/channel-sort-cross-client

Conversation

@tellaho

@tellaho tellaho commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator

Category: improvement
User Impact: Users' per-group channel sort choices now follow them across desktop clients on the same relay.
Problem: The channel sort controls from #1505 persisted only in the desktop local cache, unlike nearby sidebar preferences such as sections, stars, and mutes that sync through the relay. That meant another desktop client could show the same channel groups but lose the user's chosen Recent / A–Z ordering.
Solution: This adds encrypted NIP-78 relay sync for the existing per-group sort map using the same local-cache, startup fetch, live subscribe, reconnect, and debounced publish pattern as channel sections. The UI and default behavior stay unchanged; localStorage remains the instant/offline cache while the relay blob becomes the cross-client source of truth.

File changes

desktop/src/features/sidebar/lib/channelSortSync.ts
Adds a sync manager for channel sort preferences using kind 30078, d-tag channel-sort, encrypted-to-self content, debounced whole-blob LWW writes, and workspace-switch destroy guards.

desktop/src/features/sidebar/lib/useChannelSortPreference.ts
Wires the existing sort preference hook into startup remote fetch, live subscription updates, reconnect refresh, pending publish flush, and seed-publishing of non-default local preferences when no remote blob exists.

desktop/src/features/sidebar/lib/channelSortSync.test.mjs
Adds regression coverage for destroy/cancel behavior so pending or in-flight publishes cannot leak preferences to the wrong relay during workspace switches.

desktop/src/shared/constants/kinds.ts
Documents channel-sort alongside the other NIP-78 d-tags and adds a named KIND_CHANNEL_SORT constant for the shared 30078 kind.

Reproduction Steps

  1. Open the desktop app with PR feat(channel list): add persistent channel sort toggle #1505's sidebar sort controls available.
  2. Change a sidebar group's sort mode from A–Z to Recent.
  3. Confirm the group reorders immediately and the local cache still preserves the choice after refresh.
  4. Open another desktop client or window with the same identity and relay.
  5. Confirm the same group's sort mode is fetched from the relay and applied without changing other groups.
  6. Switch relays/workspaces while a sort publish is pending and confirm the pending write does not publish to the wrong relay.

Validation

  • tsc — clean
  • biome — clean
  • full desktop node suite — 1660/1660 pass, including 4 new sync regression tests
  • lefthook commit hooks — passed

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM — approve. CI fully green (all checks pass/skip, incl. both E2E Integration shards + relay e2e). This is a disciplined, near line-for-line mirror of the existing channelSectionsSync pattern, which is exactly the right move here.

What's good

  • Faithful pattern replication: manager class, NIP-44 encrypt-to-self, kind 30078 + d-tag, debounced whole-blob LWW, destroyed flag with both abort guards (post-fetch and pre-publish), monotonic created_at = max(now, lastRemote+1) — all consistent with channelSectionsSync.ts on main. No invented conventions.
  • The workspace-switch regression tests are real: the in-flight doPublish abort test holds fetchEvents open with a manual-release promise, fires the timer, destroys, then releases — that exercises the actual cross-relay leak vector, not a sequential approximation.
  • Seed-publish adaptation is correct: Object.keys(local.groups).length > 0 is the right non-default check for a map-shaped store (sections uses sections.length > 0).
  • KIND_CHANNEL_SORT doc-comment update keeps the d-tag inventory in kinds.ts accurate.

Minor notes (none blocking)

  1. Stacked PR — merge order matters. Base is tho/channel-sort (#1505, still open); don't merge this one first.
  2. Whole-blob LWW is a shade lossy for this shape. The sort store is a per-group keyed map (Record<groupKey, mode>) — structurally identical to mutes/stars, which use per-key mergeStores. Two clients changing different groups within the same debounce window will drop one change under whole-blob LWW. The PR body's low-frequency argument is convincing for sort toggles — but if a "my sort reset" ghost report ever shows up, per-key merge is the known fix.
  3. Fourth copy of the same manager. sections, mutes, stars, and now sort each carry a near-identical ~225-line sync manager + test scaffolding. Matching the convention rather than refactoring mid-flight is the right call for this PR, but a generic NIP-78 sync-manager factory is now worth a queued follow-up — the next preference blob shouldn't be copy #5.

Reviewed by Pinky (Buzz agent) on behalf of Wes.

@tellaho tellaho force-pushed the tho/channel-sort branch 2 times, most recently from 04d0a72 to 2fe7f2c Compare July 6, 2026 23:18
Base automatically changed from tho/channel-sort to main July 6, 2026 23:31
The per-group Recent/A-Z sort preference was desktop-only localStorage,
so it silently diverged between devices. It now syncs as encrypted
NIP-78 app data (kind 30078, d-tag "channel-sort") following the same
pattern as channel sections: NIP-44 encrypted-to-self content, debounced
publishes, read-before-write, and whole-blob last-write-wins.
localStorage stays as the instant-paint/offline cache; when no remote
blob exists yet, non-default local prefs seed-publish. The existing
orphaned-section pruning still applies on write, and user-facing sort
behavior is unchanged.

Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
@tellaho tellaho force-pushed the tho/channel-sort-cross-client branch from 865e96f to c4bc3b0 Compare July 6, 2026 23:42
@tellaho tellaho enabled auto-merge (squash) July 6, 2026 23:44
@tellaho tellaho merged commit 04a9d10 into main Jul 6, 2026
25 checks passed
@tellaho tellaho deleted the tho/channel-sort-cross-client branch July 6, 2026 23:53
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