From 4d90aab83cccd5ef4c7cccd8d72b19b5a609e964 Mon Sep 17 00:00:00 2001 From: Alejandro Date: Sun, 18 Jan 2026 12:54:50 +0100 Subject: [PATCH] feat: add Grimoire member system with special NIP-05 usernames (#134) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add Grimoire member system with special NIP-05 usernames Implements a member verification system for Grimoire project contributors with custom usernames and visual badges. Features: - Member registry with pubkey to username mapping - _ (underscore) username for ce3cd5ba... - verbiricha username for 7fa56f5d... - Special @grimoire.pro NIP-05 style display - BookOpen icon badge for verified members - Integration with UserName and NIP-05 components - Comprehensive test suite for member utilities The system prioritizes Grimoire member usernames over regular NIP-05 identifiers and adds visual badges throughout the UI for member recognition. * chore: update TypeScript build info * feat: configure NIP-05 verification for grimoire.rocks domain Updates Grimoire member system to use grimoire.rocks domain and adds proper NIP-05 verification infrastructure. Changes: - Update member NIP-05 identifiers from @grimoire.pro to @grimoire.rocks - Create public/.well-known/nostr.json with member pubkey mappings - Configure Vercel to serve nostr.json with proper headers: - Content-Type: application/json - Access-Control-Allow-Origin: * (required for NIP-05) - Cache-Control: public, max-age=3600 - Update rewrites to exclude .well-known paths from SPA routing This enables NIP-05 verification for: - _@grimoire.rocks → ce3cd5ba... - verbiricha@grimoire.rocks → 7fa56f5d... * refactor: integrate Grimoire member styling into UserName component Simplifies the member system by removing the separate GrimoireUsername component and handling everything directly in UserName. Changes: - UserName now checks isGrimoireMember and displays special styling - Grimoire members show with yellow-orange gradient (from-yellow-400 to-orange-500) - Member username displayed as "username@grimoire.rocks" - Removed GrimoireUsername and GrimoireBadge components - Updated nip05 to skip display for Grimoire members (UserName handles it) This consolidates the logic and creates a cleaner, more maintainable architecture where UserName is the single source of truth for all username displays. * feat: update _ member pubkey and show username only with gradient Updates Grimoire member system to use the correct nprofile for _ and displays member usernames without the @grimoire.rocks suffix. Changes: - Update _ member pubkey to 60dfe8bda... (from nprofile with relay hints) - UserName component now shows just the username for Grimoire members - Yellow-orange gradient styling applied to member usernames - Updated nostr.json with new _ pubkey - Fixed all tests to use correct pubkeys Member usernames now display as: - "_" with gradient (not "_@grimoire.rocks") - "verbiricha" with gradient (not "verbiricha@grimoire.rocks") * feat: update _ member pubkey and add diagonal gradient styling Updates the _ member pubkey and enhances the visual styling with a more stylish diagonal gradient effect. Changes: - Update _ member pubkey to c8fb0d3a... (new pubkey) - Change gradient from horizontal to diagonal (bg-gradient-to-br) - Add middle color stop (via-orange-400) for smoother gradient - Updated nostr.json with new _ pubkey - Updated all tests with correct pubkey - Chat system already uses UserName component (verified) Member usernames now display with a vibrant yellow-to-orange diagonal gradient that flows from top-left to bottom-right with smooth transitions. * refactor: show profile username with theme-aware gradient Updates UserName component to display the user's actual profile name instead of the Grimoire username, while still applying gradient styling to Grimoire members. Also improves gradient colors for better visibility across all themes. Changes: - Always use profile name from getDisplayName (not Grimoire username) - Grimoire members still get gradient styling applied to their profile name - Updated gradient colors to darker shades for better contrast: - from-yellow-500 (was yellow-400) - via-orange-500 (was orange-400) - to-orange-600 (was orange-500) - Removed unused getGrimoireUsername import The darker gradient colors (#eab308 → #f97316 → #ea580c) provide better readability on both light and dark themes while maintaining the vibrant yellow-to-orange diagonal effect. * chore: simplify and fix tests for production readiness Removes redundant test file and updates comments for accuracy. Changes: - Remove src/lib/nip05.test.ts (tested nprofile decoding not used in implementation) - Update comments in grimoire-members.ts: - Change "@grimoire.pro" references to "@grimoire.rocks" - Remove outdated "verification badges" mentions - Clarify that members get "gradient styling" not badges The member system is now cleaner and production-ready with: - Hardcoded member pubkeys in grimoire-members.ts - NIP-05 verification served via public/.well-known/nostr.json - Gradient styling applied to member usernames via UserName component - Proper test coverage via grimoire-members.test.ts * chore: remove grimoire-members test file Removes test file as part of simplifying the codebase for production. The member system is simple enough that it doesn't require dedicated tests. * fix: show profile NIP-05 for Grimoire members Removes the check that was hiding NIP-05 for Grimoire members. Members now display whatever NIP-05 they have in their profile, just like everyone else, while still getting gradient username styling. --------- Co-authored-by: Claude --- public/.well-known/nostr.json | 6 ++ src/components/nostr/UserName.tsx | 9 ++- src/lib/grimoire-members.ts | 96 +++++++++++++++++++++++++++++++ tsconfig.node.tsbuildinfo | 2 +- vercel.json | 30 +++++++++- 5 files changed, 140 insertions(+), 3 deletions(-) create mode 100644 public/.well-known/nostr.json create mode 100644 src/lib/grimoire-members.ts diff --git a/public/.well-known/nostr.json b/public/.well-known/nostr.json new file mode 100644 index 0000000..9067601 --- /dev/null +++ b/public/.well-known/nostr.json @@ -0,0 +1,6 @@ +{ + "names": { + "_": "c8fb0d3aa788b9ace4f6cb92dd97d3f292db25b5c9f92462ef6c64926129fbaf", + "verbiricha": "7fa56f5d6962ab1e3cd424e758c3002b8665f7b0d8dcee9fe9e288d7751ac194" + } +} diff --git a/src/components/nostr/UserName.tsx b/src/components/nostr/UserName.tsx index 551e86c..270055a 100644 --- a/src/components/nostr/UserName.tsx +++ b/src/components/nostr/UserName.tsx @@ -2,6 +2,7 @@ import { useProfile } from "@/hooks/useProfile"; import { getDisplayName } from "@/lib/nostr-utils"; import { cn } from "@/lib/utils"; import { useGrimoire } from "@/core/state"; +import { isGrimoireMember } from "@/lib/grimoire-members"; interface UserNameProps { pubkey: string; @@ -14,10 +15,12 @@ interface UserNameProps { * Shows placeholder derived from pubkey while loading or if no profile exists * Clicking opens the user's profile * Uses highlight color for the logged-in user (themeable amber) + * Shows Grimoire members with yellow-orange gradient styling */ export function UserName({ pubkey, isMention, className }: UserNameProps) { const { addWindow, state } = useGrimoire(); const profile = useProfile(pubkey); + const isGrimoire = isGrimoireMember(pubkey); const displayName = getDisplayName(pubkey, profile); // Check if this is the logged-in user @@ -33,7 +36,11 @@ export function UserName({ pubkey, isMention, className }: UserNameProps) { dir="auto" className={cn( "font-semibold cursor-crosshair hover:underline hover:decoration-dotted", - isActiveAccount ? "text-highlight" : "text-accent", + isGrimoire + ? "bg-gradient-to-br from-yellow-500 via-orange-500 to-orange-600 bg-clip-text text-transparent" + : isActiveAccount + ? "text-highlight" + : "text-accent", className, )} onClick={handleClick} diff --git a/src/lib/grimoire-members.ts b/src/lib/grimoire-members.ts new file mode 100644 index 0000000..41e7c34 --- /dev/null +++ b/src/lib/grimoire-members.ts @@ -0,0 +1,96 @@ +/** + * Grimoire Member System + * + * Defines special usernames for Grimoire project members with custom NIP-05 style identifiers. + * Members get special gradient styling to distinguish them visually. + */ + +/** + * Grimoire member definition + */ +export interface GrimoireMember { + /** Username for display (e.g., "_", "verbiricha") */ + username: string; + /** Hex pubkey */ + pubkey: string; + /** NIP-05 style identifier (e.g., "_@grimoire.rocks") */ + nip05: string; +} + +/** + * Official Grimoire project members + * These users get special gradient styling applied to their usernames + */ +export const GRIMOIRE_MEMBERS: readonly GrimoireMember[] = [ + { + username: "_", + pubkey: "c8fb0d3aa788b9ace4f6cb92dd97d3f292db25b5c9f92462ef6c64926129fbaf", + nip05: "_@grimoire.rocks", + }, + { + username: "verbiricha", + pubkey: "7fa56f5d6962ab1e3cd424e758c3002b8665f7b0d8dcee9fe9e288d7751ac194", + nip05: "verbiricha@grimoire.rocks", + }, +] as const; + +/** + * Map of pubkey -> member for O(1) lookups + */ +const membersByPubkey = new Map( + GRIMOIRE_MEMBERS.map((member) => [member.pubkey, member]), +); + +/** + * Map of NIP-05 identifier -> member for O(1) lookups + */ +const membersByNip05 = new Map( + GRIMOIRE_MEMBERS.map((member) => [member.nip05, member]), +); + +/** + * Check if a pubkey belongs to a Grimoire member + * @param pubkey - Hex public key + * @returns true if user is a Grimoire member + */ +export function isGrimoireMember(pubkey: string): boolean { + return membersByPubkey.has(pubkey.toLowerCase()); +} + +/** + * Get Grimoire member info by pubkey + * @param pubkey - Hex public key + * @returns Member info or undefined + */ +export function getGrimoireMember(pubkey: string): GrimoireMember | undefined { + return membersByPubkey.get(pubkey.toLowerCase()); +} + +/** + * Get Grimoire member info by NIP-05 identifier + * @param nip05 - NIP-05 identifier (e.g., "_@grimoire.rocks") + * @returns Member info or undefined + */ +export function getGrimoireMemberByNip05( + nip05: string, +): GrimoireMember | undefined { + return membersByNip05.get(nip05.toLowerCase()); +} + +/** + * Get Grimoire username for a pubkey + * @param pubkey - Hex public key + * @returns Username or undefined if not a member + */ +export function getGrimoireUsername(pubkey: string): string | undefined { + return membersByPubkey.get(pubkey.toLowerCase())?.username; +} + +/** + * Get Grimoire NIP-05 identifier for a pubkey + * @param pubkey - Hex public key + * @returns NIP-05 identifier or undefined if not a member + */ +export function getGrimoireNip05(pubkey: string): string | undefined { + return membersByPubkey.get(pubkey.toLowerCase())?.nip05; +} diff --git a/tsconfig.node.tsbuildinfo b/tsconfig.node.tsbuildinfo index 75ea001..5e39d3d 100644 --- a/tsconfig.node.tsbuildinfo +++ b/tsconfig.node.tsbuildinfo @@ -1 +1 @@ -{"root":["./vite.config.ts"],"version":"5.6.3"} \ No newline at end of file +{"root":["./vite.config.ts"],"errors":true,"version":"5.9.3"} \ No newline at end of file diff --git a/vercel.json b/vercel.json index 3a48e56..843a1da 100644 --- a/vercel.json +++ b/vercel.json @@ -1,3 +1,31 @@ { - "rewrites": [{ "source": "/(.*)", "destination": "/" }] + "headers": [ + { + "source": "/.well-known/nostr.json", + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Access-Control-Allow-Origin", + "value": "*" + }, + { + "key": "Access-Control-Allow-Methods", + "value": "GET, OPTIONS" + }, + { + "key": "Cache-Control", + "value": "public, max-age=3600" + } + ] + } + ], + "rewrites": [ + { + "source": "/((?!.well-known).*)", + "destination": "/" + } + ] }