Files
Toju/emergency-fix/02-user-journeys.md
T
myxelium e49b3ec112 chore: dev-stack switches, shared e2e harness, and desktop shell rules
- `LIVE_RELOAD=false npm run dev` keeps the renderer alive across a machine
  suspend; the reload client otherwise destroys the session under test.
- `dev-peer.sh` plus a separate userdata dir runs a second local peer.
- `tools/voice-probe.js` samples peer state and RTP counters from a live
  window, persisting to localStorage so a renderer reload cannot erase it.
- e2e helpers for voice pairs, peer-role election, and a TURN relay.
- Electron single-instance and dev-client-load decisions move into rules
  files with colocated specs.
2026-08-14 03:19:29 +02:00

257 lines
7.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 Bs WebSocket for that rooms 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 dont (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` = callees **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; WiFi 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).