Commit Graph

3122 Commits

Author SHA1 Message Date
filemon 4e5dbed3d2 Fix all Kind 11125 write paths to preserve profile content JSON
Critical safety fix: 9 out of 12 Kind 11125 publish paths were
hardcoding content: '' which would silently erase any structured
content (daily missions data) stored in the profile event.

Every write path that updates an existing profile now preserves
the existing event content via profile.event.content instead of
overwriting with empty string.

Fixed locations:
- useBlobbiOnboarding.ts: reroll preview (line 378) and adopt (476)
- BlobbiHatchingCeremony.tsx: add egg to has[] (302) and mark
  onboarding done (501)
- useBlobbiMigration.ts: legacy migration (190)
- useBlobbonautProfileNormalization.ts: tag normalization (85)
- BlobbiPage.tsx: auto-fix onboardingDone (547) and companion
  toggle (1183)
- useBlobbiPurchaseItem.ts: shop purchase (89)

The only two paths that still use content: '' are initial profile
creation flows (useBlobbiOnboarding auto-create and
BlobbiHatchingCeremony silent setup) where no previous event exists.

The useClaimMissionReward path already uses mergeProfileContent
for proper read-modify-write of the content JSON.
2026-04-06 03:36:20 -03:00
filemon 508a16234f Migrate daily missions to profile content JSON, convert rewards to XP
Major persistence migration for the daily missions system:

Foundation:
- New blobbonaut-content.ts defines BlobbonautProfileContent type with
  dailyMissions shape, plus parseProfileContent/mergeProfileContent for
  safe read-modify-write of Kind 11125 JSON content
- BlobbonautProfile type extended with parsed content field
- parseBlobbonautEvent now parses event.content as structured JSON
  (empty string and invalid JSON handled gracefully)

Reward system changed from coins to XP:
- DailyMissionsState.totalCoinsEarned renamed to totalXpEarned
- All mission reward values rebalanced for XP (10-50 XP range)
- Bonus mission reward: 80 coins -> 50 XP
- useClaimMissionReward now awards XP to the active companion's
  experience tag (Kind 31124) instead of coins to profile
- XP award uses fetchFreshEvent for safe read-modify-write
- XP failure is non-fatal (mission claim still succeeds)
- UI labels updated: 'coins' -> 'XP' throughout

Profile content persistence (Kind 11125):
- useClaimMissionReward now persists mission state to profile content
  JSON via mergeProfileContent, preserving unrelated content fields
- Fresh profile fetched before each write to avoid overwriting
  concurrent changes from other tabs/devices
- Tags preserved unchanged during content-only updates

Backward compatibility:
- localStorage readMissionsState handles legacy totalCoinsEarned field
  by migrating it to totalXpEarned on read
- Daily reset logic in tracker, missions hook, and reroll hook all
  handle legacy field gracefully
- Profile content parsing tolerates empty, malformed, or partial JSON

Files changed:
- New: src/blobbi/core/lib/blobbonaut-content.ts
- Modified: blobbi.ts, BlobbiPage.tsx, useClaimMissionReward.ts,
  useDailyMissions.ts, useRerollMission.ts, daily-mission-tracker.ts,
  daily-missions.ts, BlobbiMissionsModal.tsx, DailyMissionsPanel.tsx
2026-04-06 03:26:57 -03:00
filemon 4ecb3209bd Merge branch 'main' into feat/blobbi-migrate-daily-missions 2026-04-06 03:08:47 -03:00
Alex Gleason 0a5e72efd0 release: v2.6.1 v2.6.1 2026-04-06 00:58:23 -05:00
Alex Gleason 0f1021e0d3 Switch nsite preview from local-shakespeare.dev to iframe.diy
Replace the local-shakespeare.dev preview domain with iframe.diy, which
provides a service-worker based sandbox. This brings the nsite preview
implementation in line with Shakespeare's approach.

Key changes:
- iframe.diy handshake: listen for 'ready', respond with 'init'
- Derive private HMAC-SHA256 subdomains via deriveIframeSubdomain('nsite', ...)
- Inject preview script into HTML responses for console forwarding,
  SPA navigation tracking, and /index.html path normalization
