@hank-warren/pi-ask-user-question 0.2.1 → 0.2.2
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/ask-user-question.ts +18 -1
- package/package.json +1 -1
- package/view/dialog.ts +35 -9
package/ask-user-question.ts
CHANGED
|
@@ -99,7 +99,24 @@ export function registerTool(pi: ExtensionAPI): void {
|
|
|
99
99
|
done,
|
|
100
100
|
requestRender: () => tui.requestRender(),
|
|
101
101
|
}),
|
|
102
|
-
{
|
|
102
|
+
{
|
|
103
|
+
overlay: true,
|
|
104
|
+
// Bottom-anchored and full width, so the questionnaire sits
|
|
105
|
+
// directly above the input dock instead of floating over the
|
|
106
|
+
// middle of the transcript. Mirrors the geometry
|
|
107
|
+
// @juicesharp/rpiv-ask-user-question used.
|
|
108
|
+
//
|
|
109
|
+
// width MUST stay "100%" in step with the dialog rendering full
|
|
110
|
+
// width: pi composites only the columns the component emits, so
|
|
111
|
+
// a narrower box inside a full-width overlay region would let
|
|
112
|
+
// the transcript show through beside it.
|
|
113
|
+
overlayOptions: {
|
|
114
|
+
anchor: "bottom-center",
|
|
115
|
+
width: "100%",
|
|
116
|
+
maxHeight: "100%",
|
|
117
|
+
margin: { left: 0, right: 0, bottom: 0 },
|
|
118
|
+
},
|
|
119
|
+
},
|
|
103
120
|
);
|
|
104
121
|
|
|
105
122
|
// `custom()` resolving undefined means the host reported hasUI but
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hank-warren/pi-ask-user-question",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Structured questionnaire tool for Pi with numbered options, digit hotkeys and Tab-to-comment, composed from the shared permission-selector component.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
package/view/dialog.ts
CHANGED
|
@@ -18,6 +18,9 @@
|
|
|
18
18
|
* overlay onto the chat line by line and only overwrites the columns the
|
|
19
19
|
* overlay actually emits; short lines let chat text show through and the
|
|
20
20
|
* dialog renders as garbage interleaved with the transcript.
|
|
21
|
+
* 3. ALWAYS clamp lines to the inner width. A single over-long line breaks the
|
|
22
|
+
* right border and spills into the transcript, so `render` truncates as a
|
|
23
|
+
* last-resort invariant no matter what any content source produces.
|
|
21
24
|
*/
|
|
22
25
|
|
|
23
26
|
import {
|
|
@@ -27,7 +30,13 @@ import {
|
|
|
27
30
|
isPrintable,
|
|
28
31
|
} from "@hank-warren/pi-permission-selector/keys.ts";
|
|
29
32
|
import { OptionSelector, type SelectorOption } from "@hank-warren/pi-permission-selector/selector.ts";
|
|
30
|
-
import {
|
|
33
|
+
import {
|
|
34
|
+
CURSOR_MARKER,
|
|
35
|
+
decodeKittyPrintable,
|
|
36
|
+
truncateToWidth,
|
|
37
|
+
visibleWidth,
|
|
38
|
+
wrapTextWithAnsi,
|
|
39
|
+
} from "@earendil-works/pi-tui";
|
|
31
40
|
import type { QuestionnaireSession } from "../questionnaire.ts";
|
|
32
41
|
import type { QuestionnaireResult } from "../tool/schema.ts";
|
|
33
42
|
|
|
@@ -42,13 +51,18 @@ export interface DialogOptions {
|
|
|
42
51
|
/** Called exactly once with the final outcome. */
|
|
43
52
|
done(result: QuestionnaireResult): void;
|
|
44
53
|
requestRender?(): void;
|
|
45
|
-
/**
|
|
54
|
+
/**
|
|
55
|
+
* Optional width cap. Unset means fill the overlay, which is what the
|
|
56
|
+
* bottom-anchored `width: "100%"` overlay wants: a narrower box would let
|
|
57
|
+
* the transcript show through to its right.
|
|
58
|
+
*/
|
|
46
59
|
maxWidth?: number;
|
|
47
60
|
}
|
|
48
61
|
|
|
49
|
-
const DEFAULT_MAX_WIDTH = 84;
|
|
50
62
|
/** Left/right border plus one space of padding on each side. */
|
|
51
63
|
const CHROME_COLUMNS = 4;
|
|
64
|
+
/** Indent for the custom-answer field, in columns. */
|
|
65
|
+
const FIELD_INDENT = " ";
|
|
52
66
|
|
|
53
67
|
export class QuestionnaireDialog {
|
|
54
68
|
/** Focusable — set by the TUI when focus changes. Drives CURSOR_MARKER. */
|
|
@@ -139,11 +153,19 @@ export class QuestionnaireDialog {
|
|
|
139
153
|
lines.push("");
|
|
140
154
|
|
|
141
155
|
if (this.customText !== undefined) {
|
|
156
|
+
// Wrap the typed answer. Without this a long answer ran past the right
|
|
157
|
+
// border and off the screen forever, because an input field renders as
|
|
158
|
+
// one line unless something breaks it up.
|
|
159
|
+
const avail = Math.max(1, inner - FIELD_INDENT.length - 1); // -1 reserves the caret cell
|
|
160
|
+
const wrapped = wrapTextWithAnsi(this.customText, avail);
|
|
142
161
|
// CURSOR_MARKER is a zero-width APC sequence: the TUI strips it and
|
|
143
162
|
// parks the hardware cursor there, so the caret lands in the field
|
|
144
163
|
// instead of at the bottom of the screen.
|
|
145
|
-
const caret = this.focused ? CURSOR_MARKER : ""
|
|
146
|
-
|
|
164
|
+
const caret = `${this.focused ? CURSOR_MARKER : ""}▌`;
|
|
165
|
+
for (let i = 0; i < wrapped.length; i++) {
|
|
166
|
+
const last = i === wrapped.length - 1;
|
|
167
|
+
lines.push(`${FIELD_INDENT}${wrapped[i]}${last ? caret : ""}`);
|
|
168
|
+
}
|
|
147
169
|
lines.push("");
|
|
148
170
|
lines.push(this.style("dim", " enter submit · esc back to options"));
|
|
149
171
|
return lines;
|
|
@@ -154,14 +176,18 @@ export class QuestionnaireDialog {
|
|
|
154
176
|
}
|
|
155
177
|
|
|
156
178
|
render(width: number): string[] {
|
|
157
|
-
const
|
|
179
|
+
const cap = this.opts.maxWidth ?? width;
|
|
180
|
+
const outer = Math.max(20, Math.min(width, cap));
|
|
158
181
|
const inner = outer - CHROME_COLUMNS;
|
|
159
182
|
const border = (text: string) => this.style("dim", text);
|
|
160
183
|
|
|
161
184
|
const out: string[] = [border(`┌${"─".repeat(outer - 2)}┐`)];
|
|
162
|
-
for (const
|
|
163
|
-
//
|
|
164
|
-
//
|
|
185
|
+
for (const raw of this.contentLines(inner)) {
|
|
186
|
+
// Invariant (3): never let a line break the right border, whatever
|
|
187
|
+
// produced it.
|
|
188
|
+
const line = visibleWidth(raw) > inner ? truncateToWidth(raw, inner) : raw;
|
|
189
|
+
// Invariant (2): pad to the full inner width, or pi's overlay
|
|
190
|
+
// compositing leaves chat text visible to the right of each line.
|
|
165
191
|
const pad = Math.max(0, inner - visibleWidth(line));
|
|
166
192
|
out.push(`${border("│")} ${line}${" ".repeat(pad)} ${border("│")}`);
|
|
167
193
|
}
|