nandi/frqpublic Fork 0
bd10e816d10d09c3dbe1256c1b7c661d8e190ee6
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.

CLAUDE.md · 146 lines · 6.9 KBmarkdown Blame HistoryRaw
Read the clock in the reader's zone when /etc/localtime is a file bffe879 nandi 19d ago1# Working in this repo
2
Six verbs, and the last of the nix 2e24e64 nandi yesterday3## The toolchain, and where builds happen
Read the clock in the reader's zone when /etc/localtime is a file bffe879 nandi 19d ago4
The last of the Clojure 284b59c nandi yesterday5There is no nix in the build. `tools/toolchain.sh` fetches Flutter (which
6carries Dart) and Nim as sha256-pinned tarballs into `.toolchain/`, and every
7`just` recipe runs inside the environment that script prints. The host brings
8a C compiler, OpenSSL, git, curl, unzip and python3 — and GTK with the usual
9CMake/Ninja/pkg-config for the Linux target.
10
11It used to carry a JDK, the Clojure CLI, a maven repo and an Android SDK as
12well. Those were ClojureDart's and the APK's, and both are gone.
Six verbs, and the last of the nix 2e24e64 nandi yesterday13
14**Prefer Modal for a long build.** A cold Flutter toolchain plus a full
15compile is a lot of laptop, and the containers in `.modal/` do it on a real
16machine:
Read the clock in the reader's zone when /etc/localtime is a file bffe879 nandi 19d ago17
18```bash
Six verbs, and the last of the nix 2e24e64 nandi yesterday19just modal dev # the incremental Flutter loop
Read the clock in the reader's zone when /etc/localtime is a file bffe879 nandi 19d ago20```
21
One frontend where there were three, and a core that is not Clojure 438b247 nandi yesterday22The containers in `.modal/` are not a third source tree: they are CI config
Six verbs, and the last of the nix 2e24e64 nandi yesterday23that happens to live here, the way `.github/` would be. They run the very same
24`tools/toolchain.sh`, which is why a plain Debian image is enough.
Build the window somewhere with room for it 1451151 nandi 6d ago25
26`modal app logs` is no substitute for watching that command: it resolves
27deployed apps by name, not the ephemeral one a `modal run` creates, and carries
28nothing until the Sandbox starts — the image build streams to the client and
29nowhere else.
30
The web container is plain Modal bd10e81 nandi 10h ago31Two containers, and they are not the same kind of thing — which is why they
32are not written the same way. `dev` is a build that ends, and is a
33`container.toml` read by `_loader.py`: a sandbox with a volume, a toolchain
34and a command that changes. `web` is a deploy, and is plain Modal in
35`.modal/web/app.py`, because four constants and a `Popen` did not need a spec
36file to be read before the file itself made sense.
37
38`web` is a deploy: rickub builds `.modal/web/Dockerfile` into
The registry it pushes to is the one it has 627b785 nandi 10h ago39`registry.rickub.com` (`.rickub/workflows/web.yml`), and
The web container is plain Modal bd10e81 nandi 10h ago40`modal deploy .modal/web/app.py` serves that exact tag at a URL,
The registry it pushes to is the one it has 627b785 nandi 10h ago41building nothing. So `just build web` on a laptop and the thing
CI builds the image, Modal serves it 5693bd5 nandi 10h ago42on the internet come from the same two commands, run in different places —
43and a deploy is a pull rather than a compile.
44
Six verbs, and the last of the nix 2e24e64 nandi yesterday45The containers run as Modal **Sandboxes on a real VM** rather than under
46gVisor: a real kernel, a working pty, and memory that is exactly what
47`[resources] memory` asks for.
Read the clock in the reader's zone when /etc/localtime is a file bffe879 nandi 19d ago48
Follow the xorg renames, and stop wrapping nix in distrobox a400a00 nandi 17d ago49One thing this container is *not* representative of: `/etc/localtime` is a
50regular file here rather than a symlink, so anything that reads the zone out
Read the clock in the reader's zone when /etc/localtime is a file bffe879 nandi 19d ago51of its path sees nothing. That is a real deployment shape, not an artefact —
52frq.clock handles it.
Split the tree three ways, and let the phone be Flutter's 7070931 nandi 8d ago53
Build the window somewhere with room for it 1451151 nandi 6d ago54## Never pipe a long task through `tail`
55
56`tail` and `head` do not emit anything until their input ends, so a build, a
57test run or a deploy piped through one shows nothing at all until it is over —
58and if it is killed or times out first, its output is lost with it. That is the
59opposite of what you want from the commands that take longest.
60
61Let them write to the terminal, or `tee` them if you want a copy to grep
62afterwards:
63
64```bash
The web container is plain Modal bd10e81 nandi 10h ago65modal run .modal/dev/container.py 2>&1 | tee /tmp/frq-build.log
Build the window somewhere with room for it 1451151 nandi 6d ago66```
67
68Trim afterwards, on the file, where the whole run is still there to re-read.
69The same goes for `grep` and `awk` in a live pipeline: they buffer when their
70output is not a terminal, so pass `--line-buffered` / `fflush()` or watch the
71file instead.
72
One frontend where there were three, and a core that is not Clojure 438b247 nandi yesterday73## The Nim core
74
The last of the Clojure 284b59c nandi yesterday75`nim/` is the program. It owns the state, the screens, the IRC connection and
76the signing; Flutter is a renderer over the widget tree it emits. Read
77`nim/README.md` before touching it.
One frontend where there were three, and a core that is not Clojure 438b247 nandi yesterday78
79Two rules the ABI has, both of which cost a segfault to rediscover:
80
81* Every string the core returns is the **caller's** to free, with `frq_free`.
82 Nim's allocator is not Dart's.
83* `frq_init` runs once before anything else.
84
The last of the Clojure 284b59c nandi yesterday85`dart/frq_core` is the other half of that seam. It is a plain Dart package and
86not a Flutter one, deliberately: `flutter/pubspec.yaml` depends on the Flutter
87SDK, so anything living there needs a Flutter toolchain to check one assertion
88about a string, where this resolves and tests on its own.
89
90The rule that used to be here said new Dart-side code is written in Dart
91rather than ClojureDart. There is no ClojureDart left for it to rule against,
92but the reasoning it rested on still holds for the next thing: logic goes in
93Nim, the platform goes in Dart, and neither is written in a third language
94because it is already open.
The binding is Dart, and it works f7aea3b nandi yesterday95
Six verbs, and the last of the nix 2e24e64 nandi yesterday96`just test nim` and `just test dart` need no Flutter, which is most of the
The binding is Dart, and it works f7aea3b nandi yesterday97point: the whole boundary is checkable in about a second.
One frontend where there were three, and a core that is not Clojure 438b247 nandi yesterday98
The last of the Clojure 284b59c nandi yesterday99There is no `common/` any more, and that rule went with it. It said a module
100stays until there is a wasm build of the core, because a browser has no
A web version, from the same core 23846db nandi 12h ago101dart:ffi. The premise was right and the conclusion was wrong: the answer was
102not wasm but `nim js`, which compiles the same core — state, reducer, every
103screen — to JavaScript that a page loads with a `<script>` tag.
104
105So there is a web target again, `just build web`. What differs from the
106desktop is only the host: `nim/web/frq/*.nim` shadows `nim/src/frq/*.nim` by
107search path (`--path:src --path:web`, later wins), so `frq/conn` is a queue a
108WebSocket fills rather than two socket threads, `frq/store` is localStorage,
109and `frq/crypto` says plainly that it cannot sign. The shared code above them
110imports the same names either way and never learns which host it is on. Dart
111does the same thing one layer up, in `dart/frq_core/lib/src/host.dart`.
One frontend where there were three, and a core that is not Clojure 438b247 nandi yesterday112
The last of the Clojure 284b59c nandi yesterday113## The source trees
Split the tree three ways, and let the phone be Flutter's 7070931 nandi 8d ago114
115```
A web version, from the same core 23846db nandi 12h ago116nim/src the program: state, screens, IRC, signing
117nim/web the same program's host half, for a browser
118dart/frq_core the binding — plain Dart, not a Flutter package
The last of the Clojure 284b59c nandi yesterday119flutter/lib the renderer, and the app's entry point
A web version, from the same core 23846db nandi 12h ago120flutter/web the page, and the JavaScript that owns the socket
Split the tree three ways, and let the phone be Flutter's 7070931 nandi 8d ago121```
122
The last of the Clojure 284b59c nandi yesterday123`nim/src/frq/ui.nim` builds a widget tree; `frq_core` carries it across the
124FFI as JSON; `flutter/lib/nim_renderer.dart` walks it into Flutter widgets.
125The renderer knows the tag vocabulary and nothing else — no screens, no state,
126no idea what "connect" means. If a feature ever needs a change on both sides,
127the boundary is in the wrong place.
128
129There used to be two more trees. `src/` was jolt and libcosmic; `common/` and
130`flutter/src/` were ClojureDart, compiled for Android, Linux and the web. Both
A web version, from the same core 23846db nandi 12h ago131are gone. The APK went with them and has not come back — it wants
132`libfrqcore.so` cross-compiled for Android's ABIs — but the web target has,
133by a different road than the one that was expected: `just build web`.
134
135Three things the web build does not do, all of them written down where they
136are done rather than only here. It cannot sign a message, because Ed25519 in
137a browser is asynchronous and every signature here is wanted inline, so a
138reader is in a guest's position for reactions and edits. It has no
139app-password tab, because that wants a blocking call to the reader's own PDS.
140And it does not keep a broker token, because `localStorage` is readable by
141every script the origin runs.
The last of the Clojure 284b59c nandi yesterday142
143Two modules were never ported and are gone rather than moved: `frq.profile`
144(the Bluesky profile behind a nick) and `frq.replies` (asking freeq what a
145collapsed msgid was). Neither had a screen in the Nim app to appear on.
146