messaging: cannot create a group chat from an empty contact list (bootstrap UX) #191

Open
opened 2026-04-29 13:25:57 +00:00 by rawdaGastan · 1 comment
Member

Problem

The New Chat form only switches to group mode (with a Group Name field) when 2+ participants are selected — see islands/chat_form.rs:87. With 0 or 1 contacts in the system, the user cannot reach group mode at all, so the group-chat code path is unreachable through the UI.

This bites in fresh test environments / new installs (we hit it during QA — only one seeded contact, no way to exercise the group flow).

Suggested fix

Allow entering a group title as soon as the form is opened; let the user create a group with 1 selected participant (or 0 — a self-group placeholder) — the title field would be visible from the start. Or expose an explicit 'Create Group' entry alongside '+ New Chat' that doesn't depend on selection count.

Found via QA session 2026-04-29.

## Problem The New Chat form only switches to group mode (with a Group Name field) when 2+ participants are selected — see `islands/chat_form.rs:87`. With 0 or 1 contacts in the system, the user cannot reach group mode at all, so the group-chat code path is unreachable through the UI. This bites in fresh test environments / new installs (we hit it during QA — only one seeded contact, no way to exercise the group flow). ## Suggested fix Allow entering a group title as soon as the form is opened; let the user create a group with 1 selected participant (or 0 — a self-group placeholder) — the title field would be visible from the start. Or expose an explicit 'Create Group' entry alongside '+ New Chat' that doesn't depend on selection count. Found via QA session 2026-04-29.
rawdaGastan added this to the ACTIVE project 2026-05-07 12:17:02 +00:00
Author
Member

Implementation Spec for Issue #191

Objective

Make the group-chat creation path reachable in the New Chat form when the user has 0 or 1 contacts in the system, by decoupling the visibility of the Group Name field and the DM-vs-group decision from the selected-participants count. Today the title field is gated by selected_contacts.len() > 1 (islands/chat_form.rs:87), so a fresh install with zero or one contacts cannot exercise the group code path through the UI.

Requirements

  • The Group Name (title) field must be reachable from the moment the New Chat form is opened, regardless of how many contacts exist or are selected.
  • The user must be able to explicitly opt into "group" mode and create a group with 1 selected participant. Empty-participant submission stays blocked with a clear message — we do not introduce a "self-group" placeholder concept (no backend support exists for it; services::create_group always takes a non-empty participant_keys: Vec<String>).
  • The DM (1:1) path must continue to work unchanged: selecting a single contact and clicking Create without entering a group title still produces a DM via get_or_create_dm.
  • The on_create callback signature must convey the user's mode choice to the archipelago so the existing participant_keys.len() == 1 heuristic in archipelago.rs:582 no longer silently turns a 1-participant group into a DM.
  • Change is internal to chat_form.rs and archipelago.rs. No service-layer or SDK changes.
  • Compiles cleanly: cargo check -p hero_archipelagos_messaging.

Files to Modify/Create

  • archipelagos/messaging/src/islands/chat_form.rs — Always render the Group Name field; add a "Create as group" toggle (checkbox); change on_create to also carry an is_group bool; update can_create so a group needs at least 1 participant + non-empty title; show a contacts-empty hint.
  • archipelagos/messaging/src/archipelago.rs — Update handle_create (line ~574) to honor the new is_group flag instead of inferring mode from participant_keys.len(). Both desktop (~748) and mobile (~806) ChatForm { ... on_create: handle_create } call sites pick up the new signature automatically.

Implementation Plan

Step 1: Extend ChatForm so the Group Name field is always visible and the user picks DM vs Group explicitly

