HERON

The rules people and agents follow

Specifications

The specifications are the exact rules of HERON, written as an open contract: what a site folder holds, how files become addresses, what every field means, what the build checks and how a site reaches a server. Designers, editors, developers and AI agents work from the same text.

Every specification is copied to this site in full, in English and Russian. The repository on GitHub is updated first, and the site follows right after; each page ends with a link to its latest version in git.

In plain words

Think of HERON as a builder that follows a recipe book. The specifications are that recipe book: they say which ingredients go into a site folder (pages, photos, one settings file) and exactly what comes out (pages, sitemap, files for search engines and AI). Nothing happens «by magic»: every rule is written here.

Because the rules are written down, three very different helpers can work on one site without stepping on each other: you editing a text, a designer changing the look, and an AI agent adding pages. All three follow the same text.

Example. A rule from the contract says: a file content/en/about.md becomes the page /about/. You do not configure this anywhere. You create the file, and on the next build the page is there.

content/en/about.md   →   https://example.com/about/
content/ru/about.md   →   https://example.com/ru/about/

When you need these pages. When a build stops with an error code, when you want to know exactly what a field does, or when you hand the site to an agent and want it to follow the rules.

When you can skip them. To make and publish your first site, the guides are enough. Come back here when you need the exact answer.

What the contract is

A contract is an agreement between two sides. Here one side is HERON, the other is your site folder. The site promises to keep its files in the agreed places and formats, and HERON promises to build it the same way every time and to say exactly what is wrong when something does not fit.

Because the agreement is written down and public, nobody has to read HERON's code: not a person, not an AI agent. When the documentation and the behaviour differ, that is a mistake in HERON and gets fixed in HERON, together with the text.

The contract is also a promise about time. A site that follows it today builds tomorrow: the stable version of HERON keeps building older sites, and features are added or removed only on purpose, with a version change.

Map of the specifications

Page What it covers Read it when
Site folder and addresses what HERON is, the project structure, the content tree, keys, slugs and URLs, links, old addresses, the 404 page, languages you plan a site or wonder where a file goes
Page format the markdown file, the front matter of eight groups, sections and their parts, the twelve section forms you write or check a page
site.yaml every block of the settings file: site, organisation, SEO and llms.txt notes, analytics, build, schema.org, nginx, gone you change a setting
theme.yaml page types and their sections, links between pages, image sizes and formats you build or change a theme
Images and files the media folder, video, what is not cut, static, .env, what is generated and what is made by hand, the constraints you add pictures, files or secrets
Build the eight build stages, the section parser, what a template sees, generators, commands, error codes, hooks a build stopped with a code, or you write a theme
Deploy containers, update scripts, CI, the nginx minimum, moving an old site, backups before the first publication or a move
Delivery the image and the pip package, versions, the folder and image outputs, the nginx config, tags, forms, security you choose a version, build an image or set up a server

How each section form looks on a real page, with its markdown, HTML and styles: the forms reference.

Section forms at a glance

FormHow you write itWhat the theme draws
prosePlain paragraphsText block
list- item or - **Term.** textList or pairs of term and text
steps1. **Title.** textNumbered steps
checklistThree or more ### groups, each with a listCards
two-listsExactly two ### groups with listsTwo columns, like «fits / does not fit»
tableMarkdown table with a separator lineTable
factsKey and value lines, one pair per line without a headerKey facts
faq### questions without listsQuestions and answers, plus FAQ markup for search
calloutA quote >Highlighted note
timeline, gallery, alertBy the class .timeline, .gallery, .alertTimeline, gallery, warning list

How the specs stay current

  • One source. The specifications live in the HERON repository, next to the code they describe.
  • Same change, same commit. When behaviour changes, its description changes in the same commit. Tests check the sample files, so a stale sample fails before anyone copies it.
  • Git first, this site right after. A change lands in the repository first; the copy on this site is updated right after, so the gap between them stays minimal. Every page here ends with a «Source» section that names the file and links to it.
  • Permanent links. Links to the prod branch always show the latest stable rules, for example 20-data-contract.md on the prod branch. An exact version, like 0.1.0, keeps its own copy forever.
  • Inside the image. heron docs contract prints the version that matches your HERON, even offline. More in docs in the terminal.

Architecture

HERON is a file-based site builder. The input is a folder with content, images, a theme and one config. The output is a static site in dist/ or a ready nginx image with that site inside.

HERON is not copied into the site. It is delivered as one artifact, the image heron:VERSION (and the same code as a pip package for local work). The site declares the version it needs in site.yaml, and HERON refuses to build a site whose version it does not support.

HERON is built for several different sites from the start: designed around one, it would degrade into that site with abstract variable names.

Stack

Core: Python 3.12 · Jinja2 · markdown-it-py · pydantic · Pillow · click. Package manager uv, linter ruff, tests pytest.

Around it: Docker · nginx · Bash.

Forms service (heron-relay, a separate component, later): FastAPI, no database; SQLite only as a retry spool.

Components

  • heron/core/: markdown parser, tree and URLs, link resolution, renderer, image processing;
  • heron/modules/: generators: sitemap, robots, llms.txt, JSON-LD, hreflang, redirects, feed;
  • heron/contracts/: pydantic schemas of site.yaml, theme.yaml, the front matter and the HERON version;
  • heron/cli.py: the commands new, init, check, serve, build, page;
  • scripts/site.sh: building, packing into an nginx image, previewing and checking sites from the HERON repository; settings in env/.env.SITE.ENV;
  • the site folder: site.yaml, content/, data/, media/, static/, theme/, plugins/. There is no HERON code, no Dockerfile, no compose and no build scripts in it;
  • heron-relay: a separate service that receives forms: email, a CRM webhook, Telegram. It is deployed only where there are forms.

Decisions

Recorded in the delivery specification:

  • a static site builder instead of Django or Wagtail: a site has nothing to serve at run time;
  • the core is delivered as an image with a version, not as a copy in every site repository: three copies of the core mean three diverging builders;
  • forms are handled by a separate service, not by HERON: HERON must stay «one pass, no state, exit»;
  • a database stores neither content nor site settings. Files are the source of truth. Breaking this rule turns HERON into WordPress;
  • design mockups are translated into Jinja2; support.js (69 KB of React runtime) does not go to production;
  • the build runs without network: --network=none, nothing is downloaded at build time.

Diagram

                 site folder
   site.yaml · content/ · data/ · media/ · static/ · theme/
                     |
                     v
        heron:VERSION   (image or pip package)
     contracts -> parser -> resolve -> render -> generators
                     |
          +----------+-----------+
          v                      v
        dist/               nginx image
     (to hosting)      (site.sh image on the host)
          |
          v
        nginx  ------>  visitor
                            |
                            v
                     heron-relay (later)
                       |    |     |
                    email  CRM  Telegram

The site does not depend on heron-relay: if the service is down, static pages keep being served, and only form submission is lost.

The architecture text mirrors docs/architecture.md on the prod branch (revised 2026-10-04).

Questions

Do I need to read the specs to make a site?

No. The guides and the configurator are enough for most sites. The specs are for exact answers: a field name, an error code, an edge case.

Why is the contract open?

So that people and AI agents can follow the same rules without reading code, and so that a site built today still builds tomorrow. Anything not written in the contract is not promised.

Which is right if this site and git differ?

Git on the prod branch: it is updated first. The copy inside your image is right for your exact version. This site catches up right after a change.

What if I need something the contract does not cover?

Check what HERON can already do first. If it is really missing, describe the need in the repository; on your site, take the simplest honest option meanwhile.