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).
The Edit action lived only in the sidebar Manage card; owners now get it in the
profile header, right next to Share. Non-owners are unaffected (Share keeps the
right alignment when there is no Edit).
Every provider lived at /coach/<pubkey>/<id> regardless of craft (a barber got
a "coach" URL). New src/lib/labRoutes.ts maps craft kind → segment and builds
the canonical path; the router adds /lab/:craft/:pubkey/:identifier (the craft
segment is cosmetic — the page still resolves by pubkey + identifier) and keeps
/coach/... working for old links. All generated links updated: offer-form
redirects, Lab cards + "Your Lab profiles", cross-links, spot/event/admin links,
the CVM client URL, the share path, and the SPA clean-link shim.
- After publishing, land with ?published=1 and show a one-time "You're live —
share it" banner on the profile (with a Share button), dismissible.
- /lab gains a "Your Lab profiles" strip listing the viewer's own profiles,
each deep-linking to view/edit — so multi-craft providers aren't hunting
through the list.
The offer → publish loop had two dead ends: a provider's own page only offered
Delete (no Edit), and there was no way to share a listing (the whole point of
announcing a service).
- Owner "Manage" card gains an "Edit profile" button deep-linking to
/become/<craft> (prefills the existing profile). Admins still get only
take-down, not a misleading self-edit link.
- A Share button on every provider page opens ShareProfileDialog (QR + copy +
native share) with the service URL /coach/<pubkey>/<identifier>.
- ShareProfileDialog accepts an optional `path` to override the default
/<npub> share URL.
- main.tsx clean-link shim now also rewrites /coach/<pubkey>/<d> to the hash
route, so shared /coach/... links resolve on the live site.
Follow-ups (not done): live preview before publishing, draft/autosave for the
long forms, a multi-step/progress layout, and a "your Lab profiles" manage
strip on /lab.
The coach portfolio/experience/payments treatment is now a shared layer instead
of 12 copies:
- src/lib/labExtras.ts: parseLabExtras + shared option sets (languages,
availability, structured rate, payment methods, booking fee, gallery/reel).
- components/LabExtrasFields.tsx: the self-contained form block — prefills from
the existing event, owns its state, and reports the managed tags via onChange.
- components/LabExtrasSection.tsx: the detail block (all crafts).
Wired into CraftProfileForm (the 8 data-driven crafts) and the four bespoke
pages (filmer/editor/photographer/builder); CoachDetailPage renders the shared
section for every craft. Per-craft buying units (rateUnits) are set per craft in
becomeRoles.tsx (e.g. barber 'per cut', musician 'per gig', photographer 'per
shoot'). /lab facet filters now read parseLabExtras, so availability/languages
filter across crafts.
Coach-specific formats/levels/ages/credentials remain coach-only. Money stays
custody-free: declared methods + a booking fee paid directly to the provider.
- Rename 'Federation coach' -> 'National federation coach'.
- Add 'Coaching qualification' and 'Competition judge'.
- Add a free-form cert_note so a coach can name an exact cert/issuer.
The core safety chips (first aid, CPR, safeguarding, background check,
insured) stay - they're the load-bearing trust signals. cert_note is
displayed under the chips and included in the same self-attested disclaimer.
Coaching payments, kept honest and custody-free:
- payment_method (cash/lightning/cashu/bank/paypal/other) + payment_note:
a declaration of what the coach accepts, not an integration. 'Cash on site'
lets a walletless coach list without being nagged.
- pay_what_you_can flag (sliding scale).
- booking_fee + booking_fee_currency: a small fee to confirm a booking, paid
DIRECTLY to the coach's kind-0 lud16 via the existing PayWithSats rail
(mounted only while the sheet is open). Only offered for sats amounts when
the coach has a Lightning address; copy states plainly that SkateSpots does
not hold or refund it.
Deliberately NOT added: fiat/card rails (no KYC/custody) and a refundable
deposit (needs the native-only, unverified Cashu escrow). NIP.md documents the
model; AGENTS.md records the decisions.
The coach option chips rendered raw 'labUi.coachForm.…' keys because the 29
per-option translation keys were referenced dynamically (t(option.key)) and had
never been added — the literal-only i18n guard missed them. Add every option
key (EN/ES) and extend scripts/check-i18n.mjs to also scan data tables for
key/titleKey/labelKey/badgeKey entries so this can't recur.
Also fix the chip layout: the global mobile 'button { min-height:44px }' rule
turned every chip into a 44px slab; add min-h-0 to the coach option chips,
language chips, Lab facet chips and Lab active-filter chips.
Remaining nice-to-haves:
- Structured rate: rate_amount + rate_currency (sats/USD/EUR/GBP) + rate_unit
(hour/session/half-day/day/camp), rendered on the detail page; legacy
freeform 'rate' still shows and acts as an optional note.
- Section the long coach form with a FormSection divider.
- Restyle the coach identity block from a red error-looking box to a neutral
avatar card.
Enrich the coach (kind 35001) beyond the shared base schema with the
experience/portfolio fields that actually win a booking:
- Session formats, levels, age groups (repeated tags, client-filtered)
- Self-attested credentials/safety (first-aid, safeguarding, …) with an
honest 'not verified' label
- Languages spoken (free-form) and availability
- Optional booking link
- Portfolio photo gallery (up to 12) reusing PhotoDropzone + ImageLightbox
- Highlight-reel video link (YouTube/Instagram)
Option sets live in src/lib/coachOptions.ts with EN/ES keys. parseSkateCoach
exposes the new fields; NIP.md documents the tags.
Also fix the pre-existing ErrorBoundary test (ErrorFallback uses
useTranslation but the test rendered no LanguageProvider).
- 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.
- First-visit language auto-detect (Spanish devices land in es; stored choice wins).
- Sasquatch AI replies in the app language (skate trick names kept English).
- i18n safety net: scripts/check-i18n.mjs wired into npm test — fails on any t()
key missing from en/es or any en/es key drift.
- ~1,680 new keys (2,297 total) across mapUi / spotUi / marketUi / labUi and
~65 files wired to t(): Map, spots (detail/create/edit/chat), marketplace,
trades, offers, escrow, shipping, The Lab, /become/* forms, coaches, skaters.
- Policy: only skate TRICK names stay English; user-generated content 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.
- Preserve the geo 'g' tag and paid 'featured' tag when editing any Lab
profile (all 7 craft roles via CraftProfileForm + 5 bespoke pages).
- Render previously collected-but-hidden fields on the Lab profile page:
judge 'events_judged', organizer 'events_hosted', editor hardware/software,
and spot references.
- Include Event Organizer (35013) in Community Stats + counts.
- Cap Title inputs at 100 (was 120) so profiles no longer fail the parser
and silently vanish; MIN_TITLE_LEN 2 -> 1 for 1-char display names.
- Normalize bare-domain URLs (example.com) to https:// before storing instead
of dropping/hiding them (new normalizeUrlInput helper; fixes extra url fields).
- No crew strip for judging/organizing (was wrongly showing Photo & Film);
scrollable Offer Services FAB; live bio counters.
Adds a shared ErrorState/PageError with a retry action. Detail pages (spot,
shop, collection, coach, crew, event) now show an honest failure + Try again
instead of '404 Not Found' — a relay hiccup previously implied the content was
deleted. The zap/bounty leaderboards surface errors instead of 'no content yet',
and the DM inbox gains a retry button.
The spot page (and the other detail pages) used `grid lg:grid-cols-3` with
no explicit mobile column. Below the `lg` breakpoint the grid falls back to
a single implicit `auto` track, whose minimum is the min-content of its
items. A long mapper name ("Tip the Mapper" card) gave the sidebar a
~559px min-content, so the whole grid widened past the viewport and was
clipped by the global `html, body { overflow-x: hidden }` — content cut off
on the right with no way to scroll to it.
Fixes:
- Add `grid-cols-1` to detail-page grids so the mobile track is
`minmax(0, 1fr)` and can never exceed the container. Applied to Spot,
Shop, Event, NIP-19, Crew and Coach detail pages.
- Add `min-w-0` to their sidebar/main columns so long content can shrink
and truncate instead of forcing the track wider.
- Make the Spot Chat header wrap (`flex-wrap` + `gap-y`) so the trust
filter and channel badge no longer get clipped on narrow phones.
Skaters can now request to join a Lab collective (studio/build team), mirroring
the crew join-request flow:
- New addressable kind 36387 (collective join-request), documented in NIP.md
- useCollectiveJoinRequests: send / incoming / "already requested" hooks
- Owner-only notification surfaced in the bell (All + Crews tabs) via
CollectiveJoinRequestCard, reusing the synced dismissal set
- "Request to join this collective" button on collective Lab profiles
(RequestToJoinCollective), hidden for the owner and existing members
Trust model preserved: only the owner can edit their own profile roster, so the
card links them to the member editor — no forged membership possible.
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
The crew<->collective links existed on both sides (crew.labRefs, profile.crewRef)
but were unverified one-sided claims. Now:
- CrossLinkCards resolve the other entity and show a green "Verified" badge only
when BOTH sides link each other (crew lists the Lab profile AND the profile
lists the crew). One-sided claims show a plain link — never false trust.
- LinkedCrewCard / LinkedLabCard take a selfCoord; CoachDetailPage and
CrewDetailPage pass their own coordinate so the check works.
- CrewLinkField + LabLinkField now explain the mutual-verified behavior, so the
feature is discoverable. Both fields already restrict to the user's OWN
entities, so a verified link genuinely requires one owner to confirm both sides.
- Completed LabLinkField TYPE_LABEL for all crafts (artist/graffiti/tattoo/
trainer/musician/judge no longer fall back to "Lab").
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- Flesh out SkateJudge as a full craft-based Lab profile (parseCraftBase),
with JUDGE_FORMAT / SPECIALTY / LEVEL label sets covering Game of S.K.A.T.E.,
best trick, jams, contests, am series, world tour stops, etc.
- Wire judge into useLabProfiles (union type, self-tag, parse case)
- Add "judging" ServiceType to LabPage: tab, accent, filter sections, empty
state, counts, profile card icon, and ?craft=judging deep link
- New BecomeJudgePage + /become-judge route + FAB entry
- CoachDetailPage renders judge profiles (accent, icon, labels, noun, title)
- Community Stats "Judges" card now links to /lab?craft=judging
- Document kind 35012 (skatejudge) in NIP.md
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
Completes the admin moderation coverage from the previous commit.
Lab profiles (coaches, filmers, editors, photographers, builders, artists,
graffiti, tattoo, trainers, musicians):
- useLabProfiles / useLabProfile and useCoaches / useCoach now filter out
admin-hidden entries (list) and 404 hidden ones (detail) via the shared
moderation layer. Added filterAdminHiddenMixed() for the mixed-kind Lab
union (kind read from each item's event).
- CoachDetailPage (the shared Lab detail page) now shows the delete/takedown
card to admins too, not just the owner, with admin-specific copy.
DeleteEntityDialog already handles the owner-or-admin logic.
Gear / marketplace listings (NIP-99, kind 30402):
- useMarketplaceItems / useMarketplaceAtSpot filter out admin-hidden listings.
- GearSwapItemCard: admins get a "Remove (admin)" action in the card menu with
a confirmation dialog that routes through useDeleteEntity (NIP-09 delete +
hidden label). Report stays for non-owners.
SECURITY.md: updated the admin-moderation coverage list and auditor file map.
Build + lint pass, no console errors.
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
Step 3 — member visibility (best-of-both, honest):
- MemberVisibility ('public' | 'crew') stored on the member's OWN kind-1985
attestation via a `visibility` tag, so a public crew can contain a private
member. Confirmed crew-mates + founder see them; outsiders see anonymised
"hidden member" slots. Framed honestly in-UI (app-level discovery privacy).
- VisibilityPicker in ConfirmMembershipCard (choose on join) +
MemberVisibilityControl for confirmed members to change it anytime.
- useCollectiveMembers parses visibility; CollectiveMembers hides crew-only
members from non-members.
Step 4 — anti-fraud vouch layer:
- useVouches/useVouchFor: NIP-32 vouch labels, trust-WEIGHTED via web-of-trust
so fake-account vouch rings carry no weight. VouchCard with new/vouched/
scene-verified tiers, wired into every Lab profile detail page.
Step 4 — CrewDetail UX redesign:
- Activity consolidated into clean Feed/Events/Chat tabs (no more giant scroll)
- Sidebar reordered: Members → Support → Band → Connections → Founder
- Member visibility control surfaced; tighter hero
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- New addressable kinds 35007–35011 with types, label maps and parsers
- Shared parseCraftBase helper + parseLabProfile dispatcher in useLabProfiles
- Reusable, config-driven CraftProfileForm (polished shared shell, per-craft
skill sections + extra fields, entity/collective, CLINK, Pro upgrade)
- Five thin Become* pages wired into AppRouter and the Lab "Offer" menu
- LabPage: extended ServiceType, accents, scrollable tab strip, filter
sections, counts, empty states and ProfileCard to all 10 crafts
- CoachDetailPage renders all craft types (map-driven accents/icons/labels)
plus craft-specific fields (studio, books-open, credentials, remote,
graffiti crew, music links)
- NIP.md documents all five new craft schemas
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- New useDeleteEntity hook: publishes a kind:5 deletion request referencing
the target addressable event by `a` coord, `e` id, and `k` kind. Owner-only.
- New reusable DeleteEntityDialog: owner-gated trigger + confirmation with a
clear "deletion is best-effort on decentralized relays" warning, redirect,
and query invalidation so the item drops out of lists.
- Wired "Disband" into CrewDetailPage (founder only, redirects to /crews).
- Wired a "Manage → Delete Profile" card into CoachDetailPage for all Lab
profile types (coach/filmer/editor/photographer/builder), redirects to /lab.
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
Lab:
- useLabProfiles sorts featured (Pro) profiles first
- ProBadge shown on Lab cards (LabPage) + Lab detail page; featured cards
get a subtle amber highlight
Events (same no-paywall model as Lab):
- New FeaturedEventUpgrade: every event posts for free; optional fee adds a
featured badge + top placement (reuses existing featuredEvent fee)
- CreateEventPage: removed the paid-event fee gate; opt-in featured toggle,
writes a `featured` tag when active
- useSkateEvents sorts featured events first; EventCard shows ProBadge
(compact in list view, overlay on the image in full view)
Docs:
- NIP.md documents the `featured` tag (unix-seconds expiry, self-attested)
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
Nutzap:
- Gate sending behind NUTZAP_SENDING_ENABLED=false with clear docs; sending
would require P2PK-locking proofs (fund-loss risk if wrong), so it's disabled
until implementable/auditable. Receiving + reading unchanged. No dead-end
button exists (hook was never wired to UI).
Cross-linking crews ↔ Lab collectives:
- types: parseAddrCoord/parseCrewRef/parseLabRefs helpers + LAB_KINDS; add
crewRef to all 5 Lab interfaces/parsers and labRefs[] to SkateCrew
- CrewLinkField (pick one of your own crews) added to all 5 Become-* forms,
shown only for collectives; publishes an a-tag on the Lab profile
- LabLinkField (pick your own Lab profiles) added to CrewForm; publishes a-tags
- CrossLinkCards: LinkedCrewCard on Lab detail ("Part of a Crew"), LinkedLabCard
on crew detail ("Hire This Crew"), resolving coordinates via useCrew/useLabProfile
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
Phase 1 CLINK integration (recognize & display only):
- types.ts: optional noffer field + isValidNoffer guard on SkateCoach,
SkateFilmer, SkateEditor, SkatePhotographer, SkateShop; parsers read it
- NofferField: reusable opt-in input with live validation, added to all 4
Become-* Lab forms and both Create/Edit shop forms
- ClinkPayButton: secondary "Pay via CLINK" action (QR + copy + nostr: deep
link), rendered on CoachDetailPage and ShopDetailPage only when a valid
noffer is present; never replaces the existing zap / Lightning Address flow
- useQRCodeScanner: classify scanned noffer1/ndebit1 CLINK codes
- NIP.md: document the noffer tag on Lab kinds + shops and a CLINK section
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
New Photographers category (blue-indigo) — separate from Filmers:
- New addressable kind 35004 + SkatePhotographer type + parseSkatePhotographer.
- Rich photographer taxonomy as queryable `t` tags: services (action,
sequence, editorial, product, events…), camera bodies, lenses, lighting
(Profoto/Elinchrom/Godox/triggers…), support/rig, and shooting style,
plus a freeform "gear" tag and a portfolio link.
- BecomePhotographerPage form with chip-grid selectors for all gear
categories + "what's in the bag" textarea, wired to /become-photographer.
- The Lab: 4th "Photo" tab (indigo accent) with its own filters, cards,
empty state; OfferFAB gains "Offer Photography"; useLabProfiles fetches
35004; CoachDetailPage renders photographers (and editors) properly with
per-type accent, gear + portfolio display.
- NIP.md: documented the whole Lab (kinds 35001-35004) incl. photographer.
Filmer improvements (now video-focused, photography split out):
- Removed the token "Action Photography" service; added follow/line filming.
- Richer equipment list: cinema camera, HVX, action cam, gimbal, 4K/HFR.
- Filmers gain a freeform "what's in the bag" gear field + a reel/portfolio
link, both surfaced on the detail page.
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/CoachDetailPage.tsx (Browse further)