From 98ff1e09e02bc7420e39d714290898c92c6ebe6b Mon Sep 17 00:00:00 2001 From: "shakespeare.diy" Date: Mon, 16 Feb 2026 16:54:01 -0600 Subject: [PATCH] New project created with Shakespeare Co-authored-by: shakespeare.diy --- .claude/skills/ai-chat/SKILL.md | 337 + .claude/skills/nostr-comments/SKILL.md | 59 + .claude/skills/nostr-direct-messages/SKILL.md | 478 + .claude/skills/nostr-infinite-scroll/SKILL.md | 77 + .github/workflows/deploy.yml | 56 + .github/workflows/test.yml | 30 + .gitignore | 33 + .gitlab-ci.yml | 27 + .mcp.json | 14 + .vscode/mcp.json | 14 + .vscode/settings.json | 3 + AGENTS.md | 1209 +++ components.json | 20 + eslint-rules/README.md | 146 + eslint-rules/index.js | 11 + eslint-rules/no-inline-script.js | 40 + eslint-rules/no-placeholder-comments.js | 45 + eslint-rules/require-webmanifest.js | 84 + eslint.config.js | 74 + index.html | 13 + opencode.json | 9 + package-lock.json | 8522 +++++++++++++++++ package.json | 97 + postcss.config.js | 6 + public/_redirects | 2 + public/robots.txt | 2 + src/App.test.tsx | 8 + src/App.tsx | 70 + src/AppRouter.tsx | 22 + src/components/AppProvider.tsx | 111 + src/components/DMProvider.tsx | 1524 +++ src/components/EditProfileForm.tsx | 349 + src/components/ErrorBoundary.tsx | 113 + src/components/NostrProvider.tsx | 70 + src/components/NostrSync.tsx | 62 + src/components/NoteContent.test.tsx | 136 + src/components/NoteContent.tsx | 142 + src/components/RelayListManager.tsx | 286 + src/components/ScrollToTop.tsx | 12 + src/components/WalletModal.tsx | 393 + src/components/ZapButton.tsx | 58 + src/components/ZapDialog.tsx | 463 + src/components/auth/AccountSwitcher.tsx | 79 + src/components/auth/LoginArea.tsx | 59 + src/components/auth/LoginDialog.tsx | 340 + src/components/auth/SignupDialog.tsx | 361 + src/components/comments/Comment.tsx | 153 + src/components/comments/CommentForm.tsx | 90 + src/components/comments/CommentsSection.tsx | 100 + src/components/dm/DMChatArea.tsx | 409 + src/components/dm/DMConversationList.tsx | 266 + src/components/dm/DMMessagingInterface.tsx | 84 + src/components/dm/DMStatusInfo.tsx | 214 + src/components/ui/accordion.tsx | 56 + src/components/ui/alert-dialog.tsx | 139 + src/components/ui/alert.tsx | 59 + src/components/ui/aspect-ratio.tsx | 5 + src/components/ui/avatar.tsx | 48 + src/components/ui/badge-variants.ts | 21 + src/components/ui/badge.tsx | 17 + src/components/ui/breadcrumb.tsx | 115 + src/components/ui/button-variants.ts | 30 + src/components/ui/button.tsx | 28 + src/components/ui/calendar.tsx | 64 + src/components/ui/card.tsx | 79 + src/components/ui/carousel.tsx | 260 + src/components/ui/chart.tsx | 363 + src/components/ui/checkbox.tsx | 28 + src/components/ui/collapsible.tsx | 9 + src/components/ui/command.tsx | 153 + src/components/ui/context-menu.tsx | 198 + src/components/ui/dialog.tsx | 120 + src/components/ui/drawer.tsx | 116 + src/components/ui/dropdown-menu.tsx | 198 + src/components/ui/form-utils.ts | 48 + src/components/ui/form.tsx | 133 + src/components/ui/hover-card.tsx | 27 + src/components/ui/input-otp.tsx | 69 + src/components/ui/input.tsx | 22 + src/components/ui/label.tsx | 24 + src/components/ui/menubar.tsx | 234 + src/components/ui/navigation-menu-variants.ts | 5 + src/components/ui/navigation-menu.tsx | 123 + src/components/ui/pagination.tsx | 118 + src/components/ui/popover.tsx | 29 + src/components/ui/progress.tsx | 26 + src/components/ui/radio-group.tsx | 42 + src/components/ui/resizable.tsx | 43 + src/components/ui/scroll-area.tsx | 46 + src/components/ui/select.tsx | 158 + src/components/ui/separator.tsx | 29 + src/components/ui/sheet.tsx | 131 + src/components/ui/sidebar-utils.ts | 52 + src/components/ui/sidebar.tsx | 724 ++ src/components/ui/skeleton.tsx | 15 + src/components/ui/slider.tsx | 26 + src/components/ui/switch.tsx | 27 + src/components/ui/table.tsx | 117 + src/components/ui/tabs.tsx | 53 + src/components/ui/textarea.tsx | 23 + src/components/ui/toast.tsx | 127 + src/components/ui/toaster.tsx | 33 + src/components/ui/toggle-group.tsx | 59 + src/components/ui/toggle-variants.ts | 23 + src/components/ui/toggle.tsx | 22 + src/components/ui/tooltip.tsx | 28 + src/contexts/AppContext.ts | 26 + src/contexts/DMContext.ts | 138 + src/contexts/NWCContext.tsx | 8 + src/hooks/useAppContext.ts | 14 + src/hooks/useAuthor.ts | 34 + src/hooks/useComments.ts | 98 + src/hooks/useConversationMessages.ts | 87 + src/hooks/useCurrentUser.ts | 48 + src/hooks/useDMContext.ts | 45 + src/hooks/useIsMobile.tsx | 19 + src/hooks/useLocalStorage.ts | 54 + src/hooks/useLoggedInAccounts.ts | 56 + src/hooks/useLoginActions.ts | 34 + src/hooks/useNWC.ts | 221 + src/hooks/useNWCContext.ts | 15 + src/hooks/useNostr.ts | 6 + src/hooks/useNostrPublish.ts | 42 + src/hooks/usePostComment.ts | 92 + src/hooks/useShakespeare.ts | 372 + src/hooks/useTheme.ts | 20 + src/hooks/useToast.ts | 182 + src/hooks/useUploadFile.ts | 26 + src/hooks/useWallet.ts | 41 + src/hooks/useZaps.ts | 350 + src/index.css | 101 + src/lib/dmConstants.ts | 86 + src/lib/dmMessageStore.ts | 117 + src/lib/dmUtils.ts | 98 + src/lib/genUserName.test.ts | 33 + src/lib/genUserName.ts | 29 + src/lib/polyfills.ts | 90 + src/lib/utils.ts | 6 + src/main.tsx | 17 + src/pages/Index.tsx | 25 + src/pages/Messages.tsx | 24 + src/pages/NIP19Page.tsx | 42 + src/pages/NotFound.tsx | 33 + src/test/ErrorBoundary.test.tsx | 60 + src/test/TestApp.tsx | 53 + src/test/setup.ts | 40 + src/vite-env.d.ts | 1 + tailwind.config.ts | 97 + tsconfig.json | 31 + vite.config.ts | 31 + 150 files changed, 25166 insertions(+) create mode 100644 .claude/skills/ai-chat/SKILL.md create mode 100644 .claude/skills/nostr-comments/SKILL.md create mode 100644 .claude/skills/nostr-direct-messages/SKILL.md create mode 100644 .claude/skills/nostr-infinite-scroll/SKILL.md create mode 100644 .github/workflows/deploy.yml create mode 100644 .github/workflows/test.yml create mode 100644 .gitignore create mode 100644 .gitlab-ci.yml create mode 100644 .mcp.json create mode 100644 .vscode/mcp.json create mode 100644 .vscode/settings.json create mode 100644 AGENTS.md create mode 100644 components.json create mode 100644 eslint-rules/README.md create mode 100644 eslint-rules/index.js create mode 100644 eslint-rules/no-inline-script.js create mode 100644 eslint-rules/no-placeholder-comments.js create mode 100644 eslint-rules/require-webmanifest.js create mode 100644 eslint.config.js create mode 100644 index.html create mode 100644 opencode.json create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 postcss.config.js create mode 100644 public/_redirects create mode 100644 public/robots.txt create mode 100644 src/App.test.tsx create mode 100644 src/App.tsx create mode 100644 src/AppRouter.tsx create mode 100644 src/components/AppProvider.tsx create mode 100644 src/components/DMProvider.tsx create mode 100644 src/components/EditProfileForm.tsx create mode 100644 src/components/ErrorBoundary.tsx create mode 100644 src/components/NostrProvider.tsx create mode 100644 src/components/NostrSync.tsx create mode 100644 src/components/NoteContent.test.tsx create mode 100644 src/components/NoteContent.tsx create mode 100644 src/components/RelayListManager.tsx create mode 100644 src/components/ScrollToTop.tsx create mode 100644 src/components/WalletModal.tsx create mode 100644 src/components/ZapButton.tsx create mode 100644 src/components/ZapDialog.tsx create mode 100644 src/components/auth/AccountSwitcher.tsx create mode 100644 src/components/auth/LoginArea.tsx create mode 100644 src/components/auth/LoginDialog.tsx create mode 100644 src/components/auth/SignupDialog.tsx create mode 100644 src/components/comments/Comment.tsx create mode 100644 src/components/comments/CommentForm.tsx create mode 100644 src/components/comments/CommentsSection.tsx create mode 100644 src/components/dm/DMChatArea.tsx create mode 100644 src/components/dm/DMConversationList.tsx create mode 100644 src/components/dm/DMMessagingInterface.tsx create mode 100644 src/components/dm/DMStatusInfo.tsx create mode 100644 src/components/ui/accordion.tsx create mode 100644 src/components/ui/alert-dialog.tsx create mode 100644 src/components/ui/alert.tsx create mode 100644 src/components/ui/aspect-ratio.tsx create mode 100644 src/components/ui/avatar.tsx create mode 100644 src/components/ui/badge-variants.ts create mode 100644 src/components/ui/badge.tsx create mode 100644 src/components/ui/breadcrumb.tsx create mode 100644 src/components/ui/button-variants.ts create mode 100644 src/components/ui/button.tsx create mode 100644 src/components/ui/calendar.tsx create mode 100644 src/components/ui/card.tsx create mode 100644 src/components/ui/carousel.tsx create mode 100644 src/components/ui/chart.tsx create mode 100644 src/components/ui/checkbox.tsx create mode 100644 src/components/ui/collapsible.tsx create mode 100644 src/components/ui/command.tsx create mode 100644 src/components/ui/context-menu.tsx create mode 100644 src/components/ui/dialog.tsx create mode 100644 src/components/ui/drawer.tsx create mode 100644 src/components/ui/dropdown-menu.tsx create mode 100644 src/components/ui/form-utils.ts create mode 100644 src/components/ui/form.tsx create mode 100644 src/components/ui/hover-card.tsx create mode 100644 src/components/ui/input-otp.tsx create mode 100644 src/components/ui/input.tsx create mode 100644 src/components/ui/label.tsx create mode 100644 src/components/ui/menubar.tsx create mode 100644 src/components/ui/navigation-menu-variants.ts create mode 100644 src/components/ui/navigation-menu.tsx create mode 100644 src/components/ui/pagination.tsx create mode 100644 src/components/ui/popover.tsx create mode 100644 src/components/ui/progress.tsx create mode 100644 src/components/ui/radio-group.tsx create mode 100644 src/components/ui/resizable.tsx create mode 100644 src/components/ui/scroll-area.tsx create mode 100644 src/components/ui/select.tsx create mode 100644 src/components/ui/separator.tsx create mode 100644 src/components/ui/sheet.tsx create mode 100644 src/components/ui/sidebar-utils.ts create mode 100644 src/components/ui/sidebar.tsx create mode 100644 src/components/ui/skeleton.tsx create mode 100644 src/components/ui/slider.tsx create mode 100644 src/components/ui/switch.tsx create mode 100644 src/components/ui/table.tsx create mode 100644 src/components/ui/tabs.tsx create mode 100644 src/components/ui/textarea.tsx create mode 100644 src/components/ui/toast.tsx create mode 100644 src/components/ui/toaster.tsx create mode 100644 src/components/ui/toggle-group.tsx create mode 100644 src/components/ui/toggle-variants.ts create mode 100644 src/components/ui/toggle.tsx create mode 100644 src/components/ui/tooltip.tsx create mode 100644 src/contexts/AppContext.ts create mode 100644 src/contexts/DMContext.ts create mode 100644 src/contexts/NWCContext.tsx create mode 100644 src/hooks/useAppContext.ts create mode 100644 src/hooks/useAuthor.ts create mode 100644 src/hooks/useComments.ts create mode 100644 src/hooks/useConversationMessages.ts create mode 100644 src/hooks/useCurrentUser.ts create mode 100644 src/hooks/useDMContext.ts create mode 100644 src/hooks/useIsMobile.tsx create mode 100644 src/hooks/useLocalStorage.ts create mode 100644 src/hooks/useLoggedInAccounts.ts create mode 100644 src/hooks/useLoginActions.ts create mode 100644 src/hooks/useNWC.ts create mode 100644 src/hooks/useNWCContext.ts create mode 100644 src/hooks/useNostr.ts create mode 100644 src/hooks/useNostrPublish.ts create mode 100644 src/hooks/usePostComment.ts create mode 100644 src/hooks/useShakespeare.ts create mode 100644 src/hooks/useTheme.ts create mode 100644 src/hooks/useToast.ts create mode 100644 src/hooks/useUploadFile.ts create mode 100644 src/hooks/useWallet.ts create mode 100644 src/hooks/useZaps.ts create mode 100644 src/index.css create mode 100644 src/lib/dmConstants.ts create mode 100644 src/lib/dmMessageStore.ts create mode 100644 src/lib/dmUtils.ts create mode 100644 src/lib/genUserName.test.ts create mode 100644 src/lib/genUserName.ts create mode 100644 src/lib/polyfills.ts create mode 100644 src/lib/utils.ts create mode 100644 src/main.tsx create mode 100644 src/pages/Index.tsx create mode 100644 src/pages/Messages.tsx create mode 100644 src/pages/NIP19Page.tsx create mode 100644 src/pages/NotFound.tsx create mode 100644 src/test/ErrorBoundary.test.tsx create mode 100644 src/test/TestApp.tsx create mode 100644 src/test/setup.ts create mode 100644 src/vite-env.d.ts create mode 100644 tailwind.config.ts create mode 100644 tsconfig.json create mode 100644 vite.config.ts diff --git a/.claude/skills/ai-chat/SKILL.md b/.claude/skills/ai-chat/SKILL.md new file mode 100644 index 00000000..4a8f596a --- /dev/null +++ b/.claude/skills/ai-chat/SKILL.md @@ -0,0 +1,337 @@ +--- +name: ai-chat +description: Build AI-powered chat interfaces, implement streaming responses, or integrate with Shakespeare AI. +--- + +# AI Integration with Shakespeare API + +Use the `useShakespeare` hook for AI chat completions with Nostr authentication. The API dynamically provides available models, so you should query them at runtime rather than hardcoding model names. + +```tsx +import { useShakespeare, type ChatMessage, type Model } from '@/hooks/useShakespeare'; + +const { + sendChatMessage, + sendStreamingMessage, + getAvailableModels, + isLoading, + error, + isAuthenticated +} = useShakespeare(); +``` + +#### Model Selector Component + +```tsx +function ModelSelector({ onModelSelect }: { onModelSelect: (modelId: string) => void }) { + const { getAvailableModels, isLoading } = useShakespeare(); + const [models, setModels] = useState([]); + const [selectedModel, setSelectedModel] = useState(''); + + useEffect(() => { + const fetchModels = async () => { + try { + const response = await getAvailableModels(); + // Sort models by total cost (cheapest first) + const sortedModels = response.data.sort((a, b) => { + const costA = parseFloat(a.pricing.prompt) + parseFloat(a.pricing.completion); + const costB = parseFloat(b.pricing.prompt) + parseFloat(b.pricing.completion); + return costA - costB; + }); + setModels(sortedModels); + + // Select the cheapest model by default + if (sortedModels.length > 0) { + const cheapestModel = sortedModels[0]; + setSelectedModel(cheapestModel.id); + onModelSelect(cheapestModel.id); + } + } catch (err) { + console.error('Failed to fetch models:', err); + } + }; + + fetchModels(); + }, [getAvailableModels, onModelSelect]); + + const handleModelChange = (modelId: string) => { + setSelectedModel(modelId); + onModelSelect(modelId); + }; + + return ( +
+ + +
+ ); +} +``` + +#### Basic Chat Example + +```tsx +function AIChat() { + const { sendChatMessage, isLoading, error, isAuthenticated } = useShakespeare(); + const [messages, setMessages] = useState([]); + const [input, setInput] = useState(''); + const [selectedModel, setSelectedModel] = useState(''); + + const handleSend = async () => { + if (!input.trim() || !selectedModel) return; + + const newMessages = [...messages, { role: 'user', content: input }]; + setMessages(newMessages); + setInput(''); + + try { + const response = await sendChatMessage(newMessages, selectedModel); + setMessages(prev => [...prev, { + role: 'assistant', + content: response.choices[0].message.content as string + }]); + } catch (err) { + console.error('Chat error:', err); + } + }; + + if (!isAuthenticated) return
Please log in to use AI
; + + return ( +
+ {error &&
{error}
} + + {/* Model Selection */} +
+ +
+ +
+ {messages.map((msg, i) => ( +
+ {msg.role}: {msg.content} +
+ ))} +
+ +
+ setInput(e.target.value)} + onKeyPress={(e) => e.key === 'Enter' && handleSend()} + className="flex-1 p-2 border rounded" + disabled={isLoading || !selectedModel} + placeholder={!selectedModel ? "Select a model first..." : "Type your message..."} + /> + +
+
+ ); +} +``` + +#### Streaming Chat Example + +```tsx +function StreamingChat() { + const { sendStreamingMessage } = useShakespeare(); + const [messages, setMessages] = useState([]); + const [currentResponse, setCurrentResponse] = useState(''); + const [selectedModel, setSelectedModel] = useState(''); + + const handleStreaming = async (content: string) => { + if (!selectedModel) return; + + setCurrentResponse(''); + const newMessages = [...messages, { role: 'user', content }]; + setMessages(newMessages); + + try { + await sendStreamingMessage(newMessages, selectedModel, (chunk) => { + setCurrentResponse(prev => prev + chunk); + }); + + // Add the complete response to messages + if (currentResponse.trim()) { + setMessages(prev => [...prev, { + role: 'assistant', + content: currentResponse + }]); + } + } catch (err) { + console.error('Streaming error:', err); + } finally { + setCurrentResponse(''); + } + }; + + return ( +
+ {/* Model selection UI */} +
+ +
+ + {/* Chat interface */} + {/* ... rest of your chat UI */} +
+ ); +} +``` + +#### Model Information + +Models are dynamically fetched from the Shakespeare API and include: + +- **Model ID**: Unique identifier for the model +- **Name**: Human-readable model name +- **Description**: Model capabilities and use cases +- **Context Window**: Maximum token limit for conversations +- **Pricing**: Cost per token for prompt and completion +- **Free Models**: Models with `pricing.prompt === "0"` and `pricing.completion === "0"` + +#### Key Points + +- **Dynamic Model Discovery**: Always fetch available models using `getAvailableModels()` +- **Authentication Required**: User must be logged in with Nostr account +- **Free vs Premium**: Check pricing to determine if model requires credits +- **Error Handling**: Handle `isLoading` and `error` states appropriately +- **Model Selection**: Provide UI for users to choose between available models + +## Implementation Patterns and Best Practices + +### Dialog Component Patterns + +When using Dialog components, always ensure accessibility compliance by including required elements: + +```tsx +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from "@/components/ui/dialog"; + +// ✅ Correct - Always include DialogHeader with DialogTitle + + + + Dialog Title + + Optional description for screen readers + + + {/* Dialog content */} + + +``` + +**Important**: Even if you want to hide the title visually, use the `VisuallyHidden` component to maintain accessibility: + +```tsx +import { VisuallyHidden } from "@radix-ui/react-visually-hidden"; + + + + Hidden Title for Screen Readers + + +``` + +### Streaming Response Handling + +When implementing streaming chat interfaces, always accumulate streamed content in a local variable before clearing the streaming state to prevent content loss: + +```tsx +const handleStreamingResponse = async () => { + let streamedContent = ''; // ✅ Use local variable to accumulate content + + try { + await sendStreamingMessage(messages, model, (chunk) => { + streamedContent += chunk; // ✅ Accumulate in local variable + setCurrentStreamingMessage(streamedContent); // Update UI + }); + + // ✅ Save accumulated content to persistent state + if (streamedContent.trim()) { + const assistantMessage: MessageDisplay = { + id: Date.now().toString(), + role: 'assistant', + content: streamedContent, // ✅ Use accumulated content + timestamp: new Date() + }; + setMessages(prev => [...prev, assistantMessage]); + } + } finally { + setCurrentStreamingMessage(''); // ✅ Clear streaming state after saving + } +}; +``` + +### Error Boundary Patterns + +Always wrap AI components with error boundaries and provide user-friendly error messages for common failure scenarios: + +```tsx +import { ErrorBoundary } from '@/components/ErrorBoundary'; +import { Alert, AlertDescription } from '@/components/ui/alert'; + +function AIChatWithErrorBoundary() { + return ( + + + + + Something went wrong with the AI chat. Please refresh the page and try again. + + + + } + > + + + ); +} + +// In your AI component, handle specific error types gracefully: +function useAIWithErrorHandling() { + const { sendChatMessage, error, clearError } = useShakespeare(); + + const sendMessage = async (messages: ChatMessage[], modelId: string) => { + try { + await sendChatMessage(messages, modelId); + } catch (err) { + // Handle specific error types with user-friendly messages + if (err.message.includes('401')) { + throw new Error('Authentication failed. Please log in again.'); + } else if (err.message.includes('402')) { + throw new Error('Insufficient credits. Please add credits to use premium features.'); + } else if (err.message.includes('network')) { + throw new Error('Network error. Please check your internet connection.'); + } + throw err; // Re-throw for error boundary + } + }; + + return { sendMessage, error, clearError }; +} +``` diff --git a/.claude/skills/nostr-comments/SKILL.md b/.claude/skills/nostr-comments/SKILL.md new file mode 100644 index 00000000..1ca90648 --- /dev/null +++ b/.claude/skills/nostr-comments/SKILL.md @@ -0,0 +1,59 @@ +--- +name: nostr-comments +description: Implement Nostr comment systems, add discussion features to posts/articles, or build community interaction features. +--- + +# Adding Nostr Comments Sections + +The project includes a complete commenting system using NIP-22 (kind 1111) comments that can be added to any Nostr event or URL. The `CommentsSection` component provides a full-featured commenting interface with threaded replies, user authentication, and real-time updates. + +## Basic Usage + +```tsx +import { CommentsSection } from "@/components/comments/CommentsSection"; + +function ArticlePage({ article }: { article: NostrEvent }) { + return ( +
+ {/* Your article content */} +
{/* article content */}
+ + {/* Comments section */} + +
+ ); +} +``` + +## Props and Customization + +The `CommentsSection` component accepts the following props: + +- **`root`** (required): The root event or URL to comment on. Can be a `NostrEvent` or `URL` object. +- **`title`**: Custom title for the comments section (default: "Comments") +- **`emptyStateMessage`**: Message shown when no comments exist (default: "No comments yet") +- **`emptyStateSubtitle`**: Subtitle for empty state (default: "Be the first to share your thoughts!") +- **`className`**: Additional CSS classes for styling +- **`limit`**: Maximum number of comments to load (default: 500) + +```tsx + +``` + +## Commenting on URLs + +The comments system also supports commenting on external URLs, making it useful for web pages, articles, or any online content: + +```tsx + +``` diff --git a/.claude/skills/nostr-direct-messages/SKILL.md b/.claude/skills/nostr-direct-messages/SKILL.md new file mode 100644 index 00000000..26f3ef56 --- /dev/null +++ b/.claude/skills/nostr-direct-messages/SKILL.md @@ -0,0 +1,478 @@ +--- +name: nostr-direct-messages +description: Implement Nostr direct messaging features, build chat interfaces, or work with encrypted peer-to-peer communication (NIP-04 and NIP-17). +--- + +# Direct Messaging on Nostr + +This project includes a complete direct messaging system supporting both NIP-04 (legacy) and NIP-17 (modern, more private) encrypted messages with real-time subscriptions, optimistic updates, and a persistent cache-first local storage. + +**The DM system is not enabled by default** - follow the setup instructions below to add messaging functionality to your application. + +## Setup Instructions + +### 1. Add DMProvider to Your App + +First, add the `DMProvider` to your app's provider tree in `src/App.tsx`: + +```tsx +// Add these imports at the top of src/App.tsx +import { DMProvider, type DMConfig } from '@/components/DMProvider'; +import { PROTOCOL_MODE } from '@/lib/dmConstants'; + +// Add this configuration before your App component +const dmConfig: DMConfig = { + // Enable or disable DMs entirely + enabled: true, // Set to true to enable messaging functionality + + // Choose one protocol mode: + // PROTOCOL_MODE.NIP04_ONLY - Force NIP-04 (legacy) only + // PROTOCOL_MODE.NIP17_ONLY - Force NIP-17 (private) only + // PROTOCOL_MODE.NIP04_OR_NIP17 - Allow users to choose between NIP-04 and NIP-17 (defaults to NIP-17) + protocolMode: PROTOCOL_MODE.NIP17_ONLY, // Recommended for new apps +}; + +// Then wrap your app components with DMProvider: +export function App() { + return ( + + + + + + + + + + + + + + + + + + + + ); +} +``` + +### 2. Configure DM Settings + +The `DMConfig` object supports the following options: + +- `enabled` (boolean, default: `false`) - Enable/disable entire DM system. When false, no messages are loaded, stored, or processed. +- `protocolMode` (ProtocolMode, default: `PROTOCOL_MODE.NIP17_ONLY`) - Which protocols to support: + - `PROTOCOL_MODE.NIP04_ONLY` - Legacy encryption only + - `PROTOCOL_MODE.NIP17_ONLY` - Modern private messages (recommended) + - `PROTOCOL_MODE.NIP04_OR_NIP17` - Support both protocols (for backwards compatibility) + +**Note**: The DM system uses domain-based IndexedDB naming (`nostr-dm-store-${hostname}`) to prevent conflicts between multiple apps on the same domain. + +## Quick Start + +### 1. Send Messages + +```tsx +import { useDMContext } from '@/hooks/useDMContext'; +import { MESSAGE_PROTOCOL } from '@/lib/dmConstants'; + +function ComposeMessage({ recipientPubkey }: { recipientPubkey: string }) { + const { sendMessage } = useDMContext(); + const [content, setContent] = useState(''); + + const handleSend = async () => { + await sendMessage({ + recipientPubkey, + content, + protocol: MESSAGE_PROTOCOL.NIP17, // Uses NIP-44 encryption + gift wrapping + }); + setContent(''); + }; + + return ( +
{ e.preventDefault(); handleSend(); }}> +