@oh-my-pi/pi-coding-agent 16.1.17 → 16.1.18

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.
@@ -40,6 +40,9 @@ export class ExtensionList implements Component {
40
40
  #focused = false;
41
41
  #masterSwitchProvider: string | null = null;
42
42
  #maxVisible: number;
43
+ #hoveredIndex: number | null = null;
44
+ /** Item rows rendered in the last frame, for mouse hit-testing. */
45
+ #visibleCount = 0;
43
46
 
44
47
  constructor(
45
48
  private extensions: Extension[],
@@ -108,6 +111,7 @@ export class ExtensionList implements Component {
108
111
 
109
112
  render(width: number): readonly string[] {
110
113
  const lines: string[] = [];
114
+ this.#visibleCount = 0;
111
115
 
112
116
  // Search bar
113
117
  const searchPrefix = theme.fg("muted", "Search: ");
@@ -136,15 +140,20 @@ export class ExtensionList implements Component {
136
140
  for (let i = startIdx; i < endIdx; i++) {
137
141
  const listItem = this.#listItems[i];
138
142
  const isSelected = this.#focused && i === this.#selectedIndex;
143
+ const isHovered = this.#focused && i === this.#hoveredIndex && !isSelected;
139
144
 
145
+ let rowStr: string;
140
146
  if (listItem.type === "master") {
141
- rows.push(this.#renderMasterSwitch(listItem, isSelected, rowWidth));
147
+ rowStr = this.#renderMasterSwitch(listItem, isSelected, rowWidth);
142
148
  } else if (listItem.type === "kind-header") {
143
- rows.push(this.#renderKindHeader(listItem, isSelected, rowWidth));
149
+ rowStr = this.#renderKindHeader(listItem, isSelected, rowWidth);
144
150
  } else {
145
- rows.push(this.#renderExtensionRow(listItem.item, isSelected, rowWidth, masterDisabled));
151
+ rowStr = this.#renderExtensionRow(listItem.item, isSelected, rowWidth, masterDisabled);
146
152
  }
153
+ if (isHovered) rowStr = theme.bg("selectedBg", rowStr);
154
+ rows.push(rowStr);
147
155
  }
156
+ this.#visibleCount = rows.length;
148
157
 
149
158
  lines.push(
150
159
  ...renderScrollableList(rows, {
@@ -387,6 +396,57 @@ export class ExtensionList implements Component {
387
396
  this.#scrollOffset = next.scrollOffset;
388
397
  }
389
398
 
399
+ /** Toggle the selected item, or flip the provider master switch when on it. */
400
+ #activateSelected(): void {
401
+ const item = this.#listItems[this.#selectedIndex];
402
+ if (item?.type === "master") {
403
+ this.callbacks.onMasterToggle?.(item.providerId);
404
+ } else if (item?.type === "extension") {
405
+ // Only allow toggling if the provider master switch is enabled.
406
+ const masterDisabled = this.#masterSwitchProvider !== null && !isProviderEnabled(this.#masterSwitchProvider);
407
+ if (!masterDisabled) {
408
+ const newEnabled = item.item.state === "disabled";
409
+ this.callbacks.onToggle?.(item.item.id, newEnabled);
410
+ }
411
+ }
412
+ }
413
+
414
+ /** Highlight the row under the pointer (null clears). */
415
+ setHoverIndex(index: number | null): void {
416
+ this.#hoveredIndex = index;
417
+ }
418
+
419
+ /**
420
+ * Map a 0-based line within this component's render to the absolute list-item
421
+ * index, or null when the line is the search banner, a padding row, or outside
422
+ * the visible window. The first two lines are the search banner and a blank
423
+ * separator; item rows follow, windowed at the current scroll offset.
424
+ */
425
+ hitTest(line: number): number | null {
426
+ const rowLine = line - 2;
427
+ if (rowLine < 0 || rowLine >= this.#visibleCount) return null;
428
+ const index = this.#scrollOffset + rowLine;
429
+ return index < this.#listItems.length ? index : null;
430
+ }
431
+
432
+ /** Wheel notch: move the selection (and the inspector) one row. */
433
+ handleWheel(delta: -1 | 1): void {
434
+ if (delta < 0) this.#moveSelectionUp();
435
+ else this.#moveSelectionDown();
436
+ }
437
+
438
+ /** Click: select the row under the pointer, or activate it when already selected. */
439
+ handleClick(line: number): void {
440
+ const index = this.hitTest(line);
441
+ if (index === null) return;
442
+ if (index === this.#selectedIndex) {
443
+ this.#activateSelected();
444
+ return;
445
+ }
446
+ this.#selectedIndex = index;
447
+ this.#notifySelectionChange();
448
+ }
449
+
390
450
  handleInput(data: string): void {
391
451
  // Navigation
392
452
  if (matchesSelectUp(data) || data === "k") {
@@ -399,36 +459,9 @@ export class ExtensionList implements Component {
399
459
  return;
400
460
  }
401
461
 
402
- // Space: Toggle selected item
403
- if (data === " ") {
404
- const item = this.#listItems[this.#selectedIndex];
405
- if (item?.type === "master") {
406
- this.callbacks.onMasterToggle?.(item.providerId);
407
- } else if (item?.type === "extension") {
408
- // Only allow toggling if master is enabled
409
- const masterDisabled =
410
- this.#masterSwitchProvider !== null && !isProviderEnabled(this.#masterSwitchProvider);
411
- if (!masterDisabled) {
412
- const newEnabled = item.item.state === "disabled";
413
- this.callbacks.onToggle?.(item.item.id, newEnabled);
414
- }
415
- }
416
- return;
417
- }
418
-
419
- // Enter: Same as space - toggle selected item
420
- if (matchesKey(data, "enter") || matchesKey(data, "return") || data === "\n") {
421
- const item = this.#listItems[this.#selectedIndex];
422
- if (item?.type === "master") {
423
- this.callbacks.onMasterToggle?.(item.providerId);
424
- } else if (item?.type === "extension") {
425
- const masterDisabled =
426
- this.#masterSwitchProvider !== null && !isProviderEnabled(this.#masterSwitchProvider);
427
- if (!masterDisabled) {
428
- const newEnabled = item.item.state === "disabled";
429
- this.callbacks.onToggle?.(item.item.id, newEnabled);
430
- }
431
- }
462
+ // Space or Enter: activate the selected row (toggle item / master switch)
463
+ if (data === " " || matchesKey(data, "enter") || matchesKey(data, "return") || data === "\n") {
464
+ this.#activateSelected();
432
465
  return;
433
466
  }
434
467
 
@@ -232,11 +232,15 @@ export class SelectorController {
232
232
  */
233
233
  async showExtensionsDashboard(): Promise<void> {
234
234
  const dashboard = await ExtensionDashboard.create(getProjectDir(), this.ctx.settings, this.ctx.ui.terminal.rows);
235
+ // Fullscreen dashboard on the alternate screen (the /settings idiom): the
236
+ // overlay borrows the terminal's alt buffer and enables mouse tracking for
237
+ // its lifetime, leaving the transcript untouched underneath.
235
238
  const overlay = this.ctx.ui.showOverlay(dashboard, {
236
239
  width: "100%",
237
240
  maxHeight: "100%",
238
241
  anchor: "top-left",
239
242
  margin: 0,
243
+ fullscreen: true,
240
244
  });
241
245
  dashboard.onClose = () => {
242
246
  overlay.hide();