fix(crew): stop overclaiming about public relays in the coordinator copy
The transport treats a crew's wss:// coordinator URL AS A RELAY, and an https:// one falls back to the public wss://relay.contextvm.org. So 'nothing crew-specific hits a public relay' was false for https coordinators — it put ciphertext + metadata (crew coord, timing) on a public relay. - CrewCoordinatorCard: shows the actual relay in use, and states honestly whether it's the crew's own or the shared public one. - CrewForm: the coordinator field now says a wss:// endpoint is the crew's own relay and an https:// one routes over the shared public relay. - AGENTS.md: record the relay-vs-coordinator transport reality so this isn't re-derived (and Cordn RPC is not called).
This commit is contained in:
parent
c14b19aee1
commit
322ec58ada
3 changed files with 38 additions and 9 deletions
14
AGENTS.md
14
AGENTS.md
|
|
@ -616,9 +616,17 @@ session with **no other memory of this project**. So:
|
|||
Cordn's phone app (an nprofile, not a URL) was un-enterable — `CrewForm` now
|
||||
uses the shared `parseCrewCoordinatorInput` / `crewCoordinatorInputValue`
|
||||
(`lib/coordinator.ts`) and has a **"Use my coordinator"** button that prefills
|
||||
from Settings → Coordinator. **Still future:** real MLS
|
||||
(`ts-mls`) instead of group-key crypto. **Lesson: grep `src` for the feature
|
||||
before building it — this was nearly duplicated.**
|
||||
from Settings → Coordinator. ⚠️ **Transport reality (do not overclaim):** a
|
||||
crew's **`wss://` coordinator URL is used AS A RELAY** (`resolveCordnEndpoint`
|
||||
→ `relayUrl`); an `https://` one falls back to the **public**
|
||||
`wss://relay.contextvm.org`. The app publishes sealed kind-4497 events and
|
||||
queries them back — it does **not** call Cordn's coordinator RPC
|
||||
(`FetchGroupMessages`/`SubscribeGroupMessages`), so a real Cordn coordinator is
|
||||
not yet engaged, and an `https://` coordinator puts ciphertext + metadata
|
||||
(crew coord, timing) on a public relay. `CrewCoordinatorCard` + `CrewForm` now
|
||||
say this honestly (2026-09-29). **Still future:** real MLS
|
||||
(`ts-mls`) instead of group-key crypto, plus Cordn RPC interop. **Lesson: grep
|
||||
`src` for the feature before building it — this was nearly duplicated.**
|
||||
- **Empty-map nudge (2026-09-28):** `MapPage` distinguishes the two empties — the
|
||||
existing "No spots match" card is for *filters* (`filteredSpots.length === 0 &&
|
||||
allLocations.length > 0`) and a new "No spots mapped yet — be the first" card
|
||||
|
|
|
|||
|
|
@ -1086,7 +1086,7 @@ export function CrewForm({ existing }: CrewFormProps) {
|
|||
const coordinatorBlock = (
|
||||
<div className="space-y-2 rounded-lg border border-gray-700 bg-gray-900/60 p-4">
|
||||
<Label htmlFor="crew-coordinator" className="flex items-center gap-2 text-gray-300">
|
||||
<Server className="h-4 w-4" /> Custom Chat Coordinator <span className="font-normal text-gray-500">· experimental · self-host</span>
|
||||
<Server className="h-4 w-4" /> Custom Chat Coordinator <span className="font-normal text-gray-500">· experimental · your own relay</span>
|
||||
</Label>
|
||||
<p className="text-xs text-gray-500">
|
||||
<span className="font-semibold text-gray-300">You don't need this.</span> Your crew already has private,
|
||||
|
|
@ -1101,7 +1101,10 @@ export function CrewForm({ existing }: CrewFormProps) {
|
|||
Cordn coordinator
|
||||
</a>{' '}
|
||||
— even on an old phone. Paste its <span className="font-mono">nprofile</span>, a cordn.net link, or an
|
||||
endpoint URL. Leave blank to use the built-in chat.
|
||||
endpoint URL. A <span className="font-mono">wss://</span> endpoint is used as the crew's own chat relay
|
||||
(point it at a private one to keep even the metadata off public relays); an{' '}
|
||||
<span className="font-mono">https://</span> coordinator is addressed by its pubkey over the shared
|
||||
ContextVM relay. Leave blank to use the built-in chat.
|
||||
</p>
|
||||
<Input
|
||||
id="crew-coordinator"
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
|||
import { useAuthor } from '@/hooks/useAuthor';
|
||||
import { genUserName } from '@/lib/genUserName';
|
||||
import { sanitizeUrl, urlDomain } from '@/lib/sanitizeUrl';
|
||||
import { resolveCordnEndpoint } from '@/lib/cordn/endpoint';
|
||||
import { CORDN_RELAY_URL } from '@/lib/coordinator';
|
||||
import type { CrewCoordinator, SkateCrew } from '@/lib/types';
|
||||
import { nip19 } from 'nostr-tools';
|
||||
|
||||
|
|
@ -30,6 +32,12 @@ export function CrewCoordinatorCard({
|
|||
}) {
|
||||
const { coordinator } = crew;
|
||||
|
||||
// Which relay the messages actually travel over — a `wss://` coordinator URL
|
||||
// IS the relay; an `https://` one is addressed by pubkey over the shared
|
||||
// ContextVM relay (public). The copy below must not overclaim either way.
|
||||
const endpoint = resolveCordnEndpoint(crew);
|
||||
const overPublicRelay = endpoint?.relayUrl === CORDN_RELAY_URL;
|
||||
|
||||
if (!coordinator) {
|
||||
// Default state: private chat is ALREADY on for every crew (built-in NIP-17
|
||||
// giftwrap group chat, no setup). We say so plainly rather than implying a
|
||||
|
|
@ -92,15 +100,25 @@ export function CrewCoordinatorCard({
|
|||
|
||||
{coordinator.operator && <CoordinatorOperator pubkey={coordinator.operator} />}
|
||||
|
||||
{endpoint && (
|
||||
<p className="text-[11px] leading-relaxed text-gray-500">
|
||||
Delivered over{' '}
|
||||
<span className="font-mono text-gray-400">{urlDomain(endpoint.relayUrl) || endpoint.relayUrl}</span>
|
||||
{overPublicRelay ? ' — the shared ContextVM relay.' : " — your crew's own relay."}
|
||||
</p>
|
||||
)}
|
||||
|
||||
<div className="rounded-xl border border-emerald-500/15 bg-emerald-500/[0.04] p-3">
|
||||
<p className="flex items-center gap-1.5 text-xs font-bold text-emerald-300">
|
||||
<Lock className="h-3.5 w-3.5" /> Private chat is live
|
||||
</p>
|
||||
<p className="mt-1 text-[11px] leading-relaxed text-gray-500">
|
||||
The <span className="font-semibold text-gray-300">Chat</span> tab runs through this
|
||||
coordinator: messages are end-to-end encrypted (NIP-44, sealed to each member) and routed
|
||||
over ContextVM, so the coordinator only ever handles sealed traffic and nothing
|
||||
crew-specific hits a public relay.
|
||||
The <span className="font-semibold text-gray-300">Chat</span> tab routes through this
|
||||
coordinator: messages are end-to-end encrypted (NIP-44, sealed to each member), so it only
|
||||
ever handles sealed traffic.{' '}
|
||||
{overPublicRelay
|
||||
? 'Heads up: it is addressed over the shared ContextVM relay, so the ciphertext and its metadata (which crew, and when) are publicly visible — just not readable. Point the crew at a private wss:// relay to keep even that private.'
|
||||
: "Nothing crew-specific leaves your crew's own relay."}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue