@gavin-hu/my-pi-agent 0.17.0 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (274) hide show
  1. package/README.md +61 -45
  2. package/README.zh-CN.md +54 -42
  3. package/docs/extensions/README.md +34 -0
  4. package/docs/extensions/board.md +344 -0
  5. package/{extensions/bytefly/README.md → docs/extensions/bytefly.md} +465 -416
  6. package/{extensions/check/README.md → docs/extensions/check.md} +83 -68
  7. package/{extensions/context/README.md → docs/extensions/context.md} +40 -23
  8. package/docs/extensions/file-browse.md +554 -0
  9. package/{extensions/file-open/README.md → docs/extensions/file-open.md} +31 -23
  10. package/{extensions/file-read/README.md → docs/extensions/file-read.md} +47 -55
  11. package/{extensions/goal/README.md → docs/extensions/goal.md} +115 -131
  12. package/{extensions/job/README.md → docs/extensions/job.md} +66 -53
  13. package/{extensions/memory/README.md → docs/extensions/memory.md} +74 -63
  14. package/docs/extensions/plan.md +976 -0
  15. package/{extensions/question/README.md → docs/extensions/question.md} +54 -28
  16. package/{extensions/rewind/README.md → docs/extensions/rewind.md} +115 -122
  17. package/docs/extensions/session-bus.md +285 -0
  18. package/{extensions/subagent/README.md → docs/extensions/subagent.md} +166 -119
  19. package/docs/extensions/team.md +226 -0
  20. package/{extensions/todo/README.md → docs/extensions/todo.md} +85 -88
  21. package/{extensions/watch/README.md → docs/extensions/watch.md} +60 -38
  22. package/{extensions/web-access/README.md → docs/extensions/web-access.md} +93 -50
  23. package/{extensions/wechat/README.md → docs/extensions/wechat.md} +275 -192
  24. package/{extensions/worktree/README.md → docs/extensions/worktree.md} +115 -72
  25. package/extensions/board/commands.ts +87 -0
  26. package/extensions/board/config.ts +36 -0
  27. package/extensions/board/format.ts +72 -0
  28. package/extensions/board/index.ts +40 -0
  29. package/extensions/board/input.ts +54 -0
  30. package/extensions/board/join.ts +42 -0
  31. package/extensions/board/page.ts +312 -0
  32. package/extensions/board/result.ts +99 -0
  33. package/extensions/board/rows.ts +48 -0
  34. package/extensions/board/schema.ts +75 -0
  35. package/extensions/board/seat.ts +29 -0
  36. package/extensions/board/snapshot.ts +41 -0
  37. package/extensions/board/tools.ts +279 -0
  38. package/extensions/board/transport/router.ts +172 -0
  39. package/extensions/board/transport/server.ts +149 -0
  40. package/extensions/board/types.ts +54 -0
  41. package/extensions/board/view/actions.ts +306 -0
  42. package/extensions/board/view/client.ts +41 -0
  43. package/extensions/board/view/keys.ts +121 -0
  44. package/extensions/board/view/live.ts +397 -0
  45. package/extensions/board/view/page.ts +158 -0
  46. package/extensions/board/view/render.ts +496 -0
  47. package/extensions/board/view/styles.ts +211 -0
  48. package/extensions/board/view/team.ts +305 -0
  49. package/extensions/board/writes.ts +99 -0
  50. package/extensions/bytefly/notify/types.ts +1 -1
  51. package/extensions/bytefly/panel/commands.ts +40 -7
  52. package/extensions/bytefly/panel/settings-runtime.ts +0 -6
  53. package/extensions/bytefly/panel/settings.ts +11 -21
  54. package/extensions/bytefly/panel/tui/settings-view.ts +1 -1
  55. package/extensions/bytefly/picker/discovery.ts +0 -2
  56. package/extensions/bytefly/picker/types.ts +0 -2
  57. package/extensions/bytefly/update/check.ts +39 -0
  58. package/extensions/bytefly/update/format.ts +15 -0
  59. package/extensions/bytefly/update/register.ts +73 -34
  60. package/extensions/bytefly/update/state.ts +6 -3
  61. package/extensions/context/tui.ts +1 -1
  62. package/extensions/file-browse/{config.ts → config/config.ts} +13 -4
  63. package/extensions/file-browse/{files.ts → files/files.ts} +1 -14
  64. package/extensions/file-browse/{paths.ts → files/paths.ts} +1 -1
  65. package/extensions/file-browse/{git.ts → git/git.ts} +10 -3
  66. package/extensions/file-browse/index.ts +4 -4
  67. package/extensions/file-browse/{live.ts → live/live.ts} +54 -82
  68. package/extensions/file-browse/live/rev.ts +42 -0
  69. package/extensions/file-browse/{router.ts → transport/router.ts} +44 -17
  70. package/extensions/file-browse/transport/server.ts +110 -0
  71. package/extensions/file-browse/view/chrome.ts +83 -0
  72. package/extensions/file-browse/{client.ts → view/client.ts} +63 -1
  73. package/extensions/file-browse/view/diff-html.ts +94 -0
  74. package/extensions/file-browse/{html.ts → view/html.ts} +32 -158
  75. package/extensions/file-browse/{icons.ts → view/icons.ts} +1 -1
  76. package/extensions/file-browse/{live-client.ts → view/live-client.ts} +47 -1
  77. package/extensions/file-browse/{markdown.ts → view/markdown.ts} +7 -7
  78. package/extensions/file-browse/{raw-html.ts → view/raw-html.ts} +1 -1
  79. package/extensions/file-browse/{styles.ts → view/styles.ts} +31 -5
  80. package/extensions/file-browse/{tree.ts → view/tree.ts} +1 -1
  81. package/extensions/file-browse/{urls.ts → view/urls.ts} +1 -1
  82. package/extensions/file-read/schema.ts +2 -1
  83. package/extensions/goal/plan-route.ts +8 -2
  84. package/extensions/job/index.ts +15 -7
  85. package/extensions/memory/store.ts +12 -5
  86. package/extensions/plan/blocks.ts +23 -2
  87. package/extensions/plan/commands.ts +77 -21
  88. package/extensions/plan/feedback.ts +409 -63
  89. package/extensions/plan/index.ts +7 -5
  90. package/extensions/plan/plan-index.ts +44 -10
  91. package/extensions/plan/plan-text.ts +51 -24
  92. package/extensions/plan/plans.ts +162 -57
  93. package/extensions/plan/policy.ts +20 -12
  94. package/extensions/plan/review-prompt.ts +93 -0
  95. package/extensions/plan/review.ts +51 -95
  96. package/extensions/plan/runtime.ts +22 -10
  97. package/extensions/plan/sections.ts +112 -0
  98. package/extensions/plan/steps.ts +63 -95
  99. package/extensions/plan/tools/enter.ts +8 -3
  100. package/extensions/plan/tools/exit.ts +13 -19
  101. package/extensions/plan/tools/write.ts +8 -3
  102. package/extensions/plan/tools.ts +1 -1
  103. package/extensions/plan/tui.ts +29 -15
  104. package/extensions/plan/types.ts +29 -3
  105. package/extensions/plan/web-chrome.ts +2 -1
  106. package/extensions/plan/web-client.ts +122 -178
  107. package/extensions/plan/web-collab.ts +207 -0
  108. package/extensions/plan/web-comments.ts +269 -0
  109. package/extensions/plan/web-page.ts +96 -17
  110. package/extensions/plan/web-router.ts +109 -47
  111. package/extensions/plan/web-share-session.ts +191 -0
  112. package/extensions/plan/web-share.ts +169 -0
  113. package/extensions/plan/web-streams.ts +124 -0
  114. package/extensions/plan/web-tokens.ts +63 -0
  115. package/extensions/plan/web.ts +245 -151
  116. package/extensions/question/dialogs.ts +91 -1
  117. package/extensions/question/tools.ts +2 -1
  118. package/extensions/rewind/{commands.ts → commands/commands.ts} +5 -5
  119. package/extensions/rewind/{review.ts → commands/review.ts} +13 -13
  120. package/extensions/rewind/{rewind.ts → commands/rewind.ts} +6 -6
  121. package/extensions/rewind/{config.ts → config/config.ts} +1 -1
  122. package/extensions/rewind/index.ts +3 -3
  123. package/extensions/rewind/{policy.ts → state/policy.ts} +4 -4
  124. package/extensions/rewind/{runtime.ts → state/runtime.ts} +11 -11
  125. package/extensions/rewind/{snapshot-run.ts → state/snapshot-run.ts} +1 -1
  126. package/extensions/rewind/{timeline.ts → state/timeline.ts} +2 -2
  127. package/extensions/rewind/{format.ts → text/format.ts} +3 -3
  128. package/extensions/rewind/{git.ts → transport/git.ts} +2 -2
  129. package/extensions/rewind/{restore.ts → transport/restore.ts} +2 -2
  130. package/extensions/rewind/{snapshot.ts → transport/snapshot.ts} +1 -1
  131. package/extensions/rewind/{store.ts → transport/store.ts} +1 -1
  132. package/extensions/rewind/{checklist.ts → view/checklist.ts} +6 -6
  133. package/extensions/rewind/{hunk-picker.ts → view/hunk-picker.ts} +8 -8
  134. package/extensions/rewind/{path-picker.ts → view/path-picker.ts} +7 -7
  135. package/extensions/rewind/{review-tui.ts → view/review-tui.ts} +11 -11
  136. package/extensions/rewind/{rewind-tui.ts → view/rewind-tui.ts} +17 -11
  137. package/extensions/rewind/{status.ts → view/status.ts} +4 -4
  138. package/extensions/session-bus/commands.ts +113 -0
  139. package/extensions/session-bus/format.ts +101 -0
  140. package/extensions/session-bus/index.ts +75 -0
  141. package/extensions/session-bus/join-request.ts +40 -0
  142. package/extensions/session-bus/mail-watch.ts +134 -0
  143. package/extensions/session-bus/notice.ts +86 -0
  144. package/extensions/session-bus/overlap.ts +36 -0
  145. package/extensions/session-bus/protocol.ts +104 -0
  146. package/extensions/session-bus/resolve.ts +67 -0
  147. package/extensions/session-bus/result.ts +208 -0
  148. package/extensions/session-bus/rows.ts +171 -0
  149. package/extensions/session-bus/runtime.ts +466 -0
  150. package/extensions/session-bus/store.ts +365 -0
  151. package/extensions/session-bus/tools.ts +192 -0
  152. package/extensions/session-bus/tui.ts +394 -0
  153. package/extensions/session-bus/types.ts +98 -0
  154. package/extensions/subagent/agents.ts +9 -8
  155. package/extensions/subagent/invocation.ts +4 -2
  156. package/extensions/team/commands.ts +112 -0
  157. package/extensions/team/dispatch.ts +133 -0
  158. package/extensions/team/format.ts +105 -0
  159. package/extensions/team/index.ts +33 -0
  160. package/extensions/team/schema.ts +29 -0
  161. package/extensions/team/tools.ts +246 -0
  162. package/extensions/team/types.ts +48 -0
  163. package/extensions/watch/index.ts +15 -9
  164. package/extensions/web-access/fetch/page.ts +4 -1
  165. package/extensions/web-access/fetch/structured.ts +447 -0
  166. package/extensions/web-access/fetch/tool.ts +2 -1
  167. package/extensions/web-access/search/tool.ts +2 -1
  168. package/extensions/wechat/bridge.ts +76 -92
  169. package/extensions/wechat/{commands.ts → commands/commands.ts} +26 -18
  170. package/extensions/wechat/{doctor.ts → commands/doctor.ts} +5 -5
  171. package/extensions/wechat/{history.ts → commands/history.ts} +1 -1
  172. package/extensions/wechat/{login.ts → commands/login.ts} +3 -3
  173. package/extensions/wechat/{slash.ts → commands/slash.ts} +38 -2
  174. package/extensions/wechat/dialog/channels.ts +136 -0
  175. package/extensions/wechat/{interaction.ts → dialog/interaction.ts} +66 -51
  176. package/extensions/wechat/{prompt.ts → dialog/prompt.ts} +40 -25
  177. package/extensions/wechat/{remote-ui.ts → dialog/remote-ui.ts} +1 -1
  178. package/extensions/wechat/index.ts +10 -10
  179. package/extensions/wechat/{admission.ts → peer/admission.ts} +2 -2
  180. package/extensions/wechat/{peer-session.ts → peer/peer-session.ts} +2 -2
  181. package/extensions/wechat/{peer-worker.ts → peer/peer-worker.ts} +119 -56
  182. package/extensions/wechat/{presence.ts → peer/presence.ts} +11 -11
  183. package/extensions/wechat/{reply-pipeline.ts → peer/reply-pipeline.ts} +13 -5
  184. package/extensions/wechat/{credentials.ts → state/credentials.ts} +2 -2
  185. package/extensions/wechat/{state.ts → state/state.ts} +14 -2
  186. package/extensions/wechat/{format.ts → text/format.ts} +3 -3
  187. package/extensions/wechat/{notices.ts → text/notices.ts} +15 -7
  188. package/extensions/wechat/tools.ts +29 -12
  189. package/extensions/wechat/{client.ts → transport/client.ts} +26 -2
  190. package/extensions/wechat/{deliver.ts → transport/deliver.ts} +104 -40
  191. package/extensions/wechat/{inbound.ts → transport/inbound.ts} +5 -5
  192. package/extensions/wechat/{media.ts → transport/media.ts} +11 -5
  193. package/extensions/wechat/transport/outbound.ts +142 -0
  194. package/extensions/wechat/{poll.ts → transport/poll.ts} +3 -3
  195. package/extensions/wechat/types.ts +10 -1
  196. package/extensions/wechat/{login-screen.ts → view/login-screen.ts} +4 -4
  197. package/extensions/wechat/{status-screen.ts → view/status-screen.ts} +31 -26
  198. package/extensions/worktree/git.ts +5 -0
  199. package/extensions/worktree/guard.ts +3 -5
  200. package/extensions/worktree/registry.ts +11 -5
  201. package/extensions/worktree/status.ts +1 -3
  202. package/lib/README.md +12 -6
  203. package/lib/board/card.ts +290 -0
  204. package/lib/board/links.ts +117 -0
  205. package/lib/board/page-pointer.ts +256 -0
  206. package/lib/board/round-text.ts +72 -0
  207. package/lib/board/round.ts +89 -0
  208. package/lib/board/rules.ts +304 -0
  209. package/lib/board/session.ts +105 -0
  210. package/lib/board/store.ts +310 -0
  211. package/lib/board/view.ts +150 -0
  212. package/lib/errors.ts +12 -4
  213. package/lib/format.ts +13 -0
  214. package/lib/http/guard.ts +112 -0
  215. package/lib/http/index.ts +16 -5
  216. package/lib/http/router.ts +40 -13
  217. package/lib/http/serve.ts +234 -0
  218. package/lib/http/server.ts +29 -13
  219. package/lib/http/sink.ts +110 -0
  220. package/lib/http/token.ts +38 -0
  221. package/lib/interaction.ts +10 -0
  222. package/lib/notice.ts +29 -2
  223. package/lib/plan-entry.ts +7 -2
  224. package/lib/policy.ts +12 -2
  225. package/lib/qr.ts +64 -0
  226. package/lib/repo-key.ts +74 -0
  227. package/lib/report.ts +52 -33
  228. package/lib/requirements.ts +7 -9
  229. package/lib/session-label.ts +64 -0
  230. package/lib/session-roster.ts +130 -0
  231. package/lib/session-tool.ts +184 -0
  232. package/lib/tool-names.ts +25 -2
  233. package/lib/tui/README.md +16 -2
  234. package/lib/tui/dock-screen.ts +85 -17
  235. package/lib/tui/glyphs.ts +2 -0
  236. package/lib/tui/line-pane.ts +15 -4
  237. package/lib/tui/tool.ts +23 -4
  238. package/lib/web/README.md +5 -2
  239. package/lib/web/components.ts +6 -1
  240. package/lib/web/runtime.ts +7 -1
  241. package/lib/web/tokens.ts +25 -2
  242. package/package.json +27 -14
  243. package/skills/init/SKILL.md +80 -45
  244. package/skills/init/references/agents-md-quality.md +26 -2
  245. package/skills/init/references/interview-questions.md +36 -14
  246. package/skills/next/SKILL.md +77 -51
  247. package/skills/project-docs/SKILL.md +133 -0
  248. package/skills/project-docs/references/audit.md +131 -0
  249. package/skills/project-docs/references/changelog.md +105 -0
  250. package/skills/project-docs/references/discovery.md +127 -0
  251. package/skills/project-docs/references/readme.md +80 -0
  252. package/skills/project-docs/references/release.md +84 -0
  253. package/skills/project-docs/references/roadmap.md +93 -0
  254. package/skills/project-docs/scripts/state.ts +316 -0
  255. package/skills/refactor/SKILL.md +100 -125
  256. package/skills/refactor/references/handoff.md +35 -0
  257. package/skills/refactor/references/signals.md +54 -0
  258. package/skills/refactor/scripts/inventory.ts +587 -0
  259. package/extensions/file-browse/README.md +0 -467
  260. package/extensions/file-browse/server.ts +0 -134
  261. package/extensions/plan/README.md +0 -709
  262. package/extensions/plan/config.ts +0 -27
  263. package/extensions/wechat/outbound.ts +0 -100
  264. package/extensions/wechat/qrcode.ts +0 -46
  265. package/skills/changelog/SKILL.md +0 -148
  266. /package/extensions/file-browse/{diff.ts → text/diff.ts} +0 -0
  267. /package/extensions/file-browse/{fuzzy.ts → text/fuzzy.ts} +0 -0
  268. /package/extensions/file-browse/{palette.ts → view/palette.ts} +0 -0
  269. /package/extensions/rewind/{patch.ts → text/patch.ts} +0 -0
  270. /package/extensions/rewind/{paths.ts → text/paths.ts} +0 -0
  271. /package/extensions/rewind/{queue.ts → transport/queue.ts} +0 -0
  272. /package/extensions/rewind/{temp-index.ts → transport/temp-index.ts} +0 -0
  273. /package/extensions/wechat/{lock.ts → state/lock.ts} +0 -0
  274. /package/extensions/wechat/{channel.ts → text/channel.ts} +0 -0
