# Palette Deck

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

Presentation / Theming · `palette-deck` · [HTML](https://pi-tui.ratstack.sh/patterns/palette-deck/) · [JSON](https://pi-tui.ratstack.sh/patterns/palette-deck.json) · [all patterns](https://pi-tui.ratstack.sh/patterns.md)

Also known as Colour presets.

## Intent

Centralize coordinated colour choices behind named presets and a default.

## Motivation

Powerline Footer centralizes segment colours in named presets with a default lookup.

## Applicability

- Use this when several footer segments share an appearance configuration.

## Structure

```text
preset name -> palette / default
palette -> coordinated segments
```

## Participants

- [`parseColor`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/tui/README.md#colors-and-terminal-styles): Converts supported colour values into a Color.
- [`theme.style`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/tui.md#apply-themes-correctly): Styles text through semantic or concrete colours.
- `Default palette`: Handles unknown names with an explicit fallback.

Pi component APIs: [`parseColor`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/tui/README.md#colors-and-terminal-styles), [`theme.style`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/tui.md#apply-themes-correctly)

## Consequences

- Segments can share one coordinated appearance selection.
- Unknown preset names require an explicit fallback.

## Implementation

- Define a fallback for unknown preset names.
- Resolve preset values through the current colour and theme contracts.

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

### Default preset

`default`: Show coordinated synthetic segments using the default preset.

Checks at every width and theme:

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

Frames:

- 40 columns: ![Palette Deck, Default preset, 40 columns, dark theme](https://pi-tui.ratstack.sh/frames/palette-deck/default-40-dark.8162bf48d7bc.webp) ![Palette Deck, Default preset, 40 columns, light theme](https://pi-tui.ratstack.sh/frames/palette-deck/default-40-light.65f8a6a12564.webp)
- 60 columns: ![Palette Deck, Default preset, 60 columns, dark theme](https://pi-tui.ratstack.sh/frames/palette-deck/default-60-dark.55e0d8cd0450.webp) ![Palette Deck, Default preset, 60 columns, light theme](https://pi-tui.ratstack.sh/frames/palette-deck/default-60-light.8eb80c258f3c.webp)
- 80 columns: ![Palette Deck, Default preset, 80 columns, dark theme](https://pi-tui.ratstack.sh/frames/palette-deck/default-80-dark.a53fb7e0d12e.webp) ![Palette Deck, Default preset, 80 columns, light theme](https://pi-tui.ratstack.sh/frames/palette-deck/default-80-light.46e7764e6d4c.webp)
- 120 columns: ![Palette Deck, Default preset, 120 columns, dark theme](https://pi-tui.ratstack.sh/frames/palette-deck/default-120-dark.215f496092e6.webp) ![Palette Deck, Default preset, 120 columns, light theme](https://pi-tui.ratstack.sh/frames/palette-deck/default-120-light.f31910c42b87.webp)

### Alternate preset

`alternate`: Choose another named preset for the same segments.

Checks at every width and theme:

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

Frames:

- 40 columns: ![Palette Deck, Alternate preset, 40 columns, dark theme](https://pi-tui.ratstack.sh/frames/palette-deck/alternate-40-dark.caf8c2d2ec0a.webp) ![Palette Deck, Alternate preset, 40 columns, light theme](https://pi-tui.ratstack.sh/frames/palette-deck/alternate-40-light.869ed70c4b9e.webp)
- 60 columns: ![Palette Deck, Alternate preset, 60 columns, dark theme](https://pi-tui.ratstack.sh/frames/palette-deck/alternate-60-dark.f3264237bdef.webp) ![Palette Deck, Alternate preset, 60 columns, light theme](https://pi-tui.ratstack.sh/frames/palette-deck/alternate-60-light.f39ee631cfa5.webp)
- 80 columns: ![Palette Deck, Alternate preset, 80 columns, dark theme](https://pi-tui.ratstack.sh/frames/palette-deck/alternate-80-dark.c39d785eb241.webp) ![Palette Deck, Alternate preset, 80 columns, light theme](https://pi-tui.ratstack.sh/frames/palette-deck/alternate-80-light.304a704bc25a.webp)
- 120 columns: ![Palette Deck, Alternate preset, 120 columns, dark theme](https://pi-tui.ratstack.sh/frames/palette-deck/alternate-120-dark.e45f21f980b8.webp) ![Palette Deck, Alternate preset, 120 columns, light theme](https://pi-tui.ratstack.sh/frames/palette-deck/alternate-120-light.246e470f342b.webp)

### Unknown name

`unknown`: Use the declared fallback when the preset name is unknown.

Checks at every width and theme:

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

Frames:

- 40 columns: ![Palette Deck, Unknown name, 40 columns, dark theme](https://pi-tui.ratstack.sh/frames/palette-deck/unknown-40-dark.7533c8c15a6c.webp) ![Palette Deck, Unknown name, 40 columns, light theme](https://pi-tui.ratstack.sh/frames/palette-deck/unknown-40-light.4a84124ccd65.webp)
- 60 columns: ![Palette Deck, Unknown name, 60 columns, dark theme](https://pi-tui.ratstack.sh/frames/palette-deck/unknown-60-dark.c92d7359ac78.webp) ![Palette Deck, Unknown name, 60 columns, light theme](https://pi-tui.ratstack.sh/frames/palette-deck/unknown-60-light.3790fbac9c74.webp)
- 80 columns: ![Palette Deck, Unknown name, 80 columns, dark theme](https://pi-tui.ratstack.sh/frames/palette-deck/unknown-80-dark.ac6c49d043b3.webp) ![Palette Deck, Unknown name, 80 columns, light theme](https://pi-tui.ratstack.sh/frames/palette-deck/unknown-80-light.0211048573f4.webp)
- 120 columns: ![Palette Deck, Unknown name, 120 columns, dark theme](https://pi-tui.ratstack.sh/frames/palette-deck/unknown-120-dark.2b79eb08bd76.webp) ![Palette Deck, Unknown name, 120 columns, light theme](https://pi-tui.ratstack.sh/frames/palette-deck/unknown-120-light.85d790d57a86.webp)

## Sample code

`stories/patterns/presentation/palette-deck.ts`, the story the frames above were rendered from.

```ts
// Palette Deck: select coordinated theme-token colours by named preset.
import { colorToHex, parseColor, wrapTextWithAnsi } from "@earendil-works/pi-tui";
import type { ThemeColor } from "@earendil-works/pi-coding-agent";
import type { PatternStory } from "../../../src/pattern.ts";

export const story: PatternStory = {
  id: "palette-deck", title: "Palette Deck", kind: "component",
  apis: ["parseColor", "theme.style"], rows: 10,
  states: [
    { id: "default", label: "Default preset" },
    { id: "alternate", label: "Alternate preset", steps: [{ type: "action", name: "alternate" }] },
    { id: "unknown", label: "Unknown name", steps: [{ type: "action", name: "unknown" }] },
  ],
  setup({ theme, action, tui }) {
    // Presets coordinate roles, not fixed RGB values.
    const defaultPalette = { model: "accent", branch: "muted", tests: "success" } as const;
    const presets: Record<string, Record<keyof typeof defaultPalette, ThemeColor>> = {
      calm: defaultPalette,
      focus: { model: "success", branch: "accent", tests: "warning" },
    };
    let name = "calm";
    action("alternate", () => { name = "focus"; tui.requestRender(); });
    action("unknown", () => { name = "missing"; tui.requestRender(); });
    return {
      invalidate() {},
      render(width) {
        const palette = presets[name] ?? defaultPalette;
        const segment = (role: keyof typeof palette, text: string) =>
          theme.style(text, { fg: parseColor(colorToHex(theme.colors[palette[role]])), bold: true });
        return [
          theme.fg("accent", "Session footer · palette presets"), "",
          segment("model", "model: example-small"),
          segment("branch", "branch: parser-review"),
          segment("tests", "tests: 6 passing"), "",
          ...wrapTextWithAnsi(presets[name] ? "Preset: " + name :
            "Unknown preset: missing → calm", width).map(line => theme.fg("muted", line)),
        ];
      },
    };
  },
};
```

## Known uses: seen in Nico's repos

- [**pi-powerline-footer**](https://github.com/nicobailon/pi-powerline-footer): Preset-driven segment colors
  - [presets.ts:1-45](https://github.com/nicobailon/pi-powerline-footer/blob/859dee671b633fb533b07ceba3e6c1ab1c43360a/presets.ts#L1-L45) @859dee67

## For agents: choose and check

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

- [Colour Sentry](https://pi-tui.ratstack.sh/patterns/colour-sentry.md): Validate colour overrides before using them in themed terminal output.

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 [`parseColor`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/tui/README.md#colors-and-terminal-styles), [`theme.style`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/tui.md#apply-themes-correctly) before you use them.
- Every reference frame gets the verdict its state expects.

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

- [Colour Sentry](https://pi-tui.ratstack.sh/patterns/colour-sentry.md): validates values before styling

## Linked from

- [Colour Sentry](https://pi-tui.ratstack.sh/patterns/colour-sentry.md): supplies coordinated override values
