@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,510 +0,0 @@
|
|
|
1
|
-
# Issue #174 Selection Mirror + Paste Source 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:** Close the fullscreen select→middle-click-paste loop: mirror pi's (final) selection text into X11 PRIMARY when a selection gesture ends inside the attach view (B2), and add an opt-in `AGENT_BOARD_ATTACH_PASTE_SOURCE=primary|clipboard` paste-source override (C).
|
|
6
|
-
|
|
7
|
-
**Architecture:** Two pure decision helpers in `src/core/pty-scroll.mjs` (`resolvePasteSelectionSource`, `shouldMirrorSelectionToPrimary`); `PtyAttachComponent.handleMouse` observes left release/click (returns `undefined`, consumes nothing) and schedules a `process.nextTick` mirror that reads `TuiAltScreen.getActiveSelectionText()` and reuses `writePrimarySelection`; `pastePrimarySelection` parameterizes its `-selection` argument from the env knob. pi's own selection behavior is untouched.
|
|
8
|
-
|
|
9
|
-
**Tech Stack:** TypeScript (tabs), node:test + assert/strict, node --experimental-transform-types scenario with a PATH-stubbed bidirectional `xclip`.
|
|
10
|
-
|
|
11
|
-
**Spec:** `docs/superpowers/specs/2026-10-10-issue-174-selection-mirror-paste-source-design.md` (acceptance IDs A1–A5, U1). Cross-review waived by user decision (recorded in the issue research dir).
|
|
12
|
-
|
|
13
|
-
## Global Constraints
|
|
14
|
-
|
|
15
|
-
- Work ONLY inside the worktree `<WT>` (branch `issue-174-selection-mirror-paste-source`); never touch main.
|
|
16
|
-
- Tabs for indentation; English comments and commit messages; conventional commit subjects.
|
|
17
|
-
- Pure-function split is a hard spec constraint: both helpers take resolved values (no `process.env` reads inside); must not be re-inlined into the component.
|
|
18
|
-
- `handleMouse` must remain the ONLY mirror scheduling point; `writePrimarySelection`/`pastePrimarySelection` remain the only xclip touch points.
|
|
19
|
-
- The mirror branch must return `undefined` (observe only) — pi's fullscreen selection keeps every left-button event. No changes to #169's ownership model, #171's paste decision behavior, or regular-mode behavior.
|
|
20
|
-
- The kill switch `AGENT_BOARD_ATTACH_NATIVE_PASTE=0` must disable BOTH the mirror and the paste.
|
|
21
|
-
- Tests use `node:test` with self-contained fixtures under temp dirs; never the host repository.
|
|
22
|
-
- `test/` and `test-support/` are EXCLUDED from tsc — scenario files are runtime-transformed only; `src/` must stay `npm run typecheck`-clean.
|
|
23
|
-
|
|
24
|
-
---
|
|
25
|
-
|
|
26
|
-
### Task 1: Pure decision helpers (A1)
|
|
27
|
-
|
|
28
|
-
**Files:**
|
|
29
|
-
- Modify: `src/core/pty-scroll.mjs` (add exports next to `resolveAttachMouseAction` / `shouldOwnOuterMouseMode`)
|
|
30
|
-
- Test: `test/pty-scroll.test.mjs` (extend the existing import block and append tests)
|
|
31
|
-
|
|
32
|
-
**Interfaces:**
|
|
33
|
-
- Consumes: none.
|
|
34
|
-
- Produces: `resolvePasteSelectionSource(value) → "primary" | "clipboard"` (value = the raw `AGENT_BOARD_ATTACH_PASTE_SOURCE` env string or undefined; unknown → `"primary"`) and `shouldMirrorSelectionToPrimary(event, { nativePasteEnabled }) → "mirror-primary" | null`. Tasks 2–3 import both from `../core/pty-scroll.mjs`.
|
|
35
|
-
|
|
36
|
-
- [ ] **Step 1: Write the failing tests**
|
|
37
|
-
|
|
38
|
-
In `test/pty-scroll.test.mjs`, extend the existing import from `../src/core/pty-scroll.mjs` with `resolvePasteSelectionSource` and `shouldMirrorSelectionToPrimary`, then append:
|
|
39
|
-
|
|
40
|
-
```js
|
|
41
|
-
test("resolvePasteSelectionSource: clipboard opt-in, everything else primary", () => {
|
|
42
|
-
assert.equal(resolvePasteSelectionSource(undefined), "primary");
|
|
43
|
-
assert.equal(resolvePasteSelectionSource(""), "primary");
|
|
44
|
-
assert.equal(resolvePasteSelectionSource("primary"), "primary");
|
|
45
|
-
assert.equal(resolvePasteSelectionSource("clipboard"), "clipboard");
|
|
46
|
-
assert.equal(resolvePasteSelectionSource(" CLIPBOARD "), "clipboard");
|
|
47
|
-
assert.equal(resolvePasteSelectionSource("garbage"), "primary");
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
test("shouldMirrorSelectionToPrimary: only selection endings of the left button with paste enabled", () => {
|
|
51
|
-
assert.equal(shouldMirrorSelectionToPrimary(ev("release", "left"), { nativePasteEnabled: true }), "mirror-primary");
|
|
52
|
-
assert.equal(shouldMirrorSelectionToPrimary(ev("click", "left"), { nativePasteEnabled: true }), "mirror-primary");
|
|
53
|
-
assert.equal(shouldMirrorSelectionToPrimary(ev("release", "left"), { nativePasteEnabled: false }), null);
|
|
54
|
-
assert.equal(shouldMirrorSelectionToPrimary(ev("click", "left"), { nativePasteEnabled: false }), null);
|
|
55
|
-
const cases = [
|
|
56
|
-
["press", "left"],
|
|
57
|
-
["move", "left"],
|
|
58
|
-
["drag", "left"],
|
|
59
|
-
["wheel", "left"],
|
|
60
|
-
["release", "middle"],
|
|
61
|
-
["release", "right"],
|
|
62
|
-
["release", "none"],
|
|
63
|
-
["click", "middle"],
|
|
64
|
-
["click", "none"],
|
|
65
|
-
];
|
|
66
|
-
for (const [type, button] of cases) {
|
|
67
|
-
assert.equal(shouldMirrorSelectionToPrimary(ev(type, button), { nativePasteEnabled: true }), null, `${type}/${button}`);
|
|
68
|
-
}
|
|
69
|
-
});
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
(`ev` helper already exists in this file from #171 — reuse it, do not redefine.)
|
|
73
|
-
|
|
74
|
-
- [ ] **Step 2: Run tests to verify they fail**
|
|
75
|
-
|
|
76
|
-
Run: `node --test test/pty-scroll.test.mjs`
|
|
77
|
-
Expected: FAIL — the two helpers are not exported.
|
|
78
|
-
|
|
79
|
-
- [ ] **Step 3: Implement**
|
|
80
|
-
|
|
81
|
-
In `src/core/pty-scroll.mjs`, next to `resolveAttachMouseAction`:
|
|
82
|
-
|
|
83
|
-
```js
|
|
84
|
-
/**
|
|
85
|
-
* Issue #174 (C): which X11 selection buffer middle-click paste reads.
|
|
86
|
-
* "primary" = classic X11 middle-click semantics (default); "clipboard" =
|
|
87
|
-
* escape hatch for environments where PRIMARY never updates (e.g. WSLg).
|
|
88
|
-
* Unknown or unset values fall back to "primary". Pure: the raw env value is
|
|
89
|
-
* read at the call site.
|
|
90
|
-
*/
|
|
91
|
-
export function resolvePasteSelectionSource(value) {
|
|
92
|
-
return typeof value === "string" && value.trim().toLowerCase() === "clipboard" ? "clipboard" : "primary";
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Issue #174 (B2): whether a normalized mouse event ends a selection gesture
|
|
97
|
-
* we should mirror into the X11 PRIMARY selection. Only release/click of the
|
|
98
|
-
* left button with native paste enabled (pi's fullscreen selection owns the
|
|
99
|
-
* gesture; we observe without consuming). Pure by contract.
|
|
100
|
-
*/
|
|
101
|
-
export function shouldMirrorSelectionToPrimary(event, { nativePasteEnabled }) {
|
|
102
|
-
if (!nativePasteEnabled) return null;
|
|
103
|
-
if ((event.type === "release" || event.type === "click") && event.button === "left") return "mirror-primary";
|
|
104
|
-
return null;
|
|
105
|
-
}
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
- [ ] **Step 4: Run tests to verify they pass**
|
|
109
|
-
|
|
110
|
-
Run: `node --test test/pty-scroll.test.mjs`
|
|
111
|
-
Expected: PASS (new + all pre-existing cases).
|
|
112
|
-
|
|
113
|
-
- [ ] **Step 5: Commit**
|
|
114
|
-
|
|
115
|
-
```bash
|
|
116
|
-
git add src/core/pty-scroll.mjs test/pty-scroll.test.mjs
|
|
117
|
-
git commit -m "feat(attach): pure decision helpers for selection mirror and paste source"
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
---
|
|
121
|
-
|
|
122
|
-
### Task 2: B2 — mirror wiring + runner-free scenario (A2)
|
|
123
|
-
|
|
124
|
-
**Files:**
|
|
125
|
-
- Modify: `src/ui/pty-attach.ts` (import line ~13; `handleMouse` :376; new private method near `writePrimarySelection` ~:1150)
|
|
126
|
-
- Create: `test-support/pty-attach-selection-mirror-scenario.ts`
|
|
127
|
-
- Test: `test/pty-attach-selection-mirror.test.mjs`
|
|
128
|
-
|
|
129
|
-
**Interfaces:**
|
|
130
|
-
- Consumes: `shouldMirrorSelectionToPrimary(event, { nativePasteEnabled })` from Task 1; `this.writePrimarySelection(text)` (existing private); `this.tui` cast `as { getActiveSelectionText?: () => string | undefined }`.
|
|
131
|
-
- Produces: private `mirrorFullscreenSelectionToPrimary(): void`; the observe-branch in `handleMouse`.
|
|
132
|
-
|
|
133
|
-
- [ ] **Step 1: Write the failing test + scenario**
|
|
134
|
-
|
|
135
|
-
Create `test/pty-attach-selection-mirror.test.mjs`:
|
|
136
|
-
|
|
137
|
-
```js
|
|
138
|
-
import assert from "node:assert/strict";
|
|
139
|
-
import test from "node:test";
|
|
140
|
-
import { execFileSync } from "node:child_process";
|
|
141
|
-
import { fileURLToPath } from "node:url";
|
|
142
|
-
import { join } from "node:path";
|
|
143
|
-
|
|
144
|
-
const ROOT_DIR = fileURLToPath(new URL("../", import.meta.url));
|
|
145
|
-
const SCENARIO = join(ROOT_DIR, "test-support", "pty-attach-selection-mirror-scenario.ts");
|
|
146
|
-
|
|
147
|
-
// Component-level coverage (issue #174 B2): the REAL PtyAttachComponent with a
|
|
148
|
-
// fake fullscreen TUI (getActiveSelectionText fixture) and a PATH-stubbed
|
|
149
|
-
// bidirectional xclip that records reads/writes to a log. Asserts the mirror
|
|
150
|
-
// fires on left release/click (deferred one tick), never consumes the event,
|
|
151
|
-
// skips empty selections, honors the kill switch, and ignores non-endings.
|
|
152
|
-
test("attach selection mirror: left release/click mirrors pi's selection into PRIMARY", () => {
|
|
153
|
-
const out = execFileSync(process.execPath, ["--experimental-transform-types", SCENARIO], {
|
|
154
|
-
encoding: "utf8",
|
|
155
|
-
timeout: 60_000,
|
|
156
|
-
});
|
|
157
|
-
const parsed = JSON.parse(out.trim().split("\n").filter(Boolean).pop());
|
|
158
|
-
assert.equal(parsed.error, null, `scenario error: ${parsed.error}`);
|
|
159
|
-
assert.equal(parsed.releaseResultUndefined, true, "release(left) must return undefined (observe only)");
|
|
160
|
-
assert.equal(parsed.mirrorWrites, 1, "exactly one PRIMARY write for the drag-style release");
|
|
161
|
-
assert.equal(parsed.mirrorText, "mirror-fixture-text", "the mirrored text is getActiveSelectionText()'s value");
|
|
162
|
-
assert.equal(parsed.clickResultUndefined, true, "click(left) must return undefined");
|
|
163
|
-
assert.equal(parsed.clickMirrorWrites, 1, "the click case also mirrors (selection non-empty)");
|
|
164
|
-
assert.equal(parsed.emptyWrites, 0, "empty selection must not write");
|
|
165
|
-
assert.equal(parsed.killSwitchWrites, 0, "NATIVE_PASTE=0 must disable the mirror");
|
|
166
|
-
assert.equal(parsed.nonEndingWrites, 0, "press/move must not trigger the mirror");
|
|
167
|
-
});
|
|
168
|
-
```
|
|
169
|
-
|
|
170
|
-
Create `test-support/pty-attach-selection-mirror-scenario.ts`:
|
|
171
|
-
|
|
172
|
-
```ts
|
|
173
|
-
// Component-level selection-mirror coverage (issue #174 B2): the REAL
|
|
174
|
-
// PtyAttachComponent with a fake fullscreen TUI whose getActiveSelectionText
|
|
175
|
-
// returns a controllable fixture, run against a nonexistent control socket,
|
|
176
|
-
// with a PATH-stubbed bidirectional xclip that appends every invocation
|
|
177
|
-
// (args + stdin for writes) to a log file and prints a read fixture for -o.
|
|
178
|
-
// Run via `node --experimental-transform-types`.
|
|
179
|
-
import { chmodSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
180
|
-
import { tmpdir } from "node:os";
|
|
181
|
-
import { join } from "node:path";
|
|
182
|
-
import { PtyAttachComponent } from "../src/ui/pty-attach.ts";
|
|
183
|
-
|
|
184
|
-
const sleep = (ms: number) => new Promise<void>((r) => setTimeout(r, ms));
|
|
185
|
-
const READ_FIXTURE = "read-fixture-text";
|
|
186
|
-
|
|
187
|
-
const root = mkdtempSync(join(tmpdir(), "agentview-sel-mirror-"));
|
|
188
|
-
const binDir = join(root, "bin");
|
|
189
|
-
mkdirSync(binDir);
|
|
190
|
-
const logPath = join(root, "xclip.log");
|
|
191
|
-
writeFileSync(logPath, "");
|
|
192
|
-
writeFileSync(
|
|
193
|
-
join(binDir, "xclip"),
|
|
194
|
-
[
|
|
195
|
-
"#!/bin/sh",
|
|
196
|
-
`if [ "$1" = "-o" ]; then`,
|
|
197
|
-
` echo "read $*" >> "${logPath}"`,
|
|
198
|
-
` printf '%s' "${READ_FIXTURE}"`,
|
|
199
|
-
"else",
|
|
200
|
-
' text="$(cat)"',
|
|
201
|
-
` echo "write $* text=$text" >> "${logPath}"`,
|
|
202
|
-
"fi",
|
|
203
|
-
].join("\n"),
|
|
204
|
-
);
|
|
205
|
-
chmodSync(join(binDir, "xclip"), 0o755);
|
|
206
|
-
process.env.PATH = `${binDir}:${process.env.PATH}`;
|
|
207
|
-
|
|
208
|
-
// The selection pi would have finalized by the time the deferred mirror runs.
|
|
209
|
-
let selectionText: string | undefined = "mirror-fixture-text";
|
|
210
|
-
|
|
211
|
-
const writes = () =>
|
|
212
|
-
readFileSync(logPath, "utf8")
|
|
213
|
-
.split("\n")
|
|
214
|
-
.filter((l) => l.startsWith("write "));
|
|
215
|
-
|
|
216
|
-
const leftEvent = (type: "release" | "click" | "press" | "move") => ({
|
|
217
|
-
type,
|
|
218
|
-
button: "left",
|
|
219
|
-
x: 3,
|
|
220
|
-
y: 3,
|
|
221
|
-
screenX: 3,
|
|
222
|
-
screenY: 3,
|
|
223
|
-
width: 80,
|
|
224
|
-
height: 22,
|
|
225
|
-
shift: false,
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
async function withComponent(env: Record<string, string> | undefined, fn: (c: PtyAttachComponent) => Promise<void>): Promise<void> {
|
|
229
|
-
const prevEnv: Record<string, string | undefined> = {};
|
|
230
|
-
for (const [k, v] of Object.entries(env ?? {})) {
|
|
231
|
-
prevEnv[k] = process.env[k];
|
|
232
|
-
process.env[k] = v;
|
|
233
|
-
}
|
|
234
|
-
const tui = {
|
|
235
|
-
mode: "fullscreen",
|
|
236
|
-
getActiveSelectionText: () => selectionText,
|
|
237
|
-
terminal: { rows: 24, cols: 80, columns: 80, write: () => {} },
|
|
238
|
-
requestRender: () => {},
|
|
239
|
-
};
|
|
240
|
-
const theme = { fg: (_c: string, t: string) => t, bold: (t: string) => t };
|
|
241
|
-
let component: PtyAttachComponent | null = null;
|
|
242
|
-
try {
|
|
243
|
-
component = new PtyAttachComponent(
|
|
244
|
-
tui as never,
|
|
245
|
-
theme as never,
|
|
246
|
-
{} as never,
|
|
247
|
-
() => {},
|
|
248
|
-
{ socketPath: join(root, "nonexistent.sock"), title: "sel mirror e2e" } as never,
|
|
249
|
-
);
|
|
250
|
-
await fn(component);
|
|
251
|
-
} finally {
|
|
252
|
-
try { component?.dispose(); } catch {}
|
|
253
|
-
await sleep(50);
|
|
254
|
-
for (const [k, v] of Object.entries(prevEnv)) {
|
|
255
|
-
if (v === undefined) delete process.env[k];
|
|
256
|
-
else process.env[k] = v;
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
const result = {
|
|
262
|
-
error: null as string | null,
|
|
263
|
-
releaseResultUndefined: false,
|
|
264
|
-
mirrorWrites: -1,
|
|
265
|
-
mirrorText: null as string | null,
|
|
266
|
-
clickResultUndefined: false,
|
|
267
|
-
clickMirrorWrites: -1,
|
|
268
|
-
emptyWrites: -1,
|
|
269
|
-
killSwitchWrites: -1,
|
|
270
|
-
nonEndingWrites: -1,
|
|
271
|
-
};
|
|
272
|
-
|
|
273
|
-
try {
|
|
274
|
-
// 1. Drag-style release mirrors the selection exactly once, unconsumed.
|
|
275
|
-
await withComponent(undefined, async (c) => {
|
|
276
|
-
const before = writes().length;
|
|
277
|
-
const r = (c as any).handleMouse(leftEvent("release"));
|
|
278
|
-
result.releaseResultUndefined = r === undefined;
|
|
279
|
-
await sleep(80); // nextTick + xclip spawn
|
|
280
|
-
const ws = writes();
|
|
281
|
-
result.mirrorWrites = ws.length - before;
|
|
282
|
-
result.mirrorText = ws.length > before ? ws[ws.length - 1].replace(/^write .*?text=/, "") : null;
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
// 2. Click ending (non-empty selection) mirrors too, unconsumed.
|
|
286
|
-
await withComponent(undefined, async (c) => {
|
|
287
|
-
const before = writes().length;
|
|
288
|
-
const r = (c as any).handleMouse(leftEvent("click"));
|
|
289
|
-
result.clickResultUndefined = r === undefined;
|
|
290
|
-
await sleep(80);
|
|
291
|
-
result.clickMirrorWrites = writes().length - before;
|
|
292
|
-
});
|
|
293
|
-
|
|
294
|
-
// 3. Empty selection: no write.
|
|
295
|
-
await withComponent(undefined, async (c) => {
|
|
296
|
-
const prev = selectionText;
|
|
297
|
-
selectionText = undefined;
|
|
298
|
-
const before = writes().length;
|
|
299
|
-
(c as any).handleMouse(leftEvent("release"));
|
|
300
|
-
await sleep(80);
|
|
301
|
-
result.emptyWrites = writes().length - before;
|
|
302
|
-
selectionText = prev;
|
|
303
|
-
});
|
|
304
|
-
|
|
305
|
-
// 4. Kill switch: no write.
|
|
306
|
-
await withComponent({ AGENT_BOARD_ATTACH_NATIVE_PASTE: "0" }, async (c) => {
|
|
307
|
-
const before = writes().length;
|
|
308
|
-
(c as any).handleMouse(leftEvent("release"));
|
|
309
|
-
await sleep(80);
|
|
310
|
-
result.killSwitchWrites = writes().length - before;
|
|
311
|
-
});
|
|
312
|
-
|
|
313
|
-
// 5. Non-endings (press/move): no write.
|
|
314
|
-
await withComponent(undefined, async (c) => {
|
|
315
|
-
const before = writes().length;
|
|
316
|
-
(c as any).handleMouse(leftEvent("press"));
|
|
317
|
-
(c as any).handleMouse(leftEvent("move"));
|
|
318
|
-
await sleep(80);
|
|
319
|
-
result.nonEndingWrites = writes().length - before;
|
|
320
|
-
});
|
|
321
|
-
} catch (err) {
|
|
322
|
-
result.error = err instanceof Error ? err.message : String(err);
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
try {
|
|
326
|
-
rmSync(root, { recursive: true, force: true, maxRetries: 3, retryDelay: 30 });
|
|
327
|
-
} catch {}
|
|
328
|
-
console.log(JSON.stringify(result));
|
|
329
|
-
process.exit(0);
|
|
330
|
-
```
|
|
331
|
-
|
|
332
|
-
- [ ] **Step 2: Run the test to verify it fails**
|
|
333
|
-
|
|
334
|
-
Run: `node --test test/pty-attach-selection-mirror.test.mjs`
|
|
335
|
-
Expected: FAIL — `mirrorWrites` 0 (≠1), `releaseResultUndefined` still true (returns undefined today for everything), so the failing assertion is `mirrorWrites === 1` / `mirrorText`. The kill-switch/empty/non-ending cases pass trivially; the failure must be on the mirror fields.
|
|
336
|
-
|
|
337
|
-
- [ ] **Step 3: Implement the mirror**
|
|
338
|
-
|
|
339
|
-
In `src/ui/pty-attach.ts`:
|
|
340
|
-
|
|
341
|
-
Import line (~13) — extend the core import (alphabetical):
|
|
342
|
-
|
|
343
|
-
```ts
|
|
344
|
-
import { clampInt, parseMouseInputChunk, resolveAttachMouseAction, resolveWheelLines, scrollViewportTop, selectionDragScrollLines, shouldMirrorSelectionToPrimary, shouldOwnOuterMouseMode } from "../core/pty-scroll.mjs";
|
|
345
|
-
```
|
|
346
|
-
|
|
347
|
-
`handleMouse` (:376) — hoist the flag, add the observe-branch before the paste logic:
|
|
348
|
-
|
|
349
|
-
```ts
|
|
350
|
-
handleMouse(event: TuiMouseEvent): TuiMouseEventResult | undefined {
|
|
351
|
-
const nativePasteEnabled = process.env.AGENT_BOARD_ATTACH_NATIVE_PASTE !== "0";
|
|
352
|
-
if (shouldMirrorSelectionToPrimary(event, { nativePasteEnabled })) {
|
|
353
|
-
// Observe only — pi's fullscreen selection keeps the event. Mirroring must
|
|
354
|
-
// run after TuiAltScreen finalizes the selection for this event (overlays
|
|
355
|
-
// are dispatched before the selection logic), hence the deferred tick.
|
|
356
|
-
process.nextTick(() => this.mirrorFullscreenSelectionToPrimary());
|
|
357
|
-
return undefined;
|
|
358
|
-
}
|
|
359
|
-
const action = resolveAttachMouseAction(event, { nativePasteEnabled });
|
|
360
|
-
if (action !== "paste-primary") return undefined;
|
|
361
|
-
this.clearPendingClick();
|
|
362
|
-
this.clearSelection();
|
|
363
|
-
this.pastePrimarySelection();
|
|
364
|
-
return { handled: true, render: false };
|
|
365
|
-
}
|
|
366
|
-
```
|
|
367
|
-
|
|
368
|
-
Near `writePrimarySelection` (~:1150), add:
|
|
369
|
-
|
|
370
|
-
```ts
|
|
371
|
-
/** Issue #174 (B2): after a selection gesture ends inside this overlay, copy
|
|
372
|
-
* pi's final selection text into the X11 PRIMARY selection so the classic
|
|
373
|
-
* select-then-middle-click-paste loop works in fullscreen. Runs on nextTick
|
|
374
|
-
* because overlays are dispatched before TuiAltScreen finalizes the
|
|
375
|
-
* selection for the same event. No-op in regular mode (no
|
|
376
|
-
* getActiveSelectionText) and under the native-paste kill switch. */
|
|
377
|
-
private mirrorFullscreenSelectionToPrimary(): void {
|
|
378
|
-
if (this.closed) return;
|
|
379
|
-
const alt = this.tui as { getActiveSelectionText?: () => string | undefined };
|
|
380
|
-
const text = alt.getActiveSelectionText?.();
|
|
381
|
-
if (typeof text === "string" && text.length > 0) this.writePrimarySelection(text);
|
|
382
|
-
}
|
|
383
|
-
```
|
|
384
|
-
|
|
385
|
-
Do NOT touch anything else (paste decision, ownership gating, legacy paths).
|
|
386
|
-
|
|
387
|
-
- [ ] **Step 4: Run the test to verify it passes**
|
|
388
|
-
|
|
389
|
-
Run: `node --test test/pty-attach-selection-mirror.test.mjs`
|
|
390
|
-
Expected: PASS.
|
|
391
|
-
|
|
392
|
-
- [ ] **Step 5: Run the unit tests + typecheck**
|
|
393
|
-
|
|
394
|
-
Run: `node --test test/pty-scroll.test.mjs && npm run typecheck`
|
|
395
|
-
Expected: PASS both.
|
|
396
|
-
|
|
397
|
-
- [ ] **Step 6: Commit**
|
|
398
|
-
|
|
399
|
-
```bash
|
|
400
|
-
git add src/ui/pty-attach.ts test-support/pty-attach-selection-mirror-scenario.ts test/pty-attach-selection-mirror.test.mjs
|
|
401
|
-
git commit -m "feat(attach): mirror pi's fullscreen selection into X11 PRIMARY (issue #174)"
|
|
402
|
-
```
|
|
403
|
-
|
|
404
|
-
---
|
|
405
|
-
|
|
406
|
-
### Task 3: C — paste-source override + scenario case (A3)
|
|
407
|
-
|
|
408
|
-
**Files:**
|
|
409
|
-
- Modify: `src/ui/pty-attach.ts` (`pastePrimarySelection` ~:1120, one line)
|
|
410
|
-
- Modify: `test-support/pty-attach-selection-mirror-scenario.ts` (one more case)
|
|
411
|
-
- Modify: `test/pty-attach-selection-mirror.test.mjs` (assertions for the new case)
|
|
412
|
-
|
|
413
|
-
**Interfaces:**
|
|
414
|
-
- Consumes: `resolvePasteSelectionSource(value)` from Task 1.
|
|
415
|
-
- Produces: paste reads `-selection primary|clipboard` per `AGENT_BOARD_ATTACH_PASTE_SOURCE`.
|
|
416
|
-
|
|
417
|
-
- [ ] **Step 1: Extend the scenario + test first**
|
|
418
|
-
|
|
419
|
-
In `test-support/pty-attach-selection-mirror-scenario.ts`, add a reads-counter helper next to `writes` and one more case before the catch (after case 5):
|
|
420
|
-
|
|
421
|
-
```ts
|
|
422
|
-
const reads = () =>
|
|
423
|
-
readFileSync(logPath, "utf8")
|
|
424
|
-
.split("\n")
|
|
425
|
-
.filter((l) => l.startsWith("read "));
|
|
426
|
-
```
|
|
427
|
-
|
|
428
|
-
and inside the `result` object add `clipboardReads: -1, clipboardReadSource: null as string | null`. Then after case 5:
|
|
429
|
-
|
|
430
|
-
```ts
|
|
431
|
-
// 6. Paste source override: middle paste reads CLIPBOARD when configured.
|
|
432
|
-
await withComponent({ AGENT_BOARD_ATTACH_PASTE_SOURCE: "clipboard" }, async (c) => {
|
|
433
|
-
const before = reads().length;
|
|
434
|
-
const r = (c as any).handleMouse({ type: "press", button: "middle", x: 3, y: 3, screenX: 3, screenY: 3, width: 80, height: 22, shift: false });
|
|
435
|
-
result.middleHandled = r?.handled === true;
|
|
436
|
-
await sleep(200); // xclip spawn + close
|
|
437
|
-
const rs = reads();
|
|
438
|
-
result.clipboardReads = rs.length - before;
|
|
439
|
-
result.clipboardReadSource = rs.length > before ? rs[rs.length - 1] : null;
|
|
440
|
-
});
|
|
441
|
-
```
|
|
442
|
-
|
|
443
|
-
(declare `middleHandled: false` in `result` too). In `test/pty-attach-selection-mirror.test.mjs`, append:
|
|
444
|
-
|
|
445
|
-
```js
|
|
446
|
-
assert.equal(parsed.middleHandled, true, "middle press still pastes (handled)");
|
|
447
|
-
assert.equal(parsed.clipboardReads, 1, "PASTE_SOURCE=clipboard: exactly one clipboard read");
|
|
448
|
-
assert.equal(parsed.clipboardReadSource, "read -o -selection clipboard", "the read targets the clipboard selection");
|
|
449
|
-
```
|
|
450
|
-
|
|
451
|
-
- [ ] **Step 2: Run to verify the new assertions fail**
|
|
452
|
-
|
|
453
|
-
Run: `node --test test/pty-attach-selection-mirror.test.mjs`
|
|
454
|
-
Expected: FAIL — `clipboardReadSource` is `read -o -selection primary` (the hardcoded value).
|
|
455
|
-
|
|
456
|
-
- [ ] **Step 3: Implement**
|
|
457
|
-
|
|
458
|
-
In `pastePrimarySelection()` (~:1120), change the spawn line:
|
|
459
|
-
|
|
460
|
-
```ts
|
|
461
|
-
const child = spawn("xclip", ["-o", "-selection", resolvePasteSelectionSource(process.env.AGENT_BOARD_ATTACH_PASTE_SOURCE)], { stdio: ["ignore", "pipe", "ignore"] });
|
|
462
|
-
```
|
|
463
|
-
|
|
464
|
-
(extend the existing `../core/pty-scroll.mjs` import with `resolvePasteSelectionSource` — alphabetical placement).
|
|
465
|
-
|
|
466
|
-
- [ ] **Step 4: Run to verify it passes**
|
|
467
|
-
|
|
468
|
-
Run: `node --test test/pty-attach-selection-mirror.test.mjs && node --test test/pty-attach-mouse.test.mjs`
|
|
469
|
-
Expected: PASS (the #171 scenario pins the default primary source — still green).
|
|
470
|
-
|
|
471
|
-
- [ ] **Step 5: Commit**
|
|
472
|
-
|
|
473
|
-
```bash
|
|
474
|
-
git add src/ui/pty-attach.ts test-support/pty-attach-selection-mirror-scenario.ts test/pty-attach-selection-mirror.test.mjs
|
|
475
|
-
git commit -m "feat(attach): AGENT_BOARD_ATTACH_PASTE_SOURCE paste-source override (issue #174)"
|
|
476
|
-
```
|
|
477
|
-
|
|
478
|
-
---
|
|
479
|
-
|
|
480
|
-
### Task 4: Full verify + coverage gate (A4, A5)
|
|
481
|
-
|
|
482
|
-
**Files:**
|
|
483
|
-
- Modify: whatever Tasks 1–3 fallout requires (expected: none).
|
|
484
|
-
|
|
485
|
-
- [ ] **Step 1: Run the full verify pipeline**
|
|
486
|
-
|
|
487
|
-
Run: `npm run verify`
|
|
488
|
-
Expected: PASS end-to-end (typecheck + perf gate + tests + coverage lines 85 / funcs 80 / branches 70 + pack:dry).
|
|
489
|
-
|
|
490
|
-
- [ ] **Step 2: Fix any fallout, then re-run** (known pre-existing flake: `pty-runner.integration.test.mjs`, issue #95 — re-run once; still red → STOP and report BLOCKED)
|
|
491
|
-
|
|
492
|
-
- [ ] **Step 3: Commit (only if fixes were needed)**
|
|
493
|
-
|
|
494
|
-
```bash
|
|
495
|
-
git add <changed files>
|
|
496
|
-
git commit -m "test: verify fallout fixes for issue #174"
|
|
497
|
-
```
|
|
498
|
-
|
|
499
|
-
---
|
|
500
|
-
|
|
501
|
-
## Acceptance traceability
|
|
502
|
-
|
|
503
|
-
| Spec ID | Covered by |
|
|
504
|
-
| --- | --- |
|
|
505
|
-
| A1 (unit decision tables) | Task 1 |
|
|
506
|
-
| A2 (B2 mirror wiring, integration) | Task 2 |
|
|
507
|
-
| A3 (C paste source, integration) | Task 3 |
|
|
508
|
-
| A4 (static/build) | Task 4 (+ typecheck in Tasks 2–3) |
|
|
509
|
-
| A5 (existing suite regression) | Task 4 |
|
|
510
|
-
| U1 (user manual: 5-step real-terminal checklist) | Post-implementation checklist for the user — executed after merge or from the worktree install |
|