Page editor

The schema-driven content editor — the field form, content blocks, rich text, validation, and the Save vs Publish split.

Not verified yet

The page editor is where authors actually edit content. It reads a file from your repo, renders every frontmatter field through a schema-driven form, and commits your changes back as a plain git commit. This page walks the editor top to bottom: opening a page, the field form, content blocks, rich text, validation, and how Save differs from Publish.

Opening a page

The Pages screen lists the Markdown files under the folder your site config points at (pagesDir). It renders as a grid of file and folder tiles: click a folder to drill in, click a file to open it in the editor. A search box filters across the whole site (the backend walks the local clone, so even deep trees return fast), and a breadcrumb tracks the folder you're browsing.

A small amber dot on a file tile means that file has an unpublished draft waiting — someone edited it in the editor but hasn't published the change to git yet. (See Save vs Publish for what a draft is.)

Creating a page

New page opens a dialog. You pick a collection (when the site has more than one), type a title, and get a URL-safe file slug derived from the title automatically. The new file lands in the folder you're browsing, with its frontmatter seeded from the chosen collection's schema defaults, and the editor opens on it straight away.

Which collection's schema drives an existing page is resolved by directory: the file's repo path is matched against each collection's path. A _collection key stamped into the frontmatter (the new-page dialog writes one) overrides that, and pages is the historical fallback. This is what lets Astro-style layouts (one subdirectory per collection, no _collection key) map to the right schema with no extra configuration.

The field form

The form is entirely schema-driven. Every field in the collection's schema renders as a labelled control, in declaration order, with the field's description shown underneath and a red asterisk on required fields. Each control matches the field's type:

Field typeControl in the editor
textSingle-line input, with a recommended-length hint when configured.
paragraphMulti-line textarea, with a recommended-length hint.
richtextTiptap rich-text editor (see Rich text).
numberNumeric input.
booleanA labelled checkbox showing Yes or No next to the box.
dateNative date picker.
colorNative colour swatch picker.
rangeSlider.
selectSearchable single-select dropdown.
multichoiceMulti-select control.
linkURL input.
image / fileMedia picker / uploader (see Media library).
groupA card you click into to edit the nested object's own fields.
arrayA reorderable list of items (see Content blocks).

The full catalogue, with every per-type knob, lives in Field types — this table is the tour, not the reference.

