pi-extension-utils 0.5.0 → 0.5.1

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.
@@ -34,7 +34,11 @@ export function pad(s, len) {
34
34
  return s + " ".repeat(Math.max(0, len - vis));
35
35
  }
36
36
  function normalizeRenderableText(text) {
37
- return text
37
+ // Coerce defensively: a non-string here reaches truncateToWidth/text.slice
38
+ // and hard-crashes pi ("text.slice is not a function"). Callers pass values
39
+ // derived from on-disk/agent state that may be out-of-contract.
40
+ const s = typeof text === "string" ? text : text == null ? "" : String(text);
41
+ return s
38
42
  .replaceAll("\t", " ")
39
43
  .replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001A\u001C-\u001F\u007F]/g, "");
40
44
  }
@@ -94,12 +98,12 @@ export function titledTopSegment(theme, opts) {
94
98
  return dash(width);
95
99
  const labelColor = opts.labelColor ?? "text";
96
100
  const tailColor = opts.tailColor ?? "dim";
97
- const tailPlain = opts.tailPlain ?? opts.tail ?? "";
101
+ const tailPlain = normalizeRenderableText(opts.tailPlain ?? opts.tail ?? "");
98
102
  const tailRendered = opts.tailRendered ?? (opts.tail !== undefined ? theme.fg(tailColor, opts.tail) : "");
99
103
  const tailLen = visibleWidth(tailPlain);
100
104
  const canShowTail = tailLen > 0 && width - tailLen >= 14;
101
105
  const labelBudget = Math.max(0, width - (canShowTail ? tailLen + 6 : 4));
102
- const labelText = clipText(opts.label ?? "", labelBudget);
106
+ const labelText = clipText(normalizeRenderableText(opts.label ?? ""), labelBudget);
103
107
  const labelLen = visibleWidth(labelText);
104
108
  if (labelLen === 0)
105
109
  return dash(width);
@@ -118,7 +122,7 @@ export function titledTopSegment(theme, opts) {
118
122
  return "";
119
123
  const labelColor = opts.labelColor ?? "text";
120
124
  const tailColor = opts.tailColor ?? "dim";
121
- const tailPlain = opts.tailPlain ?? opts.tail ?? "";
125
+ const tailPlain = normalizeRenderableText(opts.tailPlain ?? opts.tail ?? "");
122
126
  const rawTailRendered = opts.tailRendered ?? (opts.tail !== undefined ? theme.fg(tailColor, opts.tail) : "");
123
127
  const tailLen = visibleWidth(tailPlain);
124
128
  // With a tail, the minimum chrome is:
@@ -129,7 +133,7 @@ export function titledTopSegment(theme, opts) {
129
133
  const labelBudget = tailFits ? Math.max(0, width - tailLen - 7) : Math.max(0, width - 3);
130
134
  // Defensive: callers occasionally pass undefined labels for transient/legacy
131
135
  // runs that lack agent+mode+label; treat as empty rather than crashing pi.
132
- const rawLabel = opts.label ?? "";
136
+ const rawLabel = normalizeRenderableText(opts.label ?? "");
133
137
  const labelText = truncateToWidth(rawLabel, labelBudget, "").replace(/\u001b\[[0-9;]*m/g, "");
134
138
  const labelStyled = opts.labelBold && theme.bold
135
139
  ? theme.bold(theme.fg(labelColor, labelText))
@@ -153,11 +157,12 @@ export function titledBottomSegment(theme, width, hint, focused) {
153
157
  const dash = (n) => theme.fg("dim", "─".repeat(Math.max(0, n)));
154
158
  if (width <= 0)
155
159
  return "";
156
- if (!hint)
160
+ const safeHint = normalizeRenderableText(hint);
161
+ if (!safeHint)
157
162
  return dash(width);
158
163
  // Reserve at minimum `─ ` + hint + ` `; truncate hint to fit when narrow so we never overflow.
159
164
  const hintBudget = Math.max(0, width - 3);
160
- const clipped = truncateToWidth(hint, hintBudget);
165
+ const clipped = truncateToWidth(safeHint, hintBudget);
161
166
  const clippedLen = visibleWidth(clipped);
162
167
  if (clippedLen === 0)
163
168
  return dash(width);
@@ -196,10 +201,11 @@ export function flatRule(theme, title, width, opts) {
196
201
  if (width <= 0)
197
202
  return "";
198
203
  const dash = (n) => theme.fg("dim", "─".repeat(Math.max(0, n)));
199
- if (!title)
204
+ const safeTitle = normalizeRenderableText(title);
205
+ if (!safeTitle)
200
206
  return dash(width);
201
207
  if (opts?.leadingDashes !== undefined) {
202
- const label = ` ${title} `;
208
+ const label = ` ${safeTitle} `;
203
209
  const labelW = visibleWidth(label);
204
210
  if (labelW + 4 >= width)
205
211
  return dash(width);
@@ -207,7 +213,7 @@ export function flatRule(theme, title, width, opts) {
207
213
  const trailing = Math.max(0, width - labelW - leading);
208
214
  return `${dash(leading)}${theme.fg("dim", label)}${dash(trailing)}`;
209
215
  }
210
- const clipped = truncateToWidth(title, Math.max(0, width - 4));
216
+ const clipped = truncateToWidth(safeTitle, Math.max(0, width - 4));
211
217
  const clippedLen = visibleWidth(clipped);
212
218
  const styled = theme.fg("dim", clipped);
213
219
  const trailing = Math.max(0, width - (clippedLen + 3));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-extension-utils",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "description": "Shared Pi extension utilities for coordinated widgets, fullscreen leases, and logging.",
5
5
  "type": "module",
6
6
  "main": "./dist/src/index.js",