# Transitions — where this item may go next

> One row per move this instance can make right now, with each arm's guard evaluated against it. A board offers only the lanes a card may actually reach instead of accepting a drop and having the engine refuse it afterwards — and `Workflow.Raise(item, move)` takes one of them by handing the row back.

<!-- id: workflow-transitions · area: workflow · stability: stable · html: https://osysharp.com/reference/workflow/transitions/ -->

## Summary        {#summary}
**`<Wf>.For(item).Transitions`** is the answer to *"where may this item go?"*, asked of one running instance and
answered **now** — every guard re-evaluated against this item's current data.

It is a **reflection** of the graph the engine enforces and never a substitute for it. The deposit path re-checks
everything, so a stale or spoofed read buys nothing; the value is that a screen can show the right buttons instead of
finding out on the click.

## Signature      {#signature}
```osy syntax
<Wf>.For(item).Transitions      // List<TransitionView>
Workflow.Raise(item, move)      // take one — `move` is a row this read returned
```

## Description    {#description}

### The row   {#row}
| member | what it is |
|---|---|
| `Event` | the event that fires this move — what you would deposit |
| `Target` | the state it lands in, as an **identifier** — what code compares against. The **current** state when the arm has no `goto` (it runs a body and waits again) |
| `TargetLabel` | the same state as **words**: the tracked enum member's `[Label]`, or its name when it declares none |
| `Allowed` | is it open **right now** — this arm's guard against this item |
| `Reason` | why it is closed, for a human; null when open |
| `NeedsInput` | does the event take arguments — a drop gesture cannot supply them, so a surface must open a form |
| `Slot` | the **wait** this move fills, or null when nothing is waiting for it |

### Closed moves are RETURNED, not hidden    {#closed-moves}
An arm whose guard does not hold comes back with `Allowed = false` and a `Reason`. A target the user cannot reach is
worth **showing as unavailable** rather than omitting: *"you may not move it there yet"* is a different message from a
board that silently has fewer lanes than the workflow does.

### `Slot` — a wait you can fill, or a command you can issue    {#slot}
These are different gestures and they need different affordances.

