CMS Chronicle·April 2026·

CMS Chronicle #13: Everything, and the Front Door

485 commits in 12 days. We rebuilt the agents engine top to bottom (six phases), shipped one-click Docker deploys to Fly.io, taught the CMS to teleport itself between machines, gave editors passwordless sign-in with passkeys and TOTP, and finally wrote down the master plan for the mobile companion app. This is the densest stretch in the project's history.

485 Commits in 12 Days

Chronicle #12 closed out the AI-native editor on March 27. Twelve days later, we're at 485 commits — about forty per day — and the project is recognisably bigger across at least eight orthogonal directions. This chronicle tries to do justice to all of them. It is long. Pour a coffee.

The headline themes:

  • The agents engine got six phases of work in a single sprint and is now a real autonomous content system, not a cool demo.
  • One-click Docker deploys to Fly.io — no more wrangling Dockerfiles, the CMS does it for you from the deploy wizard.
  • F122 Beam — sites can teleport themselves between CMS instances. Localhost to cloud, cloud to localhost, cloud to cloud. Live streaming mode included.
  • F59 Passwordless — passkeys (WebAuthn) and authenticator-app TOTP. The front door is finally first-class.
  • F94 Favorites, F92 Desktop PWA, F35 Webhooks, F124 Snippet Embeds — the long tail of "obviously you should have this" features that finally got built.
  • F119/F121/F31 — the docs site shipped with 89 pages, and we wrote the Next.js helpers package that makes other Next sites consume the CMS in one import.
  • F07 Master Plan — the Capacitor companion app finally has a written plan we can hand to a fresh session.

Let's go.

The Agents Engine, Reborn

We had agents in #12. They could write a draft, the curation queue picked it up, an editor approved or rejected. It worked, but it was rough. Six phases of polish later, it's a different product.

Phase 1 — quick wins. GEO role surfaced in the agent UI, MCP credential leak plugged, web-search fallback when the primary provider rate-limits. Small but they were the splinters.

Phase 2 — feedback loop closed. Every correction an editor makes in the curation queue and every rejection now feeds back into the agent's prompt as concrete examples. The agent learns the house style without you ever opening its prompt template. After three or four corrections on the same agent, you stop needing to correct it.

Phase 3 — multi-model alternatives. When an agent generates a draft, the curation queue can show alternative versions from different models side by side. Click the chip you like — Claude, Gemini, GPT — and that's the version that goes through. The losing alternatives become training signal for the feedback loop in Phase 2.

Phase 4 — per-agent cost guards. Daily, weekly, monthly USD caps per agent. Pre-flight check before the LLM call, not after. A runaway agent now stops at $1.52 of its $1.50 cap instead of burning the entire monthly budget at 3 AM. The scheduler, manual runs, and workflow steps all check the same guards.

Phase 5 — SEO score on draft. The agent generates the post AND the SEO score for it, in the same pass. By the time it lands in the curation queue, you can already see whether the title is too long, the meta description is missing, the keyword density is off. No more "approve, deploy, then notice the SEO panel is empty."

Phase 6 — the big one. Three chunks:

  • Locale-per-agent + auto-translate on approval. A Danish content writer agent and an English content writer agent, sharing the same prompt template, are now one agent with two locales. Approve the Danish draft, the English version is auto-translated and queued behind it.
  • Workflow pipelines. Agents can now be chained into workflows. Step 1: research the topic. Step 2: outline. Step 3: write the draft. Step 4: generate hero image with Nano Banana 2. Step 5: SEO check. Each step is its own agent with its own model, prompt, budget, and feedback history. The output of step N is the input to step N+1.
  • Drag-and-drop step editor + JSON mode. Reorder steps by dragging. Toggle to raw JSON if you want to edit a workflow programmatically. The same ModeToggle component is now shared across the agent and workflow editors, so the toggle behaves identically everywhere.

And a third quick-win pass on top of all of that: cron schedules for workflows, per-step collection assignment, rejection-prompt refinements.

Image generation got real too: the generate_image tool now calls Gemini Nano Banana 2 (the new model id) and pushes the result straight into the media library with auto-generated alt-text. A no-hallucination contract on failure: if the model can't generate an image, the agent says so explicitly instead of inventing a URL.

The agents marketplace shipped at L2 (local) and L3 (marketplace). You can save your tuned agents as templates and share them with other CMS instances.

If you tried agents in #12 and bounced off, try them again. It's a different product.

F122 Beam — Sites That Teleport

This one feels like sci-fi until you use it.

Phase 1. A site can be packaged into a single .beam archive — content, media, schema, settings, chat history, memories, user state, the lot. The archive is a portable, complete snapshot. Drop it on another CMS instance and the site materialises there as if it had always lived there.

Phase 2. Same thing, but live streaming. Open Beam in the source CMS, point it at a target CMS, click Start. The two instances negotiate over a streaming protocol and content changes propagate as they happen. You can edit on localhost and watch the cloud instance update in real time, or vice versa.

