mindsystem-cc 3.3.2 → 3.5.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 (28) hide show
  1. package/README.md +65 -1
  2. package/agents/ms-code-simplifier.md +1 -0
  3. package/agents/ms-flutter-reviewer.md +210 -0
  4. package/agents/ms-flutter-simplifier.md +42 -101
  5. package/bin/install.js +8 -0
  6. package/commands/ms/audit-milestone.md +209 -0
  7. package/commands/ms/do-work.md +7 -7
  8. package/commands/ms/execute-phase.md +5 -5
  9. package/commands/ms/research-project.md +10 -4
  10. package/mindsystem/templates/config.json +5 -1
  11. package/mindsystem/workflows/do-work.md +23 -23
  12. package/mindsystem/workflows/execute-phase.md +20 -20
  13. package/mindsystem/workflows/map-codebase.md +12 -6
  14. package/package.json +3 -2
  15. package/skills/flutter-senior-review/AGENTS.md +869 -0
  16. package/skills/flutter-senior-review/SKILL.md +205 -0
  17. package/skills/flutter-senior-review/principles/dependencies-data-not-callbacks.md +75 -0
  18. package/skills/flutter-senior-review/principles/dependencies-provider-tree.md +85 -0
  19. package/skills/flutter-senior-review/principles/dependencies-temporal-coupling.md +97 -0
  20. package/skills/flutter-senior-review/principles/pragmatism-consistent-error-handling.md +130 -0
  21. package/skills/flutter-senior-review/principles/pragmatism-speculative-generality.md +91 -0
  22. package/skills/flutter-senior-review/principles/state-data-clumps.md +64 -0
  23. package/skills/flutter-senior-review/principles/state-invalid-states.md +53 -0
  24. package/skills/flutter-senior-review/principles/state-single-source-of-truth.md +68 -0
  25. package/skills/flutter-senior-review/principles/state-type-hierarchies.md +75 -0
  26. package/skills/flutter-senior-review/principles/structure-composition-over-config.md +105 -0
  27. package/skills/flutter-senior-review/principles/structure-shared-visual-patterns.md +107 -0
  28. package/skills/flutter-senior-review/principles/structure-wrapper-pattern.md +90 -0
@@ -0,0 +1,205 @@
1
+ ---
2
+ name: flutter-senior-review
3
+ description: Senior engineering principles for Flutter/Dart code reviews. Apply when reviewing, refactoring, or writing Flutter code to identify structural improvements that make code evolvable, not just working.
4
+ license: MIT
5
+ metadata:
6
+ author: forgeblast
7
+ version: "1.0.0"
8
+ date: January 2026
9
+ abstract: Code review framework focused on structural improvements that typical reviews miss. Uses 3 core lenses (State Modeling, Responsibility Boundaries, Abstraction Timing) backed by 12 detailed principles organized into 4 categories. Each principle includes detection signals, smell examples, senior solutions, and Dart-specific patterns.
10
+ ---
11
+
12
+ # Flutter Senior Review
13
+
14
+ Senior engineering principles for Flutter/Dart code. Apply when reviewing, refactoring, or writing code to identify structural improvements that make code evolvable, not just working.
15
+
16
+ ## When to Apply
17
+
18
+ Reference these guidelines when:
19
+ - Reviewing code changes (commits, PRs, patches)
20
+ - Refactoring existing Flutter/Dart code
21
+ - Writing new features or components
22
+ - Identifying why code feels hard to change
23
+ - Planning structural improvements
24
+
25
+ ## Senior Mindset
26
+
27
+ Junior and mid-level engineers ask: **"Does this code work?"**
28
+ Senior engineers ask: **"How will this code change? What happens when requirements shift?"**
29
+
30
+ This distinction drives everything. Code that "works" today becomes a liability when:
31
+ - A new state is added and 5 files need coordinated updates
32
+ - A feature toggle requires touching code scattered across the codebase
33
+ - A bug fix in one place breaks assumptions elsewhere
34
+
35
+ Focus on **structural issues that compound over time** - the kind that turn "add a simple feature" into "refactor half the codebase first."
36
+
37
+ Do NOT look for:
38
+ - Style preferences or formatting
39
+ - Minor naming improvements
40
+ - "Nice to have" abstractions
41
+ - Issues already covered by linters/analyzers
42
+
43
+ ## Core Lenses
44
+
45
+ Apply these three lenses to every review. They catch 80% of structural issues.
46
+
47
+ ### Lens 1: State Modeling
48
+
49
+ **Question:** Can this code represent invalid states?
50
+
51
+ Look for:
52
+ - Multiple boolean flags (2^n possible states, many invalid)
53
+ - Primitive obsession (stringly-typed status, magic numbers)
54
+ - Same decision logic repeated in multiple places
55
+
56
+ **Senior pattern:** Sealed classes where each variant is a valid state. Factory methods that encapsulate decision logic. Compiler-enforced exhaustive handling.
57
+
58
+ Related principles: `state-invalid-states.md`, `state-type-hierarchies.md`, `state-single-source-of-truth.md`, `state-data-clumps.md`
59
+
60
+ ### Lens 2: Responsibility Boundaries
61
+
62
+ **Question:** If I remove/modify feature X, how many files change?
63
+
64
+ Look for:
65
+ - Optional feature logic scattered throughout a parent component
66
+ - Widgets with 6+ parameters (doing too much)
67
+ - Deep callback chains passing flags through layers
68
+
69
+ **Senior pattern:** Wrapper components for optional features. Typed data objects instead of flag parades. Each widget has one job.
70
+
71
+ Related principles: `structure-wrapper-pattern.md`, `structure-shared-visual-patterns.md`, `structure-composition-over-config.md`
72
+
73
+ ### Lens 3: Abstraction Timing
74
+
75
+ **Question:** Is this abstraction earned or speculative?
76
+
77
+ Look for:
78
+ - Interfaces with only one implementation
79
+ - Factories that create only one type
80
+ - "Flexible" config that's never varied
81
+ - BUT ALSO: Duplicated code that should be unified
82
+
83
+ **Senior pattern:** Abstract when you have 2-3 concrete cases, not before. Extract when duplication causes bugs or drift, not for aesthetics.
84
+
85
+ Related principles: `pragmatism-speculative-generality.md`, `dependencies-data-not-callbacks.md`
86
+
87
+ ## Principle Categories
88
+
89
+ | Category | Principles | Focus |
90
+ |----------|------------|-------|
91
+ | State & Types | 1, 3, 6, 10 | Invalid states, type hierarchies, single source of truth, data clumps |
92
+ | Structure | 2, 4, 8 | Feature isolation, visual patterns, composition |
93
+ | Dependencies | 5, 7, 9 | Coupling, provider architecture, temporal coupling |
94
+ | Pragmatism | 11, 12 | Avoiding over-engineering, consistent error handling |
95
+
96
+ ## Quick Reference
97
+
98
+ ### State & Type Safety
99
+ - **invalid-states** - Replace boolean flag combinations with sealed class hierarchies
100
+ - **type-hierarchies** - Use factories to encapsulate decision logic
101
+ - **single-source-of-truth** - One owner per state, derive the rest via selectors
102
+ - **data-clumps** - Group parameters that travel together into typed objects
103
+
104
+ ### Structure & Composition
105
+ - **wrapper-pattern** - Isolate optional feature logic into wrapper components
106
+ - **shared-visual-patterns** - Deduplicate UI with style variants
107
+ - **composition-over-config** - Small focused widgets over god widgets with many flags
108
+
109
+ ### Dependencies & Flow
110
+ - **data-not-callbacks** - Pass typed objects, not callback parades
111
+ - **provider-tree** - Root -> branch -> leaf hierarchy for providers
112
+ - **temporal-coupling** - Enforce operation sequences via types, not documentation
113
+
114
+ ### Pragmatism
115
+ - **speculative-generality** - Don't abstract until 2-3 concrete cases exist
116
+ - **consistent-error-handling** - One strategy applied everywhere, not ad-hoc try/catch
117
+
118
+ ## How to Use
119
+
120
+ Read individual principle files for detailed explanations and code examples:
121
+
122
+ ```
123
+ principles/state-invalid-states.md
124
+ principles/structure-wrapper-pattern.md
125
+ principles/dependencies-provider-tree.md
126
+ ```
127
+
128
+ Each principle file contains:
129
+ - Brief explanation of why it matters
130
+ - Detection signals (what to look for)
131
+ - Incorrect code example with explanation
132
+ - Correct code example with explanation
133
+ - Why it matters section
134
+ - Detection questions for self-review
135
+
136
+ ## Context Gathering
137
+
138
+ When asked to review code, first identify the target:
139
+
140
+ If target is unclear, ask:
141
+ - What code should be reviewed? (specific files, a feature folder, uncommitted changes, a commit, a patch file)
142
+ - Is there a specific concern or area of focus?
143
+
144
+ For the specified target, gather the relevant code:
145
+ - **Commit**: `git show <commit>`
146
+ - **Patch file**: Read the patch file
147
+ - **Uncommitted changes**: `git diff` and `git diff --cached`
148
+ - **Folder/feature**: Read the relevant files in that directory
149
+ - **Specific file**: Read that file and related files it imports/uses
150
+
151
+ ## Analysis Process
152
+
153
+ 1. **Read thoroughly** - Understand what the code does, not just its structure
154
+
155
+ 2. **Apply the three lenses** - For each lens, note specific instances (or note "no issues found")
156
+
157
+ 3. **Check for additional patterns** - If you notice issues beyond the core lenses, consult the principle files for precise diagnosis
158
+
159
+ 4. **Prioritize by evolution impact**:
160
+ - High: Will cause cascading changes when requirements shift
161
+ - Medium: Creates friction but contained to one area
162
+ - Low: Suboptimal but won't compound
163
+
164
+ 5. **Formulate concrete suggestions** - Name specific extractions, show before/after for the highest-impact change
165
+
166
+ ## Output Format
167
+
168
+ ```markdown
169
+ ## Senior Review: [Target]
170
+
171
+ ### Summary
172
+ [1-2 sentences: Overall assessment and the single most important structural opportunity]
173
+
174
+ ### Findings
175
+
176
+ #### High Impact: [Issue Name]
177
+ **What I noticed:** [Specific code pattern observed]
178
+ **Why it matters:** [How this will cause problems as code evolves]
179
+ **Suggestion:** [Concrete refactoring - name the types/widgets to extract]
180
+
181
+ #### Medium Impact: [Issue Name]
182
+ [Same structure]
183
+
184
+ #### Low Impact: [Issue Name]
185
+ [Same structure]
186
+
187
+ ### No Issues Found
188
+ [If a lens revealed no problems, briefly note: "State modeling: No boolean flag combinations or repeated decision logic detected."]
189
+
190
+ ---
191
+
192
+ **What's your take on these suggestions? Any context I'm missing?**
193
+ ```
194
+
195
+ ## Success Criteria
196
+
197
+ - At least one finding per applicable lens (or explicit "no issues" statement)
198
+ - Each finding tied to evolution impact, not just "could be better"
199
+ - Suggestions are concrete: specific types/widgets named, not vague advice
200
+ - No forced findings - if code is solid, say so
201
+ - User has opportunity to provide context before changes
202
+
203
+ ## Full Compiled Document
204
+
205
+ For the complete guide with all principles expanded: `AGENTS.md`
@@ -0,0 +1,75 @@
1
+ ---
2
+ title: Reduce Coupling Through Data
3
+ category: dependencies
4
+ impact: MEDIUM-HIGH
5
+ impactDescription: Stabilizes widget APIs
6
+ tags: coupling, parameters, typed-objects, widget-api
7
+ ---
8
+
9
+ ## Reduce Coupling Through Data, Not Callbacks
10
+
11
+ Pass typed objects, not callback parades. Child widgets receive a single typed object that encapsulates all the data they need.
12
+
13
+ **Detection signals:**
14
+ - Widgets have 4+ parameters beyond key and callbacks
15
+ - Boolean flags being passed through multiple widget layers
16
+ - Changing a child's behavior requires changing the parent's call site
17
+ - Parameters that are only used in some conditions
18
+
19
+ **Incorrect (flag parade):**
20
+
21
+ ```dart
22
+ QuestClaimButton(
23
+ quest: quest,
24
+ onSuccess: onClaimSuccess,
25
+ isExpired: isExpired,
26
+ isTutorial: isTutorial,
27
+ bonus: bonus,
28
+ showMultiplier: showMultiplier,
29
+ isPremiumUser: isPremiumUser,
30
+ )
31
+ ```
32
+
33
+ **Correct (typed data object):**
34
+
35
+ ```dart
36
+ // Data object encapsulates all context
37
+ sealed class QuestClaimMode {
38
+ final QuestEntity quest;
39
+ final RewardBonus? bonus;
40
+
41
+ const QuestClaimMode({required this.quest, this.bonus});
42
+
43
+ factory QuestClaimMode.fromContext({
44
+ required QuestEntity quest,
45
+ required UserEntity? user,
46
+ required bool isTutorialQuest,
47
+ }) {
48
+ // Decision logic centralized here
49
+ if (quest.isExpired) return QuestClaimModeExpired(quest: quest);
50
+ if (isTutorialQuest) return QuestClaimModeTutorial(quest: quest);
51
+ return QuestClaimModeNormal(
52
+ quest: quest,
53
+ bonus: user?.applyRewardBonus(quest.reward),
54
+ );
55
+ }
56
+ }
57
+
58
+ // Clean widget API
59
+ QuestClaimButton(
60
+ mode: QuestClaimMode.fromContext(quest: quest, user: user, isTutorial: isTutorial),
61
+ onSuccess: onClaimSuccess,
62
+ )
63
+ ```
64
+
65
+ **Why it matters:**
66
+ - Child widget's API is stable even as requirements change
67
+ - Parent doesn't need to know child's internal decision logic
68
+ - Data dependencies are explicit in the mode type
69
+ - Easier to test: create mode objects directly
70
+
71
+ **Detection questions:**
72
+ - Do widgets have 4+ parameters beyond key and callbacks?
73
+ - Are boolean flags being passed through multiple widget layers?
74
+ - Does changing a child's behavior require changing the parent's call site?
75
+ - Are there parameters that are only used in some conditions?
@@ -0,0 +1,85 @@
1
+ ---
2
+ title: Provider Tree Architecture
3
+ category: dependencies
4
+ impact: MEDIUM
5
+ impactDescription: Clarifies data flow
6
+ tags: riverpod, provider, architecture, dependencies
7
+ ---
8
+
9
+ ## Provider Tree Architecture
10
+
11
+ Root -> branch -> leaf hierarchy for providers. Clear mental model of which providers depend on which.
12
+
13
+ **Mental model:**
14
+ - **Root providers**: Match app lifecycle, never disposed. Hold core entities (user, games, quests). Referenced from many places. Use `keepAlive: true`.
15
+ - **Branch providers**: Combine/filter/transform core data. May become "core" as app grows (e.g., `squadQuestsProvider`).
16
+ - **Leaf providers**: Screen-specific or ephemeral. Depend on branches, rarely watched by other providers.
17
+
18
+ **Detection signals:**
19
+ - Can't draw the provider dependency graph as a tree
20
+ - Circular or confusing dependency chains
21
+ - "Leaf" providers being watched by other providers
22
+ - Provider doing too much (should split into root + branch)
23
+
24
+ **Incorrect (flat, tangled):**
25
+
26
+ ```dart
27
+ // Hard to trace what depends on what
28
+ final userProvider = ...;
29
+ final questsProvider = ...; // watches userProvider
30
+ final gamesProvider = ...; // watches userProvider
31
+ final filteredQuestsProvider = ...; // watches questsProvider, gamesProvider, some other provider
32
+ final screenStateProvider = ...; // watches 5 different providers
33
+ ```
34
+
35
+ **Correct (tree structure):**
36
+
37
+ ```dart
38
+ // Root (core entities, keepAlive: true)
39
+ @Riverpod(keepAlive: true)
40
+ Future<User> user(Ref ref) => ref.watch(authProvider.future).then((auth) => auth.user);
41
+
42
+ @Riverpod(keepAlive: true)
43
+ Future<List<Quest>> quests(Ref ref) async {
44
+ final user = await ref.watch(userProvider.future);
45
+ if (user == null) return [];
46
+ return ref.watch(questsApiProvider).fetchQuests(user.id);
47
+ }
48
+
49
+ // Branch (derived/filtered, may become core as app grows)
50
+ @riverpod
51
+ Future<List<Quest>> squadQuests(Ref ref) async {
52
+ final quests = await ref.watch(questsProvider.future);
53
+ return quests.where((q) => q.type == QuestType.squad).toList();
54
+ }
55
+
56
+ @riverpod
57
+ Future<List<Quest>> soloQuests(Ref ref) async {
58
+ final quests = await ref.watch(questsProvider.future);
59
+ return quests.where((q) => q.type == QuestType.solo).toList();
60
+ }
61
+
62
+ // Leaf (screen-specific, ephemeral)
63
+ @riverpod
64
+ class SquadQuestScreenState extends _$SquadQuestScreenState {
65
+ @override
66
+ FutureOr<ScreenState> build() async {
67
+ // Only watches branch providers
68
+ final quests = await ref.watch(squadQuestsProvider.future);
69
+ return ScreenState(quests: quests);
70
+ }
71
+ // Never watched by other providers
72
+ }
73
+ ```
74
+
75
+ **Why it matters:**
76
+ - Clear mental model of data flow
77
+ - Predictable rebuild scope
78
+ - Easier to add new derived providers
79
+ - Natural place for each piece of logic
80
+
81
+ **Detection questions:**
82
+ - Can you draw the provider dependency graph as a tree?
83
+ - Are there circular or confusing dependency chains?
84
+ - Are "leaf" providers being watched by other providers?
85
+ - Is a provider doing too much (should it split into root + branch)?
@@ -0,0 +1,97 @@
1
+ ---
2
+ title: Temporal Coupling
3
+ category: dependencies
4
+ impact: MEDIUM
5
+ impactDescription: Catches misuse at compile time
6
+ tags: builder-pattern, type-state, initialization, sequence
7
+ ---
8
+
9
+ ## Temporal Coupling (Enforce Sequences)
10
+
11
+ Enforce operation sequences via types, not documentation. Make it impossible to call methods in the wrong order.
12
+
13
+ **Detection signals:**
14
+ - Methods that must be called before others
15
+ - Comments like "must call X first" or "call after Y"
16
+ - Objects can be in an "invalid" state between operations
17
+ - Tests have setup steps that could be forgotten
18
+
19
+ **Incorrect (implicit sequence):**
20
+
21
+ ```dart
22
+ class PaymentProcessor {
23
+ void init() { ... }
24
+ void setAmount(int amount) { ... }
25
+ void setCustomer(Customer c) { ... }
26
+ Future<void> process() { ... } // Must call init, setAmount, setCustomer first!
27
+ }
28
+
29
+ // Easy to misuse:
30
+ final processor = PaymentProcessor();
31
+ processor.process(); // Boom - forgot to init
32
+ ```
33
+
34
+ **Correct (builder pattern):**
35
+
36
+ ```dart
37
+ class PaymentBuilder {
38
+ int? _amount;
39
+ Customer? _customer;
40
+
41
+ PaymentBuilder withAmount(int amount) {
42
+ _amount = amount;
43
+ return this;
44
+ }
45
+
46
+ PaymentBuilder withCustomer(Customer c) {
47
+ _customer = c;
48
+ return this;
49
+ }
50
+
51
+ Payment build() {
52
+ assert(_amount != null && _customer != null);
53
+ return Payment(amount: _amount!, customer: _customer!);
54
+ }
55
+ }
56
+
57
+ // Usage is clear
58
+ final payment = PaymentBuilder()
59
+ .withAmount(100)
60
+ .withCustomer(customer)
61
+ .build();
62
+ ```
63
+
64
+ **Correct (type-state pattern):**
65
+
66
+ ```dart
67
+ // Types enforce valid sequences
68
+ class UninitializedProcessor {
69
+ InitializedProcessor init(Config config) => InitializedProcessor(config);
70
+ }
71
+
72
+ class InitializedProcessor {
73
+ final Config _config;
74
+ InitializedProcessor(this._config);
75
+
76
+ Future<Result> process(int amount, Customer c) async {
77
+ // Can only be called on initialized processor
78
+ return _processPayment(amount, c);
79
+ }
80
+ }
81
+
82
+ // Misuse is a compile error
83
+ final processor = UninitializedProcessor();
84
+ processor.process(100, customer); // Error: process is not defined on UninitializedProcessor
85
+ ```
86
+
87
+ **Why it matters:**
88
+ - Compiler catches misuse, not runtime
89
+ - Self-documenting: types show valid sequences
90
+ - Impossible to forget required steps
91
+ - Easier onboarding for new developers
92
+
93
+ **Detection questions:**
94
+ - Are there methods that must be called before others?
95
+ - Are there comments like "must call X first" or "call after Y"?
96
+ - Can objects be in an "invalid" state between operations?
97
+ - Do tests have setup steps that could be forgotten?
@@ -0,0 +1,130 @@
1
+ ---
2
+ title: Consistent Error Handling
3
+ category: pragmatism
4
+ impact: MEDIUM
5
+ impactDescription: Improves UX and debugging
6
+ tags: error-handling, async-value, riverpod, toast
7
+ ---
8
+
9
+ ## Consistent Error Handling Strategy
10
+
11
+ One strategy applied everywhere, not ad-hoc try/catch. Use AsyncValue for state management and centralized error presentation.
12
+
13
+ **Detection signals:**
14
+ - Errors appear differently across screens (toasts vs dialogs vs inline)
15
+ - try/catch blocks scattered in widget code
16
+ - Inconsistent error messaging
17
+ - No standard retry mechanism
18
+
19
+ **Incorrect (ad-hoc handling):**
20
+
21
+ ```dart
22
+ // Screen 1: Try/catch with toast
23
+ try {
24
+ await ref.read(saveProvider.notifier).save();
25
+ } catch (e) {
26
+ ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('$e')));
27
+ }
28
+
29
+ // Screen 2: Different pattern
30
+ final result = await ref.read(saveProvider.notifier).save();
31
+ if (result.isError) {
32
+ showDialog(...); // Different error UI
33
+ }
34
+
35
+ // Screen 3: No error handling at all
36
+ await ref.read(saveProvider.notifier).save(); // Hope it works!
37
+ ```
38
+
39
+ **Correct (consistent pattern):**
40
+
41
+ ```dart
42
+ // Providers handle errors uniformly with AsyncValue.guard
43
+ @riverpod
44
+ class SaveController extends _$SaveController {
45
+ @override
46
+ FutureOr<void> build() => null;
47
+
48
+ Future<void> save(Data data) async {
49
+ state = const AsyncLoading();
50
+ state = await AsyncValue.guard(() => _repository.save(data));
51
+ // Error stays in state, not thrown
52
+ // Success navigates or shows confirmation
53
+ if (state.hasValue) {
54
+ ref.invalidate(dataProvider);
55
+ }
56
+ }
57
+ }
58
+
59
+ // Extension for centralized error listening
60
+ extension WidgetRefEx on WidgetRef {
61
+ void listenOnError<T>(
62
+ ProviderListenable<AsyncValue<T>> provider, {
63
+ bool Function(Object error)? ignoreIf,
64
+ }) {
65
+ listen(provider, (_, next) {
66
+ next.whenOrNull(
67
+ error: (error, _) {
68
+ if (ignoreIf?.call(error) == true) return;
69
+ AppToast.showError(context, error.toString());
70
+ },
71
+ );
72
+ });
73
+ }
74
+ }
75
+
76
+ // Screens use consistent pattern
77
+ Widget build(context, ref) {
78
+ // Centralized error listening - shows toast on any error
79
+ ref.listenOnError(saveProvider);
80
+
81
+ final saveState = ref.watch(saveProvider);
82
+
83
+ return AppPrimaryButton(
84
+ isLoading: saveState.isLoading,
85
+ onPressed: () => ref.read(saveProvider.notifier).save(data),
86
+ child: Text('Save'),
87
+ );
88
+ }
89
+ ```
90
+
91
+ **For first-load errors (need retry UI):**
92
+
93
+ ```dart
94
+ Widget build(context, ref) {
95
+ final dataState = ref.watch(dataProvider);
96
+
97
+ return dataState.when(
98
+ data: (data) => DataContent(data: data),
99
+ loading: () => const AppLoadingIndicator(),
100
+ error: (error, _) => Center(
101
+ child: Column(
102
+ mainAxisAlignment: MainAxisAlignment.center,
103
+ children: [
104
+ Icon(Icons.error_outline, size: 64, color: context.colorScheme.error),
105
+ const SizedBox(height: 16),
106
+ Text(LocaleKeys.common_error.tr()),
107
+ const SizedBox(height: 16),
108
+ FilledButton.icon(
109
+ onPressed: () => ref.invalidate(dataProvider),
110
+ icon: const Icon(Icons.refresh),
111
+ label: Text(LocaleKeys.common_retry.tr()),
112
+ ),
113
+ ],
114
+ ),
115
+ ),
116
+ );
117
+ }
118
+ ```
119
+
120
+ **Why it matters:**
121
+ - Users get consistent experience
122
+ - Developers follow one pattern
123
+ - Error states are explicit and testable
124
+ - Retry logic is standardized
125
+
126
+ **Detection questions:**
127
+ - Do errors appear the same way across all screens?
128
+ - Are there try/catch blocks in widget code?
129
+ - Is error messaging consistent (toasts vs dialogs vs inline)?
130
+ - Is there a standard retry mechanism?
@@ -0,0 +1,91 @@
1
+ ---
2
+ title: Speculative Generality
3
+ category: pragmatism
4
+ impact: MEDIUM
5
+ impactDescription: Reduces unnecessary complexity
6
+ tags: abstraction, yagni, over-engineering, interfaces
7
+ ---
8
+
9
+ ## Speculative Generality (Know When NOT to Abstract)
10
+
11
+ Don't abstract until 2-3 concrete cases exist. Build for today's requirements; extract abstractions when you have real examples.
12
+
13
+ **Detection signals:**
14
+ - Interface with only one implementation
15
+ - Factory that only creates one type
16
+ - Configuration options no one uses
17
+ - Abstraction added "in case we need it later"
18
+
19
+ **Incorrect (premature abstraction):**
20
+
21
+ ```dart
22
+ // "We might need different storage backends someday"
23
+ abstract class StorageStrategy {
24
+ Future<void> save(String key, String value);
25
+ Future<String?> load(String key);
26
+ }
27
+
28
+ class LocalStorageStrategy implements StorageStrategy {
29
+ @override
30
+ Future<void> save(String key, String value) => _prefs.setString(key, value);
31
+
32
+ @override
33
+ Future<String?> load(String key) => _prefs.getString(key);
34
+ }
35
+
36
+ class StorageFactory {
37
+ static StorageStrategy create(StorageType type) => switch (type) {
38
+ StorageType.local => LocalStorageStrategy(), // Only one ever used
39
+ };
40
+ }
41
+
42
+ // "We might need to support multiple payment providers"
43
+ abstract class PaymentProvider { ... }
44
+ class StripeProvider implements PaymentProvider { ... } // Only one exists
45
+ ```
46
+
47
+ **Correct (direct usage):**
48
+
49
+ ```dart
50
+ // Just use the thing directly
51
+ class LocalStorage {
52
+ final SharedPreferences _prefs;
53
+
54
+ LocalStorage(this._prefs);
55
+
56
+ Future<void> save(String key, String value) => _prefs.setString(key, value);
57
+ Future<String?> load(String key) async => _prefs.getString(key);
58
+ }
59
+
60
+ // When you ACTUALLY need a second implementation, THEN abstract
61
+ // The refactoring is straightforward and you'll know the right abstraction
62
+ // because you have concrete examples of the variation
63
+ ```
64
+
65
+ **When TO abstract:**
66
+
67
+ ```dart
68
+ // You have 2+ real implementations with actual differences
69
+ abstract class AuthProvider {
70
+ Future<User> signIn();
71
+ Future<void> signOut();
72
+ }
73
+
74
+ class GoogleAuthProvider implements AuthProvider { ... }
75
+ class AppleAuthProvider implements AuthProvider { ... }
76
+ class EmailAuthProvider implements AuthProvider { ... }
77
+
78
+ // The abstraction is earned - you know exactly what varies
79
+ ```
80
+
81
+ **Why it matters:**
82
+ - Less code to maintain
83
+ - Abstractions based on real needs fit better
84
+ - Easier to understand: no indirection to trace
85
+ - YAGNI: You Aren't Gonna Need It
86
+
87
+ **Detection questions:**
88
+ - Is there an interface with only one implementation?
89
+ - Is there a factory that only creates one type?
90
+ - Are there configuration options no one uses?
91
+ - Was this abstraction added "in case we need it later"?