Runbook: Content editing
How to author and edit case studies and blog posts. Content is git-based (ADR 0004); there is no CMS server and no external content API. Authoring today is done by editing MDX directly (Path A); a git-CMS UI for non-developers is planned but not yet built (Path B). Field-level reference: ../guides/content-authoring.md.
Where content lives
Section titled “Where content lives”apps/web/src/content/ cases/ # case studies -> /work/<slug> posts/ # blog posts -> /blog/<slug> services/ # capabilities team/ # people / authorsEach file is MDX with a frontmatter block validated by a Zod schema. Invalid or missing required fields fail the build — broken content cannot ship.
Path A — Edit MDX directly (current path)
Section titled “Path A — Edit MDX directly (current path)”- Create a branch.
- Add or edit an
.mdxfile in the correct collection folder. Use an existing entry as a template. - Fill in frontmatter per the authoring guide. Set
draft: truewhile a piece is in progress. - Add images alongside the entry — they’re committed to the repo under an enforced size budget (see the guide); the build generates the optimized derivatives.
- Run locally:
pnpm devto preview,pnpm typecheckto validate frontmatter against the schema. - Open a PR. CI validates and publishes a preview. Merge to publish.
Path B — Git-CMS UI (planned; not yet available)
Section titled “Path B — Git-CMS UI (planned; not yet available)”Not implemented yet. ADR 0004 adopts a git-based CMS (Decap CMS, with Sveltia CMS as the alternative) mounted at an admin route, to give non-developers a form-and-preview UI. It has not been built — there is no admin route or CMS dependency in the repo today. Until it ships, use Path A. The intended flow, once implemented, is:
- Sign in to the admin route.
- Pick a collection (Cases, Posts, …) and New or edit an existing entry.
- Fill the form fields (these map 1:1 to frontmatter), write the body, and add images.
- Save. The CMS commits to a branch and opens/updates a pull request.
- A reviewer approves; merge triggers the deploy pipeline. A preview deploy is available on the PR before publish.
The design goal: non-developers never touch git directly, but every change still flows through review and history.
Adding images
Section titled “Adding images”- Images are committed to the repo alongside content (
apps/web/src/{content,assets}) under an enforced size budget; Astro’s build generates the optimized, display-sized derivatives, served as immutable hashed static assets by the Worker (ADR 0004, ADR 0006). - Provide meaningful
alttext for every image (accessibility is a gate — see the NFR doc). - Image budgets are enforced. Committed content rasters (
apps/web/src/{content,assets}) must be ≤2560px long edge and ≤900 KB each — sources feed Astro’s pipeline, which generates the display-sized derivatives, so oversized originals only bloat the repo. The pre-commit hook auto-normalizes staged images; if CI’sImage budgetscheck ever flags one, runpnpm images:fixand re-commit. (Genuine exceptions go in the ALLOWLIST inscripts/images.mjs, with a reason.) - No animated GIF. The build can’t emit an animated derivative, so the
Image budgetscheck rejects any.gif— ship motion as animated WebP instead (same ≤900 KB budget). See content-authoring for how.
Publishing checklist
Section titled “Publishing checklist”- Required frontmatter complete and valid (
pnpm typecheckpasses). -
draft: falsewhen ready to publish. - Slug is correct and stable. For blog posts migrated from the legacy site, the slug must match the legacy
/blog/<slug>to preserve SEO. - Images have alt text and pass the size budget (run
pnpm images:fixif CI’sImage budgetscheck flags one). - Preview deploy reviewed.
Common pitfalls
Section titled “Common pitfalls”- Build fails after editing frontmatter → a required field is missing or mistyped; read the schema error and the authoring guide.
- Post not appearing → still
draft: true, or a futurepublishDate. - Broken blog URL after migration → slug changed; restore the legacy slug or add a redirect (ADR 0011).