Skip to content

Commit f291899

Browse files
datlechinclaude
andcommitted
audit: M+L tier polish (DDL UI batch 5-7)
Eight smaller items from docs/audit-2026-04-29-app-deep.md: M-1: Off-grid margins snapped to 6/12/18 on the 6px GNOME grid: - structure_tab column / index / FK row vertical padding 2 → 6 (three places, matches the rest of the boxed-list rows) - history_dialog pinned + all-queries heading bottom 4 → 6 - sidebar schema-header top 8 / bottom 4 → 12 / 6 (anchors the schema label and the "+" button on grid). M-5: WelcomeView's hard `set_size_request(560, -1)` replaced with ScrolledWindow `max_content_width(560) + propagate_natural_width`. Narrow viewports now collapse the welcome content gracefully instead of pushing past the window frame. M-12: on_schema_changed actually uses the (schema, table) params. Iterates open Browse tabs and dispatches FetchBrowseColumns + FetchBrowsePage + FetchBrowseRowCount for every match. Previously the params were silently dropped — Browse tabs pointing at a table that just got altered showed stale columns until a manual F5. Now they refresh automatically alongside the sidebar. L-1: editor_tab_tooltip switched from `q.chars().count()` (O(n) on the full query) to a `char_indices().take(200)` byte-prefix walk. For multi-megabyte SQL dumps we no longer scan past the 200-char preview boundary just to compute the truncate-vs-don't decision. L-5: SQL Preview SourceView in the Structure tab now connects to AdwStyleManager::dark-notify and applies "Adwaita" / "Adwaita-dark" scheme dynamically — mirroring editor.rs. Previously the scheme was frozen at the boot theme even after the user toggled system dark mode. Skipped as N/A: - M-2 (sidebar row double-padding) — `.navigation-sidebar` row styling is theme-dependent; the inner 6px isn't necessarily doubled. - M-4 (status page icon convention) — Browse loading uses spinner child, Structure loading uses synchronizing icon. Different affordances for different states; intentional. - M-15 (Test button placement) — current placement matches GNOME Connections per existing inline comment. - L-6 (keyboard menu set_pointing_to) — already correct via the present_row_menu(None) path landed in the lazy-popover commit. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent daa43bf commit f291899

6 files changed

Lines changed: 78 additions & 20 deletions

File tree

