Version history
Per-page commit feed with schema-aware diff and three flavours of rollback.
Every page-editor session ships with a History tab in the right rail.
It shows every commit that ever touched the page file, expands each row
into a schema-aware diff, and lets the author roll back at three
granularities. Because the content is just git, the feed reflects
everything — CMS publishes, direct PRs, even an author's local
git push.
What the tab shows
The top level is a chronological commit list, newest first:
✦ Patrik Chalupa · 12 min ago [CURRENT]
Norcube: edit pripadove-studie.md [▾]
✦ Honza Svábík · 1 d ago
added 2 case-study blocks, reordered hero stats [▾]
✦ Norcube AI Assist (Patrik) · 3 d ago
SEO description rewritten [▾]Rows whose subject starts with Norcube: get a small sparkles icon — the
visual signal that the edit came through the CMS rather than a direct git
push.
Expanding a row shows the per-change breakdown:
✦ Patrik Chalupa · 12 min ago
Norcube: edit pripadove-studie.md
✎ Hero · Heading
"Reálné projekty" → "Real projects" [↺ undo]
⇆ Tags
+ travel, food − sports [↺ undo]
✎ Hero compact · Stats · Item 3 · Value
"14 dní" → "14 days" [↺ undo]Container breadcrumbs walk the full nested path so deep-inside-a-block
changes read clearly. The set-diff style (+ added, − removed) shows up
for multichoice fields automatically.
The three rollback flavours
All three only mutate the working copy — no git operation happens until
the author hits Publish. This keeps history clean (a rollback
becomes a new publish commit, not a git revert) and lets the author
undo a rollback by closing the editor without publishing.
Undo one change
The icon button on every diff row. Reverts that one field (or that one inserted / removed / reordered item) to its pre-commit value. Everything else in the working copy stays put.
For item-* changes the inverse is precise — item-added rolls back via
removeItem(parent, itemId), item-removed re-inserts at the original
position via insertItem(parent, value, beforeIndex), item-reordered
restores via reorderItems(parent, beforeIdOrder). The author never sees
a "this rollback might not be reversible" warning.
Restore this version
The Google Docs mental model: replace the whole working copy with the page snapshot at this commit. The button sits at the bottom of each expanded row (hidden on the latest commit — restoring to "now" is either a no-op or "discard unsaved edits", neither of which is what the label promises).
This is the right choice when you want to wholesale roll back to a particular version. Per-field undo would create franken-state where some fields rewind and intervening edits don't.
Roll back the whole commit (per-field batched)
Still available as a batched apply but no longer the primary action — the earlier "undo every change in this commit" semantics turned out to be a UX trap (same franken-state problem as "restore but in reverse" once later commits have touched the same fields). Use Restore this version for that intent.
Identity-aware diff
The diff walker matches array items by stable _id rather than
position. When you reorder a list of blocks and ALSO edit one of them,
the diff reads "reordered + edited block X" — not "five items removed
and five inserted in a different order."
The _ids themselves live in .norcube/cms/meta/<page>.yml (the
sidecar file), not your content YAML, so build pipelines never see
them. The CMS attaches them on read so the diff walker has stable
identity, and the page's own _id stays inline as the rename anchor.
Field-level history modal
The right-rail tab covers the whole page. For surgical "what did this ONE field look like across the last N commits" lookups, every field has a small clock icon → opens the field history modal. Same machinery underneath (the clone-backed walker), different scope. Pre-dates the History tab; both share the diff machinery.
What's not there yet
- Filter by author / date range / search. Today's UI is the unfiltered chronological feed. Filters are on the list until commit history per page gets long enough that scrolling is painful.
- Cross-page history view ("what changed across the site this week"). Probably a top-level dashboard widget rather than a right-rail tab.
How it feels fast
The tab reads from a local mirror of your repo rather than making
a fresh API round-trip for every commit — a page with hundreds
of commits opens in well under a second. Author names come from
the CMS's user directory when the commit came through the editor,
and fall back to git's own author info for commits pushed directly
(so PRs, git push from the terminal, and CI-generated commits
all still surface with the right name attached).