nandi/frqpublic Fork 0
1d62d1a437b844c37e3e4d49efa7e255ef4deac7
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 · 194 lines · 9.7 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 20h 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 20h 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 20h 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 20h 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
The binding is Dart, and it works f7aea3b nandi 20h ago20dart/ .dart the binding to it, and not a Flutter package — dart/README.md
Split the tree three ways, and let the phone be Flutter's 7070931 nandi 7d ago21```
22
The binding is Dart, and it works f7aea3b nandi 20h ago23The first two are the client as it runs today. `nim/` is where the logic under
24the screens is moving, a module at a time, behind a C ABI; `dart/` is what
25calls it. One module has made the trip so far and nothing imports it yet — see
26`nim/README.md` for what is wired up and what is not.
One frontend where there were three, and a core that is not Clojure 438b247 nandi 20h ago27
28What `common/` needs of the host it asks `common/frq/io.cljc` for — the seam,
29named once and answered per target: `frq.io.dart` on Android and the desktop,
30`frq.io.web` in a browser.
Split the tree three ways, and let the phone be Flutter's 7070931 nandi 7d ago31
A freeq client in jolt, as glimmer components on Vidya 4719d6f nandi 20d ago32```
One frontend where there were three, and a core that is not Clojure 438b247 nandi 20h ago33common/frq/io.cljc the seam: filesystem, environment, config dir, clock
34common/frq/clock.cljc IRCv3 time tags → the reader's own zone
35common/frq/store.cljc the saved sign-in, mode 600 in the config directory
36common/frq/emoji.cljc the picker's catalog: every emoji and its name
37common/frq/rooms.cljc the rooms this client has been in, and their order
38common/frq/cells.cljc the atoms every screen reads
39common/frq/screens/ connect, chats, chat, discover, settings
40common/frq/irc/parse.cljc the IRC line parser, tags and all
41common/frq/irc/handshake.cljc SASL, driven from shared code
42common/frq/atproto/core.cljc handle → DID → PDS → session, and the SASL payloads
43common/frq/oauth/core.cljc the broker flow, as far as it is portable
44common/frq/msgsig.cljc message signatures
45flutter/src/frq/main.cljd the entry point: installs the host, then starts
46flutter/src/frq/hiccup.cljd the widget tags, as Flutter
47flutter/src/frq/net/ sockets: dart:io on native, WebSocket on the web
48flutter/src/frq/io/ the host's answers to the seam
A freeq client in jolt, as glimmer components on Vidya 4719d6f nandi 20d ago49```
50
One frontend where there were three, and a core that is not Clojure 438b247 nandi 20h ago51There used to be a third tree, `src/`, and another runtime under it: jolt, with
52[glimmer](https://github.com/jolt-lang/glimmer) components painted by
53**libcosmic** in a desktop window and by `libjolttui` in a terminal, plus an
54AV media plane over MoQ. It is gone. Flutter is the only frontend now, which is
55why `common/` no longer carries `#?(:jolt ...)` reader conditionals and why the
56calls, terminal and `nix run .#frq` sections that used to be here are not.
57
Stop mistaking a quiet connection for a closed one a225fb1 nandi 20d ago58## Tracing
59
60`FRQ_TRACE=1` prints every IRC line sent and received to stderr, which on
61Android is logcat.
62
A freeq client in jolt, as glimmer components on Vidya 4719d6f nandi 20d ago63## Running
64
65```bash
One frontend where there were three, and a core that is not Clojure 438b247 nandi 20h ago66just flutter-desktop run # the Linux window
67just apk run # onto a connected Android device
68just flutter-web serve # a browser, on :8080
A freeq client in jolt, as glimmer components on Vidya 4719d6f nandi 20d ago69```
70
One frontend where there were three, and a core that is not Clojure 438b247 nandi 20h ago71Every recipe lives in the `justfile` itself. The two that need a toolchain from
72Nix re-enter `nix develop` and come back to the same recipe, so `just
73flutter-desktop` and `nix develop .#flutter-desktop --command just
74flutter-desktop` are one code path rather than two. `just flutter-web` needs no
75Nix at all: `tools/toolchain.sh` fetches Flutter, a JDK and the Clojure CLI by
76sha256, which is what lets `.modal/flutter-web/` run the same script on a plain
77Debian image.
78
79All three are one `clojure -M:cljd compile` over `flutter/src` and `common/`,
80and differ only in which Flutter target runs afterwards.
Build the desktop libraries rather than fetching a release of them 0b81161 nandi 15d ago81
82frq 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 20h ago83the connect screen (or point it at `127.0.0.1`) for a local server's plain
84listener:
A freeq client in jolt, as glimmer components on Vidya 4719d6f nandi 20d ago85
86```bash
87cargo run --release --bin freeq-server # in the freeq checkout
88```
89
One frontend where there were three, and a core that is not Clojure 438b247 nandi 20h ago90A browser has no TCP, so the web build wants a WebSocket URL in the Server
91field — `wss://irc.freeq.at/irc`. And Bluesky sign-in only completes on
92`localhost`, because that is the one origin freeq's auth broker will redirect
93back to: `just web-local` is what serves the Modal-built bundle there.
Paint the same screens into a terminal ab83b42 nandi 17d ago94
Sign in with a Bluesky identity 5192124 nandi 20d ago95## Signing in
96
Sign in with Bluesky OAuth, through freeq's broker 2d4a377 nandi 20d ago97Three modes on the connect screen.
Sign in with a Bluesky identity 5192124 nandi 20d ago98
Sign in with Bluesky OAuth, through freeq's broker 2d4a377 nandi 20d ago99**Bluesky** (OAuth, the default way in) follows sleek's flow: frq binds a
100loopback port, puts it in `return_to`, and opens
101`auth.freeq.at/auth/login?handle=…`. The broker runs the OAuth dance with the
102PDS and redirects back to that port with the handoff in the URL *fragment*, so
103it never reaches a server as a query string. The page frq serves there has one
104job: POST the fragment back to itself. What comes back is a single-use SASL
105`web-token` and a durable `broker_token`; later connections mint a fresh token
106from the durable one at `/session` and skip the browser.
Sign in with a Bluesky identity 5192124 nandi 20d ago107
Remember an OAuth sign-in across restarts 4aa71e7 nandi 20d ago108The 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 ago109a restart resumes without one, along with the handle and nick it belongs to —
110and it connects on its own at launch when one is there.
Remember an OAuth sign-in across restarts 4aa71e7 nandi 20d ago111The web-token beside it is single-use and deliberately not saved. A token the
112broker no longer honours is dropped — from disk and memory — and the browser
113flow runs once more, rather than failing the same way on every Connect.
114
Sign in with Bluesky OAuth, through freeq's broker 2d4a377 nandi 20d ago115**App password** signs in without a browser, straight to the user's own PDS:
116`resolveHandle` → DID → PDS from the DID document → `createSession`. The
117password goes to that PDS and nowhere else, is never written to disk, and is
118dropped once the session exists.
Sign in with a Bluesky identity 5192124 nandi 20d ago119
Sign in with Bluesky OAuth, through freeq's broker 2d4a377 nandi 20d ago120Either way freeq sees only a token. The SASL mechanism is
121`ATPROTO-CHALLENGE` in both cases — `method: "web-token"`, which the server
122resolves through its own token store, or `method: "pds-session"` with the
123server's nonce echoed back so the token cannot be replayed elsewhere.
124
125A refused sign-in is reported and the connection carries on as a guest.
Sign in with a Bluesky identity 5192124 nandi 20d ago126
One frontend where there were three, and a core that is not Clojure 438b247 nandi 20h ago127## Targets
A freeq client in jolt, as glimmer components on Vidya 4719d6f nandi 20d ago128
One frontend where there were three, and a core that is not Clojure 438b247 nandi 20h ago129Three, from one compile, and what separates them is the host half rather than
130the screens.
A freeq client in jolt, as glimmer components on Vidya 4719d6f nandi 20d ago131
One frontend where there were three, and a core that is not Clojure 438b247 nandi 20h ago132**Android** and **the Linux desktop** are both `dart:io` underneath:
133`frq.io.dart` answers the seam, `frq.net.dart` opens a real TCP or TLS socket.
134`frq.io.dart/write-private-file!` is the one place that asks which of the two it
135is on (`Platform.isAndroid`), because assuming cost a token its file mode.
A freeq client in jolt, as glimmer components on Vidya 4719d6f nandi 20d ago136
One frontend where there were three, and a core that is not Clojure 438b247 nandi 20h ago137**The web** is not: a browser has no TCP and no filesystem, so `frq.net.web`
138carries an IRC WebSocket and `frq.io.web` keeps the seam's files in local
139storage. Bluesky sign-in works there only on `localhost` — see Running.
A freeq client in jolt, as glimmer components on Vidya 4719d6f nandi 20d ago140
One frontend where there were three, and a core that is not Clojure 438b247 nandi 20h ago141What carries over untouched is `common/` — the screens, the state, the parser,
142the protocol. See the two trees at the top.
A freeq client in jolt, as glimmer components on Vidya 4719d6f nandi 20d ago143
144## What the PoC covers
145
One frontend where there were three, and a core that is not Clojure 438b247 nandi 20h ago146* 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 ago147* Guest connect (`NICK`/`USER`), `001` welcome, `PING`/`PONG` keepalive
148* Auto-joins `#test` on `irc.freeq.at`
149* Join channels, channel buffers with unread counts, send and receive `PRIVMSG`
Ask for the backlog freeq restores channels without 94e59a2 nandi 20d ago150* Backlog on join, and `CHATHISTORY` for the channels freeq restores instead
Say when each thing was said dbf3b86 nandi 20d ago151* Twelve-hour timestamps from the server's own clock, with a heading wherever
152 the day changes
Answer a message, not just read that one was answered 4876db8 nandi 20d ago153* A chip above a reply quoting what it answers, and a click that goes there;
154 ↩ beside a sender to answer them, with `+draft/reply` on the way out
Pick any emoji, in colour 18c5ccd nandi 20d ago155* 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 ago156 picker over every emoji the backend can draw (popular first, then Unicode's own
Pick any emoji, in colour 18c5ccd nandi 20d ago157 groups, searchable by name), and a second click on a pill to take yours off
158 — sent as `TAGMSG`, and restored from the server's own tally when the
159 backlog comes back
Show the pictures people paste 56cdac3 nandi 20d ago160* Inline previews for PNG links, fetched once and cached under
Click a picture to see it full size 563ada3 nandi 20d ago161 `$XDG_CACHE_HOME/frq/media`; click one to see it full size
Send a picture by pasting it 8a491ba nandi 20d ago162* Ctrl+V in the draft attaches the picture on the clipboard: it is previewed
163 under the box and uploaded to freeq's media endpoint while you write the line
164 it goes with, and only on the way out does it become the link — which is the
165 whole of what sending an image over IRC means. The draft itself is never
166 written into. Text pastes as text, as it always did: the picture path is the
167 keystroke the field had no text to answer with
A freeq client in jolt, as glimmer components on Vidya 4719d6f nandi 20d ago168* Join/part notices, DMs bucketed under the sender's nick
169* Discover list, search over buffers, disconnect
Remember which rooms this client has been in 1d062fd nandi 20d ago170* The rooms you have opened, remembered across runs and listed in the order
171 you last used them (`$XDG_CONFIG_HOME/frq/channels.edn`)
Order the chat list by what you were last in 75e557e nandi 20d ago172* Conversations listed most recently opened first
Show who is talking, with their Bluesky picture f5548bd nandi 20d ago173* Bluesky avatars beside the sender, resolved from the DID freeq tags each
174 message with
A freeq client in jolt, as glimmer components on Vidya 4719d6f nandi 20d ago175
176## Limits
177
178* **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 20h ago179* **No `did:key` signing, no credential gates, no E2EE.**
Remember an OAuth sign-in across restarts 4aa71e7 nandi 20d ago180* **Only the broker token is persisted**, and only for OAuth. An app-password
181 sign-in is not remembered.
One frontend where there were three, and a core that is not Clojure 438b247 nandi 20h ago182* **Previews are PNG only.** The link is left in place either way.
Show the pictures people paste 56cdac3 nandi 20d ago183* **Nothing evicts the media cache.**
One frontend where there were three, and a core that is not Clojure 438b247 nandi 20h ago184* **No calls.** The AV signaling is still in the screens, but the media plane
185 it drove was `libjoltmoq` under the retired jolt half — Opus, H.264, V4L2 and
186 ALSA, none of which crosses to Flutter. The Call controls are wired to
187 actions no target installs. Flutter's camera and audio plugins are the way
188 back in, and that is its own project.
189* **Attaching a picture needs a sign-in.** The upload is filed under the DID
190 of a live session, so a guest cannot make one. It also shares nothing to your
191 PDS and posts nothing to Bluesky — those fields are opt-in and this client
192 does not send them.
Calls f31ad3d nandi 19d ago193* **No scrollback trimming or threads.**
One frontend where there were three, and a core that is not Clojure 438b247 nandi 20h ago194* A sent line waits up to 200ms for the reader to flush it.