-
-
Notifications
You must be signed in to change notification settings - Fork 1
Architecture
URSA is a thin, reactive client. A network layer parses Uptime Kuma's wire formats into clean domain models, repositories expose those as flows, and Compose renders them. Nothing is stored server-side, and the app holds as little as possible.
Uptime Kuma --Socket.IO--> core.network (adapter: parse/normalize wire -> domain)
--REST (Ktor)-> |
v
data.repository (StateFlow<DomainModel>, single source of truth)
v
ui.* (Compose, collectAsStateWithLifecycle)
core.storage (DataStore + Tink) --credentials/JWT--> core.network (login/loginByToken)
distributor (ntfy) --push--> core.push (UnifiedPush service) --> system notification
- core.network - one Socket.IO connection per active server (auth, live monitor status, heartbeats, certificates, pause/resume) plus a Ktor REST client for public status pages. All of Kuma's wire quirks are isolated here. See Network and Protocol.
- core.storage - DataStore Preferences with Tink AES-256-GCM encryption at rest; the master key lives in the Android Keystore. Only the session token is persisted.
- core.push - the UnifiedPush receive path and notification rendering, including the down-to-up transition tracking that reports how long a monitor was down. See Push Internals.
- core.work - WorkManager jobs and notifiers that run without the UI: the daily TLS-expiry reminder and the periodic slow-response check (which briefly connects to each saved server, reads the latest heartbeats, and alerts on monitors that are up but over a response-time threshold). The threshold evaluator is a pure, unit-tested object.
- data.model - the domain types (monitors, heartbeats, certificates, status pages).
- data.repository - owns the active network client and exposes StateFlows as the single source of truth.
-
ui - one
UrsaViewModel; a bottom-navigation shell (MainShell) with Monitors, Notifications, and Settings tabs, and a top-levelwheninUrsaAppthat picks the screen from state (no navigation library for the current, small screen set). System back is handled per screen. Service favicons are fetched best-effort byFaviconCache.
The Wear OS companion is a separate Gradle module (:wear) that shares the app's
applicationId. It is deliberately free of Google Play Services: instead of the
proprietary Wearable Data Layer, the watch tile (StatusTileService, built with
androidx tiles + protolayout) polls a public Kuma status page directly, and Wear OS
routes that network call through the phone. A small config Activity stores the
status-page URL on the watch. Notification and action bridging to the watch is handled
by the Wear OS system with no code. See Wear OS.
- Verify before you model. Uptime Kuma's socket API is internal and unstable, so every wire shape URSA relies on was captured from a live instance.
- One adapter owns the quirks. Positional socket events, snake_case vs camelCase fields, and double-encoded JSON are all handled in the network layer and nowhere else.
- State-driven UI. Routing is a function of ViewModel state. Reconnects keep the list visible with a status banner rather than bouncing to the login screen.
-
Kuma-matched theme. The Material 3 theme (
ui/theme) uses Uptime Kuma's own palette (from itsvars.scss) for light and dark mode by default, with an opt-in Material You mode. Status colors always stay Kuma-semantic (they come fromStatusUi, not the scheme), so up/down/pending/maintenance read the same regardless of theme.
For the code map and deeper notes, see the docs/ folder in the repository.