# Hinge Panel

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

Also known as Responsive panel.

## Intent

Switch between side-by-side and stacked sections as available space changes.

## Motivation

The MCP adapter's panel needs list and detail content in terminals too narrow for side-by-side panes.

## Applicability

- Use this when a panel must remain readable in narrow or short terminals.

## Structure

```text
width + rows -> layout
  wide -> list | detail
narrow -> list / detail
```

## Participants

- [`Component`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/tui.md#understand-the-component-model): Renders width-bounded lines and invalidates cached output.
- [`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.
- [`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.
- `Layout decision`: Chooses side-by-side or stacked content from available space.

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

## Consequences

- The same panel remains usable across terminal sizes.
- Narrow layouts stack or omit visible detail.

## Implementation

- Use the supplied width rather than a fixed fallback.
- Reserve rows for framing and controls.

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

### Wide layout

`wide`: Show list and detail beside each other where their measured widths fit.

Checks at every width and theme:

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

Frames:

- 40 columns: ![Hinge Panel, Wide layout, 40 columns, dark theme](https://pi-tui.ratstack.sh/frames/hinge-panel/wide-40-dark.b68e044395bf.webp) ![Hinge Panel, Wide layout, 40 columns, light theme](https://pi-tui.ratstack.sh/frames/hinge-panel/wide-40-light.b782c003cb3c.webp)
- 60 columns: ![Hinge Panel, Wide layout, 60 columns, dark theme](https://pi-tui.ratstack.sh/frames/hinge-panel/wide-60-dark.d9f9c86fd909.webp) ![Hinge Panel, Wide layout, 60 columns, light theme](https://pi-tui.ratstack.sh/frames/hinge-panel/wide-60-light.27572fbb1cf0.webp)
- 80 columns: ![Hinge Panel, Wide layout, 80 columns, dark theme](https://pi-tui.ratstack.sh/frames/hinge-panel/wide-80-dark.61a1e7f1548d.webp) ![Hinge Panel, Wide layout, 80 columns, light theme](https://pi-tui.ratstack.sh/frames/hinge-panel/wide-80-light.5818c5e9c4c4.webp)
- 120 columns: ![Hinge Panel, Wide layout, 120 columns, dark theme](https://pi-tui.ratstack.sh/frames/hinge-panel/wide-120-dark.15ebc1cf68f2.webp) ![Hinge Panel, Wide layout, 120 columns, light theme](https://pi-tui.ratstack.sh/frames/hinge-panel/wide-120-light.98f790763dc6.webp)

### Narrow layout

`narrow`: Stack the same sections and cap body rows.

Checks at every width and theme:

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

Frames:

- 40 columns: ![Hinge Panel, Narrow layout, 40 columns, dark theme](https://pi-tui.ratstack.sh/frames/hinge-panel/narrow-40-dark.b68e044395bf.webp) ![Hinge Panel, Narrow layout, 40 columns, light theme](https://pi-tui.ratstack.sh/frames/hinge-panel/narrow-40-light.b782c003cb3c.webp)
- 60 columns: ![Hinge Panel, Narrow layout, 60 columns, dark theme](https://pi-tui.ratstack.sh/frames/hinge-panel/narrow-60-dark.8ceb6a16a4b1.webp) ![Hinge Panel, Narrow layout, 60 columns, light theme](https://pi-tui.ratstack.sh/frames/hinge-panel/narrow-60-light.0c5a2be49d0b.webp)
- 80 columns: ![Hinge Panel, Narrow layout, 80 columns, dark theme](https://pi-tui.ratstack.sh/frames/hinge-panel/narrow-80-dark.a7487c90fa03.webp) ![Hinge Panel, Narrow layout, 80 columns, light theme](https://pi-tui.ratstack.sh/frames/hinge-panel/narrow-80-light.613c49e5a51b.webp)
- 120 columns: ![Hinge Panel, Narrow layout, 120 columns, dark theme](https://pi-tui.ratstack.sh/frames/hinge-panel/narrow-120-dark.22247b0469f3.webp) ![Hinge Panel, Narrow layout, 120 columns, light theme](https://pi-tui.ratstack.sh/frames/hinge-panel/narrow-120-light.a2feff72a359.webp)

### Don't: fixed width

`dont-fixed-width`: Return a fixed 74-column frame at narrower widths.

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 2, column 41: frame 0: visibleWidth=124, limit=40
- width at 40 dark: line 3, column 41: frame 0: visibleWidth=124, limit=40
- width at 40 dark: line 4, column 41: frame 0: visibleWidth=124, limit=40
- width at 40 dark: line 5, column 41: frame 0: visibleWidth=124, limit=40
- width at 40 dark: line 6, column 41: frame 0: visibleWidth=124, limit=40
- width at 40 dark: line 7, column 41: frame 0: visibleWidth=124, limit=40
- width at 40 dark: line 3, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 40 dark: line 4, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 40 dark: line 5, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 40 dark: line 7, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 40 dark: line 8, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 40 dark: line 9, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 40 dark: line 11, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 40 dark: line 12, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 40 dark: line 13, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 40 dark: line 15, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 40 dark: line 16, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 40 dark: line 17, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 40 dark: line 19, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 40 dark: line 20, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 40 dark: line 21, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 40 dark: line 23, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 40 dark: line 24, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 40 dark: line 25, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 40 light: line 2, column 41: frame 0: visibleWidth=124, limit=40
- width at 40 light: line 3, column 41: frame 0: visibleWidth=124, limit=40
- width at 40 light: line 4, column 41: frame 0: visibleWidth=124, limit=40
- width at 40 light: line 5, column 41: frame 0: visibleWidth=124, limit=40
- width at 40 light: line 6, column 41: frame 0: visibleWidth=124, limit=40
- width at 40 light: line 7, column 41: frame 0: visibleWidth=124, limit=40
- width at 40 light: line 3, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 40 light: line 4, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 40 light: line 5, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 40 light: line 7, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 40 light: line 8, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 40 light: line 9, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 40 light: line 11, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 40 light: line 12, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 40 light: line 13, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 40 light: line 15, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 40 light: line 16, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 40 light: line 17, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 40 light: line 19, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 40 light: line 20, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 40 light: line 21, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 40 light: line 23, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 40 light: line 24, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 40 light: line 25, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 60 dark: line 2, column 61: frame 0: visibleWidth=124, limit=60
- width at 60 dark: line 3, column 61: frame 0: visibleWidth=124, limit=60
- width at 60 dark: line 4, column 61: frame 0: visibleWidth=124, limit=60
- width at 60 dark: line 5, column 61: frame 0: visibleWidth=124, limit=60
- width at 60 dark: line 6, column 61: frame 0: visibleWidth=124, limit=60
- width at 60 dark: line 7, column 61: frame 0: visibleWidth=124, limit=60
- width at 60 dark: line 3, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 60 dark: line 4, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 60 dark: line 6, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 60 dark: line 7, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 60 dark: line 9, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 60 dark: line 10, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 60 dark: line 12, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 60 dark: line 13, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 60 dark: line 15, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 60 dark: line 16, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 60 dark: line 18, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 60 dark: line 19, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 60 light: line 2, column 61: frame 0: visibleWidth=124, limit=60
- width at 60 light: line 3, column 61: frame 0: visibleWidth=124, limit=60
- width at 60 light: line 4, column 61: frame 0: visibleWidth=124, limit=60
- width at 60 light: line 5, column 61: frame 0: visibleWidth=124, limit=60
- width at 60 light: line 6, column 61: frame 0: visibleWidth=124, limit=60
- width at 60 light: line 7, column 61: frame 0: visibleWidth=124, limit=60
- width at 60 light: line 3, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 60 light: line 4, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 60 light: line 6, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 60 light: line 7, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 60 light: line 9, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 60 light: line 10, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 60 light: line 12, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 60 light: line 13, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 60 light: line 15, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 60 light: line 16, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 60 light: line 18, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 60 light: line 19, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 80 dark: line 2, column 81: frame 0: visibleWidth=124, limit=80
- width at 80 dark: line 3, column 81: frame 0: visibleWidth=124, limit=80
- width at 80 dark: line 4, column 81: frame 0: visibleWidth=124, limit=80
- width at 80 dark: line 5, column 81: frame 0: visibleWidth=124, limit=80
- width at 80 dark: line 6, column 81: frame 0: visibleWidth=124, limit=80
- width at 80 dark: line 7, column 81: frame 0: visibleWidth=124, limit=80
- width at 80 dark: line 3, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 80 dark: line 5, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 80 dark: line 7, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 80 dark: line 9, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 80 dark: line 11, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 80 dark: line 13, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 80 light: line 2, column 81: frame 0: visibleWidth=124, limit=80
- width at 80 light: line 3, column 81: frame 0: visibleWidth=124, limit=80
- width at 80 light: line 4, column 81: frame 0: visibleWidth=124, limit=80
- width at 80 light: line 5, column 81: frame 0: visibleWidth=124, limit=80
- width at 80 light: line 6, column 81: frame 0: visibleWidth=124, limit=80
- width at 80 light: line 7, column 81: frame 0: visibleWidth=124, limit=80
- width at 80 light: line 3, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 80 light: line 5, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 80 light: line 7, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 80 light: line 9, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 80 light: line 11, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 80 light: line 13, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 120 dark: line 2, column 121: frame 0: visibleWidth=124, limit=120
- width at 120 dark: line 3, column 121: frame 0: visibleWidth=124, limit=120
- width at 120 dark: line 4, column 121: frame 0: visibleWidth=124, limit=120
- width at 120 dark: line 5, column 121: frame 0: visibleWidth=124, limit=120
- width at 120 dark: line 6, column 121: frame 0: visibleWidth=124, limit=120
- width at 120 dark: line 7, column 121: frame 0: visibleWidth=124, limit=120
- width at 120 dark: line 3, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 120 dark: line 5, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 120 dark: line 7, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 120 dark: line 9, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 120 dark: line 11, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 120 dark: line 13, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 120 light: line 2, column 121: frame 0: visibleWidth=124, limit=120
- width at 120 light: line 3, column 121: frame 0: visibleWidth=124, limit=120
- width at 120 light: line 4, column 121: frame 0: visibleWidth=124, limit=120
- width at 120 light: line 5, column 121: frame 0: visibleWidth=124, limit=120
- width at 120 light: line 6, column 121: frame 0: visibleWidth=124, limit=120
- width at 120 light: line 7, column 121: frame 0: visibleWidth=124, limit=120
- width at 120 light: line 3, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 120 light: line 5, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 120 light: line 7, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 120 light: line 9, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 120 light: line 11, column 1: frame 0: headless terminal row is wrapped (physical row)
- width at 120 light: line 13, column 1: frame 0: headless terminal row is wrapped (physical row)

Frames:

- 40 columns: ![Hinge Panel, Don't: fixed width, 40 columns, dark theme](https://pi-tui.ratstack.sh/frames/hinge-panel/dont-fixed-width-40-dark.94674e53a2a0.webp) ![Hinge Panel, Don't: fixed width, 40 columns, light theme](https://pi-tui.ratstack.sh/frames/hinge-panel/dont-fixed-width-40-light.4e99282ccbf3.webp)
- 60 columns: ![Hinge Panel, Don't: fixed width, 60 columns, dark theme](https://pi-tui.ratstack.sh/frames/hinge-panel/dont-fixed-width-60-dark.f00b3371bb4d.webp) ![Hinge Panel, Don't: fixed width, 60 columns, light theme](https://pi-tui.ratstack.sh/frames/hinge-panel/dont-fixed-width-60-light.27c304a1c096.webp)
- 80 columns: ![Hinge Panel, Don't: fixed width, 80 columns, dark theme](https://pi-tui.ratstack.sh/frames/hinge-panel/dont-fixed-width-80-dark.a03a2cef4c8e.webp) ![Hinge Panel, Don't: fixed width, 80 columns, light theme](https://pi-tui.ratstack.sh/frames/hinge-panel/dont-fixed-width-80-light.d8224fa1867f.webp)
- 120 columns: ![Hinge Panel, Don't: fixed width, 120 columns, dark theme](https://pi-tui.ratstack.sh/frames/hinge-panel/dont-fixed-width-120-dark.da9a91658e03.webp) ![Hinge Panel, Don't: fixed width, 120 columns, light theme](https://pi-tui.ratstack.sh/frames/hinge-panel/dont-fixed-width-120-light.f2ab5e0f4ab4.webp)

## Sample code

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

```ts
// Hinge Panel: switch from paired sections to a stack when their budgets do not fit.
import { truncateToWidth, visibleWidth, type Component } from "@earendil-works/pi-tui";
import type { PatternStory } from "../../../src/pattern.ts";

export const story: PatternStory = {
  id: "hinge-panel", title: "Hinge Panel", kind: "component",
  apis: ["Component", "truncateToWidth", "visibleWidth"], rows: 14,
  states: [
    { id: "wide", label: "Wide layout where space permits" },
    { id: "narrow", label: "Stacked sections", steps: [{ type: "action", name: "stack" }] },
    { id: "dont-fixed-width", label: "Don't: fixed 124-column frame",
      steps: [{ type: "action", name: "fixed" }], expectFail: ["width"] },
  ],
  setup({ theme, action, tui }) {
    let mode: "responsive" | "stacked" | "fixed" = "responsive";
    action("stack", () => { mode = "stacked"; tui.requestRender(); });
    action("fixed", () => { mode = "fixed"; tui.requestRender(); });
    const list = ["TASKS", "> Render preview", "  Check width", "  Publish sample"];
    const detail = ["RENDER PREVIEW", "State: ready", "File: src/preview.ts", "Next: inspect both themes"];
    const section = (lines: string[], width: number) => lines.map((line, i) =>
      theme.fg(i === 0 ? "accent" : "text", truncateToWidth(line, width, "…", true)));
    const component: Component = {
      invalidate() {},
      render(width) {
        const layoutWidth = mode === "fixed" ? 124 : width;
        const gap = theme.fg("border", " │ ");
        const listWidth = Math.max(24, ...list.map(visibleWidth));
        const detailMinimum = Math.max(...detail.map(visibleWidth));
        const paired = mode !== "stacked" && listWidth + visibleWidth(gap) + detailMinimum <= layoutWidth;
        const body = paired
          ? section(list, listWidth).map((line, i) => line + gap +
              section(detail, layoutWidth - listWidth - visibleWidth(gap))[i]!)
          : [...section(list, layoutWidth), "", ...section(detail, layoutWidth)];
        // The composer, not each section, owns the physical-row budget.
        const bodyRows = tui.terminal.rows - 4;
        return [
          theme.fg("accent", truncateToWidth("Hinge Panel · " + (paired ? "paired" : "stacked"), width)),
          theme.fg("border", "─".repeat(layoutWidth)),
          ...body.slice(0, bodyRows),
          theme.fg("border", "─".repeat(layoutWidth)),
          theme.fg("muted", truncateToWidth(
            mode === "fixed" ? "Don't: layout ignores render(width)" : "Same tasks · one width and row budget", width)),
        ];
      },
    };
    return component;
  },
};
```

## Known uses: seen in Nico's repos

- [**pi-mcp-adapter**](https://github.com/nicobailon/pi-mcp-adapter): Responsive list/detail TUI panels
  - [mcp-setup-panel.ts:12-37](https://github.com/nicobailon/pi-mcp-adapter/blob/85db03d87cd0f7461b55eab8d25c10bce473b801/mcp-setup-panel.ts#L12-L37) @85db03d8
  - [mcp-setup-panel.ts:45-84](https://github.com/nicobailon/pi-mcp-adapter/blob/85db03d87cd0f7461b55eab8d25c10bce473b801/mcp-setup-panel.ts#L45-L84) @85db03d8
- [**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 Hinge Panel 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.
- [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: fixed width" 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 [`Component`](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), [`visibleWidth`](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: "hinge-panel" })` lists what to read next, and `states({ id: "hinge-panel", 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

- [Column Gauge](https://pi-tui.ratstack.sh/patterns/column-gauge.md): fits each composed row

## Linked from

- [Column Gauge](https://pi-tui.ratstack.sh/patterns/column-gauge.md): supplies fitted rows
- [Section Loom](https://pi-tui.ratstack.sh/patterns/section-loom.md): chooses the arrangement
- [Elastic Overlay](https://pi-tui.ratstack.sh/patterns/elastic-overlay.md): adapts content within those bounds
- [Hinge Diff](https://pi-tui.ratstack.sh/patterns/hinge-diff.md): uses the same width-driven rearrangement
