@gavin-hu/my-pi-agent 0.20.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 (298) hide show
  1. package/README.md +46 -30
  2. package/README.zh-CN.md +40 -25
  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 -23
  9. package/docs/extensions/board.md +397 -168
  10. package/docs/extensions/bytefly.md +234 -197
  11. package/docs/extensions/check.md +15 -17
  12. package/docs/extensions/context.md +35 -15
  13. package/docs/extensions/file-browse.md +57 -63
  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 +197 -140
  20. package/docs/extensions/question.md +16 -18
  21. package/docs/extensions/rewind.md +17 -19
  22. package/docs/extensions/serve.md +145 -0
  23. package/docs/extensions/session-bus.md +38 -16
  24. package/docs/extensions/subagent.md +23 -25
  25. package/docs/extensions/team.md +83 -44
  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 +79 -25
  32. package/extensions/board/format.ts +43 -24
  33. package/extensions/board/index.ts +17 -4
  34. package/extensions/board/input.ts +4 -9
  35. package/extensions/board/join.ts +5 -20
  36. package/extensions/board/lazy-page.ts +40 -0
  37. package/extensions/board/page.ts +65 -93
  38. package/extensions/board/result.ts +30 -39
  39. package/extensions/board/rows.ts +62 -25
  40. package/extensions/board/schema.ts +49 -11
  41. package/extensions/board/snapshot.ts +8 -6
  42. package/extensions/board/tone.ts +22 -0
  43. package/extensions/board/tools.ts +154 -55
  44. package/extensions/board/transport/router.ts +3 -5
  45. package/extensions/board/transport/server.ts +59 -28
  46. package/extensions/board/types.ts +36 -42
  47. package/extensions/board/view/card.ts +229 -0
  48. package/extensions/board/view/client.ts +35 -16
  49. package/extensions/board/view/dialog.ts +169 -6
  50. package/extensions/board/view/empty.ts +47 -0
  51. package/extensions/board/view/keys.ts +13 -2
  52. package/extensions/board/view/live.ts +90 -28
  53. package/extensions/board/view/names.ts +13 -4
  54. package/extensions/board/view/page.ts +38 -13
  55. package/extensions/board/view/render.ts +219 -277
  56. package/extensions/board/view/styles.ts +130 -32
  57. package/extensions/board/view/team.ts +297 -76
  58. package/extensions/bytefly/block/fallback.ts +1 -1
  59. package/extensions/bytefly/doctor/runtime.ts +5 -2
  60. package/extensions/bytefly/footer/collect/label.ts +60 -0
  61. package/extensions/bytefly/footer/collect/snapshot.ts +8 -1
  62. package/extensions/bytefly/footer/config.ts +7 -8
  63. package/extensions/bytefly/footer/register.ts +33 -4
  64. package/extensions/bytefly/footer/render/footer.ts +18 -4
  65. package/extensions/bytefly/footer/render/key.ts +46 -0
  66. package/extensions/bytefly/footer/render/layout.ts +13 -3
  67. package/extensions/bytefly/footer/render/lines.ts +10 -7
  68. package/extensions/bytefly/footer/render/segments.ts +41 -38
  69. package/extensions/bytefly/footer/render/status.ts +0 -11
  70. package/extensions/bytefly/footer/types.ts +7 -0
  71. package/extensions/bytefly/header/component.ts +4 -20
  72. package/extensions/bytefly/header/config.ts +1 -1
  73. package/extensions/bytefly/index.ts +32 -6
  74. package/extensions/bytefly/mode/editor.ts +115 -0
  75. package/extensions/bytefly/panel/commands.ts +210 -120
  76. package/extensions/bytefly/panel/format.ts +4 -4
  77. package/extensions/bytefly/panel/settings.ts +73 -6
  78. package/extensions/bytefly/panel/tui/keys.ts +2 -6
  79. package/extensions/bytefly/update/cache.ts +1 -1
  80. package/extensions/bytefly/update/register.ts +4 -4
  81. package/extensions/bytefly/working/config.ts +5 -7
  82. package/extensions/bytefly/working/format.ts +10 -13
  83. package/extensions/bytefly/working/register.ts +22 -27
  84. package/extensions/check/commands.ts +3 -2
  85. package/extensions/check/render.ts +10 -1
  86. package/extensions/check/{tui.ts → view/check-screens.ts} +14 -19
  87. package/extensions/context/commands.ts +2 -2
  88. package/extensions/context/{tui.ts → view/context-report.ts} +51 -19
  89. package/extensions/file-browse/config/config.ts +4 -19
  90. package/extensions/file-browse/index.ts +89 -79
  91. package/extensions/file-browse/live/live.ts +1 -1
  92. package/extensions/file-browse/transport/router.ts +12 -4
  93. package/extensions/file-browse/transport/server.ts +61 -28
  94. package/extensions/file-browse/view/chrome.ts +4 -4
  95. package/extensions/file-browse/view/client.ts +3 -1
  96. package/extensions/file-browse/view/html.ts +73 -40
  97. package/extensions/file-browse/view/live-client.ts +2 -2
  98. package/extensions/file-browse/view/markdown.ts +13 -14
  99. package/extensions/file-browse/view/palette.ts +2 -2
  100. package/extensions/file-browse/view/raw-html.ts +9 -9
  101. package/extensions/file-browse/view/tree.ts +1 -1
  102. package/extensions/file-browse/view/urls.ts +20 -6
  103. package/extensions/file-read/render.ts +4 -4
  104. package/extensions/goal/plan-route.ts +2 -2
  105. package/extensions/goal/tui.ts +12 -2
  106. package/extensions/job/child.ts +527 -0
  107. package/extensions/job/clock.ts +142 -0
  108. package/extensions/job/commands.ts +4 -2
  109. package/extensions/job/config.ts +8 -1
  110. package/extensions/job/format.ts +22 -1
  111. package/extensions/job/log-writer.ts +13 -2
  112. package/extensions/job/registry.ts +63 -7
  113. package/extensions/job/render.ts +34 -0
  114. package/extensions/job/runtime.ts +296 -655
  115. package/extensions/job/session.ts +132 -19
  116. package/extensions/job/store.ts +7 -7
  117. package/extensions/job/tools.ts +21 -9
  118. package/extensions/job/types.ts +47 -0
  119. package/extensions/job/{tui.ts → view/jobs-screen.ts} +19 -99
  120. package/extensions/job/waiters.ts +44 -1
  121. package/extensions/memory/commands.ts +4 -2
  122. package/extensions/memory/{tui.ts → view/memory-picker.ts} +12 -12
  123. package/extensions/plan/commands.ts +12 -9
  124. package/extensions/plan/feedback.ts +62 -16
  125. package/extensions/plan/format.ts +8 -2
  126. package/extensions/plan/git-bash.ts +115 -16
  127. package/extensions/plan/index.ts +58 -51
  128. package/extensions/plan/plan-index.ts +4 -2
  129. package/extensions/plan/plan-text.ts +114 -0
  130. package/extensions/plan/plans.ts +5 -54
  131. package/extensions/plan/policy.ts +8 -10
  132. package/extensions/plan/review-lazy.ts +85 -0
  133. package/extensions/plan/review.ts +2 -2
  134. package/extensions/plan/runtime.ts +61 -41
  135. package/extensions/plan/sections.ts +1 -1
  136. package/extensions/plan/steps.ts +3 -3
  137. package/extensions/plan/todos.ts +1 -1
  138. package/extensions/plan/tools/cancel.ts +27 -62
  139. package/extensions/plan/tools/enter.ts +55 -81
  140. package/extensions/plan/tools/exit.ts +221 -238
  141. package/extensions/plan/tools/refusal.ts +18 -0
  142. package/extensions/plan/tools/steps.ts +75 -92
  143. package/extensions/plan/tools/write.ts +77 -120
  144. package/extensions/plan/tools.ts +188 -30
  145. package/extensions/plan/tui.ts +5 -5
  146. package/extensions/plan/types.ts +48 -5
  147. package/extensions/plan/web-blocks.ts +76 -5
  148. package/extensions/plan/web-chrome.ts +4 -0
  149. package/extensions/plan/web-client.ts +102 -2
  150. package/extensions/plan/web-collab.ts +4 -4
  151. package/extensions/plan/web-comments.ts +39 -8
  152. package/extensions/plan/web-composer.ts +14 -3
  153. package/extensions/plan/web-keys.ts +149 -25
  154. package/extensions/plan/web-lazy.ts +53 -0
  155. package/extensions/plan/web-markdown.ts +10 -68
  156. package/extensions/plan/web-outline.ts +22 -2
  157. package/extensions/plan/web-page.ts +56 -86
  158. package/extensions/plan/web-payload.ts +188 -0
  159. package/extensions/plan/web-plan.ts +23 -13
  160. package/extensions/plan/web-router.ts +18 -9
  161. package/extensions/plan/web-settle.ts +14 -7
  162. package/extensions/plan/web-share-session.ts +24 -55
  163. package/extensions/plan/web-share.ts +19 -37
  164. package/extensions/plan/web-write.ts +112 -0
  165. package/extensions/plan/web.ts +67 -131
  166. package/extensions/question/tools.ts +6 -2
  167. package/extensions/question/{tui.ts → view/question-screen.ts} +11 -11
  168. package/extensions/rewind/commands/commands.ts +6 -3
  169. package/extensions/rewind/state/runtime.ts +1 -1
  170. package/extensions/rewind/{view/status.ts → status.ts} +4 -4
  171. package/extensions/rewind/view/review-tui.ts +4 -4
  172. package/extensions/serve/config.ts +39 -0
  173. package/extensions/serve/index.ts +85 -0
  174. package/extensions/serve/registry.ts +260 -0
  175. package/extensions/session-bus/commands.ts +11 -6
  176. package/extensions/session-bus/join-request.ts +17 -9
  177. package/extensions/session-bus/mail-watch.ts +26 -17
  178. package/extensions/session-bus/protocol.ts +15 -3
  179. package/extensions/session-bus/resolve.ts +5 -7
  180. package/extensions/session-bus/result.ts +1 -27
  181. package/extensions/session-bus/runtime.ts +40 -12
  182. package/extensions/session-bus/store.ts +101 -19
  183. package/extensions/session-bus/tools.ts +5 -4
  184. package/extensions/session-bus/types.ts +19 -0
  185. package/extensions/session-bus/{tui.ts → view/session-list.ts} +16 -16
  186. package/extensions/subagent/agents.ts +1 -1
  187. package/extensions/subagent/commands.ts +4 -2
  188. package/extensions/subagent/format.ts +15 -15
  189. package/extensions/subagent/index.ts +1 -1
  190. package/extensions/subagent/render.ts +3 -3
  191. package/extensions/subagent/{background-tui.ts → view/agents-board.ts} +11 -11
  192. package/extensions/team/commands.ts +24 -5
  193. package/extensions/team/dispatch.ts +18 -4
  194. package/extensions/team/format.ts +17 -2
  195. package/extensions/team/index.ts +9 -8
  196. package/extensions/team/schema.ts +6 -5
  197. package/extensions/team/tools.ts +107 -45
  198. package/extensions/team/types.ts +3 -2
  199. package/extensions/todo/commands.ts +4 -2
  200. package/extensions/todo/format.ts +11 -0
  201. package/extensions/todo/render.ts +57 -0
  202. package/extensions/todo/runtime.ts +1 -1
  203. package/extensions/todo/tools.ts +1 -1
  204. package/extensions/todo/{tui.ts → view/todo-screen.ts} +18 -90
  205. package/extensions/todo/widget.ts +47 -0
  206. package/extensions/watch/commands.ts +4 -2
  207. package/extensions/watch/format.ts +36 -7
  208. package/extensions/watch/render.ts +57 -0
  209. package/extensions/watch/runtime.ts +27 -22
  210. package/extensions/watch/tools.ts +4 -4
  211. package/extensions/watch/{tui.ts → view/watch-screen.ts} +18 -80
  212. package/extensions/web-access/fetch/tool.ts +12 -4
  213. package/extensions/wechat/commands/commands.ts +18 -14
  214. package/extensions/wechat/index.ts +72 -37
  215. package/extensions/wechat/state/credentials.ts +1 -1
  216. package/extensions/wechat/state/state.ts +1 -1
  217. package/extensions/wechat/text/status.ts +112 -0
  218. package/extensions/wechat/types.ts +2 -2
  219. package/extensions/wechat/view/status-screen.ts +4 -103
  220. package/extensions/worktree/commands.ts +11 -9
  221. package/extensions/worktree/enter.ts +2 -2
  222. package/extensions/worktree/exit.ts +2 -2
  223. package/extensions/worktree/index.ts +5 -5
  224. package/extensions/worktree/screen.ts +3 -3
  225. package/extensions/worktree/status.ts +2 -2
  226. package/extensions/worktree/tools.ts +113 -116
  227. package/lib/README.md +28 -13
  228. package/lib/board/actions.ts +111 -0
  229. package/lib/board/artifact.ts +149 -0
  230. package/lib/board/card.ts +163 -52
  231. package/lib/board/config.ts +58 -0
  232. package/lib/board/contract.ts +113 -0
  233. package/lib/board/join.ts +38 -0
  234. package/lib/board/lanes.ts +182 -0
  235. package/lib/board/lead.ts +130 -0
  236. package/lib/board/limits.ts +78 -0
  237. package/lib/board/links.ts +31 -10
  238. package/lib/board/page-pointer.ts +30 -5
  239. package/lib/board/read.ts +83 -0
  240. package/lib/board/reason.ts +102 -0
  241. package/lib/board/round-text.ts +68 -6
  242. package/lib/board/round.ts +31 -15
  243. package/lib/board/rules.ts +151 -307
  244. package/lib/board/seat.ts +35 -0
  245. package/lib/board/session.ts +199 -51
  246. package/lib/board/skipped.ts +22 -0
  247. package/lib/board/steps.ts +487 -0
  248. package/lib/board/store.ts +119 -36
  249. package/lib/board/tone.ts +92 -0
  250. package/lib/board/view.ts +81 -63
  251. package/lib/bus-request.ts +83 -0
  252. package/{extensions/watch → lib}/child.ts +40 -21
  253. package/lib/env.ts +65 -7
  254. package/lib/format.ts +13 -0
  255. package/lib/http/index.ts +17 -11
  256. package/lib/http/mount.ts +142 -0
  257. package/lib/http/port.ts +30 -0
  258. package/lib/http/serve.ts +170 -46
  259. package/lib/http/server.ts +3 -3
  260. package/lib/lazy.ts +45 -0
  261. package/lib/plan-entry.ts +46 -21
  262. package/lib/plans/paths.ts +82 -0
  263. package/lib/policy.ts +9 -6
  264. package/lib/report.ts +3 -2
  265. package/lib/requirements.ts +10 -10
  266. package/lib/session-label.ts +129 -7
  267. package/lib/session-roster.ts +43 -1
  268. package/lib/session-tool.ts +39 -22
  269. package/lib/stray-fields.ts +7 -2
  270. package/lib/tool-names.ts +5 -10
  271. package/lib/tui/README.md +29 -11
  272. package/lib/tui/chrome.ts +16 -15
  273. package/lib/tui/cursor.ts +18 -3
  274. package/lib/tui/glyphs.ts +11 -11
  275. package/lib/tui/render-cache.ts +29 -0
  276. package/lib/tui/tokens.ts +33 -2
  277. package/lib/tui/tool.ts +33 -4
  278. package/lib/web/README.md +21 -0
  279. package/lib/web/client-runtime.ts +27 -1
  280. package/lib/web/components.ts +5 -1
  281. package/lib/web/index.ts +1 -0
  282. package/lib/web/markdown.ts +83 -0
  283. package/lib/web/markup.ts +20 -2
  284. package/lib/web/runtime.ts +9 -10
  285. package/package.json +8 -2
  286. package/skills/init/SKILL.md +5 -5
  287. package/skills/next/SKILL.md +3 -5
  288. package/extensions/board/config.ts +0 -36
  289. package/extensions/board/seat.ts +0 -28
  290. package/lib/tui/index.ts +0 -34
  291. package/skills/project-docs/SKILL.md +0 -133
  292. package/skills/project-docs/references/audit.md +0 -131
  293. package/skills/project-docs/references/changelog.md +0 -105
  294. package/skills/project-docs/references/discovery.md +0 -127
  295. package/skills/project-docs/references/readme.md +0 -80
  296. package/skills/project-docs/references/release.md +0 -84
  297. package/skills/project-docs/references/roadmap.md +0 -93
  298. package/skills/project-docs/scripts/state.ts +0 -316
