Browse Source

Add event template generator with 140+ Nostr event kinds (v0.36.2)

- Add comprehensive eventKinds.js database with all NIPs event kinds
  including templates, descriptions, NIP references, and type flags
- Create EventTemplateSelector.svelte modal with search functionality
  and category filtering (Social, Messaging, Lists, Marketplace, etc.)
- Update ComposeView with "Generate Template" button and error banner
  for displaying permission-aware publish error messages
- Enhance publishEvent() in App.svelte with detailed error handling
  that explains policy restrictions, permission issues, and provides
  actionable guidance for users
- Add permission pre-check to prevent read-only users from attempting
  to publish events
- Update CLAUDE.md with Web UI event templates documentation
- Create docs/WEB_UI_EVENT_TEMPLATES.md with comprehensive user guide

Files modified:
- app/web/src/eventKinds.js (new)
- app/web/src/EventTemplateSelector.svelte (new)
- app/web/src/ComposeView.svelte
- app/web/src/App.svelte
- docs/WEB_UI_EVENT_TEMPLATES.md (new)
- CLAUDE.md
- pkg/version/version

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
main
mleku 4 weeks ago
parent
commit
0a3e639fee
No known key found for this signature in database
  1. 25
      CLAUDE.md
  2. 95
      app/web/src/App.svelte
  3. 116
      app/web/src/ComposeView.svelte
  4. 404
      app/web/src/EventTemplateSelector.svelte
  5. 2644
      app/web/src/eventKinds.js
  6. 163
      docs/WEB_UI_EVENT_TEMPLATES.md
  7. 2
      pkg/version/version

25
CLAUDE.md

