mindsystem-cc 3.3.3 → 3.6.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/README.md +65 -1
- package/agents/ms-code-simplifier.md +1 -0
- package/agents/ms-flutter-code-quality.md +168 -0
- package/agents/ms-flutter-reviewer.md +210 -0
- package/agents/ms-flutter-simplifier.md +12 -118
- package/bin/install.js +444 -85
- package/commands/ms/audit-milestone.md +209 -0
- package/commands/ms/do-work.md +7 -7
- package/commands/ms/execute-phase.md +5 -5
- package/commands/ms/research-project.md +10 -4
- package/mindsystem/templates/config.json +5 -1
- package/mindsystem/workflows/do-work.md +23 -23
- package/mindsystem/workflows/execute-phase.md +20 -20
- package/mindsystem/workflows/map-codebase.md +12 -6
- package/package.json +3 -2
- package/skills/flutter-code-quality/SKILL.md +142 -0
- package/skills/flutter-code-simplification/SKILL.md +102 -0
- package/skills/flutter-senior-review/AGENTS.md +869 -0
- package/skills/flutter-senior-review/SKILL.md +205 -0
- package/skills/flutter-senior-review/principles/dependencies-data-not-callbacks.md +75 -0
- package/skills/flutter-senior-review/principles/dependencies-provider-tree.md +85 -0
- package/skills/flutter-senior-review/principles/dependencies-temporal-coupling.md +97 -0
- package/skills/flutter-senior-review/principles/pragmatism-consistent-error-handling.md +130 -0
- package/skills/flutter-senior-review/principles/pragmatism-speculative-generality.md +91 -0
- package/skills/flutter-senior-review/principles/state-data-clumps.md +64 -0
- package/skills/flutter-senior-review/principles/state-invalid-states.md +53 -0
- package/skills/flutter-senior-review/principles/state-single-source-of-truth.md +68 -0
- package/skills/flutter-senior-review/principles/state-type-hierarchies.md +75 -0
- package/skills/flutter-senior-review/principles/structure-composition-over-config.md +105 -0
- package/skills/flutter-senior-review/principles/structure-shared-visual-patterns.md +107 -0
- package/skills/flutter-senior-review/principles/structure-wrapper-pattern.md +90 -0
|
@@ -0,0 +1,869 @@
|
|
|
1
|
+
# Flutter Senior Review
|
|
2
|
+
|
|
3
|
+
**Version 1.0.0**
|
|
4
|
+
Forgeblast
|
|
5
|
+
January 2026
|
|
6
|
+
|
|
7
|
+
> This document is optimized for AI agents and LLMs. Principles are organized by category and prioritized by structural impact.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Abstract
|
|
12
|
+
|
|
13
|
+
Senior engineering principles for Flutter/Dart code reviews. 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. Focus is on structural improvements that make code evolvable, not just working.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Table of Contents
|
|
18
|
+
|
|
19
|
+
1. [Core Lenses](#1-core-lenses)
|
|
20
|
+
- 1.1 [State Modeling](#11-state-modeling)
|
|
21
|
+
- 1.2 [Responsibility Boundaries](#12-responsibility-boundaries)
|
|
22
|
+
- 1.3 [Abstraction Timing](#13-abstraction-timing)
|
|
23
|
+
|
|
24
|
+
2. [State & Type Safety](#2-state--type-safety) - **CRITICAL**
|
|
25
|
+
- 2.1 [Make Invalid States Unrepresentable](#21-make-invalid-states-unrepresentable)
|
|
26
|
+
- 2.2 [Explicit Type Hierarchies](#22-explicit-type-hierarchies)
|
|
27
|
+
- 2.3 [Single Source of Truth](#23-single-source-of-truth)
|
|
28
|
+
- 2.4 [Data Clumps to Records](#24-data-clumps-to-records)
|
|
29
|
+
|
|
30
|
+
3. [Structure & Composition](#3-structure--composition) - **HIGH**
|
|
31
|
+
- 3.1 [Isolate Feature Responsibility](#31-isolate-feature-responsibility)
|
|
32
|
+
- 3.2 [Extract Shared Visual Patterns](#32-extract-shared-visual-patterns)
|
|
33
|
+
- 3.3 [Composition Over Configuration](#33-composition-over-configuration)
|
|
34
|
+
|
|
35
|
+
4. [Dependencies & Flow](#4-dependencies--flow) - **MEDIUM-HIGH**
|
|
36
|
+
- 4.1 [Reduce Coupling Through Data](#41-reduce-coupling-through-data)
|
|
37
|
+
- 4.2 [Provider Tree Architecture](#42-provider-tree-architecture)
|
|
38
|
+
- 4.3 [Temporal Coupling](#43-temporal-coupling)
|
|
39
|
+
|
|
40
|
+
5. [Pragmatism](#5-pragmatism) - **MEDIUM**
|
|
41
|
+
- 5.1 [Speculative Generality](#51-speculative-generality)
|
|
42
|
+
- 5.2 [Consistent Error Handling](#52-consistent-error-handling)
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Senior Mindset
|
|
47
|
+
|
|
48
|
+
Junior and mid-level engineers ask: **"Does this code work?"**
|
|
49
|
+
Senior engineers ask: **"How will this code change? What happens when requirements shift?"**
|
|
50
|
+
|
|
51
|
+
This distinction drives everything. Code that "works" today becomes a liability when:
|
|
52
|
+
- A new state is added and 5 files need coordinated updates
|
|
53
|
+
- A feature toggle requires touching code scattered across the codebase
|
|
54
|
+
- A bug fix in one place breaks assumptions elsewhere
|
|
55
|
+
|
|
56
|
+
Focus on **structural issues that compound over time** - the kind that turn "add a simple feature" into "refactor half the codebase first."
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## 1. Core Lenses
|
|
61
|
+
|
|
62
|
+
Apply these three lenses to every review. They catch 80% of structural issues.
|
|
63
|
+
|
|
64
|
+
### 1.1 State Modeling
|
|
65
|
+
|
|
66
|
+
**Question:** Can this code represent invalid states?
|
|
67
|
+
|
|
68
|
+
Look for:
|
|
69
|
+
- Multiple boolean flags (2^n possible states, many invalid)
|
|
70
|
+
- Primitive obsession (stringly-typed status, magic numbers)
|
|
71
|
+
- Same decision logic repeated in multiple places
|
|
72
|
+
|
|
73
|
+
**Senior pattern:** Sealed classes where each variant is a valid state. Factory methods that encapsulate decision logic. Compiler-enforced exhaustive handling.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
### 1.2 Responsibility Boundaries
|
|
78
|
+
|
|
79
|
+
**Question:** If I remove/modify feature X, how many files change?
|
|
80
|
+
|
|
81
|
+
Look for:
|
|
82
|
+
- Optional feature logic scattered throughout a parent component
|
|
83
|
+
- Widgets with 6+ parameters (doing too much)
|
|
84
|
+
- Deep callback chains passing flags through layers
|
|
85
|
+
|
|
86
|
+
**Senior pattern:** Wrapper components for optional features. Typed data objects instead of flag parades. Each widget has one job.
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
### 1.3 Abstraction Timing
|
|
91
|
+
|
|
92
|
+
**Question:** Is this abstraction earned or speculative?
|
|
93
|
+
|
|
94
|
+
Look for:
|
|
95
|
+
- Interfaces with only one implementation
|
|
96
|
+
- Factories that create only one type
|
|
97
|
+
- "Flexible" config that's never varied
|
|
98
|
+
- BUT ALSO: Duplicated code that should be unified
|
|
99
|
+
|
|
100
|
+
**Senior pattern:** Abstract when you have 2-3 concrete cases, not before. Extract when duplication causes bugs or drift, not for aesthetics.
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## 2. State & Type Safety
|
|
105
|
+
|
|
106
|
+
**Impact: CRITICAL**
|
|
107
|
+
|
|
108
|
+
Issues with state modeling compound rapidly. Invalid state combinations cause bugs that are hard to reproduce and fix.
|
|
109
|
+
|
|
110
|
+
### 2.1 Make Invalid States Unrepresentable
|
|
111
|
+
|
|
112
|
+
**Impact: CRITICAL (Eliminates entire class of bugs)**
|
|
113
|
+
|
|
114
|
+
Multiple boolean flags that create 2^n possible states, where many combinations are invalid or nonsensical.
|
|
115
|
+
|
|
116
|
+
**Detection signals:**
|
|
117
|
+
- 3+ boolean parameters passed together
|
|
118
|
+
- Same boolean checks repeated in multiple places
|
|
119
|
+
- if/else chains checking flag combinations
|
|
120
|
+
- Some flag combinations would cause undefined behavior
|
|
121
|
+
|
|
122
|
+
**Incorrect (boolean flag explosion):**
|
|
123
|
+
|
|
124
|
+
```dart
|
|
125
|
+
Widget build() {
|
|
126
|
+
final isLoading = ...;
|
|
127
|
+
final isExpired = ...;
|
|
128
|
+
final isTutorial = ...;
|
|
129
|
+
final hasBonus = ...;
|
|
130
|
+
// What happens when isTutorial && isExpired?
|
|
131
|
+
// What about isLoading && hasBonus && isTutorial?
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
**Correct (sealed class hierarchy):**
|
|
136
|
+
|
|
137
|
+
```dart
|
|
138
|
+
sealed class ItemMode {
|
|
139
|
+
const ItemMode();
|
|
140
|
+
}
|
|
141
|
+
final class ItemModeNormal extends ItemMode { ... }
|
|
142
|
+
final class ItemModeTutorial extends ItemMode { ... }
|
|
143
|
+
final class ItemModeExpired extends ItemMode { ... }
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
**Why it matters:**
|
|
147
|
+
- Compiler enforces exhaustive handling via switch expressions
|
|
148
|
+
- New states added explicitly, not as boolean combinations
|
|
149
|
+
- Impossible to create invalid state combinations
|
|
150
|
+
- Self-documenting: sealed class shows all possible states
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
### 2.2 Explicit Type Hierarchies
|
|
155
|
+
|
|
156
|
+
**Impact: HIGH (Centralizes decision logic)**
|
|
157
|
+
|
|
158
|
+
Complex if/else chains determining behavior scattered across widgets rather than encoded in the type system.
|
|
159
|
+
|
|
160
|
+
**Detection signals:**
|
|
161
|
+
- Complex if/else chains determining which widget to render
|
|
162
|
+
- Same decision logic duplicated across multiple widgets
|
|
163
|
+
- Widgets receive data they only use to make decisions (not to display)
|
|
164
|
+
- New requirement would add another boolean parameter
|
|
165
|
+
|
|
166
|
+
**Incorrect (scattered decision logic):**
|
|
167
|
+
|
|
168
|
+
```dart
|
|
169
|
+
Widget _buildTrailing(BuildContext context, {required bool isExpired, required bool isTutorial}) {
|
|
170
|
+
if (quest.status.isClaimable && quest.isExpired) {
|
|
171
|
+
return QuestClaimButton(quest: quest, isExpired: true);
|
|
172
|
+
}
|
|
173
|
+
if (quest.canClaim) {
|
|
174
|
+
if (isTutorial) {
|
|
175
|
+
return PulsingGlowWrapper(child: QuestClaimButton(quest: quest, isTutorial: true));
|
|
176
|
+
}
|
|
177
|
+
return QuestClaimButton(quest: quest);
|
|
178
|
+
}
|
|
179
|
+
return _buildRewardBadge(context);
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
**Correct (factory with type hierarchy):**
|
|
184
|
+
|
|
185
|
+
```dart
|
|
186
|
+
sealed class QuestClaimMode {
|
|
187
|
+
factory QuestClaimMode.fromContext({
|
|
188
|
+
required QuestEntity quest,
|
|
189
|
+
required UserEntity? user,
|
|
190
|
+
required bool isTutorialQuest,
|
|
191
|
+
}) {
|
|
192
|
+
if (quest.status.isClaimable && quest.isExpired) {
|
|
193
|
+
return QuestClaimModeExpired(...);
|
|
194
|
+
}
|
|
195
|
+
if (quest.canClaim) {
|
|
196
|
+
return isTutorialQuest
|
|
197
|
+
? QuestClaimModeTutorial(...)
|
|
198
|
+
: QuestClaimModeNormal(...);
|
|
199
|
+
}
|
|
200
|
+
return QuestClaimModePending(...);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Widget becomes a simple switch
|
|
205
|
+
Widget _buildTrailing() => switch (claimMode) {
|
|
206
|
+
QuestClaimModePending() => QuestRewardDisplay(...),
|
|
207
|
+
QuestClaimModeTutorial() => _wrapWithTutorialEffects(QuestClaimButton(mode: claimMode)),
|
|
208
|
+
QuestClaimModeNormal() || QuestClaimModeExpired() => QuestClaimButton(mode: claimMode),
|
|
209
|
+
};
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
**Why it matters:**
|
|
213
|
+
- Decision logic lives in one place (the factory)
|
|
214
|
+
- Widgets receive pre-computed decisions, not raw data to interpret
|
|
215
|
+
- Adding new modes is explicit and compiler-checked
|
|
216
|
+
- Testing is clearer: test the factory, then test each mode's rendering
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
### 2.3 Single Source of Truth
|
|
221
|
+
|
|
222
|
+
**Impact: HIGH (Prevents stale data bugs)**
|
|
223
|
+
|
|
224
|
+
Same state tracked in multiple places - local useState duplicating provider state, widgets caching derived values.
|
|
225
|
+
|
|
226
|
+
**Detection signals:**
|
|
227
|
+
- Same data stored in both a provider and local widget state
|
|
228
|
+
- useEffect hooks syncing local state from provider state
|
|
229
|
+
- Two sources of truth could disagree
|
|
230
|
+
- Derived data being cached instead of computed
|
|
231
|
+
|
|
232
|
+
**Incorrect (duplicated state):**
|
|
233
|
+
|
|
234
|
+
```dart
|
|
235
|
+
class ItemScreen extends HookConsumerWidget {
|
|
236
|
+
Widget build(context, ref) {
|
|
237
|
+
final items = ref.watch(itemsProvider);
|
|
238
|
+
// Local state duplicating what provider knows
|
|
239
|
+
final selectedItem = useState<Item?>(null);
|
|
240
|
+
final isEditing = useState(false);
|
|
241
|
+
|
|
242
|
+
// Widget caches a derived value
|
|
243
|
+
final totalPrice = useState(0);
|
|
244
|
+
useEffect(() {
|
|
245
|
+
totalPrice.value = items.fold(0, (sum, i) => sum + i.price);
|
|
246
|
+
}, [items]);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
**Correct (single owner, derived values):**
|
|
252
|
+
|
|
253
|
+
```dart
|
|
254
|
+
// Provider owns the state
|
|
255
|
+
@riverpod
|
|
256
|
+
class ItemsController extends _$ItemsController {
|
|
257
|
+
Item? get selectedItem => state.value?.firstWhereOrNull((i) => i.isSelected);
|
|
258
|
+
int get totalPrice => state.value?.fold(0, (sum, i) => sum + i.price) ?? 0;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Widget only has ephemeral UI state
|
|
262
|
+
class ItemScreen extends HookConsumerWidget {
|
|
263
|
+
Widget build(context, ref) {
|
|
264
|
+
final items = ref.watch(itemsProvider);
|
|
265
|
+
final selectedItem = ref.watch(itemsProvider.select((s) => s.selectedItem));
|
|
266
|
+
final isTextFieldFocused = useState(false); // Truly ephemeral
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
**Why it matters:**
|
|
272
|
+
- No "which value is authoritative?" confusion
|
|
273
|
+
- State updates propagate automatically
|
|
274
|
+
- Easier debugging: one place to inspect
|
|
275
|
+
- Prevents stale data bugs
|
|
276
|
+
|
|
277
|
+
---
|
|
278
|
+
|
|
279
|
+
### 2.4 Data Clumps to Records
|
|
280
|
+
|
|
281
|
+
**Impact: MEDIUM-HIGH (Reduces parameter proliferation)**
|
|
282
|
+
|
|
283
|
+
Same 3-4 parameters appear together repeatedly across methods and constructors.
|
|
284
|
+
|
|
285
|
+
**Detection signals:**
|
|
286
|
+
- Same 3+ parameters appear in multiple function signatures
|
|
287
|
+
- Parameters are logically related (always used together)
|
|
288
|
+
- Adding a new related field requires updating many signatures
|
|
289
|
+
- Bugs from passing parameters in the wrong order
|
|
290
|
+
|
|
291
|
+
**Incorrect (repeated parameter groups):**
|
|
292
|
+
|
|
293
|
+
```dart
|
|
294
|
+
void showReward(int baseReward, int? boostedReward, int? multiplier) { ... }
|
|
295
|
+
void displayBadge(int baseReward, int? boostedReward, int? multiplier) { ... }
|
|
296
|
+
void logClaim(int baseReward, int? boostedReward, int? multiplier) { ... }
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
**Correct (typed record/class):**
|
|
300
|
+
|
|
301
|
+
```dart
|
|
302
|
+
// Record for simple data grouping
|
|
303
|
+
typedef RewardBonus = ({int base, int? boosted, int? multiplier});
|
|
304
|
+
|
|
305
|
+
// Or class if behavior is needed
|
|
306
|
+
class RewardCalculation {
|
|
307
|
+
final int base;
|
|
308
|
+
final int? boosted;
|
|
309
|
+
final int? multiplier;
|
|
310
|
+
|
|
311
|
+
bool get hasBonus => multiplier != null;
|
|
312
|
+
int get displayAmount => boosted ?? base;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Clean call sites
|
|
316
|
+
void showReward(RewardBonus bonus) { ... }
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
**Why it matters:**
|
|
320
|
+
- Single place to add new related fields
|
|
321
|
+
- Impossible to pass parameters in wrong order
|
|
322
|
+
- Semantic meaning is clear
|
|
323
|
+
- Reduces parameter count everywhere
|
|
324
|
+
|
|
325
|
+
---
|
|
326
|
+
|
|
327
|
+
## 3. Structure & Composition
|
|
328
|
+
|
|
329
|
+
**Impact: HIGH**
|
|
330
|
+
|
|
331
|
+
Structural issues make features hard to add, remove, or modify independently.
|
|
332
|
+
|
|
333
|
+
### 3.1 Isolate Feature Responsibility
|
|
334
|
+
|
|
335
|
+
**Impact: HIGH (Features become removable)**
|
|
336
|
+
|
|
337
|
+
A feature's logic is woven throughout a parent component rather than encapsulated.
|
|
338
|
+
|
|
339
|
+
**Detection signals:**
|
|
340
|
+
- More than 30% of a widget's code dedicated to one optional feature
|
|
341
|
+
- Removing a feature requires deleting scattered lines throughout the file
|
|
342
|
+
- Multiple `if (featureEnabled)` checks spread across the widget
|
|
343
|
+
- State variables only used by one feature
|
|
344
|
+
|
|
345
|
+
**Incorrect (scattered feature logic):**
|
|
346
|
+
|
|
347
|
+
```dart
|
|
348
|
+
class QuestListScreen extends HookConsumerWidget {
|
|
349
|
+
Widget build(context, ref) {
|
|
350
|
+
// 50 lines of tutorial-specific state management
|
|
351
|
+
final tutorialKey = useMemoized(GlobalKey.new);
|
|
352
|
+
final overlayVisible = useState(true);
|
|
353
|
+
final cutoutRect = useState<Rect?>(null);
|
|
354
|
+
|
|
355
|
+
useEffect(() {
|
|
356
|
+
if (isTutorialActive) {
|
|
357
|
+
// 30 lines of tutorial position measurement
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
return Stack(
|
|
362
|
+
children: [
|
|
363
|
+
questList,
|
|
364
|
+
if (showTutorialOverlay) TutorialOverlay(...),
|
|
365
|
+
],
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
**Correct (wrapper pattern):**
|
|
372
|
+
|
|
373
|
+
```dart
|
|
374
|
+
// Tutorial logic fully encapsulated
|
|
375
|
+
class TutorialQuestSpotlight extends HookConsumerWidget {
|
|
376
|
+
final Widget Function(BuildContext, {GlobalKey? tutorialKey}) builder;
|
|
377
|
+
|
|
378
|
+
Widget build(context, ref) {
|
|
379
|
+
// All tutorial state lives here
|
|
380
|
+
// Core list doesn't know tutorials exist
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Clean core component
|
|
385
|
+
class QuestListScreen extends HookConsumerWidget {
|
|
386
|
+
Widget build(context, ref) {
|
|
387
|
+
return TutorialQuestSpotlight(
|
|
388
|
+
builder: (context, {tutorialKey}) => _buildQuestList(tutorialKey),
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
**Why it matters:**
|
|
395
|
+
- Feature can be disabled/removed by removing one wrapper
|
|
396
|
+
- Core component remains focused and testable
|
|
397
|
+
- Feature logic is cohesive and isolated
|
|
398
|
+
- Multiple features can compose without polluting each other
|
|
399
|
+
|
|
400
|
+
---
|
|
401
|
+
|
|
402
|
+
### 3.2 Extract Shared Visual Patterns
|
|
403
|
+
|
|
404
|
+
**Impact: MEDIUM-HIGH (Guarantees consistency)**
|
|
405
|
+
|
|
406
|
+
Similar UI code duplicated across widgets with minor variations and subtle inconsistencies.
|
|
407
|
+
|
|
408
|
+
**Detection signals:**
|
|
409
|
+
- Similar Container/decoration patterns across multiple widgets
|
|
410
|
+
- Visual elements (colors, padding, borders) vary based on state
|
|
411
|
+
- Design change would require updating multiple files
|
|
412
|
+
- Subtle inconsistencies between similar UI elements
|
|
413
|
+
|
|
414
|
+
**Incorrect (duplicated patterns):**
|
|
415
|
+
|
|
416
|
+
```dart
|
|
417
|
+
// In QuestClaimButton
|
|
418
|
+
Container(
|
|
419
|
+
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
|
420
|
+
decoration: BoxDecoration(
|
|
421
|
+
color: isExpired ? AppColors.gray400 : AppColors.forge,
|
|
422
|
+
borderRadius: BorderRadius.circular(20),
|
|
423
|
+
),
|
|
424
|
+
child: Row(children: [Text('grape'), Text('$reward')]),
|
|
425
|
+
)
|
|
426
|
+
|
|
427
|
+
// In QuestListTile (slightly different)
|
|
428
|
+
Container(
|
|
429
|
+
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
|
430
|
+
decoration: BoxDecoration(
|
|
431
|
+
borderRadius: BorderRadius.circular(20),
|
|
432
|
+
border: Border.all(color: ...),
|
|
433
|
+
),
|
|
434
|
+
child: Row(children: [Text('grape'), Text('$reward')]),
|
|
435
|
+
)
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
**Correct (shared widget with variants):**
|
|
439
|
+
|
|
440
|
+
```dart
|
|
441
|
+
enum QuestRewardStyle { filled, outlined, disabled }
|
|
442
|
+
|
|
443
|
+
class QuestRewardDisplay extends StatelessWidget {
|
|
444
|
+
final int reward;
|
|
445
|
+
final QuestRewardStyle style;
|
|
446
|
+
|
|
447
|
+
Widget build(context) {
|
|
448
|
+
return Container(
|
|
449
|
+
decoration: style.buildDecoration(context),
|
|
450
|
+
child: Row(children: [Text('grape'), Text('$reward', style: style.buildTextStyle(context))]),
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
extension on QuestRewardStyle {
|
|
456
|
+
BoxDecoration buildDecoration(BuildContext context) => switch (this) {
|
|
457
|
+
QuestRewardStyle.filled => BoxDecoration(color: AppColors.forge, ...),
|
|
458
|
+
QuestRewardStyle.outlined => BoxDecoration(border: Border.all(...), ...),
|
|
459
|
+
QuestRewardStyle.disabled => BoxDecoration(color: AppColors.gray400, ...),
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
**Why it matters:**
|
|
465
|
+
- Visual consistency is guaranteed
|
|
466
|
+
- Style changes propagate automatically
|
|
467
|
+
- New variants are added in one place
|
|
468
|
+
- Reduces widget file sizes significantly
|
|
469
|
+
|
|
470
|
+
---
|
|
471
|
+
|
|
472
|
+
### 3.3 Composition Over Configuration
|
|
473
|
+
|
|
474
|
+
**Impact: MEDIUM (Simplifies widget APIs)**
|
|
475
|
+
|
|
476
|
+
Giant widgets with dozens of boolean flags - "god widgets" that handle every case through configuration.
|
|
477
|
+
|
|
478
|
+
**Detection signals:**
|
|
479
|
+
- Widget has more than 6-8 parameters
|
|
480
|
+
- Boolean parameters that are mutually exclusive
|
|
481
|
+
- Widget is really 3 different widgets pretending to be 1
|
|
482
|
+
- Adding a new variant would require another boolean flag
|
|
483
|
+
|
|
484
|
+
**Incorrect (god widget):**
|
|
485
|
+
|
|
486
|
+
```dart
|
|
487
|
+
AppButton(
|
|
488
|
+
label: 'Submit',
|
|
489
|
+
isPrimary: true,
|
|
490
|
+
isSecondary: false,
|
|
491
|
+
isDestructive: false,
|
|
492
|
+
isLoading: false,
|
|
493
|
+
isDisabled: false,
|
|
494
|
+
showIcon: true,
|
|
495
|
+
iconPosition: IconPosition.left,
|
|
496
|
+
size: ButtonSize.medium,
|
|
497
|
+
// ... 10 more parameters
|
|
498
|
+
)
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
**Correct (composed widgets):**
|
|
502
|
+
|
|
503
|
+
```dart
|
|
504
|
+
// Composition of focused widgets
|
|
505
|
+
PrimaryButton(
|
|
506
|
+
onPressed: handleSubmit,
|
|
507
|
+
child: Row(
|
|
508
|
+
children: [
|
|
509
|
+
Icon(Icons.check),
|
|
510
|
+
SizedBox(width: 8),
|
|
511
|
+
Text('Submit'),
|
|
512
|
+
],
|
|
513
|
+
),
|
|
514
|
+
)
|
|
515
|
+
|
|
516
|
+
// Or slot-based API for common patterns
|
|
517
|
+
PrimaryButton.icon(
|
|
518
|
+
icon: Icons.check,
|
|
519
|
+
label: 'Submit',
|
|
520
|
+
onPressed: handleSubmit,
|
|
521
|
+
)
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
**Why it matters:**
|
|
525
|
+
- Each widget has one job
|
|
526
|
+
- New variants don't bloat existing widgets
|
|
527
|
+
- Easier to understand and test
|
|
528
|
+
- Flexible: compose for custom needs, use shortcuts for common ones
|
|
529
|
+
|
|
530
|
+
---
|
|
531
|
+
|
|
532
|
+
## 4. Dependencies & Flow
|
|
533
|
+
|
|
534
|
+
**Impact: MEDIUM-HIGH**
|
|
535
|
+
|
|
536
|
+
Coupling and dependency issues make code changes ripple unexpectedly.
|
|
537
|
+
|
|
538
|
+
### 4.1 Reduce Coupling Through Data
|
|
539
|
+
|
|
540
|
+
**Impact: MEDIUM-HIGH (Stabilizes APIs)**
|
|
541
|
+
|
|
542
|
+
Parent widgets pass many callbacks and control flags to children, creating tight coupling.
|
|
543
|
+
|
|
544
|
+
**Detection signals:**
|
|
545
|
+
- Widgets have 4+ parameters beyond key and callbacks
|
|
546
|
+
- Boolean flags being passed through multiple widget layers
|
|
547
|
+
- Changing a child's behavior requires changing the parent's call site
|
|
548
|
+
- Parameters that are only used in some conditions
|
|
549
|
+
|
|
550
|
+
**Incorrect (flag parade):**
|
|
551
|
+
|
|
552
|
+
```dart
|
|
553
|
+
QuestClaimButton(
|
|
554
|
+
quest: quest,
|
|
555
|
+
onSuccess: onClaimSuccess,
|
|
556
|
+
isExpired: isExpired,
|
|
557
|
+
isTutorial: isTutorial,
|
|
558
|
+
bonus: bonus,
|
|
559
|
+
)
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
**Correct (typed data object):**
|
|
563
|
+
|
|
564
|
+
```dart
|
|
565
|
+
QuestClaimButton(
|
|
566
|
+
mode: QuestClaimMode.fromContext(quest: quest, user: user, isTutorial: isTutorial),
|
|
567
|
+
onSuccess: onClaimSuccess,
|
|
568
|
+
)
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
**Why it matters:**
|
|
572
|
+
- Child widget's API is stable even as requirements change
|
|
573
|
+
- Parent doesn't need to know child's internal decision logic
|
|
574
|
+
- Data dependencies are explicit in the mode type
|
|
575
|
+
- Easier to test: create mode objects directly
|
|
576
|
+
|
|
577
|
+
---
|
|
578
|
+
|
|
579
|
+
### 4.2 Provider Tree Architecture
|
|
580
|
+
|
|
581
|
+
**Impact: MEDIUM (Clarifies data flow)**
|
|
582
|
+
|
|
583
|
+
Flat provider structure where many providers read from many others with no clear hierarchy.
|
|
584
|
+
|
|
585
|
+
**Mental model:**
|
|
586
|
+
- **Root providers**: Match app lifecycle, never disposed. Hold core entities (user, games, quests). Referenced from many places.
|
|
587
|
+
- **Branch providers**: Combine/filter/transform core data. May become "core" as app grows.
|
|
588
|
+
- **Leaf providers**: Screen-specific or ephemeral. Depend on branches, rarely watched by other providers.
|
|
589
|
+
|
|
590
|
+
**Detection signals:**
|
|
591
|
+
- Can't draw the provider dependency graph as a tree
|
|
592
|
+
- Circular or confusing dependency chains
|
|
593
|
+
- "Leaf" providers being watched by other providers
|
|
594
|
+
- Provider doing too much (should split into root + branch)
|
|
595
|
+
|
|
596
|
+
**Correct (tree structure):**
|
|
597
|
+
|
|
598
|
+
```dart
|
|
599
|
+
// Root (core entities, keepAlive: true)
|
|
600
|
+
@Riverpod(keepAlive: true)
|
|
601
|
+
Future<User> user(Ref ref) => ...;
|
|
602
|
+
|
|
603
|
+
@Riverpod(keepAlive: true)
|
|
604
|
+
Future<List<Quest>> quests(Ref ref) => ...;
|
|
605
|
+
|
|
606
|
+
// Branch (derived/filtered, may become core as app grows)
|
|
607
|
+
@riverpod
|
|
608
|
+
Future<List<Quest>> squadQuests(Ref ref) async {
|
|
609
|
+
final quests = await ref.watch(questsProvider.future);
|
|
610
|
+
return quests.where((q) => q.type == QuestType.squad).toList();
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// Leaf (screen-specific, ephemeral)
|
|
614
|
+
@riverpod
|
|
615
|
+
class SquadQuestScreenState extends _$SquadQuestScreenState {
|
|
616
|
+
// Only watches branch providers, never watched by others
|
|
617
|
+
}
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
**Why it matters:**
|
|
621
|
+
- Clear mental model of data flow
|
|
622
|
+
- Predictable rebuild scope
|
|
623
|
+
- Easier to add new derived providers
|
|
624
|
+
- Natural place for each piece of logic
|
|
625
|
+
|
|
626
|
+
---
|
|
627
|
+
|
|
628
|
+
### 4.3 Temporal Coupling
|
|
629
|
+
|
|
630
|
+
**Impact: MEDIUM (Catches misuse at compile time)**
|
|
631
|
+
|
|
632
|
+
Operations must happen in a specific order, but nothing enforces that order.
|
|
633
|
+
|
|
634
|
+
**Detection signals:**
|
|
635
|
+
- Methods that must be called before others
|
|
636
|
+
- Comments like "must call X first" or "call after Y"
|
|
637
|
+
- Objects can be in an "invalid" state between operations
|
|
638
|
+
- Tests have setup steps that could be forgotten
|
|
639
|
+
|
|
640
|
+
**Incorrect (implicit sequence):**
|
|
641
|
+
|
|
642
|
+
```dart
|
|
643
|
+
class PaymentProcessor {
|
|
644
|
+
void init() { ... }
|
|
645
|
+
void setAmount(int amount) { ... }
|
|
646
|
+
void setCustomer(Customer c) { ... }
|
|
647
|
+
Future<void> process() { ... } // Must call init, setAmount, setCustomer first!
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
// Easy to misuse:
|
|
651
|
+
final processor = PaymentProcessor();
|
|
652
|
+
processor.process(); // Boom - forgot to init
|
|
653
|
+
```
|
|
654
|
+
|
|
655
|
+
**Correct (type-enforced sequence):**
|
|
656
|
+
|
|
657
|
+
```dart
|
|
658
|
+
// Builder pattern enforces sequence
|
|
659
|
+
class PaymentBuilder {
|
|
660
|
+
PaymentBuilder withAmount(int amount) => ...;
|
|
661
|
+
PaymentBuilder withCustomer(Customer c) => ...;
|
|
662
|
+
Payment build() => ...; // Only callable when all required fields set
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
// Or use types to enforce state
|
|
666
|
+
class UninitializedProcessor { InitializedProcessor init() => ...; }
|
|
667
|
+
class InitializedProcessor { Future<Result> process(int amount, Customer c) => ...; }
|
|
668
|
+
```
|
|
669
|
+
|
|
670
|
+
**Why it matters:**
|
|
671
|
+
- Compiler catches misuse, not runtime
|
|
672
|
+
- Self-documenting: types show valid sequences
|
|
673
|
+
- Impossible to forget required steps
|
|
674
|
+
- Easier onboarding for new developers
|
|
675
|
+
|
|
676
|
+
---
|
|
677
|
+
|
|
678
|
+
## 5. Pragmatism
|
|
679
|
+
|
|
680
|
+
**Impact: MEDIUM**
|
|
681
|
+
|
|
682
|
+
Over-engineering and inconsistency create unnecessary complexity.
|
|
683
|
+
|
|
684
|
+
### 5.1 Speculative Generality
|
|
685
|
+
|
|
686
|
+
**Impact: MEDIUM (Reduces unnecessary complexity)**
|
|
687
|
+
|
|
688
|
+
Abstractions added for hypothetical future needs that may never materialize.
|
|
689
|
+
|
|
690
|
+
**Detection signals:**
|
|
691
|
+
- Interface with only one implementation
|
|
692
|
+
- Factory that only creates one type
|
|
693
|
+
- Configuration options no one uses
|
|
694
|
+
- Abstraction added "in case we need it later"
|
|
695
|
+
|
|
696
|
+
**Incorrect (premature abstraction):**
|
|
697
|
+
|
|
698
|
+
```dart
|
|
699
|
+
// "We might need different storage backends someday"
|
|
700
|
+
abstract class StorageStrategy { ... }
|
|
701
|
+
class LocalStorageStrategy implements StorageStrategy { ... }
|
|
702
|
+
class StorageFactory {
|
|
703
|
+
static StorageStrategy create(StorageType type) => switch (type) {
|
|
704
|
+
StorageType.local => LocalStorageStrategy(), // Only one ever used
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
```
|
|
708
|
+
|
|
709
|
+
**Correct (direct usage):**
|
|
710
|
+
|
|
711
|
+
```dart
|
|
712
|
+
// Just use the thing directly
|
|
713
|
+
class LocalStorage {
|
|
714
|
+
Future<void> save(String key, String value) => ...;
|
|
715
|
+
Future<String?> load(String key) => ...;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
// When you ACTUALLY need a second implementation, THEN abstract
|
|
719
|
+
// The refactoring is straightforward and you'll know the right abstraction
|
|
720
|
+
```
|
|
721
|
+
|
|
722
|
+
**Why it matters:**
|
|
723
|
+
- Less code to maintain
|
|
724
|
+
- Abstractions based on real needs fit better
|
|
725
|
+
- Easier to understand: no indirection to trace
|
|
726
|
+
- YAGNI: You Aren't Gonna Need It
|
|
727
|
+
|
|
728
|
+
---
|
|
729
|
+
|
|
730
|
+
### 5.2 Consistent Error Handling
|
|
731
|
+
|
|
732
|
+
**Impact: MEDIUM (Improves UX and debugging)**
|
|
733
|
+
|
|
734
|
+
Ad-hoc try/catch scattered in screens, inconsistent error UX across the app.
|
|
735
|
+
|
|
736
|
+
**Detection signals:**
|
|
737
|
+
- Errors appear differently across screens (toasts vs dialogs vs inline)
|
|
738
|
+
- try/catch blocks scattered in widget code
|
|
739
|
+
- Inconsistent error messaging
|
|
740
|
+
- No standard retry mechanism
|
|
741
|
+
|
|
742
|
+
**Incorrect (ad-hoc handling):**
|
|
743
|
+
|
|
744
|
+
```dart
|
|
745
|
+
// Screen 1: Try/catch with toast
|
|
746
|
+
try {
|
|
747
|
+
await ref.read(saveProvider.notifier).save();
|
|
748
|
+
} catch (e) {
|
|
749
|
+
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('$e')));
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
// Screen 2: Different pattern
|
|
753
|
+
final result = await ref.read(saveProvider.notifier).save();
|
|
754
|
+
if (result.isError) {
|
|
755
|
+
showDialog(...); // Different error UI
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
// Screen 3: No error handling at all
|
|
759
|
+
await ref.read(saveProvider.notifier).save(); // Hope it works!
|
|
760
|
+
```
|
|
761
|
+
|
|
762
|
+
**Correct (consistent pattern):**
|
|
763
|
+
|
|
764
|
+
```dart
|
|
765
|
+
// Providers handle errors uniformly
|
|
766
|
+
@riverpod
|
|
767
|
+
class SaveController extends _$SaveController {
|
|
768
|
+
Future<void> save() async {
|
|
769
|
+
state = const AsyncLoading();
|
|
770
|
+
state = await AsyncValue.guard(() => _repository.save());
|
|
771
|
+
// Error stays in state, not thrown
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
// Screens use consistent pattern
|
|
776
|
+
Widget build(context, ref) {
|
|
777
|
+
// Centralized error listening
|
|
778
|
+
ref.listenOnError(saveProvider);
|
|
779
|
+
|
|
780
|
+
final saveState = ref.watch(saveProvider);
|
|
781
|
+
|
|
782
|
+
return AppPrimaryButton(
|
|
783
|
+
isLoading: saveState.isLoading,
|
|
784
|
+
onPressed: () => ref.read(saveProvider.notifier).save(),
|
|
785
|
+
child: Text('Save'),
|
|
786
|
+
);
|
|
787
|
+
}
|
|
788
|
+
```
|
|
789
|
+
|
|
790
|
+
**Why it matters:**
|
|
791
|
+
- Users get consistent experience
|
|
792
|
+
- Developers follow one pattern
|
|
793
|
+
- Error states are explicit and testable
|
|
794
|
+
- Retry logic is standardized
|
|
795
|
+
|
|
796
|
+
---
|
|
797
|
+
|
|
798
|
+
## Context Gathering
|
|
799
|
+
|
|
800
|
+
When asked to review code, first identify the target:
|
|
801
|
+
|
|
802
|
+
If target is unclear, ask:
|
|
803
|
+
- What code should be reviewed? (specific files, a feature folder, uncommitted changes, a commit, a patch file)
|
|
804
|
+
- Is there a specific concern or area of focus?
|
|
805
|
+
|
|
806
|
+
For the specified target, gather the relevant code:
|
|
807
|
+
- **Commit**: `git show <commit>`
|
|
808
|
+
- **Patch file**: Read the patch file
|
|
809
|
+
- **Uncommitted changes**: `git diff` and `git diff --cached`
|
|
810
|
+
- **Folder/feature**: Read the relevant files in that directory
|
|
811
|
+
- **Specific file**: Read that file and related files it imports/uses
|
|
812
|
+
|
|
813
|
+
---
|
|
814
|
+
|
|
815
|
+
## Analysis Process
|
|
816
|
+
|
|
817
|
+
1. **Read thoroughly** - Understand what the code does, not just its structure
|
|
818
|
+
|
|
819
|
+
2. **Apply the three lenses** - For each lens, note specific instances (or note "no issues found")
|
|
820
|
+
|
|
821
|
+
3. **Check for additional patterns** - If you notice issues beyond the core lenses, consult the principle sections for precise diagnosis
|
|
822
|
+
|
|
823
|
+
4. **Prioritize by evolution impact**:
|
|
824
|
+
- High: Will cause cascading changes when requirements shift
|
|
825
|
+
- Medium: Creates friction but contained to one area
|
|
826
|
+
- Low: Suboptimal but won't compound
|
|
827
|
+
|
|
828
|
+
5. **Formulate concrete suggestions** - Name specific extractions, show before/after for the highest-impact change
|
|
829
|
+
|
|
830
|
+
---
|
|
831
|
+
|
|
832
|
+
## Output Format
|
|
833
|
+
|
|
834
|
+
```markdown
|
|
835
|
+
## Senior Review: [Target]
|
|
836
|
+
|
|
837
|
+
### Summary
|
|
838
|
+
[1-2 sentences: Overall assessment and the single most important structural opportunity]
|
|
839
|
+
|
|
840
|
+
### Findings
|
|
841
|
+
|
|
842
|
+
#### High Impact: [Issue Name]
|
|
843
|
+
**What I noticed:** [Specific code pattern observed]
|
|
844
|
+
**Why it matters:** [How this will cause problems as code evolves]
|
|
845
|
+
**Suggestion:** [Concrete refactoring - name the types/widgets to extract]
|
|
846
|
+
|
|
847
|
+
#### Medium Impact: [Issue Name]
|
|
848
|
+
[Same structure]
|
|
849
|
+
|
|
850
|
+
#### Low Impact: [Issue Name]
|
|
851
|
+
[Same structure]
|
|
852
|
+
|
|
853
|
+
### No Issues Found
|
|
854
|
+
[If a lens revealed no problems, briefly note: "State modeling: No boolean flag combinations or repeated decision logic detected."]
|
|
855
|
+
|
|
856
|
+
---
|
|
857
|
+
|
|
858
|
+
**What's your take on these suggestions? Any context I'm missing?**
|
|
859
|
+
```
|
|
860
|
+
|
|
861
|
+
---
|
|
862
|
+
|
|
863
|
+
## Success Criteria
|
|
864
|
+
|
|
865
|
+
- At least one finding per applicable lens (or explicit "no issues" statement)
|
|
866
|
+
- Each finding tied to evolution impact, not just "could be better"
|
|
867
|
+
- Suggestions are concrete: specific types/widgets named, not vague advice
|
|
868
|
+
- No forced findings - if code is solid, say so
|
|
869
|
+
- User has opportunity to provide context before changes
|