@walwal-harness/cli 4.0.0-alpha.2 → 4.0.0-alpha.20
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 +226 -273
- package/assets/templates/config.json +1 -48
- package/assets/templates/gitignore-append.txt +1 -0
- package/bin/init.js +1 -0
- package/package.json +1 -1
- package/scripts/harness-dashboard-v4.sh +58 -81
- package/scripts/harness-next.sh +4 -15
- package/scripts/harness-prompts-v4.sh +106 -0
- package/scripts/harness-queue-manager.sh +59 -5
- package/scripts/harness-session-start.sh +18 -0
- package/scripts/harness-studio-v4.sh +69 -69
- package/scripts/harness-team-worker.sh +136 -123
- package/scripts/harness-user-prompt-submit.sh +31 -1
- package/skills/dispatcher/SKILL.md +7 -2
- package/skills/evaluator-functional-flutter/SKILL.md +0 -206
- package/skills/evaluator-functional-flutter/references/ia-compliance.md +0 -77
- package/skills/evaluator-functional-flutter/references/scoring-rubric.md +0 -132
- package/skills/evaluator-functional-flutter/references/static-check-rules.md +0 -99
- package/skills/generator-frontend-flutter/SKILL.md +0 -173
- package/skills/generator-frontend-flutter/references/anti-patterns.md +0 -320
- package/skills/generator-frontend-flutter/references/api-layer-pattern.md +0 -233
- package/skills/generator-frontend-flutter/references/flutter-web-pattern.md +0 -273
- package/skills/generator-frontend-flutter/references/i18n-pattern.md +0 -102
- package/skills/generator-frontend-flutter/references/riverpod-pattern.md +0 -199
|
@@ -1,199 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
docmeta:
|
|
3
|
-
id: riverpod-pattern
|
|
4
|
-
title: Riverpod 상태관리 패턴 (Page + VM)
|
|
5
|
-
type: output
|
|
6
|
-
createdAt: 2026-04-09T00:00:00Z
|
|
7
|
-
updatedAt: 2026-04-09T00:00:00Z
|
|
8
|
-
source:
|
|
9
|
-
producer: agent
|
|
10
|
-
skillId: harness-generator-frontend-flutter
|
|
11
|
-
inputs:
|
|
12
|
-
- documentId: clue-fe-flutter-riverpod
|
|
13
|
-
uri: ../../../../../moon_web/clue-fe-flutter.skill
|
|
14
|
-
relation: output-from
|
|
15
|
-
sections:
|
|
16
|
-
- sourceRange:
|
|
17
|
-
startLine: 1
|
|
18
|
-
endLine: 149
|
|
19
|
-
targetRange:
|
|
20
|
-
startLine: 32
|
|
21
|
-
endLine: 180
|
|
22
|
-
tags:
|
|
23
|
-
- flutter
|
|
24
|
-
- riverpod
|
|
25
|
-
- state-management
|
|
26
|
-
- notifier-provider
|
|
27
|
-
---
|
|
28
|
-
|
|
29
|
-
# Riverpod 상태관리 패턴 (Page + VM)
|
|
30
|
-
|
|
31
|
-
## 기본 구조: Page + VM 쌍
|
|
32
|
-
|
|
33
|
-
모든 화면은 `xxx_page.dart` + `xxx_page_vm.dart` 쌍으로 구성.
|
|
34
|
-
이는 테스트 가능성과 UI/로직 분리를 위한 강제 규약.
|
|
35
|
-
|
|
36
|
-
### VM (ViewModel)
|
|
37
|
-
|
|
38
|
-
```dart
|
|
39
|
-
// example_page_vm.dart
|
|
40
|
-
import 'package:equatable/equatable.dart';
|
|
41
|
-
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
42
|
-
import 'package:integrated_data_layer/data_layer.dart';
|
|
43
|
-
|
|
44
|
-
final pExampleProvider =
|
|
45
|
-
NotifierProvider<ExampleNotifier, ExampleState>(ExampleNotifier.new);
|
|
46
|
-
|
|
47
|
-
class ExampleNotifier extends Notifier<ExampleState> {
|
|
48
|
-
@override
|
|
49
|
-
ExampleState build() {
|
|
50
|
-
return const ExampleState();
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
Future<void> loadData() async {
|
|
54
|
-
state = state.copyWith(isLoading: true);
|
|
55
|
-
try {
|
|
56
|
-
final result = await ref.read(dataLayer).ac.getExample(exampleId: 1);
|
|
57
|
-
if (result.code == 200) {
|
|
58
|
-
state = state.copyWith(
|
|
59
|
-
isLoading: false,
|
|
60
|
-
data: result.data,
|
|
61
|
-
);
|
|
62
|
-
} else {
|
|
63
|
-
state = state.copyWith(
|
|
64
|
-
isLoading: false,
|
|
65
|
-
error: 'code=${result.code}',
|
|
66
|
-
);
|
|
67
|
-
}
|
|
68
|
-
} catch (e) {
|
|
69
|
-
state = state.copyWith(isLoading: false, error: e.toString());
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
class ExampleState extends Equatable {
|
|
75
|
-
final bool isLoading;
|
|
76
|
-
final dynamic data;
|
|
77
|
-
final String? error;
|
|
78
|
-
|
|
79
|
-
const ExampleState({
|
|
80
|
-
this.isLoading = false,
|
|
81
|
-
this.data,
|
|
82
|
-
this.error,
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
ExampleState copyWith({
|
|
86
|
-
bool? isLoading,
|
|
87
|
-
dynamic data,
|
|
88
|
-
String? error,
|
|
89
|
-
}) {
|
|
90
|
-
return ExampleState(
|
|
91
|
-
isLoading: isLoading ?? this.isLoading,
|
|
92
|
-
data: data ?? this.data,
|
|
93
|
-
error: error ?? this.error,
|
|
94
|
-
);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
@override
|
|
98
|
-
List<Object?> get props => [isLoading, data, error];
|
|
99
|
-
}
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
### Page
|
|
103
|
-
|
|
104
|
-
```dart
|
|
105
|
-
// example_page.dart
|
|
106
|
-
import 'package:flutter/material.dart';
|
|
107
|
-
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
108
|
-
import 'example_page_vm.dart';
|
|
109
|
-
|
|
110
|
-
class ExamplePage extends ConsumerStatefulWidget {
|
|
111
|
-
const ExamplePage({super.key});
|
|
112
|
-
|
|
113
|
-
@override
|
|
114
|
-
ConsumerState<ExamplePage> createState() => _ExamplePageState();
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
class _ExamplePageState extends ConsumerState<ExamplePage> {
|
|
118
|
-
@override
|
|
119
|
-
void initState() {
|
|
120
|
-
super.initState();
|
|
121
|
-
// 초기 데이터 로드는 addPostFrameCallback 으로
|
|
122
|
-
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
123
|
-
ref.read(pExampleProvider.notifier).loadData();
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
@override
|
|
128
|
-
Widget build(BuildContext context) {
|
|
129
|
-
final state = ref.watch(pExampleProvider); // watch로 상태 구독
|
|
130
|
-
|
|
131
|
-
if (state.isLoading) {
|
|
132
|
-
return const Center(child: CircularProgressIndicator());
|
|
133
|
-
}
|
|
134
|
-
if (state.error != null) {
|
|
135
|
-
return Center(child: Text(state.error!));
|
|
136
|
-
}
|
|
137
|
-
// ... 정상 상태 렌더링
|
|
138
|
-
return const SizedBox.shrink();
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
## Family 패턴 (다중 자식 컴포넌트)
|
|
144
|
-
|
|
145
|
-
리스트 아이템처럼 동일 타입 인스턴스가 여러 개 필요할 때:
|
|
146
|
-
|
|
147
|
-
```dart
|
|
148
|
-
// relay_tile_vm.dart
|
|
149
|
-
final pRelayItemProvider = NotifierProvider.family<
|
|
150
|
-
RelayItemNotifier,
|
|
151
|
-
RelayItemState,
|
|
152
|
-
({int ioId, String topic})>(
|
|
153
|
-
() => RelayItemNotifier(),
|
|
154
|
-
);
|
|
155
|
-
|
|
156
|
-
class RelayItemNotifier
|
|
157
|
-
extends FamilyNotifier<RelayItemState, ({int ioId, String topic})> {
|
|
158
|
-
@override
|
|
159
|
-
RelayItemState build(({int ioId, String topic}) arg) {
|
|
160
|
-
ref.onDispose(() {
|
|
161
|
-
// 정리 로직 (스트림 구독 해제 등)
|
|
162
|
-
});
|
|
163
|
-
return RelayItemState(ioId: arg.ioId, topic: arg.topic);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
Future<void> doAction() async {
|
|
167
|
-
final result = await ref.read(dataLayer).ac.someMethod(id: state.ioId);
|
|
168
|
-
state = state.copyWith(/* ... */);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
사용:
|
|
174
|
-
```dart
|
|
175
|
-
// Page/Widget에서
|
|
176
|
-
final itemState = ref.watch(
|
|
177
|
-
pRelayItemProvider((ioId: item.ioId, topic: item.topic)),
|
|
178
|
-
);
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
## 핵심 규칙
|
|
182
|
-
|
|
183
|
-
| 규칙 | 설명 |
|
|
184
|
-
|------|------|
|
|
185
|
-
| `ref.watch` | UI rebuild이 필요한 곳 (build 메서드 내) |
|
|
186
|
-
| `ref.read` | 일회성 호출 (이벤트 핸들러, initState 콜백) |
|
|
187
|
-
| `dataLayer` 접근 | VM 내에서 `ref.read(dataLayer).repository.method()` |
|
|
188
|
-
| State 불변성 | Equatable 상속 + copyWith 패턴 |
|
|
189
|
-
| Provider 네이밍 | `p` 접두사 + PascalCase + Provider (예: `pHomePageProvider`) |
|
|
190
|
-
| 초기 로드 | `addPostFrameCallback` 으로 첫 프레임 이후 호출 |
|
|
191
|
-
| 에러/로딩 | State에 `isLoading`, `error` 필수 포함 (UI 3가지 상태 처리) |
|
|
192
|
-
|
|
193
|
-
## 금지
|
|
194
|
-
|
|
195
|
-
- `bridges/` 사용 금지 — BLOC 기반 레거시
|
|
196
|
-
- StatefulWidget 내 직접 API 호출 — 반드시 VM 경유
|
|
197
|
-
- `setState` 로 API 응답 상태 관리 — Riverpod 상태로 대체
|
|
198
|
-
- `ref.read`를 build 메서드 내에서 사용 (→ `ref.watch`)
|
|
199
|
-
- VM 내 UI 의존 코드 (Navigator, ScaffoldMessenger 등) — UI 콜백으로 분리
|