specweave 1.0.244 → 1.0.246

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 (35) hide show
  1. package/CLAUDE.md +26 -24
  2. package/dist/src/cli/commands/detect-intent.d.ts.map +1 -1
  3. package/dist/src/cli/commands/detect-intent.js +42 -1
  4. package/dist/src/cli/commands/detect-intent.js.map +1 -1
  5. package/dist/src/cli/commands/init.d.ts.map +1 -1
  6. package/dist/src/cli/commands/init.js +42 -1
  7. package/dist/src/cli/commands/init.js.map +1 -1
  8. package/dist/src/cli/helpers/init/plugin-installer.d.ts.map +1 -1
  9. package/dist/src/cli/helpers/init/plugin-installer.js +6 -2
  10. package/dist/src/cli/helpers/init/plugin-installer.js.map +1 -1
  11. package/dist/src/core/config/types.js +3 -3
  12. package/dist/src/core/config/types.js.map +1 -1
  13. package/dist/src/core/lazy-loading/llm-plugin-detector.d.ts +11 -1
  14. package/dist/src/core/lazy-loading/llm-plugin-detector.d.ts.map +1 -1
  15. package/dist/src/core/lazy-loading/llm-plugin-detector.js +32 -6
  16. package/dist/src/core/lazy-loading/llm-plugin-detector.js.map +1 -1
  17. package/dist/src/core/types/plugin-scope.d.ts +12 -1
  18. package/dist/src/core/types/plugin-scope.d.ts.map +1 -1
  19. package/dist/src/core/types/plugin-scope.js +22 -3
  20. package/dist/src/core/types/plugin-scope.js.map +1 -1
  21. package/package.json +1 -1
  22. package/plugins/specweave/hooks/user-prompt-submit.sh +3 -3
  23. package/plugins/specweave/skills/increment-planner/SKILL.md +7 -1
  24. package/plugins/specweave/skills/npm/SKILL.md +0 -1
  25. package/plugins/specweave/skills/team-build/SKILL.md +10 -1
  26. package/plugins/specweave/skills/team-orchestrate/SKILL.md +52 -14
  27. package/plugins/specweave-frontend/commands/component-generate.md +5 -3
  28. package/plugins/specweave-frontend/commands/frontend-scaffold.md +41 -4
  29. package/plugins/specweave-frontend/skills/frontend/SKILL.md +28 -0
  30. package/plugins/specweave-frontend/skills/frontend-architect/SKILL.md +5 -2
  31. package/plugins/specweave-frontend/skills/frontend-design/SKILL.md +22 -0
  32. package/plugins/specweave-mobile/skills/expo/SKILL.md +122 -0
  33. package/plugins/specweave-mobile/skills/react-native-expert/SKILL.md +201 -1
  34. package/src/templates/AGENTS.md.template +27 -29
  35. package/src/templates/CLAUDE.md.template +3 -1
@@ -149,6 +149,43 @@ Agents are NOT all spawned simultaneously. The orchestrator follows a two-phase
149
149
  | GraphQL schema | `schema.graphql` | Backend agent | Frontend, Mobile |
150
150
  | API route types | `src/api/types/` | Backend agent | Frontend |
151
151
 
152
+ ### Organization Discovery (CRITICAL — resolve BEFORE spawning agents)
153
+
154
+ **The orchestrator MUST resolve the actual organization/owner name before spawning ANY agents.**
155
+ All `{ORG}` placeholders below must be replaced with the real value.
156
+
157
+ **Discovery chain (in order of priority):**
158
+
159
+ 1. **From config** (`repository.organization`):
160
+ ```bash
161
+ ORG=$(jq -r '.repository.organization // empty' .specweave/config.json 2>/dev/null)
162
+ ```
163
+
164
+ 2. **From sync profiles** (fallback if repository.organization not set):
165
+ ```bash
166
+ if [ -z "$ORG" ]; then
167
+ ORG=$(jq -r '[.sync.profiles[].config.owner // .sync.profiles[].config.organization] | map(select(. != null)) | first // empty' .specweave/config.json 2>/dev/null)
168
+ fi
169
+ ```
170
+
171
+ 3. **From umbrella childRepos** (fallback):
172
+ ```bash
173
+ if [ -z "$ORG" ]; then
174
+ ORG=$(jq -r '.umbrella.childRepos[0].path // empty' .specweave/config.json 2>/dev/null | sed 's|repositories/||' | cut -d/ -f1)
175
+ fi
176
+ ```
177
+
178
+ 4. **From existing filesystem** (last resort):
179
+ ```bash
180
+ if [ -z "$ORG" ]; then
181
+ ORG=$(ls -d repositories/*/ 2>/dev/null | head -1 | xargs basename 2>/dev/null)
182
+ fi
183
+ ```
184
+
185
+ 5. **If all fail**: Ask the user. NEVER guess or use a placeholder.
186
+
187
+ **NEVER read org from .env files.** Organization belongs in `.specweave/config.json`.
188
+
152
189
  ### Multi-Repo Increment Placement (CRITICAL)
153
190
 
154
191
  **In umbrella projects with a `repositories/` folder, each agent MUST create its increment in its OWN repo's `.specweave/`:**
@@ -158,11 +195,11 @@ Agents are NOT all spawned simultaneously. The orchestrator follows a two-phase
158
195
  umbrella-project/
159
196
  ├── .specweave/config.json # Umbrella config ONLY
160
197
  ├── repositories/
161
- │ ├── {org}/sw-ecom-domain/
198
+ │ ├── {ORG}/sw-ecom-domain/
162
199
  │ │ └── .specweave/increments/0001-domain-models/ # Domain agent's increment
163
- │ ├── {org}/sw-ecom-shared/
200
+ │ ├── {ORG}/sw-ecom-shared/
164
201
  │ │ └── .specweave/increments/0001-shared-types/ # Shared agent's increment
165
- │ └── {org}/sw-ecom-api/
202
+ │ └── {ORG}/sw-ecom-api/
166
203
  │ └── .specweave/increments/0001-api-endpoints/ # Backend agent's increment
167
204
 
168
205
  # WRONG: All agents dumping into umbrella root
@@ -174,6 +211,7 @@ umbrella-project/
174
211
  - Run `specweave init` in each repo if `.specweave/` doesn't exist
175
212
  - Each agent's working directory is its assigned repo inside `repositories/`
176
213
  - Never create `.specweave/increments/` in the umbrella root for multi-repo work
214
+ - Replace `{ORG}` with the actual organization discovered above
177
215
 
178
216
  ### Phase 1: Upstream Agents (Contracts First)
179
217
 
@@ -274,7 +312,7 @@ DESIGN QUALITY:
274
312
  - Invoke `sw-frontend:frontend-design` for high-quality UI polish
275
313
 
276
314
  WORKFLOW:
277
- 1. Set working directory to your assigned repo: cd repositories/{org}/{repo-name}
315
+ 1. Set working directory to your assigned repo: cd repositories/{ORG}/{repo-name}
278
316
  2. If .specweave/ doesn't exist in your repo, run: specweave init
279
317
  3. Create YOUR increment in YOUR repo: .specweave/increments/[ID]/
280
318
  4. Read the increment spec: .specweave/increments/[ID]/spec.md
@@ -296,7 +334,7 @@ RULES:
296
334
  - Follow existing code conventions (check .eslintrc, .prettierrc, tsconfig.json)
297
335
  - Run linter and type-check before signaling completion
298
336
  - All new components must have corresponding test files
299
- - ALL repository operations MUST use `repositories/{org}/` directory structure. NEVER create repos at the project root.
337
+ - ALL repository operations MUST use `repositories/{ORG}/` directory structure. NEVER create repos at the project root.
300
338
  - Create .specweave/increments/ in YOUR assigned repo, NOT in the umbrella project root.
301
339
  ```
302
340
 
@@ -327,7 +365,7 @@ AUTH SETUP:
327
365
  - Ensure auth middleware works end-to-end before signaling completion
328
366
 
329
367
  WORKFLOW:
330
- 1. Set working directory to your assigned repo: cd repositories/{org}/{repo-name}
368
+ 1. Set working directory to your assigned repo: cd repositories/{ORG}/{repo-name}
331
369
  2. If .specweave/ doesn't exist in your repo, run: specweave init
332
370
  3. Create YOUR increment in YOUR repo: .specweave/increments/[ID]/
333
371
  4. Read the increment spec: .specweave/increments/[ID]/spec.md
@@ -350,7 +388,7 @@ RULES:
350
388
  - Every new API endpoint must have request/response validation
351
389
  - Error handling must follow project conventions
352
390
  - All services must have unit tests
353
- - ALL repository operations MUST use `repositories/{org}/` directory structure. NEVER create repos at the project root.
391
+ - ALL repository operations MUST use `repositories/{ORG}/` directory structure. NEVER create repos at the project root.
354
392
  - Create .specweave/increments/ in YOUR assigned repo, NOT in the umbrella project root.
355
393
  ```
356
394
 
@@ -373,7 +411,7 @@ FILE OWNERSHIP (WRITE access):
373
411
  READ ACCESS: Any file in the repository
374
412
 
