# Input Switch

> 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 / Keys and focus · `input-switch` · [HTML](https://pi-tui.ratstack.sh/patterns/input-switch/) · [JSON](https://pi-tui.ratstack.sh/patterns/input-switch.json) · [all patterns](https://pi-tui.ratstack.sh/patterns.md)

Also known as Submitted input interception.

## Intent

Continue, transform or handle submitted input before normal processing.

## Motivation

Nico's upstream input event supports aliases and rewriting without replacing the editor.

## Applicability

- Use this when an alias or input rewrite should not replace the editor.

## Structure

```text
submission -> input handlers
  continue / transform / handled
```

## Participants

- [`pi.on`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/extensions.md#events): Registers ordered extension event handlers.
- [`InputEventResult`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/extensions.md#events): Declares continue, transform or handled input outcomes.
- [`ctx.ui.setStatus`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/extensions.md#interact-with-the-user): Writes or clears one named footer slot.
- `Input handlers`: Continue, transform or consume the submitted input in order.

Pi screen APIs: [`pi.on`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/extensions.md#events), [`InputEventResult`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/extensions.md#events), [`ctx.ui.setStatus`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/extensions.md#interact-with-the-user), `CustomEditor`, `setWidget`, `setStatus`

## Consequences

- Submitted text can continue, transform or be handled locally.
- Handler ordering affects what later extensions receive.

## Implementation

- Handlers run in extension registration order.
- A handled result stops normal processing.

## 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.

### Normal input

`continue`: Show the original synthetic submission reaching the host.

Checks at every width and theme:

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

Frames:

- 40 columns: ![Input Switch, Normal input, 40 columns, dark theme](https://pi-tui.ratstack.sh/frames/input-switch/continue-40-dark.13b81e8a657c.webp) ![Input Switch, Normal input, 40 columns, light theme](https://pi-tui.ratstack.sh/frames/input-switch/continue-40-light.d130288a75a4.webp)
- 60 columns: ![Input Switch, Normal input, 60 columns, dark theme](https://pi-tui.ratstack.sh/frames/input-switch/continue-60-dark.899f973a7520.webp) ![Input Switch, Normal input, 60 columns, light theme](https://pi-tui.ratstack.sh/frames/input-switch/continue-60-light.0dd6084787f0.webp)
- 80 columns: ![Input Switch, Normal input, 80 columns, dark theme](https://pi-tui.ratstack.sh/frames/input-switch/continue-80-dark.67b399751e36.webp) ![Input Switch, Normal input, 80 columns, light theme](https://pi-tui.ratstack.sh/frames/input-switch/continue-80-light.47df7b56ce76.webp)
- 120 columns: ![Input Switch, Normal input, 120 columns, dark theme](https://pi-tui.ratstack.sh/frames/input-switch/continue-120-dark.7140703de7cf.webp) ![Input Switch, Normal input, 120 columns, light theme](https://pi-tui.ratstack.sh/frames/input-switch/continue-120-light.5fc7417439f3.webp)

### Rewritten input

`transform`: Show an alias expanded into visible submitted text.

Checks at every width and theme:

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

Frames:

- 40 columns: ![Input Switch, Rewritten input, 40 columns, dark theme](https://pi-tui.ratstack.sh/frames/input-switch/transform-40-dark.7ac60cf11433.webp) ![Input Switch, Rewritten input, 40 columns, light theme](https://pi-tui.ratstack.sh/frames/input-switch/transform-40-light.3331813501bf.webp)
- 60 columns: ![Input Switch, Rewritten input, 60 columns, dark theme](https://pi-tui.ratstack.sh/frames/input-switch/transform-60-dark.61a62085106c.webp) ![Input Switch, Rewritten input, 60 columns, light theme](https://pi-tui.ratstack.sh/frames/input-switch/transform-60-light.dd8ea149cf7b.webp)
- 80 columns: ![Input Switch, Rewritten input, 80 columns, dark theme](https://pi-tui.ratstack.sh/frames/input-switch/transform-80-dark.76cb39cc8c5e.webp) ![Input Switch, Rewritten input, 80 columns, light theme](https://pi-tui.ratstack.sh/frames/input-switch/transform-80-light.a2f09bf31ffa.webp)
- 120 columns: ![Input Switch, Rewritten input, 120 columns, dark theme](https://pi-tui.ratstack.sh/frames/input-switch/transform-120-dark.d69a0c84a8bf.webp) ![Input Switch, Rewritten input, 120 columns, light theme](https://pi-tui.ratstack.sh/frames/input-switch/transform-120-light.3c3cd67a9cf3.webp)

### Handled locally

`handled`: Consume an input and show a keyed status instead of starting a model turn.

Checks at every width and theme:

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

Frames:

- 40 columns: ![Input Switch, Handled locally, 40 columns, dark theme](https://pi-tui.ratstack.sh/frames/input-switch/handled-40-dark.f3e9bb4b1a25.webp) ![Input Switch, Handled locally, 40 columns, light theme](https://pi-tui.ratstack.sh/frames/input-switch/handled-40-light.1f0772db9efa.webp)
- 60 columns: ![Input Switch, Handled locally, 60 columns, dark theme](https://pi-tui.ratstack.sh/frames/input-switch/handled-60-dark.88c244afac2d.webp) ![Input Switch, Handled locally, 60 columns, light theme](https://pi-tui.ratstack.sh/frames/input-switch/handled-60-light.ce0118efbdaf.webp)
- 80 columns: ![Input Switch, Handled locally, 80 columns, dark theme](https://pi-tui.ratstack.sh/frames/input-switch/handled-80-dark.60295c1b57e8.webp) ![Input Switch, Handled locally, 80 columns, light theme](https://pi-tui.ratstack.sh/frames/input-switch/handled-80-light.3e0f503e6142.webp)
- 120 columns: ![Input Switch, Handled locally, 120 columns, dark theme](https://pi-tui.ratstack.sh/frames/input-switch/handled-120-dark.00fe6f8b87d2.webp) ![Input Switch, Handled locally, 120 columns, light theme](https://pi-tui.ratstack.sh/frames/input-switch/handled-120-light.20cc7affdb6e.webp)

## Sample code

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

```ts
// Input Switch: continue, rewrite or consume a real editor submission.
// Synthetic dispatcher uses Pi's InputEventResult; no model or pi.on runtime exists here.
import { Text } from "@earendil-works/pi-tui";
import { CustomEditor, type InputEventResult } from "@earendil-works/pi-coding-agent";
import type { PatternStory } from "../../../src/pattern.ts";

export const story: PatternStory = {
  id: "input-switch",
  title: "Input Switch",
  kind: "screen",
  apis: ["InputEventResult", "CustomEditor", "setWidget", "setStatus"],
  states: [
    { id: "continue", label: "Normal input", steps: [
      { type: "text", text: "Review the parser tests" }, { type: "keys", data: "\r" },
    ] },
    { id: "transform", label: "Rewritten input", steps: [
      { type: "text", text: "/audit" }, { type: "keys", data: "\r" },
    ] },
    { id: "handled", label: "Handled locally", steps: [
      { type: "text", text: "/hold" }, { type: "keys", data: "\r" },
    ] },
  ],
  setup({ ui, theme }) {
    let delivered = 0;
    ui.setHeader(() => new Text(theme.fg("accent", "Pi · Input Switch"), 0, 0));
    const inputHandler = (text: string): InputEventResult => {
      if (text === "/audit") return { action: "transform", text: "Review the parser tests" };
      if (text === "/hold") return { action: "handled" };
      return { action: "continue" };
    };
    ui.setEditorComponent((tui, editorTheme, keys) => {
      const editor = new CustomEditor(tui, editorTheme, keys);
      editor.onSubmit = original => {
        const result = inputHandler(original);
        const consumed = result.action === "handled";
        const submitted = result.action === "transform" ? result.text : original;
        if (!consumed) delivered++;
        ui.setWidget("input-route", [
          theme.fg("muted", "Submitted: " + original),
          theme.fg(consumed ? "warning" : "accent", "Handler → " + result.action),
          theme.fg("text", consumed ? "Local command · no host delivery" : "Synthetic host: " + submitted),
          theme.fg("dim", "Delivered submissions: " + delivered),
        ]);
        ui.setStatus("input-switch", theme.fg(consumed ? "warning" : "success", consumed ? "held locally" : "delivered"));
      };
      return editor;
    });
    ui.setWidget("input-hint", [theme.fg("muted", "Enter send · /audit alias · /hold local")], { placement: "belowEditor" });
    return () => {
      ui.setStatus("input-switch", undefined);
      ui.setWidget("input-route", undefined);
      ui.setWidget("input-hint", undefined);
      ui.setEditorComponent(undefined);
    };
  },
};
```

## Known uses: seen in Nico's repos

- [**earendil-works/pi**](https://github.com/earendil-works/pi): Allow extensions to intercept submitted input
  - [packages/coding-agent/src/core/extensions/types.ts:460-505](https://github.com/earendil-works/pi/blob/3e5d91f28775b2f2df21ef3f0ec3bd799610a447/packages/coding-agent/src/core/extensions/types.ts#L460-L505) @3e5d91f2
- [**dot314**](https://github.com/nicobailon/dot314): Allow extensions to intercept submitted input
- [**pi-boomerang**](https://github.com/nicobailon/pi-boomerang): Allow extensions to intercept submitted input
- [**pi-mcp-adapter**](https://github.com/nicobailon/pi-mcp-adapter): Allow extensions to intercept submitted input
- [**pi-powerline-footer**](https://github.com/nicobailon/pi-powerline-footer): Allow extensions to intercept submitted input
- [**pi-review-loop**](https://github.com/nicobailon/pi-review-loop): Allow extensions to intercept submitted input
- [**pi-skill-palette**](https://github.com/nicobailon/pi-skill-palette): Allow extensions to intercept submitted input
- [**pi-subagents**](https://github.com/nicobailon/pi-subagents): Allow extensions to intercept submitted input

## For agents: choose and check

Choose Input Switch when your job matches its intent and applicability above. Its neighbours in Keys and focus are listed below. Read the one whose intent fits your job more closely before you commit.

- [Action Compass](https://pi-tui.ratstack.sh/patterns/action-compass.md): Resolve configurable actions through injected keybindings.
- [Focus Baton](https://pi-tui.ratstack.sh/patterns/focus-baton.md): Move keyboard ownership without closing a persistent overlay.
- [Ghost Overlay](https://pi-tui.ratstack.sh/patterns/ghost-overlay.md): Keep an overlay visible without automatically taking keyboard focus.
- [Field Baton](https://pi-tui.ratstack.sh/patterns/field-baton.md): Switch keyboard focus between a choice list and an editable task field.
- [Control Baton](https://pi-tui.ratstack.sh/patterns/control-baton.md): Pause automatic output updates while the user controls an interactive job.
- [Pause Latch](https://pi-tui.ratstack.sh/patterns/pause-latch.md): Keep pause, resume and quit controls inside the component input contract.
- [Input Lease](https://pi-tui.ratstack.sh/patterns/input-lease.md): Intercept raw terminal controls only while their owning operation is active.

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 [`pi.on`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/extensions.md#events), [`InputEventResult`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/extensions.md#events), [`ctx.ui.setStatus`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/extensions.md#interact-with-the-user), `CustomEditor`, `setWidget`, `setStatus` before you use them.
- Every reference frame gets the verdict its state expects.

Next actions: `related({ id: "input-switch" })` lists what to read next, and `states({ id: "input-switch", 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

- [Editor Steward](https://pi-tui.ratstack.sh/patterns/editor-steward.md): changes the editor rather than submission

## Linked from

No other pattern links here.
