@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,244 +0,0 @@
1
- # Key-Hint Doc Sync 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:** Sync three stale key hints with the bindings already implemented in `src/ui/dashboard.ts` (issue #245): the peek footer's key list, the README Peek/Transcript tables' missing `!`, and the README's undocumented multi-select keys.
6
-
7
- **Architecture:** One string literal in `renderPeek` plus three README edits; one render assertion added to the existing `test-support/vim-keys-wrap.ts` probe so the peek footer cannot drift again. No handler logic, no new files, no new test infrastructure.
8
-
9
- **Tech Stack:** TypeScript (runtime), `node --test` + `--experimental-transform-types` probe (test), Markdown (docs).
10
-
11
- **Spec:** `docs/superpowers/specs/2026-10-26-issue-245-key-hints-doc-sync-design.md` (decisions D1–D5; spec §6 option **A** — multi-select gets its own 9-row table — confirmed by the user)
12
-
13
- ## Global Constraints
14
-
15
- - Tabs for indentation in `.ts`; English comments and commit messages.
16
- - **Do not touch any handler logic** (spec D5): no changes to `handleListKey` / `handleSelectKey` / `handlePeekKey` / `handleSessionKey` / `handleEvidenceKey`.
17
- - **Do not touch the `?` help overlay or the NORMAL/SELECT hint lines** — they are already correct (spec D5).
18
- - The multi-select table is **exactly 9 rows**, one per `handleSelectKey` binding, including `?` and `!` (spec D3, hardened by cross-review R1-D1).
19
- - Do **not** add a `!` row to the Evidence table: `handleEvidenceKey` has no `!` binding (spec §7).
20
- - `npm run verify` must pass before the PR (AGENTS.md).
21
- - Worktree discipline: every path under `/home/elling/work/git-repo/pi-agent-board/.pi/worktrees/issue-245-key-hints-doc-sync` (= `$WT`); git via `git -C $WT`. Branch `issue-245-key-hints-doc-sync`, commit subjects end with `(issue #245)` **and** the PR-number suffix `(#N)` once known.
22
-
23
- ## Review Focus
24
-
25
- These are the conditions the spec implies but no task's tests can cover; each is pinned in the task noted.
26
-
27
- 1. **Peek footer is the last line of `renderPeek`, and `fitToHeight` slices the tail** (`src/ui/dashboard.ts:1280`). A tall peek body silently truncates the footer, making the new assertion fail for the wrong reason. Pinned by the `rows` bump in Task 1, Step 1 (cross-review R1-D3).
28
- 2. **The multi-select table count** — 9 rows, not 7. Dropping `?`/`!` re-creates the very debt this issue fixes. Pinned by Task 2, Step 3's `rg` count check (cross-review R1-D1).
29
- 3. **The Evidence table must still have no `!` row.** Pinned by Task 2, Step 3's negative check.
30
- 4. **`README.md:120`'s prose must not duplicate the new table's content** — it should point at the table instead. Pinned by Task 2, Step 4 (manual read).
31
-
32
- ---
33
-
34
- ### Task 1: Peek footer copy + render assertion
35
-
36
- **Files:**
37
- - Modify: `src/ui/dashboard.ts` (`renderPeek` footer, currently line 1543 — locate by the string `↑↓ prev/next · →/> attach`)
38
- - Modify: `test-support/vim-keys-wrap.ts` (`out.copy` block, currently lines ~119-140)
39
- - Modify: `test/dashboard-vim-keys.test.mjs` (A7 test, currently lines 38-63)
40
-
41
- **Interfaces:**
42
- - Consumes: existing `renderPeek(width)` and the probe's white-box `dash.mode` / `dash.peekId` / `dash.selectedId` handles (already used at `vim-keys-wrap.ts:100-110`).
43
- - Produces: probe field `out.copy.peekRender` (string) that Task 3's verification relies on indirectly.
44
-
45
- **Spec acceptance IDs:** A1, A2, A3, A4 (+ Review Focus 1).
46
-
47
- - [ ] **Step 1: Add the failing assertion and the probe capture**
48
-
49
- In `test-support/vim-keys-wrap.ts`, inside the `out.copy` block, after the `evidenceRender` capture and before `dash.mode = "list";`, add:
50
-
51
- ```ts
52
- // Peek footer: raise the height first — fitToHeight slices the tail, and the
53
- // footer is renderPeek's last line (cross-review R1-D3).
54
- tui.terminal.rows = 40;
55
- dash.mode = "peek"; dash.peekId = ids[0]; dash.selectedId = ids[0];
56
- const peekRender = dash.render(120).join("\n");
57
- tui.terminal.rows = 24;
58
- ```
59
-
60
- and extend the `out.copy` initializer on the following line to include `peekRender`:
61
-
62
- ```ts
63
- out.copy = { help120, listRender, selectRender, sessionRender, evidenceRender, peekRender, help80Lines: help80.length };
64
- ```
65
-
66
- In `test/dashboard-vim-keys.test.mjs`, in the A7 test: add `peekRender` to the destructuring line, and append before the closing `});`:
67
-
68
- ```js
69
- // #245 A1/A3: the peek footer advertises j/k and l, and the old wording is gone.
70
- assert.match(peekRender, /↑↓\/j\/k prev\/next/);
71
- assert.match(peekRender, /→\/l attach/);
72
- assert.doesNotMatch(peekRender, /↑↓ prev\/next · →\/> attach/);
73
- ```
74
-
75
- - [ ] **Step 2: Run the test to verify it fails**
76
-
77
- Run: `cd $WT && node --test test/dashboard-vim-keys.test.mjs`
78
- Expected: FAIL — `/↑↓\/j\/k prev\/next/` does not match, because the footer still reads `↑↓ prev/next`.
79
-
80
- - [ ] **Step 3: Change the footer copy**
81
-
82
- In `src/ui/dashboard.ts`, `renderPeek`'s else-branch (currently line 1543), replace:
83
-
84
- ```ts
85
- out.push(clip(t.fg("dim", "↑↓ prev/next · →/> attach · d done · v transcript · e evidence · r reply · esc back"), width));
86
- ```
87
-
88
- with:
89
-
90
- ```ts
91
- out.push(clip(t.fg("dim", "↑↓/j/k prev/next · →/l attach · d done · v transcript · e evidence · r reply · esc back"), width));
92
- ```
93
-
94
- (Matches the `↑↓/j/k … · →/l …` house style that #237 established for the session/evidence footers at `:1565` / `:1600`.)
95
-
96
- - [ ] **Step 4: Run the test to verify it passes**
97
-
98
- Run: `cd $WT && node --test test/dashboard-vim-keys.test.mjs`
99
- Expected: PASS — both tests, including the three new assertions.
100
-
101
- - [ ] **Step 5: Commit**
102
-
103
- ```bash
104
- git -C $WT add src/ui/dashboard.ts test-support/vim-keys-wrap.ts test/dashboard-vim-keys.test.mjs
105
- git -C $WT commit -m "docs(ui): advertise j/k and l in the peek footer (issue #245)"
106
- ```
107
-
108
- ---
109
-
110
- ### Task 2: README — `!` rows and the multi-select table
111
-
112
- **Files:**
113
- - Modify: `README.md` (multi-select prose `:120`; Peek table `:152-167`; Transcript table `:169-183`; new `### Multi-select` section after the Main list table)
114
-
115
- **Interfaces:**
116
- - Consumes: Task 1's copy as the source of truth for the peek footer wording (the README Peek table must not contradict it).
117
- - Produces: the documentation surface Task 3's U1 checklist walks.
118
-
119
- **Spec acceptance IDs:** U1 (the `!` rows + 9-row table), Review Focus 2, 3, 4.
120
-
121
- - [ ] **Step 1: Add `!` to the Peek table**
122
-
123
- In `README.md`'s Peek table, insert after the `h` row and before the `Up` / `Down` / `j` / `k` row:
124
-
125
- ```markdown
126
- | `!` | Open node-pty diagnostics and repair hints. |
127
- ```
128
-
129
- - [ ] **Step 2: Add `!` to the Transcript table**
130
-
131
- In the Transcript table, insert after the `h` row and before the `Left` / `Esc` / `<` row:
132
-
133
- ```markdown
134
- | `!` | Open node-pty diagnostics and repair hints. |
135
- ```
136
-
137
- (Wording is copied verbatim from the Main list table's `!` row so the same feature reads the same everywhere.)
138
-
139
- - [ ] **Step 3: Add the `### Multi-select` section**
140
-
141
- Insert this section immediately after the Main list table and before `### Peek`:
142
-
143
- ```markdown
144
- ### Multi-select
145
-
146
- Press `m` in the main list to act on several sessions at once.
147
-
148
- | Key | Action |
149
- | --- | --- |
150
- | `Up` / `Down` / `j` / `k` | Move the selection. |
151
- | `Space` | Toggle the current row. |
152
- | `a` | Select all visible rows. |
153
- | `u` | Clear the selection. |
154
- | `d` | Confirm marking the selected inactive rows Done. |
155
- | `Ctrl+X` | Delete the selected Done rows (confirmation prompt). |
156
- | `m` / `Esc` / `Ctrl+C` | Leave multi-select mode. |
157
- | `?` | Open the help overlay. |
158
- | `!` | Open node-pty diagnostics and repair hints. |
159
- ```
160
-
161
- - [ ] **Step 4: Point the prose at the new table instead of duplicating it**
162
-
163
- Replace the existing `README.md:120` line:
164
-
165
- ```markdown
166
- - `m` enters multi-select mode. Use `Space` to toggle rows, `a` to select all visible rows, `u` to clear the selection, `d` to mark inactive rows Done, or `Ctrl+X` to delete selected Done rows.
167
- ```
168
-
169
- with:
170
-
171
- ```markdown
172
- - `m` enters multi-select mode; see [Multi-select](#multi-select) for its keys.
173
- ```
174
-
175
- - [ ] **Step 5: Verify the tables by counting, not by eye**
176
-
177
- Run each check and confirm the stated output:
178
-
179
- ```bash
180
- cd $WT
181
- # 9 rows in the multi-select table (excluding header + separator)
182
- sed -n '/^### Multi-select/,/^### Peek/p' README.md | grep -c '^| `'
183
- # expect: 9
184
- # `!` documented four times: main list, multi-select, peek, transcript — and NOT in evidence
185
- grep -c '^| `!`' README.md
186
- # expect: 4
187
- sed -n '/^### Evidence/,/^### PTY attach/p' README.md | grep -c '^| `!`'
188
- # expect: 0
189
- ```
190
-
191
- - [ ] **Step 6: Commit**
192
-
193
- ```bash
194
- git -C $WT add README.md
195
- git -C $WT commit -m "docs: document ! and multi-select keys in the README (issue #245)"
196
- ```
197
-
198
- ---
199
-
200
- ### Task 3: Full gate + manual verification checklist
201
-
202
- **Files:** none modified — this task runs the gate and records results.
203
-
204
- **Interfaces:**
205
- - Consumes: Tasks 1–2.
206
- - Produces: the A5 evidence and the U1/U2 record posted to issue #245.
207
-
208
- **Spec acceptance IDs:** A5, U1, U2.
209
-
210
- - [ ] **Step 1: Run the full verification gate**
211
-
212
- Run: `cd $WT && npm run verify`
213
- Expected: PASS — typecheck, perf gate, all tests (currently 1404: 1401 pass / 3 opt-in skips), coverage thresholds (lines 85 / funcs 80 / branches 70), `pack:dry`.
214
-
215
- - [ ] **Step 2: Re-read the diff for the spec's non-goals**
216
-
217
- Run: `cd $WT && git diff --stat main...HEAD && git diff main...HEAD -- src/ui/dashboard.ts`
218
- Expected: `src/ui/dashboard.ts` shows **exactly one changed line** (the peek footer). If any handler, the help overlay, or the NORMAL/SELECT hint lines appear in the diff, revert that hunk — it violates spec D5.
219
-
220
- - [ ] **Step 3: U1 — walk the README against the live dashboard (after merge)**
221
-
222
- Deploy prelude (spec R1-D2): merge the PR, `git pull` on the main checkout, restart pi (the local checkout loads via auto-discovery, README "Development auto-discovery"), then `pi /agent-board`. Note: the `!` bindings themselves already ship in v0.13.0, so steps ① and ② can be sanity-checked on an older install — only the README text must be the merged version.
223
-
224
- ① Open Peek, press `!`, confirm the node-pty diagnostics view opens.
225
- ② Open the transcript (`v`), press `!`, same.
226
- ③ Enter multi-select (`m`) and walk all 9 rows of the new table: movement (`↑`/`↓`/`j`/`k`), `Space` toggle, `a`, `u`, `d`, `Ctrl+X`, `m`/`Esc`/`Ctrl+C` exit, `?`, `!`.
227
-
228
- - [ ] **Step 4: U2 — the peek footer on screen (after merge)**
229
-
230
- `space` to open Peek; read the footer.
231
- Expected: `↑↓/j/k prev/next · →/l attach · d done · v transcript · e evidence · r reply · esc back`.
232
-
233
- - [ ] **Step 5: Record results on issue #245**
234
-
235
- Post the A5 command + result, then the U1/U2 observations. If any U item cannot run yet, mark it `pending` — do not claim full acceptance.
236
-
237
- ---
238
-
239
- ## Self-Review Notes
240
-
241
- - **Spec coverage:** D1 → Task 1; D2 → Task 2 Steps 1-2; D3 → Task 2 Steps 3-4 (option A, 9 rows); D4 → Task 1 Step 1; D5 → Task 3 Step 2's revert check. A1/A3 → Task 1; A2 → Task 1 Steps 2/4; A4 → Task 3 Step 1; A5 → Task 3 Step 1; U1/U2 → Task 3 Steps 3-5. Every acceptance ID has an owning task; no task lacks an ID.
242
- - **Placeholder scan:** none — every step carries the literal text to write or the exact command with its expected output.
243
- - **Type consistency:** `peekRender` is named identically in the probe (`out.copy.peekRender`), the test destructuring, and the assertions.
244
- - **Unresolved spec item:** §6 asked A vs B; the user chose **A**, recorded in `authorization.md` (`Scope: design-approved (D3 = option A…)`). The plan implements A only.
@@ -1,344 +0,0 @@
1
- # Fullscreen selection highlight clear — 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:** In the fullscreen attach view, drop pi-tui's text selection as soon as copy-on-release has run, so the inverse-video highlight does not outlive the copy (issue #172).
6
-
7
- **Architecture:** Reuse the existing #174 deferred hook (`handleMouse` → `process.nextTick` after a left `release`/`click`) to read pi's final selection, mirror it into X11 PRIMARY as today, then clear pi's selection via a feature-detected private method and request one repaint. The predicate that schedules the hook becomes env-free; the kill switch stays where it already lives (`writePrimarySelection`). The clear is gated on `getCopyOnSelect() !== false` so users who disabled copy-on-select keep their selection for `Ctrl+X`.
8
-
9
- **Tech Stack:** TypeScript component (`src/ui/pty-attach.ts`), a pure decision predicate in `src/core/pty-scroll.mjs`, `node:test` unit tests, a component-level scenario harness with a fake fullscreen TUI and a PATH-stubbed `xclip`.
10
-
11
- ## Global Constraints
12
-
13
- - Spec: `docs/superpowers/specs/2026-10-27-issue-172-fullscreen-selection-clear-design.md` (v3).
14
- - `clearTextSelection()` is private in pi-tui's typings → always feature-detect; degrade, never throw.
15
- - `resetTextSelection()` must not be used (it clears `lastClick` and would break double-click word / triple-click line selection).
16
- - Regular/legacy mode is out of scope (issue #250); do not touch `handleLocalMouseEvent`.
17
- - Tabs for indentation, English comments, conventional commit subjects, no `git add -A`.
18
-
19
- ---
20
-
21
- ### Task 1: Replace the mirror predicate with an env-free selection-end predicate
22
-
23
- **Files:**
24
- - Modify: `src/core/pty-scroll.mjs:271-282`
25
- - Test: `test/pty-scroll.test.mjs:20`, `test/pty-scroll.test.mjs:244-262`
26
-
27
- **Interfaces:**
28
- - Produces: `isFullscreenSelectionEnd(event) → boolean` (exported from `src/core/pty-scroll.mjs`).
29
- - Removes: `shouldMirrorSelectionToPrimary(event, { nativePasteEnabled })`.
30
-
31
- - [ ] **Step 1: Update the unit test first**
32
-
33
- In `test/pty-scroll.test.mjs` replace the import name and the test block:
34
-
35
- ```js
36
- test("isFullscreenSelectionEnd: only the left button's release/click ends a fullscreen selection", () => {
37
- assert.equal(isFullscreenSelectionEnd(ev("release", "left")), true);
38
- assert.equal(isFullscreenSelectionEnd(ev("click", "left")), true);
39
- const cases = [
40
- ["press", "left"],
41
- ["move", "left"],
42
- ["drag", "left"],
43
- ["wheel", "left"],
44
- ["release", "middle"],
45
- ["release", "right"],
46
- ["release", "none"],
47
- ["click", "middle"],
48
- ["click", "none"],
49
- ["press", "middle"],
50
- ];
51
- for (const [type, button] of cases) {
52
- assert.equal(isFullscreenSelectionEnd(ev(type, button)), false, `${type}/${button}`);
53
- }
54
- });
55
- ```
56
-
57
- - [ ] **Step 2: Run the test to verify it fails**
58
-
59
- Run: `node --test test/pty-scroll.test.mjs`
60
- Expected: FAIL — `isFullscreenSelectionEnd is not a function`.
61
-
62
- - [ ] **Step 3: Replace the helper**
63
-
64
- ```js
65
- /**
66
- * Issue #174 (B2) / #172: whether a normalized mouse event ends a fullscreen text
67
- * selection gesture the attach overlay must settle — mirror the selection into X11
68
- * PRIMARY (#174) and drop pi's highlight once copy-on-select has run (#172). Only
69
- * release/click of the left button ends a selection in TuiAltScreen; pi owns the
70
- * gesture, so the overlay observes without consuming. Pure: the native-paste kill
71
- * switch is applied by writePrimarySelection, not here.
72
- */
73
- export function isFullscreenSelectionEnd(event) {
74
- return (event.type === "release" || event.type === "click") && event.button === "left";
75
- }
76
- ```
77
-
78
- - [ ] **Step 4: Run the test to verify it passes**
79
-
80
- Run: `node --test test/pty-scroll.test.mjs`
81
- Expected: PASS (all cases, no import errors).
82
-
83
- - [ ] **Step 5: Commit**
84
-
85
- ```bash
86
- git add src/core/pty-scroll.mjs test/pty-scroll.test.mjs
87
- git commit -m "refactor(attach): selection-end predicate for fullscreen settle (issue #172)"
88
- ```
89
-
90
- ---
91
-
92
- ### Task 2: Clear pi's selection in the deferred settle hook
93
-
94
- **Files:**
95
- - Modify: `src/ui/pty-attach.ts:17` (import), `:521-530` (schedule branch), `:1376-1381` (settle method)
96
- - Test: `test-support/pty-attach-selection-mirror-scenario.ts`, `test/pty-attach-selection-mirror.test.mjs`
97
-
98
- **Interfaces:**
99
- - Consumes: `isFullscreenSelectionEnd(event)` from Task 1.
100
- - Produces: `private settleFullscreenSelection(nativePasteEnabled: boolean): void`; type `FullscreenSelectionHost`.
101
-
102
- - [ ] **Step 1: Extend the scenario with clear hooks and cases**
103
-
104
- In `test-support/pty-attach-selection-mirror-scenario.ts`:
105
-
106
- 1. Add module-level knobs next to `selectionText`:
107
-
108
- ```ts
109
- let copyOnSelect = true;
110
- let clearAvailable = true;
111
- let clearCalls = 0;
112
- ```
113
-
114
- 2. Give the fake TUI the two new members (the conditional spread must be inside the object literal):
115
-
116
- ```ts
117
- const tui = {
118
- mode: "fullscreen",
119
- getActiveSelectionText: () => selectionText,
120
- getCopyOnSelect: () => copyOnSelect,
121
- ...(clearAvailable
122
- ? {
123
- clearTextSelection: () => {
124
- clearCalls += 1;
125
- selectionText = undefined; // prove read-before-clear: a wrong order yields no PRIMARY write
126
- },
127
- }
128
- : {}),
129
- terminal: { rows: 24, cols: 80, columns: 80, write: () => {} },
130
- requestRender: () => {},
131
- };
132
- ```
133
-
134
- 3. Add result fields and one reset helper:
135
-
136
- ```ts
137
- releaseClears: -1,
138
- clickClears: -1,
139
- emptyClears: -1,
140
- killSwitchClears: -1,
141
- nonEndingClears: -1,
142
- copyOnSelectOffWrites: -1,
143
- copyOnSelectOffClears: -1,
144
- fallbackError: null as string | null,
145
- fallbackWrites: -1,
146
- ```
147
-
148
- ```ts
149
- const resetFixture = (text: string | undefined = "mirror-fixture-text") => {
150
- selectionText = text;
151
- copyOnSelect = true;
152
- clearAvailable = true;
153
- clearCalls = 0;
154
- };
155
- ```
156
-
157
- 4. Call `resetFixture()` at the start of every existing case, and record the clear deltas:
158
-
159
- - case 1 (release): `result.releaseClears = clearCalls;`
160
- - case 2 (click): `result.clickClears = clearCalls;`
161
- - case 3 (empty): set `resetFixture(undefined)`, then `result.emptyClears = clearCalls;`
162
- - case 4 (kill switch): `resetFixture()`, then `result.killSwitchClears = clearCalls;`
163
- - case 5 (press/move): `resetFixture()`, then `result.nonEndingClears = clearCalls;`
164
- - new case 6 (`copyOnSelect = false`): `resetFixture(); copyOnSelect = false;` handle release; assert writes 1 and clears 0.
165
- - new case 7 (no `clearTextSelection`): `resetFixture(); clearAvailable = false;` wrap the call in try/catch, capture `fallbackError`, and record `fallbackWrites`.
166
-
167
- 5. The middle-click paste case stays last and must also call `resetFixture()`.
168
-
169
- - [ ] **Step 2: Run the scenario test to verify it fails**
170
-
171
- Run: `node --test test/pty-attach-selection-mirror.test.mjs`
172
- Expected: FAIL on the new assertions (`releaseClears` is 0, because the component never clears yet).
173
-
174
- - [ ] **Step 3: Wire the component**
175
-
176
- Import (line 17): replace `shouldMirrorSelectionToPrimary` with `isFullscreenSelectionEnd`.
177
-
178
- Replace the schedule branch:
179
-
180
- ```ts
181
- const nativePasteEnabled = process.env.AGENT_BOARD_ATTACH_NATIVE_PASTE !== "0";
182
- if (isFullscreenSelectionEnd(event)) {
183
- // Observe only — pi's fullscreen selection keeps the event. Overlays are
184
- // dispatched before TuiAltScreen finalizes (and copies) the selection for
185
- // this event, so settling must run deferred (issue #174).
186
- process.nextTick(() => this.settleFullscreenSelection(nativePasteEnabled));
187
- return undefined;
188
- }
189
- ```
190
-
191
- Add the structural type near the other top-level types in the file:
192
-
193
- ```ts
194
- /** The pi-tui members the attach view reads without a public typing contract:
195
- * getActiveSelectionText/clearTextSelection are private in TuiAltScreen's d.ts
196
- * (runtime-reachable), getCopyOnSelect is public. All are feature-detected. */
197
- interface FullscreenSelectionHost {
198
- getActiveSelectionText?: () => string | undefined;
199
- getCopyOnSelect?: () => boolean;
200
- clearTextSelection?: () => void;
201
- }
202
- ```
203
-
204
- Rename `mirrorFullscreenSelectionToPrimary` to `settleFullscreenSelection` and extend it:
205
-
206
- ```ts
207
- /** Issue #174 (B2) + #172: a selection gesture ended inside this overlay and pi has
208
- * finalized (and copied) its selection. Mirror the text into X11 PRIMARY so
209
- * middle-click paste closes the loop, then drop pi's selection so the inverse-video
210
- * highlight does not outlive the copy. Clearing is skipped when copy-on-select is
211
- * off (the user copies later with pi's Ctrl+X) and when the pi-tui method is absent
212
- * (older/newer runtime) — that degrades to the previous behaviour instead of
213
- * throwing. clearTextSelection preserves pi's multi-click history; resetTextSelection
214
- * would reset lastClick and break double-click word / triple-click line selection. */
215
- private settleFullscreenSelection(nativePasteEnabled: boolean): void {
216
- if (this.closed) return;
217
- const alt = this.tui as FullscreenSelectionHost;
218
- const text = alt.getActiveSelectionText?.();
219
- if (typeof text !== "string" || text.length === 0) return;
220
- if (nativePasteEnabled) this.writePrimarySelection(text);
221
- if (alt.getCopyOnSelect?.() === false) return;
222
- if (typeof alt.clearTextSelection !== "function") return;
223
- alt.clearTextSelection();
224
- this.scheduleRender();
225
- }
226
- ```
227
-
228
- - [ ] **Step 4: Update the test assertions**
229
-
230
- In `test/pty-attach-selection-mirror.test.mjs`, keep every existing assertion and add:
231
-
232
- ```js
233
- assert.equal(parsed.releaseClears, 1, "the highlight must be cleared once the copy has run");
234
- assert.equal(parsed.clickClears, 1, "a click ending with a live selection clears too");
235
- assert.equal(parsed.emptyClears, 0, "an empty selection must not clear");
236
- assert.equal(parsed.killSwitchClears, 1, "the highlight clear is not governed by NATIVE_PASTE");
237
- assert.equal(parsed.nonEndingClears, 0, "press/move must not clear");
238
- assert.equal(parsed.copyOnSelectOffWrites, 1, "the PRIMARY mirror still runs with copy-on-select off");
239
- assert.equal(parsed.copyOnSelectOffClears, 0, "copy-on-select off keeps the selection for Ctrl+X");
240
- assert.equal(parsed.fallbackError, null, "a missing clearTextSelection must not throw");
241
- assert.equal(parsed.fallbackWrites, 1, "the PRIMARY mirror still runs without the clear method");
242
- ```
243
-
244
- - [ ] **Step 5: Run the scenario test to verify it passes**
245
-
246
- Run: `node --test test/pty-attach-selection-mirror.test.mjs`
247
- Expected: PASS.
248
-
249
- - [ ] **Step 6: Commit**
250
-
251
- ```bash
252
- git add src/ui/pty-attach.ts test-support/pty-attach-selection-mirror-scenario.ts test/pty-attach-selection-mirror.test.mjs
253
- git commit -m "fix(attach): clear the fullscreen selection highlight after copy-on-release (issue #172)"
254
- ```
255
-
256
- ---
257
-
258
- ### Task 2b: Guard the settle against the next gesture (local CR F1/F3/F4)
259
-
260
- **Files:**
261
- - Modify: `src/ui/pty-attach.ts` (field + `handleMouse` bump + serial check)
262
- - Test: `test-support/pty-attach-selection-mirror-scenario.ts`, `test/pty-attach-selection-mirror.test.mjs`, `test/pty-scroll.test.mjs`
263
-
264
- **Interfaces:**
265
- - Produces: `private selectionGestureSerial = 0` (bumped on left press).
266
-
267
- - [ ] **Step 1: Add the failing scenario case**
268
-
269
- ```ts
270
- // 8. A release and the next left press coalesced into one tick (pi-tui emits batched
271
- // reports synchronously): the queued settle must not read/clear the new gesture.
272
- await withComponent(undefined, async (c) => {
273
- resetFixture();
274
- const before = writes().length;
275
- (c as any).handleMouse(leftEvent("release"));
276
- (c as any).handleMouse({ ...leftEvent("press"), x: 9, screenX: 9 });
277
- await sleep(80);
278
- result.coalescedWrites = writes().length - before;
279
- result.coalescedClears = clearCalls;
280
- });
281
- ```
282
-
283
- Also pin the deferral in case 1: `result.releaseSyncWrites = writes().length - before;` immediately after `handleMouse` returns.
284
-
285
- - [ ] **Step 2: Run the scenario test to verify it fails**
286
-
287
- Run: `node --test test/pty-attach-selection-mirror.test.mjs`
288
- Expected: FAIL — `coalescedWrites` is 1 (the queued settle mirrored the new gesture).
289
-
290
- - [ ] **Step 3: Implement the guard**
291
-
292
- Field next to `private selection`:
293
-
294
- ```ts
295
- private selectionGestureSerial = 0;
296
- ```
297
-
298
- In `handleMouse`, before the settle branch:
299
-
300
- ```ts
301
- if (event.type === "press" && event.button === "left") this.selectionGestureSerial += 1;
302
- ```
303
-
304
- In the settle branch, capture and compare:
305
-
306
- ```ts
307
- const serial = this.selectionGestureSerial;
308
- process.nextTick(() => {
309
- if (serial !== this.selectionGestureSerial) return;
310
- this.settleFullscreenSelection(nativePasteEnabled);
311
- });
312
- ```
313
-
314
- - [ ] **Step 4: Complete the predicate table (F4) and fix the import order (F5)**
315
-
316
- Add `["click", "right"]` to the false-cases list in `test/pty-scroll.test.mjs` and move `isFullscreenSelectionEnd` into alphabetical position.
317
-
318
- - [ ] **Step 5: Run the tests to verify they pass**
319
-
320
- Run: `node --test test/pty-scroll.test.mjs test/pty-attach-selection-mirror.test.mjs`
321
- Expected: PASS (35 tests).
322
-
323
- - [ ] **Step 6: Commit**
324
-
325
- ```bash
326
- git add src/ui/pty-attach.ts test-support/pty-attach-selection-mirror-scenario.ts test/pty-attach-selection-mirror.test.mjs test/pty-scroll.test.mjs
327
- git commit -m "fix(attach): keep a queued settle from consuming the next gesture (issue #172)"
328
- ```
329
-
330
- ---
331
-
332
- ### Task 3: Full verification
333
-
334
- **Files:** none.
335
-
336
- - [ ] **Step 1: Run the repository gate**
337
-
338
- Run: `npm run verify`
339
- Expected: typecheck, perf gate, tests, coverage thresholds, pack:dry all pass.
340
-
341
- - [ ] **Step 2: Confirm no scope drift**
342
-
343
- Run: `git diff --stat origin/main`
344
- Expected: only the spec, the plan, `src/ui/pty-attach.ts`, `src/core/pty-scroll.mjs`, `test/pty-scroll.test.mjs`, `test-support/pty-attach-selection-mirror-scenario.ts`, `test/pty-attach-selection-mirror.test.mjs`. `handleLocalMouseEvent` and `test-support/pty-attach-legacy-drag-scenario.ts` untouched.