Files
2026-07-01 17:56:31 +08:00

120 lines
5.8 KiB
Python

"""Top metrics bar for the CLI board."""
from __future__ import annotations
from rich.columns import Columns
from rich.console import Group, RenderableType
from rich.panel import Panel
from rich.text import Text
from textual.widgets import Static
from ..state.store import BoardStateStore
from .render_utils import badge, format_clock, humanize_age, priority_style, status_style, truncate_text
class MetricsBarWidget(Static):
"""Show board-wide health, activity, and viewport state."""
def __init__(self, state: BoardStateStore, *, exec_mode: str = "task", company_profile: str = "corporate") -> None:
super().__init__(id="metrics-bar")
self.state = state
self.exec_mode = exec_mode
self.company_profile = company_profile
self.pipeline_done: int = 0
self.pipeline_total: int = 0
def render(self) -> RenderableType:
metrics = self.state.metrics()
selected = self.state.selected_task()
alerts = self.state.alerts()[:3]
item_label = "work item" if self.state.snapshot.mode == "company" else "task"
board_text = Text()
board_text.append(f"{self.state.snapshot.project_id}\n", style="bold white")
board_text.append("view ", style="dim")
board_text.append(self.state.view_mode.upper(), style="bold cyan")
board_text.append(" focus ", style="dim")
board_text.append(self.state.pane_focus, style="bold white")
board_text.append(" density ", style="dim")
board_text.append(self.state.density_mode, style="bold magenta")
board_text.append("\nmode ", style="dim")
board_text.append(self.exec_mode, style="bold #22c55e")
board_text.append("/", style="dim")
board_text.append(self.company_profile, style="bold #22c55e")
if self.state.search_query:
board_text.append("\nfilter ", style="dim")
board_text.append(truncate_text(self.state.search_query, 24), style="yellow")
flow_text = Text()
flow_text.append("todo ", style="dim")
flow_text.append(str(metrics.todo_count), style="bold white")
flow_text.append(" active ", style="dim")
flow_text.append(str(metrics.in_progress_count), style="bold #22c55e")
flow_text.append(" done ", style="dim")
flow_text.append(str(metrics.done_count), style="bold #10b981")
flow_text.append("\nrun ", style="dim")
flow_text.append(str(metrics.running_count), style="bold #38bdf8")
flow_text.append(" chk ", style="dim")
flow_text.append(str(metrics.pending_checkpoint_count), style="bold #f59e0b")
if self.pipeline_total > 0:
ratio = min(1.0, self.pipeline_done / self.pipeline_total)
bar_w = 8
filled = int(ratio * bar_w)
flow_text.append("\nproj ", style="dim")
flow_text.append("\u2588" * filled, style="bold #22c55e")
flow_text.append("\u2591" * (bar_w - filled), style="dim")
flow_text.append(f" {self.pipeline_done}/{self.pipeline_total}", style="white")
health_text = Text()
health_text.append("last refresh ", style="dim")
health_text.append(format_clock(metrics.last_refreshed_at), style="bold white")
health_text.append(" stale ", style="dim")
health_text.append(str(metrics.stale_task_count), style="bold yellow")
if metrics.last_runtime_update:
health_text.append("\nruntime heartbeat ", style="dim")
health_text.append(humanize_age(metrics.last_runtime_update), style="bold #22c55e")
else:
health_text.append("\nruntime heartbeat ", style="dim")
health_text.append("idle", style="bold #64748b")
if alerts:
alert_lines: list[Text] = []
for alert in alerts:
line = Text()
line += badge(alert.level.upper(), status_style(alert.level))
line.append(f" {truncate_text(alert.title, 18)}", style="bold white")
line.append(f"\n{truncate_text(alert.message, 42)}", style="dim")
alert_lines.append(line)
attention_renderable: RenderableType = Group(*alert_lines)
else:
attention_renderable = Text("No active alerts.\nBoard health looks stable.", style="dim")
selection_text = Text()
if selected is None:
selection_text.append(
f"No {item_label} selected.\nUse the session rail or board to pick a {item_label}.",
style="dim",
)
else:
selection_text.append(f"{truncate_text(selected.title, 28)}\n", style="bold white")
selection_text += badge(selected.status.upper(), status_style(selected.status))
if selected.priority:
selection_text.append(" ")
selection_text += badge(selected.priority.upper(), priority_style(selected.priority))
if selected.pending_checkpoint:
selection_text.append(" ")
selection_text += badge("REVIEW", status_style("warn"))
runtime = self.state.runtime_for(selected.task_id)
if runtime and runtime.current_tool:
selection_text.append(f"\n\u2699{truncate_text(runtime.current_tool, 16)}", style="dim")
selection_text.append(f" {humanize_age(selected.updated_at)}", style="dim")
panels = [
Panel(board_text, title="Board", border_style="cyan"),
Panel(flow_text, title="Projection", border_style="green"),
Panel(health_text, title="Health", border_style="magenta"),
Panel(attention_renderable, title="Attention", border_style="yellow"),
Panel(selection_text, title="Selection", border_style="blue"),
]
return Columns(panels, expand=True, equal=True)