Commit Graph

4447 Commits

Author SHA1 Message Date
Alex Gleason bddfe4b838 Add on-chain Bitcoin zaps as the default zap method
Introduce kind 3043, a new Nostr event that attests an on-chain Bitcoin
payment against a target event or profile. Because every Nostr pubkey
deterministically maps to a Taproot address, any user can receive an
on-chain zap without configuring lud06/lud16 — the zap button now
appears on every post whose author is not the current user.

Publishing flow: sender builds and broadcasts a Bitcoin transaction
paying the recipient's derived Taproot address, then publishes a
kind 3043 event with an `i` tag (`bitcoin:tx:<txid>`), the recipient's
`p`, the target's `e` / `a`, and a self-reported `amount` in sats.
Before displaying or counting a kind 3043 event clients verify the
referenced transaction on-chain and use the sum of outputs paying the
recipient's address as the authoritative amount, capping the sender's
claim at the verified value to prevent spoofing.

Lightning zaps remain available as an opt-in tab inside the zap dialog
whenever the author has a Lightning address configured; otherwise the
dialog is purely on-chain. Defaults favour on-chain: USD amount presets
($1 / $5 / $10 / $25 / $100), fee-speed selection, and a 3-step
form → confirm → success flow mirroring SendBitcoinDialog.
2026-04-22 15:17:09 -05:00
Alex Gleason 664a555fbd Merge remote-tracking branch 'origin/main' into wallet
# Conflicts:
#	package-lock.json
#	package.json
2026-04-22 14:57:05 -05:00
filemon 4d00ba9542 Fix duplicate egg creation when adopting another Blobbi
The module-level setupInFlightFor guard had a race condition: the
effect cleanup unconditionally deleted the pubkey from the guard set
even when setup() was already mid-flight. If a parent re-render caused
the component to unmount/remount during the async publish window, the
new instance passed all guards and created a second egg.

Fix: track whether setup() has started in a ref. Cleanup only releases
the guard when the timer was cancelled before setup began; otherwise
setup() releases it in its own finally block.

Also stabilize the eggOnly completion timer by reading onComplete
through a ref, preventing the 1500ms timer from resetting on every
parent re-render that creates a new inline callback reference.
2026-04-22 13:14:42 -03:00
filemon ea99fdf288 Clean up self-review findings: deduplicate hexToHsl, add abort handling, fix comments
- Remove hexToHslLocal from blobbi.ts; reuse shared hexToHsl from
  color-guardrails.ts (eliminates duplicate implementation)
- Add abort flag to useSeedIdentitySync useEffect so the async sync
  loop stops on unmount and never calls updateCompanionEvent after
  teardown
- Replace relative date wording in compat cutoff comment with
  absolute date only
- Fix stale STEP numbering in BlobbiPage.tsx (5/6 → 4/5)
2026-04-22 10:31:31 -03:00
sam 8411fb997d Merge branch 'main' into feat/dms 2026-04-22 12:19:16 +05:45
sam 3cc1e1dcec dont use the generic lazy loading for messages page, its looks daft and messaging has its own loading state 2026-04-22 12:17:53 +05:45
filemon 56650efe74 Fix self-review findings: unify adult derivation, remove dead code, harden sync
- Unify adult-form derivation: replace charCode hash in
  deriveAdultFormFromSeed with the canonical seed-slice algorithm
  (offset [40..48]), and remove the duplicate deriveAdultTypeFromSeed
  from blobbi.ts (all call sites now use the single canonical function)
- Guard unconditional console.log in parseBlobbiEvent behind
  import.meta.env.DEV so it no longer spams production consoles
- Remove dead deriveColorsFromSeed (zero callers, was deprecated on
  arrival) and its stale JSDoc reference in adjustSeedForAdultType
- Replace brute-force loop in adjustSeedForAdultType with a direct
  O(1) computation: since the derivation is parseInt(slice, 16) % len,
  the target index itself is always a valid candidate
- Add fetchFreshEvent to useSeedIdentitySync before each publish,
  matching the project convention for replaceable event mutations and
  preventing stale-cache overwrites on multi-device usage
2026-04-21 23:47:12 -03:00
filemon ef64668fac Add Pandi color customizer: tinted-white body, dark-tinted patches
Pandi now applies baseColor and secondaryColor instead of staying
hardcoded black and white.

Light areas (body, head): a very soft tinted-white derived from
baseColor's hue at L=95 S=min(baseSat,30). Clearly not pure white,
but stays close — preserves the hue family without going full-
strength. Stroke uses the same hue at L=90 S=20.

