The stack's account of a failure was scattered across four writers in three processes and reachable only from a terminal, so anyone running the app from a launcher had no way to see why something did not work, and no way to attach that account to a report. The console tails the file the host assigns and filters it by domain, severity and text, with an export for the whole file or for exactly what the filters pass. Every failure the backend reports now goes to the log as well as the strip, through one reporter, which is also what stops a reason the module could not supply from rendering as a trailing colon. Only a bounded window of the file is held, shared out between domains rather than kept strictly newest first, and a domain gives up its ordinary lines before its severe ones. A writer producing thousands of lines a minute would otherwise push every quieter one out of reach within minutes, leaving a filter chip counting lines nothing could show and a header counting errors that were no longer there. What a line says about itself outranks what relayed it, so a severity the writer stated beats the pseudo level the host stamped, and a field naming a module beats the innermost scope beside it.
logos-chat-ui
A QML + C++ backend UI module for the Logos platform that provides a private messaging interface built on top of Logos Chat.
The UI connects to logos-chat-module via the Logos Core module system for all chat operations — identity, conversations, and message exchange happen over the Logos network.
Built with logos-module-builder using the mkLogosQmlModule pattern (QML frontend + C++ backend with Qt Remote Objects).
What It Does
The application provides a dark-themed chat interface laid out as cards on an inset background: a conversations card over your account card (left), the message thread (center), and a right column that appears for a group or when the conversation's details are open:
- Conversations (left) — active conversations with an avatar, a preview, a timestamp and an unread badge, under the New chat button, with your own account card beneath them
- Message thread (center) — a header naming the conversation and who is in it, the messages, and the composer; an incoming message carries its sender's avatar and name where a run of theirs begins
- Right column — the group's roster with Add member pinned to its foot, and the conversation's Details panel above it while the header's toggle is on
Core functionality:
- Identity — on startup, initializes a chat identity; the account card at the foot of the sidebar shows the account's short form and its connection state
- Addresses — your own address sits on the account card with a copy button beside it; share it with others to let them start a conversation with you
- Direct messages — paste another user's address into New chat > Direct message to open a private (1:1) conversation
- Group conversations — start a group with New chat > Group, then invite peers by address from the members panel (see below)
- Messaging — send and receive messages in real-time over the Logos network
- Chat lifecycle — auto-initializes and starts on launch; the connection state shows on the account card
Conversations are ephemeral — messages and identity exist only while the app is running.
The status bar and the developer console
A strip across the foot of the window carries whatever the app could not do: the newest failure in prose, a count of the ones behind it, and a badge on the console button. A failure stays until it is read, rather than passing on a timer, and clicking it opens the console. With nothing to report the strip rests on the newest line of the session log.
The console takes the whole window and tails the log the host is capturing for
this run, which carries libchat, chat_module, chat_ui and delivery_module
alongside the core's own output. Filter it by domain, by severity and by text;
each domain chip carries its line count, so a domain that said nothing is
distinguishable from one being hidden. Export writes either the whole
session file or exactly the lines the filters show, into your downloads
directory, and names what it wrote on the strip.
The filters change what you see and never what is written. A question that needs
more detail than the session recorded still needs RUST_LOG and a restart.
A host that captures no log leaves the console empty and says so; the console reads the file, it does not create one.
Group conversations
- New chat > Group creates a group with you as its only member (no dialog; the group opens immediately).
- Collect peers' addresses (each copies theirs from their account card), paste one into Add member at the foot of the members card, and confirm to invite.
- Membership changes are asynchronous: on devnet the group's steward commits an add only after a ~60s commit-inactivity window, then the welcome is delivered, so a peer joins minutes after the invite. A peer you invited sits on the roster as a dimmed, spinning row until the group commits it, and stays there across chat switches. The roster refreshes on selection, a message from a new member, or your own add.
- A right-click on a roster row offers Copy address, for passing a member's address on.
- Any member can add another; the invite routes from whoever proposed it.
- During the brief windows while the group is finalizing a membership change, de-mls rejects sends; these surface on the status bar at the foot of the window, so retry after a moment.
How to Run
Standalone (recommended for development)
# Run directly
nix run
# With local workspace overrides (if testing local changes)
nix run --override-input chat_module path:../logos-chat-module \
--override-input chat_module/logos-module-builder path:../logos-module-builder
The standalone app starts Logos Core, loads capability_module and chat_module, then launches the QML UI via an isolated ui-host process.
Running multiple instances on one machine
To try a real conversation or group locally, run two or more standalone apps side by side on the same host. Each instance needs its own session directory; the UI-to-backend QtRO socket name is randomized per instance and the delivery node listens on ports it picks itself, so nothing else has to be set:
# window A
nix run . -- --user-dir ~/.local/share/chat_a
# window B
nix run . -- --user-dir ~/.local/share/chat_b
Add further windows the same way, giving each a fresh session directory
(chat_c, and so on).
The standalone app hands every module its own directory under
<session dir>/module_data, so --user-dir is what keeps two instances' chat
state apart; it defaults to the platform application data location.
| Variable | Purpose |
|---|---|
LOGOS_USER_DIR |
The standalone app's session directory, for when setting it by environment is easier than by flag. --user-dir wins over it. |
QML_INSPECTOR_PORT |
Only needed when attaching the logos-qt-mcp inspector to drive an instance programmatically (default 3768); give each a distinct one then. Interactive use does not need it. |
Each node joins the logos.test Waku fleet and publishes its key package during
init, so this needs internet and ~5-20s per window to reach Online. Then
copy one window's address from its account card and paste it into another
(New chat > Direct message for a 1:1, or New chat > Group then the members
panel for a group). For
the full walkthrough with screenshots, and the scripted drivers that automate it
(doctests/exchange/run-exchange.sh for a two-party exchange,
doctests/group/run-group.sh for a three-party group), see
Two-instance message exchange.
In Basecamp
Build the .lgx package and install it:
# Build LGX
nix build .#lgx
# Install into Basecamp's plugin directory
lgpm --ui-plugins-dir ~/Library/Application\ Support/Logos/LogosBasecampDev/plugins \
install --file result/*.lgx
Or from the workspace:
ws bundle logos-chat-ui --auto-local
Build Targets
nix build # default — combined plugin + QML output
nix build .#lgx # .lgx package for distribution
nix build .#install # lgpm-installed output (modules/ + plugins/)
nix run # standalone app with chat_module
nix develop # enter development shell
Documentation
-
Two-instance message exchange — two windows exchanging encrypted messages end-to-end (with screenshots), plus how to run two instances locally.
-
Doc-test tutorials — executable walkthroughs that CI runs and publishes as an HTML report under
https://logos-co.github.io/logos-chat-ui/: The Logos Chat UI (connect + share your address) and Run the automated message-exchange test (the real two-party round-trip, captured).Enabling the report links is a one-time repo setup: Settings -> Pages -> "Deploy from a branch", branch
gh-pages/(root)(the CI publish-report job creates thegh-pagesbranch on its first run).
Module Structure
logos-chat-ui/
├── flake.nix # mkLogosQmlModule
├── metadata.json # Module config (ui_qml, interface: universal)
├── CMakeLists.txt # logos_module() macro
└── src/
├── ChatBackend.rep # QtRO interface (ChatStatus enum, props, slots, signals)
├── ChatBackend.h/cpp # Backend: chat lifecycle, conversations, messages
├── ConversationListModel.h/cpp # QAbstractListModel for conversations
├── MessageListModel.h/cpp # QAbstractListModel for messages
├── LogLine.h/cpp # One session-log line, parsed
├── SessionLogModel.h/cpp # Tails the session log; the console's source model
├── LogFilterModel.h/cpp # The console's domain/level/text predicate
├── SessionLogWriter.h/cpp # This module's own lines, into the same file
└── qml/
├── ChatView.qml # Top-level composition (thin)
└── ChatUi/ # Pure-QML component module, built on Logos.Theme
├── ChatStore.qml # Sole reader of the injected logos context
├── ConversationsPane.qml # Conversation list (left)
├── MessageThreadPane.qml # Message thread + composer (center)
├── MembersPane.qml # Group roster + add-member (right)
├── ... # dialogs, delegates, leaf components
└── qmldir
The plugin entry point and QtRO replica/source glue are generated by
mkLogosQmlModule from metadata.json#codegen (rep / backend_class /
backend_header); the repo carries the backend, the two models, and the QML
view module.
Key Components
| File | Role |
|---|---|
ChatBackend.rep |
Defines the C++/QML boundary — ChatStatus enum, state props, lifecycle slots, signals |
ChatBackend |
Derives ChatBackendSimpleSource + LogosUiPluginContext; initialises the module and subscribes to chat_module events in onContextReady(); drives the two models |
ConversationListModel |
Roles: conversationId, displayName, lastActivity, unreadCount |
MessageListModel |
Roles: sender, content, timestamp, isMe |
LogLine |
Parses one line of the session log into a time, a severity, a domain and a message; an unrecognised shape keeps its whole text |
SessionLogModel |
Tails the file the host assigns, retaining the most recent lines and counting everything the session wrote |
Requirements
Tip
When using Nix, all requirements are acquired automatically.
Dependencies
| Dependency | Purpose |
|---|---|
| Qt6 Core, RemoteObjects, Declarative | UI framework + IPC |
logos-module-builder |
Build system (mkLogosQmlModule) |
logos-chat-module |
Chat backend module |
logos-delivery-module |
Transport (Waku) — runtime dependency, pinned at v0.1.3 |
Related Repositories
| Repository | Role |
|---|---|
logos-chat-module |
Chat backend — this UI's required dependency |
logos-delivery-module |
Transport (Waku) — runtime dependency, pinned at v0.1.3 |
libchat |
Chat engine embedded by chat_module (E2EE, sessions) |
logos-module-builder |
Module build system |
logos-liblogos |
Logos Core platform |