devguard 0.1.0 → 0.2.1
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 +37 -6
- package/dist/index.js +5 -3
- package/dist/tools/agent-check.d.ts +2 -0
- package/dist/tools/agent-check.js +62 -0
- package/dist/tools/agent-list.d.ts +2 -0
- package/dist/tools/agent-list.js +70 -0
- package/dist/tools/agent-register.d.ts +2 -0
- package/dist/tools/agent-register.js +105 -0
- package/dist/tools/agent-update.d.ts +2 -0
- package/dist/tools/agent-update.js +111 -0
- package/dist/tools/branch-map.d.ts +2 -0
- package/dist/tools/branch-map.js +108 -0
- package/dist/tools/catch-me-up.js +76 -3
- package/dist/tools/search-entries.d.ts +2 -0
- package/dist/tools/search-entries.js +78 -0
- package/dist/tools/setup.js +5 -5
- package/dist/tools/write-entry.js +8 -1
- package/dist/utils/auto-setup.js +5 -5
- package/dist/utils/branch-map-html.d.ts +19 -0
- package/dist/utils/branch-map-html.js +1195 -0
- package/dist/utils/collision.d.ts +22 -0
- package/dist/utils/collision.js +104 -0
- package/dist/utils/git.d.ts +25 -0
- package/dist/utils/git.js +74 -0
- package/dist/utils/storage.d.ts +19 -0
- package/dist/utils/storage.js +105 -0
- package/package.json +1 -1
|
@@ -0,0 +1,1195 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateAndOpenBranchMap = generateAndOpenBranchMap;
|
|
4
|
+
const fs_1 = require("fs");
|
|
5
|
+
const path_1 = require("path");
|
|
6
|
+
const child_process_1 = require("child_process");
|
|
7
|
+
function generateAndOpenBranchMap(projectPath, branches, events) {
|
|
8
|
+
const projectName = projectPath.split("/").pop() || "project";
|
|
9
|
+
const html = buildHtml(branches, projectName);
|
|
10
|
+
const dir = (0, path_1.join)(projectPath, ".devguard");
|
|
11
|
+
(0, fs_1.mkdirSync)(dir, { recursive: true });
|
|
12
|
+
const filePath = (0, path_1.join)(dir, "branch-map.html");
|
|
13
|
+
(0, fs_1.writeFileSync)(filePath, html, "utf-8");
|
|
14
|
+
try {
|
|
15
|
+
const platform = process.platform;
|
|
16
|
+
if (platform === "darwin") {
|
|
17
|
+
(0, child_process_1.execSync)(`open "${filePath}"`, { timeout: 5000 });
|
|
18
|
+
}
|
|
19
|
+
else if (platform === "linux") {
|
|
20
|
+
(0, child_process_1.execSync)(`xdg-open "${filePath}"`, { timeout: 5000 });
|
|
21
|
+
}
|
|
22
|
+
else if (platform === "win32") {
|
|
23
|
+
(0, child_process_1.execSync)(`start "" "${filePath}"`, { timeout: 5000 });
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
// Silently fail — file is still written
|
|
28
|
+
}
|
|
29
|
+
return filePath;
|
|
30
|
+
}
|
|
31
|
+
function esc(str) {
|
|
32
|
+
return str
|
|
33
|
+
.replace(/&/g, "&")
|
|
34
|
+
.replace(/</g, "<")
|
|
35
|
+
.replace(/>/g, ">")
|
|
36
|
+
.replace(/"/g, """)
|
|
37
|
+
.replace(/'/g, "'");
|
|
38
|
+
}
|
|
39
|
+
function buildSummaryCell(b) {
|
|
40
|
+
if (b.diaryEntries.length === 0) {
|
|
41
|
+
return esc(b.summary);
|
|
42
|
+
}
|
|
43
|
+
const allChanges = [];
|
|
44
|
+
const allDecisions = [];
|
|
45
|
+
const allIssues = [];
|
|
46
|
+
const titles = [];
|
|
47
|
+
for (const entry of b.diaryEntries) {
|
|
48
|
+
if (entry.title)
|
|
49
|
+
titles.push(entry.title);
|
|
50
|
+
allChanges.push(...entry.whatChanged);
|
|
51
|
+
allDecisions.push(...entry.decisions);
|
|
52
|
+
allIssues.push(...entry.issues);
|
|
53
|
+
}
|
|
54
|
+
const nextSteps = b.diaryEntries[b.diaryEntries.length - 1]?.nextSteps || [];
|
|
55
|
+
let html = `<div class="summary-title" onclick="toggleSummary(event)">${esc(b.summary)} <span class="summary-toggle">▼</span></div>`;
|
|
56
|
+
html += `<div class="summary-details collapsed">`;
|
|
57
|
+
if (titles.length > 1) {
|
|
58
|
+
html += `<div class="summary-timeline"><span class="section-label">Timeline</span> ${titles.map(t => esc(t)).join(" → ")}</div>`;
|
|
59
|
+
}
|
|
60
|
+
if (allChanges.length > 0) {
|
|
61
|
+
html += `<div class="summary-section changes"><span class="section-label">Changes</span><ul>${allChanges.map(c => `<li>${esc(c)}</li>`).join("")}</ul></div>`;
|
|
62
|
+
}
|
|
63
|
+
if (allDecisions.length > 0) {
|
|
64
|
+
html += `<div class="summary-section decisions"><span class="section-label">Decisions</span><ul>${allDecisions.map(d => `<li>${esc(d)}</li>`).join("")}</ul></div>`;
|
|
65
|
+
}
|
|
66
|
+
if (allIssues.length > 0) {
|
|
67
|
+
html += `<div class="summary-section issues"><span class="section-label">Issues</span><ul>${allIssues.map(i => `<li>${esc(i)}</li>`).join("")}</ul></div>`;
|
|
68
|
+
}
|
|
69
|
+
if (nextSteps.length > 0) {
|
|
70
|
+
html += `<div class="summary-section next-steps"><span class="section-label">Next</span><ul>${nextSteps.map(n => `<li>${esc(n)}</li>`).join("")}</ul></div>`;
|
|
71
|
+
}
|
|
72
|
+
html += `</div>`;
|
|
73
|
+
return html;
|
|
74
|
+
}
|
|
75
|
+
function categorizeCommit(message) {
|
|
76
|
+
const msg = message.toLowerCase();
|
|
77
|
+
if (msg.startsWith("fix") || msg.includes("bug") || msg.includes("patch") || msg.includes("hotfix"))
|
|
78
|
+
return "Bug Fix";
|
|
79
|
+
if (msg.startsWith("feat") || msg.startsWith("add") || msg.startsWith("implement") || msg.startsWith("create"))
|
|
80
|
+
return "Feature";
|
|
81
|
+
if (msg.startsWith("refactor") || msg.startsWith("restructure") || msg.startsWith("reorganize") || msg.startsWith("clean"))
|
|
82
|
+
return "Refactor";
|
|
83
|
+
if (msg.startsWith("doc") || msg.startsWith("readme") || msg.includes("comment"))
|
|
84
|
+
return "Documentation";
|
|
85
|
+
if (msg.startsWith("test") || msg.includes("spec"))
|
|
86
|
+
return "Testing";
|
|
87
|
+
if (msg.startsWith("style") || msg.startsWith("format") || msg.startsWith("lint"))
|
|
88
|
+
return "Styling";
|
|
89
|
+
if (msg.startsWith("build") || msg.startsWith("ci") || msg.includes("deploy") || msg.includes("pipeline"))
|
|
90
|
+
return "Build/CI";
|
|
91
|
+
if (msg.startsWith("update") || msg.startsWith("upgrade") || msg.startsWith("bump"))
|
|
92
|
+
return "Update";
|
|
93
|
+
if (msg.startsWith("revert"))
|
|
94
|
+
return "Revert";
|
|
95
|
+
if (msg.startsWith("merge"))
|
|
96
|
+
return "Merge";
|
|
97
|
+
if (msg.startsWith("set up") || msg.startsWith("setup") || msg.startsWith("init") || msg.startsWith("initial"))
|
|
98
|
+
return "Setup";
|
|
99
|
+
return "Change";
|
|
100
|
+
}
|
|
101
|
+
function describeAreas(files) {
|
|
102
|
+
const areas = new Map();
|
|
103
|
+
for (const f of files) {
|
|
104
|
+
const parts = f.split("/");
|
|
105
|
+
let area;
|
|
106
|
+
if (parts.length === 1) {
|
|
107
|
+
area = "root config";
|
|
108
|
+
}
|
|
109
|
+
else if (parts[0] === "src" && parts.length >= 3) {
|
|
110
|
+
area = parts[1]; // e.g. "tools", "utils", "components"
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
area = parts[0];
|
|
114
|
+
}
|
|
115
|
+
if (!areas.has(area))
|
|
116
|
+
areas.set(area, []);
|
|
117
|
+
areas.get(area).push(parts[parts.length - 1]);
|
|
118
|
+
}
|
|
119
|
+
return [...areas.entries()]
|
|
120
|
+
.map(([area, fileNames]) => {
|
|
121
|
+
const label = area.replace(/[-_]/g, " ");
|
|
122
|
+
if (fileNames.length <= 2)
|
|
123
|
+
return `${label} (${fileNames.join(", ")})`;
|
|
124
|
+
return `${label} (${fileNames.length} files)`;
|
|
125
|
+
})
|
|
126
|
+
.join(", ");
|
|
127
|
+
}
|
|
128
|
+
function generateCommitSummary(c) {
|
|
129
|
+
const files = c.files || [];
|
|
130
|
+
const ins = c.insertions || 0;
|
|
131
|
+
const del = c.deletions || 0;
|
|
132
|
+
const body = c.body || "";
|
|
133
|
+
const category = categorizeCommit(c.message);
|
|
134
|
+
const parts = [];
|
|
135
|
+
// Category + commit message
|
|
136
|
+
parts.push(`[${category}] ${c.message}`);
|
|
137
|
+
// Commit body if present (first meaningful line)
|
|
138
|
+
if (body) {
|
|
139
|
+
const bodyLines = body.split("\n").map(l => l.trim()).filter(l => l && !l.startsWith("Co-Authored") && !l.startsWith("Signed-off"));
|
|
140
|
+
if (bodyLines.length > 0) {
|
|
141
|
+
parts.push(bodyLines.slice(0, 2).join(". "));
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// Areas affected
|
|
145
|
+
if (files.length > 0) {
|
|
146
|
+
parts.push(`Areas: ${describeAreas(files)}`);
|
|
147
|
+
}
|
|
148
|
+
// Scale of change
|
|
149
|
+
if (ins > 0 || del > 0) {
|
|
150
|
+
const net = ins - del;
|
|
151
|
+
const scale = ins + del > 500 ? "large" : ins + del > 100 ? "moderate" : "small";
|
|
152
|
+
parts.push(`${scale} change (+${ins}/-${del}, net ${net >= 0 ? "+" : ""}${net} lines)`);
|
|
153
|
+
}
|
|
154
|
+
return parts.join(" — ");
|
|
155
|
+
}
|
|
156
|
+
/** Serialize all branch data as JSON for the branch viewer JS */
|
|
157
|
+
function buildBranchDataJson(branches) {
|
|
158
|
+
const data = branches.map(b => {
|
|
159
|
+
const diaryByCommit = new Map();
|
|
160
|
+
for (const entry of b.diaryEntries) {
|
|
161
|
+
if (entry.commit)
|
|
162
|
+
diaryByCommit.set(entry.commit, entry);
|
|
163
|
+
}
|
|
164
|
+
return {
|
|
165
|
+
name: b.name,
|
|
166
|
+
isCurrent: b.isCurrent,
|
|
167
|
+
isMain: b.isMain,
|
|
168
|
+
summary: b.summary,
|
|
169
|
+
ahead: b.ahead,
|
|
170
|
+
behind: b.behind,
|
|
171
|
+
filesChanged: b.filesChanged,
|
|
172
|
+
commits: b.commits.map(c => ({
|
|
173
|
+
shortHash: c.shortHash,
|
|
174
|
+
message: c.message,
|
|
175
|
+
author: c.author,
|
|
176
|
+
date: c.date,
|
|
177
|
+
timestamp: c.timestamp,
|
|
178
|
+
files: c.files || [],
|
|
179
|
+
insertions: c.insertions || 0,
|
|
180
|
+
deletions: c.deletions || 0,
|
|
181
|
+
commitSummary: diaryByCommit.has(c.shortHash)
|
|
182
|
+
? diaryByCommit.get(c.shortHash).summary
|
|
183
|
+
: generateCommitSummary(c),
|
|
184
|
+
diary: diaryByCommit.has(c.shortHash) ? {
|
|
185
|
+
title: diaryByCommit.get(c.shortHash).title,
|
|
186
|
+
summary: diaryByCommit.get(c.shortHash).summary,
|
|
187
|
+
whatChanged: diaryByCommit.get(c.shortHash).whatChanged,
|
|
188
|
+
decisions: diaryByCommit.get(c.shortHash).decisions,
|
|
189
|
+
issues: diaryByCommit.get(c.shortHash).issues,
|
|
190
|
+
nextSteps: diaryByCommit.get(c.shortHash).nextSteps,
|
|
191
|
+
} : null,
|
|
192
|
+
})),
|
|
193
|
+
};
|
|
194
|
+
});
|
|
195
|
+
return JSON.stringify(data);
|
|
196
|
+
}
|
|
197
|
+
function buildHtml(branches, projectName) {
|
|
198
|
+
const now = new Date().toLocaleString();
|
|
199
|
+
const branchDataJson = buildBranchDataJson(branches);
|
|
200
|
+
const rows = branches.map((b, idx) => {
|
|
201
|
+
const badge = b.isCurrent
|
|
202
|
+
? `<span class="badge current">current</span>`
|
|
203
|
+
: b.isMain
|
|
204
|
+
? `<span class="badge main">main</span>`
|
|
205
|
+
: "";
|
|
206
|
+
const status = b.isMain
|
|
207
|
+
? "\u2014"
|
|
208
|
+
: b.ahead > 0 || b.behind > 0
|
|
209
|
+
? `${b.ahead} ahead / ${b.behind} behind`
|
|
210
|
+
: "even";
|
|
211
|
+
const files = b.isMain ? "\u2014" : `${b.filesChanged}`;
|
|
212
|
+
const lastCommit = b.commits.length > 0
|
|
213
|
+
? `<span class="hash">${esc(b.commits[0].shortHash)}</span> ${esc(b.commits[0].message)}`
|
|
214
|
+
: "\u2014";
|
|
215
|
+
const lastDate = b.commits.length > 0
|
|
216
|
+
? new Date(b.commits[0].timestamp * 1000).toLocaleDateString()
|
|
217
|
+
: "\u2014";
|
|
218
|
+
const summaryHtml = buildSummaryCell(b);
|
|
219
|
+
const commitCount = b.commits.length;
|
|
220
|
+
const diaryCount = b.diaryEntries.filter(e => e.commit).length;
|
|
221
|
+
const expandHint = commitCount > 0
|
|
222
|
+
? `<span class="expand-hint">${commitCount} commits${diaryCount > 0 ? `, ${diaryCount} logged` : ""} — click to explore</span>`
|
|
223
|
+
: "";
|
|
224
|
+
return `<tr class="branch-row ${b.isCurrent ? "current-row" : ""}" onclick="openBranchViewer(${idx})" data-branch="${idx}">
|
|
225
|
+
<td class="branch-name">
|
|
226
|
+
<span class="expand-arrow">▶</span>
|
|
227
|
+
${esc(b.name)} ${badge}
|
|
228
|
+
</td>
|
|
229
|
+
<td class="summary-cell">${summaryHtml}${expandHint}</td>
|
|
230
|
+
<td class="center">${status}</td>
|
|
231
|
+
<td class="center">${files}</td>
|
|
232
|
+
<td class="commit">${lastCommit}</td>
|
|
233
|
+
<td class="center">${lastDate}</td>
|
|
234
|
+
</tr>`;
|
|
235
|
+
});
|
|
236
|
+
return `<!DOCTYPE html>
|
|
237
|
+
<html lang="en">
|
|
238
|
+
<head>
|
|
239
|
+
<meta charset="UTF-8">
|
|
240
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
241
|
+
<title>Branch Map \u2014 ${esc(projectName)}</title>
|
|
242
|
+
<style>
|
|
243
|
+
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap');
|
|
244
|
+
|
|
245
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
246
|
+
|
|
247
|
+
body {
|
|
248
|
+
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
|
|
249
|
+
background: #1a1a2e;
|
|
250
|
+
color: #e0e0e0;
|
|
251
|
+
overflow-x: hidden;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/* ========== TABLE VIEW ========== */
|
|
255
|
+
|
|
256
|
+
#table-view {
|
|
257
|
+
padding: 32px;
|
|
258
|
+
transition: opacity 0.3s, transform 0.3s;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
#table-view.hidden {
|
|
262
|
+
opacity: 0;
|
|
263
|
+
transform: translateX(-40px);
|
|
264
|
+
pointer-events: none;
|
|
265
|
+
position: absolute;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
h1 {
|
|
269
|
+
font-size: 20px;
|
|
270
|
+
font-weight: 600;
|
|
271
|
+
margin-bottom: 4px;
|
|
272
|
+
color: #ffffff;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
.subtitle {
|
|
276
|
+
font-size: 13px;
|
|
277
|
+
color: #888;
|
|
278
|
+
margin-bottom: 24px;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
table {
|
|
282
|
+
width: 100%;
|
|
283
|
+
border-collapse: collapse;
|
|
284
|
+
font-size: 13px;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
th {
|
|
288
|
+
text-align: left;
|
|
289
|
+
padding: 10px 14px;
|
|
290
|
+
background: #16213e;
|
|
291
|
+
color: #aaa;
|
|
292
|
+
font-weight: 500;
|
|
293
|
+
font-size: 11px;
|
|
294
|
+
text-transform: uppercase;
|
|
295
|
+
letter-spacing: 0.5px;
|
|
296
|
+
border-bottom: 2px solid #0f3460;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
th.center, td.center { text-align: center; }
|
|
300
|
+
|
|
301
|
+
td {
|
|
302
|
+
padding: 10px 14px;
|
|
303
|
+
border-bottom: 1px solid #222244;
|
|
304
|
+
vertical-align: top;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
.branch-row { cursor: pointer; transition: background 0.15s; }
|
|
308
|
+
.branch-row:hover { background: #16213e; }
|
|
309
|
+
.branch-row.current-row { background: #1a2744; }
|
|
310
|
+
.branch-row.current-row:hover { background: #1e2f52; }
|
|
311
|
+
|
|
312
|
+
.expand-arrow {
|
|
313
|
+
display: inline-block;
|
|
314
|
+
font-size: 10px;
|
|
315
|
+
margin-right: 6px;
|
|
316
|
+
color: #666;
|
|
317
|
+
}
|
|
318
|
+
.branch-row:hover .expand-arrow { color: #4fc3f7; }
|
|
319
|
+
|
|
320
|
+
.expand-hint {
|
|
321
|
+
display: block;
|
|
322
|
+
font-size: 10px;
|
|
323
|
+
color: #555;
|
|
324
|
+
margin-top: 4px;
|
|
325
|
+
font-style: italic;
|
|
326
|
+
}
|
|
327
|
+
.branch-row:hover .expand-hint { color: #4fc3f7; }
|
|
328
|
+
|
|
329
|
+
.branch-name {
|
|
330
|
+
font-family: 'JetBrains Mono', monospace;
|
|
331
|
+
font-weight: 500;
|
|
332
|
+
white-space: nowrap;
|
|
333
|
+
color: #ffffff;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
.badge {
|
|
337
|
+
display: inline-block;
|
|
338
|
+
font-family: 'Inter', sans-serif;
|
|
339
|
+
font-size: 10px;
|
|
340
|
+
font-weight: 500;
|
|
341
|
+
padding: 2px 7px;
|
|
342
|
+
border-radius: 4px;
|
|
343
|
+
margin-left: 6px;
|
|
344
|
+
vertical-align: middle;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
.badge.current {
|
|
348
|
+
background: #0f3460;
|
|
349
|
+
color: #4fc3f7;
|
|
350
|
+
border: 1px solid #4fc3f7;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
.badge.main {
|
|
354
|
+
background: #1b4332;
|
|
355
|
+
color: #52b788;
|
|
356
|
+
border: 1px solid #52b788;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
.commit {
|
|
360
|
+
font-size: 12px;
|
|
361
|
+
color: #bbb;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
.hash {
|
|
365
|
+
font-family: 'JetBrains Mono', monospace;
|
|
366
|
+
color: #888;
|
|
367
|
+
font-size: 11px;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
.summary-cell { max-width: 500px; }
|
|
371
|
+
|
|
372
|
+
.summary-title {
|
|
373
|
+
font-weight: 500;
|
|
374
|
+
color: #e0e0e0;
|
|
375
|
+
margin-bottom: 6px;
|
|
376
|
+
cursor: pointer;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
.summary-title:hover { color: #4fc3f7; }
|
|
380
|
+
|
|
381
|
+
.summary-toggle {
|
|
382
|
+
font-size: 9px;
|
|
383
|
+
color: #555;
|
|
384
|
+
margin-left: 4px;
|
|
385
|
+
display: inline-block;
|
|
386
|
+
transition: transform 0.2s;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
.summary-title:hover .summary-toggle { color: #4fc3f7; }
|
|
390
|
+
|
|
391
|
+
.summary-details {
|
|
392
|
+
overflow: hidden;
|
|
393
|
+
max-height: 500px;
|
|
394
|
+
transition: max-height 0.3s ease, opacity 0.3s ease;
|
|
395
|
+
opacity: 1;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
.summary-details.collapsed {
|
|
399
|
+
max-height: 0;
|
|
400
|
+
opacity: 0;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
.summary-timeline {
|
|
404
|
+
font-size: 11px;
|
|
405
|
+
color: #888;
|
|
406
|
+
margin-bottom: 6px;
|
|
407
|
+
line-height: 1.4;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
.summary-section {
|
|
411
|
+
font-size: 11px;
|
|
412
|
+
margin-bottom: 4px;
|
|
413
|
+
line-height: 1.4;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
.summary-section ul {
|
|
417
|
+
margin: 2px 0 0 14px;
|
|
418
|
+
padding: 0;
|
|
419
|
+
color: #bbb;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
.summary-section li { margin-bottom: 1px; }
|
|
423
|
+
|
|
424
|
+
.section-label {
|
|
425
|
+
font-weight: 500;
|
|
426
|
+
font-size: 10px;
|
|
427
|
+
text-transform: uppercase;
|
|
428
|
+
letter-spacing: 0.3px;
|
|
429
|
+
margin-right: 4px;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
.changes .section-label { color: #4fc3f7; }
|
|
433
|
+
.decisions .section-label { color: #ce93d8; }
|
|
434
|
+
.issues .section-label { color: #ef9a9a; }
|
|
435
|
+
.next-steps .section-label { color: #a5d6a7; }
|
|
436
|
+
.summary-timeline .section-label { color: #ffcc80; }
|
|
437
|
+
|
|
438
|
+
/* ========== BRANCH VIEWER ========== */
|
|
439
|
+
|
|
440
|
+
#branch-viewer {
|
|
441
|
+
position: fixed;
|
|
442
|
+
top: 0;
|
|
443
|
+
left: 0;
|
|
444
|
+
right: 0;
|
|
445
|
+
bottom: 0;
|
|
446
|
+
background: #1a1a2e;
|
|
447
|
+
z-index: 100;
|
|
448
|
+
display: flex;
|
|
449
|
+
flex-direction: column;
|
|
450
|
+
opacity: 0;
|
|
451
|
+
transform: translateX(40px);
|
|
452
|
+
pointer-events: none;
|
|
453
|
+
transition: opacity 0.3s, transform 0.3s;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
#branch-viewer.active {
|
|
457
|
+
opacity: 1;
|
|
458
|
+
transform: translateX(0);
|
|
459
|
+
pointer-events: all;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/* --- Viewer Header --- */
|
|
463
|
+
|
|
464
|
+
.viewer-header {
|
|
465
|
+
display: flex;
|
|
466
|
+
align-items: center;
|
|
467
|
+
gap: 16px;
|
|
468
|
+
padding: 16px 24px;
|
|
469
|
+
background: #16213e;
|
|
470
|
+
border-bottom: 2px solid #0f3460;
|
|
471
|
+
flex-shrink: 0;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
.back-btn {
|
|
475
|
+
background: none;
|
|
476
|
+
border: 1px solid #333;
|
|
477
|
+
color: #4fc3f7;
|
|
478
|
+
border-radius: 6px;
|
|
479
|
+
padding: 6px 14px;
|
|
480
|
+
font-size: 13px;
|
|
481
|
+
cursor: pointer;
|
|
482
|
+
font-family: 'Inter', sans-serif;
|
|
483
|
+
transition: all 0.15s;
|
|
484
|
+
display: flex;
|
|
485
|
+
align-items: center;
|
|
486
|
+
gap: 6px;
|
|
487
|
+
}
|
|
488
|
+
.back-btn:hover { background: #1a2744; border-color: #4fc3f7; }
|
|
489
|
+
|
|
490
|
+
.viewer-branch-name {
|
|
491
|
+
font-family: 'JetBrains Mono', monospace;
|
|
492
|
+
font-size: 18px;
|
|
493
|
+
font-weight: 600;
|
|
494
|
+
color: #fff;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
.viewer-meta {
|
|
498
|
+
display: flex;
|
|
499
|
+
gap: 16px;
|
|
500
|
+
margin-left: auto;
|
|
501
|
+
font-size: 12px;
|
|
502
|
+
color: #888;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
.viewer-meta-item {
|
|
506
|
+
display: flex;
|
|
507
|
+
align-items: center;
|
|
508
|
+
gap: 4px;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
.viewer-meta-value {
|
|
512
|
+
color: #ccc;
|
|
513
|
+
font-family: 'JetBrains Mono', monospace;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
.viewer-summary {
|
|
517
|
+
font-size: 13px;
|
|
518
|
+
color: #a0b4c8;
|
|
519
|
+
padding: 10px 14px;
|
|
520
|
+
margin-top: 10px;
|
|
521
|
+
background: #16213e;
|
|
522
|
+
border-left: 3px solid #4fc3f7;
|
|
523
|
+
border-radius: 4px;
|
|
524
|
+
line-height: 1.5;
|
|
525
|
+
font-style: italic;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/* --- Viewer Body (two columns) --- */
|
|
529
|
+
|
|
530
|
+
.viewer-body {
|
|
531
|
+
display: flex;
|
|
532
|
+
flex: 1;
|
|
533
|
+
min-height: 0;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
/* --- Left: Commit Timeline --- */
|
|
537
|
+
|
|
538
|
+
.viewer-timeline {
|
|
539
|
+
width: 340px;
|
|
540
|
+
min-width: 340px;
|
|
541
|
+
border-right: 1px solid #222244;
|
|
542
|
+
display: flex;
|
|
543
|
+
flex-direction: column;
|
|
544
|
+
background: #151530;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
.timeline-header {
|
|
548
|
+
padding: 14px 18px;
|
|
549
|
+
border-bottom: 1px solid #222244;
|
|
550
|
+
display: flex;
|
|
551
|
+
align-items: center;
|
|
552
|
+
justify-content: space-between;
|
|
553
|
+
flex-shrink: 0;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
.timeline-title {
|
|
557
|
+
font-size: 11px;
|
|
558
|
+
text-transform: uppercase;
|
|
559
|
+
letter-spacing: 0.5px;
|
|
560
|
+
color: #888;
|
|
561
|
+
font-weight: 500;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
.timeline-nav {
|
|
565
|
+
display: flex;
|
|
566
|
+
align-items: center;
|
|
567
|
+
gap: 8px;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
.timeline-nav-btn {
|
|
571
|
+
background: #16213e;
|
|
572
|
+
color: #4fc3f7;
|
|
573
|
+
border: 1px solid #0f3460;
|
|
574
|
+
border-radius: 4px;
|
|
575
|
+
padding: 3px 8px;
|
|
576
|
+
font-size: 10px;
|
|
577
|
+
cursor: pointer;
|
|
578
|
+
font-family: 'Inter', sans-serif;
|
|
579
|
+
transition: background 0.15s;
|
|
580
|
+
}
|
|
581
|
+
.timeline-nav-btn:hover { background: #1e2f52; }
|
|
582
|
+
|
|
583
|
+
.timeline-position {
|
|
584
|
+
font-family: 'JetBrains Mono', monospace;
|
|
585
|
+
font-size: 11px;
|
|
586
|
+
color: #666;
|
|
587
|
+
min-width: 50px;
|
|
588
|
+
text-align: center;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
.timeline-list {
|
|
592
|
+
flex: 1;
|
|
593
|
+
overflow-y: auto;
|
|
594
|
+
padding: 8px 0;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
.timeline-list::-webkit-scrollbar { width: 5px; }
|
|
598
|
+
.timeline-list::-webkit-scrollbar-track { background: #151530; }
|
|
599
|
+
.timeline-list::-webkit-scrollbar-thumb { background: #333; border-radius: 3px; }
|
|
600
|
+
|
|
601
|
+
.tl-commit {
|
|
602
|
+
display: flex;
|
|
603
|
+
gap: 12px;
|
|
604
|
+
padding: 8px 18px;
|
|
605
|
+
cursor: pointer;
|
|
606
|
+
transition: background 0.15s;
|
|
607
|
+
position: relative;
|
|
608
|
+
}
|
|
609
|
+
.tl-commit:hover { background: #1a2744; }
|
|
610
|
+
.tl-commit.active { background: #16213e; }
|
|
611
|
+
|
|
612
|
+
.tl-dot-col {
|
|
613
|
+
display: flex;
|
|
614
|
+
flex-direction: column;
|
|
615
|
+
align-items: center;
|
|
616
|
+
width: 16px;
|
|
617
|
+
flex-shrink: 0;
|
|
618
|
+
padding-top: 4px;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
.tl-dot {
|
|
622
|
+
width: 10px;
|
|
623
|
+
height: 10px;
|
|
624
|
+
border-radius: 50%;
|
|
625
|
+
background: #333;
|
|
626
|
+
border: 2px solid #555;
|
|
627
|
+
flex-shrink: 0;
|
|
628
|
+
transition: all 0.2s;
|
|
629
|
+
z-index: 1;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
.tl-commit:first-child .tl-dot {
|
|
633
|
+
border-color: #a5d6a7;
|
|
634
|
+
background: #2e7d32;
|
|
635
|
+
box-shadow: 0 0 6px rgba(76,175,80,0.4);
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
.tl-commit.has-diary .tl-dot {
|
|
639
|
+
background: #4fc3f7;
|
|
640
|
+
border-color: #4fc3f7;
|
|
641
|
+
box-shadow: 0 0 6px rgba(79,195,247,0.4);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
.tl-commit.active .tl-dot {
|
|
645
|
+
transform: scale(1.3);
|
|
646
|
+
box-shadow: 0 0 10px rgba(79,195,247,0.6);
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
.tl-line {
|
|
650
|
+
width: 2px;
|
|
651
|
+
flex-grow: 1;
|
|
652
|
+
background: #333;
|
|
653
|
+
min-height: 16px;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
.tl-info {
|
|
657
|
+
flex: 1;
|
|
658
|
+
min-width: 0;
|
|
659
|
+
padding-bottom: 4px;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
.tl-header {
|
|
663
|
+
display: flex;
|
|
664
|
+
align-items: baseline;
|
|
665
|
+
gap: 6px;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
.tl-hash {
|
|
669
|
+
font-family: 'JetBrains Mono', monospace;
|
|
670
|
+
font-size: 11px;
|
|
671
|
+
color: #888;
|
|
672
|
+
flex-shrink: 0;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
.tl-msg {
|
|
676
|
+
font-size: 13px;
|
|
677
|
+
color: #ddd;
|
|
678
|
+
white-space: nowrap;
|
|
679
|
+
overflow: hidden;
|
|
680
|
+
text-overflow: ellipsis;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
.tl-meta {
|
|
684
|
+
font-size: 11px;
|
|
685
|
+
color: #555;
|
|
686
|
+
margin-top: 2px;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
.tl-diary-badge {
|
|
690
|
+
font-size: 9px;
|
|
691
|
+
background: #0f3460;
|
|
692
|
+
color: #4fc3f7;
|
|
693
|
+
border: 1px solid #4fc3f7;
|
|
694
|
+
border-radius: 3px;
|
|
695
|
+
padding: 1px 5px;
|
|
696
|
+
text-transform: uppercase;
|
|
697
|
+
letter-spacing: 0.3px;
|
|
698
|
+
margin-left: 6px;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
/* --- Right: Detail Panel --- */
|
|
702
|
+
|
|
703
|
+
.viewer-detail {
|
|
704
|
+
flex: 1;
|
|
705
|
+
overflow-y: auto;
|
|
706
|
+
padding: 28px 36px;
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
.viewer-detail::-webkit-scrollbar { width: 6px; }
|
|
710
|
+
.viewer-detail::-webkit-scrollbar-track { background: #1a1a2e; }
|
|
711
|
+
.viewer-detail::-webkit-scrollbar-thumb { background: #333; border-radius: 3px; }
|
|
712
|
+
|
|
713
|
+
.detail-empty {
|
|
714
|
+
display: flex;
|
|
715
|
+
align-items: center;
|
|
716
|
+
justify-content: center;
|
|
717
|
+
height: 100%;
|
|
718
|
+
color: #444;
|
|
719
|
+
font-size: 14px;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
/* Commit detail header */
|
|
723
|
+
.detail-commit-header {
|
|
724
|
+
margin-bottom: 24px;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
.detail-commit-msg {
|
|
728
|
+
font-size: 20px;
|
|
729
|
+
font-weight: 600;
|
|
730
|
+
color: #fff;
|
|
731
|
+
margin-bottom: 8px;
|
|
732
|
+
line-height: 1.3;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
.detail-commit-meta {
|
|
736
|
+
display: flex;
|
|
737
|
+
gap: 20px;
|
|
738
|
+
font-size: 12px;
|
|
739
|
+
color: #888;
|
|
740
|
+
flex-wrap: wrap;
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
.detail-meta-item {
|
|
744
|
+
display: flex;
|
|
745
|
+
align-items: center;
|
|
746
|
+
gap: 5px;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
.detail-meta-label {
|
|
750
|
+
color: #555;
|
|
751
|
+
text-transform: uppercase;
|
|
752
|
+
font-size: 10px;
|
|
753
|
+
letter-spacing: 0.3px;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
.detail-meta-value {
|
|
757
|
+
font-family: 'JetBrains Mono', monospace;
|
|
758
|
+
color: #bbb;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
.detail-summary-brief {
|
|
762
|
+
font-size: 13px;
|
|
763
|
+
color: #a0b4c8;
|
|
764
|
+
padding: 10px 14px;
|
|
765
|
+
margin-bottom: 12px;
|
|
766
|
+
background: #16213e;
|
|
767
|
+
border-left: 3px solid #4fc3f7;
|
|
768
|
+
border-radius: 4px;
|
|
769
|
+
line-height: 1.5;
|
|
770
|
+
font-style: italic;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
/* Stat bar */
|
|
774
|
+
.detail-stats {
|
|
775
|
+
display: flex;
|
|
776
|
+
gap: 16px;
|
|
777
|
+
margin-bottom: 24px;
|
|
778
|
+
padding: 14px 18px;
|
|
779
|
+
background: #151530;
|
|
780
|
+
border-radius: 8px;
|
|
781
|
+
border: 1px solid #222244;
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
.stat-block {
|
|
785
|
+
display: flex;
|
|
786
|
+
flex-direction: column;
|
|
787
|
+
gap: 2px;
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
.stat-value {
|
|
791
|
+
font-family: 'JetBrains Mono', monospace;
|
|
792
|
+
font-size: 18px;
|
|
793
|
+
font-weight: 600;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
.stat-value.green { color: #a5d6a7; }
|
|
797
|
+
.stat-value.red { color: #ef9a9a; }
|
|
798
|
+
.stat-value.blue { color: #4fc3f7; }
|
|
799
|
+
|
|
800
|
+
.stat-label {
|
|
801
|
+
font-size: 10px;
|
|
802
|
+
text-transform: uppercase;
|
|
803
|
+
letter-spacing: 0.3px;
|
|
804
|
+
color: #666;
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
/* Diary section in detail */
|
|
808
|
+
.detail-diary {
|
|
809
|
+
margin-bottom: 24px;
|
|
810
|
+
padding: 18px 22px;
|
|
811
|
+
background: #1a1a3e;
|
|
812
|
+
border: 1px solid #2a2a5e;
|
|
813
|
+
border-radius: 8px;
|
|
814
|
+
border-left: 3px solid #4fc3f7;
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
.detail-diary-title {
|
|
818
|
+
font-size: 15px;
|
|
819
|
+
font-weight: 600;
|
|
820
|
+
color: #fff;
|
|
821
|
+
margin-bottom: 14px;
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
.detail-diary-section {
|
|
825
|
+
margin-bottom: 10px;
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
.detail-diary-section-label {
|
|
829
|
+
font-size: 10px;
|
|
830
|
+
font-weight: 600;
|
|
831
|
+
text-transform: uppercase;
|
|
832
|
+
letter-spacing: 0.4px;
|
|
833
|
+
margin-bottom: 4px;
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
.detail-diary-section.changes .detail-diary-section-label { color: #4fc3f7; }
|
|
837
|
+
.detail-diary-section.decisions .detail-diary-section-label { color: #ce93d8; }
|
|
838
|
+
.detail-diary-section.issues .detail-diary-section-label { color: #ef9a9a; }
|
|
839
|
+
.detail-diary-section.next-steps .detail-diary-section-label { color: #a5d6a7; }
|
|
840
|
+
|
|
841
|
+
.detail-diary-section ul {
|
|
842
|
+
margin: 0 0 0 16px;
|
|
843
|
+
padding: 0;
|
|
844
|
+
font-size: 13px;
|
|
845
|
+
color: #ccc;
|
|
846
|
+
line-height: 1.6;
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
.detail-diary-section li { margin-bottom: 2px; }
|
|
850
|
+
|
|
851
|
+
/* Files changed list */
|
|
852
|
+
.detail-files {
|
|
853
|
+
margin-bottom: 24px;
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
.detail-files-title {
|
|
857
|
+
font-size: 11px;
|
|
858
|
+
text-transform: uppercase;
|
|
859
|
+
letter-spacing: 0.4px;
|
|
860
|
+
color: #888;
|
|
861
|
+
font-weight: 500;
|
|
862
|
+
margin-bottom: 10px;
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
.file-list {
|
|
866
|
+
list-style: none;
|
|
867
|
+
padding: 0;
|
|
868
|
+
margin: 0;
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
.file-item {
|
|
872
|
+
padding: 6px 12px;
|
|
873
|
+
font-family: 'JetBrains Mono', monospace;
|
|
874
|
+
font-size: 12px;
|
|
875
|
+
color: #bbb;
|
|
876
|
+
border-radius: 4px;
|
|
877
|
+
transition: background 0.1s;
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
.file-item:nth-child(odd) { background: #151530; }
|
|
881
|
+
.file-item:hover { background: #1a2744; }
|
|
882
|
+
|
|
883
|
+
.file-icon {
|
|
884
|
+
color: #4fc3f7;
|
|
885
|
+
margin-right: 8px;
|
|
886
|
+
font-style: normal;
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
/* Legend */
|
|
890
|
+
.timeline-legend {
|
|
891
|
+
padding: 10px 18px;
|
|
892
|
+
border-top: 1px solid #222244;
|
|
893
|
+
display: flex;
|
|
894
|
+
gap: 14px;
|
|
895
|
+
font-size: 10px;
|
|
896
|
+
color: #555;
|
|
897
|
+
flex-shrink: 0;
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
.legend-item {
|
|
901
|
+
display: flex;
|
|
902
|
+
align-items: center;
|
|
903
|
+
gap: 5px;
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
.legend-dot {
|
|
907
|
+
width: 8px;
|
|
908
|
+
height: 8px;
|
|
909
|
+
border-radius: 50%;
|
|
910
|
+
display: inline-block;
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
.legend-dot.commit-dot {
|
|
914
|
+
background: #333;
|
|
915
|
+
border: 2px solid #555;
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
.legend-dot.diary-dot {
|
|
919
|
+
background: #4fc3f7;
|
|
920
|
+
border: 2px solid #4fc3f7;
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
.legend-dot.latest-dot {
|
|
924
|
+
background: #2e7d32;
|
|
925
|
+
border: 2px solid #a5d6a7;
|
|
926
|
+
}
|
|
927
|
+
</style>
|
|
928
|
+
</head>
|
|
929
|
+
<body>
|
|
930
|
+
|
|
931
|
+
<!-- ===== TABLE VIEW ===== -->
|
|
932
|
+
<div id="table-view">
|
|
933
|
+
<h1>Branch Map — ${esc(projectName)}</h1>
|
|
934
|
+
<p class="subtitle">Generated ${esc(now)} — Click a branch to explore</p>
|
|
935
|
+
<table>
|
|
936
|
+
<thead>
|
|
937
|
+
<tr>
|
|
938
|
+
<th>Branch</th>
|
|
939
|
+
<th>Summary</th>
|
|
940
|
+
<th class="center">Status</th>
|
|
941
|
+
<th class="center">Files Changed</th>
|
|
942
|
+
<th>Last Commit</th>
|
|
943
|
+
<th class="center">Date</th>
|
|
944
|
+
</tr>
|
|
945
|
+
</thead>
|
|
946
|
+
<tbody>
|
|
947
|
+
${rows.join("\n ")}
|
|
948
|
+
</tbody>
|
|
949
|
+
</table>
|
|
950
|
+
</div>
|
|
951
|
+
|
|
952
|
+
<!-- ===== BRANCH VIEWER ===== -->
|
|
953
|
+
<div id="branch-viewer">
|
|
954
|
+
<div class="viewer-header">
|
|
955
|
+
<button class="back-btn" onclick="closeBranchViewer()">◀ All Branches</button>
|
|
956
|
+
<span class="viewer-branch-name" id="viewer-branch-name"></span>
|
|
957
|
+
<div class="viewer-meta" id="viewer-meta"></div>
|
|
958
|
+
<div class="viewer-summary" id="viewer-summary"></div>
|
|
959
|
+
</div>
|
|
960
|
+
<div class="viewer-body">
|
|
961
|
+
<div class="viewer-timeline">
|
|
962
|
+
<div class="timeline-header">
|
|
963
|
+
<span class="timeline-title">Commits</span>
|
|
964
|
+
<div class="timeline-nav">
|
|
965
|
+
<button class="timeline-nav-btn" onclick="viewerNav(-1)">▲ Newer</button>
|
|
966
|
+
<span class="timeline-position" id="viewer-pos"></span>
|
|
967
|
+
<button class="timeline-nav-btn" onclick="viewerNav(1)">▼ Older</button>
|
|
968
|
+
</div>
|
|
969
|
+
</div>
|
|
970
|
+
<div class="timeline-list" id="timeline-list"></div>
|
|
971
|
+
<div class="timeline-legend">
|
|
972
|
+
<span class="legend-item"><span class="legend-dot latest-dot"></span> Latest</span>
|
|
973
|
+
<span class="legend-item"><span class="legend-dot diary-dot"></span> Diary entry</span>
|
|
974
|
+
<span class="legend-item"><span class="legend-dot commit-dot"></span> Commit</span>
|
|
975
|
+
</div>
|
|
976
|
+
</div>
|
|
977
|
+
<div class="viewer-detail" id="viewer-detail">
|
|
978
|
+
<div class="detail-empty">Select a commit to view details</div>
|
|
979
|
+
</div>
|
|
980
|
+
</div>
|
|
981
|
+
</div>
|
|
982
|
+
|
|
983
|
+
<script>
|
|
984
|
+
const BRANCHES = ${branchDataJson};
|
|
985
|
+
|
|
986
|
+
let currentBranchIdx = null;
|
|
987
|
+
let currentCommitIdx = null;
|
|
988
|
+
|
|
989
|
+
function toggleSummary(e) {
|
|
990
|
+
e.stopPropagation();
|
|
991
|
+
const title = e.currentTarget;
|
|
992
|
+
const details = title.nextElementSibling;
|
|
993
|
+
if (!details || !details.classList.contains('summary-details')) return;
|
|
994
|
+
const collapsed = details.classList.toggle('collapsed');
|
|
995
|
+
const arrow = title.querySelector('.summary-toggle');
|
|
996
|
+
if (arrow) arrow.innerHTML = collapsed ? '▼' : '▲';
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
function openBranchViewer(idx) {
|
|
1000
|
+
const branch = BRANCHES[idx];
|
|
1001
|
+
if (!branch || branch.commits.length === 0) return;
|
|
1002
|
+
|
|
1003
|
+
currentBranchIdx = idx;
|
|
1004
|
+
currentCommitIdx = null;
|
|
1005
|
+
|
|
1006
|
+
// Header
|
|
1007
|
+
const nameEl = document.getElementById('viewer-branch-name');
|
|
1008
|
+
let badges = '';
|
|
1009
|
+
if (branch.isCurrent) badges += '<span class="badge current">current</span>';
|
|
1010
|
+
if (branch.isMain) badges += '<span class="badge main">main</span>';
|
|
1011
|
+
nameEl.innerHTML = esc(branch.name) + ' ' + badges;
|
|
1012
|
+
|
|
1013
|
+
// Meta
|
|
1014
|
+
const metaEl = document.getElementById('viewer-meta');
|
|
1015
|
+
let metaHtml = '';
|
|
1016
|
+
if (!branch.isMain) {
|
|
1017
|
+
metaHtml += '<span class="viewer-meta-item"><span class="detail-meta-label">Ahead</span> <span class="viewer-meta-value">' + branch.ahead + '</span></span>';
|
|
1018
|
+
metaHtml += '<span class="viewer-meta-item"><span class="detail-meta-label">Behind</span> <span class="viewer-meta-value">' + branch.behind + '</span></span>';
|
|
1019
|
+
metaHtml += '<span class="viewer-meta-item"><span class="detail-meta-label">Files</span> <span class="viewer-meta-value">' + branch.filesChanged + '</span></span>';
|
|
1020
|
+
}
|
|
1021
|
+
metaHtml += '<span class="viewer-meta-item"><span class="detail-meta-label">Commits</span> <span class="viewer-meta-value">' + branch.commits.length + '</span></span>';
|
|
1022
|
+
metaEl.innerHTML = metaHtml;
|
|
1023
|
+
|
|
1024
|
+
// Branch summary from diary
|
|
1025
|
+
const summaryEl = document.getElementById('viewer-summary');
|
|
1026
|
+
summaryEl.innerHTML = branch.summary ? esc(branch.summary) : '';
|
|
1027
|
+
summaryEl.style.display = branch.summary ? 'block' : 'none';
|
|
1028
|
+
|
|
1029
|
+
// Build timeline
|
|
1030
|
+
const listEl = document.getElementById('timeline-list');
|
|
1031
|
+
listEl.innerHTML = branch.commits.map(function(c, ci) {
|
|
1032
|
+
const hasDiary = !!c.diary;
|
|
1033
|
+
const date = new Date(c.timestamp * 1000);
|
|
1034
|
+
const dateStr = date.toLocaleDateString('en-US', { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' });
|
|
1035
|
+
const isLast = ci === branch.commits.length - 1;
|
|
1036
|
+
return '<div class="tl-commit' + (hasDiary ? ' has-diary' : '') + '" data-ci="' + ci + '" onclick="selectViewerCommit(' + ci + ')">' +
|
|
1037
|
+
'<div class="tl-dot-col">' +
|
|
1038
|
+
'<div class="tl-dot"></div>' +
|
|
1039
|
+
(isLast ? '' : '<div class="tl-line"></div>') +
|
|
1040
|
+
'</div>' +
|
|
1041
|
+
'<div class="tl-info">' +
|
|
1042
|
+
'<div class="tl-header">' +
|
|
1043
|
+
'<span class="tl-hash">' + esc(c.shortHash) + '</span>' +
|
|
1044
|
+
'<span class="tl-msg">' + esc(c.message) + '</span>' +
|
|
1045
|
+
(hasDiary ? '<span class="tl-diary-badge">diary</span>' : '') +
|
|
1046
|
+
'</div>' +
|
|
1047
|
+
'<div class="tl-meta">' + esc(dateStr) + ' by ' + esc(c.author) + '</div>' +
|
|
1048
|
+
'</div>' +
|
|
1049
|
+
'</div>';
|
|
1050
|
+
}).join('');
|
|
1051
|
+
|
|
1052
|
+
// Show viewer
|
|
1053
|
+
document.getElementById('table-view').classList.add('hidden');
|
|
1054
|
+
document.getElementById('branch-viewer').classList.add('active');
|
|
1055
|
+
|
|
1056
|
+
// Reset detail
|
|
1057
|
+
document.getElementById('viewer-detail').innerHTML = '<div class="detail-empty">Select a commit to view details</div>';
|
|
1058
|
+
|
|
1059
|
+
// Auto-select first commit
|
|
1060
|
+
setTimeout(function() { selectViewerCommit(0); }, 50);
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
function closeBranchViewer() {
|
|
1064
|
+
document.getElementById('branch-viewer').classList.remove('active');
|
|
1065
|
+
document.getElementById('table-view').classList.remove('hidden');
|
|
1066
|
+
currentBranchIdx = null;
|
|
1067
|
+
currentCommitIdx = null;
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
function selectViewerCommit(ci) {
|
|
1071
|
+
if (currentBranchIdx === null) return;
|
|
1072
|
+
const branch = BRANCHES[currentBranchIdx];
|
|
1073
|
+
if (ci < 0 || ci >= branch.commits.length) return;
|
|
1074
|
+
|
|
1075
|
+
// Update timeline active state
|
|
1076
|
+
const nodes = document.querySelectorAll('.tl-commit');
|
|
1077
|
+
nodes.forEach(function(n) { n.classList.remove('active'); });
|
|
1078
|
+
const activeNode = document.querySelector('.tl-commit[data-ci="' + ci + '"]');
|
|
1079
|
+
if (activeNode) {
|
|
1080
|
+
activeNode.classList.add('active');
|
|
1081
|
+
activeNode.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
currentCommitIdx = ci;
|
|
1085
|
+
|
|
1086
|
+
// Update position
|
|
1087
|
+
document.getElementById('viewer-pos').textContent = (ci + 1) + ' / ' + branch.commits.length;
|
|
1088
|
+
|
|
1089
|
+
// Hydrate detail panel
|
|
1090
|
+
hydrateDetail(branch.commits[ci]);
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
function hydrateDetail(commit) {
|
|
1094
|
+
const detail = document.getElementById('viewer-detail');
|
|
1095
|
+
let html = '';
|
|
1096
|
+
|
|
1097
|
+
// Commit header
|
|
1098
|
+
const date = new Date(commit.timestamp * 1000);
|
|
1099
|
+
const fullDate = date.toLocaleDateString('en-US', { weekday: 'short', year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' });
|
|
1100
|
+
|
|
1101
|
+
html += '<div class="detail-commit-header">';
|
|
1102
|
+
html += '<div class="detail-commit-msg">' + esc(commit.message) + '</div>';
|
|
1103
|
+
html += '<div class="detail-commit-meta">';
|
|
1104
|
+
html += '<span class="detail-meta-item"><span class="detail-meta-label">Hash</span> <span class="detail-meta-value">' + esc(commit.shortHash) + '</span></span>';
|
|
1105
|
+
html += '<span class="detail-meta-item"><span class="detail-meta-label">Author</span> <span class="detail-meta-value">' + esc(commit.author) + '</span></span>';
|
|
1106
|
+
html += '<span class="detail-meta-item"><span class="detail-meta-label">Date</span> <span class="detail-meta-value">' + esc(fullDate) + '</span></span>';
|
|
1107
|
+
html += '</div></div>';
|
|
1108
|
+
|
|
1109
|
+
// Commit summary (from diary or auto-generated)
|
|
1110
|
+
if (commit.commitSummary) {
|
|
1111
|
+
html += '<div class="detail-summary-brief">' + esc(commit.commitSummary) + '</div>';
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
// Stats bar
|
|
1115
|
+
html += '<div class="detail-stats">';
|
|
1116
|
+
html += '<div class="stat-block"><span class="stat-value blue">' + commit.files.length + '</span><span class="stat-label">Files changed</span></div>';
|
|
1117
|
+
html += '<div class="stat-block"><span class="stat-value green">+' + commit.insertions + '</span><span class="stat-label">Insertions</span></div>';
|
|
1118
|
+
html += '<div class="stat-block"><span class="stat-value red">-' + commit.deletions + '</span><span class="stat-label">Deletions</span></div>';
|
|
1119
|
+
html += '</div>';
|
|
1120
|
+
|
|
1121
|
+
// Diary section (if present)
|
|
1122
|
+
if (commit.diary) {
|
|
1123
|
+
const d = commit.diary;
|
|
1124
|
+
html += '<div class="detail-diary">';
|
|
1125
|
+
html += '<div class="detail-diary-title">' + esc(d.title) + '</div>';
|
|
1126
|
+
|
|
1127
|
+
if (d.whatChanged.length > 0) {
|
|
1128
|
+
html += '<div class="detail-diary-section changes"><div class="detail-diary-section-label">What Changed</div><ul>' +
|
|
1129
|
+
d.whatChanged.map(function(x) { return '<li>' + esc(x) + '</li>'; }).join('') + '</ul></div>';
|
|
1130
|
+
}
|
|
1131
|
+
if (d.decisions.length > 0) {
|
|
1132
|
+
html += '<div class="detail-diary-section decisions"><div class="detail-diary-section-label">Decisions</div><ul>' +
|
|
1133
|
+
d.decisions.map(function(x) { return '<li>' + esc(x) + '</li>'; }).join('') + '</ul></div>';
|
|
1134
|
+
}
|
|
1135
|
+
if (d.issues.length > 0) {
|
|
1136
|
+
html += '<div class="detail-diary-section issues"><div class="detail-diary-section-label">Issues</div><ul>' +
|
|
1137
|
+
d.issues.map(function(x) { return '<li>' + esc(x) + '</li>'; }).join('') + '</ul></div>';
|
|
1138
|
+
}
|
|
1139
|
+
if (d.nextSteps.length > 0) {
|
|
1140
|
+
html += '<div class="detail-diary-section next-steps"><div class="detail-diary-section-label">Next Steps</div><ul>' +
|
|
1141
|
+
d.nextSteps.map(function(x) { return '<li>' + esc(x) + '</li>'; }).join('') + '</ul></div>';
|
|
1142
|
+
}
|
|
1143
|
+
html += '</div>';
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
// Files changed list
|
|
1147
|
+
if (commit.files.length > 0) {
|
|
1148
|
+
html += '<div class="detail-files">';
|
|
1149
|
+
html += '<div class="detail-files-title">Files Changed</div>';
|
|
1150
|
+
html += '<ul class="file-list">';
|
|
1151
|
+
commit.files.forEach(function(f) {
|
|
1152
|
+
var ext = f.split('.').pop() || '';
|
|
1153
|
+
var icon = ext === 'ts' || ext === 'tsx' ? '◈' :
|
|
1154
|
+
ext === 'js' || ext === 'jsx' ? '◈' :
|
|
1155
|
+
ext === 'json' ? '⚙' :
|
|
1156
|
+
ext === 'md' ? '✍' :
|
|
1157
|
+
ext === 'css' || ext === 'scss' ? '☆' :
|
|
1158
|
+
'▸';
|
|
1159
|
+
html += '<li class="file-item"><i class="file-icon">' + icon + '</i>' + esc(f) + '</li>';
|
|
1160
|
+
});
|
|
1161
|
+
html += '</ul></div>';
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
detail.innerHTML = html;
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
function viewerNav(direction) {
|
|
1168
|
+
if (currentCommitIdx === null) return;
|
|
1169
|
+
selectViewerCommit(currentCommitIdx + direction);
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
function esc(str) {
|
|
1173
|
+
var d = document.createElement('div');
|
|
1174
|
+
d.textContent = str;
|
|
1175
|
+
return d.innerHTML;
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
// Keyboard navigation
|
|
1179
|
+
document.addEventListener('keydown', function(e) {
|
|
1180
|
+
if (currentBranchIdx === null) return;
|
|
1181
|
+
if (e.key === 'ArrowUp' || e.key === 'k') {
|
|
1182
|
+
e.preventDefault();
|
|
1183
|
+
viewerNav(-1);
|
|
1184
|
+
} else if (e.key === 'ArrowDown' || e.key === 'j') {
|
|
1185
|
+
e.preventDefault();
|
|
1186
|
+
viewerNav(1);
|
|
1187
|
+
} else if (e.key === 'Escape' || e.key === 'Backspace') {
|
|
1188
|
+
e.preventDefault();
|
|
1189
|
+
closeBranchViewer();
|
|
1190
|
+
}
|
|
1191
|
+
});
|
|
1192
|
+
</script>
|
|
1193
|
+
</body>
|
|
1194
|
+
</html>`;
|
|
1195
|
+
}
|