Developer Docs

Culture & Arts

The Culture vertical powers museums, theaters, galleries, cultural centers, and performing arts venues with three integrated systems: exhibition curation, artist profiles, and cultural event programming. Malet Owners configure these features via the Storefront Window layout system.

NOTE

These features live in the experiences subgraph โ€” the same cross-vertical Feature Plane that houses Entertainment & Experiences, Professional Services, and Wellness & Beauty. The architecture is documented in docs/architecture/18-progressive-vertical-extraction.md.


Architecture: Two-Plane Design

The Culture vertical uses the same Control + Feature plane separation as its sibling verticals:

  • Control Plane (malets subgraph): VerticalConfig feature flags decide which Culture modules are enabled per Malet โ€” exhibitionCuration, artistProfiles, seasonPasses, culturalCommerce.
  • Feature Plane (experiences subgraph): Self-contained ExhibitionModule and ArtistProfileModule. Each can be extracted to its own subgraph when a dedicated Culture vertical Team forms.
  • Extensions (services subgraph): Cultural event type and seating fields on existing Service entities.

Three new Storefront Window layout slot types are available: EXHIBITION_GALLERY, ARTIST_SPOTLIGHT, and SEASON_CALENDAR.

What Culture Reuses

TIP

Culture is the most reuse-heavy vertical. Three major systems come "for free" from earlier verticals:

Reused System From Vertical Culture Use Case
Event Ticketing Entertainment Shows, workshops, screenings, festivals โ€” full lifecycle + QR check-in
Gamified Loyalty Entertainment Reward repeat attendees, artwork buyers, and season pass holders
Membership Engine Wellness Season passes as membership plans with event quotas per season

Exhibition Curation

Create virtual gallery exhibitions with curated artwork collections, provenance tracking, and direct-sale capabilities.

Exhibition Types

Type Use Case
VISUAL_ART Paintings, drawings, sculptures
PHOTOGRAPHY Photo exhibitions, documentary series
MIXED_MEDIA Multi-format exhibitions combining various media
INSTALLATION Site-specific installations, immersive experiences
DIGITAL Digital art, NFT showcases, generative art

Exhibition Lifecycle

DRAFT โ†’ OPEN โ†’ CLOSED โ†’ ARCHIVED

Exhibitions start as DRAFT while the curator arranges artwork. Once ready, they're OPEN for public viewing and sales. After the run ends, they move to CLOSED and can later be ARCHIVED for historical records.

Mutations

# Create a new exhibition (Malet Owner)
mutation {
	createExhibition(
		maletId: "m_soweto_theater"
		title: "Impressions of Light"
		slug: "impressions-of-light"
		type: VISUAL_ART
		description: "A celebration of contemporary South African painting"
		coverImageUrl: "https://r2.mallnline.com/exhibitions/impressions.jpg"
	) {
		id
		title
		type
		status
	}
}

# Add artwork to the exhibition
mutation {
	addExhibitionArtwork(
		exhibitionId: "exh_1"
		title: "Sunset Over Soweto"
		artist: "Naledi Mokoena"
		imageUrl: "https://r2.mallnline.com/art/sunset_soweto.jpg"
		medium: "Oil on Canvas"
		dimensions: "36x48 inches"
		provenance: "Commissioned 2025. First exhibited at Johannesburg Art Fair."
	) {
		id
		artworks {
			title
			artist
			medium
			provenance
			isSold
		}
	}
}

# Open for public viewing
mutation {
	openExhibition(exhibitionId: "exh_1") {
		id
		status
	}
}

# Mark an artwork as sold
mutation {
	markArtworkSold(exhibitionId: "exh_1", artworkTitle: "Sunset Over Soweto") {
		id
		artworks {
			title
			isSold
			price
		}
	}
}

# Close the exhibition
mutation {
	closeExhibition(exhibitionId: "exh_1") {
		id
		status
	}
}

# Archive for historical records
mutation {
	archiveExhibition(exhibitionId: "exh_1") {
		id
		status
	}
}

# Update curator notes (private, Malet Owner only)
mutation {
	updateCuratorNotes(
		exhibitionId: "exh_1"
		curatorNotes: "Strong opening night. 3 pieces sold. Consider extending run."
	) {
		id
		curatorNotes
	}
}

Queries

# All open exhibitions for a Malet (public โ€” storefront display)
query {
	activeExhibitions(maletId: "m_soweto_theater") {
		id
		title
		type
		coverImageUrl
		startsAt
		endsAt
		artworks {
			title
			artist
			imageUrl
			medium
			price
			isSold
		}
	}
}

