- `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.
119 lines
6.3 KiB
Markdown
119 lines
6.3 KiB
Markdown
# 01 — Product overview (user + system)
|
||
|
||
## What the user thinks this app is
|
||
|
||
**Toju / MetoYou** is a Discord-like desktop chat app with:
|
||
|
||
- **Accounts** on a signal (signaling) server
|
||
- **Chat-servers** (communities) with text channels and voice channels
|
||
- **Live text chat** in those channels
|
||
- **Voice / camera / screen share** in voice channels
|
||
- **Direct messages** and **private calls**
|
||
- **Friends**, profile cards, custom emoji, GIFs, file attachments
|
||
- **Multiple signal servers** in the network settings (home + others)
|
||
|
||
The marketing promise implied by the product design:
|
||
|
||
> Log in once. Join communities anywhere on the network. Chat and voice “just work.” Switching signal hosts should not feel like logging into a second product.
|
||
|
||
---
|
||
|
||
## What the system actually is
|
||
|
||
| Layer | Role |
|
||
|-------|------|
|
||
| **Angular client** (`toju-app/`) | All UX, NgRx state, domain logic, WebRTC + WebSocket clients |
|
||
| **Electron shell** (`electron/`) | Desktop window, SQLite persistence, IPC (`window.api`), provision-secret safeStorage, screen capture helpers |
|
||
| **Signaling server** (`server/`) | Auth tokens, public server directory REST, WebSocket identify/join/presence, RTC offer/answer/ICE relay, narrow chat/DM/voice_state fallbacks, multi-device `account_sync`. **Does not store chat history.** |
|
||
| **Web / Capacitor** | Same Angular app; weaker secret storage (sessionStorage); mobile voice/UI constraints |
|
||
|
||
### Transport split (critical mental model)
|
||
|
||
| Transport | What the user experiences | What it actually carries |
|
||
|-----------|---------------------------|--------------------------|
|
||
| **WebSocket to signal server** | “I’m online / in this server / someone joined voice” | Identity, room membership, presence, SDP/ICE relay, `chat_message` + DM fallbacks, `voice_state`, `account_sync` |
|
||
| **WebRTC media** | Hearing / seeing people | Mic, camera, screen tracks (never through the signal server) |
|
||
| **WebRTC ordered data channel** | Messages syncing, files, emoji, many “live” features | Chat events, inventory sync, attachments, avatar/emoji chunks, voice/screen control, plugin bus |
|
||
|
||
If the **socket** is wrong or unauthenticated → user is invisible; no relay; chat fallback may fail.
|
||
If the **data channel** is dead → live text may still limp via `chat_message`, but **history sync, attachments, emoji, many controls** fail.
|
||
If **media** is routed wrong or peer never connects → voice UI lies (“In Voice”) with silence.
|
||
|
||
---
|
||
|
||
## Core vocabulary (user ↔ engineering)
|
||
|
||
| User says | Engineering term |
|
||
|-----------|------------------|
|
||
| “My account / login” | Home session on `homeSignalServerUrl` + local profile in SQLite/IndexedDB |
|
||
| “Another network / signal host” | Foreign `ServerEndpoint` URL; needs per-URL credential |
|
||
| “A server / community” | Saved **Room** (chat-server) with `sourceUrl` / `sourceId` = which signal hosts it |
|
||
| “Text channel / voice channel” | Channels inside a room; text vs voice types |
|
||
| “I’m in voice” | Local `VoiceSession` + `voice_state` with `isConnected` + mic ownership via `clientInstanceId` |
|
||
| “DM / call someone” | Direct-message conversation + optional `direct-call` session |
|
||
| “It logged me out” | Often `/login` or `/login?mode=authorize` — may be **foreign authorize**, not true home logout |
|
||
|
||
---
|
||
|
||
## Multi-signal identity (the footgun)
|
||
|
||
1. User registers on **Signal A** → home user id `H`.
|
||
2. User later needs **Signal B** → client is supposed to **auto-register/login** with a local **provision secret**, creating actor id `A_B` on B (username may be suffixed).
|
||
3. Peers on Signal B see the user as `A_B`, not `H`.
|
||
4. WebRTC peer map keys, `targetUserId` on relay, DM conversation ids, and call participant lists may mix `H` and `A_B`.
|
||
|
||
**Invariant the product claims:** one human, one local profile, many per-server actor credentials — UX never asks for password again except last-resort authorize / real home expiry.
|
||
|
||
**Invariant signaling claims:** non-federated — peers in the same room must share the **same signal endpoint** to discover each other. Cross-room “same person” is a client-side identity problem.
|
||
|
||
---
|
||
|
||
## Feature inventory (what “done product” includes)
|
||
|
||
Must work for a release-quality emergency fix:
|
||
|
||
1. Home register / login / logout / session restore
|
||
2. Silent foreign provision + Network settings badges
|
||
3. Discover / create / join / leave chat-servers (public, password, invite, moderated)
|
||
4. Text channels: send, edit, delete, react, typing, unread, sync after late join
|
||
5. Voice channels: join/leave, mute/deafen, speaking indicators, multi-device takeover, move between channels
|
||
6. Camera + screen share in voice / calls
|
||
7. DMs + friends + delivery states
|
||
8. Direct / group calls (ring, answer, decline, DND)
|
||
9. Attachments over data channel
|
||
10. Multi-device `account_sync` for saved rooms / chat batches / friends / avatar / emoji
|
||
11. Endpoint health, version compatibility, room signal affinity + fallback
|
||
|
||
Secondary (do not block P0 voice/auth/chat): plugins, game activity, themes, KLIPY, link previews, experimental media.
|
||
|
||
---
|
||
|
||
## Platforms
|
||
|
||
| Platform | Notes for bugs |
|
||
|----------|----------------|
|
||
| Electron desktop | Primary; provision secret in safeStorage; SQLite; best screen share |
|
||
| Browser | sessionStorage provision secret dies with tab; IndexedDB |
|
||
| Capacitor mobile | Auth routing, mic permissions, no reliable screen share; title bar hidden |
|
||
|
||
---
|
||
|
||
## Trust boundaries (short)
|
||
|
||
- REST mutations and WS `identify` require bearer/session token **per signal URL**.
|
||
- Actor user ids in request bodies are ignored server-side; token wins.
|
||
- Message bodies are **not E2E encrypted** beyond DTLS on WebRTC and TLS on WS.
|
||
- Signaling server is **not** the source of truth for chat history.
|
||
|
||
---
|
||
|
||
## Where truth lives (for agents)
|
||
|
||
| Kind of truth | Prefer |
|
||
|---------------|--------|
|
||
| Wire WebSocket types | `agents-docs/features/signaling.md` + `server/src/websocket/handler.ts` — **not** `shared-kernel/signaling-contracts.ts` |
|
||
| Auth multi-server | `agents-docs/features/authentication.md` + `emergency-fix/04-*.md` |
|
||
| Voice/WebRTC plumbing | `toju-app/.../realtime/README.md` — verify against code (see `10-code-lies`) |
|
||
| Domain UX | `toju-app/src/app/domains/<name>/README.md` |
|
||
| Known past bugs | `agents-docs/LESSONS.md` — verify symbols still exist (some lessons describe **desired** fixes as if shipped) |
|