Overview
TASKSET 5 successfully implemented a complete dual-mode personality system for SMO1 (cat mode vs. professional mode). This document captures reusable patterns, implementation decisions, and anti-patterns discovered.Pattern 1: Context + localStorage for Persistent UI State
When to Use
- Single boolean/string toggle that should survive page reload
- State doesn’t require backend persistence
- Simple enough that Redux is overkill
Implementation Shape
Why This Shape Works
mountedflag: Prevents hydration mismatch in SSR apps. Always check before rendering mode-dependent content.- Single useEffect: Runs once on mount, reads localStorage, sets mounted. Prevents multiple reads.
- Synchronous state:
setValue()is immediate; localStorage write is fire-and-forget (no await). - Null default:
stored === null ? defaultValue : ...handles both “first visit” and “value changed” cases.
Gotchas
❌ Reading localStorage in component body → React warnings, runs every render ❌ Forgettingmounted flag → hydration mismatch in Next.js
❌ Awaiting localStorage → adds unnecessary async complexity
❌ Storing objects without JSON.stringify → localStorage stores “[object Object]“
Pattern 2: Terminology Mapping & Conditional Rendering
Problem
Need to switch 20+ labels based on mode, without:- Creating duplicate components
- Cluttering JSX with ternaries
- Losing track of which labels changed
Solution
1. Extract label pairs into mapping objects:Scale: Terminology Registry
For 30+ terminology pairs:When Mapping Works Better Than i18n
Use terminology mapping if:- ✅ Single language, multiple modes
- ✅ Modes are UI-driven (not backend-driven)
- ✅ Terminology changes rarely
- ✅ Terminology is scattered across many components
next-i18next, react-i18n) if:
- ✅ Multiple languages AND multiple modes
- ✅ Terminology comes from external source (CMS, translation service)
- ✅ Terminology changes frequently
Pattern 3: Context + Terminology + Components
Architecture Layers
Integration Checklist
- Context provider wraps app in
providers.tsx - Hook exported with null-check error message
- Terminology objects co-located with component (not in utils/)
- Component guards rendering with
if (!mounted) return null - Tests wrap component in provider
- Tests cover both modes
Pattern 4: SSR-Safe Hydration
The Problem
Next.js renders on server without localStorage (or with different localStorage). Client hydrates with localStorage value. Mismatch = React warning.The Solution
Why This Works
- Server renders with
mounted = false; client gets same HTML - Client hydrates (no mismatch)
- useEffect runs, reads localStorage, sets
mounted = true - Component re-renders with correct value
When This Matters
- ✅ Next.js App Router (server-side rendering by default)
- ✅ Any hydration-aware React app
- ❌ Static SPA (no server rendering) — can skip this
Pattern 5: Testing Context-Dependent Components
Helper Function
Test Structure
Coverage Formula
For each component using mode-specific labels:- 1 test for cat mode rendering
- 1 test for professional mode rendering
- 1 test (optional) for toggle/switch behavior (if component has its own toggle)
- 1 test for default value
- 1 test for reading from localStorage
- 1 test for writing to localStorage
- 1 test for toggle action
Anti-Patterns Discovered
❌ Anti-Pattern 1: Separate Component Files
getLabel() helper
❌ Anti-Pattern 2: Feature Flags Instead of User Choice
❌ Anti-Pattern 3: Terminology in Component JSX
❌ Anti-Pattern 4: localStorage Without Effect
❌ Anti-Pattern 5: Conditional Imports
const label = isCatMode ? CAT : PRO
SMO1 TASKSET 5 Results
| Metric | Value |
|---|---|
| Files Created | 4 (1 context + 3 test files) |
| Files Modified | 7 (6 components + 1 provider) |
| Tests Added | 18 |
| Test Coverage | 5 context + 5 component + 8 component = 100% of new code |
| TypeScript Errors | 0 |
| Build Size Change | +0 bytes (context is minimal) |
| Time to Implement | ~4 hours (plan → test → deploy) |
Reusable Checklist for Next Project
When implementing dual-mode UI in another app:- Create Context + hook with
mountedflag - Wrap provider around app root
- Create TERMINOLOGY.ts registry
- Update 2-3 components as pilot
- Write tests with
renderWithCatMode()helper - Add Settings toggle UI
- Test full flow: toggle → localStorage update → page reload persists
- Verify
pnpm buildandpnpm testpass - Document pattern in project CLAUDE.md
Related Patterns in SMO1
This TASKSET 5 pattern pairs with TASKSET 0-4:- Scoring system (pawprintz, treatz, niblz formulas)
- Animation system (context-based animation gating)
- Type system (shared @smo1/types)