@mandujs/core 0.18.22 → 0.19.2

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 (91) hide show
  1. package/README.ko.md +0 -14
  2. package/package.json +4 -1
  3. package/src/brain/architecture/analyzer.ts +4 -4
  4. package/src/brain/doctor/analyzer.ts +18 -14
  5. package/src/bundler/build.test.ts +127 -0
  6. package/src/bundler/build.ts +291 -113
  7. package/src/bundler/css.ts +20 -5
  8. package/src/bundler/dev.ts +55 -2
  9. package/src/bundler/prerender.ts +195 -0
  10. package/src/change/snapshot.ts +4 -23
  11. package/src/change/types.ts +2 -3
  12. package/src/client/Form.tsx +105 -0
  13. package/src/client/__tests__/use-sse.test.ts +153 -0
  14. package/src/client/hooks.ts +105 -6
  15. package/src/client/index.ts +35 -6
  16. package/src/client/router.ts +670 -433
  17. package/src/client/rpc.ts +140 -0
  18. package/src/client/runtime.ts +24 -21
  19. package/src/client/use-fetch.ts +239 -0
  20. package/src/client/use-head.ts +197 -0
  21. package/src/client/use-sse.ts +378 -0
  22. package/src/components/Image.tsx +162 -0
  23. package/src/config/mandu.ts +5 -0
  24. package/src/config/validate.ts +34 -0
  25. package/src/content/index.ts +5 -1
  26. package/src/devtools/client/catchers/error-catcher.ts +17 -0
  27. package/src/devtools/client/catchers/network-proxy.ts +390 -367
  28. package/src/devtools/client/components/kitchen-root.tsx +479 -467
  29. package/src/devtools/client/components/panel/diff-viewer.tsx +219 -0
  30. package/src/devtools/client/components/panel/guard-panel.tsx +374 -244
  31. package/src/devtools/client/components/panel/index.ts +45 -32
  32. package/src/devtools/client/components/panel/panel-container.tsx +332 -312
  33. package/src/devtools/client/components/panel/preview-panel.tsx +188 -0
  34. package/src/devtools/client/state-manager.ts +535 -478
  35. package/src/devtools/design-tokens.ts +265 -264
  36. package/src/devtools/types.ts +345 -319
  37. package/src/filling/context.ts +65 -0
  38. package/src/filling/filling.ts +336 -14
  39. package/src/filling/index.ts +5 -1
  40. package/src/filling/session.ts +216 -0
  41. package/src/filling/ws.ts +78 -0
  42. package/src/generator/generate.ts +2 -2
  43. package/src/guard/auto-correct.ts +0 -29
  44. package/src/guard/check.ts +14 -31
  45. package/src/guard/presets/index.ts +296 -294
  46. package/src/guard/rules.ts +15 -19
  47. package/src/guard/validator.ts +834 -834
  48. package/src/index.ts +5 -1
  49. package/src/island/index.ts +373 -304
  50. package/src/kitchen/api/contract-api.ts +225 -0
  51. package/src/kitchen/api/diff-parser.ts +108 -0
  52. package/src/kitchen/api/file-api.ts +273 -0
  53. package/src/kitchen/api/guard-api.ts +83 -0
  54. package/src/kitchen/api/guard-decisions.ts +100 -0
  55. package/src/kitchen/api/routes-api.ts +50 -0
  56. package/src/kitchen/index.ts +21 -0
  57. package/src/kitchen/kitchen-handler.ts +256 -0
  58. package/src/kitchen/kitchen-ui.ts +1732 -0
  59. package/src/kitchen/stream/activity-sse.ts +145 -0
  60. package/src/kitchen/stream/file-tailer.ts +99 -0
  61. package/src/middleware/compress.ts +62 -0
  62. package/src/middleware/cors.ts +47 -0
  63. package/src/middleware/index.ts +10 -0
  64. package/src/middleware/jwt.ts +134 -0
  65. package/src/middleware/logger.ts +58 -0
  66. package/src/middleware/timeout.ts +55 -0
  67. package/src/paths.ts +0 -4
  68. package/src/plugins/hooks.ts +64 -0
  69. package/src/plugins/index.ts +3 -0
  70. package/src/plugins/types.ts +5 -0
  71. package/src/report/build.ts +0 -6
  72. package/src/resource/__tests__/backward-compat.test.ts +0 -1
  73. package/src/router/fs-patterns.ts +11 -1
  74. package/src/router/fs-routes.ts +78 -14
  75. package/src/router/fs-scanner.ts +2 -2
  76. package/src/router/fs-types.ts +2 -1
  77. package/src/runtime/adapter-bun.ts +62 -0
  78. package/src/runtime/adapter.ts +47 -0
  79. package/src/runtime/cache.ts +310 -0
  80. package/src/runtime/handler.ts +65 -0
  81. package/src/runtime/image-handler.ts +195 -0
  82. package/src/runtime/index.ts +12 -0
  83. package/src/runtime/middleware.ts +263 -0
  84. package/src/runtime/server.ts +686 -92
  85. package/src/runtime/ssr.ts +55 -29
  86. package/src/runtime/streaming-ssr.ts +106 -82
  87. package/src/spec/index.ts +0 -1
  88. package/src/spec/schema.ts +1 -0
  89. package/src/testing/index.ts +144 -0
  90. package/src/watcher/watcher.ts +27 -1
  91. package/src/spec/lock.ts +0 -56
