create-win-project 1.3.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 (198) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +187 -0
  3. package/ci/expo.yml +28 -0
  4. package/ci/laravel.yml +49 -0
  5. package/ci/nextjs.yml +29 -0
  6. package/ci/springboot.yml +33 -0
  7. package/ci/vite.yml +36 -0
  8. package/compatibility/profiles.json +237 -0
  9. package/docs/ARCHITECTURE.md +118 -0
  10. package/docs/CONTENT_MODEL.md +36 -0
  11. package/docs/DEPENDENCY_MAINTENANCE.md +31 -0
  12. package/index.js +431 -0
  13. package/lib/application-shapes.js +95 -0
  14. package/lib/banner.js +45 -0
  15. package/lib/catalog.js +343 -0
  16. package/lib/compatibility.js +116 -0
  17. package/lib/constants.js +3 -0
  18. package/lib/doctor.js +45 -0
  19. package/lib/files.js +158 -0
  20. package/lib/generator.js +366 -0
  21. package/lib/interview.js +77 -0
  22. package/lib/laravel-scaffold.js +3 -0
  23. package/lib/playbooks.js +172 -0
  24. package/lib/project-location.js +18 -0
  25. package/lib/scaffold.js +467 -0
  26. package/lib/stacks/context.js +27 -0
  27. package/lib/stacks/contract.js +142 -0
  28. package/lib/stacks/index.js +8 -0
  29. package/lib/stacks/laravel/architecture.js +27 -0
  30. package/lib/stacks/laravel/auth/oidc.js +13 -0
  31. package/lib/stacks/laravel/auth/public.js +28 -0
  32. package/lib/stacks/laravel/auth/sanctum.js +14 -0
  33. package/lib/stacks/laravel/auth/session.js +17 -0
  34. package/lib/stacks/laravel/composer.js +50 -0
  35. package/lib/stacks/laravel/generate.js +362 -0
  36. package/lib/stacks/laravel/index.js +18 -0
  37. package/lib/stacks/laravel/ui/blade.js +15 -0
  38. package/lib/stacks/laravel/ui/index.js +29 -0
  39. package/lib/stacks/laravel/ui/inertia-react.js +35 -0
  40. package/lib/stacks/laravel/ui/livewire.js +39 -0
  41. package/lib/stacks/laravel/ui/shared.js +14 -0
  42. package/lib/stacks/registry.js +42 -0
  43. package/lib/stacks/shared/contributions.js +25 -0
  44. package/lib/template.js +62 -0
  45. package/package.json +49 -0
  46. package/playbooks/INDEX.md +58 -0
  47. package/playbooks/capabilities/auth/oidc-resource-server.md +15 -0
  48. package/playbooks/capabilities/auth/spring-session.md +15 -0
  49. package/playbooks/capabilities/ci/github-actions.manifest.json +10 -0
  50. package/playbooks/capabilities/ci/github-actions.md +61 -0
  51. package/playbooks/capabilities/docker/docker.manifest.json +11 -0
  52. package/playbooks/capabilities/docker/overview.md +55 -0
  53. package/playbooks/capabilities/flyway/environments.md +5 -0
  54. package/playbooks/capabilities/flyway/flyway.manifest.json +12 -0
  55. package/playbooks/capabilities/flyway/migrations.md +7 -0
  56. package/playbooks/capabilities/flyway/testing.md +5 -0
  57. package/playbooks/capabilities/laravel/database.md +8 -0
  58. package/playbooks/capabilities/laravel/migrations.md +15 -0
  59. package/playbooks/capabilities/laravel/observability.md +8 -0
  60. package/playbooks/capabilities/laravel/oidc-resource-server.md +10 -0
  61. package/playbooks/capabilities/laravel/queues.md +8 -0
  62. package/playbooks/capabilities/laravel/sanctum-spa.md +8 -0
  63. package/playbooks/capabilities/laravel/scheduler.md +7 -0
  64. package/playbooks/capabilities/laravel/session-auth.md +8 -0
  65. package/playbooks/capabilities/laravel/storage-uploads.md +7 -0
  66. package/playbooks/capabilities/postgresql/architecture.md +5 -0
  67. package/playbooks/capabilities/postgresql/migrations.md +5 -0
  68. package/playbooks/capabilities/postgresql/postgresql.manifest.json +15 -0
  69. package/playbooks/capabilities/postgresql/schema-design.md +7 -0
  70. package/playbooks/capabilities/postgresql/security.md +5 -0
  71. package/playbooks/capabilities/postgresql/testing.md +5 -0
  72. package/playbooks/capabilities/prisma/architecture.md +5 -0
  73. package/playbooks/capabilities/prisma/migrations.md +11 -0
  74. package/playbooks/capabilities/prisma/prisma.manifest.json +14 -0
  75. package/playbooks/capabilities/prisma/runtime.md +5 -0
  76. package/playbooks/capabilities/prisma/schema.md +5 -0
  77. package/playbooks/capabilities/prisma/testing.md +5 -0
  78. package/playbooks/capabilities/supabase/architecture.md +5 -0
  79. package/playbooks/capabilities/supabase/authentication.md +5 -0
  80. package/playbooks/capabilities/supabase/expo.md +16 -0
  81. package/playbooks/capabilities/supabase/migrations.md +7 -0
  82. package/playbooks/capabilities/supabase/nextjs.md +24 -0
  83. package/playbooks/capabilities/supabase/rls.md +7 -0
  84. package/playbooks/capabilities/supabase/supabase.manifest.json +32 -0
  85. package/playbooks/capabilities/supabase/testing.md +5 -0
  86. package/playbooks/capabilities/supabase/vite.md +14 -0
  87. package/playbooks/concerns/axios.md +123 -0
  88. package/playbooks/concerns/next-safe-action.md +66 -0
  89. package/playbooks/concerns/next-themes.md +87 -0
  90. package/playbooks/concerns/nuqs.md +59 -0
  91. package/playbooks/concerns/t3-env.md +48 -0
  92. package/playbooks/concerns/tanstack-query.md +145 -0
  93. package/playbooks/concerns/zod.md +174 -0
  94. package/playbooks/concerns/zustand.md +93 -0
  95. package/playbooks/devops/makefile.manifest.json +10 -0
  96. package/playbooks/devops/makefile.md +556 -0
  97. package/playbooks/devops/pr-template.manifest.json +10 -0
  98. package/playbooks/devops/pr-template.md +106 -0
  99. package/playbooks/platform/laravel-ui/blade/architecture.md +5 -0
  100. package/playbooks/platform/laravel-ui/blade/runtime.md +5 -0
  101. package/playbooks/platform/laravel-ui/blade/security.md +5 -0
  102. package/playbooks/platform/laravel-ui/blade/structure.md +5 -0
  103. package/playbooks/platform/laravel-ui/blade/testing.md +5 -0
  104. package/playbooks/platform/laravel-ui/inertia-react/architecture.md +5 -0
  105. package/playbooks/platform/laravel-ui/inertia-react/runtime.md +5 -0
  106. package/playbooks/platform/laravel-ui/inertia-react/security.md +5 -0
  107. package/playbooks/platform/laravel-ui/inertia-react/structure.md +5 -0
  108. package/playbooks/platform/laravel-ui/inertia-react/testing.md +5 -0
  109. package/playbooks/platform/laravel-ui/livewire/architecture.md +5 -0
  110. package/playbooks/platform/laravel-ui/livewire/runtime.md +5 -0
  111. package/playbooks/platform/laravel-ui/livewire/security.md +5 -0
  112. package/playbooks/platform/laravel-ui/livewire/structure.md +5 -0
  113. package/playbooks/platform/laravel-ui/livewire/testing.md +5 -0
  114. package/playbooks/platform/mobile.manifest.json +17 -0
  115. package/playbooks/platform/mobile.md +15 -0
  116. package/playbooks/platform/web.manifest.json +17 -0
  117. package/playbooks/platform/web.md +17 -0
  118. package/playbooks/stack/expo/architecture.md +27 -0
  119. package/playbooks/stack/expo/runtime.md +14 -0
  120. package/playbooks/stack/expo/security.md +18 -0
  121. package/playbooks/stack/expo/structure.md +26 -0
  122. package/playbooks/stack/expo/testing.md +13 -0
  123. package/playbooks/stack/laravel/architecture.md +34 -0
  124. package/playbooks/stack/laravel/runtime.md +27 -0
  125. package/playbooks/stack/laravel/security.md +23 -0
  126. package/playbooks/stack/laravel/structure.md +29 -0
  127. package/playbooks/stack/laravel/testing.md +22 -0
  128. package/playbooks/stack/laravel-ui.manifest.json +42 -0
  129. package/playbooks/stack/laravel.manifest.json +35 -0
  130. package/playbooks/stack/nextjs/architecture.md +62 -0
  131. package/playbooks/stack/nextjs/runtime.md +31 -0
  132. package/playbooks/stack/nextjs/security.md +32 -0
  133. package/playbooks/stack/nextjs/structure.md +50 -0
  134. package/playbooks/stack/nextjs/testing.md +20 -0
  135. package/playbooks/stack/nextjs.manifest.json +49 -0
  136. package/playbooks/stack/no-frontend.manifest.json +25 -0
  137. package/playbooks/stack/none.manifest.json +21 -0
  138. package/playbooks/stack/react-native.manifest.json +49 -0
  139. package/playbooks/stack/react-vite/architecture.md +33 -0
  140. package/playbooks/stack/react-vite/runtime.md +13 -0
  141. package/playbooks/stack/react-vite/security.md +16 -0
  142. package/playbooks/stack/react-vite/structure.md +23 -0
  143. package/playbooks/stack/react-vite/testing.md +12 -0
  144. package/playbooks/stack/react-vite.manifest.json +46 -0
  145. package/playbooks/stack/springboot/architecture.md +40 -0
  146. package/playbooks/stack/springboot/runtime.md +28 -0
  147. package/playbooks/stack/springboot/security.md +28 -0
  148. package/playbooks/stack/springboot/structure.md +30 -0
  149. package/playbooks/stack/springboot/testing.md +22 -0
  150. package/playbooks/stack/springboot.manifest.json +38 -0
  151. package/playbooks/styling/css-modules-extensions.md +267 -0
  152. package/playbooks/styling/css-modules.manifest.json +11 -0
  153. package/playbooks/styling/native-styles.manifest.json +9 -0
  154. package/playbooks/styling/native-styles.md +29 -0
  155. package/playbooks/styling/tailwind-extensions.md +182 -0
  156. package/playbooks/styling/tailwind.manifest.json +11 -0
  157. package/playbooks/universal/accessibility.manifest.json +11 -0
  158. package/playbooks/universal/accessibility.md +45 -0
  159. package/playbooks/universal/coding-rules.manifest.json +12 -0
  160. package/playbooks/universal/coding-rules.md +281 -0
  161. package/playbooks/universal/error-handling.manifest.json +10 -0
  162. package/playbooks/universal/error-handling.md +21 -0
  163. package/playbooks/universal/git-conventions.manifest.json +10 -0
  164. package/playbooks/universal/git-conventions.md +186 -0
  165. package/playbooks/universal/observability.manifest.json +10 -0
  166. package/playbooks/universal/observability.md +29 -0
  167. package/playbooks/universal/security.manifest.json +11 -0
  168. package/playbooks/universal/security.md +30 -0
  169. package/playbooks/universal/typescript.manifest.json +11 -0
  170. package/playbooks/universal/typescript.md +272 -0
  171. package/scripts/compatibility-matrix.mjs +48 -0
  172. package/scripts/validate-content.mjs +76 -0
  173. package/scripts/verify-generated.mjs +153 -0
  174. package/templates/agents/nextjs.md +47 -0
  175. package/templates/agents/react-native.md +48 -0
  176. package/templates/agents/react-vite.md +48 -0
  177. package/templates/docker/compose/postgres.yml +29 -0
  178. package/templates/docker/compose/springboot.yml +69 -0
  179. package/templates/docker/compose/supabase.yml +18 -0
  180. package/templates/docker/compose-prod/springboot.yml +64 -0
  181. package/templates/docker/dockerfile/laravel-inertia.dev.dockerfile +21 -0
  182. package/templates/docker/dockerfile/laravel.dev.dockerfile +12 -0
  183. package/templates/docker/dockerfile/laravel.prod.dockerfile +13 -0
  184. package/templates/docker/dockerfile/nextjs.dev.dockerfile +6 -0
  185. package/templates/docker/dockerfile/nextjs.prod.dockerfile +18 -0
  186. package/templates/docker/dockerfile/springboot.dev.dockerfile +5 -0
  187. package/templates/docker/dockerfile/springboot.prod.dockerfile +10 -0
  188. package/templates/docker/dockerfile/vite.dev.dockerfile +6 -0
  189. package/templates/docker/dockerfile/vite.prod.dockerfile +12 -0
  190. package/templates/gitignore/base.gitignore +32 -0
  191. package/templates/gitignore/nextjs.gitignore +40 -0
  192. package/templates/gitignore/react-native.gitignore +43 -0
  193. package/templates/gitignore/react-vite.gitignore +39 -0
  194. package/templates/makefile/frontend.mk +31 -0
  195. package/templates/makefile/laravel.mk +47 -0
  196. package/templates/makefile/postgres.mk +40 -0
  197. package/templates/makefile/springboot.mk +125 -0
  198. package/templates/makefile/supabase.mk +42 -0
