Commit Graph

270 Commits

Author SHA1 Message Date
shakespeare.diy 6f4e28c3df Move reply context above content in NoteCard
- Repositioned "Replying to @user" to appear between the header and content
- Matches Ditto's UI pattern for better visual hierarchy
- Hide reply context when post is a repost (repost takes priority)
- Added click event stop propagation to reply context link

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 20:28:58 -06:00
shakespeare.diy bc95237b7c Fix React error #300 when logging out
The error occurred because the AccountSwitcher component was returning null while the DropdownMenu was still mounted and open. This caused React to throw a minified error #300 about invalid elements.

The fix ensures the dropdown menu is closed before removing the login, preventing the component from being unmounted while still rendering its dropdown content.

Changes:
- Added controlled state for dropdown menu (isOpen/setIsOpen)
- Created handleLogout function that closes dropdown before removing login
- Used setTimeout to ensure dropdown closes before state update

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 20:12:35 -06:00
shakespeare.diy cb76fafc40 Fix quote posts being treated as replies
- Remove e tag with mention marker from quotes (was causing reply behavior)
- Only use q tag for NIP-18 quote reposts
- Automatically append nostr:nevent URI to quote content
- Quote posts now publish as standalone kind 1 events, not replies
- Reorganized imports for better readability

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 18:02:42 -06:00
shakespeare.diy ba43340e8c Fix quote posts being treated as replies
- When quotedEvent is present, don't pass event as replyTo in ComposeBox
- This ensures quote posts are published as kind 1 with q tags, not replies
- Repost (kind 6) was already working correctly

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 17:55:15 -06:00
shakespeare.diy fcc2ef606d Remove X button from quoted event embed in compose
- Quoted events from the quote button cannot be removed
- X button still available for detected embeds typed in content
- Provides clearer UX for intentional quote posts vs detected links

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 17:45:25 -06:00
shakespeare.diy 63580133c8 Add bottom margin to embeds in compose box
- Added mb-3 to embed containers for spacing before toolbar
- Ensures proper visual separation between embedded content and action buttons

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 17:44:06 -06:00
shakespeare.diy 72cf232ed5 Increase spacing between toolbar and embeds in compose box
- Changed embed margin from mt-2 to mt-4 for better visual separation
- Applies to both quoted events and detected embeds (nevent, naddr, links)

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 17:40:41 -06:00
shakespeare.diy 3f19edd7a1 Implement quote posts and removable embeds in compose box
- Added quotedEvent prop to ComposeBox for quote post support
- Auto-detect and display embeds: nevent, note, naddr, and link previews
- Add remove button (X) to clear any embed from compose
- Quote posts open ReplyComposeModal with quoted event embedded
- Updated RepostMenu to open quote dialog instead of toast
- Added NIP-18 quote tags (q tag) when publishing quotes
- Embeds shown at bottom of compose with EmbeddedNote/EmbeddedNaddr/LinkPreview
- Support for manually typed nostr: URIs in compose content
- Invalidate event stats after quote to update counts

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 17:39:18 -06:00
shakespeare.diy 5b71e44875 Add repost menu with desktop popover and mobile drawer
- Created RepostMenu component with two options: Repost and Quote post
- Shows as popover on desktop (above button, aligned left)
- Shows as drawer on mobile with Close button
- Integrated into NoteCard, PostDetailPage, and NotificationsPage
- Implements kind 6 repost functionality
- Quote post shows placeholder toast (to be implemented)
- Includes proper event invalidation to update stats after repost

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 17:34:35 -06:00
shakespeare.diy 8249b4dabf Fix inconsistent number sizes in action buttons and adjust spacing
- Changed PostDetailPage action button numbers from text-xs to text-sm for consistency with ReactionButton
- Added tabular-nums class to all number spans for proper alignment
- Adjusted gap between action buttons from gap-4 to gap-5 for better spacing
- All action button numbers now consistently use text-sm tabular-nums

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 17:29:54 -06:00
shakespeare.diy 9db517c12e Increase action button icon size to 20px and reduce spacing
- Changed all action button icons from size-[18px] (18px) to size-5 (20px)
- Updated icons in NoteCard, PostDetailPage, NotificationsPage, and ReactionButton
- Reduced gap between action buttons from gap-6 to gap-4 for tighter spacing
- Icons affected: MessageCircle, Repeat2, Heart, Zap, MoreHorizontal

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 17:27:27 -06:00
shakespeare.diy 5b783ba7d5 Hide zap button for users without lightning addresses
- Created canZap() utility function to check if a user has lud16 or lud06
- Updated ZapButton component to use the new helper
- Conditionally render zap button in NoteCard when user can be zapped
- Conditionally render zap button in PostDetailPage when user can be zapped
- Conditionally render zap button in NotificationsPage ActionButtons when user can be zapped
- Updated ProfilePage to use canZap() helper for consistency
- Zap icon now only shows for users who can actually receive zaps

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 17:23:06 -06:00
shakespeare.diy 45d1f878f4 Prevent dialog and drawer overlay clicks from bubbling to parent elements
Add stopPropagation to dialog and drawer overlay/content click handlers to prevent clicks on modals from triggering click events on underlying elements like NoteCard.

This fixes the issue where clicking outside the zap modal would still register as a note click and navigate to the post detail page.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 17:19:04 -06:00
shakespeare.diy 06bcf8ad0e Fix modal close events triggering post navigation
Prevent clicks from closing modals (image gallery, zap dialog, etc.) from bubbling up to NoteCard and triggering unwanted navigation to post details.

Changes:
- Added handleCardClick in NoteCard to check if click originated from modal/dialog elements
- Updated ImageGallery to stop propagation when closing lightbox via backdrop or close button
- Enhanced event handling for download button in lightbox
- Prevents navigation when clicking on dialog overlays, drawer overlays, or modal content

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 17:11:23 -06:00
shakespeare.diy 4f2b50c189 Add fallback to use first p tag for reply context
When a note is a reply (has e tags) but all p tags are marked as mentions, we now fall back to using the first p tag for the reply context. This handles clients that strictly follow NIP-10 and mark all p tags with appropriate markers.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 16:57:30 -06:00
shakespeare.diy 603b2018cc Fix reply detection to use e tag pubkey as fallback
When a note has e tags with root/reply markers but the p tags use mention markers, we now fall back to using the pubkey from the e tag (5th element) to determine who is being replied to. This properly handles replies that follow NIP-10 strictly and mark mentions explicitly.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 16:54:38 -06:00
shakespeare.diy a288c28f0e Add rootMargin to infinite scroll trigger
Added rootMargin: '400px' to the intersection observer so it triggers 400px before reaching the bottom. This prevents missing the trigger when scrolling fast and ensures a smoother experience by pre-loading the next page.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 16:40:23 -06:00
shakespeare.diy a0be00b8b1 Auto-fetch page 2 when page 1 loads
Added back the auto-fetch effect that loads page 2 immediately after page 1 finishes loading. This ensures users always have 2 pages of content loaded, and then the infinite scroll ref at the bottom handles loading page 3 and beyond.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 16:17:14 -06:00
shakespeare.diy b95005be35 Simplify infinite scroll using skill pattern
Replaced the complex PageBoundary system with the simpler nostr-infinite-scroll skill pattern:
- Single intersection observer ref at the bottom of the feed
- Simplified deduplication logic
- Removed unnecessary pageItems mapping
- Cleaner, more maintainable code that matches proven patterns

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 16:15:22 -06:00
shakespeare.diy a297ebae0a Add rootMargin to PageBoundary intersection observer
Added rootMargin: '400px' to the useInView hook in PageBoundary to trigger loading the next page 400px before the boundary comes into view. This matches Agora's implementation and ensures smoother infinite scroll behavior.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 16:14:08 -06:00
shakespeare.diy 2bc84b9928 Fix missing useInView import
Re-added the useInView import from react-intersection-observer that was accidentally removed in the previous commit.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 16:13:01 -06:00
shakespeare.diy 51b9ab7dc0 Fix infinite scroll by placing PageBoundary at bottom of pages
The infinite scroll was broken because the PageBoundary component was placed at the top of each page but required pageIndex > 0 and pageIndex === totalPages - 1, which would never trigger correctly.

