@gavin-hu/my-pi-agent 0.21.0 → 0.22.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 (254) hide show
  1. package/README.md +46 -31
  2. package/README.zh-CN.md +40 -26
  3. package/assets/screens/board.png +0 -0
  4. package/assets/screens/chrome.png +0 -0
  5. package/assets/screens/file-browse.png +0 -0
  6. package/assets/screens/plan-review.png +0 -0
  7. package/assets/screens/plan.png +0 -0
  8. package/docs/extensions/README.md +22 -24
  9. package/docs/extensions/board.md +361 -147
  10. package/docs/extensions/bytefly.md +152 -111
  11. package/docs/extensions/check.md +15 -17
  12. package/docs/extensions/context.md +35 -15
  13. package/docs/extensions/file-browse.md +30 -31
  14. package/docs/extensions/file-open.md +9 -11
  15. package/docs/extensions/file-read.md +12 -14
  16. package/docs/extensions/goal.md +15 -17
  17. package/docs/extensions/job.md +54 -42
  18. package/docs/extensions/memory.md +17 -19
  19. package/docs/extensions/plan.md +166 -126
  20. package/docs/extensions/question.md +16 -18
  21. package/docs/extensions/rewind.md +17 -19
  22. package/docs/extensions/serve.md +34 -18
  23. package/docs/extensions/session-bus.md +38 -16
  24. package/docs/extensions/subagent.md +23 -25
  25. package/docs/extensions/team.md +72 -33
  26. package/docs/extensions/todo.md +18 -18
  27. package/docs/extensions/watch.md +31 -31
  28. package/docs/extensions/web-access.md +2 -4
  29. package/docs/extensions/wechat.md +34 -35
  30. package/docs/extensions/worktree.md +56 -50
  31. package/extensions/board/commands.ts +56 -15
  32. package/extensions/board/format.ts +43 -24
  33. package/extensions/board/index.ts +5 -4
  34. package/extensions/board/input.ts +4 -9
  35. package/extensions/board/join.ts +4 -40
  36. package/extensions/board/lazy-page.ts +40 -0
  37. package/extensions/board/page.ts +13 -11
  38. package/extensions/board/result.ts +30 -16
  39. package/extensions/board/rows.ts +62 -25
  40. package/extensions/board/schema.ts +42 -10
  41. package/extensions/board/snapshot.ts +2 -1
  42. package/extensions/board/tone.ts +22 -0
  43. package/extensions/board/tools.ts +131 -44
  44. package/extensions/board/types.ts +36 -32
  45. package/extensions/board/view/card.ts +229 -0
  46. package/extensions/board/view/client.ts +33 -15
  47. package/extensions/board/view/dialog.ts +169 -6
  48. package/extensions/board/view/empty.ts +47 -0
  49. package/extensions/board/view/keys.ts +13 -2
  50. package/extensions/board/view/live.ts +89 -21
  51. package/extensions/board/view/names.ts +13 -4
  52. package/extensions/board/view/page.ts +23 -9
  53. package/extensions/board/view/render.ts +208 -271
  54. package/extensions/board/view/styles.ts +123 -31
  55. package/extensions/board/view/team.ts +297 -76
  56. package/extensions/bytefly/block/fallback.ts +1 -1
  57. package/extensions/bytefly/doctor/runtime.ts +5 -2
  58. package/extensions/bytefly/footer/collect/label.ts +60 -0
  59. package/extensions/bytefly/footer/collect/snapshot.ts +8 -1
  60. package/extensions/bytefly/footer/config.ts +4 -2
  61. package/extensions/bytefly/footer/register.ts +33 -4
  62. package/extensions/bytefly/footer/render/lines.ts +7 -4
  63. package/extensions/bytefly/footer/render/segments.ts +37 -10
  64. package/extensions/bytefly/footer/render/status.ts +0 -11
  65. package/extensions/bytefly/footer/types.ts +7 -0
  66. package/extensions/bytefly/header/config.ts +1 -1
  67. package/extensions/bytefly/index.ts +32 -6
  68. package/extensions/bytefly/mode/editor.ts +115 -0
  69. package/extensions/bytefly/panel/commands.ts +210 -120
  70. package/extensions/bytefly/panel/format.ts +4 -4
  71. package/extensions/bytefly/panel/settings.ts +73 -6
  72. package/extensions/bytefly/panel/tui/keys.ts +2 -6
  73. package/extensions/bytefly/update/cache.ts +1 -1
  74. package/extensions/bytefly/update/register.ts +4 -4
  75. package/extensions/bytefly/working/config.ts +5 -7
  76. package/extensions/bytefly/working/format.ts +10 -13
  77. package/extensions/bytefly/working/register.ts +22 -27
  78. package/extensions/check/commands.ts +3 -2
  79. package/extensions/check/{tui.ts → view/check-screens.ts} +14 -19
  80. package/extensions/context/commands.ts +2 -2
  81. package/extensions/context/{tui.ts → view/context-report.ts} +51 -19
  82. package/extensions/file-browse/index.ts +67 -36
  83. package/extensions/file-read/render.ts +4 -4
  84. package/extensions/goal/plan-route.ts +2 -2
  85. package/extensions/job/child.ts +527 -0
  86. package/extensions/job/clock.ts +142 -0
  87. package/extensions/job/commands.ts +4 -2
  88. package/extensions/job/config.ts +8 -1
  89. package/extensions/job/format.ts +22 -1
  90. package/extensions/job/log-writer.ts +13 -2
  91. package/extensions/job/registry.ts +63 -7
  92. package/extensions/job/render.ts +34 -0
  93. package/extensions/job/runtime.ts +296 -655
  94. package/extensions/job/session.ts +132 -19
  95. package/extensions/job/store.ts +7 -7
  96. package/extensions/job/tools.ts +3 -3
  97. package/extensions/job/types.ts +47 -0
  98. package/extensions/job/{tui.ts → view/jobs-screen.ts} +19 -73
  99. package/extensions/job/waiters.ts +44 -1
  100. package/extensions/memory/commands.ts +4 -2
  101. package/extensions/memory/{tui.ts → view/memory-picker.ts} +12 -12
  102. package/extensions/plan/commands.ts +12 -9
  103. package/extensions/plan/format.ts +8 -2
  104. package/extensions/plan/git-bash.ts +115 -16
  105. package/extensions/plan/index.ts +53 -52
  106. package/extensions/plan/plan-index.ts +4 -2
  107. package/extensions/plan/plans.ts +5 -54
  108. package/extensions/plan/policy.ts +8 -10
  109. package/extensions/plan/review-lazy.ts +85 -0
  110. package/extensions/plan/review.ts +2 -2
  111. package/extensions/plan/runtime.ts +49 -39
  112. package/extensions/plan/sections.ts +1 -1
  113. package/extensions/plan/steps.ts +3 -3
  114. package/extensions/plan/todos.ts +1 -1
  115. package/extensions/plan/tools/cancel.ts +27 -62
  116. package/extensions/plan/tools/enter.ts +55 -81
  117. package/extensions/plan/tools/exit.ts +221 -238
  118. package/extensions/plan/tools/refusal.ts +18 -0
  119. package/extensions/plan/tools/steps.ts +75 -92
  120. package/extensions/plan/tools/write.ts +77 -120
  121. package/extensions/plan/tools.ts +188 -30
  122. package/extensions/plan/tui.ts +5 -5
  123. package/extensions/plan/types.ts +48 -5
  124. package/extensions/plan/web-client.ts +3 -0
  125. package/extensions/plan/web-lazy.ts +53 -0
  126. package/extensions/plan/web-markdown.ts +9 -67
  127. package/extensions/plan/web-payload.ts +3 -3
  128. package/extensions/plan/web-router.ts +18 -9
  129. package/extensions/plan/web.ts +1 -12
  130. package/extensions/question/tools.ts +6 -2
  131. package/extensions/question/{tui.ts → view/question-screen.ts} +11 -11
  132. package/extensions/rewind/commands/commands.ts +6 -3
  133. package/extensions/rewind/state/runtime.ts +1 -1
  134. package/extensions/rewind/{view/status.ts → status.ts} +4 -4
  135. package/extensions/rewind/view/review-tui.ts +4 -4
  136. package/extensions/serve/config.ts +10 -8
  137. package/extensions/serve/index.ts +17 -3
  138. package/extensions/serve/registry.ts +26 -17
  139. package/extensions/session-bus/commands.ts +11 -6
  140. package/extensions/session-bus/join-request.ts +2 -1
  141. package/extensions/session-bus/mail-watch.ts +26 -17
  142. package/extensions/session-bus/protocol.ts +15 -3
  143. package/extensions/session-bus/resolve.ts +5 -7
  144. package/extensions/session-bus/runtime.ts +40 -12
  145. package/extensions/session-bus/store.ts +101 -19
  146. package/extensions/session-bus/types.ts +19 -0
  147. package/extensions/session-bus/{tui.ts → view/session-list.ts} +16 -16
  148. package/extensions/subagent/agents.ts +1 -1
  149. package/extensions/subagent/commands.ts +4 -2
  150. package/extensions/subagent/format.ts +15 -15
  151. package/extensions/subagent/index.ts +1 -1
  152. package/extensions/subagent/render.ts +3 -3
  153. package/extensions/subagent/{background-tui.ts → view/agents-board.ts} +11 -11
  154. package/extensions/team/commands.ts +24 -7
  155. package/extensions/team/dispatch.ts +18 -4
  156. package/extensions/team/format.ts +17 -2
  157. package/extensions/team/index.ts +9 -8
  158. package/extensions/team/schema.ts +6 -5
  159. package/extensions/team/tools.ts +107 -45
  160. package/extensions/team/types.ts +3 -2
  161. package/extensions/todo/commands.ts +4 -2
  162. package/extensions/todo/format.ts +11 -0
  163. package/extensions/todo/render.ts +57 -0
  164. package/extensions/todo/runtime.ts +1 -1
  165. package/extensions/todo/tools.ts +1 -1
  166. package/extensions/todo/view/todo-screen.ts +122 -0
  167. package/extensions/todo/widget.ts +47 -0
  168. package/extensions/watch/commands.ts +4 -2
  169. package/extensions/watch/format.ts +36 -7
  170. package/extensions/watch/render.ts +57 -0
  171. package/extensions/watch/runtime.ts +27 -22
  172. package/extensions/watch/tools.ts +4 -4
  173. package/extensions/watch/{tui.ts → view/watch-screen.ts} +18 -80
  174. package/extensions/web-access/fetch/tool.ts +12 -4
  175. package/extensions/wechat/commands/commands.ts +18 -14
  176. package/extensions/wechat/index.ts +72 -37
  177. package/extensions/wechat/state/credentials.ts +1 -1
  178. package/extensions/wechat/state/state.ts +1 -1
  179. package/extensions/wechat/text/status.ts +112 -0
  180. package/extensions/wechat/types.ts +2 -2
  181. package/extensions/wechat/view/status-screen.ts +4 -103
  182. package/extensions/worktree/commands.ts +11 -9
  183. package/extensions/worktree/enter.ts +2 -2
  184. package/extensions/worktree/exit.ts +2 -2
  185. package/extensions/worktree/index.ts +5 -5
  186. package/extensions/worktree/screen.ts +3 -3
  187. package/extensions/worktree/status.ts +2 -2
  188. package/extensions/worktree/tools.ts +113 -116
  189. package/lib/README.md +27 -13
  190. package/lib/board/actions.ts +111 -0
  191. package/lib/board/artifact.ts +149 -0
  192. package/lib/board/card.ts +163 -52
  193. package/lib/board/config.ts +58 -0
  194. package/lib/board/contract.ts +113 -0
  195. package/lib/board/join.ts +38 -0
  196. package/lib/board/lanes.ts +182 -0
  197. package/lib/board/lead.ts +130 -0
  198. package/lib/board/limits.ts +78 -0
  199. package/lib/board/links.ts +31 -10
  200. package/lib/board/page-pointer.ts +1 -1
  201. package/lib/board/read.ts +83 -0
  202. package/lib/board/reason.ts +102 -0
  203. package/lib/board/round-text.ts +68 -6
  204. package/lib/board/round.ts +31 -15
  205. package/lib/board/rules.ts +151 -307
  206. package/lib/board/seat.ts +35 -0
  207. package/lib/board/session.ts +136 -54
  208. package/lib/board/skipped.ts +22 -0
  209. package/lib/board/steps.ts +487 -0
  210. package/lib/board/store.ts +87 -30
  211. package/lib/board/tone.ts +92 -0
  212. package/lib/board/view.ts +70 -58
  213. package/{extensions/watch → lib}/child.ts +40 -21
  214. package/lib/env.ts +65 -7
  215. package/lib/format.ts +13 -0
  216. package/lib/http/index.ts +14 -12
  217. package/lib/http/port.ts +15 -10
  218. package/lib/lazy.ts +45 -0
  219. package/lib/plan-entry.ts +39 -10
  220. package/lib/plans/paths.ts +82 -0
  221. package/lib/policy.ts +9 -6
  222. package/lib/report.ts +3 -2
  223. package/lib/requirements.ts +8 -8
  224. package/lib/session-label.ts +111 -5
  225. package/lib/session-roster.ts +43 -1
  226. package/lib/session-tool.ts +5 -4
  227. package/lib/stray-fields.ts +7 -2
  228. package/lib/tool-names.ts +5 -10
  229. package/lib/tui/README.md +23 -8
  230. package/lib/tui/chrome.ts +16 -15
  231. package/lib/tui/cursor.ts +18 -3
  232. package/lib/tui/glyphs.ts +9 -7
  233. package/lib/tui/tokens.ts +33 -2
  234. package/lib/tui/tool.ts +3 -3
  235. package/lib/web/README.md +21 -0
  236. package/lib/web/components.ts +5 -1
  237. package/lib/web/index.ts +1 -0
  238. package/lib/web/markdown.ts +83 -0
  239. package/lib/web/markup.ts +2 -1
  240. package/lib/web/runtime.ts +9 -10
  241. package/package.json +6 -1
  242. package/skills/init/SKILL.md +5 -5
  243. package/skills/next/SKILL.md +3 -5
  244. package/extensions/board/seat.ts +0 -28
  245. package/extensions/todo/tui.ts +0 -212
  246. package/lib/tui/index.ts +0 -34
  247. package/skills/project-docs/SKILL.md +0 -133
  248. package/skills/project-docs/references/audit.md +0 -131
  249. package/skills/project-docs/references/changelog.md +0 -105
  250. package/skills/project-docs/references/discovery.md +0 -127
  251. package/skills/project-docs/references/readme.md +0 -80
  252. package/skills/project-docs/references/release.md +0 -84
  253. package/skills/project-docs/references/roadmap.md +0 -93
  254. package/skills/project-docs/scripts/state.ts +0 -316