- Remove sandbox attribute (iframe.diy manages its own sandboxing)
- Serve injected script from virtual /__injected__/preview.js path
2026-04-06 00:51:39 -05:00
filemon 286572777b Fix stat crown instability on room switch; hide Closet from navigation
Stat crown fix — root cause and solution:
The heroWidth measurement used a useRef + useEffect([], ...) pattern
that created a ResizeObserver once on mount. When switching rooms,
BlobbiRoomHero unmounts and remounts inside the new room component,
creating a new DOM element. But the ResizeObserver was still watching
the old detached element, so heroWidth went stale (often falling to
the default 375px or reading 0 from the detached node).

Fix: replaced useRef + useEffect with a callback ref (heroCallbackRef)
that disconnects the old ResizeObserver and creates a new one every
time the ref is assigned to a new element. This ensures heroWidth is
always measured from the currently-mounted hero container.

Additionally, the arc spread and radius values were too extreme on
desktop (120/190/230 degrees, 300px radius) which made the stats
fly far apart. Now using balanced moderate values:
- Mobile: 80/110/140 degrees
- Desktop: 90/130/160 degrees
- Radius: smooth 110px → 200px interpolation based on container width

This produces a stable, moderate crown that looks the same on initial
render and after room switches, with desktop having slightly more
breathing room than mobile without being exaggerated.

Closet hidden:
Removed 'closet' from DEFAULT_ROOM_ORDER with a comment explaining
how to re-enable it. The BlobbiClosetRoom component, its type in
BlobbiRoomId, its ROOM_META entry, and its ROOM_COMPONENTS mapping
all remain intact — only the navigation sequence excludes it.
2026-04-06 02:34:57 -03:00
Alex Gleason be65c659b2 Derive private iframe.diy subdomains with HMAC-SHA256
The i-tag UUID used for webxdc coordination is attacker-controlled.
Using it directly as the iframe.diy subdomain lets a malicious event
author pick a subdomain that collides with another app's origin,
gaining access to its localStorage/IndexedDB.

Introduce a persistent random seed in localStorage (ditto:seed) and
derive the subdomain as base36(HMAC-SHA256(seed, prefix|identifier)).
The prefix (e.g. "webxdc") domain-separates different use-cases.
The subdomain is stable per device+app but unpredictable to event
authors.
2026-04-06 00:33:55 -05:00
filemon 7fd4b7ab69 Polish carousel stability, desktop stat spacing, poop UX, and visual harmony
Carousel stability:
- Focused item container is now overflow-hidden with explicit dimensions
  (w-20 h-[4.5rem] / sm:w-24 sm:h-[5.5rem]) so no content can push
  the layout wider/taller
- Label uses a fixed max-width (w-16 sm:w-20) with text-center truncate
  so changing items never shifts the arrows
- Preview slots also have overflow-hidden
- Empty state matches the populated carousel height

Desktop stat spacing (mobile unchanged):
- Arc spread widened to 120/190/230 degrees (was 100/160/200)
- Radius scaled up to 300px max (was 260px)
- This gives stat indicators much more breathing room on desktop
  without affecting the mobile layout at all

Poop placement refinement:
- Pre-computed safe-zone positions in lower-left and lower-right
  corners, avoiding the central Blobbi hero area
- Positions stored on each PoopInstance so they're stable
- Shovel mode: active state shows ring indicator around the button
  and label changes to 'Done'; poop gets drop-shadow-lg when
  hoverable; hover:scale-150 for clearer interaction feedback

Care room fix:
- Treat button no longer calls invalid 'interact' DirectAction;
  uses a small food item from shop instead
- Both left/right slots always render the same fixed width
  (RoomActionButton or w-14/w-20 spacer) so switching between
  hygiene/medicine items never causes layout shift

Visual harmony:
- Closet room bottom bar now uses the same flex items-center
  justify-center horizontal layout instead of a different
  flex-col structure, matching the rhythm of all other rooms
