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>
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
onboardingCompletesetting tracked insettingstable (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 newgetPopularItemsByTags()query
Item Linking Flow
useLinkItemmutation:POST /api/items/:itemId/linkwith{ 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
Backend: New Endpoint — Popular Items by Hobby Tags
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
globalItemIdlink, using the global item's category to auto-create user categories - Set
onboardingCompletesetting 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 managementOnboardingWelcome.tsx— welcome/hero stepOnboardingHobbyPicker.tsx— card-based hobby selectionOnboardingItemBrowser.tsx— grid of popular items with check/uncheckOnboardingReview.tsx— summary of selections before commit
Routing decision: Use a single component with internal step state (not TanStack Router routes). Reasons:
- Onboarding is a temporary, one-time flow — no URL navigation needed
- Step state is ephemeral — lost on completion
- 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 iconsuseFormatters— weight/price display
Frontend: Transition Design
Full-screen steps with CSS transitions. Each step is a full-viewport div that slides/fades:
- Use
framer-motionor CSStransition+transformfor 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:
- Group selected global items by their
categoryfield - For each unique category name: check if user already has a category with that name, create if not
- Create user items in each category, linked to their globalItemId
This avoids a manual "create category" step entirely.
Validation Architecture
Critical Paths
- Hobby selection → tag filtering → item display: Hobby cards must map to valid tags that return items
- Batch selection → review → commit: Selected items must persist through steps and batch-create atomically
- Onboarding trigger: Must show for new users, must not show after completion
- 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=bikepackingreturns items sorted by owner_count DESCPOST /api/onboarding/completewith 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 |