agkan 2.15.0 → 3.1.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 (118) hide show
  1. package/README.ja.md +62 -238
  2. package/README.md +48 -241
  3. package/dist/board/boardRenderer.d.ts +7 -5
  4. package/dist/board/boardRenderer.d.ts.map +1 -1
  5. package/dist/board/boardRenderer.js +43 -60
  6. package/dist/board/boardRenderer.js.map +1 -1
  7. package/dist/board/boardRoutes.d.ts +2 -0
  8. package/dist/board/boardRoutes.d.ts.map +1 -1
  9. package/dist/board/boardRoutes.js +224 -16
  10. package/dist/board/boardRoutes.js.map +1 -1
  11. package/dist/board/boardStyles.d.ts +1 -1
  12. package/dist/board/boardStyles.d.ts.map +1 -1
  13. package/dist/board/boardStyles.js +49 -3
  14. package/dist/board/boardStyles.js.map +1 -1
  15. package/dist/board/client/board.js +700 -24
  16. package/dist/board/server.d.ts +2 -1
  17. package/dist/board/server.d.ts.map +1 -1
  18. package/dist/board/server.js +4 -2
  19. package/dist/board/server.js.map +1 -1
  20. package/dist/cli/commands/tag/add.d.ts.map +1 -1
  21. package/dist/cli/commands/tag/add.js +10 -11
  22. package/dist/cli/commands/tag/add.js.map +1 -1
  23. package/dist/cli/commands/tag/attach.d.ts.map +1 -1
  24. package/dist/cli/commands/tag/attach.js +10 -11
  25. package/dist/cli/commands/tag/attach.js.map +1 -1
  26. package/dist/cli/commands/tag/rename.d.ts.map +1 -1
  27. package/dist/cli/commands/tag/rename.js +10 -11
  28. package/dist/cli/commands/tag/rename.js.map +1 -1
  29. package/dist/cli/commands/task/add.d.ts.map +1 -1
  30. package/dist/cli/commands/task/add.js +3 -3
  31. package/dist/cli/commands/task/add.js.map +1 -1
  32. package/dist/cli/commands/task/archive.d.ts +6 -0
  33. package/dist/cli/commands/task/archive.d.ts.map +1 -0
  34. package/dist/cli/commands/task/archive.js +120 -0
  35. package/dist/cli/commands/task/archive.js.map +1 -0
  36. package/dist/cli/commands/task/copy.js +2 -2
  37. package/dist/cli/commands/task/copy.js.map +1 -1
  38. package/dist/cli/commands/task/find.js +2 -2
  39. package/dist/cli/commands/task/find.js.map +1 -1
  40. package/dist/cli/commands/task/get.d.ts.map +1 -1
  41. package/dist/cli/commands/task/get.js +4 -0
  42. package/dist/cli/commands/task/get.js.map +1 -1
  43. package/dist/cli/commands/task/list.d.ts.map +1 -1
  44. package/dist/cli/commands/task/list.js +4 -2
  45. package/dist/cli/commands/task/list.js.map +1 -1
  46. package/dist/cli/commands/task/purge.d.ts.map +1 -1
  47. package/dist/cli/commands/task/purge.js +2 -9
  48. package/dist/cli/commands/task/purge.js.map +1 -1
  49. package/dist/cli/commands/task/unarchive.d.ts +6 -0
  50. package/dist/cli/commands/task/unarchive.d.ts.map +1 -0
  51. package/dist/cli/commands/task/unarchive.js +86 -0
  52. package/dist/cli/commands/task/unarchive.js.map +1 -0
  53. package/dist/cli/commands/task/update-parent.d.ts.map +1 -1
  54. package/dist/cli/commands/task/update-parent.js +10 -11
  55. package/dist/cli/commands/task/update-parent.js.map +1 -1
  56. package/dist/cli/index.js +8 -0
  57. package/dist/cli/index.js.map +1 -1
  58. package/dist/cli/utils/board-daemon.d.ts.map +1 -1
  59. package/dist/cli/utils/board-daemon.js +10 -4
  60. package/dist/cli/utils/board-daemon.js.map +1 -1
  61. package/dist/db/adapters/sqlite-storage-backend.d.ts +2 -1
  62. package/dist/db/adapters/sqlite-storage-backend.d.ts.map +1 -1
  63. package/dist/db/adapters/sqlite-storage-backend.js +82 -5
  64. package/dist/db/adapters/sqlite-storage-backend.js.map +1 -1
  65. package/dist/db/connection.d.ts +5 -0
  66. package/dist/db/connection.d.ts.map +1 -1
  67. package/dist/db/connection.js +9 -0
  68. package/dist/db/connection.js.map +1 -1
  69. package/dist/db/migrations/20260412000000_add_archive_status.d.ts +3 -0
  70. package/dist/db/migrations/20260412000000_add_archive_status.d.ts.map +1 -0
  71. package/dist/db/migrations/20260412000000_add_archive_status.js +10 -0
  72. package/dist/db/migrations/20260412000000_add_archive_status.js.map +1 -0
  73. package/dist/db/migrations/20260412000000_add_is_archived_to_tasks.d.ts +3 -0
  74. package/dist/db/migrations/20260412000000_add_is_archived_to_tasks.d.ts.map +1 -0
  75. package/dist/db/migrations/20260412000000_add_is_archived_to_tasks.js +10 -0
  76. package/dist/db/migrations/20260412000000_add_is_archived_to_tasks.js.map +1 -0
  77. package/dist/db/migrations/index.d.ts.map +1 -1
  78. package/dist/db/migrations/index.js +6 -0
  79. package/dist/db/migrations/index.js.map +1 -1
  80. package/dist/db/types/repository.d.ts +40 -0
  81. package/dist/db/types/repository.d.ts.map +1 -1
  82. package/dist/errors.d.ts +27 -0
  83. package/dist/errors.d.ts.map +1 -0
  84. package/dist/errors.js +51 -0
  85. package/dist/errors.js.map +1 -0
  86. package/dist/models/Task.d.ts +3 -0
  87. package/dist/models/Task.d.ts.map +1 -1
  88. package/dist/services/ClaudeProcessService.d.ts.map +1 -1
  89. package/dist/services/ClaudeProcessService.js +2 -1
  90. package/dist/services/ClaudeProcessService.js.map +1 -1
  91. package/dist/services/CommentService.d.ts.map +1 -1
  92. package/dist/services/CommentService.js +3 -2
  93. package/dist/services/CommentService.js.map +1 -1
  94. package/dist/services/ExportImportService.d.ts.map +1 -1
  95. package/dist/services/ExportImportService.js +17 -15
  96. package/dist/services/ExportImportService.js.map +1 -1
  97. package/dist/services/TagService.d.ts.map +1 -1
  98. package/dist/services/TagService.js +7 -6
  99. package/dist/services/TagService.js.map +1 -1
  100. package/dist/services/TaskBlockService.d.ts.map +1 -1
  101. package/dist/services/TaskBlockService.js +5 -4
  102. package/dist/services/TaskBlockService.js.map +1 -1
  103. package/dist/services/TaskService.d.ts +19 -1
  104. package/dist/services/TaskService.d.ts.map +1 -1
  105. package/dist/services/TaskService.js +68 -17
  106. package/dist/services/TaskService.js.map +1 -1
  107. package/dist/services/TaskTagService.d.ts.map +1 -1
  108. package/dist/services/TaskTagService.js +4 -3
  109. package/dist/services/TaskTagService.js.map +1 -1
  110. package/dist/services/index.d.ts +2 -0
  111. package/dist/services/index.d.ts.map +1 -1
  112. package/dist/services/index.js +3 -1
  113. package/dist/services/index.js.map +1 -1
  114. package/dist/utils/date.d.ts +10 -0
  115. package/dist/utils/date.d.ts.map +1 -0
  116. package/dist/utils/date.js +17 -0
  117. package/dist/utils/date.js.map +1 -0
  118. package/package.json +2 -2