2026-04-06 02:28:53 -03:00
filemon c9525a0233 Add carousel stability, sleep overlay, care room conditional actions, poop system
Multiple polish and interaction improvements:

1. Carousel stability:
   Focused item area now uses fixed dimensions (w-20 h-16 / sm:w-24 sm:h-20)
   so the carousel never reflows when switching between items. Arrows stay
   perfectly stable. Preview slots also have fixed w-10 h-12 dimensions.

2. Desktop stat spacing:
   Arc spread widened further on desktop: 100/160/200 degrees (was 90/140/180)
   with radius up to 260px (was 220px). Stats now have much more breathing
   room on desktop while mobile stays unchanged.

3. Bedroom sleep button centered:
   Sleep/wake button is now in the center of the bottom bar instead of
   right-aligned, making the bedroom feel more focused.

4. Sleep dark overlay:
   When Blobbi is sleeping, a radial-gradient dark overlay covers the
   entire room shell (all rooms, not just bedroom). Uses z-20 with
   pointer-events-none so controls remain usable. Scoped to the room
   shell only — app header, bottom nav, and other app UI stay unaffected.

5. Renamed Bathroom → Care Room:
   Room label changed from 'Bathroom' to 'Care Room' with icon changed
   from bathtub to bandage emoji, since the room also contains medicine.

6. Care room conditional side actions:
   ItemCarousel now supports onFocusChange callback and meta field.
   When a hygiene item is focused: Towel (left) + Shower (right).
   When a medicine item is focused: Lollipop/Treat (left) + empty (right).
   Side buttons swap reactively as the user cycles through items.

7. Temporary local poop system:
   - poop-system.ts: ephemeral generation based on hunger >= 95 (overfeed,
     kitchen-only) and hours since last interaction (time-based, random room)
   - Generated once on mount, no persistence
   - Poop appears as floating emoji in affected rooms
   - Kitchen shows a Shovel button when any poop exists anywhere
   - Shovel mode: clicking poop removes it and awards 5 XP via toast
   - All rooms accept RoomPoopState prop for future expansion
   - BlobbiRoomContext extended with lastFeedTimestamp
2026-04-06 02:05:19 -03:00
filemon 0b9cd5e1cb Unify room surface, fix desktop proportions, clear mobile bottom nav
Three structural layout fixes:

1. Remove 'background cut' feeling:
   - Room header (label + dots) is now absolutely positioned over the
     room content instead of being a separate flex block above it. This
     eliminates the stacked-panels look where each section appeared to
     have its own background surface.
   - Hero container no longer uses overflow-hidden; uses pt-10 for
     header clearance instead. The room reads as one continuous scene.
   - Room shell content area fills entirely; header and nav arrows
     float over it as overlays.

2. Desktop proportions tuned (mobile preserved):
   - Blobbi visual reduced on desktop: size-72/size-80/size-96 ladder
     (was size-80/size-[28rem]/size-[32rem])
   - Stats crown arc spread widened on desktop: 90/140/180 degrees
     (was 80/120/160), with radius up to 220px (was 180px). This gives
     the stat indicators more breathing room on wide screens.
   - Stats crown margin: mb-4 sm:mb-8 (tighter on mobile, roomier on desktop)
   - Mobile stat sizing and spacing unchanged from previous pass.

3. Mobile/tablet bottom nav clearance:
   - New shared ROOM_BOTTOM_BAR_CLASS constant in room-layout.ts
   - On max-sidebar (below 900px), bottom bars add padding:
     calc(var(--bottom-nav-height) + env(safe-area-inset-bottom) + 1rem)
   - This pushes room controls above the app's fixed bottom navigation
     (Feed/Search/Notifications/Profile)
   - On desktop (sidebar:), normal pb-6 applies since there's no
     bottom nav bar.
   - All 6 rooms now use the shared class for consistent clearance.
2026-04-06 01:32:43 -03:00
Alex Gleason b64aa4b24a Add Content-Security-Policy to webxdc fetch responses
Apply a strict CSP header to every response served from the .xdc archive
to enforce the webxdc offline sandbox. Permits same-origin, inline, eval,
wasm, data: and blob: but blocks all external network access.
2026-04-05 23:20:21 -05:00
filemon a2600d1caa Fix room layout proportions, responsive sizing, and visual composition
Addresses layout issues where the room felt cut off and controls were
disproportionate, especially on mobile.

Key changes:

RoomActionButton — responsive sizing:
  Mobile: size-14 circle, size-7 icons
  Desktop: size-20 circle, size-9 icons
  Tighter gap-1 and text-[10px] labels on mobile

ItemCarousel — responsive behavior:
  Mobile: focused item only + compact arrows, no prev/next previews
  Desktop: focused item + translucent side previews
  Smaller focused item (text-4xl mobile, text-5xl desktop)

BlobbiRoomHero — reduced visual footprint:
  Blobbi visual: tighter responsive scale (size-48 -> size-60 -> size-80)
  Stats crown: reduced margin (mb-6 sm:mb-10 vs mb-14)
  Stat indicators: size-14 sm:size-[4.5rem] (was size-[4.5rem] sm:size-20)
  Glow blur reduced on mobile (-m-16 vs -m-24)
  overflow-hidden to prevent hero from clipping

HomeRoom — unified bottom bar:
  Removed -mt-10 negative margin hack that caused visual clipping
  Photo, Carousel, and Companion now sit in one flex row with
  items-center alignment, forming a single cohesive bottom composition

All rooms — standardized bottom bar pattern:
  Consistent px-3 sm:px-6 pb-4 sm:pb-6 pt-1 spacing
  flex items-center justify-between gap-1 sm:gap-3 layout
  Spacer divs match button widths (w-14 sm:w-20) for balance

Room shell content area uses flex flex-col to ensure rooms
fill the available vertical space correctly.
2026-04-06 01:15:11 -03:00
filemon 0722d900a2 Polish room UI: single-focus carousel, unified action buttons, add Rest room
Major improvements to room layout and visual consistency:

- ItemCarousel: single-focus carousel showing one main item at center
  with translucent prev/next previews and left/right navigation arrows.
  Replaces the horizontal scroll rows in Kitchen, Care, and Home rooms.

- RoomActionButton: unified circular button component matching the
  original Photo and Companion button visual language (radial glow
  background, consistent size, hover lift, label beneath).
  Used in all rooms for consistent visual weight.

- BlobbiRestRoom: new dedicated bedroom for sleep/wake behavior,
  with a Moon/Sun toggle as a RoomActionButton on the bottom-right.
  Sleep/wake removed from Home room.

- Updated default room order: care -> kitchen -> home -> hatchery ->
  rest -> closet (looped in both directions).

- All room bottom bars now use consistent px-4 sm:px-8 pb-6 spacing
  with items-start justify-between layout for left/center/right zones.

- HatcheryRoom left/right buttons (Blobbis, Quests) upgraded to
  RoomActionButton with badge support.
2026-04-06 01:02:14 -03:00
filemon 918814371c Refactor Blobbi dashboard into room-based navigation system
Replace the tab/drawer layout with a room-based system where each room
represents a specific area of Blobbi interaction:
- Care (bathroom): hygiene tools, medicine, towel, shower
- Kitchen: food carousel, fridge modal
- Home: toys, music, sing, photo, companion, lamp/sleep
- Hatchery: hatch/evolve progress, quests sheet, Blobbis sheet
- Closet: placeholder for future wardrobe

Architecture:
- Room types, config, and navigation helpers in src/blobbi/rooms/lib/
- BlobbiRoomShell manages current room state and left/right navigation
- BlobbiRoomHero shared component for the Blobbi visual + stats crown
- Each room is a self-contained component receiving BlobbiRoomContext
- Default room order is data-driven (DEFAULT_ROOM_ORDER array) to
  support future per-user customization
