diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 87fecc4..e93c8bb 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -100,8 +100,8 @@ JSON as-is. ## Component tree App -├─ Header name, image, date steppers, session steppers, -│ Save (download)/Load (file import)/Clear (wipe) +│ Save (download)/Load (file import)/Clear (wipe), +│ ↩ Undo/redo (buttons + Ctrl+Z / Ctrl+Shift+Z / Ctrl+Y) ├─ Modal overlay dialog: confirm (action) or error (close) ├─ Sidebar │ ├─ Image portrait @@ -133,6 +133,13 @@ then a debounced (500 ms) effect writes on any store change via `Settings::VERSION` (the migration hook). `ModalState` holds title/message/ action as signals; the confirm button reads the action at click time. +Undo/redo lives in `state::undo`: the same store trigger feeds a +snapshot history (`HistoryHandle` in context). The first change of a +burst is remembered; when the burst goes quiet for 1 s, the pre-burst +state becomes an undo step. Undo/redo restore via `replace_state` — +the autosave effect persists the undone state too. Clear and import +push a snapshot first, so they are undoable themselves. + `Numput` is the shared numeric input: `+n`/`-n` adjust relative to the current value, a plain number sets absolutely, invalid input is reverted, and the value renders with thousands separators. `NumputChange` is the same @@ -157,8 +164,6 @@ currently mounted on any page. ## Current gaps - Quests, notes, contacts: models and mock data only -- No undo/redo (planned as Phase 1.5; snapshot-based, rides on the - persist machinery) - `Character` and `Wiki` pages render placeholders - Prepared spells list is empty; `Spell` is a name-only stub; attunements have no UI; `Settings` has only `version` diff --git a/README.md b/README.md index 1187f36..61d4936 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,8 @@ game. The UI is in Russian. - Campaign header: name, in-game date steppers, session counter, and Save / Load / Clear: download `dashboard.json`, import a file back, wipe to a blank campaign +- Undo/redo: header buttons and Ctrl+Z / Ctrl+Shift+Z / Ctrl+Y over + edit bursts; Clear and import are undoable too - Character sidebar: portrait, names, class, level, XP, hit points with temp HP bar, hit dice, death saves, spell slots - Inventories page (`/inv`): personal and party-common inventories, diff --git a/ROADMAP.md b/ROADMAP.md index a496b9c..e8b5396 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -105,21 +105,30 @@ Snapshot-based undo rides on the Phase 1 machinery: serialize → push → fires on any mutation, so no component changes are needed and every later phase's edits are undoable from day one. -- [ ] `state::undo.rs` (hydrate-gated): undo/redo stacks of serialized +- [x] `state::undo.rs` (hydrate-gated): undo/redo stacks of serialized `Dashboard`, capped (e.g. 100); push coalesced on the store trigger (one snapshot per ~1 s window → one undo step = one edit burst) -- [ ] a new edit after undo clears the redo stack; Clear and import push a +- [x] a new edit after undo clears the redo stack; Clear and import push a snapshot first, so they are undoable too -- [ ] `undo()` / `redo()` call `replace_state`; expose stack depths as +- [x] `undo()` / `redo()` call `replace_state`; expose stack depths as `RwSignal` for button disabled states -- [ ] header buttons: ↩ Отменить / Вернуть (disabled when empty) -- [ ] shortcuts: Ctrl+Z, Ctrl+Shift+Z (Ctrl+Y); ignore when focus is in an +- [x] header buttons: ↩ Отменить / Вернуть (disabled when empty) +- [x] shortcuts: Ctrl+Z, Ctrl+Shift+Z (Ctrl+Y); ignore when focus is in an input/textarea/select, so typing never triggers undo -- [ ] unit tests for stack semantics; e2e: edit → undo → redo; reload keeps +- [x] unit tests for stack semantics; e2e: edit → undo → redo; reload keeps the undone state (undo writes through autosave) - **Acceptance:** every edit since the last window is reversible with a keystroke; undo survives a reload. +**Done (2026-08-16):** implementation in `src/state/undo.rs`; e2e-verified +in chromium (buttons, shortcuts, reload, undoable Clear). Notes: +- Snapshots are `Dashboard` clones, not JSON: the dashboard is small. +- `HistoryHandle` is `Copy` (RwSignal fields) — context-friendly and + capture-safe under edition-2024 closure rules. +- No `std::time::Instant`: it panics on wasm32; the settle timer is the + only clock. The undo button enables with the pending burst, so a + fresh edit is undoable before the 1 s window settles. + ## Phase 2 — Character page (M, ~1-2 days) `/` currently renders the literal string "character".