Changed:
- Moved PageBoundary to the bottom of each page instead of the top
- Removed the pageIndex > 0 condition (no longer needed)
- Updated comments to reflect the new bottom placement

Now when you scroll to the bottom of page 0, it triggers loading page 1. When you reach the bottom of page 1, it triggers page 2, and so on.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 16:12:05 -06:00
shakespeare.diy c0bf2005a7 Fix toggle race condition by tracking synced timestamp
The toggle was still reverting because the NostrSync component was
re-syncing old encrypted settings whenever the query refetched.

Root cause:
- When toggle is clicked, it updates config and saves to Nostr
- The encrypted settings query refetches the data
- NostrSync effect runs again with the refetched (old) data
- This overwrites the just-updated local config

Solution:
- Added lastSyncedTimestamp ref to track which settings we've synced
- Only sync if the remote timestamp is newer than what we've synced
- Changed updateSettings call from mutateAsync to mutate (non-blocking)
- This prevents re-syncing the same settings multiple times

The toggle now works correctly without reverting.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 16:05:30 -06:00
shakespeare.diy b8471261a8 Fix toggle state issue in RelayListManager
The app relays toggle was reverting on first click due to a race condition
between local state and the config context. The issue was caused by:

1. Local state (useAppRelays) was being synced via useEffect
2. When toggle was clicked, both local state and config were updated
3. The useEffect re-ran and reset the local state from config
4. This created a race condition causing the toggle to appear to undo itself

Solution:
- Removed local useAppRelays state entirely
- Use config.useAppRelays directly from context
- Removed the useEffect dependency on config.useAppRelays
- Now only config context manages this state, eliminating the race condition

The toggle now works correctly on first click.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 15:50:47 -06:00
shakespeare.diy 229ee787d8 Fix infinite scroll to trigger on page boundary instead of pixel-based detection
- Remove `rootMargin: '800px'` from Feed.tsx PageBoundary component
- Add explicit `threshold: 0` to both Feed.tsx and ProfilePage.tsx
- Infinite scroll now triggers as soon as the page boundary enters viewport
- Fixes issue where short windows would break infinite scroll detection

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 15:46:15 -06:00
shakespeare.diy 03d5310ddd Revert to cb819e8: Use app relays for loser's race instead of separate profile relays
This reverts the codebase back to the state at commit cb819e8.

Reverted 2 commit(s):
- e03527b: Fix infinite scroll race condition and add relay logging
- 0635779: Move relay fetching inside queryFn and add debug logging

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 15:44:15 -06:00
shakespeare.diy e03527ba78 Fix infinite scroll race condition and add relay logging
Infinite scroll fixes:
- Allow boundaries within 2 pages of end to trigger (not just last page)
- Prevents race where scrolling fast skips page boundaries
- Reduced rootMargin from 800px to 400px for more controlled loading
- Pages can trigger next load even if not technically "last" yet

