@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,405 +0,0 @@
|
|
|
1
|
-
# Selected Row Highlight 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:** Make the board's selected row identifiable at rest by painting its full line (indent through the trailing pad) with the theme's `selectedBg`.
|
|
6
|
-
|
|
7
|
-
**Architecture:** One pure module-scope helper `applyRowHighlight(theme, selected, text)` decides whether to wrap a line; `renderRow` calls it on `indent + clipped body` so the highlight starts at column 0 and covers the right edge. The theme token (`selectedBg`) is pi's own selection background, so dark/light themes and 256/16-colour terminals are handled by pi. A new TS harness (`dashboard-selection-render.ts`) drives the real `DashboardComponent` and asserts the ANSI structure; the eight pre-existing `DashboardComponent` harnesses get an identity `bg` stub.
|
|
8
|
-
|
|
9
|
-
**Tech Stack:** TypeScript (src/ui), Node 20+ `node:test` + `node --experimental-transform-types` harnesses, `@earendil-works/pi-tui` (`visibleWidth`, `truncateToWidth`), pi theme tokens (`ThemeBg = "selectedBg"`).
|
|
10
|
-
|
|
11
|
-
**Spec:** `docs/superpowers/specs/2026-10-23-selected-row-highlight-design.md`
|
|
12
|
-
|
|
13
|
-
**Worktree:** `C:/Users/27499/proj/opensource/pi-agent-board/.pi/worktrees/issue-231-selected-row-highlight` — 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 with the issue number (`(#231)` lands on the PR, not on local commits).
|
|
18
|
-
- Do not change `select` mode's `◉/○`, the peek header, the flash banner, the folder-header `▌`, or `windowBody()` (open PR #226 owns the viewport).
|
|
19
|
-
- Highlight colour comes **only** from `theme.bg("selectedBg", …)` — no hardcoded RGB, no per-stage background, no `muted → dim` demotion.
|
|
20
|
-
- `\x1b[49m` (emitted by `theme.bg`) must stay inside the clipped string; the bg span must include the folder indent, the trailing pad, and the age column.
|
|
21
|
-
- `npm run verify` (typecheck + perf + test + coverage + pack:dry) is the gate before claiming done.
|
|
22
|
-
- Do not touch `research/`, `~/.github-issue-driven/`, or the main checkout; `git add <explicit files>` only.
|
|
23
|
-
|
|
24
|
-
## Review Focus
|
|
25
|
-
|
|
26
|
-
1. **Ultra-narrow terminals (`width < 24`)** — `contentWidth` has a floor of 20, so `indent + contentWidth > width` and `clip` truncates with `\x1b[0m`. The highlight may lose its closer there, but it must never leak into following rows. → A8 (Task 2).
|
|
27
|
-
2. **`selectedId` matching no visible row** (e.g. after a filter) — no row may be painted. → A5 zero-branch (Task 2).
|
|
28
|
-
3. **Folder-group indent (`indent = 4`)** — the bg opener must sit at column 0, before the indent, not after it. → A3 (Task 2).
|
|
29
|
-
4. **`select` mode** — the batch marker `◉/○` must remain visible and the highlight must still apply. → A6 (Task 2).
|
|
30
|
-
5. **Coexisting strong signals** — the flash banner keeps its own bg bar, the folder header keeps its `▌`, and the peek header stays unhighlighted. → A7 (Task 2) and U5 (manual).
|
|
31
|
-
|
|
32
|
-
---
|
|
33
|
-
|
|
34
|
-
### Task 1: Pure highlight helper
|
|
35
|
-
|
|
36
|
-
**Files:**
|
|
37
|
-
- Modify: `src/ui/dashboard.ts` (insert the exported helper next to `stageFg` / `ansiBg`, around the `STAGE_RGB` block at :1968-1996)
|
|
38
|
-
- Create: `test-support/dashboard-selection-render.ts`
|
|
39
|
-
- Create: `test/dashboard-selection.test.mjs`
|
|
40
|
-
|
|
41
|
-
**Interfaces:**
|
|
42
|
-
- Consumes: `ThemeLike` (`src/ui/dashboard.ts:50-56`, already declares `bg(color: string, text: string): string`).
|
|
43
|
-
- Produces:
|
|
44
|
-
- `export function applyRowHighlight(theme: Pick<ThemeLike, "bg">, selected: boolean, text: string): string`
|
|
45
|
-
- Harness JSON contract: `{ ok: boolean, unit: { unselectedIdentical: boolean, selectedWrapped: boolean, themeCallCount: number } }` (integration fields are added in Task 2).
|
|
46
|
-
|
|
47
|
-
- [ ] **Step 1: Write the failing test**
|
|
48
|
-
|
|
49
|
-
Create `test/dashboard-selection.test.mjs`:
|
|
50
|
-
|
|
51
|
-
```js
|
|
52
|
-
import assert from "node:assert/strict";
|
|
53
|
-
import { execFileSync } from "node:child_process";
|
|
54
|
-
import { fileURLToPath } from "node:url";
|
|
55
|
-
import { join } from "node:path";
|
|
56
|
-
import test from "node:test";
|
|
57
|
-
|
|
58
|
-
const ROOT_DIR = fileURLToPath(new URL("../", import.meta.url));
|
|
59
|
-
const SCRIPT = join(ROOT_DIR, "test-support", "dashboard-selection-render.ts");
|
|
60
|
-
|
|
61
|
-
function runHarness() {
|
|
62
|
-
const out = execFileSync(process.execPath, ["--experimental-transform-types", SCRIPT], {
|
|
63
|
-
encoding: "utf8",
|
|
64
|
-
timeout: 30_000,
|
|
65
|
-
});
|
|
66
|
-
return JSON.parse(out);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
test("applyRowHighlight is a pure pass-through when unselected and wraps once when selected (issue #231)", () => {
|
|
70
|
-
const parsed = runHarness();
|
|
71
|
-
assert.equal(parsed.ok, true);
|
|
72
|
-
assert.equal(parsed.unit.unselectedIdentical, true, "unselected input must come back byte-identical");
|
|
73
|
-
assert.equal(parsed.unit.selectedWrapped, true, "selected input must be wrapped exactly once");
|
|
74
|
-
assert.equal(parsed.unit.themeCallCount, 1, "the helper must call theme.bg once per selected row, never for unselected rows");
|
|
75
|
-
});
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
Create `test-support/dashboard-selection-render.ts` (unit part only for now):
|
|
79
|
-
|
|
80
|
-
```ts
|
|
81
|
-
// Dashboard selected-row harness: renders real DashboardComponent frames with a
|
|
82
|
-
// recording/ANSI-emitting theme stub and prints JSON flags the .mjs test asserts
|
|
83
|
-
// on. Unit checks for the pure helper live here too so one process proves both.
|
|
84
|
-
import { mkdtempSync, rmSync } from "node:fs";
|
|
85
|
-
import { tmpdir } from "node:os";
|
|
86
|
-
import { join } from "node:path";
|
|
87
|
-
import { visibleWidth } from "@earendil-works/pi-tui";
|
|
88
|
-
import { createView } from "../src/core/store.mjs";
|
|
89
|
-
import { createService } from "../src/runtime/service.mjs";
|
|
90
|
-
import { applyRowHighlight, DashboardComponent } from "../src/ui/dashboard.ts";
|
|
91
|
-
|
|
92
|
-
const BG_OPEN = "\x1b[48;2;1;2;3m";
|
|
93
|
-
const BG_CLOSE = "\x1b[49m";
|
|
94
|
-
|
|
95
|
-
const root = mkdtempSync(join(tmpdir(), "agentview-selection-render-"));
|
|
96
|
-
try {
|
|
97
|
-
let bgCalls = 0;
|
|
98
|
-
const theme = {
|
|
99
|
-
fg: (token: string, text: string) => {
|
|
100
|
-
fgCalls.push([token, text]);
|
|
101
|
-
return text;
|
|
102
|
-
},
|
|
103
|
-
bold: (text: string) => text,
|
|
104
|
-
italic: (text: string) => text,
|
|
105
|
-
strikethrough: (text: string) => text,
|
|
106
|
-
bg: (_color: string, text: string) => {
|
|
107
|
-
bgCalls += 1;
|
|
108
|
-
return `${BG_OPEN}${text}${BG_CLOSE}`;
|
|
109
|
-
},
|
|
110
|
-
};
|
|
111
|
-
const fgCalls: Array<[string, string]> = [];
|
|
112
|
-
|
|
113
|
-
// --- unit: the pure helper -------------------------------------------
|
|
114
|
-
const identity = "plain line";
|
|
115
|
-
const untouched = applyRowHighlight(theme, false, identity);
|
|
116
|
-
const callsAfterUnselected = bgCalls;
|
|
117
|
-
const wrapped = applyRowHighlight(theme, true, identity);
|
|
118
|
-
|
|
119
|
-
console.log(
|
|
120
|
-
JSON.stringify({
|
|
121
|
-
ok: true,
|
|
122
|
-
unit: {
|
|
123
|
-
unselectedIdentical: untouched === identity && callsAfterUnselected === 0,
|
|
124
|
-
selectedWrapped: wrapped === `${BG_OPEN}${identity}${BG_CLOSE}`,
|
|
125
|
-
themeCallCount: bgCalls,
|
|
126
|
-
},
|
|
127
|
-
}),
|
|
128
|
-
);
|
|
129
|
-
} finally {
|
|
130
|
-
rmSync(root, { recursive: true, force: true });
|
|
131
|
-
}
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
(Keep `createView`, `createService`, `DashboardComponent` and `visibleWidth` imported even though the unit part does not use them yet — Task 2 fills in the integration section, and the typecheck gate excludes `test-support/` anyway.)
|
|
135
|
-
|
|
136
|
-
- [ ] **Step 2: Run the test to verify it fails**
|
|
137
|
-
|
|
138
|
-
Run: `node --test test/dashboard-selection.test.mjs`
|
|
139
|
-
Expected: FAIL — the harness throws because `applyRowHighlight` is not exported from `../src/ui/dashboard.ts` (or `parsed.ok` is missing).
|
|
140
|
-
|
|
141
|
-
- [ ] **Step 3: Implement `applyRowHighlight` in `src/ui/dashboard.ts`**
|
|
142
|
-
|
|
143
|
-
Place it next to `stageFg`:
|
|
144
|
-
|
|
145
|
-
```ts
|
|
146
|
-
/**
|
|
147
|
-
* Issue #231: the selected row's full-line background. Pure (the theme is
|
|
148
|
-
* injected) so the wrap/close contract is unit-testable, and an unselected row
|
|
149
|
-
* is returned byte-identical.
|
|
150
|
-
*/
|
|
151
|
-
export function applyRowHighlight(
|
|
152
|
-
theme: Pick<ThemeLike, "bg">,
|
|
153
|
-
selected: boolean,
|
|
154
|
-
text: string,
|
|
155
|
-
): string {
|
|
156
|
-
return selected ? theme.bg("selectedBg", text) : text;
|
|
157
|
-
}
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
- [ ] **Step 4: Run the test to verify it passes**
|
|
161
|
-
|
|
162
|
-
Run: `node --test test/dashboard-selection.test.mjs`
|
|
163
|
-
Expected: PASS (1 test).
|
|
164
|
-
|
|
165
|
-
- [ ] **Step 5: Commit**
|
|
166
|
-
|
|
167
|
-
```bash
|
|
168
|
-
git add src/ui/dashboard.ts test/dashboard-selection.test.mjs test-support/dashboard-selection-render.ts
|
|
169
|
-
git commit -m "feat(ui): add pure selected-row highlight helper (issue #231)"
|
|
170
|
-
```
|
|
171
|
-
|
|
172
|
-
---
|
|
173
|
-
|
|
174
|
-
### Task 2: Paint the row, cover the indent, and keep every existing harness green
|
|
175
|
-
|
|
176
|
-
**Files:**
|
|
177
|
-
- Modify: `src/ui/dashboard.ts:1459-1483` (`renderRow` return path)
|
|
178
|
-
- Modify: `test-support/dashboard-selection-render.ts` (add the integration section and its JSON fields)
|
|
179
|
-
- Modify: `test/dashboard-selection.test.mjs` (assert the integration fields)
|
|
180
|
-
- Modify (stub only): `test-support/dashboard-delete-key-probe.ts`, `test-support/cwd-browse-tab-probe.ts`, `test-support/dashboard-shrink-render.ts`, `test-support/dashboard-refs-render.ts`, `test-support/dashboard-prewarm-smoke.ts`, `test-support/navigation-wrap.ts`, `test-support/hold-key-wrap.ts`, `test-support/ui-smoke.ts`, `test-support/attach-delete-flow-probe.ts`
|
|
181
|
-
|
|
182
|
-
**Interfaces:**
|
|
183
|
-
- Consumes: `applyRowHighlight` from Task 1.
|
|
184
|
-
- Produces: `renderRow` output whose selected-row string begins with the theme's bg opener and ends with `\x1b[49m`; harness JSON gains `integration: { selectedStartsAtCol0, selectedWidth, unselectedHasNoOpener, selectedHasArrow, unselectedHasNoArrow, selectedCloserLast, selectedCloserCount, firstFrameBgCalls, zeroSelectionBgCalls, selectMarkerPresent, selectUnselectedCircle, selectHasBg, peekHasNoOpener, peekHeaderUsesAccent, narrowMaxWidth, narrowLeaked, unselectedUsesMuted, unselectedNameIsDim }` (`firstFrameBgCalls` is the per-frame `bgCalls` counter of the selected-row render; `zeroSelectionBgCalls` is the same counter with a `selectedId` that matches no visible row).
|
|
185
|
-
|
|
186
|
-
- [ ] **Step 1: Write the failing integration assertions**
|
|
187
|
-
|
|
188
|
-
Append to `test-support/dashboard-selection-render.ts`, inside the `try` block, after the unit section, and change the single `console.log` to emit both `unit` and `integration`:
|
|
189
|
-
|
|
190
|
-
```ts
|
|
191
|
-
// --- integration: real render frames ---------------------------------
|
|
192
|
-
createView(root, { id: "v1", name: "alpha session", cwd: join(root, "proj-a") });
|
|
193
|
-
createView(root, { id: "v2", name: "beta session", cwd: join(root, "proj-b") });
|
|
194
|
-
const service = createService({
|
|
195
|
-
root,
|
|
196
|
-
runnerScript: "/no/runner.mjs",
|
|
197
|
-
piCommand: "pi",
|
|
198
|
-
piArgsPrefix: [],
|
|
199
|
-
defaultCwd: root,
|
|
200
|
-
launch: () => ({ pid: null, configPath: "/no/config.json" }),
|
|
201
|
-
launchHost: () => ({ pid: null, configPath: "/no/host-config.json" }),
|
|
202
|
-
launchTitle: () => ({ pid: null, configPath: "/no/title-config.json" }),
|
|
203
|
-
});
|
|
204
|
-
const tui = {
|
|
205
|
-
terminal: { rows: 40, cols: 160, columns: 160, write: () => {} },
|
|
206
|
-
requestRender: () => {},
|
|
207
|
-
};
|
|
208
|
-
const makeDash = () =>
|
|
209
|
-
new DashboardComponent(tui as never, theme as never, {} as never, () => {}, {
|
|
210
|
-
service,
|
|
211
|
-
root,
|
|
212
|
-
defaultCwd: root,
|
|
213
|
-
availableModels: [],
|
|
214
|
-
currentModel: null,
|
|
215
|
-
currentThinkingLevel: "off",
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
// Two cwds ⇒ showFolders=true ⇒ rows carry indent 4.
|
|
219
|
-
bgCalls = 0;
|
|
220
|
-
const dash = makeDash();
|
|
221
|
-
const lines = dash.render(160);
|
|
222
|
-
const selectedLine = lines.find((line) => line.includes("alpha session")) ?? "";
|
|
223
|
-
const otherLine = lines.find((line) => line.includes("beta session")) ?? "";
|
|
224
|
-
|
|
225
|
-
const selectedStartsAtCol0 = selectedLine.startsWith(BG_OPEN);
|
|
226
|
-
const selectedWidth = visibleWidth(selectedLine);
|
|
227
|
-
const unselectedHasNoOpener = !otherLine.includes(BG_OPEN);
|
|
228
|
-
const selectedHasArrow = selectedLine.includes("›");
|
|
229
|
-
const unselectedHasNoArrow = !otherLine.includes("›");
|
|
230
|
-
const firstFrameBgCalls = bgCalls;
|
|
231
|
-
|
|
232
|
-
// select mode: press `m`, then space to batch-mark the current row.
|
|
233
|
-
dash.handleInput("m");
|
|
234
|
-
dash.handleInput(" ");
|
|
235
|
-
const selectLines = dash.render(160);
|
|
236
|
-
const selectMarkerPresent = selectLines.some((line) => line.includes("◉"));
|
|
237
|
-
const selectHasBg = selectLines.find((line) => line.includes("alpha session"))?.includes(BG_OPEN) ?? false;
|
|
238
|
-
dash.handleInput("\x1b");
|
|
239
|
-
|
|
240
|
-
// peek: space in list mode opens the peek header (must stay unhighlighted).
|
|
241
|
-
const peekDash = makeDash();
|
|
242
|
-
peekDash.handleInput(" ");
|
|
243
|
-
const peekHasNoOpener = !peekDash.render(160).some((line) => line.includes(BG_OPEN));
|
|
244
|
-
|
|
245
|
-
// ultra-narrow: width 23 with indent 4 overflows (4 + max(20, 19) = 24 > 23).
|
|
246
|
-
const narrowLines = dash.render(23);
|
|
247
|
-
const narrowMaxWidth = Math.max(...narrowLines.map((line) => visibleWidth(line)));
|
|
248
|
-
const narrowLeaked = narrowLines.some((line) => !line.includes("alpha session") && line.includes(BG_OPEN));
|
|
249
|
-
|
|
250
|
-
// D4: unselected rows keep `muted`, never `dim`, for name/summary.
|
|
251
|
-
const unselectedUsesMuted = fgCalls.some(([token, text]) => token === "muted" && text.includes("beta session"));
|
|
252
|
-
const unselectedNameIsDim = fgCalls.some(([token, text]) => token === "dim" && text.includes("beta session"));
|
|
253
|
-
```
|
|
254
|
-
|
|
255
|
-
Then replace the `console.log(JSON.stringify({ ok: true, unit: {...} }))` with:
|
|
256
|
-
|
|
257
|
-
```ts
|
|
258
|
-
console.log(
|
|
259
|
-
JSON.stringify({
|
|
260
|
-
ok: true,
|
|
261
|
-
unit: {
|
|
262
|
-
unselectedIdentical: untouched === identity && callsAfterUnselected === 0,
|
|
263
|
-
selectedWrapped: wrapped === `${BG_OPEN}${identity}${BG_CLOSE}`,
|
|
264
|
-
themeCallCount: bgCallsBeforeIntegration,
|
|
265
|
-
},
|
|
266
|
-
integration: {
|
|
267
|
-
selectedStartsAtCol0,
|
|
268
|
-
selectedWidth,
|
|
269
|
-
unselectedHasNoOpener,
|
|
270
|
-
selectedHasArrow,
|
|
271
|
-
unselectedHasNoArrow,
|
|
272
|
-
firstFrameBgCalls,
|
|
273
|
-
selectMarkerPresent,
|
|
274
|
-
selectHasBg,
|
|
275
|
-
peekHasNoOpener,
|
|
276
|
-
narrowMaxWidth,
|
|
277
|
-
narrowLeaked,
|
|
278
|
-
unselectedUsesMuted,
|
|
279
|
-
unselectedNameIsDim,
|
|
280
|
-
},
|
|
281
|
-
}),
|
|
282
|
-
);
|
|
283
|
-
```
|
|
284
|
-
|
|
285
|
-
where `bgCallsBeforeIntegration` is captured right after the unit section (`const bgCallsBeforeIntegration = bgCalls;`).
|
|
286
|
-
|
|
287
|
-
Add to `test/dashboard-selection.test.mjs`:
|
|
288
|
-
|
|
289
|
-
```js
|
|
290
|
-
test("renderRow paints the selected row from column 0 through the padded right edge (issue #231)", () => {
|
|
291
|
-
const parsed = runHarness();
|
|
292
|
-
const it = parsed.integration;
|
|
293
|
-
assert.equal(it.selectedStartsAtCol0, true, "bg must open before the folder indent");
|
|
294
|
-
assert.equal(it.selectedWidth, 160, "bg span must reach the padded right edge");
|
|
295
|
-
assert.equal(it.unselectedHasNoOpener, true, "unselected rows must have no bg");
|
|
296
|
-
assert.equal(it.selectedHasArrow, true, "the › cursor marker stays on the selected row");
|
|
297
|
-
assert.equal(it.unselectedHasNoArrow, true, "no › on unselected rows");
|
|
298
|
-
assert.equal(it.firstFrameBgCalls, 1, "exactly one row is painted per frame");
|
|
299
|
-
});
|
|
300
|
-
|
|
301
|
-
test("selected-row highlight coexists with select mode, peek, and narrow terminals (issue #231)", () => {
|
|
302
|
-
const parsed = runHarness();
|
|
303
|
-
const it = parsed.integration;
|
|
304
|
-
assert.equal(it.selectMarkerPresent, true, "select mode's ◉ batch marker survives");
|
|
305
|
-
assert.equal(it.selectHasBg, true, "select mode keeps the row highlight");
|
|
306
|
-
assert.equal(it.peekHasNoOpener, true, "peek header is not highlighted");
|
|
307
|
-
assert.ok(it.narrowMaxWidth <= 23, "no line may exceed the requested width");
|
|
308
|
-
assert.equal(it.narrowLeaked, false, "narrow clipping must not leak bg into other rows");
|
|
309
|
-
});
|
|
310
|
-
|
|
311
|
-
test("unselected rows keep muted name/summary, not dim (issue #231 decision D4)", () => {
|
|
312
|
-
const parsed = runHarness();
|
|
313
|
-
assert.equal(parsed.integration.unselectedUsesMuted, true);
|
|
314
|
-
assert.equal(parsed.integration.unselectedNameIsDim, false);
|
|
315
|
-
});
|
|
316
|
-
```
|
|
317
|
-
|
|
318
|
-
- [ ] **Step 2: Run the tests to verify they fail**
|
|
319
|
-
|
|
320
|
-
Run: `node --test test/dashboard-selection.test.mjs`
|
|
321
|
-
Expected: FAIL — `selectedStartsAtCol0` is false (no bg is emitted yet) and `firstFrameBgCalls` is 0.
|
|
322
|
-
|
|
323
|
-
- [ ] **Step 3: Wire `applyRowHighlight` into `renderRow`**
|
|
324
|
-
|
|
325
|
-
Replace the last return of `renderRow` (`src/ui/dashboard.ts:1481-1483`):
|
|
326
|
-
|
|
327
|
-
```ts
|
|
328
|
-
line = padTo(line, contentWidth - visibleWidth(ageRaw));
|
|
329
|
-
line += t.fg("dim", ageRaw);
|
|
330
|
-
const body = clip(line, contentWidth);
|
|
331
|
-
const painted = applyRowHighlight(t, selected, `${" ".repeat(indent)}${body}`);
|
|
332
|
-
return clip(painted, width);
|
|
333
|
-
```
|
|
334
|
-
|
|
335
|
-
Add the identity `bg` stub to each pre-existing harness theme literal, e.g.:
|
|
336
|
-
|
|
337
|
-
```ts
|
|
338
|
-
const theme = { fg: (_c: string, t: string) => t, bold: (t: string) => t, italic: (t: string) => t, strikethrough: (t: string) => t, bg: (_c: string, t: string) => t };
|
|
339
|
-
```
|
|
340
|
-
|
|
341
|
-
Apply the same one-line addition to the nine files listed in **Files** above (grep `fg: (` in `test-support/*.ts` to find each literal).
|
|
342
|
-
|
|
343
|
-
- [ ] **Step 4: Run the focused tests, then the full suite**
|
|
344
|
-
|
|
345
|
-
Run: `node --test test/dashboard-selection.test.mjs`
|
|
346
|
-
Expected: PASS (4 tests).
|
|
347
|
-
|
|
348
|
-
Run: `node --test test/dashboard-*.test.mjs test/ui-smoke.test.mjs`
|
|
349
|
-
Expected: PASS — this is where a missed `bg` stub would surface as `theme.bg is not a function`.
|
|
350
|
-
|
|
351
|
-
Run: `npm test`
|
|
352
|
-
Expected: PASS (no new failures; pre-existing environment failures, if any, must be identical to a clean `git stash` baseline).
|
|
353
|
-
|
|
354
|
-
- [ ] **Step 5: Commit**
|
|
355
|
-
|
|
356
|
-
```bash
|
|
357
|
-
git add src/ui/dashboard.ts test/dashboard-selection.test.mjs test-support/dashboard-selection-render.ts \
|
|
358
|
-
test-support/dashboard-delete-key-probe.ts test-support/cwd-browse-tab-probe.ts \
|
|
359
|
-
test-support/dashboard-shrink-render.ts test-support/dashboard-refs-render.ts \
|
|
360
|
-
test-support/dashboard-prewarm-smoke.ts test-support/navigation-wrap.ts \
|
|
361
|
-
test-support/hold-key-wrap.ts test-support/ui-smoke.ts test-support/attach-delete-flow-probe.ts
|
|
362
|
-
git commit -m "feat(ui): paint the selected board row with selectedBg (issue #231)"
|
|
363
|
-
```
|
|
364
|
-
|
|
365
|
-
---
|
|
366
|
-
|
|
367
|
-
### Task 3: Full verification gate
|
|
368
|
-
|
|
369
|
-
**Files:**
|
|
370
|
-
- No source changes expected. If `npm run verify` fails, fix in the owning file from Task 1/2 and amend the Task 2 commit rather than adding a new fixup commit.
|
|
371
|
-
|
|
372
|
-
**Interfaces:**
|
|
373
|
-
- Consumes: both earlier tasks.
|
|
374
|
-
- Produces: a branch whose `npm run verify` is green.
|
|
375
|
-
|
|
376
|
-
- [ ] **Step 1: Run the repo gate**
|
|
377
|
-
|
|
378
|
-
Run: `npm run verify`
|
|
379
|
-
Expected: `typecheck` clean, `test:perf` 3 passed, `npm test` green, `test:coverage` above lines 85 / funcs 80 / branches 70, `pack:dry` lists `src/ui/dashboard.ts`.
|
|
380
|
-
|
|
381
|
-
- [ ] **Step 2: Confirm the packed tarball still ships the touched file**
|
|
382
|
-
|
|
383
|
-
Run: `npm pack --dry-run 2>&1 | grep -E "src/ui/dashboard.ts|test-support"`
|
|
384
|
-
Expected: `src/ui/dashboard.ts` present; `test-support/` absent (it is not in `files`).
|
|
385
|
-
|
|
386
|
-
- [ ] **Step 3: Record the manual-check handoff**
|
|
387
|
-
|
|
388
|
-
Write the U1–U5 steps from the spec into the PR description (they need a live pi session and cannot run in CI). Do not claim them as passed.
|
|
389
|
-
|
|
390
|
-
- [ ] **Step 4: Commit any gate fixes (only if needed)**
|
|
391
|
-
|
|
392
|
-
```bash
|
|
393
|
-
git add -A -- src test test-support
|
|
394
|
-
git commit -m "test(ui): keep the selection harness green under the full gate (issue #231)"
|
|
395
|
-
```
|
|
396
|
-
|
|
397
|
-
---
|
|
398
|
-
|
|
399
|
-
## Self-Review
|
|
400
|
-
|
|
401
|
-
- **Spec coverage:** D1 → Task 1 + Task 2 A1/A2; D2 → A10 (Task 2); D3 → A3 (Task 2); D4 → A11 (Task 2). A1–A4, A6–A8, A10, A11 → Task 2; A5 → Task 1 (unit call count) + Task 2 (per-frame count); A9 → Task 3; U1–U5 → PR handoff (Task 3 Step 3). No spec requirement is unassigned.
|
|
402
|
-
- **Step scan:** each step names one file and one checkable result; no "handle edge cases" placeholders.
|
|
403
|
-
- **Type consistency:** `applyRowHighlight(theme, selected, text)` is defined and consumed with the same three parameters in both tasks; `BG_OPEN`/`BG_CLOSE` and the JSON field names used by the `.mjs` assertions match the harness emission.
|
|
404
|
-
- **Review Focus:** each of the five lines has an owning assertion (A8, A5-zero, A3, A6, A7/A5) in Task 2, plus U5 in the PR handoff.
|
|
405
|
-
- **Proportion:** the plan is shorter than the spec and contains only signatures, assertions and the one-line stub shape.
|