* docs: add multi-account experience design spec
research document exploring multi-account UX for users with multiple
ATProto identities. covers OAuth prompt parameter support, session
groups architecture, and phased implementation plan.
๐ค Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* docs: add bluesky implementation study to multi-account spec
studied bluesky's open-source social-app to inform our design:
- session state patterns (accounts array, currentAccount reference)
- UX patterns (avatars, checkmarks, "logged out" labels)
- logout distinction (active only vs all)
- cross-tab sync approach
also updated prerequisite section with link to SDK fork PR #8.
๐ค Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* chore: update atproto SDK with prompt parameter support
updates to d4830f4 which adds PromptType and prompt parameter
to start_authorization() for multi-account flows.
๐ค Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* docs: expand bluesky implementation study with detailed patterns
- added persistence layer details (AsyncStorage, schema structure)
- documented full SessionAccount interface with all fields
- listed reducer action types for state transitions
- added token refresh strategy comparison
- documented AccountList UI implementation details
- expanded references with direct links to key source files
๐ค Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat: implement multi-account experience
backend:
- add session groups (group_id, is_active, avatar_url columns)
- add pending_add_accounts table for OAuth flow tracking
- add /auth/add-account/start endpoint with prompt=login
- add /auth/switch-account endpoint to switch active session
- add /auth/logout-all endpoint to clear all linked accounts
- update /auth/me to return linked_accounts list
- update callback to handle add-account flow
frontend:
- add LinkedAccount type and update User type
- update UserMenu with account switcher dropdown
- update ProfileMenu with accounts sub-menu for mobile
- show avatars, handles, and switch/add/logout options
๐ค Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: add-account flow now prompts for new handle
the add-account endpoint was incorrectly using the current user's handle,
which locked the OAuth flow to the same account. now:
- backend: /auth/add-account/start requires a handle in the request body
- backend: validates that handle differs from current account
- frontend: shows inline handle input before starting OAuth flow
- frontend: same UX in UserMenu (desktop) and ProfileMenu (mobile)
๐ค Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: stop propagation on add account click
the click outside handler was closing the menu before the input could
appear because the button was being removed from DOM, causing
menuRef.contains(event.target) to return false.
๐ค Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: deactivate other sessions when adding new account
when a new account is added to a session group, only the new session
should be marked as active. the frontend filters for !is_active to show
switchable accounts, so both being active meant neither showed up.
๐ค Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: filter other accounts by DID, not is_active flag
the cookie determines which session is active - we don't need a separate
is_active flag. just filter out the current user's DID to show other
accounts in the switch list.
๐ค Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: remove unnecessary is_active tracking
the cookie determines which session is active - tracking is_active
separately was redundant complexity.
- removed deactivate_other_sessions_in_group function
- simplified switch_active_account to just validate and return session_id
- switch-account endpoint now checks DID instead of is_active flag
๐ค Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: remove is_active from API response and types
is_active was never needed - the cookie determines the active session,
and we filter by DID in the frontend. removed from:
- LinkedAccountResponse model
- LinkedAccount TypeScript interface
- handleSwitchAccount checks
๐ค Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat: accounts submenu with avatars and logout-all fix
- backend: look up artist avatars in /auth/me for fresh data
- UserMenu: consolidate accounts into collapsible submenu
- ProfileMenu: show current user avatar
- fix: use window.location.href for logout-all to clear state
๐ค Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: improve multi-account UX
- use invalidateAll() instead of page reload when switching accounts
(prevents "log in" flash during account switch)
- add logout prompt for multi-account users to stay logged in as
another account instead of fully logging out
- add switch_to param to /logout for atomic logout + switch
- fix add-account validation to check ALL accounts in session group
- fix dropdown width (220px) to prevent horizontal expansion
- use HandleAutocomplete in add-account forms
- fix a11y warning in portal page (label โ span)
- remove unused .desktop-nav CSS
๐ค Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: prevent logout button click from closing menu before prompt shows
when clicking logout with multiple accounts, the DOM update that shows
the logout prompt was removing the logout button from the DOM before
the click-outside handler checked containment, causing the menu to close.
added event.stopPropagation() to handleLogoutClick in both UserMenu and
ProfileMenu to prevent the click from bubbling to the document listener.
๐ค Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat: convert logout prompt to centered modal
- Create global LogoutState class using Svelte 5 runes
- Add LogoutModal component rendered at root layout level
- Update UserMenu and ProfileMenu to use global logout state
- Modal escapes header's backdrop-filter containing block
- Fix click-outside race condition with stopPropagation
๐ค Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* test: add multi-account session management tests
covers session groups, account switching, removal, and pending add-account flow.
๐ค Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: remove is_active and avatar_url from session model
these columns were adding complexity without value:
- is_active: cookie is the source of truth for active session
- avatar_url: /auth/me already fetches fresh from Artist table
simplifies remove_account_from_group to just return first remaining session.
removes unused update_session_avatar function.
๐ค Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* migrate: drop is_active and avatar_url from user_sessions
these columns were added to dev database in earlier development
but the model was simplified before merge. this migration aligns
the database schema with the final model.
๐ค Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: make column drop migration idempotent
the columns were only added to dev database during development.
staging and prod never had them. this migration now checks if
columns exist before attempting to drop them.
๐ค Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>