<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Matrix on Electric Nexus</title><link>https://arkoniak.com/tags/matrix/</link><description>Recent content in Matrix on Electric Nexus</description><generator>Hugo</generator><language>en-us</language><lastBuildDate>Tue, 20 Oct 2026 14:20:00 +0300</lastBuildDate><atom:link href="https://arkoniak.com/tags/matrix/index.xml" rel="self" type="application/rss+xml"/><item><title>Exerakt: Reading Matrix the Way Element Shows It</title><link>https://arkoniak.com/posts/exerakt-reading/</link><pubDate>Tue, 20 Oct 2026 14:20:00 +0300</pubDate><guid>https://arkoniak.com/posts/exerakt-reading/</guid><description>The second milestone of Exerakt: a client that reads Matrix messages the way Element shows them. The interesting problems were not in drawing but in other people&amp;rsquo;s code, and every feature was checked side by side with Element.</description><content:encoded><![CDATA[<p>How Exerakt is written is up to me. But there is one part I do not choose: the Matrix server on the
other side. There are many servers, and I need two of them. My own server runs Conduit, and the
server at work runs Synapse. Drawing turned out to be the easy part, mostly because I had given up
the heavy frameworks. The interesting problems came from other people&rsquo;s code: the Matrix SDK (<code>matrix-sdk</code>),
and servers that do not behave the same way.</p>
<h2 id="element-as-the-reference">Element as the reference</h2>
<p>For the look and the features I like Element a lot, so it is no surprise that I chose it as the
reference for where the project should go. This also answered a question the pipeline cannot answer
by itself. Jev and Claude check whether a card is done, but nobody checks where the whole project is
going. For now I want to reproduce the look and feel of Element, so every feature of Exerakt is
finished by comparing Exerakt and Element side by side: the same account, the same room, the two
windows next to each other.</p>
<h2 id="what-was-built">What was built</h2>
<p>The second milestone was a client that reads Matrix the way Element shows it. It took six steps:</p>
<ol>
<li>Turning the HTML of formatted messages into the client&rsquo;s own representation.</li>
<li>Drawing it: quotes, code, lists, headings, tables, links, mentions, and spoilers, with selection
and copy working across all of them.</li>
<li>Showing replies, edits, reactions, deleted messages, and messages that could not be decrypted,
with the same wording as Element.</li>
<li>Read receipts and the line that marks where the user stopped reading.</li>
<li>Pictures and colour emoji.</li>
<li>Local time, a status line for the connection, and a final check of the whole milestone against
Element.</li>
</ol>
<h2 id="side-by-side">Side by side</h2>
<p>A card is checked by its tests, but nothing tests how the whole client looks. So every step ended
with a comparison, and since the process is automatic, Claude did it as well:</p>
<ol>
<li>A local Conduit server ran in Docker. A small program created rooms on it and filled them with
the kinds of messages the step was about: formatted text, replies and reactions, pictures.</li>
<li>Claude started both clients, logged in to the same account on this server, on a separate
workspace of an invisible (headless) Hyprland output, so nothing appeared on my screen. It
captured Exerakt with <code>grim</code>, a screenshot tool for Wayland, and Element through its developer
tools.</li>
<li>Claude put the two screenshots side by side and compared them. What could be fixed within the
step was fixed there; everything else became a task for a later step.</li>
</ol>
<p>Below are three of these comparisons from the second milestone, Exerakt on the left and Element on
the right. They are not final screenshots: they show the clients the way Claude saw them at the
moment of each check. Two of the differences in them, local time and colour emoji, were done later
within the same milestone; the others are still ahead.</p>
<p><img alt="Formatted messages in Exerakt and Element" loading="lazy" src="/images/exerakt/m2-formatting.png"></p>
<p>Works:</p>
<ul>
<li>bold, italic, inline code, and strikethrough;</li>
<li>code blocks, numbered and nested lists, headings, quotes, tables, and rules;</li>
<li>coloured text, spoilers, links, and mentions, all in the same places as in Element.</li>
</ul>
<p>Not yet:</p>
<ul>
<li>syntax highlighting and line numbers in code blocks;</li>
<li>dimmer quotes, and a mention shown as the user&rsquo;s name with an avatar;</li>
<li>local time: at that moment Exerakt showed UTC, local time came later in the same milestone.</li>
</ul>
<p><img alt="Replies, edits, and reactions in Exerakt and Element" loading="lazy" src="/images/exerakt/m2-relations.png"></p>
<p>Works:</p>
<ul>
<li>the replied-to message above the reply;</li>
<li>the edited mark;</li>
<li>reactions with their counts under the message;</li>
<li>messages grouped by sender the same way.</li>
</ul>
<p>Not yet:</p>
<ul>
<li>colour emoji, which came later in the milestone;</li>
<li>the replied-to sender with an avatar and a coloured bar;</li>
<li>icons before deleted and undecryptable messages.</li>
</ul>
<p><img alt="The read marker in Exerakt and Element" loading="lazy" src="/images/exerakt/m2-read-marker.png"></p>
<p>Works:</p>
<ul>
<li>the read marker line after the same message;</li>
<li>the same times;</li>
<li>the badge of the other user who read the room, on the same message.</li>
</ul>
<p>Not yet: avatars and coloured sender names, here and everywhere else.</p>
<h2 id="other-peoples-code">Other people&rsquo;s code</h2>
<p>As I said at the start, drawing the messages itself went fairly smoothly. The problems came from two
other places: the servers, and one of the libraries.</p>
<h3 id="the-servers">The servers</h3>
<p>Matrix servers that speak the same protocol do not always send the same things. But first, a few
technical details about two features where this showed up.</p>
<p>The first one is read receipts. When you read a message, your client tells the server, and the
clients of other people in the room show it, usually as small avatars next to the last message each
person has read. The second one is the read marker: a line in a room that shows where you stopped
reading last time, so that when you come back you see where the new messages begin. The server keeps
its position, so the line is in the same place on all your devices.</p>
<p>A Matrix client gets news from the server through one long-lived connection, and both of these
should come through it. With Synapse they did. With Conduit they did not come at all, so Exerakt
never learned where the line was or who had read what. I found this only because Claude checked
the feature against both local servers. Exerakt now asks the server for the line separately every
time a room opens, which Conduit does answer. Other people&rsquo;s read receipts on Conduit are still
missing.</p>
<h3 id="the-emoji">The emoji</h3>
<p>The library problem was with emoji. They came out in black and white. To draw a character, <code>parley</code>,
the text layout library, looks for a font that has it, and it looked through the regular fonts
first. Some of them have black-and-white emoji, so they were picked before the colour emoji font.
The fix was to find the emoji in the text in Exerakt itself and send them to the colour emoji font
directly. As a side effect, the first emoji the program shows now takes 0.7 milliseconds instead of
97 (yes, almost 140 times faster), because the slow part had been the search through the system&rsquo;s list of fonts.</p>
<h2 id="additional-lessons">Additional lessons</h2>
<p>It turned out that Bonsai is not the only one who does not know everything. When you reply to a
message in Matrix, the client also puts a copy of the original message into the reply, for older
clients that cannot show replies. A modern client has to remove that copy before showing the reply.
In the first step of the milestone Claude cut a card asking Bonsai to do exactly that, and Bonsai
did it. Only then it turned out that <code>matrix-sdk-ui</code>, the part of the Matrix SDK that builds the list
of messages, already removes the copy, so the card was withdrawn.</p>
<p>The mistake was Claude&rsquo;s, the architect&rsquo;s, like the ones in <a href="/posts/exerakt-pipeline/">the first run</a>,
and it led to a new rule for writing cards: before cutting a card, read what the dependency already
does with the data.</p>
<p>The next post is about the third milestone, writing messages, and about how the plan for the
pipeline met reality.</p>
]]></content:encoded></item><item><title>Exerakt: Why Rust, Wayland and a UI of Its Own</title><link>https://arkoniak.com/posts/exerakt-skeleton/</link><pubDate>Tue, 20 Oct 2026 13:30:00 +0300</pubDate><guid>https://arkoniak.com/posts/exerakt-skeleton/</guid><description>Why Exerakt is written for Wayland only and draws its own interface instead of using a cross-platform toolkit, how this choice was checked in the first milestone, and how the result compares with Element.</description><content:encoded><![CDATA[<p>When I was choosing what to build (<a href="/posts/exerakt-why/">the first post</a>), I said that I wanted a
Rust implementation for Wayland. With Rust everything is more or less clear: a fast language, good
memory checks, and a compiler that does not let you make too many mistakes. We saw this in the
<a href="/posts/exerakt-jev-calibration/">calibration of Jev</a>: almost everything that passed the mechanical
checks turned out to be right and did not need to be redone.</p>
<h2 id="why-wayland-only">Why Wayland only</h2>
<p>With Wayland the reader may ask: why this way? Why not go the standard route, choose a good,
time-tested cross-platform library and use it?</p>
<p>My answer has two parts. The first one is philosophical. I believe that with LLMs it is possible
(or will be in the near future) to build specialised solutions efficiently. Do you want it for macOS
or Windows? You create a project like mine, feed it the code of Exerakt, and after some time you
have your own version of the application, built and tuned for your environment, in the form you
need. The word &ldquo;soft&rdquo; in software stops being just an adjective and becomes a literal description of
what you do: you change the code to fit the problem, instead of changing the problem to fit the
&ldquo;right&rdquo; framework.</p>
<p>The second part is technical. A cross-platform library is not only a convenient and free way to get
an application that runs everywhere. It is also an extra layer of abstraction that makes you write
code not the way you want. It keeps you from using native things the way they were meant to be
used. It brings its own complex bugs that often need non-trivial workarounds, and it makes you give
up features simply because the framework does not support them yet.</p>
<p>So I chose to specialise for one protocol. And the other decisions went the same way. Wherever I
could, I avoided big ready-made solutions. They are good, great, time-tested (no sarcasm), but they
would make me bend the code the way the framework needs, not the way I want. I did not want to run, every time, into a feature that &ldquo;cannot be done the way you want because of
this limitation of the framework&rdquo; and then kick myself, thinking &ldquo;why did I make this choice?
Nobody forced me to use Qt/GTK/your favourite framework&rdquo;.</p>
<h2 id="drawing-everything-myself">Drawing everything myself</h2>
<p>The client draws its whole interface by itself, and this gives simple answers to problems that are
hard with a toolkit. For example, selecting text across many messages, the problem I had with
Fractal: in Exerakt the whole timeline is one text document, so a selection simply goes across
messages. HTML is laid out my own way, and themes have no limits beyond what I write.</p>
<p>The stack is small: <code>winit</code> for the window and input, <code>softbuffer</code> to put pixels on the screen,
<code>tiny-skia</code> to draw, and <code>parley</code> to lay out text. Everything is drawn on the CPU first, and the GPU
comes in only if the CPU turns out to be too slow.</p>
<p>These crates are young, so before going further I wrote down the criteria the first milestone had
to check. If one of them failed, one crate would be replaced and the others would stay:</p>
<ul>
<li>selecting text across several messages and copying it;</li>
<li>smooth scrolling of a timeline of 2000 messages at 1440p, on the CPU.</li>
</ul>
<h2 id="the-first-milestone">The first milestone</h2>
<p>The first milestone was a client that can read: log in, restore the session, show the rooms and
their history, and send a plain message. It took five steps:</p>
<ol>
<li>The client&rsquo;s model: the data the screen shows, such as rooms and their messages.</li>
<li>The layer that talks to Matrix: login, the session, and sync through <code>matrix-sdk</code>.</li>
<li>The window, drawing, and text.</li>
<li>The views: the room list, the timeline, and the message field.</li>
<li>Connecting all of it into a working client and testing it end to end against local Matrix
servers (Conduit and Synapse).</li>
</ol>
<p>Selection across messages works. A frame of the timeline with real data at 1440p is drawn in 3 to 4
milliseconds, less than a quarter of the time a frame has at 60 Hz. So the stack stays.</p>
<h2 id="against-element">Against Element</h2>
<p>Then I compared it with Element Desktop on the same account with 14 rooms, both with a stored
session:</p>
<table>
	<thead>
			<tr>
					<th></th>
					<th>Exerakt</th>
					<th>Element Desktop</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Start to the room list on screen</td>
					<td>about 0.25 s</td>
					<td>about 2 s</td>
			</tr>
			<tr>
					<td>Memory after opening a room</td>
					<td>69 MiB</td>
					<td>about 700 MiB</td>
			</tr>
	</tbody>