@@ -0,0 +1,306 @@
1
+ /**
2
+ * The client's writes and its two kit dialogs: one request per named action, the
3
+ * confirmation a claim, a reclaim, or a deletion asks for first, and the card dialog
4
+ * that shows a card whole.
5
+ *
6
+ * A browser is not an identity — the page writes as the session that serves it — so
7
+ * the three actions that move a card between people ask before they act, and the
8
+ * answer names the card and the session. Everything else is a single request whose
9
+ * refusal becomes a banner, never an `alert()`.
10
+ *
11
+ * Both dialogs are the kit's (`bf-dialog` + `dialogScript`): the Tab trap, `Esc`,
12
+ * the backdrop click, and the focus return are `lib/web`'s. This module only fills
13
+ * them, answers them, and notices when the user dismissed one.
14
+ */
15
+
16
+ export const BOARD_ACTIONS_JS = String.raw`(function () {
17
+ "use strict";
18
+ var board = window.bfBoard || (window.bfBoard = {});
19
+ var token = new URLSearchParams(window.location.search).get("token") || "";
20
+
21
+ /** The one place a message reaches the reader: a banner, cleared by the next success. */
22
+ function banner(message) {
23
+ var node = document.getElementById("board-banner");
24
+ if (!node) return;
25
+ node.hidden = !message;
26
+ node.textContent = message || "";
27
+ }
28
+
29
+ /** One write. The token rides in the query string, the body is JSON. */
30
+ function request(path, method, body) {
31
+ var url = path + "?token=" + encodeURIComponent(token);
32
+ var options = { method: method };
33
+ if (body !== undefined) {
34
+ options.headers = { "Content-Type": "application/json" };
35
+ options.body = JSON.stringify(body);
36
+ }
37
+ return fetch(url, options).then(function (response) {
38
+ return response.json().catch(function () { return {}; }).then(function (payload) {
39
+ if (!response.ok) {
40
+ throw new Error(payload && payload.error ? payload.error : "The board refused that (" + response.status + ").");
41
+ }
42
+ banner("");
43
+ return payload;
44
+ });
45
+ });
46
+ }
47
+
48
+ function post(path, body) {
49
+ return request(path, "POST", body);
50
+ }
51
+
52
+ function remove(path) {
53
+ return request(path, "DELETE");
54
+ }
55
+
56
+ /** Call settle(false) when the kit closes the dialog without this module asking. */
57
+ function whenClosed(dialog, settle) {
58
+ var observer = new MutationObserver(function () {
59
+ if (dialog.hidden) {
60
+ observer.disconnect();
61
+ settle(false);
62
+ }
63
+ });
64
+ observer.observe(dialog, { attributes: true, attributeFilter: ["hidden"] });
65
+ return function () {
66
+ observer.disconnect();
67
+ };
68
+ }
69
+
70
+ /**
71
+ * Ask one question in the confirm dialog, and answer whether it was accepted.
72
+ *
73
+ * Esc and a backdrop click are the kit's to handle, and both mean "no": the
74
+ * dialog's hidden attribute going back up is what settles the promise, so a
75
+ * dismissed question never leaves the caller waiting.
76
+ */
77
+ function confirm(options) {
78
+ var dialog = document.getElementById("board-confirm");
79
+ var ok = document.getElementById("confirm-ok");
80
+ var cancel = document.getElementById("confirm-cancel");
81
+ if (!dialog || !ok || !cancel || !window.bfConfirm) return Promise.resolve(true);
82
+ document.getElementById("confirm-title").textContent = options.title;
83
+ document.getElementById("confirm-body").textContent = options.body || "";
84
+ ok.textContent = options.ok || "Confirm";
85
+ ok.className = "bf-btn " + (options.danger ? "bf-btn--danger" : "bf-btn--primary");
86
+ return new Promise(function (resolve) {
87
+ var answered = false;
88
+ function finish(value) {
89
+ if (answered) return;
90
+ answered = true;
91
+ ok.removeEventListener("click", accept);
92
+ cancel.removeEventListener("click", dismiss);
93
+ stop();
94
+ if (!dialog.hidden) window.bfConfirm.close();
95
+ resolve(value);
96
+ }
97
+ function accept() { finish(true); }
98
+ function dismiss() { finish(false); }
99
+ var stop = whenClosed(dialog, finish);
100
+ ok.addEventListener("click", accept);
101
+ cancel.addEventListener("click", dismiss);
102
+ window.bfConfirm.open();
103
+ cancel.focus();
104
+ });
105
+ }
106
+
107
+ /** The question a claim, a reclaim, or a deletion asks, or none for the rest. */
108
+ function question(verb, card) {
109
+ var me = board.view ? board.view.me.label : "this session";
110
+ if (verb === "claim") {
111
+ return { title: "Claim #" + card.id + "?", body: "Claim #" + card.id + " as " + me + "?", ok: "Claim" };
112
+ }
113
+ if (verb === "reclaim") {
114
+ return {
115
+ title: "Reclaim #" + card.id + "?",
116
+ body: "Reclaim #" + card.id + " from " + (card.owner ? card.owner.label : "nobody") + "? It returns to To do and keeps its notes.",
117
+ ok: "Reclaim",
118
+ danger: true
119
+ };
120
+ }
121
+ if (verb === "drop") {
122
+ var notes = card.notes.length;
123
+ return {
124
+ title: "Delete #" + card.id + "?",
125
+ body: "Delete #" + card.id + (notes > 0 ? " and its " + notes + " notes?" : "?"),
126
+ ok: "Delete",
127
+ danger: true
128
+ };
129
+ }
130
+ return undefined;
131
+ }
132
+
133
+ /**
134
+ * Run one named action against a card, after its question when it has one.
135
+ *
136
+ * A refusal is a banner; a success needs nothing, because the next frame is the
137
+ * truth and it arrives within a second.
138
+ */
139
+ board.act = function (verb, card, extra) {
140
+ var ask = question(verb, card);
141
+ var proceed = ask ? confirm(ask) : Promise.resolve(true);
142
+ return proceed.then(function (accepted) {
143
+ if (!accepted) return undefined;
144
+ var path = "/api/cards/" + encodeURIComponent(card.id);
145
+ var done = verb === "drop" ? remove(path) : post(path + "/" + verb, extra || {});
146
+ return done.catch(function (error) {
147
+ banner(error.message);
148
+ return undefined;
149
+ });
150
+ });
151
+ };
152
+
153
+ /**
154
+ * Hand an unowned card to a teammate, from the board rather than the bus.
155
+ *
156
+ * The page records the owner; it cannot send, so the card's history says no handoff
157
+ * went out and the peer reads it the next time it looks. The team tool's assign is
158
+ * the action that messages somebody.
159
+ */
160
+ board.assignTo = function (card, label) {
161
+ return post("/api/cards/" + encodeURIComponent(card.id) + "/assign", { to: label }).catch(function (error) {
162
+ banner(error.message);
163
+ return undefined;
164
+ });
165
+ };
166
+
167
+ /** Add a card from the composer. */
168
+ board.add = function (input) {
169
+ return post("/api/cards", input).catch(function (error) {
170
+ banner(error.message);
171
+ return undefined;
172
+ });
173
+ };
174
+
175
+ /** Note the card whose dialog is open, from its own field. */
176
+ board.addNote = function () {
177
+ var input = document.getElementById("card-dialog-note");
178
+ if (!input || board.openId === undefined) return undefined;
179
+ var text = input.value.trim();
180
+ if (!text) return undefined;
181
+ var card = board.findCard(board.view, board.openId);
182
+ if (!card) return undefined;
183
+ return post("/api/cards/" + encodeURIComponent(card.id) + "/note", { text: text })
184
+ .then(function (payload) {
185
+ input.value = "";
186
+ return payload;
187
+ })
188
+ .catch(function (error) {
189
+ banner(error.message);
190
+ return undefined;
191
+ });
192
+ };
193
+
194
+ /** Fill and open the card dialog; the second argument names what to focus. */
195
+ board.openCard = function (id, focus) {
196
+ var view = board.view;
197
+ if (!view) return;
198
+ var card = board.findCard(view, id);
199
+ if (!card || !window.bfCard) return;
200
+ board.openId = id;
201
+ board.fillDialog(card, view);
202
+ window.bfCard.open();
203
+ if (focus === "note") {
204
+ var input = document.getElementById("card-dialog-note");
205
+ if (input) input.focus();
206
+ }
207
+ };
208
+
209
+ board.closeCard = function () {
210
+ if (window.bfCard) window.bfCard.close();
211
+ };
212
+
213
+ board.dialogOpen = function () {
214
+ return window.bfCard ? window.bfCard.isOpen() : false;
215
+ };
216
+
217
+ /**
218
+ * Assign to: a teammate, then the button.
219
+ *
220
+ * The names come from the roster the page already has, live first: a session that has
221
+ * gone quiet is still a label a card can record, but it is not the one to pick.
222
+ */
223
+ function assignControl(card, view) {
224
+ var row = board.el("div", "board-dialog__assign");
225
+ var select = board.el("select", "bf-input board-dialog__assign-select");
226
+ select.setAttribute("aria-label", "Assign to");
227
+ var mates = view.teammates === undefined ? [] : view.teammates.map(function (mate) { return mate.label; });
228
+ // Your own terminal is not somebody to hand a card to, and neither is the seat.
229
+ var self = view.session ? view.session.label : "";
230
+ var others = mates.filter(function (label) { return label.toLowerCase() !== self.toLowerCase(); });
231
+ if (others.length > 0) mates = others;
232
+ if (mates.length === 0) mates = ["team-lead"];
233
+ for (var i = 0; i < mates.length; i++) {
234
+ var option = board.el("option", "", mates[i]);
235
+ option.value = mates[i];
236
+ select.appendChild(option);
237
+ }
238
+ row.appendChild(board.el("span", "board-dialog__label", "Assign to"));
239
+ row.appendChild(select);
240
+ row.appendChild(board.actionButton("Assign", function () { board.assignTo(card, select.value); }));
241
+ return row;
242
+ }
243
+
244
+ /**
245
+ * The card dialog's footer: the claim an unowned card is waiting for, the lane
246
+ * buttons its owner may move to, then its destructive ones. Nothing here is a
247
+ * card-face action, so a reader cannot delete a card from the lane by hitting the
248
+ * wrong button.
249
+ */
250
+ board.fillDialogActions = function (card, view) {
251
+ var footer = document.getElementById("card-dialog-actions");
252
+ if (!footer) return;
253
+ footer.textContent = "";
254
+ // Which verb a card allows is board.may's answer, the same one the card face and a
255
+ // queue row ask; this list only decides which of them the footer shows, and in what
256
+ // order. An unclaimed card is waiting for its first owner — without Claim the dialog
257
+ // offered a reader only Drop and Close on a card they had just opened.
258
+ if (board.may(card, view, "claim")) {
259
+ footer.appendChild(board.actionButton("Claim", function () { board.act("claim", card); }, "primary"));
260
+ }
261
+ if (board.may(card, view, "move")) {
262
+ // Only the lanes this card can reach: the lane it is in is the head's status, and
263
+ // a segmented control whose selected item is a disabled button reads as a live one.
264
+ var targets = ["doing", "blocked", "done"].filter(function (lane) { return lane !== card.status; });
265
+ var move = board.el("div", "board-dialog__move");
266
+ move.appendChild(board.el("span", "board-dialog__label", "Move to"));
267
+ var lanes = board.el("div", "bf-segmented");
268
+ for (var i = 0; i < targets.length; i++) {
269
+ (function (lane) {
270
+ var item = board.el("button", "bf-segmented__item", board.laneLabel(lane));
271
+ item.type = "button";
272
+ item.addEventListener("click", function () { board.act("move", card, { status: lane }); });
273
+ lanes.appendChild(item);
274
+ })(targets[i]);
275
+ }
276
+ move.appendChild(lanes);
277
+ footer.appendChild(move);
278
+ }
279
+ if (board.may(card, view, "assign")) footer.appendChild(assignControl(card, view));
280
+ if (board.may(card, view, "release")) {
281
+ footer.appendChild(board.actionButton("Release", function () { board.act("release", card); }));
282
+ }
283
+ // Fixing what the card says follows the same rule the rules do: the holder decides,
284
+ // and anyone decides while nobody holds it — including a finished card.
285
+ if (board.may(card, view, "edit")) {
286
+ footer.appendChild(board.actionButton("Edit", function () { board.editCard(card); }));
287
+ }
288
+ if (board.may(card, view, "drop")) {
289
+ footer.appendChild(board.actionButton("Drop", function () { board.act("drop", card); }, "danger"));
290
+ }
291
+ footer.appendChild(board.actionButton("Close", function () { board.closeCard(); }));
292
+ };
293
+
294
+ // A dialog the kit closed — Esc, a backdrop click, or its own close button —
295
+ // forgets which card it showed, so the next frame stops filling a hidden panel.
296
+ var cardDialog = document.getElementById("board-card-dialog");
297
+ if (cardDialog) {
298
+ new MutationObserver(function () {
299
+ if (cardDialog.hidden) board.openId = undefined;
300
+ }).observe(cardDialog, { attributes: true, attributeFilter: ["hidden"] });
301
+ }
302
+
303
+ board.banner = banner;
304
+ board.confirm = confirm;
305
+ board.post = post;
306
+ })();`;
@@ -0,0 +1,41 @@
1
+ /**
2
+ * The client bundle: the five modules that paint, round up, write, bind keys, and
3
+ * follow the frame stream, plus the kit's own theme toggle and one `dialogScript`
4
+ * per modal, and the one entry point that starts them.
5
+ *
6
+ * Served as one string, because a page that opens six requests before it can paint
7
+ * is a page that looks broken for a moment. Nothing here is typed — no compiler sees
8
+ * a template string — so the split is by job and the class-contract test reads the
9
+ * result: `render.ts` paints, `team.ts` draws the round, `actions.ts` posts and owns
10
+ * the two dialogs, `keys.ts` binds, and `live.ts` owns the tabs and the stream.
11
+ *
12
+ * Load order is a dependency: `team` takes helpers from `render`, and `live` starts
13
+ * last, after every `board.*` handler it wires exists.
14
+ */
15
+
16
+ import { THEME_TOGGLE_JS, dialogScript, keysDialogScript } from "../../../lib/web/index.ts";
17
+ import { BOARD_ACTIONS_JS } from "./actions.ts";
18
+ import { BOARD_KEYS_JS } from "./keys.ts";
19
+ import { BOARD_LIVE_JS } from "./live.ts";
20
+ import { BOARD_RENDER_JS } from "./render.ts";
21
+ import { BOARD_TEAM_JS } from "./team.ts";
22
+
23
+ /** The whole client, in load order: paint, round, write, keys, wiring, then start. */
24
+ export const BOARD_CLIENT_JS = `${BOARD_RENDER_JS}
25
+ ${BOARD_TEAM_JS}
26
+ ${BOARD_ACTIONS_JS}
27
+ ${BOARD_KEYS_JS}
28
+ ${BOARD_LIVE_JS}
29
+ ${THEME_TOGGLE_JS}
30
+ ${dialogScript({ dialogId: "board-new-dialog", globalName: "bfNew" })}
31
+ ${dialogScript({ dialogId: "board-card-dialog", closeId: "card-dialog-close", globalName: "bfCard" })}
32
+ ${dialogScript({ dialogId: "board-confirm", globalName: "bfConfirm" })}
33
+ ${keysDialogScript("keys-open")}
34
+ (function () {
35
+ "use strict";
36
+ var board = window.bfBoard;
37
+ if (!board) return;
38
+ if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", function () { board.start(); });
39
+ else board.start();
40
+ })();
41
+ `;
@@ -0,0 +1,121 @@
1
+ /**
2
+ * The board page's keyboard: one table, and the two halves that read it.
3
+ *
4
+ * The sheet the user opens with `?` is server-rendered from {@link BOARD_KEYS}, and
5
+ * the bindings that make those keys work are {@link BOARD_KEYS_JS}, so the key a row
6
+ * advertises and the key the client listens for sit in one file — the drift this
7
+ * module exists to prevent, since no type checker sees the bundle.
8
+ *
9
+ * The vocabulary is the TUI's where it means the same thing (`j`/`k`, `?`, `Esc`);
10
+ * `n`, `/` and `t` are this page's own.
11
+ */
12
+
13
+ import type { KeyHint } from "../../../lib/web/index.ts";
14
+
15
+ /** One binding: the key or keys, and what pressing it does. */
16
+ export const BOARD_KEYS: readonly KeyHint[] = [
17
+ { keys: "n", label: "New card — open the dialog and focus the title field" },
18
+ { keys: "/", label: "Filter the cards" },
19
+ { keys: ["j", "k"], label: "Next or previous card" },
20
+ { keys: "Enter", label: "Open the focused card" },
21
+ { keys: "t", label: "Switch Board and Team" },
22
+ { keys: "Esc", label: "Close a dialog, or clear the filter" },
23
+ { keys: "?", label: "This sheet" },
24
+ ];
25
+
26
+ /**
27
+ * The bindings, as the client sees them.
28
+ *
29
+ * Every branch reads `board.` state and calls `board.` handlers that `render.ts`,
30
+ * `team.ts`, and `live.ts` already published, so this module decides only *which
31
+ * key* does what. A field with focus keeps its keys: `n` inside the note input types
32
+ * an `n`.
33
+ */
34
+ export const BOARD_KEYS_JS = String.raw`(function () {
35
+ "use strict";
36
+ var board = window.bfBoard || (window.bfBoard = {});
37
+
38
+ /** Whether the event landed in a field, where every key is text. */
39
+ function typing(target) {
40
+ if (!target || !target.tagName) return false;
41
+ var tag = target.tagName.toLowerCase();
42
+ return tag === "input" || tag === "textarea" || tag === "select" || target.isContentEditable === true;
43
+ }
44
+
45
+ /** The card nodes in reading order, so j/k walk the board as it is drawn. */
46
+ function cards() {
47
+ var seen = [];
48
+ var nodes = document.querySelectorAll(".board-card");
49
+ for (var i = 0; i < nodes.length; i++) if (!nodes[i].hidden) seen.push(nodes[i]);
50
+ return seen;
51
+ }
52
+
53
+ /** Move focus to the next or previous drawn card, and say so for a screen reader. */
54
+ function step(delta) {
55
+ var drawn = cards();
56
+ if (drawn.length === 0) return;
57
+ var at = drawn.indexOf(document.activeElement);
58
+ var next = at < 0 ? (delta > 0 ? 0 : drawn.length - 1) : (at + delta + drawn.length) % drawn.length;
59
+ var button = drawn[next].querySelector(".board-card__title");
60
+ if (button) button.focus();
61
+ }
62
+
63
+ board.bindKeys = function (doc) {
64
+ doc.addEventListener("keydown", function (event) {
65
+ if (event.metaKey || event.ctrlKey || event.altKey) return;
66
+ var target = event.target;
67
+ if (event.key === "?" && !typing(target)) {
68
+ event.preventDefault();
69
+ board.openKeys();
70
+ return;
71
+ }
72
+ if (event.key === "Escape") {
73
+ if (board.keysOpen && board.keysOpen()) {
74
+ board.closeKeys();
75
+ return;
76
+ }
77
+ if (board.dialogOpen && board.dialogOpen()) {
78
+ board.closeCard();
79
+ return;
80
+ }
81
+ // The composer is a kit dialog, so the kit's own Esc closed it in the capture
82
+ // phase already; a filter with text is what Escape is for here. An empty one
83
+ // gives the keyboard back to the page by leaving the field.
84
+ if (board.hasFilter()) {
85
+ board.clearFilter();
86
+ if (typing(target)) target.blur();
87
+ return;
88
+ }
89
+ if (typing(target)) {
90
+ target.blur();
91
+ return;
92
+ }
93
+ return;
94
+ }
95
+ if (typing(target)) {
96
+ if (event.key === "Enter" && target.id === "card-dialog-note") board.addNote();
97
+ if (event.key === "Enter" && target.id === "board-filter") board.applyFilter();
98
+ return;
99
+ }
100
+ if (event.key === "n") {
101
+ // Two modals over one board is a trap whose edge the reader cannot see, and a
102
+ // viewer's page has no composer to open at all.
103
+ if (!board.canWrite() || board.dialogOpen() || board.composerOpen()) return;
104
+ event.preventDefault();
105
+ board.focusComposer();
106
+ } else if (event.key === "/") {
107
+ event.preventDefault();
108
+ board.focusFilter();
109
+ } else if (event.key === "j") {
110
+ event.preventDefault();
111
+ step(1);
112
+ } else if (event.key === "k") {
113
+ event.preventDefault();
114
+ step(-1);
115
+ } else if (event.key === "t") {
116
+ event.preventDefault();
117
+ board.showView(board.viewName() === "team" ? "board" : "team");
118
+ }
119
+ });
120
+ };
121
+ })();`;