@mandujs/mcp 0.9.19 → 0.9.21
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.md +320 -0
- package/package.json +1 -1
- package/src/activity-monitor.ts +847 -231
- package/src/resources/handlers.ts +244 -0
- package/src/resources/skills/guides.ts +1136 -0
- package/src/resources/skills/index.ts +12 -0
- package/src/resources/skills/loader.ts +218 -0
- package/src/resources/skills/mandu-composition/SKILL.md +91 -0
- package/src/resources/skills/mandu-composition/metadata.json +13 -0
- package/src/resources/skills/mandu-composition/rules/_sections.md +26 -0
- package/src/resources/skills/mandu-composition/rules/_template.md +77 -0
- package/src/resources/skills/mandu-composition/rules/comp-arch-avoid-boolean-props.md +146 -0
- package/src/resources/skills/mandu-composition/rules/comp-arch-compound-components.md +164 -0
- package/src/resources/skills/mandu-composition/rules/comp-island-event.md +161 -0
- package/src/resources/skills/mandu-composition/rules/comp-island-slot-split.md +167 -0
- package/src/resources/skills/mandu-composition/rules/comp-pattern-children.md +149 -0
- package/src/resources/skills/mandu-composition/rules/comp-state-context-interface.md +148 -0
- package/src/resources/skills/mandu-composition/rules/comp-state-lift-state.md +150 -0
- package/src/resources/skills/mandu-deployment/SKILL.md +92 -0
- package/src/resources/skills/mandu-deployment/_sections.md +41 -0
- package/src/resources/skills/mandu-deployment/_template.md +38 -0
- package/src/resources/skills/mandu-deployment/metadata.json +13 -0
- package/src/resources/skills/mandu-deployment/rules/deploy-build-bun.md +109 -0
- package/src/resources/skills/mandu-deployment/rules/deploy-build-output.md +115 -0
- package/src/resources/skills/mandu-deployment/rules/deploy-cicd-github.md +219 -0
- package/src/resources/skills/mandu-deployment/rules/deploy-docker-bun.md +150 -0
- package/src/resources/skills/mandu-deployment/rules/deploy-docker-compose.md +223 -0
- package/src/resources/skills/mandu-deployment/rules/deploy-platform-fly.md +152 -0
- package/src/resources/skills/mandu-deployment/rules/deploy-platform-render.md +179 -0
- package/src/resources/skills/mandu-deployment/rules/deploy-platform-supabase.md +323 -0
- package/src/resources/skills/mandu-deployment/rules/deploy-platform-vercel.md +140 -0
- package/src/resources/skills/mandu-fs-routes/SKILL.md +82 -0
- package/src/resources/skills/mandu-fs-routes/metadata.json +12 -0
- package/src/resources/skills/mandu-fs-routes/rules/_sections.md +36 -0
- package/src/resources/skills/mandu-fs-routes/rules/_template.md +69 -0
- package/src/resources/skills/mandu-fs-routes/rules/routes-api-methods.md +65 -0
- package/src/resources/skills/mandu-fs-routes/rules/routes-dynamic-param.md +93 -0
- package/src/resources/skills/mandu-fs-routes/rules/routes-naming-page.md +55 -0
- package/src/resources/skills/mandu-guard/SKILL.md +129 -0
- package/src/resources/skills/mandu-guard/metadata.json +12 -0
- package/src/resources/skills/mandu-guard/rules/_sections.md +36 -0
- package/src/resources/skills/mandu-guard/rules/_template.md +82 -0
- package/src/resources/skills/mandu-guard/rules/guard-config-rules.md +100 -0
- package/src/resources/skills/mandu-guard/rules/guard-layer-direction.md +76 -0
- package/src/resources/skills/mandu-guard/rules/guard-preset-mandu.md +81 -0
- package/src/resources/skills/mandu-guard/rules/guard-validate-import.md +80 -0
- package/src/resources/skills/mandu-hydration/SKILL.md +91 -0
- package/src/resources/skills/mandu-hydration/metadata.json +12 -0
- package/src/resources/skills/mandu-hydration/rules/_sections.md +31 -0
- package/src/resources/skills/mandu-hydration/rules/_template.md +72 -0
- package/src/resources/skills/mandu-hydration/rules/hydration-data-event.md +109 -0
- package/src/resources/skills/mandu-hydration/rules/hydration-directive-use-client.md +55 -0
- package/src/resources/skills/mandu-hydration/rules/hydration-island-setup.md +113 -0
- package/src/resources/skills/mandu-hydration/rules/hydration-priority-visible.md +68 -0
- package/src/resources/skills/mandu-performance/SKILL.md +85 -0
- package/src/resources/skills/mandu-performance/metadata.json +14 -0
- package/src/resources/skills/mandu-performance/rules/_sections.md +31 -0
- package/src/resources/skills/mandu-performance/rules/_template.md +64 -0
- package/src/resources/skills/mandu-performance/rules/perf-async-defer-await.md +103 -0
- package/src/resources/skills/mandu-performance/rules/perf-async-parallel.md +95 -0
- package/src/resources/skills/mandu-performance/rules/perf-bun-file.md +124 -0
- package/src/resources/skills/mandu-performance/rules/perf-bun-serve.md +125 -0
- package/src/resources/skills/mandu-performance/rules/perf-bundle-imports.md +80 -0
- package/src/resources/skills/mandu-performance/rules/perf-bundle-island-lazy.md +145 -0
- package/src/resources/skills/mandu-performance/rules/perf-cache-react.md +98 -0
- package/src/resources/skills/mandu-performance/rules/perf-render-transitions.md +154 -0
- package/src/resources/skills/mandu-security/SKILL.md +87 -0
- package/src/resources/skills/mandu-security/metadata.json +13 -0
- package/src/resources/skills/mandu-security/rules/_sections.md +31 -0
- package/src/resources/skills/mandu-security/rules/_template.md +74 -0
- package/src/resources/skills/mandu-security/rules/sec-auth-guard.md +127 -0
- package/src/resources/skills/mandu-security/rules/sec-env-management.md +133 -0
- package/src/resources/skills/mandu-security/rules/sec-input-validate.md +148 -0
- package/src/resources/skills/mandu-security/rules/sec-protect-csrf.md +146 -0
- package/src/resources/skills/mandu-security/rules/sec-protect-headers.md +138 -0
- package/src/resources/skills/mandu-slot/SKILL.md +85 -0
- package/src/resources/skills/mandu-slot/metadata.json +12 -0
- package/src/resources/skills/mandu-slot/rules/_sections.md +36 -0
- package/src/resources/skills/mandu-slot/rules/_template.md +63 -0
- package/src/resources/skills/mandu-slot/rules/slot-basic-structure.md +38 -0
- package/src/resources/skills/mandu-slot/rules/slot-ctx-response.md +56 -0
- package/src/resources/skills/mandu-slot/rules/slot-guard-auth.md +59 -0
- package/src/resources/skills/mandu-slot/rules/slot-http-methods.md +64 -0
- package/src/resources/skills/mandu-styling/SKILL.md +118 -0
- package/src/resources/skills/mandu-styling/_sections.md +36 -0
- package/src/resources/skills/mandu-styling/_template.md +32 -0
- package/src/resources/skills/mandu-styling/metadata.json +13 -0
- package/src/resources/skills/mandu-styling/rules/style-component-compound.md +235 -0
- package/src/resources/skills/mandu-styling/rules/style-component-slots.md +255 -0
- package/src/resources/skills/mandu-styling/rules/style-component-tokens.md +205 -0
- package/src/resources/skills/mandu-styling/rules/style-island-animations.md +272 -0
- package/src/resources/skills/mandu-styling/rules/style-island-scoping.md +167 -0
- package/src/resources/skills/mandu-styling/rules/style-island-variants.md +221 -0
- package/src/resources/skills/mandu-styling/rules/style-perf-critical.md +209 -0
- package/src/resources/skills/mandu-styling/rules/style-perf-purge.md +192 -0
- package/src/resources/skills/mandu-styling/rules/style-setup-modules.md +162 -0
- package/src/resources/skills/mandu-styling/rules/style-setup-panda.md +164 -0
- package/src/resources/skills/mandu-styling/rules/style-setup-tailwind.md +161 -0
- package/src/resources/skills/mandu-styling/rules/style-theme-darkmode.md +229 -0
- package/src/resources/skills/mandu-testing/SKILL.md +99 -0
- package/src/resources/skills/mandu-testing/metadata.json +13 -0
- package/src/resources/skills/mandu-testing/rules/_sections.md +26 -0
- package/src/resources/skills/mandu-testing/rules/_template.md +65 -0
- package/src/resources/skills/mandu-testing/rules/test-component-island.md +195 -0
- package/src/resources/skills/mandu-testing/rules/test-e2e-playwright.md +196 -0
- package/src/resources/skills/mandu-testing/rules/test-mock-fetch.md +219 -0
- package/src/resources/skills/mandu-testing/rules/test-slot-unit.md +192 -0
- package/src/resources/skills/mandu-ui/SKILL.md +117 -0
- package/src/resources/skills/mandu-ui/_sections.md +23 -0
- package/src/resources/skills/mandu-ui/_template.md +32 -0
- package/src/resources/skills/mandu-ui/metadata.json +13 -0
- package/src/resources/skills/mandu-ui/rules/ui-accessibility-aria.md +232 -0
- package/src/resources/skills/mandu-ui/rules/ui-accessibility-focus.md +238 -0
- package/src/resources/skills/mandu-ui/rules/ui-composition-patterns.md +259 -0
- package/src/resources/skills/mandu-ui/rules/ui-island-integration.md +258 -0
- package/src/resources/skills/mandu-ui/rules/ui-radix-patterns.md +213 -0
- package/src/resources/skills/mandu-ui/rules/ui-shadcn-setup.md +209 -0
- package/src/resources/skills/recipes.ts +932 -0
- package/src/server.ts +3 -0
- package/src/tools/hydration.ts +8 -8
- package/src/tools/index.ts +1 -0
- package/src/tools/seo.ts +417 -0
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Tailwind CSS Setup
|
|
3
|
+
impact: CRITICAL
|
|
4
|
+
impactDescription: Primary CSS framework for Mandu applications
|
|
5
|
+
tags: styling, tailwind, setup, postcss
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Tailwind CSS Setup
|
|
9
|
+
|
|
10
|
+
**Impact: CRITICAL (Primary CSS framework for Mandu applications)**
|
|
11
|
+
|
|
12
|
+
Tailwind CSS를 Mandu 프로젝트에 설정하세요. Bun과 PostCSS 통합을 포함합니다.
|
|
13
|
+
|
|
14
|
+
**설치:**
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
bun add -d tailwindcss postcss autoprefixer
|
|
18
|
+
bun add clsx tailwind-merge
|
|
19
|
+
bunx tailwindcss init -p
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**tailwind.config.ts:**
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
import type { Config } from "tailwindcss";
|
|
26
|
+
|
|
27
|
+
export default {
|
|
28
|
+
content: [
|
|
29
|
+
"./app/**/*.{ts,tsx}",
|
|
30
|
+
"./components/**/*.{ts,tsx}",
|
|
31
|
+
"./lib/**/*.{ts,tsx}",
|
|
32
|
+
],
|
|
33
|
+
theme: {
|
|
34
|
+
extend: {
|
|
35
|
+
colors: {
|
|
36
|
+
mandu: {
|
|
37
|
+
primary: "hsl(var(--mandu-primary))",
|
|
38
|
+
secondary: "hsl(var(--mandu-secondary))",
|
|
39
|
+
accent: "hsl(var(--mandu-accent))",
|
|
40
|
+
background: "hsl(var(--mandu-background))",
|
|
41
|
+
foreground: "hsl(var(--mandu-foreground))",
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
borderRadius: {
|
|
45
|
+
mandu: "var(--mandu-radius)",
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
plugins: [],
|
|
50
|
+
} satisfies Config;
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
**postcss.config.js:**
|
|
54
|
+
|
|
55
|
+
```javascript
|
|
56
|
+
export default {
|
|
57
|
+
plugins: {
|
|
58
|
+
tailwindcss: {},
|
|
59
|
+
autoprefixer: {},
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**app/globals.css:**
|
|
65
|
+
|
|
66
|
+
```css
|
|
67
|
+
@tailwind base;
|
|
68
|
+
@tailwind components;
|
|
69
|
+
@tailwind utilities;
|
|
70
|
+
|
|
71
|
+
@layer base {
|
|
72
|
+
:root {
|
|
73
|
+
--mandu-primary: 221.2 83.2% 53.3%;
|
|
74
|
+
--mandu-secondary: 210 40% 96.1%;
|
|
75
|
+
--mandu-accent: 210 40% 96.1%;
|
|
76
|
+
--mandu-background: 0 0% 100%;
|
|
77
|
+
--mandu-foreground: 222.2 84% 4.9%;
|
|
78
|
+
--mandu-radius: 0.5rem;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.dark {
|
|
82
|
+
--mandu-primary: 217.2 91.2% 59.8%;
|
|
83
|
+
--mandu-secondary: 217.2 32.6% 17.5%;
|
|
84
|
+
--mandu-accent: 217.2 32.6% 17.5%;
|
|
85
|
+
--mandu-background: 222.2 84% 4.9%;
|
|
86
|
+
--mandu-foreground: 210 40% 98%;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## cn 유틸리티 함수
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
// lib/utils.ts
|
|
95
|
+
import { clsx, type ClassValue } from "clsx";
|
|
96
|
+
import { twMerge } from "tailwind-merge";
|
|
97
|
+
|
|
98
|
+
export function cn(...inputs: ClassValue[]) {
|
|
99
|
+
return twMerge(clsx(inputs));
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## 사용 예시
|
|
104
|
+
|
|
105
|
+
```tsx
|
|
106
|
+
// app/button/client.tsx
|
|
107
|
+
"use client";
|
|
108
|
+
|
|
109
|
+
import { cn } from "@/lib/utils";
|
|
110
|
+
|
|
111
|
+
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
112
|
+
variant?: "default" | "outline" | "ghost";
|
|
113
|
+
size?: "sm" | "md" | "lg";
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function ButtonIsland({
|
|
117
|
+
variant = "default",
|
|
118
|
+
size = "md",
|
|
119
|
+
className,
|
|
120
|
+
...props
|
|
121
|
+
}: ButtonProps) {
|
|
122
|
+
return (
|
|
123
|
+
<button
|
|
124
|
+
className={cn(
|
|
125
|
+
// Base
|
|
126
|
+
"inline-flex items-center justify-center rounded-mandu font-medium transition-colors",
|
|
127
|
+
// Variants
|
|
128
|
+
{
|
|
129
|
+
default: "bg-mandu-primary text-white hover:bg-mandu-primary/90",
|
|
130
|
+
outline: "border border-mandu-primary text-mandu-primary hover:bg-mandu-primary/10",
|
|
131
|
+
ghost: "hover:bg-mandu-secondary",
|
|
132
|
+
}[variant],
|
|
133
|
+
// Sizes
|
|
134
|
+
{
|
|
135
|
+
sm: "h-8 px-3 text-sm",
|
|
136
|
+
md: "h-10 px-4",
|
|
137
|
+
lg: "h-12 px-6 text-lg",
|
|
138
|
+
}[size],
|
|
139
|
+
className
|
|
140
|
+
)}
|
|
141
|
+
{...props}
|
|
142
|
+
/>
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## VSCode 설정
|
|
148
|
+
|
|
149
|
+
```json
|
|
150
|
+
// .vscode/settings.json
|
|
151
|
+
{
|
|
152
|
+
"tailwindCSS.experimental.classRegex": [
|
|
153
|
+
["cn\\(([^)]*)\\)", "'([^']*)'"]
|
|
154
|
+
],
|
|
155
|
+
"editor.quickSuggestions": {
|
|
156
|
+
"strings": true
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Reference: [Tailwind CSS Documentation](https://tailwindcss.com/docs)
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Dark Mode Implementation
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: User preference and accessibility support
|
|
5
|
+
tags: styling, theme, darkmode, accessibility
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Dark Mode Implementation
|
|
9
|
+
|
|
10
|
+
**Impact: MEDIUM (User preference and accessibility support)**
|
|
11
|
+
|
|
12
|
+
다크모드를 구현하여 사용자 선호도와 접근성을 지원하세요.
|
|
13
|
+
|
|
14
|
+
## CSS Variables 기반 테마
|
|
15
|
+
|
|
16
|
+
```css
|
|
17
|
+
/* app/globals.css */
|
|
18
|
+
@layer base {
|
|
19
|
+
:root {
|
|
20
|
+
--background: 0 0% 100%;
|
|
21
|
+
--foreground: 222.2 84% 4.9%;
|
|
22
|
+
--card: 0 0% 100%;
|
|
23
|
+
--card-foreground: 222.2 84% 4.9%;
|
|
24
|
+
--primary: 221.2 83.2% 53.3%;
|
|
25
|
+
--primary-foreground: 210 40% 98%;
|
|
26
|
+
--muted: 210 40% 96.1%;
|
|
27
|
+
--muted-foreground: 215.4 16.3% 46.9%;
|
|
28
|
+
--border: 214.3 31.8% 91.4%;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.dark {
|
|
32
|
+
--background: 222.2 84% 4.9%;
|
|
33
|
+
--foreground: 210 40% 98%;
|
|
34
|
+
--card: 222.2 84% 4.9%;
|
|
35
|
+
--card-foreground: 210 40% 98%;
|
|
36
|
+
--primary: 217.2 91.2% 59.8%;
|
|
37
|
+
--primary-foreground: 222.2 47.4% 11.2%;
|
|
38
|
+
--muted: 217.2 32.6% 17.5%;
|
|
39
|
+
--muted-foreground: 215 20.2% 65.1%;
|
|
40
|
+
--border: 217.2 32.6% 17.5%;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Tailwind dark: 활성화
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
// tailwind.config.ts
|
|
49
|
+
export default {
|
|
50
|
+
darkMode: "class", // 또는 "media"
|
|
51
|
+
// ...
|
|
52
|
+
};
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## 테마 Provider Island
|
|
56
|
+
|
|
57
|
+
```tsx
|
|
58
|
+
// app/theme/client.tsx
|
|
59
|
+
"use client";
|
|
60
|
+
|
|
61
|
+
import { createContext, useContext, useEffect, useState } from "react";
|
|
62
|
+
|
|
63
|
+
type Theme = "light" | "dark" | "system";
|
|
64
|
+
|
|
65
|
+
interface ThemeContextValue {
|
|
66
|
+
theme: Theme;
|
|
67
|
+
setTheme: (theme: Theme) => void;
|
|
68
|
+
resolvedTheme: "light" | "dark";
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const ThemeContext = createContext<ThemeContextValue | null>(null);
|
|
72
|
+
|
|
73
|
+
export function ThemeProviderIsland({ children }: { children: React.ReactNode }) {
|
|
74
|
+
const [theme, setTheme] = useState<Theme>("system");
|
|
75
|
+
const [resolvedTheme, setResolvedTheme] = useState<"light" | "dark">("light");
|
|
76
|
+
|
|
77
|
+
useEffect(() => {
|
|
78
|
+
// 저장된 테마 복원
|
|
79
|
+
const stored = localStorage.getItem("theme") as Theme | null;
|
|
80
|
+
if (stored) {
|
|
81
|
+
setTheme(stored);
|
|
82
|
+
}
|
|
83
|
+
}, []);
|
|
84
|
+
|
|
85
|
+
useEffect(() => {
|
|
86
|
+
const root = document.documentElement;
|
|
87
|
+
|
|
88
|
+
if (theme === "system") {
|
|
89
|
+
const systemDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
90
|
+
setResolvedTheme(systemDark ? "dark" : "light");
|
|
91
|
+
root.classList.toggle("dark", systemDark);
|
|
92
|
+
} else {
|
|
93
|
+
setResolvedTheme(theme);
|
|
94
|
+
root.classList.toggle("dark", theme === "dark");
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
localStorage.setItem("theme", theme);
|
|
98
|
+
}, [theme]);
|
|
99
|
+
|
|
100
|
+
// System 변경 감지
|
|
101
|
+
useEffect(() => {
|
|
102
|
+
if (theme !== "system") return;
|
|
103
|
+
|
|
104
|
+
const mq = window.matchMedia("(prefers-color-scheme: dark)");
|
|
105
|
+
const handler = (e: MediaQueryListEvent) => {
|
|
106
|
+
setResolvedTheme(e.matches ? "dark" : "light");
|
|
107
|
+
document.documentElement.classList.toggle("dark", e.matches);
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
mq.addEventListener("change", handler);
|
|
111
|
+
return () => mq.removeEventListener("change", handler);
|
|
112
|
+
}, [theme]);
|
|
113
|
+
|
|
114
|
+
return (
|
|
115
|
+
<ThemeContext.Provider value={{ theme, setTheme, resolvedTheme }}>
|
|
116
|
+
{children}
|
|
117
|
+
</ThemeContext.Provider>
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function useTheme() {
|
|
122
|
+
const context = useContext(ThemeContext);
|
|
123
|
+
if (!context) {
|
|
124
|
+
throw new Error("useTheme must be used within ThemeProviderIsland");
|
|
125
|
+
}
|
|
126
|
+
return context;
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## 테마 토글 컴포넌트
|
|
131
|
+
|
|
132
|
+
```tsx
|
|
133
|
+
// app/theme/toggle.tsx
|
|
134
|
+
"use client";
|
|
135
|
+
|
|
136
|
+
import { useTheme } from "./client";
|
|
137
|
+
|
|
138
|
+
export function ThemeToggleIsland() {
|
|
139
|
+
const { theme, setTheme, resolvedTheme } = useTheme();
|
|
140
|
+
|
|
141
|
+
return (
|
|
142
|
+
<button
|
|
143
|
+
onClick={() => setTheme(resolvedTheme === "dark" ? "light" : "dark")}
|
|
144
|
+
className="p-2 rounded-md hover:bg-muted"
|
|
145
|
+
aria-label={`Switch to ${resolvedTheme === "dark" ? "light" : "dark"} mode`}
|
|
146
|
+
>
|
|
147
|
+
{resolvedTheme === "dark" ? "🌙" : "☀️"}
|
|
148
|
+
</button>
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## 플래시 방지 스크립트
|
|
154
|
+
|
|
155
|
+
```tsx
|
|
156
|
+
// app/layout.tsx
|
|
157
|
+
export default function RootLayout({ children }) {
|
|
158
|
+
return (
|
|
159
|
+
<html lang="ko" suppressHydrationWarning>
|
|
160
|
+
<head>
|
|
161
|
+
{/* 다크모드 플래시 방지 */}
|
|
162
|
+
<script
|
|
163
|
+
dangerouslySetInnerHTML={{
|
|
164
|
+
__html: `
|
|
165
|
+
(function() {
|
|
166
|
+
const theme = localStorage.getItem('theme');
|
|
167
|
+
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
168
|
+
|
|
169
|
+
if (theme === 'dark' || (!theme && prefersDark)) {
|
|
170
|
+
document.documentElement.classList.add('dark');
|
|
171
|
+
}
|
|
172
|
+
})();
|
|
173
|
+
`,
|
|
174
|
+
}}
|
|
175
|
+
/>
|
|
176
|
+
</head>
|
|
177
|
+
<body>
|
|
178
|
+
<ThemeProviderIsland>
|
|
179
|
+
{children}
|
|
180
|
+
</ThemeProviderIsland>
|
|
181
|
+
</body>
|
|
182
|
+
</html>
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## Island 간 테마 동기화
|
|
188
|
+
|
|
189
|
+
```tsx
|
|
190
|
+
// useIslandEvent로 테마 변경 전파
|
|
191
|
+
import { useIslandEvent } from "@mandujs/core/client";
|
|
192
|
+
|
|
193
|
+
export function ThemeProviderIsland({ children }) {
|
|
194
|
+
const { emit } = useIslandEvent<{ theme: string }>("theme-change");
|
|
195
|
+
const [theme, setTheme] = useState("system");
|
|
196
|
+
|
|
197
|
+
const handleSetTheme = (newTheme: Theme) => {
|
|
198
|
+
setTheme(newTheme);
|
|
199
|
+
emit({ theme: newTheme }); // 다른 Island에 전파
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
// ...
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// 다른 Island에서 수신
|
|
206
|
+
export function AnotherIsland() {
|
|
207
|
+
useIslandEvent<{ theme: string }>("theme-change", (data) => {
|
|
208
|
+
console.log("Theme changed to:", data.theme);
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## 다크모드 특화 스타일
|
|
214
|
+
|
|
215
|
+
```tsx
|
|
216
|
+
// Tailwind dark: 접두사 사용
|
|
217
|
+
<div className="bg-white dark:bg-gray-900">
|
|
218
|
+
<p className="text-gray-900 dark:text-gray-100">
|
|
219
|
+
Content
|
|
220
|
+
</p>
|
|
221
|
+
</div>
|
|
222
|
+
|
|
223
|
+
// 복잡한 경우 CSS Variables 활용
|
|
224
|
+
<div className="bg-[hsl(var(--background))]">
|
|
225
|
+
{/* 자동으로 테마 반영 */}
|
|
226
|
+
</div>
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Reference: [Tailwind Dark Mode](https://tailwindcss.com/docs/dark-mode)
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: mandu-testing
|
|
3
|
+
description: |
|
|
4
|
+
Testing patterns for Mandu applications. Use when writing unit tests,
|
|
5
|
+
integration tests, or E2E tests. Triggers on test, spec, Bun test,
|
|
6
|
+
Playwright, or testing tasks.
|
|
7
|
+
license: MIT
|
|
8
|
+
metadata:
|
|
9
|
+
author: mandu
|
|
10
|
+
version: "1.0.0"
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# Mandu Testing
|
|
14
|
+
|
|
15
|
+
Mandu 애플리케이션의 테스트 패턴 가이드. Bun test를 활용한 단위 테스트, slot 테스트, Island 컴포넌트 테스트, Playwright E2E 테스트를 다룹니다.
|
|
16
|
+
|
|
17
|
+
## When to Apply
|
|
18
|
+
|
|
19
|
+
Reference these guidelines when:
|
|
20
|
+
- Writing unit tests for slots
|
|
21
|
+
- Testing Island components
|
|
22
|
+
- Setting up E2E tests with Playwright
|
|
23
|
+
- Mocking external dependencies
|
|
24
|
+
- Testing authentication flows
|
|
25
|
+
|
|
26
|
+
## Rule Categories by Priority
|
|
27
|
+
|
|
28
|
+
| Priority | Category | Impact | Prefix |
|
|
29
|
+
|----------|----------|--------|--------|
|
|
30
|
+
| 1 | Slot Testing | HIGH | `test-slot-` |
|
|
31
|
+
| 2 | Component Testing | HIGH | `test-component-` |
|
|
32
|
+
| 3 | E2E Testing | MEDIUM | `test-e2e-` |
|
|
33
|
+
| 4 | Mocking | MEDIUM | `test-mock-` |
|
|
34
|
+
|
|
35
|
+
## Quick Reference
|
|
36
|
+
|
|
37
|
+
### 1. Slot Testing (HIGH)
|
|
38
|
+
|
|
39
|
+
- `test-slot-unit` - Unit test slot handlers
|
|
40
|
+
- `test-slot-guard` - Test guard authentication
|
|
41
|
+
- `test-slot-integration` - Integration test with database
|
|
42
|
+
|
|
43
|
+
### 2. Component Testing (HIGH)
|
|
44
|
+
|
|
45
|
+
- `test-component-island` - Test Island components
|
|
46
|
+
- `test-component-render` - Test rendering output
|
|
47
|
+
- `test-component-interaction` - Test user interactions
|
|
48
|
+
|
|
49
|
+
### 3. E2E Testing (MEDIUM)
|
|
50
|
+
|
|
51
|
+
- `test-e2e-playwright` - Playwright setup and patterns
|
|
52
|
+
- `test-e2e-auth` - Test authentication flows
|
|
53
|
+
- `test-e2e-navigation` - Test page navigation
|
|
54
|
+
|
|
55
|
+
### 4. Mocking (MEDIUM)
|
|
56
|
+
|
|
57
|
+
- `test-mock-fetch` - Mock fetch requests
|
|
58
|
+
- `test-mock-database` - Mock database operations
|
|
59
|
+
|
|
60
|
+
## Bun Test Quick Start
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
# Run all tests
|
|
64
|
+
bun test
|
|
65
|
+
|
|
66
|
+
# Run specific test file
|
|
67
|
+
bun test src/slots/user.test.ts
|
|
68
|
+
|
|
69
|
+
# Watch mode
|
|
70
|
+
bun test --watch
|
|
71
|
+
|
|
72
|
+
# Coverage
|
|
73
|
+
bun test --coverage
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Test File Convention
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
spec/slots/
|
|
80
|
+
├── users.slot.ts
|
|
81
|
+
├── users.slot.test.ts # Slot tests
|
|
82
|
+
app/
|
|
83
|
+
├── dashboard/
|
|
84
|
+
│ ├── client.tsx
|
|
85
|
+
│ └── client.test.tsx # Component tests
|
|
86
|
+
tests/
|
|
87
|
+
└── e2e/
|
|
88
|
+
└── auth.spec.ts # E2E tests
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## How to Use
|
|
92
|
+
|
|
93
|
+
Read individual rule files for detailed explanations:
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
rules/test-slot-unit.md
|
|
97
|
+
rules/test-component-island.md
|
|
98
|
+
rules/test-e2e-playwright.md
|
|
99
|
+
```
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "1.0.0",
|
|
3
|
+
"organization": "Mandu Framework",
|
|
4
|
+
"date": "February 2026",
|
|
5
|
+
"abstract": "Mandu 애플리케이션 테스트 패턴 가이드. Bun test를 활용한 slot 단위/통합 테스트, Island 컴포넌트 테스트, Playwright E2E 테스트, 모킹 패턴을 다룹니다.",
|
|
6
|
+
"references": [
|
|
7
|
+
"https://bun.sh/docs/cli/test",
|
|
8
|
+
"https://playwright.dev/docs/intro",
|
|
9
|
+
"https://testing-library.com/docs/react-testing-library/intro",
|
|
10
|
+
"https://kentcdodds.com/blog/write-tests"
|
|
11
|
+
],
|
|
12
|
+
"tags": ["testing", "bun-test", "playwright", "e2e", "unit", "mandu"]
|
|
13
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Sections
|
|
2
|
+
|
|
3
|
+
This file defines all sections, their ordering, impact levels, and descriptions.
|
|
4
|
+
The section ID (in parentheses) is the filename prefix used to group rules.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 1. Slot Testing (test-slot)
|
|
9
|
+
|
|
10
|
+
**Impact:** HIGH
|
|
11
|
+
**Description:** slot 핸들러의 단위 테스트와 통합 테스트. API 로직의 정확성 검증에 필수입니다.
|
|
12
|
+
|
|
13
|
+
## 2. Component Testing (test-component)
|
|
14
|
+
|
|
15
|
+
**Impact:** HIGH
|
|
16
|
+
**Description:** Island 컴포넌트의 렌더링과 인터랙션 테스트. UI 로직의 정확성 검증입니다.
|
|
17
|
+
|
|
18
|
+
## 3. E2E Testing (test-e2e)
|
|
19
|
+
|
|
20
|
+
**Impact:** MEDIUM
|
|
21
|
+
**Description:** Playwright를 사용한 End-to-End 테스트. 전체 사용자 플로우 검증입니다.
|
|
22
|
+
|
|
23
|
+
## 4. Mocking (test-mock)
|
|
24
|
+
|
|
25
|
+
**Impact:** MEDIUM
|
|
26
|
+
**Description:** 외부 의존성 모킹. 테스트 격리와 속도 향상을 위한 기법입니다.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Rule Template
|
|
2
|
+
|
|
3
|
+
Use this template when creating new rules for mandu-testing.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
```markdown
|
|
8
|
+
---
|
|
9
|
+
title: Rule Title Here
|
|
10
|
+
impact: HIGH | MEDIUM | LOW
|
|
11
|
+
impactDescription: 영향 설명 (예: "Ensures API correctness")
|
|
12
|
+
tags: testing, tag1, tag2
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Rule Title Here
|
|
16
|
+
|
|
17
|
+
**Impact: {LEVEL} ({impactDescription})**
|
|
18
|
+
|
|
19
|
+
테스트 규칙의 목적과 중요성을 설명합니다.
|
|
20
|
+
|
|
21
|
+
**Test Example:**
|
|
22
|
+
|
|
23
|
+
\`\`\`typescript
|
|
24
|
+
import { describe, it, expect } from "bun:test";
|
|
25
|
+
import userSlot from "./user.slot";
|
|
26
|
+
|
|
27
|
+
describe("User Slot", () => {
|
|
28
|
+
it("should return user by id", async () => {
|
|
29
|
+
const ctx = createMockContext({
|
|
30
|
+
params: { id: "1" },
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const response = await userSlot.handlers.get(ctx);
|
|
34
|
+
|
|
35
|
+
expect(response.status).toBe(200);
|
|
36
|
+
expect(response.body.user.id).toBe("1");
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
\`\`\`
|
|
40
|
+
|
|
41
|
+
## Test Structure
|
|
42
|
+
|
|
43
|
+
테스트 구조와 네이밍 컨벤션을 설명합니다.
|
|
44
|
+
|
|
45
|
+
## Common Patterns
|
|
46
|
+
|
|
47
|
+
자주 사용되는 테스트 패턴을 설명합니다.
|
|
48
|
+
|
|
49
|
+
Reference: [Bun Test Docs](https://bun.sh/docs/cli/test)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Naming Convention
|
|
55
|
+
|
|
56
|
+
- 파일명: `test-{category}-{rule-name}.md`
|
|
57
|
+
- 예시: `test-slot-unit.md`, `test-e2e-playwright.md`
|
|
58
|
+
|
|
59
|
+
## Test File Location
|
|
60
|
+
|
|
61
|
+
| Type | Location | Pattern |
|
|
62
|
+
|------|----------|---------|
|
|
63
|
+
| Slot Unit | `spec/slots/*.test.ts` | Co-located |
|
|
64
|
+
| Component | `app/**/*.test.tsx` | Co-located |
|
|
65
|
+
| E2E | `tests/e2e/*.spec.ts` | Centralized |
|