Commit graph

126 commits

Author SHA1 Message Date
SkateSpots
4ae6b1d5ea feat: envelope-only DM unread in the bell 2026-10-28 22:43:52 +07:00
SkateSpots
d2a24679f0 feat(chat): on-device translation pilot (English ↔ Spanish)
- @xenova/transformers 2.17.2 in a Web Worker; Opus-MT en↔es ONNX q8 packs
  self-hosted on the relay VPS (/models + CORS); ONNX Runtime 1.14 WASM
  self-hosted in public/ort/ (37MB) loaded from ../ort/.
- Hard network guard: only model GET/HEAD (no query) and /ort/ assets allowed.
  Message text travels only via postMessage; 9 tests prove fetch is never called
  with the text and every other request is blocked.
- TranslateMessage in DMs + crew chat; Settings auto-translate off by default;
  memory-only translations; per-model download progress; 12 UI strings in all
  24 locales.
- Both model directions verified from the hosted files; tsc, 138 tests, build
  and smoke green.
2026-10-27 10:49:54 +07:00
SkateSpots
fe3d9bae22 ui(auth): compact, scroll-safe sign-in dialog
- Fix the real bug: the dialog clipped its bottom links (flex column + max-h
  calc(100dvh-2rem), content min-h-0 flex-1 overflow-y-auto).
- One dominant action per state: 'New here' is a compact row, not a second hero.
- Plain card labels (Browser extension / Signer app / Sign in with your phone)
  with tiny NIP-07/46/55 hints; protocol jargon out of the main labels.
- Bunker: input + Connect with instructions collapsed under 'Where do I get a
  bunker?'; no duplicate heading/divider.
- Secret key moved to a quiet bottom link; subtitle says 'or keep browsing'.
- 8px spacing grid (px-5 gutters, space-y-4 sections, gap-2 cards, p-2.5 cards,
  448px max width). New strings backfilled into all 20 locales.
Verified with headless 1280x900 + 390x844 screenshots.
2026-10-26 12:12:36 +07:00
SkateSpots
27084a4e7b fix(amber): send crew messages one signer intent at a time
Sending to a crew fired every recipient's gift wrap in parallel (Promise.all),
and the sealed-relay crew chat encrypted to the whole roster in parallel. Amber
is a single foreground activity: concurrent nostrsigner: intents mean only one is
answered and the rest sit until the 60s native timeout rejects with 'Amber didn't
respond'. Both paths now go sequentially for an interactive signer (parallel kept
for silent signers). Also corrected the approval hint (2 per member: encrypt +
sign, not 1 per recipient).
2026-10-13 00:06:49 +07:00
SkateSpots
fa0fc1cc15 ux(cordn): reconcile the crew chat card with the MLS tab (+ audit)
The sidebar card badged 'Private chat - On' while the MLS tab said 'not created
yet', and led with external apps. Rewrote CrewCoordinatorCard to show two
explicit rows (Chat = NIP-17 always on; Private chat = MLS with real status) and
make the in-app chat the primary CTA (?tab=mls); demoted Cordn web / Staircase
under 'Open in another app'. Panel now gives join feedback + an admin invite
hint. Adds CORDN_UX_AUDIT.md (findings, terminology model, deferred items).
2026-10-12 23:41:29 +07:00
SkateSpots
4bdb949f06 feat(messages): Crews section in the Messages hub -> each crew's private chat
- DirectMessagesPage gains a 'Crews' block listing the viewer's crews with an
  Open/Set-up badge (from one local IndexedDB read via useCordnGroupGids),
  linking into /crew/<pk>/<id>?tab=mls.
- Crew page tabs are now URL-addressable (?tab=events|chat|mls); an explicit
  param wins over the auto-default. useUserCrews + cordnGroupGids added.
