Ownership transfer is one reviewed decision over two versioned membership rows. The browser freezes the displayed current-owner version and target-member version, while the server remains responsible for authorization, tenant scope, concurrency, and the atomic role swap.
Capture a complete snapshot
The action is available only when exactly one current-user row is an owner and the target is another ordinary member. Selecting it captures the target ID and email plus both versions. Refresh clears that pending snapshot before loading, so newer roster data can never be paired with an older confirmation.
The confirmation names the recipient and both consequences: the target becomes owner, while the caller becomes a member and loses member administration. One pending-action union keeps removal and transfer mutually exclusive.
Decode a finite HTTP contract
The adapter sends the encoded target route and exactly expectedOwnerVersion plus expectedTargetVersion. It accepts only status-specific outcomes. Bad Request maps to Invalid only for invalid_transfer. Conflict maps to Stale only for stale_version. A malformed body, duplicate or extra property, mismatched code, unexpected status, transport error, or timeout-like cancellation becomes Failure. Caller-requested cancellation propagates.
Never replay a consequential command
Known Stale and Not Found outcomes clear the confirmation and perform one no-store roster reload. The owner must inspect and choose again. Invalid and ambiguous Failure outcomes keep the last roster only as untrusted context, disable all mutations, focus an alert, and require manual refresh. Unauthorized and Forbidden clear private data.
The submitting guard is set synchronously before the adapter is awaited. Every mutation control is disabled while the request is in flight, and a bUnit test holds the response open to prove rapid activation still dispatches only one command.
Make confirmed access loss a terminal success
After 204 No Content, the former owner no longer has owner authorization. Automatically refreshing the owner roster would predictably return Forbidden and could overwrite the authoritative success message. The component therefore clears the private roster, retires owner controls, names the new owner, and enters a terminal reauthentication state without another GET. Renewing authentication updates stale claims; it does not restore the former owner's permission.
The tests prove the wire contract, frozen dual versions, confirmation semantics, refresh invalidation, mutual exclusion, conflict recovery without replay, ambiguous failure locking, access clearing, accessible focus, and zero follow-up roster requests after success. Existing integration tests continue to own the atomic server guarantee.
Top comments (0)