create-react-starter-kit 1.0.6 → 1.0.7

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 (76) hide show
  1. package/dist/cli-args.d.ts.map +1 -1
  2. package/dist/cli-args.js +2 -1
  3. package/dist/cli-args.js.map +1 -1
  4. package/dist/generator/index.d.ts.map +1 -1
  5. package/dist/generator/index.js +1 -0
  6. package/dist/generator/index.js.map +1 -1
  7. package/dist/prompts/index.d.ts.map +1 -1
  8. package/dist/prompts/index.js +8 -0
  9. package/dist/prompts/index.js.map +1 -1
  10. package/dist/templates/README.md.ejs +1 -1
  11. package/dist/templates/__when_apiLayer_axios__/src/services/api.ts.ejs +23 -0
  12. package/dist/templates/__when_apiLayer_axios__/src/services/http/client.ts.ejs +174 -0
  13. package/dist/templates/__when_apiLayer_axios__/src/services/http/errors.ts.ejs +81 -0
  14. package/dist/templates/__when_apiLayer_axios__/src/services/http/index.ts.ejs +5 -0
  15. package/dist/templates/__when_apiLayer_fetch__/src/services/api.ts.ejs +22 -0
  16. package/dist/templates/__when_apiLayer_fetch__/src/services/http/client.ts.ejs +195 -0
  17. package/dist/templates/__when_apiLayer_fetch__/src/services/http/errors.ts.ejs +81 -0
  18. package/dist/templates/__when_apiLayer_fetch__/src/services/http/index.ts.ejs +5 -0
  19. package/dist/templates/__when_buildTool_webpack__/webpack.config.js.ejs +1 -1
  20. package/dist/templates/__when_e2eTesting_cypress__/cypress.config.ts.ejs +2 -1
  21. package/dist/templates/__when_e2eTesting_playwright__/playwright.config.ts.ejs +4 -3
  22. package/dist/templates/__when_serverState_rtk-query__/src/store/postsApi.ts.ejs +28 -5
  23. package/dist/templates/__when_unitTesting_vitest__/tests/feedback.test.tsx.ejs +16 -0
  24. package/dist/templates/__when_unitTesting_vitest__/tests/posts.test.tsx.ejs +1 -3
  25. package/dist/templates/docs/architecture.md.ejs +44 -25
  26. package/dist/templates/docs/best-practices.md.ejs +39 -3
  27. package/dist/templates/docs/examples/README.md.ejs +10 -10
  28. package/dist/templates/docs/examples/api.md.ejs +165 -8
  29. package/dist/templates/docs/examples/data-fetching.md.ejs +107 -6
  30. package/dist/templates/docs/examples/forms.md.ejs +66 -6
  31. package/dist/templates/docs/examples/state.md.ejs +50 -6
  32. package/dist/templates/docs/examples/styling.md.ejs +56 -8
  33. package/dist/templates/docs/examples/testing.md.ejs +63 -9
  34. package/dist/templates/docs/getting-started.md.ejs +63 -15
  35. package/dist/templates/docs/selected-stack.md.ejs +110 -8
  36. package/dist/templates/src/features/feedback/FeedbackForm.tsx.ejs +10 -0
  37. package/dist/templates/src/features/posts/PostsPanel.tsx.ejs +1 -1
  38. package/dist/templates/src/routes/PostsPage.tsx.ejs +7 -2
  39. package/dist/templates/src/types/index.ts.ejs +1 -1
  40. package/dist/templates/src/types/models.ts.ejs +18 -0
  41. package/dist/types.d.ts +1 -1
  42. package/dist/types.d.ts.map +1 -1
  43. package/package.json +1 -1
  44. package/templates/README.md.ejs +1 -1
  45. package/templates/__when_apiLayer_axios__/src/services/api.ts.ejs +23 -0
  46. package/templates/__when_apiLayer_axios__/src/services/http/client.ts.ejs +174 -0
  47. package/templates/__when_apiLayer_axios__/src/services/http/errors.ts.ejs +81 -0
  48. package/templates/__when_apiLayer_axios__/src/services/http/index.ts.ejs +5 -0
  49. package/templates/__when_apiLayer_fetch__/src/services/api.ts.ejs +22 -0
  50. package/templates/__when_apiLayer_fetch__/src/services/http/client.ts.ejs +195 -0
  51. package/templates/__when_apiLayer_fetch__/src/services/http/errors.ts.ejs +81 -0
  52. package/templates/__when_apiLayer_fetch__/src/services/http/index.ts.ejs +5 -0
  53. package/templates/__when_buildTool_webpack__/webpack.config.js.ejs +1 -1
  54. package/templates/__when_e2eTesting_cypress__/cypress.config.ts.ejs +2 -1
  55. package/templates/__when_e2eTesting_playwright__/playwright.config.ts.ejs +4 -3
  56. package/templates/__when_serverState_rtk-query__/src/store/postsApi.ts.ejs +28 -5
  57. package/templates/__when_unitTesting_vitest__/tests/feedback.test.tsx.ejs +16 -0
  58. package/templates/__when_unitTesting_vitest__/tests/posts.test.tsx.ejs +1 -3
  59. package/templates/docs/architecture.md.ejs +44 -25
  60. package/templates/docs/best-practices.md.ejs +39 -3
  61. package/templates/docs/examples/README.md.ejs +10 -10
  62. package/templates/docs/examples/api.md.ejs +165 -8
  63. package/templates/docs/examples/data-fetching.md.ejs +107 -6
  64. package/templates/docs/examples/forms.md.ejs +66 -6
  65. package/templates/docs/examples/state.md.ejs +50 -6
  66. package/templates/docs/examples/styling.md.ejs +56 -8
  67. package/templates/docs/examples/testing.md.ejs +63 -9
  68. package/templates/docs/getting-started.md.ejs +63 -15
  69. package/templates/docs/selected-stack.md.ejs +110 -8
  70. package/templates/src/features/feedback/FeedbackForm.tsx.ejs +10 -0
  71. package/templates/src/features/posts/PostsPanel.tsx.ejs +1 -1
  72. package/templates/src/routes/PostsPage.tsx.ejs +7 -2
  73. package/templates/src/types/index.ts.ejs +1 -1
  74. package/templates/src/types/models.ts.ejs +18 -0
  75. package/dist/templates/src/services/api.ts.ejs +0 -84
  76. package/templates/src/services/api.ts.ejs +0 -84
