Commit graph

12 commits

Author SHA1 Message Date
SkateSpots
087f490acc refactor(layout): hoist Layout into the router so the chrome mounts once
- AppRouter wraps every route (except /amber-callback and the 404) in ONE
  <Route element={<Layout/>}> with an <Outlet/>; the chrome now persists across
  navigation instead of being torn down and rebuilt on every page.
- Layout: children optional (falls back to <Outlet/>); the map is full-bleed via
  a route check (isFullBleed) instead of a per-page noPadding prop.
- All 59 pages + CraftProfileForm/ErrorState/NewsletterIssueView now render
  fragments instead of <Layout> (structure-preserving), imports dropped.

Removes the whole remount class: chrome no longer re-runs its hooks per
navigation, Sasquatch chat state survives navigation, and the header can't
re-open Amber on every page change.
2026-09-27 13:41:42 +07:00
SkateSpots
15901976ef feat(marketplace): wire Cashu escrow into the trade flow (native)
Completes the escrow feature on top of the tested core (src/lib/cashuEscrow.ts):
- useCashuEscrow: native-only hook wiring createEscrow (cashu-ts mint + NWC/WebLN
  funding), redeemEscrow, escrow-key derivation (coco keyring) and profile publish
- EscrowKeyCard (Wallet): advertise the kind-0 'cashu_escrow' pubkey
- EscrowRequestDialog (listing card): buyer locks funds (P2PK to seller, refundable
  after N days) and sends the 31872 carrying the cashu_token
- EscrowPanel + 'Redeem escrow' in the DM request banner (seller side); the panel
  is offline/parse-based so it renders on web too
- Buyer 'Escrow & request' supersedes the plain request when available

EXPERIMENTAL: mint-touching paths are unverified on a device (native-only wallet);
use small amounts first. Gate: 101 tests, 0 errors.
2026-09-26 21:08:36 +07:00
SkateSpots
b91df1b4b0 feat(marketplace): trade confirmation requests (kind 31872) + fix boot smoke test
Marketplace:
- Buyer can 'Ask seller to confirm' a sold listing -> publishes a 31872
  Attestation Request (d/a/p), which the seller fulfils in one tap
- Seller sees incoming requests in the DM thread ('Confirm this sale?') and the
  fulfilling 31871 carries the spec's 'request' tag
- useTradeAttestationRequest / useIncomingTradeRequests / useListingTradeRequest
- NIP.md documents 31872 and states escrow is intentionally NOT implemented
  (coco-cashu has no P2PK/HTLC locking, wallet is native-only, and release-on-
  attestation needs a witness) so nothing looks trustless that isn't

Hardening:
- scripts/smoke-test.mjs navigated to /settings etc. WITHOUT a hash, so with
  HashRouter it only ever loaded the homepage and reported PASS. It now puts the
  route in the hash, covers settings/stash/collections/stats/marketplace, and
  fails on the 'This page slammed' error boundary (not just boot failure)
2026-09-26 20:47:29 +07:00
SkateSpots
1577ddc86f feat(marketplace): trust-weight trade reputation + in-thread trade confirmation
- Reputation now exposes per-counterparty verified counts; the profile card
  trust-weights each confirmed trade by the counterparty's web-of-trust score
  (fresh-account rings can't inflate it) and labels Highly trusted / Building /
  New trader
- usePendingTradeConfirmations finds sales a seller recorded that name you but
  you haven't mirrored; the 1:1 DM thread shows a 'Did you complete this trade?'
  banner with one-tap Confirm
2026-09-26 20:22:37 +07:00
SkateSpots
09e1612db7 fix(a11y): Batch E part 1 — labels, valid nesting, touch targets
- EmptyState: render <Link> as the button (a <button> inside <Link> was invalid
  and double-focusable); AboutPage <a href> -> <Link> (no full reload)
- aria-label on icon-only controls: settings Back, conversation Refresh/Send,
  crew chat Send
- Crew invite Accept/Decline raised to 44px touch targets
2026-09-26 18:46:51 +07:00
SkateSpots
f4192e2f3d fix(ux): Batch A part 1 — dead flows
- SpotDetailPage: 'Claim This Shop' was a no-op button; wire to NIP-05 settings
  (and stop the full-page <a href> reload)
- TourDetailPage: 'Contact Guide to Book' used /messages?to= which nothing read,
  so booking silently opened an empty inbox -> /messages/:npub
- ContactsPage: row 'Message' went to the generic inbox -> the contact's thread
- SettingsPage: ?section=follows|language|stash rendered a blank dead end;
  unknown sections now fall back to the menu
- DirectMessages/Conversation: logged-out states were dead ends -> add LoginArea
- Conversation: failed sends were silent -> error toast
2026-09-26 17:43:19 +07:00
shakespeare.diy
01d35823cc a11y: announce live updates for notifications, wallet balance, DMs (U3)
Add aria-live="polite" to the three dynamic surfaces screen-reader
users otherwise miss:
- Notification bell unread-count badge (with an "N unread notifications"
  label so the number is announced meaningfully, not read as bare digits)
- Cashu wallet balance value (announces the new balance after a
  send/receive completes)
- Direct-message conversation thread: role="log" + aria-live so
  incoming messages are announced as they arrive

ToastViewport already announces via the Radix default and was left
untouched.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-24 19:39:09 +07:00
shakespeare.diy
48b127f7b0 Amber UX: honest multi-approval hint for encrypted chat/DM sends
Audit outcome for the two suspected "double-prompt" Amber flows:

• Image upload — CONFIRMED already a single signature. BlossomUploader
  (even at the pinned 0.47.1) signs one kind-24242 auth event and reuses
  it across all servers via Promise.any, so uploads = 1 Amber prompt. No
  change needed.

• DM / crew-chat send — the multiple prompts are protocol-inherent and
  MUST NOT be optimized away: NIP-17 seals+signs once per recipient (and
  to self); a Cordn crew message signs an author proof then encrypts once
  per roster member. Reducing the count would weaken confidentiality or
  authorship. So instead of a code shortcut we add transparency.

New <AmberApprovalHint>: a tiny heads-up shown ONLY to interactive
(Amber) signers before an inherently multi-approval action, telling them
how many Approve screens to expect and that it's normal. Renders nothing
for silent signers (extension/nsec/bunker), where the ops are invisible.

Wired into:
  • CrewChat composer — count derived from transport + roster size
    (Cordn: roster+1; NIP-17: roster*2).
  • ConversationPage (1:1 DM) composer — 4 approvals (encrypt+sign to the
    recipient and to yourself).

Dependencies: reviewed the full manifest; deliberately left unchanged.
Every upgrade candidate is either on the critical crypto/Nostr/payments
path (nostrify, nostr-tools, cashu, bitcoinjs, noble, scure — pinned for
safety) or a documented major bump (lucide v1, react 19, react-router 7)
with breaking changes and no user-facing benefit. No security advisories
force a change, and caret ranges already pull safe patch/minor updates.
"It must work" wins over churn.

Bumps to 1.2.20 (Android versionCode 10220 auto-derived).

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-07 20:08:39 +07:00
shakespeare.diy
aea66d11e2 Fix 78 pre-existing TypeScript errors surfaced by npm test
- useDirectMessages: correct nip44 getConversationKey (Uint8Array) + finalizeEvent EventTemplate (remove pubkey/unused ephemeralPubkey/bytesToHex)
- Access current-user metadata via useCurrentUser() spread instead of user.metadata across ~10 files (rename to userMetadata where author metadata already bound)
- useCrewGoal: explicit UseQueryResult<CrewGoal|null> return type (fixes CrewForm/CrewTreasuryCard {} inference)
- New useCommentCounts hook; fix EventsPage/MapPage misuse of useComments(string[])
- CoachDetailPage: typed pget/pbool accessors for craft-specific union fields
- MapPage: shop entries include tags/category to satisfy SkateSpot
- SkateGlobe: read event.tags; LabPage: remove duplicate serviceType; LabCrewsStrip: add 'judging'
- Icon prop widened to accept style; ReportDialog subtype guard; SecuritySettings/TourCard/EventsPage comparisons; CashuWallet onClick; ConversationPage recipients; useLoggedInAccounts NLoginType import; LicensePage icon; TourDetailPage ZapButton target + real npub link; TestApp useSkateRelay

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-03 21:18:02 +07:00
shakespeare.diy
c51d9f68d4 DM inbox: real unread indicators (was hardcoded to 0)
useConversations reported unreadCount: 0 for everything, so the inbox never
showed new messages. Add a lightweight, reliable unread signal:

- useDMRead: per-conversation, per-user last-read timestamps in localStorage
  (local-only; compares against the inner rumor's real created_at, not the
  fuzzed gift-wrap time, so it's accurate).
- DirectMessagesPage: a conversation is unread when its latest message is from
  the other person and newer than last-read — rendered as a red dot, bolder
  name, brighter preview text, and a subtle highlighted card.
- ConversationPage: opening a chat marks it read up to the newest message, so
  the dot clears on return (each page mounts fresh, so no stale state).

Scope: kept to the Messages page. Deliberately did NOT add a global nav badge,
which would require background-decrypting all gift wraps on every page — the
same heavy cost avoided for the crew bell.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-03 18:35:13 +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
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/pages/ConversationPage.tsx (Browse further)