diff --git a/nym-wallet/openspec/changes/node-families-wallet/design.md b/nym-wallet/openspec/changes/node-families-wallet/design.md index bba04fd82c..445b2218c7 100644 --- a/nym-wallet/openspec/changes/node-families-wallet/design.md +++ b/nym-wallet/openspec/changes/node-families-wallet/design.md @@ -42,20 +42,26 @@ The mock lives in `src/context/mocks/families.tsx` with fixtures in a co-located ### D4: Member-status derivation The four UI statuses map to contract reads: **Pending** = `GetPendingInvitationsForFamilyPaged` (carry the `expired` flag), **Joined** = `GetFamilyMembersPaged`, **Rejected** = `GetPastInvitationsForFamilyPaged` filtered to `Rejected` status, **Removed** = `GetPastMembersForFamilyPaged` (left/kicked) plus `Revoked` past invitations where relevant. The derivation lives in a selector hook so both UI and tests share one definition. -### D5: Storybook three-level structure +### D5: Family tab is always visible; family key is standalone +The Family tab renders for **every** wallet account (not gated on owning a family or controlling a node), so any account can start a family: it shows the create entry point when the account owns no family and the management surface when it does. The family key produced on creation is a **standalone** key, modelled as an opaque value isolated behind the `createFamily`/`acceptFamilyInvitation` request boundary. + +### D6: Large lists are paginated via the contract's exclusive `start_after` cursor +Member lists and invitation archives use the contract's cursor pagination: each page passes `start_after` (exclusive) and reads `start_next_after` from the response to fetch the next page, with the contract's default limit of 50 (max 100). The TanStack Query read hooks expose this as incremental/infinite pagination; the mock honours the same cursor semantics so paging is exercised without a chain. + +### D7: Storybook three-level structure - **Components** (`src/components/families/*.stories.tsx`): each component with explicit state args (empty, loading, error, expired, over-limit, success). - **Pages** (`src/pages/families/*.stories.tsx`): composed surfaces (owner management page, operator invites page) backed by the mock provider. - **Flows** (`*.flow.stories.tsx`): play functions (`@storybook/test`) that perform the user actions end to end (create → invite → accept → kick → disband; operator: receive → accept/reject → leave). -### D6: Playwright runs against the static Storybook build +### D8: Playwright runs against the static Storybook build Because the production app is Tauri (not a plain web target), Playwright e2e specs run against `build-storybook` served statically, exercising the flow stories as real browser sessions. This gives deterministic, chain-free e2e without packaging Tauri. *Alternative considered:* `tauri-driver`/WebDriver against the native app — heavier, flaky in CI, and unnecessary since the contract layer is mocked anyway. -### D7: Creation fee and limits are read from chain config +### D9: Creation fee and limits are read from chain config The UI reads `create_family_fee`, `family_name_length_limit`, and `family_description_length_limit` from contract `Config` (mocked in fixtures), never hardcoding 100 NYM or character counts. Validation is byte-length based to match the contract. ## Risks / Trade-offs -- **[Family key / delegation not in contract spec]** → Model the family key as an opaque value in types/mocks and isolate it behind the `createFamily`/`acceptFamilyInvitation` boundary so a later contract decision (multisig vs standalone) changes only the request layer, not the UI. +- **[Family key / delegation not in contract spec]** → Model the standalone family key as an opaque value in types/mocks and isolate it behind the `createFamily`/`acceptFamilyInvitation` boundary, so a later contract decision changes only the request layer, not the UI. - **[No `UpdateFamily` edit handler in contract spec]** → Build the edit UI + mock path now; gate real submission behind a feature check so it is dark until the contract adds the handler. - **[Status derivation from archives is subtle]** (Rejected vs Revoked vs Removed) → Centralize in one selector hook with unit tests covering each archive→status mapping. - **[Playwright-vs-Tauri divergence]** → Storybook flows test UI logic against mocks, not the real IPC bridge; a thin set of manual/native smoke checks should still cover Tauri wiring before release. @@ -63,12 +69,11 @@ The UI reads `create_family_fee`, `family_name_length_limit`, and `family_descri ## Migration Plan -Additive only — new tab, context, requests, types, stories, tests. No existing wallet behavior changes. Rollout can be gated by the Family-tab eligibility check (and a feature flag for the contract-dependent edit/key paths) so the surface ships dark until the contract dependencies land. Rollback is removal of the tab entry point. +Additive only — new tab, context, requests, types, stories, tests. No existing wallet behavior changes. The Family tab is always visible, so rollout is gated only by a feature flag for the contract-dependent edit/key paths (which ship dark until the contract dependencies land). Rollback is removal of the tab entry point. ## Open Questions -- Family key: multisig or standalone? (NYM-1210 "per Discovery decision".) Determines the create/accept request shape. - Will the contract add an `UpdateFamily` handler for NYM-1211 edits, and what is its message shape / auth? -- Exact eligibility rule for showing the Family tab (owns a family OR controls a bonded node — confirm). - Figma file/frame URLs for each component and page (to be supplied at apply time via Figma MCP). -- Pagination/refresh strategy for large member lists and invitation archives (cursor-based per the contract's `start_after`). + +_Resolved:_ family key is **standalone**; the Family tab is **always visible**; large lists are **paginated via the contract's `start_after` cursor** (default 50, max 100). diff --git a/nym-wallet/openspec/changes/node-families-wallet/proposal.md b/nym-wallet/openspec/changes/node-families-wallet/proposal.md index 90ac82e9aa..3433f2ba9f 100644 --- a/nym-wallet/openspec/changes/node-families-wallet/proposal.md +++ b/nym-wallet/openspec/changes/node-families-wallet/proposal.md @@ -5,7 +5,7 @@ Node Families is a new on-chain capability (see the `node-families-contract` spe ## What Changes **Family owner flows (NYM-1210–1215)** -- New **Family Tab** visible to eligible users, with create-family entry point. +- New **Family Tab**, always visible to any wallet account: shows a create-family entry point when the account owns no family, and the family management surface when it does. - Create a family: attach the configured creation fee (`Config::create_family_fee`), set name + description, surface insufficient-balance and fee errors. - Add/edit family **name** and **description**, with byte-length limits and input sanitisation, inline over-limit errors. - Invite a node by **node ID**: triggers the contract invite (nonce/TTL via `validity_secs`), with confirmation; warns and does not send if the node is already in a family, does not exist, or already has a pending invite from this family. @@ -27,7 +27,7 @@ Node Families is a new on-chain capability (see the `node-families-contract` spe - UI implemented from **Figma** (designs supplied via Figma MCP during apply). **Contract dependencies — NOT covered by the current `node-families-contract` spec** (see Impact; flagged for resolution): -- **Family key / multisig / delegation of node control** (NYM-1210, NYM-1217): the contract spec models ownership as `info.sender` and acceptance as a membership record only — there is no key-generation or control-delegation mechanism. NYM-1210 marks this "per Discovery decision." +- **Family key (standalone) / delegation of node control** (NYM-1210, NYM-1217): the contract spec models ownership as `info.sender` and acceptance as a membership record only — there is no key-generation or control-delegation mechanism. The family key is a **standalone** key. - **Edit name/description after creation** (NYM-1211): the contract spec has `CreateFamily` (carries name/description) and `UpdateConfig` (admin) but **no `UpdateFamily` edit handler**. ## Capabilities diff --git a/nym-wallet/openspec/changes/node-families-wallet/specs/node-families-owner/spec.md b/nym-wallet/openspec/changes/node-families-wallet/specs/node-families-owner/spec.md index 47d0c73347..3def489a87 100644 --- a/nym-wallet/openspec/changes/node-families-wallet/specs/node-families-owner/spec.md +++ b/nym-wallet/openspec/changes/node-families-wallet/specs/node-families-owner/spec.md @@ -1,12 +1,12 @@ ## ADDED Requirements -### Requirement: Family Tab is visible to eligible users and exposes a create entry point +### Requirement: Family Tab is always visible and exposes create or management based on ownership -The wallet SHALL display a **Family** tab to eligible users (an account whose connected address either owns a family or controls a bonded node). The tab SHALL present a create-family entry point when the connected address does not already own a family. When the address already owns a family, the tab SHALL show the family management surface instead of the create entry point. +The wallet SHALL display the **Family** tab for every connected wallet account, regardless of whether the account owns a family or controls a bonded node, so that any account can start a new family. When the connected address does not own a family, the tab SHALL present a create-family entry point. When the address already owns a family, the tab SHALL show the family management surface instead of the create entry point. -#### Scenario: Eligible user without a family sees the create entry point -- **WHEN** an eligible user opens the Family tab and their address owns no family -- **THEN** the tab renders a "Create family" entry point +#### Scenario: Account without a family sees the create entry point +- **WHEN** any connected account opens the Family tab and its address owns no family +- **THEN** the tab is shown and renders a "Create family" entry point #### Scenario: Owner sees management surface instead of create - **WHEN** the connected address already owns a family @@ -28,13 +28,13 @@ The wallet SHALL allow an eligible user to create a family by submitting a name - **WHEN** creation fails with `InvalidFamilyCreationFee` or `InvalidDeposit` - **THEN** the wallet shows a clear fee error and the family is not created -### Requirement: Family key is generated on creation +### Requirement: A standalone family key is generated on creation -On successful family creation the wallet SHALL generate a family key (multisig or standalone, per the Discovery decision) and present it to the owner. The exact key mechanism depends on the `node-families-contract` adding key/delegation support; until then the wallet SHALL treat the family key as an opaque value backed by mocked behavior. +On successful family creation the wallet SHALL generate a **standalone** family key and present it to the owner. The exact key mechanism depends on the `node-families-contract` adding key/delegation support; until then the wallet SHALL treat the family key as an opaque value backed by mocked behavior. -#### Scenario: Family key presented on creation +#### Scenario: Standalone family key presented on creation - **WHEN** a family is created successfully -- **THEN** the wallet generates and displays the associated family key to the owner +- **THEN** the wallet generates and displays the associated standalone family key to the owner ### Requirement: Family owner can add and edit the family name and description @@ -94,7 +94,11 @@ The wallet SHALL list the family's pending invitations with their expiry state ( ### Requirement: Family owner can view the member list grouped by status -The wallet SHALL display all nodes associated with the family grouped into four statuses: **Pending** (active pending invitations), **Joined** (current members), **Rejected** (invitations the node declined), and **Removed** (members that left or were kicked). The list SHALL refresh to reflect current contract state and SHALL render a per-status empty state when a group has no entries. Statuses are derived from the contract queries: pending invitations, current members, and the past-invitation / past-member archives. +The wallet SHALL display all nodes associated with the family grouped into four statuses: **Pending** (active pending invitations), **Joined** (current members), **Rejected** (invitations the node declined), and **Removed** (members that left or were kicked). The list SHALL refresh to reflect current contract state and SHALL render a per-status empty state when a group has no entries. Statuses are derived from the contract queries: pending invitations, current members, and the past-invitation / past-member archives. Large lists SHALL be paginated using the contract's exclusive `start_after` cursor (default page size 50, max 100), fetching subsequent pages via the returned `start_next_after`. + +#### Scenario: Large member list is paginated by cursor +- **WHEN** a status group has more entries than one page +- **THEN** the wallet fetches additional pages using `start_after`/`start_next_after` rather than loading the whole list at once #### Scenario: Members are grouped by status - **WHEN** the owner opens the member list diff --git a/nym-wallet/openspec/changes/node-families-wallet/tasks.md b/nym-wallet/openspec/changes/node-families-wallet/tasks.md index 2490fdd489..9b11bde71b 100644 --- a/nym-wallet/openspec/changes/node-families-wallet/tasks.md +++ b/nym-wallet/openspec/changes/node-families-wallet/tasks.md @@ -1,7 +1,7 @@ ## 1. Types & request bindings - [ ] 1.1 Add TS types in `src/types` for `NodeFamily`, `FamilyMembership`, `FamilyInvitation`, `PendingFamilyInvitationDetails`, `PastFamilyInvitation` (with status), `PastFamilyMember`, and contract `Config` (fee + limits) -- [ ] 1.2 Add an opaque `FamilyKey` type and create/accept request args that carry it (isolated so the multisig-vs-standalone decision changes only this layer) +- [ ] 1.2 Add a standalone `FamilyKey` type (opaque value) and create/accept request args that carry it, isolated behind the request layer - [ ] 1.3 Add `src/requests/families.ts` Tauri IPC bindings for execute msgs: createFamily, updateFamily (edit, dark/gated), disbandFamily, inviteToFamily, revokeFamilyInvitation, kickFromFamily, acceptFamilyInvitation, rejectFamilyInvitation, leaveFamily - [ ] 1.4 Add query bindings: getFamilyByOwner, getFamilyMembership, family members paged, pending invitations for family/node paged, past invitations for family paged, past members for family paged, config - [ ] 1.5 Export new requests from `src/requests/index.ts` @@ -45,7 +45,7 @@ ## 6. Family Tab & pages -- [ ] 6.1 Add the Family tab entry point with eligibility gating (owns a family OR controls a bonded node) +- [ ] 6.1 Add the Family tab, always visible for every wallet account (no eligibility gating) - [ ] 6.2 Owner management page composing components from section 4 - [ ] 6.3 Operator invites page composing components from section 5 - [ ] 6.4 Route between create entry point and management surface based on ownership