reviewflow 3.15.0 → 3.17.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 (132) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/dashboard/index.html +132 -0
  3. package/dist/dashboard/modules/i18n.d.ts.map +1 -1
  4. package/dist/dashboard/modules/i18n.js +30 -0
  5. package/dist/dashboard/modules/i18n.js.map +1 -1
  6. package/dist/dashboard/modules/worktreePanel.d.ts +111 -0
  7. package/dist/dashboard/modules/worktreePanel.d.ts.map +1 -0
  8. package/dist/dashboard/modules/worktreePanel.js +373 -0
  9. package/dist/dashboard/modules/worktreePanel.js.map +1 -0
  10. package/dist/dashboard/styles.css +312 -0
  11. package/dist/dashboard/vendor/anime.esm.min.js +7 -0
  12. package/dist/frameworks/claude/timers/claudeInvocationTimers.d.ts.map +1 -1
  13. package/dist/frameworks/claude/timers/claudeInvocationTimers.js +0 -1
  14. package/dist/frameworks/claude/timers/claudeInvocationTimers.js.map +1 -1
  15. package/dist/frameworks/scheduler/worktreeSweepScheduler.d.ts +20 -0
  16. package/dist/frameworks/scheduler/worktreeSweepScheduler.d.ts.map +1 -0
  17. package/dist/frameworks/scheduler/worktreeSweepScheduler.js +70 -0
  18. package/dist/frameworks/scheduler/worktreeSweepScheduler.js.map +1 -0
  19. package/dist/main/dependencies.d.ts +10 -0
  20. package/dist/main/dependencies.d.ts.map +1 -1
  21. package/dist/main/dependencies.js +15 -0
  22. package/dist/main/dependencies.js.map +1 -1
  23. package/dist/main/routes.d.ts.map +1 -1
  24. package/dist/main/routes.js +10 -7
  25. package/dist/main/routes.js.map +1 -1
  26. package/dist/main/server.d.ts.map +1 -1
  27. package/dist/main/server.js +16 -2
  28. package/dist/main/server.js.map +1 -1
  29. package/dist/modules/claude-invocation/interface-adapters/gateways/claudeSession.cli.gateway.d.ts.map +1 -1
  30. package/dist/modules/claude-invocation/interface-adapters/gateways/claudeSession.cli.gateway.js +17 -2
  31. package/dist/modules/claude-invocation/interface-adapters/gateways/claudeSession.cli.gateway.js.map +1 -1
  32. package/dist/modules/claude-invocation/usecases/auditBilling.usecase.d.ts +0 -2
  33. package/dist/modules/claude-invocation/usecases/auditBilling.usecase.d.ts.map +1 -1
  34. package/dist/modules/claude-invocation/usecases/auditBilling.usecase.js +6 -6
  35. package/dist/modules/claude-invocation/usecases/auditBilling.usecase.js.map +1 -1
  36. package/dist/modules/platform-integration/entities/github/githubPullRequestEvent.guard.d.ts +14 -0
  37. package/dist/modules/platform-integration/entities/github/githubPullRequestEvent.guard.d.ts.map +1 -1
  38. package/dist/modules/platform-integration/entities/github/githubPullRequestEvent.guard.js +13 -2
  39. package/dist/modules/platform-integration/entities/github/githubPullRequestEvent.guard.js.map +1 -1
  40. package/dist/modules/platform-integration/interface-adapters/controllers/webhook/github.controller.d.ts.map +1 -1
  41. package/dist/modules/platform-integration/interface-adapters/controllers/webhook/github.controller.js +14 -2
  42. package/dist/modules/platform-integration/interface-adapters/controllers/webhook/github.controller.js.map +1 -1
  43. package/dist/modules/platform-integration/interface-adapters/controllers/webhook/gitlab.controller.d.ts.map +1 -1
  44. package/dist/modules/platform-integration/interface-adapters/controllers/webhook/gitlab.controller.js +2 -3
  45. package/dist/modules/platform-integration/interface-adapters/controllers/webhook/gitlab.controller.js.map +1 -1
  46. package/dist/modules/review-execution/entities/progress/agentDefinition.type.d.ts.map +1 -1
  47. package/dist/modules/review-execution/entities/progress/agentDefinition.type.js +1 -0
  48. package/dist/modules/review-execution/entities/progress/agentDefinition.type.js.map +1 -1
  49. package/dist/modules/worktree-management/entities/sweep/lastSweepSummary.schema.d.ts +9 -0
  50. package/dist/modules/worktree-management/entities/sweep/lastSweepSummary.schema.d.ts.map +1 -0
  51. package/dist/modules/worktree-management/entities/sweep/lastSweepSummary.schema.js +8 -0
  52. package/dist/modules/worktree-management/entities/sweep/lastSweepSummary.schema.js.map +1 -0
  53. package/dist/modules/worktree-management/entities/sweep/runSweepResult.d.ts +12 -0
  54. package/dist/modules/worktree-management/entities/sweep/runSweepResult.d.ts.map +1 -0
  55. package/dist/modules/worktree-management/entities/sweep/runSweepResult.js +2 -0
  56. package/dist/modules/worktree-management/entities/sweep/runSweepResult.js.map +1 -0
  57. package/dist/modules/worktree-management/entities/worktree/worktreeSizeProbe.gateway.d.ts +4 -0
  58. package/dist/modules/worktree-management/entities/worktree/worktreeSizeProbe.gateway.d.ts.map +1 -0
  59. package/dist/modules/worktree-management/entities/worktree/worktreeSizeProbe.gateway.js +2 -0
  60. package/dist/modules/worktree-management/entities/worktree/worktreeSizeProbe.gateway.js.map +1 -0
  61. package/dist/modules/worktree-management/interface-adapters/controllers/http/worktreeOverview.routes.d.ts +19 -0
  62. package/dist/modules/worktree-management/interface-adapters/controllers/http/worktreeOverview.routes.d.ts.map +1 -0
  63. package/dist/modules/worktree-management/interface-adapters/controllers/http/worktreeOverview.routes.js +39 -0
  64. package/dist/modules/worktree-management/interface-adapters/controllers/http/worktreeOverview.routes.js.map +1 -0
  65. package/dist/modules/worktree-management/interface-adapters/gateways/worktreeSizeProbe.cli.gateway.d.ts +17 -0
  66. package/dist/modules/worktree-management/interface-adapters/gateways/worktreeSizeProbe.cli.gateway.d.ts.map +1 -0
  67. package/dist/modules/worktree-management/interface-adapters/gateways/worktreeSizeProbe.cli.gateway.js +46 -0
  68. package/dist/modules/worktree-management/interface-adapters/gateways/worktreeSizeProbe.cli.gateway.js.map +1 -0
  69. package/dist/modules/worktree-management/interface-adapters/presenters/worktreePanel.presenter.d.ts +53 -0
  70. package/dist/modules/worktree-management/interface-adapters/presenters/worktreePanel.presenter.d.ts.map +1 -0
  71. package/dist/modules/worktree-management/interface-adapters/presenters/worktreePanel.presenter.js +102 -0
  72. package/dist/modules/worktree-management/interface-adapters/presenters/worktreePanel.presenter.js.map +1 -0
  73. package/dist/modules/worktree-management/usecases/sweepStaleWorktrees.usecase.d.ts +1 -1
  74. package/dist/modules/worktree-management/usecases/sweepStaleWorktrees.usecase.d.ts.map +1 -1
  75. package/dist/modules/worktree-management/usecases/sweepStaleWorktrees.usecase.js +1 -1
  76. package/dist/modules/worktree-management/usecases/sweepStaleWorktrees.usecase.js.map +1 -1
  77. package/dist/tests/acceptance/169-migrate-claude-invocation-to-bg-mode.acceptance.test.js +5 -5
  78. package/dist/tests/acceptance/169-migrate-claude-invocation-to-bg-mode.acceptance.test.js.map +1 -1
  79. package/dist/tests/acceptance/170-prebuilt-worktree-lifecycle.acceptance.test.d.ts +5 -4
  80. package/dist/tests/acceptance/170-prebuilt-worktree-lifecycle.acceptance.test.d.ts.map +1 -1
  81. package/dist/tests/acceptance/170-prebuilt-worktree-lifecycle.acceptance.test.js +246 -11
  82. package/dist/tests/acceptance/170-prebuilt-worktree-lifecycle.acceptance.test.js.map +1 -1
  83. package/dist/tests/acceptance/173-dashboard-worktree-panel.acceptance.test.d.ts +10 -0
  84. package/dist/tests/acceptance/173-dashboard-worktree-panel.acceptance.test.d.ts.map +1 -0
  85. package/dist/tests/acceptance/173-dashboard-worktree-panel.acceptance.test.js +192 -0
  86. package/dist/tests/acceptance/173-dashboard-worktree-panel.acceptance.test.js.map +1 -0
  87. package/dist/tests/factories/lastSweepSummary.factory.d.ts +5 -0
  88. package/dist/tests/factories/lastSweepSummary.factory.d.ts.map +1 -0
  89. package/dist/tests/factories/lastSweepSummary.factory.js +12 -0
  90. package/dist/tests/factories/lastSweepSummary.factory.js.map +1 -0
  91. package/dist/tests/integration/claudeInvocation.integration.test.js +5 -1
  92. package/dist/tests/integration/claudeInvocation.integration.test.js.map +1 -1
  93. package/dist/tests/stubs/worktreeSizeProbe.stub.d.ts +11 -0
  94. package/dist/tests/stubs/worktreeSizeProbe.stub.d.ts.map +1 -0
  95. package/dist/tests/stubs/worktreeSizeProbe.stub.js +22 -0
  96. package/dist/tests/stubs/worktreeSizeProbe.stub.js.map +1 -0
  97. package/dist/tests/units/dashboard/modules/worktreePanel.test.d.ts +2 -0
  98. package/dist/tests/units/dashboard/modules/worktreePanel.test.d.ts.map +1 -0
  99. package/dist/tests/units/dashboard/modules/worktreePanel.test.js +274 -0
  100. package/dist/tests/units/dashboard/modules/worktreePanel.test.js.map +1 -0
  101. package/dist/tests/units/entities/github/githubPullRequestEvent.guard.test.js +26 -0
  102. package/dist/tests/units/entities/github/githubPullRequestEvent.guard.test.js.map +1 -1
  103. package/dist/tests/units/frameworks/claude/timers/claudeInvocationTimers.test.js +3 -3
  104. package/dist/tests/units/frameworks/claude/timers/claudeInvocationTimers.test.js.map +1 -1
  105. package/dist/tests/units/frameworks/scheduler/worktreeSweepScheduler.test.d.ts +2 -0
  106. package/dist/tests/units/frameworks/scheduler/worktreeSweepScheduler.test.d.ts.map +1 -0
  107. package/dist/tests/units/frameworks/scheduler/worktreeSweepScheduler.test.js +236 -0
  108. package/dist/tests/units/frameworks/scheduler/worktreeSweepScheduler.test.js.map +1 -0
  109. package/dist/tests/units/interface-adapters/controllers/webhook/github.controller.test.js +74 -2
  110. package/dist/tests/units/interface-adapters/controllers/webhook/github.controller.test.js.map +1 -1
  111. package/dist/tests/units/modules/claude-invocation/interface-adapters/gateways/claudeSession.cli.gateway.test.js +17 -5
  112. package/dist/tests/units/modules/claude-invocation/interface-adapters/gateways/claudeSession.cli.gateway.test.js.map +1 -1
  113. package/dist/tests/units/modules/claude-invocation/usecases/auditBilling.usecase.test.js +2 -18
  114. package/dist/tests/units/modules/claude-invocation/usecases/auditBilling.usecase.test.js.map +1 -1
  115. package/dist/tests/units/modules/worktree-management/entities/sweep/lastSweepSummary.schema.test.d.ts +2 -0
  116. package/dist/tests/units/modules/worktree-management/entities/sweep/lastSweepSummary.schema.test.d.ts.map +1 -0
  117. package/dist/tests/units/modules/worktree-management/entities/sweep/lastSweepSummary.schema.test.js +44 -0
  118. package/dist/tests/units/modules/worktree-management/entities/sweep/lastSweepSummary.schema.test.js.map +1 -0
  119. package/dist/tests/units/modules/worktree-management/interface-adapters/controllers/http/worktreeOverview.routes.test.d.ts +2 -0
  120. package/dist/tests/units/modules/worktree-management/interface-adapters/controllers/http/worktreeOverview.routes.test.d.ts.map +1 -0
  121. package/dist/tests/units/modules/worktree-management/interface-adapters/controllers/http/worktreeOverview.routes.test.js +160 -0
  122. package/dist/tests/units/modules/worktree-management/interface-adapters/controllers/http/worktreeOverview.routes.test.js.map +1 -0
  123. package/dist/tests/units/modules/worktree-management/interface-adapters/gateways/worktreeSizeProbe.cli.gateway.test.d.ts +2 -0
  124. package/dist/tests/units/modules/worktree-management/interface-adapters/gateways/worktreeSizeProbe.cli.gateway.test.d.ts.map +1 -0
  125. package/dist/tests/units/modules/worktree-management/interface-adapters/gateways/worktreeSizeProbe.cli.gateway.test.js +54 -0
  126. package/dist/tests/units/modules/worktree-management/interface-adapters/gateways/worktreeSizeProbe.cli.gateway.test.js.map +1 -0
  127. package/dist/tests/units/modules/worktree-management/interface-adapters/presenters/worktreePanel.presenter.test.d.ts +2 -0
  128. package/dist/tests/units/modules/worktree-management/interface-adapters/presenters/worktreePanel.presenter.test.d.ts.map +1 -0
  129. package/dist/tests/units/modules/worktree-management/interface-adapters/presenters/worktreePanel.presenter.test.js +259 -0
  130. package/dist/tests/units/modules/worktree-management/interface-adapters/presenters/worktreePanel.presenter.test.js.map +1 -0
  131. package/package.json +2 -1
  132. package/scripts/copyAssets.mjs +8 -0
