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
+56
View File
@@ -80,6 +80,21 @@ pub struct TableDescription {
pub check_constraints: Vec<crate::persistence::TableCheck>,
}
/// Structured payload for rendering one relationship's diagram.
///
/// ADR-0044: the relationship plus both endpoint table structures.
/// Built worker-side; rendered **App-side** (like `QueryPlan`) so the
/// diagram can be width-aware and styled.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RelationshipDiagramData {
/// The relationship itself (endpoints + referential actions).
pub rel: crate::persistence::RelationshipSchema,
/// FK-holder (the `n` side), drawn on the left.
pub child: TableDescription,
/// Referenced table (the `1` side), drawn on the right.
pub parent: TableDescription,
}
/// One user-created index on a table (ADR-0025).
///
/// Read live from the engine's native catalog
@@ -566,6 +581,13 @@ enum Request {
name: Option<String>,
reply: oneshot::Sender<Result<Vec<String>, DbError>>,
},
/// Structured data to render one relationship's diagram (ADR-0044
/// §6): the relationship + both endpoint table structures, or
/// `None` if no relationship by that name exists.
ShowRelationship {
name: String,
reply: oneshot::Sender<Result<Option<RelationshipDiagramData>, DbError>>,
},
DescribeTable {
name: String,
source: Option<String>,
@@ -1341,6 +1363,18 @@ impl Database {
recv.await.map_err(|_| DbError::WorkerGone)?
}
/// Structured data to render one relationship's diagram (ADR-0044):
/// the relationship + both endpoint table structures, or `None` if
/// no relationship by that name exists.
pub async fn show_relationship(
&self,
name: String,
) -> Result<Option<RelationshipDiagramData>, DbError> {
let (reply, recv) = oneshot::channel();
self.send(Request::ShowRelationship { name, reply }).await?;
recv.await.map_err(|_| DbError::WorkerGone)?
}
pub async fn describe_table(
&self,
name: String,
@@ -2272,6 +2306,9 @@ fn handle_request(
Request::ShowList { kind, name, reply } => {
let _ = reply.send(do_show_list(conn, kind, name.as_deref()));
}
Request::ShowRelationship { name, reply } => {
let _ = reply.send(do_show_relationship(conn, &name));
}
Request::DescribeTable {
name,
source,
@@ -5870,6 +5907,25 @@ fn do_list_tables(conn: &Connection) -> Result<Vec<String>, DbError> {
Ok(out)
}
/// Structured data to render one relationship's diagram (ADR-0044):
/// find the named relationship, then describe both endpoint tables.
/// `Ok(None)` when no relationship by that name exists (the App shows
/// a friendly not-found line).
fn do_show_relationship(
conn: &Connection,
name: &str,
) -> Result<Option<RelationshipDiagramData>, DbError> {
let Some(rel) = read_all_relationships(conn)?
.into_iter()
.find(|r| r.name == name)
else {
return Ok(None);
};
let child = do_describe_table(conn, &rel.child_table)?;
let parent = do_describe_table(conn, &rel.parent_table)?;
Ok(Some(RelationshipDiagramData { rel, child, parent }))
}
/// Pre-formatted display lines for the `show <kind>` list commands
/// (V5). A count header followed by one indented item per line, or a
/// single friendly "none yet" line for an empty collection. Reuses