Skip to content

feat(audit): undo / redo / restore-to-point (v0.6)#146

Merged
bminier merged 5 commits into
devfrom
feat/audit-undo-redo-restore
May 22, 2026
Merged

feat(audit): undo / redo / restore-to-point (v0.6)#146
bminier merged 5 commits into
devfrom
feat/audit-undo-redo-restore

Conversation

@bminier

@bminier bminier commented May 22, 2026

Copy link
Copy Markdown
Owner

Implements the v0.6 — Audit log undo/redo/restore milestone (#19 Phases 3-5) on one branch. All three feature issues are done.

Closes #124. Closes #125. Closes #126.

Design note — snapshot-restore

#124 specified inverting an op into a reverse move/add/delete request and reusing apply_*. That isn't faithful when a move's destination already shared the rule (the reverse move would wrongly strip the destination's own copy). This branch uses snapshot-restore: a restore writes each affected file's top-level key back to a key_before snapshot the log already captured — faithful by construction, uniform across rules/lists/keys, and Phase 4 falls out as the same primitive over an aggregated delta. Full rationale in a comment on #124.

Phase 3 — undo / redo (#124)

  • Kind::Restore + RestoreMeta (target id, direction, per-file snapshots) on the audit record; undo_redo_state() replays the append-only log into undoable / redoable + sequence-break detection.
  • RestorePlan with atomic N-file write + full rollback; state-mismatch detection against the log's expected value.
  • Commands: audit_undo_status / audit_undo_preview / audit_redo_preview / audit_apply_undo / audit_apply_redo (apply verifies the target id so a concurrent write can't redirect a confirmed restore).
  • Undo / Redo topbar buttons + tooltips, Ctrl/Cmd+Z / Ctrl/Cmd+Shift+Z, restore-confirm modal with the external-edit warning band, History rendering for restore entries.

Phase 4 — restore-to-point (#125)

  • plan_restore_to() walks the log forward from the target to HEAD, aggregating a per-file delta; rejects an unknown target or a restore-entry target.
  • Commands audit_restore_to_point_preview / audit_apply_restore_to_point (apply verifies expected_ops_spanned).
  • A "Restore" button on each original-write History row; the History dialog closes itself (new openModal onReady hook) before the confirm modal opens.

Phase 5 — CLI (#126)

  • claude-scope-cli undo / redo / restore <id> wrapping the same helpers. --dry-run / --yes / --json; ULID-prefix resolution for restore; redo exits non-zero after a sequence break. CLI restores log actor: cli so a GUI session sees them.

Docs

README, mdbook (undo-redo / cli / audit-log), and SMOKE_TEST.md synced.

Tests

279 Rust tests (238 lib + 26 cli + 15 bin) + 120 frontend tests pass; clippy clean, tsc clean, biome clean. Verified end-to-end via the CLI against a crafted audit log (undo dry-run/apply, restore-entry logging). Undo/redo aren't exercisable in sandbox mode (the audit log is deliberately not written under --home), so a GUI run-through against a real ~/.claude is left for manual verification before merge — see the new non-sandbox section in SMOKE_TEST.md.

🤖 Generated with Claude Code

bminier and others added 4 commits May 21, 2026 21:34
Phase 3 of the audit-log feature (#19): step backward and forward
through logged writes from the GUI.

Schema (audit.rs):
- New `Kind::Restore` variant and a `restore` field on `Record` carrying
  `RestoreMeta` { target_id, direction, files } — the per-file
  before/after snapshots a later undo needs to invert the restore itself.
- Dropped the never-emitted `Actor::Restore`; `Kind::Restore` now carries
  that meaning, and `actor` stays orthogonal (gui/cli/skill).

Undo/redo state machine:
- `undo_redo_state()` replays the append-only log into the next undoable
  op, the next redoable op, and a sequence-break flag (a write after an
  undo strands the redo stack — redo is withheld, not discarded).

Snapshot-restore (commands.rs):
- Undo writes each affected file's top-level key back to the logged
  `key_before` snapshot rather than synthesizing a reverse move/add/delete
  request. This is faithful by construction and sidesteps a faithfulness
  bug in the invert-to-request approach #124 originally proposed (undoing
  a move into a scope that already shared the rule would wrongly drop that
  scope's copy). Deviation noted on the issue.
- `RestorePlan` / `build_restore_plan` / `apply_restore_plan` (atomic
  N-file write with full rollback) / `preview_restore_plan` with
  state-mismatch detection against the log's expected value.

Tauri commands: `audit_undo_status`, `audit_undo_preview`,
`audit_redo_preview`, `audit_apply_undo`, `audit_apply_redo`. Apply
verifies the target id still matches the log so a concurrent write can't
redirect a confirmed restore.

Frontend:
- Undo / Redo topbar buttons with descriptive tooltips; Ctrl/Cmd+Z and
  Ctrl/Cmd+Shift+Z, skipped in inputs and modals like the `/` shortcut.
- Restore-confirm modal reusing the shared modal shell, with a warning
  band when a file was hand-edited since the logged op.
- History view renders `restore` entries (verb, the entry acted on, and
  the affected scopes from `restore.files`).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 4 of the audit-log feature (#19): roll the affected files back to
their state before a chosen History entry, reverting that entry and
everything logged after it.

Backend (commands.rs):
- `plan_restore_to(records, target_id)` walks the log forward from the
  target to HEAD, aggregating a per-file delta — the first `key_before`
  seen for a file (its window-start state) is the restore value, the last
  `key_after` is the expected-current value for state-mismatch detection.
  `restore` entries inside the window are walked like any other write.
  Rejects an unknown target and a target that is itself a restore entry.
- Reuses the Phase 3 `RestorePlan` / `apply_restore_plan` (atomic N-file
  write with full rollback) and `preview_restore_plan`; `RestorePlan`
  gains `ops_spanned` so the modal can say "Restoring to N ops back".
- Commands `audit_restore_to_point_preview` and
  `audit_apply_restore_to_point`; apply verifies `expected_ops_spanned`
  so a concurrent write can't widen the reverted span.

Frontend:
- Each original-write History row gets a "Restore" button (rejected on
  `restore` rows, which the backend won't target anyway). The History
  dialog closes itself — via a new `openModal` `onReady` hook exposing
  `close` — before the restore-confirm modal opens.
- The shared restore-confirm modal already handles the `to_point`
  direction: "Restoring to N ops back" line, "Restore" confirm button.
- Undo / redo / restore-to-point now share one `runRestoreFlow` driver.

Tests: restore-to-point plan + apply end-to-end, last-entry case,
unknown/restore-entry rejection, restore-of-a-restore, and a Unix
mid-batch rollback test (locked directory).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 5 of the audit-log feature (#19): bring undo / redo /
restore-to-point to claude-scope-cli, wrapping the same audit helpers the
GUI commands use — no new domain logic.

- `claude-scope-cli undo` / `redo` — act on the most recent
  undoable / redoable entry from `audit::undo_redo_state`. `redo` exits
  non-zero after a sequence break, matching the GUI's greyed-out button.
- `claude-scope-cli restore <id>` — restore-to-point; `<id>` is a full
  ULID or any unique prefix (`resolve_entry_id` reports an ambiguous
  prefix with the candidate list and exits non-zero).
- Shared `--dry-run` (print the planned restore, write nothing), `--yes`
  (skip the `Apply? [y/N]` prompt), and `--json` flags. `--json` emits the
  `RestorePreview` for a dry run, otherwise the new `restore` entry as an
  `AuditRecordView` — the same wire shape `history --json` produces.
- CLI restores log a `restore` entry with `actor: cli`, so a GUI session
  sees CLI-driven undos in its History. Log-append failure is fail-open:
  a warning, not a command failure (the restore already hit disk).
- `history --kind restore` filtering: `AuditKindArg` gains `Restore`.

Tests: undo reverts a logged move and logs a cli-actor restore entry,
dry-run writes nothing, empty-log and sequence-break error paths, and
full-ULID / case-insensitive / ambiguous-prefix id resolution.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
v0.6 audit-log work shipped — sync the docs:

- README: a feature bullet for the audit log + undo/redo/restore, and
  the Ctrl/Cmd+Z / Ctrl/Cmd+Shift+Z shortcuts in the keyboard table.
- docs/user-guide/undo-redo.md: phase table marked shipped; new "Undo
  and redo" and "Restore to before an entry" sections (sequence break,
  external-edit warning); CLI section covers undo/redo/restore.
- docs/reference/cli.md: real `undo` / `redo` / `restore` subcommand
  docs replace the "Pending" stub; `history --kind restore`.
- docs/architecture/audit-log.md: record-shape comments corrected (no
  `restore` actor; `actor` is who-triggered), new "Restore entries"
  section covering RestoreMeta, the replay-derived cursor, and why
  snapshot-restore is used over invert-to-request.
- docs/SMOKE_TEST.md: a non-sandbox History / undo / redo / restore
  checklist (audit log isn't written under --home).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@bminier bminier marked this pull request as ready for review May 22, 2026 01:59
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@bminier bminier merged commit baf410a into dev May 22, 2026
14 checks passed
@bminier bminier deleted the feat/audit-undo-redo-restore branch May 22, 2026 14:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant