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