@@ -1,319 +1,345 @@
1
- /**
2
- * Mandu Kitchen DevTools - Type Definitions
3
- * @version 1.0.3
4
- */
5
-
6
- // ============================================================================
7
- // Core Event Types
8
- // ============================================================================
9
-
10
- export interface KitchenEvent<T extends string = string, D = unknown> {
11
- type: T;
12
- timestamp: number;
13
- data: D;
14
- }
15
-
16
- // ============================================================================
17
- // Error Types
18
- // ============================================================================
19
-
20
- export type ErrorType = 'runtime' | 'unhandled' | 'react' | 'network' | 'hmr' | 'guard';
21
- export type DevToolsSeverity = 'critical' | 'error' | 'warning' | 'info';
22
-
23
- export interface NormalizedError {
24
- id: string;
25
- type: ErrorType;
26
- severity: DevToolsSeverity;
27
- message: string;
28
- stack?: string;
29
- source?: string;
30
- line?: number;
31
- column?: number;
32
- componentStack?: string;
33
- islandId?: string;
34
- timestamp: number;
35
- url: string;
36
- }
37
-
38
- // ============================================================================
39
- // Island Types
40
- // ============================================================================
41
-
42
- export type DevToolsHydrationStrategy = 'load' | 'idle' | 'visible' | 'media' | 'never';
43
- export type IslandStatus = 'ssr' | 'pending' | 'hydrating' | 'hydrated' | 'error';
44
-
45
- export interface IslandSnapshot {
46
- id: string;
47
- name: string;
48
- strategy: DevToolsHydrationStrategy;
49
- status: IslandStatus;
50
- ssrRenderTime?: number;
51
- hydrateStartTime?: number;
52
- hydrateEndTime?: number;
53
- bundleSize?: number;
54
- }
55
-
56
- // ============================================================================
57
- // Network Types
58
- // ============================================================================
59
-
60
- export interface NetworkRequest {
61
- id: string;
62
- method: string;
63
- url: string;
64
- safeHeaders: Record<string, string>;
65
- redactedHeaders: string[];
66
- body?: {
67
- available: boolean;
68
- size: number;
69
- content?: unknown;
70
- };
71
- status?: number;
72
- startTime: number;
73
- endTime?: number;
74
- isStreaming: boolean;
75
- chunkCount?: number;
76
- }
77
-
78
- export interface NetworkBodyPolicy {
79
- collectBody: boolean;
80
- optInPolicy?: {
81
- maxBytes: number;
82
- applyPIIFilter: boolean;
83
- applySecretFilter: boolean;
84
- allowedContentTypes: string[];
85
- };
86
- }
87
-
88
- // ============================================================================
89
- // Guard Types
90
- // ============================================================================
91
-
92
- export interface DevToolsGuardViolation {
93
- id: string;
94
- ruleId: string;
95
- ruleName: string;
96
- severity: 'error' | 'warning';
97
- message: string;
98
- source: {
99
- file: string;
100
- line?: number;
101
- column?: number;
102
- };
103
- target?: {
104
- file: string;
105
- line?: number;
106
- };
107
- suggestion?: string;
108
- timestamp: number;
109
- }
110
-
111
- // ============================================================================
112
- // AI Context Types
113
- // ============================================================================
114
-
115
- export interface CodeContextInfo {
116
- filePath: string;
117
- line: number;
118
- column?: number;
119
- sourcemapUrl?: string;
120
- snippet?: {
121
- content: string;
122
- lineRange: [number, number];
123
- source: 'dev-server' | 'sourcemap-inline' | 'unavailable';
124
- };
125
- }
126
-
127
- export interface AIContextPayload {
128
- error: NormalizedError;
129
- island?: IslandSnapshot;
130
- framework: { name: 'mandu'; version: string };
131
- devtools: { version: string };
132
- recentErrors?: Array<{
133
- id: string;
134
- message: string;
135
- timestamp: number;
136
- isCausedBy?: string;
137
- }>;
138
- userActions?: Array<{
139
- type: 'navigation' | 'interaction' | 'reload';
140
- targetHint?: string;
141
- timestamp: number;
142
- }>;
143
- codeContext?: CodeContextInfo;
144
- }
145
-
146
- // ============================================================================
147
- // Redaction Types
148
- // ============================================================================
149
-
150
- export interface RedactPattern {
151
- source: string;
152
- flags?: string;
153
- replacement?: string;
154
- label?: string;
155
- }
156
-
157
- // ============================================================================
158
- // Persistence Types
159
- // ============================================================================
160
-
161
- export interface PreserveLogConfig {
162
- enabled: boolean;
163
- maxPersistEvents: number;
164
- maxPersistBytes: number;
165
- priority: 'errors-first' | 'recent-first';
166
- incremental?: {
167
- enabled: boolean;
168
- idleSyncMs: number;
169
- };
170
- }
171
-
172
- // ============================================================================
173
- // Worker Types
174
- // ============================================================================
175
-
176
- export interface WorkerPolicy {
177
- timeout: number;
178
- onTimeout: 'fallback-main' | 'skip';
179
- onError: 'disable-worker' | 'retry-once';
180
- maxConsecutiveFailures: number;
181
- }
182
-
183
- export interface WorkerTask {
184
- id: string;
185
- type: 'redact' | 'truncate';
186
- data: {
187
- text: string;
188
- patterns?: RedactPattern[];
189
- maxBytes?: number;
190
- };
191
- }
192
-
193
- // ============================================================================
194
- // Config Types
195
- // ============================================================================
196
-
197
- export type Position = 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';
198
- export type Theme = 'light' | 'dark' | 'auto';
199
-
200
- export interface DevToolsConfig {
201
- enabled?: boolean;
202
- position?: Position;
203
- defaultOpen?: boolean;
204
- theme?: Theme;
205
-
206
- features?: {
207
- errorOverlay?: boolean;
208
- islandsInspector?: boolean;
209
- networkMonitor?: boolean;
210
- guardViewer?: boolean;
211
- };
212
-
213
- dataSafety?: {
214
- stringMode?: 'smart' | 'strip';
215
- collectUserActions?: boolean;
216
- collectCodeContext?: boolean;
217
- customRedactPatterns?: RedactPattern[];
218
- };
219
-
220
- network?: {
221
- collectBody?: boolean;
222
- bodyMaxBytes?: number;
223
- };
224
-
225
- persistence?: PreserveLogConfig;
226
- plugins?: KitchenPanelPlugin[];
227
- }
228
-
229
- // ============================================================================
230
- // Plugin Types
231
- // ============================================================================
232
-
233
- export interface KitchenAPI {
234
- subscribe(type: string, callback: (event: KitchenEvent) => void): () => void;
235
- getErrors(): NormalizedError[];
236
- getIslands(): IslandSnapshot[];
237
- getNetworkRequests(): NetworkRequest[];
238
- clearErrors(): void;
239
- getConfig(): DevToolsConfig;
240
- copyToClipboard(text: string): Promise<void>;
241
- openInEditor(file: string, line?: number): void;
242
- }
243
-
244
- export interface KitchenPanelPlugin {
245
- id: string;
246
- name: string;
247
- icon: string;
248
- order: number;
249
-
250
- init(api: KitchenAPI): void;
251
- destroy?(): void;
252
- render(container: HTMLElement): void;
253
- onEvent?(event: KitchenEvent): void;
254
- }
255
-
256
- // ============================================================================
257
- // Meta Log Types
258
- // ============================================================================
259
-
260
- export type MetaLogType =
261
- | 'init'
262
- | 'hook_fail'
263
- | 'render_fail'
264
- | 'persist_fail'
265
- | 'worker_timeout'
266
- | 'worker_error'
267
- | 'worker_disabled'
268
- | 'recovered';
269
-
270
- export interface KitchenMetaLog {
271
- timestamp: number;
272
- type: MetaLogType;
273
- error?: string;
274
- context: {
275
- eventCount: number;
276
- activeTab: string;
277
- memoryInfo?: { usedJSHeapSize?: number };
278
- };
279
- }
280
-
281
- // ============================================================================
282
- // Mandu Character Types
283
- // ============================================================================
284
-
285
- export type ManduState = 'normal' | 'warning' | 'error' | 'loading' | 'hmr';
286
-
287
- export interface ManduCharacterData {
288
- state: ManduState;
289
- emoji: string;
290
- message: string;
291
- }
292
-
293
- export const MANDU_CHARACTERS: Record<ManduState, ManduCharacterData> = {
294
- normal: {
295
- state: 'normal',
296
- emoji: '(◕‿◕)',
297
- message: '모든 만두가 잘 익고 있어요~',
298
- },
299
- warning: {
300
- state: 'warning',
301
- emoji: '(◕_◕)',
302
- message: '뭔가 이상해요...',
303
- },
304
- error: {
305
- state: 'error',
306
- emoji: '(ノಠ益ಠ)ノ彡┻━┻',
307
- message: '만두가 타버렸어요!',
308
- },
309
- loading: {
310
- state: 'loading',
311
- emoji: '(◕‿◕)💨',
312
- message: '만두 찌는 중...',
313
- },
314
- hmr: {
315
- state: 'hmr',
316
- emoji: '(◕‿◕)✨',
317
- message: '레시피 업데이트됨!',
318
- },
319
- };
1
+ /**
2
+ * Mandu Kitchen DevTools - Type Definitions
3
+ * @version 1.0.3
4
+ */
5
+
6
+ // ============================================================================
7
+ // Core Event Types
8
+ // ============================================================================
9
+
10
+ export interface KitchenEvent<T extends string = string, D = unknown> {
11
+ type: T;
12
+ timestamp: number;
13
+ data: D;
14
+ }
15
+
16
+ // ============================================================================
17
+ // Error Types
18
+ // ============================================================================
19
+
20
+ export type ErrorType = 'runtime' | 'unhandled' | 'react' | 'network' | 'hmr' | 'guard';
21
+ export type DevToolsSeverity = 'critical' | 'error' | 'warning' | 'info';
22
+
23
+ export interface NormalizedError {
24
+ id: string;
25
+ type: ErrorType;
26
+ severity: DevToolsSeverity;
27
+ message: string;
28
+ stack?: string;
29
+ source?: string;
30
+ line?: number;
31
+ column?: number;
32
+ componentStack?: string;
33
+ islandId?: string;
34
+ timestamp: number;
35
+ url: string;
36
+ }
37
+
38
+ // ============================================================================
39
+ // Island Types
40
+ // ============================================================================
41
+
42
+ export type DevToolsHydrationStrategy = 'load' | 'idle' | 'visible' | 'media' | 'never';
43
+ export type IslandStatus = 'ssr' | 'pending' | 'hydrating' | 'hydrated' | 'error';
44
+
45
+ export interface IslandSnapshot {
46
+ id: string;
47
+ name: string;
48
+ strategy: DevToolsHydrationStrategy;
49
+ status: IslandStatus;
50
+ ssrRenderTime?: number;
51
+ hydrateStartTime?: number;
52
+ hydrateEndTime?: number;
53
+ bundleSize?: number;
54
+ }
55
+
56
+ // ============================================================================
57
+ // Network Types
58
+ // ============================================================================
59
+
60
+ export interface NetworkRequest {
61
+ id: string;
62
+ method: string;
63
+ url: string;
64
+ safeHeaders: Record<string, string>;
65
+ redactedHeaders: string[];
66
+ body?: {
67
+ available: boolean;
68
+ size: number;
69
+ content?: unknown;
70
+ };
71
+ status?: number;
72
+ startTime: number;
73
+ endTime?: number;
74
+ isStreaming: boolean;
75
+ chunkCount?: number;
76
+ }
77
+
78
+ export interface NetworkBodyPolicy {
79
+ collectBody: boolean;
80
+ optInPolicy?: {
81
+ maxBytes: number;
82
+ applyPIIFilter: boolean;
83
+ applySecretFilter: boolean;
84
+ allowedContentTypes: string[];
85
+ };
86
+ }
87
+
88
+ // ============================================================================
89
+ // Guard Types
90
+ // ============================================================================
91
+
92
+ export interface DevToolsGuardViolation {
93
+ id: string;
94
+ ruleId: string;
95
+ ruleName: string;
96
+ severity: 'error' | 'warning';
97
+ message: string;
98
+ source: {
99
+ file: string;
100
+ line?: number;
101
+ column?: number;
102
+ };
103
+ target?: {
104
+ file: string;
105
+ line?: number;
106
+ };
107
+ suggestion?: string;
108
+ timestamp: number;
109
+ }
110
+
111
+ // ============================================================================
112
+ // Preview Types
113
+ // ============================================================================
114
+
115
+ export interface RecentChange {
116
+ filePath: string;
117
+ timestamp: number;
118
+ type: 'add' | 'change' | 'delete';
119
+ additions: number;
120
+ deletions: number;
121
+ }
122
+
123
+ // ============================================================================
124
+ // Guard Decision Types
125
+ // ============================================================================
126
+
127
+ export interface GuardDecision {
128
+ id: string;
129
+ violationKey: string; // "${ruleId}::${filePath}"
130
+ action: 'approve' | 'reject';
131
+ ruleId: string;
132
+ filePath: string;
133
+ reason?: string;
134
+ decidedAt: string;
135
+ }
136
+
137
+ // ============================================================================
138
+ // AI Context Types
139
+ // ============================================================================
140
+
141
+ export interface CodeContextInfo {
142
+ filePath: string;
143
+ line: number;
144
+ column?: number;
145
+ sourcemapUrl?: string;
146
+ snippet?: {
147
+ content: string;
148
+ lineRange: [number, number];
149
+ source: 'dev-server' | 'sourcemap-inline' | 'unavailable';
150
+ };
151
+ }
152
+
153
+ export interface AIContextPayload {
154
+ error: NormalizedError;
155
+ island?: IslandSnapshot;
156
+ framework: { name: 'mandu'; version: string };
157
+ devtools: { version: string };
158
+ recentErrors?: Array<{
159
+ id: string;
160
+ message: string;
161
+ timestamp: number;
162
+ isCausedBy?: string;
163
+ }>;
164
+ userActions?: Array<{
165
+ type: 'navigation' | 'interaction' | 'reload';
166
+ targetHint?: string;
167
+ timestamp: number;
168
+ }>;
169
+ codeContext?: CodeContextInfo;
170
+ }
171
+
172
+ // ============================================================================
173
+ // Redaction Types
174
+ // ============================================================================
175
+
176
+ export interface RedactPattern {
177
+ source: string;
178
+ flags?: string;
179
+ replacement?: string;
180
+ label?: string;
181
+ }
182
+
183
+ // ============================================================================
184
+ // Persistence Types
185
+ // ============================================================================
186
+
187
+ export interface PreserveLogConfig {
188
+ enabled: boolean;
189
+ maxPersistEvents: number;
190
+ maxPersistBytes: number;
191
+ priority: 'errors-first' | 'recent-first';
192
+ incremental?: {
193
+ enabled: boolean;
194
+ idleSyncMs: number;
195
+ };
196
+ }
197
+
198
+ // ============================================================================
199
+ // Worker Types
200
+ // ============================================================================
201
+
202
+ export interface WorkerPolicy {
203
+ timeout: number;
204
+ onTimeout: 'fallback-main' | 'skip';
205
+ onError: 'disable-worker' | 'retry-once';
206
+ maxConsecutiveFailures: number;
207
+ }
208
+
209
+ export interface WorkerTask {
210
+ id: string;
211
+ type: 'redact' | 'truncate';
212
+ data: {
213
+ text: string;
214
+ patterns?: RedactPattern[];
215
+ maxBytes?: number;
216
+ };
217
+ }
218
+
219
+ // ============================================================================
220
+ // Config Types
221
+ // ============================================================================
222
+
223
+ export type Position = 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';
224
+ export type Theme = 'light' | 'dark' | 'auto';
225
+
226
+ export interface DevToolsConfig {
227
+ enabled?: boolean;
228
+ position?: Position;
229
+ defaultOpen?: boolean;
230
+ theme?: Theme;
231
+
232
+ features?: {
233
+ errorOverlay?: boolean;
234
+ islandsInspector?: boolean;
235
+ networkMonitor?: boolean;
236
+ guardViewer?: boolean;
237
+ };
238
+
239
+ dataSafety?: {
240
+ stringMode?: 'smart' | 'strip';
241
+ collectUserActions?: boolean;
242
+ collectCodeContext?: boolean;
243
+ customRedactPatterns?: RedactPattern[];
244
+ };
245
+
246
+ network?: {
247
+ collectBody?: boolean;
248
+ bodyMaxBytes?: number;
249
+ };
250
+
251
+ persistence?: PreserveLogConfig;
252
+ plugins?: KitchenPanelPlugin[];
253
+ }
254
+
255
+ // ============================================================================
256
+ // Plugin Types
257
+ // ============================================================================
258
+
259
+ export interface KitchenAPI {
260
+ subscribe(type: string, callback: (event: KitchenEvent) => void): () => void;
261
+ getErrors(): NormalizedError[];
262
+ getIslands(): IslandSnapshot[];
263
+ getNetworkRequests(): NetworkRequest[];
264
+ clearErrors(): void;
265
+ getConfig(): DevToolsConfig;
266
+ copyToClipboard(text: string): Promise<void>;
267
+ openInEditor(file: string, line?: number): void;
268
+ }
269
+
270
+ export interface KitchenPanelPlugin {
271
+ id: string;
272
+ name: string;
273
+ icon: string;
274
+ order: number;
275
+
276
+ init(api: KitchenAPI): void;
277
+ destroy?(): void;
278
+ render(container: HTMLElement): void;
279
+ onEvent?(event: KitchenEvent): void;
280
+ }
281
+
282
+ // ============================================================================
283
+ // Meta Log Types
284
+ // ============================================================================
285
+
286
+ export type MetaLogType =
287
+ | 'init'
288
+ | 'hook_fail'
289
+ | 'render_fail'
290
+ | 'persist_fail'
291
+ | 'worker_timeout'
292
+ | 'worker_error'
293
+ | 'worker_disabled'
294
+ | 'recovered';
295
+
296
+ export interface KitchenMetaLog {
297
+ timestamp: number;
298
+ type: MetaLogType;
299
+ error?: string;
300
+ context: {
301
+ eventCount: number;
302
+ activeTab: string;
303
+ memoryInfo?: { usedJSHeapSize?: number };
304
+ };
305
+ }
306
+
307
+ // ============================================================================
308
+ // Mandu Character Types
309
+ // ============================================================================
310
+
311
+ export type ManduState = 'normal' | 'warning' | 'error' | 'loading' | 'hmr';
312
+
313
+ export interface ManduCharacterData {
314
+ state: ManduState;
315
+ emoji: string;
316
+ message: string;
317
+ }
318
+
319
+ export const MANDU_CHARACTERS: Record<ManduState, ManduCharacterData> = {
320
+ normal: {
321
+ state: 'normal',
322
+ emoji: '(◕‿◕)',
323
+ message: '모든 만두가 잘 익고 있어요~',
324
+ },
325
+ warning: {
326
+ state: 'warning',
327
+ emoji: '(◕_◕)',
328
+ message: '뭔가 이상해요...',
329
+ },
330
+ error: {
331
+ state: 'error',
332
+ emoji: '(ノಠ益ಠ)ノ彡┻━┻',
333
+ message: '만두가 타버렸어요!',
334
+ },
335
+ loading: {
336
+ state: 'loading',
337
+ emoji: '(◕‿◕)💨',
338
+ message: '만두 찌는 중...',
339
+ },
340
+ hmr: {
341
+ state: 'hmr',
342
+ emoji: '(◕‿◕)✨',
343
+ message: '레시피 업데이트됨!',
344
+ },
345
+ };