Skip to content

feat(app): add version history and restore for items - #166

Merged
aicayzer merged 5 commits into
mainfrom
feat/version-history
Oct 26, 2026
Merged

aicayzer merged 5 commits into
mainfrom
feat/version-history

Conversation

@aicayzer

Copy link
Copy Markdown
Contributor

Summary

A person can now open an earlier version of a note, read it beside the note as it is now, and restore it. Version History is a sheet from the File menu, an item's context menu, the More menu, the command palette and the page's context menu. Restoring is a new edit on the current version, so the text it replaces stays in the item's history and Undo puts it back.

Changes

  • Version History sheet (List of versions, two read-only text panes, the version's other fields, Restore), opened from NoteOrganizeActions (so the File menu, lists and the More menu), the board's menu, the palette and the page's context menu.
  • ItemHistorySource, a seam for GET /items/{id}/versions. MarfaServerVersions implements it over the package's MarfaAPI client, injected through MarfaHost into MarfaLibrary. The view model (ItemHistoryModel) and the sheet never see the generated client.
  • MarfaLibrary.restoreVersion restores the title and the text through the normal edit path: the text through saveText on the version the library holds, the title through editProperties, with an undo registered like Change Type's.
  • Docs: Model/AGENTS.md and Model/Marfa/AGENTS.md describe the feature, and the ATS line that said MAPP never uses URLSession is corrected. Strings are hand-appended to the String Catalog.

Testing

  • The Unit plan passes (run once after the last change, and the new suites again after rebasing onto main).
  • New tests (VersionHistoryTests.swift) use a fake ItemHistorySource and FakeItemSource: gaps and repeats in version numbers, an empty page that still has a cursor, a cursor that loops, paging one page at a time, offline and no-permission failures with Try Again, a failed Load More keeping the list, no history source, a cut-off long text, restore refused for an item in the bin, one deleted elsewhere, and a version of a kind MAPP does not edit, restoring a version already current (no write), the edit and the undo, and Undo refusing when the text was typed over since.
  • The tests were written with the code, not seen failing first.
  • Not tested: nothing was run against a server (the Live plan was not run), and no window was opened, so the sheet's layout and the menus are checked by reading the code only. The ATS behaviour for a plain-http address, and the page's refresh after a restore while its editor is open, were not tried.

Notes for reviewers

  • Order. The endpoint returns oldest first only, so the sheet lists oldest first and pages with Load More (100 at a time). A newest-first or since option on the endpoint would let the sheet open on the latest versions of a long history; worth an issue on marfa, not filed.
  • Scope of a restore. Title and text only. Other fields are shown to read and never written back, so a restore cannot undo another device's deliberate change to a field. A version of a type with no text or title the item keeps is shown but not restorable, and the reason is said.
  • Recently Deleted. The server answers 404 for an item in the bin, so the sheet says so and asks the person to recover it; Restore is refused.
  • Changed elsewhere. Whether a restore is possible is judged on the item as the library holds it, at the moment of the click, after the open pages hand over their last edit. Undo writes only while the item still holds what the restore wrote; otherwise it tells the person and writes nothing.
  • ATS. The history call goes through URLSession, unlike the working copy, so App Transport Security applies to a plain-http server address that ATS does not exempt. Documented in Model/Marfa/AGENTS.md.
  • Review depth: data-loss rules apply (restore writes over a person's text), so the restore and undo paths in MarfaLibrary+Writing.swift deserve the closest read.

Checklist

  • The title is a scoped Conventional Commit.
  • New behavior has a test that was seen failing first, or no test was needed and the reason is above.
  • Docs, comments and the contract still say what is true after this change.

@aicayzer
aicayzer marked this pull request as ready for review October 26, 2026 13:17
@aicayzer
aicayzer merged commit f36e6dd into main Oct 26, 2026
4 checks passed
@aicayzer
aicayzer deleted the feat/version-history branch October 26, 2026 13:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant