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>
155 lines
7.5 KiB
Markdown
155 lines
7.5 KiB
Markdown
# 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
|
|
|
|
### 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`.
|
|
|
|
```sql
|
|
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):
|
|
|
|
```ts
|
|
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
|