@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.
@@ -99,7 +99,24 @@ export function registerTool(pi: ExtensionAPI): void {
99
99
  done,
100
100
  requestRender: () => tui.requestRender(),
101
101
  }),
102
- { overlay: true },
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.1",
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 { CURSOR_MARKER, decodeKittyPrintable, visibleWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui";
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
- /** Overlay width cap. Defaults to 84 columns. */
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
- lines.push(` ${this.customText}${caret}▌`);
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 outer = Math.max(20, Math.min(width, this.opts.maxWidth ?? DEFAULT_MAX_WIDTH));
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 line of this.contentLines(inner)) {
163
- // Pad to the full inner width. Without this, pi's overlay compositing
164
- // leaves the underlying chat text visible to the right of each line.
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
  }