| name | recovery-feature-scaffold |
| description | Scaffold complete features for the Steps to Recovery app including database schema, encrypted storage, offline sync, React Query hooks, screens, and tests. Use when adding new data models (gratitude lists, resentments, daily inventory), creating new journaling features, building step work tools, or implementing any new feature requiring SQLite + Supabase sync. |
Recovery Feature Scaffold
Generate complete, production-ready features for Steps to Recovery with one command.
Quick Start
cd .claude/skills/recovery-feature-scaffold/scripts
node scaffold.js <FeatureName>
node scaffold.js GratitudeList
This generates 9 files in under 1 second with all boilerplate wired up.
This generates:
- Database migration (SQLite + Supabase)
- TypeScript types
- Encrypted storage hooks (useFeature.ts)
- CRUD operations with React Query
- List screen + Detail/Edit screen
- Navigation updates
- Test file
What Gets Generated
feature: GratitudeList
apps/mobile/src/
├── features/gratitude-list/
│ ├── types.ts # TypeScript interfaces
│ ├── hooks/
│ │ └── useGratitude.ts # React Query + encryption
│ ├── screens/
│ │ ├── GratitudeListScreen.tsx
│ │ └── GratitudeDetailScreen.tsx
│ ├── components/
│ │ └── GratitudeCard.tsx
│ └── __tests__/
│ └── gratitude.test.ts
├── lib/
│ └── database/
│ └── migrations/
│ └── 007_add_gratitude_list.sql
└── navigation/
└── AppNavigator.tsx # Auto-updated
supabase/migrations/
└── 007_add_gratitude_list.sql # RLS policies included
Manual Scaffolding (No Script)
Follow this 5-step workflow when script isn't available:
Step 1: Define Types
Create src/features/<feature>/types.ts:
export interface GratitudeItem {
id: string;
user_id: string;
encrypted_content: string;
category: 'people' | 'things' | 'experiences' | 'other';
created_at: string;
updated_at: string;
}
export interface CreateGratitudeInput {
content: string;
category: GratitudeItem['category'];
}
export interface UpdateGratitudeInput {
id: string;
content?: string;
category?: GratitudeItem['category'];
}
Step 2: Create Database Migration
SQLite migration (src/lib/database/migrations/XXX_add_feature.sql):
CREATE TABLE IF NOT EXISTS gratitude_items (
id TEXT PRIMARY KEY,
user_id TEXT NOT NULL,
encrypted_content TEXT NOT NULL,
category TEXT CHECK (category IN ('people', 'things', 'experiences', 'other')),
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_gratitude_user ON gratitude_items(user_id);
CREATE INDEX IF NOT EXISTS idx_gratitude_created ON gratitude_items(created_at DESC);
Supabase migration (supabase/migrations/XXX_add_feature.sql):
CREATE TABLE IF NOT EXISTS public.gratitude_items (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
encrypted_content TEXT NOT NULL,
category TEXT CHECK (category IN ('people', 'things', 'experiences', 'other')),
created_at TIMESTAMP WITH TIME ZONE DEFAULT now(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT now()
);
ALTER TABLE public.gratitude_items ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Users can only access their own gratitude items"
ON public.gratitude_items FOR ALL
USING (auth.uid() = user_id);
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = now();
RETURN NEW;
END;
$$ language 'plpgsql';
CREATE TRIGGER update_gratitude_items_updated_at
BEFORE public.gratitude_items
update_updated_at_column();
Step 3: Create Encrypted Hooks
src/features/<feature>/hooks/useFeature.ts:
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { useDatabase } from '../../../contexts/DatabaseContext';
import { encryptContent, decryptContent } from '../../../utils/encryption';
import { generateUUID } from '../../../utils/uuid';
import type { GratitudeItem, CreateGratitudeInput, UpdateGratitudeInput } from '../types';
const FEATURE_KEY = 'gratitude-items';
export function useGratitudeItems() {
const { db, userId } = useDatabase();
return useQuery({
queryKey: [FEATURE_KEY],
queryFn: async (): Promise<GratitudeItem[]> => {
if (!db) throw new Error('Database not initialized');
const items = await db.getAllAsync<GratitudeItem>(
'SELECT * FROM gratitude_items WHERE user_id = ? ORDER BY created_at DESC',
userId,
);
.(
items.( (item) => ({
...item,
: (item.),
})),
);
},
: !!db && !!userId,
});
}
() {
{ db, userId } = ();
queryClient = ();
{ enqueueSync } = ();
({
: (: ): <> => {
(!db) ();
id = ();
now = .();
encrypted = (input.);
db.(
,
id,
userId,
encrypted,
input.,
now,
now,
);
: = {
id,
: userId!,
: encrypted,
: input.,
: now.(),
: now.(),
};
(, id, , item);
item;
},
: {
queryClient.({ : [] });
},
});
}
() {
{ db } = ();
queryClient = ();
{ enqueueSync } = ();
({
: (: ): <> => {
(!db) ();
now = .();
: [] = [];
: ( | )[] = [];
(input.) {
updates.();
values.( (input.));
}
(input.) {
updates.();
values.(input.);
}
updates.();
values.(now);
values.(input.);
db.(, ...values);
(, input., , { : input. });
},
: {
queryClient.({ : [] });
},
});
}
() {
{ db } = ();
queryClient = ();
{ enqueueSync } = ();
({
: (: ): <> => {
(!db) ();
db.(, id);
(, id, );
},
: {
queryClient.({ : [] });
},
});
}
Step 4: Create Screens
src/features/<feature>/screens/FeatureListScreen.tsx:
import { useGratitudeItems, useDeleteGratitude } from '../hooks/useGratitude';
import { GratitudeCard } from '../components/GratitudeCard';
import { EmptyState } from '../../../components/EmptyState';
import { Button } from '../../../components/ui/Button';
export function GratitudeListScreen({ navigation }): React.ReactElement {
const { data: items, isLoading } = useGratitudeItems();
const deleteMutation = useDeleteGratitude();
if (isLoading) return <LoadingScreen />;
return (
<View className="flex-1 bg-slate-900">
<FlatList
data={items}
keyExtractor={(item) => item.id}
renderItem={({ item }) => (
<GratitudeCard
item={item}
onPress={() => navigation.navigate('GratitudeDetail', { id: item.id })}
onDelete={() => deleteMutation.mutate(item.id)}
/>
)}
ListEmptyComponent={
}
/>
navigation.navigate('GratitudeDetail', { id: 'new' })}
/>
);
}
Step 5: Add Navigation
Update src/navigation/AppNavigator.tsx:
import { GratitudeListScreen } from '../features/gratitude-list/screens/GratitudeListScreen';
import { GratitudeDetailScreen } from '../features/gratitude-list/screens/GratitudeDetailScreen';
<Stack.Screen
name="GratitudeList"
component={GratitudeListScreen}
options={{ title: 'Gratitude' }}
/>
<Stack.Screen
name="GratitudeDetail"
component={GratitudeDetailScreen}
options={{ title: 'Gratitude Item' }}
/>
Update src/navigation/types.ts:
export type RootStackParamList = {
GratitudeList: undefined;
GratitudeDetail: { id: string };
};
Feature Templates
Template: Simple List (Gratitude, Affirmations)
Single text field + category/tags.
Template: Journal Entry (Daily Reflection, Step Work)
Rich text content + mood/feelings + date.
Template: Checklist (Step Tasks, Daily Goals)
Multiple items with checkboxes + progress tracking.
Template: Relationship (People, Sponsors)
Contact info + relationship type + notes.
Best Practices
- Always encrypt sensitive content - Use
encryptContent() before storing
- Queue all mutations - Call
enqueueSync() after every write
- Invalidate queries - Use
queryClient.invalidateQueries() after mutations
- Add indexes - Index
user_id and created_at columns
- Test encryption - Verify roundtrip in generated tests
Complete Example
See references/example-output.md for full generated code from node scaffold.js GratitudeList.
Common Feature Patterns
| Feature Type | Command | Use Case |
|---|
| Simple List | node scaffold.js GratitudeList | Gratitude, affirmations, quotes |
| Journal Entry | node scaffold.js DailyInventory | Reflections, step work |
| Checklist | node scaffold.js StepOneTasks | Step work tasks, goals |
| Relationships | node scaffold.js SponsorContact | People, sponsors, contacts |
Troubleshooting
Migration number collision
Script auto-detects next migration number. If you have conflicts, manually rename files.
Missing imports
Add to tsconfig.json paths if needed:
"@/features/*": ["./src/features/*"]
Supabase deploy fails
Ensure you're logged in:
npx supabase login
npx supabase link --project-ref tbiunmmvfbakwlzykpwq