feat(ui): relationships sidebar panel + schema data (#21, ADR-0046 DB2/DB4)
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.
This commit is contained in:
@@ -211,17 +211,28 @@ holds two stacked panels (DB4) instead of one.
|
||||
|
||||
The panel needs the full `RelationshipSchema` (name, parent/child
|
||||
tables, list-based columns, on-delete/on-update actions) that the `show
|
||||
relationship` path already fetches. **`SchemaCache` is *extended*, not
|
||||
retyped:** its existing `relationships: Vec<String>` is left as-is
|
||||
(`completion.rs` borrows it as `&Vec<String>` via
|
||||
`IdentSource::Relationships` for relationship-name completion, and
|
||||
several test fixtures construct it) and a **new field
|
||||
`relationship_details: Vec<RelationshipSchema>`** is added alongside,
|
||||
populated by the same cache refresh that runs on schema change (the
|
||||
refresh is taught to query relationship detail, which today it does not
|
||||
— it only lists names). Retyping the existing field would break the
|
||||
completion borrow and the fixtures; adding a field is the
|
||||
zero-ripple change.
|
||||
relationship` path already fetches.
|
||||
|
||||
**Data home — `App`, not `SchemaCache` (revised at implementation,
|
||||
2026-06-10).** The design first proposed an additive
|
||||
`SchemaCache.relationship_details: Vec<RelationshipSchema>` field.
|
||||
Implementation revised this to a **parallel `App.relationships:
|
||||
Vec<RelationshipSchema>`** field for two reasons: (1) `SchemaCache` is
|
||||
*walker/completion-facing* — it needs only relationship **names**
|
||||
(unchanged in `SchemaCache.relationships`, still borrowed as
|
||||
`&Vec<String>` by `IdentSource::Relationships`); the full records are
|
||||
**UI-only**, so `App` is the architecturally correct home, mirroring
|
||||
`app.tables` (which the items panel already reads alongside the cache).
|
||||
(2) Adding a field to `SchemaCache` would force edits to ~23 full
|
||||
struct literals across the test suite, whereas `App` gains one field.
|
||||
The /runda guard it answered — *don't break completion by retyping
|
||||
`relationships`* — is fully honoured either way. Delivery: a worker
|
||||
`Request::ReadAllRelationships` (→ `Database::read_all_relationships`,
|
||||
returning `Vec<RelationshipSchema>` via the existing
|
||||
`read_all_relationships(conn)`); the runtime's `refresh_schema_cache`
|
||||
posts a new `AppEvent::RelationshipsRefreshed` alongside
|
||||
`SchemaCacheRefreshed`, and the `App` stores it. No behavioural
|
||||
difference from the original design.
|
||||
|
||||
The panel has **two display states** keyed off focus (DC2):
|
||||
|
||||
@@ -357,10 +368,11 @@ silently edit an invisible buffer.
|
||||
- Per-panel scroll offsets for the Tables and Relationships panels, each
|
||||
clamped against a renderer-reported viewport (DC3), mirroring
|
||||
`output_scroll` / `note_output_viewport`.
|
||||
- `SchemaCache` gains **`relationship_details: Vec<RelationshipSchema>`**
|
||||
(DB2) — *additive*; the existing `relationships: Vec<String>` (names,
|
||||
used by `completion.rs` `IdentSource::Relationships`) is unchanged. The
|
||||
cache refresh is extended to populate the new field.
|
||||
- **`App.relationships: Vec<RelationshipSchema>`** (DB2) — the full
|
||||
relationship records for the sidebar panel, delivered by
|
||||
`AppEvent::RelationshipsRefreshed` from the runtime's schema refresh.
|
||||
`SchemaCache.relationships: Vec<String>` (names, for completion) is
|
||||
unchanged. (See DB2 for why this lives on `App`, not `SchemaCache`.)
|
||||
|
||||
**Render-time derived** (pure functions of `frame.area()` + cached
|
||||
counts — *not* stored fields; this keeps the pure-render invariant and
|
||||
@@ -458,11 +470,11 @@ Phase A:
|
||||
scrolls.
|
||||
|
||||
Phase B:
|
||||
- **Schema-cache enrichment:** after each schema mutation the cache
|
||||
carries full `relationship_details` (name, parent/child, columns,
|
||||
actions) *and* the existing `relationships` names; `completion.rs`
|
||||
`IdentSource::Relationships` still resolves names (the additive field
|
||||
did not disturb it).
|
||||
- **Relationship data path:** `Database::read_all_relationships`
|
||||
returns full records through the worker thread (integration test, real
|
||||
DB via an m:n junction); `AppEvent::RelationshipsRefreshed` populates
|
||||
`App.relationships`; `SchemaCache.relationships` names are undisturbed
|
||||
(completion still resolves them).
|
||||
- **Relationships panel render (Tier-2):** empty → a single `None` line
|
||||
at the 5-row floor; the unfocused narrow format (name + arrow-break,
|
||||
ellipsis past inner width); a compound endpoint pair arrow-breaks
|
||||
|
||||
Reference in New Issue
Block a user