docs(30): research onboarding redesign phase
This commit is contained in:
154
.planning/phases/30-onboarding-redesign/30-RESEARCH.md
Normal file
154
.planning/phases/30-onboarding-redesign/30-RESEARCH.md
Normal file
@@ -0,0 +1,154 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user