Commit Graph

4447 Commits

Author SHA1 Message Date
Alex Gleason e83f550b89 Resize and convert images to JPEG before upload in post composer
Images attached via ComposeBox are now resized (max 1920px) and
converted to JPEG before uploading, matching the BackgroundPicker
behavior. Non-image files (audio, video, .xdc) are unaffected.
Blurhash is computed from the resized file for consistency.
2026-03-21 10:46:43 -05:00
Alex Gleason 0a86eb70c5 Resize and convert background images to JPEG before upload
Background images uploaded via BackgroundPicker are now resized to a
max of 1920px on the longest side and converted to JPEG (quality 0.85)
before uploading. Images that are already small JPEGs are passed through
unchanged. This reduces file size and improves load times when visiting
profiles with custom backgrounds.
2026-03-21 01:16:50 -05:00
Mary Kate Fain 6bd870ff50 feat: add Created Badges page for editing/deleting published badges
New page at /badges/created lets users manage badge definitions they've
published. Features:
- Lists all kind 30009 events authored by the current user
- Edit: inline form to update name, description, and image (republishes
  the addressable event with updated tags, preserving t/tier/price tags)
- Delete: NIP-09 deletion request with confirmation dialog
- Award: quick access to AwardBadgeDialog from each row
- View: link to the badge detail page (naddr)
- Back arrow to Badge Shop

Also added 'Created Badges' quick-action link on the Shop tab.
2026-03-21 00:36:18 -05:00
Mary Kate Fain ae1bf95ac6 fix: update BADGE_ACCOUNT_PUBKEY to match actual published badges 2026-03-21 00:32:31 -05:00
Mary Kate Fain bd05aa7677 fix: query badges from dedicated Ditto Badge account pubkey
Both AchievementsPage and ShopPage now query badge definitions from
the BADGE_ACCOUNT_PUBKEY constant (npub10qdp2fc9ta6vraczxrcs8prqnv69fru2k6s2dj48gqjcylulmtjsg9arpj)
instead of config.nip85StatsPubkey.
2026-03-21 00:28:01 -05:00
Mary Kate Fain 0aa43cd779 Merge remote-tracking branch 'origin/main' into experimental-badges
# Conflicts:
#	src/index.css
2026-03-21 00:00:41 -05:00
Alex Gleason 852b6faa69 Verify signed event pubkey matches current account before publishing
Adds a check in useNostrPublish that compares the pubkey on the signed
event against the currently selected user's pubkey. This prevents
publishing events from a mismatched signer (e.g. a browser extension
or bunker configured for a different account).
2026-03-20 23:55:01 -05:00
Mary Kate Fain 62e5372c64 feat: add Accept Badge button to badge award notifications
Each badge award notification now shows an inline Accept button that
calls useAcceptBadge. Once accepted, it changes to a checkmark with
'Accepted' text. For grouped notifications, each award gets its own
accept button.
2026-03-20 23:40:34 -05:00
Mary Kate Fain 45e06fdbc0 fix: add back arrow to Badge Create page pointing to Badge Shop 2026-03-20 23:38:15 -05:00
Alex Gleason 7a53aa2238 Add vanish preview in reply composer for kind 62 events
When replying to a Request to Vanish event, the reply composer now
shows a styled vanish preview instead of the generic author+content
card that looked broken for nuked accounts.
2026-03-20 23:37:48 -05:00
Alex Gleason 4b1f015d97 Include kind 62 vanish events in the home feed
Register kind 62 in the EXTRA_KINDS feed system so Request to Vanish
events from followed users appear in the home feed. Enabled by default
via the feedIncludeVanish toggle, and can be disabled in feed settings.
2026-03-20 23:35:50 -05:00
Alex Gleason 7ed142fb9c Add embedded vanish card for quoted kind 62 events
When a Request to Vanish event is quoted/embedded inside another
post, it renders as a compact card with caution stripes, the glitch
icon, title, npub, and optional reason — matching the dramatic style
of the full vanish display.
2026-03-20 23:26:22 -05:00
Mary Kate Fain 0d3b19af3d fix: always show back arrow on My Badges page (not a sidebar page) 2026-03-20 23:21:12 -05:00
Mary Kate Fain d8f042584f fix: point My Badges back arrow to Badge Shop instead of home 2026-03-20 23:19:21 -05:00
Alex Gleason d0ef3e2575 Add dramatic NIP-62 Request to Vanish display (kind 62)
Implement a visually striking display for kind 62 vanish events with
animated caution stripes, glitch effects, and a grand exit aesthetic.
Since the user has nuked their account, the npub is shown instead of
profile info. Supports both compact feed cards and full detail view
with comments and sharing.
2026-03-20 23:17:25 -05:00
Mary Kate Fain f0efbf683d refactor: merge community badge feed into Badge Shop as tabs
Badge Shop (/shop) now has 3 tabs following the ThemesPage pattern:
- Shop: admin badge grid with categories, search, prices (unchanged)
- Follows: NoteCard feed of badge events from followed users
- Global: NoteCard feed of all badge events

The standalone BadgesFeedPage is deleted — its content now lives as
the Follows/Global tabs inside the Shop page.

- Add useBadgeFeed hook (modeled on useThemeFeed) for infinite scroll
- Remove 'badges' sidebar item, keep 'shop' and 'achievements'
- Add 'badges' → 'shop' sidebar ID migration so existing users
  who had Badges in their sidebar seamlessly get Badge Shop instead
- Redirect /badges → /shop for backward compatibility
- Add quick-action links to Create Badge and My Badges on Shop tab
2026-03-20 23:16:55 -05:00
Alex Gleason 077a918de2 Add BookMarked icon back to the left of the repo name 2026-03-20 23:06:13 -05:00
Alex Gleason 40ff6c11ba Revert repo name to default text color for consistency with other cards 2026-03-20 23:05:24 -05:00
Alex Gleason 0cd5dd372d Simplify GitRepoCard: remove icon, hashtags, and Public badge
The activity indicator header above the post already shows the git
branch icon and 'shared a repository' text, making the in-card icon
redundant. Hashtags and the Public badge add noise without value.
2026-03-20 23:04:13 -05:00
Alex Gleason 0838efc7df Redesign GitRepoCard to mirror GitHub repository card layout
Use a GitHub-inspired structure: repo icon + name (as primary-colored
link text) + Public/Fork pill badge on the first line, description on
the second line, and hashtags rendered as language-dot metadata in a
compact bottom row. Clone URL and action buttons follow below.
2026-03-20 23:03:14 -05:00
Alex Gleason 3e859f9b74 Redesign development kind cards with bordered card style and activity indicators
Wrap GitRepoCard, PatchCard, PullRequestCard, and CustomNipCard content
in rounded-2xl bordered containers matching the quote post / embedded
note visual pattern. Add KIND_HEADER_MAP entries for all four dev kinds
(30617, 1617, 1618, 30817) so the feed shows activity labels like
'shared a repository', 'submitted a patch', 'opened a pull request',
and 'proposed a NIP' above each post.
2026-03-20 23:01:48 -05:00
Mary Kate Fain e0a5702374 refactor: query achievements from admin instead of hardcoding
Achievements are now queried as kind 30009 badge definitions from the
admin pubkey with t:achievement tag, grouped by category t-tags.
Removed the static achievementCatalog.ts - the admin publishes all
achievement badges to relays and the client discovers them dynamically.
2026-03-20 22:55:32 -05:00
Mary Kate Fain bf646b557e feat: implement comprehensive badge & achievement system (NIP-58)
Add full badge system with achievements, shop, creation, and management:

- 11 new hooks for badge queries, mutations, and DVM claims
- 4 new pages: Achievements, Badge Shop, Badge Create, Badge Manage
- 4 new shared components: BadgeShowcaseGrid, BadgeThumbnail, BadgeTierPill, AwardBadgeDialog
- Badge showcase on ProfileCard and ProfileHoverCard
- Badge award notifications (kind 8) in notification feed
- Enhanced BadgeDetailContent with accept/award/buy actions
- Achievement catalog (73 achievements across 8 categories)
- Shop category system with admin-issued badge support
- Sidebar items for Achievements and Badge Shop
- CSS animations for badge glow and confetti effects

Built entirely on standard NIP-58 (kinds 30009, 8, 30008) with
NIP-90 DVM for achievement verification and NIP-57 zaps for shop.
2026-03-20 22:51:01 -05:00
Alex Gleason 23e55ffff7 Consolidate IndexedDB into a single 'ditto' database with multiple stores
Replace three separate per-hostname databases (nostr-nip05-cache-*,
nostr-profile-cache-*, nostr-dm-store-*) with one shared 'ditto'
database containing three object stores: nip05, profiles, messages.

A single db.ts module manages the connection and upgrade logic, and
caches the promise so only one connection is created per page lifetime.
2026-03-20 21:59:48 -05:00
Alex Gleason d9e943d03c Add kind 0 profile IndexedDB cache to eliminate author loading skeletons
Profile events (kind 0) are now persisted in IndexedDB and hydrated into
memory before React renders. useAuthor seeds TanStack Query with
initialData from the cache so names, avatars, and metadata render
instantly on repeat visits instead of showing skeletons.

Cache writes happen from three sites: useAuthor (single profile fetch),
useAuthors (batch fetch), and useFeed (community feed metadata prefetch).
Only newer events overwrite older ones to prevent out-of-order downgrades.

Staleness tiers match NIP-05: < 5 min fresh, 5 min - 7 d background
refetch, > 7 d expired (skeleton until fresh data arrives).
2026-03-20 21:52:48 -05:00
Alex Gleason ddd9bb06f4 Add staleness tiers to NIP-05 cache: fresh / background recheck / expired
Cached entries now follow three tiers based on age:
- < 1 hour: fresh, rendered instantly with no network request
- 1 hour – 7 days: rendered instantly, background refetch updates cache
- > 7 days: discarded, shows skeleton until fresh verification completes
2026-03-20 21:11:03 -05:00
Alex Gleason 318f8fcc8a Add NIP-05 IndexedDB cache to eliminate loading skeletons on repeat visits
Successful NIP-05 resolutions are now persisted in IndexedDB with a
lastVerified timestamp. On subsequent page loads the cache is hydrated
into memory before React renders, so TanStack Query receives initialData
and skips the pending state entirely — verified NIP-05 names appear
instantly instead of showing a skeleton placeholder.

Failed lookups are not persisted. Stale entries are evicted when the
domain responds but the identifier is no longer present.
2026-03-20 21:01:39 -05:00
Alex Gleason d0f458f985 Go back to using Shakespeare's corsProxy (we don't even use it in Ditto yet) 2026-03-20 19:14:13 -05:00
Alex Gleason d92be3e341 Swap out default favicon and link-preview APIs 2026-03-20 16:23:50 -05:00
filemon ffab9d3aaa feat(blobbi): add dev-only instant stage transition button
- Add 'Dev Hatch' button for eggs (bypasses incubation tasks)
- Add 'Dev Evolve' button for babies (bypasses evolution tasks)
- Button only appears in dev mode (import.meta.env.DEV)
- Uses dashed amber border style to clearly indicate dev-only
- Reuses existing onHatch/onEvolve callbacks
- No button shown for adults (no further transitions)
2026-03-20 18:15:30 -03:00
filemon 4d16e1ab83 Merge branch 'main' into feat-blobbi 2026-03-20 18:13:32 -03:00
Alex Gleason 8a6b524e85 Update default corsProxy 2026-03-20 16:09:18 -05:00
filemon 6781685252 update typo in epsy url in blobbi 2026-03-20 18:03:52 -03:00
filemon 88bdf87e95 feat(blobbi): reduce evolve post requirement from 3 to 1
Make evolve process lighter and less repetitive:
- EVOLVE_REQUIRED_POSTS: 3 → 1
- Update task name: 'Create Posts' → 'Share Evolution'
- Update task description: 'Share 3 posts about evolving' → 'Post about your Blobbi evolving'
- Reduce query limit since only 1 post needed

Hatch process unchanged - still requires 3 posts with hatch-specific validation
2026-03-20 17:29:14 -03:00
filemon 6b3d98bd66 refactor(blobbi): unify task process with useActiveTaskProcess hook
- Add useActiveTaskProcess hook to consolidate hatch/evolve task logic
- Rename useSyncHatchTaskCompletions to useSyncTaskCompletions
- Fix badge to include dynamic tasks (was only counting persistent)
- Fix 'Edit Wall' task link to point to /settings/profile
- Reduce duplication in BlobbiPage.tsx by using unified hook
- Export new hook and types from blobbi/actions index
2026-03-20 16:07:38 -03:00
filemon ec7ceb2352 fix(blobbi): fix task tag writing and unify badge logic for both processes
BUG 1 - task/task_completed tags not being written:
- incrementInteractionTaskTags now accepts requiredInteractions param
  (7 for hatch, 21 for evolve) instead of hardcoded value
- useBlobbiDirectAction and useBlobbiUseInventoryItem now increment
  interactions for BOTH incubating AND evolving states
- useSyncHatchTaskCompletions now syncs for both processes

BUG 2 - evolving missions not affecting badge:
- remainingTasksCount now uses active process tasks (hatch or evolve)
- allTasksComplete now checks both incubating and evolving states
- BlobbiBottomBar uses isInTaskProcess instead of isIncubating

Architecture rules preserved:
- Persistent tasks: can be cached in task/task_completed tags
- Dynamic tasks: NEVER stored in tags, UI-only
- No infinite loops, no retroactive increments
- Interactions only increment during real user actions
2026-03-20 16:01:15 -03:00
Alex Gleason a9449bd009 Fix jumpy home feed: layout shifts, background refetches, and pre-filtering
- Add min-height placeholder to InlineImage to prevent layout shifts when
  images load from 0px to natural height
- Remove 60s refetchInterval from useFeed that caused all pages to re-sort
  in the background, triggering full feed re-renders
- Pre-filter deprecated/hidden events at Feed level (shouldHideFeedEvent)
  instead of returning null inside NoteCard after hooks have already run
- Stabilize author info skeleton/loaded height with consistent min-h-[42px]
  to prevent cascading layout recalculations
2026-03-20 13:56:26 -05:00
filemon c62c38b136 feat(blobbi): implement evolve task system with dynamic stat requirements
Add comprehensive evolution task system parallel to incubation:

Architecture:
- Separate persistent tasks (event-based, cacheable in tags) from
  dynamic tasks (stat-based, never cached, recomputed every render)
- Both hatch and evolve require all persistent AND dynamic tasks complete

New hooks:
- useEvolveTasks: 6 persistent tasks + 1 dynamic stat task (all stats >= 80)
- useStartEvolution/useStopEvolution: manage evolution state transitions

New components:
- TasksPanel: generalized task display for both hatch and evolve
- StartEvolutionDialog: confirmation dialog for starting evolution
- Updated BlobbiMissionsModal to handle both hatch and evolve flows

Evolve tasks (baby → adult):
- Create 3 themes, 3 color moments, 3 evolve posts
- 21 interactions, use Blobbi shape, edit wall once
- Dynamic: maintain all 5 stats >= 80

Key fixes:
- Renamed isEvolving variable collision in BlobbiPage
- Filter dynamic tasks from sync to prevent tag pollution
- Updated BlobbiPostModal to support evolve posts dynamically
2026-03-20 15:31:02 -03:00
DanConwayDev 33bf59f353 fix: remove unexported NLoginBunker import and prefer-const lint error 2026-03-20 17:22:38 +00:00
DanConwayDev b4d3c4833c feat: remote signer UX improvements for Amber/NIP-46 users on Android
Amber users on Android who manually approve events must switch from Ditto to
Amber to approve, then switch back. Backgrounding Ditto can freeze its
WebSocket, causing the NIP-46 response to be silently dropped — leaving the
operation hanging with no feedback and no way out. Users with Amber
notifications working correctly are unaffected, as approving via notification
does not background Ditto.

Even with auto-approve enabled, kinds outside Amber's default whitelist
require manual approval. Ditto uses several of these regularly: kind 1059
(NIP-17 gift-wrap DMs), 1111 (comments), 1311 (live chat), 31925 (RSVPs),
24242 (Blossom file upload auth), and 30078 (app settings).

This introduces signerWithNudge, a NostrSigner wrapper with the following
behaviour:

Nudge toast after 4 seconds
If a signing or encryption op is still pending after 4 s, a persistent toast
appears naming what is being approved (e.g. 'Approve file upload auth'), with
a human-readable label derived from the event kind.

Android 'Approve in signer' button
On Android the nudge toast includes an 'Approve in signer' button that opens
Amber via the nostrsigner: URI scheme, keeping the WebSocket alive. After
tapping, the button becomes a spinner and a Cancel button appears.

Automatic retry on foreground resume
When Ditto returns to the foreground after being backgrounded, it
automatically retries the pending NIP-46 request (up to 2 times) and shows a
brief 'Checking for signer response' toast.

Hard 45-second timeout
Operations with no response within 45 s are rejected with a clear error.

Cancel / Skip
The nudge toast has a Skip link throughout. After tapping 'Approve in signer'
it becomes a full Cancel button.

Multi-phase encrypt-then-sign
Saving app settings (kind 30078) and mute lists (kind 10000) require a nip44
encrypt followed immediately by a signEvent. When the encrypt nudge was shown,
a phase-transition toast tells the user a second approval is coming. The check
is kind-specific to avoid false positives.

Success feedback
A brief 'Approved' toast confirms the outcome when the nudge was shown.

Relay connectivity check
At nudge time, if the bunker relay WebSocket is not OPEN the toast warns
'Signer relay unreachable' instead of prompting for an approval that cannot
be delivered.

Accessibility
Toast buttons meet the 44 px touch target minimum. Text size and contrast
were increased for readability on small screens.
2026-03-20 17:22:37 +00:00
filemon 44b54f6c32 fix(blobbi): fix remainingTasksCount memo and allTasksComplete condition
- remainingTasksCount now depends on hatchTasks.tasks (not just completedTaskIds)
  to correctly reflect loading state and task changes
- allTasksComplete is now a memoized value that prevents false positives by
  checking: isIncubating && !isLoading && tasks.length > 0 && remaining === 0
- Changed missions badge symbol from '?' to '!' when all tasks complete
2026-03-20 12:02:12 -03:00
filemon 57f7af3141 refactor(blobbi): stabilize task sync, fix hatch content, improve UI indicators
Task Sync Stability:
- Remove hatchTasks.tasks from useEffect dependencies (was causing instability)
- Derive tasksToSync and remainingTasksCount via useMemo keyed off completedTaskIds
- Effect dependencies now only include stable primitives: completedTaskIds,
  cachedCompletedIds, hatchTasks.isLoading, companion?.state

Content Fix on Stage Transition:
- Add generateBlobbiContent() helper with correct grammar ('an egg' vs 'a baby')
- useBlobbiHatch now generates new content: '{name} is a baby Blobbi.'
- useBlobbiEvolve now generates new content: '{name} is an adult Blobbi.'
- Content always reflects current stage (was keeping old egg content)

Missions Icon Badge:
- Show remaining task count when incubating with tasks remaining
- Show '?' badge (success variant) when all tasks complete during incubation
- Badge variants: default (blue), warning (amber), success (emerald)

Blobbies Icon Badge:
- Now shows count of Blobbies needing care (any stat < CARE_THRESHOLD=40)
- Warning variant (amber) when there are needy Blobbies
- Only shows badge when count > 0

Selector Modal Warning:
- Add AlertTriangle indicator in top-right corner for Blobbies needing care
- Uses same companionNeedsCare() logic as bottom bar badge
2026-03-20 11:44:48 -03:00
filemon 23dbd9112a fix(blobbi): prevent infinite loop in task sync, add missions badge
Infinite Loop Fix:
- Add useRef anti-loop memory (lastSyncedKeyRef) to track last synced key
- Mark key as synced BEFORE calling sync to prevent race conditions
- Add guard for companion.state !== 'incubating'
- Remove syncTaskCompletions from useEffect deps (intentional, prevents
  re-triggering when mutation function reference changes)
- Reset ref on error to allow retry

Missions Badge:
- Add remainingTasksCount prop to BlobbiBottomBar
- Show badge on Missions button when incubating with incomplete tasks
- Update BottomBarButton to show badge when count > 0 (was > 1)

The interaction task tags were already being updated correctly by
incrementInteractionTaskTags() in useBlobbiDirectAction and
useBlobbiUseInventoryItem hooks during real user interactions.
2026-03-20 11:19:41 -03:00
filemon 44c8103600 refactor(blobbi): make incubation flow explicit, require Blobbi name in posts
- useStartIncubation now requires explicit mode ('start', 'restart', 'switch')
  instead of auto-detecting behavior. This makes the flow predictable.
- StartIncubationDialog determines mode and passes it to onConfirm callback
- Removed useUpdateTaskProgress hook (architecturally inconsistent - updated
  last_interaction during cache-only sync, violating the rule that only real
  user actions should update timestamps)
- BlobbiPostModal now requires blobbiName and process props for stage-aware
  post generation with Blobbi name as first hashtag
- isValidBlobbiPost() now validates the Blobbi name hashtag is present
- Added sanitizeToHashtag helper to both BlobbiPostModal and useHatchTasks
  for consistent hashtag generation
2026-03-20 10:28:10 -03:00
filemon d2441b345b fix(blobbi): make task sync fully idempotent and safe
Audit and hardening of hatch task cache sync:

1. BlobbiPage useEffect:
   - Use useMemo to create stable string keys for completion comparison
   - Only trigger sync when computed completions differ from cached
   - Skip sync entirely if no diff exists
   - Add dev-only debug logs

2. useSyncHatchTaskCompletions:
   - Remove last_interaction update (this is cache sync, not user action)
   - Add double diff check: first against companion.tasksCompleted, then against canonical.allTags
   - Return detailed result with skip reasons for debugging
   - Add dev-only debug logs for all sync decisions

3. incrementInteractionTaskTags:
   - Add check for already-completed state to prevent duplicate task_completed tags
   - Return previousCount for debugging
   - Add dev-only debug logs
   - Document that this is NOT idempotent by design (each call = real interaction)

Key guarantees:
- Cache-only sync never mutates last_interaction
- Multiple renders with same data = no publish
- WebSocket updates or refetches = no publish unless real diff
- No duplicate task_completed tags possible
2026-03-20 10:13:19 -03:00
filemon 2c828c8778 feat(blobbi): add missions modal, single incubation enforcement, and task sync
Phase 2 of Blobbi incubation audit:

- Add BlobbiMissionsModal component with HatchTasksPanel integration
- Move hatch tasks UI from main page to Missions modal
- Add useStopIncubation hook with confirmation dialog
- Enforce only one Blobbi incubating at a time (auto-stops previous)
- Enhance StartIncubationDialog to show switch warning for other incubating Blobbi
- Add useSyncHatchTaskCompletions hook to sync task completions to kind 31124 tags
- Consolidate STAT_MIN/STAT_MAX to single source in src/lib/blobbi.ts
- Remove unused BlobbiPlaceholderModal component
2026-03-20 09:59:50 -03:00
filemon 8097f0e5fb fix(blobbi): fix selection reset bug, stat safety, and migration visual traits
- Remove auto-save effect that was overwriting user selection during
  WebSocket/query updates. User selection now only persists via explicit
  handleSelectBlobbi() call.
- Add debug logging to trace selection changes in development mode.
- Add STAT_MIN=1 and STAT_MAX=100 constants to blobbi-decay.ts and update
  clamp() to use STAT_MIN instead of 0, preventing soft-lock issues.
- Fix buildMigrationTags() to always derive and include all visual traits
  during migration, ensuring every migrated event has complete visual data.
2026-03-20 09:47:11 -03:00
Alex Gleason c7d7ac3c7c Swap followers and following order on profile pages 2026-03-20 04:18:27 -05:00
Alex Gleason b1f81016a4 Add followers count and paginated followers list to profile page
Display NIP-85 follower count next to the existing following count on
profiles. Clicking the count opens a paginated modal that fetches
followers via kind:3 #p queries with infinite scroll.
2026-03-20 04:10:39 -05:00
Alex Gleason d65a4d81cc Merge branch 'feat/profile-field-weather' into 'main'
feat/profile-field-weather

See merge request soapbox-pub/ditto!86
2026-03-20 07:58:00 +00:00