@@ -2,16 +2,173 @@
2
2
 
3
3
  **Choice:** <%= stackLabels.apiLayer %>
4
4
 
5
- ## When to use
5
+ <% if (apiLayer === 'none') { -%>
6
+ ## Why there is no Axios/Fetch client
6
7
 
7
- All HTTP traffic should go through a single module for base URL, headers, and error logging.
8
+ You selected **RTK Query**, which already provides:
8
9
 
9
- ## Best practice
10
+ - a shared base query (`fetchBaseQuery`)
11
+ - caching, deduplication, and generated hooks
12
+ - a single place for headers via `prepareHeaders`
10
13
 
11
- - Type responses at the boundary.
12
- - Surface user-friendly errors in UI; log technical details to monitoring.
13
- - Do not scatter `fetch`/`axios` calls across components.
14
+ Adding Axios/Fetch beside RTK Query usually creates **two HTTP stacks**, duplicated auth logic, and inconsistent error handling.
14
15
 
15
- ## Snippet
16
+ **Where HTTP lives:** `src/store/postsApi.<%= ext %>`
16
17
 
17
- `src/services/api.<%= ext %>` — `apiGet`, `apiPost`, `fetchPosts`, `createFeedback`.
18
+ ### Extending RTK Query
19
+
20
+ ```ts
21
+ // add to endpoints
22
+ getPostById: builder.query({
23
+ query: (id) => `/posts/${id}`,
24
+ providesTags: (_result, _error, id) => [{ type: 'Posts', id }],
25
+ }),
26
+ ```
27
+
28
+ ### Auth header pattern
29
+
30
+ ```ts
31
+ prepareHeaders: (headers) => {
32
+ const token = localStorage.getItem('access_token');
33
+ if (token) headers.set('Authorization', `Bearer ${token}`);
34
+ return headers;
35
+ },
36
+ ```
37
+
38
+ ### When you might still add Axios/Fetch later
39
+
40
+ - Uploading to a third-party host that RTK should not cache
41
+ - Streaming / non-JSON APIs
42
+ - A legacy SDK that must use its own client
43
+
44
+ If that happens, isolate it under `src/services/` and keep RTK as the default for app REST resources.
45
+
46
+ See `docs/examples/data-fetching.md` for UI usage.
47
+ <% } else if (apiLayer === 'axios') { -%>
48
+ ## Layout
49
+
50
+ ```text
51
+ src/services/
52
+ http/
53
+ client.<%= ext %> # axios instance + interceptors
54
+ errors.<%= ext %> # ApiError normalization
55
+ index.<%= ext %> # public exports
56
+ api.<%= ext %> # domain methods (fetchPosts, createFeedback)
57
+ ```
58
+
59
+ ## How to use
60
+
61
+ ### From a query library
62
+
63
+ ```ts
64
+ import { fetchPosts, createFeedback } from '@/services/api';
65
+
66
+ // TanStack Query
67
+ queryFn: ({ signal }) => fetchPosts(signal)
68
+
69
+ // mutation
70
+ await createFeedback({ name, email, message })
71
+ ```
72
+
73
+ ### Direct low-level calls (rare)
74
+
75
+ ```ts
76
+ import { apiGet, apiPost, ApiError } from '@/services/http';
77
+
78
+ try {
79
+ const post = await apiGet(`/posts/${id}`);
80
+ } catch (error) {
81
+ if (error instanceof ApiError && error.isUnauthorized) {
82
+ // redirect to login / refresh token
83
+ }
84
+ }
85
+ ```
86
+
87
+ ## What the Axios setup includes
88
+
89
+ | Concern | Implementation |
90
+ |---------|----------------|
91
+ | Base URL | `env.apiBaseUrl` |
92
+ | Timeout | 15s |
93
+ | Auth | `Authorization: Bearer <token>` from `localStorage.access_token` |
94
+ | Request tracing | `X-Request-Id` on every request |
95
+ | Errors | Normalized `ApiError` with `status`, `code`, `requestId` |
96
+ | 401 handling | Dispatches `auth:unauthorized` browser event |
97
+ | Verbs | `apiGet` / `apiPost` / `apiPut` / `apiPatch` / `apiDelete` |
98
+ | Cancellation | Pass `{ signal }` from AbortController / query libs |
99
+
100
+ ## Best practices
101
+
102
+ - **Components never import axios.** Use domain helpers or at least `@/services/http`.
103
+ - Keep token reads inside the HTTP layer (`getAccessToken`), not in every feature.
104
+ - Listen once for `auth:unauthorized` near the app shell to centralize logout/redirect.
105
+ - Type responses at the boundary (`apiGet<Post[]>`).
106
+ - Log technical details; show user-friendly messages in UI.
107
+ - Prefer adding methods to `api.<%= ext %>` over spreading URLs across features.
108
+
109
+ ## Extending with a refresh-token flow
110
+
111
+ 1. Detect `ApiError.isUnauthorized` in the response interceptor.
112
+ 2. Queue in-flight requests while refreshing.
113
+ 3. Retry the original request with the new access token.
114
+ 4. Fail the queue and redirect if refresh fails.
115
+
116
+ Keep that logic in `http/client.<%= ext %>` so features stay unaware.
117
+
118
+ ## Example: add a new domain method
119
+
120
+ ```ts
121
+ // src/services/api.ts
122
+ export async function fetchPost(id: number, signal?: AbortSignal) {
123
+ return apiGet<Post>(`/posts/${id}`, { signal });
124
+ }
125
+ ```
126
+ <% } else { -%>
127
+ ## Layout
128
+
129
+ ```text
130
+ src/services/
131
+ http/
132
+ client.<%= ext %> # fetch wrapper + timeout/auth/errors
133
+ errors.<%= ext %> # ApiError normalization
134
+ index.<%= ext %>
135
+ api.<%= ext %> # domain methods
136
+ ```
137
+
138
+ ## How to use
139
+
140
+ ```ts
141
+ import { fetchPosts, createFeedback } from '@/services/api';
142
+ import { apiGet, ApiError } from '@/services/http';
143
+
144
+ await fetchPosts(signal);
145
+ await createFeedback(payload);
146
+
147
+ try {
148
+ await apiGet('/posts/1');
149
+ } catch (error) {
150
+ if (error instanceof ApiError && error.isRetryable) {
151
+ // show retry UI
152
+ }
153
+ }
154
+ ```
155
+
156
+ ## What the Fetch setup includes
157
+
158
+ | Concern | Implementation |
159
+ |---------|----------------|
160
+ | Base URL | `env.apiBaseUrl` |
161
+ | Timeout | AbortController (15s default) |
162
+ | Auth | Bearer token from `localStorage.access_token` |
163
+ | Request tracing | `X-Request-Id` |
164
+ | Errors | Normalized `ApiError` |
165
+ | 401 handling | `auth:unauthorized` event |
166
+ | Verbs | get/post/put/patch/delete helpers |
167
+
168
+ ## Best practices
169
+
170
+ - Prefer domain helpers over raw `request()` in components.
171
+ - Pass `signal` from TanStack Query / user cancellation.
172
+ - Keep JSON as the default; use `rawBody: true` for `FormData` uploads.
173
+ - Centralize auth and error mapping — do not re-implement per feature.
174
+ <% } -%>
@@ -1,18 +1,119 @@
1
1
  # Data fetching example
