The editor
The editor is a separate mode, not a separate site. Your client edits the real page, with the real styles, at the real URL.
How it loads
- A visitor loads a page → server-rendered plain text, zero editor JS.
- The client visits
/adminand signs in. - With a session present, the editor bundle lazy-loads on subsequent page loads.
Server Components render editing anchors that cost a visitor nothing, because they only exist in editor mode:
<span data-copy-field="heroTitle" data-copy-scope="about">Hello!</span>
Editing a field
Editing happens through a click-to-open popover, not contenteditable —
which fights paste-with-styles, IME input, mobile keyboards and React's
controlled inputs. The popover gives most of the perceived magic for a fraction
of the bugs.
- Text — a single-line or multiline input, respecting
maxLength. - Rich text — a Markdown editor with the allowed formatting only.
- Image — upload, or paste a URL from an allowed host.
A field referenced in JSX but absent from the YAML renders as nothing in production and shows a labelled placeholder in editor mode, so the client has something to click. A locked field is visible but not editable.
True in-place editing is a v2 upgrade, not a v1 omission.
Edit mode and view mode
Editing intercepts clicks. That is the point — but it also means a link whose
own text is a <Copy> field cannot be followed, which is most site navigation:
<Link href="/about"><Copy field="nav.about" scope="_global" /></Link>
So the toolbar carries a mode switch.
| Edit | Clicking text opens its popover. The default. |
| View | The page is handed back to the site: links, menus, dropdowns and forms all behave as a visitor's. |
View mode is not a sign-out. The toolbar stays, the changeset survives, and pending edits are still applied to the page — so it doubles as a preview of the site as it will read once published, without the outlines.
The mode is remembered for the browser session, so switching to View and clicking through three pages does not drop you back into Edit at each one.
Following one link without switching
Hold ⌘ (Ctrl on Windows and Linux) and click. The click goes straight to the page, so the browser opens the link in a new tab — which is the safer default here, since the tab you are editing in keeps its pending changes.
While the modifier is held, the editing outlines drop away, so it is visible before the click lands that the page will get it.
Any click the browser already treats specially — Shift, Alt, middle-click — passes through the same way.
The floating toolbar
| Edit / View | Whether clicking a field edits it or uses the site |
| Current page | Opens the Content panel — where you are, and the way elsewhere |
| Locale switcher | Edit another locale of the same page |
| Changeset badge | How many changes are pending |
| Publish | Review and commit |
The Content panel
Pages and collections in one panel, because finding something and going to it should be one gesture rather than two panels and a guess.
- Left — every page with content, as a tree, with the one you are on marked. Clicking navigates. Below it, the site's collections.
- Right — the selected collection's items: Open, hide/publish via
_draft, reorder, delete, and New item. - Top — a filter across both. Searching flattens the page tree, since a result list is not a hierarchy.
Open is what makes an item reachable. It needs to know the route that
renders one, which copy-ink types derives from your JSX — see Opening an
item. Until types has run there is no
Open button, and the panel says why.
Item creation is driven entirely by the collection schema: slugs are generated
from slugFrom, checked for uniqueness, and editable before the first save.
Editing an item still happens on its page, by clicking the text — the panel
gets you there and manages which items exist.
Publishing
Edits accumulate client-side for the whole session and leave as one commit. There is a before/after diff preview first.
Editing a value back to its original drops the change entirely, so the "3 changes" badge never overstates.
After publishing, the UI says:
Published — live in about 2 minutes.
Commit → rebuild → deploy takes a while, and unexplained that reads as broken. Do not remove that messaging.
Concurrency
The changeset carries the base commit SHA. If the branch has moved, the write is rejected:
Someone else changed this page — reload.
There is no merging in v1. For a single-client site this effectively never fires; for a site where two people edit at once, it fires and it is the honest outcome.
Losing work
Optimistic state survives navigation within a session. A full reload loses it, and the editor warns before one.
What a client can and cannot do
| Can | Cannot |
|---|---|
| Edit any unlocked field on any page | Create pages |
| Use the site normally in view mode | Sign in without being on the allowlist |
| Create, reorder, hide and delete collection items | Change a slug after the first save |
| Upload images and paste allowed remote URLs | Add an image host |
| Switch locale and edit translations | Edit locked fields |
| Publish, once, per session's worth of changes | Merge concurrent edits |
The line is: the client edits on the real page, and never creates pages.
Briefing your client
Three things are worth saying out loud when you hand a site over:
- Changes go live a couple of minutes after Publish. Not instantly.
- Nothing is saved until Publish. Reloading loses pending edits.
- If it says someone else changed this page, reload and redo. It is not lost data; it is a refusal to overwrite.