# Group creation flow (nostrord parity) Plan to bring the group creation flow in line with nostrord (https://github.com/nostrord/nostrord), a NIP-29 client. Analysis of both implementations, then a phased plan. ## Current state (coop) - **Dialog** (`crates/workspace/src/dialogs/new_group.rs`): three plain text inputs (Name, Group id, Relay). Slug is generated from the name when the id is blank. Metadata is `GroupMetadata::default()` with only the name set. Errors surface as toast notifications only. - **Protocol** (`crates/nip29/src/protocol.rs`): `GroupId::create_group` sends kind 9007 with the full metadata in one event. No confirmed-group-id handling and no OK-retry on metadata. - **Registry** (`GroupsRegistry::create` in `crates/nip29/src/lib.rs`): ensures and subscribes the group, upserts the list entry, publishes kind 10009, and on publish failure forgets the group and emits `GroupsEvent::Error`. ## Reference (nostrord) ### UI (`CreateGroupModal.kt`) - Title "Create a Group" ("Create Channel" for subgroups). - Avatar upload row -> `picture`. - Name (required, placeholder `#example`). - Group ID optional, hidden behind a "Set a custom ID" disclosure; filtered to lowercase `a-z0-9-_`; hint "Leave empty for a random ID. The relay may override your choice." - Relay dropdown: user's relays + current relay + suggested NIP-29 relays + "Custom relay..." revealing a URL field. Locked to the parent's relay for subgroups. - Description, multi-line (3-5 rows) -> `about`. - ACCESS SETTINGS: switch rows with NIP-29 copy — Private (only members can read), Closed (join requests ignored), Restricted (only members can post), Hidden (not discoverable). - YOUR LIST: "Private on my list" — keeps the membership in the NIP-44 encrypted section of kind 10009. - Errors: friendly mapping (`blocked:` -> creation must happen on the relay's website, `auth-required` -> authorization required, timeouts, ...) plus an "Open relay website ->" link (wss -> https). - Submit: spinner while in flight, cancelable; validation (name required, valid relay URL). ### Protocol (`GroupManager.createGroup`) Two-step flow: 1. Group ID: custom (trimmed, lowercased) or a random readable id (`ReadableId.generate()`). 2. Kind 9007 (create-group) with only the `h` tag -> await relay OK -> await the relay's kind 39000 to learn the confirmed group id (relays may override the suggested id). 3. Kind 9002 (edit-metadata) with `h` + name + access flags (presence-only tags) + about + picture + parent -> await OK (7s timeout, one retry after 1s to cover the admin-grant race). If still rejected, keep the group and warn that the relay did not accept its name. 4. Bookkeeping: persist membership, discard the self-invite race, set the private-list flag before publishing kind 10009, publish the list, request group messages and metadata. ### Existing coop infrastructure to reuse - `Group::publish` already awaits the relay OK and surfaces the rejection reason (`crates/nip29/src/group.rs`). - `GroupId::edit_metadata` / `Group::edit_metadata` for kind 9002 (`crates/nip29/src/protocol.rs`, `group.rs`). - `ui::Switch` (label + description), `PopupMenu` dropdown, `TextareaState`, `Avatar` (`crates/ui`). - Blossom upload via `state::upload` with `AppSettings::get_file_server`. - NIP-65 relay-list extraction, as in `dialogs/relays.rs` (`GossipRelays::load`). - Group panel open flow used by the Browse panel (`crates/workspace/src/panels/browse.rs` `open_panel`). ## Plan ### Phase 1 — Protocol layer (`crates/nip29`) - Adopt the two-step creation in `protocol.rs`: `GroupId::create_group` emits kind 9007 with only the `h` tag; metadata moves to the existing `edit_metadata` (kind 9002) builder. - Add `GroupCreateOptions` (name, about, picture, private/closed/restricted/ hidden, optional custom id, optional parent) passed to `GroupsRegistry::create`. - Rework `GroupsRegistry::create`: - ID: custom (trim/lowercase/validate) or a random readable id (small port of nostrord's syllable-based `ReadableId`). - `ensure()` -> publish 9007 via `Group::publish` (relay OK) -> wait briefly (with timeout) for the relay's kind 39000 to confirm the group id (the group is already subscribed via `ensure`). - Publish 9002 with the full metadata; retry once after 1s; on persistent failure keep the group and emit a warning event (nostrord parity). - On 9007 failure: `forget()` + emit `GroupsEvent::Error` with the relay's reason. - On success: upsert entry + `publish_list` + emit a new `GroupsEvent::Created(GroupKey)` so the UI can navigate. ### Phase 2 — New dialog (`crates/workspace/src/dialogs/new_group.rs`) Rebuild the modal to nostrord's layout and copy: - Header + subtitle; name field; "Set a custom ID" disclosure with filtered input. - Relay dropdown: user's NIP-65 relays (loaded like `GossipRelays::load`) + suggested group relay + "Custom relay..." revealing a URL field with validation. - Description via `TextareaState` (3-5 lines). - ACCESS SETTINGS: four `ui::Switch` rows with nostrord's NIP-29 label/description copy. - In-flight state on the Create button (spinner, disabled), inline error row with the friendly error mapping + "Open relay website ->" link (wss -> https). - Subscribe to `GroupsEvent::Created` / `GroupsEvent::Error` while open -> close the dialog and open the new group panel (same flow as `browse.rs::open_panel`). ### Phase 3 — Avatar upload - Picture row using `ui::Avatar` preview + file picker -> `state::upload` (existing blossom upload), wiring upload errors into the dialog's error row. ### Phase 4 — Optional extensions - **"Private on my list"**: requires NIP-44 encrypted-section support in `GroupList` (kind 10009 parse/build/load) — meaningful extra scope; candidate for a follow-up. - **"Create Channel" subgroup mode**: protocol support (parent tag) already exists; needs a trigger point in the group details/subgroups UI and relay-locking in the dialog. ### Validation - `cargo check` / `cargo clippy` after each phase. - Manual run of the dialog against a NIP-29 relay. - Unit tests for the readable-id and input-filter helpers. ## Open questions 1. Include "Private on my list" now, or defer the encrypted kind 10009 work? 2. Blank id behavior: nostrord generates a random readable id, coop currently slugs the name. Match nostrord? 3. Avatar upload in scope for the first pass? 4. Subgroup / "Create Channel" mode now, or group creation only?