| Read the clock in the reader's zone when /etc/localtime is a file bffe879 nandi 18d ago | 1 | # Working in this repo |
| 2 | |
| Six verbs, and the last of the nix 2e24e64 nandi 17h ago | 3 | ## The toolchain, and where builds happen |
| Read the clock in the reader's zone when /etc/localtime is a file bffe879 nandi 18d ago | 4 | |
| Six verbs, and the last of the nix 2e24e64 nandi 17h ago | 5 | There 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 |
| 7 | into `.toolchain/`, and every `just` recipe runs inside the environment that |
| 8 | script prints. `just tools android` adds Google's command-line tools, which is |
| 9 | what `just build apk` needs before Gradle can have sdkmanager finish the SDK |
| 10 | off. The host still brings a C compiler, OpenSSL, git, curl, unzip and |
| 11 | python3 — 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 |
| 14 | compile is a lot of laptop, and the containers in `.modal/` do it on a real |
| 15 | machine: |
| Read the clock in the reader's zone when /etc/localtime is a file bffe879 nandi 18d ago | 16 | |
| 17 | ```bash |
| Six verbs, and the last of the nix 2e24e64 nandi 17h ago | 18 | just modal web # the web bundle, on Modal |
| 19 | just modal dev # the incremental Flutter loop |
| Read the clock in the reader's zone when /etc/localtime is a file bffe879 nandi 18d ago | 20 | ``` |
| 21 | |
| One frontend where there were three, and a core that is not Clojure 438b247 nandi 21h ago | 22 | The containers in `.modal/` are not a third source tree: they are CI config |
| Six verbs, and the last of the nix 2e24e64 nandi 17h ago | 23 | that 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 ago | 25 | |
| 26 | `modal app logs` is no substitute for watching that command: it resolves |
| 27 | deployed apps by name, not the ephemeral one a `modal run` creates, and carries |
| 28 | nothing until the Sandbox starts — the image build streams to the client and |
| 29 | nowhere else. |
| 30 | |
| Six verbs, and the last of the nix 2e24e64 nandi 17h ago | 31 | The containers run as Modal **Sandboxes on a real VM** rather than under |
| 32 | gVisor: 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 ago | 34 | |
| Follow the xorg renames, and stop wrapping nix in distrobox a400a00 nandi 16d ago | 35 | One thing this container is *not* representative of: `/etc/localtime` is a |
| 36 | regular 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 ago | 37 | of its path sees nothing. That is a real deployment shape, not an artefact — |
| 38 | frq.clock handles it. |
| Split the tree three ways, and let the phone be Flutter's 7070931 nandi 7d ago | 39 | |
| Build the window somewhere with room for it 1451151 nandi 5d ago | 40 | ## Never pipe a long task through `tail` |
| 41 | |
| 42 | `tail` and `head` do not emit anything until their input ends, so a build, a |
| 43 | test run or a deploy piped through one shows nothing at all until it is over — |
| 44 | and if it is killed or times out first, its output is lost with it. That is the |
| 45 | opposite of what you want from the commands that take longest. |
| 46 | |
| 47 | Let them write to the terminal, or `tee` them if you want a copy to grep |
| 48 | afterwards: |
| 49 | |
| 50 | ```bash |
| Six verbs, and the last of the nix 2e24e64 nandi 17h ago | 51 | modal run .modal/web/container.py 2>&1 | tee /tmp/frq-build.log |
| Build the window somewhere with room for it 1451151 nandi 5d ago | 52 | ``` |
| 53 | |
| 54 | Trim afterwards, on the file, where the whole run is still there to re-read. |
| 55 | The same goes for `grep` and `awk` in a live pipeline: they buffer when their |
| 56 | output is not a terminal, so pass `--line-buffered` / `fflush()` or watch the |
| 57 | file instead. |
| 58 | |
| One frontend where there were three, and a core that is not Clojure 438b247 nandi 21h ago | 59 | ## The Nim core |
| 60 | |
| 61 | `nim/` is the portable logic, moving out of `common/` one module at a time as |
| 62 | a native library the Dart side calls through FFI. Read `nim/README.md` before |
| 63 | touching it — in particular the status section, which says what is actually |
| 64 | wired up (the Nim half and its ABI) and what is not (the Dart binding). |
| 65 | |
| 66 | Two 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 20h ago | 72 | `dart/frq_core` is the other half of that seam, and is **plain Dart**. New |
| 73 | code on the Dart side of the boundary is written in Dart rather than |
| 74 | ClojureDart — the core exists to have less Clojure in the tree, and adding |
| 75 | more of it to call the thing replacing it is the wrong direction. ClojureDart |
| 76 | shrinks from both ends. |
| 77 | |
| Six verbs, and the last of the nix 2e24e64 nandi 17h ago | 78 | `just test nim` and `just test dart` need no Flutter, which is most of the |
| The binding is Dart, and it works f7aea3b nandi 20h ago | 79 | point: the whole boundary is checkable in about a second. |
| One frontend where there were three, and a core that is not Clojure 438b247 nandi 21h ago | 80 | |
| 81 | A module is not deleted from `common/` when its Nim version lands: the web |
| 82 | target cannot load a native library, so the ClojureDart original is the web's |
| 83 | implementation until there is a wasm build. Deleting one would take the web |
| 84 | build with it. |
| 85 | |
| 86 | ## The two source trees |
| Split the tree three ways, and let the phone be Flutter's 7070931 nandi 7d ago | 87 | |
| 88 | ``` |
| One frontend where there were three, and a core that is not Clojure 438b247 nandi 21h ago | 89 | common/ .cljc portable — every target compiles it |
| 90 | flutter/ .cljd the Flutter half, and the host implementations |
| Split the tree three ways, and let the phone be Flutter's 7070931 nandi 7d ago | 91 | ``` |
| 92 | |
| 93 | The 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 21h ago | 94 | `.cljc` and never `.clj`. The rule for anything under `common/` is that it may |
| 95 | not require a `dart:` library — if it needs the host, it asks `frq.io`, and the |
| 96 | implementation 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 ago | 99 | |
| 100 | Adding 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 21h ago | 101 | every 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 ago | 102 | seam 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 21h ago | 104 | There used to be a third tree, `src/`, and a second runtime under it: jolt, |
| 105 | glimmer, and a libcosmic desktop window painting the same screens. It is gone, |
| 106 | along with `just cosmic`, `just tui`, the AV/MoQ media plane and the native |
| 107 | objects they loaded. Flutter is the only frontend now, and `common/` is |
| 108 | compiled by one compiler rather than two — which is why the `#?(:jolt ...)` |
| 109 | reader conditionals that used to be scattered through it are not there any |
| 110 | more. `tools/check-common.py` still guards the seam, and CI still runs it on |
| 111 | every push. |
| 112 | |
| 113 | `flutter/` builds three things, from one `clojure -M:cljd compile`: |
| Paint the desktop with Flutter too, beside libcosmic fc5f63c nandi 7d ago | 114 | |
| Six verbs, and the last of the nix 2e24e64 nandi 17h ago | 115 | `just build apk`. Impure on purpose: Gradle resolves its own dependencies over |
| 116 | the 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 |
| 118 | ours to write to. |
| Paint the desktop with Flutter too, beside libcosmic fc5f63c nandi 7d ago | 119 | |
| Six verbs, and the last of the nix 2e24e64 nandi 17h ago | 120 | `just build desktop`, Flutter's Linux target — CMake, Ninja, pkg-config and |
| 121 | GTK from the host where the APK wants a JDK and an SDK. Impure for the network |
| 122 | half of the same reasons. |
| One frontend where there were three, and a core that is not Clojure 438b247 nandi 21h ago | 123 | |
| Six verbs, and the last of the nix 2e24e64 nandi 17h ago | 124 | `just build web`, which needs least of all: a Dart, a JVM and a browser, and |
| 125 | the 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 21h ago | 127 | |
| 128 | The consequence for `common/` is that "the phone" is not a synonym for "the |
| 129 | ClojureDart side": three targets compile it. An implementation that branches on |
| 130 | the platform has to ask (`Platform.isAndroid`) rather than assume; see |
| Paint the desktop with Flutter too, beside libcosmic fc5f63c nandi 7d ago | 131 | `frq.io.dart/write-private-file!`, where assuming cost a token its file mode. |
| 132 | See flutter/README.md. |