Dark areas (ear patches, eye patches, inner ears, arms, legs, nose,
mouth): derived from secondaryColor's hue forced to L=20 S=30
(primary dark) and L=27 S=20 (lighter dark for gradients and inner
fills). Maintains proper panda light-vs-dark contrast.

Eye color: unchanged — still applied via pandiPupil3D gradient
replacement in the existing applyPupilGradient path.

No other adult form customizers were modified.
2026-04-21 23:30:42 -03:00
filemon ce4550cae5 Update dev editor to adjust seed when changing adult form
The adult form dropdown now works with the seed-truth model: selecting
a different form calls adjustSeedForAdultType() and writes the adjusted
seed through the normal update path. syncMirrorTagsToSeed then derives
all mirror tags (adult_type, colors, pattern, etc.) from the new seed.

Previously the dropdown wrote adult_type as a raw tag that was
immediately overwritten by the seed-derived value — effectively a no-op.

Also adds a read-only seed display and a note explaining that changing
the form re-derives the visual identity.
2026-04-21 23:19:08 -03:00
filemon d951aab997 Derive adultType from effectiveSeed, rename sync terminology
BlobbiCompanion.adultType now derives from the effective seed for adult
Blobbies instead of reading the (potentially stale) stored tag. Falls
back to the tag only for legacy events without a seed.

Renames to reflect the broadened scope of the sync system:
- needsColorSync -> needsSeedIdentitySync
- eventNeedsColorSync -> eventNeedsSeedIdentitySync

No behavior change beyond making adultType consistent with the seed-
truth model and aligning names with what the code already does.
2026-04-21 23:07:51 -03:00
filemon 3dac492e31 Seed-truth for full visual identity including adult_type, with compat window
Seed now determines the complete visual identity: colors, pattern,
special_mark, size, and adult_type. All corresponding tags are persisted
mirrors that get overwritten on every republish via syncMirrorTagsToSeed.

Key changes:

deriveAdultTypeFromSeed: new derivation at seed offset [40..48], indexing
  into the 16-element ADULT_FORMS array via deriveIndexFromSeed.

deriveSeedIdentity: replaces deriveColorsFromSeed as the single entry
  point for the complete seed-derived visual trait set.

Temporary adult-type compatibility (cutoff: 2026-05-01 UTC):
  For existing adult Blobbies whose stored adult_type doesn't match the
  seed-derived form, adjustSeedForAdultType brute-forces the seed bytes
  at offset [40..48] to produce the stored form. This preserves existing
  adult forms during the transition. After the cutoff, parseBlobbiEvent
  skips this adjustment automatically and the code becomes dead.

eventNeedsColorSync: now checks all mirror tags (colors, pattern, mark,
  size, adult_type for adults), not just colors.

syncMirrorTagsToSeed: expanded to overwrite all mirror tags including
  adult_type on every republish through the merge pipeline.

useSeedIdentitySync hook: new hook wired into BlobbiPage that checks
  filteredCompanions on load and republishes any with stale mirror tags.
  Tracks synced d-tags in a ref to avoid loops. Processes sequentially
  to avoid relay rate-limiting.

BlobbiPage: calls useSeedIdentitySync(filteredCompanions) after the
  existing dedup/filter step, so only visible companions are synced.
2026-04-21 22:54:50 -03:00
filemon 907370e270 Make seed the canonical source of truth for Blobbi colors
When a seed exists, base_color / secondary_color / eye_color are now
always derived from the seed via deriveColorsFromSeed(). Explicit color
tags no longer override seed-derived values -- they are persisted as
mirrors for relay indexing and backward compatibility.

Changes:
- deriveVisualTraits: seed path ignores color tags entirely; no-seed
  legacy path unchanged
- deriveColorsFromSeed: new single entry point for canonical color
  derivation (seed → HSL → guardrails)
- syncColorTagsToSeed: overwrites stale color tags on every republish
  via mergeBlobbiStateTagsForRepublish
- eventNeedsColorSync: detects events whose stored color tags differ
  from seed-derived values
- BlobbiCompanion.needsColorSync: lightweight flag for UI-driven
  republish of stale events

Existing Blobbies with a seed will change appearance on next render
(seed-derived colors replace old palette-indexed tags). Events are
backfilled on their next republish through the merge pipeline.
2026-04-21 22:14:39 -03:00
filemon 1eeaf4c10e Replace fixed color palettes with arbitrary HSL generation from seed
Blobbi colors are now derived as full-spectrum HSL values from the seed
hash instead of indexing into fixed 10/10/8-element palette arrays.

Generation changes:
- deriveBaseColorFromSeed: splits 32-bit seed value into H(0-359),
  S(30-100), L(30-75) via successive division
