Skip to content
This repository was archived by the owner on Apr 22, 2026. It is now read-only.

Commit f144eaf

Browse files
committed
feat: add visual selection indicator in TUI workspace list
When selecting workspaces via Space key, rows now display a visible indicator (◆) with bold cyan styling. The current workspace indicator (→) is preserved and both are shown when a workspace is both selected and current (◆ →). Uses Rich Text objects for cell styling via Textual's DataTable API. Closes agentspaces-4ok
1 parent ae9e627 commit f144eaf

2 files changed

Lines changed: 47 additions & 4 deletions

File tree

src/agentspaces/ui/app.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,10 +169,17 @@ def action_toggle_select(self) -> None:
169169
if cursor_row < 0 or cursor_row >= len(self.workspaces):
170170
return
171171

172+
workspace = self.workspaces[cursor_row]
173+
is_current = str(workspace.path) == self.current_path
174+
172175
if cursor_row in self.selected_rows:
173176
self.selected_rows.remove(cursor_row)
177+
table.set_row_selected(
178+
workspace.name, selected=False, is_current=is_current
179+
)
174180
else:
175181
self.selected_rows.add(cursor_row)
182+
table.set_row_selected(workspace.name, selected=True, is_current=is_current)
176183

177184
# Visual feedback
178185
self.notify(f"Selected: {len(self.selected_rows)} workspace(s)")

src/agentspaces/ui/widgets.py

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22

33
from __future__ import annotations
44

5+
import contextlib
56
from typing import TYPE_CHECKING
67

8+
from rich.text import Text
79
from textual.containers import Container, Horizontal
810
from textual.screen import ModalScreen
911
from textual.widgets import (
@@ -60,21 +62,55 @@ def load_workspaces(
6062

6163
for workspace in workspaces:
6264
# Visual indicators
63-
name_display = workspace.name
64-
if current_path and str(workspace.path) == current_path:
65-
name_display = f"→ {name_display}"
65+
is_current = current_path and str(workspace.path) == current_path
66+
if is_current:
67+
name_display = Text(f"→ {workspace.name}")
68+
else:
69+
name_display = Text(workspace.name)
6670

6771
venv_display = "✓" if workspace.has_venv else ""
6872
purpose_display = self._truncate(workspace.purpose or "", 38)
6973

74+
# DataTable supports Rich renderables per Textual docs
7075
self.add_row(
71-
name_display,
76+
name_display, # type: ignore[arg-type]
7277
workspace.branch,
7378
purpose_display,
7479
venv_display,
7580
key=workspace.name,
7681
)
7782

83+
def set_row_selected(
84+
self, row_key: str, selected: bool, is_current: bool = False
85+
) -> None:
86+
"""Update row display to show selection state.
87+
88+
Args:
89+
row_key: The row key (workspace name) to update.
90+
selected: Whether the row is selected.
91+
is_current: Whether this is the current workspace.
92+
"""
93+
# Build display name with indicators
94+
# Show both indicators when both conditions apply
95+
prefix = ""
96+
if is_current:
97+
prefix = "→ "
98+
if selected:
99+
prefix = "◆ " + prefix # "◆ → " when both selected and current
100+
101+
display_name = f"{prefix}{row_key}"
102+
103+
# Style selected rows distinctly
104+
if selected:
105+
styled = Text(display_name, style="bold cyan")
106+
else:
107+
styled = Text(display_name)
108+
109+
# DataTable supports Rich renderables per Textual docs
110+
# Suppress KeyError if row was removed between toggle and update
111+
with contextlib.suppress(KeyError):
112+
self.update_cell(row_key, "name", styled) # type: ignore[arg-type]
113+
78114
@staticmethod
79115
def _truncate(text: str, max_length: int) -> str:
80116
"""Truncate text with ellipsis if too long."""

0 commit comments

Comments
 (0)