feat: show table renders relationships as compact diagrams (ADR-0044)

show table <T> and add/drop relationship echoes now render the focal
structure box plus a Relationships section of compact stacked
connector diagrams (child-left/parent-right, n…1, actions); incidental
DDL echoes keep the prose References:/Referenced by: form. Selected by
command in handle_dsl_success via the "relationship-relevant" reach.

- output_render.rs: render_structure refactored into section helpers
  (box/prose/index/constraint), byte-identical output; new
  render_structure_with_diagrams + compact-box rendering
- app.rs: handle_dsl_success routes ShowTable/Add/DropRelationship to
  the diagram path, others to prose
- fixes: eager widths[1] index on compact (1-col) boxes; body-cell
  padding under title-widening (name wider than columns)

Tests: unit + snapshot + integration; add-relationship echo test
updated to the diagram form. Full suite 2203 pass / 0 fail / 1 ignored;
clippy clean. V1 still [/] (compound routing + self-ref remain).
This commit is contained in:
claude@clouddev1
2026-06-10 06:56:35 +00:00
parent cad90ec4a5
commit a0ee32393f
5 changed files with 258 additions and 32 deletions
+35
View File
@@ -454,3 +454,38 @@ fn app_show_relationship_not_found_shows_friendly_line() {
"friendly not-found line",
);
}
#[test]
fn app_show_table_renders_relationships_as_compact_diagrams() {
let (_p, db, _dir) = open_project_db();
let rt = rt();
rt.block_on(seed_schema(&db));
// Orders holds the FK to Customers — an outbound relationship.
let desc = rt
.block_on(db.describe_table("Orders".to_string(), None))
.expect("describe Orders");
let mut app = App::new();
app.output.push_back(rdbms_playground::app::OutputLine::echo(
"show table Orders",
Mode::Simple,
));
app.update(AppEvent::DslSucceeded {
command: Command::ShowTable {
name: "Orders".to_string(),
},
description: Some(desc),
echo: None,
});
let text: String = app
.output
.iter()
.map(|l| l.text.as_str())
.collect::<Vec<_>>()
.join("\n");
// The focal structure box, then a diagram (not the prose block).
assert!(text.contains("Relationships"), "diagram heading: {text}");
assert!(!text.contains("References:"), "prose suppressed: {text}");
assert!(text.contains("Customers"), "neighbour box: {text}");
assert!(text.contains('▶'), "connector arrow: {text}");
}
+9 -3
View File
@@ -473,9 +473,15 @@ fn add_relationship_flow_shows_parent_side_with_inbound_section() {
echo: None,
});
let rendered = rendered_text(&mut app, &Theme::dark(), 80, 24);
assert!(rendered.contains("Referenced by:"), "{rendered}");
assert!(rendered.contains("Orders.CustId"), "{rendered}");
// Tall viewport so the [ok] echo line stays visible above the
// (taller-than-prose) diagram for the endpoint-subject assertion.
let rendered = rendered_text(&mut app, &Theme::dark(), 80, 40);
// ADR-0044: `add relationship` is relationship-relevant, so its echo
// renders the relationship as a compact diagram, not the prose block.
assert!(rendered.contains("Relationships"), "heading: {rendered}");
assert!(rendered.contains("Orders"), "neighbour box: {rendered}");
assert!(rendered.contains("CustId"), "FK column: {rendered}");
assert!(rendered.contains('▶'), "connector: {rendered}");
assert!(rendered.contains("on delete cascade"), "{rendered}");
// The [ok] subject lists the endpoints. Long lines wrap in
// the panel, so we check the first half of the phrase only.