Files
amethyst/desktopApp/plans/2026-07-01-wot-score-manual-testing-sheet.md
T
nrobi144 9284e12e86 feat(desktop): Web-of-Trust score badges + amy wot verbs
Adds a friends-of-friends trust score on every user avatar in Desktop
feeds, threads, profile headers, and repost overlays. For pubkey X the
score is the count of accounts in the active user's follow set who also
follow X — Gossip / Snort convention. v1 is display-only; no threshold
filtering.

Data flow
- `commons/wot/WoTService` — sparse `SnapshotStateMap<HexKey, Int>` +
  reverse index + per-follower snapshot for diff-based updates.
  Single-writer coroutine (Channel<Op> → `Snapshot.withMutableSnapshot`)
  serializes all mutations. Cap at 5000 follows/event blocks DoS via
  hostile kind-3s. Guardrail at 2000 follows/account skips WoT for
  mega-accounts.
- `DesktopIAccount.wotService` — per-account instance, matches
  `Kind3FollowListState` / `BookmarkListState` conventions.
- `Main.kt` binds `localCache.accountPubkey`, collects
  `localCache.contactListEvents` → `applyKind3`, collects
  `localCache.followedUsers` → `onFollowSetChange` +
  `subscriptionsCoordinator.loadKind3Batched(...)` with
  `onEose = markReadyOnce`. 2 s fallback timeout guarantees badge
  visibility even if index relays never EOSE.
- `FeedMetadataCoordinator.loadKind3Batched(pubkeys, onEose)` — chunks
  authors into ≤100 per Filter within one subscription. Matches
  nostr-rs-relay defaults.

UI
- `commons/ui/components/UserAvatar` gets an optional
  `badge: @Composable BoxScope.() -> Unit`. Android call sites pass
  null (no compile-time coupling to Desktop-only tooltip APIs).
- `desktopApp/.../ui/note/WoTBadge` — Material3 `TooltipBox` +
  `PlainTooltip` (multiplatform-ready, keyboard/screen-reader a11y).
  `rememberTooltipState(isPersistent = true)` fixes the
  vanish-too-fast desktop default.
- `desktopApp/.../ui/note/WoTBadgedAvatar` — drop-in replacement for
  `UserAvatar` that overlays the badge when
  `LocalWoTService != null && LocalWoTReady && pubkey !in LocalSpamExemptKeys`.
  Score read is a plain `service.scores[userHex] ?: 0` — snapshot
  system tracks per-key, so avatars only recompose when their own
  score changes.
- Call-site migration at 4 v1 surfaces: NoteCard header (covers feed /
  thread / bookmarks / search / QuotedNoteEmbed via NoteCard),
  FeedNoteCard repost header (2 avatars), UserProfileScreen header
  (2 sizes).

Amy verbs
- `amy wot get <pubkey|npub> [--json]` — hydrates a WoTService from the
  local FsEventStore, prints score for target pubkey.
- `amy wot list [--threshold N] [--limit K] [--json]` — sorted score
  list.
- `amy wot sync [--timeout N]` — batch-fetches kind-3 for the active
  follow set from outbox/inbox relays, persists to the event store.

Tests + docs
- 14 unit tests: `WoTServiceTest` covers happy path, sparse map,
  self/follower exclusion, kind-3 churn diff, guardrail, event cap,
  ready gate, clear.
- Manual testing sheet with 17 scenarios at
  `desktopApp/plans/2026-07-01-wot-score-manual-testing-sheet.md`.
- Plan at `docs/plans/2026-07-01-feat-desktop-wot-score-plan.md`.

Prerequisite `DesktopLocalCache.consumeContactList` scoping fix landed
as a separate commit.
2026-07-02 18:03:47 +03:00

4.7 KiB

Manual testing sheet — Desktop Web-of-Trust Score Badges

Plan: docs/plans/2026-07-01-feat-desktop-wot-score-plan.md

Run with ./gradlew :desktopApp:run. Sign in with an account that has a follow list (WoT is meaningless without one).

