11 KiB
issue, issue_title
| issue | issue_title |
|---|---|
| 16 | 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.tsentirely (~1,117 lines removed). - Rewrite
openSettingsModal()insrc/config-modal.tsto useSettingsListfrompi-tuidirectly, removing theZellijModal/ZellijSettingsModalabstraction layer. - Keep the
/permission-systemslash command name, all subcommands (show,path,reset,help), and no-args interactive behaviour unchanged. - Update
tests/config-modal.test.tsto remove thezellij-modalmock 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-systemslash 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:
constructor(
items: SettingItem[],
maxVisible: number,
theme: SettingsListTheme,
onChange: (id: string, newValue: string) => void,
onCancel: () => void,
options?: SettingsListOptions,
)
onChangefires when a value is toggled — wired tocontroller.setConfig().onCancelfires on Escape — wired to thedone()callback fromctx.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:
- Instantiates
SettingsListwith items frombuildSettingItems(), theme fromgetSettingsListTheme(), and callbacks. - Wires
onChange→applySetting()→controller.setConfig()→syncSettingValues()(same logic as today). - Wires
onCancel→done()(closes the overlay). - Returns the
SettingsListinstance 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.PermissionSystemConfigControllerinterface.
What changes
openSettingsModal()body: rewritten (~60 lines → ~20 lines).- Import block: removes
ZellijModal,ZellijSettingsModal; addsSettingsListfrompi-tuiandgetSettingsListThemefrompi-coding-agent. SettingValueSyncTargetinterface: deleted (was an abstraction overZellijSettingsModal.updateValue;SettingsList.updateValueis used directly).syncSettingValues(): parameter type changes fromSettingValueSyncTargettoSettingsList(or inlined, since the function just callsupdateValuethree times).
Module-Level Changes
src/zellij-modal.ts — deleted
Entire file removed.
src/config-modal.ts — simplified
- Remove import of
ZellijModal,ZellijSettingsModalfrom./zellij-modal.js. - Add imports:
SettingsListfrom@mariozechner/pi-tui,getSettingsListThemefrom@mariozechner/pi-coding-agent. - Delete
SettingValueSyncTargetinterface. - Rewrite
openSettingsModal():- Create
SettingsListwithbuildSettingItems(current),getSettingsListTheme(),onChange,onCancel: done. - Return it directly as the component from the
ctx.ui.customfactory.
- Create
- Simplify or inline
syncSettingValues()to callsettingsList.updateValue()directly.
tests/config-modal.test.ts — mock surface trimmed
- The
pi-tuimock already stubsSettingsList— verify constructor signature matches. - Add
getSettingsListThemeto thepi-coding-agentmock (currently only mocksgetSettingsListTheme: () => ({}); confirm this is present and sufficient). - Existing assertions (completions, subcommand notifications, headless guard,
custom()call count = 1) should pass without logic changes.
TDD Order
-
Baseline. Run
npm test -- tests/config-modal.test.tsandnpm run buildto confirm green. No commit (baseline verification only). -
Delete
zellij-modal.tsand rewriteopenSettingsModal(). 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
SettingValueSyncTargetinterface. - Rewrite
openSettingsModal()to useSettingsListdirectly. - 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)
- Delete
-
Docs. Scan
README.md,AGENTS.md,docs/for references tozellij-modal.tsand remove them.- Commit:
docs: remove zellij-modal references (#16)(skip if none found)
- Commit:
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."