- Navigation direction tracked for future animated transitions
- All existing hooks, mutations, and flows preserved unchanged
2026-04-06 00:46:04 -03:00
Alex Gleason f63d8943d8 Replace webxdc.app with iframe.diy for webxdc sandboxing
Migrate the webxdc iframe runtime from webxdc.app to iframe.diy. Instead of
sending ZIP bytes to the iframe and having the SW unzip them, the parent now
unzips the .xdc archive and serves files via iframe.diy's fetch-proxy RPC.
A webxdc bridge script is served as a virtual /webxdc.js file, and a
<script> tag is injected into HTML responses via DOMParser to load it.

- Rewrite Webxdc.tsx to use iframe.diy's ready/init/fetch protocol
- Unzip .xdc archives on the parent side and serve via fetch RPC responses
- Serve webxdc bridge as virtual /webxdc.js via the fetch handler
- Inject <script src="/webxdc.js"> into HTML using DOMParser
2026-04-05 22:15:16 -05:00
Alex Gleason 517a72cce7 Fix profile avatar/banner lightbox appearing behind right sidebar
Portal ProfileImageLightbox to document.body, matching the fix
already applied to the shared Lightbox component. Without the
portal, the lightbox was trapped inside the center column's z-0
stacking context from MainLayout, causing the right sidebar
(a sibling outside that context) to paint on top.
2026-04-05 20:37:09 -05:00
Chad Curtis 6fc68766c9 Merge branch 'refactor/blobbi-remove-item-quantity-selection' into 'main'
Remove quantity selectors from Blobbi item usage flows

See merge request soapbox-pub/ditto!163
2026-04-06 00:38:27 +00:00
filemon ae81c13cc1 Merge branch 'main' into fix-items-blobbi-companion 2026-04-05 21:21:31 -03:00
filemon 41358d27ce Update comments and docs to reflect item-as-ability model
Replace outdated references to 'inventory items', 'consume',
'quantity', and 'storage decrement' across 14 files. Comments
now consistently describe items as reusable abilities sourced
from the shop catalog, not consumable inventory.
2026-04-05 21:18:35 -03:00
Chad Curtis ac8bffba23 Merge branch 'fix-items-blobbi-companion' into 'main'
Remove inventory ownership requirement from Blobbi companion items

See merge request soapbox-pub/ditto!162
2026-04-05 23:59:18 +00:00
filemon 748365de40 Remove quantity selectors from Blobbi item usage flows
Items are now single-use abilities — tap item, press Use, effect
happens immediately. No confirmation dialogs or quantity selectors.

Changes:
- Remove BlobbiUseItemConfirmDialog and InventoryUseConfirmDialog
- Remove quantity state, selectors, and multi-use loops from modals
- Simplify mutation hooks to always apply item effects once
- Drop quantity parameter from UseItemFunction type signature
- Update all call sites through the full stack (BlobbiPage, context,
  companion layer, companion item use hook)
2026-04-05 20:58:31 -03:00
filemon 361f8b9506 Remove inventory ownership requirement from Blobbi companion items
Items are now treated as abilities/tools unlocked by stage, not
consumable inventory that must be purchased. All catalog items are
shown in the companion action menu regardless of inventory quantity.

Changes:
- Source items from shop catalog instead of user inventory storage
- Remove quantity validation and storage decrement on item use
- Remove quantity badges and 'in inventory' text from all modals
- Keep stage-based filtering (egg vs baby/adult restrictions)
- Cap quantity selector at 99 instead of inventory count
2026-04-05 19:59:30 -03:00
Alex Gleason c1ec7a25ed Use published_at tag to show created/updated verbs in event action headers
Replaceable and addressable event headers now distinguish between
first publish and subsequent updates using the published_at tag:
- published_at == created_at → 'created' verb (e.g. 'created an emoji pack')
- published_at != created_at → 'updated' verb (e.g. 'updated an emoji pack')
- no published_at → 'shared' fallback for backward compatibility
2026-04-05 15:12:19 -05:00
Alex Gleason 272586d033 Merge branch 'main' of gitlab.com:soapbox-pub/ditto 2026-04-05 14:54:41 -05:00
Alex Gleason c77c098843 Add NIP-24 published_at to useNostrPublish for replaceable/addressable events
Extend useNostrPublish with an optional `prev` property on the event
template. For replaceable and addressable kinds, the hook automatically
manages published_at:

