agkan 2.14.3 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (197) hide show
  1. package/README.ja.md +62 -238
  2. package/README.md +48 -241
  3. package/dist/board/boardFavicon.d.ts +2 -0
  4. package/dist/board/boardFavicon.d.ts.map +1 -0
  5. package/dist/board/boardFavicon.js +5 -0
  6. package/dist/board/boardFavicon.js.map +1 -0
  7. package/dist/board/boardRenderer.d.ts +22 -6
  8. package/dist/board/boardRenderer.d.ts.map +1 -1
  9. package/dist/board/boardRenderer.js +67 -26
  10. package/dist/board/boardRenderer.js.map +1 -1
  11. package/dist/board/boardRoutes.d.ts +4 -2
  12. package/dist/board/boardRoutes.d.ts.map +1 -1
  13. package/dist/board/boardRoutes.js +140 -13
  14. package/dist/board/boardRoutes.js.map +1 -1
  15. package/dist/board/boardStyles.d.ts +1 -1
  16. package/dist/board/boardStyles.d.ts.map +1 -1
  17. package/dist/board/boardStyles.js +62 -8
  18. package/dist/board/boardStyles.js.map +1 -1
  19. package/dist/board/client/board.js +1007 -29
  20. package/dist/board/server.d.ts +3 -2
  21. package/dist/board/server.d.ts.map +1 -1
  22. package/dist/board/server.js +9 -7
  23. package/dist/board/server.js.map +1 -1
  24. package/dist/cli/commands/agent-guide.d.ts.map +1 -1
  25. package/dist/cli/commands/agent-guide.js +6 -0
  26. package/dist/cli/commands/agent-guide.js.map +1 -1
  27. package/dist/cli/commands/board.d.ts.map +1 -1
  28. package/dist/cli/commands/board.js +202 -15
  29. package/dist/cli/commands/board.js.map +1 -1
  30. package/dist/cli/commands/ps.d.ts +7 -0
  31. package/dist/cli/commands/ps.d.ts.map +1 -0
  32. package/dist/cli/commands/ps.js +83 -0
  33. package/dist/cli/commands/ps.js.map +1 -0
  34. package/dist/cli/commands/tag/add.d.ts.map +1 -1
  35. package/dist/cli/commands/tag/add.js +10 -11
  36. package/dist/cli/commands/tag/add.js.map +1 -1
  37. package/dist/cli/commands/tag/attach.d.ts.map +1 -1
  38. package/dist/cli/commands/tag/attach.js +10 -11
  39. package/dist/cli/commands/tag/attach.js.map +1 -1
  40. package/dist/cli/commands/tag/rename.d.ts.map +1 -1
  41. package/dist/cli/commands/tag/rename.js +10 -11
  42. package/dist/cli/commands/tag/rename.js.map +1 -1
  43. package/dist/cli/commands/task/add.js +1 -1
  44. package/dist/cli/commands/task/add.js.map +1 -1
  45. package/dist/cli/commands/task/copy.d.ts +6 -0
  46. package/dist/cli/commands/task/copy.d.ts.map +1 -0
  47. package/dist/cli/commands/task/copy.js +118 -0
  48. package/dist/cli/commands/task/copy.js.map +1 -0
  49. package/dist/cli/commands/task/list.d.ts.map +1 -1
  50. package/dist/cli/commands/task/list.js +37 -17
  51. package/dist/cli/commands/task/list.js.map +1 -1
  52. package/dist/cli/commands/task/update-parent.d.ts.map +1 -1
  53. package/dist/cli/commands/task/update-parent.js +10 -11
  54. package/dist/cli/commands/task/update-parent.js.map +1 -1
  55. package/dist/cli/index.js +6 -0
  56. package/dist/cli/index.js.map +1 -1
  57. package/dist/cli/utils/board-daemon.d.ts +7 -0
  58. package/dist/cli/utils/board-daemon.d.ts.map +1 -0
  59. package/dist/cli/utils/board-daemon.js +83 -0
  60. package/dist/cli/utils/board-daemon.js.map +1 -0
  61. package/dist/db/adapters/sqlite-storage-backend.d.ts +27 -0
  62. package/dist/db/adapters/sqlite-storage-backend.d.ts.map +1 -0
  63. package/dist/db/adapters/sqlite-storage-backend.js +498 -0
  64. package/dist/db/adapters/sqlite-storage-backend.js.map +1 -0
  65. package/dist/db/connection.d.ts +19 -0
  66. package/dist/db/connection.d.ts.map +1 -1
  67. package/dist/db/connection.js +37 -2
  68. package/dist/db/connection.js.map +1 -1
  69. package/dist/db/migrations/20260328000000_initial_schema.d.ts +3 -0
  70. package/dist/db/migrations/20260328000000_initial_schema.d.ts.map +1 -0
  71. package/dist/db/migrations/20260328000000_initial_schema.js +218 -0
  72. package/dist/db/migrations/20260328000000_initial_schema.js.map +1 -0
  73. package/dist/db/migrations/20260329000000_add_session_id_to_task_run_logs.d.ts +3 -0
  74. package/dist/db/migrations/20260329000000_add_session_id_to_task_run_logs.d.ts.map +1 -0
  75. package/dist/db/migrations/20260329000000_add_session_id_to_task_run_logs.js +7 -0
  76. package/dist/db/migrations/20260329000000_add_session_id_to_task_run_logs.js.map +1 -0
  77. package/dist/db/migrations/index.d.ts +4 -0
  78. package/dist/db/migrations/index.d.ts.map +1 -0
  79. package/dist/db/migrations/index.js +18 -0
  80. package/dist/db/migrations/index.js.map +1 -0
  81. package/dist/db/migrations/types.d.ts +17 -0
  82. package/dist/db/migrations/types.d.ts.map +1 -0
  83. package/dist/{board/client → db/migrations}/types.js +0 -1
  84. package/dist/db/migrations/types.js.map +1 -0
  85. package/dist/db/reset.d.ts.map +1 -1
  86. package/dist/db/reset.js +8 -3
  87. package/dist/db/reset.js.map +1 -1
  88. package/dist/db/schema.d.ts +4 -4
  89. package/dist/db/schema.d.ts.map +1 -1
  90. package/dist/db/schema.js +22 -207
  91. package/dist/db/schema.js.map +1 -1
  92. package/dist/db/types/repository.d.ts +226 -0
  93. package/dist/db/types/repository.d.ts.map +1 -0
  94. package/dist/db/types/repository.js +15 -0
  95. package/dist/db/types/repository.js.map +1 -0
  96. package/dist/errors.d.ts +27 -0
  97. package/dist/errors.d.ts.map +1 -0
  98. package/dist/errors.js +51 -0
  99. package/dist/errors.js.map +1 -0
  100. package/dist/models/Task.d.ts +2 -0
  101. package/dist/models/Task.d.ts.map +1 -1
  102. package/dist/services/ClaudeProcessService.d.ts +100 -0
  103. package/dist/services/ClaudeProcessService.d.ts.map +1 -0
  104. package/dist/services/ClaudeProcessService.js +279 -0
  105. package/dist/services/ClaudeProcessService.js.map +1 -0
  106. package/dist/services/CommentService.d.ts +3 -3
  107. package/dist/services/CommentService.d.ts.map +1 -1
  108. package/dist/services/CommentService.js +13 -72
  109. package/dist/services/CommentService.js.map +1 -1
  110. package/dist/services/ExportImportService.d.ts +3 -3
  111. package/dist/services/ExportImportService.d.ts.map +1 -1
  112. package/dist/services/ExportImportService.js +29 -31
  113. package/dist/services/ExportImportService.js.map +1 -1
  114. package/dist/services/MetadataService.d.ts +3 -3
  115. package/dist/services/MetadataService.d.ts.map +1 -1
  116. package/dist/services/MetadataService.js +9 -69
  117. package/dist/services/MetadataService.js.map +1 -1
  118. package/dist/services/ProcessService.d.ts +54 -0
  119. package/dist/services/ProcessService.d.ts.map +1 -0
  120. package/dist/services/ProcessService.js +147 -0
  121. package/dist/services/ProcessService.js.map +1 -0
  122. package/dist/services/TagService.d.ts +3 -3
  123. package/dist/services/TagService.d.ts.map +1 -1
  124. package/dist/services/TagService.js +16 -41
  125. package/dist/services/TagService.js.map +1 -1
  126. package/dist/services/TaskBlockService.d.ts +3 -3
  127. package/dist/services/TaskBlockService.d.ts.map +1 -1
  128. package/dist/services/TaskBlockService.js +14 -40
  129. package/dist/services/TaskBlockService.js.map +1 -1
  130. package/dist/services/TaskService.d.ts +5 -23
  131. package/dist/services/TaskService.d.ts.map +1 -1
  132. package/dist/services/TaskService.js +57 -191
  133. package/dist/services/TaskService.js.map +1 -1
  134. package/dist/services/TaskTagService.d.ts +3 -3
  135. package/dist/services/TaskTagService.d.ts.map +1 -1
  136. package/dist/services/TaskTagService.js +23 -86
  137. package/dist/services/TaskTagService.js.map +1 -1
  138. package/dist/services/TmuxService.d.ts +2 -0
  139. package/dist/services/TmuxService.d.ts.map +1 -0
  140. package/dist/services/TmuxService.js +7 -0
  141. package/dist/services/TmuxService.js.map +1 -0
  142. package/dist/services/index.d.ts +2 -0
  143. package/dist/services/index.d.ts.map +1 -1
  144. package/dist/services/index.js +3 -1
  145. package/dist/services/index.js.map +1 -1
  146. package/dist/utils/logger.d.ts +7 -0
  147. package/dist/utils/logger.d.ts.map +1 -0
  148. package/dist/utils/logger.js +18 -0
  149. package/dist/utils/logger.js.map +1 -0
  150. package/package.json +12 -5
  151. package/dist/board/client/addTaskModal.d.ts +0 -2
  152. package/dist/board/client/addTaskModal.d.ts.map +0 -1
  153. package/dist/board/client/addTaskModal.js +0 -64
  154. package/dist/board/client/addTaskModal.js.map +0 -1
  155. package/dist/board/client/autoScroll.d.ts +0 -4
  156. package/dist/board/client/autoScroll.d.ts.map +0 -1
  157. package/dist/board/client/autoScroll.js +0 -59
  158. package/dist/board/client/autoScroll.js.map +0 -1
  159. package/dist/board/client/boardPolling.d.ts +0 -15
  160. package/dist/board/client/boardPolling.d.ts.map +0 -1
  161. package/dist/board/client/boardPolling.js +0 -144
  162. package/dist/board/client/boardPolling.js.map +0 -1
  163. package/dist/board/client/burgerMenu.d.ts +0 -2
  164. package/dist/board/client/burgerMenu.d.ts.map +0 -1
  165. package/dist/board/client/burgerMenu.js +0 -80
  166. package/dist/board/client/burgerMenu.js.map +0 -1
  167. package/dist/board/client/contextMenu.d.ts +0 -2
  168. package/dist/board/client/contextMenu.d.ts.map +0 -1
  169. package/dist/board/client/contextMenu.js +0 -52
  170. package/dist/board/client/contextMenu.js.map +0 -1
  171. package/dist/board/client/detailPanel.d.ts +0 -8
  172. package/dist/board/client/detailPanel.d.ts.map +0 -1
  173. package/dist/board/client/detailPanel.js +0 -565
  174. package/dist/board/client/detailPanel.js.map +0 -1
  175. package/dist/board/client/dragDrop.d.ts +0 -6
  176. package/dist/board/client/dragDrop.d.ts.map +0 -1
  177. package/dist/board/client/dragDrop.js +0 -82
  178. package/dist/board/client/dragDrop.js.map +0 -1
  179. package/dist/board/client/filters.d.ts +0 -6
  180. package/dist/board/client/filters.d.ts.map +0 -1
  181. package/dist/board/client/filters.js +0 -167
  182. package/dist/board/client/filters.js.map +0 -1
  183. package/dist/board/client/main.d.ts +0 -2
  184. package/dist/board/client/main.d.ts.map +0 -1
  185. package/dist/board/client/main.js +0 -20
  186. package/dist/board/client/main.js.map +0 -1
  187. package/dist/board/client/tags.d.ts +0 -6
  188. package/dist/board/client/tags.d.ts.map +0 -1
  189. package/dist/board/client/tags.js +0 -198
  190. package/dist/board/client/tags.js.map +0 -1
  191. package/dist/board/client/types.d.ts +0 -48
  192. package/dist/board/client/types.d.ts.map +0 -1
  193. package/dist/board/client/types.js.map +0 -1
  194. package/dist/board/client/utils.d.ts +0 -4
  195. package/dist/board/client/utils.d.ts.map +0 -1
  196. package/dist/board/client/utils.js +0 -44
  197. package/dist/board/client/utils.js.map +0 -1
@@ -30,9 +30,277 @@
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
286
+ var _redrawDependencies = null;
287
+ function registerDependencyRedrawCallback(callback) {
288
+ _redrawDependencies = callback;
289
+ }
34
290
  var draggedCard = null;
35
291
  var sourceBody = null;
292
+ var isPendingStatusUpdate = false;
293
+ var _dragMouseX = 0;
294
+ var _dragMouseY = 0;
295
+ var _dragOffsetX = 0;
296
+ var _dragOffsetY = 0;
297
+ function getDraggedCardVirtualRect() {
298
+ if (!draggedCard) return null;
299
+ const rect = draggedCard.getBoundingClientRect();
300
+ const left = _dragMouseX - _dragOffsetX;
301
+ const top = _dragMouseY - _dragOffsetY;
302
+ return new DOMRect(left, top, rect.width, rect.height);
303
+ }
36
304
  function updateCount(status) {
37
305
  const col = document.querySelector(`.column[data-status="${status}"]`);
38
306
  if (!col) return;
@@ -55,6 +323,8 @@
55
323
  draggedCard.dataset.status = newStatus;
56
324
  updateCount(oldStatus);
57
325
  updateCount(newStatus);
326
+ updateCardButton(draggedCard, newStatus);
327
+ isPendingStatusUpdate = true;
58
328
  try {
59
329
  const res = await fetch("/api/tasks/" + taskId, {
60
330
  method: "PATCH",
@@ -62,6 +332,9 @@
62
332
  body: JSON.stringify({ status: newStatus })
63
333
  });
64
334
  if (!res.ok) throw new Error("Server error");
335
+ if (_redrawDependencies) {
336
+ _redrawDependencies();
337
+ }
65
338
  } catch {
66
339
  if (prevBody && draggedCard) {
67
340
  prevBody.appendChild(draggedCard);
@@ -70,19 +343,37 @@
70
343
  updateCount(newStatus);
71
344
  }
72
345
  showToast();
346
+ } finally {
347
+ isPendingStatusUpdate = false;
73
348
  }
74
349
  }
350
+ var _documentDragOverListener = null;
75
351
  function attachDragListeners(card) {
76
352
  card.addEventListener("dragstart", (e) => {
77
353
  draggedCard = card;
78
354
  sourceBody = card.parentElement;
79
355
  card.classList.add("dragging");
80
356
  if (e.dataTransfer) e.dataTransfer.effectAllowed = "move";
357
+ const rect = card.getBoundingClientRect();
358
+ _dragOffsetX = e.clientX - rect.left;
359
+ _dragOffsetY = e.clientY - rect.top;
360
+ _dragMouseX = e.clientX;
361
+ _dragMouseY = e.clientY;
362
+ _documentDragOverListener = (ev) => {
363
+ _dragMouseX = ev.clientX;
364
+ _dragMouseY = ev.clientY;
365
+ if (_redrawDependencies) _redrawDependencies();
366
+ };
367
+ document.addEventListener("dragover", _documentDragOverListener);
81
368
  });
82
369
  card.addEventListener("dragend", () => {
83
370
  card.classList.remove("dragging");
84
371
  draggedCard = null;
85
372
  sourceBody = null;
373
+ if (_documentDragOverListener) {
374
+ document.removeEventListener("dragover", _documentDragOverListener);
375
+ _documentDragOverListener = null;
376
+ }
86
377
  });
87
378
  }
88
379
  function initDragDrop() {
@@ -219,14 +510,43 @@
219
510
  });
220
511
  input.placeholder = currentTags.length === 0 ? "Add tags..." : "";
221
512
  }
