@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,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 任务)