# Product Client (toju-app)

Owns the user-facing Angular 21 desktop chat experience: rendering and orchestrating chat, voice, screen-share, plugin UI, theming, and identity flows on top of the Electron `window.api` bridge and the server WebSocket. Houses every bounded context the end user interacts with, organized DDD-style under `src/app/domains/`.

> **Format reference:**
> - **Vocabulary** — bold term, one-sentence definition, aliases to avoid.
> - **Relationships** — bullets with bold terms and cardinality.
> - **Boundaries / IO** — what this subdomain exposes and consumes.
> - **Invariants** — rules that always hold.
> - **Flagged ambiguities** — terms in dispute with proposed resolutions.
>
> See `agents-docs/AGENTS_CONTEXT.md` for the contract. Update in the same turn a trigger fires (see `agents-docs/AGENT_WORKFLOW.md` § CONTEXT.md upkeep).

## Vocabulary

| Term | Definition | Aliases to avoid |
|------|------------|------------------|
| **Domain** | A bounded context under `src/app/domains/<name>/` that owns its own models, services, NgRx slice, and components — e.g. *chat*, *voice-session*, *plugins*. | "module", "feature" (Angular reserves these for different things) |
| **Shared kernel** | Cross-domain contracts in `src/app/shared-kernel/` — wire-format models, P2P transfer utilities, plugin contracts, signaling contracts — imported by multiple domains. | "common", "core" |
| **Infrastructure** | Technical-runtime concerns shared by domains: `persistence/` (client-side store wiring), `realtime/` (WebSocket adapter), and `mobile/` (Capacitor/native facades). Not a domain. | "shared", "lib" |
| **Mobile facade** | An injectable service under `infrastructure/mobile/` that wraps Capacitor or web adapters so domains never import `@capacitor/*` directly. | "Capacitor service", "native plugin" |
| **Capacitor SQLite backend** | Native persistence path selected by `DatabaseService` when `PlatformService.isCapacitor` is true; uses `@capacitor-community/sqlite` instead of IndexedDB. | "mobile database", "Capacitor DB" |
| **Rules file** | A pure-function module suffixed `*.rules.ts` that encodes domain logic without Angular or NgRx dependencies — easy to unit-test. | "helpers", "utils" |
| **Custom emoji** | User-created image emoji assets stored locally, synced peer-to-peer, and referenced from messages/reactions by stable `:emoji[id](name)` tokens. | "sticker", "emote" |
| **App locale** | The active UI language for the product client, resolved by `resolveAppLocale()` in `core/i18n/`; only `en` is shipped today. | "language", "i18n locale" |
| **Translation catalog** | JSON string tables under `public/i18n/catalog/*.json`, merged to `public/i18n/en.json` via `npm run i18n:sync`, loaded at startup by `AppI18nService`. | "locale file", "messages file" |
| **Client instance** | Stable per-tab UUID (`metoyou.clientInstanceId` in `sessionStorage`) sent on WebSocket `identify` and voice-state payloads so the signaling server can route multi-device sessions without evicting other tabs or synced profiles. | "device id", "session id" |
| **Voice owner connection** | The single client instance whose `clientInstanceId` matches the user's active `voiceState.clientInstanceId` and therefore owns mic/WebRTC for that identity. | "active voice client" |

## Relationships

- A **Domain** owns zero or more **Components**, **Services**, **NgRx slices**, and **Rules files**.
- A **Domain** may consume **Shared kernel** contracts but must never import from another **Domain** directly — cross-domain coupling goes through the shared kernel or NgRx events.
- The **Realtime** infrastructure adapts server WebSocket envelopes (defined in `src/app/shared-kernel/signaling-contracts.ts` and mirrored in `server/src/websocket/types.ts`) into NgRx actions consumed by domains.
- The **Plugins** domain consumes plugin manifests loaded by Electron's `plugin-library.ts` and exposes a sandboxed runtime that other domains may hook into.
- The **Custom emoji** domain owns custom emoji assets and usage ranking; the **Chat** domain consumes it for reactions and composer insertion.

## Boundaries / IO

- **Exposes:** the Angular SPA bundle served at `:4200` in dev (`ng serve`) or mounted by Electron in production; NgRx store events that other domains in this subdomain consume; UI surface to the end user.
- **Consumes:**
  - `window.api.*` IPC surface exposed by Electron preload (defined in `electron/preload.ts` and `electron/api/`).
  - WebSocket envelopes from `server/src/websocket/` (typed by `shared-kernel/signaling-contracts.ts`).
  - REST endpoints from the server's `server/src/routes/` (server directory, invites, join requests, link metadata, klipy).
  - Plugin manifests resolved by Electron's plugin library.

## Invariants

- A **Domain** never imports from another **Domain** directly — only through the **Shared kernel** or NgRx actions.
- **Rules files** stay framework-free (no Angular, no NgRx) so they can be Vitest-tested as plain functions.
- Wire-format types (anything that crosses the WebSocket or IPC boundary) live in **Shared kernel**, never inside a single domain.

## Flagged ambiguities

- _None recorded yet — add entries when a domain term resists clean definition._

---

*Agent-procedural rules (TDD, lint, etc.) live in `/AGENTS.md`. Per-domain implementation detail lives in `src/app/domains/<name>/README.md`. This file is the bounded-context domain artefact for the product client as a whole.*
