## U12 part 5 — the move menu was guessing; now it asks the graph
**Stacks on #2521** (same file). Merge that first.
The context menu had **no adjacency data at all**, so it did two wrong
things at once: it approximated move targets from a column's
**neighbours in declared order**, and — because that approximation is
strictly weaker than the real graph — it kept a `VALID_TRANSITIONS`
shortcut for any workflow whose column-id set matched the six built-ins.
Measured, the approximation loses real operator moves:
| current | workflow graph | neighbour approximation |
|---|---|---|
| `in-progress` | in-review, todo, triage, done | todo, in-review |
| `todo` | in-progress, triage, archived | triage, in-progress |
| `done` | todo, triage, archived | in-review, archived |
So **every custom workflow has been offering a guess**: menu entries the
store would reject, and legal moves it never offered. The built-ins were
fine only because the shortcut bypassed the guess entirely.
### The fix
`BoardWorkflowColumn` gains `moveTargets`, resolved by
`resolveAllowedColumns` — *the same resolver `moveTaskInternal`
validates against*. The menu now offers exactly what the store will
accept, for any workflow. Threaded through all four metadata builders
(Board, Lane, ListView, TaskDetailModal).
Optional on the wire, deliberately: a client older than this field keeps
the neighbour fallback rather than losing its move menu mid-upgrade.
### Why deleting the legacy shortcut is safe
Not an assertion — a measurement, then a pin.
`resolveAllowedColumns(BUILTIN_CODING_WORKFLOW_IR, c)` is **identical to
`VALID_TRANSITIONS[c]` for all six columns, order included**:
```
triage ["todo","archived"] == VALID SAME
todo ["in-progress","triage","archived"] == VALID SAME
in-progress ["in-review","todo","triage","done"] == VALID SAME
in-review ["done","in-progress","todo","triage"] == VALID SAME
done ["todo","triage","archived"] == VALID SAME
archived ["done"] == VALID SAME
```
`builtin-adjacency-matches-legacy-transitions.test.ts` pins it so the
equivalence cannot drift silently — if the built-in workflow's edges
change without `VALID_TRANSITIONS` following, default menus change shape
and that test fails first. It compares **order** too, since the menu
renders targets in the order it receives them, so a reorder is
operator-visible.
Default-workflow menus are therefore byte-identical. Custom ones stop
guessing.
### What's left of the legacy vocabulary here
`COLUMNS` is gone from `TaskContextMenu` — deleting the shortcut removed
its last use. `VALID_TRANSITIONS` survives for exactly one thing: the
**no-metadata load window**, documented at the site. I measured removing
that in #2521 and it left Task Detail with no move options during load,
which is a regression rather than a cleanup. It retires when the load
window does.
### Revert-proof, two ways
- Drop the `declaredTargets` branch → the custom-workflow case fails:
the neighbour fallback returns `["backlog","building"]`, missing the
legal `shipped` jump **and** offering `backlog`, which that graph
forbids. That is exactly the defect class shipped to every custom
workflow today.
- A second case pins that an adjacency edge into a column the board
cannot show is **dropped**, not rendered as a dead menu entry.
### Verification
`pnpm test:gate` (309 + 10 + 71), `pnpm lint`, `pnpm verify:fast`, core
+ dashboard typechecks green.
**No new test failures**: five suites report 31 failures with and
without the change — an identical, pre-existing set, verified by diffing
failing test *names* against a stashed clean tree, not by comparing
counts.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Move menus for custom workflows now show only the destinations
permitted by that workflow.
* Task-specific workflow rules are applied consistently across boards,
lists, lanes, and task details.
* Invalid or unavailable destinations are excluded from move options.
* Existing clients remain supported when workflow destination data is
unavailable.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>