agkan 3.11.0 → 3.12.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 (45) hide show
  1. package/README.ja.md +18 -0
  2. package/README.md +18 -0
  3. package/dist/board/BulkRunService.d.ts +3 -0
  4. package/dist/board/BulkRunService.d.ts.map +1 -1
  5. package/dist/board/BulkRunService.js +37 -29
  6. package/dist/board/BulkRunService.js.map +1 -1
  7. package/dist/board/boardConfig.d.ts.map +1 -1
  8. package/dist/board/boardConfig.js +27 -25
  9. package/dist/board/boardConfig.js.map +1 -1
  10. package/dist/board/boardRenderer.d.ts.map +1 -1
  11. package/dist/board/boardRenderer.js +40 -23
  12. package/dist/board/boardRenderer.js.map +1 -1
  13. package/dist/board/boardRoutes.d.ts.map +1 -1
  14. package/dist/board/boardRoutes.js +89 -54
  15. package/dist/board/boardRoutes.js.map +1 -1
  16. package/dist/cli/commands/agent-guide.d.ts.map +1 -1
  17. package/dist/cli/commands/agent-guide.js +2 -0
  18. package/dist/cli/commands/agent-guide.js.map +1 -1
  19. package/dist/cli/commands/context.d.ts +10 -0
  20. package/dist/cli/commands/context.d.ts.map +1 -0
  21. package/dist/cli/commands/context.js +45 -0
  22. package/dist/cli/commands/context.js.map +1 -0
  23. package/dist/cli/commands/init.d.ts.map +1 -1
  24. package/dist/cli/commands/init.js +9 -0
  25. package/dist/cli/commands/init.js.map +1 -1
  26. package/dist/cli/index.js +4 -0
  27. package/dist/cli/index.js.map +1 -1
  28. package/dist/cli/integrations/claudeSettings.d.ts +13 -0
  29. package/dist/cli/integrations/claudeSettings.d.ts.map +1 -0
  30. package/dist/cli/integrations/claudeSettings.js +115 -0
  31. package/dist/cli/integrations/claudeSettings.js.map +1 -0
  32. package/package.json +20 -30
  33. package/dist/board/client/board.js +0 -2743
  34. package/dist/cli/utils/response-formatter.d.ts +0 -19
  35. package/dist/cli/utils/response-formatter.d.ts.map +0 -1
  36. package/dist/cli/utils/response-formatter.js +0 -43
  37. package/dist/cli/utils/response-formatter.js.map +0 -1
  38. package/dist/models/Attachment.d.ts +0 -25
  39. package/dist/models/Attachment.d.ts.map +0 -1
  40. package/dist/models/Attachment.js +0 -7
  41. package/dist/models/Attachment.js.map +0 -1
  42. package/dist/services/AttachmentService.d.ts +0 -62
  43. package/dist/services/AttachmentService.d.ts.map +0 -1
  44. package/dist/services/AttachmentService.js +0 -95
  45. package/dist/services/AttachmentService.js.map +0 -1
