# Shrinking Sieve

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

Behavioral / Lists and pickers · `shrinking-sieve` · [HTML](https://pi-tui.ratstack.sh/patterns/shrinking-sieve/) · [JSON](https://pi-tui.ratstack.sh/patterns/shrinking-sieve.json) · [all patterns](https://pi-tui.ratstack.sh/patterns.md)

Also known as Incremental filter.

## Intent

Narrow existing candidates while a search query grows.

## Motivation

Skill Palette filters its current candidates as a query grows but must rescan when that assumption stops holding.

## Applicability

- Use this when query extension can only remove candidates from a picker.

## Structure

```text
query extends -> narrow set
other edit -> full rescan
```

## Participants

- [`Input`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/tui.md#compose-built-in-components): Owns single-line editing and cursor state.
- [`SelectList`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/tui.md#compose-built-in-components): Owns a bounded selection list and selection callbacks.
- [`TUI.requestRender`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/tui.md#understand-the-component-model): Requests a coalesced redraw after state changes.
- `Candidate set`: Narrows on query extension and resets for other edits.

Pi component APIs: [`Input`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/tui.md#compose-built-in-components), [`SelectList`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/tui.md#compose-built-in-components), [`TUI.requestRender`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/tui.md#understand-the-component-model)

## Consequences

- Query extension reuses the narrowed candidate set.
- Backspace and other non-extension edits require a full rescan.

## Implementation

- Rescan all candidates when an edit does not extend the previous query.
- Reset selection on query changes rather than navigation.

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

`initial`: Show the full synthetic command list.

Checks at every width and theme:

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

Frames:

- 40 columns: ![Shrinking Sieve, All candidates, 40 columns, dark theme](https://pi-tui.ratstack.sh/frames/shrinking-sieve/initial-40-dark.0ea3a88011f8.webp) ![Shrinking Sieve, All candidates, 40 columns, light theme](https://pi-tui.ratstack.sh/frames/shrinking-sieve/initial-40-light.a950aec846c1.webp)
- 60 columns: ![Shrinking Sieve, All candidates, 60 columns, dark theme](https://pi-tui.ratstack.sh/frames/shrinking-sieve/initial-60-dark.45d8b7d77823.webp) ![Shrinking Sieve, All candidates, 60 columns, light theme](https://pi-tui.ratstack.sh/frames/shrinking-sieve/initial-60-light.2115f69ef427.webp)
- 80 columns: ![Shrinking Sieve, All candidates, 80 columns, dark theme](https://pi-tui.ratstack.sh/frames/shrinking-sieve/initial-80-dark.1a16efe3577d.webp) ![Shrinking Sieve, All candidates, 80 columns, light theme](https://pi-tui.ratstack.sh/frames/shrinking-sieve/initial-80-light.5701a7acd508.webp)
- 120 columns: ![Shrinking Sieve, All candidates, 120 columns, dark theme](https://pi-tui.ratstack.sh/frames/shrinking-sieve/initial-120-dark.8a50e04f6fd5.webp) ![Shrinking Sieve, All candidates, 120 columns, light theme](https://pi-tui.ratstack.sh/frames/shrinking-sieve/initial-120-light.889450c15eae.webp)

### Narrowed query

`extend`: Append query characters and narrow the current candidates.

Checks at every width and theme:

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

Frames:

- 40 columns: ![Shrinking Sieve, Narrowed query, 40 columns, dark theme](https://pi-tui.ratstack.sh/frames/shrinking-sieve/extend-40-dark.1bd910d359db.webp) ![Shrinking Sieve, Narrowed query, 40 columns, light theme](https://pi-tui.ratstack.sh/frames/shrinking-sieve/extend-40-light.2883a8e67639.webp)
- 60 columns: ![Shrinking Sieve, Narrowed query, 60 columns, dark theme](https://pi-tui.ratstack.sh/frames/shrinking-sieve/extend-60-dark.d8524caf22f2.webp) ![Shrinking Sieve, Narrowed query, 60 columns, light theme](https://pi-tui.ratstack.sh/frames/shrinking-sieve/extend-60-light.71f43848f5ac.webp)
- 80 columns: ![Shrinking Sieve, Narrowed query, 80 columns, dark theme](https://pi-tui.ratstack.sh/frames/shrinking-sieve/extend-80-dark.9bb891be7c6d.webp) ![Shrinking Sieve, Narrowed query, 80 columns, light theme](https://pi-tui.ratstack.sh/frames/shrinking-sieve/extend-80-light.0fd108662bac.webp)
- 120 columns: ![Shrinking Sieve, Narrowed query, 120 columns, dark theme](https://pi-tui.ratstack.sh/frames/shrinking-sieve/extend-120-dark.2db2c644b53b.webp) ![Shrinking Sieve, Narrowed query, 120 columns, light theme](https://pi-tui.ratstack.sh/frames/shrinking-sieve/extend-120-light.968553ae6e3e.webp)

### Rescan

`backspace`: Shorten the query and restore candidates from the full list.

Checks at every width and theme:

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

Frames:

- 40 columns: ![Shrinking Sieve, Rescan, 40 columns, dark theme](https://pi-tui.ratstack.sh/frames/shrinking-sieve/backspace-40-dark.e1e4010635d1.webp) ![Shrinking Sieve, Rescan, 40 columns, light theme](https://pi-tui.ratstack.sh/frames/shrinking-sieve/backspace-40-light.94d3f7488677.webp)
- 60 columns: ![Shrinking Sieve, Rescan, 60 columns, dark theme](https://pi-tui.ratstack.sh/frames/shrinking-sieve/backspace-60-dark.7da2cb0e5f5a.webp) ![Shrinking Sieve, Rescan, 60 columns, light theme](https://pi-tui.ratstack.sh/frames/shrinking-sieve/backspace-60-light.ad6b70ffa586.webp)
- 80 columns: ![Shrinking Sieve, Rescan, 80 columns, dark theme](https://pi-tui.ratstack.sh/frames/shrinking-sieve/backspace-80-dark.dac63aa213fc.webp) ![Shrinking Sieve, Rescan, 80 columns, light theme](https://pi-tui.ratstack.sh/frames/shrinking-sieve/backspace-80-light.78fd4246cf74.webp)
- 120 columns: ![Shrinking Sieve, Rescan, 120 columns, dark theme](https://pi-tui.ratstack.sh/frames/shrinking-sieve/backspace-120-dark.0a2b058bfa40.webp) ![Shrinking Sieve, Rescan, 120 columns, light theme](https://pi-tui.ratstack.sh/frames/shrinking-sieve/backspace-120-light.9e5660c91906.webp)

## Sample code

`stories/patterns/behavioral/shrinking-sieve.ts`, the story the frames above were rendered from.

```ts
// Shrinking Sieve: narrow on extension, rescan all candidates on other edits.
import { getSelectListTheme } from "@earendil-works/pi-coding-agent";
import { Input, SelectList, Text, type Component, type Focusable } from "@earendil-works/pi-tui";
import type { PatternStory } from "../../../src/pattern.ts";

const allCandidates = [
  ["build", "Compile the app"], ["bundle", "Package assets"], ["browse", "Explore tasks"],
  ["review", "Inspect a patch"], ["test", "Run local checks"],
].map(([label, description]) => ({ value: label!, label: label!, description }));

export const story: PatternStory = {
  id: "shrinking-sieve", title: "Shrinking Sieve", kind: "component",
  apis: ["Input", "SelectList", "TUI.requestRender"], rows: 12,
  states: [
    { id: "initial", label: "All candidates" },
    { id: "extend", label: "Narrowed query", steps: [
      { type: "keys", data: "b" }, { type: "keys", data: "u" },
    ] },
    { id: "backspace", label: "Rescan", steps: [{ type: "keys", data: "\x7f" }] },
  ],
  setup({ tui, theme, keybindings }) {
    const input = new Input({ prompt: "Filter: ", placeholder: "type a command" });
    let previousQuery = "", candidates = allCandidates;
    let choices = new SelectList(candidates, 5, getSelectListTheme());
    let explanation = "Full set · 5 commands";
    const view: Component & Focusable = {
      focused: false,
      invalidate() { input.invalidate(); choices.invalidate(); },
      handleInput(data) {
        if (keybindings.matches(data, "tui.select.up") || keybindings.matches(data, "tui.select.down")) {
          choices.handleInput(data);
        } else {
          input.handleInput(data);
          const query = input.getValue().toLowerCase();
          if (query !== previousQuery) {
            const extendsQuery = query.startsWith(previousQuery);
            const source = extendsQuery ? candidates : allCandidates;
            candidates = source.filter(item => item.label.includes(query));
            explanation = (extendsQuery ? "Narrow current set" : "Rescan full set")
              + " · " + source.length + " → " + candidates.length;
            choices = new SelectList(candidates, 5, getSelectListTheme());
            previousQuery = query;
          }
        }
        tui.requestRender();
      },
      render(width) {
        input.focused = this.focused;
        return [
          ...new Text(theme.fg("accent", "Command palette"), 0, 0).render(width),
          ...input.render(width), "", ...choices.render(width), "",
          ...new Text(theme.fg("success", explanation), 0, 0).render(width),
          ...new Text(theme.fg("dim", "Backspace restores excluded candidates"), 0, 0).render(width),
        ];
      },
    };
    return view;
  },
};
```

## Known uses: seen in Nico's repos

- [**pi-skill-palette**](https://github.com/nicobailon/pi-skill-palette): Narrow palette results as the query grows
  - [index.ts:622-741](https://github.com/nicobailon/pi-skill-palette/blob/a5c4429b8c2e33ab903d07856497014f3d5ad34e/index.ts#L622-L741) @a5c4429b

## For agents: choose and check

Choose Shrinking Sieve when your job matches its intent and applicability above. Its neighbours in Lists and pickers are listed below. Read the one whose intent fits your job more closely before you commit.

- [Row Window](https://pi-tui.ratstack.sh/patterns/row-window.md): Keep the selected item inside a bounded moving list window.
- [Detail Lens](https://pi-tui.ratstack.sh/patterns/detail-lens.md): Derive a selected item's detail sections from a read-only snapshot.
- [Tab Deck](https://pi-tui.ratstack.sh/patterns/tab-deck.md): Keep separate keyboard-navigable data views behind a tab strip.
- [Branch Fold](https://pi-tui.ratstack.sh/patterns/branch-fold.md): Retain validated fold identifiers while navigating a session tree.
- [Match Ladder](https://pi-tui.ratstack.sh/patterns/match-ladder.md): Rank matching options across weighted label and description fields.
- [Identity Anchor](https://pi-tui.ratstack.sh/patterns/identity-anchor.md): Retain the highlighted item's identity as asynchronous results arrive.
- [Preview Basket](https://pi-tui.ratstack.sh/patterns/preview-basket.md): Keep selection separate from thumbnail loading and zoom inspection.
- [Lazy Peek](https://pi-tui.ratstack.sh/patterns/lazy-peek.md): Load and cache preview detail only for items the user inspects.

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 [`Input`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/tui.md#compose-built-in-components), [`SelectList`](https://github.com/earendil-works/pi/blob/v1.0.3/packages/coding-agent/docs/tui.md#compose-built-in-components), [`TUI.requestRender`](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: "shrinking-sieve" })` lists what to read next, and `states({ id: "shrinking-sieve", 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

- [Match Ladder](https://pi-tui.ratstack.sh/patterns/match-ladder.md): ranks rather than only narrowing

## Linked from

- [Match Ladder](https://pi-tui.ratstack.sh/patterns/match-ladder.md): narrows candidates without field ranking