2026-10-12 23:35:35 +07:00
SkateSpots
3ae05a0c38 fix(amber): stop DM/read hooks re-decrypting on every navigation
For an interactive signer (Amber) each nip44 decrypt opens the app. The DM/NIP-17
read hooks decrypt 2x per gift wrap and DirectMessagesPage mounts two of them, so
navigating to Messages stormed Amber. Strengthened amberSafeQueryOptions for
interactive signers (staleTime/gcTime Infinity, refetchOnMount/refetchOnReconnect
false) so decryption happens once per session, mirroring useStash; same guard on
usePrivateContacts + useCordnChat. Added a manual Refresh to the Messages header
(Amber gets no polling). Gated useSyncPendingPosts' legacy re-sign loop for
interactive signers.
2026-10-12 23:28:03 +07:00
SkateSpots
9251aea5b1 feat(cordn): crew Cordn group link-out (Level 1)
Crews can carry a Cordn group ref (['cordn', <cordn1-ref-or-url>] -> SkateCrew.cordnGroup): CrewForm gains a 'Cordn group' field and CrewCoordinatorCard shows an 'Open crew chat' (cordn.net/chat/<ref>) + 'Copy for Staircase' row. Verified a real ContextVM client (@contextvm/sdk) connects + lists tools against our self-hosted coordinator.
2026-10-12 21:46:03 +07:00
SkateSpots
3d9234d65c feat(cordn): web + Staircase MLS entry points on the crew coordinator card
Relabel the cordn.net add-link accurately as 'Cordn (web)' (real MLS in the browser) and add a 'Copy for Staircase' button that copies the coordinator nprofile for the native app. Our coordinator by default; a BYO coordinator's add-link when it has a pubkey.
2026-10-12 17:23:32 +07:00
SkateSpots
e262aed938 feat(cordn): 'Open in Staircase' link on the crew coordinator card
CrewCoordinatorCard now offers a StaircaseLink in both branches: our coordinator (SKATESPOTS_COORDINATOR add-link) when a crew has none, and the crew's own coordinator add-link when it has a pubkey (falls back to the Cordn home otherwise). Lets crews jump to real MLS chat in the native Cordn client.
2026-10-12 17:04:34 +07:00
SkateSpots
a8e411faa0 feat(cordn): embedded coordinator on the relay, crews default to it, disclosure
Newlay runs its embedded Cordn coordinator (identity cordn.key, uid 10001). SKATESPOTS_COORDINATOR is the provided default: Settings -> Coordinator shows a 'Use SkateSpots coordinator' block and CrewForm prefills it for new crews; BYO coordinator stays client-side. Disclosure copy (coordinators see ciphertext + metadata, never contents) in socialUi.coordinator.ourDefault* + existing privacyNote.
2026-10-12 16:36:56 +07:00
SkateSpots
b0c4684b4a feat(onboarding): first-run checklist with dynamic crew step
A one-shot 'Get started' nudge on the map for freshly signed-up users: Add a spot / crew / wallet. The crew CTA adapts to whether any crew exists yet — 'Join a crew' (/crews) when there are some, 'Start your crew' (/create-crew) when there are none. Dismissal persists; logged-out visitors see the welcome banner instead.
2026-10-12 15:15:35 +07:00
SkateSpots
54882285b6 feat(weather): 6-day forecast
FORECAST_DAYS=6 in lib/weather.ts is the single source for the fetch length and the UI labels (spot card + map popup). New count-interpolated labels spotUi.forecastDays / mapUi.forecastDays (en/es; other locales fall back to English). Legend colours moved to lib/skateabilityLevels.ts to keep the react-refresh lint at zero.
2026-10-12 13:51:29 +07:00
SkateSpots
0b645d6d67 feat(lab): add Clothing Designer craft (kind 35015)
New data-driven Lab craft, mirroring the artist role: three skill
sections (What You Make [required] / How You Make It / Materials) plus
minimum order, turnaround, size range, samples and customisation extras,
inheriting the shared Lab extras (rate, payment, gallery, reel,
languages, availability, booking).

