mirror of
https://bitbucket.org/siakitem/my-pi.git
synced 2026-08-28 16:45:22 +00:00
169 lines
11 KiB
Markdown
169 lines
11 KiB
Markdown
---
|
|
issue: 16
|
|
issue_title: "Delete vendored src/zellij-modal.ts; rebuild settings UI on pi-tui"
|
|
---
|
|
|
|
# Delete vendored zellij-modal and rebuild settings UI on pi-tui
|
|
|
|
## Problem Statement
|
|
|
|
`src/zellij-modal.ts` is 1,117 lines vendored from upstream's sibling `zellij-modal` package.
|
|
Its header comment instructs maintainers to "keep this module in sync when upstream zellij-modal primitives change" — the textbook maintenance trap AGENTS.md warns against.
|
|
The sole consumer is `src/config-modal.ts`, which uses `ZellijModal` and `ZellijSettingsModal` to render a 3-toggle settings dialog for `yoloMode`, `permissionReviewLog`, and `debugLog`.
|
|
|
|
The vendored code is unnecessary because `pi-tui` already exports `SettingsList` — a `Component`-conformant class with built-in navigation, value cycling, `onChange`/`onCancel` callbacks, and `updateValue()`.
|
|
The entire `ZellijModal` + `ZellijSettingsModal` layer exists only to add border rendering and title-bar chrome around `SettingsList`.
|
|
Since `ctx.ui.custom` already provides overlay positioning, the modal chrome can be dropped entirely or replaced with a few lines of `Box` wrapping.
|
|
|
|
### Why keep the interactive modal at all?
|
|
|
|
The three settings (`yoloMode`, `permissionReviewLog`, `debugLog`) are simple booleans that an agent could edit in `config.json` directly.
|
|
However, toggling a setting via the slash command is instant and free; asking the agent to do it costs a round-trip of token usage.
|
|
The interactive UI earns its keep as a zero-cost escape hatch for quick config changes, especially for `yoloMode` which users toggle frequently.
|
|
Issue #10 will later consolidate config paths, but the toggle UI remains useful regardless of where the file lives.
|
|
|
|
## Goals
|
|
|
|
- Delete `src/zellij-modal.ts` entirely (~1,117 lines removed).
|
|
- Rewrite `openSettingsModal()` in `src/config-modal.ts` to use `SettingsList` from `pi-tui` directly, removing the `ZellijModal` / `ZellijSettingsModal` abstraction layer.
|
|
- Keep the `/permission-system` slash command name, all subcommands (`show`, `path`, `reset`, `help`), and no-args interactive behaviour unchanged.
|
|
- Update `tests/config-modal.test.ts` to remove the `zellij-modal` mock surface and validate the new UI shape.
|
|
- Net result: ~1,100 lines removed, zero new vendored code.
|
|
|
|
## Non-Goals
|
|
|
|
- Changing the set of configurable runtime knobs (`yoloMode`, `permissionReviewLog`, `debugLog`).
|
|
- Changing the `/permission-system` slash command name or any on-disk identity (config directory, log filenames, event channel names).
|
|
- Consolidating config paths (#10) — that issue builds on this cleanup.
|
|
|
|
## Background
|
|
|
|
### Relevant modules
|
|
|
|
| File | Role |
|
|
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
| `src/zellij-modal.ts` | Vendored modal framework (~1,117 lines). Wraps `pi-tui` `SettingsList`, `Container`, `Box`, `Text` to add border rendering, overlay sizing, title bar, and key handling. |
|
|
| `src/config-modal.ts` | Slash command handler. Registers `/permission-system`, dispatches subcommands, and calls `openSettingsModal()` for no-args invocation. |
|
|
| `src/extension-config.ts` | Loads/saves `config.json` (the 3-boolean runtime config). No changes needed. |
|
|
| `tests/config-modal.test.ts` | Mocks `pi-tui` and `pi-coding-agent` exports; tests completions, subcommands, headless guard, and custom-modal call count. |
|
|
|
|
### Permission surface
|
|
|
|
None — this change is purely UI/DX.
|
|
No policy semantics, permission surfaces, or merge precedence are affected.
|
|
|
|
### Key `pi-tui` / `pi-coding-agent` APIs
|
|
|
|
`SettingsList` from `pi-tui` is the critical primitive.
|
|
It already implements the `Component` interface (`render`, `handleInput`, `invalidate`) and accepts:
|
|
|
|
```typescript
|
|
constructor(
|
|
items: SettingItem[],
|
|
maxVisible: number,
|
|
theme: SettingsListTheme,
|
|
onChange: (id: string, newValue: string) => void,
|
|
onCancel: () => void,
|
|
options?: SettingsListOptions,
|
|
)
|
|
```
|
|
|
|
- `onChange` fires when a value is toggled — wired to `controller.setConfig()`.
|
|
- `onCancel` fires on Escape — wired to the `done()` callback from `ctx.ui.custom`.
|
|
- `updateValue(id, newValue)` lets us sync display after a persist round-trip.
|
|
|
|
`ctx.ui.custom<T>(factory, options)` renders a custom `Component` in an overlay.
|
|
The factory receives `(tui, theme, keybindings, done)` and returns the component.
|
|
|
|
`getSettingsListTheme()` from `pi-coding-agent` provides the `SettingsListTheme`.
|
|
|
|
## Design Overview
|
|
|
|
### Approach: `SettingsList` as the sole component
|
|
|
|
The `ZellijModal` + `ZellijSettingsModal` two-class indirection is replaced by passing a `SettingsList` instance directly to `ctx.ui.custom`.
|
|
No `Container`, `Box`, or `Text` wrapper is needed — `SettingsList` is already a self-contained `Component` that handles rendering, input, and invalidation.
|
|
|
|
The factory passed to `ctx.ui.custom`:
|
|
|
|
1. Instantiates `SettingsList` with items from `buildSettingItems()`, theme from `getSettingsListTheme()`, and callbacks.
|
|
2. Wires `onChange` → `applySetting()` → `controller.setConfig()` → `syncSettingValues()` (same logic as today).
|
|
3. Wires `onCancel` → `done()` (closes the overlay).
|
|
4. Returns the `SettingsList` instance directly as the component.
|
|
|
|
The overlay options on `ctx.ui.custom` handle positioning.
|
|
The elaborate border chrome from `ZellijModal` (title bar, help undertitle, rounded corners) is dropped — `SettingsList` already renders its own hint line and description, which is sufficient for 3 toggle items.
|
|
|
|
### What stays the same
|
|
|
|
- `registerPermissionSystemCommand()` signature and registration.
|
|
- `handleArgs()` — all subcommand handlers (`show`, `path`, `reset`, `help`, unknown).
|
|
- `getArgumentCompletions()` — completions for subcommands.
|
|
- `buildSettingItems()`, `applySetting()`, `summarizeConfig()` helpers — unchanged in logic.
|
|
- `PermissionSystemConfigController` interface.
|
|
|
|
### What changes
|
|
|
|
- `openSettingsModal()` body: rewritten (~60 lines → ~20 lines).
|
|
- Import block: removes `ZellijModal`, `ZellijSettingsModal`; adds `SettingsList` from `pi-tui` and `getSettingsListTheme` from `pi-coding-agent`.
|
|
- `SettingValueSyncTarget` interface: deleted (was an abstraction over `ZellijSettingsModal.updateValue`; `SettingsList.updateValue` is used directly).
|
|
- `syncSettingValues()`: parameter type changes from `SettingValueSyncTarget` to `SettingsList` (or inlined, since the function just calls `updateValue` three times).
|
|
|
|
## Module-Level Changes
|
|
|
|
### `src/zellij-modal.ts` — deleted
|
|
|
|
Entire file removed.
|
|
|
|
### `src/config-modal.ts` — simplified
|
|
|
|
- Remove import of `ZellijModal`, `ZellijSettingsModal` from `./zellij-modal.js`.
|
|
- Add imports: `SettingsList` from `@mariozechner/pi-tui`, `getSettingsListTheme` from `@mariozechner/pi-coding-agent`.
|
|
- Delete `SettingValueSyncTarget` interface.
|
|
- Rewrite `openSettingsModal()`:
|
|
- Create `SettingsList` with `buildSettingItems(current)`, `getSettingsListTheme()`, `onChange`, `onCancel: done`.
|
|
- Return it directly as the component from the `ctx.ui.custom` factory.
|
|
- Simplify or inline `syncSettingValues()` to call `settingsList.updateValue()` directly.
|
|
|
|
### `tests/config-modal.test.ts` — mock surface trimmed
|
|
|
|
- The `pi-tui` mock already stubs `SettingsList` — verify constructor signature matches.
|
|
- Add `getSettingsListTheme` to the `pi-coding-agent` mock (currently only mocks `getSettingsListTheme: () => ({})`; confirm this is present and sufficient).
|
|
- Existing assertions (completions, subcommand notifications, headless guard, `custom()` call count = 1) should pass without logic changes.
|
|
|
|
## TDD Order
|
|
|
|
1. **Baseline.**
|
|
Run `npm test -- tests/config-modal.test.ts` and `npm run build` to confirm green.
|
|
No commit (baseline verification only).
|
|
|
|
2. **Delete `zellij-modal.ts` and rewrite `openSettingsModal()`.**
|
|
Do both together — there is no useful intermediate state where the vendored file exists but nothing imports it.
|
|
- Delete `src/zellij-modal.ts`.
|
|
- Update imports in `src/config-modal.ts`.
|
|
- Delete `SettingValueSyncTarget` interface.
|
|
- Rewrite `openSettingsModal()` to use `SettingsList` directly.
|
|
- Simplify `syncSettingValues()`.
|
|
- Update test mocks if needed.
|
|
- Run full test suite and `npm run build`.
|
|
- Commit: `feat: replace vendored zellij-modal with direct pi-tui SettingsList (#16)`
|
|
|
|
3. **Docs.**
|
|
Scan `README.md`, `AGENTS.md`, `docs/` for references to `zellij-modal.ts` and remove them.
|
|
- Commit: `docs: remove zellij-modal references (#16)` (skip if none found)
|
|
|
|
## Risks and Mitigations
|
|
|
|
| Risk | Mitigation |
|
|
| ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
| **Could this silently weaken a permission?** | No. This change is purely UI/DX for the `/permission-system` settings command. No permission decision logic is touched. |
|
|
| **`SettingsList` constructor signature mismatch.** | Verified from `pi-tui` type declarations above. The vendored code already uses `SettingsList` internally with the same constructor shape. |
|
|
| **Visual regression.** Losing the `ZellijModal` border chrome (title bar, rounded corners, help undertitle). | Acceptable. `SettingsList` renders its own hint line and item descriptions. With only 3 items the chrome was decorative, not functional. If needed later, `Box` can add a border in a few lines. |
|
|
| **Test mock drift.** | The `pi-tui` mock already stubs `SettingsList`. TDD step 2 verifies mocks before committing. |
|
|
| **On-disk identity change.** | None. Command name stays `/permission-system`; config directory, log filenames, and event channels are untouched. |
|
|
|
|
## Open Questions
|
|
|
|
None — the design is straightforward.
|
|
The only aesthetic question (border chrome vs. bare `SettingsList`) is answered by "start simple, add later if needed."
|