Skip to content

Recording Device Preference

Metadata

  • System type: library

System Intent

  • What this is: A singleton in-memory store and persistence layer for the user's recording device preference ("glasses", "glasses-audio", or "phone"). It exposes React hooks for reading the preference reactively, imperative getters/setters for non-React consumers, and a hydration function that runs once on app launch to restore the saved preference from AsyncStorage. On hydration, if a glasses-mode preference is stored but no glasses device is currently connected, the preference is overridden to "phone" in-memory only — the stored value is left intact so the glasses preference is restored on the next launch when glasses are connected.

Mermaid Diagram

flowchart TD
  AppInit["AppInit (component)"] -->|"calls on init"| Hydrate["hydrateRecordingDevicePreference()"]
  Hydrate -->|"getItem(STORAGE_KEY)"| AsyncStorage["AsyncStorage"]
  AsyncStorage -->|"storedDevice | null"| Hydrate
  Hydrate -->|"isAnyDeviceAvailable()"| WearablesModule["WearablesModule (native bridge)"]
  WearablesModule -->|"isConnected: bool"| Hydrate
  Hydrate -->|"setSelectedRecordingDeviceValue(device)"| Store["selectedRecordingDevice (in-memory)"]
  Store -->|"emit()"| Listeners["subscribed React components"]

  SettingsScreen["Settings Screen"] -->|"setSelectedRecordingDevice(device)"| Store
  SettingsScreen -->|"setItem(STORAGE_KEY, device)"| AsyncStorage

Flows

Global Types

RecordingDevice = "glasses" | "glasses-audio" | "phone"

StandardError {
  message: string (human-readable description of what went wrong)
}

Flow: hydrateRecordingDevicePreference

  • Core files: main/app/lib/recording-device-preference.ts
  • Test files: main/app/__tests__/recording-device-preference.test.ts

Types

HydrateInput {
  // no arguments — reads AsyncStorage and WearablesModule internally
}

HydrateOutput {
  // void — side-effects only: updates selectedRecordingDevice in-memory and emits to listeners
}

Paths

path input output path-type notes
hydrate.glasses-connected-saved-glasses stored = "glasses", isAnyDeviceAvailable = true selectedRecordingDevice = "glasses" happy path Glasses connected; stored glasses preference honoured
hydrate.glasses-connected-saved-glasses-audio stored = "glasses-audio", isAnyDeviceAvailable = true selectedRecordingDevice = "glasses-audio" happy path Glasses connected; stored glasses-audio preference honoured
hydrate.glasses-connected-saved-phone stored = "phone", isAnyDeviceAvailable = true selectedRecordingDevice = "phone" happy path User explicitly chose phone; kept even when glasses are connected
hydrate.glasses-not-connected-saved-glasses stored = "glasses", isAnyDeviceAvailable = false selectedRecordingDevice = "phone" happy path Glasses not connected; override to phone in-memory only; AsyncStorage not updated
hydrate.glasses-not-connected-saved-glasses-audio stored = "glasses-audio", isAnyDeviceAvailable = false selectedRecordingDevice = "phone" happy path Glasses not connected; override to phone in-memory only; AsyncStorage not updated
hydrate.glasses-not-connected-saved-phone stored = "phone", isAnyDeviceAvailable = false selectedRecordingDevice = "phone" happy path Already phone; no change
hydrate.no-saved-preference-glasses-connected stored = null, isAnyDeviceAvailable = true selectedRecordingDevice = "glasses" happy path No stored preference; default is glasses when connected
hydrate.no-saved-preference-glasses-not-connected stored = null, isAnyDeviceAvailable = false selectedRecordingDevice = "phone" happy path No stored preference; default is phone when not connected
hydrate.storage-read-error AsyncStorage throws, isAnyDeviceAvailable = false selectedRecordingDevice = "phone" error Error logged; falls through to connection-aware default
hydrate.storage-read-error-glasses-connected AsyncStorage throws, isAnyDeviceAvailable = true selectedRecordingDevice = "glasses" error Error logged; falls through to connection-aware default

Pseudocode

hydrateRecordingDevicePreference():
  storedDevice = await AsyncStorage.getItem(STORAGE_KEY)

  if isRecordingDevice(storedDevice):
    isConnected = isGlassesDeviceAvailable()

    // If stored preference is a glasses mode but glasses are not connected,
    // override to phone for this session only. AsyncStorage is NOT updated —
    // the user's stored preference is preserved for the next launch.
    if (storedDevice === "glasses" || storedDevice === "glasses-audio") && !isConnected:
      log recording_device_glasses_not_connected_override { storedDevice, override: "phone" }
      setSelectedRecordingDeviceValue("phone")
      return

    setSelectedRecordingDeviceValue(storedDevice)
    return

  // No stored preference or read error — fall through to connection-aware default
  fallbackDevice = getDefaultRecordingDevice()   // calls isAnyDeviceAvailable()
  if selectedRecordingDevice !== fallbackDevice:
    selectedRecordingDevice = fallbackDevice
    emit()

Flow: setSelectedRecordingDevice

  • Core files: main/app/lib/recording-device-preference.ts

Types

SetDeviceInput {
  device: RecordingDevice
}

SetDeviceOutput {
  // void — updates in-memory store, emits to listeners, persists to AsyncStorage
}

Paths

path input output path-type notes
setDevice.success device: RecordingDevice in-memory updated, listeners notified, AsyncStorage updated happy path Called from Settings when user changes preference
setDevice.write-error AsyncStorage.setItem throws in-memory still updated; error logged; no throw error Preference change takes effect for the session even if persistence fails

Flow: getSelectedRecordingDevice / useSelectedRecordingDevice

  • Core files: main/app/lib/recording-device-preference.ts

Types

GetDeviceOutput {
  RecordingDevice   // current in-memory value
}

Paths

path input output path-type notes
getDevice.sync void current selectedRecordingDevice happy path Imperative getter; safe to call from non-React code
getDevice.hook void (React component) current selectedRecordingDevice, re-renders on change happy path Uses useSyncExternalStore; subscribes via subscribeRecordingDevicePreference

Logs

Source Location
App (JS) createFlowLogger("recording-device")

Log steps:

step when
recording_device_glasses_not_connected_override Glasses-mode preference stored but glasses unavailable at hydration; in-memory override to phone. Payload: { storedDevice, override: "phone" }
recording_device_preference_read_failed AsyncStorage.getItem throws during hydration. Payload: { error }
recording_device_preference_write_failed AsyncStorage.setItem throws during setSelectedRecordingDevice. Payload: { error, device }

Deployment

  • Mechanism: local only (shipped as part of the React Native app bundle)
  • Deploy command:
    cd main/app && npx expo start
    
  • Notes: JS-only module; no native rebuild required. The in-memory override on hydration is intentionally not persisted — AsyncStorage retains the user's glasses preference so it is restored automatically on the next launch when glasses are connected.