osmaczko 4f18e4f10b feat: tail the session log in a developer console
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.
2026-07-28 08:51:07 +02:00
2026-01-28 18:34:47 +02:00
2026-02-17 21:30:39 +00:00
2026-02-17 21:30:39 +00:00

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

# 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 the gh-pages branch 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
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
S
Description
UI App for Logos ChatSDK POC
Readme
16 MiB
Languages
QML 56.7%
C++ 25.5%
JavaScript 9.5%
Shell 5.6%
CMake 1.3%
Other 1.4%