Files
thoughts/docs/superpowers/specs/2026-05-15-federation-management-design.md

4.8 KiB

Federation Management Design

Goal

Allow users to manage their ActivityPub federation: accept/reject incoming remote follow requests, remove accepted remote followers, and unfollow remote actors they're following. Surface this in three places via a shared component set.

Architecture

Hexagonal layers respected throughout:

  • Application layer: new use cases in federation_management.rs — all business routing lives here
  • Presentation layer: handlers call use cases only, no direct port access
  • Frontend: four components under components/federation/, used in three locations

Backend

New use cases — crates/application/src/use_cases/federation_management.rs

Six functions, each taking &dyn FederationActionPort and &UserId. Return domain types (Vec<RemoteActor> or ()).

list_pending_requests(federation, user_id)       → Result<Vec<RemoteActor>, DomainError>
accept_follow_request(federation, user_id, actor_url: &str) → Result<(), DomainError>
reject_follow_request(federation, user_id, actor_url: &str) → Result<(), DomainError>
list_remote_followers(federation, user_id)        → Result<Vec<RemoteActor>, DomainError>
remove_remote_follower(federation, user_id, actor_url: &str) → Result<(), DomainError>
list_remote_following(federation, user_id)        → Result<Vec<RemoteActor>, DomainError>

Unfollow remote reuses the existing unfollow_actor use case in social.rs (already routes @handle to federation.unfollow_remote).

New HTTP endpoints — crates/presentation/src/handlers/federation_management.rs

All routes require authentication (AuthUser extractor). Actor URLs go in the JSON request body to avoid percent-encoding issues.

Method Path Body Action
GET /federation/me/followers/pending List pending follow requests
POST /federation/me/followers/accept { actor_url: String } Accept a follow request
DELETE /federation/me/followers { actor_url: String } Remove/reject a follower
GET /federation/me/followers List accepted remote followers
GET /federation/me/following List remote actors being followed
DELETE /federation/me/following { handle: String } Unfollow a remote actor (delegates to unfollow_actor)

Handlers are thin: extract auth, call use case, return JSON. No logic.


Frontend

API client additions — thoughts-frontend/lib/api.ts

Six new functions mirroring the six endpoints. All take token: string.

getPendingFollowRequests(token)
acceptFollowRequest(actorUrl: string, token)
rejectFollowRequest(actorUrl: string, token)
getRemoteFollowers(token)
removeRemoteFollower(actorUrl: string, token)
getRemoteFollowing(token)
// unfollowRemote reuses existing unfollowUser or a new call to DELETE /federation/me/following

Response schema: RemoteActorSchema (already defined — handle, display_name, avatar_url, url).

Components — thoughts-frontend/components/federation/

pending-requests.tsx

  • Client component
  • Fetches getPendingFollowRequests on mount
  • Renders list of remote actors with Accept and Reject buttons
  • On action: optimistic removal from list, then API call
  • Prop: compact?: boolean — when true, renders as a flat list without card chrome (for notifications embed)

remote-followers.tsx

  • Client component
  • Fetches getRemoteFollowers on mount
  • Renders list of accepted remote followers with a Remove button
  • On remove: optimistic removal, then API call

remote-following.tsx

  • Client component
  • Fetches getRemoteFollowing on mount
  • Renders list of remote actors being followed with an Unfollow button
  • On unfollow: optimistic removal, then API call

federation-panel.tsx

  • Composes the three above inside a shadcn Tabs component
  • Tabs: "Requests", "Followers", "Following"
  • Shows a numeric badge on "Requests" tab when pending count > 0
  • No data fetching of its own — delegates entirely to sub-components

Usage locations

app/settings/federation/page.tsx (new)

  • Server component shell, renders <FederationPanel />
  • Add "Federation" link to the settings sidebar alongside "Profile" and "API Keys"

app/users/[username]/page.tsx (modify)

  • Add a "Federation" tab to the profile tabs row
  • Render <FederationPanel /> as its content
  • Tab is only visible when isOwnProfile === true

Notifications page (modify)

  • Render <PendingRequests compact /> as a card section above the notification feed
  • Only shown when the user is authenticated

Out of scope

  • Local follow request management (local follows are auto-accepted)
  • Blocking remote actors (separate feature, already partially implemented)
  • Notification count badge in the nav for pending requests (can be added later)