Wired through types.ts (kind, label/description maps, parse, collective-
capable), labelKeys, useLabProfiles (kind list + dispatch), becomeRoles
(role card), craftFields, LabPage (tab, filters, offer button, empty
state, counts, deep links), LabCrewsStrip, NIP.md, and en/es i18n
(labOptions, becomeRolesUi, labUi). /become/clothing added to the smoke
route list. tsc, eslint, check-i18n, vitest 101/101, build and smoke all
pass.
2026-10-10 19:30:47 +07:00
SkateSpots
f1497fc0bf feat(marketplace): close the deal from an accepted offer
Seller UX: the offer thread shows a Close deal button on an accepted,
unsold listing. It opens MarkSoldDialog prefilled with the buyer, the
agreed amount, and the settlement method (lightning when a payment pointer
was attached, else cash), so the seller never pastes an npub by hand.
MarkSoldDialog gains defaultBuyer/defaultAmountSats/defaultMethod and
re-seeds on open. New marketUi.offers.closeDeal (en/es).
2026-10-10 15:51:58 +07:00
SkateSpots
057ef0a8f3 fix(marketplace): localize trade state and pending status
Trade-record state (revoked/verifying) and the listing pending status were
printed raw. New marketUi.trades.stateRevoked/stateVerifying and
marketUi.statusPending (en/es). Also reclassify the request tag as cosmetic
(never read in the client) in the audit.
2026-10-10 10:09:41 +07:00
SkateSpots
7a60099bab fix(marketplace): show an outage instead of an empty trade/reputation state
TradesPage, SellerDashboard, TradeReputationPanel, TradeReputationCard and
ShippingStatusCard now surface a relay outage explicitly instead of
rendering nothing-waiting / no-trades / hidden reputation. Shared
marketUi.loadFailed (en/es).
2026-10-10 09:32:52 +07:00
SkateSpots
b48f0e53ee feat(escrow): seller-advertised mint, preferred by the buyer
Finishes the escrow-mint work: advertiseEscrowKey now also publishes
cashu_mint (the seller's preferred mint from Wallet > Escrow) on kind 0,
documented in NIP.md. The buyer's EscrowRequestDialog reads the seller's
cashu_mint, includes it in the mint picker even if it is not in the buyer's
trust list, and prefers it (falling back to the buyer's preference, then
first trusted mint). Funds locked at a mint the seller actually uses are
the ones they can redeem. Still open: seller-side receive from a mint they
do not already have. New marketUi.escrowRequest.sellerMintHint (en/es).
2026-10-10 08:57:33 +07:00
SkateSpots
c71d176226 fix(marketplace): verify payment_hash and never pay fiat as sats
- usePayPaymentPointer now checks the seller's committed payment_hash (from
  the accepted offer) against the invoice actually paid; a swapped pointer
  is rejected before any payment.
- PayWithSats no longer offers an in-app sats Pay button for a non-SATS
  offer: a fiat amount is never paid as if it were sats. New
  marketUi.pay.nonSats (en/es).
- ListingOffersPanel passes paymentHash + currency through.
Docs updated (still open: NWC->WebLN/Cashu fallback, empty preimage).
2026-10-09 23:14:29 +07:00
SkateSpots
ac1b22674c fix(escrow): show and choose the mint, block non-P2PK mints
The escrow mint was silently the buyer's preferred/first trusted mint
(useCashuEscrow: prefs.preferredMintUrl || mints[0].url) with no UI, even
though the seller redeems there. Now:
- the escrow dialog shows the mint and lets the buyer pick from their
  trusted mints (defaults to their preference);
- a mint whose NUT-06 info advertises no NUT-11 (P2PK) support is blocked
  with a clear message before minting (cashuEscrow.mintSupportsP2PK);
