# Section Loom

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

Also known as Section composition.

## Intent

Compose a dashboard from focused render helpers under one layout budget.

## Motivation

Messenger's activity overlay combines status, workers, tasks, feed and detail from separate render helpers.

## Applicability

- Use this when several sections share formatting without sharing interaction state.

## Structure

```text
snapshot -> section helpers
helpers -> budget -> lines
```

## 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.
- `Section helpers`: Return focused sections to one budget-owning composer.

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)

## Consequences

- Sections share formatting without one large renderer.
- The composer still owns the combined row and column budget.

## Implementation

- Pure formatting does not mount or refresh a view.
- Budget width and height where sections are combined.

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

### Sections

`default`: Combine status, tasks and a feed with consistent labels.

Checks at every width and theme:

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

Frames:

- 40 columns: ![Section Loom, Sections, 40 columns, dark theme](https://pi-tui.ratstack.sh/frames/section-loom/default-40-dark.4cd603c148d8.webp) ![Section Loom, Sections, 40 columns, light theme](https://pi-tui.ratstack.sh/frames/section-loom/default-40-light.03bfff32abe4.webp)
- 60 columns: ![Section Loom, Sections, 60 columns, dark theme](https://pi-tui.ratstack.sh/frames/section-loom/default-60-dark.958cb6383f36.webp) ![Section Loom, Sections, 60 columns, light theme](https://pi-tui.ratstack.sh/frames/section-loom/default-60-light.fd461e3994e0.webp)
- 80 columns: ![Section Loom, Sections, 80 columns, dark theme](https://pi-tui.ratstack.sh/frames/section-loom/default-80-dark.9aa19b11d7a7.webp) ![Section Loom, Sections, 80 columns, light theme](https://pi-tui.ratstack.sh/frames/section-loom/default-80-light.1bbb704f7529.webp)
- 120 columns: ![Section Loom, Sections, 120 columns, dark theme](https://pi-tui.ratstack.sh/frames/section-loom/default-120-dark.018f96769de7.webp) ![Section Loom, Sections, 120 columns, light theme](https://pi-tui.ratstack.sh/frames/section-loom/default-120-light.b83ed3293878.webp)

### Compact sections

`compact`: Collapse secondary sections while keeping the same underlying snapshot.

Checks at every width and theme:

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

Frames:

- 40 columns: ![Section Loom, Compact sections, 40 columns, dark theme](https://pi-tui.ratstack.sh/frames/section-loom/compact-40-dark.65396cc301e6.webp) ![Section Loom, Compact sections, 40 columns, light theme](https://pi-tui.ratstack.sh/frames/section-loom/compact-40-light.4ccf74445212.webp)
- 60 columns: ![Section Loom, Compact sections, 60 columns, dark theme](https://pi-tui.ratstack.sh/frames/section-loom/compact-60-dark.af4b1589e9a2.webp) ![Section Loom, Compact sections, 60 columns, light theme](https://pi-tui.ratstack.sh/frames/section-loom/compact-60-light.adebe8f2a31b.webp)
- 80 columns: ![Section Loom, Compact sections, 80 columns, dark theme](https://pi-tui.ratstack.sh/frames/section-loom/compact-80-dark.28713df92cd8.webp) ![Section Loom, Compact sections, 80 columns, light theme](https://pi-tui.ratstack.sh/frames/section-loom/compact-80-light.1b7eaaaed894.webp)
- 120 columns: ![Section Loom, Compact sections, 120 columns, dark theme](https://pi-tui.ratstack.sh/frames/section-loom/compact-120-dark.c50398f707d3.webp) ![Section Loom, Compact sections, 120 columns, light theme](https://pi-tui.ratstack.sh/frames/section-loom/compact-120-light.3a2b09daf974.webp)

## Sample code

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

```ts
// Section Loom: combine focused section renderers under one layout budget.
import { truncateToWidth, type Component } from "@earendil-works/pi-tui";
import type { PatternStory } from "../../../src/pattern.ts";

export const story: PatternStory = {
  id: "section-loom", title: "Section Loom", kind: "component",
  apis: ["Component", "truncateToWidth"], rows: 14,
  states: [
    { id: "default", label: "Sections" },
    { id: "compact", label: "Compact sections", steps: [{ type: "action", name: "compact" }] },
  ],
  setup({ theme, action, tui }) {
    const snapshot = {
      status: "Preview ready · 2 checks passed",
      tasks: ["✓ Measure columns", "> Review light theme", "· Publish sample"],
      feed: ["14:02 Width check passed", "14:03 Preview rendered", "14:04 Review requested"],
    };
    let compact = false;
    action("compact", () => { compact = true; tui.requestRender(); });
    const section = (title: string, rows: readonly string[], width: number) => [
      theme.fg("accent", truncateToWidth(title, width)),
      ...rows.map(row => theme.fg("text", truncateToWidth("  " + row, width))),
    ];
    const renderStatus = (width: number) => section("STATUS", [snapshot.status], width);
    const renderTasks = (width: number) => section("TASKS", snapshot.tasks, width);
    const renderFeed = (width: number) => section("FEED", compact
      ? [snapshot.feed.at(-1)! + " · 2 earlier"]
      : snapshot.feed, width);
    const component: Component = {
      invalidate() {},
      render(width) {
        const sections = [renderStatus(width), renderTasks(width), renderFeed(width)];
        const availableRows = tui.terminal.rows - 2;
        const body = sections.flatMap((lines, i) => i === 0 ? lines : ["", ...lines]);
        return [
          theme.fg("accent", "Section Loom · " + (compact ? "compact" : "dashboard")),
          ...body.slice(0, availableRows),
          theme.fg("muted", truncateToWidth("One snapshot · section helpers do not mount UI", width)),
        ];
      },
    };
    return component;
  },
};
```

## Known uses: seen in Nico's repos

- [**pi-coordination**](https://github.com/nicobailon/pi-coordination): Centralize status and section formatting
  - [coordinate/render-utils.ts:1-65](https://github.com/nicobailon/pi-coordination/blob/7f32f5d9d597a31fa477a629dfc6dd26bc649214/coordinate/render-utils.ts#L1-L65) @7f32f5d9
- [**pi-messenger**](https://github.com/nicobailon/pi-messenger): Centralize status and section formatting
  - [overlay-render.ts:1-55](https://github.com/nicobailon/pi-messenger/blob/09937ed647a1b07a3b595bf75943feacb80ff123/overlay-render.ts#L1-L55) @09937ed6
- [**pi-messenger**](https://github.com/nicobailon/pi-messenger): Messenger overlay composed by render helpers
  - [overlay.ts:576-635](https://github.com/nicobailon/pi-messenger/blob/09937ed647a1b07a3b595bf75943feacb80ff123/overlay.ts#L576-L635) @09937ed6

## For agents: choose and check

Choose Section Loom 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.
- [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.
- 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) before you use them.
- Every reference frame gets the verdict its state expects.

Next actions: `related({ id: "section-loom" })` lists what to read next, and `states({ id: "section-loom", 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): chooses the arrangement

## Linked from

No other pattern links here.
