feat: DSL→SQL teaching echo — channel + create-table slice (ADR-0037 + ADR-0038)

Walking skeleton validating the whole echo architecture end to end; the
Command→SQL renderer currently covers `create table`, with the rest of
Bucket A / B / category-3 to follow (ADR-0038 §8).

- Channel (ADR-0037): the three-way EffectiveMode (reusing the existing
  enum, not a new SubmissionMode — recorded in the ADR) rides on
  Action::ExecuteDsl to the runtime. `replay` bypasses the interactive
  spawn, so it never echoes (silent, for free).
- Echo (ADR-0038): built at the runtime's ExecuteDsl dispatch — the worker
  gets decomposed calls, not the Command, so ADR §4's "worker builds it"
  was corrected to the dispatch layer. Gated by echo_for (advanced
  effective mode + DSL-form). Carried on DslSucceeded; rendered by
  note_ok_summary as `Executing SQL: …` immediately beneath `[ok]`. New
  src/echo.rs renderer; echo.executing_sql i18n key.
- command_to_sql: `create table` → `CREATE TABLE T (id serial PRIMARY KEY)`
  (single inline / compound table-level PK), playground type vocabulary,
  round-trip-verified against the advanced walker (the §1 contract).

Tests: echo.rs (render, round-trip contract, mode gate, Sql*-not-echoed);
app.rs (submit carries the 3-way mode; echo renders beneath [ok]).
Suite 1970/0/1; clippy clean.
This commit is contained in:
claude@clouddev1
2026-05-27 22:09:54 +00:00
parent 9a23e28f30
commit 04c8e4295f
12 changed files with 350 additions and 29 deletions
+1
View File
@@ -60,6 +60,7 @@ fn advanced_mode_select_dispatches_as_command_select() {
[Action::ExecuteDsl {
command: Command::Select { sql },
source,
..
}] => {
assert!(
sql.contains("select 1"),
+5
View File
@@ -294,6 +294,7 @@ fn create_table_flow_updates_tables_list_and_structure_view() {
app.update(AppEvent::DslSucceeded {
command: expected_cmd,
description: Some(desc.clone()),
echo: None,
});
app.update(AppEvent::TablesRefreshed(vec!["Customers".to_string()]));
@@ -358,6 +359,7 @@ fn add_column_flow_updates_structure_view() {
check: None,
},
description: Some(updated.clone()),
echo: None,
});
assert_eq!(app.current_table, Some(updated));
let rendered = rendered_text(&mut app, &Theme::dark(), 80, 24);
@@ -387,6 +389,7 @@ fn drop_table_flow_clears_items_list() {
name: "Customers".to_string(),
},
description: None,
echo: None,
});
app.update(AppEvent::TablesRefreshed(Vec::new()));
@@ -460,6 +463,7 @@ fn add_relationship_flow_shows_parent_side_with_inbound_section() {
create_fk: false,
},
description: Some(customers),
echo: None,
});
let rendered = rendered_text(&mut app, &Theme::dark(), 80, 24);
@@ -513,6 +517,7 @@ fn add_relationship_flow_shows_inbound_section_on_parent() {
check: None,
},
description: Some(customers),
echo: None,
});
let rendered = rendered_text(&mut app, &Theme::dark(), 80, 24);
assert!(rendered.contains("Referenced by:"), "{rendered}");