forgeo-cli 0.3.0__py3-none-any.whl

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.
@@ -0,0 +1,780 @@
1
+ /* Central dashboard script: renders the home instance list and the
2
+ per-instance page (kanban backlog + logs/runs/blocker/config tabs).
3
+ Plain JS, no frameworks, refreshes every 30 seconds. */
4
+
5
+ (function () {
6
+ "use strict";
7
+
8
+ var REFRESH_MS = 30000;
9
+ var TIMEOUT_MS = 5000;
10
+ var STATUS_ORDER = ["OPEN", "BLOCKED", "COMPLETED", "FAILED"];
11
+ var TABS = ["backlog", "create", "logs", "runs", "blocker", "config"];
12
+
13
+ var page = document.body.dataset.page || "home";
14
+ var match = page === "instance" ? location.pathname.match(/^\/instances\/([^/]+)\/?/) : null;
15
+ var instanceName = match ? decodeURIComponent(match[1]) : null;
16
+ var API = instanceName ? "/api/instances/" + encodeURIComponent(instanceName) + "/" : null;
17
+ var currentTab = "backlog";
18
+
19
+ function el(tag, className, text) {
20
+ var node = document.createElement(tag);
21
+ if (className) node.className = className;
22
+ if (text !== undefined && text !== null) {
23
+ node.textContent = String(text);
24
+ }
25
+ return node;
26
+ }
27
+
28
+ function setText(id, text) {
29
+ var node = document.getElementById(id);
30
+ if (node) node.textContent = text;
31
+ }
32
+
33
+ function formatTime(iso) {
34
+ if (!iso) return "—";
35
+ var d = new Date(iso);
36
+ if (isNaN(d.getTime())) return iso;
37
+ return d.toLocaleString(undefined, {
38
+ year: "numeric",
39
+ month: "short",
40
+ day: "numeric",
41
+ hour: "2-digit",
42
+ minute: "2-digit",
43
+ });
44
+ }
45
+
46
+ function formatInterval(minutes) {
47
+ if (minutes === null || minutes === undefined) return "—";
48
+ if (minutes === 1) return "1 min";
49
+ return minutes + " mins";
50
+ }
51
+
52
+ function timeEl(label, iso) {
53
+ var span = el("span", null, label + " ");
54
+ var time = el("time", null, formatTime(iso));
55
+ time.dateTime = iso || "";
56
+ span.appendChild(time);
57
+ return span;
58
+ }
59
+
60
+ function fetchJSON(url) {
61
+ var controller = typeof AbortController === "function" ? new AbortController() : null;
62
+ var timer = controller ? setTimeout(function () { controller.abort(); }, TIMEOUT_MS) : null;
63
+ var opts = controller ? { signal: controller.signal } : undefined;
64
+ return fetch(url, opts)
65
+ .then(function (resp) {
66
+ if (!resp.ok) throw new Error("HTTP " + resp.status);
67
+ return resp.json();
68
+ })
69
+ .finally(function () {
70
+ if (timer) clearTimeout(timer);
71
+ });
72
+ }
73
+
74
+ function setDown(down) {
75
+ var notice = document.getElementById("daemon-notice");
76
+ if (notice) notice.hidden = !down;
77
+ var ft = document.getElementById("fetch-time");
78
+ if (ft && ft.parentElement) {
79
+ ft.parentElement.dataset.stale = down ? "true" : "false";
80
+ }
81
+ }
82
+
83
+ function stampFetchTime() {
84
+ setText("fetch-time", new Date().toLocaleTimeString());
85
+ }
86
+
87
+ /* ------------------------------------------------------------------ */
88
+ /* Home page */
89
+ /* ------------------------------------------------------------------ */
90
+
91
+ function renderHome(instances) {
92
+ var list = document.getElementById("instance-list");
93
+ var empty = document.getElementById("empty-state");
94
+ if (!list) return;
95
+ list.textContent = "";
96
+ setText("meta-count", String(instances.length));
97
+ if (instances.length === 0) {
98
+ if (empty) empty.hidden = false;
99
+ return;
100
+ }
101
+ if (empty) empty.hidden = true;
102
+
103
+ instances.forEach(function (inst) {
104
+ var card = el("a", "instance-card", null);
105
+ card.href = "instances/" + encodeURIComponent(inst.name) + "/";
106
+
107
+ var head = el("div", "instance-card__head");
108
+ head.appendChild(el("span", "instance-card__name", inst.name));
109
+ head.appendChild(
110
+ el(
111
+ "span",
112
+ "badge badge--" + (inst.daemon_running ? "COMPLETED" : "FAILED"),
113
+ inst.daemon_running ? "running" : "stopped"
114
+ )
115
+ );
116
+ card.appendChild(head);
117
+
118
+ var grid = el("div", "instance-card__grid");
119
+
120
+ var info = el("div", "instance-card__info");
121
+ info.appendChild(el("span", "instance-card__label", "repo"));
122
+ info.appendChild(el("span", "instance-card__value", inst.repo || "(unavailable)"));
123
+ grid.appendChild(info);
124
+
125
+ info = el("div", "instance-card__info");
126
+ info.appendChild(el("span", "instance-card__label", "last outcome"));
127
+ info.appendChild(el("span", "instance-card__value", inst.last_outcome || "—"));
128
+ grid.appendChild(info);
129
+
130
+ info = el("div", "instance-card__info");
131
+ info.appendChild(el("span", "instance-card__label", "next run"));
132
+ info.appendChild(el("span", "instance-card__value", formatTime(inst.next_run_at)));
133
+ grid.appendChild(info);
134
+
135
+ var counts = el("div", "instance-card__counts");
136
+ STATUS_ORDER.forEach(function (status) {
137
+ counts.appendChild(
138
+ el(
139
+ "span",
140
+ "count-chip count-chip--" + status,
141
+ status + " " + (inst.backlog_counts[status] || 0)
142
+ )
143
+ );
144
+ });
145
+ grid.appendChild(counts);
146
+
147
+ card.appendChild(grid);
148
+ list.appendChild(card);
149
+ });
150
+ }
151
+
152
+ function refreshHome() {
153
+ fetchJSON("/api/instances")
154
+ .then(function (data) {
155
+ renderHome(data);
156
+ setDown(false);
157
+ stampFetchTime();
158
+ })
159
+ .catch(function () {
160
+ setDown(true);
161
+ });
162
+ }
163
+
164
+ /* ------------------------------------------------------------------ */
165
+ /* Instance page: kanban backlog */
166
+ /* ------------------------------------------------------------------ */
167
+
168
+ function buildColumns() {
169
+ var board = document.getElementById("tab-backlog");
170
+ if (!board) return;
171
+ STATUS_ORDER.forEach(function (status) {
172
+ var col = document.createElement("section");
173
+ col.className = "status-col";
174
+ col.dataset.status = status;
175
+
176
+ var head = el("div", "status-col__head");
177
+ var label = el("div", "status-col__label");
178
+ label.appendChild(el("span", "status-col__dot"));
179
+ label.appendChild(el("span", "status-col__name", status));
180
+ head.appendChild(label);
181
+ head.appendChild(el("span", "status-col__count", "0"));
182
+
183
+ var list = el("div", "status-col__list");
184
+ col.appendChild(head);
185
+ col.appendChild(list);
186
+ board.appendChild(col);
187
+ });
188
+ }
189
+
190
+ function renderTasks(tasks) {
191
+ var board = document.getElementById("tab-backlog");
192
+ var empty = document.getElementById("empty-state");
193
+ if (!board) return;
194
+ var hasAny = false;
195
+
196
+ STATUS_ORDER.forEach(function (status) {
197
+ var col = board.querySelector('.status-col[data-status="' + status + '"]');
198
+ if (!col) return;
199
+ var list = col.querySelector(".status-col__list");
200
+ var count = col.querySelector(".status-col__count");
201
+ var group = tasks.filter(function (t) {
202
+ return (t.status || "OPEN").toUpperCase() === status;
203
+ });
204
+ count.textContent = String(group.length);
205
+ list.textContent = "";
206
+
207
+ if (group.length === 0) {
208
+ list.appendChild(el("p", "status-col__empty", "nothing here"));
209
+ return;
210
+ }
211
+ hasAny = true;
212
+
213
+ group.forEach(function (task) {
214
+ var card = el("article", "task");
215
+ card.setAttribute("tabindex", "0");
216
+ card.setAttribute("role", "button");
217
+ card.addEventListener("click", function () {
218
+ openModal(task, card);
219
+ });
220
+ card.addEventListener("keydown", function (event) {
221
+ if (event.key === "Enter" || event.key === " ") {
222
+ event.preventDefault();
223
+ openModal(task, card);
224
+ }
225
+ });
226
+ var top = el("div", "task__top");
227
+ top.appendChild(el("span", "task__id", task.id));
228
+ top.appendChild(el("span", "badge badge--" + status, status));
229
+ card.appendChild(top);
230
+ card.appendChild(el("h3", "task__title", task.title));
231
+ if (task.description) {
232
+ card.appendChild(el("p", "task__desc", task.description));
233
+ }
234
+ var times = el("div", "task__times");
235
+ times.appendChild(timeEl("created", task.created_at));
236
+ times.appendChild(timeEl("updated", task.updated_at));
237
+ card.appendChild(times);
238
+ list.appendChild(card);
239
+ });
240
+ });
241
+
242
+ if (empty) empty.hidden = hasAny || tasks.length > 0;
243
+
244
+ syncModal(tasks);
245
+ }
246
+
247
+ /* ------------------------------------------------------------------ */
248
+ /* Task detail modal */
249
+ /* ------------------------------------------------------------------ */
250
+
251
+ var modalTaskId = null;
252
+ var modalTask = null;
253
+ var modalLastFocus = null;
254
+
255
+ function listItems(values) {
256
+ var ul = el("ul", "modal__list");
257
+ if (!values || values.length === 0) {
258
+ ul.appendChild(el("li", "modal__empty", "—"));
259
+ } else {
260
+ values.forEach(function (value) {
261
+ ul.appendChild(el("li", null, value));
262
+ });
263
+ }
264
+ return ul;
265
+ }
266
+
267
+ function showModalSection(id, show) {
268
+ var node = document.getElementById(id);
269
+ if (node) node.hidden = !show;
270
+ }
271
+
272
+ function renderModal(task) {
273
+ if (!task) return;
274
+ modalTask = task;
275
+ setText("task-modal-id", task.id);
276
+ setText("task-modal-title", task.title || "");
277
+ var badge = document.getElementById("task-modal-status");
278
+ if (badge) {
279
+ var status = (task.status || "OPEN").toUpperCase();
280
+ badge.textContent = status;
281
+ badge.className = "badge badge--" + status;
282
+ }
283
+
284
+ showModalSection("task-modal-edit", (task.status || "OPEN") === "OPEN");
285
+
286
+ setText("task-modal-description", task.description || "");
287
+ var acceptance = document.getElementById("task-modal-acceptance");
288
+ if (acceptance) {
289
+ acceptance.textContent = "";
290
+ acceptance.appendChild(listItems(task.acceptance_criteria));
291
+ }
292
+ var dependencies = document.getElementById("task-modal-dependencies");
293
+ if (dependencies) {
294
+ dependencies.textContent = "";
295
+ dependencies.appendChild(listItems(task.dependencies));
296
+ }
297
+ var files = document.getElementById("task-modal-files");
298
+ if (files) {
299
+ files.textContent = "";
300
+ files.appendChild(listItems(task.files_to_modify));
301
+ }
302
+ var command = document.getElementById("task-modal-command");
303
+ if (command) {
304
+ command.textContent = Array.isArray(task.agent_command)
305
+ ? task.agent_command.join(" ")
306
+ : task.agent_command || "";
307
+ }
308
+ var created = document.getElementById("task-modal-created");
309
+ if (created) created.textContent = formatTime(task.created_at);
310
+ var updated = document.getElementById("task-modal-updated");
311
+ if (updated) updated.textContent = formatTime(task.updated_at);
312
+
313
+ showModalSection("task-modal-description-section", Boolean(task.description));
314
+ showModalSection(
315
+ "task-modal-acceptance-section",
316
+ task.acceptance_criteria && task.acceptance_criteria.length > 0
317
+ );
318
+ showModalSection(
319
+ "task-modal-dependencies-section",
320
+ task.dependencies && task.dependencies.length > 0
321
+ );
322
+ showModalSection(
323
+ "task-modal-files-section",
324
+ task.files_to_modify && task.files_to_modify.length > 0
325
+ );
326
+ showModalSection("task-modal-command-section", Boolean(task.agent_command));
327
+ }
328
+
329
+ function splitLines(value) {
330
+ return String(value)
331
+ .split(/\r?\n/)
332
+ .map(function (line) {
333
+ return line.trim();
334
+ })
335
+ .filter(Boolean);
336
+ }
337
+
338
+ function enterEditMode() {
339
+ if (!modalTask) return;
340
+ var setValue = function (id, text) {
341
+ var node = document.getElementById(id);
342
+ if (node) node.value = text;
343
+ };
344
+ setValue("task-edit-title", modalTask.title || "");
345
+ setValue("task-edit-description", modalTask.description || "");
346
+ setValue("task-edit-acceptance", (modalTask.acceptance_criteria || []).join("\n"));
347
+ setValue("task-edit-dependencies", (modalTask.dependencies || []).join("\n"));
348
+ setValue("task-edit-files", (modalTask.files_to_modify || []).join("\n"));
349
+ setValue(
350
+ "task-edit-command",
351
+ Array.isArray(modalTask.agent_command)
352
+ ? modalTask.agent_command.join(" ")
353
+ : modalTask.agent_command || ""
354
+ );
355
+ setValue(
356
+ "task-edit-timeout",
357
+ modalTask.agent_timeout_seconds === null || modalTask.agent_timeout_seconds === undefined
358
+ ? ""
359
+ : String(modalTask.agent_timeout_seconds)
360
+ );
361
+
362
+ showModalSection("task-modal-view", false);
363
+ showModalSection("task-modal-edit-form", true);
364
+ showModalSection("task-modal-edit", false);
365
+ var error = document.getElementById("task-modal-error");
366
+ if (error) error.hidden = true;
367
+ }
368
+
369
+ function exitEditMode() {
370
+ showModalSection("task-modal-view", true);
371
+ showModalSection("task-modal-edit-form", false);
372
+ showModalSection("task-modal-edit", true);
373
+ var error = document.getElementById("task-modal-error");
374
+ if (error) error.hidden = true;
375
+ }
376
+
377
+ function collectEditForm() {
378
+ var value = function (id) {
379
+ var node = document.getElementById(id);
380
+ return node ? node.value : "";
381
+ };
382
+ var command = value("task-edit-command").trim();
383
+ var timeout = value("task-edit-timeout").trim();
384
+ var updates = {
385
+ title: value("task-edit-title").trim(),
386
+ description: value("task-edit-description").trim(),
387
+ acceptance_criteria: splitLines(value("task-edit-acceptance")),
388
+ dependencies: splitLines(value("task-edit-dependencies")),
389
+ files_to_modify: splitLines(value("task-edit-files")),
390
+ agent_command: command ? command : null,
391
+ agent_timeout_seconds: timeout === "" ? null : Number(timeout),
392
+ };
393
+ return updates;
394
+ }
395
+
396
+ function saveTask() {
397
+ if (!API || !modalTaskId) return;
398
+ var error = document.getElementById("task-modal-error");
399
+ if (error) error.hidden = true;
400
+
401
+ var title = document.getElementById("task-edit-title");
402
+ if (!title || !title.value.trim()) {
403
+ if (error) {
404
+ error.textContent = "title is required";
405
+ error.hidden = false;
406
+ }
407
+ return;
408
+ }
409
+ var timeout = document.getElementById("task-edit-timeout");
410
+ if (timeout && timeout.value.trim() !== "" && isNaN(Number(timeout.value))) {
411
+ if (error) {
412
+ error.textContent = "agent timeout must be a number";
413
+ error.hidden = false;
414
+ }
415
+ return;
416
+ }
417
+
418
+ fetch(API + "tasks/" + encodeURIComponent(modalTaskId), {
419
+ method: "PATCH",
420
+ headers: { "Content-Type": "application/json" },
421
+ body: JSON.stringify(collectEditForm()),
422
+ })
423
+ .then(function (resp) {
424
+ if (!resp.ok) {
425
+ return resp.json().then(function (data) {
426
+ throw new Error((data && data.error) || "HTTP " + resp.status);
427
+ });
428
+ }
429
+ return resp.json();
430
+ })
431
+ .then(function (task) {
432
+ renderModal(task);
433
+ exitEditMode();
434
+ return fetchJSON(API + "tasks");
435
+ })
436
+ .then(function (tasks) {
437
+ renderTasks(tasks || []);
438
+ })
439
+ .catch(function (err) {
440
+ if (error) {
441
+ error.textContent = err.message || "failed to save task";
442
+ error.hidden = false;
443
+ }
444
+ });
445
+ }
446
+
447
+ function openModal(task, opener) {
448
+ if (!task) return;
449
+ modalTaskId = task.id;
450
+ modalLastFocus = opener || document.activeElement;
451
+ exitEditMode();
452
+ renderModal(task);
453
+ var modal = document.getElementById("task-modal");
454
+ if (modal) {
455
+ modal.hidden = false;
456
+ var dialog = modal.querySelector(".modal");
457
+ if (dialog) dialog.focus();
458
+ }
459
+ }
460
+
461
+ function closeModal() {
462
+ var modal = document.getElementById("task-modal");
463
+ if (!modal || modal.hidden) return;
464
+ modal.hidden = true;
465
+ modalTaskId = null;
466
+ if (modalLastFocus && modalLastFocus.focus) modalLastFocus.focus();
467
+ modalLastFocus = null;
468
+ }
469
+
470
+ function syncModal(tasks) {
471
+ if (!modalTaskId) return;
472
+ var found = tasks.filter(function (t) {
473
+ return t.id === modalTaskId;
474
+ });
475
+ if (found.length === 0) {
476
+ closeModal();
477
+ } else {
478
+ renderModal(found[0]);
479
+ }
480
+ }
481
+
482
+ function renderStatus(status) {
483
+ setText("forgeo-name", status.name || instanceName);
484
+ var daemon = Boolean(status.daemon_running);
485
+ var badge = document.getElementById("meta-daemon");
486
+ if (badge) {
487
+ badge.textContent = daemon ? "running" : "stopped";
488
+ badge.className = "daemon-badge daemon-badge--" + (daemon ? "running" : "stopped");
489
+ }
490
+ setText("meta-repo", status.repo || "—");
491
+ setText("meta-interval", formatInterval(status.interval_minutes));
492
+ setText("meta-next", formatTime(status.next_run_at));
493
+ setText("meta-outcome", status.last_outcome || "—");
494
+ }
495
+
496
+ /* ------------------------------------------------------------------ */
497
+ /* Instance page: non-backlog tabs */
498
+ /* ------------------------------------------------------------------ */
499
+
500
+ function outcomeBadge(outcome) {
501
+ if (outcome === "SUCCESS") return "COMPLETED";
502
+ if (outcome === "BLOCKED") return "BLOCKED";
503
+ if (outcome === "ERROR" || outcome === "DIRTY") return "FAILED";
504
+ return "OPEN";
505
+ }
506
+
507
+ function renderRuns(runs) {
508
+ var body = document.getElementById("runs-body");
509
+ if (!body) return;
510
+ body.textContent = "";
511
+ if (!runs.length) {
512
+ body.appendChild(el("p", "status-col__empty", "no runs recorded"));
513
+ return;
514
+ }
515
+ var table = el("table", "run-table");
516
+ var thead = el("thead");
517
+ var headRow = el("tr");
518
+ ["finished", "kind", "task", "outcome", "exit", "commit", "duration"].forEach(function (h) {
519
+ headRow.appendChild(el("th", null, h));
520
+ });
521
+ thead.appendChild(headRow);
522
+ table.appendChild(thead);
523
+
524
+ var tbody = el("tbody");
525
+ runs.forEach(function (run) {
526
+ var row = el("tr");
527
+ row.appendChild(el("td", null, formatTime(run.finished_at)));
528
+ row.appendChild(el("td", null, run.kind || "—"));
529
+ row.appendChild(el("td", "mono", run.task_id || "—"));
530
+ row.appendChild(el("td", "badge badge--" + outcomeBadge(run.outcome), run.outcome));
531
+ row.appendChild(
532
+ el("td", "mono", run.agent_exit_code === null || run.agent_exit_code === undefined ? "—" : String(run.agent_exit_code))
533
+ );
534
+ row.appendChild(el("td", "mono", run.commit_sha || "—"));
535
+ row.appendChild(
536
+ el("td", "mono", run.duration_seconds === null || run.duration_seconds === undefined ? "—" : run.duration_seconds + "s")
537
+ );
538
+ tbody.appendChild(row);
539
+ });
540
+ table.appendChild(tbody);
541
+ body.appendChild(table);
542
+ }
543
+
544
+ function renderTextPanel(id, text, fallback) {
545
+ var node = document.getElementById(id);
546
+ if (node) node.textContent = text || fallback;
547
+ }
548
+
549
+ function loadTab(tab) {
550
+ if (!API || tab === "backlog" || tab === "create") return;
551
+ if (tab === "logs") {
552
+ fetchJSON(API + "logs?lines=200")
553
+ .then(function (data) {
554
+ renderTextPanel("logs-body", (data.lines || []).join("\n"), "(empty log)");
555
+ setDown(false);
556
+ })
557
+ .catch(function () {
558
+ setDown(true);
559
+ });
560
+ } else if (tab === "runs") {
561
+ fetchJSON(API + "runs?limit=50")
562
+ .then(function (data) {
563
+ renderRuns(data);
564
+ setDown(false);
565
+ })
566
+ .catch(function () {
567
+ setDown(true);
568
+ });
569
+ } else if (tab === "blocker") {
570
+ fetchJSON(API + "blocker")
571
+ .then(function (data) {
572
+ renderTextPanel("blocker-body", data.content, "(no blocker)");
573
+ setDown(false);
574
+ })
575
+ .catch(function () {
576
+ setDown(true);
577
+ });
578
+ } else if (tab === "config") {
579
+ fetchJSON(API + "config")
580
+ .then(function (data) {
581
+ renderTextPanel("config-body", JSON.stringify(data, null, 2), "(no config)");
582
+ setDown(false);
583
+ })
584
+ .catch(function () {
585
+ setDown(true);
586
+ });
587
+ }
588
+ }
589
+
590
+ function activate(tab) {
591
+ currentTab = tab;
592
+ TABS.forEach(function (t) {
593
+ var panel = document.getElementById("tab-" + t);
594
+ if (panel) panel.hidden = t !== tab;
595
+ });
596
+ var buttons = document.querySelectorAll(".tab[data-tab]");
597
+ for (var i = 0; i < buttons.length; i++) {
598
+ buttons[i].classList.toggle("is-active", buttons[i].dataset.tab === tab);
599
+ }
600
+ if (tab !== "backlog") loadTab(tab);
601
+ }
602
+
603
+ function refreshInstance() {
604
+ if (!API) return;
605
+ Promise.all([fetchJSON(API + "tasks"), fetchJSON(API + "status")])
606
+ .then(function (results) {
607
+ renderTasks(results[0] || []);
608
+ renderStatus(results[1] || {});
609
+ setDown(false);
610
+ stampFetchTime();
611
+ })
612
+ .catch(function () {
613
+ setDown(true);
614
+ });
615
+ }
616
+
617
+ function wireNewTask() {
618
+ var form = document.getElementById("new-task");
619
+ var error = document.getElementById("new-task-error");
620
+ if (!form || !API) return;
621
+
622
+ var criteria = [];
623
+ var criteriaInput = document.getElementById("task-acceptance-input");
624
+ var criteriaList = document.getElementById("task-acceptance-list");
625
+
626
+ function renderCriteria() {
627
+ if (!criteriaList) return;
628
+ criteriaList.textContent = "";
629
+ criteria.forEach(function (criterion, index) {
630
+ var chip = el("li", "new-task__criteria-chip", null);
631
+ chip.appendChild(document.createTextNode(criterion));
632
+ var remove = document.createElement("button");
633
+ remove.type = "button";
634
+ remove.className = "new-task__criteria-remove";
635
+ remove.setAttribute("aria-label", "Remove acceptance criterion");
636
+ remove.textContent = "×";
637
+ remove.addEventListener("click", function () {
638
+ criteria.splice(index, 1);
639
+ renderCriteria();
640
+ });
641
+ chip.appendChild(remove);
642
+ criteriaList.appendChild(chip);
643
+ });
644
+ }
645
+
646
+ function addCriterion() {
647
+ if (!criteriaInput) return;
648
+ var value = criteriaInput.value.trim();
649
+ if (!value) return;
650
+ criteria.push(value);
651
+ criteriaInput.value = "";
652
+ criteriaInput.focus();
653
+ renderCriteria();
654
+ }
655
+
656
+ var addButton = document.getElementById("task-acceptance-add");
657
+ if (addButton) addButton.addEventListener("click", addCriterion);
658
+ if (criteriaInput) {
659
+ criteriaInput.addEventListener("keydown", function (event) {
660
+ if (event.key === "Enter") {
661
+ event.preventDefault();
662
+ addCriterion();
663
+ }
664
+ });
665
+ }
666
+
667
+ form.addEventListener("submit", function (event) {
668
+ event.preventDefault();
669
+ if (error) error.hidden = true;
670
+
671
+ var title = document.getElementById("task-title").value.trim();
672
+ if (!title) {
673
+ if (error) {
674
+ error.textContent = "title is required";
675
+ error.hidden = false;
676
+ }
677
+ return;
678
+ }
679
+ var description = document.getElementById("task-description").value.trim();
680
+ if (!description) {
681
+ if (error) {
682
+ error.textContent = "description is required";
683
+ error.hidden = false;
684
+ }
685
+ return;
686
+ }
687
+ var commandInput = document.getElementById("task-command");
688
+ var command = commandInput ? commandInput.value.trim() : "";
689
+
690
+ fetch(API + "tasks", {
691
+ method: "POST",
692
+ headers: { "Content-Type": "application/json" },
693
+ body: JSON.stringify({
694
+ title: title,
695
+ description: description,
696
+ acceptance_criteria: criteria.slice(),
697
+ agent_command: command ? command : null,
698
+ }),
699
+ })
700
+ .then(function (resp) {
701
+ if (!resp.ok) {
702
+ return resp.json().then(function (data) {
703
+ throw new Error((data && data.error) || "HTTP " + resp.status);
704
+ });
705
+ }
706
+ return resp.json();
707
+ })
708
+ .then(function () {
709
+ form.reset();
710
+ criteria = [];
711
+ renderCriteria();
712
+ return fetchJSON(API + "tasks");
713
+ })
714
+ .then(function (tasks) {
715
+ renderTasks(tasks || []);
716
+ })
717
+ .catch(function (err) {
718
+ if (error) {
719
+ error.textContent = err.message || "failed to add task";
720
+ error.hidden = false;
721
+ }
722
+ });
723
+ });
724
+ }
725
+
726
+ function wire() {
727
+ if (page === "instance") {
728
+ wireNewTask();
729
+ var buttons = document.querySelectorAll(".tab[data-tab]");
730
+ for (var i = 0; i < buttons.length; i++) {
731
+ buttons[i].addEventListener("click", function () {
732
+ activate(this.dataset.tab);
733
+ });
734
+ }
735
+
736
+ var closeBtn = document.getElementById("task-modal-close");
737
+ if (closeBtn) closeBtn.addEventListener("click", closeModal);
738
+ var editBtn = document.getElementById("task-modal-edit");
739
+ if (editBtn) editBtn.addEventListener("click", enterEditMode);
740
+ var cancelBtn = document.getElementById("task-modal-cancel");
741
+ if (cancelBtn) cancelBtn.addEventListener("click", exitEditMode);
742
+ var editForm = document.getElementById("task-modal-edit-form");
743
+ if (editForm) {
744
+ editForm.addEventListener("submit", function (event) {
745
+ event.preventDefault();
746
+ saveTask();
747
+ });
748
+ }
749
+ var modal = document.getElementById("task-modal");
750
+ if (modal) {
751
+ modal.addEventListener("click", function (event) {
752
+ if (event.target === modal) closeModal();
753
+ });
754
+ }
755
+ document.addEventListener("keydown", function (event) {
756
+ if (event.key === "Escape") closeModal();
757
+ });
758
+ }
759
+ }
760
+
761
+ /* ------------------------------------------------------------------ */
762
+ /* Boot */
763
+ /* ------------------------------------------------------------------ */
764
+
765
+ function refresh() {
766
+ if (page === "home") {
767
+ refreshHome();
768
+ } else if (page === "instance") {
769
+ refreshInstance();
770
+ if (currentTab !== "backlog") loadTab(currentTab);
771
+ }
772
+ }
773
+
774
+ if (page === "instance") {
775
+ buildColumns();
776
+ }
777
+ wire();
778
+ refresh();
779
+ setInterval(refresh, REFRESH_MS);
780
+ })();