375
413
  WORKFLOW:
376
- 1. Set working directory to your assigned repo: cd repositories/{org}/{repo-name}
414
+ 1. Set working directory to your assigned repo: cd repositories/{ORG}/{repo-name}
377
415
  2. If .specweave/ doesn't exist in your repo, run: specweave init
378
416
  3. Create YOUR increment in YOUR repo: .specweave/increments/[ID]/
379
417
  4. Read the increment spec: .specweave/increments/[ID]/spec.md
@@ -395,7 +433,7 @@ RULES:
395
433
  - Always create migrations (never modify schema without migration)
396
434
  - Seed data must be idempotent
397
435
  - Schema changes must be backward-compatible when possible
398
- - ALL repository operations MUST use `repositories/{org}/` directory structure. NEVER create repos at the project root.
436
+ - ALL repository operations MUST use `repositories/{ORG}/` directory structure. NEVER create repos at the project root.
399
437
  - Create .specweave/increments/ in YOUR assigned repo, NOT in the umbrella project root.
400
438
  ```
401
439
 
@@ -424,7 +462,7 @@ FILE OWNERSHIP (WRITE access):
424
462
  READ ACCESS: Any file in the repository
425
463
 
426
464
  WORKFLOW:
427
- 1. Set working directory to your assigned repo: cd repositories/{org}/{repo-name}
465
+ 1. Set working directory to your assigned repo: cd repositories/{ORG}/{repo-name}
428
466
  2. If .specweave/ doesn't exist in your repo, run: specweave init
429
467
  3. Create YOUR increment in YOUR repo: .specweave/increments/[ID]/
430
468
  4. Read the increment spec: .specweave/increments/[ID]/spec.md
@@ -446,7 +484,7 @@ RULES:
446
484
  - Tests must cover all acceptance criteria from spec.md
447
485
  - Follow existing test patterns and utilities
448
486
  - E2E tests must include accessibility checks when applicable
449
- - ALL repository operations MUST use `repositories/{org}/` directory structure. NEVER create repos at the project root.
487
+ - ALL repository operations MUST use `repositories/{ORG}/` directory structure. NEVER create repos at the project root.
450
488
  - Create .specweave/increments/ in YOUR assigned repo, NOT in the umbrella project root.
451
489
  ```
452
490
 
@@ -471,7 +509,7 @@ FILE OWNERSHIP (WRITE access):
471
509
  READ ACCESS: Any file in the repository
472
510
 
473
511
  WORKFLOW:
474
- 1. Set working directory to your assigned repo: cd repositories/{org}/{repo-name}
512
+ 1. Set working directory to your assigned repo: cd repositories/{ORG}/{repo-name}
475
513
  2. If .specweave/ doesn't exist in your repo, run: specweave init
476
514
  3. Create YOUR increment in YOUR repo: .specweave/increments/[ID]/
477
515
  4. Read the increment spec: .specweave/increments/[ID]/spec.md
@@ -493,7 +531,7 @@ RULES:
493
531
  - NEVER commit secrets, credentials, or API keys
494
532
  - All user input must be validated and sanitized
495
533
  - Follow OWASP Top 10 guidelines
496
- - ALL repository operations MUST use `repositories/{org}/` directory structure. NEVER create repos at the project root.
534
+ - ALL repository operations MUST use `repositories/{ORG}/` directory structure. NEVER create repos at the project root.
497
535
  - Create .specweave/increments/ in YOUR assigned repo, NOT in the umbrella project root.
