Release 0.202
v0.202.1 — 2026-07-25
Release-engineering fix — use this tag, not v0.202.0. The split’s release
matrix (its first real run) collapsed to arm64-only: platform lived only in
the matrix include, whose entries merge into existing combinations with
later includes overwriting values earlier ones added — so both targets built
arm64 twice, and mantle-{server,client}:v0.202.0 + :latest published as
arm64-only manifests unusable on amd64 boxes. (The server image’s
node:sqlite probe caught it; the client had no such gate and published.)
No code changes vs v0.202.0.
platformis now an original matrix dimension (a true 4-job cross product); theincludeentries only attach the runner.- Two new tripwires in the merge job: exactly one digest per architecture BEFORE anything is tagged, and the pushed manifest must list both platforms — for both targets, closing the client’s gateless publish.
v0.202.0 — 2026-07-25
Do not deploy this tag — its published images are arm64-only (release matrix bug, fixed in v0.202.1). Everything below shipped correctly in v0.202.1.
The server tier runs Hono now — Next.js is removed from server/web. After
the member carve (v0.201.0), server/web was an API-first tier: the whole
/api/** plane plus a handful of render surfaces, with almost no React left.
Carrying the full Next.js runtime — App Router, RSC, the Edge middleware
sandbox, next build — to serve JSON and two static pages was pure weight. So
server/web now runs a Hono app under @hono/node-server, executed by
tsx — the same runtime server/api and the workers have always used. Boot
is a sub-second tsx server/main.ts; there is no compile step. client/web
stays a Next.js app, untouched.
- The gate is a faithful port of the Edge middleware. Session-HMAC verify,
the
k:'m'mobile bearer,?at=asset tokens,PUBLIC_PATHS, and CORS (including the wildcard refusal on the credential-minting/api/auth/**paths, preflight-before-auth) all moved toserver/middleware/gate.ts. Request path/method now travel viaAsyncLocalStorageinstead of injectedx-mantle-*headers. - Route files kept their shape. A local
NextResponse/cookies()/headers()compat shim (server/http-compat/) and a generated, precedence-sorted route manifest (288app/**/route.tshandlers, lazily imported and adapted onto Hono) mean the ~280 route files carry the same handler convention behind the seam — a mechanical, reviewable diff, not a rewrite. Migrating individual routes to native Hono idioms is optional future cleanup. - Render surfaces are hand-rolled, no Next renderer.
/s/<token>server-renders viareact-dom/serverwith three client islands (app/table/token-prompt) bundled intopublic/share-runtime/(Tailwind v4 CLI compile + esbuild + KaTeX);/print/pages/<id>is a plain HTML template aroundrenderPageDoc;/login,/hub,/team/*are redirect stubs. - The HTTP contract did not change. Same routes and shapes, same port
(3000), same
/api/health, and no new env vars. e2e is green in both topologies (29 passed / 0 failed), with SSE client-abort, 8 MB multipart upload, and share-assetRangeverified live under the node server. - The Docker server image drops the compile step:
buildis asset generation only (app-runtime, route manifest, share-runtime), andCMDis the exec formpnpm -C server/web exec tsx server/main.ts— exec, not the run-script form, soSIGTERMreaches the server instead of dying in the package-manager wrapper (docker stopsettles in ~0.2 s rather than burning the full 10 s grace and taking aSIGKILL). The client target is unchanged. pnpm dev:fenow runsclient/web. The client app is zero-secret and natively detached, so the old bearer-minting machinery is gone — you sign in on the login page. Config moved toclient/web/.env.detached.local(MANTLE_REMOTE=…only; the legacyserver/webfile auto-migrates). The remote box must allowlist your dev origin (http://localhost:3000) inMANTLE_API_CORS_ORIGINS— the wildcard never covers/api/auth. Seedocs/db-less-dev.md.- Scheduled backups work again on PostgreSQL 18 boxes. The image shipped
the PostgreSQL 17 client, and
pg_dumprefuses to dump a server newer than itself — so from the moment a box moved to pg18, every scheduled backup failed silently. The image now ships the 18 client (a newerpg_dumphandles older servers, so pg17 boxes are unaffected). After upgrading, run one manual backup (Settings → Backups) to confirm the pipeline is alive. - Migration guide:
docs/upgrading-to-v0.202.md— the full path from the single-image era to the split (DNS, env additions, compose adoption, per-box smoke checklist, the pg17-era notes, rollback). - The runtime moves to Node 26 (
26.5.0, V8 14.6) — base imagenode:26-slim,engines: node >=26,.nvmrcand CI matched. Node 26 is the current line, not yet LTS; it promotes around Oct 2026, so until then this pin rides ahead of LTS deliberately, for the V8 and stream performance work. Nothing in the application tree needed changing: the only native/wasm dependencies (@napi-rs/canvas,libsodium-wrappers) are N-API/wasm and survive the ABI 147 bump untouched, and thenode:sqliteengine probes that back Tables v2 and the per-app broker — the exact things a runtime bump would break — pass unchanged. - The image’s base OS moves with it: Debian 12 (bookworm) → 13 (trixie),
since that is what
node:26-slimis built on. This broke the image build until fixed: the PostgreSQL apt repo line hardcodedbookworm-pgdg, which does not resolve on trixie, andapt-get install postgresql-clientfailed outright. The codename is now derived from the base image (. /etc/os-release→${VERSION_CODENAME}-pgdg) so the next base bump can’t reintroduce it. Anything else that assumes bookworm package names in an image layer is worth a second look. - The brain’s appearance is server-rendered — one delivery path. The
colour theme + the two display fonts (system-wide: they live on the anchor
owner’s profile row, so one admin choice brands every surface and every
browser) now render straight into the
<html>tag as attributes + inline font vars, everywhere: the client app’s root layout fetches the new publicGET /api/appearanceserver-to-server (30s cache, 2s timeout, fail-soft — a page never fails over branding) and the share/print surfaces read the DB directly. The old localStorage before-paint scripts are DELETED, not coordinated with: the document arrives correct, the client providers read the attributes back as initial state, and the theme-flash on a never-visited browser (which the client-origin split would have made universal) is gone. Semantics: share/print surfaces are the brain’s brand — the owner’s appearance is the only appearance, including branded PDF exports (still forced-white paper); a default choice is the absence of the attribute. The font picker also gains a home it never had: Settings → Appearance → Typography (it was previously mounted only on an unrouted demo page, so display fonts could not be set from the UI at all). - Footprint (measured back-to-back on one host, idle boot): server image
1.81 GB, down from 2.01 GB (the
.nextoutput is gone); settled RSS ~643 MB vs ~683 MB undernext start; boot-to-ready ~3.2 s vs ~2.4 s — the +0.8 s is tsx transpiling TypeScript at startup (the same tradeserver/apiand every worker already ship with), not request-path cost. The multi-minutenext builddisappears from the image build entirely.