Last Updated: December 31, 2025 Target: iOS 26.0+, Xcode 26+ Design System: Liquid Glass
Pulse is a proximity-based developer networking app that leverages iOS 26's Liquid Glass design system to create a spatial, glass-native discovery experience. Unlike traditional messaging apps, Pulse uses floating glass nodes that morph and blend to show nearby developers in real-time.
Core Insight: The "Radar" isn't a list—it's a glass field where peers float, pulse, and morph based on proximity and availability.
Traditional Approach (Wrong for iOS 26):
🟢 jesse_codes < 10m
🟢 swift_sarah < 50m
🟡 rust_developer ~100m
⚪ pythonista_pete (idle)
iOS 26 Liquid Glass Approach:
┌────────────────────────────────────────┐
│ │
│ ◉ jesse_codes │
│ (pulsing green glass node) │
│ │
│ ◎ swift_sarah │
│ (medium glow, closer) │
│ │
│ ○ rust_developer │
│ (faint, distant) │
│ │
│ ◌ pythonista_pete │
│ (idle, flattened into background) │
│ │
└────────────────────────────────────────┘
| State | Visual Treatment | Glass Properties | Motion |
|---|---|---|---|
| Active (Green) | Bright, full saturation | .regular.tint(.green).interactive() |
Gentle pulse (0.9-1.1 scale) |
| Flow State (Yellow) | Soft glow, slight blur | .regular.tint(.yellow.opacity(0.7)) |
Slow drift |
| Idle (Gray) | Flattened, low opacity | .clear |
Static |
| Nearby (<10m) | Large node, sharp | High blur radius | Bouncy on tap |
| Medium (10-50m) | Medium node | Medium blur | Smooth on tap |
| Far (50m+) | Small node, translucent | Low blur | Subtle feedback |
import SwiftUI
import MultipeerConnectivity
struct RadarView: View {
@StateObject private var meshManager = MeshManager()
@Namespace private var glassNamespace
@State private var selectedPeer: PulsePeer?
var body: some View {
ZStack {
// Background: User's content (wallpaper, photos, etc.)
BackgroundContentView()
// Glass Field: Navigation layer floats above
GlassEffectContainer(spacing: 40) {
ForEach(meshManager.nearbyPeers) { peer in
PeerNode(peer: peer)
.glassEffect(glassTypeFor(peer))
.glassEffectID(peer.id, in: glassNamespace)
.onTapGesture {
withAnimation(.bouncy(duration: 0.4)) {
selectedPeer = peer
}
}
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
// Floating Action Button (bottom-right)
VStack {
Spacer()
HStack {
Spacer()
StatusToggleButton()
.glassEffect(.regular.tint(.blue).interactive())
.padding(32)
}
}
}
.sheet(item: $selectedPeer) { peer in
ChatView(peer: peer)
}
}
private func glassTypeFor(_ peer: PulsePeer) -> Glass {
switch peer.status {
case .active:
return .regular.tint(.green.opacity(0.8)).interactive()
case .flowState:
return .regular.tint(.yellow.opacity(0.6)).interactive()
case .idle:
return .clear
}
}
}struct PeerNode: View {
let peer: PulsePeer
@State private var isPulsing = false
var body: some View {
VStack(spacing: 8) {
// Avatar/Icon
Image(systemName: iconFor(peer.techStack.primary))
.font(.system(size: sizeFor(peer.distance)))
.foregroundStyle(.white)
// Handle
Text(peer.handle)
.font(.caption)
.fontWeight(.semibold)
.foregroundStyle(.white)
// Distance indicator
Text(distanceText(peer.distance))
.font(.caption2)
.foregroundStyle(.white.opacity(0.7))
}
.padding(paddingFor(peer.distance))
.scaleEffect(isPulsing && peer.isActive ? 1.1 : 1.0)
.animation(.easeInOut(duration: 1.5).repeatForever(autoreverses: true), value: isPulsing)
.onAppear {
if peer.isActive {
isPulsing = true
}
}
}
private func sizeFor(_ distance: Double) -> CGFloat {
switch distance {
case 0..<10: return 48
case 10..<50: return 36
default: return 24
}
}
private func paddingFor(_ distance: Double) -> CGFloat {
switch distance {
case 0..<10: return 20
case 10..<50: return 16
default: return 12
}
}
private func distanceText(_ meters: Double) -> String {
if meters < 10 { return "< 10m" }
if meters < 50 { return "~\(Int(meters))m" }
return "far"
}
private func iconFor(_ tech: TechStack) -> String {
switch tech {
case .swift: return "swift"
case .rust: return "gear.badge"
case .python: return "chevron.left.forwardslash.chevron.right"
case .javascript: return "curlybraces"
default: return "person.circle.fill"
}
}
}struct StatusToggleButton: View {
@State private var isExpanded = false
@Namespace private var statusNamespace
var body: some View {
GlassEffectContainer(spacing: 20) {
if isExpanded {
// Expanded state: Status options
HStack(spacing: 16) {
StatusButton(status: .active, color: .green)
.glassEffectID("status-1", in: statusNamespace)
StatusButton(status: .flowState, color: .yellow)
.glassEffectID("status-2", in: statusNamespace)
StatusButton(status: .idle, color: .gray)
.glassEffectID("status-3", in: statusNamespace)
}
.transition(.scale.combined(with: .opacity))
} else {
// Collapsed state: Single toggle
Button {
withAnimation(.bouncy(duration: 0.5)) {
isExpanded.toggle()
}
} label: {
Image(systemName: "circle.fill")
.font(.title2)
.foregroundStyle(.white)
.frame(width: 56, height: 56)
}
.glassEffectID("status-toggle", in: statusNamespace)
}
}
}
}
struct StatusButton: View {
let status: PeerStatus
let color: Color
var body: some View {
Button {
// Set user status
UserDefaults.standard.set(status.rawValue, forKey: "userStatus")
} label: {
Circle()
.fill(color)
.frame(width: 44, height: 44)
}
.glassEffect(.regular.tint(color.opacity(0.7)).interactive())
}
}struct ChatView: View {
let peer: PulsePeer
@StateObject private var chatManager = ChatManager()
@State private var messageText = ""
var body: some View {
ZStack {
// Background
Color.black.opacity(0.3)
.ignoresSafeArea()
VStack(spacing: 0) {
// Header (Glass)
ChatHeader(peer: peer)
.glassEffect(.regular.tint(.blue.opacity(0.6)))
.padding(.horizontal)
.padding(.top, 8)
// Messages (Content, NOT glass)
ScrollView {
LazyVStack(spacing: 12) {
ForEach(chatManager.messages) { message in
MessageBubble(message: message, isFromMe: message.senderId == UserManager.shared.myID)
}
}
.padding()
}
// Input Bar (Glass)
MessageInputBar(text: $messageText, onSend: sendMessage)
.glassEffect(.regular.interactive())
.padding()
}
}
}
private func sendMessage() {
guard !messageText.isEmpty else { return }
chatManager.send(messageText, to: peer)
messageText = ""
}
}
struct ChatHeader: View {
let peer: PulsePeer
var body: some View {
HStack {
VStack(alignment: .leading, spacing: 4) {
Text(peer.handle)
.font(.headline)
.foregroundStyle(.white)
Text(peer.techStack.joined(separator: ", "))
.font(.caption)
.foregroundStyle(.white.opacity(0.8))
}
Spacer()
Image(systemName: "xmark.circle.fill")
.font(.title3)
.foregroundStyle(.white.opacity(0.7))
}
.padding()
}
}
struct MessageBubble: View {
let message: Message
let isFromMe: Bool
var body: some View {
HStack {
if isFromMe { Spacer() }
Text(message.content)
.padding(.horizontal, 16)
.padding(.vertical, 10)
.background(
Capsule()
.fill(isFromMe ? Color.blue : Color.gray.opacity(0.3))
)
.foregroundStyle(.white)
if !isFromMe { Spacer() }
}
}
}
struct MessageInputBar: View {
@Binding var text: String
let onSend: () -> Void
var body: some View {
HStack(spacing: 12) {
TextField("Message", text: $text)
.textFieldStyle(.plain)
.foregroundStyle(.white)
.padding(.horizontal, 16)
.padding(.vertical, 10)
Button(action: onSend) {
Image(systemName: "arrow.up.circle.fill")
.font(.title2)
.foregroundStyle(.white)
}
.disabled(text.isEmpty)
.opacity(text.isEmpty ? 0.5 : 1.0)
}
.padding(8)
}
}import MultipeerConnectivity
import Combine
@MainActor
class MeshManager: NSObject, ObservableObject {
@Published var nearbyPeers: [PulsePeer] = []
@Published var isAdvertising = false
private let serviceType = "pulse-mesh"
private var myPeerID: MCPeerID
private var session: MCSession
private var advertiser: MCNearbyServiceAdvertiser?
private var browser: MCNearbyServiceBrowser?
override init() {
let savedID = UserDefaults.standard.string(forKey: "myPeerID") ?? UUID().uuidString
self.myPeerID = MCPeerID(displayName: savedID)
self.session = MCSession(peer: myPeerID, securityIdentity: nil, encryptionPreference: .required)
super.init()
session.delegate = self
setupNetworking()
}
private func setupNetworking() {
let userInfo = [
"handle": UserDefaults.standard.string(forKey: "handle") ?? "anon",
"status": UserDefaults.standard.integer(forKey: "userStatus"),
"techStack": UserDefaults.standard.stringArray(forKey: "techStack")?.joined(separator: ",") ?? ""
]
advertiser = MCNearbyServiceAdvertiser(
peer: myPeerID,
discoveryInfo: userInfo as [String: String],
serviceType: serviceType
)
advertiser?.delegate = self
browser = MCNearbyServiceBrowser(peer: myPeerID, serviceType: serviceType)
browser?.delegate = self
}
func startAdvertising() {
advertiser?.startAdvertisingPeer()
browser?.startBrowsingForPeers()
isAdvertising = true
}
func stopAdvertising() {
advertiser?.stopAdvertisingPeer()
browser?.stopBrowsingForPeers()
isAdvertising = false
}
func send(_ message: String, to peer: PulsePeer) {
guard let peerConnection = session.connectedPeers.first(where: { $0.displayName == peer.id }) else {
print("Peer not connected")
return
}
let envelope = MessageEnvelope(
id: UUID().uuidString,
senderId: UserManager.shared.myID,
recipientId: peer.id,
content: message,
timestamp: Date()
)
do {
let data = try JSONEncoder().encode(envelope)
try session.send(data, toPeers: [peerConnection], with: .reliable)
} catch {
print("Failed to send message: \(error)")
}
}
}
extension MeshManager: MCNearbyServiceAdvertiserDelegate {
nonisolated func advertiser(_ advertiser: MCNearbyServiceAdvertiser, didReceiveInvitationFromPeer peerID: MCPeerID, withContext context: Data?, invitationHandler: @escaping (Bool, MCSession?) -> Void) {
// Auto-accept all invitations
invitationHandler(true, session)
}
}
extension MeshManager: MCNearbyServiceBrowserDelegate {
nonisolated func browser(_ browser: MCNearbyServiceBrowser, foundPeer peerID: MCPeerID, withDiscoveryInfo info: [String : String]?) {
Task { @MainActor in
let peer = PulsePeer(
id: peerID.displayName,
handle: info?["handle"] ?? "Unknown",
status: PeerStatus(rawValue: Int(info?["status"] ?? "0") ?? 0) ?? .idle,
techStack: (info?["techStack"] ?? "").split(separator: ",").map(String.init),
distance: estimateDistance(for: peerID),
publicKey: Data() // TODO: Exchange keys
)
if !nearbyPeers.contains(where: { $0.id == peer.id }) {
nearbyPeers.append(peer)
}
// Invite to session
browser.invitePeer(peerID, to: session, withContext: nil, timeout: 10)
}
}
nonisolated func browser(_ browser: MCNearbyServiceBrowser, lostPeer peerID: MCPeerID) {
Task { @MainActor in
nearbyPeers.removeAll { $0.id == peerID.displayName }
}
}
}
extension MeshManager: MCSessionDelegate {
nonisolated func session(_ session: MCSession, peer peerID: MCPeerID, didChange state: MCSessionState) {
print("Peer \(peerID.displayName) changed state to \(state.rawValue)")
}
nonisolated func session(_ session: MCSession, didReceive data: Data, fromPeer peerID: MCPeerID) {
guard let envelope = try? JSONDecoder().decode(MessageEnvelope.self, from: data) else {
print("Failed to decode message")
return
}
Task { @MainActor in
NotificationCenter.default.post(name: .didReceiveMessage, object: envelope)
}
}
nonisolated func session(_ session: MCSession, didReceive stream: InputStream, withName streamName: String, fromPeer peerID: MCPeerID) {}
nonisolated func session(_ session: MCSession, didStartReceivingResourceWithName resourceName: String, fromPeer peerID: MCPeerID, with progress: Progress) {}
nonisolated func session(_ session: MCSession, didFinishReceivingResourceWithName resourceName: String, fromPeer peerID: MCPeerID, at localURL: URL?, withError error: Error?) {}
}
// MARK: - Helpers
extension MeshManager {
private func estimateDistance(for peerID: MCPeerID) -> Double {
// iOS 26 doesn't expose RSSI directly via MultipeerConnectivity
// Fallback: Random for demo, or integrate CoreBluetooth for RSSI
return Double.random(in: 5...100)
}
}import Foundation
struct PulsePeer: Identifiable, Codable {
let id: String // DID or UUID
let handle: String // @jesse_codes
var status: PeerStatus
let techStack: [String] // ["Swift", "Rust", "Python"]
var distance: Double // meters
let publicKey: Data
var lastSeen: Date = Date()
var isActive: Bool {
status == .active
}
}
enum PeerStatus: Int, Codable {
case active = 0 // Green light
case flowState = 1 // Yellow (DND but visible)
case idle = 2 // Gray
}
enum TechStack: String, Codable {
case swift, rust, python, javascript, go, java, kotlin
var primary: TechStack { self }
}struct Message: Identifiable, Codable {
let id: String
let senderId: String
let content: String
let timestamp: Date
var isRead: Bool = false
}
struct MessageEnvelope: Codable {
let id: String
let senderId: String
let recipientId: String
let content: String
let timestamp: Date
}
extension Notification.Name {
static let didReceiveMessage = Notification.Name("didReceiveMessage")
}import CryptoKit
import Foundation
struct PulseIdentity: Codable {
let did: String // did:key:z6Mk...
let handle: String // @jesse_codes
let publicKey: Data
private let privateKey: Data // Never share
static func create(handle: String) -> PulseIdentity {
let signingKey = Curve25519.Signing.PrivateKey()
let publicKeyRaw = signingKey.publicKey.rawRepresentation
let did = "did:key:z\(publicKeyRaw.base58EncodedString())"
let identity = PulseIdentity(
did: did,
handle: handle,
publicKey: publicKeyRaw,
privateKey: signingKey.rawRepresentation
)
// Store in Keychain
KeychainManager.shared.save(identity)
return identity
}
func encrypt(_ plaintext: String, for recipient: PulseIdentity) throws -> Data {
let ephemeralKey = Curve25519.KeyAgreement.PrivateKey()
let recipientPublicKey = try Curve25519.KeyAgreement.PublicKey(rawRepresentation: recipient.publicKey)
let sharedSecret = try ephemeralKey.sharedSecretFromKeyAgreement(with: recipientPublicKey)
let symmetricKey = sharedSecret.hkdfDerivedSymmetricKey(
using: SHA256.self,
salt: Data(),
sharedInfo: "pulse-e2e".data(using: .utf8)!,
outputByteCount: 32
)
let plainData = plaintext.data(using: .utf8)!
let sealedBox = try AES.GCM.seal(plainData, using: symmetricKey)
// Combine ephemeral public key + ciphertext
return ephemeralKey.publicKey.rawRepresentation + sealedBox.combined!
}
func decrypt(_ ciphertext: Data) throws -> String {
let ephemeralPublicKeyData = ciphertext.prefix(32)
let sealedBoxData = ciphertext.dropFirst(32)
let ephemeralPublicKey = try Curve25519.KeyAgreement.PublicKey(rawRepresentation: ephemeralPublicKeyData)
let myPrivateKey = try Curve25519.KeyAgreement.PrivateKey(rawRepresentation: privateKey)
let sharedSecret = try myPrivateKey.sharedSecretFromKeyAgreement(with: ephemeralPublicKey)
let symmetricKey = sharedSecret.hkdfDerivedSymmetricKey(
using: SHA256.self,
salt: Data(),
sharedInfo: "pulse-e2e".data(using: .utf8)!,
outputByteCount: 32
)
let sealedBox = try AES.GCM.SealedBox(combined: sealedBoxData)
let plainData = try AES.GCM.open(sealedBox, using: symmetricKey)
return String(data: plainData, encoding: .utf8)!
}
}
// Helper for base58 encoding (DID standard)
extension Data {
func base58EncodedString() -> String {
// Implementation: https://github.com/keefertaylor/Base58Swift
// For brevity, assume this is implemented
return "base58placeholder"
}
}Key Constraints:
- MultipeerConnectivity can run in background with
UIBackgroundModes: ["bluetooth-central", "bluetooth-peripheral"] - iOS 26 is more aggressive about suspending background networking (see iOS 26 Bluetooth issues)
- Battery drain is a critical App Review concern
Best Practices:
// In Info.plist
<key>UIBackgroundModes</key>
<array>
<string>bluetooth-central</string>
<string>bluetooth-peripheral</string>
</array>
// In MeshManager.swift
func enterBackground() {
// Throttle discovery to every 30 seconds instead of continuous
browser?.stopBrowsingForPeers()
Timer.scheduledTimer(withTimeInterval: 30, repeats: true) { [weak self] _ in
self?.browser?.startBrowsingForPeers()
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
self?.browser?.stopBrowsingForPeers()
}
}
}
func enterForeground() {
// Resume continuous discovery
browser?.startBrowsingForPeers()
}Liquid Glass automatically adapts to:
| Setting | Effect |
|---|---|
| Reduce Transparency | Increases frosting for clarity |
| Increase Contrast | Stark colors and borders |
| Reduce Motion | Tones down animations |
| Liquid Glass Opacity Slider | User-controlled transparency (iOS 26.1+) |
No code changes required—iOS 26 handles this system-wide.
PulseApp/
├── App/
│ ├── PulseApp.swift
│ ├── AppDelegate.swift (for background modes)
│ └── SceneDelegate.swift
├── Features/
│ ├── Radar/
│ │ ├── RadarView.swift
│ │ ├── PeerNode.swift
│ │ └── StatusToggleButton.swift
│ ├── Chat/
│ │ ├── ChatView.swift
│ │ ├── MessageBubble.swift
│ │ └── MessageInputBar.swift
│ └── Onboarding/
│ ├── OnboardingView.swift
│ └── HandleSetupView.swift
├── Core/
│ ├── Networking/
│ │ ├── MeshManager.swift
│ │ ├── ChatManager.swift
│ │ └── MessageRouter.swift
│ ├── Identity/
│ │ ├── PulseIdentity.swift
│ │ ├── KeychainManager.swift
│ │ └── UserManager.swift
│ ├── Models/
│ │ ├── PulsePeer.swift
│ │ ├── Message.swift
│ │ └── ChatSession.swift
│ └── Extensions/
│ ├── Color+Pulse.swift
│ └── View+Glass.swift
└── Resources/
├── Assets.xcassets
└── Info.plist
| Week | Task | Deliverable |
|---|---|---|
| 1-2 | Networking Core | MultipeerConnectivity wrapper, peer discovery, status broadcast |
| 3 | Identity & Crypto | DID generation, E2E encryption with CryptoKit, Keychain storage |
| 4-5 | Liquid Glass UI | RadarView with GlassEffectContainer, PeerNode morphing, StatusToggleButton |
| 6 | Chat UI | ChatView, MessageBubble, MessageInputBar with glass effects |
| 7 | Polish | Background modes, battery optimization, accessibility testing |
| 8 | Launch Prep | TestFlight beta, App Store submission, landing page |
- Apply
.glassEffect()to navigation elements only (toolbars, tabs, floating buttons) - Use
GlassEffectContainerfor grouping related glass elements - Tint glass sparingly (primary actions only)
- Let content breathe—don't glass everything
- Test with Reduce Transparency enabled
- Apply glass to list rows or main content
- Over-tint (makes UI look garish)
- Ignore accessibility settings
- Use glass without proper padding
- Create glass-on-glass (sampling issue)
Phase 1: Seed (0 → 100K) — Q1-Q2 2025
- Conference blitz (WWDC, Google I/O, local meetups)
- Cursor/VS Code extension integration
- Tech Twitter influencer seeding
- Open source the PulseProtocol
Phase 2: Ignite (100K → 1M) — Q3-Q4 2025
- University partnerships (CS departments, hackathons)
- Transit partnerships ("Powered by Pulse" on trains)
- Referral mechanics
- GitHub badge integration
Phase 3: Scale (1M → 10M) — 2026
- International expansion (India, Brazil, EU)
- Android launch with cross-platform mesh
- Enterprise tier (office floor analytics)
- AR integration (Vision Pro support)
| Tier | Price | Features |
|---|---|---|
| Free | $0 | Proximity discovery, 1:1 chat, 24h ephemeral messages |
| Pro | $5/mo | Persistent history, custom status, priority discovery, code highlighting |
| Teams | $10/user/mo | Office mesh analytics, team channels, SSO, compliance exports |
Revenue Projection:
- Year 2: 10M users × 5% conversion × $60/year = $30M ARR
- Year 2: 100K team seats × $120/year = $12M ARR
- Total: ~$42M ARR
- Validate naming — Is "Pulse" the right name? (Alternatives: Mesh, Flare, Ping, Nearby)
- Technical spike — Build RadarView prototype with GlassEffectContainer
- Design system — Figma mockups for all glass states
- Beta testing — Find 50 Cursor/Claude users for TestFlight
- Apple introduces Liquid Glass (Newsroom)
- Build a SwiftUI app with the new design - WWDC25
- GlassEffectContainer Documentation
- Understanding GlassEffectContainer in iOS 26 - DEV
- Liquid Glass iOS 26 Tutorial - InspiringApps
- Designing custom UI with Liquid Glass - Donny Wals
- GitHub - LiquidGlassReference by conorluddy
- GitHub - LiquidGlassSwiftUI by mertozseven
- MultipeerConnectivity Documentation
- iOS 26.3 Proximity Pairing - MacRumors
- iOS 26 Bluetooth Issues - MacObserver
- Dezeen - Apple unveils iOS 26 with Liquid Glass
- TechCrunch - iOS 26 now available
- TechCrunch - iOS 26.2 Liquid Glass rollback
Ready to build? This architecture is production-ready for iOS 26 and leverages Liquid Glass natively, not as decoration but as functional design.