Commit Graph

3394 Commits

Author SHA1 Message Date
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
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
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 bfee3dfdf1 release: v2.10.3 2026-04-20 19:40:48 -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
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
filemon 618655e921 Merge branch 'main' into fix/blobbi-adult-polish 2026-04-20 01:16:43 -03: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
Chad Curtis 8b311bde81 Merge branch 'ditto-music-feed' into 'main'
feat: redesign Music page with curated discovery experience

Closes #233

See merge request soapbox-pub/ditto!189
2026-04-19 22:27:56 +00:00
Chad Curtis b4e42778fa Merge remote-tracking branch 'origin/main' into ditto-music-feed
# Conflicts:
#	src/components/NoteCard.tsx
2026-04-19 17:27:12 -05:00
Chad Curtis 986adeb901 Reset package-lock.json to origin/main 2026-04-19 17:25:26 -05:00
Mary Kate Fain 1ce9beeaf5 Remove stray Cloudflare 524 error page committed as '&1' 2026-04-19 17:19:23 -05:00
filemon e704399c3d Fix Pandi eye detection misidentifying ears and eye patches as pupils
Pandi's 4 ear circles and 2 eye-patch circles use flat dark fills
(#1f2937, #374151) that match PUPIL_COLORS, causing the detector to
find 8 "pupils" instead of 2. This produced phantom eye groups,
broke blink/gaze targeting, and left the real pupils orphaned.

Add data-blobbi-skip attribute to the 6 non-pupil dark circles in
Pandi's SVGs, and add an early-return in isPupilElement() to respect
it. No other form has this attribute, so behavior is unchanged for
all non-Pandi adults.
2026-04-19 00:39:49 -03:00
filemon d1ae988024 Fix Catti reaction mouths rendering left-shifted instead of centered
Catti's dual cat-mouth (two mirrored Q-curve paths sharing a center
start point) caused extractMouthPositionFromElements to return only
the left half's coordinates. Every mouth generator then computed
cx=(100+82)/2=91 instead of the true center x=100.

The fix scans all Q-curve paths in the mouth section and computes the
full horizontal extent. For Catti: startX=82, endX=118, center=100.
Single-path mouths are unaffected (the extra-path loop simply doesn't
execute). Froggi's dual-path mouth already had symmetric bounds so its
result is unchanged.
2026-04-18 23:38:41 -03:00
filemon 27736c7047 Fix Catti whiskers destroyed by mouth replacement recipes
replaceMouthSection() used a global regex matching any Q-curve <path>
with a stroke attribute. Catti's whiskers are Q-curve paths that appear
after the mouth in document order, so they were matched and deleted
whenever a status reaction replaced the mouth.

The fix adds a marker-bounded replacement strategy: when a <!-- Mouth -->
comment marker exists, the regex is scoped to only the section between
that marker and the next SVG section. All 16 adult forms and the baby
SVG already have this marker. The global regex is preserved as a
fallback for SVGs without markers.
2026-04-18 23:04:55 -03:00
filemon 6f68153306 Fix sleeping Blobbi showing open eyes in Blobbies tab grid
The MoreTabContent grid rendered BlobbiStageVisual without a sleeping
recipe, so sleeping companions got the awake base SVG with open eyes.
The sleeping visual (closed clip-rects, closed-eye lines, Zzz) is
entirely recipe-driven — without the recipe the SVG renderer always
produces the awake appearance.

Pass buildSleepingRecipe() and recipeLabel='sleeping' for companions
whose state is 'sleeping' in the tab grid call site.
2026-04-18 22:19:42 -03:00
filemon 03d174e5cc Add one-shot eye-open animation on wake-up
When Blobbi transitions from sleeping to awake, the eyes now visibly
open over 400ms with an ease-in-out curve before the normal blink/gaze
loop resumes. The wake-up animation mirrors the sleep-entry animation:

- runWakeUpAnimation() queries fresh DOM elements from containerRef
- Sets clip-rects to the closed position (using BLINK_CLOSED_AMOUNT)
- Animates from closed to open, then calls onComplete
- The normal awake animation loop only starts after completion
- This prevents two rAF loops from fighting over clip-rect attributes