- deriveSecondaryColorFromSeed: harmonized from base — same saturation,
  hue shifted ±20°, lightness +12..25 above base (guarantees visible
  3D gradient)
- deriveEyeColorFromSeed: independent H(0-359), S(40-100), L(10-55)
  for dark vivid pupils

Both deriveVisualTraits() and buildEggTags() now pipe seed-derived
colors through applyColorGuardrails() before use. Guardrails are never
applied to explicit tag values — the tag-priority rule is preserved.

Legacy palette arrays are marked @deprecated but kept for reference.
No rendering code, customizers, or Pandi behavior changed.
2026-04-21 21:10:54 -03:00
filemon c5140bf118 Add color guardrail utilities for Blobbi visual trait generation
Pure HSL-based validation/adjustment functions that will make arbitrary
color generation safe in a follow-up step. Guardrails ensure:
- base colors stay within a lightness range where the SVG gradient
  pipeline (lighten/darken) produces visible 3D shading
- secondary colors are perceptually distinct from base colors so body
  gradients don't collapse into flat fills
- eye colors have enough contrast to remain visible on white sclera
  and visually distinct from the body

Generation-side only: no rendering code, customizers, or existing
tagged colors are touched.
2026-04-21 20:29:30 -03:00
filemon f0f54d76c5 Merge branch 'main' into fix/blobbi-deterministic-legacy-migration 2026-04-21 19:54:07 -03:00
filemon 819d0a88f1 Make legacy Blobbi migration deterministic
Derive the migration petId from sha256(pubkey + legacyD) instead of
crypto.getRandomValues(). The same legacy Blobbi now always produces the
same canonical d-tag, seed, and visual traits regardless of which device
or session triggers the migration.

The equivalence guard (findCanonicalEquivalent) still runs first, so
pre-existing canonicals from the random-petId era are reused and no
duplicate is created.
2026-04-21 19:45:19 -03:00
Chad Curtis 08e61eea89 Merge branch 'fix/blobbi-adult-polish' into 'main'
Fix adult Blobbi colors, reactions, and eye/eyebrow alignment

Closes #241, #242, #243, #244, #245, and #246

See merge request soapbox-pub/ditto!196
2026-04-21 22:29:35 +00:00
Chad Curtis 273469eda8 Merge branch 'fix/blobbi-progression-task-persistence' into 'main'
Move Blobbi progression missions from kind 11125 to kind 31124

Closes #239

See merge request soapbox-pub/ditto!193
2026-04-21 22:29:05 +00:00
Chad Curtis 97a219aa8c Merge branch 'fix/blobbi-sleep-eye-overlay-initial-render' into 'main'
Fix open-eye flash on first sleep transition

Closes #240

See merge request soapbox-pub/ditto!195
2026-04-21 22:28:31 +00:00
Chad Curtis 5dafdf85f7 Merge branch 'fix/blobbi-legacy-new-deduplication' into 'main'
Fix Blobbi legacy/new-format deduplication to prevent infinite duplicates

Closes #247

See merge request soapbox-pub/ditto!198
2026-04-21 22:28:00 +00:00
filemon 7830269ea1 Collapse canonical Blobbi duplicates created by migration races
filterMigratedLegacyCompanions now runs a second pass that groups
canonical companions by their migrated_from tag. Within each group
only the newest event (highest created_at) is kept; the rest are
hidden from the collection UI. Canonical companions without the tag
are never grouped — they pass through untouched.

This closes the remaining duplicate-in-UI gap left intentionally by
the earlier legacy→canonical dedup work.
2026-04-21 18:37:38 -03:00
filemon 118b0c11ab Strengthen legacy->canonical equivalence with migrated_from and base_color
Replace the name-only equivalence rule with a tiered priority:

1. migrated_from exact match (canonical event's migrated_from tag equals
   the legacy d-tag) — strongest signal, written during migration and
   preserved across all subsequent Blobbi updates.

2. Same normalized name + same raw base_color tag — covers older canonical
   copies created before migrated_from existed, where both events have an
   explicit base_color tag that matches.

3. Same normalized name when the legacy event has no base_color tag —
   weakest fallback for genuinely old bare legacy events with no visual
   tags to compare.

All tiers still require the legacy d-tag to be absent from profile.has
(the migration-completion guard).

Audited that migrated_from survives all Blobbi lifecycle operations:
mergeBlobbiStateTagsForRepublish preserves it as an unknown tag,
validateAndRepairBlobbiTags passes it through (not in schema, not
deprecated), and stage transition cleanup does not touch it.
2026-04-21 17:52:24 -03:00
filemon 4ad0a9cfb4 Fix Blobbi legacy/new-format deduplication to prevent infinite duplicates
Legacy Blobbi events (d=blobbi-{name}) persisted on relays after migration
to canonical format (d=blobbi-{hex}-{hex}), causing them to appear alongside
their canonical copies in the UI. Interacting with a still-visible legacy
Blobbi triggered another migration each time, creating unbounded duplicates.