513
+ async function createAndAddTag(name) {
514
+ const detailTaskId2 = _getDetailTaskId ? _getDetailTaskId() : null;
515
+ try {
516
+ const createRes = await fetch("/api/tags", {
517
+ method: "POST",
518
+ headers: { "Content-Type": "application/json" },
519
+ body: JSON.stringify({ name })
520
+ });
521
+ if (!createRes.ok) throw new Error("Server error");
522
+ const newTag = await createRes.json();
523
+ allAvailableTags.push(newTag);
524
+ const taskRes = await fetch("/api/tasks/" + detailTaskId2 + "/tags", {
525
+ method: "POST",
526
+ headers: { "Content-Type": "application/json" },
527
+ body: JSON.stringify({ tagId: newTag.id })
528
+ });
529
+ if (!taskRes.ok) throw new Error("Server error");
530
+ currentTags.push(newTag);
531
+ input.value = "";
532
+ inputValue = "";
533
+ renderPills();
534
+ renderDropdown();
535
+ } catch {
536
+ showToast("Failed to create tag");
537
+ }
538
+ }
222
539
  function renderDropdown() {
223
540
  const filtered = getFilteredTags();
224
541
  dropdown.innerHTML = "";
225
542
  focusedOptionIndex = -1;
226
- if (filtered.length === 0) {
543
+ const hasInput = inputValue.trim() !== "";
544
+ const exactMatch = hasInput && allAvailableTags.some((t) => t.name.toLowerCase() === inputValue.trim().toLowerCase());
545
+ const showCreate = hasInput && !exactMatch;
546
+ if (filtered.length === 0 && !showCreate) {
227
547
  const noOpt = document.createElement("div");
228
548
  noOpt.className = "tag-select-no-options";
229
- noOpt.textContent = inputValue ? "No matching tags" : "No tags available";
549
+ noOpt.textContent = hasInput ? "No matching tags" : "No tags available";
230
550
  dropdown.appendChild(noOpt);
231
551
  } else {
232
552
  filtered.forEach((t, i) => {
@@ -241,6 +561,18 @@
241
561
  });
242
562
  dropdown.appendChild(opt);
243
563
  });
564
+ if (showCreate) {
565
+ const createOpt = document.createElement("div");
566
+ createOpt.className = "tag-select-option tag-select-create-option";
567
+ createOpt.dataset.create = "true";
568
+ createOpt.textContent = `Create "${inputValue.trim()}"`;
569
+ createOpt.addEventListener("mouseover", () => setFocusedOption(filtered.length));
570
+ createOpt.addEventListener("mousedown", async (e) => {
571
+ e.preventDefault();
572
+ await createAndAddTag(inputValue.trim());
573
+ });
574
+ dropdown.appendChild(createOpt);
575
+ }
244
576
  }
245
577
  }
