refactor: Add human-friendly names and simplify Zapstore renderers

- kinds.ts: Add kind 32267 (App), update 30063 to "App Release", update 30267 to "App Collection"
- Extract PlatformIcon to shared component (zapstore/PlatformIcon.tsx)
- Update all renderer comments to use human-friendly terminology
- Remove unnecessary comments throughout Zapstore renderers
- Simplify code without changing functionality
This commit is contained in:
Claude
2026-01-11 20:00:22 +00:00
parent 8719c56f3c
commit 97a2689e28
8 changed files with 99 additions and 185 deletions

View File

@@ -78,9 +78,8 @@ function PlatformItem({ platform }: { platform: Platform }) {
}
/**
* Detail renderer for Kind 32267 - Zapstore App Metadata
* Shows comprehensive app information including screenshots
* Note: Zapstore helpers wrap getTagValue which caches internally
* Detail renderer for Kind 32267 - App
* Shows comprehensive app information including screenshots and platforms
*/
export function ZapstoreAppDetailRenderer({
event,

View File

@@ -8,71 +8,11 @@ import {
getAppSummary,
detectPlatforms,
} from "@/lib/zapstore-helpers";
import {
Globe,
Smartphone,
TabletSmartphone,
Monitor,
Laptop,
} from "lucide-react";
import type { Platform } from "@/lib/zapstore-helpers";
import { PlatformIcon } from "./zapstore/PlatformIcon";
/**
* Platform icon component with label
*/
function PlatformIcon({ platform }: { platform: Platform }) {
const iconClass = "size-4 text-muted-foreground";
const getPlatformLabel = () => {
switch (platform) {
case "android":
return "Android";
case "ios":
return "iOS";
case "web":
return "Web";
case "macos":
return "macOS";
case "windows":
return "Windows";
case "linux":
return "Linux";
default:
return platform;
}
};
const getIcon = () => {
switch (platform) {
case "android":
return <TabletSmartphone className={iconClass} />;
case "ios":
return <Smartphone className={iconClass} />;
case "web":
return <Globe className={iconClass} />;
case "macos":
return <Laptop className={iconClass} />;
case "windows":
case "linux":
return <Monitor className={iconClass} />;
default:
return null;
}
};
return (
<div className="flex items-center gap-1.5">
{getIcon()}
<span className="text-xs text-muted-foreground">
{getPlatformLabel()}
</span>
</div>
);
}
/**
* Renderer for Kind 32267 - Zapstore App Metadata
* Displays app name, icon, summary, and platform icons in feed
* Renderer for Kind 32267 - App Metadata
* Clean feed view with app name, summary, and platform icons
*/
export function ZapstoreAppRenderer({ event }: BaseEventProps) {
const appName = getAppName(event);
@@ -82,7 +22,6 @@ export function ZapstoreAppRenderer({ event }: BaseEventProps) {
return (
<BaseEventContainer event={event}>
<div className="flex flex-col gap-2">
{/* App Name */}
<ClickableEventTitle
event={event}
className="text-base font-semibold text-foreground"
@@ -90,14 +29,12 @@ export function ZapstoreAppRenderer({ event }: BaseEventProps) {
{appName}
</ClickableEventTitle>
{/* Summary */}
{summary && (
<p className="text-sm text-muted-foreground line-clamp-2">
{summary}
</p>
)}
{/* Platform Icons */}
{platforms.length > 0 && (
<div className="flex items-center gap-2">
{platforms.map((platform) => (

View File

@@ -8,78 +8,18 @@ import {
detectPlatforms,
getCurationSetIdentifier,
} from "@/lib/zapstore-helpers";
import type { Platform } from "@/lib/zapstore-helpers";
import { useNostrEvent } from "@/hooks/useNostrEvent";
import { useGrimoire } from "@/core/state";
import { UserName } from "../UserName";
import {
Package,
Globe,
Smartphone,
TabletSmartphone,
Monitor,
Laptop,
} from "lucide-react";
import { Package } from "lucide-react";
import { PlatformIcon } from "./zapstore/PlatformIcon";
interface ZapstoreAppSetDetailRendererProps {
event: NostrEvent;
}
/**
* Platform icon component with label
*/
function PlatformIcon({ platform }: { platform: Platform }) {
const iconClass = "size-4 text-muted-foreground";
const getPlatformLabel = () => {
switch (platform) {
case "android":
return "Android";
case "ios":
return "iOS";
case "web":
return "Web";
case "macos":
return "macOS";
case "windows":
return "Windows";
case "linux":
return "Linux";
default:
return platform;
}
};
const getIcon = () => {
switch (platform) {
case "android":
return <TabletSmartphone className={iconClass} />;
case "ios":
return <Smartphone className={iconClass} />;
case "web":
return <Globe className={iconClass} />;
case "macos":
return <Laptop className={iconClass} />;
case "windows":
case "linux":
return <Monitor className={iconClass} />;
default:
return null;
}
};
return (
<div className="flex items-center gap-1.5">
{getIcon()}
<span className="text-xs text-muted-foreground">
{getPlatformLabel()}
</span>
</div>
);
}
/**
* Expanded app card showing full app details
* App card showing app details with icon, summary, and platforms
*/
function AppCard({
address,
@@ -113,7 +53,6 @@ function AppCard({
return (
<div className="p-4 bg-muted/20 rounded-lg border border-border flex gap-4 hover:bg-muted/30 transition-colors">
{/* App Icon */}
{iconUrl ? (
<img
src={iconUrl}
@@ -127,9 +66,7 @@ function AppCard({
</div>
)}
{/* App Info */}
<div className="flex-1 flex flex-col gap-2 min-w-0">
{/* App Name */}
<button
onClick={handleClick}
className="text-lg font-semibold hover:underline cursor-crosshair text-left"
@@ -137,14 +74,12 @@ function AppCard({
{appName}
</button>
{/* Summary */}
{summary && (
<p className="text-sm text-muted-foreground line-clamp-2">
{summary}
</p>
)}
{/* Platform Icons */}
{platforms.length > 0 && (
<div className="flex items-center gap-2">
{platforms.map((platform) => (
@@ -158,8 +93,8 @@ function AppCard({
}
/**
* Detail renderer for Kind 30267 - Zapstore App Curation Set
* Shows comprehensive view of all apps in the collection
* Detail renderer for Kind 30267 - App Collection
* Displays all apps in the collection with comprehensive metadata
*/
export function ZapstoreAppSetDetailRenderer({
event,
@@ -170,19 +105,15 @@ export function ZapstoreAppSetDetailRenderer({
return (
<div className="flex flex-col gap-6 p-6">
{/* Header Section */}
<div className="flex flex-col gap-3">
<h1 className="text-3xl font-bold">{setName}</h1>
{/* Metadata */}
<div className="grid grid-cols-2 gap-4 text-sm">
{/* Curator */}
<div className="flex flex-col gap-1">
<h3 className="text-muted-foreground">Curated by</h3>
<UserName pubkey={event.pubkey} />
</div>
{/* Identifier */}
{identifier && (
<div className="flex flex-col gap-1">
<h3 className="text-muted-foreground">Collection ID</h3>
@@ -193,13 +124,11 @@ export function ZapstoreAppSetDetailRenderer({
)}
</div>
{/* App Count */}
<p className="text-muted-foreground">
{apps.length} {apps.length === 1 ? "app" : "apps"} in this collection
</p>
</div>
{/* Apps Section */}
<div className="flex flex-col gap-3">
<h2 className="text-xl font-semibold">Apps</h2>

View File

@@ -12,9 +12,6 @@ import { useNostrEvent } from "@/hooks/useNostrEvent";
import { useGrimoire } from "@/core/state";
import { Package } from "lucide-react";
/**
* Individual app item - fetches and displays app info
*/
function AppItem({
address,
}: {
@@ -44,8 +41,8 @@ function AppItem({
}
/**
* Renderer for Kind 30267 - Zapstore App Curation Set
* Displays collection name and list of all apps with compact layout
* Renderer for Kind 30267 - App Collection
* Compact feed view listing all apps similar to relay lists
*/
export function ZapstoreAppSetRenderer({ event }: BaseEventProps) {
const setName = getCurationSetName(event);
@@ -54,7 +51,6 @@ export function ZapstoreAppSetRenderer({ event }: BaseEventProps) {
return (
<BaseEventContainer event={event}>
<div className="flex flex-col gap-2">
{/* Collection Name */}
<ClickableEventTitle
event={event}
className="text-base font-semibold text-foreground"
@@ -62,12 +58,10 @@ export function ZapstoreAppSetRenderer({ event }: BaseEventProps) {
{setName}
</ClickableEventTitle>
{/* App Count */}
<p className="text-sm text-muted-foreground">
{apps.length} {apps.length === 1 ? "app" : "apps"}
</p>
{/* App List - Show all apps with compact spacing like relay lists */}
{apps.length > 0 && (
<div className="flex flex-col gap-0.5">
{apps.map((ref, idx) => (

View File

@@ -23,8 +23,8 @@ interface ZapstoreReleaseDetailRendererProps {
}
/**
* Detail renderer for Kind 30063 - Zapstore Release
* Shows comprehensive release information including file metadata
* Detail renderer for Kind 30063 - App Release
* Shows release information with embedded file metadata
*/
export function ZapstoreReleaseDetailRenderer({
event,
@@ -35,12 +35,10 @@ export function ZapstoreReleaseDetailRenderer({
const fileEventId = getReleaseFileEventId(event);
const appPointer = getReleaseAppPointer(event);
// Fetch related events
const appEvent = useNostrEvent(appPointer || undefined);
// Load file event with release event as context for better relay selection
const fileEvent = useNostrEvent(
fileEventId ? { id: fileEventId } : undefined,
event, // Pass release event as context to use author's relays
event,
);
const appName = appEvent ? getAppName(appEvent) : appPointer?.identifier;
@@ -54,9 +52,7 @@ export function ZapstoreReleaseDetailRenderer({
return (
<div className="flex flex-col gap-6 p-6 max-w-4xl mx-auto">
{/* Header Section */}
<div className="flex gap-4">
{/* App Icon or Package Icon */}
{appIcon ? (
<img
src={appIcon}
@@ -70,7 +66,6 @@ export function ZapstoreReleaseDetailRenderer({
</div>
)}
{/* Release Title */}
<div className="flex flex-col gap-2 flex-1 min-w-0">
<div className="flex items-baseline gap-2 flex-wrap">
<h1 className="text-3xl font-bold">{appName || "Release"}</h1>
@@ -81,7 +76,6 @@ export function ZapstoreReleaseDetailRenderer({
)}
</div>
{/* App Link */}
{appName && appPointer && (
<button
onClick={handleAppClick}
@@ -94,15 +88,12 @@ export function ZapstoreReleaseDetailRenderer({
</div>
</div>
{/* Metadata Grid */}
<div className="grid grid-cols-2 gap-4 text-sm">
{/* Publisher */}
<div className="flex flex-col gap-1">
<h3 className="text-muted-foreground">Publisher</h3>
<UserName pubkey={event.pubkey} />
</div>
{/* Release Identifier */}
{identifier && (
<div className="flex flex-col gap-1">
<h3 className="text-muted-foreground">Release ID</h3>
@@ -113,7 +104,6 @@ export function ZapstoreReleaseDetailRenderer({
)}
</div>
{/* File Metadata Section */}
{fileEvent && (
<div className="flex flex-col gap-3">
<h2 className="text-xl font-semibold flex items-center gap-2">
@@ -126,7 +116,6 @@ export function ZapstoreReleaseDetailRenderer({
</div>
)}
{/* Loading/Missing States */}
{fileEventId && !fileEvent && (
<div className="flex items-center gap-2 p-4 bg-muted/20 rounded-lg text-muted-foreground">
<FileDown className="size-5" />

View File

@@ -15,8 +15,8 @@ import { Badge } from "@/components/ui/badge";
import { Package, FileDown } from "lucide-react";
/**
* Renderer for Kind 30063 - Zapstore Release
* Displays release version and links to app and file metadata
* Renderer for Kind 30063 - App Release
* Displays release version with links to app and download file
*/
export function ZapstoreReleaseRenderer({ event }: BaseEventProps) {
const { addWindow } = useGrimoire();
@@ -24,7 +24,6 @@ export function ZapstoreReleaseRenderer({ event }: BaseEventProps) {
const fileEventId = getReleaseFileEventId(event);
const appPointer = getReleaseAppPointer(event);
// Fetch app metadata to show app name
const appEvent = useNostrEvent(appPointer || undefined);
const appName = appEvent ? getAppName(appEvent) : appPointer?.identifier;
@@ -43,7 +42,6 @@ export function ZapstoreReleaseRenderer({ event }: BaseEventProps) {
return (
<BaseEventContainer event={event}>
<div className="flex flex-col gap-2">
{/* Title */}
<ClickableEventTitle
event={event}
className="text-base font-semibold text-foreground"
@@ -56,9 +54,7 @@ export function ZapstoreReleaseRenderer({ event }: BaseEventProps) {
)}
</ClickableEventTitle>
{/* Links */}
<div className="flex items-center gap-3 flex-wrap text-sm">
{/* App Link - show app name with icon */}
{appName && (
<button
onClick={handleAppClick}
@@ -69,7 +65,6 @@ export function ZapstoreReleaseRenderer({ event }: BaseEventProps) {
</button>
)}
{/* File Link */}
{fileEventId && (
<button
onClick={handleFileClick}

View File

@@ -0,0 +1,71 @@
import type { Platform } from "@/lib/zapstore-helpers";
import {
Globe,
Smartphone,
TabletSmartphone,
Monitor,
Laptop,
} from "lucide-react";
interface PlatformIconProps {
platform: Platform;
showLabel?: boolean;
size?: "sm" | "md";
}
export function PlatformIcon({
platform,
showLabel = true,
size = "sm",
}: PlatformIconProps) {
const iconClass = size === "sm" ? "size-3" : "size-4";
const getPlatformLabel = () => {
switch (platform) {
case "android":
return "Android";
case "ios":
return "iOS";
case "web":
return "Web";
case "macos":
return "macOS";
case "windows":
return "Windows";
case "linux":
return "Linux";
default:
return platform;
}
};
const getIcon = () => {
const className = `${iconClass} text-muted-foreground`;
switch (platform) {
case "android":
return <TabletSmartphone className={className} />;
case "ios":
return <Smartphone className={className} />;
case "web":
return <Globe className={className} />;
case "macos":
return <Laptop className={className} />;
case "windows":
case "linux":
return <Monitor className={className} />;
default:
return null;
}
};
return (
<div className="flex items-center gap-1.5">
{getIcon()}
{showLabel && (
<span className="text-xs text-muted-foreground">
{getPlatformLabel()}
</span>
)}
</div>
);
}

View File

@@ -1172,8 +1172,8 @@ export const EVENT_KINDS: Record<number | string, EventKind> = {
// },
30063: {
kind: 30063,
name: "Release Artifact Set",
description: "Release artifact sets",
name: "App Release",
description: "Application release with version and files",
nip: "51",
icon: Package,
},
@@ -1193,8 +1193,8 @@ export const EVENT_KINDS: Record<number | string, EventKind> = {
},
30267: {
kind: 30267,
name: "App Curation",
description: "App curation sets",
name: "App Collection",
description: "Curated collection of applications",
nip: "51",
icon: BookHeart,
},
@@ -1345,13 +1345,13 @@ export const EVENT_KINDS: Record<number | string, EventKind> = {
nip: "89",
icon: Package,
},
// 32267: {
// kind: 32267,
// name: "Software App",
// description: "Software Application",
// nip: "",
// icon: AppWindow,
// },
32267: {
kind: 32267,
name: "App",
description: "Application metadata with platforms and screenshots",
nip: "",
icon: Package,
},
34235: {
kind: 34235,
name: "Video",