feat: workspace git detection with branch/status badge

When the workspace root is a git repo, a badge in the panel header
shows the current branch name, dirty file count, and ahead/behind
status. Updates on every root directory load.

Backend:
- git_info_for_workspace() in api/workspace.py runs lightweight git
  commands (rev-parse, status --porcelain, rev-list) with 3s timeout
- New GET /api/git-info endpoint returns branch, dirty count, modified,
  untracked, ahead, behind

Frontend:
- _refreshGitBadge() in workspace.js fetches git info on root load
- Git badge element in panel header shows branch + status
- Badge turns gold when workspace has uncommitted changes

Inspired by PR #75 (@MartinNielsenDev).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Nathan Esquenazi
2026-04-04 14:08:25 -07:00
parent e2d24f57ac
commit d8e6079a2c
5 changed files with 79 additions and 0 deletions

View File

@@ -59,9 +59,32 @@ async function loadDir(path){
clearPreview();
}
}
// Fetch git info for workspace root (non-blocking)
if(!path||path==='.') _refreshGitBadge();
}catch(e){console.warn('loadDir',e);}
}
async function _refreshGitBadge(){
const badge=$('gitBadge');
if(!badge||!S.session)return;
try{
const data=await api(`/api/git-info?session_id=${encodeURIComponent(S.session.session_id)}`);
if(data.git&&data.git.is_git){
const g=data.git;
let text=g.branch||'git';
if(g.dirty>0) text+=` \u00b7 ${g.dirty}\u2206`; // middot + delta
if(g.behind>0) text+=` \u2193${g.behind}`;
if(g.ahead>0) text+=` \u2191${g.ahead}`;
badge.textContent=text;
badge.className='git-badge'+(g.dirty>0?' dirty':'');
badge.style.display='';
} else {
badge.style.display='none';
badge.textContent='';
}
}catch(e){badge.style.display='none';}
}
function navigateUp(){
if(!S.session||S.currentDir==='.')return;
const parts=S.currentDir.split('/');