- First publish (no prev): set published_at equal to created_at
- Update (prev provided): preserve published_at from the old event
- Old event lacks published_at: don't fabricate one
- Caller already set published_at in tags: leave it alone

Callers pass `prev` when they have the old event from fetchFreshEvent,
giving the hook everything it needs without extra network requests.
Updated all 11 call sites that publish replaceable or addressable events.
Documents the prev convention in AGENTS.md.
2026-04-05 14:23:58 -05:00
Chad Curtis ea7afa94f7 Fix infinite scroll on custom profile tab feeds reloading same content
Two issues caused custom tab feeds (e.g. Magic Decks) to loop:

1. ProfileSavedFeedContent flattened pages without deduplication, so
   events returned by multiple pages rendered as visible duplicates.

2. useTabFeed only stopped paginating when rawCount === 0. For
   addressable events the relay keeps returning the same latest
   versions, so rawCount never hit zero. Changed to rawCount < limit
   (relay returned fewer than requested = exhausted).
2026-04-05 12:23:25 -05:00
Chad Curtis 0c29506402 Fix all 50 ESLint warnings by extracting non-component exports and adding missing deps
- Extract utility functions from component files into dedicated modules
  to fix react-refresh/only-export-components warnings:
  - parseBadgeDefinition -> src/lib/parseBadgeDefinition.ts
  - parseProfileBadges -> src/lib/parseProfileBadges.ts
  - getColors, paletteToTheme -> src/lib/colorMomentUtils.ts
  - parseDimToAspectRatio, eventToMediaItem -> src/lib/mediaUtils.ts
  - isAudioUrl, isImageUrl, isVideoUrl -> src/lib/mediaTypeDetection.ts
  - buildKindOptions, parseSelectedKinds -> src/lib/feedFilterUtils.ts
  - useVideoThumbnail -> src/hooks/useVideoThumbnail.ts
  - useEnvelopeDimensions -> src/hooks/useEnvelopeDimensions.ts
  - usePortalContainer -> src/hooks/usePortalContainer.ts
  - useAudioPlayer -> src/contexts/audioPlayerContextDef.ts
  - SubHeaderBar context/hooks -> src/components/SubHeaderBarContext.ts
  - EmotionDev hooks -> src/blobbi/dev/useEmotionDev.ts
  - BlobbiActions context def -> BlobbiActionsContextDef.ts

- Remove export from internal-only functions (useEventComments,
  parseEmojiPack, useScrollCarets, formatEffectSummary, getSortedEffectEntries)

- Fix react-hooks/exhaustive-deps warnings by adding missing dependencies
  to useEffect/useCallback/useMemo hooks across 14 files

- Fix logical expression dependency warnings by wrapping conditional
  values (tasks, pubkeys, authorPubkeys) in useMemo

- Move module-level constants (CORE_TAB_IDS, CORE_TAB_LABELS,
  DEFAULT_TAB_LABELS) out of ProfilePage component body

- Reorder usePushNotifications hooks so syncPreferences is defined
  before enable to fix block-scoped variable error
