ginskill-init 2.7.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.
Files changed (128) hide show
  1. package/.wrangler/cache/pages.json +4 -0
  2. package/.wrangler/cache/wrangler-account.json +6 -0
  3. package/DEVELOPMENT.md +510 -0
  4. package/README.md +104 -0
  5. package/agents/developer.md +56 -0
  6. package/agents/frontend-design.md +69 -0
  7. package/agents/mobile-reviewer.md +36 -0
  8. package/agents/review-code.md +49 -0
  9. package/agents/security-scanner.md +50 -0
  10. package/agents/tester.md +72 -0
  11. package/bin/cli.js +461 -0
  12. package/landing/ai-build-ai.png +0 -0
  13. package/landing/index.html +1495 -0
  14. package/landing/logo.png +0 -0
  15. package/package.json +37 -0
  16. package/skills/active-life-dev/SKILL.md +157 -0
  17. package/skills/active-life-dev/docs/auth.md +187 -0
  18. package/skills/active-life-dev/docs/customers.md +216 -0
  19. package/skills/active-life-dev/docs/integrations.md +209 -0
  20. package/skills/active-life-dev/docs/inventory.md +192 -0
  21. package/skills/active-life-dev/docs/modules.md +181 -0
  22. package/skills/active-life-dev/docs/orders.md +180 -0
  23. package/skills/active-life-dev/docs/patterns.md +319 -0
  24. package/skills/active-life-dev/docs/products.md +216 -0
  25. package/skills/active-life-dev/docs/schema.md +502 -0
  26. package/skills/active-life-dev/docs/setup.md +169 -0
  27. package/skills/active-life-dev/docs/vouchers.md +144 -0
  28. package/skills/ai-asset-generator/SKILL.md +247 -0
  29. package/skills/ai-asset-generator/docs/gen-image.md +274 -0
  30. package/skills/ai-asset-generator/docs/genvideo.md +341 -0
  31. package/skills/ai-asset-generator/docs/remove-background.md +19 -0
  32. package/skills/ai-asset-generator/lib/bg-remove.mjs +34 -0
  33. package/skills/ai-asset-generator/lib/env.mjs +48 -0
  34. package/skills/ai-asset-generator/lib/kie-client.mjs +100 -0
  35. package/skills/ai-build-ai/SKILL.md +127 -0
  36. package/skills/ai-build-ai/docs/agent-teams.md +293 -0
  37. package/skills/ai-build-ai/docs/checkpointing.md +161 -0
  38. package/skills/ai-build-ai/docs/create-agent.md +399 -0
  39. package/skills/ai-build-ai/docs/create-mcp.md +395 -0
  40. package/skills/ai-build-ai/docs/create-skill.md +299 -0
  41. package/skills/ai-build-ai/docs/headless-mode.md +614 -0
  42. package/skills/ai-build-ai/docs/hooks.md +578 -0
  43. package/skills/ai-build-ai/docs/memory-claude-md.md +375 -0
  44. package/skills/ai-build-ai/docs/output-styles.md +208 -0
  45. package/skills/ai-build-ai/docs/overview.md +162 -0
  46. package/skills/ai-build-ai/docs/permissions.md +391 -0
  47. package/skills/ai-build-ai/docs/plugins.md +396 -0
  48. package/skills/ai-build-ai/docs/sandbox.md +262 -0
  49. package/skills/ai-build-ai/docs/team-lead-workflow.md +648 -0
  50. package/skills/ant-design/SKILL.md +323 -0
  51. package/skills/ant-design/docs/components.md +160 -0
  52. package/skills/ant-design/docs/data-entry.md +406 -0
  53. package/skills/ant-design/docs/display.md +594 -0
  54. package/skills/ant-design/docs/feedback.md +451 -0
  55. package/skills/ant-design/docs/key-components.md +414 -0
  56. package/skills/ant-design/docs/navigation.md +310 -0
  57. package/skills/ant-design/docs/pro-components.md +543 -0
  58. package/skills/ant-design/docs/setup.md +213 -0
  59. package/skills/ant-design/docs/theme.md +265 -0
  60. package/skills/flutter-performance/SKILL.md +803 -0
  61. package/skills/flutter-performance/references/flutter-patterns.md +595 -0
  62. package/skills/icon-generator/SKILL.md +270 -0
  63. package/skills/mobile-app-review/SKILL.md +321 -0
  64. package/skills/mobile-app-review/references/apple-review.md +132 -0
  65. package/skills/mobile-app-review/references/google-play-review.md +203 -0
  66. package/skills/mongodb/SKILL.md +667 -0
  67. package/skills/mongodb/references/mongoose-patterns.md +368 -0
  68. package/skills/nestjs-architecture/SKILL.md +1086 -0
  69. package/skills/nestjs-architecture/references/advanced-patterns.md +590 -0
  70. package/skills/performance/SKILL.md +509 -0
  71. package/skills/react-fsd-architecture/SKILL.md +693 -0
  72. package/skills/react-fsd-architecture/references/fsd-patterns.md +747 -0
  73. package/skills/react-native-expo/SKILL.md +128 -0
  74. package/skills/react-native-expo/references/data-layer.md +252 -0
  75. package/skills/react-native-expo/references/design-system.md +252 -0
  76. package/skills/react-native-expo/references/navigation.md +199 -0
  77. package/skills/react-native-expo/references/performance.md +229 -0
  78. package/skills/react-native-expo/references/platform-services.md +179 -0
  79. package/skills/react-native-expo/references/state-management.md +209 -0
  80. package/skills/react-native-expo/references/ui-patterns.md +301 -0
  81. package/skills/react-query/SKILL.md +685 -0
  82. package/skills/react-query/references/query-patterns.md +365 -0
  83. package/skills/review-code/SKILL.md +374 -0
  84. package/skills/review-code/references/clean-code-principles.md +395 -0
  85. package/skills/review-code/references/frontend-patterns.md +136 -0
  86. package/skills/review-code/references/nestjs-patterns.md +184 -0
  87. package/skills/security-scanner/SKILL.md +366 -0
  88. package/skills/security-scanner/references/nestjs-security.md +260 -0
  89. package/skills/security-scanner/references/nextjs-security.md +201 -0
  90. package/skills/security-scanner/references/react-native-security.md +199 -0
  91. package/skills/traefik/SKILL.md +105 -0
  92. package/skills/traefik/docs/advanced-routing.md +186 -0
  93. package/skills/traefik/docs/auth-providers.md +137 -0
  94. package/skills/traefik/docs/cicd-devops.md +396 -0
  95. package/skills/traefik/docs/core-config.md +171 -0
  96. package/skills/traefik/docs/distributed-config.md +96 -0
  97. package/skills/traefik/docs/docker-compose.md +182 -0
  98. package/skills/traefik/docs/ha-performance.md +177 -0
  99. package/skills/traefik/docs/kubernetes.md +278 -0
  100. package/skills/traefik/docs/middleware.md +205 -0
  101. package/skills/traefik/docs/monitoring.md +357 -0
  102. package/skills/traefik/docs/security.md +391 -0
  103. package/skills/traefik/docs/tls-acme.md +155 -0
  104. package/skills/ui-ux-pro-max/SKILL.md +377 -0
  105. package/skills/ui-ux-pro-max/data/charts.csv +26 -0
  106. package/skills/ui-ux-pro-max/data/colors.csv +97 -0
  107. package/skills/ui-ux-pro-max/data/icons.csv +101 -0
  108. package/skills/ui-ux-pro-max/data/landing.csv +31 -0
  109. package/skills/ui-ux-pro-max/data/products.csv +97 -0
  110. package/skills/ui-ux-pro-max/data/react-performance.csv +45 -0
  111. package/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -0
  112. package/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
  113. package/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
  114. package/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
  115. package/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
  116. package/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
  117. package/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
  118. package/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
  119. package/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
  120. package/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
  121. package/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
  122. package/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
  123. package/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
  124. package/skills/ui-ux-pro-max/data/styles.csv +68 -0
  125. package/skills/ui-ux-pro-max/data/typography.csv +58 -0
  126. package/skills/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
  127. package/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
  128. package/skills/ui-ux-pro-max/data/web-interface.csv +31 -0
