Commit graph

707 commits

Author SHA1 Message Date
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
635e52df4a Bump version to 1.2.4 and update release notes (Amber signing hardening, upload fallbacks, URL sanitizing)
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-03 20:48:07 +07:00
shakespeare.diy
5e8544316b Audit fixes: harden Amber signEvent response handling, fix web AmberSigner signEvent naming, add Blossom upload fallbacks, sanitize untrusted URLs in FoodPage/CreateTourPage
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-03 20:20:08 +07:00
shakespeare.diy
5017c1b7ff Fix mobile bugs: DM/crew-chat crash, notification popover clipping, multi-account switch
#2 (crash): useDirectMessages called getEventHash() without importing it —
`getEventHash is not defined` broke crew chat and any NIP-17 DM send. Added
the missing import from nostr-tools.

#3 (bell not loading on phone): the notifications popover had a fixed-height
scroll area and no collision handling, so on small screens it rendered clipped
against the top edge with the tab content cut off. The popover now constrains
to the Radix available-height, uses a flex column, and the tab list/scroll area
flex correctly — all tabs (incl. Crews/News) are reachable on mobile.

#4 (multi-account on phone): the mobile header user menu had only Set Status +
Log Out. Added an Accounts section that lists other logged-in npubs to switch
between, plus "Add another account" which opens the login dialog. Desktop
already had this via AccountSwitcher; mobile now reaches parity.

Note: #1/#5/#6 (stuck on publish/vouch/delete) were reported against the shipped
1.2.2 APK, which also contained the #2 crash. Rebuild 1.2.3 and re-test — the
sign/publish paths in current source are correct (Amber round-trip has a 90s
timeout + surfaced errors).

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-03 20:09:01 +07:00
shakespeare.diy
3582262a44 Bump web version to 1.2.3
Ships the crew-aware Sasquatch agent, Nostr-native newsletter (subscribe,
mailbox/notifications, admin publishing + archive backfill) and subscriber
count. Prep for desktop deploy + Android 1.2.3 APK release to Zapstore.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-03 19:27:35 +07:00
shakespeare.diy
dc410106b3 Newsletter: subscriber count + admin archive backfill to Nostr
Two follow-ups to the Nostr-native newsletter (skipped auto-DM by design —
too spammy/fragile for now; the bell mailbox already covers the inbox feel).

Subscriber count (social proof):
- useNewsletterSubscriberCount tallies public NIP-78 subscription markers
  (deduped per author, counting current "true" state).
- Shown under the subscribe CTA as "Join N+ skaters getting the drops",
  hidden until it's meaningful (>=3) and rounded once it grows.

Archive backfill (consistency without losing the rich page):
- lib/newsletterArticle.ts converts a static NEWSLETTER_ISSUES entry into a
  NIP-23 (kind 30023) article — structured sections rendered to Markdown,
  stable slug `issue-N`, published_at from the issue date.
- NewsletterBackfillPanel (admin-only) diffs the static archive against what's
  already on-relay and publishes only the missing issues; safe to re-run,
  reports partial failures. The rich static page rendering is untouched.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-03 19:21:09 +07:00
shakespeare.diy
08f74d7991 Add Nostr-native newsletter: subscribe, mailbox & notifications
Turn the static newsletter into a live, Nostr-native marketing channel that
npubs can subscribe to and receive in-app.

Publishing (admins):
- Newsletter issues are published as NIP-23 long-form articles (kind 30023)
  tagged `skatespotsnews`, on the community relay.
- NewsletterComposer: admin-only dialog to write + publish an issue (title,
  slug, summary, cover image, Markdown body).

Subscribing (any npub):
- useNewsletterSubscription: per-user opt-in stored as a NIP-78 (kind 30078)
  marker, so it syncs across every device the user logs into.
- NewsletterSubscribeButton on the /newsletter page and issue reader.

Mailbox / notifications:
- New "News" tab in the NotificationBell showing all issues, with an unread
  badge for subscribers (tracked via a per-account last-read timestamp).
  Latest unread drop also surfaces in the "All" tab.
- Opening the bell or the /newsletter page clears the News badge.

Reading:
- useNewsletterIssues: queries admin-authored issues (author-scoped + tag +
  admin re-validation as the trust boundary against spam).
- LiveIssuesSection on /newsletter shows issue cards linking to a reader.
- NewsletterIssueView: naddr reader (wired into NIP19Page for kind 30023) with
  a small safe Markdown renderer, zap + subscribe CTAs.

