Commit Graph

4447 Commits

Author SHA1 Message Date
Alex Gleason 5c2c35130f Show a grand success screen after a successful zap
Previously, a successful send from the Zap dialog auto-closed and surfaced a
toast. That undersold what just happened — the user sent Bitcoin. Now both
rails (on-chain + Lightning) flip the dialog over to a dedicated success
screen with an animated check, amount, recipient card, rail indicator, and
(for on-chain) a "View transaction" link to mempool.space. The dialog
auto-dismisses after six seconds if the user walks away.
2026-05-07 14:55:57 -07:00
Patrick PReis da9f88d181 Add close (X) button to blobbi switcher popover 2026-05-07 15:36:01 -03:00
filemon 59929e9c4d Fix stale-write in dev editor by fetching fresh 31124 before mutation
The dev editor read tags and content from the TanStack Query cache
(companion.allTags / companion.event.content) and published without
prev, risking overwrite of concurrent changes (e.g. social
consolidation advancing the checkpoint on another device).

Apply the standard read-modify-write pattern: fetchFreshEvent before
merge, use prev.tags/content as the base, and pass prev to
publishEvent so published_at is preserved.
2026-05-06 23:12:25 -03:00
Patrick PReis 55f8d946f9 Add switch-blobbi button to BlobbiWidget
Users with multiple Blobbis can now change which one is displayed in the
widget without navigating to the full Blobbi page. A new ArrowLeftRight
icon appears below the companion (Footprints) button and opens a popover
with all available Blobbis for quick selection.
2026-05-06 23:04:04 -03:00
filemon e3127e8555 Fix stale 6-hour window in social interaction queries
Move effectiveSince computation from useMemo into queryFn so
Date.now() is evaluated fresh on each TanStack Query refetch.
Previously, long-lived pages froze the window floor at mount time,
causing interactions from friends to go undetected after hours.

Also document the intentional boost→feed reaction animation reuse.
2026-05-06 22:58:33 -03:00
filemon 478f53177e Bound social care history and simplify Blobbi social consolidation
- Gate social actions by projected stat thresholds (< 70) so visitors
      can only help stats in visual distress
    - Add energy category with Energy Drink and Power Nap Pillow items
    - Apply 6-hour recency window to interaction queries (limit 30)
    - Fix BlobbiActionsProvider tree placement so BlobbiPage shares context
      with the companion layer
    - Preserve event content in dev editor (don't overwrite checkpoint JSON)
    - Show Needs Now summary in activity tab with priority badges
    - Remove unused need-driven consolidation infrastructure

   Regression-of: 9aecefff
2026-05-06 22:27:50 -03:00
Alex Gleason 9090ecfa2b Use APP_RELAYS as nostrconnect fallback
When the user has no NIP-65 write relays configured, the nostrconnect://
URI was built with a single relay (wss://relay.ditto.pub). Fall back to
the full APP_RELAYS write list instead so the remote signer has more
connection options during the handshake.
2026-05-05 23:03:32 -05:00
Alex Gleason 3555cbcf99 Pare down Lightning zap buttons and drop sats from the display
The Send and Pay-with-WebLN buttons no longer carry a lightning-bolt icon; the on-chain tab's primary button is text-only too, so this brings the flow in line. Sats are no longer shown anywhere on the Lightning screens — the USD amount is the whole story as far as the user is concerned, and the sats figure is just an implementation detail the LNURL flow handles.

Presets drop from $1/$5/$10/$25/$100 to $0.10/$0.50/$1/$2/$5 and the default amount moves from $5 to $0.50. Lightning zaps are tip-jar-shaped, not dinner-shaped — the on-chain tab can stay where it is because a fixed-fee on-chain send doesn't make sense below a few dollars.
2026-05-05 20:07:10 -05:00
Alex Gleason 9a08c6e488 Align Lightning zap dialog with on-chain design
Both tabs now lead with a big clickable USD amount and the same //// preset row, with the sats figure relegated to a small secondary line. Dropped the Lightning-only comment textarea and emoji picker — the on-chain flow never had them and keeping two different preambles made the tabs feel like different products. Preset buttons are shorter (h-8) in both tabs.

The invoice screen now shows USD primary / sats secondary in the header and renders the QR through QRCodeCanvas instead of a data-URL img, matching the on-chain fallback's QR styling. Large-amount two-tap confirmation (>= $100) now applies to Lightning too.
2026-05-05 20:01:05 -05:00
Alex Gleason b1c49c06a3 Surface insufficient-balance state directly on the zap amount + button
When the requested amount plus fee exceeds the user's balance, the
big dollar readout and send button both go destructive-red, the
button text becomes 'Not enough Bitcoin' and disables, and the
'Balance: $X' footnote in the fee row is hidden. The zero-balance
case ("you don't have any Bitcoin yet") still shows the balance line
so users understand why send is disabled.
2026-05-05 19:42:36 -05:00
Alex Gleason 9b81175e85 Strip sats subtitle, comment, and fee icon from on-chain zap dialog
The dialog collapses to: amount, presets, Send button, fee. Removes
the sats-per-amount subtitle under the big number, the collapsible
'Add a comment' accordion, and the gauge icon next to the fee line.

Comments ride along as empty strings on the zap payload, so the
backing 8333 publish still works, it just doesn't carry user text.
2026-05-05 19:39:46 -05:00
Alex Gleason 29fa317689 Polish on-chain zap dialog microcopy and layout
- Center the 'Add a comment' accordion toggle and make its textarea
  span the full width when opened.
- Tighten the 'How does sending Bitcoin work?' FAQ: leads with the
  Nostr-key-as-wallet framing, drops the extra paragraph about
  balances, and keeps the fee and public/irreversible points concise.
- Move the fee/balance line below the Send button so it reads like a
  footnote under the primary action instead of competing with it.
2026-05-05 19:38:24 -05:00
Alex Gleason 7be92b8eec Simplify on-chain zap dialog
Redesign the on-chain flow around a sleek 'Send $X' experience: click
the big number to edit, presets sit underneath, comment collapses behind
a chevron, recipient address and OR divider are gone, the send button
just says 'Send $5.20' with the fee included, and fee speeds are
deduplicated so duplicate sat/vB tiers don't repeat.

The fee speed now auto-adjusts when the amount changes to keep the fee
below 40% of the send amount — once the user manually picks a speed,
auto-adjustment is disabled for the session.

Dialog framing switches from 'Send a Zap' to 'Send Bitcoin', the
redundant description line is dropped, and the (?) popover routes to
one of two new tab-specific FAQ entries (send-bitcoin-onchain or
send-bitcoin-lightning).
2026-05-05 19:35:52 -05:00
Alex Gleason 3caad76477 Improve icon contrast on highlighted search dropdown items
Nav items and the 'Search for...' entry in the autocomplete dropdown had
primary-colored icons on a primary-tinted circle, which lost contrast
against the primary-tinted row background when highlighted. Swap both to
accent-foreground tones when the row is selected.
2026-05-05 19:23:38 -05:00
Alex Gleason a6bfd2cb68 Add Birdex chorus button
New BirdexChorusButton plays every species' Wikipedia recording at once
— a dawn chorus for the whole life list. Hides itself when no species
has usable audio, and the feed-card variant swallows clicks so toggling
playback doesn't navigate away from the NoteCard.
2026-05-05 17:24:57 -05:00
Alex Gleason df38cfdbca Remove share button from PostActionBar
Share action is still available from the NoteMoreMenu. The inline button
in the action bar is redundant and adds visual noise to the row.
2026-05-05 17:24:49 -05:00
Alex Gleason 234d3a21a3 Support NIP-84 Highlight events (kind 9802)
Render highlight excerpts as pull-quotes with source attribution across
feed cards, detail pages, and quote embeds. Without this, kind 9802
events fell through to UnknownKindContent in cards and — worse — had
their quoted prose fed through the kind-1 tokenizer in embeds,
auto-linkifying URLs and hashtags that were part of the original source,
not the highlight author's post.

Integration points:
- HighlightContent renders the excerpt with an accent blockquote, wraps
  the highlighted span in <mark> when a context tag is present, and
  attributes the source via EmbeddedNaddr (a-tag), EmbeddedNote
  (e-tag), or a sanitized URL chip (r-tag).
- EmbeddedHighlightCard gives quoted highlights a dedicated compact
  card instead of the generic-embed fallback.
- Added 9802 to NoteCard + PostDetailPage dispatch, KIND_HEADER_MAP,
  CommentContext labels/icons, NOTIFICATION_KIND_NOUNS, and the
  EmbeddedNote dispatcher.
- Registered an EXTRA_KINDS entry with feedIncludeHighlights (off by
  default) and showHighlights (on), plus a /highlights route backed by
  KindFeedPage.
- Added a highlights notification type with its own subscription
  template, preference toggle, grouped notification row, and
  author-ownership filter so users are only notified when their own
  content is highlighted.
- feedUtils hides empty highlights with no source reference.
2026-05-05 17:24:42 -05:00
Alex Gleason 64a4643503 Merge branch 'main' of gitlab.com:soapbox-pub/ditto 2026-05-05 16:11:00 -05:00
Alex Gleason c7e0234896 Unify /i/ external content page UI
Three inconsistencies between /i/ pages cleaned up:

* Action bar now always renders three universal interaction buttons
  (Comment, React, Share) with the Comment button opening the
  compose modal and showing the top-level comment count. ISBN keeps
  its Write Review (star) button as a fourth. Previously Bitcoin
  and other non-book identifiers showed only React + Share.

* Book content tabs (Comments / Reviews) switched from the flat
  shadcn Tabs primitive to SubHeaderBar + TabButton so the curved
  arc styling matches Feed, Profile, Search, and every other tabbed
  page in the app.

* Extracted ExternalCommentsSection — the inline ComposeBox +
  threaded list + loading/empty states are now shared between the
  default single-list layout and the ISBN Comments tab instead of
  duplicated.
2026-05-05 16:00:41 -05:00
Alex Gleason c69aee40a2 Add bottom border to ComposeBox by default
Every context except the Feed composer follows the compose box with
content that needs visual separation (comment lists, wall posts,
threaded replies), so add a default border-b and let Feed opt out with
a new hideBorder prop — it sits directly above SubHeaderBar's arc
background, which already provides its own separator.

Modal usage (forceExpanded) stays borderless since it lives inside
a dialog container.
2026-05-05 15:49:55 -05:00
Alex Gleason 908c5b248c Enable commenting on NIP-73 identifier /i/ pages
ExternalContentPage already supported fetching comments for non-URL
external roots (bitcoin:tx:..., isbn:..., iso3166:..., etc.), but the
inline ComposeBox and the FAB's ReplyComposeModal were both gated on
the URL-only commentRootUrl — leaving those pages with no way to post.

Widen ComposeBox's replyTo and ReplyComposeModal's event to accept
`#${string}` NIP-73 identifiers alongside URLs and events, route them
through the existing NIP-22 publish path (usePostComment already
handled string roots), and wire the page up to the combined
commentRoot.
2026-05-05 15:46:30 -05:00
Alex Gleason 546b1aff9b Let shield icon inherit hover color from button
text-primary was applied directly to the Shield icon, overriding the
ghost button's hover:text-accent-foreground. On hover the background
turned into accent while the icon stayed primary, producing a
low-contrast pairing. Move text-primary up to the Button so the
ghost variant's hover rule can take over, and let the icon inherit
currentColor.
2026-05-05 15:01:13 -05:00
Chad Curtis 9aecefff40 Merge branch 'feat/blobbi-1124-interactions' into 'main'
Add Blobbi social interactions (kind 1124)

Closes #265

See merge request soapbox-pub/ditto!211
2026-05-05 19:11:09 +00:00
Chad Curtis a9ff5c43f0 Merge branch 'fix-blobbi-sleepy-eyes' into 'main'
Fix sleepy eye animation breaking when same Blobbi rendered in multiple places

Closes #272

See merge request soapbox-pub/ditto!217
2026-05-05 19:09:58 +00:00
Alex Gleason d34a155922 Simplify permission manager popover
- Remove the app-name subtitle under "Permissions"; the site is already
  identified by the nav bar directly above the popover.
- Drop the trash icon from the "Revoke all" button so the destructive
  action reads as plain text like the row items.
- Remove the per-row allow/deny toggle. Stored permissions are always
  "allowed" in practice (a denied prompt doesn't surface a row the user
  would want to keep around), so the toggle added noise without a
  realistic use case. Users who change their mind can remove the row and
  re-prompt.
- Drop the status check/X icon on the left of each row now that the
  toggle is gone and every listed permission is implicitly allowed.
- Show the remove button always (was opacity-0 until hover) and switch
  it from a trash icon to an X, matching the close-affordance idiom used
  elsewhere in the app.
- Drop the siteName prop from NsitePermissionManager; nothing uses it
  anymore.
2026-05-05 14:00:28 -05:00
Alex Gleason c2c5b5c3be Drop target-pubkey from nsite encryption/decryption prompts
The target pubkey was rendered as a truncated hex string in the prompt,
which is noise to the user: they can't verify it, it doesn't scope the
stored permission (which is global-to-the-app), and showing a pubkey
next to "Allow" misleadingly suggests the decision applies only to that
peer. Drop the field from NsitePromptState and stop threading it through
the four encrypt/decrypt RPC branches.
2026-05-05 13:57:15 -05:00
Alex Gleason 5e729f74cd Grant broad permissions-policy and fix nav-bar safe-area spacing on sandbox frames
Sandbox iframes live on a cross-origin subdomain, so most capability APIs
are blocked unless the parent delegates them with allow=. Add a permissive
policy covering media (camera, microphone, display-capture, encrypted-media,
picture-in-picture, autoplay, speaker-selection), sensors (accelerometer,
gyroscope, magnetometer, ambient-light-sensor, compute-pressure, battery),
input (gamepad, midi, keyboard-map, xr-spatial-tracking), and UX features
(fullscreen, geolocation, idle-detection, screen-wake-lock, clipboard-write,
web-share, window-management, storage-access) so nsites and webxdc apps can
use anything a regular web app would.

Deliberately omitted for security: payment, publickey-credentials-*,
otp-credentials, identity-credentials-get (all phishing/account-takeover
vectors), local-fonts (fingerprinting), bluetooth/hid/serial/usb/
clipboard-read (raw device access left off for now).

Drop WebxdcIframe's narrow allow= override so webxdc apps get the same
broad policy instead of downgrading to just autoplay/fullscreen/gamepad.

Also split safe-area-top from content padding in the NsitePreviewDialog
and WebxdcEmbed nav bars: the outer element reserves space for the
notch inset, the inner row keeps a fixed px-3 py-2 flex layout so
content stays vertically centered inside the intended 44px bar height.
2026-05-05 12:52:49 -05:00
Alex Gleason 9e6ed02ce1 Add nsite:// sidebar pinning with auto-launch, favicon, and highlight
Introduces a new sidebar item type for nsites that auto-opens the nsite
preview when clicked, using React Router state to prevent external URLs
from triggering auto-launch.

- Add isNsiteUri/nsiteUriToSubdomain helpers and parseNsiteSubdomain
- Create NsiteSidebarItem with site favicon and link preview title label
- Wire nsite:// dispatch in SidebarNavList, useFeedSettings, SidebarMoreMenu
- NoteMoreMenu pins named nsite events as nsite:// URIs instead of nostr:
- NsiteCard gains a Pin/Unpin button and an autoPlayKey prop that re-opens
  the player each time the sidebar item is clicked
- NsitePlayerContext tracks the active subdomain for sidebar highlighting,
  provided in MainLayout so sidebar and pages share state
- PostDetailContent consumes nsiteAutoPlay router state and clears it
  after consumption so a page refresh doesn't re-trigger auto-play
2026-05-05 12:38:26 -05:00
Alex Gleason 973acd7e9b Inject NIP-07 signer into nsites and drop native sandbox path
When a logged-in user opens an nsite preview, a window.nostr provider is
injected into the sandboxed iframe. The provider proxies signEvent, nip04,
and nip44 calls to the parent signer over the existing JSON-RPC bridge.

A permission system gates each operation:
- getPublicKey is auto-allowed (clicking Run implies consent)
- signEvent prompts are granular per event kind (like Amber)
- encrypt/decrypt prompts are per operation type
- Users can check 'Remember for this site' to persist decisions
- Permissions are scoped to (userPubkey, siteId) in localStorage

The nsite preview nav bar gains a shield icon that opens a popover for
managing stored permissions.

Kind labels for the signer nudge, the permission prompt, and the post-
detail loading title now route through a central KIND_LABELS registry
(src/lib/kindLabels.ts) instead of three divergent inline maps.

The native SandboxPlugin (iOS WKWebView / Android WebView overlay) is
removed; SandboxFrame now always uses iframe.diy, so native behavior
matches web. This drops ~1100 lines of native code, the Android-only
blob prefetch workaround in NsitePreviewDialog, and the createPluginCall
registration in MainActivity and capacitor.config.json.
2026-05-05 12:15:31 -05:00
Alex Gleason d2cf678491 Fix sing-to-Blobbi mic access on Android and preview playback on iOS
The sing action uses getUserMedia + MediaRecorder, which in a browser is
gated only by the standard web mic prompt. In Capacitor's Android
WebView it additionally requires the RECORD_AUDIO permission to be
declared in AndroidManifest.xml; without it the WebView rejects with
NotAllowedError and no system prompt is ever shown, so tapping record
silently fails on the Android app while working fine in the browser.

Also add MODIFY_AUDIO_SETTINGS, which some devices require for the
echoCancellation / noiseSuppression / autoGainControl constraints that
InlineSingCard passes to getUserMedia.

Separately, reorder AUDIO_MIME_CANDIDATES to prefer audio/mp4/aac over
audio/webm;codecs=opus. iOS WKWebView cannot decode WebM/Opus in an
<audio> element, so the recorded Blob's preview URL failed to load on
iOS. Android WebView and desktop Chromium both support mp4/aac, so
preferring it first is safe cross-platform. This mirrors the ordering
already used by useVoiceRecorder.ts.
2026-05-05 09:20:01 -05:00
Alex Gleason 0497aa33c9 Show live progress during the nostrconnect login handshake
When a user tapped "Open Signer App", the dialog previously stayed
frozen on the same screen — same button, same copy-URI fallback, no
feedback — until the login either succeeded (and the dialog dismissed)
or timed out after two minutes. With slow or flaky signers (Amber's
current listening-REQ bug being the immediate trigger, but any NIP-46
signer that takes more than a second or two to respond hits the same
hole) this looked indistinguishable from a hang. Users retapped the
button, closed the dialog, gave up.

Now the dialog swaps the QR / Open Signer App area for a centered
spinner with a live status line as the handshake advances:

- "Waiting for signer connection…" while the signer app has the user
  and we're listening on kind 24133 for the connect-ack.
- "Getting public key…" once the connect-ack arrives and we're
  issuing the NIP-46 get_public_key RPC.

On mobile the swap happens synchronously when the user taps "Open
Signer App" so they see the progress state the moment they return
from the signer — this is the most important window, since that's
exactly when the original UI left them staring at a button they
were worried they needed to re-tap. On desktop the QR stays visible
through the awaiting-connect phase (they may still be scanning with
a different device) and only swaps in once the signer has
acknowledged.

The progress view includes a Cancel link (primary color, matches the
"Create account" affordance) that aborts the in-flight subscription
and regenerates fresh connect params — equivalent to the existing
Retry path, but reachable while the handshake is live instead of
only after a failure.

The handshake phases are surfaced via the new `onStatus` callback
on `NLogin.fromNostrConnect` in @nostrify/react 0.6.0. Bumps
@nostrify/react to ^0.6.0 and @nostrify/nostrify / @nostrify/types
to their matching versions (^0.52.0 / ^0.37.0) to avoid duplicate
nested package copies that would otherwise split type identity.

Incidental cleanup while editing the dialog: the Copy URI button and
the "Tap to open your signer app" / "Scan with your signer app"
status lines are removed. The primary Open Signer App button is
self-explanatory on mobile, and the QR on desktop doesn't need a
caption.
2026-05-04 21:42:45 -05:00
Alex Gleason 0d1fe7bbca Fix nostrconnect listen effect losing successful logins on re-render
The nostrconnect listening effect depended on `login`, `onLogin`,
`onClose`, and `isWaitingForConnect`. `login` is a fresh object from
useLoginActions on every render, and every call site passes inline
arrow functions for onLogin/onClose, so the effect re-ran on every
parent render. Each re-run fired the cleanup and flipped a local
`cancelled = true` flag, and when the signer's NIP-46 response
eventually arrived, the success branch saw `cancelled === true` and
silently skipped `onLogin()` / `onClose()` — the user was logged in on
the backend but the dialog never closed.

Stabilize onLogin/onClose/login via latest-value refs, narrow the
effect deps to `[nostrConnectParams]` only, and gate the success
branch on `controller.signal.aborted` (which is only true when the
dialog was explicitly closed or handleRetry fired). Drop the unused
`isWaitingForConnect` state. Also abort the in-flight controller from
handleRetry before regenerating params, so the prior subscription
doesn't linger.

The bug was masked by most signers responding fast enough (<1s) that
parent re-renders didn't happen during the wait. It surfaced during
an upstream Amber bug that delays its listening REQ by ~8+ seconds,
giving render cycles time to fire (https://github.com/greenart7c3/Amber/pull/420).
The Amber bug is getting fixed separately; this Ditto fix stands on
its own — any signer that takes a few seconds to respond could trip
the same race.
2026-05-04 21:16:38 -05:00
filemon b895c6c696 Rename user-facing 'Event Dashboard' label to 'Dashboard'
Move canonical route to /dashboard with a redirect from /event-dashboard.
Update sidebar item id to 'dashboard'. Internal file/hook names unchanged.
2026-05-04 22:08:58 -03:00
filemon 259d33b71b Fix Event Dashboard self-review findings: stale KPIs, error visibility, safe assertions 2026-05-04 21:48:38 -03:00
Alex Gleason b5675802f4 Replace feed-style page-load skeleton with a neutral spinner 2026-05-04 19:12:31 -05:00
filemon be1d6f1ece Fix Event Dashboard backfill instability: prevent premature completion on timeout
The paginated backfill loop could mark itself as 'done' even when pages
timed out or when a retry overlap page returned only already-known events.
This caused counts to differ between reloads depending on network timing.

Changes to useMultiHashtagFeed.ts:
- Add reachedEnd flag: backfillDone only set when a definitive end condition
  is confirmed (empty batch, reached since, sub-limit overlap, or MAX_EVENTS)
- Timeout/abort sets completedCleanly=false, preventing premature mark
- Resume cursor derived from eventMap minimum timestamp on retry
- Saturated boundary handling: if overlap page at limit returns zero new
  events, fall back to exclusive cursor (oldestInBatch - 1) to probe older
- MAX_EVENTS (10,000) hard cap as explicit constant
- Query DITTO_RELAY directly via NRelay1 to avoid NPool eoseTimeout

Also includes: ConfigDrawer, territorial coverage utilities, content
fallback for municipality attribution, and real relay data integration.
2026-05-04 20:52:12 -03:00
Alex Gleason 14e5a82b1e Play shortform videos unmuted when autoplay is off
VineMedia (kind 22 / 34236 in regular feeds) always started muted on
click-to-play, even when the "autoplay videos" setting was disabled.
That made click-to-play behave differently from the regular VideoPlayer,
where a user-gesture click plays with sound.

Now VineMedia only starts muted when autoplayVideos is on (browsers
require autoplay to be muted). When the setting is off, a click is an
explicit user gesture and plays with audio. The <video> muted attribute
is also bound to state so it tracks the mute toggle correctly. If play
is rejected unmuted, we retry muted as a fallback.

Regression-of: 23e845eb
2026-05-04 18:13:04 -05:00
filemon 2a4cf19488 Connect Event Dashboard to real relay data (Phase 2)
- Add stripped venezuelaTerritorial.ts (24 states + 379 municipalities, no parroquias)
- Add useEventDashboardConfig hook with hardcoded default config
- Add useMultiHashtagFeed hook with backfill/polling/dedup and enabled option
- Add useEventDashboard adapter hook: config → feed → aggregation → typed outputs
- Wire EventDashboardPage to real data; show loading skeleton and error states
- RecentActivityList now resolves profiles via useAuthor
- Non-admin users never trigger relay queries (enabled: false guards all queries)
- No ConfigDrawer, parroquias, territorialCoverage, verified badges, or content
  fallback ported — those remain deferred to Phase 3
2026-05-04 17:53:05 -03:00
filemon 7031b09f45 Add mock Event Dashboard page to agora-3
- Add admin-gated /event-dashboard route with visual-only mock dashboard
- Add requiresAdmin support to SidebarItemDef; filter admin-only items
  from desktop sidebar, mobile drawer, More menu, and search suggestions
- Dashboard components: KPI grid, activity chart, top regions bar chart,
  distribution donut, participants list, recent activity feed, skeleton
- Page-level access control mirrors OrganizersPage pattern (login prompt
  for logged-out users, locked card for non-admins, full content for admins)
- Uses noMaxWidth layout for wider charts within the standard app shell
- All data is static mock (Venezuelan municipality codes in real CNE format)
- No relay fetching, config drawer, dedup/backfill, or localStorage yet
2026-05-04 16:54:56 -03:00
filemon bc2131ed52 Merge branch 'blobbi-drag-to-feed-clean' into feat/blobbi-1124-interactions 2026-05-04 16:01:20 -03:00
filemon af21eee389 Merge branch 'main' into blobbi-drag-to-feed-clean 2026-05-04 13:36:50 -03:00
filemon 20d7aa199d Merge branch 'main' into feat/blobbi-1124-interactions 2026-05-04 13:29:31 -03:00
Patrick PReis 16222d0145 Merge branch 'main' into fix-blobbi-sleepy-eyes
# Conflicts:
#	src/blobbi/ui/BlobbiAdultSvgRenderer.tsx
#	src/blobbi/ui/BlobbiBabySvgRenderer.tsx
2026-05-04 13:00:37 -03:00
Alex Gleason 64ca61bb32 Merge branch 'main' of gitlab.com:soapbox-pub/ditto 2026-05-04 09:52:00 -05:00
Chad Curtis 135666c956 Merge branch 'feat/blobbi-shake-reaction-stability' into 'main'
Add stable shake-to-dizzy reaction flow and preserve SVG animations

Closes #237

See merge request soapbox-pub/ditto!191
2026-05-04 14:42:07 +00:00
root 46cdbe08eb Linkify nostr URIs, URLs, hashtags, and emoji in article bodies
Long-form articles (kind 30023) previously ran their content through
react-markdown with no Nostr awareness, so `nostr:` URIs, bare URLs,
hashtags, and custom emoji all surfaced as literal text. This composes
NoteContent into the markdown renderer instead, so mentions get hover
cards, quoted notes render as embedded cards, URLs get link previews,
and custom emoji resolve — matching regular note behavior exactly, with
zero duplicated parsing.

NoteContent gains an `as: 'div' | 'span'` prop so it can be embedded
inline inside markdown elements without invalid-HTML nesting. Markdown
paragraphs render as `<div>` (with reproduced prose paragraph spacing)
so block-level quote cards and images are legal children. Headings
suppress block embeds to avoid quote cards inside an `<h1>`.

Also strips Typography's default always-on link underline in favor of
hover-only, matching the rest of the app.
2026-05-04 04:28:41 +00:00
filemon 62cc2611ea Fix dizzy-eye SMIL restart during nausea recovery drain
Replace blobbi reference equality with stable visual-identity primitive
comparisons in MemoizedBlobbiVisual memo and SVG renderer useMemo deps.
This prevents SVG DOM rebuilds (which restart SMIL animateTransform)
when the upstream companion object gets a new reference during the
imperative gradient-drain loop.
2026-05-03 19:18:49 -03:00
filemon 65b6c2afb6 Fix frozen dizzy eyes during nausea recovery drain
Lift useFillLevelUpdate above MemoizedBlobbiVisual so the memo boundary
can block the 60fps re-render cascade during level-only changes. The
arePropsEqual comparison now uses a pre-computed recipeFingerprint
(which excludes angerRise.level) instead of recipe reference equality.

During nausea drain, the structural recipe fingerprint stays constant,
so MemoizedBlobbiVisual blocks all re-renders — keeping the SVG DOM
stable and SMIL spiral-eye animations running uninterrupted. The fill
level still updates imperatively via useFillLevelUpdate called from
BlobbiCompanionVisual's root ref.
2026-05-03 19:18:49 -03:00
filemon d80f8ad70c Decouple vomit from hunger, uncap puddles, fix mouth spawn position
Vomit now triggers on any sufficiently hard shake (peakIntensity >= 0.7)
regardless of hunger level. Green nausea fill remains hunger-gated.

Remove the MAX_SPLATS = 3 cap so puddles accumulate freely until the
user clicks/taps them away.

Add a static mouthAnchor lookup table that maps baby/adult forms to
their actual mouth Y-ratio (including the +0.12 visual offset), so
the vomit drop spawns from the correct mouth position per form.
2026-05-03 19:18:49 -03:00
filemon 7ba94d72e4 Fix vomit visibility, landing position, re-vomit guard, and animation jank
- Falling drop z-index 10002 (above companion) so it visibly exits mouth
- Landed puddle stays at z-index 9998 (below companion)
- Land position changed to 20px below Blobbi's container bottom, clamped
- Spawn position adjusted to config.size * 0.65 for mouth area
- Unified transform anchor to translate(-50%, -100%) in both states
- Keyframe ends at scale(1) opacity(1) to prevent pop on landing
- Reset vomitedThisCycle + peakIntensity in onDragStart during active
  cycle so each new qualifying drag can vomit independently
2026-05-03 19:18:49 -03:00