Profile loading debug:
- Log which relays are being queried in loser's race
- Helps debug why profiles aren't loading (missing relays like nos.lol)

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 15:43:23 -06:00
shakespeare.diy 7e56b749f5 Batch prefetch "replying to" profiles in Feed
- Extract reply-to pubkeys from p tags in kind 1 events
- Add them to feedPubkeys for batch prefetching
- Prevents placeholder names in "Replying to @..." context
- Only affects kind 1 (text notes) with reply markers
- Profiles now load immediately from cache in ReplyContext component

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 15:29:26 -06:00
shakespeare.diy 2d563af655 Fix scroll boundaries: trigger at page top, not bottom
- Moved PageBoundary to the TOP of each page instead of bottom
- When you scroll to page 2, it triggers page 3 to load
- Increased rootMargin to 800px for earlier prefetching
- Separated loading spinner to bottom (only shows during fetch)
- Now you should rarely see the spinner unless network is slow

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 15:22:43 -06:00
shakespeare.diy b638123def Fix infinite scroll: auto-load page 2 and add per-page boundaries
- Auto-fetch page 2 immediately when page 1 loads
- Add PageBoundary component for each page with intersection observer
- Each page's boundary triggers the next page load when reached
- Added 400px rootMargin for smoother prefetching
- Improved loading indicator placement (only shows on last page)
- Maintains deduplication logic per page before flattening

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 15:19:21 -06:00
shakespeare.diy bde81e198f Fix infinite scroll: replace useInView with raw IntersectionObserver + ref pattern
The previous useInView approach had two failure modes:
1. rootMargin is ignored by some browsers when inside an iframe
2. React effects only fire on dep *changes* — if inView stayed true the
   whole time (sentinel already visible), subsequent pages never loaded

New approach:
- Raw IntersectionObserver with refs that stay fresh without stale closures
- Secondary useEffect triggers fetchNextPage when fetching completes and
  the sentinel is still within 800px of the viewport, covering the case
  where fetching was in progress when the sentinel first intersected
- Applied to both Feed.tsx and ProfilePage.tsx
- Removed react-intersection-observer dependency from ProfilePage

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 14:52:23 -06:00
shakespeare.diy 12c16b5b90 Fix infinite scroll: always render sentinel, increase rootMargin to 800px
- Remove `hasNextPage &&` conditional wrapping the sentinel div so it stays
  in the DOM at all times — this fixes the case where a tall post sits at
  the bottom and the sentinel is already in view when it first mounts,
  causing IntersectionObserver to miss the trigger
- Increase rootMargin from 400px → 800px so the next page loads well
  before the user reaches the bottom
- Apply both fixes to Feed.tsx and ProfilePage.tsx

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 14:41:48 -06:00
shakespeare.diy f9f2ba72f4 Revert to 9752099: fix: early sentinel trigger + short author prefetch timeout
This reverts the codebase back to the state at commit 9752099.

Reverted 2 commit(s):
- 01d9df8: fix: follow-up grouped query for orphaned authors after first prefetch
- 3993971: fix: move prefetch out of queryFn into useEffect — zero pagination blocking

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 10:47:44 -06:00
shakespeare.diy 399397117a fix: move prefetch out of queryFn into useEffect — zero pagination blocking
The queryFn now only fetches feed events and returns them immediately.
Author and stats prefetching moves to a useEffect in Feed.tsx that fires
after each render where feedItems changes.

The 400px early sentinel means the next page starts fetching while the
user is still reading. The useEffect prefetch runs concurrently — by the
time the user scrolls to new cards, authors are likely already cached.
Cards that miss the window fall back to individual useAuthor queries.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 10:43:04 -06:00
shakespeare.diy 97520999c0 fix: early sentinel trigger + short author prefetch timeout
- Feed sentinel now fires 400px before the bottom (rootMargin: '400px')
  so the next page fetch starts while the user is still reading, hiding
  the latency entirely in most cases

- Author prefetch timeout cut from 5000ms to 1500ms — relay responds
  well within that window for cached/known profiles; cards whose authors
  miss the deadline fall back to individual useAuthor queries rather than
  blocking the whole page render

- Stats prefetch reverted to fire-and-forget — not worth blocking on,
  useEventStats per-card handles misses with its own skeleton state

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 10:38:50 -06:00
shakespeare.diy fba9a33c5c fix: stop retry storm from blocking pagination; fix scroll sentinel guard
Two bugs:

