Skip to content

[3b] Add apply with inverse capture and per-session undo #441

Closed

Description

@imnasnainaec

Part of Phase 3 (#426) of the data layer plan.

The repository's apply with inverse capture and a linear per-session undo stack; settles how a ChangeSet carries boundary changes.

Done when: conformance tests cover undo and redo, boundary changes included, and a payload linked from two books, edited through one, saves and reloads with the edit.

Depends on: 1c (#432), 3a (#440).

From the plan

API

Call Kind Request Response Replaces today Issue
undo / redo command session ChangeSet, plus touched pool buckets Whole-draft snapshots in undo-history.ts 3b

Risks

Risk Effect Mitigation
Whole-record inverses restore stale fields Undo restores an old tokenRef after a reanchor pass or a Scripture edit An undo or redo re-anchors what it restores to the current text, as on main; a whole-scope change clears the history

Decisions

Question Decision From
Undo Inverse ChangeSets captured in the repository's apply; one linear stack per session; undo and redo re-anchor what they restore to the current text, as on main; a whole-scope change (New, Open or opening a session, but not a wipe) clears the history D, A, J

Contract rules

  • A ChangeSet is a list of whole-record puts and deletes, keyed by record type and id. It and its inverse also carry boundary changes, so replies and undo cover boundary edits as they do today; 3b settles how.
  • The repository's apply is all-or-nothing, records each record's prior value for undo, and keeps the derived indexes current.
  • Derived indexes are rebuilt from the records on load and never trusted over them.
  • Every repository call is async, index lookups included, and lookups take keys in batches.
  • Index lookups are part of the repository interface, and the conformance suite tests them. The in-memory implementation backs them with maps; a later backend answers them its own way.
  • Index lookups promise no order. Where a command must pick one record, the domain layer picks by a rule (1a).
  • The domain layer enforces the approval rule before it calls apply.
  • A command reply is its ChangeSet plus, beside it, the pool buckets it touched; a wipe's reply also carries hasFreeTranslations.
  • No read returns a whole document.
  • The conformance suite defines the seam. Any later backend passes it to replace the in-memory implementation.

Activity

  1. myieye commented on Oct 16, 2026

    @myieye
    Contributor

    See the ChangeSet proposal on #431 (#431 (comment)): one typed list per record kind, and boundary changes carried as the scope record's segmentation.

  2. imnasnainaec commented on Oct 21, 2026

    @imnasnainaec
    ContributorAuthor

    From #432: the done-when's shared-payload case is in the suite via replaceAll; port it to apply. The unreadable-book and failed-cleanup cases port to apply as well. Undo and redo tests go in the suite too.

  3. imnasnainaec commented on Oct 21, 2026

    @imnasnainaec
    ContributorAuthor

    #475 settles the WebView path for a draft book whose stored analysis can't be read: a write leaves that book's analysis out and saves the rest (see its "Unreadable draft books" section). That's a partial write, so it can't carry over under apply's all-or-nothing rule. Decide whether an edit touching such a book is refused, and what the undo stack records for it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions