# Pause Latch

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

Also known as Pause controls.

## Intent

Keep pause, resume and quit controls inside the component input contract.

## Motivation

Nico's arcade components need to pause local activity without losing quit and resume controls.

## Applicability

- Use this when a live view must stop changing while remaining interactive.

## Structure

```text
running -> pause -> paused
paused -> resume -> running
any state -> quit
```

## Participants

- [`Component`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/tui.md#understand-the-component-model): Renders width-bounded lines and invalidates cached output.
- [`matchesKey`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/tui.md#handle-keyboard-input-and-focus): Recognizes explicit terminal key combinations.
- [`TUI.requestRender`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/tui.md#understand-the-component-model): Requests a coalesced redraw after state changes.
- `Pause state`: Stops local change without disabling resume and quit.

Pi component APIs: [`Component`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/tui.md#understand-the-component-model), [`matchesKey`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/tui.md#handle-keyboard-input-and-focus), [`TUI.requestRender`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/tui.md#understand-the-component-model)

## Consequences

- A live view can stop changing while remaining interactive.
- Input remains active and the cited games do not demonstrate release events.

## Implementation

- Expose control hints in the rendered view.
- The cited games do not demonstrate key-release handling.

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

### Running view

`running`: Show a synthetic moving progress marker with pause and quit hints.

Checks at every width and theme:

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

Frames:

- 40 columns: ![Pause Latch, Running view, 40 columns, dark theme](https://pi-tui.ratstack.sh/frames/pause-latch/running-40-dark.1335446eae5b.webp) [animated](https://pi-tui.ratstack.sh/frames/pause-latch/running-40-dark.anim.00dbceff6376.webp) ![Pause Latch, Running view, 40 columns, light theme](https://pi-tui.ratstack.sh/frames/pause-latch/running-40-light.b262621a0053.webp) [animated](https://pi-tui.ratstack.sh/frames/pause-latch/running-40-light.anim.df2144e8af5d.webp)
- 60 columns: ![Pause Latch, Running view, 60 columns, dark theme](https://pi-tui.ratstack.sh/frames/pause-latch/running-60-dark.b674ab662a13.webp) [animated](https://pi-tui.ratstack.sh/frames/pause-latch/running-60-dark.anim.8890e31e5414.webp) ![Pause Latch, Running view, 60 columns, light theme](https://pi-tui.ratstack.sh/frames/pause-latch/running-60-light.4856dcc10388.webp) [animated](https://pi-tui.ratstack.sh/frames/pause-latch/running-60-light.anim.9aa08adffcd1.webp)
- 80 columns: ![Pause Latch, Running view, 80 columns, dark theme](https://pi-tui.ratstack.sh/frames/pause-latch/running-80-dark.e9f8bca8e01b.webp) [animated](https://pi-tui.ratstack.sh/frames/pause-latch/running-80-dark.anim.a88f98926b9a.webp) ![Pause Latch, Running view, 80 columns, light theme](https://pi-tui.ratstack.sh/frames/pause-latch/running-80-light.10ebd4afd2e7.webp) [animated](https://pi-tui.ratstack.sh/frames/pause-latch/running-80-light.anim.56e3cbb98901.webp)
- 120 columns: ![Pause Latch, Running view, 120 columns, dark theme](https://pi-tui.ratstack.sh/frames/pause-latch/running-120-dark.b2fde891809b.webp) [animated](https://pi-tui.ratstack.sh/frames/pause-latch/running-120-dark.anim.82e5e9521c20.webp) ![Pause Latch, Running view, 120 columns, light theme](https://pi-tui.ratstack.sh/frames/pause-latch/running-120-light.b76736d7f79d.webp) [animated](https://pi-tui.ratstack.sh/frames/pause-latch/running-120-light.anim.f2a839570be0.webp)

### Paused view

`paused`: Stop its movement while still accepting resume and quit.

Checks at every width and theme:

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

Frames:

- 40 columns: ![Pause Latch, Paused view, 40 columns, dark theme](https://pi-tui.ratstack.sh/frames/pause-latch/paused-40-dark.4a811001bd9f.webp) [animated](https://pi-tui.ratstack.sh/frames/pause-latch/paused-40-dark.anim.fa8fbc878d65.webp) ![Pause Latch, Paused view, 40 columns, light theme](https://pi-tui.ratstack.sh/frames/pause-latch/paused-40-light.98f689af1fd7.webp) [animated](https://pi-tui.ratstack.sh/frames/pause-latch/paused-40-light.anim.d58537e98c20.webp)
- 60 columns: ![Pause Latch, Paused view, 60 columns, dark theme](https://pi-tui.ratstack.sh/frames/pause-latch/paused-60-dark.4fdc7f9dbe0f.webp) [animated](https://pi-tui.ratstack.sh/frames/pause-latch/paused-60-dark.anim.e0ee296ce97a.webp) ![Pause Latch, Paused view, 60 columns, light theme](https://pi-tui.ratstack.sh/frames/pause-latch/paused-60-light.9be4fa19f184.webp) [animated](https://pi-tui.ratstack.sh/frames/pause-latch/paused-60-light.anim.319a0302b1d0.webp)
- 80 columns: ![Pause Latch, Paused view, 80 columns, dark theme](https://pi-tui.ratstack.sh/frames/pause-latch/paused-80-dark.ddf9d3ed2183.webp) [animated](https://pi-tui.ratstack.sh/frames/pause-latch/paused-80-dark.anim.3c19c32e1301.webp) ![Pause Latch, Paused view, 80 columns, light theme](https://pi-tui.ratstack.sh/frames/pause-latch/paused-80-light.fbb0127045c9.webp) [animated](https://pi-tui.ratstack.sh/frames/pause-latch/paused-80-light.anim.17aa9feb440e.webp)
- 120 columns: ![Pause Latch, Paused view, 120 columns, dark theme](https://pi-tui.ratstack.sh/frames/pause-latch/paused-120-dark.f533baadd6bb.webp) [animated](https://pi-tui.ratstack.sh/frames/pause-latch/paused-120-dark.anim.b77bd214d790.webp) ![Pause Latch, Paused view, 120 columns, light theme](https://pi-tui.ratstack.sh/frames/pause-latch/paused-120-light.3e53df577436.webp) [animated](https://pi-tui.ratstack.sh/frames/pause-latch/paused-120-light.anim.c7ce7700e52e.webp)

### Resumed view

`resumed`: Resume movement without recreating the interaction.

Checks at every width and theme:

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

Frames:

- 40 columns: ![Pause Latch, Resumed view, 40 columns, dark theme](https://pi-tui.ratstack.sh/frames/pause-latch/resumed-40-dark.c863f6edb2fb.webp) [animated](https://pi-tui.ratstack.sh/frames/pause-latch/resumed-40-dark.anim.62694d297e19.webp) ![Pause Latch, Resumed view, 40 columns, light theme](https://pi-tui.ratstack.sh/frames/pause-latch/resumed-40-light.394a14313605.webp) [animated](https://pi-tui.ratstack.sh/frames/pause-latch/resumed-40-light.anim.48fb049567c1.webp)
- 60 columns: ![Pause Latch, Resumed view, 60 columns, dark theme](https://pi-tui.ratstack.sh/frames/pause-latch/resumed-60-dark.6ec5d5d799cd.webp) [animated](https://pi-tui.ratstack.sh/frames/pause-latch/resumed-60-dark.anim.a0aa87b36eea.webp) ![Pause Latch, Resumed view, 60 columns, light theme](https://pi-tui.ratstack.sh/frames/pause-latch/resumed-60-light.64394d3f0f3f.webp) [animated](https://pi-tui.ratstack.sh/frames/pause-latch/resumed-60-light.anim.92d2a1c2aa09.webp)
- 80 columns: ![Pause Latch, Resumed view, 80 columns, dark theme](https://pi-tui.ratstack.sh/frames/pause-latch/resumed-80-dark.8bc821f3bf12.webp) [animated](https://pi-tui.ratstack.sh/frames/pause-latch/resumed-80-dark.anim.5fbcaef92649.webp) ![Pause Latch, Resumed view, 80 columns, light theme](https://pi-tui.ratstack.sh/frames/pause-latch/resumed-80-light.b4b99821f783.webp) [animated](https://pi-tui.ratstack.sh/frames/pause-latch/resumed-80-light.anim.e1e4187a2037.webp)
- 120 columns: ![Pause Latch, Resumed view, 120 columns, dark theme](https://pi-tui.ratstack.sh/frames/pause-latch/resumed-120-dark.d00fc3a56657.webp) [animated](https://pi-tui.ratstack.sh/frames/pause-latch/resumed-120-dark.anim.d7bebfe504a9.webp) ![Pause Latch, Resumed view, 120 columns, light theme](https://pi-tui.ratstack.sh/frames/pause-latch/resumed-120-light.49969f02f1b2.webp) [animated](https://pi-tui.ratstack.sh/frames/pause-latch/resumed-120-light.anim.ade3cbd5a932.webp)

## Sample code

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

```ts
// Pause Latch: freeze local ticks without disabling resume or quit.
// Machine: running --p--> paused --p--> running; either --q--> closed.
import { matchesKey, truncateToWidth, type Component } from "@earendil-works/pi-tui";
import type { PatternStory } from "../../../src/pattern.ts";

export const story: PatternStory = {
  id: "pause-latch",
  title: "Pause Latch",
  kind: "component",
  apis: ["Component", "matchesKey", "TUI.requestRender"],
  rows: 7,
  states: [
    { id: "running", label: "Running view", animated: true, steps: [{ type: "tick", ms: 900 }] },
    { id: "paused", label: "Paused view", animated: true, steps: [
      { type: "keys", data: "p" }, { type: "tick", ms: 600 },
    ] },
    { id: "resumed", label: "Resumed view", animated: true, steps: [
      { type: "keys", data: "p" }, { type: "tick", ms: 900 },
    ] },
  ],
  setup({ theme, tui }) {
    let mode: "running" | "paused" | "closed" = "running";
    let ticks = 0;
    const timer = setInterval(() => {
      if (mode !== "running") return;
      ticks++;
      tui.requestRender();
    }, 150);
    const latch: Component & { dispose(): void } = {
      invalidate() {},
      dispose() { clearInterval(timer); },
      handleInput(data) {
        if (matchesKey(data, "q")) { mode = "closed"; clearInterval(timer); }
        else if (matchesKey(data, "p") && mode !== "closed") mode = mode === "running" ? "paused" : "running";
        tui.requestRender();
      },
      render(width) {
        const position = ticks % 16;
        const track = "·".repeat(position) + "◆" + "·".repeat(15 - position);
        return [
          theme.fg("accent", "Pause Latch · local simulation"),
          theme.fg(mode === "running" ? "success" : "warning", mode.toUpperCase() + " · tick " + ticks),
          theme.fg("accent", "[" + track + "]"),
          theme.fg("text", "Parser scan · one marker per tick"),
          theme.fg("muted", truncateToWidth(mode === "paused" ? "p resume · q quit · ticks frozen" : "p pause · q quit", width)),
        ];
      },
    };
    return latch;
  },
};
```

## Known uses: seen in Nico's repos

- [**pi-extensions**](https://github.com/nicobailon/pi-extensions): Arcade: input handling and pause state
  - [arcade/tetris.ts:395-470](https://github.com/nicobailon/pi-extensions/blob/bca5070b541ffa7d273e74036dcd7d5f8b63eed3/arcade/tetris.ts#L395-L470) @bca5070b
  - [arcade/ping.ts:349-426](https://github.com/nicobailon/pi-extensions/blob/bca5070b541ffa7d273e74036dcd7d5f8b63eed3/arcade/ping.ts#L349-L426) @bca5070b
  - [arcade/picman.ts:248-263](https://github.com/nicobailon/pi-extensions/blob/bca5070b541ffa7d273e74036dcd7d5f8b63eed3/arcade/picman.ts#L248-L263) @bca5070b
  - [arcade/spice-invaders.ts:834-910](https://github.com/nicobailon/pi-extensions/blob/bca5070b541ffa7d273e74036dcd7d5f8b63eed3/arcade/spice-invaders.ts#L834-L910) @bca5070b
  - [arcade/badlogic-game/badlogic-game.ts:172-200](https://github.com/nicobailon/pi-extensions/blob/bca5070b541ffa7d273e74036dcd7d5f8b63eed3/arcade/badlogic-game/badlogic-game.ts#L172-L200) @bca5070b

## For agents: choose and check

Choose Pause Latch 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.
- [Input Switch](https://pi-tui.ratstack.sh/patterns/input-switch.md): Continue, transform or handle submitted input before normal processing.
- [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.
- [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.
- This pattern animates. Check every frame of the animation, not only the first.
- Read the Pi 1.0.3 docs for [`Component`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/tui.md#understand-the-component-model), [`matchesKey`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/tui.md#handle-keyboard-input-and-focus), [`TUI.requestRender`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/tui.md#understand-the-component-model) before you use them.
- Every reference frame gets the verdict its state expects.

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

- [Tick Heart](https://pi-tui.ratstack.sh/patterns/tick-heart.md): advances the live state

## Linked from

- [Tick Heart](https://pi-tui.ratstack.sh/patterns/tick-heart.md): suspends change while preserving input
