feat(mobile): add channel sections with relay sync#800
Conversation
02d06b9 to
2b5e69e
Compare
c353ec6 to
acd0e36
Compare
2b5e69e to
166c741
Compare
acd0e36 to
1772344
Compare
166c741 to
e604da9
Compare
1772344 to
b4139dc
Compare
e604da9 to
7ed25fe
Compare
b4139dc to
72301b5
Compare
Review note (Brain) — mobile parity, part of the channel-sections stackReviewed as the tip of the 3-PR stack (#789 → #792 → this). The mobile implementation itself looks good: it faithfully mirrors the desktop manager (CRUD, 5s debounced publish, LWW merge, No changes needed in this PR — but it's blocked on a fix in #792. #792's desktop LWW tie-break is currently inverted relative to this PR's mobile tie-break (desktop: smaller event id wins on a same-second tie; mobile here: larger id wins — One small confirmation from the review: the mobile debounce-vs-remote race is safe — the delayed Recommendation: merge after #789 and the corrected #792. — Brain (paired with @Pinky on this review) |
|
@wpfleger96 my agents added some comments for ya. Also, the spacing here looks like it's too much in the group sections. Can we check that out? |
72301b5 to
4a488e4
Compare
Channel sections were localStorage-only, meaning they didn't sync across devices. Adds a NIP-78 (kind 30078) event-based sync layer so sections created, renamed, deleted, or reordered on any device propagate to all others. localStorage is retained as a local cache for instant rendering on startup.
Cancel stale debounce when applying remote updates to prevent publishing overwritten state. Track pending publishes for retry on reconnect. Add pubkey validation before decrypting relay events. Extract applyRemote reconcile helper and swapSectionOrder to eliminate duplication. Fix createSection to construct section before setStore so return value is deterministic. Add NIP-01 event ID tie-breaking. Wire resetSyncState into resetWorkspaceState.
Extract swapSectionOrder to channelSectionsHelpers.ts for testability. 27 tests covering parseChannelSectionPayload validation, stripOrphanedAssignments identity semantics, localStorage round-trip, and swapSectionOrder boundary conditions.
…eorder Two review findings in useChannelSections.ts: 1. The same-second tie-break was inverted vs mobile — desktop kept the smaller event ID while mobile kept the larger. Flip >= to <= so both platforms converge to the lexicographically larger event ID. 2. reorderSections (the DnD path) wrote to localStorage but never called publishSections, so drag-reordered sections didn't sync to other devices until some unrelated mutation triggered a publish.
133a3cf to
a4b6ed5
Compare
4a488e4 to
b16cd64
Compare
…y assertion Add version: 1 to the published payload to match mobile's format, preventing future migration asymmetry. Replace pubkey! non-null assertion with an early return guard to silence the Biome warning.
User-defined channel groups for the mobile sidebar, matching the desktop feature. Sections are persisted locally via SharedPreferences and synced across devices through NIP-78 kind 30078 events with NIP-44 encryption, using the same data format as desktop so changes propagate bidirectionally. Adds CRUD (create/rename/delete sections, move up/down), channel assignment via long-press action sheet, and section rendering above the ungrouped "Channels" group. Follows the ReadStateManager pattern for relay fetch/subscribe/publish with 5-second debounced writes and last-write-wins conflict resolution.
The bottom sheet only showed "Mark as read" when a channel was unread. Now it always shows a toggling item matching desktop parity. Adds markContextUnread to ReadStateManager (rolls back to lastMessage - 1).
b16cd64 to
4c7babd
Compare
|
🤖 good eye on the spacing — the PopupMenuButton's default 48px minimum tap target was inflating the custom section header beyond the built-in header height. fixed in 4c7babd by wrapping it in a SizedBox(24x24) with empty BoxConstraints to match the natural row height. |
PopupMenuButton inflates its tap target beyond the Row's natural height, creating a visible gap between custom section titles and channels. Replace it with GestureDetector + showMenu() so the Row contains only plain Icon and Text widgets — structurally identical to the built-in _SectionHeader.
4c7babd to
5f58577
Compare
* origin/main: chore(release): release version 0.3.6 (#811) feat(mobile): add channel sections with relay sync (#800) feat(desktop): sync channel sections across devices via Nostr (#792) feat(media): support arbitrary file types with download cards (#810) feat(desktop): add user-defined channel sections to sidebar (#789) Signed-off-by: tlongwell-block <109685178+tlongwell-block@users.noreply.github.com> # Conflicts: # desktop/src-tauri/src/commands/mod.rs




Adds channel sections to the mobile app, bringing it to full parity with the desktop feature introduced in #789 and #792.
Users who organize their channels into sections on desktop currently see a flat list on mobile. This PR renders those sections, supports creating/renaming/deleting/reordering them, lets users assign channels via long-press, and syncs bidirectionally with desktop through the same NIP-78 kind 30078 events and NIP-44 encryption established in #792. Long-pressing any channel opens an action sheet with "Move to section" and a mark read/unread toggle (matching the desktop right-click menu).
channel_sections_storage.dart— SharedPreferences persistence with keysprout.channel-sections.v1:<pubkey>, matching the desktop format exactly so the same relay events decode correctly on both platformschannel_sections_manager.dart—ChannelSectionsCrypto(NIP-44 encrypt-to-self) +ChannelSectionsManagerwith full CRUD, 5-second debounced publish, kind 30078 fetch/subscribe, and last-write-wins merge (newercreatedAtwins; event ID tie-break)channel_sections_provider.dart—ChannelSectionsNotifierRiverpod provider watchingrelayConfigProvider,relaySessionProvider, andactiveWorkspaceProvider; followsReadStateNotifierpattern throughoutchannels_page.dart— renders user-defined_CustomChannelSectionwidgets above the built-in "Channels" group; adds_CustomSectionHeaderwithPopupMenuButton(rename/move up/move down/delete); adds long-press on_ChannelTileopening an action sheet with "Move to section" and an always-present mark read/unread toggle;_SectionNameDialogfor create/rename flowsread_state_manager.dart/read_state_provider.dart— addsmarkContextUnread(rolls read timestamp back tolastMessage - 1) to support the mark-as-unread toggle, mirroring the desktopReadStateManagerpatternStack: #789 (desktop UI) → #792 (relay sync) → this PR (mobile)