From 26c4f9052bb72c86509659e8548e619aa34648b2 Mon Sep 17 00:00:00 2001 From: "devin-ai-integration[bot]" <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 7 Oct 2026 17:10:35 -0700 Subject: [PATCH] fix(settings): place test dictation output above its controls (#904) Reveal the transcription readout at the top of the sidebar footer so it grows into the empty sidebar space above the Idle row instead of pushing the Test Dictation button down when it appears. Fixes #677 Co-authored-by: Jatin K Malik --- src/vocalinux/ui/settings_dialog.py | 53 +++++++++++++++-------------- tests/test_settings_dialog.py | 10 ++++++ 2 files changed, 37 insertions(+), 26 deletions(-) diff --git a/src/vocalinux/ui/settings_dialog.py b/src/vocalinux/ui/settings_dialog.py index 998247c..093e0c2 100644 --- a/src/vocalinux/ui/settings_dialog.py +++ b/src/vocalinux/ui/settings_dialog.py @@ -5844,12 +5844,13 @@ class SettingsDialog(Gtk.Dialog): self._report_shortcut_apply_result(display_name, applied) def _build_sidebar_footer(self, sidebar_box: Gtk.Box): - """Build the sidebar footer: dictation status, test action, and Close. + """Build the sidebar footer: test output, status, test action, and Close. Always visible regardless of the selected page: recognition state, live microphone level, a dictation test button, and the dialog's - in-window Close button. Test output is revealed inline, so any - instant-applied change can be verified immediately. + in-window Close button. Test output leads the footer so the revealed + box grows into the empty sidebar space above the controls instead of + pushing them down (#677). """ separator = Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL) separator.set_margin_start(8) @@ -5859,6 +5860,29 @@ class SettingsDialog(Gtk.Dialog): footer = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=8) footer.get_style_context().add_class("sidebar-footer") + # Test transcription output, revealed while testing + self.test_output_revealer = Gtk.Revealer() + self.test_output_revealer.set_transition_type(Gtk.RevealerTransitionType.SLIDE_DOWN) + + scrolled_window = Gtk.ScrolledWindow() + scrolled_window.set_min_content_height(60) + scrolled_window.set_max_content_height(100) + scrolled_window.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) + # GTK3 default IN shadow draws a left etched stripe on GNOME/Wayland (#847). + scrolled_window.set_shadow_type(Gtk.ShadowType.NONE) + scrolled_window.get_style_context().add_class("test-area") + + self.test_textview = Gtk.TextView() + self.test_textview.set_editable(False) + self.test_textview.set_cursor_visible(False) + self.test_textview.set_wrap_mode(Gtk.WrapMode.WORD) + self.test_textview.get_style_context().add_class("test-textview") + self.test_buffer = self.test_textview.get_buffer() + scrolled_window.add(self.test_textview) + self.test_output_revealer.add(scrolled_window) + + footer.pack_start(self.test_output_revealer, False, False, 0) + status_row = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8) self.recognition_indicator = Gtk.Image.new_from_icon_name( @@ -5896,29 +5920,6 @@ class SettingsDialog(Gtk.Dialog): self.audio_test_status = self.progress_info_label footer.pack_start(self.progress_info_label, False, False, 0) - # Test transcription output, revealed while testing - self.test_output_revealer = Gtk.Revealer() - self.test_output_revealer.set_transition_type(Gtk.RevealerTransitionType.SLIDE_DOWN) - - scrolled_window = Gtk.ScrolledWindow() - scrolled_window.set_min_content_height(60) - scrolled_window.set_max_content_height(100) - scrolled_window.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) - # GTK3 default IN shadow draws a left etched stripe on GNOME/Wayland (#847). - scrolled_window.set_shadow_type(Gtk.ShadowType.NONE) - scrolled_window.get_style_context().add_class("test-area") - - self.test_textview = Gtk.TextView() - self.test_textview.set_editable(False) - self.test_textview.set_cursor_visible(False) - self.test_textview.set_wrap_mode(Gtk.WrapMode.WORD) - self.test_textview.get_style_context().add_class("test-textview") - self.test_buffer = self.test_textview.get_buffer() - scrolled_window.add(self.test_textview) - self.test_output_revealer.add(scrolled_window) - - footer.pack_start(self.test_output_revealer, False, False, 0) - # Separate Close from the dictation-test controls so it reads as # dialog chrome, not as part of Test Dictation (#651). close_separator = Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL) diff --git a/tests/test_settings_dialog.py b/tests/test_settings_dialog.py index b1e4a62..6fef6dc 100644 --- a/tests/test_settings_dialog.py +++ b/tests/test_settings_dialog.py @@ -1226,6 +1226,16 @@ class TestSettingsNavigation(unittest.TestCase): self.assertIn("Gtk.Separator", before_close) self.assertIn("footer.pack_start(close_separator", before_close) + def test_dictation_output_box_leads_footer(self) -> None: + """The transcription box packs before the status row and Test button so + it reveals into the empty sidebar space above the controls (#677).""" + footer_body = self.source_code.split("def _build_sidebar_footer")[1].split("\n def ")[0] + box_pack = footer_body.index("footer.pack_start(self.test_output_revealer") + status_pack = footer_body.index("footer.pack_start(status_row") + button_pack = footer_body.index("footer.pack_start(self.test_button") + self.assertLess(box_pack, status_pack) + self.assertLess(status_pack, button_pack) + def test_test_dictation_scrolled_window_has_no_shadow(self) -> None: """GTK3 ScrolledWindow default shadow is a left stripe on GNOME/Wayland (#847).""" footer_body = self.source_code.split("def _build_sidebar_footer")[1].split("\n def ")[0]