The Nim core
The program. It owns the state, the screens, the IRC connection and the
message signing; Flutter is a renderer over the widget tree it emits, and
dart/frq_core is the FFI binding between them.
src/frq_core.nim the C ABI: every exported symbol, and nothing else
src/frq/ui.nim the widget tree, in the screens' own tag vocabulary
src/frq/cells.nim every piece of state the screens read
src/frq/reducer.nim every event they can send, and what it does
src/frq/model.nim what a message and a room are
src/frq/rooms.nim the list, the overview, and the read marker
src/frq/members.nim who is in a channel, and what the server says of them
src/frq/conn.nim the socket, the TLS, the line framing
src/frq/ircparse.nim the IRC wire format
src/frq/handshake.nim CAP and the SASL exchange inside it
src/frq/atproto.nim handle → DID → PDS → session
src/frq/crypto.nim OpenSSL, bound: SHA-256 and Ed25519
src/frq/msgsig.nim signing a mutation so freeq will accept it
src/frq/emoji.nim 1,884 emoji — generated, see below
src/frq/glyphs.nim a line split into words and pictures
src/frq/clock.nim server time in the reader's own zone
src/frq/store.nim what survives a restart
src/frq/screens/ connect, chats, chat, discover, settings
The ABI
Strings in, strings out, and JSON where the answer is not a single string —
deliberately, against a struct-based ABI: a struct means both sides agreeing
on a memory layout, and a field added later is a version skew that segfaults
instead of failing.
Two rules, both of which cost a segfault to rediscover:
- Every string the core returns is the caller's to free, with
frq_free.
Nim's allocator is not Dart's. frq_initexists and does nothing. It used to callNimMain, and on Linux
--app:libalready runs the module initialisers from a library constructor
— so calling it again ran every module's top-level code a second time, which
forconn.nimmeantopen()on channels that were already open.
Things that will bite
- Nothing is shared with the socket threads. There are two — one reader,
one writer — and they speak only in channels.recvLine(timeout)does not
time out on a TLS socket, so a single thread cannot both wait for the server
and notice what the client wants to say. --mm:orc, not refc. refc gives each thread its own GC heap, so the
Socketthe two threads share is a ref from another heap and dereferencing
it segfaults.- Every intra-package import says
frq/….import connand
import frq/connname the same file by two paths and Nim compiles it twice,
giving two sets of globals and two states. emoji.nimis generated. It wastools/emoji2nim.pyreading the
ClojureDart catalogue, which is gone; regenerating it now means going back to
Unicode'semoji-test.txtand filtering to what the Twemoji pack draws.- The crypto is bindings, not implementations.
crypto.nimis OpenSSL
through EVP. The RFC 8032 vectors intests/tcrypto.nimcheck that this
drives it correctly — the seed in the right place, the one-shot signing form,
the bytes out in the right order — and not that Ed25519 is implemented
correctly, which is OpenSSL's problem.
just test nim # the whole suite
just test nim tircparse # one file
just build lib # libfrqcore.so into build/nim
What is not here
frq.profile and frq.replies were never ported and went with the
ClojureDart rather than moving: a Bluesky profile behind a nick, and asking
freeq what a collapsed msgid was. Neither had a screen in this app to appear
on.
The profile card is state without a screen: profileViewing is moved by
nothing and rendered by nothing, and frq.profile was never ported.
The emoji picker, the overview strip and the lightbox used to be in that list.
They have screens now — the picker under the message it is for, the overview
as a pane above the compose bar, the lightbox as a panel over the
conversation — so the 🙂 chip, the Overview toggle and clicking a picture all
do what they look like they do.
Untested against a real account: nobody has watched freeq accept a SASL
challenge response or a signature. The shapes are checked and the curve is
OpenSSL's, but the server has not said yes.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 |
|