Files
meatbag/research/sdui_and_haptics_research.md
2026-06-30 15:12:58 -06:00

4.1 KiB

Meatbag Research: SDUI Inflation, Wear OS Haptics, and Server Push

This document summarizes architectural research for the Meatbag platform, focusing on performance, latency, and platform-specific haptic patterns.


1. Server-Driven UI (SDUI) in Compose Multiplatform

The Reflection-Free Pattern

To meet performance constraints and avoid runtime performance degradation on lightweight clients (such as Wear OS watches), Meatbag implements a strictly typed, reflection-free Component Registry.

@Composable
fun ComponentRegistry(primitive: UiPrimitive, onAction: (ApprovalResponse) -> Unit) {
    when (primitive) {
        is ApprovalRequest -> ApprovalRequestCard(primitive, onAction)
        is ActionList -> ActionListContainer(primitive, onAction)
        is TextInput -> TextInputField(primitive, onAction)
    }
}
  • Advantage: Compile-time safety. Polymorphic serialization (via kotlinx.serialization with custom class discriminators like "type") ensures payloads map directly to Kotlin data types without dynamic inspection.
  • State Management: Adheres to Unidirectional Data Flow (UDF). Primitives are read-only configuration blueprints; interactive widgets emit state updates upward through explicit event callbacks (onAction), keeping clients entirely stateless.

2. Wear OS Haptic Feedback APIs

To minimize human response times, we utilize precise physical notifications based on the resolved AttentionTier.

Android / Wear OS API Mapping

For triggering haptics on physical Wear OS smartwatches, the system integrates with Android's hardware Vibrator API.

1. Predefined Effects (Android SDK 29+)

Modern Wear OS devices support the VibrationEffect API, which yields crisp, hardware-optimized clicks rather than fuzzy motor rumbling.

  • AttentionTier.SILENT_HAPTIC: Double short pulses to signal prompt urgency without making sound.
    val vibrator = context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
    val effect = VibrationEffect.startComposition()
        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1.0f)
        .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1.0f, 150) // 150ms delay
        .compose()
    vibrator.vibrate(effect)
    
  • AttentionTier.BACKGROUND_NOTIFICATION: A light tick effect.
    vibrator.vibrate(VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK))
    
  • AttentionTier.OVERRIDE_SCREEN: Repetitive pulsing alert composition.
    val timings = longArrayOf(0, 200, 100, 200, 100, 500) // delay, vibe, pause, vibe...
    val amplitudes = intArrayOf(0, 255, 0, 255, 0, 255)
    vibrator.vibrate(VibrationEffect.createWaveform(timings, amplitudes, -1))
    

2. Legacy Fallbacks (Android SDK < 29)

  • For older Wear OS versions, fallback to deprecated pattern arrays:
    vibrator.vibrate(longArrayOf(0, 80, 100, 80), -1)
    

3. Server Push Architecture: WebSockets vs. SSE

Smartwatches are subject to battery constraints and intermittent connectivity. The push transport layer must be responsive yet lightweight.

Feature WebSockets Server-Sent Events (SSE)
Protocol Bi-directional, custom framing Uni-directional (Server -> Client), HTTP/1.1 or HTTP/2
Connection Overhead Low (post-handshake) Extremely low (standard HTTP connection)
Battery Impact Higher (constant connection maintenance) Lower (leverages HTTP client connection pooling)
Firewall Traversal Can be blocked by strict proxy rules Standard HTTP, easily passes through proxies
Meatbag MVP Selection WebSockets (Allows immediate client confirmation frames and bi-directional pings) Candidate for mobile clients (Fallback for phone targets via standard APNS / FCM)
  1. Wear OS Smartwatches: Utilize persistent WebSockets with optimized ping-pong intervals (e.g. 30 seconds) when the app is active in the foreground.
  2. Mobile Phones (iOS & Android): Fall back to native push notification systems (FCM/APNS) to wake up the client, which then opens a short-lived WebSocket or POST connection to render the SDUI primitive.