The primary use cases we built it for:

  • Localhost → cloud first deploy. You build a site on your laptop, Beam it to the production CMS in one click. No git, no rsync, no manual upload of _data/.
  • Disaster recovery. Cloud got nuked? Beam the latest snapshot back from your laptop.
  • Site cloning. Take a working site, Beam it under a new slug, customize the copy. We use it internally to spin up new example sites.
  • Cross-org migration. Move a site from one organisation to another without touching files.

F122 is closely related to F95 (cloud backups), which got its own attention this stretch: an S3-compatible adapter that works with R2, B2, Scaleway, Hetzner, and AWS. pCloud rewritten from REST to WebDAV (the REST API has a 2 GB JSON request limit that breaks any backup over a few hundred files; WebDAV doesn't). Storage quota management with auto-prune of the oldest snapshots when you hit the cap. Local/cloud badges on each snapshot in the UI so you can see where each one lives.

F119 — One-Click Docker Deploy

The deploy wizard now has a new template: "Run on Fly.io". You pick the region, give it a name, click Deploy. Behind the scenes:

  1. The CMS pulls the prebuilt image from GitHub Container Registry (ghcr.io/webhousecode/cms-admin:latest)
  2. Calls the Fly.io Machines API with a generated fly.toml
  3. Provisions a volume, sets the secrets, starts the machine
  4. First boot runs the auto-setup: creates the admin user, seeds the team file, installs the example CMS Demo site
  5. You're shown the live URL

No Dockerfile to write. No fly launch to run. No env vars to copy-paste. If you want to roll your own image we still publish the Dockerfile, but most users will never need it.

The progress is shown in a deploy modal with a step-by-step gauge — connect, build, push, provision, seed, healthcheck, done. Each step turns green or red so you know exactly where it failed if it fails.

The GitHub Pages adapter learned to handle a 500 from the Pages API when Pages is already enabled — turns out GitHub returns a server error instead of a 409 in that case. Now we treat it as success.

F59 — Passkeys & TOTP

The front door has been email/password since day one, with GitHub OAuth bolted on later. Both work, but they're not 2026 sign-in. This stretch we added two passwordless paths.

Phase 1: passkeys (WebAuthn). FaceID, TouchID, Windows Hello, hardware security keys. Add a passkey from Account → Security, sign in with one click. The CMS only stores the public key, so a database leak doesn't compromise anyone. Cross-device sign-in works via the browser's built-in QR-based hybrid transport — no separate app required. We learned a lot about secure-context requirements (passkeys need HTTPS or localhost — plain HTTP via LAN IP doesn't work) and ended up wiring next dev --experimental-https with mkcert into the PM2 ecosystem so dogfooding from a phone is one command away.

Phase 4: TOTP. Microsoft Authenticator, Google Authenticator, Authy, 1Password — any RFC 6238 app. Account → Security → Add new app, scan the QR, type a code, get ten backup codes. The login flow now gates on TOTP after primary credential verification: a short-lived cms-totp-pending cookie holds the user identity for five minutes while they fetch the code, then a separate verify endpoint exchanges the pending cookie for a real session. Backup codes are sha256-hashed (32 bits of entropy means bcrypt is overkill and was causing test timeouts) and single-use — constant-time comparison, atomic consumption.

We also built Phase 2: QR code login — a Discord-style cross-device flow where the desktop shows a QR, your mobile companion app scans it, and you're signed in. The full implementation is in the repo but the UI is hidden behind NEXT_PUBLIC_CMS_ENABLE_QR_LOGIN=false until the mobile app actually exists to scan it. We'll flip the flag the day we ship F07.

Don't talk about phase 3 just yet.

F121 — The Docs Site Shipped, and Other Sites Can Now Eat Their Own Dogfood

F31 (the documentation site) closed this stretch with 89 pages in English and Danish, full JSON API, GitHub Actions deploy to Fly.io. It's live at docs.webhouse.app. Every link in the CMS admin's HelpCards now points at a real page on it.

F121 is the package that came out of building the docs site: @webhouse/cms/next — drop-in SEO helpers for any Next.js site that consumes content from a webhouse.app CMS. One import gives you the metadata generator, the JSON-LD generator, the canonical URL helper, the sitemap, the robots.txt. The docs site is the proof. The next time someone builds a Next site against the CMS, they should be running a working SEO setup in about ten minutes.

The /update-docs skill landed alongside it — when CMS features ship, a single command reads the recent commits and queues doc updates on the docs site, with the post-commit hook now reminding you to run it.

The Long Tail of Obvious Features

Things that should have been there from day one and finally are:

  • F94 Favorites. Heart toggle on collections, sidebar Favorites section, command palette pin, dedicated /admin/favorites page with list and grid views. You can finally pin the three things you actually edit.
  • F92 Desktop PWA. The CMS admin installs as a desktop app via the browser's PWA install prompt. Full-screen, no browser chrome, dock icon. Same UX as a native app for editors who live in the CMS.
  • F35 Webhooks. Discord and email integrations across content events, deploy events, and agent events. Discord embeds are linked at the curation queue (not the preview URL — that broke if the preview URL was templated), they only emit absolute URLs (relative URLs broke in mobile Discord), and the redundant Generated image field was dropped in favour of a description-link.
  • F124 Snippet Embeds. A TipTap node for {{snippet:slug}} — pulls a snippet from the snippets collection at render time. Edit the snippet once, every post that embeds it updates. The snippet picker is a fuzzy-search dropdown.
  • F81 Homepage Designation. Explicit "this is the homepage" setting on a single document, instead of guessing from slug:'home' or slug:'index'.
  • Clone Site. Full filesystem copy with secret stripping. node_modules symlinked from the source so the clone doesn't take 30 seconds to install. Deploy state cleared on the clone so you don't accidentally republish the source.
  • Reserved collection name guardrail. The site validator now warns you if a collection's name conflicts with built-in admin panels (media, interactives, settings, admin, etc.). The same release added i18n preview redirects so bilingual sites have working preview URLs for non-default locales.
  • F116 HelpCards → HelpButton. The contextual help panel moved from a per-page sidebar card into a single button in the action bar with a portal popover. Less visual noise, same access.
  • F109 Inline Proofreading finished — ProseMirror decorations for inline corrections with accept/reject, plus the tab isolation fix that stops a proofread on one document leaking into another.
  • F117 MCP Parity — 43 MCP tools, full parity with the chat tool surface. Every action you can take via chat, you can take via MCP, and vice versa.
  • Instant Content Deployment — when a content change can be served via on-demand revalidation, we skip the full deploy. Editing a post and seeing the change live in 2 seconds instead of 90 is a different product.

F120 — Onboarding

New users now get a guided product tour the first time they sign in. Tooltip-based, points at the sidebar, the editor, the chat toggle, the deploy button, the AI panel. We rewrote the tour-provider once mid-stretch because the original had a re-render loop that occasionally chain-skipped two steps. The current implementation expands the sidebar before pointing at sidebar items, advances robustly through dynamic mounts, and has tooltip design polish. There's a ONBOARDING=true env flag for dev so you can iterate on the tour without resetting your user state.

F07 — Mobile COCpit Master Plan

We're not building it yet, but we wrote the master plan: docs/features/F07-mobile-cocpit-master-plan.md. A single document a fresh Claude session can pick up and start executing on. It's based on the Capacitor + Fastlane recipe from the fysiodk-aalborg-sport project (two apps, both already approved on App Store and Google Play).

The principle is unusual: Claude does everything. Code, builds, simulator automation, App Store Connect API calls, Apple Developer Portal forms via Playwright (where the API doesn't reach), Google Play Console uploads via Fastlane, Firebase project provisioning, screenshot generation, TestFlight submission. Christian provides credentials once into .env.secrets, then orchestrates and curates. Manual portal clicking is treated as a bug — file an issue and write a script. The pipeline is designed to be reusable for the next mobile app after this one.

Eight phases, 12-16 days end-to-end to a feature-complete app submitted to both stores. Phase 1 ships an MVP shell with biometric auth on day one.

Smaller Things That Mattered

  • PM2 manages cms-admin on port 3010 now, with a hard rule in CLAUDE.md that Claude cannot kill or restart the dev server without explicit permission. Cost us a session once. Fixed.
  • HTTPS dev setup end-to-end: mkcert + next dev --experimental-https wired into PM2, plus a single-shot setup-https-dev.sh script that installs the cert into the system Keychain and serves the rootCA to your phone via a temporary HTTP server with a QR code.
  • F127 Collection Purpose Metadata — collections now declare a kind and description so the chat assistant and onboarding tour can reason about what each collection is for, not just what fields it has.
  • Curation Preview modal — see exactly what an agent draft will look like as a published post, ESC to close, no more guessing.
  • Settings anchor navigation with glow highlight on the target section. Tiny but you notice it.
  • PM2 ports script (pm2-ports.sh) — pm2 list with a PORT column so you can actually tell which dev server is on which port.
  • F102 Schema Drift got more tests after the inline proofreading work uncovered an edge case.

The Direction

If #12 was the chronicle of "the AI-native editor", #13 is the chronicle of "and now also everything else". Authentication, deployment, observability, mobile groundwork, content automation, content streaming, the docs site, the feedback loop, the front door.

We are trying to build a CMS where every grim manual step has been automated away. Six months ago, deploying a site to Fly.io meant writing a Dockerfile, learning the Machines API, and debugging volumes. Now it's a click. Six months ago, signing in meant typing a password. Now your face does it. Six months ago, telling an agent how you wanted it to write meant editing a prompt template by hand. Now you correct it three times in the curation queue and it learns.

The next chronicle will probably open with the Mobile COCpit. After that, who knows. The roadmap is full and the velocity hasn't slowed.

We are still building the CMS we needed thirty years ago. Twelve more days, and it gets closer.