From 9daab53d081e97693e0ac13b8dae1456b73bfd54 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Thu, 12 Nov 2026 23:22:08 -0500 Subject: [PATCH 1/2] Let the editing-mode label observe its original editor --- .verification/editing-mode-label/author.py | 40 ++++++++++ .../editing-mode-label/fix.plan.json | 75 +++++++++++++++++++ src/toad/widgets/prompt.py | 28 ++++--- 3 files changed, 132 insertions(+), 11 deletions(-) create mode 100644 .verification/editing-mode-label/author.py create mode 100644 .verification/editing-mode-label/fix.plan.json diff --git a/.verification/editing-mode-label/author.py b/.verification/editing-mode-label/author.py new file mode 100644 index 000000000..0af5d750a --- /dev/null +++ b/.verification/editing-mode-label/author.py @@ -0,0 +1,40 @@ +import json +from pathlib import Path +r=Path.cwd();f=str(r/'src/toad/widgets/prompt.py');ops=[] +def patch(t,o,n):ops.append(dict(operation='patch_target',file_path=f,target_qualname=t,replacements=[dict(old_source=o,new_source=n)])) +patch('EditingModeLabel','''class EditingModeLabel(AutoWidth, OneRow, SidePadding, NoWrap, Label): + def apply_native_layout''','''class EditingModeLabel(AutoWidth, OneRow, SidePadding, NoWrap, Label): + """The label follows its own editor throughout this composition's lifetime.""" + + def __init__(self, editor: PromptTextArea): + super().__init__("", markup=False) + self.editor = editor + + @on(events.Mount) + def follow_editor(self) -> None: + self.watch(self.editor, "editing_mode", self.update_mode) + self.watch(self.editor, "has_focus", self.update_mode) + + def update_mode(self) -> None: + self.update((self.editor.editing_mode if self.editor.has_focus else Standard()).label) + + def apply_native_layout''') +patch('PromptTextArea.on_mount',''' self.call_after_refresh(lambda: self.show_editing_mode(self.editing_mode))\n''','') +patch('PromptTextArea',''' def show_editing_mode(self, mode: EditingMode) -> None: + """Away from the box the Standard keys are in force, so the label says STANDARD.""" + if self.is_mounted: + self.prompt.editing_mode_label.update((mode if self.has_focus else Standard()).label) + + def watch_has_focus(self, focus: bool) -> None: + self.show_editing_mode(self.editing_mode) + +''','') +patch('Prompt.compose',''' yield self.TEXT_AREA_CLASS(prompt=self).data_bind(''',''' editor = self.TEXT_AREA_CLASS(prompt=self).data_bind(''') +patch('Prompt.compose',''' slash_commands=Prompt.slash_commands, + ) + yield from self.compose_info_popups()''',''' slash_commands=Prompt.slash_commands, + ) + yield editor + yield from self.compose_info_popups()''') +patch('Prompt.compose',''' yield EditingModeLabel("", markup=False)''',''' yield EditingModeLabel(editor)''') +Path('.verification/editing-mode-label/fix.plan.json').write_text(json.dumps({'recipes':[{'recipe_id':'label-observes-original-editor-without-sibling-mount-queries','operations':ops}]},indent=2)) diff --git a/.verification/editing-mode-label/fix.plan.json b/.verification/editing-mode-label/fix.plan.json new file mode 100644 index 000000000..0dce1ab38 --- /dev/null +++ b/.verification/editing-mode-label/fix.plan.json @@ -0,0 +1,75 @@ +{ + "recipes": [ + { + "recipe_id": "label-observes-original-editor-without-sibling-mount-queries", + "operations": [ + { + "operation": "patch_target", + "file_path": "/home/ts/wt/codex-live--P181--toad/src/toad/widgets/prompt.py", + "target_qualname": "EditingModeLabel", + "replacements": [ + { + "old_source": "class EditingModeLabel(AutoWidth, OneRow, SidePadding, NoWrap, Label):\n def apply_native_layout", + "new_source": "class EditingModeLabel(AutoWidth, OneRow, SidePadding, NoWrap, Label):\n \"\"\"The label follows its own editor throughout this composition's lifetime.\"\"\"\n\n def __init__(self, editor: PromptTextArea):\n super().__init__(\"\", markup=False)\n self.editor = editor\n\n @on(events.Mount)\n def follow_editor(self) -> None:\n self.watch(self.editor, \"editing_mode\", self.update_mode)\n self.watch(self.editor, \"has_focus\", self.update_mode)\n\n def update_mode(self) -> None:\n self.update((self.editor.editing_mode if self.editor.has_focus else Standard()).label)\n\n def apply_native_layout" + } + ] + }, + { + "operation": "patch_target", + "file_path": "/home/ts/wt/codex-live--P181--toad/src/toad/widgets/prompt.py", + "target_qualname": "PromptTextArea.on_mount", + "replacements": [ + { + "old_source": " self.call_after_refresh(lambda: self.show_editing_mode(self.editing_mode))\n", + "new_source": "" + } + ] + }, + { + "operation": "patch_target", + "file_path": "/home/ts/wt/codex-live--P181--toad/src/toad/widgets/prompt.py", + "target_qualname": "PromptTextArea", + "replacements": [ + { + "old_source": " def show_editing_mode(self, mode: EditingMode) -> None:\n \"\"\"Away from the box the Standard keys are in force, so the label says STANDARD.\"\"\"\n if self.is_mounted:\n self.prompt.editing_mode_label.update((mode if self.has_focus else Standard()).label)\n\n def watch_has_focus(self, focus: bool) -> None:\n self.show_editing_mode(self.editing_mode)\n\n", + "new_source": "" + } + ] + }, + { + "operation": "patch_target", + "file_path": "/home/ts/wt/codex-live--P181--toad/src/toad/widgets/prompt.py", + "target_qualname": "Prompt.compose", + "replacements": [ + { + "old_source": " yield self.TEXT_AREA_CLASS(prompt=self).data_bind(", + "new_source": " editor = self.TEXT_AREA_CLASS(prompt=self).data_bind(" + } + ] + }, + { + "operation": "patch_target", + "file_path": "/home/ts/wt/codex-live--P181--toad/src/toad/widgets/prompt.py", + "target_qualname": "Prompt.compose", + "replacements": [ + { + "old_source": " slash_commands=Prompt.slash_commands,\n )\n yield from self.compose_info_popups()", + "new_source": " slash_commands=Prompt.slash_commands,\n )\n yield editor\n yield from self.compose_info_popups()" + } + ] + }, + { + "operation": "patch_target", + "file_path": "/home/ts/wt/codex-live--P181--toad/src/toad/widgets/prompt.py", + "target_qualname": "Prompt.compose", + "replacements": [ + { + "old_source": " yield EditingModeLabel(\"\", markup=False)", + "new_source": " yield EditingModeLabel(editor)" + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/src/toad/widgets/prompt.py b/src/toad/widgets/prompt.py index c8fb4477b..1c118ba22 100644 --- a/src/toad/widgets/prompt.py +++ b/src/toad/widgets/prompt.py @@ -262,6 +262,20 @@ def apply_native_layout(self) -> None: class EditingModeLabel(AutoWidth, OneRow, SidePadding, NoWrap, Label): + """The label follows its own editor throughout this composition's lifetime.""" + + def __init__(self, editor: PromptTextArea): + super().__init__("", markup=False) + self.editor = editor + + @on(events.Mount) + def follow_editor(self) -> None: + self.watch(self.editor, "editing_mode", self.update_mode) + self.watch(self.editor, "has_focus", self.update_mode) + + def update_mode(self) -> None: + self.update((self.editor.editing_mode if self.editor.has_focus else Standard()).label) + def apply_native_layout(self) -> None: super().apply_native_layout() self.styles.dock = "right" @@ -491,15 +505,6 @@ def on_mount(self) -> None: self.hide_suggestion_on_blur = False self.editing_mode.attach(self) self.apply_keyboard_profile() - self.call_after_refresh(lambda: self.show_editing_mode(self.editing_mode)) - - def show_editing_mode(self, mode: EditingMode) -> None: - """Away from the box the Standard keys are in force, so the label says STANDARD.""" - if self.is_mounted: - self.prompt.editing_mode_label.update((mode if self.has_focus else Standard()).label) - - def watch_has_focus(self, focus: bool) -> None: - self.show_editing_mode(self.editing_mode) def apply_keyboard_profile(self) -> None: self.call_later(self._apply_keyboard_profile) @@ -1082,15 +1087,16 @@ def compose(self) -> ComposeResult: yield PromptQuestion() with TextPrompt(): yield PromptLabel(markup=False) - yield self.TEXT_AREA_CLASS(prompt=self).data_bind( + editor = self.TEXT_AREA_CLASS(prompt=self).data_bind( shell_mode=Prompt.shell_mode, project_path=Prompt.project_path, working_directory=Prompt.working_directory, slash_commands=Prompt.slash_commands, ) + yield editor yield from self.compose_info_popups() with InfoBar(): - yield EditingModeLabel("", markup=False) + yield EditingModeLabel(editor) yield from self.compose_agent_info() yield StatusLine(markup=False).data_bind(status=Prompt.status) yield from self.compose_mode_controls() From bbcd60bd771fe92047f7c2a34b19e6eb31afb58d Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Thu, 12 Nov 2026 23:27:12 -0500 Subject: [PATCH 2/2] Keep each label with the editor that supplies all mode updates --- .../editing-mode-label/owned-label.plan.json | 59 +++++++++++++++++++ .../editing-mode-label/owned_label.py | 36 +++++++++++ src/toad/widgets/prompt.py | 26 ++++---- 3 files changed, 106 insertions(+), 15 deletions(-) create mode 100644 .verification/editing-mode-label/owned-label.plan.json create mode 100644 .verification/editing-mode-label/owned_label.py diff --git a/.verification/editing-mode-label/owned-label.plan.json b/.verification/editing-mode-label/owned-label.plan.json new file mode 100644 index 000000000..56d8b08cb --- /dev/null +++ b/.verification/editing-mode-label/owned-label.plan.json @@ -0,0 +1,59 @@ +{ + "recipes": [ + { + "recipe_id": "retain-the-original-editor-label-through-mount-and-mode-updates", + "operations": [ + { + "operation": "patch_target", + "file_path": "/home/ts/wt/codex-live--P181--toad/src/toad/widgets/prompt.py", + "target_qualname": "EditingModeLabel", + "replacements": [ + { + "old_source": " \"\"\"The label follows its own editor throughout this composition's lifetime.\"\"\"\n\n def __init__(self, editor: PromptTextArea):\n super().__init__(\"\", markup=False)\n self.editor = editor\n\n @on(events.Mount)\n def follow_editor(self) -> None:\n self.watch(self.editor, \"editing_mode\", self.update_mode)\n self.watch(self.editor, \"has_focus\", self.update_mode)\n\n def update_mode(self) -> None:\n self.update((self.editor.editing_mode if self.editor.has_focus else Standard()).label)\n\n", + "new_source": "" + } + ] + }, + { + "operation": "patch_target", + "file_path": "/home/ts/wt/codex-live--P181--toad/src/toad/widgets/prompt.py", + "target_qualname": "PromptTextArea.__init__", + "replacements": [ + { + "old_source": " self.prompt = prompt\n", + "new_source": " self.prompt = prompt\n self.editing_mode_label = EditingModeLabel(\"\", markup=False)\n" + } + ] + }, + { + "operation": "patch_target", + "file_path": "/home/ts/wt/codex-live--P181--toad/src/toad/widgets/prompt.py", + "target_qualname": "PromptTextArea.on_mount", + "replacements": [ + { + "old_source": " self.apply_keyboard_profile()\n", + "new_source": " self.apply_keyboard_profile()\n self.call_after_refresh(lambda: self.show_editing_mode(self.editing_mode))\n" + } + ] + }, + { + "operation": "insert_before_target", + "file_path": "/home/ts/wt/codex-live--P181--toad/src/toad/widgets/prompt.py", + "target_qualname": "PromptTextArea.apply_keyboard_profile", + "source": " def show_editing_mode(self, mode: EditingMode) -> None:\n \"\"\"The editor owns its label before either widget is mounted.\"\"\"\n if self.is_mounted:\n self.editing_mode_label.update((mode if self.has_focus else Standard()).label)\n\n def watch_has_focus(self, focus: bool) -> None:\n self.show_editing_mode(self.editing_mode)\n\n" + }, + { + "operation": "patch_target", + "file_path": "/home/ts/wt/codex-live--P181--toad/src/toad/widgets/prompt.py", + "target_qualname": "Prompt.compose", + "replacements": [ + { + "old_source": " yield EditingModeLabel(editor)", + "new_source": " yield editor.editing_mode_label" + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/.verification/editing-mode-label/owned_label.py b/.verification/editing-mode-label/owned_label.py new file mode 100644 index 000000000..069300875 --- /dev/null +++ b/.verification/editing-mode-label/owned_label.py @@ -0,0 +1,36 @@ +import json +from pathlib import Path +r=Path.cwd();f=str(r/'src/toad/widgets/prompt.py');ops=[] +def patch(t,o,n):ops.append(dict(operation='patch_target',file_path=f,target_qualname=t,replacements=[dict(old_source=o,new_source=n)])) +patch('EditingModeLabel',''' """The label follows its own editor throughout this composition's lifetime.""" + + def __init__(self, editor: PromptTextArea): + super().__init__("", markup=False) + self.editor = editor + + @on(events.Mount) + def follow_editor(self) -> None: + self.watch(self.editor, "editing_mode", self.update_mode) + self.watch(self.editor, "has_focus", self.update_mode) + + def update_mode(self) -> None: + self.update((self.editor.editing_mode if self.editor.has_focus else Standard()).label) + +''','') +patch('PromptTextArea.__init__',''' self.prompt = prompt\n''',''' self.prompt = prompt + self.editing_mode_label = EditingModeLabel("", markup=False) +''') +patch('PromptTextArea.on_mount',''' self.apply_keyboard_profile()\n''',''' self.apply_keyboard_profile() + self.call_after_refresh(lambda: self.show_editing_mode(self.editing_mode)) +''') +ops.append(dict(operation='insert_before_target',file_path=f,target_qualname='PromptTextArea.apply_keyboard_profile',source=''' def show_editing_mode(self, mode: EditingMode) -> None: + """The editor owns its label before either widget is mounted.""" + if self.is_mounted: + self.editing_mode_label.update((mode if self.has_focus else Standard()).label) + + def watch_has_focus(self, focus: bool) -> None: + self.show_editing_mode(self.editing_mode) + +''')) +patch('Prompt.compose',''' yield EditingModeLabel(editor)''',''' yield editor.editing_mode_label''') +Path('.verification/editing-mode-label/owned-label.plan.json').write_text(json.dumps({'recipes':[{'recipe_id':'retain-the-original-editor-label-through-mount-and-mode-updates','operations':ops}]},indent=2)) diff --git a/src/toad/widgets/prompt.py b/src/toad/widgets/prompt.py index 1c118ba22..88de56360 100644 --- a/src/toad/widgets/prompt.py +++ b/src/toad/widgets/prompt.py @@ -262,20 +262,6 @@ def apply_native_layout(self) -> None: class EditingModeLabel(AutoWidth, OneRow, SidePadding, NoWrap, Label): - """The label follows its own editor throughout this composition's lifetime.""" - - def __init__(self, editor: PromptTextArea): - super().__init__("", markup=False) - self.editor = editor - - @on(events.Mount) - def follow_editor(self) -> None: - self.watch(self.editor, "editing_mode", self.update_mode) - self.watch(self.editor, "has_focus", self.update_mode) - - def update_mode(self) -> None: - self.update((self.editor.editing_mode if self.editor.has_focus else Standard()).label) - def apply_native_layout(self) -> None: super().apply_native_layout() self.styles.dock = "right" @@ -460,6 +446,7 @@ def __init__(self, *, prompt: Prompt) -> None: super().__init__() # The composer that built this editor owns its turns, popups and modes. self.prompt = prompt + self.editing_mode_label = EditingModeLabel("", markup=False) self.set_reactive(TextArea.editing_mode, prompt.initial_editing_mode) self.set_reactive(PromptTextArea.project_path, prompt.project_path) @@ -505,6 +492,15 @@ def on_mount(self) -> None: self.hide_suggestion_on_blur = False self.editing_mode.attach(self) self.apply_keyboard_profile() + self.call_after_refresh(lambda: self.show_editing_mode(self.editing_mode)) + + def show_editing_mode(self, mode: EditingMode) -> None: + """The editor owns its label before either widget is mounted.""" + if self.is_mounted: + self.editing_mode_label.update((mode if self.has_focus else Standard()).label) + + def watch_has_focus(self, focus: bool) -> None: + self.show_editing_mode(self.editing_mode) def apply_keyboard_profile(self) -> None: self.call_later(self._apply_keyboard_profile) @@ -1096,7 +1092,7 @@ def compose(self) -> ComposeResult: yield editor yield from self.compose_info_popups() with InfoBar(): - yield EditingModeLabel(editor) + yield editor.editing_mode_label yield from self.compose_agent_info() yield StatusLine(markup=False).data_bind(status=Prompt.status) yield from self.compose_mode_controls()