#20456: fix(control-ui): show device pairing approval prompt when a new device requests access
app: web-ui
size: M
Cluster:
Device Pairing and Gateway Fixes
## Summary
Fixes #20447
When a new browser/device requests pairing, the gateway broadcasts `device.pair.requested` to all connected Control UI clients with `operator.pairing` scope. Previously this event only silently refreshed the device list (`loadDevices({ quiet: true })`), giving the already-connected operator UI no visible indication that action was needed.
- **Root cause:** `app-gateway.ts` handled `device.pair.requested` the same way as `device.pair.resolved` — just a quiet background list refresh. No queue, no overlay, no badge.
- **Fix:** Mirror the `exec.approval.requested` pattern: queue incoming requests in `devicePairingQueue` and render them as an overlay prompt that lets the operator approve or reject without navigating away. Queue entry is removed when `device.pair.resolved` is received (regardless of decision) or when the operator acts via the prompt.
### Files changed
| File | Change |
|------|--------|
| `ui/src/ui/views/device-pair-prompt.ts` | New overlay component (reuses `exec-approval-*` CSS classes) |
| `ui/src/ui/app-gateway.ts` | Populates/clears `devicePairingQueue` on events; resets on reconnect |
| `ui/src/ui/app-view-state.ts` | Adds `devicePairingQueue`, `devicePairBusy`, `devicePairError`, `handleDevicePairDecision` to view state type |
| `ui/src/ui/app.ts` | Adds reactive state fields and `handleDevicePairDecision` method |
| `ui/src/ui/app-render.ts` | Renders `renderDevicePairPrompt(state)` alongside the exec approval prompt |
## Test plan
- [ ] Open Control UI in browser A (paired)
- [ ] Open Control UI in browser B (new profile, no keypair)
- [ ] Browser A should show the device pairing overlay immediately — device name, role, IP
- [ ] Click **Approve** → overlay disappears, browser B connects
- [ ] Repeat and click **Reject** → overlay disappears, browser B shows pairing-required error
- [ ] Multiple pending requests queue correctly (counter badge shown)
- [ ] Reconnecting clears the queue (`devicePairingQueue = []` on `connectGateway`)
- [ ] All 896 existing tests pass (`npx vitest run`)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- greptile_comment -->
<h3>Greptile Summary</h3>
Added device pairing approval prompt to Control UI by mirroring the existing exec approval pattern. When `device.pair.requested` events arrive, they're now queued in `devicePairingQueue` and rendered as an overlay that lets operators approve or reject without navigating away. The queue is cleared on reconnect and items are removed when `device.pair.resolved` events arrive or when the operator makes a decision.
<h3>Confidence Score: 5/5</h3>
- This PR is safe to merge with minimal risk
- The implementation correctly mirrors the existing exec approval pattern with consistent queue management, event handling, and UI rendering. All state is properly initialized and cleared on reconnect. No logical errors or race conditions detected.
- No files require special attention
<sub>Last reviewed commit: ad9af1d</sub>
<!-- greptile_other_comments_section -->
<sub>(2/5) Greptile learns from your feedback when you react with thumbs up/down!</sub>
<!-- /greptile_comment -->
Most Similar PRs
#23678: fix(ui): Control UI shows no guidance on 1008 pairing required (#23...
by yinghaosang · 2026-02-22
82.0%
#17425: fix(gateway): auto-approve scope/role upgrades for already-paired d...
by sauerdaniel · 2026-02-15
79.5%
#3744: Gateway: fix device pairing when local connection retries with exis...
by photon3710 · 2026-01-29
79.5%
#22712: fix(gateway): auto-approve all device pairing for localhost connect...
by NewdlDewdl · 2026-02-21
79.4%
#6846: fix: bridge node.pair.* tools to device pairing store
by cortexuvula · 2026-02-02
78.8%
#20089: fix(gateway): preserve control-ui scopes when dangerouslyDisableDev...
by vashkartik · 2026-02-18
78.6%
#11868: control-ui: pairing required guided flow (Clairephone V2 Candidate A)
by shojikumaru · 2026-02-08
77.9%
#22280: fix(gateway): silently auto-approve local paired-device scope upgrades
by abhishekp76 · 2026-02-21
77.9%
#17572: fix: make dangerouslyDisableDeviceAuth bypass device identity checks
by gitwithuli · 2026-02-15
77.8%
#23503: fix: preserve pairing state on device token mismatch + migrate lega...
by dorukardahan · 2026-02-22
77.7%