# Column Gauge

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

Also known as Display-column fitting.

## Intent

Fit styled text to terminal columns before padding or framing it.

## Motivation

Pi Intercom's session rows contain styled labels and paths that must fit fixed-width frames.

## Applicability

- Use this when rows contain ANSI styling, emoji or wide characters.

## Structure

```text
text -> measure -> fit -> pad
```

## Participants

- [`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.
- [`sliceByColumn`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/tui.md#understand-the-component-model): Slices text by terminal columns.
- [`wrapTextWithAnsi`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/tui.md#understand-the-component-model): Wraps text while preserving styling across lines.
- `Row budget`: Allocates columns before content is padded or framed.

Pi component APIs: [`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), [`sliceByColumn`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/tui.md#understand-the-component-model), [`wrapTextWithAnsi`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/tui.md#understand-the-component-model)

## Consequences

- Styled and wide text aligns in display columns.
- Truncation or wrapping changes how much text is visible.

## Implementation

- String length does not measure terminal columns.
- Padding alone does not clip an oversized label.

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

### Styled row

`default`: Fit a coloured row containing wide glyphs and a long synthetic path.

Checks at every width and theme:

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

Frames:

- 40 columns: ![Column Gauge, Styled row, 40 columns, dark theme](https://pi-tui.ratstack.sh/frames/column-gauge/default-40-dark.7b8a8189db2a.webp) ![Column Gauge, Styled row, 40 columns, light theme](https://pi-tui.ratstack.sh/frames/column-gauge/default-40-light.6d3dea1e998c.webp)
- 60 columns: ![Column Gauge, Styled row, 60 columns, dark theme](https://pi-tui.ratstack.sh/frames/column-gauge/default-60-dark.237e2aaa0fbf.webp) ![Column Gauge, Styled row, 60 columns, light theme](https://pi-tui.ratstack.sh/frames/column-gauge/default-60-light.26456b3fe97d.webp)
- 80 columns: ![Column Gauge, Styled row, 80 columns, dark theme](https://pi-tui.ratstack.sh/frames/column-gauge/default-80-dark.95955789bfaf.webp) ![Column Gauge, Styled row, 80 columns, light theme](https://pi-tui.ratstack.sh/frames/column-gauge/default-80-light.b04dce78566e.webp)
- 120 columns: ![Column Gauge, Styled row, 120 columns, dark theme](https://pi-tui.ratstack.sh/frames/column-gauge/default-120-dark.7695ca6f6aa8.webp) ![Column Gauge, Styled row, 120 columns, light theme](https://pi-tui.ratstack.sh/frames/column-gauge/default-120-light.a127cb2ea1e2.webp)

### Exact-width row

`padded`: Pad short text to the same width as a clipped row.

Checks at every width and theme:

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

Frames:

- 40 columns: ![Column Gauge, Exact-width row, 40 columns, dark theme](https://pi-tui.ratstack.sh/frames/column-gauge/padded-40-dark.0830dc8ea86c.webp) ![Column Gauge, Exact-width row, 40 columns, light theme](https://pi-tui.ratstack.sh/frames/column-gauge/padded-40-light.34971dbb4fd3.webp)
- 60 columns: ![Column Gauge, Exact-width row, 60 columns, dark theme](https://pi-tui.ratstack.sh/frames/column-gauge/padded-60-dark.e898fa43865a.webp) ![Column Gauge, Exact-width row, 60 columns, light theme](https://pi-tui.ratstack.sh/frames/column-gauge/padded-60-light.fb2840cb6253.webp)
- 80 columns: ![Column Gauge, Exact-width row, 80 columns, dark theme](https://pi-tui.ratstack.sh/frames/column-gauge/padded-80-dark.5fc515fbdfd9.webp) ![Column Gauge, Exact-width row, 80 columns, light theme](https://pi-tui.ratstack.sh/frames/column-gauge/padded-80-light.8bcee53834cd.webp)
- 120 columns: ![Column Gauge, Exact-width row, 120 columns, dark theme](https://pi-tui.ratstack.sh/frames/column-gauge/padded-120-dark.7e6cb49e15e6.webp) ![Column Gauge, Exact-width row, 120 columns, light theme](https://pi-tui.ratstack.sh/frames/column-gauge/padded-120-light.78d1d2820fe2.webp)

### Don't: count characters

`dont-string-length`: Fit wide glyphs with string slicing so the row exceeds its display-column budget.

This state is a counter-example. It fails width on purpose.

Checks at every width and theme:

- width: ✓ fails, as intended
- style-leak: ✓ pass
- hard-coded-colour: ✓ pass
- height: ✓ pass

Evidence from the checker:

- width at 40 dark: line 3, column 41: frame 0: visibleWidth=76, limit=40
- width at 40 dark: line 4, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 40 light: line 3, column 41: frame 0: visibleWidth=76, limit=40
- width at 40 light: line 4, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 60 dark: line 3, column 61: frame 0: visibleWidth=116, limit=60
- width at 60 dark: line 4, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 60 light: line 3, column 61: frame 0: visibleWidth=116, limit=60
- width at 60 light: line 4, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 80 dark: line 3, column 81: frame 0: visibleWidth=156, limit=80
- width at 80 dark: line 4, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 80 light: line 3, column 81: frame 0: visibleWidth=156, limit=80
- width at 80 light: line 4, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 120 dark: line 3, column 121: frame 0: visibleWidth=236, limit=120
- width at 120 dark: line 4, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 120 light: line 3, column 121: frame 0: visibleWidth=236, limit=120
- width at 120 light: line 4, column 1: frame 0: headless terminal row is wrapped (physical row)

Frames:

- 40 columns: ![Column Gauge, Don't: count characters, 40 columns, dark theme](https://pi-tui.ratstack.sh/frames/column-gauge/dont-string-length-40-dark.f9e3c8b2e3f8.webp) ![Column Gauge, Don't: count characters, 40 columns, light theme](https://pi-tui.ratstack.sh/frames/column-gauge/dont-string-length-40-light.d3049ed9bd8d.webp)
- 60 columns: ![Column Gauge, Don't: count characters, 60 columns, dark theme](https://pi-tui.ratstack.sh/frames/column-gauge/dont-string-length-60-dark.d7cde133c2b6.webp) ![Column Gauge, Don't: count characters, 60 columns, light theme](https://pi-tui.ratstack.sh/frames/column-gauge/dont-string-length-60-light.7859a037b487.webp)
- 80 columns: ![Column Gauge, Don't: count characters, 80 columns, dark theme](https://pi-tui.ratstack.sh/frames/column-gauge/dont-string-length-80-dark.97c4260d96c7.webp) ![Column Gauge, Don't: count characters, 80 columns, light theme](https://pi-tui.ratstack.sh/frames/column-gauge/dont-string-length-80-light.c8cbf92392e3.webp)
- 120 columns: ![Column Gauge, Don't: count characters, 120 columns, dark theme](https://pi-tui.ratstack.sh/frames/column-gauge/dont-string-length-120-dark.aa7976a53ba7.webp) ![Column Gauge, Don't: count characters, 120 columns, light theme](https://pi-tui.ratstack.sh/frames/column-gauge/dont-string-length-120-light.e95cd269b216.webp)

## Sample code

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

```ts
// Column Gauge: fit styled text to display columns before framing or padding.
import { sliceByColumn, truncateToWidth, visibleWidth, wrapTextWithAnsi, type Component } from "@earendil-works/pi-tui";
import type { PatternStory } from "../../../src/pattern.ts";

export const story: PatternStory = {
  id: "column-gauge", title: "Column Gauge", kind: "component",
  apis: ["visibleWidth", "truncateToWidth", "sliceByColumn", "wrapTextWithAnsi"],
  rows: 12,
  states: [
    { id: "default", label: "Styled row" },
    { id: "padded", label: "Exact-width row", steps: [{ type: "action", name: "pad" }] },
    { id: "dont-string-length", label: "Don't: count characters",
      steps: [{ type: "action", name: "count-characters" }], expectFail: ["width"] },
  ],
  setup({ theme, action, tui }) {
    let mode: "fit" | "pad" | "characters" = "fit";
    action("pad", () => { mode = "pad"; tui.requestRender(); });
    action("count-characters", () => { mode = "characters"; tui.requestRender(); });
    const path = "src/workflows/synthetic-session/界面/preview.ts";
    const component: Component = {
      invalidate() {},
      render(width) {
        const budget = width - 4; // Two borders and two spaces belong to the frame.
        const styled = theme.fg("accent", "界面 🐀 ") + theme.fg("text", path);
        const fit = (text: string) => truncateToWidth(text, budget, "…", true);
        // Deliberately wrong: one character can occupy two display columns.
        const row = mode === "characters"
          ? theme.fg("warning", "界".repeat(width).slice(0, budget))
          : mode === "pad" ? fit(theme.fg("success", "✓ Ready")) : fit(styled);
        const frame = [
          theme.fg("border", "┌" + "─".repeat(width - 2) + "┐"),
          theme.fg("border", "│ ") + row + theme.fg("border", " │"),
          theme.fg("border", "└" + "─".repeat(width - 2) + "┘"),
        ];
        return [
          theme.fg("accent", "Column Gauge · session preview"),
          ...frame,
          theme.fg("muted", truncateToWidth(
            "Measured: " + visibleWidth(row) + " / " + budget + " columns", width)),
          theme.fg("dim", "Slice by columns:"),
          theme.fg("text", sliceByColumn(styled, 0, Math.min(budget, 24), true)),
          ...wrapTextWithAnsi(theme.fg("muted",
            mode === "characters" ? "Character slicing lets wide glyphs escape the frame."
              : "Measure, clip, then pad. ANSI styles use no columns."), width)
            .map(line => theme.fg("muted", line)), // Close each independently composable line.
        ];
      },
    };
    return component;
  },
};
```

## Known uses: seen in Nico's repos

- [**pi-intercom**](https://github.com/nicobailon/pi-intercom): Window session rows and truncate paths by visible width
  - [ui/session-list.ts:6-29](https://github.com/nicobailon/pi-intercom/blob/a5fad4df2a9fe4909bf4d9b06263c8316976b57d/ui/session-list.ts#L6-L29) @a5fad4df
  - [ui/session-list.ts:112-179](https://github.com/nicobailon/pi-intercom/blob/a5fad4df2a9fe4909bf4d9b06263c8316976b57d/ui/session-list.ts#L112-L179) @a5fad4df
- [**earendil-works/pi**](https://github.com/earendil-works/pi): Optionally pad truncated text to exact terminal width
  - [packages/tui/src/utils.ts:1-65](https://github.com/earendil-works/pi/blob/d29f268f4662fc138cc87b23b7e6c45a4e0fe57b/packages/tui/src/utils.ts#L1-L65) @d29f268f
- [**pi-autoresearch**](https://github.com/nicobailon/pi-autoresearch): Render to the available width and bound vertical output
  - [extensions/pi-autoresearch/index.ts:645-676](https://github.com/nicobailon/pi-autoresearch/blob/22bd30b19482f2be5936031b2c6b149115779f16/extensions/pi-autoresearch/index.ts#L645-L676) @22bd30b1
- [**pi-memory-workbench**](https://github.com/nicobailon/pi-memory-workbench): Render to the available width and bound vertical output
  - [todo-widget.ts:5-8](https://github.com/nicobailon/pi-memory-workbench/blob/92b4c9c3ad07841418d77118bf8bd02ad204f7c4/todo-widget.ts#L5-L8) @92b4c9c3
- [**pi-powerline-footer**](https://github.com/nicobailon/pi-powerline-footer): Render to the available width and bound vertical output
  - [index.ts:1108-1150](https://github.com/nicobailon/pi-powerline-footer/blob/859dee671b633fb533b07ceba3e6c1ab1c43360a/index.ts#L1108-L1150) @859dee67

## For agents: choose and check

Choose Column Gauge 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.

- [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.
- [Status Ribbon](https://pi-tui.ratstack.sh/patterns/status-ribbon.md): Compose a footer from independently visible and measured status segments.

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.
- "Don't: count characters" is a counter-example. Its frames fail width on purpose. Your version should not look like it.
- Read the Pi 1.0.3 docs for [`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), [`sliceByColumn`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/tui.md#understand-the-component-model), [`wrapTextWithAnsi`](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: "column-gauge" })` lists what to read next, and `states({ id: "column-gauge", 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

- [Hinge Panel](https://pi-tui.ratstack.sh/patterns/hinge-panel.md): supplies fitted rows

## Linked from

- [Hinge Panel](https://pi-tui.ratstack.sh/patterns/hinge-panel.md): fits each composed row
