cmux NEXT: see and accept team invitations in Settings > Accounts

Merged
TE
1d ago
+2006-6

Leo-ask: ask-6ef2624788

Leo, routed as "Invite teammates by account (Chief's second pick; also Leo's ask-ed33ccaa7a on multiplayer and sharing)". Leo's ask-ed33ccaa7a: "how's the state of multiplayer/inviting teammates/inviting people to see your chats/sharing?"

What was missing

Inviting a teammate by email already works end to end from NEXT (Home's Invite to Team, home.inviteToTeam, POST /api/teams/:id/invitations, which emails the invitation). The invitee could only accept on cmux.com, though: NEXT never showed an invitation addressed to you.

What this changes

Settings > Accounts now has Invitations to You above the team list: each team that invited one of your verified emails, who invited you, the role and the expiry, with Join Team and Decline. Join Team re-reads the team list and makes the joined team active (the server already saves it as the account's selection), so the team's Cloud machines are the ones cmux lists next. An invitation answered elsewhere (another device, the emailed link) says why and leaves the list.

  • CmuxAuthRuntime: CmuxTeamsAPI.receivedInvitations(), acceptInvitation(id:), declineInvitation(id:) over the team API's existing routes (GET /api/teams/invitations, POST /api/teams/invitations/:id/accept|decline; web/services/teams/received.ts), which take the signed-in user's verified email as the proof. No backend change.
  • CMUXAuthCore: CMUXReceivedTeamInvitation, CMUXTeamJoin, and three CMUXTeamError cases: invitationInvalid (410 invitation_invalid, 404 invitation_not_found), emailMismatch (409), seatLimit (409).
  • AuthCoordinator: receivedTeamInvitations(), acceptTeamInvitation(id:) (refreshes the team list, then selects the joined team), declineTeamInvitation(id:).
  • CloudAuth and AccountsTeamBackend: the same three; AccountsTeamsModel reads the invitations with the roles and runs team.accept / team.decline (the invitation id goes as team).
  • Webview: AccountsTeams.tsx draws the group from the host's texts; mock host and two gallery states (accounts-team-invited, accounts-team-joined).
  • Strings: 10 new keys in CmuxNextAccounts' catalog, in all 21 locales (most of the line count).
  • What's New highlight: team-invitations-in-settings.md.

The logic is the thin client of an existing HTTP API inside the existing Swift auth stack (CmuxAuthRuntime is shared with iOS), so it stays there rather than moving to Rust.

Verification

  • cmux-ci run --class light --script scripts/ci/package-test-lane.sh --arg suite --arg Packages/Shared/CmuxAuthRuntime --arg CmuxTeamsAPITests,AuthCoordinatorTeamManagementTests --ref 9de2d7cb48fb75cb4e99cc4b1cfc1f66ad3626d6: both suites pass (20 tests, 4 new: list/accept/decline wire, refusal mapping, coordinator join-and-activate, decline).
  • AccountsTeamManagementTests (CmuxNext, 3 new) and the webview check plus settings tests: see the comment below.
  • python3 scripts/cmux-next/prepush-gate.py: no new violations.

Observed / Not run

  • Observed: the fleet suites above.
  • Not run: a live accept against the dev backend with two accounts; the cloud-demo lane does that readback separately and posts it here.
  • iPhone parity: not in this PR; follow-up.

Changelog

Added: Settings > Accounts shows team invitations sent to your verified email, with Join Team and Decline.

No capture: webview UI; the gallery states accounts-team-invited and accounts-team-joined render it.

🤖 Generated with Claude Code

Agent session: agent session


Autofix with [code]smith
Open in [code]smith · Need help on this PR? Tag @codesmith-bot with what you need.


Summary by cubic

Settings > Accounts now lists team invitations sent to your verified email, with Join Team and Decline. Previously, invitees could only accept on cmux.com; now the joined team becomes active so its Cloud machines show up next.

New Features

  • Invitations to You shows the inviting team, inviter, role, and expiry above the team list.
  • Join Team re-reads the team list and makes the joined team active; Decline removes the invitation.
  • Invitations answered elsewhere leave the list with a reason, covered by the new invitationInvalid, emailMismatch, and seatLimit errors.