@@ -0,0 +1,373 @@
1
+ /**
2
+ * Dashboard module — worktree pool panel (SPEC-173).
3
+ * Humble object: pure functions, no global state, no direct DOM access here.
4
+ * Animation choreography lives in the consumer (index.html) and styles.css.
5
+ *
6
+ * Visual DNA: "Agentic OS" — see project_agentic_os_design_dna.md.
7
+ */
8
+
9
+ /**
10
+ * @typedef {'active' | 'idle' | 'stale'} WorktreeRowStatus
11
+ */
12
+
13
+ /**
14
+ * @typedef {Object} WorktreeRowViewModel
15
+ * @property {number} mrNumber
16
+ * @property {string} path
17
+ * @property {string} mtime
18
+ * @property {number} ageSeconds
19
+ * @property {number | null} sizeBytes
20
+ * @property {WorktreeRowStatus} status
21
+ */
22
+
23
+ /**
24
+ * @typedef {Object} WorktreeGroupViewModel
25
+ * @property {'gitlab' | 'github'} platform
26
+ * @property {string} projectPath
27
+ * @property {WorktreeRowViewModel[]} worktrees
28
+ */
29
+
30
+ /**
31
+ * @typedef {Object} LastSweepViewModel
32
+ * @property {string} ranAt
33
+ * @property {number} removed
34
+ * @property {number} failures
35
+ * @property {number} scanned
36
+ */
37
+
38
+ /**
39
+ * @typedef {Object} WorktreePanelViewModel
40
+ * @property {number} totalCount
41
+ * @property {number} totalSizeBytes
42
+ * @property {number} activeCount
43
+ * @property {number} idleCount
44
+ * @property {number} staleCount
45
+ * @property {string} nextSweepAt
46
+ * @property {LastSweepViewModel | null} lastSweep
47
+ * @property {WorktreeGroupViewModel[]} groups
48
+ */
49
+
50
+ /**
51
+ * @typedef {Object} WorktreeTotals
52
+ * @property {number} total
53
+ * @property {number} active
54
+ * @property {number} idle
55
+ * @property {number} stale
56
+ */
57
+
58
+ /**
59
+ * @typedef {Object} NullableWorktreeTotals
60
+ * @property {number | null} total
61
+ * @property {number | null} active
62
+ * @property {number | null} idle
63
+ * @property {number | null} stale
64
+ */
65
+
66
+ /**
67
+ * @typedef {{ status: 'ok'; payload: { ranAt: string; removed: number; failures: number; scanned: number } }
68
+ * | { status: 'conflict'; startedAt: string }
69
+ * | { status: 'error'; reason?: string }} ManualSweepResult
70
+ */
71
+
72
+ /**
73
+ * @param {string | number | null | undefined} text
74
+ * @returns {string}
75
+ */
76
+ function escapeHtml(text) {
77
+ if (text === null || text === undefined) return '';
78
+ return String(text)
79
+ .replace(/&/g, '&')
80
+ .replace(/</g, '&lt;')
81
+ .replace(/>/g, '&gt;')
82
+ .replace(/"/g, '&quot;')
83
+ .replace(/'/g, '&#39;');
84
+ }
85
+
86
+ /**
87
+ * @param {number | null} bytes
88
+ * @returns {string}
89
+ */
90
+ export function formatBytes(bytes) {
91
+ if (bytes === null || bytes === undefined) return '—';
92
+ if (bytes < 1024) return `${bytes} B`;
93
+ const kb = bytes / 1024;
94
+ if (kb < 1024) return `${kb.toFixed(1)} KB`;
95
+ const mb = kb / 1024;
96
+ if (mb < 1024) return `${mb.toFixed(1)} MB`;
97
+ const gb = mb / 1024;
98
+ return `${gb.toFixed(2)} GB`;
99
+ }
100
+
101
+ /**
102
+ * @param {number} ageSeconds
103
+ * @returns {string}
104
+ */
105
+ export function formatRelativeAge(ageSeconds) {
106
+ if (ageSeconds < 60) return `${ageSeconds}s`;
107
+ const minutes = Math.floor(ageSeconds / 60);
108
+ if (minutes < 60) return `${minutes}m`;
109
+ const hours = Math.floor(minutes / 60);
110
+ if (hours < 24) return `${hours}h`;
111
+ const days = Math.floor(hours / 24);
112
+ return `${days}d`;
113
+ }
114
+
115
+ /**
116
+ * @param {string} mtime
117
+ * @returns {string}
118
+ */
119
+ function formatMtime(mtime) {
120
+ const date = new Date(mtime);
121
+ if (Number.isNaN(date.getTime())) return mtime;
122
+ const pad = (value) => String(value).padStart(2, '0');
123
+ return `${date.getUTCFullYear()}-${pad(date.getUTCMonth() + 1)}-${pad(date.getUTCDate())} ${pad(
124
+ date.getUTCHours(),
125
+ )}:${pad(date.getUTCMinutes())}`;
126
+ }
127
+
128
+ /**
129
+ * @param {string} path
130
+ * @returns {string}
131
+ */
132
+ function truncatePathMiddle(path) {
133
+ if (path.length <= 48) return path;
134
+ return `${path.slice(0, 24)}…${path.slice(-20)}`;
135
+ }
136
+
137
+ /**
138
+ * @param {WorktreeRowStatus} status
139
+ * @returns {string}
140
+ */
141
+ export function renderWorktreeStatusBadge(status) {
142
+ if (status === 'active') {
143
+ return '<span class="worktree-status worktree-status-active" data-status="active"><span class="worktree-status-glyph">●</span><span class="worktree-status-label">ACTIVE</span></span>';
144
+ }
145
+ if (status === 'idle') {
146
+ return '<span class="worktree-status worktree-status-idle" data-status="idle"><span class="worktree-status-glyph">○</span><span class="worktree-status-label">IDLE</span></span>';
147
+ }
148
+ return '<span class="worktree-status worktree-status-stale" data-status="stale"><span class="worktree-status-glyph">◆</span><span class="worktree-status-label">STALE</span></span>';
149
+ }
150
+
151
+ /**
152
+ * @returns {string}
153
+ */
154
+ export function renderWorktreeEmptyState() {
155
+ return `
156
+ <div class="worktree-empty">
157
+ <svg class="worktree-empty-illustration" viewBox="0 0 120 120" width="96" height="96" aria-hidden="true">
158
+ <g fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round">
159
+ <path d="M60 102 V62" />
160
+ <path d="M60 62 L36 42" />
161
+ <path d="M60 62 L84 42" />
162
+ <path d="M36 42 L26 26" />
163
+ <path d="M36 42 L48 26" />
164
+ <path d="M84 42 L72 26" />
165
+ <path d="M84 42 L94 26" />
166
+ <circle cx="26" cy="26" r="3" />
167
+ <circle cx="48" cy="26" r="3" />
168
+ <circle cx="72" cy="26" r="3" />
169
+ <circle cx="94" cy="26" r="3" class="worktree-empty-leaf" />
170
+ <rect x="50" y="100" width="20" height="6" rx="0" />
171
+ </g>
172
+ </svg>
173
+ <div class="worktree-empty-title">// POOL EMPTY</div>
174
+ <div class="worktree-empty-subtitle">No worktree on disk. The next scheduled review will materialize one.</div>
175
+ </div>
176
+ `;
177
+ }
178
+
179
+ /**
180
+ * @param {WorktreeRowViewModel} row
181
+ * @param {string} groupLabel Display label for the row's group (e.g. "gitlab · group/project").
182
+ * @returns {string}
183
+ */
184
+ function renderRow(row, groupLabel) {
185
+ const escapedPath = escapeHtml(row.path);
186
+ const truncatedPath = escapeHtml(truncatePathMiddle(row.path));
187
+ const truncatedLabel =
188
+ groupLabel.length > 28 ? `${groupLabel.slice(0, 24)}…` : groupLabel;
189
+ return `
190
+ <tr class="worktree-row" data-status="${escapeHtml(row.status)}">
191
+ <td class="worktree-cell worktree-cell-status">${renderWorktreeStatusBadge(row.status)}</td>
192
+ <td class="worktree-cell worktree-cell-identity">
193
+ <span class="worktree-project" title="${escapeHtml(groupLabel)}">${escapeHtml(truncatedLabel)}</span>
194
+ <span class="worktree-mr">#${escapeHtml(row.mrNumber)}</span>
195
+ </td>
196
+ <td class="worktree-cell worktree-cell-path"><span title="${escapedPath}">${truncatedPath}</span></td>
197
+ <td class="worktree-cell worktree-cell-age">${escapeHtml(formatRelativeAge(row.ageSeconds))}</td>
198
+ <td class="worktree-cell worktree-cell-size">${escapeHtml(formatBytes(row.sizeBytes))}</td>
199
+ <td class="worktree-cell worktree-cell-mtime">${escapeHtml(formatMtime(row.mtime))}</td>
200
+ </tr>
201
+ `;
202
+ }
203
+
204
+ /**
205
+ * @param {WorktreeGroupViewModel} group
206
+ * @returns {string}
207
+ */
208
+ function renderGroupRows(group) {
209
+ return group.worktrees.map((row) => renderRow(row, `${group.platform} · ${group.projectPath}`)).join('');
210
+ }
211
+
212
+ /**
213
+ * Flattens the view model status counts into a single record consumed by the
214
+ * animation layer (count-up + change-flash). The presenter is the single source
215
+ * of truth for these counts — this helper just renames the keys.
216
+ *
217
+ * @param {WorktreePanelViewModel} viewModel
218
+ * @returns {WorktreeTotals}
219
+ */
220
+ export function snapshotTotals(viewModel) {
221
+ return {
222
+ total: viewModel.totalCount,
223
+ active: viewModel.activeCount,
224
+ idle: viewModel.idleCount,
225
+ stale: viewModel.staleCount,
226
+ };
227
+ }
228
+
229
+ /**
230
+ * Returns the metric keys whose value changed between two snapshots. A key
231
+ * whose previous value is null (cold start) is ignored — only transitions
232
+ * between known values are surfaced.
233
+ *
234
+ * @param {NullableWorktreeTotals} previous
235
+ * @param {WorktreeTotals} next
236
+ * @returns {Array<'total' | 'active' | 'idle' | 'stale'>}
237
+ */
238
+ export function computeChangedMetricKeys(previous, next) {
239
+ const keys = /** @type {Array<'total' | 'active' | 'idle' | 'stale'>} */ ([
240
+ 'total',
241
+ 'active',
242
+ 'idle',
243
+ 'stale',
244
+ ]);
245
+ return keys.filter((key) => previous[key] !== null && previous[key] !== next[key]);
246
+ }
247
+
248
+ /**
249
+ * @param {LastSweepViewModel | null} lastSweep
250
+ * @returns {string}
251
+ */
252
+ function renderLastSweep(lastSweep) {
253
+ if (lastSweep === null) {
254
+ return '<span class="worktree-lastsweep-value">never</span>';
255
+ }
256
+ const ranAt = formatMtime(lastSweep.ranAt);
257
+ return `<span class="worktree-lastsweep-value">${escapeHtml(ranAt)} UTC · removed ${escapeHtml(lastSweep.removed)} · failures ${escapeHtml(lastSweep.failures)} · scanned ${escapeHtml(lastSweep.scanned)}</span>`;
258
+ }
259
+
260
+ /**
261
+ * @param {string} nextSweepAt
262
+ * @returns {string}
263
+ */
264
+ function renderNextSweep(nextSweepAt) {
265
+ const date = new Date(nextSweepAt);
266
+ if (Number.isNaN(date.getTime())) return escapeHtml(nextSweepAt);
267
+ const diffMs = date.getTime() - Date.now();
268
+ if (diffMs <= 0) return 'imminent';
269
+ const totalMinutes = Math.round(diffMs / 60000);
270
+ const hours = Math.floor(totalMinutes / 60);
271
+ const minutes = totalMinutes % 60;
272
+ if (hours <= 0) return `in ${minutes}m`;
273
+ const pad = (value) => String(value).padStart(2, '0');
274
+ return `in ${hours}h ${pad(minutes)}m`;
275
+ }
276
+
277
+ /**
278
+ * @param {WorktreePanelViewModel} viewModel
279
+ * @returns {string}
280
+ */
281
+ export function renderWorktreeSection(viewModel) {
282
+ const isEmpty = viewModel.totalCount === 0;
283
+
284
+ const body = isEmpty
285
+ ? renderWorktreeEmptyState()
286
+ : `
287
+ <div class="worktree-metrics">
288
+ <div class="worktree-metric"><div class="worktree-metric-label">TOTAL</div><div class="worktree-metric-value" data-metric="total">${escapeHtml(viewModel.totalCount)}</div></div>
289
+ <div class="worktree-metric"><div class="worktree-metric-label">ACTIVE</div><div class="worktree-metric-value" data-metric="active">${escapeHtml(viewModel.activeCount)}</div></div>
290
+ <div class="worktree-metric"><div class="worktree-metric-label">IDLE</div><div class="worktree-metric-value" data-metric="idle">${escapeHtml(viewModel.idleCount)}</div></div>
291
+ <div class="worktree-metric"><div class="worktree-metric-label">STALE</div><div class="worktree-metric-value" data-metric="stale">${escapeHtml(viewModel.staleCount)}</div></div>
292
+ <div class="worktree-metric"><div class="worktree-metric-label">TOTAL SIZE</div><div class="worktree-metric-value" data-metric="size">${escapeHtml(formatBytes(viewModel.totalSizeBytes))}</div></div>
293
+ </div>
294
+ <div class="worktree-table-wrapper">
295
+ <table class="worktree-table">
296
+ <thead>
297
+ <tr>
298
+ <th class="worktree-th">STATUS</th>
299
+ <th class="worktree-th">PLATFORM · MR</th>
300
+ <th class="worktree-th">PATH</th>
301
+ <th class="worktree-th">AGE</th>
302
+ <th class="worktree-th">SIZE</th>
303
+ <th class="worktree-th">MTIME</th>
304
+ </tr>
305
+ </thead>
306
+ <tbody>
307
+ ${viewModel.groups.map(renderGroupRows).join('')}
308
+ </tbody>
309
+ </table>
310
+ </div>
311
+ `;
312
+
313
+ return `
314
+ <div class="worktree-panel" data-empty="${isEmpty ? 'true' : 'false'}">
315
+ <div class="worktree-panel-header">
316
+ <span class="worktree-panel-title">// WORKTREE POOL · ${escapeHtml(viewModel.totalCount)}</span>
317
+ <button class="worktree-sweep-button" data-action="sweep" type="button">
318
+ <svg class="worktree-sweep-broom" viewBox="0 0 24 24" width="14" height="14" aria-hidden="true">
319
+ <g fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round">
320
+ <path d="M14 4 L20 10" />
321
+ <path d="M13 5 L4 14 L10 20 L19 11" />
322
+ <path d="M4 14 L2 18" />
323
+ <path d="M6 16 L4 20" />
324
+ <path d="M8 18 L6 22" />
325
+ </g>
326
+ </svg>
327
+ <span class="worktree-sweep-label">SWEEP NOW</span>
328
+ </button>
329
+ </div>
330
+ <div class="worktree-panel-body">${body}</div>
331
+ <div class="worktree-panel-footer">
332
+ <div class="worktree-footer-block">
333
+ <span class="worktree-footer-label">// LAST SWEEP</span>
334
+ ${renderLastSweep(viewModel.lastSweep)}
335
+ </div>
336
+ <div class="worktree-footer-block worktree-footer-next">
337
+ <span class="worktree-footer-label">// NEXT SWEEP</span>
338
+ <span class="worktree-nextsweep-value">${escapeHtml(renderNextSweep(viewModel.nextSweepAt))}</span>
339
+ </div>
340
+ </div>
341
+ </div>
342
+ `;
343
+ }
344
+
345
+ /**
346
+ * @param {typeof fetch} [fetchImpl]
347
+ * @returns {Promise<WorktreePanelViewModel>}
348
+ */
349
+ export async function fetchWorktreeOverview(fetchImpl = fetch) {
350
+ const response = await fetchImpl('/api/worktrees');
351
+ if (!response.ok) {
352
+ throw new Error(`Worktree overview request failed: ${response.status}`);
353
+ }
354
+ return response.json();
355
+ }
356
+
357
+ /**
358
+ * @param {typeof fetch} [fetchImpl]
359
+ * @returns {Promise<ManualSweepResult>}
360
+ */
361
+ export async function triggerManualSweep(fetchImpl = fetch) {
362
+ const response = await fetchImpl('/api/worktrees/sweep', { method: 'POST' });
363
+ if (response.ok) {
364
+ const payload = await response.json();
365
+ return { status: 'ok', payload };
366
+ }
367
+ if (response.status === 409) {
368
+ const body = await response.json();
369
+ return { status: 'conflict', startedAt: body.startedAt };
370
+ }
371
+ const body = await response.json().catch(() => ({}));
372
+ return { status: 'error', reason: body.error };
373
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worktreePanel.js","sourceRoot":"","sources":["../../../src/dashboard/modules/worktreePanel.js"],"names":[],"mappings":"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"}
@@ -4107,3 +4107,315 @@ input:focus-visible,
4107
4107
  .budget-slider-status { font-size: 0.8rem; min-width: 7rem; }
4108
4108
  .budget-slider-status--ok { color: #16a34a; }
4109
4109
  .budget-slider-status--error { color: #dc2626; }
4110
+
4111
+ /* ============================================================
4112
+ SPEC-173 — Worktree Pool ("Agentic OS" visual DNA)
4113
+ Scoped: all rules target #worktree-section so the theme stays
4114
+ contained — no leak into the surrounding dashboard.
4115
+ Reference: project_agentic_os_design_dna.md.
4116
+ ============================================================ */
4117
+
4118
+ #worktree-section {
4119
+ --worktree-bg: #0a0908;
4120
+ --worktree-bg-elevated: #13110f;
4121
+ --worktree-border-faint: rgba(255, 180, 100, 0.12);
4122
+ --worktree-border-active: rgba(255, 138, 61, 0.65);
4123
+ --worktree-accent: #ff8a3d;
4124
+ --worktree-accent-dim: #a85a25;
4125
+ --worktree-success: #5ce28b;
4126
+ --worktree-warn: #f3c969;
4127
+ --worktree-text-primary: #f3eee8;
4128
+ --worktree-text-muted: #7a716a;
4129
+ --worktree-glow-active: 0 0 12px rgba(255, 138, 61, 0.55);
4130
+ --worktree-mono: ui-monospace, "SF Mono", "JetBrains Mono", "Berkeley Mono", monospace;
4131
+
4132
+ display: block;
4133
+ margin: 24px 0;
4134
+ font-family: var(--worktree-mono);
4135
+ color: var(--worktree-text-primary);
4136
+ }
4137
+
4138
+ #worktree-section .worktree-panel {
4139
+ position: relative;
4140
+ background: var(--worktree-bg);
4141
+ border: 1px solid var(--worktree-border-faint);
4142
+ padding: 18px 20px 16px;
4143
+ animation: worktree-reveal 250ms ease-out;
4144
+ }
4145
+
4146
+ #worktree-section .worktree-panel::before,
4147
+ #worktree-section .worktree-panel::after {
4148
+ content: "";
4149
+ position: absolute;
4150
+ width: 12px;
4151
+ height: 12px;
4152
+ pointer-events: none;
4153
+ }
4154
+ #worktree-section .worktree-panel::before {
4155
+ top: -1px;
4156
+ left: -1px;
4157
+ border-top: 2px solid var(--worktree-border-active);
4158
+ border-left: 2px solid var(--worktree-border-active);
4159
+ }
4160
+ #worktree-section .worktree-panel::after {
4161
+ bottom: -1px;
4162
+ right: -1px;
4163
+ border-bottom: 2px solid var(--worktree-border-active);
4164
+ border-right: 2px solid var(--worktree-border-active);
4165
+ }
4166
+
4167
+ @keyframes worktree-reveal {
4168
+ from { opacity: 0; transform: translateY(4px); }
4169
+ to { opacity: 1; transform: translateY(0); }
4170
+ }
4171
+
4172
+ #worktree-section .worktree-panel-header {
4173
+ display: flex;
4174
+ align-items: center;
4175
+ justify-content: space-between;
4176
+ margin-bottom: 14px;
4177
+ }
4178
+
4179
+ #worktree-section .worktree-panel-title {
4180
+ font-family: var(--worktree-mono);
4181
+ font-size: 13px;
4182
+ letter-spacing: 0.04em;
4183
+ color: var(--worktree-accent);
4184
+ text-transform: none;
4185
+ }
4186
+
4187
+ #worktree-section .worktree-sweep-button {
4188
+ display: inline-flex;
4189
+ align-items: center;
4190
+ gap: 8px;
4191
+ background: transparent;
4192
+ color: var(--worktree-accent);
4193
+ border: 1px solid var(--worktree-border-active);
4194
+ padding: 6px 12px;
4195
+ font-family: var(--worktree-mono);
4196
+ font-size: 11px;
4197
+ letter-spacing: 0.1em;
4198
+ cursor: pointer;
4199
+ border-radius: 0;
4200
+ transition: background-color 120ms ease-out, color 120ms ease-out;
4201
+ }
4202
+ #worktree-section .worktree-sweep-button:hover {
4203
+ background: rgba(255, 138, 61, 0.08);
4204
+ }
4205
+ #worktree-section .worktree-sweep-button:active {
4206
+ background: rgba(255, 138, 61, 0.18);
4207
+ }
4208
+ #worktree-section .worktree-sweep-pending {
4209
+ border-color: var(--worktree-warn);
4210
+ color: var(--worktree-warn);
4211
+ }
4212
+ #worktree-section .worktree-sweep-conflict {
4213
+ border-color: var(--worktree-warn);
4214
+ color: var(--worktree-warn);
4215
+ }
4216
+ #worktree-section .worktree-sweep-error {
4217
+ border-color: #ff5252;
4218
+ color: #ff5252;
4219
+ }
4220
+ #worktree-section .worktree-sweep-broom {
4221
+ display: inline-block;
4222
+ }
4223
+
4224
+ #worktree-section .worktree-metrics {
4225
+ display: grid;
4226
+ grid-template-columns: repeat(5, minmax(0, 1fr));
4227
+ gap: 16px;
4228
+ padding: 12px 0 14px;
4229
+ border-bottom: 1px dashed var(--worktree-border-faint);
4230
+ }
4231
+ #worktree-section .worktree-metric {
4232
+ display: flex;
4233
+ flex-direction: column;
4234
+ gap: 6px;
4235
+ }
4236
+ #worktree-section .worktree-metric-label {
4237
+ font-size: 10px;
4238
+ letter-spacing: 0.1em;
4239
+ color: var(--worktree-text-muted);
4240
+ text-transform: uppercase;
4241
+ }
4242
+ #worktree-section .worktree-metric-value {
4243
+ font-family: var(--worktree-mono);
4244
+ font-size: 26px;
4245
+ color: var(--worktree-text-primary);
4246
+ font-weight: 500;
4247
+ padding: 2px 4px;
4248
+ margin: -2px -4px;
4249
+ }
4250
+
4251
+ #worktree-section .worktree-table-wrapper {
4252
+ overflow-x: auto;
4253
+ margin-top: 10px;
4254
+ }
4255
+ #worktree-section .worktree-table {
4256
+ width: 100%;
4257
+ border-collapse: collapse;
4258
+ font-family: var(--worktree-mono);
4259
+ font-size: 12px;
4260
+ background: var(--worktree-bg);
4261
+ }
4262
+ #worktree-section .worktree-th {
4263
+ text-align: left;
4264
+ font-size: 10px;
4265
+ letter-spacing: 0.1em;
4266
+ color: var(--worktree-text-muted);
4267
+ text-transform: uppercase;
4268
+ padding: 8px 10px;
4269
+ border-bottom: 1px solid var(--worktree-border-faint);
4270
+ font-weight: normal;
4271
+ }
4272
+ #worktree-section .worktree-row {
4273
+ position: relative;
4274
+ background: var(--worktree-bg-elevated);
4275
+ transition: background-color 120ms ease-out;
4276
+ }
4277
+ #worktree-section .worktree-row:hover {
4278
+ background: rgba(255, 138, 61, 0.04);
4279
+ }
4280
+ #worktree-section .worktree-row + .worktree-row > .worktree-cell {
4281
+ border-top: 1px solid var(--worktree-border-faint);
4282
+ }
4283
+ #worktree-section .worktree-cell {
4284
+ padding: 10px 10px;
4285
+ vertical-align: middle;
4286
+ color: var(--worktree-text-primary);
4287
+ }
4288
+ #worktree-section .worktree-cell-identity {
4289
+ display: flex;
4290
+ flex-direction: column;
4291
+ gap: 2px;
4292
+ }
4293
+ #worktree-section .worktree-project {
4294
+ font-family: var(--worktree-mono);
4295
+ font-size: 12px;
4296
+ color: var(--worktree-text-primary);
4297
+ }
4298
+ #worktree-section .worktree-mr {
4299
+ font-family: var(--worktree-mono);
4300
+ font-size: 11px;
4301
+ color: var(--worktree-text-muted);
4302
+ }
4303
+ #worktree-section .worktree-cell-path,
4304
+ #worktree-section .worktree-cell-age,
4305
+ #worktree-section .worktree-cell-size,
4306
+ #worktree-section .worktree-cell-mtime {
4307
+ font-family: var(--worktree-mono);
4308
+ font-size: 12px;
4309
+ color: var(--worktree-text-muted);
4310
+ }
4311
+ #worktree-section .worktree-cell-path > span {
4312
+ cursor: help;
4313
+ }
4314
+
4315
+ #worktree-section .worktree-status {
4316
+ display: inline-flex;
4317
+ align-items: center;
4318
+ gap: 6px;
4319
+ font-family: var(--worktree-mono);
4320
+ font-size: 11px;
4321
+ letter-spacing: 0.08em;
4322
+ }
4323
+ #worktree-section .worktree-status-glyph {
4324
+ font-size: 11px;
4325
+ line-height: 1;
4326
+ }
4327
+ #worktree-section .worktree-status-active {
4328
+ color: var(--worktree-accent);
4329
+ }
4330
+ #worktree-section .worktree-status-active .worktree-status-glyph {
4331
+ text-shadow: var(--worktree-glow-active);
4332
+ animation: worktree-pulse 1.4s ease-in-out infinite;
4333
+ }
4334
+ #worktree-section .worktree-status-idle {
4335
+ color: var(--worktree-accent-dim);
4336
+ opacity: 0.85;
4337
+ }
4338
+ #worktree-section .worktree-status-stale {
4339
+ color: var(--worktree-warn);
4340
+ }
4341
+ @keyframes worktree-pulse {
4342
+ 0%, 100% { opacity: 0.75; text-shadow: 0 0 8px rgba(255, 138, 61, 0.45); }
4343
+ 50% { opacity: 1; text-shadow: 0 0 14px rgba(255, 138, 61, 0.7); }
4344
+ }
4345
+
4346
+ #worktree-section .worktree-empty {
4347
+ display: flex;
4348
+ flex-direction: column;
4349
+ align-items: center;
4350
+ justify-content: center;
4351
+ padding: 32px 16px 24px;
4352
+ gap: 12px;
4353
+ color: var(--worktree-text-muted);
4354
+ }
4355
+ #worktree-section .worktree-empty-illustration {
4356
+ color: var(--worktree-accent-dim);
4357
+ }
4358
+ #worktree-section .worktree-empty-leaf {
4359
+ animation: worktree-leaf 2s ease-in-out infinite;
4360
+ transform-origin: center;
4361
+ }
4362
+ @keyframes worktree-leaf {
4363
+ 0%, 100% { opacity: 0.5; }
4364
+ 50% { opacity: 1; }
4365
+ }
4366
+ #worktree-section .worktree-empty-title {
4367
+ font-family: var(--worktree-mono);
4368
+ font-size: 12px;
4369
+ letter-spacing: 0.08em;
4370
+ color: var(--worktree-accent);
4371
+ }
4372
+ #worktree-section .worktree-empty-subtitle {
4373
+ font-family: var(--worktree-mono);
4374
+ font-size: 11px;
4375
+ color: var(--worktree-text-muted);
4376
+ max-width: 360px;
4377
+ text-align: center;
4378
+ }
4379
+
4380
+ #worktree-section .worktree-panel-footer {
4381
+ display: flex;
4382
+ justify-content: space-between;
4383
+ gap: 16px;
4384
+ margin-top: 14px;
4385
+ padding-top: 10px;
4386
+ border-top: 1px dashed var(--worktree-border-faint);
4387
+ font-family: var(--worktree-mono);
4388
+ font-size: 11px;
4389
+ }
4390
+ #worktree-section .worktree-footer-block {
4391
+ display: flex;
4392
+ flex-direction: column;
4393
+ gap: 4px;
4394
+ }
4395
+ #worktree-section .worktree-footer-label {
4396
+ font-size: 10px;
4397
+ letter-spacing: 0.1em;
4398
+ color: var(--worktree-text-muted);
4399
+ }
4400
+ #worktree-section .worktree-lastsweep-value,
4401
+ #worktree-section .worktree-nextsweep-value {
4402
+ color: var(--worktree-text-primary);
4403
+ }
4404
+ #worktree-section .worktree-footer-next {
4405
+ text-align: right;
4406
+ }
4407
+
4408
+ #worktree-section .worktree-panel-error {
4409
+ font-family: var(--worktree-mono);
4410
+ font-size: 12px;
4411
+ color: var(--worktree-warn);
4412
+ padding: 14px 16px;
4413
+ }
4414
+
4415
+ @media (prefers-reduced-motion: reduce) {
4416
+ #worktree-section .worktree-panel,
4417
+ #worktree-section .worktree-status-active .worktree-status-glyph,
4418
+ #worktree-section .worktree-empty-leaf {
4419
+ animation: none !important;
4420
+ }
4421
+ }