The animation only fires on genuine sleeping→awake transitions via
wasSleepingRef, not on mount or refresh when Blobbi is already awake.
If cancelled mid-animation (e.g. quick sleep re-toggle), the onComplete
callback is still invoked so the hook does not get stuck.
2026-04-18 19:26:24 -03:00
Chad Curtis 243ce98dd4 Merge branch 'feat/inbox-relay-delivery' into main 2026-04-18 16:53:26 -05:00
Chad Curtis f14316f024 Send reply events to tagged users' inbox relays (NIP-65)
When publishing kind 1 or kind 1111 reply events, also deliver them to
the read (inbox) relays of p-tagged users. This follows the NIP-65
recommendation that clients send events to the read relays of each
tagged user so recipients are more likely to see replies.

The inbox delivery is fire-and-forget after the main publish succeeds,
so it does not slow down the UI or block the publish flow.
2026-04-18 16:42:48 -05:00
filemon 399a3586b2 Add one-shot eye-close animation on sleep entry
When Blobbi transitions from awake to sleeping, the eyes now visibly
close over 400ms with an ease-in-out curve before settling into the
stable sleeping state. The closed-eye lines fade in during the last
40% of the animation.

The animation is driven by a standalone rAF loop in runSleepEntryAnimation()
that queries fresh DOM elements from containerRef.current — never from
stale cached refs. A wasSleepingRef tracks the previous isSleeping value
so the animation only fires on genuine awake→sleep transitions, not on
mount or refresh when Blobbi is already sleeping.

At t=1 the DOM exactly matches the recipe's static closed state, so
there is no visual discontinuity when the animation completes.
2026-04-18 18:36:17 -03:00
filemon 3bba781f49 Fix open-eye flash on first sleep transition
Clear stale cached DOM refs in useBlobbiEyes when entering sleep.
The awake animation loop caches blink/gaze SVG elements, but
dangerouslySetInnerHTML replaces the entire SVG when the sleeping
recipe is applied. The old refs' open-eye clip geometry was being
used to querySelector into the new sleeping SVG and reset the
clip-paths back to the open position, causing both open eyes and
closed-eye lines to render simultaneously.

The sleeping recipe already sets clip rects to the closed position
in the SVG string, so no JS-side clip-path reset is needed.
Clearing the caches prevents stale operations and lets fresh
caching happen naturally when Blobbi wakes up.
2026-04-18 18:09:12 -03:00
Alex Gleason 91fe272bea release: v2.10.2 2026-04-18 09:19:01 -05:00
filemon 0618a1ca13 Skip redundant evolution persist when content is already up-to-date
The debounce hook was re-publishing a kind 31124 event with identical
evolution content 5s after every interaction, because the primary
write path already persisted the same data inline. Now compares the
serialized content against the fresh event before publishing and
skips when they match. The hook still fires for the one case where
it is genuinely needed: event-based backfill from Nostr queries.
2026-04-18 09:33:06 -03:00
filemon 3fe1256381 Harden per-Blobbi evolution persistence across interaction paths
Five targeted fixes to the evolution mission persistence flow:

1. Inline evolution content into interaction write paths: all three
   action hooks (direct action, inventory item, companion item use) now
   read the updated evolution from the session store and embed it into
   the 31124 content in the same publish. The debounce hook remains as
   a safety net for event-based backfill, not the primary persistence.

2. Scope ensuredRef per Blobbi: the 'ensure missions exist' guard in
   useHatchTasks and useEvolveTasks was a plain boolean ref that would
   not re-run when switching companions. Now keyed by pubkey:d.

3. Add companionD to query keys: hatch-tasks and evolve-tasks queries
   were keyed by pubkey only, causing stale cache reuse across Blobbis.

4. Filter persist hook by d-tag: usePersistEvolutionProgress now checks
   detail.d against companionD so it only reacts to evolution updates
   for the active companion.

5. Clear evolution in switch mode: when switching incubation from one
   Blobbi to another, the stopped Blobbi's 31124 content now has its
   evolution[] cleared, and its session store entry is removed.
2026-04-18 09:23:08 -03:00
filemon 1bce67d21d Move Blobbi progression missions from kind 11125 to kind 31124
Evolution/hatch mission progress was stored in the shared Blobbonaut
profile (kind 11125) content JSON, causing split-brain state between
the per-user profile and per-Blobbi events. After reload, progress
could disappear or get overwritten across Blobbis because the session
store was keyed by pubkey only and persisted via a debounced write to
the wrong event.

