Goldie Blocks

Saved Versions Are Immutable; Restore Continues Linear History

Save Version records an immutable checkpoint; Restore Version appends the selected state to the same history and reopens the working draft.

Decision

Owner ruling, 2026-09-09 (GB-212): Discard Changes is a separate command after Save Version and before the history separator. It replaces the live draft's authored state and nested schedule with its previousVersionKey baseline, retaining live keys, version numbers and all saved history. It does not record restore provenance or seal a checkpoint. It is an immediate action with the same destructive red treatment as Delete, without a confirmation step. The Version flyout remains open so the restored baseline and the command becoming unavailable show the outcome. The selected parent’s snapshot governs the whole restored subtree, not each child’s latest independent save. Saving a parent saves changed or never-saved descendants and reuses unchanged saved descendants. No native browser confirmation is used. The command is disabled without a saved baseline or when the captured content and schedule already match it. Dirty state is derived by comparison, never stored as a second lifecycle. Names, ownership and export filenames remain outside the content rollback, as with historical Restore. Historical Restore continues to append a checkpoint as described below. Goldie auto-checkpoints remain future work. Saved details include the local save time to the minute, in the compact row described below.

Current (GB-212, 2026-09-09): the discard operation and menu reuse the live head, immutable baseline and saved-state copy. No schema migration or new version lifecycle is required. Fresh-context settlement review is required for this destructive workflow; review found no blocking source defect.

GB-217 settlement (2026-09-09): required fresh-context review accepted retained Restore/Discard menus and secondary readout styling. An unsupported story matcher was corrected before verification; no blocking findings remained. Regression failures were observed and the final affected browser checks passed.

Owner ruling, 2026-09-08 (GB-204), refining GB-196/198's menu presentation:

  • One Version flyout shows Save Version, a separator, the live Editing entry, and every saved version as a Restore action, newest first. The live head supplies N. Saving seals that head and opens the next numbered editable state. The Editing row is informational. Before any checkpoint exists there is no restore row.
  • Restore targets are actions, not canvas navigation. The latest saved checkpoint and older checkpoints are restore targets in the same flyout. The normal menu does not expose Working draft or open a persistent historical read-only canvas. Internal historical reads and their read-only protections remain available.
  • History is linear and immutable. Explicit Save Version always adds a checkpoint. Amended by owner ruling 2026-09-15 — a save needs something to save: Save Version is disabled, and the executor files nothing, when the draft says nothing its last checkpoint does not already say. See A save needs something to save below. Unchanged descendants may reuse their sealed editions. Restore copies the selected checkpoint into the live head, seals it as the next checkpoint and opens the next editable head. All existing saved history stays intact; numbers never go backwards. For example, with v3 live and v2 saved, restoring v1 creates saved v3 from v1 and live v4.

A sealed edition records its filed name

Owner ruling, 2026-09-22, overturning the half of the 2026-09-09 ruling above that kept names outside the version workflow altogether ("Names, ownership and export filenames remain outside the content rollback"), and the 2026-09-09 export-filename ruling's corollary that "renaming the folio renames every edition's file" (Folios Schema).

A sealed edition records the resolved name it was filed under, and a rename is unsaved work. Three parts, and the third is what the ruling deliberately did not change:

  1. Sealing stamps the name. A row sealed from a head records sealedName — the resolved name (label ?? humanize(handle)) as at the moment of sealing. Its export filename reads that record, so an edition keeps the name it was filed under and a later rename no longer re-titles it.
  2. A rename is something to save. Save Version is offered when the draft's content or its name differs from the last sealed edition, at the renamed card and at every container above it, and the executor files an edition rather than reporting minted: false.
  3. A rename is not something to discard. Discard Changes still replaces content and schedule only, exactly as the 2026-09-09 ruling set it. It stays disabled after a rename alone and the executor refuses one.

In plain terms: the name a filed edition was filed under is now part of what was filed, the way the date is. Rename a card and the menu offers to file the change, because you have changed something. Discard Changes still only throws away edits to the contents — it will never quietly rename your cards back, so one destructive command cannot undo two unrelated kinds of change at once.

Why the two commands now read two facts. The 2026-09-15 ruling tied Save and Discard to one comparison so they could not contradict each other. Part 3 breaks the tie deliberately: a rename is savable and not discardable, so after a rename alone Save Version is live beside a greyed Discard Changes. That is not the old contradiction — it says there is something to file, and nothing to throw away, which is the truth about that draft. hasDiscardableChanges (folio/baselineReads.ts) keeps its content-only meaning and guards discard; hasUnsavedWork adds the filed-name comparison and gates Save. The save-time dedup reads the same name comparison as the menu, so an offered save always files.

A live head never stores its name. Only a sealed row carries sealedName. The live name stays the projection Names Are Projections requires — label ?? humanize(handle), computed where it renders, with folios.label the single store. What is stored here is not a derivation kept alongside a live fact but a record of a past filing, which is what a sealed row is for; there is no second copy of the current name to go stale.

An edition sealed before the ruling is judged on content alone. sealedName is absent on those rows, and absence means this edition recorded no name, not this edition was unnamed. The name comparison is skipped for them, so no pre-existing draft reads as renamed the moment the ruling lands.

And that is a real gap, not just a technicality. A card whose last edition predates the ruling needs one save carrying a content change before renames start registering on it: until then Save Version stays greyed after a rename alone, exactly as it did before, and the first content-bearing save stamps the name and fixes the card permanently. Verified against the development deployment on 2026-09-22: all 176 sealed editions there recorded no name, so every card then in the database sat in that state.

Three ways out were considered and refused, and the reason is the same each time — the information does not exist. Nothing on a row sealed before the ruling says what it was filed as:

  • Backfilling every sealed row from the folio's current name writes a false record into precisely the cards this ruling is about. A card renamed since its last save would have the new name stamped onto the old edition, which both loses the pending rename and mislabels the filed edition.
  • Treating an unrecorded name as "unknown, so offer the save" opens Save Version on every existing draft at once and files checkpoints that say nothing new — the duplicate-history fault the 2026-09-15 ruling closed.
  • Stamping the outgoing name onto the baseline as a rename happens would be truthful for a first rename, a guess for a second — and it writes to a sealed row, which this record forbids outright.

Doing nothing is therefore the honest option: the gap closes itself card by card, and it never affects a card or document created after the ruling, whose first edition is stamped at birth.

This is a widen with no narrowing, so it carries no migration (widen, migrate, narrow).

A save needs something to save

Owner ruling, 2026-09-15, superseding the always-checkpoint half of the 2026-09-08 ruling above. Save Version is offered only when the working draft has unsaved work. With the draft clean the row is disabled, and the executor files nothing and reports minted: false.

In plain terms: with your work already filed, Save Version is greyed out. The menu tells you where you stand before you press anything, and you never file a second dated copy of a document you have not touched.

Why it changed. Save and Discard sat side by side giving contradictory answers about one draft: Discard greyed out saying "nothing to throw away" while Save stayed live. A control that is equally available whether or not you have changed anything teaches the reader nothing, and the duplicate rows it files make the history harder to read — an identical checkpoint carries no note saying reviewed, unchanged, so it reads as a change that is not one.

One fact, both commands. Both rows read hasDiscardableChanges (folio/baselineReads.ts), so they can never disagree again. Retired by owner ruling 2026-09-22 — a rename is unsaved work but is not discardable, so the two commands read two facts. See A sealed edition records its filed name below. The menu is still the reader's gate, and saveFolioVersion dropping its force flag is still the backstop, so an agent or a second tab reaching the executor gets the same answer.

Restore still forces its seal. Reinstating v1 onto a v3 draft leaves a head whose content matches v1 exactly, so a content comparison would decline to seal it and the restore would vanish. The act recorded there is this state was reinstated, which no comparison can see. restoreVersionWrites.ts is now the only caller passing force.

If review-signatures are wanted later — a checkpoint meaning I read this and changed nothing — that is a different feature: the row would have to say so, rather than being an identical copy with a later timestamp.

  • Restore includes removed and deleted descendants. The saved schedule supplies content and order. Restore reinstates it and clears deletedAt for descendants without deleting other identities. Names, ownership and handles remain folio-level facts. Existing binding, structural authority, containment checks and atomic transaction guarantees remain unchanged. Unsaved live edits are replaced by the explicit restore action.
  • Provenance stays light. The saved checkpoint retains restoredFromVersionKey; previousVersionKey remains chronological. The menu omits status text and checkpoint provenance. Its date is the head's opening time; a saved checkpoint's date is its successor's opening time, from the same save transaction. File naming and download belong in EXPORT.
  • The header suffix is presentation only. It remains absent through the first saved checkpoint. Once two saved checkpoints exist, the normal header shows the live number, matching the Editing entry’s vN. Internal historical views may retain their historical suffix. The persisted document name never contains the suffix.
  • AI checkpoints remain future work. This refinement adds no automatic saving, acceptance, forks, branches or schema changes.

Action completion feedback