@@ -6,14 +6,58 @@
6
6
  * follows the theme and `lib/web/web.test.ts`'s "draws every colour through a token"
7
7
  * rule holds without an exception here. Colour is a role: a lane's dot, its head,
8
8
  * and the reason line read `--muted`, `--accent`, `--danger`, and `--success`
9
- * through `data-status`, never a literal.
9
+ * through `data-lane`, never a literal.
10
10
  *
11
11
  * The sheet is in three parts, in the order the page stacks: the toolbar, the
12
12
  * board (lanes, cards, and the Team tables), then its dialogs.
13
13
  */
14
14
 
15
+ import { LANE_RULES, LANES } from "../../../lib/board/lanes.ts";
16
+ import { BOARD_TONES } from "../../../lib/board/tone.ts";
15
17
  import { WEB_BASE_CSS, WEB_COMPONENTS_CSS, WEB_LAYOUT_CSS, WEB_THEME_CSS } from "../../../lib/web/index.ts";
16
18
 
19
+ /**
20
+ * A lane's colour, from the lane table: one role per lane, worn by the dot on its head and
21
+ * by the same dot in a card's dialog.
22
+ *
23
+ * Generated rather than written five times, so a lane added to `LANES` arrives with its
24
+ * colour and the two rules cannot drift from the table's `role`.
25
+ */
26
+ const LANE_ROLE_CSS = LANES.flatMap((lane) => {
27
+ const role = LANE_RULES[lane].role;
28
+ return [
29
+ `.board-lane[data-lane="${lane}"] .board-lane__dot{background:var(--${role})}`,
30
+ `.board-dialog__state[data-lane="${lane}"]::before{background:var(--${role})}`,
31
+ ];
32
+ }).join("\n");
33
+
34
+ /**
35
+ * A reason line's colour, one rule per tone the tone table names.
36
+ *
37
+ * The card face picks a tone from the reason's kind; it never picks a class named after the
38
+ * kind, so a card owing a spot-check is not drawn with a rule that says "unplanned".
39
+ */
40
+ const REASON_TONE_CSS = BOARD_TONES.map((tone) => `.board-card__reason--${tone}{color:var(--${tone})}`).join("\n");
41
+
42
+ /**
43
+ * A decision row's edge, one rule per tone the table names.
44
+ *
45
+ * Generated from the same table the terminal reads, so `gone` — ranked above `idle` — wears
46
+ * the refusal colour by being classified, not by a rule somebody remembered to add.
47
+ */
48
+ const DECISION_TONE_CSS = BOARD_TONES.map(
49
+ (tone) => `.board-team__decision[data-tone="${tone}"]{border-left-color:var(--${tone})}`,
50
+ ).join("\n");
51
+
52
+ /**
53
+ * The one tone whose sentence is coloured too.
54
+ *
55
+ * A danger row is a refusal or a loss, and that is the row's whole point; a warn row is
56
+ * somebody to ask, and colouring its sentence as well would spend the reader's attention on
57
+ * the words rather than on the edge that says "look here".
58
+ */
59
+ const DECISION_WHY_CSS = `.board-team__decision[data-tone="danger"] .board-team__why{color:var(--danger)}`;
60
+
17
61
  /** The shared shell, the components, and the board's own layout. */
