Closes the social loop opened by social-feed: a Pending follower has no way to know their request was approved, and a follower has no signal at all that someone they follow just posted. Adds a notifications surface for three v1 event types — follow_request_approved, follow_received, activity_published — plus a /notifications page, navbar unread count, and mark-as-read controls. Capabilities: - New: notifications (table, page, badge, generation hooks) - Modified: social-follows (approve + auto-accept emit) - Modified: activity-feed (public create fans out) - Modified: journal-landing (nav entry alongside follow-requests) Design picks: - Fan-out-on-write for activity_published (1:N) so /notifications is a flat single-table query and "mark read" composes trivially. 1:1 events insert directly. Cost ceiling documented at 10k followers × 50 activities/day = 500k/day, still trivial; revisit only if hot. - Single notifications table with loose subject_id (no per-type FK); renderer dereferences by type. Mastodon-style. - Two distinct nav entries (Follow requests + Notifications). Pending is "act on this", notifications is "this happened" — different semantics, kept separate. - Loader-driven unread count (no real-time channel). Real-time is deferred. - 90-day retention for read rows; unread kept indefinitely. Out of scope: per-type mute preferences, email/push, real-time, notifications about routes/replies/mentions, federated notifications. Each tracked as follow-up. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1.2 KiB
1.2 KiB
ADDED Requirements
Requirement: Follow lifecycle emits notifications
The follow lifecycle SHALL produce notifications for the recipient of the social event (see notifications spec):
- Auto-accepted public follow →
follow_receivedto the followed user. - Approved Pending request →
follow_request_approvedto the follower (now accepted). - Reject and unfollow do NOT produce notifications.
Scenario: Public auto-accept notifies the target
- WHEN a user follows another user whose
profile_visibility = 'public'(auto-accept path) - THEN a
follow_receivednotification is created for the followed user
Scenario: Approval notifies the requester
- WHEN a private user approves a Pending follow request
- THEN a
follow_request_approvednotification is created for the follower
Scenario: Reject does not notify
- WHEN a private user rejects a Pending follow request
- THEN no notification is created (silent rejection — the follower can re-request later if they want)
Scenario: Unfollow does not notify
- WHEN a follower unfollows or cancels a Pending request
- THEN no notification is created on the followed side