nandi/frqpublic Fork 0
91b0e1e0ee61cd2f4ea69587776e28de7dc7ce36
Commits
Clone
git clone https://git.rickub.com/nandi/frq.git
git clone ssh://git@rickub.com/nandi/frq.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

README.md · 157 lines · 7.5 KBmarkdown Blame HistoryRaw
A freeq client in jolt, as glimmer components on Vidya 4719d6f nandi 21d ago1# frq
2
The last of the Clojure 284b59c nandi 22h ago3A **[freeq](https://github.com/codegod100/freeq)** client written in **Nim**,
4painted by Flutter. Nim owns the state, the screens, the IRC connection and
5the message signing; Flutter is a renderer over the widget tree it emits.
A freeq client in jolt, as glimmer components on Vidya 4719d6f nandi 21d ago6
7It is a proof of concept port of [sleek](../sleek), which is the same client in
8Rust against egui directly. The screens are sleek's — connect, chats, chat,
The last of the Clojure 284b59c nandi 22h ago9discover, settings, under a tab bar.
Split the tree three ways, and let the phone be Flutter's 7070931 nandi 8d ago10
11```
The last of the Clojure 284b59c nandi 22h ago12nim/ the program: state, screens, IRC, signing
13dart/frq_core the FFI binding — plain Dart, not a Flutter package
14flutter/lib the renderer, and the app's entry point
Split the tree three ways, and let the phone be Flutter's 7070931 nandi 8d ago15```
16
A freeq client in jolt, as glimmer components on Vidya 4719d6f nandi 21d ago17```
The last of the Clojure 284b59c nandi 22h ago18nim/src/frq/ui.nim the widget tree, in the screens' own tag vocabulary
19nim/src/frq/cells.nim every piece of state the screens read
20nim/src/frq/reducer.nim every event they can send, and what it does
21nim/src/frq/conn.nim the socket, the TLS, the line framing
22nim/src/frq/ircparse.nim the IRC wire format
23nim/src/frq/handshake.nim CAP and the SASL exchange inside it
24nim/src/frq/atproto.nim handle → DID → PDS → session
25nim/src/frq/crypto.nim OpenSSL, bound: SHA-256 and Ed25519
26nim/src/frq/msgsig.nim signing a mutation so freeq will accept it
27nim/src/frq/screens/ connect, chats, chat, discover, settings
28flutter/lib/nim_renderer.dart the tags, as Flutter widgets
A freeq client in jolt, as glimmer components on Vidya 4719d6f nandi 21d ago29```
30
The last of the Clojure 284b59c nandi 22h ago31There were two other clients here. `src/` was jolt with a libcosmic window and
32a terminal; `common/` and `flutter/src/` were ClojureDart, compiled for
33Android, Linux and the web. Both are gone. The APK and the web target went with
34the second: a browser has no `dart:ffi`, so the web needs the core compiled to
35wasm rather than ClojureDart restored.
Stop mistaking a quiet connection for a closed one a225fb1 nandi 20d ago36
A freeq client in jolt, as glimmer components on Vidya 4719d6f nandi 21d ago37## Running
38
39```bash
The last of the Clojure 284b59c nandi 22h ago40just run desktop # build and open the window
41just test # nim, dart and the layout suite
42just test live # the whole stack against a real freeq
A freeq client in jolt, as glimmer components on Vidya 4719d6f nandi 21d ago43```
44
The last of the Clojure 284b59c nandi 22h ago45`tools/toolchain.sh` fetches Flutter and Nim by sha256; there is no nix and no
46JVM. The host brings a C compiler, OpenSSL, GTK and the usual
47CMake/Ninja/pkg-config.
One frontend where there were three, and a core that is not Clojure 438b247 nandi yesterday48
The last of the Clojure 284b59c nandi 22h ago49Two switches, because a Wayland window cannot be clicked from a script:
50
51```bash
52FRQ_TRACE=1 just run desktop # every line in and out, both languages
53FRQ_AUTOCONNECT=1 just run desktop # press Connect at startup
54```
Build the desktop libraries rather than fetching a release of them 0b81161 nandi 16d ago55
56frq connects to `irc.freeq.at:6697` over TLS and joins `#test`. Untick TLS on
One frontend where there were three, and a core that is not Clojure 438b247 nandi yesterday57the connect screen (or point it at `127.0.0.1`) for a local server's plain
58listener:
A freeq client in jolt, as glimmer components on Vidya 4719d6f nandi 21d ago59
60```bash
61cargo run --release --bin freeq-server # in the freeq checkout
62```
63
Sign in with a Bluesky identity 5192124 nandi 21d ago64## Signing in
65
Sign in with Bluesky OAuth, through freeq's broker 2d4a377 nandi 21d ago66Three modes on the connect screen.
Sign in with a Bluesky identity 5192124 nandi 21d ago67
Sign in with Bluesky OAuth, through freeq's broker 2d4a377 nandi 21d ago68**Bluesky** (OAuth, the default way in) follows sleek's flow: frq binds a
69loopback port, puts it in `return_to`, and opens
70`auth.freeq.at/auth/login?handle=…`. The broker runs the OAuth dance with the
71PDS and redirects back to that port with the handoff in the URL *fragment*, so
72it never reaches a server as a query string. The page frq serves there has one
73job: POST the fragment back to itself. What comes back is a single-use SASL
74`web-token` and a durable `broker_token`; later connections mint a fresh token
75from the durable one at `/session` and skip the browser.
Sign in with a Bluesky identity 5192124 nandi 21d ago76
Remember an OAuth sign-in across restarts 4aa71e7 nandi 21d ago77The durable token is saved to `$XDG_CONFIG_HOME/frq/session.edn` (mode 600) so
Connect on launch when an account is remembered 9237cf4 nandi 20d ago78a restart resumes without one, along with the handle and nick it belongs to —
79and it connects on its own at launch when one is there.
Remember an OAuth sign-in across restarts 4aa71e7 nandi 21d ago80The web-token beside it is single-use and deliberately not saved. A token the
81broker no longer honours is dropped — from disk and memory — and the browser
82flow runs once more, rather than failing the same way on every Connect.
83
Sign in with Bluesky OAuth, through freeq's broker 2d4a377 nandi 21d ago84**App password** signs in without a browser, straight to the user's own PDS:
85`resolveHandle` → DID → PDS from the DID document → `createSession`. The
86password goes to that PDS and nowhere else, is never written to disk, and is
87dropped once the session exists.
Sign in with a Bluesky identity 5192124 nandi 21d ago88
Sign in with Bluesky OAuth, through freeq's broker 2d4a377 nandi 21d ago89Either way freeq sees only a token. The SASL mechanism is
90`ATPROTO-CHALLENGE` in both cases — `method: "web-token"`, which the server
91resolves through its own token store, or `method: "pds-session"` with the
92server's nonce echoed back so the token cannot be replayed elsewhere.
93
94A refused sign-in is reported and the connection carries on as a guest.
Sign in with a Bluesky identity 5192124 nandi 21d ago95
One frontend where there were three, and a core that is not Clojure 438b247 nandi yesterday96## Targets
A freeq client in jolt, as glimmer components on Vidya 4719d6f nandi 21d ago97
The last of the Clojure 284b59c nandi 22h ago98One: the Linux desktop.
A freeq client in jolt, as glimmer components on Vidya 4719d6f nandi 21d ago99
The last of the Clojure 284b59c nandi 22h ago100There were three, and losing two is the cost of the port rather than an
101accident. **The web** needs the core compiled to wasm — a browser has no
102`dart:ffi`, so there is no way for Dart to call a native library there, and
103that was true of this design from the first day. **Android** needs
104`libfrqcore.so` cross-compiled for its ABIs; `dart:ffi` works there, so this
105is a build problem rather than a design one.
A freeq client in jolt, as glimmer components on Vidya 4719d6f nandi 21d ago106
107## What the PoC covers
108
The last of the Clojure 284b59c nandi 22h ago109* TLS (`:6697`) or plain TCP (`:6667`), out of Nim's `std/net`
A freeq client in jolt, as glimmer components on Vidya 4719d6f nandi 21d ago110* Guest connect (`NICK`/`USER`), `001` welcome, `PING`/`PONG` keepalive
111* Auto-joins `#test` on `irc.freeq.at`
112* Join channels, channel buffers with unread counts, send and receive `PRIVMSG`
Ask for the backlog freeq restores channels without 94e59a2 nandi 20d ago113* Backlog on join, and `CHATHISTORY` for the channels freeq restores instead
Say when each thing was said dbf3b86 nandi 20d ago114* Twelve-hour timestamps from the server's own clock, with a heading wherever
115 the day changes
Answer a message, not just read that one was answered 4876db8 nandi 20d ago116* A chip above a reply quoting what it answers, and a click that goes there;
117 ↩ beside a sender to answer them, with `+draft/reply` on the way out
Pick any emoji, in colour 18c5ccd nandi 20d ago118* Emoji reactions: colour pills under a message, ☺ beside the sender to open a
Split the tree three ways, and let the phone be Flutter's 7070931 nandi 8d ago119 picker over every emoji the backend can draw (popular first, then Unicode's own
Pick any emoji, in colour 18c5ccd nandi 20d ago120 groups, searchable by name), and a second click on a pill to take yours off
121 — sent as `TAGMSG`, and restored from the server's own tally when the
122 backlog comes back
Show the pictures people paste 56cdac3 nandi 20d ago123* Inline previews for PNG links, fetched once and cached under
Click a picture to see it full size 563ada3 nandi 20d ago124 `$XDG_CACHE_HOME/frq/media`; click one to see it full size
Send a picture by pasting it 8a491ba nandi 20d ago125* Ctrl+V in the draft attaches the picture on the clipboard: it is previewed
126 under the box and uploaded to freeq's media endpoint while you write the line
127 it goes with, and only on the way out does it become the link — which is the
128 whole of what sending an image over IRC means. The draft itself is never
129 written into. Text pastes as text, as it always did: the picture path is the
130 keystroke the field had no text to answer with
A freeq client in jolt, as glimmer components on Vidya 4719d6f nandi 21d ago131* Join/part notices, DMs bucketed under the sender's nick
132* Discover list, search over buffers, disconnect
Remember which rooms this client has been in 1d062fd nandi 20d ago133* The rooms you have opened, remembered across runs and listed in the order
134 you last used them (`$XDG_CONFIG_HOME/frq/channels.edn`)
Order the chat list by what you were last in 75e557e nandi 20d ago135* Conversations listed most recently opened first
Show who is talking, with their Bluesky picture f5548bd nandi 20d ago136* Bluesky avatars beside the sender, resolved from the DID freeq tags each
137 message with
A freeq client in jolt, as glimmer components on Vidya 4719d6f nandi 21d ago138
139## Limits
140
141* **TLS and plain TCP only** — no WebSocket, no iroh. On Android, plain only.
One frontend where there were three, and a core that is not Clojure 438b247 nandi yesterday142* **No `did:key` signing, no credential gates, no E2EE.**
Remember an OAuth sign-in across restarts 4aa71e7 nandi 21d ago143* **Only the broker token is persisted**, and only for OAuth. An app-password
144 sign-in is not remembered.
One frontend where there were three, and a core that is not Clojure 438b247 nandi yesterday145* **Previews are PNG only.** The link is left in place either way.
Show the pictures people paste 56cdac3 nandi 20d ago146* **Nothing evicts the media cache.**
One frontend where there were three, and a core that is not Clojure 438b247 nandi yesterday147* **No calls.** The AV signaling is still in the screens, but the media plane
148 it drove was `libjoltmoq` under the retired jolt half — Opus, H.264, V4L2 and
149 ALSA, none of which crosses to Flutter. The Call controls are wired to
150 actions no target installs. Flutter's camera and audio plugins are the way
151 back in, and that is its own project.
152* **Attaching a picture needs a sign-in.** The upload is filed under the DID
153 of a live session, so a guest cannot make one. It also shares nothing to your
154 PDS and posts nothing to Bluesky — those fields are opt-in and this client
155 does not send them.
Calls f31ad3d nandi 20d ago156* **No scrollback trimming or threads.**
One frontend where there were three, and a core that is not Clojure 438b247 nandi yesterday157* A sent line waits up to 200ms for the reader to flush it.