@buivietphi/skill-mobile-mt 2.0.1 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,375 @@
1
+ # Navigation Patterns — Complex Flows
2
+
3
+ > On-demand module. Loaded when implementing auth flows, deep links, modals, or tab navigation.
4
+ > Contains production patterns for React Native, Flutter, iOS, and Android.
5
+
6
+ ---
7
+
8
+ ## Auth-Based Navigation Flow
9
+
10
+ ### React Native (React Navigation)
11
+
12
+ ```typescript
13
+ // navigation/RootNavigator.tsx
14
+ import { NavigationContainer } from '@react-navigation/native';
15
+ import { createNativeStackNavigator } from '@react-navigation/native-stack';
16
+ import { useAuthStore, useIsLoggedIn } from '@/stores/useAuthStore';
17
+
18
+ const Stack = createNativeStackNavigator<RootStackParamList>();
19
+
20
+ export function RootNavigator() {
21
+ const isLoggedIn = useIsLoggedIn();
22
+ const [isReady, setIsReady] = useState(false);
23
+
24
+ useEffect(() => {
25
+ // Check stored token on app start
26
+ async function bootstrap() {
27
+ const hasToken = useAuthStore.getState().token;
28
+ if (hasToken) {
29
+ const valid = await useAuthStore.getState().refreshSession();
30
+ if (!valid) useAuthStore.getState().logout();
31
+ }
32
+ setIsReady(true);
33
+ }
34
+ bootstrap();
35
+ }, []);
36
+
37
+ if (!isReady) return <SplashScreen />;
38
+
39
+ return (
40
+ <NavigationContainer>
41
+ <Stack.Navigator screenOptions={{ headerShown: false }}>
42
+ {isLoggedIn ? (
43
+ // Authenticated stack
44
+ <>
45
+ <Stack.Screen name="MainTabs" component={MainTabNavigator} />
46
+ <Stack.Screen name="ProductDetail" component={ProductDetailScreen} />
47
+ <Stack.Screen name="Settings" component={SettingsScreen} />
48
+ {/* Modals */}
49
+ <Stack.Group screenOptions={{ presentation: 'modal' }}>
50
+ <Stack.Screen name="EditProfile" component={EditProfileScreen} />
51
+ <Stack.Screen name="ImageViewer" component={ImageViewerScreen} />
52
+ </Stack.Group>
53
+ </>
54
+ ) : (
55
+ // Unauthenticated stack
56
+ <>
57
+ <Stack.Screen name="Onboarding" component={OnboardingScreen} />
58
+ <Stack.Screen name="Login" component={LoginScreen} />
59
+ <Stack.Screen name="Register" component={RegisterScreen} />
60
+ <Stack.Screen name="ForgotPassword" component={ForgotPasswordScreen} />
61
+ </>
62
+ )}
63
+ </Stack.Navigator>
64
+ </NavigationContainer>
65
+ );
66
+ }
67
+ ```
68
+
69
+ ### Flutter (GoRouter)
70
+
71
+ ```dart
72
+ // navigation/app_router.dart
73
+ import 'package:go_router/go_router.dart';
74
+
75
+ final appRouter = GoRouter(
76
+ initialLocation: '/',
77
+ redirect: (context, state) {
78
+ final isLoggedIn = ref.read(authProvider).isLoggedIn;
79
+ final isAuthRoute = state.matchedLocation.startsWith('/auth');
80
+
81
+ if (!isLoggedIn && !isAuthRoute) return '/auth/login';
82
+ if (isLoggedIn && isAuthRoute) return '/';
83
+ return null;
84
+ },
85
+ routes: [
86
+ // Auth routes
87
+ GoRoute(path: '/auth/login', builder: (_, __) => const LoginScreen()),
88
+ GoRoute(path: '/auth/register', builder: (_, __) => const RegisterScreen()),
89
+
90
+ // App routes with bottom nav shell
91
+ ShellRoute(
92
+ builder: (_, __, child) => ScaffoldWithNavBar(child: child),
93
+ routes: [
94
+ GoRoute(path: '/', builder: (_, __) => const HomeScreen()),
95
+ GoRoute(path: '/search', builder: (_, __) => const SearchScreen()),
96
+ GoRoute(path: '/cart', builder: (_, __) => const CartScreen()),
97
+ GoRoute(path: '/profile', builder: (_, __) => const ProfileScreen()),
98
+ ],
99
+ ),
100
+
101
+ // Detail routes (no bottom nav)
102
+ GoRoute(
103
+ path: '/product/:id',
104
+ builder: (_, state) => ProductDetailScreen(id: state.pathParameters['id']!),
105
+ ),
106
+ ],
107
+ );
108
+ ```
109
+
110
+ ---
111
+
112
+ ## Deep Linking
113
+
114
+ ### React Native (Expo Router)
115
+
116
+ ```typescript
117
+ // app/_layout.tsx — Expo Router handles deep links automatically via file structure
118
+ // URL: myapp://product/abc-123 → app/product/[id].tsx
119
+
120
+ // app/product/[id].tsx
121
+ import { useLocalSearchParams } from 'expo-router';
122
+
123
+ export default function ProductDetailScreen() {
124
+ const { id } = useLocalSearchParams<{ id: string }>();
125
+
126
+ // Validate param exists
127
+ if (!id) return <NotFoundScreen />;
128
+
129
+ // Fetch and render
130
+ const { data, isLoading } = useProductDetail(id as ProductId);
131
+ // ...
132
+ }
133
+ ```
134
+
135
+ ### React Navigation Deep Link Config
136
+
137
+ ```typescript
138
+ // navigation/linking.ts
139
+ const linking: LinkingOptions<RootStackParamList> = {
140
+ prefixes: ['myapp://', 'https://myapp.com'],
141
+ config: {
142
+ screens: {
143
+ MainTabs: {
144
+ screens: {
145
+ Home: 'home',
146
+ Profile: 'profile/:userId',
147
+ },
148
+ },
149
+ ProductDetail: 'product/:productId',
150
+ Settings: 'settings',
151
+ },
152
+ },
153
+ };
154
+
155
+ // Handle notification deep links
156
+ import * as Notifications from 'expo-notifications';
157
+
158
+ function useNotificationDeepLink() {
159
+ const navigation = useAppNavigation();
160
+
161
+ useEffect(() => {
162
+ const sub = Notifications.addNotificationResponseReceivedListener(response => {
163
+ const data = response.notification.request.content.data;
164
+ if (data.screen === 'ProductDetail' && data.productId) {
165
+ navigation.navigate('ProductDetail', { productId: data.productId as ProductId });
166
+ }
167
+ });
168
+ return () => sub.remove();
169
+ }, [navigation]);
170
+ }
171
+ ```
172
+
173
+ ---
174
+
175
+ ## Bottom Tab Navigation
176
+
177
+ ### React Native — Lazy Tabs with State Preservation
178
+
179
+ ```typescript
180
+ // navigation/MainTabNavigator.tsx
181
+ import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
182
+
183
+ const Tab = createBottomTabNavigator<MainTabParamList>();
184
+
185
+ export function MainTabNavigator() {
186
+ return (
187
+ <Tab.Navigator
188
+ screenOptions={{
189
+ headerShown: false,
190
+ // Lazy load: render tab only when first visited
191
+ lazy: true,
192
+ // Freeze inactive tabs (prevent re-renders)
193
+ freezeOnBlur: true,
194
+ tabBarActiveTintColor: theme.colors.primary,
195
+ }}
196
+ >
197
+ <Tab.Screen
198
+ name="Home"
199
+ component={HomeScreen}
200
+ options={{
201
+ tabBarIcon: ({ color, size }) => <HomeIcon color={color} size={size} />,
202
+ tabBarLabel: 'Home',
203
+ }}
204
+ />
205
+ <Tab.Screen
206
+ name="Search"
207
+ component={SearchScreen}
208
+ options={{
209
+ tabBarIcon: ({ color, size }) => <SearchIcon color={color} size={size} />,
210
+ }}
211
+ />
212
+ <Tab.Screen
213
+ name="Cart"
214
+ component={CartScreen}
215
+ options={{
216
+ tabBarIcon: ({ color, size }) => <CartIcon color={color} size={size} />,
217
+ tabBarBadge: cartCount > 0 ? cartCount : undefined,
218
+ }}
219
+ />
220
+ <Tab.Screen
221
+ name="Profile"
222
+ component={ProfileScreen}
223
+ options={{
224
+ tabBarIcon: ({ color, size }) => <ProfileIcon color={color} size={size} />,
225
+ }}
226
+ />
227
+ </Tab.Navigator>
228
+ );
229
+ }
230
+ ```
231
+
232
+ ---
233
+
234
+ ## Modal Navigation
235
+
236
+ ### React Native — Modal Stack
237
+
238
+ ```typescript
239
+ // Present as modal (slides up from bottom on iOS)
240
+ navigation.navigate('EditProfile'); // registered in modal group
241
+
242
+ // Dismiss modal
243
+ navigation.goBack();
244
+
245
+ // Modal with result — pass callback via params or use event
246
+ // Option A: Use navigation params
247
+ navigation.navigate('SelectAddress', {
248
+ onSelect: (address: Address) => {
249
+ // handle selected address
250
+ },
251
+ });
252
+
253
+ // Option B: Use event emitter
254
+ import { DeviceEventEmitter } from 'react-native';
255
+ // In modal: DeviceEventEmitter.emit('addressSelected', address);
256
+ // In parent: DeviceEventEmitter.addListener('addressSelected', handler);
257
+ ```
258
+
259
+ ### Flutter — Modal Bottom Sheet
260
+
261
+ ```dart
262
+ // Modal bottom sheet
263
+ showModalBottomSheet(
264
+ context: context,
265
+ isScrollControlled: true, // full-height if needed
266
+ useSafeArea: true,
267
+ builder: (context) => DraggableScrollableSheet(
268
+ initialChildSize: 0.6,
269
+ minChildSize: 0.3,
270
+ maxChildSize: 0.9,
271
+ builder: (_, controller) => AddressPickerSheet(scrollController: controller),
272
+ ),
273
+ );
274
+ ```
275
+
276
+ ---
277
+
278
+ ## Push Notification Navigation
279
+
280
+ ### React Native (Expo Notifications)
281
+
282
+ ```typescript
283
+ // hooks/useNotificationSetup.ts
284
+ import * as Notifications from 'expo-notifications';
285
+ import * as Device from 'expo-device';
286
+
287
+ export function useNotificationSetup() {
288
+ useEffect(() => {
289
+ registerForPush();
290
+ }, []);
291
+
292
+ async function registerForPush() {
293
+ if (!Device.isDevice) return; // skip simulator
294
+
295
+ const { status } = await Notifications.getPermissionsAsync();
296
+ let finalStatus = status;
297
+
298
+ if (status !== 'granted') {
299
+ const { status: newStatus } = await Notifications.requestPermissionsAsync();
300
+ finalStatus = newStatus;
301
+ }
302
+
303
+ if (finalStatus !== 'granted') return;
304
+
305
+ const token = (await Notifications.getExpoPushTokenAsync()).data;
306
+ await userService.registerPushToken(token);
307
+ }
308
+ }
309
+
310
+ // Notification handler — runs when app receives notification
311
+ Notifications.setNotificationHandler({
312
+ handleNotification: async () => ({
313
+ shouldShowAlert: true,
314
+ shouldPlaySound: true,
315
+ shouldSetBadge: true,
316
+ }),
317
+ });
318
+ ```
319
+
320
+ ---
321
+
322
+ ## Permissions Handling Pattern
323
+
324
+ ```typescript
325
+ // hooks/usePermission.ts
326
+ import * as Location from 'expo-location';
327
+ import * as Camera from 'expo-camera';
328
+ import { Alert, Linking } from 'react-native';
329
+
330
+ type PermissionType = 'camera' | 'location' | 'notifications';
331
+
332
+ export function usePermission(type: PermissionType) {
333
+ const [granted, setGranted] = useState<boolean | null>(null);
334
+
335
+ const request = useCallback(async () => {
336
+ let result: { status: string };
337
+
338
+ switch (type) {
339
+ case 'camera':
340
+ result = await Camera.requestCameraPermissionsAsync();
341
+ break;
342
+ case 'location':
343
+ result = await Location.requestForegroundPermissionsAsync();
344
+ break;
345
+ case 'notifications':
346
+ result = await Notifications.requestPermissionsAsync();
347
+ break;
348
+ }
349
+
350
+ if (result.status === 'granted') {
351
+ setGranted(true);
352
+ return true;
353
+ }
354
+
355
+ // Permission denied — guide user to settings
356
+ Alert.alert(
357
+ 'Permission Required',
358
+ `Please enable ${type} access in Settings to use this feature.`,
359
+ [
360
+ { text: 'Cancel', style: 'cancel' },
361
+ { text: 'Open Settings', onPress: () => Linking.openSettings() },
362
+ ]
363
+ );
364
+ setGranted(false);
365
+ return false;
366
+ }, [type]);
367
+
368
+ return { granted, request };
369
+ }
370
+
371
+ // Usage:
372
+ // const camera = usePermission('camera');
373
+ // const canUse = await camera.request();
374
+ // if (canUse) { /* proceed */ }
375
+ ```
@@ -64,40 +64,138 @@
64
64
  ```xml
65
65
  <think>
66
66
  BUG: [description]
67
- FILE: [path]
67
+ ERROR MESSAGE: [paste exact error]
68
+
69
+ ⛔ STOP — CLASSIFY + SEARCH PROJECT FIRST (before ANY analysis):
70
+
71
+ <error_classification>
72
+ TYPE: [RUNTIME CRASH / BUILD ERROR / TYPE MISMATCH / NETWORK ERROR /
73
+ RENDER ERROR / NAVIGATION ERROR / PERFORMANCE / STATE ERROR /
74
+ NATIVE ERROR / MEMORY ERROR / INVESTIGATION]
75
+
76
+ SEARCH STRATEGY based on type:
77
+ → RUNTIME/STATE/RENDER/NAVIGATION → Search src/ FIRST → then trace outward
78
+ → BUILD/NATIVE → Search config files FIRST (tsconfig/gradle/Pod/pubspec)
79
+ → NETWORK/API → Search API service files → then .env → then interceptors
80
+ → INVESTIGATION → Search by feature name → read → report (don't fix yet)
81
+
82
+ If complex bug → Read shared/debugging-intelligence.md for pattern match
83
+ </error_classification>
84
+
85
+ <project_search>
86
+ STEP 1: Extract keywords from error:
87
+ → File/path in stack trace: [extract]
88
+ → Function/class/component name: [extract]
89
+ → Module/package name: [extract]
90
+ → Line number: [extract if available]
91
+ → Error code / HTTP status: [extract if available]
92
+
93
+ STEP 2: Filter noise from log (if user pasted log):
94
+ → SKIP: node_modules/*, React internals, engine frames
95
+ → FOCUS: lines with src/ paths, "Error:", "Caused by:", YOUR component names
96
+
97
+ STEP 3: Search project source code (MANDATORY):
98
+ → Grep "[keyword]" src/ ← ALWAYS start here (unless BUILD error)
99
+ → Grep "[function_name]" src/ ← find the actual function
100
+ → Glob "**/*[ComponentName]*" ← find the actual file
101
+ → Results: [list files found]
102
+
103
+ STEP 4: Read matched files (TOP 3-5):
104
+ → Read [file1] — [what I found: actual code, types, imports]
105
+ → Read [file2] — [what I found: related logic, callers]
106
+ → Read [file3] — [what I found: state/store connected to this]
107
+
108
+ ⛔ If I skipped Step 1-4 → GO BACK AND DO THEM NOW
109
+ ⛔ If I found 0 results in src/ → widen: lib/ → app/ → project root
110
+ </project_search>
68
111
 
69
112
  <source_verification>
70
- ⚠️ BEFORE analyzing verify I have real data:
71
- - [ ] Read the actual file (not guessing from error message)
72
- - [ ] Verified function/class names exist (grep)
113
+ ⚠️ Verify I have REAL project data (not assumptions):
114
+ - [ ] Classified error type and picked correct search strategy
115
+ - [ ] Filtered noise from log (if applicable)
116
+ - [ ] Searched src/ with Grep for error keywords → found files
117
+ - [ ] Read the actual file(s) where bug occurs
118
+ - [ ] Verified function/class names exist in project (grep result)
73
119
  - [ ] Checked package versions in package.json/pubspec.yaml
74
120
  - [ ] Identified data types from actual code (not assumed)
121
+ - [ ] Traced the call chain: who calls this → what it returns
75
122
  </source_verification>
76
123
 
77
- <context_needed>
78
- - Read [file] to understand current implementation
79
- - Grep for similar patterns: grep "[pattern]" src/
80
- - Check imports and dependencies
81
- </context_needed>
82
-
83
- <root_cause>
84
- [Analyze AFTER loading context - don't guess]
85
- - What code is executed?
86
- - What values are passed?
87
- - What conditions are checked?
88
- SOURCE: [file:line where the bug is cite exact location]
89
- </root_cause>
124
+ <root_cause_tracing>
125
+ NO FIXES WITHOUT ROOT CAUSE INVESTIGATION FIRST
126
+
127
+ STEP 1 IMMEDIATE CAUSE (what throws):
128
+ - Error type: [from classification above]
129
+ - Crash/error at: [file:line from project search]
130
+ - What code does at that line: [describe from reading]
131
+ - What value is wrong: [actual vs expected]
132
+
133
+ STEP 2 TRACE BACKWARD (what called this):
134
+ - Who calls this function? → [grep callers in src/]
135
+ - What data does caller pass? [trace data origin]
136
+ - Go up one level: who calls the caller? → [trace further]
137
+
138
+ STEP 3 — ROOT CAUSE (where chain breaks):
139
+ - Root cause at: [file:line — where correct data becomes incorrect]
140
+ - WHY it fails: [based on actual code read, NOT guess]
141
+ - Does this match a known pattern? → [check debugging-intelligence.md if loaded]
142
+ - Does root cause explain ALL symptoms? YES/NO
143
+ → If NO → theory is wrong → re-trace from Step 2
144
+ </root_cause_tracing>
145
+
146
+ <working_example>
147
+ Search for similar working code in SAME project:
148
+ → Grep for similar pattern that works: [search term]
149
+ → Found working example at: [file:line] (or "none found")
150
+ → Differences between broken vs working:
151
+ 1. [difference]
152
+ 2. [difference]
153
+ → The fix should align broken code with working pattern
154
+ </working_example>
155
+
156
+ <hypothesis>
157
+ ⛔ 1 HYPOTHESIS → 1 MINIMAL CHANGE → VERIFY
158
+
159
+ HYPOTHESIS: [specific theory based on root cause]
160
+ CHANGE: [smallest possible change to test this — 1 change only]
161
+ EXPECTED RESULT: [what should happen if hypothesis is correct]
162
+
163
+ ⛔ If this fails → REVERT → form NEW hypothesis (never stack fixes)
164
+ ⛔ If 3 hypotheses fail → STOP → question architecture
165
+ </hypothesis>
90
166
 
91
167
  <fix>
92
- [Specific change with code snippet]
168
+ [Specific change with code snippet — before → after]
93
169
 
94
170
  WHY IT WORKS:
95
- [Explain the fix based on root cause]
171
+ [Explain based on root cause tracing — not guess]
96
172
  SOURCE: [where this fix pattern comes from — project code / skill file / official docs]
173
+
174
+ DEFENSE IN DEPTH (make bug structurally impossible):
175
+ - Layer 1 (input): [validation added]
176
+ - Layer 2 (state): [guard added]
177
+ - Layer 3 (render): [null check / fallback added]
97
178
  </fix>
98
179
 
180
+ <verification>
181
+ ⛔ NO COMPLETION CLAIMS WITHOUT EVIDENCE
182
+
183
+ 🚩 Anti-rationalization check:
184
+ - Am I saying "should work now" without running it? → RUN IT
185
+ - Am I "confident" without evidence? → PROVE IT
186
+ - Am I stacking 3+ changes? → REVERT. 1 change only.
187
+
188
+ Evidence:
189
+ - [ ] Fix addresses root cause (not just symptoms)
190
+ - [ ] All symptoms explained by this root cause
191
+ - [ ] Working example pattern followed (if found)
192
+ - [ ] Defense-in-depth added at [N] layers
193
+ - [ ] Side effects checked (grep for other callers)
194
+ - [ ] Both platforms considered (iOS + Android)
195
+ </verification>
196
+
99
197
  <side_effects>
100
- - Files that import this: [list after grep]
198
+ - Files that import this: [list from grep results]
101
199
  - Tests affected: [list]
102
200
  - Platform-specific: iOS [impact] / Android [impact]
103
201
  </side_effects>
@@ -116,6 +214,64 @@ FILE: [path]
116
214
  </think>
117
215
  ```
