feat(render): incidental-DDL confirmations show structure only, no relationships (#28)

Per ADR-0050 (closing issue #28): the confirmation echo after an
incidental structural edit — create table, add/drop/rename/change
column, add/drop index — now renders the structure only (header +
column box + indexes + constraints) and no longer appends the
References:/Referenced by: relationship block.

Rationale: a confirmation reports the change just made, not the
table's relationships, which the user didn't touch. Relationship info
is still one `show table <T>` away, and the relationship-subject
surfaces (show table, add/drop relationship) keep their ADR-0044
diagrams unchanged.

Scope is all incidental DDL (user-confirmed). Mechanism: drop the
relationship-block call from render_structure (all its callers are
incidental DDL); the handle_dsl_success diagram-vs-structure routing
is unchanged. The orphaned relationship_prose_lines + cols_disp
helpers are deleted (the prose format survives in ADR-0016 §5 + git
history for a future OOS-7 always-prose setting).

ADR-0050 supersedes ADR-0044 §1's incidental-DDL prose clause and the
relationship-block half of ADR-0016 §5 (both annotated). Tests: prose-
presence unit test + snapshot removed; new unit test locks structure-
only with inbound+outbound relationships present; the misnamed add-
column integration test inverted + renamed. 2458 pass / 0 fail / 0
skip, clippy clean.
This commit is contained in:
claude@clouddev1
2026-06-12 22:45:18 +00:00
parent 66c8bdaa65
commit 8ac3537df0
8 changed files with 189 additions and 80 deletions
+4 -2
View File
@@ -2110,8 +2110,10 @@ impl App {
// ADR-0044 §1 "relationship-relevant" reach: when a
// relationship is the subject of the command (`show table`,
// `add`/`drop relationship`), render the table's
// relationships as compact diagrams; every other DDL echo
// keeps the prose `References:` / `Referenced by:` form.
// relationships as compact diagrams. Every other (incidental
// DDL) echo renders structure only — no relationship block
// at all (ADR-0050, issue #28; supersedes ADR-0044 §1's
// prose retention for these surfaces).
if matches!(
command,
Command::ShowTable { .. }
+34 -63
View File
@@ -71,27 +71,22 @@ pub fn render_data_table(data: &DataResult) -> Vec<String> {
render_table(&header_cells, &body, &alignments)
}
/// Render a table-structure listing.
/// Render an incidental-DDL structure echo (ADR-0050, issue #28).
///
/// Produces a header line (`<TableName>`), the schema table
/// itself, and — for a structure that has FK relationships
/// — `References:` / `Referenced by:` blocks below as plain
/// indented text (relationship visualization is its own
/// future ADR per §5 OOS-1).
/// Display a relationship-endpoint column list (ADR-0043): the bare
/// column for a single-column FK, `(a, b)` for a compound one.
fn cols_disp(cols: &[String]) -> String {
if cols.len() == 1 {
cols[0].clone()
} else {
format!("({})", cols.join(", "))
}
}
/// Produces a header line (`<TableName>`), the schema table, the
/// `Indexes:` section, and the constraint section — **structure only**.
/// Relationship information is deliberately omitted: a confirmation
/// echo for a structural edit (`create table`, `add`/`drop`/`rename`/
/// `change column`, `add`/`drop index`) reports the change just made,
/// not the table's relationships, which the user did not touch. The
/// relationship-subject surfaces (`show table`, `add`/`drop
/// relationship`) render diagrams via [`render_structure_with_diagrams`]
/// instead; relationships are one `show table <T>` away. ADR-0050
/// supersedes ADR-0044 §1's "incidental DDL keeps prose" and the
/// relationship-block half of ADR-0016 §5.
#[must_use]
pub fn render_structure(desc: &TableDescription) -> Vec<String> {
let mut out = structure_box_lines(desc);
out.extend(relationship_prose_lines(desc));
out.extend(index_lines(desc));
out.extend(constraint_lines(desc));
out
@@ -118,41 +113,6 @@ fn structure_box_lines(desc: &TableDescription) -> Vec<String> {
out
}
/// The `References:` / `Referenced by:` prose blocks (ADR-0016 §5),
/// retained for the incidental DDL echoes (ADR-0044 §1).
fn relationship_prose_lines(desc: &TableDescription) -> Vec<String> {
let mut out: Vec<String> = Vec::new();
if !desc.outbound_relationships.is_empty() {
out.push("References:".to_string());
for r in &desc.outbound_relationships {
out.push(format!(
" {}{}.{} ({}, on delete {}, on update {})",
cols_disp(&r.local_columns),
r.other_table,
cols_disp(&r.other_columns),
r.name,
r.on_delete,
r.on_update,
));
}
}
if !desc.inbound_relationships.is_empty() {
out.push("Referenced by:".to_string());
for r in &desc.inbound_relationships {
out.push(format!(
" {}.{}{} ({}, on delete {}, on update {})",
r.other_table,
cols_disp(&r.other_columns),
cols_disp(&r.local_columns),
r.name,
r.on_delete,
r.on_update,
));
}
}
out
}
/// Indexes section (ADR-0025), only when the table carries a
/// user-created index. A UNIQUE index is marked `[unique]` (ADR-0035
/// §4d).
@@ -1591,11 +1551,23 @@ mod tests {
}
#[test]
fn render_structure_with_relationships() {
fn render_structure_omits_relationship_prose() {
// ADR-0050 (issue #28): the incidental-DDL structure echo never
// carries the `References:` / `Referenced by:` block, even when
// the description carries both inbound and outbound
// relationships. (Relationship-subject surfaces render diagrams
// via `render_structure_with_diagrams`, not this function.)
let desc = TableDescription {
name: "Customers".to_string(),
columns: vec![col("id", Type::Serial, true, false)],
outbound_relationships: Vec::new(),
outbound_relationships: vec![RelationshipEnd {
name: "cust_region".to_string(),
other_table: "Regions".to_string(),
other_columns: vec!["id".to_string()],
local_columns: vec!["region_id".to_string()],
on_delete: ReferentialAction::NoAction,
on_update: ReferentialAction::NoAction,
}],
inbound_relationships: vec![RelationshipEnd {
name: "cust_orders".to_string(),
other_table: "Orders".to_string(),
@@ -1609,15 +1581,14 @@ mod tests {
check_constraints: Vec::new(),
};
let out = render_structure(&desc).join("\n");
assert!(
out.contains("Referenced by:"),
"expected inbound relationship section:\n{out}",
);
assert!(
out.contains("Orders.cust_id → id"),
"expected inbound relationship line:\n{out}",
);
assert_snapshot!(out);
// The structure box still renders.
assert!(out.contains("Customers"), "structure header:\n{out}");
assert!(out.contains("│ id"), "column row:\n{out}");
// No relationship block in either direction.
assert!(!out.contains("References:"), "no outbound prose:\n{out}");
assert!(!out.contains("Referenced by:"), "no inbound prose:\n{out}");
assert!(!out.contains("Orders.cust_id"), "no prose line:\n{out}");
assert!(!out.contains("Regions"), "no prose line:\n{out}");
}
#[test]
@@ -1,12 +0,0 @@
---
source: src/output_render.rs
expression: out
---
Customers
┌──────┬────────┬─────────────┐
│ Name │ Type │ Constraints │
├──────┼────────┼─────────────┤
│ id │ serial │ PK │
└──────┴────────┴─────────────┘
Referenced by:
Orders.cust_id → id (cust_orders, on delete cascade, on update no action)