Release 0.200
v0.200.0 — 2026-07-24
The true server/client split. Mantle is now TWO applications shipped as two
images from one lockstep release: mantle-server — the headless backend
(the full /api/** surface, the DBOS runner, every worker, and the public
surfaces: /s/<token> shares, the /team workspace, /hub, PDF print) — and
mantle-client — the owner UI, a ZERO-SECRET Next app holding no database
connection, no session secret, and no server code, driving the server origin
purely over bearer + CORS. Run the server alone for a headless brain; point any
client at any server via one env var (MANTLE_SERVER_ORIGIN, read per-request
— one prebuilt client image serves every box).
Under the hood: the owner web session is a first-class bearer (30-day tokens
via POST /api/auth/token, atomic rotation via /token/refresh, per-device
revocation with a Signed-in devices panel under Settings → Security); PDF
export works over ANY auth transport (the exporter mints its own short-lived
internal render cookie for the Chromium sidecar); the shared UI layer lives in
packages/web-ui; and an ESLint boundary makes a server-value import in the
client tier a build error. Deploys: docker-compose.yml (server) +
docker-compose.client.yml (client) share one .env and one
MANTLE_IMAGE_TAG; the server Caddy gains an app.<domain> vhost
(MANTLE_CLIENT_SITE_ADDRESS); the updater rolls and drift-checks both stacks.
A new end-to-end Playwright net (owner flows, SSE, asset tokens, shares, team
tokens, PDF, mini-app sandbox — run in BOTH topologies) gates the whole arc,
and set MANTLE_PUBLIC_URL on every box: the NEXT_PUBLIC_APP_URL server-side
fallback is deprecated.