- Seed ['event', id] query cache from the community activity feed so
embedded previews resolve without a second fetch.
- Add placeholderData and a 30-minute gcTime to the community activity
feed so navigation and background refetches don't flash empty.
- Surface useGoalProgress's isPartial flag in GoalCard with a '~'
prefix and tooltip so users know when a tally hit the safety cap.
Drop LNURL signer resolution and NIP-57 receipt validation from goal
progress tallying. This removes a network request per beneficiary for a
trust level that is still spoofable and that no other zap display in the
app enforces. Revert this commit to restore strict validation.
Include communitiesLoading in the hook's isLoading so the skeleton
shows while the dependent communities query is still resolving,
instead of briefly rendering the empty state.
Goals use lowercase 'a' tags (not uppercase 'A' like NIP-22 comments)
to link to communities. The activity feed's moderation filter, members-
only filter, and CommunityModerationContext provider lookup all only
checked uppercase 'A', so goals bypassed moderation and had no '...'
menu. Now all three check both tag casings.
Content-banned goals and goals from member-banned authors are now
filtered out of the fundraising tab via applyCommunityModerationToEvents,
matching the behavior of the comments tab.
Replace standalone GoalCard with NoteCard in the community fundraising
tab so goals get the same '...' menu with remove/ban actions that
comments have. Strip GoalCard down to just the compact inline renderer
(no variant prop, no skeleton, no card-only imports). Simplify
useCommunityGoals to return plain events instead of parsed wrappers.
- Unify GoalCard and GoalContent into a single component with variant prop
- Extract useGoalDisplay hook for shared display logic (author, progress,
community link, deadline, image)
- Add useNow(60s) interval so deadline labels refresh automatically
- Add generic parseATagCoordinate utility to nostrEvents.ts
- Replace DOM-mutating image onError with React state
- Remove dead isGoalFunded export and redundant created_at in publish
- Delete GoalContent.tsx (-144 net lines)
- PostDetailPage: render GoalContent for kind 9041 instead of plain text
- CommunityDetailPage: add floating action button on comments (compose) and fundraising (new goal) tabs, remove inline New Goal button
- CreateGoalDialog: support controlled open/onOpenChange props for external triggers
Implement zap goals (kind 9041) linked to communities via a-tag.
Includes goal creation dialog, progress tracking from zap receipts,
recipient profile/lightning address display, community link, and
members-only filtering. Goals appear in community detail Fundraising
tab, activity feed, and main feed via NoteCard.
Two fixes on the members-only filter UI:
1. Toggling the shield now updates feeds live, without a reload.
The previous implementation used `useLocalStorage` in two separate
components. Each call instantiates its own `useState`, so writes from
one didn't flow to the other's reader. `localStorage`'s `storage`
event only fires cross-tab, not in the tab that wrote — so same-tab
consumers stayed stale until a remount.
Replaced with a module-level singleton store subscribed via
`useSyncExternalStore`. All consumers share one source of truth;
toggling rerenders every subscriber in the same tab instantly. The
store still persists to localStorage and listens for cross-tab
`storage` events, so behaviour across tabs is unchanged.
2. Move the shield off the CommunityDetailPage tab row.
Placing the toggle inline with the TabsList made it sit on the
bottom-border stroke that belongs to the tabs, reading as if the
shield itself were an underlined tab. Moved it up one row, right-
justified on the "Founded by" label row. Visually cleaner and still
scopes the filter to the entire community (all content feeds under
the tabs, current and future), not any single tab.
Two NIP-alignment fixes:
Gap 1 — Report warnings now require `p` match (correctness).
Previously `CommunityContentWarning` looked up reports by event id only,
so any community member could publish a kind 1984 pairing a victim
event's id with their own pubkey on the `p` tag to force a warning
overlay onto an arbitrary event. Added `getApplicableReports` in
communityUtils mirroring `hasApplicableContentBan`, and use it to
require `report.targetPubkey === event.pubkey` before the warning
renders. Matches NIP.md §Reports — Content Warnings: "report warnings
MUST only attach to content when the target event's id matches the
report's `e` tag and the target event's pubkey matches the report's
`p` tag."
Gap 2 — Members-only filter toggle.
The NIP recommends canonical community feeds discard non-member
content by default. Added a shield-icon toggle that controls this as
a presentation-layer filter, defaulting on. When active, community
feeds (Activities feed, per-community Comments tab, and any future
community-scoped content surfaces) only show events authored by
chain-validated members. When off, everything scoped to the
community is shown regardless of authorship.
- `useMembersOnlyFilter` — localStorage-backed hook with cross-tab
sync; one preference shared across all community surfaces.
- `MembersOnlyToggle` — shield / shield-off icon button with tooltip
explaining current state.
- Filtering is applied post-query in the consumer pages, so toggling
is instant and doesn't invalidate the query cache.
- Community definition events (kind 34550) are never filtered — they
represent the community itself, not user-generated content.
- Toggle placement: in `CommunitiesPage` header (scopes the global
Activities feed); in `CommunityDetailPage` alongside the tabs
(scopes every content feed in that community, now and future).
- Empty-state copy hints at the filter when a list is empty only
because of it.
Drops the read-only calendar-events (kind 31922/31923) listing from
CommunityDetailPage. The feature was partial — events could be listed
but not created from the community context — and the moderation /
authorship model for community-scoped events needs its own design
pass. Keeping it half-shipped complicates the moderation foundation
this branch is establishing.
A proper community events implementation will land in its own MR with
clearer scope: creation, RSVP handling, moderation rules for
community-scoped NIP-52 events, and whether the activity feed should
surface them.
General (non-community) calendar event support is unaffected —
EventsFeedPage, CalendarEventContent, CalendarEventDetailPage, RSVP
hooks, and the feed dispatch all remain. The community activity feed
already did not include kind 31922/31923, so no change there.
Two fixes prompted by external review:
1. resolveCommunityModeration now takes the community A tag and filters
events by matching `A` tag as its first pass. The previous change
removed the A-tag existence check from parseCommunityReport on the
assumption that callers scope by relay `#A` filter; that was an
invariant of the current callers, not a property of the API. Moving
the check to the resolver restores the trust boundary at the public
API surface while keeping parseCommunityReport a pure single-event
parser. The activity feed's pre-grouping pass is dropped since the
resolver now handles per-community filtering itself.
2. Drop the `['community-members', aTag]` cache seeding from the
activity feed. The activity feed uses shared relay limits across
every subscribed community (500 awards and 500 reports total), so
per-community results can be truncated. Seeding the per-community
members cache with incomplete data would silently corrupt membership,
authority, and moderation state on community detail pages.
useCommunityMembers remains the authoritative per-community fetch.
- Extract community content warning's context subscription into the
wrapper itself so NoteCard's memo() boundary no longer depends on
moderation data. Refetches now re-render only the warning and the
three-dot menu, not the whole card.
- Rename useCommunityModeration -> useCommunityModerationForEvent and
return the full context value; PostDetailPage installs it as a
Provider, removing 7 manual communityContext prop passes. Unifies the
three previous paths for computing CommunityMenuContext down to one.
- Seed the per-community members cache from the activity feed so
opening a community detail page after the feed loads hits warm cache
instead of re-querying kind 8 awards and kind 1984 reports.
- Single-pass parse in resolveCommunityModeration (was parsing each
kind 1984 event twice across the ban and report passes).
- Drop the redundant A-tag existence check in parseCommunityReport;
callers scope events via the relay's #A filter.
- Scope ban/report cache invalidation with a predicate that only
matches activity feeds containing the affected community's A tag.
- Drop CommunityMembership.totalCount (was just members.length) and
consolidate scattered EMPTY_* sentinels into EMPTY_MEMBERSHIP and
EMPTY_RANK_MAP in communityUtils.
Rename memberMap -> rankMap to clarify it is a pre-moderation rank lookup
(includes banned members) and should not be used to list active members.
Extract canBanTarget(), getViewerAuthority(), isEventAllowedByModeration(),
CommunityMenuContext, and EMPTY_MODERATION into communityUtils as shared
primitives, eliminating duplicated logic across hooks and components.
Remove unused ApplyCommunityModerationOptions dead code.