Files
rdbms-playground/src/event.rs
T
claude@clouddev1 cad90ec4a5 feat: show relationship <name> renders a styled two-table diagram (ADR-0044)
The first wired slice of relationship visualization (V1). `show
relationship <name>` now renders the relationship as two full
structure boxes joined by a width-jogging connector (child-left /
parent-right, n…1 cardinality, on delete/update actions), styled
App-side, with a vertical-stack fallback for narrow terminals.

- db.rs: RelationshipDiagramData + show_relationship worker path
  (structured data: the relationship + both endpoint TableDescriptions)
- runtime.rs: named relationships route to the structured outcome
  (boxed); other show <kind> forms stay prose
- app.rs/event.rs/ui.rs: DslShowRelationshipSucceeded rendered App-side;
  new diagram OutputStyleClass variants; App::last_output_width from ui.rs
- output_render.rs: styled Seg layout engine (boxes, connector routing,
  side-by-side + vertical), composing the ADR-0016 box primitives

Tests: 4 unit + 4 integration; full suite 2201 pass / 0 fail / 1 ignored;
clippy nursery clean. requirements.md V1 stays [/] (show table diagrams,
compound routing, DDL-echo wiring remain).
2026-06-09 22:27:39 +00:00

278 lines
11 KiB
Rust

