pi-ui-extend 0.1.15 → 0.1.17

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 (70) hide show
  1. package/dist/app/app.d.ts +2 -0
  2. package/dist/app/app.js +21 -6
  3. package/dist/app/commands/command-controller.js +1 -0
  4. package/dist/app/commands/command-host.d.ts +2 -0
  5. package/dist/app/commands/command-navigation-actions.d.ts +9 -0
  6. package/dist/app/commands/command-navigation-actions.js +62 -3
  7. package/dist/app/commands/command-registry.d.ts +1 -0
  8. package/dist/app/commands/command-registry.js +8 -0
  9. package/dist/app/constants.d.ts +0 -1
  10. package/dist/app/constants.js +0 -1
  11. package/dist/app/icons.d.ts +1 -0
  12. package/dist/app/icons.js +2 -0
  13. package/dist/app/input/input-action-controller.d.ts +1 -0
  14. package/dist/app/input/input-action-controller.js +5 -4
  15. package/dist/app/popup/menu-items-controller.d.ts +2 -0
  16. package/dist/app/popup/menu-items-controller.js +37 -14
  17. package/dist/app/popup/popup-menu-controller.js +30 -5
  18. package/dist/app/rendering/editor-panels.js +20 -9
  19. package/dist/app/rendering/popup-menu-renderer.d.ts +12 -0
  20. package/dist/app/rendering/popup-menu-renderer.js +151 -53
  21. package/dist/app/rendering/render-controller.js +25 -15
  22. package/dist/app/rendering/render-text.js +5 -2
  23. package/dist/app/rendering/status-line-renderer.d.ts +7 -0
  24. package/dist/app/rendering/status-line-renderer.js +191 -94
  25. package/dist/app/rendering/toast-controller.d.ts +1 -0
  26. package/dist/app/rendering/toast-controller.js +17 -0
  27. package/dist/app/screen/mouse-controller.js +4 -4
  28. package/dist/app/screen/scroll-controller.d.ts +1 -0
  29. package/dist/app/screen/scroll-controller.js +6 -0
  30. package/dist/app/screen/status-controller.js +2 -1
  31. package/dist/app/session/request-history.d.ts +4 -0
  32. package/dist/app/session/request-history.js +11 -0
  33. package/dist/app/session/session-search.js +10 -0
  34. package/dist/app/session/tabs-controller.d.ts +4 -4
  35. package/dist/app/session/tabs-controller.js +64 -6
  36. package/dist/app/todo/todo-model.d.ts +2 -2
  37. package/dist/app/todo/todo-model.js +15 -17
  38. package/dist/app/types.d.ts +12 -4
  39. package/dist/config.d.ts +1 -0
  40. package/dist/config.js +10 -1
  41. package/dist/default-pix-config.js +2 -0
  42. package/dist/fuzzy.d.ts +2 -0
  43. package/dist/fuzzy.js +27 -7
  44. package/dist/input-editor.d.ts +9 -0
  45. package/dist/input-editor.js +52 -0
  46. package/dist/schemas/pix-schema.d.ts +1 -0
  47. package/dist/schemas/pix-schema.js +1 -0
  48. package/dist/theme.js +6 -6
  49. package/dist/ui.d.ts +8 -0
  50. package/external/pi-tools-suite/README.md +2 -2
  51. package/external/pi-tools-suite/src/antigravity-auth/auth-store.ts +40 -5
  52. package/external/pi-tools-suite/src/antigravity-auth/commands.ts +1 -37
  53. package/external/pi-tools-suite/src/antigravity-auth/constants.ts +0 -2
  54. package/external/pi-tools-suite/src/antigravity-auth/index.ts +3 -16
  55. package/external/pi-tools-suite/src/antigravity-auth/oauth.ts +33 -17
  56. package/external/pi-tools-suite/src/antigravity-auth/status.ts +1 -1
  57. package/external/pi-tools-suite/src/antigravity-auth/stream.ts +4 -12
  58. package/external/pi-tools-suite/src/antigravity-auth/types.ts +21 -0
  59. package/external/pi-tools-suite/src/todo/index.ts +3 -64
  60. package/external/pi-tools-suite/src/todo/state/persistence.ts +0 -1
  61. package/external/pi-tools-suite/src/todo/state/state-reducer.ts +7 -37
  62. package/external/pi-tools-suite/src/todo/todo.ts +2 -18
  63. package/external/pi-tools-suite/src/todo/tool/response-envelope.ts +2 -11
  64. package/external/pi-tools-suite/src/todo/tool/types.ts +0 -29
  65. package/external/pi-tools-suite/src/todo/view/format.ts +1 -3
  66. package/external/pi-tools-suite/src/tool-descriptions.ts +5 -4
  67. package/external/pi-tools-suite/src/usage/lib/google.ts +50 -30
  68. package/external/pi-tools-suite/src/usage/lib/types.ts +12 -2
  69. package/package.json +1 -1
  70. package/schemas/pix.json +5 -0
