Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions cards/projects/Brief/live-show-platform.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"data": {
"type": "card",
"attributes": {
"spec": "Written by `domain-interview` on 2026-09-28. Contains no design decisions — layout, style and motion are decided by whatever builds from this brief.\n\n## Overview\nA platform for artists and performers to create ticketed live shows, sell General Admission tickets to fans, and redirect ticketed fans to an external stream (YouTube, Twitch, Zoom, etc.). The platform owns ticketing, capacity enforcement, access-link delivery, artist profiles, show discovery, delay notifications, and automatic refunds on cancellation. It does not host video. The single deliverable is a public-facing show discovery page plus the ticketing and artist-management flows behind it.\n\n## Domain primer\n\n**Why this exists** — Artists who perform live (musicians, DJs, comedians, lecturers) want to monetise audiences beyond the free reach of Twitch and YouTube. Existing platforms either don't do ticketing (Twitch/YouTube) or don't do streaming (Eventbrite). This platform bridges the two: it owns the money and the access gate, and delegates the video to whatever the artist already uses.\n\n**Glossary**\n| Term | Means |\n|---|---|\n| Show | A scheduled live event created by an artist, with a date, time, description, poster image, ticket price, and capacity |\n| Artist | A performer who owns one or more shows on the platform; has a persistent profile |\n| Fan | A person who purchases a ticket; identified only by email address, no account required |\n| Ticket | A record of a fan's purchase for a specific show; carries a unique access code |\n| Access link | A URL containing or resolving to the fan's unique code; emailed on purchase; used to enter the show |\n| Stream URL | The external URL (YouTube, Twitch, Zoom, etc.) the artist provides; revealed only to ticket holders at show time |\n| Capacity | The maximum number of tickets that may be sold for a show; enforced as a hard cap |\n| Tier | Ticket tier — this platform uses General Admission only (single price, single experience) |\n| Show status | Lifecycle state of a show: Draft → Published → Live → Ended / Cancelled / Delayed |\n\n**How a practitioner works — steps, not screens**\n1. Artist creates a show: title, date/time, description, poster image, ticket price, capacity, stream URL.\n2. Artist publishes the show — it becomes discoverable on the landing page.\n3. Fan browses the landing page, finds a show, views the show detail page.\n4. Fan enters their email and pays → a Ticket is created with a unique access code → confirmation + access link emailed.\n5. At show time, fan clicks their access link → platform validates the code → if valid and show is Live, fan is redirected to the stream URL.\n6. If the show is delayed, artist updates the show time → all ticket holders receive a delay notification email.\n7. If the show is cancelled, artist cancels the show → all ticket holders receive a cancellation email → automatic refunds are triggered.\n8. After the show ends, the artist's profile reflects the past show in their history.\n\n**Unwritten rules — what a novice gets wrong**\n- Capacity is a hard gate, not a soft warning. Once sold out, the buy flow must be closed — no waitlist, no overrides.\n- The stream URL must not be visible to anyone without a valid ticket. Exposing it on the show page before show time defeats the entire access model.\n- Access links are single-show and non-transferable. The link proves purchase, not identity.\n- A \"Delayed\" show is still Live in principle — tickets remain valid, fans are notified, the stream URL does not change.\n- Refunds are triggered automatically on cancellation; the platform must not require manual admin action per ticket.\n- Artist profile pages show upcoming shows first, past shows second — not chronological from oldest.\n\n## Scope (MoSCoW)\n| Priority | Item | Why |\n|---|---|---|\n| Must | Show creation (title, date/time, description, poster image, price, capacity, stream URL) | Core artist workflow |\n| Must | Show publication and status lifecycle (Draft → Published → Live → Ended / Cancelled / Delayed) | Controls discoverability and access |\n| Must | General Admission ticketing with hard capacity cap | Revenue and access gate |\n| Must | Fan purchase flow (email + payment → unique access link emailed) | Fan acquisition |\n| Must | Access-link validation and stream URL redirect at show time | The product's core promise |\n| Must | Delay notification to all ticket holders | Critical trust feature |\n| Must | Show cancellation with automatic refund trigger | Legal and trust obligation |\n| Must | Artist profile (bio, photo, upcoming shows, past shows) | Persistent artist identity |\n| Must | Landing page: browse and discover upcoming published shows | Public entry point |\n| Should | Show search / filter by date or genre | Discoverability at scale |\n| Should | Artist follow / notify-me-of-new-shows | Fan retention |\n| Could | Multiple ticket tiers (VIP, Early Bird) | Future monetisation |\n| Could | Embedded show trailer video on show detail page | Marketing |\n| Could | Fan dashboard (\"my tickets\") requiring account login | Convenience |\n| Won't (this round) | Video hosting / streaming infrastructure | Out of scope — artist brings their own stream |\n| Won't (this round) | Real-time chat or Q&A during show | Complexity; defer to streaming platform |\n| Won't (this round) | Subscription or tip-based monetisation | Single-price GA is enough for v1 |\n\n## Schema\n| CardDef | Fields | Links | Notes |\n|---|---|---|\n|---|\n| Artist | name, bio, photo (image), genre tags, socialLinks, joinedAt | linksToMany: Show (their shows) | photo is an image field, not a URL string |\n| Show | title, dateTime (datetime), description, posterImage (image), price, streamUrl (string — NOT a realm URL), status (ShowStatus), capacity (CapacityField), soldCount (computed), isSoldOut (computed), cancellationReason | linksTo: Artist | soldCount = count of Tickets for this show; isSoldOut = soldCount ≥ capacity.total; streamUrl is a plain string because it is an external URL (Cardinal Rule 12) |\n| Ticket | accessCode (unique string, generated), fanEmail, purchasedAt (datetime), isUsed (boolean), refundStatus | linksTo: Show | accessCode is generated at purchase; sensitive: fanEmail, refundStatus |\n\n## Element coverage matrix\n| Element | Kind | New / Extend / Reuse | Source if reusing |\n|---|---|---|---|\n| Artist | card | New | — (no artist/performer card Spec found in catalog) |\n| Show | card | New | — (no live show card Spec found in catalog) |\n| Ticket | card | New | — (no ticketing card Spec found in catalog) |\n| CapacityField | field | Reuse | `@cardstack/catalog/cards/events/capacity-field` — holds total, unit, allocations; computed allocated/unallocated |\n| StatusField (Show lifecycle) | field | Reuse | `@cardstack/catalog/fields/status/status` — configurable lifecycle with transition graph; configure for Draft/Published/Live/Ended/Cancelled/Delayed |\n| Image Source Field (poster, artist photo) | field | Reuse (search at build time) | Catalog image field — allows pasted URL or realm file upload |\n| PurchaseTicketCommand | command | New | — triggered by fan checkout; creates Ticket, enforces capacity, sends access-link email |\n| CancelShowCommand | command | New | — sets show status to Cancelled, triggers refund for all Tickets, sends cancellation emails |\n| DelayShowCommand | command | New | — updates show dateTime, sets status to Delayed, sends delay notification emails |\n| RedeemAccessLinkCommand | command | New | — validates accessCode, marks Ticket.isUsed, returns streamUrl to fan |\n| Home / Landing page | card | New | — public show discovery page |\n\n## Content contracts\n\n### Landing page (show discovery)\n- **Purpose**: Public entry point; fans browse and discover upcoming published shows\n- **Mode**: reads\n- **Primary action**: Click a show to view its detail\n- **Mechanism**: navigate → Show detail page\n- **Must contain, in priority order**: 1. Grid/list of upcoming Published shows (poster, title, artist name, date/time, price, sold-out indicator) 2. Search/filter controls (date, genre) 3. Platform name and value proposition headline\n- **Key moment**: Fan sees a show they want and clicks it\n- **Empty state must say**: \"No shows scheduled right now — check back soon\"\n\n### Show detail page\n- **Purpose**: Fan evaluates a show and decides to buy a ticket\n- **Mode**: reads + writes\n- **Primary action**: Buy a ticket\n- **Mechanism**: work → PurchaseTicketCommand (writes to Ticket: accessCode, fanEmail, purchasedAt, refundStatus; decrements Show.soldCount)\n- **Must contain, in priority order**: 1. Poster image 2. Title, artist name (linked to artist profile) 3. Date and time 4. Description 5. Price and tickets remaining 6. Buy button (or \"Sold Out\" state) 7. Artist bio snippet\n- **Key moment**: Fan submits email + payment and receives confirmation\n- **Empty state**: n/a — show detail always has content if Published\n\n### Artist profile page\n- **Purpose**: Fan or artist views the artist's identity and show history\n- **Mode**: reads\n- **Primary action**: Click an upcoming show\n- **Mechanism**: navigate → Show detail page\n- **Must contain, in priority order**: 1. Artist photo and name 2. Bio 3. Genre tags 4. Upcoming shows (soonest first) 5. Past shows (most recent first)\n- **Key moment**: Fan discovers more shows from an artist they just saw\n- **Empty state must say**: \"No upcoming shows — follow to get notified\"\n\n### Artist show management (backstage)\n- **Purpose**: Artist creates, edits, and manages their shows\n- **Mode**: both\n- **Primary action**: Create a new show\n- **Mechanism**: write → Show (fields: title, dateTime, description, posterImage, price, capacity, streamUrl, status)\n- **Must contain, in priority order**: 1. List of artist's shows with status pill 2. Create show button 3. Per-show actions: Edit, Publish, Delay (opens DelayShowCommand), Cancel (opens CancelShowCommand) 4. Ticket sold count vs capacity per show\n- **Key moment**: Artist publishes a show and it appears on the landing page\n- **Empty state must say**: \"You haven't created any shows yet — create your first show\"\n\n### Ticket access / show entry\n- **Purpose**: Fan uses their access link to enter a live show\n- **Mode**: both\n- **Primary action**: Enter the show\n- **Mechanism**: work → RedeemAccessLinkCommand (reads Ticket.accessCode, validates, returns Show.streamUrl)\n- **Must contain, in priority order**: 1. Show title and artist 2. Show status (Live / Not started yet / Ended) 3. Enter show button (enabled only when status = Live) 4. Countdown to show time when not yet Live\n- **Key moment**: Fan clicks \"Enter show\" and is redirected to the stream URL\n- **Empty state must say**: \"This link is invalid or has already been used\"\n\n## Flows\n\n**Fan buys a ticket**: Landing page → Show detail → enters email + pays → PurchaseTicketCommand runs → capacity checked (hard cap) → Ticket created with unique accessCode → confirmation + access link emailed to fan → soldCount incremented\n\n**Fan watches the show**: Opens access link email → Ticket access page → RedeemAccessLinkCommand validates code → if Show.status = Live → redirect to Show.streamUrl; if not yet Live → show countdown\n\n**Artist cancels a show**: Artist dashboard → Cancel show → CancelShowCommand runs → Show.status = Cancelled → all Tickets get refundStatus = Refunded → cancellation emails sent to all fanEmails\n\n## Sample data\n\n### Artist\n- **DJ Nova** — Electronic / House DJ. Bio: \"Nova has been lighting up underground raves since 2018. Her sets blend driving techno with hypnotic melody.\" Genre: Electronic, House. Joined: 2025-03-12.\n- **The River Chorus** — Indie folk trio from Portland. Bio: \"Three-part harmonies and acoustic storytelling.\" Genre: Folk, Indie. Joined: 2025-06-01.\n- **Marcus Laine** — Stand-up comedian. Bio: \"Headline act at the 2025 Edinburgh Fringe. Sharp, honest, loud.\" Genre: Comedy. Joined: 2026-01-15.\n\n### Show\n- **Nova: Midnight Frequency** — Artist: DJ Nova. Date: 2026-10-15T22:00:00Z. Price: $18. Capacity: 500. Sold: 423. Status: Published. Stream: YouTube Live.\n- **The River Chorus: Autumn Session** — Artist: The River Chorus. Date: 2026-10-22T20:00:00Z. Price: $12. Capacity: 200. Sold: 200. Status: Published (SOLD OUT).\n- **Marcus Laine: Loud & Honest** — Artist: Marcus Laine. Date: 2026-11-05T19:30:00Z. Price: $15. Capacity: 300. Sold: 87. Status: Published. Stream: Zoom.\n- **Nova: New Year Rave** — Artist: DJ Nova. Date: 2026-12-31T23:00:00Z. Price: $25. Capacity: 1000. Sold: 0. Status: Draft.\n\n### Ticket\n- Fan: sarah@example.com — Show: Nova: Midnight Frequency — Code: MF-7X92-KNPL — Purchased: 2026-09-20T14:32:00Z — Used: false — Refund: none.\n- Fan: tom.w@example.com — Show: The River Chorus: Autumn Session — Code: AS-3Q41-RVMZ — Purchased: 2026-09-10T09:15:00Z — Used: false — Refund: none.\n- Fan: priya@example.com — Show: Marcus Laine: Loud & Honest — Code: LH-8B17-CXWT — Purchased: 2026-09-27T18:44:00Z — Used: false — Refund: none.\n\n## Open questions\n- [ ] Payment processor — which provider (Stripe, etc.) and who holds the merchant account (platform or artist)?\n- [ ] Refund mechanism — automatic refund via payment processor API, or manual trigger with automated email?\n- [ ] Artist onboarding — self-serve signup, or does the platform admin approve each artist?\n- [ ] Access link expiry — does the link expire after the show ends, or remain usable for a replay if the artist provides a VOD URL later?\n- [ ] Notification delivery — email only, or SMS / push as well?\n",
"designDirection": null,
"motion": null,
"cardInfo": {
"name": "Live Show Platform",
"summary": "A platform for artists and performers to create shows, sell General Admission tickets, and redirect ticketed fans to an external stream — with artist profiles, capacity enforcement, delay notifications, and automatic refunds on cancellation.",
"notes": null,
"cardThumbnailURL": null
}
},
"meta": {
"adoptsFrom": {
"module": "../brief",
"name": "Brief"
}
}
}
}
34 changes: 34 additions & 0 deletions cards/projects/Spec/brief.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"data": {
"type": "card",
"attributes": {
"cardTitle": "Brief",
"specType": "card",
"ref": {
"module": "../brief",
"name": "Brief"
},
"readMe": "# Brief\n\n## Summary\nThe one artifact a design pipeline writes to before anything is built, at\n`brief.gts`. Each stage owns one MarkdownField and never touches another's:\n`spec` is what the thing is, `designDirection` is how it should look and\nmove, `motion` is the build numbers for an arc the direction asked for. A\nstage replaces its own field whole, so nobody reads the whole brief back to\nadd a part, and nobody can truncate another stage's text by accident. Title\nand summary live on `cardInfo`.\n\n## API\n- `spec` — MarkdownField; domain primer, schema, coverage matrix, per-screen content contracts, flows, sample data, open questions. Written by `domain-interview`\n- `designDirection` — MarkdownField; style in full, the first screen in full, other screens as stubs, acceptance lines. Written by `design-direction`\n- `motion` — MarkdownField; one motion system, engine per beat, load-bearing structure, exhaustive effect table, the three capture points. Written by `motion-authoring`, only when the direction recorded an arc\n- `cardInfo.name` / `cardInfo.summary` — the title and the overview paragraph\n\n## Usage\n```json\n{ \"meta\": { \"adoptsFrom\": { \"module\": \"@cardstack/catalog/cards/projects/brief\", \"name\": \"Brief\" } } }\n```\nInstances live at `Brief/<slug>.json` in the realm being built for. Fill a\nfield with `patch-fields` once the card has indexed; change one block inside\na field with `apply-markdown-edit`, not a resend of the field.\n\n## Non-goals\nScores and review verdicts — `design-review` reads this card and writes\nnothing. Build state, task lists and theme tokens belong to whatever builds\nfrom the brief, not on it.",
"cardInfo": {
"name": "Brief",
"summary": null,
"notes": null,
"cardThumbnailURL": null
},
"cardDescription": "The one artifact a design pipeline writes to before anything is built — spec, design direction and motion as three fields, one per stage."
},
"relationships": {
"linkedExamples.0": {
"links": {
"self": "../Brief/live-show-platform"
}
}
},
"meta": {
"adoptsFrom": {
"module": "https://cardstack.com/base/spec",
"name": "Spec"
}
}
}
}
Loading
Loading