//! Events fed into the application's update function.
//!
//! `AppEvent` is the single input type the runtime delivers to
//! `App::update`. Synthetic instances drive Tier 3 integration
//! tests (see ADR-0008), so the type is plain data with no
//! runtime dependency.
use crossterm::event::KeyEvent;
use crate::db::{
AddColumnResult, ChangeColumnTypeResult, DataResult, DbError, DeleteResult,
DropColumnResult, InsertResult, QueryPlan, RelationshipDiagramData, TableDescription,
UpdateResult,
};
use crate::dsl::Command;
#[derive(Debug, Clone)]
pub enum AppEvent {
Key(KeyEvent),
Resize {
cols: u16,
rows: u16,
},
Tick,
/// A DSL command finished successfully. `description` is
/// `Some` for commands that produce a table view (create,
/// add column) and `None` for commands that don't (drop).
DslSucceeded {
command: Command,
description: Option<TableDescription>,
/// The DSL → SQL teaching echo (ADR-0038): equivalent advanced-mode
/// SQL, built by the runtime when a DSL-form command ran in an
/// advanced effective mode (ADR-0037). `None` when no echo applies
/// (simple mode, SQL-entered, or a form with no echo). The App
/// renders it beneath `[ok]`.
echo: Option<Vec<String>>,
},
/// A SQL `CREATE TABLE IF NOT EXISTS` matched an existing table —
/// a no-op (ADR-0035 §4). Renders the existing structure plus an
/// "already exists — skipped" note.
DslCreateSkipped {
command: Command,
description: TableDescription,
},
/// A SQL `DROP TABLE IF EXISTS` matched no table — a no-op
/// (ADR-0035 §4, 4c). Renders a "doesn't exist — skipped" note;
/// there is no structure to show.
DslDropSkipped {
command: Command,
},
/// A SQL `DROP INDEX IF EXISTS` matched no index — a no-op
/// (ADR-0035 §4d). Renders an index-specific "doesn't exist —
/// skipped" note; no structure to show.
DslDropIndexSkipped {
command: Command,
},
/// A SQL `CREATE INDEX IF NOT EXISTS` matched an existing index name
/// — a no-op (ADR-0035 §4d). `name` is the resolved index name (the
/// auto-name is not on the command). Renders "already exists —
/// skipped"; no structure to show.
DslCreateIndexSkipped {
command: Command,
name: String,
},
/// A `show data` query succeeded. `echo` is the DSL → SQL teaching
/// echo (ADR-0038) — built post-execution because the limited form
/// orders by the table's primary key (handoff §5). `None` for a
/// SQL-entered `SELECT` or any simple-mode submission.
DslDataSucceeded {
command: Command,
data: DataResult,
echo: Option<Vec<String>>,
},
/// An `explain …` command succeeded (ADR-0028). `plan`
/// carries the captured query plan; nothing was executed.
DslExplainSucceeded { command: Command, plan: QueryPlan },
/// A `show <kind>` list command (V5) — carries pre-formatted
/// display lines (tables / relationships / indexes).
DslShowListSucceeded { command: Command, lines: Vec<String> },
/// `show relationship <name>` (ADR-0044) — structured data for the
/// diagram, rendered App-side; `None` when no such relationship.
DslShowRelationshipSucceeded {
command: Command,
data: Option<RelationshipDiagramData>,
},
DslInsertSucceeded {
command: Command,
result: InsertResult,
},
DslUpdateSucceeded {
command: Command,
result: UpdateResult,
/// The DSL → SQL teaching echo (ADR-0038): `UPDATE T SET …` for an
/// `update … --all-rows` fall-through. `None` for a SQL-entered
/// `UPDATE` or any simple-mode submission.
echo: Option<Vec<String>>,
},
DslDeleteSucceeded {
command: Command,
result: DeleteResult,
/// The DSL → SQL teaching echo (ADR-0038): `DELETE FROM T` for a
/// `delete … --all-rows` fall-through. `None` for a SQL-entered
/// `DELETE` or any simple-mode submission.
echo: Option<Vec<String>>,
},
/// A `change column …` succeeded. `result` carries both the
/// post-rebuild description (for the auto-show) and the
/// optional `[client-side]` note (ADR-0017 §6).
DslChangeColumnSucceeded {
command: Command,
result: ChangeColumnTypeResult,
/// The DSL → SQL teaching echo (ADR-0038): `ALTER TABLE T ALTER
/// COLUMN c SET DATA TYPE …`. `None` in simple mode.
echo: Option<Vec<String>>,
/// The `--dont-convert` caveat (ADR-0038 §6 category 3): set by the
/// runtime to `true` when the command ran with
/// `ChangeColumnMode::DontConvert` in an advanced effective mode
/// (the only case where it is meaningful — the line references
/// "the line above," i.e. the echo). The App renders the prose
/// caveat between the existing client-side notes and the structure.
dont_convert_caveat: bool,
},
/// An `add column …` succeeded. `result` carries the
/// post-add description plus any `[client-side]` notes
/// from the auto-fill paths (ADR-0018 §9).
DslAddColumnSucceeded {
command: Command,
result: AddColumnResult,
/// The DSL → SQL teaching echo (ADR-0038): `ALTER TABLE T ADD
/// COLUMN c <ty> …`. `None` in simple mode.
echo: Option<Vec<String>>,
},
/// A `drop column …` succeeded. `result` carries the
/// post-drop description plus the names of any indexes
/// removed by `--cascade` (ADR-0025).
DslDropColumnSucceeded {
command: Command,
result: DropColumnResult,
/// The DSL → SQL teaching echo (ADR-0038): `ALTER TABLE T DROP
/// COLUMN c` for a plain (non-`--cascade`) drop. `None` in simple
/// mode, and for `--cascade` (a multi-statement echo, Phase 2).
echo: Option<Vec<String>>,
},
/// A DSL command failed. `error` is the structured
/// payload, `facts` is the runtime-built schema-resolved
/// enrichment (parent tables, attempted values,
/// pinpointed offending rows). App applies its current
/// verbosity setting (`messages_verbosity`) when rendering
/// through `friendly::translate_error` (ADR-0019 §5, §6).
DslFailed {
command: Command,
error: DbError,
facts: crate::friendly::FailureContext,
/// The original user-typed source line, retained so the
/// App can journal the failed command as an `err` record
/// (ADR-0034 §1/§2). The worker only journals successful
/// commands, so an execution failure would otherwise be
/// lost across sessions.
source: String,
},
/// Refreshed list of tables in the database.
TablesRefreshed(Vec<String>),
/// Refreshed schema lookup cache feeding Tab completion
/// for identifier slots (ADR-0022 §9 + stage 8d). Runtime
/// posts this alongside `TablesRefreshed` after project
/// load and after every successful DDL.
SchemaCacheRefreshed(crate::completion::SchemaCache),
/// A persistence failure occurred (ADR-0015 §8). The
/// application surfaces a fatal banner and exits cleanly so
/// the message remains above the shell prompt.
PersistenceFatal {
operation: String,
path: std::path::PathBuf,
message: String,
},
/// Runtime has computed the rebuild summary from
/// `project.yaml` + `data/` and is ready for the user to
/// confirm. App opens the confirmation modal.
RebuildPrepared {
summary: String,
},
/// Rebuild completed successfully. App closes the modal,
/// surfaces a friendly outcome message, and refreshes the
/// table list.
RebuildSucceeded {
summary: String,
},
/// Rebuild failed in a non-fatal way (e.g., user-visible
/// constraint problem) — surfaced like other DSL failures.
RebuildFailed {
error: String,
},
/// Runtime peeked the snapshot `undo`/`redo` would restore and
/// is ready for the user to confirm (ADR-0006 Amendment 1). App
/// opens the confirmation modal naming `command`. `is_redo`
/// selects undo vs redo wording.
UndoPrepared {
command: String,
timestamp: String,
is_redo: bool,
},
/// Nothing to undo / redo (the ring or redo stack was empty).
/// App surfaces a friendly note.
UndoUnavailable {
is_redo: bool,
},
/// Undo / redo completed. App closes the modal, notes the
/// command that was undone/redone; the runtime also refreshes
/// the table list + schema cache.
UndoSucceeded {
command: String,
is_redo: bool,
},
/// Undo / redo failed (a rare restore error). App closes the
/// modal and surfaces the error.
UndoFailed {
error: String,
is_redo: bool,
},
/// Runtime has gathered the list of available projects
/// for the load picker. App opens the picker modal.
LoadPickerReady {
entries: Vec<crate::app::LoadPickerEntry>,
},
/// A project switch (load / new / save-as / import)
/// succeeded. Carries the new display name, the temp
/// flag (drives the `[TEMP]` status-bar prefix), the
/// seed entries for input-history hydration off the new
/// project's `history.log` (I2-persist, ADR-0015 §12), and
/// the mode to restore for the switched-to project (its
/// stored mode, ADR-0015 mode-restore amendment, issue #14 —
/// "loading triggers the mode switch each time").
ProjectSwitched {
display_name: String,
is_temp: bool,
history_entries: Vec<String>,
mode: crate::mode::Mode,
},
/// A project switch failed in a non-fatal way (target
/// already exists, path unreadable, …). Surfaced as an
/// error in the output panel.
ProjectSwitchFailed {
error: String,
},
/// Export wrote a zip successfully. Carries the resolved
/// final path so the user gets a "wrote to: …" note.
ExportSucceeded {
path: std::path::PathBuf,
},
/// Export failed in a non-fatal way (target exists, IO
/// error, sequence range exhausted, …).
ExportFailed {
error: String,
},
/// A `replay <path>` finished without error, after running
/// `count` non-blank, non-comment commands from the file.
/// Surfaced as `[ok] replay — N command(s)` in the output.
ReplayCompleted {
path: String,
count: usize,
/// Pre-rendered `[skip]` warnings for app-lifecycle commands
/// whose omission can leave the replayed state incomplete —
/// `import` and a nested `replay` (ADR-0034). Other skipped
/// app commands are silent and do not appear here.
warnings: Vec<String>,
},
/// A `replay <path>` aborted at line `line_number`. `command`
/// is the line text as it appeared in the file (for the
/// user's eyeline so they can locate the failing entry);
/// `error` is the rendered parse or runtime error.
ReplayFailed {
path: String,
line_number: usize,
command: String,
error: String,
},
}