Skip to content

Feat/pairing qr redesign - #42

Merged
scotej merged 3 commits into
mainfrom
feat/pairing-qr-redesign
Jun 6, 2026
Merged

Feat/pairing qr redesign#42
scotej merged 3 commits into
mainfrom
feat/pairing-qr-redesign

Conversation

@scotej

@scotej scotej commented Jun 6, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features

    • Added QR code generation and scanning capabilities for pairing friends
    • Introduced shareable pairing links with clipboard paste support
    • Improved pairing experience with clearer waiting states and better error handling
    • Added TURN server preference configuration for improved connectivity in restrictive networks
    • Enhanced pairing code validation with checksum verification
  • Chores

    • Updated dependencies for QR code functionality
    • Improved release workflow for proper prerelease detection

scotej and others added 3 commits June 6, 2026 21:48
…ring

Pairing failed in practice: a 90s rendezvous timeout closed the room before a
human could carry a 12-word code to a second device; manual transcription was
error-prone (a slip onto a different-but-valid word silently diverged the
topic); and there was no TURN fallback for cross-network NAT.

- Remove the hard 90s timeout — the room stays open until cancel, with a soft
  "still waiting/searching" hint after 30s.
- Wire the previously-decorative TURN preference end-to-end (lib/trystero/ice.ts
  -> joinTopic -> joinRoom). Ships STUN-only: no reliable free public TURN
  exists (Open Relay verified dead 2026-06-01), so the server list is empty and
  enabling cross-network is a one-constant edit in ice.ts.
- studyvis://pair?c=... pairing link (pairLink.ts): host copies the link, a
  paste fills all 12 slots, and the QR encodes the same link. BIP39 checksum
  gate on Connect catches transcription typos before submit.
- Scan QR via webcam (PairQrScanner + jsqr) to auto-fill and connect;
  camera-denied falls back to paste. Shared lib/media.ts stream helper.

Deps: qrcode, jsqr, @types/qrcode.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bump app version 1.0.4 -> 1.1.0 (package.json, tauri.conf.json, Cargo.toml,
Cargo.lock) for the pairing redesign. The release workflow now marks tags
containing a prerelease suffix (e.g. v1.1.0-rc.1) as GitHub prereleases.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Friend discovery (pairing, inbox, presence) rendezvous over trystero's
default Nostr strategy, but joinTopic never passed relayConfig. So trystero
fell back to its appId-seeded shuffle: every StudyVis peer deterministically
lands on the SAME 5 relays from the bundled ~56-relay pool. Overlap is 100%
by construction, but those 5 may be low-uptime or unreachable from a given
network, with no diversity or fallback — discovery then fails symmetrically
for everyone, with no recovery.

Pin relayConfig.urls (src/lib/trystero/relays.ts) to 8 relays verified live
on 2026-06-01 via an ephemeral-event publish/subscribe round-trip — the exact
mechanism trystero rendezvous depends on. Passing urls makes trystero use the
whole list, replacing "whichever 5 the hash picked" with a vetted, more
redundant set. relay.damus.io and relay.froth.zone were unreachable at
verification time and are deliberately omitted.

Also: thread an optional relayConfig override through TopicConfig; warn
instead of silently dropping the 'always' (relay-only) TURN preference when
no TURN servers are configured; and correct ARCHITECTURE.md §4 — relay
selection is deterministic per appId (not random), and the app ships STUN-only
with no automatic TURN fallback (PUBLIC_TURN_SERVERS is empty).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 6, 2026 12:08
@scotej
scotej merged commit 90430c7 into main Jun 6, 2026
2 of 3 checks passed
@coderabbitai

coderabbitai Bot commented Jun 6, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2a575df3-e79f-4329-af48-91bc85c42eb7

📥 Commits

Reviewing files that changed from the base of the PR and between b051929 and af79810.

⛔ Files ignored due to path filters (2)
  • package-lock.json is excluded by !**/package-lock.json
  • src-tauri/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (26)
  • .github/workflows/release.yml
  • ARCHITECTURE.md
  • package.json
  • src-tauri/Cargo.toml
  • src-tauri/tauri.conf.json
  • src/components/PairQrCode.tsx
  • src/components/PairQrScanner.tsx
  • src/features/friends/AddFriendDialog.tsx
  • src/features/friends/AddFriendDialogView.tsx
  • src/features/friends/PairWordInput.tsx
  • src/features/friends/index.ts
  • src/features/friends/pair.ts
  • src/features/friends/pairLink.ts
  • src/features/friends/wordlist.ts
  • src/lib/media.ts
  • src/lib/trystero/ice.ts
  • src/lib/trystero/index.ts
  • src/lib/trystero/relays.ts
  • src/stories/AddFriendDialog.stories.tsx
  • src/strings.ts
  • tests/unit/ice.test.ts
  • tests/unit/media.test.ts
  • tests/unit/pairLink.test.ts
  • tests/unit/relays.test.ts
  • tests/unit/trystero-wrapper.test.ts
  • tests/unit/wordlist.test.ts

