---
phase: 13-visual-uat
plan: 01
type: verify
wave: 1
depends_on: []
files_modified: []
autonomous: false
requirements: [UAT-01]

must_haves:
  truths:
    - "Global graph at /app/graph renders all pages as nodes with correct degree sizing"
    - "Orphan pages are visually distinguished (no edges, isolated)"
    - "Zoom (scroll wheel) works smoothly without clipping issues"
    - "Pan (drag canvas) works without losing nodes off-screen"
    - "Hover on a node highlights its connected edges and neighbors"
    - "Click on a node navigates to that page"
    - "Edge-type toggle chips (Links, Backlinks, Shared Tags) filter edges correctly"
    - "Shared Tags toggle is OFF by default; toggling ON shows tag edges with capping"
    - "Local graph dock (page-view) is collapsed by default; expanding fetches /graph/local"
    - "1/2/3-hop depth control updates local graph correctly"
    - "Graph renders on both desktop and mobile viewports (Chromium DevTools mobile simulation)"
  artifacts:
    - path: ".planning/todos/pending/"
      provides: "Bug reports filed during testing, one file per issue with severity classification"
      contains: "severity: blocker|major|minor"
  key_links:
    - from: "web/src/components/graph/GraphCanvas.tsx"
      to: "web/src/stores/graphEdges.ts"
      via: "Edge toggle state drives canvas edge filtering"
    - from: "web/src/components/graph/GraphView.tsx"
      to: "web/src/hooks/useGraph.ts"
      via: "useGraph fetches /api/v1/graph for global view"
    - from: "web/src/components/graph/LocalGraphPanel.tsx"
      to: "web/src/stores/localGraphPanel.ts"
      via: "Local panel fetches /api/v1/graph/local on expand and depth change"
---

<objective>
Manually verify all 11 UAT-01 acceptance criteria for the Phase 10 graph UI against a running dev server in a Chromium desktop browser. This phase catches visual/interactive issues that automated tests (node count, edge count, API contract) cannot cover — canvas pixel rendering, zoom/pan smoothness, hover-highlight behavior, edge toggle responsiveness, node sizing visual correctness, orphan distinction, click-to-navigate, and local graph dock expand/collapse + depth control.

**No code changes. No new features. No bug fixes.** Bugs found are filed immediately as `.planning/todos/pending/` entries with severity classification. Blockers and majors must be resolved before phase close; minors are noted but non-blocking.

Output: a completed UAT checklist in VERIFICATION.md with pass/fail per criterion plus any bug todos filed.
</objective>

<execution_context>
@$HOME/.claude/anvil/workflows/execute-plan.md
</execution_context>

<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/REQUIREMENTS.md §UAT-01
@.planning/phases/13-visual-uat/13-CONTEXT.md
@.planning/phases/10-graph-ui/10-UAT.md
</context>

<this_phase_is_manual_uat>
**Phase 13 is a manual browser verification phase.** The "executor" is a human with a Chromium browser, or a delegated Playwright-driven browser session driven by the architect. There is no code to write, no tests to run, no build step.

The key files below are for reference — they implement what needs to be verified. They are NOT modified in this phase.

**Key source files (read-only — reference for understanding what to test):**
- `web/src/components/graph/GraphCanvas.tsx` — The imperative `<canvas>` wrapper (react-force-graph-2d, force engine, auto-sizing, XSS guard — `nodeLabel` empty, labels drawn as canvas text)
- `web/src/components/graph/GraphView.tsx` — Global graph route at `/app/graph` (fetches `/api/v1/graph`, wraps GraphCanvas + edge toggles)
- `web/src/components/graph/LocalGraphPanel.tsx` — Collapsible right-side dock on page view (fetches `/api/v1/graph/local?path=&depth=`)
- `web/src/stores/graphEdges.ts` — Zustand store for edge toggle state (Links ON, Backlinks ON, Shared Tags OFF by default)
- `web/src/stores/localGraphPanel.ts` — Zustand store for local graph dock state (collapsed by default, depth control)
- `web/src/hooks/useGraph.ts` — Data-fetching hook for global graph endpoint
- `internal/server/handlers_graph.go` — Graph HTTP endpoints
- `internal/graph/query.go` — Graph query layer (GraphData, Neighborhood, Backlinks)

**Dev server:** `./scripts/dev.sh` starts the Go binary on `localhost:8098`, admin credentials `AdminPass123!`.
</this_phase_is_manual_uat>

<tasks>