Pre-flight

  • Followed authors' kind-3 events need to be reachable via the configured index relays. On first launch, badges may take a couple of seconds to appear while the batch REQ completes.
  • Hashtag-spam PR (#3431) providers must be live — WoTBadgedAvatar reads LocalSpamExemptKeys for the self+follows hide predicate.

Scenarios

# Test Expected
T1 Cold start with ≥50 follows. Log in, watch avatars for 2 s. Batch REQ fires once; badges appear on some strangers within ~2 s.
T2 No badge on self. Open own profile in a Profile column. Own header avatar has no chip regardless of any follower kind-3s.
T3 No badge on followed authors. Any note by a person you follow. Card renders with clean avatar, no chip.
T4 Badge on a stranger who's followed by 2 of your follows. Find such a note (or contrive one). Small chip showing "2" bottom-right of avatar.
T5 Tooltip on hover. Hover the badge for ~1 s. Plain tooltip: "N of the people you follow follow this person".
T6 99+ overflow. Simulate a pubkey scored 200+ (e.g. a well-connected celebrity in your graph). Badge shows 99+.
T7 No layout shift. Scroll a busy column while badges arrive mid-scroll. Avatar sizes don't jump — badge overlays the existing avatar bounds.
T8 Kind-3 churn. Watch a followed author's kind-3 update mid-session (or manually publish one from another client). Their contribution to affected pubkeys' scores diffs correctly; no double-counting.
T9 Follow someone new. Follow a fresh pubkey via the UI. Their kind-3 is fetched via a subsequent loadKind3Batched; anyone they follow gets their score incremented once their kind-3 arrives.
T10 Unfollow someone. Unfollow an existing follow via the UI. Every pubkey they were crediting has their score decremented; some may drop to 0 and lose their badge.
T11 Guardrail (mega-follow account). Log in with an account following ≥ 2 000 pubkeys. LocalWoTReady becomes true immediately; no badges anywhere; no batch REQ fires.
T12 Empty graph. Log in with an account following 0 people. LocalWoTReady becomes true after the 2 s fallback timeout; no badges.
T13 consumeContactList prerequisite fix. From another client, watch a kind-3 event from a follower arrive while you're logged in. Your own _followedUsers (used by feed filters, sidebar) stays unchanged. Verify by opening a filtered feed and confirming it hasn't broken.
T14 Account switch. Switch to a different logged-in account. Old scores gone; new account's follow set drives new WoT map. No leaked badges.
T15 amy wot get. ./gradlew :cli:installDist && cli/build/install/cli/bin/amy wot get <npub> (against an account with kind-3 events in ~/.amy/shared/events-store/). Output: pubkey=<hex> score=<n> or JSON with --json.
T16 amy wot list. amy wot list --threshold 3 --limit 20 --json. JSON of top-20 pubkeys with score ≥ 3, sorted desc.
T17 amy wot sync. amy wot sync (with follows in local store). Runs a chunked kind-3 REQ against outbox relays, stores fresh events. Subsequent amy wot get reflects the update.

Known v1 limitations

  • Notification-tab avatars are not badged. Notifications use a custom 56 dp compact card that doesn't route through NoteCard / UserAvatar. Deferred to v2.
  • Search-result "person" cards (via UserSearchCard) are not badged in v1 — they use a different composable.
  • Cross-column reveal state doesn't matter (WoT is stateless per render; no reveal to persist).
  • No filter/threshold gating of feeds or notifications v1 — badges are display-only. v2 will add the threshold Setting.
  • amy wot sync uses outbox/inbox relays, not index relays. The Desktop app uses indexRelays. If the two disagree, results may differ slightly. Follow-up ticket: unified indexRelays for both.

Sign-off

  • T1 Cold start
  • T2 No self badge
  • T3 No badge on follows
  • T4 Stranger scored 2
  • T5 Tooltip
  • T6 99+ overflow
  • T7 No layout shift
  • T8 Kind-3 churn
  • T9 New follow
  • T10 Unfollow
  • T11 Guardrail
  • T12 Empty graph
  • T13 Cache prerequisite fix
  • T14 Account switch
  • T15 amy wot get
  • T16 amy wot list
  • T17 amy wot sync

Tester: ________________ Date: ________________