@ -334,6 +334,11 @@ export ORLY_AUTH_TO_WRITE=false # Require auth only for writes @@ -334,6 +334,11 @@ export ORLY_AUTH_TO_WRITE=false # Require auth only for writes
- Features: event browser with advanced filtering, sprocket management, policy management, user admin, settings
- **Event Browser:** Enhanced filter system with kind, author, tag, and time range filters (replaced simple search)
- **Policy Management Tab:** JSON editor with validation, save publishes kind 12345 event
- **Compose Tab with Event Templates:** Generate pre-filled event templates for all 140+ Nostr event kinds
- `eventKinds.js` - Comprehensive database of event kinds from NIPs with templates
- `EventTemplateSelector.svelte` - Scrolling modal with search and category filtering
- Category filters: All, Regular, Replaceable, Ephemeral, Addressable, Social, Messaging, Lists, Marketplace, Lightning, Media, Git, Calendar, Groups
- Permission-aware error messages explaining policy/role restrictions when publishing fails
**Command-line Tools (`cmd/`):**
- `relay-tester/` - Nostr protocol compliance testing
@ -816,7 +821,7 @@ Files modified: @@ -816,7 +821,7 @@ Files modified:
3. GitHub Actions workflow builds binaries for multiple platforms
4. Release created automatically with binaries and checksums
## Recent Features (v0.34.x)
## Recent Features (v0.34.x - v0.36.x)
### Directory Spider
The directory spider (`pkg/spider/directory.go`) automatically discovers and syncs metadata from other relays:
@ -860,6 +865,22 @@ WebAssembly-compatible database backend (`pkg/wasmdb/`): @@ -860,6 +865,22 @@ WebAssembly-compatible database backend (`pkg/wasmdb/`):
- **Reference Documentation**: `docs/POLICY_CONFIGURATION_REFERENCE.md` provides authoritative read vs write applicability
- See also: `pkg/policy/README.md` for quick reference
### Web UI Event Templates (v0.36.x)
The Compose tab now includes a comprehensive event template generator:
- **140+ Event Kinds**: Complete database of Nostr event kinds from the NIPs repository
- **Template Generator**: Click "Generate Template" to open searchable modal with all event types
- **Category Filtering**: Filter by Regular, Replaceable, Ephemeral, Addressable, or domain-specific categories (Social, Messaging, Lists, Marketplace, Lightning, Media, Git, Calendar, Groups)
- **Search**: Find events by name, description, kind number, or NIP reference
- **Pre-filled Templates**: Each kind includes proper tag structure and example content
- **Permission-Aware Errors**: When publishing fails, error messages explain:
- Policy restrictions (kind blocked, content limits)
- Permission issues (user role insufficient)
- Guidance on how to resolve (contact admin, policy config)
- **Key Files**:
- `app/web/src/eventKinds.js` - Event kinds database with templates
- `app/web/src/EventTemplateSelector.svelte` - Template selection modal
- `app/web/src/ComposeView.svelte` - Updated compose interface
### Policy JSON Configuration Quick Reference
```json
@ -956,4 +977,6 @@ Invite-based access control system: @@ -956,4 +977,6 @@ Invite-based access control system:
| `pkg/neo4j/MODIFYING_SCHEMA.md` | How to modify Neo4j schema |
| `pkg/neo4j/TESTING.md` | Neo4j testing guide |
| `.claude/skills/cypher/SKILL.md` | Cypher query language skill for Neo4j |
| `app/web/src/eventKinds.js` | Comprehensive Nostr event kinds database (140+ kinds with templates) |
| `docs/WEB_UI_EVENT_TEMPLATES.md` | Web UI event template generator documentation |
| `readme.adoc` | Project README with feature overview |

95
app/web/src/App.svelte

@ -117,6 +117,7 @@ @@ -117,6 +117,7 @@
// Compose tab state
let composeEventJson = "";
let composePublishError = "";
// Recovery tab state
let recoverySelectedKind = null;
@ -2562,31 +2563,42 @@ @@ -2562,31 +2563,42 @@
}
async function publishEvent() {
// Clear any previous errors
composePublishError = "";
try {
if (!composeEventJson.trim()) {
alert("Please enter an event to publish");
composePublishError = "Please enter an event to publish";
return;
}
if (!isLoggedIn) {
alert("Please log in to publish events");
composePublishError = "Please log in to publish events";
return;
}
if (!userSigner) {
alert(
"No signer available. Please log in with a valid authentication method.",
);
composePublishError = "No signer available. Please log in with a valid authentication method.";
return;
}
const event = JSON.parse(composeEventJson);
let event;
try {
event = JSON.parse(composeEventJson);
} catch (parseError) {
composePublishError = `Invalid JSON: ${parseError.message}`;
return;
}
// Validate that the event has required fields
if (!event.id || !event.sig) {
alert(
'Event must be signed before publishing. Please click "Sign" first.',
);
composePublishError = 'Event must be signed before publishing. Please click "Sign" first.';
return;
}
// Pre-check: validate user has write permission
if (userRole === "read") {
composePublishError = `Permission denied: Your current role is "${userRole}" which does not allow publishing events. Contact a relay administrator to upgrade your permissions.`;
return;
}
@ -2602,18 +2614,70 @@ @@ -2602,18 +2614,70 @@
);
if (result.success) {
composePublishError = "";
alert("Event published successfully to ORLY relay!");
// Optionally clear the editor after successful publish
// composeEventJson = '';
} else {
alert(
`Event publishing failed: ${result.reason || "Unknown error"}`,
);
// Parse the error reason and provide helpful guidance
const reason = result.reason || "Unknown error";
composePublishError = formatPublishError(reason, event.kind);
}
} catch (error) {
console.error("Error publishing event:", error);
alert("Error publishing event: " + error.message);
const errorMsg = error.message || "Unknown error";
composePublishError = formatPublishError(errorMsg, null);
}
}
// Helper function to format publish errors with helpful guidance
function formatPublishError(reason, eventKind) {
const lowerReason = reason.toLowerCase();
// Check for policy-related errors
if (lowerReason.includes("policy") || lowerReason.includes("blocked") || lowerReason.includes("denied")) {
let msg = `Policy Error: ${reason}`;
if (eventKind !== null) {
msg += `\n\nKind ${eventKind} may be restricted by the relay's policy configuration.`;
}
if (policyEnabled) {
msg += "\n\nThe relay has policy enforcement enabled. Contact a relay administrator to allow this event kind or adjust your permissions.";
}
return msg;
}
// Check for permission/auth errors
if (lowerReason.includes("auth") || lowerReason.includes("permission") || lowerReason.includes("unauthorized")) {
return `Permission Error: ${reason}\n\nYour current permissions may not allow publishing this type of event. Current role: ${userRole || "unknown"}. Contact a relay administrator to upgrade your permissions.`;
}
// Check for kind-specific restrictions
if (lowerReason.includes("kind") || lowerReason.includes("not allowed") || lowerReason.includes("restricted")) {
let msg = `Event Type Error: ${reason}`;
if (eventKind !== null) {
msg += `\n\nKind ${eventKind} is not currently allowed on this relay.`;
}
msg += "\n\nThe relay administrator may need to update the policy configuration to allow this event kind.";
return msg;
}
// Check for rate limiting
if (lowerReason.includes("rate") || lowerReason.includes("limit") || lowerReason.includes("too many")) {
return `Rate Limit Error: ${reason}\n\nPlease wait a moment before trying again.`;
}
// Check for size limits
if (lowerReason.includes("size") || lowerReason.includes("too large") || lowerReason.includes("content")) {
return `Size Limit Error: ${reason}\n\nThe event may exceed the relay's size limits. Try reducing the content length.`;
}
// Default error message
return `Publishing failed: ${reason}`;
}
// Clear the compose publish error
function clearComposeError() {
composePublishError = "";
}
// Persist selected tab to local storage
@ -2720,9 +2784,14 @@ @@ -2720,9 +2784,14 @@
{:else if selectedTab === "compose"}
<ComposeView
bind:composeEventJson
{userPubkey}
{userRole}
{policyEnabled}
publishError={composePublishError}
on:reformatJson={reformatJson}
on:signEvent={signEvent}
on:publishEvent={publishEvent}
on:clearError={clearComposeError}
/>
{:else if selectedTab === "managed-acl"}
<div class="managed-acl-view">