2026-04-05 11:57:31 -05:00
Chad Curtis b0609e7877 Make reaction emoji visible per-row in interactions modal
Replace grouped-by-emoji layout with a flat list where each reaction
row shows an inline emoji badge (similar to the zap amount badge).
Add an emoji summary bar at the top when multiple emoji types are
present. This makes it immediately obvious who reacted with what.
2026-04-05 11:07:55 -05:00
Chad Curtis 946be28b81 Use standard author layout for follow pack/set cards instead of content-first 2026-04-05 10:59:01 -05:00
Chad Curtis 89250c7472 Add kind action headers for follow packs and follow sets 2026-04-05 10:56:22 -05:00
Chad Curtis cfc7a0d31c Extract useActiveTabIndicator hook to deduplicate TabButton and SortableTabChip
The scroll-aware active indicator reporting and scroll listener logic was
duplicated between TabButton and SortableTabChip. Extract into a shared
useActiveTabIndicator hook in SubHeaderBar.
2026-04-05 10:47:34 -05:00
Chad Curtis 21003e3aed Add edit button for custom profile tabs and clear underline on tab removal
- Add pencil icon to SortableTabChip for editing existing custom tabs
- Wire onEdit to open ProfileTabEditModal with the existing tab data
- Clear the active arc underline when an active tab is removed (cleanup in useLayoutEffect)
- Round dnd-kit transform values to avoid sub-pixel rendering issues
2026-04-05 10:42:23 -05:00
Chad Curtis 93e8a6290f Merge branch 'fix/167-post-compose-box-renders-too-small-and-unclickable' into 'main'
fix: intermittent mobile compose modal collapse and unclickable input

Closes #167

See merge request soapbox-pub/ditto!146
2026-04-05 15:28:55 +00:00
Dmitriy E 47831ffa64 fix: intermittent mobile compose modal collapse and unclickable input 2026-04-05 10:27:43 -05:00
Chad Curtis 1533420320 Fix desktop tab overflow and add interest tab management in settings
SubHeaderBar: add left/right chevron scroll arrows on desktop when tabs
overflow, with gradient fade. Auto-scroll active tab into view and keep
arc hover/active indicators aligned during horizontal scroll.

ContentSettings: add Interest Tabs section with inline add/remove for
hashtags and geotags. Remove buttons always visible (mobile-friendly),
X icons use strokeWidth 4.
2026-04-05 10:20:58 -05:00
Chad Curtis e3ef542875 Fix desktop tab bar overflow: add scroll arrows and auto-scroll active tab into view
On desktop, overflowing feed tabs were completely inaccessible since the
scrollbar was hidden and there was no swipe gesture. Add left/right
chevron scroll buttons that appear only on desktop when tabs overflow,
with gradient fade indicators. Also auto-scrolls the active tab into
view when switching tabs, and keeps the arc hover/active indicators
aligned during horizontal scroll.
2026-04-05 10:15:01 -05:00
Chad Curtis 3bf55990c0 Fix missing bottom border on collapsed thread expand button
When a depth-collapsed 'Show X more replies' button was the last item
in a reply sequence, it lacked a bottom border separator. Added an
isLast prop to ExpandThreadButton that adds border-b when the button
terminates the visual sequence.
2026-04-05 09:50:52 -05:00
Chad Curtis 283b31813c release: v2.6.0 v2.6.0 2026-04-05 08:31:35 -05:00
Chad Curtis 6e1197a067 Redesign LinkFooter as compact icon+label chips 2026-04-05 08:27:37 -05:00
Chad Curtis b7d1fbf860 Fix mobile sidebar bottom links clipping into safe area 2026-04-05 08:09:21 -05:00
Chad Curtis 8fde660075 Fix Blobbi page missing bg-background/85 overlay on custom themes
DashboardShell uses fixed positioning on mobile, placing it directly
over the body background image. Without the bg-background/85 class
that MainLayout's center column provides, the raw background image
showed through unthemed. Add the same 85% opacity background overlay
used consistently across the rest of the app.
2026-04-05 07:29:06 -05:00
Chad Curtis 50c7d67928 Fix blobbi state resets caused by stale cache reads and invalidation races
All blobbi mutations now follow the read-modify-write pattern: fetch fresh
state from relays before mutating, then optimistically update the cache.
This prevents two classes of bugs:

1. Stale cache reads: mutations were reading from TanStack Query cache
   (30s staleTime) instead of relays, causing newer events to be silently
   overwritten with old stats when actions happened within the cache window.

2. Invalidation races: every mutation called invalidateCompanion() after
   the optimistic update, which triggered a refetch from relays before the
   just-published event had propagated, overwriting the optimistic data
   with the pre-mutation state.

Changes:
- ensureCanonicalBlobbiBeforeAction now fetches fresh companion + profile
  from relays (the read step) instead of using cached closure values