@@ -30,6 +30,258 @@
30
30
  setTimeout(() => toast.classList.remove("show"), 3e3);
31
31
  }
32
32
 
33
+ // src/board/client/claudeButton.ts
34
+ var _claudeModalCallback = null;
35
+ var _runningTaskIds = /* @__PURE__ */ new Set();
36
+ var _planningTaskIds = /* @__PURE__ */ new Set();
37
+ var _inFlightTaskIds = /* @__PURE__ */ new Set();
38
+ function registerClaudeModalCallback(callback) {
39
+ _claudeModalCallback = callback;
40
+ }
41
+ function getRunningTaskIds() {
42
+ return _runningTaskIds;
43
+ }
44
+ function updateButtonStates(runningTaskIds, planningTaskIds = /* @__PURE__ */ new Set()) {
45
+ _runningTaskIds = runningTaskIds;
46
+ const onlyPlanningRunning = runningTaskIds.size > 0 && [...runningTaskIds].every((id) => planningTaskIds.has(id));
47
+ const anyRunning = runningTaskIds.size > 0 && !onlyPlanningRunning;
48
+ const indicator = document.getElementById("header-running-indicator");
49
+ if (indicator) {
50
+ indicator.style.display = runningTaskIds.size > 0 ? "" : "none";
51
+ }
52
+ document.querySelectorAll(".claude-run-split").forEach((split) => {
53
+ const taskId = Number(split.dataset.taskId);
54
+ if (runningTaskIds.has(taskId)) {
55
+ replaceWithDetailBtn(split, taskId);
56
+ } else {
57
+ split.querySelectorAll(".claude-run-btn, .claude-run-toggle, .claude-run-menu-item").forEach((btn) => {
58
+ btn.disabled = anyRunning;
59
+ });
60
+ }
61
+ });
62
+ document.querySelectorAll(".claude-plan-btn").forEach((btn) => {
63
+ const taskId = Number(btn.dataset.taskId);
64
+ if (runningTaskIds.has(taskId)) {
65
+ replaceWithDetailBtn(btn, taskId);
66
+ }
67
+ });
68
+ document.querySelectorAll(".claude-detail-btn").forEach((btn) => {
69
+ const taskId = Number(btn.dataset.taskId);
70
+ if (!runningTaskIds.has(taskId)) {
71
+ const card = btn.closest(".card");
72
+ const status = card?.dataset.status;
73
+ replaceWithRunOrPlanBtn(btn, taskId, status);
74
+ }
75
+ });
76
+ }
77
+ function replaceWithDetailBtn(btn, taskId) {
78
+ const detailBtn = document.createElement("button");
79
+ detailBtn.className = "claude-detail-btn";
80
+ detailBtn.dataset.taskId = String(taskId);
81
+ detailBtn.textContent = "\u25CF Details";
82
+ attachDetailBtnListener(detailBtn);
83
+ btn.replaceWith(detailBtn);
84
+ }
85
+ function replaceWithRunOrPlanBtn(btn, taskId, status) {
86
+ _planningTaskIds.delete(taskId);
87
+ if (["review", "done", "closed"].includes(status ?? "")) {
88
+ btn.remove();
89
+ return;
90
+ }
91
+ if (["ready", "in_progress"].includes(status ?? "")) {
92
+ const split = createRunSplitElement(taskId);
93
+ btn.replaceWith(split);
94
+ } else {
95
+ const newBtn = document.createElement("button");
96
+ newBtn.className = "claude-plan-btn";
97
+ newBtn.dataset.taskId = String(taskId);
98
+ newBtn.innerHTML = "📋 Planning";
99
+ attachPlanBtnListener(newBtn);
100
+ btn.replaceWith(newBtn);
101
+ }
102
+ }
103
+ function createRunSplitElement(taskId) {
104
+ const split = document.createElement("div");
105
+ split.className = "claude-run-split";
106
+ split.dataset.taskId = String(taskId);
107
+ const mainBtn = document.createElement("button");
108
+ mainBtn.className = "claude-run-btn";
109
+ mainBtn.dataset.taskId = String(taskId);
110
+ mainBtn.innerHTML = "▶ Run";
111
+ const toggleBtn = document.createElement("button");
112
+ toggleBtn.className = "claude-run-toggle";
113
+ toggleBtn.dataset.taskId = String(taskId);
114
+ toggleBtn.title = "More options";
115
+ toggleBtn.innerHTML = "▼";
116
+ const menu = document.createElement("div");
117
+ menu.className = "claude-run-menu";
118
+ const directItem = document.createElement("button");
119
+ directItem.className = "claude-run-menu-item";
120
+ directItem.dataset.taskId = String(taskId);
121
+ directItem.dataset.command = "direct";
122
+ directItem.innerHTML = "▶ Run (current branch)";
123
+ const prItem = document.createElement("button");
124
+ prItem.className = "claude-run-menu-item";
125
+ prItem.dataset.taskId = String(taskId);
126
+ prItem.dataset.command = "pr";
127
+ prItem.innerHTML = "▶ Run (create PR)";
128
+ menu.appendChild(directItem);
129
+ menu.appendChild(prItem);
130
+ split.appendChild(mainBtn);
131
+ split.appendChild(toggleBtn);
132
+ split.appendChild(menu);
133
+ attachRunSplitListeners(split, mainBtn, toggleBtn, directItem, prItem, taskId);
134
+ return split;
135
+ }
136
+ function attachRunSplitListeners(split, mainBtn, toggleBtn, directItem, prItem, taskId) {
137
+ split.dataset.listenersAttached = "true";
138
+ mainBtn.addEventListener("click", async (e) => {
139
+ e.stopPropagation();
140
+ await triggerRunTask(taskId, split, {});
141
+ });
142
+ toggleBtn.addEventListener("click", (e) => {
143
+ e.stopPropagation();
144
+ split.classList.toggle("open");
145
+ });
146
+ directItem.addEventListener("click", async (e) => {
147
+ e.stopPropagation();
148
+ split.classList.remove("open");
149
+ await triggerRunTask(taskId, split, {});
150
+ });
151
+ prItem.addEventListener("click", async (e) => {
152
+ e.stopPropagation();
153
+ split.classList.remove("open");
154
+ await triggerRunTask(taskId, split, { command: "pr" });
155
+ });
156
+ }
157
+ function attachPlanBtnListener(btn) {
158
+ btn.dataset.listenersAttached = "true";
159
+ btn.addEventListener("click", async (e) => {
160
+ e.stopPropagation();
161
+ const taskId = Number(btn.dataset.taskId);
162
+ await triggerRunTask(taskId, btn, { command: "planning" });
163
+ });
164
+ }
165
+ function attachDetailBtnListener(btn) {
166
+ btn.dataset.listenersAttached = "true";
167
+ btn.addEventListener("click", (e) => {
168
+ e.stopPropagation();
169
+ const taskId = Number(btn.dataset.taskId);
170
+ if (_claudeModalCallback) {
171
+ _claudeModalCallback(taskId);
172
+ }
173
+ });
174
+ }
175
+ async function triggerRunTask(taskId, btn, body) {
176
+ if (_inFlightTaskIds.has(taskId)) return;
177
+ _inFlightTaskIds.add(taskId);
178
+ btn.disabled = true;
179
+ btn.querySelectorAll("button").forEach((b) => b.disabled = true);
180
+ try {
181
+ const res = await fetch(`/api/claude/tasks/${taskId}/run`, {
182
+ method: "POST",
183
+ headers: { "Content-Type": "application/json" },
184
+ body: JSON.stringify(body)
185
+ });
186
+ if (res.ok) {
187
+ _runningTaskIds = new Set(_runningTaskIds).add(taskId);
188
+ if (body.command === "planning") {
189
+ _planningTaskIds = new Set(_planningTaskIds).add(taskId);
190
+ }
191
+ replaceWithDetailBtn(btn, taskId);
192
+ } else {
193
+ btn.disabled = false;
194
+ btn.querySelectorAll("button").forEach((b) => b.disabled = false);
195
+ let errorDetail = `HTTP ${res.status}`;
196
+ try {
197
+ const data = await res.json();
198
+ if (data.error) errorDetail += `: ${data.error}`;
199
+ } catch {
200
+ }
201
+ console.error(`[claude] Failed to start task ${taskId}: ${errorDetail}`);
202
+ if (res.status === 409) {
203
+ alert(`\u3053\u306E\u30BF\u30B9\u30AF\u306F\u3059\u3067\u306B\u5B9F\u884C\u4E2D\u3067\u3059 (task ${taskId})`);
204
+ } else {
205
+ alert(`Claude\u8D77\u52D5\u30A8\u30E9\u30FC (task ${taskId}): ${errorDetail}`);
206
+ }
207
+ }
208
+ } catch (err) {
209
+ btn.disabled = false;
210
+ btn.querySelectorAll("button").forEach((b) => b.disabled = false);
211
+ console.error(`[claude] Network error starting task ${taskId}:`, err);
212
+ } finally {
213
+ _inFlightTaskIds.delete(taskId);
214
+ }
215
+ }
216
+ async function pollRunningTasks() {
217
+ try {
218
+ const res = await fetch("/api/running-tasks");
219
+ if (!res.ok) return;
220
+ const data = await res.json();
221
+ const allIds = new Set(data.tasks.map((t) => t.taskId));
222
+ const planningIds = new Set(data.tasks.filter((t) => t.command === "planning").map((t) => t.taskId));
223
+ updateButtonStates(allIds, planningIds);
224
+ } catch {
225
+ }
226
+ }
227
+ function updateCardButton(card, newStatus) {
228
+ const taskId = Number(card.dataset.id);
229
+ if (_runningTaskIds.has(taskId)) return;
230
+ const existingEl = card.querySelector(".claude-run-split, .claude-plan-btn, .claude-detail-btn");
231
+ if (!existingEl) return;
232
+ if (["review", "done", "closed"].includes(newStatus)) {
233
+ existingEl.remove();
234
+ } else if (["ready", "in_progress"].includes(newStatus)) {
235
+ if (!existingEl.classList.contains("claude-run-split")) {
236
+ const split = createRunSplitElement(taskId);
237
+ existingEl.replaceWith(split);
238
+ }
239
+ } else {
240
+ if (!existingEl.classList.contains("claude-plan-btn")) {
241
+ const newBtn = document.createElement("button");
242
+ newBtn.className = "claude-plan-btn";
243
+ newBtn.dataset.taskId = String(taskId);
244
+ newBtn.innerHTML = "📋 Planning";
245
+ attachPlanBtnListener(newBtn);
246
+ existingEl.replaceWith(newBtn);
247
+ }
248
+ }
249
+ }
250
+ function attachClaudeButtonListeners(body) {
251
+ body.querySelectorAll(".claude-run-split").forEach((split) => {
252
+ if (split.dataset.listenersAttached) return;
253
+ const taskId = Number(split.dataset.taskId);
254
+ const mainBtn = split.querySelector(".claude-run-btn");
255
+ const toggleBtn = split.querySelector(".claude-run-toggle");
256
+ const directItem = split.querySelector('[data-command="direct"]');
257
+ const prItem = split.querySelector('[data-command="pr"]');
258
+ if (mainBtn && toggleBtn && directItem && prItem) {
259
+ attachRunSplitListeners(split, mainBtn, toggleBtn, directItem, prItem, taskId);
260
+ }
261
+ });
262
+ body.querySelectorAll(".claude-plan-btn").forEach((btn) => {
263
+ if (btn.dataset.listenersAttached) return;
264
+ attachPlanBtnListener(btn);
265
+ });
266
+ body.querySelectorAll(".claude-detail-btn").forEach((btn) => {
267
+ if (btn.dataset.listenersAttached) return;
268
+ attachDetailBtnListener(btn);
269
+ });
270
+ updateButtonStates(_runningTaskIds, _planningTaskIds);
271
+ }
272
+ function initClaudeButton() {
273
+ document.querySelectorAll(".column-body").forEach((body) => {
274
+ attachClaudeButtonListeners(body);
275
+ });
276
+ document.addEventListener("click", () => {
277
+ document.querySelectorAll(".claude-run-split.open").forEach((el) => {
278
+ el.classList.remove("open");
279
+ });
280
+ });
281
+ setInterval(pollRunningTasks, 2500);
282
+ pollRunningTasks();
283
+ }
284
+
33
285
  // src/board/client/dragDrop.ts
