project-logbook 1.2.0 → 1.3.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.
Files changed (193) hide show
  1. package/README.md +2 -0
  2. package/dist/commands/build.js +7 -4
  3. package/dist/commands/build.js.map +1 -1
  4. package/dist/commands/log.js +2 -0
  5. package/dist/commands/log.js.map +1 -1
  6. package/dist/commands/new-helpers.d.ts +0 -1
  7. package/dist/commands/new-helpers.js +6 -3
  8. package/dist/commands/new-helpers.js.map +1 -1
  9. package/dist/commands/release.js +2 -0
  10. package/dist/commands/release.js.map +1 -1
  11. package/dist/commands/start.js +90 -21
  12. package/dist/commands/start.js.map +1 -1
  13. package/dist/commands/status.js +3 -0
  14. package/dist/commands/status.js.map +1 -1
  15. package/dist/commands/watch.d.ts +15 -0
  16. package/dist/commands/watch.js +153 -0
  17. package/dist/commands/watch.js.map +1 -0
  18. package/dist/index.js +7 -0
  19. package/dist/index.js.map +1 -1
  20. package/dist/lib/autolink.d.ts +5 -0
  21. package/dist/lib/autolink.js +36 -2
  22. package/dist/lib/autolink.js.map +1 -1
  23. package/dist/lib/build-helpers.d.ts +10 -1
  24. package/dist/lib/build-helpers.js +27 -4
  25. package/dist/lib/build-helpers.js.map +1 -1
  26. package/dist/lib/build-steps.js +2 -1
  27. package/dist/lib/build-steps.js.map +1 -1
  28. package/dist/lib/config.d.ts +8 -0
  29. package/dist/lib/config.js +7 -0
  30. package/dist/lib/config.js.map +1 -1
  31. package/dist/lib/custom-card-renderer.js +2 -1
  32. package/dist/lib/custom-card-renderer.js.map +1 -1
  33. package/dist/lib/entry-href.d.ts +7 -0
  34. package/dist/lib/entry-href.js +8 -0
  35. package/dist/lib/entry-href.js.map +1 -0
  36. package/dist/lib/markdown-processors.d.ts +2 -1
  37. package/dist/lib/post-template.js +3 -3
  38. package/dist/lib/post-template.js.map +1 -1
  39. package/dist/lib/template-helpers.js +3 -2
  40. package/dist/lib/template-helpers.js.map +1 -1
  41. package/dist/lib/template-types.d.ts +2 -0
  42. package/dist/lib/templates.js +5 -3
  43. package/dist/lib/templates.js.map +1 -1
  44. package/dist/linters/jira-prefix.js +32 -16
  45. package/dist/linters/jira-prefix.js.map +1 -1
  46. package/dist/templates/about-watch.md +29 -0
  47. package/dist/templates/hub.css +677 -0
  48. package/dist/templates/hub.js +628 -0
  49. package/dist/utils/id.d.ts +1 -1
  50. package/dist/utils/id.js +4 -1
  51. package/dist/utils/id.js.map +1 -1
  52. package/dist/watch/collector.d.ts +14 -0
  53. package/dist/watch/collector.js +33 -0
  54. package/dist/watch/collector.js.map +1 -0
  55. package/dist/watch/collectors/acli.d.ts +15 -0
  56. package/dist/watch/collectors/acli.js +197 -0
  57. package/dist/watch/collectors/acli.js.map +1 -0
  58. package/dist/watch/collectors/entries.d.ts +9 -0
  59. package/dist/watch/collectors/entries.js +90 -0
  60. package/dist/watch/collectors/entries.js.map +1 -0
  61. package/dist/watch/collectors/git.d.ts +12 -0
  62. package/dist/watch/collectors/git.js +58 -0
  63. package/dist/watch/collectors/git.js.map +1 -0
  64. package/dist/watch/collectors/glab.d.ts +10 -0
  65. package/dist/watch/collectors/glab.js +184 -0
  66. package/dist/watch/collectors/glab.js.map +1 -0
  67. package/dist/watch/collectors/index.d.ts +10 -0
  68. package/dist/watch/collectors/index.js +21 -0
  69. package/dist/watch/collectors/index.js.map +1 -0
  70. package/dist/watch/collectors/jira.d.ts +3 -0
  71. package/dist/watch/collectors/jira.js +110 -0
  72. package/dist/watch/collectors/jira.js.map +1 -0
  73. package/dist/watch/collectors/type-guards.d.ts +17 -0
  74. package/dist/watch/collectors/type-guards.js +35 -0
  75. package/dist/watch/collectors/type-guards.js.map +1 -0
  76. package/dist/watch/collectors/types.d.ts +37 -0
  77. package/dist/watch/collectors/types.js +9 -0
  78. package/dist/watch/collectors/types.js.map +1 -0
  79. package/dist/watch/dashboard-html.d.ts +11 -0
  80. package/dist/watch/dashboard-html.js +73 -0
  81. package/dist/watch/dashboard-html.js.map +1 -0
  82. package/dist/watch/dashboard-script.d.ts +12 -0
  83. package/dist/watch/dashboard-script.js +20 -0
  84. package/dist/watch/dashboard-script.js.map +1 -0
  85. package/dist/watch/dashboard-styles.d.ts +12 -0
  86. package/dist/watch/dashboard-styles.js +20 -0
  87. package/dist/watch/dashboard-styles.js.map +1 -0
  88. package/dist/watch/dashboard.d.ts +12 -0
  89. package/dist/watch/dashboard.js +50 -0
  90. package/dist/watch/dashboard.js.map +1 -0
  91. package/dist/watch/git-utils.d.ts +30 -0
  92. package/dist/watch/git-utils.js +101 -0
  93. package/dist/watch/git-utils.js.map +1 -0
  94. package/dist/watch/hub-state.d.ts +37 -0
  95. package/dist/watch/hub-state.js +55 -0
  96. package/dist/watch/hub-state.js.map +1 -0
  97. package/dist/watch/hub.d.ts +2 -0
  98. package/dist/watch/hub.js +219 -0
  99. package/dist/watch/hub.js.map +1 -0
  100. package/dist/watch/log-utils.d.ts +13 -0
  101. package/dist/watch/log-utils.js +24 -0
  102. package/dist/watch/log-utils.js.map +1 -0
  103. package/dist/watch/notify.d.ts +18 -0
  104. package/dist/watch/notify.js +45 -0
  105. package/dist/watch/notify.js.map +1 -0
  106. package/dist/watch/state.d.ts +34 -0
  107. package/dist/watch/state.js +36 -0
  108. package/dist/watch/state.js.map +1 -0
  109. package/dist/watch/strategies/behind-default.d.ts +5 -0
  110. package/dist/watch/strategies/behind-default.js +21 -0
  111. package/dist/watch/strategies/behind-default.js.map +1 -0
  112. package/dist/watch/strategies/idle.d.ts +5 -0
  113. package/dist/watch/strategies/idle.js +21 -0
  114. package/dist/watch/strategies/idle.js.map +1 -0
  115. package/dist/watch/strategies/in-progress.d.ts +5 -0
  116. package/dist/watch/strategies/in-progress.js +21 -0
  117. package/dist/watch/strategies/in-progress.js.map +1 -0
  118. package/dist/watch/strategies/index.d.ts +34 -0
  119. package/dist/watch/strategies/index.js +148 -0
  120. package/dist/watch/strategies/index.js.map +1 -0
  121. package/dist/watch/strategies/jira-not-in-review.d.ts +2 -0
  122. package/dist/watch/strategies/jira-not-in-review.js +29 -0
  123. package/dist/watch/strategies/jira-not-in-review.js.map +1 -0
  124. package/dist/watch/strategies/jira-still-open.d.ts +2 -0
  125. package/dist/watch/strategies/jira-still-open.js +31 -0
  126. package/dist/watch/strategies/jira-still-open.js.map +1 -0
  127. package/dist/watch/strategies/mr-approval-optional.d.ts +5 -0
  128. package/dist/watch/strategies/mr-approval-optional.js +21 -0
  129. package/dist/watch/strategies/mr-approval-optional.js.map +1 -0
  130. package/dist/watch/strategies/mr-approved.d.ts +5 -0
  131. package/dist/watch/strategies/mr-approved.js +21 -0
  132. package/dist/watch/strategies/mr-approved.js.map +1 -0
  133. package/dist/watch/strategies/mr-conflicts.d.ts +5 -0
  134. package/dist/watch/strategies/mr-conflicts.js +21 -0
  135. package/dist/watch/strategies/mr-conflicts.js.map +1 -0
  136. package/dist/watch/strategies/mr-draft.d.ts +5 -0
  137. package/dist/watch/strategies/mr-draft.js +21 -0
  138. package/dist/watch/strategies/mr-draft.js.map +1 -0
  139. package/dist/watch/strategies/mr-open-comments.d.ts +5 -0
  140. package/dist/watch/strategies/mr-open-comments.js +21 -0
  141. package/dist/watch/strategies/mr-open-comments.js.map +1 -0
  142. package/dist/watch/strategies/narrative-missing.d.ts +5 -0
  143. package/dist/watch/strategies/narrative-missing.js +21 -0
  144. package/dist/watch/strategies/narrative-missing.js.map +1 -0
  145. package/dist/watch/strategies/no-mr.d.ts +5 -0
  146. package/dist/watch/strategies/no-mr.js +21 -0
  147. package/dist/watch/strategies/no-mr.js.map +1 -0
  148. package/dist/watch/strategies/pipeline-failed.d.ts +5 -0
  149. package/dist/watch/strategies/pipeline-failed.js +21 -0
  150. package/dist/watch/strategies/pipeline-failed.js.map +1 -0
  151. package/dist/watch/strategies/pipeline-running.d.ts +5 -0
  152. package/dist/watch/strategies/pipeline-running.js +21 -0
  153. package/dist/watch/strategies/pipeline-running.js.map +1 -0
  154. package/dist/watch/strategies/ready-for-release.d.ts +7 -0
  155. package/dist/watch/strategies/ready-for-release.js +35 -0
  156. package/dist/watch/strategies/ready-for-release.js.map +1 -0
  157. package/dist/watch/strategies/ready-to-commit.d.ts +5 -0
  158. package/dist/watch/strategies/ready-to-commit.js +21 -0
  159. package/dist/watch/strategies/ready-to-commit.js.map +1 -0
  160. package/dist/watch/strategies/ready-to-push.d.ts +5 -0
  161. package/dist/watch/strategies/ready-to-push.js +21 -0
  162. package/dist/watch/strategies/ready-to-push.js.map +1 -0
  163. package/dist/watch/strategies/released.d.ts +6 -0
  164. package/dist/watch/strategies/released.js +21 -0
  165. package/dist/watch/strategies/released.js.map +1 -0
  166. package/dist/watch/strategies/squash-merged.d.ts +6 -0
  167. package/dist/watch/strategies/squash-merged.js +21 -0
  168. package/dist/watch/strategies/squash-merged.js.map +1 -0
  169. package/dist/watch/strategies/types.d.ts +31 -0
  170. package/dist/watch/strategies/types.js +12 -0
  171. package/dist/watch/strategies/types.js.map +1 -0
  172. package/dist/watch/strategies/unstaged-changes.d.ts +5 -0
  173. package/dist/watch/strategies/unstaged-changes.js +21 -0
  174. package/dist/watch/strategies/unstaged-changes.js.map +1 -0
  175. package/dist/watch/strategies/wrong-branch.d.ts +6 -0
  176. package/dist/watch/strategies/wrong-branch.js +26 -0
  177. package/dist/watch/strategies/wrong-branch.js.map +1 -0
  178. package/dist/watch/types.d.ts +190 -0
  179. package/dist/watch/types.js +25 -0
  180. package/dist/watch/types.js.map +1 -0
  181. package/dist/watch/watcher-fs.d.ts +39 -0
  182. package/dist/watch/watcher-fs.js +105 -0
  183. package/dist/watch/watcher-fs.js.map +1 -0
  184. package/dist/watch/watcher-http.d.ts +19 -0
  185. package/dist/watch/watcher-http.js +47 -0
  186. package/dist/watch/watcher-http.js.map +1 -0
  187. package/dist/watch/watcher.d.ts +4 -0
  188. package/dist/watch/watcher.js +174 -0
  189. package/dist/watch/watcher.js.map +1 -0
  190. package/package.json +2 -1
  191. package/src/templates/about-watch.md +29 -0
  192. package/src/templates/hub.css +677 -0
  193. package/src/templates/hub.js +628 -0
