# Data Flow
## Outbound messages (user sends)
```
1. User types message + presses Enter
2. input::parse_input() -> InputAction::SendText
3. App sends JsonRpcRequest via mpsc to SignalClient
4. SignalClient writes JSON-RPC to signal-cli stdin
5. signal-cli transmits via Signal protocol
```
The request is a JSON-RPC call to the `send` method with the recipient and
message body as parameters. Each request gets a unique UUID as its RPC ID.
## Inbound messages (received)
```
1. signal-cli receives message via Signal protocol
2. signal-cli writes JSON-RPC notification to stdout
3. SignalClient stdout reader parses the JSON line
4. Notification has method = "receive" with message data
5. Parsed into SignalEvent::MessageReceived
6. Sent through mpsc channel to main thread
7. App::handle_signal_event() processes it:
a. get_or_create_conversation() ensures the conversation exists
b. Message is appended to the conversation's message list
c. Message is inserted into SQLite
d. Unread count is updated (if not the active conversation)
e. Terminal bell fires (if notifications enabled and not muted)
8. Next render cycle shows the new message
```
## RPC request/response correlation
signal-cli uses JSON-RPC 2.0. There are two types of messages:
### Notifications (incoming)
Notifications arrive as JSON-RPC **requests** from signal-cli (they have a
`method` field). These include:
- `receive` -- incoming message
- `receiveTyping` -- typing indicator
- `receiveReceipt` -- delivery/read receipt
These are unsolicited and do not have an `id` field matching any outbound request.
### RPC responses
When siggy sends a request (e.g., `listContacts`, `listGroups`, `send`),
signal-cli replies with a response that has a matching `id` field and a `result`
(or `error`) field.
The `pending_requests` map in `SignalClient` stores `id -> method` pairs. When
a response arrives, the client looks up the method by ID to know how to parse
the result:
```
outbound: { "jsonrpc": "2.0", "id": "abc-123", "method": "listContacts", ... }
inbound: { "jsonrpc": "2.0", "id": "abc-123", "result": [...] }
pending_requests["abc-123"] = "listContacts"
-> parse result as Vec<Contact>
-> emit SignalEvent::ContactList
```
## Sync messages
When you send a message from your phone, signal-cli receives a sync notification.
These appear as `SignalMessage` with `is_outgoing = true` and a `destination`
field indicating the recipient. The app routes these to the correct conversation
and displays them as outgoing messages.
## Channel architecture
```
mpsc::channel
SignalClient ───────────────────────> App (main thread)
(tokio tasks) SignalEvent
mpsc::channel
App (main thread) ──────────────────> SignalClient
JsonRpcRequest (stdin writer task)
```
Both channels are unbounded `tokio::sync::mpsc` channels. The signal event
channel carries `SignalEvent` variants. The command channel carries
`JsonRpcRequest` structs to be serialized and written to signal-cli's stdin.