metacoding 1.0.0 → 1.1.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 (46) hide show
  1. package/CHANGELOG.md +68 -43
  2. package/LICENSE +1 -1
  3. package/lib/services/template-manager.d.ts +3 -0
  4. package/lib/services/template-manager.d.ts.map +1 -1
  5. package/lib/services/template-manager.js +126 -9
  6. package/lib/services/template-manager.js.map +1 -1
  7. package/lib/services/vscode.js +1 -1
  8. package/lib/services/vscode.js.map +1 -1
  9. package/package.json +4 -4
  10. package/templates/general/code-review.instructions.md +265 -0
  11. package/templates/general/{files/copilot-instructions.md.template → copilot-instructions.md} +97 -140
  12. package/templates/{python/files → general}/docs-update.instructions.md +45 -32
  13. package/templates/general/release.instructions.md +242 -0
  14. package/templates/general/test-runner.instructions.md +188 -0
  15. package/templates/node/nodejs.coding.instructions.md +249 -0
  16. package/templates/node/nodejs.docs.instructions.md +234 -0
  17. package/templates/node/nodejs.testing.instructions.md +373 -0
  18. package/templates/python/python.coding.instructions.md +339 -0
  19. package/templates/python/python.docs.instructions.md +1147 -0
  20. package/templates/python/python.testing.instructions.md +1074 -0
  21. package/templates/react/react.coding.instructions.md +695 -0
  22. package/templates/react/react.docs.instructions.md +427 -0
  23. package/templates/react/react.testing.instructions.md +193 -0
  24. package/templates/react/test-runner.instructions.md +135 -0
  25. package/templates/typescript/template.json +16 -0
  26. package/templates/typescript/typescript.coding.instructions.md +368 -0
  27. package/templates/typescript/typescript.docs.instructions.md +734 -0
  28. package/templates/typescript/typescript.testing.instructions.md +740 -0
  29. package/templates/general/files/code-review.instructions.md +0 -111
  30. package/templates/general/files/docs-update.instructions.md +0 -203
  31. package/templates/general/files/release.instructions.md +0 -72
  32. package/templates/general/files/test-runner.instructions.md +0 -107
  33. package/templates/node/files/code-review.instructions.md +0 -222
  34. package/templates/node/files/copilot-instructions.md.template +0 -391
  35. package/templates/node/files/docs-update.instructions.md +0 -203
  36. package/templates/node/files/release.instructions.md +0 -72
  37. package/templates/node/files/test-runner.instructions.md +0 -108
  38. package/templates/python/files/code-review.instructions.md +0 -215
  39. package/templates/python/files/copilot-instructions.md.template +0 -418
  40. package/templates/python/files/release.instructions.md +0 -72
  41. package/templates/python/files/test-runner.instructions.md +0 -108
  42. package/templates/react/files/code-review.instructions.md +0 -160
  43. package/templates/react/files/copilot-instructions.md.template +0 -472
  44. package/templates/react/files/docs-update.instructions.md +0 -203
  45. package/templates/react/files/release.instructions.md +0 -72
  46. package/templates/react/files/test-runner.instructions.md +0 -108
