poly-weaver 0.11.4 → 0.11.5
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/dist/agents/reviewers/handler.d.ts.map +1 -1
- package/dist/agents/reviewers/handler.js +6 -1
- package/dist/agents/reviewers/handler.js.map +1 -1
- package/dist/agents/reviewers/sanity-inject.d.ts +7 -0
- package/dist/agents/reviewers/sanity-inject.d.ts.map +1 -0
- package/dist/agents/reviewers/sanity-inject.js +22 -0
- package/dist/agents/reviewers/sanity-inject.js.map +1 -0
- package/dist/auto-update/tui.d.ts +1 -1
- package/dist/auto-update/tui.d.ts.map +1 -1
- package/dist/auto-update/tui.js +3 -2
- package/dist/auto-update/tui.js.map +1 -1
- package/dist/flow/executor.d.ts +2 -2
- package/dist/flow/executor.d.ts.map +1 -1
- package/dist/flow/executor.js +30 -7
- package/dist/flow/executor.js.map +1 -1
- package/dist/flow-editor/tui.d.ts.map +1 -1
- package/dist/flow-editor/tui.js +4 -4
- package/dist/flow-editor/tui.js.map +1 -1
- package/dist/providers/claude/completion-plan-mode.d.ts +3 -2
- package/dist/providers/claude/completion-plan-mode.d.ts.map +1 -1
- package/dist/providers/claude/completion-plan-mode.js +13 -11
- package/dist/providers/claude/completion-plan-mode.js.map +1 -1
- package/dist/providers/claude/plan-mode-pty-monitor.d.ts +9 -6
- package/dist/providers/claude/plan-mode-pty-monitor.d.ts.map +1 -1
- package/dist/providers/claude/plan-mode-pty-monitor.js +15 -6
- package/dist/providers/claude/plan-mode-pty-monitor.js.map +1 -1
- package/dist/resume-tui.d.ts +1 -1
- package/dist/resume-tui.d.ts.map +1 -1
- package/dist/resume-tui.js +3 -1
- package/dist/resume-tui.js.map +1 -1
- package/dist/session/manifest.d.ts +3 -1
- package/dist/session/manifest.d.ts.map +1 -1
- package/dist/session/restore.d.ts.map +1 -1
- package/dist/session/restore.js +2 -1
- package/dist/session/restore.js.map +1 -1
- package/dist/session/session-store.d.ts +8 -0
- package/dist/session/session-store.d.ts.map +1 -1
- package/dist/session/session-store.js +18 -1
- package/dist/session/session-store.js.map +1 -1
- package/dist/startup-tui.d.ts.map +1 -1
- package/dist/startup-tui.js +2 -2
- package/dist/startup-tui.js.map +1 -1
- package/dist/text-editing/emacs-input.d.ts +5 -0
- package/dist/text-editing/emacs-input.d.ts.map +1 -1
- package/dist/text-editing/emacs-input.js +11 -0
- package/dist/text-editing/emacs-input.js.map +1 -1
- package/dist/user/feedback-editing.d.ts +28 -0
- package/dist/user/feedback-editing.d.ts.map +1 -0
- package/dist/user/feedback-editing.js +72 -0
- package/dist/user/feedback-editing.js.map +1 -0
- package/dist/user/handler.d.ts.map +1 -1
- package/dist/user/handler.js +3 -2
- package/dist/user/handler.js.map +1 -1
- package/dist/user/host-curate-prompt.d.ts +2 -6
- package/dist/user/host-curate-prompt.d.ts.map +1 -1
- package/dist/user/host-curate-prompt.js +10 -74
- package/dist/user/host-curate-prompt.js.map +1 -1
- package/dist/user/host-prompt.d.ts +4 -13
- package/dist/user/host-prompt.d.ts.map +1 -1
- package/dist/user/host-prompt.js +407 -302
- package/dist/user/host-prompt.js.map +1 -1
- package/dist/user/prompt.d.ts +1 -1
- package/dist/user/prompt.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/user/host-prompt.js
CHANGED
|
@@ -1,35 +1,28 @@
|
|
|
1
|
-
import { bold, dim, green,
|
|
1
|
+
import { bold, dim, green, reset, visibleLength, yellow, } from "../ansi.js";
|
|
2
2
|
import { AgentInterruptedError } from "../agents/runner.js";
|
|
3
3
|
import { EmacsInputParser } from "./prompt.js";
|
|
4
|
-
import { clipToWidth } from "./curate-prompt.js";
|
|
4
|
+
import { clipToWidth, wrapToWidth } from "./curate-prompt.js";
|
|
5
5
|
import { buildPlanPane } from "./plan-pane.js";
|
|
6
6
|
import { renderShortcutHints } from "./shortcut-hints.js";
|
|
7
|
-
import {
|
|
8
|
-
import { cursorToVisual, renderWrappedLine, softWrapLine } from "../text-editing/soft-wrap.js";
|
|
7
|
+
import { aliasSelectionNavEvent, isTextBufferEvent, } from "../text-editing/emacs-input.js";
|
|
9
8
|
import { OffscreenController, } from "../offscreen/controller.js";
|
|
10
9
|
import { AttachmentManager } from "../paste-image/attachment-manager.js";
|
|
11
10
|
import { ALT_V_PASTE_IMAGE_HINT, handleBracketedPaste, handleImagePasteIntent, PASTING_IMAGE_HINT, } from "../paste-image/intents.js";
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
import { buildSplitSurface, computeSplitMetrics, SPLIT_COLLAPSE_AT_COLS, } from "./split-surface.js";
|
|
12
|
+
import { applyEditingBufferEvent, applyEditInsertedText, bufferFromText, emptyBuffer, finalizeEditText, renderMultilineEditor, } from "./feedback-editing.js";
|
|
13
|
+
// Max rows allotted to the scrollable feedback-list block (Approve/item/+Add
|
|
14
|
+
// rows only; the separator + hint are fixed chrome rendered outside this
|
|
15
|
+
// budget). This same constant is both the stacked-layout scroll budget and the
|
|
16
|
+
// wide-terminal split trigger.
|
|
17
|
+
export const FEEDBACK_AREA_MAX_ROWS = 10;
|
|
15
18
|
/** Lines of overlap when paging the plan (PageUp/PageDown). */
|
|
16
19
|
const PLAN_PAGE_OVERLAP = 1;
|
|
17
|
-
const
|
|
20
|
+
const ACTIVE_SELECTOR = "❯";
|
|
18
21
|
const INACTIVE_PREFIX = " ";
|
|
19
22
|
/**
|
|
20
|
-
* Host-backed user-review prompt
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* a controls block (Approve + bounded feedback editor + compact hint row).
|
|
24
|
-
*
|
|
25
|
-
* Key handling:
|
|
26
|
-
* - ↑/↓ toggles between Approve and the editor (does NOT scroll the plan).
|
|
27
|
-
* - PageUp/PageDown/Home/End/wheel scrolls the plan.
|
|
28
|
-
* - Ctrl+D submits feedback (or accepts when on Approve).
|
|
29
|
-
* - Ctrl+C quits poly-weaver (exit code 130). When the feedback editor has
|
|
30
|
-
* typed text, the first Ctrl+C clears the buffer instead; a second Ctrl+C
|
|
31
|
-
* on an empty buffer quits.
|
|
32
|
-
* - Ctrl+] is consumed by the host's `InputRouter` for dump capture.
|
|
23
|
+
* Host-backed user-review prompt. The review controls use the same multi-item
|
|
24
|
+
* feedback model as verdict curation: add, edit, delete, then submit the list
|
|
25
|
+
* as discrete feedback items.
|
|
33
26
|
*/
|
|
34
27
|
export class HostBackedUserPrompt {
|
|
35
28
|
host;
|
|
@@ -40,23 +33,19 @@ export class HostBackedUserPrompt {
|
|
|
40
33
|
if (!this.host.isInteractive) {
|
|
41
34
|
return { accepted: true };
|
|
42
35
|
}
|
|
36
|
+
const state = {
|
|
37
|
+
cursor: 1,
|
|
38
|
+
userAdded: [],
|
|
39
|
+
editing: undefined,
|
|
40
|
+
planScrollTop: 0,
|
|
41
|
+
planAtEnd: false,
|
|
42
|
+
};
|
|
43
|
+
const parser = new EmacsInputParser();
|
|
43
44
|
return new Promise((resolve, reject) => {
|
|
44
|
-
const state = {
|
|
45
|
-
mode: "input",
|
|
46
|
-
lines: [""],
|
|
47
|
-
cursorLine: 0,
|
|
48
|
-
cursorCol: 0,
|
|
49
|
-
feedbackScrollTop: 0,
|
|
50
|
-
planScrollTop: 0,
|
|
51
|
-
planAtEnd: false,
|
|
52
|
-
transientHint: null,
|
|
53
|
-
};
|
|
54
|
-
const parser = new EmacsInputParser();
|
|
55
|
-
const attachments = new AttachmentManager();
|
|
56
45
|
let detach;
|
|
57
46
|
let offscreenController;
|
|
58
47
|
let active = true;
|
|
59
|
-
let
|
|
48
|
+
let settlingEdit = false;
|
|
60
49
|
const cleanup = () => {
|
|
61
50
|
active = false;
|
|
62
51
|
offscreenController?.dispose();
|
|
@@ -72,185 +61,255 @@ export class HostBackedUserPrompt {
|
|
|
72
61
|
cleanup();
|
|
73
62
|
reject(err);
|
|
74
63
|
};
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
64
|
+
const submitFeedback = () => {
|
|
65
|
+
finish({ accepted: false, feedback: [...state.userAdded] });
|
|
66
|
+
};
|
|
67
|
+
const cancelEdit = () => {
|
|
68
|
+
if (settlingEdit || !state.editing)
|
|
69
|
+
return;
|
|
70
|
+
state.editing = undefined;
|
|
71
|
+
clampCursor(state);
|
|
72
|
+
};
|
|
73
|
+
const applySubmittedEdit = (editing, text) => {
|
|
74
|
+
state.editing = undefined;
|
|
75
|
+
if (editing.target.kind === "user-edit") {
|
|
76
|
+
const index = editing.target.index;
|
|
77
|
+
if (text.length > 0) {
|
|
78
|
+
state.userAdded[index] = text;
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
state.userAdded.splice(index, 1);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
else if (text.length > 0) {
|
|
85
|
+
state.userAdded.push(text);
|
|
86
|
+
}
|
|
87
|
+
state.cursor = addRowCursorIndex(state);
|
|
88
|
+
clampCursor(state);
|
|
80
89
|
};
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
90
|
+
const submitEdit = () => {
|
|
91
|
+
const editing = state.editing;
|
|
92
|
+
if (!editing || settlingEdit)
|
|
93
|
+
return;
|
|
94
|
+
const text = editing.buffer.lines.join("\n").trim();
|
|
95
|
+
if (editing.attachments.snapshot().length === 0) {
|
|
96
|
+
applySubmittedEdit(editing, text);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
settlingEdit = true;
|
|
100
|
+
void finalizeEditText(editing, text, context.persistentDir).then((finalText) => {
|
|
101
|
+
if (!active || state.editing !== editing)
|
|
102
|
+
return;
|
|
103
|
+
applySubmittedEdit(editing, finalText);
|
|
104
|
+
settlingEdit = false;
|
|
105
|
+
this.host.scheduleParentUiRender();
|
|
85
106
|
}).catch((err) => {
|
|
107
|
+
settlingEdit = false;
|
|
86
108
|
failWith(err instanceof Error ? err : new Error(String(err)));
|
|
87
109
|
});
|
|
88
110
|
};
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
111
|
+
const openUserAddEditor = () => {
|
|
112
|
+
state.cursor = addRowCursorIndex(state);
|
|
113
|
+
state.editing = {
|
|
114
|
+
target: { kind: "user-add" },
|
|
115
|
+
buffer: emptyBuffer(),
|
|
116
|
+
attachments: new AttachmentManager(),
|
|
117
|
+
transientHint: null,
|
|
118
|
+
};
|
|
95
119
|
};
|
|
96
|
-
const
|
|
97
|
-
|
|
120
|
+
const openUserEditEditor = (index) => {
|
|
121
|
+
state.cursor = index;
|
|
122
|
+
state.editing = {
|
|
123
|
+
target: { kind: "user-edit", index },
|
|
124
|
+
buffer: bufferFromText(state.userAdded[index] ?? ""),
|
|
125
|
+
attachments: new AttachmentManager(),
|
|
126
|
+
transientHint: null,
|
|
127
|
+
};
|
|
128
|
+
};
|
|
129
|
+
const applyEditPasteOutcome = (editing, outcome) => {
|
|
130
|
+
if (!active || settlingEdit || state.editing !== editing)
|
|
98
131
|
return;
|
|
99
|
-
|
|
132
|
+
editing.transientHint = outcome.hint ?? null;
|
|
100
133
|
if (outcome.inserted !== undefined)
|
|
101
|
-
|
|
134
|
+
applyEditInsertedText(editing, outcome.inserted);
|
|
102
135
|
this.host.scheduleParentUiRender();
|
|
103
136
|
};
|
|
104
|
-
const
|
|
105
|
-
void task.then(
|
|
106
|
-
if (!active ||
|
|
137
|
+
const launchEditPasteIntent = (editing, task) => {
|
|
138
|
+
void task.then((outcome) => applyEditPasteOutcome(editing, outcome)).catch((err) => {
|
|
139
|
+
if (!active || settlingEdit || state.editing !== editing)
|
|
107
140
|
return;
|
|
108
|
-
|
|
141
|
+
editing.transientHint = err instanceof Error ? err.message : String(err);
|
|
109
142
|
this.host.scheduleParentUiRender();
|
|
110
143
|
});
|
|
111
144
|
};
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
145
|
+
const processEvents = (events) => {
|
|
146
|
+
let dirty = false;
|
|
147
|
+
let finished = false;
|
|
148
|
+
for (const rawEvent of events) {
|
|
149
|
+
if (finished)
|
|
150
|
+
break;
|
|
151
|
+
const event = state.editing
|
|
152
|
+
? rawEvent
|
|
153
|
+
: aliasSelectionNavEvent(rawEvent);
|
|
154
|
+
if (state.editing) {
|
|
155
|
+
if (settlingEdit) {
|
|
156
|
+
dirty = true;
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
if (state.editing.transientHint) {
|
|
160
|
+
state.editing.transientHint = null;
|
|
127
161
|
dirty = true;
|
|
128
162
|
}
|
|
129
163
|
if (event.type === "paste") {
|
|
130
|
-
|
|
164
|
+
const editing = state.editing;
|
|
165
|
+
launchEditPasteIntent(editing, handleBracketedPaste(event.content, editing.attachments));
|
|
131
166
|
continue;
|
|
132
167
|
}
|
|
133
168
|
if (event.type === "image-paste") {
|
|
134
|
-
|
|
135
|
-
|
|
169
|
+
const editing = state.editing;
|
|
170
|
+
launchEditPasteIntent(editing, handleImagePasteIntent(editing.attachments, () => {
|
|
171
|
+
if (!active || settlingEdit || state.editing !== editing)
|
|
136
172
|
return;
|
|
137
|
-
|
|
173
|
+
editing.transientHint = PASTING_IMAGE_HINT;
|
|
138
174
|
this.host.scheduleParentUiRender();
|
|
139
175
|
}));
|
|
140
176
|
continue;
|
|
141
177
|
}
|
|
142
|
-
if (event.type === "ctrl-c") {
|
|
143
|
-
|
|
144
|
-
if (bufferEmpty) {
|
|
145
|
-
failWith(new AgentInterruptedError("Interrupted"));
|
|
146
|
-
return true;
|
|
147
|
-
}
|
|
148
|
-
state.lines = [""];
|
|
149
|
-
state.cursorLine = 0;
|
|
150
|
-
state.cursorCol = 0;
|
|
151
|
-
state.feedbackScrollTop = 0;
|
|
152
|
-
state.mode = "input";
|
|
178
|
+
if (event.type === "ctrl-c" || event.type === "escape") {
|
|
179
|
+
cancelEdit();
|
|
153
180
|
dirty = true;
|
|
154
181
|
continue;
|
|
155
182
|
}
|
|
156
|
-
if (event.type === "ctrl-d") {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
return true;
|
|
160
|
-
}
|
|
161
|
-
const text = state.lines.join("\n").trim();
|
|
162
|
-
if (text.length > 0) {
|
|
163
|
-
submitFeedback(text);
|
|
164
|
-
return true;
|
|
165
|
-
}
|
|
166
|
-
continue;
|
|
167
|
-
}
|
|
168
|
-
if (event.type === "enter") {
|
|
169
|
-
if (state.mode === "approve") {
|
|
170
|
-
finish({ accepted: true });
|
|
171
|
-
return true;
|
|
172
|
-
}
|
|
173
|
-
dirty = applyFeedbackTextBufferEvent(state, { type: "newline" }) || dirty;
|
|
183
|
+
if (event.type === "ctrl-d" || event.type === "enter") {
|
|
184
|
+
submitEdit();
|
|
185
|
+
dirty = true;
|
|
174
186
|
continue;
|
|
175
187
|
}
|
|
176
188
|
if (event.type === "ctrl-j") {
|
|
177
|
-
|
|
178
|
-
dirty = applyFeedbackTextBufferEvent(state, { type: "newline" }) || dirty;
|
|
179
|
-
}
|
|
189
|
+
dirty = applyEditingBufferEvent(state.editing, { type: "newline" }) || dirty;
|
|
180
190
|
continue;
|
|
181
191
|
}
|
|
182
192
|
if (event.type === "arrow") {
|
|
183
|
-
if (event.direction === "
|
|
184
|
-
state.
|
|
185
|
-
dirty = true;
|
|
186
|
-
}
|
|
187
|
-
else if (event.direction === "down" && state.mode === "approve") {
|
|
188
|
-
state.mode = "input";
|
|
189
|
-
dirty = true;
|
|
190
|
-
}
|
|
191
|
-
else if (state.mode === "input" &&
|
|
192
|
-
(event.direction === "left" || event.direction === "right")) {
|
|
193
|
-
dirty = applyFeedbackTextBufferEvent(state, {
|
|
193
|
+
if (event.direction === "left" || event.direction === "right") {
|
|
194
|
+
dirty = applyEditingBufferEvent(state.editing, {
|
|
194
195
|
type: event.direction === "left" ? "ctrl-b" : "ctrl-f",
|
|
195
196
|
}) || dirty;
|
|
196
197
|
}
|
|
197
198
|
continue;
|
|
198
199
|
}
|
|
199
|
-
if (event
|
|
200
|
-
|
|
201
|
-
const step = Math.max(1, planHeight - PLAN_PAGE_OVERLAP);
|
|
202
|
-
const max = Math.max(0, planLines - planHeight);
|
|
203
|
-
if (event.direction === "up") {
|
|
204
|
-
state.planScrollTop = Math.max(0, state.planScrollTop - step);
|
|
205
|
-
}
|
|
206
|
-
else {
|
|
207
|
-
state.planScrollTop = Math.min(max, state.planScrollTop + step);
|
|
208
|
-
}
|
|
209
|
-
state.planAtEnd = state.planScrollTop >= max;
|
|
210
|
-
dirty = true;
|
|
200
|
+
if (isTextBufferEvent(event)) {
|
|
201
|
+
dirty = applyEditingBufferEvent(state.editing, event) || dirty;
|
|
211
202
|
continue;
|
|
212
203
|
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
if (event.type === "ctrl-c") {
|
|
207
|
+
failWith(new AgentInterruptedError("Interrupted"));
|
|
208
|
+
finished = true;
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
if (event.type === "ctrl-d") {
|
|
212
|
+
if (state.userAdded.length === 0) {
|
|
213
|
+
finish({ accepted: true });
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
submitFeedback();
|
|
217
|
+
}
|
|
218
|
+
finished = true;
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
if (event.type === "arrow") {
|
|
222
|
+
if (event.direction === "up" || event.direction === "down") {
|
|
223
|
+
const total = navigableRows(state).length;
|
|
224
|
+
state.cursor = event.direction === "up"
|
|
225
|
+
? (state.cursor - 1 + total) % total
|
|
226
|
+
: (state.cursor + 1) % total;
|
|
216
227
|
dirty = true;
|
|
217
|
-
continue;
|
|
218
228
|
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
if (event.type === "scroll-page") {
|
|
232
|
+
const { planHeight, planLines } = planMetrics(plan, context, state, this.host);
|
|
233
|
+
const step = Math.max(1, planHeight - PLAN_PAGE_OVERLAP);
|
|
234
|
+
const max = Math.max(0, planLines - planHeight);
|
|
235
|
+
if (event.direction === "up") {
|
|
236
|
+
state.planScrollTop = Math.max(0, state.planScrollTop - step);
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
state.planScrollTop = Math.min(max, state.planScrollTop + step);
|
|
240
|
+
}
|
|
241
|
+
state.planAtEnd = state.planScrollTop >= max;
|
|
242
|
+
dirty = true;
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
if (event.type === "scroll-home") {
|
|
246
|
+
state.planScrollTop = 0;
|
|
247
|
+
state.planAtEnd = false;
|
|
248
|
+
dirty = true;
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
if (event.type === "scroll-end") {
|
|
252
|
+
const { planHeight, planLines } = planMetrics(plan, context, state, this.host);
|
|
253
|
+
state.planScrollTop = Math.max(0, planLines - planHeight);
|
|
254
|
+
state.planAtEnd = true;
|
|
255
|
+
dirty = true;
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
if (event.type === "scroll-lines") {
|
|
259
|
+
const { planHeight, planLines } = planMetrics(plan, context, state, this.host);
|
|
260
|
+
const max = Math.max(0, planLines - planHeight);
|
|
261
|
+
state.planScrollTop = Math.min(max, Math.max(0, state.planScrollTop + event.delta));
|
|
262
|
+
state.planAtEnd = state.planScrollTop >= max;
|
|
263
|
+
dirty = true;
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
if (event.type === "enter") {
|
|
267
|
+
const row = currentRow(state);
|
|
268
|
+
if (row.kind === "approve") {
|
|
269
|
+
finish({ accepted: true });
|
|
270
|
+
finished = true;
|
|
271
|
+
}
|
|
272
|
+
else if (row.kind === "item") {
|
|
273
|
+
openUserEditEditor(row.index);
|
|
223
274
|
dirty = true;
|
|
224
|
-
continue;
|
|
225
275
|
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
276
|
+
else {
|
|
277
|
+
openUserAddEditor();
|
|
278
|
+
dirty = true;
|
|
279
|
+
}
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
if (event.type === "insert") {
|
|
283
|
+
const ch = event.text.toLowerCase();
|
|
284
|
+
if (ch === "a") {
|
|
285
|
+
openUserAddEditor();
|
|
231
286
|
dirty = true;
|
|
232
287
|
continue;
|
|
233
288
|
}
|
|
234
|
-
if (
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
else if (event.type === "backspace") {
|
|
242
|
-
state.mode = "input";
|
|
243
|
-
dirty = true;
|
|
244
|
-
}
|
|
245
|
-
continue;
|
|
289
|
+
if (ch === "d") {
|
|
290
|
+
const row = currentRow(state);
|
|
291
|
+
if (row.kind === "item") {
|
|
292
|
+
state.userAdded.splice(row.index, 1);
|
|
293
|
+
clampCursor(state);
|
|
294
|
+
state.cursor = addRowCursorIndex(state);
|
|
295
|
+
dirty = true;
|
|
246
296
|
}
|
|
247
|
-
dirty = applyFeedbackTextBufferEvent(state, event) || dirty;
|
|
248
|
-
continue;
|
|
249
297
|
}
|
|
250
|
-
// tab/escape/dump ignored — dump is consumed by the host router.
|
|
251
298
|
}
|
|
252
|
-
|
|
253
|
-
|
|
299
|
+
}
|
|
300
|
+
if (dirty && !finished)
|
|
301
|
+
this.host.scheduleParentUiRender();
|
|
302
|
+
return finished;
|
|
303
|
+
};
|
|
304
|
+
const baseMode = {
|
|
305
|
+
getSurface: () => {
|
|
306
|
+
const layout = this.host.getLayout();
|
|
307
|
+
const cols = Math.max(20, layout.columns);
|
|
308
|
+
const rows = Math.max(1, layout.childRows);
|
|
309
|
+
return buildSurface(plan, context, state, cols, rows);
|
|
310
|
+
},
|
|
311
|
+
onEvents: (keys) => {
|
|
312
|
+
processEvents(parser.mapKeys(keys));
|
|
254
313
|
return true;
|
|
255
314
|
},
|
|
256
315
|
};
|
|
@@ -263,161 +322,196 @@ export class HostBackedUserPrompt {
|
|
|
263
322
|
});
|
|
264
323
|
}
|
|
265
324
|
}
|
|
266
|
-
function
|
|
267
|
-
if (state.
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
325
|
+
function navigableRows(state) {
|
|
326
|
+
if (state.userAdded.length === 0) {
|
|
327
|
+
return [{ kind: "approve" }, { kind: "add" }];
|
|
328
|
+
}
|
|
329
|
+
return [
|
|
330
|
+
...state.userAdded.map((_, index) => ({ kind: "item", index })),
|
|
331
|
+
{ kind: "add" },
|
|
332
|
+
];
|
|
333
|
+
}
|
|
334
|
+
function currentRow(state) {
|
|
335
|
+
clampCursor(state);
|
|
336
|
+
return navigableRows(state)[state.cursor] ?? { kind: "approve" };
|
|
271
337
|
}
|
|
272
|
-
function
|
|
273
|
-
|
|
274
|
-
const buffer = {
|
|
275
|
-
lines: state.lines,
|
|
276
|
-
cursorRow: state.cursorLine,
|
|
277
|
-
cursorCol: state.cursorCol,
|
|
278
|
-
};
|
|
279
|
-
const changed = applyTextBufferEvent(buffer, event);
|
|
280
|
-
state.lines = buffer.lines;
|
|
281
|
-
state.cursorLine = buffer.cursorRow;
|
|
282
|
-
state.cursorCol = buffer.cursorCol;
|
|
283
|
-
normalizeFeedbackCursor(state);
|
|
284
|
-
return changed;
|
|
338
|
+
function addRowCursorIndex(state) {
|
|
339
|
+
return state.userAdded.length === 0 ? 1 : state.userAdded.length;
|
|
285
340
|
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
function
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
const
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
if (logIdx < cursorLine) {
|
|
312
|
-
cursorVisualRow += segments.length;
|
|
313
|
-
}
|
|
314
|
-
else if (logIdx === cursorLine) {
|
|
315
|
-
const cursor = cursorToVisual(segments, state.cursorCol, line.length, bodyWidth);
|
|
316
|
-
cursorVisualRow += cursor.subRow;
|
|
317
|
-
cursorVisCol = cursor.visCol;
|
|
318
|
-
}
|
|
319
|
-
const rendered = renderWrappedLine(line, bodyWidth, state.mode === "input" && logIdx === cursorLine ? state.cursorCol : null);
|
|
320
|
-
for (let subRow = 0; subRow < rendered.length; subRow++) {
|
|
321
|
-
visualRows.push({ logIdx, subRow, body: rendered[subRow] });
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
const unboundedEditor = showPlaceholder ? 1 : visualRows.length;
|
|
326
|
-
const editorHeight = Math.min(FEEDBACK_EDITOR_MAX_ROWS, Math.max(1, unboundedEditor));
|
|
327
|
-
if (showPlaceholder) {
|
|
328
|
-
state.feedbackScrollTop = 0;
|
|
341
|
+
function clampCursor(state) {
|
|
342
|
+
const max = Math.max(0, navigableRows(state).length - 1);
|
|
343
|
+
state.cursor = Math.max(0, Math.min(state.cursor, max));
|
|
344
|
+
}
|
|
345
|
+
// Natural (unbounded) height of the feedback-list block at the given render
|
|
346
|
+
// width - the dim/active Approve row + every item row (incl. inline editor) +
|
|
347
|
+
// the +Add row. Excludes the separator/hint chrome. Used by `planMetrics` to
|
|
348
|
+
// size the plan area on scroll events.
|
|
349
|
+
function measureFeedbackListRows(state, width) {
|
|
350
|
+
return buildFeedbackListRows(state, width).lines.length;
|
|
351
|
+
}
|
|
352
|
+
function buildFeedbackListRows(state, width) {
|
|
353
|
+
clampCursor(state);
|
|
354
|
+
const lines = [];
|
|
355
|
+
const rowStarts = [];
|
|
356
|
+
const rowEnds = [];
|
|
357
|
+
let editorRow;
|
|
358
|
+
let editorCol = 0;
|
|
359
|
+
if (state.userAdded.length === 0) {
|
|
360
|
+
const active = state.cursor === 0;
|
|
361
|
+
rowStarts[0] = lines.length;
|
|
362
|
+
lines.push(clipToWidth(active
|
|
363
|
+
? `${ACTIVE_SELECTOR}${bold}${green}✓ Approve (Enter)${reset}`
|
|
364
|
+
: ` ${dim}✓ Approve${reset}`, width));
|
|
365
|
+
rowEnds[0] = lines.length;
|
|
329
366
|
}
|
|
330
367
|
else {
|
|
331
|
-
|
|
332
|
-
|
|
368
|
+
lines.push(clipToWidth(` ${dim}✓ Approve${reset}`, width));
|
|
369
|
+
}
|
|
370
|
+
for (let i = 0; i < state.userAdded.length; i++) {
|
|
371
|
+
const isCursor = state.cursor === i;
|
|
372
|
+
const leadChar = isCursor ? ACTIVE_SELECTOR : " ";
|
|
373
|
+
const head = `${leadChar} ${i + 1}. `;
|
|
374
|
+
const headWidth = visibleLength(head);
|
|
375
|
+
const indent = " ".repeat(headWidth);
|
|
376
|
+
const avail = Math.max(1, width - headWidth);
|
|
377
|
+
rowStarts[i] = lines.length;
|
|
378
|
+
if (state.editing?.target.kind === "user-edit" &&
|
|
379
|
+
state.editing.target.index === i) {
|
|
380
|
+
const renderedEditor = renderMultilineEditor(state.editing.buffer, width, head, indent);
|
|
381
|
+
editorRow = lines.length + renderedEditor.cursorRowOffset;
|
|
382
|
+
editorCol = renderedEditor.cursorCol;
|
|
383
|
+
lines.push(...renderedEditor.lines);
|
|
333
384
|
}
|
|
334
|
-
else
|
|
335
|
-
|
|
385
|
+
else {
|
|
386
|
+
const wrapped = wrapToWidth(state.userAdded[i] ?? "", avail);
|
|
387
|
+
lines.push(clipToWidth(`${head}${wrapped[0] ?? ""}`, width));
|
|
388
|
+
for (let j = 1; j < wrapped.length; j++) {
|
|
389
|
+
lines.push(clipToWidth(`${indent}${wrapped[j]}`, width));
|
|
390
|
+
}
|
|
336
391
|
}
|
|
337
|
-
|
|
338
|
-
if (state.feedbackScrollTop > maxScroll)
|
|
339
|
-
state.feedbackScrollTop = maxScroll;
|
|
340
|
-
if (state.feedbackScrollTop < 0)
|
|
341
|
-
state.feedbackScrollTop = 0;
|
|
392
|
+
rowEnds[i] = lines.length;
|
|
342
393
|
}
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
if (state.
|
|
348
|
-
const head =
|
|
349
|
-
const
|
|
350
|
-
|
|
394
|
+
{
|
|
395
|
+
const rowIndex = addRowCursorIndex(state);
|
|
396
|
+
const isCursor = state.cursor === rowIndex;
|
|
397
|
+
rowStarts[rowIndex] = lines.length;
|
|
398
|
+
if (state.editing?.target.kind === "user-add") {
|
|
399
|
+
const head = `${ACTIVE_SELECTOR} ${state.userAdded.length + 1}. `;
|
|
400
|
+
const headWidth = visibleLength(head);
|
|
401
|
+
const renderedEditor = renderMultilineEditor(state.editing.buffer, width, head, " ".repeat(headWidth));
|
|
402
|
+
editorRow = lines.length + renderedEditor.cursorRowOffset;
|
|
403
|
+
editorCol = renderedEditor.cursorCol;
|
|
404
|
+
lines.push(...renderedEditor.lines);
|
|
351
405
|
}
|
|
352
406
|
else {
|
|
353
|
-
|
|
407
|
+
lines.push(clipToWidth(isCursor
|
|
408
|
+
? `${ACTIVE_SELECTOR}${bold}${green}+ Add feedback (Enter)${reset}`
|
|
409
|
+
: ` ${dim}+ Add feedback${reset}`, width));
|
|
354
410
|
}
|
|
355
|
-
|
|
356
|
-
while (editorBody.length < editorHeight)
|
|
357
|
-
editorBody.push("");
|
|
411
|
+
rowEnds[rowIndex] = lines.length;
|
|
358
412
|
}
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
413
|
+
return {
|
|
414
|
+
lines,
|
|
415
|
+
rowStarts,
|
|
416
|
+
rowEnds,
|
|
417
|
+
editorRow,
|
|
418
|
+
editorCol,
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
function buildFeedbackPane(state, rendered, width, listRows, hintTokens) {
|
|
422
|
+
const scrollRows = Math.max(0, listRows);
|
|
423
|
+
const activeStart = rendered.rowStarts[state.cursor] ?? 0;
|
|
424
|
+
const activeEndExclusive = rendered.rowEnds[state.cursor] ?? rendered.lines.length;
|
|
425
|
+
let scrollTop = 0;
|
|
426
|
+
const total = rendered.lines.length;
|
|
427
|
+
if (scrollRows > 0 && total > scrollRows) {
|
|
428
|
+
scrollTop = activeStart;
|
|
429
|
+
if (activeEndExclusive - scrollTop < scrollRows) {
|
|
430
|
+
scrollTop = Math.max(0, total - scrollRows);
|
|
431
|
+
if (scrollTop > activeStart)
|
|
432
|
+
scrollTop = activeStart;
|
|
433
|
+
}
|
|
434
|
+
if (rendered.editorRow !== undefined) {
|
|
435
|
+
if (rendered.editorRow < scrollTop)
|
|
436
|
+
scrollTop = rendered.editorRow;
|
|
437
|
+
if (rendered.editorRow >= scrollTop + scrollRows) {
|
|
438
|
+
scrollTop = rendered.editorRow - scrollRows + 1;
|
|
365
439
|
}
|
|
366
|
-
const isFirstUnderlying = visual.logIdx === 0 && visual.subRow === 0;
|
|
367
|
-
const prefix = isFirstUnderlying && state.mode === "input"
|
|
368
|
-
? ACTIVE_PREFIX
|
|
369
|
-
: INACTIVE_PREFIX;
|
|
370
|
-
editorBody.push(`${prefix}${visual.body}`);
|
|
371
440
|
}
|
|
441
|
+
if (scrollTop < 0)
|
|
442
|
+
scrollTop = 0;
|
|
443
|
+
if (scrollTop + scrollRows > total)
|
|
444
|
+
scrollTop = total - scrollRows;
|
|
372
445
|
}
|
|
373
|
-
const
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
if (offscreenHint)
|
|
379
|
-
tokens.push(offscreenHint);
|
|
380
|
-
tokens.push(ALT_V_PASTE_IMAGE_HINT);
|
|
381
|
-
if (state.transientHint)
|
|
382
|
-
tokens.push(`${yellow}${state.transientHint}${reset}`);
|
|
383
|
-
const lines = [
|
|
384
|
-
approveRow,
|
|
385
|
-
...editorBody,
|
|
386
|
-
`${dim}${"─".repeat(width)}${reset}`,
|
|
387
|
-
`${INACTIVE_PREFIX}${renderShortcutHints(tokens)}`,
|
|
388
|
-
];
|
|
389
|
-
const approveRowInBlock = 0;
|
|
390
|
-
const editorStart = 1;
|
|
446
|
+
const listLines = scrollRows === 0
|
|
447
|
+
? []
|
|
448
|
+
: rendered.lines.slice(scrollTop, scrollTop + scrollRows);
|
|
449
|
+
while (listLines.length < scrollRows)
|
|
450
|
+
listLines.push("");
|
|
391
451
|
let cursorRow;
|
|
392
452
|
let cursorCol;
|
|
393
|
-
if (
|
|
394
|
-
cursorRow =
|
|
395
|
-
cursorCol =
|
|
396
|
-
}
|
|
397
|
-
else if (showPlaceholder) {
|
|
398
|
-
cursorRow = editorStart;
|
|
399
|
-
cursorCol = activePrefixWidth;
|
|
453
|
+
if (rendered.editorRow !== undefined) {
|
|
454
|
+
cursorRow = rendered.editorRow - scrollTop;
|
|
455
|
+
cursorCol = rendered.editorCol;
|
|
400
456
|
}
|
|
401
457
|
else {
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
const cursorLine = Math.min(Math.max(0, state.cursorLine), state.lines.length - 1);
|
|
405
|
-
const isFirstUnderlying = cursorLine === 0 && cursorVisualRow === 0;
|
|
406
|
-
const prefixLen = isFirstUnderlying ? activePrefixWidth : inactivePrefixWidth;
|
|
407
|
-
cursorCol = prefixLen + cursorVisCol;
|
|
458
|
+
cursorRow = activeStart - scrollTop;
|
|
459
|
+
cursorCol = currentRow(state).kind === "approve" ? visibleLength(ACTIVE_SELECTOR) : 0;
|
|
408
460
|
}
|
|
409
|
-
if (
|
|
461
|
+
if (scrollRows === 0) {
|
|
410
462
|
cursorRow = 0;
|
|
411
|
-
if (cursorCol < 0)
|
|
412
463
|
cursorCol = 0;
|
|
464
|
+
}
|
|
465
|
+
else {
|
|
466
|
+
if (cursorRow < 0)
|
|
467
|
+
cursorRow = 0;
|
|
468
|
+
if (cursorRow >= scrollRows)
|
|
469
|
+
cursorRow = scrollRows - 1;
|
|
470
|
+
if (cursorCol < 0)
|
|
471
|
+
cursorCol = 0;
|
|
472
|
+
if (cursorCol >= width)
|
|
473
|
+
cursorCol = Math.max(0, width - 1);
|
|
474
|
+
}
|
|
475
|
+
const lines = [
|
|
476
|
+
...listLines,
|
|
477
|
+
`${dim}${"─".repeat(width)}${reset}`,
|
|
478
|
+
`${INACTIVE_PREFIX}${renderShortcutHints(hintTokens)}`,
|
|
479
|
+
];
|
|
413
480
|
return {
|
|
414
|
-
lines: lines.map((
|
|
481
|
+
lines: lines.map((line) => clipToWidth(line, width)),
|
|
415
482
|
cursor: { row: cursorRow, col: cursorCol },
|
|
416
483
|
};
|
|
417
484
|
}
|
|
485
|
+
function promptHelpTokens(state, offscreenHint) {
|
|
486
|
+
if (state.editing) {
|
|
487
|
+
const tokens = ["Ctrl+J Newline", "Enter Submit", "Esc Cancel", ALT_V_PASTE_IMAGE_HINT];
|
|
488
|
+
if (state.editing.transientHint) {
|
|
489
|
+
tokens.push(`${yellow}${state.editing.transientHint}${reset}`);
|
|
490
|
+
}
|
|
491
|
+
if (offscreenHint)
|
|
492
|
+
tokens.push(offscreenHint);
|
|
493
|
+
return tokens;
|
|
494
|
+
}
|
|
495
|
+
const row = currentRow(state);
|
|
496
|
+
const tokens = [];
|
|
497
|
+
if (row.kind === "item") {
|
|
498
|
+
tokens.push("Enter Edit", "D Delete", "A Add");
|
|
499
|
+
}
|
|
500
|
+
else if (row.kind === "add") {
|
|
501
|
+
tokens.push("Enter/A Add");
|
|
502
|
+
}
|
|
503
|
+
else {
|
|
504
|
+
tokens.push("Enter Approve", "A Add");
|
|
505
|
+
}
|
|
506
|
+
tokens.push(state.userAdded.length === 0 ? "Ctrl+D Approve" : "Ctrl+D Submit");
|
|
507
|
+
tokens.push("Ctrl+C Quit");
|
|
508
|
+
if (offscreenHint)
|
|
509
|
+
tokens.push(offscreenHint);
|
|
510
|
+
return tokens;
|
|
511
|
+
}
|
|
418
512
|
/**
|
|
419
|
-
* Compute the plan area's available height given total `rows` and the
|
|
420
|
-
*
|
|
513
|
+
* Compute the plan area's available height given total `rows` and the controls
|
|
514
|
+
* block height. Reserves 1 row for the dim separator between plan and controls.
|
|
421
515
|
*/
|
|
422
516
|
function computePlanRows(controlsHeight, rows) {
|
|
423
517
|
return Math.max(1, rows - controlsHeight - 1);
|
|
@@ -426,8 +520,6 @@ function buildPlanLines(plan, context, state, width, planRows) {
|
|
|
426
520
|
const pane = buildPlanPane(plan, context.phaseLabel, width, context.artifactKind ?? "plan");
|
|
427
521
|
const total = pane.lines.length;
|
|
428
522
|
const maxScroll = Math.max(0, total - planRows);
|
|
429
|
-
// Re-pin to the new bottom when the user last scrolled to End and the plan
|
|
430
|
-
// window has since shrunk (e.g. the controls block grew while typing).
|
|
431
523
|
if (state.planAtEnd)
|
|
432
524
|
state.planScrollTop = maxScroll;
|
|
433
525
|
if (state.planScrollTop > maxScroll)
|
|
@@ -438,12 +530,24 @@ function buildPlanLines(plan, context, state, width, planRows) {
|
|
|
438
530
|
while (sliced.length < planRows)
|
|
439
531
|
sliced.push("");
|
|
440
532
|
return {
|
|
441
|
-
lines: sliced.map((
|
|
533
|
+
lines: sliced.map((line) => clipToWidth(line, width)),
|
|
442
534
|
total,
|
|
443
535
|
};
|
|
444
536
|
}
|
|
445
537
|
function buildSurface(plan, context, state, cols, rows) {
|
|
446
|
-
const
|
|
538
|
+
const split = computeSplitMetrics({ cols, rows, collapseAt: SPLIT_COLLAPSE_AT_COLS });
|
|
539
|
+
const natural = buildFeedbackListRows(state, cols);
|
|
540
|
+
const naturalRows = natural.lines.length;
|
|
541
|
+
const offscreenHint = primaryOffscreenHint(context);
|
|
542
|
+
const hintTokens = promptHelpTokens(state, offscreenHint);
|
|
543
|
+
if (!split.collapsed && naturalRows > FEEDBACK_AREA_MAX_ROWS) {
|
|
544
|
+
const leftRender = buildFeedbackListRows(state, split.leftWidth);
|
|
545
|
+
const left = buildFeedbackPane(state, leftRender, split.leftWidth, Math.min(leftRender.lines.length, Math.max(0, rows - 2)), hintTokens);
|
|
546
|
+
const { lines: rightLines } = buildPlanLines(plan, context, state, split.rightWidth, rows);
|
|
547
|
+
return buildSplitSurface({ lines: left.lines, cursor: left.cursor }, { lines: rightLines }, { cols, rows, collapseAt: SPLIT_COLLAPSE_AT_COLS }, "left");
|
|
548
|
+
}
|
|
549
|
+
const listRows = Math.min(naturalRows, FEEDBACK_AREA_MAX_ROWS);
|
|
550
|
+
const controls = buildFeedbackPane(state, natural, cols, listRows, hintTokens);
|
|
447
551
|
const planRows = computePlanRows(controls.lines.length, rows);
|
|
448
552
|
const { lines: planLines } = buildPlanLines(plan, context, state, cols, planRows);
|
|
449
553
|
const separator = `${dim}${"─".repeat(cols)}${reset}`;
|
|
@@ -452,16 +556,11 @@ function buildSurface(plan, context, state, cols, rows) {
|
|
|
452
556
|
separator,
|
|
453
557
|
...controls.lines,
|
|
454
558
|
];
|
|
455
|
-
// Pad / clip to exactly `rows` rows. The plan area is the only flex region;
|
|
456
|
-
// controls + separator + margins remain anchored to the bottom.
|
|
457
559
|
while (composed.length < rows)
|
|
458
560
|
composed.push("");
|
|
459
561
|
if (composed.length > rows) {
|
|
460
|
-
// Clip from the top of the plan area; never clip controls.
|
|
461
562
|
composed.splice(0, composed.length - rows);
|
|
462
563
|
}
|
|
463
|
-
// Cursor row sits in the controls block: planRows + 1 (separator) +
|
|
464
|
-
// controls.cursor.row.
|
|
465
564
|
let cursorRow = planRows + 1 + controls.cursor.row;
|
|
466
565
|
if (cursorRow < 0)
|
|
467
566
|
cursorRow = 0;
|
|
@@ -476,8 +575,14 @@ function planMetrics(plan, context, state, host) {
|
|
|
476
575
|
const layout = host.getLayout();
|
|
477
576
|
const cols = Math.max(20, layout.columns);
|
|
478
577
|
const rows = Math.max(1, layout.childRows);
|
|
479
|
-
const
|
|
480
|
-
const
|
|
578
|
+
const split = computeSplitMetrics({ cols, rows, collapseAt: SPLIT_COLLAPSE_AT_COLS });
|
|
579
|
+
const naturalRows = measureFeedbackListRows(state, cols);
|
|
580
|
+
if (!split.collapsed && naturalRows > FEEDBACK_AREA_MAX_ROWS) {
|
|
581
|
+
const pane = buildPlanPane(plan, context.phaseLabel, split.rightWidth, context.artifactKind ?? "plan");
|
|
582
|
+
return { planHeight: rows, planLines: pane.lines.length };
|
|
583
|
+
}
|
|
584
|
+
const controlsHeight = Math.min(naturalRows, FEEDBACK_AREA_MAX_ROWS) + 2;
|
|
585
|
+
const planHeight = computePlanRows(controlsHeight, rows);
|
|
481
586
|
const pane = buildPlanPane(plan, context.phaseLabel, cols, context.artifactKind ?? "plan");
|
|
482
587
|
return { planHeight, planLines: pane.lines.length };
|
|
483
588
|
}
|