Issue status defaults to `todo`, `in-progress`, `done`. Teams that want richer workflows (e.g. `backlog`, `review`, `blocked`, `qa`) register `status` as a property with their own option list. `status` is a regular property key, so there is no new API to learn.

When no extension registers `status`, fp falls back to the built-in three. Behavior is identical to projects that never installed an extension.

## Workflow Extension

Drop this file in `.fp/extensions/workflow.ts`. It declares a five-status workflow and uses the built-in status icons so the desktop renders them with the same look as the defaults.

```ts
import type { ExtensionInit } from "@fiberplane/extensions";

const init: ExtensionInit = async (fp) => {
  await fp.issues.registerProperty("status", {
    label: "Status",
    display: fp.ui.properties.select(
      fp.ui.properties.option("backlog", {
        label: "Backlog",
        icon: "no-status",
        color: "neutral",
      }),
      fp.ui.properties.option("todo", {
        label: "Todo",
        icon: "to-do",
        color: "neutral",
      }),
      fp.ui.properties.option("in-progress", {
        label: "In Progress",
        icon: "in-progress",
        color: "blue",
      }),
      fp.ui.properties.option("review", {
        label: "For Review",
        icon: "for-review",
        color: "purple",
      }),
      fp.ui.properties.option("done", {
        label: "Done",
        icon: "done",
        color: "success",
      }),
    ),
  });

  fp.on("issue:status:changing", ({ from, to }) => {
    if (from === "backlog" && to === "done") {
      return {
        code: "NO_SKIP_REVIEW",
        message: "Issues must pass through review before being marked done.",
      };
    }
    return undefined;
  });
};

export default init;
```

The order of options inside `select(...)` controls everything: the picker order, kanban column order, filter-bar order, sort priority, and tab-completion order. To reorder, edit the extension.

The first option is the default for new issues created without `--status`. The last option is treated as the completed state, so issues with it are not counted as open work.

## Reserved Values

- `deleted` is reserved as a soft-delete marker. Registering an option with `value: "deleted"` (or a case- or separator-insensitive equivalent like `Deleted` or `de-leted`) is rejected when the extension loads.
- All other non-empty strings are accepted as status values.

## Built-in Fallback

When no extension registers `status`, fp behaves as if these three options were registered:

| Value          | Label         | Icon            | Color     |
| -------------- | ------------- | --------------- | --------- |
| `todo`         | Todo          | `to-do`         | `neutral` |
| `in-progress`  | In Progress   | `in-progress`   | `blue`    |
| `done`         | Done          | `done`          | `success` |

Once an extension registers `status`, its option list replaces the fallback entirely. If multiple extensions register `status`, the last to load wins (extensions load globals first, then project, alphabetically by filename within each scope).

## Icon Names

`PropertyOption.icon` accepts three families of names, and you can mix them within a single registration.

### Built-in status icons (recommended for status)

The desktop ships six status icons. Use these for status options to match the rest of the UI:

- `to-do` (empty circle)
- `in-progress` (half-filled circle)
- `for-review` (circle with inset arrow)
- `done` (filled circle with check)
- `no-status` (dashed circle)
- `cancelled` (circle with diagonal slash)

### Lucide names that remap to built-in icons

Common Lucide names for the same shapes are accepted as aliases. They render the built-in variant, not the raw Lucide icon, so the look stays consistent:

| Input | Renders as |
| ----- | ---------- |
| `circle`, `circle-empty` | `to-do` |
| `loader-circle`, `circle-half` | `in-progress` |
| `circle-arrow-right`, `circle-arrow` | `for-review` |
| `circle-check` | `done` |
| `circle-dashed` | `no-status` |
| `circle-slash`, `circle-x` | `cancelled` |

Name matching is case-, separator-, and whitespace-insensitive.

### Raw Lucide passthrough

Any other name is looked up as a Lucide icon by its PascalCase equivalent, so `flag`, `clock`, `git-branch`, and the like render the matching Lucide icon. A name Lucide does not recognize falls back to a dashed circle.

## The "current" Set

Some surfaces (the CLI `--current` filter and "current work" views) need to know which statuses count as active work. fp derives this set automatically from the registered order; there is nothing to declare on individual options.

To set your own values, override them in TOML config. The override is read from three tiers, most specific first:

1. `.fp/config.local.toml`: this user, this project
2. `.fp/config.toml`: this project, all users
3. `~/.fiberplane/config.toml`: this user, all projects

```toml
[status]
current = ["in-progress", "review"]
```

Run `fp guide` from inside the project to see the resolved set. The project context header lists the values and labels them `[auto]` or `[from <tier>]`, so you can tell whether fp derived the set or your config did.

## Lifecycle Hook Payload

The `issue:status:changing` and `issue:status:changed` payloads carry `from` and `to` as status strings, so hook handlers can compare against any status your workflow registers:

```ts
fp.on("issue:status:changing", ({ from, to }) => {
  if (from === "backlog" && to === "done") {
    return {
      code: "NO_SKIP_REVIEW",
      message: "Issues must pass through review before being marked done.",
    };
  }
  return undefined;
});
```

The hook fires for every status transition regardless of source (CLI, desktop UI, another extension). Return a `HookValidationError` to block the change; return `undefined` to allow it.
