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.
- package/CLAUDE.md +26 -24
- package/dist/src/cli/commands/detect-intent.d.ts.map +1 -1
- package/dist/src/cli/commands/detect-intent.js +42 -1
- package/dist/src/cli/commands/detect-intent.js.map +1 -1
- package/dist/src/cli/commands/init.d.ts.map +1 -1
- package/dist/src/cli/commands/init.js +42 -1
- package/dist/src/cli/commands/init.js.map +1 -1
- package/dist/src/cli/helpers/init/plugin-installer.d.ts.map +1 -1
- package/dist/src/cli/helpers/init/plugin-installer.js +6 -2
- package/dist/src/cli/helpers/init/plugin-installer.js.map +1 -1
- package/dist/src/core/config/types.js +3 -3
- package/dist/src/core/config/types.js.map +1 -1
- package/dist/src/core/lazy-loading/llm-plugin-detector.d.ts +11 -1
- package/dist/src/core/lazy-loading/llm-plugin-detector.d.ts.map +1 -1
- package/dist/src/core/lazy-loading/llm-plugin-detector.js +32 -6
- package/dist/src/core/lazy-loading/llm-plugin-detector.js.map +1 -1
- package/dist/src/core/types/plugin-scope.d.ts +12 -1
- package/dist/src/core/types/plugin-scope.d.ts.map +1 -1
- package/dist/src/core/types/plugin-scope.js +22 -3
- package/dist/src/core/types/plugin-scope.js.map +1 -1
- package/package.json +1 -1
- package/plugins/specweave/hooks/user-prompt-submit.sh +3 -3
- package/plugins/specweave/skills/increment-planner/SKILL.md +7 -1
- package/plugins/specweave/skills/npm/SKILL.md +0 -1
- package/plugins/specweave/skills/team-build/SKILL.md +10 -1
- package/plugins/specweave/skills/team-orchestrate/SKILL.md +52 -14
- package/plugins/specweave-frontend/commands/component-generate.md +5 -3
- package/plugins/specweave-frontend/commands/frontend-scaffold.md +41 -4
- package/plugins/specweave-frontend/skills/frontend/SKILL.md +28 -0
- package/plugins/specweave-frontend/skills/frontend-architect/SKILL.md +5 -2
- package/plugins/specweave-frontend/skills/frontend-design/SKILL.md +22 -0
- package/plugins/specweave-mobile/skills/expo/SKILL.md +122 -0
- package/plugins/specweave-mobile/skills/react-native-expert/SKILL.md +201 -1
- package/src/templates/AGENTS.md.template +27 -29
- 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
|
-
│ ├── {
|
|
198
|
+
│ ├── {ORG}/sw-ecom-domain/
|
|
162
199
|
│ │ └── .specweave/increments/0001-domain-models/ # Domain agent's increment
|
|
163
|
-
│ ├── {
|
|
200
|
+
│ ├── {ORG}/sw-ecom-shared/
|
|
164
201
|
│ │ └── .specweave/increments/0001-shared-types/ # Shared agent's increment
|
|
165
|
-
│ └── {
|
|
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/{
|
|
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/{
|
|
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/{
|
|
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/{
|
|
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/{
|
|
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/{
|
|
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/{
|
|
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/{
|
|
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/{
|
|
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/{
|
|
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/{
|
|
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.
|
|
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
|
-
###
|
|
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.
|
|
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.
|
|
307
|
-
7.
|
|
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:
|
|
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)
|