nandi/frqpublic Fork 0
85852e854ee8b81416c1b67f4ae526b8d4e48a07
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.

Point the docs at .modal/, and at the file they now build 972ab37 · on 85852e854ee8b81416c1b67f4ae526b8d4e48a07 · nandi · 4d ago
CLAUDE.md · 111 lines · 5.2 KBmarkdown
Blame HistoryOpen raw

Working in this repo

Nix

STOP BUILDING LOCALLY. Build on Modal. This machine is for editing and for
evaluating — nix flake check, nix eval, nix build --dry-run, nix repl
— and not for realising a derivation. A local nix build .#frq gets killed for
memory long before it finishes, and the minutes spent finding that out are
minutes not spent on the change. So:

modal run .modal/frq/container.py   # .#appimage, on Modal

The container is .modal/, not a fourth source tree: it is CI config that
happens to live here, the way .github/ would be.

--dry-run locally to see what would be built, then hand the build to Modal.
The one exception is a derivation you already know is trivial and already
substitutable; if you are unsure, it is not the exception.

modal app logs is no substitute for watching that command: it resolves
deployed apps by name, not the ephemeral one a modal run creates, and carries
nothing until the Sandbox starts — the image build streams to the client and
nowhere else.

You are already running inside the Arch distrobox, where nix lives, so run
the evaluating commands directly — do not wrap them in distrobox enter.

The container runs as a Modal Sandbox on a real VM, which is what makes
nix build work out there at all: the ptyshim that used to stand in for a
working pty under gVisor is deprecated, and nothing here should reintroduce it.
Substitution comes from the nix-cache Modal Volume plus cache.nixos.org and
nix-cache.wasix.org — libjoltcosmic's dependency tree is the one that makes
that cache worth having.

A remote builder (eu.nixbuild.net) is also configured here, for the case
where you want the graph built somewhere other than Modal: --store ssh-ng://eu.nixbuild.net --eval-store auto rather than a builders entry, so
the whole graph stays there and only .drv files go up.

One thing this container is not representative of: /etc/localtime is a
regular file here rather than a symlink, so anything that reads the zone out
of its path sees nothing. That is a real deployment shape, not an artefact —
frq.clock handles it.

Never pipe a long task through tail

tail and head do not emit anything until their input ends, so a build, a
test run or a deploy piped through one shows nothing at all until it is over —
and if it is killed or times out first, its output is lost with it. That is the
opposite of what you want from the commands that take longest.

Let them write to the terminal, or tee them if you want a copy to grep
afterwards:

modal run .modal/frq/container.py 2>&1 | tee /tmp/frq-build.log

Trim afterwards, on the file, where the whole run is still there to re-read.
The same goes for grep and awk in a live pipeline: they buffer when their
output is not a terminal, so pass --line-buffered / fflush() or watch the
file instead.

The three source trees

common/  .cljc  jolt AND ClojureDart
src/     .clj   jolt only
flutter/ .cljd  ClojureDart only

The extension is the boundary, not a convention: ClojureDart reads .cljd and
.cljc and never .clj, jolt reads all three. So the rule for anything under
common/ is that it may not require jolt.*, glimmer* or a dart: library
— if it needs the host, it asks frq.io, and the backend that installed itself
answers. frq.io.jolt is required for its side effect by frq.app;
frq.io.dart is installed by flutter/src/frq/main.cljd, which has to await
the storage directory first.

Adding a host call means adding it to the seam in common/frq/io.cljc and to
both implementations. Name it for the result rather than the mechanism — the
seam has write-private-file! and not a chmod, because Dart has no chmod.

flutter/ builds two things, from one clojure -M:cljd compile:

just apk, out of the flake's own .#flutter shell (clojure, jdk17, flutter)
and its .#android-sdk package. Impure on purpose: Gradle fetches its own
dependencies and writes into ANDROID_HOME, so the recipe copies the store SDK
to flutter/.home and lets it finish there. It is the only APK there is — the
jolt APK, nix/android.nix, android/ and the .#apk outputs are gone,
because every backend that APK could paint with is retired.

just flutter-desktop, out of .#flutter-desktop (the same clojure and
flutter, with cmake, ninja, pkg-config and gtk3 where the JDK and the SDK are).
Impure for the network half of the same reasons and no writable-SDK dance, since
nothing writes into the store. nixGL off NixOS, like just cosmic run.

So there are two desktop GUIs and they are both first-class: just cosmic run is libcosmic under jolt, just flutter-desktop is Flutter's Linux target
over
frq.hiccup. Same screens out of common/frq/screens/, two renderers. jvui and
Vidya were experiments and are gone; libcosmic is a desktop window and does not
cross to a phone, which is what the Flutter half is for.

The consequence for common/ is that "the phone" is no longer a synonym for
"the ClojureDart side" — two targets compile it. An implementation that branches
on the platform has to ask (Platform.isAndroid) rather than assume; see
frq.io.dart/write-private-file!, where assuming cost a token its file mode.
See flutter/README.md.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
# Working in this repo

