Commit graph

9 commits

Author SHA1 Message Date
shakespeare.diy
29d41b81eb Stash: prevent data-loss clobber on save + confirm before deleting gear
Audited the Stash — a private, NIP-44-encrypted, Amber-sensitive surface.

DATA-SAFETY FIX (the important one):
The stash is a single replaceable event whose encrypted content is the WHOLE
item list, so every add/update/remove rewrites the entire event. The mutations
built the new list from the `stash` closure value — but that's legitimately
`undefined` before the decrypt completes, and with Amber the read query is
gated / staleTime:Infinity, so it may never have been decrypted at mutation
time. Result: adding one item while `stash` was empty/undefined could publish a
list containing only that item and silently WIPE all existing gear.

Fix: mutations now call `fetchLatestStash()` — a fresh relay fetch + decrypt of
the current stash immediately before saving — and build the new list on top of
that. It falls back to React Query cache (never an empty list) on failure, and
throws rather than risk a clobber if it truly can't load. saveStash also writes
the just-published value straight into the cache to keep reads consistent.

The extra decrypt per mutation is an acceptable Amber tradeoff: a mutation is
already a deliberate write (encrypt + sign prompt), and data integrity beats
prompt count.

UX:
• Delete now asks for confirmation via an AlertDialog (item name + a note that
  public items get unlisted). Previously a single tap wiped an item outright —
  destructive and, with Amber, a surprise signer prompt.

The rest of StashPage (login gate, encryption-required state, distinct
wishlist/stash/filtered empty states, skeletons, rarity cards, grid/list) is
already excellent — left as-is.

Bumps to 1.2.23 (Android versionCode 10223 auto-derived).

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-07 20:31:48 +07:00
shakespeare.diy
28cd59916a fix(amber): stop global SasquatchGuide from decrypting the stash on every page
usePersonalContext (consumed by SasquatchGuide, mounted in the app-wide Layout)
eagerly called useStash(), firing the encrypted-stash NIP-44 decrypt on EVERY
page load — one Amber popup per navigation. useStash now accepts { enabled },
and usePersonalContext passes enabled:false so it only reads the stash from
cache (populated when the user actually opens the Stash page). The AI's
getMyStash tool degrades gracefully to cached data. No behaviour change for
silent signers.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-07 19:15:00 +07:00
shakespeare.diy
b910141699 fix(amber): stop passive decryption from storming Amber on navigation (1.2.16)
With Amber as signer, every nip44 decrypt is a foreground intent (visible
popup). Several hooks decrypt inside auto-running queries, so Amber flashed on
navigation/focus and (worst case) storms — one popup per encrypted event, and
polling queries re-fired every few seconds.

New helper isInteractiveSigner() (signerCapabilities.ts) detects the Amber
(x-amber) signer, where each crypto op prompts the user. Hooks now gate passive
decryption for interactive signers only; extension/nsec/bunker keep their
silent, live-polling behaviour.

Fixes:
- useCrewDismissals (GLOBAL via header NotificationBell — the main offender):
  Amber users now use the instant localStorage mirror only, never passively
  decrypt the synced list; disabled refetchOnWindowFocus + Infinity staleTime.
  Dismissals still sync OUT on the write path.
- useDirectMessages (all 4 DM query hooks): added amberSafeQueryOptions() —
  no background polling and no focus refetch for Amber (these decrypt up to
  ~200 gift wraps × 2 each); silent signers keep the 60s poll.
- useStash / usePrivateContacts: disabled focus refetch + Infinity staleTime
  for Amber so the single decrypt runs once, not on every focus.
- useCordnChat: dropped the 5s live poll for Amber (was a popup every 5s);
  silent signers keep it.

Scanned every signEvent call site: all are inside mutations/user callbacks
(zaps, invites, DMs, chat, follows, purchases, NIP-98 auth) — no passive
auto-signing anywhere. Deliberate user actions still prompt Amber as intended.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-07 14:57:35 +07:00
shakespeare.diy
0ac5c1019d fix: eliminate all render-path nip44 getter crashes (Browser extension not available)
Root cause: NUser's extension signer exposes .nip44 as a getter that calls
.nostr (window.nostr) and THROWS when the extension isn't present. Any
synchronous access during render crashes the whole tree — the actual culprit
was useStash's `isEncryptionAvailable: !!user?.signer?.nip44` in its return
object, evaluated every render even when logged out.

- Add src/lib/signerCapabilities.ts: hasNip44/hasNip04 helpers that determine
  capability via login type + direct window.nostr probe (no throwing getter).
- useStash: use the helper for hasNip44 + isEncryptionAvailable
- useDirectMessages: replace all render-path `enabled: !!user?.signer?.nip44`
  with a pre-computed nip44Available across all 4 conversation hooks
- CrewChat, CrewRequestToJoin, ConversationPage, DirectMessagesPage,
  ContactsPage: use the safe helper in render/guard paths

Async accesses (inside queryFn/callbacks) are left as-is — they reject
gracefully within react-query and never crash render.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-30 19:37:08 +07:00
shakespeare.diy
73f51662af fix(stash): avoid nip44 getter entirely — check login type + window.nostr directly
The NUser extension signer's .nip44 getter calls .nostr which throws
"Browser extension not available" when window.nostr is absent. A try/catch
around ?.nip44 doesn't work because esbuild optimises it away.

Fix: check login type + window.nostr directly (no getter, no throw) to
determine nip44 availability, and wrap all actual signer accesses at
call-time in their own try/catch.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-30 19:21:28 +07:00
shakespeare.diy
4c4e7159ba fix(stash): guard nip44 getter to prevent crash when no signer/extension
Some signers throw "Browser extension not available" when the .nip44
getter is accessed rather than returning undefined. Wrap the check in a
try/catch so useStash never throws on mount for logged-out users or users
with signers that don't support NIP-44.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-30 19:15:44 +07:00
shakespeare.diy
9fc39651ca fix: remove MIT note from /license, fix Buy/Sell Gear button on /map, expand stash storage locations
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-16 11:21:45 +07:00
shakespeare.diy
4e227f0367 feat: License page, Video Editors in Lab, Buy/Sell Gear, Stash locations, Supporters page
1. /license page
   - New LicensePage with "YOU ARE FREE TO SKATEBOARD" license text
   - Copyright 2025-∞ with neon THUG2 styling
   - Footer: "License" and "Supporters" links added next to OpenSats

2. /lab — Video Editor category
   - New SKATE_EDITOR_KIND (35003) in types.ts
   - EDITOR_SERVICE_LABELS/DESCRIPTIONS (8 edit types)
   - EDITOR_SKILL_LABELS/DESCRIPTIONS (8 software/hardware skills)
   - SkateEditor interface + parseSkateEditor() parser
   - useLabProfiles updated to query kind 35003 + 'skateeditor' tag
   - LabPage: new "Video Editors" tab (purple, Scissors icon)
   - LabPage: "Offer Editing" button in header
   - BecomeEditorPage: full form with edit types, skills, hardware,
     software, portfolio, contact, location, wallet requirement
   - Route /become-editor registered in AppRouter

3. /map Quick Actions
   - "Sell Gear" → "Buy/Sell Gear" with ArrowRightLeft icon
   - Button now links to /marketplace (browse + sell)

4. /stash — Storage locations
   - StashItem.storageLocation field: home/warehouse/friends/parents/wardrobe/other
   - AddItemDialog: 6-button grid to tag where gear is stored
   - StashItemCard: shows location badge on each card
   - Filter bar: pill filter chips to filter stash by location
   - storageLocation saved/restored via onAdd handler

5. /supporters page
   - btcmap.org-style supporters page
   - Live Geyser GraphQL leaderboard (skatespots project)
   - Avatar grid with sat amounts, anon fallbacks
   - Mission section + "Send Sats" section (Lightning address + Geyser)
   - Added to footer and desktop "More" dropdown and mobile menu

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-16 11:05:03 +07:00
shakespeare.diy
e29d35f1e4 fix: restore full skate-spots source tree from misplaced src/src/ copy
The entire skate-spots codebase was accidentally copied into src/src/ and
public/public/ instead of src/ and public/ directly. This left the real
src/ directory as blank MKStack template stubs, explaining why the landing
page showed the default template instead of SkateSpots.

Changes made:
- Copied all files from src/src/ up to src/ (App.tsx, AppRouter.tsx, main.tsx,
  all pages, components, hooks, contexts, lib, i18n, test)
- Copied all files from public/public/ up to public/ (images, manifest, etc.)
- Deleted src/src/ and public/public/ nested directories
- Removed HelloWorld.tsx template stub from pages/
- Retained the previously fixed index.css (Tailwind v3 directives)

Full skate-spots feature set is now active:
- All 30+ routes (map, events, spots, coaches, marketplace, wallet, etc.)
- NWCProvider, LanguageProvider, PWAInstallPrompt, AmberCallbackHandler
- Skate community relays (goskatespots.nostr1.com, wotr.relatr.xyz, etc.)
- Outfit Variable font, Amber pre-check in main.tsx
- All hooks (useSkateSpots, useCoaches, useZaps, etc.)
- i18n support (en/es)

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-15 22:18:28 +07:00
Renamed from src/src/hooks/useStash.ts (Browse further)