Skip to content

Commit 9dacd2f

Browse files
committed
feat: show outcome grade, commits, tests, collab in TUI details panel
The details panel now shows outcome vs process grades side by side, commit count with style, test results, and collaboration archetype. Analysis is computed on selection and cached per session.
1 parent e7154bf commit 9dacd2f

2 files changed

Lines changed: 87 additions & 14 deletions

File tree

sesh/tui/app.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ def __init__(self, db: Database | None, live: bool = False):
3838
self.selected_session: dict | None = None
3939
self.selected_tool_calls: list[dict] = []
4040
self.selected_patterns: list[dict] = []
41+
self.selected_analysis: object | None = None # AnalysisResult cache
42+
self._analysis_cache: dict[str, object] = {} # sid -> AnalysisResult
4143

4244
# Live state
4345
self.live_snapshot: LiveSnapshot | None = None
@@ -72,22 +74,41 @@ def refresh_live(self) -> None:
7274
self.last_live_refresh = time.time()
7375

7476
def _load_selected_details(self) -> None:
75-
"""Load tool calls and patterns for the currently selected session."""
77+
"""Load tool calls, patterns, and outcome/collab for the currently selected session."""
7678
if not self.db:
7779
self.selected_session = None
7880
self.selected_tool_calls = []
7981
self.selected_patterns = []
82+
self.selected_analysis = None
8083
return
8184

8285
if self.sessions and 0 <= self.selected < len(self.sessions):
8386
sid = self.sessions[self.selected]["id"]
8487
self.selected_session = self.db.get_session(sid)
8588
self.selected_tool_calls = self.db.get_tool_calls(sid)
8689
self.selected_patterns = self.db.get_patterns(sid)
90+
91+
# Load full analysis (cached) for outcome + collaboration
92+
if sid in self._analysis_cache:
93+
self.selected_analysis = self._analysis_cache[sid]
94+
else:
95+
self.selected_analysis = None
96+
source_path = self.selected_session.get("source_path") if self.selected_session else None
97+
if source_path:
98+
from pathlib import Path
99+
p = Path(source_path)
100+
if p.exists():
101+
try:
102+
from ..analyze import analyze_session
103+
self.selected_analysis = analyze_session(p)
104+
self._analysis_cache[sid] = self.selected_analysis
105+
except Exception:
106+
pass
87107
else:
88108
self.selected_session = None
89109
self.selected_tool_calls = []
90110
self.selected_patterns = []
111+
self.selected_analysis = None
91112

92113
def move_selection(self, delta: int) -> None:
93114
"""Move the session selection by delta rows."""
@@ -210,6 +231,7 @@ def _render_wide(self, stdscr, start_y: int, max_y: int, max_x: int) -> None:
210231
draw_details(
211232
stdscr, start_y + patterns_h, left_w, right_w, details_h,
212233
self.selected_session, self.selected_tool_calls, self.selected_patterns,
234+
self.selected_analysis,
213235
)
214236

215237
def _render_narrow(self, stdscr, start_y: int, max_y: int, max_x: int) -> None:
@@ -240,6 +262,7 @@ def _render_narrow(self, stdscr, start_y: int, max_y: int, max_x: int) -> None:
240262
draw_details(
241263
stdscr, start_y + trend_h + sessions_h, 0, width, details_h,
242264
self.selected_session, self.selected_tool_calls, self.selected_patterns,
265+
self.selected_analysis,
243266
)
244267

245268

sesh/tui/panels.py

