From b9180868b7d919820783285c4000fdaca9280ee3 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 21 Dec 2025 11:57:19 +0000 Subject: [PATCH] fix: address PR review comments - nostr-kinds.ts: clarify exclusive boundary comments for kind ranges - useStable.ts: use JSON.stringify instead of join(",") to prevent false positives with arrays containing commas - useProfile.ts: check abort signal before initiating database writes to prevent race conditions - KindRenderer.tsx: rename "Regular Lists" to "Replaceable Events", simplify redundant condition (isReplaceableKind includes kinds 0, 3), remove unused `kinds` import - BaseEventRenderer.tsx: remove duplicate comment --- src/components/KindRenderer.tsx | 6 +++--- .../nostr/kinds/BaseEventRenderer.tsx | 1 - src/hooks/useProfile.ts | 21 ++++++++----------- src/hooks/useStable.ts | 7 +++---- src/lib/nostr-kinds.ts | 10 +++++---- 5 files changed, 21 insertions(+), 24 deletions(-) diff --git a/src/components/KindRenderer.tsx b/src/components/KindRenderer.tsx index 42cfda9..da229ba 100644 --- a/src/components/KindRenderer.tsx +++ b/src/components/KindRenderer.tsx @@ -1,5 +1,4 @@ import { getKindInfo } from "@/constants/kinds"; -import { kinds } from "nostr-tools"; import { NIPBadge } from "./NIPBadge"; import { Copy, CopyCheck } from "lucide-react"; import { Button } from "./ui/button"; @@ -190,7 +189,7 @@ function getKindCategory(kind: number): string { if (kind >= 20 && kind <= 39) return "Media & Content"; if (kind >= 40 && kind <= 49) return "Channels"; if (kind >= 1000 && kind <= 9999) return "Application Specific"; - if (isReplaceableKind(kind)) return "Regular Lists"; + if (isReplaceableKind(kind)) return "Replaceable Events"; if (isEphemeralKind(kind)) return "Ephemeral Events"; if (isParameterizedReplaceableKind(kind)) return "Parameterized Replaceable"; if (kind >= 40000) return "Custom/Experimental"; @@ -201,7 +200,8 @@ function getKindCategory(kind: number): string { * Determine the replaceability of an event kind */ function getEventType(kind: number): string { - if (kind === kinds.Metadata || kind === kinds.Contacts || isReplaceableKind(kind)) { + // nostr-tools' isReplaceableKind already includes kinds 0 (Metadata) and 3 (Contacts) + if (isReplaceableKind(kind)) { return "Replaceable"; } if (isParameterizedReplaceableKind(kind)) { diff --git a/src/components/nostr/kinds/BaseEventRenderer.tsx b/src/components/nostr/kinds/BaseEventRenderer.tsx index eba6107..debf32d 100644 --- a/src/components/nostr/kinds/BaseEventRenderer.tsx +++ b/src/components/nostr/kinds/BaseEventRenderer.tsx @@ -225,7 +225,6 @@ export function ClickableEventTitle({ // For replaceable/parameterized replaceable events, use AddressPointer if (isAddressableKind(event.kind)) { - // For replaceable/parameterized replaceable events, use AddressPointer const dTag = getTagValue(event, "d") || ""; pointer = { kind: event.kind, diff --git a/src/hooks/useProfile.ts b/src/hooks/useProfile.ts index 2c4423c..00b5f6b 100644 --- a/src/hooks/useProfile.ts +++ b/src/hooks/useProfile.ts @@ -50,21 +50,18 @@ export function useProfile(pubkey?: string): ProfileContent | undefined { return; } - // Save to IndexedDB (fire and forget if aborted) - const savePromise = db.profiles.put({ - ...profileData, - pubkey, - created_at: fetchedEvent.created_at, - }); + // Only update state and cache if not aborted + if (controller.signal.aborted) return; - // Only update state if not aborted - if (!controller.signal.aborted) { - setProfile(profileData); - } + setProfile(profileData); - // Await save after state update to avoid blocking UI + // Save to IndexedDB after state update to avoid blocking UI try { - await savePromise; + await db.profiles.put({ + ...profileData, + pubkey, + created_at: fetchedEvent.created_at, + }); } catch (err) { // Log but don't throw - cache failure shouldn't break the UI console.error("[useProfile] Failed to cache profile:", err); diff --git a/src/hooks/useStable.ts b/src/hooks/useStable.ts index 6013e44..1ddb430 100644 --- a/src/hooks/useStable.ts +++ b/src/hooks/useStable.ts @@ -30,21 +30,20 @@ export function useStableValue( /** * Stabilize a string array for use in dependency arrays * - * Optimized version of useStableValue for string arrays. - * Uses join(",") instead of JSON.stringify for better performance. + * Uses JSON.stringify for safe serialization (handles arrays with commas in elements). * * @param arr - The array to stabilize * @returns The memoized array * * @example * ```typescript - * // Instead of: useMemo(() => relays, [relays.join(",")]) + * // Instead of: useMemo(() => relays, [JSON.stringify(relays)]) * const stableRelays = useStableArray(relays); * ``` */ export function useStableArray(arr: T[]): T[] { // eslint-disable-next-line react-hooks/exhaustive-deps - return useMemo(() => arr, [arr.join(",")]); + return useMemo(() => arr, [JSON.stringify(arr)]); } /** diff --git a/src/lib/nostr-kinds.ts b/src/lib/nostr-kinds.ts index 7ce4a41..126ee26 100644 --- a/src/lib/nostr-kinds.ts +++ b/src/lib/nostr-kinds.ts @@ -26,14 +26,16 @@ import { } from "nostr-tools/kinds"; // Kind range boundaries (NIP-01) - exported for display purposes only +// Note: END values are exclusive (e.g., REGULAR covers 0-9999, not 10000) +// Exception: kinds 0 (Metadata) and 3 (Contacts) are replaceable despite being < 10000 export const REGULAR_START = 0; -export const REGULAR_END = 10000; +export const REGULAR_END = 10000; // exclusive: regular kinds are 0-9999 (except 0, 3) export const REPLACEABLE_START = 10000; -export const REPLACEABLE_END = 20000; +export const REPLACEABLE_END = 20000; // exclusive: replaceable kinds are 10000-19999 export const EPHEMERAL_START = 20000; -export const EPHEMERAL_END = 30000; +export const EPHEMERAL_END = 30000; // exclusive: ephemeral kinds are 20000-29999 export const PARAMETERIZED_REPLACEABLE_START = 30000; -export const PARAMETERIZED_REPLACEABLE_END = 40000; +export const PARAMETERIZED_REPLACEABLE_END = 40000; // exclusive: parameterized replaceable are 30000-39999 /** * Check if a kind is parameterized replaceable (NIP-01)