linux/crates/app/src/ui/app/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -911,8 +911,8 @@ impl SimpleComponent for App {
911911
let header_box = gtk::Box::builder()
912912
.orientation(gtk::Orientation::Horizontal)
913913
.spacing(6)
914-
.margin_top(8)
915-
.margin_bottom(4)
914+
.margin_top(12)
915+
.margin_bottom(6)
916916
.margin_start(12)
917917
.margin_end(6)
918918
.build();

linux/crates/app/src/ui/app/structure.rs

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -417,13 +417,36 @@ impl App {
417417
}
418418

419419
/// Schema state changed somewhere — reload the table list, then
420-
/// refetch any open Browse tab pointing at the affected table.
420+
/// refetch any open Browse tab pointing at the affected table so
421+
/// its grid reflects post-DDL schema (column adds / drops / type
422+
/// changes / renames).
421423
pub(super) fn on_schema_changed(
422424
&self,
423-
_schema: Option<String>,
424-
_table: Option<String>,
425+
schema: Option<String>,
426+
table: Option<String>,
425427
sender: ComponentSender<Self>,
426428
) {
429+
// Refetch the affected Browse tab(s) immediately. Tab-id
430+
// collection happens under a short-lived borrow; the sender
431+
// dispatches happen after drop so the input handlers can
432+
// re-borrow workspace_tabs without panic.
433+
if let Some(table_name) = table.as_deref() {
434+
let mut affected: Vec<Uuid> = Vec::new();
435+
for (id, tab) in self.workspace_tabs.borrow().iter() {
436+
if let WorkspaceTab::Browse(slot) = tab
437+
&& slot.schema.as_deref() == schema.as_deref()
438+
&& slot.table == table_name
439+
{
440+
affected.push(*id);
441+
}
442+
}
443+
for id in affected {
444+
sender.input(AppMsg::FetchBrowseColumns(id));
445+
sender.input(AppMsg::FetchBrowsePage(id));
446+
sender.input(AppMsg::FetchBrowseRowCount(id));
447+
}
448+
}
449+
// Sidebar refresh: re-list tables and rebuild the factory.
427450
let sender_for_cmd = sender.clone();
428451
sender.command(move |_, shutdown| {
429452
shutdown

linux/crates/app/src/ui/app/workspace_tabs.rs

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1038,11 +1038,20 @@ fn editor_tab_tooltip(query: &str, label: &str) -> Option<String> {
10381038
if q.is_empty() {
10391039
return None;
10401040
}
1041-
let preview: String = q.chars().take(200).collect();
1042-
let preview = if q.chars().count() > 200 {
1043-
format!("{preview}…")
1044-
} else {
1045-
preview
1046-
};
1041+
// Use char_indices().nth(200) to walk only the first 201 chars
1042+
// instead of materialising the whole query as a Vec<char> via
1043+
// chars().count(). For a multi-megabyte SQL dump this avoids an
1044+
// O(n) scan when only a 200-char preview matters.
1045+
let mut iter = q.char_indices();
1046+
let mut last_idx = 0;
1047+
for _ in 0..200 {
1048+
match iter.next() {
1049+
Some((i, c)) => last_idx = i + c.len_utf8(),
1050+
None => {
1051+
return if q == label { None } else { Some(q.to_string()) };
1052+
}
1053+
}
1054+
}
1055+
let preview = format!("{}…", &q[..last_idx]);
10471056
if preview == label { None } else { Some(preview) }
10481057
}

linux/crates/app/src/ui/history_dialog.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ impl Component for HistoryDialog {
271271
.margin_top(12)
272272
.margin_start(12)
273273
.margin_end(12)
274-
.margin_bottom(4)
274+
.margin_bottom(6)
275275
.visible(false)
276276
.build();
277277
pinned_heading.add_css_class("heading");
@@ -291,7 +291,7 @@ impl Component for HistoryDialog {
291291
.margin_top(12)
292292
.margin_start(12)
293293
.margin_end(12)
294-
.margin_bottom(4)
294+
.margin_bottom(6)
295295
.visible(false)
296296
.build();
297297
list_heading.add_css_class("heading");

linux/crates/app/src/ui/structure_tab.rs

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ use std::rc::Rc;
2626

2727
use relm4::adw::prelude::*;
2828
use relm4::{ComponentParts, ComponentSender, SimpleComponent, adw, gtk};
29+
use sourceview5::prelude::BufferExt;
2930

3031
use tablepro_core::sql_ddl::{BuildDdlError, DraftColumn};
3132
use tablepro_core::{ColumnInfo, ForeignKeyInfo, IndexInfo};
@@ -386,8 +387,8 @@ fn build_column_row(
386387
let row = gtk::Box::builder()
387388
.orientation(gtk::Orientation::Horizontal)
388389
.spacing(6)
389-
.margin_top(2)
390-
.margin_bottom(2)
390+
.margin_top(6)
391+
.margin_bottom(6)
391392
.margin_start(12)
392393
.margin_end(12)
393394
.build();
@@ -525,8 +526,8 @@ fn build_index_row(index: usize, idx: &IndexInfo, sender: ComponentSender<Struct
525526
let row = gtk::Box::builder()
526527
.orientation(gtk::Orientation::Horizontal)
527528
.spacing(6)
528-
.margin_top(2)
529-
.margin_bottom(2)
529+
.margin_top(6)
530+
.margin_bottom(6)
530531
.margin_start(12)
531532
.margin_end(12)
532533
.build();
@@ -593,8 +594,8 @@ fn build_fk_row(
593594
let row = gtk::Box::builder()
594595
.orientation(gtk::Orientation::Horizontal)
595596
.spacing(6)
596-
.margin_top(2)
597-
.margin_bottom(2)
597+
.margin_top(6)
598+
.margin_bottom(6)
598599
.margin_start(12)
599600
.margin_end(12)
600601
.build();
@@ -980,6 +981,14 @@ impl SimpleComponent for StructureTab {
980981
sql_view.set_left_margin(6);
981982
sql_view.set_right_margin(6);
982983
sql_view.set_bottom_margin(6);
984+
// Match the system light / dark scheme. Mirrors the editor.rs
985+
// hook so the SQL preview's syntax colours track the user's
986+
// theme choice instead of staying frozen on the boot scheme.
987+
apply_sql_scheme(&sql_buffer);
988+
let buffer_for_theme = sql_buffer.clone();
989+
adw::StyleManager::default().connect_dark_notify(move |_| {
990+
apply_sql_scheme(&buffer_for_theme);
991+
});
983992
let sql_scroll = gtk::ScrolledWindow::builder().child(&sql_view).vexpand(true).build();
984993
let sql_page = view_stack.add_titled_with_icon(
985994
&sql_scroll,
@@ -1607,6 +1616,20 @@ fn combo_entry(combo: &gtk::ComboBoxText) -> Option<gtk::Entry> {
16071616
combo.child().and_then(|c| c.dynamic_cast::<gtk::Entry>().ok())
16081617
}
16091618

1619+
/// Pick the sourceview5 style scheme matching the active Adwaita
1620+
/// light / dark mode. Called on init and on `connect_dark_notify`
1621+
/// so the SQL preview tracks system theme switches.
1622+
fn apply_sql_scheme(buffer: &sourceview5::Buffer) {
1623+
let scheme_name = if adw::StyleManager::default().is_dark() {
1624+
"Adwaita-dark"
1625+
} else {
1626+
"Adwaita"
1627+
};
1628+
if let Some(scheme) = sourceview5::StyleSchemeManager::default().scheme(scheme_name) {
1629+
buffer.set_style_scheme(Some(&scheme));
1630+
}
1631+
}
1632+
16101633
fn driver_display_name(driver_id: &str) -> &'static str {
16111634
match driver_id {
16121635
"postgres" => "PostgreSQL",

linux/crates/app/src/ui/welcome_view.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,10 @@ impl SimpleComponent for WelcomeView {
8080
.hexpand(true)
8181
.vexpand(true)
8282
.hscrollbar_policy(gtk::PolicyType::Never)
83+
// Cap the scrollable content's natural width at 560px so
84+
// narrow viewports don't push past the window edge.
85+
.max_content_width(560)
86+
.propagate_natural_width(true)
8387
.build();
8488
let outer = gtk::Box::builder()
8589
.orientation(gtk::Orientation::Vertical)
@@ -91,7 +95,6 @@ impl SimpleComponent for WelcomeView {
9195
.halign(gtk::Align::Center)
9296
.valign(gtk::Align::Center)
9397
.build();
94-
outer.set_size_request(560, -1);
9598

9699
// Single CTA on the populated page: the "+" button in the
97100
// group header. Previously we also rendered a bottom pill

0 commit comments

Comments
 (0)