The project uses shadcn/ui components located in `@/components/ui`. These are unstyled, accessible components built with Radix UI and styled with Tailwind CSS. Available components include:
- **Pagination**: Controls for navigating between pages
- **Popover**: Floating content triggered by a button
- **Progress**: Progress indicator
- **RadioGroup**: Group of radio inputs
- **Resizable**: Resizable panels and interfaces
- **ScrollArea**: Scrollable container with custom scrollbars
- **Select**: Dropdown selection component
- **Separator**: Visual divider between content
- **Sheet**: Side-anchored dialog component
- **Sidebar**: Navigation sidebar component
- **Skeleton**: Loading placeholder
- **Slider**: Input for selecting a value from a range
- **Switch**: Toggle switch control
- **Table**: Data table with headers and rows
- **Tabs**: Tabbed interface component
- **Textarea**: Multi-line text input
- **Toast**: Toast notification component
- **ToggleGroup**: Group of toggle buttons
- **Toggle**: Two-state button
- **Tooltip**: Informational text that appears on hover
These components follow a consistent pattern using React's `forwardRef` and use the `cn()` utility for class name merging. Many are built on Radix UI primitives for accessibility and customized with Tailwind CSS.
The AI assistant's behavior and knowledge is defined by the CONTEXT.md file, which serves as the system prompt. To modify the assistant's instructions or add new project-specific guidelines:
- If any existing kind or NIP might offer the required functionality, use the `nostr__read_nip` tool to investigate thoroughly. Several NIPs may need to be read before making a decision.
- Only generate new kind numbers with the `nostr__generate_kind` tool if no existing suitable kinds are found after comprehensive research.
Knowing when to create a new kind versus reusing an existing kind requires careful judgement. Introducing new kinds means the project won't be interoperable with existing clients. But deviating too far from the schema of a particular kind can cause different interoperability issues.
1.**Thorough NIP Review**: Before considering a new kind, always perform a comprehensive review of existing NIPs and their associated kinds. Use the `nostr__read_nips_index` tool to get an overview, and then `nostr__read_nip` and `nostr__read_kind` to investigate any potentially relevant NIPs or kinds in detail. The goal is to find the closest existing solution.
2.**Prioritize Existing NIPs**: Always prefer extending or using existing NIPs over creating custom kinds, even if they require minor compromises in functionality.
3.**Interoperability vs. Perfect Fit**: Consider the trade-off between:
6.**Custom Kind Publishing**: When publishing events with custom kinds generated by `nostr__generate_kind`, always include a NIP-31 "alt" tag with a human-readable description of the event's purpose.
Kinds below 1000 are considered "legacy" kinds, and may have different storage characteristics based on their kind definition. For example, kind 1 is regular, while kind 3 is replaceable.
When designing new event kinds, the `content` field should be used for semantically important data that doesn't need to be queried by relays. **Structured JSON data generally shouldn't go in the content field** (kind 0 being an early exception).
#### Guidelines
- **Use content for**: Large text, freeform human-readable content, or existing industry-standard JSON formats (Tiled maps, FHIR, GeoJSON)
The file `NIP.md` is used by this project to define a custom Nostr protocol document. If the file doesn't exist, it means this project doesn't have any custom kinds associated with it.
Whenever new kinds are generated, the `NIP.md` file in the project must be created or updated to document the custom event schema. Whenever the schema of one of these custom events changes, `NIP.md` must also be updated accordingly.
The `useNostr` hook returns an object containing a `nostr` property, with `.query()` and `.event()` methods for querying and publishing Nostr events respectively.
**Critical**: Always minimize the number of separate queries to avoid rate limiting and improve performance. Combine related queries whenever possible.
**✅ Efficient - Single query with multiple kinds:**
```typescript
// Query multiple event types in one request
const events = await nostr.query([
{
kinds: [1, 6, 16], // All repost kinds in one query
When querying events, if the event kind being returned has required tags or required JSON fields in the content, the events should be filtered through a validator function. This is not generally needed for kinds such as 1, where all tags are optional and the content is freeform text, but is especially useful for custom kinds as well as kinds with strict requirements.
```typescript
// Example validator function for NIP-52 calendar events
function validateCalendarEvent(event: NostrEvent): boolean {
// Check if it's a calendar event kind
if (![31922, 31923].includes(event.kind)) return false;
// Check for required tags according to NIP-52
const d = event.tags.find(([name]) => name === 'd')?.[1];
const title = event.tags.find(([name]) => name === 'title')?.[1];
const start = event.tags.find(([name]) => name === 'start')?.[1];
// All calendar events require 'd', 'title', and 'start' tags
if (!d || !title || !start) return false;
// Additional validation for date-based events (kind 31922)
if (event.kind === 31922) {
// start tag should be in YYYY-MM-DD format for date-based events
const dateRegex = /^\d{4}-\d{2}-\d{2}$/;
if (!dateRegex.test(start)) return false;
}
// Additional validation for time-based events (kind 31923)
if (event.kind === 31923) {
// start tag should be a unix timestamp for time-based events
const timestamp = parseInt(start);
if (isNaN(timestamp) || timestamp <= 0) return false;
}
return true;
}
function useCalendarEvents() {
const { nostr } = useNostr();
return useQuery({
queryKey: ['calendar-events'],
queryFn: async (c) => {
const signal = AbortSignal.any([c.signal, AbortSignal.timeout(1500)]);
/** A URL to a wide (~1024x768) picture to be optionally displayed in the background of a profile screen. */
banner?: string;
/** A boolean to clarify that the content is entirely or partially the result of automation, such as with chatbots or newsfeeds. */
bot?: boolean;
/** An alternative, bigger name with richer characters than `name`. `name` should always be set regardless of the presence of `display_name` in the metadata. */
display_name?: string;
/** A bech32 lightning address according to NIP-57 and LNURL specifications. */
lud06?: string;
/** An email-like lightning address according to NIP-57 and LNURL specifications. */
lud16?: string;
/** A short name to be displayed for the user. */
name?: string;
/** An email-like Nostr address according to NIP-05. */
nip05?: string;
/** A URL to the user's avatar. */
picture?: string;
/** A web URL related in any way to the event author. */
The `LoginArea` component handles all the login-related UI and interactions, including displaying login dialogs, sign up functionality, and switching between accounts. It should not be wrapped in any conditional logic.
`LoginArea` displays both "Log in" and "Sign Up" buttons when the user is logged out, and changes to an account switcher once the user is logged in. It is an inline-flex element by default. To make it expand to the width of its container, you can pass a className like `flex` (to make it a block element) or `w-full`. If it is left as inline-flex, it's recommended to set a max width.
**Important**: Social applications should include a profile menu button in the main interface (typically in headers/navigation) to provide access to account settings, profile editing, and logout functionality. Don't only show `LoginArea` in logged-out states.
-`note1`: Contains only the event ID (32 bytes) - specifically for kind:1 events (Short Text Notes) as defined in NIP-10
-`nevent1`: Contains event ID plus optional relay hints and author pubkey - for any event kind
- Use `note1` for simple references to text notes and threads
- Use `nevent1` when you need to include relay hints or author context for any event type
**`npub1` vs `nprofile1`:**
-`npub1`: Contains only the public key (32 bytes)
-`nprofile1`: Contains public key plus optional relay hints and petname
- Use `npub1` for simple user references
- Use `nprofile1` when you need to include relay hints or display name context
#### NIP-19 Routing Implementation
**Critical**: NIP-19 identifiers should be handled at the **root level** of URLs (e.g., `/note1...`, `/npub1...`, `/naddr1...`), NOT nested under paths like `/note/note1...` or `/profile/npub1...`.
This project includes a boilerplate `NIP19Page` component that provides the foundation for handling all NIP-19 identifier types at the root level. The component is configured in the routing system and ready for AI agents to populate with specific functionality.
**How it works:**
1.**Root-Level Route**: The route `/:nip19` in `AppRouter.tsx` catches all NIP-19 identifiers
2.**Automatic Decoding**: The `NIP19Page` component automatically decodes the identifier using `nip19.decode()`
3.**Type-Specific Sections**: Different sections are rendered based on the identifier type:
-`npub1`/`nprofile1`: Profile section with placeholder for profile view
-`note1`: Note section with placeholder for kind:1 text note view
-`nevent1`: Event section with placeholder for any event type view
-`naddr1`: Addressable event section with placeholder for articles, marketplace items, etc.
4.**Error Handling**: Invalid, vacant, or unsupported identifiers show 404 NotFound page
5.**Ready for Population**: Each section includes comments indicating where AI agents should implement specific functionality
**Example URLs that work automatically:**
-`/npub1abc123...` - User profile (needs implementation)
-`/note1def456...` - Kind:1 text note (needs implementation)
-`/nevent1ghi789...` - Any event with relay hints (needs implementation)
**`note1` identifiers** are specifically for **kind:1 events** (Short Text Notes) as defined in NIP-10: "Text Notes and Threads". These are the basic social media posts in Nostr.
**`nevent1` identifiers** can reference any event kind and include additional metadata like relay hints and author pubkey. Use `nevent1` when:
- The event is not a kind:1 text note
- You need to include relay hints for better discoverability
4.**Security considerations**: Always use `naddr1` for addressable events instead of just the `d` tag value, as `naddr1` contains the author pubkey needed to create secure filters
5.**Error handling**: Gracefully handle invalid or unsupported NIP-19 identifiers with 404 responses
To attach files to kind 1 events, each file's URL should be appended to the event's `content`, and an `imeta` tag should be added for each file. For kind 0 events, the URL by itself can be used in relevant fields of the JSON content.
The logged-in user has a `signer` object (matching the NIP-07 signer interface) that can be used for encryption and decryption. The signer's nip44 methods handle all cryptographic operations internally, including key derivation and conversation key management, so you never need direct access to private keys. Always use the signer interface for encryption rather than requesting private keys from users, as this maintains security and follows best practices.
Nostr text notes (kind 1, 11, and 1111) have a plaintext `content` field that may contain URLs, hashtags, and Nostr URIs. These events should render their content using the `NoteContent` component:
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 (
<divclassName="space-y-6">
{/* Your article content */}
<div>{/* article content */}</div>
{/* Comments section */}
<CommentsSectionroot={article}/>
</div>
);
}
```
#### 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
<CommentsSection
root={event}
title="Discussion"
emptyStateMessage="Start the conversation"
emptyStateSubtitle="Share your thoughts about this post"
className="mt-8"
limit={100}
/>
```
#### Commenting on URLs
The comments system also supports commenting on external URLs, making it useful for web pages, articles, or any online content:
The project includes an `AppProvider` that manages global application state including theme and relay configuration. The default configuration includes:
```typescript
const defaultConfig: AppConfig = {
theme: "light",
relayUrl: "wss://relay.nostr.band",
};
```
Preset relays are available including Ditto, Nostr.Band, Damus, and Primal. The app uses local storage to persist user preferences.
## Routing
The project uses React Router with a centralized routing configuration in `AppRouter.tsx`. To add new routes:
When no content is found (empty search results, no data available, etc.), display a minimalist empty state with the `RelaySelector` component. This allows users to easily switch relays to discover content from different sources.
```tsx
import { RelaySelector } from '@/components/RelaySelector';
import { Card, CardContent } from '@/components/ui/card';
- **Color schemes**: Incorporate the user's color preferences when specified, and choose an appropriate scheme that matches the application's purpose and aesthetic
**Do not write tests** unless the user explicitly requests them in plain language. Writing unnecessary tests wastes significant time and money. Only create tests when:
The project includes a `TestApp` component that provides all necessary context providers for testing. Wrap components with this component to provide required context providers:
**This requirement applies regardless of whether you wrote new tests or not.** The test script validates the entire codebase, including TypeScript compilation, ESLint rules, and existing test suite.