Files: archipelagos/messaging/src/islands/chat_form.rs

  • Change ChatFormProps::on_create from EventHandler<(String, Vec<String>)> to EventHandler<(String, Vec<String>, bool)>. Update its doc comment.
  • Add a new local signal is_group = use_signal(|| false) near the existing form signals.
  • Replace the if selected_contacts.read().len() > 1 { … } block at lines 87-99 with two always-visible UI elements at the top of .new-chat-view:
    1. A "Create as group" toggle row (label + <input type="checkbox">) bound to is_group. Add a new .new-chat-toggle style.
    2. The existing Group Name field, but rendered conditionally on *is_group.read() instead of selected_contacts.read().len() > 1. Keep the same .new-chat-field / .new-chat-input classes.
  • Auto-promote convenience: when selected_contacts.read().len() >= 2, force is_group to true (write only when value actually changes, using peek()-then-set pattern from line 59-61, to avoid a re-render loop). Do not auto-demote on shrink — once the user opted in, keep their choice.
  • Update can_create (line 70):
    • DM mode (!*is_group.read()): exactly 1 selected participant, not creating.
    • Group mode (*is_group.read()): at least 1 selected participant AND a non-empty trimmed title, not creating.
    • 0 participants always blocks Create.
  • Update handle_create (lines 72-80) to call props.on_create.call((title_val, keys, *is_group.read())).
  • Show a contacts-empty hint underneath the toggle when props.contacts.is_empty()"You have no contacts yet — add a contact to start a chat or group." Add a .new-chat-hint style.
  • Update the doc comment (line 86) to match the new behavior.

Dependencies: none.

Step 2: Wire the new is_group flag through the archipelago's handle_create

Files: archipelagos/messaging/src/archipelago.rs

  • Update handle_create at line 574 from move |(title, participant_keys): (String, Vec<String>)| to move |(title, participant_keys, is_group): (String, Vec<String>, bool)|.
  • Replace the dispatch logic (lines 582-597) with explicit branching on is_group:
    • if !is_group && participant_keys.len() == 1services::get_or_create_dm(...) (unchanged DM path).
    • else if is_group && !participant_keys.is_empty()services::create_group(&osis_url, &context_name, &name, participant_keys), where name is title if non-empty else "New Chat". This now also fires for 1-participant groups (the bug fix).
    • else → set create_error to a clear validation message and creating.set(false). Defensive — the form's disabled Create button already prevents reaching this branch.
  • Both ChatForm call sites (desktop ~743-749, mobile ~801-807) pass the same handle_create, so no further wiring change.

Dependencies: Step 1 (the on_create signature change must land first or together).

Step 3: Verify build and flows

Files: none (validation only).

  • Run cargo check -p hero_archipelagos_messaging from the repo root and confirm a clean build.
  • Visually trace through the Acceptance Criteria — no behavioral regressions when 2+ contacts are selected (auto-promotion preserves the prior UX).
  • No testcases/ or unit-test directory exists under archipelagos/messaging, so no test file to extend.

Dependencies: Step 2.

Acceptance Criteria

  • User can open the New Chat form with 0 or 1 contacts and reach group mode (the toggle + Group Name field are always visible).
  • Creating a group with 1 selected participant works — the form passes is_group = true and the archipelago routes to services::create_group(...) rather than get_or_create_dm(...).
  • Creating a group with 0 participants is explicitly blocked: the Create button stays disabled and a contacts-empty hint is shown.
  • DM (1:1 chat) flow continues to work unchanged.
  • Group title field is reachable without needing 2+ selected participants — it appears as soon as the user toggles "Create as group".
  • No regressions in existing message-creation flow.
  • When the user selects 2+ participants, the toggle auto-flips to group mode (preserves prior implicit behavior).
  • Compiles cleanly: cargo check -p hero_archipelagos_messaging.

Notes

Trade-off (option a vs option b): This spec uses option (a) — always-visible Group Name field with an explicit toggle. It is one file's worth of UI logic plus a 3-line signature change in archipelago.rs, with no new top-level entry point. Option (b) (sibling "Create Group" button next to "+ New Chat") requires a new toolbar action, a new Route::NewGroup variant, and either a new GroupForm or a mode prop — materially more code and two near-identical entry points the user has to choose between.

The DM-vs-group decision also moves from implicit (count-based, fragile, unreachable in the empty-contacts case) to explicit (user-driven, deterministic), which removes a class of "I chose group but only added one contact and it silently became a DM" surprises.

Dioxus / signal conventions:

  • All form state in chat_form.rs is use_signal with closure-captured handles. The new is_group signal follows the same pattern.
  • The auto-promotion to group mode at 2+ participants must use peek() to compare before .set() to avoid an infinite re-render loop, mirroring the pattern at line 59-61.
  • The EventHandler<(String, Vec<String>, bool)> change is a breaking change to a private prop — both call sites in archipelago.rs are the only consumers, so it is safe to land in one pass.

