@zigrivers/scaffold 3.6.0 → 3.8.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 (115) hide show
  1. package/README.md +127 -12
  2. package/content/knowledge/backend/backend-api-design.md +103 -0
  3. package/content/knowledge/backend/backend-architecture.md +100 -0
  4. package/content/knowledge/backend/backend-async-patterns.md +101 -0
  5. package/content/knowledge/backend/backend-auth-patterns.md +100 -0
  6. package/content/knowledge/backend/backend-conventions.md +105 -0
  7. package/content/knowledge/backend/backend-data-modeling.md +102 -0
  8. package/content/knowledge/backend/backend-deployment.md +100 -0
  9. package/content/knowledge/backend/backend-dev-environment.md +102 -0
  10. package/content/knowledge/backend/backend-observability.md +102 -0
  11. package/content/knowledge/backend/backend-project-structure.md +100 -0
  12. package/content/knowledge/backend/backend-requirements.md +103 -0
  13. package/content/knowledge/backend/backend-security.md +104 -0
  14. package/content/knowledge/backend/backend-testing.md +101 -0
  15. package/content/knowledge/backend/backend-worker-patterns.md +100 -0
  16. package/content/knowledge/cli/cli-architecture.md +101 -0
  17. package/content/knowledge/cli/cli-conventions.md +117 -0
  18. package/content/knowledge/cli/cli-dev-environment.md +121 -0
  19. package/content/knowledge/cli/cli-distribution-patterns.md +106 -0
  20. package/content/knowledge/cli/cli-interactivity-patterns.md +116 -0
  21. package/content/knowledge/cli/cli-output-patterns.md +107 -0
  22. package/content/knowledge/cli/cli-project-structure.md +124 -0
  23. package/content/knowledge/cli/cli-requirements.md +101 -0
  24. package/content/knowledge/cli/cli-shell-integration.md +130 -0
  25. package/content/knowledge/cli/cli-testing.md +134 -0
  26. package/content/knowledge/library/library-api-design.md +306 -0
  27. package/content/knowledge/library/library-architecture.md +247 -0
  28. package/content/knowledge/library/library-bundling.md +244 -0
  29. package/content/knowledge/library/library-conventions.md +229 -0
  30. package/content/knowledge/library/library-dev-environment.md +220 -0
  31. package/content/knowledge/library/library-documentation.md +300 -0
  32. package/content/knowledge/library/library-project-structure.md +237 -0
  33. package/content/knowledge/library/library-requirements.md +173 -0
  34. package/content/knowledge/library/library-security.md +257 -0
  35. package/content/knowledge/library/library-testing.md +319 -0
  36. package/content/knowledge/library/library-type-definitions.md +284 -0
  37. package/content/knowledge/library/library-versioning.md +300 -0
  38. package/content/knowledge/mobile-app/mobile-app-architecture.md +283 -0
  39. package/content/knowledge/mobile-app/mobile-app-conventions.md +180 -0
  40. package/content/knowledge/mobile-app/mobile-app-deployment.md +298 -0
  41. package/content/knowledge/mobile-app/mobile-app-dev-environment.md +257 -0
  42. package/content/knowledge/mobile-app/mobile-app-distribution.md +264 -0
  43. package/content/knowledge/mobile-app/mobile-app-observability.md +317 -0
  44. package/content/knowledge/mobile-app/mobile-app-offline-patterns.md +311 -0
  45. package/content/knowledge/mobile-app/mobile-app-project-structure.md +245 -0
  46. package/content/knowledge/mobile-app/mobile-app-push-notifications.md +321 -0
  47. package/content/knowledge/mobile-app/mobile-app-requirements.md +147 -0
  48. package/content/knowledge/mobile-app/mobile-app-security.md +338 -0
  49. package/content/knowledge/mobile-app/mobile-app-testing.md +400 -0
  50. package/content/knowledge/web-app/web-app-api-patterns.md +224 -0
  51. package/content/knowledge/web-app/web-app-architecture.md +116 -0
  52. package/content/knowledge/web-app/web-app-auth-patterns.md +256 -0
  53. package/content/knowledge/web-app/web-app-conventions.md +121 -0
  54. package/content/knowledge/web-app/web-app-data-patterns.md +218 -0
  55. package/content/knowledge/web-app/web-app-deployment-workflow.md +143 -0
  56. package/content/knowledge/web-app/web-app-deployment.md +134 -0
  57. package/content/knowledge/web-app/web-app-design-system.md +158 -0
  58. package/content/knowledge/web-app/web-app-dev-environment.md +173 -0
  59. package/content/knowledge/web-app/web-app-observability.md +221 -0
  60. package/content/knowledge/web-app/web-app-project-structure.md +160 -0
  61. package/content/knowledge/web-app/web-app-rendering-strategies.md +133 -0
  62. package/content/knowledge/web-app/web-app-requirements.md +112 -0
  63. package/content/knowledge/web-app/web-app-security.md +193 -0
  64. package/content/knowledge/web-app/web-app-session-patterns.md +214 -0
  65. package/content/knowledge/web-app/web-app-testing.md +249 -0
  66. package/content/knowledge/web-app/web-app-ux-patterns.md +162 -0
  67. package/content/methodology/backend-overlay.yml +73 -0
  68. package/content/methodology/cli-overlay.yml +69 -0
  69. package/content/methodology/library-overlay.yml +67 -0
  70. package/content/methodology/mobile-app-overlay.yml +71 -0
  71. package/content/methodology/web-app-overlay.yml +79 -0
  72. package/dist/cli/commands/init.d.ts +21 -0
  73. package/dist/cli/commands/init.d.ts.map +1 -1
  74. package/dist/cli/commands/init.js +261 -13
  75. package/dist/cli/commands/init.js.map +1 -1
  76. package/dist/cli/commands/init.test.js +206 -0
  77. package/dist/cli/commands/init.test.js.map +1 -1
  78. package/dist/config/schema.d.ts +1392 -64
  79. package/dist/config/schema.d.ts.map +1 -1
  80. package/dist/config/schema.js +82 -5
  81. package/dist/config/schema.js.map +1 -1
  82. package/dist/config/schema.test.js +302 -1
  83. package/dist/config/schema.test.js.map +1 -1
  84. package/dist/core/assembly/overlay-loader.d.ts.map +1 -1
  85. package/dist/core/assembly/overlay-loader.js +2 -1
  86. package/dist/core/assembly/overlay-loader.js.map +1 -1
  87. package/dist/core/assembly/overlay-loader.test.js +56 -0
  88. package/dist/core/assembly/overlay-loader.test.js.map +1 -1
  89. package/dist/e2e/game-pipeline.test.js +1 -0
  90. package/dist/e2e/game-pipeline.test.js.map +1 -1
  91. package/dist/e2e/project-type-overlays.test.d.ts +16 -0
  92. package/dist/e2e/project-type-overlays.test.d.ts.map +1 -0
  93. package/dist/e2e/project-type-overlays.test.js +834 -0
  94. package/dist/e2e/project-type-overlays.test.js.map +1 -0
  95. package/dist/types/config.d.ts +19 -2
  96. package/dist/types/config.d.ts.map +1 -1
  97. package/dist/types/index.d.ts +0 -1
  98. package/dist/types/index.d.ts.map +1 -1
  99. package/dist/types/index.js +0 -1
  100. package/dist/types/index.js.map +1 -1
  101. package/dist/wizard/questions.d.ts +27 -1
  102. package/dist/wizard/questions.d.ts.map +1 -1
  103. package/dist/wizard/questions.js +142 -3
  104. package/dist/wizard/questions.js.map +1 -1
  105. package/dist/wizard/questions.test.js +206 -8
  106. package/dist/wizard/questions.test.js.map +1 -1
  107. package/dist/wizard/wizard.d.ts +21 -0
  108. package/dist/wizard/wizard.d.ts.map +1 -1
  109. package/dist/wizard/wizard.js +27 -1
  110. package/dist/wizard/wizard.js.map +1 -1
  111. package/package.json +1 -1
  112. package/dist/types/wizard.d.ts +0 -14
  113. package/dist/types/wizard.d.ts.map +0 -1
  114. package/dist/types/wizard.js +0 -2
  115. package/dist/types/wizard.js.map +0 -1
