timsquad 3.3.0 → 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.
- package/README.ko.md +288 -0
- package/README.md +158 -151
- package/dist/commands/compile.d.ts +3 -0
- package/dist/commands/compile.d.ts.map +1 -0
- package/dist/commands/compile.js +170 -0
- package/dist/commands/compile.js.map +1 -0
- package/dist/commands/daemon.d.ts.map +1 -1
- package/dist/commands/daemon.js +95 -5
- package/dist/commands/daemon.js.map +1 -1
- package/dist/commands/full.js +1 -0
- package/dist/commands/full.js.map +1 -1
- package/dist/commands/git/pr.js +6 -5
- package/dist/commands/git/pr.js.map +1 -1
- package/dist/commands/git/release.js +2 -7
- package/dist/commands/git/release.js.map +1 -1
- package/dist/commands/improve.js +2 -2
- package/dist/commands/improve.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +12 -3
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/log.d.ts.map +1 -1
- package/dist/commands/log.js +2 -2
- package/dist/commands/log.js.map +1 -1
- package/dist/commands/metrics.d.ts.map +1 -1
- package/dist/commands/metrics.js +6 -2
- package/dist/commands/metrics.js.map +1 -1
- package/dist/commands/retro.js +8 -8
- package/dist/commands/retro.js.map +1 -1
- package/dist/commands/session.js +3 -3
- package/dist/commands/session.js.map +1 -1
- package/dist/commands/skills.d.ts +12 -0
- package/dist/commands/skills.d.ts.map +1 -0
- package/dist/commands/skills.js +228 -0
- package/dist/commands/skills.js.map +1 -0
- package/dist/commands/status.js +1 -1
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/upgrade.d.ts.map +1 -1
- package/dist/commands/upgrade.js +23 -1
- package/dist/commands/upgrade.js.map +1 -1
- package/dist/daemon/entry.js +3 -3
- package/dist/daemon/entry.js.map +1 -1
- package/dist/daemon/event-queue.d.ts.map +1 -1
- package/dist/daemon/event-queue.js +2 -2
- package/dist/daemon/event-queue.js.map +1 -1
- package/dist/daemon/index.d.ts +4 -2
- package/dist/daemon/index.d.ts.map +1 -1
- package/dist/daemon/index.js +214 -52
- package/dist/daemon/index.js.map +1 -1
- package/dist/daemon/jsonl-watcher.d.ts +1 -0
- package/dist/daemon/jsonl-watcher.d.ts.map +1 -1
- package/dist/daemon/jsonl-watcher.js.map +1 -1
- package/dist/daemon/meta-cache.d.ts +1 -0
- package/dist/daemon/meta-cache.d.ts.map +1 -1
- package/dist/daemon/meta-cache.js +9 -0
- package/dist/daemon/meta-cache.js.map +1 -1
- package/dist/daemon/session-notes.d.ts +33 -0
- package/dist/daemon/session-notes.d.ts.map +1 -0
- package/dist/daemon/session-notes.js +74 -0
- package/dist/daemon/session-notes.js.map +1 -0
- package/dist/daemon/session-state.d.ts +27 -0
- package/dist/daemon/session-state.d.ts.map +1 -0
- package/dist/daemon/session-state.js +165 -0
- package/dist/daemon/session-state.js.map +1 -0
- package/dist/daemon/shutdown.d.ts.map +1 -1
- package/dist/daemon/shutdown.js +9 -1
- package/dist/daemon/shutdown.js.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/agent-generator.d.ts +4 -0
- package/dist/lib/agent-generator.d.ts.map +1 -1
- package/dist/lib/agent-generator.js +52 -3
- package/dist/lib/agent-generator.js.map +1 -1
- package/dist/lib/compile-rules.d.ts +66 -0
- package/dist/lib/compile-rules.d.ts.map +1 -0
- package/dist/lib/compile-rules.js +114 -0
- package/dist/lib/compile-rules.js.map +1 -0
- package/dist/lib/compiler.d.ts +105 -0
- package/dist/lib/compiler.d.ts.map +1 -0
- package/dist/lib/compiler.js +368 -0
- package/dist/lib/compiler.js.map +1 -0
- package/dist/lib/config.d.ts +1 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +8 -1
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/project.d.ts.map +1 -1
- package/dist/lib/project.js +8 -3
- package/dist/lib/project.js.map +1 -1
- package/dist/lib/skill-generator.d.ts.map +1 -1
- package/dist/lib/skill-generator.js +22 -1
- package/dist/lib/skill-generator.js.map +1 -1
- package/dist/lib/template.d.ts.map +1 -1
- package/dist/lib/template.js +6 -0
- package/dist/lib/template.js.map +1 -1
- package/dist/types/config.d.ts +1 -0
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/config.js +12 -1
- package/dist/types/config.js.map +1 -1
- package/dist/types/project.d.ts +1 -1
- package/dist/types/project.d.ts.map +1 -1
- package/dist/types/project.js +2 -0
- package/dist/types/project.js.map +1 -1
- package/package.json +4 -4
- package/templates/base/agents/base/tsq-architect.md +2 -2
- package/templates/base/agents/overlays/domain/mobile/_common.md +13 -0
- package/templates/base/knowledge/checklists/plan-quality.md +31 -0
- package/templates/base/knowledge/checklists/stability-verification.md +14 -0
- package/templates/base/skills/controller/SKILL.md +111 -0
- package/templates/base/skills/controller/references/README.md +35 -0
- package/templates/base/skills/controller/rules/README.md +18 -0
- package/templates/base/skills/mobile/dart/SKILL.md +69 -0
- package/templates/base/skills/mobile/dart/rules/async-patterns.md +112 -0
- package/templates/base/skills/mobile/dart/rules/code-style.md +96 -0
- package/templates/base/skills/mobile/dart/rules/null-safety.md +84 -0
- package/templates/base/skills/mobile/dart/rules/type-system.md +111 -0
- package/templates/base/skills/mobile/flutter/SKILL.md +89 -0
- package/templates/base/skills/mobile/flutter/ci-cd/SKILL.md +82 -0
- package/templates/base/skills/mobile/flutter/ci-cd/references/ci-cd-pipeline.md +314 -0
- package/templates/base/skills/mobile/flutter/ci-cd/rules/code-signing.md +106 -0
- package/templates/base/skills/mobile/flutter/ci-cd/rules/codemagic-setup.md +116 -0
- package/templates/base/skills/mobile/flutter/ci-cd/rules/fastlane-setup.md +105 -0
- package/templates/base/skills/mobile/flutter/ci-cd/rules/github-actions.md +112 -0
- package/templates/base/skills/mobile/flutter/ci-cd/rules/store-deployment.md +106 -0
- package/templates/base/skills/mobile/flutter/ci-cd/rules/versioning.md +107 -0
- package/templates/base/skills/mobile/flutter/i18n/SKILL.md +78 -0
- package/templates/base/skills/mobile/flutter/i18n/references/i18n-architecture.md +225 -0
- package/templates/base/skills/mobile/flutter/i18n/rules/arb-files.md +182 -0
- package/templates/base/skills/mobile/flutter/i18n/rules/locale-switching.md +226 -0
- package/templates/base/skills/mobile/flutter/i18n/rules/localization-setup.md +137 -0
- package/templates/base/skills/mobile/flutter/i18n/rules/plural-gender.md +159 -0
- package/templates/base/skills/mobile/flutter/i18n/rules/text-direction.md +199 -0
- package/templates/base/skills/mobile/flutter/monitoring/SKILL.md +81 -0
- package/templates/base/skills/mobile/flutter/monitoring/references/monitoring-architecture.md +269 -0
- package/templates/base/skills/mobile/flutter/monitoring/rules/analytics.md +227 -0
- package/templates/base/skills/mobile/flutter/monitoring/rules/crashlytics-setup.md +195 -0
- package/templates/base/skills/mobile/flutter/monitoring/rules/logging.md +258 -0
- package/templates/base/skills/mobile/flutter/monitoring/rules/performance-monitoring.md +248 -0
- package/templates/base/skills/mobile/flutter/monitoring/rules/sentry-integration.md +249 -0
- package/templates/base/skills/mobile/flutter/networking/SKILL.md +88 -0
- package/templates/base/skills/mobile/flutter/networking/references/api-client-architecture.md +305 -0
- package/templates/base/skills/mobile/flutter/networking/rules/caching.md +212 -0
- package/templates/base/skills/mobile/flutter/networking/rules/connectivity.md +213 -0
- package/templates/base/skills/mobile/flutter/networking/rules/dio-setup.md +159 -0
- package/templates/base/skills/mobile/flutter/networking/rules/error-handling.md +209 -0
- package/templates/base/skills/mobile/flutter/networking/rules/interceptors.md +205 -0
- package/templates/base/skills/mobile/flutter/networking/rules/retrofit-patterns.md +194 -0
- package/templates/base/skills/mobile/flutter/push-notifications/SKILL.md +87 -0
- package/templates/base/skills/mobile/flutter/push-notifications/references/notification-architecture.md +340 -0
- package/templates/base/skills/mobile/flutter/push-notifications/references/platform-setup.md +286 -0
- package/templates/base/skills/mobile/flutter/push-notifications/rules/background-processing.md +308 -0
- package/templates/base/skills/mobile/flutter/push-notifications/rules/deep-linking.md +217 -0
- package/templates/base/skills/mobile/flutter/push-notifications/rules/fcm-setup.md +164 -0
- package/templates/base/skills/mobile/flutter/push-notifications/rules/local-notifications.md +262 -0
- package/templates/base/skills/mobile/flutter/push-notifications/rules/notification-handling.md +210 -0
- package/templates/base/skills/mobile/flutter/push-notifications/rules/notification-permissions.md +246 -0
- package/templates/base/skills/mobile/flutter/push-notifications/rules/rich-notifications.md +320 -0
- package/templates/base/skills/mobile/flutter/references/freezed-patterns.md +162 -0
- package/templates/base/skills/mobile/flutter/references/project-structure.md +170 -0
- package/templates/base/skills/mobile/flutter/rules/animations.md +112 -0
- package/templates/base/skills/mobile/flutter/rules/architecture.md +121 -0
- package/templates/base/skills/mobile/flutter/rules/navigation-routing.md +117 -0
- package/templates/base/skills/mobile/flutter/rules/performance.md +112 -0
- package/templates/base/skills/mobile/flutter/rules/platform-adaptive.md +126 -0
- package/templates/base/skills/mobile/flutter/rules/state-management.md +110 -0
- package/templates/base/skills/mobile/flutter/rules/testing.md +131 -0
- package/templates/base/skills/mobile/flutter/rules/widget-conventions.md +122 -0
- package/templates/base/skills/mobile/flutter/security/SKILL.md +86 -0
- package/templates/base/skills/mobile/flutter/security/references/mobile-security-checklist.md +168 -0
- package/templates/base/skills/mobile/flutter/security/rules/api-key-protection.md +206 -0
- package/templates/base/skills/mobile/flutter/security/rules/authentication.md +248 -0
- package/templates/base/skills/mobile/flutter/security/rules/data-protection.md +271 -0
- package/templates/base/skills/mobile/flutter/security/rules/obfuscation.md +213 -0
- package/templates/base/skills/mobile/flutter/security/rules/secure-storage.md +171 -0
- package/templates/base/skills/mobile/flutter/security/rules/ssl-pinning.md +197 -0
- package/templates/base/skills/stability-verification/SKILL.md +64 -0
- package/templates/base/skills/stability-verification/references/release-checklist.md +34 -0
- package/templates/base/skills/stability-verification/references/security-fix-patterns.md +112 -0
- package/templates/base/skills/stability-verification/rules/verification-layers.md +67 -0
- package/templates/base/skills/stability-verification/rules/verification-workflow.md +69 -0
- package/templates/base/skills/stability-verification/scripts/verify.sh +294 -0
- package/templates/platforms/claude-code/CLAUDE.md.template +25 -0
- package/templates/platforms/claude-code/rules/build-gate.md +28 -0
- package/templates/platforms/claude-code/rules/completion-verification.md +30 -0
- package/templates/platforms/claude-code/rules/context-monitor.md +23 -0
- package/templates/platforms/claude-code/rules/plan-review.md +45 -0
- package/templates/platforms/claude-code/rules/quality-guards.md +43 -0
- package/templates/platforms/claude-code/rules/session-notes.md +18 -0
- package/templates/platforms/claude-code/rules/skill-suggest.md +27 -0
- package/templates/platforms/claude-code/scripts/build-gate.sh +73 -0
- package/templates/platforms/claude-code/scripts/completion-guard.sh +93 -0
- package/templates/platforms/claude-code/scripts/phase-guard.sh +79 -0
- package/templates/platforms/claude-code/scripts/safe-guard.sh +83 -0
- package/templates/platforms/claude-code/scripts/skill-rules.json +85 -0
- package/templates/platforms/claude-code/scripts/skill-suggest.sh +105 -0
- package/templates/platforms/claude-code/settings.json +111 -3
- package/templates/project-types/mobile-app/config.yaml +123 -0
- package/templates/project-types/mobile-app/process/workflow.xml +191 -0
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Text Direction & RTL Layout Support
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: "RTL 미대응 → 아랍어/히브리어 사용자 UX 파괴, 레이아웃 반전 오류"
|
|
5
|
+
tags: rtl, ltr, directionality, edge-insets-directional, alignment-directional
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Text Direction & RTL Layout Support
|
|
9
|
+
|
|
10
|
+
**Impact: HIGH (RTL 미대응 → 아랍어/히브리어 사용자 UX 파괴, 레이아웃 반전 오류)**
|
|
11
|
+
|
|
12
|
+
RTL (Right-to-Left) 레이아웃 지원. Directionality 위젯, EdgeInsetsDirectional,
|
|
13
|
+
AlignmentDirectional, 아이콘 미러링. RTL 로캘 지원 시 필수.
|
|
14
|
+
|
|
15
|
+
### Directional 위젯 사용
|
|
16
|
+
|
|
17
|
+
**Incorrect (하드코딩된 left/right):**
|
|
18
|
+
```dart
|
|
19
|
+
Container(
|
|
20
|
+
padding: const EdgeInsets.only(left: 16, right: 8),
|
|
21
|
+
alignment: Alignment.centerLeft,
|
|
22
|
+
child: Row(
|
|
23
|
+
children: [
|
|
24
|
+
const Icon(Icons.arrow_back), // RTL에서도 왼쪽 화살표
|
|
25
|
+
const SizedBox(width: 8),
|
|
26
|
+
Text(title),
|
|
27
|
+
],
|
|
28
|
+
),
|
|
29
|
+
);
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
**Correct (Directional 위젯으로 RTL 자동 대응):**
|
|
33
|
+
```dart
|
|
34
|
+
Container(
|
|
35
|
+
padding: const EdgeInsetsDirectional.only(start: 16, end: 8),
|
|
36
|
+
alignment: AlignmentDirectional.centerStart,
|
|
37
|
+
child: Row(
|
|
38
|
+
children: [
|
|
39
|
+
Icon(
|
|
40
|
+
// RTL에서는 자동으로 arrow_forward (오른쪽→왼쪽 방향)
|
|
41
|
+
Directionality.of(context) == TextDirection.rtl
|
|
42
|
+
? Icons.arrow_forward
|
|
43
|
+
: Icons.arrow_back,
|
|
44
|
+
),
|
|
45
|
+
const SizedBox(width: 8),
|
|
46
|
+
Text(title),
|
|
47
|
+
],
|
|
48
|
+
),
|
|
49
|
+
);
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### EdgeInsets → EdgeInsetsDirectional 변환
|
|
53
|
+
|
|
54
|
+
```dart
|
|
55
|
+
// ❌ 물리적 방향 (RTL에서 반전 안 됨)
|
|
56
|
+
EdgeInsets.only(left: 16)
|
|
57
|
+
EdgeInsets.fromLTRB(16, 8, 0, 8)
|
|
58
|
+
EdgeInsets.symmetric(horizontal: 16) // ✅ 대칭은 OK
|
|
59
|
+
|
|
60
|
+
// ✅ 논리적 방향 (RTL에서 자동 반전)
|
|
61
|
+
EdgeInsetsDirectional.only(start: 16)
|
|
62
|
+
EdgeInsetsDirectional.fromSTEB(16, 8, 0, 8) // Start, Top, End, Bottom
|
|
63
|
+
EdgeInsetsDirectional.only(start: 16, end: 8)
|
|
64
|
+
|
|
65
|
+
// Padding 위젯도 동일
|
|
66
|
+
Padding(
|
|
67
|
+
padding: const EdgeInsetsDirectional.only(start: 16),
|
|
68
|
+
child: child,
|
|
69
|
+
);
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Alignment → AlignmentDirectional 변환
|
|
73
|
+
|
|
74
|
+
```dart
|
|
75
|
+
// ❌ 물리적 정렬
|
|
76
|
+
Alignment.centerLeft
|
|
77
|
+
Alignment.topRight
|
|
78
|
+
|
|
79
|
+
// ✅ 논리적 정렬
|
|
80
|
+
AlignmentDirectional.centerStart // LTR: left, RTL: right
|
|
81
|
+
AlignmentDirectional.topEnd // LTR: right, RTL: left
|
|
82
|
+
|
|
83
|
+
// Container, Align, Positioned 등에 적용
|
|
84
|
+
Container(
|
|
85
|
+
alignment: AlignmentDirectional.centerStart,
|
|
86
|
+
child: child,
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
Align(
|
|
90
|
+
alignment: AlignmentDirectional.topEnd,
|
|
91
|
+
child: badge,
|
|
92
|
+
);
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Positioned → PositionedDirectional
|
|
96
|
+
|
|
97
|
+
```dart
|
|
98
|
+
// ❌ 물리적 위치 (RTL 미대응)
|
|
99
|
+
Positioned(
|
|
100
|
+
left: 16,
|
|
101
|
+
top: 8,
|
|
102
|
+
child: badge,
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
// ✅ 논리적 위치 (RTL 자동 반전)
|
|
106
|
+
PositionedDirectional(
|
|
107
|
+
start: 16,
|
|
108
|
+
top: 8,
|
|
109
|
+
child: badge,
|
|
110
|
+
);
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### 아이콘 미러링
|
|
114
|
+
|
|
115
|
+
```dart
|
|
116
|
+
/// 방향성을 가진 아이콘 (화살표, 체브론 등)은 RTL에서 미러링
|
|
117
|
+
class DirectionalIcon extends StatelessWidget {
|
|
118
|
+
final IconData icon;
|
|
119
|
+
final double? size;
|
|
120
|
+
final Color? color;
|
|
121
|
+
|
|
122
|
+
const DirectionalIcon(this.icon, {super.key, this.size, this.color});
|
|
123
|
+
|
|
124
|
+
@override
|
|
125
|
+
Widget build(BuildContext context) {
|
|
126
|
+
final isRtl = Directionality.of(context) == TextDirection.rtl;
|
|
127
|
+
|
|
128
|
+
// 방향성 아이콘만 미러링 (체크마크, 플러스 등은 미러링 X)
|
|
129
|
+
if (isRtl) {
|
|
130
|
+
return Transform.flip(
|
|
131
|
+
flipX: true,
|
|
132
|
+
child: Icon(icon, size: size, color: color),
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return Icon(icon, size: size, color: color);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// 사용 예
|
|
141
|
+
DirectionalIcon(Icons.arrow_forward_ios, size: 16), // RTL에서 자동 반전
|
|
142
|
+
Icon(Icons.check, size: 16), // 방향 무관 → 미러링 불필요
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### RTL 테스트
|
|
146
|
+
|
|
147
|
+
```dart
|
|
148
|
+
// 테스트에서 RTL 환경 시뮬레이션
|
|
149
|
+
testWidgets('renders correctly in RTL', (tester) async {
|
|
150
|
+
await tester.pumpWidget(
|
|
151
|
+
Directionality(
|
|
152
|
+
textDirection: TextDirection.rtl,
|
|
153
|
+
child: MediaQuery(
|
|
154
|
+
data: const MediaQueryData(),
|
|
155
|
+
child: Material(
|
|
156
|
+
child: MyWidget(),
|
|
157
|
+
),
|
|
158
|
+
),
|
|
159
|
+
),
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
// start padding이 RTL에서 오른쪽에 적용되는지 확인
|
|
163
|
+
final container = tester.widget<Container>(find.byType(Container));
|
|
164
|
+
// ...assertions
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
// 전체 앱을 RTL로 실행 (디버그 확인용)
|
|
168
|
+
MaterialApp(
|
|
169
|
+
locale: const Locale('ar'), // 아랍어 → RTL 자동 적용
|
|
170
|
+
// ...
|
|
171
|
+
);
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Row/Column 방향
|
|
175
|
+
|
|
176
|
+
```dart
|
|
177
|
+
// Row는 TextDirection을 자동 반영
|
|
178
|
+
// LTR: [icon] [text] [chevron →]
|
|
179
|
+
// RTL: [← chevron] [text] [icon]
|
|
180
|
+
Row(
|
|
181
|
+
children: [
|
|
182
|
+
const Icon(Icons.person),
|
|
183
|
+
Expanded(child: Text(context.l10n.profileLabel)),
|
|
184
|
+
const DirectionalIcon(Icons.chevron_right),
|
|
185
|
+
],
|
|
186
|
+
);
|
|
187
|
+
// → Row 내부 순서 변경 불필요, Flutter가 자동 반전
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### 규칙
|
|
191
|
+
|
|
192
|
+
- `EdgeInsets` → `EdgeInsetsDirectional` 로 교체 (left/right → start/end)
|
|
193
|
+
- `Alignment` → `AlignmentDirectional` 로 교체 (centerLeft → centerStart)
|
|
194
|
+
- `Positioned` → `PositionedDirectional` 로 교체 (left → start)
|
|
195
|
+
- 대칭 padding/margin (`symmetric`) → 그대로 사용 가능 (반전 불필요)
|
|
196
|
+
- 방향성 아이콘 (화살표, 체브론) → RTL에서 `Transform.flip` 미러링
|
|
197
|
+
- 방향 무관 아이콘 (체크, 플러스, 검색) → 미러링 금지
|
|
198
|
+
- RTL 테스트 → `Directionality(textDirection: TextDirection.rtl)` 로 래핑
|
|
199
|
+
- `Row`/`ListView` → TextDirection 자동 반영, 순서 변경 불필요
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: monitoring
|
|
3
|
+
description: |
|
|
4
|
+
Flutter 모니터링 & 분석 가이드라인.
|
|
5
|
+
Firebase Crashlytics, Analytics, Performance Monitoring,
|
|
6
|
+
Sentry 통합, 구조화된 로깅.
|
|
7
|
+
version: "1.0.0"
|
|
8
|
+
tags: [flutter, crashlytics, analytics, performance, sentry, monitoring, logging]
|
|
9
|
+
user-invocable: false
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Monitoring & Analytics
|
|
13
|
+
|
|
14
|
+
Flutter 앱 모니터링 통합 가이드.
|
|
15
|
+
Firebase Crashlytics + Analytics + Performance, Sentry, 구조화된 로깅으로
|
|
16
|
+
프로덕션 품질을 수치로 관리.
|
|
17
|
+
|
|
18
|
+
## Philosophy
|
|
19
|
+
|
|
20
|
+
- 크래시는 0순위 — Crashlytics 비활성화 금지, 모든 비치명 에러도 기록
|
|
21
|
+
- 이벤트는 설계 — 무분별한 로깅 금지, 이벤트 택소노미 먼저 정의
|
|
22
|
+
- 성능은 측정 — 체감이 아닌 custom trace와 HTTP metric으로 수치화
|
|
23
|
+
|
|
24
|
+
## Resources
|
|
25
|
+
|
|
26
|
+
5개 규칙 + 1개 참조. 크래시부터 로깅까지 모니터링 파이프라인 전체를 커버.
|
|
27
|
+
|
|
28
|
+
| Priority | Type | Resource | Description |
|
|
29
|
+
|----------|------|----------|-------------|
|
|
30
|
+
| CRITICAL | rule | [crashlytics-setup](rules/crashlytics-setup.md) | Crashlytics 초기화, 에러 캡처, 사용자 식별 |
|
|
31
|
+
| HIGH | rule | [analytics](rules/analytics.md) | Analytics 이벤트 택소노미, 사용자 속성, 화면 추적 |
|
|
32
|
+
| HIGH | rule | [performance-monitoring](rules/performance-monitoring.md) | Performance custom trace, HTTP metric, 프레임 모니터링 |
|
|
33
|
+
| MEDIUM | rule | [sentry-integration](rules/sentry-integration.md) | Sentry 초기화, breadcrumbs, scope, Crashlytics 공존 |
|
|
34
|
+
| MEDIUM | rule | [logging](rules/logging.md) | 구조화된 로깅, 레벨 관리, 민감 데이터 마스킹 |
|
|
35
|
+
| — | ref | [monitoring-architecture](references/monitoring-architecture.md) | 모니터링 레이어, 대시보드, 알림, 데이터 보존 |
|
|
36
|
+
|
|
37
|
+
## Quick Rules
|
|
38
|
+
|
|
39
|
+
### Crashlytics
|
|
40
|
+
- `FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterFatalError`
|
|
41
|
+
- `PlatformDispatcher.instance.onError` → 비동기 에러 캡처
|
|
42
|
+
- 비치명 에러 → `recordError(error, stack, fatal: false)`
|
|
43
|
+
- `setUserIdentifier` → 크래시와 사용자 매핑
|
|
44
|
+
- `setCustomKey` → 디바이스 상태, 기능 플래그 등 컨텍스트
|
|
45
|
+
|
|
46
|
+
### Analytics
|
|
47
|
+
- 이벤트 택소노미 먼저 정의 (screen_view, button_click, feature_use)
|
|
48
|
+
- `setUserProperty` → user_type, subscription_tier 등 세그먼트 기준
|
|
49
|
+
- `AnalyticsObserver` → go_router 화면 전환 자동 추적
|
|
50
|
+
- 디버그 → `adb shell setprop debug.firebase.analytics.app <package>`
|
|
51
|
+
|
|
52
|
+
### Performance
|
|
53
|
+
- `FirebasePerformance.instance.newTrace('name')` → custom trace
|
|
54
|
+
- HTTP metric → 자동 수집 (dio interceptor 또는 http_client)
|
|
55
|
+
- 느린 프레임 임계값 16ms (60fps), 8ms (120fps)
|
|
56
|
+
|
|
57
|
+
### Sentry
|
|
58
|
+
- `SentryFlutter.init` → DSN 환경별 분리 (dev/staging/prod)
|
|
59
|
+
- `Sentry.addBreadcrumb` → 네비게이션, HTTP, 사용자 액션 추적
|
|
60
|
+
- Crashlytics + Sentry 공존 → 각자 역할 분리 (crash vs context)
|
|
61
|
+
|
|
62
|
+
### Logging
|
|
63
|
+
- `logger` 패키지 → 레벨별 색상 출력 (개발), JSON 구조화 (프로덕션)
|
|
64
|
+
- 릴리스 빌드 → verbose/debug 레벨 비활성화
|
|
65
|
+
- 민감 데이터 (토큰, PII) → 마스킹 필수
|
|
66
|
+
|
|
67
|
+
## Checklist
|
|
68
|
+
|
|
69
|
+
| Priority | Item |
|
|
70
|
+
|----------|------|
|
|
71
|
+
| CRITICAL | `FlutterError.onError` + `PlatformDispatcher.onError` 연동 |
|
|
72
|
+
| CRITICAL | Crashlytics 디버그 모드 비활성화 (`setCrashlyticsCollectionEnabled`) |
|
|
73
|
+
| CRITICAL | 릴리스 빌드에서 dSYM / ProGuard mapping 업로드 설정 |
|
|
74
|
+
| HIGH | Analytics 이벤트 택소노미 문서화 후 구현 |
|
|
75
|
+
| HIGH | `AnalyticsObserver` go_router 연동 |
|
|
76
|
+
| HIGH | Performance custom trace 핵심 플로우에 적용 |
|
|
77
|
+
| HIGH | HTTP metric 수집 (dio interceptor) |
|
|
78
|
+
| MEDIUM | Sentry DSN 환경별 분리 |
|
|
79
|
+
| MEDIUM | Sentry breadcrumb 네비게이션/HTTP 자동 기록 |
|
|
80
|
+
| MEDIUM | 구조화된 로깅 + 릴리스 레벨 필터 |
|
|
81
|
+
| MEDIUM | 민감 데이터 마스킹 검증 |
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Monitoring Architecture & Dashboard Configuration
|
|
3
|
+
category: reference
|
|
4
|
+
source: internal
|
|
5
|
+
tags: architecture, dashboard, alerting, data-retention, crashlytics, sentry, analytics
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Monitoring Architecture & Dashboard Configuration
|
|
9
|
+
|
|
10
|
+
모니터링 레이어 구조, 대시보드 구성, 알림 설정, 데이터 보존 정책.
|
|
11
|
+
프로덕션 운영 시 참조.
|
|
12
|
+
|
|
13
|
+
## Key Concepts
|
|
14
|
+
|
|
15
|
+
- **레이어 모델**: crash → error → performance → analytics → logging (우선순위 순)
|
|
16
|
+
- **대시보드 분리**: Firebase Console (크래시/성능) + Sentry (에러 컨텍스트)
|
|
17
|
+
- **알림 전략**: 크래시 스파이크, ANR, 성능 저하에 대한 자동 알림
|
|
18
|
+
- **데이터 보존**: 도구별 보존 기간 + 비용 최적화
|
|
19
|
+
|
|
20
|
+
## Monitoring Layer Model
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
┌─────────────────────────────────────────────────────────┐
|
|
24
|
+
│ Monitoring Layers │
|
|
25
|
+
├─────────┬───────────────┬───────────────┬───────────────┤
|
|
26
|
+
│ Layer │ Tool │ Priority │ Purpose │
|
|
27
|
+
├─────────┼───────────────┼───────────────┼───────────────┤
|
|
28
|
+
│ L1 │ Crashlytics │ CRITICAL │ 앱 크래시 │
|
|
29
|
+
│ Crash │ │ │ ANR/프리징 │
|
|
30
|
+
│ │ │ │ 안정성 % │
|
|
31
|
+
├─────────┼───────────────┼───────────────┼───────────────┤
|
|
32
|
+
│ L2 │ Sentry │ HIGH │ 비치명 에러 │
|
|
33
|
+
│ Error │ + Crashlytics │ │ 에러 컨텍스트 │
|
|
34
|
+
│ │ │ │ breadcrumbs │
|
|
35
|
+
├─────────┼───────────────┼───────────────┼───────────────┤
|
|
36
|
+
│ L3 │ Firebase │ HIGH │ 앱 시작 시간 │
|
|
37
|
+
│ Perf │ Performance │ │ HTTP 응답 │
|
|
38
|
+
│ │ │ │ 프레임 렌더링 │
|
|
39
|
+
├─────────┼───────────────┼───────────────┼───────────────┤
|
|
40
|
+
│ L4 │ Firebase │ MEDIUM │ 사용자 행동 │
|
|
41
|
+
│ Analyt │ Analytics │ │ 전환 퍼널 │
|
|
42
|
+
│ │ │ │ 리텐션 │
|
|
43
|
+
├─────────┼───────────────┼───────────────┼───────────────┤
|
|
44
|
+
│ L5 │ logger + │ LOW │ 디버깅 추적 │
|
|
45
|
+
│ Logging │ Sentry/Crash │ │ 원격 컨텍스트 │
|
|
46
|
+
│ │ │ │ 감사 로그 │
|
|
47
|
+
└─────────┴───────────────┴───────────────┴───────────────┘
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Directory Structure
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
lib/
|
|
54
|
+
├── core/
|
|
55
|
+
│ └── monitoring/
|
|
56
|
+
│ ├── monitoring_service.dart # 통합 초기화 + Facade
|
|
57
|
+
│ ├── crashlytics/
|
|
58
|
+
│ │ ├── crashlytics_service.dart # Crashlytics 래퍼
|
|
59
|
+
│ │ └── crashlytics_error_handler.dart # 에러 핸들러 체인
|
|
60
|
+
│ ├── analytics/
|
|
61
|
+
│ │ ├── analytics_service.dart # Analytics 래퍼
|
|
62
|
+
│ │ ├── analytics_events.dart # 이벤트 상수
|
|
63
|
+
│ │ └── analytics_observer.dart # GoRouter observer
|
|
64
|
+
│ ├── performance/
|
|
65
|
+
│ │ ├── performance_service.dart # Custom trace 래퍼
|
|
66
|
+
│ │ └── performance_interceptor.dart # Dio HTTP metric
|
|
67
|
+
│ ├── sentry/
|
|
68
|
+
│ │ ├── sentry_service.dart # Sentry 래퍼
|
|
69
|
+
│ │ ├── sentry_breadcrumb_service.dart # Breadcrumb 관리
|
|
70
|
+
│ │ └── sentry_navigator_observer.dart # 네비게이션 추적
|
|
71
|
+
│ ├── logging/
|
|
72
|
+
│ │ ├── app_logger.dart # Logger 래퍼
|
|
73
|
+
│ │ ├── log_sanitizer.dart # 민감 데이터 마스킹
|
|
74
|
+
│ │ └── remote_log_output.dart # 원격 로그 출력
|
|
75
|
+
│ └── providers/
|
|
76
|
+
│ └── monitoring_providers.dart # Riverpod providers
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Initialization Order
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
main.dart 초기화 순서:
|
|
83
|
+
│
|
|
84
|
+
├─ 1. WidgetsFlutterBinding.ensureInitialized()
|
|
85
|
+
├─ 2. Firebase.initializeApp()
|
|
86
|
+
├─ 3. FlutterError.onError = Crashlytics.recordFlutterFatalError
|
|
87
|
+
├─ 4. PlatformDispatcher.instance.onError → Crashlytics.recordError
|
|
88
|
+
├─ 5. SentryFlutter.init(appRunner: ...)
|
|
89
|
+
│ └─ 내부에서 runApp() 호출
|
|
90
|
+
│ │
|
|
91
|
+
│ └─ App 초기화 시:
|
|
92
|
+
│ ├─ MonitoringService.initialize()
|
|
93
|
+
│ │ ├─ CrashlyticsService.setUser()
|
|
94
|
+
│ │ ├─ AnalyticsService.setUserProperties()
|
|
95
|
+
│ │ ├─ PerformanceService.enable()
|
|
96
|
+
│ │ └─ AppLogger.initialize()
|
|
97
|
+
│ └─ GoRouter observers: [AnalyticsObserver, SentryObserver]
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Dashboard Configuration
|
|
101
|
+
|
|
102
|
+
### Firebase Console
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
Firebase Console > Crashlytics:
|
|
106
|
+
├─ Overview: 크래시 없는 사용자 % (목표: 99.5% 이상)
|
|
107
|
+
├─ Issues: 크래시 목록 (발생 빈도, 영향 사용자 수)
|
|
108
|
+
├─ Trends: 버전별 안정성 추이
|
|
109
|
+
└─ Velocity alerts: 크래시 급증 알림
|
|
110
|
+
|
|
111
|
+
Firebase Console > Performance:
|
|
112
|
+
├─ Dashboard: 앱 시작 시간, 화면 렌더링
|
|
113
|
+
├─ Network: HTTP 요청 성능 (응답 시간, 성공률)
|
|
114
|
+
├─ Custom traces: 비즈니스 로직 성능
|
|
115
|
+
└─ Threshold alerts: 성능 임계값 알림
|
|
116
|
+
|
|
117
|
+
Firebase Console > Analytics:
|
|
118
|
+
├─ Dashboard: 활성 사용자, 세션
|
|
119
|
+
├─ Events: 이벤트 발생 빈도
|
|
120
|
+
├─ Funnels: 전환 퍼널 (가입 → 매치 생성 → 완료)
|
|
121
|
+
├─ Retention: 일별/주별 리텐션
|
|
122
|
+
└─ Audiences: 사용자 세그먼트
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Sentry Dashboard
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
Sentry > Issues:
|
|
129
|
+
├─ 에러 목록 (발생 빈도, 영향 사용자, 첫 발생/마지막 발생)
|
|
130
|
+
├─ 에러 상세 (breadcrumbs, 태그, 디바이스 정보)
|
|
131
|
+
└─ 에러 그룹화 (fingerprint 기반)
|
|
132
|
+
|
|
133
|
+
Sentry > Performance:
|
|
134
|
+
├─ 트랜잭션 목록 (p50, p75, p95)
|
|
135
|
+
├─ 느린 트랜잭션 식별
|
|
136
|
+
└─ Web Vitals (웹뷰 사용 시)
|
|
137
|
+
|
|
138
|
+
Sentry > Alerts:
|
|
139
|
+
├─ Issue alerts: 새 에러 발생 시
|
|
140
|
+
├─ Metric alerts: 에러율 임계값 초과 시
|
|
141
|
+
└─ Integrations: Slack, PagerDuty, Email
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Alerting Strategy
|
|
145
|
+
|
|
146
|
+
```
|
|
147
|
+
┌──────────────────┬────────────┬──────────────────────────────┐
|
|
148
|
+
│ Alert Type │ Severity │ Action │
|
|
149
|
+
├──────────────────┼────────────┼──────────────────────────────┤
|
|
150
|
+
│ Crash spike │ P0 │ Slack #alerts + PagerDuty │
|
|
151
|
+
│ (>1% 사용자 영향) │ │ 즉시 대응 (핫픽스 검토) │
|
|
152
|
+
├──────────────────┼────────────┼──────────────────────────────┤
|
|
153
|
+
│ ANR rate >0.5% │ P1 │ Slack #alerts │
|
|
154
|
+
│ │ │ 24시간 내 조사 │
|
|
155
|
+
├──────────────────┼────────────┼──────────────────────────────┤
|
|
156
|
+
│ New fatal crash │ P1 │ Slack #alerts │
|
|
157
|
+
│ │ │ 다음 스프린트 수정 │
|
|
158
|
+
├──────────────────┼────────────┼──────────────────────────────┤
|
|
159
|
+
│ Performance │ P2 │ Slack #monitoring │
|
|
160
|
+
│ degradation │ │ 앱 시작 >3s, API p95 >2s │
|
|
161
|
+
├──────────────────┼────────────┼──────────────────────────────┤
|
|
162
|
+
│ Error rate │ P2 │ Sentry alert │
|
|
163
|
+
│ spike (비치명) │ │ 다음 스프린트 우선순위 검토 │
|
|
164
|
+
├──────────────────┼────────────┼──────────────────────────────┤
|
|
165
|
+
│ Drop in daily │ P3 │ Weekly review │
|
|
166
|
+
│ active users │ │ Analytics 퍼널 분석 │
|
|
167
|
+
└──────────────────┴────────────┴──────────────────────────────┘
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## Data Retention Policy
|
|
171
|
+
|
|
172
|
+
```
|
|
173
|
+
┌────────────────┬──────────────┬────────────┬─────────────────┐
|
|
174
|
+
│ Tool │ Free Tier │ Paid Tier │ Recommendation │
|
|
175
|
+
├────────────────┼──────────────┼────────────┼─────────────────┤
|
|
176
|
+
│ Crashlytics │ 90일 │ 90일 │ 그대로 사용 │
|
|
177
|
+
│ Analytics │ 14개월 │ 14개월 │ BigQuery 연동 │
|
|
178
|
+
│ Performance │ 자동 관리 │ 자동 관리 │ 그대로 사용 │
|
|
179
|
+
│ Sentry (Free) │ 30일 │ 90일 │ 주요 이슈 북마크 │
|
|
180
|
+
│ App logs │ 세션 내 │ — │ 원격 로깅 연동 │
|
|
181
|
+
└────────────────┴──────────────┴────────────┴─────────────────┘
|
|
182
|
+
|
|
183
|
+
BigQuery 연동 (Analytics 장기 보존):
|
|
184
|
+
Firebase Console > Project Settings > Integrations > BigQuery
|
|
185
|
+
- 일별 raw event export
|
|
186
|
+
- SQL 쿼리로 커스텀 분석
|
|
187
|
+
- Data Studio / Looker 대시보드 연동
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## Monitoring Service (Facade)
|
|
191
|
+
|
|
192
|
+
```dart
|
|
193
|
+
/// 모니터링 통합 초기화 Facade
|
|
194
|
+
class MonitoringService {
|
|
195
|
+
final CrashlyticsService _crashlytics;
|
|
196
|
+
final AnalyticsService _analytics;
|
|
197
|
+
final PerformanceService _performance;
|
|
198
|
+
final SentryScopeManager _sentry;
|
|
199
|
+
final AppLogger _logger;
|
|
200
|
+
|
|
201
|
+
MonitoringService({
|
|
202
|
+
required CrashlyticsService crashlytics,
|
|
203
|
+
required AnalyticsService analytics,
|
|
204
|
+
required PerformanceService performance,
|
|
205
|
+
required SentryScopeManager sentry,
|
|
206
|
+
required AppLogger logger,
|
|
207
|
+
}) : _crashlytics = crashlytics,
|
|
208
|
+
_analytics = analytics,
|
|
209
|
+
_performance = performance,
|
|
210
|
+
_sentry = sentry,
|
|
211
|
+
_logger = logger;
|
|
212
|
+
|
|
213
|
+
/// 사용자 로그인 시 모든 서비스에 사용자 설정
|
|
214
|
+
Future<void> identifyUser({
|
|
215
|
+
required String userId,
|
|
216
|
+
String? email,
|
|
217
|
+
Map<String, String>? properties,
|
|
218
|
+
}) async {
|
|
219
|
+
await _crashlytics.setUser(userId);
|
|
220
|
+
await _analytics.setUserId(userId);
|
|
221
|
+
await _sentry.setUser(id: userId, email: email);
|
|
222
|
+
_logger.info('User identified: $userId');
|
|
223
|
+
|
|
224
|
+
if (properties != null) {
|
|
225
|
+
await _crashlytics.setContext(
|
|
226
|
+
appVersion: properties['app_version'] ?? '',
|
|
227
|
+
buildNumber: properties['build_number'] ?? '',
|
|
228
|
+
subscriptionTier: properties['tier'],
|
|
229
|
+
);
|
|
230
|
+
for (final entry in properties.entries) {
|
|
231
|
+
await _analytics.setUserProperty(
|
|
232
|
+
name: entry.key,
|
|
233
|
+
value: entry.value,
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/// 로그아웃 시 모든 서비스 정리
|
|
240
|
+
Future<void> clearUser() async {
|
|
241
|
+
await _crashlytics.clearUser();
|
|
242
|
+
await _analytics.setUserId(null);
|
|
243
|
+
await _sentry.clearUser();
|
|
244
|
+
_logger.info('User cleared from monitoring');
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/// Riverpod Provider
|
|
249
|
+
final monitoringServiceProvider = Provider<MonitoringService>((ref) {
|
|
250
|
+
return MonitoringService(
|
|
251
|
+
crashlytics: ref.watch(crashlyticsServiceProvider),
|
|
252
|
+
analytics: ref.watch(analyticsServiceProvider),
|
|
253
|
+
performance: ref.watch(performanceServiceProvider),
|
|
254
|
+
sentry: ref.watch(sentryScopeManagerProvider),
|
|
255
|
+
logger: AppLogger.instance,
|
|
256
|
+
);
|
|
257
|
+
});
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
## Common Pitfalls
|
|
261
|
+
|
|
262
|
+
1. **초기화 순서**: Firebase → Crashlytics 핸들러 → Sentry → 나머지 (순서 중요)
|
|
263
|
+
2. **디버그 데이터 오염**: 개발 중 크래시/이벤트가 프로덕션 대시보드에 혼합 → 환경 분리
|
|
264
|
+
3. **이벤트 쿼터**: Sentry 무료 5K/월, Analytics 500이벤트/사용자/일 → 필터링 필수
|
|
265
|
+
4. **PII 유출**: 로그/이벤트에 이메일/전화번호 포함 → 마스킹 + sendDefaultPii:false
|
|
266
|
+
5. **소스맵 미업로드**: 난독화된 스택트레이스 → dSYM/ProGuard mapping 업로드 자동화
|
|
267
|
+
6. **과도한 breadcrumb**: Sentry 기본 100개 제한 → 중요한 것만 기록
|
|
268
|
+
7. **성능 오버헤드**: 모니터링 SDK 자체가 성능에 영향 → 샘플링률 조절
|
|
269
|
+
8. **알림 피로**: 모든 에러에 알림 → 임계값 기반 알림으로 전환
|