| Read the clock in the reader's zone when /etc/localtime is a file bffe879 nandi 18d ago | 1 | # Working in this repo |
| 2 | |
| 3 | ## Nix |
| 4 | |
| Build the window somewhere with room for it 1451151 nandi 5d ago | 5 | **STOP BUILDING LOCALLY. Build on Modal.** This machine is for editing and for |
| 6 | evaluating — `nix flake check`, `nix eval`, `nix build --dry-run`, `nix repl` |
| One frontend where there were three, and a core that is not Clojure 438b247 nandi 14h ago | 7 | — and not for realising a derivation. A local build of the whole graph gets |
| 8 | killed for memory long before it finishes, and the minutes spent finding that |
| 9 | out are minutes not spent on the change. So: |
| Read the clock in the reader's zone when /etc/localtime is a file bffe879 nandi 18d ago | 10 | |
| 11 | ```bash |
| One frontend where there were three, and a core that is not Clojure 438b247 nandi 14h ago | 12 | modal run .modal/flutter-web/container.py # the web bundle, on Modal |
| 13 | modal run .modal/flutter-dev/container.py # the incremental Flutter loop |
| Read the clock in the reader's zone when /etc/localtime is a file bffe879 nandi 18d ago | 14 | ``` |
| 15 | |
| One frontend where there were three, and a core that is not Clojure 438b247 nandi 14h ago | 16 | The containers in `.modal/` are not a third source tree: they are CI config |
| 17 | that happens to live here, the way `.github/` would be. |
| Point the docs at .modal/, and at the file they now build 972ab37 nandi 5d ago | 18 | |
| Build the window somewhere with room for it 1451151 nandi 5d ago | 19 | `--dry-run` locally to see what *would* be built, then hand the build to Modal. |
| 20 | The one exception is a derivation you already know is trivial and already |
| 21 | substitutable; if you are unsure, it is not the exception. |
| 22 | |
| 23 | `modal app logs` is no substitute for watching that command: it resolves |
| 24 | deployed apps by name, not the ephemeral one a `modal run` creates, and carries |
| 25 | nothing until the Sandbox starts — the image build streams to the client and |
| 26 | nowhere else. |
| 27 | |
| 28 | You are already running inside the Arch distrobox, where `nix` lives, so run |
| 29 | the evaluating commands directly — do not wrap them in `distrobox enter`. |
| 30 | |
| One frontend where there were three, and a core that is not Clojure 438b247 nandi 14h ago | 31 | The containers run as Modal **Sandboxes on a real VM**, which is what makes a |
| 32 | build work out there at all: the ptyshim that used to stand in for a working |
| 33 | pty under gVisor is deprecated, and nothing here should reintroduce it. Neither |
| 34 | container carries nix: `tools/toolchain.sh` fetches Flutter, a JDK and the |
| 35 | Clojure CLI by sha256 onto the `devshell` Volume, and the build runs out of |
| 36 | those. |
| Build the window somewhere with room for it 1451151 nandi 5d ago | 37 | |
| 38 | A remote builder (`eu.nixbuild.net`) is also configured here, for the case |
| One frontend where there were three, and a core that is not Clojure 438b247 nandi 14h ago | 39 | where you want a derivation built somewhere other than Modal: `--store |
| Build the window somewhere with room for it 1451151 nandi 5d ago | 40 | ssh-ng://eu.nixbuild.net --eval-store auto` rather than a `builders` entry, so |
| 41 | the whole graph stays there and only .drv files go up. |
| Read the clock in the reader's zone when /etc/localtime is a file bffe879 nandi 18d ago | 42 | |
| Follow the xorg renames, and stop wrapping nix in distrobox a400a00 nandi 16d ago | 43 | One thing this container is *not* representative of: `/etc/localtime` is a |
| 44 | 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 | 45 | of its path sees nothing. That is a real deployment shape, not an artefact — |
| 46 | frq.clock handles it. |
| Split the tree three ways, and let the phone be Flutter's 7070931 nandi 7d ago | 47 | |
| Build the window somewhere with room for it 1451151 nandi 5d ago | 48 | ## Never pipe a long task through `tail` |
| 49 | |
| 50 | `tail` and `head` do not emit anything until their input ends, so a build, a |
| 51 | test run or a deploy piped through one shows nothing at all until it is over — |
| 52 | and if it is killed or times out first, its output is lost with it. That is the |
| 53 | opposite of what you want from the commands that take longest. |
| 54 | |
| 55 | Let them write to the terminal, or `tee` them if you want a copy to grep |
| 56 | afterwards: |
| 57 | |
| 58 | ```bash |
| One frontend where there were three, and a core that is not Clojure 438b247 nandi 14h ago | 59 | modal run .modal/flutter-web/container.py 2>&1 | tee /tmp/frq-build.log |
| Build the window somewhere with room for it 1451151 nandi 5d ago | 60 | ``` |
| 61 | |
| 62 | Trim afterwards, on the file, where the whole run is still there to re-read. |
| 63 | The same goes for `grep` and `awk` in a live pipeline: they buffer when their |
| 64 | output is not a terminal, so pass `--line-buffered` / `fflush()` or watch the |
| 65 | file instead. |
| 66 | |
| One frontend where there were three, and a core that is not Clojure 438b247 nandi 14h ago | 67 | ## The Nim core |
| 68 | |
| 69 | `nim/` is the portable logic, moving out of `common/` one module at a time as |
| 70 | a native library the Dart side calls through FFI. Read `nim/README.md` before |
| 71 | touching it — in particular the status section, which says what is actually |
| 72 | wired up (the Nim half and its ABI) and what is not (the Dart binding). |
| 73 | |
| 74 | Two rules the ABI has, both of which cost a segfault to rediscover: |
| 75 | |
| 76 | * Every string the core returns is the **caller's** to free, with `frq_free`. |
| 77 | Nim's allocator is not Dart's. |
| 78 | * `frq_init` runs once before anything else. |
| 79 | |
| The binding is Dart, and it works f7aea3b nandi 14h ago | 80 | `dart/frq_core` is the other half of that seam, and is **plain Dart**. New |
| 81 | code on the Dart side of the boundary is written in Dart rather than |
| 82 | ClojureDart — the core exists to have less Clojure in the tree, and adding |
| 83 | more of it to call the thing replacing it is the wrong direction. ClojureDart |
| 84 | shrinks from both ends. |
| 85 | |
| 86 | `just nim-test` and `just dart-test` need no Flutter, which is most of the |
| 87 | 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 14h ago | 88 | |
| 89 | A module is not deleted from `common/` when its Nim version lands: the web |
| 90 | target cannot load a native library, so the ClojureDart original is the web's |
| 91 | implementation until there is a wasm build. Deleting one would take the web |
| 92 | build with it. |
| 93 | |
| 94 | ## The two source trees |
| Split the tree three ways, and let the phone be Flutter's 7070931 nandi 7d ago | 95 | |
| 96 | ``` |
| One frontend where there were three, and a core that is not Clojure 438b247 nandi 14h ago | 97 | common/ .cljc portable — every target compiles it |
| 98 | 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 | 99 | ``` |
| 100 | |
| 101 | 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 14h ago | 102 | `.cljc` and never `.clj`. The rule for anything under `common/` is that it may |
| 103 | not require a `dart:` library — if it needs the host, it asks `frq.io`, and the |
| 104 | implementation that installed itself answers. `frq.io.dart` is installed by |
| 105 | `flutter/src/frq/main.cljd`, which has to await the storage directory first; |
| 106 | `frq.io.web` by `main_web.cljd`. |
| Split the tree three ways, and let the phone be Flutter's 7070931 nandi 7d ago | 107 | |
| 108 | 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 14h ago | 109 | 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 | 110 | seam has `write-private-file!` and not a chmod, because Dart has no chmod. |
| 111 | |
| One frontend where there were three, and a core that is not Clojure 438b247 nandi 14h ago | 112 | There used to be a third tree, `src/`, and a second runtime under it: jolt, |
| 113 | glimmer, and a libcosmic desktop window painting the same screens. It is gone, |
| 114 | along with `just cosmic`, `just tui`, the AV/MoQ media plane and the native |
| 115 | objects they loaded. Flutter is the only frontend now, and `common/` is |
| 116 | compiled by one compiler rather than two — which is why the `#?(:jolt ...)` |
| 117 | reader conditionals that used to be scattered through it are not there any |
| 118 | more. `tools/check-common.py` still guards the seam, and CI still runs it on |
| 119 | every push. |
| 120 | |
| 121 | `flutter/` builds three things, from one `clojure -M:cljd compile`: |
| Paint the desktop with Flutter too, beside libcosmic fc5f63c nandi 7d ago | 122 | |
| 123 | `just apk`, out of the flake's own `.#flutter` shell (clojure, jdk17, flutter) |
| 124 | and its `.#android-sdk` package. Impure on purpose: Gradle fetches its own |
| 125 | dependencies and writes into `ANDROID_HOME`, so the recipe copies the store SDK |
| One frontend where there were three, and a core that is not Clojure 438b247 nandi 14h ago | 126 | to `flutter/.home` and lets it finish there. |
| Paint the desktop with Flutter too, beside libcosmic fc5f63c nandi 7d ago | 127 | |
| 128 | `just flutter-desktop`, out of `.#flutter-desktop` (the same clojure and |
| 129 | flutter, with cmake, ninja, pkg-config and gtk3 where the JDK and the SDK are). |
| One frontend where there were three, and a core that is not Clojure 438b247 nandi 14h ago | 130 | Impure for the network half of the same reasons and no writable-SDK dance, |
| 131 | since nothing writes into the store. nixGL off NixOS. |
| 132 | |
| 133 | `just flutter-web`, out of no nix shell at all — `tools/toolchain.sh` fetches |
| 134 | the three pinned tarballs it needs, which is what lets `.modal/flutter-web/` |
| 135 | run the same script on a plain Debian image. |
| 136 | |
| 137 | The consequence for `common/` is that "the phone" is not a synonym for "the |
| 138 | ClojureDart side": three targets compile it. An implementation that branches on |
| 139 | the platform has to ask (`Platform.isAndroid`) rather than assume; see |
| Paint the desktop with Flutter too, beside libcosmic fc5f63c nandi 7d ago | 140 | `frq.io.dart/write-private-file!`, where assuming cost a token its file mode. |
| 141 | See flutter/README.md. |