From 2bad8f6a77eb1a5ef6ef262d53eb0ec2e5e47368 Mon Sep 17 00:00:00 2001 From: nrobi144 Date: Thu, 23 Jul 2026 11:07:45 +0300 Subject: [PATCH] docs(desktop): full moderation & safety manual testing sheet (core + follow-ups) Co-Authored-By: Claude Opus 4.8 (1M context) --- ...-moderation-safety-manual-testing-sheet.md | 184 ++++++++++++++---- 1 file changed, 144 insertions(+), 40 deletions(-) diff --git a/desktopApp/plans/2026-07-23-desktop-moderation-safety-manual-testing-sheet.md b/desktopApp/plans/2026-07-23-desktop-moderation-safety-manual-testing-sheet.md index 064a1234f1..dcd30309a5 100644 --- a/desktopApp/plans/2026-07-23-desktop-moderation-safety-manual-testing-sheet.md +++ b/desktopApp/plans/2026-07-23-desktop-moderation-safety-manual-testing-sheet.md @@ -1,53 +1,157 @@ -# Manual Testing — Desktop Moderation & Safety +# Manual Testing Sheet — Desktop Moderation & Safety + +**Feature branch:** `worktree-feat-desktop-moderation-safety` +**Covers commits:** `9090dfe8` → `274f8c01` (enforcement, report, CW blur, follow-ups) +**Date executed:** ____________ **Tester:** ____________ **Build:** ____________ -Branch: `worktree-feat-desktop-moderation-safety` Run: `./gradlew :desktopApp:run` -Prereq: log in with a **writeable** account (local nsec or bunker). For mute-list -sync tests, use an account that already has a kind-10000 mute list from mobile. +--- -## Enforcement (the bug fix) +## 0. Setup & Prerequisites -| # | Scenario | Expected | -|---|----------|----------| -| E1 | Account has a mobile-set mute on user X → open Following/Global | X's notes do NOT appear | -| E2 | Right-click a note → **Mute user** | Author's notes vanish from the feed **without restart** (may take a beat while it publishes) | -| E3 | Mute a user you follow | Their notes are hidden in Following too (mute wins over follow) | -| E4 | Open a thread where a reply author is muted | Muted reply hidden; thread root still shows | -| E5 | Open the muted user's profile | (v1) profile still renders — enforcement is feed/notification/DM-scoped, not profile | -| E6 | Notifications tab with a muted user reacting/replying | Their notifications are hidden | -| E7 | DM conversation list with a muted user | Their conversation is filtered out | -| E8 | Hidden word present in mute list (from mobile) → note containing it | Note is filtered | -| E9 | Read-only (npub) account | No crash; public mute portion still enforced; no Mute/Report items in menu | +You need up to **three accounts** to cover every path. Note their npubs below. -## Report (NIP-56) +| Role | Requirement | npub / note | +|------|-------------|-------------| +| **A — main (writeable)** | Local `nsec` login. Ideally already has a kind-10000 mute list set from the Amethyst mobile app (to test hydration). | | +| **B — bunker (writeable, NIP-46)** | NIP-46 remote signer login. Used only for the async-decrypt path. | | +| **C — read-only (npub)** | Login with an `npub` only (no signer). | | +| **Target user X** | Any account you can mute/report/block. Should be posting notes visible in your feeds. | | +| **Followed user Y** | A user account **A follows** — used to prove mute wins over follow. | | -| # | Scenario | Expected | -|---|----------|----------| -| R1 | Right-click a note → **Report…** | Dialog opens with reason radios + comment field | -| R2 | Pick a reason → **Report** | kind-1984 published to relays (verify via `amy` or a relay log); no local hide | -| R3 | **Block & report** | Report published AND author muted (notes vanish) | -| R4 | Read-only account | No Report item in the menu | +Legend for results: **P** = pass, **F** = fail (add note), **B** = blocked/couldn't test, **N/A**. -## Content warning (NIP-36) +> Tip: to observe published events (mutes = kind 10000, reports = kind 1984), tail a +> relay you publish to, or use `amy` to fetch them by author/kind. -| # | Scenario | Expected | -|---|----------|----------| -| C1 | Feed contains a note tagged `content-warning` / NSFW | Renders collapsed: "Sensitive content" + reason + **Show** | -| C2 | Click **Show** | Note body reveals; stays revealed while scrolling (per-note) | -| C3 | Thread root that is CW-tagged | Auto-revealed (forceReveal) — you navigated into it | +--- -> Note: the "always show sensitive content" account setting defaults to blur. -> A settings toggle to flip it is a deferred follow-up (see plan Non-goals). +## 1. Enforcement — the core bug fix (feeds + DMs) -## Regression +Logged in as **A**. -| # | Scenario | Expected | -|---|----------|----------| -| X1 | Feeds with no mutes | Everything renders as before; no perf regression scrolling | -| X2 | Spam-collapse (hashtag spam) | Still works; CW + spam don't conflict on the same note | +| # | Steps | Expected | Result | +|---|-------|----------|:---:| +| E1 | A already has a mobile-set mute on user X → open **Following** and **Global** | X's notes do **not** appear in either feed | | +| E2 | Right-click one of X's notes → **Mute user** | X's notes disappear from the feed **without restarting** the app (allow a moment for publish + flow update) | | +| E3 | Mute followed user **Y** (E2 flow) → open **Following** | Y's notes are hidden even though you follow Y (mute wins over follow) | | +| E4 | Open a **thread** whose root is by someone else but has a reply from a muted user | The muted user's **reply is hidden**; the thread **root is still shown** | | +| E5 | Open the **Notifications** tab; have a muted user react to / reply to your note | The muted user's notification does **not** appear | | +| E6 | Open the **DM / Messages** conversation list while a muted user has messaged you | The muted user's conversation is filtered out of the list | | +| E7 | A's mute list (from mobile) contains a **hidden word** → find a note containing that word | The note is hidden/collapsed | | +| E8 | Open the **Reads** (long-form) tab and **Search** with a muted author present | Muted author's articles/results are hidden | | +| E9 | **Unmute** X (see §4) | X's notes **reappear** across feeds, live | | -## Automated coverage -`./gradlew :desktopApp:test --tests "com.vitorpamplona.amethyst.desktop.filters.DesktopMuteEnforcementTest"` -covers E1/E3/E8 at the filter level (muted author hidden, hidden even if followed, -hidden-word note dropped, clean notes pass). +--- + +## 2. Thread + Profile enforcement (follow-up A) + +Logged in as **A**. + +| # | Steps | Expected | Result | +|---|-------|----------|:---:| +| T1 | Mute user X → open a thread containing X's reply | X's reply hidden; root shown; sibling replies still there | | +| T2 | Open **X's profile** → Notes tab | X's own notes are hidden (empty / filtered) | | +| T3 | X's profile → **Replies** tab | X's replies hidden too | | +| T4 | While X's profile is open, **Unmute** X from the header overflow (§5) | Their notes **repopulate** the tabs live | | + +--- + +## 3. Report — NIP-56 (note + user) + +Logged in as **A**. + +| # | Steps | Expected | Result | +|---|-------|----------|:---:| +| R1 | Right-click a note → **Report…** | Dialog opens with reason radios (Spam / Profanity / Impersonation / Nudity / Illegal / Malware) + a comment field | | +| R2 | Pick a reason, add a comment → **Report** | A **kind-1984** event is published (verify on a relay / via `amy`) tagging the note id + author; the note is **not** hidden | | +| R3 | Report a note → **Block & report** | Report published **and** the author is muted (their notes vanish) | | +| R4 | Open X's profile → header **⋮** → **Report…** → pick reason → **Report** | kind-1984 published tagging **the user** (no `e` tag required) | | +| R5 | Profile **⋮** → **Block & report** | Report published + user muted | | + +--- + +## 4. Content warning — NIP-36 blur (follow-up B render) + +Logged in as **A**. Ensure "Always show sensitive content" is **OFF** (§6) for C1–C3. + +| # | Steps | Expected | Result | +|---|-------|----------|:---:| +| C1 | Find a note tagged `content-warning` / NSFW in a feed | Renders **collapsed**: "Sensitive content" + the reason text + a **Show** button; media not visible | | +| C2 | Click **Show** | The note body/media reveals; stays revealed while scrolling away and back (per-note) | | +| C3 | Open a CW-tagged note as a **thread root** | Auto-revealed (you navigated into it — `forceReveal`) | | +| C4 | Turn **ON** "Always show sensitive content" (§6) → revisit a CW note in the feed | Renders **unblurred** immediately, no Show gate | | +| C5 | Turn it **OFF** again | CW notes blur again | | +| C6 | Confirm spam-collapse still works and doesn't conflict on a note that is both spammy and CW | One collapse shown; **Show** reveals the note | | + +--- + +## 5. Profile moderation actions (follow-up B) + +Logged in as **A**, viewing **X's** profile (not your own). + +| # | Steps | Expected | Result | +|---|-------|----------|:---:| +| P1 | Profile header shows a **⋮ (MoreVert)** button next to Follow | Present for other users on a writeable account | | +| P2 | ⋮ → menu | Shows **Mute user** (or **Unmute user** if already muted) + **Report…** | | +| P3 | ⋮ → **Mute user** | X muted; label flips to **Unmute user** on reopen; notes hide | | +| P4 | ⋮ → **Unmute user** | X unmuted; notes return | | +| P5 | Open **your own** profile | **No** ⋮ moderation button (can't mute yourself) | | + +--- + +## 6. Settings — sensitive toggle + management screens (follow-ups B & C) + +Open **Settings → Content Filters** as **A**. + +| # | Steps | Expected | Result | +|---|-------|----------|:---:| +| S1 | Locate the **Moderation & Safety** section (below Hashtag-spam filter) | Section renders with an "Always show sensitive content" switch | | +| S2 | Toggle the switch on → **restart the app** → reopen settings | Switch is **still on** (persisted via prefs) | | +| S3 | **Muted users** list | Lists every muted/blocked user with a resolved display name (or short pubkey) + **Unmute** | | +| S4 | Click **Unmute** on a user | Row disappears; that user's notes reappear in feed live; kind-10000 re-published | | +| S5 | **Hidden words**: type a word → **Add** | Word appears in the list; notes containing it collapse in feeds | | +| S6 | **Hidden words**: **Remove** a word | Word gone; matching notes reappear | | +| S7 | **Muted threads** list (mute a thread first, if none) | Lists muted thread ids (truncated) + **Unmute**; removing restores the thread | | +| S8 | Mute a user via note menu (§1 E2) → reopen settings | The new user shows up in the **Muted users** list immediately | | + +--- + +## 7. Account edge cases + +| # | Account | Steps | Expected | Result | +|---|---------|-------|----------|:---:| +| A1 | **C (read-only)** | Right-click a note; open a profile ⋮; open settings Moderation section | **No** Mute/Report items in menus; management lists render **read-only** (no Unmute/Add/Remove); sensitive toggle still works (local pref). No crash. | | +| A2 | **C (read-only)** | A public (unencrypted) mute entry exists on this npub | Public mute portion is still **enforced** in feeds | | +| A3 | **B (bunker)** | Log in fresh; the private mute list must be decrypted via the remote signer | Enforcement engages **once decryption resolves** (may lag a beat); no crash while waiting; approve the decrypt on the signer if prompted | | +| A4 | **A** | Mute someone, then kill the relay / go offline mid-publish | App doesn't crash; local hide still applied; re-syncs when back online | | + +--- + +## 8. Regression + +| # | Steps | Expected | Result | +|---|-------|----------|:---:| +| X1 | Browse feeds with **no** mutes set | Everything renders as before; no scroll jank / perf regression | | +| X2 | Existing hashtag-spam collapse | Still works independently of moderation | | +| X3 | Reactions, reposts, zaps, bookmarks on a note | Unchanged (moderation menu items are additive to ShareMenu) | | +| X4 | Open/refresh a thread and a profile repeatedly | No leaks / duplicate VMs (filters recreated on account change only) | | + +--- + +## Automated coverage (already green) + +- `./gradlew :desktopApp:test --tests "com.vitorpamplona.amethyst.desktop.filters.DesktopMuteEnforcementTest"` + — muted author hidden (even if followed), hidden-word drop, clean notes pass (§1 E1/E3/E7). +- `./gradlew :commons:jvmTest --tests "com.vitorpamplona.amethyst.commons.moderation.PreferencesSensitiveContentSettingsTest"` + — sensitive toggle mapping `on→true`, `off→null` (never false), persistence (§6 S2). + +## Sign-off + +- [ ] All §1 enforcement scenarios pass (the core bug fix). +- [ ] All follow-ups (§2, §5, §6) pass. +- [ ] Edge cases (§7) behave with no crash. +- [ ] No regressions (§8). + +Overall: ☐ PASS ☐ FAIL — notes: ______________________________________________