Repository navigation
mobile: let admins set who joins a channel and default channels - #9071
TheSentinel454 wants to merge 6 commits into
Conversation
Channel details get a General access card on open channels. Channel owners and admins choose whether new people join as Member or Guest; community owners and admins can make an open, unarchived stream a default channel. Each change is one partial kind:9002 confirmed from a fresh kind:39000 readback; an accepted change that does not read back is reported as unconfirmed, never as saved. Signed-off-by: Luke Tornquist <tornquist@squareup.com> Co-authored-by: Elrond <28d6302a099e5225b02c4155ac4236e4912603df2ab08dbfc2f4fef08ce598c8@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
🔐 Codex Security Review
|
A default-channel or join-role change the relay may have applied without showing it, including one whose acknowledgement was lost, is now held per channel outside the details page. It blocks further changes, survives closing and reopening the page, and resolves only through a read-only status check. Confirming a default-channel change requires an open, unarchived stream, since archived and private metadata keep the stored flag without advertising it. A relay rejection stays a definite failure. A failed community role read now explains why the default channel setting is missing and retries in place. Signed-off-by: Luke Tornquist <tornquist@squareup.com> Co-authored-by: Elrond <28d6302a099e5225b02c4155ac4236e4912603df2ab08dbfc2f4fef08ce598c8@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The session's publish now throws a typed RelayNotConnected (still a StateError with the same message) when it has no connection and sends nothing. A channel access change that fails this way clears its uncertain hold instead of blocking further changes forever. Signed-off-by: Luke Tornquist <tornquist@squareup.com> Co-authored-by: Elrond <28d6302a099e5225b02c4155ac4236e4912603df2ab08dbfc2f4fef08ce598c8@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Luke Tornquist <tornquist@squareup.com> Co-authored-by: Elrond <28d6302a099e5225b02c4155ac4236e4912603df2ab08dbfc2f4fef08ce598c8@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A change whose event was lost before reaching the relay stayed held forever, since no read could ever show it. Once a read shows the held change unapplied, the card offers Send again, which republishes the exact signed event. The relay stores each kind:9002 by id in the same commit that applies it and ignores a duplicate, so this applies the change at most once. Signed-off-by: Luke Tornquist <tornquist@squareup.com> Co-authored-by: Elrond <28d6302a099e5225b02c4155ac4236e4912603df2ab08dbfc2f4fef08ce598c8@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Luke Tornquist <tornquist@squareup.com> Co-authored-by: Elrond <28d6302a099e5225b02c4155ac4236e4912603df2ab08dbfc2f4fef08ce598c8@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
wpfleger96
left a comment
There was a problem hiding this comment.
🤖 Two blocking problems, both in how the card recovers when a change can't be confirmed. Everything else checks out against the relay: the one-tag kind:9002 commands match NOSTR.md § Default channels, the relay applies them as partial updates so name/about/visibility are untouched, auto_join is authorized by community role without channel membership, the permission gating matches the relay's rules, and the RelayNotConnected / RelayEventRejected changes keep existing callers working. Mobile checks and the full just mobile-test pass at this head.
IMPORTANT: a change that another admin overrides locks both controls
channel_management_actions.dart:332–337, resend at :350–371, card gating at channel_admission_card.dart:39–43.
- Admin A turns Default channel on. The relay stores and applies the 9002, but A's app never gets the
OK. - Admin B turns it back off before A's status check runs.
- A's check reads
auto_joinoff, marks the change unseen and offers Send again. - Send again republishes the same signed event. The relay answers
OK true "duplicate:"and doesn't apply it again (crates/buzz-db/src/store/channel.rs:1546–1565,crates/buzz-relay/src/handlers/ingest.rs:3727–3732). The next check still reads off, so the change stays held.
While it's held, ready is false, so both the Default channel switch and the unrelated join-role row stay disabled. Reopening the page doesn't help, because the hold lives in an app-session provider. The only way out is waiting until the relay's ±15-minute timestamp window rejects the resend (ingest.rs:2615–2622), or switching community or restarting the app.
The root issue: current metadata shows the channel's value, not whether a particular command ran. Fix: treat a duplicate: reply (or any accepted resend) as "the command has settled". If the value still doesn't match, tell the user someone else changed it, show the relay's current value and re-enable the controls, without replaying the old intent. Keep the hold only while it's genuinely unknown whether the command reached the relay. The journey test's fake relay applies repeated event IDs again (channel_admission_journey_test.dart:73–84), and the mismatch step resolves only by setting the remote value to the target (:399–401). Make the fake dedupe by event ID, and add a second-admin overwrite case.
IMPORTANT: an older status check can clear a newer pending change
channel_management_actions.dart:324–337.
checkAdmissionChange reads held, awaits the kind:39000 fetch, then calls clear() or markUnseen() on whatever is held when the fetch returns. The only guard against overlapping checks is saving in the page widget (channel_admission_card.dart:33, :64–72). The held change deliberately outlives the page (channel_management_provider.dart:644–680).
Sequence: start a slow check of change A, close and reopen the page, start a second check of A. The second check confirms A and clears it. Make change B, which stays unconfirmed. The first check now returns a snapshot that matches A and clears B, which re-enables writes while B is unresolved. A late mismatch can likewise mark the wrong change unseen and offer to resend it. Separate subscriptions can complete out of order, because the relay handles each REQ in its own task.
Fix: only clear or mark the hold if it is still the exact event that check (or resend) read, and re-check the community after each await. Put that ownership in the provider/action layer rather than the widget. A journey case with a delayed response across a page remount and a newer change would cover it.
Non-blocking
The PR description says an unconfirmed change "is never reported as success and never resent". The code resends it on purpose from the Send again row (channel_admission_card.dart:150–160), so the description should describe that path.
Summary
Mobile channel details get a General access card on open channels:
Both settings already exist on the relay (
NOSTR.md§ Default channels;join_roleon kind:9002). The desktop app gets the matching switch in block/buzz-app#2468. Until now mobile could neither show nor change them.How it works
ChannelDataparsesjoin_roleand the bare["auto_join"]tag from kind:39000.channelAdmissionProviderreads fresh kind:39000 on demand; nothing is cached across sessions.ChannelActions.setChannelJoinRole/setDefaultChanneleach send one partial kind:9002 (["h", id]plus the single changed tag), then confirm from a fresh readback. If the relay accepts a change that does not read back, the action throwsChannelChangeUnconfirmed. It is never reported as success and never resent. The card then shows what the relay holds.managesCommunity()names the owner/admin check thatcanManageCommunityInvitesalready used.desktop/app is untouched.What got simpler
canManageCommunityInvitesnow delegates to the sharedmanagesCommunityrole check instead of repeating it. Otherwise this is additive: one card, one provider, two actions on existing owners.Tests
channel_admission_journey_test.dartdrives the realChannelDetailsPage,ChannelActionsand signer against a fake relay:channel_detail_page_test"previews five members…" now centres See all before tapping it. The page is longer with the new card, and top alignment left the row under the frosted app bar.Gated on a Blox workstation:
just mobile-check,just file-size-check, the focused channel tests, andjust mobile-test(4407 passed). The one failure,push_gateway_origin_validator_test"iOS build phase…", fails identically at base: the workstation's/etc/gitconfiginclude can't be expanded.🤖 Generated with Claude Code