nandi/frqpublic Fork 0
3975b4bc9fe9fd59fc0e81ca2bb7ffdba986934a
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 · 132 lines · 6.0 KBmarkdown Blame HistoryRaw
Read the clock in the reader's zone when /etc/localtime is a file bffe879 nandi 18d ago1# Working in this repo
2
Six verbs, and the last of the nix 2e24e64 nandi 13h ago3## The toolchain, and where builds happen
Read the clock in the reader's zone when /etc/localtime is a file bffe879 nandi 18d ago4
Six verbs, and the last of the nix 2e24e64 nandi 13h ago5There is no nix in the build any more. `tools/toolchain.sh` fetches Flutter
6(which carries Dart), a JDK, the Clojure CLI and Nim as sha256-pinned tarballs
7into `.toolchain/`, and every `just` recipe runs inside the environment that
8script prints. `just tools android` adds Google's command-line tools, which is
9what `just build apk` needs before Gradle can have sdkmanager finish the SDK
10off. The host still brings a C compiler, OpenSSL, git, curl, unzip and
11python3 — and GTK with the usual CMake/Ninja/pkg-config for the Linux targets.
12
13**Prefer Modal for a long build.** A cold Flutter toolchain plus a full
14compile is a lot of laptop, and the containers in `.modal/` do it on a real
15machine:
Read the clock in the reader's zone when /etc/localtime is a file bffe879 nandi 18d ago16
17```bash
Six verbs, and the last of the nix 2e24e64 nandi 13h ago18just modal web # the web bundle, on Modal
19just modal dev # the incremental Flutter loop
Read the clock in the reader's zone when /etc/localtime is a file bffe879 nandi 18d ago20```
21
One frontend where there were three, and a core that is not Clojure 438b247 nandi 17h ago22The containers in `.modal/` are not a third source tree: they are CI config
Six verbs, and the last of the nix 2e24e64 nandi 13h ago23that 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 5d 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
Six verbs, and the last of the nix 2e24e64 nandi 13h ago31The containers run as Modal **Sandboxes on a real VM** rather than under
32gVisor: a real kernel, a working pty, and memory that is exactly what
33`[resources] memory` asks for.
Read the clock in the reader's zone when /etc/localtime is a file bffe879 nandi 18d ago34
Follow the xorg renames, and stop wrapping nix in distrobox a400a00 nandi 16d ago35One thing this container is *not* representative of: `/etc/localtime` is a
36regular 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 18d ago37of its path sees nothing. That is a real deployment shape, not an artefact —
38frq.clock handles it.
Split the tree three ways, and let the phone be Flutter's 7070931 nandi 7d ago39
Build the window somewhere with room for it 1451151 nandi 5d ago40## Never pipe a long task through `tail`
41
42`tail` and `head` do not emit anything until their input ends, so a build, a
43test run or a deploy piped through one shows nothing at all until it is over —
44and if it is killed or times out first, its output is lost with it. That is the
45opposite of what you want from the commands that take longest.
46
47Let them write to the terminal, or `tee` them if you want a copy to grep
48afterwards:
49
50```bash
Six verbs, and the last of the nix 2e24e64 nandi 13h ago51modal run .modal/web/container.py 2>&1 | tee /tmp/frq-build.log
Build the window somewhere with room for it 1451151 nandi 5d ago52```
53
54Trim afterwards, on the file, where the whole run is still there to re-read.
55The same goes for `grep` and `awk` in a live pipeline: they buffer when their
56output is not a terminal, so pass `--line-buffered` / `fflush()` or watch the
57file instead.
58
One frontend where there were three, and a core that is not Clojure 438b247 nandi 17h ago59## The Nim core
60
61`nim/` is the portable logic, moving out of `common/` one module at a time as
62a native library the Dart side calls through FFI. Read `nim/README.md` before
63touching it — in particular the status section, which says what is actually
64wired up (the Nim half and its ABI) and what is not (the Dart binding).
65
66Two rules the ABI has, both of which cost a segfault to rediscover:
67
68* Every string the core returns is the **caller's** to free, with `frq_free`.
69 Nim's allocator is not Dart's.
70* `frq_init` runs once before anything else.
71
The binding is Dart, and it works f7aea3b nandi 17h ago72`dart/frq_core` is the other half of that seam, and is **plain Dart**. New
73code on the Dart side of the boundary is written in Dart rather than
74ClojureDart — the core exists to have less Clojure in the tree, and adding
75more of it to call the thing replacing it is the wrong direction. ClojureDart
76shrinks from both ends.
77
Six verbs, and the last of the nix 2e24e64 nandi 13h ago78`just test nim` and `just test dart` need no Flutter, which is most of the
The binding is Dart, and it works f7aea3b nandi 17h ago79point: the whole boundary is checkable in about a second.
One frontend where there were three, and a core that is not Clojure 438b247 nandi 17h ago80
81A module is not deleted from `common/` when its Nim version lands: the web
82target cannot load a native library, so the ClojureDart original is the web's
83implementation until there is a wasm build. Deleting one would take the web
84build with it.
85
86## The two source trees
Split the tree three ways, and let the phone be Flutter's 7070931 nandi 7d ago87
88```
One frontend where there were three, and a core that is not Clojure 438b247 nandi 17h ago89common/ .cljc portable — every target compiles it
90flutter/ .cljd the Flutter half, and the host implementations
Split the tree three ways, and let the phone be Flutter's 7070931 nandi 7d ago91```
92
93The extension is the boundary, not a convention: ClojureDart reads `.cljd` and
One frontend where there were three, and a core that is not Clojure 438b247 nandi 17h ago94`.cljc` and never `.clj`. The rule for anything under `common/` is that it may
95not require a `dart:` library — if it needs the host, it asks `frq.io`, and the
96implementation that installed itself answers. `frq.io.dart` is installed by
97`flutter/src/frq/main.cljd`, which has to await the storage directory first;
98`frq.io.web` by `main_web.cljd`.
Split the tree three ways, and let the phone be Flutter's 7070931 nandi 7d ago99
100Adding a host call means adding it to the seam in `common/frq/io.cljc` and to
One frontend where there were three, and a core that is not Clojure 438b247 nandi 17h ago101every implementation. Name it for the result rather than the mechanism — the
Split the tree three ways, and let the phone be Flutter's 7070931 nandi 7d ago102seam has `write-private-file!` and not a chmod, because Dart has no chmod.
103
One frontend where there were three, and a core that is not Clojure 438b247 nandi 17h ago104There used to be a third tree, `src/`, and a second runtime under it: jolt,
105glimmer, and a libcosmic desktop window painting the same screens. It is gone,
106along with `just cosmic`, `just tui`, the AV/MoQ media plane and the native
107objects they loaded. Flutter is the only frontend now, and `common/` is
108compiled by one compiler rather than two — which is why the `#?(:jolt ...)`
109reader conditionals that used to be scattered through it are not there any
110more. `tools/check-common.py` still guards the seam, and CI still runs it on
111every push.
112
113`flutter/` builds three things, from one `clojure -M:cljd compile`:
Paint the desktop with Flutter too, beside libcosmic fc5f63c nandi 7d ago114
Six verbs, and the last of the nix 2e24e64 nandi 13h ago115`just build apk`. Impure on purpose: Gradle resolves its own dependencies over
116the network and has sdkmanager install a platform and build-tools into
117`ANDROID_HOME` as it goes, which is why that SDK lives in `.toolchain/` and is
118ours to write to.
Paint the desktop with Flutter too, beside libcosmic fc5f63c nandi 7d ago119
Six verbs, and the last of the nix 2e24e64 nandi 13h ago120`just build desktop`, Flutter's Linux target — CMake, Ninja, pkg-config and
121GTK from the host where the APK wants a JDK and an SDK. Impure for the network
122half of the same reasons.
One frontend where there were three, and a core that is not Clojure 438b247 nandi 17h ago123
Six verbs, and the last of the nix 2e24e64 nandi 13h ago124`just build web`, which needs least of all: a Dart, a JVM and a browser, and
125the browser is not ours. That is what lets `.modal/web/` run the same
126`tools/build-web.sh` on a plain Debian image.
One frontend where there were three, and a core that is not Clojure 438b247 nandi 17h ago127
128The consequence for `common/` is that "the phone" is not a synonym for "the
129ClojureDart side": three targets compile it. An implementation that branches on
130the platform has to ask (`Platform.isAndroid`) rather than assume; see
Paint the desktop with Flutter too, beside libcosmic fc5f63c nandi 7d ago131`frq.io.dart/write-private-file!`, where assuming cost a token its file mode.
132See flutter/README.md.