block / block/buzz

[Bug] Custom sidebar sections do not sync between mobile and desktop (either direction)

Open
#4,883 0 comments 1 reaction 0 assignees View on GitHub
Dominant language
Rust
Stars
32.7k
Forks
4.3k
Avg merge
1d 13h
Merged PRs (30d)
253

Description

## Summary

Custom sidebar sections do not sync between the mobile app and the desktop app in either direction. Both platforms ship the feature and both claim to sync it over the same NIP-78 blob (#792 desktop, #800 mobile), but in practice each device keeps its own organization:

- Move a channel into a section **on the phone** → on desktop that channel is still unorganized (sits in the flat Channels group).
- Organize channels into sections **on desktop** → the phone does not reflect it.

The sections themselves render fine locally on each platform. It's the cross-device state that never converges.

## Reproduction

1. On desktop, create a section (e.g. `Ops`) and drag a channel into it.
2. Open the same community on mobile with the same identity, fully connected to the relay.
3. Phone shows the channel in the default group, not in `Ops`.
4. On the phone, long-press a channel → **Move to section** → pick/create a section.
5. Return to desktop. The channel is not in that section.

Both apps are online and connected to the relay throughout; other synced state (messages, read state) is flowing normally.

## What I checked in the code

The wire contract looks aligned on both sides, which is why this reads as a real bug rather than an unimplemented feature:

- Same event: kind `30078`, `["d","channel-sections"]`, `["t","channel-sections"]`
(`desktop/src/features/sidebar/lib/channelSectionsSync.ts`, `mobile/lib/features/channels/channel_sections/channel_sections_manager.dart`)
- Same payload shape: `{version, sections:[{id,name,icon,order}], assignments:{channelId → sectionId}}`
- Same crypto: NIP-44 encrypt-to-self
- Same merge policy: whole-blob last-write-wins, newer `createdAt` wins, event-id tie-break

Two things stood out as candidate causes:

**1. The synced blob is not scoped per community, but the desktop cache is.**

`#1477` (`fix(sidebar): scope channel sections storage to relay URL`) scoped desktop's *local* store by normalized relay URL:

```ts
// desktop/src/features/sidebar/lib/channelSectionsStorage.ts
export function storageKey(pubkey: string, relayUrl?: string): string {
if (!relayUrl) return `${STORAGE_KEY_PREFIX}:${pubkey}`;
const normalized = normalizeRelayUrl(relayUrl);
...
```

Mobile's key was never given the same treatment:

```dart
// mobile/lib/features/channels/channel_sections/channel_sections_storage.dart:5
String channelSectionsKey(String pubkey) => 'buzz.channel-sections.v1:$pubkey';
```

Meanwhile the *relay* event carries no community/relay discriminator at all — the `d` tag is the constant `"channel-sections"` on both platforms. So one identity has exactly one sections blob across every community it's a member of, while desktop maintains a separate local view per relay. For a multi-community user that means each publish overwrites the other community's organization wholesale, and the two platforms disagree about what the blob is even supposed to contain.

**2. Every failure on this path is silent.**

Decrypt/parse failures are swallowed on both sides (`catch {}` in `decryptAndParse`, `catch (_) {}` in `_mergeEvent`), and desktop's publish failure only reaches `console.warn`. If any of the above is misfiring at runtime there is no user-visible signal and nothing in the UI to distinguish "not synced yet" from "sync is broken."

## Suggestion

- Scope the synced blob per community — e.g. `["d", "channel-sections:"]` (with a migration read of the legacy unscoped `d` tag), and scope mobile's local key the same way desktop's is.
- Surface sync failures instead of swallowing them — at minimum a debug-visible signal when a fetched blob fails to decrypt or parse, since today both platforms fail closed and look identical to "no remote state."
- A cross-platform test that publishes a blob from one client's serializer and reads it back through the other's parser would catch schema/scope drift before it reaches users.

## Related

- #792 — desktop cross-device sync for sections
- #800 — mobile sections with relay sync (states bidirectional desktop↔mobile sync as the goal)
- #1477 — scoped desktop section storage to relay URL (mobile has no equivalent)
- #3363 — *adjacent but the premise looks stale*: it says mobile implements starring only and has no sections. Mobile sections landed in #800; the current gap is that they don't sync, not that they're missing.
- #3667 — NIP-29 channel subgroups (different, server-side hierarchy proposal)

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.