mirror of
https://github.com/purrgrammer/grimoire.git
synced 2026-06-04 09:41:13 +02:00
feat: show metric type labels in trusted assertion feed view
Add Label components to the assertion feed renderer so you can see at a glance which metrics an assertion carries (Followers, Posts, Zaps, etc.) instead of just numeric values. Also swap Badge → Label for the kind indicator for visual consistency. Replace hardcoded green/yellow/red rank colors with theme variables (success/warning/destructive) in both feed and detail renderers so the rank bar works correctly across all themes. Add Label to CLAUDE.md shared components list (22 imports across the codebase). https://claude.ai/code/session_01XjwLaShFSVPR5gNA7iUjuB
This commit is contained in:
@@ -343,6 +343,7 @@ This allows `applyTheme()` to switch themes at runtime.
|
||||
- **Shared Components** — Use these instead of rolling your own:
|
||||
- **`UserName`** (`src/components/nostr/UserName.tsx`): Always use for displaying user pubkeys. Shows display name, Grimoire member badge, supporter flame. Clicking opens profile. Accepts optional `relayHints` prop for fetching profiles from specific relays.
|
||||
- **`RelayLink`** (`src/components/nostr/RelayLink.tsx`): Always use for displaying relay URLs. Shows relay favicon, insecure `ws://` warnings, read/write badges, and opens relay detail window on click. Never render raw relay URL strings.
|
||||
- **`Label`** (`src/components/ui/label.tsx`): Dotted-border tag/badge for metadata labels (language, kind, status, metric type). Two sizes: `sm` (default) and `md`. Use instead of ad-hoc `<span>` tags for tag-like indicators.
|
||||
- **`RichText`** (`src/components/nostr/RichText.tsx`): Universal Nostr content renderer. Parses mentions, hashtags, custom emoji, media embeds, and nostr: references. Use for any event body text — never render `event.content` as a raw string.
|
||||
- **`CustomEmoji`** (`src/components/nostr/CustomEmoji.tsx`): Renders NIP-30 custom emoji images inline. Shows shortcode tooltip, handles load errors gracefully.
|
||||
|
||||
|
||||
@@ -21,10 +21,9 @@ import { Progress } from "@/components/ui/progress";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
function rankColor(rank: number) {
|
||||
if (rank >= 70) return { indicator: "bg-green-600", text: "text-green-600" };
|
||||
if (rank >= 40)
|
||||
return { indicator: "bg-yellow-600", text: "text-yellow-600" };
|
||||
return { indicator: "bg-red-600", text: "text-red-600" };
|
||||
if (rank >= 70) return { indicator: "bg-success", text: "text-success" };
|
||||
if (rank >= 40) return { indicator: "bg-warning", text: "text-warning" };
|
||||
return { indicator: "bg-destructive", text: "text-destructive" };
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,7 +3,7 @@ import {
|
||||
BaseEventContainer,
|
||||
ClickableEventTitle,
|
||||
} from "./BaseEventRenderer";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { UserName } from "../UserName";
|
||||
import { ExternalIdentifierInline } from "../ExternalIdentifierDisplay";
|
||||
import {
|
||||
@@ -20,10 +20,9 @@ import { Progress } from "@/components/ui/progress";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
function rankColor(rank: number) {
|
||||
if (rank >= 70) return { indicator: "bg-green-600", text: "text-green-600" };
|
||||
if (rank >= 40)
|
||||
return { indicator: "bg-yellow-600", text: "text-yellow-600" };
|
||||
return { indicator: "bg-red-600", text: "text-red-600" };
|
||||
if (rank >= 70) return { indicator: "bg-success", text: "text-success" };
|
||||
if (rank >= 40) return { indicator: "bg-warning", text: "text-warning" };
|
||||
return { indicator: "bg-destructive", text: "text-destructive" };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -122,6 +121,11 @@ function MetricsPreview({
|
||||
const tags = getAssertionTags(event);
|
||||
const rankTag = tags.find((t) => t.name === "rank");
|
||||
|
||||
// Collect metric type labels for the tag row
|
||||
const metricLabels = tags
|
||||
.filter((t) => t.name !== "rank" && t.name !== "t")
|
||||
.map((t) => ASSERTION_TAG_LABELS[t.name] || t.name);
|
||||
|
||||
let summaryMetrics: { label: string; value: string; unit?: string }[] = [];
|
||||
|
||||
if (event.kind === 30382) {
|
||||
@@ -192,6 +196,15 @@ function MetricsPreview({
|
||||
{/* Rank bar */}
|
||||
{rankTag && <RankBar rank={parseInt(rankTag.value, 10)} />}
|
||||
|
||||
{/* Metric type labels */}
|
||||
{metricLabels.length > 0 && (
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{metricLabels.map((l) => (
|
||||
<Label key={l}>{l}</Label>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Summary metrics */}
|
||||
{summaryMetrics.length > 0 && (
|
||||
<div className="flex flex-wrap gap-x-4 gap-y-1">
|
||||
@@ -221,14 +234,9 @@ export function TrustedAssertionRenderer({ event }: BaseEventProps) {
|
||||
return (
|
||||
<BaseEventContainer event={event}>
|
||||
<div className="flex flex-col gap-2">
|
||||
{/* Kind badge + subject as title */}
|
||||
{/* Kind label + subject as title */}
|
||||
<div className="flex flex-col gap-1">
|
||||
<Badge
|
||||
variant="outline"
|
||||
className="w-fit gap-1 h-5 px-1.5 text-muted-foreground"
|
||||
>
|
||||
{kindLabel}
|
||||
</Badge>
|
||||
<Label className="w-fit">{kindLabel}</Label>
|
||||
{subject && <SubjectTitle event={event} subject={subject} />}
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user