Repository navigation
Let community admins set default channels - #2468
TheSentinel454 wants to merge 2 commits into
Conversation
Members -> General access gets a Default channel switch for community owners and admins on open, unarchived stream channels. It sends the relay's narrow kind:9002 auto_join command on its own and changes only after the channel's kind:39000 readback confirms it. 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 default-channel attempt now remembers that it is one and confirms only when the readback shows the requested value; archived and private metadata keep the change uncertain. A failed community role read shows an error with a retry instead of hiding the setting silently. 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>
kazemek21
left a comment
There was a problem hiding this comment.
Kyle reviewed the PR and the Nexus review-tracker notes and approved with the two non-blocking suggestions below.
Posted by Codex (gpt-6.1-sol) on behalf of kylez
| </Button> | ||
| </div> | ||
| )} | ||
| {base?.autoJoin !== undefined && !solo && community.manager && ( |
There was a problem hiding this comment.
[P2] Preserve default-channel management for community admins with read-only access
Should this control remain available when a community admin is a channel Guest or nonmember? discovery.get() marks Guests/previews read-only, so the early return hides it; nonmembers also fail lifecycleSettings(). The relay accepts this command based on community role without requiring channel membership. Please separate that management path from channel posting/edit rights.
Posted by Codex (gpt-6.1-sol) on behalf of kylez
| (draft.joinRole !== undefined && details.joinRole !== draft.joinRole) || | ||
| // Other saves never send the setting. Archived and private channels | ||
| // keep it without reporting it, so missing state proves nothing. | ||
| (defaultChange && details.autoJoin !== draft.autoJoin) || |
There was a problem hiding this comment.
[P2] Confirm only the default state for a default-only command
If another admin renames the channel between the last authorization read and confirmation, the toggle can succeed but this comparison leaves it permanently unconfirmed. check() repeats the old name comparison and attempts.has(id) prevents another save. Should the defaultChange branch confirm only the requested autoJoin state, while retaining the existing full-details checks for ordinary saves?
Posted by Codex (gpt-6.1-sol) on behalf of kylez
wpfleger96
left a comment
There was a problem hiding this comment.
🤖 Automated review of head 3b74e39ed5f9d31bcda94dad059ddafe68cb12a8.
IMPORTANT: main already ships this feature. #1624 (merged 2026-11-05, 84224a52c7) added a "Starter channel for new members" switch to ChannelSettingsPanel.tsx, shown only to community owners and admins (managesCommunity(role)) on active public stream channels. It sends the same two-tag kind 9002 [["h", id], ["auto_join", "true"|"false"]] through the lifecycle route (auto-join-on / auto-join-off in channel-lifecycle-protocol.ts), and both host validators already admit that shape (validateLifecycleTemplate and channel_writes.rs lifecycle). It also confirms from the republished 39000 and parses the bare auto_join tag into ChannelSummary.autoJoin.
This PR adds a second control for the same relay setting in Members → General access and a second admission path for the identical command on the channel-details route (defaultTemplate in channel-details-protocol.ts, the early return in details() in channel_writes.rs). Rebased onto main as-is, admins would see two switches for one setting with different names ("Default channel" and "Starter channel for new members") and different save, confirmation and error paths, and the docs would describe the same setting twice under two names. It is also why the PR conflicts with main in ChannelGeneralAccess.tsx, which now warns about starter channels when one is made private.
Fix: close this PR in favor of #1624. Or, if something here should survive, rebase onto main and keep only that change on top of the existing lifecycle command and ChannelSummary.autoJoin, without the parallel channel-details path. The one difference I found is the join-role wording ("join automatically as guests/members").
Smaller note on the same flow (only matters if any of this survives): a community admin who is not a member of the channel gets the read-only preview in the members dialog, so they never see the switch here, even though the relay lets them set it without membership.
|
🤖 Design review (critical path: new-member onboarding) Looks good from a design standpoint. No must-fix items. Optional:
Not checked: no screenshots in the PR and I didn't run the app, so I haven't seen the row in light/dark, narrow windows, or enlarged text, or what a new member sees on first join. |
| {base?.autoJoin !== undefined && !solo && community.manager && ( | ||
| <SwitchPreferenceRow | ||
| label="Default channel" | ||
| description={`New community members join automatically as ${base.joinRole === "guest" ? "guests" : "members"}.`} |
There was a problem hiding this comment.
🤖 Please use View only for the guest case, matching the General access menu (generalAccessOptions["view-only"].label). For example: "New community members join automatically with View only access." Calling the same access mode "guests" here makes it sound like a different setting.
| {base?.autoJoin !== undefined && | ||
| !solo && | ||
| !community.manager && | ||
| community.readError && ( | ||
| <div className={styles.accessFeedback}> | ||
| <p role="alert" className="text-body-sm text-danger"> | ||
| Couldn’t check your community role, so the default channel | ||
| setting is unavailable. |
There was a problem hiding this comment.
🤖 Please keep this warning and Retry action hidden for channel admins who are not community owners/admins. !community.manager && community.readError also matches a failed roster lookup for those users, even though they would never be offered the Default channel switch. They should not see an alert about a setting they cannot use when their channel access is unaffected. Please scope the role-check recovery to users for whom this control is relevant, and add regression coverage for a channel admin who is only a community member when the role lookup fails.
Summary
Community owners and admins can make a channel a default channel from the app: Members → General access gets a "Default channel" switch on open, unarchived stream channels. The relay then joins every new community member to it, with the channel's join role (Member or Guest).
The relay already supported this (
kind:9002["auto_join", "true"|"false"], community owner/admin only, open and unarchived streams only —NOSTR.md§ Default channels in block/buzz). Until now it was only reachable from the relay CLI.How it works
["h", <channel>]+["auto_join", ...], never combined with a details save. A community admin is not necessarily a channel admin, and the details editor always resends name/about, which needs channel admin. Both the TS broker validator and the native Rust validator admit exactly that two-tag shape for routechannel-detailsand reject it combined with anything else.useCommunityRole), and the write re-reads the channel before sending.["auto_join"]tag in the new state. An accepted write that does not read back stays in the existing uncertain state and cannot be resubmitted until refreshed.What got simpler
Nothing removed. The change reuses the existing General access flow, details protocol and readback confirmation rather than adding a second write path or store.
Tests
channel-details.test.ts: the narrow command's exact tag shape, validator rejection of combined or malformed commands, authorization by community role rather than channel role, and readback confirmation (including a private channel whose readback carries no default-channel state).ChannelGeneralAccess.test.tsx: switch visibility by community role and channel eligibility, toggling on and off through the real flow, and the unconfirmed path.channel_writes.rs:details_admit_only_the_exact_default_channel_command.Gated on a Blox workstation: biome, icon check, rustfmt,
check-push.mjs(types + related unit tests, design system, clippy), focused Vitest, andcargo test relay::channel_writes.🤖 Generated with Claude Code