1. Pagination stalling at page 2: batcher was rejecting promises for
   pubkeys not found by the relay (retry: 2, delay: 1s). With 15+ posts
   each having p-tag mentions, a single page could generate 20-40 retry
   queries firing over 2 seconds. These flooded the relay's concurrent
   subscription slots, causing the page 3 feed query to time out. The
   8-second timeout would expire, queryFn would throw, and getNextPageParam
   would return undefined — stopping pagination permanently.
   Fix: resolve with {} for missing profiles (show generated name, no retry).
   Only seed real profile data into cache; missing profiles stay uncached
   so they can be fetched if encountered again.

2. Scroll sentinel used isFetchingNextPage as the guard, which is false
   during background refetchInterval refetches. If the sentinel was in
   view during a 60s background refetch, fetchNextPage() fired while the
   refetch was running, potentially causing a double-fetch or being silently
   dropped. Now guards on isFetching (true for any in-flight query).

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 10:27:28 -06:00
shakespeare.diy a44c6e85eb fix: batch useAuthor queries per tick; show stats skeletons while loading
useAuthor batcher:
- All useAuthor() calls in the same JS tick now coalesce into a single
  relay query instead of each opening their own subscription. With 20
  cards mounting simultaneously (post authors, repost headers, reply-to
  lines, @mentions), this reduces N round trips to 1. The batcher uses
  a Promise.resolve() microtask flush so it captures all calls from the
  same render cycle before firing.
- TanStack Query cache is seeded per-pubkey so subsequent renders hit
  cache as normal. retry: false since the batcher handles retries.

Stats skeletons:
- While useEventStats is loading, show small skeleton placeholders next
  to each action button (reply, repost, reaction, zap) instead of blank
  space. Gives the user feedback that counts are incoming.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 10:15:14 -06:00
shakespeare.diy 1dfe209690 fix: move author/stats prefetch inside useFeed queryFn to eliminate race
Previously the feed had two separate systems (batch hooks in Feed.tsx +
individual hooks in NoteCard) that raced each other. The batch ran as a
useQuery at the component level, but NoteCards mounted in the same render
cycle and fired their own per-card queries before the batch resolved.
This got worse with each page as both systems scaled up.

The fix: prefetch authors and stats *inside* useFeed's queryFn, after the
feed events are fetched but before the queryFn returns. By the time React
re-renders with the new page data, the ['author', pubkey] and
['event-stats', id] caches are already populated. NoteCard's individual
useAuthor and useEventStats hooks then resolve instantly from cache
without firing any extra network requests.

New page loads only fetch authors/stats for IDs not already in cache, so
page 1's data is never re-requested when page 2 loads.

- Remove useAuthors + useBatchEventStats calls from Feed.tsx entirely
- Export computePageStats from useTrending for use in useFeed
- useBatchEventStats / useAuthors still exist for non-feed use cases

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 09:58:16 -06:00
shakespeare.diy 0d8176834a fix: proper mobile action bar layout and pagination stats/author stability
Action bar (NoteCard):
- Left-aligned buttons with consistent ml-2 spacing between them
- More button pushed to the far right with ml-auto
- Stat counts use text-xs so they never blow out the row width
- No artificial max-width cap — works naturally on any screen size

Pagination stats/authors (useAuthors + useBatchEventStats):
- Both batch hooks now filter out already-cached IDs before building
  the query key and firing the request
- When page 2 loads, only genuinely new pubkeys/event IDs are fetched;
  page 1's data is untouched in cache
- The query key stabilises to an empty string once all items are cached,
  so no more cascading re-fetches as the feed grows
- useAuthor: return {} instead of throwing when no kind-0 event is found,
  eliminating the retry storm when many cards mount simultaneously

Also removes the usePageBatch hack introduced in the previous attempt.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 09:49:34 -06:00
shakespeare.diy f1778babb6 fix: mobile action bar layout + infinite scroll stats/author drop
- NoteCard: replace fixed gap-6 action bar with justify-between + max-w-xs
  on mobile so stats never overflow or wrap on narrow screens