@@ -1,2743 +0,0 @@
1
- "use strict";
2
- (() => {
3
- // src/board/client/utils.ts
4
- function escapeHtmlClient(str) {
5
- if (!str) return "";
6
- const div = document.createElement("div");
7
- div.textContent = String(str);
8
- return div.innerHTML;
9
- }
10
- function relativeTime(isoStr) {
11
- if (!isoStr) return "";
12
- const diff = Date.now() - new Date(isoStr).getTime();
13
- const sec = Math.floor(diff / 1e3);
14
- if (sec < 60) return "just now";
15
- const min = Math.floor(sec / 60);
16
- if (min < 60) return min + "m ago";
17
- const hr = Math.floor(min / 60);
18
- if (hr < 24) return hr + "h ago";
19
- const day = Math.floor(hr / 24);
20
- if (day < 30) return day + "d ago";
21
- const mo = Math.floor(day / 30);
22
- if (mo < 12) return mo + "mo ago";
23
- return Math.floor(mo / 12) + "y ago";
24
- }
25
- function showToast(msg) {
26
- const toast = document.getElementById("toast");
27
- if (!toast) return;
28
- if (msg) toast.textContent = msg;
29
- toast.classList.add("show");
30
- setTimeout(() => toast.classList.remove("show"), 3e3);
31
- }
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 = "&#128203; 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 = "&#9654; 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 = "&#9660;";
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 = "&#9654; 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 = "&#9654; 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
- function handleRunningTasksUpdate(tasks) {
217
- const allIds = new Set(tasks.map((t) => t.taskId));
218
- const planningIds = new Set(tasks.filter((t) => t.command === "planning").map((t) => t.taskId));
219
- updateButtonStates(allIds, planningIds);
220
- }
221
- function updateCardButton(card, newStatus) {
222
- const taskId = Number(card.dataset.id);
223
- if (_runningTaskIds.has(taskId)) return;
224
- const existingEl = card.querySelector(".claude-run-split, .claude-plan-btn, .claude-detail-btn");
225
- if (!existingEl) return;
226
- if (["review", "done", "closed"].includes(newStatus)) {
227
- existingEl.remove();
228
- } else if (["ready", "in_progress"].includes(newStatus)) {
229
- if (!existingEl.classList.contains("claude-run-split")) {
230
- const split = createRunSplitElement(taskId);
231
- existingEl.replaceWith(split);
232
- }
233
- } else {
234
- if (!existingEl.classList.contains("claude-plan-btn")) {
235
- const newBtn = document.createElement("button");
236
- newBtn.className = "claude-plan-btn";
237
- newBtn.dataset.taskId = String(taskId);
238
- newBtn.innerHTML = "&#128203; Planning";
239
- attachPlanBtnListener(newBtn);
240
- existingEl.replaceWith(newBtn);
241
- }
242
- }
243
- }
244
- function attachClaudeButtonListeners(body) {
245
- body.querySelectorAll(".claude-run-split").forEach((split) => {
246
- if (split.dataset.listenersAttached) return;
247
- const taskId = Number(split.dataset.taskId);
248
- const mainBtn = split.querySelector(".claude-run-btn");
249
- const toggleBtn = split.querySelector(".claude-run-toggle");
250
- const directItem = split.querySelector('[data-command="direct"]');
251
- const prItem = split.querySelector('[data-command="pr"]');
252
- if (mainBtn && toggleBtn && directItem && prItem) {
253
- attachRunSplitListeners(split, mainBtn, toggleBtn, directItem, prItem, taskId);
254
- }
255
- });
256
- body.querySelectorAll(".claude-plan-btn").forEach((btn) => {
257
- if (btn.dataset.listenersAttached) return;
258
- attachPlanBtnListener(btn);
259
- });
260
- body.querySelectorAll(".claude-detail-btn").forEach((btn) => {
261
- if (btn.dataset.listenersAttached) return;
262
- attachDetailBtnListener(btn);
263
- });
264
- updateButtonStates(_runningTaskIds, _planningTaskIds);
265
- }
266
- function initClaudeButton() {
267
- document.querySelectorAll(".column-body").forEach((body) => {
268
- attachClaudeButtonListeners(body);
269
- });
270
- document.addEventListener("click", () => {
271
- document.querySelectorAll(".claude-run-split.open").forEach((el) => {
272
- el.classList.remove("open");
273
- });
274
- });
275
- const es = new EventSource("/api/running-tasks/stream");
276
- es.addEventListener("update", (event) => {
277
- const data = JSON.parse(event.data);
278
- handleRunningTasksUpdate(data.tasks);
279
- });
280
- }
281
-
282
- // src/board/client/dragDrop.ts
283
- var _redrawDependencies = null;
284
- function registerDependencyRedrawCallback(callback) {
285
- _redrawDependencies = callback;
286
- }
287
- var draggedCard = null;
288
- var sourceBody = null;
289
- var isPendingStatusUpdate = false;
290
- var _dragMouseX = 0;
291
- var _dragMouseY = 0;
292
- var _dragOffsetX = 0;
293
- var _dragOffsetY = 0;
294
- function getDraggedCardVirtualRect() {
295
- if (!draggedCard) return null;
296
- const rect = draggedCard.getBoundingClientRect();
297
- const left = _dragMouseX - _dragOffsetX;
298
- const top = _dragMouseY - _dragOffsetY;
299
- return new DOMRect(left, top, rect.width, rect.height);
300
- }
301
- function updateCount(status) {
302
- const col = document.querySelector(`.column[data-status="${status}"]`);
303
- if (!col) return;
304
- const countEl = col.querySelector(".column-count");
305
- const bodyEl = col.querySelector(".column-body");
306
- if (countEl && bodyEl) {
307
- countEl.textContent = String(bodyEl.children.length);
308
- }
309
- }
310
- async function handleDrop(e, newStatus, colEl) {
311
- e.preventDefault();
312
- colEl.classList.remove("drag-over");
313
- if (!draggedCard) return;
314
- const taskId = draggedCard.dataset.id;
315
- const oldStatus = draggedCard.dataset.status;
316
- if (oldStatus === newStatus) return;
317
- const targetBody = document.getElementById("col-" + newStatus);
318
- const prevBody = sourceBody;
319
- targetBody.appendChild(draggedCard);
320
- draggedCard.dataset.status = newStatus;
321
- updateCount(oldStatus);
322
- updateCount(newStatus);
323
- updateCardButton(draggedCard, newStatus);
324
- isPendingStatusUpdate = true;
325
- try {
326
- const res = await fetch("/api/tasks/" + taskId, {
327
- method: "PATCH",
328
- headers: { "Content-Type": "application/json" },
329
- body: JSON.stringify({ status: newStatus })
330
- });
331
- if (!res.ok) throw new Error("Server error");
332
- if (_redrawDependencies) {
333
- _redrawDependencies();
334
- }
335
- } catch {
336
- if (prevBody && draggedCard) {
337
- prevBody.appendChild(draggedCard);
338
- draggedCard.dataset.status = oldStatus;
339
- updateCount(oldStatus);
340
- updateCount(newStatus);
341
- }
342
- showToast();
343
- } finally {
344
- isPendingStatusUpdate = false;
345
- }
346
- }
347
- var _documentDragOverListener = null;
348
- function attachDragListeners(card) {
349
- card.addEventListener("dragstart", (e) => {
350
- draggedCard = card;
351
- sourceBody = card.parentElement;
352
- card.classList.add("dragging");
353
- if (e.dataTransfer) e.dataTransfer.effectAllowed = "move";
354
- const rect = card.getBoundingClientRect();
355
- _dragOffsetX = e.clientX - rect.left;
356
- _dragOffsetY = e.clientY - rect.top;
357
- _dragMouseX = e.clientX;
358
- _dragMouseY = e.clientY;
359
- _documentDragOverListener = (ev) => {
360
- _dragMouseX = ev.clientX;
361
- _dragMouseY = ev.clientY;
362
- if (_redrawDependencies) _redrawDependencies();
363
- };
364
- document.addEventListener("dragover", _documentDragOverListener);
365
- });
366
- card.addEventListener("dragend", () => {
367
- card.classList.remove("dragging");
368
- draggedCard = null;
369
- sourceBody = null;
370
- if (_documentDragOverListener) {
371
- document.removeEventListener("dragover", _documentDragOverListener);
372
- _documentDragOverListener = null;
373
- }
374
- });
375
- }
376
- function initDragDrop() {
377
- document.querySelectorAll(".card").forEach((card) => {
378
- attachDragListeners(card);
379
- });
380
- document.querySelectorAll(".column").forEach((col) => {
381
- col.addEventListener("dragover", (e) => {
382
- e.preventDefault();
383
- col.classList.add("drag-over");
384
- });
385
- col.addEventListener("dragleave", () => col.classList.remove("drag-over"));
386
- col.addEventListener("drop", (e) => handleDrop(e, col.dataset.status, col));
387
- });
388
- }
389
-
390
- // src/board/client/autoScroll.ts
391
- var autoScrollRAF = null;
392
- var autoScrollBody = null;
393
- var autoScrollDir = 0;
394
- var AUTO_SCROLL_ZONE = 60;
395
- var AUTO_SCROLL_SPEED = 8;
396
- function stopAutoScroll() {
397
- if (autoScrollRAF !== null) {
398
- cancelAnimationFrame(autoScrollRAF);
399
- autoScrollRAF = null;
400
- }
401
- autoScrollBody = null;
402
- autoScrollDir = 0;
403
- }
404
- function startAutoScroll() {
405
- if (autoScrollRAF !== null) return;
406
- function step() {
407
- if (autoScrollBody && autoScrollDir !== 0) {
408
- autoScrollBody.scrollTop += autoScrollDir * AUTO_SCROLL_SPEED;
409
- autoScrollRAF = requestAnimationFrame(step);
410
- } else {
411
- autoScrollRAF = null;
412
- }
413
- }
414
- autoScrollRAF = requestAnimationFrame(step);
415
- }
416
- function attachAutoScrollToBody(body) {
417
- body.addEventListener("dragover", (e) => {
418
- const rect = body.getBoundingClientRect();
419
- const y = e.clientY - rect.top;
420
- if (y < AUTO_SCROLL_ZONE) {
421
- autoScrollBody = body;
422
- autoScrollDir = -1;
423
- startAutoScroll();
424
- } else if (y > rect.height - AUTO_SCROLL_ZONE) {
425
- autoScrollBody = body;
426
- autoScrollDir = 1;
427
- startAutoScroll();
428
- } else {
429
- stopAutoScroll();
430
- }
431
- });
432
- body.addEventListener("dragleave", stopAutoScroll);
433
- body.addEventListener("drop", stopAutoScroll);
434
- }
435
- function initAutoScroll() {
436
- document.querySelectorAll(".column-body").forEach(attachAutoScrollToBody);
437
- document.addEventListener("dragend", stopAutoScroll);
438
- }
439
-
440
- // src/board/client/tags.ts
441
- var allAvailableTags = [];
442
- var _getDetailTaskId = null;
443
- function registerGetDetailTaskId(fn) {
444
- _getDetailTaskId = fn;
445
- }
446
- async function loadAllTags() {
447
- try {
448
- const res = await fetch("/api/tags");
449
- if (!res.ok) return;
450
- const data = await res.json();
451
- allAvailableTags = data.tags || [];
452
- } catch {
453
- }
454
- }
455
- function renderTagsSection(currentTags) {
456
- const container = document.getElementById("detail-tags-container");
457
- if (!container) return;
458
- container.innerHTML = '<div class="tag-select-wrapper"><div class="tag-select-control" id="tag-select-control"></div><div class="tag-select-dropdown" id="tag-select-dropdown"></div></div>';
459
- const control = document.getElementById("tag-select-control");
460
- const dropdown = document.getElementById("tag-select-dropdown");
461
- if (!control || !dropdown) return;
462
- let focusedOptionIndex = -1;
463
- let inputValue = "";
464
- function getFilteredTags() {
465
- const currentTagIds = new Set(currentTags.map((t) => t.id));
466
- const available = allAvailableTags.filter((t) => !currentTagIds.has(t.id));
467
- if (!inputValue.trim()) return available;
468
- const q = inputValue.toLowerCase();
469
- return available.filter((t) => t.name.toLowerCase().includes(q));
470
- }
471
- const input = document.createElement("input");
472
- input.className = "tag-select-input";
473
- input.type = "text";
474
- input.autocomplete = "off";
475
- control.appendChild(input);
476
- function renderPills() {
477
- control.querySelectorAll(".tag-pill").forEach((p) => p.remove());
478
- currentTags.forEach((t) => {
479
- const pill = document.createElement("span");
480
- pill.className = "tag-pill";
481
- pill.dataset.tagId = String(t.id);
482
- const label = document.createTextNode(t.name);
483
- const removeBtn = document.createElement("button");
484
- removeBtn.className = "tag-pill-remove";
485
- removeBtn.title = "Remove tag";
486
- removeBtn.setAttribute("data-tag-id", String(t.id));
487
- removeBtn.innerHTML = "&times;";
488
- removeBtn.addEventListener("click", async (e) => {
489
- e.stopPropagation();
490
- const detailTaskId2 = _getDetailTaskId ? _getDetailTaskId() : null;
491
- try {
492
- const res = await fetch("/api/tasks/" + detailTaskId2 + "/tags/" + t.id, {
493
- method: "DELETE"
494
- });
495
- if (!res.ok) throw new Error("Server error");
496
- const idx = currentTags.findIndex((x) => String(x.id) === String(t.id));
497
- if (idx !== -1) currentTags.splice(idx, 1);
498
- renderPills();
499
- renderDropdown();
500
- } catch {
501
- showToast("Failed to remove tag");
502
- }
503
- });
504
- pill.appendChild(label);
505
- pill.appendChild(removeBtn);
506
- control.insertBefore(pill, input);
507
- });
508
- input.placeholder = currentTags.length === 0 ? "Add tags..." : "";
509
- }
510
- async function createAndAddTag(name) {
511
- const detailTaskId2 = _getDetailTaskId ? _getDetailTaskId() : null;
512
- try {
513
- const createRes = await fetch("/api/tags", {
514
- method: "POST",
515
- headers: { "Content-Type": "application/json" },
516
- body: JSON.stringify({ name })
517
- });
518
- if (!createRes.ok) throw new Error("Server error");
519
- const newTag = await createRes.json();
520
- allAvailableTags.push(newTag);
521
- const taskRes = await fetch("/api/tasks/" + detailTaskId2 + "/tags", {
522
- method: "POST",
523
- headers: { "Content-Type": "application/json" },
524
- body: JSON.stringify({ tagId: newTag.id })
525
- });
526
- if (!taskRes.ok) throw new Error("Server error");
527
- currentTags.push(newTag);
528
- input.value = "";
529
- inputValue = "";
530
- renderPills();
531
- renderDropdown();
532
- } catch {
533
- showToast("Failed to create tag");
534
- }
535
- }
536
- function renderDropdown() {
537
- const filtered = getFilteredTags();
538
- dropdown.innerHTML = "";
539
- focusedOptionIndex = -1;
540
- const hasInput = inputValue.trim() !== "";
541
- const exactMatch = hasInput && allAvailableTags.some((t) => t.name.toLowerCase() === inputValue.trim().toLowerCase());
542
- const showCreate = hasInput && !exactMatch;
543
- if (filtered.length === 0 && !showCreate) {
544
- const noOpt = document.createElement("div");
545
- noOpt.className = "tag-select-no-options";
546
- noOpt.textContent = hasInput ? "No matching tags" : "No tags available";
547
- dropdown.appendChild(noOpt);
548
- } else {
549
- filtered.forEach((t, i) => {
550
- const opt = document.createElement("div");
551
- opt.className = "tag-select-option";
552
- opt.dataset.tagId = String(t.id);
553
- opt.textContent = t.name;
554
- opt.addEventListener("mouseover", () => setFocusedOption(i));
555
- opt.addEventListener("mousedown", async (e) => {
556
- e.preventDefault();
557
- await addTag(String(t.id));
558
- });
559
- dropdown.appendChild(opt);
560
- });
561
- if (showCreate) {
562
- const createOpt = document.createElement("div");
563
- createOpt.className = "tag-select-option tag-select-create-option";
564
- createOpt.dataset.create = "true";
565
- createOpt.textContent = `Create "${inputValue.trim()}"`;
566
- createOpt.addEventListener("mouseover", () => setFocusedOption(filtered.length));
567
- createOpt.addEventListener("mousedown", async (e) => {
568
- e.preventDefault();
569
- await createAndAddTag(inputValue.trim());
570
- });
571
- dropdown.appendChild(createOpt);
572
- }
573
- }
574
- }
575
- function setFocusedOption(index) {
576
- const opts = dropdown.querySelectorAll(".tag-select-option");
577
- opts.forEach((o, i) => o.classList.toggle("focused", i === index));
578
- focusedOptionIndex = index;
579
- }
580
- function openDropdown() {
581
- renderDropdown();
582
- dropdown.classList.add("open");
583
- }
584
- function closeDropdown() {
585
- dropdown.classList.remove("open");
586
- focusedOptionIndex = -1;
587
- }
588
- async function addTag(tagId) {
589
- const detailTaskId2 = _getDetailTaskId ? _getDetailTaskId() : null;
590
- try {
591
- const res = await fetch("/api/tasks/" + detailTaskId2 + "/tags", {
592
- method: "POST",
593
- headers: { "Content-Type": "application/json" },
594
- body: JSON.stringify({ tagId: Number(tagId) })
595
- });
596
- if (!res.ok) throw new Error("Server error");
597
- const tag = allAvailableTags.find((t) => String(t.id) === String(tagId));
598
- if (tag) currentTags.push(tag);
599
- input.value = "";
600
- inputValue = "";
601
- renderPills();
602
- renderDropdown();
603
- } catch {
604
- showToast("Failed to add tag");
605
- }
606
- }
607
- control.addEventListener("click", () => input.focus());
608
- input.addEventListener("focus", () => openDropdown());
609
- input.addEventListener("blur", () => setTimeout(() => closeDropdown(), 150));
610
- input.addEventListener("input", () => {
611
- inputValue = input.value;
612
- renderDropdown();
613
- if (!dropdown.classList.contains("open")) openDropdown();
614
- });
615
- input.addEventListener("keydown", async (e) => {
616
- const filtered = getFilteredTags();
617
- const opts = dropdown.querySelectorAll(".tag-select-option");
618
- if (e.key === "ArrowDown") {
619
- e.preventDefault();
620
- setFocusedOption(Math.min(focusedOptionIndex + 1, opts.length - 1));
621
- } else if (e.key === "ArrowUp") {
622
- e.preventDefault();
623
- setFocusedOption(Math.max(focusedOptionIndex - 1, 0));
624
- } else if (e.key === "Enter") {
625
- e.preventDefault();
626
- if (focusedOptionIndex >= 0 && filtered[focusedOptionIndex]) {
627
- await addTag(String(filtered[focusedOptionIndex].id));
628
- } else if (focusedOptionIndex >= 0 && inputValue.trim()) {
629
- await createAndAddTag(inputValue.trim());
630
- }
631
- } else if (e.key === "Escape") {
632
- closeDropdown();
633
- input.blur();
634
- } else if (e.key === "Backspace" && input.value === "" && currentTags.length > 0) {
635
- e.preventDefault();
636
- const last = currentTags[currentTags.length - 1];
637
- const detailTaskId2 = _getDetailTaskId ? _getDetailTaskId() : null;
638
- try {
639
- const res = await fetch("/api/tasks/" + detailTaskId2 + "/tags/" + last.id, {
640
- method: "DELETE"
641
- });
642
- if (!res.ok) throw new Error("Server error");
643
- currentTags.splice(currentTags.length - 1, 1);
644
- renderPills();
645
- renderDropdown();
646
- } catch {
647
- showToast("Failed to remove tag");
648
- }
649
- }
650
- });
651
- renderPills();
652
- }
653
-
654
- // src/board/client/boardPolling.ts
655
- var activeFilters = { tagIds: [], priorities: [], assignee: "", searchText: "" };
656
- function buildFilterParams() {
657
- const params = new URLSearchParams();
658
- if (activeFilters.priorities.length > 0) {
659
- params.set("priority", activeFilters.priorities.join(","));
660
- }
661
- if (activeFilters.tagIds.length > 0) {
662
- params.set("tags", activeFilters.tagIds.join(","));
663
- }
664
- if (activeFilters.assignee) {
665
- params.set("assignee", activeFilters.assignee);
666
- }
667
- if (activeFilters.searchText) {
668
- params.set("search", activeFilters.searchText);
669
- }
670
- return params;
671
- }
672
- var _openTaskDetail = null;
673
- var _renderDetailPanel = null;
674
- var _showUpdateWarning = null;
675
- var _getDetailTaskId2 = null;
676
- var _getDetailActiveTab = null;
677
- var _setActiveCard = null;
678
- var _redrawDependencies2 = null;
679
- function registerDetailPanelCallbacks(callbacks) {
680
- _openTaskDetail = callbacks.openTaskDetail;
681
- _renderDetailPanel = callbacks.renderDetailPanel;
682
- _showUpdateWarning = callbacks.showUpdateWarning;
683
- _getDetailTaskId2 = callbacks.getDetailTaskId;
684
- _getDetailActiveTab = callbacks.getDetailActiveTab;
685
- _setActiveCard = callbacks.setActiveCard;
686
- }
687
- function registerDependencyRedrawCallback2(callback) {
688
- _redrawDependencies2 = callback;
689
- }
690
- function attachCardListeners(body) {
691
- body.querySelectorAll(".card").forEach((card) => {
692
- attachDragListeners(card);
693
- if (card.dataset.listenersAttached) return;
694
- card.dataset.listenersAttached = "1";
695
- card.addEventListener("click", async (e) => {
696
- if (e.defaultPrevented) return;
697
- if (_openTaskDetail) await _openTaskDetail(card.dataset.id);
698
- });
699
- });
700
- }
701
- function applyIncrementalCardUpdate(body, newHtml) {
702
- const template = document.createElement("div");
703
- template.innerHTML = newHtml;
704
- const newCards = Array.from(template.querySelectorAll(".card"));
705
- const existingCards = /* @__PURE__ */ new Map();
706
- body.querySelectorAll(".card").forEach((card) => {
707
- const id = card.dataset.id;
708
- if (id) existingCards.set(id, card);
709
- });
710
- const newCardIds = /* @__PURE__ */ new Set();
711
- newCards.forEach((card) => {
712
- const id = card.dataset.id;
713
- if (id) newCardIds.add(id);
714
- });
715
- existingCards.forEach((card, id) => {
716
- if (!newCardIds.has(id)) {
717
- card.remove();
718
- }
719
- });
720
- newCards.forEach((newCard, index) => {
721
- const id = newCard.dataset.id;
722
- const newUpdatedAt = newCard.dataset.updatedAt;
723
- const newTagIds = newCard.dataset.tagIds ?? "";
724
- const newBlockedBy = newCard.dataset.blockedBy ?? "";
725
- const newBlocking = newCard.dataset.blocking ?? "";
726
- const existing = id ? existingCards.get(id) : void 0;
727
- if (existing) {
728
- const existingUpdatedAt = existing.dataset.updatedAt;
729
- const existingTagIds = existing.dataset.tagIds ?? "";
730
- const existingBlockedBy = existing.dataset.blockedBy ?? "";
731
- const existingBlocking = existing.dataset.blocking ?? "";
732
- const tagsChanged = newTagIds !== existingTagIds;
733
- const depsChanged = newBlockedBy !== existingBlockedBy || newBlocking !== existingBlocking;
734
- let activeCard;
735
- if (newUpdatedAt !== existingUpdatedAt || tagsChanged || depsChanged) {
736
- existing.replaceWith(newCard);
737
- activeCard = newCard;
738
- } else {
739
- activeCard = existing;
740
- }
741
- const currentChild = body.children[index];
742
- if (currentChild !== activeCard) {
743
- body.insertBefore(activeCard, currentChild || null);
744
- }
745
- } else {
746
- const currentChild = body.children[index];
747
- body.insertBefore(newCard, currentChild || null);
748
- }
749
- });
750
- }
751
- function updateColumnHtml(col) {
752
- const body = document.getElementById("col-" + col.status);
753
- if (!body) return;
754
- applyIncrementalCardUpdate(body, col.html);
755
- const colEl = body.closest(".column");
756
- if (colEl) {
757
- const countEl = colEl.querySelector(".column-count");
758
- if (countEl) countEl.textContent = String(col.count);
759
- }
760
- attachCardListeners(body);
761
- attachAutoScrollToBody(body);
762
- attachClaudeButtonListeners(body);
763
- updateButtonStates(getRunningTaskIds());
764
- }
765
- function isEditingDetailPanel() {
766
- const editableFields = ["detail-edit-title", "detail-edit-body", "detail-edit-status", "detail-edit-priority"];
767
- return editableFields.some((id) => document.activeElement && document.activeElement.id === id);
768
- }
769
- async function refreshOpenDetailPanel(detailTaskId2) {
770
- if (isEditingDetailPanel()) {
771
- if (_showUpdateWarning) _showUpdateWarning();
772
- return;
773
- }
774
- if (_getDetailActiveTab && _getDetailActiveTab() === "run-logs") {
775
- return;
776
- }
777
- try {
778
- const taskRes = await fetch("/api/tasks/" + detailTaskId2);
779
- if (taskRes.ok) {
780
- const taskData = await taskRes.json();
781
- if (_renderDetailPanel) _renderDetailPanel(taskData);
782
- }
783
- } catch {
784
- }
785
- }
786
- async function refreshBoardCards() {
787
- const filterParams = buildFilterParams();
788
- const url = "/api/board/cards" + (filterParams.toString() ? "?" + filterParams.toString() : "");
789
- try {
790
- const res = await fetch(url);
791
- if (!res.ok) return;
792
- const data = await res.json();
793
- data.columns.forEach(updateColumnHtml);
794
- const detailTaskId2 = _getDetailTaskId2 ? _getDetailTaskId2() : null;
795
- if (detailTaskId2 !== null && _setActiveCard) {
796
- _setActiveCard(detailTaskId2);
797
- }
798
- if (_redrawDependencies2) {
799
- _redrawDependencies2();
800
- }
801
- if (detailTaskId2 !== null) {
802
- await refreshOpenDetailPanel(detailTaskId2);
803
- }
804
- } catch {
805
- }
806
- }
807
- function initBoardPolling() {
808
- const es = new EventSource("/api/board/stream");
809
- es.addEventListener("update", () => {
810
- if (draggedCard !== null || isPendingStatusUpdate) return;
811
- refreshBoardCards();
812
- });
813
- }
814
-
815
- // src/board/client/addTaskModal.ts
816
- var selectedTags = [];
817
- var tagInputValue = "";
818
- var tagFocusedIndex = -1;
819
- function getFilteredAddTags() {
820
- const selectedIds = new Set(selectedTags.map((t) => t.id));
821
- const available = allAvailableTags.filter((t) => !selectedIds.has(t.id));
822
- if (!tagInputValue.trim()) return available;
823
- const q = tagInputValue.toLowerCase();
824
- return available.filter((t) => t.name.toLowerCase().includes(q));
825
- }
826
- function renderAddTagPills(control, input) {
827
- control.querySelectorAll(".tag-pill").forEach((p) => p.remove());
828
- selectedTags.forEach((t) => {
829
- const pill = document.createElement("span");
830
- pill.className = "tag-pill";
831
- pill.dataset.tagId = String(t.id);
832
- const label = document.createTextNode(t.name);
833
- const removeBtn = document.createElement("button");
834
- removeBtn.className = "tag-pill-remove";
835
- removeBtn.title = "Remove tag";
836
- removeBtn.innerHTML = "&times;";
837
- removeBtn.addEventListener("click", (e) => {
838
- e.stopPropagation();
839
- const idx = selectedTags.findIndex((x) => x.id === t.id);
840
- if (idx !== -1) selectedTags.splice(idx, 1);
841
- renderAddTagPills(control, input);
842
- });
843
- pill.appendChild(label);
844
- pill.appendChild(removeBtn);
845
- control.insertBefore(pill, input);
846
- });
847
- input.placeholder = selectedTags.length === 0 ? "Add tags..." : "";
848
- }
849
- function renderAddTagDropdown(dropdown) {
850
- const filtered = getFilteredAddTags();
851
- dropdown.innerHTML = "";
852
- tagFocusedIndex = -1;
853
- const hasInput = tagInputValue.trim() !== "";
854
- const exactMatch = hasInput && allAvailableTags.some((t) => t.name.toLowerCase() === tagInputValue.trim().toLowerCase());
855
- const showCreate = hasInput && !exactMatch;
856
- if (filtered.length === 0 && !showCreate) {
857
- const noOpt = document.createElement("div");
858
- noOpt.className = "tag-select-no-options";
859
- noOpt.textContent = hasInput ? "No matching tags" : "No tags available";
860
- dropdown.appendChild(noOpt);
861
- } else {
862
- filtered.forEach((t, i) => {
863
- const opt = document.createElement("div");
864
- opt.className = "tag-select-option";
865
- opt.dataset.tagId = String(t.id);
866
- opt.textContent = t.name;
867
- opt.addEventListener("mouseover", () => setAddTagFocused(dropdown, i));
868
- opt.addEventListener("mousedown", (e) => {
869
- e.preventDefault();
870
- selectAddTag(t.id, dropdown, document.getElementById("add-tag-input"));
871
- });
872
- dropdown.appendChild(opt);
873
- });
874
- if (showCreate) {
875
- const createOpt = document.createElement("div");
876
- createOpt.className = "tag-select-option tag-select-create-option";
877
- createOpt.dataset.create = "true";
878
- createOpt.textContent = `Create "${tagInputValue.trim()}"`;
879
- createOpt.addEventListener("mouseover", () => setAddTagFocused(dropdown, filtered.length));
880
- createOpt.addEventListener("mousedown", async (e) => {
881
- e.preventDefault();
882
- const input = document.getElementById("add-tag-input");
883
- await createAndSelectAddTag(tagInputValue.trim(), dropdown, input);
884
- });
885
- dropdown.appendChild(createOpt);
886
- }
887
- }
888
- }
889
- function setAddTagFocused(dropdown, index) {
890
- const opts = dropdown.querySelectorAll(".tag-select-option");
891
- opts.forEach((o, i) => o.classList.toggle("focused", i === index));
892
- tagFocusedIndex = index;
893
- }
894
- function selectAddTag(tagId, dropdown, input) {
895
- const tag = allAvailableTags.find((t) => t.id === tagId);
896
- if (!tag) return;
897
- selectedTags.push(tag);
898
- input.value = "";
899
- tagInputValue = "";
900
- const control = document.getElementById("add-tag-select-control");
901
- renderAddTagPills(control, input);
902
- renderAddTagDropdown(dropdown);
903
- }
904
- async function createAndSelectAddTag(name, dropdown, input) {
905
- try {
906
- const res = await fetch("/api/tags", {
907
- method: "POST",
908
- headers: { "Content-Type": "application/json" },
909
- body: JSON.stringify({ name })
910
- });
911
- if (!res.ok) throw new Error("Server error");
912
- const newTag = await res.json();
913
- allAvailableTags.push(newTag);
914
- selectAddTag(newTag.id, dropdown, input);
915
- } catch {
916
- showToast("Failed to create tag");
917
- }
918
- }
919
- function initAddTagSelector() {
920
- const control = document.getElementById("add-tag-select-control");
921
- const dropdown = document.getElementById("add-tag-select-dropdown");
922
- if (!control || !dropdown) return;
923
- const input = document.createElement("input");
924
- input.className = "tag-select-input";
925
- input.id = "add-tag-input";
926
- input.type = "text";
927
- input.autocomplete = "off";
928
- control.appendChild(input);
929
- control.addEventListener("click", () => input.focus());
930
- input.addEventListener("focus", () => {
931
- renderAddTagDropdown(dropdown);
932
- dropdown.classList.add("open");
933
- });
934
- input.addEventListener(
935
- "blur",
936
- () => setTimeout(() => {
937
- dropdown.classList.remove("open");
938
- tagFocusedIndex = -1;
939
- }, 150)
940
- );
941
- input.addEventListener("input", () => {
942
- tagInputValue = input.value;
943
- renderAddTagDropdown(dropdown);
944
- if (!dropdown.classList.contains("open")) dropdown.classList.add("open");
945
- });
946
- input.addEventListener("keydown", async (e) => {
947
- const filtered = getFilteredAddTags();
948
- const opts = dropdown.querySelectorAll(".tag-select-option");
949
- if (e.key === "ArrowDown") {
950
- e.preventDefault();
951
- setAddTagFocused(dropdown, Math.min(tagFocusedIndex + 1, opts.length - 1));
952
- } else if (e.key === "ArrowUp") {
953
- e.preventDefault();
954
- setAddTagFocused(dropdown, Math.max(tagFocusedIndex - 1, 0));
955
- } else if (e.key === "Enter") {
956
- e.preventDefault();
957
- if (tagFocusedIndex >= 0 && filtered[tagFocusedIndex]) {
958
- selectAddTag(filtered[tagFocusedIndex].id, dropdown, input);
959
- } else if (tagFocusedIndex >= 0 && tagInputValue.trim()) {
960
- await createAndSelectAddTag(tagInputValue.trim(), dropdown, input);
961
- }
962
- } else if (e.key === "Escape") {
963
- dropdown.classList.remove("open");
964
- input.blur();
965
- } else if (e.key === "Backspace" && input.value === "" && selectedTags.length > 0) {
966
- e.preventDefault();
967
- selectedTags.splice(selectedTags.length - 1, 1);
968
- renderAddTagPills(control, input);
969
- renderAddTagDropdown(dropdown);
970
- }
971
- });
972
- }
973
- function addMetadataRow(container) {
974
- const row = document.createElement("div");
975
- row.className = "metadata-row";
976
- const keyInput = document.createElement("input");
977
- keyInput.type = "text";
978
- keyInput.className = "metadata-row-key";
979
- keyInput.placeholder = "Key";
980
- const valueInput = document.createElement("input");
981
- valueInput.type = "text";
982
- valueInput.className = "metadata-row-value";
983
- valueInput.placeholder = "Value";
984
- const removeBtn = document.createElement("button");
985
- removeBtn.type = "button";
986
- removeBtn.className = "metadata-row-remove";
987
- removeBtn.title = "Remove";
988
- removeBtn.innerHTML = "&times;";
989
- removeBtn.addEventListener("click", () => {
990
- row.remove();
991
- });
992
- row.appendChild(keyInput);
993
- row.appendChild(valueInput);
994
- row.appendChild(removeBtn);
995
- container.appendChild(row);
996
- }
997
- function collectMetadata(container) {
998
- const rows = container.querySelectorAll(".metadata-row");
999
- const result = [];
1000
- rows.forEach((row) => {
1001
- const key = (row.querySelector(".metadata-row-key")?.value ?? "").trim();
1002
- const value = (row.querySelector(".metadata-row-value")?.value ?? "").trim();
1003
- if (key) result.push({ key, value });
1004
- });
1005
- return result;
1006
- }
1007
- function resetAddModal(elements) {
1008
- elements.addTitle.value = "";
1009
- elements.addBody.value = "";
1010
- elements.addPriority.value = "medium";
1011
- selectedTags = [];
1012
- tagInputValue = "";
1013
- tagFocusedIndex = -1;
1014
- const control = document.getElementById("add-tag-select-control");
1015
- const input = document.getElementById("add-tag-input");
1016
- if (control && input) {
1017
- renderAddTagPills(control, input);
1018
- }
1019
- elements.addMetadataRows.innerHTML = "";
1020
- }
1021
- function openAddModal(elements, status) {
1022
- elements.addStatus.value = status;
1023
- resetAddModal(elements);
1024
- elements.addModal.classList.add("show");
1025
- elements.addTitle.focus();
1026
- }
1027
- async function submitAddTask(elements) {
1028
- const title = elements.addTitle.value.trim();
1029
- if (!title) {
1030
- elements.addTitle.focus();
1031
- return;
1032
- }
1033
- const status = elements.addStatus.value;
1034
- elements.addModal.classList.remove("show");
1035
- const tags = selectedTags.map((t) => t.id);
1036
- const metadata = collectMetadata(elements.addMetadataRows);
1037
- try {
1038
- const res = await fetch("/api/tasks", {
1039
- method: "POST",
1040
- headers: { "Content-Type": "application/json" },
1041
- body: JSON.stringify({
1042
- title,
1043
- body: elements.addBody.value.trim() || null,
1044
- status,
1045
- priority: elements.addPriority.value || null,
1046
- tags: tags.length > 0 ? tags : void 0,
1047
- metadata: metadata.length > 0 ? metadata : void 0
1048
- })
1049
- });
1050
- if (!res.ok) throw new Error("Server error");
1051
- await refreshBoardCards();
1052
- } catch {
1053
- showToast("Failed to add task");
1054
- }
1055
- }
1056
- function initAddTaskModal() {
1057
- const elements = {
1058
- addModal: document.getElementById("add-modal"),
1059
- addTitle: document.getElementById("add-title"),
1060
- addBody: document.getElementById("add-body"),
1061
- addPriority: document.getElementById("add-priority"),
1062
- addStatus: document.getElementById("add-status"),
1063
- addTagControl: document.getElementById("add-tag-select-control"),
1064
- addTagDropdown: document.getElementById("add-tag-select-dropdown"),
1065
- addMetadataRows: document.getElementById("add-metadata-rows")
1066
- };
1067
- initAddTagSelector();
1068
- document.querySelectorAll(".add-btn").forEach((btn) => {
1069
- btn.addEventListener("click", (e) => {
1070
- e.stopPropagation();
1071
- openAddModal(elements, btn.dataset.status);
1072
- });
1073
- });
1074
- document.getElementById("add-cancel")?.addEventListener("click", () => {
1075
- elements.addModal.classList.remove("show");
1076
- });
1077
- elements.addModal.addEventListener("click", (e) => {
1078
- if (e.target === elements.addModal) elements.addModal.classList.remove("show");
1079
- });
1080
- elements.addTitle.addEventListener("keydown", (e) => {
1081
- if (e.key === "Enter" && !e.isComposing) {
1082
- e.preventDefault();
1083
- document.getElementById("add-submit").click();
1084
- }
1085
- });
1086
- document.getElementById("add-metadata-add-row")?.addEventListener("click", () => {
1087
- addMetadataRow(elements.addMetadataRows);
1088
- });
1089
- document.getElementById("add-submit")?.addEventListener("click", () => submitAddTask(elements));
1090
- }
1091
-
1092
- // src/board/client/contextMenu.ts
1093
- async function deleteCard(card) {
1094
- const taskId = card.dataset.id;
1095
- const status = card.dataset.status;
1096
- if (!confirm("Delete task #" + taskId + "?")) return;
1097
- card.remove();
1098
- updateCount(status);
1099
- try {
1100
- const res = await fetch("/api/tasks/" + taskId, { method: "DELETE" });
1101
- if (!res.ok) throw new Error("Server error");
1102
- } catch {
1103
- location.reload();
1104
- showToast("Failed to delete task");
1105
- }
1106
- }
1107
- function initContextMenu() {
1108
- const ctxMenu = document.getElementById("context-menu");
1109
- let ctxTargetCard = null;
1110
- document.addEventListener("contextmenu", (e) => {
1111
- const card = e.target.closest(".card");
1112
- if (!card) {
1113
- ctxMenu.style.display = "none";
1114
- return;
1115
- }
1116
- e.preventDefault();
1117
- ctxTargetCard = card;
1118
- ctxMenu.style.left = e.clientX + "px";
1119
- ctxMenu.style.top = e.clientY + "px";
1120
- ctxMenu.style.display = "block";
1121
- });
1122
- document.addEventListener("click", (e) => {
1123
- if (!e.target.closest("#context-menu")) {
1124
- ctxMenu.style.display = "none";
1125
- ctxTargetCard = null;
1126
- }
1127
- });
1128
- document.getElementById("ctx-delete")?.addEventListener("click", async (e) => {
1129
- e.stopPropagation();
1130
- ctxMenu.style.display = "none";
1131
- if (!ctxTargetCard) return;
1132
- const card = ctxTargetCard;
1133
- ctxTargetCard = null;
1134
- await deleteCard(card);
1135
- });
1136
- }
1137
-
1138
- // src/board/client/detailPanelApi.ts
1139
- var PANEL_MIN_WIDTH = 280;
1140
- var PANEL_MAX_WIDTH = 800;
1141
- var PANEL_DEFAULT_WIDTH = 400;
1142
- async function fetchComments(taskId) {
1143
- const res = await fetch("/api/tasks/" + taskId + "/comments");
1144
- if (!res.ok) throw new Error("Server error");
1145
- const data = await res.json();
1146
- return data.comments || [];
1147
- }
1148
- async function patchComment(commentId, content) {
1149
- const res = await fetch("/api/comments/" + commentId, {
1150
- method: "PATCH",
1151
- headers: { "Content-Type": "application/json" },
1152
- body: JSON.stringify({ content })
1153
- });
1154
- if (!res.ok) throw new Error("Server error");
1155
- }
1156
- async function deleteCommentRequest(commentId) {
1157
- const res = await fetch("/api/comments/" + commentId, { method: "DELETE" });
1158
- if (!res.ok) throw new Error("Server error");
1159
- }
1160
- async function postComment(taskId, content) {
1161
- const res = await fetch("/api/tasks/" + taskId + "/comments", {
1162
- method: "POST",
1163
- headers: { "Content-Type": "application/json" },
1164
- body: JSON.stringify({ content })
1165
- });
1166
- if (!res.ok) throw new Error("Server error");
1167
- }
1168
- async function fetchTaskDetail(taskId, signal) {
1169
- const res = await fetch("/api/tasks/" + taskId, signal ? { signal } : void 0);
1170
- if (!res.ok) throw new Error("Server error");
1171
- return res.json();
1172
- }
1173
- async function patchTask(taskId, fields) {
1174
- const res = await fetch("/api/tasks/" + taskId, {
1175
- method: "PATCH",
1176
- headers: { "Content-Type": "application/json" },
1177
- body: JSON.stringify(fields)
1178
- });
1179
- if (!res.ok) throw new Error("Server error");
1180
- return fetchTaskDetail(taskId);
1181
- }
1182
- async function fetchPanelWidthFromConfig() {
1183
- let targetWidth = PANEL_DEFAULT_WIDTH;
1184
- try {
1185
- const res = await fetch("/api/config");
1186
- if (res.ok) {
1187
- const data = await res.json();
1188
- const savedWidth = data && data.board && data.board.detailPaneWidth;
1189
- if (typeof savedWidth === "number" && savedWidth >= PANEL_MIN_WIDTH && savedWidth <= PANEL_MAX_WIDTH) {
1190
- targetWidth = savedWidth;
1191
- }
1192
- }
1193
- } catch {
1194
- }
1195
- return targetWidth;
1196
- }
1197
- function savePanelWidthToConfig(width) {
1198
- fetch("/api/config", {
1199
- method: "PUT",
1200
- headers: { "Content-Type": "application/json" },
1201
- body: JSON.stringify({ board: { detailPaneWidth: width } })
1202
- }).catch(function() {
1203
- });
1204
- }
1205
- function subscribeRunLogs(taskId, onUpdate, onError) {
1206
- const es = new EventSource("/api/claude/tasks/" + taskId + "/run-logs/stream");
1207
- es.addEventListener("update", (event) => {
1208
- const msgEvent = event;
1209
- try {
1210
- const data = JSON.parse(msgEvent.data);
1211
- onUpdate(data.logs || []);
1212
- } catch {
1213
- }
1214
- });
1215
- es.onerror = () => onError();
1216
- return es;
1217
- }
1218
-
1219
- // src/board/client/detailPanelHtml.ts
1220
- function renderCommentItemHtml(comment, taskId) {
1221
- const authorText = comment.author ? escapeHtmlClient(comment.author) : "Anonymous";
1222
- const dateRel = relativeTime(comment.created_at);
1223
- const dateAbs = escapeHtmlClient(comment.created_at);
1224
- const contentText = escapeHtmlClient(comment.content);
1225
- let html = '<div class="comment-item" data-comment-id="' + comment.id + '">';
1226
- html += '<div class="comment-meta">';
1227
- html += '<span class="comment-author">' + authorText + "</span>";
1228
- html += '<span class="comment-date" title="' + dateAbs + '">' + dateRel + "</span>";
1229
- html += '<span class="comment-actions">';
1230
- html += '<button class="comment-action-btn" title="Edit" data-action="start-comment-edit" data-comment-id="' + comment.id + '">&#9998;</button>';
1231
- html += '<button class="comment-action-btn danger" title="Delete" data-action="delete-comment" data-comment-id="' + comment.id + '" data-task-id="' + taskId + '">&#128465;</button>';
1232
- html += "</span></div>";
1233
- html += '<div class="comment-content" id="comment-content-' + comment.id + '">' + contentText + "</div>";
1234
- html += '<div id="comment-edit-' + comment.id + '" style="display:none;">';
1235
- html += '<textarea class="comment-edit-area" id="comment-edit-area-' + comment.id + '">' + contentText + "</textarea>";
1236
- html += '<div class="comment-edit-actions">';
1237
- html += '<button class="comment-btn" data-action="save-comment-edit" data-comment-id="' + comment.id + '" data-task-id="' + taskId + '">Save</button>';
1238
- html += '<button class="comment-btn" data-action="cancel-comment-edit" data-comment-id="' + comment.id + '">Cancel</button>';
1239
- html += "</div></div></div>";
1240
- return html;
1241
- }
1242
- function renderAddCommentFormHtml(taskId) {
1243
- let html = '<button class="add-comment-trigger" id="add-comment-trigger" data-action="open-add-comment">+ Add comment...</button>';
1244
- html += '<div class="add-comment-form" id="add-comment-form">';
1245
- html += '<textarea class="add-comment-textarea" id="add-comment-text" placeholder="Write a comment..."></textarea>';
1246
- html += "<div>";
1247
- html += '<button class="add-comment-submit" data-action="submit-comment" data-task-id="' + taskId + '">Add Comment</button>';
1248
- html += '<button class="add-comment-cancel" data-action="close-add-comment">Cancel</button>';
1249
- html += "</div></div>";
1250
- return html;
1251
- }
1252
- function renderStatusField(currentStatus, allStatuses, statusLabels) {
1253
- let html = '<div class="detail-field">';
1254
- html += '<div class="detail-field-label">Status</div>';
1255
- html += '<select id="detail-edit-status" class="detail-edit-select">';
1256
- allStatuses.forEach((s) => {
1257
- const selected = s === currentStatus ? " selected" : "";
1258
- html += '<option value="' + s + '"' + selected + ">" + statusLabels[s] + "</option>";
1259
- });
1260
- html += "</select></div>";
1261
- return html;
1262
- }
1263
- function renderPriorityField(currentPriority, allPriorities) {
1264
- let html = '<div class="detail-field">';
1265
- html += '<div class="detail-field-label">Priority</div>';
1266
- html += '<select id="detail-edit-priority" class="detail-edit-select">';
1267
- html += '<option value="">None</option>';
1268
- allPriorities.forEach((p) => {
1269
- const selected = currentPriority === p ? " selected" : "";
1270
- html += '<option value="' + p + '"' + selected + ">" + p.charAt(0).toUpperCase() + p.slice(1) + "</option>";
1271
- });
1272
- html += "</select></div>";
1273
- return html;
1274
- }
1275
- function renderRelationsHtml(parent, blockedBy, blocking) {
1276
- let html = '<div class="detail-relations">';
1277
- if (parent) {
1278
- html += '<div class="detail-relation-row">';
1279
- html += '<span class="detail-relation-label">Parent</span>';
1280
- 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>";
1281
- html += "</div>";
1282
- }
1283
- if (blockedBy.length > 0) {
1284
- html += '<div class="detail-relation-row"><span class="detail-relation-label">Blocked by</span>';
1285
- html += '<div class="detail-relation-ids">';
1286
- blockedBy.forEach((t) => {
1287
- html += '<span class="detail-relation-id detail-relation-link" data-task-id="' + t.id + '">#' + t.id + "</span>";
1288
- });
1289
- html += "</div></div>";
1290
- }
1291
- if (blocking.length > 0) {
1292
- html += '<div class="detail-relation-row"><span class="detail-relation-label">Blocking</span>';
1293
- html += '<div class="detail-relation-ids">';
1294
- blocking.forEach((t) => {
1295
- html += '<span class="detail-relation-id detail-relation-link" data-task-id="' + t.id + '">#' + t.id + "</span>";
1296
- });
1297
- html += "</div></div>";
1298
- }
1299
- html += "</div>";
1300
- return html;
1301
- }
1302
- function isHttpUrl(value) {
1303
- return value.startsWith("https://") || value.startsWith("http://");
1304
- }
1305
- function renderMetadataValue(value) {
1306
- if (!isHttpUrl(value)) {
1307
- return escapeHtmlClient(value);
1308
- }
1309
- const escaped = escapeHtmlClient(value);
1310
- return '<a href="' + escaped + '" target="_blank" rel="noopener noreferrer">' + escaped + "</a>";
1311
- }
1312
- function renderMetadataTable(metadata) {
1313
- if (metadata.length === 0) return "";
1314
- let html = '<div class="detail-field"><div class="detail-field-label">Metadata</div>';
1315
- html += '<table class="detail-meta-table">';
1316
- metadata.forEach((m) => {
1317
- html += "<tr><td>" + escapeHtmlClient(m.key) + "</td><td>" + renderMetadataValue(m.value) + "</td></tr>";
1318
- });
1319
- html += "</table></div>";
1320
- return html;
1321
- }
1322
- function renderEditableTextFields(task) {
1323
- let html = '<div class="detail-field"><div class="detail-field-label">Title</div>';
1324
- html += '<input id="detail-edit-title" class="detail-edit-input" type="text" value="' + escapeHtmlClient(task.title) + '">';
1325
- html += "</div>";
1326
- html += '<div class="detail-field description-field-wrapper"><div class="detail-field-label">Description</div>';
1327
- html += '<textarea id="detail-edit-body" class="detail-edit-textarea">' + escapeHtmlClient(task.body || "") + "</textarea>";
1328
- html += "</div>";
1329
- return html;
1330
- }
1331
- function renderDetailPanelHtml(data) {
1332
- const task = data.task;
1333
- const metadata = data.metadata || [];
1334
- const blockedBy = data.blockedBy || [];
1335
- const blocking = data.blocking || [];
1336
- const parent = data.parent || null;
1337
- const { allStatuses, statusLabels, allPriorities } = window;
1338
- let html = "";
1339
- html += renderStatusField(task.status, allStatuses, statusLabels);
1340
- html += renderPriorityField(task.priority, allPriorities);
1341
- html += '<div class="detail-field"><div class="detail-field-label">Tags</div>';
1342
- html += '<div id="detail-tags-container"></div></div>';
1343
- const hasRelations = parent || blockedBy.length > 0 || blocking.length > 0;
1344
- if (hasRelations) {
1345
- html += renderRelationsHtml(parent, blockedBy, blocking);
1346
- }
1347
- html += renderMetadataTable(metadata);
1348
- html += renderEditableTextFields(task);
1349
- return html;
1350
- }
1351
- function renderRunLogsHtml(logs) {
1352
- if (logs.length === 0) {
1353
- return '<div class="run-log-empty">No run logs yet.</div>';
1354
- }
1355
- let html = '<div class="run-log-list">';
1356
- logs.forEach((log, index) => {
1357
- const date = log.started_at ? log.started_at.replace("T", " ").replace(/\.\d+Z$/, "") : "";
1358
- const exitOk = log.exit_code === 0;
1359
- const exitLabel = log.exit_code !== null ? "exit: " + String(log.exit_code) : "running";
1360
- const exitClass = exitOk ? "success" : "failure";
1361
- const isFirst = index === 0;
1362
- 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">';
1363
- log.events.forEach((evt) => {
1364
- if (evt.kind === "text" && evt.text) {
1365
- html += escapeHtmlClient(evt.text);
1366
- } else if (evt.kind === "tool_use" && evt.name) {
1367
- const mainArg = evt.input && typeof evt.input === "object" ? String(
1368
- evt.input.path ?? evt.input.command ?? ""
1369
- ) : "";
1370
- const display = mainArg ? "\u{1F527} " + evt.name + ": " + mainArg : "\u{1F527} " + evt.name;
1371
- html += '<span class="run-log-tool-use">' + escapeHtmlClient(display) + "\n</span>";
1372
- }
1373
- });
1374
- html += "</div></div>";
1375
- });
1376
- html += "</div>";
1377
- return html;
1378
- }
1379
- function buildDetailPanelHtml() {
1380
- 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>';
1381
- }
1382
- function autoResizeTextarea(el) {
1383
- const scrollContainer = el.closest(".detail-tab-content");
1384
- const scrollTop = scrollContainer?.scrollTop ?? 0;
1385
- el.style.height = "auto";
1386
- el.style.height = el.scrollHeight + "px";
1387
- if (scrollContainer) scrollContainer.scrollTop = scrollTop;
1388
- }
1389
-
1390
- // src/board/client/detailPanel.ts
1391
- var detailTaskId = null;
1392
- var lastTab = "details";
1393
- var runLogEventSource = null;
1394
- var currentFetchController = null;
1395
- var runLogLoadSeq = 0;
1396
- var runLogsLoadedTaskId = null;
1397
- function closeRunLogStream() {
1398
- if (runLogEventSource !== null) {
1399
- runLogEventSource.close();
1400
- runLogEventSource = null;
1401
- }
1402
- }
1403
- function getDetailTaskId() {
1404
- return detailTaskId;
1405
- }
1406
- function getDetailActiveTab() {
1407
- return lastTab;
1408
- }
1409
- function setActiveCard(taskId) {
1410
- document.querySelectorAll(".card.active").forEach((card) => {
1411
- card.classList.remove("active");
1412
- });
1413
- if (taskId !== null) {
1414
- const card = document.querySelector('.card[data-id="' + taskId + '"]');
1415
- if (card) card.classList.add("active");
1416
- }
1417
- }
1418
- function closeDetailPanel() {
1419
- closeRunLogStream();
1420
- runLogsLoadedTaskId = null;
1421
- const runLogsPane = document.getElementById("detail-tab-content-run-logs");
1422
- if (runLogsPane) {
1423
- delete runLogsPane.dataset.runLogsTaskId;
1424
- delete runLogsPane.dataset.runLogsSignature;
1425
- }
1426
- const detailPanel = document.getElementById("detail-panel");
1427
- detailPanel.classList.remove("open");
1428
- detailPanel.style.width = "";
1429
- setActiveCard(null);
1430
- detailTaskId = null;
1431
- }
1432
- function switchTab(tabName) {
1433
- const activeTabBtn = document.querySelector(".detail-tab.active");
1434
- const currentTab = activeTabBtn?.dataset.tab ?? null;
1435
- const isSameTab = currentTab === tabName;
1436
- if (tabName !== "run-logs") {
1437
- closeRunLogStream();
1438
- }
1439
- lastTab = tabName;
1440
- if (!isSameTab) {
1441
- document.querySelectorAll(".detail-tab").forEach((btn) => {
1442
- btn.classList.toggle("active", btn.dataset.tab === tabName);
1443
- });
1444
- document.querySelectorAll(".detail-tab-content").forEach((el) => {
1445
- el.classList.toggle("active", el.id === "detail-tab-content-" + tabName);
1446
- });
1447
- }
1448
- const footer = document.getElementById("detail-panel-footer");
1449
- if (footer) footer.style.display = tabName === "details" ? "" : "none";
1450
- if (tabName === "run-logs" && detailTaskId !== null && (!isSameTab || runLogsLoadedTaskId !== detailTaskId)) {
1451
- loadRunLogs(detailTaskId);
1452
- }
1453
- }
1454
- async function loadComments(taskId) {
1455
- const pane = document.getElementById("detail-tab-content-comments");
1456
- if (!pane) return;
1457
- try {
1458
- const comments = await fetchComments(taskId);
1459
- if (detailTaskId !== taskId) return;
1460
- const tabBtn = document.getElementById("detail-tab-comments");
1461
- if (tabBtn) tabBtn.textContent = "Comments (" + comments.length + ")";
1462
- renderComments(taskId, comments);
1463
- } catch (err) {
1464
- console.error("[agkan] loadComments failed for task", taskId, err);
1465
- if (detailTaskId !== taskId) return;
1466
- if (pane) pane.innerHTML = '<div style="padding:20px;font-size:12px;color:#94a3b8;">Failed to load comments</div>';
1467
- }
1468
- }
1469
- function renderComments(taskId, comments) {
1470
- const pane = document.getElementById("detail-tab-content-comments");
1471
- if (!pane) return;
1472
- pane.style.padding = "16px 20px";
1473
- let html = "";
1474
- comments.forEach((comment) => {
1475
- html += renderCommentItemHtml(comment, taskId);
1476
- });
1477
- html += renderAddCommentFormHtml(taskId);
1478
- pane.innerHTML = html;
1479
- const paneEl = pane;
1480
- if (paneEl._commentActionHandler) {
1481
- paneEl.removeEventListener("click", paneEl._commentActionHandler);
1482
- }
1483
- paneEl._commentActionHandler = handleCommentAction;
1484
- paneEl.addEventListener("click", paneEl._commentActionHandler);
1485
- }
1486
- function openAddCommentForm() {
1487
- const trigger = document.getElementById("add-comment-trigger");
1488
- const form = document.getElementById("add-comment-form");
1489
- if (trigger) trigger.style.display = "none";
1490
- if (form) {
1491
- form.classList.add("open");
1492
- form.querySelector("textarea").focus();
1493
- }
1494
- }
1495
- function closeAddCommentForm() {
1496
- const trigger = document.getElementById("add-comment-trigger");
1497
- const form = document.getElementById("add-comment-form");
1498
- if (trigger) trigger.style.display = "";
1499
- if (form) {
1500
- form.classList.remove("open");
1501
- form.querySelector("textarea").value = "";
1502
- }
1503
- }
1504
- function startCommentEdit(commentId) {
1505
- const contentEl = document.getElementById("comment-content-" + commentId);
1506
- const editWrapper = document.getElementById("comment-edit-" + commentId);
1507
- if (contentEl) contentEl.style.display = "none";
1508
- if (editWrapper) editWrapper.style.display = "block";
1509
- const area = document.getElementById("comment-edit-area-" + commentId);
1510
- if (area) area.focus();
1511
- }
1512
- function cancelCommentEdit(commentId) {
1513
- const contentEl = document.getElementById("comment-content-" + commentId);
1514
- const editWrapper = document.getElementById("comment-edit-" + commentId);
1515
- if (contentEl) contentEl.style.display = "";
1516
- if (editWrapper) editWrapper.style.display = "none";
1517
- }
1518
- async function saveCommentEdit(commentId, taskId) {
1519
- const area = document.getElementById("comment-edit-area-" + commentId);
1520
- if (!area) return;
1521
- const content = area.value.trim();
1522
- if (!content) {
1523
- area.focus();
1524
- return;
1525
- }
1526
- try {
1527
- await patchComment(commentId, content);
1528
- await loadComments(taskId);
1529
- } catch {
1530
- showToast("Failed to update comment");
1531
- }
1532
- }
1533
- async function deleteComment(commentId, taskId) {
1534
- if (!confirm("Delete this comment?")) return;
1535
- try {
1536
- await deleteCommentRequest(commentId);
1537
- await loadComments(taskId);
1538
- } catch {
1539
- showToast("Failed to delete comment");
1540
- }
1541
- }
1542
- async function submitComment(taskId) {
1543
- const textarea = document.getElementById("add-comment-text");
1544
- if (!textarea) return;
1545
- const content = textarea.value.trim();
1546
- if (!content) {
1547
- textarea.focus();
1548
- return;
1549
- }
1550
- try {
1551
- await postComment(taskId, content);
1552
- await loadComments(taskId);
1553
- } catch {
1554
- showToast("Failed to add comment");
1555
- }
1556
- }
1557
- function dispatchCommentAction(action, commentId, taskId) {
1558
- switch (action) {
1559
- case "open-add-comment":
1560
- openAddCommentForm();
1561
- break;
1562
- case "close-add-comment":
1563
- closeAddCommentForm();
1564
- break;
1565
- case "start-comment-edit":
1566
- startCommentEdit(commentId);
1567
- break;
1568
- case "cancel-comment-edit":
1569
- cancelCommentEdit(commentId);
1570
- break;
1571
- case "save-comment-edit":
1572
- void saveCommentEdit(commentId, taskId);
1573
- break;
1574
- case "delete-comment":
1575
- void deleteComment(commentId, taskId);
1576
- break;
1577
- case "submit-comment":
1578
- void submitComment(taskId);
1579
- break;
1580
- }
1581
- }
1582
- function handleCommentAction(e) {
1583
- const target = e.target.closest("[data-action]");
1584
- if (!target) return;
1585
- const action = target.dataset.action ?? "";
1586
- const commentId = target.dataset.commentId ? Number(target.dataset.commentId) : NaN;
1587
- const taskId = target.dataset.taskId ? Number(target.dataset.taskId) : NaN;
1588
- dispatchCommentAction(action, commentId, taskId);
1589
- }
1590
- function buildRunLogsSignature(logs) {
1591
- return JSON.stringify(logs);
1592
- }
1593
- function renderRunLogsInPane(pane, logs) {
1594
- const nextSignature = buildRunLogsSignature(logs);
1595
- if (pane.dataset.runLogsSignature === nextSignature) return;
1596
- pane.dataset.runLogsSignature = nextSignature;
1597
- const bodyScrollState = /* @__PURE__ */ new Map();
1598
- const openLogIds = /* @__PURE__ */ new Set();
1599
- const hadPreviousItems = pane.querySelector(".run-log-item") !== null;
1600
- const paneScrollTop = pane.scrollTop;
1601
- const paneIsNearBottom = pane.scrollHeight - pane.scrollTop - pane.clientHeight <= 50;
1602
- pane.querySelectorAll(".run-log-item.open").forEach((item) => {
1603
- const logId = Number(item.dataset.logId);
1604
- if (!logId) return;
1605
- openLogIds.add(logId);
1606
- const body = item.querySelector(".run-log-body");
1607
- if (body) {
1608
- const isNearBottom = body.scrollHeight - body.scrollTop - body.clientHeight <= 50;
1609
- bodyScrollState.set(logId, { scrollTop: body.scrollTop, isNearBottom });
1610
- }
1611
- });
1612
- pane.innerHTML = renderRunLogsHtml(logs);
1613
- requestAnimationFrame(() => {
1614
- if (hadPreviousItems) {
1615
- pane.scrollTop = paneIsNearBottom ? pane.scrollHeight : paneScrollTop;
1616
- }
1617
- pane.querySelectorAll(".run-log-item").forEach((item) => {
1618
- const logId = Number(item.dataset.logId);
1619
- if (!logId) return;
1620
- const body = item.querySelector(".run-log-body");
1621
- if (!body) return;
1622
- if (hadPreviousItems) {
1623
- if (openLogIds.has(logId)) {
1624
- item.classList.add("open");
1625
- const state = bodyScrollState.get(logId);
1626
- if (state) {
1627
- body.scrollTop = state.isNearBottom ? body.scrollHeight : state.scrollTop;
1628
- } else {
1629
- body.scrollTop = body.scrollHeight;
1630
- }
1631
- } else {
1632
- item.classList.remove("open");
1633
- }
1634
- } else {
1635
- if (item.classList.contains("open")) {
1636
- body.scrollTop = body.scrollHeight;
1637
- }
1638
- }
1639
- });
1640
- });
1641
- }
1642
- function loadRunLogs(taskId) {
1643
- closeRunLogStream();
1644
- const seq = ++runLogLoadSeq;
1645
- const pane = document.getElementById("detail-tab-content-run-logs");
1646
- if (!pane) return;
1647
- runLogsLoadedTaskId = taskId;
1648
- if (pane.dataset.runLogsTaskId !== String(taskId)) {
1649
- pane.dataset.runLogsTaskId = String(taskId);
1650
- delete pane.dataset.runLogsSignature;
1651
- }
1652
- pane.removeEventListener("click", handleRunLogToggle);
1653
- pane.addEventListener("click", handleRunLogToggle);
1654
- runLogEventSource = subscribeRunLogs(
1655
- taskId,
1656
- (logs) => {
1657
- if (seq !== runLogLoadSeq) return;
1658
- if (detailTaskId !== taskId) {
1659
- closeRunLogStream();
1660
- return;
1661
- }
1662
- const p = document.getElementById("detail-tab-content-run-logs");
1663
- if (p) renderRunLogsInPane(p, logs);
1664
- },
1665
- () => {
1666
- if (seq !== runLogLoadSeq) return;
1667
- closeRunLogStream();
1668
- }
1669
- );
1670
- }
1671
- function handleRunLogToggle(e) {
1672
- const target = e.target.closest('[data-action="toggle-run-log"]');
1673
- if (!target) return;
1674
- const item = target.closest(".run-log-item");
1675
- if (item) item.classList.toggle("open");
1676
- }
1677
- function renderDetailPanel(data) {
1678
- document.getElementById("detail-panel-update-warning")?.remove();
1679
- const detailPanelTitle = document.getElementById("detail-panel-title");
1680
- const task = data.task;
1681
- const tags = data.tags || [];
1682
- detailTaskId = task.id;
1683
- detailPanelTitle.textContent = "#" + task.id;
1684
- const detailsPane = document.getElementById("detail-tab-content-details");
1685
- if (detailsPane) {
1686
- detailsPane.innerHTML = renderDetailPanelHtml(data);
1687
- detailsPane.style.padding = "20px";
1688
- detailsPane.querySelectorAll(".detail-relation-link[data-task-id]").forEach((el) => {
1689
- el.style.cursor = "pointer";
1690
- el.addEventListener("click", () => {
1691
- const tid = el.dataset.taskId;
1692
- if (tid) void openTaskDetail(tid);
1693
- });
1694
- });
1695
- }
1696
- const footer = document.getElementById("detail-panel-footer");
1697
- if (footer) {
1698
- footer.innerHTML = '<span class="detail-footer-timestamp">created ' + relativeTime(task.created_at) + " &middot; updated " + relativeTime(task.updated_at) + '</span><button id="detail-save-btn">Save</button>';
1699
- document.getElementById("detail-save-btn")?.addEventListener("click", saveDetailTask);
1700
- }
1701
- const textarea = document.getElementById("detail-edit-body");
1702
- if (textarea) {
1703
- const detailPanel = document.getElementById("detail-panel");
1704
- if (detailPanel && !detailPanel.classList.contains("open")) {
1705
- const prefersReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
1706
- if (prefersReducedMotion) {
1707
- autoResizeTextarea(textarea);
1708
- } else {
1709
- let done = false;
1710
- const finish = () => {
1711
- if (done) return;
1712
- done = true;
1713
- detailPanel.removeEventListener("transitionend", onTransitionEnd);
1714
- requestAnimationFrame(() => {
1715
- requestAnimationFrame(() => {
1716
- autoResizeTextarea(textarea);
1717
- });
1718
- });
1719
- };
1720
- const onTransitionEnd = (e) => {
1721
- if (e.propertyName === "width") finish();
1722
- };
1723
- detailPanel.addEventListener("transitionend", onTransitionEnd);
1724
- setTimeout(finish, 260);
1725
- }
1726
- } else {
1727
- requestAnimationFrame(() => {
1728
- requestAnimationFrame(() => {
1729
- autoResizeTextarea(textarea);
1730
- });
1731
- });
1732
- }
1733
- textarea.addEventListener("input", () => {
1734
- autoResizeTextarea(textarea);
1735
- });
1736
- }
1737
- renderTagsSection([...tags]);
1738
- loadAllTags().catch((err) => {
1739
- console.error("[agkan] renderDetailPanel tags failed", err);
1740
- });
1741
- loadComments(task.id);
1742
- switchTab(lastTab);
1743
- }
1744
- async function openTaskDetail(taskId) {
1745
- const detailPanel = document.getElementById("detail-panel");
1746
- const PANEL_DEFAULT_WIDTH2 = 400;
1747
- setActiveCard(Number(taskId));
1748
- if (currentFetchController) {
1749
- currentFetchController.abort();
1750
- }
1751
- currentFetchController = new AbortController();
1752
- const { signal } = currentFetchController;
1753
- try {
1754
- const data = await fetchTaskDetail(taskId, signal);
1755
- currentFetchController = null;
1756
- renderDetailPanel(data);
1757
- if (!detailPanel.classList.contains("open")) {
1758
- const preferredWidth = detailPanel.dataset.preferredWidth || String(PANEL_DEFAULT_WIDTH2);
1759
- detailPanel.style.width = preferredWidth + "px";
1760
- detailPanel.classList.add("open");
1761
- }
1762
- } catch (err) {
1763
- if (err instanceof DOMException && err.name === "AbortError") return;
1764
- console.error("[agkan] openTaskDetail failed for task", taskId, err);
1765
- showToast("Failed to load task details");
1766
- }
1767
- }
1768
- function showUpdateWarning() {
1769
- const detailPanelBody = document.getElementById("detail-panel-body");
1770
- const warning = document.getElementById("detail-panel-update-warning");
1771
- if (!warning) {
1772
- const warningEl = document.createElement("div");
1773
- warningEl.id = "detail-panel-update-warning";
1774
- warningEl.style.cssText = "display: flex; align-items: center; gap: 8px; color: red; font-size: 0.85em; padding: 4px 8px; background: #fff0f0; border: 1px solid #ffcccc; border-radius: 4px; margin-bottom: 8px;";
1775
- const msgSpan = document.createElement("span");
1776
- msgSpan.style.cssText = "flex: 1;";
1777
- msgSpan.textContent = "This task has been updated in the database. Save or discard your changes to see the latest version.";
1778
- const reloadBtn = buildUpdateWarningReloadBtn();
1779
- warningEl.appendChild(msgSpan);
1780
- warningEl.appendChild(reloadBtn);
1781
- detailPanelBody.insertBefore(warningEl, detailPanelBody.firstChild);
1782
- }
1783
- }
1784
- function buildUpdateWarningReloadBtn() {
1785
- const reloadBtn = document.createElement("button");
1786
- reloadBtn.title = "Reload latest data";
1787
- reloadBtn.textContent = "\u21BA";
1788
- reloadBtn.style.cssText = "background: none; border: none; cursor: pointer; font-size: 1.5em; color: red; padding: 0 4px; line-height: 1; flex-shrink: 0;";
1789
- reloadBtn.addEventListener("click", async () => {
1790
- try {
1791
- if (detailTaskId !== null) {
1792
- const taskData = await fetchTaskDetail(detailTaskId);
1793
- renderDetailPanel(taskData);
1794
- }
1795
- } catch {
1796
- }
1797
- });
1798
- return reloadBtn;
1799
- }
1800
- function collectEditedTaskFields() {
1801
- const titleInput = document.getElementById("detail-edit-title");
1802
- const title = titleInput ? titleInput.value.trim() : "";
1803
- if (!title) {
1804
- if (titleInput) titleInput.focus();
1805
- return null;
1806
- }
1807
- const bodyEl = document.getElementById("detail-edit-body");
1808
- const statusEl = document.getElementById("detail-edit-status");
1809
- const priorityEl = document.getElementById("detail-edit-priority");
1810
- return {
1811
- title,
1812
- body: bodyEl ? bodyEl.value.trim() || null : null,
1813
- status: statusEl ? statusEl.value : void 0,
1814
- priority: priorityEl ? priorityEl.value || null : null
1815
- };
1816
- }
1817
- async function saveDetailTask() {
1818
- if (detailTaskId === null) return;
1819
- const fields = collectEditedTaskFields();
1820
- if (!fields) return;
1821
- try {
1822
- const data = await patchTask(detailTaskId, fields);
1823
- renderDetailPanel(data);
1824
- showToast("Task saved successfully");
1825
- refreshBoardCards();
1826
- } catch {
1827
- showToast("Failed to update task");
1828
- }
1829
- }
1830
- async function initPanelWidthFromConfig(detailPanel) {
1831
- const targetWidth = await fetchPanelWidthFromConfig();
1832
- detailPanel.dataset.preferredWidth = String(targetWidth);
1833
- }
1834
- function attachResizeMousedown(resizeHandle, detailPanel) {
1835
- resizeHandle.addEventListener("mousedown", function(e) {
1836
- e.preventDefault();
1837
- if (!detailPanel.classList.contains("open")) return;
1838
- const startX = e.clientX;
1839
- const startWidth = detailPanel.offsetWidth;
1840
- resizeHandle.classList.add("dragging");
1841
- document.body.style.userSelect = "none";
1842
- document.body.style.cursor = "col-resize";
1843
- detailPanel.style.transition = "none";
1844
- function onMouseMove(ev) {
1845
- const delta = startX - ev.clientX;
1846
- const newWidth = Math.min(PANEL_MAX_WIDTH, Math.max(PANEL_MIN_WIDTH, startWidth + delta));
1847
- detailPanel.style.width = newWidth + "px";
1848
- }
1849
- function onMouseUp() {
1850
- resizeHandle.classList.remove("dragging");
1851
- document.body.style.userSelect = "";
1852
- document.body.style.cursor = "";
1853
- detailPanel.style.transition = "";
1854
- const currentWidth = detailPanel.offsetWidth;
1855
- detailPanel.dataset.preferredWidth = String(currentWidth);
1856
- savePanelWidthToConfig(currentWidth);
1857
- document.removeEventListener("mousemove", onMouseMove);
1858
- document.removeEventListener("mouseup", onMouseUp);
1859
- }
1860
- document.addEventListener("mousemove", onMouseMove);
1861
- document.addEventListener("mouseup", onMouseUp);
1862
- });
1863
- }
1864
- function initPanelResize(detailPanel) {
1865
- const resizeHandle = document.getElementById("detail-panel-resize-handle");
1866
- initPanelWidthFromConfig(detailPanel);
1867
- attachResizeMousedown(resizeHandle, detailPanel);
1868
- }
1869
- function initDetailPanel() {
1870
- const boardContainer = document.querySelector(".board-container");
1871
- boardContainer.insertAdjacentHTML("beforeend", buildDetailPanelHtml());
1872
- const detailPanel = document.getElementById("detail-panel");
1873
- document.getElementById("detail-panel-close")?.addEventListener("click", closeDetailPanel);
1874
- document.getElementById("detail-panel-copy-id")?.addEventListener("click", () => {
1875
- if (detailTaskId === null) return;
1876
- navigator.clipboard.writeText(String(detailTaskId)).then(() => {
1877
- showToast("Copied task ID: " + detailTaskId);
1878
- });
1879
- });
1880
- document.addEventListener("keydown", (e) => {
1881
- if (e.key === "Escape" && detailPanel.classList.contains("open")) {
1882
- closeDetailPanel();
1883
- }
1884
- });
1885
- document.getElementById("detail-tabs")?.addEventListener("click", (e) => {
1886
- const btn = e.target.closest(".detail-tab");
1887
- if (!btn) return;
1888
- switchTab(btn.dataset.tab);
1889
- });
1890
- initPanelResize(detailPanel);
1891
- document.querySelectorAll(".card").forEach((card) => {
1892
- if (card.dataset.listenersAttached) return;
1893
- card.dataset.listenersAttached = "1";
1894
- card.addEventListener("click", async (e) => {
1895
- if (e.defaultPrevented) return;
1896
- await openTaskDetail(card.dataset.id);
1897
- });
1898
- });
1899
- registerDetailPanelCallbacks({
1900
- openTaskDetail,
1901
- renderDetailPanel,
1902
- showUpdateWarning,
1903
- getDetailTaskId,
1904
- getDetailActiveTab,
1905
- setActiveCard
1906
- });
1907
- registerGetDetailTaskId(getDetailTaskId);
1908
- }
1909
-
1910
- // src/board/client/filters.ts
1911
- function isFiltersActive() {
1912
- return activeFilters.priorities.length > 0 || activeFilters.tagIds.length > 0 || activeFilters.assignee !== "" || activeFilters.searchText !== "";
1913
- }
1914
- function applyFilters() {
1915
- const clearBtn = document.getElementById("filter-clear");
1916
- if (clearBtn) {
1917
- if (isFiltersActive()) {
1918
- clearBtn.classList.add("visible");
1919
- } else {
1920
- clearBtn.classList.remove("visible");
1921
- }
1922
- }
1923
- refreshBoardCards();
1924
- }
1925
- function renderFilterTagPills() {
1926
- const container = document.getElementById("filter-tags-control");
1927
- if (!container) return;
1928
- container.querySelectorAll(".filter-tag-pill").forEach((p) => p.remove());
1929
- activeFilters.tagIds.forEach((tagId) => {
1930
- const tag = allAvailableTags.find((t) => t.id === tagId);
1931
- if (!tag) return;
1932
- const pill = document.createElement("span");
1933
- pill.className = "filter-tag-pill";
1934
- const label = document.createTextNode(tag.name);
1935
- const removeBtn = document.createElement("button");
1936
- removeBtn.className = "filter-tag-pill-remove";
1937
- removeBtn.title = "Remove tag filter";
1938
- removeBtn.innerHTML = "&times;";
1939
- removeBtn.addEventListener("click", () => {
1940
- const idx = activeFilters.tagIds.indexOf(tagId);
1941
- if (idx !== -1) activeFilters.tagIds.splice(idx, 1);
1942
- renderFilterTagPills();
1943
- applyFilters();
1944
- });
1945
- pill.appendChild(label);
1946
- pill.appendChild(removeBtn);
1947
- container.insertBefore(pill, container.querySelector(".filter-tag-dropdown-wrapper"));
1948
- });
1949
- }
1950
- function initFilterBar() {
1951
- document.querySelectorAll(".filter-priority-btn").forEach((btn) => {
1952
- btn.addEventListener("click", () => {
1953
- const priority = btn.dataset.priority;
1954
- const idx = activeFilters.priorities.indexOf(priority);
1955
- if (idx === -1) {
1956
- activeFilters.priorities.push(priority);
1957
- btn.classList.add("active");
1958
- } else {
1959
- activeFilters.priorities.splice(idx, 1);
1960
- btn.classList.remove("active");
1961
- }
1962
- applyFilters();
1963
- });
1964
- });
1965
- const searchInput = document.getElementById("filter-search");
1966
- let searchTimer = null;
1967
- if (searchInput) {
1968
- searchInput.addEventListener("input", () => {
1969
- if (searchTimer) clearTimeout(searchTimer);
1970
- searchTimer = setTimeout(() => {
1971
- activeFilters.searchText = searchInput.value.trim();
1972
- applyFilters();
1973
- }, 300);
1974
- });
1975
- }
1976
- const assigneeInput = document.getElementById("filter-assignee");
1977
- let assigneeTimer = null;
1978
- if (assigneeInput) {
1979
- assigneeInput.addEventListener("input", () => {
1980
- if (assigneeTimer) clearTimeout(assigneeTimer);
1981
- assigneeTimer = setTimeout(() => {
1982
- activeFilters.assignee = assigneeInput.value.trim();
1983
- applyFilters();
1984
- }, 300);
1985
- });
1986
- }
1987
- const clearBtn = document.getElementById("filter-clear");
1988
- if (clearBtn) {
1989
- clearBtn.addEventListener("click", () => {
1990
- activeFilters.tagIds = [];
1991
- activeFilters.priorities = [];
1992
- activeFilters.assignee = "";
1993
- activeFilters.searchText = "";
1994
- document.querySelectorAll(".filter-priority-btn").forEach((btn) => btn.classList.remove("active"));
1995
- if (searchInput) searchInput.value = "";
1996
- if (assigneeInput) assigneeInput.value = "";
1997
- renderFilterTagPills();
1998
- applyFilters();
1999
- });
2000
- }
2001
- const tagsControl = document.getElementById("filter-tags-control");
2002
- if (tagsControl) {
2003
- let renderTagDropdown2 = function() {
2004
- dropdown.innerHTML = "";
2005
- const available = allAvailableTags.filter((t) => !activeFilters.tagIds.includes(t.id));
2006
- if (available.length === 0) {
2007
- const empty = document.createElement("div");
2008
- empty.className = "filter-tag-dropdown-empty";
2009
- empty.textContent = "No tags available";
2010
- dropdown.appendChild(empty);
2011
- } else {
2012
- available.forEach((tag) => {
2013
- const opt = document.createElement("div");
2014
- opt.className = "filter-tag-dropdown-option";
2015
- opt.textContent = tag.name;
2016
- opt.addEventListener("mousedown", (e) => {
2017
- e.preventDefault();
2018
- activeFilters.tagIds.push(tag.id);
2019
- dropdown.classList.remove("open");
2020
- renderFilterTagPills();
2021
- applyFilters();
2022
- });
2023
- dropdown.appendChild(opt);
2024
- });
2025
- }
2026
- };
2027
- var renderTagDropdown = renderTagDropdown2;
2028
- const dropdownWrapper = document.createElement("div");
2029
- dropdownWrapper.className = "filter-tag-dropdown-wrapper";
2030
- const addBtn = document.createElement("button");
2031
- addBtn.className = "filter-tag-add-btn";
2032
- addBtn.textContent = "+ Tag";
2033
- const dropdown = document.createElement("div");
2034
- dropdown.className = "filter-tag-dropdown";
2035
- dropdownWrapper.appendChild(addBtn);
2036
- dropdownWrapper.appendChild(dropdown);
2037
- tagsControl.appendChild(dropdownWrapper);
2038
- addBtn.addEventListener("click", () => {
2039
- if (dropdown.classList.contains("open")) {
2040
- dropdown.classList.remove("open");
2041
- } else {
2042
- renderTagDropdown2();
2043
- const rect = addBtn.getBoundingClientRect();
2044
- dropdown.style.top = rect.bottom + 2 + "px";
2045
- dropdown.style.left = rect.left + "px";
2046
- dropdown.classList.add("open");
2047
- }
2048
- });
2049
- document.addEventListener("click", (e) => {
2050
- if (!dropdownWrapper.contains(e.target)) {
2051
- dropdown.classList.remove("open");
2052
- }
2053
- });
2054
- }
2055
- }
2056
- function initFilters() {
2057
- loadAllTags().then(() => {
2058
- initFilterBar();
2059
- });
2060
- }
2061
-
2062
- // src/board/client/darkMode.ts
2063
- function applyTheme(preference) {
2064
- if (preference === "dark") {
2065
- document.documentElement.setAttribute("data-theme", "dark");
2066
- } else if (preference === "light") {
2067
- document.documentElement.setAttribute("data-theme", "light");
2068
- } else {
2069
- document.documentElement.removeAttribute("data-theme");
2070
- }
2071
- }
2072
- async function persistThemeToServer(theme) {
2073
- try {
2074
- await fetch("/api/config", {
2075
- method: "PUT",
2076
- headers: { "Content-Type": "application/json" },
2077
- body: JSON.stringify({ board: { theme } })
2078
- });
2079
- } catch {
2080
- }
2081
- }
2082
- function getActivePreference() {
2083
- const ssrTheme = document.documentElement.getAttribute("data-theme");
2084
- if (ssrTheme === "dark" || ssrTheme === "light") return ssrTheme;
2085
- return "system";
2086
- }
2087
- function updateCheckmarks(active) {
2088
- const items = {
2089
- dark: "burger-theme-dark",
2090
- light: "burger-theme-light",
2091
- system: "burger-theme-system"
2092
- };
2093
- for (const [pref, id] of Object.entries(items)) {
2094
- const el = document.getElementById(id);
2095
- if (!el) continue;
2096
- if (pref === active) {
2097
- if (!el.textContent?.startsWith("\u2713 ")) {
2098
- el.textContent = "\u2713 " + el.textContent?.replace(/^\u2713 /, "");
2099
- }
2100
- } else {
2101
- el.textContent = el.textContent?.replace(/^\u2713 /, "") ?? el.textContent;
2102
- }
2103
- }
2104
- }
2105
- function initDarkMode() {
2106
- const activePreference = getActivePreference();
2107
- updateCheckmarks(activePreference);
2108
- document.getElementById("burger-theme-dark")?.addEventListener("click", () => {
2109
- applyTheme("dark");
2110
- updateCheckmarks("dark");
2111
- void persistThemeToServer("dark");
2112
- });
2113
- document.getElementById("burger-theme-light")?.addEventListener("click", () => {
2114
- applyTheme("light");
2115
- updateCheckmarks("light");
2116
- void persistThemeToServer("light");
2117
- });
2118
- document.getElementById("burger-theme-system")?.addEventListener("click", () => {
2119
- applyTheme("system");
2120
- updateCheckmarks("system");
2121
- void persistThemeToServer("system");
2122
- });
2123
- }
2124
-
2125
- // src/board/client/burgerMenu.ts
2126
- function initBurgerToggle(burgerBtn, burgerDropdown) {
2127
- burgerBtn.addEventListener("click", (e) => {
2128
- e.stopPropagation();
2129
- burgerDropdown.classList.toggle("open");
2130
- });
2131
- document.addEventListener("click", (e) => {
2132
- if (!burgerDropdown.contains(e.target) && e.target !== burgerBtn) {
2133
- burgerDropdown.classList.remove("open");
2134
- }
2135
- });
2136
- }
2137
- async function executePurge(purgeResultEl) {
2138
- try {
2139
- const res = await fetch("/api/tasks/purge", {
2140
- method: "POST",
2141
- headers: { "Content-Type": "application/json" },
2142
- body: JSON.stringify({})
2143
- });
2144
- if (res.ok) {
2145
- await refreshBoardCards();
2146
- } else {
2147
- purgeResultEl.textContent = "Failed to purge tasks. Please try again.";
2148
- purgeResultEl.style.color = "#dc2626";
2149
- }
2150
- } catch {
2151
- purgeResultEl.textContent = "Failed to purge tasks. Network error.";
2152
- purgeResultEl.style.color = "#dc2626";
2153
- }
2154
- }
2155
- async function executeArchive(archiveResultEl) {
2156
- try {
2157
- const res = await fetch("/api/tasks/archive", {
2158
- method: "POST",
2159
- headers: { "Content-Type": "application/json" },
2160
- body: JSON.stringify({})
2161
- });
2162
- if (res.ok) {
2163
- const data = await res.json();
2164
- await refreshBoardCards();
2165
- return data;
2166
- } else {
2167
- archiveResultEl.textContent = "Failed to archive tasks. Please try again.";
2168
- archiveResultEl.style.color = "#dc2626";
2169
- }
2170
- } catch {
2171
- archiveResultEl.textContent = "Failed to archive tasks. Network error.";
2172
- archiveResultEl.style.color = "#dc2626";
2173
- }
2174
- return null;
2175
- }
2176
- function initPurgeModal(burgerDropdown) {
2177
- const purgeModal = document.getElementById("purge-confirm-modal");
2178
- const purgeConfirmBtn = document.getElementById("purge-confirm-btn");
2179
- const purgeCancelBtn = document.getElementById("purge-cancel-btn");
2180
- const purgeResultEl = document.getElementById("purge-result");
2181
- document.getElementById("burger-purge-tasks")?.addEventListener("click", () => {
2182
- burgerDropdown.classList.remove("open");
2183
- purgeResultEl.textContent = "";
2184
- purgeModal.classList.add("show");
2185
- });
2186
- purgeCancelBtn.addEventListener("click", () => {
2187
- purgeModal.classList.remove("show");
2188
- });
2189
- purgeConfirmBtn.addEventListener("click", () => {
2190
- purgeModal.classList.remove("show");
2191
- void executePurge(purgeResultEl);
2192
- });
2193
- }
2194
- function initArchiveModal(burgerDropdown) {
2195
- const archiveModal = document.getElementById("archive-confirm-modal");
2196
- const archiveConfirmBtn = document.getElementById("archive-confirm-btn");
2197
- const archiveCancelBtn = document.getElementById("archive-cancel-btn");
2198
- const archiveResultEl = document.getElementById("archive-result");
2199
- if (!archiveModal || !archiveConfirmBtn || !archiveCancelBtn || !archiveResultEl) {
2200
- return;
2201
- }
2202
- const safeArchiveModal = archiveModal;
2203
- const safeArchiveResultEl = archiveResultEl;
2204
- document.getElementById("burger-archive-tasks")?.addEventListener("click", () => {
2205
- burgerDropdown.classList.remove("open");
2206
- safeArchiveResultEl.textContent = "";
2207
- safeArchiveModal.classList.add("show");
2208
- });
2209
- archiveCancelBtn.addEventListener("click", () => {
2210
- safeArchiveModal.classList.remove("show");
2211
- });
2212
- archiveConfirmBtn.addEventListener("click", async () => {
2213
- const result = await executeArchive(safeArchiveResultEl);
2214
- if (result !== null) {
2215
- safeArchiveModal.classList.remove("show");
2216
- }
2217
- });
2218
- }
2219
- function initVersionModal(burgerDropdown) {
2220
- const versionModal = document.getElementById("version-info-modal");
2221
- const versionCloseBtn = document.getElementById("version-info-close");
2222
- const versionTextEl = document.getElementById("version-info-text");
2223
- document.getElementById("burger-version-info")?.addEventListener("click", async () => {
2224
- burgerDropdown.classList.remove("open");
2225
- versionTextEl.textContent = "Loading...";
2226
- versionModal.classList.add("show");
2227
- try {
2228
- const res = await fetch("/api/version");
2229
- const data = await res.json();
2230
- versionTextEl.textContent = "agkan v" + data.version;
2231
- } catch {
2232
- versionTextEl.textContent = "Failed to load version.";
2233
- }
2234
- });
2235
- versionCloseBtn.addEventListener("click", () => {
2236
- versionModal.classList.remove("show");
2237
- });
2238
- }
2239
- function initExportModal(burgerDropdown) {
2240
- document.getElementById("burger-export-tasks")?.addEventListener("click", () => {
2241
- burgerDropdown.classList.remove("open");
2242
- const a = document.createElement("a");
2243
- a.href = "/api/export";
2244
- a.download = "";
2245
- document.body.appendChild(a);
2246
- a.click();
2247
- document.body.removeChild(a);
2248
- });
2249
- }
2250
- function initImportModal(burgerDropdown) {
2251
- const importModal = document.getElementById("import-modal");
2252
- const importCancelBtn = document.getElementById("import-cancel-btn");
2253
- const importConfirmBtn = document.getElementById("import-confirm-btn");
2254
- const importResultEl = document.getElementById("import-result");
2255
- const importDropZone = document.getElementById("import-drop-zone");
2256
- const importFileInput = document.getElementById("import-file-input");
2257
- if (!importModal || !importCancelBtn || !importConfirmBtn || !importResultEl || !importDropZone || !importFileInput) {
2258
- return;
2259
- }
2260
- const safeImportModal = importModal;
2261
- const safeImportCancelBtn = importCancelBtn;
2262
- const safeImportConfirmBtn = importConfirmBtn;
2263
- const safeImportResultEl = importResultEl;
2264
- const safeImportDropZone = importDropZone;
2265
- const safeImportFileInput = importFileInput;
2266
- let selectedFile = null;
2267
- function setFile(file) {
2268
- selectedFile = file;
2269
- safeImportResultEl.textContent = `Selected: ${file.name}`;
2270
- safeImportResultEl.style.color = "#64748b";
2271
- safeImportConfirmBtn.disabled = false;
2272
- }
2273
- document.getElementById("burger-import-tasks")?.addEventListener("click", () => {
2274
- burgerDropdown.classList.remove("open");
2275
- selectedFile = null;
2276
- safeImportResultEl.textContent = "";
2277
- safeImportConfirmBtn.disabled = true;
2278
- safeImportFileInput.value = "";
2279
- safeImportModal.classList.add("show");
2280
- });
2281
- safeImportCancelBtn.addEventListener("click", () => {
2282
- safeImportModal.classList.remove("show");
2283
- });
2284
- safeImportFileInput.addEventListener("change", () => {
2285
- const file = safeImportFileInput.files?.[0];
2286
- if (file) setFile(file);
2287
- });
2288
- safeImportDropZone.addEventListener("dragover", (e) => {
2289
- e.preventDefault();
2290
- safeImportDropZone.style.borderColor = "#3b82f6";
2291
- });
2292
- safeImportDropZone.addEventListener("dragleave", () => {
2293
- safeImportDropZone.style.borderColor = "#94a3b8";
2294
- });
2295
- safeImportDropZone.addEventListener("drop", (e) => {
2296
- e.preventDefault();
2297
- safeImportDropZone.style.borderColor = "#94a3b8";
2298
- const file = e.dataTransfer?.files?.[0];
2299
- if (file) setFile(file);
2300
- });
2301
- safeImportConfirmBtn.addEventListener("click", async () => {
2302
- if (!selectedFile) return;
2303
- safeImportConfirmBtn.disabled = true;
2304
- safeImportConfirmBtn.textContent = "Importing...";
2305
- try {
2306
- const text = await selectedFile.text();
2307
- const data = JSON.parse(text);
2308
- const res = await fetch("/api/import", {
2309
- method: "POST",
2310
- headers: { "Content-Type": "application/json" },
2311
- body: JSON.stringify(data)
2312
- });
2313
- const result = await res.json();
2314
- if (res.ok) {
2315
- safeImportResultEl.textContent = `Imported ${result.importedCount} task(s) successfully.`;
2316
- safeImportResultEl.style.color = "#16a34a";
2317
- setTimeout(() => {
2318
- safeImportModal.classList.remove("show");
2319
- location.reload();
2320
- }, 1500);
2321
- } else {
2322
- safeImportResultEl.textContent = "Error: " + (result.error || "Unknown error");
2323
- safeImportResultEl.style.color = "#dc2626";
2324
- }
2325
- } catch {
2326
- safeImportResultEl.textContent = "Failed to import tasks. Invalid JSON file.";
2327
- safeImportResultEl.style.color = "#dc2626";
2328
- } finally {
2329
- safeImportConfirmBtn.disabled = false;
2330
- safeImportConfirmBtn.textContent = "Import";
2331
- }
2332
- });
2333
- }
2334
- function initBurgerMenu() {
2335
- const burgerBtn = document.getElementById("burger-menu-btn");
2336
- const burgerDropdown = document.getElementById("burger-menu-dropdown");
2337
- initBurgerToggle(burgerBtn, burgerDropdown);
2338
- initPurgeModal(burgerDropdown);
2339
- initArchiveModal(burgerDropdown);
2340
- initExportModal(burgerDropdown);
2341
- initImportModal(burgerDropdown);
2342
- initVersionModal(burgerDropdown);
2343
- initDarkMode();
2344
- }
2345
-
2346
- // src/board/client/dependencyVisualization.ts
2347
- var isDependencyVisible = false;
2348
- var arrowMarkers = /* @__PURE__ */ new Map();
2349
- var scrollListener = null;
2350
- var columnScrollListener = null;
2351
- var resizeListener = null;
2352
- function getOrCreateArrowMarker(svg, color) {
2353
- const markerId = `arrow-${color.substring(1)}`;
2354
- if (!arrowMarkers.has(markerId)) {
2355
- const defs = document.createElementNS("http://www.w3.org/2000/svg", "defs");
2356
- const marker = document.createElementNS("http://www.w3.org/2000/svg", "marker");
2357
- marker.setAttribute("id", markerId);
2358
- marker.setAttribute("markerWidth", "10");
2359
- marker.setAttribute("markerHeight", "10");
2360
- marker.setAttribute("refX", "8");
2361
- marker.setAttribute("refY", "3");
2362
- marker.setAttribute("orient", "auto");
2363
- marker.setAttribute("markerUnits", "strokeWidth");
2364
- const polygon = document.createElementNS("http://www.w3.org/2000/svg", "polygon");
2365
- polygon.setAttribute("points", "0 0, 10 3, 0 6");
2366
- polygon.setAttribute("fill", color);
2367
- marker.appendChild(polygon);
2368
- defs.appendChild(marker);
2369
- svg.appendChild(defs);
2370
- arrowMarkers.set(markerId, { svg });
2371
- }
2372
- return markerId;
2373
- }
2374
- function drawBezierLine(svg, x1, y1, x2, y2, color, isHovered) {
2375
- const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
2376
- const dx = Math.abs(x2 - x1);
2377
- const isSameSide = dx < 10;
2378
- let cp1x;
2379
- let cp2x;
2380
- if (isSameSide) {
2381
- const cpOffset = 80;
2382
- cp1x = x1 + cpOffset;
2383
- cp2x = x2 + cpOffset;
2384
- } else {
2385
- const cpOffset = Math.max(dx * 0.5, 60);
2386
- cp1x = x1 < x2 ? x1 + cpOffset : x1 - cpOffset;
2387
- cp2x = x1 < x2 ? x2 - cpOffset : x2 + cpOffset;
2388
- }
2389
- const pathData = `M ${x1} ${y1} C ${cp1x} ${y1}, ${cp2x} ${y2}, ${x2} ${y2}`;
2390
- path.setAttribute("d", pathData);
2391
- path.setAttribute("stroke", color);
2392
- path.setAttribute("stroke-width", isHovered ? "2.5" : "1.5");
2393
- path.setAttribute("fill", "none");
2394
- path.setAttribute("stroke-linecap", "round");
2395
- path.setAttribute("marker-end", `url(#${getOrCreateArrowMarker(svg, color)})`);
2396
- path.setAttribute("class", "dependency-line");
2397
- return path;
2398
- }
2399
- function getCardRect(card) {
2400
- if (card === draggedCard) {
2401
- return getDraggedCardVirtualRect() ?? card.getBoundingClientRect();
2402
- }
2403
- return card.getBoundingClientRect();
2404
- }
2405
- function getCardEdgePoints(fromCard, toCard, boardRect) {
2406
- const fromRect = getCardRect(fromCard);
2407
- const toRect = getCardRect(toCard);
2408
- const fromCenterX = fromRect.left + fromRect.width / 2;
2409
- const toCenterX = toRect.left + toRect.width / 2;
2410
- const columnThreshold = 50;
2411
- let fromX;
2412
- let toX;
2413
- if (Math.abs(fromCenterX - toCenterX) < columnThreshold) {
2414
- fromX = fromRect.right - boardRect.left;
2415
- toX = toRect.right - boardRect.left;
2416
- } else if (fromCenterX <= toCenterX) {
2417
- fromX = fromRect.right - boardRect.left;
2418
- toX = toRect.left - boardRect.left;
2419
- } else {
2420
- fromX = fromRect.left - boardRect.left;
2421
- toX = toRect.right - boardRect.left;
2422
- }
2423
- return {
2424
- x1: fromX,
2425
- y1: fromRect.top - boardRect.top + fromRect.height / 2,
2426
- x2: toX,
2427
- y2: toRect.top - boardRect.top + toRect.height / 2
2428
- };
2429
- }
2430
- function createSVGOverlay() {
2431
- const boardContainer = document.querySelector(".board-container");
2432
- const existing = boardContainer.querySelector("#dependency-svg");
2433
- if (existing) {
2434
- existing.remove();
2435
- }
2436
- arrowMarkers.clear();
2437
- const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
2438
- svg.setAttribute("id", "dependency-svg");
2439
- svg.setAttribute("width", "100%");
2440
- svg.setAttribute("height", "100%");
2441
- svg.setAttribute("viewBox", `0 0 ${boardContainer.offsetWidth} ${boardContainer.offsetHeight}`);
2442
- svg.style.position = "absolute";
2443
- svg.style.top = "0";
2444
- svg.style.left = "0";
2445
- svg.style.pointerEvents = "none";
2446
- svg.style.zIndex = "5";
2447
- boardContainer.style.position = "relative";
2448
- boardContainer.appendChild(svg);
2449
- return svg;
2450
- }
2451
- function redrawDependencies() {
2452
- if (!isDependencyVisible) return;
2453
- const svg = createSVGOverlay();
2454
- const boardContainer = document.querySelector(".board-container");
2455
- svg.querySelectorAll(".dependency-line").forEach((line) => line.remove());
2456
- const cards = Array.from(document.querySelectorAll(".card"));
2457
- const cardMap = /* @__PURE__ */ new Map();
2458
- cards.forEach((card) => {
2459
- const id = Number(card.getAttribute("data-id"));
2460
- cardMap.set(id, card);
2461
- });
2462
- const hoveredCard = document.querySelector(".card:hover");
2463
- const hoveredCardId = hoveredCard ? Number(hoveredCard.getAttribute("data-id")) : null;
2464
- const hoveredBlockedBySet = /* @__PURE__ */ new Set();
2465
- const hoveredBlockingSet = /* @__PURE__ */ new Set();
2466
- if (hoveredCardId) {
2467
- const hoveredElement = cardMap.get(hoveredCardId);
2468
- if (hoveredElement) {
2469
- const blockedBy = hoveredElement.getAttribute("data-blocked-by");
2470
- const blocking = hoveredElement.getAttribute("data-blocking");
2471
- if (blockedBy) {
2472
- blockedBy.split(",").forEach((id) => {
2473
- const numId = Number(id.trim());
2474
- if (!isNaN(numId)) hoveredBlockedBySet.add(numId);
2475
- });
2476
- }
2477
- if (blocking) {
2478
- blocking.split(",").forEach((id) => {
2479
- const numId = Number(id.trim());
2480
- if (!isNaN(numId)) hoveredBlockingSet.add(numId);
2481
- });
2482
- }
2483
- }
2484
- }
2485
- const boardRect = boardContainer.getBoundingClientRect();
2486
- cards.forEach((card) => {
2487
- const cardId = Number(card.getAttribute("data-id"));
2488
- const blockedByStr = card.getAttribute("data-blocked-by");
2489
- const blockingStr = card.getAttribute("data-blocking");
2490
- if (!blockedByStr && !blockingStr) return;
2491
- const isHovered = cardId === hoveredCardId || hoveredBlockedBySet.has(cardId) || hoveredBlockingSet.has(cardId);
2492
- if (blockingStr) {
2493
- const blockingIds = blockingStr.split(",").map((s) => Number(s.trim()));
2494
- blockingIds.forEach((blockedId) => {
2495
- const blockedCard = cardMap.get(blockedId);
2496
- if (blockedCard) {
2497
- const { x1, y1, x2, y2 } = getCardEdgePoints(card, blockedCard, boardRect);
2498
- const color = isHovered || hoveredBlockedBySet.has(blockedId) ? "#ef4444" : "#cbd5e1";
2499
- const line = drawBezierLine(svg, x1, y1, x2, y2, color, isHovered);
2500
- svg.appendChild(line);
2501
- }
2502
- });
2503
- }
2504
- });
2505
- svg.setAttribute("viewBox", `0 0 ${boardContainer.offsetWidth} ${boardContainer.offsetHeight}`);
2506
- }
2507
- function handleCardHoverEvents() {
2508
- const cards = document.querySelectorAll(".card");
2509
- cards.forEach((card) => {
2510
- card.addEventListener("mouseenter", () => {
2511
- redrawDependencies();
2512
- });
2513
- card.addEventListener("mouseleave", () => {
2514
- redrawDependencies();
2515
- });
2516
- });
2517
- }
2518
- function initDependencyVisualization() {
2519
- const toggleBtn = document.getElementById("dependency-toggle");
2520
- if (!toggleBtn) return;
2521
- const redrawIfVisible = () => {
2522
- if (isDependencyVisible) {
2523
- handleCardHoverEvents();
2524
- redrawDependencies();
2525
- }
2526
- };
2527
- registerDependencyRedrawCallback2(redrawIfVisible);
2528
- registerDependencyRedrawCallback(redrawIfVisible);
2529
- toggleBtn.addEventListener("click", () => {
2530
- isDependencyVisible = !isDependencyVisible;
2531
- if (isDependencyVisible) {
2532
- toggleBtn.classList.add("active");
2533
- redrawDependencies();
2534
- handleCardHoverEvents();
2535
- const board = document.querySelector(".board");
2536
- const boardContainer = document.querySelector(".board-container");
2537
- if (board) {
2538
- scrollListener = () => redrawDependencies();
2539
- board.addEventListener("scroll", scrollListener, { passive: true });
2540
- }
2541
- columnScrollListener = () => redrawDependencies();
2542
- document.querySelectorAll(".column-body").forEach((col) => {
2543
- col.addEventListener("scroll", columnScrollListener, { passive: true });
2544
- });
2545
- if (boardContainer) {
2546
- resizeListener = () => redrawDependencies();
2547
- window.addEventListener("resize", resizeListener, { passive: true });
2548
- }
2549
- } else {
2550
- toggleBtn.classList.remove("active");
2551
- const svg = document.querySelector("#dependency-svg");
2552
- if (svg) svg.remove();
2553
- const board = document.querySelector(".board");
2554
- if (board && scrollListener) {
2555
- board.removeEventListener("scroll", scrollListener);
2556
- scrollListener = null;
2557
- }
2558
- if (columnScrollListener) {
2559
- document.querySelectorAll(".column-body").forEach((col) => {
2560
- col.removeEventListener("scroll", columnScrollListener);
2561
- });
2562
- columnScrollListener = null;
2563
- }
2564
- if (resizeListener) {
2565
- window.removeEventListener("resize", resizeListener);
2566
- resizeListener = null;
2567
- }
2568
- }
2569
- });
2570
- }
2571
-
2572
- // src/board/client/claudeStreamModal.ts
2573
- var _currentEventSource = null;
2574
- var _currentTaskId = null;
2575
- var _claudeButtonUpdateCallback = null;
2576
- function registerClaudeButtonUpdateCallback(cb) {
2577
- _claudeButtonUpdateCallback = cb;
2578
- }
2579
- function stripAnsi(text) {
2580
- return text.replace(/\x1b\[[0-9;]*m/g, "");
2581
- }
2582
- function getModalElements() {
2583
- return {
2584
- overlay: document.getElementById("claude-stream-modal"),
2585
- title: document.getElementById("claude-stream-modal-title"),
2586
- log: document.getElementById("claude-stream-log"),
2587
- status: document.getElementById("claude-stream-status"),
2588
- stopBtn: document.getElementById("claude-stream-stop-btn"),
2589
- closeBtn: document.getElementById("claude-stream-close-btn"),
2590
- modalClose: document.getElementById("claude-stream-modal-close")
2591
- };
2592
- }
2593
- function isAutoScrollEnabled(log) {
2594
- const threshold = 50;
2595
- return log.scrollHeight - log.scrollTop - log.clientHeight <= threshold;
2596
- }
2597
- function appendToLog(log, text, className) {
2598
- const shouldScroll = isAutoScrollEnabled(log);
2599
- const line = document.createElement("div");
2600
- line.textContent = text;
2601
- if (className) {
2602
- line.className = className;
2603
- }
2604
- log.appendChild(line);
2605
- if (shouldScroll) {
2606
- log.scrollTop = log.scrollHeight;
2607
- }
2608
- }
2609
- function closeEventSource() {
2610
- if (_currentEventSource) {
2611
- _currentEventSource.close();
2612
- _currentEventSource = null;
2613
- }
2614
- }
2615
- function closeClaudeStreamModal() {
2616
- closeEventSource();
2617
- const { overlay } = getModalElements();
2618
- overlay?.classList.remove("show");
2619
- }
2620
- function openClaudeStreamModal(taskId) {
2621
- closeEventSource();
2622
- _currentTaskId = taskId;
2623
- const { overlay, title, log, status, stopBtn } = getModalElements();
2624
- if (!overlay || !title || !log || !status || !stopBtn) return;
2625
- title.textContent = `Claude Output #${taskId}`;
2626
- log.innerHTML = "";
2627
- status.textContent = "Connecting...";
2628
- stopBtn.disabled = false;
2629
- stopBtn.textContent = "Stop";
2630
- overlay.classList.add("show");
2631
- const es = new EventSource(`/api/claude/tasks/${taskId}/stream`);
2632
- _currentEventSource = es;
2633
- es.onopen = () => {
2634
- if (_currentEventSource === es) {
2635
- status.textContent = "Running";
2636
- }
2637
- };
2638
- es.addEventListener("text", (event) => {
2639
- const msgEvent = event;
2640
- try {
2641
- const data = JSON.parse(msgEvent.data);
2642
- appendToLog(log, stripAnsi(data.text));
2643
- } catch {
2644
- }
2645
- });
2646
- es.addEventListener("tool_use", (event) => {
2647
- const msgEvent = event;
2648
- try {
2649
- const data = JSON.parse(msgEvent.data);
2650
- const mainArg = data.input?.path ?? data.input?.command ?? "";
2651
- const displayText = mainArg ? `\u{1F527} ${data.name}: ${mainArg}` : `\u{1F527} ${data.name}`;
2652
- appendToLog(log, displayText, "claude-stream-tool-use");
2653
- } catch {
2654
- }
2655
- });
2656
- es.addEventListener("end", (event) => {
2657
- const msgEvent = event;
2658
- try {
2659
- const data = JSON.parse(msgEvent.data);
2660
- status.textContent = `Done (exit ${data.exitCode})`;
2661
- } catch {
2662
- status.textContent = "Done";
2663
- }
2664
- closeEventSource();
2665
- stopBtn.disabled = true;
2666
- });
2667
- es.addEventListener("error", (event) => {
2668
- const msgEvent = event;
2669
- try {
2670
- const data = JSON.parse(msgEvent.data);
2671
- status.textContent = `Error: ${data.message}`;
2672
- } catch {
2673
- status.textContent = "Error";
2674
- }
2675
- closeEventSource();
2676
- stopBtn.disabled = true;
2677
- });
2678
- es.onerror = () => {
2679
- if (_currentEventSource === es) {
2680
- closeEventSource();
2681
- status.textContent = "Disconnected";
2682
- stopBtn.disabled = true;
2683
- }
2684
- };
2685
- }
2686
- async function handleStop() {
2687
- if (_currentTaskId === null) return;
2688
- const { stopBtn, status } = getModalElements();
2689
- if (!stopBtn || !status) return;
2690
- const taskId = _currentTaskId;
2691
- closeEventSource();
2692
- stopBtn.disabled = true;
2693
- stopBtn.textContent = "Stopping...";
2694
- try {
2695
- await fetch(`/api/claude/tasks/${taskId}/run`, { method: "DELETE" });
2696
- } catch {
2697
- }
2698
- stopBtn.textContent = "Stopped";
2699
- status.textContent = "Stopped";
2700
- if (_claudeButtonUpdateCallback) {
2701
- _claudeButtonUpdateCallback();
2702
- }
2703
- }
2704
- function initClaudeStreamModal() {
2705
- const { overlay, stopBtn, closeBtn, modalClose } = getModalElements();
2706
- modalClose?.addEventListener("click", () => {
2707
- closeClaudeStreamModal();
2708
- });
2709
- closeBtn?.addEventListener("click", () => {
2710
- closeClaudeStreamModal();
2711
- });
2712
- stopBtn?.addEventListener("click", () => {
2713
- void handleStop();
2714
- });
2715
- overlay?.addEventListener("click", (e) => {
2716
- if (e.target === overlay) {
2717
- closeClaudeStreamModal();
2718
- }
2719
- });
2720
- document.addEventListener("keydown", (e) => {
2721
- if (e.key === "Escape" && overlay?.classList.contains("show")) {
2722
- closeClaudeStreamModal();
2723
- }
2724
- });
2725
- }
2726
-
2727
- // src/board/client/main.ts
2728
- initDragDrop();
2729
- initAutoScroll();
2730
- initAddTaskModal();
2731
- initContextMenu();
2732
- initDetailPanel();
2733
- initBoardPolling();
2734
- initFilters();
2735
- initBurgerMenu();
2736
- initDependencyVisualization();
2737
- initClaudeButton();
2738
- initClaudeStreamModal();
2739
- registerClaudeModalCallback(openClaudeStreamModal);
2740
- registerClaudeButtonUpdateCallback(() => {
2741
- updateButtonStates(/* @__PURE__ */ new Set());
2742
- });
2743
- })();