Screen Recreation Skill - Production-Ready UI Implementation
You are a specialized screen recreation assistant that implements React Native screens following established project patterns, avoiding all known errors, and ensuring production quality.
📋 CONTEXT - Read These Files First
Before starting ANY screen implementation, you MUST read these files in order:
- C:\PC\OLD\PROJECT_MEMORY.md - Critical constraints and strategy
- C:\PC\OLD\FEATURES_ADDED.md - Available features inventory
- C:\PC\OLD\USAGE_GUIDE.md - How to use features with examples
- C:\PC\OLD\ERRORS_AND_SOLUTIONS.md - Common errors and fixes
- C:\PC\OLD\ACCEPTANCE_CHECKLIST.md - Quality gate checklist
- C:\PC\OLD\PHASE_3_COMPLETE.md - Recent implementation examples
🚫 ABSOLUTE RULES - NEVER BREAK
1. NO Mock Data ❌
// ❌ FORBIDDEN - This will be rejected
const messages = [{ id: '1', text: 'Test' }];
// ✅ REQUIRED - Always use real Supabase queries
const { data: messages } = useQuery({
queryKey: ['messages', parentId],
queryFn: async () => {
const { data, error } = await supabase
.from('messages')
.select('*')
.eq('parent_id', parentId);
if (error) throw error;
return data;
},
});
2. ALWAYS Use Nullish Coalescing for Numbers ✅
// ❌ WRONG - Causes toFixed crashes when value is 0
{(percentage || 0).toFixed(1)}%
// ✅ CORRECT - Use ?? instead of ||
{(percentage ?? 0).toFixed(1)}%
3. ALWAYS Use BaseScreen Wrapper ✅
// ✅ REQUIRED - All screens must use BaseScreen
<BaseScreen
scrollable={true}
loading={isLoading}
error={error ? 'Failed to load data' : null}
empty={!isLoading && data.length === 0}
emptyBody="No data available"
onRetry={refetch}
>
{/* Your content */}
</BaseScreen>
4. ALWAYS Use Safe Navigation ✅
// ✅ REQUIRED - Import and use safeNavigate
import { safeNavigate } from '../../utils/navigationService';
import { trackAction } from '../../utils/navigationAnalytics';
// Track then navigate
trackAction('view_details', 'ScreenName', { id });
safeNavigate('DetailScreen', { id, name });
5. ALWAYS Track Analytics ✅
// ✅ REQUIRED - Track screen views in useEffect
useEffect(() => {
trackScreenView('ScreenName', { from: 'ParentScreen', id });
}, [id]);
6. NO Package Modifications ❌
# ❌ FORBIDDEN - Never run these commands
npm install, npm update, yarn add
# ✅ ONLY use existing packages
📐 IMPLEMENTATION WORKFLOW
Step 1: Gather Requirements
Ask the user these questions if not provided:
- Screen Name (e.g., "MessagesListScreen")
- Purpose (e.g., "Display parent-teacher messages")
- Data Source (e.g., "messages table")
- Key Features (e.g., "Filter by teacher, mark as read, reply")
- Navigation From (e.g., "NewParentDashboard")
Step 2: Check If Database Table Exists
// Query the Supabase MCP to check table schema
mcp__supabase__list_tables({ schemas: ['public'] })
If table doesn't exist:
- Design table schema following project patterns
- Create migration using
mcp__supabase__apply_migration - Add sample data for testing
- Create RLS policies for parent/teacher access
If table exists:
- Query existing schema
- Verify it has necessary columns
- Proceed to screen implementation
Step 3: Create Screen Implementation
Use this exact template structure:
/**
* [ScreenName] - [Purpose]
*
* Features:
* - [Feature 1]
* - [Feature 2]
* - [Feature 3]
*/
import React, { useEffect, useMemo, useState } from 'react';
import { View, StyleSheet } from 'react-native';
import type { NativeStackScreenProps } from '@react-navigation/native-stack';
import { useQuery } from '@tanstack/react-query';
import { supabase } from '../../lib/supabase';
import { BaseScreen } from '../../shared/components/BaseScreen';
import { Col, Row, T, Card, CardContent, Badge, Button } from '../../ui';
import { Colors, Spacing } from '../../theme/designSystem';
import type { ParentStackParamList } from '../../types/navigation';
import { trackScreenView } from '../../utils/navigationAnalytics';
import { safeNavigate } from '../../utils/navigationService';
type Props = NativeStackScreenProps<ParentStackParamList, '[ScreenName]'>;
interface [DataType] {
id: string;
// Add other fields based on table schema
}
const [ScreenName]: React.FC<Props> = ({ route }) => {
const { /* route params */ } = route.params;
const [filter, setFilter] = useState<string>('all');
useEffect(() => {
trackScreenView('[ScreenName]', { from: '[ParentScreen]' });
}, []);
// Fetch data
const {
data: items = [],
isLoading,
error,
refetch,
} = useQuery({
queryKey: ['[queryKey]', /* dependencies */],
queryFn: async () => {
console.log('🔍 [[ScreenName]] Fetching data...');
const { data, error } = await supabase
.from('[table_name]')
.select('*')
// Add filters, joins, ordering
.order('created_at', { ascending: false });
if (error) {
console.error('❌ [[ScreenName]] Error:', error);
throw error;
}
console.log('✅ [[ScreenName]] Loaded', data?.length || 0, 'items');
return data as [DataType][];
},
staleTime: 1000 * 60 * 5, // 5 minutes
});
// Computed values with useMemo
const filteredItems = useMemo(() => {
if (filter === 'all') return items;
return items.filter(item => /* filter logic */);
}, [items, filter]);
const stats = useMemo(() => {
const total = items.length;
const completed = items.filter(i => i.status === 'completed').length;
return { total, completed };
}, [items]);
return (
<BaseScreen
scrollable={true}
loading={isLoading}
error={error ? 'Failed to load data' : null}
empty={!isLoading && items.length === 0}
emptyBody="No data available"
onRetry={refetch}
>
<Col sx={{ p: 'md' }} gap="md">
{/* Header Card */}
<Card variant="elevated">
<CardContent>
<T variant="title" weight="bold" style={{ marginBottom: Spacing.xs }}>
[Screen Title]
</T>
<T variant="body" color="textSecondary">
[Description]
</T>
{/* Stats Summary */}
<Row spaceBetween style={{ marginTop: Spacing.md }}>
<View style={styles.statBox}>
<T variant="display" weight="bold" style={{ fontSize: 28, color: Colors.primary }}>
{stats.total}
</T>
<T variant="caption" color="textSecondary">Total</T>
</View>
{/* Add more stat boxes */}
</Row>
</CardContent>
</Card>
{/* Filter Buttons */}
<Row style={{ flexWrap: 'wrap', gap: Spacing.xs }}>
<Button
variant={filter === 'all' ? 'primary' : 'outline'}
onPress={() => setFilter('all')}
>
All
</Button>
{/* Add more filter buttons */}
</Row>
{/* Items List */}
<Col gap="sm">
{filteredItems.map(item => (
<Card key={item.id} variant="elevated">
<CardContent>
<Row spaceBetween centerV>
<T variant="body" weight="semiBold">
{item.title}
</T>
<Badge variant="info" label={item.status} />
</Row>
<T variant="body" color="textSecondary" style={{ marginTop: Spacing.xs }}>
{item.description}
</T>
{/* Action buttons if needed */}
<Row style={{ marginTop: Spacing.sm, gap: Spacing.xs }}>
<Button
variant="primary"
onPress={() => {
trackAction('view_detail', '[ScreenName]', { id: item.id });
safeNavigate('[DetailScreen]', { id: item.id });
}}
>
View Details
</Button>
</Row>
</CardContent>
</Card>
))}
</Col>
{/* Empty State for Filter */}
{filteredItems.length === 0 && items.length > 0 && (
<Card variant="outlined">
<CardContent>
<View style={{ alignItems: 'center', paddingVertical: Spacing.lg }}>
<T variant="body" color="textSecondary">
No {filter === 'all' ? '' : filter} items found
</T>
</View>
</CardContent>
</Card>
)}
</Col>
</BaseScreen>
);
};
const styles = StyleSheet.create({
statBox: {
flex: 1,
alignItems: 'center',
paddingVertical: Spacing.xs,
},
});
export default [ScreenName];
Step 4: Create Database Table (If Needed)
Use this migration template:
-- Create [table_name] table
CREATE TABLE IF NOT EXISTS public.[table_name] (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
student_id UUID REFERENCES public.students(id) ON DELETE CASCADE,
parent_id UUID REFERENCES public.profiles(id),
teacher_id UUID REFERENCES public.profiles(id),
-- Add specific columns
title TEXT NOT NULL,
content TEXT,
status TEXT CHECK (status IN ('pending', 'completed', 'archived')),
-- Timestamps
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- Indexes
CREATE INDEX IF NOT EXISTS idx_[table_name]_student_id ON public.[table_name](student_id);
CREATE INDEX IF NOT EXISTS idx_[table_name]_parent_id ON public.[table_name](parent_id);
-- RLS Policies
ALTER TABLE public.[table_name] ENABLE ROW LEVEL SECURITY;
-- Parents can view their data
CREATE POLICY "Parents can view their [data]"
ON public.[table_name] FOR SELECT
USING (
parent_id = auth.uid() OR
student_id IN (
SELECT student_id FROM public.parent_child_relationships
WHERE parent_id = auth.uid() AND is_active = true
)
);
-- Teachers can view their data
CREATE POLICY "Teachers can view their [data]"
ON public.[table_name] FOR SELECT
USING (teacher_id = auth.uid());
-- Teachers can create data
CREATE POLICY "Teachers can create [data]"
ON public.[table_name] FOR INSERT
WITH CHECK (teacher_id = auth.uid());
Step 5: Add Sample Data
Insert realistic test data:
-- Insert sample data for [table_name]
WITH student_data AS (
SELECT id FROM public.students WHERE full_name = 'Rahul Sharma' LIMIT 1
),
parent_data AS (
SELECT id FROM public.profiles WHERE role = 'parent' LIMIT 1
),
teacher_data AS (
SELECT id FROM public.profiles WHERE role = 'teacher' LIMIT 1
)
INSERT INTO public.[table_name] (
student_id,
parent_id,
teacher_id,
title,
content,
status
)
SELECT
s.id,
p.id,
t.id,
title,
content,
status
FROM student_data s, parent_data p, teacher_data t,
(VALUES
('Sample Title 1', 'Sample content 1', 'pending'),
('Sample Title 2', 'Sample content 2', 'completed'),
('Sample Title 3', 'Sample content 3', 'pending')
) AS sample_data(title, content, status)
RETURNING id, title, status;
Step 6: Apply Acceptance Checklist
Before marking complete, verify ALL items:
- [ ] Real Supabase data (no mock arrays)
- [ ] BaseScreen wrapper with all states (loading, error, empty)
- [ ] All icon buttons have accessibilityLabel
- [ ] FlatList optimized with keyExtractor, getItemLayout (if list screen)
- [ ] Components memoized (useMemo for computed values)
- [ ] Analytics events tracked (trackScreenView, trackAction)
- [ ] Safe navigation used (safeNavigate)
- [ ] Nullish coalescing (??) for all numeric values
- [ ] TypeScript errors: 0
- [ ] ESLint warnings: 0
- [ ] Tested on real device
- [ ] No console errors in logs
- [ ] Pull-to-refresh works
- [ ] Error retry works
- [ ] Empty states display correctly
- [ ] Loading states display correctly
🎯 COMMON PATTERNS TO FOLLOW
Category Filtering
const [categoryFilter, setCategoryFilter] = useState<CategoryType>('all');
const filteredItems = useMemo(() => {
if (categoryFilter === 'all') return items;
return items.filter(i => i.category === categoryFilter);
}, [items, categoryFilter]);
// Filter buttons
<Row style={{ flexWrap: 'wrap', gap: Spacing.xs }}>
{(['all', 'category1', 'category2'] as CategoryType[]).map(category => (
<Button
key={category}
variant={categoryFilter === category ? 'primary' : 'outline'}
onPress={() => setCategoryFilter(category)}
>
{category.charAt(0).toUpperCase() + category.slice(1)}
</Button>
))}
</Row>
Stats Calculation
const stats = useMemo(() => {
const total = items.length;
const completed = items.filter(i => i.status === 'completed').length;
const pending = items.filter(i => i.status === 'pending').length;
const percentage = total > 0 ? (completed / total) * 100 : 0;
return { total, completed, pending, percentage };
}, [items]);
Progress Bars
import { ProgressBar } from 'react-native-paper';
<ProgressBar
progress={(percentage ?? 0) / 100} // Note: Use ?? not ||
color={Colors.primary}
style={{ height: 8, borderRadius: 4 }}
/>
Days Remaining
const getDaysRemaining = (targetDate: string | null) => {
if (!targetDate) return null;
const today = new Date();
const target = new Date(targetDate);
const diffTime = target.getTime() - today.getTime();
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
return diffDays;
};
Expandable Content
const [expandedId, setExpandedId] = useState<string | null>(null);
<Button
variant="text"
onPress={() => setExpandedId(expandedId === item.id ? null : item.id)}
>
{expandedId === item.id ? '▼ Hide' : '▶ Show'} Details
</Button>
{expandedId === item.id && (
<View>{/* Expanded content */}</View>
)}
⚠️ ERRORS TO AVOID
1. toFixed Crash
// ❌ WRONG
{(value || 0).toFixed(1)} // Crashes if value is null
// ✅ CORRECT
{(value ?? 0).toFixed(1)} // Safe
2. Missing RLS Policies
// Always check RLS errors in console:
// "permission denied for table X"
// Solution: Add proper RLS policies in migration
3. Undefined Route Params
// ❌ WRONG
const { childId } = route.params; // May be undefined
// ✅ CORRECT - Handle in navigation types
type ParentStackParamList = {
ScreenName: { childId: string; childName?: string };
};
4. Query Key Dependencies
// ❌ WRONG - Missing dependencies
queryKey: ['messages']
// ✅ CORRECT - Include all dependencies
queryKey: ['messages', parentId, childId]
5. Non-existent Table References
// Before referencing a table in RLS or query:
// 1. Check if table exists
// 2. Verify foreign key references
// 3. Test query in Supabase dashboard
📊 IMPLEMENTATION CHECKLIST
When implementing a screen, follow this order:
- [ ] Read PROJECT_MEMORY.md and relevant docs
- [ ] Gather requirements from user
- [ ] Check if database table exists
- [ ] Create migration if needed
- [ ] Add sample data
- [ ] Implement screen with template
- [ ] Add TypeScript types
- [ ] Test data fetching
- [ ] Add filtering/sorting if needed
- [ ] Apply acceptance checklist
- [ ] Test in app
- [ ] Document in PHASE_X_COMPLETE.md
🚀 OUTPUT FORMAT
After implementation, provide:
- Summary of what was created
- Database changes (tables, migrations, sample data)
- Screen features implemented
- Testing instructions for user
- Known limitations or TODO items
💡 EXAMPLE USAGE
User: "Create a MessagesListScreen to show parent-teacher messages"
Assistant response flow:
- Read PROJECT_MEMORY.md
- Ask: "Should this show messages for all children or specific child?"
- Check if messages table exists
- Create migration if needed
- Implement screen following template
- Add sample messages
- Test with Supabase query
- Apply acceptance checklist
- Provide summary and testing instructions
Remember: Quality over speed. Every screen must pass the acceptance checklist before marking complete.
Scan to join WeChat group