`engines`, `@types/node` and the three esbuild targets said node20 while both
stages of the image have been node:24-alpine for a while — and since
24.10.2026 the development machines run 24 too. So the bundles are built for
exactly the runtime that carries them instead of for a version nobody runs
any more.
`npm run check`: 110 files, 1825 tests, green on node 24.22.1.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
/svc/examples lists FILIUS' examples, which lie beside F in the image (GPL
like this repository, kept out of the dashboard); /svc/projects makes a
project from one; /svc/projects/<id>/import takes a .fls (up to 64 MiB),
/copy copies a project as it is now, /export gives it back as a .fls with its
warnings. Large files go into the blob store on import and come back from it
on export, which is refused when one is missing.
Found on the way: Node's request body is a Buffer, and Yjs takes no Buffer
into a document, so every .fls with binary files would have failed; and
fflate, now in the bundle, imports createRequire itself, which the build's
banner declared again, so F did not start. The banner names it otherwise and
a test builds and starts the bundle. The proxy's /svc/ takes 70 MB.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Moving a device, moving or resizing a documentation item went into the
shared document at every pointer move: 59 updates a second, each leaving
about 18 bytes of history (M4 on test-filius). Now the editor keeps the drag
while it lasts, shows it itself and tells the others through the awareness
(moving, at most every 50 ms, the end at once), and writes it once when it
is let go. A drag the browser cancels or a second finger turns into a pinch
is written where it is; WebKit sends a second pointerdown for the first
finger when another comes, which had lost the drag.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
F hands out the editor's page and bundle (editor/ beside main.mjs, or
FILIUS_EDITOR_DIR): GET and HEAD only, one wall against leaving the folder,
F's own routes first, assets/ cached a year, the rest asked for again; the
build lays .br and .gz beside the files and F sends the better one the
browser takes (bundle 695 KB to 212 KB).
deploy/Dockerfile: two stages, Node 24, no node_modules, not root.
deploy/docker-compose.yml with an env file per environment: the file sets
the stack's name, volume, image tag and port (test 8081, production 8080).
Measured with both stacks side by side behind nginx and Chromium.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
From 16 KB a picture's or binary file's entry names its bytes by their
SHA-256 instead of holding them; the bytes are on F, content-addressed
(GET/POST /blobs with the session's grant, from the editor's origin, at
most 16 MiB). A document's blob side says where they are: the editor's
fetches and uploads, the room thread's asks F's main thread, and a start
waits for the ones asked for; without a side everything stays in the
document. Text stays Y.Text. A blob whose bytes are not there is never
written back empty. A blob F has not got yet is asked for again a few
times, and a running room reads late bytes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
?project=<uuid> opens the shared document over /sync and the room over
/sim (RemoteSimHost: messages wait for the socket, a lost socket comes
back with what it watched, F's refusal or parting ends it); instead of
the editor, a word why when F refuses or lets go. F tells each
connection its access at once. Who only reads sees everything and
changes nothing: no palette or undo, a locked surface, the sheet's
fields disabled (tabs stay), no starting or stopping; thawed, the
document comes anew. The toolbar names who started the simulation.
Refusals carry no message, so Hocuspocus writes nothing outside F's
JSON log. A browser test runs three people against a real F.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>