116
app/web/src/ComposeView.svelte

@ -1,9 +1,17 @@ @@ -1,9 +1,17 @@
<script>
export let composeEventJson = "";
export let userPubkey = "";
export let userRole = "";
export let policyEnabled = false;
export let publishError = "";
import { createEventDispatcher } from "svelte";
import EventTemplateSelector from "./EventTemplateSelector.svelte";
const dispatch = createEventDispatcher();
let isTemplateSelectorOpen = false;
function reformatJson() {
dispatch("reformatJson");
}
@ -15,10 +23,32 @@ @@ -15,10 +23,32 @@
function publishEvent() {
dispatch("publishEvent");
}
function openTemplateSelector() {
isTemplateSelectorOpen = true;
}
function handleTemplateSelect(event) {
const { kind, template } = event.detail;
composeEventJson = JSON.stringify(template, null, 2);
dispatch("templateSelected", { kind, template });
}
function handleTemplateSelectorClose() {
isTemplateSelectorOpen = false;
}
function clearError() {
publishError = "";
dispatch("clearError");
}
</script>
<div class="compose-view">
<div class="compose-header">
<button class="compose-btn template-btn" on:click={openTemplateSelector}
>Generate Template</button
>
<button class="compose-btn reformat-btn" on:click={reformatJson}
>Reformat</button
>
@ -27,16 +57,34 @@ @@ -27,16 +57,34 @@
>Publish</button
>
</div>
{#if publishError}
<div class="error-banner">
<div class="error-content">
<span class="error-icon">&#9888;</span>
<span class="error-message">{publishError}</span>
</div>
<button class="error-dismiss" on:click={clearError}>&times;</button>
</div>
{/if}
<div class="compose-editor">
<textarea
bind:value={composeEventJson}
class="compose-textarea"
placeholder="Enter your Nostr event JSON here..."
placeholder="Enter your Nostr event JSON here, or click 'Generate Template' to start with a template..."
spellcheck="false"
></textarea>
</div>
</div>
<EventTemplateSelector
bind:isOpen={isTemplateSelectorOpen}
{userPubkey}
on:select={handleTemplateSelect}
on:close={handleTemplateSelectorClose}
/>
<style>
.compose-view {
position: fixed;
@ -71,6 +119,16 @@ @@ -71,6 +119,16 @@
background: var(--button-hover-bg);
}
.template-btn {
background: var(--primary);
color: var(--text-color);
}
.template-btn:hover {
background: var(--primary);
filter: brightness(0.9);
}
.reformat-btn {
background: var(--info);
color: var(--text-color);
@ -101,6 +159,53 @@ @@ -101,6 +159,53 @@
filter: brightness(0.9);
}
.error-banner {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.75em 1em;
margin: 0 0.5em;
background: #f8d7da;
border: 1px solid #f5c6cb;
border-radius: 0.25rem;
color: #721c24;
}
:global(.dark-theme) .error-banner {
background: #4a1c24;
border-color: #6a2c34;
color: #f8d7da;
}
.error-content {
display: flex;
align-items: center;
gap: 0.5em;
}
.error-icon {
font-size: 1.2em;
}
.error-message {
font-size: 0.9rem;
line-height: 1.4;
}
.error-dismiss {
background: none;
border: none;
font-size: 1.25rem;
cursor: pointer;
color: inherit;
padding: 0 0.25em;
opacity: 0.7;
}
.error-dismiss:hover {
opacity: 1;
}
.compose-editor {
flex: 1;
display: flex;
@ -137,5 +242,14 @@ @@ -137,5 +242,14 @@
.compose-view {
left: 160px;
}
.compose-header {
flex-wrap: wrap;
}
.compose-btn {
flex: 1;
min-width: calc(50% - 0.5em);
}
}
</style>

404
app/web/src/EventTemplateSelector.svelte

@ -0,0 +1,404 @@ @@ -0,0 +1,404 @@
<script>
import { createEventDispatcher } from "svelte";
import { eventKinds, kindCategories, createTemplateEvent, searchEventKinds } from "./eventKinds.js";
export let isOpen = false;
export let userPubkey = "";
const dispatch = createEventDispatcher();
let searchQuery = "";
let selectedCategory = "all";
let filteredKinds = eventKinds;
// Filter kinds based on search and category
$: {
let kinds = eventKinds;
// Apply category filter
const category = kindCategories.find(c => c.id === selectedCategory);
if (category) {
kinds = kinds.filter(category.filter);
}
// Apply search filter
if (searchQuery.trim()) {
const query = searchQuery.toLowerCase();
kinds = kinds.filter(k =>
k.name.toLowerCase().includes(query) ||
k.description.toLowerCase().includes(query) ||
k.kind.toString().includes(query) ||
(k.nip && k.nip.includes(query))
);
}
filteredKinds = kinds;
}
function selectKind(kindInfo) {
const template = createTemplateEvent(kindInfo.kind, userPubkey);
dispatch("select", {
kind: kindInfo,
template: template
});
closeModal();
}
function closeModal() {
isOpen = false;
searchQuery = "";
selectedCategory = "all";
dispatch("close");
}
function handleKeydown(event) {
if (event.key === "Escape") {
closeModal();
}
}
function handleBackdropClick(event) {
if (event.target === event.currentTarget) {
closeModal();
}
}
function getKindBadgeClass(kindInfo) {
if (kindInfo.isAddressable) return "badge-addressable";
if (kindInfo.isReplaceable) return "badge-replaceable";
if (kindInfo.kind >= 20000 && kindInfo.kind < 30000) return "badge-ephemeral";
return "badge-regular";
}
function getKindBadgeText(kindInfo) {
if (kindInfo.isAddressable) return "Addressable";
if (kindInfo.isReplaceable) return "Replaceable";
if (kindInfo.kind >= 20000 && kindInfo.kind < 30000) return "Ephemeral";
return "Regular";
}
</script>
<svelte:window on:keydown={handleKeydown} />
{#if isOpen}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div class="modal-backdrop" on:click={handleBackdropClick}>
<div class="modal-content">
<div class="modal-header">
<h2>Generate Event Template</h2>
<button class="close-btn" on:click={closeModal}>&times;</button>
</div>
<div class="modal-filters">
<div class="search-box">
<input
type="text"
placeholder="Search by name, description, or kind number..."
bind:value={searchQuery}
class="search-input"
/>
</div>
<div class="category-tabs">
{#each kindCategories as category}
<button
class="category-tab"
class:active={selectedCategory === category.id}
on:click={() => selectedCategory = category.id}
>
{category.name}
</button>
{/each}
</div>
</div>
<div class="modal-body">
<div class="kinds-list">
{#if filteredKinds.length === 0}
<div class="no-results">
No event kinds found matching "{searchQuery}"
</div>
{:else}
{#each filteredKinds as kindInfo}
<button
class="kind-item"
on:click={() => selectKind(kindInfo)}
>
<div class="kind-header">
<span class="kind-number">Kind {kindInfo.kind}</span>
<span class="kind-badge {getKindBadgeClass(kindInfo)}">
{getKindBadgeText(kindInfo)}
</span>
{#if kindInfo.nip && kindInfo.nip !== "XX"}
<span class="nip-badge">NIP-{kindInfo.nip}</span>
{/if}
</div>
<div class="kind-name">{kindInfo.name}</div>
<div class="kind-description">{kindInfo.description}</div>
</button>
{/each}
{/if}
</div>
</div>
<div class="modal-footer">
<span class="result-count">{filteredKinds.length} event type{filteredKinds.length !== 1 ? 's' : ''}</span>
<button class="cancel-btn" on:click={closeModal}>Cancel</button>
</div>
</div>
</div>
{/if}
<style>
.modal-backdrop {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.7);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.modal-content {
background: var(--card-bg);
border-radius: 0.5rem;
width: 90%;
max-width: 800px;
max-height: 85vh;
display: flex;
flex-direction: column;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
border: 1px solid var(--border-color);
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem 1.5rem;
border-bottom: 1px solid var(--border-color);
}
.modal-header h2 {
margin: 0;
font-size: 1.25rem;
color: var(--text-color);
}
.close-btn {
background: none;
border: none;
font-size: 1.5rem;
cursor: pointer;
color: var(--text-color);
padding: 0;
width: 2rem;
height: 2rem;
display: flex;
align-items: center;
justify-content: center;
border-radius: 0.25rem;
}
.close-btn:hover {
background: var(--button-hover-bg);
}
.modal-filters {
padding: 1rem 1.5rem;
border-bottom: 1px solid var(--border-color);
}
.search-box {
margin-bottom: 0.75rem;
}
.search-input {
width: 100%;
padding: 0.75rem;
border: 1px solid var(--border-color);
border-radius: 0.25rem;
background: var(--input-bg);
color: var(--input-text-color);
font-size: 0.9rem;
}
.search-input:focus {
outline: none;
border-color: var(--accent-color);
}
.category-tabs {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
.category-tab {
padding: 0.4rem 0.75rem;
border: 1px solid var(--border-color);
border-radius: 1rem;
background: transparent;
color: var(--text-color);
font-size: 0.75rem;
cursor: pointer;
transition: all 0.2s;
}
.category-tab:hover {
background: var(--button-hover-bg);
}
.category-tab.active {
background: var(--accent-color);
border-color: var(--accent-color);
color: white;
}
.modal-body {
flex: 1;
overflow-y: auto;
padding: 1rem 1.5rem;
}
.kinds-list {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.kind-item {
display: block;
width: 100%;
text-align: left;
padding: 0.75rem 1rem;
border: 1px solid var(--border-color);
border-radius: 0.375rem;
background: var(--bg-color);
cursor: pointer;
transition: all 0.2s;
}
.kind-item:hover {
border-color: var(--accent-color);
background: var(--button-hover-bg);
}
.kind-header {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.25rem;
}
.kind-number {
font-family: monospace;
font-size: 0.8rem;
color: var(--accent-color);
font-weight: bold;
}
.kind-badge {
font-size: 0.65rem;
padding: 0.15rem 0.4rem;
border-radius: 0.25rem;
font-weight: 500;
}
.badge-regular {
background: #6c757d;
color: white;
}
.badge-replaceable {
background: #17a2b8;
color: white;
}
.badge-ephemeral {
background: #ffc107;
color: black;
}
.badge-addressable {
background: #28a745;
color: white;
}
.nip-badge {
font-size: 0.65rem;
padding: 0.15rem 0.4rem;
border-radius: 0.25rem;
background: var(--primary);
color: var(--text-color);
}
.kind-name {
font-weight: 600;
color: var(--text-color);
margin-bottom: 0.25rem;
}
.kind-description {
font-size: 0.85rem;
color: var(--text-color);
opacity: 0.7;
}
.no-results {
text-align: center;
padding: 2rem;
color: var(--text-color);
opacity: 0.6;
}
.modal-footer {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem 1.5rem;
border-top: 1px solid var(--border-color);
}
.result-count {
font-size: 0.85rem;
color: var(--text-color);
opacity: 0.7;
}
.cancel-btn {
padding: 0.5rem 1rem;
border: 1px solid var(--border-color);
border-radius: 0.25rem;
background: var(--button-bg);
color: var(--button-text);
cursor: pointer;
font-size: 0.9rem;
}
.cancel-btn:hover {
background: var(--button-hover-bg);
}
@media (max-width: 640px) {
.modal-content {
width: 95%;
max-height: 90vh;
}
.category-tabs {
overflow-x: auto;
flex-wrap: nowrap;
padding-bottom: 0.5rem;
}
.category-tab {
white-space: nowrap;
}
}
</style>

2644
app/web/src/eventKinds.js

File diff suppressed because it is too large Load Diff

163
docs/WEB_UI_EVENT_TEMPLATES.md

@ -0,0 +1,163 @@ @@ -0,0 +1,163 @@
# Web UI Event Templates
The ORLY Web UI includes a comprehensive event template generator that helps users create properly-structured Nostr events for any of the 140+ defined event kinds.
## Overview
The Compose tab provides a "Generate Template" button that opens a searchable, categorized modal dialog. Users can browse or search for any Nostr event kind and instantly load a pre-filled template with the correct structure, tags, and example content.
## Features
### Event Kind Database (`app/web/src/eventKinds.js`)
A comprehensive JavaScript database containing:
- **140+ event kinds** from the NIPs (Nostr Implementation Possibilities) repository
- Each entry includes:
- Kind number
- Human-readable name
- Description
- NIP reference (where applicable)
- Event type flags (replaceable, addressable, ephemeral)
- Pre-built template with proper tag structure
### Template Selector Modal (`app/web/src/EventTemplateSelector.svelte`)
A user-friendly modal interface featuring:
- **Search functionality**: Find events by name, description, kind number, or NIP reference
- **Category filters**: Quick-filter buttons for event types:
- All Kinds
- Regular Events (0-9999)
- Replaceable (10000-19999)
- Ephemeral (20000-29999)
- Addressable (30000-39999)
- Domain-specific: Social, Messaging, Lists, Marketplace, Lightning, Media, Git, Calendar, Groups
- **Visual badges**: Color-coded indicators showing event type
- **NIP references**: Quick reference to the defining NIP
- **Keyboard navigation**: Escape key closes the modal
### Permission-Aware Error Handling
When publishing fails, the system provides detailed, actionable error messages:
| Error Type | Description | User Guidance |
|------------|-------------|---------------|
| Policy Error | Event kind blocked by relay policy | Contact relay administrator to allow the kind |
| Permission Error | User role insufficient | Shows current role, suggests permission upgrade |
| Kind Restriction | Event type not allowed | Policy configuration may need updating |
| Rate Limit | Too many requests | Wait before retrying |
| Size Limit | Event too large | Reduce content length |
## Usage
### Generating a Template
1. Navigate to the **Compose** tab in the Web UI
2. Click the **Generate Template** button (purple button)
3. In the modal:
- Use the search box to find specific event types
- Or click category tabs to filter by event type
- Click on any event kind to select it
4. The template is loaded into the editor with:
- Correct `kind` value
- Proper tag structure with placeholder values
- Example content (where applicable)
- Current timestamp
- Your pubkey (if logged in)
### Editing and Publishing
1. Replace placeholder values (marked with `<angle_brackets>`) with actual data
2. Click **Reformat** to clean up JSON formatting
3. Click **Sign** to sign the event with your key
4. Click **Publish** to send to the relay
### Understanding Templates
Templates use placeholder values in angle brackets that must be replaced:
```json
{
"kind": 1,
"content": "Your note content here",
"tags": [
["p", "<pubkey_to_mention>"],
["e", "<event_id_to_reference>"]
],
"created_at": 1702857600,
"pubkey": "<your_pubkey_here>"
}
```
## Event Categories
### Regular Events (0-9999)
Standard events that are stored indefinitely. Examples:
- Kind 0: User Metadata
- Kind 1: Short Text Note
- Kind 7: Reaction
- Kind 1984: Reporting
### Replaceable Events (10000-19999)
Events where only the latest version is kept. Examples:
- Kind 10000: Mute List
- Kind 10002: Relay List Metadata
- Kind 13194: Wallet Info
### Ephemeral Events (20000-29999)
Events not intended for permanent storage. Examples:
- Kind 22242: Client Authentication
- Kind 24133: Nostr Connect
### Addressable Events (30000-39999)
Parameterized replaceable events identified by kind + pubkey + d-tag. Examples:
- Kind 30023: Long-form Content
- Kind 30311: Live Event
- Kind 34550: Community Definition
## API Reference
### Helper Functions in `eventKinds.js`
```javascript
import {
eventKinds, // Array of all event kinds
kindCategories, // Array of category filter definitions
getEventKind, // Get kind info by number
searchEventKinds, // Search by query string
createTemplateEvent // Generate template with current timestamp
} from './eventKinds.js';
// Get information about a specific kind
const kind1 = getEventKind(1);
// Returns: { kind: 1, name: "Short Text Note", description: "...", template: {...} }
// Search for kinds
const results = searchEventKinds("zap");
// Returns: Array of matching kinds
// Create a template event
const template = createTemplateEvent(1, "abc123...");
// Returns: Event object with current timestamp and provided pubkey
```
## Troubleshooting
### "Permission denied" error
Your user role does not allow publishing events. Check your role in the header badge and contact a relay administrator.
### "Policy Error: kind blocked"
The relay's policy configuration does not allow this event kind. If you're an administrator, check `ORLY_POLICY_PATH` or the Policy tab.
### "Event must be signed before publishing"
Click the **Sign** button before **Publish**. Events must be cryptographically signed before the relay will accept them.
### Template not loading
Ensure JavaScript is enabled and the page has fully loaded. Try refreshing the page.
## Related Documentation
- [POLICY_USAGE_GUIDE.md](./POLICY_USAGE_GUIDE.md) - Policy configuration for event restrictions
- [POLICY_CONFIGURATION_REFERENCE.md](./POLICY_CONFIGURATION_REFERENCE.md) - Policy rule reference
- [NIPs Repository](https://github.com/nostr-protocol/nips) - Official Nostr protocol specifications

2
pkg/version/version

@ -1 +1 @@ @@ -1 +1 @@
v0.36.1
v0.36.2

Loading…
Cancel
Save