Skip to main content

Search & Discovery — Technical Reference

← Back to Search & Discovery

Where this lives

Backend

Frontend

A pre-existing backend bug fixed while wiring this page

user-bulk-operations.resolver.js's mutations were written to destructure { input } from their GraphQL args and read input.userIds / input.requestIds / input.action. But the actual schema (user.type.js / social-actions.type.js) declares every bulk mutation with flat arguments (userIds: [ID!]!, followerIds: [ID!]!, requestIds: [ID!]!, action: String!) — there is no input wrapper anywhere in the schema. Calling any of these mutations as originally written would throw Cannot read properties of undefined (reading 'userIds') immediately. Separately, the manager's return shape ({ success, message, results: { success, failed, skipped, errors } }) didn't match either BulkOperationResponse or BulkOperationResult's flat, non-nullable fields (successCount!, failedCount!, total!, processedCount!), which would have thrown a "Cannot return null for non-nullable field" GraphQL error. Both were fixed directly in the resolver (see mapToBulkOperationResponse / mapToBulkOperationResult helpers in the file) as part of building the frontend for this page — not a pre-existing "done" feature as earlier notes assumed.

getBulkOperationHistory and getBulkOperationStatus call userManager.getBulkOperationHistory / userManager.getBulkOperationStatus, but neither method exists in bulk-operations.manager.js (only getBulkOperationLimits does) — there is no operation-tracking persistence layer behind these two fields or behind cancelBulkOperation. They're left as-is (still throwing a clean GraphQL error if ever called) but the frontend deliberately does not call them; bulk actions run synchronously and report their result inline instead of via a polled progress bar.

hideFromSuggestions is now wired to the "Show in Explore" toggle

Fixed this session: the schema (user-presence.type.js) has declared the single mutation hideFromSuggestions(enabled: Boolean!): Boolean! for a while, resolved (user-search-discovery.resolver.js) into search-discovery.manager.js#hideFromSuggestions/#showInSuggestions depending on the enabled flag — but no frontend screen called it. AccountPrivacyPage.tsx's Settings → Privacy → Show in Explore toggle previously persisted through the generic updatePrivacySettings mutation, which flips the same underlying appear_in_suggestions flag but doesn't invalidate the caller's cached recommendation results. The toggle's handler (handleToggleShowInExplore) now calls the dedicated hideFromSuggestions mutation instead (enabled: !nextValue, since the mutation's enabled means "hide me" while the toggle's own value means "show me"), so switching it off also invalidates the cache immediately instead of waiting for it to expire naturally.

Technical implementation checklist

  • searchUsers — wired; three separate call sites, Explore's search bar, and PublicSearchPage.tsx for logged-out visitors
  • interestSuggestions(limit, interests) + updateInterests(interests) — interest-based suggestions; user.interests text[] (model attr interestTags), getUsersByInterests overlap match (migration 20260721120000)
  • suggestedUsers — wired; SuggestedUsers.tsx, now with isFollowing + followContext social-proof data
  • usersYouMayKnow / similarUsers — wired; RecommendationsSection.tsx and DiscoverPeoplePage.tsx
  • getRecommendedUsers / getTrendingUsers / nearbyUsers — wired; RecommendationsSection.tsx (all five tabs — Recommended/You may know/Similar/Trending/Nearby — now render via a real tab bar, fixed this session, previously only getRecommendedUsers rendered since activeTab was hardcoded) and DiscoverPeoplePage.tsx (all render)
  • dismissSuggestion(userId) — wired; RecommendationsSection.tsx's per-card dismiss (X) button
  • hideFromSuggestionsFixed this session: AccountPrivacyPage.tsx's Settings → Privacy → Show in Explore toggle now calls the dedicated hideFromSuggestions mutation instead of the generic updatePrivacySettings field — see note above
  • bulkFollowUsers / bulkUnfollowUsers / bulkBlockUsers / bulkRemoveFollowers / bulkProcessFollowRequests — wired; BulkFollowerActionsPage.tsx (backend resolver bug fixed as part of this work — see above)
  • getBulkOperationHistory / getBulkOperationStatus / cancelBulkOperation — now implemented: a bulk_operation table + model + access-service; each bulk action records real counts/status, status/history read the real rows, and cancel returns false for already-finished (synchronous) operations