- usePageBatch: new hook that batches author/stats prefetches per page
  instead of across the whole accumulated feed. The old approach used a
  single query key built from ALL loaded event IDs; adding page 2 changed
  the key, abandoned the old cache entry, and fired a fresh request for
  every item — causing stats and author data to visually disappear on
  pages 2 and 3. Now each page gets its own stable cache entry that is
  never invalidated by subsequent page loads.

- Feed: swap useAuthors + useBatchEventStats calls for usePageBatch

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 09:40:18 -06:00
shakespeare.diy e3b8a65fce Fix abort signal handling for nostrconnect to prevent immediate timeout
- Add optional abortSignal parameter to nostrconnect method
- Pass abort controller signal from LoginDialog to nostrconnect
- Properly handle AbortError when dialog is closed
- Prevent "signal has been aborted" error on dialog close
- Use custom abort controller instead of only relying on timeout signal

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 04:35:13 -06:00
shakespeare.diy 606f75417b Move remote signer option under More Options alongside secret key
- Change tab order: Secret Key first, Remote Signer second
- Remove "Use remote signer" standalone button
- Add clickable "More Options" button that expands collapsible
- Remote signer is now at the same level as secret key in the tabs
- Don't auto-generate nostrconnect session on dialog open

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 04:33:51 -06:00
shakespeare.diy f0728d1737 Implement remote signer login option with nostrconnect
- Add nostrconnect method to useLoginActions for NIP-46 client-initiated connections
- Add helper functions generateNostrConnectParams and generateNostrConnectURI
- Create QRCodeCanvas component wrapper for displaying QR codes
- Update LoginDialog to support remote signer login with QR code scanning
- Add "Use remote signer" button when extension is available
- Display QR code on desktop, "Open Signer App" button on mobile
- Support manual bunker URI input as fallback option
- Add connection retry on error
- Use existing useIsMobile hook for responsive behavior

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 04:31:51 -06:00
shakespeare.diy 3c4c84d255 Fix URL parsing with trailing punctuation and preserve whitespace formatting
- Strip trailing punctuation from URLs (.,;:!?)])  that are likely not part of the URL
- Fixes issue where (https://example.com) was parsed incorrectly
- Preserve newlines and formatting after embedded content
- Only collapse excessive whitespace (3+ newlines) instead of removing all whitespace
- Maintains proper text flow after link previews and embedded events

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 04:27:33 -06:00
shakespeare.diy 973064af99 Fix whitespace handling for naddr URLs with embedded events
- Preserve one newline after naddr-embed tokens when present
- Prevents text from flowing directly after URL without spacing
- Embedded event now properly displays with preserved formatting

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 04:25:38 -06:00
shakespeare.diy 724663f7dd Support mentions in all contexts including inside quotes and parentheses
- Removed lookahead that was preventing matches in certain contexts
- NIP-19 identifiers now render as mentions regardless of surrounding characters
- Maintains single @ display by consuming optional @ prefix

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 04:23:08 -06:00
shakespeare.diy d3d98a00bf Fix double @@ rendering for mentions and support mentions inside quotes
- Updated regex to optionally match @ before NIP-19 identifiers
- Removed word boundary requirement to allow mentions in quotes
- Mentions now render correctly with single @ symbol in all contexts

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 04:22:04 -06:00
shakespeare.diy 625dd91016 Adjust ComposeBox styling: increase avatar size and add text opacity
- Change avatar from size-11 to size-12 for better visual balance
- Increase textarea top padding from pt-2 to pt-2.5
- Add opacity-85 to textarea text for softer appearance

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 04:20:42 -06:00
shakespeare.diy bac9609b0d Right sidebars pt-5 → pt-4
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 04:12:04 -06:00
shakespeare.diy 26508210f7 Right sidebars pt-6 → pt-5
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-02-18 04:11:30 -06:00