@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,499 +0,0 @@
1
- # Attach Double-Cursor Jiggle Retry 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:** Add jiggle retry logic that re-sends resize jiggle with backoff until a full-clear sequence (`\x1b[2J`) is detected in the PTY output, fixing the double-cursor desync on cold-start/streaming attach.
6
-
7
- **Architecture:** Pure state machine in `src/core/pty-attach-jiggle-retry.mjs` handles all retry logic (clear detection, backoff scheduling, stop conditions). The UI component `src/ui/pty-attach.ts` acts as glue — feeding socket data to the state machine and managing the retry timer.
8
-
9
- **Tech Stack:** TypeScript (ESM), Node.js `node:test`, `@xterm/headless`
10
-
11
- ## Global Constraints
12
-
13
- - Pure logic must be dependency-free (no socket, no xterm, no timers) — testable in isolation
14
- - Use `node:test` + `node:assert/strict` for tests (existing project pattern)
15
- - Commit messages in English, conventional format (`feat:`/`fix:`/`test:`)
16
- - Worktree: `/home/elling/git-repo/github/pi-agent-board/.claude/worktrees/issue-2-attach-double-cursor-jiggle-retry`
17
- - Don't touch `main` branch — all work on `issue-2-attach-double-cursor-jiggle-retry`
18
-
19
- ---
20
-
21
- ### Task 1: Pure Logic — Jiggle Retry State Machine
22
-
23
- **Files:**
24
- - Create: `src/core/pty-attach-jiggle-retry.mjs`
25
- - Test: `test/pty-attach-jiggle-retry.test.mjs`
26
-
27
- **Interfaces:**
28
- - Consumes: nothing (standalone module)
29
- - Produces:
30
- - `BACKOFF_MS: readonly number[]` — `[120, 500, 1500, 3000]`
31
- - `MAX_RETRIES: number` — `4`
32
- - `createJiggleRetryState(): JiggleRetryState`
33
- - `feedOutput(state, data, carry): { state, carry, clearFound }`
34
- - `nextRetryDelay(state): number | null`
35
- - `advanceRetry(state): JiggleRetryState`
36
- - `stopRetry(state): JiggleRetryState`
37
- - `hasFullClearSequence(data: string): boolean`
38
-
39
- - [ ] **Step 1: Write the failing tests**
40
-
41
- ```javascript
42
- import assert from "node:assert/strict";
43
- import test from "node:test";
44
- import {
45
- BACKOFF_MS,
46
- MAX_RETRIES,
47
- createJiggleRetryState,
48
- feedOutput,
49
- nextRetryDelay,
50
- advanceRetry,
51
- stopRetry,
52
- hasFullClearSequence,
53
- } from "../src/core/pty-attach-jiggle-retry.mjs";
54
-
55
- // --- hasFullClearSequence ---
56
-
57
- test("detects \\x1b[2J in plain data", () => {
58
- assert.equal(hasFullClearSequence("hello\x1b[2Jworld"), true);
59
- });
60
-
61
- test("detects \\x1b[2J at start", () => {
62
- assert.equal(hasFullClearSequence("\x1b[2J"), true);
63
- });
64
-
65
- test("detects \\x1b[2J followed by \\x1b[H", () => {
66
- assert.equal(hasFullClearSequence("\x1b[2J\x1b[H"), true);
67
- });
68
-
69
- test("rejects data without \\x1b[2J", () => {
70
- assert.equal(hasFullClearSequence("hello world"), false);
71
- });
72
-
73
- test("rejects \\x1b[3J (scrollback clear, not full clear)", () => {
74
- assert.equal(hasFullClearSequence("\x1b[3J"), false);
75
- });
76
-
77
- test("rejects partial escape \\x1b[2 (no J)", () => {
78
- assert.equal(hasFullClearSequence("\x1b[2"), false);
79
- });
80
-
81
- // --- createJiggleRetryState ---
82
-
83
- test("initial state is fresh", () => {
84
- const s = createJiggleRetryState();
85
- assert.equal(s.retryIndex, 0);
86
- assert.equal(s.clearDetected, false);
87
- assert.equal(s.stopped, false);
88
- });
89
-
90
- // --- feedOutput ---
91
-
92
- test("feedOutput detects clear sequence", () => {
93
- const s = createJiggleRetryState();
94
- const r = feedOutput(s, "some output \x1b[2J more", "");
95
- assert.equal(r.clearFound, true);
96
- assert.equal(r.state.clearDetected, true);
97
- });
98
-
99
- test("feedOutput returns clearFound=false when no clear", () => {
100
- const s = createJiggleRetryState();
101
- const r = feedOutput(s, "normal output", "");
102
- assert.equal(r.clearFound, false);
103
- assert.equal(r.state.clearDetected, false);
104
- });
105
-
106
- test("feedOutput handles cross-chunk split: \\x1b[ + 2J", () => {
107
- const s = createJiggleRetryState();
108
- const r1 = feedOutput(s, "data\x1b[", "");
109
- assert.equal(r1.clearFound, false);
110
- assert.equal(r1.carry, "\x1b[");
111
- const r2 = feedOutput(r1.state, "2Jrest", r1.carry);
112
- assert.equal(r2.clearFound, true);
113
- });
114
-
115
- test("feedOutput handles cross-chunk split: \\x1b + [2J", () => {
116
- const s = createJiggleRetryState();
117
- const r1 = feedOutput(s, "data\x1b", "");
118
- assert.equal(r1.clearFound, false);
119
- assert.equal(r1.carry, "\x1b");
120
- const r2 = feedOutput(r1.state, "[2Jrest", r1.carry);
121
- assert.equal(r2.clearFound, true);
122
- });
123
-
124
- test("feedOutput clears carry after successful detection", () => {
125
- const s = createJiggleRetryState();
126
- const r1 = feedOutput(s, "data\x1b[", "");
127
- const r2 = feedOutput(r1.state, "2Jrest", r1.carry);
128
- assert.equal(r2.carry, "");
129
- });
130
-
131
- test("feedOutput keeps carry for non-matching partial", () => {
132
- const s = createJiggleRetryState();
133
- const r = feedOutput(s, "data\x1b[3", "");
134
- assert.equal(r.clearFound, false);
135
- assert.ok(r.carry.length > 0);
136
- });
137
-
138
- // --- nextRetryDelay ---
139
-
140
- test("nextRetryDelay returns BACKOFF_MS in order", () => {
141
- let s = createJiggleRetryState();
142
- for (let i = 0; i < MAX_RETRIES; i++) {
143
- assert.equal(nextRetryDelay(s), BACKOFF_MS[i]);
144
- s = advanceRetry(s);
145
- }
146
- });
147
-
148
- test("nextRetryDelay returns null after MAX_RETRIES", () => {
149
- let s = createJiggleRetryState();
150
- for (let i = 0; i < MAX_RETRIES; i++) s = advanceRetry(s);
151
- assert.equal(nextRetryDelay(s), null);
152
- });
153
-
154
- test("nextRetryDelay returns null when clearDetected", () => {
155
- const s = createJiggleRetryState();
156
- const r = feedOutput(s, "\x1b[2J", "");
157
- assert.equal(nextRetryDelay(r.state), null);
158
- });
159
-
160
- test("nextRetryDelay returns null when stopped", () => {
161
- const s = stopRetry(createJiggleRetryState());
162
- assert.equal(nextRetryDelay(s), null);
163
- });
164
-
165
- // --- advanceRetry ---
166
-
167
- test("advanceRetry increments retryIndex", () => {
168
- const s0 = createJiggleRetryState();
169
- const s1 = advanceRetry(s0);
170
- assert.equal(s1.retryIndex, 1);
171
- const s2 = advanceRetry(s1);
172
- assert.equal(s2.retryIndex, 2);
173
- });
174
-
175
- // --- stopRetry ---
176
-
177
- test("stopRetry sets stopped=true", () => {
178
- const s = stopRetry(createJiggleRetryState());
179
- assert.equal(s.stopped, true);
180
- });
181
-
182
- // --- Immutability ---
183
-
184
- test("feedOutput does not mutate original state", () => {
185
- const s = createJiggleRetryState();
186
- feedOutput(s, "\x1b[2J", "");
187
- assert.equal(s.clearDetected, false);
188
- });
189
-
190
- test("advanceRetry does not mutate original state", () => {
191
- const s = createJiggleRetryState();
192
- advanceRetry(s);
193
- assert.equal(s.retryIndex, 0);
194
- });
195
-
196
- test("stopRetry does not mutate original state", () => {
197
- const s = createJiggleRetryState();
198
- stopRetry(s);
199
- assert.equal(s.stopped, false);
200
- });
201
- ```
202
-
203
- - [ ] **Step 2: Run tests to verify they fail**
204
-
205
- ```bash
206
- cd /home/elling/git-repo/github/pi-agent-board/.claude/worktrees/issue-2-attach-double-cursor-jiggle-retry
207
- node --test test/pty-attach-jiggle-retry.test.mjs
208
- ```
209
-
210
- Expected: FAIL — module not found
211
-
212
- - [ ] **Step 3: Implement the state machine**
213
-
214
- ```javascript
215
- /** Pure logic for attach jiggle-retry: detect full-clear, schedule backoff retries. */
216
-
217
- export const BACKOFF_MS = Object.freeze([120, 500, 1500, 3000]);
218
- export const MAX_RETRIES = BACKOFF_MS.length;
219
-
220
- const FULL_CLEAR = "\x1b[2J";
221
- /** Carry enough bytes to catch a split escape sequence at chunk boundary. */
222
- const CARRY_LEN = FULL_CLEAR.length - 1; // 3 bytes: \x1b, \x1b[, \x1b[2
223
-
224
- /**
225
- * @typedef {Object} JiggleRetryState
226
- * @property {number} retryIndex - Current retry round (0 = initial, not yet retried).
227
- * @property {boolean} clearDetected - Whether a full-clear sequence was seen.
228
- * @property {boolean} stopped - Whether the retry chain has been stopped.
229
- */
230
-
231
- /** Create initial retry state. */
232
- export function createJiggleRetryState() {
233
- return { retryIndex: 0, clearDetected: false, stopped: false };
234
- }
235
-
236
- /**
237
- * Feed PTY output data into the state machine.
238
- * Returns new state, updated carry buffer, and whether a clear was found.
239
- * @param {JiggleRetryState} state
240
- * @param {string} data - New output data chunk.
241
- * @param {string} carry - Leftover partial escape sequence from previous chunk.
242
- * @returns {{ state: JiggleRetryState, carry: string, clearFound: boolean }}
243
- */
244
- export function feedOutput(state, data, carry) {
245
- const combined = carry + data;
246
- const clearFound = hasFullClearSequence(combined);
247
- const newCarry = clearFound ? "" : tailCarry(combined);
248
- const newState = clearFound
249
- ? { ...state, clearDetected: true }
250
- : state;
251
- return { state: newState, carry: newCarry, clearFound };
252
- }
253
-
254
- /**
255
- * Get the delay before the next retry, or null if no more retries.
256
- * @param {JiggleRetryState} state
257
- * @returns {number | null}
258
- */
259
- export function nextRetryDelay(state) {
260
- if (state.stopped || state.clearDetected) return null;
261
- if (state.retryIndex >= MAX_RETRIES) return null;
262
- return BACKOFF_MS[state.retryIndex];
263
- }
264
-
265
- /**
266
- * Advance to the next retry round.
267
- * @param {JiggleRetryState} state
268
- * @returns {JiggleRetryState}
269
- */
270
- export function advanceRetry(state) {
271
- return { ...state, retryIndex: state.retryIndex + 1 };
272
- }
273
-
274
- /**
275
- * Stop the retry chain (attach settled, component closed, etc.).
276
- * @param {JiggleRetryState} state
277
- * @returns {JiggleRetryState}
278
- */
279
- export function stopRetry(state) {
280
- return { ...state, stopped: true };
281
- }
282
-
283
- /**
284
- * Check if data contains the full-clear escape sequence.
285
- * @param {string} data
286
- * @returns {boolean}
287
- */
288
- export function hasFullClearSequence(data) {
289
- return data.includes(FULL_CLEAR);
290
- }
291
-
292
- /** Extract the tail carry for cross-chunk boundary detection. */
293
- function tailCarry(data) {
294
- // We only need to carry up to CARRY_LEN bytes to catch a split \x1b[2J.
295
- const tail = data.slice(-CARRY_LEN);
296
- // Find the last \x1b in the tail — everything before it can't be part of
297
- // a split escape sequence.
298
- const escIdx = tail.lastIndexOf("\x1b");
299
- return escIdx >= 0 ? tail.slice(escIdx) : "";
300
- }
301
- ```
302
-
303
- - [ ] **Step 4: Run tests to verify they pass**
304
-
305
- ```bash
306
- cd /home/elling/git-repo/github/pi-agent-board/.claude/worktrees/issue-2-attach-double-cursor-jiggle-retry
307
- node --test test/pty-attach-jiggle-retry.test.mjs
308
- ```
309
-
310
- Expected: All 18 tests PASS
311
-
312
- - [ ] **Step 5: Commit**
313
-
314
- ```bash
315
- cd /home/elling/git-repo/github/pi-agent-board/.claude/worktrees/issue-2-attach-double-cursor-jiggle-retry
316
- git add src/core/pty-attach-jiggle-retry.mjs test/pty-attach-jiggle-retry.test.mjs
317
- git commit -m "feat: add jiggle retry state machine with clear-sequence detection"
318
- ```
319
-
320
- ---
321
-
322
- ### Task 2: Integrate Retry Chain into PtyAttachComponent
323
-
324
- **Files:**
325
- - Modify: `src/ui/pty-attach.ts`
326
-
327
- **Interfaces:**
328
- - Consumes: `createJiggleRetryState`, `feedOutput`, `nextRetryDelay`, `advanceRetry`, `stopRetry`, `BACKOFF_MS` from `src/core/pty-attach-jiggle-retry.mjs`
329
- - Produces: no new exports — internal behavior change only
330
-
331
- - [ ] **Step 1: Add import and new private fields**
332
-
333
- In `src/ui/pty-attach.ts`, add to the imports at top:
334
- ```typescript
335
- import {
336
- createJiggleRetryState,
337
- feedOutput as feedJiggleRetry,
338
- nextRetryDelay,
339
- advanceRetry,
340
- stopRetry,
341
- } from "../core/pty-attach-jiggle-retry.mjs";
342
- ```
343
-
344
- Add new private fields to the class (after existing fields, near `forcedRedrawAfterLiveOutput`):
345
- ```typescript
346
- // Jiggle retry chain: re-send resize jiggle until we see a full-clear sequence
347
- // in the PTY output, proving the child pi-tui did a fullRender and the replay
348
- // garbage has been flushed. Replaces the one-shot forceChildRedrawAfterLiveOutput.
349
- private jiggleRetryState = createJiggleRetryState();
350
- private jiggleRetryTimer: ReturnType<typeof setTimeout> | null = null;
351
- private clearCarry = "";
352
- ```
353
-
354
- - [ ] **Step 2: Add new private methods**
355
-
356
- Add these methods to the class (near `forceChildRedraw`):
357
-
358
- ```typescript
359
- /** Start the jiggle retry chain after initial jiggle. */
360
- private startJiggleRetry(): void {
361
- this.jiggleRetryState = createJiggleRetryState();
362
- this.clearCarry = "";
363
- this.scheduleNextJiggleRetry();
364
- }
365
-
366
- /** Check socket output for full-clear sequence; cancel retry if found. */
367
- private checkClearSequence(data: string): void {
368
- const result = feedJiggleRetry(this.jiggleRetryState, data, this.clearCarry);
369
- this.jiggleRetryState = result.state;
370
- this.clearCarry = result.carry;
371
- if (result.clearFound) this.cancelJiggleRetry();
372
- }
373
-
374
- /** Schedule the next jiggle retry with backoff. */
375
- private scheduleNextJiggleRetry(): void {
376
- const delay = nextRetryDelay(this.jiggleRetryState);
377
- if (delay === null) return;
378
- this.jiggleRetryTimer = setTimeout(() => {
379
- this.jiggleRetryTimer = null;
380
- if (this.closed || !this.connected) return;
381
- this.forceChildRedraw();
382
- this.jiggleRetryState = advanceRetry(this.jiggleRetryState);
383
- this.scheduleNextJiggleRetry();
384
- }, delay);
385
- this.jiggleRetryTimer.unref?.();
386
- }
387
-
388
- /** Cancel the jiggle retry chain. */
389
- private cancelJiggleRetry(): void {
390
- if (this.jiggleRetryTimer) {
391
- clearTimeout(this.jiggleRetryTimer);
392
- this.jiggleRetryTimer = null;
393
- }
394
- this.jiggleRetryState = stopRetry(this.jiggleRetryState);
395
- }
396
- ```
397
-
398
- - [ ] **Step 3: Wire into connect() — start retry after initial jiggle**
399
-
400
- In `connect()`, after `this.forceChildRedraw()` (around line 309), add:
401
- ```typescript
402
- this.startJiggleRetry();
403
- ```
404
-
405
- - [ ] **Step 4: Wire into onSocketData() — check for clear sequence**
406
-
407
- In `onSocketData()`, inside the `msg.type === "output"` branch, after `this.pushOutput(msg.data, ...)`, replace the `this.forceChildRedrawAfterLiveOutput()` call with:
408
- ```typescript
409
- this.checkClearSequence(msg.data);
410
- ```
411
-
412
- - [ ] **Step 5: finishAttachTransition() — do NOT cancel retry (updated post-review)**
413
-
414
- **Update (final review, commit 806bf3e):** the retry chain intentionally survives the settle transition. Originally this step wired `cancelJiggleRetry()` into `finishAttachTransition()`, but review found that kills the chain at ~410ms in the silent cold-start case — the exact failure mode this feature fixes — leaving only retry 1 reachable. The chain now self-terminates on: clear detected / max retries / close(). Post-settle jiggles only fire when the child consumed all earlier jiggles (screen already stale), trading a brief full-render flicker for self-healing.
415
-
416
- - [ ] **Step 6: Wire into close() — cancel retry**
417
-
418
- In `close()`, add (alongside other cleanup):
419
- ```typescript
420
- this.cancelJiggleRetry();
421
- ```
422
-
423
- - [ ] **Step 7: Remove old one-shot logic**
424
-
425
- Delete the `forcedRedrawAfterLiveOutput` field declaration.
426
-
427
- Delete the `forceChildRedrawAfterLiveOutput()` method entirely.
428
-
429
- Delete the `liveRedrawTimer` field declaration.
430
-
431
- In `close()`, remove `this.liveRedrawTimer` cleanup if present (it was cleaned by `clearRedrawTimer()` which also clears `liveRedrawTimer` — verify and clean up).
432
-
433
- - [ ] **Step 8: Verify TypeScript compiles**
434
-
435
- ```bash
436
- cd /home/elling/git-repo/github/pi-agent-board/.claude/worktrees/issue-2-attach-double-cursor-jiggle-retry
437
- npx tsc --noEmit
438
- ```
439
-
440
- Expected: No errors
441
-
442
- - [ ] **Step 9: Run all existing tests**
443
-
444
- ```bash
445
- cd /home/elling/git-repo/github/pi-agent-board/.claude/worktrees/issue-2-attach-double-cursor-jiggle-retry
446
- node --test test/*.test.mjs
447
- ```
448
-
449
- Expected: All tests PASS (no regressions)
450
-
451
- - [ ] **Step 10: Commit**
452
-
453
- ```bash
454
- cd /home/elling/git-repo/github/pi-agent-board/.claude/worktrees/issue-2-attach-double-cursor-jiggle-retry
455
- git add src/ui/pty-attach.ts
456
- git commit -m "feat: integrate jiggle retry chain into attach component"
457
- ```
458
-
459
- ---
460
-
461
- ### Task 3: Final Verification — Full Test Suite + Lint
462
-
463
- **Files:**
464
- - No new files; verification only
465
-
466
- - [ ] **Step 1: Run full test suite**
467
-
468
- ```bash
469
- cd /home/elling/git-repo/github/pi-agent-board/.claude/worktrees/issue-2-attach-double-cursor-jiggle-retry
470
- node --test test/*.test.mjs
471
- ```
472
-
473
- Expected: All PASS
474
-
475
- - [ ] **Step 2: TypeScript check**
476
-
477
- ```bash
478
- cd /home/elling/git-repo/github/pi-agent-board/.claude/worktrees/issue-2-attach-double-cursor-jiggle-retry
479
- npx tsc --noEmit
480
- ```
481
-
482
- Expected: No errors
483
-
484
- - [ ] **Step 3: Check for lint issues**
485
-
486
- ```bash
487
- cd /home/elling/git-repo/github/pi-agent-board/.claude/worktrees/issue-2-attach-double-cursor-jiggle-retry
488
- npx biome check src/core/pty-attach-jiggle-retry.mjs src/ui/pty-attach.ts test/pty-attach-jiggle-retry.test.mjs 2>&1 || true
489
- ```
490
-
491
- Expected: No new issues (fix if any)
492
-
493
- - [ ] **Step 4: Final commit (if any fixes needed)**
494
-
495
- ```bash
496
- cd /home/elling/git-repo/github/pi-agent-board/.claude/worktrees/issue-2-attach-double-cursor-jiggle-retry
497
- git add -p # stage only relevant fixes
498
- git commit -m "fix: address lint/type issues from final verification"
499
- ```