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>
This commit is contained in:
shakespeare.diy 2026-09-03 01:07:44 +07:00
parent e2a0184260
commit 042e4af8ed
2 changed files with 40 additions and 15 deletions

2
NIP.md
View file

@ -2064,7 +2064,7 @@ bare `p` tag on the crew is only a claim until the member self-labels.
| `sponsor` | Optional **sponsoring shop** — a NIP-15 stall coordinate (`30017:pubkey:d`) for a local skate shop that backs the crew. Renders a "Sponsored by [Shop]" banner linking to the shop. Only shown when the shop resolves. | | `sponsor` | Optional **sponsoring shop** — a NIP-15 stall coordinate (`30017:pubkey:d`) for a local skate shop that backs the crew. Renders a "Sponsored by [Shop]" banner linking to the shop. Only shown when the shop resolves. |
| `coordinator` | Optional **encrypted-chat coordinator** for the crew's private group. Shape: `["coordinator", <url>, <coordinatorPubkey?>, <operatorPubkey?>]`. `<url>` is an `http(s)`/`wss` endpoint of a **Cordn** coordinator (an MLS / RFC 9420 Delivery Service reached over ContextVM). The crew event stays public and discoverable; message content, membership secrets and MLS ratchets live client-side. Nothing crew-specific is published to public relays. `<coordinatorPubkey>` is the coordinator service pubkey (hex) for ContextVM addressing; `<operatorPubkey>` is the human (hex) who runs it, if delegated. | | `coordinator` | Optional **encrypted-chat coordinator** for the crew's private group. Shape: `["coordinator", <url>, <coordinatorPubkey?>, <operatorPubkey?>]`. `<url>` is an `http(s)`/`wss` endpoint of a **Cordn** coordinator (an MLS / RFC 9420 Delivery Service reached over ContextVM). The crew event stays public and discoverable; message content, membership secrets and MLS ratchets live client-side. Nothing crew-specific is published to public relays. `<coordinatorPubkey>` is the coordinator service pubkey (hex) for ContextVM addressing; `<operatorPubkey>` is the human (hex) who runs it, if delegated. |
| `video` | Optional **crew video / montage** ("The Part") — a **YouTube** or **Instagram** URL, rendered as the crew's headline embed. Only these two sources are accepted (skate parts live on YouTube, clips on Instagram); any other URL is ignored. No native/self-hosted video. Clients must parse it to a known embed (YouTube video id / Instagram shortcode) and reject anything else rather than render an untrusted iframe. | | `video` | Optional **crew video / montage** ("The Part") — a **YouTube** or **Instagram** URL, rendered as the crew's headline embed. Only these two sources are accepted (skate parts live on YouTube, clips on Instagram); any other URL is ignored. No native/self-hosted video. Clients must parse it to a known embed (YouTube video id / Instagram shortcode) and reject anything else rather than render an untrusted iframe. |
| `crew-role` | Optional **skate role** assigned to a roster member. Shape: `["crew-role", <memberPubkey>, <role>]`, repeatable (a member may hold several). `<role>` ∈ `filmer`, `photographer`, `editor`, `artist`, `manager`, `og`. These are the *functions* on a skate team (the filmer is as vital as the skaters) and are purely a **flex/label** — they grant **no powers** (only the `admin` marker does). Only honoured for the founder or a listed member; roles on non-roster pubkeys are ignored. | | `crew-role` | Optional **skate role** assigned to a roster member. Shape: `["crew-role", <memberPubkey>, <role>]`, repeatable (a member may hold several). `<role>` ∈ the full Lab-craft set — `filmer`, `photographer`, `editor`, `coach`, `builder`, `artist`, `graffiti`, `tattoo`, `trainer`, `musician`, `judge` — plus the crew-specific `manager` and `og`. The Lab-craft roles mirror the Lab profession kinds (35001–35012), so a crew's "filmer" speaks the same language as a Lab Filmer profile. These are the *functions* on a skate team (the filmer is as vital as the skaters) and are purely a **flex/label** — they grant **no powers** (only the `admin` marker does). Only honoured for the founder or a listed member; roles on non-roster pubkeys are ignored. |
### Crew-Hosted Events ### Crew-Hosted Events

