@tekyzinc/gsd-t 2.46.11 → 2.50.10
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.
- package/CHANGELOG.md +11 -0
- package/README.md +22 -2
- package/bin/debug-ledger.js +193 -0
- package/bin/gsd-t.js +259 -1
- package/commands/gsd-t-debug.md +26 -1
- package/commands/gsd-t-execute.md +31 -3
- package/commands/gsd-t-help.md +18 -2
- package/commands/gsd-t-integrate.md +16 -0
- package/commands/gsd-t-quick.md +18 -1
- package/commands/gsd-t-test-sync.md +5 -1
- package/commands/gsd-t-verify.md +6 -1
- package/commands/gsd-t-wave.md +26 -0
- package/docs/GSD-T-README.md +83 -1
- package/docs/architecture.md +9 -1
- package/docs/requirements.md +30 -0
- package/package.json +1 -1
- package/templates/CLAUDE-global.md +19 -2
- package/templates/stacks/_security.md +243 -0
- package/templates/stacks/desktop.ini +2 -0
- package/templates/stacks/docker.md +202 -0
- package/templates/stacks/firebase.md +166 -0
- package/templates/stacks/flutter.md +205 -0
- package/templates/stacks/github-actions.md +201 -0
- package/templates/stacks/graphql.md +216 -0
- package/templates/stacks/neo4j.md +218 -0
- package/templates/stacks/nextjs.md +184 -0
- package/templates/stacks/node-api.md +196 -0
- package/templates/stacks/playwright.md +528 -0
- package/templates/stacks/postgresql.md +225 -0
- package/templates/stacks/python.md +243 -0
- package/templates/stacks/react-native.md +216 -0
- package/templates/stacks/react.md +293 -0
- package/templates/stacks/redux.md +193 -0
- package/templates/stacks/rest-api.md +202 -0
- package/templates/stacks/supabase.md +188 -0
- package/templates/stacks/tailwind.md +169 -0
- package/templates/stacks/typescript.md +176 -0
- package/templates/stacks/vite.md +176 -0
- package/templates/stacks/vue.md +189 -0
- package/templates/stacks/zustand.md +203 -0
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
# React Standards
|
|
2
|
+
|
|
3
|
+
These rules are MANDATORY. Violations fail the task. No exceptions.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Server State — React Query
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
MANDATORY:
|
|
11
|
+
├── Use React Query for ALL server data fetching — NEVER useEffect + fetch
|
|
12
|
+
├── NEVER store server data in useState — it belongs in the query cache
|
|
13
|
+
├── useQuery for reads, useMutation for writes
|
|
14
|
+
└── Set staleTime explicitly — do not rely on defaults
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
**BAD** — `useEffect(() => { fetch(...).then(setUsers); }, []);`
|
|
18
|
+
|
|
19
|
+
**GOOD**
|
|
20
|
+
```tsx
|
|
21
|
+
const { data: users } = useQuery({
|
|
22
|
+
queryKey: ['users'],
|
|
23
|
+
queryFn: api.getUsers,
|
|
24
|
+
staleTime: 5 * 60 * 1000,
|
|
25
|
+
});
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## 2. Component Design
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
MANDATORY:
|
|
34
|
+
├── Max 150 lines per component file — extract if longer
|
|
35
|
+
├── Container/Presenter split: containers fetch data, presenters render UI
|
|
36
|
+
├── Extract complex logic into custom hooks (useXxx)
|
|
37
|
+
├── One component per file
|
|
38
|
+
└── No business logic in JSX — compute values above the return statement
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**BAD** — 300-line component mixing fetch, transform, and render logic.
|
|
42
|
+
|
|
43
|
+
**GOOD**
|
|
44
|
+
```tsx
|
|
45
|
+
// Container fetches; Presenter renders
|
|
46
|
+
function UserListContainer() {
|
|
47
|
+
const { data } = useQuery({ queryKey: ['users'], queryFn: api.getUsers });
|
|
48
|
+
return <UserList users={data ?? []} />;
|
|
49
|
+
}
|
|
50
|
+
function UserList({ users }: { users: User[] }) {
|
|
51
|
+
return <ul>{users.map(u => <UserRow key={u.id} user={u} />)}</ul>;
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## 3. Props Rules
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
MANDATORY:
|
|
61
|
+
├── Define props as TypeScript interfaces
|
|
62
|
+
├── Destructure props in the function signature
|
|
63
|
+
├── NEVER use defaultProps — use default parameter values instead
|
|
64
|
+
└── Avoid prop drilling beyond 2 levels — use Context or composition
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**GOOD**
|
|
68
|
+
```tsx
|
|
69
|
+
interface ButtonProps { label: string; variant?: 'primary' | 'secondary'; }
|
|
70
|
+
function Button({ label, variant = 'primary' }: ButtonProps) { ... }
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## 4. Key Prop Rules
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
MANDATORY:
|
|
79
|
+
├── NEVER use array index as key on dynamic (add/remove/reorder) lists
|
|
80
|
+
├── Use stable unique IDs from data (item.id, item.slug)
|
|
81
|
+
└── Never generate keys at render time (Math.random(), Date.now())
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
**BAD** — `items.map((item, i) => <Row key={i} />)`
|
|
85
|
+
|
|
86
|
+
**GOOD** — `items.map(item => <Row key={item.id} />)`
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## 5. Hooks Rules
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
MANDATORY:
|
|
94
|
+
├── Only call hooks at the top level — NEVER inside conditionals or loops
|
|
95
|
+
├── NEVER call a hook conditionally — restructure using early JSX returns
|
|
96
|
+
├── Custom hooks MUST start with "use" prefix
|
|
97
|
+
└── One concern per custom hook
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**BAD** — `if (isLoggedIn) { const data = useUserData(); }`
|
|
101
|
+
|
|
102
|
+
**GOOD** — Call `useUserData()` unconditionally; return early in JSX if not logged in.
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## 6. Memoization
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
USE SPARINGLY — profile first, optimize second:
|
|
110
|
+
├── React.memo: only when parent re-renders often AND child props rarely change
|
|
111
|
+
├── useMemo: only for computationally expensive operations (not string/array literals)
|
|
112
|
+
└── useCallback: only when the function is passed to a memoized child or is a
|
|
113
|
+
useEffect dependency
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
**BAD** — `const label = useMemo(() => \`Hello, ${name}\`, [name]);` (trivial — no benefit)
|
|
117
|
+
|
|
118
|
+
**GOOD** — `const sorted = useMemo(() => items.sort(expensiveSort), [items]);`
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## 7. Lazy Loading
|
|
123
|
+
|
|
124
|
+
```
|
|
125
|
+
MANDATORY for route-level components:
|
|
126
|
+
├── Wrap with React.lazy() + Suspense
|
|
127
|
+
├── Always provide a Suspense fallback (skeleton or spinner — not null)
|
|
128
|
+
└── Group related routes in one lazy chunk to minimize round trips
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
```tsx
|
|
132
|
+
const Dashboard = React.lazy(() => import('./Dashboard'));
|
|
133
|
+
<Suspense fallback={<DashboardSkeleton />}><Dashboard /></Suspense>
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## 8. Error Boundaries
|
|
139
|
+
|
|
140
|
+
```
|
|
141
|
+
MANDATORY:
|
|
142
|
+
├── Wrap every route and major feature section in an ErrorBoundary
|
|
143
|
+
├── Display a user-friendly fallback UI — never a blank screen
|
|
144
|
+
├── Log to error tracking (Sentry) in componentDidCatch
|
|
145
|
+
└── Use granular boundaries — one top-level boundary is not enough
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## 9. Accessibility (a11y)
|
|
151
|
+
|
|
152
|
+
```
|
|
153
|
+
MANDATORY:
|
|
154
|
+
├── Use semantic HTML (button, nav, main, header, section)
|
|
155
|
+
├── All interactive elements MUST be keyboard-navigable (Tab, Enter, Escape)
|
|
156
|
+
├── Images require alt text (alt="" for decorative)
|
|
157
|
+
├── Form inputs MUST have associated <label> (htmlFor + id)
|
|
158
|
+
├── Modals: trap focus inside, restore on close, Escape closes
|
|
159
|
+
├── Icon-only buttons require aria-label
|
|
160
|
+
└── NEVER remove focus outlines — style them, never hide them
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
**BAD** — `<div onClick={del}>Delete</div>` / `<img src={x} />`
|
|
164
|
+
|
|
165
|
+
**GOOD** — `<button onClick={del} aria-label="Delete">Delete</button>` / `<img src={x} alt="Profile photo" />`
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## 10. State Management — When to Use What
|
|
170
|
+
|
|
171
|
+
```
|
|
172
|
+
MANDATORY — choose the right tool for the data type:
|
|
173
|
+
├── UI toggles, form inputs → useState
|
|
174
|
+
├── Complex local state (multi-step) → useReducer
|
|
175
|
+
├── Server/API data → React Query (NEVER useState)
|
|
176
|
+
├── Auth session, permissions → React Context
|
|
177
|
+
├── Theme preference → React Context + localStorage
|
|
178
|
+
└── URL state (page, filters) → React Router (useSearchParams)
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
**Rules:**
|
|
182
|
+
- Never duplicate server state in local state — React Query is the single source of truth for API data
|
|
183
|
+
- Lift state only when needed — if two siblings need the same state, lift to parent, not a global store
|
|
184
|
+
- Derive, don't store — if a value can be computed from existing state, compute it
|
|
185
|
+
|
|
186
|
+
**BAD** — storing derived state:
|
|
187
|
+
```tsx
|
|
188
|
+
const [users, setUsers] = useState(allUsers);
|
|
189
|
+
const [filteredUsers, setFilteredUsers] = useState([]);
|
|
190
|
+
const [userCount, setUserCount] = useState(0);
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
**GOOD** — derive from source:
|
|
194
|
+
```tsx
|
|
195
|
+
const [filter, setFilter] = useState('all');
|
|
196
|
+
const filteredUsers = useMemo(
|
|
197
|
+
() => users.filter(u => filter === 'all' || u.status === filter),
|
|
198
|
+
[users, filter]
|
|
199
|
+
);
|
|
200
|
+
const userCount = filteredUsers.length;
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## 11. Form Management — react-hook-form + Zod
|
|
206
|
+
|
|
207
|
+
```
|
|
208
|
+
MANDATORY when project uses forms:
|
|
209
|
+
├── One Zod schema per form — schema is the single source of truth for validation
|
|
210
|
+
├── Use react-hook-form with zodResolver — NEVER validate in event handlers
|
|
211
|
+
├── Show field-level errors below the field — not in a toast
|
|
212
|
+
├── Disable submit button while submitting
|
|
213
|
+
├── Use noValidate on <form> — browser validation conflicts with custom validation
|
|
214
|
+
└── Use setError('root', ...) for server-side errors
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
**GOOD**
|
|
218
|
+
```tsx
|
|
219
|
+
const schema = z.object({
|
|
220
|
+
email: z.string().email('Enter a valid email'),
|
|
221
|
+
name: z.string().min(2, 'Name must be at least 2 characters'),
|
|
222
|
+
});
|
|
223
|
+
type FormData = z.infer<typeof schema>;
|
|
224
|
+
|
|
225
|
+
function MyForm() {
|
|
226
|
+
const { register, handleSubmit, formState: { errors } } = useForm<FormData>({
|
|
227
|
+
resolver: zodResolver(schema),
|
|
228
|
+
});
|
|
229
|
+
return (
|
|
230
|
+
<form onSubmit={handleSubmit(onSubmit)} noValidate>
|
|
231
|
+
<label htmlFor="email">Email</label>
|
|
232
|
+
<input id="email" {...register('email')} />
|
|
233
|
+
{errors.email && <span className="error">{errors.email.message}</span>}
|
|
234
|
+
<button type="submit" disabled={isSubmitting}>Save</button>
|
|
235
|
+
</form>
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
## 12. React Naming Conventions
|
|
243
|
+
|
|
244
|
+
```
|
|
245
|
+
MANDATORY:
|
|
246
|
+
├── Components: PascalCase (UserList.tsx, DataTable.tsx)
|
|
247
|
+
├── Hooks: camelCase + use (useAuth.ts, useUsers.ts)
|
|
248
|
+
├── Services: camelCase + Service (userService.ts)
|
|
249
|
+
├── Event handlers: handle prefix (handleClick, handleSubmit)
|
|
250
|
+
├── Callback props: on prefix (onClick, onClose, onChange)
|
|
251
|
+
├── Boolean state: is/has/can prefix (isOpen, hasError, canEdit)
|
|
252
|
+
├── Folders: kebab-case (user-settings/, danger-window/)
|
|
253
|
+
└── Non-component files: camelCase (helpers.ts, permissions.ts)
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
---
|
|
257
|
+
|
|
258
|
+
## 13. Anti-Patterns
|
|
259
|
+
|
|
260
|
+
```
|
|
261
|
+
NEVER:
|
|
262
|
+
├── useEffect for data fetching (use React Query — Section 1)
|
|
263
|
+
├── Prop drilling beyond 2 levels
|
|
264
|
+
├── Array index as key on dynamic lists (Section 4)
|
|
265
|
+
├── Conditional hook calls (Section 5)
|
|
266
|
+
├── Direct state mutation: state.list.push(x) — return new objects/arrays
|
|
267
|
+
├── console.log in committed code
|
|
268
|
+
├── Derived state in useState when it can be computed (Section 10)
|
|
269
|
+
├── Validate forms in event handlers (use react-hook-form + zod — Section 11)
|
|
270
|
+
└── dangerouslySetInnerHTML without sanitization — see _security.md
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
---
|
|
274
|
+
|
|
275
|
+
## React Verification Checklist
|
|
276
|
+
|
|
277
|
+
- [ ] Server data fetched with React Query — no `useEffect` + fetch
|
|
278
|
+
- [ ] No component exceeds 150 lines
|
|
279
|
+
- [ ] Container/Presenter pattern applied to data-fetching components
|
|
280
|
+
- [ ] All props typed with TypeScript interfaces
|
|
281
|
+
- [ ] No array index as key on dynamic lists
|
|
282
|
+
- [ ] No conditional hook calls
|
|
283
|
+
- [ ] Memoization justified — not preemptive
|
|
284
|
+
- [ ] Route components use `React.lazy` + `Suspense` with a fallback
|
|
285
|
+
- [ ] `ErrorBoundary` wraps each major feature section
|
|
286
|
+
- [ ] All interactive elements keyboard-accessible with ARIA labels
|
|
287
|
+
- [ ] No `console.log` in committed code
|
|
288
|
+
- [ ] No direct state mutations — always return new objects/arrays
|
|
289
|
+
- [ ] `dangerouslySetInnerHTML` usage reviewed against `_security.md`
|
|
290
|
+
- [ ] State tool matches data type (useState/useReducer/Context/React Query/Router)
|
|
291
|
+
- [ ] No derived state in useState — compute from source
|
|
292
|
+
- [ ] Forms use react-hook-form + zod — no manual validation in handlers
|
|
293
|
+
- [ ] React naming conventions followed (handle*, on*, is/has/can)
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# Redux Toolkit Standards
|
|
2
|
+
|
|
3
|
+
These rules are MANDATORY. Violations fail the task. No exceptions.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. RTK Only — No Legacy Redux
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
MANDATORY:
|
|
11
|
+
├── Use @reduxjs/toolkit (RTK) — NEVER raw redux with manual action creators/reducers
|
|
12
|
+
├── Use createSlice for all state slices
|
|
13
|
+
├── Use configureStore — NEVER createStore
|
|
14
|
+
├── Use RTK Query for server data — NEVER manual async thunks for API calls
|
|
15
|
+
└── Use TypeScript with typed hooks (useAppSelector, useAppDispatch)
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## 2. Slice Design
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
MANDATORY:
|
|
24
|
+
├── One slice per domain: userSlice, cartSlice, uiSlice
|
|
25
|
+
├── Name slices clearly: name: 'auth', name: 'cart'
|
|
26
|
+
├── Define state interface with TypeScript
|
|
27
|
+
├── Reducers handle one concern — keep them focused
|
|
28
|
+
├── Use prepare callbacks for action payload shaping
|
|
29
|
+
└── Export actions and reducer separately
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
**GOOD**
|
|
33
|
+
```typescript
|
|
34
|
+
interface AuthState {
|
|
35
|
+
user: User | null;
|
|
36
|
+
status: 'idle' | 'loading' | 'succeeded' | 'failed';
|
|
37
|
+
error: string | null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const initialState: AuthState = {
|
|
41
|
+
user: null,
|
|
42
|
+
status: 'idle',
|
|
43
|
+
error: null,
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const authSlice = createSlice({
|
|
47
|
+
name: 'auth',
|
|
48
|
+
initialState,
|
|
49
|
+
reducers: {
|
|
50
|
+
setUser: (state, action: PayloadAction<User>) => {
|
|
51
|
+
state.user = action.payload;
|
|
52
|
+
state.status = 'succeeded';
|
|
53
|
+
},
|
|
54
|
+
logout: (state) => {
|
|
55
|
+
state.user = null;
|
|
56
|
+
state.status = 'idle';
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
export const { setUser, logout } = authSlice.actions;
|
|
62
|
+
export default authSlice.reducer;
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## 3. RTK Query — Server Data
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
MANDATORY:
|
|
71
|
+
├── Use RTK Query for ALL API calls — NEVER createAsyncThunk for data fetching
|
|
72
|
+
├── Define one API slice per backend service
|
|
73
|
+
├── Use tag-based cache invalidation — not manual cache updates
|
|
74
|
+
├── Type all endpoints with request/response types
|
|
75
|
+
├── Handle loading, error, and empty states using hook results
|
|
76
|
+
└── Set keepUnusedDataFor to control cache lifetime
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**GOOD**
|
|
80
|
+
```typescript
|
|
81
|
+
export const usersApi = createApi({
|
|
82
|
+
reducerPath: 'usersApi',
|
|
83
|
+
baseQuery: fetchBaseQuery({ baseUrl: '/api/v1' }),
|
|
84
|
+
tagTypes: ['User'],
|
|
85
|
+
endpoints: (builder) => ({
|
|
86
|
+
getUsers: builder.query<User[], UserFilters>({
|
|
87
|
+
query: (filters) => ({ url: '/users', params: filters }),
|
|
88
|
+
providesTags: ['User'],
|
|
89
|
+
}),
|
|
90
|
+
getUser: builder.query<User, string>({
|
|
91
|
+
query: (id) => `/users/${id}`,
|
|
92
|
+
providesTags: (result, error, id) => [{ type: 'User', id }],
|
|
93
|
+
}),
|
|
94
|
+
createUser: builder.mutation<User, CreateUserInput>({
|
|
95
|
+
query: (body) => ({ url: '/users', method: 'POST', body }),
|
|
96
|
+
invalidatesTags: ['User'],
|
|
97
|
+
}),
|
|
98
|
+
}),
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
export const { useGetUsersQuery, useGetUserQuery, useCreateUserMutation } = usersApi;
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## 4. Store Configuration
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
MANDATORY:
|
|
110
|
+
├── configureStore with typed RootState and AppDispatch
|
|
111
|
+
├── Add RTK Query middleware for cache management
|
|
112
|
+
├── Create typed hooks: useAppSelector, useAppDispatch
|
|
113
|
+
├── Redux DevTools enabled by default in development
|
|
114
|
+
└── Keep store configuration in a single store.ts file
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
**GOOD**
|
|
118
|
+
```typescript
|
|
119
|
+
// store.ts
|
|
120
|
+
export const store = configureStore({
|
|
121
|
+
reducer: {
|
|
122
|
+
auth: authReducer,
|
|
123
|
+
cart: cartReducer,
|
|
124
|
+
[usersApi.reducerPath]: usersApi.reducer,
|
|
125
|
+
},
|
|
126
|
+
middleware: (getDefaultMiddleware) =>
|
|
127
|
+
getDefaultMiddleware().concat(usersApi.middleware),
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
export type RootState = ReturnType<typeof store.getState>;
|
|
131
|
+
export type AppDispatch = typeof store.dispatch;
|
|
132
|
+
|
|
133
|
+
// hooks.ts
|
|
134
|
+
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
|
|
135
|
+
export const useAppDispatch: () => AppDispatch = useDispatch;
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## 5. Selectors
|
|
141
|
+
|
|
142
|
+
```
|
|
143
|
+
MANDATORY:
|
|
144
|
+
├── Use createSelector (reselect) for derived/computed state
|
|
145
|
+
├── Select minimal state — NEVER select the entire slice
|
|
146
|
+
├── Co-locate selectors with their slice file
|
|
147
|
+
├── Name selectors: select{Thing} (selectActiveUsers, selectCartTotal)
|
|
148
|
+
└── Memoized selectors for filtered/sorted/computed data
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
**GOOD**
|
|
152
|
+
```typescript
|
|
153
|
+
// In authSlice.ts
|
|
154
|
+
export const selectUser = (state: RootState) => state.auth.user;
|
|
155
|
+
export const selectIsAuthenticated = (state: RootState) => !!state.auth.user;
|
|
156
|
+
|
|
157
|
+
// Memoized computed selector
|
|
158
|
+
export const selectActiveUsers = createSelector(
|
|
159
|
+
[(state: RootState) => state.users.list],
|
|
160
|
+
(users) => users.filter(u => u.isActive)
|
|
161
|
+
);
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## 6. Anti-Patterns
|
|
167
|
+
|
|
168
|
+
```
|
|
169
|
+
NEVER:
|
|
170
|
+
├── Raw redux (createStore, manual action types, switch reducers)
|
|
171
|
+
├── createAsyncThunk for API calls — use RTK Query
|
|
172
|
+
├── Selecting entire slice in a component
|
|
173
|
+
├── Storing server data in slices — use RTK Query
|
|
174
|
+
├── Mutating state outside of createSlice reducers (Immer only works inside)
|
|
175
|
+
├── String action types — use createSlice auto-generated types
|
|
176
|
+
├── Dispatching in useEffect for data fetching — use RTK Query hooks
|
|
177
|
+
└── console.log in reducers
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## Redux Toolkit Verification Checklist
|
|
183
|
+
|
|
184
|
+
- [ ] RTK only — no legacy redux patterns
|
|
185
|
+
- [ ] createSlice for all state management
|
|
186
|
+
- [ ] RTK Query for all API calls — no createAsyncThunk for fetching
|
|
187
|
+
- [ ] Tag-based cache invalidation on mutations
|
|
188
|
+
- [ ] Typed store (RootState, AppDispatch, typed hooks)
|
|
189
|
+
- [ ] Selectors with createSelector for computed state
|
|
190
|
+
- [ ] Minimal state selected — no full-slice subscriptions
|
|
191
|
+
- [ ] Store configured with RTK Query middleware
|
|
192
|
+
- [ ] Loading, error, empty states handled via hook results
|
|
193
|
+
- [ ] No console.log in reducers or slices
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# REST API Design Standards
|
|
2
|
+
|
|
3
|
+
These rules are MANDATORY. Violations fail the task. No exceptions.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. URL Design
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
MANDATORY:
|
|
11
|
+
├── Nouns, not verbs: /users, /orders — NEVER /getUsers, /createOrder
|
|
12
|
+
├── Plural collection names: /users, /products, /orders
|
|
13
|
+
├── Nested resources for relationships: /users/{id}/orders
|
|
14
|
+
├── Max 2 levels of nesting — flatten beyond that
|
|
15
|
+
├── Kebab-case for multi-word paths: /order-items — not /orderItems or /order_items
|
|
16
|
+
├── API version in URL path: /api/v1/users — not in headers
|
|
17
|
+
└── NEVER expose internal IDs or database structure in URLs
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
**BAD** — `/api/getUserById?id=123`, `/api/v1/order_items`
|
|
21
|
+
|
|
22
|
+
**GOOD** — `/api/v1/users/123`, `/api/v1/order-items`
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## 2. HTTP Methods
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
MANDATORY:
|
|
30
|
+
├── GET: Read (no side effects, cacheable)
|
|
31
|
+
├── POST: Create new resource (returns 201 + Location header)
|
|
32
|
+
├── PUT: Full replace of a resource
|
|
33
|
+
├── PATCH: Partial update of a resource
|
|
34
|
+
├── DELETE: Remove a resource (returns 204 or 200)
|
|
35
|
+
├── GET must NEVER modify data
|
|
36
|
+
└── POST is not a catch-all — use the correct method
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## 3. Response Format
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
MANDATORY — consistent envelope for all responses:
|
|
45
|
+
├── Success: { "data": {...} } or { "data": [...] }
|
|
46
|
+
├── Collection: { "data": [...], "meta": { "total": N, "page": 1, "pageSize": 20 } }
|
|
47
|
+
├── Error: { "error": { "code": "NOT_FOUND", "message": "User not found" } }
|
|
48
|
+
├── Use camelCase for JSON keys — matches JavaScript conventions
|
|
49
|
+
├── Include timestamps as ISO 8601 strings with timezone (2026-03-25T10:00:00Z)
|
|
50
|
+
└── Null fields: include with null value — don't omit the key
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
**GOOD**
|
|
54
|
+
```json
|
|
55
|
+
{
|
|
56
|
+
"data": {
|
|
57
|
+
"id": "abc-123",
|
|
58
|
+
"email": "user@example.com",
|
|
59
|
+
"displayName": "Jane Doe",
|
|
60
|
+
"createdAt": "2026-03-25T10:00:00Z",
|
|
61
|
+
"avatar": null
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## 4. Pagination
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
MANDATORY for all list endpoints:
|
|
72
|
+
├── Offset-based: ?page=1&pageSize=20 (simple, good for UI pages)
|
|
73
|
+
├── Cursor-based: ?cursor=abc&limit=20 (better for large/realtime datasets)
|
|
74
|
+
├── Default pageSize: 20, max pageSize: 100
|
|
75
|
+
├── Include pagination metadata in response: total, page, pageSize, hasMore
|
|
76
|
+
├── NEVER return unbounded lists — always paginate or limit
|
|
77
|
+
└── Support sorting: ?sort=createdAt&order=desc
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
**GOOD**
|
|
81
|
+
```json
|
|
82
|
+
{
|
|
83
|
+
"data": [...],
|
|
84
|
+
"meta": {
|
|
85
|
+
"total": 342,
|
|
86
|
+
"page": 2,
|
|
87
|
+
"pageSize": 20,
|
|
88
|
+
"hasMore": true
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## 5. Error Responses
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
MANDATORY:
|
|
99
|
+
├── Use standard HTTP status codes correctly (see table below)
|
|
100
|
+
├── Error body includes: code (machine-readable), message (human-readable)
|
|
101
|
+
├── Validation errors include field-level details
|
|
102
|
+
├── NEVER expose stack traces, SQL errors, or internal paths to clients
|
|
103
|
+
└── Log the full error server-side — return a safe summary to the client
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
| Status | Meaning | When to use |
|
|
107
|
+
|--------|---------|------------|
|
|
108
|
+
| 200 | OK | Successful GET, PUT, PATCH, DELETE |
|
|
109
|
+
| 201 | Created | Successful POST (include Location header) |
|
|
110
|
+
| 204 | No Content | Successful DELETE with no response body |
|
|
111
|
+
| 400 | Bad Request | Invalid input, malformed JSON, validation error |
|
|
112
|
+
| 401 | Unauthorized | Missing or invalid authentication |
|
|
113
|
+
| 403 | Forbidden | Authenticated but insufficient permissions |
|
|
114
|
+
| 404 | Not Found | Resource doesn't exist |
|
|
115
|
+
| 409 | Conflict | Duplicate resource, optimistic lock failure |
|
|
116
|
+
| 422 | Unprocessable Entity | Valid JSON but semantic errors |
|
|
117
|
+
| 429 | Too Many Requests | Rate limit exceeded |
|
|
118
|
+
| 500 | Internal Server Error | Unexpected server failure |
|
|
119
|
+
|
|
120
|
+
**Validation error example:**
|
|
121
|
+
```json
|
|
122
|
+
{
|
|
123
|
+
"error": {
|
|
124
|
+
"code": "VALIDATION_ERROR",
|
|
125
|
+
"message": "Request validation failed",
|
|
126
|
+
"details": [
|
|
127
|
+
{ "field": "email", "message": "Must be a valid email address" },
|
|
128
|
+
{ "field": "name", "message": "Must be at least 2 characters" }
|
|
129
|
+
]
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## 6. Filtering and Search
|
|
137
|
+
|
|
138
|
+
```
|
|
139
|
+
MANDATORY:
|
|
140
|
+
├── Filter via query params: ?status=active&role=admin
|
|
141
|
+
├── Search via query param: ?search=jane (server decides which fields to search)
|
|
142
|
+
├── Date ranges: ?createdAfter=2026-01-01&createdBefore=2026-03-01
|
|
143
|
+
├── Multiple values: ?status=active,pending (comma-separated)
|
|
144
|
+
├── NEVER accept raw SQL or query expressions from the client
|
|
145
|
+
└── Validate and whitelist all filter parameters
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## 7. Versioning
|
|
151
|
+
|
|
152
|
+
```
|
|
153
|
+
MANDATORY:
|
|
154
|
+
├── Version in URL path: /api/v1/, /api/v2/
|
|
155
|
+
├── Bump major version only for breaking changes
|
|
156
|
+
├── Support previous version for a deprecation period (minimum 3 months)
|
|
157
|
+
├── Document breaking changes in a changelog
|
|
158
|
+
└── New fields are NOT breaking changes — clients should ignore unknown fields
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## 8. Rate Limiting
|
|
164
|
+
|
|
165
|
+
```
|
|
166
|
+
MANDATORY for public APIs:
|
|
167
|
+
├── Implement rate limiting per client/API key
|
|
168
|
+
├── Return 429 with Retry-After header
|
|
169
|
+
├── Include rate limit headers: X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset
|
|
170
|
+
└── Different limits per tier (free vs paid) if applicable
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## 9. Anti-Patterns
|
|
176
|
+
|
|
177
|
+
```
|
|
178
|
+
NEVER:
|
|
179
|
+
├── Verbs in URLs (/getUser, /deleteOrder)
|
|
180
|
+
├── GET requests that modify data
|
|
181
|
+
├── Exposing internal errors, stack traces, or SQL to clients
|
|
182
|
+
├── Unbounded list responses without pagination
|
|
183
|
+
├── Inconsistent response shapes between endpoints
|
|
184
|
+
├── Accepting raw query expressions from clients
|
|
185
|
+
├── Breaking changes without version bump
|
|
186
|
+
└── 200 OK with error body — use proper status codes
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## REST API Verification Checklist
|
|
192
|
+
|
|
193
|
+
- [ ] URLs use nouns, plural, kebab-case
|
|
194
|
+
- [ ] Correct HTTP methods (GET reads, POST creates, etc.)
|
|
195
|
+
- [ ] Consistent response envelope (data, error, meta)
|
|
196
|
+
- [ ] All list endpoints paginated with metadata
|
|
197
|
+
- [ ] Error responses include code + message, no internals exposed
|
|
198
|
+
- [ ] Validation errors include field-level details
|
|
199
|
+
- [ ] API versioned in URL path
|
|
200
|
+
- [ ] Rate limiting with proper headers (if public)
|
|
201
|
+
- [ ] Timestamps in ISO 8601 with timezone
|
|
202
|
+
- [ ] Filtering via whitelisted query params only
|