Developer Docs

Onboarding & Malet Creation Architecture

The Mallnline onboarding system is a guided "first visit to first value" flow that segments users by intent and routes them to the right platform vertical. For Malet Owners, this extends into a 5-step Malet creation wizard that transforms intent into a live storefront. This document covers the architecture, components, state management, and extension patterns.


Overview

Every new visitor to Mallnline arrives at the Lobby (/lobby), where an OnboardingHero presents two primary intents: Buy or Sell. Clicking either launches the /onboarding wizard, which walks the user through persona identification, interest/offering selection, and (for business owners) reach sizing. The final step delivers a personalized recommendation with direct CTAs.

User Journey

 โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
 โ”‚                   LOBBY (/lobby)                 โ”‚
 โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
 โ”‚  โ”‚         OnboardingHero (dual-mode)          โ”‚  โ”‚
 โ”‚  โ”‚  Anonymous: gradient mesh + buy/sell CTAs   โ”‚  โ”‚
 โ”‚  โ”‚  Authenticated: greeting + quick-access     โ”‚  โ”‚
 โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
 โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
 โ”‚  โ”‚          IntentBar (quick-actions)           โ”‚  โ”‚
 โ”‚  โ”‚  Browse โ”‚ Book โ”‚ Explore โ”‚ Search โ”‚ Business โ”‚  โ”‚
 โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
 โ”‚          ... feeds, products, floors ...         โ”‚
 โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
 โ”‚  โ”‚   Bottom CTA: "Take the Guided Tour"        โ”‚  โ”‚
 โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
 โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                        โ”‚
                        โ–ผ
 โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
 โ”‚              WIZARD (/onboarding)                โ”‚
 โ”‚  Step 1: Who are you? (Visitor/Murchaser/Owner)  โ”‚
 โ”‚  Step 2: What are you looking for? / offering?   โ”‚
 โ”‚  Step 3: What's your reach? (owners only)        โ”‚
 โ”‚  Step 4: Here's your path (recommendation)       โ”‚
 โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Components

OnboardingHero

Path: src/lib/components/onboarding/OnboardingHero.svelte

A dual-mode hero component that adapts based on authentication:

Mode Content
Anonymous Animated gradient mesh blobs, "Discover your next favorite" heading, "I'm Looking to Buy" / "I'm Looking to Sell" CTAs, "Just browsing?" hint
Authenticated Time-of-day greeting (Good morning/afternoon/evening), quick-access action buttons (Orders, Manage Malet, Create Malet, Search)

Props:

  • isAuthenticated: boolean โ€” Whether the user is logged in
  • userName: string โ€” Display name for greeting
  • hasMalets: boolean โ€” Whether user owns any Malets (shows manage vs create CTA)

OnboardingWizard

Path: src/lib/components/onboarding/OnboardingWizard.svelte

A reusable step wizard shell with:

  • Animated gradient progress bar
  • Dot step indicator (active dot extends width)
  • CSS-only slide-in animations (forward/backward direction)
  • Back / Continue / Get Started navigation
  • "Skip for now" link

Props:

  • currentStep, totalSteps, progress โ€” Progress tracking
  • canAdvance, canGoBack, isLastStep โ€” Navigation control
  • onAdvance, onBack, onComplete โ€” Callbacks
  • stepContent: Snippet โ€” Svelte 5 Snippet for current step content

PersonaCard

Path: src/lib/components/onboarding/PersonaCard.svelte

Selectable card with glassmorphism border on selection, animated checkmark badge, and hover-scale icon. Renders an icon, label, and description.

IntentBar

Path: src/lib/components/onboarding/IntentBar.svelte

Horizontal scrolling quick-action bar with gradient icon backgrounds and staggered reveal animation. Supports snap-scroll on mobile and centered wrap on desktop.


State Management

OnboardingState

Path: src/stores/onboardingStore.svelte.ts

A Svelte 5 reactive class (using $state() and getter-based derived) that manages:

class OnboardingState {
    persona: Persona | null           // 'visitor' | 'murchaser' | 'owner'
    interest: MurchaserInterest | null // 'products' | 'services' | 'events' | 'not_sure'
    offering: OwnerOffering | null     // 'physical' | 'digital' | 'professional' | 'events_exp' | 'other'
    reach: BusinessReach | null        // 'solo' | 'small_team' | 'organization'
    step: number                       // Current wizard step (0-based)

    // Derived
    get totalSteps()                   // Adapts: visitor=2, murchaser=3, owner=4
    get progress()                     // Percentage (0-100)
    get canAdvance()                   // True when current step's selection is made
    get canGoBack()                    // True when step > 0
    get isLastStep()                   // True at final step
    get recommendedVerticals()         // Maps selections to vertical catalog entries

    // Actions
    advance() / back() / reset()       // Step navigation
    complete()                         // Sets localStorage completion flag
    seedFromIntent(intent)             // Pre-fills persona from URL ?intent= param
}

Exported singleton: export const onboarding = new OnboardingState()

Persistence

  • localStorage('mall_onboarding_done') โ€” Returning users bypass onboarding
  • URL seeding โ€” ?intent=murchaser or ?intent=owner pre-fills persona to skip Step 1

Wizard Route

Path: src/routes/onboarding/+page.svelte

The wizard page uses the OnboardingWizard shell with a {#snippet stepContent()} that conditionally renders each step based on onboarding.step.

Step Flow by Persona

Persona Step 0 Step 1 Step 2 Step 3
Visitor Who? Recommendation โ€” โ€”
Murchaser Who? What interests you? Recommendation โ€”
Owner Who? What do you offer? What's your reach? Recommendation

Recommendation Panel

The final step renders:

  • A "Perfect match" badge
  • Recommended vertical chips (from onboarding.recommendedVerticals)
  • Primary CTA (e.g., "Browse Products" for murchasers, "Explore For Business" for owners)
  • Secondary CTA ("Back to Lobby")

Data Strategy

Current (Static Demo)

Vertical catalog and recommendation mapping are hardcoded in onboardingStore.svelte.ts. This enables prototyping without backend dependencies.

Production Path

Replace static mappings with GraphQL queries:

// Future: wire to real data
get recommendedVerticals() {
    // Query: verticals(filter: { interests: $interest }) { name, slug, maletCount }
    return this.queryVerticals(this.interest);
}

Extending the Wizard

Adding a New Step

  1. Increment logic in get totalSteps() on OnboardingState
  2. Add state field (e.g., budget = $state<Budget | null>(null))
  3. Add step rendering in the {#snippet stepContent()} block
  4. Add canAdvance logic for the new step

Adding a New Persona

  1. Extend the Persona type union
  2. Add PersonaCard in Step 0
  3. Define step flow in get totalSteps()
  4. Add recommendation mapping in get recommendedVerticals()


Malet Creation Wizard

The Malet Creation Wizard is a 5-step flow at /create-malet that transforms the onboarding intent into a live Malet. It is the primary path for new Malet Owners arriving from the /for-business page or the onboarding wizard's "Sell" CTA.

User Journey

 โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
 โ”‚           WIZARD (/create-malet)                 โ”‚
 โ”‚  Step 1: Vertical Picker (multi-select)          โ”‚
 โ”‚  Step 2: Identity (name, handle, tagline, email) โ”‚
 โ”‚  Step 3: Vertical Config + Discovery Tags        โ”‚
 โ”‚  Step 4: Tier Selection (Starter/Pro/Enterprise)  โ”‚
 โ”‚  Step 5: Review & Launch                          โ”‚
 โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Entry Points

Source Behavior
/for-business pricing CTAs Links to /create-malet?tier=starter|pro|enterprise โ€” pre-selects tier
/profile page Redirects to /create-malet (legacy form removed)
OnboardingHero authenticated "Create Malet" quick-action CTA

Components

VerticalPicker

Path: src/lib/components/create-malet/VerticalPicker.svelte

Multi-select vertical card grid. Users can toggle multiple categories to create a multi-vertical Malet. Shows a "Multi-vertical" hint when 2+ are selected. Includes a "Not sure yet" skip option.

IdentityForm

Path: src/lib/components/create-malet/IdentityForm.svelte

Name, handle (with real-time debounced availability check via checkHandleAvailability query), tagline, and contact email fields. Handle is auto-slugified from the name with manual override.

VerticalConfig

Path: src/lib/components/create-malet/VerticalConfig.svelte

Dynamic config fields driven by the selected vertical(s) โ€” select, multi-select, toggle, and text inputs. Below the config fields, a Discovery Tags section offers curated tag suggestions (from SECTION_TAGS) plus a custom tag input with slug format validation. Custom tags are auto-registered in the backend registry.

TierPicker

Path: src/lib/components/create-malet/TierPicker.svelte

Three-tier pricing cards (Starter free / Pro $29/mo / Enterprise custom) with feature comparison. The ?tier= URL param auto-selects a tier on entry.

ReviewLaunch

Path: src/lib/components/create-malet/ReviewLaunch.svelte

Summary card with gradient header, vertical icon, handle preview, plan badge, and tags list. Handles the createOneMalet mutation, assigns owner role, and navigates to the new Malet on success.

State Management

CreateMaletState

Path: src/stores/createMaletStore.svelte.ts

A Svelte 5 reactive class (using $state() and getter-based derived) that manages the entire wizard flow:

class CreateMaletState {
	step: number; // Current wizard step (0-4)
	name: string; // Malet display name
	handle: string; // Malet URL handle (m|handle)
	handleAvailable: boolean | null; // Real-time availability check result
	tagline: string; // Short description
	contactEmail: string; // Contact email
	verticalTypes: string[]; // Multi-vertical selection (array)
	sectionTags: string[]; // Discovery tags (curated + custom)
	verticalConfig: Record<string, unknown>; // Per-vertical config fields
	tier: 'starter' | 'pro' | 'enterprise'; // Selected pricing tier
	submitting: boolean; // Submission loading state
	error: string; // Error message

	// Derived
	get selectedSection(); // First selected vertical's section metadata
	get selectedTier(); // Full tier config object
	get verticalConfigFields(); // Merged config fields from all selected verticals
	get previewUrl(); // Full Malet URL preview
	get canAdvanceStep(); // Validation gate per step

	// Actions
	toMaletInput(); // Builds CreateMaletInput payload
	checkHandleAvailability(); // Debounced GraphQL availability check
	reset(); // Resets all state to defaults
}

Exported singleton: export const createMaletState = new CreateMaletState()

Multi-Vertical Support

Malets can belong to multiple verticals simultaneously. This is a key architectural feature:

  • Backend: Malet.verticalTypes: string[] โ€” first entry is the primary vertical, verticalType is kept for backwards compatibility
  • Frontend: VerticalPicker toggles entries in the array; VerticalConfig merges config fields from all selected verticals (deduped by key)
  • Discovery: Multi-vertical Malets appear in every section they belong to

Open Tag System

The discovery tags in Step 3 use a suggest-and-extend model:

  1. Curated suggestions from SECTION_TAGS are shown as toggleable chips grouped by category
  2. Custom tag input allows Malet Owners to type their own tags (slug format: lowercase-with-hyphens, max 40 chars)
  3. Backend auto-registration: SectionTagService.validateAndRegister() creates new SectionTag records for valid custom slugs with category: "User"
  4. Race-condition safe: Duplicate key errors are silently ignored (another request may have registered the same tag)

See Tag Registry for full details.