@nestos/kanban 0.5.1 → 0.5.2

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 (147) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +73 -29
  3. package/dist/{activity-C6xDM-cg.d.cts → activity-BbC4sgKL.d.cts} +28 -2
  4. package/dist/{activity-Dw8DOw0L.d.ts → activity-Czlan5xW.d.ts} +28 -2
  5. package/dist/activity-module-DaO1Rry0.d.cts +22 -0
  6. package/dist/activity-module-T22wliZu.d.ts +22 -0
  7. package/dist/activity-ports-Dqxa86zT.d.cts +12 -0
  8. package/dist/activity-ports-cxfrelaL.d.ts +12 -0
  9. package/dist/activity.cjs +10 -282
  10. package/dist/activity.cjs.map +1 -1
  11. package/dist/activity.d.cts +8 -30
  12. package/dist/activity.d.ts +8 -30
  13. package/dist/activity.js +9 -281
  14. package/dist/activity.js.map +1 -1
  15. package/dist/{chunk-O4OGAAV4.js → chunk-3JYLJPE5.js} +29 -5
  16. package/dist/chunk-3JYLJPE5.js.map +1 -0
  17. package/dist/{chunk-ATUFFNLV.js → chunk-3LEIHB7Y.js} +5 -5
  18. package/dist/chunk-4XF6LQSU.cjs.map +1 -1
  19. package/dist/chunk-5Q7WB63A.cjs +282 -0
  20. package/dist/chunk-5Q7WB63A.cjs.map +1 -0
  21. package/dist/{chunk-PSKQAFDF.cjs → chunk-7KDOTBRX.cjs} +1 -1
  22. package/dist/chunk-7KDOTBRX.cjs.map +1 -0
  23. package/dist/{chunk-5DLZWW5Y.js → chunk-AHHEGTUA.js} +3 -3
  24. package/dist/chunk-AOHQCX7K.cjs +95 -0
  25. package/dist/chunk-AOHQCX7K.cjs.map +1 -0
  26. package/dist/{chunk-IUI5CI2S.cjs → chunk-DINLR6E6.cjs} +96 -92
  27. package/dist/chunk-DINLR6E6.cjs.map +1 -0
  28. package/dist/{chunk-L44TM4EE.cjs → chunk-EYTVC2H5.cjs} +568 -454
  29. package/dist/chunk-EYTVC2H5.cjs.map +1 -0
  30. package/dist/chunk-FEE2YBZ7.js +1972 -0
  31. package/dist/chunk-FEE2YBZ7.js.map +1 -0
  32. package/dist/chunk-FNO5K3A7.js +282 -0
  33. package/dist/chunk-FNO5K3A7.js.map +1 -0
  34. package/dist/chunk-IHHDXZPZ.js +382 -0
  35. package/dist/chunk-IHHDXZPZ.js.map +1 -0
  36. package/dist/chunk-J3EAIVA3.js +280 -0
  37. package/dist/chunk-J3EAIVA3.js.map +1 -0
  38. package/dist/{chunk-DFTOTBGF.js → chunk-KH3WF3K4.js} +20 -16
  39. package/dist/chunk-KH3WF3K4.js.map +1 -0
  40. package/dist/{chunk-TB2RZGDV.js → chunk-M35X5PHO.js} +14 -1
  41. package/dist/chunk-M35X5PHO.js.map +1 -0
  42. package/dist/chunk-QRGZD2JU.cjs +1972 -0
  43. package/dist/chunk-QRGZD2JU.cjs.map +1 -0
  44. package/dist/{chunk-4KF7KUAI.cjs → chunk-RBOER6EW.cjs} +6 -6
  45. package/dist/chunk-RBOER6EW.cjs.map +1 -0
  46. package/dist/{chunk-6MN3H3YF.cjs → chunk-RSH6QSEC.cjs} +16 -3
  47. package/dist/chunk-RSH6QSEC.cjs.map +1 -0
  48. package/dist/{chunk-T7P5EIC4.cjs → chunk-VMRDXOLX.cjs} +33 -33
  49. package/dist/chunk-VMRDXOLX.cjs.map +1 -0
  50. package/dist/{chunk-5XKOCKY2.js → chunk-X4UHZX6F.js} +1 -1
  51. package/dist/chunk-XBSGUVCY.cjs +382 -0
  52. package/dist/chunk-XBSGUVCY.cjs.map +1 -0
  53. package/dist/{chunk-OHQTKGQN.js → chunk-XKDZHAUE.js} +242 -128
  54. package/dist/chunk-XKDZHAUE.js.map +1 -0
  55. package/dist/chunk-ZUZGAO6G.cjs +280 -0
  56. package/dist/chunk-ZUZGAO6G.cjs.map +1 -0
  57. package/dist/{content-CAIp1njn.d.cts → content-C5aJZFlw.d.cts} +1 -1
  58. package/dist/{content-CGMJdyH_.d.ts → content-hLH5BNud.d.ts} +1 -1
  59. package/dist/contracts/index.cjs +13 -3
  60. package/dist/contracts/index.cjs.map +1 -1
  61. package/dist/contracts/index.d.cts +5 -4
  62. package/dist/contracts/index.d.ts +5 -4
  63. package/dist/contracts/index.js +14 -4
  64. package/dist/{discovery-DxpirOz3.d.cts → discovery-9E-LfwIw.d.cts} +1 -1
  65. package/dist/{discovery-gSqWpuRm.d.ts → discovery-D60yrjej.d.ts} +1 -1
  66. package/dist/discovery.cjs +63 -65
  67. package/dist/discovery.cjs.map +1 -1
  68. package/dist/discovery.d.cts +4 -4
  69. package/dist/discovery.d.ts +4 -4
  70. package/dist/discovery.js +5 -7
  71. package/dist/discovery.js.map +1 -1
  72. package/dist/execution-bridge-fpCnH6z5.d.cts +52 -0
  73. package/dist/execution-bridge-rk0uVy_n.d.ts +52 -0
  74. package/dist/execution.cjs +6 -356
  75. package/dist/execution.cjs.map +1 -1
  76. package/dist/execution.d.cts +4 -51
  77. package/dist/execution.d.ts +4 -51
  78. package/dist/execution.js +9 -359
  79. package/dist/execution.js.map +1 -1
  80. package/dist/index.cjs +10 -12
  81. package/dist/index.cjs.map +1 -1
  82. package/dist/index.d.cts +9 -16
  83. package/dist/index.d.ts +9 -16
  84. package/dist/index.js +10 -12
  85. package/dist/index.js.map +1 -1
  86. package/dist/{module-CWNK2BMj.d.ts → module-CYfE7YhM.d.ts} +2 -2
  87. package/dist/{module-BbvwdCDF.d.cts → module-Ca9L3qCn.d.cts} +2 -2
  88. package/dist/{planning-CcFZYdIL.d.cts → planning-CHrOqQ6b.d.cts} +45 -10
  89. package/dist/{planning-CcFZYdIL.d.ts → planning-CHrOqQ6b.d.ts} +45 -10
  90. package/dist/{ports-Bn6ro2FB.d.ts → ports-B0McVg7B.d.ts} +4 -3
  91. package/dist/ports-BeGO1VRI.d.cts +49 -0
  92. package/dist/{ports-CaL6geqR.d.cts → ports-GQy0x-RI.d.cts} +4 -3
  93. package/dist/ports-ykGiNUSx.d.ts +49 -0
  94. package/dist/runtime/index.cjs +205 -0
  95. package/dist/runtime/index.cjs.map +1 -0
  96. package/dist/runtime/index.d.cts +75 -0
  97. package/dist/runtime/index.d.ts +75 -0
  98. package/dist/runtime/index.js +205 -0
  99. package/dist/runtime/index.js.map +1 -0
  100. package/dist/runtime/integration.cjs +423 -0
  101. package/dist/runtime/integration.cjs.map +1 -0
  102. package/dist/runtime/integration.d.cts +658 -0
  103. package/dist/runtime/integration.d.ts +658 -0
  104. package/dist/runtime/integration.js +423 -0
  105. package/dist/runtime/integration.js.map +1 -0
  106. package/dist/storage.cjs +9 -277
  107. package/dist/storage.cjs.map +1 -1
  108. package/dist/storage.d.cts +6 -6
  109. package/dist/storage.d.ts +6 -6
  110. package/dist/storage.js +11 -279
  111. package/dist/storage.js.map +1 -1
  112. package/dist/{store-q63eSMoT.d.cts → store-DPwHzGiz.d.cts} +2 -2
  113. package/dist/{store-Ct0gOAAg.d.ts → store-DgdrPCV9.d.ts} +2 -2
  114. package/dist/task-projection-CSorlMws.d.ts +11 -0
  115. package/dist/task-projection-DKKB_ueb.d.cts +11 -0
  116. package/examples/standalone.ts +22 -0
  117. package/package.json +117 -87
  118. package/dist/chunk-2YFPV5GF.cjs +0 -16
  119. package/dist/chunk-2YFPV5GF.cjs.map +0 -1
  120. package/dist/chunk-4KF7KUAI.cjs.map +0 -1
  121. package/dist/chunk-6MN3H3YF.cjs.map +0 -1
  122. package/dist/chunk-DFTOTBGF.js.map +0 -1
  123. package/dist/chunk-G6FCDLDF.js +0 -16
  124. package/dist/chunk-G6FCDLDF.js.map +0 -1
  125. package/dist/chunk-IUI5CI2S.cjs.map +0 -1
  126. package/dist/chunk-KYNYPKKH.cjs +0 -71
  127. package/dist/chunk-KYNYPKKH.cjs.map +0 -1
  128. package/dist/chunk-L44TM4EE.cjs.map +0 -1
  129. package/dist/chunk-LSE6JG5I.cjs +0 -20
  130. package/dist/chunk-LSE6JG5I.cjs.map +0 -1
  131. package/dist/chunk-O4OGAAV4.js.map +0 -1
  132. package/dist/chunk-OHQTKGQN.js.map +0 -1
  133. package/dist/chunk-PSKQAFDF.cjs.map +0 -1
  134. package/dist/chunk-QTMTBEBA.js +0 -20
  135. package/dist/chunk-QTMTBEBA.js.map +0 -1
  136. package/dist/chunk-T7P5EIC4.cjs.map +0 -1
  137. package/dist/chunk-TB2RZGDV.js.map +0 -1
  138. package/dist/ui/index.cjs +0 -1961
  139. package/dist/ui/index.cjs.map +0 -1
  140. package/dist/ui/index.d.cts +0 -209
  141. package/dist/ui/index.d.ts +0 -209
  142. package/dist/ui/index.js +0 -1961
  143. package/dist/ui/index.js.map +0 -1
  144. package/dist/ui/kanban.css +0 -194
  145. /package/dist/{chunk-ATUFFNLV.js.map → chunk-3LEIHB7Y.js.map} +0 -0
  146. /package/dist/{chunk-5DLZWW5Y.js.map → chunk-AHHEGTUA.js.map} +0 -0
  147. /package/dist/{chunk-5XKOCKY2.js.map → chunk-X4UHZX6F.js.map} +0 -0
