Up to MAX_PARALLEL_ACCOUNTS = 5 accounts stay signed in on one browser.
Each browser tab is pinned to one account, so two accounts can be used side by side. These are
the states the accounts section actually has — not just the happy two-account case.
Fail-closed: the control is disabled and explains itself, rather than accepting the sign-in and silently evicting an account.
Switching to Ada (Consulting)…
A switch is a full document reload, not a state update. React state, the app-registry cache, the flag cache, mounted federated remotes and any open stream all die with the document — nothing survives to be read under the wrong identity.
Each row carries its own sign-out. Removing an account revokes its session server-side and erases its entire storage namespace — token, session id, cached user, and its active-organization choice.
A parked account can expire while another is in use. The roster keeps the entry so the user is told what happened.
| Key | Store | Scope |
|---|---|---|
ff.accounts | localStorage | Roster: id, email, display name, added-at. No tokens. |
ff.activeAccountId | localStorage | Browser-wide default for a new tab. |
ff.activeAccountId | sessionStorage | Per tab. Wins over the default — this is what makes accounts parallel. |
ff.acct.<id>.authToken | localStorage | Per account. |
ff.acct.<id>.sessionId | localStorage | Per account. |
ff.acct.<id>.user | localStorage | Per account. |
ff.acct.<id>.activeOrganizationId | localStorage | Per account — org choice never crosses accounts. |
authToken/sessionId/user/
ff.activeOrganizationId keys are migrated into the active namespace on first
load and then deleted, so a stale global key cannot shadow a namespaced one.[data-case] · [data-account-count] ·
[data-guard='max-accounts'] · [data-action='sign-out-account'] ·
[data-account-state='expired'] · [data-state='switching'].