Commit Graph

14 Commits

Author SHA1 Message Date
Alejandro
97f18de358 feat: message reactions (#110)
* Add subtle inline reactions to chat messages

Implements NIP-25 reaction display for chat messages with per-message lazy loading:

- Created MessageReactions component that independently loads kind 7 reactions
  for each message using EventStore timeline queries
- Displays reactions as tiny inline badges in bottom-right corner (doesn't affect
  message height)
- Aggregates reactions by emoji with deduplication by pubkey
- Supports both unicode emoji and NIP-30 custom emoji with images
- Shows reaction count next to each emoji
- Integrated into both regular user messages and zap messages in ChatViewer
- Reactions load reactively - new reactions appear automatically via EventStore
  observables

No "+" button for adding reactions yet - this is display-only for now.
Works with NIP-29 groups and will work with any chat protocol that uses
kind 7 reactions with e-tags.

* Fix reaction loading to use protocol-specific relay hints

Previously MessageReactions was only querying EventStore without actually
fetching reactions from relays. Now it properly:

- Starts a relay subscription per message to fetch kind 7 reactions
- Uses protocol-specific relay hints via getConversationRelays() helper:
  * NIP-29 groups: Single relay from conversation.metadata.relayUrl
  * NIP-53 live chats: Multiple relays from conversation.metadata.liveActivity.relays
- Memoizes relay array in MessageItem to prevent unnecessary re-subscriptions
- Cleans up subscriptions when message unmounts or changes

This ensures reactions are actually fetched and displayed correctly across
different chat protocols.

* Remove unused NostrEvent import

* Move reactions inline after timestamp with subtler styling

Reactions now appear directly after the timestamp in the message header:
- Removed absolute positioning and background color
- Increased spacing between emoji and count (gap-1 instead of gap-0.5)
- Simple inline display with no border or background
- Appears in natural reading flow: "Alice  10:30 AM  ❤️ 3  👍 1"
- Removed relative positioning from message container (no longer needed)

This makes reactions much more subtle and integrated into the message UI.

* Add detailed tooltips to reactions showing who reacted

Each reaction badge now shows a tooltip with:
- Emoji and count on first line
- Comma-separated list of display names who reacted

Implementation:
- Split into ReactionBadge component per reaction
- Loads profiles for all reactor pubkeys using eventStore.profiles()
- Uses getDisplayName() helper for human-readable names
- Tooltip format: "❤️ 3\nAlice, Bob, Carol"

This makes it easy to see exactly who reacted with each emoji.

* Simplify reaction tooltips to show truncated pubkeys

Changed tooltip implementation from loading profiles (which wasn't working
with EventStore API) to showing truncated pubkeys for simplicity and performance:

- Removed profile loading logic (eventStore.profiles() doesn't exist)
- Tooltips now show: "❤️ 3\nabcd1234..., efgh5678..."
- Truncated to first 8 chars for readability
- No external API calls needed, purely computed from reaction data
- Can be enhanced later to load profiles if needed

Build verified: TypeScript compilation passes, all tests pass.
This is production-ready code.

* Add emoji reaction picker to chat messages

Implements complete reaction functionality with searchable emoji picker:

**UI Enhancements:**
- Reactions display horizontally with hidden scrollbar (hide-scrollbar CSS utility)
- Messages with many reactions scroll smoothly without visible scrollbar
- Inline positioning after timestamp for clean, integrated look

**Emoji Picker Dialog:**
- Real-time search using FlexSearch (EmojiSearchService)
- Quick reaction bar with common emojis (❤️ 👍 🔥 😂 🎉 👀 🤔 💯)
- Frequently used section based on localStorage history
- Support for both unicode and NIP-30 custom emoji
- Grid layout with 48-emoji results
- Auto-focus search input for keyboard-first UX

**Protocol Implementation:**
- Added sendReaction() method to ChatProtocolAdapter base class
- NIP-29 groups: kind 7 with e-tag + h-tag (group context)
- NIP-53 live chats: kind 7 with e-tag + a-tag (activity context)
- NIP-C7 DMs: kind 7 with e-tag + p-tag (partner context)
- All reactions include k-tag for reacted event kind
- NIP-30 custom emoji support via emoji tags

**Context Menu Integration:**
- Added "React" action to ChatMessageContextMenu with Smile icon
- Opens emoji picker dialog on click
- Passes conversation and adapter for protocol-specific reactions
- Only shows when conversation and adapter are available

**Frequently Used Tracking:**
- Stores reaction history in localStorage (grimoire:reaction-history)
- Displays top 8 most-used reactions when no search query
- Increments count on each reaction sent

**Tooltips:**
- Show emoji + count + truncated pubkeys
- Format: "❤️ 3\nabcd1234..., efgh5678..."
- Future enhancement: load profiles for display names

Ready for testing! Users can now right-click messages → React → search/pick emoji.

* Load user's custom emoji list in reaction picker

The emoji picker now loads custom emoji from:
- User's emoji list (kind 10030) - personal custom emoji
- Context emojis from conversation messages
- Unicode emojis (default set)

EmojiSearchService prioritizes:
1. User emoji (highest priority)
2. Context emoji (from conversation)
3. Emoji sets (if any)
4. Unicode emoji (default)

When searching or browsing, users will see their custom emoji alongside
standard unicode emoji, making it easy to use personal/community emoji
in reactions.

* Fix reaction container to prevent message width expansion

Wrapped reactions in a scrollable container with proper constraints:
- inline-flex: displays inline with message metadata
- max-w-full: prevents expanding beyond message width
- overflow-x-auto: enables horizontal scrolling for many reactions
- hide-scrollbar: hides scrollbar for clean appearance

Messages with many reactions now stay within their width and scroll
horizontally without a visible scrollbar.

* Highlight reaction counts when active user has reacted

Changes:
- Reaction counts now show in highlight color (text-highlight) when the
  active user has reacted with that emoji
- Added font-semibold to make user's reactions more prominent
- Checks if activeAccount.pubkey is in reaction.pubkeys array
- Provides clear visual feedback showing which reactions you've made

This makes it easy to see at a glance which reactions are yours in a
conversation with many reactions.

* Refine reaction badge styling: increase spacing and remove bold

- Increase gap between reaction badges from gap-1 to gap-2
- Remove font-semibold from highlighted reaction counts
- Keep text-highlight color for active user's reactions
- Results in more subtle, polished appearance

* Refactor emoji picker to use useEmojiSearch hook

- Replace manual emoji service management with useEmojiSearch hook
- Use same emoji loading approach as chat autocomplete
- Subscribe to EventStore observables for reactive updates
- Load user's emoji list (kind 10030) and emoji sets (kind 30030)
- Fix custom emoji search not working
- Remove redundant async loading logic

* Fix emoji picker UI issues

- Remove quick reaction bar (❤️ 👍 🔥 etc.)
- Fix custom emoji in "Recently used" section - now renders images instead of shortcodes
- Increase grid spacing from gap-2 to gap-3 to reduce crowding
- Add helpers to properly lookup and render custom emoji from service

* Improve reaction badge sizing and spacing

- Increase custom emoji size from size-3 (12px) to size-3.5 (14px)
- Increase gap between emoji and count from gap-1 to gap-1.5
- Add object-contain to custom emoji images for proper aspect ratio
- Add leading-none to unicode emoji for consistent vertical alignment
- Results in better visual balance between custom and unicode emoji

* Fix custom emoji shrinking in reaction badges

- Add flex-shrink-0 to custom emoji images to prevent compression
- Add flex-shrink-0 to unicode emoji spans for consistency
- Ensures both custom and unicode emoji maintain their size-3.5 dimensions

* Improve emoji picker UX with fixed layout

- Always show exactly 2 rows (16 emoji) to prevent height jumping
- Merge recently used with search results into unified grid
- When no search: show recently used first, then fill with other emoji
- When searching: show top 16 results
- Remove separate "Recently used" section for cleaner layout
- Add aspect-square to buttons for consistent sizing
- Add object-contain to custom emoji for proper aspect ratio
- Replace scrollable area with fixed-height grid

* Refine emoji picker to show single row with fixed height

- Show only 1 row (8 emoji) instead of 2 rows for more compact UI
- Add min-h-[3.5rem] to prevent height changes
- Ensure custom emoji (w-6 h-6) matches unicode emoji (text-2xl) size
- Add leading-none to unicode emoji for better vertical alignment
- Empty state "No emojis found" maintains same grid height
- Consistent sizing between custom and unicode emoji across the picker

* Fix emoji sizing in picker to match unicode and custom emoji

- Reduce unicode emoji from text-2xl (24px) to text-xl (20px)
- Reduce custom emoji from w-6 h-6 (24px) to size-5 (20px)
- Both now render at same 20px size for visual consistency
- Fixes custom emoji appearing too large compared to unicode emoji

* ui: dialog tweaks

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-16 18:47:16 +01:00
Alejandro
f464c68bde feat: theme selector (#95)
* feat: Add reusable theme system with Plan 9 proof of concept

Implement a comprehensive theme system that:
- Defines typed Theme interface with colors, syntax highlighting, scrollbar, and gradient variables
- Creates ThemeProvider with React context for runtime theme switching
- Persists theme selection to localStorage
- Includes 3 built-in themes: dark (default), light, and plan9

Theme structure supports:
- Core UI colors (background, foreground, primary, secondary, accent, etc.)
- Status colors (success, warning, info) replacing hardcoded Tailwind colors
- Syntax highlighting variables for code blocks
- Diff highlighting colors (inserted, deleted, meta)
- Scrollbar styling variables
- Gradient colors for branding

Technical changes:
- Update CSS to use new theme variables throughout
- Update prism-theme.css to use syntax variables instead of hardcoded values
- Remove chart colors (unused)
- Add success/warning/info to tailwind.config.js
- Wire up ThemeProvider in main.tsx

For Nostr publishing (future):
- d tag: "grimoire-theme"
- name tag: theme display name

* feat: Add theme selector to user menu, remove configurable border radius

- Remove border radius from theme configuration (borders are always square)
- Add theme selector dropdown to user menu (available to all users)
- Theme selector shows active theme indicator
- Theme selection persists via localStorage

* fix: Improve theme contrast and persistence

- Fix theme persistence: properly check localStorage before using default
- Plan9: make blue subtler (reduce saturation), darken gradient colors
  for better contrast on pale yellow background
- Light theme: improve contrast with darker muted foreground and borders
- Change theme selector from flat list to dropdown submenu

* fix: Replace Plan9 yellow accent with purple, add zap/live theme colors

- Replace Plan9's bright yellow accent with purple (good contrast on pale yellow)
- Add zap and live colors to theme system (used by ZapReceiptRenderer, StatusBadge)
- Make light theme gradient orange darker for better contrast
- Update ZapReceiptRenderer to use theme zap color instead of hardcoded yellow-500
- Update StatusBadge to use theme live color instead of hardcoded red-600
- Add CSS variables and Tailwind utilities for zap/live colors

* fix: Make gradient orange darker, theme status colors

- Make gradient orange darker in light and plan9 themes for better contrast
- Make req viewer status colors themeable:
  - loading/connecting → text-warning
  - live/receiving → text-success
  - error/failed → text-destructive
  - eose → text-info
- Update relay status icons to use theme colors
- Update tests to expect theme color classes

* fix: Use themeable zap color for active user names

- Replace hardcoded text-orange-400 with text-zap in UserName component
- Replace hardcoded text-orange-400 with text-zap in SpellRenderer ($me placeholder)
- Now uses dark amber/gold with proper contrast on light/plan9 themes

* feat: Add highlight theme color for active user display

Add dedicated 'highlight' color to theme system for displaying the
logged-in user's name, replacing the use of 'zap' color which felt
semantically incorrect. The highlight color is optimized for contrast
on each theme's background.

- Add highlight to ThemeColors interface and apply.ts
- Add --highlight CSS variable to index.css (light and dark)
- Add highlight to tailwind.config.js
- Configure appropriate highlight values for dark, light, and plan9 themes
- Update UserName.tsx to use text-highlight for active account
- Update SpellRenderer.tsx MePlaceholder to use text-highlight

* fix: Restore original orange-400 highlight color for dark theme

Update dark theme highlight to match original text-orange-400 color
(27 96% 61%) for backward compatibility with existing appearance.

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-14 19:24:37 +01:00
Alejandro
7036fb5539 Fix chat composer placeholder and text alignment (#84)
* Fix chat composer placeholder and text alignment

- Adjusted .ProseMirror min-height from 2rem to 1.25rem to match container
- Added flexbox layout to .ProseMirror for proper vertical centering
- Removed float:left and height:0 from placeholder causing misalignment
- Moved padding from editor props to wrapper div
- Updated EditorContent to use flex items-center for alignment

Resolves vertical alignment issues in chat composer input field.

* Fix cursor placement in chat composer placeholder

- Changed from flexbox to line-height for vertical centering
- Removed flex from .ProseMirror to fix cursor positioning
- Set line-height: 1.25rem to match min-height for proper alignment
- Removed flex items-center from EditorContent className

This ensures the cursor appears at the correct position when focusing
the input field, rather than after the placeholder text.

* Fix cursor placement on mobile devices

- Made placeholder absolutely positioned to prevent it from affecting cursor
- Added position: relative to .ProseMirror container
- This ensures cursor appears at the start of input on mobile browsers

The absolute positioning removes the placeholder from the normal layout flow,
preventing mobile browsers from placing the cursor after the pseudo-element.

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-13 22:07:04 +01:00
Alejandro
2bad592a3a feat: emoji autocompletion (#54)
* feat: add NIP-30 emoji autocompletion to editor

Implement emoji autocomplete triggered by `:` in the MentionEditor:

- EmojiSearchService: flexsearch-based indexing for emoji shortcodes
- useEmojiSearch hook: loads Unicode emojis + user's custom emoji (kind 10030/30030)
- EmojiSuggestionList: grid-based suggestion UI with keyboard nav
- Update MentionEditor with second Mention extension for emoji
- Serialize emoji as `:shortcode:` format with NIP-30 emoji tags
- Update chat adapters to include emoji tags in messages

Sources:
- Unicode: ~300 common emojis with shortcodes
- Custom: user's emoji list (kind 10030) and referenced sets (kind 30030)
- Context: emoji tags from events being replied to

* feat: add rich emoji preview in editor

Emoji inserted via the autocomplete now display as actual images/characters
instead of :shortcode: text:

- Custom emoji: renders as inline <img> with proper sizing
- Unicode emoji: renders as text with emoji font sizing
- Both show :shortcode: on hover via title attribute

CSS styles ensure proper vertical alignment with surrounding text.

* fix: store emoji url and source attributes in node schema

The TipTap Mention extension only defines `id` and `label` by default.
Added `addAttributes()` to EmojiMention extension to also store `url`
and `source` attributes, fixing emoji tags not being included in sent
messages.

* fix: improve emoji node rendering in editor

- Remove redundant renderLabel (nodeView handles display)
- Add renderText for proper clipboard behavior
- Make nodeView more robust with null checks
- Add fallback to shortcode if image fails to load
- Unicode emoji shows character, custom shows image

* fix: serialize unicode emoji as actual characters, not shortcodes

When sending messages:
- Unicode emoji (😄, 🔥) → outputs 😄, 🔥 (the actual character)
- Custom emoji (:pepe:) → outputs :pepe: with emoji tag for rendering

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-12 11:30:52 +01:00
Alejandro Gómez
0b20a628e2 feat: add mention editor and NIP-29 chat enhancements
Implements rich text editing with profile mentions, NIP-29 system messages,
day markers, and naddr support for a more complete chat experience.

Editor Features:
- TipTap-based rich text editor with @mention autocomplete
- FlexSearch-powered profile search (case-insensitive)
- Converts mentions to nostr:npub URIs on submission
- Keyboard navigation (Arrow keys, Enter, Escape)
- Fixed Enter key and Send button submission

NIP-29 Chat Improvements:
- System messages for join/leave events (kinds 9000, 9001, 9021, 9022)
- Styled system messages aligned left with muted text
- Shows "joined" instead of "was added" for consistency
- Accepts kind 39000 naddr (group metadata addresses)
- Day markers between messages from different days
- Day markers use locale-aware formatting (short month, no year)

Components:
- src/components/editor/MentionEditor.tsx - TipTap editor with mention support
- src/components/editor/ProfileSuggestionList.tsx - Autocomplete dropdown
- src/services/profile-search.ts - FlexSearch service for profile indexing
- src/hooks/useProfileSearch.ts - React hook for profile search

Dependencies:
- @tiptap/react, @tiptap/starter-kit, @tiptap/extension-mention
- @tiptap/extension-placeholder, @tiptap/suggestion
- flexsearch@0.7.43, tippy.js@6.3.7

Tests:
- Added 6 new tests for naddr parsing in NIP-29 adapter
- All 710 tests passing

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-12 10:26:38 +01:00
Alejandro Gómez
2987a37e65 feat: spells 2025-12-20 14:25:40 +01:00
Alejandro Gómez
8f80742ef1 feat: tab names 2025-12-19 12:49:29 +01:00
Alejandro Gómez
b81ac599a2 feat: improve layout animations and consolidate controls
Animation improvements:
- Use professional easing curve: cubic-bezier(0.25, 0.1, 0.25, 1)
- Reduce duration from 200ms to 150ms for snappier feel
- Enable animations only during preset application (not manual resize/drag)
- Add CSS layout containment for better performance
- Add/remove 'animating-layout' class to control when animations occur

UI consolidation:
- Merge layout preset dropdown and insertion settings into single control
- Create unified LayoutControls component with sections:
  * Presets (apply existing layouts)
  * Insert Mode (balanced/horizontal/vertical)
  * Split ratio slider with +/- buttons
- Remove separate icons, now just one SlidersHorizontal button
- Cleaner, more discoverable interface

Benefits:
- Smoother, more natural-feeling animations
- No animation jank during manual operations
- Single unified control reduces UI clutter
- All layout settings in one place

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-18 13:02:19 +01:00
Alejandro Gómez
d624b5b05a ui: simplify layout settings and add smooth animations
- Remove success notification when applying layouts (keep only errors)
- Add CSS transitions for smooth window resizing/repositioning
- Replace large settings Dialog with compact Popover
- Reduce settings UI from ~220 lines to ~97 lines
- Remove verbose descriptions and preview section
- Make settings match site's minimal UI patterns
- Settings now update live without Save/Cancel buttons
- Create popover.tsx component using Radix UI primitives

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-18 12:55:26 +01:00
Alejandro Gómez
a066284825 chore: petrify 2025-12-18 10:19:52 +01:00
Alejandro Gómez
19cdde0110 feat: syntax highlighting 2025-12-15 13:11:59 +01:00
Alejandro Gómez
e5c871617e chore: cleanup, a11y and state migrations 2025-12-14 16:32:45 +01:00
Alejandro Gómez
ad17658ef3 fix: contrast 2025-12-11 23:00:06 +01:00
Alejandro Gómez
cd41034b2f 👶 2025-12-09 16:26:31 +01:00