Three changes:

1. Filter migrated legacy Blobbies from the rendered collection. A legacy
   Blobbi is hidden only when a canonical Blobbi with the same normalized
   name exists AND the legacy d-tag is no longer in profile.has (confirming
   migration already occurred).

2. Guard ensureCanonicalBlobbiBeforeAction against re-migration. Before
   creating a new canonical event, query all companions and look for an
   existing canonical equivalent by normalized name. If found, reuse it
   and fix up profile.has/current_companion instead of migrating again.

3. Store a migrated_from tag on newly migrated events for future stronger
   equivalence lookups (additive, not depended on by current dedup logic).
2026-04-21 17:32:50 -03:00
filemon 3e5840b9a2 Merge branch 'main' into feat/blobbi-shake-reaction-stability 2026-04-21 12:35:25 -03:00
Chad Curtis c23af72da7 Fix lightbox swipe-to-dismiss flicker and locked controls race
The dismiss animation only translated the image strip, leaving the top
bar, nav buttons, dot indicators, and bottom bar stationary — visible
as a jarring flicker of controls. The backdrop also flashed back to full
opacity for one frame before the portal unmounted.

Wrap all visible content (everything except the backdrop) in a single
container that receives the translateY transform so the entire UI sweeps
away as one unit. Reorder the setTimeout callback so onClose fires
before clearing the animating lock, and add an unmount-cleanup effect as
a safety net against stuck controls.

Regression-of: cc655891
2026-04-20 20:00:50 -05:00
Chad Curtis b29f7ec4d5 Fix white status bar text on light theme (iOS)
App.tsx had a useEffect that unconditionally set SystemBarsStyle.Dark
(white icons) on mount, overriding the theme-aware logic in main.tsx
that had already set the correct style. On light themes this produced
white-on-white status bar text.

Remove the hardcoded override entirely — main.tsx handles initial
detection and MutationObservers cover all subsequent theme changes.
2026-04-20 19:12:06 -05:00
Chad Curtis a42e5f085e Fix envelope cards hard to tap on mobile
The hover wobble animation was triggering on touch devices via the
sticky :hover pseudo-class, rotating the envelope while the user
was trying to tap it. Restrict the wobble to true pointer-hover
devices with @media (hover: hover) and (pointer: fine).

Also tighten the entrance animation: remove rotation so tap targets
stay stable, reduce duration from 0.4s to 0.3s, start closer to
final size (0.85 vs 0.6), and cap stagger delay at 300ms so later
envelopes settle before the user can scroll to them.
2026-04-20 19:05:31 -05:00
Chad Curtis cc655891d5 Add swipe-to-dismiss gesture to lightbox overlays
Users can now swipe up or down to dismiss the full-screen image
lightbox, matching the native mobile pattern of flicking an image
away instead of reaching for the X button. The image follows the
finger with opacity fade, and commits the dismiss after 15% of
viewport height. When zoomed in the gesture is disabled so it
doesn't conflict with panning.

Applies to both the main Lightbox (feeds, galleries, media collage)
and the ProfileImageLightbox (avatar/banner taps).
2026-04-20 19:01:03 -05:00
Chad Curtis 708c25d938 Clear inline wall compose box after posting from modal
When a user typed in the inline ComposeBox then tapped the FAB (which
covers the Post button), the modal opened with the same draft text.
After posting from the modal, the inline ComposeBox still showed the
old text because it was a separate React instance with its own state,
leading to accidental double-posts.

Bump a key on the inline ComposeBox after a successful modal post so
React remounts it, picking up the already-cleared localStorage draft.
2026-04-20 18:54:21 -05:00
lemon 5fa021329e remove kind 5/1984 moderation from community membership resolution
The deletion and report queries were unscoped (fetching globally) and the
moderation overlay needs more design work. Strip it out for now and leave
TODOs for a follow-up.
2026-04-20 11:57:03 -07:00
Chad Curtis a7cd13228b Add autoplay videos setting (default off)
Adds a new autoplayVideos config field and a toggle in Settings > Content >
Video Playback. When enabled, videos auto-play muted in feeds, collage
thumbnails, profile sidebar tiles, the Vines feed, and the VideoPlayer
component. The preference syncs across devices via encrypted settings.