# All exhibitions for a Malet (Malet Owner โ€” includes drafts/archived)
query {
	maletExhibitions(maletId: "m_soweto_theater", status: DRAFT) {
		id
		title
		status
		artworks {
			title
			artist
		}
	}
}

# Single exhibition by ID
query {
	exhibition(id: "exh_1") {
		id
		title
		description
		type
		status
		curatorNotes
		artworks {
			title
			artist
			medium
			dimensions
			year
			imageUrl
			price
			isSold
			provenance
		}
	}
}

ExhibitionArtwork Fields

Each artwork embedded in an exhibition tracks:

Field Type Description
title String! Artwork title
artist String! Artist name
artistProfileId ID Link to on-platform ArtistProfile (optional)
medium String Material/medium (e.g., "Oil on Canvas", "Bronze")
dimensions String Physical size (e.g., "36x48 inches")
year Int Year created
imageUrl String! High-resolution image for display
price Float Sale price in cents (null = Not For Sale)
isSold Boolean! Whether the piece has been sold
provenance String Origin/ownership history
sortOrder Int! Display order in the gallery

Artist Profiles

Public showcase pages for performing artists, visual artists, and instructors. Each profile is scoped per Malet โ€” the same artist exhibiting at multiple venues has separate profiles per Malet.

Mutations

# Create an artist profile (Malet Owner)
mutation {
	createArtistProfile(
		maletId: "m_soweto_theater"
		name: "Naledi Mokoena"
		slug: "naledi-mokoena"
		bio: "Contemporary South African painter known for vibrant township scenes."
		photoUrl: "https://r2.mallnline.com/artists/naledi.jpg"
		portfolioUrl: "https://naledi.art"
	) {
		id
		name
		slug
		isActive
	}
}

# Update artist bio
mutation {
	updateArtistProfile(
		artistId: "art_1"
		bio: "Award-winning South African painter. Featured at Johannesburg Art Fair 2025."
	) {
		id
		bio
	}
}

# Deactivate profile (soft-disable โ€” not publicly visible)
mutation {
	deactivateArtistProfile(artistId: "art_1") {
		id
		isActive
	}
}

Queries

# All active artists for a Malet (public โ€” artist spotlight carousel)
query {
	maletArtists(maletId: "m_soweto_theater") {
		id
		name
		slug
		bio
		specialties
		photoUrl
		portfolioUrl
		socialLinks {
			platform
			url
		}
	}
}

# Single artist by ID
query {
	artistProfile(id: "art_1") {
		id
		name
		bio
		specialties
		socialLinks {
			platform
			url
		}
		isActive
	}
}

# Artist by slug (for SEO-friendly URLs)
query {
	artistBySlug(maletId: "m_soweto_theater", slug: "naledi-mokoena") {
		id
		name
		bio
		portfolioUrl
	}
}

Each artist profile supports an array of social media links:

# Example social links structure
socialLinks: [
  { platform: "Instagram", url: "https://instagram.com/naledi_art" }
  { platform: "Behance", url: "https://behance.net/naledi" }
  { platform: "X", url: "https://x.com/naledi_mokoena" }
]

Cultural Event Programming

Extended service fields for cultural events on the existing services subgraph:

Service Fields

Field Type Description
culturalEventType CulturalEventType SHOW, EXHIBITION, WORKSHOP, SCREENING, FESTIVAL
seatingType SeatingType RESERVED, GENERAL_ADMISSION, TIMED_ENTRY

Cultural Event Types

Type Example Seating Model
SHOW Theater production, concert, dance recital Usually RESERVED
EXHIBITION Art exhibition, museum display Usually TIMED_ENTRY
WORKSHOP Art class, writing workshop Usually GENERAL_ADMISSION
SCREENING Film screening, documentary showing RESERVED or GENERAL_ADMISSION
FESTIVAL Multi-day cultural festival, art fair GENERAL_ADMISSION

Seating Types

Type Description
RESERVED Specific seats assigned at purchase โ€” for venues with seat maps
GENERAL_ADMISSION First-come, first-served entry โ€” capacity tracked but no assigned seats
TIMED_ENTRY Entry within a specific time window โ€” for exhibitions and museums

Example: Create a Cultural Event