@@ -0,0 +1,300 @@
1
+ ---
2
+ name: library-versioning
3
+ description: Semver discipline, breaking change detection, release automation, and changelog management for published libraries
4
+ topics: [library, versioning, semver, breaking-changes, release-automation, changelog, changesets]
5
+ ---
6
+
7
+ Library versioning is a communication protocol with consumers. Semver (Semantic Versioning) is not merely a numbering scheme — it is a contract about backward compatibility. Breaking that contract without a major version bump is one of the most damaging things a library can do. Consumers set version ranges expecting that minor updates are safe to take automatically. Violating that expectation causes production incidents for real applications. Versioning discipline must be enforced by tooling, not willpower.
8
+
9
+ ## Summary
10
+
11
+ Enforce semver through tooling: use changesets or semantic-release to automate versioning based on change metadata. Use automated breaking change detection (API Extractor or type-coverage checks) to catch accidental breaking changes before publish. Every release requires a CHANGELOG entry with migration guidance for breaking changes. Pre-releases (`alpha`, `beta`, `rc`) allow consumers to opt into early testing without affecting stable installs. Tag releases in git to enable diff-based changelog generation.
12
+
13
+ Versioning workflow:
14
+ 1. Author creates changeset file describing the change type (patch/minor/major)
15
+ 2. CI aggregates changesets and proposes a version bump PR
16
+ 3. Version bump PR merges, triggering publish to npm
17
+ 4. Git tag pushed matching the published version
18
+ 5. GitHub Release created from changelog content
19
+
20
+ ## Deep Guidance
21
+
22
+ ### Changesets Workflow
23
+
24
+ Changesets is the recommended tool for managing versioning in library projects. It decouples the decision of "what version bump does this change require" from "when do we publish."
25
+
26
+ **Setup:**
27
+ ```bash
28
+ npm install --save-dev @changesets/cli
29
+ npx changeset init
30
+ ```
31
+
32
+ This creates a `.changeset/` directory at the project root.
33
+
34
+ **Creating a changeset (run for every PR that changes behavior):**
35
+ ```bash
36
+ npx changeset add
37
+ # Interactive prompt:
38
+ # ? Which packages would you like to include? my-library
39
+ # ? What type of change is this for my-library?
40
+ # major (Breaking change)
41
+ # minor (New feature)
42
+ # > patch (Bug fix)
43
+ # ? Please enter a summary for this change:
44
+ # Fix parseConfig() incorrectly ignoring the encoding option
45
+ ```
46
+
47
+ This creates a markdown file in `.changeset/`:
48
+ ```markdown
49
+ <!-- .changeset/silver-wolves-grin.md -->
50
+ ---
51
+ "my-library": patch
52
+ ---
53
+
54
+ Fix parseConfig() incorrectly ignoring the encoding option when parsing file input.
55
+ ```
56
+
57
+ **Version bump and publish:**
58
+ ```bash
59
+ # Update package.json version and CHANGELOG.md
60
+ npx changeset version
61
+
62
+ # Publish to npm
63
+ npx changeset publish
64
+ ```
65
+
66
+ In CI, automate this with the Changesets GitHub Action:
67
+ ```yaml
68
+ # .github/workflows/release.yml
69
+ # name: Release — CI publishes on push to main
70
+ on:
71
+ push:
72
+ branches: [main]
73
+
74
+ jobs:
75
+ release:
76
+ runs-on: ubuntu-latest
77
+ steps:
78
+ - uses: actions/checkout@v4
79
+ - uses: actions/setup-node@v4
80
+ with:
81
+ node-version: 20
82
+ registry-url: 'https://registry.npmjs.org'
83
+ - run: npm ci
84
+ - uses: changesets/action@v1
85
+ with:
86
+ publish: npm run release
87
+ env:
88
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
89
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
90
+ ```
91
+
92
+ The action opens a "Version Packages" PR when changesets are present and publishes when that PR merges.
93
+
94
+ ### Breaking Change Detection with API Extractor
95
+
96
+ Microsoft's API Extractor catches breaking changes by comparing the current API surface against a committed baseline:
97
+
98
+ **Setup:**
99
+ ```bash
100
+ npm install --save-dev @microsoft/api-extractor
101
+ npx api-extractor init
102
+ ```
103
+
104
+ ```json
105
+ // api-extractor.json (key settings)
106
+ {
107
+ "mainEntryPointFilePath": "<projectFolder>/dist/types/index.d.ts",
108
+ "apiReport": {
109
+ "enabled": true,
110
+ "reportFolder": "<projectFolder>/etc/"
111
+ },
112
+ "docModel": {
113
+ "enabled": true
114
+ }
115
+ }
116
+ ```
117
+
118
+ ```bash
119
+ # Generate initial API report (commit this file)
120
+ npx api-extractor run --local
121
+
122
+ # In CI: compare against committed report
123
+ npx api-extractor run
124
+ # Fails if the API surface changed in ways not reflected in the committed report
125
+ ```
126
+
127
+ The generated `etc/my-library.api.md` file shows the complete public API surface in a reviewable format. When a PR changes it, reviewers can see exactly what changed. If the change is intentional, update the committed report; if not, fix the breaking change.
128
+
129
+ **API report excerpt:**
130
+ ```markdown
131
+ // @public
132
+ export function parseConfig(input: string, options?: ParseOptions): Config;
133
+
134
+ // @public
135
+ export interface ParseOptions {
136
+ encoding?: BufferEncoding;
137
+ strict?: boolean;
138
+ }
139
+
140
+ // @public
141
+ export class ParseError extends Error {
142
+ constructor(message: string, line: number, column: number);
143
+ readonly column: number;
144
+ readonly line: number;
145
+ }
146
+ ```
147
+
148
+ This format makes breaking changes immediately visible in code review.
149
+
150
+ ### Pre-Release Channels
151
+
152
+ Pre-releases allow consumers to test upcoming changes without affecting stable installs:
153
+
154
+ **With changesets:**
155
+ ```bash
156
+ # Enter pre-release mode
157
+ npx changeset pre enter alpha
158
+ # Or: beta, rc
159
+
160
+ # Create changesets and version as normal
161
+ npx changeset add
162
+ npx changeset version
163
+ # Produces: 2.0.0-alpha.1
164
+
165
+ # Exit pre-release mode
166
+ npx changeset pre exit
167
+ ```
168
+
169
+ **Manual pre-release versioning:**
170
+ ```json
171
+ // package.json
172
+ "version": "2.0.0-alpha.1"
173
+ ```
174
+
175
+ ```bash
176
+ npm publish --tag alpha
177
+ # Consumers opt-in: npm install my-library@alpha
178
+ # Stable consumers (npm install my-library) are unaffected
179
+ ```
180
+
181
+ **Pre-release channel strategy:**
182
+ - `alpha` — internal testing only, may change drastically, no API stability
183
+ - `beta` — public testing, API reasonably stable, looking for feedback
184
+ - `rc` (release candidate) — API frozen, looking for final integration issues
185
+ - Stable — semver protected, change policy enforced
186
+
187
+ ### Release Automation with GitHub Actions
188
+
189
+ Full release workflow with provenance and attestation:
190
+
191
+ ```yaml
192
+ # .github/workflows/release.yml
193
+ # name: Release — CI publishes on tag push
194
+ on:
195
+ push:
196
+ tags:
197
+ - 'v*'
198
+
199
+ permissions:
200
+ contents: write
201
+ id-token: write # For npm provenance
202
+
203
+ jobs:
204
+ release:
205
+ runs-on: ubuntu-latest
206
+ steps:
207
+ - uses: actions/checkout@v4
208
+
209
+ - uses: actions/setup-node@v4
210
+ with:
211
+ node-version: '20'
212
+ registry-url: 'https://registry.npmjs.org'
213
+
214
+ - name: Install dependencies
215
+ run: npm ci
216
+
217
+ - name: Build
218
+ run: npm run build
219
+
220
+ - name: Test
221
+ run: npm test
222
+
223
+ - name: Publish to npm
224
+ run: npm publish --provenance --access public
225
+ env:
226
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
227
+
228
+ - name: Create GitHub Release
229
+ uses: softprops/action-gh-release@v2
230
+ with:
231
+ generate_release_notes: true
232
+ draft: false
233
+ ```
234
+
235
+ The `--provenance` flag publishes npm provenance attestation — a cryptographic link between the published package and the GitHub Actions run that built it. This allows consumers to verify the package was built from the expected source.
236
+
237
+ ### CHANGELOG Generation
238
+
239
+ Keep a Changelog format, managed automatically:
240
+
241
+ ```bash
242
+ # With conventional commits, generate changelog automatically:
243
+ npx conventional-changelog-cli -p angular -i CHANGELOG.md -s
244
+
245
+ # Or with changesets:
246
+ npx changeset version # Updates CHANGELOG.md automatically
247
+ ```
248
+
249
+ **Manual CHANGELOG structure:**
250
+ ```markdown
251
+ # Changelog
252
+
253
+ All notable changes to this project will be documented in this file.
254
+ Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
255
+ Versioning: [Semantic Versioning](https://semver.org/spec/v2.0.0.html)
256
+
257
+ ## [Unreleased]
258
+
259
+ ## [2.1.0] - 2024-03-15
260
+
261
+ ### Added
262
+ - `parseConfigFile(path)` for file-based parsing
263
+ - `ParseOptions.maxSize` to limit input size
264
+
265
+ ### Fixed
266
+ - `parseConfig()` no longer ignores `encoding` option for buffer inputs
267
+
268
+ ## [2.0.0] - 2024-01-10
269
+
270
+ ### Breaking Changes
271
+ - Removed `parse()` (deprecated in 1.5.0). Replacement: `parseConfig()`.
272
+ - `Config.timeout` is now milliseconds (was seconds). Multiply existing values × 1000.
273
+ - Dropped Node 16 support. Minimum: Node 18.
274
+
275
+ ### Migration Guide
276
+ See: https://my-library.dev/guides/migration-v2
277
+
278
+ [Unreleased]: https://github.com/org/my-library/compare/v2.1.0...HEAD
279
+ [2.1.0]: https://github.com/org/my-library/compare/v2.0.0...v2.1.0
280
+ [2.0.0]: https://github.com/org/my-library/releases/tag/v2.0.0
281
+ ```
282
+
283
+ ### Git Tag Strategy
284
+
285
+ Tag every release:
286
+ ```bash
287
+ # After publishing to npm
288
+ git tag v2.1.0
289
+ git push origin v2.1.0
290
+
291
+ # Or use npm version (updates package.json, commits, and tags)
292
+ npm version minor -m "chore(release): v%s"
293
+ git push && git push --tags
294
+ ```
295
+
296
+ Tags are the source of truth for "what was published when." They enable:
297
+ - Reproducible builds from any historical version
298
+ - `git diff v2.0.0 v2.1.0` to review what changed between releases
299
+ - Automated changelog generation tools
300
+ - GitHub Release creation linked to the exact commit
@@ -0,0 +1,283 @@
1
+ ---
2
+ name: mobile-app-architecture
3
+ description: MVVM/MVI/TCA patterns, navigation architecture, dependency injection, and state management for iOS and Android mobile apps
4
+ topics: [mobile-app, architecture, mvvm, mvi, tca, navigation, dependency-injection, state-management]
5
+ ---
6
+
7
+ Mobile app architecture determines testability, scalability, and developer velocity. The wrong architecture is expensive to reverse — a monolithic ViewController or God Activity becomes unmaintainable at scale. Both iOS and Android ecosystems have converged on unidirectional data flow patterns: TCA and MVVM+Combine/async for iOS, MVI and MVVM+Flow for Android. Choose the pattern that matches your team's size and complexity requirements, not the most sophisticated available option.
8
+
9
+ ## Summary
10
+
11
+ iOS architectures: MVVM with SwiftUI/Combine for mid-size apps, TCA (The Composable Architecture) for large apps requiring strict testability and state isolation. Android architectures: MVVM with StateFlow for most apps, MVI for complex state management. Both platforms benefit from clean architecture layers — presentation, domain, data — with dependency injection (Hilt for Android, constructor injection or a container for iOS). Navigation architecture is separate from view architecture: use Coordinator (iOS) or Navigation Component (Android).
12
+
13
+ ## Deep Guidance
14
+
15
+ ### iOS Architecture Patterns
16
+
17
+ **MVVM with SwiftUI**
18
+
19
+ The standard pattern for new iOS apps. The ViewModel is an `@Observable` class (iOS 17+) or `ObservableObject` (iOS 13+) that holds and transforms state:
20
+
21
+ ```swift
22
+ @Observable
23
+ final class UserProfileViewModel {
24
+ var user: User?
25
+ var isLoading = false
26
+ var error: Error?
27
+
28
+ private let repository: UserRepository
29
+
30
+ init(repository: UserRepository) {
31
+ self.repository = repository
32
+ }
33
+
34
+ func loadUser(id: String) async {
35
+ isLoading = true
36
+ defer { isLoading = false }
37
+ do {
38
+ user = try await repository.fetchUser(id: id)
39
+ } catch {
40
+ self.error = error
41
+ }
42
+ }
43
+ }
44
+
45
+ struct UserProfileView: View {
46
+ @State private var viewModel = UserProfileViewModel(repository: LiveUserRepository())
47
+
48
+ var body: some View {
49
+ Group {
50
+ if viewModel.isLoading { ProgressView() }
51
+ else if let user = viewModel.user { UserDetailView(user: user) }
52
+ else if viewModel.error != nil { ErrorView() }
53
+ }
54
+ .task { await viewModel.loadUser(id: userId) }
55
+ }
56
+ }
57
+ ```
58
+
59
+ Rules for healthy MVVM:
60
+ - ViewModels must not import UIKit or SwiftUI — they are platform-agnostic
61
+ - One ViewModel per screen/feature, not per view hierarchy level
62
+ - ViewModels receive dependencies via constructor injection — no singletons
63
+ - ViewModels hold only UI state, not business logic — business logic belongs in services/repositories
64
+ - Test ViewModels by injecting fake dependencies and asserting state transitions
65
+
66
+ **TCA (The Composable Architecture)**
67
+
68
+ For large apps with complex state, strict testability requirements, or large teams. TCA provides a single-direction state mutation model:
69
+
70
+ ```swift
71
+ @Reducer
72
+ struct UserProfileFeature {
73
+ @ObservableState
74
+ struct State: Equatable {
75
+ var user: User?
76
+ var isLoading = false
77
+ var error: String?
78
+ }
79
+
80
+ enum Action {
81
+ case loadUser(String)
82
+ case userLoaded(Result<User, Error>)
83
+ }
84
+
85
+ @Dependency(\.userRepository) var userRepository
86
+
87
+ var body: some ReducerOf<Self> {
88
+ Reduce { state, action in
89
+ switch action {
90
+ case .loadUser(let id):
91
+ state.isLoading = true
92
+ return .run { send in
93
+ await send(.userLoaded(Result { try await userRepository.fetchUser(id: id) }))
94
+ }
95
+ case .userLoaded(.success(let user)):
96
+ state.isLoading = false
97
+ state.user = user
98
+ return .none
99
+ case .userLoaded(.failure(let error)):
100
+ state.isLoading = false
101
+ state.error = error.localizedDescription
102
+ return .none
103
+ }
104
+ }
105
+ }
106
+ }
107
+ ```
108
+
109
+ TCA benefits: every state mutation is explicit, side effects are isolated and cancellable, testing is deterministic. TCA costs: steep learning curve, boilerplate-heavy for simple features, requires team-wide adoption to be consistent.
110
+
111
+ **Clean Architecture layers for iOS**
112
+ ```
113
+ Presentation Layer: Views + ViewModels
114
+ ↓ calls
115
+ Domain Layer: Use Cases + Domain Models + Repository Protocols
116
+ ↓ calls
117
+ Data Layer: Repository Implementations + Network + Persistence
118
+ ```
119
+
120
+ - Domain layer has zero dependencies on UIKit, SwiftUI, or any specific framework
121
+ - Repository protocols defined in the domain layer, implemented in the data layer
122
+ - Use cases encapsulate single business operations: `FetchUserProfileUseCase`, `SubmitOrderUseCase`
123
+
124
+ ### Android Architecture Patterns
125
+
126
+ **MVVM with StateFlow**
127
+
128
+ The Google-recommended pattern, aligning with Android's official architecture guidance:
129
+
130
+ ```kotlin
131
+ data class UserProfileUiState(
132
+ val user: User? = null,
133
+ val isLoading: Boolean = false,
134
+ val error: String? = null
135
+ )
136
+
137
+ @HiltViewModel
138
+ class UserProfileViewModel @Inject constructor(
139
+ private val userRepository: UserRepository
140
+ ) : ViewModel() {
141
+
142
+ private val _uiState = MutableStateFlow(UserProfileUiState())
143
+ val uiState: StateFlow<UserProfileUiState> = _uiState.asStateFlow()
144
+
145
+ fun loadUser(userId: String) {
146
+ viewModelScope.launch {
147
+ _uiState.update { it.copy(isLoading = true) }
148
+ userRepository.fetchUser(userId)
149
+ .onSuccess { user ->
150
+ _uiState.update { it.copy(user = user, isLoading = false) }
151
+ }
152
+ .onFailure { error ->
153
+ _uiState.update { it.copy(error = error.message, isLoading = false) }
154
+ }
155
+ }
156
+ }
157
+ }
158
+
159
+ @Composable
160
+ fun UserProfileScreen(viewModel: UserProfileViewModel = hiltViewModel()) {
161
+ val uiState by viewModel.uiState.collectAsStateWithLifecycle()
162
+ // render based on uiState
163
+ }
164
+ ```
165
+
166
+ **MVI (Model-View-Intent)**
167
+
168
+ For features with complex state machines where MVVM state updates become hard to reason about:
169
+
170
+ ```kotlin
171
+ sealed class UserProfileIntent {
172
+ data class LoadUser(val userId: String) : UserProfileIntent()
173
+ data object Refresh : UserProfileIntent()
174
+ }
175
+
176
+ sealed class UserProfileEffect {
177
+ data class ShowError(val message: String) : UserProfileEffect()
178
+ data object NavigateToLogin : UserProfileEffect()
179
+ }
180
+ ```
181
+
182
+ MVI separates user intentions from state mutations. The `SharedFlow` channel (`_effect`) handles one-shot events (navigation, toasts) that must not survive recomposition — a critical distinction from `StateFlow`.
183
+
184
+ **One-shot events vs. state**
185
+ - Use `StateFlow` for persistent UI state: loading, data, errors that survive recomposition
186
+ - Use `SharedFlow` or `Channel` (as `Flow`) for one-shot effects: navigation commands, snackbar messages, dialog triggers
187
+ - Never put navigation events in `StateFlow` — they replay on configuration change, causing double navigation
188
+
189
+ **Clean Architecture layers for Android**
190
+ ```
191
+ UI Layer: Composables + ViewModels
192
+ ↓ calls
193
+ Domain Layer: Use Cases + Domain Models + Repository Interfaces
194
+ ↓ calls
195
+ Data Layer: Repository Implementations + RemoteDataSource + LocalDataSource
196
+ ```
197
+
198
+ - Domain layer: pure Kotlin module (`core/domain`) with no Android dependencies
199
+ - Use cases: single `operator fun invoke()` or `execute()` function
200
+ - Repository pattern: the domain defines the interface; data layer implements it
201
+
202
+ ### Dependency Injection
203
+
204
+ **Android: Hilt**
205
+
206
+ Hilt is the recommended DI framework for Android:
207
+
208
+ ```kotlin
209
+ // Define a module
210
+ @Module
211
+ @InstallIn(SingletonComponent::class)
212
+ object NetworkModule {
213
+ @Provides
214
+ @Singleton
215
+ fun provideOkHttpClient(): OkHttpClient = OkHttpClient.Builder().build()
216
+
217
+ @Provides
218
+ @Singleton
219
+ fun provideRetrofit(client: OkHttpClient): Retrofit = Retrofit.Builder()
220
+ .baseUrl(BuildConfig.BASE_URL)
221
+ .client(client)
222
+ .addConverterFactory(GsonConverterFactory.create())
223
+ .build()
224
+ }
225
+
226
+ // Inject into ViewModel
227
+ @HiltViewModel
228
+ class HomeViewModel @Inject constructor(
229
+ private val userRepository: UserRepository,
230
+ private val analyticsService: AnalyticsService
231
+ ) : ViewModel()
232
+ ```
233
+
234
+ Hilt scopes: `SingletonComponent` (app lifetime), `ActivityRetainedComponent` (ViewModel lifetime), `ViewModelComponent` (ViewModel scope), `FragmentComponent`, `ActivityComponent`. Match the scope to the dependency's actual lifetime.
235
+
236
+ **iOS: Constructor injection + container**
237
+
238
+ Swift does not have a dominant DI framework. Use constructor injection as the default:
239
+
240
+ ```swift
241
+ // Dependency container
242
+ final class AppDependencies {
243
+ static let shared = AppDependencies()
244
+
245
+ lazy var networkClient: NetworkClient = URLSessionNetworkClient()
246
+ lazy var userRepository: UserRepository = NetworkUserRepository(client: networkClient)
247
+ lazy var analyticsService: AnalyticsService = FirebaseAnalyticsService()
248
+ }
249
+
250
+ // Inject at the composition root (app entry point or Coordinator)
251
+ let viewModel = UserProfileViewModel(
252
+ repository: AppDependencies.shared.userRepository
253
+ )
254
+ ```
255
+
256
+ For testing: define protocols for all dependencies and inject fakes in tests. Never call `AppDependencies.shared` inside a ViewModel — inject via constructor.
257
+
258
+ ### State Management
259
+
260
+ **iOS state scoping**
261
+ - `@State`: view-local ephemeral state (animation flags, text field values) — does not survive view destruction
262
+ - `@Binding`: two-way binding from parent to child — child can mutate parent's state
263
+ - `@Observable` / `@ObservableObject`: shared mutable state in a ViewModel — survives view re-renders
264
+ - `@Environment`: dependency injection through the view tree (theme, locale, custom services)
265
+ - `@EnvironmentObject`: globally shared state accessed without explicit passing — use sparingly, only for truly app-wide state (user session, theme)
266
+ - Avoid prop-drilling state through 4+ view layers — use `@Environment` or restructure to lift state to a shared ancestor ViewModel
267
+
268
+ **Android state scoping**
269
+ - `remember { }`: view-local ephemeral state in Compose — survives recomposition, not configuration change
270
+ - `rememberSaveable { }`: survives configuration change by saving to Bundle
271
+ - `StateFlow` in ViewModel: survives configuration change automatically (ViewModel lifecycle)
272
+ - `SavedStateHandle` in ViewModel: persists through process death for critical state (form data, scroll position)
273
+ - Hoist state to the lowest ancestor that needs it — do not hoist everything to the ViewModel
274
+
275
+ **Handling configuration changes (Android)**
276
+ - ViewModel automatically survives rotation and theme change — the primary benefit of the ViewModel
277
+ - Always collect `StateFlow` with `collectAsStateWithLifecycle()` in Compose — stops collection when UI is not visible, preventing wasted work and crashes in background
278
+
279
+ **Background state handling (iOS)**
280
+ - ScenePhase: observe `\.scenePhase` in SwiftUI to react to foreground/background transitions
281
+ - Save in-progress work when entering background: `@Environment(\.scenePhase) var scenePhase`
282
+ - `@AppStorage`: lightweight persistence backed by UserDefaults for small values
283
+ - Combine state from multiple sources with `Publishers.CombineLatest` or async/await TaskGroup