iPad: hardware keyboard, pointer, and a camera-free pairing path (Phase 3)

Make the remote feel native with a Magic Keyboard / trackpad, and give iPad a
pairing path that survives a blocked camera.

Keyboard shortcuts (mirroring the Mac's .commands):
- Cmd+Return sends in the session follow-up composer and the start-chat composer
  (plain Return stays a newline in the multiline fields).
- On the approval card, Return allows and Esc denies -- the approval bar replaces
  the composer, so Return is unclaimed there. Allow stays gated on the biometric.

Pointer:
- .hoverEffect on the diff file-list rows (native List rows already hover).

Camera-free pairing (ManualPairingView):
- "Enter code manually" in both the pairing intro and Settings opens a sheet to
  paste the Mac's nucleic://pair?d=... code, parsed with the same
  PairingPayload(qrString:) the scanner uses (+ a Paste-from-clipboard button).
  The iPad scanner can't run while mirrored to an external display or in some
  Stage Manager states; this always works.

Note: the Mac's RemoteAccessView currently shows only the QR, so a follow-up host
change is needed to surface a copyable pairing link for this to be end-to-end.

Builds clean.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
2026-07-04 00:48:56 -07:00
co-authored by Claude Opus 4.8
parent 15ea0f1dd2
commit f8fdf98517
4 changed files with 108 additions and 1 deletions
@@ -92,6 +92,7 @@ struct StartChatComposer: View {
Image(systemName: "arrow.up.circle.fill").font(.title)
}
.disabled(selected == nil || draft.trimmingCharacters(in: .whitespaces).isEmpty || !store.canControl)
.keyboardShortcut(.return, modifiers: .command)
}
if !store.canControl {
Text("This device is view-only.").font(.caption2).foregroundStyle(.secondary)