- the create call uses the selected mint.
Still open (scoped separately): consult a seller-advertised mint and
seller-side receive from an unfamiliar mint. Recorded in the audit.
2026-10-09 23:07:24 +07:00
SkateSpots
077fb2d5d1 fix(marketplace): payment, attestation, and escrow safety
Next batch from MARKETPLACE_FLOW_AUDIT.md:
- bolt11.ts: getBolt11Expiry / isBolt11Expired; usePayPaymentPointer refuses
  an expired invoice before paying (a lapsed offer invoice used to fail deep
  in the wallet with a confusing error).
- PayWithSats shows unsupported pointer kinds (creq/clink) as unavailable up
  front instead of enabling a button that fails after the click.
- useTradeAttestations: verify events (onlyVerified) and keep the newest per
  (author, kind, d), so a newer s=revoked supersedes a stale valid copy;
  useTradeReputation no longer trusts unverified relay events.
- EscrowRequestDialog persists the minted token locally before publishing
  (lib/pendingEscrow.ts), so a failed send cannot strand the locked funds.
2026-10-09 23:00:35 +07:00
SkateSpots
5f8975d631 fix(ux): URL-addressable marketplace tabs, offers error state, nav reset
Next P1 slice from UX_AUDIT_2026-10-09.md:
- Marketplace hub tabs now live in the URL (?tab=...) so refresh, share and
  back/forward keep the tab; unknown values fall back to Browse.
- ListingOffersPanel shows an error state on a relay outage instead of
  returning null (a failure no longer reads as no offers).
- Nav customizer Reset reseeds from DEFAULT_TAB_IDS (4) instead of a
  hardcoded 3, matching MIDDLE_SLOTS.
New marketUi.offers.loadFailed (en/es).
2026-10-09 21:04:52 +07:00
SkateSpots
0d15a7ce3d fix(marketplace): close the P0 audit gaps + borrow the guidelines page
P0 fixes from UX_AUDIT_2026-10-09.md:
- useMarketplaceItem now filters admin-hidden listings and skips
  showcase-only (not-for-sale) items, so a direct /listing/... URL cannot
  surface removed or non-sale content.
- Listing detail hides Make an offer for logged-out users and for
  fixed-price listings (Buy is the single primary CTA there).

Borrowed from the Plebeian market review: a /marketplace-guidelines page
(peer-to-peer, buyer-beware, no checkout, prohibited items, soft
moderation), linked from the marketplace hub; new marketTerms en/es keys.
Records the remaining borrowables in the audit doc.
2026-10-09 20:21:04 +07:00
SkateSpots
18a9e28f0b feat(trust): network alerts for reported accounts you follow
Fifth slice of TRUST_SCORE.md. useNetworkAlerts reads the viewer's NIP-85
assertions for the accounts they follow and surfaces those trusted people
reported or muted. New /alerts page lists them with Ignore / Unfollow /
Mute / Report (reusing useFollowActions, useMuteUser, ReportDialog) —
inform, never auto-act. Settings -> Trust links to it with a live count;
ignored accounts are device-local. /alerts added to the smoke list.
2026-10-09 18:32:47 +07:00
SkateSpots
e66888128f feat(trust): a real mute (NIP-51 kind 10000) that hides content
Completes the safety loop. useMuteList reads the viewer's kind-10000 mute
list plus optimistic local mutes (lib/localMutes.ts); useMuteUser
republishes the list with the target added, records it locally and
invalidates content queries. useAdminModeration carries a new
mutedPubkeys set, and the shared filterAdminHidden/filterAdminHiddenMixed
drop muted authors — so every list hook filters them with no per-hook
changes (admin tooling with includeHidden still sees them). The flagged
notice gains a Mute action.
2026-10-09 18:23:04 +07:00
SkateSpots
3b06e90d15 feat(trust): let the viewer choose the trust provider (NIP-85 kind 10040)
Settings -> Trust is now functional, not read-only. useSetTrustProvider
publishes a kind-10040 designation for a custom provider (npub or 64-hex +
wss relay, the five 30382:<type> rows) and can clear it to fall back to the
community provider. The card validates input, prompts the signer, and
invalidates the provider + assertion queries so scores refresh. Docs
updated.
2026-10-09 17:55:00 +07:00
SkateSpots
fd9316331b feat(trust): Settings -> Trust and name AGPL on the License page
- Settings -> Trust section (?section=trust, TrustSettingsCard): a
  show/hide-marks toggle (useTrustDisplay; Flagged is never hidden) and the
  active provider + relay (useTrustProvider), linked to /trust.
- TrustBadge respects the display preference.
- Name AGPL-3.0 on the License page by updating its existing copy
  (licenseLabel + body) in en/es.
- Settings + card i18n keys; /settings?section=trust added to the smoke
  list. Docs updated.
2026-10-09 17:45:56 +07:00
SkateSpots
d1fae2688c feat(trust): attribute the badge and add a /trust explainer
Legibility pass: a trust verdict has to be explainable and sourced.

- TrustBadge tooltips now attribute the claim (from your network / the
  community provider / the provider you chose) and link to /trust.
- New /trust page: the three states, why absence is not distrust, who
  computes it, presence-is-not-endorsement, and point-of-view. Public,
  linked from the badge; added to the smoke-test route list.
- New trustUi.* keys and four walletUi.trust attribution keys (en/es).
2026-10-09 17:30:00 +07:00
SkateSpots
2d1144bb7e feat(trust): batch provider reads, neutral direct follows, flagged notice
Second pass of TRUST_SCORE.md.

- useTrustAssertions now batches: every TrustBadge registers its subject into
  a shared per-viewer union and ONE query fetches the union (no per-card
  query), with a debounced refetch when a list grows. Same hook API.
- A direct follow (or self) is no longer shown as Verified - only a
  friend-of-friend or a provider assertion counts; Flagged is never
  suppressed.
- useUserReports reads public NIP-56 reports for an account, and
  TrustFlaggedNotice shows a flagged counterparty with the reporters grouped
  by the viewer's network plus Report / Dismiss. Wired into the listing
  seller card and the coach detail page.
- New walletUi.trust.* keys (en/es).
2026-10-09 17:17:11 +07:00
SkateSpots
fead9ca781 feat(trust): spec-correct NIP-85 consumer + Verified/Unknown/Flagged badge
First slice of TRUST_SCORE.md.

- lib/nip85.ts: parse kind-10040 provider designations, resolve a provider
  with a house fallback, parse kind-30382 assertions (rank/followers/
  reporters/muters/hops; absent hops means no path, absence is not
  distrust), and author-check the provider.
- hooks/useTrustAssertions.ts: batch read from the resolved provider,
  signature-verified via onlyVerified + the author check.
- lib/trustLadder.ts: three buckets and thresholds (flagged = trusted
  reporters >= 2; verified rank >= 2).
- hooks/useTrustBucket.ts: prefers a NIP-85 assertion (verify + flag),
  falls back to the local WoT ladder (verify only).
- TrustBadge.tsx: now renders Verified / Unknown / Flagged with glyph,
  colour and tooltip; Unknown hidden by default, Flagged always shown.
  Reaches the map/tour/event cards, marketplace card, listing detail and
  coach detail through the shared component.

Open follow-ups (in TRUST_SCORE.md): batch the bucket at list level, a
'who reported' breakdown, a provider-choice setting, alerts, confidence
scoring.
2026-10-09 17:07:34 +07:00
SkateSpots
486ffdb000 feat(map): add autocomplete suggestions to the search bar
Typing in the map search pill now opens a suggestions dropdown built
from the items that already match the active filters:
- Spots: title matches; selecting one flies to it and opens its sheet.
- Places: distinct city/region/country tokens split out of each item's
  location; selecting one sets the query to that token (e.g. spain ->
  Spain).

