nandi/frqpublic Fork 0
438b247afa2e97c6a6954709771915e2e72203f6
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 · 193 lines · 9.6 KBmarkdown Blame HistoryRaw
A freeq client in jolt, as glimmer components on Vidya 4719d6f nandi 20d ago1# frq
2
3A **[freeq](https://github.com/codegod100/freeq)** client written in
One frontend where there were three, and a core that is not Clojure 438b247 nandi 21h ago4**[ClojureDart](https://github.com/tensegritics/ClojureDart)**, painted by
5Flutter — one set of screens on Android, on the Linux desktop and in a browser.
6See [flutter/README.md](flutter/README.md).
A freeq client in jolt, as glimmer components on Vidya 4719d6f nandi 20d ago7
8It is a proof of concept port of [sleek](../sleek), which is the same client in
9Rust against egui directly. The screens are sleek's — connect, chats, chat,
One frontend where there were three, and a core that is not Clojure 438b247 nandi 21h ago10discover, settings, under a tab bar — but each is hiccup over the widget tags
11`frq.hiccup` translates into Flutter, and state lives in atoms instead of an
12`AppState` struct.
A freeq client in jolt, as glimmer components on Vidya 4719d6f nandi 20d ago13
One frontend where there were three, and a core that is not Clojure 438b247 nandi 21h ago14Source lives in three trees:
Split the tree three ways, and let the phone be Flutter's 7070931 nandi 7d ago15
16```
One frontend where there were three, and a core that is not Clojure 438b247 nandi 21h ago17common/ .cljc portable: the screens, the state, the protocol — no dart:
18flutter/ .cljd the Flutter half, and the host's answers — flutter/README.md
19nim/ .nim the portable logic as a native library — nim/README.md
Split the tree three ways, and let the phone be Flutter's 7070931 nandi 7d ago20```
21
One frontend where there were three, and a core that is not Clojure 438b247 nandi 21h ago22The first two are the client as it runs today; `nim/` is where the logic under
23the screens is moving, a module at a time, behind a C ABI the Dart side calls
24through FFI. One module has made the trip so far. See `nim/README.md` for
25what is wired up and what is not.
26
27What `common/` needs of the host it asks `common/frq/io.cljc` for — the seam,
28named once and answered per target: `frq.io.dart` on Android and the desktop,
29`frq.io.web` in a browser.
Split the tree three ways, and let the phone be Flutter's 7070931 nandi 7d ago30
A freeq client in jolt, as glimmer components on Vidya 4719d6f nandi 20d ago31```
One frontend where there were three, and a core that is not Clojure 438b247 nandi 21h ago32common/frq/io.cljc the seam: filesystem, environment, config dir, clock
33common/frq/clock.cljc IRCv3 time tags → the reader's own zone
34common/frq/store.cljc the saved sign-in, mode 600 in the config directory
35common/frq/emoji.cljc the picker's catalog: every emoji and its name
36common/frq/rooms.cljc the rooms this client has been in, and their order
37common/frq/cells.cljc the atoms every screen reads
38common/frq/screens/ connect, chats, chat, discover, settings
39common/frq/irc/parse.cljc the IRC line parser, tags and all
40common/frq/irc/handshake.cljc SASL, driven from shared code
41common/frq/atproto/core.cljc handle → DID → PDS → session, and the SASL payloads
42common/frq/oauth/core.cljc the broker flow, as far as it is portable
43common/frq/msgsig.cljc message signatures
44flutter/src/frq/main.cljd the entry point: installs the host, then starts
45flutter/src/frq/hiccup.cljd the widget tags, as Flutter
46flutter/src/frq/net/ sockets: dart:io on native, WebSocket on the web
47flutter/src/frq/io/ the host's answers to the seam
A freeq client in jolt, as glimmer components on Vidya 4719d6f nandi 20d ago48```
49
One frontend where there were three, and a core that is not Clojure 438b247 nandi 21h ago50There used to be a third tree, `src/`, and another runtime under it: jolt, with
51[glimmer](https://github.com/jolt-lang/glimmer) components painted by
52**libcosmic** in a desktop window and by `libjolttui` in a terminal, plus an
53AV media plane over MoQ. It is gone. Flutter is the only frontend now, which is
54why `common/` no longer carries `#?(:jolt ...)` reader conditionals and why the
55calls, terminal and `nix run .#frq` sections that used to be here are not.
56
Stop mistaking a quiet connection for a closed one a225fb1 nandi 20d ago57## Tracing
58
59`FRQ_TRACE=1` prints every IRC line sent and received to stderr, which on
60Android is logcat.
61
A freeq client in jolt, as glimmer components on Vidya 4719d6f nandi 20d ago62## Running
63
64```bash
One frontend where there were three, and a core that is not Clojure 438b247 nandi 21h ago65just flutter-desktop run # the Linux window
66just apk run # onto a connected Android device
67just flutter-web serve # a browser, on :8080
A freeq client in jolt, as glimmer components on Vidya 4719d6f nandi 20d ago68```
69
One frontend where there were three, and a core that is not Clojure 438b247 nandi 21h ago70Every recipe lives in the `justfile` itself. The two that need a toolchain from
71Nix re-enter `nix develop` and come back to the same recipe, so `just
72flutter-desktop` and `nix develop .#flutter-desktop --command just
73flutter-desktop` are one code path rather than two. `just flutter-web` needs no
74Nix at all: `tools/toolchain.sh` fetches Flutter, a JDK and the Clojure CLI by
75sha256, which is what lets `.modal/flutter-web/` run the same script on a plain
76Debian image.
77
78All three are one `clojure -M:cljd compile` over `flutter/src` and `common/`,
79and differ only in which Flutter target runs afterwards.
Build the desktop libraries rather than fetching a release of them 0b81161 nandi 15d ago80
81frq 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 21h ago82the connect screen (or point it at `127.0.0.1`) for a local server's plain
83listener:
A freeq client in jolt, as glimmer components on Vidya 4719d6f nandi 20d ago84
85```bash
86cargo run --release --bin freeq-server # in the freeq checkout
87```
88
One frontend where there were three, and a core that is not Clojure 438b247 nandi 21h ago89A browser has no TCP, so the web build wants a WebSocket URL in the Server
90field — `wss://irc.freeq.at/irc`. And Bluesky sign-in only completes on
91`localhost`, because that is the one origin freeq's auth broker will redirect
92back to: `just web-local` is what serves the Modal-built bundle there.
Paint the same screens into a terminal ab83b42 nandi 17d ago93
Sign in with a Bluesky identity 5192124 nandi 20d ago94## Signing in
95
Sign in with Bluesky OAuth, through freeq's broker 2d4a377 nandi 20d ago96Three modes on the connect screen.
Sign in with a Bluesky identity 5192124 nandi 20d ago97
Sign in with Bluesky OAuth, through freeq's broker 2d4a377 nandi 20d ago98**Bluesky** (OAuth, the default way in) follows sleek's flow: frq binds a
99loopback port, puts it in `return_to`, and opens
100`auth.freeq.at/auth/login?handle=…`. The broker runs the OAuth dance with the
101PDS and redirects back to that port with the handoff in the URL *fragment*, so
102it never reaches a server as a query string. The page frq serves there has one
103job: POST the fragment back to itself. What comes back is a single-use SASL
104`web-token` and a durable `broker_token`; later connections mint a fresh token
105from the durable one at `/session` and skip the browser.
Sign in with a Bluesky identity 5192124 nandi 20d ago106
Remember an OAuth sign-in across restarts 4aa71e7 nandi 20d ago107The 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 ago108a restart resumes without one, along with the handle and nick it belongs to —
109and it connects on its own at launch when one is there.
Remember an OAuth sign-in across restarts 4aa71e7 nandi 20d ago110The web-token beside it is single-use and deliberately not saved. A token the
111broker no longer honours is dropped — from disk and memory — and the browser
112flow runs once more, rather than failing the same way on every Connect.
113
Sign in with Bluesky OAuth, through freeq's broker 2d4a377 nandi 20d ago114**App password** signs in without a browser, straight to the user's own PDS:
115`resolveHandle` → DID → PDS from the DID document → `createSession`. The
116password goes to that PDS and nowhere else, is never written to disk, and is
117dropped once the session exists.
Sign in with a Bluesky identity 5192124 nandi 20d ago118
Sign in with Bluesky OAuth, through freeq's broker 2d4a377 nandi 20d ago119Either way freeq sees only a token. The SASL mechanism is
120`ATPROTO-CHALLENGE` in both cases — `method: "web-token"`, which the server
121resolves through its own token store, or `method: "pds-session"` with the
122server's nonce echoed back so the token cannot be replayed elsewhere.
123
124A refused sign-in is reported and the connection carries on as a guest.
Sign in with a Bluesky identity 5192124 nandi 20d ago125
One frontend where there were three, and a core that is not Clojure 438b247 nandi 21h ago126## Targets
A freeq client in jolt, as glimmer components on Vidya 4719d6f nandi 20d ago127
One frontend where there were three, and a core that is not Clojure 438b247 nandi 21h ago128Three, from one compile, and what separates them is the host half rather than
129the screens.
A freeq client in jolt, as glimmer components on Vidya 4719d6f nandi 20d ago130
One frontend where there were three, and a core that is not Clojure 438b247 nandi 21h ago131**Android** and **the Linux desktop** are both `dart:io` underneath:
132`frq.io.dart` answers the seam, `frq.net.dart` opens a real TCP or TLS socket.
133`frq.io.dart/write-private-file!` is the one place that asks which of the two it
134is on (`Platform.isAndroid`), because assuming cost a token its file mode.
A freeq client in jolt, as glimmer components on Vidya 4719d6f nandi 20d ago135
One frontend where there were three, and a core that is not Clojure 438b247 nandi 21h ago136**The web** is not: a browser has no TCP and no filesystem, so `frq.net.web`
137carries an IRC WebSocket and `frq.io.web` keeps the seam's files in local
138storage. Bluesky sign-in works there only on `localhost` — see Running.
A freeq client in jolt, as glimmer components on Vidya 4719d6f nandi 20d ago139
One frontend where there were three, and a core that is not Clojure 438b247 nandi 21h ago140What carries over untouched is `common/` — the screens, the state, the parser,
141the protocol. See the two trees at the top.
A freeq client in jolt, as glimmer components on Vidya 4719d6f nandi 20d ago142
143## What the PoC covers
144
One frontend where there were three, and a core that is not Clojure 438b247 nandi 21h ago145* TLS (`:6697`, out of `dart:io`) or plain TCP (`:6667`); a WebSocket on the web
A freeq client in jolt, as glimmer components on Vidya 4719d6f nandi 20d ago146* Guest connect (`NICK`/`USER`), `001` welcome, `PING`/`PONG` keepalive
147* Auto-joins `#test` on `irc.freeq.at`
148* Join channels, channel buffers with unread counts, send and receive `PRIVMSG`
Ask for the backlog freeq restores channels without 94e59a2 nandi 20d ago149* Backlog on join, and `CHATHISTORY` for the channels freeq restores instead
Say when each thing was said dbf3b86 nandi 20d ago150* Twelve-hour timestamps from the server's own clock, with a heading wherever
151 the day changes
Answer a message, not just read that one was answered 4876db8 nandi 20d ago152* A chip above a reply quoting what it answers, and a click that goes there;
153 ↩ beside a sender to answer them, with `+draft/reply` on the way out
Pick any emoji, in colour 18c5ccd nandi 20d ago154* 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 7d ago155 picker over every emoji the backend can draw (popular first, then Unicode's own
Pick any emoji, in colour 18c5ccd nandi 20d ago156 groups, searchable by name), and a second click on a pill to take yours off
157 — sent as `TAGMSG`, and restored from the server's own tally when the
158 backlog comes back
Show the pictures people paste 56cdac3 nandi 20d ago159* Inline previews for PNG links, fetched once and cached under
Click a picture to see it full size 563ada3 nandi 20d ago160 `$XDG_CACHE_HOME/frq/media`; click one to see it full size
Send a picture by pasting it 8a491ba nandi 20d ago161* Ctrl+V in the draft attaches the picture on the clipboard: it is previewed
162 under the box and uploaded to freeq's media endpoint while you write the line
163 it goes with, and only on the way out does it become the link — which is the
164 whole of what sending an image over IRC means. The draft itself is never
165 written into. Text pastes as text, as it always did: the picture path is the
166 keystroke the field had no text to answer with
A freeq client in jolt, as glimmer components on Vidya 4719d6f nandi 20d ago167* Join/part notices, DMs bucketed under the sender's nick
168* Discover list, search over buffers, disconnect
Remember which rooms this client has been in 1d062fd nandi 20d ago169* The rooms you have opened, remembered across runs and listed in the order
170 you last used them (`$XDG_CONFIG_HOME/frq/channels.edn`)
Order the chat list by what you were last in 75e557e nandi 20d ago171* Conversations listed most recently opened first
Show who is talking, with their Bluesky picture f5548bd nandi 20d ago172* Bluesky avatars beside the sender, resolved from the DID freeq tags each
173 message with
A freeq client in jolt, as glimmer components on Vidya 4719d6f nandi 20d ago174
175## Limits
176
177* **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 21h ago178* **No `did:key` signing, no credential gates, no E2EE.**
Remember an OAuth sign-in across restarts 4aa71e7 nandi 20d ago179* **Only the broker token is persisted**, and only for OAuth. An app-password
180 sign-in is not remembered.
One frontend where there were three, and a core that is not Clojure 438b247 nandi 21h ago181* **Previews are PNG only.** The link is left in place either way.
Show the pictures people paste 56cdac3 nandi 20d ago182* **Nothing evicts the media cache.**
One frontend where there were three, and a core that is not Clojure 438b247 nandi 21h ago183* **No calls.** The AV signaling is still in the screens, but the media plane
184 it drove was `libjoltmoq` under the retired jolt half — Opus, H.264, V4L2 and
185 ALSA, none of which crosses to Flutter. The Call controls are wired to
186 actions no target installs. Flutter's camera and audio plugins are the way
187 back in, and that is its own project.
188* **Attaching a picture needs a sign-in.** The upload is filed under the DID
189 of a live session, so a guest cannot make one. It also shares nothing to your
190 PDS and posts nothing to Bluesky — those fields are opt-in and this client
191 does not send them.
Calls f31ad3d nandi 19d ago192* **No scrollback trimming or threads.**
One frontend where there were three, and a core that is not Clojure 438b247 nandi 21h ago193* A sent line waits up to 200ms for the reader to flush it.