2
2
 
3
3
  **Library:** <%= stackLabels.serverState %>
4
+ <% if (apiLayer !== 'none') { -%>
4
5
  **Transport:** <%= stackLabels.apiLayer %>
6
+ <% } else { -%>
7
+ **Transport:** RTK Query `fetchBaseQuery` (no separate Axios/Fetch client)
8
+ <% } -%>
5
9
 
6
- **Live demo:** Posts panel on `/`
10
+ **Live demo:** `/posts` `src/features/posts/PostsPanel.<%= jsxExt %>`
7
11
 
8
12
  ## When to use
9
13
 
10
- Any remote read/write that benefits from caching, deduplication, retries, or background refresh.
14
+ Any remote read/write that benefits from caching, deduplication, retries, or background refresh:
11
15
 
12
- ## Best practice
16
+ - lists and detail pages
17
+ - polling dashboards
18
+ - mutations that should refresh related queries
13
19
 
14
- Separate server cache from client UI state. Key queries by resource identity, not by component instance.
20
+ ## How to use in this project
15
21
 
16
- ## Snippet
22
+ <% if (serverState === 'rtk-query') { -%>
23
+ ### 1. Define endpoints
17
24
 
18
- See `src/features/posts/PostsPanel.<%= jsxExt %>` and `src/services/api.<%= ext %>`.
25
+ See `src/store/postsApi.<%= ext %>`:
26
+
27
+ ```ts
28
+ export const postsApi = createApi({
29
+ reducerPath: 'postsApi',
30
+ baseQuery: fetchBaseQuery({
31
+ baseUrl: env.apiBaseUrl,
32
+ prepareHeaders: (headers) => {
33
+ const token = localStorage.getItem('access_token');
34
+ if (token) headers.set('Authorization', `Bearer ${token}`);
35
+ return headers;
36
+ },
37
+ }),
38
+ tagTypes: ['Posts'],
39
+ endpoints: (builder) => ({
40
+ getPosts: builder.query({
41
+ query: () => '/posts?_limit=5',
42
+ providesTags: ['Posts'],
43
+ }),
44
+ createFeedback: builder.mutation({
45
+ query: (body) => ({ url: '/posts', method: 'POST', body }),
46
+ invalidatesTags: ['Posts'],
47
+ }),
48
+ }),
49
+ });
50
+ ```
51
+
52
+ ### 2. Consume hooks in UI
53
+
54
+ ```tsx
55
+ const { data, isLoading, error, refetch, isFetching } = useGetPostsQuery();
56
+ const [createFeedback] = useCreateFeedbackMutation();
57
+ await createFeedback(values).unwrap();
58
+ ```
59
+
60
+ ### Best practices (RTK Query)
61
+
62
+ - Prefer **tags** over manual `refetch()` for coherence after writes.
63
+ - Keep `prepareHeaders` as the single place for auth.
64
+ - Split large APIs with `injectEndpoints` as the app grows.
65
+ - Use `transformResponse` for shaping DTO → UI models at the boundary.
66
+ - Handle errors with `error` from the hook; surface user-friendly messages in UI.
67
+ <% } else if (serverState === 'tanstack-query') { -%>
68
+ ### Query example
69
+
70
+ ```tsx
71
+ const { data, isLoading, error, refetch } = useQuery({
72
+ queryKey: ['posts'],
73
+ queryFn: ({ signal }) => fetchPosts(signal),
74
+ });
75
+ ```
76
+
77
+ `fetchPosts` lives in `src/services/api.<%= ext %>` and already goes through the shared HTTP client.
78
+
79
+ ### Mutation example
80
+
81
+ ```tsx
82
+ const mutation = useMutation({
83
+ mutationFn: createFeedback,
84
+ onSuccess: () => queryClient.invalidateQueries({ queryKey: ['posts'] }),
85
+ });
86
+ ```
87
+
88
+ ### Best practices (TanStack Query)
89
+
90
+ - Hierarchical keys: `['posts']`, `['posts', postId]`, `['users', userId, 'posts']`
91
+ - Always accept/pass `AbortSignal` for cancellable requests
92
+ - Set sensible `staleTime` (already defaulted in `providers.<%= jsxExt %>`)
93
+ - Keep `queryFn` pure: no React state writes inside it
94
+ <% } else if (serverState === 'swr') { -%>
95
+ ### Example
96
+
97
+ ```tsx
98
+ const { data, error, isLoading, mutate } = useSWR('/posts?_limit=5');
99
+ ```
100
+
101
+ The shared fetcher in `providers.<%= jsxExt %>` calls `apiGet`.
102
+
103
+ ### Best practices (SWR)
104
+
105
+ - Use stable keys (URL or serialized params)
106
+ - Call `mutate` after writes to revalidate
107
+ - Prefer the global fetcher; override per-hook only when necessary
108
+ <% } else { -%>
109
+ Manual fetching is used in the posts panel. As soon as you add caching needs, introduce TanStack Query, SWR, or RTK Query.
110
+ <% } -%>
111
+
112
+ ## Best practice (all libraries)
113
+
114
+ Separate **server cache** from **client UI state**. Key queries by resource identity, not by component instance.
115
+
116
+ ## Related docs
117
+
118
+ - HTTP transport details: `docs/examples/api.md`
119
+ - Architecture boundaries: `docs/architecture.md`
@@ -3,16 +3,76 @@
3
3
  **Forms:** <%= stackLabels.forms %>