@@ -0,0 +1,628 @@
1
+ (function () {
2
+ 'use strict';
3
+
4
+ const projects = {};
5
+ const projectEvents = {};
6
+ const expandedProjects = new Set();
7
+ const projectsEl = document.getElementById('projects');
8
+ const countEl = document.getElementById('project-count');
9
+ const connEl = document.getElementById('conn-status');
10
+ const sortBtn = document.getElementById('sort-toggle');
11
+ const jiraSortBtn = document.getElementById('jira-sort-toggle');
12
+
13
+ // --- Tab switching ---
14
+ const tabs = document.querySelectorAll('.tab-btn');
15
+ const tabContents = document.querySelectorAll('.tab-content');
16
+
17
+ function switchTab(target) {
18
+ tabs.forEach((t) => t.classList.remove('active'));
19
+ tabContents.forEach((tc) => tc.classList.remove('active'));
20
+ const btn = document.querySelector('.tab-btn[data-tab="' + target + '"]');
21
+ if (btn) btn.classList.add('active');
22
+ document.getElementById('tab-' + target).classList.add('active');
23
+ sortBtn.style.display = target === 'projects' ? '' : 'none';
24
+ jiraSortBtn.style.display = target === 'jira' ? '' : 'none';
25
+ }
26
+
27
+ tabs.forEach((tab) => {
28
+ tab.addEventListener('click', () => {
29
+ const target = tab.getAttribute('data-tab');
30
+ window.location.hash = target;
31
+ switchTab(target);
32
+ });
33
+ });
34
+
35
+ function applyHash() {
36
+ const hash = window.location.hash.replace('#', '') || 'projects';
37
+ switchTab(hash);
38
+ }
39
+ applyHash();
40
+ window.addEventListener('hashchange', applyHash);
41
+
42
+ // --- Help tab: fetch strategies ---
43
+ fetch('/_logbook/strategies')
44
+ .then((r) => r.json())
45
+ .then((strategies) => {
46
+ let currentSource = '';
47
+ const tbody = strategies
48
+ .map((s) => {
49
+ let groupHeader = '';
50
+ if (s.source !== currentSource) {
51
+ currentSource = s.source;
52
+ groupHeader = '<tr class="help-group-header"><td colspan="5">' + currentSource + '</td></tr>';
53
+ }
54
+ const blockers =
55
+ s.blockedBy && s.blockedBy.length > 0
56
+ ? '<span class="blocked-by">blocked by: ' + s.blockedBy.join(', ') + '</span>'
57
+ : '';
58
+ return (
59
+ groupHeader +
60
+ '<tr>' +
61
+ '<td><span class="badge badge-' +
62
+ s.severity +
63
+ '">' +
64
+ s.label +
65
+ '</span></td>' +
66
+ '<td>' +
67
+ s.source +
68
+ '</td>' +
69
+ '<td>' +
70
+ s.severity +
71
+ '</td>' +
72
+ '<td>' +
73
+ (s.description || '') +
74
+ '</td>' +
75
+ '<td>' +
76
+ blockers +
77
+ '</td>' +
78
+ '</tr>'
79
+ );
80
+ })
81
+ .join('');
82
+ document.getElementById('help-table-body').innerHTML = tbody;
83
+ })
84
+ .catch(function () {});
85
+
86
+ // --- Sort toggle ---
87
+ var sortMode = localStorage.getItem('logbook-sort') || 'newest';
88
+ sortBtn.textContent = 'Sort (' + (sortMode === 'newest' ? 'Newest' : 'Name') + ')';
89
+ sortBtn.addEventListener('click', function () {
90
+ sortMode = sortMode === 'newest' ? 'name' : 'newest';
91
+ localStorage.setItem('logbook-sort', sortMode);
92
+ sortBtn.textContent = 'Sort (' + (sortMode === 'newest' ? 'Newest' : 'Name') + ')';
93
+ renderProjects();
94
+ });
95
+
96
+ // --- Utility functions ---
97
+ function relativeTime(iso) {
98
+ if (!iso) return '';
99
+ var diff = Date.now() - new Date(iso).getTime();
100
+ var s = Math.floor(diff / 1000);
101
+ if (s < 60) return 'just now';
102
+ var m = Math.floor(s / 60);
103
+ if (m < 60) return m + ' min ago';
104
+ var h = Math.floor(m / 60);
105
+ if (h < 24) return h + 'h ago';
106
+ return Math.floor(h / 24) + 'd ago';
107
+ }
108
+
109
+ function escapeHtml(s) {
110
+ var el = document.createElement('div');
111
+ el.textContent = s;
112
+ return el.innerHTML;
113
+ }
114
+
115
+ function renderCollectorFooter(p) {
116
+ var parts = [];
117
+ if (p.glabLastUpdated) parts.push('GitLab updated ' + relativeTime(p.glabLastUpdated));
118
+ if (p.jiraLastUpdated) parts.push('Jira updated ' + relativeTime(p.jiraLastUpdated));
119
+ if (parts.length === 0) return '';
120
+ return '<div class="card-collector-footer">' + parts.join('. ') + '.</div>';
121
+ }
122
+
123
+ function updateTabFooters() {
124
+ var jiraFooter = document.getElementById('jira-tab-footer');
125
+ var jiraTimestamps = Object.values(projects)
126
+ .map(function (p) {
127
+ return p.jiraLastUpdated;
128
+ })
129
+ .filter(Boolean);
130
+ if (jiraFooter) {
131
+ if (jiraTimestamps.length > 0) {
132
+ var latest = jiraTimestamps.sort().pop();
133
+ jiraFooter.textContent = 'Jira updated ' + relativeTime(latest) + '.';
134
+ jiraFooter.style.display = '';
135
+ } else {
136
+ jiraFooter.style.display = 'none';
137
+ }
138
+ }
139
+ var glabFooter = document.getElementById('gitlab-tab-footer');
140
+ var glabTimestamps = Object.values(projects)
141
+ .map(function (p) {
142
+ return p.glabLastUpdated;
143
+ })
144
+ .filter(Boolean);
145
+ if (glabFooter) {
146
+ if (glabTimestamps.length > 0) {
147
+ var latest = glabTimestamps.sort().pop();
148
+ glabFooter.textContent = 'GitLab updated ' + relativeTime(latest) + '.';
149
+ glabFooter.style.display = '';
150
+ } else {
151
+ glabFooter.style.display = 'none';
152
+ }
153
+ }
154
+ }
155
+
156
+ function renderBranchMeta(p) {
157
+ var branch = p.branch || '\u2014';
158
+ var branchUrl =
159
+ p.mrUrl ||
160
+ (p.repositoryUrl && p.branch
161
+ ? p.repositoryUrl.replace(/\/$/, '') + '/-/tree/' + encodeURIComponent(p.branch)
162
+ : null);
163
+ var value = branchUrl
164
+ ? '<a class="meta-link" href="' +
165
+ escapeHtml(branchUrl) +
166
+ '" target="_blank" rel="noopener">' +
167
+ escapeHtml(branch) +
168
+ '<span class="link-icon">\u00A0&#x2197;</span></a>'
169
+ : escapeHtml(branch);
170
+ return (
171
+ '<div class="meta-item"><span class="meta-label">branch:</span> <span class="meta-value">' +
172
+ value +
173
+ '</span></div>'
174
+ );
175
+ }
176
+
177
+ function renderTicketMeta(p) {
178
+ var ticket = p.activeTicket || 'none';
179
+ var ticketUrl =
180
+ p.jiraBaseUrl && p.activeTicket
181
+ ? p.jiraBaseUrl.replace(/\/+$/, '') + '/browse/' + encodeURIComponent(p.activeTicket)
182
+ : null;
183
+ var value = ticketUrl
184
+ ? '<a class="meta-link" href="' +
185
+ escapeHtml(ticketUrl) +
186
+ '" target="_blank" rel="noopener">' +
187
+ escapeHtml(ticket) +
188
+ '<span class="link-icon">\u00A0&#x2197;</span></a>'
189
+ : escapeHtml(ticket);
190
+ var jiraLabel = p.jiraStatus ? ' <span class="jira-status">' + escapeHtml(p.jiraStatus) + '</span>' : '';
191
+ return (
192
+ '<div class="meta-item"><span class="meta-label">ticket:</span> <span class="meta-value">' +
193
+ value +
194
+ jiraLabel +
195
+ '</span></div>'
196
+ );
197
+ }
198
+
199
+ function renderPathMeta(p) {
200
+ if (!p.projectPath) return '';
201
+ return (
202
+ '<div class="card-path"><span class="meta-label">path:</span> <span class="meta-value meta-path" title="' +
203
+ escapeHtml(p.projectPath) +
204
+ '">' +
205
+ escapeHtml(p.projectPath) +
206
+ '</span></div>'
207
+ );
208
+ }
209
+
210
+ function renderTimeline(path) {
211
+ var events = projectEvents[path];
212
+ if (!events || events.length === 0) return '';
213
+ var items = events
214
+ .slice(0, 20)
215
+ .map(function (e) {
216
+ return (
217
+ '<li><span class="activity-time">' +
218
+ relativeTime(e.timestamp) +
219
+ '</span>' +
220
+ '<span class="activity-source">' +
221
+ escapeHtml(e.source) +
222
+ '</span>' +
223
+ '<span class="activity-desc">' +
224
+ escapeHtml(e.description) +
225
+ '</span></li>'
226
+ );
227
+ })
228
+ .join('');
229
+ return '<div class="activity-timeline"><h4>Activity</h4><ul class="activity-list">' + items + '</ul></div>';
230
+ }
231
+
232
+ function renderProjects() {
233
+ var entries = Object.values(projects);
234
+ countEl.textContent = entries.length;
235
+ countEl.style.display = entries.length > 0 ? '' : 'none';
236
+ if (entries.length === 0) {
237
+ projectsEl.innerHTML = '<div class="empty">Waiting for projects to connect...</div>';
238
+ return;
239
+ }
240
+ if (sortMode === 'newest') {
241
+ entries.sort(function (a, b) {
242
+ return (b.lastActivity || '').localeCompare(a.lastActivity || '');
243
+ });
244
+ } else {
245
+ entries.sort(function (a, b) {
246
+ return (a.projectName || '').localeCompare(b.projectName || '');
247
+ });
248
+ }
249
+ var html = entries
250
+ .map(function (p) {
251
+ var mrIds = new Set([
252
+ 'mr-draft',
253
+ 'mr-open-comments',
254
+ 'mr-approved',
255
+ 'mr-approval-optional',
256
+ 'mr-conflicts',
257
+ 'no-mr',
258
+ ]);
259
+ var badges = (p.statuses || [])
260
+ .map(function (s) {
261
+ var isMr = mrIds.has(s.id) && p.mrUrl;
262
+ if (isMr)
263
+ return (
264
+ '<a class="badge badge-' +
265
+ s.severity +
266
+ '" href="' +
267
+ escapeHtml(p.mrUrl) +
268
+ '" target="_blank" rel="noopener" title="' +
269
+ (s.detail || '') +
270
+ '">' +
271
+ s.label +
272
+ '</a>'
273
+ );
274
+ return (
275
+ '<span class="badge badge-' + s.severity + '" title="' + (s.detail || '') + '">' + s.label + '</span>'
276
+ );
277
+ })
278
+ .join('');
279
+ var log = p.latestLog ? '<div class="card-log">' + escapeHtml(p.latestLog) + '</div>' : '';
280
+ var collectorFooter = renderCollectorFooter(p);
281
+ var d = p.details;
282
+ var grid = d
283
+ ? '<div class="details-grid">' +
284
+ '<div class="detail-item"><div class="detail-label">LOC changed</div><div class="detail-value">' +
285
+ d.locChanged +
286
+ '</div></div>' +
287
+ '<div class="detail-item"><div class="detail-label">Insertions</div><div class="detail-value positive">+' +
288
+ d.insertions +
289
+ '</div></div>' +
290
+ '<div class="detail-item"><div class="detail-label">Deletions</div><div class="detail-value negative">-' +
291
+ d.deletions +
292
+ '</div></div>' +
293
+ '<div class="detail-item"><div class="detail-label">Staged</div><div class="detail-value">' +
294
+ d.stagedFiles +
295
+ '</div></div>' +
296
+ '<div class="detail-item"><div class="detail-label">Unstaged</div><div class="detail-value">' +
297
+ d.unstagedFiles +
298
+ '</div></div>' +
299
+ '<div class="detail-item"><div class="detail-label">Commits ahead</div><div class="detail-value">' +
300
+ d.commitsAhead +
301
+ '</div></div>' +
302
+ '<div class="detail-item"><div class="detail-label">On branch</div><div class="detail-value">' +
303
+ d.commitsOnBranch +
304
+ '</div></div>' +
305
+ '<div class="detail-item"><div class="detail-label">vs</div><div class="detail-value">' +
306
+ escapeHtml(d.defaultBranch) +
307
+ '</div></div>' +
308
+ '</div>'
309
+ : '';
310
+ var details = '<div class="card-details">' + grid + renderTimeline(p.projectPath) + '</div>';
311
+ var cls = expandedProjects.has(p.projectPath) ? ' expanded' : '';
312
+ var borderColor = p.primaryColor && /^#[0-9a-fA-F]{3,8}$/.test(p.primaryColor) ? p.primaryColor : '#dee2e6';
313
+ var agentLabel = p.agentName ? '<span class="card-agent">' + escapeHtml(p.agentName) + '</span>' : '';
314
+ var previewBtns = p.activeTicket
315
+ ? '<span class="preview-buttons">' +
316
+ '<button class="preview-btn" data-preview="story" data-project="' +
317
+ escapeHtml(p.projectPath) +
318
+ '">&#x1F4D6; Story</button>' +
319
+ '<button class="preview-btn" data-preview="log" data-project="' +
320
+ escapeHtml(p.projectPath) +
321
+ '">&#x1F4CB; Log</button>' +
322
+ '<button class="preview-btn" data-preview="ticket" data-project="' +
323
+ escapeHtml(p.projectPath) +
324
+ '">&#x1F3AB; Ticket</button>' +
325
+ '</span>'
326
+ : '';
327
+ return (
328
+ '<div class="card' +
329
+ cls +
330
+ '" data-path="' +
331
+ escapeHtml(p.projectPath) +
332
+ '" style="border-left-color:' +
333
+ borderColor +
334
+ '">' +
335
+ '<div class="card-header"><span class="card-title">' +
336
+ escapeHtml(p.projectName) +
337
+ agentLabel +
338
+ '</span><span class="card-time">' +
339
+ relativeTime(p.lastActivity) +
340
+ '</span></div>' +
341
+ '<div class="card-meta">' +
342
+ renderBranchMeta(p) +
343
+ renderTicketMeta(p) +
344
+ '</div>' +
345
+ renderPathMeta(p) +
346
+ '<div class="badges">' +
347
+ badges +
348
+ previewBtns +
349
+ '</div>' +
350
+ log +
351
+ details +
352
+ collectorFooter +
353
+ '</div>'
354
+ );
355
+ })
356
+ .join('');
357
+ projectsEl.innerHTML = html;
358
+ projectsEl.querySelectorAll('.card').forEach(function (card) {
359
+ card.addEventListener('click', function (e) {
360
+ if (e.target.closest('a')) return;
361
+ var path = card.getAttribute('data-path');
362
+ if (expandedProjects.has(path)) expandedProjects.delete(path);
363
+ else expandedProjects.add(path);
364
+ renderProjects();
365
+ });
366
+ });
367
+ }
368
+
369
+ // --- Jira tab ---
370
+ var jiraSortMode = localStorage.getItem('logbook-jira-sort') || 'updated';
371
+ var jiraSortLabels = { updated: 'Updated', key: 'Key', status: 'Status' };
372
+ jiraSortBtn.textContent = 'Sort (' + jiraSortLabels[jiraSortMode] + ')';
373
+ jiraSortBtn.addEventListener('click', function () {
374
+ var modes = ['updated', 'key', 'status'];
375
+ var idx = modes.indexOf(jiraSortMode);
376
+ jiraSortMode = modes[(idx + 1) % modes.length];
377
+ localStorage.setItem('logbook-jira-sort', jiraSortMode);
378
+ jiraSortBtn.textContent = 'Sort (' + jiraSortLabels[jiraSortMode] + ')';
379
+ renderJiraIssues();
380
+ });
381
+
382
+ var cachedJiraIssues = [];
383
+
384
+ function renderJiraIssues() {
385
+ var container = document.getElementById('jira-issues-content');
386
+ var issues = cachedJiraIssues;
387
+ if (!issues || issues.length === 0) {
388
+ container.innerHTML =
389
+ '<div class="placeholder-msg">No assigned Jira issues found.<br>Set the <code>JIRA_API_TOKEN</code> environment variable and connect a project with <code>jiraBaseUrl</code> configured.</div>';
390
+ return;
391
+ }
392
+
393
+ var sorted = [...issues];
394
+ if (jiraSortMode === 'updated') {
395
+ sorted.sort(function (a, b) {
396
+ return (b.updated || '').localeCompare(a.updated || '');
397
+ });
398
+ } else if (jiraSortMode === 'key') {
399
+ sorted.sort(function (a, b) {
400
+ return a.key.localeCompare(b.key);
401
+ });
402
+ } else if (jiraSortMode === 'status') {
403
+ sorted.sort(function (a, b) {
404
+ return a.status.localeCompare(b.status);
405
+ });
406
+ }
407
+
408
+ var grouped = {};
409
+ sorted.forEach(function (i) {
410
+ if (!grouped[i.jiraBaseUrl]) grouped[i.jiraBaseUrl] = [];
411
+ grouped[i.jiraBaseUrl].push(i);
412
+ });
413
+
414
+ var html = '';
415
+ for (var baseUrl in grouped) {
416
+ if (!grouped.hasOwnProperty(baseUrl)) continue;
417
+ var items = grouped[baseUrl];
418
+ html +=
419
+ '<h3 class="group-heading"><a href="' +
420
+ escapeHtml(baseUrl) +
421
+ '" target="_blank" rel="noopener">' +
422
+ escapeHtml(baseUrl) +
423
+ '<span class="group-link-icon"> &#x2197;</span></a></h3>';
424
+ html +=
425
+ '<table class="help-table"><thead><tr><th>Key</th><th>Status</th><th>Summary</th><th>Last Update</th></tr></thead><tbody>';
426
+ items.forEach(function (i) {
427
+ html +=
428
+ '<tr>' +
429
+ '<td class="cell-mono"><a class="meta-link" href="' +
430
+ escapeHtml(i.url) +
431
+ '" target="_blank" rel="noopener">' +
432
+ escapeHtml(i.key) +
433
+ '<span class="link-icon">\u00A0&#x2197;</span></a></td>' +
434
+ '<td><span class="jira-status">' +
435
+ escapeHtml(i.status) +
436
+ '</span></td>' +
437
+ '<td>' +
438
+ escapeHtml(i.summary) +
439
+ '</td>' +
440
+ '<td class="cell-time">' +
441
+ relativeTime(i.updated) +
442
+ '</td>' +
443
+ '</tr>';
444
+ });
445
+ html += '</tbody></table>';
446
+ }
447
+ container.innerHTML = html;
448
+ }
449
+
450
+ function loadJiraIssues() {
451
+ fetch('/_logbook/jira-issues')
452
+ .then(function (r) {
453
+ return r.json();
454
+ })
455
+ .then(function (issues) {
456
+ var countEl2 = document.getElementById('jira-count');
457
+ var count = issues ? issues.length : 0;
458
+ countEl2.textContent = count;
459
+ countEl2.style.display = count > 0 ? '' : 'none';
460
+ cachedJiraIssues = issues || [];
461
+ renderJiraIssues();
462
+ })
463
+ .catch(function () {
464
+ document.getElementById('jira-issues-content').innerHTML =
465
+ '<div class="placeholder-msg">Failed to load Jira issues.</div>';
466
+ });
467
+ }
468
+ loadJiraIssues();
469
+ setInterval(loadJiraIssues, 5 * 60 * 1000);
470
+
471
+ // --- GitLab tab ---
472
+ function loadGitLabMrs() {
473
+ fetch('/_logbook/gitlab-mrs')
474
+ .then(function (r) {
475
+ return r.json();
476
+ })
477
+ .then(function (mrs) {
478
+ var container = document.getElementById('gitlab-mrs-content');
479
+ var countEl3 = document.getElementById('gitlab-count');
480
+ var count = mrs ? mrs.length : 0;
481
+ countEl3.textContent = count;
482
+ countEl3.style.display = count > 0 ? '' : 'none';
483
+ if (!mrs || mrs.length === 0) {
484
+ container.innerHTML =
485
+ '<div class="placeholder-msg">No open merge requests found.<br>Install the <code>glab</code> CLI and run <code>glab auth login</code> to connect.</div>';
486
+ return;
487
+ }
488
+ var grouped = {};
489
+ mrs.forEach(function (m) {
490
+ if (!grouped[m.projectUrl]) grouped[m.projectUrl] = [];
491
+ grouped[m.projectUrl].push(m);
492
+ });
493
+ var html = '';
494
+ for (var projUrl in grouped) {
495
+ if (!grouped.hasOwnProperty(projUrl)) continue;
496
+ var items = grouped[projUrl];
497
+ var fullName = items[0].projectName || projUrl;
498
+ var projName = fullName.includes(' / ') ? fullName.split(' / ').pop() : fullName;
499
+ html +=
500
+ '<h3 class="group-heading">' +
501
+ '<a href="' +
502
+ escapeHtml(projUrl) +
503
+ '" target="_blank" rel="noopener">' +
504
+ escapeHtml(projName) +
505
+ '<span class="group-link-icon"> &#x2197;</span></a></h3>';
506
+ html +=
507
+ '<table class="help-table"><thead><tr><th>Title</th><th>Status</th><th>Comments</th><th>Branch</th></tr></thead><tbody>';
508
+ items.forEach(function (m) {
509
+ var statusBadge =
510
+ m.status === 'draft'
511
+ ? '<span class="badge badge-info">Draft</span>'
512
+ : '<span class="badge badge-success">Open</span>';
513
+ var branchUrl = projUrl ? projUrl.replace(/\/$/, '') + '/-/tree/' + encodeURIComponent(m.sourceBranch) : '';
514
+ var branchCell = branchUrl
515
+ ? '<a class="meta-link" href="' +
516
+ escapeHtml(branchUrl) +
517
+ '" target="_blank" rel="noopener">' +
518
+ escapeHtml(m.sourceBranch) +
519
+ '<span class="link-icon">\u00A0&#x2197;</span></a>'
520
+ : escapeHtml(m.sourceBranch);
521
+ var titleCell = m.url
522
+ ? '<a class="meta-link" href="' +
523
+ escapeHtml(m.url) +
524
+ '" target="_blank" rel="noopener">' +
525
+ escapeHtml(m.title) +
526
+ '<span class="link-icon">\u00A0&#x2197;</span></a>'
527
+ : escapeHtml(m.title);
528
+ html +=
529
+ '<tr>' +
530
+ '<td>' +
531
+ titleCell +
532
+ '</td>' +
533
+ '<td>' +
534
+ statusBadge +
535
+ '</td>' +
536
+ '<td>' +
537
+ m.comments +
538
+ '</td>' +
539
+ '<td class="cell-branch">' +
540
+ branchCell +
541
+ '</td>' +
542
+ '</tr>';
543
+ });
544
+ html += '</tbody></table>';
545
+ }
546
+ container.innerHTML = html;
547
+ })
548
+ .catch(function () {
549
+ document.getElementById('gitlab-mrs-content').innerHTML =
550
+ '<div class="placeholder-msg">Failed to load merge requests.</div>';
551
+ });
552
+ }
553
+ loadGitLabMrs();
554
+ setInterval(loadGitLabMrs, 5 * 60 * 1000);
555
+
556
+ // --- SSE connection ---
557
+ function connect() {
558
+ var es = new EventSource('/_logbook/events');
559
+ es.onopen = function () {
560
+ connEl.textContent = 'connected';
561
+ connEl.classList.remove('disconnected');
562
+ };
563
+ es.onmessage = function (e) {
564
+ try {
565
+ var data = JSON.parse(e.data);
566
+ if (data.type === 'status-update') {
567
+ projects[data.project.projectPath] = data.project;
568
+ renderProjects();
569
+ updateTabFooters();
570
+ } else if (data.type === 'project-removed') {
571
+ delete projects[data.projectPath];
572
+ delete projectEvents[data.projectPath];
573
+ renderProjects();
574
+ } else if (data.type === 'full-state') {
575
+ for (var i = 0; i < data.projects.length; i++) projects[data.projects[i].projectPath] = data.projects[i];
576
+ if (data.events)
577
+ for (var path in data.events) {
578
+ if (data.events.hasOwnProperty(path)) projectEvents[path] = data.events[path];
579
+ }
580
+ renderProjects();
581
+ updateTabFooters();
582
+ } else if (data.type === 'activity-event') {
583
+ if (!projectEvents[data.projectPath]) projectEvents[data.projectPath] = [];
584
+ projectEvents[data.projectPath].unshift(data.event);
585
+ renderProjects();
586
+ }
587
+ } catch (_) {}
588
+ };
589
+ es.onerror = function () {
590
+ connEl.textContent = 'disconnected';
591
+ connEl.classList.add('disconnected');
592
+ es.close();
593
+ setTimeout(connect, 3000);
594
+ };
595
+ }
596
+
597
+ setInterval(renderProjects, 30000);
598
+ connect();
599
+
600
+ // --- Lightbox ---
601
+ var lightbox = document.getElementById('lightbox');
602
+ var lightboxTitle = document.getElementById('lightbox-title');
603
+ var lightboxBody = document.getElementById('lightbox-body');
604
+ document.getElementById('lightbox-close').addEventListener('click', function () {
605
+ lightbox.classList.remove('open');
606
+ });
607
+ lightbox.addEventListener('click', function (e) {
608
+ if (e.target === lightbox) lightbox.classList.remove('open');
609
+ });
610
+
611
+ document.addEventListener('click', function (e) {
612
+ var btn = e.target.closest('.preview-btn');
613
+ if (!btn) return;
614
+ e.stopPropagation();
615
+ var type = btn.getAttribute('data-preview');
616
+ var path = btn.getAttribute('data-project');
617
+ var project = projects[path];
618
+ if (!project) return;
619
+ var content;
620
+ if (type === 'story') content = project.renderedStory;
621
+ else if (type === 'log') content = project.renderedLog;
622
+ else if (type === 'ticket') content = project.renderedTicket;
623
+ var titleMap = { story: 'Story', log: 'Log', ticket: 'Ticket' };
624
+ lightboxTitle.textContent = (titleMap[type] || type) + ' \u2014 ' + project.projectName;
625
+ lightboxBody.innerHTML = content || '<p class="empty-content">No content available yet.</p>';
626
+ lightbox.classList.add('open');
627
+ });
628
+ })();
@@ -1,4 +1,4 @@
1
- /** Parse a ticket ID string like "LB-123" or "LB-123-slug" into prefix and number. */
1
+ /** Parse a ticket ID string like "LB-123", "#45", or "#45-slug" into prefix and number. */
2
2
  export declare function parseTicketId(input: string): {
3
3
  prefix: string;
4
4
  number: number;
package/dist/utils/id.js CHANGED
@@ -1,7 +1,10 @@
1
1
  import fs from 'fs-extra';
2
2
  import { join } from 'node:path';
3
- /** Parse a ticket ID string like "LB-123" or "LB-123-slug" into prefix and number. */
3
+ /** Parse a ticket ID string like "LB-123", "#45", or "#45-slug" into prefix and number. */
4
4
  export function parseTicketId(input) {
5
+ if (/^#\d+(?:-.*)?$/.test(input)) {
6
+ return { prefix: '#', number: parseInt(input.replace(/^#/, ''), 10) };
7
+ }
5
8
  const match = input.match(/^([A-Za-z]+)-(\d+)(?:-.*)?$/);
6
9
  if (match) {
7
10
  return { prefix: match[1].toUpperCase(), number: parseInt(match[2], 10) };
@@ -1 +1 @@
1
- {"version":3,"file":"id.js","sourceRoot":"","sources":["../../src/utils/id.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,UAAU,CAAC;AAC1B,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,sFAAsF;AACtF,MAAM,UAAU,aAAa,CAAC,KAAa;IACzC,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,6BAA6B,CAAC,CAAC;IACzD,IAAI,KAAK,EAAE,CAAC;QACV,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,EAAE,MAAM,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC;IAC5E,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,uFAAuF;AACvF,MAAM,UAAU,SAAS,CAAC,UAAkB,EAAE,MAAc;IAC1D,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC;QAAE,OAAO,GAAG,MAAM,CAAC,WAAW,EAAE,IAAI,CAAC;IAEnE,MAAM,OAAO,GAAG,EAAE,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE;QACtD,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,CAAC;YAC9C,OAAO,IAAI,CAAC,WAAW,EAAE,CAAC;QAC5B,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,MAAM,CAAC,IAAI,MAAM,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC,CAAC;QAC3E,IAAI,KAAK,EAAE,CAAC;YACV,MAAM,GAAG,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YACnC,IAAI,GAAG,GAAG,SAAS,EAAE,CAAC;gBACpB,SAAS,GAAG,GAAG,CAAC;YAClB,CAAC;QACH,CAAC;IACH,CAAC;IAED,MAAM,UAAU,GAAG,SAAS,GAAG,CAAC,CAAC;IACjC,OAAO,GAAG,MAAM,CAAC,WAAW,EAAE,IAAI,UAAU,EAAE,CAAC;AACjD,CAAC"}
1
+ {"version":3,"file":"id.js","sourceRoot":"","sources":["../../src/utils/id.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,UAAU,CAAC;AAC1B,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,2FAA2F;AAC3F,MAAM,UAAU,aAAa,CAAC,KAAa;IACzC,IAAI,gBAAgB,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACjC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC;IACxE,CAAC;IACD,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,6BAA6B,CAAC,CAAC;IACzD,IAAI,KAAK,EAAE,CAAC;QACV,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,EAAE,MAAM,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC;IAC5E,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,uFAAuF;AACvF,MAAM,UAAU,SAAS,CAAC,UAAkB,EAAE,MAAc;IAC1D,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC;QAAE,OAAO,GAAG,MAAM,CAAC,WAAW,EAAE,IAAI,CAAC;IAEnE,MAAM,OAAO,GAAG,EAAE,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE;QACtD,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,CAAC;YAC9C,OAAO,IAAI,CAAC,WAAW,EAAE,CAAC;QAC5B,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,MAAM,CAAC,IAAI,MAAM,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC,CAAC;QAC3E,IAAI,KAAK,EAAE,CAAC;YACV,MAAM,GAAG,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YACnC,IAAI,GAAG,GAAG,SAAS,EAAE,CAAC;gBACpB,SAAS,GAAG,GAAG,CAAC;YAClB,CAAC;QACH,CAAC;IACH,CAAC;IAED,MAAM,UAAU,GAAG,SAAS,GAAG,CAAC,CAAC;IACjC,OAAO,GAAG,MAAM,CAAC,WAAW,EAAE,IAAI,UAAU,EAAE,CAAC;AACjD,CAAC"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Detail metrics collector for logbook watch.
3
+ *
4
+ * Provides the expanded detail view data (LOC, staged/unstaged, commits).
5
+ * Uses shared git-utils helpers to avoid duplication with the git collector.
6
+ */
7
+ import type { ProjectDetails } from './types.js';
8
+ /**
9
+ * Collect detailed project metrics for the expandable detail view.
10
+ *
11
+ * @param cwd - Project root directory containing .project-logbook config.
12
+ * @returns The detailed metrics snapshot.
13
+ */
14
+ export declare function collectDetails(cwd: string): Promise<ProjectDetails>;