Version history

Per-page commit feed with schema-aware diff and three flavours of rollback.

Not verified yet

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).

On this page