@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,449 +0,0 @@
1
- # Vim-style j/k/l Board Keys 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:** Bind `j`/`k` navigation and `l` attach in the board's list/select/peek/evidence modes so vim users' muscle memory works, without touching text-input modes or the `h` = Hold contract (issue #229).
6
-
7
- **Architecture:** Pure additive key branches in the five per-mode handlers of `src/ui/dashboard.ts`, reusing the exact code paths of the arrow keys (`moveSelection`, `peekStep`, `attachSelected`, `attachPeek`); copy sync in the `?` help overlay, the NORMAL/SELECT hint lines, and the four README view tables. Behavior is verified by a `test-support` probe driving the public `handleInput()` with fake service/done injection.
8
-
9
- **Tech Stack:** TypeScript (runtime), `node --test` + `--experimental-transform-types` probes (test).
10
-
11
- **Spec:** `docs/superpowers/specs/2026-10-23-issue-229-vim-jkl-design.md` (acceptance IDs A1–A9, U1–U2 referenced below)
12
-
13
- ## Global Constraints
14
-
15
- - Tabs for indentation; English comments and commit messages (AGENTS.md).
16
- - Tests use `node:test` with `assert/strict`; probes run via `node --experimental-transform-types` and live in `test-support/` (excluded from tsconfig).
17
- - Conventional commit subjects `feat`/`fix`/`...` with `(issue #229)` reference; PR number appended at merge time.
18
- - `npm run verify` (typecheck + perf gate + tests + coverage thresholds lines 85 / funcs 80 / branches 70 + pack:dry) must pass before the PR.
19
- - `j`/`k` inherit **wrap-around** semantics via `moveSelection`/`peekStep` — do NOT implement clamp (spec D2).
20
- - `h` = Hold (PR #151 contract), dispatch/filter/rename/reply stay literal — off-limits (spec D4/D6).
21
- - Worktree discipline: all edits under `/home/elling/work/git-repo/pi-agent-board/.pi/worktrees/issue-229-vim-jkl-board-list` (= `$WT`), git via `git -C $WT`.
22
-
23
- ## Review Focus
24
-
25
- 1. **Uppercase `J`/`K`/`L` stay unbound** — list mode still shows the printable fallback toast for them. Expected: unchanged current behavior, pinned by a probe assertion (Task 1, Step 1).
26
- 2. **`l` on a busy row** (`hostActive=false && agentBusy=true`) must take the same `confirm-stop-first` branch as `→`, not attach directly. Pinned by the `busyL` probe segment (Task 1, Step 1).
27
- 3. **Help rows got longer** — at 80 cols `renderCenteredBox` clips (existing fallback, `dashboard.ts:1846`); full wording must be visible at 120 cols. Pinned by the render assertions (Task 2, Step 1).
28
- 4. **`j`/`k` in select mode trigger `prewarmScheduler.schedule()`** — inherited `moveSelection` side effect, select is in the `prewarmSelected` allow-list, no behavior change. No test; reviewers confirm no new coupling.
29
- 5. **Bracketed paste containing `j`/`k`/`l` must not move the selection** (multi-byte paste string never equals a single-byte key). Pinned by the paste segment (Task 1, Step 1).
30
-
31
- ---
32
-
33
- ### Task 1: Key bindings in the five mode handlers
34
-
35
- **Files:**
36
- - Create: `test-support/vim-keys-wrap.ts`
37
- - Create: `test/dashboard-vim-keys.test.mjs`
38
- - Modify: `src/ui/dashboard.ts` (handleListKey :359, handleSelectKey :400, handlePeekKey :427, handleEvidenceKey :484 — line refs are main @ 759d659)
39
-
40
- **Interfaces:**
41
- - Consumes: existing `moveSelection(delta)`, `peekStep(delta)`, `attachSelected()`, `attachPeek()`, `planDashboardAttach` (src/ui/dashboard-decisions.mjs:34).
42
- - Produces: no new symbols; later tasks rely on the probe's `out.copy` field added in Task 2.
43
-
44
- **Spec acceptance IDs:** A1, A2, A3, A4, A5, A6, A9 (+ Review Focus 1, 2, 5). Spec D3 dead-code cleanup folded in (Step 4).
45
-
46
- - [ ] **Step 1: Write the failing probe and test**
47
-
48
- Create `test-support/vim-keys-wrap.ts`:
49
-
50
- ```ts
51
- // Vim-keys probe: drive j/k/l through the public handleInput() across modes
52
- // and report observations as JSON (issue #229, spec A1-A9 + review-focus pins).
53
- // Run via `node --experimental-transform-types`. Not typechecked.
54
- import { mkdtempSync } from "node:fs";
55
- import { tmpdir } from "node:os";
56
- import { join } from "node:path";
57
- import { createService } from "../src/runtime/service.mjs";
58
- import { createView } from "../src/core/store.mjs";
59
- import { DashboardComponent } from "../src/ui/dashboard.ts";
60
-
61
- const root = mkdtempSync(join(tmpdir(), "agentview-vim-keys-"));
62
- createView(root, { id: "v1", name: "one", cwd: root });
63
- createView(root, { id: "v2", name: "two", cwd: root });
64
- createView(root, { id: "v3", name: "three", cwd: root });
65
-
66
- const service = createService({
67
- root,
68
- runnerScript: "/no/runner.mjs",
69
- piCommand: "pi",
70
- piArgsPrefix: [],
71
- defaultCwd: root,
72
- launch: () => ({ pid: null, configPath: "/no/config.json" }),
73
- launchHost: () => ({ pid: null, configPath: "/no/host-config.json" }),
74
- launchTitle: () => ({ pid: null, configPath: "/no/title-config.json" }),
75
- });
76
-
77
- const tui = { terminal: { rows: 24, cols: 80, columns: 80, write: () => {} }, requestRender: () => {} };
78
- const theme = { fg: (_c, t) => t, bold: (t) => t, bg: (_c, t) => t };
79
- const doneCalls = [];
80
- const dash = new DashboardComponent(tui, theme, {}, (r) => doneCalls.push(r), {
81
- service, root, defaultCwd: root,
82
- availableModels: [], currentModel: null, currentThinkingLevel: "off",
83
- });
84
-
85
- // TS-private is runtime-accessible; read white-box state for assertions.
86
- const ids = dash.orderedIds;
87
- const out = { ids, list: [], select: [], peek: [], attach: [], negative: [], scroll: [], text: {}, notices: [], busyL: null };
88
-
89
- // A1: list j/k == arrow keys (wrap at both ends); j must not pop the INSERT toast.
90
- dash.handleInput("j"); out.list.push(dash.selectedId);
91
- out.notices.push(dash.inputNotice === null);
92
- dash.handleInput("j"); out.list.push(dash.selectedId);
93
- dash.handleInput("j"); out.list.push(dash.selectedId); // wrap last -> first
94
- dash.handleInput("k"); out.list.push(dash.selectedId); // wrap first -> last
95
- // RF1: uppercase J stays on the printable fallback (current behavior pinned).
96
- dash.handleInput("J"); out.notices.push(dash.inputNotice !== null);
97
- // RF5: bracketed paste containing j/k/l must not move the selection.
98
- dash.handleInput("\x1b[200~jkl\x1b[201~"); out.list.push(dash.selectedId);
99
-
100
- // A2: select mode j/k move the selection (wrap).
101
- dash.mode = "select"; dash.selectedId = ids[0];
102
- dash.handleInput("j"); out.select.push(dash.selectedId);
103
- dash.handleInput("k"); out.select.push(dash.selectedId);
104
- dash.handleInput("k"); out.select.push(dash.selectedId); // wrap first -> last
105
-
106
- // A9 negative: select "l" must not attach / leave the mode.
107
- const doneBeforeSelect = doneCalls.length;
108
- dash.handleInput("l");
109
- out.negative.push(["select-l", dash.mode, doneCalls.length - doneBeforeSelect]);
110
- dash.mode = "list";
111
-
112
- // A3: peek j/k == peekStep (wrap).
113
- dash.mode = "peek"; dash.peekId = ids[0]; dash.selectedId = ids[0];
114
- dash.handleInput("j"); out.peek.push(dash.peekId);
115
- dash.handleInput("k"); out.peek.push(dash.peekId);
116
- dash.handleInput("k"); out.peek.push(dash.peekId); // wrap first -> last
117
-
118
- // A4: l attaches in list / peek / evidence (non-busy rows -> direct done).
119
- dash.mode = "list"; dash.selectedId = ids[0];
120
- dash.handleInput("l"); out.attach.push(doneCalls.at(-1));
121
- dash.mode = "peek"; dash.peekId = ids[1]; dash.selectedId = ids[1];
122
- dash.handleInput("l"); out.attach.push(doneCalls.at(-1));
123
- dash.mode = "evidence"; dash.selectedId = ids[2];
124
- dash.handleInput("l"); out.attach.push(doneCalls.at(-1));
125
-
126
- // A9 negative: session "l" must not attach / leave the mode.
127
- dash.mode = "session"; dash.selectedId = ids[0];
128
- const doneBeforeSession = doneCalls.length;
129
- dash.handleInput("l");
130
- out.negative.push(["session-l", dash.mode, doneCalls.length - doneBeforeSession]);
131
-
132
- // A6: session / evidence j/k still scroll (regression guard).
133
- dash.mode = "session";
134
- dash.handleInput("j"); out.scroll.push(dash.sessionScrollTop);
135
- dash.handleInput("k"); out.scroll.push(dash.sessionScrollTop);
136
- dash.mode = "evidence";
137
- dash.handleInput("j"); out.scroll.push(dash.evidenceScrollTop);
138
- dash.handleInput("k"); out.scroll.push(dash.evidenceScrollTop);
139
-
140
- // A5: text modes keep j/k/l literal.
141
- for (const m of ["dispatch", "filter", "rename", "reply"]) {
142
- dash.mode = m; dash.input = "";
143
- dash.handleInput("j"); dash.handleInput("k"); dash.handleInput("l");
144
- out.text[m] = dash.input;
145
- }
146
- dash.mode = "list"; dash.input = "";
147
-
148
- // RF2: busy row + l -> confirm branch (same as →), not a direct attach.
149
- const busyService = {
150
- rows: () => [{
151
- meta: { id: "b1", name: "busy", cwd: "/", sessionFile: "/s/b1.jsonl" },
152
- state: { semanticState: "working", processState: "alive", pendingQuestions: [] },
153
- alive: true, hostAlive: false, hostActive: false, hostReady: false, host: null,
154
- }],
155
- reconcile: async () => 0,
156
- };
157
- const done2 = [];
158
- const dash2 = new DashboardComponent(tui, theme, {}, (r) => done2.push(r), {
159
- service: busyService, root, defaultCwd: root,
160
- availableModels: [], currentModel: null, currentThinkingLevel: "off",
161
- });
162
- dash2.selectedId = "b1";
163
- dash2.handleInput("l");
164
- out.busyL = { mode: dash2.mode, done: done2.length, prompt: dash2.pending?.prompt ?? null };
165
- dash2.dispose();
166
-
167
- dash.dispose();
168
- console.log(JSON.stringify(out));
169
- ```
170
-
171
- Create `test/dashboard-vim-keys.test.mjs`:
172
-
173
- ```js
174
- import assert from "node:assert/strict";
175
- import { execFileSync } from "node:child_process";
176
- import { fileURLToPath } from "node:url";
177
- import { join } from "node:path";
178
- import test from "node:test";
179
-
180
- const ROOT_DIR = fileURLToPath(new URL("../", import.meta.url));
181
- const PROBE = join(ROOT_DIR, "test-support", "vim-keys-wrap.ts");
182
-
183
- test("vim j/k/l bindings mirror the arrow keys across board modes (issue #229)", () => {
184
- const out = JSON.parse(execFileSync(process.execPath, ["--experimental-transform-types", PROBE], { encoding: "utf8", timeout: 30_000 }));
185
- const { ids } = out;
186
- assert.equal(ids.length, 3, "probe must see 3 rows");
187
- // A1: list j/k wrap like the arrows; j/k never pop the INSERT toast.
188
- assert.deepEqual(out.list, [ids[1], ids[2], ids[0], ids[2], ids[2]], "j down x2, wrap; k wrap; paste no-move");
189
- assert.deepEqual(out.notices, [true, true], "j: no INSERT toast; J: keeps the printable fallback");
190
- // A2: select mode j/k move (wrap).
191
- assert.deepEqual(out.select, [ids[1], ids[0], ids[2]]);
192
- // A3: peek j/k step (wrap).
193
- assert.deepEqual(out.peek, [ids[1], ids[0], ids[2]]);
194
- // A4: l attaches in list / peek / evidence.
195
- assert.deepEqual(out.attach.map((r) => [r.action, r.viewId, r.stopFirst]), [
196
- ["attach", ids[0], false],
197
- ["attach", ids[1], false],
198
- ["attach", ids[2], false],
199
- ]);
200
- // A9: l stays unbound in select / session.
201
- assert.deepEqual(out.negative, [["select-l", "select", 0], ["session-l", "session", 0]]);
202
- // A6: session / evidence j/k still scroll.
203
- assert.deepEqual(out.scroll, [1, 0, 1, 0]);
204
- // A5: text modes keep j/k/l literal.
205
- for (const m of ["dispatch", "filter", "rename", "reply"]) assert.equal(out.text[m], "jkl", m);
206
- // RF2: busy row + l -> confirm branch, no direct attach.
207
- assert.equal(out.busyL.mode, "confirm");
208
- assert.equal(out.busyL.done, 0);
209
- assert.match(out.busyL.prompt, /Interrupt and attach/);
210
- });
211
- ```
212
-
213
- - [ ] **Step 2: Run the test to verify it fails**
214
-
215
- Run: `cd $WT && node --test test/dashboard-vim-keys.test.mjs`
216
- Expected: FAIL — list assertion shows `j` does not move the selection (falls into the printable fallback instead).
217
-
218
- - [ ] **Step 3: Implement the key bindings**
219
-
220
- In `src/ui/dashboard.ts`:
221
-
222
- `handleListKey` — extend the three branches:
223
-
224
- ```ts
225
- if (matchesKey(data, Key.up) || data === "k") return void this.moveSelection(-1);
226
- if (matchesKey(data, Key.down) || data === "j") return void this.moveSelection(1);
227
- if (matchesKey(data, Key.right) || data === ">" || data === "l") return this.attachSelected();
228
- ```
229
-
230
- `handleSelectKey`:
231
-
232
- ```ts
233
- if (matchesKey(data, Key.up) || data === "k") return void this.moveSelection(-1);
234
- if (matchesKey(data, Key.down) || data === "j") return void this.moveSelection(1);
235
- ```
236
-
237
- `handlePeekKey`:
238
-
239
- ```ts
240
- if (matchesKey(data, Key.right) || data === ">" || data === "l") return this.attachPeek();
241
- // ...
242
- if (matchesKey(data, Key.up) || data === "k") {
243
- this.peekStep(-1);
244
- return;
245
- }
246
- if (matchesKey(data, Key.down) || data === "j") {
247
- this.peekStep(1);
248
- return;
249
- }
250
- ```
251
-
252
- `handleEvidenceKey`:
253
-
254
- ```ts
255
- if (data === "a" || matchesKey(data, Key.enter) || matchesKey(data, Key.right) || data === "l") return this.attachSelected();
256
- ```
257
-
258
- - [ ] **Step 4: Run the test to verify it passes, then remove the dead duplicate lines (spec D3)**
259
-
260
- Run: `cd $WT && node --test test/dashboard-vim-keys.test.mjs`
261
- Expected: PASS.
262
-
263
- Then in `handleListKey` delete the duplicated lines (zero behavior change, re-run the test after):
264
-
265
- ```ts
266
- if (data === "d") return this.confirmDone();
267
- if (data === "d") return this.confirmDone(); // DELETE this duplicate
268
- ```
269
- ```ts
270
- if (matchesKey(data, Key.ctrl("x"))) return this.handleDeleteKey();
271
- if (matchesKey(data, Key.ctrl("x"))) return this.handleDeleteKey(); // DELETE this duplicate
272
- ```
273
-
274
- Run: `cd $WT && node --test test/dashboard-vim-keys.test.mjs test/dashboard-delete-key.test.mjs test/dashboard-hold-key.test.mjs`
275
- Expected: all PASS (no regression in the neighboring key paths).
276
-
277
- - [ ] **Step 5: Typecheck and commit**
278
-
279
- Run: `cd $WT && npm run typecheck && node --test test/dashboard-navigation.test.mjs` (existing wrap test still passes)
280
- Then:
281
-
282
- ```bash
283
- git -C $WT add test-support/vim-keys-wrap.ts test/dashboard-vim-keys.test.mjs src/ui/dashboard.ts
284
- git -C $WT commit -m "feat(ui): vim-style j/k navigation and l attach in board modes (issue #229)"
285
- ```
286
-
287
- ---
288
-
289
- ### Task 2: Help overlay and hint-line copy
290
-
291
- **Files:**
292
- - Modify: `test-support/vim-keys-wrap.ts` (append render segment)
293
- - Modify: `test/dashboard-vim-keys.test.mjs` (append A7 test)
294
- - Modify: `src/ui/dashboard.ts` (renderHelp rows :1701-1729, listHints :1342-1364)
295
-
296
- **Interfaces:**
297
- - Consumes: Task 1's probe file and `dash.render(width): string[]` (public, :1212).
298
- - Produces: probe `out.copy = { help120, listRender, selectRender, help80Lines }`.
299
-
300
- **Spec acceptance IDs:** A7 (+ Review Focus 3). Covers spec §4 items 1-3 including the D5 doc-debt rows.
301
-
302
- - [ ] **Step 1: Extend the probe with render capture and write the failing A7 test**
303
-
304
- Append before `dash.dispose();` in `test-support/vim-keys-wrap.ts`:
305
-
306
- ```ts
307
- // A7: help overlay + hint lines copy.
308
- dash.mode = "help"; dash.helpReturnMode = "list";
309
- const help120 = dash.render(120).join("\n");
310
- const help80 = dash.render(80); // must not throw; renderCenteredBox clips
311
- dash.mode = "list";
312
- const listRender = dash.render(120).join("\n");
313
- dash.mode = "select";
314
- const selectRender = dash.render(120).join("\n");
315
- dash.mode = "list";
316
- out.copy = { help120, listRender, selectRender, help80Lines: help80.length };
317
- ```
318
-
319
- Append to `test/dashboard-vim-keys.test.mjs`:
320
-
321
- ```js
322
- test("help overlay and hint lines document j/k/l (issue #229 A7)", () => {
323
- const out = JSON.parse(execFileSync(process.execPath, ["--experimental-transform-types", PROBE], { encoding: "utf8", timeout: 30_000 }));
324
- const { help120, listRender, selectRender, help80Lines } = out.copy;
325
- assert.match(help120, /↑\/↓ or j\/k/);
326
- assert.match(help120, /→ \/ > \/ l/);
327
- assert.match(help120, /In select.*j\/k/);
328
- assert.match(help120, /In peek.*↑↓\/j\/k/);
329
- assert.match(help120, /In peek.*→\/l attach/);
330
- assert.match(help120, /In session.*↑↓\/j\/k scroll/);
331
- assert.match(help120, /In evidence.*↑↓\/j\/k scroll/);
332
- assert.match(help120, /In evidence.*→\/l attach/);
333
- // Negative: stale pre-change wording is gone from every row that named bare arrows.
334
- assert.doesNotMatch(help120, /In peek.*· ↑↓ adjacent/);
335
- assert.doesNotMatch(help120, /In session.*· ↑↓ scroll/);
336
- assert.doesNotMatch(help120, /In evidence.*· ↑↓ scroll/);
337
- assert.match(listRender, /→\/l attach/);
338
- assert.match(selectRender, /↑↓\/j\/k move/);
339
- assert.ok(help80Lines > 0, "80-col help render must not throw (RF3)");
340
- });
341
- ```
342
-
343
- - [ ] **Step 2: Run the test to verify it fails**
344
-
345
- Run: `cd $WT && node --test test/dashboard-vim-keys.test.mjs`
346
- Expected: FAIL — `↑/↓ or j/k` not found in the help render.
347
-
348
- - [ ] **Step 3: Update the help overlay rows and hint lines**
349
-
350
- In `renderHelp` (`src/ui/dashboard.ts:1701-1729`), change these rows:
351
-
352
- ```ts
353
- ["↑/↓ or j/k", "Normal: move selection; Insert: move cursor"],
354
- ["→ / > / l", "Attach to the selected real Pi session"],
355
- ["In select", "j/k move · a select all · u clear · d move done · ctrl+x delete · esc cancel"],
356
- ["In peek", "→/l attach · v transcript · e evidence · r reply · ↑↓/j/k adjacent"],
357
- ["In session", "← back · ↑↓/j/k scroll · e evidence · enter attach · r reply"],
358
- ["In evidence", "← back · ↑↓/j/k scroll · →/l attach · r reply · v transcript · x clear diag"],
359
- ```
360
-
361
- (Note the trailing `x clear diag` — shortened from `x clear diagnostics` so the longer row stays readable; `renderCenteredBox` clips at narrow widths regardless, RF3.)
362
-
363
- In `listHints` (:1342-1364): SELECT branch gains `"↑↓/j/k move"` as the first hint after the count/unread spread; NORMAL row changes `"→ attach"` to `"→/l attach"`.
364
-
365
- - [ ] **Step 4: Run the tests to verify they pass**
366
-
367
- Run: `cd $WT && node --test test/dashboard-vim-keys.test.mjs`
368
- Expected: PASS (both tests).
369
-
370
- - [ ] **Step 5: Commit**
371
-
372
- ```bash
373
- git -C $WT add test-support/vim-keys-wrap.ts test/dashboard-vim-keys.test.mjs src/ui/dashboard.ts
374
- git -C $WT commit -m "docs(ui): document j/k/l in help overlay and hint lines (issue #229)"
375
- ```
376
-
377
- ---
378
-
379
- ### Task 3: README key tables and full verification gate
380
-
381
- **Files:**
382
- - Modify: `README.md` (Main list table, Peek table, Transcript table, Evidence table)
383
-
384
- **Interfaces:**
385
- - Consumes: Task 1/2 behavior as implemented.
386
- - Produces: docs matching behavior; A8 green run.
387
-
388
- **Spec acceptance IDs:** A8 (+ U2 groundwork — the tables are what U2 checks against).
389
-
390
- - [ ] **Step 1: Update the four README tables**
391
-
392
- In `README.md`:
393
-
394
- Main list table:
395
- - `| `Up` / `Down` | Move the selection. |` → `| `Up` / `Down` / `j` / `k` | Move the selection. |`
396
- - `| `Right` / `>` | Attach to the selected session. |` → `| `Right` / `>` / `l` | Attach to the selected session. |`
397
-
398
- Peek table:
399
- - `| `a`, `Right`, or `>` | Attach to the session. |` → `| `a`, `Right`, `>`, or `l` | Attach to the session. |`
400
- - `| `Up` / `Down` | Move to the previous or next session. |` → `| `Up` / `Down` / `j` / `k` | Move to the previous or next session. |`
401
-
402
- Transcript table:
403
- - `| `Up` / `Down` | Scroll one line. |` → `| `Up` / `Down` / `j` / `k` | Scroll one line. |`
404
-
405
- Evidence table:
406
- - `| `Up` / `Down` | Scroll one line. |` → `| `Up` / `Down` / `j` / `k` | Scroll one line. |`
407
- - `| `Enter`, `a`, or `Right` | Attach to the session. |` → `| `Enter`, `a`, `Right`, or `l` | Attach to the session. |`
408
-
409
- - [ ] **Step 2: Run the full verification gate**
410
-
411
- Run: `cd $WT && npm run verify`
412
- Expected: PASS — typecheck, perf gate, all tests, coverage thresholds (lines 85 / funcs 80 / branches 70), pack:dry.
413
-
414
- - [ ] **Step 3: Commit**
415
-
416
- ```bash
417
- git -C $WT add README.md
418
- git -C $WT commit -m "docs: document j/k/l keys in the README view tables (issue #229)"
419
- ```
420
-
421
- ---
422
-
423
- ### Task 4: Post-implementation manual verification (pre-merge, by user)
424
-
425
- **Spec acceptance IDs:** U1, U2. No code changes; this task is the executable checklist.
426
-
427
- - [ ] **Step 1: U1 — real dashboard feel**
428
-
429
- Deploy prelude (prevents loading stale code): `pi list` to record the current install source (this machine: `git:github.com/zhuxixi/pi-agent-board`) → `pi remove <that source>` → `pi install /home/elling/work/git-repo/pi-agent-board/.pi/worktrees/issue-229-vim-jkl-board-list` → `pi /agent-board`.
430
-
431
- Checklist: ① `j`/`k` walk list rows incl. wrap at both ends ② `j` no longer pops the INSERT toast ③ `l` attaches the selected session ④ after detach, `?` shows the new bindings (incl. the evidence row's `→/l attach`) ⑤ in INSERT mode `j`/`k`/`l` stay literal characters.
432
-
433
- Cleanup: `pi remove /home/elling/work/git-repo/pi-agent-board/.pi/worktrees/issue-229-vim-jkl-board-list` → reinstall the recorded original source.
434
-
435
- - [ ] **Step 2: U2 — README vs behavior**
436
-
437
- Walk the four README tables row by row against the live dashboard; every listed key works, no key is missing.
438
-
439
- - [ ] **Step 3: Record results**
440
-
441
- Comment the U1/U2 outcomes on issue #229 before merge. If any item fails or cannot run, mark it `pending` — do not claim full acceptance.
442
-
443
- ---
444
-
445
- ## Self-Review Notes
446
-
447
- - **Spec coverage:** §3 bindings → Task 1; §4 copy items 1-3 → Task 2, items 4-5 → Task 3; D3 dead-code → Task 1 Step 4; A1-A9 → Task 1/2; A8 → Task 3; U1/U2 → Task 4. Every acceptance ID has a task; every task cites IDs.
448
- - **Placeholder scan:** none — all code and commands are concrete.
449
- - **Type consistency:** probe field names (`out.list/select/peek/attach/negative/scroll/text/notices/busyL/copy`) match between the probe and both tests; `DashboardResult` shape `{action, viewId, stopFirst}` matches `src/ui/dashboard.ts:58`.
@@ -1,153 +0,0 @@
1
- # Attach Background-Only Cells 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:** Stop `lineToAnsi()` from trimming cells that carry only a non-default background, so full-screen TUIs (nvim, lazygit, htop) keep their theme background in the attach view.
6
-
7
- **Architecture:** The per-line scan in `lineToAnsi()` already walks every cell; it gains a `lastBg` cursor (last cell with `!isBgDefault()`), and the render bound becomes `renderEnd = max(last, lastBg)`. The main loop, the empty-line guard, and the cursor-past-content check all switch from `last` to `renderEnd`. Default-background screens keep the existing cheap path (`""` for blank lines, no padding). A component-level smoke fixture drives a real `@xterm/headless` terminal through `PtyAttachComponent.render()` and asserts the emitted ANSI.
8
-
9
- **Tech Stack:** TypeScript (`src/ui`), Node 22/24 `node:test`, `node --experimental-transform-types` fixtures, `@xterm/headless`.
10
-
11
- **Spec:** `docs/superpowers/specs/2026-10-23-issue-232-attach-bg-only-cells-design.md`
12
-
13
- **Worktree:** `/home/elling/work/git-repo/pi-agent-board/.pi/worktrees/issue-232-attach-bg-only-cells` — 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 (`fix(attach): …`) with `(#232)` only on the PR title.
18
- - Do not change selection/copy semantics, OSC-8 link handling, mouse handling, or the wide-char skip (`getWidth() === 0`).
19
- - Padding must never be emitted for default-background cells — the cheap path for ordinary shells stays byte-identical.
20
- - `git add <explicit files>` only; never `git add -A`.
21
- - `npm run verify` (typecheck + perf + test + coverage + pack:dry) is the gate before claiming done.
22
- - Fixture cells that must be background-only are produced **only** by erase sequences (`\x1b[K` / `\x1b[2J`) under an active bg; literal spaces keep `getChars()` non-empty and make the test pass on unpatched code (spec A8).
23
-
24
- ## Review Focus
25
-
26
- 1. **Blank painted line** — all cells `getChars() === ""`, `isBgDefault() === false`; the line must render as an SGR run plus padding, not `""`. → A1.
27
- 2. **Trailing paint after content** — characters end at col 1, paint starts at col 40; cols 2–39 stay default, 40–79 painted. → A2.
28
- 3. **Default-background regression** — plain text screens still serialize blank lines to `""` (no padding, no SGR). → A3.
29
- 4. **Wide chars** — `中` (cols 0–1, continuation cell skipped) followed by painted erase cells; display width must stay 80 with 78 padding spaces. → A4.
30
- 5. **Cursor inside the padded region** — exactly one `CURSOR_MARKER` and one inverse block, no double block. → A5.
31
- 6. **Selection over padding** — padded cells inside a selection render inverse (`7`); copied text is unaffected. → A9.
32
-
33
- ---
34
-
35
- ### Task 1: Fixture and failing tests (red)
36
-
37
- **Files:**
38
- - Create: `test-support/attach-bg-paint-smoke.ts`
39
- - Create: `test/pty-attach-bg-paint.test.mjs`
40
-
41
- **Interfaces:**
42
- - Consumes: `PtyAttachComponent` (`src/ui/pty-attach.ts:162`), constructor `(tui, theme, keybindings, done, opts)`; `render(width: number): string[]` returns `[header, ...body(rows-2), footer]`.
43
- - Produces: JSON on stdout from the fixture:
44
-
45
- ```ts
46
- {
47
- blankPainted: string[]; // 10 body lines, whole screen erased with bg #1e1e2e
48
- contentPainted: string[]; // one row: "AB" + 38 default spaces + 40 painted cells
49
- plain: string[]; // ordinary text screen (default bg)
50
- wide: string[]; // "中" + 78 painted cells
51
- cursor: string[]; // painted blank row, cursor visible at col 30
52
- selection: string[]; // painted blank row, selection cols 20–60
53
- }
54
- ```
55
-
56
- - [ ] **Step 1: Write the fixture.** Model it on `test-support/hyperlink-cell-smoke.ts` (stub `tui` with `terminal: { rows: 12, cols: 80, columns: 80, write: () => {} }`, identity `theme`, `socketPath: "/no/such/socket"`, `dispose()` in `finally`). Cast the component to expose `term`, `receivedOutput`, `finishAttachTransition`, and `selection`.
57
-
58
- Per scenario:
59
- - `blankPainted`: `\x1b[48;2;30;30;46m\x1b[2J` then `\x1b[?25l` (hide cursor).
60
- - `contentPainted`: default-bg rows, then `AB\x1b[41G\x1b[48;2;30;30;46m\x1b[K` on one row; hide cursor.
61
- - `plain`: `hello\r\nworld` only; hide cursor.
62
- - `wide`: `中\x1b[48;2;30;30;46m\x1b[K`; hide cursor.
63
- - `cursor`: painted blank row (`\x1b[48;2;30;30;46m\x1b[2J`), then park the cursor visibly at row 5 col 30 via `\x1b[6;31H`; do **not** send `\x1b[?25l`.
64
- - `selection`: painted blank screen; set the private `selection = { anchor: { line: 5, col: 20 }, focus: { line: 5, col: 60 } }` (type `MouseSelection`, `src/ui/pty-attach.ts:159`) before `finishAttachTransition()`.
65
-
66
- Return `attach.render(80).slice(1, -1)` per scenario (the component's body lines; height comes from `tui.terminal.rows - 2`).
67
-
68
- - [ ] **Step 2: Write the assertions** in `test/pty-attach-bg-paint.test.mjs`, spawning the fixture with `execFileSync(process.execPath, ["--experimental-transform-types", FIXTURE], { encoding: "utf8", timeout: 30_000 })` exactly like `test/pty-attach-hyperlinks.test.mjs`:
69
- - A1: every `blankPainted` line is non-empty and includes `48;2;30;30;46`.
70
- - A2: the `contentPainted` line containing `AB` has exactly 38 spaces before the next SGR, and the paint run covers 40 cells.
71
- - A3: in `plain`, the blank body lines are exactly `""`.
72
- - A4: the `wide` line contains `中`, ends with 78 spaces plus `\x1b[0m`, and has display width 80.
73
- - A5: the cursor row contains exactly one `\x1b_pi:c\x07` and one `\x1b[7m \x1b[0m`.
74
- - A9: the selection row contains an SGR that includes `7` (e.g. `\x1b[0;7;`) inside the padded region.
75
-
76
- - [ ] **Step 3: Run the tests and record the red output.**
77
-
78
- ```bash
79
- node --test test/pty-attach-bg-paint.test.mjs
80
- ```
81
-
82
- Expected: A1, A2, A4, A5, A9 fail against the unpatched `lineToAnsi` (blank painted lines are `""`, padding absent). A3 passes. Save the output — it is the A8 "red" evidence for the PR description.
83
-
84
- ---
85
-
86
- ### Task 2: Patch `lineToAnsi()` (green)
87
-
88
- **Files:**
89
- - Modify: `src/ui/pty-attach.ts` (`lineToAnsi`, `:1962-2026`)
90
-
91
- **Interfaces:**
92
- - Consumes: `BufferCellLike.isBgDefault()` (`:142`), already on the interface.
93
- - Produces: no signature change.
94
-
95
- - [ ] **Step 1: Add the painted-extent scan (C1).** After the `if (!line)` early return, declare `let lastBg = -1;`, and inside the scan loop add `if (!cell.isBgDefault()) lastBg = x;` after the `getChars()` check. Compute `const renderEnd = Math.max(last, lastBg);` and make the empty-line guard test `renderEnd < 0`.
96
-
97
- - [ ] **Step 2: Render through the painted extent (C2).** Change the main loop bound to `x <= renderEnd`.
98
-
99
- - [ ] **Step 3: Fix the cursor bound (C3).** Change the post-loop check to `cursor!.col > renderEnd` and update the adjacent comment to say "painted region" rather than "line content".
100
-
101
- - [ ] **Step 4: Add the explanatory comment** above `lastBg` (English): full-screen TUIs paint an explicit theme bg over empty regions; trimming at the last character drops it and lets the host terminal's default bg show through (issue #232). Keep it to 3–4 lines.
102
-
103
- - [ ] **Step 5: Run the new tests.**
104
-
105
- ```bash
106
- node --test test/pty-attach-bg-paint.test.mjs
107
- ```
108
-
109
- Expected: all pass, including A3 (plain screen unchanged).
110
-
111
- - [ ] **Step 6: Commit.**
112
-
113
- ```bash
114
- git add src/ui/pty-attach.ts test-support/attach-bg-paint-smoke.ts test/pty-attach-bg-paint.test.mjs
115
- git commit -m "fix(attach): render cells that only carry a background (#232)"
116
- ```
117
-
118
- ---
119
-
120
- ### Task 3: Red-green mutation proof (A8)
121
-
122
- - [ ] **Step 1:** Temporarily change the main loop bound back to `x <= last` (leave `renderEnd` in place).
123
- - [ ] **Step 2:** Run `node --test test/pty-attach-bg-paint.test.mjs`; expect A1/A2/A4 to fail.
124
- - [ ] **Step 3:** Restore `x <= renderEnd`; re-run; expect all pass.
125
- - [ ] **Step 4:** Record both outputs (commands + failure names) for the PR description. Do not commit the reverted state.
126
-
127
- ---
128
-
129
- ### Task 4: Full gate
130
-
131
- - [ ] **Step 1:** `npm run typecheck` → exit 0 (A6; note `test-support/` is outside `tsconfig`, so fixture types surface through Task 1 instead).
132
- - [ ] **Step 2:** `npm run verify` → typecheck + perf gate + full suite + coverage thresholds (lines 85 / funcs 80 / branches 70) + pack dry-run (A7).
133
-
134
- ---
135
-
136
- ### Task 5: Manual acceptance (U1, after the PR branch is installable)
137
-
138
- - [ ] **Step 1:** Confirm the worktree HEAD equals the PR head (`git log -1`).
139
- - [ ] **Step 2:** `npm install` in the worktree; load the checkout with `pi install "$(pwd)"`; **restart pi**.
140
- - [ ] **Step 3:** `/agent-board` → attach to an nvim session with catppuccin-mocha; observe the empty buffer area (`#1e1e2e`) and the cursorline (highlight spans the full row).
141
- - [ ] **Step 4:** `pi remove "$(pwd)"`; report the observation in the PR/issue.
142
-
143
- ---
144
-
145
- ## Acceptance traceability
146
-
147
- | Spec ID | Plan task |
148
- | --- | --- |
149
- | A1, A2, A3, A4, A5, A9 | Task 1 (red) + Task 2 Step 5 (green) |
150
- | A8 | Task 3 (+ Task 1 Step 3 red output) |
151
- | A6 | Task 4 Step 1 |
152
- | A7 | Task 4 Step 2 |
153
- | U1 | Task 5 |