fix: worktree session UI safety and visibility (review)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+12
-6
@@ -287,8 +287,15 @@ class HqtApp(App):
|
|||||||
|
|
||||||
async def _open() -> None:
|
async def _open() -> None:
|
||||||
# is_git_repo is async; resolve it before pushing so the dialog can
|
# is_git_repo is async; resolve it before pushing so the dialog can
|
||||||
# disable the worktree control for non-repo projects.
|
# disable the worktree control for non-repo projects. This worker is
|
||||||
|
# run raw (not via _run_service_worker), so it must never raise: any
|
||||||
|
# failure (e.g. git not on PATH) falls back to False, which simply
|
||||||
|
# disables the worktree checkbox in the dialog.
|
||||||
|
try:
|
||||||
is_git_repo = await worktree.is_git_repo(Path(project_path))
|
is_git_repo = await worktree.is_git_repo(Path(project_path))
|
||||||
|
except Exception as exc: # noqa: BLE001 - keep the worker from crashing the TUI
|
||||||
|
log.warning("is_git_repo check failed for %s: %s", project_path, exc)
|
||||||
|
is_git_repo = False
|
||||||
self.push_screen(NewSessionScreen(harness_names, is_git_repo), on_dismiss)
|
self.push_screen(NewSessionScreen(harness_names, is_git_repo), on_dismiss)
|
||||||
|
|
||||||
self.run_worker(_open())
|
self.run_worker(_open())
|
||||||
@@ -343,11 +350,10 @@ class HqtApp(App):
|
|||||||
parts.append("uncommitted changes")
|
parts.append("uncommitted changes")
|
||||||
if state and state.unique_commits > 0:
|
if state and state.unique_commits > 0:
|
||||||
parts.append(f"{state.unique_commits} unmerged commit(s)")
|
parts.append(f"{state.unique_commits} unmerged commit(s)")
|
||||||
warning = (
|
warning = None
|
||||||
" and ".join(parts).capitalize() + " — work will be lost"
|
if parts:
|
||||||
if parts
|
joined = " and ".join(parts)
|
||||||
else None
|
warning = joined[:1].upper() + joined[1:] + " — work will be lost"
|
||||||
)
|
|
||||||
|
|
||||||
def on_dismiss(result: tuple[bool, bool] | None) -> None:
|
def on_dismiss(result: tuple[bool, bool] | None) -> None:
|
||||||
if result is None:
|
if result is None:
|
||||||
|
|||||||
@@ -37,9 +37,14 @@ def format_session_text(
|
|||||||
) -> str:
|
) -> str:
|
||||||
color = _STATUS_COLORS.get(status_text, "#c6d0f5") # default: Text
|
color = _STATUS_COLORS.get(status_text, "#c6d0f5") # default: Text
|
||||||
name = nickname
|
name = nickname
|
||||||
# Mark worktree sessions with their branch. The nickname defaults to the
|
# Worktree sessions always carry the ⎇ marker so they're visually distinct
|
||||||
# branch at creation, so only append the marker when it adds information.
|
# from plain sessions. The nickname defaults to the branch at creation, so
|
||||||
if worktree_branch and nickname != worktree_branch:
|
# when they match we show the glyph once (no duplicated branch); when they
|
||||||
|
# differ we show both so the branch is still visible.
|
||||||
|
if worktree_branch:
|
||||||
|
if nickname == worktree_branch:
|
||||||
|
name = f"⎇ {nickname}"
|
||||||
|
else:
|
||||||
name = f"{nickname} ⎇ {worktree_branch}"
|
name = f"{nickname} ⎇ {worktree_branch}"
|
||||||
label = escape(f"{name} [{harness_name}]")
|
label = escape(f"{name} [{harness_name}]")
|
||||||
return f"[{color}]{symbol}[/] {label} {status_text}"
|
return f"[{color}]{symbol}[/] {label} {status_text}"
|
||||||
|
|||||||
+10
-4
@@ -1404,18 +1404,24 @@ async def test_confirm_delete_cancel_returns_none():
|
|||||||
|
|
||||||
|
|
||||||
def test_format_session_text_appends_worktree_branch_marker():
|
def test_format_session_text_appends_worktree_branch_marker():
|
||||||
|
"""nickname != branch → show both nickname and branch around the ⎇ glyph."""
|
||||||
from hqt.tui.widgets.session_list import format_session_text
|
from hqt.tui.widgets.session_list import format_session_text
|
||||||
|
|
||||||
text = format_session_text("mywork", "claude", "idle", "●", "feature-x")
|
text = format_session_text("mywork", "claude", "idle", "●", "feature-x")
|
||||||
assert "⎇ feature-x" in text
|
assert "mywork ⎇ feature-x" in text
|
||||||
|
|
||||||
|
|
||||||
def test_format_session_text_no_marker_when_nickname_equals_branch():
|
def test_format_session_text_marker_shows_once_when_nickname_equals_branch():
|
||||||
"""Avoid duplication: when nickname already equals the branch, no marker."""
|
"""nickname == branch (the common case) → ⎇ still appears, glyph once, no
|
||||||
|
duplicated branch name."""
|
||||||
from hqt.tui.widgets.session_list import format_session_text
|
from hqt.tui.widgets.session_list import format_session_text
|
||||||
|
|
||||||
text = format_session_text("feature-x", "claude", "idle", "●", "feature-x")
|
text = format_session_text("feature-x", "claude", "idle", "●", "feature-x")
|
||||||
assert "⎇" not in text
|
assert "⎇" in text
|
||||||
|
# Glyph appears exactly once, and the branch is not duplicated.
|
||||||
|
assert text.count("⎇") == 1
|
||||||
|
assert "⎇ feature-x" in text
|
||||||
|
assert "feature-x ⎇" not in text
|
||||||
|
|
||||||
|
|
||||||
def test_format_session_text_no_marker_for_plain_session():
|
def test_format_session_text_no_marker_for_plain_session():
|
||||||
|
|||||||
Reference in New Issue
Block a user