- useBlobbiCareActivity fetches fresh companion before streak updates
- Removed all invalidateCompanion()/invalidateProfile() calls after
  optimistic updates across every action hook
- updateCompanionEvent now updates ALL blobbi-collection query caches
  for the user, not just the specific d-tag list it was instantiated with,
  keeping BlobbiPage and companion layer caches in sync
2026-04-05 07:20:26 -05:00
Chad Curtis e355c43925 Fix cross-device settings sync and smart sync gate
Settings (theme, sidebar, etc.) changed on one device were not applied
on other devices. Three root causes:

1. NostrSync seeded lastSyncedTimestamp to remoteSync on first load,
   then the guard (remoteSync <= lastSyncedTimestamp) blocked the same
   data from being applied. Settings were never applied on page reload.

2. The encrypted settings query had staleTime: Infinity and
   refetchOnWindowFocus: false, so remote changes were never fetched.

3. useInitialSync was missing customTheme, corsProxy, faviconUrl, and
   linkPreviewUrl fields.

To avoid gating every F5 behind a spinner, a lastSync timestamp is
now persisted to localStorage whenever settings are applied. On reload,
InitialSyncGate checks this: if present, render immediately from
localStorage and let NostrSync hot-swap remote changes in background.
If absent (new browser, cleared storage), show the spinner until
settings load.
2026-04-05 06:55:05 -05:00
Chad Curtis 696204870d Fix custom theme not applying on new device login
Initial sync applied the theme mode (e.g. 'custom') from encrypted
settings but not the customTheme config (colors, fonts, background),
so the theme appeared broken on first login requiring manual setup
which also triggered an unwanted kind 16767 publish.
2026-04-05 06:33:43 -05:00
Chad Curtis 0a7e01d17c Match own-profile follow link style to the following/already-following states
Use the same icon + primary semibold text + full-width button layout
instead of muted small text with an outline button.
2026-04-05 06:17:52 -05:00
Chad Curtis dd87bc96ec Fix top nav arc overlapping letter compose picker drawer
Set hasSubHeader on LetterComposePage so the MobileTopBar uses a flat
rect instead of the down-arc variant, preventing the 20px arc overhang
from painting over the LetterEditor picker panel.
2026-04-05 06:15:34 -05:00
Chad Curtis a12d5db560 Add follow URI system with QR sharing and immersive follow page
Introduce a /follow/:npub deep link that auto-follows a user when
visited by a logged-in user, or presents an immersive business card
with a 'Follow on Ditto' CTA for logged-out visitors. The page applies
the target user's profile theme, renders their feed with infinite
scroll, and uses the same banner/avatar/arc styling as the main profile.

Add a FollowQRDialog that generates a themed QR code for the follow
URL. The QR colors are derived from the active theme: primary color
for modules (with contrast-safe darkening/lightening), and background
color for the QR background. Foreground text color is used when it is
colorful and offers significantly better contrast.

Surface the QR dialog from: own profile page (top-level button),
profile more menu, desktop sidebar account popover, and mobile drawer.
2026-04-05 06:01:48 -05:00
Alex Gleason 614634789c Merge branch 'main' of nostr://npub10qdp2fc9ta6vraczxrcs8prqnv69fru2k6s2dj48gqjcylulmtjsg9arpj/relay.ngit.dev/ditto 2026-04-04 23:17:35 -05:00
Alex Gleason 29696fa3d3 Apply nearest-neighbor scaling to small custom emoji images
Custom emoji images with natural dimensions <= 16x16 now render with
image-rendering: pixelated to preserve crisp pixels instead of blurring.

Also consolidates 6 direct <img> sites to use the shared CustomEmojiImg
component so all custom emoji rendering benefits from this behavior.
2026-04-04 22:58:42 -05:00
Chad Curtis ffc31e8e8f Merge branch 'fix/blobbi-reuse-existing-eggs' into 'main'
Fix repeated egg creation and reuse existing eggs during ceremony

See merge request soapbox-pub/ditto!158
2026-04-05 02:09:45 +00:00