A move **with** a `Slot` answers a wait: somebody may hold it, it appears on a [board](https://osysharp.com/reference/workflow/work-by-item/), it has
a `Candidates` gate and possibly an SLA. A move with **no** `Slot` is a command — most often one the workflow declares
once, live in every non-terminal state:

```osy syntax
workflow TicketFlow {
  on Cancel { goto Cancelled; }        // no slot waits for this; it is a menu item, not a lane
  …
}
```

Both are real moves and both appear here. Without `Slot` they arrive as indistinguishable rows and a page has to
re-derive the workflow's shape to know which is which.

### `Target` is an identifier; `TargetLabel` is the words    {#target-label}
A state's name is the tracked enum's **member** name, so the member's `[Label]` is the label every other surface
already renders for that value — a board's lanes, a card, a form. `TargetLabel` brings it here.

```osy syntax
enum TicketStatus {
  Open,
  [Label("Awaiting customer")] AwaitingCustomer,
}
```

Both fields exist because both are wanted, and they are not interchangeable: a screen **shows** `TargetLabel` and
**compares against** `Target`. Rendering the identifier puts a machine spelling in front of a person; comparing
against the label breaks the moment somebody adds a `[Label]`.

⚠ **`Event` has no counterpart, and that is a statement rather than a gap.** An event is a declared name and carries
no `[Label]`, so there is nothing to fall back from. An app rendering `m.Event` directly is showing an identifier;
labelling its own verbs is currently the only answer.

### One row per EVENT, not per arm    {#folding}
An event may declare several arms (`on X { when (a) { goto A; } default { goto B; } }`) and the engine fires the first
whose guard holds. The read folds them the same way, so `Target` is the arm that **would actually be taken** right
now — which is the question a caller is asking.

The same fold gives **nearest scope wins**: a state's own arm is considered before a workflow-level one for the same
event, exactly as dispatch does.

### What is NOT a move    {#not-moves}
`Complete`, `Expire` and `Deadline` arms are engine-fired — a timer, or a requirement becoming satisfied. Nobody drops
a card to make a deadline pass, so offering them would describe a UI that cannot exist. A run that is not `Waiting`
(terminal, failed, cancelled) returns an **empty list**, which lets a board render *"no moves"* honestly.

### How do I take one of the listed moves?    {#taking}
`Workflow.Raise(item, move)` hands the **row** back rather than naming an event. Every other raise form names its
event at compile time; this list is a runtime one, so passing the view is what keeps it honest — you can only take a
move the engine itself listed for this instance.

## Examples       {#examples}
A card wall: the lanes this card may be dropped into, and the commands beside them.

```osy title="the moves a card can make, and taking one" test app=workflow-transitions
enum CardStatus { Triage, Doing, Done, Cancelled }

[Principal] entity Person {
  [Required, MaxLength(80)] string Name;
  security { allow read when IsAuthenticated || IsAnonymous; }
}

entity Card {
  [Required, MaxLength(200)] string Title;
  CardStatus Status;
  bool Signed;
  security { allow read, create, update when IsAuthenticated || IsAnonymous; }
}

workflow CardFlow {
  Tracks    = Card.Status;
  Autostart = true;
  Initial   = Triage;

  event Start();
  event Finish(string note);
  event Cancel();

  // Declared on the WORKFLOW: available in every non-terminal state, and no slot waits for it.
  on Cancel { goto Cancelled; }

  state Triage {
    subscribe Start();
    on Start { goto Doing; }
  }

  state Doing {
    subscribe Finish(string note);
    on Finish { when (this.Item.Signed) { goto Done; } }
  }

  terminal success Done { }
  terminal error   Cancelled { Message = "cancelled"; }
}

// What the buttons say. `TargetLabel` is for the person; `Target` is what the page compares against.
List<Osysharp.Workflow.TransitionView> Choices(Card card) {
  return CardFlow.For(card).Transitions.Where(t => t.Allowed).ToList();
}

// The lanes: moves that fill a wait, which is what a board drops into.
List<Osysharp.Workflow.TransitionView> Lanes(Card card) {
  return CardFlow.For(card).Transitions.Where(t => t.Slot != null).ToList();
}

// The menu: moves with nothing waiting for them.
List<Osysharp.Workflow.TransitionView> Commands(Card card) {
  return CardFlow.For(card).Transitions.Where(t => t.Slot == null).ToList();
}

// Taking one, by handing the row back.
void TakeFirstOpen(Card card) {
  var move = CardFlow.For(card).Transitions.Where(t => t.Allowed && !t.NeedsInput).First();
  Workflow.Raise(card, move);
}
```

## Notes          {#notes}
**It is re-read, never stored.** Guards are evaluated at the moment of the call, so a page that wants live buttons
re-reads rather than caching — the same relationship a `canPress` policy has with the server that goes on enforcing it.

**A `live` read over this wakes on the ITEM.** A workflow read subscribes to the entity the run is FOR, never to its
own row type — those rows are synthesised per call and nobody commits one. So a raise wakes it (the tracked property
moves), and so do [`Claim`](https://osysharp.com/reference/workflow/inbox-act/) / `Release` / [`Assign`](https://osysharp.com/reference/workflow/assign/), which change no property
on the item but signal it deliberately. Without that a hand-over left the buttons beside it answering from before.

**`Allowed` is about the ARM's guard, not about you.** Whether *this caller* may fill a particular wait is the slot's
own [`Candidates`](https://osysharp.com/reference/workflow/candidates/), asked with `<Wf>.For(item).<Slot>.Candidates(u)`.

## See also       {#see-also}
- [Candidates (slot)](https://osysharp.com/reference/workflow/candidates/) — whether the viewer may fill a wait, which `Allowed` does not answer
- [Assign — handing a slot to a named colleague](https://osysharp.com/reference/workflow/assign/) — handing a wait to a named colleague once you know which slot it is
- [Requires — named preconditions, and the live checklist](https://osysharp.com/reference/workflow/requires/) — what must hold before a move can be taken, as a live checklist
- [Workflow.WorkByItem&lt;T&gt; (one row per item — the board read)](https://osysharp.com/reference/workflow/work-by-item/) — the board this read draws the lanes for
- [Workflow.Run (start a workflow)](https://osysharp.com/reference/workflow/run/) — raising an event by name, when the author knows it at compile time
