# Dialog Fuse

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

Also known as Countdown dialog.

## Intent

Show remaining time before a transient dialog automatically dismisses.

## Motivation

Nico's upstream dialog timeout adds a visible countdown before automatic dismissal.

## Applicability

- Use this when a prompt should expire without user input.

## Structure

```text
dialog + timeout -> countdown
zero -> cancellation result
```

## Participants

- [`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.
- [`ExtensionUIDialogOptions`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/extensions.md#interact-with-the-user): Carries a timeout or abort signal for a built-in dialog.
- `Deadline`: Ends the prompt with a cancellation result when time runs out.

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

## Consequences

- Transient prompts can end without user input.
- Callers must treat timeout as a cancellation result.

## Implementation

- Handle the timeout result as cancellation.
- Clear timer resources when the view closes.

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

### Countdown starts

`initial`: Show a synthetic selection prompt with its timeout countdown.

Checks at every width and theme:

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

Frames:

- 40 columns: ![Dialog Fuse, Countdown starts, 40 columns, dark theme](https://pi-tui.ratstack.sh/frames/dialog-fuse/initial-40-dark.df5574c806a3.webp) [animated](https://pi-tui.ratstack.sh/frames/dialog-fuse/initial-40-dark.anim.38cbc2166106.webp) ![Dialog Fuse, Countdown starts, 40 columns, light theme](https://pi-tui.ratstack.sh/frames/dialog-fuse/initial-40-light.802c52b03b15.webp) [animated](https://pi-tui.ratstack.sh/frames/dialog-fuse/initial-40-light.anim.27aeba53772d.webp)
- 60 columns: ![Dialog Fuse, Countdown starts, 60 columns, dark theme](https://pi-tui.ratstack.sh/frames/dialog-fuse/initial-60-dark.0b339d0896af.webp) [animated](https://pi-tui.ratstack.sh/frames/dialog-fuse/initial-60-dark.anim.550aa08eaa1b.webp) ![Dialog Fuse, Countdown starts, 60 columns, light theme](https://pi-tui.ratstack.sh/frames/dialog-fuse/initial-60-light.57c8aa5a3e65.webp) [animated](https://pi-tui.ratstack.sh/frames/dialog-fuse/initial-60-light.anim.c830a91d7f41.webp)
- 80 columns: ![Dialog Fuse, Countdown starts, 80 columns, dark theme](https://pi-tui.ratstack.sh/frames/dialog-fuse/initial-80-dark.2bd4fd3f96fd.webp) [animated](https://pi-tui.ratstack.sh/frames/dialog-fuse/initial-80-dark.anim.97b2647c053f.webp) ![Dialog Fuse, Countdown starts, 80 columns, light theme](https://pi-tui.ratstack.sh/frames/dialog-fuse/initial-80-light.9db0268d108c.webp) [animated](https://pi-tui.ratstack.sh/frames/dialog-fuse/initial-80-light.anim.870136e8e734.webp)
- 120 columns: ![Dialog Fuse, Countdown starts, 120 columns, dark theme](https://pi-tui.ratstack.sh/frames/dialog-fuse/initial-120-dark.89fd80b6a5b9.webp) [animated](https://pi-tui.ratstack.sh/frames/dialog-fuse/initial-120-dark.anim.14862bdd147b.webp) ![Dialog Fuse, Countdown starts, 120 columns, light theme](https://pi-tui.ratstack.sh/frames/dialog-fuse/initial-120-light.b087f2f56eaa.webp) [animated](https://pi-tui.ratstack.sh/frames/dialog-fuse/initial-120-light.anim.568a1947fd28.webp)

### Time remaining

`ticking`: Advance the visible countdown without a selection.

Checks at every width and theme:

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

Frames:

- 40 columns: ![Dialog Fuse, Time remaining, 40 columns, dark theme](https://pi-tui.ratstack.sh/frames/dialog-fuse/ticking-40-dark.df5574c806a3.webp) [animated](https://pi-tui.ratstack.sh/frames/dialog-fuse/ticking-40-dark.anim.fa8cbff6cb8c.webp) ![Dialog Fuse, Time remaining, 40 columns, light theme](https://pi-tui.ratstack.sh/frames/dialog-fuse/ticking-40-light.802c52b03b15.webp) [animated](https://pi-tui.ratstack.sh/frames/dialog-fuse/ticking-40-light.anim.19744127b0eb.webp)
- 60 columns: ![Dialog Fuse, Time remaining, 60 columns, dark theme](https://pi-tui.ratstack.sh/frames/dialog-fuse/ticking-60-dark.0b339d0896af.webp) [animated](https://pi-tui.ratstack.sh/frames/dialog-fuse/ticking-60-dark.anim.a1dfaaa898b1.webp) ![Dialog Fuse, Time remaining, 60 columns, light theme](https://pi-tui.ratstack.sh/frames/dialog-fuse/ticking-60-light.57c8aa5a3e65.webp) [animated](https://pi-tui.ratstack.sh/frames/dialog-fuse/ticking-60-light.anim.b4ed4e977254.webp)
- 80 columns: ![Dialog Fuse, Time remaining, 80 columns, dark theme](https://pi-tui.ratstack.sh/frames/dialog-fuse/ticking-80-dark.2bd4fd3f96fd.webp) [animated](https://pi-tui.ratstack.sh/frames/dialog-fuse/ticking-80-dark.anim.fe15dfd279c3.webp) ![Dialog Fuse, Time remaining, 80 columns, light theme](https://pi-tui.ratstack.sh/frames/dialog-fuse/ticking-80-light.9db0268d108c.webp) [animated](https://pi-tui.ratstack.sh/frames/dialog-fuse/ticking-80-light.anim.3790898ebdd4.webp)
- 120 columns: ![Dialog Fuse, Time remaining, 120 columns, dark theme](https://pi-tui.ratstack.sh/frames/dialog-fuse/ticking-120-dark.89fd80b6a5b9.webp) [animated](https://pi-tui.ratstack.sh/frames/dialog-fuse/ticking-120-dark.anim.f4071e62142f.webp) ![Dialog Fuse, Time remaining, 120 columns, light theme](https://pi-tui.ratstack.sh/frames/dialog-fuse/ticking-120-light.b087f2f56eaa.webp) [animated](https://pi-tui.ratstack.sh/frames/dialog-fuse/ticking-120-light.anim.ae9ecaa59ba0.webp)

### Timeout result

`expired`: Show the host receiving an undefined selection after expiry.

Checks at every width and theme:

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

Frames:

- 40 columns: ![Dialog Fuse, Timeout result, 40 columns, dark theme](https://pi-tui.ratstack.sh/frames/dialog-fuse/expired-40-dark.595729d76d7b.webp) ![Dialog Fuse, Timeout result, 40 columns, light theme](https://pi-tui.ratstack.sh/frames/dialog-fuse/expired-40-light.2f8296554c6c.webp)
- 60 columns: ![Dialog Fuse, Timeout result, 60 columns, dark theme](https://pi-tui.ratstack.sh/frames/dialog-fuse/expired-60-dark.f796e39ad351.webp) ![Dialog Fuse, Timeout result, 60 columns, light theme](https://pi-tui.ratstack.sh/frames/dialog-fuse/expired-60-light.914d10e53879.webp)
- 80 columns: ![Dialog Fuse, Timeout result, 80 columns, dark theme](https://pi-tui.ratstack.sh/frames/dialog-fuse/expired-80-dark.521aa79ab37e.webp) ![Dialog Fuse, Timeout result, 80 columns, light theme](https://pi-tui.ratstack.sh/frames/dialog-fuse/expired-80-light.cf2ce9b072ec.webp)
- 120 columns: ![Dialog Fuse, Timeout result, 120 columns, dark theme](https://pi-tui.ratstack.sh/frames/dialog-fuse/expired-120-dark.faa5be5957b9.webp) ![Dialog Fuse, Timeout result, 120 columns, light theme](https://pi-tui.ratstack.sh/frames/dialog-fuse/expired-120-light.362b5a04cda6.webp)

## Sample code

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

```ts
// Dialog Fuse: a fixed deadline cancels a prompt without user input.
import type { ExtensionUIDialogOptions } from "@earendil-works/pi-coding-agent";
import type { PatternStory } from "../../../src/pattern.ts";

export const story: PatternStory = {
  id: "dialog-fuse", title: "Dialog Fuse", kind: "screen",
  apis: ["ctx.ui.select", "ExtensionUIDialogOptions", "ctx.ui.setWidget"],
  states: [
    { id: "initial", label: "Countdown starts", animated: true, steps: [{ type: "tick", ms: 100 }] },
    { id: "ticking", label: "Time remaining", animated: true, steps: [{ type: "tick", ms: 1000 }] },
    { id: "expired", label: "Timeout result", steps: [
      { type: "tick", ms: 1000 }, { type: "resize", rows: 12 },
    ] },
  ],
  setup({ ui, theme }) {
    const deadline: ExtensionUIDialogOptions = { timeout: 2000 };
    ui.setEditorText("Draft remains unchanged");
    ui.setWidget("deadline", [theme.fg("muted", "Transient prompt · fixed 2s deadline")]);
    ui.setStatus("prompt", theme.fg("muted", "pending"));
    void ui.select("Choose next task", ["Review patch", "Run tests"], deadline).then(selection => {
      if (selection === undefined) {
        ui.setWidget("deadline", [
          theme.fg("success", "Expired · selection is undefined"),
          theme.fg("muted", "No task started · draft kept"),
        ]);
        ui.setStatus("prompt", theme.fg("muted", "expired"));
        return;
      }
      ui.setWidget("deadline", [theme.fg("success", "Selected: " + selection)]);
      ui.setStatus("prompt", theme.fg("success", "answered"));
    });
  },
};
```

## Known uses: seen in Nico's repos

- [**earendil-works/pi**](https://github.com/earendil-works/pi): Auto-dismiss extension dialogs with a live countdown
  - [packages/coding-agent/src/core/extensions/types.ts:45-60](https://github.com/earendil-works/pi/blob/77477f6166be8e0eb1ca2f7ab9fc3c271fde6586/packages/coding-agent/src/core/extensions/types.ts#L45-L60) @77477f61
- [**pi-powerline-footer**](https://github.com/nicobailon/pi-powerline-footer): Timed dismissible welcome overlay
  - [index.ts:3584-3648](https://github.com/nicobailon/pi-powerline-footer/blob/859dee671b633fb533b07ceba3e6c1ab1c43360a/index.ts#L3584-L3648) @859dee67

## For agents: choose and check

Choose Dialog Fuse 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.
- [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.
- [Settings Bench](https://pi-tui.ratstack.sh/patterns/settings-bench.md): Keep configuration interaction separate from the live activity view.
- [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.
- This pattern animates. Check every frame of the animation, not only the first.
- Read the Pi 1.0.3 docs for [`ctx.ui.select`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/extensions.md#interact-with-the-user), [`ExtensionUIDialogOptions`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/extensions.md#interact-with-the-user), `ctx.ui.setWidget` before you use them.
- Every reference frame gets the verdict its state expects.

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

- [Idle Fuse](https://pi-tui.ratstack.sh/patterns/idle-fuse.md): expires from inactivity instead

## Linked from

- [Idle Fuse](https://pi-tui.ratstack.sh/patterns/idle-fuse.md): uses a fixed expiry deadline
