Show all RSVP statuses in Activity tab, not just 'Going'

Activity feed now shows accepted, tentative, and declined RSVPs with
status-specific display:
- accepted: 'is going to' + green 'Going' badge
- tentative: 'might go to' + amber 'Maybe' badge
- declined: 'can't make it to' + muted 'Can't Go' badge

Previously only accepted RSVPs were shown, filtering out potentially
useful social signal from the follow list.
This commit is contained in:
Derek Ross
2026-03-01 19:26:35 -05:00
parent 4a16b0b223
commit 2e46d10755
+21 -12
View File
@@ -219,10 +219,11 @@ function ActivitySection() {
}
}
// Only keep accepted RSVPs, filter muted
const accepted = Array.from(latestByAuthorEvent.values()).filter((e) => {
// Keep RSVPs with a valid status, filter muted
const validStatuses = new Set(['accepted', 'tentative', 'declined']);
const filtered = Array.from(latestByAuthorEvent.values()).filter((e) => {
const status = getTag(e.tags, 'status');
if (status !== 'accepted') return false;
if (!status || !validStatuses.has(status)) return false;
if (muteItems.length > 0 && isEventMuted(e, muteItems)) return false;
return true;
});
@@ -234,7 +235,7 @@ function ActivitySection() {
// Extract referenced event coordinates and batch-fetch them
const coords = new Set<string>();
for (const rsvp of accepted) {
for (const rsvp of filtered) {
const aTag = getTag(rsvp.tags, 'a');
if (aTag) coords.add(aTag);
}
@@ -267,7 +268,7 @@ function ActivitySection() {
}
return {
rsvps: accepted.map((rsvp) => {
rsvps: filtered.map((rsvp) => {
const aTag = getTag(rsvp.tags, 'a');
return {
rsvp,
@@ -304,13 +305,13 @@ function ActivitySection() {
// Auto-fetch next pages if we got raw results but zero accepted items.
// This handles relay non-determinism where a batch may contain only
// declined/tentative/malformed RSVPs. Keep paginating until we find
// accepted ones or exhaust the data (up to 5 extra pages).
// malformed RSVPs (no status tag). Keep paginating until we find
// valid ones or exhaust the data (up to 5 extra pages).
useEffect(() => {
if (!rsvpQuery.data?.pages || rsvpQuery.isFetching || !hasNextPage) return;
const totalAccepted = rsvpQuery.data.pages.reduce((sum, p) => sum + p.rsvps.length, 0);
const totalItems = rsvpQuery.data.pages.reduce((sum, p) => sum + p.rsvps.length, 0);
const pageCount = rsvpQuery.data.pages.length;
if (totalAccepted === 0 && pageCount < 5) {
if (totalItems === 0 && pageCount < 5) {
fetchNextPage();
}
}, [rsvpQuery.data?.pages, rsvpQuery.isFetching, hasNextPage, fetchNextPage]);
@@ -409,11 +410,19 @@ function ActivitySection() {
// ─── ActivityItem ─────────────────────────────────────────────────────────────
const RSVP_DISPLAY: Record<string, { verb: string; label: string; className: string }> = {
accepted: { verb: 'is going to', label: 'Going', className: 'bg-emerald-500/10 text-emerald-600 border-emerald-500/30' },
tentative: { verb: 'might go to', label: 'Maybe', className: 'bg-amber-500/10 text-amber-600 border-amber-500/30' },
declined: { verb: "can't make it to", label: "Can't Go", className: 'bg-muted text-muted-foreground border-border' },
};
function ActivityItem({ rsvp, referencedEvent }: { rsvp: NostrEvent; referencedEvent?: NostrEvent }) {
const author = useAuthor(rsvp.pubkey);
const metadata = author.data?.metadata;
const displayName = getDisplayName(metadata, rsvp.pubkey);
const profileUrl = useProfileUrl(rsvp.pubkey, metadata);
const status = getTag(rsvp.tags, 'status') ?? 'accepted';
const display = RSVP_DISPLAY[status] ?? RSVP_DISPLAY.accepted;
return (
<div className="border-b border-border px-4 py-3">
@@ -435,9 +444,9 @@ function ActivityItem({ rsvp, referencedEvent }: { rsvp: NostrEvent; referencedE
<Link to={profileUrl} className="font-semibold truncate max-w-[160px] hover:underline">
{displayName}
</Link>
<span className="text-muted-foreground shrink-0">is going to an event</span>
<Badge variant="outline" className="bg-emerald-500/10 text-emerald-600 border-emerald-500/30 text-[10px] shrink-0">
Attending
<span className="text-muted-foreground shrink-0">{display.verb} an event</span>
<Badge variant="outline" className={cn(display.className, 'text-[10px] shrink-0')}>
{display.label}
</Badge>
</div>
</div>