118
216
 
217
+ ### Diagnostic Scan (user unsure / vague / "check this for me")
218
+
219
+ ```xml
220
+ <think>
221
+ USER SAID: [what user described or asked — could be vague]
222
+ AREA: [extract: screen name / feature name / module name / file name]
223
+
224
+ <area_identification>
225
+ What did user mention or show?
226
+ → Screen/feature name: [extract from user's words]
227
+ → File pasted/referenced: [if any]
228
+ → Behavior described: [if any]
229
+ → If unclear → I should ask: "Which screen or feature should I check?"
230
+ </area_identification>
231
+
232
+ <project_search>
233
+ Search broadly for this area:
234
+ → Grep "[feature]" src/ → found: [list files]
235
+ → Glob "**/*[ScreenName]*" → found: [list files]
236
+ → Also search: related hooks, services, stores, utils
237
+ → Total files to scan: [N files]
238
+ </project_search>
239
+
240
+ <diagnostic_scan>
241
+ For EACH file, run the checklist:
242
+
243
+ FILE: [file1:path]
244
+ □ Crash risks: [findings or "clean"]
245
+ □ Memory leaks: [findings or "clean"]
246
+ □ Race conditions: [findings or "clean"]
247
+ □ Security: [findings or "clean"]
248
+ □ Performance: [findings or "clean"]
249
+ □ UX/states: [findings or "clean"]
250
+ □ Data flow: [trace API → state → render — any break?]
251
+ □ Edge cases: [empty data? error response? offline? slow?]
252
+
253
+ FILE: [file2:path]
254
+ □ ... (repeat for each file)
255
+ </diagnostic_scan>
256
+
257
+ <report>
258
+ Scanned: [N files] in [area name]
259
+
260
+ 🔴 Issues found:
261
+ 1. [SEVERITY] [file:line] — [description]
262
+ 2. [SEVERITY] [file:line] — [description]
263
+
264
+ 🟡 Suspicious (might be intentional):
265
+ 1. [file:line] — [what looks off and why]
266
+
267
+ ✅ Looks good:
268
+ - [aspect that's well-implemented]
269
+
270
+ Recommendation: [what to fix first / what to investigate deeper]
271
+ </report>
272
+ </think>
273
+ ```
274
+
119
275
  **Example:**
120
276
  ```xml
121
277
  <think>