| Split the tree three ways, and let the phone be Flutter's 7070931 nandi 7d ago | 1 | # The ClojureDart half |
| 2 | |
| 3 | Nothing here builds yet. This is the boundary, drawn before the port rather |
| 4 | than after it, so that the question "can this file go on the phone?" has a |
| 5 | filesystem answer. |
| 6 | |
| 7 | ## The three trees |
| 8 | |
| 9 | ``` |
| 10 | common/ .cljc both compilers. No jolt, no glimmer, no dart. |
| 11 | src/ .clj jolt: glimmer, jolt.ffi, the cosmic and tui backends. |
| 12 | flutter/ .cljd ClojureDart: Flutter widgets, dart:io, dart:ffi. |
| 13 | ``` |
| 14 | |
| 15 | The extension is the boundary and the compilers enforce it. ClojureDart reads |
| 16 | `.cljd` and `.cljc` and never `.clj`, so a namespace that reaches for |
| 17 | `jolt.host` cannot accidentally end up in the APK — it is a `.clj` and the Dart |
| 18 | compiler cannot see it. jolt reads all three, which is why `common/` works at |
| 19 | all: one copy of `frq.clock`, compiled twice. |
| 20 | |
| 21 | Where both need a namespace but the answer differs, `.cljd` wins over `.cljc` |
| 22 | in ClojureDart's own resolution, so a file here shadows a shared one without |
| 23 | either side knowing. Reader conditionals work too, with one trap from |
| 24 | ClojureDart's FAQ: the `:clj` feature is always on under cljd, so `:clj` goes |
| 25 | **last** in a conditional, and macro code that wants the Clojure path during |
| 26 | host evaluation asks for `:cljd/clj-host`. |
| 27 | |
| 28 | ## What has crossed |
| 29 | |
| 30 | `frq.io` is the seam — the host's job named once, with `frq.io.jolt` answering |
| 31 | it on the desktop and `frq.io.dart` here. It carries the filesystem, the |
| 32 | environment, the config directory and the clock. |
| 33 | |
| 34 | Moved to `common/` and running under jolt today: |
| 35 | |
| 36 | | namespace | lines | note | |
| 37 | |---------------|-------|-------------------------------------------------| |
| 38 | | `frq.emoji` | 1,914 | data; nothing to port | |
| 39 | | `frq.glyphs` | 84 | data | |
| 40 | | `frq.av.dial` | 117 | already touched neither jolt nor glimmer | |
| 41 | | `frq.clock` | 93 | zone-hunting moved into the backends | |
| 42 | | `frq.store` | 122 | `install -m 600` became `write-private-file!` | |
| 43 | |
| 44 | `frq.clock` is the shape the rest should follow. It used to open with four |
| 45 | guesses at the reader's zone — `TZ`, the target of `/etc/localtime`, the file |
| 46 | itself by path, then Android's `persist.sys.timezone` — and then convert days |
| 47 | to a date by printing one with `jolt.time.local` and taking a `subs` of the |
| 48 | result. Both are gone: the guessing is a libc question and lives in |
| 49 | `frq.io.jolt`, where Dart answers it in one call instead; the conversion is |
| 50 | eleven lines of Hinnant's algorithm, checked against `java.time.LocalDate` for |
| 51 | every day from 1901 to 2052. |
| 52 | |
| 53 | ## What has not |
| 54 | |
| 55 | Roughly 4,000 lines are portable in substance and still `.clj` because the seam |
| 56 | does not reach far enough yet. In the order worth doing them: |
| 57 | |
| 58 | 1. **`frq.wire`, `frq.msgsig`** — need a crypto seam beside the io one. |
| 59 | 2. **`frq.irc`** (433) — the parser is pure; the reader is a blocking thread in |
| 60 | a `future`, and Dart has no threads. It becomes a `Stream` over |
| 61 | `SecureSocket`, which is also what makes TLS work on the phone at all. |
| 62 | 3. **`frq.atproto`** (209), **`frq.oauth`** (182) — hand-rolled HTTPS over |
| 63 | `jolt.mvn-http`'s OpenSSL bindings, which is why sign-in is desktop-only |
| 64 | today. `dart:io` has TLS in the runtime; this is the single biggest thing |
| 65 | the port buys. |
| 66 | 4. **`frq.state`** (1,930) — mostly portable logic, but its ratoms are |
| 67 | glimmer's. Needs the reactive layer decided first. |
| 68 | 5. **`frq.app`** (1,834) — not a port. Flutter brings its own reconciler, so |
| 69 | the screens are rewritten against `cljd.flutter`. |
| 70 | |
| 71 | Not coming: `frq.tui` (no terminal Flutter), `frq.cosmic` (libcosmic is |
| 72 | desktop-only), and the media plane — `moq/`, `codec/`, `capture/`, `av/`, about |
| 73 | 3,800 lines of FFI against C libraries that do not exist on Android either way. |
| 74 | `dart:ffi` does not conjure V4L2; that half wants Flutter's camera and audio |
| 75 | plugins and is its own project. |
| 76 | |
| Build the APK, out of ClojureDart and Flutter e2b0e6b nandi 7d ago | 77 | ## Building it |
| 78 | |
| 79 | ```bash |
| 80 | just apk # the debug APK |
| 81 | just apk install # and onto a connected device |
| 82 | just apk run # and launched |
| 83 | just apk log # logcat |
| 84 | ``` |
| 85 | |
| 86 | Impure on purpose. Gradle resolves its own dependencies over the network and |
| 87 | installs build-tools and a platform into `ANDROID_HOME` as it goes, so it can |
| 88 | neither run in a sandbox nor write to the store. What nix gives is the |
| 89 | toolchain — clojure, a JDK, Flutter, and an SDK composed by androidenv — and |
| 90 | the recipe copies that SDK to `flutter/.home` for Gradle to finish off. That |
| 91 | copy and everything Gradle leaves behind are gitignored. |
| 92 | |
| 93 | Two things the Flutter template wanted that are deliberately not here. There is |
| 94 | no `ndkVersion` in `android/app/build.gradle.kts`: setting it makes Gradle |
| 95 | fetch that exact NDK, and there is no native code to need one — the app is |
| 96 | Dart, and path_provider is platform channels rather than JNI. And `ios/`, |
| 97 | `macos/`, `windows/`, `web/` are deleted; `android/` and `linux/` are the |
| 98 | targets. |
| 99 | |
| 100 | It is signed with `~/.android/debug.keystore`, through the template's |
| 101 | `signingConfig = signingConfigs.getByName("debug")` — which release builds also |
| 102 | use, so `flutter build apk --release` is not shippable until a real |
| 103 | `signingConfigs.release` is wired up. The jolt APK's key was generated inside |
| 104 | its own nix derivation and never written anywhere, which is why the first |
| 105 | install over it needed an uninstall: Android will not update a package across a |
| 106 | signature change. |
| 107 | |
| 108 | ## What it paints |
| 109 | |
| 110 | `frq.main` is a socket, not the client: the clock and the saved session, read |
| 111 | through exactly the `common/` namespaces the desktop reads them through. That |
| 112 | is the whole point of it — proof the shared half compiles and runs under a |
| 113 | second compiler. The screens are still to be written. |