34
286
  var _redrawDependencies = null;
35
287
  function registerDependencyRedrawCallback(callback) {
@@ -37,6 +289,7 @@
37
289
  }
38
290
  var draggedCard = null;
39
291
  var sourceBody = null;
292
+ var isPendingStatusUpdate = false;
40
293
  var _dragMouseX = 0;
41
294
  var _dragMouseY = 0;
42
295
  var _dragOffsetX = 0;
@@ -70,6 +323,8 @@
70
323
  draggedCard.dataset.status = newStatus;
71
324
  updateCount(oldStatus);
72
325
  updateCount(newStatus);
326
+ updateCardButton(draggedCard, newStatus);
327
+ isPendingStatusUpdate = true;
73
328
  try {
74
329
  const res = await fetch("/api/tasks/" + taskId, {
75
330
  method: "PATCH",
@@ -88,6 +343,8 @@
88
343
  updateCount(newStatus);
89
344
  }
90
345
  showToast();
346
+ } finally {
347
+ isPendingStatusUpdate = false;
91
348
  }
92
349
  }
93
350
  var _documentDragOverListener = null;
@@ -423,6 +680,7 @@
423
680
  var _renderDetailPanel = null;
424
681
  var _showUpdateWarning = null;
425
682
  var _getDetailTaskId2 = null;
