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