···11"""Shared AT Protocol apps list — used by both the TUI and the web frontend."""
2233-import json
43import random
55-from pathlib import Path
6477-_JSON_PATH = Path(__file__).parent / "atproto_apps.json"
55+from core.shared import ATPROTO_APPS
8699-ATPROTO_APPS: list[dict[str, str]] = json.loads(_JSON_PATH.read_text())
77+__all__ = ["ATPROTO_APPS", "pick_random_apps"]
1081191210def pick_random_apps(count: int) -> list[dict[str, str]]:
···11import { useEffect, useState, type InputHTMLAttributes } from "react";
22+import { HANDLE_PLACEHOLDERS as PLACEHOLDERS } from "../../lib/shared";
23import { inputStyles } from "./Form";
33-44-const PLACEHOLDERS = [
55- "handle.blacksky.app",
66- "handle.bsky.social",
77- "handle.eurosky.social",
88- "handle.northsky.social",
99- "handle.selfhosted.social",
1010- "handle.tngl.sh",
1111- "handle.pds.witchcraft.systems",
1212- "handle.your-domain.com",
1313-];
144155interface HandleInputProps extends Omit<
166 InputHTMLAttributes<HTMLInputElement>,
+2-1
web/src/hooks/useDiscovery.ts
···44import { TTLCache } from "../lib/cache";
55import { getRecord, resolveIdentitiesBatch } from "../lib/atproto";
66import { SITE } from "../lib/lexicon";
77+import { SERVICES } from "../lib/shared";
78import { is } from "@atcute/lexicons/validations";
89import { mainSchema as siteSchema } from "../lexicons/types/xyz/atbbs/site";
910import type { XyzAtbbsSite } from "../lexicons";
···3334 (async () => {
3435 try {
3536 const response = await fetch(
3636- `https://lightrail.microcosm.blue/xrpc/com.atproto.sync.listReposByCollection?collection=${SITE}&limit=50`,
3737+ `${SERVICES.lightrail}/com.atproto.sync.listReposByCollection?collection=${SITE}&limit=50`,
3738 );
3839 const data = (await response.json()) as { repos: LightrailRepo[] };
3940 if (!data.repos.length) return;
+3-2
web/src/lib/atproto.ts
···11/** Read-side wrappers for Slingshot and Constellation (no auth needed). */
2233import { TTLCache } from "./cache";
44+import { SERVICES } from "./shared";
45import { parseAtUri } from "./util";
5666-const SLINGSHOT = "https://slingshot.microcosm.blue/xrpc";
77-const CONSTELLATION = "https://constellation.microcosm.blue/xrpc";
77+const SLINGSHOT = SERVICES.slingshot;
88+const CONSTELLATION = SERVICES.constellation;
89910export interface MiniDoc {
1011 did: string;
+2-10
web/src/lib/atprotoApps.ts
···11-// App list shared with the Python TUI (see core/atproto_apps.py).
22-import apps from "../../../core/atproto_apps.json";
33-44-export interface AtprotoApp {
55- name: string;
66- url: string;
77-}
88-99-export const ATPROTO_APPS: AtprotoApp[] = apps;
11+export { ATPROTO_APPS, type AtprotoApp } from "./shared";
22+import { ATPROTO_APPS, type AtprotoApp } from "./shared";
103114export function pickRandomApps(count: number): AtprotoApp[] {
1212- // Shuffle a copy of the list and take the first `count` entries.
135 const shuffled = [...ATPROTO_APPS].sort(() => Math.random() - 0.5);
146 return shuffled.slice(0, count);
157}