mirror of
https://github.com/purrgrammer/grimoire.git
synced 2026-04-09 23:16:50 +02:00
0a4318ae8c4b8bbfedc1cfcacfc5eb3f3c3bd2ab
153 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
143fa7b4b6 |
feat: refactor emoji search with singleton service, Dexie caching, and keyword-enriched search
Replace hand-rolled ~350-entry emoji list with unicode-emoji-json (~1,900 emojis) and emojilib keywords for richer search matching. Move EmojiSearchService to a singleton with Dexie-backed caching for instant availability on startup. Add recency+frequency emoji usage tracking shared across the emoji picker and editor autocomplete. |
||
|
|
0af86b667e | fix: make sure condensed view works in pop out windows | ||
|
|
48ce35cbea | feat: nip-5a nsites | ||
|
|
4a501634c5 | chore: sync NIPs | ||
|
|
9ded99420f | feat: favorite spells | ||
|
|
62785fa336 |
feat: add inbox (#p) chunking to relay filter splitting and clean up logging
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. |
||
|
|
9af896127e |
wip: relay filter chunking for REQ subscriptions
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
400e60107f | fix: don't transition away from eose state | ||
|
|
5619122b80 |
feat: add Educational Resource (kind 30142) with AMB metadata support (#260)
* feat: add Educational Resource (kind 30142) with AMB metadata support - Add feed and detail renderers for AMB Educational Resource events - Add amb-helpers library with cached helper functions and tests - Handle broken thumbnail images with BookOpen placeholder fallback - Surface primary resource URL prominently in both renderers (bookmark-style) - Register kind 30142 with GraduationCap icon in kind registry - Link NIP-AMB badge to community NIP event (kind 30817) * fix: address PR #260 review comments for Educational Resource renderers - Rename Kind30142*Renderer to EducationalResource*Renderer (human-friendly names) - Localize language names with Intl.DisplayNames via shared locale-utils - Use ExternalLink component for license and reference URLs - Localize ISO dates with formatISODate, fixing UTC timezone shift bug - Remove # prefix from keyword labels in both feed and detail renderers - Remove image/thumbnail from feed renderer - Extract getBrowserLanguage to shared locale-utils, reuse in amb-helpers * fix: mock getBrowserLanguage in tests for Node < 21 compat Tests were directly mutating navigator.language which doesn't exist as a global in Node < 21, causing ReferenceError in CI. |
||
|
|
80bd6c4e72 | fix: normalize supported NIPs to strings | ||
|
|
80a421b9fe |
Centralize relay publishing via PublishService (#211)
* 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> |
||
|
|
8c9ecd574c |
refactor(editor): replace DOM manipulation with React node views and floating-ui (#253)
* refactor(editor): replace DOM manipulation with React node views and floating-ui - Convert all inline node views (emoji, blob attachment, event preview) from imperative document.createElement() to React components via ReactNodeViewRenderer - Replace tippy.js with @floating-ui/react-dom for suggestion popup positioning - Create useSuggestionRenderer hook bridging Tiptap suggestion callbacks to React state - Extract shared EmojiMention, SubmitShortcut, and inline node extensions to separate files - Extract types (EmojiTag, BlobAttachment, SerializedContent) to editor/types.ts - Extract serialization logic to editor/utils/serialize.ts - Remove redundant DOM keydown listener from RichEditor (handled by SubmitShortcut extension) - Remove tippy.js dependency (-1045 lines net, RichEditor 632→297, MentionEditor 1038→354) https://claude.ai/code/session_01CzeTFzSETs9wSPH2feDE6u * fix(editor): fix suggestion popover positioning, scrolling, and profile click behavior - Replace UserName component in ProfileSuggestionList with plain text display so clicking a suggestion autocompletes instead of opening their profile (UserName has an onClick that calls addWindow and stopPropagation) - Add react-virtuoso to ProfileSuggestionList for efficient lazy rendering of up to 20 search results with fixed item height scrolling - Add profile avatars with lazy loading and initial-letter fallback - Fix SuggestionPopover positioning with autoUpdate for scroll/resize tracking - Add size middleware to constrain popover max-height to available viewport space https://claude.ai/code/session_01CzeTFzSETs9wSPH2feDE6u * refactor(editor): convert emoji suggestion from grid to scrollable list with Virtuoso Replace the 8-column grid layout with a vertical list matching the profile suggestion style — each row shows the emoji preview alongside its :shortcode: name. Uses react-virtuoso with fixedItemHeight for lazy rendering and smooth keyboard-driven scrolling through large emoji sets. https://claude.ai/code/session_01CzeTFzSETs9wSPH2feDE6u * fix(editor): set mentionSuggestionChar to ':' for emoji nodes When backspacing over a mention-based node, Tiptap inserts the node's mentionSuggestionChar attribute as undo text. The EmojiMention extension inherits Mention's default of '@', so deleting an emoji left '@' instead of ':'. Fix by explicitly setting mentionSuggestionChar: ':' in the emoji command's attrs for both RichEditor and MentionEditor. https://claude.ai/code/session_01CzeTFzSETs9wSPH2feDE6u * test(editor): add comprehensive test suite for custom TipTap extensions Tests all 8 custom extensions using headless TipTap Editor instances in jsdom environment (TipTap has no official testing package): - EmojiMention: schema, renderText (unicode vs custom), mentionSuggestionChar attribute handling, backspace behavior regression test - BlobAttachmentRichNode/InlineNode: schema (block vs inline), attributes, renderText URL serialization, parseHTML selectors - NostrEventPreviewRichNode/InlineNode: schema, renderText encoding for note/nevent/naddr back to nostr: URIs - SubmitShortcut: Mod-Enter always submits, Enter behavior with enterSubmits flag - FilePasteHandler: media type filtering (image/video/audio), non-media rejection, mixed paste filtering, edge cases (no files, no callback) - NostrPasteHandler: bech32 regex matching (npub/note/nevent/naddr/nprofile), nostr: prefix handling, URL exclusion, node creation (mention vs preview), surrounding text preservation, multiple entities - Serialization: formatBlobSize, serializeRichContent (emoji tag extraction, blob dedup, address refs), serializeInlineContent (mention→nostr: URI, emoji→shortcode, blob→URL, event preview encoding) 90 new tests total. https://claude.ai/code/session_01CzeTFzSETs9wSPH2feDE6u * fix(editor): paste handler and serialization bugs found via adversarial testing NostrPasteHandler fixes: - Punctuation after bech32 now matches (npub1..., npub1...! etc.) Changed trailing lookahead from (?=$|\s) to (?=$|\s|[.,!?;:)\]}>]) - Fixed double-space between entities — unconditional " " after every entity caused doubled spaces. Now only adds trailing space when entity is at the very end of pasted text (for cursor positioning). - Tightened regex character class from [\w] to [a-z0-9] to match actual bech32 charset (rejects uppercase, underscore) - Wrapped dispatch in try/catch to handle block-node-at-inline-position errors gracefully (falls back to default paste) Serialization fix: - serializeRichContent now guards blob collection with `url && sha256` matching the defensive checks already in serializeInlineContent. Previously null sha256 would corrupt the dedup Set and null url would produce invalid BlobAttachment entries. Added 22 new edge case tests: - Paste handler: punctuation boundaries, double-space regression, malformed bech32 fallback, uppercase rejection, error resilience - Serialization: empty editor, null sha256/url blobs, invalid pubkey fallback, missing mention attrs, inline dedup, multi-paragraph https://claude.ai/code/session_01CzeTFzSETs9wSPH2feDE6u * fix(editor): raise suggestion search limits for profiles and emojis Both suggestion dropdowns use Virtuoso for virtualized rendering, so they can handle large result sets without performance issues. The previous limits (20 profiles, 24 emojis) were too restrictive — users with many custom emojis sharing a substring or large contact lists couldn't scroll to find the right match. Raised both limits to 200 to allow thorough browsing while still bounding the result set. https://claude.ai/code/session_01CzeTFzSETs9wSPH2feDE6u * refactor(chat): rework emoji picker to scrollable list with search Replace the fixed 1-row grid (8 emojis) with a scrollable virtualized list matching the editor's EmojiSuggestionList look & feel: - Search box at top with magnifying glass icon - Virtuoso-backed scrollable list (8 visible items, unlimited results) - Each row shows emoji icon + :shortcode: label - Keyboard navigation: arrow keys to select, Enter to confirm - Mouse hover highlights, click selects - Frequently used emojis still shown first when no search query - Narrower dialog (max-w-xs) for a compact picker feel https://claude.ai/code/session_01CzeTFzSETs9wSPH2feDE6u * fix: add address field to EmojiTag in editor types, fix GroupMessageOptions - Add optional `address` field to EmojiTag in editor/types.ts to match NIP-30 changes from main (30030 emoji set address) - Extend GroupMessageOptions with MetaTagOptions to fix type error in GroupMessageBlueprint's setMetaTags call Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(editor): restore address attr, fix serialization, UserName, no-scroll - Restore `address` attribute in shared EmojiMention extension (emoji.ts) that was dropped during refactor — required for NIP-30 emoji set tracking - Extract `address` from emoji nodes in both serializeRichContent and serializeInlineContent so it makes it into published events - Fix MentionEditorProps.onSubmit signature: use EmojiTag[] (not the narrower inline type) so address field flows through to callers - Restore UserName component in ProfileSuggestionList for proper display with Grimoire member badges and supporter flame - Remove scrollbar when all items fit: set overflow:hidden on Virtuoso when items.length <= MAX_VISIBLE (profile list, emoji list, emoji picker dialog) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
cffb981ad1 |
feat: add emoji set address as 4th param in NIP-30 emoji tags
NIP-30 allows an optional 4th tag parameter specifying the source emoji set address (e.g. "30030:pubkey:identifier"). This threads that address through the full emoji pipeline so it appears in posts, replies, reactions, and zap requests. - Add local blueprints.ts with patched NoteBlueprint, NoteReplyBlueprint, GroupMessageBlueprint, and ReactionBlueprint that emit the 4th param; marked TODO to revert once applesauce-common supports it upstream - Add address? to EmojiWithAddress, EmojiTag, EmojiSearchResult, and EmojiTag in create-zap-request - Store address in EmojiSearchService.addEmojiSet (30030:pubkey:identifier) - Thread address through both editor serializers (MentionEditor, RichEditor) and the emoji node TipTap attributes - Fix EmojiPickerDialog to pass address when calling onEmojiSelect and when re-indexing context emojis - Update SendMessageOptions.emojiTags and sendReaction customEmoji param to use EmojiTag throughout the adapter chain Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
0b132624ff | fix: pluralization | ||
|
|
6d3d225b47 | feat: show nip-29 role if specified | ||
|
|
36d321649d | ui: show hashtags as labels, not metadata | ||
|
|
66a422d3f8 | feat: add track and playlist rendering | ||
|
|
d630a72409 |
Add relay list management UI and settings integration (#254)
* 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> |
||
|
|
dd6b30b82e |
Add NIP-85 Trusted Assertions support with renderers (#252)
* 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> |
||
|
|
6c2adc01e0 |
refactor: extract relay auth manager into standalone package (#249)
* refactor: extract relay auth manager into standalone package Decouple NIP-42 relay authentication from Grimoire internals into a generic, framework-agnostic package at packages/relay-auth-manager/. The new package uses dependency injection for pool, signer, and storage (localStorage-like interface), making it reusable in any applesauce-based app. Fixes the bug where auth prompts appeared even when the signer couldn't sign events - now only emits pending challenges when a signer is available. Key changes: - New package with RelayAuthManager class, pure auth state machine, and comprehensive test suite (103 tests) - Grimoire's relay-state-manager now delegates all auth logic to the package, retaining only connection/notice tracking - Auth preferences moved from Dexie to localStorage via pluggable storage - Reactive signer lifecycle: auto-auth re-evaluates when signer appears - Pool relay lifecycle via add$/remove$ observables (no polling) https://claude.ai/code/session_01XqrjeQVtJKw9uC1XAw6rqd * fix: prevent auth prompts when signer is unavailable Three bugs fixed: 1. Race condition in emitState(): states$ was emitted before pendingChallenges$, so relay-state-manager's states$ subscriber would read stale pendingChallenges$.value. Now pendingChallenges$ is emitted first for consistent reads. 2. relay-state-manager only subscribed to states$, missing pendingChallenges$ changes. Now subscribes to both. 3. canAccountSign used constructor.name which is fragile under minification. Now uses account.type !== "readonly" (stable property from applesauce-accounts). Added 3 regression tests verifying pendingChallenges$.value consistency when observed from states$ subscribers. https://claude.ai/code/session_01XqrjeQVtJKw9uC1XAw6rqd * fix: migrate auth preferences from Dexie to localStorage One-time migration preserves existing user preferences: reads from Dexie relayAuthPreferences table, writes to localStorage, injects into the running manager, then clears the Dexie table. Skips if localStorage already has data (idempotent). Removes the DexieAuthStorage adapter — sync/async impedance mismatch made it fragile. localStorage is the right fit for the sync getItem/setItem interface. https://claude.ai/code/session_01XqrjeQVtJKw9uC1XAw6rqd * docs: add README for relay-auth-manager package Covers constructor options, observables, methods, auth lifecycle, preferences, storage interface, state machine, and dependency interfaces. https://claude.ai/code/session_01XqrjeQVtJKw9uC1XAw6rqd * fix: address 3 bugs in relay-auth-manager 1. State machine: authenticated + CHALLENGE_RECEIVED with "never" preference now correctly auto-rejects instead of entering a dead challenge_received state that nobody acts on. 2. Auto-auth error handlers now guard with `status === "authenticating"` before overwriting state, preventing a late-resolving promise from clobbering a valid state transition (e.g. disconnect → "none"). 3. API no longer leaks mutable internal references. getRelayState(), getAllStates(), getAllPreferences(), and states$ emissions all return shallow copies. Previously, consumers holding a reference to a previous emission would see it silently mutate. All three fixes include regression tests (110 tests, up from 103). https://claude.ai/code/session_01XqrjeQVtJKw9uC1XAw6rqd * feat: make relay-auth-manager production-ready - authenticate() now waits for authenticated$ confirmation via firstValueFrom(race(...)) — resolves only when relay confirms, rejects on disconnect. Uses Promise.all to avoid unhandled rejections. - Add retry() for relays stuck in "failed" state. - Add removePreference() to prevent unbounded preference growth. - Add normalizeUrl option for custom URL normalization — preferences and session rejections now consistently normalized via normalizeForKey(). - Wire Grimoire's normalizeRelayURL as the normalizer in relay-auth.ts. - Update README with new methods, normalizeUrl option, retry lifecycle. - 123 package tests passing. https://claude.ai/code/session_01XqrjeQVtJKw9uC1XAw6rqd * chore: prepare relay-auth-manager for npm publishing - Point exports/main/types to dist/ for correct npm resolution - Add tsconfig.build.json for emitting ESM JS + declarations - Add build/clean/prepublishOnly scripts - Add LICENSE, description, keywords, author, files field - Add Vite resolve alias + tsconfig paths for workspace dev (resolves source directly, no pre-build needed for dev) - Fix TypeScript strict errors in test file - Clean npm pack output: dist/, README.md, LICENSE only https://claude.ai/code/session_01XqrjeQVtJKw9uC1XAw6rqd --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
62ce435043 |
feat(kind-1111): show root scope and parent context per NIP-22 (#245)
* feat(kind-1111): show root scope and parent context per NIP-22 The Kind 1111 renderer previously only showed the immediate parent reply, missing the root scope that defines what a comment thread is about. Now shows both the root item (blog post, file, URL, podcast, etc.) and the parent reply (when replying to another comment), giving full threading context. - Add NIP-22 helper library using applesauce pointer helpers (getEventPointerFromETag, getAddressPointerFromATag, getProfilePointerFromPTag) for tag parsing - Support external identifiers (I-tags) with NIP-73 type-specific icons - Unified ScopeRow component for consistent inline display - Only show parent reply line for nested comments (not top-level) https://claude.ai/code/session_01Dxedi6VWdZG8nFpba211oR * fix(kind-1111): unify scope row styling, fix redundant icons and link style - Replace separate NostrEventCard/ExternalScopeCard with children-based ScopeRow so root and reply rows look identical - Remove redundant icons: root events show only KindBadge, replies show only Reply arrow — no double icons - External URLs now use the standard link style (text-accent + dotted underline on hover) matching UrlList, with ExternalLink icon - Children are direct flex items for proper gap spacing https://claude.ai/code/session_01Dxedi6VWdZG8nFpba211oR * fix(kind-1111): match icon sizes and make external URLs clickable - Set KindBadge iconClassname to size-3 so it matches Reply and ExternalLink icons (was size-4 by default) - Use the I-tag value itself as href when it's a URL and no hint is provided — most web I-tags only have the URL in value, not in hint https://claude.ai/code/session_01Dxedi6VWdZG8nFpba211oR * fix(kind-1111): use muted style for external link rows Match the muted-foreground + dotted underline style used by other scope rows instead of accent color. https://claude.ai/code/session_01Dxedi6VWdZG8nFpba211oR --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
53d156ba04 |
Limit message batch in NIP-53 chat adapter (#239)
Apply the same EOSE-gating pattern from NIP-29 adapter to prevent partial renders during initial message load. The loadMessages observable now only emits after EOSE is received from relays. https://claude.ai/code/session_01NFuxEMNBiFW2RA2gHffvMh Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
4be8c6e819 |
Optimize initial chat scroll to avoid slow animation on load (#238)
* fix: disable smooth scroll on initial chat load The chat viewer was using smooth scrolling for all followOutput events, including when first loading the message list. This caused a slow, animated scroll towards the latest messages on initial load. Fix by tracking whether initial render is complete and only enabling smooth scrolling for subsequent followOutput events (new incoming messages). The ref is reset when switching between conversations. https://claude.ai/code/session_0174r1Ddh5e2RuhHf2ZrFiNc * fix: use instant scroll instead of disabling scroll on initial load The previous fix returned `false` which disabled scrolling entirely, causing incorrect scroll position. Changed to return "auto" which scrolls instantly (no animation) while still positioning correctly. https://claude.ai/code/session_0174r1Ddh5e2RuhHf2ZrFiNc * fix: buffer NIP-29 messages until EOSE to prevent partial renders The loadMessages observable was emitting on every event as they streamed in before EOSE, causing multiple re-renders with partial message lists. This resulted in incorrect scroll positions during initial chat load. Now uses combineLatest with a BehaviorSubject to track EOSE state and only emits the message list after the initial batch is fully loaded. New messages after EOSE continue to trigger immediate updates. https://claude.ai/code/session_0174r1Ddh5e2RuhHf2ZrFiNc --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
eca21d3191 |
feat: load media setting (#230)
* feat: add custom media renderer support to RichText Add renderMedia prop to RichText component that allows overriding how images, videos, and audio are rendered across the entire RichText subtree (including Link and Gallery components). The custom renderer receives: - url: the media URL - type: "image" | "video" | "audio" - imeta: optional NIP-92 metadata (dimensions, blurhash, alt, etc.) This enables chat-specific media rendering with compact thumbnails, lightboxes, or any custom presentation. https://claude.ai/code/session_01AeeN5d5EcVLGjZbGueZxaD * feat: add ChatMediaRenderer for compact inline media display Custom media renderer for chat that shows: - Media type icon (image/video/audio) - Filename (extracted from URL or imeta alt) - File size (from imeta if available) - Blossom link button (opens blob viewer for blossom URLs) Usage: <RichText event={message} renderMedia={ChatMediaRenderer} /> https://claude.ai/code/session_01AeeN5d5EcVLGjZbGueZxaD * feat: use ChatMediaRenderer in chat messages - Updated ChatMediaRenderer to use getHashFromURL from blossom-client-sdk - Integrated ChatMediaRenderer into ChatViewer for message content https://claude.ai/code/session_01AeeN5d5EcVLGjZbGueZxaD * style: make ChatMediaRenderer inline with subtle dotted border Use a subtle dotted bottom border instead of background/padding to better integrate with surrounding text flow. https://claude.ai/code/session_01AeeN5d5EcVLGjZbGueZxaD * feat: improve ChatMediaRenderer UX - Full dotted border with rounded corners (was bottom-only) - Increased gap between elements - Images/videos open in MediaDialog zoom viewer on click - Audio still opens in new tab - Better extension extraction for display https://claude.ai/code/session_01AeeN5d5EcVLGjZbGueZxaD * feat: improve blossom integration in ChatMediaRenderer - Use HardDrive icon instead of Flower2 (consistent with rest of app) - Add commandString for Edit functionality in window toolbar - Pass blobUrl with extension to BlossomViewer for correct media preview - Extended BlossomViewer.BlobDetailView to accept blobUrl prop https://claude.ai/code/session_01AeeN5d5EcVLGjZbGueZxaD * refactor: simplify ChatMediaRenderer styling - All icons now size-3 - Use dotted underline only (cleaner inline appearance) - All media types open in new tab on click - Removed MediaDialog dependency https://claude.ai/code/session_01AeeN5d5EcVLGjZbGueZxaD * fix: inherit custom media renderer in nested RichText components Previously, nested RichText components (like those in QuotedEvent/KindRenderer) would always reset the media renderer context to null because the provider used `renderMedia ?? null`. This caused embedded events in chat to use the default media renderer instead of ChatMediaRenderer. Now RichText inherits the parent media renderer when no explicit renderMedia prop is passed, allowing custom renderers to work throughout the entire rich text subtree including quoted events. https://claude.ai/code/session_01AeeN5d5EcVLGjZbGueZxaD * feat: improve ChatMediaRenderer with expandable inline media - More compact representation: "icon truncated-hash blossom" - Click filename to expand and show actual media inline (image/video/audio) - Tooltip shows full filename and file size on hover - Audio support with inline audio player when expanded - Fixed BlossomViewer to detect media type from URL extension when mimeType is not available from blob descriptor https://claude.ai/code/session_01AeeN5d5EcVLGjZbGueZxaD * feat: improve ChatMediaRenderer with better UX - Click to expand shows MediaEmbed (not collapsible once expanded) - Tooltip only shows when imeta is present, displays: alt, mime type, dimensions, size, duration - More compact representation: [icon] truncated-hash [blossom] - Pass mediaType to BlossomViewer for correct preview rendering - BlossomViewer now accepts mediaType hint for preview type detection https://claude.ai/code/session_01AeeN5d5EcVLGjZbGueZxaD * fix: blossom blob command now parses --type flag - Added mediaType and blobUrl fields to BlossomCommandResult - Blob subcommand now parses --type image|video|audio flag - Fixed server URL overflow in blob detail view with truncation https://claude.ai/code/session_01AeeN5d5EcVLGjZbGueZxaD * refactor: add global loadMedia setting with CompactMediaRenderer - Add `loadMedia` setting to Appearance section (enabled by default) - Create CompactMediaRenderer in src/components/nostr/ (renamed from ChatMediaRenderer) - Link.tsx and Gallery.tsx now check the setting and use CompactMediaRenderer when disabled - Tooltip format improved: "Field <value>" with field name and value side by side - Shows: Hash, Size, Dimensions, Type, Duration, Alt - Remove renderMedia prop from ChatViewer (now automatic based on setting) - Delete old ChatMediaRenderer.tsx This makes media rendering a site-wide setting rather than chat-specific. https://claude.ai/code/session_01AeeN5d5EcVLGjZbGueZxaD --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
a75dd2b3fb |
Remove compact mode settings and simplify settings architecture (#232)
* refactor: remove unused compactModeKinds from app state The compactModeKinds feature was partially implemented but never used: - Settings dialog allowed configuration but the value was never consumed - REQ viewer uses --view flag for compact mode, not this state value Removed: - compactModeKinds from GrimoireState type and initial state - setCompactModeKinds logic function and state hook callback - Compact Events section from SettingsDialog (now shows placeholder) - compactModeKinds from AppearanceSettings in settings service - Migration now strips compactModeKinds if present instead of adding it https://claude.ai/code/session_01DiWUxiS5BAzU9mrKvCUuMW * chore: delete unused SettingsDialog component The SettingsDialog was imported but never used - the Settings menu item opens a window via addWindow("settings", ...) instead. https://claude.ai/code/session_01DiWUxiS5BAzU9mrKvCUuMW * refactor: simplify settings service to only used settings Removed unused settings that were defined but never consumed: - RelaySettings (fallbackRelays, discoveryRelays, outbox*, etc.) - PrivacySettings (shareReadReceipts, blurWalletBalances, etc.) - DatabaseSettings (maxEventsCached, autoCleanupDays, etc.) - NotificationSettings (enabled, notifyOnMention, etc.) - DeveloperSettings (debugMode, showEventIds, logLevel, etc.) - Most of AppearanceSettings (theme, fontSizeMultiplier, etc.) - Most of PostSettings (defaultRelayMode, customPostRelays) Only kept settings that are actually used: - post.includeClientTag - appearance.showClientTags Also simplified useSettings hook to match. https://claude.ai/code/session_01DiWUxiS5BAzU9mrKvCUuMW --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
8f2f055566 |
feat: repository tree visualization (#31)
* docs: add plan for repository tree visualization feature Comprehensive plan covering: - git-natural-api library analysis and API documentation - useGitTree/useGitBlob hooks for fetching git data - FileTreeView component using Radix Collapsible - Shiki migration with on-demand language loading - Multi-server fallback for redundant clone URLs - Dexie caching for offline access * docs: add comprehensive Shiki migration plan Detailed plan for migrating from Prism.js to Shiki with lazy loading: - Analysis of all 8 components using SyntaxHighlight - Shiki service with singleton highlighter and on-demand language loading - Custom Grimoire dark theme matching current Prism styles - Language alias mapping for 50+ extensions - React hook for async highlighting with loading states - CSS theme preserving minimalistic dark aesthetics - Migration steps with rollback plan * feat: migrate syntax highlighting from Prism.js to Shiki Replace Prism.js with Shiki for syntax highlighting with several key improvements: - Lazy loading: Languages loaded on-demand via dynamic imports instead of bundling all upfront - Broader language support: 200+ TextMate grammars vs 11 statically imported - Singleton highlighter: Core languages (JS, TS, JSON, diff, bash) preloaded, others loaded on first use New files: - src/lib/shiki.ts: Shiki service with highlightCode(), normalizeLanguage(), language aliases - src/hooks/useHighlightedCode.ts: React hook for async highlighting with loading states - src/styles/shiki-theme.css: Grimoire dark theme matching previous minimalistic style Updated components: - SyntaxHighlight: Now uses Shiki with graceful loading/error states - CodeSnippetRenderer/DetailRenderer: Simplified, removed manual language mapping - MarkdownContent: Removed type casts, any language now supported Removed: - prismjs and @types/prismjs dependencies - src/styles/prism-theme.css * feat: add repository file tree visualization Add file tree explorer to the Repository detail renderer (kind 30617) using @fiatjaf/git-natural-api for fetching git trees via HTTP. New files: - src/lib/git-types.ts: TypeScript types for DirectoryTree, SelectedFile, etc. - src/hooks/useGitTree.ts: Hook to fetch git repository tree from clone URLs - Tries multiple clone URLs in sequence - Uses getDirectoryTreeAt with filter capability when available - Falls back to shallowCloneRepositoryAt otherwise - src/hooks/useGitBlob.ts: Hook to fetch individual file content by hash - Detects binary files - Returns both raw Uint8Array and decoded text - src/components/ui/FileTreeView.tsx: Recursive tree view component - Collapsible directories with chevron icons - File icons based on extension (code, json, text, image, etc.) - Alphabetical sorting with directories first - src/components/nostr/kinds/RepositoryFilesSection.tsx: Main integration - Side-by-side tree and file preview layout - Syntax-highlighted file content using existing SyntaxHighlight - Binary file detection with appropriate UI - Loading/error states Modified: - RepositoryDetailRenderer.tsx: Added RepositoryFilesSection below relays Dependencies: - Added @fiatjaf/git-natural-api from JSR * fix: improve repository tree visualization UX - Collapse directories by default in file tree - Hide files section on tree loading error - Add code-like skeleton loader with file header - Fix syntax highlight size jump between loading/loaded states - Replace purple accent with grayscale theme - Preload Rust and Markdown languages for reliable highlighting * refactor: improve git tree and syntax highlighting - Remove shallow clone fallback from useGitTree, only use no-blobs fetch Servers without filter capability are skipped instead of downloading blobs - Add light theme support for Shiki syntax highlighting Theme is automatically selected based on current color scheme * fix: improve dark theme contrast for syntax highlighting * fix: address code review issues - useGitTree: use useStableArray for cloneUrls to fix dependency tracking - useGitTree/useGitBlob: add isMounted checks to prevent state updates after unmount - RepositoryFilesSection: remove unnecessary useMemo for language - FileTreeView: use path instead of hash for React keys - shiki: track failed languages to avoid repeated console warnings * fix: improve dark theme contrast for syntax highlighting - Add CSS variables for syntax highlighting instead of hardcoded colors - Add --syntax-constant and --syntax-tag variables to light and dark themes - Use high contrast colors for dark mode (bright green strings, purple keywords) - Simplify Shiki transformer to output CSS classes instead of inline styles - Remove unused parameters from transformer callback * fix: restore syntax highlighting colors Revert the CSS class-based approach which was failing to classify tokens. Instead, keep Shiki's inline styles from the theme and only remove backgrounds to let CSS handle those. The theme colors now provide syntax highlighting directly. * feat: add copy button and CSS variable-based syntax highlighting - Add copy button next to file name in file viewer header (icon-only) - Use Shiki's createCssVariablesTheme for proper theme integration - Map Shiki CSS variables to our theme system variables - Syntax highlighting now works correctly across all themes (light/dark) * refactor: create IconCopyButton component and use CopyCheck consistently - Add IconCopyButton component for reusable icon-only copy buttons - Refactor RepositoryFilesSection to use IconCopyButton - Replace Check with CopyCheck in ChatMessageContextMenu - Replace Check with CopyCheck in BaseEventRenderer - Use text-success instead of text-green-500 for consistency * fix: add HTML, CSS, TOML to core languages and expand token mappings - Add html, css, toml to CORE_LANGUAGES for eager loading - Add variableDefaults to cssVarsTheme for proper initialization - Expand shiki-theme.css with more token type mappings: - HTML/XML: tag, attribute, attr-value - CSS: selector, property - Additional: variable, operator, number, boolean, regex, etc. * fix: improve diff line spacing with flex layout - Use flex-col with gap-0 on code element for tight line packing - Reduce line-height from 1.5 to 1.4 for tighter spacing - Add .line display:block with min-height for consistent sizing - Simplify diff background styling (remove negative margin hack) * fix: improve code block line spacing and wrap long lines - Increase line-height from 1.4 to 1.5 for better readability - Use pre-wrap instead of pre to allow long line wrapping - Add overflow-wrap: break-word to break long URLs/strings * chore: remove planning docs * chore: update @fiatjaf/git-natural-api to 0.2.3 * fix: make code blocks horizontally scrollable with full-width diff backgrounds - Use white-space: pre for horizontal scrolling instead of wrapping - Add width: fit-content and min-width: 100% to code element - Ensure diff line backgrounds extend full width when scrolling --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
0b3c657705 | feat(req-viewer): added today on since/until, added window-title (#221) | ||
|
|
fdc7b1499f |
fix: build proper q-tag with relay hint and author pubkey for replies (#224)
* fix: build proper q-tag with relay hint and author pubkey for replies When sending replies in NIP-29 and NIP-C7 adapters, now build the full q-tag format per NIP-C7 spec: ["q", eventId, relayUrl, pubkey] Previously only the event ID was included, making it harder for clients to fetch the referenced event. Now: - NIP-29: includes group relay URL and author pubkey - NIP-C7: includes seen relay hint and author pubkey https://claude.ai/code/session_01Jy51Ayk57fzaFuuFFm1j1K * chore: remove unused NIP-C7 adapter The NIP-C7 adapter was already disabled/commented out everywhere. Removing the file to reduce dead code. https://claude.ai/code/session_01Jy51Ayk57fzaFuuFFm1j1K * chore: remove NIP-C7 references from docs and code - Remove nip-c7 from ChatProtocol type - Remove commented NIP-C7 adapter imports and switch cases - Update comments to reference NIP-29 instead of NIP-C7 - Update kind 9 renderer docs to reference NIP-29 - Clean up chat-parser docs and error messages https://claude.ai/code/session_01Jy51Ayk57fzaFuuFFm1j1K --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
3282581636 |
Add follow mode (-f) to REQ command (#220)
* feat: add -f (follow) option to req command Add tail -f style auto-refresh behavior to the req command. When enabled, new events are automatically displayed instead of being buffered behind a "X new events" button. |
||
|
|
a28ffc1ec3 |
fix: filter invalid relay URLs from event tags (#217)
* fix: filter invalid relay URLs from event tags Add validation to prevent invalid URLs from being used as relay hints. The issue occurred when "r" tags containing non-relay URLs (like https://(strangelove@basspistol.org/) were being extracted and used as relay connection targets. Changes: - Add isValidRelayURL() helper to validate relay URLs (must have ws:// or wss:// protocol and valid URL structure) - Update extractRelayContext() in loaders.ts to filter r-tags, e-tag relay hints, and a-tag relay hints using the new validator - Add comprehensive tests for isValidRelayURL() https://claude.ai/code/session_01Ca2fKD2r4wHKRD8rcRohj9 * refactor: use applesauce isSafeRelayURL for relay URL validation Refactor relay URL validation to use applesauce's isSafeRelayURL helper which provides a fast regex-based check for valid websocket URLs. Changes: - Update isValidRelayURL in relay-url.ts to use isSafeRelayURL as fast path, with URL constructor fallback for IP addresses - Re-export isSafeRelayURL from relay-url.ts for convenience - Update loaders.ts to use isSafeRelayURL directly from applesauce - Add relay URL validation to: - nostr-utils.ts: getEventPointerFromQTag (q-tag relay hints) - zapstore-helpers.ts: getAppReferences (a-tag relay hints) - nip89-helpers.ts: getHandlerReferences (a-tag relay hints) - PublicChatsRenderer.tsx: extractGroups (group relay URLs) This ensures consistent validation across all relay URL extraction points using applesauce's battle-tested validation. https://claude.ai/code/session_01Ca2fKD2r4wHKRD8rcRohj9 * chore: remove unused isSafeRelayURL re-export The re-export was added but all consumers import directly from applesauce-core/helpers/relays instead. https://claude.ai/code/session_01Ca2fKD2r4wHKRD8rcRohj9 --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
3f3ebcf5f6 |
Fix duplicate client tag in spell encoding (#215)
The client tag was being added twice to spells: 1. Unconditionally in encodeSpell() in spell-conversion.ts 2. Conditionally in publish-spell.ts based on user settings Removed the unconditional addition in encodeSpell() so the client tag is only added once by publish-spell.ts when the user setting is enabled. Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
e008d76021 |
feat: NIP-34 status events (#209)
* 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> |
||
|
|
9b36120dfe |
Add NIP-56 Report (Kind 1984) renderer and helpers (#210)
* Add NIP-56 Report renderer (kind 1984) - Add nip56-helpers.ts with report parsing and type definitions - Add ReportRenderer for displaying report events in feeds - Support profile, event, and blob report targets - Display report type with color-coded badges and icons - Show embedded reported event when available https://claude.ai/code/session_012ux81GyM8iZ1GLnKHC7esJ * refactor(nip56): apply applesauce caching and neutral styling - Use getOrComputeCachedValue for report parsing (applesauce pattern) - Rename parseReport to getReportInfo for consistency - Use muted/neutral colors for all report type icons and badges - Use QuotedEvent component for embedding reported events - Remove unnecessary useMemo (helper caches internally) https://claude.ai/code/session_012ux81GyM8iZ1GLnKHC7esJ * refactor(nip56): use collapsed quote and cleaner copy - Use "Reported <username> for <reason>" format - Remove redundant "Event by:" line for event reports - Use depth=2 for QuotedEvent to show collapsed by default - Content may be disturbing, user can expand on demand https://claude.ai/code/session_012ux81GyM8iZ1GLnKHC7esJ * feat(nip56): hide preview and clickable header - Add hidePreview prop to QuotedEvent for sensitive content - Hide text preview when collapsed, show "Click to reveal content" - Make report header clickable to open report detail - UserName stops propagation so clicking username opens profile https://claude.ai/code/session_012ux81GyM8iZ1GLnKHC7esJ * style(nip56): use dotted underline hover, reduce spacing - Remove background highlight on hover - Use underline dotted with cursor crosshair (consistent with app) - Reduce gap between header and quoted event https://claude.ai/code/session_012ux81GyM8iZ1GLnKHC7esJ * refactor(nip56): use RichText for comment and applesauce helpers - Render report comments using RichText like kind 1 notes - Use getTagValue/getTagValues helpers instead of direct tag access - Add explanatory comments where direct tag access is still required https://claude.ai/code/session_012ux81GyM8iZ1GLnKHC7esJ --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
7838b0ab98 |
Add NIP-88 Poll support with renderers and helpers (#207)
* feat(nip88): add poll event renderers Implement NIP-88 poll events support with: - PollRenderer: Feed view for kind 1068 polls showing question, options, type - PollDetailRenderer: Detail view with live vote counts and percentages - PollResponseRenderer: Feed view for kind 1018 showing voted options - nip88-helpers: Utilities for parsing poll data and counting votes * fix(nip88): use RelayLink component and differentiate poll type icons - Use ListCheck icon for single choice, ListChecks for multi choice - Replace relay text spans with clickable RelayLink components - Shorten "Vote on these relays" to "Relays" * refactor(nip88): production readiness improvements - Add symbol-based caching to all helper functions using getOrComputeCachedValue - Use QuotedEvent component for embedded polls in PollResponseRenderer - Simplify PollResponseRenderer by leveraging QuotedEvent's loading states - Add clear documentation about what can/cannot be cached * fix(nip88): restore option label resolution in PollResponseRenderer --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
32d584090f | fix: unused import | ||
|
|
f551604866 |
Add profile fallback for pubkey-based NIP-29 group IDs (#203)
* feat: add profile metadata fallback for NIP-29 groups When a NIP-29 group ID is a valid pubkey and the relay doesn't support NIP-29 (no kind 39000 metadata), fall back to using the pubkey's profile metadata (kind 0) for group name, description, and icon. This allows users to create simple group chats using their pubkey as the group identifier on relays that don't have full NIP-29 support. Changes: - Add isValidPubkey() helper to validate 64-char hex strings - Modify resolveConversation() to fetch profile when metadata is missing - Add comprehensive tests for pubkey validation and parsing - Prefer NIP-29 metadata over profile fallback when available Tests: 20 NIP-29 adapter tests passing, 1037 total tests passing Build: Successful * refactor: extract group metadata resolution into shared helper Refactored profile fallback logic into a reusable helper that both NIP-29 adapter and GroupListViewer can use. This fixes the issue where GroupListViewer wasn't benefiting from the profile metadata fallback. Changes: - Created shared `group-metadata-helpers.ts` with: - `isValidPubkey()` - validates 64-character hex strings - `resolveGroupMetadata()` - unified metadata resolution with fallback - `ResolvedGroupMetadata` type with source tracking - Updated NIP-29 adapter to use shared helper (DRY) - Updated GroupListViewer to resolve metadata with profile fallback - Added resolvedMetadata to GroupInfo interface - Added useEffect to resolve metadata for all groups - Updated GroupListItem to use resolved metadata Priority: 1. NIP-29 metadata (kind 39000) if available 2. Profile metadata (kind 0) if groupId is a valid pubkey 3. Fallback to groupId as name Tests: All 1037 tests passing Build: Successful --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
7cf75c648c |
feat(nip-66): add relay discovery and monitor announcement renderers (#172)
* feat(nip-66): add relay discovery and monitor announcement renderers Implements NIP-66 support to display relay health metrics and monitoring information. Users can now view relay performance data (RTT, network type, supported NIPs) and monitor announcements to make informed decisions about relay selection and reliability. Includes 58 comprehensive tests for all helper functions and event parsing. * refactor(nip-66): improve UI with Label, NIPBadge, and clickable titles Enhance NIP-66 renderers with better UI components: - Use NIPBadge component for clickable NIP numbers - Replace section headers with Label component for consistency - Add ClickableEventTitle to monitor announcements - Improve requirement icons with CheckCircle/XCircle for clarity - Add proper icons throughout for better visual hierarchy * refactor(nip-66): use Hammer icon for PoW requirements Replace Zap (lightning bolt) icon with Hammer icon for proof-of-work indicators to better represent the mining/work metaphor. Updates both feed and detail renderers for relay discovery events. * refactor(nip-66): improve feed UI with clickable titles and simplified layout - Add ClickableEventTitle to relay discovery feed items for opening detail view - Remove "Monitoring" label from relay monitor feed items for cleaner layout - Remove unused imports (RelayLink, Label, Activity) from feed renderers - Maintain existing Label and NIPBadge usage in detail renderers * refactor(nip-66): add Label component for check types in monitor feed Add "Check Types" label to relay monitor feed renderer for better visual hierarchy and consistency with detail renderer. * refactor(nip-66): remove Check Types label from monitor feed Remove label title to simplify monitor feed layout - check type badges are displayed directly without a header for cleaner appearance. * refactor(nip-66): use Label component for individual check types in monitor feed Replace Badge components with Label components for check types to match the design system and provide better visual consistency. * refactor(nip-66): rename components to human-readable names Rename NIP-66 component exports to match established naming convention: - Kind10166Renderer → MonitorAnnouncementRenderer - Kind10166DetailRenderer → MonitorAnnouncementDetailRenderer - Kind30166Renderer → RelayDiscoveryRenderer - Kind30166DetailRenderer → RelayDiscoveryDetailRenderer This follows the pattern used elsewhere (e.g., LiveActivityRenderer vs Kind30311Renderer) to make code more readable without memorizing kind numbers. * refactor(nip-66): extract relay kinds display into reusable component Create RelayKindsDisplay component to show accepted/rejected kinds in a consistent format across detail views. Used in RelayDiscoveryDetailRenderer to reduce code duplication and improve maintainability. * refactor(nip-66): extract supported NIPs display into reusable component Create RelaySupportedNips component to show relay-supported NIPs in a consistent format. Used in RelayDiscoveryDetailRenderer to reduce code duplication and improve maintainability. * refactor(nip-66): add icon to supported NIPs component for consistent styling Add FileText icon to RelaySupportedNips Label to match the visual hierarchy pattern used in other relay detail sections (Performance Metrics, Characteristics, Requirements, etc.). * refactor(nip-66): use nicer NIP rendering from RelayViewer in shared component Update RelaySupportedNips component to match RelayViewer's nicer styling: - Show NIP names alongside numbers (showName=true) for better readability - Use gap-2 for better spacing - Use h3 title styling instead of Label with icon - Make component reusable with optional title customization - Use in both RelayViewer and RelayDiscoveryDetailRenderer --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
8e4442ae34 |
feat: improve embedded event resolution with relay hints (#200)
* fix: parse NIP-29 q-tags with relay hints for embedded event resolution The issue was that q-tag relay hints were being discarded when parsing chat messages. When a message quoted another event, only the event ID was extracted from the q-tag, ignoring the relay URL hint at tag[2]. Changes: - Add getEventPointerFromQTag and getQuotePointer helpers to nostr-utils - Update Message.replyTo type from string to EventPointer | AddressPointer - Update all chat adapters (NIP-29, NIP-10, NIP-53, NIP-C7) to parse full pointers with relay hints - Update loadReplyMessage signature to accept EventPointer with relays - Update ReplyPreview to pass full pointers to event loading - Update ChatView and ChatViewer to use new pointer-based q-tag parsing This ensures embedded events (quotes/replies) are fetched using the relay hint from the q-tag when the event is not found in the local store. * fix: pass full EventPointer with relay hints through embed chain The previous commit fixed q-tag parsing in chat adapters, but the RichText/Mention chain was also discarding relay hints from nevent. EventEmbed was only extracting `pointer.id` and passing it as a string to QuotedEvent, losing the relay hints. Same issue in EmbeddedEvent. Changes: - Update QuotedEvent props: eventPointer instead of eventId string - Update EmbeddedEvent props: eventPointer instead of eventId string - Update EventEmbed to pass full pointer to QuotedEvent - Update MarkdownContent to pass full pointers for note/nevent - Update EventRefList to pass full pointer instead of just ID - Update HighlightDetailRenderer to pass full eventPointer - Update RepostRenderer to extract e-tag as EventPointer with hints Now nevent mentions in content like nostr:nevent1... correctly use the relay hints from the bech32 encoding when fetching the event. * fix: use EventPointer for repost replyTo in NIP-10 adapter Apply the same relay hints fix to the repostToMessage function that was added in a recent main commit. * refactor: use applesauce helpers for relay operations in chat adapters - Use mergeRelaySets for relay deduplication and normalization - Use getOutboxes helper instead of manual relay list parsing - Simplify getThreadRelays in NIP-10 adapter - Simplify loadReplyMessage in NIP-10, NIP-29, and NIP-53 adapters * refactor: use mergeRelaySets in relay-selection service - Simplify selectRelaysForInteraction using mergeRelaySets for priority-based relay combination - Use mergeRelaySets for unique relay extraction in selectRelaysForFilter - Reduces manual Set operations and gains automatic normalization --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
7c6014378b |
Add ID filtering to REQ command (#202)
* feat: add -i/--id flag for direct event ID filtering in REQ command Add a new -i/--id flag for direct event lookup via filter.ids, and clarify -e flag behavior for tag-based filtering (#e/#a tags). Changes: - Add -i/--id flag: accepts note1, nevent, or hex event IDs for direct lookup - Clarify -e flag: now always routes to #e/#a tags (including nevent) - Update man page with new flag documentation and examples - Add comprehensive tests for the new -i/--id flag This aligns with nak's behavior where -i is for direct ID filtering and -e is for tag-based event references. * feat: support raw coordinate format (kind:pubkey:d) in -e flag The -e flag now accepts raw a-tag coordinates like `30023:pubkey:article` in addition to naddr bech32 encoding. Both route to #a tag filtering. Examples: - req -e 30023:abc123...:my-article # Raw coordinate - req -e naddr1... # Bech32 encoded (same effect) * feat: add event ID previews in REQ viewer query dropdown When using -i/--id flag for direct event lookup, the query dropdown now shows clickable event ID previews (truncated hex). Click any ID to open the event detail view. Works in both accordion (complex queries) and simple card views. --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
93ffd365f5 |
feat: add repost system messages with grouping in chat (#194)
* feat: add repost system messages with grouping in chat - Add kind 6 and 16 (reposts) to NIP-10 adapter filters - Convert simple reposts (no content) to system messages - Implement consecutive system message grouping - Groups consecutive system messages with same action - Format: "alice, bob and 3 others reposted" - Works for all system messages (reposts, join, leave, etc.) - Update scroll-to-message to handle grouped messages - Ignore reposts with content (quotes) to keep it simple System message grouping UX: - 1 person: "alice reposted" - 2 people: "alice and bob reposted" - 3 people: "alice, bob and charlie reposted" - 4+ people: "alice, bob and 3 others reposted" * feat: show all reposts regardless of content Remove content filtering for reposts - now all kind 6 and 16 events are shown as system messages. Quote reposts (with content) will just display as 'reposted' without showing the quote text, keeping the chat interface clean and consistent. * refactor: extract and test system message grouping logic Extract grouping logic to separate module: - Created src/lib/chat/group-system-messages.ts with: - groupSystemMessages(): Groups consecutive system messages - isGroupedSystemMessage(): Type guard with validation - GroupedSystemMessage interface - Updated ChatViewer to import from new module - Improved type guard with additional validation: - Check array lengths match (authors.length === messageIds.length) - Ensure arrays are not empty - Validate all field types Added comprehensive test coverage (26 tests): - Basic grouping behavior - Edge cases (empty arrays, single messages) - Mixed message types (user, zap, system) - Timestamp preservation - Large group handling (100+ items) - Type guard validation All tests pass (1006 total), build succeeds. Production ready. --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
53f29b9b63 |
fix: improve tooltip contrast across all themes (#192)
* fix: improve tooltip contrast across all themes CRITICAL FIX: Plan 9 theme had a catastrophic contrast failure where tooltip text (HSL 60, 100%, 96%) was nearly identical to the background (HSL 60, 100%, 94%), creating a ~1:1 contrast ratio that made tooltips completely unreadable. Changes: - Added dedicated `tooltip` and `tooltipForeground` colors to theme system - Updated all three built-in themes (dark, light, plan9) with proper colors - Modified tooltip component to use new colors instead of primary/primary-foreground - Added rounded corners and shadow to tooltips for better visual separation Theme-specific tooltip colors: - Dark theme: Dark blue-gray background (#217.2 32.6% 17.5%) with light text - Light theme: Very dark background (#222.2 47.4% 11.2%) with light text - Plan 9 theme: Dark blue (#220 50% 25%) with pale yellow text All tooltip colors now meet WCAG AA standards (4.5:1+ contrast ratio) and are clearly visible against their respective theme backgrounds. Files modified: - src/lib/themes/types.ts: Added tooltip color types - src/lib/themes/builtin/*.ts: Added tooltip colors to all themes - src/lib/themes/apply.ts: Apply tooltip CSS variables on theme change - src/index.css: Added tooltip CSS variables for light/dark themes - tailwind.config.js: Exposed tooltip colors as Tailwind utilities - src/components/ui/tooltip.tsx: Use new tooltip colors with improved styling * fix: increase dark mode tooltip lightness for better visibility Dark mode tooltips were too dark (17.5% lightness) against the very dark background (4.9% lightness), making the tooltip box hard to distinguish. Changes: - Increased dark mode tooltip lightness from 17.5% to 30% - This provides ~6:1 contrast ratio between tooltip and background - Tooltip box now clearly visible against dark background - Text contrast remains excellent (light text on medium-dark background) The tooltip now stands out properly while maintaining high text readability. * fix: improve ChatViewer group tooltip contrast in dark mode The group description tooltip in ChatViewer had poor contrast due to using `text-primary-foreground` color classes that conflicted with the new tooltip background colors. Issues fixed: 1. Description text using `text-primary-foreground/90` - replaced with `opacity-90` 2. Protocol button using `bg-primary-foreground/20` with `text-primary-foreground` (light-on-light, ~1.5:1 contrast) - now uses `bg-tooltip-foreground/20` 3. All other text using `text-primary-foreground` variants - replaced with `opacity-*` This allows the text to inherit the correct `text-tooltip-foreground` color from the TooltipContent component, ensuring proper contrast against the `bg-tooltip` background in all themes. Files modified: - src/components/ChatViewer.tsx: Updated tooltip text color classes --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
adf8a62954 |
feat: add hashtag support to rich editor (#185)
* feat: add automatic hashtag extraction and t tags in POST command
Extract hashtags from post content and automatically add them as t tags to published events.
Changes:
- Add hashtag extraction logic to RichEditor.serializeContent() using Unicode-aware regex
- Update SerializedContent interface to include hashtags field
- Update RichEditor props and callbacks to pass hashtags through the pipeline
- Add t tags for each hashtag in PostViewer.handlePublish()
Hashtags are deduplicated and stored in lowercase (following Nostr convention).
Example: #bitcoin #nostr #Bitcoin → ["t", "bitcoin"], ["t", "nostr"]
* refactor: use NoteBlueprint for automatic hashtag/mention extraction
Replace manual hashtag and mention extraction with applesauce's NoteBlueprint,
which automatically extracts hashtags, mentions, and event quotes from text content.
Changes:
- Simplify SerializedContent interface by removing manually extracted fields
- Remove hashtag extraction regex and mention/eventRef tracking from editors
- Replace manual event building with factory.create(NoteBlueprint, ...)
- Use q tags for event quotes (NIP-18) instead of e tags
Benefits:
- ~70 lines of code removed
- Leverage battle-tested applesauce extraction logic
- Automatic benefits from future applesauce improvements
- Correct semantic tags (q for quotes, p for mentions, t for hashtags)
What still works:
- Custom emoji tags (NIP-30)
- Blob attachments/imeta tags (NIP-92)
- Address references (naddr - not yet in applesauce)
- Client tag
All tests pass (980/980).
* refactor: use NoteReplyBlueprint in NIP-10 adapter
Replace manual NIP-10 tag building with NoteReplyBlueprint, which automatically
handles root/reply markers, p-tag copying, and all the threading logic.
Changes:
- Simplify sendMessage from ~95 lines to ~40 lines
- Remove manual e-tag building with root/reply markers
- Remove manual p-tag deduplication logic
- Use factory.create(NoteReplyBlueprint, parentEvent, content, options)
- Automatically get hashtags, mentions, and event quotes via setShortTextContent
Benefits:
- ~55 lines of complex threading logic removed
- Leverage battle-tested applesauce NIP-10 implementation
- Automatic root detection from parent's existing tags
- Cleaner, more maintainable code
All tests pass (980/980).
* refactor: use GroupMessageBlueprint and ReactionBlueprint in chat adapters
Replace manual event building with applesauce blueprints in all chat adapters.
Changes:
- NIP-29: Use GroupMessageBlueprint for kind 9 messages
* Auto-handles h-tag, hashtags, mentions, emojis
* Manually add q-tag for replies (NIP-29 specific)
* ~15 lines removed
- All adapters (NIP-10, NIP-29, NIP-53, NIP-C7): Use ReactionBlueprint for kind 7 reactions
* Auto-handles e-tag, k-tag, p-tag, custom emoji support
* Protocol-specific tags (h-tag, a-tag) added manually
* ~60 lines removed across 4 adapters
Benefits:
- ~75 lines of code removed total
- Leverage battle-tested applesauce blueprints
- Automatic hashtag, mention, and quote extraction
- Cleaner, more maintainable code
All tests pass (980/980).
* fix: add required previous field to GroupMessageBlueprint options
GroupMessageBlueprintOptions requires a 'previous' field for message threading.
Added empty array for now since we don't support threading yet.
* docs: add comprehensive blueprint documentation to applesauce skills
Added detailed documentation for:
- NoteBlueprint (automatic hashtag/mention/quote extraction)
- NoteReplyBlueprint (NIP-10 threading)
- ReactionBlueprint (kind 7 reactions)
- GroupMessageBlueprint (NIP-29 groups)
- DeleteBlueprint (NIP-09 deletion)
- EventFactory usage patterns
- Before/after examples showing code reduction
- Best practices for using blueprints
This documents the refactoring work done throughout the codebase.
* fix: use single newline separator in TipTap getText() calls
TipTap's getText() uses double newlines (\n\n) by default to separate
block nodes like paragraphs, which was causing extra blank lines in
posted content.
Changed to getText({ blockSeparator: '\n' }) in both RichEditor and
MentionEditor to use single newlines between paragraphs.
---------
Co-authored-by: Claude <noreply@anthropic.com>
|
||
|
|
d18cdc31d5 |
feat: client tag (#183)
* feat: show client tag in event header Display "via <client>" after the timestamp in BaseEventContainer when the event has a client tag. Uses compact 10px font with reduced opacity to minimize visual noise. * feat: make client tag link to NIP-89 app definition When the client tag has a third element with a valid 31990 address (NIP-89 app handler), make the client name clickable to open the app definition event. * fix: use parseAddressPointer from nip89-helpers instead of non-existent parseCoordinate * feat: add NIP-89 app address to client tag - Add GRIMOIRE_APP_ADDRESS and GRIMOIRE_CLIENT_TAG constants - Update all client tag usages to include the 31990 app definition address - Update tests to verify the app address is included - Update spell.ts documentation --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
13fec0345f |
feat: zap goals nip-75 (#174)
* feat: add NIP-75 Zap Goal rendering Add feed and detail view rendering for kind 9041 (Zap Goals): - GoalRenderer: Shows clickable title, description, and progress bar with target/raised amounts - GoalDetailRenderer: Adds sorted contributor breakdown with individual contribution totals - nip75-helpers: Helper functions for extracting goal metadata (amount, relays, deadline, beneficiaries) Both views fetch and tally zaps from the goal's specified relays. * fix: improve NIP-75 goal rendering - Remove icons from goal renderers - Remove "sats" suffixes from amounts - Use muted text instead of destructive color for closed goals - Content is the title, summary tag is the description - Only show description if summary tag exists * feat: add zap button to goal renderers Show a 'Zap this Goal' button in both feed and detail views when the goal is still open (not past its closed_at deadline). * style: unify progress indicator styles - Update user menu and welcome page progress indicators to match goal renderer style: bar on top, progress/total below with percentage - Remove "sats" suffix from progress displays - Make goal zap button primary variant and full-width * fix: polish goal renderers for release - Remove limit parameter from useTimeline (use whatever relays send) - Add more spacing between "Support Grimoire" header and progress bar * refactor: extract useGoalProgress hook for NIP-75 goals - Create useGoalProgress hook with shared goal logic - Handle relay selection: goal relays → user inbox → aggregators - Calculate progress, contributors, and all metadata in one place - Simplify both GoalRenderer and GoalDetailRenderer --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
c52e783fce |
fix(nip-29): fetch admin and member lists in parallel and correctly tag admins (#169)
* fix(nip-29): fetch admin and member lists in parallel and correctly tag admins This commit improves NIP-29 group member fetching with two key changes: 1. **Parallel fetching**: Admins (kind 39001) and members (kind 39002) are now fetched in parallel using separate subscriptions, improving performance 2. **Correct admin role tagging**: Users in kind 39001 events now correctly default to "admin" role instead of "member" when no explicit role tag is provided, as per NIP-29 spec Changes: - Split participantsFilter into separate adminsFilter and membersFilter - Use Promise.all to fetch both kinds in parallel - Updated normalizeRole helper to accept a defaultRole parameter - Process kind 39001 with "admin" default, kind 39002 with "member" default - Added clearer logging for admin/member event counts Related: src/lib/chat/adapters/nip-29-adapter.ts:192-320 * refactor(nip-29): simplify parallel fetch using pool.request Use pool.request() with both filters in a single call instead of manual subscription management. This is cleaner and more idiomatic: - Auto-closes on EOSE (no manual unsubscribe needed) - Fetches both kinds (39001 and 39002) in parallel with one request - Reduces code complexity significantly * fix(nip-29): use limit 1 for replaceable participant events Kinds 39001 and 39002 are replaceable events with d-tag, so there should only be one valid event of each kind per group. Changed limit from 5 to 1. * fix(chat): change "Sign in to send messages" to "Sign in to post" Simplified the login prompt text in chat interface for clarity. --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
97b3842692 |
fix: convert NIP-10 chat zap amounts to sats (#161)
* fix: convert NIP-10 chat zap amounts to sats The NIP-10 adapter was storing zap amounts in millisats instead of sats, which was inconsistent with: - The MessageMetadata type definition (zapAmount documented as "Amount in sats") - Other chat adapters like NIP-53 which correctly convert to sats - UI expectations for displaying zap amounts This fix adds the millisat-to-sat conversion (Math.floor(amount / 1000)) matching the implementation in the NIP-53 adapter. Without this fix, zap amounts would display 1000x larger than intended (e.g., a 1000 sat zap would show as 1,000,000). * chore: update TypeScript build info --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
8f008ddd39 |
feat: nip-10 chat interface (#153)
* docs: add comprehensive NIP-10 thread chat design documentation
Add detailed design documents for implementing NIP-10 thread chat feature:
- nip10-thread-chat-design.md: Full architecture, data structures, adapter
implementation plan, relay selection strategy, UI requirements, and 7-phase
implementation checklist
- nip10-thread-chat-examples.md: Complete code examples showing identifier
parsing, conversation resolution, message loading, reply sending with proper
NIP-10 tags, and ChatViewer integration
- nip10-thread-chat-summary.md: Quick reference with visual comparisons,
architecture diagrams, protocol comparison table, data flow, and FAQ
The feature will enable "chat nevent1..." to display kind 1 threaded
conversations as chat interfaces, with the root event prominently displayed
at the top and all replies shown as chat messages below.
Key design decisions:
- Use e-tags with NIP-10 markers (root/reply) instead of q-tags
- Merge multiple relay sources (seen, hints, outbox) for coverage
- Display root event centered with full feed renderer
- Reuse existing ChatViewer infrastructure via adapter pattern
- Support both nevent (with relay hints) and note (ID only) formats
* feat: implement NIP-10 thread chat support
Add complete NIP-10 thread chat implementation enabling "chat nevent..." to
display kind 1 threaded conversations as chat interfaces.
**Type Definitions**:
- Add ThreadIdentifier for nevent/note event pointers
- Add "nip-10" to ChatProtocol type
- Extend ConversationMetadata with thread-specific fields (rootEventId,
providedEventId, threadDepth, relays)
**NIP-10 Adapter** (src/lib/chat/adapters/nip-10-adapter.ts):
- parseIdentifier: Decode nevent/note format, reject non-kind-1 events
- resolveConversation: Fetch provided event, find root via NIP-10 refs,
determine conversation relays (merge hints, outbox, fallbacks)
- loadMessages: Subscribe to kind 1 replies, kind 7 reactions, kind 9735 zaps
- sendMessage: Build proper NIP-10 tags (root/reply markers), add p-tags for
all participants
- sendReaction: Send kind 7 with proper event/author references
- Smart relay selection: Merges seen relays, nevent hints, author outbox,
user outbox (limit 7 relays for performance)
**ChatViewer Updates**:
- Detect NIP-10 threads (protocol === "nip-10")
- Fetch and display root event at top (centered with KindRenderer)
- Show visual separator ("Replies") between root and messages
- Update empty state message for threads ("No replies yet...")
- Enhanced header: Show "Author • Preview" for thread chats
- Update getAdapter to handle "nip-10" protocol
**Chat Parser**:
- Add Nip10Adapter to priority list (before other adapters to catch
nevent/note)
- Update error message with nevent/note format examples
- Update adapter priority documentation
**Component Enhancements**:
- ReplyPreview: Show "thread root" when replying to root event (NIP-10)
- RelaysDropdown: Support conversation.metadata.relays for thread relay
breakdown
- ChatMessageContextMenu: Add "Zap" option to context menu (opens ZapWindow)
**Features**:
- Root event displayed with full feed renderer (can interact: like, zap, etc.)
- All replies shown as chat messages with proper threading
- Reply/React/Zap options on all messages
- Relay dropdown shows breakdown of thread relays
- Participants dropdown shows all thread participants
- @ mention autocomplete works for participants
- Proper NIP-10 tag structure for nested replies
- Smart relay selection for maximum reach
**Usage**:
chat nevent1qqsxyz... # Thread with relay hints
chat note1abc... # Thread with event ID only
Root event is centered at top, all replies below as chat messages. Sending
replies creates kind 1 events with proper NIP-10 root/reply markers and
p-tags for all participants.
* fix: remove unused imports and mark intentionally unused parameter
- Remove unused getTagValues import
- Prefix providedEvent parameter with underscore to indicate intentional non-use (reserved for future relay hint extraction)
* feat: hide reply preview when directly replying to root in NIP-10 threads
In NIP-10 thread chats, the root event is already displayed prominently at
the top, so showing a reply preview when replying directly to the root is
redundant.
Changes:
- Skip reply preview for regular messages when replying to thread root
- Skip reply preview for zap messages when zapping the thread root
- Root event remains visible at top as reference point
This reduces visual clutter and makes the chat feel cleaner when replies
are directed at the already-visible root post.
* refactor: simplify NIP-10 thread chat implementation
Major simplifications based on user feedback:
- Render root event as regular message instead of special feed renderer
- Remove special-case logic for hiding reply preview when replying to root
- Include root event in messages list using RxJS combineLatest
- Change root author role from "admin" to "op" (original poster)
- Fix NIP-10 protocol link handler in chat header
- Add "Chat" option to event context menu for kind 1 events
- Fix unused parameter warnings in nip-10-adapter
This refactoring reduces complexity and makes NIP-10 threads behave
more consistently with other chat protocols.
* feat: add zap support for NIP-10 thread chat messages
Implement getZapConfig method to enable zapping messages in NIP-10 threads:
- Returns message author as recipient
- Includes event pointer for the message being zapped
- Uses conversation metadata relays for zap receipt publishing
- Integrates with new zap infrastructure from main branch
* fix: pass protocol and identifier directly to chat window
Fix 'undefined protocol' error when opening chat from event menu.
Instead of passing raw nevent as args, directly pass the parsed
protocol and identifier props that ChatViewer expects.
* debug: add logging to diagnose chat protocol issue
Add console logging to:
- chat command argParser to see input args and parsed result
- ChatViewer to see received props
This will help diagnose why 'undefined protocol' error appears
when opening chat via command or event menu.
* refactor: improve NIP-10 thread chat UX and relay/participant handling
UI Improvements:
- Remove "thread root" marker from reply previews - treat all replies uniformly
- Hide "load older messages" for NIP-10 threads (all replies loaded reactively)
- Display "Thread" with note icon instead of "Group" for NIP-10 conversations
- Remove debug logging from chat command parser and ChatViewer
Participant Management:
- Derive participants dynamically from messages (like live-chat does)
- Root author (OP) always listed first with "op" role
- All unique message authors included in member list
- Updates in real-time as new people reply
Relay Management:
- Expand relay collection to include participant outbox relays
- Fetch relays from root author, provided event author, and p-tagged participants
- Check up to 5 participants for relay diversity
- Increase max relay limit from 7 to 10 for better coverage
- Add logging for relay collection debugging
This makes NIP-10 threads feel more like proper chat conversations with
accurate participant lists and better relay coverage across the thread.
* refactor: remove debug logging from NIP-10 adapter
Remove informational console.log statements to reduce noise in production.
Keep only console.warn and console.error for actual problems.
This makes the adapter production-ready while maintaining useful error
and warning messages for debugging issues.
* fix: resolve lint errors from logging cleanup
- Fix empty block statement by simplifying next handler
- Prefix unused parameters with underscore (_response, _err)
- All lint checks now pass with 0 errors
* refactor: use AGGREGATOR_RELAYS constant and remove design docs
Replace hardcoded relay URLs with AGGREGATOR_RELAYS constant:
- Import AGGREGATOR_RELAYS from @/services/loaders
- Use constant for fallback relays in getThreadRelays()
- Use constant for default relays in getDefaultRelays()
Remove design documentation files (no longer needed):
- docs/nip10-thread-chat-design.md
- docs/nip10-thread-chat-examples.md
- docs/nip10-thread-chat-summary.md
This improves maintainability by centralizing relay configuration
and reduces repository clutter.
* refactor: remove relay.nostr.band and update AGGREGATOR_RELAYS
relay.nostr.band is no longer operational, so remove it from the codebase:
AGGREGATOR_RELAYS changes:
- Removed: wss://relay.nostr.band/
- Removed: wss://purplepag.es/
- Added: wss://relay.snort.social/
- Added: wss://relay.damus.io/
- New list: nos.lol, relay.snort.social, relay.primal.net, relay.damus.io
Updated code:
- src/services/loaders.ts: Updated AGGREGATOR_RELAYS constant
- src/lib/chat/adapters/nip-53-adapter.ts: Use AGGREGATOR_RELAYS instead of hardcoded relays
Updated tests:
- All test files updated to expect new relay URLs
- Replaced relay.nostr.band references with relay.snort.social
- Replaced purplepag.es references with relay.snort.social
- Fixed URL formats to include trailing slashes for normalization
All 980 tests passing ✓
* fix: change grimRelays from let to const in supporters.ts
Fix lint error from rebase - grimRelays is never reassigned so it should
use const instead of let.
* style: reduce padding on sign-in message to match composer
Change from px-3 py-2 to px-2 py-1 to match the horizontal and vertical
padding of the logged-in message composer (px-2 py-1), ensuring
consistent height between logged-in and logged-out states.
* feat: make sign-in message clickable to open login dialog
Add clickable 'Sign in' link to the logged-out message composer:
- Import LoginDialog component
- Add showLogin state management
- Make 'Sign in' text an underlined button that opens the login dialog
- Add LoginDialog component with controlled state
This provides a better UX by allowing users to quickly sign in
directly from the chat interface.
---------
Co-authored-by: Claude <noreply@anthropic.com>
|
||
|
|
9e11fb590f |
Add donate call to action feature (#150)
* feat: add donate CTA and supporter recognition system Adds donation call-to-action and visual recognition for Grimoire supporters who zap the project. **UserMenu Changes:** - Add "Support Grimoire ⚡" button that opens ZapWindow with preset donation address - Add monthly goal tracker with progress bar (currently showing placeholder values) - Integrate Lightning address (grimoire@coinos.io) and donation pubkey from members list **Supporter Tracking System:** - Create supporters service to monitor kind 9735 (zap receipt) events - Track users who zap Grimoire donation address - Cache supporter info (pubkey, total sats, zap count, last zap timestamp) in localStorage - Reactive updates via RxJS BehaviorSubject - Initialize tracking on app startup **Visual Flair for Supporters:** - Add useIsSupporter hook for checking supporter status - Style supporter usernames with yellow/gold color - Add filled ⚡ zap icon badge next to supporter names - Only applies to non-Grimoire members (members keep their existing gradient badges) **Implementation Details:** - Constants: GRIMOIRE_DONATE_PUBKEY and GRIMOIRE_LIGHTNING_ADDRESS in grimoire-members.ts - Service automatically processes zap receipts and persists supporter data - Monthly goal tracker uses placeholder values (42k/500k sats, 8.4% progress) - Future: Make goal dynamic by calculating from actual zap receipts Related to zap feature implementation in #141, #144, #145 * feat: migrate donation tracking to IndexedDB with monthly calculations Replaces in-memory Map + localStorage with proper Dexie database storage for accurate monthly donation tracking. **Database Changes (Version 17):** - Add `grimoireZaps` table to store individual zap receipts - Schema: eventId (PK), senderPubkey, amountSats, timestamp, comment - Indexes on senderPubkey and timestamp for efficient queries **Supporters Service:** - Store each zap receipt as separate DB record with full metadata - Track individual zap timestamps (not just latest per user) - Cache total and monthly donations for synchronous access - Refresh cache on new zaps for reactive UI updates **Monthly Calculations:** - `getMonthlyDonations()` - Last 30 days (rolling window) - `getCurrentMonthDonations()` - Current calendar month - Both use indexed DB queries for efficiency - Cached values updated on each new zap **UserMenu:** - Set monthly goal to 210M sats (2.1 BTC) - Dynamic progress calculation from actual zap data - Reactive updates when new donations arrive - Number formatting: 1M+ → "2.1M", 1k+ → "42k" **Benefits:** - Accurate historical tracking with timestamps - Efficient monthly queries using DB indexes - No data loss on localStorage quota issues - Foundation for supporter leaderboards and analytics * fix: properly await async zap processing in subscription Changes processZapReceipt calls from forEach to Promise.all to ensure async DB operations complete before processing next batch of events. Prevents race conditions where zaps might not be properly stored in DB if multiple events arrive simultaneously. * perf: optimize Dexie queries for donation tracking Replaces inefficient toArray() + reduce patterns with direct Dexie iteration APIs for better memory efficiency and performance. **Optimizations:** 1. **Supporter Count** - Use `uniqueKeys()` instead of loading all records - Before: Load all → create Set → get size - After: `orderBy('senderPubkey').uniqueKeys().length` - ~90% memory reduction for large datasets 2. **Aggregation Queries** - Use `.each()` iterator pattern - `getTotalDonationsAsync()` - Stream records, accumulate sum - `getMonthlyDonationsAsync()` - Indexed query + iteration - `getCurrentMonthDonations()` - Indexed query + iteration - `getSupporterInfo()` - Per-pubkey indexed query with iteration - `getAllSupporters()` - Stream all, group in Map, sort 3. **Cache Refresh** - Optimized `refreshSupporters()` - uniqueKeys for supporter set - Direct iteration for total/monthly sums - Single indexed query for monthly window **Monthly Goal:** - Update from 210M sats to 210k sats (0.0021 BTC) - More achievable target for initial launch **Benefits:** - Lower memory usage (no intermediate arrays) - Faster queries (direct iteration vs map/reduce) - Better scalability with growing zap history - Leverages Dexie's indexed cursors for efficiency * refactor: singleton supporters service with optimized Dexie queries Complete refactor of donation tracking to proper singleton pattern with relay-based subscriptions and zero in-memory caching. **Singleton Service Pattern:** - Class-based SupportersService matching relay-liveness/accounts patterns - Single `init()` method initializes subscriptions - Observable `supporters$` for reactive UI updates - Proper cleanup with `destroy()` method **Relay-Based Subscription:** - Fetch Grimoire's inbox relays via relayListCache - Subscribe to zaps using `#p` tag filter (NIP-57 recipient tag) - Use createTimelineLoader with proper relay hints - Fallback to aggregator relays if no inbox relays found - Dual subscription: loader + event store timeline for comprehensive coverage **Optimized Dexie Schema:** - Add compound index: `[senderPubkey+timestamp]` - Enables efficient per-user date range queries - Schema: `&eventId, senderPubkey, timestamp, [senderPubkey+timestamp]` **Zero In-Memory Caching:** - Remove cachedTotalDonations and cachedMonthlyDonations - All queries go directly to IndexedDB - Use Dexie iteration APIs (`.each()`, `.uniqueKeys()`) - Compound index queries for monthly aggregations **Premium Supporter Detection:** - New threshold: 2.1k sats/month = premium supporter - `isPremiumSupporter()` uses compound index query - `getMonthlySupporterInfo()` returns monthly stats per user **Badge Logic Updates:** - Premium supporters (2.1k+/month): Zap badge in username color - Regular supporters: Yellow text + yellow filled zap icon - useIsSupporter returns `{ isSupporter, isPremiumSupporter }` **UserMenu Updates:** - Use async `getMonthlyDonations()` with useState/useEffect - Subscribe to `supporters$` to trigger monthly recalculation - Remove synchronous function calls **Key Benefits:** - Proper singleton lifecycle management - Accurate relay selection for zap discovery - No memory overhead from caching - Efficient compound index queries - Scales to thousands of zaps without performance degradation * fix: fetch Grimoire relay list before subscribing to zaps Ensures kind 10002 relay list is loaded before subscribing to zap receipts, preventing fallback to aggregators only. **Problem:** At startup, relayListCache was empty, so getInboxRelays() returned null and service fell back to aggregator relays only, missing zaps published to Grimoire's actual inbox relays. **Solution:** 1. Explicitly fetch Grimoire's kind 10002 relay list using addressLoader 2. Wait up to 5 seconds for relay list to load 3. Then get inbox relays from populated cache 4. Subscribe to those relays + aggregators **Flow:** init() → subscribeToZapReceipts() → fetch kind 10002 (5s timeout) → getInboxRelays() from cache → subscribe to inbox relays + aggregators → keep subscription open for live updates Fixes startup zap loading issue. * feat: compact support section with manual refresh and full clickability Makes the entire support section clickable to open zap dialog and adds manual refresh button for donation stats. **Changes:** - Entire support section now clickable (opens zap dialog) - Add refresh button with spinning animation - Remove 'Help us build...' tagline for more compact design - Keep just title, stats, and progress bar - Manual refresh re-fetches Grimoire relay list and reloads zaps **Layout:** Click anywhere to donate, click refresh icon to manually sync zaps. * refactor: replace BehaviorSubject with Dexie useLiveQuery for reactive supporter tracking Replace manual BehaviorSubject pattern with Dexie's built-in useLiveQuery hook for reactive database queries. This simplifies the code and leverages Dexie's optimized change detection. Changes: - Remove BehaviorSubject from SupportersService - Remove refreshSupporters() method and all calls to it - Update useIsSupporter hook to use useLiveQuery for supporter pubkeys - Update GrimoireWelcome to use useLiveQuery for monthly donations - Update UserMenu to use useLiveQuery for monthly donations - Remove unused imports (cn, useEffect, useState) and fields (initialized) Benefits: - Less code to maintain (no manual observable management) - Automatic reactivity when DB changes - Better performance with Dexie's built-in change detection * fix: improve cold start zap loading and fix subscription memory leak Fixes several issues with zap loading on cold start: 1. Memory leak fix: Timeline subscription wasn't being stored or cleaned up - Now properly add timeline subscription to main subscription for cleanup 2. Better cold start handling: - Increase timeout from 5s to 10s for relay list fetching - Add 100ms delay after addressLoader to let relayListCache update - Add more detailed logging to debug cold start issues 3. Improved logging: - Show which relays are being used for subscription - Log when processing zap events from eventStore - Better error messages with context These changes should help diagnose why zaps aren't loading on cold start and prevent memory leaks from unclosed subscriptions. * feat: add hardcoded relay fallback for instant cold start zap loading Add hardcoded relays (wss://nos.lol, wss://lightning.red) to immediately start loading zaps on cold start, without waiting for relay list fetch. Changes: - Add GRIMOIRE_ZAP_RELAYS constant with hardcoded reliable relays - Refactor subscribeToZapReceipts() to start with hardcoded relays immediately - Move relay list fetching to separate non-blocking method fetchAndMergeRelayList() - Relay list fetch now happens in parallel with subscription (non-blocking) - Still fetch and log additional relays from kind 10002 in background This ensures zaps load immediately on app start rather than waiting for relay list to be fetched from network. * fix: remove dead relay.nostr.band and add detailed zap processing logs Remove relay.nostr.band from all relay lists as it's dead. Changes: - Remove from AGGREGATOR_RELAYS in loaders.ts - Remove from NIP-53 fallback relays - Update all test files to use alternative relays Add detailed logging to debug progress bar showing 0: - Log each zap processing step (validation, recipient check, sender check) - Log duplicate zaps, invalid zaps, and 0-sat zaps - Log existing zap count in DB on init - Add timestamps to successful zap recordings This will help diagnose why the progress bar shows 0 even though zaps are being fetched. * fix: reduce zap query limit from 1000 to 500 to avoid relay rejections Many relays reject REQ filters with limit > 500. This was likely causing the zap subscription to fail silently on some relays, resulting in no zaps being fetched and the progress bar showing 0. Reduced limit from 1000 to 500 to be compatible with more relays. * fix: remove lightning.red and aggregator relays, add monthly calculation debug logs Changes: - Remove wss://lightning.red from hardcoded relays (only use wss://nos.lol) - Remove aggregator relays from zap subscription (don't use for fetching zaps) - Remove AGGREGATOR_RELAYS import Add debug logging to diagnose progress bar issue: - GrimoireWelcome: Log count and total of zaps found in last 30 days - UserMenu: Log count and total of zaps found in last 30 days - Show cutoff date in GrimoireWelcome log This will help identify if: 1. Zaps are being stored in DB 2. Zaps are within the 30-day window 3. Monthly calculation is running and finding the zaps * fix: subscribe to eventStore.insert$ to catch zaps in real-time The previous approach using eventStore.timeline() was only emitting once with the initial state (0 events) and not re-emitting when new zaps were added to the store. Changed to subscribe to eventStore.insert$ which fires for every new event added to the store. This catches zaps as they arrive from relays in real-time. Changes: - Subscribe to eventStore.insert$ instead of eventStore.timeline() - Filter events for kind 9735 with #p tag matching Grimoire pubkey - Process each zap as it's inserted into the store - Add logging to show when zaps are received from insert$ stream This should fix the progress bar showing 0 even though zaps are being fetched from relays. * fix: also subscribe to eventStore.timeline() to process existing zaps The insert$ stream only fires for NEW events being added. If events were already in the eventStore (from cache or previous session), we wouldn't see them. Now subscribing to both: - insert$ - catches new events in real-time as they're added - timeline() - emits ALL matching events (existing + new) This ensures we process: 1. Zaps already in the eventStore from previous sessions 2. Zaps that loaded from relays before our subscription was active 3. New zaps arriving in real-time Added logging to distinguish between new (insert$) and all (timeline) events. * fix: subscribe directly to loader observable to process zaps as they arrive Complete rethink of the zap sync strategy. The issue was that we were subscribing to eventStore.timeline() and eventStore.insert$ which were not working as expected. The TimelineLoader returns Observable<NostrEvent> that emits individual events as they arrive from relays. We should subscribe directly to this. Changes: - Remove eventStore.timeline() and eventStore.insert$ subscriptions - Subscribe directly to loader().subscribe() with next/error/complete handlers - Process each event in the next() handler as it arrives from relays - Log each zap received from loader for debugging - Remove unused eventStore import This is the simplest and most direct approach - process events exactly as the loader emits them from the relays. * chore: merge main and remove debug logging for production Merged latest changes from main branch and removed all debug logging added during development to make the code production-ready. Changes: - Merged origin/main into branch - Removed console.log statements from supporters service init/subscribe - Removed debug logging from processZapReceipt method - Removed debug logging from fetchAndMergeRelayList method - Removed debug logging from GrimoireWelcome monthly calculation - Removed debug logging from UserMenu monthly calculation - Kept only error logging for production debugging The donation system is now production-ready with clean, minimal logging. * fix: remove yellow text color from regular supporters, keep only badge Regular supporters (who zapped but not 2.1k+/month) should only show the yellow zap badge next to their name, not have their username colored yellow. Changes: - Remove yellow text color (text-yellow-500) from regular supporters - Regular supporters now: normal username color + yellow zap badge - Premium supporters still: normal username color + badge in username color - Updated component documentation to reflect this change This provides cleaner visual hierarchy where only Grimoire team members get special username colors (gradient), while supporters are distinguished by their badge alone. --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
3adc9bdfc3 |
feat: add invoice description fallback for wallet transactions
Add fallback to lightning invoice description when transaction description is not available. Improves transaction list readability by showing invoice descriptions instead of generic "Payment" labels. - Add getInvoiceDescription helper with applesauce caching pattern - Update TransactionLabel to check invoice description - Update detail dialog to show invoice description as fallback - Maintains zap detection logic and UI Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> |
||
|
|
97dd30f587 |
Add anonymous zap option with throwaway signer (#154)
* feat: add anonymous zap option Add "Zap anonymously" checkbox that allows users to send zaps without revealing their identity. When enabled, creates a throwaway keypair to sign the zap request instead of using the active account's signer. This also enables users without a signer account to send zaps by checking the anonymous option. * feat: prioritize recipient's inbox relays for zap receipts Add selectZapRelays utility that properly selects relays for zap receipt publication with the following priority: 1. Recipient's inbox relays (so they see the zap) 2. Sender's inbox relays (so sender can verify) 3. Fallback aggregator relays This ensures zap receipts are published where recipients will actually see them, rather than just the sender's relays. Includes comprehensive tests for relay selection logic. --------- Co-authored-by: Claude <noreply@anthropic.com> |