Commit Graph

231 Commits

Author SHA1 Message Date
Alejandro Gómez
c4bc3ab445 ui: improve relay tooltip, update docs 2025-12-22 19:43:00 +01:00
Alejandro Gómez
3f1c66ec01 ui: adjustments 2025-12-22 19:33:53 +01:00
Claude
b9756b119b refactor: simplify relay list UI with compact status indicators and rich tooltips
**Compact Relay Item Display:**
- Removed left-side inbox/outbox count indicators (were causing misalignment)
- Replaced "EOSE" text with checkmark icon (✓)
- Event count shown as [N] badge (only if > 0)
- Auth icon now always visible (even for unauthenticated relays)
- Clean right-side layout: [count] [✓] [auth] [wifi]

**Always-Visible Auth Status:**
- Modified getAuthIcon() to always return an icon (never null)
- Unauthenticated relays show subtle shield icon (muted-foreground/40)
- Provides at-a-glance view of auth status for all relays
- Label: "No Authentication Required" for clarity

**Rich Hover Tooltips:**
- Comprehensive tooltip shows all relay details on hover
- Displays: connection status, auth status, subscription state, event count
- Shows inbox/outbox counts when available (moved from inline display)
- Formatted as structured table for easy scanning
- Positioned on left side to avoid blocking content

**Benefits:**
 Perfect alignment (no variable-width counts on left)
 Cleaner, more scannable visual design
 All information still accessible via hover
 Consistent icon count (always 2-4 icons per relay)
 Easy to spot EOSE status at a glance (green checkmark)

All 639 tests passing.
2025-12-22 18:23:12 +00:00
Claude
af8cf427d6 fix: implement per-relay EOSE detection by subscribing to relays individually
**CRITICAL FIX for EOSE detection:**

**The Problem:**
- Used pool.subscription(relays, filters) which creates a RelayGroup
- RelayGroup tracks per-relay EOSE internally but only emits ONE "EOSE" when ALL relays finish
- This caused:
  1. EOSE indicators taking forever to appear (waiting for slowest relay)
  2. REQ stuck in LOADING state when fast relays finish but slow relays never do
  3. No way to show per-relay EOSE status accurately

**The Solution:**
Subscribe to each relay individually using pool.relay(url).subscription():
- Each relay subscription emits its own EOSE immediately when that relay finishes
- We track per-relay EOSE in relayStates map with accurate timing
- Overall EOSE is derived when ALL relays reach terminal state (eose/error/disconnected)
- EOSE indicators now appear immediately as each relay finishes

**Technical Details:**
- Changed from: pool.subscription(relays, filters)
- Changed to: relays.map(url => pool.relay(url).subscription(filters))
- Added eoseReceivedRef to track overall EOSE in closures
- Mark specific relay as EOSE when that relay emits "EOSE"
- Calculate overall EOSE when all relays in terminal states
- Use url from subscription context (more reliable than event._relay)

**Benefits:**
 Instant per-relay EOSE indicators (no waiting for slowest relay)
 Accurate relay state tracking (each relay independent)
 REQ transitions to LIVE/CLOSED as soon as all relays finish
 Better user feedback (see which relays are done vs still loading)

All 639 tests passing.
2025-12-22 18:10:52 +00:00
Claude
b95ce25955 refactor: normalize hardcoded relay URLs and reorganize relay dropdown UI
**Normalize All Relay URLs:**
- Added trailing slashes to AGGREGATOR_RELAYS constants
- Ensures consistency with RelayStateManager's normalization
- Fixes fallback relay connection state tracking issue
- All hardcoded relay URLs now match normalized keys in relayStates

**Reorganize Relay Item UI:**
- Removed type indicator icons (LinkIcon/Sparkles/Inbox) from individual relay items
- Strategy type is already shown in header, no need to repeat per-item
- Moved inbox/outbox indicators from right side to left side of relay URL
- Left side now shows: inbox count (Mail icon) and/or outbox count (Send icon)
- Right side shows: event count, EOSE indicator, auth status, connection status
- Cleaner, more semantic layout with better visual hierarchy

**Why This Matters:**
The relay URL normalization fix ensures that fallback relays (AGGREGATOR_RELAYS)
now show accurate connection state in the UI. Previously, the non-normalized
URLs couldn't match keys in relayStates, making them appear disconnected even
when connected. This was the root cause of the "fallback relays not tracking"
issue.

All 639 tests passing.
2025-12-22 18:02:41 +00:00
Claude
b5e1cffc91 fix: add EOSE indicator, mute all icons, and fix relay URL normalization bug
Critical fixes for ReqViewer relay state accuracy:

1. **URL Normalization Fix** (fixes mismatch with CONN):
   - Added normalizeRelayURL to normalize all relay URLs in finalRelays
   - RelayStateManager normalizes URLs (adds trailing slash, lowercase) but
     finalRelays did not, causing lookup failures in relayStates
   - Now normalizedRelays is used for all state lookups and passed to
     useReqTimelineEnhanced to ensure consistency
   - This fixes the bug where ReqViewer showed different connected relay
     counts than CONN viewer

2. **EOSE Indicator**:
   - Added back EOSE indicator to relay dropdown (was removed in UI redesign)
   - Shows subtle "EOSE" text when relay has sent End of Stored Events
   - Includes tooltip explaining "End of stored events received"

3. **Muted Icons** (per user request for subtlety):
   - Type indicators: blue-500/purple-500 → muted-foreground/60
   - Strategy header icons: all → muted-foreground/60
   - Section headers: green-500 → muted-foreground
   - Connection icons: green-500/yellow-500/red-500 → /70 opacity variants
   - Auth icons: same color reduction for consistency
   - Maintains semantic meaning while reducing visual noise

All 639 tests passing.
2025-12-22 17:53:24 +00:00
Claude
ce3a4a7322 fix: handle all relays disconnecting before EOSE (stuck in LOADING bug)
Critical Edge Case Fix:
Previously, when all relays disconnected before sending EOSE, the state
remained stuck in LOADING because overallEoseReceived stayed false.

Solution: Check if all relays are in terminal states
- Terminal states: eose, error, or disconnected
- If all terminal AND no overall EOSE yet, derive state from events:
  * No events → FAILED
  * Has events, all disconnected, streaming → OFFLINE
  * Has events, all disconnected, non-streaming → CLOSED
  * Some active, some terminal → PARTIAL

New Test Coverage (5 tests):
1. All relays disconnect before EOSE, no events → FAILED
2. All relays disconnect before EOSE, with events (streaming) → OFFLINE
3. All relays disconnect before EOSE, with events (non-streaming) → CLOSED
4. Some EOSE, others disconnect before EOSE → PARTIAL
5. Mix of EOSE and errors, all terminal → PARTIAL

This fixes the user-reported issue where disconnected relays show LOADING
instead of transitioning to appropriate terminal state.

Tests: 639/639 passing (added 5 new edge case tests)
2025-12-22 17:38:31 +00:00
Claude
70651ae29f fix: improve relay state tracking and add relay type indicators
State Tracking Fixes:
- Sync connection state for ALL relays in query, not just initialized ones
- Defensively initialize missing relay states during sync
- Handle events from unknown relays (defensive initialization)
- Add debug console logs to track state transitions

Relay Type Indicators:
- Explicit relays: Blue link icon (relays specified directly)
- Outbox relays: Purple sparkles (NIP-65 selected)
- Fallback relays: Gray inbox icon (fallback when outbox incomplete)
- Each type has tooltip explaining source

This should fix:
- "0/4 relays but events coming in" bug
- "Stuck in LOADING" when events are arriving
- Missing visibility for relay source types

Tests: 634/634 passing
2025-12-22 16:36:56 +00:00
Claude
c9bf2fe599 fix: show ALL queried relays in dropdown (outbox + fallback + explicit)
Previously only showed relays from NIP-65 reasoning array, missing fallback
relays. Now always iterates over finalRelays (actual queried relays) and
looks up NIP-65 info if available.

Fixes:
- Fallback relays now visible in dropdown
- Relays show connection/subscription status regardless of source
- NIP-65 info (inbox/outbox counts) shown when available
- Works for outbox, fallback, and explicit relay configurations

Tests: 634/634 passing
2025-12-22 16:30:35 +00:00
Claude
1bb2727930 fix: remove unused variables and apply prettier formatting
- Remove unused connectedCount and relayStatesForReq variables
- Fix prettier formatting in ReqViewer.tsx
- All tests passing (634/634)
- Build successful
2025-12-22 16:25:42 +00:00
Claude
c60abe6df4 feat: implement production-grade REQ state machine with per-relay tracking
Core Infrastructure:
- Add ReqRelayState and ReqOverallState types for granular state tracking
- Implement deriveOverallState() state machine with 8 query states
- Create useReqTimelineEnhanced hook combining RelayStateManager + event tracking
- Add comprehensive unit tests (27 tests, all passing)

State Machine Logic:
- DISCOVERING: NIP-65 relay selection in progress
- CONNECTING: Waiting for first relay connection
- LOADING: Initial events loading
- LIVE: Streaming with active relays (only when actually connected!)
- PARTIAL: Some relays ok, some failed/disconnected
- OFFLINE: All relays disconnected after being live
- CLOSED: Query completed, all relays closed
- FAILED: All relays failed to connect

UI Updates:
- Single-word status indicators with detailed tooltips
- Condensed relay status into NIP-65 section (no duplicate lists)
- Per-relay subscription state badges (RECEIVING, EOSE, ERROR, OFFLINE)
- Event counts per relay
- Connection + Auth status integrated into single dropdown

Fixes Critical Bug:
- Solves "LIVE with 0 relays" issue (Scenario 5 from analysis)
- Distinguishes real EOSE from relay disconnections
- Accurate status for all 7 edge cases documented in analysis

Technical Approach:
- Hybrid: RelayStateManager for connections + event._relay for tracking
- Works around applesauce-relay catchError bug without forking
- No duplicate subscriptions
- Production-quality error handling

Tests: 27/27 passing including edge case scenarios
2025-12-22 16:18:15 +00:00
Claude
bebb4ed834 docs: add comprehensive ReqViewer state machine analysis and improvement plan
Analysis document:
- Identified critical bug in applesauce-relay catchError handling
- Documented 7 edge cases causing "LIVE with 0 relays" issue
- Root cause: relay disconnections treated as EOSE messages
- Detailed Nostr protocol semantics and applesauce behavior

Implementation plan:
- Hybrid approach: RelayStateManager + event metadata tracking
- New state types: ReqRelayState, ReqOverallState
- Enhanced hook: useReqTimelineEnhanced with per-relay tracking
- 3-phase rollout: infrastructure → UI → testing
- Comprehensive state machine with 8 query states, 8 relay states

This provides the foundation for production-quality REQ status tracking
that accurately handles disconnections, timeouts, and partial failures.
2025-12-22 15:59:00 +00:00
Alejandro
0d8f8b7807 Merge pull request #17 from purrgrammer/claude/applesauce-helpers-investigation-EWmaW
docs: add applesauce helpers investigation and refactoring plan
2025-12-22 15:19:41 +01:00
Claude
c40c4ae1f9 fix: TypeScript error in useStableFilters 2025-12-22 14:05:53 +00:00
Claude
912794f4e0 chore: lint fix 2025-12-22 14:04:46 +00:00
Claude
33539c291b refactor: use applesauce helpers for pointer parsing and filter comparison
Phase 2 & 3 of applesauce helpers refactoring plan.

**Phase 2: Replace manual pointer parsing**
- ReactionRenderer.tsx: replaced manual coordinate parsing with parseCoordinate helper
- Benefits: more robust, handles edge cases, consistent with applesauce patterns

**Phase 3: Improve filter comparison**
- useStable.ts: replaced JSON.stringify with isFilterEqual for filter comparison
- Benefits: handles undefined values correctly, supports NIP-ND AND operator
- Implementation uses ref pattern to maintain stable reference when filters are equal

All tests pass (607 tests).
2025-12-22 14:04:09 +00:00
Claude
4b7148510a refactor: remove unnecessary useMemo from applesauce helper calls
Phase 1 of applesauce helpers refactoring plan.

Removed useMemo from direct applesauce helper calls since these helpers
cache their results internally using symbol-based caching. This improves
code clarity without sacrificing performance.

Changes:
- ArticleRenderer.tsx: removed useMemo from getArticleTitle, getArticleSummary
- HighlightRenderer.tsx: removed useMemo from 6 highlight helpers
- HighlightDetailRenderer.tsx: removed useMemo from 6 highlight helpers
- CodeSnippetDetailRenderer.tsx: removed useMemo from 8 NIP-C0 helpers
- ChatView.tsx: removed useMemo from getNip10References, getTagValue

Kept useMemo in LiveActivityRenderer.tsx for parseLiveActivity and related
functions since these don't implement their own caching (noted for future
optimization).

All tests pass (607 tests).
2025-12-22 13:18:44 +00:00
Claude
2189d5a969 docs: add applesauce helpers investigation and refactoring plan
- Created APPLESAUCE_REFACTORING_PLAN.md with detailed analysis
- Updated CLAUDE.md with Applesauce Helpers & Caching section
- Enhanced applesauce-core skill with helper documentation

