@gavin-hu/my-pi-agent 0.18.0 → 0.20.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 (324) hide show
  1. package/README.md +66 -52
  2. package/README.zh-CN.md +56 -46
  3. package/docs/extensions/README.md +34 -0
  4. package/docs/extensions/board.md +507 -0
  5. package/{extensions/bytefly/README.md → docs/extensions/bytefly.md} +774 -574
  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 +561 -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} +62 -52
  13. package/{extensions/memory/README.md → docs/extensions/memory.md} +71 -60
  14. package/{extensions/plan/README.md → docs/extensions/plan.md} +440 -322
  15. package/{extensions/question/README.md → docs/extensions/question.md} +54 -31
  16. package/{extensions/rewind/README.md → docs/extensions/rewind.md} +115 -122
  17. package/docs/extensions/session-bus.md +298 -0
  18. package/{extensions/subagent/README.md → docs/extensions/subagent.md} +165 -118
  19. package/docs/extensions/team.md +254 -0
  20. package/{extensions/todo/README.md → docs/extensions/todo.md} +85 -88
  21. package/{extensions/watch/README.md → docs/extensions/watch.md} +54 -36
  22. package/{extensions/web-access/README.md → docs/extensions/web-access.md} +93 -50
  23. package/{extensions/wechat/README.md → docs/extensions/wechat.md} +260 -163
  24. package/{extensions/worktree/README.md → docs/extensions/worktree.md} +137 -79
  25. package/extensions/board/commands.ts +105 -0
  26. package/extensions/board/config.ts +36 -0
  27. package/extensions/board/format.ts +170 -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 +388 -0
  32. package/extensions/board/result.ts +108 -0
  33. package/extensions/board/rows.ts +48 -0
  34. package/extensions/board/schema.ts +90 -0
  35. package/extensions/board/seat.ts +28 -0
  36. package/extensions/board/snapshot.ts +42 -0
  37. package/extensions/board/tools.ts +444 -0
  38. package/extensions/board/transport/router.ts +91 -0
  39. package/extensions/board/transport/server.ts +138 -0
  40. package/extensions/board/types.ts +121 -0
  41. package/extensions/board/view/client.ts +51 -0
  42. package/extensions/board/view/dialog.ts +43 -0
  43. package/extensions/board/view/keys.ts +125 -0
  44. package/extensions/board/view/live.ts +231 -0
  45. package/extensions/board/view/names.ts +91 -0
  46. package/extensions/board/view/page.ts +118 -0
  47. package/extensions/board/view/render.ts +519 -0
  48. package/extensions/board/view/styles.ts +228 -0
  49. package/extensions/board/view/team.ts +327 -0
  50. package/extensions/bytefly/footer/budget.ts +6 -5
  51. package/extensions/bytefly/footer/collect/entries.ts +12 -4
  52. package/extensions/bytefly/footer/collect/snapshot.ts +8 -17
  53. package/extensions/bytefly/footer/config.ts +19 -8
  54. package/extensions/bytefly/footer/price/price.ts +30 -8
  55. package/extensions/bytefly/footer/register.ts +5 -5
  56. package/extensions/bytefly/footer/render/format.ts +18 -0
  57. package/extensions/bytefly/footer/render/lines.ts +23 -7
  58. package/extensions/bytefly/footer/render/segments.ts +117 -31
  59. package/extensions/bytefly/footer/types.ts +29 -5
  60. package/extensions/bytefly/footer/unpriced.ts +42 -17
  61. package/extensions/bytefly/panel/commands.ts +67 -7
  62. package/extensions/bytefly/panel/filter.ts +68 -0
  63. package/extensions/bytefly/panel/format.ts +16 -15
  64. package/extensions/bytefly/panel/join.ts +56 -13
  65. package/extensions/bytefly/panel/prices.ts +43 -0
  66. package/extensions/bytefly/panel/settings-runtime.ts +0 -6
  67. package/extensions/bytefly/panel/settings.ts +75 -43
  68. package/extensions/bytefly/panel/tui/entries.ts +26 -8
  69. package/extensions/bytefly/panel/tui/frame.ts +130 -93
  70. package/extensions/bytefly/panel/tui/keys.ts +352 -0
  71. package/extensions/bytefly/panel/tui/list-view.ts +25 -46
  72. package/extensions/bytefly/panel/tui/panes.ts +76 -0
  73. package/extensions/bytefly/panel/tui/section-view.ts +126 -0
  74. package/extensions/bytefly/panel/tui/settings-section.ts +54 -125
  75. package/extensions/bytefly/panel/tui/settings-view.ts +30 -131
  76. package/extensions/bytefly/panel/tui/side-pane.ts +224 -0
  77. package/extensions/bytefly/panel/tui.ts +306 -259
  78. package/extensions/bytefly/picker/discovery.ts +0 -2
  79. package/extensions/bytefly/picker/types.ts +0 -2
  80. package/extensions/bytefly/update/check.ts +39 -0
  81. package/extensions/bytefly/update/format.ts +15 -0
  82. package/extensions/bytefly/update/register.ts +73 -34
  83. package/extensions/bytefly/update/state.ts +6 -3
  84. package/extensions/check/commands.ts +2 -1
  85. package/extensions/context/tui.ts +1 -1
  86. package/extensions/file-browse/{config.ts → config/config.ts} +1 -1
  87. package/extensions/file-browse/files/classify.ts +136 -0
  88. package/extensions/file-browse/files/files.ts +12 -0
  89. package/extensions/file-browse/files/index.ts +90 -0
  90. package/extensions/file-browse/files/list.ts +261 -0
  91. package/extensions/file-browse/{paths.ts → files/paths.ts} +1 -1
  92. package/extensions/file-browse/files/tree.ts +101 -0
  93. package/extensions/file-browse/{git.ts → git/git.ts} +10 -3
  94. package/extensions/file-browse/index.ts +4 -4
  95. package/extensions/file-browse/{live.ts → live/live.ts} +20 -69
  96. package/extensions/file-browse/{router.ts → transport/router.ts} +21 -17
  97. package/extensions/file-browse/transport/server.ts +110 -0
  98. package/extensions/file-browse/{chrome.ts → view/chrome.ts} +4 -4
  99. package/extensions/file-browse/{client.ts → view/client.ts} +1 -1
  100. package/extensions/file-browse/{diff-html.ts → view/diff-html.ts} +3 -3
  101. package/extensions/file-browse/view/emit.ts +51 -0
  102. package/extensions/file-browse/{html.ts → view/html.ts} +19 -18
  103. package/extensions/file-browse/{icons.ts → view/icons.ts} +1 -1
  104. package/extensions/file-browse/{markdown.ts → view/markdown.ts} +18 -20
  105. package/extensions/file-browse/{raw-html.ts → view/raw-html.ts} +19 -14
  106. package/extensions/file-browse/{styles.ts → view/styles.ts} +29 -5
  107. package/extensions/file-browse/{tree.ts → view/tree.ts} +1 -1
  108. package/extensions/file-browse/{urls.ts → view/urls.ts} +1 -1
  109. package/extensions/file-read/schema.ts +2 -1
  110. package/extensions/job/runtime.ts +4 -4
  111. package/extensions/memory/store.ts +12 -5
  112. package/extensions/plan/blocks.ts +23 -2
  113. package/extensions/plan/commands.ts +21 -16
  114. package/extensions/plan/feedback.ts +185 -52
  115. package/extensions/plan/format.ts +10 -3
  116. package/extensions/plan/index.ts +23 -5
  117. package/extensions/plan/plan-files.ts +88 -0
  118. package/extensions/plan/plan-index.ts +43 -9
  119. package/extensions/plan/plan-text.ts +33 -23
  120. package/extensions/plan/plans.ts +27 -34
  121. package/extensions/plan/policy.ts +30 -17
  122. package/extensions/plan/review.ts +3 -27
  123. package/extensions/plan/runtime.ts +52 -11
  124. package/extensions/plan/sections.ts +112 -0
  125. package/extensions/plan/steps.ts +3 -22
  126. package/extensions/plan/todos.ts +51 -3
  127. package/extensions/plan/tools/exit.ts +20 -95
  128. package/extensions/plan/tools/todo-seed.ts +4 -2
  129. package/extensions/plan/tools/write.ts +12 -1
  130. package/extensions/plan/tui.ts +5 -34
  131. package/extensions/plan/types.ts +15 -8
  132. package/extensions/plan/web-blocks.ts +89 -0
  133. package/extensions/plan/web-chrome.ts +3 -5
  134. package/extensions/plan/web-client.ts +179 -330
  135. package/extensions/plan/web-collab.ts +3 -3
  136. package/extensions/plan/web-comments.ts +18 -8
  137. package/extensions/plan/web-composer.ts +80 -0
  138. package/extensions/plan/web-draft.ts +40 -0
  139. package/extensions/plan/web-keys.ts +134 -0
  140. package/extensions/plan/web-outline.ts +192 -0
  141. package/extensions/plan/web-page.ts +4 -8
  142. package/extensions/plan/web-plan.ts +150 -0
  143. package/extensions/plan/web-router.ts +3 -58
  144. package/extensions/plan/web-settle.ts +194 -0
  145. package/extensions/plan/web-share-session.ts +24 -30
  146. package/extensions/plan/web-streams.ts +14 -14
  147. package/extensions/plan/web-tokens.ts +2 -2
  148. package/extensions/plan/web.ts +66 -120
  149. package/extensions/rewind/{commands.ts → commands/commands.ts} +5 -5
  150. package/extensions/rewind/{review.ts → commands/review.ts} +15 -14
  151. package/extensions/rewind/{rewind.ts → commands/rewind.ts} +6 -6
  152. package/extensions/rewind/{config.ts → config/config.ts} +1 -1
  153. package/extensions/rewind/index.ts +3 -3
  154. package/extensions/rewind/{policy.ts → state/policy.ts} +4 -4
  155. package/extensions/rewind/{runtime.ts → state/runtime.ts} +11 -11
  156. package/extensions/rewind/{snapshot-run.ts → state/snapshot-run.ts} +1 -1
  157. package/extensions/rewind/{timeline.ts → state/timeline.ts} +2 -2
  158. package/extensions/rewind/{format.ts → text/format.ts} +3 -3
  159. package/extensions/rewind/{git.ts → transport/git.ts} +2 -2
  160. package/extensions/rewind/{restore.ts → transport/restore.ts} +2 -2
  161. package/extensions/rewind/{snapshot.ts → transport/snapshot.ts} +1 -1
  162. package/extensions/rewind/{store.ts → transport/store.ts} +1 -1
  163. package/extensions/rewind/{checklist.ts → view/checklist.ts} +6 -6
  164. package/extensions/rewind/{hunk-picker.ts → view/hunk-picker.ts} +8 -8
  165. package/extensions/rewind/{path-picker.ts → view/path-picker.ts} +7 -7
  166. package/extensions/rewind/{review-tui.ts → view/review-tui.ts} +11 -11
  167. package/extensions/rewind/{rewind-tui.ts → view/rewind-tui.ts} +17 -11
  168. package/extensions/rewind/{status.ts → view/status.ts} +4 -4
  169. package/extensions/session-bus/commands.ts +17 -69
  170. package/extensions/session-bus/format.ts +22 -45
  171. package/extensions/session-bus/index.ts +13 -7
  172. package/extensions/session-bus/join-request.ts +40 -0
  173. package/extensions/session-bus/mail-watch.ts +134 -0
  174. package/extensions/session-bus/notice.ts +32 -4
  175. package/{lib/claims → extensions/session-bus}/overlap.ts +11 -18
  176. package/extensions/session-bus/protocol.ts +19 -49
  177. package/extensions/session-bus/resolve.ts +67 -0
  178. package/extensions/session-bus/result.ts +16 -51
  179. package/extensions/session-bus/rows.ts +11 -5
  180. package/extensions/session-bus/runtime.ts +220 -267
  181. package/extensions/session-bus/store.ts +55 -7
  182. package/extensions/session-bus/tools.ts +31 -66
  183. package/extensions/session-bus/tui.ts +5 -5
  184. package/extensions/session-bus/types.ts +23 -14
  185. package/extensions/subagent/agents.ts +9 -8
  186. package/extensions/subagent/commands.ts +2 -1
  187. package/extensions/subagent/invocation.ts +4 -2
  188. package/extensions/team/commands.ts +145 -0
  189. package/extensions/team/dispatch.ts +133 -0
  190. package/extensions/team/format.ts +119 -0
  191. package/extensions/team/index.ts +34 -0
  192. package/extensions/team/schema.ts +29 -0
  193. package/extensions/team/tools.ts +267 -0
  194. package/extensions/team/types.ts +65 -0
  195. package/extensions/todo/index.ts +4 -1
  196. package/extensions/todo/nudge.ts +8 -2
  197. package/extensions/web-access/fetch/page.ts +4 -1
  198. package/extensions/web-access/fetch/structured.ts +447 -0
  199. package/extensions/web-access/fetch/tool.ts +2 -1
  200. package/extensions/web-access/search/tool.ts +2 -1
  201. package/extensions/wechat/bridge.ts +106 -251
  202. package/extensions/wechat/{commands.ts → commands/commands.ts} +26 -18
  203. package/extensions/wechat/{doctor.ts → commands/doctor.ts} +5 -5
  204. package/extensions/wechat/{history.ts → commands/history.ts} +1 -1
  205. package/extensions/wechat/{login.ts → commands/login.ts} +3 -3
  206. package/extensions/wechat/{slash.ts → commands/slash.ts} +38 -2
  207. package/extensions/wechat/dialog/channels.ts +136 -0
  208. package/extensions/wechat/{interaction.ts → dialog/interaction.ts} +37 -37
  209. package/extensions/wechat/{prompt.ts → dialog/prompt.ts} +2 -2
  210. package/extensions/wechat/{remote-ui.ts → dialog/remote-ui.ts} +1 -1
  211. package/extensions/wechat/index.ts +10 -10
  212. package/extensions/wechat/intake.ts +245 -0
  213. package/extensions/wechat/{admission.ts → peer/admission.ts} +2 -2
  214. package/extensions/wechat/{peer-session.ts → peer/peer-session.ts} +2 -2
  215. package/extensions/wechat/{peer-worker.ts → peer/peer-worker.ts} +100 -22
  216. package/extensions/wechat/{presence.ts → peer/presence.ts} +8 -7
  217. package/extensions/wechat/{reply-pipeline.ts → peer/reply-pipeline.ts} +1 -1
  218. package/extensions/wechat/{credentials.ts → state/credentials.ts} +2 -2
  219. package/extensions/wechat/{state.ts → state/state.ts} +14 -2
  220. package/extensions/wechat/{format.ts → text/format.ts} +3 -3
  221. package/extensions/wechat/{notices.ts → text/notices.ts} +8 -0
  222. package/extensions/wechat/tools.ts +29 -12
  223. package/extensions/wechat/{client.ts → transport/client.ts} +26 -2
  224. package/extensions/wechat/{deliver.ts → transport/deliver.ts} +72 -28
  225. package/extensions/wechat/{inbound.ts → transport/inbound.ts} +5 -5
  226. package/extensions/wechat/{media.ts → transport/media.ts} +11 -5
  227. package/extensions/wechat/transport/outbound.ts +142 -0
  228. package/extensions/wechat/{poll.ts → transport/poll.ts} +3 -3
  229. package/extensions/wechat/types.ts +10 -1
  230. package/extensions/wechat/{login-screen.ts → view/login-screen.ts} +4 -4
  231. package/extensions/wechat/{status-screen.ts → view/status-screen.ts} +31 -26
  232. package/extensions/worktree/commands.ts +24 -17
  233. package/extensions/worktree/exit.ts +12 -6
  234. package/extensions/worktree/git.ts +22 -14
  235. package/extensions/worktree/guard.ts +18 -290
  236. package/extensions/worktree/handoff.ts +10 -2
  237. package/extensions/worktree/index.ts +2 -1
  238. package/extensions/worktree/lifecycle.ts +2 -0
  239. package/extensions/worktree/registry.ts +20 -11
  240. package/extensions/worktree/root-tools.ts +13 -3
  241. package/extensions/worktree/runtime.ts +8 -2
  242. package/extensions/worktree/screen.ts +15 -8
  243. package/extensions/worktree/shell.ts +303 -0
  244. package/extensions/worktree/state.ts +1 -1
  245. package/extensions/worktree/status.ts +42 -9
  246. package/extensions/worktree/tools.ts +19 -13
  247. package/lib/README.md +11 -8
  248. package/lib/board/card.ts +479 -0
  249. package/lib/board/links.ts +141 -0
  250. package/lib/board/page-pointer.ts +310 -0
  251. package/lib/board/round-text.ts +81 -0
  252. package/lib/board/round.ts +94 -0
  253. package/lib/board/rules.ts +437 -0
  254. package/lib/board/session.ts +105 -0
  255. package/lib/board/store.ts +497 -0
  256. package/lib/board/view.ts +234 -0
  257. package/lib/errors.ts +12 -4
  258. package/lib/format.ts +35 -0
  259. package/lib/http/guard.ts +112 -0
  260. package/lib/http/index.ts +16 -5
  261. package/lib/http/serve.ts +237 -0
  262. package/lib/http/server.ts +12 -10
  263. package/lib/http/sink.ts +135 -0
  264. package/lib/http/token.ts +38 -0
  265. package/lib/plan-entry.ts +11 -0
  266. package/lib/policy.ts +25 -2
  267. package/lib/requirements.ts +6 -7
  268. package/lib/session-label.ts +64 -0
  269. package/lib/session-roster.ts +201 -0
  270. package/lib/session-tool.ts +184 -0
  271. package/lib/spawn.ts +37 -3
  272. package/lib/stray-fields.ts +45 -0
  273. package/lib/tool-names.ts +38 -2
  274. package/lib/tui/glyphs.ts +3 -2
  275. package/lib/web/README.md +57 -4
  276. package/lib/web/client-runtime.ts +123 -0
  277. package/lib/web/components.ts +5 -0
  278. package/lib/web/index.ts +4 -2
  279. package/lib/web/markup.ts +88 -1
  280. package/lib/web/names.ts +42 -0
  281. package/lib/web/runtime.ts +7 -1
  282. package/lib/web/tokens.ts +25 -2
  283. package/package.json +26 -14
  284. package/skills/next/SKILL.md +4 -3
  285. package/skills/project-docs/SKILL.md +133 -0
  286. package/skills/project-docs/references/audit.md +131 -0
  287. package/skills/project-docs/references/changelog.md +105 -0
  288. package/skills/project-docs/references/discovery.md +127 -0
  289. package/skills/project-docs/references/readme.md +80 -0
  290. package/skills/project-docs/references/release.md +84 -0
  291. package/skills/project-docs/references/roadmap.md +93 -0
  292. package/skills/project-docs/scripts/state.ts +316 -0
  293. package/skills/refactor/SKILL.md +100 -125
  294. package/skills/refactor/references/handoff.md +35 -0
  295. package/skills/refactor/references/signals.md +54 -0
  296. package/skills/refactor/scripts/inventory.ts +587 -0
  297. package/extensions/bytefly/panel/tui/extensions-view.ts +0 -40
  298. package/extensions/bytefly/panel/tui/skills-view.ts +0 -50
  299. package/extensions/file-browse/README.md +0 -506
  300. package/extensions/file-browse/files.ts +0 -564
  301. package/extensions/file-browse/server.ts +0 -141
  302. package/extensions/job/process.ts +0 -35
  303. package/extensions/plan/split.ts +0 -87
  304. package/extensions/session-bus/README.md +0 -247
  305. package/extensions/session-bus/team.ts +0 -102
  306. package/extensions/wechat/outbound.ts +0 -100
  307. package/lib/plans/sections.ts +0 -172
  308. package/lib/plans/setting.ts +0 -52
  309. package/lib/plans/split.ts +0 -215
  310. package/lib/plans/store.ts +0 -19
  311. package/lib/plans/team.ts +0 -151
  312. package/skills/changelog/SKILL.md +0 -148
  313. package/skills/parallel-dev/SKILL.md +0 -150
  314. /package/extensions/file-browse/{rev.ts → live/rev.ts} +0 -0
  315. /package/extensions/file-browse/{diff.ts → text/diff.ts} +0 -0
  316. /package/extensions/file-browse/{fuzzy.ts → text/fuzzy.ts} +0 -0
  317. /package/extensions/file-browse/{live-client.ts → view/live-client.ts} +0 -0
  318. /package/extensions/file-browse/{palette.ts → view/palette.ts} +0 -0
  319. /package/extensions/rewind/{patch.ts → text/patch.ts} +0 -0
  320. /package/extensions/rewind/{paths.ts → text/paths.ts} +0 -0
  321. /package/extensions/rewind/{queue.ts → transport/queue.ts} +0 -0
  322. /package/extensions/rewind/{temp-index.ts → transport/temp-index.ts} +0 -0
  323. /package/extensions/wechat/{lock.ts → state/lock.ts} +0 -0
  324. /package/extensions/wechat/{channel.ts → text/channel.ts} +0 -0
