diff --git a/AGENTS.md b/AGENTS.md
index cebef66..d23200e 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -2134,10 +2134,11 @@ The community relay moved off the hosted relay.tools instance
default; **Settings → Coordinator** shows a "Use SkateSpots coordinator" block
and **CrewForm** prefills it for new crews. Disclosure copy lives in
`socialUi.coordinator.ourDefault*` + the existing `privacyNote` (coordinators see
- ciphertext + metadata, never contents). `CrewCoordinatorCard` shows an **"Open in
- Staircase"** link (`coordinatorAddUrl`) in both branches (the native Cordn client
- for real MLS) — our coordinator by default, a BYO coordinator's add-link when it
- has a pubkey.
+ ciphertext + metadata, never contents). `CrewCoordinatorCard` offers two real-MLS
+ entry points in both branches — **"Cordn (web)"** (the cordn.net add-link → real
+ MLS in the browser) and **"Copy for Staircase"** (copies the nprofile to add in
+ the native app) — our coordinator by default, a BYO coordinator's when it has a
+ pubkey.
- **Cutover (2026-10-12):** `SKATE_RELAY` (`src/lib/types.ts`) → the new relay; the
default pools, the DM/CVM/zap relay arrays, the `cvm-server` + `push-service`
defaults, and the archive default all point at the new relay.
diff --git a/src/components/crew/CrewCoordinatorCard.tsx b/src/components/crew/CrewCoordinatorCard.tsx
index 935b580..43ebd7c 100644
--- a/src/components/crew/CrewCoordinatorCard.tsx
+++ b/src/components/crew/CrewCoordinatorCard.tsx
@@ -1,9 +1,10 @@
import { Link } from 'react-router-dom';
-import { Lock, ShieldCheck, Server, ExternalLink, Settings } from 'lucide-react';
+import { Lock, ShieldCheck, Server, ExternalLink, Settings, Copy } from 'lucide-react';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { useAuthor } from '@/hooks/useAuthor';
import { useTranslation } from '@/contexts/language';
+import { useToast } from '@/hooks/useToast';
import { genUserName } from '@/lib/genUserName';
import { sanitizeUrl, urlDomain } from '@/lib/sanitizeUrl';
import { resolveCordnEndpoint } from '@/lib/cordn/endpoint';
@@ -143,22 +144,51 @@ export function CrewCoordinatorCard({
);
}
-/** Link to open a coordinator in Staircase (the native Cordn client) for real
- * MLS chat. Falls back to the Cordn home when the coordinator has no pubkey. */
+/** Real-MLS entry points for a coordinator: open the Cordn **web** client
+ * (cordn.net add-link), or copy the nprofile to add it in the **Staircase**
+ * native app. Falls back to the Cordn home when there's no pubkey. */
function StaircaseLink({ coordinator }: { coordinator: { pubkey?: string; relays?: string[] } }) {
const { t } = useTranslation();
- const href = coordinator.pubkey
+ const { toast } = useToast();
+ const nprofile = coordinator.pubkey
+ ? nip19.nprofileEncode({ pubkey: coordinator.pubkey, relays: coordinator.relays ?? [] })
+ : null;
+ const webUrl = coordinator.pubkey
? coordinatorAddUrl({ pubkey: coordinator.pubkey, relays: coordinator.relays ?? [] })
: CORDN_HOME;
+
+ const copyNprofile = async () => {
+ if (!nprofile) return;
+ try {
+ await navigator.clipboard.writeText(nprofile);
+ toast({ title: t('common.copied') });
+ } catch {
+ toast({ title: t('common.tryAgain'), variant: 'destructive' });
+ }
+ };
+
+ const btn =
+ 'inline-flex items-center gap-1.5 rounded-lg border px-2.5 py-1.5 text-xs font-bold transition-colors';
return (
-
-