@huseynsnmz/pi-board 0.15.1 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (215) hide show
  1. package/CHANGELOG.md +26 -4
  2. package/CONTRIBUTING.md +7 -0
  3. package/README.md +53 -419
  4. package/SECURITY.md +19 -0
  5. package/docs/how-it-works.md +31 -0
  6. package/docs/reference.md +276 -0
  7. package/package.json +18 -19
  8. package/src/index.ts +9 -1
  9. package/src/runtime/service.mjs +12 -0
  10. package/src/ui/dashboard.ts +14 -8
  11. package/src/ui/double-left.ts +5 -1
  12. package/src/ui/focus.ts +7 -1
  13. package/src/ui/waiting.ts +10 -5
  14. package/IMPLEMENTATION_PLAN.md +0 -920
  15. package/PRD.md +0 -484
  16. package/PROGRESS.md +0 -142
  17. package/VERIFY.md +0 -115
  18. package/docs/BATCH_SELECTION_READ_FLOW.md +0 -277
  19. package/docs/EXPLORATION.md +0 -187
  20. package/docs/PTY_ATTACH_IMPLEMENTATION_PLAN.md +0 -581
  21. package/docs/RELEASE.md +0 -148
  22. package/docs/superpowers/plans/2026-08-15-screenlog-gc.md +0 -704
  23. package/docs/superpowers/plans/2026-08-16-attach-double-cursor-jiggle-retry.md +0 -499
  24. package/docs/superpowers/plans/2026-08-21-attach-coldstart-jiggle-rearm.md +0 -797
  25. package/docs/superpowers/plans/2026-08-21-dashboard-keypress-lag.md +0 -366
  26. package/docs/superpowers/plans/2026-08-21-issue-14-needs-input-no-auto-done.md +0 -576
  27. package/docs/superpowers/plans/2026-08-22-jiggle-shrink-and-hold.md +0 -337
  28. package/docs/superpowers/plans/2026-08-22-launch-cwd-favorites.md +0 -916
  29. package/docs/superpowers/plans/2026-08-22-question-tool-grouping.md +0 -211
  30. package/docs/superpowers/plans/2026-08-22-rename-state-labels.md +0 -182
  31. package/docs/superpowers/plans/2026-08-22-stable-list-order.md +0 -51
  32. package/docs/superpowers/plans/2026-08-27-locks-acquirelock-spin.md +0 -614
  33. package/docs/superpowers/plans/2026-08-27-pty-runner-test-flaky.md +0 -32
  34. package/docs/superpowers/plans/2026-08-29-code-refs-badges.md +0 -223
  35. package/docs/superpowers/plans/2026-08-30-circular-navigation.md +0 -308
  36. package/docs/superpowers/plans/2026-08-30-post-exit-timing-fix.md +0 -37
  37. package/docs/superpowers/plans/2026-08-30-pty-attach-quality-debt.md +0 -311
  38. package/docs/superpowers/plans/2026-08-30-readme-v2.md +0 -294
  39. package/docs/superpowers/plans/2026-09-01-attach-detach-gate-cursor-anchor.md +0 -284
  40. package/docs/superpowers/plans/2026-09-02-attach-detach-editor-state.md +0 -722
  41. package/docs/superpowers/plans/2026-09-03-code-refs-pr-backlink-narrow.md +0 -551
  42. package/docs/superpowers/plans/2026-09-03-detach-gate-glyph-fallback.md +0 -146
  43. package/docs/superpowers/plans/2026-09-04-evidence-outputpreview.md +0 -209
  44. package/docs/superpowers/plans/2026-09-04-warm-host-reclaim.md +0 -796
  45. package/docs/superpowers/plans/2026-09-05-issue-13-drainnextfollowup-pty-probe.md +0 -114
  46. package/docs/superpowers/plans/2026-09-05-issue-38-windows-wezterm-ime-cursor.md +0 -73
  47. package/docs/superpowers/plans/2026-09-05-issue-39-truncate-codepoint-boundary.md +0 -143
  48. package/docs/superpowers/plans/2026-09-05-issue-61-mention-fallback-guards.md +0 -226
  49. package/docs/superpowers/plans/2026-09-05-issue-63-flaky-manual-completion.md +0 -87
  50. package/docs/superpowers/plans/2026-09-05-issue-64-changelog-release-helper.md +0 -53
  51. package/docs/superpowers/plans/2026-09-05-pty-host-stacking-sock-race.md +0 -731
  52. package/docs/superpowers/plans/2026-09-08-attach-ctrl-left-detach.md +0 -30
  53. package/docs/superpowers/plans/2026-09-08-dashboard-shrink-repaint.md +0 -68
  54. package/docs/superpowers/plans/2026-09-08-issue-11-attach-runtime-desync-heal.md +0 -917
  55. package/docs/superpowers/plans/2026-09-08-legacy-stale-host-recovery.md +0 -125
  56. package/docs/superpowers/plans/2026-09-08-spawn-async-error-swallow.md +0 -56
  57. package/docs/superpowers/plans/2026-09-08-stale-model-attach-guard.md +0 -96
  58. package/docs/superpowers/plans/2026-09-09-claimpid-blocks-replace.md +0 -267
  59. package/docs/superpowers/plans/2026-09-09-harden-runner-architecture.md +0 -603
  60. package/docs/superpowers/plans/2026-09-09-single-writer-completion.md +0 -252
  61. package/docs/superpowers/plans/2026-09-10-reader-consistency.md +0 -115
  62. package/docs/superpowers/plans/2026-09-14-attach-cursor-dectcem-gate.md +0 -469
  63. package/docs/superpowers/plans/2026-09-14-attach-snapshot.md +0 -92
  64. package/docs/superpowers/plans/2026-09-14-host-meta-orphan-lock.md +0 -771
  65. package/docs/superpowers/plans/2026-09-14-issue-106-terminal-frame-cognition.md +0 -299
  66. package/docs/superpowers/plans/2026-09-14-issue-113-foreground-preview-race.md +0 -609
  67. package/docs/superpowers/plans/2026-09-14-terminal-model.md +0 -145
  68. package/docs/superpowers/plans/2026-09-15-coordinator-pipe-root-normalize.md +0 -224
  69. package/docs/superpowers/plans/2026-09-15-lease-publish-eprem-reclaim.md +0 -341
  70. package/docs/superpowers/plans/2026-09-18-detach-anchor-reporter-endpoint.md +0 -875
  71. package/docs/superpowers/plans/2026-09-20-control-lifecycle.md +0 -116
  72. package/docs/superpowers/plans/2026-09-20-issue-121-perf-gate-out-of-coverage.md +0 -517
  73. package/docs/superpowers/plans/2026-09-23-a5-mid-stream-assertion.md +0 -25
  74. package/docs/superpowers/plans/2026-09-23-cwd-browse-tab-drill.md +0 -393
  75. package/docs/superpowers/plans/2026-09-23-terminal-query-forwarding.md +0 -51
  76. package/docs/superpowers/plans/2026-09-23-unconditional-detach-key.md +0 -58
  77. package/docs/superpowers/plans/2026-09-24-control-reconcile-flake.md +0 -698
  78. package/docs/superpowers/plans/2026-09-24-probe-attached-clients.md +0 -605
  79. package/docs/superpowers/plans/2026-09-29-issue-145-holding-semantic-state.md +0 -1183
  80. package/docs/superpowers/plans/2026-09-29-issue-145-user-verification.md +0 -66
  81. package/docs/superpowers/plans/2026-09-30-board-delete-key-x-y-confirm.md +0 -649
  82. package/docs/superpowers/plans/2026-09-30-issue-153-runner-stop-boot-window.md +0 -618
  83. package/docs/superpowers/plans/2026-09-30-issue-95-test-discipline.md +0 -555
  84. package/docs/superpowers/plans/2026-09-30-publish-dispatch-tag-check.md +0 -259
  85. package/docs/superpowers/plans/2026-10-06-attach-settle-scheme-delivery.md +0 -751
  86. package/docs/superpowers/plans/2026-10-09-issue-167-attach-middle-paste.md +0 -509
  87. package/docs/superpowers/plans/2026-10-09-issue-169-fullscreen-mouse-ownership.md +0 -346
  88. package/docs/superpowers/plans/2026-10-10-attach-delete-in-place.md +0 -832
  89. package/docs/superpowers/plans/2026-10-10-issue-174-selection-mirror-paste-source.md +0 -510
  90. package/docs/superpowers/plans/2026-10-14-issue-170-attach-reply-guard.md +0 -771
  91. package/docs/superpowers/plans/2026-10-14-issue-178-accept-registration-race.md +0 -1257
  92. package/docs/superpowers/plans/2026-10-14-issue-184-snapshot-window-contract.md +0 -657
  93. package/docs/superpowers/plans/2026-10-14-issue-185-attach-wheel-alt-screen.md +0 -47
  94. package/docs/superpowers/plans/2026-10-14-issue-187-motionless-drag.md +0 -931
  95. package/docs/superpowers/plans/2026-10-14-issue-189-mixed-input-segments.md +0 -823
  96. package/docs/superpowers/plans/2026-10-15-issue-188-x10-mouse-release.md +0 -526
  97. package/docs/superpowers/plans/2026-10-16-issue-198-attach-wheel-forward-fallback.md +0 -32
  98. package/docs/superpowers/plans/2026-10-18-attach-wheel-replay-pseudo-scrollback.md +0 -437
  99. package/docs/superpowers/plans/2026-10-19-issue-213-reply-guard-csi-key.md +0 -257
  100. package/docs/superpowers/plans/2026-10-19-issue-214-attach-forward-alt-modifier.md +0 -202
  101. package/docs/superpowers/plans/2026-10-21-issue-221-ask-user-question.md +0 -198
  102. package/docs/superpowers/plans/2026-10-23-issue-222-attach-ready.md +0 -290
  103. package/docs/superpowers/plans/2026-10-23-issue-225-monotonic-foreground-state.md +0 -450
  104. package/docs/superpowers/plans/2026-10-23-issue-229-vim-jkl.md +0 -449
  105. package/docs/superpowers/plans/2026-10-23-issue-232-attach-bg-only-cells.md +0 -153
  106. package/docs/superpowers/plans/2026-10-23-lease-heartbeat-staleness.md +0 -537
  107. package/docs/superpowers/plans/2026-10-23-selected-row-highlight.md +0 -405
  108. package/docs/superpowers/plans/2026-10-24-issue-217-windows-pid-reuse-stopping.md +0 -367
  109. package/docs/superpowers/plans/2026-10-24-issue-230-warm-host-background-runs.md +0 -806
  110. package/docs/superpowers/plans/2026-10-24-issue-238-windows-process-identity-token.md +0 -490
  111. package/docs/superpowers/plans/2026-10-24-issue-241-coordinator-root-exit.md +0 -461
  112. package/docs/superpowers/plans/2026-10-25-issue-239-coordinator-replacement-identity.md +0 -504
  113. package/docs/superpowers/plans/2026-10-26-issue-245-key-hints-doc-sync.md +0 -244
  114. package/docs/superpowers/plans/2026-10-27-issue-172-fullscreen-selection-clear.md +0 -344
  115. package/docs/superpowers/plans/2026-10-27-issue-252-select-key-hints.md +0 -209
  116. package/docs/superpowers/plans/2026-10-28-issue-255-coordinator-endpoint-zombie.md +0 -442
  117. package/docs/superpowers/plans/2026-10-28-issue-257-legacy-pty-runner-endpoint-cleanup.md +0 -256
  118. package/docs/superpowers/plans/2026-11-03-cwd-favorites-full-pool.md +0 -266
  119. package/docs/superpowers/specs/2026-08-15-screenlog-gc-design.md +0 -105
  120. package/docs/superpowers/specs/2026-08-16-attach-double-cursor-jiggle-retry-design.md +0 -142
  121. package/docs/superpowers/specs/2026-08-21-attach-coldstart-jiggle-rearm-design.md +0 -110
  122. package/docs/superpowers/specs/2026-08-21-dashboard-keypress-lag-design.md +0 -59
  123. package/docs/superpowers/specs/2026-08-21-issue-14-needs-input-no-auto-done-design.md +0 -109
  124. package/docs/superpowers/specs/2026-08-22-cwd-favorites-design.md +0 -131
  125. package/docs/superpowers/specs/2026-08-22-jiggle-shrink-and-hold-design.md +0 -111
  126. package/docs/superpowers/specs/2026-08-22-question-tool-grouping-design.md +0 -114
  127. package/docs/superpowers/specs/2026-08-22-rename-state-labels-design.md +0 -80
  128. package/docs/superpowers/specs/2026-08-27-locks-acquirelock-spin-design.md +0 -97
  129. package/docs/superpowers/specs/2026-08-27-pty-runner-test-flaky-design.md +0 -35
  130. package/docs/superpowers/specs/2026-08-29-code-refs-badges-design.md +0 -128
  131. package/docs/superpowers/specs/2026-08-30-circular-navigation-design.md +0 -47
  132. package/docs/superpowers/specs/2026-08-30-eprm-atomicwrite-race-design.md +0 -77
  133. package/docs/superpowers/specs/2026-08-30-post-exit-timing-fix-design.md +0 -80
  134. package/docs/superpowers/specs/2026-08-30-pty-attach-quality-debt-design.md +0 -105
  135. package/docs/superpowers/specs/2026-08-30-readme-v2-design.md +0 -115
  136. package/docs/superpowers/specs/2026-09-01-attach-detach-gate-cursor-anchor-design.md +0 -78
  137. package/docs/superpowers/specs/2026-09-02-attach-detach-editor-state-design.md +0 -120
  138. package/docs/superpowers/specs/2026-09-03-code-refs-pr-backlink-narrow-design.md +0 -92
  139. package/docs/superpowers/specs/2026-09-03-detach-gate-glyph-fallback-design.md +0 -99
  140. package/docs/superpowers/specs/2026-09-04-evidence-outputpreview-design.md +0 -50
  141. package/docs/superpowers/specs/2026-09-04-warm-host-reclaim-design.md +0 -106
  142. package/docs/superpowers/specs/2026-09-05-issue-13-drainnextfollowup-pty-probe-design.md +0 -64
  143. package/docs/superpowers/specs/2026-09-05-issue-38-windows-wezterm-ime-design.md +0 -48
  144. package/docs/superpowers/specs/2026-09-05-issue-39-truncate-codepoint-boundary-design.md +0 -64
  145. package/docs/superpowers/specs/2026-09-05-issue-61-mention-fallback-design.md +0 -71
  146. package/docs/superpowers/specs/2026-09-05-issue-63-flaky-manual-completion-design.md +0 -49
  147. package/docs/superpowers/specs/2026-09-05-issue-64-changelog-helper-design.md +0 -76
  148. package/docs/superpowers/specs/2026-09-05-pty-host-stacking-sock-race-design.md +0 -510
  149. package/docs/superpowers/specs/2026-09-07-issue-11-attach-runtime-desync-heal-design.md +0 -130
  150. package/docs/superpowers/specs/2026-09-08-attach-ctrl-left-detach-design.md +0 -58
  151. package/docs/superpowers/specs/2026-09-08-dashboard-shrink-repaint-design.md +0 -52
  152. package/docs/superpowers/specs/2026-09-08-legacy-stale-host-recovery-design.md +0 -87
  153. package/docs/superpowers/specs/2026-09-08-spawn-async-error-swallow-design.md +0 -56
  154. package/docs/superpowers/specs/2026-09-08-stale-model-attach-guard-design.md +0 -79
  155. package/docs/superpowers/specs/2026-09-09-claimpid-blocks-replace-design.md +0 -147
  156. package/docs/superpowers/specs/2026-09-09-harden-runner-architecture-design.md +0 -304
  157. package/docs/superpowers/specs/2026-09-14-attach-cursor-dectcem-gate-design.md +0 -114
  158. package/docs/superpowers/specs/2026-09-14-host-meta-orphan-lock-design.md +0 -120
  159. package/docs/superpowers/specs/2026-09-14-issue-106-terminal-frame-cognition-design.md +0 -146
  160. package/docs/superpowers/specs/2026-09-14-issue-113-foreground-preview-race-design.md +0 -116
  161. package/docs/superpowers/specs/2026-09-15-coordinator-pipe-root-normalize-design.md +0 -84
  162. package/docs/superpowers/specs/2026-09-15-lease-publish-eprem-reclaim-design.md +0 -92
  163. package/docs/superpowers/specs/2026-09-18-detach-anchor-reporter-endpoint-design.md +0 -123
  164. package/docs/superpowers/specs/2026-09-20-issue-121-perf-gate-out-of-coverage-design.md +0 -204
  165. package/docs/superpowers/specs/2026-09-23-a5-mid-stream-assertion-design.md +0 -43
  166. package/docs/superpowers/specs/2026-09-23-cwd-browse-tab-drill-design.md +0 -95
  167. package/docs/superpowers/specs/2026-09-23-terminal-query-forwarding-design.md +0 -63
  168. package/docs/superpowers/specs/2026-09-23-unconditional-detach-key-design.md +0 -57
  169. package/docs/superpowers/specs/2026-09-24-control-reconcile-flake-design.md +0 -98
  170. package/docs/superpowers/specs/2026-09-24-probe-attached-clients-design.md +0 -190
  171. package/docs/superpowers/specs/2026-09-29-issue-145-holding-semantic-state-design.md +0 -394
  172. package/docs/superpowers/specs/2026-09-30-board-delete-key-x-y-confirm-design.md +0 -86
  173. package/docs/superpowers/specs/2026-09-30-issue-153-runner-stop-boot-window-design.md +0 -75
  174. package/docs/superpowers/specs/2026-09-30-issue-95-test-discipline-design.md +0 -226
  175. package/docs/superpowers/specs/2026-09-30-publish-dispatch-tag-check-design.md +0 -129
  176. package/docs/superpowers/specs/2026-10-06-attach-settle-scheme-delivery-design.md +0 -196
  177. package/docs/superpowers/specs/2026-10-09-issue-167-attach-middle-paste-design.md +0 -125
  178. package/docs/superpowers/specs/2026-10-09-issue-169-fullscreen-mouse-ownership-design.md +0 -130
  179. package/docs/superpowers/specs/2026-10-10-attach-delete-in-place-design.md +0 -224
  180. package/docs/superpowers/specs/2026-10-10-issue-174-selection-mirror-paste-source-design.md +0 -154
  181. package/docs/superpowers/specs/2026-10-14-issue-170-attach-reply-guard-design.md +0 -195
  182. package/docs/superpowers/specs/2026-10-14-issue-178-accept-registration-race-design.md +0 -287
  183. package/docs/superpowers/specs/2026-10-14-issue-184-snapshot-window-contract-design.md +0 -288
  184. package/docs/superpowers/specs/2026-10-14-issue-185-attach-wheel-alt-screen-design.md +0 -126
  185. package/docs/superpowers/specs/2026-10-14-issue-187-motionless-drag-design.md +0 -269
  186. package/docs/superpowers/specs/2026-10-14-issue-189-mixed-input-segments-design.md +0 -369
  187. package/docs/superpowers/specs/2026-10-15-issue-188-x10-mouse-release-design.md +0 -252
  188. package/docs/superpowers/specs/2026-10-16-issue-198-attach-wheel-forward-fallback-design.md +0 -67
  189. package/docs/superpowers/specs/2026-10-18-attach-wheel-replay-pseudo-scrollback-design.md +0 -250
  190. package/docs/superpowers/specs/2026-10-19-issue-213-reply-guard-csi-key-design.md +0 -165
  191. package/docs/superpowers/specs/2026-10-19-issue-214-attach-forward-alt-modifier-design.md +0 -131
  192. package/docs/superpowers/specs/2026-10-20-issue-221-ask-user-question-design.md +0 -270
  193. package/docs/superpowers/specs/2026-10-22-issue-225-monotonic-foreground-state-design.md +0 -225
  194. package/docs/superpowers/specs/2026-10-23-issue-222-attach-ready-design.md +0 -204
  195. package/docs/superpowers/specs/2026-10-23-issue-229-vim-jkl-design.md +0 -90
  196. package/docs/superpowers/specs/2026-10-23-issue-232-attach-bg-only-cells-design.md +0 -146
  197. package/docs/superpowers/specs/2026-10-23-lease-heartbeat-staleness-design.md +0 -271
  198. package/docs/superpowers/specs/2026-10-23-selected-row-highlight-design.md +0 -191
  199. package/docs/superpowers/specs/2026-10-24-issue-217-windows-pid-reuse-stopping-design.md +0 -334
  200. package/docs/superpowers/specs/2026-10-24-issue-230-warm-host-background-runs-design.md +0 -283
  201. package/docs/superpowers/specs/2026-10-24-issue-238-windows-process-identity-token-design.md +0 -286
  202. package/docs/superpowers/specs/2026-10-24-issue-241-coordinator-root-exit-design.md +0 -381
  203. package/docs/superpowers/specs/2026-10-25-issue-239-coordinator-replacement-identity-design.md +0 -331
  204. package/docs/superpowers/specs/2026-10-26-issue-245-key-hints-doc-sync-design.md +0 -72
  205. package/docs/superpowers/specs/2026-10-27-issue-172-fullscreen-selection-clear-design.md +0 -248
  206. package/docs/superpowers/specs/2026-10-27-issue-252-select-key-hints-design.md +0 -95
  207. package/docs/superpowers/specs/2026-10-28-issue-255-coordinator-endpoint-zombie-design.md +0 -380
  208. package/docs/superpowers/specs/2026-10-28-issue-257-legacy-pty-runner-endpoint-cleanup-design.md +0 -197
  209. package/docs/superpowers/specs/2026-11-03-cwd-favorites-full-pool-design.md +0 -106
  210. package/scripts/budget-audit.mjs +0 -135
  211. package/scripts/patch-vulns.mjs +0 -59
  212. package/scripts/release.mjs +0 -446
  213. package/scripts/release_helper.mjs +0 -309
  214. package/scripts/run-perf-gate.mjs +0 -40
  215. package/scripts/run-tests.mjs +0 -375
