Add channel visibility & ephemeral TTL controls to manage sidebar#911
Merged
Conversation
Adds the ability to edit a channel's visibility (open/private) and its ephemeral TTL after creation, and pins the lifecycle buttons in a sticky footer so they stay visible. Relay (minimal slice): - kind:9002 handler recognizes new `visibility` and `ttl` tags, validates them (visibility in open/private; ttl = positive seconds or "" to clear), gates them owner/admin-only (same tier as name/about/archived), persists, resets ttl_deadline on ttl change, and emits visibility_changed / ttl_changed system messages. Visibility flips invalidate the accessible-channels cache. - sprout-db ChannelUpdate gains `visibility` and tri-state `ttl_seconds` (Some(Some(n))=set, Some(None)=clear -> permanent, None=untouched). Desktop: - Sidebar lifecycle section: Private switch, Ephemeral switch, and a friendly duration field (1d/12h/30m, combos like 1d12h) shown when ephemeral is on, with inline validation and a dirty-aware save button. - Sticky border-t footer for Archive/Unarchive/Leave/Delete. - Friendly 1d/12h/30m parse/format lives entirely in the web layer; the wire protocol stays numeric seconds. Tauri command uses a double_option deserializer so null=clear survives serde. Note: relay validation and the sprout-db update SQL are exercised by CI (they need a live Postgres) rather than inline unit tests. Co-authored-by: Brain <21994759fc7a6fa6b965551d35cfd7897d262f2495467f2d78694ddcfa6a5c7e@sprout-oss.stage.blox.sqprod.co> Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Collaborator
Author
Screenshots — channel visibility & ephemeral TTL controlsCaptured from the desktop app via the Playwright e2e harness ( Full management sheetPrivate + Ephemeral controls in the lifecycle section, with the sticky footer (Leave / Archive / Delete) pinned at the bottom. Private toggleFlip a channel private after creation (owner/admin only). Ephemeral + friendly TTLEphemeral on with a friendly duration field ( TTL validationAn invalid/empty duration blocks save with an inline error — can't accidentally clobber an existing TTL. Sticky footerArchive / Leave / Delete pulled out of the scroll area into a Default lifecycle stateBoth switches off, as a baseline. |
Captures the new channel lifecycle controls (Private/Ephemeral switches, friendly TTL field with validation) and the sticky Archive/Leave/Delete footer via the existing Playwright screenshot harness on the mock backend. Co-authored-by: Brain <21994759fc7a6fa6b965551d35cfd7897d262f2495467f2d78694ddcfa6a5c7e@sprout-oss.stage.blox.sqprod.co> Signed-off-by: Wes <wesbillman@users.noreply.github.com>
When a channel flips from open to private, connections that subscribed while it was open kept receiving events: fan-out does not re-check channel access per event, so a non-member's live subscription leaked private messages. Detect the open->private transition and close every live channel-scoped subscription held by a connection whose authenticated pubkey is not a current member; members keep their subscriptions. Also harden TTL editing to fail closed: a parse failure now rejects the edit instead of silently clearing the TTL to permanent. - subscription.rs: channel_subscriber_conns enumerates distinct conns subscribed to a channel across the kind and wildcard indexes. - state.rs: pubkey_for_conn forward lookup (conn -> authenticated pubkey). - side_effects.rs: shared per-conn eviction helper reused by both the member-remove path and the new non-member eviction path. Co-authored-by: Brain <21994759fc7a6fa6b965551d35cfd7897d262f2495467f2d78694ddcfa6a5c7e@sprout-oss.stage.blox.sqprod.co> Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Stale subscriptions could survive an open->private visibility flip on other relay nodes, leaking private-channel events. Add a fan-out access filter applied at both the local dispatch and the multi-node Redis consumer: channel-less and open-channel events pass through untouched; on a private channel a recipient is kept only if its connection's authenticated pubkey is a current member. Unknown/unauthenticated recipients and visibility-lookup errors fail closed. The per-channel visibility cache caches only `private`, never `open`. The gate fails open on a non-private result, so a stale cached `open` on another node would mask the filter for the whole TTL after a flip (the cache has no cross-node invalidation). Caching only `private` keeps it fail-safe: the worst stale entry is an over-restrictive `private`, never a leak. Open-channel events cost one channel lookup. The existing open->private eviction is kept for immediate CLOSED UX on the ingesting node; this filter is the cluster-wide backstop. Co-authored-by: Brain <21994759fc7a6fa6b965551d35cfd7897d262f2495467f2d78694ddcfa6a5c7e@sprout-oss.stage.blox.sqprod.co> Signed-off-by: Wes <wesbillman@users.noreply.github.com>
tlongwell-block
pushed a commit
that referenced
this pull request
Jun 10, 2026
* origin/main: Fix post-compact handoff context for OpenAI providers (#931) chore(release): release version 0.3.15 (#936) fix: persona is source of truth at spawn + thread-depth conventions (#930) fix: skip avatar reconciliation for legacy agent records (#933) feat(desktop): add nest commit identity guidance with human sign-off (#929) feat: provider/model selection for personas and runtime-aware env injection (#794) fix: reconcile agent profile on startup when relay publish was missed (#921) Revamp first-run onboarding (#924) Update setup loading screen (#926) fix(dm): keep hidden DMs hidden across refetch via relay-signed visibility snapshot (NIP-DV) (#857) Maximize desktop window on launch (#925) feat: preview features (experiments settings UI) (#888) fix(updater): send no-cache header on update check to avoid stale manifest (#922) fix(desktop): refresh channel state after unarchive (#923) Add channel visibility & ephemeral TTL controls to manage sidebar (#911) ci(release): add Intel macOS (x86_64) DMG as a release target (#748) Signed-off-by: npub1mprnacetjua2xx3p5eddmhxyk6wv929ymm5py8kd2xfxurxahspqqlgyta <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@sprout-oss.stage.blox.sqprod.co> # Conflicts: # desktop/src/features/sidebar/ui/AppSidebar.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.






Summary
Adds the ability to edit a channel's visibility (open/private) and its ephemeral TTL after creation, plus a sticky footer so the lifecycle buttons (Archive/Unarchive/Leave/Delete) stay visible in the channel manage sidebar. Requested by @wes.
Previously these fields could only be set at channel-creation time; the kind:9002 edit-metadata path didn't know about them.
What changed
Relay (minimal slice)
visibilityandttltags, validates them (visibility ∈open/private; ttl = positive seconds, or""to clear → permanent), gates them owner/admin-only (same tier as name/about/archived), persists, resetsttl_deadline = NOW() + ttlon ttl change, and emitsvisibility_changed/ttl_changedsystem messages.ChannelUpdategainsvisibilityand a tri-statettl_seconds(Some(Some(n))=set,Some(None)=clear,None=untouched). Clearing nulls both ttl columns.Desktop
1d/12h/30m, combos like1d12h) shown when ephemeral is on, with inline validation and a dirty-aware "Save visibility" button (only enables when something actually changed and the duration is valid; an empty/invalid field can't accidentally clear an existing TTL).border-tfooter for Archive/Unarchive/Leave/Delete, pulled out of the scroll area.1d/12h/30mparse/format lives entirely in the web layer; the wire protocol stays numeric seconds, matching channel creation.double_optiondeserializer so a presentnullsurvives serde asSome(None)(clear) rather than collapsing toNone(no-change).Verification
rust-tests,rust-clippy,desktop-test,desktop-tauri-test,desktop-tauri-clippy,mobile-test— all ✓The relay validation and the sprout-db update SQL are exercised by the Rust test suite + CI (live Postgres paths) rather than additional inline unit tests.
🤖 Adversarial review requested from @Pinky in parallel.