4
4
  **Validation:** <%= stackLabels.validation %>
5
5
 
6
- **Live demo:** Feedback form on `/`
6
+ **Live demo:** `/feedback` `src/features/feedback/FeedbackForm.<%= jsxExt %>`
7
+ **Schemas:** `src/lib/validation.<%= ext %>`
7
8
 
8
9
  ## When to use
9
10
 
10
- Multi-field input with client-side rules and async submit.
11
+ Multi-field input with client-side rules and async submit:
11
12
 
12
- ## Best practice
13
+ - contact / feedback forms
14
+ - settings screens
15
+ - multi-step wizards (add a stepper on top of the same schema)
13
16
 
14
- Define schemas once in `src/lib/validation.<%= ext %>` and reuse across UI and API boundaries.
17
+ ## How to use in this project
15
18
 
16
- ## Snippet
19
+ 1. Define the schema once in `src/lib/validation.<%= ext %>`.
20
+ 2. Bind inputs with <%= stackLabels.forms %>.
21
+ 3. On submit, call the API layer:
22
+ <% if (serverState === 'rtk-query') { -%>
23
+ - RTK mutation: `useCreateFeedbackMutation()` → `.unwrap()`
24
+ <% } else { -%>
25
+ - `createFeedback()` from `@/services/api`
26
+ <% } -%>
27
+ 4. Show success with `role="status"` and reset the form.
17
28
 