Key findings:
- Applesauce helpers cache internally using symbols
- No need for useMemo when calling applesauce helpers
- Identified 40+ useMemo instances that can be removed
- Documented available helpers and custom grimoire helpers
- Provided migration strategy and refactoring opportunities
2025-12-22 13:12:56 +00:00
Alejandro Gómez
32c895e150 chore: lint fix 2025-12-22 13:25:38 +01:00
Alejandro
ea3d0e1119 Merge pull request #16 from purrgrammer/claude/pluggable-storage-engine-F53Fc
Make storage engine pluggable for testing
2025-12-22 13:24:24 +01:00
Claude
729c83011e fix: add IndexedDB polyfill for test environment
Use fake-indexeddb to provide IndexedDB API in Node.js test environment.
This fixes 10 failing tests in spellbook-storage.test.ts that were
previously blocked by missing IndexedDB.

Changes:
- Add fake-indexeddb as dev dependency
- Create vitest setup file that imports the polyfill
- Update vitest.config.ts to use setup file
- Fix relay-selection.test.ts to clear cache between tests for isolation
2025-12-22 12:18:38 +00:00
Alejandro
2fda75ef71 Merge pull request #15 from purrgrammer/claude/documentation-EeWQZ
docs: add codebase analysis and accessibility plan
2025-12-22 13:14:00 +01:00
Alejandro
addea243d1 Merge pull request #12 from purrgrammer/claude/kind-utilities-EeWQZ
feat: add centralized nostr kind utilities
2025-12-22 13:12:52 +01:00
Alejandro
d35345f720 Merge pull request #13 from purrgrammer/claude/useprofile-race-fix-EeWQZ
fix: prevent race conditions in useProfile hook
2025-12-22 13:12:25 +01:00
Claude
a86b783f58 docs: add plan for pluggable storage engine
Analyze test failures due to missing IndexedDB in Node environment.
Present two options: fake-indexeddb polyfill vs full storage abstraction.
Recommend fake-indexeddb as pragmatic solution.
2025-12-22 12:11:33 +00:00
Alejandro
8862695ee6 Merge pull request #14 from purrgrammer/claude/stabilization-hooks-EeWQZ
feat: add dependency stabilization hooks
2025-12-22 13:10:51 +01:00
Claude
cdad01dc03 docs: add codebase analysis and accessibility plan
CODEBASE_ANALYSIS.md:
- Comprehensive architecture review
- State management analysis (Jotai + EventStore + Dexie)
- Component patterns assessment
- Security audit findings (zero vulnerabilities)
- Performance analysis
- 12-week S-tier improvement roadmap

ACCESSIBILITY_PLAN.md:
- Current state assessment (16% ARIA coverage)
- WCAG 2.1 AA compliance roadmap
- Phased implementation plan:
  - Phase 1: Foundation (keyboard nav, focus management)
  - Phase 2: Screen reader support
  - Phase 3: Visual accessibility
  - Phase 4: Advanced features
- Component-specific accessibility requirements
2025-12-22 12:03:33 +00:00
Claude
645e12cddc fix: prevent race conditions in useProfile hook
- Replace mounted boolean flag with AbortController pattern
- Check abort signal before initiating database writes
- Proper cleanup on unmount/pubkey change

This prevents stale data from being written to IndexedDB when:
- Component unmounts during async operations
- Pubkey changes while a fetch is in progress
2025-12-22 12:02:03 +00:00
Claude
bdfc634c54 feat: add dependency stabilization hooks
- Create src/hooks/useStable.ts with:
  - useStableValue<T>() - stabilizes any value using JSON.stringify
  - useStableArray<T>() - stabilizes string arrays (uses JSON.stringify
    for safety, handles arrays with commas in elements)
  - useStableFilters<T>() - specialized for Nostr filters

- Update timeline hooks to use stabilization:
  - useTimeline.ts - use useStableFilters for filter dependencies
  - useReqTimeline.ts - use useStableValue for filter dependencies
  - useLiveTimeline.ts - use useStableArray for relay dependencies

Prevents unnecessary re-renders and subscription restarts when
filter/relay objects are recreated with the same content.
2025-12-22 12:00:42 +00:00
Alejandro Gómez
62676feeba fix: respect zap message 2025-12-22 12:59:56 +01:00
Claude
96216450f4 feat: add centralized nostr kind utilities
- Create src/lib/nostr-kinds.ts with:
  - Re-exports from nostr-tools/kinds (isRegularKind, isReplaceableKind, etc.)
  - New isParameterizedReplaceableKind() function
  - New isAddressableKind() for determining naddr vs nevent encoding
  - NIP-01 boundary constants with clarifying comments
  - getKindCategory() for display purposes

- Update KindRenderer.tsx to use shared utilities:
  - Replace inline range checks with helper functions
  - Fix "Regular Lists" -> "Replaceable Events" naming
  - Simplify redundant condition (isReplaceableKind includes kinds 0, 3)

- Update BaseEventRenderer.tsx to use isAddressableKind()