Docs: NIP.md documents the 30023 issue schema, the 30078 subscription marker,
and the admin trust boundary.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-03 19:16:49 +07:00
shakespeare.diy
cdf8cee3a6 Update Sasquatch avatar to the correct mascot image
Point CREW_AGENT_AVATAR at the intended Sasquatch artwork. Since it's the
single source of truth, this updates the agent's image everywhere (panel
header/rows/empty state, crew page button, CrewForm toggle) and in the
agent's published kind-0 profile.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-03 18:59:05 +07:00
shakespeare.diy
878da94b7b Make Crew Agent genuinely crew-aware; swap bee emoji for Sasquatch avatar
Sasquatch now knows the crew it lives on. The agent is seeded with a rich
crew dossier — its own upcoming events, recent feed digest, roster size,
founder, open call, vibe, band, home spots, website/IG — and the system
prompt instructs it to answer crew-specific questions ("what's our next
jam?", "who runs this?", "what are we looking for?") directly from memory,
while still using CVM tools for wider-network discovery.

- useCrewAgentChat: expanded CrewContext + crew dossier + two-mode prompt
- CrewDetailPage: wires real crew events + feed digest into the agent ctx
- CrewAgentPanel: crew-specific suggestions, crew-aware copy
- Replaced 🐝 bee emoji with the Sasquatch mascot avatar everywhere
  (panel header/rows/empty state, crew page button, CrewForm toggle)

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-03 18:55:54 +07:00
shakespeare.diy
0b786e546f Notification bell: drop the "Browse all crews" footer
A notification center should surface the user's own actionable items, not a
generic nav shortcut — and it showed on every tab (including Social) where it
made even less sense. Removed the footer entirely; the tabs now sit flush.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-03 18:38:33 +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
3833625bde Signup: require a display name (changeable later), remove skip
Per request, a display name is now mandatory when signing up:
- Removed the "Skip for now" button from the profile step — the only forward
  path is Save, which is disabled until a non-empty name is entered.
- Marked the field required (*), added helper copy ("Required — how you'll show
  up across SkateSpots. Change it anytime."), and a hint under the button.
- finishSignup guards against an empty/whitespace name (defense-in-depth for
  keyboard/Enter), trims the name before publishing kind 0.
- Reworded the step copy: name required, bio + avatar still optional.

Only affects the primary nsec signup dialog (also the endpoint the mnemonic
flow feeds into); the mnemonic dialog has no name step.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-03 18:32:08 +07:00
shakespeare.diy
39afc00b6e Crew chat: unread dot on the Chat tab (cheap, honest, no bell spam)
Instead of wiring encrypted crew chat into the global notification bell — which
would mean background-decrypting every crew's messages to power a badge (slow,
battery-hungry, unreliable with NIP-17's random timestamps) — add a subtle
unread dot to the crew's own Chat tab.

- useCrewChatUnread: member-only, single-crew, runs only while the crew page is
  open. Uses a pure metadata query (newest Cordn envelope by created_at, no
  decryption) vs. the local last-seen timestamp. For the built-in NIP-17
  transport (random timestamps, needs decryption) it deliberately returns false
  rather than fire false pings — the in-chat "New" divider already covers
  "what did I miss" once the tab is opened.
- Tabs made controlled; the dot is suppressed while the Chat tab is active, so
  it clears the moment you look, and is gone on the next visit (last-seen
  advances). No global notification noise.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-03 18:21:38 +07:00
shakespeare.diy
e843bd526a Cordn crew chat: ephemeral envelopes (coordinator can't see the sender)
Close the metadata gap: the outer kind-4497 envelope was signed with the
sender's real key, so a coordinator/relay could see exactly who was talking.

- buildEnvelope now signs each envelope with a fresh ephemeral key (NIP-59
  style) — the coordinator never learns the sender.
- Authorship moves inside the ciphertext: a new v2 sealed payload
  (`nip44-group-v2`) carries a fully-signed inner author-proof event (kind 4498)
  bound to the crew via an `a` tag. openGroupMessage verifies the signature,
  author, content match, and group binding — so authorship stays unforgeable and
  a proof can't be replayed into another crew.
- Transport trusts the verified v2 proof and surfaces the real sender; legacy
  v1 envelopes remain readable (outer-key binding) for back-compat.
- NIP.md updated with the v2 format + an honest note on the residual
  verbatim-replay caveat (full replay/forward-secrecy hardening lands with MLS).

Scope decision: kept to crew chat only. Regular 1:1/group DMs already use NIP-17
gift wrap (ephemeral keys, sealed per recipient) and have no coordinator, so
they need no change and stay idiot-proof.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-03 18:14:58 +07:00
shakespeare.diy
739469aa1d Crew chat: frame built-in E2E chat as the default, Cordn as honest opt-in
Decision (per Marmot/Concord/Cordn analysis): a semi-public, socially-verified
skate crew fits Concord-style shared-key chat, not MLS. The built-in NIP-17
giftwrap group chat is already the right answer and works with zero setup — so
stop implying a coordinator is required and stop over-claiming MLS guarantees
the code doesn't yet provide.

- CrewCoordinatorCard: default state now reassures that private E2E chat is
  already ON (no setup) for everyone, instead of prompting founders to "set up a
  coordinator". Founders get a small, honestly-labeled advanced link to the
  self-hosted Cordn option. Fixed the "E2E · MLS / forward secrecy" badge — the
  coordinator path is NIP-44 group encryption routed over ContextVM, not an MLS
  ratchet.
- CrewForm: the coordinator field is now clearly "experimental · self-host",
  states you don't need it, and links to the Cordn repo.
- NIP.md: reworded the crew-chat section + tag/profile descriptions to match the
  honest crypto seam (NIP-44 group today; MLS a future upgrade), so docs no
  longer claim forward secrecy that isn't shipped.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-03 18:02:53 +07:00
shakespeare.diy
e698e4880f Crew chat: subtle "New" divider for messages since last visit
A returning member couldn't tell new messages from old ones. Add the universal
unread-divider pattern (Slack/Discord-style) directly inside the chat:

- useCrewChatRead: per-crew, per-pubkey last-seen timestamp in localStorage
  (local-only, zero network cost, works for both NIP-17 and Cordn transports).
- CrewChat snapshots last-seen once per open so the divider stays put while
  reading, draws a minimal "New" line above the first message since last visit,
  and quietly advances last-seen to the newest rendered message.

No new fetches, no config, nothing for the user to manage — it just works.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-03 15:58:54 +07:00
shakespeare.diy
f37d0184d3 Collective join-requests: one-tap approve from the bell
Turn the collective join-request into a genuinely seamless flow instead of a
dead-end deep-link:

- New useApproveCollectiveJoinRequest: owner appends the requester to their own
  Lab-profile roster (re-signed, preserving all tags) and clears the request in
  one action — mirrors the crew founder's "Add to crew".
- CollectiveJoinRequestCard now resolves the collective and offers a single
  "Add to collective" tap, with graceful states for loading, already-a-member,
  and unresolvable-profile (never traps the owner).
- Removed the old ?manage=members deep-link (pointed at a read-only card).
- Requester-side copy now sets accurate expectations ("added in one tap").

Trust model unchanged: only the owner signs their profile event.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-03 15:49:49 +07:00
shakespeare.diy
314c8620f1 Lab collective join-requests → notification bell
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>
2026-09-03 15:41:03 +07:00
shakespeare.diy
64b91c0ae2 Mutual crew<->Lab link verification (trust badge) + polish
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>
2026-09-03 15:29:57 +07:00
shakespeare.diy
a89fca8e28 Lab: payment-readiness prompt on every provider form + drop downhill/longboard from judge
- New PaymentReadinessNote component: checks the logged-in user's Nostr profile
  (kind 0) for a Lightning address / Silent Payment and either confirms "you can
  get paid" or links them to Edit Profile to add one. Wired into CraftProfileForm
  (coach, filmer, editor, photographer, builder, artist, graffiti, tattoo,
  trainer, musician, judge) and the standalone coach/filmer/editor/photographer/
  builder forms, alongside the existing CLINK (noffer) field. Lightning address
  stays a single source of truth on kind 0 — never duplicated/stale per event.
- become-judge: removed the "Downhill / Longboard" event format (not part of the
  skateboarding activities this app covers).

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-03 15:21:34 +07:00
shakespeare.diy
598362a196 Fix date-window chips overflowing off the phone screen
The chips (ANYTIME / TODAY / THIS WEEKEND / THIS WEEK / THIS MONTH) ran off the
right edge on mobile and got cut off. Now on phones they render as a 5-column
grid that fits the screen exactly — with compact labels (Any / Today / Wknd /
Week / Month) so nothing truncates. Desktop keeps the full-label flex row.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-03 15:11:30 +07:00
shakespeare.diy
8110efe821 Compress /events chrome on mobile so event cards reach above the fold
The mobile toolbar stacked 5 rows of controls (title, view tabs, upcoming/past
sub-tabs, search+city, date chips) before any card showed. Tightened for phones
without removing any feature or touching desktop:
- Toolbar vertical padding + section spacing reduced (py-2/space-y-2 on mobile)
- View tabs and Upcoming/Past sub-tabs use py-1.5/py-1 on mobile
- Date-window chips: smaller top margin + padding
- Results grid top margin reduced on mobile
- "N results" count line hidden on mobile (pure noise on a small screen)

All changes are md:-gated, so desktop is unchanged.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-03 15:08:45 +07:00
shakespeare.diy
d577c8e455 Prevent scheduling events in the past + full events-section audit
Audited the whole events flow end-to-end (create, hosting dashboard, discovery
grid, detail, RSVP, sponsors) and confirmed the wiring is coherent:
- Create → parse → display round-trips correctly for sponsors, tour/spot/crew/
  musician links, entry fee, and featured listings
- Sponsor parser safely skips shop-coordinate `sponsor` tags (no name at pos 2),
  so events and tour/shop sponsorships never collide
- Batch RSVP counts feed the grid; hosting dashboard + detail keep per-item counts

Fix surfaced by the audit: CreateEventPage let a host pick a start time in the
past, silently producing a dead event in the Past tab. Added a `min={today}` on
the date pickers and a submit guard (10-min grace) with a clear message.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-03 15:02:25 +07:00
shakespeare.diy
fbb70983cd Tighten /events header on mobile so cards sit higher above the fold
The filter toolbar was already sticky (stays put while cards scroll under it).
This reclaims the wasted vertical space above it on phones: smaller title,
reduced top padding + margins, and the subtitle is hidden on mobile. Desktop
layout unchanged.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-03 14:53:53 +07:00
shakespeare.diy
5a0e6d21a0 Hide visible scrollbar on mobile for a clean, app-like feel
On phones (max-width 767px) the gray scrollbar track is now hidden across the
app (html/body) — swipe-to-scroll still works, only the bar is gone. Desktop
keeps its custom 8px scrollbar unchanged.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-03 14:49:43 +07:00
shakespeare.diy
135fbaab40 Events discovery polish + real bug/perf fixes across the grid
UX (all participants):
- EventCard now shows a live status pill (In 2 days / HAPPENING NOW / ENDED) on
  both the full and compact variants — the at-a-glance signal was previously
  detail-page only
- EventCard shows a "Presented by <sponsor>" ribbon so paid sponsor placements
  get real visibility in the discovery feed (not just after a click)
- Added a bottom legibility gradient so overlays stay readable on any cover

FIX (crash risk): EventsPage getTrustScore did `trustScores?.map.get` — since
useBatchTrustScores returns a plain Map (no `.map`), this threw TypeError once
scores loaded. Now reads the Map directly.

PERF (N+1 query storm): rendering the grid fired one RSVP query PER card
(~100 queries on a full page). Added useBatchEventRsvps — a single #a-filtered
query for all event coordinates — and thread goingCount into EventCard as a prop.
Cards without the prop (detail page, hosting dashboard) keep their own query.

Removed now-dead getRelativeTime/relativeTime/isUpcoming (superseded by the
EventCountdown pill).

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-03 14:47:02 +07:00
shakespeare.diy
854d18c322 Give admins moderator control over events + fix a spoofing hole
Admins (the two pubkeys in src/lib/admins.ts) can now moderate any event:
- Post authoritative Event Updates (incl. cancel/reschedule) on events they don't own,
  clearly labelled "Admin" and marked with an ["admin","true"] tag
- Take down any event (already wired via DeleteEntityDialog + isAdminPubkey)

SECURITY FIX: kind-7103 Event Updates are now only trusted from the event's host
(pubkey in the `a` coordinate) or a SkateSpots admin. Previously ANY npub could
publish a fake "CANCELLED" update — spoofing the detail-page banner and spamming
RSVPers' notification bells. Enforced in both parseEventUpdate() and
useActivityNotifications.

Note: admins intentionally CANNOT open the edit form on others' events — an
addressable event (31923) can only be replaced by the key that signed it; editing
under a different key would fork it, not edit it. Moderation is done via
authoritative updates + takedown instead.

Docs: NIP.md updated with the host/admin trust rule and the admin marker tag.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-03 14:40:17 +07:00
shakespeare.diy
25c57cbb2f Fix event UX correctness: live countdown, cancellation banner, NIP-52 tag cleanup
- FIX: EventCountdown label never updated (memo ignored the interval tick) — now recomputes every 30s for both EventCountdown and useEventStatus
- Add loud cancelled / rescheduled banner on EventDetailPage derived from host updates; close RSVP + dim hero when cancelled so attendees don't show up to a dead jam
- Clean up non-standard NIP-52 `a` tags (['a', coord, '', ''] → ['a', coord]) in RSVP and host-update publishing
- Correct misleading kind comment in useEventRsvps

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-03 14:33:28 +07:00
shakespeare.diy
29d09eb6ec Add rich Open Graph / Twitter share metadata + canonical share link to event page
- Per-event og:title/description/image/url + twitter:summary_large_image via useSeoMeta
- Share description composes date + location + blurb; falls back to site card image
- handleShare now uses the canonical /#/event/... URL, richer text, and toast feedback on clipboard copy

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-03 12:46:45 +07:00
shakespeare.diy
87c81a63c9 Event detail hero redesign + live countdown + sponsors
- Immersive EventDetailPage hero: full-bleed cover with legibility gradient,
  bold uppercase title, type pills, host/music-act chips, and a 3-up meta bar
  (date block · time · where). Past events render dimmed.
- EventCountdown: live status pill — "IN 3 DAYS" / "IN 2H 14M" /
  "HAPPENING NOW" (pulsing) / "ENDED", refreshing every 30s.
- Actions (edit/calendar/share) moved into a clean glass overlay on the cover.
- Event sponsors: EventSponsor type + parse/serialize (mirrors TourSponsor),
  EventSponsors "Presented by" strip (title sponsor hero row + logo grid),
  sponsor editor on CreateEventPage, and sponsor preservation on edit.
- Document event sponsor tag in NIP.md.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-03 12:04:07 +07:00
shakespeare.diy
78f4a6d09a Host tools: attendee list w/ trust, event updates, edit-alerts, Hosting tab
- AttendeeList: tabbed Going/Interested with faces + names + a "who you trust"
  signal (reuses batch trust scores); replaces the plain avatar stack
- Event Updates (kind 7103): usePostEventUpdate + useEventUpdates; host-only
  composer with update/reschedule/relocation/cancelled categories that p-tags
  every RSVPer so they get notified; public feed on the event page
- EditEventPage: auto-post a reschedule/relocation update to notify attendees
  when the host changes time or location
- Activity bell: new 'event-update' notification type for RSVPers
- EventsPage: "Hosting" tab (logged-in) with a HostingDashboard listing the
  user's upcoming/past events, live RSVP counts, and quick edit/update actions
- Document Event Update kind 7103 in NIP.md

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-03 11:25:57 +07:00
shakespeare.diy
7439b980bb Events Phase 1: NIP-52 RSVPs, attendee stacks, date filter, host notifications
- Adopt NIP-52 RSVP (kind 31925): parseEventRsvp + RsvpStatus types
- useEventRsvps (counts by status, attendee pubkeys, my status) + useRsvp
  mutation that replaces the user's prior RSVP via a stable d-tag
- RsvpButtons (Going / Interested / Can't make it) + AttendeeStack avatar row
- EventDetailPage: "Who's going" card with RSVP control + attendee avatars
- EventCard: going count on both compact and full cards
- EventsPage: quick date-window chips (Today / Weekend / Week / Month)
- Activity bell: host RSVP notifications (new 'rsvp' activity type)
- Document RSVP schema in NIP.md

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-03 10:47:57 +07:00
shakespeare.diy
453adc4604 Bring desktop/tablet "More" menu to parity with mobile burger
The desktop "More" dropdown was only populated from the customizable nav
catalog, so directory/account destinations that live only in the mobile
burger menu (notably Skaters) never appeared. Add Skaters, View Profile
(logged in), and Settings to the desktop More menu so nothing is lost on
larger screens.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-03 10:29:54 +07:00
shakespeare.diy
c34f194d8f Polish notifications: full activity center (social + crews), not just crews
- New useActivityNotifications: single combined #p query classifies reactions,
  reposts, zaps, mentions and replies referencing the user; parses zap sats
- New useNotificationsRead: per-account last-read timestamp for unread badge
- New ActivityNotificationCard: actor avatar + type badge, verb, snippet, links
  to the actor's profile
- Rewrite NotificationBell into a tabbed activity center (All / Social / Crews)
  with unread tracking, mark-all-read, and the existing crew invites/requests

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-03 10:21:15 +07:00
shakespeare.diy
35915557bb Add in-app routing (foot/bike/moto/car) via OSRM — no external maps handoff
- New src/lib/routing.ts: keyless OSRM client, travel modes, distance/skate-time formatters
- New RouteDialog: self-contained Leaflet map drawing the route from the user's
  location to a spot/shop/event, with a mode toggle and distance/time summary
- Wire the "Directions" buttons on Spot/Shop/Event detail pages to route in-app
  instead of opening Google/Apple Maps

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-03 10:17:06 +07:00
shakespeare.diy
f4bca7b736 Admins can endorse join-requests; founder ratifies (kind 9087)
Closes the admin/founder asymmetry without breaking the trust model: only the
founder authors the crew event, so only they add members — but an admin can now
publicly *vouch* for a join-request, and the founder ratifies with one click.

Protocol (SkateSpots-native kind 9087, regular event):
- e-tags the join-request, a-tags the crew, p-tags the requester (role
  "requester") and the founder (so it hits the founder's bell).
- Clients keep only endorsements authored by a real admin of the endorsed crew
  and dedupe one-per-admin-per-request (no credibility inflation).
- New parseCrewEndorsement + CrewEndorsement type; documented in NIP.md.

Hooks:
- useEndorseJoinRequest (admin publishes an endorsement, admin-gated).
- useJoinRequestEndorsements: batch-fetch endorsements for all visible requests
  in ONE #e query, grouped per request, admin-verified per crew.
- useCrewNotifications attaches endorsements to each request entry.

UX (JoinRequestCard):
- Founder: primary becomes "Approve & add" when vouched, with a vouch strip
  (stacked admin avatars + "Vouched by Rider +1 more") as social proof.
- Admin: the old dead-end "Only the founder can add members" becomes a real
  "Vouch for them" action, then a "Vouched — waiting on the founder" state,
  plus a one-line explainer so the path isn't a mystery.
- Endorsements enrich the existing request card (no duplicate notification).

Build clean, no console errors. Not deployed.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-03 01:40:04 +07:00
shakespeare.diy
0f2215f46c Sync crew-notification dismissals across devices (private NIP-78)
Fixes the caveat that dismissing a handled invite/join-request on one device
left it un-dismissed on another (it was per-device localStorage).

- New useCrewDismissals hook: stores the dismissed-id set as a single private,
  addressable NIP-78 event (kind 30078, d=skatespots:crew-notif-dismissals)
  with a NIP-44 payload encrypted to the user's own key. One list covers both
  invites and join-requests.
- useDismissCrewNotification: optimistic local update + best-effort encrypted
  publish, so dismissals feel instant and sync across every device the user
  logs in from.
- Graceful degradation: always resolves the UNION of the synced list and a
  local mirror, so it still works offline and on signers without NIP-44 (they
  just don't sync) — no regression from the old behavior.
- Rewired useCrewInvites (accept/decline/approve/dismiss) to the synced set;
  removed the two localStorage-only dismissal helpers. Filtering moved into
  useCrewNotifications so both invite + request lists share one source of truth.
- Documented the kind-30078 dismissal event in NIP.md.

Build clean, no console errors. Not deployed.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-03 01:32:04 +07:00
shakespeare.diy
042e4af8ed Expand crew roles to the full Lab craft roster
Crew roles previously covered only filmer/photographer/editor/artist plus
manager/og. Bring in the rest of the Lab professions so a crew can label any
member by the craft they bring:

- Added: coach, builder, graffiti, tattoo, trainer, musician, judge —
  mirroring the Lab profession kinds 35001–35012 (with a `labKind` link in
  CREW_ROLE_META for future cross-linking to Lab profiles).
- Kept the crew-specific manager + og, and preserved existing slugs so no
  stored crew data breaks.
- Reordered for display: media crafts → Lab crafts → crew-specific.
- Aligned emoji with the Lab (📸 🎓 🔨 🖌️ 💉 💪 🎵 ⚖️).

All consumers (CrewForm role chips, CrewLeadership, CollectiveMembers,
parseCrewRoles) pick these up automatically via CREW_ROLE_ORDER / _META.
Updated the crew-role docs in NIP.md.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-03 01:07:44 +07:00
shakespeare.diy
e2a0184260 Polish create-crew wizard: live feedback, smart stepper, member preview
Wizard chrome:
- Replace the flat progress bar with a proper numbered stepper — icons per
  step, a filled/checkmark state when a step has content, past-step checks,
  and clickable dots (forward jumps still gated on a name).
- Direction-aware step transitions (slide from right going forward, left
  going back) with fade-in.
- Sticky footer gets arrow icons, a shadow, a "ready to roll / add a name"
  hint on the final step, and Create Crew disabled until the name is valid.
- Enter / form-submit before the last step now advances instead of publishing
  a half-filled crew (edit mode unaffected).

Field-level feedback:
- Crew name: inline "Looks good" + check icon when valid, teal border, helper
  line, 100-char cap.
- About: live character counter (turns amber near the 3000 limit).

Member input:
- Live preview chip (avatar + name + npub) that resolves the typed
  npub/nprofile/hex BEFORE adding, so founders confirm the right skater.
- Add button disabled until a valid, not-yet-added key is present; amber hint
  when the text isn't a key yet; "Added" state when already on the roster.
- Extracted a shared decodePubkeyInput helper (also now accepts nostr:nprofile).

Crew Agent:
- Honest beta framing (browser-run, free-tier AI, occasionally slow) and an
  on-state confirmation line when enabled.

No publish/handleSubmit behavior changed; all edits are UX/IA only.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-03 00:59:30 +07:00
shakespeare.diy
5716870863 Redesign create-crew into a guided, idiotproof wizard
Audit finding: the crew form was one 1,100-line endless scroll with ~15
sections in a punishing order (logo dead-last, MLS chat coordinator sitting
inline with the crew name), only one validation gate, and no sense of
progress or what's optional.

Fix — split the flow by intent:
- Create mode: a guided 4-step wizard with a sticky progress bar, per-step
  hints, Back/Continue + "Skip to end", and a sticky footer nav.
    1. The Basics   — logo, name, about, home turf (all you actually need)
    2. Identity & Vibe — vibe tags, crew type
    3. Squad & Spots  — members/roles, home spots, open call
    4. Extras         — treasury, video, sponsor, links, band, NIP-05,
                        agent, and the advanced chat coordinator (reframed
                        in plain language, clearly skippable)
  Only the crew name is gated, and only at step 1. Clickable step dots allow
  jumping back freely.
- Edit mode: unchanged UX intent — renders every section flat on one page
  (power users want it all at once).

All field state and the entire publish/handleSubmit logic are preserved
verbatim; sections were extracted into composable blocks with zero behavior
change. Softened the coordinator copy and the map-pin section so a first-timer
isn't scared off.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-03 00:44:59 +07:00
shakespeare.diy
a2c34800ce Add sponsor picker + notifiable crew join-requests
Part A — Sponsor picker:
- CrewSponsorPicker searches the shop directory and emits a 30017 coordinate
  so founders select a sponsoring shop instead of pasting a raw coordinate.
- Wired into CrewForm, replacing the raw coordinate input.

Part B — Notifiable join-requests (kind 31969):
- New addressable join-request event p-tagged to founder + admins, making the
  request→approve direction symmetric with invites (surfaced in the bell, not
  buried in a DM inbox).
- useCrewInvites: useSendJoinRequest, useIncomingJoinRequests,
  useApproveJoinRequest (founder adds member), useDismissJoinRequest (local).
- CrewRequestToJoin now publishes the structured event.
- JoinRequestCard (Add to crew / Dismiss) wired into NotificationBell and
  counted in the unread badge.
- Documented kind 31969 in NIP.md alongside invites.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-03 00:23:55 +07:00
shakespeare.diy
50d8d9e614 Wire live Cordn encrypted crew chat over the coordinator (ContextVM)
The crew coordinator is no longer just a pointer — when a crew configures one,
the Chat tab runs through it for real.

- src/lib/cordn: clean transport/crypto split so an MLS (ts-mls) upgrade is a
  drop-in swap of the crypto layer only.
  - types.ts: CordnMessage/CordnEndpoint + envelope kind 4497 + app tag.
  - crypto.ts: sealGroupMessage/openGroupMessage — real NIP-44 group encryption
    (per-recipient sealing), the exact seam MLS encrypt-to-epoch slots into.
  - client.ts: buildEnvelope/publishEnvelope/fetchGroupMessages with per-group
    cursor ordering and ciphertext→author sender binding (anti-spoof).
  - endpoint.ts: resolve a crew's `coordinator` to a usable relay endpoint
    (wss/ws direct; https falls back to the ContextVM relay).
- useCordnChat: fetch-then-poll hook mirroring the NIP-17 chat API.
- CrewChat: uses Cordn when the crew has a coordinator, NIP-17 fallback
  otherwise; header badge shows the active transport (Cordn vs Private).
- CrewCoordinatorCard: reflects that private chat is live.
- NIP.md: document envelope kind 4497, delivery (fetch-then-poll + cursors),
  the nip44-group sealed content, and the honest crypto→MLS seam.

Real encryption, real coordinator routing, non-breaking (fallback intact),
with the MLS ratchet as a clearly-marked next step.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-03 00:15:22 +07:00
shakespeare.diy
cbd30fee00 Add skate roles to the crew roster (filmer, photographer, etc.)
A skate team is more than "members" — the filmer, photographer and editor are
core functions. Crews can now assign optional, non-pretentious skate roles:

- Types: CrewRole (filmer/photographer/editor/artist/manager/og) + CREW_ROLE_META
  labels/emoji, `crew-role` tag parser, and `crewRoles` on SkateCrew. Roles are
  additive to membership and grant NO powers (only `admin` does); a member can
  hold roles AND be an admin. Only honoured for founder/listed members.
- CollectiveMembers: optional rolesByPubkey → renders 🎥 Filmer / 📷 Photographer
  flex chips next to each member.
- CrewLeadership: leader + captains show their skate-role chips.
- CrewForm: per-member role toggles + a founder ("Your roles") picker; publishes
  `crew-role` tags scoped to the roster.
- CrewDetailPage: builds rolesByPubkey and threads it to members + leadership.
- NIP.md: document the `crew-role` tag + example.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-03 00:07:30 +07:00
shakespeare.diy
91045e17ae Add "The Part" — crew video/montage (YouTube + Instagram)
A skate crew is defined by its footage. Crews can now feature a headline video:
- videoEmbed.ts: safe parser accepting ONLY YouTube and Instagram URLs (full
  parts on YT, clips on IG). Extracts video id / shortcode; rejects everything
  else so we never render an untrusted iframe. No native/self-hosted video.
- CrewVideoSection ("The Part"): cinematic section — YouTube click-to-load
  facade (poster + play button, no heavy iframe/cookies until play) and an
  Instagram permalink embed, framed with skate language.
- SkateCrew gains `videoUrl` (tag `video`), validated through the embed parser.
- CrewForm: "The Part" input with live source detection (YouTube/Instagram) and
  validation; publishes the `video` tag.
- Crews grid: crews with footage flex a "▶ Part" badge as a discovery signal.
- NIP.md: document the `video` tag (YouTube/Instagram only) + example.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-02 23:59:50 +07:00
shakespeare.diy
4f3339650d Add crew roles & relationships (Phase 3: sponsor + Cordn coordinator)
- Extend SkateCrew with `sponsorRef` (sponsoring shop, NIP-15 stall coord) and
  `coordinator` (Cordn/MLS encrypted-chat Delivery Service pointer), plus parser.
- CrewSponsorBanner: "Sponsored by [Shop]" banner that resolves and links the
  backing shop; renders nothing if the shop can't be resolved.
- CrewCoordinatorCard: surfaces the crew's private-chat coordinator (E2E · MLS),
  explains what the coordinator can/can't see, shows the operator, and prompts
  the leader to set one up when absent — the UI foundation for Cordn chat.
- Wire the sponsor banner (under hero) and coordinator card (sidebar) into
  CrewDetailPage.
- CrewForm: add "Sponsored by" and "Private Chat Coordinator" inputs with
  validation; publish `sponsor` and `coordinator` tags.
- NIP.md: document the `sponsor` and `coordinator` tags, the structured invite
  kind 36229 (with accept/decline flow), and the Cordn/MLS private-chat model
  and why it's the chosen fit for crews.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-02 23:52:33 +07:00
shakespeare.diy
8a384c79d2 Redesign crew detail + crews page (Phase 2: visual)
- New CrewHero: immersive full-bleed banner with per-crew accent glow, club-crest
  logo overlapping the banner, member-count crest, big team name with neon shadow,
  reputation crown + live activity pulse, focus/verification chips, "Led by"
  team-leader inline, vibes and bio. Actions are passed in from the page.
- New CrewLeadership card: frames the founder as "Team Leader" and admins as
  "Captains" with role crests, verified ticks and quick-DM — establishing the
  crew hierarchy and laying groundwork for coordinator roles.
- Rewire CrewDetailPage to use CrewHero + CrewLeadership; wire live activity via
  useCrewsActivity and confirmed-member/admin sets. Removed the old flat hero
  card and now-unused imports.
- Upgrade CrewsPage header into a proper hero band with gradient, glow and
  stat pills.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-02 23:07:27 +07:00
shakespeare.diy
e8c1fbc71a Add crew invitation & notification system (Phase 1)
- Upgrade CrewInviteDialog with a "Send invite" tab: invite a specific npub
  with live invitee preview, optional note, and founder-only "invite as admin"
  toggle. Preserves the shareable-link flow on a second tab.
- Add NotificationBell to desktop + mobile headers: unread badge, popover with
  incoming crew invites (accept/decline) and pending-confirmation crews
  (confirm with public/crew-only visibility).
- Wire useSendCrewInvite / useMyCrewInvites / accept / decline hooks and the
  useCrewNotifications aggregator into the UI.
- Membership stays unforgeable (founder p-tag + invitee NIP-32 attestation).

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-02 22:59:34 +07:00
shakespeare.diy
6ea96171ad Surface Sponsors & Support up top on the profile
- Move sponsors from a buried sidebar card to a prominent full-width "Backed by"
  bar right under the stats strip, above the About/content grid
- Redesigned as a horizontal pill row with an Award badge + gradient/glow accent,
  so reppin' brands reads as a badge of pride
- Removed the duplicate sidebar Sponsors card

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-02 22:38:36 +07:00
shakespeare.diy
2bb0f98f45 Redesign profile stats strip into a balanced, scannable grid
- Even 2x2 (mobile) / 1x4 (desktop) grid instead of a ragged flex-wrap where
  "Gear" dropped to its own row and "Member since" floated awkwardly
- Each stat now has an icon badge + prominent count + label, so the number reads
  as a number (previously the tiny colored count looked like a status dot)
- Zero-value stats render in a muted, inert state — a brand-new profile now reads
  as "nothing yet" rather than a wall of dead grey zeros
- Moved "Zaps received" + "Member since" into a clean secondary meta line below,
  keeping the primary grid focused on the four core contributions

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-02 22:29:10 +07:00
shakespeare.diy
9958020776 Make profile stat pills clickable — jump to their section
- StatPill now accepts an onClick and renders as a hover-lift button when it has
  a non-zero value (inert div when empty), with clear affordance
- Spots / Shops / Crews / Gear pills smooth-scroll to their matching section
  (added ids + scroll-mt-24 offset so headings aren't hidden under sticky nav)

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-02 22:23:22 +07:00