mutation {
	createOneService(
		input: {
			service: {
				name: "Hamlet โ€” Opening Night"
				description: "Shakespeare's tragedy performed by the Soweto Players"
				price: 15000 # R150.00 (cents)
				maletId: "m_soweto_theater"
				culturalEventType: SHOW
				seatingType: RESERVED
			}
		}
	) {
		id
		name
		culturalEventType
		seatingType
	}
}

TIP

Cultural events integrate with the existing Event Ticketing system. Use Event + EventTicket entities for tiered pricing (General, VIP, Patron), atomic capacity tracking, and QR check-in at the venue.


Season Passes

Season passes are powered by the Membership Engine from the Wellness vertical โ€” reused as multi-event subscriptions with event quotas.

How It Works

A "Gold Season Pass" is a MembershipPlan with a WellnessPlanTier and a sessionsPerMonth quota representing events per season:

# Create a season pass plan
mutation {
	createMembershipPlan(
		maletId: "m_soweto_theater"
		name: "Gold Season Pass"
		tier: GOLD
		sessionsPerMonth: 12 # 12 events per season
		monthlyPrice: 30000 # R300/month
		productDiscountPercent: 15 # 15% off merchandise
	) {
		id
		name
		tier
		sessionsPerMonth
	}
}

Each time a season pass holder attends an event, recordMembershipUsage decrements their remaining quota. When the season ends, renewMembershipPeriod resets the counter.


Workroom Steps

The Culture vertical ships with 4 default Workroom steps for exhibition/show lifecycle management:

Step Type Assignee Description
Exhibition Setup APPROVAL Malet Owner Curate artwork, set pricing for pieces available for sale
Artist Coordination FORM Malet Owner Confirm artist profiles, bios, and portfolio links for the event program
Event Program FORM Malet Owner Finalize show schedule, ticket tiers, and seating arrangements
Post-Event Review FORM Malet Owner Record attendance, artwork sales, and feedback for future programming

These steps are automatically configured on new Culture Malets via the vertical seed (indigo/amber branding).


Deferred Features

The following capabilities are planned for future phases using in-house, open-source/standard solutions โ€” no third-party SaaS dependencies:

Feature Phase Approach
Interactive Seat Maps Phase 2 SVG-based seat map renderer with zone selection (in-house)
Recurring Events Phase 2 rrule (RFC 5545 / iCalendar) on Service for weekly/monthly recurrence
Digital Content DRM Phase 2 Signed URLs with time-limited access tokens via media subgraph
Season Pass Auto-Renewal Phase 2 Stripe Billing via existing MembershipSubscription.stripeSubscriptionId

TIP

All Phase 2 deferred features are tracked in the consolidated docs/PHASE2_BACKLOG.md. The seasonPasses and culturalCommerce feature flags are already present in the Control Plane.


Frontend Implementation

Three Svelte 5 storefront widgets render the Culture vertical in the Visitor-facing storefront:

Components

Widget File Description
ExhibitionGallery src/lib/components/culture/ExhibitionGallery.svelte Exhibition cards with cover images, type badges (Visual Art/Photography/Mixed Media/Installation/Digital), status indicators (Now Open/Coming Soon/Closed/Archived), date ranges, and artwork counts with availability.
ArtistShowcase src/lib/components/culture/ArtistShowcase.svelte Artist profile cards with circular photos, specialty tags, bio previews, and social media platform icons. Links to artist detail pages.
SeasonCalendar src/lib/components/culture/SeasonCalendar.svelte Vertical timeline of exhibitions sorted chronologically with color-coded status dots, date ranges, and artwork counts.

File Map

File Purpose
src/lib/queries/culture.ts GraphQL queries (GET_ACTIVE_EXHIBITIONS, GET_EXHIBITION, GET_MALET_ARTISTS) + TypeScript interfaces
src/lib/utils/cultureUtils.ts 11 utility functions โ€” exhibition type labels/icons, status display, artwork pricing (NFS handling), date ranges, social platform icons
src/lib/components/storefront/WidgetRegistry.svelte Widget registration: EXHIBITION_GALLERY, ARTIST_SHOWCASE, SEASON_CALENDAR
tests/cultureUtils.test.ts 16 unit tests covering all utility functions

Layout Slot Types

Slot Type Widget Auth Required
EXHIBITION_GALLERY ExhibitionGallery No
ARTIST_SHOWCASE ArtistShowcase No
SEASON_CALENDAR SeasonCalendar No

NOTE

For Visitor-facing documentation on exhibitions, artworks, and artist profiles, see the Support Manual: Exhibitions & Artists.