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

Lifecycle / Status and widgets · `notice-fuse` · [HTML](https://pi-tui.ratstack.sh/patterns/notice-fuse/) · [JSON](https://pi-tui.ratstack.sh/patterns/notice-fuse.json) · [all patterns](https://pi-tui.ratstack.sh/patterns.md)

Also known as Expiring status.

## Intent

Clear a one-off keyed notice after its display interval.

## Motivation

Skill Palette clears a shortcut notice from a timer that could otherwise erase a replacement notice.

## Applicability

- Use this when feedback should not remain in the footer indefinitely.

## Structure

```text
notice -> keyed slot + timer
new notice -> replace owned timer
latest deadline -> clear
```

## Participants

- [`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.
- `Notice timer`: Clears only the current notice at its own deadline.

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

## Consequences

- One-off feedback clears without a persistent footer slot.
- Timers need replacement and stale-context guards.

## Implementation

- An older timer must not erase a newer value.
- Cancel owned timers and reject stale session contexts.

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

### Notice shown

`notice`: Show a synthetic shortcut notice in a keyed slot.

Checks at every width and theme:

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

Frames:

- 40 columns: ![Notice Fuse, Notice shown, 40 columns, dark theme](https://pi-tui.ratstack.sh/frames/notice-fuse/notice-40-dark.755e678f5a68.webp) [animated](https://pi-tui.ratstack.sh/frames/notice-fuse/notice-40-dark.anim.9c4d3e6d1bb6.webp) ![Notice Fuse, Notice shown, 40 columns, light theme](https://pi-tui.ratstack.sh/frames/notice-fuse/notice-40-light.d17f640c57b4.webp) [animated](https://pi-tui.ratstack.sh/frames/notice-fuse/notice-40-light.anim.3e4ce3c9093e.webp)
- 60 columns: ![Notice Fuse, Notice shown, 60 columns, dark theme](https://pi-tui.ratstack.sh/frames/notice-fuse/notice-60-dark.939a489f3008.webp) [animated](https://pi-tui.ratstack.sh/frames/notice-fuse/notice-60-dark.anim.64712b857caf.webp) ![Notice Fuse, Notice shown, 60 columns, light theme](https://pi-tui.ratstack.sh/frames/notice-fuse/notice-60-light.99f3c4ea912d.webp) [animated](https://pi-tui.ratstack.sh/frames/notice-fuse/notice-60-light.anim.51fb7cd86e0f.webp)
- 80 columns: ![Notice Fuse, Notice shown, 80 columns, dark theme](https://pi-tui.ratstack.sh/frames/notice-fuse/notice-80-dark.fc1825de54b9.webp) [animated](https://pi-tui.ratstack.sh/frames/notice-fuse/notice-80-dark.anim.4b7c79584ab3.webp) ![Notice Fuse, Notice shown, 80 columns, light theme](https://pi-tui.ratstack.sh/frames/notice-fuse/notice-80-light.e1ac3f668ec3.webp) [animated](https://pi-tui.ratstack.sh/frames/notice-fuse/notice-80-light.anim.eda6fb4e351c.webp)
- 120 columns: ![Notice Fuse, Notice shown, 120 columns, dark theme](https://pi-tui.ratstack.sh/frames/notice-fuse/notice-120-dark.ed33cc19e899.webp) [animated](https://pi-tui.ratstack.sh/frames/notice-fuse/notice-120-dark.anim.e3284eb7a674.webp) ![Notice Fuse, Notice shown, 120 columns, light theme](https://pi-tui.ratstack.sh/frames/notice-fuse/notice-120-light.ac4c09f30754.webp) [animated](https://pi-tui.ratstack.sh/frames/notice-fuse/notice-120-light.anim.f97cb58d6ebf.webp)

### New notice

`replacement`: Replace it before the old timer expires.

Checks at every width and theme:

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

Frames:

- 40 columns: ![Notice Fuse, New notice, 40 columns, dark theme](https://pi-tui.ratstack.sh/frames/notice-fuse/replacement-40-dark.755e678f5a68.webp) [animated](https://pi-tui.ratstack.sh/frames/notice-fuse/replacement-40-dark.anim.de7a5b86f9a4.webp) ![Notice Fuse, New notice, 40 columns, light theme](https://pi-tui.ratstack.sh/frames/notice-fuse/replacement-40-light.d17f640c57b4.webp) [animated](https://pi-tui.ratstack.sh/frames/notice-fuse/replacement-40-light.anim.a031e98f7150.webp)
- 60 columns: ![Notice Fuse, New notice, 60 columns, dark theme](https://pi-tui.ratstack.sh/frames/notice-fuse/replacement-60-dark.939a489f3008.webp) [animated](https://pi-tui.ratstack.sh/frames/notice-fuse/replacement-60-dark.anim.0fefdba7bbd5.webp) ![Notice Fuse, New notice, 60 columns, light theme](https://pi-tui.ratstack.sh/frames/notice-fuse/replacement-60-light.99f3c4ea912d.webp) [animated](https://pi-tui.ratstack.sh/frames/notice-fuse/replacement-60-light.anim.27c038e635bc.webp)
- 80 columns: ![Notice Fuse, New notice, 80 columns, dark theme](https://pi-tui.ratstack.sh/frames/notice-fuse/replacement-80-dark.fc1825de54b9.webp) [animated](https://pi-tui.ratstack.sh/frames/notice-fuse/replacement-80-dark.anim.37fe55cab627.webp) ![Notice Fuse, New notice, 80 columns, light theme](https://pi-tui.ratstack.sh/frames/notice-fuse/replacement-80-light.e1ac3f668ec3.webp) [animated](https://pi-tui.ratstack.sh/frames/notice-fuse/replacement-80-light.anim.776d31c84283.webp)
- 120 columns: ![Notice Fuse, New notice, 120 columns, dark theme](https://pi-tui.ratstack.sh/frames/notice-fuse/replacement-120-dark.ed33cc19e899.webp) [animated](https://pi-tui.ratstack.sh/frames/notice-fuse/replacement-120-dark.anim.6366111e1c35.webp) ![Notice Fuse, New notice, 120 columns, light theme](https://pi-tui.ratstack.sh/frames/notice-fuse/replacement-120-light.ac4c09f30754.webp) [animated](https://pi-tui.ratstack.sh/frames/notice-fuse/replacement-120-light.anim.2f679d33b73d.webp)

### Latest notice expires

`expired`: Keep the replacement through the old deadline and clear at its own deadline.

Checks at every width and theme:

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

Frames:

- 40 columns: ![Notice Fuse, Latest notice expires, 40 columns, dark theme](https://pi-tui.ratstack.sh/frames/notice-fuse/expired-40-dark.fd93053b6b5f.webp) [animated](https://pi-tui.ratstack.sh/frames/notice-fuse/expired-40-dark.anim.8aeba0a9cf02.webp) ![Notice Fuse, Latest notice expires, 40 columns, light theme](https://pi-tui.ratstack.sh/frames/notice-fuse/expired-40-light.4182e39f30d9.webp) [animated](https://pi-tui.ratstack.sh/frames/notice-fuse/expired-40-light.anim.b11007fa6bc9.webp)
- 60 columns: ![Notice Fuse, Latest notice expires, 60 columns, dark theme](https://pi-tui.ratstack.sh/frames/notice-fuse/expired-60-dark.fe18e3eeb9a9.webp) [animated](https://pi-tui.ratstack.sh/frames/notice-fuse/expired-60-dark.anim.326601a2a977.webp) ![Notice Fuse, Latest notice expires, 60 columns, light theme](https://pi-tui.ratstack.sh/frames/notice-fuse/expired-60-light.d56bdad0e7f6.webp) [animated](https://pi-tui.ratstack.sh/frames/notice-fuse/expired-60-light.anim.546371b8b91b.webp)
- 80 columns: ![Notice Fuse, Latest notice expires, 80 columns, dark theme](https://pi-tui.ratstack.sh/frames/notice-fuse/expired-80-dark.b365d1aef84d.webp) [animated](https://pi-tui.ratstack.sh/frames/notice-fuse/expired-80-dark.anim.0ce0fe107e98.webp) ![Notice Fuse, Latest notice expires, 80 columns, light theme](https://pi-tui.ratstack.sh/frames/notice-fuse/expired-80-light.9e072179646f.webp) [animated](https://pi-tui.ratstack.sh/frames/notice-fuse/expired-80-light.anim.245773047232.webp)
- 120 columns: ![Notice Fuse, Latest notice expires, 120 columns, dark theme](https://pi-tui.ratstack.sh/frames/notice-fuse/expired-120-dark.cbbbe7703304.webp) [animated](https://pi-tui.ratstack.sh/frames/notice-fuse/expired-120-dark.anim.666987adbf5a.webp) ![Notice Fuse, Latest notice expires, 120 columns, light theme](https://pi-tui.ratstack.sh/frames/notice-fuse/expired-120-light.fcb37f97e799.webp) [animated](https://pi-tui.ratstack.sh/frames/notice-fuse/expired-120-light.anim.8e3e69aca047.webp)

## Sample code

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

```ts
// Notice Fuse: cancel the previous expiry so it cannot erase a newer notice.
import type { PatternStory } from "../../../src/pattern.ts";

export const story: PatternStory = {
  id: "notice-fuse", title: "Notice Fuse", kind: "screen",
  apis: ["setStatus"],
  states: [
    { id: "notice", label: "Notice shown", animated: true, steps: [{ type: "tick", ms: 400 }] },
    { id: "replacement", label: "New notice", animated: true, steps: [
      { type: "action", name: "replace" }, { type: "tick", ms: 700 },
    ] },
    { id: "expired", label: "Latest notice expires", animated: true, steps: [{ type: "tick", ms: 500 }] },
  ],
  setup({ ui, theme, action }) {
    let timer: ReturnType<typeof setTimeout> | undefined;
    let proofTimer: ReturnType<typeof setTimeout> | undefined;
    let noticeGeneration = 0;
    let currentNotice: string | undefined;
    const notice = (text: string) => {
      if (timer !== undefined) clearTimeout(timer);
      const generation = ++noticeGeneration;
      currentNotice = text;
      ui.setStatus("notice", theme.fg("success", text));
      timer = setTimeout(() => {
        if (generation !== noticeGeneration) return;
        currentNotice = undefined;
        ui.setStatus("notice", undefined);
      }, 1000);
    };
    ui.setEditorText("Review the task shortcuts.");
    ui.setWidget("hint", [theme.fg("muted", "Shortcut notice expires after 1s")]);
    notice("Shortcut copied");
    action("replace", () => {
      notice("New shortcut ready");
      // Cross the old deadline (1000ms) but not the new one (1400ms).
      proofTimer = setTimeout(() => {
        if (currentNotice !== "New shortcut ready") throw new Error("Old expiry erased replacement");
      }, 650);
    });
    return () => {
      noticeGeneration++;
      if (timer !== undefined) clearTimeout(timer);
      if (proofTimer !== undefined) clearTimeout(proofTimer);
    };
  },
};
```

## Known uses: seen in Nico's repos

- [**pi-skill-palette**](https://github.com/nicobailon/pi-skill-palette): Expire one-off status notices
  - [index.ts:907-908](https://github.com/nicobailon/pi-skill-palette/blob/a5c4429b8c2e33ab903d07856497014f3d5ad34e/index.ts#L907-L908) @a5c4429b

## For agents: choose and check

Choose Notice Fuse when your job matches its intent and applicability above. Its neighbours in Status and widgets are listed below. Read the one whose intent fits your job more closely before you commit.

- [Keyed Slot](https://pi-tui.ratstack.sh/patterns/keyed-slot.md): Publish and clear compact state under one stable footer key.
- [Signal Pair](https://pi-tui.ratstack.sh/patterns/signal-pair.md): Pair a compact footer signal with a structured near-editor widget.
- [Widget Dock](https://pi-tui.ratstack.sh/patterns/widget-dock.md): Mount and replace auxiliary content under a stable widget key.
- [Result Relay](https://pi-tui.ratstack.sh/patterns/result-relay.md): Replace pending feedback with a persistent non-modal result widget.
- [Work Caption](https://pi-tui.ratstack.sh/patterns/work-caption.md): Set task-specific text in Pi's active working indicator.

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

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

- [Refresh Lease](https://pi-tui.ratstack.sh/patterns/refresh-lease.md): also ties timers to owners

## Linked from

- [Keyed Slot](https://pi-tui.ratstack.sh/patterns/keyed-slot.md): adds a deadline to the slot
- [Work Caption](https://pi-tui.ratstack.sh/patterns/work-caption.md): shows an independent temporary notice