A couple of behaviours worth calling out:

  • Booleans read as a toggle, not raw true/false. The checkbox sits inline with the field label and the adjacent text flips between Yes and No as you toggle it, so the field reads in plain language rather than as a code literal.
  • Nested groups drill in. A group field shows a summary card (title, optional thumbnail and badges from the schema's preview); clicking it navigates one level deeper, with a breadcrumb back to the top. Nullable groups get a "Set value" affordance and a "Delete (set null)" action.
  • Suggested values. A field with a suggest-mode computed value shows a Suggested: … Use affordance beneath it — it offers the derived value without overwriting what you typed, so you apply it only when you want to. auto-mode computed fields fill themselves in silently as their sources change. See Computed fields.

Content blocks and arrays

An array field renders as a vertical list of items. For a blocks array (a page built from stacked sections) that list is your page layout.

  • Reorder by dragging. Grab the handle on the left of any row and drag; a violet insertion line shows where it'll land.
  • Per-item actions live in the kebab menu on each row: Copy, Duplicate, Paste above / Paste below, and Delete. Copy/paste works across items (and across arrays of the same kind) through a shared clipboard. Duplicating or pasting always mints a fresh item identity, so two rows never share one.
  • Adding items. Single-shape arrays add with one button. When the array allows several block shapes, a type picker lets you choose which block to insert, both from the "Add item" button and from the inline + affordances that appear between rows.
  • Item previews. Each row shows a title, optional thumbnail, and badges resolved from the schema's preview config, so a list of blocks reads as "Hero", "Media + text", "FAQ" rather than "Item 0, Item 1".
  • Limits. When the schema sets minItems / maxItems, the editor shows the count and disables the add affordances at the cap.

Multi-shape arrays carry a discriminator key on each item that selects the block shape. The array's discriminator defaults to _type (configurable via idKey). Note that _component is also treated as a reserved discriminator key in the editor — content authored with a _component: key (common in Astro / Hugo / Eleventy setups) round-trips untouched rather than surfacing as out-of-schema. See Field types.

Rich text

richtext fields (and the page body, when the collection has one) render in a Tiptap editor with a formatting toolbar. The body uses the full toolbar; individual richtext fields can narrow it.

The wire format decides how the content is stored: markdown (the default) round-trips through Markdown, and html stores HTML. That's set per field in the schema, not in the editor.

The toolbar is schema-gated. A richtext field's options decide which marks and nodes are available — bold, italic, strikethrough, inline code, links, headings (and which levels), bullet and numbered lists, blockquote, code block, horizontal rule. Anything not enabled is dropped from the toolbar and its keyboard shortcut becomes a no-op, so a field restricted to bold + italic can't grow a heading via ⌘-shortcut. A field that enables nothing shows no toolbar at all.

Out-of-schema content

If a page carries a frontmatter key the schema doesn't describe, it shows up in an Out-of-schema content panel at the bottom of the form (an amber, collapsible section listing each stray key, its value type, and an editor for simple values). You get two actions per key:

  • Add to schema infers a field type from the value and appends it to the schema file that owns this level, so the key becomes a first-class field on the next render.
  • Remove drops the key from the page.

Whether this panel appears at all is governed by the collection's extraKeysPolicy: show (the default) renders it, while hide and strip suppress it. With strip, out-of-schema top-level keys are also dropped from the frontmatter when you publish. Reserved metadata keys (_type, _collection, _id, _component) are never treated as out-of-schema.

The Errors panel

The Errors panel (in the right-hand rail) runs the same schema-aware validation a static build would care about: required fields, length and range bounds, item counts, and so on. Errors (build-blocking) and warnings (soft hints like recommended length) are listed separately, each with the field label, the rule that failed, a message, and the field's path.

Clicking an error focuses the offending field: the editor drills into the right level, flips back from Preview to Edit if you were previewing, scrolls the field into view, and ring-pulses it. The errors count stays visible whether you're in Edit or Preview mode, because validity is a property of the content, not the current view. See Validation and its required rule.

Save vs Publish

The editor draws a hard line between staging your work and committing it to git.

  • Save flushes a draft. Drafts autosave continuously as you type (roughly a 0.7-second debounce), and the Save button forces the pending write through immediately — handy before you step away. No git operation happens. A draft is shared per (site, file) and is last-write-wins: there's no per-author draft, and no merge. While a page has unsaved work the breadcrumb shows an "Unpublished changes" chip, and the file's tile on the Pages list grows its amber dot.
  • Publish commits the working copy to your branch with a subject like Norcube: edit <filename>. That's the only git write the edit flow performs. On a successful publish the draft is cleared.

At publish time the editor cleans the frontmatter at the YAML boundary: it honours whenHidden: 'drop' and the strip extraKeysPolicy, and it prunes contentless array items so a half-added block row can't ship and break the downstream build.

If the page still has build-blocking issues when you hit Publish, you get a "Publish with unresolved issues?" confirm dialog — the toolbar also shows an amber issue count next to the Publish button. Drafts and works-in-progress are legitimate, so this is a speed-bump you can pass with "Publish anyway", not a hard block.

The kebab menu holds the rest:

ActionWhat it does
RefreshRe-reads the file and config from the repo (bypassing the cached parse).
DiscardThrows away the draft and resets the working copy to the committed file.
DeleteCommits a removal of the file (recoverable from git history).

Publishing and deploys

Publishing is not a special Norcube deployment step. It's a normal git commit on your branch, so whatever CI or host you already use (Cloudflare Pages, Vercel, Netlify, your own pipeline) picks it up exactly like a commit pushed from a laptop. There's nothing Norcube needs to "deploy" — your build owns that.

The flow also works the other way. When a commit lands on the backing branch from outside Norcube, GitHub sends a (signature-verified) push webhook; Norcube invalidates its cached config for that repo and branch and refreshes its local clone, so the next time you open the editor you see the up-to-date content. You can also force this from the editor's Refresh action.

What's not there yet

  • No device / responsive preview toggles. There's a full-width Preview mode that swaps the editor for a live render of your own site, but no phone/tablet frame switcher. Live preview has its own page: Live preview.
  • No per-author drafts. The draft behind Save is shared per file and last-write-wins, so two people editing the same file at once can overwrite each other's in-progress work. Publish is the durable boundary.

Where to next

On this page