@@ -0,0 +1,695 @@
1
+ ---
2
+ description: 'React/Frontend-specific coding standards and best practices'
3
+ applyTo: '**/*.{tsx,jsx,ts,js}'
4
+ language: 'react'
5
+ ---
6
+
7
+ # React/Frontend Coding Standards and Best Practices
8
+
9
+ ## Language and Framework Preferences
10
+
11
+ - **Primary Language:** TypeScript with React 18+ for all React projects
12
+ - **Build Tool:** Vite for development and build tooling
13
+ - **Code Style:** Prettier with ESLint for consistent formatting and linting
14
+ - **State Management:** Context API for simple state, Zustand/Redux Toolkit for complex state
15
+ - **Target Compatibility:** Modern browsers (ES2020+), Node.js 18+
16
+
17
+ ## Code Quality Guidelines
18
+
19
+ - **Component Design:** Small, focused components with single responsibilities
20
+ - **Functions:** Prefer functional components with hooks over class components
21
+ - **Performance:** Use React.memo, useMemo, and useCallback judiciously
22
+ - **Error Handling:** Implement error boundaries and proper error handling
23
+ - **Accessibility:** Follow WCAG guidelines and use semantic HTML
24
+ - **Type Safety:** Strict TypeScript configuration with comprehensive type coverage
25
+
26
+ ## Naming Conventions
27
+
28
+ - **Files:** PascalCase for components (e.g., `UserProfile.tsx`), camelCase for utilities (e.g., `apiHelpers.ts`)
29
+ - **Components:** PascalCase (e.g., `UserCard`, `NavigationMenu`)
30
+ - **Functions/Hooks:** camelCase (e.g., `useUserData`, `handleSubmit`)
31
+ - **Variables:** camelCase (e.g., `userData`, `isLoading`)
32
+ - **Constants:** SCREAMING_SNAKE_CASE (e.g., `API_ENDPOINTS`, `DEFAULT_TIMEOUT`)
33
+ - **Interfaces/Types:** PascalCase with descriptive names (e.g., `UserData`, `ApiResponse`)
34
+ - **CSS Classes:** kebab-case (e.g., `user-card`, `navigation-menu`)
35
+
36
+ ## Code Organization
37
+
38
+ - **Feature-Based Structure:** Organize by features rather than file types
39
+ - **Component Co-location:** Keep related files (component, styles, tests) together
40
+ - **Barrel Exports:** Use index.ts files for clean imports
41
+ - **Separation of Concerns:** Separate business logic from presentation logic
42
+
43
+ ### Recommended Project Structure
44
+
45
+ ```
46
+ src/
47
+ components/ # Reusable UI components
48
+ ui/ # Basic UI primitives (Button, Input, Modal)
49
+ layout/ # Layout components (Header, Sidebar, Footer)
50
+ features/ # Feature-specific components and logic
51
+ auth/ # Authentication feature
52
+ dashboard/ # Dashboard feature
53
+ hooks/ # Custom React hooks
54
+ services/ # API calls and external services
55
+ utils/ # Utility functions
56
+ types/ # TypeScript type definitions
57
+ stores/ # State management (Context, Zustand, etc.)
58
+ assets/ # Static assets (images, icons, fonts)
59
+ ```
60
+
61
+ ## React-Specific Best Practices
62
+
63
+ ### Component Design Patterns
64
+
65
+ ```tsx
66
+ // Good: Functional component with TypeScript
67
+ interface UserCardProps {
68
+ user: User;
69
+ onEdit?: (user: User) => void;
70
+ className?: string;
71
+ }
72
+
73
+ export const UserCard: React.FC<UserCardProps> = ({
74
+ user,
75
+ onEdit,
76
+ className,
77
+ }) => {
78
+ const handleEditClick = useCallback(() => {
79
+ onEdit?.(user);
80
+ }, [user, onEdit]);
81
+
82
+ return (
83
+ <div className={`user-card ${className || ''}`}>
84
+ <h3>{user.name}</h3>
85
+ <p>{user.email}</p>
86
+ {onEdit && <button onClick={handleEditClick}>Edit</button>}
87
+ </div>
88
+ );
89
+ };
90
+ ```
91
+
92
+ ### Custom Hooks
93
+
94
+ ```tsx
95
+ // Good: Custom hook for data fetching
96
+ interface UseUserDataResult {
97
+ user: User | null;
98
+ loading: boolean;
99
+ error: string | null;
100
+ refetch: () => void;
101
+ }
102
+
103
+ export const useUserData = (userId: string): UseUserDataResult => {
104
+ const [user, setUser] = useState<User | null>(null);
105
+ const [loading, setLoading] = useState(true);
106
+ const [error, setError] = useState<string | null>(null);
107
+
108
+ const fetchUser = useCallback(async () => {
109
+ try {
110
+ setLoading(true);
111
+ setError(null);
112
+ const userData = await userService.getUser(userId);
113
+ setUser(userData);
114
+ } catch (err) {
115
+ setError(err instanceof Error ? err.message : 'Unknown error');
116
+ } finally {
117
+ setLoading(false);
118
+ }
119
+ }, [userId]);
120
+
121
+ useEffect(() => {
122
+ fetchUser();
123
+ }, [fetchUser]);
124
+
125
+ return { user, loading, error, refetch: fetchUser };
126
+ };
127
+ ```
128
+
129
+ ### State Management Patterns
130
+
131
+ ```tsx
132
+ // Good: Context for global state
133
+ interface AppContextType {
134
+ user: User | null;
135
+ theme: 'light' | 'dark';
136
+ setUser: (user: User | null) => void;
137
+ setTheme: (theme: 'light' | 'dark') => void;
138
+ }
139
+
140
+ const AppContext = createContext<AppContextType | undefined>(undefined);
141
+
142
+ export const useAppContext = (): AppContextType => {
143
+ const context = useContext(AppContext);
144
+ if (!context) {
145
+ throw new Error('useAppContext must be used within AppProvider');
146
+ }
147
+ return context;
148
+ };
149
+
150
+ export const AppProvider: React.FC<{ children: React.ReactNode }> = ({
151
+ children,
152
+ }) => {
153
+ const [user, setUser] = useState<User | null>(null);
154
+ const [theme, setTheme] = useState<'light' | 'dark'>('light');
155
+
156
+ const value = useMemo(
157
+ () => ({
158
+ user,
159
+ theme,
160
+ setUser,
161
+ setTheme,
162
+ }),
163
+ [user, theme]
164
+ );
165
+
166
+ return <AppContext.Provider value={value}>{children}</AppContext.Provider>;
167
+ };
168
+ ```
169
+
170
+ ## Performance Optimization
171
+
172
+ ### Component Optimization
173
+
174
+ ```tsx
175
+ // Good: Memoized component to prevent unnecessary re-renders
176
+ interface UserListProps {
177
+ users: User[];
178
+ onUserSelect: (user: User) => void;
179
+ }
180
+
181
+ export const UserList = React.memo<UserListProps>(({ users, onUserSelect }) => {
182
+ return (
183
+ <div className="user-list">
184
+ {users.map((user) => (
185
+ <UserCard key={user.id} user={user} onEdit={onUserSelect} />
186
+ ))}
187
+ </div>
188
+ );
189
+ });
190
+
191
+ UserList.displayName = 'UserList';
192
+ ```
193
+
194
+ ### Optimized Callbacks and Values
195
+
196
+ ```tsx
197
+ // Good: Memoized callbacks and computed values
198
+ export const Dashboard: React.FC = () => {
199
+ const [filter, setFilter] = useState('');
200
+ const [users, setUsers] = useState<User[]>([]);
201
+
202
+ // Memoize expensive computations
203
+ const filteredUsers = useMemo(() => {
204
+ return users.filter((user) =>
205
+ user.name.toLowerCase().includes(filter.toLowerCase())
206
+ );
207
+ }, [users, filter]);
208
+
209
+ // Memoize callbacks to prevent child re-renders
210
+ const handleUserSelect = useCallback((user: User) => {
211
+ console.log('Selected user:', user);
212
+ }, []);
213
+
214
+ const handleFilterChange = useCallback(
215
+ (event: React.ChangeEvent<HTMLInputElement>) => {
216
+ setFilter(event.target.value);
217
+ },
218
+ []
219
+ );
220
+
221
+ return (
222
+ <div>
223
+ <input
224
+ value={filter}
225
+ onChange={handleFilterChange}
226
+ placeholder="Filter users..."
227
+ />
228
+ <UserList users={filteredUsers} onUserSelect={handleUserSelect} />
229
+ </div>
230
+ );
231
+ };
232
+ ```
233
+
234
+ ## Error Handling and Validation
235
+
236
+ ### Error Boundaries
237
+
238
+ ```tsx
239
+ interface ErrorBoundaryState {
240
+ hasError: boolean;
241
+ error?: Error;
242
+ }
243
+
244
+ class ErrorBoundary extends React.Component<
245
+ React.PropsWithChildren<{}>,
246
+ ErrorBoundaryState
247
+ > {
248
+ constructor(props: React.PropsWithChildren<{}>) {
249
+ super(props);
250
+ this.state = { hasError: false };
251
+ }
252
+
253
+ static getDerivedStateFromError(error: Error): ErrorBoundaryState {
254
+ return { hasError: true, error };
255
+ }
256
+
257
+ componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
258
+ console.error('Error caught by boundary:', error, errorInfo);
259
+ }
260
+
261
+ render() {
262
+ if (this.state.hasError) {
263
+ return (
264
+ <div className="error-boundary">
265
+ <h2>Something went wrong</h2>
266
+ <p>{this.state.error?.message}</p>
267
+ </div>
268
+ );
269
+ }
270
+
271
+ return this.props.children;
272
+ }
273
+ }
274
+ ```
275
+
276
+ ### Form Validation
277
+
278
+ ```tsx
279
+ // Good: Form with validation using react-hook-form
280
+ import { useForm } from 'react-hook-form';
281
+ import { zodResolver } from '@hookform/resolvers/zod';
282
+ import { z } from 'zod';
283
+
284
+ const userSchema = z.object({
285
+ name: z.string().min(2, 'Name must be at least 2 characters'),
286
+ email: z.string().email('Invalid email address'),
287
+ age: z.number().min(18, 'Must be at least 18 years old'),
288
+ });
289
+
290
+ type UserFormData = z.infer<typeof userSchema>;
291
+
292
+ export const UserForm: React.FC = () => {
293
+ const {
294
+ register,
295
+ handleSubmit,
296
+ formState: { errors, isSubmitting },
297
+ } = useForm<UserFormData>({
298
+ resolver: zodResolver(userSchema),
299
+ });
300
+
301
+ const onSubmit = async (data: UserFormData) => {
302
+ try {
303
+ await userService.createUser(data);
304
+ } catch (error) {
305
+ console.error('Failed to create user:', error);
306
+ }
307
+ };
308
+
309
+ return (
310
+ <form onSubmit={handleSubmit(onSubmit)}>
311
+ <div>
312
+ <input {...register('name')} placeholder="Name" />
313
+ {errors.name && <p className="error">{errors.name.message}</p>}
314
+ </div>
315
+
316
+ <div>
317
+ <input {...register('email')} placeholder="Email" />
318
+ {errors.email && <p className="error">{errors.email.message}</p>}
319
+ </div>
320
+
321
+ <div>
322
+ <input
323
+ {...register('age', { valueAsNumber: true })}
324
+ type="number"
325
+ placeholder="Age"
326
+ />
327
+ {errors.age && <p className="error">{errors.age.message}</p>}
328
+ </div>
329
+
330
+ <button type="submit" disabled={isSubmitting}>
331
+ {isSubmitting ? 'Creating...' : 'Create User'}
332
+ </button>
333
+ </form>
334
+ );
335
+ };
336
+ ```
337
+
338
+ ## Testing Standards
339
+
340
+ ### Component Testing
341
+
342
+ ```tsx
343
+ // Good: Component testing with React Testing Library
344
+ import { render, screen, fireEvent } from '@testing-library/react';
345
+ import { UserCard } from './UserCard';
346
+
347
+ const mockUser: User = {
348
+ id: '1',
349
+ name: 'John Doe',
350
+ email: 'john@example.com',
351
+ };
352
+
353
+ describe('UserCard', () => {
354
+ it('renders user information correctly', () => {
355
+ render(<UserCard user={mockUser} />);
356
+
357
+ expect(screen.getByText('John Doe')).toBeInTheDocument();
358
+ expect(screen.getByText('john@example.com')).toBeInTheDocument();
359
+ });
360
+
361
+ it('calls onEdit when edit button is clicked', () => {
362
+ const mockOnEdit = jest.fn();
363
+ render(<UserCard user={mockUser} onEdit={mockOnEdit} />);
364
+
365
+ const editButton = screen.getByText('Edit');
366
+ fireEvent.click(editButton);
367
+
368
+ expect(mockOnEdit).toHaveBeenCalledWith(mockUser);
369
+ });
370
+
371
+ it('does not render edit button when onEdit is not provided', () => {
372
+ render(<UserCard user={mockUser} />);
373
+
374
+ expect(screen.queryByText('Edit')).not.toBeInTheDocument();
375
+ });
376
+ });
377
+ ```
378
+
379
+ ### Hook Testing
380
+
381
+ ```tsx
382
+ // Good: Custom hook testing
383
+ import { renderHook, waitFor } from '@testing-library/react';
384
+ import { useUserData } from './useUserData';
385
+
386
+ // Mock the service
387
+ jest.mock('../services/userService');
388
+
389
+ describe('useUserData', () => {
390
+ beforeEach(() => {
391
+ jest.clearAllMocks();
392
+ });
393
+
394
+ it('fetches user data successfully', async () => {
395
+ const mockUser = { id: '1', name: 'John Doe' };
396
+ (userService.getUser as jest.Mock).mockResolvedValue(mockUser);
397
+
398
+ const { result } = renderHook(() => useUserData('1'));
399
+
400
+ expect(result.current.loading).toBe(true);
401
+ expect(result.current.user).toBe(null);
402
+
403
+ await waitFor(() => {
404
+ expect(result.current.loading).toBe(false);
405
+ });
406
+
407
+ expect(result.current.user).toEqual(mockUser);
408
+ expect(result.current.error).toBe(null);
409
+ });
410
+
411
+ it('handles error states correctly', async () => {
412
+ const errorMessage = 'User not found';
413
+ (userService.getUser as jest.Mock).mockRejectedValue(
414
+ new Error(errorMessage)
415
+ );
416
+
417
+ const { result } = renderHook(() => useUserData('1'));
418
+
419
+ await waitFor(() => {
420
+ expect(result.current.loading).toBe(false);
421
+ });
422
+
423
+ expect(result.current.user).toBe(null);
424
+ expect(result.current.error).toBe(errorMessage);
425
+ });
426
+ });
427
+ ```
428
+
429
+ ## Accessibility Standards
430
+
431
+ ### Semantic HTML and ARIA
432
+
433
+ ```tsx
434
+ // Good: Accessible form component
435
+ export const AccessibleForm: React.FC = () => {
436
+ const [fieldError, setFieldError] = useState<string>('');
437
+
438
+ return (
439
+ <form role="form" aria-label="User registration form">
440
+ <fieldset>
441
+ <legend>Personal Information</legend>
442
+
443
+ <div className="form-group">
444
+ <label htmlFor="username">Username:</label>
445
+ <input
446
+ id="username"
447
+ type="text"
448
+ aria-required="true"
449
+ aria-describedby={fieldError ? 'username-error' : undefined}
450
+ aria-invalid={fieldError ? 'true' : 'false'}
451
+ />
452
+ {fieldError && (
453
+ <div
454
+ id="username-error"
455
+ role="alert"
456
+ aria-live="polite"
457
+ className="error-message"
458
+ >
459
+ {fieldError}
460
+ </div>
461
+ )}
462
+ </div>
463
+ </fieldset>
464
+
465
+ <button type="submit" aria-describedby="submit-help">
466
+ Register
467
+ </button>
468
+ <div id="submit-help" className="help-text">
469
+ Click to create your account
470
+ </div>
471
+ </form>
472
+ );
473
+ };
474
+ ```
475
+
476
+ ### Keyboard Navigation
477
+
478
+ ```tsx
479
+ // Good: Keyboard accessible dropdown
480
+ export const Dropdown: React.FC<DropdownProps> = ({ options, onSelect }) => {
481
+ const [isOpen, setIsOpen] = useState(false);
482
+ const [activeIndex, setActiveIndex] = useState(-1);
483
+
484
+ const handleKeyDown = (event: React.KeyboardEvent) => {
485
+ switch (event.key) {
486
+ case 'Enter':
487
+ case ' ':
488
+ event.preventDefault();
489
+ if (activeIndex >= 0) {
490
+ onSelect(options[activeIndex]);
491
+ }
492
+ setIsOpen(!isOpen);
493
+ break;
494
+ case 'ArrowDown':
495
+ event.preventDefault();
496
+ setActiveIndex((prev) => (prev < options.length - 1 ? prev + 1 : 0));
497
+ break;
498
+ case 'ArrowUp':
499
+ event.preventDefault();
500
+ setActiveIndex((prev) => (prev > 0 ? prev - 1 : options.length - 1));
501
+ break;
502
+ case 'Escape':
503
+ setIsOpen(false);
504
+ setActiveIndex(-1);
505
+ break;
506
+ }
507
+ };
508
+
509
+ return (
510
+ <div className="dropdown" onKeyDown={handleKeyDown}>
511
+ <button
512
+ aria-haspopup="listbox"
513
+ aria-expanded={isOpen}
514
+ onClick={() => setIsOpen(!isOpen)}
515
+ >
516
+ Select option
517
+ </button>
518
+ {isOpen && (
519
+ <ul role="listbox" className="dropdown-menu">
520
+ {options.map((option, index) => (
521
+ <li
522
+ key={option.id}
523
+ role="option"
524
+ aria-selected={index === activeIndex}
525
+ className={index === activeIndex ? 'active' : ''}
526
+ onClick={() => onSelect(option)}
527
+ >
528
+ {option.label}
529
+ </li>
530
+ ))}
531
+ </ul>
532
+ )}
533
+ </div>
534
+ );
535
+ };
536
+ ```
537
+
538
+ ## Styling and CSS Standards
539
+
540
+ ### CSS-in-JS with Styled Components
541
+
542
+ ```tsx
543
+ import styled from 'styled-components';
544
+
545
+ // Good: Styled component with theme support
546
+ const Button = styled.button<{ variant?: 'primary' | 'secondary' }>`
547
+ padding: ${({ theme }) => theme.spacing.md};
548
+ border: none;
549
+ border-radius: ${({ theme }) => theme.borderRadius.sm};
550
+ font-weight: 500;
551
+ cursor: pointer;
552
+ transition: all 0.2s ease;
553
+
554
+ ${({ variant = 'primary', theme }) => {
555
+ switch (variant) {
556
+ case 'primary':
557
+ return `
558
+ background-color: ${theme.colors.primary};
559
+ color: ${theme.colors.white};
560
+
561
+ &:hover {
562
+ background-color: ${theme.colors.primaryDark};
563
+ }
564
+ `;
565
+ case 'secondary':
566
+ return `
567
+ background-color: ${theme.colors.secondary};
568
+ color: ${theme.colors.text};
569
+
570
+ &:hover {
571
+ background-color: ${theme.colors.secondaryDark};
572
+ }
573
+ `;
574
+ }
575
+ }}
576
+
577
+ &:disabled {
578
+ opacity: 0.6;
579
+ cursor: not-allowed;
580
+ }
581
+ `;
582
+ ```
583
+
584
+ ### CSS Modules
585
+
586
+ ```tsx
587
+ // UserCard.module.css
588
+ .userCard {
589
+ border: 1px solid #e0e0e0;
590
+ border-radius: 8px;
591
+ padding: 16px;
592
+ margin-bottom: 16px;
593
+ }
594
+
595
+ .userCard:hover {
596
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
597
+ }
598
+
599
+ .userInfo {
600
+ margin-bottom: 12px;
601
+ }
602
+
603
+ .userName {
604
+ font-size: 1.2em;
605
+ font-weight: bold;
606
+ margin-bottom: 4px;
607
+ }
608
+
609
+ // UserCard.tsx
610
+ import styles from './UserCard.module.css';
611
+
612
+ export const UserCard: React.FC<UserCardProps> = ({ user }) => {
613
+ return (
614
+ <div className={styles.userCard}>
615
+ <div className={styles.userInfo}>
616
+ <h3 className={styles.userName}>{user.name}</h3>
617
+ <p>{user.email}</p>
618
+ </div>
619
+ </div>
620
+ );
621
+ };
622
+ ```
623
+
624
+ ## Common Anti-Patterns to Avoid
625
+
626
+ - **Inline Styles for Complex Styling:** Use CSS-in-JS or CSS modules instead
627
+ - **Prop Drilling:** Use Context or state management for deeply nested props
628
+ - **Mutating Props:** Always treat props as read-only
629
+ - **Missing Keys in Lists:** Always provide unique keys for list items
630
+ - **Not Cleaning Up Effects:** Remove event listeners and cancel async operations
631
+ - **Using Index as Key:** Use stable, unique identifiers for keys
632
+ - **Overusing useEffect:** Consider if the effect is really necessary
633
+ - **Not Memoizing Expensive Calculations:** Use useMemo for expensive computations
634
+ - **Creating Objects/Functions in Render:** This causes unnecessary re-renders
635
+ - **Ignoring Accessibility:** Always consider screen readers and keyboard navigation
636
+
637
+ ## Security Considerations
638
+
639
+ ### XSS Prevention
640
+
641
+ ```tsx
642
+ // Good: Safe rendering of user content
643
+ import DOMPurify from 'dompurify';
644
+
645
+ interface SafeHtmlProps {
646
+ content: string;
647
+ }
648
+
649
+ export const SafeHtml: React.FC<SafeHtmlProps> = ({ content }) => {
650
+ const sanitizedContent = useMemo(() => {
651
+ return DOMPurify.sanitize(content);
652
+ }, [content]);
653
+
654
+ return <div dangerouslySetInnerHTML={{ __html: sanitizedContent }} />;
655
+ };
656
+
657
+ // Good: Escape user input in URL parameters
658
+ const SearchResults: React.FC = () => {
659
+ const query = useSearchParams().get('q') || '';
660
+ const encodedQuery = encodeURIComponent(query);
661
+
662
+ return (
663
+ <div>
664
+ <h2>Results for: {query}</h2>
665
+ <a href={`/search/advanced?query=${encodedQuery}`}>Advanced search</a>
666
+ </div>
667
+ );
668
+ };
669
+ ```
670
+
671
+ ### Environment Variables
672
+
673
+ ```tsx
674
+ // Good: Environment configuration
675
+ interface Config {
676
+ apiUrl: string;
677
+ environment: 'development' | 'staging' | 'production';
678
+ enableAnalytics: boolean;
679
+ }
680
+
681
+ const config: Config = {
682
+ apiUrl: import.meta.env.VITE_API_URL || 'http://localhost:3000',
683
+ environment:
684
+ (import.meta.env.VITE_ENVIRONMENT as Config['environment']) ||
685
+ 'development',
686
+ enableAnalytics: import.meta.env.VITE_ENABLE_ANALYTICS === 'true',
687
+ };
688
+
689
+ // Validate required environment variables
690
+ if (!config.apiUrl) {
691
+ throw new Error('VITE_API_URL environment variable is required');
692
+ }
693
+
694
+ export default config;
695
+ ```