Skip to content

fix(ui): allow Universal Clipboard for bunker URI copy#8

Merged
DocNR merged 1 commit into
mainfrom
feat/allow-universal-clipboard-bunker
Apr 20, 2026
Merged

fix(ui): allow Universal Clipboard for bunker URI copy#8
DocNR merged 1 commit into
mainfrom
feat/allow-universal-clipboard-bunker

Conversation

@DocNR
Copy link
Copy Markdown
Owner

@DocNR DocNR commented Apr 20, 2026

Summary

  • Drops .localOnly: true from the bunker URI copy in ConnectSheet.swift (keeps the 120s expiration).
  • Nsec export in ExportKeySheet.swift stays localOnly: true (raw private key, non-negotiable).
  • Enables Universal Clipboard for the primary cross-device pairing UX: copy bunker URI on iPhone → paste into Mac browser client (Coracle, noStrudel, etc.).

Why

Audit finding A10.2 originally locked both nsec and bunker URI to localOnly: true to prevent Universal Clipboard leakage. For the bunker URI that turned out to defeat the main cross-device pairing flow — users resort to AirDrop or manual retype, or silently fail (clipboard shows stale content on Mac).

Expiration behavior (important caveat)

The 120s .expirationDate fires on iPhone only. Universal Clipboard does not propagate expiration metadata to Mac-side NSPasteboard, so the Mac clipboard stays populated until the user next copies something on any UC-connected device. Apple provides no cross-device expiration API.

Residual exposure on the Mac is bounded by SharedStorage.rotateBunkerSecret() firing on successful pair — once any client pairs with the secret, any stale clipboard copies become dead weight (the secret is single-use). Audit note at ~/hq/clave/security-audits/2026-04-17-pre-external-testflight.md A10.2 updated to reflect this.

Test plan

  • Xcode build clean (same pre-existing Sendable warnings from PR feat: hide nostrconnect UI behind dev menu + add log export #4, no new issues)
  • Copy bunker URI on iPhone → paste into Mac browser → URI appears (verified on build 20)
  • Coracle bunker via Mac paste → login completes
  • noStrudel bunker via Mac paste → login completes
  • Nsec export → Mac clipboard stays empty (localOnly still enforced on nsec)
  • iPhone-side: 120s expiration fires (paste on iPhone after 2min shows empty)
  • Mac-side: does NOT auto-clear at 120s (documented limitation, not a bug)

Related

🤖 Generated with Claude Code

Drops `.localOnly: true` from the bunker URI copy path while keeping the
120s expiration. Nsec export in ExportKeySheet.swift stays locked (raw
private key, non-negotiable).

Audit finding A10.2 originally applied `localOnly: true` to both nsec
and bunker URI to prevent Universal Clipboard from syncing either to
nearby Apple-ID devices. For the bunker URI, that turned out to defeat
the primary cross-device pairing flow: users copy their bunker URI on
iPhone to paste into a Mac browser client (Coracle, noStrudel, etc.),
and Universal Clipboard is how that works without friction. Without
this change, users fall back to AirDrop or manual retype.

The 120s expiration still auto-clears the clipboard on both devices,
so the window of secret exposure is bounded.

Scope note recorded in security-audits/2026-04-17-pre-external-testflight.md.
@DocNR DocNR marked this pull request as ready for review April 20, 2026 03:28
@DocNR DocNR merged commit 4e44633 into main Apr 20, 2026
@DocNR DocNR deleted the feat/allow-universal-clipboard-bunker branch April 20, 2026 03:28
DocNR added a commit that referenced this pull request May 2, 2026
Brainstorm review of design-system.md against shipped code surfaced 9
inconsistencies + 1 anti-pattern still present. Fixed everything in one
batch so the next TestFlight archive carries it all.

Code:
- HomeView: drop .padding(.bottom, 8) on SlimIdentityBar invocation —
  slim banner owns its outer bottom padding (12pt); stacking another 8pt
  on top was double-counting (review #4)
- HomeView: drop .padding(.bottom, 8) inside statsRow — listSectionSpacing(0)
  carries the gap to Connected Clients; the residual padding kept the
  visible gap excessive after polish round 2 (review #2)
- AccountDetailView: avatarLarge letter fallback opacity 0.25 → 0.22 to
  align with SlimIdentityBar's 0.22 (review #3)
- ConnectSheet: add .presentationBackground(Color(.systemGroupedBackground))
  — was the last sheet still defaulting to translucent (review #9)
- ApprovalSheet: rename @State capExceeded → showConnectionCapAlert for
  naming convention parity with HomeView (review #7)

design-system.md:
- New "Cross-platform applicability" section at the top — clarifies what
  carries directly to clave.casa web companion (color tokens, displayLabel
  rule, identity-vs-functional zone philosophy, avatar treatments, copy
  patterns, anti-patterns) vs what's iOS-only (SwiftUI modifiers, haptics,
  sheet/toolbar conventions)
- §3 Typography: corrected initial-letter font scale — AvatarView uses
  size*0.35 mono (pubkey) or size*0.4 proportional (name); was wrongly
  documented as a single 0.37 (review #1)
- §4 Avatars: added Treatment Selection Rule table (B on neutral bg,
  C on saturated theme gradient) + clarified 1-vs-2 letter behavior
  (review #5, #6)
- §4 Sizing scale: expanded table to include initial font + border
  thickness per slot, with the ~5% border scaling rule (review #8)
- §5 Spacing: explicit "single source of truth" note on slim banner
  bottom padding; new "Stats row" subsection capturing the
  ultraThinMaterial-on-small-cards-OK rule (review gap #10, #4)
- §6 HomeView gradient: documented palette[0] defensive fallback when
  currentAccount is nil (review #13)
- §7 Patterns: new "State variable naming" subsection with the
  showCapAlert / showAccountCapAlert / showConnectionCapAlert
  convention (review #7 doc side)
- §11 Anti-patterns: audit-point note that ConnectSheet was the last
  surface to acquire .presentationBackground (review #9 doc side)

Build green on iOS Simulator 26.4. pbxproj still 41 — assumes user hasn't
yet archived 41; bump to 42 if needed before re-archive.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

1 participant