buildSearchSuggestions lives in lib (pure, testable) and
MapSearchSuggestions.tsx is presentational; MapPage owns the list and
keyboard nav (ArrowUp/Down, Enter, Escape). Adds mapPage.searchSuggestPlaces
(en/es only; other locales fall back to English).
2026-10-09 16:31:31 +07:00
SkateSpots
281a4c6ba5 feat(i18n): add search/filter to the language picker
The language list now has a search input in both the header dropdown
(pinned above the scrolling list) and the inline list used in the mobile
nav sheet. Filtering matches the English name, native name, or locale
code, and shows a localized empty state. Adds nav.searchLanguages and
nav.noLanguages to en/es (other locales fall back to English).
2026-10-08 18:16:13 +07:00
SkateSpots
f7c9f9ef4c i18n: sweep hardcoded English (deckCost, becomeRoles, weather, skaterLabels, notify, misc) into 4 locales 2026-10-07 18:09:57 +07:00
SkateSpots
ae02f51d20 i18n(es): translate SEO titles & descriptions across 40 pages (seoUi) 2026-10-07 17:17:26 +07:00
SkateSpots
94a6628faa i18n(es): translate page/component toasts (follow, delete, reports, sync, review queue) 2026-10-07 17:14:18 +07:00
SkateSpots
2e9cbfba2b i18n(es): use "peña" instead of "cuadrilla" for crew (158 occurrences) 2026-10-07 17:09:39 +07:00
SkateSpots
dd2062dc74 i18n(es): translate status presets, SetStatusDialog, nutzap + wallet toasts 2026-10-07 17:04:04 +07:00
SkateSpots
69458e1ae6 i18n(es): translate the Sasquatch onboarding guide script 2026-10-07 16:58:43 +07:00
SkateSpots
97c90c506a i18n(es): translate zap/NWC/delete/stash toasts + reuse feesUi keys for fee metadata 2026-10-07 16:55:33 +07:00
SkateSpots
b01bd22e96 i18n(es): translate SupportSkateSpots + hook toasts (follow, report, offline, publish) 2026-10-07 16:50:00 +07:00
SkateSpots
7e578fb7d9 i18n(es): translate shared component literals (camera, quality meter, entity picker, offline, comments) 2026-10-07 16:44:23 +07:00
SkateSpots
c7a6421be1 i18n(es): wire StashPage hardcoded strings through t() (30+ literals) 2026-10-07 16:39:41 +07:00
SkateSpots
30a0aaa42b feat(create-spot): make address + access type required; move them out of the optional collapsible 2026-10-07 11:30:55 +07:00
SkateSpots
efd7e9608d feat(create-spot): pin map, progressive disclosure, mandatory photo+description, inline errors, draft autosave 2026-10-07 10:29:01 +07:00
SkateSpots
24322b3b1c feat(map): tap drops a pin + confirm card instead of jumping into create-spot 2026-10-07 10:26:36 +07:00
SkateSpots
39572998a0 feat(clink): real bech32+TLV decode/validation (lib/clink.ts), spec-correct raw QR/copy, show decoded pricing; validate scanner 2026-10-06 11:16:20 +07:00
SkateSpots
9ca5f2a604 fix(audit UI/UX): 44px slabs, missing grid base cols, aria-labels, dark weather panel, contrast, sheet width, stretched-link SpotCard 2026-10-05 21:47:29 +07:00
SkateSpots
5728c226d5 fix(audit P1): comment cache key, ticket verify fails closed, featured amount check, no empty-object persist, sticky sub-bars 2026-10-05 21:07:07 +07:00
SkateSpots
a9dcb5719c feat(supporters): per-row dates + 'updated', dedicated /donate page, extract shared support cards 2026-10-05 19:54:49 +07:00
SkateSpots
4ffad80728 feat(supporters): ways-to-support trio (purpose per rail, scrolls to each) 2026-10-05 19:44:31 +07:00