@huseynsnmz/pi-board 0.15.3 → 0.17.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 (216) hide show
  1. package/CHANGELOG.md +24 -4
  2. package/CONTRIBUTING.md +7 -0
  3. package/README.md +55 -419
  4. package/SECURITY.md +19 -0
  5. package/docs/how-it-works.md +31 -0
  6. package/docs/reference.md +277 -0
  7. package/package.json +5 -11
  8. package/src/commands/agent-board.ts +26 -2
  9. package/src/commands/attach-flow.ts +4 -0
  10. package/src/core/double-press.mjs +6 -2
  11. package/src/core/slash-commands.mjs +52 -0
  12. package/src/index.ts +1 -1
  13. package/src/runtime/service.mjs +12 -0
  14. package/src/ui/dashboard.ts +59 -25
  15. package/src/ui/pty-attach.ts +8 -3
  16. package/IMPLEMENTATION_PLAN.md +0 -920
  17. package/PRD.md +0 -484
  18. package/PROGRESS.md +0 -142
  19. package/VERIFY.md +0 -115
  20. package/docs/BATCH_SELECTION_READ_FLOW.md +0 -277
  21. package/docs/EXPLORATION.md +0 -187
  22. package/docs/PTY_ATTACH_IMPLEMENTATION_PLAN.md +0 -581
  23. package/docs/RELEASE.md +0 -148
  24. package/docs/superpowers/plans/2026-08-15-screenlog-gc.md +0 -704
  25. package/docs/superpowers/plans/2026-08-16-attach-double-cursor-jiggle-retry.md +0 -499
  26. package/docs/superpowers/plans/2026-08-21-attach-coldstart-jiggle-rearm.md +0 -797
  27. package/docs/superpowers/plans/2026-08-21-dashboard-keypress-lag.md +0 -366
  28. package/docs/superpowers/plans/2026-08-21-issue-14-needs-input-no-auto-done.md +0 -576
  29. package/docs/superpowers/plans/2026-08-22-jiggle-shrink-and-hold.md +0 -337
  30. package/docs/superpowers/plans/2026-08-22-launch-cwd-favorites.md +0 -916
  31. package/docs/superpowers/plans/2026-08-22-question-tool-grouping.md +0 -211
  32. package/docs/superpowers/plans/2026-08-22-rename-state-labels.md +0 -182
  33. package/docs/superpowers/plans/2026-08-22-stable-list-order.md +0 -51
  34. package/docs/superpowers/plans/2026-08-27-locks-acquirelock-spin.md +0 -614
  35. package/docs/superpowers/plans/2026-08-27-pty-runner-test-flaky.md +0 -32
  36. package/docs/superpowers/plans/2026-08-29-code-refs-badges.md +0 -223
  37. package/docs/superpowers/plans/2026-08-30-circular-navigation.md +0 -308
  38. package/docs/superpowers/plans/2026-08-30-post-exit-timing-fix.md +0 -37
  39. package/docs/superpowers/plans/2026-08-30-pty-attach-quality-debt.md +0 -311
  40. package/docs/superpowers/plans/2026-08-30-readme-v2.md +0 -294
  41. package/docs/superpowers/plans/2026-09-01-attach-detach-gate-cursor-anchor.md +0 -284
  42. package/docs/superpowers/plans/2026-09-02-attach-detach-editor-state.md +0 -722
  43. package/docs/superpowers/plans/2026-09-03-code-refs-pr-backlink-narrow.md +0 -551
  44. package/docs/superpowers/plans/2026-09-03-detach-gate-glyph-fallback.md +0 -146
  45. package/docs/superpowers/plans/2026-09-04-evidence-outputpreview.md +0 -209
  46. package/docs/superpowers/plans/2026-09-04-warm-host-reclaim.md +0 -796
  47. package/docs/superpowers/plans/2026-09-05-issue-13-drainnextfollowup-pty-probe.md +0 -114
  48. package/docs/superpowers/plans/2026-09-05-issue-38-windows-wezterm-ime-cursor.md +0 -73
  49. package/docs/superpowers/plans/2026-09-05-issue-39-truncate-codepoint-boundary.md +0 -143
  50. package/docs/superpowers/plans/2026-09-05-issue-61-mention-fallback-guards.md +0 -226
  51. package/docs/superpowers/plans/2026-09-05-issue-63-flaky-manual-completion.md +0 -87
  52. package/docs/superpowers/plans/2026-09-05-issue-64-changelog-release-helper.md +0 -53
  53. package/docs/superpowers/plans/2026-09-05-pty-host-stacking-sock-race.md +0 -731
  54. package/docs/superpowers/plans/2026-09-08-attach-ctrl-left-detach.md +0 -30
  55. package/docs/superpowers/plans/2026-09-08-dashboard-shrink-repaint.md +0 -68
  56. package/docs/superpowers/plans/2026-09-08-issue-11-attach-runtime-desync-heal.md +0 -917
  57. package/docs/superpowers/plans/2026-09-08-legacy-stale-host-recovery.md +0 -125
  58. package/docs/superpowers/plans/2026-09-08-spawn-async-error-swallow.md +0 -56
  59. package/docs/superpowers/plans/2026-09-08-stale-model-attach-guard.md +0 -96
  60. package/docs/superpowers/plans/2026-09-09-claimpid-blocks-replace.md +0 -267
  61. package/docs/superpowers/plans/2026-09-09-harden-runner-architecture.md +0 -603
  62. package/docs/superpowers/plans/2026-09-09-single-writer-completion.md +0 -252
  63. package/docs/superpowers/plans/2026-09-10-reader-consistency.md +0 -115
  64. package/docs/superpowers/plans/2026-09-14-attach-cursor-dectcem-gate.md +0 -469
  65. package/docs/superpowers/plans/2026-09-14-attach-snapshot.md +0 -92
  66. package/docs/superpowers/plans/2026-09-14-host-meta-orphan-lock.md +0 -771
  67. package/docs/superpowers/plans/2026-09-14-issue-106-terminal-frame-cognition.md +0 -299
  68. package/docs/superpowers/plans/2026-09-14-issue-113-foreground-preview-race.md +0 -609
  69. package/docs/superpowers/plans/2026-09-14-terminal-model.md +0 -145
  70. package/docs/superpowers/plans/2026-09-15-coordinator-pipe-root-normalize.md +0 -224
  71. package/docs/superpowers/plans/2026-09-15-lease-publish-eprem-reclaim.md +0 -341
  72. package/docs/superpowers/plans/2026-09-18-detach-anchor-reporter-endpoint.md +0 -875
  73. package/docs/superpowers/plans/2026-09-20-control-lifecycle.md +0 -116
  74. package/docs/superpowers/plans/2026-09-20-issue-121-perf-gate-out-of-coverage.md +0 -517
  75. package/docs/superpowers/plans/2026-09-23-a5-mid-stream-assertion.md +0 -25
  76. package/docs/superpowers/plans/2026-09-23-cwd-browse-tab-drill.md +0 -393
  77. package/docs/superpowers/plans/2026-09-23-terminal-query-forwarding.md +0 -51
  78. package/docs/superpowers/plans/2026-09-23-unconditional-detach-key.md +0 -58
  79. package/docs/superpowers/plans/2026-09-24-control-reconcile-flake.md +0 -698
  80. package/docs/superpowers/plans/2026-09-24-probe-attached-clients.md +0 -605
  81. package/docs/superpowers/plans/2026-09-29-issue-145-holding-semantic-state.md +0 -1183
  82. package/docs/superpowers/plans/2026-09-29-issue-145-user-verification.md +0 -66
  83. package/docs/superpowers/plans/2026-09-30-board-delete-key-x-y-confirm.md +0 -649
  84. package/docs/superpowers/plans/2026-09-30-issue-153-runner-stop-boot-window.md +0 -618
  85. package/docs/superpowers/plans/2026-09-30-issue-95-test-discipline.md +0 -555
  86. package/docs/superpowers/plans/2026-09-30-publish-dispatch-tag-check.md +0 -259
  87. package/docs/superpowers/plans/2026-10-06-attach-settle-scheme-delivery.md +0 -751
  88. package/docs/superpowers/plans/2026-10-09-issue-167-attach-middle-paste.md +0 -509
  89. package/docs/superpowers/plans/2026-10-09-issue-169-fullscreen-mouse-ownership.md +0 -346
  90. package/docs/superpowers/plans/2026-10-10-attach-delete-in-place.md +0 -832
  91. package/docs/superpowers/plans/2026-10-10-issue-174-selection-mirror-paste-source.md +0 -510
  92. package/docs/superpowers/plans/2026-10-14-issue-170-attach-reply-guard.md +0 -771
  93. package/docs/superpowers/plans/2026-10-14-issue-178-accept-registration-race.md +0 -1257
  94. package/docs/superpowers/plans/2026-10-14-issue-184-snapshot-window-contract.md +0 -657
  95. package/docs/superpowers/plans/2026-10-14-issue-185-attach-wheel-alt-screen.md +0 -47
  96. package/docs/superpowers/plans/2026-10-14-issue-187-motionless-drag.md +0 -931
  97. package/docs/superpowers/plans/2026-10-14-issue-189-mixed-input-segments.md +0 -823
  98. package/docs/superpowers/plans/2026-10-15-issue-188-x10-mouse-release.md +0 -526
  99. package/docs/superpowers/plans/2026-10-16-issue-198-attach-wheel-forward-fallback.md +0 -32
  100. package/docs/superpowers/plans/2026-10-18-attach-wheel-replay-pseudo-scrollback.md +0 -437
  101. package/docs/superpowers/plans/2026-10-19-issue-213-reply-guard-csi-key.md +0 -257
  102. package/docs/superpowers/plans/2026-10-19-issue-214-attach-forward-alt-modifier.md +0 -202
  103. package/docs/superpowers/plans/2026-10-21-issue-221-ask-user-question.md +0 -198
  104. package/docs/superpowers/plans/2026-10-23-issue-222-attach-ready.md +0 -290
  105. package/docs/superpowers/plans/2026-10-23-issue-225-monotonic-foreground-state.md +0 -450
  106. package/docs/superpowers/plans/2026-10-23-issue-229-vim-jkl.md +0 -449
  107. package/docs/superpowers/plans/2026-10-23-issue-232-attach-bg-only-cells.md +0 -153
  108. package/docs/superpowers/plans/2026-10-23-lease-heartbeat-staleness.md +0 -537
  109. package/docs/superpowers/plans/2026-10-23-selected-row-highlight.md +0 -405
  110. package/docs/superpowers/plans/2026-10-24-issue-217-windows-pid-reuse-stopping.md +0 -367
  111. package/docs/superpowers/plans/2026-10-24-issue-230-warm-host-background-runs.md +0 -806
  112. package/docs/superpowers/plans/2026-10-24-issue-238-windows-process-identity-token.md +0 -490
  113. package/docs/superpowers/plans/2026-10-24-issue-241-coordinator-root-exit.md +0 -461
  114. package/docs/superpowers/plans/2026-10-25-issue-239-coordinator-replacement-identity.md +0 -504
  115. package/docs/superpowers/plans/2026-10-26-issue-245-key-hints-doc-sync.md +0 -244
  116. package/docs/superpowers/plans/2026-10-27-issue-172-fullscreen-selection-clear.md +0 -344
  117. package/docs/superpowers/plans/2026-10-27-issue-252-select-key-hints.md +0 -209
  118. package/docs/superpowers/plans/2026-10-28-issue-255-coordinator-endpoint-zombie.md +0 -442
  119. package/docs/superpowers/plans/2026-10-28-issue-257-legacy-pty-runner-endpoint-cleanup.md +0 -256
  120. package/docs/superpowers/plans/2026-11-03-cwd-favorites-full-pool.md +0 -266
  121. package/docs/superpowers/specs/2026-08-15-screenlog-gc-design.md +0 -105
  122. package/docs/superpowers/specs/2026-08-16-attach-double-cursor-jiggle-retry-design.md +0 -142
  123. package/docs/superpowers/specs/2026-08-21-attach-coldstart-jiggle-rearm-design.md +0 -110
  124. package/docs/superpowers/specs/2026-08-21-dashboard-keypress-lag-design.md +0 -59
  125. package/docs/superpowers/specs/2026-08-21-issue-14-needs-input-no-auto-done-design.md +0 -109
  126. package/docs/superpowers/specs/2026-08-22-cwd-favorites-design.md +0 -131
  127. package/docs/superpowers/specs/2026-08-22-jiggle-shrink-and-hold-design.md +0 -111
  128. package/docs/superpowers/specs/2026-08-22-question-tool-grouping-design.md +0 -114
  129. package/docs/superpowers/specs/2026-08-22-rename-state-labels-design.md +0 -80
  130. package/docs/superpowers/specs/2026-08-27-locks-acquirelock-spin-design.md +0 -97
  131. package/docs/superpowers/specs/2026-08-27-pty-runner-test-flaky-design.md +0 -35
  132. package/docs/superpowers/specs/2026-08-29-code-refs-badges-design.md +0 -128
  133. package/docs/superpowers/specs/2026-08-30-circular-navigation-design.md +0 -47
  134. package/docs/superpowers/specs/2026-08-30-eprm-atomicwrite-race-design.md +0 -77
  135. package/docs/superpowers/specs/2026-08-30-post-exit-timing-fix-design.md +0 -80
  136. package/docs/superpowers/specs/2026-08-30-pty-attach-quality-debt-design.md +0 -105
  137. package/docs/superpowers/specs/2026-08-30-readme-v2-design.md +0 -115
  138. package/docs/superpowers/specs/2026-09-01-attach-detach-gate-cursor-anchor-design.md +0 -78
  139. package/docs/superpowers/specs/2026-09-02-attach-detach-editor-state-design.md +0 -120
  140. package/docs/superpowers/specs/2026-09-03-code-refs-pr-backlink-narrow-design.md +0 -92
  141. package/docs/superpowers/specs/2026-09-03-detach-gate-glyph-fallback-design.md +0 -99
  142. package/docs/superpowers/specs/2026-09-04-evidence-outputpreview-design.md +0 -50
  143. package/docs/superpowers/specs/2026-09-04-warm-host-reclaim-design.md +0 -106
  144. package/docs/superpowers/specs/2026-09-05-issue-13-drainnextfollowup-pty-probe-design.md +0 -64
  145. package/docs/superpowers/specs/2026-09-05-issue-38-windows-wezterm-ime-design.md +0 -48
  146. package/docs/superpowers/specs/2026-09-05-issue-39-truncate-codepoint-boundary-design.md +0 -64
  147. package/docs/superpowers/specs/2026-09-05-issue-61-mention-fallback-design.md +0 -71
  148. package/docs/superpowers/specs/2026-09-05-issue-63-flaky-manual-completion-design.md +0 -49
  149. package/docs/superpowers/specs/2026-09-05-issue-64-changelog-helper-design.md +0 -76
  150. package/docs/superpowers/specs/2026-09-05-pty-host-stacking-sock-race-design.md +0 -510
  151. package/docs/superpowers/specs/2026-09-07-issue-11-attach-runtime-desync-heal-design.md +0 -130
  152. package/docs/superpowers/specs/2026-09-08-attach-ctrl-left-detach-design.md +0 -58
  153. package/docs/superpowers/specs/2026-09-08-dashboard-shrink-repaint-design.md +0 -52
  154. package/docs/superpowers/specs/2026-09-08-legacy-stale-host-recovery-design.md +0 -87
  155. package/docs/superpowers/specs/2026-09-08-spawn-async-error-swallow-design.md +0 -56
  156. package/docs/superpowers/specs/2026-09-08-stale-model-attach-guard-design.md +0 -79
  157. package/docs/superpowers/specs/2026-09-09-claimpid-blocks-replace-design.md +0 -147
  158. package/docs/superpowers/specs/2026-09-09-harden-runner-architecture-design.md +0 -304
  159. package/docs/superpowers/specs/2026-09-14-attach-cursor-dectcem-gate-design.md +0 -114
  160. package/docs/superpowers/specs/2026-09-14-host-meta-orphan-lock-design.md +0 -120
  161. package/docs/superpowers/specs/2026-09-14-issue-106-terminal-frame-cognition-design.md +0 -146
  162. package/docs/superpowers/specs/2026-09-14-issue-113-foreground-preview-race-design.md +0 -116
  163. package/docs/superpowers/specs/2026-09-15-coordinator-pipe-root-normalize-design.md +0 -84
  164. package/docs/superpowers/specs/2026-09-15-lease-publish-eprem-reclaim-design.md +0 -92
  165. package/docs/superpowers/specs/2026-09-18-detach-anchor-reporter-endpoint-design.md +0 -123
  166. package/docs/superpowers/specs/2026-09-20-issue-121-perf-gate-out-of-coverage-design.md +0 -204
  167. package/docs/superpowers/specs/2026-09-23-a5-mid-stream-assertion-design.md +0 -43
  168. package/docs/superpowers/specs/2026-09-23-cwd-browse-tab-drill-design.md +0 -95
  169. package/docs/superpowers/specs/2026-09-23-terminal-query-forwarding-design.md +0 -63
  170. package/docs/superpowers/specs/2026-09-23-unconditional-detach-key-design.md +0 -57
  171. package/docs/superpowers/specs/2026-09-24-control-reconcile-flake-design.md +0 -98
  172. package/docs/superpowers/specs/2026-09-24-probe-attached-clients-design.md +0 -190
  173. package/docs/superpowers/specs/2026-09-29-issue-145-holding-semantic-state-design.md +0 -394
  174. package/docs/superpowers/specs/2026-09-30-board-delete-key-x-y-confirm-design.md +0 -86
  175. package/docs/superpowers/specs/2026-09-30-issue-153-runner-stop-boot-window-design.md +0 -75
  176. package/docs/superpowers/specs/2026-09-30-issue-95-test-discipline-design.md +0 -226
  177. package/docs/superpowers/specs/2026-09-30-publish-dispatch-tag-check-design.md +0 -129
  178. package/docs/superpowers/specs/2026-10-06-attach-settle-scheme-delivery-design.md +0 -196
  179. package/docs/superpowers/specs/2026-10-09-issue-167-attach-middle-paste-design.md +0 -125
  180. package/docs/superpowers/specs/2026-10-09-issue-169-fullscreen-mouse-ownership-design.md +0 -130
  181. package/docs/superpowers/specs/2026-10-10-attach-delete-in-place-design.md +0 -224
  182. package/docs/superpowers/specs/2026-10-10-issue-174-selection-mirror-paste-source-design.md +0 -154
  183. package/docs/superpowers/specs/2026-10-14-issue-170-attach-reply-guard-design.md +0 -195
  184. package/docs/superpowers/specs/2026-10-14-issue-178-accept-registration-race-design.md +0 -287
  185. package/docs/superpowers/specs/2026-10-14-issue-184-snapshot-window-contract-design.md +0 -288
  186. package/docs/superpowers/specs/2026-10-14-issue-185-attach-wheel-alt-screen-design.md +0 -126
  187. package/docs/superpowers/specs/2026-10-14-issue-187-motionless-drag-design.md +0 -269
  188. package/docs/superpowers/specs/2026-10-14-issue-189-mixed-input-segments-design.md +0 -369
  189. package/docs/superpowers/specs/2026-10-15-issue-188-x10-mouse-release-design.md +0 -252
  190. package/docs/superpowers/specs/2026-10-16-issue-198-attach-wheel-forward-fallback-design.md +0 -67
  191. package/docs/superpowers/specs/2026-10-18-attach-wheel-replay-pseudo-scrollback-design.md +0 -250
  192. package/docs/superpowers/specs/2026-10-19-issue-213-reply-guard-csi-key-design.md +0 -165
  193. package/docs/superpowers/specs/2026-10-19-issue-214-attach-forward-alt-modifier-design.md +0 -131
  194. package/docs/superpowers/specs/2026-10-20-issue-221-ask-user-question-design.md +0 -270
  195. package/docs/superpowers/specs/2026-10-22-issue-225-monotonic-foreground-state-design.md +0 -225
  196. package/docs/superpowers/specs/2026-10-23-issue-222-attach-ready-design.md +0 -204
  197. package/docs/superpowers/specs/2026-10-23-issue-229-vim-jkl-design.md +0 -90
  198. package/docs/superpowers/specs/2026-10-23-issue-232-attach-bg-only-cells-design.md +0 -146
  199. package/docs/superpowers/specs/2026-10-23-lease-heartbeat-staleness-design.md +0 -271
  200. package/docs/superpowers/specs/2026-10-23-selected-row-highlight-design.md +0 -191
  201. package/docs/superpowers/specs/2026-10-24-issue-217-windows-pid-reuse-stopping-design.md +0 -334
  202. package/docs/superpowers/specs/2026-10-24-issue-230-warm-host-background-runs-design.md +0 -283
  203. package/docs/superpowers/specs/2026-10-24-issue-238-windows-process-identity-token-design.md +0 -286
  204. package/docs/superpowers/specs/2026-10-24-issue-241-coordinator-root-exit-design.md +0 -381
  205. package/docs/superpowers/specs/2026-10-25-issue-239-coordinator-replacement-identity-design.md +0 -331
  206. package/docs/superpowers/specs/2026-10-26-issue-245-key-hints-doc-sync-design.md +0 -72
  207. package/docs/superpowers/specs/2026-10-27-issue-172-fullscreen-selection-clear-design.md +0 -248
  208. package/docs/superpowers/specs/2026-10-27-issue-252-select-key-hints-design.md +0 -95
  209. package/docs/superpowers/specs/2026-10-28-issue-255-coordinator-endpoint-zombie-design.md +0 -380
  210. package/docs/superpowers/specs/2026-10-28-issue-257-legacy-pty-runner-endpoint-cleanup-design.md +0 -197
  211. package/docs/superpowers/specs/2026-11-03-cwd-favorites-full-pool-design.md +0 -106
  212. package/scripts/budget-audit.mjs +0 -135
  213. package/scripts/release.mjs +0 -446
  214. package/scripts/release_helper.mjs +0 -309
  215. package/scripts/run-perf-gate.mjs +0 -40
  216. package/scripts/run-tests.mjs +0 -375