18
62
  export const BOARD_CSS = `${WEB_BASE_CSS}
19
63
  ${WEB_THEME_CSS}
@@ -31,12 +75,16 @@ ${WEB_COMPONENTS_CSS}
31
75
  .board-picker{display:none;margin-bottom:var(--space-3)}
32
76
 
33
77
  /* The main pane is this page's only child of the shell, so it grows to fill the
34
- column and the footer stays at the bottom however short the board is. */
35
- .board-main{flex:1 1 auto}
78
+ column and the footer stays at the bottom however short the board is. Its top
79
+ padding moves to the panels: a sticky head pins to the scroll box's padding
80
+ edge, so a gutter above it is a strip a card scrolls through unheaded. */
81
+ .board-main{flex:1 1 auto;padding-top:0}
36
82
  /* One panel per tab; the shell's main pane is the only scroll box. */
37
- .board-panel{display:flex;flex-direction:column;min-width:0}
83
+ .board-panel{display:flex;flex-direction:column;min-width:0;padding-top:var(--space-5)}
38
84
  .board-panel[hidden]{display:none}
39
85
  .board-no-cards{max-width:32rem;margin:auto;padding:var(--space-8) var(--space-6)}
86
+ /* One way back out of a filter that matched nothing, under the sentence that says so. */
87
+ .board-no-cards .bf-btn{margin-top:var(--space-2)}
40
88
 
41
89
  /* Lanes: trays of cards, each with a head that sticks while the pane scrolls. A lane is
42
90
  sized by the work it holds rather than by its share of the row, so one holding nothing
@@ -50,44 +98,75 @@ ${WEB_COMPONENTS_CSS}
50
98
  one — and the floor is the narrowest a tray is still a tray at. It is set on the two
51
99
  states that draw one, so a rail keeps its own width, and both breakpoints below
52
100
  replace the basis, so the floor only ever applies to the row it was chosen for:
53
- four lanes at 1180px are 60rem, which is inside the row before the wrap. */
54
- .board-lane[data-empty="false"][data-folded="false"]{min-width:15rem}
101
+ five lanes at 1180px are 60rem, which is inside the row before the wrap. */
102
+ .board-lane[data-empty="false"][data-folded="false"]{min-width:12rem}
103
+ /* Except finished work: it is history, and a lane of five finished cards used to take
104
+ half the row while the one card still being worked sat on the floor and elided its
105
+ own title. A done lane draws as if it held three cards, however many it holds. */
106
+ .board-lane[data-lane="done"]{flex-grow:calc(1 + min(3, var(--lane-count)))}
55
107
  /* The rail: a dot, a word, and a count — the fact that the lane is showing nothing, not a
56
108
  tray. A lane is a rail when it holds no cards and when it is folded, because both say
57
109
  the same thing: there is nothing here to read. */
58
110
  .board-lane[data-empty="true"],.board-lane[data-folded="true"]{flex:0 0 8.5rem}
59
111
  .board-lane__head{position:sticky;top:0;z-index:1;display:flex;align-items:center;gap:var(--space-2);margin:0 calc(-1 * var(--space-2)) var(--space-1);padding:var(--space-2) var(--space-2);background:var(--surface);border-bottom:1px solid var(--border);border-radius:var(--radius-lg) var(--radius-lg) 0 0;font:var(--fw-semibold) var(--fs-xs) var(--font-ui);letter-spacing:.06em;text-transform:uppercase;color:var(--muted)}
60
- .board-lane__toggle{appearance:none;display:flex;align-items:center;gap:var(--space-2);border:0;background:none;padding:0;font:inherit;letter-spacing:inherit;text-transform:inherit;color:inherit;cursor:pointer}
112
+ /* The fold control is the only thing in the head that acts, and a 16px line is a poor target:
113
+ the padding gives it 24px without growing the row it sits in. */
114
+ .board-lane__toggle{appearance:none;display:flex;align-items:center;gap:var(--space-2);border:0;background:none;padding:4px 0;margin:-4px 0;min-height:24px;font:inherit;letter-spacing:inherit;text-transform:inherit;color:inherit;cursor:pointer}
61
115
  .board-lane__toggle:hover{color:var(--accent)}
62
116
  .board-lane__toggle::after{content:"▾";margin-left:2px;font-size:var(--fs-sm);color:var(--muted)}
63
117
  .board-lane__toggle:hover::after{color:var(--accent)}
64
118
  .board-lane__toggle[aria-expanded="false"]::after{content:"▸"}
65
119
  .board-lane__count{font-variant-numeric:tabular-nums;color:var(--muted);background:var(--canvas);border-radius:var(--radius-pill);padding:0 var(--space-2);font-size:var(--fs-xs);letter-spacing:0}
66
120
  .board-lane__dot{width:var(--space-2);height:var(--space-2);border-radius:var(--radius-pill);background:var(--muted)}
67
- .board-lane[data-status="doing"] .board-lane__dot{background:var(--accent)}
68
- .board-lane[data-status="blocked"] .board-lane__dot{background:var(--danger)}
69
- .board-lane[data-status="done"] .board-lane__dot{background:var(--success)}
121
+ ${LANE_ROLE_CSS}
122
+ ${DECISION_TONE_CSS}
123
+ ${DECISION_WHY_CSS}
70
124
  .board-lane__list{display:flex;flex-direction:column;gap:var(--space-3);margin:0;padding:0;list-style:none}
71
- .board-lane__empty{margin:0;font:var(--fs-sm) var(--font-ui);color:var(--dim)}
125
+ .board-lane__empty{margin:0;font:var(--fs-sm) var(--font-ui);color:var(--muted)}
72
126
 
73
127
  /* A card: the title, then the facts a reader needs to act on it. Its lane says
74
128
  which state it is in, so the card itself carries no second colour bar. */
75
- .board-card{display:flex;flex-direction:column;gap:var(--space-2);padding:var(--space-3);border:1px solid var(--border);border-radius:var(--radius-md);background:var(--canvas);transition:border-color var(--motion-fast) var(--motion-ease),transform var(--motion-fast) var(--motion-ease)}
129
+ .board-card{display:flex;flex-direction:column;gap:var(--space-2);padding:var(--space-3);border:1px solid var(--border);border-radius:var(--radius-md);background:var(--canvas);cursor:pointer;transition:border-color var(--motion-fast) var(--motion-ease),transform var(--motion-fast) var(--motion-ease)}
76
130
  .board-card:hover{border-color:var(--border-strong);transform:translateY(-1px)}
77
131
  .board-card[hidden]{display:none}
78
132
  .board-card__head{display:flex;align-items:baseline;gap:var(--space-2);margin:0;min-width:0}
79
133
  .board-card__id{font:var(--fs-xs) var(--font-mono);color:var(--dim)}
80
- .board-card__title{margin:0;padding:0;border:0;background:none;text-align:left;cursor:pointer;font:var(--fw-semibold) var(--fs-base)/var(--lh-snug) var(--font-ui);color:var(--text);display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
134
+ .board-card__title{margin:0;padding:0;border:0;background:none;text-align:left;cursor:pointer;font:var(--fw-semibold) var(--fs-base)/var(--lh-snug) var(--font-ui);color:var(--text);display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;overflow-wrap:anywhere}
81
135
  .board-card__title:hover{color:var(--accent)}
82
136
  /* Finished work recedes: it is history, and the live lanes should not compete with it. */
83
- .board-card[data-status="done"] .board-card__title{color:var(--muted)}
84
- .board-card[data-status="done"] .board-card__title:hover{color:var(--accent)}
85
- .board-card[data-status="done"] .bf-chip--accent{color:var(--muted);border-color:var(--border)}
137
+ .board-card[data-lane="done"] .board-card__title{color:var(--muted)}
138
+ .board-card[data-lane="done"] .board-card__title:hover{color:var(--accent)}
139
+ .board-card[data-lane="done"] .bf-chip--accent{color:var(--muted);border-color:var(--border)}
86
140
  .board-card__meta{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-2);margin:0;font:var(--fs-xs) var(--font-ui);color:var(--muted)}
87
- .board-card__paths{display:flex;flex-wrap:wrap;gap:var(--space-1);margin:0;padding:0;list-style:none}
141
+ /* How much history the card carries and how recent it is: a count and a stamp, in the
142
+ meta line's own voice, so a card with ten notes cannot look like one with none. */
143
+ .board-card__notes{display:inline-flex;align-items:baseline;gap:var(--space-1);font-variant-numeric:tabular-nums}
144
+ .board-card__paths{display:flex;flex-wrap:wrap;gap:var(--space-1) var(--space-2);margin:0;padding:0;list-style:none}
145
+ /* A chip is one line and nowrap by design, so an owner label or a path longer than the card
146
+ would spill past its border and across the lane beside it: clipped to the card instead,
147
+ with the whole of it on the hover title the renderer sets. The list item has to be the one
148
+ that shrinks, because a flex item's automatic minimum is its chip's width. */
149
+ .board-card__paths li{min-width:0;max-width:100%}
150
+ .board-card .bf-chip{display:block;max-width:100%;overflow:hidden;text-overflow:ellipsis}
151
+ /* A path is something a reader types, so it wears the mono face the dialog's ladder uses. */
152
+ .board-card__paths .bf-chip{font-family:var(--font-mono)}
88
153
  .board-card__reason{margin:0;font:var(--fs-sm)/var(--lh-snug) var(--font-ui);color:var(--muted);display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;overflow-wrap:anywhere}
89
- .board-card__reason--blocked{color:var(--danger)}
90
- .board-card__reason--unplanned{color:var(--warn)}
154
+ /* The tone rules come *after* the base rule, and that is load-bearing: a tone class is one
155
+ class and so is the base, so the later of two equal selectors wins. Generated before it,
156
+ every tone was inert and a blocked card's reason drew muted — the one thing this table
157
+ exists to stop. The pages suite pins the order, not just the rule's presence. */
158
+ ${REASON_TONE_CSS}
159
+
160
+ /* The other side of a link: what leaving this card undone holds up. It is a cost and not a
161
+ verdict, so it stays in the card's own voice: warn already means "a reader has to decide",
162
+ and a card can hold others up while nothing about it needs deciding. */
163
+ .board-card__holds{margin:0;font:var(--fs-sm)/var(--lh-snug) var(--font-ui);color:var(--muted);display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;overflow-wrap:anywhere}
164
+ /* Where the reader has been: j/k walks the drawn cards and wraps, so a lane walked and
165
+ come back to looked exactly like a lane arriving for the first time. A read card keeps
166
+ a quiet edge and the one the reader is on wears the accent one — after the hover rule,
167
+ so the mark outlives the pointer that made it. */
168
+ .board-card[data-visited="true"]{box-shadow:inset 3px 0 0 var(--border-strong)}
169
+ .board-card[data-current="true"]{border-color:var(--accent);box-shadow:inset 3px 0 0 var(--accent)}
91
170
 
92
171
  /* The Team tab: a queue and a roster side by side, then one quiet strip. */
93
172
  .board-team{display:grid;gap:var(--space-5);min-width:0}
@@ -99,19 +178,20 @@ ${WEB_COMPONENTS_CSS}
99
178
  /* The queue: one row per card, its kind as an edge, its way out in the foot. */
100
179
  .board-team__queue{display:flex;flex-direction:column;gap:var(--space-2);min-width:0}
101
180
  /* The queue is the tallest thing here and it can be unbounded; capping it keeps the two
102
- panels on one screen. Stacked below 1180px there is no second column to balance, and a
103
- nested scroll on a phone is worse than the page's own. */
181
+ panels on one screen. The roster can be unbounded the same way — a board with a long
182
+ history names an owner per label that ever claimed a card — so it takes the same cap and
183
+ the same shade. Stacked below 1180px there is no second column to balance, and a nested
184
+ scroll on a phone is worse than the page's own. */
104
185
  @media (min-width:1181px){
105
186
  /* A row cut at the edge reads as a bug unless something says the list continues, and a
106
187
  mac's scrollbars are hidden until it moves: an inset shade in the page's own colour
107
188
  marks the edge without a literal. */
108
189
  /* A floor, because a short window must not turn three rows into a scroll box. */
109
- .board-team__queue{max-height:max(18rem,calc(100dvh - 16rem));overflow:auto;overscroll-behavior:contain;padding-right:var(--space-1);box-shadow:inset 0 calc(-1 * var(--space-6)) var(--space-6) calc(-1 * var(--space-6)) var(--canvas)}
190
+ .board-team__queue,.board-team__roster{max-height:max(18rem,calc(100dvh - 16rem));overflow:auto;overscroll-behavior:contain;padding-right:var(--space-1);box-shadow:inset 0 calc(-1 * var(--space-6)) var(--space-6) calc(-1 * var(--space-6)) var(--canvas)}
110
191
  }
111
192
  .board-team__decision{display:grid;grid-template-columns:minmax(0,1fr) auto;grid-template-areas:"open foot" "why why";align-items:center;gap:var(--space-1) var(--space-3);padding:var(--space-2) var(--space-3);background:var(--raised);border:1px solid var(--border);border-left:3px solid var(--dim);border-radius:var(--radius-md);transition:border-color var(--motion-base) var(--motion-ease),transform var(--motion-base) var(--motion-ease)}
112
193
  .board-team__decision:hover{border-color:var(--border-strong);transform:translateY(-1px)}
113
- .board-team__decision[data-kind="blocked"]{border-left-color:var(--danger)}
114
- .board-team__decision[data-kind="idle"]{border-left-color:var(--warn)}
194
+ /* The edge's colour comes from the row's tone, spliced below from the board's tone table. */
115
195
  /* Nobody holds it, so the edge is drawn but not filled: a shape, not a claim. */
116
196
  .board-team__decision[data-kind="unassigned"]{border-left-style:dashed}
117
197
  .board-team__open{grid-area:open;display:flex;align-items:baseline;gap:var(--space-2);appearance:none;border:0;background:none;padding:0;margin:0;text-align:left;cursor:pointer;color:var(--text);font:var(--fw-medium) var(--fs-md) var(--font-ui);min-width:0}
@@ -120,7 +200,6 @@ ${WEB_COMPONENTS_CSS}
120
200
  .board-team__title{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}
121
201
  /* Two lines, then a tooltip: the "why" is the reason the row is on this tab. */
122
202
  .board-team__why{grid-area:why;margin:0;color:var(--muted);font:var(--fs-sm)/var(--lh-snug) var(--font-ui);display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;overflow-wrap:anywhere}
123
- .board-team__why--blocked{color:var(--danger)}
124
203
  .board-team__foot{grid-area:foot;display:flex;align-items:center;justify-content:flex-end;gap:var(--space-2)}
125
204
  /* A row with no owner chip still puts its one action on the row's edge. */
126
205
  .board-team__foot>.bf-btn:only-child{margin-left:auto}
@@ -128,6 +207,9 @@ ${WEB_COMPONENTS_CSS}
128
207
  .board-team__roster{display:flex;flex-direction:column;gap:var(--space-1);margin:0;padding:0;list-style:none;min-width:0}
129
208
  /* No row rule: the caption already delimits the list, and a second hairline beside it
130
209
  was the same pen drawn twice. */
210
+ /* One row is one line: the name shrinks, the metrics take the rest, and a narrow column
211
+ wraps them under the name rather than clipping either. A row that always broke after the
212
+ name cost 56px, which is what made a roster of twenty a wall. */
131
213
  .board-team__mate{display:flex;flex-wrap:wrap;align-items:baseline;gap:var(--space-1) var(--space-2);padding:var(--space-2) 0;min-width:0}
132
214
  .board-team__dot{width:8px;height:8px;border-radius:var(--radius-pill);background:var(--muted);flex:0 0 auto;align-self:center}
133
215
  .board-team__mate[data-state="live"] .board-team__dot{background:var(--success)}
@@ -136,14 +218,25 @@ ${WEB_COMPONENTS_CSS}
136
218
  .board-team__mate[data-state="gone"] .board-team__dot{background:var(--danger)}
137
219
  /* A record that is not there is a hollow ring, never a colour that claims it is. */
138
220
  .board-team__mate[data-state="absent"] .board-team__dot,.board-team__mate[data-state="unknown"] .board-team__dot{background:transparent;box-shadow:inset 0 0 0 1px var(--border-strong)}
139
- .board-team__name{font:var(--fw-medium) var(--fs-md) var(--font-ui);color:var(--text)}
140
- .board-team__mets{flex:1 0 100%;margin:0;color:var(--muted);font:var(--fs-xs) var(--font-ui)}
221
+ .board-team__name{font:var(--fw-medium) var(--fs-md) var(--font-ui);color:var(--text);min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
222
+ .board-team__mets{flex:1 1 auto;min-width:0;margin:0;text-align:right;color:var(--muted);font:var(--fs-xs) var(--font-ui)}
141
223
  .board-team__empty{margin:0;color:var(--muted);font:var(--fs-sm) var(--font-ui)}
224
+ /* The roster's one control: the owners the bus has no record of, counted in one row and
225
+ opened from it. Full width, because it stands for a group of rows rather than a row. */
226
+ .board-team__tail{display:flex;flex:1 1 100%;align-items:center;gap:var(--space-2);appearance:none;border:0;background:none;padding:0;margin:0;min-height:24px;cursor:pointer;text-align:left;color:var(--muted);font:var(--fs-md) var(--font-ui);text-decoration:underline dotted;text-underline-offset:3px}
227
+ .board-team__tail:hover{color:var(--text);text-decoration:underline}
228
+ /* The strip's ids past the sixth: one span, hidden whole, so the count that opens them
229
+ controls a single node. Its contents are the strip's own flex items, which is what gives
230
+ them the strip's gap and wrapping rather than jamming #7#8#9 together. */
231
+ .board-team__ids{display:contents}
232
+ /* The strip's count is its fold control: the number is what says how much is out of sight. */
233
+ .board-team__fold{appearance:none;border:0;background:none;padding:5px 8px;margin:-5px -8px;min-height:24px;cursor:pointer;color:var(--muted);font:var(--fs-sm) var(--font-ui);text-decoration:underline dotted;text-underline-offset:3px}
234
+ .board-team__fold:hover{color:var(--accent);text-decoration:underline}
142
235
  /* Done stays quiet; its ids are buttons only because they open their card. */
143
236
  .board-team__done{margin:0;font:var(--fs-sm) var(--font-ui);color:var(--muted);display:flex;flex-wrap:wrap;align-items:baseline;gap:var(--space-2)}
144
237
  /* An id in the Done strip is a button, so it wears the only affordance it has: a
145
238
  dotted line, visible before the pointer arrives. */
146
- .board-team__id{appearance:none;border:0;background:none;padding:0;margin:0;cursor:pointer;font:var(--fs-sm) var(--font-mono);color:var(--muted);text-decoration:underline dotted;text-underline-offset:3px}
239
+ .board-team__id{appearance:none;border:0;background:none;padding:5px 8px;margin:-5px -8px;min-height:24px;cursor:pointer;font:var(--fs-sm) var(--font-mono);color:var(--muted);text-decoration:underline dotted;text-underline-offset:3px}
147
240
  .board-team__id:hover{color:var(--accent);text-decoration:underline}
148
241
 
149
242
  /* The card dialog: the record of one card, in two named sections. Nothing here scrolls on
@@ -184,12 +277,10 @@ ${WEB_COMPONENTS_CSS}
184
277
  /* Who and when is the footnote of the note above it. */
185
278
  .board-dialog__note-foot{display:flex;align-items:baseline;gap:var(--space-2);margin:0;max-width:68ch}
186
279
  .board-dialog__note-by{font:var(--fw-medium) var(--fs-xs) var(--font-ui);color:var(--muted)}
187
- .board-dialog__note-at{font:var(--fs-xs) var(--font-mono);color:var(--dim)}
280
+ .board-dialog__note-at{font:var(--fs-xs) var(--font-mono);color:var(--muted)}
188
281
  .board-dialog__state{display:inline-flex;align-items:center;gap:var(--space-2);font:var(--fs-xs) var(--font-ui);letter-spacing:.06em;text-transform:uppercase;color:var(--muted)}
189
282
  .board-dialog__state::before{content:"";width:var(--space-2);height:var(--space-2);border-radius:var(--radius-pill);background:var(--muted)}
190
- .board-dialog__state[data-status="doing"]::before{background:var(--accent)}
191
- .board-dialog__state[data-status="blocked"]::before{background:var(--danger)}
192
- .board-dialog__state[data-status="done"]::before{background:var(--success)}
283
+
193
284
  .board-skeleton{height:4.5rem;border:1px dashed var(--border);border-radius:var(--radius-md);background:var(--surface)}
194
285
 
195
286
  @media (max-width:1180px){
@@ -214,6 +305,13 @@ ${WEB_COMPONENTS_CSS}
214
305
  .board-filter{order:9;width:100%}
215
306
  .board-team__foot{flex-wrap:wrap}
216
307
  }
308
+ /* Five lane names do not fit a phone: the strip becomes wrapped pills rather than a
309
+ horizontal scroller that can hide the lane a reader is on. */
310
+ @media (max-width:420px){
311
+ .board-picker{flex-wrap:wrap;border:0;background:none;gap:var(--space-1)}
312
+ .board-picker .bf-segmented__item{border:1px solid var(--border);border-radius:var(--radius-pill);padding:5px 10px}
313
+ .board-picker .bf-segmented__item[aria-pressed="true"]{border-color:var(--accent)}
314
+ }
217
315
  @media (prefers-reduced-motion: reduce){
218
316
  .board-card,.board-team__decision{transition:none}
219
317
  .board-card:hover,.board-team__decision:hover{transform:none}