# Mode Fence

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

Lifecycle / Lifecycle and mounting · `mode-fence` · [HTML](https://pi-tui.ratstack.sh/patterns/mode-fence/) · [JSON](https://pi-tui.ratstack.sh/patterns/mode-fence.json) · [all patterns](https://pi-tui.ratstack.sh/patterns.md)

Also known as Mode-aware UI.

## Intent

Keep terminal components separate from dialog-capable and no-UI modes.

## Motivation

The Discord host cannot answer terminal prompts and its old UI adapter lacks parts of the current contract.

## Applicability

- Use this when the same extension runs in TUI, RPC, JSON or print mode.

## Structure

```text
ctx.mode -> terminal mount?
ctx.hasUI -> supported dialog?
no UI -> safe cancellation
```

## Participants

- [`ExtensionContext.mode`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/extensions.md#account-for-each-mode): Separates TUI from RPC, JSON and print modes.
- [`ExtensionContext.hasUI`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/extensions.md#account-for-each-mode): Reports dialog-capable TUI or RPC availability.
- [`ctx.ui.confirm`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/extensions.md#interact-with-the-user): Returns a confirmation decision.
- [`ctx.ui.setWidget`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/extensions.md#interact-with-the-user): Mounts, replaces or clears one near-editor widget.
- `Cancellation result`: Prevents an unavailable prompt from implying approval.

Pi screen APIs: [`ExtensionContext.mode`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/extensions.md#account-for-each-mode), [`ExtensionContext.hasUI`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/extensions.md#account-for-each-mode), [`ctx.ui.confirm`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/extensions.md#interact-with-the-user), [`ctx.ui.setWidget`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/extensions.md#interact-with-the-user), `setWidget`, `Text`

## Consequences

- Non-interactive work need not depend on terminal rendering.
- RPC supports dialogs but not custom terminal components.

## Implementation

- hasUI includes RPC and does not imply terminal components.
- Do not copy the incomplete 0.65-era headless adapter.
- Treat false or undefined dialog results as safe cancellation.

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

### Terminal mode

`tui`: Show a mounted synthetic component in TUI mode.

Checks at every width and theme:

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

Frames:

- 40 columns: ![Mode Fence, Terminal mode, 40 columns, dark theme](https://pi-tui.ratstack.sh/frames/mode-fence/tui-40-dark.955fc6c541fd.webp) ![Mode Fence, Terminal mode, 40 columns, light theme](https://pi-tui.ratstack.sh/frames/mode-fence/tui-40-light.50a75c983b92.webp)
- 60 columns: ![Mode Fence, Terminal mode, 60 columns, dark theme](https://pi-tui.ratstack.sh/frames/mode-fence/tui-60-dark.44bbce1c33e7.webp) ![Mode Fence, Terminal mode, 60 columns, light theme](https://pi-tui.ratstack.sh/frames/mode-fence/tui-60-light.d8ec64f503fb.webp)
- 80 columns: ![Mode Fence, Terminal mode, 80 columns, dark theme](https://pi-tui.ratstack.sh/frames/mode-fence/tui-80-dark.f4b8f67a5979.webp) ![Mode Fence, Terminal mode, 80 columns, light theme](https://pi-tui.ratstack.sh/frames/mode-fence/tui-80-light.0943ef59feb0.webp)
- 120 columns: ![Mode Fence, Terminal mode, 120 columns, dark theme](https://pi-tui.ratstack.sh/frames/mode-fence/tui-120-dark.9239c82af2c2.webp) ![Mode Fence, Terminal mode, 120 columns, light theme](https://pi-tui.ratstack.sh/frames/mode-fence/tui-120-light.a6929739027a.webp)

### RPC client

`rpc`: Show a synthetic client-observed dialog result with no terminal factory mount.

Checks at every width and theme:

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

Frames:

- 40 columns: ![Mode Fence, RPC client, 40 columns, dark theme](https://pi-tui.ratstack.sh/frames/mode-fence/rpc-40-dark.f63d5232aca0.webp) ![Mode Fence, RPC client, 40 columns, light theme](https://pi-tui.ratstack.sh/frames/mode-fence/rpc-40-light.13969e470a35.webp)
- 60 columns: ![Mode Fence, RPC client, 60 columns, dark theme](https://pi-tui.ratstack.sh/frames/mode-fence/rpc-60-dark.703a67631250.webp) ![Mode Fence, RPC client, 60 columns, light theme](https://pi-tui.ratstack.sh/frames/mode-fence/rpc-60-light.65801c32bdad.webp)
- 80 columns: ![Mode Fence, RPC client, 80 columns, dark theme](https://pi-tui.ratstack.sh/frames/mode-fence/rpc-80-dark.c77016ba3341.webp) ![Mode Fence, RPC client, 80 columns, light theme](https://pi-tui.ratstack.sh/frames/mode-fence/rpc-80-light.e9bc45f27b06.webp)
- 120 columns: ![Mode Fence, RPC client, 120 columns, dark theme](https://pi-tui.ratstack.sh/frames/mode-fence/rpc-120-dark.1a99f2f04c2d.webp) ![Mode Fence, RPC client, 120 columns, light theme](https://pi-tui.ratstack.sh/frames/mode-fence/rpc-120-light.d199b7d34cbb.webp)

### No UI

`headless`: Show a fixture transcript recording a skipped mount and a cancelled action in print mode.

Checks at every width and theme:

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

Frames:

- 40 columns: ![Mode Fence, No UI, 40 columns, dark theme](https://pi-tui.ratstack.sh/frames/mode-fence/headless-40-dark.ce7f747d5986.webp) ![Mode Fence, No UI, 40 columns, light theme](https://pi-tui.ratstack.sh/frames/mode-fence/headless-40-light.52cbe10d172c.webp)
- 60 columns: ![Mode Fence, No UI, 60 columns, dark theme](https://pi-tui.ratstack.sh/frames/mode-fence/headless-60-dark.7e7b000c8d05.webp) ![Mode Fence, No UI, 60 columns, light theme](https://pi-tui.ratstack.sh/frames/mode-fence/headless-60-light.56f10e40faae.webp)
- 80 columns: ![Mode Fence, No UI, 80 columns, dark theme](https://pi-tui.ratstack.sh/frames/mode-fence/headless-80-dark.0e106df6ffac.webp) ![Mode Fence, No UI, 80 columns, light theme](https://pi-tui.ratstack.sh/frames/mode-fence/headless-80-light.a78e21bdf97b.webp)
- 120 columns: ![Mode Fence, No UI, 120 columns, dark theme](https://pi-tui.ratstack.sh/frames/mode-fence/headless-120-dark.34b68d802fd4.webp) ![Mode Fence, No UI, 120 columns, light theme](https://pi-tui.ratstack.sh/frames/mode-fence/headless-120-light.74f1b9dde6c4.webp)

## Sample code

`stories/patterns/lifecycle/mode-fence.ts`, the story the frames above were rendered from.

```ts
// Mode Fence: distinguish terminal mounts, RPC dialogs, and no-UI cancellation.
import { Text } from "@earendil-works/pi-tui";
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
import type { PatternStory } from "../../../src/pattern.ts";

type ModeFixture = Pick<ExtensionContext, "mode" | "hasUI"> & { confirmed: boolean };

export const story: PatternStory = {
  id: "mode-fence", title: "Mode Fence", kind: "screen",
  apis: ["ExtensionContext.mode", "ExtensionContext.hasUI", "setWidget", "Text"],
  states: [
    { id: "tui", label: "Terminal mode" },
    { id: "rpc", label: "RPC client", steps: [{ type: "action", name: "rpc" }] },
    { id: "headless", label: "No UI", steps: [{ type: "action", name: "print" }] },
  ],
  setup({ ui, theme, action }) {
    // RPC/print are fixture transcripts, not real runtimes in this TUI harness.
    const renderFixture = ({ mode, hasUI, confirmed }: ModeFixture) => {
      ui.setWidget("terminal", undefined);
      let terminalMounted = false;
      if (mode === "tui") {
        ui.setWidget("terminal", (_tui, theme) =>
          new Text(theme.fg("accent", "Terminal task preview · 3 tasks"), 0, 0));
        terminalMounted = true;
      }
      // Synthetic client-observed confirmation. No unavailable dialog is called.
      const approved = hasUI && confirmed;
      ui.setWidget("mode", [
        theme.fg("accent", "Mode: " + mode + " · hasUI: " + hasUI),
        theme.fg("muted", terminalMounted ? "Terminal factory mounted" : "Terminal factory skipped"),
        theme.fg(approved ? "success" : "warning", approved
          ? "Fixture dialog result: approved" : "No UI · action cancelled safely"),
      ]);
    };
    ui.setEditorText("Review the mode decision.");
    action("rpc", () => renderFixture({ mode: "rpc", hasUI: true, confirmed: true }));
    action("print", () => renderFixture({ mode: "print", hasUI: false, confirmed: false }));
    renderFixture({ mode: "tui", hasUI: true, confirmed: true });
  },
};
```

## Known uses: seen in Nico's repos

- [**pi-discord**](https://github.com/nicobailon/pi-discord): Refuse headless dialogs safely
  - [daemon/headless-ui.js:4-24](https://github.com/nicobailon/pi-discord/blob/8dce54fe2a42108d0cc992579818fe74cb458c37/daemon/headless-ui.js#L4-L24) @8dce54fe
- [**pi-extensions**](https://github.com/nicobailon/pi-extensions): Session-scoped editor component mount
  - [raw-paste/index.ts:95-104](https://github.com/nicobailon/pi-extensions/blob/bca5070b541ffa7d273e74036dcd7d5f8b63eed3/raw-paste/index.ts#L95-L104) @bca5070b
- [**dot314**](https://github.com/nicobailon/dot314): Separate compact status from structured detail
  - [extensions/plan-mode.ts:575-590](https://github.com/nicobailon/dot314/blob/17cce138f3f687211d0485d204408a9650953c61/extensions/plan-mode.ts#L575-L590) @17cce138
  - [extensions/pi-codex-goal/src/goal-runtime-status.ts:42-63](https://github.com/nicobailon/dot314/blob/17cce138f3f687211d0485d204408a9650953c61/extensions/pi-codex-goal/src/goal-runtime-status.ts#L42-L63) @17cce138
  - [extensions/plan-mode.ts:1-18](https://github.com/nicobailon/dot314/blob/17cce138f3f687211d0485d204408a9650953c61/extensions/plan-mode.ts#L1-L18) @17cce138
- [**pi-custom-compaction**](https://github.com/nicobailon/pi-custom-compaction): Separate compact status from structured detail
  - [runtime/session-state.ts:49-78](https://github.com/nicobailon/pi-custom-compaction/blob/a0e4700badb1c5c1c2dd12eeb250ff067fa67b7e/runtime/session-state.ts#L49-L78) @a0e4700b
  - [runtime/session-state.ts:105-183](https://github.com/nicobailon/pi-custom-compaction/blob/a0e4700badb1c5c1c2dd12eeb250ff067fa67b7e/runtime/session-state.ts#L105-L183) @a0e4700b
- [**pi-extensions**](https://github.com/nicobailon/pi-extensions): Separate compact status from structured detail
  - [ralph-wiggum/index.ts:190-215](https://github.com/nicobailon/pi-extensions/blob/bca5070b541ffa7d273e74036dcd7d5f8b63eed3/ralph-wiggum/index.ts#L190-L215) @bca5070b
- [**pi-memory-workbench**](https://github.com/nicobailon/pi-memory-workbench): Separate compact status from structured detail
  - [index.ts:72-82](https://github.com/nicobailon/pi-memory-workbench/blob/92b4c9c3ad07841418d77118bf8bd02ad204f7c4/index.ts#L72-L82) @92b4c9c3
- [**pi-messenger**](https://github.com/nicobailon/pi-messenger): Separate compact status from structured detail
  - [index.ts:295-305](https://github.com/nicobailon/pi-messenger/blob/09937ed647a1b07a3b595bf75943feacb80ff123/index.ts#L295-L305) @09937ed6

## For agents: choose and check

Choose Mode Fence when your job matches its intent and applicability above. Its neighbours in Lifecycle and mounting are listed below. Read the one whose intent fits your job more closely before you commit.

- [Elastic Overlay](https://pi-tui.ratstack.sh/patterns/elastic-overlay.md): Resolve overlay size and placement from current terminal dimensions.
- [Event Relay](https://pi-tui.ratstack.sh/patterns/event-relay.md): Update visible UI from named extension event channels.
- [Process Shell](https://pi-tui.ratstack.sh/patterns/process-shell.md): Bind a temporary terminal process view to one custom interaction.
- [Output Vault](https://pi-tui.ratstack.sh/patterns/output-vault.md): Keep completed job output available after its foreground view closes.
- [Deferred Crest](https://pi-tui.ratstack.sh/patterns/deferred-crest.md): Mount optional startup content only after deferred discovery remains eligible.
- [Session Memento](https://pi-tui.ratstack.sh/patterns/session-memento.md): Reconstruct deliberate view state from typed custom session entries.
- [Snapshot Lens](https://pi-tui.ratstack.sh/patterns/snapshot-lens.md): Derive compact status and bounded detail from lifecycle snapshots.

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

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

- [Done Contract](https://pi-tui.ratstack.sh/patterns/done-contract.md): settles terminal-only custom interactions

## Linked from

- [Deferred Crest](https://pi-tui.ratstack.sh/patterns/deferred-crest.md): guards the available mount surface