<task type="manual" tdd="false">
  <name>Task 1: Start dev server and verify global graph renders with degree-sized nodes and orphans</name>
  <files>web/src/components/graph/GraphCanvas.tsx, web/src/components/graph/GraphView.tsx</files>
  <behavior>
    - Navigate to http://localhost:8098/app/graph (login as admin: AdminPass123! if needed)
    - The page renders a force-directed canvas with nodes representing pages
    - Nodes are sized proportionally to their degree (more links = larger node)
    - Pages with no links (orphans) are visually distinct — dimmer, outlined, or positioned at the periphery
    - Active/current page node is visually accented (different color or highlight)
  </behavior>
  <action>
    1. `./scripts/dev.sh` (from repo root) — starts Go backend + embedded SPA on :8098
    2. Open Chromium browser, navigate to http://localhost:8098/app/graph
    3. Observe node rendering: verify nodes exist, are sized differently, and the force layout has settled
    4. Check orphan pages: find a page with no links — verify it appears isolated (no edge lines connecting it) and visually dimmer/outlined
    5. Check active page accent: the current page node should have a distinct visual treatment
  </action>
  <acceptance_criteria>
    - Global graph `/app/graph` renders all pages as nodes on a canvas (not a blank page or error)
    - Node sizes vary by degree — high-degree pages have visibly larger nodes than low-degree ones
    - Orphan pages (no links) have no edge lines and appear visually distinct (dimmer color, outline, or similar)
    - Active page node is visually accented
  </acceptance_criteria>
  <done>Global graph renders correctly: degree-sized nodes, orphan distinction, active page accent verified.</done>
</task>

<task type="manual" tdd="false">
  <name>Task 2: Verify zoom, pan, hover-highlight, and click navigation</name>
  <files>web/src/components/graph/GraphCanvas.tsx</files>
  <behavior>
    - Scroll wheel zooms in/out smoothly; canvas content scales without disappearing
    - Drag on empty canvas area pans the view; nodes stay within reachable bounds
    - Hovering cursor over a node highlights that node, its connected edges, and its neighbor nodes
    - Moving cursor away removes the highlight
    - Clicking a node navigates the browser to that page (e.g., /app/page/some-path)
  </behavior>
  <action>
    1. At /app/graph: scroll wheel forward/back — zoom should feel responsive, nodes should not clip/disappear at any zoom level
    2. Click and drag on empty canvas area — view should pan in the drag direction; nodes should not fly off-screen permanently
    3. Hover over a node with known links — its connected edges should brighten or highlight, and neighbor nodes should visually pop
    4. Move cursor off the node — highlight should clear
    5. Click on a node — browser should navigate to that page's URL
  </action>
  <acceptance_criteria>
    - Zoom (scroll wheel) works: canvas zooms in/out, no nodes clip or disappear entirely at reasonable zoom levels
    - Pan (drag canvas) works: view moves with drag; releasing drag leaves nodes in a reachable position
    - Hover highlights the hovered node, its incident edges, and its neighbor nodes
    - Hover off clears the highlight
    - Click on a node navigates to that page (URL changes to /app/page/...)
  </acceptance_criteria>
  <done>Zoom, pan, hover-highlight, and click-to-navigate all work as expected.</done>
</task>

<task type="manual" tdd="false">
  <name>Task 3: Verify edge-type toggle chips filter correctly</name>
  <files>web/src/components/graph/GraphView.tsx, web/src/stores/graphEdges.ts</files>
  <behavior>
    - Three toggle chips are visible: Links, Backlinks, Shared Tags
    - Shared Tags is OFF by default (the chip is not active/pressed)
    - Links and Backlinks are ON by default
    - Toggling any chip on/off immediately updates the visible edges on the canvas
    - Turning OFF both Links and Backlinks hides all link edges (only tag edges remain if Shared Tags is ON)
    - Toggling Shared Tags ON shows colored edges connecting pages through shared tags; works on both global and local graph views
    - Shared Tags ON with many shared tags does not create a hairball (cap is in effect)
  </behavior>
  <action>
    1. At /app/graph: observe the three toggle chips above/alongside the canvas
    2. Confirm Shared Tags is OFF by default (unpressed/deselected appearance)
    3. Toggle Shared Tags ON — verify tag edges appear (colored differently from link edges)
    4. Toggle Links OFF — link edges disappear; only backlinks + tag edges visible
    5. Toggle Backlinks OFF — backlink edges disappear
    6. Toggle Links back ON — link edges reappear
    7. Leave Shared Tags ON, toggle both Links and Backlinks OFF — verify tag edges remain visible
    8. Navigate to a page with a local graph dock, expand it — the same edge toggles should affect the local graph (shared state)
  </action>
  <acceptance_criteria>
    - Three toggle chips (Links, Backlinks, Shared Tags) are visible on the global graph page
    - Shared Tags is OFF by default (visually unpressed/deselected)
    - Links and Backlinks are ON by default
    - Toggling any chip on/off immediately filters edges on the canvas
    - Links OFF + Backlinks OFF = no link edges visible
    - Shared Tags ON shows tag edges; Shared Tags OFF hides them
    - Edge toggle state is shared between global graph and local graph dock
    - Shared Tags ON does not produce a hairball (tag cap working)
  </acceptance_criteria>
  <done>Edge toggle chips work: correct defaults, immediate filtering, shared state between global and local views.</done>
</task>

