ima-claude 2.18.0 → 2.25.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.
- package/README.md +55 -9
- package/dist/cli.js +5 -1
- package/package.json +1 -1
- package/plugins/ima-claude/.claude-plugin/plugin.json +2 -2
- package/plugins/ima-claude/agents/explorer.md +29 -15
- package/plugins/ima-claude/agents/implementer.md +58 -13
- package/plugins/ima-claude/agents/memory.md +19 -19
- package/plugins/ima-claude/agents/reviewer.md +56 -34
- package/plugins/ima-claude/agents/tester.md +59 -16
- package/plugins/ima-claude/agents/wp-developer.md +66 -21
- package/plugins/ima-claude/hooks/bootstrap.sh +42 -44
- package/plugins/ima-claude/hooks/prompt_coach_digest.md +14 -17
- package/plugins/ima-claude/hooks/prompt_coach_system.md +10 -12
- package/plugins/ima-claude/personalities/README.md +17 -6
- package/plugins/ima-claude/personalities/enable-efficient.md +61 -0
- package/plugins/ima-claude/personalities/enable-terse.md +71 -0
- package/plugins/ima-claude/skills/agentic-workflows/SKILL.md +97 -0
- package/plugins/ima-claude/skills/agentic-workflows/references/phases/deliver.md +181 -0
- package/plugins/ima-claude/skills/agentic-workflows/references/phases/draft.md +99 -0
- package/plugins/ima-claude/skills/agentic-workflows/references/phases/gather.md +130 -0
- package/plugins/ima-claude/skills/agentic-workflows/references/phases/outline.md +106 -0
- package/plugins/ima-claude/skills/agentic-workflows/references/phases/review.md +137 -0
- package/plugins/ima-claude/skills/agentic-workflows/references/standards/draft-format.md +159 -0
- package/plugins/ima-claude/skills/agentic-workflows/references/standards/editorial-standards.md +160 -0
- package/plugins/ima-claude/skills/agentic-workflows/references/standards/outline-format.md +110 -0
- package/plugins/ima-claude/skills/agentic-workflows/references/templates/avada-construction-guide.md +263 -0
- package/plugins/ima-claude/skills/agentic-workflows/references/templates/avada-webinar-example.txt +275 -0
- package/plugins/ima-claude/skills/agentic-workflows/references/templates/cta-block-catalog.md +169 -0
- package/plugins/ima-claude/skills/agentic-workflows/references/templates/espo-email-preparation.md +241 -0
- package/plugins/ima-claude/skills/agentic-workflows/references/templates/webinar-recap-email-espo.html +339 -0
- package/plugins/ima-claude/skills/agentic-workflows/references/templates/webinar-reminder-email-espo.html +458 -0
- package/plugins/ima-claude/skills/agentic-workflows/references/workflows/editorial/webinar-summary.md +81 -0
- package/plugins/ima-claude/skills/architect/SKILL.md +54 -168
- package/plugins/ima-claude/skills/compound-bridge/SKILL.md +41 -94
- package/plugins/ima-claude/skills/design-to-code/SKILL.md +91 -0
- package/plugins/ima-claude/skills/design-to-code/references/guardrails.md +46 -0
- package/plugins/ima-claude/skills/design-to-code/references/phase-a-design-to-prompt.md +141 -0
- package/plugins/ima-claude/skills/design-to-code/references/phase-b-prompt-to-code.md +155 -0
- package/plugins/ima-claude/skills/design-to-code/references/prompt-template.md +95 -0
- package/plugins/ima-claude/skills/discourse/SKILL.md +79 -194
- package/plugins/ima-claude/skills/discourse-admin/SKILL.md +41 -103
- package/plugins/ima-claude/skills/docs-organize/SKILL.md +63 -203
- package/plugins/ima-claude/skills/ember-discourse/SKILL.md +90 -200
- package/plugins/ima-claude/skills/espocrm/SKILL.md +14 -23
- package/plugins/ima-claude/skills/espocrm-api/SKILL.md +79 -192
- package/plugins/ima-claude/skills/functional-programmer/SKILL.md +33 -237
- package/plugins/ima-claude/skills/gh-cli/SKILL.md +26 -65
- package/plugins/ima-claude/skills/ima-bootstrap/SKILL.md +71 -104
- package/plugins/ima-claude/skills/ima-bootstrap/references/ima-brand.md +32 -22
- package/plugins/ima-claude/skills/ima-brand/SKILL.md +18 -23
- package/plugins/ima-claude/skills/ima-copywriting/SKILL.md +68 -179
- package/plugins/ima-claude/skills/ima-doc2pdf/SKILL.md +32 -102
- package/plugins/ima-claude/skills/ima-editorial-scorecard/SKILL.md +38 -63
- package/plugins/ima-claude/skills/ima-editorial-workflow/SKILL.md +69 -114
- package/plugins/ima-claude/skills/ima-email-creator/SKILL.md +16 -22
- package/plugins/ima-claude/skills/ima-forms-expert/SKILL.md +21 -37
- package/plugins/ima-claude/skills/jira-checkpoint/SKILL.md +39 -120
- package/plugins/ima-claude/skills/jquery/SKILL.md +107 -233
- package/plugins/ima-claude/skills/js-fp/SKILL.md +75 -296
- package/plugins/ima-claude/skills/js-fp-api/SKILL.md +52 -162
- package/plugins/ima-claude/skills/js-fp-react/SKILL.md +47 -270
- package/plugins/ima-claude/skills/js-fp-vue/SKILL.md +55 -209
- package/plugins/ima-claude/skills/js-fp-wordpress/SKILL.md +59 -204
- package/plugins/ima-claude/skills/livecanvas/SKILL.md +19 -32
- package/plugins/ima-claude/skills/mcp-atlassian/SKILL.md +146 -136
- package/plugins/ima-claude/skills/mcp-atlassian/references/direct-api-attachments.md +115 -0
- package/plugins/ima-claude/skills/mcp-atlassian/references/direct-api-auth.md +103 -0
- package/plugins/ima-claude/skills/mcp-atlassian/references/direct-api-bulk.md +149 -0
- package/plugins/ima-claude/skills/mcp-atlassian/references/direct-api-misc.md +195 -0
- package/plugins/ima-claude/skills/mcp-atlassian/references/direct-api-sprints.md +158 -0
- package/plugins/ima-claude/skills/mcp-context7/SKILL.md +32 -64
- package/plugins/ima-claude/skills/mcp-gitea/SKILL.md +98 -188
- package/plugins/ima-claude/skills/mcp-github/SKILL.md +60 -124
- package/plugins/ima-claude/skills/mcp-memory/SKILL.md +1 -177
- package/plugins/ima-claude/skills/mcp-qdrant/SKILL.md +58 -115
- package/plugins/ima-claude/skills/mcp-sequential/SKILL.md +32 -87
- package/plugins/ima-claude/skills/mcp-serena/SKILL.md +54 -80
- package/plugins/ima-claude/skills/mcp-tavily/SKILL.md +40 -63
- package/plugins/ima-claude/skills/mcp-vestige/SKILL.md +75 -116
- package/plugins/ima-claude/skills/php-authnet/SKILL.md +32 -65
- package/plugins/ima-claude/skills/php-fp/SKILL.md +50 -129
- package/plugins/ima-claude/skills/php-fp-wordpress/SKILL.md +25 -73
- package/plugins/ima-claude/skills/phpunit-wp/SKILL.md +103 -463
- package/plugins/ima-claude/skills/playwright/SKILL.md +69 -220
- package/plugins/ima-claude/skills/prompt-starter/SKILL.md +35 -82
- package/plugins/ima-claude/skills/prompt-starter/references/code-review.md +38 -0
- package/plugins/ima-claude/skills/py-fp/SKILL.md +78 -384
- package/plugins/ima-claude/skills/quasar-fp/SKILL.md +54 -255
- package/plugins/ima-claude/skills/quickstart/SKILL.md +7 -11
- package/plugins/ima-claude/skills/rails/SKILL.md +63 -184
- package/plugins/ima-claude/skills/resume-session/SKILL.md +14 -35
- package/plugins/ima-claude/skills/rg/SKILL.md +61 -146
- package/plugins/ima-claude/skills/ruby-fp/SKILL.md +66 -163
- package/plugins/ima-claude/skills/save-session/SKILL.md +10 -39
- package/plugins/ima-claude/skills/scorecard/SKILL.md +24 -38
- package/plugins/ima-claude/skills/skill-analyzer/SKILL.md +42 -71
- package/plugins/ima-claude/skills/skill-creator/SKILL.md +79 -250
- package/plugins/ima-claude/skills/task-master/SKILL.md +11 -31
- package/plugins/ima-claude/skills/task-planner/SKILL.md +44 -153
- package/plugins/ima-claude/skills/task-runner/SKILL.md +61 -143
- package/plugins/ima-claude/skills/unit-testing/SKILL.md +59 -134
- package/plugins/ima-claude/skills/wp-ddev/SKILL.md +38 -120
- package/plugins/ima-claude/skills/wp-local/SKILL.md +26 -108
|
@@ -5,50 +5,25 @@ description: "FP patterns for React with hooks, HOCs, and pure components - refe
|
|
|
5
5
|
|
|
6
6
|
# JavaScript FP - React
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
FP patterns for React 16.8+ (hooks era): business logic in custom hooks, HOCs for DI, appropriate memoization.
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
**Foundation**: Builds on `js-fp` core. See `../js-fp/SKILL.md` for purity, composition, DI, and testing patterns.
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
- Need pure, testable component logic
|
|
14
|
-
- Implementing custom hooks with FP principles
|
|
15
|
-
- HOC patterns for dependency injection
|
|
16
|
-
- Performance optimization without over-engineering
|
|
12
|
+
## Pure Component + Custom Hook Pattern
|
|
17
13
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
**Pure components** with **business logic in custom hooks**, **HOCs for dependency injection**, and **appropriate memoization** (not obsessive).
|
|
21
|
-
|
|
22
|
-
**Foundation**: This skill builds on `js-fp` core principles. Reference `../js-fp/SKILL.md` for purity, composition, dependency injection, and testing patterns.
|
|
23
|
-
|
|
24
|
-
## Pure Component with Custom Hook Pattern
|
|
25
|
-
|
|
26
|
-
**Rule**: Separate business logic (custom hooks) from presentation (components).
|
|
14
|
+
Separate business logic (hooks) from presentation (components).
|
|
27
15
|
|
|
28
16
|
```typescript
|
|
29
17
|
import { memo, useMemo, useCallback } from 'react'
|
|
30
18
|
|
|
31
|
-
interface UserData {
|
|
32
|
-
id: string
|
|
33
|
-
name: string
|
|
34
|
-
email: string
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
interface UserConfig {
|
|
38
|
-
showEmail: boolean
|
|
39
|
-
variant: 'compact' | 'detailed'
|
|
40
|
-
}
|
|
41
|
-
|
|
42
19
|
// ───── Custom hook with pure business logic ─────
|
|
43
20
|
const useUserLogic = (userData: UserData, config: UserConfig) => {
|
|
44
|
-
// Pure computation - no side effects
|
|
45
21
|
const displayData = useMemo(() => ({
|
|
46
22
|
...userData,
|
|
47
23
|
displayName: userData.name.trim(),
|
|
48
24
|
shouldShowEmail: config.showEmail && userData.email
|
|
49
25
|
}), [userData, config])
|
|
50
26
|
|
|
51
|
-
// Pre-compiled handlers
|
|
52
27
|
const handleAction = useCallback((action: string) => ({
|
|
53
28
|
type: 'USER_ACTION',
|
|
54
29
|
payload: { userId: userData.id, action }
|
|
@@ -78,34 +53,24 @@ const UserCard = memo<UserCardProps>(({ userData, config, onAction }) => {
|
|
|
78
53
|
UserCard.displayName = 'UserCard'
|
|
79
54
|
```
|
|
80
55
|
|
|
81
|
-
## HOC for Dependency Injection
|
|
56
|
+
## HOC for Dependency Injection
|
|
82
57
|
|
|
83
|
-
|
|
58
|
+
Inject dependencies via HOCs for testability.
|
|
84
59
|
|
|
85
60
|
```typescript
|
|
86
|
-
// ───── Service interfaces ─────
|
|
87
61
|
interface ServiceDependencies {
|
|
88
|
-
userService: {
|
|
89
|
-
|
|
90
|
-
updateUser: (id: string, data: Partial<UserData>) => Promise<UserData>
|
|
91
|
-
}
|
|
92
|
-
logger: {
|
|
93
|
-
info: (message: string, meta?: any) => void
|
|
94
|
-
error: (message: string, meta?: any) => void
|
|
95
|
-
}
|
|
62
|
+
userService: { getUser: (id: string) => Promise<UserData>; updateUser: (id: string, data: Partial<UserData>) => Promise<UserData> }
|
|
63
|
+
logger: { info: (message: string, meta?: any) => void; error: (message: string, meta?: any) => void }
|
|
96
64
|
}
|
|
97
65
|
|
|
98
|
-
// ───── HOC factory (core: function factories) ─────
|
|
99
66
|
export const withUserService = <P extends object>(
|
|
100
67
|
WrappedComponent: React.ComponentType<P & ServiceDependencies>
|
|
101
68
|
) => {
|
|
102
69
|
const WithUserServiceComponent = (props: P) => {
|
|
103
|
-
// Service injection - can be mocked for testing
|
|
104
70
|
const services: ServiceDependencies = {
|
|
105
71
|
userService: useUserService(),
|
|
106
72
|
logger: useLogger()
|
|
107
73
|
}
|
|
108
|
-
|
|
109
74
|
return <WrappedComponent {...props} {...services} />
|
|
110
75
|
}
|
|
111
76
|
|
|
@@ -115,12 +80,7 @@ export const withUserService = <P extends object>(
|
|
|
115
80
|
return WithUserServiceComponent
|
|
116
81
|
}
|
|
117
82
|
|
|
118
|
-
|
|
119
|
-
const UserProfile = ({
|
|
120
|
-
userId,
|
|
121
|
-
userService,
|
|
122
|
-
logger
|
|
123
|
-
}: { userId: string } & ServiceDependencies) => {
|
|
83
|
+
const UserProfile = ({ userId, userService, logger }: { userId: string } & ServiceDependencies) => {
|
|
124
84
|
const [user, setUser] = useState<UserData | null>(null)
|
|
125
85
|
const [loading, setLoading] = useState(true)
|
|
126
86
|
|
|
@@ -136,57 +96,33 @@ const UserProfile = ({
|
|
|
136
96
|
setLoading(false)
|
|
137
97
|
}
|
|
138
98
|
}
|
|
139
|
-
|
|
140
99
|
loadUser()
|
|
141
100
|
}, [userId, userService, logger])
|
|
142
101
|
|
|
143
102
|
if (loading) return <div>Loading...</div>
|
|
144
103
|
if (!user) return <div>User not found</div>
|
|
145
|
-
|
|
146
104
|
return <UserCard userData={user} config={{ showEmail: true, variant: 'detailed' }} />
|
|
147
105
|
}
|
|
148
106
|
|
|
149
|
-
// ───── Enhanced component with service injection ─────
|
|
150
107
|
export const UserProfileWithServices = withUserService(UserProfile)
|
|
151
108
|
```
|
|
152
109
|
|
|
153
110
|
## Compound Component Pattern
|
|
154
111
|
|
|
155
|
-
|
|
112
|
+
Composition for flexible, reusable component APIs.
|
|
156
113
|
|
|
157
114
|
```typescript
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
// ───── Context for compound component ─────
|
|
161
|
-
interface ModalContextValue {
|
|
162
|
-
isOpen: boolean
|
|
163
|
-
onClose: () => void
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
const ModalContext = createContext<ModalContextValue | null>(null)
|
|
115
|
+
const ModalContext = createContext<{ isOpen: boolean; onClose: () => void } | null>(null)
|
|
167
116
|
|
|
168
117
|
const useModalContext = () => {
|
|
169
118
|
const context = useContext(ModalContext)
|
|
170
|
-
if (!context)
|
|
171
|
-
throw new Error('Modal components must be used within Modal')
|
|
172
|
-
}
|
|
119
|
+
if (!context) throw new Error('Modal components must be used within Modal')
|
|
173
120
|
return context
|
|
174
121
|
}
|
|
175
122
|
|
|
176
|
-
// ───── Main compound component ─────
|
|
177
|
-
interface ModalProps {
|
|
178
|
-
isOpen: boolean
|
|
179
|
-
onClose: () => void
|
|
180
|
-
children: React.ReactNode
|
|
181
|
-
}
|
|
182
|
-
|
|
183
123
|
const Modal = ({ isOpen, onClose, children }: ModalProps) => {
|
|
184
|
-
// Keyboard handling (side effect isolated)
|
|
185
124
|
useEffect(() => {
|
|
186
|
-
const handleEscape = (
|
|
187
|
-
if (event.key === 'Escape') onClose()
|
|
188
|
-
}
|
|
189
|
-
|
|
125
|
+
const handleEscape = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose() }
|
|
190
126
|
if (isOpen) {
|
|
191
127
|
document.addEventListener('keydown', handleEscape)
|
|
192
128
|
return () => document.removeEventListener('keydown', handleEscape)
|
|
@@ -206,148 +142,49 @@ const Modal = ({ isOpen, onClose, children }: ModalProps) => {
|
|
|
206
142
|
)
|
|
207
143
|
}
|
|
208
144
|
|
|
209
|
-
// ───── Compound component parts ─────
|
|
210
145
|
Modal.Header = ({ children }: { children: React.ReactNode }) => {
|
|
211
146
|
const { onClose } = useModalContext()
|
|
212
|
-
|
|
213
|
-
return (
|
|
214
|
-
<div className="modal-header">
|
|
215
|
-
{children}
|
|
216
|
-
<button onClick={onClose} aria-label="Close">×</button>
|
|
217
|
-
</div>
|
|
218
|
-
)
|
|
147
|
+
return <div className="modal-header">{children}<button onClick={onClose} aria-label="Close">×</button></div>
|
|
219
148
|
}
|
|
220
|
-
|
|
221
|
-
Modal.
|
|
222
|
-
<div className="modal-body">{children}</div>
|
|
223
|
-
)
|
|
224
|
-
|
|
225
|
-
Modal.Footer = ({ children }: { children: React.ReactNode }) => (
|
|
226
|
-
<div className="modal-footer">{children}</div>
|
|
227
|
-
)
|
|
228
|
-
|
|
229
|
-
// ───── Usage - composition over configuration ─────
|
|
230
|
-
const UserEditModal = ({ isOpen, onClose, user }: {
|
|
231
|
-
isOpen: boolean
|
|
232
|
-
onClose: () => void
|
|
233
|
-
user: UserData
|
|
234
|
-
}) => (
|
|
235
|
-
<Modal isOpen={isOpen} onClose={onClose}>
|
|
236
|
-
<Modal.Header>Edit User: {user.name}</Modal.Header>
|
|
237
|
-
<Modal.Body>
|
|
238
|
-
<UserEditForm user={user} />
|
|
239
|
-
</Modal.Body>
|
|
240
|
-
<Modal.Footer>
|
|
241
|
-
<button onClick={onClose}>Cancel</button>
|
|
242
|
-
<button type="submit" form="user-edit-form">Save</button>
|
|
243
|
-
</Modal.Footer>
|
|
244
|
-
</Modal>
|
|
245
|
-
)
|
|
149
|
+
Modal.Body = ({ children }: { children: React.ReactNode }) => <div className="modal-body">{children}</div>
|
|
150
|
+
Modal.Footer = ({ children }: { children: React.ReactNode }) => <div className="modal-footer">{children}</div>
|
|
246
151
|
```
|
|
247
152
|
|
|
248
|
-
## Performance
|
|
249
|
-
|
|
250
|
-
**⚠️ IMPORTANT**: Follow core principles - optimize only when needed with evidence.
|
|
153
|
+
## Performance (Evidence-Based Only)
|
|
251
154
|
|
|
252
155
|
```typescript
|
|
253
|
-
// ✅
|
|
254
|
-
const ExpensiveComponent = memo(({ data }: { data: LargeDataSet }) => {
|
|
255
|
-
// Expensive rendering logic
|
|
256
|
-
return <ComplexVisualization data={data} />
|
|
257
|
-
})
|
|
258
|
-
|
|
259
|
-
// ✅ Good - useMemo for expensive computations
|
|
260
|
-
const useExpensiveComputation = (largeDataSet: LargeDataSet) => {
|
|
261
|
-
const result = useMemo(() => {
|
|
262
|
-
return performExpensiveCalculation(largeDataSet) // Only when actually expensive
|
|
263
|
-
}, [largeDataSet])
|
|
264
|
-
|
|
265
|
-
return result
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
// ✅ Good - useCallback to prevent prop changes
|
|
269
|
-
const Parent = () => {
|
|
270
|
-
const [count, setCount] = useState(0)
|
|
271
|
-
|
|
272
|
-
const handleClick = useCallback(() => {
|
|
273
|
-
setCount(c => c + 1)
|
|
274
|
-
}, [])
|
|
275
|
-
|
|
276
|
-
return <MemoizedChild onCount={handleClick} />
|
|
277
|
-
}
|
|
156
|
+
// ✅ memo for genuinely expensive renders
|
|
157
|
+
const ExpensiveComponent = memo(({ data }: { data: LargeDataSet }) => <ComplexVisualization data={data} />)
|
|
278
158
|
|
|
279
|
-
//
|
|
280
|
-
const
|
|
159
|
+
// ✅ useMemo for expensive computations
|
|
160
|
+
const result = useMemo(() => performExpensiveCalculation(largeDataSet), [largeDataSet])
|
|
281
161
|
|
|
282
|
-
//
|
|
283
|
-
const
|
|
284
|
-
const fullName = useMemo(() => `${first} ${last}`, [first, last]) // Overkill
|
|
285
|
-
return <p>{fullName}</p>
|
|
286
|
-
}
|
|
162
|
+
// ✅ useCallback to stabilize props to memoized children
|
|
163
|
+
const handleClick = useCallback(() => setCount(c => c + 1), [])
|
|
287
164
|
|
|
288
|
-
//
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
return <p>{fullName}</p>
|
|
292
|
-
}
|
|
165
|
+
// ❌ memo on trivial components — not needed
|
|
166
|
+
// ❌ useMemo for simple expressions: `${first} ${last}` — just write it inline
|
|
167
|
+
// ❌ useCallback when child isn't memoized — no benefit
|
|
293
168
|
```
|
|
294
169
|
|
|
295
|
-
## Testing
|
|
296
|
-
|
|
297
|
-
### Test Custom Hooks
|
|
170
|
+
## Testing
|
|
298
171
|
|
|
299
172
|
```typescript
|
|
300
|
-
//
|
|
173
|
+
// Hook tests
|
|
301
174
|
import { renderHook } from '@testing-library/react-hooks'
|
|
302
|
-
import { useUserLogic } from '../useUserLogic'
|
|
303
175
|
|
|
304
176
|
describe('useUserLogic', () => {
|
|
305
177
|
it('processes user data correctly', () => {
|
|
306
178
|
const { result } = renderHook(() =>
|
|
307
|
-
useUserLogic(
|
|
308
|
-
{ id: '1', name: ' John ', email: 'john@test.com' },
|
|
309
|
-
{ showEmail: true, variant: 'compact' }
|
|
310
|
-
)
|
|
179
|
+
useUserLogic({ id: '1', name: ' John ', email: 'john@test.com' }, { showEmail: true, variant: 'compact' })
|
|
311
180
|
)
|
|
312
|
-
|
|
313
181
|
expect(result.current.displayData.displayName).toBe('John')
|
|
314
|
-
expect(result.current.displayData.
|
|
315
|
-
})
|
|
316
|
-
|
|
317
|
-
it('hides email when config.showEmail is false', () => {
|
|
318
|
-
const { result } = renderHook(() =>
|
|
319
|
-
useUserLogic(
|
|
320
|
-
{ id: '1', name: 'John', email: 'john@test.com' },
|
|
321
|
-
{ showEmail: false, variant: 'compact' }
|
|
322
|
-
)
|
|
323
|
-
)
|
|
324
|
-
|
|
325
|
-
expect(result.current.displayData.shouldShowEmail).toBe(false)
|
|
182
|
+
expect(result.current.displayData.shouldShowEmail).toBe(true)
|
|
326
183
|
})
|
|
327
184
|
})
|
|
328
|
-
```
|
|
329
|
-
|
|
330
|
-
### Test Components with React Testing Library
|
|
331
|
-
|
|
332
|
-
```typescript
|
|
333
|
-
// __tests__/UserCard.test.tsx
|
|
334
|
-
import { render, screen, userEvent } from '@testing-library/react'
|
|
335
|
-
import UserCard from '../UserCard'
|
|
336
185
|
|
|
186
|
+
// Component tests
|
|
337
187
|
describe('UserCard', () => {
|
|
338
|
-
it('renders user data correctly', () => {
|
|
339
|
-
render(
|
|
340
|
-
<UserCard
|
|
341
|
-
userData={{ id: '1', name: 'John', email: 'john@test.com' }}
|
|
342
|
-
config={{ showEmail: true, variant: 'compact' }}
|
|
343
|
-
onAction={jest.fn()}
|
|
344
|
-
/>
|
|
345
|
-
)
|
|
346
|
-
|
|
347
|
-
expect(screen.getByText('John')).toBeInTheDocument()
|
|
348
|
-
expect(screen.getByText('john@test.com')).toBeInTheDocument()
|
|
349
|
-
})
|
|
350
|
-
|
|
351
188
|
it('calls onAction when button clicked', async () => {
|
|
352
189
|
const onAction = jest.fn()
|
|
353
190
|
render(
|
|
@@ -357,91 +194,31 @@ describe('UserCard', () => {
|
|
|
357
194
|
onAction={onAction}
|
|
358
195
|
/>
|
|
359
196
|
)
|
|
360
|
-
|
|
361
197
|
await userEvent.click(screen.getByRole('button'))
|
|
362
198
|
expect(onAction).toHaveBeenCalled()
|
|
363
199
|
})
|
|
364
200
|
})
|
|
365
201
|
```
|
|
366
202
|
|
|
367
|
-
## Anti-Patterns
|
|
368
|
-
|
|
369
|
-
### ❌ Overusing Context
|
|
370
|
-
|
|
371
|
-
```typescript
|
|
372
|
-
// ❌ Context for local state
|
|
373
|
-
const UserContext = createContext<UserData | null>(null)
|
|
374
|
-
|
|
375
|
-
// ✅ Props for local state (simpler)
|
|
376
|
-
<UserCard userData={user} />
|
|
377
|
-
```
|
|
378
|
-
|
|
379
|
-
### ❌ Premature Memoization
|
|
203
|
+
## Anti-Patterns
|
|
380
204
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
const c = useCallback(() => {}, []) // Overkill when passed to non-memoized children
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
// ✅ Memoize only when needed
|
|
390
|
-
const Component = () => {
|
|
391
|
-
const a = 2 // Simple calculation
|
|
392
|
-
const b = 'hello' // Simple value
|
|
393
|
-
const c = () => {} // Only memo if passed to memoized child
|
|
394
|
-
}
|
|
395
|
-
```
|
|
205
|
+
| Pattern | Avoid | Use Instead |
|
|
206
|
+
|---------|-------|-------------|
|
|
207
|
+
| Context for local state | `createContext` + provider for 2-component data | Props |
|
|
208
|
+
| Premature memoization | `useMemo(() => 1 + 1, [])` | Direct computation |
|
|
209
|
+
| `useCallback` on non-memoized children | Adds overhead, no benefit | Plain function |
|
|
396
210
|
|
|
397
211
|
## Quality Gates
|
|
398
212
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
5. ✅ **Testability**: Can inject mocks for all dependencies?
|
|
406
|
-
6. ✅ **FP principles**: Pure functions, immutable updates?
|
|
407
|
-
7. ✅ **Performance**: Optimized without over-engineering?
|
|
408
|
-
|
|
409
|
-
## When to Load Additional Content
|
|
410
|
-
|
|
411
|
-
### Hooks Advanced
|
|
412
|
-
**File**: `references/hooks-advanced.md`
|
|
413
|
-
**When**: Complex custom hooks, advanced patterns
|
|
414
|
-
**Contains**: Hook composition, state machines, async patterns, effect isolation
|
|
415
|
-
|
|
416
|
-
### Performance Patterns
|
|
417
|
-
**File**: `references/performance-patterns.md`
|
|
418
|
-
**When**: Performance optimization needed, large lists
|
|
419
|
-
**Contains**: React.memo strategies, virtualization, code splitting
|
|
420
|
-
|
|
421
|
-
### Working Examples
|
|
422
|
-
**File**: `examples/ProductCard.tsx`
|
|
423
|
-
**When**: Need complete working component example
|
|
424
|
-
**Contains**: Full ProductCard component with custom hook, types, and exports
|
|
425
|
-
|
|
426
|
-
## Foundation Reference
|
|
427
|
-
|
|
428
|
-
**Core FP Principles**: `../js-fp/SKILL.md`
|
|
429
|
-
- Purity and side effect isolation
|
|
430
|
-
- Composition patterns
|
|
431
|
-
- Dependency injection
|
|
432
|
-
- Immutability
|
|
433
|
-
- Testing strategies
|
|
434
|
-
|
|
435
|
-
**Deep Dive**: `../js-fp/core-principles.md` for complete FP philosophy
|
|
436
|
-
|
|
437
|
-
## Success Metrics
|
|
438
|
-
|
|
439
|
-
- **Testability**: 100% testable custom hooks
|
|
440
|
-
- **Performance**: Appropriate memoization, sub-100ms renders
|
|
441
|
-
- **Maintainability**: Clear separation of concerns
|
|
442
|
-
- **Code Quality**: Simple, readable component logic
|
|
443
|
-
- **Bundle Size**: Tree-shakeable, minimal overhead
|
|
213
|
+
1. Business logic in custom hook, not component body?
|
|
214
|
+
2. Dependencies injected via HOC (not imported directly)?
|
|
215
|
+
3. `memo`/`useMemo`/`useCallback` only where evidence of need?
|
|
216
|
+
4. Compound components for flexible APIs?
|
|
217
|
+
5. All hooks testable without rendering full component tree?
|
|
218
|
+
6. Immutable state updates throughout?
|
|
444
219
|
|
|
445
|
-
##
|
|
220
|
+
## References
|
|
446
221
|
|
|
447
|
-
|
|
222
|
+
- `references/hooks-advanced.md` — complex hooks, state machines, async patterns
|
|
223
|
+
- `references/performance-patterns.md` — React.memo strategies, virtualization, code splitting
|
|
224
|
+
- `examples/ProductCard.tsx` — complete working component with custom hook
|