# Widget Dock

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

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

Also known as Keyed widget.

## Intent

Mount and replace auxiliary content under a stable widget key.

## Motivation

Web Access and Powerline Footer install auxiliary content that must disappear when disabled.

## Applicability

- Use this when persistent content belongs above or below the editor.

## Structure

```text
owner -> widget key -> content
update -> replace content
end -> clear key
```

## Participants

- [`ctx.ui.setWidget`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/extensions.md#interact-with-the-user): Mounts, replaces or clears one near-editor widget.
- [`truncateToWidth`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/tui.md#understand-the-component-model): Clips text to its allotted columns and can pad the result.
- `Widget owner`: Keeps the keyed content and component reference in sync.

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

## Consequences

- Content can be replaced under one stable near-editor key.
- The key and local component reference must be cleared together.

## Implementation

- Clear the widget and its local component reference together.
- Fit rendered rows to current terminal dimensions.

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

### Above editor

`above`: Mount a compact synthetic progress widget above the editor.

Checks at every width and theme:

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

Frames:

- 40 columns: ![Widget Dock, Above editor, 40 columns, dark theme](https://pi-tui.ratstack.sh/frames/widget-dock/above-40-dark.f451a630cdff.webp) ![Widget Dock, Above editor, 40 columns, light theme](https://pi-tui.ratstack.sh/frames/widget-dock/above-40-light.50addd361c78.webp)
- 60 columns: ![Widget Dock, Above editor, 60 columns, dark theme](https://pi-tui.ratstack.sh/frames/widget-dock/above-60-dark.5b3b929ee017.webp) ![Widget Dock, Above editor, 60 columns, light theme](https://pi-tui.ratstack.sh/frames/widget-dock/above-60-light.22684e147d41.webp)
- 80 columns: ![Widget Dock, Above editor, 80 columns, dark theme](https://pi-tui.ratstack.sh/frames/widget-dock/above-80-dark.689bb9c02990.webp) ![Widget Dock, Above editor, 80 columns, light theme](https://pi-tui.ratstack.sh/frames/widget-dock/above-80-light.6a7795bd566b.webp)
- 120 columns: ![Widget Dock, Above editor, 120 columns, dark theme](https://pi-tui.ratstack.sh/frames/widget-dock/above-120-dark.49d4ddd25e90.webp) ![Widget Dock, Above editor, 120 columns, light theme](https://pi-tui.ratstack.sh/frames/widget-dock/above-120-light.54f153fa0fe3.webp)

### Below editor

`below`: Show the same auxiliary content below the editor.

Checks at every width and theme:

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

Frames:

- 40 columns: ![Widget Dock, Below editor, 40 columns, dark theme](https://pi-tui.ratstack.sh/frames/widget-dock/below-40-dark.54fb0b099212.webp) ![Widget Dock, Below editor, 40 columns, light theme](https://pi-tui.ratstack.sh/frames/widget-dock/below-40-light.6aac73120cb1.webp)
- 60 columns: ![Widget Dock, Below editor, 60 columns, dark theme](https://pi-tui.ratstack.sh/frames/widget-dock/below-60-dark.f11785dffe83.webp) ![Widget Dock, Below editor, 60 columns, light theme](https://pi-tui.ratstack.sh/frames/widget-dock/below-60-light.c81cae5fa347.webp)
- 80 columns: ![Widget Dock, Below editor, 80 columns, dark theme](https://pi-tui.ratstack.sh/frames/widget-dock/below-80-dark.0c76300c2cc9.webp) ![Widget Dock, Below editor, 80 columns, light theme](https://pi-tui.ratstack.sh/frames/widget-dock/below-80-light.cc27a5904b66.webp)
- 120 columns: ![Widget Dock, Below editor, 120 columns, dark theme](https://pi-tui.ratstack.sh/frames/widget-dock/below-120-dark.78e05ea089a8.webp) ![Widget Dock, Below editor, 120 columns, light theme](https://pi-tui.ratstack.sh/frames/widget-dock/below-120-light.5335148c065d.webp)

### Removed

`removed`: Clear its owned key without changing another widget.

Checks at every width and theme:

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

Frames:

- 40 columns: ![Widget Dock, Removed, 40 columns, dark theme](https://pi-tui.ratstack.sh/frames/widget-dock/removed-40-dark.c7314dd47abf.webp) ![Widget Dock, Removed, 40 columns, light theme](https://pi-tui.ratstack.sh/frames/widget-dock/removed-40-light.08857abb1e04.webp)
- 60 columns: ![Widget Dock, Removed, 60 columns, dark theme](https://pi-tui.ratstack.sh/frames/widget-dock/removed-60-dark.454ec9da805f.webp) ![Widget Dock, Removed, 60 columns, light theme](https://pi-tui.ratstack.sh/frames/widget-dock/removed-60-light.721c3b912e68.webp)
- 80 columns: ![Widget Dock, Removed, 80 columns, dark theme](https://pi-tui.ratstack.sh/frames/widget-dock/removed-80-dark.ceea56c5a9a2.webp) ![Widget Dock, Removed, 80 columns, light theme](https://pi-tui.ratstack.sh/frames/widget-dock/removed-80-light.6ad2d832553f.webp)
- 120 columns: ![Widget Dock, Removed, 120 columns, dark theme](https://pi-tui.ratstack.sh/frames/widget-dock/removed-120-dark.04984a0c6de8.webp) ![Widget Dock, Removed, 120 columns, light theme](https://pi-tui.ratstack.sh/frames/widget-dock/removed-120-light.f053df9c91d7.webp)

## Sample code

`stories/patterns/structural/widget-dock.ts`, the story the frames above were rendered from.

```ts
// Widget Dock: replace keyed auxiliary content and clear its local reference together.
import { Text, truncateToWidth, type Component } from "@earendil-works/pi-tui";
import type { PatternStory } from "../../../src/pattern.ts";

export const story: PatternStory = {
  id: "widget-dock", title: "Widget Dock", kind: "screen",
  apis: ["ctx.ui.setWidget", "Text", "truncateToWidth"],
  states: [
    { id: "above", label: "Above editor" },
    { id: "below", label: "Below editor", steps: [{ type: "action", name: "move-below" }] },
    { id: "removed", label: "Removed", steps: [{ type: "action", name: "remove" }] },
  ],
  setup({ ui, theme, action }) {
    const widgetKey = "preview";
    let mounted: Component | undefined;
    const install = (placement: "aboveEditor" | "belowEditor") => {
      ui.setWidget(widgetKey, (_tui, widgetTheme) => {
        const text = new Text([
          widgetTheme.fg("accent", "Task preview · 2/3"),
          widgetTheme.fg("success", "✓ Measure columns · ✓ Dark frame"),
          widgetTheme.fg("warning", "> Light frame queued"),
        ].join("\n"), 0, 0);
        mounted = {
          invalidate() { text.invalidate(); },
          render(width) {
            return text.render(width).slice(0, 3).map(line => truncateToWidth(line, width));
          },
        };
        return mounted;
      }, { placement });
    };
    const remove = () => {
      ui.setWidget(widgetKey, undefined);
      mounted = undefined; // Never retain a component after its key is removed.
    };

    ui.setEditorText("Review src/preview.ts");
    ui.setWidget("draft-hint", [theme.fg("muted", "Review the draft before submitting.")]);
    install("aboveEditor");
    action("move-below", () => {
      mounted?.invalidate();
      install("belowEditor"); // Replacement uses the same owned key.
    });
    action("remove", remove);
    return () => { remove(); ui.setWidget("draft-hint", undefined); };
  },
};
```

## Known uses: seen in Nico's repos

- [**dot314**](https://github.com/nicobailon/dot314): Mount, update and remove keyed widgets
  - [extensions/command-center/index.ts:410-458](https://github.com/nicobailon/dot314/blob/17cce138f3f687211d0485d204408a9650953c61/extensions/command-center/index.ts#L410-L458) @17cce138
- [**pi-powerline-footer**](https://github.com/nicobailon/pi-powerline-footer): Mount, update and remove keyed widgets
  - [index.ts:3120-3185](https://github.com/nicobailon/pi-powerline-footer/blob/859dee671b633fb533b07ceba3e6c1ab1c43360a/index.ts#L3120-L3185) @859dee67
- [**pi-web-access**](https://github.com/nicobailon/pi-web-access): Mount, update and remove keyed widgets
  - [index.ts:641-641](https://github.com/nicobailon/pi-web-access/blob/9a0779976ba47350be18f8cfacaffbe2a407113e/index.ts#L641-L641) @9a077997
  - [index.ts:1278-1304](https://github.com/nicobailon/pi-web-access/blob/9a0779976ba47350be18f8cfacaffbe2a407113e/index.ts#L1278-L1304) @9a077997
- [**pi-interactive-shell**](https://github.com/nicobailon/pi-interactive-shell): Show bounded background-session status below the editor
  - [background-widget.ts:24-106](https://github.com/nicobailon/pi-interactive-shell/blob/77df9a8142a2f731635a4c5a01d68feecb5cced4/background-widget.ts#L24-L106) @77df9a81

## For agents: choose and check

Choose Widget Dock 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.
- [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.
- [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.
- Read the Pi 1.0.3 docs for [`ctx.ui.setWidget`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/extensions.md#interact-with-the-user), [`truncateToWidth`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/tui.md#understand-the-component-model), `Text` before you use them.
- Every reference frame gets the verdict its state expects.

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

- [Result Relay](https://pi-tui.ratstack.sh/patterns/result-relay.md): replaces pending content with a result

## Linked from

- [Signal Pair](https://pi-tui.ratstack.sh/patterns/signal-pair.md): provides the detail surface