18
- See `src/features/feedback/FeedbackForm.<%= jsxExt %>`.
29
+ <% if (forms === 'react-hook-form') { -%>
30
+ ### React Hook Form pattern
31
+
32
+ ```tsx
33
+ const {
34
+ register,
35
+ handleSubmit,
36
+ formState: { errors, isSubmitting },
37
+ } = useForm({
38
+ defaultValues,
39
+ resolver: zodResolver(feedbackSchema), // when using Zod
40
+ });
41
+
42
+ const onSubmit = handleSubmit(async (values) => {
43
+ await createFeedback(values);
44
+ });
45
+ ```
46
+ <% } else if (forms === 'formik') { -%>
47
+ ### Formik pattern
48
+
49
+ Use `<Formik>` + `<Field>` and pass a Yup schema via `validationSchema` when selected.
50
+ <% } else if (forms === 'tanstack-form') { -%>
51
+ ### TanStack Form pattern
52
+
53
+ Create the form with `useForm`, validate with your schema inside `onSubmit`, and bind each field through `form.Field`.
54
+ <% } else { -%>
55
+ ### Native form pattern
56
+
57
+ Keep values in `useState`, run the schema/validator on submit, then call the API helper.
58
+ <% } -%>
59
+
60
+ ## Best practices
61
+
62
+ - **Single source of truth for rules** — schemas in `src/lib`, not duplicated `if` checks in JSX.
63
+ - Validate **on submit** first; add on-blur validation when users need earlier feedback.
64
+ - Map API validation errors (422) back onto fields when the backend returns field paths.
65
+ - Disable the submit button while `isSubmitting` to prevent double posts.
66
+ - Keep forms accessible: real `<label>`, `aria-invalid`, and visible error text.
67
+ - Do not store form drafts in global client state unless the draft must survive across routes.
68
+
69
+ ## Testing tips
70
+
71
+ - Assert validation messages for empty submit.
72
+ - Mock the network boundary (API helper or RTK mutation) and assert the success status region.
73
+ - Prefer `userEvent` over `fireEvent` for realistic typing.
74
+
75
+ ## Related
76
+
77
+ - HTTP / RTK details: `docs/examples/api.md`
78
+ - Live UI: `/feedback`
@@ -2,16 +2,60 @@
2
2
 
