Meridian handbook
For agents

Checklists

What done means for a coding agent handing a Meridian site back, as checklists with the command that proves each item, covering the project check, every route rendering, accessibility, content, SEO, performance, deploy configuration and secrets.

Work through every list before you say a site is finished. Each item names the command that proves it. When an item cannot be proved (no database, no running server, a decision only the person can make), say so in your report instead of ticking it.

The project

  • engine-dmo check exits 0.
  • engine-dmo check --strict exits 0.
  • The text output of engine-dmo check has no warning: line for a template, or each one left is explained. The summary, --strict and --json do not count template warnings, so read the lines.
  • engine-dmo seed verify says the seed matches the schema, and engine-dmo seed apply on an empty database exits 0 (only apply checks field values).
  • On engine-dmo, a site with no partner businesses has the member portal turned off (portal: { mode: "off" } in config/portal/portal.yaml).
  • engine-dmo schema plan exits 0 with “No changes” (the database matches the files).
  • No id: was typed by hand or copied; git diff shows ids only where a verb or check --fix wrote them.
  • AGENTS.md and the skills were not edited by hand (regenerate them with engine-dmo guide --out AGENTS.md and engine-dmo guide --skill --out .claude/skills/meridian-project/SKILL.md if in doubt).
  • No file under storage/, public/build/, node_modules/, .meridian/ or any .env is committed.

Every route renders

  • engine-dmo a11y audit --all reports 0 failures on every site (--site <key> for each). It renders every page the sitemap would list, plus the home page, whether or not seo.sitemap.enabled is on, and fails any that does not answer 200.
  • Pages outside the sitemap render: the search page with and without a query, each index page filtered by each facet, the last page of each paginated index, and the 404 page (engine-dmo render /no-such-page --json answers 404 with your template and empty errors).
  • Every locale renders: engine-dmo render <path> --locale <code> --json for one page of each type.
  • Every page that runs query() has rendered (a non-indexed field fails only at render).
  • Every render --json shows "errors": [] and "status": 200. A 301 still exits 0, with kind: redirect and a non-empty a11y (a page has exactly one h1, this one has 0), and a11y audit --path on one fails html-lang and page-title. Neither is a defect in the page: use the address it redirects to.

Accessibility

  • engine-dmo a11y audit --all reports 0 warnings as well as 0 failures.
  • render --json shows "a11y": [] on each kind of page.
  • Every page: lang and dir on <html>, one h1, a skip link to <main id="main">, labelled <nav> landmarks, aria-current="page" on the current menu item.
  • Every form control has a visible label; errors are summarised and linked; required fields are marked in text, not colour alone.
  • Every library image has alt text in every locale (or is marked decorative); engine-dmo media stats shows alt text coverage.
  • Your CSS keeps text contrast at 4.5:1 (3:1 for large text and interface parts), shows a visible focus outline, and reflows at 320 CSS pixels wide.
  • engine-dmo a11y statement --out accessibility-statement.html written for the person to complete, and a page for it in the footer menu.
  • You told the person a manual pass (keyboard only, a screen reader, 200% and 400% zoom) is still needed: the automated checks are evidence, not conformance.

Content

  • Every routed type has published documents with realistic text, within each field’s limits.
  • Every page the navigation links to exists and is published in every locale it is linked from.
  • Every string the templates print is in config/strings/<locale>.yaml for every enabled locale (check lists missing keys).
  • Every label in the schema has every enabled locale (check warns otherwise).
  • Every label check does not warn about has every enabled locale too: endpoint input labels in endpoints.yaml, navigation item labels in seed/navigation/, and the first option of each select.
  • Every email template has its text in each enabled locale (email templates have no t(); see Enquiry form).
  • Photographs are the person’s own, licensed and credited; none is a generated image presented as a real place.
  • A staff user exists or the person knows how to create one: engine-dmo user create --email ... --name ....

SEO

  • base.html prints {{ head() }} once in <head> (title, description, canonical, hreflang, robots, Open Graph).
  • base.html prints {{ jsonld() }} once in <head>. Prove it: engine-dmo render <path> | grep -c 'application/ld+json' prints 1 on an entry page and an index page. check warns only when a type has a structured_data block, and the warning does not fail it.
  • Every routed type that is a place, an event or an article has a structured_data block, and engine-dmo check --strict passes with it (SEO and structured data).
  • seo.title_template is set in site.yaml, so titles carry the site’s name: engine-dmo render <path> | grep '<title>'.
  • seo.sitemap: { enabled: true } is set. It defaults to off, when /sitemap.xml answers 404 and a11y audit --all still passes: engine-dmo render /sitemap.xml --json answers 200.
  • seo.robots: { enabled: true } is set. It defaults to off, when /robots.txt answers 404: engine-dmo render /robots.txt --json answers 200 and names the sitemap.
  • Each type’s documents have a meaningful summary (used as the meta description), or the type has an seo field that is filled.
  • render --json of an entry page shows a canonical on the site’s configured domain and, on a multilingual site, hreflang for each published locale and x-default.
  • Filtered index pages are noindex (the engine does this; check it is not overridden).
  • server.base_url is the real public origin in production.

Performance

  • Every library image goes through img(), picture() or figure() with a declared transform; only the main image of a page has priority=true.
  • render --json images shows sensible sizes and no image downloads far wider than it displays; engine-dmo dev status --json lists oversized images when dev up has run.
  • render --json queries does not grow with the number of items on the page. The engine’s own work is a steady baseline: about 4 to 8 statements for a 404, about 20 for a plain page, 30 to 45 for a page with images, navigation or its own query(), and an index of 24 items ran 18, the same as an index of one. A count in those ranges is fine. The problem to look for is one sql_summary repeated once per item, or a count that rises when you add items: a query() inside a loop. Use with() or one query outside the loop.
  • Stylesheets and scripts are linked through assets.url() so they carry a version; no inline executable scripts.
  • Expensive repeated fragments use {% cache %} where the content allows (Caching).

Deploy configuration

  • ENVIRONMENT=production engine-dmo check --no-db exits 0.
  • server.security.signing_key: ${SIGNING_KEY}, server.base_url from the environment, server.trusted_proxies matching the reverse proxy (or real_ip_header: "").
  • A real mail driver (smtp, postmark, ses or resend) with mail.from; staff two-factor sign-in left on.
  • Storage chosen for production (a persistent volume for filesystem, or s3) (Storage and media).
  • A search service configured, or the person told the search page runs in browse mode.
  • The deploy sequence written down for the person: back up, migrate up, schema apply, serve, health --readiness --wait 60s, warm (Deploying).
  • Extensions, if any, built for the production engine’s exact build.

Secrets

  • grep -rn for passwords, keys and tokens under config/ finds none: every secret is a bare ${VAR} (check enforces this for known secret paths; check custom ones yourself).
  • No secret in templates, seed files, public/, commit messages or your report.
  • .env is in .gitignore; .env.example lists every variable with placeholder values only.
  • Your report lists every environment variable the person must set for production, without values.

The report

Tell the person what you built, how to run it (engine-dmo dev up, the URL, how to sign in), what you verified with which command, what you could not verify and why, the environment variables to set, and every decision left to them.