Lines changed: 63 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -254,11 +254,13 @@ def draw_sessions(
254254

255255
def draw_details(
256256
win, y: int, x: int, width: int, height: int,
257-
session: dict | None, tool_calls: list[dict], patterns: list[dict]
257+
session: dict | None, tool_calls: list[dict], patterns: list[dict],
258+
analysis=None,
258259
) -> None:
259260
"""Draw the session details panel.
260261
261-
Shows detailed info about the currently selected session.
262+
Shows detailed info about the currently selected session,
263+
including outcome grade, commits, tests, and collaboration.
262264
"""
263265
draw_box(win, y, x, height, width, "Details")
264266

@@ -277,9 +279,52 @@ def add_line(label: str, value: str, value_attr: int = 0) -> None:
277279
safe_addstr(win, y + 1 + row, x + 3 + len(label) + 2, value, value_attr or dim_attr())
278280
row += 1
279281

280-
grade = session.get("grade", "?") or "?"
281-
score = session.get("score", 0) or 0
282-
add_line("Grade", f"{grade} ({score}/100)", grade_color(grade))
282+
def add_raw(text: str, attr: int = 0) -> None:
283+
nonlocal row
284+
if row >= available_rows:
285+
return
286+
safe_addstr(win, y + 1 + row, x + 3, truncate(text, width - 6), attr or dim_attr())
287+
row += 1
288+
289+
# Outcome + Process grades side by side (if analysis available)
290+
if analysis and hasattr(analysis, "outcome") and analysis.outcome:
291+
o = analysis.outcome
292+
p_grade = session.get("grade", "?") or "?"
293+
p_score = session.get("score", 0) or 0
294+
o_grade = o.grade or "N/A"
295+
o_score = o.score if o.score is not None else 0
296+
297+
# Outcome grade
298+
label = "Outcome"
299+
safe_addstr(win, y + 1 + row, x + 3, f"{label}: ", dim_attr())
300+
o_str = f"{o_grade} ({o_score}/100)" if o_grade != "N/A" else "N/A"
301+
safe_addstr(win, y + 1 + row, x + 3 + len(label) + 2, o_str, grade_color(o_grade))
302+
# Process grade on same line
303+
sep_x = x + 3 + len(label) + 2 + len(o_str) + 2
304+
if sep_x + 20 < x + width:
305+
safe_addstr(win, y + 1 + row, sep_x, "Process: ", dim_attr())
306+
safe_addstr(win, y + 1 + row, sep_x + 9, f"{p_grade} ({p_score}/100)", grade_color(p_grade))
307+
row += 1
308+
309+
# Commits
310+
if o.commit_count > 0:
311+
style = f" ({o.commit_style})" if o.commit_style and o.commit_style != "none" else ""
312+
add_line("Commits", f"{o.commit_count}{style}", curses.color_pair(1))
313+
314+
# Tests
315+
if o.test_snapshots:
316+
last = o.test_snapshots[-1]
317+
passed = getattr(last, "passed", 0) or 0
318+
failed = getattr(last, "failed", 0) or 0
319+
if failed > 0:
320+
add_line("Tests", f"{passed} pass, {failed} FAIL", curses.color_pair(4))
321+
else:
322+
add_line("Tests", f"{passed} passing", curses.color_pair(1))
323+
else:
324+
# Fallback: just process grade
325+
grade = session.get("grade", "?") or "?"
326+
score = session.get("score", 0) or 0
327+
add_line("Grade", f"{grade} ({score}/100)", grade_color(grade))
283328

284329
duration = session.get("duration_minutes")
285330
if duration is not None:
@@ -307,14 +352,22 @@ def add_line(label: str, value: str, value_attr: int = 0) -> None:
307352
if files_touched:
308353
add_line("Files", f"{len(files_touched)} touched")
309354

310-
# Model
311-
model = session.get("model")
312-
if model:
313-
add_line("Model", truncate(model, width - 12))
355+
# Collaboration (if analysis available)
356+
if analysis and hasattr(analysis, "collaboration") and analysis.collaboration:
357+
c = analysis.collaboration
358+
if c.human_turns >= 2 and c.archetype:
359+
if row < available_rows:
360+
row += 1 # blank line
361+
arch_color = curses.color_pair(1) if c.archetype == "Partnership" else (
362+
curses.color_pair(4) if c.archetype in ("Spec Dump", "Micromanager") else dim_attr()
363+
)
364+
add_line("Collab", f"{c.archetype} ({c.score}/100)", arch_color)
365+
corr = f"{c.corrections}c" if c.corrections else "0c"
366+
aff = f"{c.affirmations}a" if c.affirmations else "0a"
367+
add_raw(f" {corr} {aff} ~{c.avg_words_per_turn:.0f} words/turn", dim_attr())
314368

315369
# Patterns
316370
if patterns:
317-
# Count by type
318371
pattern_counts: dict[str, int] = {}
319372
for p in patterns:
320373
pt = p.get("type", "unknown")
@@ -328,9 +381,6 @@ def add_line(label: str, value: str, value_attr: int = 0) -> None:
328381
break
329382
safe_addstr(win, y + 1 + row, x + 3, f" {pt} ({cnt})", curses.color_pair(3))
330383
row += 1
331-
elif row < available_rows:
332-
row += 1
333-
safe_addstr(win, y + 1 + row, x + 3, "No patterns", curses.color_pair(1))
334384

335385
# Session ID at the bottom if space permits
336386
if row + 1 < available_rows:

0 commit comments

Comments
 (0)