devguard 0.2.1 → 0.3.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 +15 -0
- package/dist/index.js +2 -0
- package/dist/tools/daily-view.d.ts +2 -0
- package/dist/tools/daily-view.js +87 -0
- package/dist/utils/daily-view-html.d.ts +6 -0
- package/dist/utils/daily-view-html.js +596 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -29,6 +29,7 @@ From then on, your AI writes diary entries on its own — after finishing a feat
|
|
|
29
29
|
| `read_entries` | Reads recent entries to catch you up |
|
|
30
30
|
| `catch_me_up` | Morning briefing — diary entries + git state + branch map in one shot |
|
|
31
31
|
| `branch_map` | Opens a visual branch map in your browser |
|
|
32
|
+
| `daily_view` | Opens a calendar dashboard showing diary entries by date |
|
|
32
33
|
| `setup` | Re-run setup manually if needed |
|
|
33
34
|
|
|
34
35
|
## Branch Map
|
|
@@ -43,6 +44,16 @@ Run `branch_map` to open an interactive HTML visualization of your repo in the b
|
|
|
43
44
|
|
|
44
45
|
Designed for people who don't want to think about git.
|
|
45
46
|
|
|
47
|
+
## Daily View
|
|
48
|
+
|
|
49
|
+
Run `daily_view` to open a calendar dashboard in your browser. It pulls diary entries from all branches and displays them on a monthly grid:
|
|
50
|
+
|
|
51
|
+
- **Entry badges** on each day showing how many entries were logged
|
|
52
|
+
- **Click any day** to expand and see full details — what changed, decisions, issues, next steps
|
|
53
|
+
- **Keyboard navigation** — arrow keys to switch months, Escape to close the detail panel
|
|
54
|
+
|
|
55
|
+
A quick way to see your work history at a glance.
|
|
56
|
+
|
|
46
57
|
## Branch-Aware Diary
|
|
47
58
|
|
|
48
59
|
Entries are automatically routed by branch:
|
|
@@ -62,3 +73,7 @@ Entries are markdown files stored locally in your project under `.devguard/`. Ea
|
|
|
62
73
|
- **Commit hash** — links the entry to a specific commit for traceability
|
|
63
74
|
|
|
64
75
|
Multiple sessions and agents all append to the same file. The diary builds up over time, making summaries richer and the branch map more useful with every session.
|
|
76
|
+
|
|
77
|
+
## Contact
|
|
78
|
+
|
|
79
|
+
Join the Discord for questions, feedback, or feature requests: https://discord.gg/BrzRHHzjFQ
|
package/dist/index.js
CHANGED
|
@@ -9,6 +9,7 @@ const read_entries_js_1 = require("./tools/read-entries.js");
|
|
|
9
9
|
const catch_me_up_js_1 = require("./tools/catch-me-up.js");
|
|
10
10
|
const setup_js_1 = require("./tools/setup.js");
|
|
11
11
|
const branch_map_js_1 = require("./tools/branch-map.js");
|
|
12
|
+
const daily_view_js_1 = require("./tools/daily-view.js");
|
|
12
13
|
const auto_setup_js_1 = require("./utils/auto-setup.js");
|
|
13
14
|
// Auto-setup on first run: adds .devdiary/ to .gitignore
|
|
14
15
|
// and auto-logging instruction to CLAUDE.md or .cursorrules
|
|
@@ -23,6 +24,7 @@ const server = new mcp_js_1.McpServer({
|
|
|
23
24
|
(0, catch_me_up_js_1.registerCatchMeUp)(server);
|
|
24
25
|
(0, setup_js_1.registerSetup)(server);
|
|
25
26
|
(0, branch_map_js_1.registerBranchMap)(server);
|
|
27
|
+
(0, daily_view_js_1.registerDailyView)(server);
|
|
26
28
|
async function main() {
|
|
27
29
|
const transport = new stdio_js_1.StdioServerTransport();
|
|
28
30
|
await server.connect(transport);
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.registerDailyView = registerDailyView;
|
|
37
|
+
const zod_1 = require("zod");
|
|
38
|
+
const git = __importStar(require("../utils/git.js"));
|
|
39
|
+
const storage = __importStar(require("../utils/storage.js"));
|
|
40
|
+
const daily_view_html_js_1 = require("../utils/daily-view-html.js");
|
|
41
|
+
function registerDailyView(server) {
|
|
42
|
+
server.tool("daily_view", "Opens a calendar dashboard in the browser. Each day shows bullet points of what you worked on — pulled from diary entries across all branches.", {
|
|
43
|
+
project_path: zod_1.z.string().describe("Absolute path to the project directory"),
|
|
44
|
+
}, async ({ project_path }) => {
|
|
45
|
+
if (!git.isGitRepo(project_path)) {
|
|
46
|
+
return {
|
|
47
|
+
content: [{ type: "text", text: "This project isn't using git yet — nothing to show." }],
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
const currentBranch = git.getBranch(project_path);
|
|
51
|
+
// Collect all diary entries from main stem
|
|
52
|
+
const mainEntries = storage.readEntries(project_path, 90);
|
|
53
|
+
const allParsed = [];
|
|
54
|
+
for (const content of mainEntries) {
|
|
55
|
+
const entries = storage.parseDiaryEntries(content);
|
|
56
|
+
for (const entry of entries) {
|
|
57
|
+
const date = entry.date ? entry.date.slice(0, 10) : "";
|
|
58
|
+
if (date)
|
|
59
|
+
allParsed.push({ date, entry });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
// Collect diary entries from all branches
|
|
63
|
+
const branchFiles = storage.listBranchFiles(project_path);
|
|
64
|
+
for (const bf of branchFiles) {
|
|
65
|
+
const entries = storage.parseDiaryEntries(bf.content);
|
|
66
|
+
for (const entry of entries) {
|
|
67
|
+
const date = entry.date ? entry.date.slice(0, 10) : "";
|
|
68
|
+
if (date)
|
|
69
|
+
allParsed.push({ date, entry });
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
// Group by date
|
|
73
|
+
const dateMap = new Map();
|
|
74
|
+
for (const { date, entry } of allParsed) {
|
|
75
|
+
if (!dateMap.has(date))
|
|
76
|
+
dateMap.set(date, []);
|
|
77
|
+
dateMap.get(date).push(entry);
|
|
78
|
+
}
|
|
79
|
+
const days = Array.from(dateMap.entries())
|
|
80
|
+
.map(([date, entries]) => ({ date, entries }))
|
|
81
|
+
.sort((a, b) => a.date.localeCompare(b.date));
|
|
82
|
+
const filePath = (0, daily_view_html_js_1.generateAndOpenDailyView)(project_path, days, currentBranch);
|
|
83
|
+
return {
|
|
84
|
+
content: [{ type: "text", text: `Daily view opened in your browser.\n\nSaved to: ${filePath}` }],
|
|
85
|
+
};
|
|
86
|
+
});
|
|
87
|
+
}
|
|
@@ -0,0 +1,596 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateAndOpenDailyView = generateAndOpenDailyView;
|
|
4
|
+
const fs_1 = require("fs");
|
|
5
|
+
const path_1 = require("path");
|
|
6
|
+
const child_process_1 = require("child_process");
|
|
7
|
+
function generateAndOpenDailyView(projectPath, days, currentBranch) {
|
|
8
|
+
const projectName = projectPath.split("/").pop() || "project";
|
|
9
|
+
const html = buildHtml(days, projectName, currentBranch);
|
|
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, "daily-view.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 buildHtml(days, projectName, currentBranch) {
|
|
40
|
+
// Build a JSON-safe data structure for the JS side
|
|
41
|
+
const daysJson = JSON.stringify(days.map((d) => ({
|
|
42
|
+
date: d.date,
|
|
43
|
+
entries: d.entries.map((e) => ({
|
|
44
|
+
title: e.title,
|
|
45
|
+
summary: e.summary,
|
|
46
|
+
date: e.date,
|
|
47
|
+
commit: e.commit,
|
|
48
|
+
whatChanged: e.whatChanged,
|
|
49
|
+
decisions: e.decisions,
|
|
50
|
+
issues: e.issues,
|
|
51
|
+
nextSteps: e.nextSteps,
|
|
52
|
+
})),
|
|
53
|
+
})));
|
|
54
|
+
return `<!DOCTYPE html>
|
|
55
|
+
<html lang="en">
|
|
56
|
+
<head>
|
|
57
|
+
<meta charset="UTF-8">
|
|
58
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
59
|
+
<title>${esc(projectName)} — Daily View</title>
|
|
60
|
+
<style>
|
|
61
|
+
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap');
|
|
62
|
+
|
|
63
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
64
|
+
|
|
65
|
+
body {
|
|
66
|
+
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
|
|
67
|
+
background: #1a1a2e;
|
|
68
|
+
color: #e0e0e0;
|
|
69
|
+
min-height: 100vh;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/* Header */
|
|
73
|
+
.header {
|
|
74
|
+
display: flex;
|
|
75
|
+
align-items: center;
|
|
76
|
+
justify-content: space-between;
|
|
77
|
+
padding: 20px 32px;
|
|
78
|
+
border-bottom: 1px solid #2a2a4a;
|
|
79
|
+
background: #16162a;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.header h1 {
|
|
83
|
+
font-size: 20px;
|
|
84
|
+
font-weight: 600;
|
|
85
|
+
color: #fff;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.header h1 span {
|
|
89
|
+
color: #4fc3f7;
|
|
90
|
+
font-weight: 400;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.header .branch-badge {
|
|
94
|
+
font-size: 13px;
|
|
95
|
+
background: #2a2a4a;
|
|
96
|
+
color: #a0a0c0;
|
|
97
|
+
padding: 4px 12px;
|
|
98
|
+
border-radius: 12px;
|
|
99
|
+
font-family: 'JetBrains Mono', monospace;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/* Nav */
|
|
103
|
+
.nav {
|
|
104
|
+
display: flex;
|
|
105
|
+
align-items: center;
|
|
106
|
+
justify-content: center;
|
|
107
|
+
gap: 16px;
|
|
108
|
+
padding: 16px 32px;
|
|
109
|
+
border-bottom: 1px solid #2a2a4a;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.nav button {
|
|
113
|
+
background: #2a2a4a;
|
|
114
|
+
color: #e0e0e0;
|
|
115
|
+
border: none;
|
|
116
|
+
padding: 8px 16px;
|
|
117
|
+
border-radius: 8px;
|
|
118
|
+
cursor: pointer;
|
|
119
|
+
font-family: inherit;
|
|
120
|
+
font-size: 14px;
|
|
121
|
+
transition: background 0.15s;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.nav button:hover { background: #3a3a5a; }
|
|
125
|
+
|
|
126
|
+
.nav .month-label {
|
|
127
|
+
font-size: 18px;
|
|
128
|
+
font-weight: 600;
|
|
129
|
+
min-width: 200px;
|
|
130
|
+
text-align: center;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/* Calendar grid */
|
|
134
|
+
.calendar-container {
|
|
135
|
+
max-width: 1200px;
|
|
136
|
+
margin: 24px auto;
|
|
137
|
+
padding: 0 24px;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.weekday-header {
|
|
141
|
+
display: grid;
|
|
142
|
+
grid-template-columns: repeat(7, 1fr);
|
|
143
|
+
gap: 4px;
|
|
144
|
+
margin-bottom: 4px;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.weekday-header div {
|
|
148
|
+
text-align: center;
|
|
149
|
+
font-size: 12px;
|
|
150
|
+
font-weight: 600;
|
|
151
|
+
color: #808098;
|
|
152
|
+
padding: 8px 0;
|
|
153
|
+
text-transform: uppercase;
|
|
154
|
+
letter-spacing: 0.5px;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.calendar-grid {
|
|
158
|
+
display: grid;
|
|
159
|
+
grid-template-columns: repeat(7, 1fr);
|
|
160
|
+
gap: 4px;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.day-cell {
|
|
164
|
+
min-height: 120px;
|
|
165
|
+
background: #20203a;
|
|
166
|
+
border-radius: 8px;
|
|
167
|
+
padding: 8px;
|
|
168
|
+
cursor: default;
|
|
169
|
+
transition: background 0.15s, box-shadow 0.15s;
|
|
170
|
+
position: relative;
|
|
171
|
+
overflow: hidden;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.day-cell.empty {
|
|
175
|
+
background: transparent;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.day-cell.today {
|
|
179
|
+
box-shadow: inset 0 0 0 2px #4fc3f7;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.day-cell.has-entries {
|
|
183
|
+
cursor: pointer;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
.day-cell.has-entries:hover {
|
|
187
|
+
background: #2a2a4a;
|
|
188
|
+
box-shadow: 0 2px 12px rgba(79, 195, 247, 0.1);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.day-cell.selected {
|
|
192
|
+
background: #2a2a4a;
|
|
193
|
+
box-shadow: inset 0 0 0 2px #4fc3f7, 0 2px 12px rgba(79, 195, 247, 0.15);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.day-number {
|
|
197
|
+
font-size: 13px;
|
|
198
|
+
font-weight: 600;
|
|
199
|
+
color: #808098;
|
|
200
|
+
margin-bottom: 6px;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
.day-cell.has-entries .day-number {
|
|
204
|
+
color: #4fc3f7;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
.day-cell.today .day-number {
|
|
208
|
+
color: #fff;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
.day-bullets {
|
|
212
|
+
list-style: none;
|
|
213
|
+
padding: 0;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
.day-bullets li {
|
|
217
|
+
font-size: 11px;
|
|
218
|
+
color: #b0b0c8;
|
|
219
|
+
padding: 2px 0;
|
|
220
|
+
line-height: 1.4;
|
|
221
|
+
white-space: nowrap;
|
|
222
|
+
overflow: hidden;
|
|
223
|
+
text-overflow: ellipsis;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
.day-bullets li::before {
|
|
227
|
+
content: "";
|
|
228
|
+
display: inline-block;
|
|
229
|
+
width: 5px;
|
|
230
|
+
height: 5px;
|
|
231
|
+
background: #4fc3f7;
|
|
232
|
+
border-radius: 50%;
|
|
233
|
+
margin-right: 6px;
|
|
234
|
+
vertical-align: middle;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
.day-entry-count {
|
|
238
|
+
position: absolute;
|
|
239
|
+
top: 8px;
|
|
240
|
+
right: 8px;
|
|
241
|
+
background: #4fc3f7;
|
|
242
|
+
color: #1a1a2e;
|
|
243
|
+
font-size: 10px;
|
|
244
|
+
font-weight: 700;
|
|
245
|
+
width: 18px;
|
|
246
|
+
height: 18px;
|
|
247
|
+
border-radius: 50%;
|
|
248
|
+
display: flex;
|
|
249
|
+
align-items: center;
|
|
250
|
+
justify-content: center;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/* Detail panel */
|
|
254
|
+
.detail-overlay {
|
|
255
|
+
display: none;
|
|
256
|
+
position: fixed;
|
|
257
|
+
top: 0; left: 0; right: 0; bottom: 0;
|
|
258
|
+
background: rgba(10, 10, 20, 0.7);
|
|
259
|
+
z-index: 100;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
.detail-overlay.open { display: flex; align-items: center; justify-content: center; }
|
|
263
|
+
|
|
264
|
+
.detail-panel {
|
|
265
|
+
background: #1e1e38;
|
|
266
|
+
border-radius: 16px;
|
|
267
|
+
width: 90%;
|
|
268
|
+
max-width: 700px;
|
|
269
|
+
max-height: 80vh;
|
|
270
|
+
overflow-y: auto;
|
|
271
|
+
padding: 28px 32px;
|
|
272
|
+
box-shadow: 0 12px 40px rgba(0,0,0,0.5);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
.detail-panel::-webkit-scrollbar { width: 6px; }
|
|
276
|
+
.detail-panel::-webkit-scrollbar-track { background: transparent; }
|
|
277
|
+
.detail-panel::-webkit-scrollbar-thumb { background: #3a3a5a; border-radius: 3px; }
|
|
278
|
+
|
|
279
|
+
.detail-header {
|
|
280
|
+
display: flex;
|
|
281
|
+
align-items: center;
|
|
282
|
+
justify-content: space-between;
|
|
283
|
+
margin-bottom: 20px;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
.detail-header h2 {
|
|
287
|
+
font-size: 18px;
|
|
288
|
+
font-weight: 600;
|
|
289
|
+
color: #fff;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
.detail-close {
|
|
293
|
+
background: none;
|
|
294
|
+
border: none;
|
|
295
|
+
color: #808098;
|
|
296
|
+
font-size: 22px;
|
|
297
|
+
cursor: pointer;
|
|
298
|
+
padding: 4px 8px;
|
|
299
|
+
border-radius: 6px;
|
|
300
|
+
transition: background 0.15s;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
.detail-close:hover { background: #2a2a4a; color: #e0e0e0; }
|
|
304
|
+
|
|
305
|
+
.detail-entry {
|
|
306
|
+
margin-bottom: 20px;
|
|
307
|
+
padding: 16px;
|
|
308
|
+
background: #252545;
|
|
309
|
+
border-radius: 10px;
|
|
310
|
+
border-left: 3px solid #4fc3f7;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
.detail-entry:last-child { margin-bottom: 0; }
|
|
314
|
+
|
|
315
|
+
.detail-entry h3 {
|
|
316
|
+
font-size: 15px;
|
|
317
|
+
font-weight: 600;
|
|
318
|
+
color: #e0e0e0;
|
|
319
|
+
margin-bottom: 4px;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
.detail-entry .entry-meta {
|
|
323
|
+
font-size: 12px;
|
|
324
|
+
color: #808098;
|
|
325
|
+
font-family: 'JetBrains Mono', monospace;
|
|
326
|
+
margin-bottom: 12px;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
.detail-section {
|
|
330
|
+
margin-top: 10px;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
.detail-section .section-label {
|
|
334
|
+
font-size: 11px;
|
|
335
|
+
font-weight: 600;
|
|
336
|
+
text-transform: uppercase;
|
|
337
|
+
letter-spacing: 0.5px;
|
|
338
|
+
margin-bottom: 4px;
|
|
339
|
+
display: block;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
.detail-section.changes .section-label { color: #4fc3f7; }
|
|
343
|
+
.detail-section.decisions .section-label { color: #ce93d8; }
|
|
344
|
+
.detail-section.issues .section-label { color: #ef9a9a; }
|
|
345
|
+
.detail-section.next-steps .section-label { color: #a5d6a7; }
|
|
346
|
+
|
|
347
|
+
.detail-section ul {
|
|
348
|
+
list-style: none;
|
|
349
|
+
padding: 0;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
.detail-section ul li {
|
|
353
|
+
font-size: 13px;
|
|
354
|
+
color: #c0c0d8;
|
|
355
|
+
padding: 3px 0;
|
|
356
|
+
padding-left: 14px;
|
|
357
|
+
position: relative;
|
|
358
|
+
line-height: 1.5;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
.detail-section ul li::before {
|
|
362
|
+
content: "";
|
|
363
|
+
position: absolute;
|
|
364
|
+
left: 0;
|
|
365
|
+
top: 10px;
|
|
366
|
+
width: 5px;
|
|
367
|
+
height: 5px;
|
|
368
|
+
border-radius: 50%;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
.detail-section.changes ul li::before { background: #4fc3f7; }
|
|
372
|
+
.detail-section.decisions ul li::before { background: #ce93d8; }
|
|
373
|
+
.detail-section.issues ul li::before { background: #ef9a9a; }
|
|
374
|
+
.detail-section.next-steps ul li::before { background: #a5d6a7; }
|
|
375
|
+
|
|
376
|
+
/* Empty state */
|
|
377
|
+
.empty-state {
|
|
378
|
+
text-align: center;
|
|
379
|
+
padding: 80px 32px;
|
|
380
|
+
color: #808098;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
.empty-state h2 { font-size: 18px; margin-bottom: 8px; color: #a0a0c0; }
|
|
384
|
+
.empty-state p { font-size: 14px; }
|
|
385
|
+
</style>
|
|
386
|
+
</head>
|
|
387
|
+
<body>
|
|
388
|
+
|
|
389
|
+
<div class="header">
|
|
390
|
+
<h1>${esc(projectName)} <span>/ daily view</span></h1>
|
|
391
|
+
<div class="branch-badge">${esc(currentBranch)}</div>
|
|
392
|
+
</div>
|
|
393
|
+
|
|
394
|
+
<div class="nav">
|
|
395
|
+
<button onclick="prevMonth()">← Prev</button>
|
|
396
|
+
<div class="month-label" id="month-label"></div>
|
|
397
|
+
<button onclick="nextMonth()">Next →</button>
|
|
398
|
+
<button onclick="goToday()" style="margin-left: 12px; background: #4fc3f7; color: #1a1a2e; font-weight: 600;">Today</button>
|
|
399
|
+
</div>
|
|
400
|
+
|
|
401
|
+
<div class="calendar-container">
|
|
402
|
+
<div class="weekday-header">
|
|
403
|
+
<div>Sun</div><div>Mon</div><div>Tue</div><div>Wed</div><div>Thu</div><div>Fri</div><div>Sat</div>
|
|
404
|
+
</div>
|
|
405
|
+
<div class="calendar-grid" id="calendar-grid"></div>
|
|
406
|
+
</div>
|
|
407
|
+
|
|
408
|
+
<div class="detail-overlay" id="detail-overlay" onclick="closeDetail(event)">
|
|
409
|
+
<div class="detail-panel" id="detail-panel" onclick="event.stopPropagation()">
|
|
410
|
+
<div class="detail-header">
|
|
411
|
+
<h2 id="detail-title"></h2>
|
|
412
|
+
<button class="detail-close" onclick="closeDetail()">×</button>
|
|
413
|
+
</div>
|
|
414
|
+
<div id="detail-body"></div>
|
|
415
|
+
</div>
|
|
416
|
+
</div>
|
|
417
|
+
|
|
418
|
+
<script>
|
|
419
|
+
const DAYS_DATA = ${daysJson};
|
|
420
|
+
|
|
421
|
+
// Index entries by date
|
|
422
|
+
const dateMap = {};
|
|
423
|
+
DAYS_DATA.forEach(d => { dateMap[d.date] = d.entries; });
|
|
424
|
+
|
|
425
|
+
// State
|
|
426
|
+
const today = new Date();
|
|
427
|
+
let viewYear = today.getFullYear();
|
|
428
|
+
let viewMonth = today.getMonth();
|
|
429
|
+
|
|
430
|
+
const MONTH_NAMES = ["January","February","March","April","May","June","July","August","September","October","November","December"];
|
|
431
|
+
|
|
432
|
+
function todayStr() {
|
|
433
|
+
const y = today.getFullYear();
|
|
434
|
+
const m = String(today.getMonth() + 1).padStart(2, "0");
|
|
435
|
+
const d = String(today.getDate()).padStart(2, "0");
|
|
436
|
+
return y + "-" + m + "-" + d;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function dateStr(y, m, d) {
|
|
440
|
+
return y + "-" + String(m + 1).padStart(2, "0") + "-" + String(d).padStart(2, "0");
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
function esc(s) {
|
|
444
|
+
const el = document.createElement("span");
|
|
445
|
+
el.textContent = s;
|
|
446
|
+
return el.innerHTML;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
function render() {
|
|
450
|
+
document.getElementById("month-label").textContent = MONTH_NAMES[viewMonth] + " " + viewYear;
|
|
451
|
+
|
|
452
|
+
const grid = document.getElementById("calendar-grid");
|
|
453
|
+
grid.innerHTML = "";
|
|
454
|
+
|
|
455
|
+
const firstDay = new Date(viewYear, viewMonth, 1).getDay();
|
|
456
|
+
const daysInMonth = new Date(viewYear, viewMonth + 1, 0).getDate();
|
|
457
|
+
const todayString = todayStr();
|
|
458
|
+
|
|
459
|
+
// Empty cells before first day
|
|
460
|
+
for (let i = 0; i < firstDay; i++) {
|
|
461
|
+
const cell = document.createElement("div");
|
|
462
|
+
cell.className = "day-cell empty";
|
|
463
|
+
grid.appendChild(cell);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
for (let d = 1; d <= daysInMonth; d++) {
|
|
467
|
+
const ds = dateStr(viewYear, viewMonth, d);
|
|
468
|
+
const entries = dateMap[ds] || [];
|
|
469
|
+
const isToday = ds === todayString;
|
|
470
|
+
const hasEntries = entries.length > 0;
|
|
471
|
+
|
|
472
|
+
const cell = document.createElement("div");
|
|
473
|
+
cell.className = "day-cell" + (isToday ? " today" : "") + (hasEntries ? " has-entries" : "");
|
|
474
|
+
|
|
475
|
+
if (hasEntries) {
|
|
476
|
+
cell.onclick = function() { openDetail(ds, entries); };
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
let inner = '<div class="day-number">' + d + '</div>';
|
|
480
|
+
|
|
481
|
+
if (hasEntries) {
|
|
482
|
+
inner += '<div class="day-entry-count">' + entries.length + '</div>';
|
|
483
|
+
inner += '<ul class="day-bullets">';
|
|
484
|
+
// Show up to 4 bullet items from whatChanged across all entries
|
|
485
|
+
const bullets = [];
|
|
486
|
+
for (const e of entries) {
|
|
487
|
+
for (const item of e.whatChanged) {
|
|
488
|
+
bullets.push(item);
|
|
489
|
+
if (bullets.length >= 4) break;
|
|
490
|
+
}
|
|
491
|
+
if (bullets.length >= 4) break;
|
|
492
|
+
}
|
|
493
|
+
if (bullets.length === 0) {
|
|
494
|
+
// Fallback to entry titles
|
|
495
|
+
for (const e of entries) {
|
|
496
|
+
if (e.title) bullets.push(e.title);
|
|
497
|
+
if (bullets.length >= 4) break;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
for (const b of bullets) {
|
|
501
|
+
inner += '<li>' + esc(b) + '</li>';
|
|
502
|
+
}
|
|
503
|
+
inner += '</ul>';
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
cell.innerHTML = inner;
|
|
507
|
+
grid.appendChild(cell);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
function openDetail(ds, entries) {
|
|
512
|
+
const overlay = document.getElementById("detail-overlay");
|
|
513
|
+
const d = new Date(ds + "T12:00:00");
|
|
514
|
+
document.getElementById("detail-title").textContent =
|
|
515
|
+
MONTH_NAMES[d.getMonth()] + " " + d.getDate() + ", " + d.getFullYear();
|
|
516
|
+
|
|
517
|
+
let html = "";
|
|
518
|
+
for (const e of entries) {
|
|
519
|
+
html += '<div class="detail-entry">';
|
|
520
|
+
html += '<h3>' + esc(e.title) + '</h3>';
|
|
521
|
+
|
|
522
|
+
const meta = [];
|
|
523
|
+
if (e.commit) meta.push(e.commit);
|
|
524
|
+
if (e.date) {
|
|
525
|
+
try { meta.push(new Date(e.date).toLocaleTimeString()); } catch(x) {}
|
|
526
|
+
}
|
|
527
|
+
if (meta.length) html += '<div class="entry-meta">' + esc(meta.join(" / ")) + '</div>';
|
|
528
|
+
|
|
529
|
+
if (e.whatChanged.length) {
|
|
530
|
+
html += '<div class="detail-section changes"><span class="section-label">What Changed</span><ul>';
|
|
531
|
+
e.whatChanged.forEach(function(item) { html += '<li>' + esc(item) + '</li>'; });
|
|
532
|
+
html += '</ul></div>';
|
|
533
|
+
}
|
|
534
|
+
if (e.decisions.length) {
|
|
535
|
+
html += '<div class="detail-section decisions"><span class="section-label">Decisions</span><ul>';
|
|
536
|
+
e.decisions.forEach(function(item) { html += '<li>' + esc(item) + '</li>'; });
|
|
537
|
+
html += '</ul></div>';
|
|
538
|
+
}
|
|
539
|
+
if (e.issues.length) {
|
|
540
|
+
html += '<div class="detail-section issues"><span class="section-label">Issues</span><ul>';
|
|
541
|
+
e.issues.forEach(function(item) { html += '<li>' + esc(item) + '</li>'; });
|
|
542
|
+
html += '</ul></div>';
|
|
543
|
+
}
|
|
544
|
+
if (e.nextSteps.length) {
|
|
545
|
+
html += '<div class="detail-section next-steps"><span class="section-label">Next Steps</span><ul>';
|
|
546
|
+
e.nextSteps.forEach(function(item) { html += '<li>' + esc(item) + '</li>'; });
|
|
547
|
+
html += '</ul></div>';
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
html += '</div>';
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
if (!html) html = '<div style="color:#808098;text-align:center;padding:20px;">No details available</div>';
|
|
554
|
+
|
|
555
|
+
document.getElementById("detail-body").innerHTML = html;
|
|
556
|
+
overlay.classList.add("open");
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
function closeDetail(event) {
|
|
560
|
+
if (event && event.target !== document.getElementById("detail-overlay")) return;
|
|
561
|
+
document.getElementById("detail-overlay").classList.remove("open");
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
function prevMonth() {
|
|
565
|
+
viewMonth--;
|
|
566
|
+
if (viewMonth < 0) { viewMonth = 11; viewYear--; }
|
|
567
|
+
render();
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
function nextMonth() {
|
|
571
|
+
viewMonth++;
|
|
572
|
+
if (viewMonth > 11) { viewMonth = 0; viewYear++; }
|
|
573
|
+
render();
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
function goToday() {
|
|
577
|
+
viewYear = today.getFullYear();
|
|
578
|
+
viewMonth = today.getMonth();
|
|
579
|
+
render();
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
document.addEventListener("keydown", function(e) {
|
|
583
|
+
if (e.key === "Escape") {
|
|
584
|
+
document.getElementById("detail-overlay").classList.remove("open");
|
|
585
|
+
} else if (e.key === "ArrowLeft") {
|
|
586
|
+
prevMonth();
|
|
587
|
+
} else if (e.key === "ArrowRight") {
|
|
588
|
+
nextMonth();
|
|
589
|
+
}
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
render();
|
|
593
|
+
</script>
|
|
594
|
+
</body>
|
|
595
|
+
</html>`;
|
|
596
|
+
}
|