@d3ara1n/pi-ask-user 2.1.2 → 2.1.3

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/index.ts +24 -6
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@d3ara1n/pi-ask-user",
3
- "version": "2.1.2",
3
+ "version": "2.1.3",
4
4
  "type": "module",
5
5
  "description": "Ask-user tool for pi — renders in the bottom editor slot (not a screen overlay), so the transcript stays visible and scrollable above the panel while you decide",
6
6
  "main": "src/index.ts",
package/src/index.ts CHANGED
@@ -860,6 +860,19 @@ class AskUserPanel implements Component, Focusable {
860
860
  }
861
861
  }
862
862
 
863
+ /** Build a bordered content row that ALWAYS fits innerW: truncateToWidth
864
+ * is the hard safety net (any content — LLM-generated headers, user-typed
865
+ * custom answers, tab names — is clamped so the TUI render can never crash
866
+ * on an over-wide line), padRight then fills short content to keep the
867
+ * right border aligned. Shared by every bordered screen so the width
868
+ * invariant holds uniformly. */
869
+ private makeRow(th: Theme, borderColor: ThemeColor, innerW: number) {
870
+ return (content: string) => {
871
+ const fitted = truncateToWidth(content, innerW);
872
+ return th.fg(borderColor, "│") + padRight(fitted, innerW) + th.fg(borderColor, "│");
873
+ };
874
+ }
875
+
863
876
  // ── render ──
864
877
 
865
878
  render(width: number): string[] {
@@ -933,8 +946,7 @@ class AskUserPanel implements Component, Focusable {
933
946
  const th = this.theme;
934
947
  const innerW = Math.max(20, width - 2);
935
948
  const lines: string[] = [];
936
- const row = (content: string) =>
937
- th.fg("border", "│") + padRight(content, innerW) + th.fg("border", "│");
949
+ const row = this.makeRow(th, "border", innerW);
938
950
 
939
951
  if (this.messageEditing) {
940
952
  return this.renderMessageEditor(width, innerW, th);
@@ -1053,7 +1065,7 @@ class AskUserPanel implements Component, Focusable {
1053
1065
  // unmistakable as the review/confirm screen — not another question. The
1054
1066
  // question screen keeps the default "border" color.
1055
1067
  const bc: import("@earendil-works/pi-coding-agent").ThemeColor = "success";
1056
- const row = (content: string) => th.fg(bc, "│") + padRight(content, innerW) + th.fg(bc, "│");
1068
+ const row = this.makeRow(th, bc, innerW);
1057
1069
  lines.push(th.fg(bc, `╭${"─".repeat(innerW)}╮`));
1058
1070
  lines.push(row(this.renderTabBarContent(th)));
1059
1071
  lines.push(row(""));
@@ -1128,7 +1140,7 @@ class AskUserPanel implements Component, Focusable {
1128
1140
  private renderMessageEditor(_width: number, innerW: number, th: Theme): string[] {
1129
1141
  const lines: string[] = [];
1130
1142
  const bc: import("@earendil-works/pi-coding-agent").ThemeColor = "success";
1131
- const row = (content: string) => th.fg(bc, "│") + padRight(content, innerW) + th.fg(bc, "│");
1143
+ const row = this.makeRow(th, bc, innerW);
1132
1144
  lines.push(th.fg(bc, `╭${"─".repeat(innerW)}╮`));
1133
1145
  lines.push(row(` ${th.fg("accent", th.bold(`${ICON_OTHER} Note to assistant`))}`));
1134
1146
  lines.push(th.fg(bc, `├${"─".repeat(innerW)}┤`));
@@ -1164,7 +1176,13 @@ class AskUserPanel implements Component, Focusable {
1164
1176
  const customAnswered = !!committedCustom;
1165
1177
  const glyph = this.optionGlyph(opt, i, st, multi, th, isCursor, customAnswered);
1166
1178
  // For "Type something.", show the committed text instead of the placeholder.
1167
- const displayLabel = opt.isOther && customAnswered ? committedCustom! : opt.label;
1179
+ // Custom text is arbitrary-length user input truncate to the label
1180
+ // column (the full text appears on the review screen and result card,
1181
+ // both of which wrap). Without this a long custom answer makes the row
1182
+ // exceed the terminal width and crashes the TUI render.
1183
+ const labelMaxW = Math.max(0, innerW - 5); // 1 lead + 2 prefix + 1 glyph + 1 space
1184
+ const displayLabel =
1185
+ opt.isOther && customAnswered ? truncForDisplay(committedCustom!, labelMaxW) : opt.label;
1168
1186
  const labelColor = isCursor
1169
1187
  ? "accent"
1170
1188
  : opt.isOther
@@ -1400,7 +1418,7 @@ class AskUserResultView implements Component {
1400
1418
  };
1401
1419
  for (const q of this.questions) {
1402
1420
  const ans = this.result.answers.find((a) => a.tab === q.tab);
1403
- lines.push(th.fg("muted", q.header));
1421
+ lines.push(truncateToWidth(th.fg("muted", q.header), width));
1404
1422
  if (!ans) {
1405
1423
  lines.push(`${indent}${th.fg("dim", "(no answer)")}`);
1406
1424
  } else if (ans.kind === "skipped") {