ADR 0004 — Site templates: one repository each, one curated catalogue
- Status: Accepted
- Date: 2026-08-12
- Context repos: cratly.io site (this repo),
templates/*,editor
Problem
Cratly ships one site template. Two things push against treating that as the permanent shape:
- A single starting point cannot serve a monolingual brochure site and a multilingual one with a different design. More templates are coming, and the shape decided now is the one they will be created in.
- Nothing on this site explains how a website repository comes into existence at all. The guide begins at "a repository containing Markdown files", the developer map mentions copying a template in one table cell, and the actual instructions live in a README nobody is pointed to.
The eventual goal is stronger than documentation: the editor should be able to offer a choice of templates while setting up an empty repository. Whether the browser can do that at all had to be answered before choosing a layout, because a distribution format that the editor cannot read would have to be rebuilt later.
What was measured
Not assumed — checked against gitlab.com and against the deployed configuration:
- The editor's CSP allows
connect-src 'self' https:. Open on purpose: the GitLab instance is the user's choice, so an allowlist is impossible. https://gitlab.com/api/v4/…answers withaccess-control-allow-origin: *and a preflight permittingAuthorization. Cross-origin reads of a public project work from a browser, including across instances — a template on gitlab.com while the target repository is self-hosted.- The static host serving cratly.io emits
Access-Control-Allow-Originper rule, opt-in, and handles noOPTIONSpreflight. Good enough for a public JSON catalogue; not for an archive download requiring headers. - The editor already writes multi-file commits (
buildBootstrapActions), and the GitLab commit API acceptsbase64content for binaries.
So the transport is not the obstacle. What remains are two obstacles of a different kind: a template carries a .gitlab-ci.yml, and the editor must not learn what a theme framework is.
Decision
One repository per template, listed in a hand-maintained catalogue at https://cratly.io/templates.json.
- Sibling repositories, not folders in one. A monorepo of templates would be cheaper to maintain today — one pipeline, one lockfile — but it destroys GitLab's custom project templates path, which requires each template to be a project in the configured group. A project created from such a repository would contain every template. Sibling repositories keep both distribution paths alive and let each template carry its own framework version, which they will need as soon as they age apart.
- The adapter is part of the name:
<adapter>-<variant>. Templates sit flat in one group, because the template picker offers the projects of the configured group and "projects in nested subgroups are not included in the template list" — a subgroup per adapter would hide them all. So the hierarchy cannot carry that information and the name has to. It is also the only place it can appear: the picker shows project names, not the catalogue'sadapterfield, which is where a person actually chooses. Agnosticism is visible rather than assumed, and a second adapter's "plain" does not collide. - The catalogue lives in this repository and is published as a static file with
Access-Control-Allow-Origin: *. It is the specification side of the product, which is where a cross-adapter list belongs; publishing it here also avoids inventing a second host and a second CORS surface. - Curated, never free-text. Copying a template means committing a foreign pipeline into a customer repository. Whatever offers that choice offers entries from this catalogue, and shows the file list before committing — the bootstrap already previews its commit that way.
- Templates are data to the editor. A name, a description, a repository coordinate, a list of files. Everything framework-specific stays inside the template repository. This is the same boundary ADR 0003 protects structurally; here it has to be protected by what the catalogue format does not contain.
The templates reference holds the format and the obligations of a template repository; Starting a new website is the route in for people.
Consequences
- Adding a template is: create a repository satisfying the reference, add one catalogue entry. Registering it as a GitLab project template needs nothing further if the group is configured.
- The catalogue is not generated, so it can drift from the repositories it names. With a handful of entries this is cheaper than a generator; if it stops being true, the fix is a CI check in this repository that resolves every entry, not a build step.
- The editor's template picker reads this catalogue and copies the chosen repository in one commit. It sends no token to the instance a catalogue entry names: templates are public, and the user's token belongs to their own instance.