Extend relay filter chunking to route #p tags to inbox/read relays,
matching the existing outbox/write routing for authors. Remove debug
console.log statements across the codebase while preserving error-level
logging. Delete unused logger utility. Expand test coverage for all
chunking scenarios.
* feat: centralize publish flow with RxJS-based PublishService
Create a unified PublishService that:
- Provides consistent relay selection (outbox + state + hints + fallbacks)
- Emits RxJS observables for per-relay status updates
- Handles EventStore integration automatically
- Supports both fire-and-forget and observable-based publishing
Refactor all publish locations to use the centralized service:
- hub.ts: Use PublishService for ActionRunner publish
- delete-event.ts: Use PublishService (fixes missing eventStore.add)
- publish-spell.ts: Use PublishService with relay hint support
- PostViewer.tsx: Use publishWithUpdates() for per-relay UI tracking
This lays the groundwork for the event log feature by providing
observable hooks into all publish operations.
* feat: add LOG command for relay event introspection
Add an ephemeral event log system that tracks relay operations:
- EventLogService (src/services/event-log.ts):
- Subscribes to PublishService for PUBLISH events with per-relay status
- Monitors relay pool for CONNECT/DISCONNECT events
- Tracks AUTH challenges and results
- Captures NOTICE messages from relays
- Uses RxJS BehaviorSubject for reactive updates
- Circular buffer with configurable max entries (default 500)
- useEventLog hook (src/hooks/useEventLog.ts):
- React hook for filtering and accessing log entries
- Filter by type, relay, or limit
- Retry failed relays directly from the hook
- EventLogViewer component (src/components/EventLogViewer.tsx):
- Tab-based filtering (All/Publish/Connect/Auth/Notice)
- Expandable PUBLISH entries showing per-relay status
- Click to retry failed relays
- Auto-scroll to new entries (pause on scroll)
- Clear log button
- LOG command accessible via Cmd+K palette
* fix: prevent duplicate log entries and check relay OK response
- EventLogService: Check for existing entry before creating new one
when handling publish events (prevents duplicates from start/complete)
- PublishService: Check response.ok from pool.publish() to detect
relay rejections instead of assuming success on resolve
- Update test mock to return proper publish response format
* feat: keep relay selection in call site, compact logs
* chore: cleanup
* fix: make Timestamp component locale-aware via formatTimestamp
Timestamp was hardcoded to "es" locale. Now uses formatTimestamp()
from useLocale.ts for consistent locale-aware time formatting.
Added Timestamp to CLAUDE.md shared components documentation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: improve event-log reliability, add ERROR type and per-relay timing
Service improvements:
- Fix notice$ duplicate logging with per-relay dedup tracking
- Remove dead Array.isArray code path (notice$ emits strings)
- Increase relay poll interval from 1s to 5s
- Clean publishIdToEntryId map on terminal state, not just overflow
- Immutable entry updates (spread instead of in-place mutation)
- Extract NewEntry<T>/AddEntryInput helper types for clean addEntry signature
- Clear lastNoticePerRelay on log clear
New capabilities:
- ERROR log type: subscribes to relay.error$ for connection failure reasons
- RelayStatusEntry with updatedAt timestamp for per-relay response timing
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: improve EventLogViewer with virtualization, timing, and error display
- Virtualize log list with react-virtuoso for 500-entry buffer performance
- Add ErrorEntry renderer for new ERROR log type (AlertTriangle icon)
- Show per-relay response time (e.g. "142ms", "2.3s") in publish details
- Make all entry types expandable (connect/disconnect now have details)
- Show absolute timestamp in all expanded detail views
- Group ERROR events under Connect tab filter
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: prevent duplicate PUBLISH log entries from completion event
PublishService emits publish$ twice: once at start, once on completion.
The eager publishIdToEntryId cleanup in handleStatusUpdate fired before
the completion emission, causing handlePublishEvent to create a second
entry. Removed eager cleanup — overflow eviction is sufficient.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* feat(settings): add relay lists management section
- Fetch additional relay list kinds (10006, 10007, 10050) on login
- Add "Relays" tab to Settings with accordion UI for each relay list kind
- Support NIP-65 relay list (kind 10002) with read/write markers
- Support blocked relays (10006), search relays (10007), DM relays (10050)
- Add/remove relays with URL sanitization and normalization
- Explicit save button publishes only modified lists as replaceable events
https://claude.ai/code/session_01JHirYU56sKDKYhRx6aCQ54
* docs: add plan for honoring blocked & search relay lists
Detailed implementation plan for:
- Kind 10006: filter blocked relays from all connection paths
- Kind 10007: use search relays for NIP-50 queries
https://claude.ai/code/session_01JHirYU56sKDKYhRx6aCQ54
* refactor(settings): extract relay list logic into tested lib, fix UX issues
Extract parseRelayEntries, buildRelayListTags, sanitizeRelayInput, and
comparison/mode helpers into src/lib/relay-list-utils.ts with 52 tests
covering roundtrips, normalization, edge cases, and mode conversions.
UX fixes:
- Replace RelayLink (navigates away on click) with static RelaySettingsRow
- Remove redundant inbox/outbox icons (mode dropdown is sufficient)
- Always-visible delete button instead of hover-only opacity
- Per-accordion dirty indicator (CircleDot icon) for modified lists
- Discard button to reset all changes
- Read-only account explanation text
- Human-friendly descriptions (no NIP references or kind numbers)
- Separator between relay list and add input
- Larger relay icons and text for readability
https://claude.ai/code/session_01JHirYU56sKDKYhRx6aCQ54
* feat(settings): use KindBadge and NIPBadge in relay list accordions
Replace plain text kind names with KindBadge (full variant showing icon,
name, and kind number) and add NIPBadge next to each list description.
This gives power users the protocol context they expect.
Also document KindBadge and NIPBadge as shared components in CLAUDE.md.
https://claude.ai/code/session_01JHirYU56sKDKYhRx6aCQ54
* feat(settings): add favorite relays list (kind 10012) to relay settings
Add kind 10012 (Favorite Relays / Relay Feeds) to the settings UI and
account sync fetching. Uses "relay" tags like other NIP-51 lists.
https://claude.ai/code/session_01JHirYU56sKDKYhRx6aCQ54
* fix(kinds): use semantic icons for blocked and search relay lists
- Kind 10006 (Blocked Relays): Radio → ShieldBan
- Kind 10007 (Search Relays): Radio → Search
These icons propagate to KindBadge, settings accordions, and event
renderers via getKindInfo(). Generic relay kinds (10002, 30002, etc.)
keep the Radio icon.
https://claude.ai/code/session_01JHirYU56sKDKYhRx6aCQ54
---------
Co-authored-by: Claude <noreply@anthropic.com>
* feat: add NIP-85 Trusted Assertions feed & detail renderers
Add support for NIP-85 Trusted Assertion events (kinds 30382-30385) and
the Trusted Provider Declaration (kind 10040) with kind constants,
helper library, and feed + detail renderers.
- Add kind entries for 10040, 30382, 30383, 30384, 30385 to EVENT_KINDS
- Create src/lib/nip85-helpers.ts with cached helpers for parsing
assertion data (user, event, address, external) and provider lists
- Create shared TrustedAssertionRenderer for all 4 assertion kinds with
rank bar, subject display, and compact metrics preview
- Create TrustedAssertionDetailRenderer with full metrics table,
rank visualization, topics, and raw tag fallback
- Create TrustedProviderListRenderer/DetailRenderer for kind 10040
with provider table and encrypted entries indicator
- Register all renderers in kinds/index.tsx
https://claude.ai/code/session_01XjwLaShFSVPR5gNA7iUjuB
* refactor: extract NIP-73 helpers and shared ExternalIdentifierDisplay
Move getExternalIdentifierIcon() and getExternalIdentifierLabel() from
nip22-helpers.ts into a new nip73-helpers.ts since they are NIP-73
utilities, not NIP-22 specific. Add inferExternalIdentifierType() and
getExternalIdentifierHref() helpers.
Create shared ExternalIdentifierDisplay components (inline + block
variants) that use proper NIP-73 type-specific icons (Globe for web,
BookOpen for ISBN, Podcast for podcasts, Film for ISAN, etc.) instead
of a generic ExternalLink icon.
- Kind 1111 renderer now uses ExternalIdentifierInline for root scope
- Kind 30385 assertion renderer uses ExternalIdentifierInline (feed)
and ExternalIdentifierBlock (detail) for NIP-73 subjects
- nip22-helpers.ts re-exports from nip73-helpers for compatibility
https://claude.ai/code/session_01XjwLaShFSVPR5gNA7iUjuB
* fix: UI polish for NIP-85 renderers
Address review feedback across all NIP-85 components:
Icon: Replace BarChart3 with ShieldCheck for assertion kind constants
(30382-30385) — communicates "verified trust data" vs generic analytics.
Feed renderer: Subject is now the visual anchor (ClickableEventTitle)
with kind label as a small outline Badge above it. Rank bar widened to
w-32, color-coded green/yellow/red by score threshold. Fix "Zaps Recd"
abbreviation to "Zaps In".
Detail renderer: Metrics grouped into Activity, Zaps, Moderation
sections with uppercase section headers. Kind 30384 addresses now
show "Kind X by <UserName> / d-tag" instead of raw hex. ExternalMetrics
type badges now show NIP-73 icons + friendly labels (getExternalTypeLabel)
instead of raw k-tag values like "podcast:item:guid".
Provider list: Feed uses compact Badge for kind:tag + count summary.
Detail uses stacked cards instead of 3-column table for narrow panels.
ExternalIdentifierBlock: Linked blocks now show dotted underline on
label + ExternalLink icon for clear click affordance.
Add getExternalTypeLabel() to nip73-helpers for k-value display names.
https://claude.ai/code/session_01XjwLaShFSVPR5gNA7iUjuB
* fix: use Progress component for rank bars, consistent sats formatting
Replace custom rank bar divs with the existing Progress UI component
(with new indicatorClassName prop for color-coded fills).
Make sat amount displays consistent with zap receipt renderer pattern:
value and "sats" unit are rendered as separate elements — numeric value
in font-medium, unit in smaller muted text — matching how Kind9735
displays amounts across the app.
https://claude.ai/code/session_01XjwLaShFSVPR5gNA7iUjuB
* fix: use RelayLink for relay URLs, remove redundant kindTag, add relay hints
Provider list renderers now use RelayLink instead of raw relay URL
strings — shows favicon, insecure ws:// warning, opens relay detail
on click.
Remove kindTag display from provider cards — it's an internal
protocol detail redundant in the UI context.
Pass relay hints from provider entries to UserName so profiles can
be fetched from the relay the provider actually publishes to.
Add UserName relayHints prop (forwarded to useProfile).
Add RelayLink and UserName to Shared Components section in CLAUDE.md
so they're consistently used across the codebase.
https://claude.ai/code/session_01XjwLaShFSVPR5gNA7iUjuB
* docs: expand shared components section in CLAUDE.md
Add BaseEventContainer, ClickableEventTitle, RichText, QuotedEvent,
and CustomEmoji to the shared components reference. These are the
core building blocks used across all kind renderers — documenting
them prevents re-implementation and ensures consistent patterns.
https://claude.ai/code/session_01XjwLaShFSVPR5gNA7iUjuB
* docs: trim shared components list in CLAUDE.md
Remove BaseEventContainer and QuotedEvent — these are internal
patterns that kind renderer authors already know from context,
not general-purpose components that get misused or forgotten.
https://claude.ai/code/session_01XjwLaShFSVPR5gNA7iUjuB
* feat: show metric type labels in trusted assertion feed view
Add Label components to the assertion feed renderer so you can see at a
glance which metrics an assertion carries (Followers, Posts, Zaps, etc.)
instead of just numeric values. Also swap Badge → Label for the kind
indicator for visual consistency.
Replace hardcoded green/yellow/red rank colors with theme variables
(success/warning/destructive) in both feed and detail renderers so the
rank bar works correctly across all themes.
Add Label to CLAUDE.md shared components list (22 imports across the
codebase).
https://claude.ai/code/session_01XjwLaShFSVPR5gNA7iUjuB
* feat: show provider kind tag in trusted provider list renderers
Add Label with formatKindTag() to both feed and detail views so each
provider row shows what it provides (e.g. "User Assertion: Rank").
Also swap Badge → Label for consistency with the assertion renderers.
https://claude.ai/code/session_01XjwLaShFSVPR5gNA7iUjuB
* fix: stabilize relayHints in useProfile to prevent fetch abort loop
relayHints was used directly in the useEffect dependency array, so
callers passing a new array literal (e.g. [p.relay]) on every render
caused the effect to re-run each cycle — aborting the previous network
fetch before it could complete. The IndexedDB fast-path masked this in
the feed view (profiles already cached), but the detail view showed
raw pubkeys because profiles were never fetched from the network.
Wrap relayHints in a JSON.stringify-based useMemo (same pattern as
useStableArray) so the effect only re-runs when the actual relay
values change.
https://claude.ai/code/session_01XjwLaShFSVPR5gNA7iUjuB
---------
Co-authored-by: Claude <noreply@anthropic.com>
* feat(nip34): Add NIP-34 issue status renderers and locale-aware formatting
- Add IssueStatusRenderer for feed view (kinds 1630-1633: Open/Resolved/Closed/Draft)
- Add IssueStatusDetailRenderer for detail view with status badge and embedded issue
- Update IssueRenderer/IssueDetailRenderer to fetch and display current issue status
- Status validation respects issue author, repo owner, and maintainers
- Add status helper functions to nip34-helpers.ts (getStatusType, findCurrentStatus, etc.)
- Use parseReplaceableAddress from applesauce-core for coordinate parsing
- Expand formatTimestamp utility with 'long' and 'datetime' styles
- Fix locale-aware date formatting across all detail renderers
- Update CLAUDE.md with useLocale hook and formatTimestamp documentation
https://claude.ai/code/session_01C6Lty4k9pKxdwnYUCcpzV2
* refactor(nip34): Use theme semantic colors for issue status
Replace hardcoded colors with theme semantic colors:
- Resolved/merged: accent (positive)
- Closed: destructive (negative)
- Draft: muted
- Open: neutral foreground
Also fixes import placement in nip34-helpers.ts.
https://claude.ai/code/session_01C6Lty4k9pKxdwnYUCcpzV2
* fix(nip34): Use repository relays instead of AGGREGATOR_RELAYS
Status events for issues are now fetched from the relays configured
in the repository definition, not from hardcoded aggregator relays.
This respects the relay hints provided by repository maintainers for
better decentralization and reliability.
https://claude.ai/code/session_01C6Lty4k9pKxdwnYUCcpzV2
* perf(nip34): Add memoization caching to helper functions
Use getOrComputeCachedValue from applesauce-core to cache computed
values on event objects. This prevents redundant computation when
helpers are called multiple times for the same event.
Also added documentation in CLAUDE.md about best practices for
writing helper libraries that compute data from Nostr events.
https://claude.ai/code/session_01C6Lty4k9pKxdwnYUCcpzV2
* fix(nip34): Add relay fallback chain for status event fetching
Status events now use a fallback chain for relay selection:
1. Repository configured relays (from "relays" tag)
2. Repo author's outbox relays (from kind:10002)
3. AGGREGATOR_RELAYS as final fallback
This ensures status events can be fetched even when repository
doesn't have relays configured.
https://claude.ai/code/session_01C6Lty4k9pKxdwnYUCcpzV2
* feat(nip34): Add status rendering to Patch and PR renderers
- PatchRenderer and PatchDetailRenderer now show merge/closed/draft status
- PullRequestRenderer and PullRequestDetailRenderer now show merge/closed/draft status
- Status events fetched from repository relays with author outbox fallback
- For patches and PRs, kind 1631 displays as "merged" instead of "resolved"
- Fixed destructive color contrast in dark theme (30.6% -> 50% lightness)
https://claude.ai/code/session_01C6Lty4k9pKxdwnYUCcpzV2
* refactor(nip34): Extract StatusIndicator component, improve UI layout
- Create reusable StatusIndicator component for issues/patches/PRs
- Move status icon next to status text in feed renderers (not title)
- Place status badge below title in detail renderers
- Fix dark theme destructive color contrast (0 90% 65%)
- Remove duplicate getStatusIcon/getStatusColorClass functions
https://claude.ai/code/session_01C6Lty4k9pKxdwnYUCcpzV2
* fix(nip34): Make status badge width fit content
https://claude.ai/code/session_01C6Lty4k9pKxdwnYUCcpzV2
* fix(theme): Improve destructive color contrast on dark theme
Increase lightness from 65% to 70% for better readability.
https://claude.ai/code/session_01C6Lty4k9pKxdwnYUCcpzV2
* fix(theme): Use lighter coral red for destructive on dark theme
Changed to 0 75% 75% (~#E89090) for better contrast against #020817 background.
https://claude.ai/code/session_01C6Lty4k9pKxdwnYUCcpzV2
* docs: Fix applesauce helper documentation in CLAUDE.md
- Fix parseCoordinate -> parseReplaceableAddress (correct function name)
- Clarify getTagValue (applesauce) vs getTagValues (custom Grimoire)
- Add getOrComputeCachedValue to helpers list
- Improve code example with proper imports and patterns
https://claude.ai/code/session_01C6Lty4k9pKxdwnYUCcpzV2
* fix(nip34): Render status event content as rich text
Use MarkdownContent component for status event content in
Issue, Patch, and PR detail renderers.
https://claude.ai/code/session_01C6Lty4k9pKxdwnYUCcpzV2
* fix(nip34): Smaller status indicators, improve issue feed layout
- Use shared StatusIndicator in IssueStatusRenderer (smaller size)
- Render status event content as markdown
- Put status on its own line between title and repo in IssueRenderer
https://claude.ai/code/session_01C6Lty4k9pKxdwnYUCcpzV2
* fix(nip34): Use warning color for closed status instead of destructive
- Change closed status from red (destructive) to orange (warning)
- Improve dark theme status colors contrast (warning: 38 92% 60%)
- Less aggressive visual for closed issues/patches/PRs
https://claude.ai/code/session_01C6Lty4k9pKxdwnYUCcpzV2
---------
Co-authored-by: Claude <noreply@anthropic.com>
* fix: only prompt relay auth for accounts that can sign
- Add canAccountSign() helper to check if account is read-only
- Block auth prompts for read-only accounts in shouldPromptAuth()
- Throw error when authenticateRelay() called with read-only account
- Document all major app hooks in CLAUDE.md for future reference
Read-only accounts cannot sign events, so they should never be prompted
for relay authentication or attempt to authenticate. This prevents
confusing UX where users are asked to sign but cannot.
* refactor: extract canAccountSign helper to useAccount
- Move canAccountSign function from relay-state-manager to useAccount.ts
- Import and reuse the shared helper in relay-state-manager
- Update useAccount hook to use the extracted helper internally
- Follows DRY principle by centralizing account sign capability logic
This keeps the account sign capability detection logic in one place,
making it easier to maintain and ensuring consistency across the app.
---------
Co-authored-by: Claude <noreply@anthropic.com>
Reply Functionality:
- Added Reply button to each message (visible on hover)
- Button appears in message header next to timestamp
- Uses Reply icon from lucide-react
- Clicking reply sets the replyTo state with message ID
- Reply preview shows in composer when replying
Active Account Requirements:
- Check for active account using accountManager.active$
- Only show composer if user has active account
- Only enable reply buttons if user has active account
- Show "Sign in to send messages" message when no active account
- Prevent sending messages without active account
UI Improvements:
- Reply button uses opacity transition on hover (0 → 100)
- Positioned with ml-auto to align right in header
- Reply button only visible on group hover for clean UI
- Consistent styling with muted-foreground color scheme
Benefits:
- Users can reply to specific messages inline
- Clear indication when authentication is required
- Prevents errors from attempting to send without account
- Professional chat UX with hover interactions
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Add slash commands for common workflows:
- /commit-push-pr: Streamlined PR creation
- /verify: Full verification suite (lint + test + build)
- /test: Run tests with results summary
- /lint-fix: Auto-fix lint and formatting
- /review: Code review for quality and Nostr patterns
Update settings.json:
- Expand permissions for common safe bash commands
- Add PostToolUse hook for auto-formatting with Prettier
Update CLAUDE.md:
- Add Verification Requirements section
- Document available slash commands
- Emphasize running /verify before PRs
Co-authored-by: Claude <noreply@anthropic.com>
- 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