# Remote Actor Search & Follow Allows local users to search for and follow users on other ActivityPub instances (e.g. `@user@mastodon.social`) directly from the existing search page. ## Architecture Approach A: new `FederationActionPort` domain trait + dedicated `/federation/*` REST endpoints. Keeps hexagonal arch intact — presentation has no dep on `activitypub-base`. ## Domain changes **`domain/src/models/remote_actor.rs`** — add `avatar_url: Option` **`domain/src/errors.rs`** — add `ExternalService(String)` variant **`domain/src/ports.rs`** — new trait: ```rust pub trait FederationActionPort: Send + Sync { async fn lookup_actor(&self, handle: &str) -> Result; async fn follow_remote(&self, local_user_id: &UserId, handle: &str) -> Result<(), DomainError>; } ``` ## activitypub-base impl `impl domain::ports::FederationActionPort for ActivityPubService` in `service.rs`: - `lookup_actor`: calls `webfinger_resolve_actor(handle, &data)` → maps `DbActor` to `domain::RemoteActor` - `follow_remote`: delegates to existing `self.follow(local_user_id.inner(), handle)` (already handles WebFinger + Follow activity + federation DB record) ## Bootstrap refactor `factory.rs` currently builds `FederationData` + `ApFederationConfig` directly. Switch to `ActivityPubService::new(...)` which creates both internally. `Infrastructure` holds `Arc` instead of `ApFederationConfig`. `main.rs` uses `infra.ap_service.federation_config().middleware()`. `AppState` gets one new field: ```rust pub federation: Arc, ``` Wired to `Arc::clone(&ap_service)` in factory. ## REST endpoints **`api-types/src/responses.rs`** — new: ```rust pub struct RemoteActorResponse { pub handle: String, pub display_name: Option, pub avatar_url: Option, pub url: String, } ``` **`presentation/src/handlers/federation.rs`** (new file): | Method | Path | Auth | Body | Response | |--------|------|------|------|----------| | GET | `/federation/lookup?handle=@user@instance.tld` | none | — | `RemoteActorResponse` | | POST | `/federation/follow` | bearer | `{"handle":"@user@instance.tld"}` | 204 | Mounted in `routes.rs` under `/federation`. Error mapping: `DomainError::ExternalService` → 502, `DomainError::NotFound` → 404. ## Frontend **`lib/api.ts`**: - `RemoteActorSchema` + `RemoteActor` type - `lookupRemoteActor(handle, token)` → `GET /federation/lookup?handle=...` - `followRemoteUser(handle, token)` → `POST /federation/follow` **`app/search/page.tsx`**: - Detect `@user@instance.tld` via regex `/^@[\w.-]+@[\w.-]+\.\w+$/` - If matches: call `lookupRemoteActor` in parallel with local search - Pass remote actor result to component; show in Users tab above local results **`components/remote-user-card.tsx`** (new client component): - Displays avatar, handle, display name - Follow button calls `followRemoteUser(handle, token)` - No unfollow needed for MVP (remote following status not tracked locally)