3
3
  **Library:** <%= stackLabels.stateManagement %>
4
4
 
5
- **Live demo:** Counter panel on `/`
5
+ **Live demo:** `/counter` `src/features/counter/CounterPanel.<%= jsxExt %>`
6
+ **Store wiring:** `src/store/`
6
7
 
7
8
  ## When to use
8
9
 
9
- Shared interactive state that is not fetched from an API (wizard steps, UI toggles, local counters).
10
+ Shared interactive state that is **not** fetched from an API:
10
11
 
11
- ## Best practice
12
+ - wizard steps, UI toggles, selected tabs
13
+ - ephemeral counters / filters that reset often
14
+ - cross-route UI that should survive navigation but not a full reload
12
15
 
13
- Keep stores/slices focused. Prefer feature-local state; promote to global only when multiple distant trees need it.
16
+ If the data comes from your backend, prefer **<%= stackLabels.serverState %>** instead.
14
17
 
15
- ## Snippet
18
+ ## How to use in this project
16
19
 
17
- See `src/features/counter/CounterPanel.<%= jsxExt %>` and `src/store/`.
20
+ <% if (stateManagement === 'redux') { -%>
21
+ 1. Counter slice lives in `src/store/index.<%= ext %>` (`counterSlice` + `store`).
22
+ 2. Read/write with `useSelector` / `useDispatch` from the feature panel (or add typed hooks later).
23
+
24
+ ```tsx
25
+ import { useDispatch, useSelector } from 'react-redux';
26
+ import { increment, type RootState } from '@/store';
27
+
28
+ const value = useSelector((s: RootState) => s.counter.value);
29
+ const dispatch = useDispatch();
30
+ dispatch(increment());
31
+ ```
32
+ <% } else if (stateManagement === 'zustand') { -%>
33
+ Create/consume the store from `src/store/index.<%= ext %>` with selectors so components only re-render on the slices they need.
34
+
35
+ ```tsx
36
+ import { useCounterStore } from '@/store';
37
+
38
+ const value = useCounterStore((s) => s.value);
39
+ const increment = useCounterStore((s) => s.increment);
40
+ ```
41
+ <% } else if (stateManagement === 'jotai') { -%>
42
+ Define atoms in `src/store/index.<%= ext %>` and compose them. Wrap the tree with `Provider` (already done in `providers.<%= jsxExt %>`).
43
+ <% } else if (stateManagement === 'context') { -%>
44
+ Use the provided context + reducer pattern in `src/store/context`. Keep the context value stable; expose actions through a custom hook.
45
+ <% } else { -%>
46
+ No global client-state library was selected. Prefer React local state until multiple distant trees need the same data.
47
+ <% } -%>
48
+
49
+ ## Best practices
50
+
51
+ - **Feature-local first.** Promote to global only when props drilling becomes painful across routes.
52
+ - **One concern per store/slice.** Avoid a single “appState” blob.
53
+ - **Do not mirror server entities** in client state when you already use <%= stackLabels.serverState %>.
54
+ - Prefer selectors/hooks over reading the entire store in leaf components.
55
+ - Keep side effects (HTTP, analytics) out of reducers; trigger them from thunks/listeners/components.
56
+
57
+ ## Common pitfalls
58
+
59
+ - Putting API responses into Redux/Zustand “because we might need them later”
60
+ - Updating global state on every keystroke in a form (forms belong to <%= stackLabels.forms %>)
61
+ - Forgetting to reset ephemeral UI state when leaving a flow
@@ -5,19 +5,67 @@
5
5
 