Subtleties:

  • The placeholder name "New Chat" (set at archipelago.rs:590, mirrored at services::messaging_service::NEW_CHAT_PLACEHOLDER:267) becomes defensive only — keep for safety.
  • The new validation-error branch must call creating.set(false) and create_error.set(Some(...)) to mirror existing error handling and avoid leaving the Create button stuck in the loading state.
  • Single-participant groups are valid in the backend: services::create_group accepts any non-empty Vec<String>. No server-side change required.
## Implementation Spec for Issue #191 ### Objective Make the group-chat creation path reachable in the New Chat form when the user has 0 or 1 contacts in the system, by decoupling the visibility of the Group Name field and the DM-vs-group decision from the selected-participants count. Today the title field is gated by `selected_contacts.len() > 1` (`islands/chat_form.rs:87`), so a fresh install with zero or one contacts cannot exercise the group code path through the UI. ### Requirements - The Group Name (title) field must be reachable from the moment the New Chat form is opened, regardless of how many contacts exist or are selected. - The user must be able to explicitly opt into "group" mode and create a group with 1 selected participant. Empty-participant submission stays blocked with a clear message — we do not introduce a "self-group" placeholder concept (no backend support exists for it; `services::create_group` always takes a non-empty `participant_keys: Vec<String>`). - The DM (1:1) path must continue to work unchanged: selecting a single contact and clicking Create without entering a group title still produces a DM via `get_or_create_dm`. - The `on_create` callback signature must convey the user's mode choice to the archipelago so the existing `participant_keys.len() == 1` heuristic in `archipelago.rs:582` no longer silently turns a 1-participant group into a DM. - Change is internal to `chat_form.rs` and `archipelago.rs`. No service-layer or SDK changes. - Compiles cleanly: `cargo check -p hero_archipelagos_messaging`. ### Files to Modify/Create - `archipelagos/messaging/src/islands/chat_form.rs` — Always render the Group Name field; add a "Create as group" toggle (checkbox); change `on_create` to also carry an `is_group` bool; update `can_create` so a group needs at least 1 participant + non-empty title; show a contacts-empty hint. - `archipelagos/messaging/src/archipelago.rs` — Update `handle_create` (line ~574) to honor the new `is_group` flag instead of inferring mode from `participant_keys.len()`. Both desktop (~748) and mobile (~806) `ChatForm { ... on_create: handle_create }` call sites pick up the new signature automatically. ### Implementation Plan #### Step 1: Extend `ChatForm` so the Group Name field is always visible and the user picks DM vs Group explicitly Files: `archipelagos/messaging/src/islands/chat_form.rs` - Change `ChatFormProps::on_create` from `EventHandler<(String, Vec<String>)>` to `EventHandler<(String, Vec<String>, bool)>`. Update its doc comment. - Add a new local signal `is_group = use_signal(|| false)` near the existing form signals. - Replace the `if selected_contacts.read().len() > 1 { … }` block at lines 87-99 with two always-visible UI elements at the top of `.new-chat-view`: 1. A "Create as group" toggle row (label + `<input type="checkbox">`) bound to `is_group`. Add a new `.new-chat-toggle` style. 2. The existing Group Name field, but rendered conditionally on `*is_group.read()` instead of `selected_contacts.read().len() > 1`. Keep the same `.new-chat-field` / `.new-chat-input` classes. - Auto-promote convenience: when `selected_contacts.read().len() >= 2`, force `is_group` to `true` (write only when value actually changes, using `peek()`-then-`set` pattern from line 59-61, to avoid a re-render loop). Do not auto-demote on shrink — once the user opted in, keep their choice. - Update `can_create` (line 70): - DM mode (`!*is_group.read()`): exactly 1 selected participant, not creating. - Group mode (`*is_group.read()`): at least 1 selected participant AND a non-empty trimmed title, not creating. - 0 participants always blocks Create. - Update `handle_create` (lines 72-80) to call `props.on_create.call((title_val, keys, *is_group.read()))`. - Show a contacts-empty hint underneath the toggle when `props.contacts.is_empty()` — `"You have no contacts yet — add a contact to start a chat or group."` Add a `.new-chat-hint` style. - Update the doc comment (line 86) to match the new behavior. Dependencies: none. #### Step 2: Wire the new `is_group` flag through the archipelago's `handle_create` Files: `archipelagos/messaging/src/archipelago.rs` - Update `handle_create` at line 574 from `move |(title, participant_keys): (String, Vec<String>)|` to `move |(title, participant_keys, is_group): (String, Vec<String>, bool)|`. - Replace the dispatch logic (lines 582-597) with explicit branching on `is_group`: - `if !is_group && participant_keys.len() == 1` → `services::get_or_create_dm(...)` (unchanged DM path). - `else if is_group && !participant_keys.is_empty()` → `services::create_group(&osis_url, &context_name, &name, participant_keys)`, where `name` is `title` if non-empty else `"New Chat"`. This now also fires for 1-participant groups (the bug fix). - `else` → set `create_error` to a clear validation message and `creating.set(false)`. Defensive — the form's `disabled` Create button already prevents reaching this branch. - Both `ChatForm` call sites (desktop ~743-749, mobile ~801-807) pass the same `handle_create`, so no further wiring change. Dependencies: Step 1 (the `on_create` signature change must land first or together). #### Step 3: Verify build and flows Files: none (validation only). - Run `cargo check -p hero_archipelagos_messaging` from the repo root and confirm a clean build. - Visually trace through the Acceptance Criteria — no behavioral regressions when 2+ contacts are selected (auto-promotion preserves the prior UX). - No `testcases/` or unit-test directory exists under `archipelagos/messaging`, so no test file to extend. Dependencies: Step 2. ### Acceptance Criteria - [ ] User can open the New Chat form with 0 or 1 contacts and reach group mode (the toggle + Group Name field are always visible). - [ ] Creating a group with 1 selected participant works — the form passes `is_group = true` and the archipelago routes to `services::create_group(...)` rather than `get_or_create_dm(...)`. - [ ] Creating a group with 0 participants is explicitly blocked: the Create button stays disabled and a contacts-empty hint is shown. - [ ] DM (1:1 chat) flow continues to work unchanged. - [ ] Group title field is reachable without needing 2+ selected participants — it appears as soon as the user toggles "Create as group". - [ ] No regressions in existing message-creation flow. - [ ] When the user selects 2+ participants, the toggle auto-flips to group mode (preserves prior implicit behavior). - [ ] Compiles cleanly: `cargo check -p hero_archipelagos_messaging`. ### Notes **Trade-off (option a vs option b):** This spec uses option (a) — always-visible Group Name field with an explicit toggle. It is one file's worth of UI logic plus a 3-line signature change in `archipelago.rs`, with no new top-level entry point. Option (b) (sibling "Create Group" button next to "+ New Chat") requires a new toolbar action, a new `Route::NewGroup` variant, and either a new `GroupForm` or a `mode` prop — materially more code and two near-identical entry points the user has to choose between. The DM-vs-group decision also moves from implicit (count-based, fragile, unreachable in the empty-contacts case) to explicit (user-driven, deterministic), which removes a class of "I chose group but only added one contact and it silently became a DM" surprises. **Dioxus / signal conventions:** - All form state in `chat_form.rs` is `use_signal` with closure-captured handles. The new `is_group` signal follows the same pattern. - The auto-promotion to group mode at 2+ participants must use `peek()` to compare before `.set()` to avoid an infinite re-render loop, mirroring the pattern at line 59-61. - The `EventHandler<(String, Vec<String>, bool)>` change is a breaking change to a private prop — both call sites in `archipelago.rs` are the only consumers, so it is safe to land in one pass. **Subtleties:** - The placeholder name `"New Chat"` (set at `archipelago.rs:590`, mirrored at `services::messaging_service::NEW_CHAT_PLACEHOLDER:267`) becomes defensive only — keep for safety. - The new validation-error branch must call `creating.set(false)` and `create_error.set(Some(...))` to mirror existing error handling and avoid leaving the Create button stuck in the loading state. - Single-participant groups are valid in the backend: `services::create_group` accepts any non-empty `Vec<String>`. No server-side change required.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
lhumina_code/hero_archipelagos#191
No description provided.