@kood/claude-code 0.6.6 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. package/dist/index.js +7 -1
  2. package/package.json +1 -1
  3. package/templates/.claude/agents/analyst.md +5 -0
  4. package/templates/.claude/agents/architect.md +5 -0
  5. package/templates/.claude/agents/build-fixer.md +1 -0
  6. package/templates/.claude/agents/code-reviewer.md +1 -0
  7. package/templates/.claude/agents/critic.md +4 -0
  8. package/templates/.claude/agents/deep-executor.md +1 -0
  9. package/templates/.claude/agents/dependency-manager.md +2 -0
  10. package/templates/.claude/agents/deployment-validator.md +2 -0
  11. package/templates/.claude/agents/designer.md +2 -0
  12. package/templates/.claude/agents/document-writer.md +3 -0
  13. package/templates/.claude/agents/explore.md +1 -0
  14. package/templates/.claude/agents/git-operator.md +2 -0
  15. package/templates/.claude/agents/implementation-executor.md +2 -0
  16. package/templates/.claude/agents/ko-to-en-translator.md +3 -0
  17. package/templates/.claude/agents/lint-fixer.md +2 -0
  18. package/templates/.claude/agents/planner.md +3 -0
  19. package/templates/.claude/agents/pm.md +349 -0
  20. package/templates/.claude/agents/qa-tester.md +1 -0
  21. package/templates/.claude/agents/refactor-advisor.md +4 -0
  22. package/templates/.claude/agents/researcher.md +9 -1
  23. package/templates/.claude/agents/scientist.md +1 -0
  24. package/templates/.claude/agents/security-reviewer.md +1 -0
  25. package/templates/.claude/agents/tdd-guide.md +1 -0
  26. package/templates/.claude/agents/vision.md +1 -0
  27. package/templates/.claude/instructions/agent-patterns/agent-teams-usage.md +376 -0
  28. package/templates/.claude/instructions/sourcing/reliable-search.md +49 -2
  29. package/templates/.claude/scripts/agent-teams/check-availability.sh +238 -0
  30. package/templates/.claude/scripts/agent-teams/setup-tmux.sh +125 -0
  31. package/templates/.claude/skills/agent-teams-setup/SKILL.md +460 -0
  32. package/templates/.claude/skills/brainstorm/SKILL.md +1 -0
  33. package/templates/.claude/skills/bug-fix/SKILL.md +1 -0
  34. package/templates/.claude/skills/crawler/SKILL.md +2 -0
  35. package/templates/.claude/skills/docs-creator/SKILL.md +1 -0
  36. package/templates/.claude/skills/docs-fetch/SKILL.md +6 -4
  37. package/templates/.claude/skills/docs-refactor/SKILL.md +1 -0
  38. package/templates/.claude/skills/elon-musk/SKILL.md +1 -0
  39. package/templates/.claude/skills/execute/SKILL.md +1 -0
  40. package/templates/.claude/skills/feedback/SKILL.md +1 -0
  41. package/templates/.claude/skills/figma-to-code/SKILL.md +1 -0
  42. package/templates/.claude/skills/genius-thinking/SKILL.md +1 -0
  43. package/templates/.claude/skills/global-uiux-design/SKILL.md +1 -0
  44. package/templates/.claude/skills/korea-uiux-design/SKILL.md +1 -0
  45. package/templates/.claude/skills/nextjs-react-best-practices/SKILL.md +1 -0
  46. package/templates/.claude/skills/plan/SKILL.md +1 -0
  47. package/templates/.claude/skills/prd/SKILL.md +1 -0
  48. package/templates/.claude/skills/project-optimizer/AGENTS.md +275 -0
  49. package/templates/.claude/skills/project-optimizer/SKILL.md +375 -0
  50. package/templates/.claude/skills/project-optimizer/rules/arch-config-centralize.md +66 -0
  51. package/templates/.claude/skills/project-optimizer/rules/arch-hot-path.md +35 -0
  52. package/templates/.claude/skills/project-optimizer/rules/arch-interface-segregation.md +51 -0
  53. package/templates/.claude/skills/project-optimizer/rules/arch-module-boundary.md +42 -0
  54. package/templates/.claude/skills/project-optimizer/rules/build-cache.md +57 -0
  55. package/templates/.claude/skills/project-optimizer/rules/build-code-split.md +56 -0
  56. package/templates/.claude/skills/project-optimizer/rules/build-incremental.md +65 -0
  57. package/templates/.claude/skills/project-optimizer/rules/build-minify.md +61 -0
  58. package/templates/.claude/skills/project-optimizer/rules/build-tree-shake.md +60 -0
  59. package/templates/.claude/skills/project-optimizer/rules/code-complexity.md +65 -0
  60. package/templates/.claude/skills/project-optimizer/rules/code-dead-elimination.md +32 -0
  61. package/templates/.claude/skills/project-optimizer/rules/code-duplication.md +54 -0
  62. package/templates/.claude/skills/project-optimizer/rules/code-error-handling.md +75 -0
  63. package/templates/.claude/skills/project-optimizer/rules/code-naming.md +52 -0
  64. package/templates/.claude/skills/project-optimizer/rules/concurrency-defer-await.md +54 -0
  65. package/templates/.claude/skills/project-optimizer/rules/concurrency-parallel.md +90 -0
  66. package/templates/.claude/skills/project-optimizer/rules/concurrency-pipeline.md +68 -0
  67. package/templates/.claude/skills/project-optimizer/rules/concurrency-pool.md +68 -0
  68. package/templates/.claude/skills/project-optimizer/rules/deps-lightweight-alt.md +37 -0
  69. package/templates/.claude/skills/project-optimizer/rules/deps-peer-align.md +44 -0
  70. package/templates/.claude/skills/project-optimizer/rules/deps-security-audit.md +45 -0
  71. package/templates/.claude/skills/project-optimizer/rules/deps-unused-removal.md +25 -0
  72. package/templates/.claude/skills/project-optimizer/rules/deps-version-pin.md +40 -0
  73. package/templates/.claude/skills/project-optimizer/rules/dx-ci-speed.md +47 -0
  74. package/templates/.claude/skills/project-optimizer/rules/dx-dev-server.md +35 -0
  75. package/templates/.claude/skills/project-optimizer/rules/dx-lint-config.md +36 -0
  76. package/templates/.claude/skills/project-optimizer/rules/dx-test-coverage.md +34 -0
  77. package/templates/.claude/skills/project-optimizer/rules/dx-type-safety.md +49 -0
  78. package/templates/.claude/skills/project-optimizer/rules/io-batch-queries.md +67 -0
  79. package/templates/.claude/skills/project-optimizer/rules/io-cache-layer.md +67 -0
  80. package/templates/.claude/skills/project-optimizer/rules/io-connection-reuse.md +67 -0
  81. package/templates/.claude/skills/project-optimizer/rules/io-serialize-minimal.md +61 -0
  82. package/templates/.claude/skills/project-optimizer/rules/io-stream.md +75 -0
  83. package/templates/.claude/skills/project-optimizer/rules/memory-bounded-cache.md +65 -0
  84. package/templates/.claude/skills/project-optimizer/rules/memory-large-data.md +64 -0
  85. package/templates/.claude/skills/project-optimizer/rules/memory-lazy-init.md +78 -0
  86. package/templates/.claude/skills/project-optimizer/rules/memory-leak-prevention.md +79 -0
  87. package/templates/.claude/skills/project-optimizer/rules/memory-pool-reuse.md +70 -0
  88. package/templates/.claude/skills/ralph/SKILL.md +1 -0
  89. package/templates/.claude/skills/refactor/SKILL.md +1 -0
  90. package/templates/.claude/skills/research/SKILL.md +1 -0
  91. package/templates/.claude/skills/sql-optimizer/SKILL.md +438 -0
  92. package/templates/.claude/skills/sql-optimizer/orm-patterns.md +218 -0
  93. package/templates/.claude/skills/startup-validator/SKILL.md +1 -0
  94. package/templates/.claude/skills/tanstack-start-react-best-practices/AGENTS.md +53 -14
  95. package/templates/.claude/skills/tanstack-start-react-best-practices/SKILL.md +94 -27
  96. package/templates/.claude/skills/tanstack-start-react-best-practices/rules/bundle-defer-third-party.md +42 -19
  97. package/templates/.claude/skills/tanstack-start-react-best-practices/rules/client-optimistic-updates.md +109 -0
  98. package/templates/.claude/skills/tanstack-start-react-best-practices/rules/client-suspense-query.md +74 -0
  99. package/templates/.claude/skills/tanstack-start-react-best-practices/rules/client-use-hook.md +81 -0
  100. package/templates/.claude/skills/tanstack-start-react-best-practices/rules/rerender-react-compiler.md +81 -0
  101. package/templates/.claude/skills/tanstack-start-react-best-practices/rules/routing-beforeload-auth.md +121 -0
  102. package/templates/.claude/skills/tanstack-start-react-best-practices/rules/routing-file-conventions.md +104 -0
  103. package/templates/.claude/skills/tanstack-start-react-best-practices/rules/routing-link-navigation.md +119 -0
  104. package/templates/.claude/skills/tanstack-start-react-best-practices/rules/routing-nested-layouts.md +155 -0
  105. package/templates/.claude/skills/tanstack-start-react-best-practices/rules/routing-path-params.md +89 -0
  106. package/templates/.claude/skills/tanstack-start-react-best-practices/rules/routing-pending-component.md +110 -0
  107. package/templates/.claude/skills/tanstack-start-react-best-practices/rules/routing-preload-strategy.md +91 -0
  108. package/templates/.claude/skills/tanstack-start-react-best-practices/rules/routing-router-context.md +120 -0
  109. package/templates/.claude/skills/tanstack-start-react-best-practices/rules/routing-search-params.md +114 -0
  110. package/templates/.claude/skills/tanstack-start-react-best-practices/rules/server-deferred-data.md +1 -1
  111. package/templates/.claude/skills/tanstack-start-react-best-practices/rules/server-error-boundaries.md +79 -0
  112. package/templates/.claude/skills/tanstack-start-react-best-practices/rules/server-middleware.md +85 -0
  113. package/templates/.claude/skills/tanstack-start-react-best-practices/rules/server-serialization.md +56 -21
  114. package/templates/.claude/skills/tanstack-start-react-best-practices/rules/server-streaming.md +84 -0
  115. package/templates/.claude/skills/tanstack-start-react-best-practices/rules/server-validator.md +71 -0
  116. package/templates/.claude/skills/tauri-react-best-practices/AGENTS.md +527 -0
  117. package/templates/.claude/skills/tauri-react-best-practices/SKILL.md +571 -0
  118. package/templates/.claude/skills/tauri-react-best-practices/rules/bundle-barrel-imports.md +140 -0
  119. package/templates/.claude/skills/tauri-react-best-practices/rules/bundle-cargo-profile.md +96 -0
  120. package/templates/.claude/skills/tauri-react-best-practices/rules/bundle-frontend-treeshake.md +242 -0
  121. package/templates/.claude/skills/tauri-react-best-practices/rules/bundle-lazy-components.md +255 -0
  122. package/templates/.claude/skills/tauri-react-best-practices/rules/bundle-remove-unused-commands.md +160 -0
  123. package/templates/.claude/skills/tauri-react-best-practices/rules/deploy-ci-pipeline.md +269 -0
  124. package/templates/.claude/skills/tauri-react-best-practices/rules/deploy-signing.md +207 -0
  125. package/templates/.claude/skills/tauri-react-best-practices/rules/deploy-updater.md +226 -0
  126. package/templates/.claude/skills/tauri-react-best-practices/rules/ipc-async-commands.md +172 -0
  127. package/templates/.claude/skills/tauri-react-best-practices/rules/ipc-batch-commands.md +133 -0
  128. package/templates/.claude/skills/tauri-react-best-practices/rules/ipc-binary-response.md +198 -0
  129. package/templates/.claude/skills/tauri-react-best-practices/rules/ipc-channel-streaming.md +186 -0
  130. package/templates/.claude/skills/tauri-react-best-practices/rules/ipc-error-handling.md +250 -0
  131. package/templates/.claude/skills/tauri-react-best-practices/rules/ipc-type-safe.md +227 -0
  132. package/templates/.claude/skills/tauri-react-best-practices/rules/perf-derived-state.md +231 -0
  133. package/templates/.claude/skills/tauri-react-best-practices/rules/perf-functional-setstate.md +191 -0
  134. package/templates/.claude/skills/tauri-react-best-practices/rules/perf-index-maps.md +276 -0
  135. package/templates/.claude/skills/tauri-react-best-practices/rules/perf-lazy-state-init.md +196 -0
  136. package/templates/.claude/skills/tauri-react-best-practices/rules/plugin-lifecycle.md +265 -0
  137. package/templates/.claude/skills/tauri-react-best-practices/rules/plugin-mobile-compat.md +199 -0
  138. package/templates/.claude/skills/tauri-react-best-practices/rules/plugin-permission-scope.md +193 -0
  139. package/templates/.claude/skills/tauri-react-best-practices/rules/react-error-boundary.md +239 -0
  140. package/templates/.claude/skills/tauri-react-best-practices/rules/react-event-listener.md +151 -0
  141. package/templates/.claude/skills/tauri-react-best-practices/rules/react-file-src.md +155 -0
  142. package/templates/.claude/skills/tauri-react-best-practices/rules/react-invoke-hook.md +139 -0
  143. package/templates/.claude/skills/tauri-react-best-practices/rules/react-optimistic-update.md +211 -0
  144. package/templates/.claude/skills/tauri-react-best-practices/rules/security-capability-split.md +205 -0
  145. package/templates/.claude/skills/tauri-react-best-practices/rules/security-csp.md +207 -0
  146. package/templates/.claude/skills/tauri-react-best-practices/rules/security-least-privilege.md +106 -0
  147. package/templates/.claude/skills/tauri-react-best-practices/rules/security-no-wildcard.md +253 -0
  148. package/templates/.claude/skills/tauri-react-best-practices/rules/security-scope-paths.md +160 -0
  149. package/templates/.claude/skills/tauri-react-best-practices/rules/state-async-mutex.md +270 -0
  150. package/templates/.claude/skills/tauri-react-best-practices/rules/state-mutex-pattern.md +265 -0
  151. package/templates/.claude/skills/tauri-react-best-practices/rules/state-react-sync.md +375 -0
  152. package/templates/.claude/skills/tauri-react-best-practices/rules/state-single-container.md +275 -0
  153. package/templates/tanstack-start/docs/architecture.md +238 -167
  154. package/templates/tanstack-start/docs/library/tanstack-router/error-handling.md +777 -38
  155. package/templates/tanstack-start/docs/library/tanstack-router/hooks.md +549 -37
  156. package/templates/tanstack-start/docs/library/tanstack-router/index.md +895 -111
  157. package/templates/tanstack-start/docs/library/tanstack-router/navigation.md +641 -43
  158. package/templates/tanstack-start/docs/library/tanstack-router/route-context.md +889 -38
  159. package/templates/tanstack-start/docs/library/tanstack-router/search-params.md +891 -29
  160. package/templates/tanstack-start/docs/library/tanstack-start/auth-patterns.md +972 -36
  161. package/templates/tanstack-start/docs/library/tanstack-start/index.md +1525 -881
  162. package/templates/tanstack-start/docs/library/tanstack-start/middleware.md +1099 -20
  163. package/templates/tanstack-start/docs/library/tanstack-start/routing.md +796 -30
  164. package/templates/tanstack-start/docs/library/tanstack-start/server-functions.md +953 -35
  165. package/templates/tanstack-start/docs/library/tanstack-start/setup.md +371 -15
  166. package/templates/tauri/CLAUDE.md +189 -0
  167. package/templates/tauri/docs/guides/distribution.md +261 -0
  168. package/templates/tauri/docs/guides/getting-started.md +302 -0
  169. package/templates/tauri/docs/guides/mobile.md +288 -0
  170. package/templates/tauri/docs/library/tauri/index.md +510 -0