498
536
  ```
499
537
 
@@ -524,7 +562,7 @@ Each agent has exclusive WRITE access to specific file patterns. This prevents m
524
562
  3. **Shared files require coordination** -- if two domains need to modify the same file (e.g., `package.json`), the orchestrator assigns a primary owner and others request changes via messages
525
563
  4. **New files** -- agents can create new files ONLY within their ownership patterns
526
564
  5. **Conflict detection** -- the orchestrator checks for ownership overlap before spawning and resolves ambiguity upfront
527
- 6. **Repository directory structure** -- for multi-repo setups, ALL repository cloning and creation MUST use the `repositories/{org}/` directory convention. NEVER create repositories at the project root or arbitrary locations.
565
+ 6. **Repository directory structure** -- for multi-repo setups, ALL repository cloning and creation MUST use the `repositories/{ORG}/` directory convention. NEVER create repositories at the project root or arbitrary locations.
528
566
 
529
567
  ---
530
568
 
@@ -469,14 +469,16 @@ Before considering a component complete:
469
469
  - [ ] TypeScript interface with JSDoc comments
470
470
  - [ ] All variants implemented and tested
471
471
  - [ ] Unit tests with >80% coverage
472
- - [ ] Storybook stories for all variants
472
+ - [ ] Storybook stories for all variants with realistic data
473
473
  - [ ] README documentation
474
474
  - [ ] Accessibility compliance (ARIA, keyboard nav)
475
475
  - [ ] Responsive design
476
- - [ ] Error states handled
477
- - [ ] Loading states (if applicable)
476
+ - [ ] Error states handled (styled fallbacks, never raw undefined/NaN)
477
+ - [ ] Loading states with skeleton shimmer (if applicable)
478
478
  - [ ] Dark mode support (if applicable)
479
479
  - [ ] Performance optimized (React.memo, useMemo)
480
+ - [ ] Images use placeholder URLs or AI-generated assets (never "No image" boxes)
481
+ - [ ] Numeric data uses Intl.NumberFormat (prices, dates, percentages)
480
482
 
481
483
  ## Workflow
482
484
 
@@ -166,7 +166,43 @@ Generate these essential config files:
166
166
  9. **API Integration**: Fetch wrapper, error handling, typing
167
167
  10. **Performance**: Code splitting, lazy loading, memoization
168
168
 
169
- ### 6. Best Practices
169
+ ### 6. Demo-Ready Presentation (MANDATORY)
170
+
171
+ Every scaffolded project MUST be demoable immediately after setup. No broken visuals, no missing data.
172
+
173
+ **Data Formatting**:
174
+ - ALWAYS use `Intl.NumberFormat` for prices/currencies — NEVER display raw numbers that could show `$NaN`, `$undefined`, or `$null`
175
+ - Create a `formatPrice()` utility in `lib/utils` that handles edge cases:
176
+ ```typescript
177
+ export function formatPrice(price: number | null | undefined, currency = 'USD', locale = 'en-US'): string {
178
+ if (price == null || isNaN(price)) return 'Price unavailable';
179
+ return new Intl.NumberFormat(locale, { style: 'currency', currency }).format(price / 100);
180
+ }
181
+ ```
182
+ - Apply the same defensive pattern to all displayed data — dates, percentages, counts
183
+
184
+ **Placeholder Images**:
185
+ - NEVER show "No image" text boxes or broken image icons. Use one of these approaches:
186
+ 1. **Unsplash** (free, high-quality): `https://images.unsplash.com/photo-{id}?w=600&h=400&fit=crop`
187
+ 2. **Picsum** (free, random): `https://picsum.photos/seed/{slug}/600/400`
188
+ 3. **AI-generated**: Invoke `/sw-media:image` to generate custom product/hero images
189
+ 4. **SVG placeholders**: Use tasteful gradient SVGs with subtle icons (not gray boxes)
190
+ - For e-commerce: provide 4-6 realistic product image URLs in seed data
191
+ - For dashboards: use chart/graph placeholder components with realistic mock data
192
+ - For landing pages: use hero images that match the brand aesthetic
193
+
194
+ **Seed Data Requirements**:
195
+ - Include a `lib/mock-data.ts` with realistic, complete seed data
196
+ - Every product must have: name, price (valid number), image URL, description
197
+ - Every user profile must have: name, avatar URL, email
198
+ - Use diverse, realistic names and data (not "Test User 1", "Product A")
199
+
200
+ **Loading & Error States**:
201
+ - Skeleton loaders with shimmer animation (not spinner-only)
202
+ - Error states must show helpful messages with retry actions
203
+ - Empty states must be visually designed, not just "No items found" text
204
+
205
+ ### 7. Best Practices
170
206
 
171
207
  - **Component Organization**: Atomic Design pattern
172
208
  - **Type Safety**: No `any` types, strict mode
@@ -176,15 +212,16 @@ Generate these essential config files:
176
212
  - **Security**: CSP headers, XSS prevention
177
213
  - **Code Quality**: Consistent naming, clear structure
178
214
 
179
- ### 7. Workflow
215
+ ### 8. Workflow
180
216
 
181
217
  1. Ask about framework choice (React/Next/Vue/Angular)
182
218
  2. Confirm styling approach (Tailwind/styled-components/CSS Modules)
183
219
  3. Verify state management needs
184
- 4. Generate complete file structure
220
+ 4. Generate complete file structure with mock data and placeholder images
185
221
  5. Create configuration files
186
222
  6. Set up package.json with scripts
187
- 7. Provide setup instructions
223
+ 7. Verify all pages render without NaN/undefined/broken images
224
+ 8. Provide setup instructions
188
225
 
189
226
  ## Example Usage
190
227
 