## Nix

**STOP BUILDING LOCALLY. Build on Modal.** This machine is for editing and for
evaluating — `nix flake check`, `nix eval`, `nix build --dry-run`, `nix repl`
— and not for realising a derivation. A local `nix build .#frq` gets killed for
memory long before it finishes, and the minutes spent finding that out are
minutes not spent on the change. So:

```bash
modal run .modal/frq/container.py   # .#appimage, on Modal
```

The container is `.modal/`, not a fourth source tree: it is CI config that
happens to live here, the way `.github/` would be.

`--dry-run` locally to see what *would* be built, then hand the build to Modal.
The one exception is a derivation you already know is trivial and already
substitutable; if you are unsure, it is not the exception.

`modal app logs` is no substitute for watching that command: it resolves
deployed apps by name, not the ephemeral one a `modal run` creates, and carries
nothing until the Sandbox starts — the image build streams to the client and
nowhere else.

You are already running inside the Arch distrobox, where `nix` lives, so run
the evaluating commands directly — do not wrap them in `distrobox enter`.

The container runs as a Modal **Sandbox on a real VM**, which is what makes
`nix build` work out there at all: the ptyshim that used to stand in for a
working pty under gVisor is deprecated, and nothing here should reintroduce it.
Substitution comes from the `nix-cache` Modal Volume plus cache.nixos.org and
nix-cache.wasix.org — libjoltcosmic's dependency tree is the one that makes
that cache worth having.

A remote builder (`eu.nixbuild.net`) is also configured here, for the case
where you want the graph built somewhere other than Modal: `--store
ssh-ng://eu.nixbuild.net --eval-store auto` rather than a `builders` entry, so
the whole graph stays there and only .drv files go up.

One thing this container is *not* representative of: `/etc/localtime` is a
regular file here rather than a symlink, so anything that reads the zone out
of its path sees nothing. That is a real deployment shape, not an artefact —
frq.clock handles it.

## Never pipe a long task through `tail`

`tail` and `head` do not emit anything until their input ends, so a build, a
test run or a deploy piped through one shows nothing at all until it is over —
and if it is killed or times out first, its output is lost with it. That is the
opposite of what you want from the commands that take longest.

Let them write to the terminal, or `tee` them if you want a copy to grep
afterwards:

```bash
modal run .modal/frq/container.py 2>&1 | tee /tmp/frq-build.log
```

Trim afterwards, on the file, where the whole run is still there to re-read.
The same goes for `grep` and `awk` in a live pipeline: they buffer when their
output is not a terminal, so pass `--line-buffered` / `fflush()` or watch the
file instead.

## The three source trees

```
common/  .cljc  jolt AND ClojureDart
src/     .clj   jolt only
flutter/ .cljd  ClojureDart only
```

The extension is the boundary, not a convention: ClojureDart reads `.cljd` and
`.cljc` and never `.clj`, jolt reads all three. So the rule for anything under
`common/` is that it may not require `jolt.*`, `glimmer*` or a `dart:` library
— if it needs the host, it asks `frq.io`, and the backend that installed itself
answers. `frq.io.jolt` is required for its side effect by `frq.app`;
`frq.io.dart` is installed by `flutter/src/frq/main.cljd`, which has to await
the storage directory first.

Adding a host call means adding it to the seam in `common/frq/io.cljc` and to
both implementations. Name it for the result rather than the mechanism — the
seam has `write-private-file!` and not a chmod, because Dart has no chmod.

`flutter/` builds two things, from one `clojure -M:cljd compile`:

`just apk`, out of the flake's own `.#flutter` shell (clojure, jdk17, flutter)
and its `.#android-sdk` package. Impure on purpose: Gradle fetches its own
dependencies and writes into `ANDROID_HOME`, so the recipe copies the store SDK
to `flutter/.home` and lets it finish there. It is the only APK there is — the
jolt APK, `nix/android.nix`, `android/` and the `.#apk` outputs are gone,
because every backend that APK could paint with is retired.

`just flutter-desktop`, out of `.#flutter-desktop` (the same clojure and
flutter, with cmake, ninja, pkg-config and gtk3 where the JDK and the SDK are).
Impure for the network half of the same reasons and no writable-SDK dance, since
nothing writes into the store. nixGL off NixOS, like `just cosmic run`.

So there are two desktop GUIs and they are both first-class: `just cosmic
run` is libcosmic under jolt, `just flutter-desktop` is Flutter's Linux target
over
`frq.hiccup`. Same screens out of `common/frq/screens/`, two renderers. jvui and
Vidya were experiments and are gone; libcosmic is a desktop window and does not
cross to a phone, which is what the Flutter half is for.

The consequence for `common/` is that "the phone" is no longer a synonym for
"the ClojureDart side" — two targets compile it. An implementation that branches
on the platform has to ask (`Platform.isAndroid`) rather than assume; see
`frq.io.dart/write-private-file!`, where assuming cost a token its file mode.
See flutter/README.md.