adopt two-step NIP-29 group creation with confirmation
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
# 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?
|
||||
Reference in New Issue
Block a user