4aeea55984
Record the submission mode per history entry so advanced commands are reusable in simple mode, and fix the bug where a ':'-one-shot command lost its ':' across sessions (ADR-0052, closing #30). Format: the history.log status token gains an optional ':adv' suffix (ok / ok:adv / err / err:adv); 'source' stays last and canonical, so replay is unaffected. The in-memory ring (still Vec<String>) stores advanced entries ': '-prefixed; recall strips the ':' in advanced mode and keeps it in simple; hydration reconstructs the prefix from the tag. Journaling moved from the worker to the dispatch layer (spawn_dsl_- dispatch / run_replay / app-command sites), where the mode is in scope with no worker plumbing; finalize_persistence writes only yaml/csv (commit-db-last still atomic for state). The journal write is now best-effort (command already committed), consistent with the failure path. App commands journal simple, so they recall bare. Journaling is now uniform (every successful command, per ADR-0034) — closing a gap where show tables/relationships/explain didn't journal. Amends ADR-0034 (status tag + journaling location), ADR-0015 §6 (history.log out of the worker tx), ADR-0040 (journal-write best-effort). 15 worker-level journaling tests retired, re-covered at the new layer (history.rs format, app.rs recall matrix, iteration6 cross-session regression, replay). 2471 pass / 0 fail / 0 skip, clippy clean.
151 lines
6.6 KiB
Rust
151 lines
6.6 KiB
Rust
//! Actions returned by the application's update function.
|
|
//!
|
|
//! `update` is pure with respect to the runtime: it mutates
|
|
//! state in place and returns a list of `Action`s for the
|
|
//! runtime to enact (quit, dispatch a DSL command to the
|
|
//! database, etc.). Side effects belong here, not in update
|
|
//! itself, which keeps update directly testable without a Tokio
|
|
//! runtime, a real terminal, or a database.
|
|
|
|
use crate::app::EffectiveMode;
|
|
use crate::dsl::Command;
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub enum Action {
|
|
/// Stop the event loop and exit cleanly.
|
|
Quit,
|
|
/// Hand a parsed DSL command to the database worker.
|
|
///
|
|
/// `command` is the parsed AST that the worker executes;
|
|
/// `source` is the original user-typed text, retained for
|
|
/// `history.log` per ADR-0015 §5. The runtime feeds the
|
|
/// result back as `AppEvent::DslSucceeded` /
|
|
/// `AppEvent::DslFailed`.
|
|
ExecuteDsl {
|
|
command: Command,
|
|
source: String,
|
|
/// The effective mode the line was submitted under (ADR-0037):
|
|
/// `Simple` / `AdvancedPersistent` / `AdvancedOneShot`. Output-only
|
|
/// — execution semantics do not depend on it; the runtime uses it
|
|
/// to gate the DSL → SQL teaching echo (ADR-0038), which fires for
|
|
/// DSL-form commands submitted in an advanced effective mode.
|
|
submission_mode: EffectiveMode,
|
|
},
|
|
/// Record a *failed* submission to `history.log` as an `err`
|
|
/// record (ADR-0034 §1/§2). Emitted by the pure-sync `App`
|
|
/// for both failure kinds — a line that failed to parse (at
|
|
/// submit) and a command the worker rejected (on
|
|
/// `AppEvent::DslFailed`) — because the App does no I/O. The
|
|
/// runtime appends best-effort: a failure to record a failure
|
|
/// must never escalate a user error into a fatal (ADR-0034
|
|
/// §4). `source` is the original user-typed text.
|
|
JournalFailure {
|
|
source: String,
|
|
/// Whether the failed submission was advanced (ADR-0052): tags the
|
|
/// `err` record `err:adv` so a failed advanced command hydrates in
|
|
/// its `:`-prefixed form, recallable in simple mode. App commands
|
|
/// (mode-agnostic) are `false`.
|
|
advanced: bool,
|
|
},
|
|
/// User issued the `rebuild` app-level command (ADR-0015
|
|
/// §7, §11). Runtime computes a summary from
|
|
/// `project.yaml` + `data/` and posts back as
|
|
/// `AppEvent::RebuildPrepared`, which opens the
|
|
/// confirmation modal.
|
|
PrepareRebuild,
|
|
/// User confirmed the rebuild from inside the modal.
|
|
/// Runtime wipes the current schema/data and reconstructs
|
|
/// from text sources.
|
|
Rebuild {
|
|
source: String,
|
|
},
|
|
/// Open the load-picker modal. Runtime lists projects in
|
|
/// the active data root and posts back as
|
|
/// `AppEvent::LoadPickerReady`.
|
|
OpenLoadPicker,
|
|
/// Switch to the project at `path` (absolute or relative
|
|
/// to the active data root). Runtime drops the current
|
|
/// project, opens the new one, refreshes app state.
|
|
LoadProject {
|
|
path: std::path::PathBuf,
|
|
source: String,
|
|
},
|
|
/// Save the current project to `target` and switch to it.
|
|
/// `target` is a name or absolute path; relative names
|
|
/// resolve against `<data-root>/projects/` per ADR-0015 §1.
|
|
SaveAs {
|
|
target: String,
|
|
source: String,
|
|
},
|
|
/// Close the current project (auto-save guarantees state
|
|
/// is on disk) and create a fresh auto-named temp.
|
|
NewProject {
|
|
source: String,
|
|
},
|
|
/// Export the current project to a zip file. `target` is
|
|
/// `None` for the default filename
|
|
/// (`YYYYMMDD-<projectname>-export-NN.zip`) under the
|
|
/// active data root, or `Some(path)` for an explicit
|
|
/// target. Relative paths resolve under
|
|
/// `<data-root>/`. Per ADR-0015 §11 the zip excludes
|
|
/// `playground.db` and `history.log`.
|
|
Export {
|
|
target: Option<String>,
|
|
source: String,
|
|
},
|
|
/// Import a previously-exported zip and switch to the
|
|
/// resulting project. `zip_path` is the user-typed path to
|
|
/// the source archive (relative to CWD or absolute).
|
|
/// `as_target` is the optional user-supplied destination
|
|
/// name; when `None`, the destination is derived from the
|
|
/// zip's top-level folder. Collisions auto-suffix `-NN`
|
|
/// (ADR-0015 §11 amendment).
|
|
Import {
|
|
zip_path: String,
|
|
as_target: Option<String>,
|
|
source: String,
|
|
},
|
|
/// Replay a script of DSL commands from a file. The runtime
|
|
/// reads the file, iterates non-blank/non-comment lines, and
|
|
/// dispatches each through the same path as interactive
|
|
/// input. On per-line failure the runtime reports the line
|
|
/// number and stops (no rollback). On success it reports the
|
|
/// number of commands run.
|
|
///
|
|
/// `path` is the literal user-typed path; the runtime
|
|
/// resolves relative paths against the active project's root
|
|
/// so `replay history.log` works inside any project. Replay
|
|
/// itself is NOT written to `history.log` — only the
|
|
/// individual commands it dispatches are, since they are
|
|
/// what mutate state.
|
|
Replay {
|
|
path: String,
|
|
},
|
|
/// User issued `undo` (`PrepareUndo`) or `redo` (`PrepareRedo`)
|
|
/// (ADR-0006 Amendment 1). The runtime peeks the snapshot the
|
|
/// command would restore and posts `AppEvent::UndoPrepared`
|
|
/// (opening the confirmation modal) or `AppEvent::UndoUnavailable`
|
|
/// (nothing to undo/redo). Only emitted when undo is enabled —
|
|
/// the `App` notes "undo is off" itself under `--no-undo`.
|
|
PrepareUndo,
|
|
PrepareRedo,
|
|
/// User confirmed `undo` / `redo` from inside the modal. The
|
|
/// runtime restores the snapshot through the worker, then
|
|
/// refreshes the table list + schema cache.
|
|
Undo,
|
|
Redo,
|
|
/// Copy text to the system clipboard (the `copy` command, ADR-0041).
|
|
/// The `App` builds the full text from `App.output` (so `update`
|
|
/// stays pure); the runtime performs the I/O — emit an OSC 52 escape
|
|
/// to the terminal *and* a best-effort native write, ignoring native
|
|
/// failure (a headless host is fine; OSC 52 carried it).
|
|
CopyToClipboard(String),
|
|
/// User changed the input mode mid-session (the `mode` command).
|
|
/// The runtime records it through the worker so `project.yaml`
|
|
/// reflects the live mode and it is restored on the next open
|
|
/// (ADR-0015 mode-restore amendment, issue #14). Best-effort:
|
|
/// a persistence failure here must not escalate a UI mode toggle
|
|
/// into a fatal — the in-memory mode has already changed.
|
|
PersistMode(crate::mode::Mode),
|
|
}
|