<?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>Element on Electric Nexus</title><link>https://arkoniak.com/tags/element/</link><description>Recent content in Element 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/element/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></channel></rss>