Current (GB-216, 2026-09-09): menu-local actions report at their point of initiation. Save Version keeps the Version flyout open, shows “Saving…” while pending and disables repeat activation. Successful completion restores “Save Version”; the reactive history supplies the new Editing head above the previous head, now a Restore action. No optimistic history or success toast is added. Failure keeps the menu actionable with an inline error and retry guidance.

Restore also keeps the Version flyout open. Its existing operation updates the ledger; the new Editing head and restored checkpoint provide confirmation with the new version numbers. Existing restore error feedback remains unchanged.

Every version entry occupies one compact row (owner, 2026-09-11): restore icon, 14px vN, a middle dot, then local date and 12-hour time including minutes in one smaller 12px size. Month names are exactly three letters (including Sep), and years use two digits (26). The live head has no status suffix; a tick at the far right identifies it. Its restore-icon slot stays empty and its row is disabled. Historical rows use the right-pointing Redo2 icon for direct restore, opposite to the red left-pointing Undo2 icon on Discard Changes. The restore icon sits at the left like other menu actions. Neither Saved/Restored nor restore-source numbers appear. The flyout uses the standard menu width. Missing dates remain “Date unavailable”.

Download uses the existing JSON exporter. EXPORT contains the current filename, its existing editing action, the JSON format choice and Download. Download stays in place and keeps the menu open: Download → Downloading… → Downloaded ✓ → Download after 1.5 seconds. Pending activation is disabled. Failure exposes an inline error and permits retry. Filename and format are retained. “Downloaded” means the file was handed to the browser; browser disk completion is not observed. No export history or new generation architecture is introduced.

Save Version and Download do not display duplicate glyph progress or success. Generic ActionStatus remains available for work that needs persistent feedback outside its initiating menu. Pending rows expose busy/disabled semantics and text feedback is announced without relying on colour. Selected-value readouts (View and Format) use 10px regular muted foreground text, retaining that secondary treatment on focus.

Earlier ruling and retained duplication support

Owner ruling, 2026-09-04 (decision-11):

  1. Older saved Folio versions are sealed. A sealed version is never mutated in place.
  2. The current live head is the only version that may be edited in place.
  3. A card displaying a sealed version offers no mutating operation. Every mutating operation is unavailable there, and a write that would land on a sealed edition is refused.
  4. This decision does not decide whether a sealed version may later be the source of a new editable lineage (a fork). Shallow versus deep fork, copy-on-write, descendant cloning, fork UI and branch presentation are all undecided and not to be designed or built now.
  5. Every lineage, version and pin fact needed to keep a future fork possible is preserved. derived_from and the other lineage support are not removed or narrowed merely because forking is deferred.

In plain terms: a signed and filed version of a deed is never altered on the page. Only the working copy on the desk is edited. If you are looking at a filed version, the pen is withheld. Whether you may one day open a fresh working copy that starts from an old filed version is a separate question, left open on purpose, and the file keeps every cross-reference that question would need.

The distinction to carry everywhere: sealed versions cannot be mutated in place; future derivation from a sealed version remains an open possibility.

What is preserved, verified against the tree 2026-09-04

  • folios.derivedFromFolioKey, folios.derivedFromVersionKey and the by_derived_from_folio_key index (convex/schema.ts).
  • folioVersions.previousVersionKey and the branched member of creationMethod.
  • requireDerivationLegal (convex/folio/writeGuards.ts) checks that a source edition belongs to the source folio and that lineage runs within one kind; it says nothing about the source edition's status, so a sealed source is already representable. That stays as it is.
  • The About card's identity.derived_from projection (convex/folio/aboutCodec.ts).

No code or acceptance criterion in the tree assumes a sealed version can never become a derivation source.

Consequences

  • Stage D of the folio unification (settled constraint 22 of that plan): every content, field, composition, About, duplicate and numbering executor refuses a write that would land on a sealed edition, and the canvas withholds those operations while a card shows a sealed edition. The executor prologue already refuses a sealed head; the shown-edition rule is the part Stage D adds.
  • Duplicate / derive (Stage D row 5) keeps recording derivedFromFolioKey and derivedFromVersionKey, and may name a sealed edition as its source, because the guard already allows it. That is a copy with lineage, not a fork of the version's editing history; fork semantics remain undecided.
  • Not a licence to narrow. A clean-up that would drop a lineage column, the branched creation method or the derivation guard because "nothing forks yet" is refused by this record.

Implementation status

Tree audit, 2026-09-06: immutable editions, exact historical reads and a live draft exist. GB-196 implements the revised save behaviour, restore, provenance, inherited historical read-only state and header presentation above.

On this page