# Chat Domain

Text messaging, reactions, GIF search, typing indicators, and the user list. All UI is under `feature/`; application services handle GIF integration; domain rules govern message editing, deletion, and sync.

## Module map

```
chat/
├── application/
│   └── services/
│       ├── klipy.service.ts                     GIF search via the KLIPY API (proxied through the server)
│       └── link-metadata.service.ts             Link preview metadata fetching
│
├── domain/
│   └── rules/
│       ├── message.rules.ts                     canEditMessage, normaliseDeletedMessage, getMessageTimestamp
│       └── message-sync.rules.ts               Inventory-based sync: chunkArray, findMissingIds, limits
│
├── feature/
│   ├── chat-messages/                       Main chat view (orchestrates composer, list, overlays)
│   │   ├── chat-messages.component.ts             Root component: replies, GIF picker, reactions, drag-drop
│   │   ├── components/
│   │   │   ├── message-composer/                  Markdown toolbar, file drag-drop, send
│   │   │   ├── message-item/                      Single message bubble with edit/delete/react
│   │   │   ├── message-list/                      Paginated list (50 msgs/page), auto-scroll, Prism highlighting
│   │   │   └── message-overlays/                  Context menus, reaction picker, reply preview
│   │   ├── models/                                View models for messages
│   │   └── services/
│   │       └── chat-markdown.service.ts           Markdown-to-HTML rendering
│   │
│   ├── chat-image-proxy-fallback.directive.ts  Image proxy fallback for broken URLs
│   ├── klipy-gif-picker/                    GIF search/browse picker panel
│   ├── typing-indicator/                    "X is typing..." display (3 s TTL, max 4 names)
│   └── user-list/                           Online user sidebar
│
└── index.ts                                 Barrel exports
```

## Component composition

`ChatMessagesComponent` is the root of the chat view. It renders the message list, composer, and overlays as child components and coordinates cross-cutting interactions like replying to a message or inserting a GIF.

```mermaid
graph TD
    Chat[ChatMessagesComponent]
    List[MessageListComponent]
    Composer[MessageComposerComponent]
    Overlays[MessageOverlays]
    Item[MessageItemComponent]
    GIF[KlipyGifPickerComponent]
    Typing[TypingIndicatorComponent]
    Users[UserListComponent]

    Chat --> List
    Chat --> Composer
    Chat --> Overlays
    Chat --> GIF
    List --> Item
    Item --> Overlays

    click Chat "feature/chat-messages/chat-messages.component.ts" "Root chat view" _blank
    click List "feature/chat-messages/components/message-list/" "Paginated message list" _blank
    click Composer "feature/chat-messages/components/message-composer/" "Markdown toolbar + send" _blank
    click Overlays "feature/chat-messages/components/message-overlays/" "Context menus, reaction picker" _blank
    click Item "feature/chat-messages/components/message-item/" "Single message bubble" _blank
    click GIF "feature/klipy-gif-picker/" "GIF search panel" _blank
    click Typing "feature/typing-indicator/" "Typing indicator" _blank
    click Users "feature/user-list/" "Online user sidebar" _blank
```

## Message lifecycle

Messages are created in the composer, broadcast to peers over the data channel, and rendered in the list. Editing and deletion are sender-only operations.

```mermaid
sequenceDiagram
    participant User
    participant Composer as MessageComposer
    participant Store as NgRx Store
    participant DC as Data Channel
    participant Peer as Remote Peer

    User->>Composer: Type + send
    Composer->>Store: dispatch addMessage
    Composer->>DC: broadcastMessage(chat-message)
    DC->>Peer: chat-message event

    Note over User: Edit
    User->>Store: dispatch editMessage
    User->>DC: broadcastMessage(edit-message)

    Note over User: Delete
    User->>Store: dispatch deleteMessage (normaliseDeletedMessage)
    User->>DC: broadcastMessage(delete-message)
```

## Text channel scoping

`ChatMessagesComponent` renders only the active text channel selected in `store/rooms`. Legacy messages without an explicit `channelId` are treated as `general` for backward compatibility, while new sends and typing events attach the active `channelId` so one text channel does not leak state into the rest of the server. Voice channels live in the same server-owned channel list, but they do not participate in chat-message routing.

If a room has no text channels, the room shell in `features/room/chat-room/` renders an empty state instead of mounting the chat view. The chat domain only mounts once a valid text channel exists.

## Message sync

When a peer connects (or reconnects), both sides exchange an inventory of their recent messages so each can request anything it missed. The inventory is capped at 1 000 messages and sent in chunks of 200.

```mermaid
sequenceDiagram
    participant A as Peer A
    participant B as Peer B

    A->>B: inventory (up to 1000 msg IDs + timestamps)
    B->>B: findMissingIds(remote, local)
    B->>A: request missing message IDs
    A->>B: message payloads (chunked, 200/batch)
```

`findMissingIds` compares each remote item's timestamp and reaction/attachment counts against the local map. Any item that is missing, newer, or has different counts is requested.

## GIF integration

`KlipyService` checks availability on the active server, then proxies search requests through the server API. Rendered remote images now attempt a direct load first and only fall back to the image proxy after the browser reports a load failure, which is the practical approximation of a CORS or mixed-content fallback path in the renderer.

```mermaid
graph LR
    Picker[KlipyGifPickerComponent]
    Klipy[KlipyService]
    SD[ServerDirectoryFacade]
    API[Server API]

    Picker --> Klipy
    Klipy --> SD
    Klipy --> API

    click Picker "feature/klipy-gif-picker/" "GIF search panel" _blank
    click Klipy "application/services/klipy.service.ts" "GIF search via KLIPY API" _blank
    click SD "../server-directory/application/server-directory.facade.ts" "Resolves API base URL" _blank
```

## Domain rules

| Function | Purpose |
|---|---|
| `canEditMessage(msg, userId)` | Only the sender can edit their own message |
| `normaliseDeletedMessage(msg)` | Strips content and reactions from deleted messages |
| `getMessageTimestamp(msg)` | Returns `editedAt` if present, otherwise `timestamp` |
| `getLatestTimestamp(msgs)` | Max timestamp across a batch, used for sync ordering |
| `chunkArray(items, size)` | Splits arrays into fixed-size chunks for batched transfer |
| `findMissingIds(remote, local)` | Compares inventories and returns IDs to request |

## Typing indicator

`TypingIndicatorComponent` listens for typing events from peers scoped to the current server and active text channel. Each positive event resets a 3-second TTL timer for that channel; an explicit `isTyping: false` event clears that user immediately. If no new event arrives within 3 seconds, the user is removed from the typing list. At most 4 names are shown; beyond that it displays "N users are typing".
