Skip to content

perf(ui): reduce rebuilds and virtualize heavy surfaces #93

Description

@ZhuchkaTriplesix

Context

UI performance review across lib/. Goal: cut unnecessary widget rebuilds and virtualize the heaviest surfaces (result grid, connection tree, code editor). Findings are grouped by severity; each can be tackled incrementally.

Review-only for now — no code changed yet. This issue tracks the implementation.

HIGH

H1. Interface scale rebuilds the whole app on every slider tick

  • lib/app/app.dart — single ListenableBuilder merges themeController + uiScaleController; setScalePreview() fires notifyListeners() per drag step (lib/core/layout/ui_scale_controller.dart:18-23), rebuilding ShadcnApp → MainScreen → tree → SQL workspaces → editors.
  • materialThemeFromQuerya() / lightShadcnTheme / darkShadcnTheme / activeTheme reallocate ThemeData (VS Code color merge + applyTokenColorsToEditor) 3x per frame even though only scale changed.
  • Fix: local ValueNotifier<double> for live preview in the preferences dialog; call commitScale() only on onChangeEnd. Split theme-only vs scale-only builders. Memoize ThemeData/QueryaTheme by (preset, overrides, brightness).

H2. Result grid has no virtualization

  • lib/features/main_screen/results_tab.dart:155-198material.Table + IntrinsicColumnWidth + ...rows.map(... SelectableText ...) materializes all cells (cap = 5000 rows). Intrinsic pass is O(rows×cols).
  • Fix: virtualized grid (ListView.builder + fixed/measured-once column widths), Text + "copy cell" instead of SelectableText, pagination. Wrap body in RepaintBoundary.

H3. Connection tree built eagerly, per-row TextPainter

  • lib/features/connections/connections_panel.dart (SliverToBoxAdapter + Column), connections_panel_pg_tree.dart:957-983 / connections_panel_mysql.dart:447-510 (for loops build all tables/views at once).
  • connections_panel_pg_tree.dart:40-48TextPainter.layout per row per build for overflow detection.
  • Fix: flatten visible nodes → SliverList.builder; replace manual TextPainter with Text(overflow: ellipsis) + Tooltip; add ValueKey on leaf rows.

H4. Syntax highlighting runs synchronously per keystroke

  • lib/core/editor/querya_highlight_controller.darthighlighter.highlight(text) runs inside buildTextSpan for buffers < 8KB on every keystroke; no debounce.
  • lib/core/editor/syntax_highlight_isolate.dart:126-129Future.value(syntaxHighlightInIsolate(job)) runs synchronously on the UI thread (no isolate despite the name).
  • Fix: debounce (50-150ms) + always compute/isolate; reuse previous span while pending.

MEDIUM

  • M1. Splitter drag setState rebuilds both panels/editor/grid — main_screen.dart:255-271, workspace_panel.dart:224-231, postgres_sql_workspace.dart:434-441, mysql_sql_workspace.dart. Fix: ValueNotifier<double> + narrow ValueListenableBuilder.
  • M2. AppSettingsRevision.bump() on any setting write (incl. scale commit) reloads all open SQL workspaces — app_settings.dart:222-228, postgres_sql_workspace.dart:92-154. Fix: granular channels / exclude ui_scale.
  • M3. ConnectionsPanel rebuilds on any MainScreenWorkspaceState change though it only needs activeConnection?.idmain_screen.dart:234-251.
  • M4. Connection forms setState(() {}) per keystroke — postgresql_connection_form.dart:62, mysql_connection_form.dart:62, redis_connection_form.dart:53, mongodb_connection_form.dart:93. Fix: ValueNotifier<bool> formValid around buttons only.
  • M5. Stats polling rebuilds full page — redis_view.dart:130-137, postgres_stats_view.dart:124-130, mongo_stats_view.dart:150-167. Fix: compare snapshot, split sections.
  • M6. Redis key editor builds all hash/list/set members in Column inside SingleChildScrollViewredis_key_editor.dart:495-687.
  • M7. Mongo documents: JsonEncoder.withIndent in build when expanded + hover setState + missing keys — mongo_documents_view.dart:220-237,404-481.
  • M8. MySQL result row→string conversion is synchronous on UI thread; PG already uses compute(convertResultRowsToStrings)mysql_sql_workspace.dart:155-166.
  • M9. Theme/highlighter pair recreated on theme change without caching — syntax_highlight_service.dart, token_colors_highlighter_config.dart, theme_controller.dart:73-79.

LOW

  • L1. RegExp compiled in build — sql_query_history_dialog.dart:72-75, postgres_stats_view.dart:555-557. Hoist to static final.
  • L2. QueryaDropdown.menuChildren rebuilt every build — querya_dropdown.dart:201.
  • L3. Hover setState in list rows (cumulative) — mongo/redis views.
  • L4. TextEditingController not disposed in Redis TTL dialog — redis_key_editor.dart:694-709.

Suggested order

  1. H1 (scale/theme decoupling) — biggest win, smallest change.
  2. H4 (highlight debounce + true isolate).
  3. H2 (result grid virtualization).
  4. H3 (connection tree virtualization).
  5. MEDIUM batch (splitter drag, settings revision, form validation).
  6. LOW cleanups.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions