Skip to content

Architecture and Development

Pummelchen edited this page Sep 18, 2026 · 14 revisions

Architecture and Development

How to run RoomCAD locally, run its tests, and find the rules. The rules themselves are not repeated here — they live in the repository, in AGENTS.md (the one instruction file) and RELEASE.md (the release standard). One copy, in the repository, is the point: a rule written down twice drifts, and the copy in the wiki is the one nobody updates.

Run it locally

ROOMCAD_DB_PATH=/tmp/roomcad.db ROOMCAD_PASSWORD=letmein \
  python3 roomcad/server/server.py &   # the API on 127.0.0.1:8078
cd roomcad/web && ./serve.sh           # the app on http://localhost:8080

There is no build step and nothing to install. web/Caddyfile proxies /api/* to the local API, so saving, versions and live collaboration all work locally; skip the API and you still get the app, with a "server not reachable" banner. The first serve.sh run downloads Caddy into web/bin/ (git-ignored).

The 3D view needs WebGPU — a current Chrome or Edge, or Safari Technology Preview. There is no CDN fallback: Three.js and Rapier are vendored under roomcad/web/lib/.

Run the tests

./tests/run.sh              # everything — the real gate
./tests/run.sh --fast       # skip the four long fuzz files, ~45 s
./tests/run.sh plan-editing # just the files whose name matches
ROOMCAD_TEST_TIMEOUT=600 ./tests/run.sh   # seconds per file (default 1800)

41 files and 2405 checks. city-fuzz alone takes about twelve minutes, so the full sweep is 13–15 minutes and --fast is what you iterate with. The same suite runs in CI on every push and pull request.

There is no test framework: each file is a plain Node script that prints N passed, M failed and exits non-zero, and the runner totals them. Five are worth knowing by name:

File What it pins that nothing else can
boot.test.mjs the app's real import graph — every specifier resolved through the import map, every element id the page must have. No browser, no dependencies. It proves the graph and the DOM contract, not that anything renders
app-wiring.test.mjs boots the real app.js, adopts the room the server names, and drives its own keyboard handler
app-clicks.test.mjs clicks the real buttons and the inspector's delegated actions
city-turns.test.mjs the traffic's junction decisions — the bug where a bus left the grid
login.test.mjs the password gate, including that it fails closed when the server is unreachable

Anything that drives the traffic must seed it. city.js uses real Math.random() in production on purpose, so an unseeded fuzz is a coin toss that fails on a busy machine. city-fuzz, city-physics and city-turns each call setTransportRandom() first; seed any new drive the same way.

Orientation

The app is plain ES modules with no bundler, loaded through the import map in roomcad/web/index.html.

Module What lives there
plan.js the room model: grid and snapping, wall and opening geometry, wall joins, room detection, auto-layout, the .rcad format. Pure — no DOM, no network
store.js editing state, tools, selection, undo/redo, applying a remotely-changed room. It does no I/O
editor2d.js the 2D canvas: drawing, hit-testing, zoom, pan, rotate
walk3d.js the Three.js walkthrough, Rapier physics, sun, lights, bloom
city.js the stylised city around the room: streets, blocks, traffic, signals, weather
app.js UI glue and every /api/* call — with login.js, the only module that touches the network
server/server.py the whole API: stdlib Python, SQLite, SSE for live collaboration

plan.js and store.js do no I/O, which is why so much of the app can be tested in Node with no server and no DOM. app.js and walk3d.js used to be impossible to import at all; the test harness resolves the page's import map for the whole graph now, so they can be driven for real. AGENTS.md explains how, and the traps that go with it.

Deploying

Deployment is a separate, documented operation: Hosting and Deployment is the procedure, and roomcad/server/deploy.sh is the script. It validates the candidate Caddyfile with the server's own Caddy before installing it, and refuses to report success until the live URL answers. The version is not bumped by deploying: bump roomcad/web/version.js in the same change as the user-facing fix you are shipping.

Releasing

A deploy is not a release, and the two use different scripts. ./release.sh cuts a release: it is a dry run until you pass --publish, and then it tags, packages and publishes a GitHub Release. RELEASE.md is the standard it implements, and it is edited in this repository and nowhere else.

Releases are a source archive — nothing here compiles, so there is no binary and no architecture to assert. release.sh reads the version out of roomcad/web/version.js, so the tag cannot disagree with what the footer shows; it refuses a dirty tree or a branch that is not main; it checks the packaged archive for the files the standard requires by reading the archive rather than the working tree; and after publishing it re-downloads the asset and compares the digest.

The notes live in docs/release-notes-vX.Y.md, one section per user-visible change with the check that backs it, ending in a checksum block the script substitutes at publish time — the committed file keeps the placeholders, so a rebuild can never leave a stale digest in the tree.

The first release is v10.8, cut from main. Its assets are roomcad-10.8-src.tar.gz and its .sha256; the same digest is quoted in the release notes, and the archive carries the project's LICENSE, its THIRD_PARTY_NOTICES.md and a README-binaries.txt stating the platform floor.

Clone this wiki locally