@huseynsnmz/pi-board 0.15.1 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -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 +18 -19
- package/src/index.ts +9 -1
- package/src/runtime/service.mjs +12 -0
- package/src/ui/dashboard.ts +14 -8
- package/src/ui/double-left.ts +5 -1
- package/src/ui/focus.ts +7 -1
- package/src/ui/waiting.ts +10 -5
- 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/patch-vulns.mjs +0 -59
- 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,449 +0,0 @@
|
|
|
1
|
-
# Vim-style j/k/l Board Keys 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:** Bind `j`/`k` navigation and `l` attach in the board's list/select/peek/evidence modes so vim users' muscle memory works, without touching text-input modes or the `h` = Hold contract (issue #229).
|
|
6
|
-
|
|
7
|
-
**Architecture:** Pure additive key branches in the five per-mode handlers of `src/ui/dashboard.ts`, reusing the exact code paths of the arrow keys (`moveSelection`, `peekStep`, `attachSelected`, `attachPeek`); copy sync in the `?` help overlay, the NORMAL/SELECT hint lines, and the four README view tables. Behavior is verified by a `test-support` probe driving the public `handleInput()` with fake service/done injection.
|
|
8
|
-
|
|
9
|
-
**Tech Stack:** TypeScript (runtime), `node --test` + `--experimental-transform-types` probes (test).
|
|
10
|
-
|
|
11
|
-
**Spec:** `docs/superpowers/specs/2026-10-23-issue-229-vim-jkl-design.md` (acceptance IDs A1–A9, U1–U2 referenced below)
|
|
12
|
-
|
|
13
|
-
## Global Constraints
|
|
14
|
-
|
|
15
|
-
- Tabs for indentation; English comments and commit messages (AGENTS.md).
|
|
16
|
-
- Tests use `node:test` with `assert/strict`; probes run via `node --experimental-transform-types` and live in `test-support/` (excluded from tsconfig).
|
|
17
|
-
- Conventional commit subjects `feat`/`fix`/`...` with `(issue #229)` reference; PR number appended at merge time.
|
|
18
|
-
- `npm run verify` (typecheck + perf gate + tests + coverage thresholds lines 85 / funcs 80 / branches 70 + pack:dry) must pass before the PR.
|
|
19
|
-
- `j`/`k` inherit **wrap-around** semantics via `moveSelection`/`peekStep` — do NOT implement clamp (spec D2).
|
|
20
|
-
- `h` = Hold (PR #151 contract), dispatch/filter/rename/reply stay literal — off-limits (spec D4/D6).
|
|
21
|
-
- Worktree discipline: all edits under `/home/elling/work/git-repo/pi-agent-board/.pi/worktrees/issue-229-vim-jkl-board-list` (= `$WT`), git via `git -C $WT`.
|
|
22
|
-
|
|
23
|
-
## Review Focus
|
|
24
|
-
|
|
25
|
-
1. **Uppercase `J`/`K`/`L` stay unbound** — list mode still shows the printable fallback toast for them. Expected: unchanged current behavior, pinned by a probe assertion (Task 1, Step 1).
|
|
26
|
-
2. **`l` on a busy row** (`hostActive=false && agentBusy=true`) must take the same `confirm-stop-first` branch as `→`, not attach directly. Pinned by the `busyL` probe segment (Task 1, Step 1).
|
|
27
|
-
3. **Help rows got longer** — at 80 cols `renderCenteredBox` clips (existing fallback, `dashboard.ts:1846`); full wording must be visible at 120 cols. Pinned by the render assertions (Task 2, Step 1).
|
|
28
|
-
4. **`j`/`k` in select mode trigger `prewarmScheduler.schedule()`** — inherited `moveSelection` side effect, select is in the `prewarmSelected` allow-list, no behavior change. No test; reviewers confirm no new coupling.
|
|
29
|
-
5. **Bracketed paste containing `j`/`k`/`l` must not move the selection** (multi-byte paste string never equals a single-byte key). Pinned by the paste segment (Task 1, Step 1).
|
|
30
|
-
|
|
31
|
-
---
|
|
32
|
-
|
|
33
|
-
### Task 1: Key bindings in the five mode handlers
|
|
34
|
-
|
|
35
|
-
**Files:**
|
|
36
|
-
- Create: `test-support/vim-keys-wrap.ts`
|
|
37
|
-
- Create: `test/dashboard-vim-keys.test.mjs`
|
|
38
|
-
- Modify: `src/ui/dashboard.ts` (handleListKey :359, handleSelectKey :400, handlePeekKey :427, handleEvidenceKey :484 — line refs are main @ 759d659)
|
|
39
|
-
|
|
40
|
-
**Interfaces:**
|
|
41
|
-
- Consumes: existing `moveSelection(delta)`, `peekStep(delta)`, `attachSelected()`, `attachPeek()`, `planDashboardAttach` (src/ui/dashboard-decisions.mjs:34).
|
|
42
|
-
- Produces: no new symbols; later tasks rely on the probe's `out.copy` field added in Task 2.
|
|
43
|
-
|
|
44
|
-
**Spec acceptance IDs:** A1, A2, A3, A4, A5, A6, A9 (+ Review Focus 1, 2, 5). Spec D3 dead-code cleanup folded in (Step 4).
|
|
45
|
-
|
|
46
|
-
- [ ] **Step 1: Write the failing probe and test**
|
|
47
|
-
|
|
48
|
-
Create `test-support/vim-keys-wrap.ts`:
|
|
49
|
-
|
|
50
|
-
```ts
|
|
51
|
-
// Vim-keys probe: drive j/k/l through the public handleInput() across modes
|
|
52
|
-
// and report observations as JSON (issue #229, spec A1-A9 + review-focus pins).
|
|
53
|
-
// Run via `node --experimental-transform-types`. Not typechecked.
|
|
54
|
-
import { mkdtempSync } from "node:fs";
|
|
55
|
-
import { tmpdir } from "node:os";
|
|
56
|
-
import { join } from "node:path";
|
|
57
|
-
import { createService } from "../src/runtime/service.mjs";
|
|
58
|
-
import { createView } from "../src/core/store.mjs";
|
|
59
|
-
import { DashboardComponent } from "../src/ui/dashboard.ts";
|
|
60
|
-
|
|
61
|
-
const root = mkdtempSync(join(tmpdir(), "agentview-vim-keys-"));
|
|
62
|
-
createView(root, { id: "v1", name: "one", cwd: root });
|
|
63
|
-
createView(root, { id: "v2", name: "two", cwd: root });
|
|
64
|
-
createView(root, { id: "v3", name: "three", cwd: root });
|
|
65
|
-
|
|
66
|
-
const service = createService({
|
|
67
|
-
root,
|
|
68
|
-
runnerScript: "/no/runner.mjs",
|
|
69
|
-
piCommand: "pi",
|
|
70
|
-
piArgsPrefix: [],
|
|
71
|
-
defaultCwd: root,
|
|
72
|
-
launch: () => ({ pid: null, configPath: "/no/config.json" }),
|
|
73
|
-
launchHost: () => ({ pid: null, configPath: "/no/host-config.json" }),
|
|
74
|
-
launchTitle: () => ({ pid: null, configPath: "/no/title-config.json" }),
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
const tui = { terminal: { rows: 24, cols: 80, columns: 80, write: () => {} }, requestRender: () => {} };
|
|
78
|
-
const theme = { fg: (_c, t) => t, bold: (t) => t, bg: (_c, t) => t };
|
|
79
|
-
const doneCalls = [];
|
|
80
|
-
const dash = new DashboardComponent(tui, theme, {}, (r) => doneCalls.push(r), {
|
|
81
|
-
service, root, defaultCwd: root,
|
|
82
|
-
availableModels: [], currentModel: null, currentThinkingLevel: "off",
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
// TS-private is runtime-accessible; read white-box state for assertions.
|
|
86
|
-
const ids = dash.orderedIds;
|
|
87
|
-
const out = { ids, list: [], select: [], peek: [], attach: [], negative: [], scroll: [], text: {}, notices: [], busyL: null };
|
|
88
|
-
|
|
89
|
-
// A1: list j/k == arrow keys (wrap at both ends); j must not pop the INSERT toast.
|
|
90
|
-
dash.handleInput("j"); out.list.push(dash.selectedId);
|
|
91
|
-
out.notices.push(dash.inputNotice === null);
|
|
92
|
-
dash.handleInput("j"); out.list.push(dash.selectedId);
|
|
93
|
-
dash.handleInput("j"); out.list.push(dash.selectedId); // wrap last -> first
|
|
94
|
-
dash.handleInput("k"); out.list.push(dash.selectedId); // wrap first -> last
|
|
95
|
-
// RF1: uppercase J stays on the printable fallback (current behavior pinned).
|
|
96
|
-
dash.handleInput("J"); out.notices.push(dash.inputNotice !== null);
|
|
97
|
-
// RF5: bracketed paste containing j/k/l must not move the selection.
|
|
98
|
-
dash.handleInput("\x1b[200~jkl\x1b[201~"); out.list.push(dash.selectedId);
|
|
99
|
-
|
|
100
|
-
// A2: select mode j/k move the selection (wrap).
|
|
101
|
-
dash.mode = "select"; dash.selectedId = ids[0];
|
|
102
|
-
dash.handleInput("j"); out.select.push(dash.selectedId);
|
|
103
|
-
dash.handleInput("k"); out.select.push(dash.selectedId);
|
|
104
|
-
dash.handleInput("k"); out.select.push(dash.selectedId); // wrap first -> last
|
|
105
|
-
|
|
106
|
-
// A9 negative: select "l" must not attach / leave the mode.
|
|
107
|
-
const doneBeforeSelect = doneCalls.length;
|
|
108
|
-
dash.handleInput("l");
|
|
109
|
-
out.negative.push(["select-l", dash.mode, doneCalls.length - doneBeforeSelect]);
|
|
110
|
-
dash.mode = "list";
|
|
111
|
-
|
|
112
|
-
// A3: peek j/k == peekStep (wrap).
|
|
113
|
-
dash.mode = "peek"; dash.peekId = ids[0]; dash.selectedId = ids[0];
|
|
114
|
-
dash.handleInput("j"); out.peek.push(dash.peekId);
|
|
115
|
-
dash.handleInput("k"); out.peek.push(dash.peekId);
|
|
116
|
-
dash.handleInput("k"); out.peek.push(dash.peekId); // wrap first -> last
|
|
117
|
-
|
|
118
|
-
// A4: l attaches in list / peek / evidence (non-busy rows -> direct done).
|
|
119
|
-
dash.mode = "list"; dash.selectedId = ids[0];
|
|
120
|
-
dash.handleInput("l"); out.attach.push(doneCalls.at(-1));
|
|
121
|
-
dash.mode = "peek"; dash.peekId = ids[1]; dash.selectedId = ids[1];
|
|
122
|
-
dash.handleInput("l"); out.attach.push(doneCalls.at(-1));
|
|
123
|
-
dash.mode = "evidence"; dash.selectedId = ids[2];
|
|
124
|
-
dash.handleInput("l"); out.attach.push(doneCalls.at(-1));
|
|
125
|
-
|
|
126
|
-
// A9 negative: session "l" must not attach / leave the mode.
|
|
127
|
-
dash.mode = "session"; dash.selectedId = ids[0];
|
|
128
|
-
const doneBeforeSession = doneCalls.length;
|
|
129
|
-
dash.handleInput("l");
|
|
130
|
-
out.negative.push(["session-l", dash.mode, doneCalls.length - doneBeforeSession]);
|
|
131
|
-
|
|
132
|
-
// A6: session / evidence j/k still scroll (regression guard).
|
|
133
|
-
dash.mode = "session";
|
|
134
|
-
dash.handleInput("j"); out.scroll.push(dash.sessionScrollTop);
|
|
135
|
-
dash.handleInput("k"); out.scroll.push(dash.sessionScrollTop);
|
|
136
|
-
dash.mode = "evidence";
|
|
137
|
-
dash.handleInput("j"); out.scroll.push(dash.evidenceScrollTop);
|
|
138
|
-
dash.handleInput("k"); out.scroll.push(dash.evidenceScrollTop);
|
|
139
|
-
|
|
140
|
-
// A5: text modes keep j/k/l literal.
|
|
141
|
-
for (const m of ["dispatch", "filter", "rename", "reply"]) {
|
|
142
|
-
dash.mode = m; dash.input = "";
|
|
143
|
-
dash.handleInput("j"); dash.handleInput("k"); dash.handleInput("l");
|
|
144
|
-
out.text[m] = dash.input;
|
|
145
|
-
}
|
|
146
|
-
dash.mode = "list"; dash.input = "";
|
|
147
|
-
|
|
148
|
-
// RF2: busy row + l -> confirm branch (same as →), not a direct attach.
|
|
149
|
-
const busyService = {
|
|
150
|
-
rows: () => [{
|
|
151
|
-
meta: { id: "b1", name: "busy", cwd: "/", sessionFile: "/s/b1.jsonl" },
|
|
152
|
-
state: { semanticState: "working", processState: "alive", pendingQuestions: [] },
|
|
153
|
-
alive: true, hostAlive: false, hostActive: false, hostReady: false, host: null,
|
|
154
|
-
}],
|
|
155
|
-
reconcile: async () => 0,
|
|
156
|
-
};
|
|
157
|
-
const done2 = [];
|
|
158
|
-
const dash2 = new DashboardComponent(tui, theme, {}, (r) => done2.push(r), {
|
|
159
|
-
service: busyService, root, defaultCwd: root,
|
|
160
|
-
availableModels: [], currentModel: null, currentThinkingLevel: "off",
|
|
161
|
-
});
|
|
162
|
-
dash2.selectedId = "b1";
|
|
163
|
-
dash2.handleInput("l");
|
|
164
|
-
out.busyL = { mode: dash2.mode, done: done2.length, prompt: dash2.pending?.prompt ?? null };
|
|
165
|
-
dash2.dispose();
|
|
166
|
-
|
|
167
|
-
dash.dispose();
|
|
168
|
-
console.log(JSON.stringify(out));
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
Create `test/dashboard-vim-keys.test.mjs`:
|
|
172
|
-
|
|
173
|
-
```js
|
|
174
|
-
import assert from "node:assert/strict";
|
|
175
|
-
import { execFileSync } from "node:child_process";
|
|
176
|
-
import { fileURLToPath } from "node:url";
|
|
177
|
-
import { join } from "node:path";
|
|
178
|
-
import test from "node:test";
|
|
179
|
-
|
|
180
|
-
const ROOT_DIR = fileURLToPath(new URL("../", import.meta.url));
|
|
181
|
-
const PROBE = join(ROOT_DIR, "test-support", "vim-keys-wrap.ts");
|
|
182
|
-
|
|
183
|
-
test("vim j/k/l bindings mirror the arrow keys across board modes (issue #229)", () => {
|
|
184
|
-
const out = JSON.parse(execFileSync(process.execPath, ["--experimental-transform-types", PROBE], { encoding: "utf8", timeout: 30_000 }));
|
|
185
|
-
const { ids } = out;
|
|
186
|
-
assert.equal(ids.length, 3, "probe must see 3 rows");
|
|
187
|
-
// A1: list j/k wrap like the arrows; j/k never pop the INSERT toast.
|
|
188
|
-
assert.deepEqual(out.list, [ids[1], ids[2], ids[0], ids[2], ids[2]], "j down x2, wrap; k wrap; paste no-move");
|
|
189
|
-
assert.deepEqual(out.notices, [true, true], "j: no INSERT toast; J: keeps the printable fallback");
|
|
190
|
-
// A2: select mode j/k move (wrap).
|
|
191
|
-
assert.deepEqual(out.select, [ids[1], ids[0], ids[2]]);
|
|
192
|
-
// A3: peek j/k step (wrap).
|
|
193
|
-
assert.deepEqual(out.peek, [ids[1], ids[0], ids[2]]);
|
|
194
|
-
// A4: l attaches in list / peek / evidence.
|
|
195
|
-
assert.deepEqual(out.attach.map((r) => [r.action, r.viewId, r.stopFirst]), [
|
|
196
|
-
["attach", ids[0], false],
|
|
197
|
-
["attach", ids[1], false],
|
|
198
|
-
["attach", ids[2], false],
|
|
199
|
-
]);
|
|
200
|
-
// A9: l stays unbound in select / session.
|
|
201
|
-
assert.deepEqual(out.negative, [["select-l", "select", 0], ["session-l", "session", 0]]);
|
|
202
|
-
// A6: session / evidence j/k still scroll.
|
|
203
|
-
assert.deepEqual(out.scroll, [1, 0, 1, 0]);
|
|
204
|
-
// A5: text modes keep j/k/l literal.
|
|
205
|
-
for (const m of ["dispatch", "filter", "rename", "reply"]) assert.equal(out.text[m], "jkl", m);
|
|
206
|
-
// RF2: busy row + l -> confirm branch, no direct attach.
|
|
207
|
-
assert.equal(out.busyL.mode, "confirm");
|
|
208
|
-
assert.equal(out.busyL.done, 0);
|
|
209
|
-
assert.match(out.busyL.prompt, /Interrupt and attach/);
|
|
210
|
-
});
|
|
211
|
-
```
|
|
212
|
-
|
|
213
|
-
- [ ] **Step 2: Run the test to verify it fails**
|
|
214
|
-
|
|
215
|
-
Run: `cd $WT && node --test test/dashboard-vim-keys.test.mjs`
|
|
216
|
-
Expected: FAIL — list assertion shows `j` does not move the selection (falls into the printable fallback instead).
|
|
217
|
-
|
|
218
|
-
- [ ] **Step 3: Implement the key bindings**
|
|
219
|
-
|
|
220
|
-
In `src/ui/dashboard.ts`:
|
|
221
|
-
|
|
222
|
-
`handleListKey` — extend the three branches:
|
|
223
|
-
|
|
224
|
-
```ts
|
|
225
|
-
if (matchesKey(data, Key.up) || data === "k") return void this.moveSelection(-1);
|
|
226
|
-
if (matchesKey(data, Key.down) || data === "j") return void this.moveSelection(1);
|
|
227
|
-
if (matchesKey(data, Key.right) || data === ">" || data === "l") return this.attachSelected();
|
|
228
|
-
```
|
|
229
|
-
|
|
230
|
-
`handleSelectKey`:
|
|
231
|
-
|
|
232
|
-
```ts
|
|
233
|
-
if (matchesKey(data, Key.up) || data === "k") return void this.moveSelection(-1);
|
|
234
|
-
if (matchesKey(data, Key.down) || data === "j") return void this.moveSelection(1);
|
|
235
|
-
```
|
|
236
|
-
|
|
237
|
-
`handlePeekKey`:
|
|
238
|
-
|
|
239
|
-
```ts
|
|
240
|
-
if (matchesKey(data, Key.right) || data === ">" || data === "l") return this.attachPeek();
|
|
241
|
-
// ...
|
|
242
|
-
if (matchesKey(data, Key.up) || data === "k") {
|
|
243
|
-
this.peekStep(-1);
|
|
244
|
-
return;
|
|
245
|
-
}
|
|
246
|
-
if (matchesKey(data, Key.down) || data === "j") {
|
|
247
|
-
this.peekStep(1);
|
|
248
|
-
return;
|
|
249
|
-
}
|
|
250
|
-
```
|
|
251
|
-
|
|
252
|
-
`handleEvidenceKey`:
|
|
253
|
-
|
|
254
|
-
```ts
|
|
255
|
-
if (data === "a" || matchesKey(data, Key.enter) || matchesKey(data, Key.right) || data === "l") return this.attachSelected();
|
|
256
|
-
```
|
|
257
|
-
|
|
258
|
-
- [ ] **Step 4: Run the test to verify it passes, then remove the dead duplicate lines (spec D3)**
|
|
259
|
-
|
|
260
|
-
Run: `cd $WT && node --test test/dashboard-vim-keys.test.mjs`
|
|
261
|
-
Expected: PASS.
|
|
262
|
-
|
|
263
|
-
Then in `handleListKey` delete the duplicated lines (zero behavior change, re-run the test after):
|
|
264
|
-
|
|
265
|
-
```ts
|
|
266
|
-
if (data === "d") return this.confirmDone();
|
|
267
|
-
if (data === "d") return this.confirmDone(); // DELETE this duplicate
|
|
268
|
-
```
|
|
269
|
-
```ts
|
|
270
|
-
if (matchesKey(data, Key.ctrl("x"))) return this.handleDeleteKey();
|
|
271
|
-
if (matchesKey(data, Key.ctrl("x"))) return this.handleDeleteKey(); // DELETE this duplicate
|
|
272
|
-
```
|
|
273
|
-
|
|
274
|
-
Run: `cd $WT && node --test test/dashboard-vim-keys.test.mjs test/dashboard-delete-key.test.mjs test/dashboard-hold-key.test.mjs`
|
|
275
|
-
Expected: all PASS (no regression in the neighboring key paths).
|
|
276
|
-
|
|
277
|
-
- [ ] **Step 5: Typecheck and commit**
|
|
278
|
-
|
|
279
|
-
Run: `cd $WT && npm run typecheck && node --test test/dashboard-navigation.test.mjs` (existing wrap test still passes)
|
|
280
|
-
Then:
|
|
281
|
-
|
|
282
|
-
```bash
|
|
283
|
-
git -C $WT add test-support/vim-keys-wrap.ts test/dashboard-vim-keys.test.mjs src/ui/dashboard.ts
|
|
284
|
-
git -C $WT commit -m "feat(ui): vim-style j/k navigation and l attach in board modes (issue #229)"
|
|
285
|
-
```
|
|
286
|
-
|
|
287
|
-
---
|
|
288
|
-
|
|
289
|
-
### Task 2: Help overlay and hint-line copy
|
|
290
|
-
|
|
291
|
-
**Files:**
|
|
292
|
-
- Modify: `test-support/vim-keys-wrap.ts` (append render segment)
|
|
293
|
-
- Modify: `test/dashboard-vim-keys.test.mjs` (append A7 test)
|
|
294
|
-
- Modify: `src/ui/dashboard.ts` (renderHelp rows :1701-1729, listHints :1342-1364)
|
|
295
|
-
|
|
296
|
-
**Interfaces:**
|
|
297
|
-
- Consumes: Task 1's probe file and `dash.render(width): string[]` (public, :1212).
|
|
298
|
-
- Produces: probe `out.copy = { help120, listRender, selectRender, help80Lines }`.
|
|
299
|
-
|
|
300
|
-
**Spec acceptance IDs:** A7 (+ Review Focus 3). Covers spec §4 items 1-3 including the D5 doc-debt rows.
|
|
301
|
-
|
|
302
|
-
- [ ] **Step 1: Extend the probe with render capture and write the failing A7 test**
|
|
303
|
-
|
|
304
|
-
Append before `dash.dispose();` in `test-support/vim-keys-wrap.ts`:
|
|
305
|
-
|
|
306
|
-
```ts
|
|
307
|
-
// A7: help overlay + hint lines copy.
|
|
308
|
-
dash.mode = "help"; dash.helpReturnMode = "list";
|
|
309
|
-
const help120 = dash.render(120).join("\n");
|
|
310
|
-
const help80 = dash.render(80); // must not throw; renderCenteredBox clips
|
|
311
|
-
dash.mode = "list";
|
|
312
|
-
const listRender = dash.render(120).join("\n");
|
|
313
|
-
dash.mode = "select";
|
|
314
|
-
const selectRender = dash.render(120).join("\n");
|
|
315
|
-
dash.mode = "list";
|
|
316
|
-
out.copy = { help120, listRender, selectRender, help80Lines: help80.length };
|
|
317
|
-
```
|
|
318
|
-
|
|
319
|
-
Append to `test/dashboard-vim-keys.test.mjs`:
|
|
320
|
-
|
|
321
|
-
```js
|
|
322
|
-
test("help overlay and hint lines document j/k/l (issue #229 A7)", () => {
|
|
323
|
-
const out = JSON.parse(execFileSync(process.execPath, ["--experimental-transform-types", PROBE], { encoding: "utf8", timeout: 30_000 }));
|
|
324
|
-
const { help120, listRender, selectRender, help80Lines } = out.copy;
|
|
325
|
-
assert.match(help120, /↑\/↓ or j\/k/);
|
|
326
|
-
assert.match(help120, /→ \/ > \/ l/);
|
|
327
|
-
assert.match(help120, /In select.*j\/k/);
|
|
328
|
-
assert.match(help120, /In peek.*↑↓\/j\/k/);
|
|
329
|
-
assert.match(help120, /In peek.*→\/l attach/);
|
|
330
|
-
assert.match(help120, /In session.*↑↓\/j\/k scroll/);
|
|
331
|
-
assert.match(help120, /In evidence.*↑↓\/j\/k scroll/);
|
|
332
|
-
assert.match(help120, /In evidence.*→\/l attach/);
|
|
333
|
-
// Negative: stale pre-change wording is gone from every row that named bare arrows.
|
|
334
|
-
assert.doesNotMatch(help120, /In peek.*· ↑↓ adjacent/);
|
|
335
|
-
assert.doesNotMatch(help120, /In session.*· ↑↓ scroll/);
|
|
336
|
-
assert.doesNotMatch(help120, /In evidence.*· ↑↓ scroll/);
|
|
337
|
-
assert.match(listRender, /→\/l attach/);
|
|
338
|
-
assert.match(selectRender, /↑↓\/j\/k move/);
|
|
339
|
-
assert.ok(help80Lines > 0, "80-col help render must not throw (RF3)");
|
|
340
|
-
});
|
|
341
|
-
```
|
|
342
|
-
|
|
343
|
-
- [ ] **Step 2: Run the test to verify it fails**
|
|
344
|
-
|
|
345
|
-
Run: `cd $WT && node --test test/dashboard-vim-keys.test.mjs`
|
|
346
|
-
Expected: FAIL — `↑/↓ or j/k` not found in the help render.
|
|
347
|
-
|
|
348
|
-
- [ ] **Step 3: Update the help overlay rows and hint lines**
|
|
349
|
-
|
|
350
|
-
In `renderHelp` (`src/ui/dashboard.ts:1701-1729`), change these rows:
|
|
351
|
-
|
|
352
|
-
```ts
|
|
353
|
-
["↑/↓ or j/k", "Normal: move selection; Insert: move cursor"],
|
|
354
|
-
["→ / > / l", "Attach to the selected real Pi session"],
|
|
355
|
-
["In select", "j/k move · a select all · u clear · d move done · ctrl+x delete · esc cancel"],
|
|
356
|
-
["In peek", "→/l attach · v transcript · e evidence · r reply · ↑↓/j/k adjacent"],
|
|
357
|
-
["In session", "← back · ↑↓/j/k scroll · e evidence · enter attach · r reply"],
|
|
358
|
-
["In evidence", "← back · ↑↓/j/k scroll · →/l attach · r reply · v transcript · x clear diag"],
|
|
359
|
-
```
|
|
360
|
-
|
|
361
|
-
(Note the trailing `x clear diag` — shortened from `x clear diagnostics` so the longer row stays readable; `renderCenteredBox` clips at narrow widths regardless, RF3.)
|
|
362
|
-
|
|
363
|
-
In `listHints` (:1342-1364): SELECT branch gains `"↑↓/j/k move"` as the first hint after the count/unread spread; NORMAL row changes `"→ attach"` to `"→/l attach"`.
|
|
364
|
-
|
|
365
|
-
- [ ] **Step 4: Run the tests to verify they pass**
|
|
366
|
-
|
|
367
|
-
Run: `cd $WT && node --test test/dashboard-vim-keys.test.mjs`
|
|
368
|
-
Expected: PASS (both tests).
|
|
369
|
-
|
|
370
|
-
- [ ] **Step 5: Commit**
|
|
371
|
-
|
|
372
|
-
```bash
|
|
373
|
-
git -C $WT add test-support/vim-keys-wrap.ts test/dashboard-vim-keys.test.mjs src/ui/dashboard.ts
|
|
374
|
-
git -C $WT commit -m "docs(ui): document j/k/l in help overlay and hint lines (issue #229)"
|
|
375
|
-
```
|
|
376
|
-
|
|
377
|
-
---
|
|
378
|
-
|
|
379
|
-
### Task 3: README key tables and full verification gate
|
|
380
|
-
|
|
381
|
-
**Files:**
|
|
382
|
-
- Modify: `README.md` (Main list table, Peek table, Transcript table, Evidence table)
|
|
383
|
-
|
|
384
|
-
**Interfaces:**
|
|
385
|
-
- Consumes: Task 1/2 behavior as implemented.
|
|
386
|
-
- Produces: docs matching behavior; A8 green run.
|
|
387
|
-
|
|
388
|
-
**Spec acceptance IDs:** A8 (+ U2 groundwork — the tables are what U2 checks against).
|
|
389
|
-
|
|
390
|
-
- [ ] **Step 1: Update the four README tables**
|
|
391
|
-
|
|
392
|
-
In `README.md`:
|
|
393
|
-
|
|
394
|
-
Main list table:
|
|
395
|
-
- `| `Up` / `Down` | Move the selection. |` → `| `Up` / `Down` / `j` / `k` | Move the selection. |`
|
|
396
|
-
- `| `Right` / `>` | Attach to the selected session. |` → `| `Right` / `>` / `l` | Attach to the selected session. |`
|
|
397
|
-
|
|
398
|
-
Peek table:
|
|
399
|
-
- `| `a`, `Right`, or `>` | Attach to the session. |` → `| `a`, `Right`, `>`, or `l` | Attach to the session. |`
|
|
400
|
-
- `| `Up` / `Down` | Move to the previous or next session. |` → `| `Up` / `Down` / `j` / `k` | Move to the previous or next session. |`
|
|
401
|
-
|
|
402
|
-
Transcript table:
|
|
403
|
-
- `| `Up` / `Down` | Scroll one line. |` → `| `Up` / `Down` / `j` / `k` | Scroll one line. |`
|
|
404
|
-
|
|
405
|
-
Evidence table:
|
|
406
|
-
- `| `Up` / `Down` | Scroll one line. |` → `| `Up` / `Down` / `j` / `k` | Scroll one line. |`
|
|
407
|
-
- `| `Enter`, `a`, or `Right` | Attach to the session. |` → `| `Enter`, `a`, `Right`, or `l` | Attach to the session. |`
|
|
408
|
-
|
|
409
|
-
- [ ] **Step 2: Run the full verification gate**
|
|
410
|
-
|
|
411
|
-
Run: `cd $WT && npm run verify`
|
|
412
|
-
Expected: PASS — typecheck, perf gate, all tests, coverage thresholds (lines 85 / funcs 80 / branches 70), pack:dry.
|
|
413
|
-
|
|
414
|
-
- [ ] **Step 3: Commit**
|
|
415
|
-
|
|
416
|
-
```bash
|
|
417
|
-
git -C $WT add README.md
|
|
418
|
-
git -C $WT commit -m "docs: document j/k/l keys in the README view tables (issue #229)"
|
|
419
|
-
```
|
|
420
|
-
|
|
421
|
-
---
|
|
422
|
-
|
|
423
|
-
### Task 4: Post-implementation manual verification (pre-merge, by user)
|
|
424
|
-
|
|
425
|
-
**Spec acceptance IDs:** U1, U2. No code changes; this task is the executable checklist.
|
|
426
|
-
|
|
427
|
-
- [ ] **Step 1: U1 — real dashboard feel**
|
|
428
|
-
|
|
429
|
-
Deploy prelude (prevents loading stale code): `pi list` to record the current install source (this machine: `git:github.com/zhuxixi/pi-agent-board`) → `pi remove <that source>` → `pi install /home/elling/work/git-repo/pi-agent-board/.pi/worktrees/issue-229-vim-jkl-board-list` → `pi /agent-board`.
|
|
430
|
-
|
|
431
|
-
Checklist: ① `j`/`k` walk list rows incl. wrap at both ends ② `j` no longer pops the INSERT toast ③ `l` attaches the selected session ④ after detach, `?` shows the new bindings (incl. the evidence row's `→/l attach`) ⑤ in INSERT mode `j`/`k`/`l` stay literal characters.
|
|
432
|
-
|
|
433
|
-
Cleanup: `pi remove /home/elling/work/git-repo/pi-agent-board/.pi/worktrees/issue-229-vim-jkl-board-list` → reinstall the recorded original source.
|
|
434
|
-
|
|
435
|
-
- [ ] **Step 2: U2 — README vs behavior**
|
|
436
|
-
|
|
437
|
-
Walk the four README tables row by row against the live dashboard; every listed key works, no key is missing.
|
|
438
|
-
|
|
439
|
-
- [ ] **Step 3: Record results**
|
|
440
|
-
|
|
441
|
-
Comment the U1/U2 outcomes on issue #229 before merge. If any item fails or cannot run, mark it `pending` — do not claim full acceptance.
|
|
442
|
-
|
|
443
|
-
---
|
|
444
|
-
|
|
445
|
-
## Self-Review Notes
|
|
446
|
-
|
|
447
|
-
- **Spec coverage:** §3 bindings → Task 1; §4 copy items 1-3 → Task 2, items 4-5 → Task 3; D3 dead-code → Task 1 Step 4; A1-A9 → Task 1/2; A8 → Task 3; U1/U2 → Task 4. Every acceptance ID has a task; every task cites IDs.
|
|
448
|
-
- **Placeholder scan:** none — all code and commands are concrete.
|
|
449
|
-
- **Type consistency:** probe field names (`out.list/select/peek/attach/negative/scroll/text/notices/busyL/copy`) match between the probe and both tests; `DashboardResult` shape `{action, viewId, stopFirst}` matches `src/ui/dashboard.ts:58`.
|
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
# Attach Background-Only Cells 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 `lineToAnsi()` from trimming cells that carry only a non-default background, so full-screen TUIs (nvim, lazygit, htop) keep their theme background in the attach view.
|
|
6
|
-
|
|
7
|
-
**Architecture:** The per-line scan in `lineToAnsi()` already walks every cell; it gains a `lastBg` cursor (last cell with `!isBgDefault()`), and the render bound becomes `renderEnd = max(last, lastBg)`. The main loop, the empty-line guard, and the cursor-past-content check all switch from `last` to `renderEnd`. Default-background screens keep the existing cheap path (`""` for blank lines, no padding). A component-level smoke fixture drives a real `@xterm/headless` terminal through `PtyAttachComponent.render()` and asserts the emitted ANSI.
|
|
8
|
-
|
|
9
|
-
**Tech Stack:** TypeScript (`src/ui`), Node 22/24 `node:test`, `node --experimental-transform-types` fixtures, `@xterm/headless`.
|
|
10
|
-
|
|
11
|
-
**Spec:** `docs/superpowers/specs/2026-10-23-issue-232-attach-bg-only-cells-design.md`
|
|
12
|
-
|
|
13
|
-
**Worktree:** `/home/elling/work/git-repo/pi-agent-board/.pi/worktrees/issue-232-attach-bg-only-cells` — every path below is relative to it; never edit the main checkout.
|
|
14
|
-
|
|
15
|
-
## Global Constraints
|
|
16
|
-
|
|
17
|
-
- Tabs for indentation; English comments; conventional commit subjects (`fix(attach): …`) with `(#232)` only on the PR title.
|
|
18
|
-
- Do not change selection/copy semantics, OSC-8 link handling, mouse handling, or the wide-char skip (`getWidth() === 0`).
|
|
19
|
-
- Padding must never be emitted for default-background cells — the cheap path for ordinary shells stays byte-identical.
|
|
20
|
-
- `git add <explicit files>` only; never `git add -A`.
|
|
21
|
-
- `npm run verify` (typecheck + perf + test + coverage + pack:dry) is the gate before claiming done.
|
|
22
|
-
- Fixture cells that must be background-only are produced **only** by erase sequences (`\x1b[K` / `\x1b[2J`) under an active bg; literal spaces keep `getChars()` non-empty and make the test pass on unpatched code (spec A8).
|
|
23
|
-
|
|
24
|
-
## Review Focus
|
|
25
|
-
|
|
26
|
-
1. **Blank painted line** — all cells `getChars() === ""`, `isBgDefault() === false`; the line must render as an SGR run plus padding, not `""`. → A1.
|
|
27
|
-
2. **Trailing paint after content** — characters end at col 1, paint starts at col 40; cols 2–39 stay default, 40–79 painted. → A2.
|
|
28
|
-
3. **Default-background regression** — plain text screens still serialize blank lines to `""` (no padding, no SGR). → A3.
|
|
29
|
-
4. **Wide chars** — `中` (cols 0–1, continuation cell skipped) followed by painted erase cells; display width must stay 80 with 78 padding spaces. → A4.
|
|
30
|
-
5. **Cursor inside the padded region** — exactly one `CURSOR_MARKER` and one inverse block, no double block. → A5.
|
|
31
|
-
6. **Selection over padding** — padded cells inside a selection render inverse (`7`); copied text is unaffected. → A9.
|
|
32
|
-
|
|
33
|
-
---
|
|
34
|
-
|
|
35
|
-
### Task 1: Fixture and failing tests (red)
|
|
36
|
-
|
|
37
|
-
**Files:**
|
|
38
|
-
- Create: `test-support/attach-bg-paint-smoke.ts`
|
|
39
|
-
- Create: `test/pty-attach-bg-paint.test.mjs`
|
|
40
|
-
|
|
41
|
-
**Interfaces:**
|
|
42
|
-
- Consumes: `PtyAttachComponent` (`src/ui/pty-attach.ts:162`), constructor `(tui, theme, keybindings, done, opts)`; `render(width: number): string[]` returns `[header, ...body(rows-2), footer]`.
|
|
43
|
-
- Produces: JSON on stdout from the fixture:
|
|
44
|
-
|
|
45
|
-
```ts
|
|
46
|
-
{
|
|
47
|
-
blankPainted: string[]; // 10 body lines, whole screen erased with bg #1e1e2e
|
|
48
|
-
contentPainted: string[]; // one row: "AB" + 38 default spaces + 40 painted cells
|
|
49
|
-
plain: string[]; // ordinary text screen (default bg)
|
|
50
|
-
wide: string[]; // "中" + 78 painted cells
|
|
51
|
-
cursor: string[]; // painted blank row, cursor visible at col 30
|
|
52
|
-
selection: string[]; // painted blank row, selection cols 20–60
|
|
53
|
-
}
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
- [ ] **Step 1: Write the fixture.** Model it on `test-support/hyperlink-cell-smoke.ts` (stub `tui` with `terminal: { rows: 12, cols: 80, columns: 80, write: () => {} }`, identity `theme`, `socketPath: "/no/such/socket"`, `dispose()` in `finally`). Cast the component to expose `term`, `receivedOutput`, `finishAttachTransition`, and `selection`.
|
|
57
|
-
|
|
58
|
-
Per scenario:
|
|
59
|
-
- `blankPainted`: `\x1b[48;2;30;30;46m\x1b[2J` then `\x1b[?25l` (hide cursor).
|
|
60
|
-
- `contentPainted`: default-bg rows, then `AB\x1b[41G\x1b[48;2;30;30;46m\x1b[K` on one row; hide cursor.
|
|
61
|
-
- `plain`: `hello\r\nworld` only; hide cursor.
|
|
62
|
-
- `wide`: `中\x1b[48;2;30;30;46m\x1b[K`; hide cursor.
|
|
63
|
-
- `cursor`: painted blank row (`\x1b[48;2;30;30;46m\x1b[2J`), then park the cursor visibly at row 5 col 30 via `\x1b[6;31H`; do **not** send `\x1b[?25l`.
|
|
64
|
-
- `selection`: painted blank screen; set the private `selection = { anchor: { line: 5, col: 20 }, focus: { line: 5, col: 60 } }` (type `MouseSelection`, `src/ui/pty-attach.ts:159`) before `finishAttachTransition()`.
|
|
65
|
-
|
|
66
|
-
Return `attach.render(80).slice(1, -1)` per scenario (the component's body lines; height comes from `tui.terminal.rows - 2`).
|
|
67
|
-
|
|
68
|
-
- [ ] **Step 2: Write the assertions** in `test/pty-attach-bg-paint.test.mjs`, spawning the fixture with `execFileSync(process.execPath, ["--experimental-transform-types", FIXTURE], { encoding: "utf8", timeout: 30_000 })` exactly like `test/pty-attach-hyperlinks.test.mjs`:
|
|
69
|
-
- A1: every `blankPainted` line is non-empty and includes `48;2;30;30;46`.
|
|
70
|
-
- A2: the `contentPainted` line containing `AB` has exactly 38 spaces before the next SGR, and the paint run covers 40 cells.
|
|
71
|
-
- A3: in `plain`, the blank body lines are exactly `""`.
|
|
72
|
-
- A4: the `wide` line contains `中`, ends with 78 spaces plus `\x1b[0m`, and has display width 80.
|
|
73
|
-
- A5: the cursor row contains exactly one `\x1b_pi:c\x07` and one `\x1b[7m \x1b[0m`.
|
|
74
|
-
- A9: the selection row contains an SGR that includes `7` (e.g. `\x1b[0;7;`) inside the padded region.
|
|
75
|
-
|
|
76
|
-
- [ ] **Step 3: Run the tests and record the red output.**
|
|
77
|
-
|
|
78
|
-
```bash
|
|
79
|
-
node --test test/pty-attach-bg-paint.test.mjs
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
Expected: A1, A2, A4, A5, A9 fail against the unpatched `lineToAnsi` (blank painted lines are `""`, padding absent). A3 passes. Save the output — it is the A8 "red" evidence for the PR description.
|
|
83
|
-
|
|
84
|
-
---
|
|
85
|
-
|
|
86
|
-
### Task 2: Patch `lineToAnsi()` (green)
|
|
87
|
-
|
|
88
|
-
**Files:**
|
|
89
|
-
- Modify: `src/ui/pty-attach.ts` (`lineToAnsi`, `:1962-2026`)
|
|
90
|
-
|
|
91
|
-
**Interfaces:**
|
|
92
|
-
- Consumes: `BufferCellLike.isBgDefault()` (`:142`), already on the interface.
|
|
93
|
-
- Produces: no signature change.
|
|
94
|
-
|
|
95
|
-
- [ ] **Step 1: Add the painted-extent scan (C1).** After the `if (!line)` early return, declare `let lastBg = -1;`, and inside the scan loop add `if (!cell.isBgDefault()) lastBg = x;` after the `getChars()` check. Compute `const renderEnd = Math.max(last, lastBg);` and make the empty-line guard test `renderEnd < 0`.
|
|
96
|
-
|
|
97
|
-
- [ ] **Step 2: Render through the painted extent (C2).** Change the main loop bound to `x <= renderEnd`.
|
|
98
|
-
|
|
99
|
-
- [ ] **Step 3: Fix the cursor bound (C3).** Change the post-loop check to `cursor!.col > renderEnd` and update the adjacent comment to say "painted region" rather than "line content".
|
|
100
|
-
|
|
101
|
-
- [ ] **Step 4: Add the explanatory comment** above `lastBg` (English): full-screen TUIs paint an explicit theme bg over empty regions; trimming at the last character drops it and lets the host terminal's default bg show through (issue #232). Keep it to 3–4 lines.
|
|
102
|
-
|
|
103
|
-
- [ ] **Step 5: Run the new tests.**
|
|
104
|
-
|
|
105
|
-
```bash
|
|
106
|
-
node --test test/pty-attach-bg-paint.test.mjs
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
Expected: all pass, including A3 (plain screen unchanged).
|
|
110
|
-
|
|
111
|
-
- [ ] **Step 6: Commit.**
|
|
112
|
-
|
|
113
|
-
```bash
|
|
114
|
-
git add src/ui/pty-attach.ts test-support/attach-bg-paint-smoke.ts test/pty-attach-bg-paint.test.mjs
|
|
115
|
-
git commit -m "fix(attach): render cells that only carry a background (#232)"
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
---
|
|
119
|
-
|
|
120
|
-
### Task 3: Red-green mutation proof (A8)
|
|
121
|
-
|
|
122
|
-
- [ ] **Step 1:** Temporarily change the main loop bound back to `x <= last` (leave `renderEnd` in place).
|
|
123
|
-
- [ ] **Step 2:** Run `node --test test/pty-attach-bg-paint.test.mjs`; expect A1/A2/A4 to fail.
|
|
124
|
-
- [ ] **Step 3:** Restore `x <= renderEnd`; re-run; expect all pass.
|
|
125
|
-
- [ ] **Step 4:** Record both outputs (commands + failure names) for the PR description. Do not commit the reverted state.
|
|
126
|
-
|
|
127
|
-
---
|
|
128
|
-
|
|
129
|
-
### Task 4: Full gate
|
|
130
|
-
|
|
131
|
-
- [ ] **Step 1:** `npm run typecheck` → exit 0 (A6; note `test-support/` is outside `tsconfig`, so fixture types surface through Task 1 instead).
|
|
132
|
-
- [ ] **Step 2:** `npm run verify` → typecheck + perf gate + full suite + coverage thresholds (lines 85 / funcs 80 / branches 70) + pack dry-run (A7).
|
|
133
|
-
|
|
134
|
-
---
|
|
135
|
-
|
|
136
|
-
### Task 5: Manual acceptance (U1, after the PR branch is installable)
|
|
137
|
-
|
|
138
|
-
- [ ] **Step 1:** Confirm the worktree HEAD equals the PR head (`git log -1`).
|
|
139
|
-
- [ ] **Step 2:** `npm install` in the worktree; load the checkout with `pi install "$(pwd)"`; **restart pi**.
|
|
140
|
-
- [ ] **Step 3:** `/agent-board` → attach to an nvim session with catppuccin-mocha; observe the empty buffer area (`#1e1e2e`) and the cursorline (highlight spans the full row).
|
|
141
|
-
- [ ] **Step 4:** `pi remove "$(pwd)"`; report the observation in the PR/issue.
|
|
142
|
-
|
|
143
|
-
---
|
|
144
|
-
|
|
145
|
-
## Acceptance traceability
|
|
146
|
-
|
|
147
|
-
| Spec ID | Plan task |
|
|
148
|
-
| --- | --- |
|
|
149
|
-
| A1, A2, A3, A4, A5, A9 | Task 1 (red) + Task 2 Step 5 (green) |
|
|
150
|
-
| A8 | Task 3 (+ Task 1 Step 3 red output) |
|
|
151
|
-
| A6 | Task 4 Step 1 |
|
|
152
|
-
| A7 | Task 4 Step 2 |
|
|
153
|
-
| U1 | Task 5 |
|