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
+10
View File
@@ -197,6 +197,16 @@ Referenced by:
The relationship sections retain today's plain-text format The relationship sections retain today's plain-text format
to leave room for the future relationship-rendering ADR. to leave room for the future relationship-rendering ADR.
> **Superseded.** ADR-0044 replaced this prose block with compact
> diagrams on relationship-subject surfaces (`show table`,
> `add`/`drop relationship`). **ADR-0050 (2026-06-12, issue #28)** then
> removed the relationship block entirely from incidental-DDL structure
> echoes (`create table`, `add`/`drop`/`rename`/`change column`,
> `add`/`drop index`) — those render structure only — and **deleted the
> prose renderer**. The `References:` / `Referenced by:` format above is
> retained here as documentation/provenance should the OOS-7
> always-prose display setting ever be built.
### 6. Theme integration ### 6. Theme integration
Theme colors apply to the box-drawing characters via the Theme colors apply to the box-drawing characters via the
@@ -103,6 +103,10 @@ Prose-retained surfaces (**unchanged** from ADR-0016 §5):
`add`/`drop index` — keep the terse `References:` / `add`/`drop index` — keep the terse `References:` /
`Referenced by:` prose. A simple `add column` on a heavily-related `Referenced by:` prose. A simple `add column` on a heavily-related
table should not print a wall of diagrams. table should not print a wall of diagrams.
*(**Superseded 2026-06-12 by ADR-0050** (issue #28): these incidental
DDL echoes now render **structure only** — no relationship block at
all, neither prose nor diagram. The prose renderer was deleted. The
diagram surfaces below are unchanged.)*
So this **partially supersedes ADR-0016 §5**: the prose block is So this **partially supersedes ADR-0016 §5**: the prose block is
replaced by diagrams on the relationship-subject surfaces and replaced by diagrams on the relationship-subject surfaces and
@@ -0,0 +1,119 @@
# ADR-0050: Incidental-DDL confirmations omit relationship info (structure-only)
## Status
**Accepted + implemented 2026-06-12 (issue #28).** Closes Gitea **#28**.
Both forks below were escalated to the user and user-chosen before any
code was written; implemented test-first. **Supersedes** the
incidental-DDL clause of **ADR-0044 §1** and the part of **ADR-0016 §5**
that placed a relationship block under every structure echo. The
diagram behaviour ADR-0044 introduced for relationship-subject surfaces
is unchanged.
## Context
ADR-0016 §5 rendered a structure box followed by a plain-text
`References:` / `Referenced by:` relationship block under **every**
structure echo. ADR-0044 §1 split that by surface:
- **Relationship-subject surfaces** — `show table <T>`,
`add 1:n relationship`, `drop relationship`, `show relationship <name>`
— render relationships as compact **diagrams** (the user asked for, or
acted on, a relationship).
- **Incidental DDL auto-shows** — `create table`, `add`/`drop`/`rename`/
`change column`, `add`/`drop index` — kept the terse **prose** block,
with the rationale *"a simple `add column` on a heavily-related table
should not print a wall of diagrams."*
Issue #28 reconsiders the deeper question ADR-0044 did not ask: should
an incidental-DDL confirmation show relationship information **at all**?
Owner preference: **no.** A confirmation echo should focus on the change
just made — the new / updated structure — not re-print the table's
relationships, which the user did not touch. The terse prose was the
lesser of "prose vs diagram", but the right answer for these surfaces is
**neither**.
## Decision
**Incidental-DDL confirmation echoes render the structure only** — the
table-name header, the column / type / constraints box, the `Indexes:`
section, and the constraint section — with **no relationship section**
(neither prose nor diagram).
- **Scope: all incidental DDL** (user-chosen, over "just `add column`"):
`create table`, `add column`, `drop column`, `rename column`,
`change column`, `add index`, `drop index`. The rule is uniform — a
structural edit confirms structure, never relationships. (For a
freshly `create`d table the relationship section was empty anyway; the
rule still applies for consistency of the mental model.)
- **Relationship-subject surfaces are unchanged.** `show table`,
`add`/`drop relationship`, and `show relationship <name>` still render
diagrams. Relationships appear **only** when the user asks for them
(`show table` / `show relationship`) or acts on one
(`add`/`drop relationship`).
- **No information is lost.** Anything dropped from an incidental echo is
one `show table <T>` away.
### Mechanism
The `handle_dsl_success` routing (`app.rs`) is **unchanged**: it still
sends relationship-subject commands to the diagram renderer and
everything else to `render_structure`. The change is entirely inside
`render_structure` (`output_render.rs`): it no longer appends the
relationship block — `render_structure` = structure box + indexes +
constraints. All of `render_structure`'s callers are incidental DDL
(verified), so this single edit covers the whole scope with no
per-command branching.
### Prose renderer disposition
The orphaned prose renderer (`relationship_prose_lines`, and its
sole helper `cols_disp`) is **deleted** (user-chosen, over retaining it
dormant). After this change no shipped surface renders the prose form,
so keeping it would be dead code. The prose format remains documented in
**ADR-0016 §5** and in git history; if ADR-0044's OOS-7 user-configurable
"always-prose" display setting is ever built, it re-introduces the ~30
lines from that provenance.
## Forks (all user-chosen)
- **Scope:** *all incidental DDL*, not just `add column` — the owner's
rationale ("confirm the change, not untouched relationships") applies
uniformly, gives a clean mental model, and is the simpler edit (remove
one call vs a per-command flag).
- **Prose renderer:** *delete* it — no dead code — over retaining a
public, tested-but-uncalled renderer for the speculative OOS-7 setting.
## Consequences
- Incidental confirmations are shorter and on-topic; a heavily-related
table no longer prints a relationship wall after `add column`.
- One relationship renderer (prose) leaves the codebase; the diagram
renderer (ADR-0044) is the only relationship render path that ships.
- `requirements.md` is unaffected (this is an ADR-tracked refinement of a
decided area, like ADR-0044 itself); the change is cross-referenced
from the commit + this ADR.
## Tests
- **Unit (`output_render.rs`):** the prose-asserting test
`render_structure_with_relationships` (+ its snapshot) is removed; a
new test asserts `render_structure` on a description **carrying** both
inbound and outbound relationships emits the structure box but **no**
`References:` / `Referenced by:` lines. The box/index/constraint tests
are unaffected (their descriptions have no relationships).
- **Integration (`walking_skeleton.rs`):** the misnamed
`add_relationship_flow_shows_inbound_section_on_parent` (which sends an
`AddColumn` and asserted the inbound prose) is inverted + renamed to
assert the add-column confirmation shows the structure but **omits**
the relationship prose.
- **Unchanged:** the diagram tests (`show_list.rs` `show table`,
`walking_skeleton.rs` `add relationship`) still pass — they already
assert prose is absent and diagrams are present.
## Out of scope
- The diagram form and its per-surface defaults (ADR-0044) — unchanged.
- The OOS-7 user-configurable display setting (always-prose / -diagram /
auto-by-width) — still a future follow-up; this ADR removes the prose
*renderer*, not the *idea* of a prose mode.
File diff suppressed because one or more lines are too long
+4 -2
View File
@@ -2110,8 +2110,10 @@ impl App {
// ADR-0044 §1 "relationship-relevant" reach: when a // ADR-0044 §1 "relationship-relevant" reach: when a
// relationship is the subject of the command (`show table`, // relationship is the subject of the command (`show table`,
// `add`/`drop relationship`), render the table's // `add`/`drop relationship`), render the table's
// relationships as compact diagrams; every other DDL echo // relationships as compact diagrams. Every other (incidental
// keeps the prose `References:` / `Referenced by:` form. // 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!( if matches!(
command, command,
Command::ShowTable { .. } 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_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 /// Produces a header line (`<TableName>`), the schema table, the
/// itself, and — for a structure that has FK relationships /// `Indexes:` section, and the constraint section — **structure only**.
/// — `References:` / `Referenced by:` blocks below as plain /// Relationship information is deliberately omitted: a confirmation
/// indented text (relationship visualization is its own /// echo for a structural edit (`create table`, `add`/`drop`/`rename`/
/// future ADR per §5 OOS-1). /// `change column`, `add`/`drop index`) reports the change just made,
/// Display a relationship-endpoint column list (ADR-0043): the bare /// not the table's relationships, which the user did not touch. The
/// column for a single-column FK, `(a, b)` for a compound one. /// relationship-subject surfaces (`show table`, `add`/`drop
fn cols_disp(cols: &[String]) -> String { /// relationship`) render diagrams via [`render_structure_with_diagrams`]
if cols.len() == 1 { /// instead; relationships are one `show table <T>` away. ADR-0050
cols[0].clone() /// supersedes ADR-0044 §1's "incidental DDL keeps prose" and the
} else { /// relationship-block half of ADR-0016 §5.
format!("({})", cols.join(", "))
}
}
#[must_use] #[must_use]
pub fn render_structure(desc: &TableDescription) -> Vec<String> { pub fn render_structure(desc: &TableDescription) -> Vec<String> {
let mut out = structure_box_lines(desc); let mut out = structure_box_lines(desc);
out.extend(relationship_prose_lines(desc));
out.extend(index_lines(desc)); out.extend(index_lines(desc));
out.extend(constraint_lines(desc)); out.extend(constraint_lines(desc));
out out
@@ -118,41 +113,6 @@ fn structure_box_lines(desc: &TableDescription) -> Vec<String> {
out 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 /// Indexes section (ADR-0025), only when the table carries a
/// user-created index. A UNIQUE index is marked `[unique]` (ADR-0035 /// user-created index. A UNIQUE index is marked `[unique]` (ADR-0035
/// §4d). /// §4d).
@@ -1591,11 +1551,23 @@ mod tests {
} }
#[test] #[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 { let desc = TableDescription {
name: "Customers".to_string(), name: "Customers".to_string(),
columns: vec![col("id", Type::Serial, true, false)], 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 { inbound_relationships: vec![RelationshipEnd {
name: "cust_orders".to_string(), name: "cust_orders".to_string(),
other_table: "Orders".to_string(), other_table: "Orders".to_string(),
@@ -1609,15 +1581,14 @@ mod tests {
check_constraints: Vec::new(), check_constraints: Vec::new(),
}; };
let out = render_structure(&desc).join("\n"); let out = render_structure(&desc).join("\n");
assert!( // The structure box still renders.
out.contains("Referenced by:"), assert!(out.contains("Customers"), "structure header:\n{out}");
"expected inbound relationship section:\n{out}", assert!(out.contains("│ id"), "column row:\n{out}");
); // No relationship block in either direction.
assert!( assert!(!out.contains("References:"), "no outbound prose:\n{out}");
out.contains("Orders.cust_id → id"), assert!(!out.contains("Referenced by:"), "no inbound prose:\n{out}");
"expected inbound relationship line:\n{out}", assert!(!out.contains("Orders.cust_id"), "no prose line:\n{out}");
); assert!(!out.contains("Regions"), "no prose line:\n{out}");
assert_snapshot!(out);
} }
#[test] #[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)
+17 -3
View File
@@ -494,7 +494,12 @@ fn add_relationship_flow_shows_parent_side_with_inbound_section() {
} }
#[test] #[test]
fn add_relationship_flow_shows_inbound_section_on_parent() { fn add_column_confirmation_omits_relationship_prose() {
// ADR-0050 (issue #28): an incidental-DDL confirmation echo (here
// `add column`) renders the structure only — never the
// `References:` / `Referenced by:` relationship block — even when
// the table carries relationships the user did not touch. The
// relationships remain one `show table` away.
let mut app = App::new(); let mut app = App::new();
let customers = TableDescription { let customers = TableDescription {
name: "Customers".to_string(), name: "Customers".to_string(),
@@ -535,8 +540,17 @@ fn add_relationship_flow_shows_inbound_section_on_parent() {
echo: None, echo: None,
}); });
let rendered = rendered_text(&mut app, &Theme::dark(), 80, 24); let rendered = rendered_text(&mut app, &Theme::dark(), 80, 24);
assert!(rendered.contains("Referenced by:"), "{rendered}"); // The structure box still renders (table name + the column box from
assert!(rendered.contains("Orders.CustId → Id"), "{rendered}"); // the returned description).
assert!(rendered.contains("Customers"), "structure header:\n{rendered}");
assert!(rendered.contains("Constraints"), "structure box:\n{rendered}");
// The relationship block is gone — neither prose heading nor line.
assert!(!rendered.contains("Referenced by:"), "no prose heading:\n{rendered}");
assert!(!rendered.contains("References:"), "no prose heading:\n{rendered}");
assert!(
!rendered.contains("Orders.CustId → Id"),
"no prose line:\n{rendered}",
);
} }
#[test] #[test]