| Read the clock in the reader's zone when /etc/localtime is a file bffe879 nandi 19d ago | 1 | # Working in this repo |
| 2 | |
| Six verbs, and the last of the nix 2e24e64 nandi yesterday | 3 | ## The toolchain, and where builds happen |
| Read the clock in the reader's zone when /etc/localtime is a file bffe879 nandi 19d ago | 4 | |
| The last of the Clojure 284b59c nandi yesterday | 5 | There is no nix in the build. `tools/toolchain.sh` fetches Flutter (which |
| 6 | carries Dart) and Nim as sha256-pinned tarballs into `.toolchain/`, and every |
| 7 | `just` recipe runs inside the environment that script prints. The host brings |
| 8 | a C compiler, OpenSSL, git, curl, unzip and python3 — and GTK with the usual |
| 9 | CMake/Ninja/pkg-config for the Linux target. |
| 10 | |
| 11 | It used to carry a JDK, the Clojure CLI, a maven repo and an Android SDK as |
| 12 | well. Those were ClojureDart's and the APK's, and both are gone. |
| Six verbs, and the last of the nix 2e24e64 nandi yesterday | 13 | |
| 14 | **Prefer Modal for a long build.** A cold Flutter toolchain plus a full |
| 15 | compile is a lot of laptop, and the containers in `.modal/` do it on a real |
| 16 | machine: |
| Read the clock in the reader's zone when /etc/localtime is a file bffe879 nandi 19d ago | 17 | |
| 18 | ```bash |
| Six verbs, and the last of the nix 2e24e64 nandi yesterday | 19 | just modal dev # the incremental Flutter loop |
| Read the clock in the reader's zone when /etc/localtime is a file bffe879 nandi 19d ago | 20 | ``` |
| 21 | |
| One frontend where there were three, and a core that is not Clojure 438b247 nandi yesterday | 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 yesterday | 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 6d 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 | |
| CI builds the image, Modal serves it 5693bd5 nandi 8h ago | 31 | Two containers, and they are not the same kind of thing. `dev` is a build |
| 32 | that ends. `web` is a deploy: CI builds `.modal/web/Dockerfile` into the |
| 33 | GitLab registry, and `modal deploy .modal/web/container.py` serves that exact |
| 34 | tag at a URL, building nothing. So `just build web` on a laptop and the thing |
| 35 | on the internet come from the same two commands, run in different places — |
| 36 | and a deploy is a pull rather than a compile. |
| 37 | |
| Six verbs, and the last of the nix 2e24e64 nandi yesterday | 38 | The containers run as Modal **Sandboxes on a real VM** rather than under |
| 39 | gVisor: a real kernel, a working pty, and memory that is exactly what |
| 40 | `[resources] memory` asks for. |
| Read the clock in the reader's zone when /etc/localtime is a file bffe879 nandi 19d ago | 41 | |
| Follow the xorg renames, and stop wrapping nix in distrobox a400a00 nandi 17d ago | 42 | One thing this container is *not* representative of: `/etc/localtime` is a |
| 43 | 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 19d ago | 44 | of its path sees nothing. That is a real deployment shape, not an artefact — |
| 45 | frq.clock handles it. |
| Split the tree three ways, and let the phone be Flutter's 7070931 nandi 8d ago | 46 | |
| Build the window somewhere with room for it 1451151 nandi 6d ago | 47 | ## Never pipe a long task through `tail` |
| 48 | |
| 49 | `tail` and `head` do not emit anything until their input ends, so a build, a |
| 50 | test run or a deploy piped through one shows nothing at all until it is over — |
| 51 | and if it is killed or times out first, its output is lost with it. That is the |
| 52 | opposite of what you want from the commands that take longest. |
| 53 | |
| 54 | Let them write to the terminal, or `tee` them if you want a copy to grep |
| 55 | afterwards: |
| 56 | |
| 57 | ```bash |
| Six verbs, and the last of the nix 2e24e64 nandi yesterday | 58 | modal run .modal/web/container.py 2>&1 | tee /tmp/frq-build.log |
| Build the window somewhere with room for it 1451151 nandi 6d ago | 59 | ``` |
| 60 | |
| 61 | Trim afterwards, on the file, where the whole run is still there to re-read. |
| 62 | The same goes for `grep` and `awk` in a live pipeline: they buffer when their |
| 63 | output is not a terminal, so pass `--line-buffered` / `fflush()` or watch the |
| 64 | file instead. |
| 65 | |
| One frontend where there were three, and a core that is not Clojure 438b247 nandi yesterday | 66 | ## The Nim core |
| 67 | |
| The last of the Clojure 284b59c nandi yesterday | 68 | `nim/` is the program. It owns the state, the screens, the IRC connection and |
| 69 | the signing; Flutter is a renderer over the widget tree it emits. Read |
| 70 | `nim/README.md` before touching it. |
| One frontend where there were three, and a core that is not Clojure 438b247 nandi yesterday | 71 | |
| 72 | Two rules the ABI has, both of which cost a segfault to rediscover: |
| 73 | |
| 74 | * Every string the core returns is the **caller's** to free, with `frq_free`. |
| 75 | Nim's allocator is not Dart's. |
| 76 | * `frq_init` runs once before anything else. |
| 77 | |
| The last of the Clojure 284b59c nandi yesterday | 78 | `dart/frq_core` is the other half of that seam. It is a plain Dart package and |
| 79 | not a Flutter one, deliberately: `flutter/pubspec.yaml` depends on the Flutter |
| 80 | SDK, so anything living there needs a Flutter toolchain to check one assertion |
| 81 | about a string, where this resolves and tests on its own. |
| 82 | |
| 83 | The rule that used to be here said new Dart-side code is written in Dart |
| 84 | rather than ClojureDart. There is no ClojureDart left for it to rule against, |
| 85 | but the reasoning it rested on still holds for the next thing: logic goes in |
| 86 | Nim, the platform goes in Dart, and neither is written in a third language |
| 87 | because it is already open. |
| The binding is Dart, and it works f7aea3b nandi yesterday | 88 | |
| Six verbs, and the last of the nix 2e24e64 nandi yesterday | 89 | `just test nim` and `just test dart` need no Flutter, which is most of the |
| The binding is Dart, and it works f7aea3b nandi yesterday | 90 | 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 yesterday | 91 | |
| The last of the Clojure 284b59c nandi yesterday | 92 | There is no `common/` any more, and that rule went with it. It said a module |
| 93 | stays until there is a wasm build of the core, because a browser has no |
| A web version, from the same core 23846db nandi 9h ago | 94 | dart:ffi. The premise was right and the conclusion was wrong: the answer was |
| 95 | not wasm but `nim js`, which compiles the same core — state, reducer, every |
| 96 | screen — to JavaScript that a page loads with a `<script>` tag. |
| 97 | |
| 98 | So there is a web target again, `just build web`. What differs from the |
| 99 | desktop is only the host: `nim/web/frq/*.nim` shadows `nim/src/frq/*.nim` by |
| 100 | search path (`--path:src --path:web`, later wins), so `frq/conn` is a queue a |
| 101 | WebSocket fills rather than two socket threads, `frq/store` is localStorage, |
| 102 | and `frq/crypto` says plainly that it cannot sign. The shared code above them |
| 103 | imports the same names either way and never learns which host it is on. Dart |
| 104 | does 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 yesterday | 105 | |
| The last of the Clojure 284b59c nandi yesterday | 106 | ## The source trees |
| Split the tree three ways, and let the phone be Flutter's 7070931 nandi 8d ago | 107 | |
| 108 | ``` |
| A web version, from the same core 23846db nandi 9h ago | 109 | nim/src the program: state, screens, IRC, signing |
| 110 | nim/web the same program's host half, for a browser |
| 111 | dart/frq_core the binding — plain Dart, not a Flutter package |
| The last of the Clojure 284b59c nandi yesterday | 112 | flutter/lib the renderer, and the app's entry point |
| A web version, from the same core 23846db nandi 9h ago | 113 | flutter/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 ago | 114 | ``` |
| 115 | |
| The last of the Clojure 284b59c nandi yesterday | 116 | `nim/src/frq/ui.nim` builds a widget tree; `frq_core` carries it across the |
| 117 | FFI as JSON; `flutter/lib/nim_renderer.dart` walks it into Flutter widgets. |
| 118 | The renderer knows the tag vocabulary and nothing else — no screens, no state, |
| 119 | no idea what "connect" means. If a feature ever needs a change on both sides, |
| 120 | the boundary is in the wrong place. |
| 121 | |
| 122 | There used to be two more trees. `src/` was jolt and libcosmic; `common/` and |
| 123 | `flutter/src/` were ClojureDart, compiled for Android, Linux and the web. Both |
| A web version, from the same core 23846db nandi 9h ago | 124 | are gone. The APK went with them and has not come back — it wants |
| 125 | `libfrqcore.so` cross-compiled for Android's ABIs — but the web target has, |
| 126 | by a different road than the one that was expected: `just build web`. |
| 127 | |
| 128 | Three things the web build does not do, all of them written down where they |
| 129 | are done rather than only here. It cannot sign a message, because Ed25519 in |
| 130 | a browser is asynchronous and every signature here is wanted inline, so a |
| 131 | reader is in a guest's position for reactions and edits. It has no |
| 132 | app-password tab, because that wants a blocking call to the reader's own PDS. |
| 133 | And it does not keep a broker token, because `localStorage` is readable by |
| 134 | every script the origin runs. |
| The last of the Clojure 284b59c nandi yesterday | 135 | |
| 136 | Two modules were never ported and are gone rather than moved: `frq.profile` |
| 137 | (the Bluesky profile behind a nick) and `frq.replies` (asking freeq what a |
| 138 | collapsed msgid was). Neither had a screen in the Nim app to appear on. |
| 139 | |