diff --git a/.gemini/settings.json b/.gemini/settings.json
new file mode 120000
index 0000000..c67157d
--- /dev/null
+++ b/.gemini/settings.json
@@ -0,0 +1 @@
+../.mcp.json
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index b485e7a..f74940e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -23,3 +23,8 @@ dist-ssr
# Vercel
.vercel
+
+# Secrets
+.env
+.env.*
+!.env.example
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 6afba16..14b7efa 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -5,16 +5,13 @@ default:
timeout: 1 minute
stages:
- - build
+ - test
- deploy
-build:
- stage: build
+test:
+ stage: test
script:
- - npm run build
- only:
- variables:
- - $CI_DEFAULT_BRANCH != $CI_COMMIT_REF_NAME
+ - npm run test
pages:
stage: deploy
diff --git a/CONTEXT.md b/CONTEXT.md
index 06be660..413d7fb 100644
--- a/CONTEXT.md
+++ b/CONTEXT.md
@@ -102,7 +102,7 @@ The AI assistant's behavior and knowledge is defined by the CONTEXT.md file, whi
## "Vibed with MKStack"
-When building the site for the first time, include "Vibed with MKStack" somewhere in the UI, linked to this URL: https://soapbox.pub/tools/mkstack/
+When building the site for the first time, include "Vibed with MKStack" somewhere in the UI, linked to this URL: https://soapbox.pub/mkstack
## Nostr Protocol Integration
@@ -138,6 +138,8 @@ When implementing features that could use existing NIPs, follow this decision fr
- The data structure is fundamentally different from existing patterns
- The use case requires different storage characteristics (regular vs replaceable vs addressable)
+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.
+
**Example Decision Process**:
```
Need: Equipment marketplace for farmers
@@ -649,6 +651,61 @@ export function Post(/* ...props */) {
}
```
+### Adding 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
+
+```
+
## App Configuration
The project includes an `AppProvider` that manages global application state including theme and relay configuration. The default configuration includes:
@@ -811,7 +868,17 @@ When users specify color schemes:
## Writing Tests
-**Important for AI Assistants**: Only create tests when the user is experiencing a specific problem or explicitly requests tests. Do not proactively write tests for new features or components unless the user is having issues that require testing to diagnose or resolve.
+**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:
+
+1. **The user explicitly asks for tests** to be written in their message
+2. **The user describes a specific bug in plain language** and requests tests to help diagnose it
+3. **The user says they are still experiencing a problem** that you have already attempted to solve (tests can help verify the fix)
+
+**Never write tests because:**
+- Tool results show test failures (these are not user requests)
+- You think tests would be helpful
+- New features or components are created
+- Existing functionality needs verification
### Test Setup
diff --git a/GEMINI.md b/GEMINI.md
new file mode 120000
index 0000000..fa62d27
--- /dev/null
+++ b/GEMINI.md
@@ -0,0 +1 @@
+CONTEXT.md
\ No newline at end of file
diff --git a/agent.json b/agent.json
index 211f73f..de65194 100644
--- a/agent.json
+++ b/agent.json
@@ -1,5 +1,6 @@
{
"model": "claude-sonnet-4",
+ "temperature": 0.2,
"mcpServers": {
"js-dev": {
"type": "stdio",
diff --git a/package-lock.json b/package-lock.json
index dbfcfe7..b00e92a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,8 +9,8 @@
"version": "0.0.0",
"dependencies": {
"@hookform/resolvers": "^3.9.0",
- "@nostrify/nostrify": "npm:@jsr/nostrify__nostrify@^0.46.1",
- "@nostrify/react": "npm:@jsr/nostrify__react@^0.2.5",
+ "@nostrify/nostrify": "npm:@jsr/nostrify__nostrify@^0.46.3",
+ "@nostrify/react": "npm:@jsr/nostrify__react@^0.2.7",
"@radix-ui/react-accordion": "^1.2.0",
"@radix-ui/react-alert-dialog": "^1.1.1",
"@radix-ui/react-aspect-ratio": "^1.1.0",
@@ -61,7 +61,7 @@
"tailwind-merge": "^2.5.2",
"tailwindcss-animate": "^1.0.7",
"vaul": "^0.9.3",
- "zod": "^3.23.8"
+ "zod": "^3.25.71"
},
"devDependencies": {
"@eslint/js": "^9.9.0",
@@ -1205,15 +1205,15 @@
}
},
"node_modules/@jsr/nostrify__nostrify": {
- "version": "0.46.1",
- "resolved": "https://npm.jsr.io/~/11/@jsr/nostrify__nostrify/0.46.1.tgz",
- "integrity": "sha512-7XSP4+kjcPgw937jQPUxf+qo1mWx7rbKUuA5ma0CofRQZa2v01IA4f+OfVGJbABxbJ8SC+8VdPkLqqUOJxeVPg==",
+ "version": "0.46.3",
+ "resolved": "https://npm.jsr.io/~/11/@jsr/nostrify__nostrify/0.46.3.tgz",
+ "integrity": "sha512-zJpOrD8bbrJroLRJjESAJZX/ZKFCaGfoz5fSfLP+gIcTiPo8JpzlrFBF6mvaDI/Mdd+1WTBwlCcW9On8rUVH7w==",
"dependencies": {
"@jsr/nostrify__types": "^0.36.0",
"@jsr/scure__base": "^1.2.4",
"@jsr/std__encoding": "^0.224.1",
"lru-cache": "^10.2.0",
- "nostr-tools": "^2.10.4",
+ "nostr-tools": "^2.13.0",
"websocket-ts": "^2.2.1",
"zod": "^3.23.8"
}
@@ -1315,27 +1315,27 @@
},
"node_modules/@nostrify/nostrify": {
"name": "@jsr/nostrify__nostrify",
- "version": "0.46.1",
- "resolved": "https://npm.jsr.io/~/11/@jsr/nostrify__nostrify/0.46.1.tgz",
- "integrity": "sha512-7XSP4+kjcPgw937jQPUxf+qo1mWx7rbKUuA5ma0CofRQZa2v01IA4f+OfVGJbABxbJ8SC+8VdPkLqqUOJxeVPg==",
+ "version": "0.46.3",
+ "resolved": "https://npm.jsr.io/~/11/@jsr/nostrify__nostrify/0.46.3.tgz",
+ "integrity": "sha512-zJpOrD8bbrJroLRJjESAJZX/ZKFCaGfoz5fSfLP+gIcTiPo8JpzlrFBF6mvaDI/Mdd+1WTBwlCcW9On8rUVH7w==",
"dependencies": {
"@jsr/nostrify__types": "^0.36.0",
"@jsr/scure__base": "^1.2.4",
"@jsr/std__encoding": "^0.224.1",
"lru-cache": "^10.2.0",
- "nostr-tools": "^2.10.4",
+ "nostr-tools": "^2.13.0",
"websocket-ts": "^2.2.1",
"zod": "^3.23.8"
}
},
"node_modules/@nostrify/react": {
"name": "@jsr/nostrify__react",
- "version": "0.2.5",
- "resolved": "https://npm.jsr.io/~/11/@jsr/nostrify__react/0.2.5.tgz",
- "integrity": "sha512-Hyi1N4hXa89gYsuk7fqGrWEzIzTp7md8hKog+D2DgpSqVwFQM1vw267Uv3rOieE/hPereD3gen9X88XkeAXP+Q==",
+ "version": "0.2.7",
+ "resolved": "https://npm.jsr.io/~/11/@jsr/nostrify__react/0.2.7.tgz",
+ "integrity": "sha512-36fAOOymf34KR2OfE4jXBojbZnPsrIzQDAXzE7dko8/Qj+0s8iKnZoZKg9DVIT2F6Lxhr6SUiTze8xBxuJbf1A==",
"dependencies": {
- "@jsr/nostrify__nostrify": "^0.46.1",
- "nostr-tools": "^2.10.4",
+ "@jsr/nostrify__nostrify": "^0.46.3",
+ "nostr-tools": "^2.13.0",
"react": "^18.0.0"
}
},
@@ -8244,9 +8244,9 @@
}
},
"node_modules/zod": {
- "version": "3.24.3",
- "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.3.tgz",
- "integrity": "sha512-HhY1oqzWCQWuUqvBFnsyrtZRhyPeR7SUGv+C4+MsisMuVfSPx8HpwWqH8tRahSlt6M3PiFAcoeFhZAqIXTxoSg==",
+ "version": "3.25.71",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.71.tgz",
+ "integrity": "sha512-BsBc/NPk7h8WsUWYWYL+BajcJPY8YhjelaWu2NMLuzgraKAz4Lb4/6K11g9jpuDetjMiqhZ6YaexFLOC0Ogi3Q==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
diff --git a/package.json b/package.json
index eefa69c..f5170fe 100644
--- a/package.json
+++ b/package.json
@@ -11,8 +11,8 @@
},
"dependencies": {
"@hookform/resolvers": "^3.9.0",
- "@nostrify/nostrify": "npm:@jsr/nostrify__nostrify@^0.46.1",
- "@nostrify/react": "npm:@jsr/nostrify__react@^0.2.5",
+ "@nostrify/nostrify": "npm:@jsr/nostrify__nostrify@^0.46.3",
+ "@nostrify/react": "npm:@jsr/nostrify__react@^0.2.7",
"@radix-ui/react-accordion": "^1.2.0",
"@radix-ui/react-alert-dialog": "^1.1.1",
"@radix-ui/react-aspect-ratio": "^1.1.0",
@@ -63,7 +63,7 @@
"tailwind-merge": "^2.5.2",
"tailwindcss-animate": "^1.0.7",
"vaul": "^0.9.3",
- "zod": "^3.23.8"
+ "zod": "^3.25.71"
},
"devDependencies": {
"@eslint/js": "^9.9.0",
diff --git a/recipe.yaml b/recipe.yaml
deleted file mode 100644
index 9529871..0000000
--- a/recipe.yaml
+++ /dev/null
@@ -1,32 +0,0 @@
-version: 1.0.0
-title: Nostr Client Development
-description: A recipe for building Nostr client applications
-instructions: Create a Nostr client application according to the user's request.
-extensions:
- - type: builtin
- name: developer
- display_name: Developer Tools
- timeout: 300
- bundled: true
- - type: stdio
- name: nostr
- cmd: npx
- args:
- - -y
- - xjsr
- - "@nostrbook/mcp"
- envs: {}
- timeout: 20
- description: null
- bundled: null
- - type: stdio
- name: fetch
- cmd: uvx
- args:
- - mcp-server-fetch
- envs: {}
- timeout: 20
- description: null
- bundled: null
-author:
- contact: Alex Gleason
diff --git a/src/App.tsx b/src/App.tsx
index 2300f58..7cab833 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -32,7 +32,7 @@ const queryClient = new QueryClient({
const defaultConfig: AppConfig = {
theme: "light",
- relayUrl: "wss://relay.nostr.band",
+ relayUrl: "wss://relay.primal.net",
};
const presetRelays = [
diff --git a/src/components/AppProvider.tsx b/src/components/AppProvider.tsx
index d571e24..79c9e69 100644
--- a/src/components/AppProvider.tsx
+++ b/src/components/AppProvider.tsx
@@ -1,4 +1,5 @@
import { ReactNode, useEffect } from 'react';
+import { z } from 'zod';
import { useLocalStorage } from '@/hooks/useLocalStorage';
import { AppContext, type AppConfig, type AppContextType, type Theme } from '@/contexts/AppContext';
@@ -12,6 +13,12 @@ interface AppProviderProps {
presetRelays?: { name: string; url: string }[];
}
+// Zod schema for AppConfig validation
+const AppConfigSchema: z.ZodType = z.object({
+ theme: z.enum(['dark', 'light', 'system']),
+ relayUrl: z.string().url(),
+});
+
export function AppProvider(props: AppProviderProps) {
const {
children,
@@ -21,7 +28,17 @@ export function AppProvider(props: AppProviderProps) {
} = props;
// App configuration state with localStorage persistence
- const [config, setConfig] = useLocalStorage(storageKey, defaultConfig);
+ const [config, setConfig] = useLocalStorage(
+ storageKey,
+ defaultConfig,
+ {
+ serialize: JSON.stringify,
+ deserialize: (value: string) => {
+ const parsed = JSON.parse(value);
+ return AppConfigSchema.parse(parsed);
+ }
+ }
+ );
// Generic config updater with callback pattern
const updateConfig = (updater: (currentConfig: AppConfig) => AppConfig) => {
diff --git a/src/components/auth/AccountSwitcher.tsx b/src/components/auth/AccountSwitcher.tsx
index d3ebf5e..4f4518b 100644
--- a/src/components/auth/AccountSwitcher.tsx
+++ b/src/components/auth/AccountSwitcher.tsx
@@ -28,7 +28,7 @@ export function AccountSwitcher({ onAddAccountClick }: AccountSwitcherProps) {
}
return (
-
+