From a6e092b38d9e2ca2729652b2ff98b5ff8cbb0130 Mon Sep 17 00:00:00 2001 From: Dariusz Jarosz Date: Tue, 13 Oct 2026 13:07:42 -0500 Subject: [PATCH] Clean up attachment section from update and add new log entry. --- tools/developer_tools/bely-cli/README.md | 7 +-- .../src/bely_cli/tui/screens/compose.py | 31 ++-------- .../bely-cli/test/test_tui_screens.py | 61 ++++++------------- 3 files changed, 25 insertions(+), 74 deletions(-) diff --git a/tools/developer_tools/bely-cli/README.md b/tools/developer_tools/bely-cli/README.md index fd9b9ea46..6ec7ce794 100644 --- a/tools/developer_tools/bely-cli/README.md +++ b/tools/developer_tools/bely-cli/README.md @@ -303,15 +303,14 @@ equivalent of Home's old menu items, plus what Textual provides by default: **Entry composer** -A Markdown-aware `TextArea` for the entry body, an optional attachment path, and three -buttons (`Tab`/arrows move focus between the fields and buttons; `Enter` presses the +A Markdown-aware `TextArea` for the entry body and three buttons (`Tab`/arrows move focus between the fields and buttons; `Enter` presses the focused button): | Button | Action | |--------|--------| -| Save `^S` | Save (and upload the attachment, if a path was entered). Also `ctrl+s` from anywhere in the dialog. | +| Save `^S` | Save the entry. Also `ctrl+s` from anywhere in the dialog. | | Edit in $EDITOR | Suspend the TUI and open the buffer in `$EDITOR`; the edited text comes back into the `TextArea`. | -| Cancel `Esc` | Cancel; asks for confirmation first if the buffer or attachment has unsaved changes. Also `Esc` from anywhere in the dialog. | +| Cancel `Esc` | Cancel; asks for confirmation first if the buffer has unsaved changes. Also `Esc` from anywhere in the dialog. | An empty new entry is skipped rather than saved, matching `entry add`'s behavior. diff --git a/tools/developer_tools/bely-cli/src/bely_cli/tui/screens/compose.py b/tools/developer_tools/bely-cli/src/bely_cli/tui/screens/compose.py index a12749402..390bb25dc 100644 --- a/tools/developer_tools/bely-cli/src/bely_cli/tui/screens/compose.py +++ b/tools/developer_tools/bely-cli/src/bely_cli/tui/screens/compose.py @@ -15,12 +15,12 @@ from textual import work from textual.app import ComposeResult from textual.binding import Binding -from textual.containers import Horizontal, Vertical -from textual.widgets import Button, Input, Static, TextArea +from textual.containers import Vertical +from textual.widgets import Button, Static, TextArea from ... import core from ...common import editor_changed, format_error_message -from .dialog import CANCEL_HINT, SAVE_HINT, DialogButtons, DialogScreen, LoadingScreen, hinted_label +from .dialog import CANCEL_HINT, SAVE_HINT, DialogButtons, DialogScreen, hinted_label class ComposeScreen(DialogScreen): @@ -39,7 +39,6 @@ class ComposeScreen(DialogScreen): BINDINGS = [Binding("ctrl+s", "submit", "Save", show=False)] - # Save first: right after the attachment field in tab order, since it's used most. BUTTON_ROWS = [["compose-save", "compose-editor", "compose-cancel"]] def __init__(self, doc, entry, api, *, is_new, factory=None, reply_to=None): @@ -62,9 +61,6 @@ def compose(self) -> ComposeResult: with Vertical(id="compose-dialog", classes="dialog"): yield Static(title, id="compose-title") yield TextArea(self._initial_text, language="markdown", id="compose-area") - with Horizontal(id="compose-attach-row"): - yield Static("Attachment:", id="compose-attach-label") - yield Input(placeholder="optional file path", id="compose-attach") with DialogButtons(): yield Button(hinted_label("Save", SAVE_HINT), variant="primary", id="compose-save") yield Button(hinted_label("Edit in $EDITOR"), id="compose-editor") @@ -91,8 +87,7 @@ def action_cancel(self): def _is_dirty(self): area = self.query_one("#compose-area", TextArea) - attach_path = self.query_one("#compose-attach", Input).value.strip() - return editor_changed(self._initial_text, area.text) or bool(attach_path) + return editor_changed(self._initial_text, area.text) # -- cancel, with a dirty-buffer confirmation -- @@ -137,29 +132,13 @@ async def _save(self): self.dismiss(None) return - attach_path = self.query_one("#compose-attach", Input).value.strip() - if attach_path: - try: - attach_path = core.validate_attachment_path(attach_path) - except ValueError as e: - self.notify(str(e), severity="error") - return - - if not self.is_new and not editor_changed(self._initial_text, text) and not attach_path: + if not self.is_new and not editor_changed(self._initial_text, text): self.notify("No changes made.") self.dismiss(None) return try: saved_entry = await asyncio.to_thread(core.save_entry, self.api, self.entry, text) - if attach_path: - await self.app.push_screen(LoadingScreen("Uploading attachment…")) - try: - await asyncio.to_thread( - core.upload_attachment, self.api, self.doc.id, - saved_entry.log_id, attach_path) - finally: - self.app.pop_screen() except Exception as e: self.notify( f"Save failed: {format_error_message(e, self.factory)}", severity="error") diff --git a/tools/developer_tools/bely-cli/test/test_tui_screens.py b/tools/developer_tools/bely-cli/test/test_tui_screens.py index e13bd68a5..90e2b214c 100644 --- a/tools/developer_tools/bely-cli/test/test_tui_screens.py +++ b/tools/developer_tools/bely-cli/test/test_tui_screens.py @@ -636,25 +636,18 @@ async def test_reply_loads_blank_template_sets_parent_and_titles_composer(self): self.assertEqual(saved.parent_log_id, 7) self.assertEqual(saved.log_entry, "reply text") - async def test_reply_uses_existing_attachment_flow(self): + async def test_reply_does_not_show_attachment_field(self): api = FakeLogbookApi() - api.upload_attachment = lambda **kwargs: None doc = SimpleNamespace(id=1, name="Doc") parent = SimpleNamespace(log_id=7) app = App() async with app.run_test() as pilot: task = app.run_worker(open_composer(app, doc, api, reply_to=parent)) await pilot.pause() - screen = app.screen - screen.query_one("#compose-area", TextArea).text = "reply text" - screen.query_one("#compose-attach", Input).value = "/tmp/reply.txt" - with patch("bely_cli.core.validate_attachment_path", return_value="/tmp/reply.txt"), \ - patch("bely_cli.core.upload_attachment") as upload: - screen.query_one("#compose-save", Button).press() - await pilot.pause() - await pilot.pause() - saved = await task.wait() - upload.assert_called_once_with(api, 1, saved.log_id, "/tmp/reply.txt") + self.assertEqual(len(app.screen.query("#compose-attach")), 0) + app.screen.query_one("#compose-cancel", Button).press() + await pilot.pause() + await task.wait() async def test_reply_cancel_returns_none(self): api = FakeLogbookApi() @@ -774,40 +767,20 @@ async def test_editor_button_opens_editor_and_updates_buffer(self): screen.query_one("#compose-area", TextArea).text, "existing text edited") # buffer is dirty now, so skip Cancel here -- not what this test covers - async def test_tab_from_attachment_field_reaches_save_next(self): - api = FakeLogbookApi() - doc = SimpleNamespace(id=1, name="Doc") - entry = SimpleNamespace(log_id=7, log_entry="existing text") - app = App() - async with app.run_test() as pilot: - app.run_worker(app.push_screen_wait(ComposeScreen(doc, entry, api, is_new=False))) - await pilot.pause() - screen = app.screen - screen.query_one("#compose-attach", Input).focus() - await pilot.pause() - await pilot.press("tab") - await pilot.pause() - self.assertIs(screen.focused, screen.query_one("#compose-save", Button)) - - async def test_down_and_up_arrows_move_between_attachment_and_buttons(self): + async def test_composer_modes_do_not_show_attachment_field(self): api = FakeLogbookApi() doc = SimpleNamespace(id=1, name="Doc") - entry = SimpleNamespace(log_id=7, log_entry="existing text") - app = App() - async with app.run_test() as pilot: - app.run_worker(app.push_screen_wait(ComposeScreen(doc, entry, api, is_new=False))) - await pilot.pause() - screen = app.screen - screen.query_one("#compose-attach", Input).focus() - await pilot.pause() - - await pilot.press("down") - await pilot.pause() - self.assertIs(screen.focused, screen.query_one("#compose-save", Button)) - - await pilot.press("up") - await pilot.pause() - self.assertIs(screen.focused, screen.query_one("#compose-attach", Input)) + for is_new, entry in ( + (True, SimpleNamespace(log_id=None, log_entry="")), + (False, SimpleNamespace(log_id=7, log_entry="existing text")), + ): + with self.subTest(is_new=is_new): + app = App() + async with app.run_test() as pilot: + app.run_worker(app.push_screen_wait( + ComposeScreen(doc, entry, api, is_new=is_new))) + await pilot.pause() + self.assertEqual(len(app.screen.query("#compose-attach")), 0) async def test_left_and_right_arrows_cycle_between_buttons(self): api = FakeLogbookApi()