📝 Walkthrough

Walkthrough

This PR enhances the peer pairing feature with QR code generation and scanning, introduces a curated relay and TURN configuration system, refactors timeout behavior to user-friendly long-wait hints, and updates the pairing dialog UI to support both QR codes and encoded pairing links alongside traditional word entry.

Changes

QR-Code Pairing Workflow

Layer / File(s) Summary
QR code components and media utilities
src/components/PairQrCode.tsx, src/components/PairQrScanner.tsx, src/lib/media.ts
PairQrCode generates and displays QR images asynchronously from a value. PairQrScanner opens a webcam, scans frames with jsQR, and fires onDecode on first success. Media utilities wrap getUserMedia and safely stop media streams.
Pair link encoding/decoding and BIP39 checksum validation
src/features/friends/pairLink.ts, src/features/friends/wordlist.ts, src/features/friends/index.ts, tests/unit/pairLink.test.ts, tests/unit/wordlist.test.ts
encodePairLink serializes words into studyvis://pair?c=word-word-... URLs. decodePairLink parses and validates the format, word count, and BIP39 membership. New pairCodeChecksumValid validates the full mnemonic checksum via @scure/bip39.
ICE/TURN and relay configuration
src/lib/trystero/ice.ts, src/lib/trystero/relays.ts, src/lib/trystero/index.ts, tests/unit/ice.test.ts, tests/unit/trystero-wrapper.test.ts
ice.ts maps TurnPreference to ICE options; defaults to STUN-only when PUBLIC_TURN_SERVERS is empty, warns on unsupported "always" relay preference. relays.ts exports curated DEFAULT_RELAY_URLS for deterministic relay selection. joinTopic forwards turnConfig, rtcConfig, and relayConfig to trystero.
Dialog state refactoring: timeout to long-wait hints
src/features/friends/AddFriendDialog.tsx, src/features/friends/pair.ts
Removes fixed timeout behavior; adds LONG_WAIT_HINT_MS timer to show "still searching" after 30 seconds. Passes turnPreference from settings to pairing calls. Simplifies error handling by removing explicit PairTimeoutError branching and using generic error messaging.
Host-side pairing UI with QR code and link sharing
src/features/friends/AddFriendDialogView.tsx (host section), src/strings.ts (host strings)
HostPanel renders PairQrCode and replaces onCopyWords with onCopyLink, encoding and copying the pairing link via clipboard. Removes timeout-based retry buttons. HostStatusLine shows long-wait messaging after hint timer fires. Updated strings describe QR and link sharing.
Join-side pairing UI with scanning, pasting, and checksum validation
src/features/friends/AddFriendDialogView.tsx (join section), src/features/friends/PairWordInput.tsx, src/strings.ts (join strings)
JoinPanel adds pairCodeChecksumValid gate before enabling connect, integrates PairQrScanner with dedicated scanning mode, decodes pasted links via decodePairLink, and sanitizes input. Form adds explicit scan/paste/clear buttons. Updated strings include checksum hints, paste failure messaging, and QR/camera guidance.
Testing, strings, stories, docs, and version updates
tests/unit/ice.test.ts, tests/unit/media.test.ts, tests/unit/pairLink.test.ts, tests/unit/relays.test.ts, tests/unit/wordlist.test.ts, tests/unit/trystero-wrapper.test.ts, src/stories/AddFriendDialog.stories.tsx, ARCHITECTURE.md, .github/workflows/release.yml, package.json, src-tauri/Cargo.toml, src-tauri/tauri.conf.json
Comprehensive test suites for ICE, media, link encoding/decoding, relay validation, and checksum functions. Stories updated with HostStillWaiting and JoinStillSearching long-wait scenarios. Versions bumped to 1.1.0 across package manifests. Release workflow now differentiates prerelease tags by presence of - in tag name. Architecture docs clarify that Nostr is currently wired, STUN-only by default, and relays are pinned.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant HostPanel
  participant PairQrCode
  participant JoinPanel
  participant PairQrScanner
  participant pair.ts
  
  User->>HostPanel: open pairing dialog
  HostPanel->>PairQrCode: render QR for words
  PairQrCode-->>User: show QR code image
  User->>HostPanel: click copy link
  HostPanel-->>User: clipboard has pairing URL
  
  User->>JoinPanel: paste or scan
  alt Paste flow
    User->>JoinPanel: paste link
    JoinPanel->>JoinPanel: decodePairLink
    JoinPanel->>JoinPanel: validate checksum
    JoinPanel-->>User: enable connect if valid
  else Scan flow
    User->>JoinPanel: click scan
    JoinPanel->>PairQrScanner: start camera
    PairQrScanner-->>User: show video stream
    User->>PairQrScanner: point at QR
    PairQrScanner->>JoinPanel: onDecode(text)
    JoinPanel->>JoinPanel: decodePairLink + validate
    JoinPanel-->>User: auto-connect if valid
  end
  
  User->>JoinPanel: click connect
  JoinPanel->>pair.ts: join with turnPreference
  pair.ts->>pair.ts: buildIceOptions
  pair.ts-->>User: peer connection established
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

