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).
This commit is contained in:
claude@clouddev1
2026-06-09 22:27:39 +00:00
parent bb02dfb752
commit cad90ec4a5
8 changed files with 756 additions and 1 deletions
+52
View File
@@ -76,6 +76,19 @@ pub enum OutputStyleClass {
/// every `[client-side]` category-3 prose note (ADR-0038 §6).
/// Resolves to `theme.muted`.
Hint,
/// A relationship-diagram box's title row — the table name
/// (ADR-0044 §2.1). Bold accent so it cannot read as a column.
DiagramTableName,
/// A relationship-diagram key marker — `(PK)` / `●` on the
/// participating columns (ADR-0044 §2.2).
DiagramKey,
/// A relationship-diagram cardinality label — `1` / `n`
/// (ADR-0044 §2).
DiagramCardinality,
/// A relationship-diagram connector — box-drawing line, elbows
/// and arrowhead between the two boxes (ADR-0044 §2.3). Muted so
/// the structure, not the wiring, leads.
DiagramConnector,
}
/// A styled span of an output line: a byte range over the
@@ -268,6 +281,11 @@ pub struct App {
/// logical OutputLines. Required for accurate scroll capping
/// when long lines wrap to multiple display rows.
pub last_output_total_wrapped: usize,
/// The most recent inner width (in columns) of the output panel,
/// recorded by the renderer (ADR-0044 §3). Drives the relationship
/// diagram's side-by-side vs vertical layout choice. Defaults to
/// `80` until the first render measures the real width.
pub last_output_width: u16,
/// Prettified display name of the currently-open project,
/// rendered in the status bar (P-NAME-3, ADR-0015 §2). `None`
/// during very-early startup before the runtime has opened a
@@ -432,6 +450,7 @@ impl App {
output_scroll: 0,
last_output_visible: 0,
last_output_total_wrapped: 0,
last_output_width: 80,
project_name: None,
project_is_temp: false,
fatal_message: None,
@@ -614,6 +633,10 @@ impl App {
}
Vec::new()
}
AppEvent::DslShowRelationshipSucceeded { command, data } => {
self.handle_dsl_show_relationship_success(&command, data.as_ref());
Vec::new()
}
AppEvent::DslInsertSucceeded { command, result } => {
self.handle_dsl_insert_success(&command, &result);
Vec::new()
@@ -1694,6 +1717,35 @@ impl App {
}
}
/// `show relationship <name>` (ADR-0044): render the relationship
/// as a styled two-table diagram, App-side, sized to the current
/// output-panel width. `None` is the friendly not-found line.
fn handle_dsl_show_relationship_success(
&mut self,
command: &Command,
data: Option<&crate::db::RelationshipDiagramData>,
) {
self.note_ok_summary(command);
match data {
Some(data) => {
for line in crate::output_render::render_relationship_diagram(
data,
self.last_output_width,
self.mode,
) {
self.push_output(line);
}
}
None => {
let name = match command {
Command::ShowList { name: Some(n), .. } => n.as_str(),
_ => "",
};
self.note_system(format!("No relationship named `{name}`."));
}
}
}
fn handle_dsl_insert_success(&mut self, command: &Command, result: &InsertResult) {
self.note_ok_summary(command);
self.note_system(crate::t!("ok.rows_inserted", count = result.rows_affected));