@react-spa-scaffold/mcp 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (173) hide show
  1. package/README.md +423 -0
  2. package/dist/features/index.d.ts +5 -0
  3. package/dist/features/index.d.ts.map +1 -0
  4. package/dist/features/index.js +3 -0
  5. package/dist/features/index.js.map +1 -0
  6. package/dist/features/registry.d.ts +10 -0
  7. package/dist/features/registry.d.ts.map +1 -0
  8. package/dist/features/registry.js +508 -0
  9. package/dist/features/registry.js.map +1 -0
  10. package/dist/features/types.d.ts +45 -0
  11. package/dist/features/types.d.ts.map +1 -0
  12. package/dist/features/types.js +5 -0
  13. package/dist/features/types.js.map +1 -0
  14. package/dist/features/versions.d.ts +16 -0
  15. package/dist/features/versions.d.ts.map +1 -0
  16. package/dist/features/versions.js +46 -0
  17. package/dist/features/versions.js.map +1 -0
  18. package/dist/features/versions.json +5 -0
  19. package/dist/index.d.ts +22 -0
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/index.js +43 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/resources/docs.d.ts +29 -0
  24. package/dist/resources/docs.d.ts.map +1 -0
  25. package/dist/resources/docs.js +105 -0
  26. package/dist/resources/docs.js.map +1 -0
  27. package/dist/resources/index.d.ts +2 -0
  28. package/dist/resources/index.d.ts.map +1 -0
  29. package/dist/resources/index.js +2 -0
  30. package/dist/resources/index.js.map +1 -0
  31. package/dist/server.d.ts +12 -0
  32. package/dist/server.d.ts.map +1 -0
  33. package/dist/server.js +115 -0
  34. package/dist/server.js.map +1 -0
  35. package/dist/tools/get-example.d.ts +51 -0
  36. package/dist/tools/get-example.d.ts.map +1 -0
  37. package/dist/tools/get-example.js +90 -0
  38. package/dist/tools/get-example.js.map +1 -0
  39. package/dist/tools/get-features.d.ts +30 -0
  40. package/dist/tools/get-features.d.ts.map +1 -0
  41. package/dist/tools/get-features.js +46 -0
  42. package/dist/tools/get-features.js.map +1 -0
  43. package/dist/tools/get-scaffold.d.ts +77 -0
  44. package/dist/tools/get-scaffold.d.ts.map +1 -0
  45. package/dist/tools/get-scaffold.js +153 -0
  46. package/dist/tools/get-scaffold.js.map +1 -0
  47. package/dist/tools/index.d.ts +4 -0
  48. package/dist/tools/index.d.ts.map +1 -0
  49. package/dist/tools/index.js +4 -0
  50. package/dist/tools/index.js.map +1 -0
  51. package/dist/utils/docs.d.ts +14 -0
  52. package/dist/utils/docs.d.ts.map +1 -0
  53. package/dist/utils/docs.js +64 -0
  54. package/dist/utils/docs.js.map +1 -0
  55. package/dist/utils/examples.d.ts +27 -0
  56. package/dist/utils/examples.d.ts.map +1 -0
  57. package/dist/utils/examples.js +399 -0
  58. package/dist/utils/examples.js.map +1 -0
  59. package/dist/utils/index.d.ts +5 -0
  60. package/dist/utils/index.d.ts.map +1 -0
  61. package/dist/utils/index.js +5 -0
  62. package/dist/utils/index.js.map +1 -0
  63. package/dist/utils/paths.d.ts +28 -0
  64. package/dist/utils/paths.d.ts.map +1 -0
  65. package/dist/utils/paths.js +40 -0
  66. package/dist/utils/paths.js.map +1 -0
  67. package/dist/utils/scaffold.d.ts +50 -0
  68. package/dist/utils/scaffold.d.ts.map +1 -0
  69. package/dist/utils/scaffold.js +500 -0
  70. package/dist/utils/scaffold.js.map +1 -0
  71. package/dist/version.d.ts +5 -0
  72. package/dist/version.d.ts.map +1 -0
  73. package/dist/version.js +19 -0
  74. package/dist/version.js.map +1 -0
  75. package/package.json +63 -0
  76. package/templates/.bundled +0 -0
  77. package/templates/CLAUDE.md +145 -0
  78. package/templates/docs/API_REFERENCE.md +58 -0
  79. package/templates/docs/ARCHITECTURE.md +185 -0
  80. package/templates/docs/CODING_STANDARDS.md +53 -0
  81. package/templates/docs/COMPONENT_GUIDELINES.md +301 -0
  82. package/templates/docs/E2E_TESTING.md +116 -0
  83. package/templates/docs/INTERNATIONALIZATION.md +67 -0
  84. package/templates/docs/TESTING.md +259 -0
  85. package/templates/docs/WORKFLOW.md +170 -0
  86. package/templates/src/App.tsx +42 -0
  87. package/templates/src/components/layout/Header.tsx +19 -0
  88. package/templates/src/components/layout/index.ts +1 -0
  89. package/templates/src/components/shared/ErrorBoundary/ErrorBoundary.tsx +104 -0
  90. package/templates/src/components/shared/ErrorBoundary/index.ts +1 -0
  91. package/templates/src/components/shared/LanguageSwitcher/LanguageSwitcher.tsx +45 -0
  92. package/templates/src/components/shared/LanguageSwitcher/index.ts +1 -0
  93. package/templates/src/components/shared/SEO/SEO.tsx +55 -0
  94. package/templates/src/components/shared/SEO/index.ts +1 -0
  95. package/templates/src/components/shared/ThemeToggle/ThemeToggle.tsx +41 -0
  96. package/templates/src/components/shared/ThemeToggle/index.ts +1 -0
  97. package/templates/src/components/shared/index.ts +4 -0
  98. package/templates/src/components/ui/button.tsx +48 -0
  99. package/templates/src/components/ui/dropdown-menu.tsx +228 -0
  100. package/templates/src/components/ui/form-error.tsx +95 -0
  101. package/templates/src/components/ui/loading.tsx +58 -0
  102. package/templates/src/components/ui/skeleton.tsx +52 -0
  103. package/templates/src/components/ui/sonner.tsx +34 -0
  104. package/templates/src/components/ui/spinner.tsx +40 -0
  105. package/templates/src/components/ui/visually-hidden.tsx +51 -0
  106. package/templates/src/contexts/mobileContext.tsx +66 -0
  107. package/templates/src/contexts/queryContext.tsx +28 -0
  108. package/templates/src/hooks/index.ts +7 -0
  109. package/templates/src/hooks/useContactForm.ts +33 -0
  110. package/templates/src/hooks/useExampleQuery.ts +20 -0
  111. package/templates/src/hooks/useLanguage.ts +23 -0
  112. package/templates/src/hooks/useMediaQuery.ts +53 -0
  113. package/templates/src/hooks/useThemeEffect.ts +31 -0
  114. package/templates/src/hooks/useTouchSizes.ts +16 -0
  115. package/templates/src/i18n/config.ts +11 -0
  116. package/templates/src/i18n/detectLanguage.ts +57 -0
  117. package/templates/src/i18n/index.ts +20 -0
  118. package/templates/src/i18n/loadCatalog.ts +30 -0
  119. package/templates/src/index.css +98 -0
  120. package/templates/src/lib/api.ts +142 -0
  121. package/templates/src/lib/config.ts +15 -0
  122. package/templates/src/lib/constants.ts +8 -0
  123. package/templates/src/lib/env.ts +53 -0
  124. package/templates/src/lib/format.ts +119 -0
  125. package/templates/src/lib/index.ts +24 -0
  126. package/templates/src/lib/routes.ts +11 -0
  127. package/templates/src/lib/storage.ts +91 -0
  128. package/templates/src/lib/storageKeys.ts +10 -0
  129. package/templates/src/lib/utils.ts +6 -0
  130. package/templates/src/lib/validations.ts +39 -0
  131. package/templates/src/locales/de.po +65 -0
  132. package/templates/src/locales/en.po +65 -0
  133. package/templates/src/locales/es.po +65 -0
  134. package/templates/src/main.tsx +107 -0
  135. package/templates/src/mocks/fixtures/index.ts +1 -0
  136. package/templates/src/mocks/fixtures/todos.ts +40 -0
  137. package/templates/src/mocks/handlers/index.ts +7 -0
  138. package/templates/src/mocks/handlers/todos.ts +59 -0
  139. package/templates/src/mocks/index.ts +3 -0
  140. package/templates/src/mocks/node.ts +9 -0
  141. package/templates/src/pages/Home.tsx +27 -0
  142. package/templates/src/pages/NotFound.tsx +28 -0
  143. package/templates/src/pages/index.ts +2 -0
  144. package/templates/src/stores/index.ts +2 -0
  145. package/templates/src/stores/preferencesStore.ts +85 -0
  146. package/templates/src/test/index.ts +8 -0
  147. package/templates/src/test/mocks.ts +17 -0
  148. package/templates/src/test/providers.tsx +54 -0
  149. package/templates/src/test-setup.ts +54 -0
  150. package/templates/src/types/api.ts +31 -0
  151. package/templates/src/types/index.ts +2 -0
  152. package/templates/src/types/preferences.ts +5 -0
  153. package/templates/src/vite-env.d.ts +10 -0
  154. package/templates/tests/unit/components/ErrorBoundary.test.tsx +193 -0
  155. package/templates/tests/unit/components/Header.test.tsx +33 -0
  156. package/templates/tests/unit/components/LanguageSwitcher.test.tsx +40 -0
  157. package/templates/tests/unit/components/Loading.test.tsx +76 -0
  158. package/templates/tests/unit/components/SEO.test.tsx +80 -0
  159. package/templates/tests/unit/components/ThemeToggle.test.tsx +62 -0
  160. package/templates/tests/unit/contexts/mobileContext.test.tsx +54 -0
  161. package/templates/tests/unit/hooks/useContactForm.test.ts +60 -0
  162. package/templates/tests/unit/hooks/useExampleQuery.test.tsx +94 -0
  163. package/templates/tests/unit/hooks/useLanguage.test.tsx +75 -0
  164. package/templates/tests/unit/hooks/useMediaQuery.test.ts +57 -0
  165. package/templates/tests/unit/hooks/useThemeEffect.test.ts +42 -0
  166. package/templates/tests/unit/i18n/detectLanguage.test.ts +40 -0
  167. package/templates/tests/unit/i18n/loadCatalog.test.ts +70 -0
  168. package/templates/tests/unit/lib/api.test.ts +142 -0
  169. package/templates/tests/unit/lib/format.test.ts +100 -0
  170. package/templates/tests/unit/lib/storage.test.ts +90 -0
  171. package/templates/tests/unit/lib/utils.test.ts +19 -0
  172. package/templates/tests/unit/lib/validations.test.ts +56 -0
  173. package/templates/tests/unit/stores/preferencesStore.test.ts +75 -0
