How the pieces fit
A map for developers working on Cratly rather than with it. It answers the questions that are expensive to reconstruct after a break: which repository owns what, what the pieces promise each other, and what a change in one of them obliges you to do in the others.
If you only want to build or edit a website, start with the Guide instead.
Decisions
The reasoning behind the contracts, recorded when it was made:
- ADR 0001 — Section-type schema: who owns the grammar, who produces the manifest, and why fetching it at runtime was avoided.
- ADR 0002 — Licensing: MIT for what others build with, GPL for the finished application.
- ADR 0003 — Repository topology: why the repositories stay separate, and what had to change instead.
- ADR 0004 — Site templates: one repository per template, one curated catalogue, and what a browser can actually do with it.
The repositories
| Repository | Published as | Owns |
|---|---|---|
| site (this one) | cratly.io | The framework-agnostic specifications, the section-type schema, the user guide, and this map |
| editor | editor.cratly.io | The browser-based editor and the GitLab review workflow |
| scavold | npm scavold, scavold.io | The reference adapter: turns a content repository into a VitePress site |
| templates/scavold-plain | — | One of the site templates — starting points for a new website repository. Further templates are flat sibling repositories in cratly/templates/, listed in the same catalogue |
| test-website | — | Fixture site: the surface on which adapter changes are exercised |
| tools | container registry | CI base image that pre-seeds the package cache for site pipelines |
Alongside them sit the website repositories themselves — one per site, holding content, theme and pipeline. They are the only repositories editors ever touch.
The three contracts
Everything the pieces know about each other passes through three artifacts. Each bug found so far has lived in one of these seams, not inside a single repository.
.cratly.config.yaml — hand-written, read by both sides
Describes the website: where pages and media live, which container types and frontmatter fields exist. The editor reads it to configure its UI; the adapter reads it to locate sources and apply content rules. Neither may extend it unilaterally — the specification is owned here.
.cratly/sections.json — generated by the adapter, committed, read by the editor
The section-type manifest: which typed properties each container kind accepts, so the editor can render matching controls. Written by the adapter's build, committed to the website repository, and fetched by the editor through the GitLab API. See ADR 0001 for why it is split this way.
It records the adapter's version, so every adapter upgrade changes this file in every website — even when no container type changed. Rebuilding and committing it is part of an upgrade, not an afterthought, and the site pipeline fails when the committed copy has drifted from what the build produces.
The build artifact — produced by the site pipeline, read by the editor
The site pipeline publishes the built site as a GitLab job artifact, which the editor downloads to show a preview of a draft. See CI / CD.
Who depends on whom
specifications (this repo)
│
┌────────┴────────┐
│ │
editor scavold
│ │
└──── website repository ────┘
│
site pipeline ── uses ── CI image (tools)The rule that keeps this workable: the editor knows nothing about Scavold, and Scavold knows nothing about the editor. Both know the contracts. A second adapter (Next.js, Astro, …) is meant to be possible without touching the editor — so anything framework-specific that creeps into the editor is a bug, and anything about editing that creeps into an adapter is too.
Rules that follow
- Versions decouple the repositories, not branches. Websites depend on a published
scavoldrelease, so the adapter'smainmay move freely. Publish the producer first — a-rc.Npre-release is the tool for that — then upgrade the consumers. A consumer that depends on unreleased behaviour is how a contract silently rots. - Two generated files are committed on purpose:
bun.lockand.cratly/sections.json. CI verifies the second is current. Never let CI write either of them back. - Website repositories keep a protected default branch. Drafts are branches, publishing is a merge — that is the editor's workflow, not a preference. Its Check repository setup panel verifies it.
- Everything an author uploads into the media folder has to reach the published site. The editor accepts any file type there and writes references relative to that folder, as the
media-filetype specifies; turning those into working URLs is the adapter's job. Images become scaled variants, everything else is published by copying — and references are rewritten wherever they occur: image syntax, links, and container arguments declared asmedia-file. An adapter that covers only images leaves authors with dead links they cannot diagnose, because nothing fails during the build. (The reference adapter covers all of it from 0.2.0-rc.3 onwards.) - The CI image seeds a cache. It can never make a build wrong, only slower — each website's own lockfile and
--frozen-lockfiledecide what gets installed.
Where to change what
| I want to … | Change it here | Then |
|---|---|---|
| add or alter a config key | the specification in this repo | teach the adapter, then the editor |
| add a property type for the editor's controls | the section-type schema here | adapter emits it, editor renders it |
| add a container type to one website | that website's .cratly.config.yaml | rebuild so the manifest picks it up |
| add a built-in container to the adapter | scavold (component, manifest, docs) | exercise it in test-website |
| change how editing behaves | editor | — |
| speed up or fix site pipelines | tools, then rebuild the image | — |
| start a new website | copy a site template | see its README, and Starting a new website |
| add a site template | a new sibling repository, plus one entry in docs/public/templates.json here | see the templates reference |
Verifying an adapter change
Unit tests in scavold cover the build-time logic. What they cannot cover is the seam: whether a real site still builds and still produces the markup the documentation promises.
scavold therefore carries its own fixture site, built and checked on every commit:
bun run test:fixture # builds test/fixture-site and asserts what came outIt generates its media inputs, builds the site from scratch and checks the emitted manifest, the container markup, the image ladder, the published non-image media and the video container — the places where defects have actually appeared. Its CI job makes the check unavoidable rather than optional, which is why it exists: three defects were found this way in one afternoon, each documented as working for months, and each had cost a release to verify beforehand.
Before publishing, the same fixture is worth building once against the packed package rather than the working tree, because npm pack honours the files list and the exports map while a local path does not:
cd scavold && npm pack
# install the tarball into a scratch copy of a site, then build ittest-website is a different fixture with a different job: it is the live GitLab project the editor's end-to-end tests authenticate against, with real branches and merge requests. Keeping the two apart is ADR 0003 — while they were one, neither was maintained for both purposes.
Two habits earned their place while writing those checks:
Show that a new test fails without the fix. Put the defect back, run the test, expect red, restore. Twice a convincing-looking test guarded nothing — once because the outcome was identical with and without the fix, once because the defect needed a different trigger than the test used (automatic linking fires while typing, not while loading a document). A test that has never been red has not been tested.
Everything the build produces stays out of git. public/media/ is generated in full — image variants and verbatim copies of other media — while bun.lock and .cratly/sections.json are the two generated files that are committed on purpose. When a release starts emitting a new kind of output, the ignore rules need to catch up, or every download ends up duplicated in the repository.
Local setup
Every repository uses Bun and the same three commands: bun install, bun run dev, bun run build. Documentation sites (site, scavold) additionally serve their docs with bun run dev / bun run docs:dev; test-website and the site templates are ordinary VitePress sites.