<task type="manual" tdd="false">
  <name>Task 4: Verify local graph dock behavior (collapse, expand, depth control)</name>
  <files>web/src/components/graph/LocalGraphPanel.tsx, web/src/stores/localGraphPanel.ts</files>
  <behavior>
    - On any page view, the right-side "Local graph" dock is collapsed by default
    - Clicking to expand it reveals a smaller force-directed graph showing the current page and its neighbors
    - A depth control (1/2/3 hops) is visible; default is 1
    - Changing depth updates the graph — fetching /api/v1/graph/local?path=<page>&depth=<n>
    - Navigating to a different page auto-updates the local graph to the new page
  </behavior>
  <action>
    1. Navigate to any page (e.g., /app/page/index) — verify the right-side "Local graph" dock is collapsed by default
    2. Click to expand the dock — verify a smaller force-directed graph canvas appears
    3. Verify the graph shows the current page as the center node with its direct neighbors (depth 1)
    4. If the page has links: neighbors are visible; if orphan: only the page node itself
    5. Change depth to 2 — verify the graph expands to show pages 2 hops away
    6. Change depth to 3 — same; verify the graph doesn't explode at depth 3
    7. Navigate to a different page (click a node or sidebar link) — verify the local graph auto-updates to the new page
  </action>
  <acceptance_criteria>
    - Local graph dock is collapsed by default on page view
    - Expanding the dock renders a local force-directed graph showing the current page + neighbors
    - Depth control is visible with options 1, 2, 3; default is 1
    - Changing depth updates the graph canvas (fetches new data)
    - Navigating to a different page auto-updates the local graph
    - Edge toggles (Links, Backlinks, Shared Tags) affect the local graph as well
  </acceptance_criteria>
  <done>Local graph dock: collapsed by default, expands to show neighborhood, depth control works, auto-updates on page switch.</done>
</task>

<task type="manual" tdd="false">
  <name>Task 5: Verify graph renders on mobile viewport</name>
  <files>web/src/components/graph/GraphCanvas.tsx, web/src/components/graph/GraphView.tsx</files>
  <behavior>
    - In Chromium DevTools mobile device simulation (e.g., iPhone 14 or Pixel 7 preset), the global and local graph views render without breaking
    - Canvas auto-sizes to the viewport width
    - Nodes and toggle chips are still usable (touch targets adequate, no horizontal overflow)
    - The local graph dock on page view is still reachable and expandable
  </behavior>
  <action>
    1. Open Chromium DevTools (F12), toggle device toolbar (Ctrl+Shift+M)
    2. Select a mobile preset (e.g., iPhone 14, 390×844)
    3. Navigate to /app/graph — verify canvas renders, toggles are reachable, no horizontal scrollbar
    4. Verify zoom works via pinch gesture (or scroll wheel in simulation)
    5. Navigate to a page — verify the local graph dock is reachable and expandable within the mobile viewport
  </action>
  <acceptance_criteria>
    - Global graph renders on mobile viewport (width ~390px) without horizontal overflow
    - Edge toggle chips are visible and usable (not hidden or overlapping)
    - Canvas auto-sizes to fit the viewport
    - Local graph dock is reachable and expandable on mobile viewport
    - No console errors during mobile viewport rendering
  </acceptance_criteria>
  <done>Graph renders correctly on mobile viewport: canvas auto-sized, toggles reachable, no overflow or console errors.</done>
</task>

</tasks>

<bug_workflow>
If a criterion FAILS, immediately file a bug in `.planning/todos/pending/`:

```
---
severity: blocker | major | minor
source: Phase 13 UAT-01
criterion: <which of the 11 criteria failed>
---

# <one-line summary of the bug>

## Steps to reproduce

1. ...
2. ...

## Expected

...

## Actual

...
```

**Severity classification:**
- **blocker:** Renders graph unusable (crash, blank canvas, all nodes missing, JS error in console)
- **major:** Breaks a success criterion (e.g., hover-highlight broken, edge toggle doesn't filter, depth control has no effect)
- **minor:** Cosmetic (e.g., slight clipping at extreme zoom, label overlap, orphan distinction too subtle)

Blockers and majors must be resolved before phase close. Minors are noted but non-blocking.
</bug_workflow>

<threat_model>
**No threat model for this phase.** Phase 13 is manual browser verification only — no code changes, no new endpoints, no dependency additions. The security surface is unchanged from Phase 10 (which was security-reviewed at v1.0 close).
</threat_model>

<verification>
- All 11 UAT-01 criteria checked in Chromium desktop browser
- Dev server started via `./scripts/dev.sh`, serving on `localhost:8098`
- Admin credentials `AdminPass123!` used if login required
- Any bugs filed in `.planning/todos/pending/` with severity classification
- VERIFICATION.md written with pass/fail per criterion
</verification>

<success_criteria>
- All 11 UAT-01 acceptance criteria verified (pass or documented fail with bug filed)
- No blocker-level bugs found (or, if found, acknowledged and routed to a fix phase)
- Major bugs filed as todos with clear repro steps
- VERIFICATION.md produced with pass/fail per criterion
</success_criteria>

<output>
Create `.planning/phases/13-visual-uat/13-VERIFICATION.md` when done, listing each criterion, its result (PASS/FAIL), and links to any bug todos filed.
</output>
