agntcms
Core / Concepts

Concepts

The nine concepts below were derived from real headless CMS projects shipped in production, then adapted so an agent is the natural actor on each. Together they are the rails — they keep the design system from drifting and your day-to-day editing on track.

Site#


agntcms isn't a CMS pointed at a Next.js app — it is the Next.js app. There is no separate dashboard talking to your codebase through an API. The framework runs inside your repo, edits files in your repo, and ships those files in your production build. That single decision is what lets the design system stay in bounds: every editable value flows through a typed schema defined in code, so the rules a designer wrote into the components are the same rules an editor — or an agent — operates within. A site builder for marketing sites with the discipline of a design system, not a freeform page editor.

Page#


The largest editable unit. A slug, SEO metadata, and an ordered array of sections. Lives at content/pages/<slug>.json. Composition happens at the page level and only at the page level — editors and agents rearrange sections, they don't introduce raw HTML between them.

Section#


A reusable, code-defined content type: Hero, FeatureGrid, TextBlock, Testimonials. Three files — a field schema (schema.ts), a React component (component.tsx), and a definition (index.ts) exporting a SectionDefinition. Optionally a preview.png for the admin.

Sections are the unit at which the design system is enforced. An editor cannot drop a div with custom styles into a page — they pick a registered section and fill its fields. Registration is explicit: add an import and one line to the sections array in agntcms/config.ts. Without that line the section is not visible in the admin. No folder scanning, no codegen — by design.

Sections can also be replaced in place — a swap from one section type to another that fills in defaults from the new schema. The framework's replaceSection endpoint owns the move so the page JSON stays valid throughout.

Field#


A typed property of a section: text, richText, image, video, button, link, reference, list, select, boolean, number. The set is built-in and not extensible in v0.5 — that constraint is the point. A richText field renders Markdown, not arbitrary HTML. An image field stores a content-addressed asset, not a URL the editor can override. A link field is a discriminated union over internal / external / email / phone with per-branch validation. Fields are declared in schema.ts; values live in content/pages/<slug>.json in exactly the shape the schema expects.

Slot#


A compile-time wrapper around a field value that makes it impossible to render an editable value without EditableText, EditableImage, or an equivalent. The component receives props.title not as a string but as EditableSlot<'text', string>. To render it, wrap in <EditableText slot={props.title} />.

This is what makes inline editing reliable: the preview UI always knows the coordinates of every editable field — page, section, field path. The slot is a guarantee at the type level that you remembered to pass those coordinates.

Draft / Published#


Drafts in content/drafts/<slug>.json, published pages in content/pages/<slug>.json. Publishing means moving the file from drafts/ to pages/ and writing a snapshot to content/history/<slug>/<timestamp>.json.

Drafts are also committed to git. Nothing in progress is lost when the editor switches machines or hands the project off. The split is what lets you accumulate many edits behind the live site, or publish after every small change — your rhythm, not the framework's.

Version history#


Every publish writes a full-page snapshot to content/history/<slug>/<timestamp>.json before the new content lands in content/pages/. The history therefore stores the version that was live up to (and not including) this publish — so rollback always returns the last known publicly-working state.

The directory is append-only — content/history/ is never deleted from. Globals get their own parallel store at content/history-globals/. Snapshots are committed to git like everything else; the version trail is part of your repo, not an opaque service.

Asset#


Images and files uploaded through the inline-edit UI or the admin land at public/assets/<sha256>.<ext>. The filename is the SHA-256 of the file contents — identical uploads deduplicate automatically, and cache invalidation comes for free (new content, new hash, new URL).

On a page, an asset is referenced by an ImageValue{ filename, alt } — stored inside section data. filename is the hashed asset; alt is the description collected in the image picker and required for screen readers and SEO. Never rename the files in public/assets/ — the JSON references the hash, and a rename breaks everything pointing at it.

Frozen zone#


Files owned by the framework that you do not edit by hand: app/api/agntcms/, app/[[...slug]]/, app/not-found.tsx, app/sitemap.ts, app/robots.ts, .claude/. Updates arrive through pnpm update @agntcms/next and pnpm update @agntcms/skills, not through merges.

If a frozen file has been accidentally changed, the agntcms-frozen-guard skill diffs against the canonical version and offers to restore it. Any feature that requires editing frozen files by hand is either a design mistake or a breaking change in the framework itself.