6
6
  ## When to use
7
7
 
8
- Establish design tokens early; use a component library for complex accessible primitives.
8
+ Establish design tokens early; use a component library for complex accessible primitives (dialogs, menus, focus traps).
9
9
 
10
- ## Best practice
10
+ ## How to use in this project
11
+
12
+ | Piece | Location |
13
+ |-------|----------|
14
+ | Global styles / tokens | `src/styles/global.<%= styling === 'sass' && uiLibrary !== 'shadcn' ? 'scss' : 'css' %>` |
15
+ | Button adapter | `src/components/ui/UiButton.<%= jsxExt %>` |
16
+ | Layout chrome | `src/components/layout/` |
17
+
18
+ <% if (styling === 'tailwind') { -%>
19
+ ### Tailwind
20
+
21
+ Utility classes are available across the app. Prefer composing utilities in components over one-off CSS files unless the rule is truly global.
22
+
23
+ ```tsx
24
+ <button className="rounded-md bg-slate-900 px-3 py-2 text-white hover:bg-slate-800">
25
+ Save
26
+ </button>
27
+ ```
28
+
29
+ **Best practices**
30
+
31
+ - Extract repeated class clusters into small components (`UiButton`) instead of giant `className` strings everywhere.
32
+ - Keep design tokens (colors, spacing) consistent; avoid magic hex values in features.
33
+ <% } else if (styling === 'css-modules') { -%>
34
+ ### CSS Modules
35
+
36
+ Colocate `*.module.css` next to the component. Import the `styles` object and reference `styles.root`.
37
+ <% } else if (styling === 'styled-components') { -%>
38
+ ### Styled Components
39
+
40
+ Create styled primitives next to the component. Prefer transient props (`$active`) so DOM attributes stay clean.
41
+ <% } else { -%>
42
+ ### Sass/SCSS
43
+
44
+ Shared variables/mixins belong under `src/styles/`. Feature-specific partials can live next to the feature.
45
+ <% } -%>
11
46
 
12
47
  <% if (uiLibrary === 'shadcn') { -%>
13
- Treat shadcn-style primitives as owned source — customize freely, keep `cn()` helpers consistent.
48
+ ## shadcn/ui notes
49
+
50
+ Treat shadcn-style primitives as **owned source** — customize freely, keep `cn()` helpers consistent, and avoid patching node_modules.
14
51
  <% } else if (uiLibrary === 'none') { -%>
15
- Invest in a small set of shared primitives (`UiButton`, layout) before adding a full kit.
52
+ ## No UI kit
53
+
54
+ Invest in a small set of shared primitives (`UiButton`, layout) before adding a full kit. This keeps accessibility and spacing consistent.
16
55
  <% } else { -%>
17
- Wrap third-party components in thin adapters (`UiButton`) so swapping kits later is cheaper.
56
+ ## <%= stackLabels.uiLibrary %> notes
57
+
58
+ Wrap third-party components in thin adapters (`UiButton`) so swapping kits later is cheaper and theme overrides stay centralized (see `providers.<%= jsxExt %>`).
18
59
  <% } -%>
19
60
 
20
- ## Snippet
61
+ ## Best practices
62
+
63
+ - One visual language: spacing, type scale, and radius should feel intentional.
64
+ - Prefer composition over deep CSS specificity wars.
65
+ - Ensure focus states are visible for keyboard users.
66
+ - Keep marketing/landing flair out of app chrome unless product requires it.
67
+
68
+ ## Related
21
69
 
22
- Global styles: `src/styles/global.<%= styling === 'sass' && uiLibrary !== 'shadcn' ? 'scss' : 'css' %>`
23
- Button adapter: `src/components/ui/UiButton.<%= jsxExt %>`
70
+ - Architecture: `docs/architecture.md`
71
+ - Accessibility checklist: `docs/best-practices.md`
@@ -8,20 +8,74 @@
8
8
 