Now:
- Daily missions remain on kind 11125 (per-user, correct)
- Evolution missions live on kind 31124 content JSON (per-Blobbi)
- Session store split: daily keyed by pubkey, evolution by pubkey:d
- usePersistEvolutionProgress writes to 31124 instead of 11125
- useHatchTasks/useEvolveTasks read from companion.evolution
- serializeProfileContent strips legacy evolution from 11125
- Start/stop incubation/evolution seed/clear 31124 content directly
- Interaction tallies pass companion d-tag for per-Blobbi tracking
2026-04-18 08:59:19 -03:00
Chad Curtis 00fa9cad57 Merge branch 'fix/blobbi-progression-state-separation' into 'main'
Separate progression state from activity state to prevent evolution reset

Closes #238

See merge request soapbox-pub/ditto!192
2026-04-18 11:35:58 +00:00
filemon 9b9abaa855 Fix stale references to state_started_at in progression migration cleanup
- StartIncubationDialog: display progressionState instead of state in
  restart dialog text (was showing 'active' instead of 'incubating')
- blobbi-tag-schema: update deprecated tag replacedBy and category
  comment to reference progression_started_at instead of state_started_at
- blobbi.ts: update deprecation comments for incubation_time and
  start_incubation to reference progression_started_at
2026-04-18 08:10:56 -03:00
filemon 06b53dbc82 Fix stale comments and result types from progression-state migration
Update JSDoc comments in useBlobbiIncubation to reference
progression_state/progression_started_at instead of the deprecated
state/state_started_at. Rename stateStartedAt to progressionStartedAt
in StartIncubationResult and StartEvolutionResult interfaces.
2026-04-18 07:34:07 -03:00
filemon bf6788c141 Separate progression state from activity state to prevent evolution reset
Sleep/wake toggles overwrote 'evolving' with 'sleeping', permanently
destroying evolution progress. The root cause was using a single state
tag for two orthogonal concerns: activity (active/sleeping/hibernating)
and progression (incubating/evolving).

Introduce progression_state and progression_started_at as new tags
orthogonal to the activity state tag. Sleep, wake, and hibernation
changes now never touch progression. Parser auto-migrates legacy
events that stored progression in the state tag on read.
2026-04-18 07:03:52 -03:00
Chad Curtis 363e39d72c Harden against malformed untrusted data that crashes pages
- ExternalContentPage: new URL() on non-URL NIP-73 identifiers (isbn:,
  iso3166:) threw TypeError crashing the page; now URL is only created
  for url-type content and #-prefixed strings are used for other types
- ExternalContentPage & RelayPage: decodeURIComponent on malformed
  percent-encoded URL params threw URIError; now wrapped in try/catch
- useMastodonPost: new URL() on invalid URLs inside queryFn now returns
  null instead of letting the error propagate
- colorUtils/themeEvent: malformed hex color values from theme events
  produced NaN CSS variables; added isValidHex guard in parseColorTags
2026-04-18 04:50:34 -05:00
Chad Curtis 36373400f8 Fix crash on invalid blurhash strings from Nostr events
Validate blurhash hashes before passing them to react-blurhash's
<Blurhash> component, which throws when the encoded length doesn't
match the component count header. Malformed hashes from third-party
events (e.g. length 92 instead of 94) now gracefully fall back to a
skeleton placeholder instead of crashing the page.
2026-04-18 04:39:14 -05:00
Alex Gleason 54a49f1ece release: v2.10.1 2026-04-17 22:43:30 -05:00
Alex Gleason a2f2d9ff89 Add configurable shareOrigin to AppConfig
window.location.origin resolves to capacitor://localhost on iOS and
https://localhost on Android, which produces broken QR codes, broken
copy-link actions, and a broken remote-login callback URL on native
builds.

Add an optional shareOrigin field to AppConfig and a useShareOrigin
hook that falls back to window.location.origin when unset. Replace
all 13 call sites that build shareable URLs.

The origin can be configured three ways, in order of precedence:
user localStorage > ditto.json > VITE_SHARE_ORIGIN env var. Native
deployments can set VITE_SHARE_ORIGIN=https://ditto.pub at build time
so that shared URLs resolve correctly when opened on another device
(and get caught by DeepLinkHandler when opened on the same app via
Universal/App Links).

Regression-of: a12d5db5
2026-04-17 22:39:44 -05:00
Alex Gleason cb26238729 release: v2.10.0 2026-04-17 19:34:53 -05:00