@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,519 @@
1
+ /**
2
+ * The client's renderer: snapshot in, DOM out — the toolbar, the lanes, the cards,
3
+ * and the card dialog's contents.
4
+ *
5
+ * This file runs in the browser, where no type checker sees it, so it holds to one
6
+ * rule: **every value from the snapshot is set as `textContent`** — never as
7
+ * `innerHTML`, never into a template string. A card title is model-written and a note
8
+ * can be anything a person typed, so the only safe renderer is one that never parses
9
+ * them.
10
+ *
11
+ * A frame replaces what changed and leaves the rest alone: a lane is rebuilt only
12
+ * when its cards' revisions moved, so the filter input, a collapsed lane, the scroll
13
+ * offset, and an open dialog all survive a peer's write.
14
+ *
15
+ * The client modules share one namespace, `window.bfBoard`: this one paints,
16
+ * `team.ts` draws the round, `dialog.ts` opens a card, `keys.ts` binds, and `live.ts`
17
+ * owns the frame stream and the wiring.
18
+ *
19
+ * `plural`, `tabTitle`, `matchesQuery`, `el`, `typing`, and `paintStatus` are not written
20
+ * here. Their one bodies live in `lib/` beside the rules they express, and the assembler
21
+ * hands the source in — a second copy in the bundle is a second wording for `1 card`, a
22
+ * second shape for the tab, a filter that finds a different card than the tool does, or an
23
+ * element builder and a typing test that drift from the review page's.
24
+ */
25
+
26
+ /**
27
+ * The renderer, with what it needs spliced in: the counts' wording, the tab title's
28
+ * shape, what a query matches, and the browser runtime's element builder and typing test —
29
+ * each written once in `lib/` and handed in as source.
30
+ */
31
+ export function renderJs(rules: {
32
+ plural: string;
33
+ tabTitle: string;
34
+ matches: string;
35
+ age: string;
36
+ runtime: string;
37
+ }): string {
38
+ return renderTemplate
39
+ .replace("__PLURAL__", rules.plural)
40
+ .replace("__TAB_TITLE__", rules.tabTitle)
41
+ .replace("__MATCHES__", rules.matches)
42
+ .replace("__AGE__", rules.age)
43
+ .replace("__RUNTIME__", rules.runtime);
44
+ }
45
+
46
+ const renderTemplate = String.raw`(function () {
47
+ "use strict";
48
+ var board = window.bfBoard || (window.bfBoard = {});
49
+
50
+ /** Lanes in the order the server sends them, with the words a reader expects. */
51
+ var LANE_LABEL = { todo: "To do", doing: "Doing", blocked: "Blocked", done: "Done" };
52
+ var LANE_EMPTY = { todo: "nothing waiting", doing: "nothing in flight", blocked: "nothing blocked", done: "nothing finished yet" };
53
+ /** Path chips a lane card shows before it counts the rest. */
54
+ var PATH_CHIPS = 3;
55
+ /**
56
+ * Finished cards a lane may hold before it starts folded.
57
+ *
58
+ * A board of 26 cards is 19 of them finished, and an open Done lane is four times the
59
+ * height of the three that carry work. Three or four is work a reader still checks;
60
+ * past this it is history, and the count in the head says how much.
61
+ */
62
+ var DONE_FOLD_AT = 6;
63
+ /** The two tones a reason line can take, by the kind reasonOf reports. */
64
+ var REASON_TONE = { blocked: N.cardReasonBlocked, unplanned: N.cardReasonUnplanned };
65
+
66
+ __RUNTIME__
67
+
68
+ /**
69
+ * One button that calls back, so nothing on the page is a click handler in markup.
70
+ *
71
+ * 'plain' is the bordered kit button, 'ghost' the text one: a card's primary
72
+ * action (Claim, Move) is bordered and the rest are text, which is what makes one
73
+ * of three controls on a card the obvious one.
74
+ */
75
+ function actionButton(label, onClick, variant) {
76
+ var kind = variant === "plain" ? "" : variant ? " bf-btn--" + variant : " bf-btn--ghost";
77
+ var node = el("button", "bf-btn" + kind, label);
78
+ node.type = "button";
79
+ node.addEventListener("click", onClick);
80
+ return node;
81
+ }
82
+
83
+ /** A kit chip, with a tone when the meaning is a state. */
84
+ function chip(text, tone) {
85
+ return el("span", "bf-chip" + (tone ? " bf-chip--" + tone : ""), text);
86
+ }
87
+
88
+ /**
89
+ * "12m", "3h", "2d" — an age, compact enough for a meta line.
90
+ *
91
+ * The transcript prints the same rule for the same fact, so the body is spliced in from
92
+ * lib/format.ts rather than written twice. No backticks in here: this is a template.
93
+ */
94
+ var ageText = __AGE__;
95
+ function ago(at) { return ageText(Date.now(), at); }
96
+
97
+ /** Bytes, the way the footer says them. */
98
+ function size(bytes) {
99
+ if (bytes < 1024) return bytes + " B";
100
+ if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + " kB";
101
+ return (bytes / (1024 * 1024)).toFixed(1) + " MB";
102
+ }
103
+
104
+ /** Every card the snapshot carries, in lane order. */
105
+ function cards(view) {
106
+ var all = [];
107
+ for (var i = 0; i < view.lanes.length; i++) all = all.concat(view.lanes[i].cards);
108
+ return all;
109
+ }
110
+
111
+ /** One card by id, or undefined when the last frame no longer has it. */
112
+ function findCard(view, id) {
113
+ var all = cards(view);
114
+ for (var i = 0; i < all.length; i++) if (all[i].id === id) return all[i];
115
+ return undefined;
116
+ }
117
+
118
+ /** The one line under a card's meta: why it sits where it does. */
119
+ function reasonOf(card, view) {
120
+ var last = card.notes.length > 0 ? card.notes[card.notes.length - 1] : undefined;
121
+ // An order is the strongest reason a card is not being worked on, and the one a
122
+ // reader cannot see from the lane: it comes before the note that says the same.
123
+ // The sentence is the snapshot's, so this line and the tool's refusal name one list.
124
+ if (card.waits !== undefined) return { text: "waits on " + card.waits, kind: "waiting" };
125
+ if (card.status === "blocked") {
126
+ // The lane already says blocked and the tone already says danger; the line says why.
127
+ // The recorded reason comes first: the note is what somebody happened to write last,
128
+ // and it is the reason a move to blocked asks for. The slot carries the reason alone —
129
+ // the lane head already says blocked, and a status prefix in it would be a second voice.
130
+ if (card.blockedBy) return { text: card.blockedBy, kind: "blocked" };
131
+ return { text: last ? last.text : "blocked, with nothing recorded", kind: "blocked" };
132
+ }
133
+ // An offer is a decision the lead already made once: the lane says nobody holds it, and
134
+ // this says how long the broadcast has gone unanswered, which is the difference between
135
+ // nobody having looked and nobody taking it.
136
+ if (card.status === "todo" && card.offeredAt !== undefined) {
137
+ return { text: "offered " + ago(card.offeredAt) + " ago · nobody took it", kind: "waiting" };
138
+ }
139
+ if (card.status === "doing" && !card.plan) {
140
+ return { text: "no plan recorded", kind: "unplanned" };
141
+ }
142
+ if (card.status === "doing") return { text: "planned", kind: "plan" };
143
+ if (card.status === "done") {
144
+ var landed = card.landed ? " · landed " + card.landed.commit : "";
145
+ return { text: "✓ done" + (card.owner ? " by " + card.owner.label : "") + landed, kind: "done" };
146
+ }
147
+ return last ? { text: last.text, kind: "note" } : undefined;
148
+ }
149
+
150
+ /** A path hint, or the +N chip that points at the rest. */
151
+ function paths(card, limit) {
152
+ var list = el("ul", N.cardPaths);
153
+ var shown = card.paths.slice(0, limit);
154
+ for (var i = 0; i < shown.length; i++) {
155
+ var item = el("li", "");
156
+ item.appendChild(chip(shown[i]));
157
+ list.appendChild(item);
158
+ }
159
+ if (card.paths.length > limit) {
160
+ var rest = el("li", "");
161
+ rest.appendChild(chip("+" + (card.paths.length - limit)));
162
+ list.appendChild(rest);
163
+ }
164
+ return list;
165
+ }
166
+
167
+ /** One card in its lane. */
168
+ function cardNode(card, view) {
169
+ var item = el("li", N.card);
170
+ item.setAttribute("data-card", card.id);
171
+ item.setAttribute("data-status", card.status);
172
+ item.setAttribute("data-rev", String(card.rev));
173
+
174
+ var head = el("p", N.cardHead);
175
+ head.appendChild(el("span", N.cardId, "#" + card.id));
176
+ var title = actionButton(card.title, function () {
177
+ board.openCard(card.id);
178
+ });
179
+ title.className = N.cardTitle;
180
+ head.appendChild(title);
181
+ item.appendChild(head);
182
+
183
+ var meta = el("p", N.cardMeta);
184
+ meta.appendChild(card.owner ? chip(card.owner.label, "accent") : el("span", "", "unassigned"));
185
+ // The order inside a lane is the priority, so an urgent card says so: an order
186
+ // nobody can see reads as an accident. The lane's colour stays the card's own.
187
+ if (card.priority !== "normal") meta.appendChild(chip(card.priority));
188
+ // The stamp, so the clock can keep the age honest between frames: a board can be
189
+ // quiet for ten minutes while the page still says "live".
190
+ var age = el("span", "", ago(card.updatedAt));
191
+ age.setAttribute("data-at", String(card.updatedAt));
192
+ meta.appendChild(age);
193
+ item.appendChild(meta);
194
+
195
+ if (card.paths.length > 0) item.appendChild(paths(card, PATH_CHIPS));
196
+
197
+ var reason = reasonOf(card, view);
198
+ if (reason) {
199
+ // The tone is a lookup, not a suffix: a name assembled from a prefix is a name no
200
+ // test can compare with the stylesheet.
201
+ var tone = REASON_TONE[reason.kind];
202
+ var line = el("p", tone === undefined ? N.cardReason : N.cardReason + " " + tone, reason.text);
203
+ // It clamps to one line on the card, so the full sentence is the tooltip.
204
+ line.title = reason.text;
205
+ item.appendChild(line);
206
+ }
207
+
208
+ return item;
209
+ }
210
+
211
+ /**
212
+ * The cards as the page draws them.
213
+ *
214
+ * A folded lane keeps its cards in the document, and so does a panel the other tab
215
+ * hides: the box is the only honest test of whether a reader can see one, and it is
216
+ * what j/k walks and what the dialog's position counts.
217
+ */
218
+ function drawnCards() {
219
+ var nodes = document.querySelectorAll(".board-card");
220
+ var seen = [];
221
+ for (var i = 0; i < nodes.length; i++) {
222
+ if (nodes[i].hidden) continue;
223
+ if (!nodes[i].getClientRects || nodes[i].getClientRects().length === 0) continue;
224
+ seen.push(nodes[i]);
225
+ }
226
+ return seen;
227
+ }
228
+
229
+ /** The toolbar's identity, progress, footer, and the tab's own name. */
230
+ function renderChrome(view) {
231
+ var me = document.getElementById("board-me");
232
+ if (me) {
233
+ me.textContent = view.me.label;
234
+ me.title = "This page is served as " + view.me.label + ", and it cannot change a card";
235
+ }
236
+ var progress = document.getElementById("board-progress");
237
+ if (progress) {
238
+ var total = view.counts.total;
239
+ // One or two cards is not a ratio: an empty bar beside "0/1 done" is noise.
240
+ progress.hidden = total < 3;
241
+ var fill = document.getElementById("board-progress-fill");
242
+ if (fill) fill.style.width = total === 0 ? "0%" : Math.round((view.counts.done / total) * 100) + "%";
243
+ var text = document.getElementById("board-progress-text");
244
+ if (text) text.textContent = view.counts.done + "/" + total + " done";
245
+ }
246
+ var where = document.getElementById("board-where");
247
+ if (where) {
248
+ // A footer that only counts read cards would say "2 cards" over a board holding
249
+ // three files, so the unreadable ones are named beside the size.
250
+ where.textContent =
251
+ "store " + view.where + " · " + plural(view.counts.total, "card") + " · " + size(view.bytes) +
252
+ (view.skipped > 0 ? " · " + plural(view.skipped, "unreadable") : "");
253
+ }
254
+ // The tab has to tell two boards apart, and a board's own name is the last segment of
255
+ // its store: one board per repository, so that key is the subject. The count is what
256
+ // a browser full of tabs is read for, and an empty board says nothing rather than 0.
257
+ var open = view.counts.total - view.counts.done;
258
+ document.title = tabTitle("board", view.where.split("/").pop(), open > 0 ? open + " open" : undefined);
259
+ }
260
+
261
+ /** 1 card, 2 cards — the one body, handed in by the bundle's assembler. */
262
+ var plural = __PLURAL__;
263
+ /** board · my-pi-agent · 3 open, the tab's one shape, handed in the same way. */
264
+ var tabTitle = __TAB_TITLE__;
265
+
266
+ /**
267
+ * Rewrite every age already on the page from its own timestamp.
268
+ *
269
+ * A frame carries the ages of the moment it was built, so on a board nobody is
270
+ * writing to the cards would slowly lie about how long they have sat there. The
271
+ * ticker calls this; it touches nothing else, so a filter, a folded lane, or an
272
+ * open dialog survives it.
273
+ */
274
+ function refreshAges() {
275
+ var nodes = document.querySelectorAll("[data-at]");
276
+ for (var i = 0; i < nodes.length; i++) {
277
+ var next = ago(Number(nodes[i].getAttribute("data-at")));
278
+ if (nodes[i].textContent !== next) nodes[i].textContent = next;
279
+ }
280
+ }
281
+
282
+ /** Whether the filter keeps a card: the module's one predicate, spliced in as source. */
283
+ var matches = __MATCHES__;
284
+
285
+ /** The lane section for one status, created once and then reused. */
286
+ function laneNode(lane) {
287
+ var section = document.querySelector('.board-lane[data-status="' + lane.status + '"]');
288
+ if (section) return section;
289
+ section = el("section", N.lane);
290
+ section.setAttribute("data-status", lane.status);
291
+ var head = el("h2", N.laneHead);
292
+ var toggle = actionButton(LANE_LABEL[lane.status] || lane.status, function () {
293
+ board.toggleLane(lane.status);
294
+ });
295
+ toggle.className = N.laneToggle;
296
+ toggle.setAttribute("aria-expanded", "true");
297
+ toggle.setAttribute("aria-controls", "lane-" + lane.status);
298
+ toggle.id = "lane-head-" + lane.status;
299
+ head.appendChild(el("span", N.laneDot));
300
+ head.appendChild(toggle);
301
+ head.appendChild(el("span", N.laneCount));
302
+ section.appendChild(head);
303
+ var list = el("ol", N.laneList);
304
+ list.id = "lane-" + lane.status;
305
+ section.appendChild(list);
306
+ section.appendChild(el("p", N.laneEmpty, LANE_EMPTY[lane.status] || "nothing"));
307
+ return section;
308
+ }
309
+
310
+ /** Draw the lanes, rebuilding only one whose cards actually moved. */
311
+ function renderLanes(view) {
312
+ var lanes = document.getElementById("board-lanes");
313
+ if (!lanes) return;
314
+ if (lanes.getAttribute("data-skeleton") === "1") {
315
+ // The placeholders have done their job; the real lanes replace them whole.
316
+ lanes.textContent = "";
317
+ lanes.removeAttribute("data-skeleton");
318
+ }
319
+ var query = board.query || "";
320
+ var empty = document.getElementById("board-no-cards");
321
+ if (empty) empty.hidden = view.counts.total !== 0;
322
+ lanes.hidden = view.counts.total === 0;
323
+ for (var i = 0; i < view.lanes.length; i++) {
324
+ var lane = view.lanes[i];
325
+ var section = laneNode(lane);
326
+ if (section.parentNode !== lanes) lanes.appendChild(section);
327
+ // Finished work starts folded, once: the default is what the page opens with, and
328
+ // toggleLane writes a boolean from then on, so a reader who unfolds Done keeps it
329
+ // unfolded through every frame a peer's write causes.
330
+ if (lane.status === "done" && lane.cards.length >= DONE_FOLD_AT) {
331
+ board.collapsed = board.collapsed || {};
332
+ if (board.collapsed[lane.status] === undefined) board.collapsed[lane.status] = true;
333
+ }
334
+ // A lane holding nothing is a rail: its dot, its word, and its count, in the width
335
+ // the fact deserves. The filter never does this — filtering is a view of the cards,
336
+ // not a statement that the lane has none.
337
+ section.setAttribute("data-empty", lane.cards.length === 0 ? "true" : "false");
338
+ // The lane's share of the row follows the work it holds: the sheet turns this number
339
+ // into a flex-grow, so two cards earn more width than one and a title is not wrapped
340
+ // for want of a column the lane is not using. A count, never text.
341
+ section.style.setProperty("--lane-count", String(lane.cards.length));
342
+ var shown = lane.cards.filter(function (card) { return matches(card, query); });
343
+ var sig = lane.cards.map(function (card) { return card.id + ":" + card.rev; }).join(",") + "|" + query;
344
+ if (section.getAttribute("data-sig") !== sig) {
345
+ section.setAttribute("data-sig", sig);
346
+ var list = section.querySelector(".board-lane__list");
347
+ list.textContent = "";
348
+ for (var j = 0; j < shown.length; j++) list.appendChild(cardNode(shown[j], view));
349
+ }
350
+ var count = section.querySelector(".board-lane__count");
351
+ if (count) {
352
+ count.textContent = query === "" || shown.length === lane.cards.length
353
+ ? String(lane.cards.length)
354
+ : shown.length + "/" + lane.cards.length;
355
+ }
356
+ var none = section.querySelector(".board-lane__empty");
357
+ if (none) {
358
+ none.hidden = shown.length !== 0;
359
+ none.textContent = query !== "" && lane.cards.length > 0 ? "no match" : LANE_EMPTY[lane.status] || "nothing";
360
+ }
361
+ var collapsed = board.collapsed && board.collapsed[lane.status] === true;
362
+ var list = section.querySelector(".board-lane__list");
363
+ if (list) list.hidden = collapsed;
364
+ // A folded lane shows nothing, so it takes the rail's width the way an empty one
365
+ // does: the count in its head is the whole fact, and the lanes with cards keep the
366
+ // width that would otherwise be a tray of nothing.
367
+ section.setAttribute("data-folded", collapsed ? "true" : "false");
368
+ var toggle = section.querySelector(".board-lane__toggle");
369
+ if (toggle) toggle.setAttribute("aria-expanded", String(!collapsed));
370
+ }
371
+ renderPicker(view);
372
+ }
373
+
374
+ /** The lane selector the narrow layout shows instead of four columns. */
375
+ function renderPicker(view) {
376
+ var picker = document.getElementById("board-picker");
377
+ if (!picker) return;
378
+ picker.textContent = "";
379
+ for (var i = 0; i < view.lanes.length; i++) {
380
+ (function (lane) {
381
+ var item = el("button", "bf-segmented__item", (LANE_LABEL[lane.status] || lane.status) + " " + lane.cards.length);
382
+ item.type = "button";
383
+ item.setAttribute("role", "tab");
384
+ item.setAttribute("data-lane", lane.status);
385
+ item.addEventListener("click", function () { board.showLane(lane.status); });
386
+ picker.appendChild(item);
387
+ })(view.lanes[i]);
388
+ }
389
+ board.applyLane();
390
+ }
391
+
392
+ /** Fill the card dialog's contents from a card, without opening or closing it. */
393
+ function fillDialog(card, view) {
394
+ document.getElementById("card-dialog-title").textContent = card.title;
395
+ document.getElementById("card-dialog-id").textContent = "#" + card.id;
396
+ // The lane is a status of its own, not a word inside a sentence: the same dot and
397
+ // label a lane head wears, so the dialog and the board speak the same vocabulary.
398
+ var state = document.getElementById("card-dialog-state");
399
+ state.setAttribute("data-status", card.status);
400
+ state.textContent = card.status;
401
+
402
+ // One fact per row of the ladder, so a reader can pick out a value instead of
403
+ // hunting for it in a sentence. A row with nothing to say takes its label with it:
404
+ // an empty cell under a heading reads as a missing value, not as an absence.
405
+ var pair = function (name, shown) {
406
+ var dt = document.getElementById("card-dialog-" + name + "-label");
407
+ var dd = document.getElementById("card-dialog-" + name);
408
+ if (dt) dt.hidden = !shown;
409
+ if (dd) dd.hidden = !shown;
410
+ return dd;
411
+ };
412
+ var setRow = function (name, shown, value, full) {
413
+ var dd = pair(name, shown);
414
+ dd.textContent = shown ? value : "";
415
+ if (full === undefined) dd.removeAttribute("title");
416
+ else dd.setAttribute("title", full);
417
+ };
418
+
419
+ var owner = document.getElementById("card-dialog-owner");
420
+ owner.textContent = "";
421
+ owner.appendChild(card.owner ? chip(card.owner.label, "accent") : document.createTextNode("unassigned"));
422
+ pair("owner", true);
423
+
424
+ var priority = pair("priority", card.priority !== "normal");
425
+ priority.textContent = "";
426
+ if (card.priority !== "normal") priority.appendChild(chip(card.priority));
427
+
428
+ setRow("updated", true, ago(card.updatedAt));
429
+ // How long the card has been in the lane it is in now, which updatedAt cannot say:
430
+ // a note filed against a blocked card moves that stamp without moving the card. Absent
431
+ // for a card written before the log existed, so the row takes its label with it.
432
+ setRow("lane", card.since !== undefined, card.status + " · " + ago(card.since));
433
+ setRow("by", true, card.by);
434
+ setRow("landed", card.landed !== undefined, card.landed ? card.landed.commit : "", card.landed ? card.landed.commit : undefined);
435
+
436
+ // The dialog and the card face say one thing, from one field: the ids it was linked
437
+ // to are the storage, and a reader is owed the lanes that make them a decision.
438
+ setRow("depends", card.waits !== undefined, card.waits || "", card.waits);
439
+ // The other side of the same link: what leaving this card undone is holding up, which
440
+ // is the one thing a card's own row cannot say.
441
+ setRow("dependents", card.dependents !== undefined, card.dependents || "", card.dependents);
442
+
443
+ // A long plan path elides on one line, and the whole of it is on the hover title and
444
+ // in the text, so it stays copyable: the reason to open a card is often its plan.
445
+ setRow("plan", Boolean(card.plan), card.plan || "", card.plan || undefined);
446
+
447
+ pair("paths", card.paths.length > 0);
448
+ var pathList = document.getElementById("card-dialog-path-list");
449
+ pathList.textContent = "";
450
+ for (var i = 0; i < card.paths.length; i++) {
451
+ var item = el("li", "");
452
+ var hint = chip(card.paths[i]);
453
+ hint.className += " " + N.dialogPath;
454
+ hint.title = card.paths[i];
455
+ item.appendChild(hint);
456
+ pathList.appendChild(item);
457
+ }
458
+
459
+ var notes = document.getElementById("card-dialog-notes");
460
+ notes.textContent = "";
461
+ for (var j = 0; j < card.notes.length; j++) {
462
+ var note = card.notes[j];
463
+ // The prose is the record and reads at body size; who wrote it and when is its
464
+ // footnote, so it follows the sentence instead of standing over it.
465
+ var row = el("li", N.dialogNote);
466
+ row.appendChild(el("p", N.dialogNoteText, note.text));
467
+ var foot = el("p", N.dialogNoteFoot);
468
+ foot.appendChild(el("span", N.dialogNoteBy, note.by));
469
+ foot.appendChild(el("span", N.dialogNoteAt, ago(note.at)));
470
+ row.appendChild(foot);
471
+ notes.appendChild(row);
472
+ }
473
+ var count = document.getElementById("card-dialog-note-count");
474
+ if (count) count.textContent = card.notes.length === 0 ? " · none" : " · " + card.notes.length;
475
+ var earlier = document.getElementById("card-dialog-earlier");
476
+ earlier.hidden = card.notesDropped === 0;
477
+ earlier.textContent = card.notesDropped === 0 ? "" : card.notesDropped + " earlier notes dropped";
478
+
479
+ // A modal whose title changes is not announced, so walking with j/k says where it
480
+ // landed: the position is counted over the cards as they are drawn, not as stored, so
481
+ // a filter or a folded lane cannot make the number disagree with the screen.
482
+ var where = document.getElementById("card-dialog-where");
483
+ if (where) {
484
+ var drawn = drawnCards();
485
+ var at = 0;
486
+ for (var k = 0; k < drawn.length; k++) {
487
+ if (drawn[k].getAttribute("data-card") === String(card.id)) at = k + 1;
488
+ }
489
+ // Opened from the Team tab there is no drawn board to count along, so the line
490
+ // names the card instead of claiming a position it cannot know.
491
+ where.textContent = drawn.length === 0 ? card.title : "card " + at + " of " + drawn.length + ": " + card.title;
492
+ }
493
+ }
494
+
495
+ /** Paint a whole snapshot. */
496
+ board.render = function (view) {
497
+ board.view = view;
498
+ renderChrome(view);
499
+ renderLanes(view);
500
+ board.renderTeam(view);
501
+ if (board.openId !== undefined) {
502
+ var card = findCard(view, board.openId);
503
+ if (card) fillDialog(card, view);
504
+ else board.closeCard();
505
+ }
506
+ };
507
+
508
+ board.el = el;
509
+ board.typing = typing;
510
+ board.chip = chip;
511
+ board.actionButton = actionButton;
512
+ board.ago = ago;
513
+ board.findCard = findCard;
514
+ board.drawnCards = drawnCards;
515
+ board.matches = matches;
516
+ board.fillDialog = fillDialog;
517
+ board.refreshAges = refreshAges;
518
+ board.plural = plural;
519
+ })();`;