Coverage Summary for Class: RemoteConfigMirror (com.georgeci.moneysurfer.appconfig)

Class Method, % Branch, % Line, % Instruction, %
RemoteConfigMirror 100% (1/1) 100% (1/1) 100% (1/1)
RemoteConfigMirror$DefaultImpls 0% (0/1) 0% (0/1) 0% (0/2)
Total 50% (1/2) 50% (1/2) 33.3% (1/3)


 package com.georgeci.moneysurfer.appconfig
 
 import kotlinx.coroutines.flow.Flow
 
 /**
  * Persistent copy of the last accepted `appConfig/flags` payload, so the RemoteGlobal layer resolves
  * before — and without — a network round trip.
  *
  * Two properties depend on it. A cold start reads the mirror during `hydrate()`, long before any
  * fetch could return, so `Config.snapshot` is correct on the first frame rather than a few hundred
  * milliseconds later. And a device that never reaches Firestore again keeps resolving the last
  * values the server did send, instead of silently falling through to Build.
  *
  * Declared here rather than in `app-config/remote` because it is storage, not Firestore: the
  * implementation lives in `data-local` next to the other DataStore-backed layers, and `api` must
  * stay SDK-free so both sides can see the contract.
  *
  * Values are raw encoded strings, exactly as `ConfigCodec` expects them — the mirror never decodes,
  * so a value this build cannot read stays visible to the debug panel instead of being dropped by the
  * writer.
  */
 interface RemoteConfigMirror {
 
     /** Reads the warmed in-memory copy. `null` means the mirror does not hold this name. */
     fun raw(name: String): String?
 
     /**
      * Emits once with the current state, then again whenever the stored values actually change.
      *
      * Deliberately not "after every [replaceAll]": the backing store skips a write whose result
      * equals what it already holds, and a refresh re-sends the same flags nearly every time, so an
      * unchanged payload produces no emission. Anything that needs to know a refresh *happened* —
      * a last-checked timestamp, a staleness watchdog — cannot be built on this flow.
      */
     val changes: Flow<Unit>
 
     /** Warms the in-memory copy from storage. Idempotent. Must not fail — see [isDegraded]. */
     suspend fun hydrate()
 
     /** `true` while the backing store cannot be read; cleared by the next successful read. */
     val isDegraded: Boolean get() = false
 
     /**
      * Replaces the whole mirror with [values] — deliberately not a merge.
      *
      * Retraction has to propagate: a key the owner deletes from `appConfig/flags` must stop being
      * served, and a merge would pin the last value it ever had on every device forever. The same
      * reasoning makes an absent document clear the mirror rather than preserve it.
      */
     suspend fun replaceAll(values: Map<String, String>)
 }