@guillaumemeyer/dsh-plan-approval 0.1.9 → 0.1.11
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/README.md +122 -47
- package/client.js +37 -63
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
#
|
|
1
|
+
# 🐋 DeepSeek Harness Enhanced Plan Mode
|
|
2
2
|
|
|
3
|
-
A fullscreen,
|
|
3
|
+
A fullscreen, **enhanced plan mode** for DeepSeek Harness (DSH).
|
|
4
4
|
|
|
5
5
|
It replaces the small in-chat plan review card with a full-screen review surface so
|
|
6
6
|
large plans are readable and navigable. It renders automatically when the agent
|
|
@@ -9,67 +9,125 @@ review contract.
|
|
|
9
9
|
|
|
10
10
|
## Features
|
|
11
11
|
|
|
12
|
-
- **Fullscreen review** in the frame-wide `shell.overlay` layer,
|
|
12
|
+
- **Fullscreen review** in the frame-wide `shell.overlay` layer, at **90vw × 90vh**.
|
|
13
13
|
- **Line navigation** (vim-style): `j`/`k` move line-by-line, `gg` jump to top,
|
|
14
14
|
`G` (Shift+g) jump to bottom, with a straight left highlight on the cursor line
|
|
15
15
|
that auto-scrolls.
|
|
16
|
-
- **Inline search**: `/` opens a search bar; matching lines highlight live, `Enter
|
|
17
|
-
|
|
16
|
+
- **Inline search**: `/` opens a search bar; matching lines highlight live, `Enter` cycles
|
|
17
|
+
to the next match and `Shift+Enter` to the previous, `Esc` closes (`n`/`N` also work once
|
|
18
|
+
the overlay re-takes focus).
|
|
18
19
|
- **Inline comments**: `c` (or **clicking a line**) drops a comment anchored to that
|
|
19
20
|
line, shown as an inline chip. Comments are **integrated into the plan**:
|
|
20
21
|
- On **request changes (`s`)** they're serialized into the feedback answer so the
|
|
21
22
|
agent revises the plan with them.
|
|
22
|
-
- On **approval (`a`)
|
|
23
|
-
|
|
23
|
+
- On **approval (`a`)** the plan is approved and plan mode exits; the merged plan
|
|
24
|
+
(comments included) is what's copied with `y`. Comments are not attached to an
|
|
25
|
+
approval — use `s` (request changes) to send them back for revision.
|
|
24
26
|
- **Actions**: `a` approve · `s` request changes · `y` copy plan · `q` quit plan.
|
|
25
27
|
- A bottom **status line** lists every keyboard shortcut.
|
|
26
28
|
|
|
27
|
-
## Install
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
29
|
+
## Install (into a DSH deployment)
|
|
30
|
+
|
|
31
|
+
This is a `dsh.client` browser plugin, so it must be installed and mounted where DSH
|
|
32
|
+
composes its **web surface** (host/web composition), then the host restarted so the
|
|
33
|
+
client-modules node half produces the bundle.
|
|
34
|
+
|
|
35
|
+
1. **Install the package** into the DSH deployment's web app:
|
|
36
|
+
|
|
37
|
+
```sh
|
|
38
|
+
npm i @guillaumemeyer/dsh-plan-approval
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
2. **Find the web-surface (browser roster) composition.** This is the `cordis.yml` /
|
|
42
|
+
`cordis.patch.yml` in the DSH web app that lists the other browser roster rows
|
|
43
|
+
(`ui-plan`, `ui-user-questions`, `ui-tool`, …). (The shipped `@deepseek-ai/dsh-web-app`
|
|
44
|
+
`cordis.patch.yml` is the reference for this "browser plugin roster" section.)
|
|
45
|
+
|
|
46
|
+
3. **Add one row** to that roster:
|
|
47
|
+
|
|
48
|
+
```yaml
|
|
49
|
+
- id: ui-plan-approval
|
|
50
|
+
name: '@guillaumemeyer/dsh-plan-approval'
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
4. **Restart the DSH host.** The `@deepseek-ai/dsh-client-modules` node half re-scans
|
|
54
|
+
loader entries for packages declaring `dsh.client`, bundles
|
|
55
|
+
`/plugins/@guillaumemeyer/dsh-plan-approval/client.js`, and the browser loads it.
|
|
56
|
+
(With a `pnpm run dev:web` watcher, the bundle is rebuilt automatically without a
|
|
57
|
+
restart; in a production/built deployment a host restart is required.)
|
|
58
|
+
|
|
59
|
+
> The row **must** live in the **web-surface `dsh.client` roster** (a
|
|
60
|
+
> `dsh.client` `platform: "web"` package). Placing it in an agent preset or a
|
|
61
|
+
> non-web composition will **not** be scanned into `window.__DSH_BOOT__`.
|
|
62
|
+
|
|
63
|
+
### Verify it's mounted
|
|
64
|
+
|
|
65
|
+
After restart, the package's browser bundle is served under
|
|
66
|
+
`/plugins/@guillaumemeyer/dsh-plan-approval/client.js`, and the plugin appears in the
|
|
67
|
+
`shell.overlay` slot.
|
|
68
|
+
|
|
69
|
+
## Use it from DSH
|
|
70
|
+
|
|
71
|
+
1. **Enter plan mode** — switch the agent into plan mode (the `/plan` command or the
|
|
72
|
+
session's plan control) and ask it to plan.
|
|
73
|
+
2. **Present a plan** — when the agent has the plan, it calls `exit_plan_mode` with the
|
|
74
|
+
plan markdown. The full-screen review overlay opens automatically.
|
|
75
|
+
3. **Review the plan**:
|
|
76
|
+
- **Navigate**: `j`/`k` move the cursor line, `gg` jump to top, `G` (Shift+g) jump to
|
|
77
|
+
bottom; the plan auto-scrolls to follow the cursor.
|
|
78
|
+
- **Search**: `/` opens the search bar — type to highlight matches, `Enter` to the
|
|
79
|
+
next match, `Shift+Enter` to the previous, `Esc` to close.
|
|
80
|
+
- **Comment**: `c` (or **click** any line) anchors an inline comment to that line;
|
|
81
|
+
type it and press `Enter` to add (`Esc` to cancel). Comments show as inline chips.
|
|
82
|
+
- **Copy**: `y` copies the plan, including any inline comments (the merged plan).
|
|
83
|
+
4. **Decide**:
|
|
84
|
+
- `a` **approve** the plan.
|
|
85
|
+
- `s` **request changes** — the feedback box opens; submit your requested changes
|
|
86
|
+
(plus any inline comments) → the agent keeps planning and revises the plan.
|
|
87
|
+
- `q` **quit plan** — cancel the review and go talk instead.
|
|
88
|
+
|
|
89
|
+
The status line at the bottom shows every shortcut: `a approve · s changes · c comment ·
|
|
90
|
+
y copy · q quit · j/k move · gg/G ends · / search`.
|
|
91
|
+
|
|
92
|
+
### Keyboard shortcuts
|
|
93
|
+
|
|
94
|
+
| Key | Action |
|
|
95
|
+
| --- | --- |
|
|
96
|
+
| `a` | Approve the plan |
|
|
97
|
+
| `s` | Request changes (opens feedback box; `Enter` submits) |
|
|
98
|
+
| `c` | Add an inline comment at the current line (`Enter` add, `Esc` cancel) |
|
|
99
|
+
| `y` | Copy the plan (merged with comments) |
|
|
100
|
+
| `q` | Quit / cancel the plan review |
|
|
101
|
+
| `j` / `k` | Move the cursor down / up one line |
|
|
102
|
+
| `gg` | Jump to top |
|
|
103
|
+
| `G` (Shift+g) | Jump to bottom |
|
|
104
|
+
| `/` | Open search (type to highlight; `Enter` next, `Shift+Enter` prev, `Esc` close) |
|
|
105
|
+
| `Esc` | Close the search/comment box, otherwise quit |
|
|
51
106
|
|
|
52
107
|
## How the review answers map
|
|
53
108
|
|
|
54
109
|
The underlying `exit_plan_mode` review is binary (approve vs. keep-planning). This
|
|
55
110
|
plugin keeps that contract and layers comments onto it:
|
|
56
111
|
|
|
57
|
-
- **`
|
|
58
|
-
the
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
The
|
|
72
|
-
|
|
112
|
+
- **`a` (approve)** always sends the plain approve answer (`{ selected: [approve] }`),
|
|
113
|
+
so the host leaves plan mode and the plan is carried out from the next step — whether
|
|
114
|
+
or not there are inline comments. Attaching the comments as `custom` on an approval
|
|
115
|
+
would make an unpatched host treat the approval as "keep planning" (keeping the
|
|
116
|
+
session in plan mode instead of switching to execution), so the approve path
|
|
117
|
+
deliberately keeps the answer clean.
|
|
118
|
+
- **`s` (request changes)** sends the typed feedback **plus** the inline comments as the
|
|
119
|
+
`custom` answer → the host keeps planning, and the agent revises the plan with your
|
|
120
|
+
comments. This is the path to use when you want your comments considered.
|
|
121
|
+
- The merged plan (plan + inline comments) is still available to copy with `y`.
|
|
122
|
+
|
|
123
|
+
## Host change: approve-with-notes (optional)
|
|
124
|
+
|
|
125
|
+
The shipped `@deepseek-ai/dsh-plan-mode` tool treats any `custom` value on the review
|
|
126
|
+
answer as "keep planning". The plugin's approve path keeps that contract by sending a
|
|
127
|
+
plain approval, so an approval always leaves plan mode and starts execution. If you
|
|
128
|
+
additionally want inline comments auto-attached to an approval, re-enable the
|
|
129
|
+
`custom` field in `approve()` (`client.js`) and patch the host's `exit_plan_mode` to
|
|
130
|
+
accept it:
|
|
73
131
|
|
|
74
132
|
```js
|
|
75
133
|
// before: if (... || item.custom !== void 0) { /* keep planning */ }
|
|
@@ -84,6 +142,23 @@ plus include it in the result `render` so the model sees the comments after appr
|
|
|
84
142
|
This requires a host restart to take effect, and is a change to a shipped package that
|
|
85
143
|
an upgrade would overwrite — ideally upstream it.
|
|
86
144
|
|
|
145
|
+
## Publishing updates
|
|
146
|
+
|
|
147
|
+
To publish a new version, ensure a local `.env` (untracked) holds your npm token, then:
|
|
148
|
+
|
|
149
|
+
```sh
|
|
150
|
+
cp .env.example .env # once: edit to add NPM_TOKEN=...
|
|
151
|
+
npm run release # runs publish:check (preversion), bumps patch, publishes to npm
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
- The token is read from `.env` and referenced by the committed `.npmrc` via `${NPM_TOKEN}`,
|
|
155
|
+
so it is never tracked.
|
|
156
|
+
- `preversion` runs `node --check client.js && npm pack --dry-run` automatically before
|
|
157
|
+
the version bump.
|
|
158
|
+
- The script is named `release` because `publish` is a reserved npm lifecycle name: npm
|
|
159
|
+
auto-runs a `publish` script during `npm publish`. Naming it `publish` made `npm publish`
|
|
160
|
+
re-run the script, bumping and publishing a version on every loop.
|
|
161
|
+
|
|
87
162
|
## Notes
|
|
88
163
|
|
|
89
164
|
- Client-only browser plugin; the node half (`index.js`) is an empty `apply`.
|
package/client.js
CHANGED
|
@@ -12,6 +12,7 @@ window.__ModuleLoader__.load({
|
|
|
12
12
|
.prv-title{font-size:17px;font-weight:600;margin:0;line-height:1.3;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
|
|
13
13
|
.prv-search{display:flex;align-items:center;gap:10px;padding:8px 22px;border-bottom:1px solid var(--dsw-alias-border-l1);flex-shrink:0;}
|
|
14
14
|
.prv-search-input{flex:1 1 auto;background:var(--dsw-alias-bg-overlay);color:var(--dsw-alias-label-primary);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:6px 10px;font-size:13px;line-height:18px;}
|
|
15
|
+
.prv-search-input::placeholder{color:var(--dsw-alias-label-secondary);opacity:1;}
|
|
15
16
|
.prv-search-count{font-size:12px;color:var(--dsw-alias-label-secondary);white-space:nowrap;}
|
|
16
17
|
.prv-body{flex:1 1 auto;min-height:0;overflow-y:auto;padding:18px 26px 26px;overscroll-behavior:contain;position:relative;}
|
|
17
18
|
.prv-line{font-size:14px;line-height:1.7;padding:0 6px;border-left:2px solid transparent;}
|
|
@@ -30,6 +31,7 @@ window.__ModuleLoader__.load({
|
|
|
30
31
|
.prv-comment-field{margin:6px 0 2px;display:block;}
|
|
31
32
|
.prv-field{flex-shrink:0;padding:12px 22px;border-top:1px solid var(--dsw-alias-border-l1);}
|
|
32
33
|
.prv-input{width:100%;box-sizing:border-box;background:var(--dsw-alias-bg-overlay);color:var(--dsw-alias-label-primary);border:1px solid var(--dsw-alias-border-l2);border-radius:10px;padding:10px 12px;font-size:14px;line-height:1.5;resize:vertical;min-height:64px;}
|
|
34
|
+
.prv-input::placeholder{color:var(--dsw-alias-label-secondary);opacity:1;}
|
|
33
35
|
.prv-fieldActions{display:flex;gap:8px;justify-content:flex-end;margin-top:8px;}
|
|
34
36
|
.prv-footer{flex-shrink:0;display:flex;align-items:center;gap:14px;padding:12px 22px;border-top:1px solid var(--dsw-alias-border-l1);flex-wrap:wrap;}
|
|
35
37
|
.prv-status{font-size:12px;line-height:16px;color:var(--dsw-alias-label-secondary);min-width:150px;}
|
|
@@ -54,46 +56,6 @@ window.__ModuleLoader__.load({
|
|
|
54
56
|
document.head.appendChild(tag);
|
|
55
57
|
}
|
|
56
58
|
|
|
57
|
-
const OVERLAY_CSS = `.prv-root{position:fixed;inset:0;z-index:2147480000;background:rgba(8,10,16,0.62);display:flex;align-items:center;justify-content:center;pointer-events:auto;font-family:inherit;}
|
|
58
|
-
.prv-panel{width:90vw;height:90vh;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);border:1px solid var(--dsw-alias-border-l2);border-radius:18px;box-shadow:0 36px 90px rgba(0,0,0,0.55);display:flex;flex-direction:column;overflow:hidden;}
|
|
59
|
-
.prv-header{display:flex;align-items:center;gap:12px;padding:16px 22px;border-bottom:1px solid var(--dsw-alias-border-l1);flex-shrink:0;}
|
|
60
|
-
.prv-badge{background:var(--dsw-alias-state-warn-primary);color:var(--dsw-alias-bg-base);font-size:11px;font-weight:600;letter-spacing:.05em;text-transform:uppercase;padding:3px 9px;border-radius:999px;}
|
|
61
|
-
.prv-title{font-size:17px;font-weight:600;margin:0;line-height:1.3;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
|
|
62
|
-
.prv-search{display:flex;align-items:center;gap:10px;padding:8px 22px;border-bottom:1px solid var(--dsw-alias-border-l1);flex-shrink:0;}
|
|
63
|
-
.prv-search-input{flex:1 1 auto;background:var(--dsw-alias-bg-overlay);color:var(--dsw-alias-label-primary);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:6px 10px;font-size:13px;line-height:18px;}
|
|
64
|
-
.prv-search-count{font-size:12px;color:var(--dsw-alias-label-secondary);white-space:nowrap;}
|
|
65
|
-
.prv-body{flex:1 1 auto;min-height:0;overflow-y:auto;padding:18px 26px 26px;overscroll-behavior:contain;position:relative;}
|
|
66
|
-
.prv-line{font-size:14px;line-height:1.7;padding:0 6px;border-left:2px solid transparent;}
|
|
67
|
-
.prv-line-current{background:var(--dsw-alias-bg-layer-2);border-left-color:var(--dsw-alias-brand-primary);}
|
|
68
|
-
.prv-line-match{background:var(--dsw-alias-bg-overlay);}
|
|
69
|
-
.prv-line-match-active{background:var(--dsw-alias-state-warn-primary);color:var(--dsw-alias-bg-base);}
|
|
70
|
-
.prv-plain{white-space:pre-wrap;}
|
|
71
|
-
.prv-h1{font-size:22px;font-weight:700;margin:8px 0 6px;line-height:1.3;}
|
|
72
|
-
.prv-h2{font-size:18px;font-weight:600;margin:12px 0 4px;line-height:1.3;}
|
|
73
|
-
.prv-h3{font-size:15px;font-weight:600;margin:10px 0 4px;line-height:1.3;}
|
|
74
|
-
.prv-h4{font-size:14px;font-weight:600;margin:8px 0 4px;line-height:1.3;}
|
|
75
|
-
.prv-li{font-size:14px;line-height:1.65;margin:0 0 4px;padding-left:16px;position:relative;}
|
|
76
|
-
.prv-li:before{content:"•";position:absolute;left:6px;color:var(--dsw-alias-label-secondary);}
|
|
77
|
-
.prv-blank{height:10px;}
|
|
78
|
-
.prv-comment{display:inline-block;margin:0 4px;padding:1px 7px;border-radius:999px;background:var(--dsw-alias-bg-overlay);color:var(--dsw-alias-state-warn-primary);font-size:12px;line-height:16px;vertical-align:middle;white-space:normal;}
|
|
79
|
-
.prv-comment-field{margin:6px 0 2px;display:block;}
|
|
80
|
-
.prv-field{flex-shrink:0;padding:12px 22px;border-top:1px solid var(--dsw-alias-border-l1);}
|
|
81
|
-
.prv-input{width:100%;box-sizing:border-box;background:var(--dsw-alias-bg-overlay);color:var(--dsw-alias-label-primary);border:1px solid var(--dsw-alias-border-l2);border-radius:10px;padding:10px 12px;font-size:14px;line-height:1.5;resize:vertical;min-height:64px;}
|
|
82
|
-
.prv-fieldActions{display:flex;gap:8px;justify-content:flex-end;margin-top:8px;}
|
|
83
|
-
.prv-footer{flex-shrink:0;display:flex;align-items:center;gap:14px;padding:12px 22px;border-top:1px solid var(--dsw-alias-border-l1);flex-wrap:wrap;}
|
|
84
|
-
.prv-status{font-size:12px;line-height:16px;color:var(--dsw-alias-label-secondary);min-width:150px;}
|
|
85
|
-
.prv-status.error{color:var(--dsw-alias-state-error-primary);}
|
|
86
|
-
.prv-status.ok{color:var(--dsw-alias-state-success-primary);}
|
|
87
|
-
.prv-hints{display:flex;align-items:center;gap:2px;flex-wrap:wrap;font-size:12px;color:var(--dsw-alias-label-secondary);}
|
|
88
|
-
.prv-hints kbd{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:5px;padding:1px 6px;font-family:inherit;font-size:11px;font-weight:600;color:var(--dsw-alias-label-primary);margin:0 3px 0 8px;}
|
|
89
|
-
.prv-actions{display:flex;gap:8px;margin-left:auto;}
|
|
90
|
-
.prv-btn{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);border-radius:999px;padding:7px 14px;font-size:13px;font-weight:500;cursor:pointer;line-height:18px;}
|
|
91
|
-
.prv-btn:hover:not(:disabled){background:var(--dsw-alias-bg-layer-1);}
|
|
92
|
-
.prv-btn:disabled{opacity:.55;cursor:default;}
|
|
93
|
-
.prv-btn.warn{border-color:var(--dsw-alias-state-warn-primary);color:var(--dsw-alias-state-warn-primary);}
|
|
94
|
-
.prv-btn.primary{background:var(--dsw-alias-state-success-primary);border-color:var(--dsw-alias-state-success-primary);color:var(--dsw-alias-bg-base);}
|
|
95
|
-
.prv-btn.ghost{border-color:transparent;background:transparent;color:var(--dsw-alias-label-secondary);}
|
|
96
|
-
.prv-btn.ghost:hover:not(:disabled){background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);}`;
|
|
97
59
|
|
|
98
60
|
function planReviewOf(questions) {
|
|
99
61
|
if (!questions || questions.length !== 1) return void 0;
|
|
@@ -105,7 +67,7 @@ function planReviewOf(questions) {
|
|
|
105
67
|
if (options.length > 2) return void 0;
|
|
106
68
|
const approve = options.find((o) => o.label === intent.approve);
|
|
107
69
|
if (approve === void 0) return void 0;
|
|
108
|
-
return { id: question.id, question: question.question, plan: question.detail,
|
|
70
|
+
return { id: question.id, question: question.question, plan: question.detail, approveOption: approve };
|
|
109
71
|
}
|
|
110
72
|
|
|
111
73
|
function parsePlan(plan) {
|
|
@@ -127,11 +89,8 @@ const PLAN_HINTS = [
|
|
|
127
89
|
['j/k', 'move'], ['gg/G', 'ends'], ['/', 'search'],
|
|
128
90
|
];
|
|
129
91
|
|
|
130
|
-
function
|
|
131
|
-
const sessions = props.sessions;
|
|
132
|
-
const current = props.useSessions((s) => s.current);
|
|
92
|
+
function usePlanSession(sessions, current) {
|
|
133
93
|
const [snap, setSnap] = react.useState(null);
|
|
134
|
-
|
|
135
94
|
react.useEffect(() => {
|
|
136
95
|
if (!sessions || !current) { setSnap(null); return void 0; }
|
|
137
96
|
const bind = sessions.binding(current);
|
|
@@ -141,6 +100,13 @@ function PlanReviewOverlay(props) {
|
|
|
141
100
|
update();
|
|
142
101
|
return face.subscribe(update);
|
|
143
102
|
}, [sessions, current]);
|
|
103
|
+
return snap;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function PlanReviewOverlay(props) {
|
|
107
|
+
const sessions = props.sessions;
|
|
108
|
+
const current = props.useSessions((s) => s.current);
|
|
109
|
+
const snap = usePlanSession(sessions, current);
|
|
144
110
|
|
|
145
111
|
const [mode, setMode] = react.useState(null);
|
|
146
112
|
const [text, setText] = react.useState('');
|
|
@@ -178,7 +144,12 @@ function PlanReviewOverlay(props) {
|
|
|
178
144
|
}
|
|
179
145
|
}
|
|
180
146
|
const reviewKey = wait ? wait.key : null;
|
|
181
|
-
const planLines = review ? parsePlan(review.plan) : [];
|
|
147
|
+
const planLines = react.useMemo(() => (review ? parsePlan(review.plan) : []), [review ? review.plan : null]);
|
|
148
|
+
const commentsByLine = react.useMemo(() => {
|
|
149
|
+
const byLine = {};
|
|
150
|
+
for (const c of comments) { (byLine[c.line] || (byLine[c.line] = [])).push(c); }
|
|
151
|
+
return byLine;
|
|
152
|
+
}, [comments]);
|
|
182
153
|
|
|
183
154
|
react.useEffect(() => {
|
|
184
155
|
setMode(null); setText(''); setError(null); setNotice(null); setBusy(false);
|
|
@@ -198,24 +169,25 @@ function PlanReviewOverlay(props) {
|
|
|
198
169
|
|
|
199
170
|
if (!review || !wait) return null;
|
|
200
171
|
|
|
201
|
-
const
|
|
202
|
-
if (busy) return;
|
|
203
|
-
setBusy(true); setError(null); setNotice(null);
|
|
204
|
-
wait.respond({ ok: true, value: { sessionId: wait.sessionId, answer: payload } })
|
|
205
|
-
.then((receipt) => { if (!receipt || !receipt.accepted) throw new Error('response rejected'); })
|
|
206
|
-
.catch((cause) => { setBusy(false); setError(cause instanceof Error ? cause.message : String(cause)); });
|
|
207
|
-
};
|
|
208
|
-
const submitCancel = () => {
|
|
172
|
+
const send = (payload, rejectMessage = 'response rejected') => {
|
|
209
173
|
if (busy) return;
|
|
210
174
|
setBusy(true); setError(null); setNotice(null);
|
|
211
|
-
wait.respond(
|
|
212
|
-
.then((receipt) => { if (!receipt || !receipt.accepted) throw new Error(
|
|
175
|
+
wait.respond(payload)
|
|
176
|
+
.then((receipt) => { if (!receipt || !receipt.accepted) throw new Error(rejectMessage); })
|
|
213
177
|
.catch((cause) => { setBusy(false); setError(cause instanceof Error ? cause.message : String(cause)); });
|
|
214
178
|
};
|
|
179
|
+
const submitAnswer = (payload) => send(
|
|
180
|
+
{ ok: true, value: { sessionId: wait.sessionId, answer: payload } }, 'response rejected');
|
|
181
|
+
const submitCancel = () => send(
|
|
182
|
+
{ ok: false, error: { code: 'cancelled', message: 'the user closed this question request', details: {} } }, 'cancellation rejected');
|
|
215
183
|
|
|
216
184
|
const approve = () => {
|
|
217
|
-
|
|
218
|
-
|
|
185
|
+
// Always send a plain approval. Attaching the comments as `custom` here would
|
|
186
|
+
// make an unpatched host (dsh-plan-mode) treat this as "keep planning",
|
|
187
|
+
// leaving the session in plan mode instead of switching to execution. Use
|
|
188
|
+
// `s` (request changes) to send comments back for revision; use `y` to copy
|
|
189
|
+
// the plan with comments for your own reference.
|
|
190
|
+
submitAnswer({ answers: [{ id: review.id, selected: [review.approveOption.label] }] });
|
|
219
191
|
};
|
|
220
192
|
const quit = () => submitCancel();
|
|
221
193
|
const copyPlan = () => {
|
|
@@ -275,7 +247,8 @@ function PlanReviewOverlay(props) {
|
|
|
275
247
|
setCursor(matches[prev]);
|
|
276
248
|
};
|
|
277
249
|
const onSearchKey = (e) => {
|
|
278
|
-
if (e.key === 'Enter') { e.preventDefault();
|
|
250
|
+
if (e.key === 'Enter' && e.shiftKey) { e.preventDefault(); searchPrev(); }
|
|
251
|
+
else if (e.key === 'Enter') { e.preventDefault(); searchNext(); }
|
|
279
252
|
else if (e.key === 'Escape') { e.preventDefault(); closeSearch(); }
|
|
280
253
|
};
|
|
281
254
|
|
|
@@ -311,8 +284,8 @@ function PlanReviewOverlay(props) {
|
|
|
311
284
|
if (busy) return;
|
|
312
285
|
const rawKey = e.key || '';
|
|
313
286
|
const key = rawKey.toLowerCase();
|
|
314
|
-
if (rawKey === 'G') { e.preventDefault(); goBottom(); return; }
|
|
315
|
-
if (rawKey === 'N') { e.preventDefault(); searchPrev(); return; }
|
|
287
|
+
if (rawKey === 'G') { e.preventDefault(); ggArmed.current = false; goBottom(); return; }
|
|
288
|
+
if (rawKey === 'N') { e.preventDefault(); ggArmed.current = false; searchPrev(); return; }
|
|
316
289
|
if (key === 'g') {
|
|
317
290
|
e.preventDefault();
|
|
318
291
|
if (ggArmed.current) { ggArmed.current = false; goTop(); }
|
|
@@ -328,7 +301,8 @@ function PlanReviewOverlay(props) {
|
|
|
328
301
|
else if (key === 'a') { e.preventDefault(); approve(); }
|
|
329
302
|
else if (key === 's') { e.preventDefault(); openInput('changes'); }
|
|
330
303
|
else if (key === 'y') { e.preventDefault(); copyPlan(); }
|
|
331
|
-
else if (key === 'q'
|
|
304
|
+
else if (key === 'q') { e.preventDefault(); quit(); }
|
|
305
|
+
else if (key === 'escape') {
|
|
332
306
|
e.preventDefault();
|
|
333
307
|
if (searchOpen) closeSearch();
|
|
334
308
|
else if (commentDraft) cancelComment();
|
|
@@ -353,7 +327,7 @@ function PlanReviewOverlay(props) {
|
|
|
353
327
|
(i === cursor ? ' prv-line-current' : '') +
|
|
354
328
|
(matchSet.has(i) ? ' prv-line-match' : '') +
|
|
355
329
|
(i === activeLine ? ' prv-line-match-active' : '');
|
|
356
|
-
const lineComments =
|
|
330
|
+
const lineComments = commentsByLine[i] || [];
|
|
357
331
|
const isComposing = commentDraft && commentLine === i;
|
|
358
332
|
const children = [];
|
|
359
333
|
if (line.kind !== 'blank') children.push(line.text);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@guillaumemeyer/dsh-plan-approval",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.11",
|
|
4
4
|
"description": "Fullscreen, Grok-Build-style DSH plan-review overlay with vim navigation, inline search, inline comments, and comment integration into the plan.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"access": "public"
|
|
33
33
|
},
|
|
34
34
|
"scripts": {
|
|
35
|
-
"
|
|
35
|
+
"release": "node scripts/publish.mjs",
|
|
36
36
|
"publish:dry": "npm pack --dry-run",
|
|
37
37
|
"publish:check": "node --check client.js && npm pack --dry-run",
|
|
38
38
|
"preversion": "npm run publish:check"
|