Package-level declarations
Types
Connectivity as the decision core sees it. CaptivePortal is "linked but walled off".
The heart of the offline-first read path: a PURE function from (data snapshot, connectivity) to the single ScreenState a screen renders. No coroutines, no I/O — exhaustively unit-testable.
Coarse error classification the DecisionEngine routes on. The caller supplies the classifier.
Whether a screen reads from cache, the network, or both — the read strategy a caller hands to its store stream. Kept as a sealed interface so new variants can be added without breaking the FetchPolicy.NetworkWithCache access pattern.
How stale cached data is, on a time axis only — network state is deliberately NOT an input (that's a separate "am I online" concern). Drives a per-card freshness indicator ("Updated 2 min ago" → "Refresh now" → "Retry").
The write-side counterpart to ScreenState: the state of a single mutation (submit/update/delete) a screen renders while it runs. One exhaustive type instead of isSubmitting + submitError flags.
The one UI state an offline-first screen renders. Collapses the usual scatter of isLoading / isFromCache / isRefreshing / error / networkStatus flags into a single exhaustive type a when can render. Produced by DecisionEngine.decide from a StoreData snapshot + Connectivity.
The whole state of a screen that both DISPLAYS data and MUTATES it — the read ScreenState and the write MutationState in one snapshot, plus the offline-sync status (outboxPending count + isSyncing) so a "3 pending · syncing…" banner needs no extra plumbing.
Functions
Pure staleness computation. First match wins:
Sibling stream: the per-card FreshnessBand over time, computed purely from (fetchedAt, ttl, lastError) — network state is deliberately NOT an input (a separate connectivity banner owns that). Run alongside screenStateStream to drive a freshness indicator that updates independently of the content decision.
The reactive read path: turns a data Flow + a connectivity Flow into a Flow<ScreenState<T>> by running DecisionEngine.decide on each emission. This is the live form of the pure decision core — a ViewModel collects this and exposes it as UI state.
Combines the read state, the write state, and the offline-sync signals into one stream. Only screen is required; the rest default to a single benign emission, so the combined stream emits as soon as the screen does. The app wires outboxPending/isSyncing to its :offline-outbox queue if it uses one.
The offline-first write path: runs mutation and emits MutationState (Submitting → Success/Failed). On a failure classified retryable by isRetryable (offline / 5xx / timeout), it hands payload to enqueueOffline so the write is durably queued for later replay — then reports Failed(queuedOffline = true), letting the UI say "saved, will sync". Non-retryable failures (a 4xx the server rejected) surface as Failed(queuedOffline = false).