fix: use RelayLink for relay URLs, remove redundant kindTag, add relay hints

Provider list renderers now use RelayLink instead of raw relay URL
strings — shows favicon, insecure ws:// warning, opens relay detail
on click.

Remove kindTag display from provider cards — it's an internal
protocol detail redundant in the UI context.

Pass relay hints from provider entries to UserName so profiles can
be fetched from the relay the provider actually publishes to.

Add UserName relayHints prop (forwarded to useProfile).

Add RelayLink and UserName to Shared Components section in CLAUDE.md
so they're consistently used across the codebase.

https://claude.ai/code/session_01XjwLaShFSVPR5gNA7iUjuB
This commit is contained in:
Claude
2026-02-19 22:22:39 +00:00
parent 2c9010979a
commit 2740f98c8c
4 changed files with 38 additions and 35 deletions

View File

@@ -340,6 +340,9 @@ This allows `applyTheme()` to switch themes at runtime.
- Example: `formatTimestamp(event.created_at, "long")` instead of manual `toLocaleDateString()`
- **File Organization**: By domain (`nostr/`, `ui/`, `services/`, `hooks/`, `lib/`)
- **State Logic**: All UI state mutations go through `src/core/logic.ts` pure functions
- **Shared Components**:
- **`RelayLink`** (`src/components/nostr/RelayLink.tsx`): Always use this when displaying relay URLs. It shows the relay favicon, handles insecure `ws://` warnings, and opens the relay detail window on click. Never render raw relay URL strings — use `<RelayLink url={relayUrl} />` instead.
- **`UserName`** (`src/components/nostr/UserName.tsx`): Always use this for displaying user pubkeys. Accepts optional `relayHints` prop for fetching profiles from specific relays.
## Important Patterns

View File

@@ -10,6 +10,7 @@ interface UserNameProps {
pubkey: string;
isMention?: boolean;
className?: string;
relayHints?: string[];
}
/**
@@ -25,9 +26,14 @@ interface UserNameProps {
* - Premium supporters (2.1k+ sats/month): Flame badge in their username color
* - Regular supporters: Yellow flame badge (no username color change)
*/
export function UserName({ pubkey, isMention, className }: UserNameProps) {
export function UserName({
pubkey,
isMention,
className,
relayHints,
}: UserNameProps) {
const { addWindow, state } = useGrimoire();
const profile = useProfile(pubkey);
const profile = useProfile(pubkey, relayHints);
const isGrimoire = isGrimoireMember(pubkey);
const { isSupporter, isPremiumSupporter } = useIsSupporter(pubkey);
const displayName = getDisplayName(pubkey, profile);

View File

@@ -1,12 +1,11 @@
import { NostrEvent } from "@/types/nostr";
import { UserName } from "../UserName";
import { RelayLink } from "../RelayLink";
import {
getTrustedProviders,
hasEncryptedProviders,
formatKindTag,
} from "@/lib/nip85-helpers";
import { Shield, Lock, Radio } from "lucide-react";
import { Badge } from "@/components/ui/badge";
import { Shield, Lock } from "lucide-react";
/**
* Trusted Provider List Detail Renderer (Kind 10040)
@@ -50,28 +49,18 @@ export function TrustedProviderListDetailRenderer({
<div className="flex flex-col gap-2">
{providers.map((p, i) => (
<div
key={`${p.kindTag}-${i}`}
className="flex flex-col gap-1.5 p-3 rounded-md border border-border/50 bg-muted/30"
key={`${p.servicePubkey}-${i}`}
className="flex flex-col gap-2 p-3 rounded-md border border-border/50 bg-muted/30"
>
{/* Metric badge */}
<Badge
variant="secondary"
className="w-fit h-5 px-1.5 text-xs font-mono"
>
{formatKindTag(p.kindTag)}
</Badge>
{/* Provider */}
<div className="flex items-center gap-1.5 text-sm">
<span className="text-muted-foreground text-xs">Provider:</span>
<UserName pubkey={p.servicePubkey} className="text-sm" />
</div>
{/* Provider name */}
<UserName
pubkey={p.servicePubkey}
relayHints={[p.relay]}
className="text-sm font-medium"
/>
{/* Relay */}
<div className="flex items-center gap-1.5 text-xs text-muted-foreground">
<Radio className="size-3 shrink-0" />
<span className="font-mono break-all">{p.relay}</span>
</div>
<RelayLink url={p.relay} showInboxOutbox={false} />
</div>
))}
</div>

View File

@@ -5,6 +5,7 @@ import {
} from "./BaseEventRenderer";
import { Badge } from "@/components/ui/badge";
import { UserName } from "../UserName";
import { RelayLink } from "../RelayLink";
import {
getTrustedProviders,
hasEncryptedProviders,
@@ -33,7 +34,7 @@ export function TrustedProviderListRenderer({ event }: BaseEventProps) {
{/* Compact summary */}
<div className="flex flex-wrap items-center gap-1.5">
<Badge variant="outline" className="h-5 px-1.5 text-muted-foreground">
{providers.length} mapping{providers.length !== 1 ? "s" : ""}
{providers.length} provider{providers.length !== 1 ? "s" : ""}
</Badge>
{hasEncrypted && (
<Badge
@@ -46,21 +47,25 @@ export function TrustedProviderListRenderer({ event }: BaseEventProps) {
)}
</div>
{/* Provider preview: show unique service keys */}
{/* Provider preview */}
{previewProviders.length > 0 && (
<div className="flex flex-col gap-0.5">
<div className="flex flex-col gap-1">
{previewProviders.map((p, i) => (
<div
key={`${p.kindTag}-${i}`}
key={`${p.servicePubkey}-${i}`}
className="flex items-center gap-1.5 text-xs text-muted-foreground"
>
<Badge
variant="secondary"
className="h-4 px-1 text-[10px] font-mono shrink-0"
>
{p.kindTag}
</Badge>
<UserName pubkey={p.servicePubkey} className="text-xs" />
<UserName
pubkey={p.servicePubkey}
relayHints={[p.relay]}
className="text-xs"
/>
<span className="text-muted-foreground/50">on</span>
<RelayLink
url={p.relay}
showInboxOutbox={false}
className="inline-flex"
/>
</div>
))}
{providers.length > 3 && (