query SearchUsers($query: String!, $limit: Int, $offset: Int, $filters: JSON) {
searchUsers(query: $query, limit: $limit, offset: $offset, filters: $filters) {
id username profilePicture isVerified
}
}

query SuggestedUsers($limit: Int, $offset: Int, $category: String) {
suggestedUsers(limit: $limit, offset: $offset, category: $category) {
id username profilePicture
}
}

Recommendations

query UsersYouMayKnow($limit: Int, $offset: Int) {
usersYouMayKnow(limit: $limit, offset: $offset) {
peopleYouMayKnow {
user { id username }
matchScore
reasons { type mutualConnectionsCount confidence }
}
total
hasMore
}
}

query SimilarUsers($limit: Int, $offset: Int) {
similarUsers(limit: $limit, offset: $offset) {
similarUsers { user { id username } similarityScore sharedInterestScore activitySimilarity }
total
hasMore
}
}

query GetTrendingUsers($limit: Int, $timeframe: String) {
getTrendingUsers(limit: $limit, timeframe: $timeframe) {
user { username }
trendingReason
}
}

query GetRecommendedUsers($limit: Int, $offset: Int, $algorithm: String) {
getRecommendedUsers(limit: $limit, offset: $offset, algorithm: $algorithm) { username }
}

query NearbyUsers($radiusKm: Float, $limit: Int) {
nearbyUsers(radiusKm: $radiusKm, limit: $limit) { user { id username } city distanceKm }
}

mutation DismissSuggestion($userId: ID!) { dismissSuggestion(userId: $userId) }

mutation HideFromSuggestions($enabled: Boolean!) { hideFromSuggestions(enabled: $enabled) }

dismissSuggestion(userId) (declared in user-recommendations.type.js, resolved by user-recommendations.resolver.js into search-discovery.manager.js#dismissSuggestion) removes that user from the caller's "people you may know" suggestions going forward; RecommendationsSection.tsx also hides the card client-side immediately, before the mutation resolves.

hideFromSuggestions(enabled: true) is a self-toggle — it hides the caller from other users' suggestion lists (persists appear_in_suggestions: false); it does not hide one specific other user from the caller's own suggestions, and it has no effect on searchUsers (direct search isn't filtered by this flag today). Fixed this session: AccountPrivacyPage.tsx's Settings → Privacy → Show in Explore toggle now calls this mutation (see the note above) — previously no frontend screen did.

suggestedUsers and suggestion cards elsewhere can also select User.isFollowing and User.followContext { count sample { id username } }followContext returns social-proof ("Followed by ...") data: how many accounts the viewer follows also follow this user, plus one sample user for the label. Both fields resolve to null for anonymous callers or when viewing your own profile.

Bulk relationship actions

mutation BulkFollowUsers($userIds: [ID!]!) {
bulkFollowUsers(userIds: $userIds) { success message successCount failedCount total errors { userId reason } }
}
mutation BulkUnfollowUsers($userIds: [ID!]!) {
bulkUnfollowUsers(userIds: $userIds) { success message successCount failedCount total errors { userId reason } }
}
mutation BulkBlockUsers($userIds: [ID!]!) {
bulkBlockUsers(userIds: $userIds) { success message processedCount failedCount }
}
mutation BulkRemoveFollowers($followerIds: [ID!]!) {
bulkRemoveFollowers(followerIds: $followerIds) { success message processedCount failedCount }
}
mutation BulkProcessFollowRequests($requestIds: [ID!]!, $action: String!) {
bulkProcessFollowRequests(requestIds: $requestIds, action: $action) { success message processedCount failedCount }
}

action for bulkProcessFollowRequests is 'accept' or 'reject', applied to every id in requestIds — the manager itself supports mixed per-request actions ({requestId, action}[]), but the GraphQL field only exposes the single-action-for-many-ids shape, so the resolver expands requestIds into that array server-side.