feat(desktop): detect and surface gone branches for cleanup

Add `gone` branch detection (upstream tracking ref deleted on remote) to
the composer coding rail and the sidebar project tree, plus a bulk archive
action for sessions on gone branches.

Part 1 — composer "gone" indicator:
- Add `_branch_gone()` to `web_git.py` (remote/REST path) and
  `branchGone()` to `git-review-ops.ts` (Electron local path), both using
  `git for-each-ref --format=%(upstream:track)`.
- Add `gone: boolean` to `HermesRepoStatus` type.
- Show amber "gone" badge in `CodingStatusRow` next to the branch name.
- Rides existing repo-status refresh edges (cwd change, workspace change
  tick, busy→idle, window focus) — no new polling.

Part 2a — session DB staleness fix:
- Re-probe `git_branch`/`git_repo_root` at turn-complete (gateway
  `_run_prompt_submit` finally block) and on session resume (both
  deferred and eager paths), using the existing `_persist_session_git_meta`
  daemon-thread helper. Keeps the session DB's branch column fresh so the
  sidebar tree and gone-branch cleanup are accurate.

Part 2b — sidebar gone-branch detection + cleanup UI:
- Add `gone_branches()` to `git_probe.py` (one `for-each-ref` per repo).
- Add `gone_fn` parameter to `project_tree.build_tree()` + `_annotate_gone()`
  helper that marks branch lanes with `gone: True`.
- Wire `_gone_branches_for_repo` into the gateway's `_build_project_tree`.
- Add `gone?: boolean` to `SidebarSessionGroup` (renderer type).
- Show amber "gone" badge on sidebar branch lanes in `WorkspaceHeader`.
- Add bulk "Archive sessions" action to `WorkspaceMenu` for gone lanes:
  confirms, optimistically tombstones, calls `setSessionArchived` per
  session, surfaces success/failure toasts.
- i18n strings for all 4 locales (en, ja, zh, zh-hant).

Tests:
- `test_status_gone_after_remote_branch_deleted` — E2E with bare remote,
  push, delete, fetch --prune, verify gone=true.
- `test_gone_fn_annotates_branch_lanes` — project tree annotation.
- `test_gone_fn_absent_leaves_no_gone_field` — no spurious gone field.
- Updated `sampleStatus` in coding-status.test.ts with `gone: false`.
This commit is contained in:
ethernet 2026-07-15 17:41:31 -04:00
parent b80b52aa46
commit db5cbff884
18 changed files with 315 additions and 17 deletions

View file

@ -150,6 +150,20 @@ def _default_branch_name(cwd: str) -> str | None:
return None
def _branch_gone(cwd: str, branch: str | None) -> bool:
"""True when ``branch``'s upstream tracking ref has been deleted on the remote.
This is the signal that a branch was merged (or deleted) remotely and the
local branch is now stale the exact state ``git branch --list`` shows as
``[gone]``. Returns False for detached HEAD, no upstream, or a live
upstream.
"""
if not branch:
return False
track = _git_out(cwd, ["for-each-ref", "--format=%(upstream:track)", f"refs/heads/{branch}"])
return "[gone]" in track
# ── porcelain v2 status parsing ──────────────────────────────────────────────
@ -239,6 +253,7 @@ def repo_status(cwd: str) -> dict | None:
"branch": branch,
"defaultBranch": _default_branch_name(cwd),
"detached": detached,
"gone": _branch_gone(cwd, branch),
"ahead": ahead,
"behind": behind,
"staged": sum(f["staged"] for f in files),