@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,526 +0,0 @@
1
- # X10 mouse release classification 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 decoding an X10/normal mouse release (`Cb = 3`) as a press, so a drag in the legacy attach path copies the selection on release instead of clearing it.
6
-
7
- **Architecture:** One classification fix inside the pure mouse parser (`parseMouseEventPrefix`, X10 branch) plus the tests that pin it: unit rows on the exported pure functions (`parseMouseEvent`, `parseMouseInputChunk`), and a component-level scenario that drives real X10 bytes through the real `PtyAttachComponent` + runner socket and observes the OSC 52 copy. No handler change, no new export, no new env flag, no state.
8
-
9
- **Tech Stack:** Node ESM (`.mjs` runtime, `.ts` UI), `node:test` + `assert/strict`, `@xterm/headless`, the repo's `node --experimental-transform-types` scenario pattern.
10
-
11
- **Spec:** `docs/superpowers/specs/2026-10-15-issue-188-x10-mouse-release-design.md` (issue #188).
12
- **Worktree / branch:** `.pi/worktrees/issue-188-x10-release` on branch `issue-188-x10-release` (baseline `main` @ `7cf0853`). Every command below runs from that worktree root.
13
-
14
- ## Global Constraints
15
-
16
- - **One edit site.** The behaviour change lives in `parseMouseEventPrefix` (`src/core/pty-scroll.mjs:53-66`). Do not add a handler-side release rule, a new export, a new env flag, or config.
17
- - **Do not touch the SGR branch or the passive branch** (`sgr[5] === "m"` already marks SGR/passive releases).
18
- - **Do not touch the release branch's `shouldCopy` logic** (`src/ui/pty-attach.ts:1081-1088`): geometry-based drag detection is #187, clearing the highlight after a copy is #172. This issue changes neither.
19
- - **Wheel stays `"press"`**: any release rule must exclude bit 6 (`64`), otherwise `64`/`65` wheel reports change classification. `mouseWheelDirection` must keep returning +1/−1 for X10 wheel up/down.
20
- - **Tabs** for indentation, **English** comments and commit messages, conventional commit subjects with the issue number, e.g. `fix(attach): … (issue #188)`.
21
- - **Unit test file:** extend `test/pty-scroll.test.mjs` (do not create a second parser test file).
22
- - **Component test pair:** `test-support/pty-attach-x10-scenario.ts` + `test/pty-attach-x10.test.mjs`, modelled on `test-support/pty-attach-mouse-scenario.ts` + `test/pty-attach-mouse.test.mjs`.
23
- - **Hermetic tests:** the scenario must stub `xclip` on `PATH` so `writePrimarySelection()` never touches the host X11 clipboard.
24
- - **No `git add -A`**: stage files by path.
25
- - **Definition of done:** `npm run verify` exits 0 (typecheck + perf gate + full `node --test test/*.test.mjs` + coverage thresholds + `pack:dry`).
26
- - **Acceptance IDs** (`A1`–`A6`, `U1` from the spec §5) are referenced per task; the mapping must stay bidirectional.
27
-
28
- ### Acceptance ↔ task map
29
-
30
- | Spec ID | What it pins | Task |
31
- |---|---|---|
32
- | A1 | X10 press / motion / release / modifier-release classification | Task 1 |
33
- | A2 | Wheel, middle, right, motion+button-3, non-mouse, passive rows unchanged | Task 1 |
34
- | A3 | Concatenated X10 reports decode in order (and a 7-byte chunk is rejected) | Task 1 |
35
- | A4 | X10 drag release copies (OSC 52 payload) | Task 2 |
36
- | A5 | Click / right button / wheel branches unchanged at component level | Task 2 |
37
- | A6 | `npm run verify` (static + full suite + coverage + pack) | Task 3 |
38
- | U1 | Optional real-terminal check (no X10-only terminal available → `pending`) | Task 3 (disposition) |
39
-
40
- ---
41
-
42
- ### Task 1: Classify the X10 release code in the parser (A1, A2, A3)
43
-
44
- **Files:**
45
- - Modify: `src/core/pty-scroll.mjs:53-66` (the X10 branch of `parseMouseEventPrefix`)
46
- - Test: `test/pty-scroll.test.mjs` (append two `test(...)` blocks; existing assertions stay untouched)
47
-
48
- **Interfaces:**
49
- - Consumes: nothing from earlier tasks.
50
- - Produces: `parseMouseEvent(data)` returns `{ encoding: "x10", button: number, col: number, row: number, action: "press" | "move" | "release" }` for 6-byte X10 reports; `parseMouseInputChunk(data)` returns the per-report array with the same `mouse` shape (unchanged signatures).
51
-
52
- - [ ] **Step 1: Write the failing tests**
53
-
54
- Append to `test/pty-scroll.test.mjs` (it already imports `mouseWheelDirection`, `parseMouseEvent`, `parseMouseInputChunk`):
55
-
56
- ```js
57
- // Issue #188: in the X10/normal encoding (ESC [ M Cb Cx Cy, each byte = value
58
- // + 32) the low two bits of Cb are the button, except value 3, which is the
59
- // protocol's release code and carries no button identity; bit 5 is motion and
60
- // bit 6 is wheel. Classifying 3 as a press made the attach surface clear the
61
- // drag selection on release instead of copying it.
62
- test("parseMouseEvent classifies X10 button codes (issue #188)", () => {
63
- // Cb = 0/1/2: left/middle/right press. col 1, row 2 (body row 0).
64
- assert.deepEqual(parseMouseEvent("\x1b[M !\""), { encoding: "x10", button: 0, col: 1, row: 2, action: "press" });
65
- assert.deepEqual(parseMouseEvent("\x1b[M!!\""), { encoding: "x10", button: 1, col: 1, row: 2, action: "press" });
66
- assert.deepEqual(parseMouseEvent("\x1b[M\"!\""), { encoding: "x10", button: 2, col: 1, row: 2, action: "press" });
67
- // Cb = 32: motion (col 8); Cb = 32|3: motion without a button held.
68
- assert.deepEqual(parseMouseEvent("\x1b[M@(\""), { encoding: "x10", button: 32, col: 8, row: 2, action: "move" });
69
- assert.deepEqual(parseMouseEvent("\x1b[MC!\""), { encoding: "x10", button: 35, col: 1, row: 2, action: "move" });
70
- // Cb = 3: release; 3|4 shift, 3|16 ctrl must stay releases.
71
- assert.deepEqual(parseMouseEvent("\x1b[M#!\""), { encoding: "x10", button: 3, col: 1, row: 2, action: "release" });
72
- assert.deepEqual(parseMouseEvent("\x1b[M#4\""), { encoding: "x10", button: 3, col: 20, row: 2, action: "release" });
73
- assert.deepEqual(parseMouseEvent("\x1b[M'!\""), { encoding: "x10", button: 7, col: 1, row: 2, action: "release" });
74
- assert.deepEqual(parseMouseEvent("\x1b[M3!\""), { encoding: "x10", button: 19, col: 1, row: 2, action: "release" });
75
- // Cb = 64/65: wheel, and the release rule must not capture it.
76
- assert.deepEqual(parseMouseEvent("\x1b[M`!\""), { encoding: "x10", button: 64, col: 1, row: 2, action: "press" });
77
- assert.deepEqual(parseMouseEvent("\x1b[Ma!\""), { encoding: "x10", button: 65, col: 1, row: 2, action: "press" });
78
- assert.equal(mouseWheelDirection("\x1b[M`!\""), 1);
79
- assert.equal(mouseWheelDirection("\x1b[Ma!\""), -1);
80
- });
81
-
82
- test("parseMouseInputChunk decodes a concatenated X10 gesture in order (issue #188)", () => {
83
- assert.deepEqual(parseMouseInputChunk("\x1b[M !\"\x1b[M@(\"\x1b[M#4\""), [
84
- { length: 6, raw: "\x1b[M !\"", mouse: { encoding: "x10", button: 0, col: 1, row: 2, action: "press" } },
85
- { length: 6, raw: "\x1b[M@(\"", mouse: { encoding: "x10", button: 32, col: 8, row: 2, action: "move" } },
86
- { length: 6, raw: "\x1b[M#4\"", mouse: { encoding: "x10", button: 3, col: 20, row: 2, action: "release" } },
87
- ]);
88
- // Seven bytes is not an X10 report: the all-or-nothing chunk parser rejects it
89
- // (#189), which is why every literal above is exactly six bytes.
90
- assert.equal(parseMouseInputChunk("\x1b[M !\"\x1b[M#20\""), null);
91
- });
92
- ```
93
-
94
- - [ ] **Step 2: Run the tests to verify they fail**
95
-
96
- Run: `node --test test/pty-scroll.test.mjs`
97
- Expected: FAIL in both new tests on the release rows, e.g. `action: 'press'` where `'release'` was expected (3 rows in the first test, 1 in the second). The wheel/middle/right/motion rows already pass.
98
-
99
- - [ ] **Step 3: Write the minimal implementation**
100
-
101
- Replace the X10 branch in `src/core/pty-scroll.mjs` (currently `:52-66`) with:
102
-
103
- ```js
104
- // X10/normal mouse: ESC [ M Cb Cx Cy. Cb is encoded as button + 32.
105
- if (input.startsWith("\x1b[M") && input.length >= 6) {
106
- // Cb packs the button (bits 0-1), modifiers (2-4), motion (5) and wheel (6),
107
- // except bit value 3 in bits 0-1: the protocol's release code, which carries
108
- // no button identity. Decoding it as a press made the attach surface clear
109
- // the drag selection on release instead of copying it (issue #188). Wheel
110
- // reports never carry a release, so bit 6 keeps the press classification.
111
- const button = input.charCodeAt(3) - 32;
112
- const motion = (button & 32) !== 0;
113
- const wheel = (button & 64) !== 0;
114
- const release = !motion && !wheel && (button & 3) === 3;
115
- return {
116
- length: 6,
117
- raw: input.slice(0, 6),
118
- mouse: {
119
- encoding: "x10",
120
- button,
121
- col: input.charCodeAt(4) - 32,
122
- row: input.charCodeAt(5) - 32,
123
- action: release ? "release" : motion ? "move" : "press",
124
- },
125
- };
126
- }
127
- ```
128
-
129
- - [ ] **Step 4: Run the tests to verify they pass**
130
-
131
- Run: `node --test test/pty-scroll.test.mjs`
132
- Expected: PASS — every test in the file, including the pre-existing X10 rows (`\x1b[M !!"` → press, `mouseWheelDirection` wheel rows).
133
-
134
- - [ ] **Step 5: Commit**
135
-
136
- ```bash
137
- git add src/core/pty-scroll.mjs test/pty-scroll.test.mjs
138
- git commit -m "fix(attach): decode the X10 mouse release code as release (issue #188)"
139
- ```
140
-
141
- ---
142
-
143
- ### Task 2: Component-level proof that an X10 drag release copies (A4, A5)
144
-
145
- **Files:**
146
- - Create: `test-support/pty-attach-x10-scenario.ts`
147
- - Create: `test/pty-attach-x10.test.mjs`
148
-
149
- **Interfaces:**
150
- - Consumes: Task 1's classification (the scenario fails without it — Step 3 proves that).
151
- - Produces: a scenario that prints one JSON line on stdout:
152
- `{ ok, sawContent, error, cases: { drag, click, rightButton, wheel } }` where each case is `{ osc52: string[], forwarded: string[], paintedRightAfterRelease: boolean, paintedAfterWindow: boolean }`.
153
-
154
- - [ ] **Step 1: Create the scenario**
155
-
156
- Create `test-support/pty-attach-x10-scenario.ts`:
157
-
158
- ```ts
159
- // Component-level X10 mouse-report coverage (issue #188): the REAL
160
- // PtyAttachComponent against a REAL runner socket (runner/pty-runner.mjs) with a
161
- // fake PTY (test-support/fake-pty-pi.mjs, default mode -> one "fake pi ready"
162
- // line) and a fake TUI capturing terminal.write, driven by X10-encoded reports
163
- // through the public handleInput seam. Verifies:
164
- // 1. an X10 drag (press -> motion -> release) copies the selection: exactly one
165
- // OSC 52 write whose payload is the selected text. This is the issue #188
166
- // regression — before the fix the release was decoded as a non-primary
167
- // press, so the selection was cleared and nothing was copied;
168
- // 2. a same-point X10 click copies nothing, keeps its 1-cell highlight right
169
- // after the release and loses it once the pending-click window elapsed;
170
- // 3. an X10 right press + release copies nothing (the release finds no live
171
- // selection);
172
- // 4. an X10 wheel report the local viewport cannot scroll is forwarded to the
173
- // child raw instead of being consumed.
174
- // Hermetic: a PATH-stubbed xclip so writePrimarySelection() never touches the
175
- // host X11 clipboard. Run via `node --experimental-transform-types`.
176
- import { chmodSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
177
- import { Socket } from "node:net";
178
- import { tmpdir } from "node:os";
179
- import { join, resolve } from "node:path";
180
- import { spawn } from "node:child_process";
181
- import { atomicWriteJson } from "../src/core/atomic.mjs";
182
- import * as P from "../src/core/paths.mjs";
183
- import { createView, readHost } from "../src/core/store.mjs";
184
- import { PtyAttachComponent } from "../src/ui/pty-attach.ts";
185
-
186
- const root = mkdtempSync(join(tmpdir(), "agentview-attach-x10-"));
187
- const viewId = "x10-e2e";
188
-
189
- const binDir = join(root, "bin");
190
- mkdirSync(binDir);
191
- writeFileSync(join(binDir, "xclip"), "#!/bin/sh\nexit 0\n");
192
- chmodSync(join(binDir, "xclip"), 0o755);
193
- process.env.PATH = `${binDir}:${process.env.PATH}`;
194
-
195
- function isAlive(pid: number): boolean {
196
- try {
197
- process.kill(pid, 0);
198
- return true;
199
- } catch {
200
- return false;
201
- }
202
- }
203
-
204
- const sleep = (ms: number) => new Promise<void>((r) => setTimeout(r, ms));
205
-
206
- async function waitFor(predicate: () => boolean, timeoutMs: number): Promise<boolean> {
207
- const start = Date.now();
208
- while (Date.now() - start < timeoutMs) {
209
- if (predicate()) return true;
210
- await sleep(25);
211
- }
212
- return false;
213
- }
214
-
215
- const meta = createView(root, { id: viewId, name: "x10 attach e2e", cwd: root });
216
- const configPath = P.hostConfigPath(root, viewId);
217
- atomicWriteJson(configPath, {
218
- root,
219
- viewId,
220
- sessionFile: meta.sessionFile,
221
- cwd: process.cwd(),
222
- initialPrompt: null,
223
- piCommand: process.execPath,
224
- piArgsPrefix: [resolve("test-support/fake-pty-pi.mjs")],
225
- model: null,
226
- tools: null,
227
- env: { AGENT_BOARD_ALLOW_PIPE_FALLBACK: "1" },
228
- cols: 80,
229
- rows: 22,
230
- });
231
-
232
- const runner = spawn(process.execPath, [resolve("runner/pty-runner.mjs"), configPath], {
233
- stdio: ["ignore", "pipe", "pipe"],
234
- });
235
- runner.stderr.resume();
236
-
237
- // X10 reports used below (ESC [ M Cb Cx Cy, each byte = value + 32; body row 0
238
- // is terminal row 2): press/motion/release of a full-line drag, a same-point
239
- // click release, a right-button press, and a wheel notch.
240
- const PRESS = "\x1b[M !\"";
241
- const MOTION = "\x1b[M@(\"";
242
- const RELEASE = "\x1b[M#4\"";
243
- const CLICK_RELEASE = "\x1b[M#!\"";
244
- const RIGHT_PRESS = "\x1b[M\"!\"";
245
- const WHEEL_UP = "\x1b[M`!\"";
246
-
247
- const result = {
248
- ok: false,
249
- sawContent: false,
250
- cases: {} as Record<string, Record<string, unknown>>,
251
- error: null as string | null,
252
- };
253
-
254
- try {
255
- const ready = await waitFor(() => {
256
- const host = readHost(root, viewId);
257
- return (
258
- !!host &&
259
- host.state === "alive" &&
260
- !!host.socketPath &&
261
- !!host.childPid &&
262
- isAlive(host.runnerPid) &&
263
- isAlive(host.childPid)
264
- );
265
- }, 10_000);
266
- if (!ready) throw new Error("host never became ready");
267
-
268
- // Intercept component -> runner frames so a forwarded wheel report is observable.
269
- const frames: string[] = [];
270
- const socketProto = Socket.prototype as any;
271
- const origWrite = socketProto.write;
272
- socketProto.write = function (data: any, ...rest: any[]) {
273
- if (typeof data === "string") frames.push(data);
274
- return origWrite.call(this, data, ...rest);
275
- };
276
- const forwardedInputs = () =>
277
- frames
278
- .map((line) => {
279
- try {
280
- return JSON.parse(line);
281
- } catch {
282
- return null;
283
- }
284
- })
285
- .filter((msg) => !!msg && msg.type === "input" && typeof msg.data === "string")
286
- .map((msg) => msg.data as string);
287
-
288
- const writes: string[] = [];
289
- const tui = {
290
- terminal: { rows: 24, cols: 80, columns: 80, write: (d: string) => writes.push(String(d)) },
291
- requestRender: () => {},
292
- };
293
- const theme = { fg: (_c: string, t: string) => t, bold: (t: string) => t };
294
- const component = new PtyAttachComponent(
295
- tui as never,
296
- theme,
297
- {} as never,
298
- () => {},
299
- {
300
- socketPath: readHost(root, viewId)!.socketPath,
301
- screenLogPath: P.screenLogPath(root, viewId),
302
- title: "x10 attach e2e",
303
- } as never,
304
- );
305
-
306
- const deadline = Date.now() + 8000;
307
- while (Date.now() < deadline && !result.sawContent) {
308
- const lines = component.render(80) ?? [];
309
- if (lines.join("\n").includes("fake pi ready")) result.sawContent = true;
310
- else await sleep(40);
311
- }
312
- if (!result.sawContent) throw new Error("session content never rendered");
313
-
314
- const osc52Payloads = () =>
315
- writes
316
- .filter((w) => w.includes("\x1b]52;c;"))
317
- .map((w) => Buffer.from(w.replace(/.*\x1b]52;c;/, "").replace("\x07", ""), "base64").toString("utf8"));
318
-
319
- // Selection paint on the fixture line: selected cells are rendered with inverse
320
- // video (\x1b[0;7;…), while the bare cursor block on an empty row is a
321
- // different sequence (\x1b[7m), so this probe only sees a live selection.
322
- const selectionPainted = () => {
323
- const line = (component.render(80) ?? []).find((l) => l.includes("fake pi ready")) ?? "";
324
- return line.includes("\x1b[0;7;");
325
- };
326
-
327
- const runCase = async (name: string, sequences: string[], settleMs = 400) => {
328
- writes.length = 0;
329
- frames.length = 0;
330
- for (const seq of sequences.slice(0, -1)) {
331
- component.handleInput(seq);
332
- await sleep(15);
333
- }
334
- component.handleInput(sequences[sequences.length - 1]);
335
- // Read the immediate state in the same tick as the release: the pending-click
336
- // window (DOUBLE_CLICK_MS = 260) must not be able to race this probe.
337
- const paintedRightAfterRelease = selectionPainted();
338
- await sleep(settleMs);
339
- result.cases[name] = {
340
- osc52: osc52Payloads(),
341
- forwarded: forwardedInputs(),
342
- paintedRightAfterRelease,
343
- paintedAfterWindow: selectionPainted(),
344
- };
345
- };
346
-
347
- await runCase("drag", [PRESS, MOTION, RELEASE]);
348
- await runCase("click", [PRESS, CLICK_RELEASE]);
349
- await runCase("rightButton", [RIGHT_PRESS, CLICK_RELEASE]);
350
- await runCase("wheel", [WHEEL_UP]);
351
-
352
- result.ok =
353
- (result.cases.drag.osc52 as string[]).length === 1 &&
354
- (result.cases.click.osc52 as string[]).length === 0 &&
355
- (result.cases.rightButton.osc52 as string[]).length === 0 &&
356
- (result.cases.wheel.osc52 as string[]).length === 0 &&
357
- (result.cases.wheel.forwarded as string[]).includes(WHEEL_UP);
358
-
359
- try {
360
- component.dispose();
361
- } catch {}
362
- } catch (err) {
363
- result.error = err instanceof Error ? err.message : String(err);
364
- }
365
-
366
- try {
367
- const pid = readHost(root, viewId)?.childPid;
368
- if (pid) process.kill(pid, "SIGKILL");
369
- } catch {}
370
- try {
371
- runner.kill("SIGKILL");
372
- } catch {}
373
- await sleep(50);
374
- try {
375
- rmSync(root, { recursive: true, force: true, maxRetries: 3, retryDelay: 30 });
376
- } catch {}
377
-
378
- console.log(JSON.stringify(result));
379
- ```
380
-
381
- - [ ] **Step 2: Create the test driver**
382
-
383
- Create `test/pty-attach-x10.test.mjs`:
384
-
385
- ```js
386
- import assert from "node:assert/strict";
387
- import test from "node:test";
388
- import { execFileSync } from "node:child_process";
389
- import { fileURLToPath } from "node:url";
390
- import { join } from "node:path";
391
-
392
- const ROOT_DIR = fileURLToPath(new URL("../", import.meta.url));
393
- const SCENARIO = join(ROOT_DIR, "test-support", "pty-attach-x10-scenario.ts");
394
-
395
- // Component-level coverage (issue #188): the REAL PtyAttachComponent against a
396
- // REAL runner socket with a fake PTY, driven with X10-encoded mouse reports
397
- // through the legacy handleInput path. Pins the copy-on-release effect (#188)
398
- // and the unchanged click / right-button / wheel branches.
399
- test("attach X10 mouse: drag release copies; click, right button and wheel do not (issue #188)", () => {
400
- const out = execFileSync(process.execPath, ["--experimental-transform-types", SCENARIO], {
401
- encoding: "utf8",
402
- timeout: 60_000,
403
- });
404
- const parsed = JSON.parse(out.trim().split("\n").filter(Boolean).pop());
405
- assert.equal(parsed.error, null, `scenario error: ${parsed.error}`);
406
- assert.equal(parsed.sawContent, true, "session content must render before mouse dispatch");
407
-
408
- // A4: the X10 drag copies exactly once, with the selected text as payload.
409
- assert.deepEqual(parsed.cases.drag.osc52, ["fake pi ready"], "X10 drag release must copy the selection");
410
-
411
- // A5: a same-point click copies nothing and its highlight clears after the window.
412
- assert.deepEqual(parsed.cases.click.osc52, []);
413
- assert.equal(parsed.cases.click.paintedRightAfterRelease, true, "the click keeps its 1-cell highlight during the pending-click window");
414
- assert.equal(parsed.cases.click.paintedAfterWindow, false, "the pending-click window clears the click highlight");
415
-
416
- // A5: right press + release copies nothing.
417
- assert.deepEqual(parsed.cases.rightButton.osc52, []);
418
-
419
- // A5: an X10 wheel report the local viewport cannot use is forwarded raw.
420
- assert.deepEqual(parsed.cases.wheel.osc52, []);
421
- assert.deepEqual(parsed.cases.wheel.forwarded, ["\x1b[M`!\""]);
422
-
423
- assert.equal(parsed.ok, true);
424
- });
425
- ```
426
-
427
- - [ ] **Step 3: Run the test to verify it passes**
428
-
429
- Run: `node --test test/pty-attach-x10.test.mjs`
430
- Expected: PASS (`ok 1`). If the drag assertion fails, the parser rule from Task 1 is not in place — do not weaken the assertion.
431
-
432
- - [ ] **Step 4: Prove the test is a real regression test**
433
-
434
- Run (restores the pre-fix parser from Task 1's parent commit, then brings the fix back):
435
-
436
- ```bash
437
- git checkout HEAD~1 -- src/core/pty-scroll.mjs
438
- node --test test/pty-attach-x10.test.mjs # expect FAIL: "X10 drag release must copy the selection"
439
- git checkout HEAD -- src/core/pty-scroll.mjs
440
- node --test test/pty-attach-x10.test.mjs # expect PASS
441
- git diff --exit-code src/core/pty-scroll.mjs
442
- ```
443
-
444
- Expected: the first run fails with the drag assertion (`[]` instead of `["fake pi ready"]`), the second passes, and the final `git diff --exit-code` prints nothing (the tree is back to the committed rule).
445
-
446
- - [ ] **Step 5: Commit**
447
-
448
- ```bash
449
- git add test-support/pty-attach-x10-scenario.ts test/pty-attach-x10.test.mjs
450
- git commit -m "test(attach): drive real X10 press/motion/release through the component (issue #188)"
451
- ```
452
-
453
- ---
454
-
455
- ### Task 3: Repo gate and acceptance reconciliation (A6, U1 disposition)
456
-
457
- **Files:**
458
- - No source changes expected. If coverage thresholds fail, extend `test/pty-scroll.test.mjs` (Task 1's style) — never weaken or delete assertions.
459
-
460
- **Interfaces:**
461
- - Consumes: Task 1's parser rule, Task 2's scenario + driver.
462
- - Produces: a recorded A1–A6 evidence set and the U1 disposition for the issue comment / PR body.
463
-
464
- - [ ] **Step 1: Run the repo gate**
465
-
466
- Run: `npm run verify`
467
- Expected: exit code 0 — `typecheck`, the perf gate, the full `node --test test/*.test.mjs` suite, coverage thresholds (lines 85 / funcs 80 / branches 70), and `pack:dry`.
468
-
469
- - [ ] **Step 2: If coverage or the suite fails**
470
-
471
- Read the failing output and fix the cause in the tests, not by loosening thresholds or `assert` calls. A coverage dip on `src/core/pty-scroll.mjs` means an X10 branch is unreached: add the missing case to Task 1's unit block (for example a modifier+wheel combination) and re-run `npm run verify`.
472
-
473
- - [ ] **Step 3: Record the acceptance evidence**
474
-
475
- Collect, verbatim, for the issue comment and PR body:
476
-
477
- ```bash
478
- node --test test/pty-scroll.test.mjs 2>&1 | tail -5
479
- node --test test/pty-attach-x10.test.mjs 2>&1 | tail -5
480
- npm run verify 2>&1 | tail -5
481
- git log --oneline 7cf0853..HEAD
482
- ```
483
-
484
- Map them to the spec matrix: A1–A3 → the `pty-scroll` run; A4–A5 → the `pty-attach-x10` run; A6 → `npm run verify`. Mark **U1 as `pending`** with its reason and recipe pointer (spec §6.3: no X10-only terminal on this machine; the component-level scenario drives the same byte stream through the real socket), and state that it is non-blocking by design.
485
-
486
- - [ ] **Step 4: Commit (only if Step 2 changed files)**
487
-
488
- ```bash
489
- git add test/pty-scroll.test.mjs
490
- git commit -m "test(attach): cover the remaining X10 classification branches (issue #188)"
491
- ```
492
-
493
- If Steps 1–2 left the tree clean, skip this step — the branch is ready for local code review with two commits (plus the spec commit).
494
-
495
- ---
496
-
497
- ## Self-Review
498
-
499
- **1. Spec coverage:**
500
- - §3.1 classification contract → Task 1 Step 3 (exact code, `(button & 96) === 0` and `(button & 3) === 3` rule, wheel/motion exclusion).
501
- - §3.2 parser-layer (no handler patch) → Global Constraints "One edit site".
502
- - §3.3 alternatives → not implemented by design; the handler-side alternative stays rejected.
503
- - §5 A1/A2/A3 → Task 1 Step 1 rows (press, middle `\x1b[M!!"`, right `\x1b[M"!"`, motion `\x1b[M@("`, motion+3 `\x1b[MC!"`, release `\x1b[M#!"`/`\x1b[M#4"`, shift `\x1b[M'!"`, ctrl `\x1b[M3!"`, wheel `\x1b[M\`!"`/`\x1b[Ma!"`, `parseMouseEvent("x")` and the passive rows already covered by the pre-existing test block, chunk + 7-byte rejection).
504
- - §5 A4/A5 → Task 2 (drag copies via OSC 52 payload; click highlight window, right button, wheel forward).
505
- - §5 A6 → Task 3 Step 1.
506
- - §5 U1 → Task 3 Step 3 disposition (`pending`, non-blocking, recipe pointer).
507
- - §6.1 not-changed list → A2's middle/right/wheel rows plus Global Constraints on SGR/passive and `shouldCopy`.
508
- - §6.2 #187/#172/#189 boundaries → Global Constraints ("do not touch `shouldCopy`", chunk all-or-nothing pinned in A3) and Task 2's click row (no retention assertion).
509
- - §6.3 guarantee boundary → no code change needed; A2 pins the unchanged rows.
510
- - §7 testability split → Task 1 (pure, no I/O) vs Task 2 (component seam, public `handleInput`, no private-field assertions; the render probe reads the public `render()` output).
511
- - §8 risks → the wheel regression risk is pinned by A2; the #187/#172 rebases are handled by Task 3 Step 1 re-running `npm run verify`.
512
-
513
- **2. Placeholder scan:** no TBD/TODO/"handle edge cases" steps; every code step carries the full code, every command carries its expected result.
514
-
515
- **3. Type consistency:** `parseMouseEvent`/`parseMouseInputChunk` shapes are used identically in Task 1 and Task 2 (`{ encoding, button, col, row, action }`); the scenario's JSON keys (`osc52`, `forwarded`, `paintedRightAfterRelease`, `paintedAfterWindow`, `cases.drag|click|rightButton|wheel`, `ok`, `sawContent`, `error`) match between the scenario and the driver assertion by assertion.
516
-
517
- ---
518
-
519
- ## Errata (post-implementation, 2026-10-15)
520
-
521
- Two deviations from the task text above happened during execution; the committed code is authoritative.
522
-
523
- 1. **Task 2's `selectionPainted()` had to strip SGR sequences before locating the fixture line.** The plan's contiguous `includes("fake pi ready")` cannot match a 1-cell selection at column 0, because the attribute reset splits the text (`\x1b[0;7;39;49mf\x1b[0;39;49make pi ready`), so the probe would have returned `false` for a correctly painted click. The committed probe matches the fixture text on an SGR-stripped copy and still runs the `\x1b[0;7;` paint test on the raw line (`test-support/pty-attach-x10-scenario.ts`). Approved by the controller mid-task; recorded in `task-2-report.md`.
524
- 2. **One unit row was added after the final whole-branch review.** The review found the `!wheel` guard was not load-bearing in any test (wheel `Cb` 64/65 have low bits 0/1); a row for `Cb = 67` (`64|3`, horizontal wheel) → `"press"` now pins it in Task 1's block (`test/pty-scroll.test.mjs`), commit `ae581b7`.
525
-
526
- Commit sequence as executed: `5af72cb` (spec), `79cd179` (plan), `e16179f` (parser fix + A1–A3), `be3dade` (A4–A5 component scenario), `ae581b7` (review hardening). Everything else ran as written; A6 (`npm run verify`) is green at `ae581b7` (1175 tests, 1172 pass, 0 fail; coverage 91.97 / 81.47 / 89.23).
@@ -1,32 +0,0 @@
1
- # Plan — issue #198 attach wheel forward-fallback
2
-
3
- - Spec: `docs/superpowers/specs/2026-10-16-issue-198-attach-wheel-forward-fallback-design.md` (acceptance IDs from its §5)
4
- - Worktree: `.pi/worktrees/issue-198-attach-wheel-forward-fallback` on branch `issue-198-attach-wheel-forward-fallback` (base `main` @ `eab48d0`)
5
- - Flow note: same degradation as #185 — direct implementation in the parent session (the `subagent` tool is unavailable there), TDD discipline kept (red → green).
6
-
7
- ## Tasks
8
-
9
- ### T1 — production-topology regression test (A1, integration) — **red first**
10
- - Add `FAKE_PTY_STREAM_MODE=alt-flood` to `test-support/fake-pty-pi.mjs`: one-shot `\x1b[?1049h`, then absolute-positioned frames (no newline past the last row) fast enough to pass 130 KB before the attach connects.
11
- - New `test-support/pty-attach-wheel-fallback-scenario.ts` + `test/pty-attach-wheel-fallback.test.mjs`: pin `buffer.type === "normal"` and local room `0`, then require the wheel to be forwarded.
12
-
13
- ### T2 — implementation (A1)
14
- - `src/ui/pty-attach.ts` `handleMouse` wheel branch: `route === "none"` → `undefined`; `"local"` + `tryScrollBy` → handled; otherwise ( `"child"`, or `"local"` that cannot move) forward when connected, else `undefined`. Comments cite #183/#185/#198.
15
-
16
- ### T3 — keep branch coverage honest (A2, A3)
17
- - Remove the per-tick `?1049h` re-assertion from the `alt` fixture mode (it masked this bug) and force the legacy replay path in that scenario (`AGENT_BOARD_TERMINAL_SNAPSHOT=0`), documenting the premise in the header; restore the env afterwards.
18
- - Update #183's scenario comment + assertion message to the new meaning (disconnected half of the rule).
19
-
20
- ### T4 — docs (A6)
21
- - Rewrite the `README.md` PTY-attach wheel sentence to the parity rule.
22
-
23
- ### T5 — gate (A4, A5)
24
- - `npm test`, then `npm run verify` (typecheck, perf gate, tests, coverage, pack:dry).
25
-
26
- ### T6 — user verification (U1, post-merge)
27
- - Merge → `git -C ~/.pi/agent/git/github.com/zhuxixi/pi-agent-board pull --ff-only` → restart pi → wheel vs `PageUp`/`PageDown` in a real session (this time the production topology is what the new scenario encodes, so the local gate is meaningful).
28
-
29
- ## Out of scope
30
-
31
- - Conveying the alternate-screen flag through the runner's terminal model / snapshot DTO (correctness no longer depends on it).
32
- - Any change to middle-click, selection mirroring, kill switches, or `AGENT_BOARD_WHEEL_LINES` semantics.