@@ -0,0 +1,209 @@
1
+ # State Management — Zustand v5 + MMKV
2
+
3
+ ## Architecture Overview
4
+
5
+ - **Server state**: React Query (never duplicate in Zustand)
6
+ - **Client state**: Zustand with MMKV or AsyncStorage persistence
7
+ - **Secure storage**: react-native-keychain (JWT tokens only)
8
+ - **Fast sync storage**: MMKV (non-sensitive persisted state)
9
+
10
+ ## Zustand Store Patterns
11
+
12
+ ### Simple Store
13
+
14
+ ```typescript
15
+ import { create } from "zustand"
16
+ import { persist, createJSONStorage } from "zustand/middleware"
17
+ import { MMKV } from "react-native-mmkv"
18
+
19
+ const storage = new MMKV()
20
+ const mmkvStorage = {
21
+ getItem: (name: string) => storage.getString(name) ?? null,
22
+ setItem: (name: string, value: string) => storage.set(name, value),
23
+ removeItem: (name: string) => storage.delete(name),
24
+ }
25
+
26
+ interface UserStore {
27
+ user: UserRes | null
28
+ setUser: (user: UserRes | null) => void
29
+ updateUser: (partial: Partial<UserRes>) => void
30
+ clearUser: () => void
31
+ isPremium: () => boolean
32
+ }
33
+
34
+ export const useUserStore = create<UserStore>()(
35
+ persist(
36
+ (set, get) => ({
37
+ user: null,
38
+ setUser: (user) => set({ user }),
39
+ updateUser: (partial) => set({ user: { ...get().user!, ...partial } }),
40
+ clearUser: () => set({ user: null }),
41
+ isPremium: () => get().user?.subscription === "premium",
42
+ }),
43
+ {
44
+ name: "user-store",
45
+ storage: createJSONStorage(() => mmkvStorage),
46
+ partialize: (state) => ({ user: state.user }),
47
+ },
48
+ ),
49
+ )
50
+ ```
51
+
52
+ ### Composite Store with Slice Factories
53
+
54
+ The app uses a composite store pattern:
55
+
56
+ ```typescript
57
+ import { StateCreator } from "zustand"
58
+
59
+ // Slice factory
60
+ interface StySlice {
61
+ styBalance: StyBalance | null
62
+ isCheckInSheetVisible: boolean
63
+ setStyBalance: (balance: StyBalance | null) => void
64
+ syncStyBalance: () => Promise<void>
65
+ hasSty: () => boolean
66
+ }
67
+
68
+ export const createStySlice: StateCreator<AppStore, [], [], StySlice> = (set, get) => ({
69
+ styBalance: null,
70
+ isCheckInSheetVisible: false,
71
+ setStyBalance: (balance) => set({ styBalance: balance }),
72
+ syncStyBalance: async () => {
73
+ try {
74
+ const balance = await getStyBalance()
75
+ set({ styBalance: balance })
76
+ } catch (error) {
77
+ appLog.error("[Sty] Failed to sync balance:", error)
78
+ }
79
+ },
80
+ hasSty: () => (get().styBalance?.total ?? 0) > 0,
81
+ })
82
+
83
+ // Composite store (app-store.ts)
84
+ type AppStore = AppState & ChatSlice & StySlice & AdaptySlice
85
+
86
+ export const useAppStore = create<AppStore>()(
87
+ persist(
88
+ (set, get) => ({
89
+ ...createChatSlice(set, get),
90
+ ...createStySlice(set, get),
91
+ ...createAdaptySlice(set, get),
92
+ isAuthenticated: false,
93
+ isAppInitialized: false,
94
+ isHydrated: false,
95
+ setAuthenticated: (v) => set({ isAuthenticated: v }),
96
+ logout: () => { clearTokens(); set({ isAuthenticated: false }) },
97
+ initializeApp: () => set({ isAppInitialized: true }),
98
+ }),
99
+ {
100
+ name: "app-store",
101
+ storage: createJSONStorage(() => AsyncStorage),
102
+ partialize: (state) => ({ isAuthenticated: state.isAuthenticated }),
103
+ onRehydrateStorage: () => () => {
104
+ useAppStore.setState({ isHydrated: true })
105
+ },
106
+ },
107
+ ),
108
+ )
109
+ ```
110
+
111
+ ### Using Stores in Components
112
+
113
+ **ALWAYS use `useShallow`** when selecting multiple fields:
114
+
115
+ ```typescript
116
+ import { useShallow } from "zustand/react/shallow"
117
+
118
+ // CORRECT — useShallow prevents unnecessary re-renders
119
+ const { user, setUser } = useUserStore(
120
+ useShallow((state) => ({ user: state.user, setUser: state.setUser })),
121
+ )
122
+
123
+ // CORRECT — single primitive selector (no useShallow needed)
124
+ const styBalance = useUserStore((state) => state.styBalance)
125
+
126
+ // WRONG — creates new object reference every render
127
+ const { user, setUser } = useUserStore((state) => ({
128
+ user: state.user,
129
+ setUser: state.setUser,
130
+ }))
131
+ ```
132
+
133
+ ### Feature Dialog Stores
134
+
135
+ Features use dedicated stores for dialog state:
136
+
137
+ ```typescript
138
+ interface ItemDialogsStore {
139
+ activeDialog: ItemDialogTypeEnum | null
140
+ selectedItemId: string | null
141
+ openDialog: (type: ItemDialogTypeEnum, itemId: string) => void
142
+ closeDialog: () => void
143
+ }
144
+
145
+ export const useItemDialogsStore = create<ItemDialogsStore>((set) => ({
146
+ activeDialog: null,
147
+ selectedItemId: null,
148
+ openDialog: (type, itemId) => set({ activeDialog: type, selectedItemId: itemId }),
149
+ closeDialog: () => set({ activeDialog: null, selectedItemId: null }),
150
+ }))
151
+ ```
152
+
153
+ ### Hydration Check
154
+
155
+ ```typescript
156
+ // Wait for Zustand hydration before using persisted state
157
+ const isHydrated = useAppStore((s) => s.isHydrated)
158
+
159
+ // Or use persist API
160
+ const isHydrated = useAppStore.persist.hasHydrated()
161
+ useEffect(() => {
162
+ const unsub = useAppStore.persist.onFinishHydration(() => { /* ready */ })
163
+ return unsub
164
+ }, [])
165
+ ```
166
+
167
+ ## Existing Stores
168
+
169
+ | Store | File | Purpose |
170
+ |-------|------|---------|
171
+ | `useAppStore` | `shared/stores/app-store.ts` | Composite: auth + chat + sty + adapty slices |
172
+ | `useUserStore` | `shared/stores/user-store.ts` | User profile, subscription, access level |
173
+ | `useFeedbackStore` | `shared/stores/feedback-store.ts` | Feedback dialog state |
174
+ | `useTryOnStore` | `shared/stores/try-on-store.ts` | Try-on feature state |
175
+ | `useNewItemsStore` | `shared/stores/new-items-store.ts` | New items tracking |
176
+ | `useFullBodyImageStore` | `shared/stores/full-body-image-store.ts` | Full body photo state |
177
+ | `useVersionCheckStore` | `shared/stores/version-check-store.ts` | App version check |
178
+ | `useStylistNameStore` | `shared/stores/stylist-name-store.ts` | AI stylist name |
179
+ | `useDuplicateCheckStore` | `shared/stores/duplicate-check-store.ts` | Duplicate detection |
180
+ | `useUnreadStylistStore` | `shared/stores/unread-stylist-store.ts` | Unread chat badge |
181
+
182
+ ## MMKV Direct Usage
183
+
184
+ For simple key-value outside Zustand:
185
+
186
+ ```typescript
187
+ import { MMKV } from "react-native-mmkv"
188
+ const storage = new MMKV()
189
+
190
+ storage.set("user.language", "en") // String
191
+ storage.set("onboarding.complete", true) // Boolean
192
+ storage.set("app.launchCount", 5) // Number
193
+ const lang = storage.getString("user.language")
194
+ storage.delete("key")
195
+ storage.contains("key")
196
+ ```
197
+
198
+ ## Best Practices
199
+
200
+ - **Server state in React Query**, not Zustand — never cache API responses in stores
201
+ - **Use `partialize`** to exclude runtime-only state from persistence
202
+ - **Use `useShallow`** when selecting multiple fields
203
+ - **Single selector for primitives** — no `useShallow` needed
204
+ - **Keep stores small** — split by domain
205
+ - **MMKV for speed** — synchronous, 30-100x faster than AsyncStorage
206
+ - **Keychain for secrets** — JWT tokens only in `react-native-keychain`
207
+ - **Batch state updates** in single `set()` call
208
+ - **Use `getState()`** for reading in async functions (no hook needed)
209
+ - **Atomic updates**: Read + write in single `set()` or use `get()` inside setter
@@ -0,0 +1,301 @@
1
+ # UI Patterns — Forms, Bottom Sheets, Components
2
+
3
+ ## Forms — react-hook-form + zod
4
+
5
+ ### Schema + Hook
6
+
7
+ ```typescript
8
+ import { z } from "zod"
9
+ import { useForm, Controller } from "react-hook-form"
10
+ import { zodResolver } from "@hookform/resolvers/zod"
11
+
12
+ const createItemSchema = z.object({
13
+ name: z.string().min(1, "Name is required").max(100),
14
+ category: z.string().min(1, "Category is required"),
15
+ brand: z.string().optional(),
16
+ notes: z.string().max(500).optional(),
17
+ })
18
+
19
+ type CreateItemForm = z.infer<typeof createItemSchema>
20
+
21
+ const useItemForm = (defaults?: Partial<CreateItemForm>) =>
22
+ useForm<CreateItemForm>({
23
+ resolver: zodResolver(createItemSchema),
24
+ defaultValues: { name: "", category: "", brand: "", ...defaults },
25
+ })
26
+ ```
27
+
28
+ ### Form Component
29
+
30
+ ```typescript
31
+ const ItemForm = () => {
32
+ const { control, handleSubmit, formState: { errors } } = useItemForm()
33
+ const { mutate: createItem, isPending } = useCreateItem()
34
+
35
+ return (
36
+ <View>
37
+ <Controller
38
+ control={control}
39
+ name="name"
40
+ render={({ field: { onChange, onBlur, value } }) => (
41
+ <TextInput
42
+ placeholder="Item name"
43
+ onChangeText={onChange} // onChangeText, NOT onChange
44
+ onBlur={onBlur}
45
+ value={value}
46
+ />
47
+ )}
48
+ />
49
+ {errors.name && (
50
+ <Typography variant="b3" color={Theme.color.error}>
51
+ {errors.name.message}
52
+ </Typography>
53
+ )}
54
+ <Button title="Save" onPress={handleSubmit((d) => createItem(d))} loading={isPending} />
55
+ </View>
56
+ )
57
+ }
58
+ ```
59
+
60
+ ### React Native Specifics
61
+
62
+ - Use `onChangeText` not `onChange` for TextInput
63
+ - Use `Controller` wrapper (RN doesn't support `ref` registration)
64
+ - Stick with **zod v3** syntax — v4 has RN compatibility issues
65
+ - Memoize `onSubmit` with `useCallback` for list forms
66
+
67
+ ## Bottom Sheet — @gorhom/bottom-sheet v5
68
+
69
+ ### Basic Usage
70
+
71
+ ```typescript
72
+ import BottomSheet, { BottomSheetView, BottomSheetBackdrop } from "@gorhom/bottom-sheet"
73
+
74
+ const MySheet = () => {
75
+ const ref = useRef<BottomSheet>(null)
76
+ const snapPoints = useMemo(() => ["25%", "50%", "90%"], [])
77
+
78
+ return (
79
+ <BottomSheet
80
+ ref={ref}
81
+ snapPoints={snapPoints}
82
+ enableDynamicSizing={false} // Disable when using fixed snap points
83
+ enablePanDownToClose
84
+ index={-1} // Start closed
85
+ backdropComponent={BottomSheetBackdrop}
86
+ >
87
+ <BottomSheetView>{/* Content */}</BottomSheetView>
88
+ </BottomSheet>
89
+ )
90
+ }
91
+ ```
92
+
93
+ ### With Scrollable Content
94
+
95
+ ```typescript
96
+ import { BottomSheetScrollView, BottomSheetFlashList } from "@gorhom/bottom-sheet"
97
+
98
+ // ScrollView
99
+ <BottomSheet snapPoints={["50%", "90%"]}>
100
+ <BottomSheetScrollView>{/* content */}</BottomSheetScrollView>
101
+ </BottomSheet>
102
+
103
+ // FlashList
104
+ <BottomSheet snapPoints={["50%", "90%"]}>
105
+ <BottomSheetFlashList data={items} renderItem={renderItem} estimatedItemSize={60} />
106
+ </BottomSheet>
107
+ ```
108
+
109
+ ### Dynamic Sizing (v5 default)
110
+
111
+ v5 enables dynamic sizing by default. Disable for fixed snap points or limit height:
112
+ ```typescript
113
+ <BottomSheet enableDynamicSizing={false} snapPoints={["50%"]} />
114
+ <BottomSheet enableDynamicSizing maxDynamicContentSize={500} />
115
+ ```
116
+
117
+ ### ⚠️ CRITICAL: Dynamic Sizing + Scrollable Content
118
+
119
+ **NEVER** use `enableDynamicSizing={true}` with `BottomSheetScrollView` or `BottomSheetFlashList`.
120
+ Dynamic sizing measures children's intrinsic height via `BottomSheetView`, but scrollable content
121
+ has no fixed intrinsic height → sheet appears truncated (header-only).
122
+
123
+ ```typescript
124
+ // ❌ WRONG — sheet will appear truncated, only showing header
125
+ <BottomSheetWrapper ref={ref} name="my-sheet">
126
+ <BottomSheetScrollView>{/* list content */}</BottomSheetScrollView>
127
+ </BottomSheetWrapper>
128
+
129
+ // ✅ CORRECT — disable dynamic sizing, use explicit snap points
130
+ const snapPoints = useMemo(() => ["50%"], [])
131
+
132
+ <BottomSheetWrapper ref={ref} name="my-sheet" enableDynamicSizing={false} snapPoints={snapPoints}>
133
+ <BottomSheetScrollView>{/* list content */}</BottomSheetScrollView>
134
+ </BottomSheetWrapper>
135
+ ```
136
+
137
+ The app's `BottomSheetWrapper` defaults to `enableDynamicSizing={true}`. Always override when content is scrollable.
138
+
139
+ ### BottomSheetWrapper (App Component)
140
+
141
+ Located at `@/shared/components/bottom-sheet`. Wraps `@gorhom/bottom-sheet` BottomSheetModal with:
142
+ - Auto safe-area bottom padding
143
+ - Header component (title + close button + drag handle)
144
+ - Backdrop with 0.5 opacity
145
+ - Patched `present()` that always cleans up first (no stuck states)
146
+
147
+ ```typescript
148
+ import { BottomSheetModal } from "@gorhom/bottom-sheet"
149
+ import { BottomSheetWrapper } from "@/shared/components/bottom-sheet"
150
+
151
+ const ref = useRef<BottomSheetModal>(null)
152
+ const snapPoints = useMemo(() => ["50%"], [])
153
+
154
+ <BottomSheetWrapper
155
+ ref={ref}
156
+ name="my-sheet"
157
+ enableDynamicSizing={false}
158
+ snapPoints={snapPoints}
159
+ header={{
160
+ title: "Sheet Title",
161
+ showCloseButton: true,
162
+ closeButtonText: "Cancel",
163
+ }}
164
+ onDismiss={() => setSheetOpen(false)}
165
+ >
166
+ {/* Content */}
167
+ </BottomSheetWrapper>
168
+
169
+ // Open: ref.current?.present()
170
+ // Close: ref.current?.dismiss()
171
+ ```
172
+
173
+ ## Shared Components
174
+
175
+ ### Typography
176
+
177
+ ```typescript
178
+ import { Typography } from "@/shared/components"
179
+
180
+ <Typography variant="h1">Title</Typography>
181
+ <Typography variant="b2" color={Theme.color.textSecondary}>Body</Typography>
182
+ <Typography variant="b3" numberOfLines={2}>Truncated</Typography>
183
+ ```
184
+
185
+ ### Button
186
+
187
+ ```typescript
188
+ import { Button } from "@/shared/components"
189
+
190
+ // Variants: primary (brand+shadow), secondary (neutral), outline, ghost, danger
191
+ // Sizes: small (36px), medium (44px), large (52px)
192
+ <Button title="Save" variant="primary" onPress={handlePress} loading={isPending} />
193
+ <Button title="Cancel" variant="secondary" onPress={handleCancel} />
194
+ <Button title="Delete" variant="danger" onPress={handleDelete} />
195
+ ```
196
+
197
+ ### Image (expo-image)
198
+
199
+ ```typescript
200
+ import { Image } from "expo-image"
201
+
202
+ <Image
203
+ source={{ uri: imageUrl }}
204
+ style={{ width: 120, height: 120, borderRadius: Spacing.sm }}
205
+ contentFit="cover"
206
+ transition={200}
207
+ cachePolicy="memory-disk"
208
+ recyclingKey={item._id} // Important in lists
209
+ />
210
+ ```
211
+
212
+ ### Loading Skeletons
213
+
214
+ ```typescript
215
+ import { Shimmer } from "@/shared/components"
216
+
217
+ {isLoading ? (
218
+ <Shimmer width={200} height={20} borderRadius={4} />
219
+ ) : (
220
+ <Typography variant="b1">{data.name}</Typography>
221
+ )}
222
+ ```
223
+
224
+ Platform-specific: `shimmer.ios.tsx` / `shimmer.android.tsx`
225
+
226
+ ### Toast Messages
227
+
228
+ ```typescript
229
+ import { showSuccessToast, showErrorToast } from "@/shared/components/toast"
230
+
231
+ showSuccessToast("Success", "Item saved successfully")
232
+ showErrorToast("Error", "Failed to save item")
233
+ ```
234
+
235
+ ### Dialogs
236
+
237
+ ```typescript
238
+ // Pattern: Zustand store controls dialog visibility
239
+ const { isOpen, open, close } = useDialogStore(
240
+ useShallow((s) => ({ isOpen: s.isOpen, open: s.open, close: s.close }))
241
+ )
242
+
243
+ <Dialog visible={isOpen} onDismiss={close}>
244
+ <Dialog.Title>Confirm</Dialog.Title>
245
+ <Dialog.Content><Typography variant="b1">Are you sure?</Typography></Dialog.Content>
246
+ <Dialog.Actions>
247
+ <Button title="Cancel" variant="secondary" onPress={close} />
248
+ <Button title="Delete" variant="danger" onPress={handleDelete} />
249
+ </Dialog.Actions>
250
+ </Dialog>
251
+ ```
252
+
253
+ ### Glass Morphism Components
254
+
255
+ Available in `@/shared/components/`: `GlassCard`, `GlassButton`, `GlassInput`
256
+
257
+ ## Keyboard Handling
258
+
259
+ ```typescript
260
+ import { KeyboardAwareScrollView } from "react-native-keyboard-controller"
261
+
262
+ <KeyboardAwareScrollView>{/* Form content */}</KeyboardAwareScrollView>
263
+ ```
264
+
265
+ ## Image Picking & Compression
266
+
267
+ ```typescript
268
+ import * as ImagePicker from "expo-image-picker"
269
+ import { compressImage } from "@/shared/utils/compress-image"
270
+
271
+ const result = await ImagePicker.launchImageLibraryAsync({
272
+ mediaTypes: ImagePicker.MediaTypeOptions.Images,
273
+ quality: 0.8,
274
+ allowsEditing: true,
275
+ aspect: [1, 1],
276
+ })
277
+ if (!result.canceled) {
278
+ const compressed = await compressImage(result.assets[0].uri)
279
+ }
280
+ ```
281
+
282
+ ## Calendar
283
+
284
+ ```typescript
285
+ import { Calendar } from "@marceloterreiro/flash-calendar"
286
+
287
+ <Calendar
288
+ calendarActiveDateRanges={activeDateRanges}
289
+ onCalendarDayPress={handleDayPress}
290
+ />
291
+ ```
292
+
293
+ ## Popover
294
+
295
+ ```typescript
296
+ import Popover from "react-native-popover-view"
297
+
298
+ <Popover from={<TouchableOpacity><Icon name="more" /></TouchableOpacity>}>
299
+ {/* Menu content */}
300
+ </Popover>
301
+ ```