@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,450 +0,0 @@
1
- # Monotonic Foreground State Rebuild Implementation Plan
2
-
3
- > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
4
-
5
- **Goal:** Stop a stale `needs_input` projection from resurrecting the "Needs answer" group after a foreground question is answered.
6
-
7
- **Architecture:** A new module-level, pure projection cache stores the freshest status fields with a strictly increasing per-view stamp. `writeForegroundState` stamps and remembers; `syncRowEvent` adopts the snapshot when it is strictly newer than the disk rebuild (`statusFromRow` now carries `updatedAt`).
8
-
9
- **Tech Stack:** Node 22, `node:test` + `assert/strict`, plain ESM `.mjs`, no build step.
10
-
11
- **Spec:** `docs/superpowers/specs/2026-10-22-issue-225-monotonic-foreground-state-design.md`
12
-
13
- ## Global Constraints
14
-
15
- - Tabs for indentation; English comments and commit messages.
16
- - Conventional commit subjects; the PR number is appended at merge time.
17
- - Run `npm run verify` before claiming completion (`npm test` alone skips the perf gate and coverage thresholds).
18
- - Do not change the reducer, `foreground-preview-cache.mjs`, the coordinator, or manual-verdict writes.
19
- - Cache keys are view ids; a view id is never reused (ids are generated), so leftover entries are only a memory concern.
20
-
21
- ## Review Focus
22
-
23
- - **Same-millisecond bursts**: start/end of a question inside one millisecond must still order correctly (`nextStamp`, not `Date.now`).
24
- - **Manual verdicts**: a `completed` / `holding` row (`autoState === null`) must never be overwritten by a cached projection.
25
- - **Clone isolation**: mutating `status.pendingQuestions` after `remember`/`adopt` must not change the cache.
26
- - **Other views**: stamps and entries are per view id; one view's events must not order another's.
27
- - **Non-question fields untouched**: `review` / `diagnostics` / `autoState` / `lastVisitedAt` are not copied by adoption.
28
-
29
- ---
30
-
31
- ### Task 1: `foreground-projection-cache` module (A1–A3, A6-unit)
32
-
33
- **Files:**
34
- - Create: `src/core/foreground-projection-cache.mjs`
35
- - Test: `test/foreground-projection-cache.test.mjs` (new file)
36
-
37
- **Interfaces:**
38
- - Produces: `createForegroundProjectionCache()` → `{ nextStamp(viewId), remember(viewId, status, stamp), adopt(viewId, status), forget(viewId), clear(), size() }` and the module-level singleton `foregroundProjectionCache`.
39
-
40
- - [ ] **Step 1: Write the failing tests**
41
-
42
- Create `test/foreground-projection-cache.test.mjs`:
43
-
44
- ```js
45
- import assert from "node:assert/strict";
46
- import { test } from "node:test";
47
- import { createForegroundProjectionCache } from "../src/core/foreground-projection-cache.mjs";
48
-
49
- function status(overrides = {}) {
50
- return {
51
- semanticState: "working",
52
- processState: "alive",
53
- summary: "Working",
54
- currentTool: null,
55
- question: null,
56
- pendingQuestions: [],
57
- error: null,
58
- lastActivityAt: 100,
59
- latestAssistantPreview: "",
60
- lastAgentActivityAt: null,
61
- updatedAt: 100,
62
- ...overrides,
63
- };
64
- }
65
-
66
- test("adopt copies the snapshot only when the cached stamp is strictly newer", () => {
67
- const cache = createForegroundProjectionCache();
68
- cache.remember("v1", status({ semanticState: "working", summary: "Done" }), 200);
69
- const stale = status({ semanticState: "needs_input", question: "Q?", pendingQuestions: [{ toolCallId: "q1", question: "Q?" }] });
70
- assert.equal(cache.adopt("v1", stale), true);
71
- assert.equal(stale.semanticState, "working");
72
- assert.equal(stale.summary, "Done");
73
- assert.equal(stale.question, null);
74
- assert.deepEqual(stale.pendingQuestions, []);
75
- assert.equal(stale.updatedAt, 200);
76
-
77
- const equal = status({ semanticState: "needs_input" });
78
- equal.updatedAt = 200;
79
- assert.equal(cache.adopt("v1", equal), false);
80
- assert.equal(equal.semanticState, "needs_input");
81
-
82
- const newer = status({ semanticState: "completed" });
83
- newer.updatedAt = 300;
84
- assert.equal(cache.adopt("v1", newer), false);
85
- assert.equal(newer.semanticState, "completed");
86
- });
87
-
88
- test("non-whitelisted fields are never copied", () => {
89
- const cache = createForegroundProjectionCache();
90
- cache.remember("v1", status({ summary: "cached" }), 200);
91
- const live = status({ summary: "disk", updatedAt: 100 });
92
- live.review = { ready: true };
93
- live.diagnostics = { code: "keep" };
94
- live.autoState = { state: "keep" };
95
- cache.adopt("v1", live);
96
- assert.equal(live.summary, "cached");
97
- assert.deepEqual(live.review, { ready: true });
98
- assert.deepEqual(live.diagnostics, { code: "keep" });
99
- assert.deepEqual(live.autoState, { state: "keep" });
100
- });
101
-
102
- test("remember and adopt clone nested values in both directions", () => {
103
- const cache = createForegroundProjectionCache();
104
- const source = status({ currentTool: { name: "bash", path: null }, pendingQuestions: [{ toolCallId: "q1", question: "Q?" }] });
105
- cache.remember("v1", source, 200);
106
- source.pendingQuestions[0].question = "mutated";
107
- source.currentTool.name = "mutated";
108
-
109
- const target = status({ updatedAt: 100 });
110
- assert.equal(cache.adopt("v1", target), true);
111
- assert.equal(target.pendingQuestions[0].question, "Q?");
112
- assert.equal(target.currentTool.name, "bash");
113
- target.pendingQuestions[0].question = "mutated again";
114
- const again = status({ updatedAt: 100 });
115
- cache.adopt("v1", again);
116
- assert.equal(again.pendingQuestions[0].question, "Q?");
117
- });
118
-
119
- test("nextStamp is strictly increasing per view even when the clock is frozen", () => {
120
- const cache = createForegroundProjectionCache();
121
- const realNow = Date.now;
122
- Date.now = () => 1_700_000_000_000;
123
- try {
124
- const a1 = cache.nextStamp("v1");
125
- const a2 = cache.nextStamp("v1");
126
- const b1 = cache.nextStamp("v2");
127
- assert.ok(a2 > a1);
128
- assert.equal(a1, 1_700_000_000_000);
129
- assert.equal(b1, 1_700_000_000_000);
130
- assert.ok(a2 === b1 || a2 > b1);
131
- } finally {
132
- Date.now = realNow;
133
- }
134
- });
135
-
136
- test("forget drops the entry", () => {
137
- const cache = createForegroundProjectionCache();
138
- cache.remember("v1", status(), 200);
139
- assert.equal(cache.size(), 1);
140
- assert.equal(cache.forget("v1"), true);
141
- assert.equal(cache.size(), 0);
142
- const stale = status({ updatedAt: 100 });
143
- assert.equal(cache.adopt("v1", stale), false);
144
- });
145
- ```
146
-
147
- - [ ] **Step 2: Run the tests to verify they fail**
148
-
149
- Run: `node --test test/foreground-projection-cache.test.mjs`
150
- Expected: FAIL — `Cannot find module '../src/core/foreground-projection-cache.mjs'`.
151
-
152
- - [ ] **Step 3: Implement the module**
153
-
154
- Create `src/core/foreground-projection-cache.mjs`:
155
-
156
- ```js
157
- /**
158
- * Process-local read-your-writes cache for foreground status projections
159
- * (issue #225).
160
- *
161
- * `syncRowEvent` rebuilds its status from `state.json` on every event, while
162
- * `writeForegroundState` is fire-and-forget: two events can have two
163
- * `sync_foreground` commands in flight, and an older projection can materialize
164
- * last. Because the rebuilt status carries no freshness signal, the next event
165
- * reduces onto the stale state and writes it back — a cleared question comes
166
- * back as `needs_input` until a terminal event lands.
167
- *
168
- * This cache stores the freshest whitelisted status fields with a strictly
169
- * increasing per-view stamp (`nextStamp`), so a disk rebuild that is older than
170
- * the last write is corrected before the new event is reduced.
171
- *
172
- * Module-level by necessity: `serviceFor()` creates a new service instance per
173
- * call (src/index.ts), so a per-instance cache would be discarded between
174
- * events.
175
- */
176
-
177
- /** Fields a cached snapshot may restore on a stale rebuild. */
178
- const CACHED_FIELDS = [
179
- "semanticState",
180
- "processState",
181
- "summary",
182
- "currentTool",
183
- "question",
184
- "pendingQuestions",
185
- "error",
186
- "lastActivityAt",
187
- "latestAssistantPreview",
188
- "lastAgentActivityAt",
189
- ];
190
-
191
- /** @param {any} value */
192
- function cloneValue(value) {
193
- if (value === null || typeof value !== "object") return value;
194
- return JSON.parse(JSON.stringify(value));
195
- }
196
-
197
- /** @param {any} status */
198
- function snapshot(status) {
199
- /** @type {Record<string, unknown>} */
200
- const fields = {};
201
- for (const key of CACHED_FIELDS) fields[key] = cloneValue(status?.[key]);
202
- return fields;
203
- }
204
-
205
- export function createForegroundProjectionCache() {
206
- /** @type {Map<string, { stamp: number, fields: Record<string, unknown> }>} */
207
- const known = new Map();
208
- /** @type {Map<string, number>} */
209
- const stamps = new Map();
210
-
211
- function nextStamp(viewId) {
212
- const stamp = Math.max(Date.now(), (stamps.get(viewId) ?? 0) + 1);
213
- stamps.set(viewId, stamp);
214
- return stamp;
215
- }
216
-
217
- function remember(viewId, status, stamp) {
218
- if (!viewId || !status || typeof stamp !== "number" || !Number.isFinite(stamp)) return;
219
- known.set(viewId, { stamp, fields: snapshot(status) });
220
- }
221
-
222
- function adopt(viewId, status) {
223
- if (!viewId || !status) return false;
224
- const entry = known.get(viewId);
225
- if (!entry) return false;
226
- const at = typeof status.updatedAt === "number" ? status.updatedAt : 0;
227
- if (entry.stamp <= at) return false;
228
- for (const key of CACHED_FIELDS) status[key] = cloneValue(entry.fields[key]);
229
- status.updatedAt = entry.stamp;
230
- return true;
231
- }
232
-
233
- function forget(viewId) {
234
- stamps.delete(viewId);
235
- return known.delete(viewId);
236
- }
237
-
238
- function clear() {
239
- known.clear();
240
- stamps.clear();
241
- }
242
-
243
- function size() {
244
- return known.size;
245
- }
246
-
247
- return { nextStamp, remember, adopt, forget, clear, size };
248
- }
249
-
250
- /** Shared cache used by the runtime service (module-level: see module doc). */
251
- export const foregroundProjectionCache = createForegroundProjectionCache();
252
- ```
253
-
254
- - [ ] **Step 4: Run the tests to verify they pass**
255
-
256
- Run: `node --test test/foreground-projection-cache.test.mjs`
257
- Expected: PASS (5 tests).
258
-
259
- - [ ] **Step 5: Commit**
260
-
261
- ```bash
262
- git add src/core/foreground-projection-cache.mjs test/foreground-projection-cache.test.mjs
263
- git commit -m "feat: foreground projection cache with monotonic per-view stamps (#225)"
264
- ```
265
-
266
- ---
267
-
268
- ### Task 2: Wire the cache into the service (A4–A7, A8)
269
-
270
- **Files:**
271
- - Modify: `src/runtime/service.mjs` (`statusFromRow` :814, `writeForegroundState` :854, `syncRowEvent` :1540, eviction `:775` and `:2131`)
272
- - Modify: `src/core/types.mjs` (`RunStatus` typedef)
273
- - Test: `test/service.test.mjs` (append after the existing foreground-sync tests)
274
-
275
- **Interfaces:**
276
- - Consumes: `foregroundProjectionCache` from Task 1 (`nextStamp` / `remember` / `adopt` / `forget`).
277
- - Produces: no public API change; `RunStatus` gains optional `updatedAt`.
278
-
279
- - [ ] **Step 1: Write the failing integration tests**
280
-
281
- Append to `test/service.test.mjs`:
282
-
283
- ```js
284
- test("a stale needs_input snapshot cannot resurrect a cleared question (issue #225)", async () => {
285
- const root = freshRoot();
286
- const prevCoordinator = setEnv("AGENT_BOARD_COORDINATOR", "off");
287
- const realNow = Date.now;
288
- Date.now = () => 1_700_000_000_000;
289
- try {
290
- createView(root, { id: "v1", name: "a", cwd: "/r" });
291
- const svc = service(root);
292
- assert.equal(await svc.syncHostedEvent("v1", {
293
- type: "tool_execution_start",
294
- toolCallId: "q1",
295
- toolName: "ask_questions",
296
- args: { questions: [{ question: "Choose a mode?" }] },
297
- }), true);
298
- const stale = readState(root, "v1");
299
- assert.equal(stale.semanticState, "needs_input");
300
-
301
- assert.equal(await svc.syncHostedEvent("v1", { type: "tool_execution_end", toolCallId: "q1", toolName: "ask_questions", isError: false }), true);
302
- // Simulate the older projection materializing after the newer one.
303
- writeState(root, stale);
304
-
305
- assert.equal(await svc.syncHostedEvent("v1", { type: "tool_execution_start", toolCallId: "t1", toolName: "bash", args: { command: "ls" } }), true);
306
- const next = readState(root, "v1");
307
- assert.equal(next.semanticState, "working");
308
- assert.equal(next.needsInput, false);
309
- assert.deepEqual(next.pendingQuestions, []);
310
- } finally {
311
- Date.now = realNow;
312
- setEnv("AGENT_BOARD_COORDINATOR", prevCoordinator);
313
- rmSync(root, { recursive: true, force: true });
314
- }
315
- });
316
-
317
- test("a manual verdict survives a foreground tool event (issue #225)", async () => {
318
- const root = freshRoot();
319
- try {
320
- createView(root, { id: "v1", name: "a", cwd: "/r" });
321
- const done = readState(root, "v1");
322
- done.semanticState = "completed";
323
- done.processState = "exited";
324
- done.autoState = null;
325
- done.summary = "Done";
326
- done.updatedAt = Date.now() - 1000;
327
- writeState(root, done);
328
-
329
- const svc = service(root, { sendStateCommand: async () => ({ status: "rejected", reason: "manual_fence" }) });
330
- await svc.syncHostedEvent("v1", { type: "tool_execution_start", toolCallId: "t1", toolName: "bash", args: { command: "ls" } });
331
- const next = readState(root, "v1");
332
- assert.equal(next.semanticState, "completed");
333
- assert.equal(next.autoState, null);
334
- } finally {
335
- rmSync(root, { recursive: true, force: true });
336
- }
337
- });
338
- ```
339
-
340
- - [ ] **Step 2: Run them to verify the first fails**
341
-
342
- Run: `node --test --test-name-pattern="issue #225" test/service.test.mjs`
343
- Expected: the stale-snapshot test FAILS with `actual: 'needs_input'`, `expected: 'working'` (pre-fix the disk rebuild wins); the manual-verdict test passes (it is a regression guard).
344
-
345
- - [ ] **Step 3: Add `updatedAt` to `RunStatus`**
346
-
347
- In `src/core/types.mjs`, inside the `RunStatus` typedef, after `lastEventAt`:
348
-
349
- ```js
350
- * @property {number} [updatedAt] In-memory foreground rebuild freshness (issue #225);
351
- * not persisted on run status files.
352
- ```
353
-
354
- - [ ] **Step 4: Wire `statusFromRow`**
355
-
356
- In `statusFromRow`, after `lastActivityAt: s.lastActivityAt,` add:
357
-
358
- ```js
359
- updatedAt: typeof s.updatedAt === "number" ? s.updatedAt : 0,
360
- ```
361
-
362
- - [ ] **Step 5: Stamp and remember in `writeForegroundState`**
363
-
364
- Replace the first two statements of `writeForegroundState`:
365
-
366
- ```js
367
- async function writeForegroundState(row, status, opts = {}) {
368
- const projected = projectViewState(status, Date.now(), readState(root, row.meta.id) ?? row.state ?? null);
369
- ```
370
-
371
- with:
372
-
373
- ```js
374
- async function writeForegroundState(row, status, opts = {}) {
375
- // Issue #225: a strictly increasing stamp makes the background rebuild able
376
- // to tell a stale disk snapshot from this write, even within one millisecond.
377
- const stamp = foregroundProjectionCache.nextStamp(row.meta.id);
378
- const projected = projectViewState(status, stamp, readState(root, row.meta.id) ?? row.state ?? null);
379
- foregroundProjectionCache.remember(row.meta.id, status, stamp);
380
- ```
381
-
382
- - [ ] **Step 6: Adopt in `syncRowEvent`**
383
-
384
- After `foregroundPreviewCache.backfill(row.meta.id, status);` add:
385
-
386
- ```js
387
- // Issue #225: correct a stale disk rebuild before the new event is reduced.
388
- foregroundProjectionCache.adopt(row.meta.id, status);
389
- ```
390
-
391
- - [ ] **Step 7: Evict alongside the preview cache**
392
-
393
- At `archiveView` (next to `foregroundPreviewCache.forget(viewId);`) and at `archiveByState` (next to `foregroundPreviewCache.forget(row.meta.id);`) add the matching call:
394
-
395
- ```js
396
- foregroundProjectionCache.forget(viewId);
397
- ```
398
-
399
- ```js
400
- foregroundProjectionCache.forget(row.meta.id);
401
- ```
402
-
403
- - [ ] **Step 8: Add the import**
404
-
405
- At the top of `src/runtime/service.mjs`, next to the preview-cache import:
406
-
407
- ```js
408
- import { foregroundProjectionCache } from "../core/foreground-projection-cache.mjs";
409
- ```
410
-
411
- - [ ] **Step 9: Run the tests**
412
-
413
- Run: `node --test --test-name-pattern="issue #225" test/service.test.mjs`
414
- Expected: PASS (both).
415
-
416
- Then run: `npm test`
417
- Expected: PASS — all existing foreground-sync, preview-cache, archive, and reducer tests stay green. (A7)
418
-
419
- - [ ] **Step 10: Run the full gate**
420
-
421
- Run: `npm run verify`
422
- Expected: PASS — typecheck, perf gate, tests, coverage thresholds (85/80/70), pack dry-run. (A8)
423
-
424
- - [ ] **Step 11: Commit**
425
-
426
- ```bash
427
- git add src/runtime/service.mjs src/core/types.mjs test/service.test.mjs
428
- git commit -m "fix: keep foreground rows monotonic across out-of-order state writes (#225)"
429
- ```
430
-
431
- ---
432
-
433
- ### Task 3: Post-merge live verification (manual, not a code task)
434
-
435
- **Files:** none.
436
-
437
- - [ ] **Step 1: Update the board clone the running process loads**
438
-
439
- ```bash
440
- cd ~/.pi/agent/git/github.com/zhuxixi/pi-agent-board
441
- git checkout -- package-lock.json
442
- git pull
443
- git log -1 --oneline
444
- ```
445
-
446
- - [ ] **Step 2: Restart Pi / the board service**, open the dashboard.
447
-
448
- - [ ] **Step 3: Trigger a question** (`ask_user_question` or `question`), answer it, and keep watching.
449
-
450
- Expected: the row returns to **RUNNING** within about a second, not at turn end. (U1)