9
9
  ## When to use
10
10
 
11
- - Unit: pure logic + component behavior
12
- - E2E: critical journeys across routes
13
- - Visual: regressions in UI appearance
11
+ | Layer | Use for | Avoid using for |
12
+ |-------|---------|-----------------|
13
+ | Unit / RTL | logic, component behavior, form validation | full auth/payment journeys |
14
+ | E2E | critical user journeys across routes | asserting every CSS class |
15
+ | Visual | regressions in appearance / states | business-rule correctness |
14
16
 
15
- ## Best practice
17
+ ## How to run
16
18
 
17
- Keep e2e thin. Prefer Testing Library queries that reflect how users find elements (`getByRole`, labels).
19
+ <% if (unitTesting !== 'none') { -%>
20
+ ```bash
21
+ <%= packageManager === 'npm' ? 'npm run' : packageManager %> test
22
+ <%= packageManager === 'npm' ? 'npm run' : packageManager %> test:watch
23
+ ```
24
+ <% } -%>
25
+ <% if (e2eTesting === 'playwright') { -%>
26
+ ```bash
27
+ <%= packageManager === 'npm' ? 'npm run' : packageManager %> test:e2e:install # once
28
+ <%= packageManager === 'npm' ? 'npm run' : packageManager %> test:e2e
29
+ <%= packageManager === 'npm' ? 'npm run' : packageManager %> test:e2e:ui
30
+ ```
18
31
 
19
- ## Locations
32
+ E2E base URL: `http://127.0.0.1:<%= buildTool === 'webpack' ? 3000 : 5173 %>`
33
+ <% } else if (e2eTesting === 'cypress') { -%>
34
+ ```bash
35
+ <%= packageManager === 'npm' ? 'npm run' : packageManager %> test:e2e
36
+ ```
37
+ <% } -%>
38
+ <% if (visualTesting === 'storybook') { -%>
39
+ ```bash
40
+ <%= packageManager === 'npm' ? 'npm run' : packageManager %> storybook
41
+ ```
42
+ <% } -%>
43
+
44
+ ## Project layout
20
45
 
21
46
  - Setup: `tests/setup.<%= ext %>` (when applicable)
22
- - Component smoke: `tests/App.test.<%= jsxExt %>`
47
+ - Unit suites: `tests/*.test.<%= jsxExt %>`
23
48
  <% if (e2eTesting === 'playwright') { -%>
24
- - E2E: `tests/e2e/home.spec.<%= ext %>`
49
+ - E2E: `tests/e2e/*.spec.<%= ext %>` (excluded from unit runner)
25
50
  <% } else if (e2eTesting === 'cypress') { -%>
26
- - E2E: `cypress/e2e/home.cy.<%= ext %>`
51
+ - E2E: `cypress/e2e/*`
52
+ <% } -%>
53
+ <% if (visualTesting === 'storybook') { -%>
54
+ - Stories: `src/**/*.stories.<%= jsxExt %>`
27
55
  <% } -%>
56
+
57
+ ## Best practices
58
+
59
+ - Query the way users do: `getByRole`, label text, placeholder — not implementation details.
60
+ - Mock at the **network/domain boundary**:
61
+ <% if (serverState === 'rtk-query') { -%>
62
+ - mock RTK hooks/endpoints for unit tests when you are not integration-testing the store
63
+ <% } else { -%>
64
+ - mock `@/services/api` helpers rather than axios/fetch internals
65
+ <% } -%>
66
+ - Keep e2e **thin**: home load, posts happy path, feedback submit.
67
+ - Do not let unit tests import Playwright specs (unit config excludes `tests/e2e`).
68
+ - For async UI, prefer `findBy*` / `waitFor` over arbitrary `sleep`.
69
+
70
+ ## Example unit test shape
71
+
72
+ ```tsx
73
+ renderWithProviders(<FeedbackForm />);
74
+ await user.click(screen.getByRole('button', { name: /Send feedback/i }));
75
+ expect(screen.getAllByText(/required|valid email/i).length).toBeGreaterThan(0);
76
+ ```
77
+
78
+ ## Related
79
+
80
+ - Strategy & CI: `docs/best-practices.md`
81
+ - Forms guide: `docs/examples/forms.md`