ext-toggle: stage-then-commit UX (space stages, enter applies)

Replaces the single-pick + immediate-apply flow with a SettingsList
overlay where:

- ↑/↓ navigate
- space stages a toggle (●/○ flip in-place; not yet applied)
- enter commits all staged renames at once and triggers ctx.reload()
- esc cancels, no changes applied

Implementation: ctx.ui.custom() builds a Container with header, a
SettingsList (which cycles values on space), and a footer status line
showing pending changes (e.g. 'pending: notify→off, foo→on'). The
wrapper's handleInput intercepts Enter via matchesKey(data, Key.enter)
before SettingsList sees it — SettingsList would otherwise consume
Enter for cycling.

Disable guards still fire on the space-stage attempt: a refused toggle
is reverted via settingsList.updateValue and the reason shown in the
footer. ssh-controlmaster guard during --ssh therefore now refuses at
stage time, not commit time — clearer feedback.

Subdir extensions render as read-only rows (no , so SettingsList
will not cycle them).

Batches multiple toggles into a single ctx.reload() instead of one
reload per change, which was awkward when flipping several at once.
This commit is contained in:
2026-05-07 20:51:13 +02:00
parent c624eafe64
commit e47cbe5795
3 changed files with 230 additions and 105 deletions
+19 -9
View File
@@ -162,11 +162,15 @@ Notification text: `Pi — Done (Ns)` where N is the rounded elapsed seconds.
### `ext-toggle.ts`
Registers `/ext` slash command. Lists files in `~/.pi/agent/extensions/`,
shows `` (active) / `` (disabled) plus dir/symlink hints, and lets the
user toggle individual extensions by renaming them between `name.ts` and
`name.ts.off`. Calls `ctx.reload()` after a toggle so the change takes
effect without restarting pi.
Registers `/ext` slash command. Opens a multi-toggle overlay built on
`SettingsList` from pi-tui. Lists files in `~/.pi/agent/extensions/` with
`● enabled` / `○ disabled` values. Space stages a toggle; Enter commits
all pending renames at once and calls `ctx.reload()`; Escape cancels.
**UX rationale:** the previous single-pick + immediate-apply flow made
it awkward to flip several extensions in a row (every toggle reloaded
the whole runtime). Stage-then-commit batches reloads to one per
session.
**Key design decisions:**
@@ -203,10 +207,16 @@ effect without restarting pi.
**API used:**
- `pi.registerCommand(name, { description, handler })` — registers `/ext`.
- `ctx.ui.select(title, items)` — picker; returns selected string or `undefined`.
- `ctx.ui.confirm(title, message)` — yes/no dialog returning `boolean`.
- `ctx.ui.notify(message, level)` — transient toast.
- `ctx.reload()` — reloads extensions/skills/prompts/themes; same as `/reload`.
- `ctx.ui.custom<T>((tui, theme, kb, done) => Component)` — full-overlay
with own keyboard handling. The wrapper component intercepts `enter`
via `matchesKey(data, Key.enter)` before forwarding to `SettingsList`,
which would otherwise consume Enter for value-cycling.
- `SettingsList` from pi-tui — the list itself. Cycles values on space
(and on enter, but enter is intercepted upstream). `onChange` fires
per cycle and is where staging happens.
- `getSettingsListTheme()` from pi-coding-agent — themed colors.
- `ctx.ui.notify(message, level)` — toast for post-commit status / errors.
- `ctx.reload()` — same reload as `/reload` command.
No flags, no `agent_*` event handlers — fully passive until `/ext` is invoked.