246
578
  function setFocusedOption(index) {
@@ -296,6 +628,8 @@
296
628
  e.preventDefault();
297
629
  if (focusedOptionIndex >= 0 && filtered[focusedOptionIndex]) {
298
630
  await addTag(String(filtered[focusedOptionIndex].id));
631
+ } else if (focusedOptionIndex >= 0 && inputValue.trim()) {
632
+ await createAndAddTag(inputValue.trim());
299
633
  }
300
634
  } else if (e.key === "Escape") {
301
635
  closeDropdown();
@@ -346,14 +680,20 @@
346
680
  var _renderDetailPanel = null;
347
681
  var _showUpdateWarning = null;
348
682
  var _getDetailTaskId2 = null;
683
+ var _getDetailActiveTab = null;
349
684
  var _setActiveCard = null;
685
+ var _redrawDependencies2 = null;
350
686
  function registerDetailPanelCallbacks(callbacks) {
351
687
  _openTaskDetail = callbacks.openTaskDetail;
352
688
  _renderDetailPanel = callbacks.renderDetailPanel;
353
689
  _showUpdateWarning = callbacks.showUpdateWarning;
354
690
  _getDetailTaskId2 = callbacks.getDetailTaskId;
691
+ _getDetailActiveTab = callbacks.getDetailActiveTab;
355
692
  _setActiveCard = callbacks.setActiveCard;
356
693
  }
694
+ function registerDependencyRedrawCallback2(callback) {
695
+ _redrawDependencies2 = callback;
696
+ }
357
697
  function attachCardListeners(body) {
358
698
  body.querySelectorAll(".card").forEach((card) => {
359
699
  attachDragListeners(card);
@@ -416,6 +756,8 @@
416
756
  }
417
757
  attachCardListeners(body);
418
758
  attachAutoScrollToBody(body);
759
+ attachClaudeButtonListeners(body);
760
+ updateButtonStates(getRunningTaskIds());
419
761
  }
420
762
  function isEditingDetailPanel() {
421
763
  const editableFields = ["detail-edit-title", "detail-edit-body", "detail-edit-status", "detail-edit-priority"];
@@ -426,6 +768,9 @@
426
768
  if (_showUpdateWarning) _showUpdateWarning();
427
769
  return;
428
770
  }
771
+ if (_getDetailActiveTab && _getDetailActiveTab() === "run-logs") {
772
+ return;
773
+ }
429
774
  try {
430
775
  const taskRes = await fetch("/api/tasks/" + detailTaskId2);
431
776
  if (taskRes.ok) {
@@ -447,6 +792,9 @@
447
792
  if (detailTaskId2 !== null && _setActiveCard) {
448
793
  _setActiveCard(detailTaskId2);
449
794
  }
795
+ if (_redrawDependencies2) {
796
+ _redrawDependencies2();
797
+ }
450
798
  if (detailTaskId2 !== null) {
451
799
  await refreshOpenDetailPanel(detailTaskId2);
452
800
  }
@@ -454,7 +802,7 @@
454
802
  }
455
803
  }
456
804
  async function pollBoardUpdates() {
457
- if (draggedCard !== null) return;
805
+ if (draggedCard !== null || isPendingStatusUpdate) return;
458
806
  try {
459
807
  const res = await fetch("/api/board/updated-at");
460
808
  if (!res.ok) return;
@@ -512,10 +860,13 @@
512
860
  const filtered = getFilteredAddTags();
513
861
  dropdown.innerHTML = "";
514
862
  tagFocusedIndex = -1;
515
- if (filtered.length === 0) {
863
+ const hasInput = tagInputValue.trim() !== "";
864
+ const exactMatch = hasInput && allAvailableTags.some((t) => t.name.toLowerCase() === tagInputValue.trim().toLowerCase());
865
+ const showCreate = hasInput && !exactMatch;
866
+ if (filtered.length === 0 && !showCreate) {
516
867
  const noOpt = document.createElement("div");
517
868
  noOpt.className = "tag-select-no-options";
518
- noOpt.textContent = tagInputValue ? "No matching tags" : "No tags available";
869
+ noOpt.textContent = hasInput ? "No matching tags" : "No tags available";
519
870
  dropdown.appendChild(noOpt);
520
871
  } else {
521
872
  filtered.forEach((t, i) => {
@@ -530,6 +881,19 @@
530
881
  });
531
882
  dropdown.appendChild(opt);
532
883
  });
884
+ if (showCreate) {
885
+ const createOpt = document.createElement("div");
886
+ createOpt.className = "tag-select-option tag-select-create-option";
887
+ createOpt.dataset.create = "true";
888
+ createOpt.textContent = `Create "${tagInputValue.trim()}"`;
889
+ createOpt.addEventListener("mouseover", () => setAddTagFocused(dropdown, filtered.length));
890
+ createOpt.addEventListener("mousedown", async (e) => {
891
+ e.preventDefault();
892
+ const input = document.getElementById("add-tag-input");
893
+ await createAndSelectAddTag(tagInputValue.trim(), dropdown, input);
894
+ });
895
+ dropdown.appendChild(createOpt);
896
+ }
533
897
  }
534
898
  }
535
899
  function setAddTagFocused(dropdown, index) {
@@ -547,6 +911,21 @@
547
911
  renderAddTagPills(control, input);
548
912
  renderAddTagDropdown(dropdown);
549
913
  }
914
+ async function createAndSelectAddTag(name, dropdown, input) {
915
+ try {
916
+ const res = await fetch("/api/tags", {
917
+ method: "POST",
918
+ headers: { "Content-Type": "application/json" },
919
+ body: JSON.stringify({ name })
920
+ });
921
+ if (!res.ok) throw new Error("Server error");
922
+ const newTag = await res.json();
923
+ allAvailableTags.push(newTag);
924
+ selectAddTag(newTag.id, dropdown, input);
925
+ } catch {
926
+ showToast("Failed to create tag");
927
+ }
928
+ }
550
929
  function initAddTagSelector() {
551
930
  const control = document.getElementById("add-tag-select-control");
552
931
  const dropdown = document.getElementById("add-tag-select-dropdown");
@@ -574,7 +953,7 @@
574
953
  renderAddTagDropdown(dropdown);
575
954
  if (!dropdown.classList.contains("open")) dropdown.classList.add("open");
576
955
  });
577
- input.addEventListener("keydown", (e) => {
956
+ input.addEventListener("keydown", async (e) => {
578
957
  const filtered = getFilteredAddTags();
579
958
  const opts = dropdown.querySelectorAll(".tag-select-option");
580
959
  if (e.key === "ArrowDown") {
@@ -587,6 +966,8 @@
587
966
  e.preventDefault();
588
967
  if (tagFocusedIndex >= 0 && filtered[tagFocusedIndex]) {
589
968
  selectAddTag(filtered[tagFocusedIndex].id, dropdown, input);
969
+ } else if (tagFocusedIndex >= 0 && tagInputValue.trim()) {
970
+ await createAndSelectAddTag(tagInputValue.trim(), dropdown, input);
590
971
  }
591
972
  } else if (e.key === "Escape") {
592
973
  dropdown.classList.remove("open");
@@ -636,7 +1017,7 @@
636
1017
  function resetAddModal(elements) {
637
1018
  elements.addTitle.value = "";
638
1019
  elements.addBody.value = "";
639
- elements.addPriority.value = "";
1020
+ elements.addPriority.value = "medium";
640
1021
  selectedTags = [];
641
1022
  tagInputValue = "";
642
1023
  tagFocusedIndex = -1;
@@ -794,8 +1175,8 @@
794
1175
  });
795
1176
  if (!res.ok) throw new Error("Server error");
796
1177
  }
797
- async function fetchTaskDetail(taskId) {
798
- const res = await fetch("/api/tasks/" + taskId);
1178
+ async function fetchTaskDetail(taskId, signal) {
1179
+ const res = await fetch("/api/tasks/" + taskId, signal ? { signal } : void 0);
799
1180
  if (!res.ok) throw new Error("Server error");
800
1181
  return res.json();
801
1182
  }
@@ -841,6 +1222,12 @@
841
1222
  }).catch(function() {
842
1223
  });
843
1224
  }
1225
+ async function fetchRunLogs(taskId) {
1226
+ const res = await fetch("/api/claude/tasks/" + taskId + "/run-logs");
1227
+ if (!res.ok) throw new Error("Server error");
1228
+ const data = await res.json();
1229
+ return data.logs || [];
1230
+ }
844
1231
 
845
1232
  // src/board/client/detailPanelHtml.ts
846
1233
  function renderCommentItemHtml(comment, taskId) {
@@ -903,14 +1290,14 @@
903
1290
  if (parent) {
904
1291
  html += '<div class="detail-relation-row">';
905
1292
  html += '<span class="detail-relation-label">Parent</span>';
906
- html += '<div class="detail-relation-ids"><span class="detail-relation-id">#' + parent.id + " " + escapeHtmlClient(parent.title) + "</span></div>";
1293
+ html += '<div class="detail-relation-ids"><span class="detail-relation-id detail-relation-link" data-task-id="' + parent.id + '">#' + parent.id + " " + escapeHtmlClient(parent.title) + "</span></div>";
907
1294
  html += "</div>";
908
1295
  }
909
1296
  if (blockedBy.length > 0) {
910
1297
  html += '<div class="detail-relation-row"><span class="detail-relation-label">Blocked by</span>';
911
1298
  html += '<div class="detail-relation-ids">';
912
1299
  blockedBy.forEach((t) => {
913
- html += '<span class="detail-relation-id">#' + t.id + "</span>";
1300
+ html += '<span class="detail-relation-id detail-relation-link" data-task-id="' + t.id + '">#' + t.id + "</span>";
914
1301
  });
915
1302
  html += "</div></div>";
916
1303
  }
@@ -918,7 +1305,7 @@
918
1305
  html += '<div class="detail-relation-row"><span class="detail-relation-label">Blocking</span>';
919
1306
  html += '<div class="detail-relation-ids">';
920
1307
  blocking.forEach((t) => {
921
- html += '<span class="detail-relation-id">#' + t.id + "</span>";
1308
+ html += '<span class="detail-relation-id detail-relation-link" data-task-id="' + t.id + '">#' + t.id + "</span>";
922
1309
  });
923
1310
  html += "</div></div>";
924
1311
  }
@@ -926,11 +1313,10 @@
926
1313
  return html;
927
1314
  }
928
1315
  function renderMetadataTable(metadata) {
929
- const otherMeta = metadata.filter((m) => m.key !== "priority");
930
- if (otherMeta.length === 0) return "";
1316
+ if (metadata.length === 0) return "";
931
1317
  let html = '<div class="detail-field"><div class="detail-field-label">Metadata</div>';
932
1318
  html += '<table class="detail-meta-table">';
933
- otherMeta.forEach((m) => {
1319
+ metadata.forEach((m) => {
934
1320
  html += "<tr><td>" + escapeHtmlClient(m.key) + "</td><td>" + escapeHtmlClient(m.value) + "</td></tr>";
935
1321
  });
936
1322
  html += "</table></div>";
@@ -968,8 +1354,36 @@
968
1354
  html += renderEditableTextFields(task);
969
1355
  return html;
970
1356
  }
1357
+ function renderRunLogsHtml(logs) {
1358
+ if (logs.length === 0) {
1359
+ return '<div class="run-log-empty">No run logs yet.</div>';
1360
+ }
1361
+ let html = '<div class="run-log-list">';
1362
+ logs.forEach((log, index) => {
1363
+ const date = log.started_at ? log.started_at.replace("T", " ").replace(/\.\d+Z$/, "") : "";
1364
+ const exitOk = log.exit_code === 0;
1365
+ const exitLabel = log.exit_code !== null ? "exit: " + String(log.exit_code) : "running";
1366
+ const exitClass = exitOk ? "success" : "failure";
1367
+ const isFirst = index === 0;
1368
+ 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">';
1369
+ log.events.forEach((evt) => {
1370
+ if (evt.kind === "text" && evt.text) {
1371
+ html += escapeHtmlClient(evt.text);
1372
+ } else if (evt.kind === "tool_use" && evt.name) {
1373
+ const mainArg = evt.input && typeof evt.input === "object" ? String(
1374
+ evt.input.path ?? evt.input.command ?? ""
1375
+ ) : "";
1376
+ const display = mainArg ? "\u{1F527} " + evt.name + ": " + mainArg : "\u{1F527} " + evt.name;
1377
+ html += '<span class="run-log-tool-use">' + escapeHtmlClient(display) + "\n</span>";
1378
+ }
1379
+ });
1380
+ html += "</div></div>";
1381
+ });
1382
+ html += "</div>";
1383
+ return html;
1384
+ }
971
1385
  function buildDetailPanelHtml() {
972
- 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-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>';
1386
+ 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>';
973
1387
  }
974
1388
  function autoResizeTextarea(el) {
975
1389
  const scrollContainer = el.closest(".detail-tab-content");
@@ -982,9 +1396,22 @@
982
1396
  // src/board/client/detailPanel.ts
983
1397
  var detailTaskId = null;
984
1398
  var lastTab = "details";
1399
+ var runLogPollingInterval = null;
1400
+ var currentFetchController = null;
1401
+ var runLogLoadSeq = 0;
1402
+ var runLogsLoadedTaskId = null;
1403
+ function stopRunLogPolling() {
1404
+ if (runLogPollingInterval !== null) {
1405
+ clearInterval(runLogPollingInterval);
1406
+ runLogPollingInterval = null;
1407
+ }
1408
+ }
985
1409
  function getDetailTaskId() {
986
1410
  return detailTaskId;
987
1411
  }
1412
+ function getDetailActiveTab() {
1413
+ return lastTab;
1414
+ }
988
1415
  function setActiveCard(taskId) {
989
1416
  document.querySelectorAll(".card.active").forEach((card) => {
990
1417
  card.classList.remove("active");
@@ -995,6 +1422,13 @@
995
1422
  }
996
1423
  }
997
1424
  function closeDetailPanel() {
1425
+ stopRunLogPolling();
1426
+ runLogsLoadedTaskId = null;
1427
+ const runLogsPane = document.getElementById("detail-tab-content-run-logs");
1428
+ if (runLogsPane) {
1429
+ delete runLogsPane.dataset.runLogsTaskId;
1430
+ delete runLogsPane.dataset.runLogsSignature;
1431
+ }
998
1432
  const detailPanel = document.getElementById("detail-panel");
999
1433
  detailPanel.classList.remove("open");
1000
1434
  detailPanel.style.width = "";
@@ -1002,26 +1436,41 @@
1002
1436
  detailTaskId = null;
1003
1437
  }
1004
1438
  function switchTab(tabName) {
1439
+ const activeTabBtn = document.querySelector(".detail-tab.active");
1440
+ const currentTab = activeTabBtn?.dataset.tab ?? null;
1441
+ const isSameTab = currentTab === tabName;
1442
+ if (tabName !== "run-logs") {
1443
+ stopRunLogPolling();
1444
+ }
1005
1445
  lastTab = tabName;
1006
- document.querySelectorAll(".detail-tab").forEach((btn) => {
1007
- btn.classList.toggle("active", btn.dataset.tab === tabName);
1008
- });
1009
- document.querySelectorAll(".detail-tab-content").forEach((el) => {
1010
- el.classList.toggle("active", el.id === "detail-tab-content-" + tabName);
1011
- });
1446
+ if (!isSameTab) {
1447
+ document.querySelectorAll(".detail-tab").forEach((btn) => {
1448
+ btn.classList.toggle("active", btn.dataset.tab === tabName);
1449
+ });
1450
+ document.querySelectorAll(".detail-tab-content").forEach((el) => {
1451
+ el.classList.toggle("active", el.id === "detail-tab-content-" + tabName);
1452
+ });
1453
+ }
1012
1454
  const footer = document.getElementById("detail-panel-footer");
1013
1455
  if (footer) footer.style.display = tabName === "details" ? "" : "none";
1456
+ if (tabName === "run-logs" && detailTaskId !== null && (!isSameTab || runLogsLoadedTaskId !== detailTaskId)) {
1457
+ loadRunLogs(detailTaskId).catch((err) => {
1458
+ console.error("[agkan] switchTab loadRunLogs failed", err);
1459
+ });
1460
+ }
1014
1461
  }
1015
1462
  async function loadComments(taskId) {
1016
- const tabBtn = document.getElementById("detail-tab-comments");
1017
1463
  const pane = document.getElementById("detail-tab-content-comments");
1018
1464
  if (!pane) return;
1019
1465
  try {
1020
1466
  const comments = await fetchComments(taskId);
1467
+ if (detailTaskId !== taskId) return;
1468
+ const tabBtn = document.getElementById("detail-tab-comments");
1021
1469
  if (tabBtn) tabBtn.textContent = "Comments (" + comments.length + ")";
1022
1470
  renderComments(taskId, comments);
1023
1471
  } catch (err) {
1024
1472
  console.error("[agkan] loadComments failed for task", taskId, err);
1473
+ if (detailTaskId !== taskId) return;
1025
1474
  if (pane) pane.innerHTML = '<div style="padding:20px;font-size:12px;color:#94a3b8;">Failed to load comments</div>';
1026
1475
  }
1027
1476
  }
@@ -1146,6 +1595,99 @@
1146
1595
  const taskId = target.dataset.taskId ? Number(target.dataset.taskId) : NaN;
1147
1596
  dispatchCommentAction(action, commentId, taskId);
1148
1597
  }
1598
+ function buildRunLogsSignature(logs) {
1599
+ return JSON.stringify(logs);
1600
+ }
1601
+ function renderRunLogsInPane(pane, logs) {
1602
+ const nextSignature = buildRunLogsSignature(logs);
1603
+ if (pane.dataset.runLogsSignature === nextSignature) return;
1604
+ pane.dataset.runLogsSignature = nextSignature;
1605
+ const bodyScrollState = /* @__PURE__ */ new Map();
1606
+ const openLogIds = /* @__PURE__ */ new Set();
1607
+ const hadPreviousItems = pane.querySelector(".run-log-item") !== null;
1608
+ const paneScrollTop = pane.scrollTop;
1609
+ const paneIsNearBottom = pane.scrollHeight - pane.scrollTop - pane.clientHeight <= 50;
1610
+ pane.querySelectorAll(".run-log-item.open").forEach((item) => {
1611
+ const logId = Number(item.dataset.logId);
1612
+ if (!logId) return;
1613
+ openLogIds.add(logId);
1614
+ const body = item.querySelector(".run-log-body");
1615
+ if (body) {
1616
+ const isNearBottom = body.scrollHeight - body.scrollTop - body.clientHeight <= 50;
1617
+ bodyScrollState.set(logId, { scrollTop: body.scrollTop, isNearBottom });
1618
+ }
1619
+ });
1620
+ pane.innerHTML = renderRunLogsHtml(logs);
1621
+ requestAnimationFrame(() => {
1622
+ if (hadPreviousItems) {
1623
+ pane.scrollTop = paneIsNearBottom ? pane.scrollHeight : paneScrollTop;
1624
+ }
1625
+ pane.querySelectorAll(".run-log-item").forEach((item) => {
1626
+ const logId = Number(item.dataset.logId);
1627
+ if (!logId) return;
1628
+ const body = item.querySelector(".run-log-body");
1629
+ if (!body) return;
1630
+ if (hadPreviousItems) {
1631
+ if (openLogIds.has(logId)) {
1632
+ item.classList.add("open");
1633
+ const state = bodyScrollState.get(logId);
1634
+ if (state) {
1635
+ body.scrollTop = state.isNearBottom ? body.scrollHeight : state.scrollTop;
1636
+ } else {
1637
+ body.scrollTop = body.scrollHeight;
1638
+ }
1639
+ } else {
1640
+ item.classList.remove("open");
1641
+ }
1642
+ } else {
1643
+ if (item.classList.contains("open")) {
1644
+ body.scrollTop = body.scrollHeight;
1645
+ }
1646
+ }
1647
+ });
1648
+ });
1649
+ }
1650
+ async function loadRunLogs(taskId) {
1651
+ stopRunLogPolling();
1652
+ const seq = ++runLogLoadSeq;
1653
+ const pane = document.getElementById("detail-tab-content-run-logs");
1654
+ if (!pane) return;
1655
+ runLogsLoadedTaskId = taskId;
1656
+ if (pane.dataset.runLogsTaskId !== String(taskId)) {
1657
+ pane.dataset.runLogsTaskId = String(taskId);
1658
+ delete pane.dataset.runLogsSignature;
1659
+ }
1660
+ pane.removeEventListener("click", handleRunLogToggle);
1661
+ pane.addEventListener("click", handleRunLogToggle);
1662
+ try {
1663
+ const logs = await fetchRunLogs(taskId);
1664
+ if (seq !== runLogLoadSeq) return;
1665
+ renderRunLogsInPane(pane, logs);
1666
+ runLogPollingInterval = setInterval(() => {
1667
+ if (detailTaskId !== taskId) {
1668
+ stopRunLogPolling();
1669
+ return;
1670
+ }
1671
+ fetchRunLogs(taskId).then((updated) => {
1672
+ if (seq !== runLogLoadSeq) return;
1673
+ const p = document.getElementById("detail-tab-content-run-logs");
1674
+ if (p) renderRunLogsInPane(p, updated);
1675
+ }).catch(() => stopRunLogPolling());
1676
+ }, 2e3);
1677
+ } catch (err) {
1678
+ console.error("[agkan] loadRunLogs failed for task", taskId, err);
1679
+ if (seq !== runLogLoadSeq) return;
1680
+ runLogsLoadedTaskId = null;
1681
+ delete pane.dataset.runLogsSignature;
1682
+ pane.innerHTML = '<div style="padding:20px;font-size:12px;color:#94a3b8;">Failed to load run logs</div>';
1683
+ }
1684
+ }
1685
+ function handleRunLogToggle(e) {
1686
+ const target = e.target.closest('[data-action="toggle-run-log"]');
1687
+ if (!target) return;
1688
+ const item = target.closest(".run-log-item");
1689
+ if (item) item.classList.toggle("open");
1690
+ }
1149
1691
  function renderDetailPanel(data) {
1150
1692
  document.getElementById("detail-panel-update-warning")?.remove();
1151
1693
  const detailPanelTitle = document.getElementById("detail-panel-title");
@@ -1157,6 +1699,13 @@
1157
1699
  if (detailsPane) {
1158
1700
  detailsPane.innerHTML = renderDetailPanelHtml(data);
1159
1701
  detailsPane.style.padding = "20px";
1702
+ detailsPane.querySelectorAll(".detail-relation-link[data-task-id]").forEach((el) => {
1703
+ el.style.cursor = "pointer";
1704
+ el.addEventListener("click", () => {
1705
+ const tid = el.dataset.taskId;
1706
+ if (tid) void openTaskDetail(tid);
1707
+ });
1708
+ });
1160
1709
  }
1161
1710
  const footer = document.getElementById("detail-panel-footer");
1162
1711
  if (footer) {
@@ -1165,9 +1714,36 @@
1165
1714
  }
1166
1715
  const textarea = document.getElementById("detail-edit-body");
1167
1716
  if (textarea) {
1168
- requestAnimationFrame(() => {
1169
- autoResizeTextarea(textarea);
1170
- });
1717
+ const detailPanel = document.getElementById("detail-panel");
1718
+ if (detailPanel && !detailPanel.classList.contains("open")) {
1719
+ const prefersReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
1720
+ if (prefersReducedMotion) {
1721
+ autoResizeTextarea(textarea);
1722
+ } else {
1723
+ let done = false;
1724
+ const finish = () => {
1725
+ if (done) return;
1726
+ done = true;
1727
+ detailPanel.removeEventListener("transitionend", onTransitionEnd);
1728
+ requestAnimationFrame(() => {
1729
+ requestAnimationFrame(() => {
1730
+ autoResizeTextarea(textarea);
1731
+ });
1732
+ });
1733
+ };
1734
+ const onTransitionEnd = (e) => {
1735
+ if (e.propertyName === "width") finish();
1736
+ };
1737
+ detailPanel.addEventListener("transitionend", onTransitionEnd);
1738
+ setTimeout(finish, 260);
1739
+ }
1740
+ } else {
1741
+ requestAnimationFrame(() => {
1742
+ requestAnimationFrame(() => {
1743
+ autoResizeTextarea(textarea);
1744
+ });
1745
+ });
1746
+ }
1171
1747
  textarea.addEventListener("input", () => {
1172
1748
  autoResizeTextarea(textarea);
1173
1749
  });
@@ -1182,16 +1758,23 @@
1182
1758
  async function openTaskDetail(taskId) {
1183
1759
  const detailPanel = document.getElementById("detail-panel");
1184
1760
  const PANEL_DEFAULT_WIDTH2 = 400;
1761
+ setActiveCard(Number(taskId));
1762
+ if (currentFetchController) {
1763
+ currentFetchController.abort();
1764
+ }
1765
+ currentFetchController = new AbortController();
1766
+ const { signal } = currentFetchController;
1185
1767
  try {
1186
- const data = await fetchTaskDetail(taskId);
1768
+ const data = await fetchTaskDetail(taskId, signal);
1769
+ currentFetchController = null;
1187
1770
  renderDetailPanel(data);
1188
- setActiveCard(Number(taskId));
1189
1771
  if (!detailPanel.classList.contains("open")) {
1190
1772
  const preferredWidth = detailPanel.dataset.preferredWidth || String(PANEL_DEFAULT_WIDTH2);
1191
1773
  detailPanel.style.width = preferredWidth + "px";
1192
1774
  detailPanel.classList.add("open");
1193
1775
  }
1194
1776
  } catch (err) {
1777
+ if (err instanceof DOMException && err.name === "AbortError") return;
1195
1778
  console.error("[agkan] openTaskDetail failed for task", taskId, err);
1196
1779
  showToast("Failed to load task details");
1197
1780
  }
@@ -1216,7 +1799,7 @@
1216
1799
  const reloadBtn = document.createElement("button");
1217
1800
  reloadBtn.title = "Reload latest data";
1218
1801
  reloadBtn.textContent = "\u21BA";
1219
- reloadBtn.style.cssText = "background: none; border: none; cursor: pointer; font-size: 1.1em; color: red; padding: 0 2px; line-height: 1; flex-shrink: 0;";
1802
+ reloadBtn.style.cssText = "background: none; border: none; cursor: pointer; font-size: 1.5em; color: red; padding: 0 4px; line-height: 1; flex-shrink: 0;";
1220
1803
  reloadBtn.addEventListener("click", async () => {
1221
1804
  try {
1222
1805
  if (detailTaskId !== null) {
@@ -1303,6 +1886,12 @@
1303
1886
  boardContainer.insertAdjacentHTML("beforeend", buildDetailPanelHtml());
1304
1887
  const detailPanel = document.getElementById("detail-panel");
1305
1888
  document.getElementById("detail-panel-close")?.addEventListener("click", closeDetailPanel);
1889
+ document.getElementById("detail-panel-copy-id")?.addEventListener("click", () => {
1890
+ if (detailTaskId === null) return;
1891
+ navigator.clipboard.writeText(String(detailTaskId)).then(() => {
1892
+ showToast("Copied task ID: " + detailTaskId);
1893
+ });
1894
+ });
1306
1895
  document.addEventListener("keydown", (e) => {
1307
1896
  if (e.key === "Escape" && detailPanel.classList.contains("open")) {
1308
1897
  closeDetailPanel();
@@ -1325,6 +1914,7 @@
1325
1914
  renderDetailPanel,
1326
1915
  showUpdateWarning,
1327
1916
  getDetailTaskId,
1917
+ getDetailActiveTab,
1328
1918
  setActiveCard
1329
1919
  });
1330
1920
  registerGetDetailTaskId(getDetailTaskId);
@@ -1714,6 +2304,387 @@
1714
2304
  initDarkMode();
1715
2305
  }
1716
2306
 
2307
+ // src/board/client/dependencyVisualization.ts
2308
+ var isDependencyVisible = false;
2309
+ var arrowMarkers = /* @__PURE__ */ new Map();
2310
+ var scrollListener = null;
2311
+ var columnScrollListener = null;
2312
+ var resizeListener = null;
2313
+ function getOrCreateArrowMarker(svg, color) {
2314
+ const markerId = `arrow-${color.substring(1)}`;
2315
+ if (!arrowMarkers.has(markerId)) {
2316
+ const defs = document.createElementNS("http://www.w3.org/2000/svg", "defs");
2317
+ const marker = document.createElementNS("http://www.w3.org/2000/svg", "marker");
2318
+ marker.setAttribute("id", markerId);
2319
+ marker.setAttribute("markerWidth", "10");
2320
+ marker.setAttribute("markerHeight", "10");
2321
+ marker.setAttribute("refX", "8");
2322
+ marker.setAttribute("refY", "3");
2323
+ marker.setAttribute("orient", "auto");
2324
+ marker.setAttribute("markerUnits", "strokeWidth");
2325
+ const polygon = document.createElementNS("http://www.w3.org/2000/svg", "polygon");
2326
+ polygon.setAttribute("points", "0 0, 10 3, 0 6");
2327
+ polygon.setAttribute("fill", color);
2328
+ marker.appendChild(polygon);
2329
+ defs.appendChild(marker);
2330
+ svg.appendChild(defs);
2331
+ arrowMarkers.set(markerId, { svg });
2332
+ }
2333
+ return markerId;
2334
+ }
2335
+ function drawBezierLine(svg, x1, y1, x2, y2, color, isHovered) {
2336
+ const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
2337
+ const dx = Math.abs(x2 - x1);
2338
+ const isSameSide = dx < 10;
2339
+ let cp1x;
2340
+ let cp2x;
2341
+ if (isSameSide) {
2342
+ const cpOffset = 80;
2343
+ cp1x = x1 + cpOffset;
2344
+ cp2x = x2 + cpOffset;
2345
+ } else {
2346
+ const cpOffset = Math.max(dx * 0.5, 60);
2347
+ cp1x = x1 < x2 ? x1 + cpOffset : x1 - cpOffset;
2348
+ cp2x = x1 < x2 ? x2 - cpOffset : x2 + cpOffset;
2349
+ }
2350
+ const pathData = `M ${x1} ${y1} C ${cp1x} ${y1}, ${cp2x} ${y2}, ${x2} ${y2}`;
2351
+ path.setAttribute("d", pathData);
2352
+ path.setAttribute("stroke", color);
2353
+ path.setAttribute("stroke-width", isHovered ? "2.5" : "1.5");
2354
+ path.setAttribute("fill", "none");
2355
+ path.setAttribute("stroke-linecap", "round");
2356
+ path.setAttribute("marker-end", `url(#${getOrCreateArrowMarker(svg, color)})`);
2357
+ path.setAttribute("class", "dependency-line");
2358
+ return path;
2359
+ }
2360
+ function getCardRect(card) {
2361
+ if (card === draggedCard) {
2362
+ return getDraggedCardVirtualRect() ?? card.getBoundingClientRect();
2363
+ }
2364
+ return card.getBoundingClientRect();
2365
+ }
2366
+ function getCardEdgePoints(fromCard, toCard, boardRect) {
2367
+ const fromRect = getCardRect(fromCard);
2368
+ const toRect = getCardRect(toCard);
2369
+ const fromCenterX = fromRect.left + fromRect.width / 2;
2370
+ const toCenterX = toRect.left + toRect.width / 2;
2371
+ const columnThreshold = 50;
2372
+ let fromX;
2373
+ let toX;
2374
+ if (Math.abs(fromCenterX - toCenterX) < columnThreshold) {
2375
+ fromX = fromRect.right - boardRect.left;
2376
+ toX = toRect.right - boardRect.left;
2377
+ } else if (fromCenterX <= toCenterX) {
2378
+ fromX = fromRect.right - boardRect.left;
2379
+ toX = toRect.left - boardRect.left;
2380
+ } else {
2381
+ fromX = fromRect.left - boardRect.left;
2382
+ toX = toRect.right - boardRect.left;
2383
+ }
2384
+ return {
2385
+ x1: fromX,
2386
+ y1: fromRect.top - boardRect.top + fromRect.height / 2,
2387
+ x2: toX,
2388
+ y2: toRect.top - boardRect.top + toRect.height / 2
2389
+ };
2390
+ }
2391
+ function createSVGOverlay() {
2392
+ const boardContainer = document.querySelector(".board-container");
2393
+ const existing = boardContainer.querySelector("#dependency-svg");
2394
+ if (existing) {
2395
+ existing.remove();
2396
+ }
2397
+ arrowMarkers.clear();
2398
+ const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
2399
+ svg.setAttribute("id", "dependency-svg");
2400
+ svg.setAttribute("width", "100%");
2401
+ svg.setAttribute("height", "100%");
2402
+ svg.setAttribute("viewBox", `0 0 ${boardContainer.offsetWidth} ${boardContainer.offsetHeight}`);
2403
+ svg.style.position = "absolute";
2404
+ svg.style.top = "0";
2405
+ svg.style.left = "0";
2406
+ svg.style.pointerEvents = "none";
2407
+ svg.style.zIndex = "5";
2408
+ boardContainer.style.position = "relative";
2409
+ boardContainer.appendChild(svg);
2410
+ return svg;
2411
+ }
2412
+ function redrawDependencies() {
2413
+ if (!isDependencyVisible) return;
2414
+ const svg = createSVGOverlay();
2415
+ const boardContainer = document.querySelector(".board-container");
2416
+ svg.querySelectorAll(".dependency-line").forEach((line) => line.remove());
2417
+ const cards = Array.from(document.querySelectorAll(".card"));
2418
+ const cardMap = /* @__PURE__ */ new Map();
2419
+ cards.forEach((card) => {
2420
+ const id = Number(card.getAttribute("data-id"));
2421
+ cardMap.set(id, card);
2422
+ });
2423
+ const hoveredCard = document.querySelector(".card:hover");
2424
+ const hoveredCardId = hoveredCard ? Number(hoveredCard.getAttribute("data-id")) : null;
2425
+ const hoveredBlockedBySet = /* @__PURE__ */ new Set();
2426
+ const hoveredBlockingSet = /* @__PURE__ */ new Set();
2427
+ if (hoveredCardId) {
2428
+ const hoveredElement = cardMap.get(hoveredCardId);
2429
+ if (hoveredElement) {
2430
+ const blockedBy = hoveredElement.getAttribute("data-blocked-by");
2431
+ const blocking = hoveredElement.getAttribute("data-blocking");
2432
+ if (blockedBy) {
2433
+ blockedBy.split(",").forEach((id) => {
2434
+ const numId = Number(id.trim());
2435
+ if (!isNaN(numId)) hoveredBlockedBySet.add(numId);
2436
+ });
2437
+ }
2438
+ if (blocking) {
2439
+ blocking.split(",").forEach((id) => {
2440
+ const numId = Number(id.trim());
2441
+ if (!isNaN(numId)) hoveredBlockingSet.add(numId);
2442
+ });
2443
+ }
2444
+ }
2445
+ }
2446
+ const boardRect = boardContainer.getBoundingClientRect();
2447
+ cards.forEach((card) => {
2448
+ const cardId = Number(card.getAttribute("data-id"));
2449
+ const blockedByStr = card.getAttribute("data-blocked-by");
2450
+ const blockingStr = card.getAttribute("data-blocking");
2451
+ if (!blockedByStr && !blockingStr) return;
2452
+ const isHovered = cardId === hoveredCardId || hoveredBlockedBySet.has(cardId) || hoveredBlockingSet.has(cardId);
2453
+ if (blockingStr) {
2454
+ const blockingIds = blockingStr.split(",").map((s) => Number(s.trim()));
2455
+ blockingIds.forEach((blockedId) => {
2456
+ const blockedCard = cardMap.get(blockedId);
2457
+ if (blockedCard) {
2458
+ const { x1, y1, x2, y2 } = getCardEdgePoints(card, blockedCard, boardRect);
2459
+ const color = isHovered || hoveredBlockedBySet.has(blockedId) ? "#ef4444" : "#cbd5e1";
2460
+ const line = drawBezierLine(svg, x1, y1, x2, y2, color, isHovered);
2461
+ svg.appendChild(line);
2462
+ }
2463
+ });
2464
+ }
2465
+ });
2466
+ svg.setAttribute("viewBox", `0 0 ${boardContainer.offsetWidth} ${boardContainer.offsetHeight}`);
2467
+ }
2468
+ function handleCardHoverEvents() {
2469
+ const cards = document.querySelectorAll(".card");
2470
+ cards.forEach((card) => {
2471
+ card.addEventListener("mouseenter", () => {
2472
+ redrawDependencies();
2473
+ });
2474
+ card.addEventListener("mouseleave", () => {
2475
+ redrawDependencies();
2476
+ });
2477
+ });
2478
+ }
2479
+ function initDependencyVisualization() {
2480
+ const toggleBtn = document.getElementById("dependency-toggle");
2481
+ if (!toggleBtn) return;
2482
+ const redrawIfVisible = () => {
2483
+ if (isDependencyVisible) {
2484
+ handleCardHoverEvents();
2485
+ redrawDependencies();
2486
+ }
2487
+ };
2488
+ registerDependencyRedrawCallback2(redrawIfVisible);
2489
+ registerDependencyRedrawCallback(redrawIfVisible);
2490
+ toggleBtn.addEventListener("click", () => {
2491
+ isDependencyVisible = !isDependencyVisible;
2492
+ if (isDependencyVisible) {
2493
+ toggleBtn.classList.add("active");
2494
+ redrawDependencies();
2495
+ handleCardHoverEvents();
2496
+ const board = document.querySelector(".board");
2497
+ const boardContainer = document.querySelector(".board-container");
2498
+ if (board) {
2499
+ scrollListener = () => redrawDependencies();
2500
+ board.addEventListener("scroll", scrollListener, { passive: true });
2501
+ }
2502
+ columnScrollListener = () => redrawDependencies();
2503
+ document.querySelectorAll(".column-body").forEach((col) => {
2504
+ col.addEventListener("scroll", columnScrollListener, { passive: true });
2505
+ });
2506
+ if (boardContainer) {
2507
+ resizeListener = () => redrawDependencies();
2508
+ window.addEventListener("resize", resizeListener, { passive: true });
2509
+ }
2510
+ } else {
2511
+ toggleBtn.classList.remove("active");
2512
+ const svg = document.querySelector("#dependency-svg");
2513
+ if (svg) svg.remove();
2514
+ const board = document.querySelector(".board");
2515
+ if (board && scrollListener) {
2516
+ board.removeEventListener("scroll", scrollListener);
2517
+ scrollListener = null;
2518
+ }
2519
+ if (columnScrollListener) {
2520
+ document.querySelectorAll(".column-body").forEach((col) => {
2521
+ col.removeEventListener("scroll", columnScrollListener);
2522
+ });
2523
+ columnScrollListener = null;
2524
+ }
2525
+ if (resizeListener) {
2526
+ window.removeEventListener("resize", resizeListener);
2527
+ resizeListener = null;
2528
+ }
2529
+ }
2530
+ });
2531
+ }
2532
+
2533
+ // src/board/client/claudeStreamModal.ts
2534
+ var _currentEventSource = null;
2535
+ var _currentTaskId = null;
2536
+ var _claudeButtonUpdateCallback = null;
2537
+ function registerClaudeButtonUpdateCallback(cb) {
2538
+ _claudeButtonUpdateCallback = cb;
2539
+ }
2540
+ function stripAnsi(text) {
2541
+ return text.replace(/\x1b\[[0-9;]*m/g, "");
2542
+ }
2543
+ function getModalElements() {
2544
+ return {
2545
+ overlay: document.getElementById("claude-stream-modal"),
2546
+ title: document.getElementById("claude-stream-modal-title"),
2547
+ log: document.getElementById("claude-stream-log"),
2548
+ status: document.getElementById("claude-stream-status"),
2549
+ stopBtn: document.getElementById("claude-stream-stop-btn"),
2550
+ closeBtn: document.getElementById("claude-stream-close-btn"),
2551
+ modalClose: document.getElementById("claude-stream-modal-close")
2552
+ };
2553
+ }
2554
+ function isAutoScrollEnabled(log) {
2555
+ const threshold = 50;
2556
+ return log.scrollHeight - log.scrollTop - log.clientHeight <= threshold;
2557
+ }
2558
+ function appendToLog(log, text, className) {
2559
+ const shouldScroll = isAutoScrollEnabled(log);
2560
+ const line = document.createElement("div");
2561
+ line.textContent = text;
2562
+ if (className) {
2563
+ line.className = className;
2564
+ }
2565
+ log.appendChild(line);
2566
+ if (shouldScroll) {
2567
+ log.scrollTop = log.scrollHeight;
2568
+ }
2569
+ }
2570
+ function closeEventSource() {
2571
+ if (_currentEventSource) {
2572
+ _currentEventSource.close();
2573
+ _currentEventSource = null;
2574
+ }
2575
+ }
2576
+ function closeClaudeStreamModal() {
2577
+ closeEventSource();
2578
+ const { overlay } = getModalElements();
2579
+ overlay?.classList.remove("show");
2580
+ }
2581
+ function openClaudeStreamModal(taskId) {
2582
+ closeEventSource();
2583
+ _currentTaskId = taskId;
2584
+ const { overlay, title, log, status, stopBtn } = getModalElements();
2585
+ if (!overlay || !title || !log || !status || !stopBtn) return;
2586
+ title.textContent = `Claude Output #${taskId}`;
2587
+ log.innerHTML = "";
2588
+ status.textContent = "Connecting...";
2589
+ stopBtn.disabled = false;
2590
+ stopBtn.textContent = "Stop";
2591
+ overlay.classList.add("show");
2592
+ const es = new EventSource(`/api/claude/tasks/${taskId}/stream`);
2593
+ _currentEventSource = es;
2594
+ es.onopen = () => {
2595
+ if (_currentEventSource === es) {
2596
+ status.textContent = "Running";
2597
+ }
2598
+ };
2599
+ es.addEventListener("text", (event) => {
2600
+ const msgEvent = event;
2601
+ try {
2602
+ const data = JSON.parse(msgEvent.data);
2603
+ appendToLog(log, stripAnsi(data.text));
2604
+ } catch {
2605
+ }
2606
+ });
2607
+ es.addEventListener("tool_use", (event) => {
2608
+ const msgEvent = event;
2609
+ try {
2610
+ const data = JSON.parse(msgEvent.data);
2611
+ const mainArg = data.input?.path ?? data.input?.command ?? "";
2612
+ const displayText = mainArg ? `\u{1F527} ${data.name}: ${mainArg}` : `\u{1F527} ${data.name}`;
2613
+ appendToLog(log, displayText, "claude-stream-tool-use");
2614
+ } catch {
2615
+ }
2616
+ });
2617
+ es.addEventListener("end", (event) => {
2618
+ const msgEvent = event;
2619
+ try {
2620
+ const data = JSON.parse(msgEvent.data);
2621
+ status.textContent = `Done (exit ${data.exitCode})`;
2622
+ } catch {
2623
+ status.textContent = "Done";
2624
+ }
2625
+ closeEventSource();
2626
+ stopBtn.disabled = true;
2627
+ });
2628
+ es.addEventListener("error", (event) => {
2629
+ const msgEvent = event;
2630
+ try {
2631
+ const data = JSON.parse(msgEvent.data);
2632
+ status.textContent = `Error: ${data.message}`;
2633
+ } catch {
2634
+ status.textContent = "Error";
2635
+ }
2636
+ closeEventSource();
2637
+ stopBtn.disabled = true;
2638
+ });
2639
+ es.onerror = () => {
2640
+ if (_currentEventSource === es) {
2641
+ closeEventSource();
2642
+ status.textContent = "Disconnected";
2643
+ stopBtn.disabled = true;
2644
+ }
2645
+ };
2646
+ }
2647
+ async function handleStop() {
2648
+ if (_currentTaskId === null) return;
2649
+ const { stopBtn, status } = getModalElements();
2650
+ if (!stopBtn || !status) return;
2651
+ const taskId = _currentTaskId;
2652
+ closeEventSource();
2653
+ stopBtn.disabled = true;
2654
+ stopBtn.textContent = "Stopping...";
2655
+ try {
2656
+ await fetch(`/api/claude/tasks/${taskId}/run`, { method: "DELETE" });
2657
+ } catch {
2658
+ }
2659
+ stopBtn.textContent = "Stopped";
2660
+ status.textContent = "Stopped";
2661
+ if (_claudeButtonUpdateCallback) {
2662
+ _claudeButtonUpdateCallback();
2663
+ }
2664
+ }
2665
+ function initClaudeStreamModal() {
2666
+ const { overlay, stopBtn, closeBtn, modalClose } = getModalElements();
2667
+ modalClose?.addEventListener("click", () => {
2668
+ closeClaudeStreamModal();
2669
+ });
2670
+ closeBtn?.addEventListener("click", () => {
2671
+ closeClaudeStreamModal();
2672
+ });
2673
+ stopBtn?.addEventListener("click", () => {
2674
+ void handleStop();
2675
+ });
2676
+ overlay?.addEventListener("click", (e) => {
2677
+ if (e.target === overlay) {
2678
+ closeClaudeStreamModal();
2679
+ }
2680
+ });
2681
+ document.addEventListener("keydown", (e) => {
2682
+ if (e.key === "Escape" && overlay?.classList.contains("show")) {
2683
+ closeClaudeStreamModal();
2684
+ }
2685
+ });
2686
+ }
2687
+
1717
2688
  // src/board/client/main.ts
1718
2689
  initDragDrop();
1719
2690
  initAutoScroll();
@@ -1723,4 +2694,11 @@
1723
2694
  initBoardPolling();
1724
2695
  initFilters();
1725
2696
  initBurgerMenu();
2697
+ initDependencyVisualization();
2698
+ initClaudeButton();
2699
+ initClaudeStreamModal();
2700
+ registerClaudeModalCallback(openClaudeStreamModal);
2701
+ registerClaudeButtonUpdateCallback(() => {
2702
+ updateButtonStates(/* @__PURE__ */ new Set());
2703
+ });
1726
2704
  })();