@@ -529,6 +529,34 @@ const useStore = create<Store>((set) => ({
529
529
  8. **Error Handling**: Implement Error Boundaries
530
530
  9. **Documentation**: Comment complex logic, document APIs
531
531
  10. **Security**: Sanitize user input, validate data
532
+ 11. **Demo-Ready Output**: Every page must render without visual bugs — no `$NaN`, no "No image" boxes, no raw `undefined`
533
+
534
+ ## Data Display & Formatting (MANDATORY)
535
+
536
+ Never display raw, unformatted, or potentially-null data to users:
537
+
538
+ ```typescript
539
+ // Prices — ALWAYS use Intl.NumberFormat, NEVER raw template literals
540
+ // BAD: `$${product.price}` → shows "$NaN" if price is undefined
541
+ // GOOD: formatPrice(product.price)
542
+ export function formatPrice(cents: number | null | undefined, currency = 'USD'): string {
543
+ if (cents == null || isNaN(cents)) return 'Price unavailable';
544
+ return new Intl.NumberFormat('en-US', { style: 'currency', currency }).format(cents / 100);
545
+ }
546
+
547
+ // Dates — ALWAYS use Intl.DateTimeFormat
548
+ export function formatDate(date: string | Date | null | undefined): string {
549
+ if (!date) return '';
550
+ const d = new Date(date);
551
+ if (isNaN(d.getTime())) return '';
552
+ return new Intl.DateTimeFormat('en-US', { dateStyle: 'medium' }).format(d);
553
+ }
554
+ ```
555
+
556
+ **Images**: Never show "No image" text or broken icons. Use placeholder services:
557
+ - Products: `https://picsum.photos/seed/{slug}/600/400`
558
+ - Avatars: `https://i.pravatar.cc/150?u={id}`
559
+ - Custom: invoke `/sw-media:image` for AI-generated visuals
532
560
 
533
561
  ## Tools and Libraries
534
562
 
@@ -303,8 +303,11 @@ When making architectural decisions, consider:
303
303
  3. Set up build tools and development environment
304
304
  4. Configure linting, formatting, and Git hooks
305
305
  5. Create base components and layouts
306
- 6. Set up testing infrastructure
307
- 7. Configure deployment pipeline
306
+ 6. Include `lib/formatters.ts` with `formatPrice()`, `formatDate()`, `formatNumber()` utilities using `Intl` APIs
307
+ 7. Include `lib/mock-data.ts` with realistic seed data (real names, valid prices, placeholder image URLs)
308
+ 8. Set up testing infrastructure
309
+ 9. Verify all pages render without NaN/undefined/broken images
310
+ 10. Configure deployment pipeline
308
311
 
309
312
  ### Design Component Architecture
310
313
  1. Analyze UI requirements and design system
@@ -288,6 +288,28 @@ Tertiary Content → Subtle, discovered on exploration
288
288
  }
289
289
  ```
290
290
 
291
+ ## Visual Media & Placeholder Strategy
292
+
293
+ When building UIs that display images, products, or media content:
294
+
295
+ **NEVER leave empty image placeholders.** Every image slot must show something visually appealing.
296
+
297
+ 1. **Product/Hero images**: Invoke `/sw-media:image` to generate custom AI images matching the brand
298
+ 2. **Profile avatars**: Use `https://i.pravatar.cc/150?u={unique-id}` for realistic avatars
299
+ 3. **Stock photography**: Use `https://picsum.photos/seed/{slug}/{width}/{height}` for contextual photos
300
+ 4. **Branded graphics**: Generate with AI or use gradient SVGs with brand colors
301
+
302
+ **Data Display Rules**:
303
+ - Prices MUST use `Intl.NumberFormat` with currency — showing `$NaN` is a critical visual bug
304
+ - Dates MUST use `Intl.DateTimeFormat` — never show raw ISO strings or "Invalid Date"
305
+ - Empty/null values MUST show styled fallbacks ("Price unavailable", skeleton loaders) — never raw `undefined` or `null`
306
+ - Counts and stats should use compact notation for large numbers
307
+
308
+ **Empty & Error States**:
309
+ - Empty states need illustrations or icons, a clear message, and a CTA
310
+ - Error states need a visual indicator, human-readable message, and retry action
311
+ - Loading states use skeleton shimmer animations (see Loading States section above)
312
+
291
313
  ## When to Use This Agent
292
314
 
293
315
  - Creating landing pages
@@ -144,6 +144,128 @@ public:
144
144
  };
145
145
  ```
146
146
 
147
+ ## Expo Monorepo Setup
148
+
149
+ ### Monorepo with Expo (Workspaces)
150
+
151
+ Expo supports monorepos but requires explicit Metro configuration. Metro does NOT resolve workspace packages or follow symlinks by default.
152
+
153
+ ### Step-by-Step Setup
154
+
155
+ #### 1. Root package.json
156
+
157
+ ```jsonc
158
+ {
159
+ "private": true,
160
+ "workspaces": ["apps/*", "packages/*"]
161
+ }
162
+ ```
163
+
164
+ #### 2. Shared Package
165
+
166
+ ```jsonc
167
+ // packages/shared/package.json
168
+ {
169
+ "name": "@myapp/shared",
170
+ "main": "src/index.ts", // Point to SOURCE, not dist — Metro transpiles
171
+ "types": "src/index.ts"
172
+ }
173
+ ```
174
+
175
+ #### 3. Mobile App Dependencies
176
+
177
+ ```jsonc
178
+ // apps/mobile/package.json
179
+ {
180
+ "dependencies": {
181
+ "@myapp/shared": "*" // workspace:* for pnpm
182
+ }
183
+ }
184
+ ```
185
+
186
+ #### 4. Metro Config (Critical)
187
+
188
+ ```javascript
189
+ // apps/mobile/metro.config.js
190
+ const { getDefaultConfig } = require('expo/metro-config');
191
+ const path = require('path');
192
+
193
+ const monorepoRoot = path.resolve(__dirname, '../..');
194
+ const config = getDefaultConfig(__dirname);
195
+
196
+ // Watch the entire monorepo
197
+ config.watchFolders = [monorepoRoot];
198
+
199
+ // Resolve node_modules from both app and root (for hoisted deps)
200
+ config.resolver.nodeModulesPaths = [
201
+ path.resolve(__dirname, 'node_modules'),
202
+ path.resolve(monorepoRoot, 'node_modules'),
203
+ ];
204
+
205
+ // Prevent duplicate React/RN instances (crashes at runtime)
206
+ config.resolver.extraNodeModules = {
207
+ 'react': path.resolve(__dirname, 'node_modules/react'),
208
+ 'react-native': path.resolve(__dirname, 'node_modules/react-native'),
209
+ 'react-dom': path.resolve(__dirname, 'node_modules/react-dom'),
210
+ };
211
+
212
+ // Enable symlink resolution (workspace packages are symlinked)
213
+ config.resolver.unstable_enableSymlinks = true;
214
+
215
+ module.exports = config;
216
+ ```
217
+
218
+ #### 5. TypeScript Path Aliases (Editor Only)
219
+
220
+ ```jsonc
221
+ // apps/mobile/tsconfig.json
222
+ {
223
+ "compilerOptions": {
224
+ "baseUrl": ".",
225
+ "paths": {
226
+ "@myapp/shared": ["../../packages/shared/src"],
227
+ "@myapp/shared/*": ["../../packages/shared/src/*"]
228
+ }
229
+ }
230
+ }
231
+ ```
232
+
233
+ **Important**: `tsconfig.json` paths only affect the editor and `tsc`. Metro uses its own resolver configured in `metro.config.js`.
234
+
235
+ ### EAS Build with Monorepos
236
+
237
+ ```jsonc
238
+ // apps/mobile/eas.json
239
+ {
240
+ "build": {
241
+ "production": {
242
+ "node": "20.0.0"
243
+ }
244
+ }
245
+ }
246
+ ```
247
+
248
+ EAS Build automatically detects monorepo structure. If using pnpm:
249
+
250
+ ```bash
251
+ # Set install command for EAS
252
+ eas secret:create --name EAS_BUILD_INSTALL_COMMAND --value "pnpm install --frozen-lockfile"
253
+ ```
254
+
255
+ For yarn workspaces, EAS uses `yarn install` by default — no extra config needed.
256
+
257
+ ### Troubleshooting Monorepo Issues
258
+
259
+ | Error | Fix |
260
+ |-------|-----|
261
+ | `Unable to resolve module @myapp/shared` | Add `watchFolders: [monorepoRoot]` to metro.config.js |
262
+ | `Unable to resolve module react` (duplicate) | Set `extraNodeModules.react` to app's copy |
263
+ | Module found by TS but crashes at runtime | Configure Metro resolver (tsconfig paths ≠ Metro) |
264
+ | `ENOENT` errors on workspace packages | Set `resolver.unstable_enableSymlinks: true` |
265
+ | EAS Build fails with missing packages | Verify workspace setup, add `EAS_BUILD_INSTALL_COMMAND` for pnpm |
266
+
267
+ ---
268
+
147
269
  ## EAS Build and EAS Submit
148
270
 
149
271
  ### EAS Configuration
@@ -159,7 +159,176 @@ eas --version # Expo
159
159
 
160
160
  ---
161
161
 
162
- ## Part 3: Common Build Issues
162
+ ## Part 3: Monorepo & Metro Configuration
163
+
164
+ ### Monorepo Project Structure
165
+
166
+ ```
167
+ my-monorepo/
168
+ ├── package.json # workspaces: ["apps/*", "packages/*"]
169
+ ├── apps/
170
+ │ ├── mobile/ # React Native / Expo app
171
+ │ │ ├── app/ # Expo Router screens
172
+ │ │ ├── metro.config.js # ← Critical: must configure for monorepo
173
+ │ │ ├── tsconfig.json # Path aliases (editor only)
174
+ │ │ └── package.json # depends on @myapp/shared
175
+ │ └── web/ # Next.js / Vite web app
176
+ ├── packages/
177
+ │ ├── shared/ # @myapp/shared — shared types, utils, API client
178
+ │ │ ├── src/
179
+ │ │ └── package.json # "name": "@myapp/shared"
180
+ │ └── ui/ # @myapp/ui — shared components
181
+ ```
182
+
183
+ ### Metro Configuration for Monorepos
184
+
185
+ Metro does NOT follow symlinks or resolve workspace packages by default. You must configure it explicitly.
186
+
187
+ #### Expo Projects (Recommended)
188
+
189
+ ```javascript
190
+ // metro.config.js
191
+ const { getDefaultConfig } = require('expo/metro-config');
192
+ const path = require('path');
193
+
194
+ // Find the monorepo root (where root package.json with workspaces lives)
195
+ const monorepoRoot = path.resolve(__dirname, '../..');
196
+
197
+ const config = getDefaultConfig(__dirname);
198
+
199
+ // 1. Watch all files in the monorepo (so Metro sees shared packages)
200
+ config.watchFolders = [monorepoRoot];
201
+
202
+ // 2. Tell Metro where to find node_modules (handles hoisted deps)
203
+ config.resolver.nodeModulesPaths = [
204
+ path.resolve(__dirname, 'node_modules'), // app-level
205
+ path.resolve(monorepoRoot, 'node_modules'), // hoisted root
206
+ ];
207
+
208
+ // 3. Ensure react/react-native resolve from the app (avoid duplicates)
209
+ config.resolver.extraNodeModules = {
210
+ 'react': path.resolve(__dirname, 'node_modules/react'),
211
+ 'react-native': path.resolve(__dirname, 'node_modules/react-native'),
212
+ };
213
+
214
+ module.exports = config;
215
+ ```
216
+
217
+ #### Bare React Native Projects
218
+
219
+ ```javascript
220
+ // metro.config.js
221
+ const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config');
222
+ const path = require('path');
223
+
224
+ const monorepoRoot = path.resolve(__dirname, '../..');
225
+
226
+ const config = {
227
+ watchFolders: [monorepoRoot],
228
+ resolver: {
229
+ nodeModulesPaths: [
230
+ path.resolve(__dirname, 'node_modules'),
231
+ path.resolve(monorepoRoot, 'node_modules'),
232
+ ],
233
+ // Prevent duplicate React instances
234
+ extraNodeModules: {
235
+ 'react': path.resolve(__dirname, 'node_modules/react'),
236
+ 'react-native': path.resolve(__dirname, 'node_modules/react-native'),
237
+ },
238
+ // Handle symlinks (yarn/pnpm workspaces create symlinks)
239
+ unstable_enableSymlinks: true,
240
+ },
241
+ };
242
+
243
+ module.exports = mergeConfig(getDefaultConfig(__dirname), config);
244
+ ```
245
+
246
+ ### TypeScript Path Aliases vs Metro Resolver
247
+
248
+ **TypeScript `paths`** only affect the editor and type checker — Metro ignores them entirely. You need BOTH:
249
+
250
+ ```jsonc
251
+ // tsconfig.json — for editor intellisense and tsc
252
+ {
253
+ "compilerOptions": {
254
+ "baseUrl": ".",
255
+ "paths": {
256
+ "@myapp/shared": ["../../packages/shared/src"],
257
+ "@myapp/shared/*": ["../../packages/shared/src/*"],
258
+ "@/*": ["./src/*"]
259
+ }
260
+ }
261
+ }
262
+ ```
263
+
264
+ ```javascript
265
+ // metro.config.js — for actual runtime bundling
266
+ config.resolver.extraNodeModules = {
267
+ '@myapp/shared': path.resolve(monorepoRoot, 'packages/shared/src'),
268
+ };
269
+ // OR use a custom resolver:
270
+ config.resolver.resolveRequest = (context, moduleName, platform) => {
271
+ if (moduleName.startsWith('@myapp/shared')) {
272
+ const subPath = moduleName.replace('@myapp/shared', '');
273
+ return context.resolveRequest(
274
+ context,
275
+ path.resolve(monorepoRoot, 'packages/shared/src') + subPath,
276
+ platform
277
+ );
278
+ }
279
+ return context.resolveRequest(context, moduleName, platform);
280
+ };
281
+ ```
282
+
283
+ ### Package Manager Workspace Setup
284
+
285
+ ```jsonc
286
+ // Root package.json (yarn/npm workspaces)
287
+ {
288
+ "private": true,
289
+ "workspaces": ["apps/*", "packages/*"]
290
+ }
291
+
292
+ // pnpm-workspace.yaml (pnpm)
293
+ // packages:
294
+ // - "apps/*"
295
+ // - "packages/*"
296
+ ```
297
+
298
+ ```jsonc
299
+ // packages/shared/package.json
300
+ {
301
+ "name": "@myapp/shared",
302
+ "main": "src/index.ts", // Point to source for Metro (not dist)
303
+ "types": "src/index.ts"
304
+ }
305
+ ```
306
+
307
+ ```jsonc
308
+ // apps/mobile/package.json
309
+ {
310
+ "dependencies": {
311
+ "@myapp/shared": "*" // workspace:* for pnpm
312
+ }
313
+ }
314
+ ```
315
+
316
+ **Important**: For Metro bundling, point shared package `main` to **source** (`src/index.ts`), not compiled output. Metro transpiles everything itself.
317
+
318
+ ### Common Monorepo Pitfalls
319
+
320
+ | Problem | Cause | Fix |
321
+ |---------|-------|-----|
322
+ | `Unable to resolve module @myapp/shared` | Metro can't see workspace package | Add `watchFolders` pointing to monorepo root |
323
+ | `Unable to resolve module react` (duplicate) | Multiple React copies in node_modules | Pin via `extraNodeModules` to app's copy |
324
+ | Module works in web but not mobile | Webpack resolves workspaces; Metro doesn't | Configure `metro.config.js` resolver |
325
+ | Types resolve but runtime fails | tsconfig `paths` ≠ Metro resolver | Configure both tsconfig AND metro.config.js |
326
+ | `ENOENT` on symlinked packages | Metro doesn't follow symlinks by default | Set `unstable_enableSymlinks: true` |
327
+ | Hoisted deps not found | Dependencies hoisted to root node_modules | Add root `node_modules` to `nodeModulesPaths` |
328
+
329
+ ---
330
+
331
+ ## Part 3b: Common Build Issues
163
332
 
164
333
  ### Metro Cache Issues
165
334
 
@@ -185,6 +354,37 @@ cd ios && rm -rf build Pods Podfile.lock && pod install && cd ..
185
354
  cd android && ./gradlew clean && cd ..
186
355
  ```
187
356
 
357
+ ### Metro Resolution Errors
358
+
359
+ **"Unable to resolve module X"** — Systematic debugging:
360
+
361
+ ```bash
362
+ # 1. Verify the package exists and is installed
363
+ ls node_modules/@myapp/shared 2>/dev/null || echo "NOT in app node_modules"
364
+ ls ../../node_modules/@myapp/shared 2>/dev/null || echo "NOT in root node_modules"
365
+
366
+ # 2. Check if it's a symlink (workspaces create these)
367
+ ls -la node_modules/@myapp/ 2>/dev/null
368
+
369
+ # 3. Verify metro.config.js has watchFolders and nodeModulesPaths
370
+ cat metro.config.js
371
+
372
+ # 4. Check the shared package's main/module entry point
373
+ cat ../../packages/shared/package.json | grep -E '"main"|"module"|"exports"'
374
+
375
+ # 5. Clear cache and restart
376
+ watchman watch-del-all && npx expo start --clear
377
+ ```
378
+
379
+ **"Unable to resolve module X from Y: X could not be found within the project or in these directories: node_modules"**
380
+
381
+ This error means Metro's resolver checked its configured paths and found nothing. Checklist:
382
+ 1. Is `watchFolders` set to include the directory containing the package?
383
+ 2. Is `nodeModulesPaths` set to include all relevant `node_modules` directories?
384
+ 3. Does the package's `package.json` have a valid `main` field pointing to an existing file?
385
+ 4. If using pnpm, is `unstable_enableSymlinks: true` set in the resolver?
386
+ 5. After config changes, always run `npx expo start --clear` (Metro caches aggressively)
387
+
188
388
  ---
189
389
 
190
390
  ## Part 4: New Architecture (Fabric, Turbo Modules, JSI)