@@ -3,8 +3,9 @@
3
3
  *
4
4
  * A frame is a whole snapshot, so every message repaints from it rather than patching
5
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.
6
+ * is showing, the filter text, which lanes are collapsed, which card's dialog is open,
7
+ * and where the reader is on the board — the card they are on and the ones they have
8
+ * read; none of it is authoritative, and none of it is sent anywhere.
8
9
  */
9
10
 
10
11
  export const BOARD_LIVE_JS = String.raw`(function () {
@@ -28,7 +29,7 @@ export const BOARD_LIVE_JS = String.raw`(function () {
28
29
  else node.textContent = "connecting…";
29
30
  }
30
31
 
31
- /** Show one tab: the panel, aria-current, and the address so a link is shareable. */
32
+ /** Show one tab: the panel, the selected tab, and the address so a link is shareable. */
32
33
  function showView(view) {
33
34
  var team = view === "team";
34
35
  document.documentElement.setAttribute("data-view", team ? "team" : "board");
@@ -37,14 +38,40 @@ export const BOARD_LIVE_JS = String.raw`(function () {
37
38
  var tabs = document.querySelectorAll("[data-view][role='tab']");
38
39
  for (var i = 0; i < tabs.length; i++) {
39
40
  var current = tabs[i].getAttribute("data-view") === (team ? "team" : "board");
40
- if (current) tabs[i].setAttribute("aria-current", "page");
41
- else tabs[i].removeAttribute("aria-current");
41
+ // A tablist is read through aria-selected, and a roving tabindex is what makes one
42
+ // Tab land on the strip rather than on each tab in turn. Both are the markup's own
43
+ // attributes: a state painted only in a class is a state a screen reader cannot read.
44
+ tabs[i].setAttribute("aria-selected", current ? "true" : "false");
45
+ tabs[i].setAttribute("tabindex", current ? "0" : "-1");
42
46
  }
43
47
  var url = new URL(window.location.href);
44
48
  url.searchParams.set("view", team ? "team" : "board");
45
49
  window.history.replaceState(null, "", url.toString());
46
50
  }
47
51
 
52
+ /**
53
+ * Arrow keys move along the tab strip, the way a tablist is expected to behave.
54
+ *
55
+ * Automatic activation: the panels are both in the document already, so showing one is a
56
+ * hidden attribute rather than a request, and a reader who arrows across the strip sees
57
+ * each tab he lands on. Home and End are the ends of it.
58
+ */
59
+ function bindTabKeys(tabs) {
60
+ for (var i = 0; i < tabs.length; i++) {
61
+ tabs[i].addEventListener("keydown", function (event) {
62
+ var names = ["ArrowLeft", "ArrowRight", "Home", "End"];
63
+ if (names.indexOf(event.key) === -1) return;
64
+ event.preventDefault();
65
+ var at = 0;
66
+ for (var j = 0; j < tabs.length; j++) if (tabs[j] === event.currentTarget) at = j;
67
+ var step = event.key === "ArrowLeft" ? -1 : 1;
68
+ var next = event.key === "Home" ? 0 : event.key === "End" ? tabs.length - 1 : (at + step + tabs.length) % tabs.length;
69
+ showView(tabs[next].getAttribute("data-view"));
70
+ tabs[next].focus();
71
+ });
72
+ }
73
+ }
74
+
48
75
  board.viewName = function () {
49
76
  return document.documentElement.getAttribute("data-view") === "team" ? "team" : "board";
50
77
  };
@@ -54,27 +81,40 @@ export const BOARD_LIVE_JS = String.raw`(function () {
54
81
  var narrow = window.matchMedia && window.matchMedia("(max-width: 860px)").matches;
55
82
  var lanes = document.querySelectorAll(".board-lane");
56
83
  for (var i = 0; i < lanes.length; i++) {
57
- var selected = lanes[i].getAttribute("data-status") === board.lane;
84
+ var selected = lanes[i].getAttribute("data-lane") === board.shown;
58
85
  lanes[i].hidden = narrow && !selected;
59
86
  }
60
- var items = document.querySelectorAll("#board-picker [data-lane]");
87
+ var items = document.querySelectorAll("#board-picker [data-picker-lane]");
61
88
  for (var j = 0; j < items.length; j++) {
62
- if (items[j].getAttribute("data-lane") === board.lane) items[j].setAttribute("aria-current", "page");
63
- else items[j].removeAttribute("aria-current");
89
+ // A pressed button, because the picker shows one lane rather than joining a tablist:
90
+ // "true" is the lane a reader is looking at, and the kit paints it from this state.
91
+ items[j].setAttribute("aria-pressed", items[j].getAttribute("data-picker-lane") === board.shown ? "true" : "false");
64
92
  }
65
93
  }
66
94
 
67
95
  board.applyLane = applyLane;
68
96
 
69
97
  board.showLane = function (lane) {
70
- board.lane = lane;
98
+ board.shown = lane;
71
99
  applyLane();
72
100
  };
73
101
 
74
- /** Fold a lane's cards away and leave its head, count and all. */
75
- board.toggleLane = function (status) {
102
+ /**
103
+ * Fold one list away and leave its control standing.
104
+ *
105
+ * Three things on the page fold: a lane, the roster's tail of owners nothing answers to, and
106
+ * the Done strip. They share one map, and a key says which kind it is: a lane keeps its own
107
+ * name, and the two the Team tab owns are team-roster and team-done, because done is
108
+ * already a lane.
109
+ *
110
+ * The key is also recorded, because the panel a fold lives in is repainted: the element the
111
+ * reader clicked no longer exists when the frame lands, and the renderer focuses
112
+ * data-fold="<key>" to put the keyboard back where it was.
113
+ */
114
+ board.toggleFold = function (key) {
76
115
  board.collapsed = board.collapsed || {};
77
- board.collapsed[status] = board.collapsed[status] !== true;
116
+ board.collapsed[key] = board.collapsed[key] !== true;
117
+ board.foldFocus = key;
78
118
  if (board.view) board.render(board.view);
79
119
  };
80
120
 
@@ -121,14 +161,14 @@ export const BOARD_LIVE_JS = String.raw`(function () {
121
161
  * The grey cards a page shows while it waits for its first frame.
122
162
  *
123
163
  * They are marked as placeholders rather than given a lane status: the renderer
124
- * looks lanes up by data-status, so a skeleton that carried one would be adopted
164
+ * looks lanes up by data-lane, so a skeleton that carried one would be adopted
125
165
  * as the real lane and the cards would never be drawn into it.
126
166
  */
127
167
  function skeletons() {
128
168
  var lanes = document.getElementById("board-lanes");
129
169
  if (!lanes || lanes.childElementCount > 0) return;
130
170
  lanes.setAttribute("data-skeleton", "1");
131
- for (var i = 0; i < 4; i++) {
171
+ for (var i = 0; i < board.laneCount; i++) {
132
172
  var lane = board.el("section", N.lane);
133
173
  for (var j = 0; j < 2; j++) lane.appendChild(board.el("div", N.skeleton));
134
174
  lanes.appendChild(lane);
@@ -137,18 +177,18 @@ export const BOARD_LIVE_JS = String.raw`(function () {
137
177
 
138
178
  /**
139
179
  * The lane a narrow reader lands on: the first with a card in it, else the first
140
- * lane. Opening on an empty To do while the only card sits in Blocked is a blank
180
+ * lane. Opening on an empty Backlog while the only card sits in Blocked is a blank
141
181
  * page with a picker nobody has a reason to press.
142
182
  */
143
183
  function laneWithWork(view) {
144
- for (var i = 0; i < view.lanes.length; i++) if (view.lanes[i].cards.length > 0) return view.lanes[i].status;
145
- return view.lanes[0] ? view.lanes[0].status : "todo";
184
+ for (var i = 0; i < view.lanes.length; i++) if (view.lanes[i].cards.length > 0) return view.lanes[i].key;
185
+ return view.lanes[0] ? view.lanes[0].key : "backlog";
146
186
  }
147
187
 
148
188
  /** One frame: paint and record when it arrived. */
149
189
  function paint(view) {
150
190
  board.lastFrame = Date.now();
151
- board.lane = board.lane || laneWithWork(view);
191
+ board.shown = board.shown || laneWithWork(view);
152
192
  badge("live", board.lastFrame);
153
193
  board.render(view);
154
194
  }
@@ -181,16 +221,36 @@ export const BOARD_LIVE_JS = String.raw`(function () {
181
221
  board.applyFilter();
182
222
  });
183
223
  }
224
+ // The no-match state's one control: a view action, like the tab switch, and it puts the
225
+ // reader back in the field they were typing in.
226
+ var clear = document.getElementById("board-no-match-clear");
227
+ if (clear) {
228
+ clear.addEventListener("click", function () {
229
+ board.clearFilter();
230
+ board.focusFilter();
231
+ });
232
+ }
184
233
  var tabs = document.querySelectorAll("[data-view][role='tab']");
185
234
  for (var i = 0; i < tabs.length; i++) {
186
235
  tabs[i].addEventListener("click", function (event) {
187
236
  showView(event.currentTarget.getAttribute("data-view"));
188
237
  });
189
238
  }
239
+ bindTabKeys(tabs);
240
+ // A card's title button takes the keyboard from j/k, a Tab, or a click, and the mark
241
+ // follows focus rather than the key that moved it. A focus outside the cards leaves the
242
+ // last card marked, which is what a reader coming back from the filter expects.
243
+ document.addEventListener("focusin", function (event) {
244
+ var node = event.target;
245
+ var card = node && node.closest ? node.closest(".board-card") : null;
246
+ if (!card) return;
247
+ board.current = card.getAttribute("data-card");
248
+ board.markCards();
249
+ });
190
250
  var picker = document.getElementById("board-picker");
191
251
  if (picker) {
192
252
  picker.addEventListener("click", function (event) {
193
- var lane = event.target && event.target.getAttribute ? event.target.getAttribute("data-lane") : null;
253
+ var lane = event.target && event.target.getAttribute ? event.target.getAttribute("data-picker-lane") : null;
194
254
  if (lane) board.showLane(lane);
195
255
  });
196
256
  }
@@ -212,10 +272,18 @@ export const BOARD_LIVE_JS = String.raw`(function () {
212
272
  }
213
273
 
214
274
  board.start = function () {
215
- board.lane = undefined;
275
+ board.shown = undefined;
216
276
  board.openId = undefined;
217
277
  board.query = "";
218
278
  board.collapsed = {};
279
+ // The fold a reader just moved, and the panels' remembered signatures: both belong to the
280
+ // tab, and a fresh tab is a fresh read of what the store holds.
281
+ board.foldFocus = undefined;
282
+ board.teamPanels = undefined;
283
+ // Where the reader is and has been: the two marks a card face can wear, which live as
284
+ // long as the tab does and never reach the store.
285
+ board.current = undefined;
286
+ board.visited = {};
219
287
  showView(new URLSearchParams(window.location.search).get("view") === "team" ? "team" : "board");
220
288
  wireChrome();
221
289
  skeletons();
@@ -35,16 +35,23 @@ export const BOARD_CLASS = {
35
35
  laneList: "board-lane__list",
36
36
  laneEmpty: "board-lane__empty",
37
37
 
38
- // One card, and the two tones its reason line can take.
38
+ // One card, the facts its meta line carries, and the two tones its reason line can take.
39
39
  card: "board-card",
40
40
  cardHead: "board-card__head",
41
41
  cardId: "board-card__id",
42
42
  cardTitle: "board-card__title",
43
43
  cardMeta: "board-card__meta",
44
+ cardNotes: "board-card__notes",
44
45
  cardPaths: "board-card__paths",
45
46
  cardReason: "board-card__reason",
46
- cardReasonBlocked: "board-card__reason--blocked",
47
- cardReasonUnplanned: "board-card__reason--unplanned",
47
+ // One class per tone, never per reason kind: the same tone is what the terminal paints.
48
+ cardReasonMuted: "board-card__reason--muted",
49
+ cardReasonDim: "board-card__reason--dim",
50
+ cardReasonWarn: "board-card__reason--warn",
51
+ cardReasonAccent: "board-card__reason--accent",
52
+ cardReasonDanger: "board-card__reason--danger",
53
+ cardReasonSuccess: "board-card__reason--success",
54
+ cardHolds: "board-card__holds",
48
55
 
49
56
  // The card dialog: the record of one card, as a ladder of facts and its notes.
50
57
  dialogPanel: "board-dialog__panel",
@@ -79,7 +86,6 @@ export const BOARD_CLASS = {
79
86
  teamTitle: "board-team__title",
80
87
  teamFoot: "board-team__foot",
81
88
  teamWhy: "board-team__why",
82
- teamWhyBlocked: "board-team__why--blocked",
83
89
  teamRoster: "board-team__roster",
84
90
  teamMate: "board-team__mate",
85
91
  teamDot: "board-team__dot",
@@ -88,4 +94,7 @@ export const BOARD_CLASS = {
88
94
  teamEmpty: "board-team__empty",
89
95
  teamDone: "board-team__done",
90
96
  teamId: "board-team__id",
97
+ teamDoneIds: "board-team__ids",
98
+ teamTail: "board-team__tail",
99
+ teamFold: "board-team__fold",
91
100
  } as const satisfies Readonly<Record<string, string>>;
@@ -13,6 +13,7 @@
13
13
  * contract test holds every class to the kit or to this page's stylesheet.
14
14
  */
15
15
 
16
+ import { escapeHtml } from "../../../lib/escape.ts";
16
17
  import {
17
18
  baseAttribute,
18
19
  brandLockup,
@@ -36,7 +37,14 @@ export type PageView = "board" | "team";
36
37
  * shared link opens the tab it names. The document has no form and no write affordance:
37
38
  * a page that cannot change a card cannot offer a button the server would refuse.
38
39
  */
39
- export function renderBoardPage(input: { view: PageView; base?: string }): string {
40
+ export function renderBoardPage(input: {
41
+ view: PageView;
42
+ base?: string;
43
+ /** The repository this page is a board of, which its header names. */
44
+ repo: string;
45
+ /** The repository root, which is the header name's hover: a name alone cannot tell two clones apart. */
46
+ root: string;
47
+ }): string {
40
48
  const team = input.view === "team";
41
49
  // The page answers under the session's mount, so its own script and theme are named
42
50
  // from that prefix rather than from the origin's root.
@@ -51,13 +59,13 @@ ${docHead({ title: tabTitle("board"), css: BOARD_CSS, base })}
51
59
  <a class="skip" href="#main">Skip to the cards</a>
52
60
  <header class="bf-app__header"><div class="bf-toolbar ${N.toolbar}">
53
61
  ${brandLockup("board")}
54
- <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>
62
+ <span class="bf-chip bf-chip--outline" id="board-repo" title="${escapeHtml(input.root)}">${escapeHtml(input.repo)}</span>
55
63
  <p class="bf-status ${N.status}" id="board-status" data-state="connecting" aria-live="polite">connecting…</p>
56
64
  <span class="bf-spacer"></span>
57
65
  <div class="${N.controls}">
58
66
  <div class="bf-segmented" role="tablist" aria-label="View">
59
- <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>
60
- <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>
67
+ <button type="button" class="bf-segmented__item" role="tab" id="tab-board" data-view="board" aria-controls="panel-board" aria-selected="${team ? "false" : "true"}" tabindex="${team ? "-1" : "0"}">Board</button>
68
+ <button type="button" class="bf-segmented__item" role="tab" id="tab-team" data-view="team" aria-controls="panel-team" aria-selected="${team ? "true" : "false"}" tabindex="${team ? "0" : "-1"}">Team</button>
61
69
  </div>
62
70
  <p class="bf-progress ${N.progress}" id="board-progress" hidden>
63
71
  <span class="bf-progress__track"><span class="bf-progress__fill" id="board-progress-fill"></span></span>
@@ -72,12 +80,13 @@ ${docHead({ title: tabTitle("board"), css: BOARD_CSS, base })}
72
80
  </div></header>
73
81
  <main class="bf-app__main ${N.main}" id="main" tabindex="-1">
74
82
  <section class="${N.panel}" id="panel-board" role="tabpanel" aria-labelledby="tab-board"${team ? " hidden" : ""}>
75
- <div class="bf-segmented ${N.picker}" id="board-picker" role="tablist" aria-label="Lane"></div>
83
+ <div class="bf-segmented ${N.picker}" id="board-picker" role="group" aria-label="Lane"></div>
76
84
  <div class="${N.lanes}" id="board-lanes"></div>
77
85
  <div class="bf-empty ${N.noCards}" id="board-no-cards" hidden>
78
- <p class="bf-empty__title">No cards yet</p>
79
- <p class="bf-empty__sub">One card per feature: what it delivers, who holds it, and where to start.</p>
80
- <p class="bf-empty__sub"><code>board add</code> makes one — this page reads, and the tools write.</p>
86
+ <p class="bf-empty__title" id="board-no-cards-title">No cards yet</p>
87
+ <p class="bf-empty__sub" id="board-no-cards-sub">One card per feature: what it delivers, who holds it, and where to start.</p>
88
+ <p class="bf-empty__sub" id="board-no-cards-hint"><code>board add</code> makes one — this page reads, and the tools write.</p>
89
+ <button type="button" class="bf-btn" id="board-no-match-clear" hidden>Clear filter</button>
81
90
  </div>
82
91
  </section>
83
92
  <section class="${N.panel}" id="panel-team" role="tabpanel" aria-labelledby="tab-team"${team ? "" : " hidden"}>
@@ -109,6 +118,11 @@ ${docHead({ title: tabTitle("board"), css: BOARD_CSS, base })}
109
118
  <dt id="card-dialog-depends-label" hidden>waits on</dt><dd class="${N.dialogValue}" id="card-dialog-depends" hidden></dd>
110
119
  <dt id="card-dialog-dependents-label" hidden>waited on by</dt><dd class="${N.dialogValue}" id="card-dialog-dependents" hidden></dd>
111
120
  <dt id="card-dialog-plan-label" hidden>plan</dt><dd class="${N.dialogValue} ${N.dialogPlan}" id="card-dialog-plan" hidden></dd>
121
+ <dt id="card-dialog-goal-label" hidden>goal</dt><dd class="${N.dialogValue}" id="card-dialog-goal" hidden></dd>
122
+ <dt id="card-dialog-acceptance-label" hidden>acceptance</dt><dd class="${N.dialogValue}" id="card-dialog-acceptance" hidden></dd>
123
+ <dt id="card-dialog-scope-out-label" hidden>scope out</dt><dd class="${N.dialogValue}" id="card-dialog-scope-out" hidden></dd>
124
+ <dt id="card-dialog-approved-label" hidden>approved</dt><dd class="${N.dialogValue}" id="card-dialog-approved" hidden></dd>
125
+ <dt id="card-dialog-reviewed-label" hidden>reviewed</dt><dd class="${N.dialogValue}" id="card-dialog-reviewed" hidden></dd>
112
126
  <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>
113
127
  </dl>
114
128
  </section>
@@ -121,7 +135,7 @@ ${docHead({ title: tabTitle("board"), css: BOARD_CSS, base })}
121
135
  </div>
122
136
  </div>
123
137
 
124
- ${keysSheet(BOARD_KEYS)}
138
+ ${keysSheet(BOARD_KEYS, "A card dialog keeps /, t, and ? for itself; Esc closes it.")}
125
139
  <script src="${base}/app.js" defer></script>
126
140
  </body>
127
141
  </html>