Add context filter to Tasks TUI and fix calendar UI bugs

Tasks TUI:
- Add context support to TaskBackend interface (get_context, set_context,
  get_contexts methods)
- Implement context methods in DstaskClient
- Add Context section to FilterSidebar (above projects/tags)
- Context changes persist via backend CLI

Calendar TUI:
- Remove duplicate header from InvitesPanel (use border_title instead)
- Fix border_title color to use $primary
- Fix WeekGrid to always scroll to work day start (7am) on mount
This commit is contained in:
Bendt
2025-12-19 11:51:53 -05:00
parent be2f67bb7b
commit 3629757e70
6 changed files with 205 additions and 34 deletions

View File

@@ -39,6 +39,7 @@ class InvitesPanel(Widget):
min-height: 3;
padding: 0 1;
border: round $primary;
border-title-color: $primary;
}
"""
@@ -74,7 +75,12 @@ class InvitesPanel(Widget):
def on_mount(self) -> None:
"""Set border title on mount."""
self.border_title = "Invites"
self._update_border_title()
def _update_border_title(self) -> None:
"""Update border title with invite count."""
count = len(self.invites)
self.border_title = f"Invites ({count})" if count else "Invites"
def _get_theme_color(self, color_name: str) -> str:
"""Get a color from the current theme."""
@@ -97,25 +103,21 @@ class InvitesPanel(Widget):
return fallbacks.get(color_name, "white")
def get_content_height(self, container, viewport, width: int) -> int:
"""Calculate height: header + invite rows."""
# Header (1) + invites (2 lines each) + empty message if no invites
"""Calculate height: invite rows only (no internal header)."""
if not self.invites:
return 2 # Header + "No pending invites"
return 1 + len(self.invites) * 2 # Header + 2 lines per invite
return 1 # "No pending invites"
return len(self.invites) * 2 # 2 lines per invite
def render_line(self, y: int) -> Strip:
"""Render a line of the panel."""
if y == 0:
return self._render_header()
if not self.invites:
if y == 1:
if y == 0:
return self._render_empty_message()
return Strip.blank(self.size.width)
# Each invite takes 2 lines
invite_idx = (y - 1) // 2
line_in_invite = (y - 1) % 2
invite_idx = y // 2
line_in_invite = y % 2
if 0 <= invite_idx < len(self.invites):
return self._render_invite_line(
@@ -126,15 +128,6 @@ class InvitesPanel(Widget):
return Strip.blank(self.size.width)
def _render_header(self) -> Strip:
"""Render the panel header."""
primary_color = self._get_theme_color("primary")
count = len(self.invites)
header = f"Invites ({count})" if count else "Invites"
header = header[: self.size.width].ljust(self.size.width)
style = Style(bold=True, color=primary_color)
return Strip([Segment(header, style)])
def _render_empty_message(self) -> Strip:
"""Render empty state message."""
msg = "No pending invites"
@@ -179,6 +172,7 @@ class InvitesPanel(Widget):
self.invites = invites
if self.selected_index >= len(invites):
self.selected_index = max(0, len(invites) - 1)
self._update_border_title()
self.refresh()
def select_next(self) -> None:
@@ -203,11 +197,11 @@ class InvitesPanel(Widget):
"""Handle mouse clicks."""
y = event.y
if y == 0 or not self.invites:
if not self.invites:
return
# Calculate which invite was clicked
invite_idx = (y - 1) // 2
# Calculate which invite was clicked (2 lines per invite)
invite_idx = y // 2
if 0 <= invite_idx < len(self.invites):
self.selected_index = invite_idx
self.post_message(self.InviteSelected(self.invites[invite_idx]))