Add git widget to session cards in dashboard

This commit is contained in:
Jared Miller 2026-01-28 14:58:35 -05:00
parent 599f911964
commit ae0251b6d3
Signed by: shmup
GPG key ID: 22B5C6D66A38B06C

View file

@ -500,6 +500,69 @@
font-size: 11px; font-size: 11px;
} }
.git-line {
font-family: monospace;
}
.git-icon {
margin-right: 4px;
opacity: 0.7;
}
.git-additions {
color: #4CAF50;
}
.git-deletions {
color: #f44336;
}
.git-file {
font-family: monospace;
font-size: 0.85em;
}
.git-file-modified {
color: #ff9800;
}
.git-file-added {
color: #4CAF50;
}
.git-file-deleted {
color: #f44336;
}
.git-file-untracked {
color: #9e9e9e;
}
.git-files {
margin-top: 8px;
}
.git-files summary {
cursor: pointer;
user-select: none;
padding: 4px 0;
}
.git-files summary:hover {
opacity: 0.8;
}
.git-file-list {
margin-top: 4px;
padding-left: 16px;
}
.git-file-more {
color: #666;
font-style: italic;
margin-top: 4px;
}
.empty-state { .empty-state {
padding: 32px 16px; padding: 32px 16px;
text-align: center; text-align: center;
@ -696,6 +759,8 @@
mode: 'normal', mode: 'normal',
model: null, model: null,
idle_since: null, idle_since: null,
git_branch: null,
git_files_json: null,
}); });
renderSessions(); renderSessions();
}); });
@ -758,6 +823,16 @@
} }
}); });
es.addEventListener('git', (e) => {
const data = JSON.parse(e.data);
const session = state.sessions.get(data.session_id);
if (session) {
session.git_branch = data.branch;
session.git_files_json = data.files_json;
updateStatsWidget(data.session_id);
}
});
state.eventSource = es; state.eventSource = es;
} }
@ -790,6 +865,14 @@
return `${Math.floor(seconds / 3600)}h`; return `${Math.floor(seconds / 3600)}h`;
} }
function getFileClass(status) {
if (status === 'M') return 'git-file-modified';
if (status === 'A') return 'git-file-added';
if (status === 'D') return 'git-file-deleted';
if (status === '??') return 'git-file-untracked';
return '';
}
function renderStatsWidget(session) { function renderStatsWidget(session) {
const lines = []; const lines = [];
@ -831,6 +914,74 @@
lines.push(`<div class="stats-line">${badges.join(' ')}</div>`); lines.push(`<div class="stats-line">${badges.join(' ')}</div>`);
} }
// Git info
if (session.git_branch || session.git_files_json) {
const gitParts = [];
let files = [];
let totalAdditions = 0;
let totalDeletions = 0;
// Parse files if available
if (session.git_files_json) {
try {
files = JSON.parse(session.git_files_json);
files.forEach(f => {
totalAdditions += f.additions || 0;
totalDeletions += f.deletions || 0;
});
} catch (e) {
console.error('Failed to parse git files JSON:', e);
}
}
// Build git summary line
if (session.git_branch) {
gitParts.push(`<span class="git-icon"></span>`);
gitParts.push(`<span class="stats-value">${escapeHtml(session.git_branch)}</span>`);
}
if (files.length > 0) {
gitParts.push(`| <span class="stats-value">${files.length}</span> file${files.length === 1 ? '' : 's'}`);
}
if (totalAdditions > 0) {
gitParts.push(`| <span class="git-additions">+${totalAdditions}</span>`);
}
if (totalDeletions > 0) {
gitParts.push(`| <span class="git-deletions">-${totalDeletions}</span>`);
}
if (gitParts.length > 0) {
let gitLine = `<div class="stats-line git-line">${gitParts.join(' ')}</div>`;
// Add collapsible file list if there are files
if (files.length > 0) {
const fileListItems = files.slice(0, 10).map(f => {
const filePath = escapeHtml(f.path);
const fileClass = getFileClass(f.status);
const additions = f.additions ? `<span class="git-additions">+${f.additions}</span>` : '';
const deletions = f.deletions ? `<span class="git-deletions">-${f.deletions}</span>` : '';
return `<div class="git-file ${fileClass}">${escapeHtml(f.status)} ${filePath} ${additions} ${deletions}</div>`;
}).join('');
const moreText = files.length > 10 ? `<div class="git-file-more">and ${files.length - 10} more...</div>` : '';
gitLine += `
<details class="git-files">
<summary>${files.length} changed file${files.length === 1 ? '' : 's'}</summary>
<div class="git-file-list">
${fileListItems}
${moreText}
</div>
</details>
`;
}
lines.push(gitLine);
}
}
if (lines.length === 0) { if (lines.length === 0) {
return ''; return '';
} }