# 02 — User journeys (how it is supposed to feel) Each journey: **steps the user takes**, **what should happen**, **failure symptoms**, **where to dig**. --- ## J1 — First-time home login **User steps** 1. Open app → login/register. 2. Pick (or accept default) signal server. 3. Register or log in with username/password. 4. Land on dashboard / saved servers. **Supposed to happen** - Session token stored for that URL (`metoyou.authTokens` + credential store). - Local user profile scoped in DB; NgRx `currentUser` set with `homeSignalServerUrl`. - **Provision secret** created and persisted (Electron safeStorage / web sessionStorage). - Signing public key registered on **home** server when possible. - WebSocket connects, `identify` with home token + `clientInstanceId`, ready for joins. **Broken looks like** - Stuck on login; bounce back after “success”. - Dashboard with no token → later `SESSION_EXPIRED`. - Later foreign joins always open authorize (missing secret). **Code** - `domains/authentication/` (`AuthenticationService`, login/register UI) - `store/users/users.effects.ts` (`authenticateUser`, `prepareAuthenticatedUserStorage`, `ensureHomeProvisionSecret`) - Feature: `agents-docs/features/authentication.md` --- ## J2 — Restart app still logged in **User steps** 1. Quit Electron fully; reopen. 2. Expect same user without typing password. **Supposed to happen** - Load user from local DB + valid home token (credential store or legacy token fallback). - Identify on home (and later foreign) sockets. - Opportunistic `ensureProvisioned` for active endpoints **without** login UI. **Broken looks like** - Flash of dashboard then `/login`. - Profile restored but chat/presence dead (“alone”). - Foreign rooms immediately open `/login?mode=authorize`. **Code** - `loadCurrentUser$`, `hasValidPersistedSession`, `migrateHomeCredential` - Lessons: identify legacy token fallback; persisted user still needs token --- ## J3 — Join a community on the *same* signal server **User steps** 1. Discover or invite → Join. 2. Open a text channel; send “hello”. 3. Join a voice channel; talk. **Supposed to happen** - REST join with bearer; then WS `join_server`. - Receive `server_users` / `user_joined`; peer mesh forms. - Text: local add + DC `chat-message` + WS `chat_message` fallback. - Voice: `voice_state` broadcast; WebRTC offer/answer; same-channel audio routing. **Broken looks like** - Joined in UI but not in others’ member lists (identify/join race). - Messages only on sender device. - Voice tile appears, no audio. --- ## J4 — Join a community on a *different* signal server (critical) **User steps** 1. Already logged into home Signal A. 2. Open invite / browser card whose `sourceUrl` is Signal B. 3. Join and chat/voice. **Supposed to happen** 1. `ensureCredentialForServerUrl(B)` → silent provision (or reuse credential). 2. REST + WS use **actor id on B**, not home id. 3. No authorize login page. 4. Peers on B see actor display name (may show `#prefix` disambiguation). 5. Voice/chat use B’s WebSocket for that room’s affinity. **Broken looks like** - Redirect to `/login?mode=authorize&serverId=…` while user bar still shows logged in. - Join “succeeds” locally but invisible on B. - Can see members but WebRTC never connects (initiator/identity mismatch). - DMs/calls to that person later miss rings or fork conversations. **Code** - `SignalServerAuthorizeService`, `SignalServerProvisionerService`, `room-signaling-connection.ts` - Story: `agents-docs/user-stories/silent-cross-signal-server-auth.md` - Pack: `04-auth-login-bugs.md`, `09-identity-cross-signal.md` --- ## J5 — Send a text message (server channel) **User steps** 1. In text channel, type and send. 2. Peer in same room should see it live; late joiner should catch up after connecting. **Supposed to happen** - Optimistic local message with stable id (attachments bind to that id). - Broadcast on data channel; also relay `chat_message` on signaling for peers without DC. - Edits/deletes primarily P2P (+ `account_sync` to sibling devices). - On peer connect: inventory ↔ sync-batch (up to 20k recent msgs, chunks of 200). **Broken looks like** - Sender sees it; others don’t (no presence / no join / DC+fallback both fail). - Others see live but not history (DC inventory never ran). - Multi-device: one device has history, another empty (`account_sync` / identify). - Attachments “Waiting for image…” forever (announce vs message ordering). **Pack:** `08-messaging-visibility.md` --- ## J6 — Join voice in a channel **User steps** 1. Click a voice channel. 2. Grant mic if prompted. 3. Hear others; they hear you; speaking indicators; optional camera/screen. **Supposed to happen** - Leave any previous voice/call first (exclusive). - Publish `voice_state` with channel/server ids + `clientInstanceId`. - Only one device owns mic (others passive; Join = takeover). - Peer connections already for chat mesh; mic tracks attached only to same-channel peers. - Playback only for peers in same voice channel. **Broken looks like** - “Connecting” forever. - One-way audio. - UI shows peers in channel but silent. - After network blip: forever dead until full app restart. - Works same-home, fails cross-home (initiator uses home id vs peer actor id). **Pack:** `06-voice-webrtc-bugs.md` --- ## J7 — Call someone from DM / people card **User steps** 1. Open DM or people card → Call. 2. Callee hears ring / sees modal (unless DND). 3. Answer → private call UI with optional chat panel. **Supposed to happen** - `direct-call` event delivered via PeerDelivery (DC then signaling). - `targetUserId` = callee’s **currently connected signal identity**. - Callee admission checks **all local aliases** (home + every provisioned actor id). - Caller never sits “In Voice” if ring could not be delivered. **Broken looks like** - Caller In Voice; callee silent (outbound route null / wrong id). - Callee never notified (inbound alias filter — partially fixed). - Cross-signal: two DM threads; replies land in the “wrong” empty one. **Pack:** `09-identity-cross-signal.md` --- ## J8 — Network blip / signal server restart **User steps** 1. In voice + chat; Wi‑Fi blips or signal process restarts. 2. Continue without manual reconnect. **Supposed to happen** - WS reconnect with backoff; health probe forces fresh socket on instance change. - `reIdentifyAndRejoin` then room resync. - Peer disconnect grace 10s; then reconnect loop (~12 × 5s). - DC close triggers repair; chat fallback covers live text meanwhile. - Voice presence clears on dead voice-active disconnect server-side. **Broken looks like** - Zombie “online” with no events. - Give-up after ~60s with **no error UI**. - Docs promise soft DC replace; code tears down full peer (audio drop). - Identify skipped → alone forever until manual leave/rejoin. **Pack:** `05-signaling-multi-server.md`, `07-data-channel-drops.md` --- ## J9 — Two devices, same account **User steps** 1. Desktop in voice; phone/browser also logged in. 2. Second device shows “in voice on another device”; can Takeover. 3. Chat/history appears on both. **Supposed to happen** - Distinct `clientInstanceId` per tab (sessionStorage). - Broadcasts reach sibling connections; `account_sync` for owned state. - Voice exclusive; takeover yields mic on old owner. **Broken looks like** - Tabs evict each other (shared clientInstanceId in localStorage — lesson says use sessionStorage). - Second device never gets chat batches. - Both think they own voice / neither transmits. --- ## J10 — Logout **User steps** 1. Title-bar Logout (desktop) or Settings → Logout (mobile). **Supposed to happen** - Disconnect sockets; clear current user id; reset rooms/users/messages; `/login`. **Broken looks like** - Stale credentials for foreign URLs linger and confuse next account (verify credential clear scope when fixing auth).