- Add comprehensive tests for all utilities
2025-12-22 11:58:46 +00:00
Alejandro Gómez
64002ed1ec fix: zap sender in compact zaps 2025-12-22 12:49:28 +01:00
Alejandro Gómez
a9ae32ad01 fix: clickable spellbook link 2025-12-22 12:38:37 +01:00
Alejandro Gómez
e7a7bb05a4 feat: clickable spell and spellbook links 2025-12-22 12:38:37 +01:00
Alejandro
de063c8bfe Merge pull request #11 from purrgrammer/claude/add-since-until-spells-xluIg
fix: include since/until in spell filter JSON display
2025-12-22 12:37:24 +01:00
Alejandro
81566ab136 Merge pull request #10 from purrgrammer/claude/fix-placeholder-styling-7Yo4G
fix: remove custom font size from placeholders and hide embeds in previews
2025-12-22 12:36:54 +01:00
Claude
412ebccc4f fix: include since/until in spell filter JSON display
The filter JSON in SpellDetailRenderer was missing since/until
fields when they were in relative format (e.g., "7d", "now").
This happened because decodeSpell only adds these fields to
the filter object when they're unix timestamps.

Now we extract the raw since/until values from event tags and
include them in a displayFilter object for the JSON viewer,
ensuring users see the complete filter regardless of format.

The command preview already worked correctly since it uses
the reconstructed command string which includes these values.
2025-12-22 10:01:42 +00:00
Claude
470c802364 fix: ensure compact previews properly truncate RichText to single line
- Add line-clamp-1 to all compact preview components for consistent truncation
- Fix DefaultCompactPreview, GenericRepostCompactPreview, ZapCompactPreview
- Fix Kind1111Renderer parent event preview truncation
- Apply auto-formatting fixes from linter
2025-12-22 09:38:27 +00:00
Claude
41b012ae3e fix: remove custom font size from placeholders and hide embeds in previews
- Remove text-sm from MediaPlaceholder and EventPlaceholder to inherit parent font size
- Add options to hide media and event embeds in HighlightRenderer preview
- Ensures placeholders like [image], [note] match surrounding text size
2025-12-22 09:33:09 +00:00
Alejandro Gómez
125052f4c9 fix: spellbook preview or direct behavior 2025-12-21 22:47:24 +01:00
Alejandro Gómez
7f92277986 config: add vercel url rewrite 2025-12-21 22:39:07 +01:00
Alejandro Gómez
3c62a0e236 ui: share spellbook dialog improvements 2025-12-21 22:18:37 +01:00
Alejandro Gómez
fc63b3c685 fix: event publication 2025-12-21 21:50:09 +01:00
Alejandro Gómez
64212121bd ui: compact view tweaks 2025-12-21 21:19:31 +01:00
Alejandro Gómez
6481f1e04f feat: add voice message support (NIP-A0) and legacy video kinds
- Add VoiceMessageRenderer for kinds 1222 (voice message) and 1244 (voice reply)
- Add compact preview components with mic icon for voice messages
- Support legacy NIP-71 video kinds 34235/34236 via existing video renderers
- Add fallback URL tag parsing for video events
- Replace music note icon with mic icon for audio embeds
- Remove border/padding from audio player for cleaner display
- Add kind metadata (names, icons) for voice and legacy video kinds

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-21 20:36:53 +01:00
Alejandro Gómez
3346a2077d feat(req): add --view flag for list/compact display mode
- Add -v, --view <list|compact> flag to req command
- Remove UI toggle controls from ReqViewer header
- View mode is now set via command flag instead of runtime toggle
- Update man page with new flag documentation and example

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-21 20:29:32 +01:00
Alejandro Gómez
78a8c8e5b2 chore: apply prettier formatting fixes
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-21 20:12:40 +01:00
Alejandro Gómez
f4d0e86f09 feat: complete spellbook UX overhaul with enhanced state tracking
- Redesign SpellbookDropdown with clear status indicators (ownership, storage)
- Add SpellbookStatus component showing you/other and local/published/network
- Enhance activeSpellbook type with source, localId, isPublished fields
- Fix PublishSpellbook action to properly yield events (caller handles side-effects)
- Add k tags extraction from REQ windows for kind-based filtering/discovery
- Update terminology from "Layout" to "Spellbook" consistently
- Add comprehensive tests for k tags and source tracking

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-21 19:19:24 +01:00
Alejandro Gómez
be01c7d882 refactor: repo link 2025-12-21 18:19:36 +01:00
Alejandro Gómez
fffb80cd5c ui: remove icon 2025-12-21 18:13:46 +01:00