@@ -1,337 +0,0 @@
1
- # Jiggle shrink-and-hold 协议改造 Implementation Plan(issue #25)
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:** 把 attach 的 jiggle 从"±1 脉冲对"改成"shrink-and-hold"协议——缩住直到见到全清,冷启动自愈 ≤3s,同时以 G1-G5 守卫保证不破坏任何现有功能。
6
-
7
- **Architecture:** 协议主体在 `pty-attach-jiggle-controller.mjs`(armed→held→restored 状态 + G1/G2 计时);`pty-attach-jiggle-retry.mjs` 状态机不动(退避表仅作 G2 计时);`pty-attach.ts` 替换 forceChildRedraw 胶水并接入 G3/G4/G5;E2E stub 改 hold 语义并新增 shell 兜底用例。
8
-
9
- **Tech Stack:** Node 24(node:test)、TypeScript(tsc --noEmit)、node-pty、@xterm/headless。
10
-
11
- ## Global Constraints
12
-
13
- - 工作目录:`/home/elling/git-repo/github/pi-agent-board/.pi/worktrees/issue-25-jiggle-shrink-and-hold`(**禁碰主 checkout**)。
14
- - hold 尺寸 = (cols−1)×(rows−1);restore = 原始尺寸;restore **幂等(只发一次)**。
15
- - G1 = start 后 6s 无 `\x1b[?2026h` → restore;G2 = 退避表走完(56.12s)无全清 → restore。
16
- - re-arm(首个 TUI 帧)→ **restore 原尺寸且不重排链**(无脉冲)。
17
- - G3:close 时 `restoreAndStop()`;G5:`start()` 前若 held 先 restore;G4:`notifyExternalResize(c,r)` 取消 hold/计时器并把 (c,r) 记为新原尺寸。
18
- - 删除 `JIGGLE_RESTORE_MS`(无脉冲对)。
19
- - 代码风格:tab 缩进、双引号、JSDoc、core 模块 .mjs;commit 用 conventional commits;`git add` 按文件。
20
- - Spec:`docs/superpowers/specs/2026-08-22-jiggle-shrink-and-hold-design.md`(已提交 b9b3aab)。
21
-
22
- ---
23
-
24
- ### Task 1: 控制器协议改造(hold 状态机 + G1/G2/G3/G4/G5)
25
-
26
- **Files:**
27
- - Modify: `src/core/pty-attach-jiggle-controller.mjs`
28
- - Test: `test/pty-attach-jiggle-controller.test.mjs`
29
-
30
- **Interfaces:**
31
- - Consumes: `pty-attach-jiggle-retry.mjs` 的 `createJiggleRetryState/nextRetryDelay/advanceRetry/stopRetry` 与 `feedOutput`(含 frameStartFound/clearFound)——**全部原样使用,不改**。
32
- - Produces(Task 2/3 依赖):
33
- - deps: `{ sendResize(cols, rows), setTimeoutFn, clearTimeoutFn }`(`sendJiggle`/`shouldFire` **删除**)
34
- - 返回 `{ start(cols, rows), feed(data), restoreAndStop(), notifyExternalResize(cols, rows), getState() }`
35
- - `getState()` → `{ held, clearDetected, stopped, tuiFrameSeen, originalCols, originalRows }`(retryIndex 可保留内部)
36
-
37
- **行为规格(精确):**
38
-
39
- ```js
40
- start(cols, rows):
41
- 1. 清全部计时器;若 held → sendResize(prevOriginalCols, prevOriginalRows)(G5,幂等标记复位)
42
- 2. originalCols/Rows = (cols, rows)
43
- 3. sendResize(cols, rows); sendResize(cols-1, rows-1); held = true; restored = false
44
- 4. tuiFrameSeen = false; clearDetected = false; stopped = false; state = createJiggleRetryState()
45
- 5. G1 计时器 = 6s:fire 时若 !tuiFrameSeen → restoreIfHeld()
46
- 6. 预算链照常排(nextRetryDelay/advanceRetry 循环),但回调 fire 时 **no-op**(不发脉冲、不 sendResize;仅推进计数);delay 走完(null)→ stopRetry + restoreIfHeld()(G2)
47
-
48
- feed(data):
49
- 1. 若 clearDetected → return(沿用)
50
- 2. feedOutput 扫描(沿用)
51
- 3. clearFound → restoreIfHeld(); 停链(stopRetry+clearTimer); clearDetected=true; 取消 G1
52
- 4. frameStartFound 且 !tuiFrameSeen → tuiFrameSeen=true; 取消 G1; **restoreIfHeld()**; 不重排链、不碰预算
53
-
54
- restoreIfHeld(): if (held && !restored) { sendResize(originalCols, originalRows); restored = true; held = false; }
55
-
56
- restoreAndStop(): 清全部计时器; restoreIfHeld(); state = stopRetry(state)
57
-
58
- notifyExternalResize(cols, rows):
59
- 清全部计时器; held = false; restored = false(hold 作废,G4)
60
- originalCols/Rows = (cols, rows)
61
- state = stopRetry(state)(链终止;本次 attach 语义已让位于真实 resize)
62
- ```
63
-
64
- - [ ] **Step 1: 重写单测**(全量替换 test/pty-attach-jiggle-controller.test.mjs,fake scheduler 记录 sendResize 调用序列):
65
-
66
- ```js
67
- import assert from "node:assert/strict";
68
- import test from "node:test";
69
- import { createJiggleRetryController } from "../src/core/pty-attach-jiggle-controller.mjs";
70
-
71
- function fakeScheduler() {
72
- const timers = new Map();
73
- let nextId = 1;
74
- return {
75
- timers,
76
- setTimeoutFn(fn, ms) {
77
- const id = nextId++;
78
- timers.set(id, { fn, ms });
79
- return id;
80
- },
81
- clearTimeoutFn(id) { timers.delete(id); },
82
- fire(id) { const t = timers.get(id); assert.ok(t, "no such timer"); timers.delete(id); t.fn(); },
83
- fireNext() { const first = timers.entries().next().value; assert.ok(first, "no pending timer"); this.fire(first[0]); },
84
- delays() { return [...timers.values()].map((t) => t.ms); },
85
- findByDelay(ms) { for (const [id, t] of timers) if (t.ms === ms) return id; return null; },
86
- };
87
- }
88
-
89
- function makeController() {
90
- const scheduler = fakeScheduler();
91
- const resizes = [];
92
- const controller = createJiggleRetryController({
93
- sendResize: (c, r) => resizes.push([c, r]),
94
- setTimeoutFn: scheduler.setTimeoutFn,
95
- clearTimeoutFn: scheduler.clearTimeoutFn,
96
- });
97
- return { controller, scheduler, resizes };
98
- }
99
-
100
- test("start() arms hold: sends original then shrunk size, holds", () => {
101
- const { controller, scheduler, resizes } = makeController();
102
- controller.start(170, 36);
103
- assert.deepEqual(resizes, [[170, 36], [169, 35]]);
104
- assert.equal(controller.getState().held, true);
105
- assert.notEqual(scheduler.findByDelay(6000), null, "G1 timer armed"); // 6000 in delays
106
- assert.ok(scheduler.delays().includes(6000), "G1 timer present");
107
- });
108
-
109
- test("feed with clear restores original size exactly once and stops", () => {
110
- const { controller, scheduler, resizes } = makeController();
111
- controller.start(170, 36);
112
- controller.feed("x\x1b[2J\x1b[Hy");
113
- assert.deepEqual(resizes.slice(-1), [[170, 36]]);
114
- assert.equal(controller.getState().clearDetected, true);
115
- assert.equal(controller.getState().stopped, true);
116
- assert.equal(controller.getState().held, false);
117
- // 幂等:后续 feed 不再发
118
- controller.feed("more\x1b[2J");
119
- assert.equal(resizes.filter(([c]) => c === 170).length, 2); // start 的 + restore 的,无第三发
120
- });
121
-
122
- test("re-arm (first TUI frame) restores size and does NOT reschedule chain", () => {
123
- const { controller, scheduler, resizes } = makeController();
124
- controller.start(170, 36);
125
- const budgetBefore = scheduler.delays().length;
126
- controller.feed("\x1b[?2026h first frame");
127
- assert.deepEqual(resizes.slice(-1), [[170, 36]]);
128
- assert.equal(controller.getState().tuiFrameSeen, true);
129
- assert.equal(scheduler.delays().length, budgetBefore, "no new timers scheduled");
130
- // G1 已取消
131
- assert.equal(scheduler.findByDelay(6000), null);
132
- });
133
-
134
- test("G1: no TUI frame within 6s restores original size", () => {
135
- const { controller, scheduler, resizes } = makeController();
136
- controller.start(170, 36);
137
- const g1 = scheduler.findByDelay(6000);
138
- assert.ok(g1);
139
- scheduler.fire(g1);
140
- assert.deepEqual(resizes.slice(-1), [[170, 36]]);
141
- assert.equal(controller.getState().held, false);
142
- });
143
-
144
- test("G1 cancelled once a frame arrives (no double restore)", () => {
145
- const { controller, scheduler, resizes } = makeController();
146
- controller.start(170, 36);
147
- controller.feed("\x1b[?2026h frame");
148
- const g1 = scheduler.findByDelay(6000);
149
- assert.equal(g1, null);
150
- });
151
-
152
- test("G2: backoff budget exhausted without clear restores original size", () => {
153
- const { controller, scheduler, resizes } = makeController();
154
- controller.start(170, 36);
155
- controller.feed("\x1b[?2026h frame"); // re-arm restores, cancels G1
156
- const before = resizes.length;
157
- // 排干全部退避计时器(无脉冲 fire 均为推进)
158
- for (let i = 0; i < 8; i++) scheduler.fireNext();
159
- assert.equal(controller.getState().stopped, true);
160
- // hold 已在 re-arm 时恢复,G2 无需再发(幂等)
161
- assert.equal(resizes.length, before);
162
- });
163
-
164
- test("G2 without any frame: budget exhausted restores", () => {
165
- const { controller, scheduler, resizes } = makeController();
166
- controller.start(170, 36);
167
- const g1 = scheduler.findByDelay(6000);
168
- scheduler.fireNext(); // 第一个退避计时器(120ms)推进,no-op
169
- for (let i = 0; i < 7; i++) scheduler.fireNext();
170
- assert.equal(controller.getState().stopped, true);
171
- assert.deepEqual(resizes.slice(-1), [[170, 36]], "G2 restore fired");
172
- });
173
-
174
- test("restoreAndStop() restores then stops (G3)", () => {
175
- const { controller, scheduler, resizes } = makeController();
176
- controller.start(170, 36);
177
- controller.restoreAndStop();
178
- assert.deepEqual(resizes.slice(-1), [[170, 36]]);
179
- assert.equal(controller.getState().stopped, true);
180
- assert.equal(scheduler.timers.size, 0);
181
- });
182
-
183
- test("start() after a held start restores previous hold first (G5)", () => {
184
- const { controller, scheduler, resizes } = makeController();
185
- controller.start(170, 36);
186
- controller.start(196, 39); // 新连接
187
- assert.deepEqual(resizes.slice(0, 4), [[170, 36], [169, 35], [170, 36], [196, 39]]);
188
- assert.deepEqual(resizes.slice(-1), [[195, 38]]);
189
- });
190
-
191
- test("notifyExternalResize cancels hold, adopts new size, stops chain (G4)", () => {
192
- const { controller, scheduler, resizes } = makeController();
193
- controller.start(170, 36);
194
- resizes.length = 0;
195
- controller.notifyExternalResize(200, 50);
196
- assert.equal(scheduler.timers.size, 0, "all timers cleared");
197
- assert.equal(controller.getState().held, false);
198
- assert.equal(controller.getState().stopped, true);
199
- assert.equal(controller.getState().originalCols, 200);
200
- // 再 feed clear 不再触发任何 restore
201
- controller.feed("\x1b[2J");
202
- assert.equal(resizes.length, 0);
203
- });
204
-
205
- test("full G1 window passes while frames keep arriving → held restored by G2 only", () => {
206
- const { controller, scheduler, resizes } = makeController();
207
- controller.start(170, 36);
208
- controller.feed("\x1b[?2026h frame"); // restores once
209
- const before = resizes.length;
210
- // 无全清的持续输出
211
- for (let i = 0; i < 8; i++) scheduler.fireNext();
212
- assert.equal(resizes.length, before, "no extra resizes");
213
- assert.equal(controller.getState().stopped, true);
214
- });
215
- ```
216
-
217
- - [ ] **Step 2: 跑测试确认失败**(接口不匹配即失败)
218
- Run: `node --test test/pty-attach-jiggle-controller.test.mjs` → FAIL
219
-
220
- - [ ] **Step 3: 实现控制器改造**(按上方行为规格重写 `src/core/pty-attach-jiggle-controller.mjs`;保留模块头注释并更新为 hold 协议描述;G1 常量 `NO_FRAME_RESTORE_MS = 6000`)
221
-
222
- - [ ] **Step 4: 跑测试确认通过** → PASS(此时 pty-attach.ts 尚未接线,typecheck 可能因 deps 变化报错——本任务只保证控制器单测绿;组件接线在 Task 2)
223
-
224
- - [ ] **Step 5: Commit**
225
- ```bash
226
- git add src/core/pty-attach-jiggle-controller.mjs test/pty-attach-jiggle-controller.test.mjs
227
- git commit -m "feat: shrink-and-hold jiggle protocol with G1-G5 guards (issue #25)"
228
- ```
229
-
230
- ---
231
-
232
- ### Task 2: 组件接线(forceChildRedraw → start;G3/G4/G5 接入)
233
-
234
- **Files:**
235
- - Modify: `src/ui/pty-attach.ts`
236
-
237
- **Interfaces:**
238
- - Consumes: Task 1 的 `{ start(cols, rows), feed, restoreAndStop, notifyExternalResize, getState }`;deps `{ sendResize, setTimeoutFn, clearTimeoutFn }`。
239
-
240
- **精确改动:**
241
- 1. 控制器字段初始化改为:
242
- ```ts
243
- private readonly jiggleRetry = createJiggleRetryController({
244
- sendResize: (cols, rows) => this.sendResize(cols, rows),
245
- setTimeoutFn: (fn, ms) => {
246
- const t = setTimeout(fn, ms);
247
- t.unref?.();
248
- return t;
249
- },
250
- clearTimeoutFn: (t) => clearTimeout(t as ReturnType<typeof setTimeout>),
251
- });
252
- ```
253
- 2. connect 处理器:删除 `this.forceChildRedraw()` 与其前独立 `this.sendResize()`(协议已在 start 内含 sendResize 原尺寸);改为 `this.jiggleRetry.start(this.cols, this.rows);`
254
- 3. `forceChildRedraw()` 方法整个删除;`JIGGLE_RESTORE_MS` 常量及其注释删除;`clearRedrawTimer()` 若仅被 forceChildRedraw/close 使用则一并删除(先 grep 确认无他引用;close() 中 `this.clearRedrawTimer()` 调用改为无操作或删除该行)。
255
- 4. `resizeIfNeeded`:在 `this.term.resize(...)` 前插入 `this.jiggleRetry.notifyExternalResize(size.cols, size.rows);`(G4)——注意它必须在确认尺寸真的变化后调用(函数内 size 更新之后、sendResize 之前)。
256
- 5. `close()`:`this.jiggleRetry.stop()` 改为 `this.jiggleRetry.restoreAndStop()`(G3)。
257
- 6. `checkClearSequence` → `this.jiggleRetry.feed(data)` 不变。
258
-
259
- - [ ] **Step 1: 应用上述 6 处改动**
260
- - [ ] **Step 2: 验证**
261
- Run: `npm run typecheck`(必须干净);`node --test test/*.test.mjs`(除 E2E 外全绿;E2E 在 Task 3 重写)
262
- - [ ] **Step 3: Commit**
263
- ```bash
264
- git add src/ui/pty-attach.ts
265
- git commit -m "feat: wire shrink-and-hold controller into attach component (issue #25)"
266
- ```
267
-
268
- ---
269
-
270
- ### Task 3: E2E 重写(hold 语义)+ shell 兜底用例
271
-
272
- **Files:**
273
- - Modify: `test-support/fake-coldstart-tui-pi.mjs`
274
- - Test: `test/pty-attach-cold-start-e2e.test.mjs`(重写)
275
-
276
- **stub 新语义(精确):**
277
-
278
- ```js
279
- #!/usr/bin/env node
280
- // Fake cold-booting pi for the attach shrink-and-hold E2E (issue #25).
281
- // Boot noise first; after STUB_TUI_DELAY_MS "starts the TUI": installs the
282
- // resize listener and emits \x1b[?2026h-wrapped frames. Holds the protocol:
283
- // whenever the PTY size becomes (cols, rows) where cols/rows differ from the
284
- // size at TUI start (the hold's promised width delta), the running TUI
285
- // responds with a fullRender containing \x1b[2J — because pi-tui's
286
- // widthChanged path fires for ANY width change from its baseline.
287
- const delay = Number(process.env.STUB_TUI_DELAY_MS ?? 8000);
288
- process.stdout.write("boot: loading extensions...\n");
289
- let started = false;
290
- let baseline = null;
291
- function startTui() {
292
- if (started) return;
293
- started = true;
294
- process.stdout.on("resize", () => {
295
- const c = process.stdout.columns;
296
- const r = process.stdout.rows;
297
- if (baseline === null) baseline = [c, r]; // 首个见到的尺寸=基线
298
- if (c === baseline[0] && r === baseline[1]) return; // 无变化不全清
299
- baseline = [c, r]; // 全清后新基线
300
- process.stdout.write(
301
- `\x1b[?2026h\x1b[2J\x1b[H\x1b[3Jframe@${c}x${r}\x1b[?2026l`,
302
- );
303
- });
304
- process.stdout.write("\x1b[?2004h\x1b[?25l");
305
- frame();
306
- }
307
- function frame() { process.stdout.write("\x1b[?2026hstub: working...\x1b[?2026l"); }
308
- setTimeout(startTui, delay);
309
- setInterval(() => { if (started) frame(); }, 100);
310
- process.on("SIGTERM", () => process.exit(0));
311
- process.on("SIGINT", () => process.exit(0));
312
- ```
313
-
314
- **E2E 用例 1(冷启动 ≤3s 自愈 + 终态原尺寸)**:runner + stub(delay 8000)+ 真控制器。connect 时仅 `controller.start(196, 39)`(不再手动 sendResize/jiggle——协议在 start 内)。喂全部 output 给 feed。断言:
315
- - 首个 `\x1b[2J` 到达时刻 − 首帧(tuiFrameSeen 翻转)时刻 ≤ **3000ms**(frameAt/clearAt 都取自 controller.getState() 翻转的采样点,沿用 #10 的采样法)
316
- - `getState().held === false` 且收到过 resize(196,39)(终态原尺寸;从 runner 侧回读 host.json cols/rows == 196/39 亦可作证)
317
-
318
- **E2E 用例 2(shell 型子进程 G1 兜底)**:piArgsPrefix 指向 `test-support/fake-pty-pi.mjs`(既有,无 2026h、无 resize 响应)。connect 后 `controller.start(120, 36)`;喂 output;**手动 fire G1**不可行(真计时器)——改为等真 6s:断言 8s 内收到 sendResize(120, 36)(G1 restore)。测试时长 ~8s 可接受。
319
-
320
- **E2E 用例 3(可选,若前两个已覆盖 G4 则略)**:略——G4 由单测覆盖,不在 E2E 层重复。
321
-
322
- - [ ] **Step 1: 重写 stub 与 E2E**(保留 node-pty skip 守卫、freshRoot/waitFor/send 约定)
323
- - [ ] **Step 2: 跑 E2E**
324
- Run: `node --test test/pty-attach-cold-start-e2e.test.mjs` → 两用例 PASS(用例 1 ~10-12s;用例 2 ~8s)
325
- - [ ] **Step 3: Commit**
326
- ```bash
327
- git add test-support/fake-coldstart-tui-pi.mjs test/pty-attach-cold-start-e2e.test.mjs
328
- git commit -m "test: hold-protocol E2E cold-start and shell-fallback cases (issue #25)"
329
- ```
330
-
331
- ---
332
-
333
- ### Task 4: 全量验证 + spec 对照
334
-
335
- - [ ] **Step 1**: `npm run typecheck` 干净;`npm test` 全绿(含新 E2E)
336
- - [ ] **Step 2**: 对照 spec 验收 1/2/3/4:E2E ≤3s ✓;shell G1 兜底 ✓;8 风险面守卫测试映射清单(写进 commit 后的验证评论)✓
337
- - [ ] **Step 3**: `git status --short` 空;`git log --oneline main..HEAD` = 5 commits(spec+plan+3 任务)