Cratly
Zu Deutsch wechselnDENAV.openEditor

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:

  1. 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.

  2. 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 $id is 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 with Access-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: the build job in cratly/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.yaml only 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.yaml spec was moved out of scavold/CRATLY-CONFIG.md into this repo with the typed props: 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.

See also