diff --git a/memory-bank/progress.md b/memory-bank/progress.md index cd99523..a5b38cd 100644 --- a/memory-bank/progress.md +++ b/memory-bank/progress.md @@ -58,6 +58,8 @@ - ✅ **Safe file operations**: Atomic file writing with rollback capability - ✅ **Manager module**: Complete HostsManager class for edit operations - ✅ **Error handling**: Comprehensive error handling with user feedback +- ✅ **Enhanced read-only error messages**: Clear, informative error messages with ❌ indicator and helpful instructions +- ✅ **Status bar positioning**: Fixed status bar layout to appear above footer for maximum visibility - ✅ **Keyboard shortcuts**: All edit mode shortcuts implemented and tested - ✅ **Live testing**: Manual testing confirms all functionality works correctly - ✅ **Human-readable formatting**: Tab-based column alignment with proper spacing @@ -109,7 +111,8 @@ 5. ✅ **Entry reordering**: Move entries up/down with Ctrl+Up/Down keyboard shortcuts 6. ✅ **Manager module**: Complete HostsManager class for all edit operations 7. ✅ **Safe file operations**: Atomic file writing with rollback capability -8. ✅ **Comprehensive testing**: 38 new tests for manager module (135 total tests) +8. ✅ **Enhanced error messages**: Professional read-only mode error messages with clear instructions +9. ✅ **Comprehensive testing**: 38 new tests for manager module (135 total tests) ### Recent Major Accomplishments - ✅ **Complete Phase 3 implementation**: Full edit mode foundation with permission management diff --git a/src/hosts/main.py b/src/hosts/main.py index c09deb4..4df0d95 100644 --- a/src/hosts/main.py +++ b/src/hosts/main.py @@ -28,7 +28,7 @@ class HostsManagerApp(App): CSS = """ .hosts-container { - height: 100%; + height: 1fr; } .left-pane { @@ -59,6 +59,16 @@ class HostsManagerApp(App): color: $text; height: 1; padding: 0 1; + dock: bottom; + } + + .status-error { + background: $error; + color: $text; + height: 1; + padding: 0 1; + text-style: bold; + dock: bottom; } /* DataTable styling to match background */ @@ -115,20 +125,22 @@ class HostsManagerApp(App): """Create the application layout.""" yield Header() - with Horizontal(classes="hosts-container"): - left_pane = Vertical(classes="left-pane") - left_pane.border_title = "Hosts Entries" - with left_pane: - yield DataTable(id="entries-table") - yield left_pane + with Vertical(): + with Horizontal(classes="hosts-container"): + left_pane = Vertical(classes="left-pane") + left_pane.border_title = "Hosts Entries" + with left_pane: + yield DataTable(id="entries-table") + yield left_pane + + right_pane = Vertical(classes="right-pane") + right_pane.border_title = "Entry Details" + with right_pane: + yield Static("", id="entry-details") + yield right_pane - right_pane = Vertical(classes="right-pane") - right_pane.border_title = "Entry Details" - with right_pane: - yield Static("", id="entry-details") - yield right_pane + yield Static("", classes="status-bar", id="status") - yield Static("", classes="status-bar", id="status") yield Footer() def on_ready(self) -> None: @@ -367,8 +379,26 @@ class HostsManagerApp(App): status_widget = self.query_one("#status", Static) if message: - status_widget.update(message) + # Check if this is an error message (starts with ❌) + if message.startswith("❌"): + # Use error styling for error messages + status_widget.remove_class("status-bar") + status_widget.add_class("status-error") + status_widget.update(message) + # Auto-clear error message after 5 seconds + self.set_timer(5.0, lambda: self.update_status()) + else: + # Use normal styling for regular messages + status_widget.remove_class("status-error") + status_widget.add_class("status-bar") + status_widget.update(message) + # Auto-clear regular message after 3 seconds + self.set_timer(3.0, lambda: self.update_status()) else: + # Reset to normal status display + status_widget.remove_class("status-error") + status_widget.add_class("status-bar") + mode = "Edit mode" if self.edit_mode else "Read-only mode" entry_count = len(self.hosts_file.entries) active_count = len(self.hosts_file.get_active_entries()) @@ -485,7 +515,7 @@ class HostsManagerApp(App): def action_toggle_entry(self) -> None: """Toggle the active state of the selected entry.""" if not self.edit_mode: - self.update_status("Not in edit mode - press 'Ctrl+E' to enable editing") + self.update_status("❌ Cannot toggle entry: Application is in read-only mode. Press 'Ctrl+E' to enable edit mode.") return if not self.hosts_file.entries: @@ -513,7 +543,7 @@ class HostsManagerApp(App): def action_move_entry_up(self) -> None: """Move the selected entry up in the list.""" if not self.edit_mode: - self.update_status("Not in edit mode - press 'Ctrl+E' to enable editing") + self.update_status("❌ Cannot move entry: Application is in read-only mode. Press 'Ctrl+E' to enable edit mode.") return if not self.hosts_file.entries: @@ -544,7 +574,7 @@ class HostsManagerApp(App): def action_move_entry_down(self) -> None: """Move the selected entry down in the list.""" if not self.edit_mode: - self.update_status("Not in edit mode - press 'Ctrl+E' to enable editing") + self.update_status("❌ Cannot move entry: Application is in read-only mode. Press 'Ctrl+E' to enable edit mode.") return if not self.hosts_file.entries: @@ -575,7 +605,7 @@ class HostsManagerApp(App): def action_save_file(self) -> None: """Save the hosts file to disk.""" if not self.edit_mode: - self.update_status("Not in edit mode - no changes to save") + self.update_status("❌ Cannot save: Application is in read-only mode. No changes to save.") return success, message = self.manager.save_hosts_file(self.hosts_file) diff --git a/tests/test_main.py b/tests/test_main.py index 90bef97..e0a34fd 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -243,14 +243,17 @@ class TestHostsManagerApp: app = HostsManagerApp() - # Mock the query_one method + # Mock the query_one method and set_timer to avoid event loop issues mock_status = Mock() app.query_one = Mock(return_value=mock_status) + app.set_timer = Mock() # Mock the timer to avoid event loop issues app.update_status("Custom status message") # Verify status was updated with custom message mock_status.update.assert_called_once_with("Custom status message") + # Verify timer was set for auto-clearing + app.set_timer.assert_called_once() def test_action_reload(self): """Test reload action."""