@@ -0,0 +1,239 @@
1
+ # invoke 실패에 대한 ErrorBoundary 처리
2
+
3
+ ## 왜 중요한가
4
+
5
+ Tauri Command 호출은 Rust panic, 권한 거부, 타입 불일치 등 다양한 이유로 실패할 수 있습니다. ErrorBoundary 없이는 전체 앱이 크래시되거나 사용자에게 불친절한 에러 메시지가 노출됩니다.
6
+
7
+ ## ❌ 잘못된 패턴
8
+
9
+ ```tsx
10
+ import { invoke } from '@tauri-apps/api/core'
11
+ import { useEffect, useState } from 'react'
12
+
13
+ function DataViewer() {
14
+ const [data, setData] = useState(null)
15
+
16
+ useEffect(() => {
17
+ // ❌ 에러 처리 없음, 실패 시 앱 크래시
18
+ invoke('fetch_data').then(setData)
19
+ }, [])
20
+
21
+ // data가 null이면 렌더링 에러 발생 가능
22
+ return <div>{data.items.map(...)}</div>
23
+ }
24
+ ```
25
+
26
+ **문제점:**
27
+ - try/catch 없는 invoke 호출
28
+ - 에러 상태 관리 없음
29
+ - 사용자에게 피드백 없음
30
+
31
+ ## ✅ 올바른 패턴
32
+
33
+ ```tsx
34
+ import { invoke } from '@tauri-apps/api/core'
35
+ import { useEffect, useState } from 'react'
36
+ import { ErrorBoundary } from 'react-error-boundary'
37
+
38
+ function DataViewer() {
39
+ const [data, setData] = useState<DataType | null>(null)
40
+ const [error, setError] = useState<Error | null>(null)
41
+ const [loading, setLoading] = useState(true)
42
+
43
+ useEffect(() => {
44
+ invoke<DataType>('fetch_data')
45
+ .then(setData)
46
+ .catch(err => setError(err))
47
+ .finally(() => setLoading(false))
48
+ }, [])
49
+
50
+ if (loading) return <div>Loading...</div>
51
+ if (error) throw error // ErrorBoundary로 전파
52
+ if (!data) return <div>No data</div>
53
+
54
+ return (
55
+ <div>
56
+ {data.items.map(item => (
57
+ <div key={item.id}>{item.name}</div>
58
+ ))}
59
+ </div>
60
+ )
61
+ }
62
+
63
+ function App() {
64
+ return (
65
+ <ErrorBoundary
66
+ fallback={<ErrorFallback />}
67
+ onError={(error, errorInfo) => {
68
+ console.error('Caught by ErrorBoundary:', error, errorInfo)
69
+ }}
70
+ >
71
+ <DataViewer />
72
+ </ErrorBoundary>
73
+ )
74
+ }
75
+
76
+ function ErrorFallback({ error, resetErrorBoundary }: {
77
+ error: Error
78
+ resetErrorBoundary: () => void
79
+ }) {
80
+ return (
81
+ <div role="alert">
82
+ <h2>Something went wrong</h2>
83
+ <pre>{error.message}</pre>
84
+ <button onClick={resetErrorBoundary}>Try again</button>
85
+ </div>
86
+ )
87
+ }
88
+ ```
89
+
90
+ **Tauri 특화 에러 타입 처리:**
91
+
92
+ ```tsx
93
+ import { invoke } from '@tauri-apps/api/core'
94
+
95
+ type TauriError = {
96
+ message: string
97
+ kind?: 'PermissionDenied' | 'NotFound' | 'InvalidData' | 'Unknown'
98
+ }
99
+
100
+ function parseTauriError(error: unknown): TauriError {
101
+ if (typeof error === 'string') {
102
+ // Rust에서 String 에러
103
+ return { message: error, kind: 'Unknown' }
104
+ }
105
+
106
+ if (error instanceof Error) {
107
+ // JS Error 객체
108
+ const message = error.message
109
+
110
+ if (message.includes('permission denied')) {
111
+ return { message, kind: 'PermissionDenied' }
112
+ }
113
+ if (message.includes('not found')) {
114
+ return { message, kind: 'NotFound' }
115
+ }
116
+ if (message.includes('invalid')) {
117
+ return { message, kind: 'InvalidData' }
118
+ }
119
+
120
+ return { message, kind: 'Unknown' }
121
+ }
122
+
123
+ return { message: 'Unknown error', kind: 'Unknown' }
124
+ }
125
+
126
+ function DataViewer() {
127
+ const [error, setError] = useState<TauriError | null>(null)
128
+
129
+ useEffect(() => {
130
+ invoke('fetch_data')
131
+ .then(setData)
132
+ .catch(err => setError(parseTauriError(err)))
133
+ }, [])
134
+
135
+ if (error) {
136
+ if (error.kind === 'PermissionDenied') {
137
+ return <div>권한이 필요합니다. 설정에서 권한을 허용해주세요.</div>
138
+ }
139
+ if (error.kind === 'NotFound') {
140
+ return <div>데이터를 찾을 수 없습니다.</div>
141
+ }
142
+ return <div>오류: {error.message}</div>
143
+ }
144
+
145
+ return <div>{/* ... */}</div>
146
+ }
147
+ ```
148
+
149
+ **재시도 로직:**
150
+
151
+ ```tsx
152
+ import { invoke } from '@tauri-apps/api/core'
153
+ import { useState, useCallback } from 'react'
154
+
155
+ function useInvokeWithRetry<T>(
156
+ command: string,
157
+ args?: Record<string, unknown>,
158
+ maxRetries = 3
159
+ ) {
160
+ const [data, setData] = useState<T | null>(null)
161
+ const [error, setError] = useState<Error | null>(null)
162
+ const [loading, setLoading] = useState(false)
163
+ const [retries, setRetries] = useState(0)
164
+
165
+ const execute = useCallback(async () => {
166
+ setLoading(true)
167
+ setError(null)
168
+
169
+ for (let i = 0; i <= maxRetries; i++) {
170
+ try {
171
+ const result = await invoke<T>(command, args)
172
+ setData(result)
173
+ setRetries(i)
174
+ return
175
+ } catch (err) {
176
+ if (i === maxRetries) {
177
+ setError(err as Error)
178
+ setRetries(i)
179
+ } else {
180
+ await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)))
181
+ }
182
+ }
183
+ }
184
+
185
+ setLoading(false)
186
+ }, [command, args, maxRetries])
187
+
188
+ return { data, error, loading, retries, execute }
189
+ }
190
+
191
+ // 사용 예시
192
+ function DataViewer() {
193
+ const { data, error, loading, retries, execute } = useInvokeWithRetry<Data>(
194
+ 'fetch_data'
195
+ )
196
+
197
+ useEffect(() => {
198
+ execute()
199
+ }, [execute])
200
+
201
+ if (loading) return <div>Loading... (Attempt {retries + 1})</div>
202
+ if (error) return <div>Failed after {retries} retries: {error.message}</div>
203
+ return <div>{data && JSON.stringify(data)}</div>
204
+ }
205
+ ```
206
+
207
+ ## 추가 컨텍스트
208
+
209
+ **ErrorBoundary 라이브러리:**
210
+ - [react-error-boundary](https://github.com/bvaughn/react-error-boundary): 가장 인기
211
+ - React 19+ 내장 ErrorBoundary (계획 중)
212
+
213
+ **Rust 에러를 사용자 친화적으로 변환:**
214
+
215
+ ```rust
216
+ // Rust 측에서 에러 메시지 커스터마이징
217
+ #[tauri::command]
218
+ fn fetch_data() -> Result<Data, String> {
219
+ match load_data() {
220
+ Ok(data) => Ok(data),
221
+ Err(_) => Err("데이터를 불러올 수 없습니다. 나중에 다시 시도해주세요.".to_string())
222
+ }
223
+ }
224
+ ```
225
+
226
+ **전역 에러 처리:**
227
+ ```tsx
228
+ function App() {
229
+ return (
230
+ <ErrorBoundary fallback={<GlobalError />}>
231
+ <Router />
232
+ </ErrorBoundary>
233
+ )
234
+ }
235
+ ```
236
+
237
+ **참고:** [Error Handling in Tauri](https://beta.tauri.app/develop/calling-rust/#error-handling)
238
+
239
+ 영향도: HIGH - 사용자 경험, 앱 안정성, 에러 복구 가능성
@@ -0,0 +1,151 @@
1
+ # listen/unlisten 라이프사이클 관리
2
+
3
+ ## 왜 중요한가
4
+
5
+ Tauri의 이벤트 리스너는 수동으로 등록 해제하지 않으면 메모리에 남아 누수를 일으킵니다. 컴포넌트가 여러 번 마운트/언마운트되는 경우 리스너가 중복 등록되어 동일 이벤트에 여러 핸들러가 실행될 수 있습니다.
6
+
7
+ ## ❌ 잘못된 패턴
8
+
9
+ ```tsx
10
+ import { listen } from '@tauri-apps/api/event'
11
+ import { useEffect, useState } from 'react'
12
+
13
+ function NotificationPanel() {
14
+ const [messages, setMessages] = useState<string[]>([])
15
+
16
+ useEffect(() => {
17
+ // ❌ unlisten 호출 없음 (메모리 누수)
18
+ listen<string>('notification', event => {
19
+ setMessages(prev => [...prev, event.payload])
20
+ })
21
+ }, [])
22
+
23
+ return (
24
+ <div>
25
+ {messages.map((msg, i) => <div key={i}>{msg}</div>)}
26
+ </div>
27
+ )
28
+ }
29
+ ```
30
+
31
+ **문제점:**
32
+ - cleanup에서 unlisten 호출 안 함 (리스너 계속 활성 상태)
33
+ - 컴포넌트 재마운트 시 리스너 중복 등록
34
+ - 언마운트된 컴포넌트에서 setState 호출 위험
35
+
36
+ ## ✅ 올바른 패턴
37
+
38
+ ```tsx
39
+ import { listen, UnlistenFn } from '@tauri-apps/api/event'
40
+ import { useEffect, useState } from 'react'
41
+
42
+ function NotificationPanel() {
43
+ const [messages, setMessages] = useState<string[]>([])
44
+
45
+ useEffect(() => {
46
+ let unlisten: UnlistenFn | undefined
47
+
48
+ listen<string>('notification', event => {
49
+ setMessages(prev => [...prev, event.payload])
50
+ }).then(unlistenFn => {
51
+ unlisten = unlistenFn
52
+ })
53
+
54
+ return () => {
55
+ unlisten?.()
56
+ }
57
+ }, [])
58
+
59
+ return (
60
+ <div>
61
+ {messages.map((msg, i) => <div key={i}>{msg}</div>)}
62
+ </div>
63
+ )
64
+ }
65
+ ```
66
+
67
+ **once() 패턴 (일회성 이벤트):**
68
+
69
+ ```tsx
70
+ import { once } from '@tauri-apps/api/event'
71
+
72
+ function WelcomeScreen() {
73
+ const [ready, setReady] = useState(false)
74
+
75
+ useEffect(() => {
76
+ let unlisten: UnlistenFn | undefined
77
+
78
+ once<void>('app-ready', () => {
79
+ setReady(true)
80
+ }).then(unlistenFn => {
81
+ unlisten = unlistenFn
82
+ })
83
+
84
+ return () => unlisten?.()
85
+ }, [])
86
+
87
+ return ready ? <MainApp /> : <SplashScreen />
88
+ }
89
+ ```
90
+
91
+ **커스텀 hook 패턴:**
92
+
93
+ ```tsx
94
+ import { listen, Event, UnlistenFn } from '@tauri-apps/api/event'
95
+ import { useEffect, useState } from 'react'
96
+
97
+ function useEvent<T>(
98
+ eventName: string,
99
+ handler: (event: Event<T>) => void
100
+ ) {
101
+ useEffect(() => {
102
+ let unlisten: UnlistenFn | undefined
103
+
104
+ listen<T>(eventName, handler).then(unlistenFn => {
105
+ unlisten = unlistenFn
106
+ })
107
+
108
+ return () => unlisten?.()
109
+ }, [eventName, handler])
110
+ }
111
+
112
+ // 사용 예시
113
+ function StatusBar() {
114
+ const [status, setStatus] = useState('idle')
115
+
116
+ useEvent<string>('status-update', event => {
117
+ setStatus(event.payload)
118
+ })
119
+
120
+ return <div>Status: {status}</div>
121
+ }
122
+ ```
123
+
124
+ ## 추가 컨텍스트
125
+
126
+ **cleanup 타이밍:**
127
+ - useEffect return 함수는 언마운트 시 또는 의존성 변경 시 실행
128
+ - 리스너는 항상 cleanup에서 제거해야 메모리 누수 방지
129
+
130
+ **글로벌 이벤트 vs 로컬 이벤트:**
131
+ - `listen()`: 모든 윈도우에서 수신
132
+ - `emit()`: 현재 윈도우로 전송
133
+ - `once()`: 한 번만 실행 후 자동 제거
134
+
135
+ **타입 안정성:**
136
+ ```tsx
137
+ type NotificationPayload = {
138
+ title: string
139
+ body: string
140
+ level: 'info' | 'warning' | 'error'
141
+ }
142
+
143
+ listen<NotificationPayload>('notification', event => {
144
+ // event.payload는 타입 안전하게 추론됨
145
+ console.log(event.payload.title)
146
+ })
147
+ ```
148
+
149
+ **참고:** [Tauri Events Guide](https://beta.tauri.app/develop/calling-rust/#events)
150
+
151
+ 영향도: HIGH - 메모리 누수, 중복 핸들러 실행 방지
@@ -0,0 +1,155 @@
1
+ # convertFileSrc()로 로컬 파일 렌더링
2
+
3
+ ## 왜 중요한가
4
+
5
+ Tauri 앱에서 로컬 파일을 브라우저에서 렌더링하려면 `asset:` 프로토콜 URL로 변환해야 합니다. 직접 파일 경로를 사용하면 CORS 에러가 발생하고, base64 변환은 메모리 낭비와 성능 저하를 일으킵니다.
6
+
7
+ ## ❌ 잘못된 패턴
8
+
9
+ ```tsx
10
+ import { invoke } from '@tauri-apps/api/core'
11
+ import { useState, useEffect } from 'react'
12
+
13
+ function ImageViewer({ filePath }: { filePath: string }) {
14
+ const [imageData, setImageData] = useState('')
15
+
16
+ useEffect(() => {
17
+ // ❌ 파일을 base64로 읽어서 메모리에 전체 로드
18
+ invoke<string>('read_file_as_base64', { path: filePath })
19
+ .then(base64 => setImageData(`data:image/png;base64,${base64}`))
20
+ }, [filePath])
21
+
22
+ return <img src={imageData} alt="Local file" />
23
+ }
24
+ ```
25
+
26
+ **문제점:**
27
+ - 파일 전체를 메모리에 로드 (큰 파일 시 느림)
28
+ - Rust ↔ JS 직렬화 오버헤드
29
+ - 비디오/오디오 스트리밍 불가능
30
+
31
+ ## ✅ 올바른 패턴
32
+
33
+ ```tsx
34
+ import { convertFileSrc } from '@tauri-apps/api/core'
35
+
36
+ function ImageViewer({ filePath }: { filePath: string }) {
37
+ // ✅ 파일 경로를 asset: 프로토콜 URL로 변환
38
+ const assetUrl = convertFileSrc(filePath)
39
+
40
+ return <img src={assetUrl} alt="Local file" />
41
+ }
42
+ ```
43
+
44
+ **비디오/오디오 예시:**
45
+
46
+ ```tsx
47
+ import { convertFileSrc } from '@tauri-apps/api/core'
48
+
49
+ function VideoPlayer({ videoPath }: { videoPath: string }) {
50
+ const videoUrl = convertFileSrc(videoPath)
51
+
52
+ return (
53
+ <video controls>
54
+ <source src={videoUrl} type="video/mp4" />
55
+ Your browser does not support video playback.
56
+ </video>
57
+ )
58
+ }
59
+
60
+ function AudioPlayer({ audioPath }: { audioPath: string }) {
61
+ const audioUrl = convertFileSrc(audioPath)
62
+
63
+ return (
64
+ <audio controls>
65
+ <source src={audioUrl} type="audio/mpeg" />
66
+ </audio>
67
+ )
68
+ }
69
+ ```
70
+
71
+ **PDF 뷰어 예시:**
72
+
73
+ ```tsx
74
+ import { convertFileSrc } from '@tauri-apps/api/core'
75
+
76
+ function PDFViewer({ pdfPath }: { pdfPath: string }) {
77
+ const pdfUrl = convertFileSrc(pdfPath)
78
+
79
+ return (
80
+ <iframe
81
+ src={pdfUrl}
82
+ width="100%"
83
+ height="600px"
84
+ title="PDF Viewer"
85
+ />
86
+ )
87
+ }
88
+ ```
89
+
90
+ **동적 파일 목록:**
91
+
92
+ ```tsx
93
+ import { convertFileSrc } from '@tauri-apps/api/core'
94
+ import { readDir } from '@tauri-apps/plugin-fs'
95
+ import { useEffect, useState } from 'react'
96
+
97
+ function ImageGallery({ dirPath }: { dirPath: string }) {
98
+ const [images, setImages] = useState<string[]>([])
99
+
100
+ useEffect(() => {
101
+ readDir(dirPath).then(entries => {
102
+ const imageUrls = entries
103
+ .filter(e => /\.(png|jpg|jpeg|gif)$/i.test(e.name))
104
+ .map(e => convertFileSrc(e.path))
105
+ setImages(imageUrls)
106
+ })
107
+ }, [dirPath])
108
+
109
+ return (
110
+ <div className="gallery">
111
+ {images.map((url, i) => (
112
+ <img key={i} src={url} alt={`Image ${i}`} />
113
+ ))}
114
+ </div>
115
+ )
116
+ }
117
+ ```
118
+
119
+ ## 추가 컨텍스트
120
+
121
+ **asset: 프로토콜 작동 방식:**
122
+ - `convertFileSrc('/path/to/file.png')` → `asset://localhost/path/to/file.png`
123
+ - Tauri 런타임이 프로토콜 요청을 가로채서 파일 스트림 제공
124
+ - 브라우저는 일반 HTTP 리소스처럼 처리 (캐싱, 부분 로드 지원)
125
+
126
+ **권한 설정 (tauri.conf.json):**
127
+
128
+ ```json
129
+ {
130
+ "app": {
131
+ "security": {
132
+ "assetProtocol": {
133
+ "enable": true,
134
+ "scope": [
135
+ "$APPDATA/**",
136
+ "$RESOURCE/**"
137
+ ]
138
+ }
139
+ }
140
+ }
141
+ }
142
+ ```
143
+
144
+ **파일 경로 타입:**
145
+ - 절대 경로: `/Users/name/file.png`, `C:\Users\name\file.png`
146
+ - 앱 리소스 경로: `$RESOURCE/assets/image.png`
147
+ - AppData 경로: `$APPDATA/cache/thumb.png`
148
+
149
+ **브라우저 캐싱:**
150
+ - asset: URL은 브라우저 캐시 활용 (동일 파일 재요청 방지)
151
+ - base64 Data URL은 캐싱 불가
152
+
153
+ **참고:** [Asset Protocol Scope](https://beta.tauri.app/develop/calling-rust/#asset-protocol-scope)
154
+
155
+ 영향도: HIGH - 성능, 메모리 사용량, 스트리밍 지원
@@ -0,0 +1,139 @@
1
+ # useEffect + invoke 패턴으로 Tauri Command 호출
2
+
3
+ ## 왜 중요한가
4
+
5
+ Tauri의 `invoke()` 호출은 비동기 IPC이므로 React 컴포넌트 라이프사이클과 통합 시 cleanup이 필수입니다. 컴포넌트 언마운트 후 응답이 도착하면 "Can't perform state update on unmounted component" 경고와 메모리 누수가 발생합니다.
6
+
7
+ ## ❌ 잘못된 패턴
8
+
9
+ ```tsx
10
+ import { invoke } from '@tauri-apps/api/core'
11
+ import { useState } from 'react'
12
+
13
+ function UserProfile({ userId }: { userId: string }) {
14
+ const [user, setUser] = useState(null)
15
+
16
+ // ❌ cleanup 없음, 언마운트 후 setState 위험
17
+ invoke('get_user', { userId })
18
+ .then(setUser)
19
+ .catch(console.error)
20
+
21
+ return user ? <div>{user.name}</div> : <div>Loading...</div>
22
+ }
23
+ ```
24
+
25
+ **문제점:**
26
+ - useEffect 없이 invoke 호출 (매 렌더링마다 실행)
27
+ - cleanup 로직 없음 (언마운트 후 setState)
28
+ - 에러 처리 불충분
29
+
30
+ ## ✅ 올바른 패턴
31
+
32
+ ```tsx
33
+ import { invoke } from '@tauri-apps/api/core'
34
+ import { useEffect, useState } from 'react'
35
+
36
+ function UserProfile({ userId }: { userId: string }) {
37
+ const [user, setUser] = useState<User | null>(null)
38
+ const [error, setError] = useState<string | null>(null)
39
+ const [loading, setLoading] = useState(true)
40
+
41
+ useEffect(() => {
42
+ let cancelled = false
43
+
44
+ setLoading(true)
45
+ setError(null)
46
+
47
+ invoke<User>('get_user', { userId })
48
+ .then(data => {
49
+ if (!cancelled) setUser(data)
50
+ })
51
+ .catch(err => {
52
+ if (!cancelled) setError(err.message)
53
+ })
54
+ .finally(() => {
55
+ if (!cancelled) setLoading(false)
56
+ })
57
+
58
+ return () => {
59
+ cancelled = true
60
+ }
61
+ }, [userId])
62
+
63
+ if (loading) return <div>Loading...</div>
64
+ if (error) return <div>Error: {error}</div>
65
+ return user ? <div>{user.name}</div> : null
66
+ }
67
+ ```
68
+
69
+ **개선된 커스텀 hook 패턴:**
70
+
71
+ ```tsx
72
+ import { invoke } from '@tauri-apps/api/core'
73
+ import { useEffect, useState } from 'react'
74
+
75
+ function useInvoke<T>(
76
+ command: string,
77
+ args?: Record<string, unknown>
78
+ ) {
79
+ const [data, setData] = useState<T | null>(null)
80
+ const [error, setError] = useState<Error | null>(null)
81
+ const [loading, setLoading] = useState(true)
82
+
83
+ useEffect(() => {
84
+ let cancelled = false
85
+
86
+ setLoading(true)
87
+ setError(null)
88
+
89
+ invoke<T>(command, args)
90
+ .then(result => !cancelled && setData(result))
91
+ .catch(err => !cancelled && setError(err))
92
+ .finally(() => !cancelled && setLoading(false))
93
+
94
+ return () => {
95
+ cancelled = true
96
+ }
97
+ }, [command, JSON.stringify(args)])
98
+
99
+ return { data, error, loading }
100
+ }
101
+
102
+ // 사용 예시
103
+ function UserProfile({ userId }: { userId: string }) {
104
+ const { data: user, error, loading } = useInvoke<User>(
105
+ 'get_user',
106
+ { userId }
107
+ )
108
+
109
+ if (loading) return <div>Loading...</div>
110
+ if (error) return <div>Error: {error.message}</div>
111
+ return user ? <div>{user.name}</div> : null
112
+ }
113
+ ```
114
+
115
+ ## 추가 컨텍스트
116
+
117
+ **AbortController 패턴 (Tauri v2.1+):**
118
+
119
+ ```tsx
120
+ useEffect(() => {
121
+ const controller = new AbortController()
122
+
123
+ invoke('long_running_task', { signal: controller.signal })
124
+ .then(setResult)
125
+ .catch(err => {
126
+ if (err.name !== 'AbortError') setError(err)
127
+ })
128
+
129
+ return () => controller.abort()
130
+ }, [])
131
+ ```
132
+
133
+ **권장 의존성 관리:**
134
+ - 원시 값 (`userId`, `query`): 직접 의존성 배열에 추가
135
+ - 객체/배열 args: `JSON.stringify(args)` 또는 `useMemo`로 안정화
136
+
137
+ **참고:** TanStack Query와 함께 사용 시 `queryFn`에서 invoke 호출하면 자동 cleanup 제공.
138
+
139
+ 영향도: HIGH - 메모리 누수, 상태 버그, 콘솔 경고 방지