Files
Toju/agents-docs/features/server-discovery.md
Myx 9e1d75d038 style: Consistent backdrop and create server in server-rail
wider server rail with larger icons ans slightly animated.
2026-06-05 02:34:02 +02:00

6.2 KiB

Server Discovery

Area: server-directory Status: Active Last updated: 2025-02-14

Overview

Server discovery lets a signed-in user find public servers to join without knowing an exact name. It spans the signaling server (REST routes + CQRS query handlers that rank public servers) and the product client (server-directory domain API/facade plus the /dashboard landing and /servers browse page). It complements the existing free-text GET /api/servers search with two curated lists — featured and trending.

Responsibilities

  • Server: rank and return public servers as featured (most-populated) and trending (most-recently-active) lists, capped per request.
  • Client: fetch those lists through ServerDirectoryFacade and render them via the reusable app-server-browser component on /servers and /dashboard.
  • It does NOT own: free-text search (GET /api/servers), join/access checks (/api/servers/:id/join), invites, or room signal-affinity. Discovery is read-only browsing; joining flows through existing paths.

Key concepts

  • Featured: public servers ranked by membership count descending, ties broken by most recent lastSeen (rankFeaturedServers).
  • Trending: public servers ranked by most recent lastSeen descending, ties broken by membership count (rankTrendingServers).
  • Discovery limit: each route clamps limit to [1, 50] (parseDiscoveryLimit), default 12.

API Endpoints

Both endpoints live in server/src/routes/servers.ts and must be registered before the parameterised /:id route, otherwise Express resolves featured/trending as a server id.

GET /api/servers/featured

  • Method: GET
  • Authentication: None (public discovery)
  • Rate Limiting: No
  • Query params: limit (optional integer; clamped to [1, 50], default 12)

GET /api/servers/trending

  • Method: GET
  • Authentication: None (public discovery)
  • Rate Limiting: No
  • Query params: limit (optional integer; clamped to [1, 50], default 12)

Response Schema (both)

{
  "servers": "ServerInfo[] — enriched public servers (icon, channels, sourceId/sourceName/sourceUrl filled by the client API layer)",
  "total": "number — count of servers returned",
  "limit": "number — the effective clamped limit"
}

ServerInfo matches the shape returned by GET /api/servers search results, so the client normalises and renders all three lists identically.

Error Responses

  • 500 Internal Server Error: query handler / persistence failure.

Server internals

  • Routes delegate to CQRS query handlers handleGetFeaturedServers / handleGetTrendingServers (server/src/cqrs/queries/handlers/), dispatched via GetFeaturedServers / GetTrendingServers query types.
  • Ranking lives in server/src/cqrs/queries/handlers/server-ranking.util.ts (rankFeaturedServers, rankTrendingServers, loadMembershipCounts). Membership counts load in a single grouped query.
  • Results pass through the same enrichServer() step as search before serialisation.

Client internals

  • ServerDirectoryApiService.getFeaturedServers() / getTrendingServers() call the routes through a shared private getDiscoveryServers(path) helper and normalise into ServerInfo[].
  • ServerDirectoryServiceServerDirectoryFacade expose getFeaturedServers() / getTrendingServers() as the domain boundary.
  • FindServersComponent (/servers) composes Recently active (the user's saved rooms, capped at 6), Featured, and Trending sections, all rendered through app-server-browser with [showMyServers]="true".
  • DashboardComponent (/dashboard) is a single-column landing page (max-width centered, no in-page sidebars): a header greeting (no emoji), a global search with Ctrl+K focus and localStorage-backed Recent Searches chips shown beneath it, three primary action cards (Find People → /people, Find Servers → /servers, Create Server → /create-server — one link each), and discovery panels People you might know, Popular Servers, Your Friends, and Recently Active Servers. Each list is capped at 5 (DISCOVERY_LIMIT). It loads popularServers on init from getFeaturedServers(5), falling back to getTrendingServers(5) when featured is empty; reuses app-friend-button for Add and app-user-avatar for people rows. peopleYouMightKnow excludes existing friends (via FriendService.friendIds()); friends lists discovered people who are friends. "See all" header links route to the matching /people or /servers page (no duplicated footer links). Recent searches are recorded on Enter (deduped, most-recent-first, capped at 8) and persisted under metoyou_dashboard_recent_searches.
  • The servers-rail top button (servers-rail.component) is the Dashboard button (lucideLayoutDashboard, title="Dashboard"); its goToDashboard() handler deselects any active voice server and navigates to /dashboard. A Create a server button (lucidePlus, data-testid="server-rail-create") sits below the saved-server icons and opens app-create-server-dialog (a Toju modal on desktop / bottom sheet on mobile) which dispatches RoomsActions.createRoom directly; the dashboard / /create-server route remains as an alternative entry point. Rail icons (h-12 w-12, md:h-11 w-11) animate their corner radius on hover and :active for a Discord-style squircle effect.
  • On mobile (ViewportService.isMobile()), DashboardComponent, FindPeopleComponent (/people), and FindServersComponent (/servers) each mount their page body inside a single <swiper-container> slide next to app-servers-rail (rail shrink-0, content flex-1 with a left border), mirroring the chat-room / DM-workspace mobile layout so the primary navigation rail stays reachable. The page body is shared between the desktop and mobile branches via an <ng-template #pageContent> + [ngTemplateOutlet], and each component declares schemas: [CUSTOM_ELEMENTS_SCHEMA] for the Swiper custom elements.
  • Product-client domain README: toju-app/src/app/domains/server-directory/README.md
  • People discovery (/people): toju-app/src/app/domains/direct-message/README.md