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:
+34
@@ -252,6 +252,12 @@ pub struct App {
|
||||
/// [`App::input_validity_verdict`] once typing pauses.
|
||||
pub input_indicator: Option<crate::dsl::walker::Severity>,
|
||||
pub tables: Vec<String>,
|
||||
/// All relationships as full schema records, for the sidebar
|
||||
/// relationships panel (ADR-0046 DB2). Refreshed by the runtime
|
||||
/// alongside `tables`. Kept on the App (not `SchemaCache`) because
|
||||
/// only the UI needs the details — the walker/completion need just
|
||||
/// the names, which stay in `SchemaCache::relationships`.
|
||||
pub relationships: Vec<crate::persistence::RelationshipSchema>,
|
||||
/// Last successfully described table, shown in the output
|
||||
/// pane until the next DDL operation.
|
||||
pub current_table: Option<TableDescription>,
|
||||
@@ -449,6 +455,7 @@ impl App {
|
||||
hint: None,
|
||||
input_indicator: None,
|
||||
tables: Vec::new(),
|
||||
relationships: Vec::new(),
|
||||
current_table: None,
|
||||
history: Vec::new(),
|
||||
history_cursor: None,
|
||||
@@ -721,6 +728,11 @@ impl App {
|
||||
self.schema_cache = cache;
|
||||
Vec::new()
|
||||
}
|
||||
AppEvent::RelationshipsRefreshed(relationships) => {
|
||||
trace!(count = relationships.len(), "relationships refreshed");
|
||||
self.relationships = relationships;
|
||||
Vec::new()
|
||||
}
|
||||
AppEvent::PersistenceFatal {
|
||||
operation,
|
||||
path,
|
||||
@@ -5098,6 +5110,28 @@ mod tests {
|
||||
assert_eq!(app.input_cursor, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn relationships_refreshed_event_updates_the_field() {
|
||||
// ADR-0046 DB2: the runtime posts RelationshipsRefreshed; the
|
||||
// App stores it for the sidebar relationships panel to render.
|
||||
use crate::dsl::action::ReferentialAction;
|
||||
let mut app = App::new();
|
||||
assert!(app.relationships.is_empty());
|
||||
app.update(AppEvent::RelationshipsRefreshed(vec![
|
||||
crate::persistence::RelationshipSchema {
|
||||
name: "Customers_Orders".to_string(),
|
||||
parent_table: "Customers".to_string(),
|
||||
parent_columns: vec!["id".to_string()],
|
||||
child_table: "Orders".to_string(),
|
||||
child_columns: vec!["customer_id".to_string()],
|
||||
on_delete: ReferentialAction::Cascade,
|
||||
on_update: ReferentialAction::NoAction,
|
||||
},
|
||||
]));
|
||||
assert_eq!(app.relationships.len(), 1);
|
||||
assert_eq!(app.relationships[0].name, "Customers_Orders");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn input_scroll_offset_resets_when_the_buffer_is_replaced() {
|
||||
// ADR-0046 DA3: the horizontal scroll offset must not leak from
|
||||
|
||||
Reference in New Issue
Block a user