# Settings Bench

> For agents: start with the [agent guide](https://pi-tui.ratstack.sh/llms.txt). Every page here has a Markdown twin, and every pattern has a JSON record.

Behavioral / Overlays and dialogs · `settings-bench` · [HTML](https://pi-tui.ratstack.sh/patterns/settings-bench/) · [JSON](https://pi-tui.ratstack.sh/patterns/settings-bench.json) · [all patterns](https://pi-tui.ratstack.sh/patterns.md)

Also known as Settings panel.

## Intent

Keep configuration interaction separate from the live activity view.

## Motivation

Messenger separates configuration from activity, while Subagents asks dependent admin choices.

## Applicability

- Use this when several settings need dependent choices or value cycling.

## Structure

```text
entity -> setting choices
choice -> save / reload boundary
```

## Participants

- [`SettingsList`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/tui.md#compose-built-in-components): Owns settings values, cycling and submenus.
- [`ctx.ui.select`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/extensions.md#interact-with-the-user): Returns a selected string or cancellation.
- [`ctx.ui.editor`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/extensions.md#interact-with-the-user): Opens a multi-line text-editing dialog.
- [`ctx.ui.custom`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/tui.md#use-custom-screens-and-overlays): Mounts one interaction and resolves through done.
- `Settings controller`: Keeps displayed choices separate from persistence and reload.

Pi screen APIs: [`SettingsList`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/tui.md#compose-built-in-components), [`ctx.ui.select`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/extensions.md#interact-with-the-user), [`ctx.ui.editor`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/extensions.md#interact-with-the-user), [`ctx.ui.custom`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/tui.md#use-custom-screens-and-overlays), `SelectList`, `ctx.ui.setWidget`

## Consequences

- Configuration changes do not complicate the live activity view.
- Displayed choices, persisted writes and reload-only changes differ.

## Implementation

- Distinguish a displayed choice from a persisted write.
- Some tool ownership changes require reload rather than a live UI update.

## States

Each state was rendered at 40, 60, 80 and 120 columns in Pi's dark and light themes. Each frame links one WebP. An animated frame links its first image, then the animation.

### Settings list

`settings`: Show synthetic configurable values apart from the activity dashboard.

Checks at every width and theme:

- width: ✓ pass
- style-leak: ✓ pass
- hard-coded-colour: ✓ pass
- height: ✓ pass

Frames:

- 40 columns: ![Settings Bench, Settings list, 40 columns, dark theme](https://pi-tui.ratstack.sh/frames/settings-bench/settings-40-dark.3e7201bc11f5.webp) ![Settings Bench, Settings list, 40 columns, light theme](https://pi-tui.ratstack.sh/frames/settings-bench/settings-40-light.1aa84a023d0d.webp)
- 60 columns: ![Settings Bench, Settings list, 60 columns, dark theme](https://pi-tui.ratstack.sh/frames/settings-bench/settings-60-dark.5e0d12f5e0f5.webp) ![Settings Bench, Settings list, 60 columns, light theme](https://pi-tui.ratstack.sh/frames/settings-bench/settings-60-light.b2132cba2864.webp)
- 80 columns: ![Settings Bench, Settings list, 80 columns, dark theme](https://pi-tui.ratstack.sh/frames/settings-bench/settings-80-dark.dd2163d0db5f.webp) ![Settings Bench, Settings list, 80 columns, light theme](https://pi-tui.ratstack.sh/frames/settings-bench/settings-80-light.e9af3af8c85c.webp)
- 120 columns: ![Settings Bench, Settings list, 120 columns, dark theme](https://pi-tui.ratstack.sh/frames/settings-bench/settings-120-dark.c954bc90cb73.webp) ![Settings Bench, Settings list, 120 columns, light theme](https://pi-tui.ratstack.sh/frames/settings-bench/settings-120-light.03bbba349475.webp)

### Dependent choice

`dependent`: Choose an entity before showing its model or scope options.

Checks at every width and theme:

- width: ✓ pass
- style-leak: ✓ pass
- hard-coded-colour: ✓ pass
- height: ✓ pass

Frames:

- 40 columns: ![Settings Bench, Dependent choice, 40 columns, dark theme](https://pi-tui.ratstack.sh/frames/settings-bench/dependent-40-dark.4b1d5412ffe7.webp) ![Settings Bench, Dependent choice, 40 columns, light theme](https://pi-tui.ratstack.sh/frames/settings-bench/dependent-40-light.cac6a46d0f15.webp)
- 60 columns: ![Settings Bench, Dependent choice, 60 columns, dark theme](https://pi-tui.ratstack.sh/frames/settings-bench/dependent-60-dark.6abe40d66f2d.webp) ![Settings Bench, Dependent choice, 60 columns, light theme](https://pi-tui.ratstack.sh/frames/settings-bench/dependent-60-light.d6c099f1f546.webp)
- 80 columns: ![Settings Bench, Dependent choice, 80 columns, dark theme](https://pi-tui.ratstack.sh/frames/settings-bench/dependent-80-dark.108011028e55.webp) ![Settings Bench, Dependent choice, 80 columns, light theme](https://pi-tui.ratstack.sh/frames/settings-bench/dependent-80-light.bd7c6f24cfad.webp)
- 120 columns: ![Settings Bench, Dependent choice, 120 columns, dark theme](https://pi-tui.ratstack.sh/frames/settings-bench/dependent-120-dark.435de4212d64.webp) ![Settings Bench, Dependent choice, 120 columns, light theme](https://pi-tui.ratstack.sh/frames/settings-bench/dependent-120-light.b20b53ae8b31.webp)

### Saved selection

`saved`: Return to the dashboard with an explicit saved-setting notice.

Checks at every width and theme:

- width: ✓ pass
- style-leak: ✓ pass
- hard-coded-colour: ✓ pass
- height: ✓ pass

Frames:

- 40 columns: ![Settings Bench, Saved selection, 40 columns, dark theme](https://pi-tui.ratstack.sh/frames/settings-bench/saved-40-dark.aa6b7d7484e9.webp) ![Settings Bench, Saved selection, 40 columns, light theme](https://pi-tui.ratstack.sh/frames/settings-bench/saved-40-light.8fc1627b9a34.webp)
- 60 columns: ![Settings Bench, Saved selection, 60 columns, dark theme](https://pi-tui.ratstack.sh/frames/settings-bench/saved-60-dark.89e6fc3b8c9d.webp) ![Settings Bench, Saved selection, 60 columns, light theme](https://pi-tui.ratstack.sh/frames/settings-bench/saved-60-light.c5b081de7d29.webp)
- 80 columns: ![Settings Bench, Saved selection, 80 columns, dark theme](https://pi-tui.ratstack.sh/frames/settings-bench/saved-80-dark.adea93e9a16b.webp) ![Settings Bench, Saved selection, 80 columns, light theme](https://pi-tui.ratstack.sh/frames/settings-bench/saved-80-light.581a3ced693b.webp)
- 120 columns: ![Settings Bench, Saved selection, 120 columns, dark theme](https://pi-tui.ratstack.sh/frames/settings-bench/saved-120-dark.9af81641bbae.webp) ![Settings Bench, Saved selection, 120 columns, light theme](https://pi-tui.ratstack.sh/frames/settings-bench/saved-120-light.3cf8016cea7f.webp)

## Sample code

`stories/patterns/behavioral/settings-bench.ts`, the story the frames above were rendered from.

```ts
// Settings Bench: keep editable choices separate from saved configuration.
import { getSelectListTheme, getSettingsListTheme } from "@earendil-works/pi-coding-agent";
import { SelectList, SettingsList, Text } from "@earendil-works/pi-tui";
import type { PatternStory } from "../../../src/pattern.ts";

type Configuration = { entity: string; model: string };

export const story: PatternStory = {
  id: "settings-bench", title: "Settings Bench", kind: "screen",
  apis: ["ctx.ui.custom", "SettingsList", "SelectList", "ctx.ui.setWidget"],
  states: [
    { id: "settings", label: "Settings list" },
    { id: "dependent", label: "Dependent choice", steps: [
      { type: "keys", data: "\r" }, { type: "keys", data: "\x1b[B" }, { type: "keys", data: "\r" },
    ] },
    { id: "saved", label: "Saved selection", steps: [
      { type: "keys", data: "\x1b[B" }, { type: "keys", data: "\r" },
      { type: "keys", data: "s" }, { type: "resize", rows: 12 },
    ] },
  ],
  setup({ ui, theme }) {
    let saved: Configuration = { entity: "Builder", model: "fast" };
    ui.setEditorText("Continue the task review");
    ui.setWidget("activity", [
      theme.fg("accent", "Activity · 3 tasks · no config writes"),
      theme.fg("muted", "Saved: Builder / fast"),
    ]);
    void ui.custom<Configuration | undefined>((tui, theme, keys, done) => {
      const draft = { ...saved };
      let mode: "settings" | "submenu" = "settings";
      const settings = new SettingsList([
        { id: "entity", label: "Entity", currentValue: draft.entity, values: ["Builder", "Reviewer"] },
        { id: "model", label: "Model", currentValue: draft.model, submenu: (_current, finish) => {
          mode = "submenu";
          // Model options depend on the entity chosen in the preceding step.
          const models = draft.entity === "Reviewer" ? ["balanced", "careful"] : ["fast", "balanced"];
          const choices = new SelectList(models.map(label => ({ value: label, label })), 3, getSelectListTheme());
          choices.onSelect = item => { mode = "settings"; finish(item.value); };
          choices.onCancel = () => { mode = "settings"; finish(); };
          return choices;
        } },
      ], 3, getSettingsListTheme(), (id, value) => {
        if (id === "entity") {
          draft.entity = value;
          draft.model = value === "Reviewer" ? "balanced" : "fast";
          settings.updateValue("model", draft.model);
        }
        if (id === "model") draft.model = value;
        tui.requestRender();
      }, () => done(undefined));
      return {
        invalidate() { settings.invalidate(); },
        handleInput(data) {
          if (mode === "settings" && data === "s") done({ ...draft });
          else settings.handleInput(data);
          tui.requestRender();
        },
        render(width) {
          return [
            ...new Text(theme.fg("accent", "Configure " + draft.entity), 0, 0).render(width),
            ...settings.render(width),
            ...new Text(theme.fg("dim", mode === "submenu"
              ? "enter picks model · escape returns" : "s saves · escape discards"), 0, 0).render(width),
          ];
        },
      };
    }).then(selection => {
      if (selection === undefined) return;
      // Synthetic persistence boundary: update a fixture, never real settings.
      saved = selection;
      ui.setWidget("activity", [
        theme.fg("success", "Saved fixture: " + saved.entity + " / " + saved.model),
        theme.fg("muted", "Reload applies config · tasks unchanged"),
      ]);
      ui.setStatus("settings", theme.fg("warning", "reload required"));
    });
  },
};
```

## Known uses: seen in Nico's repos

- [**pi-messenger**](https://github.com/nicobailon/pi-messenger): Interactive configuration overlay
  - [config-overlay.ts:1-45](https://github.com/nicobailon/pi-messenger/blob/09937ed647a1b07a3b595bf75943feacb80ff123/config-overlay.ts#L1-L45) @09937ed6
- [**pi-tool-display**](https://github.com/nicobailon/pi-tool-display): Tool display: responsive settings modal
  - [src/config-modal.ts:398-455](https://github.com/nicobailon/pi-tool-display/blob/fca8c858a0989b63eba18ab935f3d8ed78354c3a/src/config-modal.ts#L398-L455) @fca8c858
- [**pi-subagents**](https://github.com/nicobailon/pi-subagents): Layer admin choices as sequential selector steps
  - [src/slash/subagents-admin.ts:170-459](https://github.com/nicobailon/pi-subagents/blob/6826b0545216077195ae8ffe468a6434715814b7/src/slash/subagents-admin.ts#L170-L459) @6826b054

## For agents: choose and check

Choose Settings Bench when your job matches its intent and applicability above. Its neighbours in Overlays and dialogs are listed below. Read the one whose intent fits your job more closely before you commit.

- [Shared Shell](https://pi-tui.ratstack.sh/patterns/shared-shell.md): Wrap specialized dialog content in a shared themed frame.
- [Warning Gate](https://pi-tui.ratstack.sh/patterns/warning-gate.md): Follow a consequential selection with a separate warning confirmation.
- [Abort Lantern](https://pi-tui.ratstack.sh/patterns/abort-lantern.md): Settle foreground loading before opening a separate result view.
- [Dialog Fuse](https://pi-tui.ratstack.sh/patterns/dialog-fuse.md): Show remaining time before a transient dialog automatically dismisses.
- [Abort Tether](https://pi-tui.ratstack.sh/patterns/abort-tether.md): Tie a built-in dialog's lifetime to an operation's abort signal.
- [Idle Fuse](https://pi-tui.ratstack.sh/patterns/idle-fuse.md): Reset a component-owned inactivity timer after every input.
- [Action Sieve](https://pi-tui.ratstack.sh/patterns/action-sieve.md): Derive dialog choices from current control and completion state.
- [Done Contract](https://pi-tui.ratstack.sh/patterns/done-contract.md): Resolve each custom interaction with a typed selection or cancellation.

Check your version:

- Render your version at 40, 60, 80 and 120 columns in the dark and light themes.
- Check width: no rendered line is wider than the terminal.
- Check style-leak: no line ends with colour, bold or a link still switched on.
- Check hard-coded-colour: every colour on screen comes from the active theme.
- Check height: the output fits in the rows the terminal has.
- Read the Pi 1.0.3 docs for [`SettingsList`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/tui.md#compose-built-in-components), [`ctx.ui.select`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/extensions.md#interact-with-the-user), [`ctx.ui.editor`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/extensions.md#interact-with-the-user), [`ctx.ui.custom`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/tui.md#use-custom-screens-and-overlays), `SelectList`, `ctx.ui.setWidget` before you use them.
- Every reference frame gets the verdict its state expects.

Next actions: `related({ id: "settings-bench" })` lists what to read next, and `states({ id: "settings-bench", width: 40 })` returns every frame and verdict at one width. The [build-pi-tui-component skill](https://pi-tui.ratstack.sh/skills/build-pi-tui-component.md) walks through the whole loop.

## Related

- [Action Sieve](https://pi-tui.ratstack.sh/patterns/action-sieve.md): derives currently available choices

## Linked from

- [Shared Shell](https://pi-tui.ratstack.sh/patterns/shared-shell.md): supplies specialized settings content
