oh-my-customcode 0.30.7 → 0.30.9

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.
@@ -0,0 +1,44 @@
1
+ # Flutter Guide
2
+
3
+ metadata:
4
+ name: flutter
5
+ description: Flutter cross-platform development reference documentation
6
+
7
+ source:
8
+ type: external
9
+ origin: flutter.dev
10
+ urls:
11
+ - https://docs.flutter.dev/
12
+ - https://docs.flutter.dev/app-architecture
13
+ - https://riverpod.dev/docs/introduction/getting_started
14
+ - https://bloclibrary.dev/
15
+ - https://dart.dev/effective-dart
16
+ last_fetched: "2026-03-11"
17
+
18
+ documents:
19
+ - name: fundamentals
20
+ path: ./fundamentals.md
21
+ description: Flutter widget system, BuildContext, rendering pipeline
22
+
23
+ - name: state-management
24
+ path: ./state-management.md
25
+ description: Riverpod 3.0, BLoC 9.0, Provider patterns and selection guide
26
+
27
+ - name: architecture
28
+ path: ./architecture.md
29
+ description: MVVM structure, project organization, go_router, freezed
30
+
31
+ - name: performance
32
+ path: ./performance.md
33
+ description: DevTools, const optimization, RepaintBoundary, Isolate
34
+
35
+ - name: testing
36
+ path: ./testing.md
37
+ description: Widget tests, golden tests, integration tests, mocking
38
+
39
+ - name: security
40
+ path: ./security.md
41
+ description: OWASP Mobile Top 10, secure storage, certificate pinning, platform security
42
+
43
+ used_by:
44
+ - fe-flutter-agent
@@ -0,0 +1,119 @@
1
+ # Flutter Performance
2
+
3
+ > Reference: docs.flutter.dev/perf/best-practices
4
+
5
+ ## Frame Budget
6
+
7
+ - Target: **<8ms build + <8ms render = 16.67ms** (60fps)
8
+ - Profile with: `flutter run --profile` (not debug mode)
9
+ - Tool: DevTools Performance view → identify jank frames
10
+
11
+ ## Build-Time Optimization
12
+
13
+ ### const Constructors
14
+
15
+ ```dart
16
+ // GOOD — zero rebuild cost
17
+ const Text('Hello');
18
+ const SizedBox(height: 8);
19
+ const Icon(Icons.star);
20
+
21
+ // BAD — new instance every build
22
+ Text('Hello');
23
+ SizedBox(height: 8);
24
+ ```
25
+
26
+ ### Localize setState
27
+
28
+ ```dart
29
+ // BAD — rebuilds entire screen
30
+ class _ScreenState extends State<Screen> {
31
+ int count = 0;
32
+ Widget build(BuildContext context) {
33
+ return Column(children: [
34
+ ExpensiveHeader(), // rebuilt unnecessarily
35
+ Text('$count'),
36
+ ElevatedButton(onPressed: () => setState(() => count++), child: Text('+'))
37
+ ]);
38
+ }
39
+ }
40
+
41
+ // GOOD — only counter rebuilds
42
+ class _ScreenState extends State<Screen> {
43
+ Widget build(BuildContext context) {
44
+ return Column(children: [
45
+ const ExpensiveHeader(), // not rebuilt
46
+ CounterWidget(), // isolated StatefulWidget
47
+ ]);
48
+ }
49
+ }
50
+ ```
51
+
52
+ ### Extract Widgets (not methods)
53
+
54
+ ```dart
55
+ // BAD — no Element identity, always rebuilds
56
+ Widget _buildHeader() => Container(...);
57
+
58
+ // GOOD — has Element identity, diffed efficiently
59
+ class HeaderWidget extends StatelessWidget {
60
+ const HeaderWidget({super.key});
61
+ @override
62
+ Widget build(BuildContext context) => Container(...);
63
+ }
64
+ ```
65
+
66
+ ## Rendering Optimization
67
+
68
+ ### RepaintBoundary
69
+
70
+ ```dart
71
+ // Wrap frequently repainting subtrees
72
+ RepaintBoundary(
73
+ child: AnimatedWidget(...), // only this subtree repaints
74
+ )
75
+ ```
76
+
77
+ Detect with: DevTools → Rendering → "Highlight repaints"
78
+
79
+ ### Avoid Expensive Widgets
80
+
81
+ | Avoid | Use Instead | Reason |
82
+ |-------|-------------|--------|
83
+ | `Opacity` widget | `color.withValues(alpha: 0.5)` | Opacity widget triggers saveLayer |
84
+ | `ClipRRect` in animations | Pre-clip static content | saveLayer per frame |
85
+ | `Container` for sizing | `SizedBox` | Lighter, no decoration |
86
+
87
+ ## List Performance
88
+
89
+ ```dart
90
+ // GOOD — lazy construction, O(visible) not O(total)
91
+ ListView.builder(
92
+ itemCount: items.length,
93
+ itemExtent: 72.0, // skip intrinsic layout passes
94
+ itemBuilder: (context, index) => ProductTile(items[index]),
95
+ );
96
+
97
+ // BAD — builds ALL children upfront
98
+ ListView(children: items.map((i) => ProductTile(i)).toList());
99
+ ```
100
+
101
+ ## Compute Offloading
102
+
103
+ ```dart
104
+ // CPU-intensive work (>16ms) on isolate
105
+ final result = await Isolate.run(() {
106
+ return heavyJsonParsing(rawData);
107
+ });
108
+
109
+ // Web-compatible alternative
110
+ final result = await compute(heavyJsonParsing, rawData);
111
+ ```
112
+
113
+ ## DevTools Workflow
114
+
115
+ 1. **Inspector** → identify widget causing rebuild
116
+ 2. **Performance view** → identify jank frames (>16ms)
117
+ 3. **CPU Profiler** → identify expensive Dart methods
118
+ 4. **Memory view** → detect object leaks
119
+ 5. **Network** → monitor API call timing
@@ -0,0 +1,144 @@
1
+ # Flutter State Management
2
+
3
+ > Reference: riverpod.dev, bloclibrary.dev, docs.flutter.dev/data-and-backend/state-mgmt
4
+
5
+ ## Selection Guide
6
+
7
+ | Approach | Complexity | Scalability | Best For |
8
+ |----------|-----------|-------------|----------|
9
+ | **Riverpod 3.0** | Medium | Excellent | New projects (default) |
10
+ | **BLoC 9.0** | High | Excellent | Enterprise, regulated industries |
11
+ | **Provider** | Low | Moderate | Simple apps, learning |
12
+ | **setState** | Low | Poor | Ephemeral local UI state |
13
+ | **GetX** | Low | Poor | **AVOID** for new projects |
14
+
15
+ ## Riverpod 3.0 (Default)
16
+
17
+ ### Provider Types
18
+
19
+ ```dart
20
+ // Simple value provider
21
+ @riverpod
22
+ String greeting(Ref ref) => 'Hello, World!';
23
+
24
+ // Async data provider
25
+ @riverpod
26
+ Future<List<Product>> products(Ref ref) async {
27
+ final api = ref.watch(apiClientProvider);
28
+ return api.getProducts();
29
+ }
30
+
31
+ // Stateful notifier
32
+ @riverpod
33
+ class Counter extends _$Counter {
34
+ @override
35
+ int build() => 0;
36
+
37
+ void increment() => state++;
38
+ void decrement() => state--;
39
+ }
40
+
41
+ // Async stateful notifier
42
+ @riverpod
43
+ class ProductList extends _$ProductList {
44
+ @override
45
+ Future<List<Product>> build() async {
46
+ return ref.watch(productRepositoryProvider).getAll();
47
+ }
48
+
49
+ Future<void> addProduct(Product product) async {
50
+ await ref.read(productRepositoryProvider).add(product);
51
+ ref.invalidateSelf();
52
+ }
53
+ }
54
+ ```
55
+
56
+ ### UI Consumption
57
+
58
+ ```dart
59
+ class ProductListScreen extends ConsumerWidget {
60
+ @override
61
+ Widget build(BuildContext context, WidgetRef ref) {
62
+ final state = ref.watch(productListProvider);
63
+
64
+ return state.when(
65
+ loading: () => const CircularProgressIndicator(),
66
+ error: (e, st) => Text('Error: $e'),
67
+ data: (products) => ListView.builder(
68
+ itemCount: products.length,
69
+ itemBuilder: (_, i) => ProductCard(products[i]),
70
+ ),
71
+ );
72
+ }
73
+ }
74
+ ```
75
+
76
+ ### Key Rules
77
+
78
+ - `ref.watch()` in build methods only (reactive)
79
+ - `ref.read()` in callbacks and event handlers (one-time)
80
+ - Never call `ref.watch()` inside non-build methods
81
+ - Use `family` for parameterized providers
82
+ - Use `keepAlive` sparingly (expensive computations only)
83
+
84
+ ## BLoC 9.0 (Enterprise)
85
+
86
+ ### Cubit (Simple)
87
+
88
+ ```dart
89
+ class CounterCubit extends Cubit<int> {
90
+ CounterCubit() : super(0);
91
+
92
+ void increment() => emit(state + 1);
93
+ void decrement() => emit(state - 1);
94
+ }
95
+
96
+ // UI
97
+ BlocBuilder<CounterCubit, int>(
98
+ builder: (context, count) => Text('$count'),
99
+ )
100
+ ```
101
+
102
+ ### Bloc (Full Events)
103
+
104
+ ```dart
105
+ // Events
106
+ sealed class AuthEvent {}
107
+ class LoginRequested extends AuthEvent {
108
+ final String email, password;
109
+ LoginRequested(this.email, this.password);
110
+ }
111
+
112
+ // States
113
+ sealed class AuthState {}
114
+ class AuthInitial extends AuthState {}
115
+ class AuthLoading extends AuthState {}
116
+ class AuthSuccess extends AuthState { final User user; AuthSuccess(this.user); }
117
+ class AuthFailure extends AuthState { final String error; AuthFailure(this.error); }
118
+
119
+ // Bloc
120
+ class AuthBloc extends Bloc<AuthEvent, AuthState> {
121
+ AuthBloc(this._authRepo) : super(AuthInitial()) {
122
+ on<LoginRequested>(_onLogin);
123
+ }
124
+
125
+ final AuthRepository _authRepo;
126
+
127
+ Future<void> _onLogin(LoginRequested event, Emitter<AuthState> emit) async {
128
+ emit(AuthLoading());
129
+ final result = await _authRepo.login(event.email, event.password);
130
+ switch (result) {
131
+ case Ok(:final value): emit(AuthSuccess(value));
132
+ case Error(:final error): emit(AuthFailure(error.toString()));
133
+ }
134
+ }
135
+ }
136
+ ```
137
+
138
+ ### Key Rules
139
+
140
+ - One event per user action
141
+ - Cubit for simple state, Bloc when audit trail needed
142
+ - Never emit state in constructor
143
+ - `BlocListener` for side effects, `BlocBuilder` for UI
144
+ - Cancel subscriptions in `close()`
@@ -0,0 +1,155 @@
1
+ # Flutter Testing
2
+
3
+ > Reference: docs.flutter.dev/testing/overview
4
+
5
+ ## Test Pyramid
6
+
7
+ | Level | Speed | Confidence | Tool |
8
+ |-------|-------|------------|------|
9
+ | Unit | Fast | Low | `flutter_test` |
10
+ | Widget | Fast | Medium | `testWidgets`, `WidgetTester` |
11
+ | Integration | Slow | High | `integration_test` package |
12
+ | Golden | Fast | Visual | `matchesGoldenFile()` |
13
+
14
+ ## Widget Tests (Primary)
15
+
16
+ ```dart
17
+ testWidgets('ProductCard displays name and price', (tester) async {
18
+ await tester.pumpWidget(
19
+ MaterialApp(
20
+ home: ProductCard(
21
+ product: Product(id: 1, name: 'Widget', price: 9.99),
22
+ ),
23
+ ),
24
+ );
25
+
26
+ expect(find.text('Widget'), findsOneWidget);
27
+ expect(find.text('\$9.99'), findsOneWidget);
28
+ });
29
+
30
+ testWidgets('tapping add button calls onAdd', (tester) async {
31
+ var called = false;
32
+ await tester.pumpWidget(
33
+ MaterialApp(
34
+ home: AddButton(onAdd: () => called = true),
35
+ ),
36
+ );
37
+
38
+ await tester.tap(find.byType(ElevatedButton));
39
+ expect(called, isTrue);
40
+ });
41
+ ```
42
+
43
+ ## Riverpod Testing
44
+
45
+ ```dart
46
+ testWidgets('ProductListScreen shows products', (tester) async {
47
+ await tester.pumpWidget(
48
+ ProviderScope(
49
+ overrides: [
50
+ productListProvider.overrideWith(
51
+ () => FakeProductListNotifier(),
52
+ ),
53
+ ],
54
+ child: const MaterialApp(home: ProductListScreen()),
55
+ ),
56
+ );
57
+
58
+ await tester.pumpAndSettle();
59
+ expect(find.byType(ProductCard), findsNWidgets(3));
60
+ });
61
+ ```
62
+
63
+ ## BLoC Testing
64
+
65
+ ```dart
66
+ blocTest<CounterCubit, int>(
67
+ 'increment emits [1] when initial state is 0',
68
+ build: () => CounterCubit(),
69
+ act: (cubit) => cubit.increment(),
70
+ expect: () => [1],
71
+ );
72
+
73
+ blocTest<AuthBloc, AuthState>(
74
+ 'login emits [loading, success] on valid credentials',
75
+ build: () => AuthBloc(FakeAuthRepository()),
76
+ act: (bloc) => bloc.add(LoginRequested('user@test.com', 'pass123')),
77
+ expect: () => [
78
+ isA<AuthLoading>(),
79
+ isA<AuthSuccess>(),
80
+ ],
81
+ );
82
+ ```
83
+
84
+ ## Mocking with mocktail
85
+
86
+ ```dart
87
+ class MockProductRepository extends Mock implements ProductRepository {}
88
+
89
+ void main() {
90
+ late MockProductRepository mockRepo;
91
+
92
+ setUp(() {
93
+ mockRepo = MockProductRepository();
94
+ });
95
+
96
+ test('getProducts returns list', () async {
97
+ when(() => mockRepo.getAll()).thenAnswer(
98
+ (_) async => Ok([Product(id: 1, name: 'Test', price: 9.99)]),
99
+ );
100
+
101
+ final result = await mockRepo.getAll();
102
+ expect(result, isA<Ok<List<Product>>>());
103
+ });
104
+ }
105
+ ```
106
+
107
+ ## Golden Tests
108
+
109
+ ```dart
110
+ testWidgets('ProductCard matches golden', (tester) async {
111
+ await tester.pumpWidget(
112
+ MaterialApp(
113
+ home: ProductCard(product: testProduct),
114
+ ),
115
+ );
116
+
117
+ await expectLater(
118
+ find.byType(ProductCard),
119
+ matchesGoldenFile('goldens/product_card.png'),
120
+ );
121
+ });
122
+
123
+ // Update goldens: flutter test --update-goldens
124
+ ```
125
+
126
+ ## Accessibility Testing
127
+
128
+ ```dart
129
+ testWidgets('meets accessibility guidelines', (tester) async {
130
+ final handle = tester.ensureSemantics();
131
+ await tester.pumpWidget(const MaterialApp(home: MyScreen()));
132
+
133
+ await expectLater(tester, meetsGuideline(androidTapTargetGuideline));
134
+ await expectLater(tester, meetsGuideline(iOSTapTargetGuideline));
135
+ await expectLater(tester, meetsGuideline(textContrastGuideline));
136
+
137
+ handle.dispose();
138
+ });
139
+ ```
140
+
141
+ ## Test Organization
142
+
143
+ ```
144
+ test/
145
+ ├── unit/
146
+ │ ├── repositories/product_repository_test.dart
147
+ │ └── viewmodels/home_viewmodel_test.dart
148
+ ├── widget/
149
+ │ ├── screens/product_list_screen_test.dart
150
+ │ └── widgets/product_card_test.dart
151
+ ├── goldens/
152
+ │ └── product_card.png
153
+ integration_test/
154
+ └── app_test.dart
155
+ ```
@@ -9,6 +9,14 @@ guides:
9
9
  type: internal