</table>
<p>Of course the client is still very far from its final shape, and it does not have a huge number of
features that can grow its size and slow its start. Even so, the numbers are very encouraging, and
they support my confidence that I should keep going in this direction.</p>
<p>The next post is about the second milestone: reading Matrix messages the way Element shows them.</p>
]]></content:encoded></item><item><title>Exerakt: Why I Let Models Write My Matrix Client</title><link>https://arkoniak.com/posts/exerakt-why/</link><pubDate>Sun, 18 Oct 2026 10:00:00 +0300</pubDate><guid>https://arkoniak.com/posts/exerakt-why/</guid><description>Why I started Exerakt: to see how far models can get writing a real program on their own, in a structured process and without a big bill, and to get a Matrix desktop client I actually want to use.</description><content:encoded><![CDATA[<h2 id="the-experiment">The experiment</h2>
<p>I like trying new things. At work I use AI all the time, but as an assistant: I am in the loop for
every feature. I agree with the agent on what it is going to write, correct it as it goes, and keep
its work in line with the bigger picture of the project. This works very well. What I had never
tried is the opposite: let the model work on its own from start to finish, choose the solutions,
find its own mistakes and improve its own code.</p>
<p>At the same time I wanted to use my own experience. A model works better when its work is
structured: there is a problem statement, a breakdown into tasks, steps that end with tests and a
review of the design. Not &ldquo;make it good&rdquo;, but a process that looks like the classic way of building
software, the one I know works. There is nothing new here. It is only a formalisation of what
already exists, written down precisely enough to be run without me.</p>
<p>And I did not want to spend thousands of dollars on it. So the third thing I wanted to see is how
the models work together when each one does the job that fits its price:</p>
<ul>
<li>Claude is the organiser and planner: it designs the interfaces, writes the tests and splits the
work into small tasks.</li>
<li>Jev, a small and very cheap decision model, is the orchestrator: it checks the result against the
task and decides what happens next.</li>
<li><a href="https://prismml.com/news/bonsai-2-27b">Bonsai</a>, a 27B model running locally on my GPU, does all
the grunt work.</li>
</ul>
<p>I had heard about this kind of setup, but I had never used it in practice.</p>
<h2 id="the-project">The project</h2>
<p>Then the question was what to build. I did not want another to-do application or another proof of
concept. I have made too many pet projects like that, and they led nowhere. This time it had to be
something I really need and will use every day.</p>
<p>I chose a desktop client for <a href="https://matrix.org/">Matrix</a>. Matrix is an open, federated
chat protocol: anyone can run a server (I run my own), servers talk to each other the way email
servers do, rooms can be end-to-end encrypted, and any client works with any server.</p>
<p>This may look like a strange choice, because there are already many Matrix desktop clients. But each
of them has something that does not work for me:</p>
<ul>
<li>Element is the closest to what I need, but it runs only on Electron, which is heavy, and on my
Hyprland desktop the left Alt key opens its menu, while I switch keyboard layouts with left Alt.</li>
<li>FluffyChat is poor in features: it turns message HTML into its own widgets, so formatting comes
out badly, and it has no threads.</li>
<li>With the others there were other unpleasant things: no custom themes in Nheko, no text selection
with the mouse in Fractal.</li>
</ul>
<p>These are all good clients, and I have no big complaints about them. I could live with their
shortcomings or send pull requests. The closest to my wishes was
<a href="https://github.com/surakin/tesseract">Tesseract</a>, but it develops fast enough that keeping a fork
would be expensive, and I did not want to carry its Windows and macOS code.</p>
<p>So two things came together: the wish to try a process where models write the code, and the
wish to have a good client made for my needs, one I can change in any direction without depending on
the plans, means or taste of its maintainers. That is how Exerakt was born: a Matrix desktop client
for Wayland, written in Rust.</p>
<p>How it is going is the subject of the next posts.</p>
]]></content:encoded></item></channel></rss>