@@ -1,9 +1,13 @@
1
1
  import { colorLine } from "../../theme.js";
2
2
  import { stringDisplayWidth } from "../../terminal-width.js";
3
+ import { resolveColor, resolveModelColor } from "../../config.js";
3
4
  import { SLASH_COMMAND_DESCRIPTION_COLUMN, } from "../constants.js";
4
5
  import { APP_ICONS } from "../icons.js";
5
6
  import { ellipsizeDisplay, padOrTrimPlain, sanitizeText } from "./render-text.js";
7
+ import { modelProviderThemeColor, thinkingLevelThemeColor } from "./status-line-renderer.js";
6
8
  const POPUP_MENU_ESCAPE_BUTTON = "Esc";
9
+ const POPUP_MENU_DESCRIPTION_GAP = " ";
10
+ const POPUP_MENU_HEADER_SIDE_PADDING = 2;
7
11
  export class PopupMenuRenderer {
8
12
  host;
9
13
  constructor(host) {
@@ -25,7 +29,7 @@ export class PopupMenuRenderer {
25
29
  const menuWidth = this.effectivePopupMenuWidth(width);
26
30
  const rightMargin = Math.max(0, width - margin - menuWidth);
27
31
  const selected = line.target?.kind === "popup-menu" && activeMenu.selectedIndex === line.target.index;
28
- const foreground = this.popupLineForeground(line, selected);
32
+ const foreground = this.popupLineForeground(line);
29
33
  const background = this.popupLineBackground(line, selected);
30
34
  const plain = `${" ".repeat(margin)}${padOrTrimPlain(line.text, menuWidth)}${" ".repeat(rightMargin)}`;
31
35
  if (this.host.screenStyler.selectionRangeForRow(row, width)) {
@@ -59,7 +63,7 @@ export class PopupMenuRenderer {
59
63
  for (const item of menu.visibleItems()) {
60
64
  const label = item.label.padEnd(18, " ");
61
65
  const description = item.description ?? "";
62
- const marker = item.selected ? "" : " ";
66
+ const marker = item.selected ? "" : " ";
63
67
  const rawText = `${marker} ${label}${description}`;
64
68
  const text = ellipsizeDisplay(rawText, options.userContentWidth);
65
69
  const line = options.userLine(text);
@@ -73,7 +77,7 @@ export class PopupMenuRenderer {
73
77
  {
74
78
  start: labelStart,
75
79
  end: labelEnd,
76
- foreground: this.userMessageActionForeground(item.selected, item.value),
80
+ foreground: this.userMessageActionForeground(item.value),
77
81
  bold: item.selected,
78
82
  },
79
83
  ...(descriptionStart < contentStart + text.length
@@ -91,11 +95,12 @@ export class PopupMenuRenderer {
91
95
  lines.push({ text: " No matching slash commands", variant: "muted" });
92
96
  }
93
97
  for (const item of visibleItems) {
94
- const command = item.label.padEnd(SLASH_COMMAND_DESCRIPTION_COLUMN, " ");
95
- const description = item.description ?? "";
98
+ const marker = item.selected ? " " : " ";
99
+ const text = `${marker}${this.labelDescriptionText(item.label, item.description, width - 2)}`;
96
100
  lines.push({
97
- text: `${command}${description}`,
98
- variant: item.selected ? "accent" : "normal",
101
+ text,
102
+ variant: "normal",
103
+ segments: this.itemHighlightSegments(item, text),
99
104
  target: { kind: "popup-menu", index: item.index },
100
105
  });
101
106
  }
@@ -111,11 +116,12 @@ export class PopupMenuRenderer {
111
116
  });
112
117
  }
113
118
  for (const item of visibleItems) {
114
- const model = item.label.padEnd(SLASH_COMMAND_DESCRIPTION_COLUMN, " ");
115
- const description = item.description ?? "";
119
+ const marker = item.selected ? " " : " ";
120
+ const text = `${marker}${this.labelDescriptionText(item.label, item.description, width - 2)}`;
116
121
  lines.push({
117
- text: `${model}${description}`,
118
- variant: this.selectableItemVariant(item.selected, item.value),
122
+ text,
123
+ variant: this.selectableItemVariant(item.value),
124
+ segments: [...this.modelMenuItemSegments(item.value), ...this.itemHighlightSegments(item, text)],
119
125
  target: { kind: "popup-menu", index: item.index },
120
126
  });
121
127
  }
@@ -128,11 +134,12 @@ export class PopupMenuRenderer {
128
134
  lines.push({ text: " No matching thinking levels", variant: "muted" });
129
135
  }
130
136
  for (const item of visibleItems) {
131
- const level = item.label.padEnd(SLASH_COMMAND_DESCRIPTION_COLUMN, " ");
132
- const description = item.description ?? "";
137
+ const marker = item.selected ? " " : " ";
138
+ const text = `${marker}${this.labelDescriptionText(item.label, item.description, width - 2)}`;
133
139
  lines.push({
134
- text: `${level}${description}`,
135
- variant: this.selectableItemVariant(item.selected, item.value),
140
+ text,
141
+ variant: this.selectableItemVariant(item.value),
142
+ segments: this.thinkingMenuItemSegments(item.value),
136
143
  target: { kind: "popup-menu", index: item.index },
137
144
  });
138
145
  }
@@ -151,12 +158,13 @@ export class PopupMenuRenderer {
151
158
  for (const item of visibleItems) {
152
159
  const label = item.label;
153
160
  const description = item.description ?? "";
154
- const text = `${label} ${description}`;
155
- const segments = this.resumeMenuItemSegments(item.value, label, description, text);
161
+ const marker = item.selected ? "▶ " : " ";
162
+ const text = `${marker}${label} ${description}`;
163
+ const segments = [...(this.resumeMenuItemSegments(item.value, label, description, text) ?? []), ...this.itemHighlightSegments(item, text)];
156
164
  lines.push({
157
165
  text,
158
- variant: item.selected ? "accent" : "normal",
159
- ...(segments ? { segments } : {}),
166
+ variant: "normal",
167
+ ...(segments.length === 0 ? {} : { segments }),
160
168
  target: { kind: "popup-menu", index: item.index },
161
169
  });
162
170
  }
@@ -170,18 +178,24 @@ export class PopupMenuRenderer {
170
178
  }
171
179
  renderUserMessageJumpMenu(width, menu, directQuery) {
172
180
  const lines = [this.popupMenuHeader("Jump to user message", width)];
173
- if (!this.hasPopupActionItems(menu.items)) {
181
+ if (this.host.userMessageJumpLoading) {
182
+ lines.push({ text: ` ${APP_ICONS.timerSand} Loading user messages`, variant: "muted" });
183
+ }
184
+ else if (!this.hasPopupActionItems(menu.items)) {
174
185
  lines.push({
175
186
  text: this.host.entries.some((entry) => entry.kind === "user") ? " No matching user messages" : " No user messages yet",
176
187
  variant: "muted",
177
188
  });
178
189
  }
179
- const labelWidth = Math.max(1, width);
190
+ const labelWidth = Math.max(1, width - 2);
180
191
  for (const item of menu.visibleItems()) {
181
192
  const label = ellipsizeDisplay(item.label, labelWidth);
193
+ const marker = item.selected ? "▶ " : " ";
194
+ const text = `${marker}${label}`;
182
195
  lines.push({
183
- text: label,
184
- variant: item.selected ? "accent" : "normal",
196
+ text,
197
+ variant: "normal",
198
+ segments: this.itemHighlightSegments(item, text),
185
199
  target: { kind: "popup-menu", index: item.index },
186
200
  });
187
201
  }
@@ -193,11 +207,10 @@ export class PopupMenuRenderer {
193
207
  renderQueueMessageMenu(width, menu) {
194
208
  const lines = [this.popupMenuHeader("Queued message", width)];
195
209
  for (const item of menu.visibleItems()) {
196
- const label = item.label.padEnd(18, " ");
197
- const description = item.description ?? "";
210
+ const marker = item.selected ? " " : " ";
198
211
  lines.push({
199
- text: `${label}${description}`,
200
- variant: this.queueMessageItemVariant(item.selected, item.value),
212
+ text: `${marker}${this.labelDescriptionText(item.label, item.description, width - 2, 16)}`,
213
+ variant: this.queueMessageItemVariant(item.value),
201
214
  target: { kind: "popup-menu", index: item.index },
202
215
  });
203
216
  }
@@ -209,11 +222,13 @@ export class PopupMenuRenderer {
209
222
  lines.push({ text: ` ${request?.options.emptyText ?? "No matching items"}`, variant: "muted" });
210
223
  }
211
224
  for (const item of menu.visibleItems()) {
212
- const label = item.label.padEnd(SLASH_COMMAND_DESCRIPTION_COLUMN, " ");
213
- const description = item.description ?? "";
225
+ const marker = item.selected ? " " : " ";
226
+ const text = `${marker}${this.labelDescriptionText(item.label, item.description, width - 2)}`;
227
+ const segments = this.sdkMenuItemSegments(item, text);
214
228
  lines.push({
215
- text: `${label}${description}`,
216
- variant: this.sdkItemVariant(item.selected, item.value),
229
+ text,
230
+ variant: this.sdkItemVariant(item.value),
231
+ ...(segments.length === 0 ? {} : { segments }),
217
232
  target: { kind: "popup-menu", index: item.index },
218
233
  });
219
234
  }
@@ -225,39 +240,117 @@ export class PopupMenuRenderer {
225
240
  hasPopupActionItems(items) {
226
241
  return items.length > 0;
227
242
  }
228
- userMessageActionForeground(selected, value) {
229
- if (selected)
230
- return this.host.theme.colors.accent;
243
+ labelDescriptionText(label, description, width, labelColumn = SLASH_COMMAND_DESCRIPTION_COLUMN) {
244
+ const safeLabel = sanitizeText(label).replace(/\s+/gu, " ");
245
+ const safeDescription = description ? sanitizeText(description).replace(/\s+/gu, " ") : "";
246
+ if (!safeDescription)
247
+ return ellipsizeDisplay(safeLabel, width);
248
+ const gapWidth = stringDisplayWidth(POPUP_MENU_DESCRIPTION_GAP);
249
+ const labelDisplayWidth = stringDisplayWidth(safeLabel);
250
+ const descriptionDisplayWidth = stringDisplayWidth(safeDescription);
251
+ if (width <= gapWidth + 1)
252
+ return ellipsizeDisplay(safeLabel, width);
253
+ if (labelDisplayWidth <= labelColumn && labelColumn + gapWidth + descriptionDisplayWidth <= width) {
254
+ return `${safeLabel}${" ".repeat(labelColumn - labelDisplayWidth)}${POPUP_MENU_DESCRIPTION_GAP}${safeDescription}`;
255
+ }
256
+ if (labelDisplayWidth + gapWidth + descriptionDisplayWidth <= width) {
257
+ return `${safeLabel}${POPUP_MENU_DESCRIPTION_GAP}${safeDescription}`;
258
+ }
259
+ const labelWidth = descriptionDisplayWidth < width - gapWidth - 1
260
+ ? Math.max(1, width - gapWidth - descriptionDisplayWidth)
261
+ : Math.max(1, Math.min(labelColumn, width - gapWidth - 1));
262
+ const visibleLabel = ellipsizeDisplay(safeLabel, labelWidth);
263
+ const padding = " ".repeat(Math.max(0, labelWidth - stringDisplayWidth(visibleLabel)));
264
+ return `${visibleLabel}${padding}${POPUP_MENU_DESCRIPTION_GAP}${safeDescription}`;
265
+ }
266
+ userMessageActionForeground(value) {
231
267
  if (value === "undo")
232
268
  return this.host.theme.colors.error;
233
269
  return this.host.theme.colors.inputForeground;
234
270
  }
235
- selectableItemVariant(selected, value) {
236
- if (selected)
237
- return "accent";
271
+ selectableItemVariant(value) {
238
272
  return value.current ? "muted" : "normal";
239
273
  }
240
- queueMessageItemVariant(selected, value) {
241
- if (selected)
242
- return "accent";
274
+ thinkingMenuItemSegments(value) {
275
+ const markerOffset = 2; // "▶ " or " "
276
+ return [{
277
+ start: markerOffset,
278
+ end: markerOffset + value.level.length,
279
+ foreground: thinkingLevelThemeColor(value.level, this.host.theme.colors, this.availableThinkingLevels()),
280
+ }];
281
+ }
282
+ modelMenuItemSegments(value) {
283
+ const markerOffset = 2; // "▶ " or " "
284
+ return [{
285
+ start: markerOffset,
286
+ end: markerOffset + value.ref.length,
287
+ foreground: this.modelMenuItemColor(value),
288
+ }];
289
+ }
290
+ modelMenuItemColor(value) {
291
+ const configuredColor = this.host.modelColors
292
+ ? resolveModelColor(value.ref, this.host.modelColors)
293
+ : undefined;
294
+ return configuredColor
295
+ ? resolveColor(configuredColor, this.host.theme.colors)
296
+ : modelProviderThemeColor(value.model.provider, this.host.theme.colors);
297
+ }
298
+ availableThinkingLevels() {
299
+ const levels = this.host.session?.getAvailableThinkingLevels();
300
+ return Array.isArray(levels) && levels.length > 0 ? levels.map(String) : ["off", "minimal", "low", "medium", "high", "xhigh"];
301
+ }
302
+ queueMessageItemVariant(value) {
243
303
  return value === "cancel" ? "error" : "normal";
244
304
  }
245
- sdkItemVariant(selected, value) {
246
- if (selected)
247
- return "accent";
305
+ sdkItemVariant(value) {
248
306
  return value.variant ?? "normal";
249
307
  }
308
+ sdkMenuItemSegments(item, text) {
309
+ return [
310
+ ...this.highlightSegments(item.labelHighlightRanges ?? item.value.labelHighlightRanges ?? [], text, 2),
311
+ ...this.descriptionHighlightSegments(item.description, item.descriptionHighlightRanges ?? item.value.descriptionHighlightRanges ?? [], text),
312
+ ];
313
+ }
314
+ itemHighlightSegments(item, text) {
315
+ return this.highlightSegments(item.labelHighlightRanges ?? [], text, 2);
316
+ }
317
+ highlightSegments(ranges, text, markerOffset) {
318
+ if (ranges.length === 0)
319
+ return [];
320
+ return ranges.flatMap((range) => {
321
+ const start = Math.max(markerOffset, markerOffset + range.start);
322
+ const end = Math.min(text.length, markerOffset + range.end);
323
+ if (end <= start)
324
+ return [];
325
+ return [{
326
+ start,
327
+ end,
328
+ foreground: this.host.theme.colors.accent,
329
+ bold: true,
330
+ }];
331
+ });
332
+ }
333
+ descriptionHighlightSegments(description, ranges, text) {
334
+ if (!description || ranges.length === 0)
335
+ return [];
336
+ const safeDescription = sanitizeText(description).replace(/\s+/gu, " ");
337
+ const descriptionStart = text.indexOf(safeDescription, 2);
338
+ if (descriptionStart < 0)
339
+ return [];
340
+ return this.highlightSegments(ranges, text, descriptionStart);
341
+ }
250
342
  resumeMenuItemSegments(value, label, description, text) {
251
343
  if (value.kind !== "session")
252
344
  return undefined;
253
345
  const sessionLabel = value.session.name ?? value.session.firstMessage.slice(0, 50);
254
- const sessionLabelStart = Math.max(0, label.length - sessionLabel.length);
346
+ const markerOffset = 2; // "▶ " or " "
347
+ const sessionLabelStart = Math.max(0, label.length - sessionLabel.length) + markerOffset;
255
348
  const muted = this.host.theme.colors.popupMuted;
256
349
  const segments = [];
257
- if (sessionLabelStart > 0)
258
- segments.push({ start: 0, end: sessionLabelStart, foreground: muted });
350
+ if (sessionLabelStart > markerOffset)
351
+ segments.push({ start: markerOffset, end: sessionLabelStart, foreground: muted });
259
352
  if (description.length > 0)
260
- segments.push({ start: label.length, end: text.length, foreground: muted });
353
+ segments.push({ start: markerOffset + label.length, end: text.length, foreground: muted });
261
354
  return segments.length > 0 ? segments : undefined;
262
355
  }
263
356
  popupMenuHeader(title, width) {
@@ -268,10 +361,8 @@ export class PopupMenuRenderer {
268
361
  target: { kind: "popup-menu-close" },
269
362
  };
270
363
  }
271
- popupLineForeground(line, selected) {
364
+ popupLineForeground(line) {
272
365
  const colors = this.host.theme.colors;
273
- if (selected)
274
- return colors.popupSelectedForeground;
275
366
  if (line.colorOverride)
276
367
  return line.colorOverride;
277
368
  switch (line.variant) {
@@ -300,8 +391,15 @@ export function formatPopupMenuHeader(title, width) {
300
391
  const buttonWidth = stringDisplayWidth(POPUP_MENU_ESCAPE_BUTTON);
301
392
  if (safeWidth <= buttonWidth + 1)
302
393
  return padOrTrimPlain(POPUP_MENU_ESCAPE_BUTTON, safeWidth);
303
- const titleWidth = safeWidth - buttonWidth - 1;
394
+ const sidePadding = safeWidth >= buttonWidth + POPUP_MENU_HEADER_SIDE_PADDING * 2 + 2
395
+ ? POPUP_MENU_HEADER_SIDE_PADDING
396
+ : 1;
397
+ const contentWidth = Math.max(1, safeWidth - sidePadding * 2);
398
+ if (contentWidth <= buttonWidth + 1) {
399
+ return padOrTrimPlain(`${" ".repeat(sidePadding)}${POPUP_MENU_ESCAPE_BUTTON}`, safeWidth);
400
+ }
401
+ const titleWidth = contentWidth - buttonWidth - 1;
304
402
  const titleText = ellipsizeDisplay(sanitizedTitle, titleWidth);
305
- const gapWidth = Math.max(1, safeWidth - stringDisplayWidth(titleText) - buttonWidth);
306
- return `${titleText}${" ".repeat(gapWidth)}${POPUP_MENU_ESCAPE_BUTTON}`;
403
+ const gapWidth = Math.max(1, contentWidth - stringDisplayWidth(titleText) - buttonWidth);
404
+ return `${" ".repeat(sidePadding)}${titleText}${" ".repeat(gapWidth)}${POPUP_MENU_ESCAPE_BUTTON}${" ".repeat(sidePadding)}`;
307
405
  }
@@ -189,18 +189,26 @@ export class AppRenderController {
189
189
  setRenderedBackground(row, rendered.line?.backgroundOverride);
190
190
  appendFrameOutput("inputStatus", row, this.renderFrameRow(row, rendered.output(row)));
191
191
  }
192
+ const statusLayout = this.deps.statusLineRenderer.layout(columns);
193
+ const statusLineRenderer = this.deps.statusLineRenderer;
194
+ const inputBorderWidgetsLayout = statusLineRenderer.inputBorderWidgetsLayout?.(columns);
192
195
  if (inputBottomSeparatorRow > 1) {
193
196
  const separatorText = inputFrameLine(columns, "bottom");
194
197
  const row = toScreenRow(inputBottomSeparatorRow);
195
198
  if (row < statusRow) {
196
- this.deps.mouseController.renderedRowTexts.set(row, separatorText);
197
- appendFrameOutput("inputStatus", row, this.renderFrameRow(row, this.deps.screenStyler.styleLine(row, separatorText, columns, {
198
- foreground: this.deps.theme.colors.inputBorder,
199
- })));
199
+ const text = inputBorderWidgetsLayout
200
+ ? overlayText(separatorText, inputBorderWidgetsLayout.inputBorderWidgetStartColumn ?? 1, inputBorderWidgetsLayout.text)
201
+ : separatorText;
202
+ this.deps.mouseController.renderedRowTexts.set(row, text);
203
+ const output = inputBorderWidgetsLayout && statusLineRenderer.renderInputBorderWidgets
204
+ ? statusLineRenderer.renderInputBorderWidgets(row, inputBorderWidgetsLayout, separatorText, columns)
205
+ : this.deps.screenStyler.styleLine(row, separatorText, columns, {
206
+ foreground: this.deps.theme.colors.inputBorder,
207
+ });
208
+ appendFrameOutput("inputStatus", row, this.renderFrameRow(row, output));
200
209
  }
201
210
  }
202
- const statusLayout = this.deps.statusLineRenderer.layout(columns);
203
- this.updateStatusMouseState(statusLayout, statusRow);
211
+ this.updateStatusMouseState(statusLayout, statusRow, inputBorderWidgetsLayout, toScreenRow(inputBottomSeparatorRow));
204
212
  appendFrameOutput("inputStatus", statusRow, this.renderFrameRow(statusRow, this.deps.statusLineRenderer.render(statusRow, statusLayout, columns)));
205
213
  const voiceProgressOverlay = this.renderVoiceProgressOverlay(this.deps.voiceProgressOverlayText(), columns, statusRow);
206
214
  if (voiceProgressOverlay) {
@@ -278,20 +286,22 @@ export class AppRenderController {
278
286
  const text = fixedCellText(flash.text, width);
279
287
  return `\x1b[${flash.y};${flash.startColumn}H\x1b[7m${text}${ANSI_RESET}`;
280
288
  }
281
- updateStatusMouseState(statusLayout, statusRow) {
289
+ updateStatusMouseState(statusLayout, statusRow, inputBorderWidgetsLayout, inputBorderWidgetsRow) {
290
+ const widgetLayout = inputBorderWidgetsLayout;
291
+ const widgetRow = inputBorderWidgetsRow ?? statusRow;
282
292
  this.deps.mouseController.statusModelTarget = this.deps.statusLineRenderer.modelTarget(statusLayout.text, statusRow);
283
293
  this.deps.mouseController.statusThinkingTarget = this.deps.statusLineRenderer.thinkingTarget(statusLayout.text, statusRow);
284
294
  this.deps.mouseController.statusContextTarget = this.deps.statusLineRenderer.contextTarget(statusLayout.text, statusRow, statusLayout);
285
295
  this.deps.mouseController.statusModelUsageTarget = this.deps.statusLineRenderer.modelUsageTarget(statusLayout.text, statusRow, statusLayout);
286
- this.deps.mouseController.statusDraftQueueTarget = this.deps.statusLineRenderer.draftQueueTarget?.(statusLayout, statusRow);
287
- this.deps.mouseController.statusUserJumpTarget = this.deps.statusLineRenderer.userJumpTarget?.(statusLayout, statusRow);
288
- this.deps.mouseController.statusThinkingExpandTarget = this.deps.statusLineRenderer.thinkingExpandTarget?.(statusLayout, statusRow);
289
- this.deps.mouseController.statusCompactToolsTarget = this.deps.statusLineRenderer.compactToolsTarget?.(statusLayout, statusRow);
290
- this.deps.mouseController.statusTerminalBellSoundTarget = this.deps.statusLineRenderer.terminalBellSoundTarget?.(statusLayout, statusRow);
296
+ this.deps.mouseController.statusDraftQueueTarget = widgetLayout ? this.deps.statusLineRenderer.draftQueueTarget?.(widgetLayout, widgetRow) : undefined;
297
+ this.deps.mouseController.statusUserJumpTarget = widgetLayout ? this.deps.statusLineRenderer.userJumpTarget?.(widgetLayout, widgetRow) : undefined;
298
+ this.deps.mouseController.statusThinkingExpandTarget = widgetLayout ? this.deps.statusLineRenderer.thinkingExpandTarget?.(widgetLayout, widgetRow) : undefined;
299
+ this.deps.mouseController.statusCompactToolsTarget = widgetLayout ? this.deps.statusLineRenderer.compactToolsTarget?.(widgetLayout, widgetRow) : undefined;
300
+ this.deps.mouseController.statusTerminalBellSoundTarget = widgetLayout ? this.deps.statusLineRenderer.terminalBellSoundTarget?.(widgetLayout, widgetRow) : undefined;
291
301
  this.deps.mouseController.statusSessionTarget = this.deps.statusLineRenderer.sessionTarget(statusLayout.text, statusRow, statusLayout.sessionLabel, statusLayout.workspaceLabel);
292
- this.deps.mouseController.statusPromptEnhancerTarget = this.deps.statusLineRenderer.promptEnhancerTarget(statusLayout, statusRow);
293
- this.deps.mouseController.statusVoiceMicTarget = this.deps.statusLineRenderer.voiceMicTarget(statusLayout, statusRow);
294
- this.deps.mouseController.statusVoiceLanguageTarget = this.deps.statusLineRenderer.voiceLanguageTarget(statusLayout, statusRow);
302
+ this.deps.mouseController.statusPromptEnhancerTarget = widgetLayout ? this.deps.statusLineRenderer.promptEnhancerTarget(widgetLayout, widgetRow) : undefined;
303
+ this.deps.mouseController.statusVoiceMicTarget = widgetLayout ? this.deps.statusLineRenderer.voiceMicTarget(widgetLayout, widgetRow) : undefined;
304
+ this.deps.mouseController.statusVoiceLanguageTarget = widgetLayout ? this.deps.statusLineRenderer.voiceLanguageTarget(widgetLayout, widgetRow) : undefined;
295
305
  this.deps.mouseController.renderedRowTexts.set(statusRow, statusLayout.text);
296
306
  }
297
307
  renderVoiceProgressOverlay(message, width, rows) {
@@ -21,9 +21,12 @@ export function shortHash(text) {
21
21
  export function hasLspDiagnosticsAfterMutation(output) {
22
22
  return /lsp\s+(?:errors?|warnings?|diagnostics?)\s+after\s+mutation/i.test(output) || /lsp\s+diagnostics\s*:/i.test(output);
23
23
  }
24
- const LSP_DIAGNOSTIC_MUTATION_TOOLS = new Set(["apply_patch", "ast_apply"]);
24
+ const LSP_DIAGNOSTIC_MUTATION_TOOLS = new Set(["apply_patch", "ast_apply", "edit", "write"]);
25
25
  export function hasToolLspDiagnosticsAfterMutation(entry) {
26
- return LSP_DIAGNOSTIC_MUTATION_TOOLS.has(entry.toolName.toLowerCase()) && hasLspDiagnosticsAfterMutation(entry.output);
26
+ return LSP_DIAGNOSTIC_MUTATION_TOOLS.has(normalizedToolName(entry.toolName)) && hasLspDiagnosticsAfterMutation(entry.output);
27
+ }
28
+ function normalizedToolName(toolName) {
29
+ return toolName.split(/[.:/]/).filter(Boolean).at(-1)?.trim().toLowerCase() ?? toolName.toLowerCase();
27
30
  }
28
31
  export function lspDiagnosticSeverityForLine(line) {
29
32
  const counts = lspDiagnosticCounts(line);
@@ -35,7 +35,10 @@ export declare class StatusLineRenderer {
35
35
  private readonly host;
36
36
  constructor(host: StatusLineRendererHost);
37
37
  layout(width: number): StatusLineLayout;
38
+ inputBorderWidgetsLayout(width: number): StatusLineLayout | undefined;
38
39
  render(row: number, layout: StatusLineLayout, width: number): string;
40
+ renderInputBorderWidgets(row: number, layout: StatusLineLayout, borderText: string, width: number): string;
41
+ private inputBorderWidgetSegments;
39
42
  modelTarget(statusText: string, row: number): StatusModelTarget | undefined;
40
43
  thinkingTarget(statusText: string, row: number): StatusThinkingTarget | undefined;
41
44
  contextTarget(statusText: string, row: number, layout: StatusLineLayout): StatusContextTarget | undefined;
@@ -66,10 +69,14 @@ export declare class StatusLineRenderer {
66
69
  private modelUsageProgressColor;
67
70
  private modelProviderColor;
68
71
  private thinkingLevelColor;
72
+ private statusThinkingDisplayLabel;
69
73
  private availableThinkingLevels;
70
74
  private contextBarLabel;
71
75
  private widgetLayout;
76
+ private iconButtonText;
77
+ private voiceBorderWidgetText;
72
78
  private voiceWidgetLayout;
79
+ private voiceBorderWidgetParts;
73
80
  private statusDotColor;
74
81
  }
75
82
  export declare function thinkingLevelThemeColor(label: string, colors: Theme["colors"], availableLevels?: readonly string[]): string;