diff --git a/docs/superpowers/specs/2026-05-15-federation-management-design.md b/docs/superpowers/specs/2026-05-15-federation-management-design.md new file mode 100644 index 0000000..9ff5882 --- /dev/null +++ b/docs/superpowers/specs/2026-05-15-federation-management-design.md @@ -0,0 +1,116 @@ +# 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` or `()`). + +``` +list_pending_requests(federation, user_id) → Result, 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, DomainError> +remove_remote_follower(federation, user_id, actor_url: &str) → Result<(), DomainError> +list_remote_following(federation, user_id) → Result, 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`. + +```typescript +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 `` +- 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 `` as its content +- Tab is only visible when `isOwnProfile === true` + +**Notifications page** (modify) +- Render `` 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)