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