📱 A rabbit hops with QR delight,
Scanning codes from left and right,
Links encoded, checksums strong,
TURN preferences guide us along,
Waiting gently, no timeout's plight! 🐰

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/pairing-qr-redesign

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR redesigns the friend-pairing UX to support QR/link-based pairing (in addition to words), improves pairing correctness via BIP39 checksum validation, and hardens Trystero rendezvous configuration (relay pinning + optional ICE/TURN wiring). It also updates docs/tests and bumps the app version to 1.1.0.

Changes:

  • Add pairing links (studyvis://pair?c=...) with QR rendering + QR scanning + clipboard paste flow, and update pairing dialog copy/states (remove hard timeout; add “still waiting/searching” hint).
  • Add checksum validation for pairing codes to prevent “valid words but wrong room” silent failures.
  • Pin curated Nostr relay URLs for rendezvous and add ICE/TURN option plumbing + tests; update release prerelease tagging logic and version numbers.

Reviewed changes

Copilot reviewed 26 out of 28 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
tests/unit/wordlist.test.ts Adds tests for pairing-code checksum validation.
tests/unit/trystero-wrapper.test.ts Extends Trystero wrapper tests to assert forwarded ICE + relay configuration.
tests/unit/relays.test.ts Adds tests validating relay URL shape (wss, non-empty, no duplicates).
tests/unit/pairLink.test.ts Adds tests for pairing link encode/decode behavior and invalid inputs.
tests/unit/media.test.ts Adds tests for MediaStream stop helper behavior.
tests/unit/ice.test.ts Adds tests for TURN preference → ICE option mapping and warnings.
src/strings.ts Updates pairing dialog strings for QR/link UX, checksum hint, scan/paste flows, and long-wait hints.
src/stories/AddFriendDialog.stories.tsx Updates Storybook stories for new phases/props and long-wait states.
src/lib/trystero/relays.ts Introduces pinned curated DEFAULT_RELAY_URLS for Nostr rendezvous.
src/lib/trystero/index.ts Adds optional ICE + relayConfig to TopicConfig and pins DEFAULT_RELAY_URLS by default.
src/lib/trystero/ice.ts Adds TURN preference mapping + shipped server list stub (empty) and helpers.
src/lib/media.ts Adds shared webcam-open and MediaStream stop utilities.
src/features/friends/wordlist.ts Adds checksum validation helper using validateMnemonic.
src/features/friends/PairWordInput.tsx Allows pasting a full pairing link to populate all word slots.
src/features/friends/pairLink.ts Adds encode/decode for compact pairing links.
src/features/friends/pair.ts Wires TURN preference into pairing (via buildIceOptions).
src/features/friends/index.ts Re-exports new pairing helpers (checksum + pairLink).
src/features/friends/AddFriendDialogView.tsx Adds QR code display, QR scan UI, paste button, checksum gating, and long-wait hints.
src/features/friends/AddFriendDialog.tsx Removes deadline-based pairing timeout; adds long-wait hint timer; copies pairing link to clipboard; forwards TURN preference from settings.
src/components/PairQrScanner.tsx Adds camera-based QR scanning component (jsQR).
src/components/PairQrCode.tsx Adds QR rendering component (qrcode).
src-tauri/tauri.conf.json Bumps app version to 1.1.0.
src-tauri/Cargo.toml Bumps Rust package version to 1.1.0.
src-tauri/Cargo.lock Updates locked version to 1.1.0.
package.json Bumps version to 1.1.0 and adds jsqr, qrcode, @types/qrcode.
package-lock.json Locks new dependencies and updated tree.
ARCHITECTURE.md Updates Trystero strategy/relay selection and TURN documentation to match new wiring.
.github/workflows/release.yml Makes prerelease status conditional on tag suffix (e.g. -rc.1).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +56 to +60
if (result?.data) {
stopped = true
onDecodeRef.current(result.data)
return
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants