- StashPage: Friends only / Add to Wishlist / Add Item / Edit Item / Public
stash / Uploading… / Add Photos / placeholders / item count / list-grid aria
labels now go through t() (t added to the sub-components).
- Manual Pro copy no longer claims the pass is already active: it says the pass
activates once the provider confirms the payment.
- Bumped low-contrast gray-600 guidance text to gray-400 (DM empty state, E2EE
note, stash item count).
- New src/lib/dateFnsI18n.ts re-exports date-fns with formatDistanceToNow /
formatDistanceToNowStrict / formatDistance / format defaulting to the active
locale, plus dateLocaleCode() for native toLocale{Date,Time}String calls.
- LanguageProvider keeps the locale in sync during render, so the first paint is
already in-language.
- Codemod: 11 date-fns imports now use the shim; ~39 native date/time calls pass
the app locale code instead of undefined/[]/hardcoded 'en-US'. Number
.toLocaleString() calls are deliberately untouched.
- 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.
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.
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)
- 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
- 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
- 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
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>
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>
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>
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>
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)