@@ -1,405 +0,0 @@
1
- # Selected Row Highlight Implementation Plan
2
-
3
- > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
4
-
5
- **Goal:** Make the board's selected row identifiable at rest by painting its full line (indent through the trailing pad) with the theme's `selectedBg`.
6
-
7
- **Architecture:** One pure module-scope helper `applyRowHighlight(theme, selected, text)` decides whether to wrap a line; `renderRow` calls it on `indent + clipped body` so the highlight starts at column 0 and covers the right edge. The theme token (`selectedBg`) is pi's own selection background, so dark/light themes and 256/16-colour terminals are handled by pi. A new TS harness (`dashboard-selection-render.ts`) drives the real `DashboardComponent` and asserts the ANSI structure; the eight pre-existing `DashboardComponent` harnesses get an identity `bg` stub.
8
-
9
- **Tech Stack:** TypeScript (src/ui), Node 20+ `node:test` + `node --experimental-transform-types` harnesses, `@earendil-works/pi-tui` (`visibleWidth`, `truncateToWidth`), pi theme tokens (`ThemeBg = "selectedBg"`).
10
-
11
- **Spec:** `docs/superpowers/specs/2026-10-23-selected-row-highlight-design.md`
12
-
13
- **Worktree:** `C:/Users/27499/proj/opensource/pi-agent-board/.pi/worktrees/issue-231-selected-row-highlight` — every path below is relative to it; never edit the main checkout.
14
-
15
- ## Global Constraints
16
-
17
- - Tabs for indentation; English comments; conventional commit subjects with the issue number (`(#231)` lands on the PR, not on local commits).
18
- - Do not change `select` mode's `◉/○`, the peek header, the flash banner, the folder-header `▌`, or `windowBody()` (open PR #226 owns the viewport).
19
- - Highlight colour comes **only** from `theme.bg("selectedBg", …)` — no hardcoded RGB, no per-stage background, no `muted → dim` demotion.
20
- - `\x1b[49m` (emitted by `theme.bg`) must stay inside the clipped string; the bg span must include the folder indent, the trailing pad, and the age column.
21
- - `npm run verify` (typecheck + perf + test + coverage + pack:dry) is the gate before claiming done.
22
- - Do not touch `research/`, `~/.github-issue-driven/`, or the main checkout; `git add <explicit files>` only.
23
-
24
- ## Review Focus
25
-
26
- 1. **Ultra-narrow terminals (`width < 24`)** — `contentWidth` has a floor of 20, so `indent + contentWidth > width` and `clip` truncates with `\x1b[0m`. The highlight may lose its closer there, but it must never leak into following rows. → A8 (Task 2).
27
- 2. **`selectedId` matching no visible row** (e.g. after a filter) — no row may be painted. → A5 zero-branch (Task 2).
28
- 3. **Folder-group indent (`indent = 4`)** — the bg opener must sit at column 0, before the indent, not after it. → A3 (Task 2).
29
- 4. **`select` mode** — the batch marker `◉/○` must remain visible and the highlight must still apply. → A6 (Task 2).
30
- 5. **Coexisting strong signals** — the flash banner keeps its own bg bar, the folder header keeps its `▌`, and the peek header stays unhighlighted. → A7 (Task 2) and U5 (manual).
31
-
32
- ---
33
-
34
- ### Task 1: Pure highlight helper
35
-
36
- **Files:**
37
- - Modify: `src/ui/dashboard.ts` (insert the exported helper next to `stageFg` / `ansiBg`, around the `STAGE_RGB` block at :1968-1996)
38
- - Create: `test-support/dashboard-selection-render.ts`
39
- - Create: `test/dashboard-selection.test.mjs`
40
-
41
- **Interfaces:**
42
- - Consumes: `ThemeLike` (`src/ui/dashboard.ts:50-56`, already declares `bg(color: string, text: string): string`).
43
- - Produces:
44
- - `export function applyRowHighlight(theme: Pick<ThemeLike, "bg">, selected: boolean, text: string): string`
45
- - Harness JSON contract: `{ ok: boolean, unit: { unselectedIdentical: boolean, selectedWrapped: boolean, themeCallCount: number } }` (integration fields are added in Task 2).
46
-
47
- - [ ] **Step 1: Write the failing test**
48
-
49
- Create `test/dashboard-selection.test.mjs`:
50
-
51
- ```js
52
- import assert from "node:assert/strict";
53
- import { execFileSync } from "node:child_process";
54
- import { fileURLToPath } from "node:url";
55
- import { join } from "node:path";
56
- import test from "node:test";
57
-
58
- const ROOT_DIR = fileURLToPath(new URL("../", import.meta.url));
59
- const SCRIPT = join(ROOT_DIR, "test-support", "dashboard-selection-render.ts");
60
-
61
- function runHarness() {
62
- const out = execFileSync(process.execPath, ["--experimental-transform-types", SCRIPT], {
63
- encoding: "utf8",
64
- timeout: 30_000,
65
- });
66
- return JSON.parse(out);
67
- }
68
-
69
- test("applyRowHighlight is a pure pass-through when unselected and wraps once when selected (issue #231)", () => {
70
- const parsed = runHarness();
71
- assert.equal(parsed.ok, true);
72
- assert.equal(parsed.unit.unselectedIdentical, true, "unselected input must come back byte-identical");
73
- assert.equal(parsed.unit.selectedWrapped, true, "selected input must be wrapped exactly once");
74
- assert.equal(parsed.unit.themeCallCount, 1, "the helper must call theme.bg once per selected row, never for unselected rows");
75
- });
76
- ```
77
-
78
- Create `test-support/dashboard-selection-render.ts` (unit part only for now):
79
-
80
- ```ts
81
- // Dashboard selected-row harness: renders real DashboardComponent frames with a
82
- // recording/ANSI-emitting theme stub and prints JSON flags the .mjs test asserts
83
- // on. Unit checks for the pure helper live here too so one process proves both.
84
- import { mkdtempSync, rmSync } from "node:fs";
85
- import { tmpdir } from "node:os";
86
- import { join } from "node:path";
87
- import { visibleWidth } from "@earendil-works/pi-tui";
88
- import { createView } from "../src/core/store.mjs";
89
- import { createService } from "../src/runtime/service.mjs";
90
- import { applyRowHighlight, DashboardComponent } from "../src/ui/dashboard.ts";
91
-
92
- const BG_OPEN = "\x1b[48;2;1;2;3m";
93
- const BG_CLOSE = "\x1b[49m";
94
-
95
- const root = mkdtempSync(join(tmpdir(), "agentview-selection-render-"));
96
- try {
97
- let bgCalls = 0;
98
- const theme = {
99
- fg: (token: string, text: string) => {
100
- fgCalls.push([token, text]);
101
- return text;
102
- },
103
- bold: (text: string) => text,
104
- italic: (text: string) => text,
105
- strikethrough: (text: string) => text,
106
- bg: (_color: string, text: string) => {
107
- bgCalls += 1;
108
- return `${BG_OPEN}${text}${BG_CLOSE}`;
109
- },
110
- };
111
- const fgCalls: Array<[string, string]> = [];
112
-
113
- // --- unit: the pure helper -------------------------------------------
114
- const identity = "plain line";
115
- const untouched = applyRowHighlight(theme, false, identity);
116
- const callsAfterUnselected = bgCalls;
117
- const wrapped = applyRowHighlight(theme, true, identity);
118
-
119
- console.log(
120
- JSON.stringify({
121
- ok: true,
122
- unit: {
123
- unselectedIdentical: untouched === identity && callsAfterUnselected === 0,
124
- selectedWrapped: wrapped === `${BG_OPEN}${identity}${BG_CLOSE}`,
125
- themeCallCount: bgCalls,
126
- },
127
- }),
128
- );
129
- } finally {
130
- rmSync(root, { recursive: true, force: true });
131
- }
132
- ```
133
-
134
- (Keep `createView`, `createService`, `DashboardComponent` and `visibleWidth` imported even though the unit part does not use them yet — Task 2 fills in the integration section, and the typecheck gate excludes `test-support/` anyway.)
135
-
136
- - [ ] **Step 2: Run the test to verify it fails**
137
-
138
- Run: `node --test test/dashboard-selection.test.mjs`
139
- Expected: FAIL — the harness throws because `applyRowHighlight` is not exported from `../src/ui/dashboard.ts` (or `parsed.ok` is missing).
140
-
141
- - [ ] **Step 3: Implement `applyRowHighlight` in `src/ui/dashboard.ts`**
142
-
143
- Place it next to `stageFg`:
144
-
145
- ```ts
146
- /**
147
- * Issue #231: the selected row's full-line background. Pure (the theme is
148
- * injected) so the wrap/close contract is unit-testable, and an unselected row
149
- * is returned byte-identical.
150
- */
151
- export function applyRowHighlight(
152
- theme: Pick<ThemeLike, "bg">,
153
- selected: boolean,
154
- text: string,
155
- ): string {
156
- return selected ? theme.bg("selectedBg", text) : text;
157
- }
158
- ```
159
-
160
- - [ ] **Step 4: Run the test to verify it passes**
161
-
162
- Run: `node --test test/dashboard-selection.test.mjs`
163
- Expected: PASS (1 test).
164
-
165
- - [ ] **Step 5: Commit**
166
-
167
- ```bash
168
- git add src/ui/dashboard.ts test/dashboard-selection.test.mjs test-support/dashboard-selection-render.ts
169
- git commit -m "feat(ui): add pure selected-row highlight helper (issue #231)"
170
- ```
171
-
172
- ---
173
-
174
- ### Task 2: Paint the row, cover the indent, and keep every existing harness green
175
-
176
- **Files:**
177
- - Modify: `src/ui/dashboard.ts:1459-1483` (`renderRow` return path)
178
- - Modify: `test-support/dashboard-selection-render.ts` (add the integration section and its JSON fields)
179
- - Modify: `test/dashboard-selection.test.mjs` (assert the integration fields)
180
- - Modify (stub only): `test-support/dashboard-delete-key-probe.ts`, `test-support/cwd-browse-tab-probe.ts`, `test-support/dashboard-shrink-render.ts`, `test-support/dashboard-refs-render.ts`, `test-support/dashboard-prewarm-smoke.ts`, `test-support/navigation-wrap.ts`, `test-support/hold-key-wrap.ts`, `test-support/ui-smoke.ts`, `test-support/attach-delete-flow-probe.ts`
181
-
182
- **Interfaces:**
183
- - Consumes: `applyRowHighlight` from Task 1.
184
- - Produces: `renderRow` output whose selected-row string begins with the theme's bg opener and ends with `\x1b[49m`; harness JSON gains `integration: { selectedStartsAtCol0, selectedWidth, unselectedHasNoOpener, selectedHasArrow, unselectedHasNoArrow, selectedCloserLast, selectedCloserCount, firstFrameBgCalls, zeroSelectionBgCalls, selectMarkerPresent, selectUnselectedCircle, selectHasBg, peekHasNoOpener, peekHeaderUsesAccent, narrowMaxWidth, narrowLeaked, unselectedUsesMuted, unselectedNameIsDim }` (`firstFrameBgCalls` is the per-frame `bgCalls` counter of the selected-row render; `zeroSelectionBgCalls` is the same counter with a `selectedId` that matches no visible row).
185
-
186
- - [ ] **Step 1: Write the failing integration assertions**
187
-
188
- Append to `test-support/dashboard-selection-render.ts`, inside the `try` block, after the unit section, and change the single `console.log` to emit both `unit` and `integration`:
189
-
190
- ```ts
191
- // --- integration: real render frames ---------------------------------
192
- createView(root, { id: "v1", name: "alpha session", cwd: join(root, "proj-a") });
193
- createView(root, { id: "v2", name: "beta session", cwd: join(root, "proj-b") });
194
- const service = createService({
195
- root,
196
- runnerScript: "/no/runner.mjs",
197
- piCommand: "pi",
198
- piArgsPrefix: [],
199
- defaultCwd: root,
200
- launch: () => ({ pid: null, configPath: "/no/config.json" }),
201
- launchHost: () => ({ pid: null, configPath: "/no/host-config.json" }),
202
- launchTitle: () => ({ pid: null, configPath: "/no/title-config.json" }),
203
- });
204
- const tui = {
205
- terminal: { rows: 40, cols: 160, columns: 160, write: () => {} },
206
- requestRender: () => {},
207
- };
208
- const makeDash = () =>
209
- new DashboardComponent(tui as never, theme as never, {} as never, () => {}, {
210
- service,
211
- root,
212
- defaultCwd: root,
213
- availableModels: [],
214
- currentModel: null,
215
- currentThinkingLevel: "off",
216
- });
217
-
218
- // Two cwds ⇒ showFolders=true ⇒ rows carry indent 4.
219
- bgCalls = 0;
220
- const dash = makeDash();
221
- const lines = dash.render(160);
222
- const selectedLine = lines.find((line) => line.includes("alpha session")) ?? "";
223
- const otherLine = lines.find((line) => line.includes("beta session")) ?? "";
224
-
225
- const selectedStartsAtCol0 = selectedLine.startsWith(BG_OPEN);
226
- const selectedWidth = visibleWidth(selectedLine);
227
- const unselectedHasNoOpener = !otherLine.includes(BG_OPEN);
228
- const selectedHasArrow = selectedLine.includes("›");
229
- const unselectedHasNoArrow = !otherLine.includes("›");
230
- const firstFrameBgCalls = bgCalls;
231
-
232
- // select mode: press `m`, then space to batch-mark the current row.
233
- dash.handleInput("m");
234
- dash.handleInput(" ");
235
- const selectLines = dash.render(160);
236
- const selectMarkerPresent = selectLines.some((line) => line.includes("◉"));
237
- const selectHasBg = selectLines.find((line) => line.includes("alpha session"))?.includes(BG_OPEN) ?? false;
238
- dash.handleInput("\x1b");
239
-
240
- // peek: space in list mode opens the peek header (must stay unhighlighted).
241
- const peekDash = makeDash();
242
- peekDash.handleInput(" ");
243
- const peekHasNoOpener = !peekDash.render(160).some((line) => line.includes(BG_OPEN));
244
-
245
- // ultra-narrow: width 23 with indent 4 overflows (4 + max(20, 19) = 24 > 23).
246
- const narrowLines = dash.render(23);
247
- const narrowMaxWidth = Math.max(...narrowLines.map((line) => visibleWidth(line)));
248
- const narrowLeaked = narrowLines.some((line) => !line.includes("alpha session") && line.includes(BG_OPEN));
249
-
250
- // D4: unselected rows keep `muted`, never `dim`, for name/summary.
251
- const unselectedUsesMuted = fgCalls.some(([token, text]) => token === "muted" && text.includes("beta session"));
252
- const unselectedNameIsDim = fgCalls.some(([token, text]) => token === "dim" && text.includes("beta session"));
253
- ```
254
-
255
- Then replace the `console.log(JSON.stringify({ ok: true, unit: {...} }))` with:
256
-
257
- ```ts
258
- console.log(
259
- JSON.stringify({
260
- ok: true,
261
- unit: {
262
- unselectedIdentical: untouched === identity && callsAfterUnselected === 0,
263
- selectedWrapped: wrapped === `${BG_OPEN}${identity}${BG_CLOSE}`,
264
- themeCallCount: bgCallsBeforeIntegration,
265
- },
266
- integration: {
267
- selectedStartsAtCol0,
268
- selectedWidth,
269
- unselectedHasNoOpener,
270
- selectedHasArrow,
271
- unselectedHasNoArrow,
272
- firstFrameBgCalls,
273
- selectMarkerPresent,
274
- selectHasBg,
275
- peekHasNoOpener,
276
- narrowMaxWidth,
277
- narrowLeaked,
278
- unselectedUsesMuted,
279
- unselectedNameIsDim,
280
- },
281
- }),
282
- );
283
- ```
284
-
285
- where `bgCallsBeforeIntegration` is captured right after the unit section (`const bgCallsBeforeIntegration = bgCalls;`).
286
-
287
- Add to `test/dashboard-selection.test.mjs`:
288
-
289
- ```js
290
- test("renderRow paints the selected row from column 0 through the padded right edge (issue #231)", () => {
291
- const parsed = runHarness();
292
- const it = parsed.integration;
293
- assert.equal(it.selectedStartsAtCol0, true, "bg must open before the folder indent");
294
- assert.equal(it.selectedWidth, 160, "bg span must reach the padded right edge");
295
- assert.equal(it.unselectedHasNoOpener, true, "unselected rows must have no bg");
296
- assert.equal(it.selectedHasArrow, true, "the › cursor marker stays on the selected row");
297
- assert.equal(it.unselectedHasNoArrow, true, "no › on unselected rows");
298
- assert.equal(it.firstFrameBgCalls, 1, "exactly one row is painted per frame");
299
- });
300
-
301
- test("selected-row highlight coexists with select mode, peek, and narrow terminals (issue #231)", () => {
302
- const parsed = runHarness();
303
- const it = parsed.integration;
304
- assert.equal(it.selectMarkerPresent, true, "select mode's ◉ batch marker survives");
305
- assert.equal(it.selectHasBg, true, "select mode keeps the row highlight");
306
- assert.equal(it.peekHasNoOpener, true, "peek header is not highlighted");
307
- assert.ok(it.narrowMaxWidth <= 23, "no line may exceed the requested width");
308
- assert.equal(it.narrowLeaked, false, "narrow clipping must not leak bg into other rows");
309
- });
310
-
311
- test("unselected rows keep muted name/summary, not dim (issue #231 decision D4)", () => {
312
- const parsed = runHarness();
313
- assert.equal(parsed.integration.unselectedUsesMuted, true);
314
- assert.equal(parsed.integration.unselectedNameIsDim, false);
315
- });
316
- ```
317
-
318
- - [ ] **Step 2: Run the tests to verify they fail**
319
-
320
- Run: `node --test test/dashboard-selection.test.mjs`
321
- Expected: FAIL — `selectedStartsAtCol0` is false (no bg is emitted yet) and `firstFrameBgCalls` is 0.
322
-
323
- - [ ] **Step 3: Wire `applyRowHighlight` into `renderRow`**
324
-
325
- Replace the last return of `renderRow` (`src/ui/dashboard.ts:1481-1483`):
326
-
327
- ```ts
328
- line = padTo(line, contentWidth - visibleWidth(ageRaw));
329
- line += t.fg("dim", ageRaw);
330
- const body = clip(line, contentWidth);
331
- const painted = applyRowHighlight(t, selected, `${" ".repeat(indent)}${body}`);
332
- return clip(painted, width);
333
- ```
334
-
335
- Add the identity `bg` stub to each pre-existing harness theme literal, e.g.:
336
-
337
- ```ts
338
- const theme = { fg: (_c: string, t: string) => t, bold: (t: string) => t, italic: (t: string) => t, strikethrough: (t: string) => t, bg: (_c: string, t: string) => t };
339
- ```
340
-
341
- Apply the same one-line addition to the nine files listed in **Files** above (grep `fg: (` in `test-support/*.ts` to find each literal).
342
-
343
- - [ ] **Step 4: Run the focused tests, then the full suite**
344
-
345
- Run: `node --test test/dashboard-selection.test.mjs`
346
- Expected: PASS (4 tests).
347
-
348
- Run: `node --test test/dashboard-*.test.mjs test/ui-smoke.test.mjs`
349
- Expected: PASS — this is where a missed `bg` stub would surface as `theme.bg is not a function`.
350
-
351
- Run: `npm test`
352
- Expected: PASS (no new failures; pre-existing environment failures, if any, must be identical to a clean `git stash` baseline).
353
-
354
- - [ ] **Step 5: Commit**
355
-
356
- ```bash
357
- git add src/ui/dashboard.ts test/dashboard-selection.test.mjs test-support/dashboard-selection-render.ts \
358
- test-support/dashboard-delete-key-probe.ts test-support/cwd-browse-tab-probe.ts \
359
- test-support/dashboard-shrink-render.ts test-support/dashboard-refs-render.ts \
360
- test-support/dashboard-prewarm-smoke.ts test-support/navigation-wrap.ts \
361
- test-support/hold-key-wrap.ts test-support/ui-smoke.ts test-support/attach-delete-flow-probe.ts
362
- git commit -m "feat(ui): paint the selected board row with selectedBg (issue #231)"
363
- ```
364
-
365
- ---
366
-
367
- ### Task 3: Full verification gate
368
-
369
- **Files:**
370
- - No source changes expected. If `npm run verify` fails, fix in the owning file from Task 1/2 and amend the Task 2 commit rather than adding a new fixup commit.
371
-
372
- **Interfaces:**
373
- - Consumes: both earlier tasks.
374
- - Produces: a branch whose `npm run verify` is green.
375
-
376
- - [ ] **Step 1: Run the repo gate**
377
-
378
- Run: `npm run verify`
379
- Expected: `typecheck` clean, `test:perf` 3 passed, `npm test` green, `test:coverage` above lines 85 / funcs 80 / branches 70, `pack:dry` lists `src/ui/dashboard.ts`.
380
-
381
- - [ ] **Step 2: Confirm the packed tarball still ships the touched file**
382
-
383
- Run: `npm pack --dry-run 2>&1 | grep -E "src/ui/dashboard.ts|test-support"`
384
- Expected: `src/ui/dashboard.ts` present; `test-support/` absent (it is not in `files`).
385
-
386
- - [ ] **Step 3: Record the manual-check handoff**
387
-
388
- Write the U1–U5 steps from the spec into the PR description (they need a live pi session and cannot run in CI). Do not claim them as passed.
389
-
390
- - [ ] **Step 4: Commit any gate fixes (only if needed)**
391
-
392
- ```bash
393
- git add -A -- src test test-support
394
- git commit -m "test(ui): keep the selection harness green under the full gate (issue #231)"
395
- ```
396
-
397
- ---
398
-
399
- ## Self-Review
400
-
401
- - **Spec coverage:** D1 → Task 1 + Task 2 A1/A2; D2 → A10 (Task 2); D3 → A3 (Task 2); D4 → A11 (Task 2). A1–A4, A6–A8, A10, A11 → Task 2; A5 → Task 1 (unit call count) + Task 2 (per-frame count); A9 → Task 3; U1–U5 → PR handoff (Task 3 Step 3). No spec requirement is unassigned.
402
- - **Step scan:** each step names one file and one checkable result; no "handle edge cases" placeholders.
403
- - **Type consistency:** `applyRowHighlight(theme, selected, text)` is defined and consumed with the same three parameters in both tasks; `BG_OPEN`/`BG_CLOSE` and the JSON field names used by the `.mjs` assertions match the harness emission.
404
- - **Review Focus:** each of the five lines has an owning assertion (A8, A5-zero, A3, A6, A7/A5) in Task 2, plus U5 in the PR handoff.
405
- - **Proportion:** the plan is shorter than the spec and contains only signatures, assertions and the one-line stub shape.