feat: ADR-0006 §8 steps 4-5 — undo/redo commands + confirm-modal flow

Commands & grammar (step 4):
- AppCommand::Undo/Redo, grammar nodes + REGISTRY entries, catalog
  help/usage + keys; parse tests
- replay skips undo/redo (is_app_lifecycle_entry_word) + completion
  entry-keyword lockstep; replay-skip test extended

Wiring (step 5):
- Action::{PrepareUndo,PrepareRedo,Undo,Redo} + AppEvent::{UndoPrepared,
  UndoUnavailable,UndoSucceeded,UndoFailed}
- App: undo_enabled flag, Modal::UndoConfirm, dispatch + event handling
  + confirm-key handler (Y confirms / N/Esc cancels); "turned off" when
  --no-undo; "nothing to undo/redo" when empty
- ui::render_undo_confirm names the command + snapshot time
- runtime: opens with undo enabled (!--no-undo), threads it through the
  project-switch path, spawn_prepare_undo/spawn_undo (peek->modal,
  restore->refresh tables + schema cache)
- 9 Tier-1 app tests + 3 parse tests

1692 passed / 0 failed / 1 ignored; clippy clean.
This commit is contained in:
claude@clouddev1
2026-05-24 20:48:30 +00:00
parent a97069c02e
commit 25800e3eb5
14 changed files with 541 additions and 9 deletions
+6 -3
View File
@@ -151,7 +151,8 @@ fn replay_of_actual_history_log_runs_ok_commands_and_skips_err() {
#[test]
fn replay_skips_app_lifecycle_commands_silently() {
// ADR-0034: a real `history.log` contains app-lifecycle commands
// (`save as` / `load` / `new` / `export` / `mode` / `rebuild` …).
// (`save as` / `load` / `new` / `export` / `mode` / `rebuild` /
// `undo` / `redo` …).
// Replay skips them — they are session navigation, not schema/data
// reconstruction, and the worker dispatch cannot run them (it would
// panic on a parsed app command, or abort on the modal forms that
@@ -177,8 +178,10 @@ fn replay_skips_app_lifecycle_commands_silently() {
2026-05-24T10:00:08Z|ok|rebuild\n\
2026-05-24T10:00:09Z|ok|help\n\
2026-05-24T10:00:10Z|ok|quit\n\
2026-05-24T10:00:11Z|ok|add column T: v (text)\n\
2026-05-24T10:00:12Z|ok|insert into T (id, v) values (1, 'alpha')\n",
2026-05-24T10:00:11Z|ok|undo\n\
2026-05-24T10:00:12Z|ok|redo\n\
2026-05-24T10:00:13Z|ok|add column T: v (text)\n\
2026-05-24T10:00:14Z|ok|insert into T (id, v) values (1, 'alpha')\n",
);
let events =
rt().block_on(async { run_replay(&db, project.path(), "history.log").await });
+2
View File
@@ -250,6 +250,8 @@ fn command_kind_label(cmd: &rdbms_playground::dsl::Command) -> String {
AppCommand::Import { .. } => "App(Import)".into(),
AppCommand::Mode { .. } => "App(Mode)".into(),
AppCommand::Messages { .. } => "App(Messages)".into(),
AppCommand::Undo => "App(Undo)".into(),
AppCommand::Redo => "App(Redo)".into(),
},
}
}