683
+ var _getDetailActiveTab = null;
426
684
  var _setActiveCard = null;
427
685
  var _redrawDependencies2 = null;
428
686
  function registerDetailPanelCallbacks(callbacks) {
@@ -430,6 +688,7 @@
430
688
  _renderDetailPanel = callbacks.renderDetailPanel;
431
689
  _showUpdateWarning = callbacks.showUpdateWarning;
432
690
  _getDetailTaskId2 = callbacks.getDetailTaskId;
691
+ _getDetailActiveTab = callbacks.getDetailActiveTab;
433
692
  _setActiveCard = callbacks.setActiveCard;
434
693
  }
435
694
  function registerDependencyRedrawCallback2(callback) {
@@ -466,11 +725,16 @@
466
725
  newCards.forEach((newCard, index) => {
467
726
  const id = newCard.dataset.id;
468
727
  const newUpdatedAt = newCard.dataset.updatedAt;
728
+ const newBlockedBy = newCard.dataset.blockedBy ?? "";
729
+ const newBlocking = newCard.dataset.blocking ?? "";
469
730
  const existing = id ? existingCards.get(id) : void 0;
470
731
  if (existing) {
471
732
  const existingUpdatedAt = existing.dataset.updatedAt;
733
+ const existingBlockedBy = existing.dataset.blockedBy ?? "";
734
+ const existingBlocking = existing.dataset.blocking ?? "";
735
+ const depsChanged = newBlockedBy !== existingBlockedBy || newBlocking !== existingBlocking;
472
736
  let activeCard;
473
- if (newUpdatedAt !== existingUpdatedAt) {
737
+ if (newUpdatedAt !== existingUpdatedAt || depsChanged) {
474
738
  existing.replaceWith(newCard);
475
739
  activeCard = newCard;
476
740
  } else {
@@ -497,6 +761,8 @@
497
761
  }
498
762
  attachCardListeners(body);
499
763
  attachAutoScrollToBody(body);
764
+ attachClaudeButtonListeners(body);
765
+ updateButtonStates(getRunningTaskIds());
500
766
  }
501
767
  function isEditingDetailPanel() {
502
768
  const editableFields = ["detail-edit-title", "detail-edit-body", "detail-edit-status", "detail-edit-priority"];
@@ -507,6 +773,9 @@
507
773
  if (_showUpdateWarning) _showUpdateWarning();
508
774
  return;
509
775
  }
776
+ if (_getDetailActiveTab && _getDetailActiveTab() === "run-logs") {
777
+ return;
778
+ }
510
779
  try {
511
780
  const taskRes = await fetch("/api/tasks/" + detailTaskId2);
512
781
  if (taskRes.ok) {
@@ -538,7 +807,7 @@
538
807
  }
539
808
  }
540
809
  async function pollBoardUpdates() {
541
- if (draggedCard !== null) return;
810
+ if (draggedCard !== null || isPendingStatusUpdate) return;
542
811
  try {
543
812
  const res = await fetch("/api/board/updated-at");
544
813
  if (!res.ok) return;
@@ -911,8 +1180,8 @@
911
1180
  });
912
1181
  if (!res.ok) throw new Error("Server error");
913
1182
  }
914
- async function fetchTaskDetail(taskId) {
915
- const res = await fetch("/api/tasks/" + taskId);
1183
+ async function fetchTaskDetail(taskId, signal) {
1184
+ const res = await fetch("/api/tasks/" + taskId, signal ? { signal } : void 0);
916
1185
  if (!res.ok) throw new Error("Server error");
917
1186
  return res.json();
918
1187
  }
@@ -958,6 +1227,12 @@
958
1227
  }).catch(function() {
959
1228
  });
960
1229
  }
1230
+ async function fetchRunLogs(taskId) {
1231
+ const res = await fetch("/api/claude/tasks/" + taskId + "/run-logs");
1232
+ if (!res.ok) throw new Error("Server error");
1233
+ const data = await res.json();
1234
+ return data.logs || [];
1235
+ }
961
1236
 
962
1237
  // src/board/client/detailPanelHtml.ts
963
1238
  function renderCommentItemHtml(comment, taskId) {
@@ -1067,10 +1342,7 @@
1067
1342
  const blockedBy = data.blockedBy || [];
1068
1343
  const blocking = data.blocking || [];
1069
1344
  const parent = data.parent || null;
1070
- const win = window;
1071
- const allStatuses = win.allStatuses;
1072
- const statusLabels = win.statusLabels;
1073
- const allPriorities = win.allPriorities;
1345
+ const { allStatuses, statusLabels, allPriorities } = window;
1074
1346
  let html = "";
1075
1347
  html += renderStatusField(task.status, allStatuses, statusLabels);
1076
1348
  html += renderPriorityField(task.priority, allPriorities);
@@ -1084,8 +1356,36 @@
1084
1356
  html += renderEditableTextFields(task);
1085
1357
  return html;
1086
1358
  }
1359
+ function renderRunLogsHtml(logs) {
1360
+ if (logs.length === 0) {
1361
+ return '<div class="run-log-empty">No run logs yet.</div>';
1362
+ }
1363
+ let html = '<div class="run-log-list">';
1364
+ logs.forEach((log, index) => {
1365
+ const date = log.started_at ? log.started_at.replace("T", " ").replace(/\.\d+Z$/, "") : "";
1366
+ const exitOk = log.exit_code === 0;
1367
+ const exitLabel = log.exit_code !== null ? "exit: " + String(log.exit_code) : "running";
1368
+ const exitClass = exitOk ? "success" : "failure";
1369
+ const isFirst = index === 0;
1370
+ html += '<div class="run-log-item' + (isFirst ? " open" : "") + '" data-log-id="' + log.id + '"><div class="run-log-header" data-action="toggle-run-log"><span class="run-log-toggle">&#9654;</span><span class="run-log-date">' + escapeHtmlClient(date) + '</span><span class="run-log-exit ' + exitClass + '">' + escapeHtmlClient(exitLabel) + '</span></div><div class="run-log-body">';
1371
+ log.events.forEach((evt) => {
1372
+ if (evt.kind === "text" && evt.text) {
1373
+ html += escapeHtmlClient(evt.text);
1374
+ } else if (evt.kind === "tool_use" && evt.name) {
1375
+ const mainArg = evt.input && typeof evt.input === "object" ? String(
1376
+ evt.input.path ?? evt.input.command ?? ""
1377
+ ) : "";
1378
+ const display = mainArg ? "\u{1F527} " + evt.name + ": " + mainArg : "\u{1F527} " + evt.name;
1379
+ html += '<span class="run-log-tool-use">' + escapeHtmlClient(display) + "\n</span>";
1380
+ }
1381
+ });
1382
+ html += "</div></div>";
1383
+ });
1384
+ html += "</div>";
1385
+ return html;
1386
+ }
1087
1387
  function buildDetailPanelHtml() {
1088
- return '<div class="detail-panel" id="detail-panel"><div class="detail-panel-resize-handle" id="detail-panel-resize-handle"></div><div class="detail-panel-header"><h2 id="detail-panel-title">Task Detail</h2><button class="detail-panel-copy-id" id="detail-panel-copy-id" title="Copy task ID">&#x2398;</button><button class="detail-panel-close" id="detail-panel-close" title="Close">&times;</button></div><div class="detail-tabs" id="detail-tabs"><button class="detail-tab active" data-tab="details">Details</button><button class="detail-tab" data-tab="comments" id="detail-tab-comments">Comments</button></div><div class="detail-panel-body" id="detail-panel-body"><div class="detail-tab-content active" id="detail-tab-content-details"></div><div class="detail-tab-content" id="detail-tab-content-comments"></div></div><div class="detail-panel-footer" id="detail-panel-footer"><button id="detail-save-btn">Save</button></div></div>';
1388
+ return '<div class="detail-panel" id="detail-panel"><div class="detail-panel-resize-handle" id="detail-panel-resize-handle"></div><div class="detail-panel-header"><h2 id="detail-panel-title">Task Detail</h2><button class="detail-panel-copy-id" id="detail-panel-copy-id" title="Copy task ID">&#x2398;</button><button class="detail-panel-close" id="detail-panel-close" title="Close">&times;</button></div><div class="detail-tabs" id="detail-tabs"><button class="detail-tab active" data-tab="details">Details</button><button class="detail-tab" data-tab="comments" id="detail-tab-comments">Comments</button><button class="detail-tab" data-tab="run-logs" id="detail-tab-run-logs">Run Logs</button></div><div class="detail-panel-body" id="detail-panel-body"><div class="detail-tab-content active" id="detail-tab-content-details"></div><div class="detail-tab-content" id="detail-tab-content-comments"></div><div class="detail-tab-content" id="detail-tab-content-run-logs"></div></div><div class="detail-panel-footer" id="detail-panel-footer"><button id="detail-save-btn">Save</button></div></div>';
1089
1389
  }
1090
1390
  function autoResizeTextarea(el) {
1091
1391
  const scrollContainer = el.closest(".detail-tab-content");
@@ -1098,9 +1398,22 @@
1098
1398
  // src/board/client/detailPanel.ts
1099
1399
  var detailTaskId = null;
1100
1400
  var lastTab = "details";
1401
+ var runLogPollingInterval = null;
1402
+ var currentFetchController = null;
1403
+ var runLogLoadSeq = 0;
1404
+ var runLogsLoadedTaskId = null;
1405
+ function stopRunLogPolling() {
1406
+ if (runLogPollingInterval !== null) {
1407
+ clearInterval(runLogPollingInterval);
1408
+ runLogPollingInterval = null;
1409
+ }
1410
+ }
1101
1411
  function getDetailTaskId() {
1102
1412
  return detailTaskId;
1103
1413
  }
1414
+ function getDetailActiveTab() {
1415
+ return lastTab;
1416
+ }
1104
1417
  function setActiveCard(taskId) {
1105
1418
  document.querySelectorAll(".card.active").forEach((card) => {
1106
1419
  card.classList.remove("active");
@@ -1111,6 +1424,13 @@
1111
1424
  }
1112
1425
  }
1113
1426
  function closeDetailPanel() {
1427
+ stopRunLogPolling();
1428
+ runLogsLoadedTaskId = null;
1429
+ const runLogsPane = document.getElementById("detail-tab-content-run-logs");
1430
+ if (runLogsPane) {
1431
+ delete runLogsPane.dataset.runLogsTaskId;
1432
+ delete runLogsPane.dataset.runLogsSignature;
1433
+ }
1114
1434
  const detailPanel = document.getElementById("detail-panel");
1115
1435
  detailPanel.classList.remove("open");
1116
1436
  detailPanel.style.width = "";
@@ -1118,26 +1438,41 @@
1118
1438
  detailTaskId = null;
1119
1439
  }
1120
1440
  function switchTab(tabName) {
1441
+ const activeTabBtn = document.querySelector(".detail-tab.active");
1442
+ const currentTab = activeTabBtn?.dataset.tab ?? null;
1443
+ const isSameTab = currentTab === tabName;
1444
+ if (tabName !== "run-logs") {
1445
+ stopRunLogPolling();
1446
+ }
1121
1447
  lastTab = tabName;
1122
- document.querySelectorAll(".detail-tab").forEach((btn) => {
1123
- btn.classList.toggle("active", btn.dataset.tab === tabName);
1124
- });
1125
- document.querySelectorAll(".detail-tab-content").forEach((el) => {
1126
- el.classList.toggle("active", el.id === "detail-tab-content-" + tabName);
1127
- });
1448
+ if (!isSameTab) {
1449
+ document.querySelectorAll(".detail-tab").forEach((btn) => {
1450
+ btn.classList.toggle("active", btn.dataset.tab === tabName);
1451
+ });
1452
+ document.querySelectorAll(".detail-tab-content").forEach((el) => {
1453
+ el.classList.toggle("active", el.id === "detail-tab-content-" + tabName);
1454
+ });
1455
+ }
1128
1456
  const footer = document.getElementById("detail-panel-footer");
1129
1457
  if (footer) footer.style.display = tabName === "details" ? "" : "none";
1458
+ if (tabName === "run-logs" && detailTaskId !== null && (!isSameTab || runLogsLoadedTaskId !== detailTaskId)) {
1459
+ loadRunLogs(detailTaskId).catch((err) => {
1460
+ console.error("[agkan] switchTab loadRunLogs failed", err);
1461
+ });
1462
+ }
1130
1463
  }
1131
1464
  async function loadComments(taskId) {
1132
- const tabBtn = document.getElementById("detail-tab-comments");
1133
1465
  const pane = document.getElementById("detail-tab-content-comments");
1134
1466
  if (!pane) return;
1135
1467
  try {
1136
1468
  const comments = await fetchComments(taskId);
1469
+ if (detailTaskId !== taskId) return;
1470
+ const tabBtn = document.getElementById("detail-tab-comments");
1137
1471
  if (tabBtn) tabBtn.textContent = "Comments (" + comments.length + ")";
1138
1472
  renderComments(taskId, comments);
1139
1473
  } catch (err) {
1140
1474
  console.error("[agkan] loadComments failed for task", taskId, err);
1475
+ if (detailTaskId !== taskId) return;
1141
1476
  if (pane) pane.innerHTML = '<div style="padding:20px;font-size:12px;color:#94a3b8;">Failed to load comments</div>';
1142
1477
  }
1143
1478
  }
@@ -1262,6 +1597,99 @@
1262
1597
  const taskId = target.dataset.taskId ? Number(target.dataset.taskId) : NaN;
1263
1598
  dispatchCommentAction(action, commentId, taskId);
1264
1599
  }
1600
+ function buildRunLogsSignature(logs) {
1601
+ return JSON.stringify(logs);
1602
+ }
1603
+ function renderRunLogsInPane(pane, logs) {
1604
+ const nextSignature = buildRunLogsSignature(logs);
1605
+ if (pane.dataset.runLogsSignature === nextSignature) return;
1606
+ pane.dataset.runLogsSignature = nextSignature;
1607
+ const bodyScrollState = /* @__PURE__ */ new Map();
1608
+ const openLogIds = /* @__PURE__ */ new Set();
1609
+ const hadPreviousItems = pane.querySelector(".run-log-item") !== null;
1610
+ const paneScrollTop = pane.scrollTop;
1611
+ const paneIsNearBottom = pane.scrollHeight - pane.scrollTop - pane.clientHeight <= 50;
1612
+ pane.querySelectorAll(".run-log-item.open").forEach((item) => {
1613
+ const logId = Number(item.dataset.logId);
1614
+ if (!logId) return;
1615
+ openLogIds.add(logId);
1616
+ const body = item.querySelector(".run-log-body");
1617
+ if (body) {
1618
+ const isNearBottom = body.scrollHeight - body.scrollTop - body.clientHeight <= 50;
1619
+ bodyScrollState.set(logId, { scrollTop: body.scrollTop, isNearBottom });
1620
+ }
1621
+ });
1622
+ pane.innerHTML = renderRunLogsHtml(logs);
1623
+ requestAnimationFrame(() => {
1624
+ if (hadPreviousItems) {
1625
+ pane.scrollTop = paneIsNearBottom ? pane.scrollHeight : paneScrollTop;
1626
+ }
1627
+ pane.querySelectorAll(".run-log-item").forEach((item) => {
1628
+ const logId = Number(item.dataset.logId);
1629
+ if (!logId) return;
1630
+ const body = item.querySelector(".run-log-body");
1631
+ if (!body) return;
1632
+ if (hadPreviousItems) {
1633
+ if (openLogIds.has(logId)) {
1634
+ item.classList.add("open");
1635
+ const state = bodyScrollState.get(logId);
1636
+ if (state) {
1637
+ body.scrollTop = state.isNearBottom ? body.scrollHeight : state.scrollTop;
1638
+ } else {
1639
+ body.scrollTop = body.scrollHeight;
1640
+ }
1641
+ } else {
1642
+ item.classList.remove("open");
1643
+ }
1644
+ } else {
1645
+ if (item.classList.contains("open")) {
1646
+ body.scrollTop = body.scrollHeight;
1647
+ }
1648
+ }
1649
+ });
1650
+ });
1651
+ }
1652
+ async function loadRunLogs(taskId) {
1653
+ stopRunLogPolling();
1654
+ const seq = ++runLogLoadSeq;
1655
+ const pane = document.getElementById("detail-tab-content-run-logs");
1656
+ if (!pane) return;
1657
+ runLogsLoadedTaskId = taskId;
1658
+ if (pane.dataset.runLogsTaskId !== String(taskId)) {
1659
+ pane.dataset.runLogsTaskId = String(taskId);
1660
+ delete pane.dataset.runLogsSignature;
1661
+ }
1662
+ pane.removeEventListener("click", handleRunLogToggle);
1663
+ pane.addEventListener("click", handleRunLogToggle);
1664
+ try {
1665
+ const logs = await fetchRunLogs(taskId);
1666
+ if (seq !== runLogLoadSeq) return;
1667
+ renderRunLogsInPane(pane, logs);
1668
+ runLogPollingInterval = setInterval(() => {
1669
+ if (detailTaskId !== taskId) {
1670
+ stopRunLogPolling();
1671
+ return;
1672
+ }
1673
+ fetchRunLogs(taskId).then((updated) => {
1674
+ if (seq !== runLogLoadSeq) return;
1675
+ const p = document.getElementById("detail-tab-content-run-logs");
1676
+ if (p) renderRunLogsInPane(p, updated);
1677
+ }).catch(() => stopRunLogPolling());
1678
+ }, 2e3);
1679
+ } catch (err) {
1680
+ console.error("[agkan] loadRunLogs failed for task", taskId, err);
1681
+ if (seq !== runLogLoadSeq) return;
1682
+ runLogsLoadedTaskId = null;
1683
+ delete pane.dataset.runLogsSignature;
1684
+ pane.innerHTML = '<div style="padding:20px;font-size:12px;color:#94a3b8;">Failed to load run logs</div>';
1685
+ }
1686
+ }
1687
+ function handleRunLogToggle(e) {
1688
+ const target = e.target.closest('[data-action="toggle-run-log"]');
1689
+ if (!target) return;
1690
+ const item = target.closest(".run-log-item");
1691
+ if (item) item.classList.toggle("open");
1692
+ }
1265
1693
  function renderDetailPanel(data) {
1266
1694
  document.getElementById("detail-panel-update-warning")?.remove();
1267
1695
  const detailPanelTitle = document.getElementById("detail-panel-title");
@@ -1288,9 +1716,36 @@
1288
1716
  }
1289
1717
  const textarea = document.getElementById("detail-edit-body");
1290
1718
  if (textarea) {
1291
- requestAnimationFrame(() => {
1292
- autoResizeTextarea(textarea);
1293
- });
1719
+ const detailPanel = document.getElementById("detail-panel");
1720
+ if (detailPanel && !detailPanel.classList.contains("open")) {
1721
+ const prefersReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
1722
+ if (prefersReducedMotion) {
1723
+ autoResizeTextarea(textarea);
1724
+ } else {
1725
+ let done = false;
1726
+ const finish = () => {
1727
+ if (done) return;
1728
+ done = true;
1729
+ detailPanel.removeEventListener("transitionend", onTransitionEnd);
1730
+ requestAnimationFrame(() => {
1731
+ requestAnimationFrame(() => {
1732
+ autoResizeTextarea(textarea);
1733
+ });
1734
+ });
1735
+ };
1736
+ const onTransitionEnd = (e) => {
1737
+ if (e.propertyName === "width") finish();
1738
+ };
1739
+ detailPanel.addEventListener("transitionend", onTransitionEnd);
1740
+ setTimeout(finish, 260);
1741
+ }
1742
+ } else {
1743
+ requestAnimationFrame(() => {
1744
+ requestAnimationFrame(() => {
1745
+ autoResizeTextarea(textarea);
1746
+ });
1747
+ });
1748
+ }
1294
1749
  textarea.addEventListener("input", () => {
1295
1750
  autoResizeTextarea(textarea);
1296
1751
  });
@@ -1305,16 +1760,23 @@
1305
1760
  async function openTaskDetail(taskId) {
1306
1761
  const detailPanel = document.getElementById("detail-panel");
1307
1762
  const PANEL_DEFAULT_WIDTH2 = 400;
1763
+ setActiveCard(Number(taskId));
1764
+ if (currentFetchController) {
1765
+ currentFetchController.abort();
1766
+ }
1767
+ currentFetchController = new AbortController();
1768
+ const { signal } = currentFetchController;
1308
1769
  try {
1309
- const data = await fetchTaskDetail(taskId);
1770
+ const data = await fetchTaskDetail(taskId, signal);
1771
+ currentFetchController = null;
1310
1772
  renderDetailPanel(data);
1311
- setActiveCard(Number(taskId));
1312
1773
  if (!detailPanel.classList.contains("open")) {
1313
1774
  const preferredWidth = detailPanel.dataset.preferredWidth || String(PANEL_DEFAULT_WIDTH2);
1314
1775
  detailPanel.style.width = preferredWidth + "px";
1315
1776
  detailPanel.classList.add("open");
1316
1777
  }
1317
1778
  } catch (err) {
1779
+ if (err instanceof DOMException && err.name === "AbortError") return;
1318
1780
  console.error("[agkan] openTaskDetail failed for task", taskId, err);
1319
1781
  showToast("Failed to load task details");
1320
1782
  }
@@ -1339,7 +1801,7 @@
1339
1801
  const reloadBtn = document.createElement("button");
1340
1802
  reloadBtn.title = "Reload latest data";
1341
1803
  reloadBtn.textContent = "\u21BA";
1342
- reloadBtn.style.cssText = "background: none; border: none; cursor: pointer; font-size: 1.1em; color: red; padding: 0 2px; line-height: 1; flex-shrink: 0;";
1804
+ reloadBtn.style.cssText = "background: none; border: none; cursor: pointer; font-size: 1.5em; color: red; padding: 0 4px; line-height: 1; flex-shrink: 0;";
1343
1805
  reloadBtn.addEventListener("click", async () => {
1344
1806
  try {
1345
1807
  if (detailTaskId !== null) {
@@ -1454,6 +1916,7 @@
1454
1916
  renderDetailPanel,
1455
1917
  showUpdateWarning,
1456
1918
  getDetailTaskId,
1919
+ getDetailActiveTab,
1457
1920
  setActiveCard
1458
1921
  });
1459
1922
  registerGetDetailTaskId(getDetailTaskId);
@@ -1686,7 +2149,7 @@
1686
2149
  }
1687
2150
  });
1688
2151
  }
1689
- async function executePurge() {
2152
+ async function executePurge(purgeResultEl) {
1690
2153
  try {
1691
2154
  const res = await fetch("/api/tasks/purge", {
1692
2155
  method: "POST",
@@ -1695,9 +2158,35 @@
1695
2158
  });
1696
2159
  if (res.ok) {
1697
2160
  await refreshBoardCards();
2161
+ } else {
2162
+ purgeResultEl.textContent = "Failed to purge tasks. Please try again.";
2163
+ purgeResultEl.style.color = "#dc2626";
2164
+ }
2165
+ } catch {
2166
+ purgeResultEl.textContent = "Failed to purge tasks. Network error.";
2167
+ purgeResultEl.style.color = "#dc2626";
2168
+ }
2169
+ }
2170
+ async function executeArchive(archiveResultEl) {
2171
+ try {
2172
+ const res = await fetch("/api/tasks/archive", {
2173
+ method: "POST",
2174
+ headers: { "Content-Type": "application/json" },
2175
+ body: JSON.stringify({})
2176
+ });
2177
+ if (res.ok) {
2178
+ const data = await res.json();
2179
+ await refreshBoardCards();
2180
+ return data;
2181
+ } else {
2182
+ archiveResultEl.textContent = "Failed to archive tasks. Please try again.";
2183
+ archiveResultEl.style.color = "#dc2626";
1698
2184
  }
1699
2185
  } catch {
2186
+ archiveResultEl.textContent = "Failed to archive tasks. Network error.";
2187
+ archiveResultEl.style.color = "#dc2626";
1700
2188
  }
2189
+ return null;
1701
2190
  }
1702
2191
  function initPurgeModal(burgerDropdown) {
1703
2192
  const purgeModal = document.getElementById("purge-confirm-modal");
@@ -1714,7 +2203,32 @@
1714
2203
  });
1715
2204
  purgeConfirmBtn.addEventListener("click", () => {
1716
2205
  purgeModal.classList.remove("show");
1717
- void executePurge();
2206
+ void executePurge(purgeResultEl);
2207
+ });
2208
+ }
2209
+ function initArchiveModal(burgerDropdown) {
2210
+ const archiveModal = document.getElementById("archive-confirm-modal");
2211
+ const archiveConfirmBtn = document.getElementById("archive-confirm-btn");
2212
+ const archiveCancelBtn = document.getElementById("archive-cancel-btn");
2213
+ const archiveResultEl = document.getElementById("archive-result");
2214
+ if (!archiveModal || !archiveConfirmBtn || !archiveCancelBtn || !archiveResultEl) {
2215
+ return;
2216
+ }
2217
+ const safeArchiveModal = archiveModal;
2218
+ const safeArchiveResultEl = archiveResultEl;
2219
+ document.getElementById("burger-archive-tasks")?.addEventListener("click", () => {
2220
+ burgerDropdown.classList.remove("open");
2221
+ safeArchiveResultEl.textContent = "";
2222
+ safeArchiveModal.classList.add("show");
2223
+ });
2224
+ archiveCancelBtn.addEventListener("click", () => {
2225
+ safeArchiveModal.classList.remove("show");
2226
+ });
2227
+ archiveConfirmBtn.addEventListener("click", async () => {
2228
+ const result = await executeArchive(safeArchiveResultEl);
2229
+ if (result !== null) {
2230
+ safeArchiveModal.classList.remove("show");
2231
+ }
1718
2232
  });
1719
2233
  }
1720
2234
  function initVersionModal(burgerDropdown) {
@@ -1837,6 +2351,7 @@
1837
2351
  const burgerDropdown = document.getElementById("burger-menu-dropdown");
1838
2352
  initBurgerToggle(burgerBtn, burgerDropdown);
1839
2353
  initPurgeModal(burgerDropdown);
2354
+ initArchiveModal(burgerDropdown);
1840
2355
  initExportModal(burgerDropdown);
1841
2356
  initImportModal(burgerDropdown);
1842
2357
  initVersionModal(burgerDropdown);
@@ -2069,6 +2584,161 @@
2069
2584
  });
