From 4bd0282da65db9f118ef5250582ab67079fad538 Mon Sep 17 00:00:00 2001 From: historia Date: Mon, 31 Aug 2026 17:19:27 -0400 Subject: fix: truncate excess spaces on generate audiobooks tui --- app/tests/test_hub.py | 9 ++++++++- app/tests/test_tui.py | 26 ++++++++++++++++++++++++++ app/ui/hub.py | 7 ++++++- app/ui/tui.py | 12 ++++++++++++ 4 files changed, 52 insertions(+), 2 deletions(-) diff --git a/app/tests/test_hub.py b/app/tests/test_hub.py index a822f1a..ef246ad 100644 --- a/app/tests/test_hub.py +++ b/app/tests/test_hub.py @@ -967,8 +967,12 @@ class ConvertFlowTests(unittest.TestCase): # The model menu was fed from the live query (label, id); ids are # padded so the capability columns line up across entries. A mixed # tts+clone family reads as the "tts" and "clone" columns. - self.assertEqual(self._field("model_id")["choices"], + model_field = self._field("model_id") + self.assertEqual(model_field["choices"], [("higgs tts clone", "higgs")]) + # The padded table lives in the pick menu only: the form row + # collapses its column padding back to the gutter. + self.assertTrue(model_field["compact_label"]) def test_model_menu_lines_the_type_column_up(self): # Ids are padded to the widest id, and every capability word sits @@ -1816,6 +1820,9 @@ class ConvertFlowTests(unittest.TestCase): ("VoiceDesign".ljust(11) + " (design)", "design")]) self.assertEqual({label.index("(") for label, _ in mode_field["choices"]}, {13}) + # The padded table lives in the pick menu only: the form row + # collapses its column padding back to the gutter. + self.assertTrue(mode_field["compact_label"]) speaker_field = self._field("speaker") clone_dir_field = self._field("clone_dir") # The .wav directory browser alerts on the .wavs it lists. diff --git a/app/tests/test_tui.py b/app/tests/test_tui.py index 085a1f1..e3383cb 100644 --- a/app/tests/test_tui.py +++ b/app/tests/test_tui.py @@ -631,6 +631,32 @@ class FormTests(TuiTestCase): self.assertNotIn(" qwen", painted) self.assertNotIn(" clone", painted) + def test_compact_label_collapses_padded_choice_on_the_row(self): + # A choice field may opt into a compact row display: the pick + # menu keeps the padded table label (aligned capability columns), + # while the painted row collapses its runs of column padding back + # to the two-space gutter. Fields without the flag paint the + # label verbatim. + padded = "supertonic".ljust(22) + " tts" + fields = [ + {"key": "model", "label": "Model", "kind": "choice", + "value": "supertonic", + "choices": [(padded, "supertonic"), + ("a-much-longer-model-id".ljust(22) + " tts", + "a-much-longer-model-id")], + "compact_label": True}, + {"key": "plain", "label": "Plain", "kind": "choice", + "value": "a", "choices": [("a b", "a")]}, + ] + screen = FakeScreen(keys=[27]) + marker = object() + self.assertIs(tui.form(screen, "Form", fields, back_value=marker), + marker) + painted = [text for _, _, text, _ in screen.strings] + self.assertIn(" supertonic tts", painted) + self.assertNotIn(padded, painted) + self.assertIn(" a b", painted) + def test_text_field_edits_then_saves(self): # Down to the text row, Enter opens the editor, type 'x', Enter, # then Tab -> Save, Enter. diff --git a/app/ui/hub.py b/app/ui/hub.py index abb375a..de5701f 100644 --- a/app/ui/hub.py +++ b/app/ui/hub.py @@ -1362,6 +1362,9 @@ def _audiocpp_fields(stdscr, api_url: Optional[str] = None, {"key": prefix + "model_id", "label": "Model", "kind": "choice", "value": default_model, "choices": [(_label(m), m.get("id")) for m in models], + # The pick menu shows the padded capability table; the form row + # collapses its column padding back to the two-space gutter. + "compact_label": True, "on_change": reset_voice}, # The label tracks the entry's capability: a built-in speaker on # CustomVoice, otherwise the name of a server-side voice to clone. @@ -1513,7 +1516,9 @@ def _qwen_fields(remote_modes: Optional[list] = None, initial_clone = str(initial_wavs[0]) if initial_wavs else "" fields = [ {"key": prefix + "mode", "label": "Model", "kind": "choice", - "value": default_mode, "choices": model_choices}, + "value": default_mode, "choices": model_choices, + # Same as the audio.cpp picker: padded menu table, compact row. + "compact_label": True}, {"key": prefix + "speaker", "label": "Speaker", "kind": "choice", "value": default_speaker, "choices": speakers, "visible": lambda fs: _field_value(fs, prefix + "mode") == "custom"}, diff --git a/app/ui/tui.py b/app/ui/tui.py index a4854b0..1e95353 100644 --- a/app/ui/tui.py +++ b/app/ui/tui.py @@ -1035,6 +1035,11 @@ def form(scr, title: str, fields: Sequence[dict], keep their value across hide/show. A field may set ``on_change`` to a callable of the field list, invoked whenever its value changes so dependent fields (choices, visibility, defaults) can be recomputed. + A ``choice`` field may set ``compact_label`` to show its picked + label on the form row with runs of spaces collapsed back to the + two-space gutter — the pick menu always shows the full label, so a + padded table (aligned capability columns) reads aligned there and + compact on the row. A choice field whose resolved list is empty cannot be opened: Enter is a no-op, or flashes the field's optional ``on_empty_choices`` message (string or callable of the field list) — an explanation the @@ -1130,6 +1135,13 @@ def form(scr, title: str, fields: Sequence[dict], and len(choices[0]) == 2: for label, value in choices: if value == field["value"]: + if field.get("compact_label"): + # The pick menu keeps the full padded label + # (e.g. the model picker's aligned capability + # table); the form row collapses its runs of + # column padding back to the two-space gutter + # so a short pick reads without a far gap. + return re.sub(" {2,}", " ", label) return label return str(field["value"]) -- cgit v1.2.3