The left column now stacks a Tables panel over a Relationships panel.
Each relationship renders as three narrow lines — its name, then the
endpoints broken at the arrow (Customers.id -> / indented
Orders.customer_id) — ellipsized past the inner width. The panel is
content-sized within [5 rows ("(none)" when empty), half the column];
the Tables panel keeps the rest (>=3 rows). Phase C adds focus+scroll
for content beyond the cap (clipped for now).
Data path: a new worker Request::ReadAllRelationships +
Database::read_all_relationships returns full RelationshipSchema
records; the runtime posts them via a RelationshipsRefreshed event
alongside the schema-cache refresh, and the App holds them in a new
`relationships` field.
ADR deviation (recorded in ADR-0046 DB2 + index): DB2 specified this
data on SchemaCache; it lives on the App instead — SchemaCache is
walker/completion-facing and needs only relationship names (untouched),
while the full records are UI-only, so App is the cleaner home and it
avoids editing ~23 SchemaCache literals. No behavioural difference.
Tests: panel-height bounds, the three-line render, the empty "(none)"
case, a snapshot, read_all_relationships end-to-end (real DB via the
m:n junction), and the event->field handler.
`create m:n relationship from <T1> to <T2> [as <name>]` generates a
junction table with one FK column per parent PK column ({table}_{pkcol},
typed via fk_target_type), a compound PK over them, and two CASCADE 1:n
relationships -- all in one do_create_table call = one undo step.
Auto-named {T1}_{T2} (optional `as`), both modes, compound-parent PKs
supported (ADR-0043). Self-referential m:n / PK-less parent / internal
junction name / name collision all refused.
Wired across every surface: grammar (separate CREATE_M2N node), worker
executor, runtime dispatch, completion ("m:n" composite), hints,
highlighting, help + usage catalog + disambiguator, and the advanced-mode
DSL->SQL teaching echo (render_create_m2n, round-trips as valid SQL).
Generalized/fixed framework assumptions the build + two /runda passes
surfaced (all behaviour-preserving for existing commands):
- simple-mode dispatch committed simple.first() unconditionally -> tries
candidates, so `create table` no longer shadows `create m:n`.
- the completion continuation-merge was advanced-only -> runs in simple
mode too when an entry word has >1 DSL form (gated simple_count>1).
- do_create_table now rejects internal `__rdbms_*` names (closes a
pre-existing hole on the DSL create-table path too, not just m:n).
- usage disambiguator now recognizes the `m:n` opener.
Tests: 14 integration (tests/it/m2n.rs), 7 typing-surface matrix, echo /
highlight / usage / internal-name units. Closes C4.
2237 pass / 0 fail / 1 ignored. Clippy clean.