@@ -0,0 +1,48 @@
1
+ # t3-env for Next.js
2
+
3
+ > Optional: adopt this when a Next.js project needs one typed, startup/build-time environment boundary. Install `@t3-oss/env-nextjs` and `zod` in the same change.
4
+
5
+ ## Setup
6
+
7
+ Declare only values the selected stack actually consumes:
8
+
9
+ ```ts
10
+ import { createEnv } from '@t3-oss/env-nextjs'
11
+ import { z } from 'zod'
12
+
13
+ export const env = createEnv({
14
+ server: {
15
+ DATABASE_URL: z.string().min(1).optional(),
16
+ },
17
+ client: {
18
+ NEXT_PUBLIC_API_URL: z.string().url().optional(),
19
+ NEXT_PUBLIC_SUPABASE_URL: z.string().url().optional(),
20
+ NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY: z.string().min(1).optional(),
21
+ },
22
+ runtimeEnv: {
23
+ DATABASE_URL: process.env.DATABASE_URL,
24
+ NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL,
25
+ NEXT_PUBLIC_SUPABASE_URL: process.env.NEXT_PUBLIC_SUPABASE_URL,
26
+ NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY: process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY,
27
+ },
28
+ })
29
+ ```
30
+
31
+ Make a value required only for a stack that cannot run without it. Client-prefixed values are bundled and are never secrets.
32
+
33
+ ## Rules
34
+
35
+ - Server-only values belong in `server`; client-visible values require `NEXT_PUBLIC_` and belong in `client`.
36
+ - Add each new value to the schema, runtime mapping, `.env.example`, deployment configuration, and generated environment documentation.
37
+ - Keep secrets out of logs, client schemas, public prefixes, and committed environment files.
38
+ - Do not copy speculative JWT, administrator, or provider secrets into every project.
39
+
40
+ ## Agent Quick Reference
41
+
42
+ ```text
43
+ New value? → identify its runtime owner and whether the browser needs it
44
+ Server secret? → server schema, no public prefix
45
+ Browser value? → client schema with NEXT_PUBLIC_; treat as public
46
+ Required value? → fail validation before serving traffic
47
+ Unused value? → remove it from schema, examples, CI, and deployment config
48
+ ```
@@ -0,0 +1,145 @@
1
+ # TanStack Query — Server State
2
+
3
+ > **When to use:** Any data fetched from a server or API that needs caching, background refetch,
4
+ > loading/error states, or invalidation after mutations.
5
+ > Required for React Native. Optional for web stacks.
6
+
7
+ ---
8
+
9
+ # 1. The Mental Model
10
+
11
+ ```text
12
+ TanStack Query owns: fetch, cache, loading, error, background refetch, invalidation
13
+ Your code owns: queryFn (calls a service), queryKey (cache identity)
14
+ ```
15
+
16
+ Never manage loading/error state manually when TanStack Query can do it.
17
+
18
+ ---
19
+
20
+ # 2. Query Key Convention
21
+
22
+ ```text
23
+ Resource list: ['products']
24
+ Resource by ID: ['products', id]
25
+ Resource with filters: ['products', { category, page }]
26
+ Nested resource: ['products', productId, 'reviews']
27
+ Non-resource (dashboard): ['dashboard', 'stats']
28
+ Auth session: ['auth', 'session']
29
+ ```
30
+
31
+ Keys are the cache identity. Same key = same cache entry. Be consistent.
32
+
33
+ ---
34
+
35
+ # 3. Read Hook
36
+
37
+ ```ts
38
+ // features/products/hooks/useProducts.ts
39
+ import { useQuery } from '@tanstack/react-query'
40
+ import { productService } from '../services/productService'
41
+
42
+ export const productKeys = {
43
+ all: ['products'] as const,
44
+ byId: (id: string) => ['products', id] as const,
45
+ filtered: (filters: ProductFilters) => ['products', 'filtered', filters] as const,
46
+ }
47
+
48
+ export function useProducts(filters?: ProductFilters) {
49
+ return useQuery({
50
+ queryKey: filters ? productKeys.filtered(filters) : productKeys.all,
51
+ queryFn: () => productService.getAll(filters),
52
+ staleTime: 1000 * 60 * 5, // 5 minutes
53
+ })
54
+ }
55
+
56
+ export function useProduct(id: string) {
57
+ return useQuery({
58
+ queryKey: productKeys.byId(id),
59
+ queryFn: () => productService.getById(id),
60
+ enabled: !!id,
61
+ })
62
+ }
63
+ ```
64
+
65
+ ---
66
+
67
+ # 4. Mutation Hook
68
+
69
+ ```ts
70
+ // features/products/hooks/useCreateProduct.ts
71
+ import { useMutation, useQueryClient } from '@tanstack/react-query'
72
+
73
+ export function useCreateProduct() {
74
+ const queryClient = useQueryClient()
75
+
76
+ return useMutation({
77
+ mutationFn: (input: CreateProductInput) => productService.create(input),
78
+ onSuccess: () => {
79
+ queryClient.invalidateQueries({ queryKey: productKeys.all })
80
+ },
81
+ })
82
+ }
83
+ ```
84
+
85
+ ---
86
+
87
+ # 5. QueryClient Setup
88
+
89
+ ```ts
90
+ // lib/queryClient.ts (web: src/lib/queryClient.ts)
91
+ // lib/queryClient.ts (RN: lib/queryClient.ts)
92
+ import { QueryClient } from '@tanstack/react-query'
93
+
94
+ export const queryClient = new QueryClient({
95
+ defaultOptions: {
96
+ queries: {
97
+ staleTime: 1000 * 60 * 5, // 5min — most data
98
+ retry: 1,
99
+ refetchOnWindowFocus: false, // web only; ignored on RN
100
+ },
101
+ },
102
+ })
103
+ ```
104
+
105
+ Stale time guide:
106
+ ```text
107
+ Real-time (notifications, live status) → staleTime: 0
108
+ Normal data (products, users, orders) → staleTime: 1000 * 60 * 5 (5min)
109
+ Slow-changing (profile, settings) → staleTime: 1000 * 60 * 30 (30min)
110
+ Static (categories, countries) → staleTime: Infinity
111
+ ```
112
+
113
+ ---
114
+
115
+ # 6. Rules
116
+
117
+ - Hooks call services. Services call the API client. Never call `fetch` or `axios` directly in `queryFn`.
118
+ - Always define query keys in a `[resource]Keys` object — never inline magic strings.
119
+ - Always invalidate related query keys after a mutation succeeds.
120
+ - `enabled: !!id` — guard queries that depend on a param that may not exist yet.
121
+ - Never put query results in Zustand — TanStack Query IS your cache.
122
+
123
+ ---
124
+
125
+ # 7. Platform Notes
126
+
127
+ > **React Native** — works identically. `refetchOnWindowFocus` is ignored (no window).
128
+ > Wrap the root component in `<QueryClientProvider client={queryClient}>` in your root layout.
129
+
130
+ > **Next.js** — Server Components do NOT use TanStack Query.
131
+ > TanStack Query is for Client Components that need cached/refetched server data.
132
+
133
+ ---
134
+
135
+ # 8. Agent Quick Reference
136
+
137
+ ```text
138
+ New data fetch? → useQuery hook → features/[name]/hooks/
139
+ New mutation? → useMutation hook → features/[name]/hooks/
140
+ → invalidateQueries on success
141
+ QueryKey for a list? → ['resource']
142
+ QueryKey for a single item? → ['resource', id]
143
+ Query depends on a param? → enabled: !!param
144
+ Server data in Zustand? → move to useQuery instead
145
+ ```
@@ -0,0 +1,174 @@
1
+ # Zod + React Hook Form — Validation
2
+
3
+ > **When to use:** Any project with forms or untrusted input (API responses, URL params, env vars).
4
+
5
+ ---
6
+
7
+ # 1. Schema First, Always
8
+
9
+ Write the Zod schema before writing the form component or service. The schema is the source of truth.
10
+
11
+ ```ts
12
+ // features/auth/schemas/login.schema.ts
13
+ import { z } from 'zod'
14
+
15
+ export const loginSchema = z.object({
16
+ email: z.string().email('Invalid email address'),
17
+ password: z.string().min(8, 'Password must be at least 8 characters'),
18
+ })
19
+
20
+ export type LoginInput = z.infer<typeof loginSchema>
21
+ ```
22
+
23
+ Never write a separate TypeScript type for form data — infer it from the schema.
24
+
25
+ ---
26
+
27
+ # 2. Schema Placement
28
+
29
+ ```text
30
+ Feature-specific form? → features/[name]/schemas/[name].schema.ts
31
+ Shared across multiple features? → src/schemas/[name].schema.ts (web)
32
+ schemas/[name].schema.ts (RN)
33
+ API response shape? → features/[name]/types.ts (type only, no runtime parse needed)
34
+ OR parse with z.safeParse() at the API boundary if strict
35
+ ```
36
+
37
+ ---
38
+
39
+ # 3. Web Form (React Hook Form)
40
+
41
+ ```tsx
42
+ // features/auth/components/LoginForm.tsx
43
+ 'use client' // Next.js only — omit for React Vite
44
+ import { useForm } from 'react-hook-form'
45
+ import { zodResolver } from '@hookform/resolvers/zod'
46
+ import { loginSchema, type LoginInput } from '../schemas/login.schema'
47
+
48
+ export function LoginForm({ onSubmit }: { onSubmit: (data: LoginInput) => void }) {
49
+ const {
50
+ register,
51
+ handleSubmit,
52
+ formState: { errors, isSubmitting },
53
+ } = useForm<LoginInput>({ resolver: zodResolver(loginSchema) })
54
+
55
+ return (
56
+ <form onSubmit={handleSubmit(onSubmit)}>
57
+ <input {...register('email')} placeholder="Email" />
58
+ {errors.email && <span>{errors.email.message}</span>}
59
+
60
+ <input type="password" {...register('password')} placeholder="Password" />
61
+ {errors.password && <span>{errors.password.message}</span>}
62
+
63
+ <button type="submit" disabled={isSubmitting}>
64
+ {isSubmitting ? 'Submitting...' : 'Login'}
65
+ </button>
66
+ </form>
67
+ )
68
+ }
69
+ ```
70
+
71
+ ---
72
+
73
+ # 4. React Native Form (React Hook Form + Controller)
74
+
75
+ React Native has no native `<input>` — use `Controller` to bridge RHF with RN's `TextInput`.
76
+
77
+ ```tsx
78
+ // features/auth/components/LoginForm.tsx (React Native)
79
+ import { Controller, useForm } from 'react-hook-form'
80
+ import { zodResolver } from '@hookform/resolvers/zod'
81
+ import { loginSchema, type LoginInput } from '../schemas/login.schema'
82
+
83
+ export function LoginForm({ onSubmit }: { onSubmit: (data: LoginInput) => void }) {
84
+ const {
85
+ control,
86
+ handleSubmit,
87
+ formState: { errors, isSubmitting },
88
+ } = useForm<LoginInput>({ resolver: zodResolver(loginSchema) })
89
+
90
+ return (
91
+ <View className="gap-4">
92
+ <Controller
93
+ control={control}
94
+ name="email"
95
+ render={({ field }) => (
96
+ <TextInput
97
+ value={field.value}
98
+ onChangeText={field.onChange}
99
+ placeholder="Email"
100
+ keyboardType="email-address"
101
+ autoCapitalize="none"
102
+ />
103
+ )}
104
+ />
105
+ {errors.email && <Text>{errors.email.message}</Text>}
106
+
107
+ <Controller
108
+ control={control}
109
+ name="password"
110
+ render={({ field }) => (
111
+ <TextInput
112
+ value={field.value}
113
+ onChangeText={field.onChange}
114
+ placeholder="Password"
115
+ secureTextEntry
116
+ />
117
+ )}
118
+ />
119
+ {errors.password && <Text>{errors.password.message}</Text>}
120
+
121
+ <PrimaryButton
122
+ title={isSubmitting ? 'Submitting...' : 'Login'}
123
+ onPress={handleSubmit(onSubmit)}
124
+ disabled={isSubmitting}
125
+ />
126
+ </View>
127
+ )
128
+ }
129
+ ```
130
+
131
+ ---
132
+
133
+ # 5. Validating API Responses
134
+
135
+ Parse untrusted data at the boundary, not deep in hooks or components.
136
+
137
+ ```ts
138
+ // features/products/services/productService.ts
139
+ import { z } from 'zod'
140
+
141
+ const productSchema = z.object({
142
+ id: z.string(),
143
+ name: z.string(),
144
+ price: z.number(),
145
+ })
146
+
147
+ export async function getProduct(id: string) {
148
+ const raw = await apiClient.get(`/products/${id}`)
149
+ return productSchema.parse(raw) // throws if shape is wrong
150
+ }
151
+ ```
152
+
153
+ ---
154
+
155
+ # 6. Rules
156
+
157
+ - Schema first — always write the schema before the form.
158
+ - Never write a separate TypeScript type for form input — use `z.infer<typeof schema>`.
159
+ - Validate on the server too (or in the service) — never trust client-only validation.
160
+ - Use `z.string().min(1)` not `z.string().nonempty()` — better cross-platform compatibility.
161
+ - Route on `error.code` after submission, never on `error.message`.
162
+
163
+ ---
164
+
165
+ # 7. Agent Quick Reference
166
+
167
+ ```text
168
+ New form? → schema first in features/[name]/schemas/
169
+ → useForm + zodResolver
170
+ → web: register(); RN: Controller + TextInput
171
+ New type for form data? → z.infer<typeof schema> instead
172
+ Validate API response? → z.safeParse() or schema.parse() in service
173
+ Shared schema? → src/schemas/ or schemas/ (not in features/)
174
+ ```
@@ -0,0 +1,93 @@
1
+ # Zustand — Client State Management
2
+
3
+ > **When to use:** Shared UI state that persists across components or screens but is NOT server data.
4
+ > If the project has no such need, this concern does not apply.
5
+
6
+ ---
7
+
8
+ # 1. The One Rule
9
+
10
+ ```text
11
+ Zustand → sidebar, active tab, auth user, wizard steps, user preferences, modal state
12
+ TanStack Query → anything fetched from a server or API (users, posts, orders, products)
13
+ ```
14
+
15
+ Never put server data in Zustand. Never put UI state in TanStack Query.
16
+
17
+ ---
18
+
19
+ # 2. Store Setup
20
+
21
+ ```ts
22
+ // src/stores/uiStore.ts (web)
23
+ // stores/uiStore.ts (React Native)
24
+ import { create } from 'zustand'
25
+
26
+ type UIState = {
27
+ sidebarOpen: boolean
28
+ toggleSidebar: () => void
29
+ }
30
+
31
+ export const useUIStore = create<UIState>((set) => ({
32
+ sidebarOpen: false,
33
+ toggleSidebar: () => set((state) => ({ sidebarOpen: !state.sidebarOpen })),
34
+ }))
35
+ ```
36
+
37
+ ---
38
+
39
+ # 3. Auth Store Pattern
40
+
41
+ ```ts
42
+ // stores/authStore.ts
43
+ import { create } from 'zustand'
44
+
45
+ type AuthState = {
46
+ user: User | null
47
+ setUser: (user: User | null) => void
48
+ logout: () => void
49
+ }
50
+
51
+ export const useAuthStore = create<AuthState>((set) => ({
52
+ user: null,
53
+ setUser: (user) => set({ user }),
54
+ logout: () => set({ user: null }),
55
+ }))
56
+ ```
57
+
58
+ ---
59
+
60
+ # 4. Rules
61
+
62
+ - One store per domain concern: `authStore`, `cartStore`, `settingsStore` — not one giant global store.
63
+ - Do NOT store server data (fetched from API) in Zustand — use TanStack Query.
64
+ - Do NOT store local component-only state in Zustand — use `useState`.
65
+ - Keep stores narrow. If a store grows beyond 5–6 fields, split it.
66
+
67
+ ---
68
+
69
+ # 5. What Belongs Where
70
+
71
+ ```text
72
+ useState / useReducer → one component, resets on unmount
73
+ Zustand → multiple components/screens, survives navigation
74
+ TanStack Query → anything from a server, needs caching or refetch
75
+ ```
76
+
77
+ ---
78
+
79
+ # 6. Platform Notes
80
+
81
+ > **React Native** — works identically. Import from `zustand` the same way.
82
+ > Zustand has no dependency on the DOM or browser APIs.
83
+
84
+ ---
85
+
86
+ # 7. Agent Quick Reference
87
+
88
+ ```text
89
+ New shared UI state? → stores/[name]Store.ts
90
+ State only one component needs → useState instead
91
+ State fetched from server? → TanStack Query instead
92
+ Store getting large? → split into focused stores
93
+ ```
@@ -0,0 +1,10 @@
1
+ {
2
+ "id": "makefile",
3
+ "kind": "devops",
4
+ "label": "Makefile",
5
+ "required": false,
6
+ "folders": [],
7
+ "concerns": [
8
+ { "id": "makefile", "required": true, "sections": ["Core Rules"] }
9
+ ]
10
+ }