mindsystem-cc 3.21.0 → 3.22.1
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/README.md +4 -12
- package/agents/ms-debugger.md +196 -880
- package/agents/ms-plan-checker.md +30 -30
- package/agents/ms-plan-writer.md +1 -1
- package/agents/ms-product-researcher.md +4 -2
- package/agents/ms-verifier.md +25 -117
- package/commands/ms/add-phase.md +3 -4
- package/commands/ms/add-todo.md +3 -4
- package/commands/ms/adhoc.md +3 -4
- package/commands/ms/audit-milestone.md +4 -3
- package/commands/ms/complete-milestone.md +2 -2
- package/commands/ms/config.md +36 -9
- package/commands/ms/create-roadmap.md +3 -4
- package/commands/ms/debug.md +27 -28
- package/commands/ms/design-phase.md +8 -5
- package/commands/ms/discuss-phase.md +2 -2
- package/commands/ms/doctor.md +9 -6
- package/commands/ms/execute-phase.md +2 -5
- package/commands/ms/help.md +2 -2
- package/commands/ms/insert-phase.md +3 -4
- package/commands/ms/map-codebase.md +1 -2
- package/commands/ms/new-milestone.md +1 -3
- package/commands/ms/new-project.md +3 -5
- package/commands/ms/plan-milestone-gaps.md +3 -4
- package/commands/ms/plan-phase.md +2 -3
- package/commands/ms/progress.md +1 -0
- package/commands/ms/remove-phase.md +3 -4
- package/commands/ms/research-phase.md +4 -4
- package/commands/ms/research-project.md +9 -16
- package/commands/ms/review-design.md +4 -2
- package/commands/ms/verify-work.md +6 -8
- package/mindsystem/templates/config.json +2 -1
- package/mindsystem/templates/roadmap.md +1 -1
- package/mindsystem/templates/state.md +2 -2
- package/mindsystem/templates/verification-report.md +3 -26
- package/mindsystem/workflows/diagnose-issues.md +0 -1
- package/mindsystem/workflows/discuss-phase.md +7 -3
- package/mindsystem/workflows/execute-phase.md +2 -18
- package/mindsystem/workflows/map-codebase.md +6 -12
- package/mindsystem/workflows/mockup-generation.md +46 -22
- package/mindsystem/workflows/plan-phase.md +12 -5
- package/mindsystem/workflows/verify-work.md +96 -69
- package/package.json +1 -1
- package/scripts/__pycache__/ms-tools.cpython-314.pyc +0 -0
- package/scripts/__pycache__/test_ms_tools.cpython-314-pytest-9.0.2.pyc +0 -0
- package/scripts/ms-tools.py +751 -6
- package/scripts/test_ms_tools.py +786 -0
- package/skills/senior-review/AGENTS.md +531 -0
- package/skills/{flutter-senior-review → senior-review}/SKILL.md +47 -36
- package/skills/senior-review/principles/dependencies-api-boundary-design.md +32 -0
- package/skills/senior-review/principles/dependencies-data-not-flags.md +32 -0
- package/skills/senior-review/principles/dependencies-temporal-coupling.md +32 -0
- package/skills/senior-review/principles/pragmatism-consistent-error-handling.md +32 -0
- package/skills/senior-review/principles/pragmatism-speculative-generality.md +32 -0
- package/skills/senior-review/principles/state-invalid-states.md +33 -0
- package/skills/senior-review/principles/state-single-source-of-truth.md +32 -0
- package/skills/senior-review/principles/state-type-hierarchies.md +32 -0
- package/skills/senior-review/principles/structure-composition-over-config.md +32 -0
- package/skills/senior-review/principles/structure-feature-isolation.md +32 -0
- package/skills/senior-review/principles/structure-module-cohesion.md +32 -0
- package/agents/ms-flutter-code-quality.md +0 -169
- package/agents/ms-flutter-reviewer.md +0 -211
- package/agents/ms-flutter-simplifier.md +0 -79
- package/mindsystem/references/debugging/debugging-mindset.md +0 -11
- package/mindsystem/references/debugging/hypothesis-testing.md +0 -11
- package/mindsystem/references/debugging/investigation-techniques.md +0 -11
- package/mindsystem/references/debugging/verification-patterns.md +0 -11
- package/mindsystem/references/debugging/when-to-research.md +0 -11
- package/mindsystem/references/git-integration.md +0 -254
- package/mindsystem/references/verification-patterns.md +0 -595
- package/mindsystem/workflows/debug.md +0 -14
- package/mindsystem/workflows/verify-phase.md +0 -625
- package/skills/flutter-code-quality/SKILL.md +0 -143
- package/skills/flutter-code-simplification/SKILL.md +0 -102
- package/skills/flutter-senior-review/AGENTS.md +0 -869
- package/skills/flutter-senior-review/principles/dependencies-data-not-callbacks.md +0 -75
- package/skills/flutter-senior-review/principles/dependencies-provider-tree.md +0 -85
- package/skills/flutter-senior-review/principles/dependencies-temporal-coupling.md +0 -97
- package/skills/flutter-senior-review/principles/pragmatism-consistent-error-handling.md +0 -130
- package/skills/flutter-senior-review/principles/pragmatism-speculative-generality.md +0 -91
- package/skills/flutter-senior-review/principles/state-data-clumps.md +0 -64
- package/skills/flutter-senior-review/principles/state-invalid-states.md +0 -53
- package/skills/flutter-senior-review/principles/state-single-source-of-truth.md +0 -68
- package/skills/flutter-senior-review/principles/state-type-hierarchies.md +0 -75
- package/skills/flutter-senior-review/principles/structure-composition-over-config.md +0 -105
- package/skills/flutter-senior-review/principles/structure-shared-visual-patterns.md +0 -107
- package/skills/flutter-senior-review/principles/structure-wrapper-pattern.md +0 -90
|
@@ -1,75 +0,0 @@
|
|
|
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?
|
|
@@ -1,85 +0,0 @@
|
|
|
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)?
|
|
@@ -1,97 +0,0 @@
|
|
|
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?
|
|
@@ -1,130 +0,0 @@
|
|
|
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?
|
|
@@ -1,91 +0,0 @@
|
|
|
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"?
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Data Clumps to Records
|
|
3
|
-
category: state
|
|
4
|
-
impact: MEDIUM-HIGH
|
|
5
|
-
impactDescription: Reduces parameter proliferation
|
|
6
|
-
tags: record, typedef, data-modeling, parameters
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## Data Clumps to Records
|
|
10
|
-
|
|
11
|
-
Group parameters that travel together into typed objects (records or classes).
|
|
12
|
-
|
|
13
|
-
**Detection signals:**
|
|
14
|
-
- Same 3+ parameters appear in multiple function signatures
|
|
15
|
-
- Parameters are logically related (always used together)
|
|
16
|
-
- Adding a new related field requires updating many signatures
|
|
17
|
-
- Bugs from passing parameters in the wrong order
|
|
18
|
-
|
|
19
|
-
**Incorrect (repeated parameter groups):**
|
|
20
|
-
|
|
21
|
-
```dart
|
|
22
|
-
void showReward(int baseReward, int? boostedReward, int? multiplier) { ... }
|
|
23
|
-
void displayBadge(int baseReward, int? boostedReward, int? multiplier) { ... }
|
|
24
|
-
void logClaim(int baseReward, int? boostedReward, int? multiplier) { ... }
|
|
25
|
-
|
|
26
|
-
Widget buildReward({
|
|
27
|
-
required int baseReward,
|
|
28
|
-
int? boostedReward,
|
|
29
|
-
int? multiplier,
|
|
30
|
-
}) { ... }
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
**Correct (typed record/class):**
|
|
34
|
-
|
|
35
|
-
```dart
|
|
36
|
-
// Record for simple data grouping
|
|
37
|
-
typedef RewardBonus = ({int base, int? boosted, int? multiplier});
|
|
38
|
-
|
|
39
|
-
// Or class if behavior is needed
|
|
40
|
-
class RewardCalculation {
|
|
41
|
-
final int base;
|
|
42
|
-
final int? boosted;
|
|
43
|
-
final int? multiplier;
|
|
44
|
-
|
|
45
|
-
bool get hasBonus => multiplier != null;
|
|
46
|
-
int get displayAmount => boosted ?? base;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// Clean call sites
|
|
50
|
-
void showReward(RewardBonus bonus) { ... }
|
|
51
|
-
Widget buildReward({required RewardBonus bonus}) { ... }
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
**Why it matters:**
|
|
55
|
-
- Single place to add new related fields
|
|
56
|
-
- Impossible to pass parameters in wrong order
|
|
57
|
-
- Semantic meaning is clear
|
|
58
|
-
- Reduces parameter count everywhere
|
|
59
|
-
|
|
60
|
-
**Detection questions:**
|
|
61
|
-
- Do the same 3+ parameters appear in multiple function signatures?
|
|
62
|
-
- Are parameters logically related (always used together)?
|
|
63
|
-
- Would adding a new related field require updating many signatures?
|
|
64
|
-
- Are there bugs from passing parameters in the wrong order?
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Make Invalid States Unrepresentable
|
|
3
|
-
category: state
|
|
4
|
-
impact: CRITICAL
|
|
5
|
-
impactDescription: Eliminates entire class of bugs
|
|
6
|
-
tags: sealed-class, boolean-flags, state-modeling, type-safety
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## Make Invalid States Unrepresentable
|
|
10
|
-
|
|
11
|
-
Replace boolean flag combinations with sealed class hierarchies where each variant represents exactly one valid state.
|
|
12
|
-
|
|
13
|
-
**Detection signals:**
|
|
14
|
-
- 3+ boolean parameters passed together
|
|
15
|
-
- Same boolean checks repeated in multiple places
|
|
16
|
-
- if/else chains checking flag combinations
|
|
17
|
-
- Some flag combinations would cause undefined behavior
|
|
18
|
-
|
|
19
|
-
**Incorrect (boolean flag explosion):**
|
|
20
|
-
|
|
21
|
-
```dart
|
|
22
|
-
Widget build() {
|
|
23
|
-
final isLoading = ...;
|
|
24
|
-
final isExpired = ...;
|
|
25
|
-
final isTutorial = ...;
|
|
26
|
-
final hasBonus = ...;
|
|
27
|
-
// What happens when isTutorial && isExpired?
|
|
28
|
-
// What about isLoading && hasBonus && isTutorial?
|
|
29
|
-
}
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
**Correct (sealed class hierarchy):**
|
|
33
|
-
|
|
34
|
-
```dart
|
|
35
|
-
sealed class ItemMode {
|
|
36
|
-
const ItemMode();
|
|
37
|
-
}
|
|
38
|
-
final class ItemModeNormal extends ItemMode { ... }
|
|
39
|
-
final class ItemModeTutorial extends ItemMode { ... }
|
|
40
|
-
final class ItemModeExpired extends ItemMode { ... }
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
**Why it matters:**
|
|
44
|
-
- Compiler enforces exhaustive handling via switch expressions
|
|
45
|
-
- New states added explicitly, not as boolean combinations
|
|
46
|
-
- Impossible to create invalid state combinations
|
|
47
|
-
- Self-documenting: sealed class shows all possible states
|
|
48
|
-
|
|
49
|
-
**Detection questions:**
|
|
50
|
-
- Are there 3+ boolean parameters being passed together?
|
|
51
|
-
- Do you see the same boolean checks repeated in multiple places?
|
|
52
|
-
- Are there if/else chains checking combinations of flags?
|
|
53
|
-
- Could some flag combinations cause undefined behavior?
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Single Source of Truth
|
|
3
|
-
category: state
|
|
4
|
-
impact: HIGH
|
|
5
|
-
impactDescription: Prevents stale data bugs
|
|
6
|
-
tags: state-ownership, provider, derived-state, riverpod
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## Single Source of Truth (State Ownership)
|
|
10
|
-
|
|
11
|
-
One owner per state, derive the rest via selectors. Push long-lived state up to providers; keep only ephemeral UI state local.
|
|
12
|
-
|
|
13
|
-
**Detection signals:**
|
|
14
|
-
- Same data stored in both a provider and local widget state
|
|
15
|
-
- useEffect hooks syncing local state from provider state
|
|
16
|
-
- Two sources of truth could disagree
|
|
17
|
-
- Derived data being cached instead of computed
|
|
18
|
-
|
|
19
|
-
**Incorrect (duplicated state):**
|
|
20
|
-
|
|
21
|
-
```dart
|
|
22
|
-
class ItemScreen extends HookConsumerWidget {
|
|
23
|
-
Widget build(context, ref) {
|
|
24
|
-
final items = ref.watch(itemsProvider);
|
|
25
|
-
// Local state duplicating what provider knows
|
|
26
|
-
final selectedItem = useState<Item?>(null);
|
|
27
|
-
final isEditing = useState(false);
|
|
28
|
-
|
|
29
|
-
// Widget caches a derived value
|
|
30
|
-
final totalPrice = useState(0);
|
|
31
|
-
useEffect(() {
|
|
32
|
-
totalPrice.value = items.fold(0, (sum, i) => sum + i.price);
|
|
33
|
-
}, [items]);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
**Correct (single owner, derived values):**
|
|
39
|
-
|
|
40
|
-
```dart
|
|
41
|
-
// Provider owns the state
|
|
42
|
-
@riverpod
|
|
43
|
-
class ItemsController extends _$ItemsController {
|
|
44
|
-
Item? get selectedItem => state.value?.firstWhereOrNull((i) => i.isSelected);
|
|
45
|
-
int get totalPrice => state.value?.fold(0, (sum, i) => sum + i.price) ?? 0;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// Widget only has ephemeral UI state
|
|
49
|
-
class ItemScreen extends HookConsumerWidget {
|
|
50
|
-
Widget build(context, ref) {
|
|
51
|
-
final items = ref.watch(itemsProvider);
|
|
52
|
-
final selectedItem = ref.watch(itemsProvider.select((s) => s.selectedItem));
|
|
53
|
-
final isTextFieldFocused = useState(false); // Truly ephemeral
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
**Why it matters:**
|
|
59
|
-
- No "which value is authoritative?" confusion
|
|
60
|
-
- State updates propagate automatically
|
|
61
|
-
- Easier debugging: one place to inspect
|
|
62
|
-
- Prevents stale data bugs
|
|
63
|
-
|
|
64
|
-
**Detection questions:**
|
|
65
|
-
- Is the same data stored in both a provider and local widget state?
|
|
66
|
-
- Are there useEffect hooks syncing local state from provider state?
|
|
67
|
-
- Could two sources of truth disagree? What happens then?
|
|
68
|
-
- Is there derived data being cached instead of computed?
|