Three things stood between the flagship program and the web target, and each is answered here rather than worked around. The brush was a path. (rl/load-texture "brush.png") hands raylib a filename to open, and a bare relative path means nothing on a target with no filesystem. It is (embed "brush.png") now, decoded through a new binding — LoadImageFromMemory, declared (Ptr u8) plus an explicit count because the shim generator refuses a slice parameter and says so, with a Flan wrapper taking the slice apart exactly as collision-point-poly? and load-font-ex already do. One decode now serves both textures: the unflipped upload first, then ImageFlipHorizontal in place, then the mirrored one. load-texture and load-image lose their only call site in this repository; that is deliberate, because a path-based load is the thing that cannot work here. A package's C may now be addressed to one target, the way a link line already could. A .c file may carry a tag before its extension — flan_agent.web.c — and on that target it is compiled and *replaces* the untagged file of the same base name. Replacement rather than plain tagging, so that teaching a package about a new target is additive: the file that was right on three targets is not renamed to say so. Selection is in Build and not in Load, for the reason select_lflags gives. The dev agent on the web is a no-op, and the reasoning is written at length in vendor/agent/flan_agent.web.c. Short version: the agent is a socket server and a browser has no sockets, so the missing <sys/time.h> was the surface and not the cause. Refusing vendor:agent on a web target was the other candidate and is ruled out by arithmetic — Flan has no conditional compilation, sand.flan calls agent/start unconditionally, Reach cannot prune a package something reachable calls into, so a refusal means the program does not build for the browser at all. This does not contradict the `barf` decision made earlier today. `barf` is asked to make something durable, and a no-op returns success to a program that now believes bytes are on disk. The agent is asked to accept redefinitions, and on the web there is no editor, no socket and no session — --dev is refused by name on every wasm target — so there is nothing to lose. sand.flan already says the same of a native release build at the call site. test/test_web.ml builds sand.flan for the browser and reads the module for brush.png's own bytes, whole. Not "IHDR": stb_image carries that string itself, linked in from raylib, so it would pass on a build where the embed emitted nothing. It is not run — node has no DOM, so main reaches InitWindow and dies inside glfwInit on `window is not defined`, which says the module is live and nothing about whether the canvas paints. test/dune gains brush.png, because an embed is read by the checker and the headless case reaches sand.flan through ../../ from a sandboxed _build. test_session's C-c C-k case now passes ~origin, which is what both editor paths already send; omitting it was testing a request nobody makes. dune test is green. Docs follow in the next commit.
Description
Languages
OCaml
67.2%
Emacs Lisp
15.2%
C
10.4%
HTML
2.9%
Standard ML
2.8%
Other
1.5%