@mandujs/core 0.18.13 โ†’ 0.18.15

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mandujs/core",
3
- "version": "0.18.13",
3
+ "version": "0.18.15",
4
4
  "description": "Mandu Framework Core - Spec, Generator, Guard, Runtime",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -43,6 +43,19 @@ const baseStyles = `
43
43
  line-height: 1.5;
44
44
  }
45
45
 
46
+ button {
47
+ background: none;
48
+ border: none;
49
+ cursor: pointer;
50
+ font-family: inherit;
51
+ color: inherit;
52
+ font-size: inherit;
53
+ -webkit-appearance: none;
54
+ appearance: none;
55
+ padding: 0;
56
+ margin: 0;
57
+ }
58
+
46
59
  .mk-badge-container {
47
60
  position: fixed;
48
61
  z-index: ${zIndex.devtools};
@@ -247,7 +247,10 @@ export function ManduBadge({
247
247
  onBlur={() => setIsHovered(false)}
248
248
  aria-label={`Mandu Kitchen: ${character.message}${count > 0 ? `, ${count} issues` : ''}`}
249
249
  >
250
- <span aria-hidden="true">๐ŸฅŸ</span>
250
+ <span
251
+ aria-hidden="true"
252
+ style={{ fontFamily: "'Segoe UI Emoji', 'Apple Color Emoji', 'Noto Color Emoji', sans-serif" }}
253
+ >๐ŸฅŸ</span>
251
254
  {count > 0 && <span style={countStyle}>{count}</span>}
252
255
  </button>
253
256
  );
@@ -112,7 +112,7 @@ const styles = {
112
112
  tabActive: {
113
113
  color: colors.brand.accent,
114
114
  borderBottomColor: colors.brand.accent,
115
- backgroundColor: colors.background.medium,
115
+ backgroundColor: 'rgba(232, 150, 122, 0.12)',
116
116
  },
117
117
  tabIcon: {
118
118
  fontSize: typography.fontSize.md,
@@ -166,6 +166,8 @@ export function PanelContainer({
166
166
  }: PanelContainerProps): React.ReactElement {
167
167
  const [isResizing, setIsResizing] = useState(false);
168
168
  const [height, setHeight] = useState(400);
169
+ const [hoveredTab, setHoveredTab] = useState<TabId | null>(null);
170
+ const [isCloseHovered, setIsCloseHovered] = useState(false);
169
171
 
170
172
  const getTabBadgeCount = useCallback((tabId: TabId): number => {
171
173
  switch (tabId) {
@@ -206,8 +208,13 @@ export function PanelContainer({
206
208
  <span>Mandu Kitchen</span>
207
209
  </div>
208
210
  <button
209
- style={styles.closeButton}
211
+ style={{
212
+ ...styles.closeButton,
213
+ ...(isCloseHovered ? { color: colors.text.primary, backgroundColor: 'rgba(255, 255, 255, 0.08)' } : {}),
214
+ }}
210
215
  onClick={onClose}
216
+ onMouseEnter={() => setIsCloseHovered(true)}
217
+ onMouseLeave={() => setIsCloseHovered(false)}
211
218
  aria-label="ํŒจ๋„ ๋‹ซ๊ธฐ"
212
219
  >
213
220
  ร—
@@ -229,8 +236,14 @@ export function PanelContainer({
229
236
  style={{
230
237
  ...styles.tab,
231
238
  ...(isActive ? styles.tabActive : {}),
239
+ ...(!isActive && hoveredTab === tab.id ? {
240
+ color: colors.text.primary,
241
+ backgroundColor: 'rgba(255, 255, 255, 0.05)',
242
+ } : {}),
232
243
  }}
233
244
  onClick={() => onTabChange(tab.id)}
245
+ onMouseEnter={() => setHoveredTab(tab.id)}
246
+ onMouseLeave={() => setHoveredTab(null)}
234
247
  >
235
248
  <span style={styles.tabIcon}>{tab.icon}</span>
236
249
  <span>{tab.label}</span>
@@ -37,6 +37,13 @@ export interface NegotiationRequest {
37
37
  /** ๊ตฌํ˜„ํ•˜๋ ค๋Š” ๊ธฐ๋Šฅ์˜ ์˜๋„ */
38
38
  intent: string;
39
39
 
40
+ /**
41
+ * ์˜๋ฌธ feature name slug (์—์ด์ „ํŠธ๊ฐ€ ์ œ๊ณต).
42
+ * ์ œ๊ณต๋˜๋ฉด extractFeatureName()์„ ๊ฑด๋„ˆ๋›ฐ๊ณ  ์ด ๊ฐ’์„ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉ.
43
+ * ์˜ˆ: "chat", "user-auth", "payment", "file-upload"
44
+ */
45
+ featureName?: string;
46
+
40
47
  /** ์š”๊ตฌ์‚ฌํ•ญ ๋ชฉ๋ก */
41
48
  requirements?: string[];
42
49
 
@@ -1010,24 +1017,26 @@ function toCamelCase(str: string): string {
1010
1017
  .replace(/^(.)/, (_, c) => c.toLowerCase());
1011
1018
  }
1012
1019
 
1013
- // ํ•œ๊ตญ์–ด ๋„๋ฉ”์ธ ํ‚ค์›Œ๋“œ โ†’ ์˜๋ฌธ slug ๋งคํ•‘
1014
- const KOREAN_DOMAIN_MAP: Record<string, string> = {
1015
- "์ฑ„ํŒ…": "chat", "๋ฉ”์‹œ์ง€": "message", "์‚ฌ์šฉ์ž": "user", "์œ ์ €": "user",
1016
- "์ธ์ฆ": "auth", "๋กœ๊ทธ์ธ": "login", "๋กœ๊ทธ์•„์›ƒ": "logout", "ํšŒ์›๊ฐ€์ž…": "signup",
1017
- "๊ฒฐ์ œ": "payment", "์ฃผ๋ฌธ": "order", "์ƒํ’ˆ": "product", "์žฅ๋ฐ”๊ตฌ๋‹ˆ": "cart",
1018
- "์•Œ๋ฆผ": "notification", "ํ‘ธ์‹œ": "push",
1019
- "๊ฒŒ์‹œ๊ธ€": "post", "๊ฒŒ์‹œํŒ": "board", "๋Œ“๊ธ€": "comment", "์ข‹์•„์š”": "like",
1020
- "๊ฒ€์ƒ‰": "search", "ํ•„ํ„ฐ": "filter", "์ •๋ ฌ": "sort",
1021
- "ํŒŒ์ผ": "file", "์—…๋กœ๋“œ": "upload", "๋‹ค์šด๋กœ๋“œ": "download", "์ด๋ฏธ์ง€": "image",
1022
- "๋Œ€์‹œ๋ณด๋“œ": "dashboard", "ํ†ต๊ณ„": "stats", "๋ถ„์„": "analytics",
1023
- "์„ค์ •": "settings", "ํ”„๋กœํ•„": "profile", "๊ณ„์ •": "account",
1024
- "ํŒ€": "team", "ํ”„๋กœ์ ํŠธ": "project", "ํƒœ์Šคํฌ": "task", "์ผ์ •": "schedule",
1025
- "์ปดํฌ๋„ŒํŠธ": "component", "๋ ˆ์ด์•„์›ƒ": "layout", "๋„ค๋น„๊ฒŒ์ด์…˜": "navigation",
1026
- "์‹ค์‹œ๊ฐ„": "realtime", "์ŠคํŠธ๋ฆฌ๋ฐ": "streaming", "์›น์†Œ์ผ“": "websocket",
1027
- };
1020
+ /** ์—์ด์ „ํŠธ ์ œ๊ณต slug ์ •๋ฆฌ: lowercase kebab-case, ๋นˆ ๋ฌธ์ž์—ด์ด๋ฉด falsy */
1021
+ function sanitizeSlug(slug: string | undefined): string {
1022
+ if (!slug) return "";
1023
+ return slug
1024
+ .trim()
1025
+ .toLowerCase()
1026
+ .replace(/\s+/g, "-")
1027
+ .replace(/[^a-z0-9-]/g, "")
1028
+ .replace(/-{2,}/g, "-")
1029
+ .replace(/^-+|-+$/g, "");
1030
+ }
1028
1031
 
1032
+ /**
1033
+ * intent์—์„œ feature name ์ถ”์ถœ (featureName ๋ฏธ์ œ๊ณต ์‹œ fallback)
1034
+ *
1035
+ * MCP ์—์ด์ „ํŠธ๋Š” ํ•ญ์ƒ featureName์„ ์˜๋ฌธ์œผ๋กœ ์ œ๊ณตํ•˜๋ฏ€๋กœ,
1036
+ * ์ด ํ•จ์ˆ˜๋Š” CLI/ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์ง์ ‘ ํ˜ธ์ถœ ์‹œ fallback์œผ๋กœ๋งŒ ์‚ฌ์šฉ.
1037
+ */
1029
1038
  function extractFeatureName(intent: string): string {
1030
- // 1. ์˜๋ฌธ ํŒจํ„ด ์šฐ์„  ์‹œ๋„ (๊ฐ€์žฅ ์‹ ๋ขฐ๋„ ๋†’์Œ)
1039
+ // 1. ์˜๋ฌธ ํŒจํ„ด ์ถ”์ถœ
1031
1040
  const englishPatterns = [
1032
1041
  /(?:add|implement|create|build)\s+(.+)/i,
1033
1042
  /(.+?)\s+(?:feature|system|module|service)/i,
@@ -1046,46 +1055,13 @@ function extractFeatureName(intent: string): string {
1046
1055
  }
1047
1056
  }
1048
1057
 
1049
- // 2. ํ•œ๊ธ€ ํŒจํ„ด ์‹œ๋„ โ†’ ๋งค์นญ๋œ ํ…์ŠคํŠธ์—์„œ ๋„๋ฉ”์ธ ํ‚ค์›Œ๋“œ ์ถ”์ถœ
1050
- const koreanPatterns = [
1051
- /(?:์ถ”๊ฐ€|๊ตฌํ˜„|๋งŒ๋“ค|๊ฐœ๋ฐœ|์ž‘์„ฑ|์ƒ์„ฑ)(?:์–ด|ํ•ด|ํ•˜)[์ค˜์š”]?\s*[:\-]?\s*(.+)/,
1052
- /(.+?)\s*(?:๊ธฐ๋Šฅ|์‹œ์Šคํ…œ|๋ชจ๋“ˆ|์„œ๋น„์Šค|ํŽ˜์ด์ง€|์ปดํฌ๋„ŒํŠธ)\s*(?:์ถ”๊ฐ€|๊ตฌํ˜„|๋งŒ๋“ค|๊ฐœ๋ฐœ)?/,
1053
- /(.+?)\s*(?:์„|๋ฅผ|์˜|์—)\s/,
1054
- ];
1055
-
1056
- for (const pattern of koreanPatterns) {
1057
- const match = intent.match(pattern);
1058
- if (match) {
1059
- const captured = match[1].trim();
1060
- // ์บก์ฒ˜๋œ ํ…์ŠคํŠธ์—์„œ ๋„๋ฉ”์ธ ํ‚ค์›Œ๋“œ ๋งค์นญ
1061
- const slug = koreanToSlug(captured);
1062
- if (slug) return slug;
1063
- }
1064
- }
1065
-
1066
- // 3. intent ์ „์ฒด์—์„œ ๋„๋ฉ”์ธ ํ‚ค์›Œ๋“œ ์ง์ ‘ ํƒ์ƒ‰
1067
- const slug = koreanToSlug(intent);
1068
- if (slug) return slug;
1069
-
1070
- // 4. intent์—์„œ ์˜๋ฌธ ๋‹จ์–ด ์ถ”์ถœ ์‹œ๋„
1058
+ // 2. intent ๋‚ด ์˜๋ฌธ ๋‹จ์–ด ์ถ”์ถœ
1071
1059
  const englishWord = intent.match(/[a-z][a-z0-9-]{2,}/i)?.[0]?.toLowerCase();
1072
1060
  if (englishWord) return englishWord;
1073
1061
 
1074
1062
  return "feature";
1075
1063
  }
1076
1064
 
1077
- function koreanToSlug(text: string): string {
1078
- const matches: string[] = [];
1079
- for (const [korean, english] of Object.entries(KOREAN_DOMAIN_MAP)) {
1080
- if (text.includes(korean)) {
1081
- matches.push(english);
1082
- }
1083
- }
1084
- if (matches.length === 0) return "";
1085
- // ์—ฌ๋Ÿฌ ํ‚ค์›Œ๋“œ ๋งค์นญ ์‹œ ํ•˜์ดํฐ์œผ๋กœ ์—ฐ๊ฒฐ (์ตœ๋Œ€ 2๊ฐœ)
1086
- return matches.slice(0, 2).join("-");
1087
- }
1088
-
1089
1065
  /**
1090
1066
  * ํ”„๋ฆฌ์…‹์— ๋”ฐ๋ผ ๊ตฌ์กฐ๋ฅผ ์กฐ์ •
1091
1067
  * FSD, Clean, Hexagonal ๋“ฑ ํ”„๋ฆฌ์…‹๋ณ„ ๋ ˆ์ด์–ด ๋งคํ•‘ ์ ์šฉ
@@ -1181,8 +1157,8 @@ export async function negotiate(
1181
1157
  // 1. ์นดํ…Œ๊ณ ๋ฆฌ ๊ฐ์ง€
1182
1158
  const category = request.category || detectCategory(intent);
1183
1159
 
1184
- // 2. ๊ธฐ๋Šฅ ์ด๋ฆ„ ์ถ”์ถœ
1185
- const featureName = extractFeatureName(intent);
1160
+ // 2. ๊ธฐ๋Šฅ ์ด๋ฆ„: ์—์ด์ „ํŠธ ์ œ๊ณต ๊ฐ’ ์šฐ์„ , ์—†์œผ๋ฉด ์ž๋™ ์ถ”์ถœ
1161
+ const featureName = sanitizeSlug(request.featureName) || extractFeatureName(intent);
1186
1162
 
1187
1163
  // 3. ๊ด€๋ จ ๊ฒฐ์ • ๊ฒ€์ƒ‰
1188
1164
  const categoryTags = CATEGORY_KEYWORDS[category] || [];