usePlayerControls now listens for the volumechange event to keep the
volume UI in sync when the video is programmatically muted for autoplay.
2026-04-20 12:26:58 -05:00
sam ef100bfac1 guard messages if not authed 2026-04-20 18:39:19 +05:45
sam a5c52c72be dms first pass 2026-04-20 18:14:35 +05:45
sam 865a472ef1 delete legacy mkstack dms 2026-04-20 18:10:23 +05:45
filemon 618655e921 Merge branch 'main' into fix/blobbi-adult-polish 2026-04-20 01:16:43 -03:00
lemon 8c83758461 replace Follows tab with Activities tab showing community events and comments 2026-04-19 17:42:31 -07:00
lemon da1d872dd7 hide media, protocol, language, kind, and replies filters on communities search tab 2026-04-19 17:42:31 -07:00
lemon 70f74c6f9d simplify community NoteCard: remove moderators list, separator, and stats badges 2026-04-19 17:42:31 -07:00
lemon 556af013db add Communities tab to search page with global kind 34550 feed 2026-04-19 17:42:31 -07:00
lemon b7a128ad28 shorten empty events message to 'No events yet' 2026-04-19 17:42:31 -07:00
lemon c17be3d191 simplify empty events state: remove icon, border, and card background 2026-04-19 17:42:31 -07:00
lemon e2d3a164a6 remove separator line between founder and tabs 2026-04-19 17:42:31 -07:00
lemon 88d2fdd904 remove stats badges from community detail page header 2026-04-19 17:42:31 -07:00
lemon 6929097466 replace comment button with ComposeBox in community detail page 2026-04-19 17:42:31 -07:00
lemon 52dae96a61 add dedicated community detail page with members, events, and comments tabs 2026-04-19 17:42:31 -07:00
lemon c82c6f4179 add communities page with NIP-72 hierarchical community support 2026-04-19 17:42:31 -07:00
filemon 436324fe8f Fix adult eyebrow alignment by anchoring to eye top instead of eye center
Eyebrow Y position was calculated as eye.cy + offsetY, which broke on
forms with large eye whites (catti ry=16, froggi/owli r=22, droppi/
pandi/rocky r=12) because the fixed offset did not account for the
distance between eye center and eye top.

Propagate eye white vertical radius through the pipeline: write
data-eye-rx/ry on blink groups in addEyeAnimation, read data-eye-ry
in detectFromProcessedSvg, add eyeWhiteRy to EyePosition type. The
eyebrow formula now rebases recipe offsets from center-relative to
top-relative using the actual eye white radius, producing a consistent
gap above the eye top regardless of eye size.

Remove FORM_EYEBROW_OFFSETS (owli, froggi) — the radius-aware formula
handles all forms correctly without per-form overrides. Baby eyebrow
formula is unchanged.
2026-04-19 21:15:58 -03:00
filemon d0a11e266f Fix body detection for adult forms using circle, ellipse, and rect bodies
detectBodyPath() only matched <path> elements, so 10 of 16 adult forms
failed body detection — anger-rise was silently skipped and dirt/dust
fell back to hardcoded positions.

Add data-blobbi-body="true" marker to the primary body element of all
16 forms (base + sleeping = 32 elements). Extend detectBodyPath() with
a marker-first strategy that supports <circle>, <ellipse>, and <rect>
via path synthesis. Update the anger-rise overlay insertion regex in
apply.ts to find the marked element instead of only matching <path>.

Existing gradient-name and comment-based fallbacks remain for backwards
compatibility.
2026-04-19 20:55:06 -03:00
filemon 5bf99176bb Fix eye tracking lost on flat-fill adults after eyeColor replacement
The eyeColor fallback replaced known pupil fills with arbitrary colors,
causing isPupilElement() to no longer recognize them via PUPIL_COLORS.
Inject a data-blobbi-pupil marker during the fill replacement and check
for it first in both isPupilElement() copies so detection is independent
of the actual fill color value.

Regression-of: 9c20102d
2026-04-19 20:34:51 -03:00
filemon 9c20102dad Apply eyeColor and secondaryColor consistently to all adult Blobbi forms
eyeColor was silently dropped for 12 of 16 adult forms because they use
hardcoded fill attributes on pupil circles instead of gradient references.
Add a scoped flat-fill fallback in applyPupilGradient() that replaces the
known default pupil color within the <!-- Pupils --> comment block only.

secondaryColor was threaded through the type system but never read by any
adult customizer. Following the baby two-tone pattern (secondaryColor at
center, baseColor at edge), add an optional innerColor parameter to the
gradient builders and pass secondaryColor to each form's main body
gradient. Pandi remains excluded from body color changes by design.
2026-04-19 20:06:25 -03:00