# Abort Tether

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

Also known as Abortable dialog.

## Intent

Tie a built-in dialog's lifetime to an operation's abort signal.

## Motivation

Nico's upstream dialogs can outlive the task that requested them without a cancellation signal.

## Applicability

- Use this when the owning task can end before a user answers.

## Structure

```text
owner signal -> dialog
abort -> 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.
- [`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.
- [`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.
- `Abort signal`: Ties the pending prompt to its owning operation.

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), [`ctx.ui.confirm`](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)

## Consequences

- The owning task can dismiss its pending prompt.
- Already-aborted and later-aborted signals both need cancellation handling.

## Implementation

- Handle an already-aborted signal.
- Do not leave the dialog promise pending on abort.

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

### Pending prompt

`open`: Show a prompt while its synthetic owner is active.

Checks at every width and theme:

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

Frames:

- 40 columns: ![Abort Tether, Pending prompt, 40 columns, dark theme](https://pi-tui.ratstack.sh/frames/abort-tether/open-40-dark.90f82a9df3c8.webp) ![Abort Tether, Pending prompt, 40 columns, light theme](https://pi-tui.ratstack.sh/frames/abort-tether/open-40-light.054e73456d0d.webp)
- 60 columns: ![Abort Tether, Pending prompt, 60 columns, dark theme](https://pi-tui.ratstack.sh/frames/abort-tether/open-60-dark.3e474b44dd7b.webp) ![Abort Tether, Pending prompt, 60 columns, light theme](https://pi-tui.ratstack.sh/frames/abort-tether/open-60-light.563faf533ec2.webp)
- 80 columns: ![Abort Tether, Pending prompt, 80 columns, dark theme](https://pi-tui.ratstack.sh/frames/abort-tether/open-80-dark.757c7da2870b.webp) ![Abort Tether, Pending prompt, 80 columns, light theme](https://pi-tui.ratstack.sh/frames/abort-tether/open-80-light.779ad1bf5233.webp)
- 120 columns: ![Abort Tether, Pending prompt, 120 columns, dark theme](https://pi-tui.ratstack.sh/frames/abort-tether/open-120-dark.88dbf29df14f.webp) ![Abort Tether, Pending prompt, 120 columns, light theme](https://pi-tui.ratstack.sh/frames/abort-tether/open-120-light.3003bf947d28.webp)

### Owner cancelled

`aborted`: Cancel the owner and show the dialog's cancellation result.

Checks at every width and theme:

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

Frames:

- 40 columns: ![Abort Tether, Owner cancelled, 40 columns, dark theme](https://pi-tui.ratstack.sh/frames/abort-tether/aborted-40-dark.1d2ea23c844d.webp) ![Abort Tether, Owner cancelled, 40 columns, light theme](https://pi-tui.ratstack.sh/frames/abort-tether/aborted-40-light.3d66c4de8c56.webp)
- 60 columns: ![Abort Tether, Owner cancelled, 60 columns, dark theme](https://pi-tui.ratstack.sh/frames/abort-tether/aborted-60-dark.aa50b1c5ff99.webp) ![Abort Tether, Owner cancelled, 60 columns, light theme](https://pi-tui.ratstack.sh/frames/abort-tether/aborted-60-light.90e9a3e7c701.webp)
- 80 columns: ![Abort Tether, Owner cancelled, 80 columns, dark theme](https://pi-tui.ratstack.sh/frames/abort-tether/aborted-80-dark.aca8501a7787.webp) ![Abort Tether, Owner cancelled, 80 columns, light theme](https://pi-tui.ratstack.sh/frames/abort-tether/aborted-80-light.107472bd6777.webp)
- 120 columns: ![Abort Tether, Owner cancelled, 120 columns, dark theme](https://pi-tui.ratstack.sh/frames/abort-tether/aborted-120-dark.7fe201d80879.webp) ![Abort Tether, Owner cancelled, 120 columns, light theme](https://pi-tui.ratstack.sh/frames/abort-tether/aborted-120-light.8dd79678fdf9.webp)

### No pending prompt

`already-aborted`: Pass an already-aborted signal and show immediate cancellation.

Checks at every width and theme:

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

Frames:

- 40 columns: ![Abort Tether, No pending prompt, 40 columns, dark theme](https://pi-tui.ratstack.sh/frames/abort-tether/already-aborted-40-dark.f42a08f35964.webp) ![Abort Tether, No pending prompt, 40 columns, light theme](https://pi-tui.ratstack.sh/frames/abort-tether/already-aborted-40-light.739f12d36b7d.webp)
- 60 columns: ![Abort Tether, No pending prompt, 60 columns, dark theme](https://pi-tui.ratstack.sh/frames/abort-tether/already-aborted-60-dark.81d0b915a6d6.webp) ![Abort Tether, No pending prompt, 60 columns, light theme](https://pi-tui.ratstack.sh/frames/abort-tether/already-aborted-60-light.31b83199e002.webp)
- 80 columns: ![Abort Tether, No pending prompt, 80 columns, dark theme](https://pi-tui.ratstack.sh/frames/abort-tether/already-aborted-80-dark.3d18d9d796e3.webp) ![Abort Tether, No pending prompt, 80 columns, light theme](https://pi-tui.ratstack.sh/frames/abort-tether/already-aborted-80-light.3f9289450432.webp)
- 120 columns: ![Abort Tether, No pending prompt, 120 columns, dark theme](https://pi-tui.ratstack.sh/frames/abort-tether/already-aborted-120-dark.0fa91357abf6.webp) ![Abort Tether, No pending prompt, 120 columns, light theme](https://pi-tui.ratstack.sh/frames/abort-tether/already-aborted-120-light.a9ccbd3a3fe5.webp)

## Sample code

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

```ts
// Abort Tether: a dialog's lifetime follows its owning operation's signal.
import type { ExtensionUIDialogOptions } from "@earendil-works/pi-coding-agent";
import type { PatternStory } from "../../../src/pattern.ts";

export const story: PatternStory = {
  id: "abort-tether", title: "Abort Tether", kind: "screen",
  apis: ["ctx.ui.confirm", "ctx.ui.select", "ExtensionUIDialogOptions"],
  states: [
    { id: "open", label: "Pending prompt" },
    { id: "aborted", label: "Owner cancelled", steps: [
      { type: "action", name: "abort-owner" }, { type: "resize", rows: 12 },
    ] },
    { id: "already-aborted", label: "No pending prompt", steps: [{ type: "action", name: "pre-aborted" }] },
  ],
  setup({ ui, theme, action }) {
    const owner = new AbortController();
    const options: ExtensionUIDialogOptions = { signal: owner.signal };
    ui.setEditorText("Keep this draft");
    ui.setWidget("owner", [theme.fg("muted", "Owner active · awaiting confirmation")]);
    ui.setStatus("owner", theme.fg("muted", "active"));
    void ui.confirm("Apply task patch?", "The owner may cancel this prompt.", options).then(confirmed => {
      ui.setWidget("owner", [
        theme.fg("success", "Owner aborted · confirmation is " + confirmed),
        theme.fg("muted", "No patch applied · draft kept"),
      ]);
      ui.setStatus("owner", theme.fg("muted", "cancelled"));
    });
    action("abort-owner", () => owner.abort());
    action("pre-aborted", () => {
      // Reusing the aborted signal resolves immediately without mounting a view.
      void ui.select("This prompt must not open", ["Apply patch"], options).then(selection => {
        ui.setWidget("owner", [
          theme.fg("success", "Already aborted · immediate return"),
          theme.fg("muted", "Selection: " + String(selection)),
          theme.fg("dim", "No pending prompt · editor owns focus"),
        ]);
      });
    });
    return () => owner.abort();
  },
};
```

## Known uses: seen in Nico's repos

- [**earendil-works/pi**](https://github.com/earendil-works/pi): Cancel extension dialogs with AbortSignal
  - [packages/coding-agent/src/core/extensions/types.ts:50-64](https://github.com/earendil-works/pi/blob/9771fa1e447ca5f1d564bf49d2dbef2c7f79e334/packages/coding-agent/src/core/extensions/types.ts#L50-L64) @9771fa1e

## For agents: choose and check

Choose Abort Tether 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.
- [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.
- 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), [`ctx.ui.confirm`](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) before you use them.
- Every reference frame gets the verdict its state expects.

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

- [Abort Lantern](https://pi-tui.ratstack.sh/patterns/abort-lantern.md): owns cancellation during work

## Linked from

- [Abort Lantern](https://pi-tui.ratstack.sh/patterns/abort-lantern.md): cancels a prompt instead of a loader
