Commit Graph

3175 Commits

Author SHA1 Message Date
filemon 8d964d30da Add furniture edit mode for home room (kind 11127)
Implement the first furniture editing pass for the Blobbi House home room:

- Add edit mode toggle (Move/X button) in home room top-right corner
- Item selection with blue ring highlight on tap
- Long-press (~1.2s) to activate drag mode with animated progress ring
- Mouse and touch drag support with pointer capture
- Wall items constrained to wall plane, floor items to floor plane
- Positions clamped to 0..1000 normalized space with edge padding
- Persist position changes to kind 11127 on drag-end via fetchFreshEvent
- Preserve all sibling rooms, scene data, and unknown keys

New files:
- useRoomItemEditor hook (edit state + persistence)

Extended files:
- house-content: updateRoomItemPosition + patchRoomItem helpers
- item-coordinates: inverse mapping (pixel delta -> normalized delta)
- RoomItemsLayer: edit mode with selection, long-press, drag interaction
- BlobbiHomeRoom: wired edit toggle, banner, and editor callbacks
2026-04-08 23:38:37 -03:00
filemon 418ba446db Merge branch 'main' into feat/blobbi-room-scene-foundation 2026-04-08 23:27:43 -03:00
Alex Gleason 76d53859cf Simplify webxdc to always open in fullscreen panel 2026-04-08 20:47:46 -05:00
Alex Gleason e482afbd3f Fix sandbox origin isolation and Android build issues 2026-04-08 20:47:42 -05:00
Alex Gleason 11ff27efe2 Enable iOS swipe-back navigation and fix bottom nav layout 2026-04-08 20:47:37 -05:00
Alex Gleason 8f6f678132 Add safe area padding and fix fullscreen sandbox on iOS 2026-04-08 20:47:32 -05:00
Alex Gleason f25139103c Add native SandboxPlugin for iOS and Android 2026-04-08 20:47:28 -05:00
filemon 76dcf41cc9 Render floor items on the perspective-transformed floor plane
Floor items (backFloor, frontFloor) now live inside perspective containers
that replicate the floor scene geometry from RoomSceneLayer. Items
foreshorten naturally with the floor instead of feeling pasted flat.

Rendering model change:
- Wall layers (wallBack, wallDecor): still flat, full-viewport positioned
- Floor layers (backFloor, frontFloor): each gets its own perspective
  container matching the floor scene (perspective 600px, rotateX 22deg,
  height 160%, perspectiveOrigin 50% 0%)
- Floor item coordinates are now local to the tilted surface: x/y 0..1000
  maps to 0%..100% within the floor container
- Overlay layer: flat, full-viewport, above everything

Also:
- Export FLOOR_PERSPECTIVE, FLOOR_TILT, FLOOR_OVERFLOW from RoomSceneLayer
  so item layer reuses the exact same geometry constants
- Split toScreenPosition into toWallPosition/toFloorPosition for clarity
- Floor toScreenSize now uses 100% height reference (floor container)
  instead of FLOOR_PERCENT (full viewport)

Item seeding investigation:
- Confirmed buildDefaultHouseContent correctly includes DEFAULT_HOME_ITEMS
- JSON round-trip preserves all 3 items through serialize + parseHouseContent
- Legacy migration path (buildHouseWithLegacyData) also preserves items
- Empty items on existing houses are from bootstraps before the items commit;
  truly new houses created after this point will include starter items
2026-04-08 21:39:38 -03:00
filemon 5c3ebd8dfd Add furniture rendering foundation for home room (kind 11127)
Implement layer-based item rendering for the Blobbi House home room:

- item-catalog.ts: builtin item registry with 3 starter items (poster,
  rug, potted plant) — each defined with plane, layer, and normalized size
- item-coordinates.ts: converts persisted 0..1000 positions to CSS %,
  mapping wall items into the wall area and floor items into the floor area
- BuiltinItemVisual.tsx: inline SVG visuals for each builtin catalog item
- RoomItemsLayer.tsx: groups items into z-ordered layer containers
  (wallBack/wallDecor/backFloor/frontFloor/overlay) with Blobbi hero at z-5
- BlobbiHomeRoom reads items from house.layout.rooms.home.items and renders
  them via RoomItemsLayer as siblings of RoomSceneLayer
- Default home room seeded with 3 starter items for new houses
- Existing houses with empty items[] render nothing (no migration needed)
2026-04-08 21:22:53 -03:00
Alex Gleason 0028b506e7 Fix webxdc bridge: serve script via resolveFile instead of injectedScripts
SandboxFrame's virtual script serving intercepted /webxdc.js and served
the empty placeholder content before resolveFile was ever called. The
dynamically generated bridge script (which embeds selfAddr etc.) was
never reaching the iframe.

Move bridge serving and HTML injection into resolveFileWithBridge so
the content is served from bridgeScriptRef after onReady populates it.
2026-04-08 16:55:01 -05:00
Alex Gleason 926c27d51c Fix webxdc race condition: await onReady before sending init
The sandbox frame was sending init immediately and calling onReady
concurrently, so fetch requests arrived before the archive was
downloaded and unzipped. Now onReady is awaited before init is sent,
matching the original Webxdc behavior.
2026-04-08 16:50:44 -05:00
Alex Gleason c4454ee2a1 Refactor iframe.diy usage into unified SandboxFrame component
Extract duplicated sandbox protocol logic from NsitePreviewDialog and
Webxdc into a single SandboxFrame component. Shared utilities (MIME
types, base64, HTML injection, JSON-RPC types) move to src/lib/sandbox/.

Add configurable sandboxDomain to AppConfig so the iframe.diy domain
can be overridden via ditto.json, preparing for native Capacitor
implementations.

Strip unused console/navigation/error RPC from previewInjectedScript,
leaving only the /index.html path normalization.
2026-04-08 16:41:23 -05:00
filemon e90d657b98 Harden room navigation layer for safe furniture rendering
- Add deriveNavigableRooms() that filters house roomOrder against known
  IDs and enabled state, with safe fallback to defaults
- Replace unsafe BlobbiRoomId cast with deriveNavigableRooms in dashboard
- Add effect in BlobbiRoomShell to reset current room when roomOrder
  changes and current room is no longer in the list
- Thread houseLoading into dashboard and gate BlobbiRoomShell behind it
  to prevent flicker/temporary-default rendering during bootstrap
- Export isKnownRoomId and deriveNavigableRooms from house barrel
2026-04-08 17:49:53 -03:00
filemon a26f5ae626 Stabilize Blobbi House (kind 11127) architecture before furniture phase
- Fix bootstrap race: guard with in-flight ref instead of attempted-once flag,
  wait for profile event before bootstrapping to avoid missing legacy migration
- Thread parsed house + roomOrder through BlobbiRoomContext so consumers don't
  re-parse content; BlobbiRoomShell now receives room order from house layout
- Make parseHouseContent resilient: derive roomOrder from rooms map when empty
- Add alt tag (NIP-31) to house event tags for protocol compliance
- Mark legacy scene helpers (room-scene-content.ts, scene/defaults.ts) as
  deprecated with clear pointers to house content helpers
- Update JSDoc in scene types to reflect kind 11127 as source of truth
- Document kind 11127 in NIP.md with full event structure and tag spec
- isLoading now accounts for bootstrap-pending state (profile not yet loaded)
2026-04-08 17:31:40 -03:00
Chad Curtis e56737f776 Fix blobbi discovery: query by author instead of relying on profile.has[]
The Blobbi collection was previously discovered via the profile's has[] tag
list, meaning any blobbi whose d-tag was missing from that secondary index
would be invisible to the user despite existing on the relay.

Now useBlobbisCollection() without args queries all kind 31124 events by
author + ecosystem namespace tag — the user authored these events, so that
is the source of truth. The profile.has[] list is still used for selection
ordering preference, but no longer gates discovery.

The dList parameter remains available for targeted fetches (e.g. the
companion layer only needs one specific blobbi).
2026-04-08 11:02:03 -05:00
Chad Curtis feb6c1a9f6 Add drop shadow and solid gradient to overflow tab arrows 2026-04-08 10:27:17 -05:00
Chad Curtis 6f8d225597 Increase overflow tab arrow stroke to 4 and boost contrast 2026-04-08 10:22:04 -05:00
Chad Curtis 9ecd99a6a1 Add 'Write a letter' option to profile more menu
Adds a Mail-icon menu item in the profile more menu for other users'
profiles. Navigates to /letters/compose?to={npub} so the recipient is
pre-filled, matching the same flow used by the notification reply button.
2026-04-08 04:01:11 -05:00
Chad Curtis 287097627d Hide delivery method when push disabled; fix persistent description
Only show the delivery method radio group when push notifications are
enabled. Update the persistent option description to explain it is for
devices that don't support push notifications (e.g. GrapheneOS).
2026-04-08 00:20:20 -05:00
filemon 4c9afe6963 Introduce Blobbi House (kind 11127) and migrate room scenes from profile
Phase 1 of the Blobbi House migration: create a dedicated replaceable
event (kind 11127) for house/room data, moving room scene ownership
out of kind 11125 (Blobbonaut Profile).

New house domain (src/blobbi/house/):
- Kind 11127, canonical d-tag: blobbi-house-{pubkeyPrefix12}
- Tags: d, b (ecosystem), name, version
- Content: version, meta, layout (roomOrder + rooms map)
- Each room: label, enabled, scene (wall/floor/theme), items[]
- Future-ready item schema: kind, plane, layer, position (0..1000),
  scale, rotation, visible (items arrays empty for Phase 1)

Migration behavior:
- If 11127 exists: use as-is
- If 11127 missing + legacy roomCustomization in 11125: auto-migrate
- If 11127 missing + no legacy data: publish default house
- 11125 is never mutated during migration

Read/write pipeline migrated:
- useRoomScene now reads from houseEvent.content (11127)
- useRoomSceneEditor now writes to kind 11127
- BlobbiHomeRoom passes houseEvent + updateHouseEvent from context
- BlobbiRoomContext gains houseEvent + updateHouseEvent fields
- BlobbiPage creates useBlobbiHouse hook, threads through dashboard

Content safety:
- All house writes preserve unknown top-level keys, unknown rooms,
  roomOrder, items when editing scene, scene when editing items
- Kind 11125 write paths completely untouched (dailyMissions,
  progression, profile tags all preserved)
2026-04-07 20:34:21 -03:00
filemon b2536bfe64 Ground Blobbi on the floor with weighted flex spacers in hero layout
Replace justify-center with a 3:2 top:bottom flex spacer ratio so
Blobbi is consistently pushed toward the wall-floor junction (~60%
from top). This fixes the mobile grounding issue where Blobbi appeared
to float against the wall due to justify-center centering the entire
stats+visual+name group in the middle of a short viewport.

Root cause: RoomSceneLayer places the floor at a fixed 60% from the
top, but BlobbiRoomHero used justify-center which positions Blobbi
at 50% — well above the floor on mobile (small viewport makes the
proportional offset more visible). On desktop the taller viewport
made the gap less noticeable.

The 3:2 ratio places the content group's anchor at 60% from the top,
aligning the Blobbi body with the floor plane across all breakpoints.
2026-04-07 18:58:53 -03:00
Chad Curtis 3ee491a63b Add push vs persistent notification delivery option for Android
Default to push mode (no foreground service). Persistent mode with
the always-on background polling service is opt-in via the new
Delivery Method section in notification settings.

- Add notificationStyle ('push' | 'persistent') to EncryptedSettings
- Show radio group in NotificationSettings on native platforms
- Pass notificationStyle through Capacitor plugin to SharedPreferences
- DittoNotificationPlugin starts/stops foreground service on style change
- MainActivity only starts service on launch when style is persistent
- Re-enable unread polling on native when push mode is active
2026-04-07 10:54:30 -05:00
filemon 7e57d1ad6b Move Decor button to a small top-right frosted pill 2026-04-07 12:24:00 -03:00
filemon 290d40ac2e Add room customization UI, visual polish, and perspective tuning for home room POC
- Add lightweight customization sheet with wall/floor type selectors,
  color preset swatches (10 wall + 10 floor), theme colors toggle, and
  reset button. Decor button added to home room bottom bar.
- Add patchRoomSceneContent() for safe field-level partial updates that
  preserve sibling fields within the room scene and across all content sections.
- Add useRoomSceneEditor hook (fetchFreshEvent + patch + save + optimistic update).
- Polish shell surfaces: frosted-glass header pill, backdrop-blur nav arrows
  and bottom bar for readability over room backgrounds.
- Tune floor perspective: 600px distance, 22deg tilt, 60/40 wall-floor split,
  wider baseboard shadow for natural room feel across all floor types.
2026-04-07 12:19:25 -03:00
filemon 3b72cd88cc Add room scene foundation for Blobbi room customization (Phase 1 POC)
Implement the initial room-scene architecture for the home room:
- Declarative wall types (paint, wallpaper, brick) and floor types (wood, tile, carpet)
- Floor rendering with CSS 3D perspective transform for realistic depth
- Optional theme-based color derivation (palette input, not full replacement)
- Safe persistence in kind 11125 content under 'roomCustomization' section
- Scene layer renders behind Blobbi in the center content area only

Architecture: types, defaults, resolver, content helpers, render components,
and a useRoomScene hook — all isolated under src/blobbi/rooms/scene/.
2026-04-07 11:54:27 -03:00
Chad Curtis 7944f73da3 fix: use fetchFreshEvent and preserve non-p-tags in Follow All handlers
FollowPackDetailContent, TeamSoapboxCard, and InitialSyncGate all had
handleFollowAll implementations that queried kind 3 directly (bypassing
fetchFreshEvent) and rebuilt the tag array with only p-tags, silently
dropping all non-p-tags (relay hints, petnames, etc.). They also did
not pass prev for published_at preservation.

Align all three with the safe pattern already used in FollowPage and
useFollowActions.
2026-04-07 09:03:07 -05:00
Chad Curtis 17c1936817 Support follow pack/set naddr identifiers on /follow URL
The /follow route now accepts naddr1 identifiers for follow packs
(kind 39089) and follow sets (kind 30000) in addition to npub/nprofile.

Renders an immersive fullscreen layout with pack info hero, avatar
stack, big Follow All CTA with status indicator, and Feed/Members
tabs using the standard SubHeaderBar arc.

Follow All uses the safe fetch-fresh -> modify -> publish pattern
with prev for published_at preservation.

Shared components (PackFeedTab, MemberCard, MemberCardSkeleton) and
parsePackEvent are reused from FollowPackDetailContent and packUtils.

Also fixes SubHeaderBar tab indicator positioning when innerClassName
centers the tab container (adds containerOffset + ResizeObserver for
layout-dependent recalculation).
2026-04-07 08:55:27 -05:00
Chad Curtis c570f4689d Merge branch 'curated-ditto-feed' into 'main'
Curate Ditto feed by curator follow list with photos, divines, videos, and music

See merge request soapbox-pub/ditto!164
2026-04-07 12:52:23 +00:00
Chad Curtis 064ab1e101 Address MR review: extract feed hook, fix cache key, add error handling, make curator configurable
- Remove unused 'authors' parameter from useInfiniteHotFeed
- Extract inline query from Feed.tsx into useCuratedDittoFeed hook
- Use content-based fingerprint for query key instead of list length
- Add error state handling so curator fetch failure shows empty state
  instead of infinite skeletons for first-time visitors
- Move hardcoded curator pubkey to AppConfig (curatorPubkey) so it
  can be overridden via ditto.json without a code change
- Remove LANDING_KINDS/LANDING_WEBXDC_FILTER from Feed.tsx (now in hook)
2026-04-07 07:48:23 -05:00
filemon 96387d9941 Merge branch 'main' into feat/blobbi-persist-daily-mission-progress 2026-04-06 21:06:00 -03:00
Alex Gleason 9c0d49b904 Add OPFS as blocked API in lockdown-mode skill 2026-04-06 18:42:45 -05:00
filemon 9848d84f4f Persist intermediate daily mission progress to kind 11125
Add useDailyMissionsPersistence hook that debounces writes of all
intermediate mission state (currentCount, completed, rerolls, daily
resets) to kind 11125. Previously only claimed rewards were persisted,
so progress was lost on page refresh or device switch.

The hook listens for daily-missions-updated DOM events dispatched by
the tracker, reroll hook, and daily reset. It debounces writes by 2s,
skips no-op writes via fingerprint comparison, and skips events from
the claim hook (which already persists immediately). Uses the standard
fetchFreshEvent -> updateDailyMissionsContent -> publishEvent path,
which preserves progression, unknown keys, and all sibling sections.

Kind 11125 is now the real source of truth for the full daily mission
state. The in-memory session store is only a short-lived UI cache.
2026-04-06 20:31:53 -03:00
filemon ff758b078c Merge branch 'feat/room-navigation-labels' into feat/blobbi-profile-progression-foundation 2026-04-06 20:01:19 -03:00
filemon fe5221e973 Merge branch 'main' into feat/blobbi-profile-progression-foundation 2026-04-06 19:55:31 -03:00
filemon c48079406d Remove localStorage as source of truth for daily missions
Replace localStorage with an in-memory session store. Kind 11125
content JSON is now the ONLY persistent source of truth for daily
missions.

Architecture:
- On page load / account switch, useDailyMissions hydrates from
  profile.content.dailyMissions (parsed from the kind 11125 event)
- During the session, progress/rerolls update an in-memory Map
- Claims persist to kind 11125 via updateDailyMissionsContent()
- On page refresh the Map is empty → re-hydrates from kind 11125
- Unclaimed progress is lost on refresh (intentional tradeoff vs
  cross-account leakage)

What changed:
- daily-missions.ts: replaced localStorage helpers with in-memory
  Map<pubkey, DailyMissionsState> (sessionStore). readDailyMissionsState
  and writeDailyMissionsState now operate on the Map, not localStorage.
  Added clearDailyMissionsState. Removed getDailyMissionsStorageKey.
- useDailyMissions.ts: accepts persistedDailyMissions option (from
  profile.content.dailyMissions). Hydrates from kind 11125 when the
  session store is empty. Uses persistedMissionToMission() (previously
  defined but never called).
- useClaimMissionReward.ts: reads from session store instead of
  localStorage. Still persists to kind 11125 on claim.
- useRerollMission.ts: reads/writes session store only.
- daily-mission-tracker.ts: reads/writes session store only. Removed
  ensureCurrentState (no longer creates state — the hook handles init).
- BlobbiPage.tsx: passes profile?.content.dailyMissions to useDailyMissions
- BlobbiMissionsModal.tsx: passes profile?.content.dailyMissions to
  useDailyMissions

localStorage usage for daily missions: ZERO
- No localStorage.getItem calls for blobbi:daily-missions
- No localStorage.setItem calls for blobbi:daily-missions
- No blobbi:daily-missions key referenced anywhere in the codebase

Remaining risks:
- Unclaimed progress (feed counts, rerolls) is lost on page refresh
  since only claims persist to kind 11125. A future enhancement could
  persist intermediate state on a debounce, but this is out of scope
  for the foundation phase.
2026-04-06 19:07:34 -03:00
Alex Gleason 69634e7c05 Update lockdown-mode skill with cross-platform availability info
Lockdown Mode is not iOS-only — it's available on iOS 16+, iPadOS 16+,
watchOS 10+, and macOS Ventura+. Add platform availability section with
Apple Support reference link, rename report file to ios-report.txt to
clarify it's iOS-specific, and broaden the skill description.
2026-04-06 16:13:57 -05:00
Alex Gleason db48ce7c40 Add raw diagnostic report as skill reference file 2026-04-06 16:05:52 -05:00
Alex Gleason 36c6e537a7 Add lockdown-mode agent skill with iOS Lockdown Mode API reference 2026-04-06 15:59:29 -05:00
filemon 76623cd510 Fix daily missions leaking between accounts, add DEV progression panel
Root cause: The localStorage key 'blobbi:daily-missions' was shared
across all accounts. When switching users on the same device/day,
needsDailyReset() only checked the date (not the pubkey), so Account B
inherited Account A's entire mission state — progress, claimed status,
rerolls, and lifetime XP.

Fix: Scope all localStorage reads/writes by pubkey.

New centralized helpers in daily-missions.ts:
- getDailyMissionsStorageKey(pubkey) → 'blobbi:daily-missions:<pubkey>'
- readDailyMissionsState(pubkey) — returns null for missing/undefined pubkey
- writeDailyMissionsState(pubkey, state) — no-ops for undefined pubkey

Replaced all 4 duplicated local read/write functions:
- useDailyMissions.ts: now reads from pubkey-scoped key, re-reads on
  pubkey change (account switch)
- useClaimMissionReward.ts: reads/writes with user.pubkey
- useRerollMission.ts: reads/writes with user.pubkey
- daily-mission-tracker.ts: reads/writes with pubkey, no-ops when
  logged out

Account isolation now works:
- Each pubkey has its own localStorage slot
- Switching accounts reads from the new user's slot (or null → fresh)
- Logged-out state never persists (pubkey guard)
- Anonymous mode cannot contaminate logged-in state

DEV-only progression test panel:
- ProgressionDevPanel component (localhost dev mode only)
- Buttons: +10 XP, +50 XP, +200 XP, +1 Level, Reset
- All writes flow through updateProgressionContent + upsertLevelTag
- Uses fetchFreshEvent + prev for safe read-modify-write
- Accessible from Blobbis panel → 'Level' button
- Wired into BlobbiPage and room context
2026-04-06 17:58:44 -03:00
filemon f1b0868e30 Harden kind 11125 content infrastructure with centralized section updates
Centralize all kind 11125 content writes through section-specific helpers
that guarantee independent sections never overwrite each other.

New file: content-json.ts
- safeParseContent() with ParsedContentResult (parseOk flag + dev warnings)
- updateContentSection() generic helper for any top-level section
- Dependency-free to prevent circular imports between blobbonaut-content
  and progression modules

Redesigned: blobbonaut-content.ts
- Added updateDailyMissionsContent() as the standard daily missions write path
- Re-exports safeParseContent/updateContentSection for backward compatibility
- Deprecated mergeProfileContent() with JSDoc pointing to new helpers
- Added comprehensive source-of-truth documentation in module header

Updated: progression.ts
- Imports safeParseContent from content-json.ts (eliminates circular dep)
- Added standard write path documentation

Refactored: useClaimMissionReward.ts
- Replaced mergeProfileContent() with updateDailyMissionsContent()
- mergeProfileContent now has zero callers (kept for backward compat)

Tests: 54 new tests across 2 test files
- content-json.test.ts: 17 tests for safeParseContent + updateContentSection
- content-coexistence.test.ts: 37 tests covering all coexistence guarantees
  (progression/dailyMissions isolation, sibling game preservation, unknown
  key preservation, malformed data safety, invalid JSON recovery, global
  level derivation, future section scalability, legacy content handling)
2026-04-06 17:16:59 -03:00
Alex Gleason cbc3df0bef Allow any dev server host via ALLOWED_HOSTS env var 2026-04-06 14:40:31 -05:00
filemon ffdf6f0f36 Add Blobbi progression foundation (Phase 1)
Introduce the progression system as a new section inside kind 11125
content JSON, alongside the existing dailyMissions field. No existing
behavior is changed — this establishes the safe, scalable foundation
for future game progression features.

New file: src/blobbi/core/lib/progression.ts
- TypeScript types for progression structure (global + per-game)
- Default Blobbi game progression (level 1, xp 0, starter unlocks)
- deriveGlobalLevel() — always sum of game levels, never primary
- parseProgression() — validates raw JSON safely with fallbacks
- mergeProgression() — conservative deep merge preserving siblings
- upsertLevelTag() — mirrors global level into queryable tag
- updateProgressionContent() — centralized entry point for all
  future progression writes (preserves dailyMissions + unknowns)

Modified: blobbonaut-content.ts
- Added progression field to BlobbonautProfileContent interface
- Wired parseProgression into parseProfileContent for validation

Modified: blobbi.ts
- Added 'level' to MANAGED_BLOBBONAUT_PROFILE_TAG_NAMES

Tests: 31 new tests covering all helpers and edge cases
2026-04-06 16:37:48 -03:00
Alex Gleason 2ecd557430 Fix IndexedDB crash on iOS Lockdown Mode
openDatabase() now catches errors from idb's openDB() (which throws
synchronously when indexedDB is undefined) and returns null. All
consumers — profileCache, nip05Cache, dmMessageStore — check for null
and silently degrade to in-memory only.

The DM message store also stops re-throwing errors, which previously
could produce unhandled rejections in DMProvider.
2026-04-06 13:41:32 -05:00
filemon c965ff27c4 Merge branch 'main' into feat/room-navigation-labels 2026-04-06 12:27:03 -03:00
filemon f5f7c90ce4 Add destination labels and nudge animation to room navigation arrows
Show the destination room name next to each chevron arrow to improve
discoverability. On desktop the label reveals on hover/focus via a
max-width transition; on mobile the label is always visible at reduced
opacity. Arrows also receive a subtle horizontal nudge animation and
scale-up on hover to reinforce clickability.
2026-04-06 12:25:28 -03:00
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
Alex Gleason 594e7ea8fa ci: add build-web job to produce downloadable artifact
The old 'pages' job was removed when deploying switched to nsite,
which broke the artifact download URL on the docs site. This adds
a new build-web job that builds the web app on main and saves the
dist/ directory as a downloadable artifact.
2026-04-06 01:09:10 -05: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