@@ -0,0 +1,231 @@
1
+ /**
2
+ * The client's wiring: the tabs, the lane picker, the filter, and the live frame stream.
3
+ *
4
+ * A frame is a whole snapshot, so every message repaints from it rather than patching
5
+ * it — the renderer decides what actually changed. The only client state is which tab
6
+ * is showing, the filter text, which lanes are collapsed, and which card's dialog is
7
+ * open; none of it is authoritative, and none of it is sent anywhere.
8
+ */
9
+
10
+ export const BOARD_LIVE_JS = String.raw`(function () {
11
+ "use strict";
12
+ var board = window.bfBoard || (window.bfBoard = {});
13
+ var token = new URLSearchParams(window.location.search).get("token") || "";
14
+ var LANE_LABEL = { todo: "To do", doing: "Doing", blocked: "Blocked", done: "Done" };
15
+
16
+ /** The lane's word, for the dialog's move buttons. */
17
+ board.laneLabel = function (lane) {
18
+ return LANE_LABEL[lane] || lane;
19
+ };
20
+
21
+ /** The badge's text and its kit status role: connecting, live, reconnecting. */
22
+ function badge(state, at) {
23
+ var node = document.getElementById("board-status");
24
+ if (!node) return;
25
+ node.setAttribute("data-state", state);
26
+ node.className = "bf-status " + N.status +
27
+ (state === "live" ? " bf-status--saved" : state === "reconnecting" ? " bf-status--error" : "");
28
+ // "now" would read as "live · now ago", so a fresh frame says only "live".
29
+ if (state === "live") {
30
+ var age = at === undefined ? "now" : board.ago(at);
31
+ node.textContent = age === "now" ? "live" : "live · " + age;
32
+ }
33
+ else if (state === "reconnecting") node.textContent = "reconnecting…";
34
+ else node.textContent = "connecting…";
35
+ }
36
+
37
+ /** Show one tab: the panel, aria-current, and the address so a link is shareable. */
38
+ function showView(view) {
39
+ var team = view === "team";
40
+ document.documentElement.setAttribute("data-view", team ? "team" : "board");
41
+ document.getElementById("panel-board").hidden = team;
42
+ document.getElementById("panel-team").hidden = !team;
43
+ var tabs = document.querySelectorAll("[data-view][role='tab']");
44
+ for (var i = 0; i < tabs.length; i++) {
45
+ var current = tabs[i].getAttribute("data-view") === (team ? "team" : "board");
46
+ if (current) tabs[i].setAttribute("aria-current", "page");
47
+ else tabs[i].removeAttribute("aria-current");
48
+ }
49
+ var url = new URL(window.location.href);
50
+ url.searchParams.set("view", team ? "team" : "board");
51
+ window.history.replaceState(null, "", url.toString());
52
+ }
53
+
54
+ board.viewName = function () {
55
+ return document.documentElement.getAttribute("data-view") === "team" ? "team" : "board";
56
+ };
57
+
58
+ /** The narrow layout shows one lane; the picker is the only way to change it. */
59
+ function applyLane() {
60
+ var narrow = window.matchMedia && window.matchMedia("(max-width: 860px)").matches;
61
+ var lanes = document.querySelectorAll(".board-lane");
62
+ for (var i = 0; i < lanes.length; i++) {
63
+ var selected = lanes[i].getAttribute("data-status") === board.lane;
64
+ lanes[i].hidden = narrow && !selected;
65
+ }
66
+ var items = document.querySelectorAll("#board-picker [data-lane]");
67
+ for (var j = 0; j < items.length; j++) {
68
+ if (items[j].getAttribute("data-lane") === board.lane) items[j].setAttribute("aria-current", "page");
69
+ else items[j].removeAttribute("aria-current");
70
+ }
71
+ }
72
+
73
+ board.applyLane = applyLane;
74
+
75
+ board.showLane = function (lane) {
76
+ board.lane = lane;
77
+ applyLane();
78
+ };
79
+
80
+ /** Fold a lane's cards away and leave its head, count and all. */
81
+ board.toggleLane = function (status) {
82
+ board.collapsed = board.collapsed || {};
83
+ board.collapsed[status] = board.collapsed[status] !== true;
84
+ if (board.view) board.render(board.view);
85
+ };
86
+
87
+ board.showView = showView;
88
+
89
+ board.focusFilter = function () {
90
+ var input = document.getElementById("board-filter");
91
+ if (input) {
92
+ input.focus();
93
+ input.select();
94
+ }
95
+ };
96
+
97
+ /** Apply the filter box's text to the board, without waiting for a frame. */
98
+ board.applyFilter = function () {
99
+ var input = document.getElementById("board-filter");
100
+ board.query = input ? input.value.trim() : "";
101
+ if (board.view) board.render(board.view);
102
+ };
103
+
104
+ board.hasFilter = function () {
105
+ return board.query !== undefined && board.query !== "";
106
+ };
107
+
108
+ board.clearFilter = function () {
109
+ var input = document.getElementById("board-filter");
110
+ if (input && input.value !== "") {
111
+ input.value = "";
112
+ board.applyFilter();
113
+ }
114
+ };
115
+
116
+ board.openKeys = function () {
117
+ if (window.bfKeys) window.bfKeys.open();
118
+ };
119
+ board.closeKeys = function () {
120
+ if (window.bfKeys) window.bfKeys.close();
121
+ };
122
+ board.keysOpen = function () {
123
+ return window.bfKeys ? window.bfKeys.isOpen() : false;
124
+ };
125
+
126
+ /**
127
+ * The grey cards a page shows while it waits for its first frame.
128
+ *
129
+ * They are marked as placeholders rather than given a lane status: the renderer
130
+ * looks lanes up by data-status, so a skeleton that carried one would be adopted
131
+ * as the real lane and the cards would never be drawn into it.
132
+ */
133
+ function skeletons() {
134
+ var lanes = document.getElementById("board-lanes");
135
+ if (!lanes || lanes.childElementCount > 0) return;
136
+ lanes.setAttribute("data-skeleton", "1");
137
+ for (var i = 0; i < 4; i++) {
138
+ var lane = board.el("section", N.lane);
139
+ for (var j = 0; j < 2; j++) lane.appendChild(board.el("div", N.skeleton));
140
+ lanes.appendChild(lane);
141
+ }
142
+ }
143
+
144
+ /**
145
+ * The lane a narrow reader lands on: the first with a card in it, else the first
146
+ * lane. Opening on an empty To do while the only card sits in Blocked is a blank
147
+ * page with a picker nobody has a reason to press.
148
+ */
149
+ function laneWithWork(view) {
150
+ for (var i = 0; i < view.lanes.length; i++) if (view.lanes[i].cards.length > 0) return view.lanes[i].status;
151
+ return view.lanes[0] ? view.lanes[0].status : "todo";
152
+ }
153
+
154
+ /** One frame: paint and record when it arrived. */
155
+ function paint(view) {
156
+ board.lastFrame = Date.now();
157
+ board.lane = board.lane || laneWithWork(view);
158
+ badge("live", board.lastFrame);
159
+ board.render(view);
160
+ }
161
+
162
+ /** The live channel: a reconnect is the browser's own retry, not a second request. */
163
+ function stream() {
164
+ var source = new EventSource("/events?token=" + encodeURIComponent(token));
165
+ source.onopen = function () {
166
+ badge("live", board.lastFrame);
167
+ };
168
+ source.onmessage = function (event) {
169
+ var view;
170
+ try {
171
+ view = JSON.parse(event.data);
172
+ } catch (error) {
173
+ return;
174
+ }
175
+ paint(view);
176
+ };
177
+ source.onerror = function () {
178
+ badge("reconnecting");
179
+ };
180
+ }
181
+
182
+ /** The filter and the two tab sets. */
183
+ function wireChrome() {
184
+ var filter = document.getElementById("board-filter");
185
+ if (filter) {
186
+ filter.addEventListener("input", function () {
187
+ board.applyFilter();
188
+ });
189
+ }
190
+ var tabs = document.querySelectorAll("[data-view][role='tab']");
191
+ for (var i = 0; i < tabs.length; i++) {
192
+ tabs[i].addEventListener("click", function (event) {
193
+ showView(event.currentTarget.getAttribute("data-view"));
194
+ });
195
+ }
196
+ var picker = document.getElementById("board-picker");
197
+ if (picker) {
198
+ picker.addEventListener("click", function (event) {
199
+ var lane = event.target && event.target.getAttribute ? event.target.getAttribute("data-lane") : null;
200
+ if (lane) board.showLane(lane);
201
+ });
202
+ }
203
+ if (window.matchMedia) {
204
+ var query = window.matchMedia("(max-width: 860px)");
205
+ var onChange = function () {
206
+ applyLane();
207
+ };
208
+ if (query.addEventListener) query.addEventListener("change", onChange);
209
+ else if (query.addListener) query.addListener(onChange);
210
+ }
211
+ // The badge says how fresh the last frame is, which is the one thing a quiet
212
+ // board cannot show by changing; the ages are the other, so the same tick keeps
213
+ // them honest without waiting for somebody to write.
214
+ window.setInterval(function () {
215
+ if (board.lastFrame !== undefined) badge("live", board.lastFrame);
216
+ board.refreshAges();
217
+ }, 1000);
218
+ }
219
+
220
+ board.start = function () {
221
+ board.lane = undefined;
222
+ board.openId = undefined;
223
+ board.query = "";
224
+ board.collapsed = {};
225
+ showView(new URLSearchParams(window.location.search).get("view") === "team" ? "team" : "board");
226
+ wireChrome();
227
+ skeletons();
228
+ board.bindKeys(document);
229
+ stream();
230
+ };
231
+ })();`;
@@ -0,0 +1,91 @@
1
+ /**
2
+ * The board page's class vocabulary, as one table.
3
+ *
4
+ * The document (`page.ts`) and the client bundle cannot import from each other, so a
5
+ * class name used to be a string in each — and two of them were assembled at runtime
6
+ * (`"board-card__reason" + kind`), which is why nothing could compare what the page
7
+ * wears with what `styles.ts` draws. Reading both halves from this table makes those two
8
+ * sets enumerable, and `pages.test.ts` holds them equal.
9
+ *
10
+ * A name here is a **class**, not an id: ids are how the client finds an element
11
+ * (`getElementById`), they are not styled, and `pages.test.ts` pins their list.
12
+ */
13
+
14
+ /** Every `board-*` class the page wears. Keys are what the code reads; values are what CSS matches. */
15
+ export const BOARD_CLASS = {
16
+ // The shell, the toolbar, and the two panel wrappers.
17
+ toolbar: "board-toolbar",
18
+ controls: "board-controls",
19
+ main: "board-main",
20
+ panel: "board-panel",
21
+ filter: "board-filter",
22
+ picker: "board-picker",
23
+ progress: "board-progress",
24
+ status: "board-status",
25
+ noCards: "board-no-cards",
26
+ skeleton: "board-skeleton",
27
+
28
+ // One lane: a tray of cards with a head that sticks and folds.
29
+ lanes: "board-lanes",
30
+ lane: "board-lane",
31
+ laneHead: "board-lane__head",
32
+ laneDot: "board-lane__dot",
33
+ laneToggle: "board-lane__toggle",
34
+ laneCount: "board-lane__count",
35
+ laneList: "board-lane__list",
36
+ laneEmpty: "board-lane__empty",
37
+
38
+ // One card, and the two tones its reason line can take.
39
+ card: "board-card",
40
+ cardHead: "board-card__head",
41
+ cardId: "board-card__id",
42
+ cardTitle: "board-card__title",
43
+ cardMeta: "board-card__meta",
44
+ cardPaths: "board-card__paths",
45
+ cardReason: "board-card__reason",
46
+ cardReasonBlocked: "board-card__reason--blocked",
47
+ cardReasonUnplanned: "board-card__reason--unplanned",
48
+
49
+ // The card dialog: the record of one card, as a ladder of facts and its notes.
50
+ dialogPanel: "board-dialog__panel",
51
+ dialogHead: "board-dialog__head",
52
+ dialogId: "board-dialog__id",
53
+ dialogState: "board-dialog__state",
54
+ dialogBody: "board-dialog__body",
55
+ dialogSection: "board-dialog__section",
56
+ dialogHeading: "board-dialog__heading",
57
+ dialogCount: "board-dialog__count",
58
+ dialogFacts: "board-dialog__facts",
59
+ dialogPaths: "board-dialog__paths",
60
+ dialogPath: "board-dialog__path",
61
+ dialogValue: "board-dialog__value",
62
+ dialogPlan: "board-dialog__plan",
63
+ dialogNotes: "board-dialog__notes",
64
+ dialogNote: "board-dialog__note",
65
+ dialogNoteFoot: "board-dialog__note-foot",
66
+ dialogNoteBy: "board-dialog__note-by",
67
+ dialogNoteAt: "board-dialog__note-at",
68
+ dialogNoteText: "board-dialog__note-text",
69
+
70
+ // The Team tab: the queue of decisions, the roster, and the Done strip.
71
+ team: "board-team",
72
+ teamGrid: "board-team__grid",
73
+ teamGroup: "board-team__group",
74
+ teamCaption: "board-team__caption",
75
+ teamQueue: "board-team__queue",
76
+ teamDecision: "board-team__decision",
77
+ teamOpen: "board-team__open",
78
+ teamRef: "board-team__ref",
79
+ teamTitle: "board-team__title",
80
+ teamFoot: "board-team__foot",
81
+ teamWhy: "board-team__why",
82
+ teamWhyBlocked: "board-team__why--blocked",
83
+ teamRoster: "board-team__roster",
84
+ teamMate: "board-team__mate",
85
+ teamDot: "board-team__dot",
86
+ teamName: "board-team__name",
87
+ teamMets: "board-team__mets",
88
+ teamEmpty: "board-team__empty",
89
+ teamDone: "board-team__done",
90
+ teamId: "board-team__id",
91
+ } as const satisfies Readonly<Record<string, string>>;
@@ -0,0 +1,118 @@
1
+ /**
2
+ * The board page's document: an inert shell the client fills from the snapshot.
3
+ *
4
+ * The page carries no card text in its markup — not even escaped — so there is no
5
+ * path from a card file to the document's HTML. Every field the client shows is set
6
+ * as `textContent` from the JSON frame, which is the one property a page with a
7
+ * model-writable store should have.
8
+ *
9
+ * The shell is complete before the script runs: the toolbar, both panels, the three
10
+ * kit dialogs, the keyboard sheet, and the live region are in the markup, so a slow
11
+ * frame shows a skeleton rather than an empty page. Every id here is a contract with
12
+ * the client (`render.ts`, `team.ts`, `live.ts`, `actions.ts`), and the class
13
+ * contract test holds every class to the kit or to this page's stylesheet.
14
+ */
15
+
16
+ import { brandLockup, button, docHead, keysSheet, tabTitle, themeToggle } from "../../../lib/web/index.ts";
17
+ import { BOARD_KEYS } from "./keys.ts";
18
+ import { BOARD_CLASS as N } from "./names.ts";
19
+ import { BOARD_CSS } from "./styles.ts";
20
+
21
+ /** The two views the page offers. */
22
+ export type PageView = "board" | "team";
23
+
24
+ /**
25
+ * Render the document for one initial view.
26
+ *
27
+ * Both panels are rendered and one is `hidden`, so switching tabs costs no request and a
28
+ * shared link opens the tab it names. The document has no form and no write affordance:
29
+ * a page that cannot change a card cannot offer a button the server would refuse.
30
+ */
31
+ export function renderBoardPage(input: { view: PageView }): string {
32
+ const team = input.view === "team";
33
+ return `<!doctype html>
34
+ <html lang="en" data-view="${team ? "team" : "board"}">
35
+ <head>
36
+ ${docHead({ title: tabTitle("board"), css: BOARD_CSS })}
37
+ </head>
38
+ <body class="bf-app">
39
+ <h1 class="sr-only">board — the shared feature list for this repository</h1>
40
+ <a class="skip" href="#main">Skip to the cards</a>
41
+ <header class="bf-app__header"><div class="bf-toolbar ${N.toolbar}">
42
+ ${brandLockup("board")}
43
+ <span class="bf-chip bf-chip--accent" id="board-me" title="This page is served as this session, and it cannot change a card">board</span>
44
+ <p class="bf-status ${N.status}" id="board-status" data-state="connecting" aria-live="polite">connecting…</p>
45
+ <span class="bf-spacer"></span>
46
+ <div class="${N.controls}">
47
+ <div class="bf-segmented" role="tablist" aria-label="View">
48
+ <button type="button" class="bf-segmented__item" role="tab" id="tab-board" data-view="board" aria-controls="panel-board"${team ? "" : ' aria-current="page"'}>Board</button>
49
+ <button type="button" class="bf-segmented__item" role="tab" id="tab-team" data-view="team" aria-controls="panel-team"${team ? ' aria-current="page"' : ""}>Team</button>
50
+ </div>
51
+ <p class="bf-progress ${N.progress}" id="board-progress" hidden>
52
+ <span class="bf-progress__track"><span class="bf-progress__fill" id="board-progress-fill"></span></span>
53
+ <span id="board-progress-text">0/0</span>
54
+ </p>
55
+ <label class="bf-field ${N.filter}"><span class="sr-only">Filter cards</span>
56
+ <input class="bf-input" id="board-filter" type="search" placeholder="filter cards" autocomplete="off">
57
+ </label>
58
+ ${button({ label: "?", variant: "icon", id: "keys-open", title: "Keyboard shortcuts", attrs: { "aria-haspopup": "dialog" } })}
59
+ ${themeToggle()}
60
+ </div>
61
+ </div></header>
62
+ <main class="bf-app__main ${N.main}" id="main" tabindex="-1">
63
+ <section class="${N.panel}" id="panel-board" role="tabpanel" aria-labelledby="tab-board"${team ? " hidden" : ""}>
64
+ <div class="bf-segmented ${N.picker}" id="board-picker" role="tablist" aria-label="Lane"></div>
65
+ <div class="${N.lanes}" id="board-lanes"></div>
66
+ <div class="bf-empty ${N.noCards}" id="board-no-cards" hidden>
67
+ <p class="bf-empty__title">No cards yet</p>
68
+ <p class="bf-empty__sub">One card per feature: what it delivers, who holds it, and where to start.</p>
69
+ <p class="bf-empty__sub"><code>board add</code> makes one — this page reads, and the tools write.</p>
70
+ </div>
71
+ </section>
72
+ <section class="${N.panel}" id="panel-team" role="tabpanel" aria-labelledby="tab-team"${team ? "" : " hidden"}>
73
+ <div class="${N.team}" id="board-team"></div>
74
+ </section>
75
+ </main>
76
+ <footer class="bf-app__footer"><span id="board-where"></span></footer>
77
+
78
+ <div class="bf-dialog" id="board-card-dialog" hidden>
79
+ <div class="bf-dialog__panel ${N.dialogPanel}" role="dialog" aria-modal="true" aria-labelledby="card-dialog-title" tabindex="-1">
80
+ <div class="bf-dialog__head ${N.dialogHead}">
81
+ <span class="${N.dialogId}" id="card-dialog-id"></span>
82
+ <h2 id="card-dialog-title"></h2>
83
+ <span class="bf-spacer"></span>
84
+ <span class="${N.dialogState}" id="card-dialog-state"></span>
85
+ <button type="button" class="bf-btn bf-btn--icon" id="card-dialog-close" aria-label="Close">×</button>
86
+ </div>
87
+ <div class="bf-dialog__body ${N.dialogBody}">
88
+ <p class="sr-only" id="card-dialog-where" aria-live="polite"></p>
89
+ <section class="${N.dialogSection}" aria-labelledby="card-dialog-card-head">
90
+ <h3 class="${N.dialogHeading}" id="card-dialog-card-head">the card</h3>
91
+ <dl class="${N.dialogFacts}">
92
+ <dt id="card-dialog-owner-label">owner</dt><dd class="${N.dialogValue}" id="card-dialog-owner"></dd>
93
+ <dt id="card-dialog-priority-label" hidden>priority</dt><dd class="${N.dialogValue}" id="card-dialog-priority" hidden></dd>
94
+ <dt id="card-dialog-updated-label">updated</dt><dd class="${N.dialogValue}" id="card-dialog-updated"></dd>
95
+ <dt id="card-dialog-lane-label" hidden>in lane</dt><dd class="${N.dialogValue}" id="card-dialog-lane" hidden></dd>
96
+ <dt id="card-dialog-by-label">last edited</dt><dd class="${N.dialogValue}" id="card-dialog-by"></dd>
97
+ <dt id="card-dialog-landed-label" hidden>landed</dt><dd class="${N.dialogValue}" id="card-dialog-landed" hidden></dd>
98
+ <dt id="card-dialog-depends-label" hidden>waits on</dt><dd class="${N.dialogValue}" id="card-dialog-depends" hidden></dd>
99
+ <dt id="card-dialog-dependents-label" hidden>waited on by</dt><dd class="${N.dialogValue}" id="card-dialog-dependents" hidden></dd>
100
+ <dt id="card-dialog-plan-label" hidden>plan</dt><dd class="${N.dialogValue} ${N.dialogPlan}" id="card-dialog-plan" hidden></dd>
101
+ <dt id="card-dialog-paths-label" hidden>paths</dt><dd id="card-dialog-paths" hidden><ul class="${N.dialogPaths}" id="card-dialog-path-list"></ul></dd>
102
+ </dl>
103
+ </section>
104
+ <section class="${N.dialogSection}" aria-labelledby="card-dialog-notes-head">
105
+ <h3 class="${N.dialogHeading}" id="card-dialog-notes-head">notes<span class="${N.dialogCount}" id="card-dialog-note-count"></span></h3>
106
+ <ol class="${N.dialogNotes}" id="card-dialog-notes"></ol>
107
+ <p class="bf-dialog__hint" id="card-dialog-earlier" hidden></p>
108
+ </section>
109
+ </div>
110
+ </div>
111
+ </div>
112
+
113
+ ${keysSheet(BOARD_KEYS)}
114
+ <script src="/app.js" defer></script>
115
+ </body>
116
+ </html>
117
+ `;
118
+ }