Page editor
The schema-driven content editor — the field form, content blocks, rich text, validation, and the Save vs Publish split.
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 type | Control in the editor |
|---|---|
text | Single-line input, with a recommended-length hint when configured. |
paragraph | Multi-line textarea, with a recommended-length hint. |
richtext | Tiptap rich-text editor (see Rich text). |
number | Numeric input. |
boolean | A labelled checkbox showing Yes or No next to the box. |
date | Native date picker. |
color | Native colour swatch picker. |
range | Slider. |
select | Searchable single-select dropdown. |
multichoice | Multi-select control. |
link | URL input. |
image / file | Media picker / uploader (see Media library). |
group | A card you click into to edit the nested object's own fields. |
array | A 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 betweenYesandNoas you toggle it, so the field reads in plain language rather than as a code literal. - Nested groups drill in. A
groupfield shows a summary card (title, optional thumbnail and badges from the schema'spreview); 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 aSuggested: … Useaffordance 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
previewconfig, 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:
| Action | What it does |
|---|---|
| Refresh | Re-reads the file and config from the repo (bypassing the cached parse). |
| Discard | Throws away the draft and resets the working copy to the committed file. |
| Delete | Commits 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
Schema reference
Every field type and config knob that drives the form.
Live preview
See your own site render as you type, before you commit.
Version history
Per-page commit feed, schema-aware diff, and rollback.
Media library
Named storages, the picker, the publicUrl contract.
AI Assistant
A chat sidebar that proposes edits as reviewable ops.