# Status Ribbon

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

Also known as Footer segments.

## Intent

Compose a footer from independently visible and measured status segments.

## Motivation

Powerline Footer must order optional segments and move secondary content when the footer runs out of columns.

## Applicability

- Use this when several optional signals compete for one footer line.

## Structure

```text
footer data -> segments
segments -> measure -> footer
```

## Participants

- [`ctx.ui.setFooter`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/extensions.md#interact-with-the-user): Installs a footer factory with read-only footer data.
- [`ReadonlyFooterDataProvider`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/tui.md#choose-an-integration-point): Provides branch, status and provider data without mutation.
- [`visibleWidth`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/tui.md#understand-the-component-model): Measures rendered terminal columns rather than string length.
- [`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.
- `Segment list`: Keeps visibility, ordering and measured output separate.

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

## Consequences

- Independent segments support responsive visibility.
- Hidden segments and spacing need explicit width accounting.

## Implementation

- Hidden segments must not consume spacing.
- The read-only footer provider has no session-ID getter.

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

### All segments

`default`: Show branch, model and two keyed statuses in a synthetic footer.

Checks at every width and theme:

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

Frames:

- 40 columns: ![Status Ribbon, All segments, 40 columns, dark theme](https://pi-tui.ratstack.sh/frames/status-ribbon/default-40-dark.c7d711cd43b3.webp) ![Status Ribbon, All segments, 40 columns, light theme](https://pi-tui.ratstack.sh/frames/status-ribbon/default-40-light.79bfdd5de044.webp)
- 60 columns: ![Status Ribbon, All segments, 60 columns, dark theme](https://pi-tui.ratstack.sh/frames/status-ribbon/default-60-dark.546dc43822a8.webp) ![Status Ribbon, All segments, 60 columns, light theme](https://pi-tui.ratstack.sh/frames/status-ribbon/default-60-light.3d5010dc8d0d.webp)
- 80 columns: ![Status Ribbon, All segments, 80 columns, dark theme](https://pi-tui.ratstack.sh/frames/status-ribbon/default-80-dark.38544c181497.webp) ![Status Ribbon, All segments, 80 columns, light theme](https://pi-tui.ratstack.sh/frames/status-ribbon/default-80-light.ee826ec6c019.webp)
- 120 columns: ![Status Ribbon, All segments, 120 columns, dark theme](https://pi-tui.ratstack.sh/frames/status-ribbon/default-120-dark.106d4ae8984b.webp) ![Status Ribbon, All segments, 120 columns, light theme](https://pi-tui.ratstack.sh/frames/status-ribbon/default-120-light.6147d7ea4d89.webp)

### Secondary segments

`compact`: Move or omit secondary segments when the width cannot fit them.

Checks at every width and theme:

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

Frames:

- 40 columns: ![Status Ribbon, Secondary segments, 40 columns, dark theme](https://pi-tui.ratstack.sh/frames/status-ribbon/compact-40-dark.ded43728d068.webp) ![Status Ribbon, Secondary segments, 40 columns, light theme](https://pi-tui.ratstack.sh/frames/status-ribbon/compact-40-light.d56fe0a39900.webp)
- 60 columns: ![Status Ribbon, Secondary segments, 60 columns, dark theme](https://pi-tui.ratstack.sh/frames/status-ribbon/compact-60-dark.a54daafb9e32.webp) ![Status Ribbon, Secondary segments, 60 columns, light theme](https://pi-tui.ratstack.sh/frames/status-ribbon/compact-60-light.64512596d3cc.webp)
- 80 columns: ![Status Ribbon, Secondary segments, 80 columns, dark theme](https://pi-tui.ratstack.sh/frames/status-ribbon/compact-80-dark.a82dbec1ae84.webp) ![Status Ribbon, Secondary segments, 80 columns, light theme](https://pi-tui.ratstack.sh/frames/status-ribbon/compact-80-light.3539c422376a.webp)
- 120 columns: ![Status Ribbon, Secondary segments, 120 columns, dark theme](https://pi-tui.ratstack.sh/frames/status-ribbon/compact-120-dark.ae270add3f15.webp) ![Status Ribbon, Secondary segments, 120 columns, light theme](https://pi-tui.ratstack.sh/frames/status-ribbon/compact-120-light.7b54b6ee8f49.webp)

### Hidden segment

`empty`: Hide an empty segment without leaving a gap.

Checks at every width and theme:

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

Frames:

- 40 columns: ![Status Ribbon, Hidden segment, 40 columns, dark theme](https://pi-tui.ratstack.sh/frames/status-ribbon/empty-40-dark.5077e07bf2c4.webp) ![Status Ribbon, Hidden segment, 40 columns, light theme](https://pi-tui.ratstack.sh/frames/status-ribbon/empty-40-light.44253ac270d2.webp)
- 60 columns: ![Status Ribbon, Hidden segment, 60 columns, dark theme](https://pi-tui.ratstack.sh/frames/status-ribbon/empty-60-dark.5bc19c0ddab2.webp) ![Status Ribbon, Hidden segment, 60 columns, light theme](https://pi-tui.ratstack.sh/frames/status-ribbon/empty-60-light.27336f9e42d9.webp)
- 80 columns: ![Status Ribbon, Hidden segment, 80 columns, dark theme](https://pi-tui.ratstack.sh/frames/status-ribbon/empty-80-dark.af94c058bb3b.webp) ![Status Ribbon, Hidden segment, 80 columns, light theme](https://pi-tui.ratstack.sh/frames/status-ribbon/empty-80-light.67478dfd07fb.webp)
- 120 columns: ![Status Ribbon, Hidden segment, 120 columns, dark theme](https://pi-tui.ratstack.sh/frames/status-ribbon/empty-120-dark.1a142e3ce5c9.webp) ![Status Ribbon, Hidden segment, 120 columns, light theme](https://pi-tui.ratstack.sh/frames/status-ribbon/empty-120-light.548044da014b.webp)

## Sample code

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

```ts
// Status Ribbon: measure independent visible footer segments before composing rows.
import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
import type { PatternStory } from "../../../src/pattern.ts";

export const story: PatternStory = {
  id: "status-ribbon", title: "Status Ribbon", kind: "screen",
  apis: ["ctx.ui.setFooter", "ReadonlyFooterDataProvider", "visibleWidth", "truncateToWidth"],
  states: [
    { id: "default", label: "All segments" },
    { id: "compact", label: "Secondary segments", steps: [{ type: "action", name: "compact" }] },
    { id: "empty", label: "Hidden segment", steps: [{ type: "action", name: "hide-preview" }] },
  ],
  setup({ ui, theme, action, tui }) {
    let compact = false;
    ui.setEditorText("Review src/preview.ts");
    ui.setStatus("checks", theme.fg("warning", "checks 2/3"));
    ui.setStatus("preview", theme.fg("success", "preview ready"));
    ui.setFooter((footerTui, footerTheme, footerData) => {
      const unsubscribe = footerData.onBranchChange(() => footerTui.requestRender());
      return {
        dispose: unsubscribe,
        invalidate() {},
        render(width) {
          const segments = [
            footerTheme.fg("accent", "branch " + (footerData.getGitBranch() ?? "none")),
            compact ? "" : footerTheme.fg("muted", "model sample-small"),
            ...footerData.getExtensionStatuses().values(),
          ].filter(segment => visibleWidth(segment) > 0);
          const separator = footerTheme.fg("dim", " · ");
          const rows: string[] = [];
          let row = "";
          // Hidden segments are removed before separators or width allocation.
          for (const segment of segments) {
            const fitted = truncateToWidth(segment, width);
            const next = row ? row + separator + fitted : fitted;
            if (visibleWidth(next) > width && row) { rows.push(row); row = fitted; }
            else row = next;
          }
          if (row) rows.push(row);
          return rows;
        },
      };
    });
    action("compact", () => { compact = true; tui.requestRender(); });
    action("hide-preview", () => { ui.setStatus("preview", undefined); });
    return () => {
      ui.setStatus("checks", undefined); ui.setStatus("preview", undefined); ui.setFooter(undefined);
    };
  },
};
```

## Known uses: seen in Nico's repos

- [**pi-powerline-footer**](https://github.com/nicobailon/pi-powerline-footer): Compose a footer from independently rendered segments
  - [segments.ts:540-576](https://github.com/nicobailon/pi-powerline-footer/blob/859dee671b633fb533b07ceba3e6c1ab1c43360a/segments.ts#L540-L576) @859dee67
  - [index.ts:1085-1150](https://github.com/nicobailon/pi-powerline-footer/blob/859dee671b633fb533b07ceba3e6c1ab1c43360a/index.ts#L1085-L1150) @859dee67
- [**earendil-works/pi**](https://github.com/earendil-works/pi): Expose read-only footer data to custom footer components
  - [packages/coding-agent/src/core/footer-data-provider.ts:40-80](https://github.com/earendil-works/pi/blob/7b902612e96a8bf49cf6f34345f09a44e5ca6926/packages/coding-agent/src/core/footer-data-provider.ts#L40-L80) @7b902612
- [**pi-powerline-footer**](https://github.com/nicobailon/pi-powerline-footer): Expose read-only footer data to custom footer components

## For agents: choose and check

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

- [Column Gauge](https://pi-tui.ratstack.sh/patterns/column-gauge.md): Fit styled text to terminal columns before padding or framing it.
- [Hinge Panel](https://pi-tui.ratstack.sh/patterns/hinge-panel.md): Switch between side-by-side and stacked sections as available space changes.
- [Section Loom](https://pi-tui.ratstack.sh/patterns/section-loom.md): Compose a dashboard from focused render helpers under one layout budget.

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.setFooter`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/extensions.md#interact-with-the-user), [`ReadonlyFooterDataProvider`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/tui.md#choose-an-integration-point), [`visibleWidth`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/tui.md#understand-the-component-model), [`truncateToWidth`](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: "status-ribbon" })` lists what to read next, and `states({ id: "status-ribbon", 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

- [Keyed Slot](https://pi-tui.ratstack.sh/patterns/keyed-slot.md): supplies keyed footer signals

## Linked from

No other pattern links here.