10
10
 
11
11
  # Frontend
12
+ - name: flutter
13
+ description: Flutter/Dart development patterns and best practices
14
+ path: ./flutter/
15
+ source:
16
+ type: external
17
+ origin: flutter.dev
18
+ url: https://docs.flutter.dev/
19
+
12
20
  - name: web-design
13
21
  description: Web design best practices and accessibility reference
14
22
  path: ./web-design/
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.30.7",
2
+ "version": "0.30.9",
3
3
  "lastUpdated": "2026-03-09T00:00:00.000Z",
4
4
  "components": [
5
5
  {
@@ -12,19 +12,19 @@
12
12
  "name": "agents",
13
13
  "path": ".claude/agents",
14
14
  "description": "AI agent definitions (flat .md files with prefixes)",
15
- "files": 43
15
+ "files": 44
16
16
  },
17
17
  {
18
18
  "name": "skills",
19
19
  "path": ".claude/skills",
20
20
  "description": "Reusable skill modules (includes slash commands)",
21
- "files": 67
21
+ "files": 68
22
22
  },
23
23
  {
24
24
  "name": "guides",
25
25
  "path": "guides",
26
26
  "description": "Reference documentation",
27
- "files": 23
27
+ "files": 24
28
28
  },
29
29
  {
30
30
  "name": "hooks",