ADR 0001 — Section-type schema: ownership, location, and delivery
- Status: Accepted
- Date: 2026-06-17
- Context repos: cratly.io site (this repo),
scavold,editor
Problem
The cratly editor needs to render the right controls (text inputs, checkboxes, media pickers, page pickers, selects) when an author inserts or edits a section in a page. To do that it must know, per section kind, which typed properties that kind accepts. cratly is meant to stay framework-agnostic — Scavold/VitePress is only the reference way to build a site — so the editor must not bake in framework-specific knowledge.
Decision
Split the information into two artifacts with different owners:
Grammar (meta-schema) — a JSON Schema describing the manifest shape and the property-type vocabulary. Owned by cratly, published at
https://cratly.io/schema/section-types/v0.json. Stable and versioned. Tools bundle the version(s) they support rather than fetching it at runtime.Manifest (
.cratly/sections.json) — the actual section kinds and typed props for one website. Produced by the adapter (Scavold is the reference adapter) and emitted into each website repo at build time. The editor reads it through the GitLab API — the same path it already uses for the file tree and Markdown.
The canonical grammar and the framework-agnostic spec live in this repo (the cratly.io site), not in scavold or editor, so adapter authors depend on a neutral, stable source rather than another tool's release cycle.
Why CORS is not a blocker
The editor and cratly.io are served from different hosts (the editor runs at the root of its own host — not under an /editor/ prefix — and the site will be cratly.io). A naive "editor fetches the schema from cratly.io at runtime" design would hit CORS. We avoid it by never making that runtime cross-origin fetch:
- The grammar is bundled into the editor (a JSON Schema
$idis an identifier, not a mandatory fetch target). The public copy on cratly.io is for humans, agents, and third-party tooling, and may additionally be served withAccess-Control-Allow-Origin: *. - The per-site manifest comes from the website repo via the GitLab API, not a fetch to cratly.io.
Delivery of the manifest
The manifest is a committed generated artifact, handled like a lockfile:
- The adapter build writes
.cratly/sections.json(skipping identical re-writes); the developer commits it when it changes. The editor always reads the committed file from the repo branch — no runtime generation, no CI write-back, no bot tokens. - CI verifies rather than writes: after building, CI fails if the working tree shows the manifest is out of date (
git status --porcelain .cratly/sections.json). This keeps the committed file honest without granting CI push access. Reference implementation: thebuildjob incratly/test-website's.gitlab-ci.yml.
Consequences
- Built-in section prop schemas are generated from Scavold components, never hand-copied into site config. A site's
.cratly.config.yamlonly declares custom sections and overrides labels/hints; the adapter merges built-ins ⊕ config ⊕ explicit overrides. - Follow-up work (tracked in the README): the
.cratly.config.yamlspec was moved out ofscavold/CRATLY-CONFIG.mdinto this repo with the typedprops:map added; the editor docs were moved into this VitePress site; Scavold gained manifest generation (lib/sectionManifest.js). Remaining: wire the GitLab project and deployment.