The panes met edge to edge with only a hairline between them, so nothing on screen was grouped and the eye had to find the boundaries. The sidebar is now a conversations card over the account card on an inset background, its rows carry the avatar with the time and unread count stacked out of the name's way, and one full-width New chat button naming what each entry starts replaces the header and its menu. The status strip goes with it: the connection state belongs on the account card, and the messages it carried were either failures the toast already reports or narration of what the list itself shows.
3.9 KiB
Two-instance message exchange
The chat UI tutorial drives a single chat UI
window: it connects to the network and shares its address. A message
exchange, though, is inherently two-party, and the doc-test ui_test harness
cannot express it on its own: it drives one app instance and can only set
literal values on fields, so it can never read one window's address out and
paste it into another. The
automated exchange tutorial runs that
round-trip headless via the exchange app and captures the result in one
screenshot.
This page covers the other half end-to-end: two real chat UI windows exchanging
encrypted messages over the live network. It is driven by
doctests/exchange/run-exchange.mjs,
which speaks the logos-qt-mcp
inspector protocol to both instances at once, so it can read Alice's address
out of her window and hand it to Bob, then capture each side of the
conversation. The screenshots below are produced by that script.
Two instances coexist on one host because each picks a random QtRO socket name
and each delivery node picks its own listening ports; the script gives each its
own session dir (--user-dir) and QML inspector port (QML_INSPECTOR_PORT).
Run two instances interactively
To exchange a message by hand, launch two standalone apps; a distinct session dir keeps the two module instances cleanly apart:
# window A
nix run . -- --user-dir ~/.local/share/chat_a
# window B
nix run . -- --user-dir ~/.local/share/chat_b
Both nodes join the logos.test Waku fleet (and publish their key packages to
the key-package registry during init), so this needs internet and ~5-20s to
reach Online. Then, in window A copy the address off the account card; in window B
pick New chat > Direct message and paste A's address — the conversation opens on B's
side and the invite goes out. Once it appears in A's list (she has joined),
send the first message from B; reply from either side.
Regenerating the screenshots
doctests/exchange/run-exchange.sh docs/images/exchange
The wrapper builds the standalone app from the flake, launches Alice and Bob offscreen, runs the exchange, writes the PNGs below, and tears both instances down. It exits non-zero if the round-trip does not complete, so it doubles as an end-to-end integration check.
The exchange
1. Alice shares her address
Alice waits for the network to come up (Online), at which point the backend
has called get_address() and her account card carries her address. Her key
package was published to the registry during init, so the address alone lets a
peer open a conversation with her.
2. Bob opens a conversation and sends the first message
Bob pastes Alice's address into New chat > Direct message. The backend calls
create_conversation(address), which fetches her key package from the registry
and sends her the cryptographic invite; the new thread opens empty. Once Alice
has joined (the conversation shows up in her list), Bob sends the first
message. Bob's window shows the message sent.
3. Alice receives it
Bob's message lands, the message_received event fires, and the thread of the
conversation she joined shows it.
4. Alice replies
Alice replies with send_message(convo_id, content); the reply travels back
over the same conversation.
5. Bob receives the reply
The same path in reverse: Bob's thread now shows both messages. A real bidirectional, end-to-end-encrypted round-trip between two independent UI instances.




