triples-agentic 2.4.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 (77) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +326 -0
  3. package/docs/workflow.md +163 -0
  4. package/install.sh +98 -0
  5. package/package.json +54 -0
  6. package/src/agents/README.md +85 -0
  7. package/src/agents/jiwoo-prd.md +84 -0
  8. package/src/agents/kaede-backend.md +95 -0
  9. package/src/agents/kotone-flutter.md +100 -0
  10. package/src/agents/lynn-testcase.md +92 -0
  11. package/src/agents/nakyoung-tasks.md +89 -0
  12. package/src/agents/seoyeon.md +76 -0
  13. package/src/agents/shion-qa.md +89 -0
  14. package/src/agents/sohyun-ios.md +97 -0
  15. package/src/agents/yeonji-android.md +98 -0
  16. package/src/agents/yooyeon-rfc.md +82 -0
  17. package/src/agents/yubin-frontend.md +88 -0
  18. package/src/bin/setup.js +640 -0
  19. package/src/hooks/README.md +102 -0
  20. package/src/hooks/dangerous-commands.json +33 -0
  21. package/src/hooks/dangerous-commands.md +18 -0
  22. package/src/knowledge/README.md +129 -0
  23. package/src/knowledge/general/boy-scout-rule.md +13 -0
  24. package/src/knowledge/general/composition-over-inheritance.md +14 -0
  25. package/src/knowledge/general/dry.md +14 -0
  26. package/src/knowledge/general/fail-fast.md +13 -0
  27. package/src/knowledge/general/kiss.md +15 -0
  28. package/src/knowledge/general/least-surprise.md +13 -0
  29. package/src/knowledge/general/slap.md +29 -0
  30. package/src/knowledge/general/solid.md +44 -0
  31. package/src/knowledge/general/tdd.md +76 -0
  32. package/src/knowledge/general/yagni.md +12 -0
  33. package/src/knowledge/mobile/android/android-architecture.md +83 -0
  34. package/src/knowledge/mobile/android/android-platform.md +60 -0
  35. package/src/knowledge/mobile/android/kotlin-concurrency.md +75 -0
  36. package/src/knowledge/mobile/android/kotlin-core.md +88 -0
  37. package/src/knowledge/mobile/flutter/dart-async.md +93 -0
  38. package/src/knowledge/mobile/flutter/dart-core.md +97 -0
  39. package/src/knowledge/mobile/flutter/flutter-architecture.md +88 -0
  40. package/src/knowledge/mobile/flutter/flutter-platform.md +79 -0
  41. package/src/knowledge/mobile/ios/ios-architecture.md +88 -0
  42. package/src/knowledge/mobile/ios/ios-platform.md +66 -0
  43. package/src/knowledge/mobile/ios/swift-concurrency.md +99 -0
  44. package/src/knowledge/mobile/ios/swift-core.md +79 -0
  45. package/src/knowledge/planning/architecture-database.md +47 -0
  46. package/src/knowledge/planning/architecture-patterns.md +64 -0
  47. package/src/knowledge/planning/architecture-security.md +61 -0
  48. package/src/knowledge/planning/estimation.md +82 -0
  49. package/src/knowledge/planning/orchestration.md +70 -0
  50. package/src/knowledge/planning/prd-quality-gates.md +38 -0
  51. package/src/knowledge/planning/prd-writing.md +59 -0
  52. package/src/knowledge/planning/product-principles.md +48 -0
  53. package/src/knowledge/planning/product-prioritization.md +45 -0
  54. package/src/knowledge/planning/rfc-quality-gates.md +38 -0
  55. package/src/knowledge/planning/rfc-writing.md +81 -0
  56. package/src/knowledge/planning/task-decomposition.md +61 -0
  57. package/src/knowledge/planning/task-readiness.md +64 -0
  58. package/src/knowledge/quality/qa-execution.md +55 -0
  59. package/src/knowledge/quality/qa-reporting.md +71 -0
  60. package/src/knowledge/quality/test-case-quality.md +61 -0
  61. package/src/knowledge/quality/test-case-writing.md +76 -0
  62. package/src/knowledge/quality/testing-strategy.md +70 -0
  63. package/src/knowledge/quality/testing-types.md +84 -0
  64. package/src/knowledge/web/backend/api-design.md +74 -0
  65. package/src/knowledge/web/backend/api-security.md +54 -0
  66. package/src/knowledge/web/backend/backend-security.md +84 -0
  67. package/src/knowledge/web/backend/backend-structure.md +78 -0
  68. package/src/knowledge/web/frontend/frontend-components.md +49 -0
  69. package/src/knowledge/web/frontend/frontend-performance.md +41 -0
  70. package/src/knowledge/web/frontend/frontend-state.md +59 -0
  71. package/src/knowledge/web/frontend/web-accessibility.md +51 -0
  72. package/src/knowledge/web/frontend/web-performance.md +51 -0
  73. package/src/knowledge/web/frontend/web-security.md +59 -0
  74. package/src/templates/prd.md +109 -0
  75. package/src/templates/rfc.md +156 -0
  76. package/src/templates/task-breakdown.md +172 -0
  77. package/src/templates/test-case.md +157 -0
