poly-weaver 0.11.3 → 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/cli.js +3 -0
- package/dist/cli.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 +3 -0
- package/dist/flow-editor/tui.d.ts.map +1 -1
- package/dist/flow-editor/tui.js +90 -12
- package/dist/flow-editor/tui.js.map +1 -1
- package/dist/markdown.d.ts.map +1 -1
- package/dist/markdown.js +386 -30
- package/dist/markdown.js.map +1 -1
- package/dist/offscreen/artifact-pane.d.ts +20 -0
- package/dist/offscreen/artifact-pane.d.ts.map +1 -0
- package/dist/offscreen/artifact-pane.js +72 -0
- package/dist/offscreen/artifact-pane.js.map +1 -0
- package/dist/offscreen/controller.d.ts +16 -3
- package/dist/offscreen/controller.d.ts.map +1 -1
- package/dist/offscreen/controller.js +84 -75
- package/dist/offscreen/controller.js.map +1 -1
- package/dist/offscreen/inquiry-pane.d.ts +14 -0
- package/dist/offscreen/inquiry-pane.d.ts.map +1 -0
- package/dist/offscreen/inquiry-pane.js +101 -0
- package/dist/offscreen/inquiry-pane.js.map +1 -0
- package/dist/offscreen/panes.d.ts +14 -0
- package/dist/offscreen/panes.d.ts.map +1 -0
- package/dist/offscreen/panes.js +2 -0
- package/dist/offscreen/panes.js.map +1 -0
- package/dist/offscreen/split-inquiry.d.ts +28 -0
- package/dist/offscreen/split-inquiry.d.ts.map +1 -0
- package/dist/offscreen/split-inquiry.js +88 -0
- package/dist/offscreen/split-inquiry.js.map +1 -0
- package/dist/orchestrator.d.ts +3 -0
- package/dist/orchestrator.d.ts.map +1 -1
- package/dist/orchestrator.js +18 -2
- package/dist/orchestrator.js.map +1 -1
- package/dist/paste-image/attachment-manager.d.ts +20 -0
- package/dist/paste-image/attachment-manager.d.ts.map +1 -0
- package/dist/paste-image/attachment-manager.js +88 -0
- package/dist/paste-image/attachment-manager.js.map +1 -0
- package/dist/paste-image/bracketed-paste.d.ts +13 -0
- package/dist/paste-image/bracketed-paste.d.ts.map +1 -0
- package/dist/paste-image/bracketed-paste.js +82 -0
- package/dist/paste-image/bracketed-paste.js.map +1 -0
- package/dist/paste-image/clipboard-ffi.d.ts +9 -0
- package/dist/paste-image/clipboard-ffi.d.ts.map +1 -0
- package/dist/paste-image/clipboard-ffi.js +149 -0
- package/dist/paste-image/clipboard-ffi.js.map +1 -0
- package/dist/paste-image/clipboard.d.ts +38 -0
- package/dist/paste-image/clipboard.d.ts.map +1 -0
- package/dist/paste-image/clipboard.js +454 -0
- package/dist/paste-image/clipboard.js.map +1 -0
- package/dist/paste-image/image-format.d.ts +7 -0
- package/dist/paste-image/image-format.d.ts.map +1 -0
- package/dist/paste-image/image-format.js +61 -0
- package/dist/paste-image/image-format.js.map +1 -0
- package/dist/paste-image/intents.d.ts +11 -0
- package/dist/paste-image/intents.d.ts.map +1 -0
- package/dist/paste-image/intents.js +66 -0
- package/dist/paste-image/intents.js.map +1 -0
- package/dist/paste-image/persistence.d.ts +2 -0
- package/dist/paste-image/persistence.d.ts.map +1 -0
- package/dist/paste-image/persistence.js +8 -0
- package/dist/paste-image/persistence.js.map +1 -0
- package/dist/paste-image/staging.d.ts +8 -0
- package/dist/paste-image/staging.d.ts.map +1 -0
- package/dist/paste-image/staging.js +23 -0
- package/dist/paste-image/staging.js.map +1 -0
- package/dist/paste-image/token.d.ts +14 -0
- package/dist/paste-image/token.d.ts.map +1 -0
- package/dist/paste-image/token.js +26 -0
- package/dist/paste-image/token.js.map +1 -0
- 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/providers/option-catalog.d.ts.map +1 -1
- package/dist/providers/option-catalog.js +0 -1
- package/dist/providers/option-catalog.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 +8 -2
- package/dist/startup-tui.d.ts.map +1 -1
- package/dist/startup-tui.js +77 -6
- package/dist/startup-tui.js.map +1 -1
- package/dist/terminal/input-router.d.ts +9 -0
- package/dist/terminal/input-router.d.ts.map +1 -1
- package/dist/terminal/input-router.js +14 -0
- package/dist/terminal/input-router.js.map +1 -1
- package/dist/text-editing/emacs-input.d.ts +11 -0
- package/dist/text-editing/emacs-input.d.ts.map +1 -1
- package/dist/text-editing/emacs-input.js +254 -17
- package/dist/text-editing/emacs-input.js.map +1 -1
- package/dist/user/curate-handler.d.ts.map +1 -1
- package/dist/user/curate-handler.js +4 -1
- package/dist/user/curate-handler.js.map +1 -1
- package/dist/user/curate-prompt.d.ts +2 -0
- package/dist/user/curate-prompt.d.ts.map +1 -1
- package/dist/user/curate-prompt.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 +5 -2
- package/dist/user/handler.js.map +1 -1
- package/dist/user/host-curate-prompt.d.ts +5 -6
- package/dist/user/host-curate-prompt.d.ts.map +1 -1
- package/dist/user/host-curate-prompt.js +149 -81
- 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 +444 -264
- package/dist/user/host-prompt.js.map +1 -1
- package/dist/user/prompt.d.ts +3 -1
- package/dist/user/prompt.d.ts.map +1 -1
- package/dist/user/shortcut-hints.d.ts +1 -0
- package/dist/user/shortcut-hints.d.ts.map +1 -1
- package/dist/user/shortcut-hints.js +1 -1
- package/dist/user/shortcut-hints.js.map +1 -1
- package/dist/user/split-surface.d.ts +5 -1
- package/dist/user/split-surface.d.ts.map +1 -1
- package/dist/user/split-surface.js +15 -8
- package/dist/user/split-surface.js.map +1 -1
- package/package.json +1 -2
package/dist/user/host-prompt.js
CHANGED
|
@@ -1,32 +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 {
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
import { aliasSelectionNavEvent, isTextBufferEvent, } from "../text-editing/emacs-input.js";
|
|
8
|
+
import { OffscreenController, } from "../offscreen/controller.js";
|
|
9
|
+
import { AttachmentManager } from "../paste-image/attachment-manager.js";
|
|
10
|
+
import { ALT_V_PASTE_IMAGE_HINT, handleBracketedPaste, handleImagePasteIntent, PASTING_IMAGE_HINT, } from "../paste-image/intents.js";
|
|
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;
|
|
12
18
|
/** Lines of overlap when paging the plan (PageUp/PageDown). */
|
|
13
19
|
const PLAN_PAGE_OVERLAP = 1;
|
|
14
|
-
const
|
|
20
|
+
const ACTIVE_SELECTOR = "❯";
|
|
15
21
|
const INACTIVE_PREFIX = " ";
|
|
16
22
|
/**
|
|
17
|
-
* Host-backed user-review prompt
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* a controls block (Approve + bounded feedback editor + compact hint row).
|
|
21
|
-
*
|
|
22
|
-
* Key handling:
|
|
23
|
-
* - ↑/↓ toggles between Approve and the editor (does NOT scroll the plan).
|
|
24
|
-
* - PageUp/PageDown/Home/End/wheel scrolls the plan.
|
|
25
|
-
* - Ctrl+D submits feedback (or accepts when on Approve).
|
|
26
|
-
* - Ctrl+C quits poly-weaver (exit code 130). When the feedback editor has
|
|
27
|
-
* typed text, the first Ctrl+C clears the buffer instead; a second Ctrl+C
|
|
28
|
-
* on an empty buffer quits.
|
|
29
|
-
* - 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.
|
|
30
26
|
*/
|
|
31
27
|
export class HostBackedUserPrompt {
|
|
32
28
|
host;
|
|
@@ -37,20 +33,21 @@ export class HostBackedUserPrompt {
|
|
|
37
33
|
if (!this.host.isInteractive) {
|
|
38
34
|
return { accepted: true };
|
|
39
35
|
}
|
|
36
|
+
const state = {
|
|
37
|
+
cursor: 1,
|
|
38
|
+
userAdded: [],
|
|
39
|
+
editing: undefined,
|
|
40
|
+
planScrollTop: 0,
|
|
41
|
+
planAtEnd: false,
|
|
42
|
+
};
|
|
43
|
+
const parser = new EmacsInputParser();
|
|
40
44
|
return new Promise((resolve, reject) => {
|
|
41
|
-
const state = {
|
|
42
|
-
mode: "input",
|
|
43
|
-
lines: [""],
|
|
44
|
-
cursorLine: 0,
|
|
45
|
-
cursorCol: 0,
|
|
46
|
-
feedbackScrollTop: 0,
|
|
47
|
-
planScrollTop: 0,
|
|
48
|
-
planAtEnd: false,
|
|
49
|
-
};
|
|
50
|
-
const parser = new EmacsInputParser();
|
|
51
45
|
let detach;
|
|
52
46
|
let offscreenController;
|
|
47
|
+
let active = true;
|
|
48
|
+
let settlingEdit = false;
|
|
53
49
|
const cleanup = () => {
|
|
50
|
+
active = false;
|
|
54
51
|
offscreenController?.dispose();
|
|
55
52
|
offscreenController = undefined;
|
|
56
53
|
detach?.();
|
|
@@ -64,291 +61,457 @@ export class HostBackedUserPrompt {
|
|
|
64
61
|
cleanup();
|
|
65
62
|
reject(err);
|
|
66
63
|
};
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
return
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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);
|
|
89
|
+
};
|
|
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();
|
|
106
|
+
}).catch((err) => {
|
|
107
|
+
settlingEdit = false;
|
|
108
|
+
failWith(err instanceof Error ? err : new Error(String(err)));
|
|
109
|
+
});
|
|
110
|
+
};
|
|
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
|
+
};
|
|
119
|
+
};
|
|
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)
|
|
131
|
+
return;
|
|
132
|
+
editing.transientHint = outcome.hint ?? null;
|
|
133
|
+
if (outcome.inserted !== undefined)
|
|
134
|
+
applyEditInsertedText(editing, outcome.inserted);
|
|
135
|
+
this.host.scheduleParentUiRender();
|
|
136
|
+
};
|
|
137
|
+
const launchEditPasteIntent = (editing, task) => {
|
|
138
|
+
void task.then((outcome) => applyEditPasteOutcome(editing, outcome)).catch((err) => {
|
|
139
|
+
if (!active || settlingEdit || state.editing !== editing)
|
|
140
|
+
return;
|
|
141
|
+
editing.transientHint = err instanceof Error ? err.message : String(err);
|
|
142
|
+
this.host.scheduleParentUiRender();
|
|
143
|
+
});
|
|
144
|
+
};
|
|
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) {
|
|
89
156
|
dirty = true;
|
|
90
157
|
continue;
|
|
91
158
|
}
|
|
92
|
-
if (
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
finish({ accepted: false, comments: text });
|
|
100
|
-
return true;
|
|
101
|
-
}
|
|
159
|
+
if (state.editing.transientHint) {
|
|
160
|
+
state.editing.transientHint = null;
|
|
161
|
+
dirty = true;
|
|
162
|
+
}
|
|
163
|
+
if (event.type === "paste") {
|
|
164
|
+
const editing = state.editing;
|
|
165
|
+
launchEditPasteIntent(editing, handleBracketedPaste(event.content, editing.attachments));
|
|
102
166
|
continue;
|
|
103
167
|
}
|
|
104
|
-
if (event.type === "
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
168
|
+
if (event.type === "image-paste") {
|
|
169
|
+
const editing = state.editing;
|
|
170
|
+
launchEditPasteIntent(editing, handleImagePasteIntent(editing.attachments, () => {
|
|
171
|
+
if (!active || settlingEdit || state.editing !== editing)
|
|
172
|
+
return;
|
|
173
|
+
editing.transientHint = PASTING_IMAGE_HINT;
|
|
174
|
+
this.host.scheduleParentUiRender();
|
|
175
|
+
}));
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
if (event.type === "ctrl-c" || event.type === "escape") {
|
|
179
|
+
cancelEdit();
|
|
180
|
+
dirty = true;
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
if (event.type === "ctrl-d" || event.type === "enter") {
|
|
184
|
+
submitEdit();
|
|
185
|
+
dirty = true;
|
|
110
186
|
continue;
|
|
111
187
|
}
|
|
112
188
|
if (event.type === "ctrl-j") {
|
|
113
|
-
|
|
114
|
-
dirty = applyFeedbackTextBufferEvent(state, { type: "newline" }) || dirty;
|
|
115
|
-
}
|
|
189
|
+
dirty = applyEditingBufferEvent(state.editing, { type: "newline" }) || dirty;
|
|
116
190
|
continue;
|
|
117
191
|
}
|
|
118
192
|
if (event.type === "arrow") {
|
|
119
|
-
if (event.direction === "
|
|
120
|
-
state.
|
|
121
|
-
dirty = true;
|
|
122
|
-
}
|
|
123
|
-
else if (event.direction === "down" && state.mode === "approve") {
|
|
124
|
-
state.mode = "input";
|
|
125
|
-
dirty = true;
|
|
126
|
-
}
|
|
127
|
-
else if (state.mode === "input" &&
|
|
128
|
-
(event.direction === "left" || event.direction === "right")) {
|
|
129
|
-
dirty = applyFeedbackTextBufferEvent(state, {
|
|
193
|
+
if (event.direction === "left" || event.direction === "right") {
|
|
194
|
+
dirty = applyEditingBufferEvent(state.editing, {
|
|
130
195
|
type: event.direction === "left" ? "ctrl-b" : "ctrl-f",
|
|
131
196
|
}) || dirty;
|
|
132
197
|
}
|
|
133
198
|
continue;
|
|
134
199
|
}
|
|
135
|
-
if (event
|
|
136
|
-
|
|
137
|
-
const step = Math.max(1, planHeight - PLAN_PAGE_OVERLAP);
|
|
138
|
-
const max = Math.max(0, planLines - planHeight);
|
|
139
|
-
if (event.direction === "up") {
|
|
140
|
-
state.planScrollTop = Math.max(0, state.planScrollTop - step);
|
|
141
|
-
}
|
|
142
|
-
else {
|
|
143
|
-
state.planScrollTop = Math.min(max, state.planScrollTop + step);
|
|
144
|
-
}
|
|
145
|
-
state.planAtEnd = state.planScrollTop >= max;
|
|
146
|
-
dirty = true;
|
|
200
|
+
if (isTextBufferEvent(event)) {
|
|
201
|
+
dirty = applyEditingBufferEvent(state.editing, event) || dirty;
|
|
147
202
|
continue;
|
|
148
203
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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;
|
|
227
|
+
dirty = true;
|
|
228
|
+
}
|
|
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);
|
|
152
274
|
dirty = true;
|
|
153
|
-
continue;
|
|
154
275
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
state.planScrollTop = Math.max(0, planLines - planHeight);
|
|
158
|
-
state.planAtEnd = true;
|
|
276
|
+
else {
|
|
277
|
+
openUserAddEditor();
|
|
159
278
|
dirty = true;
|
|
160
|
-
continue;
|
|
161
279
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
if (event.type === "insert") {
|
|
283
|
+
const ch = event.text.toLowerCase();
|
|
284
|
+
if (ch === "a") {
|
|
285
|
+
openUserAddEditor();
|
|
167
286
|
dirty = true;
|
|
168
287
|
continue;
|
|
169
288
|
}
|
|
170
|
-
if (
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
else if (event.type === "backspace") {
|
|
178
|
-
state.mode = "input";
|
|
179
|
-
dirty = true;
|
|
180
|
-
}
|
|
181
|
-
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;
|
|
182
296
|
}
|
|
183
|
-
dirty = applyFeedbackTextBufferEvent(state, event) || dirty;
|
|
184
|
-
continue;
|
|
185
297
|
}
|
|
186
|
-
// tab/escape/dump ignored — dump is consumed by the host router.
|
|
187
298
|
}
|
|
188
|
-
|
|
189
|
-
|
|
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));
|
|
190
313
|
return true;
|
|
191
314
|
},
|
|
192
315
|
};
|
|
193
316
|
const mode = context.offscreen
|
|
194
|
-
? (offscreenController = new OffscreenController(this.host, context.offscreen.workdir, context.offscreen.slots
|
|
317
|
+
? (offscreenController = new OffscreenController(this.host, context.offscreen.workdir, context.offscreen.slots, {
|
|
318
|
+
primary: reviewArtifactPaneConfig(plan, context),
|
|
319
|
+
})).wrap(baseMode)
|
|
195
320
|
: baseMode;
|
|
196
321
|
detach = this.host.attachParentUi(mode);
|
|
197
322
|
});
|
|
198
323
|
}
|
|
199
324
|
}
|
|
200
|
-
function
|
|
201
|
-
if (state.
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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
|
+
];
|
|
205
333
|
}
|
|
206
|
-
function
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
lines: state.lines,
|
|
210
|
-
cursorRow: state.cursorLine,
|
|
211
|
-
cursorCol: state.cursorCol,
|
|
212
|
-
};
|
|
213
|
-
const changed = applyTextBufferEvent(buffer, event);
|
|
214
|
-
state.lines = buffer.lines;
|
|
215
|
-
state.cursorLine = buffer.cursorRow;
|
|
216
|
-
state.cursorCol = buffer.cursorCol;
|
|
217
|
-
normalizeFeedbackCursor(state);
|
|
218
|
-
return changed;
|
|
334
|
+
function currentRow(state) {
|
|
335
|
+
clampCursor(state);
|
|
336
|
+
return navigableRows(state)[state.cursor] ?? { kind: "approve" };
|
|
219
337
|
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
const
|
|
237
|
-
const
|
|
238
|
-
|
|
239
|
-
let
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
else if (logIdx === cursorLine) {
|
|
249
|
-
const cursor = cursorToVisual(segments, state.cursorCol, line.length, bodyWidth);
|
|
250
|
-
cursorVisualRow += cursor.subRow;
|
|
251
|
-
cursorVisCol = cursor.visCol;
|
|
252
|
-
}
|
|
253
|
-
const rendered = renderWrappedLine(line, bodyWidth, state.mode === "input" && logIdx === cursorLine ? state.cursorCol : null);
|
|
254
|
-
for (let subRow = 0; subRow < rendered.length; subRow++) {
|
|
255
|
-
visualRows.push({ logIdx, subRow, body: rendered[subRow] });
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
const unboundedEditor = showPlaceholder ? 1 : visualRows.length;
|
|
260
|
-
const editorHeight = Math.min(FEEDBACK_EDITOR_MAX_ROWS, Math.max(1, unboundedEditor));
|
|
261
|
-
if (showPlaceholder) {
|
|
262
|
-
state.feedbackScrollTop = 0;
|
|
338
|
+
function addRowCursorIndex(state) {
|
|
339
|
+
return state.userAdded.length === 0 ? 1 : state.userAdded.length;
|
|
340
|
+
}
|
|
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;
|
|
263
366
|
}
|
|
264
367
|
else {
|
|
265
|
-
|
|
266
|
-
|
|
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);
|
|
267
384
|
}
|
|
268
|
-
else
|
|
269
|
-
|
|
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
|
+
}
|
|
270
391
|
}
|
|
271
|
-
|
|
272
|
-
if (state.feedbackScrollTop > maxScroll)
|
|
273
|
-
state.feedbackScrollTop = maxScroll;
|
|
274
|
-
if (state.feedbackScrollTop < 0)
|
|
275
|
-
state.feedbackScrollTop = 0;
|
|
392
|
+
rowEnds[i] = lines.length;
|
|
276
393
|
}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
if (state.
|
|
282
|
-
const head =
|
|
283
|
-
const
|
|
284
|
-
|
|
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);
|
|
285
405
|
}
|
|
286
406
|
else {
|
|
287
|
-
|
|
407
|
+
lines.push(clipToWidth(isCursor
|
|
408
|
+
? `${ACTIVE_SELECTOR}${bold}${green}+ Add feedback (Enter)${reset}`
|
|
409
|
+
: ` ${dim}+ Add feedback${reset}`, width));
|
|
288
410
|
}
|
|
289
|
-
|
|
290
|
-
while (editorBody.length < editorHeight)
|
|
291
|
-
editorBody.push("");
|
|
411
|
+
rowEnds[rowIndex] = lines.length;
|
|
292
412
|
}
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
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;
|
|
299
439
|
}
|
|
300
|
-
const isFirstUnderlying = visual.logIdx === 0 && visual.subRow === 0;
|
|
301
|
-
const prefix = isFirstUnderlying && state.mode === "input"
|
|
302
|
-
? ACTIVE_PREFIX
|
|
303
|
-
: INACTIVE_PREFIX;
|
|
304
|
-
editorBody.push(`${prefix}${visual.body}`);
|
|
305
440
|
}
|
|
441
|
+
if (scrollTop < 0)
|
|
442
|
+
scrollTop = 0;
|
|
443
|
+
if (scrollTop + scrollRows > total)
|
|
444
|
+
scrollTop = total - scrollRows;
|
|
306
445
|
}
|
|
307
|
-
const
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
if (offscreenHint)
|
|
313
|
-
tokens.push(offscreenHint);
|
|
314
|
-
const lines = [
|
|
315
|
-
approveRow,
|
|
316
|
-
...editorBody,
|
|
317
|
-
`${dim}${"─".repeat(width)}${reset}`,
|
|
318
|
-
`${INACTIVE_PREFIX}${renderShortcutHints(tokens)}`,
|
|
319
|
-
];
|
|
320
|
-
const approveRowInBlock = 0;
|
|
321
|
-
const editorStart = 1;
|
|
446
|
+
const listLines = scrollRows === 0
|
|
447
|
+
? []
|
|
448
|
+
: rendered.lines.slice(scrollTop, scrollTop + scrollRows);
|
|
449
|
+
while (listLines.length < scrollRows)
|
|
450
|
+
listLines.push("");
|
|
322
451
|
let cursorRow;
|
|
323
452
|
let cursorCol;
|
|
324
|
-
if (
|
|
325
|
-
cursorRow =
|
|
326
|
-
cursorCol =
|
|
327
|
-
}
|
|
328
|
-
else if (showPlaceholder) {
|
|
329
|
-
cursorRow = editorStart;
|
|
330
|
-
cursorCol = activePrefixWidth;
|
|
453
|
+
if (rendered.editorRow !== undefined) {
|
|
454
|
+
cursorRow = rendered.editorRow - scrollTop;
|
|
455
|
+
cursorCol = rendered.editorCol;
|
|
331
456
|
}
|
|
332
457
|
else {
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
const cursorLine = Math.min(Math.max(0, state.cursorLine), state.lines.length - 1);
|
|
336
|
-
const isFirstUnderlying = cursorLine === 0 && cursorVisualRow === 0;
|
|
337
|
-
const prefixLen = isFirstUnderlying ? activePrefixWidth : inactivePrefixWidth;
|
|
338
|
-
cursorCol = prefixLen + cursorVisCol;
|
|
458
|
+
cursorRow = activeStart - scrollTop;
|
|
459
|
+
cursorCol = currentRow(state).kind === "approve" ? visibleLength(ACTIVE_SELECTOR) : 0;
|
|
339
460
|
}
|
|
340
|
-
if (
|
|
461
|
+
if (scrollRows === 0) {
|
|
341
462
|
cursorRow = 0;
|
|
342
|
-
if (cursorCol < 0)
|
|
343
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
|
+
];
|
|
344
480
|
return {
|
|
345
|
-
lines: lines.map((
|
|
481
|
+
lines: lines.map((line) => clipToWidth(line, width)),
|
|
346
482
|
cursor: { row: cursorRow, col: cursorCol },
|
|
347
483
|
};
|
|
348
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
|
+
}
|
|
349
512
|
/**
|
|
350
|
-
* Compute the plan area's available height given total `rows` and the
|
|
351
|
-
*
|
|
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.
|
|
352
515
|
*/
|
|
353
516
|
function computePlanRows(controlsHeight, rows) {
|
|
354
517
|
return Math.max(1, rows - controlsHeight - 1);
|
|
@@ -357,8 +520,6 @@ function buildPlanLines(plan, context, state, width, planRows) {
|
|
|
357
520
|
const pane = buildPlanPane(plan, context.phaseLabel, width, context.artifactKind ?? "plan");
|
|
358
521
|
const total = pane.lines.length;
|
|
359
522
|
const maxScroll = Math.max(0, total - planRows);
|
|
360
|
-
// Re-pin to the new bottom when the user last scrolled to End and the plan
|
|
361
|
-
// window has since shrunk (e.g. the controls block grew while typing).
|
|
362
523
|
if (state.planAtEnd)
|
|
363
524
|
state.planScrollTop = maxScroll;
|
|
364
525
|
if (state.planScrollTop > maxScroll)
|
|
@@ -369,12 +530,24 @@ function buildPlanLines(plan, context, state, width, planRows) {
|
|
|
369
530
|
while (sliced.length < planRows)
|
|
370
531
|
sliced.push("");
|
|
371
532
|
return {
|
|
372
|
-
lines: sliced.map((
|
|
533
|
+
lines: sliced.map((line) => clipToWidth(line, width)),
|
|
373
534
|
total,
|
|
374
535
|
};
|
|
375
536
|
}
|
|
376
537
|
function buildSurface(plan, context, state, cols, rows) {
|
|
377
|
-
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);
|
|
378
551
|
const planRows = computePlanRows(controls.lines.length, rows);
|
|
379
552
|
const { lines: planLines } = buildPlanLines(plan, context, state, cols, planRows);
|
|
380
553
|
const separator = `${dim}${"─".repeat(cols)}${reset}`;
|
|
@@ -383,16 +556,11 @@ function buildSurface(plan, context, state, cols, rows) {
|
|
|
383
556
|
separator,
|
|
384
557
|
...controls.lines,
|
|
385
558
|
];
|
|
386
|
-
// Pad / clip to exactly `rows` rows. The plan area is the only flex region;
|
|
387
|
-
// controls + separator + margins remain anchored to the bottom.
|
|
388
559
|
while (composed.length < rows)
|
|
389
560
|
composed.push("");
|
|
390
561
|
if (composed.length > rows) {
|
|
391
|
-
// Clip from the top of the plan area; never clip controls.
|
|
392
562
|
composed.splice(0, composed.length - rows);
|
|
393
563
|
}
|
|
394
|
-
// Cursor row sits in the controls block: planRows + 1 (separator) +
|
|
395
|
-
// controls.cursor.row.
|
|
396
564
|
let cursorRow = planRows + 1 + controls.cursor.row;
|
|
397
565
|
if (cursorRow < 0)
|
|
398
566
|
cursorRow = 0;
|
|
@@ -407,12 +575,24 @@ function planMetrics(plan, context, state, host) {
|
|
|
407
575
|
const layout = host.getLayout();
|
|
408
576
|
const cols = Math.max(20, layout.columns);
|
|
409
577
|
const rows = Math.max(1, layout.childRows);
|
|
410
|
-
const
|
|
411
|
-
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);
|
|
412
586
|
const pane = buildPlanPane(plan, context.phaseLabel, cols, context.artifactKind ?? "plan");
|
|
413
587
|
return { planHeight, planLines: pane.lines.length };
|
|
414
588
|
}
|
|
415
589
|
function primaryOffscreenHint(context) {
|
|
416
590
|
return context.offscreen?.slots.find((slot) => slot.binding === "primary")?.hint;
|
|
417
591
|
}
|
|
592
|
+
function reviewArtifactPaneConfig(plan, context) {
|
|
593
|
+
return {
|
|
594
|
+
inquirySide: "left",
|
|
595
|
+
renderArtifact: (width) => buildPlanPane(plan, context.phaseLabel, width, context.artifactKind ?? "plan").lines,
|
|
596
|
+
};
|
|
597
|
+
}
|
|
418
598
|
//# sourceMappingURL=host-prompt.js.map
|