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.
- package/LICENSE +21 -0
- package/README.md +326 -0
- package/docs/workflow.md +163 -0
- package/install.sh +98 -0
- package/package.json +54 -0
- package/src/agents/README.md +85 -0
- package/src/agents/jiwoo-prd.md +84 -0
- package/src/agents/kaede-backend.md +95 -0
- package/src/agents/kotone-flutter.md +100 -0
- package/src/agents/lynn-testcase.md +92 -0
- package/src/agents/nakyoung-tasks.md +89 -0
- package/src/agents/seoyeon.md +76 -0
- package/src/agents/shion-qa.md +89 -0
- package/src/agents/sohyun-ios.md +97 -0
- package/src/agents/yeonji-android.md +98 -0
- package/src/agents/yooyeon-rfc.md +82 -0
- package/src/agents/yubin-frontend.md +88 -0
- package/src/bin/setup.js +640 -0
- package/src/hooks/README.md +102 -0
- package/src/hooks/dangerous-commands.json +33 -0
- package/src/hooks/dangerous-commands.md +18 -0
- package/src/knowledge/README.md +129 -0
- package/src/knowledge/general/boy-scout-rule.md +13 -0
- package/src/knowledge/general/composition-over-inheritance.md +14 -0
- package/src/knowledge/general/dry.md +14 -0
- package/src/knowledge/general/fail-fast.md +13 -0
- package/src/knowledge/general/kiss.md +15 -0
- package/src/knowledge/general/least-surprise.md +13 -0
- package/src/knowledge/general/slap.md +29 -0
- package/src/knowledge/general/solid.md +44 -0
- package/src/knowledge/general/tdd.md +76 -0
- package/src/knowledge/general/yagni.md +12 -0
- package/src/knowledge/mobile/android/android-architecture.md +83 -0
- package/src/knowledge/mobile/android/android-platform.md +60 -0
- package/src/knowledge/mobile/android/kotlin-concurrency.md +75 -0
- package/src/knowledge/mobile/android/kotlin-core.md +88 -0
- package/src/knowledge/mobile/flutter/dart-async.md +93 -0
- package/src/knowledge/mobile/flutter/dart-core.md +97 -0
- package/src/knowledge/mobile/flutter/flutter-architecture.md +88 -0
- package/src/knowledge/mobile/flutter/flutter-platform.md +79 -0
- package/src/knowledge/mobile/ios/ios-architecture.md +88 -0
- package/src/knowledge/mobile/ios/ios-platform.md +66 -0
- package/src/knowledge/mobile/ios/swift-concurrency.md +99 -0
- package/src/knowledge/mobile/ios/swift-core.md +79 -0
- package/src/knowledge/planning/architecture-database.md +47 -0
- package/src/knowledge/planning/architecture-patterns.md +64 -0
- package/src/knowledge/planning/architecture-security.md +61 -0
- package/src/knowledge/planning/estimation.md +82 -0
- package/src/knowledge/planning/orchestration.md +70 -0
- package/src/knowledge/planning/prd-quality-gates.md +38 -0
- package/src/knowledge/planning/prd-writing.md +59 -0
- package/src/knowledge/planning/product-principles.md +48 -0
- package/src/knowledge/planning/product-prioritization.md +45 -0
- package/src/knowledge/planning/rfc-quality-gates.md +38 -0
- package/src/knowledge/planning/rfc-writing.md +81 -0
- package/src/knowledge/planning/task-decomposition.md +61 -0
- package/src/knowledge/planning/task-readiness.md +64 -0
- package/src/knowledge/quality/qa-execution.md +55 -0
- package/src/knowledge/quality/qa-reporting.md +71 -0
- package/src/knowledge/quality/test-case-quality.md +61 -0
- package/src/knowledge/quality/test-case-writing.md +76 -0
- package/src/knowledge/quality/testing-strategy.md +70 -0
- package/src/knowledge/quality/testing-types.md +84 -0
- package/src/knowledge/web/backend/api-design.md +74 -0
- package/src/knowledge/web/backend/api-security.md +54 -0
- package/src/knowledge/web/backend/backend-security.md +84 -0
- package/src/knowledge/web/backend/backend-structure.md +78 -0
- package/src/knowledge/web/frontend/frontend-components.md +49 -0
- package/src/knowledge/web/frontend/frontend-performance.md +41 -0
- package/src/knowledge/web/frontend/frontend-state.md +59 -0
- package/src/knowledge/web/frontend/web-accessibility.md +51 -0
- package/src/knowledge/web/frontend/web-performance.md +51 -0
- package/src/knowledge/web/frontend/web-security.md +59 -0
- package/src/templates/prd.md +109 -0
- package/src/templates/rfc.md +156 -0
- package/src/templates/task-breakdown.md +172 -0
- 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
|
+
```
|