PureXML/third_party/rules-swift/exp/when-to-create.md

135 lines
5.4 KiB
Markdown

# When to Create a New Target
Always-loaded core. Use the decision tree first. The DO/DON'T list and the checklist below cover the corner cases.
## TARGET CREATION DECISION TREE
```
Need to add new code?
├─ Is it a reusable domain model?
│ └─ YES → Add to SharedModels
│
├─ Is it UI-related?
│ ├─ Colors/semantic colors? → Add to AppColors
│ ├─ Fonts/typography? → Add to AppFont
│ ├─ Combined theme? → Add to AppTheme (uses AppColors + AppFont)
│ ├─ Component system infrastructure? → Add to Components (core system)
│ ├─ Hot reload support? → Add to SharedComponents
│ ├─ Reusable app component? → Add to AppComponents
│ └─ View helper/modifier? → Add to SharedViews
│
├─ Is it a complete user-facing feature?
│ └─ YES → Create new *Feature target
│ Example: ProfileFeature, SettingsFeature, PaymentFeature
│
├─ Is it API/networking related?
│ ├─ Client-side? → Add to ApiClient (or create new client target)
│ ├─ Server-side? → Add to ApiServer
│ ├─ Shared DTOs? → Add to ApiShared (OpenAPI generated)
│ └─ Middleware? → Create new *Middleware target
│
├─ Is it shared infrastructure?
│ └─ YES → Create new Shared* target
│ Example: SharedUtilities, SharedNetworking
│
└─ Still unsure?
└─ Ask: "Could this be reused in isolation?"
├─ YES → Create new target
└─ NO → Add to most specific existing target
```
## WHEN TO CREATE A NEW TARGET
### ✅ DO Create New Target When:
1. **New Feature Module**
- Complete user-facing feature (login, profile, payments)
- Example: `ProfileFeature`, `PaymentFlowFeature`
2. **Reusable Infrastructure**
- Can be tested in isolation
- Might be used by multiple features
- Example: `CachingMiddleware`, `LoggingUtility`
3. **Third-Party Integration**
- Wraps external library
- Isolates external dependencies
- Example: `ApplePayIntegration`, `BiometricAuth`
4. **Platform Separation**
- Platform-specific code (iOS vs macOS)
- Example: `IOSBiometrics`, `MacOSNotifications`
5. **Build Optimization**
- Large, stable code that rarely changes
- Expensive compilation (generated code)
- Example: `ApiShared` (OpenAPI generated)
### ❌ DON'T Create New Target When:
1. **Single Use Case**
- Only used by one feature
- Tightly coupled to specific screen
- → Add to that feature's target
2. **Trivial Helper**
- 1-2 small functions
- No external dependencies
- → Add to existing utility target
3. **Temporary Code**
- Proof of concept
- Spike/experiment
- → Keep in feature until proven stable
## DECISION CHECKLIST
### Before Creating a New Target
- [ ] Target has single, clear responsibility
- [ ] Target name follows conventions (*Feature, Shared*, Api*, *Components)
- [ ] Dependencies are minimal and explicit
- [ ] No circular dependencies introduced
- [ ] Can be built and tested in isolation
- [ ] Fits into architectural layer (Foundation/Infrastructure/Feature/App)
- [ ] Test target created alongside source target
- [ ] Product registered in Package.swift products array
- [ ] Imports and declared dependencies point only to lower layers (no higher-layer dependencies)
### Before Adding to Existing Target
- [ ] New code shares responsibility with the existing target
- [ ] No better-suited target exists
- [ ] Not creating a "god target" with mixed concerns
- [ ] Won't introduce unwanted dependencies to target consumers
### Before Modifying Package.swift
- [ ] Used closure-with-local-variables pattern for `deps`
- [ ] Used closure-with-local-variables pattern for `allProducts`
- [ ] Used closure-with-local-variables pattern for `targets`
- [ ] NEVER used inline array definitions
- [ ] Grouped dependencies by purpose (OpenAPI, Vapor, Apple-only, etc.)
- [ ] Grouped products by platform (base vs. appleOnly)
- [ ] Grouped targets by layer (Foundation, Infrastructure, Features)
- [ ] Used comment headers (e.g., `// ---------- Shared Models ----------`)
- [ ] In `Package.swift`: separated platform-specific code with `#if os(iOS) || os(macOS)`
- [ ] In `Package.swift` only: did NOT use `#if canImport(UIKit)` (manifest evaluates it lazily and breaks Linux CI; `#if os()` is manifest-parse-time and safe). In regular Swift source files, `#if canImport(UIKit)` / `#if canImport(AppKit)` is the correct idiom for platform-conditional code.
- [ ] Each target/product/dependency has descriptive variable name
- [ ] Used trailing commas in all arrays
- [ ] Applied `.when(platforms:)` for platform-specific dependencies within targets
### Before Adding Font/Resource Target
- [ ] Used `.process()` for resources, NEVER `.copy()`
- [ ] Created `FontRegistration.swift` with CoreText registration
- [ ] Used `Bundle.module` for resource access, NEVER `Bundle.main`
- [ ] Used `#if canImport(UIKit)` / `#if canImport(AppKit)` for platform imports
- [ ] Filtered font files by extension (.otf, .ttf)
- [ ] Used `CTFontManagerRegisterFontsForURL` with error handling
- [ ] Added console logging for registration success/failure
- [ ] Called `FontRegistration.registerFonts()` in app init BEFORE UI renders
- [ ] NEVER used Info.plist font registration (`UIAppFonts`, `ATSApplicationFontsPath`)
- [ ] Target has zero dependencies (fonts are Foundation layer)
- [ ] Resources organized in dedicated subdirectory (e.g., `Fonts/`)