# Work Caption

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

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

Also known as Working message.

## Intent

Set task-specific text in Pi's active working indicator.

## Motivation

Nico's upstream working-message API lets active work replace Pi's generic indicator text.

## Applicability

- Use this when streaming work needs a short progress description.

## Structure

```text
active work -> message override
end -> default message
```

## Participants

- [`ctx.ui.setWorkingMessage`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/extensions.md#interact-with-the-user): Overrides or restores the active working label.
- `Active operation`: Provides the task-specific caption and restores its default.

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

## Consequences

- The active indicator can describe the current task.
- RPC does not render this override.

## Implementation

- This method is a no-op in RPC mode.
- Restore the default message when the override ends.

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

### Default indicator

`default`: Show Pi's normal working label.

Checks at every width and theme:

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

Frames:

- 40 columns: ![Work Caption, Default indicator, 40 columns, dark theme](https://pi-tui.ratstack.sh/frames/work-caption/default-40-dark.eb79fac448a8.webp) ![Work Caption, Default indicator, 40 columns, light theme](https://pi-tui.ratstack.sh/frames/work-caption/default-40-light.48820239922e.webp)
- 60 columns: ![Work Caption, Default indicator, 60 columns, dark theme](https://pi-tui.ratstack.sh/frames/work-caption/default-60-dark.af885028733c.webp) ![Work Caption, Default indicator, 60 columns, light theme](https://pi-tui.ratstack.sh/frames/work-caption/default-60-light.9d20a2ef3b00.webp)
- 80 columns: ![Work Caption, Default indicator, 80 columns, dark theme](https://pi-tui.ratstack.sh/frames/work-caption/default-80-dark.2167af969a39.webp) ![Work Caption, Default indicator, 80 columns, light theme](https://pi-tui.ratstack.sh/frames/work-caption/default-80-light.79ba5d2764bb.webp)
- 120 columns: ![Work Caption, Default indicator, 120 columns, dark theme](https://pi-tui.ratstack.sh/frames/work-caption/default-120-dark.2064688b1c1c.webp) ![Work Caption, Default indicator, 120 columns, light theme](https://pi-tui.ratstack.sh/frames/work-caption/default-120-light.a605dc2cbbc6.webp)

### Task label

`custom`: Show a synthetic task-specific working message.

Checks at every width and theme:

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

Frames:

- 40 columns: ![Work Caption, Task label, 40 columns, dark theme](https://pi-tui.ratstack.sh/frames/work-caption/custom-40-dark.d10d37fedb9f.webp) [animated](https://pi-tui.ratstack.sh/frames/work-caption/custom-40-dark.anim.bcca45b024d0.webp) ![Work Caption, Task label, 40 columns, light theme](https://pi-tui.ratstack.sh/frames/work-caption/custom-40-light.9caa2ca76543.webp) [animated](https://pi-tui.ratstack.sh/frames/work-caption/custom-40-light.anim.c6522e73aab7.webp)
- 60 columns: ![Work Caption, Task label, 60 columns, dark theme](https://pi-tui.ratstack.sh/frames/work-caption/custom-60-dark.75a8c8d26036.webp) [animated](https://pi-tui.ratstack.sh/frames/work-caption/custom-60-dark.anim.bdaee7759bed.webp) ![Work Caption, Task label, 60 columns, light theme](https://pi-tui.ratstack.sh/frames/work-caption/custom-60-light.399debcb8718.webp) [animated](https://pi-tui.ratstack.sh/frames/work-caption/custom-60-light.anim.21349840cd9b.webp)
- 80 columns: ![Work Caption, Task label, 80 columns, dark theme](https://pi-tui.ratstack.sh/frames/work-caption/custom-80-dark.d8271f389840.webp) [animated](https://pi-tui.ratstack.sh/frames/work-caption/custom-80-dark.anim.209ee2e56f77.webp) ![Work Caption, Task label, 80 columns, light theme](https://pi-tui.ratstack.sh/frames/work-caption/custom-80-light.805c9260df66.webp) [animated](https://pi-tui.ratstack.sh/frames/work-caption/custom-80-light.anim.6cf2452fce73.webp)
- 120 columns: ![Work Caption, Task label, 120 columns, dark theme](https://pi-tui.ratstack.sh/frames/work-caption/custom-120-dark.68e3786dc533.webp) [animated](https://pi-tui.ratstack.sh/frames/work-caption/custom-120-dark.anim.a53205aaf032.webp) ![Work Caption, Task label, 120 columns, light theme](https://pi-tui.ratstack.sh/frames/work-caption/custom-120-light.94c6a643ea5a.webp) [animated](https://pi-tui.ratstack.sh/frames/work-caption/custom-120-light.anim.06f017d4ed0a.webp)

### Default restored

`restored`: Clear the override and show the normal label again.

Checks at every width and theme:

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

Frames:

- 40 columns: ![Work Caption, Default restored, 40 columns, dark theme](https://pi-tui.ratstack.sh/frames/work-caption/restored-40-dark.eb79fac448a8.webp) ![Work Caption, Default restored, 40 columns, light theme](https://pi-tui.ratstack.sh/frames/work-caption/restored-40-light.48820239922e.webp)
- 60 columns: ![Work Caption, Default restored, 60 columns, dark theme](https://pi-tui.ratstack.sh/frames/work-caption/restored-60-dark.af885028733c.webp) ![Work Caption, Default restored, 60 columns, light theme](https://pi-tui.ratstack.sh/frames/work-caption/restored-60-light.9d20a2ef3b00.webp)
- 80 columns: ![Work Caption, Default restored, 80 columns, dark theme](https://pi-tui.ratstack.sh/frames/work-caption/restored-80-dark.2167af969a39.webp) ![Work Caption, Default restored, 80 columns, light theme](https://pi-tui.ratstack.sh/frames/work-caption/restored-80-light.79ba5d2764bb.webp)
- 120 columns: ![Work Caption, Default restored, 120 columns, dark theme](https://pi-tui.ratstack.sh/frames/work-caption/restored-120-dark.2064688b1c1c.webp) ![Work Caption, Default restored, 120 columns, light theme](https://pi-tui.ratstack.sh/frames/work-caption/restored-120-light.a605dc2cbbc6.webp)

## Sample code

`stories/patterns/presentation/work-caption.ts`, the story the frames above were rendered from.

```ts
// Work Caption: give active work a task-specific label and restore Pi's default.
import type { PatternStory } from "../../../src/pattern.ts";

export const story: PatternStory = {
  id: "work-caption", title: "Work Caption", kind: "screen",
  apis: ["ctx.ui.setWorkingMessage", "ctx.ui.setWidget", "ctx.ui.getEditorText"],
  states: [
    { id: "default", label: "Default indicator" },
    { id: "custom", label: "Task label", animated: true, steps: [
      { type: "action", name: "custom" }, { type: "tick", ms: 600 },
    ] },
    { id: "restored", label: "Default restored", steps: [{ type: "action", name: "restore" }] },
  ],
  setup({ ui, theme, action }) {
    const timers: ReturnType<typeof setTimeout>[] = [];
    ui.setEditorText("Review the task summaries");
    ui.setWidget("task", [
      theme.fg("accent", "Active task · prepare previews"),
      theme.fg("muted", "The draft remains editable."),
    ]);
    ui.setWorkingMessage(); // The harness projects Pi's default working label.
    action("custom", () => {
      ui.setWorkingMessage("Reading task summaries…");
      timers.push(setTimeout(() => ui.setWorkingMessage("Checking preview widths…"), 200));
      timers.push(setTimeout(() => ui.setWorkingMessage("Packing preview frames…"), 400));
    });
    action("restore", () => {
      timers.forEach(clearTimeout);
      ui.setWorkingMessage();
      if (ui.getEditorText() !== "Review the task summaries") throw new Error("Caption changed the draft");
    });
    return () => { timers.forEach(clearTimeout); ui.setWorkingMessage(); ui.setWidget("task", undefined); };
  },
};
```

## Known uses: seen in Nico's repos

- [**earendil-works/pi**](https://github.com/earendil-works/pi): Let extensions set the active working message
  - [packages/coding-agent/src/core/extensions/types.ts:75-88](https://github.com/earendil-works/pi/blob/271b49da3c959863afc08ae669f53b55fb424b7f/packages/coding-agent/src/core/extensions/types.ts#L75-L88) @271b49da
- [**dot314**](https://github.com/nicobailon/dot314): Let extensions set the active working message
- [**pi-discord**](https://github.com/nicobailon/pi-discord): Let extensions set the active working message
- [**pi-powerline-footer**](https://github.com/nicobailon/pi-powerline-footer): Let extensions set the active working message
- [**pi-prompt-template-model**](https://github.com/nicobailon/pi-prompt-template-model): Let extensions set the active working message
- [**pi-prune**](https://github.com/nicobailon/pi-prune): Let extensions set the active working message

## For agents: choose and check

Choose Work Caption 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.
- [Notice Fuse](https://pi-tui.ratstack.sh/patterns/notice-fuse.md): Clear a one-off keyed notice after its display interval.

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

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

- [Notice Fuse](https://pi-tui.ratstack.sh/patterns/notice-fuse.md): shows an independent temporary notice

## Linked from

No other pattern links here.
