reviewflow 3.16.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 (92) hide show
  1. package/CHANGELOG.md +8 -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/scheduler/worktreeSweepScheduler.d.ts +8 -2
  13. package/dist/frameworks/scheduler/worktreeSweepScheduler.d.ts.map +1 -1
  14. package/dist/frameworks/scheduler/worktreeSweepScheduler.js +38 -3
  15. package/dist/frameworks/scheduler/worktreeSweepScheduler.js.map +1 -1
  16. package/dist/main/dependencies.d.ts +6 -0
  17. package/dist/main/dependencies.d.ts.map +1 -1
  18. package/dist/main/dependencies.js +9 -0
  19. package/dist/main/dependencies.js.map +1 -1
  20. package/dist/main/routes.d.ts.map +1 -1
  21. package/dist/main/routes.js +7 -0
  22. package/dist/main/routes.js.map +1 -1
  23. package/dist/main/server.d.ts.map +1 -1
  24. package/dist/main/server.js +7 -2
  25. package/dist/main/server.js.map +1 -1
  26. package/dist/modules/review-execution/entities/progress/agentDefinition.type.d.ts.map +1 -1
  27. package/dist/modules/review-execution/entities/progress/agentDefinition.type.js +1 -0
  28. package/dist/modules/review-execution/entities/progress/agentDefinition.type.js.map +1 -1
  29. package/dist/modules/worktree-management/entities/sweep/lastSweepSummary.schema.d.ts +9 -0
  30. package/dist/modules/worktree-management/entities/sweep/lastSweepSummary.schema.d.ts.map +1 -0
  31. package/dist/modules/worktree-management/entities/sweep/lastSweepSummary.schema.js +8 -0
  32. package/dist/modules/worktree-management/entities/sweep/lastSweepSummary.schema.js.map +1 -0
  33. package/dist/modules/worktree-management/entities/sweep/runSweepResult.d.ts +12 -0
  34. package/dist/modules/worktree-management/entities/sweep/runSweepResult.d.ts.map +1 -0
  35. package/dist/modules/worktree-management/entities/sweep/runSweepResult.js +2 -0
  36. package/dist/modules/worktree-management/entities/sweep/runSweepResult.js.map +1 -0
  37. package/dist/modules/worktree-management/entities/worktree/worktreeSizeProbe.gateway.d.ts +4 -0
  38. package/dist/modules/worktree-management/entities/worktree/worktreeSizeProbe.gateway.d.ts.map +1 -0
  39. package/dist/modules/worktree-management/entities/worktree/worktreeSizeProbe.gateway.js +2 -0
  40. package/dist/modules/worktree-management/entities/worktree/worktreeSizeProbe.gateway.js.map +1 -0
  41. package/dist/modules/worktree-management/interface-adapters/controllers/http/worktreeOverview.routes.d.ts +19 -0
  42. package/dist/modules/worktree-management/interface-adapters/controllers/http/worktreeOverview.routes.d.ts.map +1 -0
  43. package/dist/modules/worktree-management/interface-adapters/controllers/http/worktreeOverview.routes.js +39 -0
  44. package/dist/modules/worktree-management/interface-adapters/controllers/http/worktreeOverview.routes.js.map +1 -0
  45. package/dist/modules/worktree-management/interface-adapters/gateways/worktreeSizeProbe.cli.gateway.d.ts +17 -0
  46. package/dist/modules/worktree-management/interface-adapters/gateways/worktreeSizeProbe.cli.gateway.d.ts.map +1 -0
  47. package/dist/modules/worktree-management/interface-adapters/gateways/worktreeSizeProbe.cli.gateway.js +46 -0
  48. package/dist/modules/worktree-management/interface-adapters/gateways/worktreeSizeProbe.cli.gateway.js.map +1 -0
  49. package/dist/modules/worktree-management/interface-adapters/presenters/worktreePanel.presenter.d.ts +53 -0
  50. package/dist/modules/worktree-management/interface-adapters/presenters/worktreePanel.presenter.d.ts.map +1 -0
  51. package/dist/modules/worktree-management/interface-adapters/presenters/worktreePanel.presenter.js +102 -0
  52. package/dist/modules/worktree-management/interface-adapters/presenters/worktreePanel.presenter.js.map +1 -0
  53. package/dist/modules/worktree-management/usecases/sweepStaleWorktrees.usecase.d.ts +1 -1
  54. package/dist/modules/worktree-management/usecases/sweepStaleWorktrees.usecase.d.ts.map +1 -1
  55. package/dist/modules/worktree-management/usecases/sweepStaleWorktrees.usecase.js +1 -1
  56. package/dist/modules/worktree-management/usecases/sweepStaleWorktrees.usecase.js.map +1 -1
  57. package/dist/tests/acceptance/173-dashboard-worktree-panel.acceptance.test.d.ts +10 -0
  58. package/dist/tests/acceptance/173-dashboard-worktree-panel.acceptance.test.d.ts.map +1 -0
  59. package/dist/tests/acceptance/173-dashboard-worktree-panel.acceptance.test.js +192 -0
  60. package/dist/tests/acceptance/173-dashboard-worktree-panel.acceptance.test.js.map +1 -0
  61. package/dist/tests/factories/lastSweepSummary.factory.d.ts +5 -0
  62. package/dist/tests/factories/lastSweepSummary.factory.d.ts.map +1 -0
  63. package/dist/tests/factories/lastSweepSummary.factory.js +12 -0
  64. package/dist/tests/factories/lastSweepSummary.factory.js.map +1 -0
  65. package/dist/tests/stubs/worktreeSizeProbe.stub.d.ts +11 -0
  66. package/dist/tests/stubs/worktreeSizeProbe.stub.d.ts.map +1 -0
  67. package/dist/tests/stubs/worktreeSizeProbe.stub.js +22 -0
  68. package/dist/tests/stubs/worktreeSizeProbe.stub.js.map +1 -0
  69. package/dist/tests/units/dashboard/modules/worktreePanel.test.d.ts +2 -0
  70. package/dist/tests/units/dashboard/modules/worktreePanel.test.d.ts.map +1 -0
  71. package/dist/tests/units/dashboard/modules/worktreePanel.test.js +274 -0
  72. package/dist/tests/units/dashboard/modules/worktreePanel.test.js.map +1 -0
  73. package/dist/tests/units/frameworks/scheduler/worktreeSweepScheduler.test.js +117 -0
  74. package/dist/tests/units/frameworks/scheduler/worktreeSweepScheduler.test.js.map +1 -1
  75. package/dist/tests/units/modules/worktree-management/entities/sweep/lastSweepSummary.schema.test.d.ts +2 -0
  76. package/dist/tests/units/modules/worktree-management/entities/sweep/lastSweepSummary.schema.test.d.ts.map +1 -0
  77. package/dist/tests/units/modules/worktree-management/entities/sweep/lastSweepSummary.schema.test.js +44 -0
  78. package/dist/tests/units/modules/worktree-management/entities/sweep/lastSweepSummary.schema.test.js.map +1 -0
  79. package/dist/tests/units/modules/worktree-management/interface-adapters/controllers/http/worktreeOverview.routes.test.d.ts +2 -0
  80. package/dist/tests/units/modules/worktree-management/interface-adapters/controllers/http/worktreeOverview.routes.test.d.ts.map +1 -0
  81. package/dist/tests/units/modules/worktree-management/interface-adapters/controllers/http/worktreeOverview.routes.test.js +160 -0
  82. package/dist/tests/units/modules/worktree-management/interface-adapters/controllers/http/worktreeOverview.routes.test.js.map +1 -0
  83. package/dist/tests/units/modules/worktree-management/interface-adapters/gateways/worktreeSizeProbe.cli.gateway.test.d.ts +2 -0
  84. package/dist/tests/units/modules/worktree-management/interface-adapters/gateways/worktreeSizeProbe.cli.gateway.test.d.ts.map +1 -0
  85. package/dist/tests/units/modules/worktree-management/interface-adapters/gateways/worktreeSizeProbe.cli.gateway.test.js +54 -0
  86. package/dist/tests/units/modules/worktree-management/interface-adapters/gateways/worktreeSizeProbe.cli.gateway.test.js.map +1 -0
  87. package/dist/tests/units/modules/worktree-management/interface-adapters/presenters/worktreePanel.presenter.test.d.ts +2 -0
  88. package/dist/tests/units/modules/worktree-management/interface-adapters/presenters/worktreePanel.presenter.test.d.ts.map +1 -0
  89. package/dist/tests/units/modules/worktree-management/interface-adapters/presenters/worktreePanel.presenter.test.js +259 -0
  90. package/dist/tests/units/modules/worktree-management/interface-adapters/presenters/worktreePanel.presenter.test.js.map +1 -0
  91. package/package.json +2 -1
  92. 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":"AAAA;;;;;;GAMG;AAEH;;GAEG;AAEH;;;;;;;;GAQG;AAEH;;;;;GAKG;AAEH;;;;;;GAMG;AAEH;;;;;;;;;;GAUG;AAEH;;;;;;GAMG;AAEH;;;;;;GAMG;AAEH;;;;GAIG;AAEH;;;GAGG;AACH,SAAS,UAAU,CAAC,IAAI;IACtB,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,SAAS;QAAE,OAAO,EAAE,CAAC;IACnD,OAAO,MAAM,CAAC,IAAI,CAAC;SAChB,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC;SACvB,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;AAC5B,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,KAAK;IAC/B,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,GAAG,CAAC;IACtD,IAAI,KAAK,GAAG,IAAI;QAAE,OAAO,GAAG,KAAK,IAAI,CAAC;IACtC,MAAM,EAAE,GAAG,KAAK,GAAG,IAAI,CAAC;IACxB,IAAI,EAAE,GAAG,IAAI;QAAE,OAAO,GAAG,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC;IAC5C,MAAM,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;IACrB,IAAI,EAAE,GAAG,IAAI;QAAE,OAAO,GAAG,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC;IAC5C,MAAM,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;IACrB,OAAO,GAAG,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC;AAC/B,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,UAAU;IAC1C,IAAI,UAAU,GAAG,EAAE;QAAE,OAAO,GAAG,UAAU,GAAG,CAAC;IAC7C,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,GAAG,EAAE,CAAC,CAAC;IAC5C,IAAI,OAAO,GAAG,EAAE;QAAE,OAAO,GAAG,OAAO,GAAG,CAAC;IACvC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,EAAE,CAAC,CAAC;IACvC,IAAI,KAAK,GAAG,EAAE;QAAE,OAAO,GAAG,KAAK,GAAG,CAAC;IACnC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,EAAE,CAAC,CAAC;IACpC,OAAO,GAAG,IAAI,GAAG,CAAC;AACpB,CAAC;AAED;;;GAGG;AACH,SAAS,WAAW,CAAC,KAAK;IACxB,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC;IAC7B,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;QAAE,OAAO,KAAK,CAAC;IAC/C,MAAM,GAAG,GAAG,CAAC,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACtD,OAAO,GAAG,IAAI,CAAC,cAAc,EAAE,IAAI,GAAG,CAAC,IAAI,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,IAAI,GAAG,CAC7F,IAAI,CAAC,WAAW,EAAE,CACnB,IAAI,GAAG,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC,EAAE,CAAC;AACnC,CAAC;AAED;;;GAGG;AACH,SAAS,kBAAkB,CAAC,IAAI;IAC9B,IAAI,IAAI,CAAC,MAAM,IAAI,EAAE;QAAE,OAAO,IAAI,CAAC;IACnC,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;AACnD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,yBAAyB,CAAC,MAAM;IAC9C,IAAI,MAAM,KAAK,QAAQ,EAAE,CAAC;QACxB,OAAO,gLAAgL,CAAC;IAC1L,CAAC;IACD,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;QACtB,OAAO,0KAA0K,CAAC;IACpL,CAAC;IACD,OAAO,6KAA6K,CAAC;AACvL,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,wBAAwB;IACtC,OAAO;;;;;;;;;;;;;;;;;;;;;GAqBN,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,SAAS,SAAS,CAAC,GAAG,EAAE,UAAU;IAChC,MAAM,WAAW,GAAG,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACzC,MAAM,aAAa,GAAG,UAAU,CAAC,kBAAkB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;IAC/D,MAAM,cAAc,GAClB,UAAU,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,UAAU,CAAC;IACtE,OAAO;4CACmC,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC;uDACX,yBAAyB,CAAC,GAAG,CAAC,MAAM,CAAC;;gDAE5C,UAAU,CAAC,UAAU,CAAC,KAAK,UAAU,CAAC,cAAc,CAAC;qCAChE,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC;;kEAEK,WAAW,KAAK,aAAa;oDAC3C,UAAU,CAAC,iBAAiB,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;qDAC5C,UAAU,CAAC,WAAW,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;sDACrC,UAAU,CAAC,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;;GAErF,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,SAAS,eAAe,CAAC,KAAK;IAC5B,OAAO,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,QAAQ,MAAM,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AAC3G,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAAC,SAAS;IACtC,OAAO;QACL,KAAK,EAAE,SAAS,CAAC,UAAU;QAC3B,MAAM,EAAE,SAAS,CAAC,WAAW;QAC7B,IAAI,EAAE,SAAS,CAAC,SAAS;QACzB,KAAK,EAAE,SAAS,CAAC,UAAU;KAC5B,CAAC;AACJ,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,wBAAwB,CAAC,QAAQ,EAAE,IAAI;IACrD,MAAM,IAAI,GAAG,2DAA2D,CAAC,CAAC;QACxE,OAAO;QACP,QAAQ;QACR,MAAM;QACN,OAAO;KACR,CAAC,CAAC;IACH,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,IAAI,IAAI,QAAQ,CAAC,GAAG,CAAC,KAAK,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AACrF,CAAC;AAED;;;GAGG;AACH,SAAS,eAAe,CAAC,SAAS;IAChC,IAAI,SAAS,KAAK,IAAI,EAAE,CAAC;QACvB,OAAO,qDAAqD,CAAC;IAC/D,CAAC;IACD,MAAM,KAAK,GAAG,WAAW,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IAC3C,OAAO,0CAA0C,UAAU,CAAC,KAAK,CAAC,kBAAkB,UAAU,CAAC,SAAS,CAAC,OAAO,CAAC,eAAe,UAAU,CAAC,SAAS,CAAC,QAAQ,CAAC,cAAc,UAAU,CAAC,SAAS,CAAC,OAAO,CAAC,SAAS,CAAC;AACrN,CAAC;AAED;;;GAGG;AACH,SAAS,eAAe,CAAC,WAAW;IAClC,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,WAAW,CAAC,CAAC;IACnC,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;QAAE,OAAO,UAAU,CAAC,WAAW,CAAC,CAAC;IACjE,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC3C,IAAI,MAAM,IAAI,CAAC;QAAE,OAAO,UAAU,CAAC;IACnC,MAAM,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,KAAK,CAAC,CAAC;IAChD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,GAAG,EAAE,CAAC,CAAC;IAC5C,MAAM,OAAO,GAAG,YAAY,GAAG,EAAE,CAAC;IAClC,IAAI,KAAK,IAAI,CAAC;QAAE,OAAO,MAAM,OAAO,GAAG,CAAC;IACxC,MAAM,GAAG,GAAG,CAAC,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACtD,OAAO,MAAM,KAAK,KAAK,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC;AACzC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,qBAAqB,CAAC,SAAS;IAC7C,MAAM,OAAO,GAAG,SAAS,CAAC,UAAU,KAAK,CAAC,CAAC;IAE3C,MAAM,IAAI,GAAG,OAAO;QAClB,CAAC,CAAC,wBAAwB,EAAE;QAC5B,CAAC,CAAC;;4IAEsI,UAAU,CAAC,SAAS,CAAC,UAAU,CAAC;8IAC9B,UAAU,CAAC,SAAS,CAAC,WAAW,CAAC;0IACrC,UAAU,CAAC,SAAS,CAAC,SAAS,CAAC;4IAC7B,UAAU,CAAC,SAAS,CAAC,UAAU,CAAC;gJAC5B,UAAU,CAAC,WAAW,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC;;;;;;;;;;;;;;;cAenL,SAAS,CAAC,MAAM,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;;;;KAIvD,CAAC;IAEJ,OAAO;8CACqC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO;;gEAER,UAAU,CAAC,SAAS,CAAC,UAAU,CAAC;;;;;;;;;;;;;;yCAcvD,IAAI;;;;YAIjC,eAAe,CAAC,SAAS,CAAC,SAAS,CAAC;;;;mDAIG,UAAU,CAAC,eAAe,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;;;;GAIlG,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,SAAS,GAAG,KAAK;IAC3D,MAAM,QAAQ,GAAG,MAAM,SAAS,CAAC,gBAAgB,CAAC,CAAC;IACnD,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CAAC,qCAAqC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;IAC1E,CAAC;IACD,OAAO,QAAQ,CAAC,IAAI,EAAE,CAAC;AACzB,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,SAAS,GAAG,KAAK;IACxD,MAAM,QAAQ,GAAG,MAAM,SAAS,CAAC,sBAAsB,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAC7E,IAAI,QAAQ,CAAC,EAAE,EAAE,CAAC;QAChB,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;QACtC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;IACnC,CAAC;IACD,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;QAC5B,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;QACnC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC;IAC3D,CAAC;IACD,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACrD,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC;AACjD,CAAC"}
@@ -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
+ }