2070
2585
  }
2071
2586
 
2587
+ // src/board/client/claudeStreamModal.ts
2588
+ var _currentEventSource = null;
2589
+ var _currentTaskId = null;
2590
+ var _claudeButtonUpdateCallback = null;
2591
+ function registerClaudeButtonUpdateCallback(cb) {
2592
+ _claudeButtonUpdateCallback = cb;
2593
+ }
2594
+ function stripAnsi(text) {
2595
+ return text.replace(/\x1b\[[0-9;]*m/g, "");
2596
+ }
2597
+ function getModalElements() {
2598
+ return {
2599
+ overlay: document.getElementById("claude-stream-modal"),
2600
+ title: document.getElementById("claude-stream-modal-title"),
2601
+ log: document.getElementById("claude-stream-log"),
2602
+ status: document.getElementById("claude-stream-status"),
2603
+ stopBtn: document.getElementById("claude-stream-stop-btn"),
2604
+ closeBtn: document.getElementById("claude-stream-close-btn"),
2605
+ modalClose: document.getElementById("claude-stream-modal-close")
2606
+ };
2607
+ }
2608
+ function isAutoScrollEnabled(log) {
2609
+ const threshold = 50;
2610
+ return log.scrollHeight - log.scrollTop - log.clientHeight <= threshold;
2611
+ }
2612
+ function appendToLog(log, text, className) {
2613
+ const shouldScroll = isAutoScrollEnabled(log);
2614
+ const line = document.createElement("div");
2615
+ line.textContent = text;
2616
+ if (className) {
2617
+ line.className = className;
2618
+ }
2619
+ log.appendChild(line);
2620
+ if (shouldScroll) {
2621
+ log.scrollTop = log.scrollHeight;
2622
+ }
2623
+ }
2624
+ function closeEventSource() {
2625
+ if (_currentEventSource) {
2626
+ _currentEventSource.close();
2627
+ _currentEventSource = null;
2628
+ }
2629
+ }
2630
+ function closeClaudeStreamModal() {
2631
+ closeEventSource();
2632
+ const { overlay } = getModalElements();
2633
+ overlay?.classList.remove("show");
2634
+ }
2635
+ function openClaudeStreamModal(taskId) {
2636
+ closeEventSource();
2637
+ _currentTaskId = taskId;
2638
+ const { overlay, title, log, status, stopBtn } = getModalElements();
2639
+ if (!overlay || !title || !log || !status || !stopBtn) return;
2640
+ title.textContent = `Claude Output #${taskId}`;
2641
+ log.innerHTML = "";
2642
+ status.textContent = "Connecting...";
2643
+ stopBtn.disabled = false;
2644
+ stopBtn.textContent = "Stop";
2645
+ overlay.classList.add("show");
2646
+ const es = new EventSource(`/api/claude/tasks/${taskId}/stream`);
2647
+ _currentEventSource = es;
2648
+ es.onopen = () => {
2649
+ if (_currentEventSource === es) {
2650
+ status.textContent = "Running";
2651
+ }
2652
+ };
2653
+ es.addEventListener("text", (event) => {
2654
+ const msgEvent = event;
2655
+ try {
2656
+ const data = JSON.parse(msgEvent.data);
2657
+ appendToLog(log, stripAnsi(data.text));
2658
+ } catch {
2659
+ }
2660
+ });
2661
+ es.addEventListener("tool_use", (event) => {
2662
+ const msgEvent = event;
2663
+ try {
2664
+ const data = JSON.parse(msgEvent.data);
2665
+ const mainArg = data.input?.path ?? data.input?.command ?? "";
2666
+ const displayText = mainArg ? `\u{1F527} ${data.name}: ${mainArg}` : `\u{1F527} ${data.name}`;
2667
+ appendToLog(log, displayText, "claude-stream-tool-use");
2668
+ } catch {
2669
+ }
2670
+ });
2671
+ es.addEventListener("end", (event) => {
2672
+ const msgEvent = event;
2673
+ try {
2674
+ const data = JSON.parse(msgEvent.data);
2675
+ status.textContent = `Done (exit ${data.exitCode})`;
2676
+ } catch {
2677
+ status.textContent = "Done";
2678
+ }
2679
+ closeEventSource();
2680
+ stopBtn.disabled = true;
2681
+ });
2682
+ es.addEventListener("error", (event) => {
2683
+ const msgEvent = event;
2684
+ try {
2685
+ const data = JSON.parse(msgEvent.data);
2686
+ status.textContent = `Error: ${data.message}`;
2687
+ } catch {
2688
+ status.textContent = "Error";
2689
+ }
2690
+ closeEventSource();
2691
+ stopBtn.disabled = true;
2692
+ });
2693
+ es.onerror = () => {
2694
+ if (_currentEventSource === es) {
2695
+ closeEventSource();
2696
+ status.textContent = "Disconnected";
2697
+ stopBtn.disabled = true;
2698
+ }
2699
+ };
2700
+ }
2701
+ async function handleStop() {
2702
+ if (_currentTaskId === null) return;
2703
+ const { stopBtn, status } = getModalElements();
2704
+ if (!stopBtn || !status) return;
2705
+ const taskId = _currentTaskId;
2706
+ closeEventSource();
2707
+ stopBtn.disabled = true;
2708
+ stopBtn.textContent = "Stopping...";
2709
+ try {
2710
+ await fetch(`/api/claude/tasks/${taskId}/run`, { method: "DELETE" });
2711
+ } catch {
2712
+ }
2713
+ stopBtn.textContent = "Stopped";
2714
+ status.textContent = "Stopped";
2715
+ if (_claudeButtonUpdateCallback) {
2716
+ _claudeButtonUpdateCallback();
2717
+ }
2718
+ }
2719
+ function initClaudeStreamModal() {
2720
+ const { overlay, stopBtn, closeBtn, modalClose } = getModalElements();
2721
+ modalClose?.addEventListener("click", () => {
2722
+ closeClaudeStreamModal();
2723
+ });
2724
+ closeBtn?.addEventListener("click", () => {
2725
+ closeClaudeStreamModal();
2726
+ });
2727
+ stopBtn?.addEventListener("click", () => {
2728
+ void handleStop();
2729
+ });
2730
+ overlay?.addEventListener("click", (e) => {
2731
+ if (e.target === overlay) {
2732
+ closeClaudeStreamModal();
2733
+ }
2734
+ });
2735
+ document.addEventListener("keydown", (e) => {
2736
+ if (e.key === "Escape" && overlay?.classList.contains("show")) {
2737
+ closeClaudeStreamModal();
2738
+ }
2739
+ });
2740
+ }
2741
+
2072
2742
  // src/board/client/main.ts
2073
2743
  initDragDrop();
2074
2744
  initAutoScroll();
@@ -2079,4 +2749,10 @@
2079
2749
  initFilters();
2080
2750
  initBurgerMenu();
2081
2751
  initDependencyVisualization();
2752
+ initClaudeButton();
2753
+ initClaudeStreamModal();
2754
+ registerClaudeModalCallback(openClaudeStreamModal);
2755
+ registerClaudeButtonUpdateCallback(() => {
2756
+ updateButtonStates(/* @__PURE__ */ new Set());
2757
+ });
2082
2758
  })();