@@ -0,0 +1,60 @@
1
+ ---
2
+ name: android-platform
3
+ description: Android Material Design 3, local storage, background work, testing, and Play Store requirements
4
+ ---
5
+
6
+ # Android — Platform & Distribution
7
+
8
+ ## Material Design 3
9
+
10
+ - Use MD3 components (`Button`, `Card`, `TextField`, `TopAppBar`, `NavigationBar`)
11
+ - Color roles: `primary`, `secondary`, `tertiary`, `error`, `surface`, `background`
12
+ - Dynamic color (Material You): supported on Android 12+ with `dynamicColorScheme()`
13
+ - Always support dark mode from day one: `isSystemInDarkTheme()`
14
+ - Use `dp` for all dimensions, never hardcode pixel values
15
+
16
+ ## Local Storage
17
+
18
+ | Use case | Solution |
19
+ |---|---|
20
+ | Structured relational data | Room |
21
+ | Simple settings (non-sensitive) | DataStore (Preferences) |
22
+ | Sensitive data (tokens) | EncryptedSharedPreferences |
23
+ | Complex typed settings | DataStore (Proto) |
24
+ | Binary files, documents | FileManager |
25
+
26
+ Never use SharedPreferences for sensitive data.
27
+
28
+ ## Background Work
29
+
30
+ - **WorkManager** — guaranteed background tasks (syncs, uploads, notifications scheduling)
31
+ - **Coroutines + Dispatchers.IO** — async I/O on demand within the app lifecycle
32
+ - Never do network/disk I/O on `Dispatchers.Main` (UI thread)
33
+
34
+ ```kotlin
35
+ viewModelScope.launch(Dispatchers.IO) {
36
+ val result = repository.fetchData()
37
+ withContext(Dispatchers.Main) { _uiState.update { it.copy(data = result) } }
38
+ }
39
+ ```
40
+
41
+ ## Testing
42
+
43
+ - **Unit tests**: ViewModel + use cases — JUnit 5, Turbine (Flow testing), Mockk
44
+ - **UI tests**: Composable testing with `ComposeTestRule`
45
+ - **Integration tests**: Room in-memory database; Retrofit MockWebServer
46
+
47
+ ## Performance
48
+
49
+ - Profile with Android Studio Profiler (CPU, Memory, Network tabs)
50
+ - Use `LazyColumn` / `LazyRow` for large lists — never `Column` + scroll for dynamic data
51
+ - Avoid object allocation in Composable functions (causes GC pressure)
52
+ - Minimize recompositions: stable types, `remember`, `derivedStateOf`
53
+
54
+ ## Play Store Requirements
55
+
56
+ - Target latest stable `targetSdkVersion` (within one year of release)
57
+ - 64-bit support required
58
+ - Privacy declarations for all permissions and data collected
59
+ - App Bundle (`.aab`) format required for new apps
60
+ - Content rating questionnaire must be completed
@@ -0,0 +1,75 @@
1
+ ---
2
+ name: kotlin-concurrency
3
+ description: Kotlin coroutines, Flow, scope functions, and async/await patterns for Android development
4
+ ---
5
+
6
+ # Kotlin — Coroutines & Concurrency
7
+
8
+ ## Coroutines Basics
9
+
10
+ ```kotlin
11
+ // Launch fire-and-forget
12
+ viewModelScope.launch {
13
+ val result = repository.fetchUser(id) // suspend function
14
+ _uiState.update { it.copy(user = result) }
15
+ }
16
+
17
+ // Async/await for parallel execution
18
+ val (user, orders) = awaitAll(
19
+ async { repository.fetchUser(id) },
20
+ async { repository.fetchOrders(id) }
21
+ )
22
+ ```
23
+
24
+ ## Flow
25
+
26
+ ```kotlin
27
+ // Cold stream — executes per collector
28
+ val userFlow: Flow<User> = repository.observeUser(id)
29
+
30
+ viewModelScope.launch {
31
+ userFlow
32
+ .map { user -> user.toUiModel() }
33
+ .catch { e -> handleError(e) }
34
+ .collect { uiModel -> _uiState.update { it.copy(user = uiModel) } }
35
+ }
36
+
37
+ // StateFlow — hot, always has a value
38
+ private val _uiState = MutableStateFlow(LoginUiState())
39
+ val uiState: StateFlow<LoginUiState> = _uiState.asStateFlow()
40
+ ```
41
+
42
+ ## Dispatchers
43
+
44
+ | Dispatcher | Use for |
45
+ |---|---|
46
+ | `Dispatchers.Main` | UI updates, collect StateFlow |
47
+ | `Dispatchers.IO` | Network, file, database operations |
48
+ | `Dispatchers.Default` | CPU-intensive work (parsing, sorting large lists) |
49
+
50
+ Never block the Main dispatcher.
51
+
52
+ ## Scope Functions
53
+
54
+ | Function | Context | Returns | Use when |
55
+ |---|---|---|---|
56
+ | `let` | `it` | Lambda result | Null checks, transforming result |
57
+ | `run` | `this` | Lambda result | Object configuration + compute |
58
+ | `with` | `this` | Lambda result | Multiple calls on same object |
59
+ | `apply` | `this` | Context object | Object initialization |
60
+ | `also` | `it` | Context object | Side effects (logging) |
61
+
62
+ ## Error Handling in Coroutines
63
+
64
+ ```kotlin
65
+ viewModelScope.launch {
66
+ try {
67
+ val result = repository.fetchData()
68
+ _uiState.update { it.copy(data = result, error = null) }
69
+ } catch (e: HttpException) {
70
+ _uiState.update { it.copy(error = "Network error: ${e.message}") }
71
+ } catch (e: CancellationException) {
72
+ throw e // always rethrow CancellationException
73
+ }
74
+ }
75
+ ```
@@ -0,0 +1,88 @@
1
+ ---
2
+ name: kotlin-core
3
+ description: Kotlin null safety, data classes, sealed classes, extension functions, and idiomatic collection operations
4
+ ---
5
+
6
+ # Kotlin — Core Language
7
+
8
+ ## Null Safety
9
+
10
+ ```kotlin
11
+ // Nullable type explicitly declared
12
+ var name: String? = null
13
+
14
+ // Safe call — returns null instead of NPE
15
+ val length = name?.length
16
+
17
+ // Elvis operator — provide fallback
18
+ val displayName = name ?: "Anonymous"
19
+
20
+ // Non-null assertion — only when truly certain
21
+ val definiteLength = name!!.length // avoid unless necessary
22
+
23
+ // Let for null-checking scope
24
+ name?.let { nonNullName ->
25
+ println("Name: $nonNullName")
26
+ }
27
+ ```
28
+
29
+ ## Data Classes
30
+
31
+ ```kotlin
32
+ data class User(
33
+ val id: String,
34
+ val email: String,
35
+ val name: String,
36
+ )
37
+ // Auto-generates: equals(), hashCode(), toString(), copy()
38
+ val updated = user.copy(name = "New Name")
39
+ ```
40
+
41
+ ## Sealed Classes & When
42
+
43
+ ```kotlin
44
+ sealed class Result<out T> {
45
+ data class Success<T>(val data: T) : Result<T>()
46
+ data class Error(val exception: Exception) : Result<Nothing>()
47
+ object Loading : Result<Nothing>()
48
+ }
49
+
50
+ // Exhaustive when — no else needed
51
+ when (result) {
52
+ is Result.Success -> showData(result.data)
53
+ is Result.Error -> showError(result.exception)
54
+ is Result.Loading -> showSpinner()
55
+ }
56
+ ```
57
+
58
+ ## Extension Functions
59
+
60
+ ```kotlin
61
+ fun String.toTitleCase(): String =
62
+ split(" ").joinToString(" ") { it.replaceFirstChar { c -> c.uppercase() } }
63
+
64
+ // Usage: "hello world".toTitleCase() → "Hello World"
65
+ ```
66
+
67
+ ## Collections
68
+
69
+ ```kotlin
70
+ val names: List<String> = listOf("Alice", "Bob") // immutable
71
+ val mutableNames = mutableListOf("Alice") // mutable
72
+
73
+ // Functional operations
74
+ val activeAdultUsers = users
75
+ .filter { it.isActive && it.age >= 18 }
76
+ .map { UserSummary(it.id, it.name) }
77
+ .sortedBy { it.name }
78
+ .take(10)
79
+ ```
80
+
81
+ ## Idiomatic Kotlin Rules
82
+
83
+ - Prefer `val` over `var` — immutability by default
84
+ - Prefer `data class` over plain class for DTOs and models
85
+ - Use `object` for singletons
86
+ - Named arguments for functions with 3+ parameters
87
+ - `when` over chained `if/else if`
88
+ - String templates over concatenation: `"Hello, $name!"` not `"Hello, " + name + "!"`
@@ -0,0 +1,93 @@
1
+ ---
2
+ name: dart-async
3
+ description: Dart async/await, Future, Stream, error handling, mixins, and extension methods
4
+ ---
5
+
6
+ # Dart — Async, Streams & Advanced Patterns
7
+
8
+ ## Future — One-time async value
9
+
10
+ ```dart
11
+ Future<User> fetchUser(String id) async {
12
+ final response = await http.get(Uri.parse('/users/$id'));
13
+ return User.fromJson(jsonDecode(response.body) as Map<String, dynamic>);
14
+ }
15
+
16
+ // Error handling
17
+ try {
18
+ final user = await fetchUser(id);
19
+ print(user.name);
20
+ } on SocketException {
21
+ throw NetworkException('No internet connection');
22
+ } catch (e) {
23
+ rethrow; // preserve stack trace
24
+ }
25
+ ```
26
+
27
+ ## Stream — Sequence of async values
28
+
29
+ ```dart
30
+ Stream<List<Message>> watchMessages(String roomId) {
31
+ return firestore
32
+ .collection('messages')
33
+ .where('roomId', isEqualTo: roomId)
34
+ .snapshots()
35
+ .map((snap) => snap.docs.map(Message.fromDoc).toList());
36
+ }
37
+
38
+ // Consuming a stream
39
+ await for (final messages in watchMessages(roomId)) {
40
+ updateUI(messages);
41
+ }
42
+ ```
43
+
44
+ ## FutureBuilder & StreamBuilder (Flutter widgets)
45
+
46
+ ```dart
47
+ FutureBuilder<User>(
48
+ future: fetchUser(userId),
49
+ builder: (context, snapshot) {
50
+ if (snapshot.connectionState == ConnectionState.waiting) {
51
+ return const CircularProgressIndicator();
52
+ }
53
+ if (snapshot.hasError) return Text('Error: ${snapshot.error}');
54
+ return Text(snapshot.data!.name);
55
+ },
56
+ )
57
+ ```
58
+
59
+ Prefer Riverpod's `AsyncNotifier` over `FutureBuilder` in new code — it handles caching and state more cleanly.
60
+
61
+ ## Mixins
62
+
63
+ ```dart
64
+ mixin Serializable {
65
+ Map<String, dynamic> toJson();
66
+ String toJsonString() => jsonEncode(toJson());
67
+ }
68
+
69
+ class User with Serializable {
70
+ final String name;
71
+ User(this.name);
72
+ @override
73
+ Map<String, dynamic> toJson() => {'name': name};
74
+ }
75
+ ```
76
+
77
+ ## Extension Methods
78
+
79
+ ```dart
80
+ extension StringExtension on String {
81
+ String get titleCase =>
82
+ split(' ').map((w) => w[0].toUpperCase() + w.substring(1)).join(' ');
83
+ }
84
+
85
+ // Usage
86
+ 'hello world'.titleCase // 'Hello World'
87
+
88
+ extension ListExtension<T> on List<T> {
89
+ T? firstWhereOrNull(bool Function(T) test) {
90
+ try { return firstWhere(test); } catch (_) { return null; }
91
+ }
92
+ }
93
+ ```
@@ -0,0 +1,97 @@
1
+ ---
2
+ name: dart-core
3
+ description: Dart null safety, core types, classes and constructors, enums, and idiomatic collection operations
4
+ ---
5
+
6
+ # Dart — Core Language
7
+
8
+ ## Null Safety
9
+
10
+ ```dart
11
+ String name = 'Alice'; // Non-nullable
12
+ String? nickname; // Nullable
13
+
14
+ // Null-aware operators
15
+ String display = nickname ?? 'No nickname'; // fallback
16
+ int? length = nickname?.length; // safe access
17
+ nickname ??= 'Default'; // assign if null
18
+ String definite = nickname!; // assert non-null (throws if null)
19
+ ```
20
+
21
+ ## Core Types
22
+
23
+ ```dart
24
+ int age = 25;
25
+ double price = 9.99;
26
+ bool isActive = true;
27
+ String name = 'Alice';
28
+
29
+ List<String> tags = ['flutter', 'dart']; // immutable
30
+ Set<int> ids = {1, 2, 3};
31
+ Map<String, dynamic> data = {'name': 'Alice', 'age': 25};
32
+
33
+ // Const — compile-time constants
34
+ const pi = 3.14159;
35
+ const colors = ['red', 'green', 'blue'];
36
+ ```
37
+
38
+ ## Classes & Constructors
39
+
40
+ ```dart
41
+ class User {
42
+ final String id;
43
+ final String email;
44
+ String name;
45
+
46
+ // Named constructor with initializer list
47
+ User({required this.id, required this.email, required this.name});
48
+
49
+ // Named constructor
50
+ User.anonymous() : id = '', email = '', name = 'Guest';
51
+
52
+ // Factory constructor
53
+ factory User.fromJson(Map<String, dynamic> json) {
54
+ return User(id: json['id'], email: json['email'], name: json['name']);
55
+ }
56
+
57
+ // copyWith pattern
58
+ User copyWith({String? name}) =>
59
+ User(id: id, email: email, name: name ?? this.name);
60
+ }
61
+ ```
62
+
63
+ ## Enhanced Enums (Dart 2.17+)
64
+
65
+ ```dart
66
+ enum PaymentStatus {
67
+ pending('Pending', Colors.orange),
68
+ completed('Completed', Colors.green),
69
+ failed('Failed', Colors.red);
70
+
71
+ const PaymentStatus(this.label, this.color);
72
+ final String label;
73
+ final Color color;
74
+ }
75
+
76
+ // Usage
77
+ PaymentStatus.completed.label // 'Completed'
78
+ PaymentStatus.completed.color // Colors.green
79
+ ```
80
+
81
+ ## Functional Collections
82
+
83
+ ```dart
84
+ final activeAdults = users
85
+ .where((u) => u.isActive && u.age >= 18)
86
+ .map((u) => UserSummary(u.id, u.name))
87
+ .toList();
88
+
89
+ final total = orders.fold(0.0, (sum, order) => sum + order.total);
90
+ ```
91
+
92
+ ## Dart Style
93
+
94
+ - `lowerCamelCase` for variables/functions, `UpperCamelCase` for classes
95
+ - Prefer `final` over `var`; `const` over `final` where possible
96
+ - Format with `dart format` (built-in, no Prettier needed)
97
+ - Lint with `flutter_lints` or `very_good_analysis`
@@ -0,0 +1,88 @@
1
+ ---
2
+ name: flutter-architecture
3
+ description: Flutter architecture with Riverpod, widget composition, GoRouter navigation, and state management patterns
4
+ ---
5
+
6
+ # Flutter Architecture
7
+
8
+ ## Architecture: Riverpod + Clean Layers
9
+
10
+ ```
11
+ UI Layer (Widgets)
12
+ ↕ ref.watch / Consumer
13
+ Riverpod Provider (state + business logic)
14
+ ↕ Repository interface
15
+ Data Layer (API, Local DB, Cache)
16
+ ```
17
+
18
+ ## Widget Philosophy
19
+
20
+ Everything in Flutter is a widget. Widgets are immutable — they rebuild cheaply.
21
+
22
+ ```dart
23
+ // Stateless: no mutable state
24
+ class UserCard extends StatelessWidget {
25
+ const UserCard({super.key, required this.user});
26
+ final User user;
27
+
28
+ @override
29
+ Widget build(BuildContext context) => Card(child: Text(user.name));
30
+ }
31
+ ```
32
+
33
+ ## Riverpod — State Management
34
+
35
+ ```dart
36
+ // AsyncNotifier for complex state
37
+ class OrdersNotifier extends AsyncNotifier<List<Order>> {
38
+ @override
39
+ Future<List<Order>> build() =>
40
+ ref.read(orderRepoProvider).fetchOrders();
41
+
42
+ Future<void> placeOrder(OrderRequest request) async {
43
+ state = const AsyncLoading();
44
+ state = await AsyncValue.guard(
45
+ () => ref.read(orderRepoProvider).place(request),
46
+ );
47
+ }
48
+ }
49
+
50
+ final ordersProvider =
51
+ AsyncNotifierProvider<OrdersNotifier, List<Order>>(OrdersNotifier.new);
52
+
53
+ // Simple FutureProvider
54
+ final userProvider = FutureProvider.family<User, String>(
55
+ (ref, userId) => ref.read(userRepoProvider).fetchUser(userId),
56
+ );
57
+ ```
58
+
59
+ ## Navigation — GoRouter
60
+
61
+ ```dart
62
+ final router = GoRouter(
63
+ routes: [
64
+ GoRoute(path: '/', builder: (_, __) => const HomeScreen()),
65
+ GoRoute(
66
+ path: '/user/:id',
67
+ builder: (ctx, state) =>
68
+ UserScreen(userId: state.pathParameters['id']!),
69
+ ),
70
+ ],
71
+ );
72
+ ```
73
+
74
+ ## Networking — Dio
75
+
76
+ ```dart
77
+ final dio = Dio(BaseOptions(
78
+ baseUrl: 'https://api.example.com',
79
+ headers: {'Content-Type': 'application/json'},
80
+ ));
81
+
82
+ dio.interceptors.add(AuthInterceptor(tokenService));
83
+
84
+ Future<User> fetchUser(String id) async {
85
+ final response = await dio.get('/users/$id');
86
+ return User.fromJson(response.data as Map<String, dynamic>);
87
+ }
88
+ ```
@@ -0,0 +1,79 @@
1
+ ---
2
+ name: flutter-platform
3
+ description: Flutter Material 3, local storage, platform channels, performance, testing, and publishing for Android and iOS
4
+ ---
5
+
6
+ # Flutter — Platform & Distribution
7
+
8
+ ## Material 3 Theming
9
+
10
+ ```dart
11
+ MaterialApp(
12
+ theme: ThemeData(
13
+ useMaterial3: true,
14
+ colorScheme: ColorScheme.fromSeed(seedColor: Colors.indigo),
15
+ ),
16
+ darkTheme: ThemeData(
17
+ useMaterial3: true,
18
+ colorScheme: ColorScheme.fromSeed(
19
+ seedColor: Colors.indigo,
20
+ brightness: Brightness.dark,
21
+ ),
22
+ ),
23
+ )
24
+ ```
25
+
26
+ ## Local Storage
27
+
28
+ | Use case | Package |
29
+ |---|---|
30
+ | Structured data | Hive or Isar |
31
+ | Relational data | drift (SQLite) |
32
+ | Simple key-value | shared_preferences |
33
+ | Sensitive data (tokens) | flutter_secure_storage |
34
+ | Files | path_provider |
35
+
36
+ Never store credentials in SharedPreferences.
37
+
38
+ ## Platform Channels (when Flutter doesn't cover a native feature)
39
+
40
+ ```dart
41
+ const platform = MethodChannel('com.example.app/biometrics');
42
+ final bool result = await platform.invokeMethod<bool>('authenticate') ?? false;
43
+ ```
44
+
45
+ Prefer existing pub.dev packages before writing platform channels. Check:
46
+ 1. Pub points (quality score)
47
+ 2. Last published date
48
+ 3. Platform support (Android/iOS/Web checkboxes)
49
+
50
+ ## Performance
51
+
52
+ - `const` constructor everywhere possible — prevents unnecessary rebuilds
53
+ - `ListView.builder` / `GridView.builder` for long lists (lazy rendering)
54
+ - Avoid `setState` high in the widget tree — keep state low
55
+ - Profile with Flutter DevTools (Timeline, Widget Rebuilds inspector)
56
+ - `RepaintBoundary` around complex widgets that change independently
57
+
58
+ ## Testing
59
+
60
+ ```dart
61
+ // Widget test
62
+ testWidgets('shows user name', (tester) async {
63
+ await tester.pumpWidget(const MaterialApp(home: UserCard(user: mockUser)));
64
+ expect(find.text(mockUser.name), findsOneWidget);
65
+ });
66
+
67
+ // Unit test
68
+ test('formats currency correctly', () {
69
+ expect(formatCurrency(1234.56), '\$1,234.56');
70
+ });
71
+ ```
72
+
73
+ ## Publishing
74
+
75
+ - **Android**: `flutter build appbundle --release` → `.aab`
76
+ - **iOS**: `flutter build ios --release` → archive via Xcode
77
+ - Version: `version: 1.0.0+1` in `pubspec.yaml` (semantic version + build number)
78
+ - Icons: `flutter_launcher_icons` package
79
+ - Splash screen: `flutter_native_splash` package
@@ -0,0 +1,88 @@
1
+ ---
2
+ name: ios-architecture
3
+ description: iOS MVVM architecture, SwiftUI view design, state management (@Observable, @ObservableObject), and networking
4
+ ---
5
+
6
+ # iOS Architecture
7
+
8
+ ## Architecture Pattern: MVVM
9
+
10
+ ```
11
+ View (SwiftUI) — observes ViewModel state
12
+ ↕ @Published / @Observable
13
+ ViewModel — business logic, state management
14
+ ↕ async/await
15
+ Repository / Service — data access, network calls
16
+ ```
17
+
18
+ ## SwiftUI View Design
19
+
20
+ - Views are value types (structs) — cheap to recreate
21
+ - Extract subviews into separate structs when body exceeds ~30 lines
22
+ - `@State` for local ephemeral state only
23
+ - `@StateObject` / `@ObservedObject` for ViewModels
24
+ - `@EnvironmentObject` for app-wide state (theme, auth context)
25
+
26
+ ```swift
27
+ // View observes ViewModel — correct pattern
28
+ struct LoginView: View {
29
+ @StateObject private var viewModel = LoginViewModel()
30
+
31
+ var body: some View {
32
+ VStack {
33
+ TextField("Email", text: $viewModel.email)
34
+ Button("Log in") { viewModel.login() }
35
+ }
36
+ }
37
+ }
38
+ ```
39
+
40
+ ## State Management
41
+
42
+ ```swift
43
+ // @Observable macro (iOS 17+, Swift 5.9)
44
+ @Observable
45
+ class LoginViewModel {
46
+ var email = ""
47
+ var isLoading = false
48
+
49
+ func login() async {
50
+ isLoading = true
51
+ defer { isLoading = false }
52
+ // network call
53
+ }
54
+ }
55
+
56
+ // @ObservableObject (iOS 13+ compatibility)
57
+ class LoginViewModel: ObservableObject {
58
+ @Published var email = ""
59
+ @Published var isLoading = false
60
+ }
61
+ ```
62
+
63
+ ## Networking — async/await
64
+
65
+ ```swift
66
+ func fetchUser(id: String) async throws -> User {
67
+ let url = URL(string: "https://api.example.com/users/\(id)")!
68
+ let (data, response) = try await URLSession.shared.data(from: url)
69
+ guard let http = response as? HTTPURLResponse, http.statusCode == 200 else {
70
+ throw APIError.invalidResponse
71
+ }
72
+ return try JSONDecoder().decode(User.self, from: data)
73
+ }
74
+ ```
75
+
76
+ ## Navigation — NavigationStack (iOS 16+)
77
+
78
+ ```swift
79
+ NavigationStack(path: $navigationPath) {
80
+ HomeView()
81
+ .navigationDestination(for: UserRoute.self) { route in
82
+ switch route {
83
+ case .profile(let id): UserProfileView(userId: id)
84
+ case .settings: SettingsView()
85
+ }
86
+ }
87
+ }
88
+ ```