package/dist/ui/index.cjs DELETED
@@ -1,1961 +0,0 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } async function _asyncNullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return await rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class; var _class2; var _class3; var _class4;
2
-
3
- var _chunkKYNYPKKHcjs = require('../chunk-KYNYPKKH.cjs');
4
-
5
-
6
-
7
-
8
- var _chunkPSKQAFDFcjs = require('../chunk-PSKQAFDF.cjs');
9
-
10
-
11
- var _chunk6MN3H3YFcjs = require('../chunk-6MN3H3YF.cjs');
12
- require('../chunk-LSE6JG5I.cjs');
13
-
14
- // src/ui/html.ts
15
- function escapeHTML(value2) {
16
- return String(_nullishCoalesce(value2, () => ( ""))).replace(/[&<>"']/g, (character) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[character]);
17
- }
18
- var escapeAttr = escapeHTML;
19
-
20
- // src/ui/task-state.ts
21
- function taskStatus(ui, task) {
22
- if (!task) return "";
23
- return `<span class="kanban__task-state" data-task-phase="${escapeAttr(task.phase)}">${escapeHTML(ui.text("taskPhase." + task.phase))}${task.reason ? `<small>${escapeHTML(taskReason(ui, task.reason))}</small>` : ""}${task.phase === "waiting-start" && task.reason !== "MANUAL_START_REQUIRED" && task.startAt !== void 0 ? `<time data-task-start-at="${task.startAt}" datetime="${new Date(task.startAt).toISOString()}">${escapeHTML(startTimeText(ui, task.startAt))}</time>` : ""}</span>`;
24
- }
25
- function taskControls(ui, task, fixAvailable = false) {
26
- return task.actions.filter((action) => ["run", "pause", "resume", "edit", "cancel"].includes(action) || action === "fix" && fixAvailable).map((action) => {
27
- const label = ui.text("taskAction." + action), icon = action === "cancel" ? ui.icons.close : ui.icons[action];
28
- return `<button type="button" data-action="task-${action}" title="${escapeAttr(label)}" aria-label="${escapeAttr(label)}">${_nullishCoalesce(icon, () => ( escapeHTML(label)))}</button>`;
29
- }).join("");
30
- }
31
- function phaseFilter(ui, selected) {
32
- const phases = ["waiting-start", "analyzing", "queued", "waiting-dependency", "preparing", "running", "pausing", "paused", "resuming", "waiting-input", "error", "review", "completed", "cancelled", "unknown"];
33
- return `<label>${escapeHTML(ui.text("taskState"))}<select name="phase"><option value="">${escapeHTML(ui.text("all"))}</option>${phases.map((phase) => `<option value="${phase}" ${phase === selected ? "selected" : ""}>${escapeHTML(ui.text("taskPhase." + phase))}</option>`).join("")}</select></label>`;
34
- }
35
- function startTimeText(ui, startAt) {
36
- return ui.text("taskStartAt") + " " + new Date(startAt).toLocaleString() + " \xB7 " + Math.max(0, Math.ceil((startAt - Date.now()) / 1e3)) + "s";
37
- }
38
- function updateTaskCountdowns(editor, ui) {
39
- for (const node of editor.querySelectorAll("[data-task-start-at]")) {
40
- const at = Number(node.dataset.taskStartAt);
41
- if (Number.isFinite(at)) node.textContent = startTimeText(ui, at);
42
- }
43
- }
44
- function taskReason(ui, reason) {
45
- const key = "taskReason." + reason, value2 = ui.text(key);
46
- return value2 === key || value2 === "kanban." + key ? reason : value2;
47
- }
48
-
49
- // src/ui/session-links.ts
50
- function sessionLink(ui, id, label = id, url) {
51
- if (!url) return `<button type="button" data-session="${escapeAttr(id)}">${escapeHTML(label)}</button>`;
52
- return linkPair(ui, url, label, id);
53
- }
54
- function linkPair(ui, href, label, sessionId) {
55
- return `<span class="kanban__session-links"><a href="${escapeAttr(href)}"${sessionId ? ` data-session="${escapeAttr(sessionId)}" title="${escapeAttr(ui.text("openSessionInTab"))}"` : ""}>${escapeHTML(label)}</a></span>`;
56
- }
57
-
58
- // src/ui/references.ts
59
- function referenceMenu(ui, board, entity, actions = "") {
60
- const reference = _chunkPSKQAFDFcjs.kanbanReference.call(void 0, board, entity);
61
- const path = `/board/${encodeURIComponent(board.id)}${entity ? entity.kind ? "/@record/" + encodeURIComponent(entity.id) : "/" + encodeURIComponent(entity.id) : ""}`;
62
- return `<details class="kanban__menu" data-popover><summary aria-label="${escapeAttr(ui.text("referenceActions"))}" title="${escapeAttr(ui.text("referenceActions"))}">${ui.icons.more}</summary>
63
- <div class="kanban__menu-content"><code class="kanban__menu-reference">${escapeHTML(reference)}</code>${actions}<button type="button" data-action="copy-reference" data-copy="${escapeAttr(reference)}">${escapeHTML(ui.text("copyReference"))}</button>
64
- <button type="button" data-action="copy-link" data-copy="${escapeAttr(path)}">${escapeHTML(ui.text("copyLink"))}</button></div></details>`;
65
- }
66
- async function copyReference(ui, button2) {
67
- const value2 = _nullishCoalesce(button2.dataset.copy, () => ( ""));
68
- const text = button2.dataset.action === "copy-link" ? _nullishCoalesce(_optionalChain([ui, 'access', _ => _.referenceUrl, 'optionalCall', _2 => _2(value2)]), () => ( value2)) : value2;
69
- try {
70
- await navigator.clipboard.writeText(text);
71
- if (button2.dataset.action === "copy-resource") {
72
- button2.title = ui.text("copied");
73
- button2.setAttribute("aria-label", ui.text("copied"));
74
- } else button2.textContent = ui.text("copied");
75
- } catch (e) {
76
- throw Object.assign(new Error(ui.text("copyFailed")), { code: "COPY_FAILED" });
77
- }
78
- }
79
-
80
- // src/ui/suggestion-field.ts
81
- function suggestionField(options) {
82
- return `<div class="kanban__suggest" data-kanban-suggest="${escapeAttr(JSON.stringify(options))}"></div>`;
83
- }
84
- function selectedItems(board, ids) {
85
- return ids.map((id) => ({ id, label: _nullishCoalesce(_optionalChain([board, 'access', _3 => _3.items, 'access', _4 => _4.find, 'call', _5 => _5((item) => item.id === id), 'optionalAccess', _6 => _6.title]), () => ( id)) }));
86
- }
87
-
88
- // src/ui/session-view.ts
89
- function executionBadge(ui, session, sessionUrl) {
90
- if (!session) return "";
91
- const resource = _nullishCoalesce(session.resourceId, () => ( session.sessionId));
92
- return `<span class="kanban__execution" data-execution-state="${escapeAttr(session.state)}" data-stale="${session.stale}"><span>${sessionUrl ? sessionLink(ui, resource, ui.text("executionSession"), sessionUrl(resource)) : escapeHTML(ui.text("executionSession"))} \xB7 ${escapeHTML(ui.text("sessionState." + session.state))}${session.stale ? " \xB7 " + escapeHTML(ui.text("activityStale")) : ""}</span><small>${escapeHTML(activityTime(ui, session))}</small></span>`;
93
- }
94
- function activityTime(ui, session) {
95
- if (session.lastActivityAt === void 0) return ui.text("activityUnknown");
96
- const minutes = Math.max(0, Math.floor((Date.now() - session.lastActivityAt) / 6e4));
97
- return ui.text("lastActivity") + " \xB7 " + (minutes < 1 ? ui.text("justNow") : minutes < 60 ? ui.text("minutesAgo").replace("{count}", String(minutes)) : new Date(session.lastActivityAt).toLocaleString());
98
- }
99
- function executionSessionView(ui, detail, editable, sessionUrl) {
100
- const session = detail.current, resource = _nullishCoalesce(_optionalChain([session, 'optionalAccess', _7 => _7.resourceId]), () => ( _optionalChain([session, 'optionalAccess', _8 => _8.sessionId])));
101
- return `<section class="kanban__execution-detail"><h4>${escapeHTML(ui.text("executionSession"))}</h4><p>${escapeHTML(ui.text("executionSessionHelp"))}</p>
102
- ${detail.continuation ? `<button type="button" data-action="continue-execution" data-dispatch="${escapeAttr(detail.continuation.previousDispatchId)}">${escapeHTML(ui.text("continueExecution"))}</button>` : ""}
103
- ${session ? `${executionBadge(ui, session)}${sessionLink(ui, resource, _nullishCoalesce(session.title, () => ( session.sessionId)), _optionalChain([sessionUrl, 'optionalCall', _9 => _9(resource)]))}<small>${escapeHTML(session.sessionId)}</small>${session.observedAt ? `<small>${escapeHTML(ui.text("observedAt"))} \xB7 ${escapeHTML(new Date(session.observedAt).toLocaleString())}</small>` : ""}${session.code ? `<p>${escapeHTML(ui.text("activityReadFailed"))} (${escapeHTML(session.code)})</p>` : ""}` : `<p>${escapeHTML(ui.text("noExecutionSession"))}</p>`}
104
- ${editable ? `<details><summary>${escapeHTML(ui.text(session ? "changeExecutionSession" : "bindExecutionSession"))}</summary><form data-form="execution-session">${suggestionField({ name: "executionSession", label: "executionSession", kind: "session", manual: true, required: true })}<button>${escapeHTML(ui.text("bindExecutionSession"))}</button><p data-execution-error role="alert" hidden></p></form></details>` : ""}
105
- </section>`;
106
- }
107
- function relatedSessionsView(ui, detail, sessionUrl) {
108
- const sessions = _nullishCoalesce(detail.sessions, () => ( legacySessions(detail)));
109
- return `${detail.sessionsCode ? `<p role="alert">${escapeHTML(ui.text("sessionRelationsIncomplete"))} (${escapeHTML(detail.sessionsCode)})</p>` : ""}
110
- ${sessions.length ? `<ul class="kanban__session-list">${sessions.map((session) => relatedSession(ui, session, sessionUrl)).join("")}</ul>` : `<p>${escapeHTML(ui.text("noRelatedSessions"))}</p>`}`;
111
- }
112
- function relatedSession(ui, session, sessionUrl) {
113
- const resource = _nullishCoalesce(session.resourceId, () => ( session.sessionId)), title = _nullishCoalesce(session.title, () => ( session.sessionId));
114
- return `<li class="kanban__related-session" data-related-session="${escapeAttr(session.sessionId)}"><div class="kanban__session-heading" title="${escapeAttr(title)}">${sessionLink(ui, resource, title, _optionalChain([sessionUrl, 'optionalCall', _10 => _10(resource)]))}</div>
115
- <small>${escapeHTML(session.sessionId)}</small>${session.createdAt !== void 0 ? `<small>${escapeHTML(ui.text("sessionCreatedAt"))} \xB7 ${escapeHTML(date(session.createdAt))}</small>` : ""}<p>${session.current ? `<strong>${escapeHTML(ui.text("currentExecution"))}</strong> \xB7 ` : ""}${session.purposes.map((purpose) => escapeHTML(ui.text("sessionPurpose." + purpose))).join(" \xB7 ")} \xB7 ${escapeHTML(ui.text("sessionState." + session.state))}${session.stale ? " \xB7 " + escapeHTML(ui.text("activityStale")) : ""}</p>
116
- ${session.code ? `<p role="alert">${escapeHTML(ui.text("activityReadFailed"))} (${escapeHTML(session.code)})</p>` : ""}
117
- ${session.executions.length ? `<details><summary>${escapeHTML(ui.text("sessionExecutions"))} (${session.executions.length})</summary><ol>${session.executions.map((run) => executionView(ui, run)).join("")}</ol></details>` : ""}</li>`;
118
- }
119
- function executionView(ui, run) {
120
- const label = run.previousDispatchId ? ui.text("sessionContinuation") : ui.text("sessionPurpose." + run.purpose);
121
- const showState = run.state && run.state !== run.outcome && !(run.state === "completed" && ["succeeded", "accepted", "rejected"].includes(_nullishCoalesce(run.outcome, () => ( ""))));
122
- const fields = [
123
- [ui.text("sessionExecutionId"), run.id],
124
- [ui.text("sessionRoundId"), run.roundId],
125
- [ui.text("sessionTurnId"), run.turnId],
126
- [ui.text("sessionPreviousExecution"), run.previousDispatchId],
127
- [ui.text(run.roundId ? "sessionExecutionStarted" : "sessionExecutionPrepared"), date(run.createdAt)],
128
- [ui.text("sessionExecutionEnded"), date(run.completedAt)],
129
- ..._nullishCoalesce(_optionalChain([run, 'access', _11 => _11.resources, 'optionalAccess', _12 => _12.map, 'call', _13 => _13((resource) => [resource.label, resource.value])]), () => ( []))
130
- ];
131
- return `<li class="kanban__session-run"><p>${escapeHTML(label)}${run.attempt ? " \xB7 #" + run.attempt : ""}${run.outcome ? " \xB7 " + escapeHTML(ui.text("sessionRunOutcome." + run.outcome)) : ""}${showState ? " \xB7 " + escapeHTML(ui.text("sessionState." + run.state)) : ""}</p>
132
- <dl>${fields.filter((field2) => field2[1] !== void 0).map(([key, value2]) => `<dt>${escapeHTML(key)}</dt><dd>${escapeHTML(value2)}</dd>`).join("")}</dl>${run.code ? `<p role="alert">${escapeHTML(run.code)}</p>` : ""}</li>`;
133
- }
134
- function date(value2) {
135
- return value2 === void 0 ? void 0 : new Date(value2).toLocaleString();
136
- }
137
- function legacySessions(detail) {
138
- return [...new Map(detail.history.map((row) => [row.sessionId, row])).values()].map((row) => ({
139
- ..._optionalChain([detail, 'access', _14 => _14.current, 'optionalAccess', _15 => _15.sessionId]) === row.sessionId ? detail.current : { sessionId: row.sessionId, state: "unknown", stale: true, recent: false },
140
- current: _optionalChain([detail, 'access', _16 => _16.current, 'optionalAccess', _17 => _17.sessionId]) === row.sessionId,
141
- purposes: ["execution"],
142
- executions: []
143
- }));
144
- }
145
- function executionFilter(ui, value2 = "all") {
146
- return `<select name="execution" aria-label="${escapeAttr(ui.text("executionFilter"))}">${["all", "running", "recent", "active"].map((filter) => `<option value="${filter}" ${filter === value2 ? "selected" : ""}>${escapeHTML(ui.text("executionFilter." + filter))}</option>`).join("")}</select>`;
147
- }
148
-
149
- // src/ui/view.ts
150
- var states = ["proposed", "backlog", "open", "claimed", "review", "completed", "cancelled"];
151
- var button = (ui, action, key, primary = false) => `<button type="button"${primary ? ' class="kanban__primary"' : ""} data-action="${escapeAttr(action)}">${escapeHTML(ui.text(key))}</button>`;
152
- function contextTabs(ui, active = "board") {
153
- return `<nav class="kanban__context-tabs" aria-label="${escapeAttr(ui.text("recordContext"))}">${[["board", "tabBoard"], ["log", "tabLog"], ["decision", "tabDecision"]].map(([value2, key]) => `<button type="button" data-context="${value2}" ${active === value2 ? 'aria-current="page"' : ""}>${escapeHTML(ui.text(key))}</button>`).join("")}</nav>`;
154
- }
155
- function field(ui, name, key, value2 = "", required = true, type = "text") {
156
- return `<label>${escapeHTML(ui.text(key))}<input name="${name}" type="${type}" value="${escapeAttr(value2)}" ${required ? "required" : ""}></label>`;
157
- }
158
- function textarea(ui, name, key, value2 = "") {
159
- return `<label>${escapeHTML(ui.text(key))}<textarea name="${name}" required>${escapeHTML(value2)}</textarea></label>`;
160
- }
161
- function owns(item, actor) {
162
- return !!actor && !!item.owner && (_nullishCoalesce(item.owner.kind, () => ( "agent"))) === actor.kind && (_nullishCoalesce(item.owner.id, () => ( item.owner.agentId))) === actor.id;
163
- }
164
- function navigation(ui, boards, selected) {
165
- return `<header class="kanban__sidebar-header"><h2>${escapeHTML(ui.text("title"))}</h2>${button(ui, "new-board", "createShort")}</header>
166
- <section class="kanban__board-list"><p class="kanban__caption">${escapeHTML(ui.text("scope"))}</p><nav aria-label="${escapeAttr(ui.text("title"))}">${boards.map((board) => `<button type="button" data-board="${escapeAttr(board.id)}" ${board.id === selected ? 'aria-current="page"' : ""}><small>${escapeHTML(_chunkPSKQAFDFcjs.kanbanReference.call(void 0, board))}</small><span>${escapeHTML(board.title)}</span></button>`).join("")}</nav></section>
167
- <footer class="kanban__sidebar-tools"><details class="kanban__reference-lookup"><summary>${escapeHTML(ui.text("openReference"))}</summary>${referenceLookup(ui)}</details>
168
- <details class="kanban__scope-note"><summary>${escapeHTML(ui.text("scopeInfo"))}</summary><p>${escapeHTML(ui.text("scopeHelp"))}</p></details></footer>`;
169
- }
170
- function referenceLookup(ui) {
171
- return `<form data-form="reference"><input name="reference" aria-label="${escapeAttr(ui.text("reference"))}" placeholder="${escapeAttr(ui.text("referencePlaceholder"))}" required><button>${escapeHTML(ui.text("openShort"))}</button></form>`;
172
- }
173
- function boardHeader(ui, board, active = "board", automation = false) {
174
- const [action, key] = active === "log" ? ["new-log", "newLog"] : active === "decision" ? ["new-decision", "newDecision"] : ["new-item", "newItem"];
175
- const actions = `<div class="kanban__mobile-create">${button(ui, action, key)}</div>${button(ui, "refresh", "refresh")}${automation ? button(ui, "automation", "automationTitle") : ""}`;
176
- return `<header class="kanban__page-header" data-titlebar><span class="kanban__navigation" data-navigation></span><div class="kanban__board-heading"><h2 data-titlebar-title title="${escapeAttr(board.title)}">${escapeHTML(board.title)}</h2></div>
177
- <div class="kanban__header-actions"><span class="kanban__desktop-create">${button(ui, action, key, true)}</span><span class="kanban__window-navigation" data-window-navigation></span>${referenceMenu(ui, board, void 0, actions)}</div></header>`;
178
- }
179
- function catalogView(ui, boards) {
180
- return `<header class="kanban__page-header" data-titlebar><span class="kanban__navigation" data-navigation></span><div class="kanban__board-heading"><h2 data-titlebar-title>${escapeHTML(ui.text("title"))}</h2></div><span class="kanban__window-navigation" data-window-navigation></span>${button(ui, "new-board", "createShort")}</header>
181
- <div class="kanban__record-list">${boards.map((board) => `<button type="button" class="kanban__card" data-board="${escapeAttr(board.id)}"><strong>${escapeHTML(board.title)}</strong><small>${escapeHTML(_chunkPSKQAFDFcjs.kanbanReference.call(void 0, board))}</small></button>`).join("") || `<p>${escapeHTML(ui.text("emptyBoards"))}</p>`}</div>`;
182
- }
183
- function boardView(ui, board, page, query, activity = false, automation = false, sessionUrl) {
184
- const columns = query.view === "archive" ? [{ id: "archive", label: ui.text("archive"), states }] : _nullishCoalesce(board.columns, () => ( states.map((state) => ({ id: state, label: ui.text(state), states: [state] }))));
185
- return `${boardHeader(ui, board, "board", automation)}${contextTabs(ui)}<form data-form="filter" class="kanban__filters"><input name="search" aria-label="${escapeAttr(ui.text("search"))}" placeholder="${escapeAttr(ui.text("search"))}" value="${escapeAttr(_nullishCoalesce(query.search, () => ( "")))}">
186
- <details class="kanban__filter-more" data-popover><summary>${escapeHTML(ui.text("moreFilters"))}${query.area || query.milestone || query.view !== "all" || query.execution && query.execution !== "all" ? '<span class="kanban__filter-dot" aria-hidden="true"></span>' : ""}</summary><div class="kanban__filter-fields"><label>${escapeHTML(ui.text("filter"))}<select name="view" aria-label="${escapeAttr(ui.text("filter"))}">${["all", "backlog", "ready", "in-progress", "review", "closed", "proposed", "archive"].map((view) => `<option value="${view}" ${view === query.view ? "selected" : ""}>${escapeHTML(ui.text(view === "ready" ? "open" : view === "in-progress" ? "claimed" : view === "closed" ? "completed" : view))}</option>`).join("")}</select></label>${phaseFilter(ui, query.phase)}${activity ? executionFilter(ui, query.execution) : ""}${suggestionField({ name: "area", label: "area", kind: "area", free: true, selected: query.area ? [{ id: query.area, label: query.area }] : [] })}
187
- ${suggestionField({ name: "milestone", label: "milestone", kind: "milestone", free: true, selected: query.milestone ? [{ id: query.milestone, label: query.milestone }] : [] })}</div></details><button>${escapeHTML(ui.text("filter"))}</button></form>
188
- <p class="kanban__count" role="status">${escapeHTML(ui.text("total", { count: page.total }))}</p><div class="kanban__columns" data-has-items="${page.items.length > 0}">${columns.map((column) => {
189
- const items = page.items.filter((item) => column.states.includes(item.status));
190
- return `<section class="kanban__column" data-empty="${items.length === 0}"><h3>${escapeHTML(column.label)} <small>${items.length}</small></h3><div class="kanban__column-items" data-column="${escapeAttr(column.id)}" tabindex="0" aria-label="${escapeAttr(column.label + " \xB7 " + ui.text("columnItems"))}">${items.map((item) => itemCard(ui, board, item, sessionUrl)).join("") || `<p>${escapeHTML(ui.text("emptyItems"))}</p>`}</div></section>`;
191
- }).join("")}</div><footer class="kanban__toolbar"><small>${escapeHTML(ui.text("scrollHint"))}</small>${button(ui, "scroll-left", "previousColumns")}${button(ui, "scroll-right", "nextColumns")}${query.cursor ? button(ui, "first", "first") : ""}${page.nextCursor ? button(ui, "next", "next") : ""}</footer>`;
192
- }
193
- function itemCard(ui, board, item, sessionUrl) {
194
- const content = `<strong>${escapeHTML(item.title)}</strong><small>${escapeHTML(_chunkPSKQAFDFcjs.kanbanReference.call(void 0, board, item))}</small><small>P${item.planning.priority} \xB7 ${escapeHTML(_nullishCoalesce(item.planning.area, () => ( "")))} \xB7 ${escapeHTML(_nullishCoalesce(item.planning.milestone, () => ( "")))}</small>${taskStatus(ui, item.task)}${item.pendingAmendments ? `<small class="kanban__badge">${escapeHTML(ui.text("pendingAmendments"))} (${item.pendingAmendments})</small>` : ""}${item.progress ? `<small>${item.progress.completed}/${item.progress.total}</small>` : ""}`;
195
- const identity = _optionalChain([item, 'access', _18 => _18.task, 'optionalAccess', _19 => _19.sessionId]) ? `data-session="${escapeAttr(item.task.sessionId)}"` : `data-item="${escapeAttr(item.id)}"`;
196
- return `<article class="kanban__card"><button type="button" class="kanban__item-open" ${identity}>${content}</button>${_optionalChain([item, 'access', _20 => _20.task, 'optionalAccess', _21 => _21.sessionId]) ? `<button type="button" data-item="${escapeAttr(item.id)}" title="${escapeAttr(ui.text("details"))}" aria-label="${escapeAttr(ui.text("details"))}">${escapeHTML(ui.text("details"))}</button>` : ""}<span data-item-execution="${escapeAttr(item.id)}">${executionBadge(ui, item.execution, sessionUrl)}</span></article>`;
197
- }
198
-
199
- // src/ui/children.ts
200
- function childType(board, parent) {
201
- const types = _nullishCoalesce(board.workTypes, () => ( _chunkPSKQAFDFcjs.DEFAULT_KANBAN_WORK_TYPES));
202
- const rank = _optionalChain([types, 'access', _22 => _22.find, 'call', _23 => _23((type) => type.id === (_nullishCoalesce(_nullishCoalesce(_optionalChain([parent, 'access', _24 => _24.planning, 'optionalAccess', _25 => _25.kind]), () => ( board.defaultWorkType)), () => ( "task")))), 'optionalAccess', _26 => _26.rank]);
203
- const candidates = types.filter((type) => rank !== void 0 && type.rank > rank);
204
- return _optionalChain([(_nullishCoalesce(candidates.find((type) => type.id === (_nullishCoalesce(board.defaultWorkType, () => ( "task")))), () => ( candidates.sort((a, b) => a.rank - b.rank)[0]))), 'optionalAccess', _27 => _27.id]);
205
- }
206
- function childrenView(ui, page, cursor, canCreate, sessionUrl) {
207
- return `<h4>${escapeHTML(ui.text("children"))} (${page.total})</h4><div class="kanban__actions">${canCreate ? button(ui, "new-child", "newChild") : ""}</div>
208
- <div class="kanban__record-list">${page.items.map((item) => `<article class="kanban__card"><button type="button" data-item="${escapeAttr(item.id)}"><strong>${escapeHTML(item.title)}</strong></button>${taskStatus(ui, item.task)}${_optionalChain([item, 'access', _28 => _28.task, 'optionalAccess', _29 => _29.sessionId]) ? sessionLink(ui, item.task.sessionId, ui.text("executionSession"), _optionalChain([sessionUrl, 'optionalCall', _30 => _30(item.task.sessionId)])) : ""}${item.execution ? sessionLink(ui, _nullishCoalesce(item.execution.resourceId, () => ( item.execution.sessionId)), ui.text("executionSession"), _optionalChain([sessionUrl, 'optionalCall', _31 => _31(_nullishCoalesce(item.execution.resourceId, () => ( item.execution.sessionId)))])) : ""}</article>`).join("")}</div>
209
- <div class="kanban__actions">${cursor ? button(ui, "children-first", "first") : ""}${page.nextCursor ? `<button type="button" data-action="children-next" data-cursor="${escapeAttr(page.nextCursor)}">${escapeHTML(ui.text("next"))}</button>` : ""}</div>`;
210
- }
211
-
212
- // src/ui/panel-header.ts
213
- function panelHeader(ui, title, board, entity) {
214
- const label = escapeAttr(ui.text("close"));
215
- return `<header class="kanban__panel-header" data-titlebar><button type="button" class="kanban__panel-back" data-titlebar-back data-action="close-panel" aria-label="${label}" title="${label}">${_nullishCoalesce(ui.icons.back, () => ( ui.icons.close))}</button>
216
- <h3 data-titlebar-title title="${escapeAttr(title)}">${escapeHTML(title)}</h3><span class="kanban__window-navigation" data-window-navigation></span>${board ? referenceMenu(ui, board, entity, `<p class="kanban__menu-title">${escapeHTML(title)}</p>`) : ""}</header>`;
217
- }
218
-
219
- // src/ui/context-view.ts
220
- function recordsView(ui, page, kind, hasPrevious, board) {
221
- return `${board ? boardHeader(ui, board, kind) : `<header class="kanban__toolbar"><h2>${escapeHTML(ui.text(kind))}</h2></header>`}
222
- ${contextTabs(ui, kind)}<p>${escapeHTML(ui.text("recordHelp"))}</p><div class="kanban__record-list" tabindex="0" aria-label="${escapeAttr(ui.text(kind))}">${recordLinks(ui, page, board)}${!page.total ? `<p>${escapeHTML(ui.text("emptyRecords"))}</p>` : ""}</div>
223
- <footer class="kanban__toolbar">${hasPrevious ? button(ui, "records-first", "first") : ""}${page.nextCursor ? button(ui, "records-next", "next") : ""}</footer>`;
224
- }
225
- function recordLinks(ui, page, board) {
226
- return page.items.map((record) => `<button type="button" class="kanban__card" data-record="${escapeAttr(record.id)}"><strong>${escapeHTML(record.title)}</strong>${board && !record.activity ? `<small>${escapeHTML(_chunkPSKQAFDFcjs.kanbanReference.call(void 0, board, record))}</small>` : ""}<small>${escapeHTML(ui.text(record.activity ? "automaticLog" : record.kind))} \xB7 ${escapeHTML(ui.text(record.status))} \xB7 ${escapeHTML(new Date(record.createdAt).toLocaleString())}</small></button>`).join("");
227
- }
228
- function sourcesView(ui, sources) {
229
- return sources.length ? `<ul>${sources.map((source) => `<li><button type="button" data-session="${escapeAttr(source.sessionId)}">${escapeHTML(source.sessionId)}</button><small>${escapeHTML(source.messageId)} \xB7 ${escapeHTML(source.revision)} \xB7 ${escapeHTML(source.role)}</small></li>`).join("")}</ul>` : `<p>${escapeHTML(ui.text("noSources"))}</p>`;
230
- }
231
- function recordForm(ui, kind, board, itemId = "", resources = []) {
232
- return `${panelHeader(ui, ui.text(kind === "log" ? "newLog" : "newDecision"))}<form data-form="record">
233
- <input type="hidden" name="kind" value="${kind}">${field(ui, "title", "name")}${textarea(ui, "content", "content")}${kind === "decision" ? textarea(ui, "rationale", "rationale") + suggestionField({ name: "supersedes", label: "supersedes", kind: "decision" }) : ""}
234
- ${kind === "decision" && resources.length ? `<label>${escapeHTML(ui.text("recordResources"))}<select name="recordResources" multiple>${resources.map((resource) => `<option value="${escapeAttr(JSON.stringify(resource.reference))}">${escapeHTML(resource.label)}</option>`).join("")}</select></label>` : ""}
235
- ${itemChoices(board, itemId)}<details><summary>${escapeHTML(ui.text("sources"))}</summary>${suggestionField({ name: "sourceSession", label: "sourceSession", kind: "session", manual: true })}${field(ui, "sourceMessage", "sourceMessage", "", false)}${field(ui, "sourceRevision", "sourceRevision", "", false)}</details><button>${escapeHTML(ui.text("save"))}</button></form>`;
236
- }
237
- function recordDetail(ui, record, board, executions) {
238
- return `${panelHeader(ui, record.title, board, record)}
239
- <p>${escapeHTML(ui.text(record.kind))} \xB7 ${escapeHTML(ui.text(record.status))} \xB7 ${escapeHTML(ui.text(record.origin))}</p>${record.activity ? "" : `<small>${escapeHTML(ui.text("recordId"))}: ${escapeHTML(record.id)}</small>`}<p>${escapeHTML(ui.text("author"))}: ${escapeHTML(record.author.id)} \xB7 ${escapeHTML(new Date(record.createdAt).toLocaleString())}</p>
240
- <section data-reference-links></section><section class="kanban__prose"><h4>${escapeHTML(ui.text("content"))}</h4><p>${escapeHTML(record.content)}</p>${record.rationale ? `<h4>${escapeHTML(ui.text("rationale"))}</h4><p>${escapeHTML(record.rationale)}</p>` : ""}</section>
241
- ${record.status === "draft" ? `<div class="kanban__actions">${button(ui, "confirm-record", "confirmRecord")}${button(ui, "reject-record", "rejectRecord")}</div>` : ""}
242
- ${record.kind === "decision" && record.status === "confirmed" && executions ? recordExecutionView(ui, board, executions) : ""}
243
- ${(_nullishCoalesce(record.externalRefs, () => ( []))).map((ref) => `<p>${escapeHTML(ref.system)}: ${escapeHTML(ref.id)}</p>`).join("")}
244
- ${record.supersedes ? recordLink(ui, record.supersedes, "supersedes") : ""}${record.supersededBy ? recordLink(ui, record.supersededBy, "superseded") : ""}
245
- <h4>${escapeHTML(ui.text("itemIds"))}</h4>${record.itemIds.map((id) => `<button type="button" data-item="${escapeAttr(id)}">${escapeHTML(_nullishCoalesce(_optionalChain([board, 'access', _32 => _32.items, 'access', _33 => _33.find, 'call', _34 => _34((item) => item.id === id), 'optionalAccess', _35 => _35.title]), () => ( id)))}</button>`).join("")}
246
- ${record.activity ? "" : `<form data-form="record-link">${suggestionField({ name: "itemId", label: "itemIds", kind: "item", required: true })}<button>${escapeHTML(ui.text("linkRecord"))}</button></form>`}<h4>${escapeHTML(ui.text("sources"))}</h4>${sourcesView(ui, record.sources)}`;
247
- }
248
- function recordExecutionView(ui, board, executions) {
249
- const completed = executions.some((run) => run.state === "completed");
250
- const actions = completed ? `<p>${escapeHTML(ui.text("completedFollowup"))}</p>` : `${button(ui, "run-record", "runRecord")}<details><summary>${escapeHTML(ui.text("referenceActions"))}</summary>${button(ui, "run-record-separate", "runRecordSeparate")}</details>`;
251
- return `<div class="kanban__actions">${actions}</div>${completed ? "" : `<p>${escapeHTML(ui.text("runRecordHelp"))}</p>`}
252
- ${executions.map((run) => `<p><button type="button" ${run.rootItemId && run.boardId === board.id ? `data-item="${escapeAttr(run.rootItemId)}"` : `data-board="${escapeAttr(run.boardId)}"`}>${escapeHTML(ui.text(run.rootItemId && run.boardId === board.id ? "executionWork" : "executionBoard"))}</button> \xB7 ${escapeHTML(ui.text(run.state))}${run.code ? ` \xB7 ${escapeHTML(run.code)}` : ""}</p>`).join("")}`;
253
- }
254
- function recordLink(ui, id, key) {
255
- return `<p>${escapeHTML(ui.text(key))}: <button type="button" data-record="${escapeAttr(id)}">${escapeHTML(id)}</button></p>`;
256
- }
257
- function amendmentsView(ui, item) {
258
- const pending = _nullishCoalesce(_optionalChain([item, 'access', _36 => _36.amendments, 'optionalAccess', _37 => _37.filter, 'call', _38 => _38((proposal) => proposal.status === "pending")]), () => ( []));
259
- if (!pending.length) return "";
260
- return `<section class="kanban__amendments"><h4>${escapeHTML(ui.text("amendments"))}</h4><p>${escapeHTML(ui.text("amendmentHint"))}</p>${pending.map((proposal) => `<article><strong>${escapeHTML(proposal.proposedBy.id)}</strong><small> v${proposal.baseRevision}</small>
261
- <details><summary>${escapeHTML(ui.text("currentText"))}</summary><p>${escapeHTML(item.title)}</p><p>${escapeHTML(item.responsibility)}</p><p>${escapeHTML(item.acceptance)}</p></details>
262
- <h5>${escapeHTML(ui.text("proposedText"))}</h5><p>${escapeHTML(proposal.proposed.title)}</p><p>${escapeHTML(proposal.proposed.responsibility)}</p><p>${escapeHTML(proposal.proposed.acceptance)}</p>
263
- ${sourcesView(ui, proposal.sources)}<form data-form="amendment" data-amendment="${escapeAttr(proposal.id)}"><select name="decision" aria-label="${escapeAttr(ui.text("amendments"))}"><option value="accept">${escapeHTML(ui.text("applyAmendment"))}</option><option value="reject">${escapeHTML(ui.text("rejectAmendment"))}</option></select>${field(ui, "reason", "reason", "", false)}<button>${escapeHTML(ui.text("save"))}</button></form></article>`).join("")}</section>`;
264
- }
265
- function itemChoices(board, itemId) {
266
- return suggestionField({ name: "itemIds", label: "itemIds", kind: "item", multiple: true, selected: selectedItems(board, itemId ? [itemId] : []) });
267
- }
268
-
269
- // src/ui/event-history.ts
270
- function meaningfulItemEvents(events) {
271
- const last = /* @__PURE__ */ new Map(), hidden = /* @__PURE__ */ new Set();
272
- for (const event of [...events].sort((a, b) => a.sequence - b.sequence)) {
273
- if (event.kind === "renew") {
274
- hidden.add(event);
275
- continue;
276
- }
277
- if (event.kind !== "observe-execution" || !event.data.dispatchId || !event.data.reason) continue;
278
- const key = JSON.stringify([event.boardId, event.itemId, event.data.dispatchId]);
279
- const state = JSON.stringify([event.data.reason, event.data.code, event.data.status]);
280
- if (last.get(key) === state) hidden.add(event);
281
- last.set(key, state);
282
- }
283
- return events.filter((event) => !hidden.has(event));
284
- }
285
-
286
- // src/ui/forms.ts
287
- function newBoardForm(ui) {
288
- return `${panelHeader(ui, ui.text("newBoard"))}<form data-form="board">${field(ui, "title", "boardName")}
289
- <label><input name="requireEstimate" type="checkbox">${escapeHTML(ui.text("requireEstimate"))}</label>
290
- ${save(ui)}</form>`;
291
- }
292
- function newItemForm(ui, board, parent, resources) {
293
- const preset = parent ? { kind: childType(board, parent), parentId: parent.id, priority: _nullishCoalesce(_optionalChain([parent, 'access', _39 => _39.planning, 'optionalAccess', _40 => _40.priority]), () => ( 2)), dependencies: [], references: [] } : void 0;
294
- return `${panelHeader(ui, ui.text("newItem"))}<form data-form="item">${field(ui, "title", "name")}
295
- ${textarea(ui, "responsibility", "responsibility")}${textarea(ui, "acceptance", "acceptance")}${launchModeField(ui)}${planningFields(ui, board, void 0, preset)}${itemResourceField(ui, resources, (_nullishCoalesce(board.workTypes, () => ( _chunkPSKQAFDFcjs.DEFAULT_KANBAN_WORK_TYPES))).some((type) => type.id === (_nullishCoalesce(_nullishCoalesce(_optionalChain([preset, 'optionalAccess', _41 => _41.kind]), () => ( board.defaultWorkType)), () => ( "task"))) && type.executable))}${save(ui)}</form>`;
296
- }
297
- function planningFields(ui, board, item, preset) {
298
- const plan = _nullishCoalesce(preset, () => ( _optionalChain([item, 'optionalAccess', _42 => _42.planning]))), types = _nullishCoalesce(board.workTypes, () => ( _chunkPSKQAFDFcjs.DEFAULT_KANBAN_WORK_TYPES));
299
- return `<label>${escapeHTML(ui.text("type"))}<select name="kind">${types.map((type) => `<option data-executable="${type.executable}" value="${escapeAttr(type.id)}" ${type.id === (_nullishCoalesce(_nullishCoalesce(_optionalChain([plan, 'optionalAccess', _43 => _43.kind]), () => ( board.defaultWorkType)), () => ( "task"))) ? "selected" : ""}>${escapeHTML(type.label)}</option>`).join("")}</select></label>
300
- ${field(ui, "priority", "priority", String(_nullishCoalesce(_optionalChain([plan, 'optionalAccess', _44 => _44.priority]), () => ( 2))), true, "number")}
301
- ${suggestionField({ name: "area", label: "area", kind: "area", free: true, selected: _optionalChain([plan, 'optionalAccess', _45 => _45.area]) ? [{ id: plan.area, label: plan.area }] : [] })}${suggestionField({ name: "milestone", label: "milestone", kind: "milestone", free: true, selected: _optionalChain([plan, 'optionalAccess', _46 => _46.milestone]) ? [{ id: plan.milestone, label: plan.milestone }] : [] })}
302
- ${suggestionField({ name: "parentId", label: "parent", kind: "parent", excludeItemId: _optionalChain([item, 'optionalAccess', _47 => _47.id]), selected: selectedItems(board, _optionalChain([plan, 'optionalAccess', _48 => _48.parentId]) ? [plan.parentId] : []) })}${suggestionField({ name: "dependencies", label: "dependencies", kind: "dependency", multiple: true, excludeItemId: _optionalChain([item, 'optionalAccess', _49 => _49.id]), selected: selectedItems(board, _nullishCoalesce(_optionalChain([plan, 'optionalAccess', _50 => _50.dependencies]), () => ( []))) })}
303
- ${field(ui, "dueAt", "dueAt", dateInput(_optionalChain([plan, 'optionalAccess', _51 => _51.dueAt])), false, "datetime-local")}`;
304
- }
305
- function dateInput(at) {
306
- if (at === void 0) return "";
307
- const date2 = new Date(at);
308
- return new Date(at - date2.getTimezoneOffset() * 6e4).toISOString().slice(0, 16);
309
- }
310
- var save = (ui) => `<div class="kanban__actions"><button>${escapeHTML(ui.text("save"))}</button>${button(ui, "close-panel", "close")}</div>`;
311
- function detailView(ui, board, item, actor, archived, events, relatedSessions = false, guidance, interactive = false, task = _chunkKYNYPKKHcjs.projectKanbanTask.call(void 0, item), fixAvailable = false) {
312
- return `${panelHeader(ui, item.title, board, item)}${taskStatus(ui, task)}${archived ? "" : `<div class="kanban__actions">${taskControls(ui, task, fixAvailable)}</div>`}
313
- <p>${escapeHTML(ui.text("origin"))}: ${escapeHTML(item.origin ? ui.text(item.origin.kind === "discovery" ? "flow" : "manual") + " \xB7 " + item.origin.principal.id : ui.text("unknownOrigin"))}</p>
314
- <dl><dt>${escapeHTML(ui.text("itemId"))}</dt><dd>${escapeHTML(item.id)}</dd><dt>${escapeHTML(ui.text("responsibility"))}</dt><dd>${escapeHTML(item.responsibility)}</dd>
315
- <dt>${escapeHTML(ui.text("acceptance"))}</dt><dd>${escapeHTML(item.acceptance)}</dd><dt>${escapeHTML(ui.text("owner"))}</dt><dd>${escapeHTML(_nullishCoalesce(_nullishCoalesce(_optionalChain([item, 'access', _52 => _52.owner, 'optionalAccess', _53 => _53.id]), () => ( _optionalChain([item, 'access', _54 => _54.owner, 'optionalAccess', _55 => _55.agentId]))), () => ( ui.text("none"))))}</dd></dl>
316
- <p>${escapeHTML(ui.text(item.status))}${item.blocked ? " \xB7 " + escapeHTML(ui.text("blocked") + ": " + item.blocked.reason) : ""}</p>
317
- ${interactive ? "<section data-item-operations></section>" : ""}
318
- ${item.status === "review" ? interactive ? `<details><summary>${escapeHTML(ui.text("reviewFlow"))}</summary>${reviewGuidance(ui, guidance)}</details>` : reviewGuidance(ui, guidance) : ""}
319
- <div class="kanban__actions">${controls(ui, board, item, actor, archived, interactive)}</div>
320
- ${_nullishCoalesce(_optionalChain([item, 'access', _56 => _56.issues, 'optionalAccess', _57 => _57.filter, 'call', _58 => _58((issue) => !issue.resolvedBy), 'access', _59 => _59.map, 'call', _60 => _60((issue) => `<form data-form="task-reply"><input type="hidden" name="attemptId" value="${escapeAttr(issue.attemptId)}"><input type="hidden" name="replyTo" value="${escapeAttr(issue.id)}">${textarea(ui, "content", "message")}<button>${escapeHTML(ui.text("send"))}</button></form>`), 'access', _61 => _61.join, 'call', _62 => _62("")]), () => ( ""))}
321
- ${amendmentsView(ui, item)}
322
- ${archived ? "" : detailForms(ui, board, item, actor)}
323
- ${(_nullishCoalesce(_optionalChain([item, 'access', _63 => _63.planning, 'optionalAccess', _64 => _64.dependencies]), () => ( []))).length ? `<p>${escapeHTML(ui.text("dependencies"))}: ${(_nullishCoalesce(_optionalChain([item, 'access', _65 => _65.planning, 'optionalAccess', _66 => _66.dependencies]), () => ( []))).map((id) => `<button type="button" data-item="${escapeAttr(id)}">${escapeHTML(_nullishCoalesce(_optionalChain([board, 'access', _67 => _67.items, 'access', _68 => _68.find, 'call', _69 => _69((i) => i.id === id), 'optionalAccess', _70 => _70.title]), () => ( id)))}</button>`).join(" ")}</p>` : ""}
324
- ${_optionalChain([item, 'access', _71 => _71.planning, 'optionalAccess', _72 => _72.parentId]) ? `<p>${escapeHTML(ui.text("parent"))}: <button type="button" data-item="${escapeAttr(item.planning.parentId)}">${escapeHTML(_nullishCoalesce(_optionalChain([board, 'access', _73 => _73.items, 'access', _74 => _74.find, 'call', _75 => _75((parent) => parent.id === _optionalChain([item, 'access', _76 => _76.planning, 'optionalAccess', _77 => _77.parentId])), 'optionalAccess', _78 => _78.title]), () => ( item.planning.parentId)))}</button></p>` : ""}
325
- <section data-children></section><section data-related-records></section><h4>${escapeHTML(ui.text("sources"))}</h4>${sourcesView(ui, _nullishCoalesce(item.sources, () => ( [])))}
326
- <div data-analysis></div><div data-execution-session></div><section data-related-sessions-section></section><section data-reference-links></section><section data-work-resource-section></section>
327
- ${relatedSessions ? "" : `<h4>${escapeHTML(ui.text("sessions"))}</h4>${item.participants.map((p) => `<button type="button" data-session="${escapeAttr(p.sessionId)}">${escapeHTML(p.sessionId)} \xB7 ${escapeHTML(p.roles.join(", "))}</button>`).join("") || `<p>${escapeHTML(ui.text("none"))}</p>`}`}
328
- ${archived ? "" : `<details><summary>${escapeHTML(ui.text("registerSession"))}</summary><p>${escapeHTML(ui.text("registerSessionHelp"))}</p><form data-form="session">${suggestionField({ name: "sessionId", label: "session", kind: "session", manual: true, required: true })}<button>${escapeHTML(ui.text("registerSession"))}</button></form></details>`}
329
- <section data-item-events>${itemEventsView(ui, events)}</section>`;
330
- }
331
- function reviewGuidance(ui, guidance) {
332
- const [summary, ...steps] = _nullishCoalesce(_optionalChain([guidance, 'optionalAccess', _79 => _79.paragraphs]), () => ( [ui.text("reviewHelp")]));
333
- return `<section class="kanban__review-guide"><strong>${escapeHTML(_nullishCoalesce(_optionalChain([guidance, 'optionalAccess', _80 => _80.title]), () => ( ui.text("reviewHelpTitle"))))}</strong><p>${escapeHTML(summary)}</p>
334
- ${steps.length ? `<details><summary>${escapeHTML(ui.text("reviewFlow"))}</summary><ol>${steps.map((text) => `<li>${escapeHTML(text)}</li>`).join("")}</ol></details>` : ""}</section>`;
335
- }
336
- function itemEventsView(ui, events) {
337
- return `<details><summary>${escapeHTML(ui.text("eventsHelp"))}</summary><ol>${meaningfulItemEvents(events).map((event) => `<li>${escapeHTML(event.kind)} \xB7 ${escapeHTML(_nullishCoalesce(_nullishCoalesce(event.actor.id, () => ( event.actor.agentId)), () => ( "")))} \xB7 ${escapeHTML(new Date(event.at).toLocaleString())}${event.data.message ? `<p>${escapeHTML(event.data.message)}</p>` : ""}</li>`).join("")}</ol></details>`;
338
- }
339
- function controls(ui, board, item, actor, archived, interactive = false) {
340
- if (archived) return button(ui, "restore", "restore");
341
- if (["completed", "cancelled"].includes(item.status)) return button(ui, "archive", "archiveItem");
342
- const executable = _chunkPSKQAFDFcjs.executableWorkItem.call(void 0, board, item);
343
- let result = item.status === "proposed" ? button(ui, "publish", "publish") : item.status === "backlog" && executable ? button(ui, "ready", "ready") : item.status === "open" ? button(ui, "defer", "defer") : "";
344
- if (owns(item, actor) && item.status === "claimed") result += button(ui, "renew", "renew");
345
- if (item.status === "review" && !owns(item, actor)) result += interactive ? "" : button(ui, "accept", "accept") + button(ui, "reject", "reject");
346
- if (item.blocked) result += button(ui, "unblock", "unblock");
347
- if (item.status === "review" && owns(item, actor) && board.policy.review !== "assignee") result += `<p>${escapeHTML(ui.text("noReview"))}</p>`;
348
- return result;
349
- }
350
- function detailForms(ui, board, item, actor) {
351
- if (["completed", "cancelled"].includes(item.status)) return "";
352
- let forms = "";
353
- const executable = _chunkPSKQAFDFcjs.executableWorkItem.call(void 0, board, item);
354
- if (!executable && ["backlog", "proposed", "open"].includes(item.status)) forms += `<form data-form="close-plan">${field(ui, "resultRef", "result")}<button>${escapeHTML(ui.text("closePlan"))}</button></form>`;
355
- if (!board.automation && executable && item.status === "open" && !item.blocked) forms += `<form data-form="claim">${board.policy.requireEstimate !== false ? estimates(ui) : ""}<button>${escapeHTML(ui.text("claim"))}</button></form>`;
356
- if (item.status === "claimed" && owns(item, actor)) forms += `<form data-form="submit">${field(ui, "resultRef", "result")}<button>${escapeHTML(ui.text("submit"))}</button></form>`;
357
- const automatic = _optionalChain([item, 'access', _81 => _81.eligiblePrincipals, 'optionalAccess', _82 => _82.some, 'call', _83 => _83((principal) => principal.kind === _optionalChain([board, 'access', _84 => _84.automation, 'optionalAccess', _85 => _85.worker, 'access', _86 => _86.kind]) && principal.id === board.automation.worker.id)]);
358
- let secondary = "";
359
- if (["proposed", "backlog", "open"].includes(item.status) && !automatic && !board.automation) secondary += `<details><summary>${escapeHTML(ui.text("editItem"))}</summary><form data-form="edit-item">${field(ui, "title", "name", item.title)}${textarea(ui, "responsibility", "responsibility", item.responsibility)}${textarea(ui, "acceptance", "acceptance", item.acceptance)}<button>${escapeHTML(ui.text("save"))}</button></form></details>`;
360
- if (!board.automation && _optionalChain([item, 'access', _87 => _87.launch, 'optionalAccess', _88 => _88.admittedAt]) === void 0 && ["proposed", "backlog", "open"].includes(item.status)) secondary += `<details><summary>${escapeHTML(ui.text("plan"))}</summary><form data-form="plan">${planningFields(ui, board, item)}<button>${escapeHTML(ui.text("save"))}</button></form></details><form data-form="cancel">${field(ui, "reason", "reason")}<button>${escapeHTML(ui.text("cancel"))}</button></form>`;
361
- if (owns(item, actor)) secondary += `<form data-form="release">${field(ui, "reason", "reason")}<button>${escapeHTML(ui.text("release"))}</button></form>`;
362
- if (!item.blocked && (!item.owner || owns(item, actor))) secondary += `<form data-form="block">${field(ui, "reason", "reason")}<button>${escapeHTML(ui.text("block"))}</button></form>`;
363
- return forms + `<details><summary>${escapeHTML(ui.text("moreActions"))}</summary>` + secondary + `<form data-form="message">${textarea(ui, "message", "message")}<button>${escapeHTML(ui.text("send"))}</button></form></details>`;
364
- }
365
- function estimates(ui) {
366
- return `${field(ui, "complexity", "complexity", "1", true, "number")}${field(ui, "workUnits", "workUnits", "1", true, "number")}
367
- ${field(ui, "duration", "duration", "30", true, "number")}<label>${escapeHTML(ui.text("confidence"))}<select name="confidence">${["low", "medium", "high"].map((value2) => `<option value="${value2}">${escapeHTML(ui.text(value2))}</option>`).join("")}</select></label>`;
368
- }
369
- function itemResourceField(ui, catalog, executable) {
370
- if (!catalog || !catalog.resources.length && !catalog.selected) return "";
371
- const options = [...catalog.resources], selected = catalog.selected ? JSON.stringify(catalog.selected) : "";
372
- if (catalog.selected && !options.some((option) => option.reference.system === catalog.selected.system && option.reference.id === catalog.selected.id)) options.push({ reference: catalog.selected, label: catalog.selected.id });
373
- return `<label>${escapeHTML(ui.text("itemResource"))}<select name="itemResource" data-resource-required="${catalog.required === true}" ${catalog.required && executable ? "required" : ""} ${catalog.readonly ? "disabled" : ""}>
374
- <option value="">${escapeHTML(ui.text("none"))}</option>${options.map((option) => `<option value="${escapeAttr(JSON.stringify(option.reference))}" ${JSON.stringify(option.reference) === selected ? "selected" : ""}>${escapeHTML(option.label)}</option>`).join("")}</select>
375
- ${catalog.readonly ? `<input type="hidden" name="itemResource" value="${escapeAttr(selected)}">` : ""}</label>`;
376
- }
377
- function editTaskForm(ui, board, item, editId, resources) {
378
- return `${panelHeader(ui, item.title, board, item)}<p>${escapeHTML(ui.text("editLeaseHelp"))}</p><form data-form="save-edit"><input type="hidden" name="editId" value="${escapeAttr(editId)}">${field(ui, "title", "name", item.title)}${textarea(ui, "responsibility", "responsibility", item.responsibility)}${textarea(ui, "acceptance", "acceptance", item.acceptance)}${launchModeField(ui, _optionalChain([item, 'access', _89 => _89.launch, 'optionalAccess', _90 => _90.mode]))}${planningFields(ui, board, item)}${itemResourceField(ui, resources, true)}${save(ui)}</form>`;
379
- }
380
- function launchModeField(ui, mode = "automatic") {
381
- return `<label>${escapeHTML(ui.text("taskStartMode"))}<select name="launchMode">${["automatic", "manual"].map((value2) => `<option value="${value2}" ${mode === value2 ? "selected" : ""}>${escapeHTML(ui.text("taskMode." + value2))}</option>`).join("")}</select></label>`;
382
- }
383
-
384
- // src/ui/automation.ts
385
- function automationForm(ui, board, catalog) {
386
- const settings = board.automation;
387
- const select = (name, label) => {
388
- const selected = _optionalChain([settings, 'optionalAccess', _91 => _91[name]]), targets = [...name === "preparation" ? _nullishCoalesce(catalog.preparations, () => ( [])) : catalog.targets];
389
- if (_optionalChain([settings, 'optionalAccess', _92 => _92[name]]) && !targets.some((entry) => sameReference(entry.reference, selected)))
390
- targets.push({ reference: settings[name], label: ui.text("automationTargetUnavailable") });
391
- return `<label>${escapeHTML(ui.text(label))}<select name="${name}" ${name === "preparation" ? "" : "required"}><option value="">${escapeHTML(ui.text("automationChoose"))}</option>${targets.map((entry) => {
392
- const value2 = JSON.stringify(entry.reference);
393
- return `<option value="${escapeAttr(value2)}" ${sameReference(entry.reference, selected) ? "selected" : ""}>${escapeHTML(entry.label)}</option>`;
394
- }).join("")}</select></label>`;
395
- };
396
- return `${panelHeader(ui, ui.text("automationTitle"))}<form data-form="automation">
397
- <p>${escapeHTML(ui.text("automationHelp"))}</p>
398
- <label><input type="checkbox" name="enabled" ${_optionalChain([settings, 'optionalAccess', _93 => _93.enabled]) ? "checked" : ""}>${escapeHTML(ui.text("automationEnabled"))}</label>
399
- ${select("planner", "automationPlanner")}${select("executor", "automationExecutor")}${select("preparation", "automationPreparation")}<div data-preparation-options>${preparationFields(board, catalog)}</div>
400
- <label><input type="checkbox" name="autoAccept" ${_optionalChain([settings, 'optionalAccess', _94 => _94.autoAccept]) ? "checked" : ""}>${escapeHTML(ui.text("automationAutoAccept"))}</label>
401
- <p>${escapeHTML(ui.text("automationReviewHelp"))}</p>
402
- <div class="kanban__actions"><button>${escapeHTML(ui.text("save"))}</button>${button(ui, "close-panel", "close")}</div></form>`;
403
- }
404
- function analysisView(ui, status) {
405
- if (!status) return `<p>${escapeHTML(ui.text("automationPending"))}</p>`;
406
- return `<section><h4>${escapeHTML(ui.text("automationAnalysis"))}</h4><p>${escapeHTML(ui.text("automationState." + status.state))}</p>
407
- ${status.reason ? `<p>${escapeHTML(status.reason)}</p>` : ""}${status.code ? `<p>${escapeHTML(ui.text("errors." + status.code))}</p>` : ""}
408
- ${["failed", "clarification"].includes(status.state) ? button(ui, "reanalyze", "automationReanalyze") : ""}</section>`;
409
- }
410
- function preparationFields(board, catalog, reference = _optionalChain([board, 'access', _95 => _95.automation, 'optionalAccess', _96 => _96.preparation])) {
411
- const selected = reference;
412
- const entries = _nullishCoalesce(_optionalChain([catalog, 'access', _97 => _97.preparations, 'optionalAccess', _98 => _98.filter, 'call', _99 => _99((entry) => sameReference(entry.reference, selected))]), () => ( []));
413
- return entries.flatMap((entry) => (_nullishCoalesce(entry.options, () => ( []))).map((option) => {
414
- const value2 = _nullishCoalesce(_nullishCoalesce(_optionalChain([board, 'access', _100 => _100.automation, 'optionalAccess', _101 => _101.preparationOptions, 'optionalAccess', _102 => _102[option.name]]), () => ( option.defaultValue)), () => ( ""));
415
- const name = "preparation-option-" + option.name;
416
- const control = option.choices ? `<select name="${escapeAttr(name)}">${option.choices.map((choice) => `<option value="${escapeAttr(choice.value)}" ${choice.value === value2 ? "selected" : ""}>${escapeHTML(choice.label)}</option>`).join("")}</select>` : `<input name="${escapeAttr(name)}" value="${escapeAttr(value2)}" maxlength="4096">`;
417
- return `<label>${escapeHTML(option.label)}${control}</label>`;
418
- })).join("");
419
- }
420
- function sameReference(left, right) {
421
- const a = left;
422
- const b = right;
423
- return !!a && !!b && a.system === b.system && a.id === b.id && a.revision === b.revision;
424
- }
425
-
426
- // src/ui/suggestion-picker.ts
427
- var KanbanSuggestionPicker = (_class = class {
428
- constructor(root, config, fetch, presentation) {;_class.prototype.__init.call(this);_class.prototype.__init2.call(this);_class.prototype.__init3.call(this);_class.prototype.__init4.call(this);_class.prototype.__init5.call(this);_class.prototype.__init6.call(this);_class.prototype.__init7.call(this);_class.prototype.__init8.call(this);_class.prototype.__init9.call(this);_class.prototype.__init10.call(this);
429
- this.root = root;
430
- this.config = config;
431
- this.fetch = fetch;
432
- this.presentation = presentation;
433
- this.selected = [..._nullishCoalesce(config.selected, () => ( []))];
434
- this.mount();
435
- this.bind();
436
- this.renderValues();
437
- this.close();
438
- }
439
- __init() {this.input = document.createElement("input")}
440
- __init2() {this.list = document.createElement("div")}
441
- __init3() {this.chips = document.createElement("div")}
442
- __init4() {this.values = document.createElement("div")}
443
- __init5() {this.hint = document.createElement("small")}
444
-
445
- __init6() {this.rows = []}
446
- __init7() {this.active = -1}
447
- __init8() {this.loading = false}
448
- __init9() {this.generation = 0}
449
-
450
- __init10() {this.abort = new AbortController()}
451
- mount() {
452
- const caption = document.createElement("label"), id = "kanban-suggest-" + _chunk6MN3H3YFcjs.randomUUID.call(void 0, );
453
- caption.textContent = this.presentation.text(this.config.label);
454
- caption.htmlFor = this.input.id = id;
455
- this.input.type = "text";
456
- this.input.autocomplete = "off";
457
- this.input.maxLength = 256;
458
- this.input.required = !!this.config.required;
459
- this.input.setAttribute("role", "combobox");
460
- this.input.setAttribute("aria-autocomplete", "list");
461
- this.input.setAttribute("aria-controls", this.list.id = id + "-list");
462
- this.input.setAttribute("aria-describedby", this.hint.id = id + "-hint");
463
- this.input.placeholder = this.config.free ? this.presentation.text(this.config.label) + " \xB7 " + this.presentation.text("suggestFreePlaceholder") : this.presentation.text("suggestPlaceholder");
464
- if (this.config.free) this.input.name = this.config.name;
465
- this.input.value = this.config.multiple ? "" : _nullishCoalesce(_optionalChain([this, 'access', _103 => _103.selected, 'access', _104 => _104[0], 'optionalAccess', _105 => _105.label]), () => ( ""));
466
- this.list.className = "kanban__suggest-list";
467
- this.list.setAttribute("role", "listbox");
468
- this.list.setAttribute("aria-label", caption.textContent);
469
- this.chips.className = "kanban__suggest-chips";
470
- this.hint.setAttribute("aria-live", "polite");
471
- this.hint.textContent = this.presentation.text(this.config.free ? "suggestFreeHint" : "suggestHint");
472
- this.root.replaceChildren(caption, this.chips, this.input, this.values, this.list, this.hint);
473
- }
474
- bind() {
475
- const signal = this.abort.signal;
476
- this.input.addEventListener("focus", () => {
477
- if (!this.config.multiple && this.selected.length) this.input.select();
478
- if (!this.loading) void this.search();
479
- }, { signal });
480
- this.input.addEventListener("click", () => {
481
- if (this.list.hidden && !this.loading) void this.search();
482
- }, { signal });
483
- this.input.addEventListener("input", () => {
484
- if (!this.config.multiple && !this.config.free) {
485
- this.selected = [];
486
- this.renderValues();
487
- }
488
- this.validate();
489
- clearTimeout(this.timer);
490
- this.generation++;
491
- this.rows = [];
492
- this.active = -1;
493
- this.list.replaceChildren();
494
- this.hint.textContent = this.presentation.text("loading");
495
- this.open();
496
- this.timer = setTimeout(() => {
497
- void this.search();
498
- }, 150);
499
- }, { signal });
500
- this.input.addEventListener("keydown", (event) => this.keydown(event), { signal });
501
- this.list.addEventListener("pointerdown", (event) => event.preventDefault(), { signal });
502
- this.root.addEventListener("click", (event) => this.click(event), { signal });
503
- document.addEventListener("scroll", () => this.position(), { signal, capture: true });
504
- window.addEventListener("resize", () => this.position(), { signal });
505
- this.root.addEventListener("focusout", (event) => {
506
- if (!this.root.contains(event.relatedTarget)) this.close();
507
- }, { signal });
508
- }
509
- async search() {
510
- const generation = ++this.generation, operationId = _chunk6MN3H3YFcjs.randomUUID.call(void 0, ), started = Date.now();
511
- const search = !this.config.multiple && _optionalChain([this, 'access', _106 => _106.selected, 'access', _107 => _107[0], 'optionalAccess', _108 => _108.label]) === this.input.value ? "" : this.input.value.trim();
512
- const kind = _optionalChain([this, 'access', _109 => _109.root, 'access', _110 => _110.closest, 'call', _111 => _111("form"), 'optionalAccess', _112 => _112.querySelector, 'call', _113 => _113("[name=kind]"), 'optionalAccess', _114 => _114.value]);
513
- this.revealInput();
514
- this.loading = true;
515
- this.hint.textContent = this.presentation.text("loading");
516
- try {
517
- const page = await this.fetch({
518
- kind: this.config.kind,
519
- search,
520
- limit: 12,
521
- ...this.config.multiple ? { excludeIds: this.selected.map((row) => row.id) } : {},
522
- ...this.config.excludeItemId ? { excludeItemId: this.config.excludeItemId } : {},
523
- ...this.config.kind === "parent" && kind ? { childKind: kind } : {}
524
- });
525
- if (generation !== this.generation || this.abort.signal.aborted || !this.root.isConnected) return;
526
- this.rows = page.items.filter((row) => !this.config.multiple || !this.selected.some((value2) => value2.id === row.id));
527
- if (this.config.manual && search && !this.rows.some((row) => row.id === search)) this.rows.push({ id: search, label: search, detail: this.presentation.text("suggestManualId") });
528
- this.active = this.rows.length ? 0 : -1;
529
- this.renderOptions();
530
- this.hint.textContent = this.presentation.text(page.hasMore ? "suggestRefine" : this.rows.length ? this.config.free ? "suggestFreeHint" : "suggestHint" : "suggestEmpty");
531
- this.log({ operationId, stage: this.config.kind, result: "success", count: page.items.length, durationMs: Date.now() - started });
532
- } catch (error) {
533
- if (generation !== this.generation || this.abort.signal.aborted) return;
534
- this.rows = [];
535
- this.list.replaceChildren();
536
- this.hint.textContent = this.presentation.text("suggestFailed");
537
- const retry = document.createElement("button");
538
- retry.type = "button";
539
- retry.textContent = this.presentation.text("retry");
540
- retry.dataset.suggestRetry = "";
541
- this.list.append(retry);
542
- this.open();
543
- const raw = _optionalChain([error, 'optionalAccess', _115 => _115.code]), code = typeof raw === "string" && /^[A-Z_0-9]{1,80}$/.test(raw) ? raw : "KANBAN_SUGGEST_FAILED";
544
- this.log({ operationId, stage: this.config.kind, result: "failure", code, durationMs: Date.now() - started });
545
- } finally {
546
- if (generation === this.generation) this.loading = false;
547
- }
548
- }
549
- log(event) {
550
- try {
551
- _optionalChain([this, 'access', _116 => _116.presentation, 'access', _117 => _117.log, 'optionalCall', _118 => _118(event)]);
552
- } catch (e2) {
553
- }
554
- }
555
- renderOptions() {
556
- this.list.replaceChildren();
557
- this.rows.forEach((row, index) => {
558
- const button2 = document.createElement("button");
559
- button2.type = "button";
560
- button2.tabIndex = -1;
561
- button2.dataset.suggestIndex = String(index);
562
- button2.id = this.list.id + "-" + index;
563
- button2.setAttribute("role", "option");
564
- const title = document.createElement("strong"), detail = document.createElement("small");
565
- title.textContent = row.label;
566
- detail.textContent = [row.status ? this.presentation.text(row.status) : "", row.archived ? this.presentation.text("archive") : "", row.detail, this.config.free ? "" : row.id].filter(Boolean).join(" \xB7 ");
567
- button2.append(title, detail);
568
- this.list.append(button2);
569
- });
570
- this.open();
571
- this.markActive();
572
- }
573
- click(event) {
574
- const button2 = event.target.closest("button");
575
- if (!button2) return;
576
- if (button2.dataset.suggestIndex !== void 0) this.choose(this.rows[Number(button2.dataset.suggestIndex)]);
577
- else if (button2.dataset.suggestRemove !== void 0) {
578
- this.selected = this.selected.filter((row) => row.id !== button2.dataset.suggestRemove);
579
- this.renderValues();
580
- this.changed();
581
- this.input.focus();
582
- } else if (button2.hasAttribute("data-suggest-retry")) {
583
- void this.search();
584
- this.input.focus();
585
- }
586
- }
587
- choose(row) {
588
- if (!row) return;
589
- this.selected = this.config.multiple ? [...this.selected, row] : [row];
590
- this.input.value = this.config.multiple ? "" : row.label;
591
- this.renderValues();
592
- this.input.setCustomValidity("");
593
- this.hint.textContent = this.presentation.text(this.config.free ? "suggestFreeHint" : "suggestHint");
594
- this.changed();
595
- this.close();
596
- this.input.focus();
597
- }
598
- renderValues() {
599
- this.values.replaceChildren();
600
- this.chips.replaceChildren();
601
- if (!this.config.free) for (const row of this.selected.length ? this.selected : this.config.multiple ? [] : [{ id: "", label: "" }]) {
602
- const input = document.createElement("input");
603
- input.type = "hidden";
604
- input.name = this.config.name;
605
- input.value = row.id;
606
- this.values.append(input);
607
- }
608
- if (this.config.multiple) for (const row of this.selected) {
609
- const chip = document.createElement("button");
610
- chip.type = "button";
611
- chip.dataset.suggestRemove = row.id;
612
- const name = document.createElement("span"), remove = document.createElement("span");
613
- name.textContent = row.label;
614
- remove.innerHTML = this.presentation.icons.close;
615
- remove.setAttribute("aria-hidden", "true");
616
- chip.append(name, remove);
617
- chip.setAttribute("aria-label", this.presentation.text("suggestRemove") + ": " + row.label);
618
- this.chips.append(chip);
619
- }
620
- }
621
- validate() {
622
- const unselected = !this.config.free && !!this.input.value.trim() && (this.config.multiple || !this.selected.length);
623
- this.input.setCustomValidity(unselected ? this.presentation.text("suggestPickRequired") : "");
624
- }
625
- changed() {
626
- this.validate();
627
- this.root.dispatchEvent(new Event("input", { bubbles: true }));
628
- }
629
- keydown(event) {
630
- if (event.key === "Escape" && !this.list.hidden) {
631
- event.preventDefault();
632
- event.stopPropagation();
633
- this.close();
634
- return;
635
- }
636
- if (event.key === "ArrowDown" || event.key === "ArrowUp") {
637
- event.preventDefault();
638
- event.stopPropagation();
639
- if (this.list.hidden) {
640
- void this.search();
641
- return;
642
- }
643
- this.active = this.rows.length ? (this.active + (event.key === "ArrowDown" ? 1 : -1) + this.rows.length) % this.rows.length : -1;
644
- this.markActive();
645
- } else if (event.key === "Enter" && !this.list.hidden) {
646
- event.preventDefault();
647
- event.stopPropagation();
648
- this.choose(this.rows[this.active]);
649
- } else if (event.key === "Tab" && !this.list.querySelector("[data-suggest-retry]")) this.close();
650
- else if (event.key === "Backspace" && this.config.multiple && !this.input.value && this.selected.length) {
651
- this.selected.pop();
652
- this.renderValues();
653
- this.changed();
654
- }
655
- }
656
- markActive() {
657
- [...this.list.children].forEach((node, index) => node.setAttribute("aria-selected", String(index === this.active)));
658
- if (this.active >= 0) {
659
- this.input.setAttribute("aria-activedescendant", this.list.id + "-" + this.active);
660
- const row = this.list.children[this.active];
661
- if (row && row.offsetTop < this.list.scrollTop) this.list.scrollTop = row.offsetTop;
662
- else if (row && row.offsetTop + row.offsetHeight > this.list.scrollTop + this.list.clientHeight) this.list.scrollTop = row.offsetTop + row.offsetHeight - this.list.clientHeight;
663
- } else this.input.removeAttribute("aria-activedescendant");
664
- }
665
- open() {
666
- this.list.hidden = false;
667
- this.input.setAttribute("aria-expanded", "true");
668
- this.position();
669
- }
670
- revealInput() {
671
- const panel = this.root.closest("[data-panel]");
672
- if (!panel) return;
673
- const input = this.input.getBoundingClientRect(), bounds = panel.getBoundingClientRect();
674
- const top = (_nullishCoalesce(_optionalChain([panel, 'access', _119 => _119.querySelector, 'call', _120 => _120(".kanban__panel-header"), 'optionalAccess', _121 => _121.getBoundingClientRect, 'call', _122 => _122(), 'access', _123 => _123.bottom]), () => ( bounds.top))) + 8, bottom = bounds.bottom - 12;
675
- if (input.bottom > bottom) panel.scrollTop += input.bottom - bottom;
676
- else if (input.top < top) panel.scrollTop -= top - input.top;
677
- }
678
- position() {
679
- if (this.list.hidden || !this.root.isConnected) return;
680
- const input = this.input.getBoundingClientRect(), scope = (_nullishCoalesce(_nullishCoalesce(this.root.closest("[data-panel]"), () => ( this.root.closest(".kanban"))), () => ( document.documentElement))).getBoundingClientRect();
681
- const below = Math.max(0, scope.bottom - input.bottom - 8), above = Math.max(0, input.top - scope.top - 8), up = above > below && below < 160;
682
- const height = Math.min(210, up ? above : below), width = Math.min(Math.max(input.width, 220), Math.max(0, scope.width - 16));
683
- const left = Math.max(scope.left + 8, Math.min(input.left, scope.right - width - 8));
684
- Object.assign(this.list.style, {
685
- position: "fixed",
686
- zIndex: "30",
687
- left: `${left}px`,
688
- width: `${width}px`,
689
- maxHeight: `${height}px`,
690
- top: up ? "auto" : `${input.bottom + 4}px`,
691
- bottom: up ? `${innerHeight - input.top + 4}px` : "auto"
692
- });
693
- }
694
- close() {
695
- this.loading = false;
696
- if (this.hint.textContent === this.presentation.text("loading")) this.hint.textContent = this.presentation.text(this.config.free ? "suggestFreeHint" : "suggestHint");
697
- clearTimeout(this.timer);
698
- this.generation++;
699
- this.list.hidden = true;
700
- this.input.setAttribute("aria-expanded", "false");
701
- this.input.removeAttribute("aria-activedescendant");
702
- }
703
- destroy() {
704
- clearTimeout(this.timer);
705
- this.generation++;
706
- this.abort.abort();
707
- }
708
- }, _class);
709
-
710
- // src/ui/suggestions.ts
711
- var KanbanSuggestions = (_class2 = class {
712
- constructor(root, service, boardId, presentation) {;_class2.prototype.__init11.call(this);
713
- this.root = root;
714
- this.service = service;
715
- this.boardId = boardId;
716
- this.presentation = presentation;
717
- this.observer = new MutationObserver(() => this.sync());
718
- this.observer.observe(root, { childList: true, subtree: true });
719
- this.sync();
720
- }
721
-
722
- __init11() {this.fields = /* @__PURE__ */ new Map()}
723
- sync() {
724
- for (const [node, field2] of this.fields) if (!this.root.contains(node)) {
725
- field2.destroy();
726
- this.fields.delete(node);
727
- }
728
- if (!this.service) return;
729
- for (const node of this.root.querySelectorAll("[data-kanban-suggest]")) {
730
- if (this.fields.has(node)) continue;
731
- const options = JSON.parse(node.dataset.kanbanSuggest);
732
- const picker = new KanbanSuggestionPicker(node, options, (query) => this.service.suggest(this.boardId(), query), this.presentation);
733
- this.fields.set(node, picker);
734
- }
735
- }
736
- destroy() {
737
- this.observer.disconnect();
738
- for (const field2 of this.fields.values()) field2.destroy();
739
- this.fields.clear();
740
- }
741
- }, _class2);
742
-
743
- // src/ui/index.ts
744
- var _uicommon = require('@nestos/ui-common');
745
-
746
- // src/ui/drawer.ts
747
- var KanbanDrawer = (_class3 = class {
748
- constructor(editor, sidebar) {;_class3.prototype.__init12.call(this);_class3.prototype.__init13.call(this);_class3.prototype.__init14.call(this);
749
- this.editor = editor;
750
- this.sidebar = sidebar;
751
- }
752
- __init12() {this.key = ""}
753
- __init13() {this.focusRequested = false}
754
-
755
-
756
-
757
- __init14() {this.restoreRequested = false}
758
- get element() {
759
- return this.editor.querySelector("[data-panel]");
760
- }
761
- get open() {
762
- return !this.element.hidden;
763
- }
764
- show(html, key) {
765
- const panel = this.element, same = this.open && this.key === key, top = same ? panel.scrollTop : 0;
766
- if (!this.open) {
767
- const active = document.activeElement;
768
- this.restoreTarget = _nullishCoalesce(active, () => ( void 0));
769
- this.restoreItem = _optionalChain([active, 'optionalAccess', _124 => _124.dataset, 'access', _125 => _125.item]);
770
- this.restoreRecord = _optionalChain([active, 'optionalAccess', _126 => _126.dataset, 'access', _127 => _127.record]);
771
- }
772
- this.focusRequested ||= same && panel.contains(document.activeElement);
773
- const retained = same ? ["[data-work-resource-section]", "[data-related-sessions-section]"].map((selector) => ({ selector, node: panel.querySelector(selector) })) : [];
774
- panel.innerHTML = html;
775
- for (const { selector, node } of retained) if (_optionalChain([node, 'optionalAccess', _128 => _128.hasChildNodes, 'call', _129 => _129()])) _optionalChain([panel, 'access', _130 => _130.querySelector, 'call', _131 => _131(selector), 'optionalAccess', _132 => _132.replaceWith, 'call', _133 => _133(node)]);
776
- const error = document.createElement("p");
777
- error.dataset.panelError = "";
778
- error.setAttribute("role", "alert");
779
- const header = panel.querySelector(".kanban__panel-header");
780
- if (header) header.after(error);
781
- else panel.prepend(error);
782
- panel.hidden = false;
783
- panel.scrollTop = top;
784
- this.key = key;
785
- this.focusRequested ||= !same;
786
- this.editor.querySelector("[data-content]").inert = true;
787
- if (this.sidebar) this.sidebar.inert = true;
788
- this.editor.querySelector("[data-backdrop]").hidden = false;
789
- this.editor.classList.add("kanban--detail");
790
- }
791
- close() {
792
- this.element.hidden = true;
793
- this.element.replaceChildren();
794
- this.key = "";
795
- this.focusRequested = false;
796
- this.restoreRequested = true;
797
- this.editor.querySelector("[data-content]").inert = false;
798
- if (this.sidebar) this.sidebar.inert = false;
799
- this.editor.querySelector("[data-backdrop]").hidden = true;
800
- this.editor.classList.remove("kanban--detail");
801
- }
802
- flushFocus() {
803
- if (this.focusRequested && this.open) {
804
- this.focusRequested = false;
805
- this.element.focus({ preventScroll: true });
806
- } else if (this.restoreRequested) {
807
- this.restoreRequested = false;
808
- const matches = [...this.editor.querySelectorAll("[data-item], [data-record]")];
809
- const target = _optionalChain([this, 'access', _134 => _134.restoreTarget, 'optionalAccess', _135 => _135.isConnected]) ? this.restoreTarget : matches.find((node) => this.restoreItem ? node.dataset.item === this.restoreItem : this.restoreRecord ? node.dataset.record === this.restoreRecord : false);
810
- _optionalChain([(_nullishCoalesce(target, () => ( this.editor.querySelector("button")))), 'optionalAccess', _136 => _136.focus, 'call', _137 => _137({ preventScroll: true })]);
811
- }
812
- }
813
- keydown(event) {
814
- if (!this.open || event.key !== "Tab") return;
815
- const nodes = [...this.element.querySelectorAll('button, input:not([type=hidden]), select, textarea, a[href], summary, [tabindex="0"]')].filter((node) => !node.matches(":disabled") && node.tabIndex >= 0 && !node.closest("[hidden], [inert]") && !node.hidden && !node.closest("details:not([open]) :not(summary)"));
816
- const first = nodes[0], last = nodes[nodes.length - 1], active = document.activeElement;
817
- if (!first) {
818
- event.preventDefault();
819
- this.element.focus();
820
- return;
821
- }
822
- if (event.shiftKey && (active === first || active === this.element)) {
823
- event.preventDefault();
824
- last.focus();
825
- } else if (!event.shiftKey && (active === last || !this.element.contains(active))) {
826
- event.preventDefault();
827
- first.focus();
828
- }
829
- }
830
- destroy() {
831
- if (this.sidebar) this.sidebar.inert = false;
832
- this.editor.classList.remove("kanban--detail");
833
- }
834
- }, _class3);
835
- function drawerMarkup(ui) {
836
- return `<button type="button" class="kanban__backdrop" data-backdrop data-action="dismiss-panel" aria-label="${escapeAttr(ui.text("closeDetail"))}" tabindex="-1" hidden></button>
837
- <section data-panel class="kanban__panel" role="dialog" aria-modal="true" aria-label="${escapeAttr(ui.text("details"))}" tabindex="-1" hidden></section>`;
838
- }
839
- function preserveBoardScroll(container, render) {
840
- const columns = [...container.querySelectorAll("[data-column]")].map((node) => ({ id: node.dataset.column, top: node.scrollTop }));
841
- const left = _nullishCoalesce(_optionalChain([container, 'access', _138 => _138.querySelector, 'call', _139 => _139(".kanban__columns"), 'optionalAccess', _140 => _140.scrollLeft]), () => ( 0));
842
- render();
843
- for (const node of container.querySelectorAll("[data-column]")) node.scrollTop = _nullishCoalesce(_optionalChain([columns, 'access', _141 => _141.find, 'call', _142 => _142((column) => column.id === node.dataset.column), 'optionalAccess', _143 => _143.top]), () => ( 0));
844
- const board = container.querySelector(".kanban__columns");
845
- if (board) board.scrollLeft = left;
846
- }
847
-
848
- // src/ui/diagnostics.ts
849
- function failureDetails(error) {
850
- if (!error || typeof error !== "object") return {};
851
- const value2 = error;
852
- const known = /^(?:Error|TypeError|ReferenceError|SyntaxError|RangeError|SecurityError|QuotaExceededError|NotSupportedError|InvalidStateError|DataCloneError|AbortError|NetworkError)$/;
853
- const errorName = typeof value2.name === "string" && known.test(value2.name) ? value2.name : void 0;
854
- const frame = typeof value2.stack === "string" ? value2.stack.match(/(?:^|\/)([A-Za-z0-9_.-]+\.m?js):(\d+):(\d+)/m) : null;
855
- return { ...errorName ? { errorName } : {}, ...frame ? { source: `${frame[1]}:${frame[2]}:${frame[3]}` } : {} };
856
- }
857
-
858
- // src/ui/operations.ts
859
- function itemOperationsView(ui, view, sessionUrl) {
860
- return `<button type="button" data-action="refresh-item-operations">${escapeHTML(ui.text("operationRefresh"))}</button><h4>${escapeHTML(ui.text("operationsTitle"))}</h4>
861
- <ol class="kanban__operations">${view.operations.map((operation) => operationCard(ui, view, operation, sessionUrl)).join("")}</ol>`;
862
- }
863
- function operationCard(ui, view, operation, url) {
864
- const reason = operation.code ? ui.text("errors." + operation.code) : "";
865
- return `<li data-operation-id="${escapeAttr(operation.id)}"><strong>${escapeHTML(operation.label)}</strong> \xB7 ${escapeHTML(ui.text("operationState." + operation.state))}
866
- ${operation.reason ? `<p>${escapeHTML(operation.reason)}</p>` : ""}${operation.code ? `<p role="alert">${escapeHTML(reason.endsWith("errors." + operation.code) ? operation.code : reason)}</p>` : ""}
867
- ${operation.summary ? `<pre class="kanban__operation-summary">${escapeHTML(operation.summary)}</pre>` : ""}
868
- ${operation.sessionId ? sessionLink(ui, operation.sessionId, ui.text("operationSession"), _optionalChain([url, 'optionalCall', _144 => _144(operation.sessionId)])) : ""}
869
- <form data-form="item-operation" data-operation="${escapeAttr(operation.id)}" data-revision="${escapeAttr(view.revision)}">
870
- ${choices(ui, operation)}${operation.confirmation ? `<label><input type="checkbox" name="confirmed" required>${escapeHTML(operation.confirmation)}</label>` : ""}
871
- <button type="submit"${operation.enabled ? "" : " disabled"}>${escapeHTML(operation.label)}</button></form>${configuration(ui, view, operation)}</li>`;
872
- }
873
- function choices(ui, operation) {
874
- return (_nullishCoalesce(operation.fields, () => ( []))).map((field2) => `<label>${escapeHTML(field2.label)}<select name="choice-${escapeAttr(field2.id)}" required>
875
- <option value="">${escapeHTML(ui.text("operationChoose"))}</option>${field2.choices.map((choice) => `<option value="${escapeAttr(choice.id)}"${field2.selected === choice.id ? " selected" : ""}>${escapeHTML(choice.label + (choice.detail ? " \xB7 " + choice.detail : ""))}</option>`).join("")}</select></label>`).join("");
876
- }
877
- function configuration(ui, view, operation) {
878
- if (!operation.configurable) return "";
879
- return `<details><summary>${escapeHTML(ui.text("operationConfigure"))}</summary><form data-form="operation-binding" data-operation="${escapeAttr(operation.id)}" data-revision="${escapeAttr(view.revision)}">
880
- <label>${escapeHTML(ui.text("operationFlow"))}<select name="target" required><option value="">${escapeHTML(ui.text("operationChoose"))}</option>
881
- ${(_nullishCoalesce(view.targets, () => ( []))).map((target) => `<option value="${escapeAttr(JSON.stringify(target.reference))}"${JSON.stringify(target.reference) === JSON.stringify(operation.binding) ? " selected" : ""}>${escapeHTML(target.label)}</option>`).join("")}</select></label>
882
- <button type="submit">${escapeHTML(ui.text("save"))}</button>${_optionalChain([view, 'access', _145 => _145.targets, 'optionalAccess', _146 => _146.length]) ? "" : `<p>${escapeHTML(ui.text("operationNoFlows"))}</p>`}</form></details>`;
883
- }
884
-
885
- // src/ui/work-resources.ts
886
- function workResourcesView(ui, resources) {
887
- if (!resources.length) return `<p>${escapeHTML(ui.text("none"))}</p>`;
888
- return resources.map((resource) => `<article class="kanban__work-resource" data-work-resource="${escapeAttr(resource.id)}">
889
- <strong>${escapeHTML(resource.title)}</strong>${copy(ui, resource.title, ui.text("copyWorkResourceName"))} \xB7 ${escapeHTML(ui.text("workResourceState." + resource.state))}
890
- <p><code>${escapeHTML(resource.location)}</code>${copy(ui, resource.location, ui.text("copyWorkResourcePath"))}</p>
891
- <dl>${resource.properties.map((row) => `<dt>${escapeHTML(row.label)}</dt><dd>${escapeHTML(row.value)}${copy(ui, row.value, ui.text("copyValue") + " \xB7 " + row.label)}</dd>`).join("")}</dl>
892
- ${resource.reasons.map((reason) => `<p>${escapeHTML(reasonLabel(ui, reason))}</p>`).join("")}
893
- <button type="button" data-action="cleanup-work-resource" data-resource="${escapeAttr(resource.id)}" data-version="${escapeAttr(resource.version)}" ${resource.canCleanup ? "" : "disabled"}>${escapeHTML(ui.text("cleanupWorkResource"))}</button>
894
- </article>`).join("");
895
- }
896
- function copy(ui, value2, label) {
897
- return `<button type="button" class="kanban__copy-button" data-action="copy-resource" data-copy="${escapeAttr(value2)}" title="${escapeAttr(label)}" aria-label="${escapeAttr(label)}">${_nullishCoalesce(ui.icons.copy, () => ( escapeHTML(ui.text("copyValue"))))}</button>`;
898
- }
899
- function reasonLabel(ui, reason) {
900
- const key = "workResourceReason." + reason, label = ui.text(key);
901
- return label.includes(key) ? reason : label;
902
- }
903
-
904
- // src/ui/renderer.ts
905
- function createRenderer(ui) {
906
- const bind = (fn) => (...args) => fn(ui, ...args);
907
- return {
908
- label: ui.text.bind(ui),
909
- boardView: bind(boardView),
910
- catalogView: bind(catalogView),
911
- navigation: bind(navigation),
912
- button: bind(button),
913
- detailView: bind(detailView),
914
- itemEventsView: bind(itemEventsView),
915
- newBoardForm: bind(newBoardForm),
916
- newItemForm: bind(newItemForm),
917
- recordLinks: bind(recordLinks),
918
- recordsView: bind(recordsView),
919
- recordDetail: bind(recordDetail),
920
- recordForm: bind(recordForm),
921
- executionSessionView: bind(executionSessionView),
922
- executionBadge: bind(executionBadge),
923
- copyReference: bind(copyReference)
924
- };
925
- }
926
-
927
- // src/ui/commands.ts
928
- var value = (data, key) => String(_nullishCoalesce(data.get(key), () => ( ""))).trim();
929
- function readPlan(data, previous) {
930
- const area = value(data, "area"), milestone = value(data, "milestone"), parentId = value(data, "parentId"), dueAt = value(data, "dueAt");
931
- return {
932
- kind: value(data, "kind"),
933
- ..._optionalChain([previous, 'optionalAccess', _147 => _147.execution]) ? { execution: previous.execution } : {},
934
- priority: Number(value(data, "priority")),
935
- references: _nullishCoalesce(_optionalChain([previous, 'optionalAccess', _148 => _148.references]), () => ( [])),
936
- dependencies: [...new Set(data.getAll("dependencies").flatMap((value2) => String(value2).split(",")).map((id) => id.trim()).filter(Boolean))],
937
- ...area ? { area } : {},
938
- ...milestone ? { milestone } : {},
939
- ...parentId ? { parentId } : {},
940
- ...dueAt ? { dueAt: new Date(dueAt).getTime() } : {}
941
- };
942
- }
943
- function boardInput(data, actor, id) {
944
- return {
945
- id,
946
- title: value(data, "title"),
947
- scope: { kind: "system" },
948
- members: [{ ...actor, responsibility: "Manage personal work", roles: ["worker", "reviewer", "coordinator"] }],
949
- policy: { requireEstimate: data.has("requireEstimate"), review: "assignee" }
950
- };
951
- }
952
- function formCommand(kind, data, board, item, id = _chunk6MN3H3YFcjs.randomUUID.call(void 0, )) {
953
- if (kind === "item") return { type: "create-item", ...value(data, "launchMode") ? { launch: { mode: value(data, "launchMode") } } : {}, item: { id, title: value(data, "title"), responsibility: value(data, "responsibility"), acceptance: value(data, "acceptance"), ...value(data, "itemResource") ? { externalRefs: [JSON.parse(value(data, "itemResource"))] } : {} }, planning: readPlan(data) };
954
- if (kind === "record") {
955
- const sessionId = value(data, "sourceSession"), messageId = value(data, "sourceMessage"), revision = value(data, "sourceRevision");
956
- if ([sessionId, messageId, revision].some(Boolean) && ![sessionId, messageId, revision].every(Boolean)) throw Object.assign(new Error("Missing source fields"), { code: "SOURCE_FIELDS_REQUIRED" });
957
- return { type: "append-record", record: {
958
- id,
959
- kind: value(data, "kind"),
960
- title: value(data, "title"),
961
- content: value(data, "content"),
962
- externalRefs: data.getAll("recordResources").map((value2) => JSON.parse(String(value2))),
963
- itemIds: [...new Set(data.getAll("itemIds").flatMap((value2) => String(value2).split(",")).map((id2) => id2.trim()).filter(Boolean))],
964
- sources: sessionId ? [{ sessionId, messageId, revision, role: "user" }] : [],
965
- ...value(data, "rationale") ? { rationale: value(data, "rationale") } : {},
966
- ...value(data, "supersedes") ? { supersedes: value(data, "supersedes") } : {}
967
- } };
968
- }
969
- if (!item) throw new Error("ITEM_NOT_FOUND");
970
- const itemId = item.id, token = _nullishCoalesce(_optionalChain([item, 'access', _149 => _149.owner, 'optionalAccess', _150 => _150.token]), () => ( ""));
971
- if (kind === "edit-item") return { type: "edit-item", itemId, title: value(data, "title"), responsibility: value(data, "responsibility"), acceptance: value(data, "acceptance") };
972
- if (kind === "amendment") return { type: "review-amendment", itemId, amendmentId: value(data, "amendmentId"), accept: value(data, "decision") === "accept", reason: value(data, "reason") || void 0 };
973
- if (kind === "claim") return { type: "claim", itemId, ...board.policy.requireEstimate !== false ? { estimate: estimate(data) } : {} };
974
- if (kind === "close-plan") return { type: "close-plan", itemId, resultRef: value(data, "resultRef") };
975
- if (kind === "submit") return { type: "submit", itemId, token, resultRef: value(data, "resultRef") };
976
- if (kind === "plan") return { type: "plan-item", itemId, planning: readPlan(data, item.planning) };
977
- if (kind === "cancel") return { type: "cancel-item", itemId, reason: value(data, "reason") };
978
- if (kind === "release") return { type: "release-item", itemId, token, next: "backlog", reason: value(data, "reason") };
979
- if (kind === "block") return { type: "block-item", itemId, token, reason: value(data, "reason") };
980
- if (kind === "session") return { type: "register-session", itemId, sessionId: value(data, "sessionId"), role: "observer", subscribed: true };
981
- if (kind === "message") return { type: "message", itemId, message: value(data, "message"), recipientSessionIds: item.participants.filter((p) => p.subscribed).map((p) => p.sessionId) };
982
- throw new Error("UNKNOWN_COMMAND");
983
- }
984
- function estimate(data) {
985
- return { complexity: Number(value(data, "complexity")), workUnits: Number(value(data, "workUnits")), durationMs: Number(value(data, "duration")) * 6e4, confidence: value(data, "confidence") };
986
- }
987
- function actionCommand(action, item) {
988
- const itemId = item.id, token = _nullishCoalesce(_optionalChain([item, 'access', _151 => _151.owner, 'optionalAccess', _152 => _152.token]), () => ( ""));
989
- if (action === "ready" || action === "defer") return { type: "set-stage", itemId, stage: action === "ready" ? "open" : "backlog" };
990
- if (action === "publish") return { type: "publish-proposal", itemId };
991
- if (action === "renew") return { type: "renew", itemId, token };
992
- if (action === "accept" || action === "reject") return { type: "review", itemId, accept: action === "accept" };
993
- if (action === "archive") return { type: "archive-items", itemIds: [itemId], reason: "manual" };
994
- if (action === "restore") return { type: "restore-item", itemId };
995
- if (action === "unblock") return { type: "unblock-item", itemId, token };
996
- throw new Error("UNKNOWN_COMMAND");
997
- }
998
-
999
- // src/ui/index.ts
1000
- var KanbanWorkbench = (_class4 = class {
1001
- constructor(options, container) {;_class4.prototype.__init15.call(this);_class4.prototype.__init16.call(this);_class4.prototype.__init17.call(this);_class4.prototype.__init18.call(this);_class4.prototype.__init19.call(this);_class4.prototype.__init20.call(this);_class4.prototype.__init21.call(this);_class4.prototype.__init22.call(this);_class4.prototype.__init23.call(this);_class4.prototype.__init24.call(this);_class4.prototype.__init25.call(this);_class4.prototype.__init26.call(this);_class4.prototype.__init27.call(this);_class4.prototype.__init28.call(this);
1002
- this.options = options;
1003
- this.container = container;
1004
- this.ui = createRenderer(options.presentation);
1005
- }
1006
-
1007
-
1008
-
1009
-
1010
-
1011
-
1012
- __init15() {this.itemArchived = false}
1013
-
1014
- __init16() {this.boardId = ""}
1015
-
1016
- __init17() {this.recordId = ""}
1017
-
1018
-
1019
-
1020
-
1021
- __init18() {this.itemId = ""}
1022
- __init19() {this.query = { view: "all", limit: 50 }}
1023
-
1024
- __init20() {this.generation = 0}
1025
- __init21() {this.eventGeneration = 0}
1026
- __init22() {this.abort = new AbortController()}
1027
- __init23() {this.busy = false}
1028
-
1029
- __init24() {this.operationStage = "ui.operation"}
1030
- __init25() {this.dirty = false}
1031
- __init26() {this.visible = true}
1032
- __init27() {this.invalid = false}
1033
-
1034
-
1035
-
1036
- __init28() {this.windowMounts = /* @__PURE__ */ new Map()}
1037
- async start() {
1038
- const { sidebar, editor } = this.options, signal = this.abort.signal;
1039
- _optionalChain([sidebar, 'optionalAccess', _153 => _153.classList, 'access', _154 => _154.add, 'call', _155 => _155("kanban__sidebar")]);
1040
- editor.classList.add("kanban");
1041
- editor.innerHTML = `<p data-status role="status"></p><p data-error role="alert"></p><div data-retry hidden>${this.ui.button("refresh", "retry")}</div><div data-content class="kanban__content"></div>${drawerMarkup(this.options.presentation)}`;
1042
- this.drawer = new KanbanDrawer(editor, sidebar);
1043
- this.suggestions = new KanbanSuggestions(editor, this.options.service, () => this.boardId, this.options.presentation);
1044
- document.addEventListener("pointerdown", (event) => {
1045
- for (const menu of editor.querySelectorAll("[data-popover][open]")) if (!menu.contains(event.target)) menu.open = false;
1046
- }, { signal });
1047
- editor.addEventListener("toggle", (event) => {
1048
- const opened = event.target;
1049
- if (opened instanceof HTMLDetailsElement && opened.open && opened.hasAttribute("data-related-sessions")) {
1050
- void this.loadRelatedSessions();
1051
- return;
1052
- }
1053
- if (opened instanceof HTMLDetailsElement && opened.open && opened.hasAttribute("data-work-resources")) {
1054
- void this.loadWorkResources();
1055
- return;
1056
- }
1057
- if (!(opened instanceof HTMLDetailsElement) || !opened.open || !opened.hasAttribute("data-popover")) return;
1058
- for (const menu of editor.querySelectorAll("[data-popover][open]")) if (menu !== opened) menu.open = false;
1059
- }, { capture: true, signal });
1060
- editor.addEventListener("change", (event) => {
1061
- const target = event.target;
1062
- if (!(target instanceof HTMLSelectElement) || target.name !== "kind") return;
1063
- const resource = _optionalChain([target, 'access', _156 => _156.closest, 'call', _157 => _157("form"), 'optionalAccess', _158 => _158.querySelector, 'call', _159 => _159('select[name="itemResource"]')]);
1064
- if (resource) resource.required = resource.dataset.resourceRequired === "true" && _optionalChain([target, 'access', _160 => _160.selectedOptions, 'access', _161 => _161[0], 'optionalAccess', _162 => _162.dataset, 'access', _163 => _163.executable]) === "true";
1065
- }, { signal });
1066
- editor.addEventListener("keydown", (event) => {
1067
- const menu = editor.querySelector("[data-popover][open]");
1068
- if (event.key === "Escape" && menu) {
1069
- event.preventDefault();
1070
- event.stopPropagation();
1071
- menu.open = false;
1072
- _optionalChain([menu, 'access', _164 => _164.querySelector, 'call', _165 => _165("summary"), 'optionalAccess', _166 => _166.focus, 'call', _167 => _167()]);
1073
- return;
1074
- }
1075
- this.drawer.keydown(event);
1076
- if (event.key === "Escape" && this.drawer.open) {
1077
- event.preventDefault();
1078
- void this.run(() => this.requestClose(false));
1079
- }
1080
- }, { signal });
1081
- for (const element of sidebar ? [sidebar, editor] : [editor]) element.addEventListener("click", (event) => {
1082
- if (event instanceof MouseEvent && (event.ctrlKey || event.metaKey || event.shiftKey || event.altKey || event.button !== 0)) return;
1083
- if (!event.target.closest("[data-action], [data-board], [data-item], [data-session], [data-record], [data-context]")) return;
1084
- void this.run(async () => {
1085
- await this.click(event);
1086
- if (element === sidebar) _optionalChain([this, 'access', _168 => _168.options, 'access', _169 => _169.activated, 'optionalCall', _170 => _170()]);
1087
- });
1088
- }, { signal });
1089
- _optionalChain([sidebar, 'optionalAccess', _171 => _171.addEventListener, 'call', _172 => _172("submit", (event) => {
1090
- event.preventDefault();
1091
- void this.run(() => this.submit(event));
1092
- }, { signal })]);
1093
- editor.addEventListener("submit", (event) => {
1094
- event.preventDefault();
1095
- void this.run(() => this.submit(event));
1096
- }, { signal });
1097
- editor.addEventListener("change", (event) => {
1098
- const target = event.target;
1099
- if (target.name === "preparation" && this.board && this.automationCatalog) {
1100
- const container = editor.querySelector("[data-preparation-options]");
1101
- if (container) container.innerHTML = preparationFields(this.board, this.automationCatalog, target.value ? JSON.parse(target.value) : null);
1102
- }
1103
- }, { signal });
1104
- editor.addEventListener("input", (event) => {
1105
- const target = event.target;
1106
- if (target.closest("[data-form=reference]")) return;
1107
- if (target.closest("form")) this.generation++;
1108
- if (target.closest("[data-panel]")) {
1109
- this.dirty = true;
1110
- _optionalChain([this, 'access', _173 => _173.options, 'access', _174 => _174.changed, 'optionalCall', _175 => _175()]);
1111
- }
1112
- }, { signal });
1113
- this.activityTimer = setInterval(() => {
1114
- if (!this.visible || document.hidden) return;
1115
- updateTaskCountdowns(this.options.editor, this.options.presentation);
1116
- if (_optionalChain([this, 'access', _176 => _176.options, 'access', _177 => _177.service, 'optionalAccess', _178 => _178.queryActivity]) && (_optionalChain([this, 'access', _179 => _179.board, 'optionalAccess', _180 => _180.automation, 'optionalAccess', _181 => _181.enabled]) || _optionalChain([this, 'access', _182 => _182.page, 'optionalAccess', _183 => _183.items, 'access', _184 => _184.some, 'call', _185 => _185((item) => item.execution)]) || this.query.execution && this.query.execution !== "all")) this.refreshInBackground(true);
1117
- }, 15e3);
1118
- if (this.options.initialResource !== void 0) await this.openResource(this.options.initialResource);
1119
- else await this.run(() => this.refresh());
1120
- }
1121
- open(target) {
1122
- const path = target.boardId ? `/board/${encodeURIComponent(target.boardId)}${target.recordId ? "/@record/" + encodeURIComponent(target.recordId) : target.itemId ? "/" + encodeURIComponent(target.itemId) : ""}` : "/";
1123
- return this.openResource(path);
1124
- }
1125
- dispose() {
1126
- return this.destroy();
1127
- }
1128
- async openResource(path) {
1129
- if (this.busy || this.dirty) throw new Error(this.ui.label("unsaved"));
1130
- this.closePanel();
1131
- const parts = path.split("/");
1132
- this.boardId = decodeURIComponent(_nullishCoalesce(parts[2], () => ( "")));
1133
- this.itemId = decodeURIComponent(_nullishCoalesce(parts[3], () => ( "")));
1134
- this.recordId = parts[3] === "@record" ? decodeURIComponent(_nullishCoalesce(parts[4], () => ( ""))) : "";
1135
- if (this.recordId) this.itemId = "";
1136
- this.recordKind = ["@log", "@decision"].includes(_nullishCoalesce(parts[3], () => ( ""))) ? parts[3].slice(1) : void 0;
1137
- if (this.recordKind) this.itemId = "";
1138
- this.recordCursor = void 0;
1139
- this.query = { view: "all", limit: 50 };
1140
- await this.run(() => this.refresh());
1141
- }
1142
- createBoard() {
1143
- return this.run(async () => {
1144
- if (this.dirty) throw new Error(this.ui.label("unsaved"));
1145
- this.showBoardForm();
1146
- });
1147
- }
1148
- showBoardForm() {
1149
- this.itemId = "";
1150
- this.drawer.show(this.ui.newBoardForm(), "new-board");
1151
- this.dirty = true;
1152
- }
1153
- getActiveResourceId() {
1154
- return this.boardId ? `/board/${encodeURIComponent(this.boardId)}${this.recordId ? "/@record/" + encodeURIComponent(this.recordId) : this.itemId ? "/" + encodeURIComponent(this.itemId) : this.recordKind ? "/@" + this.recordKind : ""}` : "/";
1155
- }
1156
- getViewState() {
1157
- return {
1158
- title: _optionalChain([this, 'access', _186 => _186.drawer, 'optionalAccess', _187 => _187.open]) ? _nullishCoalesce(_optionalChain([this, 'access', _188 => _188.panel, 'call', _189 => _189(), 'access', _190 => _190.querySelector, 'call', _191 => _191("h3"), 'optionalAccess', _192 => _192.textContent]), () => ( _optionalChain([this, 'access', _193 => _193.board, 'optionalAccess', _194 => _194.title]))) : _nullishCoalesce(_optionalChain([this, 'access', _195 => _195.board, 'optionalAccess', _196 => _196.title]), () => ( this.ui.label("title"))),
1159
- context: _optionalChain([this, 'access', _197 => _197.board, 'optionalAccess', _198 => _198.title]),
1160
- dirty: this.dirty,
1161
- busy: this.busy
1162
- };
1163
- }
1164
- captureRecovery() {
1165
- if (this.busy || this.dirty) throw new Error(this.ui.label("unsaved"));
1166
- return null;
1167
- }
1168
- restoreRecovery() {
1169
- }
1170
- suspendForRecovery() {
1171
- this.generation++;
1172
- this.abort.abort();
1173
- _optionalChain([this, 'access', _199 => _199.suggestions, 'optionalAccess', _200 => _200.destroy, 'call', _201 => _201()]);
1174
- if (this.activityTimer) clearInterval(this.activityTimer);
1175
- }
1176
- async destroy() {
1177
- for (const release of this.windowMounts.values()) release();
1178
- this.windowMounts.clear();
1179
- this.suspendForRecovery();
1180
- _optionalChain([this, 'access', _202 => _202.releaseNavigation, 'optionalCall', _203 => _203()]);
1181
- this.releaseNavigation = void 0;
1182
- _optionalChain([this, 'access', _204 => _204.suggestions, 'optionalAccess', _205 => _205.destroy, 'call', _206 => _206()]);
1183
- _optionalChain([this, 'access', _207 => _207.drawer, 'optionalAccess', _208 => _208.destroy, 'call', _209 => _209()]);
1184
- _optionalChain([this, 'access', _210 => _210.options, 'access', _211 => _211.sidebar, 'optionalAccess', _212 => _212.replaceChildren, 'call', _213 => _213()]);
1185
- this.options.editor.replaceChildren();
1186
- this.options.editor.inert = false;
1187
- this.options.editor.classList.remove("kanban");
1188
- _optionalChain([this, 'access', _214 => _214.options, 'access', _215 => _215.sidebar, 'optionalAccess', _216 => _216.classList, 'access', _217 => _217.remove, 'call', _218 => _218("kanban__sidebar")]);
1189
- _optionalChain([this, 'access', _219 => _219.container, 'optionalAccess', _220 => _220.replaceChildren, 'call', _221 => _221()]);
1190
- _optionalChain([this, 'access', _222 => _222.container, 'optionalAccess', _223 => _223.classList, 'access', _224 => _224.remove, 'call', _225 => _225("kanban-layout")]);
1191
- }
1192
- invalidateFromEvents() {
1193
- this.invalid = true;
1194
- this.eventGeneration++;
1195
- this.refreshInBackground();
1196
- }
1197
- setVisible(visible) {
1198
- this.visible = visible;
1199
- if (visible && this.invalid) this.invalidateFromEvents();
1200
- }
1201
- refreshInBackground(activityOnly = false) {
1202
- if (this.background || this.busy || this.dirty || !this.visible || this.abort.signal.aborted) return;
1203
- if (_optionalChain([document, 'access', _226 => _226.activeElement, 'optionalAccess', _227 => _227.closest, 'call', _228 => _228("form")]) && this.options.editor.contains(document.activeElement)) return;
1204
- const started = Date.now(), operationId = _chunk6MN3H3YFcjs.randomUUID.call(void 0, );
1205
- const reading = activityOnly ? this.refreshActivity(this.generation) : this.refresh(), generation = this.generation;
1206
- this.background = reading.catch((error) => {
1207
- if (!this.alive(generation)) return;
1208
- this.invalid = false;
1209
- if (!this.abort.signal.aborted) {
1210
- this.reportRead("ui.background", error, operationId, started);
1211
- this.error(errorText(error, this.ui.label, "ui.background"));
1212
- }
1213
- }).finally(() => {
1214
- this.background = void 0;
1215
- if (this.invalid && !this.busy && !this.dirty && this.visible && !this.abort.signal.aborted) this.refreshInBackground();
1216
- });
1217
- }
1218
- queryPage() {
1219
- const service = this.options.service;
1220
- return this.query.execution && this.query.execution !== "all" && service.queryActivity ? service.queryActivity(this.boardId, this.query) : service.query(this.boardId, this.query);
1221
- }
1222
- async refreshActivity(generation) {
1223
- if (this.item && this.itemId) {
1224
- await Promise.all([this.loadExecutionSections(generation, this.item, this.itemArchived), _optionalChain([this, 'access', _229 => _229.operations, 'optionalAccess', _230 => _230.operations, 'access', _231 => _231.some, 'call', _232 => _232((operation) => ["running", "unknown"].includes(operation.state))]) ? this.loadOperations(generation) : void 0]);
1225
- return;
1226
- }
1227
- if (!this.board || this.recordId || this.recordKind || !_optionalChain([this, 'access', _233 => _233.options, 'access', _234 => _234.service, 'optionalAccess', _235 => _235.queryActivity])) return;
1228
- await this.supplement("ui.activity", generation, () => this.options.service.queryActivity(this.boardId, this.query), (page) => {
1229
- if (page.revision !== _optionalChain([this, 'access', _236 => _236.page, 'optionalAccess', _237 => _237.revision])) {
1230
- this.invalid = true;
1231
- this.refreshInBackground();
1232
- return;
1233
- }
1234
- if (JSON.stringify(page) === JSON.stringify(this.page)) return;
1235
- const sameItems = page.total === this.page.total && page.nextCursor === this.page.nextCursor && JSON.stringify(page.items.map((item) => item.id)) === JSON.stringify(this.page.items.map((item) => item.id));
1236
- this.page = page;
1237
- if (sameItems) {
1238
- const sessions = new Map(page.items.map((item) => [item.id, item.execution]));
1239
- for (const slot of this.content().querySelectorAll("[data-item-execution]")) {
1240
- const html = this.ui.executionBadge(sessions.get(slot.dataset.itemExecution), this.options.sessionUrl);
1241
- if (slot.innerHTML !== html) slot.innerHTML = html;
1242
- }
1243
- return;
1244
- }
1245
- preserveBoardScroll(this.content(), () => {
1246
- this.content().innerHTML = this.ui.boardView(this.board, page, this.query, true, !!_optionalChain([this, 'access', _238 => _238.options, 'access', _239 => _239.service, 'optionalAccess', _240 => _240.automationCatalog]), this.options.sessionUrl);
1247
- });
1248
- this.mountHeaderActions();
1249
- });
1250
- }
1251
- async refresh() {
1252
- const service = this.options.service, generation = ++this.generation, events = this.eventGeneration;
1253
- if (!service) {
1254
- this.content().innerHTML = `<p>${escapeHTML(this.ui.label("unavailable"))}</p>`;
1255
- return;
1256
- }
1257
- this.operationStage = "ui.snapshot";
1258
- const pageRequest = this.boardId ? this.queryPage() : void 0;
1259
- const itemRequest = this.boardId && this.itemId ? service.item(this.boardId, this.itemId) : void 0;
1260
- const [boards, actor, initialPage, initialItem] = await Promise.all([_nullishCoalesce(_optionalChain([service, 'access', _241 => _241.catalog, 'optionalCall', _242 => _242()]), () => ( service.list())), service.currentPrincipal(), pageRequest, itemRequest]);
1261
- if (!this.alive(generation)) return;
1262
- this.actor = actor;
1263
- if (!this.boardId && this.options.initialResource === void 0) this.boardId = _nullishCoalesce(_optionalChain([boards, 'access', _243 => _243[0], 'optionalAccess', _244 => _244.id]), () => ( ""));
1264
- this.board = boards.find((board) => board.id === this.boardId);
1265
- if (this.boardId && !this.board) throw Object.assign(new Error("Board unavailable"), { code: "BOARD_NOT_FOUND" });
1266
- if (this.query.execution && this.query.execution !== "all" && !service.queryActivity) throw Object.assign(new Error("Execution Session projection unavailable"), { code: "EXECUTION_SESSION_UNAVAILABLE" });
1267
- const page = this.board ? await _asyncNullishCoalesce(initialPage, async () => ( await this.queryPage())) : void 0;
1268
- if (!this.alive(generation)) return;
1269
- this.page = page;
1270
- this.invalid = events !== this.eventGeneration;
1271
- if (this.options.sidebar) this.options.sidebar.innerHTML = this.ui.navigation(boards, this.boardId);
1272
- this.operationStage = "ui.render";
1273
- preserveBoardScroll(this.content(), () => {
1274
- this.content().innerHTML = this.board && page ? this.ui.boardView(this.board, page, this.query, !!service.queryActivity, !!service.automationCatalog, this.options.sessionUrl) : this.ui.catalogView(boards);
1275
- });
1276
- if (this.recordKind) await this.showRecords(generation);
1277
- if (!this.alive(generation)) return;
1278
- _optionalChain([this, 'access', _245 => _245.releaseNavigation, 'optionalCall', _246 => _246()]);
1279
- this.releaseNavigation = void 0;
1280
- const navigation2 = this.content().querySelector("[data-navigation]");
1281
- if (navigation2 && !this.options.mountTitleBar) this.releaseNavigation = _optionalChain([this, 'access', _247 => _247.options, 'access', _248 => _248.mountNavigation, 'optionalCall', _249 => _249(navigation2)]);
1282
- if (this.recordId) await this.showRecord(this.recordId, generation);
1283
- else if (this.itemId) await this.showItem(generation, initialItem);
1284
- else if (!this.recordKind) void this.refreshActivity(generation);
1285
- this.mountHeaderActions();
1286
- this.operationStage = "ui.selection";
1287
- this.options.selected(this.getActiveResourceId());
1288
- }
1289
- async showItem(generation = this.generation, loaded) {
1290
- const service = this.options.service;
1291
- if (!service || !this.board || !this.itemId) return;
1292
- this.operationStage = "ui.item.load";
1293
- const { item, archive, task } = await _asyncNullishCoalesce(loaded, async () => ( await service.item(this.boardId, this.itemId)));
1294
- if (!this.alive(generation)) return;
1295
- this.operationStage = "ui.item.render";
1296
- const referencesChanged = _optionalChain([this, 'access', _250 => _250.item, 'optionalAccess', _251 => _251.id]) === item.id && JSON.stringify(this.item.externalRefs) !== JSON.stringify(item.externalRefs);
1297
- if (!this.drawer.open || this.itemArchived !== !!archive || JSON.stringify(this.item) !== JSON.stringify(item)) this.drawer.show(this.ui.detailView(this.board, item, this.actor, !!archive, [], !!service.executionSession, _optionalChain([this, 'access', _252 => _252.options, 'access', _253 => _253.reviewGuidance, 'optionalCall', _254 => _254(this.board, item)]), !!service.itemOperations && !archive && (_nullishCoalesce(_optionalChain([this, 'access', _255 => _255.options, 'access', _256 => _256.itemOperationsAvailable, 'optionalCall', _257 => _257(this.board, item)]), () => ( true))), _nullishCoalesce(task, () => ( _chunkKYNYPKKHcjs.projectKanbanTask.call(void 0, item))), !!service.fixTask), "item:" + item.id);
1298
- this.item = item;
1299
- this.itemArchived = !!archive;
1300
- this.loadItemSections(generation, item, !!archive);
1301
- const resources = this.panel().querySelector("[data-work-resources]");
1302
- if (referencesChanged && resources) {
1303
- resources.dataset.associationRevision = String(Number(_nullishCoalesce(resources.dataset.associationRevision, () => ( 0))) + 1);
1304
- delete resources.dataset.loaded;
1305
- if (resources.open) void this.loadWorkResources(false, true);
1306
- }
1307
- this.options.selected(this.getActiveResourceId());
1308
- }
1309
- loadItemSections(generation, item, archived) {
1310
- const service = this.options.service, boardId = this.boardId;
1311
- if (service.executionSession && !this.panel().querySelector("[data-related-sessions]"))
1312
- this.setSection("[data-related-sessions-section]", `<details data-related-sessions><summary>${escapeHTML(this.ui.label("relatedSessions"))}</summary><button type="button" data-action="refresh-related-sessions">${escapeHTML(this.ui.label("refresh"))}</button><div data-related-sessions-body></div></details>`);
1313
- if (service.workResources) {
1314
- const resources = this.panel().querySelector("[data-work-resources]");
1315
- if (!resources) this.setSection("[data-work-resource-section]", `<details data-work-resources><summary>${escapeHTML(this.ui.label("workResourceTitle"))}</summary><p>${escapeHTML(this.ui.label("workResourceHelp"))}</p><div class="kanban__actions"><button type="button" data-action="refresh-work-resources">${escapeHTML(this.ui.label("refresh"))}</button><button type="button" data-action="inspect-work-resources">${escapeHTML(this.ui.label("inspectWorkResources"))}</button></div><div data-work-resource-body></div></details>`);
1316
- }
1317
- void this.supplement("ui.item.events", generation, () => service.events(boardId, 0, 200), (events) => {
1318
- this.setSection("[data-item-events]", this.ui.itemEventsView(events.filter((event) => event.itemId === item.id).reverse()));
1319
- }, "[data-item-events]");
1320
- void this.supplement("ui.item.records", generation, () => service.records(boardId, { itemId: item.id, limit: 10 }), (records) => {
1321
- this.setSection("[data-related-records]", `<h4>${escapeHTML(this.ui.label("relatedRecords"))} (${records.total})</h4>${this.ui.recordLinks(records, this.board)}<div class="kanban__actions">${this.ui.button("new-log", "newLog")}${this.ui.button("new-decision", "newDecision")}</div>`);
1322
- }, "[data-related-records]");
1323
- void this.loadOperations(generation);
1324
- void this.loadExecutionSections(generation, item, archived, true);
1325
- if (!service.executionSession) this.loadReferenceSection(generation, item);
1326
- void this.supplement("ui.item.children", generation, () => this.showChildren(generation), () => {
1327
- }, "[data-children]");
1328
- }
1329
- async loadExecutionSections(generation, item, archived, references = false) {
1330
- const service = this.options.service, boardId = this.boardId, reads = [];
1331
- if (service.executionSession) reads.push(this.supplement("ui.item.session", generation, () => service.executionSession(boardId, item.id), (execution) => {
1332
- this.setSection("[data-execution-session]", this.ui.executionSessionView(execution, !!this.options.executionReference && !archived && !["completed", "cancelled"].includes(item.status) && _optionalChain([item, 'access', _258 => _258.launch, 'optionalAccess', _259 => _259.admittedAt]) === void 0, this.options.sessionUrl));
1333
- if (references) this.loadReferenceSection(generation, item, new Set(execution.history.map((binding) => binding.sessionId)));
1334
- }, "[data-execution-session]"));
1335
- if (_optionalChain([this, 'access', _260 => _260.board, 'optionalAccess', _261 => _261.automation]) && service.analysisStatus) reads.push(this.supplement("ui.item.analysis", generation, () => service.analysisStatus(boardId, item.id), (analysis) => {
1336
- this.setSection("[data-analysis]", analysisView(this.options.presentation, _nullishCoalesce(analysis, () => ( null))));
1337
- }, "[data-analysis]"));
1338
- await Promise.all(reads);
1339
- }
1340
- async loadOperations(generation) {
1341
- const service = this.options.service, item = this.item;
1342
- if (!_optionalChain([service, 'optionalAccess', _262 => _262.itemOperations]) || !item || !this.panel().querySelector("[data-item-operations]")) return;
1343
- await this.supplement("ui.item.operations", generation, () => service.itemOperations(this.boardId, item.id), (view) => {
1344
- this.operations = view;
1345
- this.setSection("[data-item-operations]", itemOperationsView(this.options.presentation, view, this.options.sessionUrl));
1346
- }, "[data-item-operations]");
1347
- }
1348
- async submitOperation(form, data, binding) {
1349
- const service = this.options.service, action = form.dataset.operation, itemId = this.item.id;
1350
- const request = { operationId: form.dataset.resourceId, itemId, action, expectedRevision: form.dataset.revision };
1351
- const entry = _optionalChain([this, 'access', _263 => _263.operations, 'optionalAccess', _264 => _264.operations, 'access', _265 => _265.find, 'call', _266 => _266((operation) => operation.id === action)]);
1352
- this.operationStage = binding ? "ui.operation.bind" : "ui.operation.start";
1353
- const view = binding ? await service.bindItemOperation(this.boardId, { ...request, target: JSON.parse(value(data, "target")) }) : await service.runItemOperation(this.boardId, {
1354
- ...request,
1355
- confirmed: data.has("confirmed"),
1356
- selections: Object.fromEntries((_nullishCoalesce(_optionalChain([entry, 'optionalAccess', _267 => _267.fields]), () => ( []))).map((field2) => [field2.id, value(data, "choice-" + field2.id)]))
1357
- });
1358
- this.dirty = false;
1359
- this.operations = view;
1360
- _optionalChain([this, 'access', _268 => _268.options, 'access', _269 => _269.committed, 'optionalCall', _270 => _270()]);
1361
- this.setSection("[data-item-operations]", itemOperationsView(this.options.presentation, view, this.options.sessionUrl));
1362
- _optionalChain([this, 'access', _271 => _271.options, 'access', _272 => _272.changed, 'optionalCall', _273 => _273()]);
1363
- const result = view.operations.find((operation) => operation.id === action);
1364
- if (!binding && _optionalChain([result, 'optionalAccess', _274 => _274.state]) === "succeeded") await this.refresh();
1365
- if (!binding && _optionalChain([entry, 'optionalAccess', _275 => _275.openSessionOnRun]) && _optionalChain([result, 'optionalAccess', _276 => _276.sessionId])) await this.options.openSession(result.sessionId);
1366
- }
1367
- loadReferenceSection(generation, item, skip) {
1368
- void this.supplement("ui.item.references", generation, () => this.showReferenceLinks(_nullishCoalesce(item.externalRefs, () => ( [])), generation, skip), () => {
1369
- }, "[data-reference-links]");
1370
- }
1371
- async supplement(stage, generation, read, apply, selector) {
1372
- const started = Date.now(), operationId = _chunk6MN3H3YFcjs.randomUUID.call(void 0, );
1373
- try {
1374
- const value2 = await read();
1375
- if (!this.alive(generation) || this.dirty) return;
1376
- apply(value2);
1377
- try {
1378
- _optionalChain([this, 'access', _277 => _277.options, 'access', _278 => _278.presentation, 'access', _279 => _279.log, 'optionalCall', _280 => _280({ operationId, stage, result: "success", durationMs: Date.now() - started })]);
1379
- } catch (e3) {
1380
- }
1381
- } catch (error) {
1382
- if (!this.alive(generation)) return;
1383
- this.reportRead(stage, error, operationId, started);
1384
- const slot = selector ? this.panel().querySelector(selector) : void 0;
1385
- if (slot) {
1386
- const notice = _nullishCoalesce(slot.querySelector("[data-read-error]"), () => ( document.createElement("p")));
1387
- notice.setAttribute("role", "alert");
1388
- notice.setAttribute("data-read-error", "");
1389
- notice.textContent = errorText(error, this.ui.label, stage);
1390
- if (!notice.parentElement) slot.append(notice);
1391
- } else this.error(errorText(error, this.ui.label, stage));
1392
- }
1393
- }
1394
- setSection(selector, html) {
1395
- const slot = this.panel().querySelector(selector);
1396
- if (slot && slot.innerHTML !== html) slot.innerHTML = html;
1397
- }
1398
- reportRead(stage, error, operationId = _chunk6MN3H3YFcjs.randomUUID.call(void 0, ), started = Date.now()) {
1399
- const raw = _optionalChain([error, 'optionalAccess', _281 => _281.code]);
1400
- try {
1401
- _optionalChain([this, 'access', _282 => _282.options, 'access', _283 => _283.presentation, 'access', _284 => _284.log, 'optionalCall', _285 => _285({ operationId, stage, result: "failure", ...failureDetails(error), code: typeof raw === "string" && /^[A-Z][A-Z0-9_]{0,79}$/.test(raw) ? raw : "KANBAN_OPERATION_FAILED", durationMs: Date.now() - started })]);
1402
- } catch (e4) {
1403
- }
1404
- }
1405
- async showChildren(generation, cursor) {
1406
- const { service } = this.options, item = this.item, board = this.board;
1407
- if (!service || !item || !board || !childType(board, item)) return;
1408
- const query = { parentId: item.id, view: "all", limit: 50, ...cursor ? { cursor } : {} };
1409
- const page = await (_nullishCoalesce(_optionalChain([service, 'access', _286 => _286.queryActivity, 'optionalCall', _287 => _287(board.id, query)]), () => ( service.query(board.id, query))));
1410
- if (!this.alive(generation)) return;
1411
- this.panel().querySelector("[data-children]").innerHTML = childrenView(this.options.presentation, page, cursor, !["completed", "cancelled"].includes(item.status) && !item.closedAt, this.options.sessionUrl);
1412
- }
1413
- async showRecords(generation) {
1414
- if (!this.recordKind || !this.options.service) return;
1415
- let page;
1416
- try {
1417
- page = await this.options.service.records(this.boardId, { kind: this.recordKind, limit: 50, ...this.recordCursor ? { cursor: this.recordCursor } : {} });
1418
- } catch (error) {
1419
- if (_optionalChain([error, 'optionalAccess', _288 => _288.code]) !== "CURSOR_STALE" || !this.recordCursor) throw error;
1420
- this.recordCursor = void 0;
1421
- page = await this.options.service.records(this.boardId, { kind: this.recordKind, limit: 50 });
1422
- }
1423
- if (!this.alive(generation)) return;
1424
- this.recordPage = page;
1425
- this.content().innerHTML = this.ui.recordsView(page, this.recordKind, !!this.recordCursor, this.board);
1426
- }
1427
- async showRecord(id, generation = this.generation) {
1428
- if (!this.options.service) return;
1429
- const record = await this.options.service.record(this.boardId, id);
1430
- const executions = record.kind === "decision" ? await _optionalChain([this, 'access', _289 => _289.options, 'access', _290 => _290.service, 'access', _291 => _291.recordExecutions, 'optionalCall', _292 => _292(this.boardId, id)]) : void 0;
1431
- if (!this.alive(generation)) return;
1432
- this.recordId = id;
1433
- this.drawer.show(this.ui.recordDetail(record, this.board, executions), "record:" + id);
1434
- await this.showReferenceLinks(_nullishCoalesce(record.externalRefs, () => ( [])), generation);
1435
- this.options.selected(this.getActiveResourceId());
1436
- }
1437
- async showReferenceLinks(references, generation, skip = /* @__PURE__ */ new Set()) {
1438
- if (!this.options.referenceLink) return;
1439
- const started = Date.now();
1440
- const results = await Promise.allSettled(references.filter((ref) => !skip.has(ref.id)).map((ref) => this.options.referenceLink(ref)));
1441
- if (!this.alive(generation)) return;
1442
- const resolved = results.flatMap((result) => result.status === "fulfilled" && result.value ? [result.value] : []);
1443
- const links = [...new Map(resolved.map((link) => [link.href, link])).values()];
1444
- const slot = this.panel().querySelector("[data-reference-links]");
1445
- if (slot) slot.innerHTML = links.map((link) => linkPair(this.options.presentation, link.href, link.label, link.sessionId)).join("");
1446
- const errors = results.filter((result) => result.status === "rejected");
1447
- if (errors.length) {
1448
- const raw = _optionalChain([errors, 'access', _293 => _293[0], 'access', _294 => _294.reason, 'optionalAccess', _295 => _295.code]), code = typeof raw === "string" && /^[A-Z][A-Z0-9_]{0,79}$/.test(raw) ? raw : "SESSION_LINK_UNAVAILABLE";
1449
- if (slot) {
1450
- const error = document.createElement("p");
1451
- error.setAttribute("role", "alert");
1452
- error.textContent = this.ui.label("sessionLinkUnavailable") + " (" + code + ")";
1453
- slot.append(error);
1454
- }
1455
- _optionalChain([this, 'access', _296 => _296.options, 'access', _297 => _297.presentation, 'access', _298 => _298.log, 'optionalCall', _299 => _299({ operationId: _chunk6MN3H3YFcjs.randomUUID.call(void 0, ), stage: "ui.reference-links", result: "failure", code, ...failureDetails(errors[0].reason), durationMs: Date.now() - started })]);
1456
- }
1457
- }
1458
- async loadWorkResources(inspect = false, refresh = false) {
1459
- const service = this.options.service, boardId = this.boardId, itemId = _optionalChain([this, 'access', _300 => _300.item, 'optionalAccess', _301 => _301.id]);
1460
- const section = this.panel().querySelector("[data-work-resources]");
1461
- if (!_optionalChain([service, 'optionalAccess', _302 => _302.workResources]) || !itemId || !section || section.dataset.loading || !refresh && section.dataset.loaded) return;
1462
- const operationId = _chunk6MN3H3YFcjs.randomUUID.call(void 0, ), started = Date.now(), revision = section.dataset.associationRevision;
1463
- section.dataset.loading = "true";
1464
- if (!section.dataset.loaded) this.setSection("[data-work-resource-body]", `<p role="status">${escapeHTML(this.ui.label("loading"))}</p>`);
1465
- try {
1466
- const resources = await service.workResources(boardId, itemId, { inspect });
1467
- if (this.abort.signal.aborted || !section.isConnected || _optionalChain([this, 'access', _303 => _303.item, 'optionalAccess', _304 => _304.id]) !== itemId || this.boardId !== boardId || revision !== section.dataset.associationRevision) return;
1468
- this.setSection("[data-work-resource-body]", workResourcesView(this.options.presentation, resources));
1469
- section.dataset.loaded = "true";
1470
- } catch (error) {
1471
- if (!section.isConnected || this.abort.signal.aborted) return;
1472
- this.reportRead("ui.item.work-resources", error, operationId, started);
1473
- const body = section.querySelector("[data-work-resource-body]"), notice = document.createElement("p");
1474
- notice.setAttribute("role", "alert");
1475
- notice.textContent = errorText(error, this.ui.label, "ui.item.work-resources");
1476
- _optionalChain([body, 'optionalAccess', _305 => _305.append, 'call', _306 => _306(notice)]);
1477
- } finally {
1478
- delete section.dataset.loading;
1479
- if (!this.abort.signal.aborted && section.isConnected && section.open && revision !== section.dataset.associationRevision) void this.loadWorkResources(false, true);
1480
- }
1481
- }
1482
- async loadRelatedSessions(refresh = false) {
1483
- const service = this.options.service, boardId = this.boardId, itemId = _optionalChain([this, 'access', _307 => _307.item, 'optionalAccess', _308 => _308.id]);
1484
- const details = this.panel().querySelector("[data-related-sessions]");
1485
- if (!_optionalChain([service, 'optionalAccess', _309 => _309.executionSession]) || !itemId || !details || details.dataset.loading || !refresh && details.dataset.loaded) return;
1486
- details.dataset.loading = "true";
1487
- this.setSection("[data-related-sessions-body]", `<p role="status">${escapeHTML(this.ui.label("loading"))}</p>`);
1488
- try {
1489
- await this.supplement("ui.item.related-sessions", this.generation, () => service.executionSession(boardId, itemId, { includeRelated: true }), (detail) => {
1490
- if (_optionalChain([this, 'access', _310 => _310.item, 'optionalAccess', _311 => _311.id]) !== itemId || this.boardId !== boardId) return;
1491
- this.setSection("[data-related-sessions-body]", relatedSessionsView(this.options.presentation, detail, this.options.sessionUrl));
1492
- details.dataset.loaded = "true";
1493
- }, "[data-related-sessions-body]");
1494
- } finally {
1495
- delete details.dataset.loading;
1496
- }
1497
- }
1498
- async click(event) {
1499
- const target = event.target.closest("button, a");
1500
- if (!target || target.matches(":disabled")) return;
1501
- if (target.dataset.action === "refresh-related-sessions") {
1502
- await this.loadRelatedSessions(true);
1503
- return;
1504
- }
1505
- for (const menu of this.options.editor.querySelectorAll("[data-popover][open]")) if (!menu.contains(target)) menu.open = false;
1506
- if (target.dataset.session) {
1507
- event.preventDefault();
1508
- await this.options.openSession(target.dataset.session);
1509
- return;
1510
- }
1511
- if (this.dirty && !["close-panel", "copy-reference", "copy-link", "copy-resource"].includes(_nullishCoalesce(target.dataset.action, () => ( "")))) throw new Error(this.ui.label("unsaved"));
1512
- const path = this.navigationTarget(target);
1513
- if (path && this.options.navigate) {
1514
- await this.options.navigate(path);
1515
- return;
1516
- }
1517
- if (target.dataset.board) {
1518
- await this.navigate(target.dataset.board);
1519
- return;
1520
- }
1521
- if (target.dataset.context) {
1522
- this.recordKind = target.dataset.context === "board" ? void 0 : target.dataset.context;
1523
- this.recordCursor = void 0;
1524
- this.closePanel();
1525
- await this.refresh();
1526
- return;
1527
- }
1528
- if (target.dataset.record) {
1529
- this.itemId = "";
1530
- this.item = void 0;
1531
- await this.showRecord(target.dataset.record);
1532
- return;
1533
- }
1534
- if (target.dataset.item) {
1535
- this.recordId = "";
1536
- this.itemId = target.dataset.item;
1537
- await this.showItem();
1538
- return;
1539
- }
1540
- const action = target.dataset.action;
1541
- if (action === "continue-execution" && this.item && target.dataset.dispatch) {
1542
- await this.execute({ type: "continue-execution", previousDispatchId: target.dataset.dispatch, dispatchId: _chunk6MN3H3YFcjs.randomUUID.call(void 0, ) });
1543
- return;
1544
- }
1545
- if (action === "refresh-work-resources" || action === "inspect-work-resources") {
1546
- await this.loadWorkResources(action === "inspect-work-resources", true);
1547
- return;
1548
- }
1549
- if (action === "cleanup-work-resource" && this.item && _optionalChain([this, 'access', _312 => _312.options, 'access', _313 => _313.service, 'optionalAccess', _314 => _314.cleanupWorkResource])) {
1550
- if (!globalThis.confirm(this.ui.label("cleanupWorkResourceConfirm"))) return;
1551
- try {
1552
- await this.options.service.cleanupWorkResource(this.boardId, { operationId: _chunk6MN3H3YFcjs.randomUUID.call(void 0, ), itemId: this.item.id, resourceId: target.dataset.resource, version: target.dataset.version });
1553
- } finally {
1554
- await this.loadWorkResources(false, true);
1555
- }
1556
- return;
1557
- }
1558
- if (action === "close-panel" || action === "dismiss-panel") {
1559
- await this.requestClose(action === "close-panel");
1560
- return;
1561
- }
1562
- if (action === "copy-reference" || action === "copy-link" || action === "copy-resource") {
1563
- await this.ui.copyReference(target.closest("[data-action]"));
1564
- return;
1565
- }
1566
- if (action === "automation" && this.board && _optionalChain([this, 'access', _315 => _315.options, 'access', _316 => _316.service, 'optionalAccess', _317 => _317.automationCatalog])) {
1567
- this.automationCatalog = await this.options.service.automationCatalog();
1568
- this.itemId = "";
1569
- this.drawer.show(automationForm(this.options.presentation, this.board, this.automationCatalog), "automation");
1570
- this.dirty = true;
1571
- return;
1572
- }
1573
- if (action === "task-edit" && this.item && this.board) {
1574
- const editId = _chunk6MN3H3YFcjs.randomUUID.call(void 0, ), itemId = this.item.id;
1575
- await this.execute({ type: "begin-edit", itemId, editId, leaseMs: 3e5 });
1576
- this.editing = { itemId, editId };
1577
- const resources = await _optionalChain([this, 'access', _318 => _318.options, 'access', _319 => _319.service, 'optionalAccess', _320 => _320.itemResources, 'optionalCall', _321 => _321(this.boardId)]);
1578
- if (resources) resources.selected = _optionalChain([this, 'access', _322 => _322.item, 'access', _323 => _323.externalRefs, 'optionalAccess', _324 => _324.find, 'call', _325 => _325((ref) => resources.resources.some((r) => r.reference.system === ref.system && r.reference.id === ref.id))]);
1579
- this.drawer.show(editTaskForm(this.options.presentation, this.board, this.item, editId, resources), "edit:" + itemId);
1580
- this.dirty = true;
1581
- return;
1582
- }
1583
- if (action === "task-fix" && this.item && _optionalChain([this, 'access', _326 => _326.options, 'access', _327 => _327.service, 'optionalAccess', _328 => _328.fixTask])) {
1584
- target.dataset.operationId ??= _chunk6MN3H3YFcjs.randomUUID.call(void 0, );
1585
- const fix = await this.options.service.fixTask(this.boardId, { itemId: this.item.id, operationId: target.dataset.operationId });
1586
- await this.options.openSession(fix.sessionId);
1587
- return;
1588
- }
1589
- if (_optionalChain([action, 'optionalAccess', _329 => _329.startsWith, 'call', _330 => _330("task-")]) && this.item) {
1590
- if (action === "task-cancel") await this.execute({ type: "cancel-item", itemId: this.item.id, reason: this.ui.label("taskAction.cancel") });
1591
- else if (action === "task-run") await this.execute({ type: "request-run", itemId: this.item.id });
1592
- else if (action === "task-pause" || action === "task-resume") await this.execute({ type: "request-control", itemId: this.item.id, controlId: _chunk6MN3H3YFcjs.randomUUID.call(void 0, ), action: action === "task-pause" ? "pause" : "resume" });
1593
- return;
1594
- }
1595
- if (action === "reanalyze" && _optionalChain([this, 'access', _331 => _331.item, 'optionalAccess', _332 => _332.planning])) {
1596
- await this.execute({ type: "retry-analysis", itemId: this.item.id, retryId: _chunk6MN3H3YFcjs.randomUUID.call(void 0, ) });
1597
- return;
1598
- }
1599
- if (action === "new-board") {
1600
- this.showBoardForm();
1601
- return;
1602
- }
1603
- if (action === "children-next" || action === "children-first") {
1604
- await this.showChildren(this.generation, action === "children-next" ? target.dataset.cursor : void 0);
1605
- return;
1606
- }
1607
- if (action === "new-child" && this.board && this.item && childType(this.board, this.item)) {
1608
- await this.showItemForm(this.item);
1609
- return;
1610
- }
1611
- if (action === "new-item" && this.board) {
1612
- await this.showItemForm();
1613
- return;
1614
- }
1615
- if (action === "new-log" || action === "new-decision") {
1616
- const resources = action === "new-decision" ? await _optionalChain([this, 'access', _333 => _333.options, 'access', _334 => _334.service, 'optionalAccess', _335 => _335.recordResources, 'optionalCall', _336 => _336()]) : [];
1617
- this.recordId = "";
1618
- this.drawer.show(this.ui.recordForm(action === "new-log" ? "log" : "decision", this.board, this.itemId, resources), action);
1619
- this.dirty = true;
1620
- return;
1621
- }
1622
- if ((action === "run-record" || action === "run-record-separate") && _optionalChain([this, 'access', _337 => _337.options, 'access', _338 => _338.service, 'optionalAccess', _339 => _339.runRecord])) {
1623
- const record = await this.options.service.record(this.boardId, this.recordId);
1624
- target.dataset.operationId ??= _chunk6MN3H3YFcjs.randomUUID.call(void 0, );
1625
- await this.options.service.runRecord(this.boardId, { operationId: target.dataset.operationId, recordId: record.id, expectedRevision: record.revision, placement: action === "run-record-separate" ? "new-board" : "current-board" });
1626
- _optionalChain([this, 'access', _340 => _340.options, 'access', _341 => _341.committed, 'optionalCall', _342 => _342()]);
1627
- await this.refresh();
1628
- return;
1629
- }
1630
- if (action === "scroll-left" || action === "scroll-right") {
1631
- const board = this.content().querySelector(".kanban__columns");
1632
- if (board) board.scrollLeft += board.clientWidth * (action === "scroll-left" ? -0.8 : 0.8);
1633
- return;
1634
- }
1635
- if (action === "records-next" || action === "records-first") {
1636
- this.recordCursor = action === "records-next" ? _optionalChain([this, 'access', _343 => _343.recordPage, 'optionalAccess', _344 => _344.nextCursor]) : void 0;
1637
- await this.refresh();
1638
- return;
1639
- }
1640
- if ((action === "confirm-record" || action === "reject-record") && this.recordId) {
1641
- await this.execute({ type: "review-record", recordId: this.recordId, accept: action === "confirm-record" });
1642
- return;
1643
- }
1644
- if (action === "next" || action === "first") {
1645
- this.query.cursor = action === "next" ? _optionalChain([this, 'access', _345 => _345.page, 'optionalAccess', _346 => _346.nextCursor]) : void 0;
1646
- await this.refresh();
1647
- return;
1648
- }
1649
- if (action === "refresh-item-operations") {
1650
- this.dirty = false;
1651
- await this.loadOperations(this.generation);
1652
- _optionalChain([this, 'access', _347 => _347.options, 'access', _348 => _348.changed, 'optionalCall', _349 => _349()]);
1653
- return;
1654
- }
1655
- if (action === "refresh") {
1656
- await this.refresh();
1657
- return;
1658
- }
1659
- if (action && this.item) await this.execute(actionCommand(action, this.item));
1660
- }
1661
- async openReference(reference) {
1662
- if (this.dirty) throw new Error(this.ui.label("unsaved"));
1663
- const target = await this.options.service.resolve(reference);
1664
- if (this.options.navigate) {
1665
- const leaf = target.recordId ? "/@record/" + encodeURIComponent(target.recordId) : target.itemId ? "/" + encodeURIComponent(target.itemId) : "";
1666
- await this.options.navigate(`/board/${encodeURIComponent(target.boardId)}${leaf}`);
1667
- return;
1668
- }
1669
- this.closePanel();
1670
- this.boardId = target.boardId;
1671
- this.itemId = _nullishCoalesce(target.itemId, () => ( ""));
1672
- this.recordId = _nullishCoalesce(target.recordId, () => ( ""));
1673
- this.recordKind = void 0;
1674
- this.recordCursor = void 0;
1675
- this.query = { view: "all", limit: 50 };
1676
- await this.refresh();
1677
- _optionalChain([this, 'access', _350 => _350.options, 'access', _351 => _351.activated, 'optionalCall', _352 => _352()]);
1678
- }
1679
- async navigate(id) {
1680
- this.boardId = id;
1681
- this.itemId = "";
1682
- this.recordKind = void 0;
1683
- this.recordCursor = void 0;
1684
- this.query = { view: "all", limit: 50 };
1685
- this.closePanel();
1686
- await this.refresh();
1687
- }
1688
- async submit(event) {
1689
- const form = event.target, kind = form.dataset.form, data = new FormData(form);
1690
- form.dataset.resourceId ??= _chunk6MN3H3YFcjs.randomUUID.call(void 0, );
1691
- if (!form.reportValidity()) return;
1692
- if (kind === "item-operation" || kind === "operation-binding") {
1693
- await this.submitOperation(form, data, kind === "operation-binding");
1694
- return;
1695
- }
1696
- if (kind === "reference") {
1697
- await this.openReference(value(data, "reference"));
1698
- return;
1699
- }
1700
- if (kind === "filter") {
1701
- if (this.dirty) throw new Error(this.ui.label("unsaved"));
1702
- this.query = { phase: value(data, "phase") || void 0, view: value(data, "view"), execution: value(data, "execution") || "all", search: value(data, "search"), area: value(data, "area") || void 0, milestone: value(data, "milestone") || void 0, limit: 50 };
1703
- await this.refresh();
1704
- return;
1705
- }
1706
- if (kind === "board" && this.actor && this.options.service) {
1707
- const created = await this.options.service.create(boardInput(data, this.actor, form.dataset.resourceId));
1708
- _optionalChain([this, 'access', _353 => _353.options, 'access', _354 => _354.committed, 'optionalCall', _355 => _355()]);
1709
- this.dirty = false;
1710
- await this.navigate(created.id);
1711
- return;
1712
- }
1713
- if (kind === "automation" && this.automationCatalog) {
1714
- await this.execute({ type: "configure-automation", settings: {
1715
- enabled: data.has("enabled"),
1716
- autoAccept: data.has("autoAccept"),
1717
- ...value(data, "preparation") ? {
1718
- preparation: JSON.parse(value(data, "preparation")),
1719
- preparationOptions: Object.fromEntries([...data.entries()].filter(([key]) => key.startsWith("preparation-option-")).map(([key, value2]) => [key.slice("preparation-option-".length), String(value2)]))
1720
- } : {},
1721
- planner: JSON.parse(value(data, "planner")),
1722
- executor: JSON.parse(value(data, "executor")),
1723
- coordinator: this.automationCatalog.coordinator,
1724
- worker: this.automationCatalog.worker
1725
- } });
1726
- return;
1727
- }
1728
- if (kind === "task-reply" && this.item && _optionalChain([this, 'access', _356 => _356.options, 'access', _357 => _357.service, 'optionalAccess', _358 => _358.replyTask])) {
1729
- await this.options.service.replyTask(this.boardId, { itemId: this.item.id, attemptId: value(data, "attemptId"), replyTo: value(data, "replyTo"), operationId: form.dataset.resourceId, content: value(data, "content") });
1730
- this.dirty = false;
1731
- await this.refresh();
1732
- return;
1733
- }
1734
- if (kind === "save-edit" && this.item) {
1735
- const planning = formCommand("plan", data, this.board, this.item);
1736
- if (planning.type !== "plan-item") return;
1737
- await this.execute({ type: "save-edit", launchMode: value(data, "launchMode"), itemId: this.item.id, editId: value(data, "editId"), title: value(data, "title"), responsibility: value(data, "responsibility"), acceptance: value(data, "acceptance"), planning: planning.planning, externalRefs: [...(_nullishCoalesce(this.item.externalRefs, () => ( []))).filter((ref) => !value(data, "itemResource") || ref.system !== JSON.parse(value(data, "itemResource")).system), ...value(data, "itemResource") ? [JSON.parse(value(data, "itemResource"))] : []] });
1738
- this.editing = void 0;
1739
- return;
1740
- }
1741
- if (kind === "amendment") data.set("amendmentId", _nullishCoalesce(form.dataset.amendment, () => ( "")));
1742
- if (kind === "record-link" && this.recordId) {
1743
- await this.execute({ type: "link-record", recordId: this.recordId, itemId: value(data, "itemId") });
1744
- return;
1745
- }
1746
- if (kind && this.board) {
1747
- if (kind === "execution-session" && this.item && this.options.executionReference) {
1748
- await this.execute({ type: "link-reference", itemId: this.item.id, reference: this.options.executionReference(value(data, "executionSession"), form.dataset.resourceId) });
1749
- return;
1750
- }
1751
- await this.execute(formCommand(kind, data, this.board, this.item, form.dataset.resourceId));
1752
- }
1753
- }
1754
- async showItemForm(parent) {
1755
- const board = this.board, generation = this.generation;
1756
- if (!board) return;
1757
- const resources = await _optionalChain([this, 'access', _359 => _359.options, 'access', _360 => _360.service, 'optionalAccess', _361 => _361.itemResources, 'optionalCall', _362 => _362(board.id, _optionalChain([parent, 'optionalAccess', _363 => _363.id]))]);
1758
- if (this.abort.signal.aborted || generation !== this.generation) return;
1759
- this.itemId = "";
1760
- this.drawer.show(this.ui.newItemForm(board, parent, resources), parent ? "new-child" : "new-item");
1761
- this.dirty = true;
1762
- }
1763
- async execute(command) {
1764
- if (!this.board || !this.options.service) return;
1765
- if (command.type === "review") {
1766
- const workflow = await this.options.service.workflow(this.board.id);
1767
- const dispatch = Object.values(workflow.dispatches).find((dispatch2) => dispatch2.itemId === ("itemId" in command ? command.itemId : "") && dispatch2.outcome === "succeeded");
1768
- if (dispatch) command = { type: "accept-execution", dispatchId: dispatch.id, accept: command.accept };
1769
- }
1770
- const fingerprint = JSON.stringify(command);
1771
- if (this.pending && (this.pending.boardId !== this.board.id || this.pending.fingerprint !== fingerprint)) throw Object.assign(new Error("Pending write"), { code: "WRITE_OUTCOME_UNKNOWN" });
1772
- this.pending ??= { boardId: this.board.id, fingerprint, request: { operationId: _chunk6MN3H3YFcjs.randomUUID.call(void 0, ), expectedRevision: this.board.revision, command } };
1773
- try {
1774
- await this.options.service.execute(this.board.id, this.pending.request);
1775
- } catch (error) {
1776
- if (_optionalChain([error, 'optionalAccess', _364 => _364.name]) === "KanbanError" || _optionalChain([error, 'optionalAccess', _365 => _365.outcome]) === "not-committed") this.pending = void 0;
1777
- throw error;
1778
- }
1779
- this.pending = void 0;
1780
- _optionalChain([this, 'access', _366 => _366.options, 'access', _367 => _367.committed, 'optionalCall', _368 => _368()]);
1781
- if (command.type === "configure-automation") this.closePanel();
1782
- this.dirty = false;
1783
- this.query.cursor = void 0;
1784
- this.recordCursor = void 0;
1785
- if (command.type === "create-item") this.itemId = command.item.id;
1786
- if (command.type === "append-record") {
1787
- this.recordId = command.record.id;
1788
- this.itemId = "";
1789
- this.item = void 0;
1790
- }
1791
- await this.refresh();
1792
- }
1793
- async run(action) {
1794
- if (this.busy || this.abort.signal.aborted) return;
1795
- const operationId = _chunk6MN3H3YFcjs.randomUUID.call(void 0, ), started = Date.now();
1796
- this.operationStage = "ui.operation";
1797
- this.generation++;
1798
- this.busy = true;
1799
- this.status(this.ui.label("loading"));
1800
- this.error("");
1801
- this.options.editor.setAttribute("aria-busy", "true");
1802
- let succeeded = false;
1803
- try {
1804
- await action();
1805
- succeeded = true;
1806
- } catch (error) {
1807
- const raw = _optionalChain([error, 'optionalAccess', _369 => _369.code]);
1808
- try {
1809
- _optionalChain([this, 'access', _370 => _370.options, 'access', _371 => _371.presentation, 'access', _372 => _372.log, 'optionalCall', _373 => _373({ operationId, stage: this.operationStage, result: "failure", ...failureDetails(error), code: typeof raw === "string" && /^[A-Z][A-Z0-9_]{0,79}$/.test(raw) ? raw : "KANBAN_OPERATION_FAILED", durationMs: Date.now() - started })]);
1810
- } catch (e5) {
1811
- }
1812
- if (!this.abort.signal.aborted) this.error(errorText(error, this.ui.label, this.operationStage));
1813
- } finally {
1814
- this.busy = false;
1815
- if (!this.abort.signal.aborted) {
1816
- this.status("");
1817
- this.options.editor.removeAttribute("aria-busy");
1818
- this.options.editor.inert = false;
1819
- this.mountHeaderActions();
1820
- this.drawer.flushFocus();
1821
- _optionalChain([this, 'access', _374 => _374.options, 'access', _375 => _375.changed, 'optionalCall', _376 => _376()]);
1822
- if (succeeded && this.invalid && this.visible && !this.dirty) this.refreshInBackground();
1823
- }
1824
- }
1825
- }
1826
- navigationTarget(target) {
1827
- if (target.dataset.board) return `/board/${encodeURIComponent(target.dataset.board)}`;
1828
- const board = `/board/${encodeURIComponent(this.boardId)}`;
1829
- if (target.dataset.item) return `${board}/${encodeURIComponent(target.dataset.item)}`;
1830
- if (target.dataset.record) return `${board}/@record/${encodeURIComponent(target.dataset.record)}`;
1831
- if (target.dataset.context) return board + (target.dataset.context === "board" ? "" : "/@" + target.dataset.context);
1832
- }
1833
- mountHeaderActions() {
1834
- for (const [slot, release] of this.windowMounts) if (!this.options.editor.contains(slot)) {
1835
- release();
1836
- this.windowMounts.delete(slot);
1837
- }
1838
- if (this.options.mountTitleBar) {
1839
- for (const header of this.options.editor.querySelectorAll("[data-titlebar]")) if (!this.windowMounts.has(header)) this.mountTitleBar(header);
1840
- return;
1841
- }
1842
- for (const slot of this.options.editor.querySelectorAll("[data-window-navigation]")) {
1843
- if (!this.windowMounts.has(slot) && this.options.mountWindowList) this.windowMounts.set(slot, this.options.mountWindowList(slot));
1844
- }
1845
- for (const header of this.options.editor.querySelectorAll("[data-titlebar]")) if (!this.windowMounts.has(header)) this.mountDefaultTitleBar(header);
1846
- }
1847
- mountDefaultTitleBar(header) {
1848
- const back = _nullishCoalesce(header.querySelector("[data-titlebar-back]"), () => ( void 0));
1849
- const title = _nullishCoalesce(header.querySelector("[data-titlebar-title]"), () => ( void 0));
1850
- _optionalChain([back, 'optionalAccess', _377 => _377.classList, 'access', _378 => _378.add, 'call', _379 => _379("ui-nav-button")]);
1851
- _optionalChain([header, 'access', _380 => _380.querySelector, 'call', _381 => _381(".kanban__menu > summary"), 'optionalAccess', _382 => _382.classList, 'access', _383 => _383.add, 'call', _384 => _384("ui-nav-button")]);
1852
- const view = _uicommon.mountTitleBar.call(void 0, header, {
1853
- title,
1854
- back,
1855
- leading: [...header.querySelectorAll("[data-navigation] > button")],
1856
- trailing: [...header.querySelectorAll("[data-window-navigation]")],
1857
- actions: [...header.querySelectorAll(".kanban__desktop-create, :scope > [data-action=new-board]")],
1858
- menu: _nullishCoalesce(header.querySelector(".kanban__menu"), () => ( void 0))
1859
- });
1860
- view.element.dataset.titlebarSlot = "";
1861
- header.dataset.titlebarHost = "true";
1862
- view.update({ title: _nullishCoalesce(_optionalChain([title, 'optionalAccess', _385 => _385.textContent]), () => ( "")) });
1863
- this.windowMounts.set(header, () => {
1864
- view.destroy();
1865
- delete header.dataset.titlebarHost;
1866
- });
1867
- }
1868
- mountTitleBar(header) {
1869
- const slot = document.createElement("div");
1870
- slot.dataset.titlebarSlot = "";
1871
- header.prepend(slot);
1872
- header.dataset.titlebarHost = "true";
1873
- const release = this.options.mountTitleBar(slot, {
1874
- title: _nullishCoalesce(header.querySelector("[data-titlebar-title]"), () => ( void 0)),
1875
- back: _nullishCoalesce(header.querySelector("[data-titlebar-back]"), () => ( void 0)),
1876
- menu: _nullishCoalesce(header.querySelector(".kanban__menu"), () => ( void 0)),
1877
- actions: [...header.querySelectorAll(".kanban__desktop-create, :scope > [data-action=new-board]")]
1878
- });
1879
- this.windowMounts.set(header, () => {
1880
- release();
1881
- slot.remove();
1882
- delete header.dataset.titlebarHost;
1883
- });
1884
- }
1885
- closePanel() {
1886
- this.dirty = false;
1887
- this.itemId = "";
1888
- this.recordId = "";
1889
- this.item = void 0;
1890
- this.drawer.close();
1891
- }
1892
- async requestClose(explicit) {
1893
- if (this.editing && explicit) {
1894
- await this.execute({ type: "end-edit", ...this.editing });
1895
- this.editing = void 0;
1896
- }
1897
- if (this.dirty && !explicit) throw new Error(this.ui.label("unsaved"));
1898
- if (this.options.navigate && this.boardId) {
1899
- if (this.dirty) this.closePanel();
1900
- await this.options.navigate(`/board/${encodeURIComponent(this.boardId)}`);
1901
- return;
1902
- }
1903
- this.closePanel();
1904
- void this.refreshActivity(this.generation);
1905
- this.options.selected(this.getActiveResourceId());
1906
- }
1907
- alive(generation) {
1908
- return generation === this.generation && !this.abort.signal.aborted;
1909
- }
1910
- content() {
1911
- return this.options.editor.querySelector("[data-content]");
1912
- }
1913
- panel() {
1914
- return this.options.editor.querySelector("[data-panel]");
1915
- }
1916
- status(message) {
1917
- this.options.editor.querySelector("[data-status]").textContent = message;
1918
- }
1919
- error(message) {
1920
- const error = this.options.editor.querySelector("[data-error]");
1921
- error.textContent = message;
1922
- error.hidden = _nullishCoalesce(_optionalChain([this, 'access', _386 => _386.drawer, 'optionalAccess', _387 => _387.open]), () => ( false));
1923
- const panelError = this.options.editor.querySelector("[data-panel-error]");
1924
- if (panelError) panelError.textContent = message;
1925
- const executionError = this.options.editor.querySelector("[data-execution-error]");
1926
- if (executionError) {
1927
- executionError.textContent = message;
1928
- executionError.hidden = !message;
1929
- if (message) {
1930
- const panel = this.panel(), bottom = executionError.getBoundingClientRect().bottom, edge = panel.getBoundingClientRect().bottom;
1931
- if (bottom > edge) panel.scrollTop += bottom - edge + 12;
1932
- }
1933
- }
1934
- this.options.editor.querySelector("[data-retry]").hidden = !message || (_nullishCoalesce(_optionalChain([this, 'access', _388 => _388.drawer, 'optionalAccess', _389 => _389.open]), () => ( false)));
1935
- }
1936
- }, _class4);
1937
- function errorText(error, label, stage) {
1938
- const code = _optionalChain([error, 'optionalAccess', _390 => _390.outcome]) === "unknown" ? "WRITE_OUTCOME_UNKNOWN" : _optionalChain([error, 'optionalAccess', _391 => _391.code]);
1939
- if (code === "REVISION_CONFLICT" || code === "CURSOR_STALE") return label("conflict");
1940
- if (error instanceof Error && error.message === label("unsaved")) return error.message;
1941
- const safe = typeof code === "string" && /^[A-Z_]{1,80}$/.test(code) ? code : "KANBAN_OPERATION_FAILED";
1942
- const translated = label("errors." + safe);
1943
- const details = safe === "KANBAN_OPERATION_FAILED" ? failureDetails(error) : {};
1944
- const diagnostic = details.errorName ? `
1945
- ${label("diagnostic")}: ${stage} \xB7 ${details.errorName}${details.source ? " \xB7 " + details.source : ""}` : "";
1946
- return (translated !== "kanban.errors." + safe && translated !== "errors." + safe ? `${translated} (${safe})` : `${label("failed")}: ${safe}`) + diagnostic;
1947
- }
1948
- async function mountKanban(container, options) {
1949
- const sidebar = document.createElement("aside"), editor = document.createElement("main");
1950
- container.classList.add("kanban-layout");
1951
- container.replaceChildren(sidebar, editor);
1952
- const view = new KanbanWorkbench({ ...options, sidebar, editor }, container);
1953
- await view.start();
1954
- return view;
1955
- }
1956
-
1957
-
1958
-
1959
-
1960
- exports.KanbanSuggestionPicker = KanbanSuggestionPicker; exports.KanbanWorkbench = KanbanWorkbench; exports.mountKanban = mountKanban;
1961
- //# sourceMappingURL=index.cjs.map