View file

@ -1676,10 +1676,19 @@ export interface SkateCrew {
* `["crew-role", <memberPubkey>, <role>]` * `["crew-role", <memberPubkey>, <role>]`
*/ */
export type CrewRole = export type CrewRole =
| 'filmer' // ── Lab crafts (mirror the Lab profession kinds 35001–35012) ──
| 'photographer' | 'filmer' // 35002
| 'editor' | 'photographer' // 35004
| 'artist' | 'editor' // 35003
| 'coach' // 35001
| 'builder' // 35005
| 'artist' // 35007 (designer / board graphics)
| 'graffiti' // 35008
| 'tattoo' // 35009
| 'trainer' // 35010 (physio / strength)
| 'musician' // 35011
| 'judge' // 35012
// ── Crew-specific roles (no Lab equivalent) ──
| 'manager' | 'manager'
| 'og'; | 'og';
@ -1688,18 +1697,34 @@ export interface CrewMemberRole {
role: CrewRole; role: CrewRole;
} }
/** Human labels + emoji for each crew role (skate-native language). */ /**
export const CREW_ROLE_META: Record<CrewRole, { label: string; emoji: string }> = { * Human labels + emoji for each crew role (skate-native language). Lab crafts
filmer: { label: 'Filmer', emoji: '🎥' }, * reuse the same emoji as their Lab profession so the language stays consistent
photographer: { label: 'Photographer', emoji: '📷' }, * across the app; `labKind` links a role back to its Lab profile kind (if any)
editor: { label: 'Editor', emoji: '✂️' }, * so we can cross-link "this filmer has a Lab profile" later.
artist: { label: 'Artist', emoji: '🎨' }, */
manager: { label: 'Team Manager', emoji: '📋' }, export const CREW_ROLE_META: Record<CrewRole, { label: string; emoji: string; labKind?: number }> = {
og: { label: 'OG', emoji: '👑' }, filmer: { label: 'Filmer', emoji: '🎥', labKind: SKATE_FILMER_KIND },
photographer: { label: 'Photographer', emoji: '📸', labKind: SKATE_PHOTOGRAPHER_KIND },
editor: { label: 'Editor', emoji: '✂️', labKind: SKATE_EDITOR_KIND },
coach: { label: 'Coach', emoji: '🎓', labKind: SKATE_COACH_KIND },
builder: { label: 'Builder', emoji: '🔨', labKind: SKATE_BUILDER_KIND },
artist: { label: 'Artist', emoji: '🎨', labKind: SKATE_ARTIST_KIND },
graffiti: { label: 'Graffiti', emoji: '🖌️', labKind: SKATE_GRAFFITI_KIND },
tattoo: { label: 'Tattoo', emoji: '💉', labKind: SKATE_TATTOO_KIND },
trainer: { label: 'Trainer', emoji: '💪', labKind: SKATE_TRAINER_KIND },
musician: { label: 'Musician', emoji: '🎵', labKind: SKATE_MUSICIAN_KIND },
judge: { label: 'Judge', emoji: '⚖️', labKind: SKATE_JUDGE_KIND },
manager: { label: 'Team Manager', emoji: '📋' },
og: { label: 'OG', emoji: '👑' },
}; };
/** All assignable crew roles, in display order. */ /** All assignable crew roles, in display order (media crafts → Lab crafts → crew-specific). */
export const CREW_ROLE_ORDER: CrewRole[] = ['filmer', 'photographer', 'editor', 'artist', 'manager', 'og']; export const CREW_ROLE_ORDER: CrewRole[] = [
'filmer', 'photographer', 'editor',
'coach', 'builder', 'artist', 'graffiti', 'tattoo', 'trainer', 'musician', 'judge',
'manager', 'og',
];
const CREW_ROLE_SET = new Set<string>(CREW_ROLE_ORDER); const CREW_ROLE_SET = new Set<string>(CREW_ROLE_ORDER);