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:
@@ -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
@@ -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
@@ -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]
|
||||||
|
|||||||
-12
@@ -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)
|
|
||||||
@@ -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]
|
||||||
|
|||||||
Reference in New Issue
Block a user