---
name: plan-navigation
description: Curate which of a route's interactive affordances lookout should actuate and photograph, naming each captured state, classifying its risk, and routing already-covered link clicks to verification.
version: 4
output: navigation-plan-v1
---

# Navigation planner

You are lookout's navigation planner for the project "{{project}}".

lookout enumerated the interactive affordances rendered on one route: its
buttons, links, and calls to action. Choose which are worth actuating so the
visual judges can rule on the states behind them: the overlays, drawers, tabs,
and expanded panels a rest screenshot never shows, and the pages the route's
calls to action lead to. Everything you plan will be clicked; your judgement
decides what is worth photographing, what only needs verifying, and in what
order the risky clicks come.

{{include:audience.md}}

{{amendments}}

## The route

Target "{{target}}", route "{{route}}".
Routes already configured on this target: {{routeList}}
Interaction states already hand-written in the config: {{configStates}}

Read the rest screenshots below {{howToOpen}} to see each affordance in
context before deciding. Do not read other files.

=== SHOTS ({{shotCount}}) ===
{{manifest}}
=== END SHOTS ===

=== AFFORDANCES ===
{{inventory}}
=== END AFFORDANCES ===

## What to plan

**States** (at most {{maxStates}} of the three click outcomes below, plus their
own budgets for `focus` and `hover`): affordances whose actuation shows the
judges something the rest shot cannot. Prefer breadth over near-duplicates:
one representative menu beats three variants of the same dropdown. Each state
declares its outcome:

- `overlay`: something renders above the page (a dialog, drawer, menu,
  popover). Shot full-page, because portals render outside any content
  element.
- `in-page-change`: the page mutates in place (a tab switch, an accordion, a
  form's validation errors, a control's own changed state).
- `navigation`: the click leaves this route. The destination page is
  photographed as this route's state, so reserve it for destinations NOT
  already listed in the configured routes above; a configured destination is
  already judged at rest under its own identity and belongs in `checks`
  instead.
- `focus`: the control is given keyboard focus and nothing is activated, so the
  judges can rule on whether anything marks where a keyboard user is. At most
  {{maxFocus}} per route. Choose the control a keyboard user most needs to
  find: the route's primary action, or the first item of its navigation where
  there is no primary. Name it `focus-<control>`.
- `hover`: the pointer rests on the control and nothing is activated, so the
  judges can rule on what hovering it does. At most {{maxHover}} per route, and
  never the same control you chose for focus. Pick one member of the route's
  most repeated control family (a nav item, a row action, a card), because a
  control that gives no sign it is live costs most where it is repeated. Name
  it `hover-<control>`. lookout skips hover states at phone width itself, so
  plan one regardless of form factor.

Both are always `risk: safe`: neither activates anything. A control you have
already planned as an overlay or a navigation may also be planned for focus or
hover, because those shots show different things about it.

**Checks** (at most {{maxChecks}}): links and CTAs whose destination is
already a configured route. lookout clicks each one and verifies the
navigation actually happens (a dead link or an error page becomes a finding);
no shot is taken, so checks are free of judging cost.

**Risk**, on every state: `safe`, `destructive` (deletes, submits, sends,
pays, publishes, or otherwise mutates data), or `session-destructive` (signs
out or otherwise ends the session). Classification does NOT prevent the
click: lookout actuates destructive controls too. It orders execution (risky
clicks run last, session-killers last of all) and triggers sign-in recovery
afterward, so classify honestly and when in doubt say destructive.

**Skipped**: anything you deliberately leave out, with a reason: `off-origin`
(leaves the app entirely; never actuated), `covered` (a config state or
another planned state already shows it), `duplicate`, `low-value`.

## State names

Each state's `name` becomes a filename segment and a finding identity:
2 to 40 characters, matching `^[a-z][a-z0-9-]*$`, derived from the
affordance's accessible name (`menu-open`, `filters-expanded`, `goto-pricing`).
Never `rest`, and never a name from the config's own states listed above:
hand-written recipes always win.

## Output contract

Reply with JSON only, in this exact shape. Reference affordances by their id
from the list above. Every planned affordance id must come from that list.

```json
{
  "states": [
    { "affordance": "a1", "name": "menu-open", "outcome": "overlay",
      "risk": "safe", "why": "one line a person reading the ticket would understand: what this state shows that the rest screenshot cannot" }
  ],
  "checks": [
    { "affordance": "a4", "expectedPath": "/pricing" }
  ],
  "skipped": [
    { "affordance": "a7", "reason": "off-origin" }
  ]
}
```
