claudeck 1.1.1 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -4
- package/config/skillsmp-config.json +5 -0
- package/db.js +248 -0
- package/package.json +11 -2
- package/public/css/panels/git-panel.css +220 -0
- package/public/css/panels/skills-manager.css +975 -0
- package/public/css/ui/input-history.css +109 -0
- package/public/css/ui/messages.css +51 -0
- package/public/css/ui/notification-bell.css +421 -0
- package/public/css/ui/sessions.css +41 -0
- package/public/css/ui/worktree.css +442 -0
- package/public/index.html +43 -10
- package/public/js/core/api.js +83 -0
- package/public/js/core/dom.js +15 -0
- package/public/js/features/background-sessions.js +11 -0
- package/public/js/features/chat.js +501 -3
- package/public/js/features/input-history.js +122 -0
- package/public/js/features/projects.js +16 -1
- package/public/js/features/sessions.js +77 -30
- package/public/js/main.js +3 -0
- package/public/js/panels/git-panel.js +385 -6
- package/public/js/panels/skills-manager.js +1005 -0
- package/public/js/ui/messages.js +58 -0
- package/public/js/ui/notification-bell.js +240 -0
- package/public/js/ui/notification-history.js +210 -0
- package/public/js/ui/parallel.js +11 -0
- package/public/js/ui/tab-sdk.js +1 -1
- package/public/style.css +4 -0
- package/server/agent-loop.js +13 -0
- package/server/notification-logger.js +27 -0
- package/server/routes/notifications.js +57 -1
- package/server/routes/sessions.js +41 -0
- package/server/routes/skills.js +454 -0
- package/server/routes/worktrees.js +93 -0
- package/server/utils/git-worktree.js +297 -0
- package/server/ws-handler.js +708 -629
- package/server.js +17 -1
|
@@ -48,13 +48,32 @@ async function switchBranch(branch) {
|
|
|
48
48
|
|
|
49
49
|
try {
|
|
50
50
|
const result = await execCommand(`git checkout "${branch}"`, cwd);
|
|
51
|
-
if (result.error && result.stderr) {
|
|
52
|
-
|
|
51
|
+
if (result.exitCode !== 0 || (result.error && result.stderr)) {
|
|
52
|
+
// Show error to the user — typically "uncommitted changes" blocking the switch
|
|
53
|
+
const errMsg = (result.stderr || result.stdout || "").trim();
|
|
54
|
+
showBranchError(errMsg || "Cannot switch branch");
|
|
55
|
+
// Reset dropdown to actual current branch
|
|
56
|
+
await loadBranches();
|
|
57
|
+
return;
|
|
53
58
|
}
|
|
54
59
|
await refreshAll();
|
|
55
60
|
} catch {}
|
|
56
61
|
}
|
|
57
62
|
|
|
63
|
+
function showBranchError(msg) {
|
|
64
|
+
// Remove old error
|
|
65
|
+
const old = $.gitBranchSelect.parentElement.querySelector(".git-branch-error");
|
|
66
|
+
if (old) old.remove();
|
|
67
|
+
|
|
68
|
+
const el = document.createElement("div");
|
|
69
|
+
el.className = "git-branch-error";
|
|
70
|
+
el.textContent = msg.split("\n")[0]; // first line only
|
|
71
|
+
$.gitBranchSelect.parentElement.appendChild(el);
|
|
72
|
+
|
|
73
|
+
// Auto-dismiss after 5s
|
|
74
|
+
setTimeout(() => el.remove(), 5000);
|
|
75
|
+
}
|
|
76
|
+
|
|
58
77
|
// ── Status ──────────────────────────────────────────────
|
|
59
78
|
|
|
60
79
|
function parseStatusCode(x, y) {
|
|
@@ -136,7 +155,27 @@ function renderGroup(title, files, action) {
|
|
|
136
155
|
|
|
137
156
|
const group = document.createElement("div");
|
|
138
157
|
group.className = "git-status-group";
|
|
139
|
-
|
|
158
|
+
|
|
159
|
+
// Group header with bulk action button
|
|
160
|
+
const bulkLabel = action === "stage" ? "Stage All" : "Unstage All";
|
|
161
|
+
const bulkSymbol = action === "stage" ? "++" : "\u2212\u2212";
|
|
162
|
+
group.innerHTML = `
|
|
163
|
+
<div class="git-status-group-title">
|
|
164
|
+
<span>${title} (${files.length})</span>
|
|
165
|
+
<button class="git-bulk-action" title="${bulkLabel}">${bulkSymbol}</button>
|
|
166
|
+
</div>
|
|
167
|
+
`;
|
|
168
|
+
|
|
169
|
+
group.querySelector(".git-bulk-action").addEventListener("click", async () => {
|
|
170
|
+
const cwd = getCwd();
|
|
171
|
+
if (!cwd) return;
|
|
172
|
+
const fileArgs = files.map((f) => `"${f.file}"`).join(" ");
|
|
173
|
+
const cmd = action === "stage"
|
|
174
|
+
? `git add ${fileArgs}`
|
|
175
|
+
: `git reset HEAD ${fileArgs}`;
|
|
176
|
+
await execCommand(cmd, cwd);
|
|
177
|
+
await loadStatus();
|
|
178
|
+
});
|
|
140
179
|
|
|
141
180
|
for (const f of files) {
|
|
142
181
|
const row = document.createElement("div");
|
|
@@ -144,10 +183,16 @@ function renderGroup(title, files, action) {
|
|
|
144
183
|
row.innerHTML = `
|
|
145
184
|
<span class="git-status-badge ${f.cls}">${f.badge}</span>
|
|
146
185
|
<span class="git-status-name" title="${escapeHtml(f.file)}">${escapeHtml(f.file)}</span>
|
|
147
|
-
<button class="git-status-action" title="${action === "stage" ? "Stage" : "Unstage"}">${action === "stage" ? "+" : "\u2212"}</button>
|
|
186
|
+
<button class="git-status-action git-stage-btn" title="${action === "stage" ? "Stage" : "Unstage"}">${action === "stage" ? "+" : "\u2212"}</button>
|
|
148
187
|
`;
|
|
149
188
|
|
|
150
|
-
|
|
189
|
+
// File diff preview on name click
|
|
190
|
+
row.querySelector(".git-status-name").addEventListener("click", () => {
|
|
191
|
+
showFileDiff(f.file, action === "unstage", f.badge === "?");
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
// Stage / unstage button
|
|
195
|
+
row.querySelector(".git-stage-btn").addEventListener("click", async () => {
|
|
151
196
|
const cwd = getCwd();
|
|
152
197
|
if (!cwd) return;
|
|
153
198
|
const cmd = action === "stage"
|
|
@@ -157,12 +202,51 @@ function renderGroup(title, files, action) {
|
|
|
157
202
|
await loadStatus();
|
|
158
203
|
});
|
|
159
204
|
|
|
205
|
+
// Discard button — only for tracked changed files (not staged, not untracked)
|
|
206
|
+
if (action === "stage" && f.badge !== "?") {
|
|
207
|
+
const discardBtn = document.createElement("button");
|
|
208
|
+
discardBtn.className = "git-status-action git-discard-btn";
|
|
209
|
+
discardBtn.title = "Discard changes";
|
|
210
|
+
discardBtn.textContent = "\u2715";
|
|
211
|
+
discardBtn.addEventListener("click", async () => {
|
|
212
|
+
const cwd = getCwd();
|
|
213
|
+
if (!cwd) return;
|
|
214
|
+
await execCommand(`git checkout -- "${f.file}"`, cwd);
|
|
215
|
+
await loadStatus();
|
|
216
|
+
});
|
|
217
|
+
row.appendChild(discardBtn);
|
|
218
|
+
}
|
|
219
|
+
|
|
160
220
|
group.appendChild(row);
|
|
161
221
|
}
|
|
162
222
|
|
|
163
223
|
$.gitStatusList.appendChild(group);
|
|
164
224
|
}
|
|
165
225
|
|
|
226
|
+
// ── File Diff ──────────────────────────────────────────
|
|
227
|
+
|
|
228
|
+
async function showFileDiff(file, isStaged, isUntracked) {
|
|
229
|
+
const cwd = getCwd();
|
|
230
|
+
if (!cwd) return;
|
|
231
|
+
|
|
232
|
+
let diffText = "";
|
|
233
|
+
if (isUntracked) {
|
|
234
|
+
// Untracked file — show full content as additions
|
|
235
|
+
const result = await execCommand(`cat "${file}"`, cwd);
|
|
236
|
+
if (!result.error && result.stdout) {
|
|
237
|
+
diffText = result.stdout.split("\n").map((l) => "+" + l).join("\n");
|
|
238
|
+
}
|
|
239
|
+
} else if (isStaged) {
|
|
240
|
+
const result = await execCommand(`git diff --cached -- "${file}"`, cwd);
|
|
241
|
+
diffText = result.stdout || "";
|
|
242
|
+
} else {
|
|
243
|
+
const result = await execCommand(`git diff -- "${file}"`, cwd);
|
|
244
|
+
diffText = result.stdout || "";
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
showDiffModal(diffText, file);
|
|
248
|
+
}
|
|
249
|
+
|
|
166
250
|
// ── Commit ──────────────────────────────────────────────
|
|
167
251
|
|
|
168
252
|
async function handleCommit() {
|
|
@@ -230,17 +314,312 @@ async function loadLog() {
|
|
|
230
314
|
<span class="git-log-subject">${escapeHtml(subject || "")}</span>
|
|
231
315
|
<span class="git-log-time">${escapeHtml(time || "")}</span>
|
|
232
316
|
`;
|
|
317
|
+
|
|
318
|
+
// Clickable commit hash → show commit diff
|
|
319
|
+
item.querySelector(".git-log-hash").addEventListener("click", () => showCommitDiff(hash));
|
|
320
|
+
|
|
233
321
|
$.gitLogList.appendChild(item);
|
|
234
322
|
}
|
|
235
323
|
} catch {}
|
|
236
324
|
}
|
|
237
325
|
|
|
326
|
+
async function showCommitDiff(hash) {
|
|
327
|
+
const cwd = getCwd();
|
|
328
|
+
if (!cwd) return;
|
|
329
|
+
const result = await execCommand(`git show "${hash}" --stat --patch`, cwd);
|
|
330
|
+
if (!result.error) showDiffModal(result.stdout, `Commit ${hash}`);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// ── Branch Info ────────────────────────────────────────
|
|
334
|
+
|
|
335
|
+
async function loadBranchInfo() {
|
|
336
|
+
const cwd = getCwd();
|
|
337
|
+
if (!cwd || !$.gitBranchInfo) return;
|
|
338
|
+
|
|
339
|
+
try {
|
|
340
|
+
const branchResult = await execCommand("git rev-parse --abbrev-ref HEAD", cwd);
|
|
341
|
+
const branch = branchResult.stdout?.trim();
|
|
342
|
+
if (!branch || branchResult.error) {
|
|
343
|
+
$.gitBranchInfo.classList.add("hidden");
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Get ahead/behind tracking info
|
|
348
|
+
const trackResult = await execCommand(
|
|
349
|
+
"git rev-list --left-right --count HEAD...@{upstream}",
|
|
350
|
+
cwd
|
|
351
|
+
);
|
|
352
|
+
|
|
353
|
+
let trackingHtml = "";
|
|
354
|
+
if (!trackResult.error && trackResult.stdout?.trim()) {
|
|
355
|
+
const [ahead, behind] = trackResult.stdout.trim().split(/\s+/);
|
|
356
|
+
const parts = [];
|
|
357
|
+
if (parseInt(ahead) > 0) parts.push(`<span class="git-branch-ahead">\u2191${ahead}</span>`);
|
|
358
|
+
if (parseInt(behind) > 0) parts.push(`<span class="git-branch-behind">\u2193${behind}</span>`);
|
|
359
|
+
if (parts.length > 0) trackingHtml = parts.join(" ");
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
$.gitBranchInfo.classList.remove("hidden");
|
|
363
|
+
$.gitBranchInfo.innerHTML = `
|
|
364
|
+
<svg class="git-branch-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
365
|
+
<line x1="6" y1="3" x2="6" y2="15"/><circle cx="18" cy="6" r="3"/><circle cx="6" cy="18" r="3"/><path d="M18 9a9 9 0 0 1-9 9"/>
|
|
366
|
+
</svg>
|
|
367
|
+
<strong>${escapeHtml(branch)}</strong>
|
|
368
|
+
${trackingHtml ? `<span class="git-branch-tracking">${trackingHtml}</span>` : '<span class="git-branch-tracking git-branch-synced">in sync</span>'}
|
|
369
|
+
`;
|
|
370
|
+
} catch {
|
|
371
|
+
$.gitBranchInfo.classList.add("hidden");
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// ── Worktrees ──────────────────────────────────────────
|
|
376
|
+
|
|
377
|
+
async function loadWorktrees() {
|
|
378
|
+
const cwd = getCwd();
|
|
379
|
+
if (!cwd || !$.gitWorktreeSection) return;
|
|
380
|
+
|
|
381
|
+
try {
|
|
382
|
+
const res = await fetch(`/api/worktrees?project_path=${encodeURIComponent(cwd)}`);
|
|
383
|
+
const worktrees = await res.json();
|
|
384
|
+
const visible = worktrees.filter((wt) => wt.status === "active" || wt.status === "completed");
|
|
385
|
+
|
|
386
|
+
if (visible.length === 0) {
|
|
387
|
+
$.gitWorktreeSection.classList.add("hidden");
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
$.gitWorktreeSection.classList.remove("hidden");
|
|
392
|
+
$.gitWorktreeList.innerHTML = "";
|
|
393
|
+
|
|
394
|
+
for (const wt of visible) {
|
|
395
|
+
const badgeClass = wt.status === "active" ? "running" : "ready";
|
|
396
|
+
const item = document.createElement("div");
|
|
397
|
+
item.className = "git-worktree-item";
|
|
398
|
+
item.innerHTML = `
|
|
399
|
+
<span class="git-worktree-badge ${badgeClass}">${escapeHtml(wt.status)}</span>
|
|
400
|
+
<span class="git-worktree-name" title="${escapeHtml(wt.branch_name)}">${escapeHtml(wt.branch_name)}</span>
|
|
401
|
+
<div class="git-worktree-actions">
|
|
402
|
+
<button class="wt-view" data-tooltip="View Diff" title="View Diff">
|
|
403
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>
|
|
404
|
+
</button>
|
|
405
|
+
<button class="wt-merge" data-tooltip="Squash Merge" title="Squash Merge">
|
|
406
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="16 3 21 3 21 8"/><line x1="4" y1="20" x2="21" y2="3"/></svg>
|
|
407
|
+
</button>
|
|
408
|
+
<button class="wt-discard" data-tooltip="Discard Worktree" title="Discard Worktree">
|
|
409
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>
|
|
410
|
+
</button>
|
|
411
|
+
</div>
|
|
412
|
+
`;
|
|
413
|
+
|
|
414
|
+
item.querySelector(".wt-view").addEventListener("click", async () => {
|
|
415
|
+
try {
|
|
416
|
+
const r = await fetch(`/api/worktrees/${encodeURIComponent(wt.id)}/diff`);
|
|
417
|
+
const data = await r.json();
|
|
418
|
+
if (data.error) return;
|
|
419
|
+
showDiffModal(data.diff, `Worktree: ${wt.branch_name}`);
|
|
420
|
+
} catch {}
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
item.querySelector(".wt-merge").addEventListener("click", async (e) => {
|
|
424
|
+
const btn = e.currentTarget;
|
|
425
|
+
btn.disabled = true;
|
|
426
|
+
try {
|
|
427
|
+
const r = await fetch(`/api/worktrees/${encodeURIComponent(wt.id)}/merge`, {
|
|
428
|
+
method: "POST",
|
|
429
|
+
headers: { "Content-Type": "application/json" },
|
|
430
|
+
body: JSON.stringify({}),
|
|
431
|
+
});
|
|
432
|
+
const data = await r.json();
|
|
433
|
+
if (data.ok) {
|
|
434
|
+
loadWorktrees();
|
|
435
|
+
loadLog();
|
|
436
|
+
}
|
|
437
|
+
} catch {} finally {
|
|
438
|
+
btn.disabled = false;
|
|
439
|
+
}
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
item.querySelector(".wt-discard").addEventListener("click", (e) => {
|
|
443
|
+
const btn = e.currentTarget;
|
|
444
|
+
const actions = btn.closest(".git-worktree-actions");
|
|
445
|
+
|
|
446
|
+
// Show inline confirm/cancel replacing the action buttons
|
|
447
|
+
const confirm = document.createElement("div");
|
|
448
|
+
confirm.className = "git-wt-confirm";
|
|
449
|
+
confirm.innerHTML = `
|
|
450
|
+
<span class="git-wt-confirm-label">Delete?</span>
|
|
451
|
+
<button class="git-wt-confirm-yes" data-tooltip="Confirm delete" title="Confirm">Yes</button>
|
|
452
|
+
<button class="git-wt-confirm-no" data-tooltip="Cancel" title="Cancel">No</button>
|
|
453
|
+
`;
|
|
454
|
+
actions.classList.add("hidden");
|
|
455
|
+
actions.parentElement.appendChild(confirm);
|
|
456
|
+
|
|
457
|
+
confirm.querySelector(".git-wt-confirm-no").addEventListener("click", () => {
|
|
458
|
+
confirm.remove();
|
|
459
|
+
actions.classList.remove("hidden");
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
confirm.querySelector(".git-wt-confirm-yes").addEventListener("click", async () => {
|
|
463
|
+
confirm.querySelector(".git-wt-confirm-yes").disabled = true;
|
|
464
|
+
confirm.querySelector(".git-wt-confirm-no").disabled = true;
|
|
465
|
+
confirm.querySelector(".git-wt-confirm-label").textContent = "Deleting...";
|
|
466
|
+
try {
|
|
467
|
+
const r = await fetch(`/api/worktrees/${encodeURIComponent(wt.id)}`, { method: "DELETE" });
|
|
468
|
+
const data = await r.json();
|
|
469
|
+
if (data.ok) loadWorktrees();
|
|
470
|
+
} catch {
|
|
471
|
+
confirm.remove();
|
|
472
|
+
actions.classList.remove("hidden");
|
|
473
|
+
}
|
|
474
|
+
});
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
$.gitWorktreeList.appendChild(item);
|
|
478
|
+
}
|
|
479
|
+
} catch {
|
|
480
|
+
$.gitWorktreeSection.classList.add("hidden");
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Parse a unified diff into per-file sections.
|
|
486
|
+
* Splits on "diff --git" boundaries.
|
|
487
|
+
*/
|
|
488
|
+
function parseDiffSections(diffText) {
|
|
489
|
+
const sections = [];
|
|
490
|
+
const lines = diffText.split("\n");
|
|
491
|
+
let current = null;
|
|
492
|
+
|
|
493
|
+
for (const line of lines) {
|
|
494
|
+
if (line.startsWith("diff --git ")) {
|
|
495
|
+
if (current) sections.push(current);
|
|
496
|
+
// Extract filename from "diff --git a/path b/path"
|
|
497
|
+
const match = line.match(/diff --git a\/(.+?) b\/(.+)/);
|
|
498
|
+
const fileName = match ? match[2] : line;
|
|
499
|
+
current = { fileName, lines: [] };
|
|
500
|
+
} else if (current) {
|
|
501
|
+
current.lines.push(line);
|
|
502
|
+
} else {
|
|
503
|
+
// Lines before the first "diff --git" (e.g., commit stats from git show)
|
|
504
|
+
if (!sections.length && !current) {
|
|
505
|
+
current = { fileName: "", lines: [] };
|
|
506
|
+
}
|
|
507
|
+
if (current) current.lines.push(line);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
if (current) sections.push(current);
|
|
511
|
+
return sections;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* Count additions and deletions in diff lines.
|
|
516
|
+
*/
|
|
517
|
+
function countDiffStats(lines) {
|
|
518
|
+
let add = 0, del = 0;
|
|
519
|
+
for (const l of lines) {
|
|
520
|
+
if (l.startsWith("+") && !l.startsWith("+++")) add++;
|
|
521
|
+
else if (l.startsWith("-") && !l.startsWith("---")) del++;
|
|
522
|
+
}
|
|
523
|
+
return { add, del };
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
/**
|
|
527
|
+
* Render colored diff lines into a container element.
|
|
528
|
+
*/
|
|
529
|
+
function renderDiffLines(container, lines) {
|
|
530
|
+
for (const line of lines) {
|
|
531
|
+
const span = document.createElement("span");
|
|
532
|
+
span.textContent = line + "\n";
|
|
533
|
+
if (line.startsWith("+++") || line.startsWith("---")) {
|
|
534
|
+
span.className = "diff-line-meta";
|
|
535
|
+
} else if (line.startsWith("+")) {
|
|
536
|
+
span.className = "diff-line-added";
|
|
537
|
+
} else if (line.startsWith("-")) {
|
|
538
|
+
span.className = "diff-line-removed";
|
|
539
|
+
} else if (line.startsWith("@@")) {
|
|
540
|
+
span.className = "diff-line-hunk";
|
|
541
|
+
}
|
|
542
|
+
container.appendChild(span);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
function showDiffModal(diffText, title) {
|
|
547
|
+
const overlay = document.createElement("div");
|
|
548
|
+
overlay.className = "modal-overlay";
|
|
549
|
+
overlay.innerHTML = `
|
|
550
|
+
<div class="modal git-diff-modal">
|
|
551
|
+
<div class="modal-header">
|
|
552
|
+
<h3>${escapeHtml(title)}</h3>
|
|
553
|
+
<button class="modal-close">×</button>
|
|
554
|
+
</div>
|
|
555
|
+
<div class="git-diff-body"></div>
|
|
556
|
+
</div>
|
|
557
|
+
`;
|
|
558
|
+
|
|
559
|
+
const body = overlay.querySelector(".git-diff-body");
|
|
560
|
+
|
|
561
|
+
if (!diffText || !diffText.trim()) {
|
|
562
|
+
body.innerHTML = '<div class="git-diff-empty">(no changes)</div>';
|
|
563
|
+
} else {
|
|
564
|
+
const sections = parseDiffSections(diffText);
|
|
565
|
+
|
|
566
|
+
if (sections.length <= 1 && sections[0]?.fileName === "") {
|
|
567
|
+
// Single block without file headers (e.g., single file diff)
|
|
568
|
+
const pre = document.createElement("pre");
|
|
569
|
+
pre.className = "git-diff-content";
|
|
570
|
+
renderDiffLines(pre, sections[0]?.lines || diffText.split("\n"));
|
|
571
|
+
body.appendChild(pre);
|
|
572
|
+
} else {
|
|
573
|
+
// Multi-file diff — render per-file collapsible sections
|
|
574
|
+
for (const section of sections) {
|
|
575
|
+
const { add, del } = countDiffStats(section.lines);
|
|
576
|
+
|
|
577
|
+
const fileSection = document.createElement("div");
|
|
578
|
+
fileSection.className = "git-diff-file";
|
|
579
|
+
|
|
580
|
+
const header = document.createElement("div");
|
|
581
|
+
header.className = "git-diff-file-header";
|
|
582
|
+
header.innerHTML = `
|
|
583
|
+
<svg class="git-diff-chevron" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
584
|
+
<polyline points="6 9 12 15 18 9"/>
|
|
585
|
+
</svg>
|
|
586
|
+
<span class="git-diff-file-name">${escapeHtml(section.fileName)}</span>
|
|
587
|
+
<span class="git-diff-file-stats">
|
|
588
|
+
${add ? `<span class="diff-stat-add">+${add}</span>` : ""}
|
|
589
|
+
${del ? `<span class="diff-stat-del">-${del}</span>` : ""}
|
|
590
|
+
</span>
|
|
591
|
+
`;
|
|
592
|
+
|
|
593
|
+
const content = document.createElement("pre");
|
|
594
|
+
content.className = "git-diff-content git-diff-file-content";
|
|
595
|
+
renderDiffLines(content, section.lines);
|
|
596
|
+
|
|
597
|
+
// Toggle collapse on header click
|
|
598
|
+
header.addEventListener("click", () => {
|
|
599
|
+
fileSection.classList.toggle("collapsed");
|
|
600
|
+
});
|
|
601
|
+
|
|
602
|
+
fileSection.appendChild(header);
|
|
603
|
+
fileSection.appendChild(content);
|
|
604
|
+
body.appendChild(fileSection);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
overlay.querySelector(".modal-close").addEventListener("click", () => overlay.remove());
|
|
610
|
+
overlay.addEventListener("click", (e) => { if (e.target === overlay) overlay.remove(); });
|
|
611
|
+
document.addEventListener("keydown", function esc(e) {
|
|
612
|
+
if (e.key === "Escape") { overlay.remove(); document.removeEventListener("keydown", esc); }
|
|
613
|
+
});
|
|
614
|
+
document.body.appendChild(overlay);
|
|
615
|
+
}
|
|
616
|
+
|
|
238
617
|
// ── Refresh ─────────────────────────────────────────────
|
|
239
618
|
|
|
240
619
|
async function refreshAll() {
|
|
241
620
|
$.gitRefreshBtn.classList.add("spinning");
|
|
242
621
|
try {
|
|
243
|
-
await Promise.all([loadBranches(), loadStatus(), loadLog()]);
|
|
622
|
+
await Promise.all([loadBranches(), loadBranchInfo(), loadStatus(), loadLog(), loadWorktrees()]);
|
|
244
623
|
} finally {
|
|
245
624
|
$.gitRefreshBtn.classList.remove("spinning");
|
|
246
625
|
}
|