Fix TUI bugs: folder selection, filter stability, UI consistency

- Mail: Fix folder/account selector not triggering reload (use direct
  fetch instead of reactive reload_needed flag)
- Tasks: Store all_projects/all_tags on mount so filters don't change
  when filtering; add OR search for multiple tags
- Sync: Use rounded borders and border_title for sidebar/activity log
- Calendar: Remove padding from mini-calendar, add rounded border and
  border_title to invites panel
This commit is contained in:
Bendt
2025-12-19 11:24:15 -05:00
parent 25385c6482
commit be2f67bb7b
5 changed files with 71 additions and 37 deletions

View File

@@ -38,6 +38,7 @@ class InvitesPanel(Widget):
height: auto;
min-height: 3;
padding: 0 1;
border: round $primary;
}
"""
@@ -71,6 +72,10 @@ class InvitesPanel(Widget):
if invites:
self.invites = invites
def on_mount(self) -> None:
"""Set border title on mount."""
self.border_title = "Invites"
def _get_theme_color(self, color_name: str) -> str:
"""Get a color from the current theme."""
try:

View File

@@ -62,7 +62,7 @@ class MonthCalendar(Widget):
MonthCalendar {
width: 24;
height: auto;
padding: 0 1;
padding: 0;
}
"""

View File

@@ -216,16 +216,10 @@ class SyncDashboard(App):
.sidebar {
width: 30;
height: 100%;
border: solid $primary;
border: round $primary;
padding: 0;
}
.sidebar-title {
text-style: bold;
padding: 1;
background: $primary-darken-2;
}
.countdown-container {
height: 5;
padding: 0 1;
@@ -269,15 +263,10 @@ class SyncDashboard(App):
.log-container {
height: 1fr;
border: solid $primary;
border: round $primary;
padding: 0;
}
.log-title {
padding: 0 1;
background: $primary-darken-2;
}
ListView {
height: 1fr;
}
@@ -338,8 +327,7 @@ class SyncDashboard(App):
with Horizontal(classes="dashboard"):
# Sidebar with task list
with Vertical(classes="sidebar"):
yield Static("Tasks", classes="sidebar-title")
with Vertical(classes="sidebar", id="tasks-sidebar"):
yield ListView(
# Stage 1: Sync local changes to server
TaskListItem(
@@ -416,8 +404,7 @@ class SyncDashboard(App):
yield Static("0%", id="progress-percent")
# Log for selected task
with Vertical(classes="log-container"):
yield Static("Activity Log", classes="log-title")
with Vertical(classes="log-container", id="log-container"):
yield Log(id="task-log")
yield Footer()
@@ -427,6 +414,13 @@ class SyncDashboard(App):
# Set theme from shared config
self.theme = get_theme_name()
# Set border titles
try:
self.query_one("#tasks-sidebar").border_title = "Tasks"
self.query_one("#log-container").border_title = "Activity Log"
except Exception:
pass
# Store references to task items
task_list = self.query_one("#task-list", ListView)
for item in task_list.children:

View File

@@ -353,7 +353,9 @@ class EmailViewerApp(App):
self.current_message_id = 0
self.current_message_index = 0
self.selected_messages.clear()
self.reload_needed = True
self.search_query = "" # Clear search when switching folders
# Directly fetch instead of relying on reload_needed watcher
self.fetch_envelopes()
except Exception as e:
logging.error(f"Error selecting folder: {e}")
@@ -372,9 +374,11 @@ class EmailViewerApp(App):
self.current_message_id = 0
self.current_message_index = 0
self.selected_messages.clear()
self.search_query = "" # Clear search when switching accounts
# Refresh folders for new account
self.fetch_folders()
self.reload_needed = True
# Directly fetch instead of relying on reload_needed watcher
self.fetch_envelopes()
except Exception as e:
logging.error(f"Error selecting account: {e}")

View File

@@ -197,6 +197,8 @@ class TasksApp(App):
self.tasks = []
self.projects = []
self.tags = []
self.all_projects = [] # Stable list of all projects (not filtered)
self.all_tags = [] # Stable list of all tags (not filtered)
self.current_project_filter = None
self.current_tag_filters = []
self.current_sort_column = "priority"
@@ -261,7 +263,10 @@ class TasksApp(App):
height = max(10, min(90, height))
notes_pane.styles.height = f"{height}%"
# Load tasks (this will also update the sidebar)
# Load ALL projects and tags once for stable sidebar
self._load_all_filters()
# Load tasks (filtered by current filters)
self.load_tasks()
def _setup_columns(self, table: DataTable, columns: list[str]) -> None:
@@ -375,32 +380,58 @@ class TasksApp(App):
if not self.backend:
return
# Get tasks with current filters
self.tasks = self.backend.get_tasks(
project=self.current_project_filter,
tags=self.current_tag_filters if self.current_tag_filters else None,
)
# Get ALL tasks first (unfiltered)
all_tasks = self.backend.get_tasks()
# Apply client-side filtering for OR logic
self.tasks = self._filter_tasks(all_tasks)
# Sort tasks
self._sort_tasks()
# Also load projects and tags for filtering
self.projects = self.backend.get_projects()
self.tags = self.backend.get_tags()
# Update sidebar with available filters
self._update_sidebar()
# Update table
self._update_table()
def _load_all_filters(self) -> None:
"""Load all projects and tags once for stable sidebar."""
if not self.backend:
return
self.all_projects = self.backend.get_projects()
self.all_tags = self.backend.get_tags()
# Update sidebar with stable filter options
self._update_sidebar()
def _filter_tasks(self, tasks: list[Task]) -> list[Task]:
"""Filter tasks by current project and tag filters using OR logic.
- If project filter is set, only show tasks from that project
- If tag filters are set, show tasks that have ANY of the selected tags (OR)
"""
filtered = tasks
# Filter by project (single project filter is AND)
if self.current_project_filter:
filtered = [t for t in filtered if t.project == self.current_project_filter]
# Filter by tags using OR logic - show tasks with ANY of the selected tags
if self.current_tag_filters:
filtered = [
t
for t in filtered
if any(tag in t.tags for tag in self.current_tag_filters)
]
return filtered
def _update_sidebar(self) -> None:
"""Update the filter sidebar with current projects and tags."""
"""Update the filter sidebar with all available projects and tags."""
try:
sidebar = self.query_one("#sidebar", FilterSidebar)
# Convert projects to (name, count) tuples
project_data = [(p.name, p.task_count) for p in self.projects if p.name]
sidebar.update_filters(projects=project_data, tags=self.tags)
# Use stable all_projects/all_tags, not filtered ones
project_data = [(p.name, p.task_count) for p in self.all_projects if p.name]
sidebar.update_filters(projects=project_data, tags=self.all_tags)
except Exception:
pass # Sidebar may not be mounted yet