@mandujs/core 0.19.0 → 0.20.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 +0 -14
- package/package.json +4 -1
- package/src/brain/architecture/analyzer.ts +4 -4
- package/src/brain/doctor/analyzer.ts +18 -14
- package/src/bundler/build.test.ts +127 -0
- package/src/bundler/build.ts +385 -115
- package/src/bundler/css.ts +20 -5
- package/src/bundler/dev.ts +55 -2
- package/src/bundler/prerender.ts +195 -0
- package/src/bundler/types.ts +20 -0
- package/src/change/snapshot.ts +4 -23
- package/src/change/types.ts +2 -3
- package/src/client/Form.tsx +105 -0
- package/src/client/__tests__/use-sse.test.ts +153 -0
- package/src/client/hooks.ts +105 -6
- package/src/client/index.ts +35 -6
- package/src/client/router.ts +670 -433
- package/src/client/rpc.ts +140 -0
- package/src/client/runtime.ts +24 -21
- package/src/client/use-fetch.ts +239 -0
- package/src/client/use-head.ts +197 -0
- package/src/client/use-sse.ts +378 -0
- package/src/components/Image.tsx +162 -0
- package/src/config/mandu.ts +5 -0
- package/src/config/validate.ts +34 -0
- package/src/content/index.ts +5 -1
- package/src/devtools/client/catchers/error-catcher.ts +17 -0
- package/src/devtools/client/catchers/network-proxy.ts +390 -367
- package/src/devtools/client/components/kitchen-root.tsx +479 -467
- package/src/devtools/client/components/mandu-character.tsx +77 -53
- package/src/devtools/client/components/panel/diff-viewer.tsx +219 -0
- package/src/devtools/client/components/panel/errors-panel.tsx +2 -2
- package/src/devtools/client/components/panel/guard-panel.tsx +363 -234
- package/src/devtools/client/components/panel/index.ts +45 -32
- package/src/devtools/client/components/panel/islands-panel.tsx +30 -14
- package/src/devtools/client/components/panel/network-panel.tsx +2 -3
- package/src/devtools/client/components/panel/panel-container.tsx +273 -100
- package/src/devtools/client/components/panel/preview-panel.tsx +212 -0
- package/src/devtools/client/state-manager.ts +535 -478
- package/src/devtools/design-tokens.ts +265 -264
- package/src/devtools/init.ts +1 -1
- package/src/devtools/types.ts +321 -295
- package/src/filling/filling.ts +328 -6
- package/src/filling/index.ts +5 -1
- package/src/filling/session.ts +216 -0
- package/src/filling/ws.ts +78 -0
- package/src/generator/generate.ts +2 -2
- package/src/guard/auto-correct.ts +0 -29
- package/src/guard/check.ts +14 -31
- package/src/guard/presets/index.ts +296 -294
- package/src/guard/rules.ts +15 -19
- package/src/guard/validator.ts +834 -834
- package/src/index.ts +6 -1
- package/src/island/index.ts +373 -304
- package/src/kitchen/api/contract-api.ts +225 -0
- package/src/kitchen/api/diff-parser.ts +108 -0
- package/src/kitchen/api/file-api.ts +273 -0
- package/src/kitchen/api/guard-api.ts +83 -0
- package/src/kitchen/api/guard-decisions.ts +100 -0
- package/src/kitchen/api/routes-api.ts +50 -0
- package/src/kitchen/index.ts +21 -0
- package/src/kitchen/kitchen-handler.ts +335 -0
- package/src/kitchen/kitchen-ui.ts +1732 -0
- package/src/kitchen/stream/activity-sse.ts +145 -0
- package/src/kitchen/stream/file-tailer.ts +99 -0
- package/src/middleware/compress.ts +62 -0
- package/src/middleware/cors.ts +47 -0
- package/src/middleware/index.ts +10 -0
- package/src/middleware/jwt.ts +134 -0
- package/src/middleware/logger.ts +58 -0
- package/src/middleware/timeout.ts +55 -0
- package/src/observability/event-bus.ts +79 -0
- package/src/observability/index.ts +8 -0
- package/src/observability/logger-adapter.ts +36 -0
- package/src/paths.ts +0 -4
- package/src/plugins/hooks.ts +64 -0
- package/src/plugins/index.ts +3 -0
- package/src/plugins/types.ts +5 -0
- package/src/report/build.ts +0 -6
- package/src/resource/__tests__/backward-compat.test.ts +0 -1
- package/src/router/fs-patterns.ts +11 -1
- package/src/router/fs-routes.ts +78 -14
- package/src/router/fs-scanner.ts +2 -2
- package/src/router/fs-types.ts +2 -1
- package/src/runtime/adapter-bun.ts +62 -0
- package/src/runtime/adapter.ts +47 -0
- package/src/runtime/cache.ts +310 -0
- package/src/runtime/handler.ts +65 -0
- package/src/runtime/image-handler.ts +195 -0
- package/src/runtime/index.ts +13 -0
- package/src/runtime/middleware.ts +263 -0
- package/src/runtime/ppr.ts +74 -0
- package/src/runtime/server.ts +706 -71
- package/src/runtime/ssr.ts +70 -31
- package/src/runtime/streaming-ssr.ts +121 -78
- package/src/spec/index.ts +0 -1
- package/src/spec/schema.ts +1 -0
- package/src/testing/index.ts +189 -0
- package/src/watcher/watcher.ts +27 -1
- package/src/spec/lock.ts +0 -56
|
@@ -8,24 +8,34 @@ import type { ManduState } from '../../types';
|
|
|
8
8
|
import { MANDU_CHARACTERS } from '../../types';
|
|
9
9
|
import { colors, typography, animation, testIds } from '../../design-tokens';
|
|
10
10
|
|
|
11
|
-
// ============================================================================
|
|
12
|
-
// Styles
|
|
13
|
-
// ============================================================================
|
|
14
|
-
|
|
15
11
|
const styles = {
|
|
16
12
|
container: {
|
|
17
13
|
display: 'flex',
|
|
18
14
|
alignItems: 'center',
|
|
19
15
|
gap: '12px',
|
|
20
16
|
padding: '12px 16px',
|
|
21
|
-
borderRadius: '
|
|
22
|
-
|
|
17
|
+
borderRadius: '16px',
|
|
18
|
+
background: `linear-gradient(180deg, ${colors.background.medium}, ${colors.background.dark})`,
|
|
19
|
+
border: `1px solid ${colors.background.light}`,
|
|
20
|
+
boxShadow: '0 14px 32px rgba(7, 7, 12, 0.28)',
|
|
23
21
|
transition: `all ${animation.duration.normal} ${animation.easing.easeOut}`,
|
|
24
22
|
},
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
mark: {
|
|
24
|
+
display: 'flex',
|
|
25
|
+
alignItems: 'center',
|
|
26
|
+
justifyContent: 'center',
|
|
27
|
+
minWidth: '44px',
|
|
28
|
+
height: '44px',
|
|
29
|
+
padding: '0 12px',
|
|
30
|
+
borderRadius: '9999px',
|
|
31
|
+
fontSize: '11px',
|
|
27
32
|
lineHeight: 1,
|
|
33
|
+
letterSpacing: '0.12em',
|
|
34
|
+
fontWeight: 700,
|
|
28
35
|
userSelect: 'none' as const,
|
|
36
|
+
fontFamily: typography.fontFamily.mono,
|
|
37
|
+
textTransform: 'uppercase' as const,
|
|
38
|
+
border: `1px solid ${colors.background.light}`,
|
|
29
39
|
},
|
|
30
40
|
content: {
|
|
31
41
|
display: 'flex',
|
|
@@ -35,7 +45,7 @@ const styles = {
|
|
|
35
45
|
message: {
|
|
36
46
|
fontSize: '14px',
|
|
37
47
|
color: colors.text.primary,
|
|
38
|
-
fontWeight:
|
|
48
|
+
fontWeight: 600,
|
|
39
49
|
},
|
|
40
50
|
status: {
|
|
41
51
|
fontSize: '12px',
|
|
@@ -51,10 +61,6 @@ const stateColors: Record<ManduState, string> = {
|
|
|
51
61
|
hmr: colors.brand.accent,
|
|
52
62
|
};
|
|
53
63
|
|
|
54
|
-
// ============================================================================
|
|
55
|
-
// Animation Keyframes (inline)
|
|
56
|
-
// ============================================================================
|
|
57
|
-
|
|
58
64
|
const bounceAnimation = `
|
|
59
65
|
@keyframes mk-bounce {
|
|
60
66
|
0%, 100% { transform: translateY(0); }
|
|
@@ -80,15 +86,11 @@ const shakeAnimation = `
|
|
|
80
86
|
const sparkleAnimation = `
|
|
81
87
|
@keyframes mk-sparkle {
|
|
82
88
|
0% { transform: scale(1); }
|
|
83
|
-
50% { transform: scale(1.
|
|
89
|
+
50% { transform: scale(1.08); }
|
|
84
90
|
100% { transform: scale(1); }
|
|
85
91
|
}
|
|
86
92
|
`;
|
|
87
93
|
|
|
88
|
-
// ============================================================================
|
|
89
|
-
// Props
|
|
90
|
-
// ============================================================================
|
|
91
|
-
|
|
92
94
|
export interface ManduCharacterProps {
|
|
93
95
|
state: ManduState;
|
|
94
96
|
errorCount?: number;
|
|
@@ -97,10 +99,6 @@ export interface ManduCharacterProps {
|
|
|
97
99
|
onClick?: () => void;
|
|
98
100
|
}
|
|
99
101
|
|
|
100
|
-
// ============================================================================
|
|
101
|
-
// Component
|
|
102
|
-
// ============================================================================
|
|
103
|
-
|
|
104
102
|
export function ManduCharacter({
|
|
105
103
|
state,
|
|
106
104
|
errorCount = 0,
|
|
@@ -126,25 +124,34 @@ export function ManduCharacter({
|
|
|
126
124
|
}
|
|
127
125
|
};
|
|
128
126
|
|
|
129
|
-
const containerStyle = {
|
|
127
|
+
const containerStyle: React.CSSProperties = {
|
|
130
128
|
...styles.container,
|
|
131
129
|
borderLeft: `4px solid ${stateColor}`,
|
|
132
130
|
cursor: onClick ? 'pointer' : 'default',
|
|
133
131
|
...(compact && {
|
|
134
132
|
padding: '8px 12px',
|
|
135
133
|
gap: '8px',
|
|
134
|
+
borderRadius: '14px',
|
|
136
135
|
}),
|
|
137
136
|
};
|
|
138
137
|
|
|
139
|
-
const
|
|
140
|
-
...styles.
|
|
138
|
+
const markStyle: React.CSSProperties = {
|
|
139
|
+
...styles.mark,
|
|
141
140
|
animation: getAnimation(),
|
|
142
|
-
|
|
141
|
+
color: stateColor,
|
|
142
|
+
backgroundColor: `${stateColor}18`,
|
|
143
|
+
borderColor: `${stateColor}45`,
|
|
144
|
+
boxShadow: `inset 0 1px 0 rgba(255, 255, 255, 0.03), 0 0 0 1px ${stateColor}10`,
|
|
145
|
+
...(compact && {
|
|
146
|
+
minWidth: '36px',
|
|
147
|
+
height: '36px',
|
|
148
|
+
padding: '0 10px',
|
|
149
|
+
fontSize: '10px',
|
|
150
|
+
}),
|
|
143
151
|
};
|
|
144
152
|
|
|
145
153
|
return (
|
|
146
154
|
<>
|
|
147
|
-
{/* Inject keyframes */}
|
|
148
155
|
<style>
|
|
149
156
|
{bounceAnimation}
|
|
150
157
|
{pulseAnimation}
|
|
@@ -166,8 +173,8 @@ export function ManduCharacter({
|
|
|
166
173
|
}
|
|
167
174
|
}}
|
|
168
175
|
>
|
|
169
|
-
<span style={
|
|
170
|
-
{character.
|
|
176
|
+
<span style={markStyle} aria-hidden="true">
|
|
177
|
+
{character.mark}
|
|
171
178
|
</span>
|
|
172
179
|
|
|
173
180
|
{!compact && (
|
|
@@ -175,7 +182,7 @@ export function ManduCharacter({
|
|
|
175
182
|
<span style={styles.message}>{character.message}</span>
|
|
176
183
|
{errorCount > 0 && (
|
|
177
184
|
<span style={styles.status}>
|
|
178
|
-
{errorCount}개의 {state === 'error' ? '에러' : '경고'}가
|
|
185
|
+
{errorCount}개의 {state === 'error' ? '에러' : '경고'}가 있습니다
|
|
179
186
|
</span>
|
|
180
187
|
)}
|
|
181
188
|
</div>
|
|
@@ -185,10 +192,6 @@ export function ManduCharacter({
|
|
|
185
192
|
);
|
|
186
193
|
}
|
|
187
194
|
|
|
188
|
-
// ============================================================================
|
|
189
|
-
// Badge Component (for mini display)
|
|
190
|
-
// ============================================================================
|
|
191
|
-
|
|
192
195
|
export interface ManduBadgeProps {
|
|
193
196
|
state: ManduState;
|
|
194
197
|
count?: number;
|
|
@@ -210,28 +213,28 @@ export function ManduBadge({
|
|
|
210
213
|
display: 'flex',
|
|
211
214
|
alignItems: 'center',
|
|
212
215
|
justifyContent: 'center',
|
|
213
|
-
width: '
|
|
214
|
-
height: '
|
|
215
|
-
padding: 0,
|
|
216
|
-
borderRadius: '
|
|
217
|
-
|
|
218
|
-
? colors.background.light
|
|
216
|
+
width: '72px',
|
|
217
|
+
height: '56px',
|
|
218
|
+
padding: '0 14px',
|
|
219
|
+
borderRadius: '18px',
|
|
220
|
+
background: isPressed
|
|
221
|
+
? `linear-gradient(180deg, ${colors.background.light}, ${colors.background.medium})`
|
|
219
222
|
: isHovered
|
|
220
|
-
? colors.background.medium
|
|
221
|
-
: colors.background.dark,
|
|
222
|
-
border: `
|
|
223
|
+
? `linear-gradient(180deg, ${colors.background.medium}, ${colors.background.dark})`
|
|
224
|
+
: `linear-gradient(180deg, ${colors.background.dark}, ${colors.background.medium})`,
|
|
225
|
+
border: `1px solid ${isPressed ? stateColor : isHovered ? colors.brand.accent : `${stateColor}80`}`,
|
|
223
226
|
cursor: 'pointer',
|
|
224
227
|
transition: `all 200ms ${animation.easing.spring}`,
|
|
225
228
|
boxShadow: isPressed
|
|
226
|
-
?
|
|
229
|
+
? '0 8px 18px rgba(8, 6, 18, 0.36), inset 0 2px 4px rgba(0, 0, 0, 0.12)'
|
|
227
230
|
: isHovered
|
|
228
|
-
? `0
|
|
229
|
-
: `0
|
|
231
|
+
? `0 18px 36px rgba(8, 6, 18, 0.42), 0 0 0 4px ${stateColor}18`
|
|
232
|
+
: `0 10px 22px rgba(8, 6, 18, 0.3), 0 0 0 1px ${stateColor}14`,
|
|
230
233
|
userSelect: 'none',
|
|
231
234
|
transform: isPressed
|
|
232
235
|
? 'scale(0.92) translateY(1px)'
|
|
233
236
|
: isHovered
|
|
234
|
-
? 'scale(1.
|
|
237
|
+
? 'scale(1.04) translateY(-2px)'
|
|
235
238
|
: 'scale(1) translateY(0px)',
|
|
236
239
|
outline: 'none',
|
|
237
240
|
lineHeight: 1,
|
|
@@ -247,17 +250,29 @@ export function ManduBadge({
|
|
|
247
250
|
};
|
|
248
251
|
|
|
249
252
|
const textStyle: React.CSSProperties = {
|
|
253
|
+
display: 'flex',
|
|
254
|
+
flexDirection: 'column',
|
|
255
|
+
alignItems: 'flex-start',
|
|
256
|
+
gap: '3px',
|
|
250
257
|
fontFamily: typography.fontFamily.sans,
|
|
251
258
|
color: colors.brand.accent,
|
|
252
|
-
fontSize: '
|
|
259
|
+
fontSize: '13px',
|
|
253
260
|
fontWeight: typography.fontWeight.bold,
|
|
254
261
|
lineHeight: 1,
|
|
255
|
-
letterSpacing: '0.
|
|
262
|
+
letterSpacing: '0.08em',
|
|
256
263
|
transition: `transform 200ms ${animation.easing.spring}`,
|
|
257
|
-
transform: isHovered ? '
|
|
264
|
+
transform: isHovered ? 'translateY(-1px)' : 'translateY(0px)',
|
|
258
265
|
userSelect: 'none',
|
|
259
266
|
};
|
|
260
267
|
|
|
268
|
+
const subtextStyle: React.CSSProperties = {
|
|
269
|
+
fontSize: '9px',
|
|
270
|
+
fontWeight: typography.fontWeight.medium,
|
|
271
|
+
letterSpacing: '0.16em',
|
|
272
|
+
color: colors.text.secondary,
|
|
273
|
+
textTransform: 'uppercase',
|
|
274
|
+
};
|
|
275
|
+
|
|
261
276
|
const countBubbleStyle: React.CSSProperties = {
|
|
262
277
|
position: 'absolute',
|
|
263
278
|
top: '-4px',
|
|
@@ -286,14 +301,23 @@ export function ManduBadge({
|
|
|
286
301
|
style={badgeStyle}
|
|
287
302
|
onClick={onClick}
|
|
288
303
|
onMouseEnter={() => setIsHovered(true)}
|
|
289
|
-
onMouseLeave={() => {
|
|
304
|
+
onMouseLeave={() => {
|
|
305
|
+
setIsHovered(false);
|
|
306
|
+
setIsPressed(false);
|
|
307
|
+
}}
|
|
290
308
|
onMouseDown={() => setIsPressed(true)}
|
|
291
309
|
onMouseUp={() => setIsPressed(false)}
|
|
292
310
|
onFocus={() => setIsHovered(true)}
|
|
293
|
-
onBlur={() => {
|
|
311
|
+
onBlur={() => {
|
|
312
|
+
setIsHovered(false);
|
|
313
|
+
setIsPressed(false);
|
|
314
|
+
}}
|
|
294
315
|
aria-label={`Mandu Kitchen: ${character.message}${count > 0 ? `, ${count} issues` : ''}`}
|
|
295
316
|
>
|
|
296
|
-
<span aria-hidden="true" style={textStyle}>
|
|
317
|
+
<span aria-hidden="true" style={textStyle}>
|
|
318
|
+
<span>MK</span>
|
|
319
|
+
<span style={subtextStyle}>Dev</span>
|
|
320
|
+
</span>
|
|
297
321
|
{count > 0 && (
|
|
298
322
|
<span style={countBubbleStyle}>
|
|
299
323
|
{count > 99 ? '99+' : count}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mandu Kitchen DevTools - Diff Viewer
|
|
3
|
+
* @version 2.0.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React from 'react';
|
|
7
|
+
import { colors, typography, spacing, borderRadius, animation } from '../../../design-tokens';
|
|
8
|
+
|
|
9
|
+
// ============================================================================
|
|
10
|
+
// Types
|
|
11
|
+
// ============================================================================
|
|
12
|
+
|
|
13
|
+
export interface DiffLine {
|
|
14
|
+
type: 'add' | 'remove' | 'context';
|
|
15
|
+
content: string;
|
|
16
|
+
oldLine?: number;
|
|
17
|
+
newLine?: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface DiffHunk {
|
|
21
|
+
header: string;
|
|
22
|
+
lines: DiffLine[];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface FileDiff {
|
|
26
|
+
filePath: string;
|
|
27
|
+
hunks: DiffHunk[];
|
|
28
|
+
additions: number;
|
|
29
|
+
deletions: number;
|
|
30
|
+
isNew: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface DiffViewerProps {
|
|
34
|
+
diff: FileDiff;
|
|
35
|
+
onClose: () => void;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ============================================================================
|
|
39
|
+
// Styles
|
|
40
|
+
// ============================================================================
|
|
41
|
+
|
|
42
|
+
const styles = {
|
|
43
|
+
container: {
|
|
44
|
+
display: 'flex',
|
|
45
|
+
flexDirection: 'column' as const,
|
|
46
|
+
height: '100%',
|
|
47
|
+
overflow: 'hidden',
|
|
48
|
+
},
|
|
49
|
+
header: {
|
|
50
|
+
display: 'flex',
|
|
51
|
+
alignItems: 'center',
|
|
52
|
+
justifyContent: 'space-between',
|
|
53
|
+
padding: `${spacing.sm} ${spacing.md}`,
|
|
54
|
+
borderBottom: `1px solid ${colors.background.light}`,
|
|
55
|
+
backgroundColor: colors.background.medium,
|
|
56
|
+
flexShrink: 0,
|
|
57
|
+
},
|
|
58
|
+
headerLeft: {
|
|
59
|
+
display: 'flex',
|
|
60
|
+
alignItems: 'center',
|
|
61
|
+
gap: spacing.sm,
|
|
62
|
+
overflow: 'hidden',
|
|
63
|
+
},
|
|
64
|
+
fileName: {
|
|
65
|
+
fontSize: typography.fontSize.sm,
|
|
66
|
+
fontFamily: typography.fontFamily.mono,
|
|
67
|
+
color: colors.text.primary,
|
|
68
|
+
overflow: 'hidden',
|
|
69
|
+
textOverflow: 'ellipsis',
|
|
70
|
+
whiteSpace: 'nowrap' as const,
|
|
71
|
+
},
|
|
72
|
+
stats: {
|
|
73
|
+
display: 'flex',
|
|
74
|
+
gap: spacing.sm,
|
|
75
|
+
fontSize: typography.fontSize.xs,
|
|
76
|
+
flexShrink: 0,
|
|
77
|
+
},
|
|
78
|
+
addStat: {
|
|
79
|
+
color: colors.semantic.success,
|
|
80
|
+
fontWeight: typography.fontWeight.medium,
|
|
81
|
+
},
|
|
82
|
+
removeStat: {
|
|
83
|
+
color: colors.semantic.error,
|
|
84
|
+
fontWeight: typography.fontWeight.medium,
|
|
85
|
+
},
|
|
86
|
+
closeBtn: {
|
|
87
|
+
background: 'transparent',
|
|
88
|
+
border: 'none',
|
|
89
|
+
color: colors.text.secondary,
|
|
90
|
+
fontSize: typography.fontSize.md,
|
|
91
|
+
cursor: 'pointer',
|
|
92
|
+
padding: spacing.xs,
|
|
93
|
+
borderRadius: borderRadius.sm,
|
|
94
|
+
transition: `color ${animation.duration.fast}`,
|
|
95
|
+
flexShrink: 0,
|
|
96
|
+
},
|
|
97
|
+
body: {
|
|
98
|
+
flex: 1,
|
|
99
|
+
overflow: 'auto',
|
|
100
|
+
fontFamily: typography.fontFamily.mono,
|
|
101
|
+
fontSize: typography.fontSize.xs,
|
|
102
|
+
lineHeight: typography.lineHeight.normal,
|
|
103
|
+
},
|
|
104
|
+
hunkHeader: {
|
|
105
|
+
padding: `${spacing.xs} ${spacing.md}`,
|
|
106
|
+
backgroundColor: `${colors.semantic.info}10`,
|
|
107
|
+
color: colors.semantic.info,
|
|
108
|
+
fontSize: typography.fontSize.xs,
|
|
109
|
+
borderBottom: `1px solid ${colors.background.light}`,
|
|
110
|
+
},
|
|
111
|
+
line: {
|
|
112
|
+
display: 'flex',
|
|
113
|
+
minHeight: '20px',
|
|
114
|
+
borderBottom: `1px solid ${colors.background.dark}`,
|
|
115
|
+
},
|
|
116
|
+
lineNumber: {
|
|
117
|
+
width: '40px',
|
|
118
|
+
padding: `0 ${spacing.xs}`,
|
|
119
|
+
textAlign: 'right' as const,
|
|
120
|
+
color: colors.text.muted,
|
|
121
|
+
userSelect: 'none' as const,
|
|
122
|
+
flexShrink: 0,
|
|
123
|
+
fontSize: typography.fontSize.xs,
|
|
124
|
+
lineHeight: '20px',
|
|
125
|
+
},
|
|
126
|
+
lineContent: {
|
|
127
|
+
flex: 1,
|
|
128
|
+
padding: `0 ${spacing.sm}`,
|
|
129
|
+
whiteSpace: 'pre' as const,
|
|
130
|
+
overflow: 'hidden',
|
|
131
|
+
textOverflow: 'ellipsis',
|
|
132
|
+
lineHeight: '20px',
|
|
133
|
+
},
|
|
134
|
+
emptyState: {
|
|
135
|
+
padding: spacing.xl,
|
|
136
|
+
textAlign: 'center' as const,
|
|
137
|
+
color: colors.text.muted,
|
|
138
|
+
fontSize: typography.fontSize.sm,
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const lineStyles: Record<string, React.CSSProperties> = {
|
|
143
|
+
add: {
|
|
144
|
+
backgroundColor: `${colors.semantic.success}15`,
|
|
145
|
+
color: colors.text.primary,
|
|
146
|
+
},
|
|
147
|
+
remove: {
|
|
148
|
+
backgroundColor: `${colors.semantic.error}15`,
|
|
149
|
+
color: colors.text.primary,
|
|
150
|
+
},
|
|
151
|
+
context: {
|
|
152
|
+
backgroundColor: 'transparent',
|
|
153
|
+
color: colors.text.secondary,
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const linePrefix: Record<string, string> = {
|
|
158
|
+
add: '+',
|
|
159
|
+
remove: '-',
|
|
160
|
+
context: ' ',
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
// ============================================================================
|
|
164
|
+
// Component
|
|
165
|
+
// ============================================================================
|
|
166
|
+
|
|
167
|
+
export function DiffViewer({ diff, onClose }: DiffViewerProps): React.ReactElement {
|
|
168
|
+
if (diff.hunks.length === 0) {
|
|
169
|
+
return (
|
|
170
|
+
<div style={styles.container}>
|
|
171
|
+
<div style={styles.header}>
|
|
172
|
+
<div style={styles.headerLeft}>
|
|
173
|
+
<span style={styles.fileName}>{diff.filePath}</span>
|
|
174
|
+
</div>
|
|
175
|
+
<button style={styles.closeBtn} onClick={onClose} aria-label="닫기">×</button>
|
|
176
|
+
</div>
|
|
177
|
+
<div style={styles.emptyState}>변경 사항 없음</div>
|
|
178
|
+
</div>
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return (
|
|
183
|
+
<div style={styles.container}>
|
|
184
|
+
<div style={styles.header}>
|
|
185
|
+
<div style={styles.headerLeft}>
|
|
186
|
+
<span style={styles.fileName}>
|
|
187
|
+
{diff.isNew ? '(new) ' : ''}{diff.filePath}
|
|
188
|
+
</span>
|
|
189
|
+
<div style={styles.stats}>
|
|
190
|
+
<span style={styles.addStat}>+{diff.additions}</span>
|
|
191
|
+
<span style={styles.removeStat}>-{diff.deletions}</span>
|
|
192
|
+
</div>
|
|
193
|
+
</div>
|
|
194
|
+
<button style={styles.closeBtn} onClick={onClose} aria-label="닫기">×</button>
|
|
195
|
+
</div>
|
|
196
|
+
|
|
197
|
+
<div style={styles.body}>
|
|
198
|
+
{diff.hunks.map((hunk, hi) => (
|
|
199
|
+
<div key={hi}>
|
|
200
|
+
<div style={styles.hunkHeader}>{hunk.header}</div>
|
|
201
|
+
{hunk.lines.map((line, li) => (
|
|
202
|
+
<div key={li} style={{ ...styles.line, ...lineStyles[line.type] }}>
|
|
203
|
+
<span style={styles.lineNumber}>
|
|
204
|
+
{line.type !== 'add' ? (line.oldLine ?? '') : ''}
|
|
205
|
+
</span>
|
|
206
|
+
<span style={styles.lineNumber}>
|
|
207
|
+
{line.type !== 'remove' ? (line.newLine ?? '') : ''}
|
|
208
|
+
</span>
|
|
209
|
+
<span style={styles.lineContent}>
|
|
210
|
+
{linePrefix[line.type]}{line.content}
|
|
211
|
+
</span>
|
|
212
|
+
</div>
|
|
213
|
+
))}
|
|
214
|
+
</div>
|
|
215
|
+
))}
|
|
216
|
+
</div>
|
|
217
|
+
</div>
|
|
218
|
+
);
|
|
219
|
+
}
|
|
@@ -176,8 +176,8 @@ export function ErrorsPanel({
|
|
|
176
176
|
<div style={styles.emptyState}>
|
|
177
177
|
<ManduCharacter state="normal" compact />
|
|
178
178
|
<p style={styles.emptyMessage}>
|
|
179
|
-
|
|
180
|
-
|
|
179
|
+
활성 오류가 없습니다.<br />
|
|
180
|
+
새 이슈가 생기면 여기에 표시됩니다.
|
|
181
181
|
</p>
|
|
182
182
|
</div>
|
|
183
183
|
</div>
|