Files
grimoire/claudedocs/event-rendering-system-analysis.md
2025-12-11 18:11:59 +01:00

1128 lines
33 KiB
Markdown

# Event Rendering System - Comprehensive Analysis & Improvement Plan
**Date**: 2025-12-11
**Context**: Grimoire Nostr Protocol Explorer
**Scope**: Deep architectural analysis of event rendering system covering 150+ registered event kinds with ~20 custom renderers
---
## Executive Summary
The current event rendering system has a **solid foundation** with good architectural patterns (registry-based routing, component reuse, type safety), but suffers from **inconsistencies in application** and **missing abstractions** that limit scalability, maintainability, and extensibility.
**Key Findings:**
-**Strengths**: Registry pattern, BaseEventContainer, applesauce integration, type safety
-**Critical Issues**: Hardcoded detail renderers, inconsistent depth tracking, no error boundaries, missing threading abstraction
- 🎯 **Opportunity**: Transform from "working prototype" to "production-grade framework" with systematic improvements
---
## Part 1: Current State Analysis
### Architecture Overview
```
Current System Layers:
┌─────────────────────────────────────┐
│ Renderer Layer │ Kind1Renderer, Kind6Renderer, etc.
│ (~20 custom, 130+ using default) │
├─────────────────────────────────────┤
│ Component Layer │ BaseEventContainer, EventAuthor, EventMenu
│ (Reusable UI components) │ RichText, EmbeddedEvent, MediaEmbed
├─────────────────────────────────────┤
│ Registry Layer │ kindRenderers map, KindRenderer router
│ (Routing & fallback) │ DefaultKindRenderer
├─────────────────────────────────────┤
│ Data Layer │ EventStore (applesauce), useNostrEvent hook
│ (Reactive state) │ RelayPool, Dexie cache
└─────────────────────────────────────┘
```
### Event Kind Categories
Analysis of 150+ registered kinds reveals **7 fundamental patterns**:
1. **Content-Primary** (1, 30023, 9802)
- Main payload in `content` field
- Rich text rendering, markdown, media embeds
- Examples: Notes, articles, highlights
2. **Reference Events** (6, 7, 9735)
- Point to other events via e/a tags
- Embed referenced content
- Examples: Reposts, reactions, zaps
3. **Metadata Events** (0, 3, 10002)
- Structured data in content JSON
- Key-value pairs, lists, configurations
- Examples: Profiles, contacts, relay lists
4. **List Events** (30000-39999 replaceable)
- Arrays of items in tags
- Follow sets, mute lists, bookmarks
- Addressable/replaceable nature
5. **Media Events** (20, 21, 22, 1063)
- Content is URLs with metadata
- Images, videos, files
- Thumbnails, dimensions, MIME types
6. **Action Events** (5, 1984)
- Represent operations on other events
- Deletions, reports, moderation
- Usually invisible to end users
7. **Communication Events** (4, 14, 1111)
- Threaded messaging
- DMs, comments, chat messages
- Multiple threading models (NIP-10, NIP-22, NIP-28)
---
## Part 2: What's Common to All Events
### Universal Requirements
Every event, regardless of kind, needs:
1. **Author Context** - WHO created this
- Profile info (name, avatar, NIP-05)
- Clickable to open profile
- Badge/verification indicators
2. **Temporal Context** - WHEN was this created
- Relative timestamps ("2h ago")
- Absolute time on hover (ISO format)
- Locale-aware formatting
3. **Event Identity** - WHAT is this
- Kind badge with icon and name
- Event ID (bech32 format: nevent/naddr)
- Copy/share capabilities
4. **Actions** - User operations
- Open in detail view
- Copy event ID
- View raw JSON
- (Future: Reply, React, Zap, Share)
5. **Relay Context** - WHERE was this seen
- List of relays that served the event
- Relay health indicators
- Relay preferences for publishing
6. **Addressability** - HOW to reference
- Regular events: nevent (id + relays + author)
- Addressable events: naddr (kind + pubkey + identifier + relays)
- note1 (deprecated but still supported)
### Current Implementation
**Well-Handled:**
- ✅ Author, Temporal, Identity, Actions (1-4) via `BaseEventContainer` + `EventMenu`
- ✅ Addressability (6) logic in EventDetailViewer
**Missing Universally:**
- ❌ Signature verification indicator
- ❌ Edit history (NIP-09 deletion event tracking)
- ❌ Engagement preview (reply count, zap total, reaction summary)
- ❌ Related events indicator
- ❌ Community/context badges (NIP-72 communities, NIP-29 groups)
**Recommendation:** Extend `BaseEventContainer` with optional engagement footer and verification indicator.
---
## Part 3: Rendering Context Analysis
### Three Primary Contexts
1. **Feed/Timeline** - Compact, scannable view
- Emphasis on density, quick scanning
- Show summary/preview, not full content
- Inline media thumbnails
- Minimal interaction chrome
2. **Detail** - Expansive, full-content view
- Emphasis on readability, completeness
- Full markdown rendering, full-size media
- Show relationships (replies, zaps, reactions)
- Additional metadata and actions
3. **Embedded** - Nested preview within another event
- Context-aware depth limiting
- Minimal chrome (no duplicate headers if already in context)
- Click to expand/navigate
- Performance-conscious (lazy load)
### Current Implementation
**Feed Rendering:**
- ✅ Works well with `KindRenderer` + `BaseEventContainer`
- ✅ Consistent pattern across all kinds
- ⚠️ No virtualization for performance
**Detail Rendering:**
-**CRITICAL**: Hardcoded switch statement in `EventDetailViewer.tsx`:
```tsx
event.kind === kinds.Metadata ? <Kind0DetailRenderer />
: event.kind === kinds.Contacts ? <Kind3DetailView />
: event.kind === kinds.LongFormArticle ? <Kind30023DetailRenderer />
: <KindRenderer event={event} />
```
- ❌ Breaks registry pattern - not extensible
- ❌ Only 5 kinds have detail renderers, rest fallback to feed
**Embedded Rendering:**
- ⚠️ Uses same as feed (via `EmbeddedEvent` → `KindRenderer`)
- ⚠️ No context awareness
- ⚠️ Depth tracking inconsistent
### Recommended Architecture
**Unified Registry Pattern:**
```tsx
// Proposed structure
export const kindRenderers: KindRendererRegistry = {
1: {
feed: Kind1Renderer,
detail: Kind1DetailRenderer, // optional, fallback to feed
embed: Kind1EmbedRenderer, // optional, fallback to feed
},
// Or simplified:
1: Kind1Renderer, // if no variants needed
30023: {
feed: Kind30023Renderer, // Compact: title + summary
detail: Kind30023DetailRenderer, // Full markdown + relationships
}
};
// Usage
function KindRenderer({ event, context = 'feed' }) {
const registry = kindRenderers[event.kind];
const Renderer = registry?.[context] || registry?.feed || registry || DefaultKindRenderer;
return <Renderer event={event} context={context} />;
}
```
**Benefits:**
- ✅ Consistent pattern for all contexts
- ✅ Extensible - add detail renderers without modifying router
- ✅ Self-documenting - registry shows available variants
- ✅ Type-safe - validate registry at compile time
---
## Part 4: Depth Tracking & Nesting
### The Problem
Events can reference other events infinitely:
- Kind 6 (repost) of Kind 6 of Kind 6... → infinite loop
- Kind 1 (note) replying to Kind 1 replying to Kind 1... → deep nesting
- Kind 9735 (zap) of article containing zaps... → exponential expansion
### Current State
- ✅ `Kind1Renderer` passes `depth` to `RichText`
- ✅ `RichText` uses depth to limit nesting
- ❌ `Kind6Renderer` (repost) doesn't track depth → infinite loop possible
- ❌ `Kind9735Renderer` (zap) embeds without depth → can nest infinitely
- ❌ `EmbeddedEvent` doesn't auto-increment depth
### Solution: Systematic Depth Management
```tsx
// 1. Universal depth constant
export const MAX_EMBED_DEPTH = 3;
// 2. All renderers receive and honor depth
export interface BaseEventProps {
event: NostrEvent;
depth?: number;
context?: 'feed' | 'detail' | 'embed';
}
// 3. EmbeddedEvent auto-increments
export function EmbeddedEvent({ eventId, depth = 0, ...props }) {
const event = useNostrEvent(eventId);
if (!event) return <LoadingState />;
if (depth >= MAX_EMBED_DEPTH) {
return <CollapsedPreview event={event} onExpand={...} />;
}
return <KindRenderer event={event} depth={depth + 1} />;
}
// 4. Depth-aware rendering
export function Kind6Renderer({ event, depth = 0 }) {
if (depth >= MAX_EMBED_DEPTH) {
return <BaseEventContainer event={event}>
<div>Repost of <EventLink id={...} /></div>
</BaseEventContainer>;
}
return <BaseEventContainer event={event}>
<div>Reposted</div>
<EmbeddedEvent eventId={...} depth={depth} />
</BaseEventContainer>;
}
```
**Benefits:**
- ✅ Prevents infinite loops
- ✅ Improves performance (limits cascade fetching)
- ✅ Better UX (collapsed deep threads with expand option)
- ✅ Consistent behavior across all renderers
---
## Part 5: Threading & Reply Abstraction
### The Challenge
Multiple threading models exist in Nostr:
1. **NIP-10** (Kind 1 notes)
- `e` tags with markers: `["e", id, relay, "root"|"reply"]`
- Root = original post, Reply = immediate parent
- Mentions = other referenced events
2. **NIP-22** (Kind 1111 comments)
- **Uppercase tags** = root scope: `K`, `E`, `A`, `I`, `P`
- **Lowercase tags** = parent item: `k`, `e`, `a`, `i`, `p`
- Can thread on events OR external identifiers (URLs, podcasts, etc.)
- MUST NOT reply to kind 1 notes (use kind 1 instead)
3. **NIP-28** (Kind 42 channel messages)
- Replies within channel context
- Different tag structure
4. **NIP-29** (Kinds 10, 11, 12 group messages)
- Group-specific threading
- Additional permissions layer
### Current Implementation
- ✅ Kind1Renderer shows NIP-10 reply indicator
- ✅ Uses `getNip10References` from applesauce
- ❌ No support for NIP-22 (Kind 1111 not implemented)
- ❌ No support for other threading models
- ❌ No generic threading components
### Proposed Abstraction
**Helper Layer:**
```tsx
// src/lib/threading.ts
export interface ThreadReference {
type: 'nip10' | 'nip22' | 'nip28' | 'nip29';
root?: EventPointer | AddressPointer | string; // string for external (NIP-22)
parent?: EventPointer | AddressPointer | string;
mentions?: Array<EventPointer | AddressPointer>;
rootAuthor?: string;
parentAuthor?: string;
}
export function getThreadReferences(event: NostrEvent): ThreadReference | null {
// Detect threading model by kind and tags
if (event.kind === 1) return getNip10Thread(event);
if (event.kind === 1111) return getNip22Thread(event);
if (event.kind === 42) return getNip28Thread(event);
// ... etc
return null;
}
```
**Component Layer:**
```tsx
// Generic thread indicator
export function ThreadIndicator({ event, depth = 0 }) {
const refs = getThreadReferences(event);
if (!refs) return null;
const parentEvent = useNostrEvent(refs.parent);
return (
<div className="thread-indicator">
<Reply className="icon" />
<span>Replying to</span>
{parentEvent ? (
<ThreadPreview event={parentEvent} depth={depth} />
) : refs.type === 'nip22' && typeof refs.parent === 'string' ? (
<ExternalThreadContext url={refs.parent} />
) : (
<LoadingIndicator />
)}
</div>
);
}
// Generic thread tree (for detail view)
export function ThreadTree({ rootEvent }) {
const replies = useReplies(rootEvent);
return (
<div className="thread-tree">
{replies.map(reply => (
<ThreadBranch key={reply.id} event={reply} />
))}
</div>
);
}
```
**Benefits:**
- ✅ Single component works across all threading models
- ✅ Extensible to new NIPs
- ✅ Reusable across different renderers
- ✅ Consistent UX for users
- ✅ Easier to maintain (one place to fix threading bugs)
---
## Part 6: Metadata Extraction Patterns
### Current Approaches
1. **Applesauce Helpers** ✅ (GOOD)
- `getArticleTitle`, `getZapAmount`, `getNip10References`
- Well-tested, consistent, handles edge cases
- Examples: `Kind30023Renderer`, `Kind9735Renderer`
2. **Manual Tag Parsing** ⚠️ (INCONSISTENT)
- `event.tags.find(t => t[0] === "e")`
- Error-prone, repeated code, misses edge cases
- Examples: `Kind6Renderer`, various places
3. **JSON Parsing** ❌ (ERROR-PRONE)
- `JSON.parse(event.content)` without try/catch
- Can crash entire app if malformed
- Examples: Profile metadata, relay lists
### What Applesauce Provides
Currently has helpers for:
- ✅ Articles (30023): title, summary, published, image
- ✅ Zaps (9735): amount, sender, request, pointers
- ✅ Threading (1): NIP-10 references
- ✅ Profiles (0): metadata parsing
- ✅ Relays: seen relays, relay hints
### What's Missing
Need helpers for:
- ❌ File metadata (1063): url, hash, size, mime, dimensions
- ❌ Media events (20, 21, 22): URLs, thumbnails, dimensions
- ❌ List events (30000+): systematic list item extraction
- ❌ Comments (1111): NIP-22 uppercase/lowercase tag parsing
- ❌ Reactions (7): emoji normalization (+ → ❤️)
- ❌ Reposts (6, 16, 18): reposted event extraction
- ❌ Highlights (9802): context, highlight text
- ❌ Calendar events (31922-31925): date/time parsing
- ❌ Polls (1068): options, votes, tally
- ❌ Communities (34550): community info extraction
### Recommendation
**Architecture Principle:** Renderers should NEVER parse tags/content directly.
```tsx
// BAD ❌
const eTag = event.tags.find(t => t[0] === "e")?.[1];
// GOOD ✅
import { getRepostedEvent } from '@/lib/helpers/repost';
const repostPointer = getRepostedEvent(event);
```
**Action Items:**
1. Contribute missing helpers to applesauce-core (if generic)
2. Create local helper library for Grimoire-specific needs
3. Audit all renderers, replace manual parsing with helpers
4. Enforce via ESLint rule: no direct `event.tags.find`
---
## Part 7: Performance & Scalability
### Current Bottlenecks
1. **No Virtualization**
- All events in feed render immediately
- 1000 events = 1000 DOM nodes = slow scroll
- Wastes memory on off-screen content
2. **No Memoization**
- RichText parses content on every render
- Profile lookups happen repeatedly
- JSON.parse re-runs unnecessarily
- Expensive computations not cached
3. **No Lazy Loading**
- All renderer code loaded upfront
- ~20 renderer components = large initial bundle
- Could code-split by kind
4. **Heavy Base Components**
- Every event has `BaseEventContainer` overhead
- Profile fetch for every `EventAuthor`
- Could batch profile fetches
5. **Cascading Fetches**
- Embedded event triggers fetch
- That event might embed another
- Exponential growth without depth limiting
### Good News
- ✅ EventStore handles deduplication
- ✅ Dexie provides offline caching
- ✅ Reactive system (RxJS) is efficient
### Solutions
**1. Virtual Scrolling**
```tsx
import { Virtuoso } from 'react-virtuoso';
function EventFeed({ events }) {
return (
<Virtuoso
data={events}
itemContent={(index, event) => (
<KindRenderer event={event} />
)}
/>
);
}
```
**2. Memoization**
```tsx
// Wrap all renderers
export const Kind1Renderer = React.memo(({ event, depth }) => {
const refs = useMemo(() => getNip10References(event), [event.id]);
const handleClick = useCallback(() => {...}, [event.id]);
return <BaseEventContainer event={event}>
<RichText event={event} depth={depth} />
</BaseEventContainer>;
});
```
**3. Code Splitting**
```tsx
// Lazy load detail renderers
const Kind30023DetailRenderer = lazy(() =>
import('./kinds/Kind30023DetailRenderer')
);
// Use with Suspense
<Suspense fallback={<LoadingSpinner />}>
<Kind30023DetailRenderer event={event} />
</Suspense>
```
**4. Batch Profile Fetches**
```tsx
// Instead of individual useProfile in every EventAuthor
// Batch load all visible profiles
function EventFeed({ events }) {
const pubkeys = useMemo(() =>
events.map(e => e.pubkey), [events]
);
useBatchProfiles(pubkeys); // Prefetch
return events.map(e => <KindRenderer event={e} />);
}
```
**Performance Targets:**
- Feed with 10,000 events: Smooth 60fps scroll
- Initial render: < 100ms
- Event interaction: < 50ms response
- Bundle size: < 300KB for core, lazy load rest
---
## Part 8: Error Handling & Resilience
### Current Error Scenarios
1. **Malformed Events**
- Invalid JSON in content
- Missing required tags
- Incorrect tag structure
2. **Network Failures**
- Relays timeout
- Event not found
- Incomplete data
3. **Parsing Failures**
- Markdown rendering errors
- NIP-19 decode failures
- Media load failures
4. **Rendering Errors**
- Component crashes
- Infinite loops (depth issue)
- Out of memory
### Current Handling
- ⚠️ Some try/catch in parsers (inconsistent)
- ⚠️ EmbeddedEvent shows "Loading..." forever if fetch fails
- ❌ No error boundaries around renderers
- ✅ DefaultKindRenderer for unknown kinds (good!)
### Solution: Error Boundaries
```tsx
// Per-event error boundary
export function EventErrorBoundary({ children, event }) {
return (
<ErrorBoundary
fallback={({ error, resetErrorBoundary }) => (
<div className="event-error-card">
<AlertCircle className="icon" />
<div>
<h4>Failed to render event</h4>
<p className="text-sm text-muted-foreground">
Kind {event.kind} • {event.id.slice(0, 8)}
</p>
<details>
<summary>Error details</summary>
<pre>{error.message}</pre>
</details>
</div>
<div className="actions">
<button onClick={resetErrorBoundary}>Retry</button>
<button onClick={() => viewJson(event)}>View JSON</button>
<button onClick={() => reportIssue(event, error)}>Report</button>
</div>
</div>
)}
>
{children}
</ErrorBoundary>
);
}
// Usage in feed
{events.map(event => (
<EventErrorBoundary key={event.id} event={event}>
<KindRenderer event={event} />
</EventErrorBoundary>
))}
```
**Benefits:**
- ✅ One broken event doesn't break entire feed
- ✅ User gets actionable error info
- ✅ Developer gets diagnostics
- ✅ Graceful degradation
---
## Part 9: Accessibility & Internationalization
### Accessibility Gaps
**Keyboard Navigation:**
- ❌ No keyboard shortcuts for common actions
- ❌ Can't navigate between events with Tab
- ❌ Can't expand/collapse without mouse
**Screen Reader Support:**
- ❌ EventMenu has no aria-label
- ❌ Embedded events don't announce properly
- ❌ Time stamps are "2h ago" but no absolute time for SR
- ⚠️ BaseEventContainer uses `<div>` not `<article>`
**Visual:**
- ⚠️ Muted text might not meet WCAG AA contrast
- ❌ No prefers-reduced-motion support
- ❌ Focus indicators inconsistent
**RTL Support:**
- ❌ Noted in TODO as partially implemented
- ❌ Inline elements conflict with RTL alignment
### I18n Gaps
- ⚠️ Timestamps use locale from state but inconsistently
- ❌ Number formatting hardcoded to "en" (zap amounts)
- ❌ Kind names are English-only strings
- ❌ Error messages hardcoded English
- ❌ No language detection for content
### Solutions
**Semantic HTML:**
```tsx
export function BaseEventContainer({ event, children }) {
return (
<article className="event-card" aria-labelledby={`event-${event.id}`}>
<header className="event-header">
<EventAuthor pubkey={event.pubkey} />
<time dateTime={...} aria-label={absoluteTime}>
{relativeTime}
</time>
<EventMenu event={event} aria-label="Event actions" />
</header>
<section className="event-content">{children}</section>
</article>
);
}
```
**Keyboard Navigation:**
```tsx
// Arrow keys to navigate events
// Enter to open detail
// Escape to close
// Tab to focus actions
useKeyboardNavigation({
onUp: () => focusPrevEvent(),
onDown: () => focusNextEvent(),
onEnter: () => openEventDetail(),
});
```
**I18n:**
```tsx
import { useTranslation } from 'react-i18next';
export function EventMenu({ event }) {
const { t } = useTranslation();
return (
<DropdownMenuItem>
{t('event.actions.copy_id')}
</DropdownMenuItem>
);
}
```
---
## Part 10: Developer Experience
### Current DX
**Good:**
- ✅ Clear file structure (`src/components/nostr/kinds/`)
- ✅ TypeScript types (`BaseEventProps`)
- ✅ README documenting pattern
- ✅ Consistent naming (`KindXRenderer`)
**Friction:**
- ❌ Can't hot-reload new renderer without modifying `index.tsx`
- ❌ No component gallery (Storybook)
- ❌ Hard to test renderers in isolation
- ❌ Manual registration in multiple places
- ❌ No development tooling (event inspector)
- ❌ No renderer generator CLI
### Ideal DX
**1. Convention-Based Registration**
```bash
# Just create the file, auto-discovered
src/components/nostr/kinds/Kind1111Renderer.tsx
# No need to modify index.tsx
```
**2. Component Gallery**
```tsx
// Visit /dev/renderers in dev mode
// Browse all renderers with sample events
// Test with different contexts (feed/detail/embed)
// Inspect props, performance
```
**3. Testing Utilities**
```tsx
import { renderKind, mockEvent } from '@/test/utils';
test('Kind1Renderer displays content', () => {
const event = mockEvent({ kind: 1, content: 'Hello' });
const { getByText } = renderKind(1, event);
expect(getByText('Hello')).toBeInTheDocument();
});
```
**4. Generator CLI**
```bash
npm run generate:renderer -- --kind 1111 --nip 22
# Scaffolds:
# - Kind1111Renderer.tsx with boilerplate
# - Kind1111Renderer.test.tsx
# - Updates registry
# - Adds to documentation
```
**5. Dev Tools**
```tsx
// Browser extension or dev panel
<EventInspector event={event}>
<KindRenderer event={event} />
</EventInspector>
// Shows: props, state, performance, helper calls, errors
```
---
## Part 11: What's Working Well (To Preserve)
These patterns are **strengths** to maintain and enhance:
1. ✅ **Registry Pattern**: Centralized kind → renderer mapping
2. ✅ **BaseEventContainer**: Consistent header/footer
3. ✅ **Applesauce Integration**: Using library helpers
4. ✅ **Type Safety**: TypeScript interfaces
5. ✅ **Separation of Concerns**: Rendering separate from data fetching
6. ✅ **Recursive Rendering**: KindRenderer can nest
7. ✅ **Universal Actions**: EventMenu available everywhere
8. ✅ **Event Identity**: Good handling of regular vs addressable
9. ✅ **Default Fallback**: Unknown kinds still display
10. ✅ **Component Reuse**: EmbeddedEvent, MediaEmbed, RichText
**Don't throw away these foundations - build on them!**
---
## Part 12: Comprehensive Improvement Roadmap
### Phase 1: Foundation Fixes (1-2 weeks)
**Goal:** Fix critical architectural issues and quick wins
**1.1 Unified Detail Renderer Registry**
- Remove hardcoded switch in EventDetailViewer
- Create `detailRenderers` map parallel to `kindRenderers`
- Fallback logic: detail → feed → default
- Files: `src/components/nostr/kinds/index.tsx`, `EventDetailViewer.tsx`
- **Impact:** HIGH | **Effort:** LOW
**1.2 Systematic Depth Tracking**
- Add `MAX_EMBED_DEPTH` constant
- Update `BaseEventProps` to require depth
- Audit all renderers using `EmbeddedEvent`
- Implement `CollapsedPreview` for max depth
- Files: All `*Renderer.tsx` files
- **Impact:** HIGH | **Effort:** MEDIUM
**1.3 Error Boundaries**
- Create `EventErrorBoundary` component
- Wrap all events in feeds
- Add diagnostic error cards
- File: `src/components/EventErrorBoundary.tsx`
- **Impact:** HIGH | **Effort:** LOW
**1.4 Fix JSON Viewer Scrolling**
- From TODO: "JSON viewer scrolling"
- Add `overflow-auto` and `max-height` to JSON container
- File: `src/components/JsonViewer.tsx`
- **Impact:** MEDIUM | **Effort:** TRIVIAL
**1.5 Renderer Memoization**
- Wrap all renderer components with `React.memo`
- Add `useMemo` for expensive computations
- Add `useCallback` for handlers
- Files: All `*Renderer.tsx` files
- **Impact:** MEDIUM | **Effort:** LOW
**Deliverables:**
- [ ] Detail renderer registry implemented
- [ ] All renderers honor depth (with tests)
- [ ] Error boundaries deployed
- [ ] JSON viewer scrolls properly
- [ ] All renderers memoized
---
### Phase 2: Component Library (2-3 weeks)
**Goal:** Build reusable abstractions for common patterns
**2.1 Generic Threading Components**
- `getThreadReferences()` helper supporting NIP-10, NIP-22, NIP-28
- `<ThreadIndicator>` component
- `<ThreadContext>` for parent preview
- `<ThreadTree>` for detail view reply chains
- Files: `src/lib/threading.ts`, `src/components/Thread/`
- **Impact:** HIGH | **Effort:** HIGH
**2.2 NIP-22 Comment Support**
- Implement `Kind1111Renderer` (from TODO)
- NIP-22 tag parsing helpers (K/k, E/e, A/a, I/i, P/p)
- External identifier display (I tags)
- Nested comment threading
- Files: `src/lib/helpers/nip22.ts`, `src/components/nostr/kinds/Kind1111Renderer.tsx`
- **Impact:** HIGH | **Effort:** HIGH
**2.3 Relationship Panels**
- `<RepliesPanel>` - Show replies to event
- `<ZapsPanel>` - Show zaps with total/list
- `<ReactionsPanel>` - Group reactions by emoji
- `<EngagementFooter>` - Universal engagement indicators
- Use in detail renderers
- Files: `src/components/nostr/Relationships/`
- **Impact:** MEDIUM | **Effort:** MEDIUM
**2.4 Enhanced Media Components**
- Multi-stage rendering (placeholder → thumbnail → full → error)
- Lazy loading with IntersectionObserver
- NSFW blur with content-warning tag support
- Quality selection for videos
- Accessibility improvements (alt text, captions)
- Files: Enhance `src/components/nostr/MediaEmbed.tsx`
- **Impact:** MEDIUM | **Effort:** MEDIUM
**2.5 Context-Aware Rendering**
- Add `context` prop to BaseEventProps
- Renderers adapt to feed vs detail vs embed
- Update all existing renderers
- Files: `src/components/nostr/kinds/index.tsx`, all renderers
- **Impact:** MEDIUM | **Effort:** LOW
**Deliverables:**
- [ ] Threading works across NIP-10, NIP-22, NIP-28
- [ ] Kind 1111 (comments) fully functional
- [ ] Detail views show relationships
- [ ] Media rendering has all stages
- [ ] Context awareness implemented
---
### Phase 3: Architecture Evolution (3-4 weeks)
**Goal:** Transform into production-grade framework
**3.1 Performance Optimization**
- Virtual scrolling with react-virtuoso
- Code splitting for detail renderers
- Batch profile fetching
- Suspense boundaries
- Performance monitoring
- Files: `src/components/ReqViewer.tsx`, `EventDetailViewer.tsx`
- **Impact:** HIGH | **Effort:** MEDIUM
**3.2 Helper Library Expansion**
- Audit all renderers for manual tag parsing
- Create helpers for all missing NIPs:
- File metadata (1063)
- Media events (20, 21, 22)
- Lists (30000+)
- Reposts (6, 16, 18)
- Highlights (9802)
- Calendar (31922-31925)
- Polls (1068)
- Submit generic ones to applesauce-core
- Files: `src/lib/helpers/` directory structure
- **Impact:** HIGH | **Effort:** HIGH
**3.3 Accessibility Improvements**
- Semantic HTML (`<article>`, `<time>`, proper headings)
- ARIA labels and roles
- Keyboard navigation system
- Focus management
- Screen reader testing and fixes
- WCAG AA compliance audit
- Files: All renderers, BaseEventContainer
- **Impact:** MEDIUM | **Effort:** MEDIUM
**3.4 Internationalization**
- i18next integration
- Extract all hardcoded strings
- Locale-aware number/date formatting
- Kind name translations
- RTL support improvements
- Files: Setup i18n infrastructure, translate all components
- **Impact:** MEDIUM | **Effort:** MEDIUM
**3.5 Composable Renderer System**
- Break renderers into smaller components:
- Content components (primary payload)
- Metadata components (structured data)
- Relationship components (connections)
- Action components (interactions)
- Enable mix-and-match composition
- Files: Refactor all complex renderers
- **Impact:** MEDIUM | **Effort:** HIGH
**Deliverables:**
- [ ] Smooth 60fps scroll with 10K events
- [ ] All NIPs have helper functions
- [ ] WCAG AA compliant
- [ ] Multi-language support
- [ ] Composable renderer architecture
---
### Phase 4: Developer Experience (2-3 weeks)
**Goal:** Make development delightful and efficient
**4.1 Component Gallery (Storybook)**
- Setup Storybook
- Create stories for all renderers
- Mock event generator
- Interactive playground
- Visual regression testing
- Files: `.storybook/`, `src/components/nostr/kinds/*.stories.tsx`
- **Impact:** HIGH | **Effort:** MEDIUM
**4.2 Testing Infrastructure**
- Test utilities: `renderKind()`, `mockEvent()`
- Unit tests for all helpers
- Integration tests for renderers
- E2E tests for common flows
- Coverage targets (>80%)
- Files: `src/test/`, `*.test.tsx` for all renderers
- **Impact:** HIGH | **Effort:** HIGH
**4.3 Generator CLI**
- `generate:renderer` command
- Scaffolds: renderer, tests, types
- Auto-updates registry
- Generates documentation stub
- Files: `scripts/generate-renderer.ts`
- **Impact:** MEDIUM | **Effort:** LOW
**4.4 Development Tools**
- Event inspector dev panel
- Performance profiler
- Helper call tracer
- Error diagnostic viewer
- Files: `src/dev-tools/`
- **Impact:** MEDIUM | **Effort:** MEDIUM
**4.5 Documentation**
- Architecture guide (this document as living docs)
- Renderer patterns guide
- Helper function reference
- Contribution guide
- API documentation (TypeDoc)
- Files: `docs/` directory
- **Impact:** HIGH | **Effort:** MEDIUM
**Deliverables:**
- [ ] Storybook with all renderers
- [ ] >80% test coverage
- [ ] Renderer generator working
- [ ] Dev tools panel functional
- [ ] Comprehensive documentation
---
## Part 13: Success Metrics
**Phase 1 Success:**
- Zero infinite loop bugs
- <1% event rendering errors in production
- Detail renderers added without modifying router
**Phase 2 Success:**
- NIP-22 comments working end-to-end
- Detail views show full relationship context
- All threading models supported
**Phase 3 Success:**
- 10K event feed scrolls at 60fps
- Zero manual tag parsing in renderers
- WCAG AA accessibility audit passes
**Phase 4 Success:**
- New renderer takes <30min to scaffold
- >80% test coverage maintained
- Storybook has 100% renderer coverage
**Overall Success:**
- Contributors can add renderers without asking questions
- Users report high quality, responsive UI
- Grimoire becomes reference implementation for Nostr clients
---
## Part 14: Priority Decision Matrix
**Immediate (Week 1-2):**
1. Detail renderer registry fix
2. Depth tracking safety
3. Error boundaries
4. JSON viewer scroll fix
**Short-term (Week 3-6):**
1. NIP-22 comment support (user request in TODO)
2. Threading abstraction
3. Memoization & performance basics
4. Relationship panels
**Medium-term (Week 7-12):**
1. Virtual scrolling
2. Helper library expansion
3. Accessibility improvements
4. Component gallery
**Long-term (Month 4+):**
1. Plugin architecture
2. Advanced dev tools
3. Full i18n
4. Composable system evolution
---
## Part 15: Risk Assessment
**Technical Risks:**
- 🟡 **Breaking changes**: Depth prop changes might break existing renderers
- *Mitigation:* Make depth optional with default 0, gradual rollout
- 🟡 **Performance regression**: Memoization might increase memory
- *Mitigation:* Monitor metrics, iterate based on data
- 🟢 **Compatibility**: Changes to BaseEventProps might affect community code
- *Mitigation:* Keep backward compatibility, deprecate gradually
**Resource Risks:**
- 🟡 **Time**: 10-12 weeks of focused work
- *Mitigation:* Phased approach, can ship incrementally
- 🟢 **Expertise**: Some NIPs are complex (NIP-22, NIP-29)
- *Mitigation:* Study specifications, prototype early
**User Impact:**
- 🟢 **Disruption**: Most changes are internal improvements
- 🟢 **Testing**: Can test thoroughly in staging before production
- 🟢 **Rollback**: Registry pattern makes rollback easy (swap renderers)
---
## Conclusion
The Grimoire event rendering system has **excellent foundations** but needs **systematic improvements** to scale from prototype to production.
**The path forward:**
1.**Preserve** what works (registry, base components, type safety)
2. 🔧 **Fix** critical issues (detail registry, depth tracking, error handling)
3. 🏗️ **Build** missing abstractions (threading, relationships, helpers)
4.**Optimize** performance (virtualization, memoization, lazy loading)
5. 🎨 **Polish** UX (accessibility, i18n, media rendering)
6. 🛠️ **Empower** developers (tooling, testing, documentation)
**This roadmap transforms the system from "working" to "world-class" in ~3 months** with measurable success criteria and manageable risk.
The result will be a **reference implementation** that other Nostr clients can learn from and contribute to.
---
**Next Steps:**
1. Review this analysis with team/community
2. Prioritize phases based on user feedback
3. Create GitHub issues/project board from roadmap
4. Begin Phase 1 implementation
5. Update TODO.md with prioritized items
**Questions for Discussion:**
- Which Phase 1 items are most critical?
- Should we tackle NIP-22 (comments) in Phase 1 given TODO mention?
- What's the community appetite for contributing to applesauce helpers?
- Performance targets: 10K events reasonable or aim higher/lower?
- Should we build plugin architecture in Phase 3 or defer?