Files
amethyst/commons
nrobi144andClaude Opus 4.7 a07c9ac7cc feat(commons,desktop): wire latency tracker into RelayHealthStore + persistence
Phase 2 of relay-latency-health: hook the Phase 1 tracker into the existing
RelayHealthStore lifecycle and persist its rings via the existing
PreferencesRelayHealthPersistence so samples survive restarts.

commonMain:
  - RelayLatencyProvider: small interface so the store can drive a tracker
    that lives in jvmAndroidMain (the impl needs ConcurrentHashMap).
  - RelayHealthSnapshot: optional `latencySamples` field. Default empty;
    older saved snapshots load cleanly without it.
  - RelayHealthStore now takes optional `latencyTracker` / `nip11Provider`
    / `authProvider` constructor params:
      * exposes `latencySnapshots: StateFlow<ImmutableMap<Url, RelayLatencySnapshot>>`
        — MutableStateFlow updated inside the existing 60 s reclassify tick
        (one timer, not two — the tracker is scope-less and gets
        `sweep(now)` called from reclassify).
      * exposes `slowRelays: StateFlow<ImmutableMap<Url, SlowReason>>` —
        derived via `_latencySnapshots.map(classifySlowRelays).stateIn(
        scope, SharingStarted.Eagerly, persistentMapOf())`. The classifier
        reads `nip11Provider()` / `authProvider` live, so paid/auth-only
        relays only join the cohort once their auth completes.
      * `init {}` restores persisted samples into the tracker; the
        existing `schedulePersist()` now bundles `tracker.samplesForPersistence()`
        into the saved snapshot via a new private `snapshotForPersist()`
        helper. The same helper feeds the final flush in `close()`.
    No new dispatcher / scope / timer — everything piggybacks on the
    existing infra (single SupervisorJob, 5 s persist debounce, 60 s tick).

jvmAndroidMain:
  - RelayLatencyTracker now implements RelayLatencyProvider. Overrides drop
    the inline `System.currentTimeMillis()` default; callers from commonMain
    pass `TimeUtils.nowMillis()` explicitly.

desktopApp (jvmMain):
  - PreferencesRelayHealthPersistence persists per-relay latency rings in
    separate keys (`lat_<account-prefix>_<sha256(url)[..16]>`) so the 8 KB
    Preferences ceiling on the main `health_<account>` key isn't blown by a
    user with many relays. Each key holds one relay's four metric rings as
    `wss://relay.url\tok:csv|eose:csv|fr:csv|ping:csv`. On save, keys for
    relays no longer in the snapshot get removed so the prefs node doesn't
    grow unboundedly across account churn.

Notes:
  - Persistence still uses the existing 5 s debounce path. The deepened plan
    called for 30 s for `lat_*` keys; deferring that micro-optimization
    until we observe write thrash in practice. The cap on writes is
    one-rewrite-per-5s-of-activity which matches what the existing snooze
    persistence already does, so latency adds zero new flush events.
  - Tracker is wired only when a `RelayLatencyProvider` is passed to the
    store. Existing tests / Android continue to compile and run with
    latency unconfigured — `latencySnapshots` stays empty and `slowRelays`
    derives to empty. Desktop wiring lands in Phase 3.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-18 12:11:25 +03:00
..