@huseynsnmz/pi-board 0.15.1 → 0.16.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 (215) hide show
  1. package/CHANGELOG.md +26 -4
  2. package/CONTRIBUTING.md +7 -0
  3. package/README.md +53 -419
  4. package/SECURITY.md +19 -0
  5. package/docs/how-it-works.md +31 -0
  6. package/docs/reference.md +276 -0
  7. package/package.json +18 -19
  8. package/src/index.ts +9 -1
  9. package/src/runtime/service.mjs +12 -0
  10. package/src/ui/dashboard.ts +14 -8
  11. package/src/ui/double-left.ts +5 -1
  12. package/src/ui/focus.ts +7 -1
  13. package/src/ui/waiting.ts +10 -5
  14. package/IMPLEMENTATION_PLAN.md +0 -920
  15. package/PRD.md +0 -484
  16. package/PROGRESS.md +0 -142
  17. package/VERIFY.md +0 -115
  18. package/docs/BATCH_SELECTION_READ_FLOW.md +0 -277
  19. package/docs/EXPLORATION.md +0 -187
  20. package/docs/PTY_ATTACH_IMPLEMENTATION_PLAN.md +0 -581
  21. package/docs/RELEASE.md +0 -148
  22. package/docs/superpowers/plans/2026-08-15-screenlog-gc.md +0 -704
  23. package/docs/superpowers/plans/2026-08-16-attach-double-cursor-jiggle-retry.md +0 -499
  24. package/docs/superpowers/plans/2026-08-21-attach-coldstart-jiggle-rearm.md +0 -797
  25. package/docs/superpowers/plans/2026-08-21-dashboard-keypress-lag.md +0 -366
  26. package/docs/superpowers/plans/2026-08-21-issue-14-needs-input-no-auto-done.md +0 -576
  27. package/docs/superpowers/plans/2026-08-22-jiggle-shrink-and-hold.md +0 -337
  28. package/docs/superpowers/plans/2026-08-22-launch-cwd-favorites.md +0 -916
  29. package/docs/superpowers/plans/2026-08-22-question-tool-grouping.md +0 -211
  30. package/docs/superpowers/plans/2026-08-22-rename-state-labels.md +0 -182
  31. package/docs/superpowers/plans/2026-08-22-stable-list-order.md +0 -51
  32. package/docs/superpowers/plans/2026-08-27-locks-acquirelock-spin.md +0 -614
  33. package/docs/superpowers/plans/2026-08-27-pty-runner-test-flaky.md +0 -32
  34. package/docs/superpowers/plans/2026-08-29-code-refs-badges.md +0 -223
  35. package/docs/superpowers/plans/2026-08-30-circular-navigation.md +0 -308
  36. package/docs/superpowers/plans/2026-08-30-post-exit-timing-fix.md +0 -37
  37. package/docs/superpowers/plans/2026-08-30-pty-attach-quality-debt.md +0 -311
  38. package/docs/superpowers/plans/2026-08-30-readme-v2.md +0 -294
  39. package/docs/superpowers/plans/2026-09-01-attach-detach-gate-cursor-anchor.md +0 -284
  40. package/docs/superpowers/plans/2026-09-02-attach-detach-editor-state.md +0 -722
  41. package/docs/superpowers/plans/2026-09-03-code-refs-pr-backlink-narrow.md +0 -551
  42. package/docs/superpowers/plans/2026-09-03-detach-gate-glyph-fallback.md +0 -146
  43. package/docs/superpowers/plans/2026-09-04-evidence-outputpreview.md +0 -209
  44. package/docs/superpowers/plans/2026-09-04-warm-host-reclaim.md +0 -796
  45. package/docs/superpowers/plans/2026-09-05-issue-13-drainnextfollowup-pty-probe.md +0 -114
  46. package/docs/superpowers/plans/2026-09-05-issue-38-windows-wezterm-ime-cursor.md +0 -73
  47. package/docs/superpowers/plans/2026-09-05-issue-39-truncate-codepoint-boundary.md +0 -143
  48. package/docs/superpowers/plans/2026-09-05-issue-61-mention-fallback-guards.md +0 -226
  49. package/docs/superpowers/plans/2026-09-05-issue-63-flaky-manual-completion.md +0 -87
  50. package/docs/superpowers/plans/2026-09-05-issue-64-changelog-release-helper.md +0 -53
  51. package/docs/superpowers/plans/2026-09-05-pty-host-stacking-sock-race.md +0 -731
  52. package/docs/superpowers/plans/2026-09-08-attach-ctrl-left-detach.md +0 -30
  53. package/docs/superpowers/plans/2026-09-08-dashboard-shrink-repaint.md +0 -68
  54. package/docs/superpowers/plans/2026-09-08-issue-11-attach-runtime-desync-heal.md +0 -917
  55. package/docs/superpowers/plans/2026-09-08-legacy-stale-host-recovery.md +0 -125
  56. package/docs/superpowers/plans/2026-09-08-spawn-async-error-swallow.md +0 -56
  57. package/docs/superpowers/plans/2026-09-08-stale-model-attach-guard.md +0 -96
  58. package/docs/superpowers/plans/2026-09-09-claimpid-blocks-replace.md +0 -267
  59. package/docs/superpowers/plans/2026-09-09-harden-runner-architecture.md +0 -603
  60. package/docs/superpowers/plans/2026-09-09-single-writer-completion.md +0 -252
  61. package/docs/superpowers/plans/2026-09-10-reader-consistency.md +0 -115
  62. package/docs/superpowers/plans/2026-09-14-attach-cursor-dectcem-gate.md +0 -469
  63. package/docs/superpowers/plans/2026-09-14-attach-snapshot.md +0 -92
  64. package/docs/superpowers/plans/2026-09-14-host-meta-orphan-lock.md +0 -771
  65. package/docs/superpowers/plans/2026-09-14-issue-106-terminal-frame-cognition.md +0 -299
  66. package/docs/superpowers/plans/2026-09-14-issue-113-foreground-preview-race.md +0 -609
  67. package/docs/superpowers/plans/2026-09-14-terminal-model.md +0 -145
  68. package/docs/superpowers/plans/2026-09-15-coordinator-pipe-root-normalize.md +0 -224
  69. package/docs/superpowers/plans/2026-09-15-lease-publish-eprem-reclaim.md +0 -341
  70. package/docs/superpowers/plans/2026-09-18-detach-anchor-reporter-endpoint.md +0 -875
  71. package/docs/superpowers/plans/2026-09-20-control-lifecycle.md +0 -116
  72. package/docs/superpowers/plans/2026-09-20-issue-121-perf-gate-out-of-coverage.md +0 -517
  73. package/docs/superpowers/plans/2026-09-23-a5-mid-stream-assertion.md +0 -25
  74. package/docs/superpowers/plans/2026-09-23-cwd-browse-tab-drill.md +0 -393
  75. package/docs/superpowers/plans/2026-09-23-terminal-query-forwarding.md +0 -51
  76. package/docs/superpowers/plans/2026-09-23-unconditional-detach-key.md +0 -58
  77. package/docs/superpowers/plans/2026-09-24-control-reconcile-flake.md +0 -698
  78. package/docs/superpowers/plans/2026-09-24-probe-attached-clients.md +0 -605
  79. package/docs/superpowers/plans/2026-09-29-issue-145-holding-semantic-state.md +0 -1183
  80. package/docs/superpowers/plans/2026-09-29-issue-145-user-verification.md +0 -66
  81. package/docs/superpowers/plans/2026-09-30-board-delete-key-x-y-confirm.md +0 -649
  82. package/docs/superpowers/plans/2026-09-30-issue-153-runner-stop-boot-window.md +0 -618
  83. package/docs/superpowers/plans/2026-09-30-issue-95-test-discipline.md +0 -555
  84. package/docs/superpowers/plans/2026-09-30-publish-dispatch-tag-check.md +0 -259
  85. package/docs/superpowers/plans/2026-10-06-attach-settle-scheme-delivery.md +0 -751
  86. package/docs/superpowers/plans/2026-10-09-issue-167-attach-middle-paste.md +0 -509
  87. package/docs/superpowers/plans/2026-10-09-issue-169-fullscreen-mouse-ownership.md +0 -346
  88. package/docs/superpowers/plans/2026-10-10-attach-delete-in-place.md +0 -832
  89. package/docs/superpowers/plans/2026-10-10-issue-174-selection-mirror-paste-source.md +0 -510
  90. package/docs/superpowers/plans/2026-10-14-issue-170-attach-reply-guard.md +0 -771
  91. package/docs/superpowers/plans/2026-10-14-issue-178-accept-registration-race.md +0 -1257
  92. package/docs/superpowers/plans/2026-10-14-issue-184-snapshot-window-contract.md +0 -657
  93. package/docs/superpowers/plans/2026-10-14-issue-185-attach-wheel-alt-screen.md +0 -47
  94. package/docs/superpowers/plans/2026-10-14-issue-187-motionless-drag.md +0 -931
  95. package/docs/superpowers/plans/2026-10-14-issue-189-mixed-input-segments.md +0 -823
  96. package/docs/superpowers/plans/2026-10-15-issue-188-x10-mouse-release.md +0 -526
  97. package/docs/superpowers/plans/2026-10-16-issue-198-attach-wheel-forward-fallback.md +0 -32
  98. package/docs/superpowers/plans/2026-10-18-attach-wheel-replay-pseudo-scrollback.md +0 -437
  99. package/docs/superpowers/plans/2026-10-19-issue-213-reply-guard-csi-key.md +0 -257
  100. package/docs/superpowers/plans/2026-10-19-issue-214-attach-forward-alt-modifier.md +0 -202
  101. package/docs/superpowers/plans/2026-10-21-issue-221-ask-user-question.md +0 -198
  102. package/docs/superpowers/plans/2026-10-23-issue-222-attach-ready.md +0 -290
  103. package/docs/superpowers/plans/2026-10-23-issue-225-monotonic-foreground-state.md +0 -450
  104. package/docs/superpowers/plans/2026-10-23-issue-229-vim-jkl.md +0 -449
  105. package/docs/superpowers/plans/2026-10-23-issue-232-attach-bg-only-cells.md +0 -153
  106. package/docs/superpowers/plans/2026-10-23-lease-heartbeat-staleness.md +0 -537
  107. package/docs/superpowers/plans/2026-10-23-selected-row-highlight.md +0 -405
  108. package/docs/superpowers/plans/2026-10-24-issue-217-windows-pid-reuse-stopping.md +0 -367
  109. package/docs/superpowers/plans/2026-10-24-issue-230-warm-host-background-runs.md +0 -806
  110. package/docs/superpowers/plans/2026-10-24-issue-238-windows-process-identity-token.md +0 -490
  111. package/docs/superpowers/plans/2026-10-24-issue-241-coordinator-root-exit.md +0 -461
  112. package/docs/superpowers/plans/2026-10-25-issue-239-coordinator-replacement-identity.md +0 -504
  113. package/docs/superpowers/plans/2026-10-26-issue-245-key-hints-doc-sync.md +0 -244
  114. package/docs/superpowers/plans/2026-10-27-issue-172-fullscreen-selection-clear.md +0 -344
  115. package/docs/superpowers/plans/2026-10-27-issue-252-select-key-hints.md +0 -209
  116. package/docs/superpowers/plans/2026-10-28-issue-255-coordinator-endpoint-zombie.md +0 -442
  117. package/docs/superpowers/plans/2026-10-28-issue-257-legacy-pty-runner-endpoint-cleanup.md +0 -256
  118. package/docs/superpowers/plans/2026-11-03-cwd-favorites-full-pool.md +0 -266
  119. package/docs/superpowers/specs/2026-08-15-screenlog-gc-design.md +0 -105
  120. package/docs/superpowers/specs/2026-08-16-attach-double-cursor-jiggle-retry-design.md +0 -142
  121. package/docs/superpowers/specs/2026-08-21-attach-coldstart-jiggle-rearm-design.md +0 -110
  122. package/docs/superpowers/specs/2026-08-21-dashboard-keypress-lag-design.md +0 -59
  123. package/docs/superpowers/specs/2026-08-21-issue-14-needs-input-no-auto-done-design.md +0 -109
  124. package/docs/superpowers/specs/2026-08-22-cwd-favorites-design.md +0 -131
  125. package/docs/superpowers/specs/2026-08-22-jiggle-shrink-and-hold-design.md +0 -111
  126. package/docs/superpowers/specs/2026-08-22-question-tool-grouping-design.md +0 -114
  127. package/docs/superpowers/specs/2026-08-22-rename-state-labels-design.md +0 -80
  128. package/docs/superpowers/specs/2026-08-27-locks-acquirelock-spin-design.md +0 -97
  129. package/docs/superpowers/specs/2026-08-27-pty-runner-test-flaky-design.md +0 -35
  130. package/docs/superpowers/specs/2026-08-29-code-refs-badges-design.md +0 -128
  131. package/docs/superpowers/specs/2026-08-30-circular-navigation-design.md +0 -47
  132. package/docs/superpowers/specs/2026-08-30-eprm-atomicwrite-race-design.md +0 -77
  133. package/docs/superpowers/specs/2026-08-30-post-exit-timing-fix-design.md +0 -80
  134. package/docs/superpowers/specs/2026-08-30-pty-attach-quality-debt-design.md +0 -105
  135. package/docs/superpowers/specs/2026-08-30-readme-v2-design.md +0 -115
  136. package/docs/superpowers/specs/2026-09-01-attach-detach-gate-cursor-anchor-design.md +0 -78
  137. package/docs/superpowers/specs/2026-09-02-attach-detach-editor-state-design.md +0 -120
  138. package/docs/superpowers/specs/2026-09-03-code-refs-pr-backlink-narrow-design.md +0 -92
  139. package/docs/superpowers/specs/2026-09-03-detach-gate-glyph-fallback-design.md +0 -99
  140. package/docs/superpowers/specs/2026-09-04-evidence-outputpreview-design.md +0 -50
  141. package/docs/superpowers/specs/2026-09-04-warm-host-reclaim-design.md +0 -106
  142. package/docs/superpowers/specs/2026-09-05-issue-13-drainnextfollowup-pty-probe-design.md +0 -64
  143. package/docs/superpowers/specs/2026-09-05-issue-38-windows-wezterm-ime-design.md +0 -48
  144. package/docs/superpowers/specs/2026-09-05-issue-39-truncate-codepoint-boundary-design.md +0 -64
  145. package/docs/superpowers/specs/2026-09-05-issue-61-mention-fallback-design.md +0 -71
  146. package/docs/superpowers/specs/2026-09-05-issue-63-flaky-manual-completion-design.md +0 -49
  147. package/docs/superpowers/specs/2026-09-05-issue-64-changelog-helper-design.md +0 -76
  148. package/docs/superpowers/specs/2026-09-05-pty-host-stacking-sock-race-design.md +0 -510
  149. package/docs/superpowers/specs/2026-09-07-issue-11-attach-runtime-desync-heal-design.md +0 -130
  150. package/docs/superpowers/specs/2026-09-08-attach-ctrl-left-detach-design.md +0 -58
  151. package/docs/superpowers/specs/2026-09-08-dashboard-shrink-repaint-design.md +0 -52
  152. package/docs/superpowers/specs/2026-09-08-legacy-stale-host-recovery-design.md +0 -87
  153. package/docs/superpowers/specs/2026-09-08-spawn-async-error-swallow-design.md +0 -56
  154. package/docs/superpowers/specs/2026-09-08-stale-model-attach-guard-design.md +0 -79
  155. package/docs/superpowers/specs/2026-09-09-claimpid-blocks-replace-design.md +0 -147
  156. package/docs/superpowers/specs/2026-09-09-harden-runner-architecture-design.md +0 -304
  157. package/docs/superpowers/specs/2026-09-14-attach-cursor-dectcem-gate-design.md +0 -114
  158. package/docs/superpowers/specs/2026-09-14-host-meta-orphan-lock-design.md +0 -120
  159. package/docs/superpowers/specs/2026-09-14-issue-106-terminal-frame-cognition-design.md +0 -146
  160. package/docs/superpowers/specs/2026-09-14-issue-113-foreground-preview-race-design.md +0 -116
  161. package/docs/superpowers/specs/2026-09-15-coordinator-pipe-root-normalize-design.md +0 -84
  162. package/docs/superpowers/specs/2026-09-15-lease-publish-eprem-reclaim-design.md +0 -92
  163. package/docs/superpowers/specs/2026-09-18-detach-anchor-reporter-endpoint-design.md +0 -123
  164. package/docs/superpowers/specs/2026-09-20-issue-121-perf-gate-out-of-coverage-design.md +0 -204
  165. package/docs/superpowers/specs/2026-09-23-a5-mid-stream-assertion-design.md +0 -43
  166. package/docs/superpowers/specs/2026-09-23-cwd-browse-tab-drill-design.md +0 -95
  167. package/docs/superpowers/specs/2026-09-23-terminal-query-forwarding-design.md +0 -63
  168. package/docs/superpowers/specs/2026-09-23-unconditional-detach-key-design.md +0 -57
  169. package/docs/superpowers/specs/2026-09-24-control-reconcile-flake-design.md +0 -98
  170. package/docs/superpowers/specs/2026-09-24-probe-attached-clients-design.md +0 -190
  171. package/docs/superpowers/specs/2026-09-29-issue-145-holding-semantic-state-design.md +0 -394
  172. package/docs/superpowers/specs/2026-09-30-board-delete-key-x-y-confirm-design.md +0 -86
  173. package/docs/superpowers/specs/2026-09-30-issue-153-runner-stop-boot-window-design.md +0 -75
  174. package/docs/superpowers/specs/2026-09-30-issue-95-test-discipline-design.md +0 -226
  175. package/docs/superpowers/specs/2026-09-30-publish-dispatch-tag-check-design.md +0 -129
  176. package/docs/superpowers/specs/2026-10-06-attach-settle-scheme-delivery-design.md +0 -196
  177. package/docs/superpowers/specs/2026-10-09-issue-167-attach-middle-paste-design.md +0 -125
  178. package/docs/superpowers/specs/2026-10-09-issue-169-fullscreen-mouse-ownership-design.md +0 -130
  179. package/docs/superpowers/specs/2026-10-10-attach-delete-in-place-design.md +0 -224
  180. package/docs/superpowers/specs/2026-10-10-issue-174-selection-mirror-paste-source-design.md +0 -154
  181. package/docs/superpowers/specs/2026-10-14-issue-170-attach-reply-guard-design.md +0 -195
  182. package/docs/superpowers/specs/2026-10-14-issue-178-accept-registration-race-design.md +0 -287
  183. package/docs/superpowers/specs/2026-10-14-issue-184-snapshot-window-contract-design.md +0 -288
  184. package/docs/superpowers/specs/2026-10-14-issue-185-attach-wheel-alt-screen-design.md +0 -126
  185. package/docs/superpowers/specs/2026-10-14-issue-187-motionless-drag-design.md +0 -269
  186. package/docs/superpowers/specs/2026-10-14-issue-189-mixed-input-segments-design.md +0 -369
  187. package/docs/superpowers/specs/2026-10-15-issue-188-x10-mouse-release-design.md +0 -252
  188. package/docs/superpowers/specs/2026-10-16-issue-198-attach-wheel-forward-fallback-design.md +0 -67
  189. package/docs/superpowers/specs/2026-10-18-attach-wheel-replay-pseudo-scrollback-design.md +0 -250
  190. package/docs/superpowers/specs/2026-10-19-issue-213-reply-guard-csi-key-design.md +0 -165
  191. package/docs/superpowers/specs/2026-10-19-issue-214-attach-forward-alt-modifier-design.md +0 -131
  192. package/docs/superpowers/specs/2026-10-20-issue-221-ask-user-question-design.md +0 -270
  193. package/docs/superpowers/specs/2026-10-22-issue-225-monotonic-foreground-state-design.md +0 -225
  194. package/docs/superpowers/specs/2026-10-23-issue-222-attach-ready-design.md +0 -204
  195. package/docs/superpowers/specs/2026-10-23-issue-229-vim-jkl-design.md +0 -90
  196. package/docs/superpowers/specs/2026-10-23-issue-232-attach-bg-only-cells-design.md +0 -146
  197. package/docs/superpowers/specs/2026-10-23-lease-heartbeat-staleness-design.md +0 -271
  198. package/docs/superpowers/specs/2026-10-23-selected-row-highlight-design.md +0 -191
  199. package/docs/superpowers/specs/2026-10-24-issue-217-windows-pid-reuse-stopping-design.md +0 -334
  200. package/docs/superpowers/specs/2026-10-24-issue-230-warm-host-background-runs-design.md +0 -283
  201. package/docs/superpowers/specs/2026-10-24-issue-238-windows-process-identity-token-design.md +0 -286
  202. package/docs/superpowers/specs/2026-10-24-issue-241-coordinator-root-exit-design.md +0 -381
  203. package/docs/superpowers/specs/2026-10-25-issue-239-coordinator-replacement-identity-design.md +0 -331
  204. package/docs/superpowers/specs/2026-10-26-issue-245-key-hints-doc-sync-design.md +0 -72
  205. package/docs/superpowers/specs/2026-10-27-issue-172-fullscreen-selection-clear-design.md +0 -248
  206. package/docs/superpowers/specs/2026-10-27-issue-252-select-key-hints-design.md +0 -95
  207. package/docs/superpowers/specs/2026-10-28-issue-255-coordinator-endpoint-zombie-design.md +0 -380
  208. package/docs/superpowers/specs/2026-10-28-issue-257-legacy-pty-runner-endpoint-cleanup-design.md +0 -197
  209. package/docs/superpowers/specs/2026-11-03-cwd-favorites-full-pool-design.md +0 -106
  210. package/scripts/budget-audit.mjs +0 -135
  211. package/scripts/patch-vulns.mjs +0 -59
  212. package/scripts/release.mjs +0 -446
  213. package/scripts/release_helper.mjs +0 -309
  214. package/scripts/run-perf-gate.mjs +0 -40
  215. 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.