Phase 2 — Auth + Servers
Phase 2 ships the full auth + server stack. Uptime Pocket can now persist servers across launches, securely store Kuma credentials, and keep a live socket connection to a Kuma instance with the live data flowing into the Monitors tab.
Persistence
- SQLite via
expo-sqlitefor server metadata (id, name, url, auth kind, notification mode, kuma version, connection state, timestamps). Hand-rolled SQL behind a thinserversRepo— the only module that touches theserverstable directly. - Idempotent migration runner (
src/data/db/migrate.ts) readsschema_versionand applies only newer migrations on launch. expo-secure-storefor credentials, namespaced by server id (uptime-pocket.cred.<serverId>). zod-validated on read. Empty/malformed values are rejected on write.
Security model
Server.auth(full strategy) is gone — server records only carryauthKind: 'bearer' | 'password'.- Secrets never enter the in-memory store. To connect to a Kuma instance, the connection manager calls
loadCredentials(serverId)which reads from SecureStore. - Type-level enforcement: the
Servershape has no field that can hold a secret.
Connection management
KumaConnectionManagerclass owns the socket + REST lifecycle:connect / disconnect / pause / resume / recheck / fetchHeartbeats.useKumaConnection()hook keeps the manager in sync withactiveServerIdand cleans up on unmount/server change.- Bridges socket events into the Zustand
monitorsandserversstores. - Live
monitorsstore with per-server maps forstatus,error,monitors, andincidents. - Version detection via
GET /api/statuson connect. Outdated Kuma (< 2.0.0) is surfaced in the server detail screen.
Onboarding flow
- New
/welcomescreen — first-launch CTA with 4 feature highlights and an "Add your Kuma server" button. OnboardingGateinapp/_layout.tsxwatchesservers.lengthand routes between/welcome(empty) and/(populated). Re-activates if the last server is removed.
Add Server flow
- Rewired with zod validation (URL, name length, credentials presence).
- Refactored from 9
useStatecalls to a singleuseReducer. - Persists to SQLite + SecureStore, probes
/api/statusfor version, markshasOnboarded = trueon success.
Server detail screen (/servers/[id])
- Live connection status banner (idle / connecting / connected / reconnecting / error).
- Outdated-Kuma warning when version < 2.0.0.
- Connection metadata (url, auth kind, version, timestamps).
- Notification mode section.
- Delete confirm dialog (requires typing the server name) — removes from SQLite, SecureStore, and in-memory state.
- "Open in Kuma" deep-link button.
Servers tab
- Real list of persisted servers (no more sample data).
- Live
connectionStatusprop onServerCard— pending color while connecting. - Tap → detail screen. Long-press → set as active.
Monitors tab
- Subscribes to
useMonitorsfor the active server. - Connection status banner ("Connecting…" / "Error: ").
- Filter chips (All / Up / Down) with proper filtering.
- Featured
<MonitorCard>+<MonitorRow>list. - Empty state distinguishes "filtered empty" from "waiting for monitors".
Testing
Jest infrastructure landed in this release:
jest.config.js(jest-expo preset) +jest.setup.ts(in-memory stubs forexpo-secure-storeandexpo-sqlite).- 13 unit tests, all passing:
credentials.test.ts(9 tests): bearer round-trip, password round-trip, namespacing, delete, malformed data, availability.manager.test.ts(4 tests): connect with stored creds, error when no creds, unknown id, disconnect clears state.
Quality gates
All green on the v0.3.0 commit:
npm run typecheck— 0 errorsnpm run lint— cleannpm run lint:doctor— No issues found (69 files scanned)npm test— 13/13 passingnpx expo prebuild --platform ios— succeeds
Known caveats
- Web bundling is blocked by an
expo-sqlitewasm bundler issue (pre-existing, not in our code). iOS and Android builds are unaffected. We don't ship web in this release. npm auditflags 12 pre-existing vulnerabilities (mostlydrizzle-orm,uuid, and Expo transitive deps). None are reachable from our code. We'll address in a follow-up.