nandi/frqpublic Fork 0
463098d16892952fa740dea5029203611ddab1db
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 19h 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 19h 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 19h 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 19h 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 18h 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 18h 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 19h 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 19h 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 19h 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
Six verbs, and the last of the nix 2e24e64 nandi 14h ago56calls and terminal sections that used to be here are not.
One frontend where there were three, and a core that is not Clojure 438b247 nandi 19h ago57
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
Six verbs, and the last of the nix 2e24e64 nandi 14h ago66just run desktop # the Linux window
67just run apk # onto a connected Android device
68just run web # a browser, on :8080
A freeq client in jolt, as glimmer components on Vidya 4719d6f nandi 20d ago69```
70
Six verbs, and the last of the nix 2e24e64 nandi 14h ago71Every recipe lives in the `justfile` itself, and none of them needs Nix:
72`tools/toolchain.sh` fetches Flutter, a JDK, the Clojure CLI and Nim by
73sha256 into `.toolchain/`, and every recipe runs inside that. It is the same
74script `.modal/` runs, which is what lets a plain Debian image build this.
75`just build apk` additionally asks it for Google's command-line tools, and
76sdkmanager finishes the Android SDK off.
One frontend where there were three, and a core that is not Clojure 438b247 nandi 19h ago77
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 19h 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 19h 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
Six verbs, and the last of the nix 2e24e64 nandi 14h ago92back to: `just serve` 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 19h 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 19h 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 19h 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 19h 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 19h 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 19h 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 19h 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 19h 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 19h 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 19h ago193* A sent line waits up to 200ms for the reader to flush it.