mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-10-05 19:28:25 +00:00
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
7.9 KiB
7.9 KiB
title, type, status, date, origin, module
| title | type | status | date | origin | module |
|---|---|---|---|---|---|
| Desktop Moderation & Safety — deferred follow-ups (management screens, sensitive-content toggle, thread/profile enforcement) | feat | active | 2026-07-23 | desktopApp/plans/2026-07-23-feat-desktop-moderation-safety-plan.md | desktopApp (+ commons/jvmMain) |
✨ Desktop Moderation & Safety — follow-ups
Continuation of the shipped core (branch worktree-feat-desktop-moderation-safety,
commits 9090dfe8→f4435bfe). The account-layer write API
(hideUser/showUser/hideWord/showWord/hideThread/showThread, report) and the
LocalDesktopIAccount CompositionLocal already exist — these follow-ups are the
remaining UI + persistence.
Scope (the 3 deferred items)
- Thread + Profile enforcement —
ThreadScreen/UserProfileScreenbuild theirDesktopThreadFilter/DesktopProfileFeedFilterwith the defaulthidden = { EMPTY }, so mutes don't apply there. Wire the real lambda. - Sensitive-content toggle + profile moderation actions — persist an
"Always show sensitive content" setting and back
DesktopIAccount.showSensitiveContentSettingwith it; add Mute/Report to the profile screen. - Management screens — list + remove Blocked users / Hidden words / Muted threads, reachable from the Content Filters settings section.
Key findings (grounded 2026-07-23)
ThreadScreen(ui/ThreadScreen.kt:98) +UserProfileScreen(ui/UserProfileScreen.kt:120) takeaccount: AccountState.LoggedIn?— notDesktopIAccount. ButLocalDesktopIAccount.currentis now in scope inside them, so no param/caller threading needed (callers:DeckColumnContainer.kt:566,:720). Just read the local and pass{ LocalDesktopIAccount.current?.hiddenUsers?.value ?: LiveHiddenUsers.EMPTY }to the filter. (Filters already accept the lambda.)- Persistence pattern:
commons/jvmMain/.../moderation/PreferencesHashtagSpamSettings.kt—Preferences.userRoot().node(NODE)withMutableStateFlowfields. Mirror for a sensitive-content boolean. - Content Filters UI:
desktopApp/.../ui/settings/HashtagSpamSettingsSection.ktis the section composable; add the toggle + management entries alongside it. - Read side for management screens:
DesktopIAccount.hiddenUsersState.flow(LiveHiddenUsers) already exposeshiddenUsers/hiddenWords/mutedThreadssets. Remove =account.showUser/showWord/showThread. Resolve a blocked pubkey to a name vialocalCache.getUserIfExists(hex)?.toBestDisplayName().
Technical Approach — phases
Phase A — Thread + Profile enforcement (S)
- In
ThreadScreenandUserProfileScreen, captureval iAccount = LocalDesktopIAccount.currentand passhidden = { iAccount?.hiddenUsers?.value ?: LiveHiddenUsers.EMPTY }toDesktopThreadFilter/ bothDesktopProfileFeedFilterconstructions (ThreadScreen.kt:125,UserProfileScreen.kt:192,212). - Thread root intentionally stays visible (filter already only hides replies).
- Success: muting a reply author hides them in an open thread + on the profile Notes/Replies tabs, live.
Phase B — Sensitive-content toggle + profile moderation (M)
- New
commons/jvmMain/.../moderation/PreferencesSensitiveContentSettings.kt: persistedalwaysShowSensitive: MutableStateFlow<Boolean>(default false) under a stable prefs node; exposeshowSensitiveContent: StateFlow<Boolean?>mappingtrue → true,false → null(null = respect warnings, matchingNote.isHiddenFor). DesktopIAccount: replace the placeholdershowSensitiveContentSetting = MutableStateFlow(null)with this store's flow; addsetAlwaysShowSensitive(Boolean).- Content Filters section: a "Show sensitive content" switch (reads/writes via
LocalDesktopIAccount.current). UserProfileScreen: add Mute user + Report… actions (reuseReportNoteDialog; callaccount.hideUser/account.report(userHex, …)), shown only for writeable accounts and not for self.- Success: toggling the switch flips CW blur app-wide and persists across restart; profile has working Mute/Report.
Phase C — Management screens (M)
- New
desktopApp/.../ui/settings/BlockedContentSettings.kt(or 3 small composables): three expandable lists driven byLocalDesktopIAccount.current?.hiddenUsers(collectAsState):- Blocked/muted users → rows with resolved display name + "Unmute"
(
showUser). - Hidden words → text rows + "Remove" (
showWord) + an add-word field (hideWord). - Muted threads → note-id rows + "Unmute" (
showThread).
- Blocked/muted users → rows with resolved display name + "Unmute"
(
- Surfaced from the Content Filters section (expander or sub-screen).
- Success: lists reflect the live mute set and removing an entry publishes the updated kind-10000 and un-hides immediately.
System-Wide Impact
- Interaction graph: removing a mute →
account.showUser/Word/Thread→MuteListEvent.removesigned +justConsumeMyOwnEvent+ broadcast → mute flow re-emits → feedsinvalidateData→ entry disappears from feed AND from the management list (same flow). No separate refresh path. - State lifecycle: management-list edits and the sensitive toggle are independent stores (NIP-51 event vs local prefs); no shared partial-failure.
- API parity: the CW toggle now feeds three readers —
Note.isHiddenFor(feed filters) andSpamCheckedNoteRender(blur). Both read the sameshowSensitiveContentvalue; verify one source of truth. - Read-only accounts: management screens are read-only (show lists, disable remove/add); the sensitive toggle still works (it's local prefs, not signed).
Integration test scenarios
- Mute a reply author → open the thread → reply hidden; root still shown.
- Mute a user → open their profile → their Notes/Replies tabs empty.
- Toggle "show sensitive content" on → CW notes render unblurred everywhere; restart app → still on.
- Unmute from the Blocked-users screen → their notes reappear in feed live.
- Add a hidden word in the management screen → matching notes collapse.
- Read-only account → management screen lists render, remove/add disabled.
Acceptance Criteria
- Muting hides in open threads (replies) + on profile tabs, live.
- "Always show sensitive content" toggle persists and flips CW blur app-wide.
- Profile screen has working Mute + Report actions (writeable, non-self) — MoreVert overflow.
- Blocked-users / Hidden-words / Muted-threads lists in
ModerationSettingsSection; remove publishes the updated mute list and un-hides live; hidden-words has an add field. - All reachable from the Content Filters settings section.
- Read-only + bunker accounts behave (write actions gated on
isWriteable(); management remove/add hidden for read-only). - Unit test: sensitive-content mapping (
true→true,false→null, persists) inPreferencesSensitiveContentSettingsTest. spotlessApplyclean; commons + desktopApp compile; tests green.
Status: COMPLETE — commit 49d0518d. Manual run (./gradlew :desktopApp:run)
- PR remain (nostr-git repo →
ngit-prflow).
Dependencies & Risks
- Low risk — all reuse the shipped write API +
LocalDesktopIAccount. The one new persisted store mirrors an existing pattern. No new third-party deps. - Watch: two readers of
showSensitiveContent(feed filter viaLiveHiddenUsersvs blur composable) must agree — thread the same setting intoDesktopHiddenUsersState(already takes ashowSensitiveContentflow param).
Sources & References
- Origin core plan:
desktopApp/plans/2026-07-23-feat-desktop-moderation-safety-plan.md desktopApp/.../ui/settings/HashtagSpamSettingsSection.kt,commons/jvmMain/.../moderation/PreferencesHashtagSpamSettings.ktdesktopApp/.../model/{DesktopIAccount,DesktopHiddenUsersState,LocalDesktopIAccount}.ktdesktopApp/.../ui/{ThreadScreen,UserProfileScreen}.kt,desktopApp/.../ui/note/{ShareMenu,ReportNoteDialog}.kt