oficina / slides Public

Clone
git clone https://amadan.net/oficina/slides

Plain git — no account needed to clone.

Download

Download this file

CI

ci.d/10-gate is the only CI this repo has. It runs on every push to the amadan remote, and it is the same script you can run locally — one definition rather than two that drift. ci execs it, so a runner with no step support runs exactly the same thing rather than a thinner Go-only version of it, which is what the four scaffold steps here used to be.

It needs a Rust toolchain with the wasm32-unknown-unknown target and the rustfmt and clippy components (rust-toolchain.toml pins 1.98.0 and asks for all three), Go, and — since slice 2 — Node 20 or newer with npm, because the editor's bundle and the fixture vectors are both built by JS this repo owns. CGO_ENABLED=0 is exported for every step. The gate sources ~/.cargo/env itself and checks every tool up front, so a missing dependency fails on the first line with its own name rather than fifty lines in as cargo: command not found.

make migration-check runs the rastrillo CLI through go run off go.mod rather than needing a binary on PATH — a bare rastrillo migration check is what failed a sibling repo's very first CI run, with nothing in the scaffold saying what to install.

Known trade-off: npm ci needs the npm registry reachable at run time. The Rust and Go steps are offline once their caches are warm; the Node step is not. It is that way for a reason — the runner clones a fresh worktree per branch, so fixtures/node_modules never carries over, and npm ci against the committed fixtures/package-lock.json is what makes the installed yjs exactly the pinned 13.6.27 rather than whatever a bare npm install resolves this week. Docs made the same call and documents the same trade-off. If the registry is unreachable from the runner, that is the failure this line produces; it is not a bug in the gate. The cache directory is the one part of it that differs by machine, so the step honours NPM_CACHE and falls back to $HOME/.npm: the runner's HOME is writable and needs nothing, a sandboxed local run may need NPM_CACHE pointed somewhere it can write, or npm fails on its cache rather than on anything to do with the install.

The JS half

Four steps, in this order, between the Rust tests and make ci.

  • (cd fixtures && npm ci --cache "${NPM_CACHE:-$HOME/.npm}") — the trade-off above, and the reason for it.
  • *`(cd fixtures && node --test .test.mjs)`** — the JS side's own tests: the op vocabulary, the ProseMirror schema, the save queue, the live-update cursor, the resync controller, the inspector's ops, the canvas maths, the switcher, uploads. Most of what the editor gets wrong it gets wrong in a browser with nothing in a log, which is the argument for running them here rather than by hand.

Run from fixtures/ with a glob, never as node --test fixtures/. Node 24 takes a directory argument as if it were a test file, runs nothing, and exits 0 — a green-looking way to run no tests at all.

  • (cd fixtures && node generate.mjs) then a clean-tree check on fixtures/vectors — the vectors are the contract between the JS ops and the Rust core (spec §2.8). internal/slides/core/vectors_test.go, under make ci below, loads each committed vector through the core and requires the same view the JS side produced. A vector nobody regenerated makes that test compare the core against a JS implementation that no longer exists, so it still passes while the two sides have already drifted. This step is what turns that into a failure: vectors are stale: run node fixtures/generate.mjs and commit.
  • node fixtures/build-bundles.mjs then a clean-tree check on internal/slides/static — the editor bundle is committed, hashed, and built by nothing at run time, so a stale one is served perfectly happily by a server that cannot tell. Same check as the wasm cmp below, and safe for the same reason: the build is byte-reproducible (no sourcemap, no banner, no timestamp, no absolute paths, and esbuild's work happens in the pinned 0.28.2 native binary, so the Node running it does not enter into the result).

The last two check git diff and git status --porcelain, because each catches half of it. git diff sees a tracked file that changed or was deleted; it never sees the new file a changed content hash writes beside the old one. A stale hashed bundle left behind is not a cosmetic problem — assets_embed.go requires exactly one editor.<hash>.js and panics at boot when it finds two.

Both restore the tracked files on failure (git checkout) and print what they found: they mutated a committed directory as a side effect of reading it, and a modified tracked file left behind would let a second run of the same script pass with nobody having committed anything. The untracked half is deliberately left alone — a new vector or a bundle under a new hash is exactly the file you have to commit next, and an earlier version of this gate git cleaned it away before you could.

Two things that fail silently

  • The gate fails if internal/slides/core/testdata/slides_core.wasm differs from a fresh build. go:embed will otherwise embed a stale module and every Go test will pass against it. When it fails, rebuild and commit the artifact:

``sh ./scripts/build-wasm.sh cp core/target/wasm32-unknown-unknown/release/slides_core.wasm \ internal/slides/core/testdata/slides_core.wasm ``

That comparison only works because scripts/build-wasm.sh remaps the registry and workspace path prefixes rustc would otherwise bake into the binary. Without it the artifact reproduces byte-for-byte only on the machine, and under the $HOME, that built it, and the cmp reports a stale artifact everywhere else.

  • cargo fmt --check covers core/. It was not in the gate before 2026-09-07, and by then three files had drifted; they were reformatted in the commit that added the check.

The runner

Jobs are claimed by an amadan runner on the machine that has one. Two things about it are wrong by default, and both fail in ways that read like something else:

  • The runner must be told to serve this repo. amadan runner -repo … is an allowlist, and a repo nobody listed gets amadan ci status exit 3 forever — "no status recorded", which reads like the gate has not run yet rather than like nothing will ever run it. ~/.config/systemd/user/amadan-runner.service was edited to list -repo oficina/slides on 2026-09-07.
  • The runner's PATH is the systemd unit's, not your shell's, and ~/.cargo/bin is not on it. The gate sources ~/.cargo/env rather than depending on that being fixed.

A restart abandons any job already running, so check the runner is idle before restarting it.

The Go half

make ci is the last step, so the Go half of the gate and the make ci you run between edits are the same definition: go vet ./..., a gofmt check, go test ./..., and rastrillo migration check, which fails the moment models.go and migrations/ disagree instead of at boot on whatever machine notices next.

The browser drive (go test -tags browser ./...) is deliberately outside all of this. It needs a Chromium, and this project's sandbox will not start one, so a gate that ran it would be reporting "could not see" as green.