Files
GearBox/.planning/milestones/v2.2-phases/30-onboarding-redesign/30-RESEARCH.md
Jean-Luc Makiola 2853477a75
All checks were successful
CI / ci (push) Successful in 1m15s
CI / e2e (push) Has been skipped
CI / deploy (push) Has been skipped
chore: archive v2.2 User Experience Polish milestone
Phases 28-31 archived to milestones/v2.2-phases/
Requirements and roadmap snapshots archived to milestones/

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 16:00:35 +02:00

7.5 KiB

Phase 30: Onboarding Redesign — Research

Researched: 2026-04-12 Status: Complete

Executive Summary

Phase 30 replaces the current 4-step modal onboarding wizard with a full-screen, catalog-driven, hobby-personalized experience. The existing codebase has strong infrastructure for catalog items (globalItems + tags + globalItemTags), discovery queries, and item linking — the main work is building new frontend components and one new backend endpoint for popular items by tag/hobby.

Current State Analysis

Existing Onboarding (OnboardingWizard.tsx)

  • 4-step modal: Welcome → Create Category → Add Item → Done
  • Centered card overlay (fixed inset-0 z-50, max-w-md, backdrop blur)
  • Manual entry — user types category name, item name, weight, price
  • Skip available on all steps
  • onboardingComplete setting tracked in settings table (key-value, per-user)
  • Trigger logic in __root.tsx (~lines 97-107): shows wizard when authenticated + onboardingComplete !== "true" + not dismissed

Catalog Infrastructure (Reusable)

  • globalItems table: brand, model, category, weightGrams, priceCents, imageUrl, description, etc.
  • tags table: id, name (unique)
  • globalItemTags table: many-to-many join (globalItemId, tagId)
  • searchGlobalItems(): ILIKE search with AND-logic tag filtering — exactly what hobby filtering needs
  • getGlobalItemWithOwnerCount(): single item + count of users who linked it — provides "popularity" metric
  • GlobalItemCard component: displays brand, model, image, weight, price, category badges
  • useGlobalItems hook: fetches with query + tag params
  • Discovery service: getRecentGlobalItems(), getPopularSetups(), getTrendingCategories() — patterns for a new getPopularItemsByTags() query

Item Linking Flow

  • useLinkItem mutation: POST /api/items/:itemId/link with { globalItemId }
  • This creates a user item linked to a global catalog item
  • For onboarding batch-add, we need a new batch endpoint or loop through individual creates

Technical Approach

Needed: GET /api/discovery/popular-items?tags=bikepacking,hiking&limit=20

Returns global items filtered by tags, ordered by owner count (number of user items referencing each global item). Pattern follows existing getPopularSetups() with owner count from items.globalItemId.

SELECT gi.*, COUNT(i.id) as owner_count
FROM global_items gi
LEFT JOIN items i ON i.global_item_id = gi.id
JOIN global_item_tags git ON git.global_item_id = gi.id
JOIN tags t ON t.id = git.tag_id
WHERE t.name IN (...hobby_tags)
GROUP BY gi.id
ORDER BY owner_count DESC, gi.id DESC
LIMIT ?

Backend: Batch Add from Catalog

Needed: POST /api/onboarding/complete — batch-creates user items from selected global item IDs, auto-creates categories, marks onboarding complete.

Accepts: { globalItemIds: number[], hobbyTags: string[] }

  • For each selected globalItem: create a user item with globalItemId link, using the global item's category to auto-create user categories
  • Set onboardingComplete setting to "true"
  • Return created items summary

This is a single transactional endpoint to avoid partial state.

Backend: Hobby Tag Mapping

Need a predefined mapping of hobby → tags. This can be a static config (no DB table needed):

const HOBBY_TAG_MAP: Record<string, string[]> = {
  bikepacking: ["bikepacking", "cycling", "camping"],
  hiking: ["hiking", "backpacking", "camping"],
  climbing: ["climbing", "mountaineering"],
  cycling: ["cycling", "road-cycling", "gravel"],
  // extensible...
};

Store in a shared constants file. Frontend uses it for hobby card rendering; backend uses it for tag queries.

Frontend: Full-Screen Onboarding Flow

Component structure:

  • OnboardingFlow.tsx — top-level full-screen component with step management
  • OnboardingWelcome.tsx — welcome/hero step
  • OnboardingHobbyPicker.tsx — card-based hobby selection
  • OnboardingItemBrowser.tsx — grid of popular items with check/uncheck
  • OnboardingReview.tsx — summary of selections before commit

Routing decision: Use a single component with internal step state (not TanStack Router routes). Reasons:

  1. Onboarding is a temporary, one-time flow — no URL navigation needed
  2. Step state is ephemeral — lost on completion
  3. Simpler to manage as a controlled component rendered from __root.tsx

Reusable components:

  • GlobalItemCard — adapt for selectable mode (add checkbox overlay)
  • LucideIcon — for hobby card icons
  • useFormatters — weight/price display

Frontend: Transition Design

Full-screen steps with CSS transitions. Each step is a full-viewport div that slides/fades:

  • Use framer-motion or CSS transition + transform for step transitions
  • Check if project already has framer-motion — if not, CSS transitions are sufficient
  • Step indicator: full-width progress bar (not dots)

Category Auto-Creation Logic

When user confirms selections:

  1. Group selected global items by their category field
  2. For each unique category name: check if user already has a category with that name, create if not
  3. Create user items in each category, linked to their globalItemId

This avoids a manual "create category" step entirely.

Validation Architecture

Critical Paths

  1. Hobby selection → tag filtering → item display: Hobby cards must map to valid tags that return items
  2. Batch selection → review → commit: Selected items must persist through steps and batch-create atomically
  3. Onboarding trigger: Must show for new users, must not show after completion
  4. Empty catalog state: Hobby with no tagged items should show graceful empty state

Edge Cases

  • User with no catalog items for their hobby (empty tags)
  • User selects items, goes back, changes hobby — selections should reset
  • Browser refresh mid-onboarding — starts over (acceptable since onboarding is quick)
  • Multiple hobbies selected — combined tag results, deduplicated
  • Global item has no category — needs fallback category assignment

Testable Assertions

  • GET /api/discovery/popular-items?tags=bikepacking returns items sorted by owner_count DESC
  • POST /api/onboarding/complete with valid globalItemIds creates items and sets onboardingComplete
  • OnboardingFlow renders when onboardingComplete !== "true" and user is authenticated
  • Hobby cards render with correct icons and labels
  • Item selection state persists across steps (hobby → browse → review)
  • Skipping browse step marks onboarding complete without creating items

Dependencies

  • Phase 28 (Depends on): Must be complete — provides the catalog data foundation
  • Existing tags in DB: The hobby-tag mapping assumes tags like "bikepacking", "hiking" exist in the tags table. If catalog data is sparse, the onboarding will show empty grids. This is acceptable for launch — catalog enrichment (Phase 25) populates tags.

Risks and Mitigations

Risk Impact Mitigation
Few catalog items tagged for hobbies Empty onboarding grid Show "Skip" option prominently; fall back to recent items if tag results < threshold
Batch item creation fails mid-transaction Partial state Wrap in DB transaction — all-or-nothing
framer-motion dependency bloat Bundle size Use CSS transitions instead — no new dependency
Hobby-tag mapping becomes stale Irrelevant results Store mapping in editable config; admin can update

RESEARCH COMPLETE