@@ -0,0 +1,259 @@
1
+ # Testing Guidelines
2
+
3
+ ## Overview
4
+
5
+ - **Framework**: Vitest + React Testing Library
6
+ - **Coverage threshold**: 80% (lines, branches, functions, statements)
7
+ - **Test location**: `tests/unit/` mirroring `src/` structure
8
+
9
+ ## File Structure
10
+
11
+ ```
12
+ tests/unit/
13
+ ├── components/ # Component tests
14
+ ├── hooks/ # Hook tests
15
+ ├── lib/ # Utility function tests
16
+ ├── stores/ # Zustand store tests
17
+ ├── contexts/ # Context provider tests
18
+ └── i18n/ # Internationalization tests
19
+ ```
20
+
21
+ ## Naming Conventions
22
+
23
+ ```typescript
24
+ // File: [name].test.ts or [name].test.tsx (for JSX)
25
+ // Describe blocks: match the module/function name
26
+ // Test names: describe behavior, not implementation
27
+
28
+ describe('formatDate', () => {
29
+ it('returns "Invalid date" for invalid input', () => {});
30
+ it('formats date with custom options', () => {});
31
+ });
32
+ ```
33
+
34
+ ## Imports
35
+
36
+ ```typescript
37
+ // Vitest - test framework
38
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
39
+
40
+ // Testing Library - DOM utilities
41
+ import { screen, renderHook, act, waitFor, fireEvent } from '@testing-library/react';
42
+ import userEvent from '@testing-library/user-event';
43
+
44
+ // Custom utilities from @/test
45
+ import { render, mockMatchMedia, createTestQueryClient, server } from '@/test';
46
+ ```
47
+
48
+ ## Core Patterns
49
+
50
+ ### Use `it.each` for Similar Tests
51
+
52
+ ```typescript
53
+ // ✅ Good - parameterized tests
54
+ it.each([
55
+ { input: 0, expected: '0 Bytes' },
56
+ { input: 1024, expected: '1 KB' },
57
+ { input: 1024 * 1024, expected: '1 MB' },
58
+ ])('formats $input bytes', ({ input, expected }) => {
59
+ expect(formatBytes(input)).toBe(expected);
60
+ });
61
+
62
+ // ❌ Avoid - repetitive individual tests
63
+ it('formats 0 bytes', () => expect(formatBytes(0)).toBe('0 Bytes'));
64
+ it('formats 1024 bytes', () => expect(formatBytes(1024)).toBe('1 KB'));
65
+ it('formats 1MB', () => expect(formatBytes(1024 * 1024)).toBe('1 MB'));
66
+ ```
67
+
68
+ ### Use Shared Mocks
69
+
70
+ ```typescript
71
+ import { mockMatchMedia } from '@/test';
72
+
73
+ describe('useMediaQuery', () => {
74
+ beforeEach(() => {
75
+ window.matchMedia = mockMatchMedia(false);
76
+ });
77
+
78
+ it('detects desktop viewport', () => {
79
+ window.matchMedia = mockMatchMedia(true); // matches min-width query
80
+ const { result } = renderHook(() => useIsDesktop());
81
+ expect(result.current).toBe(true);
82
+ });
83
+ });
84
+ ```
85
+
86
+ ### Component Testing
87
+
88
+ ```typescript
89
+ import { screen } from '@testing-library/react';
90
+ import { render } from '@/test';
91
+
92
+ describe('Header', () => {
93
+ it('renders navigation links', () => {
94
+ render(<Header />);
95
+ expect(screen.getByRole('navigation')).toBeInTheDocument();
96
+ });
97
+ });
98
+ ```
99
+
100
+ ### Hook Testing
101
+
102
+ ```typescript
103
+ import { renderHook, act } from '@testing-library/react';
104
+
105
+ describe('useCounter', () => {
106
+ it('increments value', () => {
107
+ const { result } = renderHook(() => useCounter());
108
+
109
+ act(() => result.current.increment());
110
+
111
+ expect(result.current.count).toBe(1);
112
+ });
113
+ });
114
+ ```
115
+
116
+ ### Async Testing
117
+
118
+ ```typescript
119
+ // For promises
120
+ it('fetches data', async () => {
121
+ const result = await fetchData();
122
+ expect(result).toBeDefined();
123
+ });
124
+
125
+ // For state updates
126
+ it('updates after async action', async () => {
127
+ const { result } = renderHook(() => useAsync());
128
+
129
+ await waitFor(() => {
130
+ expect(result.current.data).toBeDefined();
131
+ });
132
+ });
133
+ ```
134
+
135
+ ### Mocking
136
+
137
+ ```typescript
138
+ import { mockMatchMedia } from '@/test';
139
+
140
+ // Mock modules at top of file
141
+ vi.mock('@/lib/storage', () => ({
142
+ setStorageItem: vi.fn(() => true),
143
+ }));
144
+
145
+ // Mock browser APIs using shared utilities
146
+ beforeEach(() => {
147
+ window.matchMedia = mockMatchMedia(false);
148
+ });
149
+
150
+ afterEach(() => {
151
+ vi.restoreAllMocks();
152
+ });
153
+
154
+ // Mock fetch for API tests
155
+ const mockFetch = vi.fn();
156
+ beforeEach(() => {
157
+ global.fetch = mockFetch;
158
+ });
159
+ mockFetch.mockResolvedValueOnce({ ok: true, json: () => Promise.resolve({}) });
160
+ ```
161
+
162
+ ### MSW (Mock Service Worker)
163
+
164
+ MSW handlers are organized in `src/mocks/`:
165
+
166
+ ```
167
+ src/mocks/
168
+ ├── handlers/
169
+ │ ├── index.ts # Combines all handlers
170
+ │ └── todos.ts # Example domain handlers
171
+ ├── fixtures/
172
+ │ └── todos.ts # Response data
173
+ ├── browser.ts # Browser worker setup
174
+ └── node.ts # Node server for tests
175
+ ```
176
+
177
+ Override handlers per-test:
178
+
179
+ ```typescript
180
+ import { http, HttpResponse, server } from '@/test';
181
+
182
+ it('handles API error', async () => {
183
+ server.use(http.get('/api/todos', () => new HttpResponse(null, { status: 500 })));
184
+ // Test error handling...
185
+ });
186
+ ```
187
+
188
+ MSW handlers auto-reset after each test via `src/test-setup.ts`.
189
+
190
+ ### Store Testing (Zustand)
191
+
192
+ ```typescript
193
+ import { act } from '@testing-library/react';
194
+
195
+ describe('preferencesStore', () => {
196
+ beforeEach(() => {
197
+ usePreferencesStore.setState({ theme: 'light' });
198
+ });
199
+
200
+ it.each(['light', 'dark', 'system'] as const)('sets theme to %s', (theme) => {
201
+ act(() => usePreferencesStore.getState().setTheme(theme));
202
+ expect(usePreferencesStore.getState().theme).toBe(theme);
203
+ });
204
+ });
205
+ ```
206
+
207
+ ## Test Organization
208
+
209
+ ### Structure Within Test Files
210
+
211
+ ```typescript
212
+ describe('ModuleName', () => {
213
+ // Setup/teardown at top
214
+ beforeEach(() => {});
215
+ afterEach(() => {});
216
+
217
+ // Group related tests
218
+ describe('methodName', () => {
219
+ it('handles normal case', () => {});
220
+ it('handles edge case', () => {});
221
+ it('handles error case', () => {});
222
+ });
223
+ });
224
+ ```
225
+
226
+ ### What to Test
227
+
228
+ | Type | Test Focus |
229
+ | ---------- | --------------------------------------------- |
230
+ | Components | Rendering, user interactions, accessibility |
231
+ | Hooks | Return values, state changes, side effects |
232
+ | Utils | Input/output, edge cases, error handling |
233
+ | Stores | State mutations, computed values, persistence |
234
+
235
+ ### What NOT to Test
236
+
237
+ - Implementation details (internal state, private methods)
238
+ - Third-party library internals
239
+ - Static content without logic
240
+ - TypeScript types (compiler handles this)
241
+
242
+ ## Running Tests
243
+
244
+ ```bash
245
+ npm test # Run all tests
246
+ npm run test:watch # Watch mode
247
+ npm run test:coverage # With coverage report
248
+ npm run test:ui # Visual UI
249
+ ```
250
+
251
+ ## Checklist for New Tests
252
+
253
+ - [ ] File follows `[name].test.ts(x)` naming
254
+ - [ ] Uses `it.each` for parameterized cases
255
+ - [ ] Mocks are cleared in `beforeEach`/`afterEach`
256
+ - [ ] No duplicate helper functions
257
+ - [ ] Tests behavior, not implementation
258
+ - [ ] Async tests use `await`/`waitFor` properly
259
+ - [ ] Coverage threshold maintained (80%)
@@ -0,0 +1,170 @@
1
+ # Workflow Commands
2
+
3
+ Custom commands to support TDD feature development.
4
+
5
+ ## Commands
6
+
7
+ ### /implement
8
+
9
+ **Purpose**: Setup for TDD feature implementation.
10
+
11
+ **Usage**:
12
+
13
+ ```
14
+ /implement <feature description>
15
+ ```
16
+
17
+ **What it does**:
18
+
19
+ 1. Creates a task branch
20
+ 2. Provides instructions to run `/feature-dev` with TDD context
21
+ 3. Reminds you to run `/check` before committing
22
+
23
+ **Note**: This is a setup command. You'll manually run `/feature-dev` with the TDD instructions provided.
24
+
25
+ ### /check
26
+
27
+ **Purpose**: Pre-commit quality validation.
28
+
29
+ **Usage**:
30
+
31
+ ```
32
+ /check
33
+ ```
34
+
35
+ **Runs**:
36
+
37
+ 1. `npm run test` - All tests must pass
38
+ 2. `npm run typecheck` - No TypeScript errors
39
+ 3. `npm run lint` - No lint errors
40
+ 4. `npm run build` - Build must succeed
41
+
42
+ ### /feature-dev
43
+
44
+ **Purpose**: Full guided feature development (plugin command).
45
+
46
+ **Usage**:
47
+
48
+ ```
49
+ /feature-dev:feature-dev <feature description>
50
+ ```
51
+
52
+ **7 Phases**:
53
+
54
+ 1. Discovery - Understand requirements
55
+ 2. Codebase Exploration - Find patterns
56
+ 3. Clarifying Questions - Resolve ambiguities
57
+ 4. Architecture Design - Plan approach
58
+ 5. Implementation - Build the feature
59
+ 6. Quality Review - Check code quality
60
+ 7. Summary - Document what was built
61
+
62
+ ## TDD Workflow
63
+
64
+ The `tdd-workflow` skill (`.claude/skills/tdd-workflow/SKILL.md`) provides TDD guidance.
65
+
66
+ ### Red-Green-Refactor
67
+
68
+ 1. **RED**: Write failing test first
69
+ 2. **GREEN**: Minimal code to pass
70
+ 3. **REFACTOR**: Clean up (tests stay green)
71
+
72
+ ### Test Location
73
+
74
+ Tests go in `tests/unit/` mirroring `src/`:
75
+
76
+ | Source | Test |
77
+ | --------------------------- | --------------------------------------- |
78
+ | `src/hooks/useAuth.ts` | `tests/unit/hooks/useAuth.test.ts` |
79
+ | `src/components/Button.tsx` | `tests/unit/components/Button.test.tsx` |
80
+
81
+ ### Test Patterns
82
+
83
+ See [docs/TESTING.md](TESTING.md) for detailed patterns.
84
+
85
+ ## Typical Workflow
86
+
87
+ ```
88
+ 1. /implement Add user authentication
89
+ → Creates branch, shows TDD instructions
90
+
91
+ 2. /feature-dev:feature-dev Add user authentication
92
+ → Follow the 7 phases
93
+ → During Phase 5, apply TDD (tests first)
94
+
95
+ 3. /check
96
+ → Verify all quality gates pass
97
+
98
+ 4. Commit
99
+ → Create conventional commit
100
+ ```
101
+
102
+ ## File Structure
103
+
104
+ ```
105
+ .claude/
106
+ ├── commands/
107
+ │ ├── implement/implement.md # /implement
108
+ │ └── quality/check.md # /check
109
+ ├── skills/
110
+ │ └── tdd-workflow/SKILL.md # TDD guidance
111
+ └── settings.local.json # Permissions
112
+ ```
113
+
114
+ ## MCP Server Integration
115
+
116
+ Use MCP servers instead of WebSearch for documentation. They provide **structured, version-accurate data** directly from source.
117
+
118
+ ### Why MCP over WebSearch?
119
+
120
+ | Benefit | Explanation |
121
+ | ----------------- | ------------------------------------------------------ |
122
+ | **Accuracy** | Fetches from official sources, not outdated blog posts |
123
+ | **Version-aware** | Gets docs for the exact library version in use |
124
+ | **Structured** | Returns code snippets, types, examples consistently |
125
+ | **Faster** | Direct API calls vs parsing HTML from search results |
126
+
127
+ ### Shadcn MCP (UI Components)
128
+
129
+ ```
130
+ mcp__shadcn__search_items_in_registries # Find components
131
+ mcp__shadcn__view_items_in_registries # View component code
132
+ mcp__shadcn__get_item_examples_from_registries # Usage examples
133
+ mcp__shadcn__get_add_command_for_items # CLI add command
134
+ mcp__shadcn__list_items_in_registries # List all components
135
+ ```
136
+
137
+ ### Context7 MCP (All 3rd Party Libraries)
138
+
139
+ Use for **any npm package**—not just React libraries:
140
+
141
+ ```
142
+ resolve-library-id # Get library ID (e.g., "react-hook-form")
143
+ get-library-docs # Fetch documentation
144
+ ```
145
+
146
+ **Common libraries in this project**:
147
+
148
+ - `react-hook-form` - Form handling
149
+ - `@tanstack/react-query` - Server state
150
+ - `zustand` - Client state
151
+ - `zod` - Validation schemas
152
+ - `@lingui/react` - i18n
153
+ - `msw` - API mocking
154
+
155
+ ### Decision Flow
156
+
157
+ ```
158
+ Need UI component? → Shadcn MCP
159
+ Need library docs? → Context7 MCP (any npm package)
160
+ Need general info? → WebSearch (fallback only)
161
+ ```
162
+
163
+ ### Adding Shadcn Components
164
+
165
+ ```bash
166
+ npx shadcn@latest add button # Single component
167
+ npx shadcn@latest add dialog card input # Multiple components
168
+ ```
169
+
170
+ Components are installed to `src/components/ui/`. Import directly (no barrel exports).
@@ -0,0 +1,42 @@
1
+ import { useLingui } from '@lingui/react/macro';
2
+ import { lazy, Suspense } from 'react';
3
+ import { Route, Routes } from 'react-router';
4
+
5
+ import { Header } from '@/components/layout';
6
+ import { SEO } from '@/components/shared';
7
+ import { PageLoading } from '@/components/ui/loading';
8
+ import { SkipLink } from '@/components/ui/visually-hidden';
9
+ import { useThemeEffect } from '@/hooks';
10
+ import { ROUTES } from '@/lib/routes';
11
+
12
+ // Lazy load pages for code splitting
13
+ // eslint-disable-next-line lingui/no-unlocalized-strings
14
+ const HomePage = lazy(() => import('@/pages/Home').then((m) => ({ default: m.HomePage })));
15
+ // eslint-disable-next-line lingui/no-unlocalized-strings
16
+ const NotFoundPage = lazy(() => import('@/pages/NotFound').then((m) => ({ default: m.NotFoundPage })));
17
+
18
+ export default function App() {
19
+ const { t } = useLingui();
20
+ useThemeEffect();
21
+
22
+ return (
23
+ <div className="bg-background text-foreground min-h-screen">
24
+ <SEO
25
+ description={t({
26
+ message: 'A modern React 19 application with TypeScript and Vite',
27
+ comment: 'Default site-wide meta description for SEO',
28
+ })}
29
+ />
30
+ <SkipLink />
31
+ <Header />
32
+ <main id="main">
33
+ <Suspense fallback={<PageLoading />}>
34
+ <Routes>
35
+ <Route path={ROUTES.HOME} element={<HomePage />} />
36
+ <Route path={ROUTES.NOT_FOUND} element={<NotFoundPage />} />
37
+ </Routes>
38
+ </Suspense>
39
+ </main>
40
+ </div>
41
+ );
42
+ }
@@ -0,0 +1,19 @@
1
+ import { Trans } from '@lingui/react/macro';
2
+
3
+ import { LanguageSwitcher, ThemeToggle } from '@/components/shared';
4
+
5
+ export function Header() {
6
+ return (
7
+ <header className="border-border border-b">
8
+ <div className="container mx-auto flex h-14 items-center justify-between px-4">
9
+ <h1 className="text-lg font-semibold">
10
+ <Trans comment="Application name displayed in the header navigation">My App</Trans>
11
+ </h1>
12
+ <div className="flex items-center gap-2">
13
+ <LanguageSwitcher />
14
+ <ThemeToggle />
15
+ </div>
16
+ </div>
17
+ </header>
18
+ );
19
+ }
@@ -0,0 +1 @@
1
+ export { Header } from './Header';
@@ -0,0 +1,104 @@
1
+ import { Component, type ErrorInfo, type ReactNode } from 'react';
2
+ import { Trans } from '@lingui/react/macro';
3
+
4
+ import { SENTRY_CONFIG } from '@/lib/config';
5
+
6
+ interface Props {
7
+ children: ReactNode;
8
+ fallback?: ReactNode;
9
+ onError?: (error: Error, errorInfo: ErrorInfo) => void;
10
+ onReset?: () => void;
11
+ }
12
+
13
+ interface State {
14
+ hasError: boolean;
15
+ error: Error | null;
16
+ }
17
+
18
+ export class ErrorBoundary extends Component<Props, State> {
19
+ constructor(props: Props) {
20
+ super(props);
21
+ this.state = { hasError: false, error: null };
22
+ }
23
+
24
+ static getDerivedStateFromError(error: Error): State {
25
+ return { hasError: true, error };
26
+ }
27
+
28
+ componentDidCatch(error: Error, errorInfo: ErrorInfo) {
29
+ console.error('ErrorBoundary caught an error:', error, errorInfo);
30
+
31
+ // Call custom error handler if provided
32
+ this.props.onError?.(error, errorInfo);
33
+
34
+ // Report to Sentry in production (if enabled and configured)
35
+ if (import.meta.env.PROD && SENTRY_CONFIG.enabled && SENTRY_CONFIG.dsn) {
36
+ // eslint-disable-next-line lingui/no-unlocalized-strings
37
+ import('@sentry/react')
38
+ .then((Sentry) => {
39
+ Sentry.captureException(error, {
40
+ extra: { componentStack: errorInfo.componentStack },
41
+ });
42
+ })
43
+ .catch(() => {
44
+ // Sentry failed to load, error already logged above
45
+ });
46
+ }
47
+ }
48
+
49
+ /**
50
+ * Reset the error boundary state
51
+ */
52
+ reset = () => {
53
+ this.props.onReset?.();
54
+ this.setState({ hasError: false, error: null });
55
+ };
56
+
57
+ render() {
58
+ if (this.state.hasError) {
59
+ if (this.props.fallback) {
60
+ return this.props.fallback;
61
+ }
62
+
63
+ return (
64
+ <div className="flex min-h-screen items-center justify-center p-4">
65
+ <div className="text-center">
66
+ <h1 className="text-destructive text-2xl font-bold">
67
+ <Trans comment="Error boundary - main error heading">Something went wrong</Trans>
68
+ </h1>
69
+ <p className="text-muted-foreground mt-2">
70
+ <Trans comment="Error boundary - error explanation">
71
+ We're sorry, but something unexpected happened.
72
+ </Trans>
73
+ </p>
74
+ {import.meta.env.DEV && this.state.error && (
75
+ <details className="bg-muted mt-4 rounded-md p-4 text-left">
76
+ <summary className="cursor-pointer font-medium">
77
+ <Trans comment="Error boundary - debug section heading">Error details</Trans>
78
+ </summary>
79
+ <pre className="mt-2 overflow-auto text-sm">{this.state.error.message}</pre>
80
+ <pre className="mt-1 overflow-auto text-xs opacity-75">{this.state.error.stack}</pre>
81
+ </details>
82
+ )}
83
+ <div className="mt-6 flex justify-center gap-3">
84
+ <button
85
+ onClick={this.reset}
86
+ className="bg-secondary text-secondary-foreground rounded px-4 py-2 transition-colors hover:opacity-90"
87
+ >
88
+ <Trans comment="Error boundary - try again button">Try Again</Trans>
89
+ </button>
90
+ <button
91
+ onClick={() => window.location.reload()}
92
+ className="bg-primary text-primary-foreground rounded px-4 py-2 transition-colors hover:opacity-90"
93
+ >
94
+ <Trans comment="Error boundary - refresh button">Refresh Page</Trans>
95
+ </button>
96
+ </div>
97
+ </div>
98
+ </div>
99
+ );
100
+ }
101
+
102
+ return this.props.children;
103
+ }
104
+ }
@@ -0,0 +1 @@
1
+ export { ErrorBoundary } from './ErrorBoundary';
@@ -0,0 +1,45 @@
1
+ import { useLingui } from '@lingui/react/macro';
2
+ import { Languages } from 'lucide-react';
3
+
4
+ import { Button } from '@/components/ui/button';
5
+ import {
6
+ DropdownMenu,
7
+ DropdownMenuContent,
8
+ DropdownMenuItem,
9
+ DropdownMenuTrigger,
10
+ } from '@/components/ui/dropdown-menu';
11
+ import { useLanguage } from '@/hooks/useLanguage';
12
+ import { LOCALE_LABELS } from '@/i18n';
13
+
14
+ export function LanguageSwitcher() {
15
+ const { t } = useLingui();
16
+ const { currentLocale, changeLanguage, supportedLocales } = useLanguage();
17
+
18
+ return (
19
+ <DropdownMenu>
20
+ <DropdownMenuTrigger asChild>
21
+ <Button
22
+ variant="ghost"
23
+ size="icon"
24
+ aria-label={t({
25
+ message: 'Change language',
26
+ comment: 'Accessibility label for the language selector dropdown button',
27
+ })}
28
+ >
29
+ <Languages className="size-5" />
30
+ </Button>
31
+ </DropdownMenuTrigger>
32
+ <DropdownMenuContent align="end">
33
+ {supportedLocales.map((locale) => (
34
+ <DropdownMenuItem
35
+ key={locale}
36
+ onClick={() => changeLanguage(locale)}
37
+ className={currentLocale === locale ? 'bg-accent' : ''}
38
+ >
39
+ {LOCALE_LABELS[locale].native}
40
+ </DropdownMenuItem>
41
+ ))}
42
+ </DropdownMenuContent>
43
+ </DropdownMenu>
44
+ );
45
+ }
@@ -0,0 +1 @@
1
+ export { LanguageSwitcher } from './LanguageSwitcher';