mirror of
https://github.com/purrgrammer/grimoire.git
synced 2026-04-09 23:16:50 +02:00
dd6b30b82e3d2caa15f15cbb50f5cef4721040ff
23 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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> |
||
|
|
f9733878e2 |
Hide scrollbars completely by setting width and height to 0 (#225)
* fix: hide tabbar scrollbar by setting width/height to 0 The no-scrollbar utility's display:none wasn't fully overriding the global scrollbar styles that set width/height to 8px. Adding explicit width:0 and height:0 ensures the scrollbar is completely hidden in WebKit browsers. https://claude.ai/code/session_018RiPf74GNf2oWcoYNRoZyx * fix: use !important to override global scrollbar styles The global * selector's scrollbar-width: thin was overriding the no-scrollbar utility due to CSS cascade order. Adding !important ensures the utility always wins. https://claude.ai/code/session_018RiPf74GNf2oWcoYNRoZyx --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
2ad3f90174 |
Improve mobile UX with larger touch targets (#223)
- Add useIsMobile hook for viewport detection - TabBar: larger height (48px), disable reorder on mobile, hide workspace numbers - Header buttons: larger touch targets for SpellbookDropdown, UserMenu, LayoutControls - Window toolbar: larger buttons (40px) on mobile - Mosaic dividers: wider (12px) on mobile for easier dragging - CommandLauncher: larger items, footer text, hide kbd hints on mobile - Editor suggestions: responsive widths, min-height 44px touch targets - EventFooter: larger kind/relay buttons on mobile - CodeCopyButton: larger padding and icon on mobile - MembersDropdown: larger trigger and list items on mobile All changes use mobile-first Tailwind (base styles for mobile, md: for desktop) to meet Apple HIG 44px minimum touch target recommendation. Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> |
||
|
|
d69cc1fec6 |
Migrate from Tailwind CSS v3 to v4 (#219)
- Replace JS config (tailwind.config.js) with CSS-first @theme directive - Add @tailwindcss/vite plugin for improved Vite integration - Update src/index.css with v4 syntax (@import, @theme, @utility) - Convert @layer utilities to @utility syntax - Fix hardcoded scrollbar colors in command-launcher.css - Add Tailwind v4 skill document (.claude/skills/tailwind-v4.md) - Update CLAUDE.md with Tailwind v4 quick reference https://claude.ai/code/session_01T6RenqDof8br6Nt9aKcjvq 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> |
||
|
|
b83b26ea9a |
Fix nostr entity matching in paste handler (#197)
* fix: only match nostr entities at word boundaries in paste handler Updates the paste handler regex to only match nostr bech32 entities (npub, note, nevent, naddr, nprofile) when surrounded by whitespace or at string boundaries. This prevents URLs containing nostr entities (e.g., https://njump.me/npub1...) from being incorrectly converted to mentions. Uses a capture group (^|\s) instead of lookbehind assertion for Safari compatibility (lookbehind only supported in Safari 16.4+). * fix: disable pointer events on links in editor Adds pointer-events: none to anchor tags within the ProseMirror editor to prevent clicking on pasted URLs from navigating away. This allows users to edit the text containing links rather than accidentally triggering navigation. --------- 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> |
||
|
|
2e8ef0e5db |
fix: add iOS PWA notch support with safe area insets (#190)
- Update viewport meta tag to include viewport-fit=cover - Add apple-mobile-web-app-capable and status-bar-style meta tags - Add CSS safe area insets to body element for proper notch handling - Ensures content extends into safe areas on iOS devices while respecting notches and rounded corners Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
94982ca7f4 |
feat(post): add POST command with rich text editor and relay selection (#180)
* feat(post): add POST command with rich text editor and relay selection Phase 3: Create POST command for publishing kind 1 notes Features: - RichEditor component with @mentions, :emoji: autocomplete - Image/video upload via Blossom with drag-and-drop - Relay selection UI with write relays pre-selected by default - Per-relay publish status tracking (loading/success/error) - Submit with Ctrl/Cmd+Enter keyboard shortcut - Multi-line editing with rich previews for attachments - NIP-30 emoji tags and NIP-92 imeta tags for attachments Implementation: - Add 'post' to AppId type - Add POST command to man pages - Create PostViewer component using RichEditor - Wire PostViewer into WindowRenderer - Publish events using EventFactory and RelayPool - Track per-relay status with visual indicators Usage: - Run 'post' command to open the composer - Type content with @mentions and :emoji: - Upload media via button or drag-and-drop - Select/deselect relays before publishing - Press Publish button or Ctrl/Cmd+Enter to post - View per-relay publish status in real-time * feat(editor): add nostr bech32 paste handler with inline previews Implements paste handling for nostr: URIs (npub, note, nevent, naddr, nprofile) that transforms them into rich inline preview chips in the chat composer. Changes: - Add NostrEventPreview TipTap node type for displaying bech32 previews - Add NostrPasteHandler extension to detect and transform pasted bech32 strings - Update serializeContent to convert previews back to nostr: URIs on submit - Add CSS styling for preview chips with hover effects - Support all major bech32 types: npub, note, nevent, naddr, nprofile Features: - Automatic detection of nostr: URIs in pasted text (with or without prefix) - Visual preview chips with type icon, label, and truncated ID - Maintains nostr: URI format in final message content (NIP-27 compatible) - Simple implementation without event fetching (fast, no loading states) - Styled with primary theme colors for consistency Technical details: - Uses ProseMirror Plugin API for paste interception - Inline atomic nodes for previews (similar to mentions and blob attachments) - Regex pattern matches all valid bech32 formats - Proper error handling for invalid bech32 strings - Extensible foundation for future rich metadata fetching * refactor(editor): improve nostr preview display with kind icons Updates the nostr event preview chips to show more meaningful information: Changes: - Add getKindIcon() helper function with 15+ kind mappings - Display format: kind-icon + author/id (e.g., "📄 a1b2c3d4") - For naddr: show kind icon + author pubkey (first 8 chars) - For nevent: show kind icon + author if available, else event ID - For note: show 📝 + event ID (first 8 chars) - For npub/nprofile: show 👤 + pubkey (first 8 chars) - Removed verbose type labels (NPUB, NEVENT, etc.) - Increased label max-width to 120px for better readability Kind icon mappings: - 👤 Profile (kind 0) - 📝 Note (kind 1) - 👥 Contacts (kind 3) - 🔁 Repost (kind 6) - ❤️ Reaction (kind 7) - ⚡ Zap (kind 9735) - 📄 Long-form (kind 30023) - 🎙️ Live event (kind 30311) - 📦 File metadata (kind 1063) - 📌 Addressable events (30000-39999) - 🔄 Replaceable events (10000-19999) Result: More compact, visually intuitive previews that immediately convey the content type and author without requiring event fetching. * refactor(editor): simplify nostr preview display and reuse mentions Major simplification of the nostr bech32 preview display: Profile handling (npub/nprofile): - Now creates regular @mention nodes instead of custom preview nodes - Reuses existing mention infrastructure, styling, and UserName component - Displays as "@username" with existing mention chip styling - Serializes to nostr:npub1... on submit (same as manual @mentions) Event/Address display (note/nevent/naddr): - Removed emoji icons for cleaner, more minimal appearance - Display format: "event abc12345" for note/nevent - Display format: "address article-slug" for naddr (shows d identifier) - Falls back to short pubkey if naddr has no d identifier - Simple text-only chips with type + identifier Benefits: - Less visual noise (no emojis) - Consistent mention styling for all profiles - Profile mentions can now be clicked/hovered like manual mentions - Smaller code footprint (removed complex icon mapping logic) - Better UX: profiles look and behave like regular mentions Technical changes: - Paste handler creates mention nodes for npub/nprofile - NostrEventPreview only handles note/nevent/naddr now - Removed npub/nprofile from serialization (handled by mention serializer) - Updated type definitions to reflect reduced scope * feat(editor): add drag-and-drop and paste file upload support Adds comprehensive file upload support via drag-and-drop and paste directly into the chat composer editor. Changes to MentionEditor: - Add FileDropHandler TipTap extension to intercept file drops/pastes - Filter for valid media types (image/video/audio) - Add onFileDrop callback prop to communicate with parent - Handle both drag-and-drop and clipboard paste events Changes to useBlossomUpload hook: - Update open() method to accept optional File[] parameter - Add initialFiles state to track pre-selected files - Clear initialFiles when dialog closes - Pass initialFiles to BlossomUploadDialog component Changes to BlossomUploadDialog: - Add initialFiles prop for pre-selecting files - Auto-select first file when initialFiles provided - Generate preview URL for images/video on initialization - Seamless UX: dropped files immediately appear in dialog Changes to ChatViewer: - Wire up onFileDrop callback to open upload dialog - Pass dropped files to upload dialog via open(files) - Fix onClick handler to properly call openUpload() User experience: - Drag image/video/audio file onto chat composer → upload dialog opens - Paste image from clipboard → upload dialog opens - File automatically selected and previewed - Click upload to complete (same as manual file selection) Technical details: - Uses ProseMirror Plugin API for drop/paste interception - File type validation: /^(image|video|audio)\// - Bonus: Also works with clipboard paste (Ctrl+V) - Clean state management with automatic cleanup * fix(editor): add dragover handler to enable file drag-and-drop The drag-and-drop wasn't working because we need to handle dragover events and call preventDefault() to signal that drops are allowed. Changes: - Add handleDOMEvents with dragover handler to FileDropHandler - Check for Files type in dataTransfer - Set dropEffect to 'copy' for visual feedback - Call preventDefault() to allow drops Without this, the browser blocks all drops by default and handleDrop never fires. This is a standard requirement for HTML5 drag-and-drop API. * feat(editor): show usernames when pasting npub/nprofile mentions When pasting npub or nprofile URIs, the editor now looks up the profile from the event store cache and uses the display name if available. Changes: - Add getDisplayNameForPubkey helper function - Import eventStore and profile helpers - Synchronously check if profile is cached via .value property - Use getDisplayName from nostr-utils for consistent formatting - Falls back to short pubkey (first 8 chars) if profile not cached Result: - Paste npub1abc... → shows "@alice" if profile is cached - Paste npub1abc... → shows "@abc12345" if profile not cached - Consistent with typed @mentions behavior - No async delays or loading states needed * refactor(editor): remove drag-and-drop, keep paste file support Removes drag-and-drop file handling while keeping clipboard paste functionality. Drag-and-drop will be implemented differently later. Changes: - Rename FileDropHandler → FilePasteHandler - Remove handleDrop and dragover event handlers - Keep handlePaste for clipboard file support - Rename onFileDrop prop → onFilePaste for clarity - Update ChatViewer to use onFilePaste callback - Update all references and comments Result: - Paste images from clipboard (Ctrl+V) still works - Drag-and-drop removed completely - Clearer naming reflects actual functionality * refactor(editor): extract shared extensions and create React node views Phase 1 of RichEditor variant implementation: - Extract NostrPasteHandler extension for reuse across editors - Extract FilePasteHandler extension for clipboard file handling - Create NostrEventPreviewRich React node view using DetailKindRenderer - Create BlobAttachmentRich React node view with full-size media previews - Create rich TipTap node extensions using ReactNodeViewRenderer - Update MentionEditor to use shared extensions These shared components will be used by the upcoming RichEditor variant for long-form content composition with full previews. * feat(editor): add RichEditor component for long-form content Phase 2 of RichEditor variant implementation: - Create RichEditor component based on MentionEditor - No slash commands (removed SlashCommand extension) - Multi-line support with Enter for newlines - Ctrl/Cmd+Enter to submit - Full-size image/video previews using BlobAttachmentRich - Full event rendering using NostrEventPreviewRich - Configurable min/max height (defaults: 200px-600px) - Retains @mentions and :emoji: autocomplete - Reuses shared extensions (NostrPasteHandler, FilePasteHandler) - Targets kind 1 notes composition Key differences from MentionEditor: - Block-level rich previews instead of inline badges - Multi-line editing without Enter-to-submit - Resizable with overflow-y: auto - No command palette functionality * refactor(post): improve PostViewer layout and editor previews UI improvements: - Remove flex-1 from editor, use fixed min/max height - Move upload button next to publish button (icon-only) - Place relay selector below action buttons (not fixed) - Limit image/video previews to max-h-96 - Add pointer-events-none to event previews to prevent accidental interaction - Use EventDetailSkeleton for loading event previews Layout changes: - Single scrollable container with space-y-4 - Editor: 150-400px height range - Action buttons in single row (upload icon + publish button) - Relay list with max-h-64 scroll area - Better spacing and visual hierarchy * feat(post): add draft persistence and improve relay UI Draft persistence: - Save draft content to localStorage every 2 seconds - Load draft on component mount (per-user with pubkey) - Clear draft after successful publish - Prevent data loss on tab change or page reload UI improvements: - Reset button now icon-only (RotateCcw) with tooltip - Remove borders from relay list items - Use RelayLink component for relay rendering - Show relay icons and secure/insecure indicators - Cleaner, more compact relay list appearance - Increased spacing between relay items (space-y-1) Storage key format: grimoire-post-draft-{pubkey} * feat(post): enhance draft storage with full JSON state persistence Draft persistence improvements: - Store complete editor state as JSON (preserves blobs, emojis, mentions, formatting) - Save selected relays array in draft - Restore full editor content using setContent() method - Maintain draft per-user with pubkey-based key RichEditor enhancements: - Add getJSON() method to export editor state - Add setContent() method to restore from JSON - Enables lossless draft save/restore UI improvements: - Change "Relays" label to plain text without Label component - Keep unselected relays visible during/after publish - Only update status for selected relays during publish Draft storage format: { editorState: {...}, // Full TipTap JSON state selectedRelays: [...], // Array of selected relay URLs timestamp: 1234567890 } * feat(post): add nostr tag extraction, retry failed relays, and disable empty publish Nostr tag extraction: - Extract p tags from @mentions (pubkey references) - Extract e tags from note/nevent references (event ID references) - Extract a tags from naddr references (address/parameterized replaceable) - Update SerializedContent interface to include mentions, eventRefs, addressRefs - Serialize editor content walks all node types to extract references - Build complete tag array for kind 1 events with proper NIP compliance Retry failed relays: - Add retryRelay() function to republish to specific failed relay - Make error icon clickable with hover state - Show "Click to retry" in tooltip - Rebuild event and attempt publish again - Update status indicators in real-time Disable publish when empty: - Track isEditorEmpty state - Update every 2 seconds along with draft save - Disable publish button when editor isEmpty() - Prevents publishing empty notes Tag generation order: 1. p tags (mentions) 2. e tags (event references) 3. a tags (address references) 4. emoji tags (NIP-30) 5. imeta tags (NIP-92 blob attachments) This ensures proper Nostr event structure with all referenced pubkeys, events, and addresses tagged. * feat(post): add client tag and remove reset button Client tag: - Add ["client", "grimoire"] tag to all published events - Added to both handlePublish and retryRelay functions - Identifies posts as coming from Grimoire client UI cleanup: - Remove reset button from relay selection UI - Relay selection persists across sessions via draft storage - Simplify header to just show relay count - Remove unused RotateCcw icon import Tag generation order (final): 1. p tags (mentions) 2. e tags (event references) 3. a tags (address references) 4. client tag (grimoire) 5. emoji tags (NIP-30) 6. imeta tags (NIP-92) * fix(post): trim content before publishing Content trimming: - Trim content in handlePublish() before building event - Trim content in retryRelay() before building event - Removes leading and trailing whitespace from published notes - Prevents accidental whitespace-only or padded messages - Applied via content.trim() before factory.build() * fix(post): reuse signed event for retries instead of recreating Event reuse for retries: - Add lastPublishedEvent state to store the signed event - Store signed event after creation in handlePublish() - Simplify retryRelay() to republish the same signed event - Remove duplicate event creation and signing in retryRelay() - Remove duplicate client tag addition in retryRelay() Benefits: - Same event ID and signature across all relays - More efficient - no need to re-sign - Consistent event across retries - Single source of truth for published event Flow: 1. User publishes - event created, signed, stored 2. Some relays fail - event remains in state 3. User retries - same event republished to failed relay 4. New post overwrites lastPublishedEvent with new event * feat(post): add published event preview and reset button After successful publish, replace editor and action buttons with: - Kind1Renderer preview of the published event - Reset button to compose another post - Relay list remains visible with publish status indicators Users can click "Compose Another Post" to reset the form and return to the editor for the next post. * feat(post): improve layout and relay handling - Fix double scrollbar issue by using flexbox layout instead of overflow-auto - Disable relay checkboxes during preview mode (can't change after publish) - Use aggregator relays as fallback when no write relays configured - Remove conditional error message since fallback relays always available - Improve layout responsiveness with flex-1 and flex-shrink-0 classes Aggregator relays (nos.lol, relay.snort.social, relay.primal.net, relay.damus.io) ensure users can always publish even without configured write relays. * fix(editor): use feed renderer and skeleton for event previews When pasting event URLs in the editor, use KindRenderer (feed) and EventCardSkeleton instead of DetailKindRenderer and EventDetailSkeleton. Event previews in the editor are inline/compact like feed items, so they should use the feed renderer rather than the detail view renderer. * fix(post): remove flex-1 and improve scrolling layout - Change layout from flex-col with flex-1 to overflow-y-auto on outer container - Remove flex-1 from all child elements to prevent layout issues with complex posts - Hide scrollbar in RichEditor for cleaner appearance - Single scrollbar on container prevents double-scrollbar issues - Editor now truly full-width without extra padding This creates a more predictable layout that avoids overlaps and weird behavior when posts contain lots of content (images, videos, embedded events). * feat(post): add max-width constraint to content Add max-w-2xl (768px) with mx-auto to center and constrain post content width, similar to ZapWindow. Prevents content from becoming too wide on large screens while maintaining good readability. * fix(editor): add remove button and fix cursor positioning for event embeds - Add X button on hover to NostrEventPreviewRich (same pattern as BlobAttachmentRich) - Fix cursor positioning after pasting nevent/naddr by tracking node sizes correctly - Import TextSelection from prosemirror-state to properly set cursor position - Change from `tr.insert(from + index, node)` to `tr.insert(insertPos, node)` with cumulative position tracking Previously, cursor would stay behind the event embed node after pasting. Now it correctly positions after the inserted content. * feat(post): add discard button, settings dropdown, and improve button layout - Add Discard button to clear editor state and draft - Add Settings dropdown with client tag toggle (enabled by default) - Limit publish button width to w-32 and push to right with spacer - Conditionally include client tag based on setting - Improve action button layout with better spacing * feat(post): add relay connectivity status, input widget, and improve error handling - Show relay connectivity status (Server/ServerOff icons) next to each relay in list - Add input widget to add custom relays during post composition - Validates relay URLs (supports with/without wss:// prefix) - Normalizes URLs automatically (defaults to wss://) - Shows Plus button enabled only when input is valid - Press Enter or click Plus to add relay - Improve signing failure handling: draft is only cleared after successful publish - New relay input widget disabled during preview mode - Connect to relay pool state using use$() hook for reactive connectivity status Technical details: - Import Server, ServerOff, Plus icons and Input component - Add newRelayInput state for relay URL input - Use pool.relays$ observable to get real-time connection state - Create isValidRelayInput() validator with URL pattern matching - Create handleAddRelay() to normalize and add relays to list - Update relay list rendering to show connectivity icons - Add input + button widget after relay list * feat(post): add auth status icons and window-specific draft persistence - Show auth status icon next to connectivity icon for each relay - Uses getAuthIcon() utility from relay-status-utils - Displays shield icons for authenticated/failed/rejected states - Includes tooltip with auth status label - Make draft persistence window-specific using window ID - Draft key format: `grimoire-post-draft-{pubkey}-{windowId}` - Each post window maintains its own independent draft - Falls back to pubkey-only key when windowId not available - Remove toast notification when adding relays (less noisy UX) - Still shows error toast if relay URL is invalid - Pass windowId prop from WindowRenderer to PostViewer Technical details: - Import getAuthIcon from @/lib/relay-status-utils - Import useRelayState hook to get relay auth status - Add PostViewerProps interface with optional windowId - Update all draft key computations to include windowId - Update dependency arrays for useEffect/useCallback with windowId - Get relay state via getRelay(url) for auth icon display * feat(post): add global persistent settings and event JSON preview Settings (persist in localStorage, global across all post windows): - "Include client tag" - toggle whether to add client tag to events (default: true) - "Show event JSON" - display copyable JSON preview of event (default: false) Features: - Settings stored in localStorage at "grimoire-post-settings" - Settings persist across sessions and windows - Event JSON preview shows unsigned event initially, updates to signed version after signing - Preview displays "(Signed)" or "(Unsigned)" label - Uses CopyableJsonViewer component for syntax highlighting and copy button - Preview limited to max-h-64 with scrolling Draft persistence improvements: - Added relays now saved in draft state under "addedRelays" field - On draft load, custom relays are restored to relay list - Relays identified as "added" if not in user's write relay list Technical details: - Added PostSettings interface with includeClientTag and showEventJson fields - Settings loaded on mount from localStorage, saved on change via useEffect - updateSetting callback handles individual setting updates - previewEvent state holds current event (unsigned or signed) - setPreviewEvent called before and after signing in handlePublish - Draft format: { editorState, selectedRelays, addedRelays, timestamp } - Draft loading checks addedRelays and restores non-duplicate relays * feat(post): add live draft JSON preview with 2-second updates This commit enhances the POST command with a live JSON preview feature: - Added generateDraftEventJson() that builds unsigned draft event from editor content - JSON preview updates every 2 seconds when "Show event JSON" setting is enabled - Extracts all tags from editor: mentions (p), events (e), addresses (a), emojis, blobs (imeta) - Displays as "Event JSON (Draft - Unsigned)" to distinguish from signed events - Uses CopyableJsonViewer component for syntax highlighting and copying The live preview helps users understand event structure before publishing. * feat(post): improve JSON preview responsiveness and layout This commit addresses UX feedback on the JSON preview feature: - Reduced update interval from 2000ms to 200ms for much more responsive UI - Moved JSON preview below relay list for better visual flow - Increased max-height from 256px to 400px for better visibility - Separated JSON update logic into dedicated effect for cleaner code - JSON preview now feels instant when typing (10x faster updates) The JSON preview is now much more pleasant to use and doesn't feel laggy when editing content. * refactor(post): replace intervals with debounced onChange handlers This commit improves the code quality and UX of the POST command: **Debounced onChange Pattern:** - Added onChange prop to RichEditor that fires on every content change - Replaced interval-based updates with proper debounced handlers - Draft saves debounced to 2000ms (unchanged behavior, cleaner code) - JSON updates debounced to 200ms (same responsiveness, event-driven) - Much cleaner React pattern - no weird intervals **JSON Preview Improvements:** - Removed "Event JSON" title header for cleaner look - Shows signed event JSON when published and setting is enabled - Shows draft (unsigned) JSON when composing - Always scrollable up to 400px height **Technical Improvements:** - Added onUpdate handler to TipTap editor config - Proper cleanup of debounce timeouts on unmount - Type-safe timeout refs using ReturnType<typeof setTimeout> * refactor(post): remove JSON preview and ensure proper content serialization **Removed JSON preview feature:** - Removed showEventJson setting and all related UI - Removed draftEventJson state and generation logic - Removed "Show event JSON" checkbox from settings - Simplified PostViewer code by ~100 lines - Cleaner, production-ready codebase **Ensured proper content serialization:** - Added renderText() to Mention extension to serialize as nostr:npub URIs - Added renderText() to BlobAttachmentRichNode to serialize URLs - NostrEventPreviewRichNode already had renderText() for nostr:note/nevent/naddr - Editor now properly converts all rich content to plain text: - @mentions → nostr:npub... - Event references → nostr:note/nevent... - Address references → nostr:naddr... - Blob attachments → URLs - Custom emojis → :shortcode: **Result:** - Cleaner, simplified code ready for production - All editor elements properly serialize to content string - JSON preview can be re-added later with better implementation * perf(post): reduce draft save debounce from 2s to 500ms Makes the draft saving feel more responsive without hammering localStorage on every keystroke. * fix(post): preserve content when signing fails or is rejected Critical bug fix: Separate signing from publishing to handle failures correctly. **The Problem:** - When user rejected signing (or signing failed), we treated it as a publishing failure - Set all relay states to "error" even though we never tried to publish - Destroyed the user's post content **The Solution:** - Split signing and publishing into separate try-catch blocks - If signing fails: show toast, reset isPublishing, and RETURN EARLY - User keeps their content and can try again - Only update relay states if we actually attempted to publish **User Experience:** - User rejects signing → toast appears, post is preserved, can edit or try again - Publishing fails → relay states show errors, can retry individual relays - No more losing your carefully crafted post to a rejected signature! * refactor(post): use Promise.allSettled for relay publishing Use Promise.allSettled instead of Promise.all to wait for all relay publish attempts to complete, regardless of individual success/failure. More defensive and semantically correct since we're handling errors individually per relay and want all attempts to finish. * feat(post): use GRIMOIRE_CLIENT_TAG constant with NIP-89 app address Update PostViewer to use the new GRIMOIRE_CLIENT_TAG constant from main which includes the NIP-89 app definition address (31990) as the third element. Before: ["client", "grimoire"] After: ["client", "grimoire", "31990:..."] This allows Nostr clients to display a clickable link to Grimoire's app definition event. * fix(post): preserve content when publishing fails to all relays Critical UX improvement: Only clear draft and show preview if at least one relay succeeded. **The Problem:** - When publishing failed to ALL relays, we still cleared the draft - User lost their content with no way to retry - Frustrating experience when all relays are down **The Solution:** - Track success/failure by returning result from each publish promise - Count successful publishes from Promise.allSettled results - Only clear draft and show preview if successCount > 0 - Keep editor visible with content if all relays fail **User Experience:** - All relays succeed → Clear draft, show preview, success toast - Some relays succeed → Clear draft, show preview, warning toast - All relays fail → Keep editor with content, error toast, can retry **Toast Messages:** - All succeeded: "Published to all X relays" - Partial success: "Published to X of Y relays" - Total failure: "Failed to publish to any relay. Please check..." * fix(post): preserve content when signing or publishing Critical fix: Don't clear editor content until publish succeeds. **The Problem:** - RichEditor automatically cleared content on submit - Content was wiped immediately when clicking "Publish" - User lost their content before knowing if publish succeeded **The Solution:** - Remove automatic `clearContent()` from RichEditor's handleSubmit - Let PostViewer control when to clear the editor - Only clear editor after successful publish (successCount > 0) **User Experience Now:** - Click "Publish" → content stays visible while publishing - Publishing succeeds → content clears, show preview - Publishing fails → content stays, user can edit and retry - User rejects signing → content stays, can try again No more losing your post to a rejected signature or failed publish! --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
2987a37e65 | feat: spells | ||
|
|
8f80742ef1 | feat: tab names | ||
|
|
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> |
||
|
|
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> |
||
|
|
a066284825 | chore: petrify | ||
|
|
19cdde0110 | feat: syntax highlighting | ||
|
|
e5c871617e | chore: cleanup, a11y and state migrations | ||
|
|
ad17658ef3 | fix: contrast | ||
|
|
cd41034b2f | 👶 |