- `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.
7.8 KiB
7.8 KiB
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
- Open app → login/register.
- Pick (or accept default) signal server.
- Register or log in with username/password.
- 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
currentUserset withhomeSignalServerUrl. - Provision secret created and persisted (Electron safeStorage / web sessionStorage).
- Signing public key registered on home server when possible.
- WebSocket connects,
identifywith 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
- Quit Electron fully; reopen.
- 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
ensureProvisionedfor 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
- Discover or invite → Join.
- Open a text channel; send “hello”.
- 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+ WSchat_messagefallback. - Voice:
voice_statebroadcast; 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
- Already logged into home Signal A.
- Open invite / browser card whose
sourceUrlis Signal B. - Join and chat/voice.
Supposed to happen
ensureCredentialForServerUrl(B)→ silent provision (or reuse credential).- REST + WS use actor id on B, not home id.
- No authorize login page.
- Peers on B see actor display name (may show
#prefixdisambiguation). - 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
- In text channel, type and send.
- 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_messageon signaling for peers without DC. - Edits/deletes primarily P2P (+
account_syncto 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
- Click a voice channel.
- Grant mic if prompted.
- Hear others; they hear you; speaking indicators; optional camera/screen.
Supposed to happen
- Leave any previous voice/call first (exclusive).
- Publish
voice_statewith 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
- Open DM or people card → Call.
- Callee hears ring / sees modal (unless DND).
- Answer → private call UI with optional chat panel.
Supposed to happen
direct-callevent 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
- In voice + chat; Wi‑Fi blips or signal process restarts.
- Continue without manual reconnect.
Supposed to happen
- WS reconnect with backoff; health probe forces fresh socket on instance change.
reIdentifyAndRejointhen 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
- Desktop in voice; phone/browser also logged in.
- Second device shows “in voice on another device”; can Takeover.
- Chat/history appears on both.
Supposed to happen
- Distinct
clientInstanceIdper tab (sessionStorage). - Broadcasts reach sibling connections;
account_syncfor 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
- 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).