Skip to content

Commit cd31f9c

Browse files
committed
chore(skills): fix emcn design review with verified codebase patterns
1 parent 6686a6d commit cd31f9c

3 files changed

Lines changed: 143 additions & 165 deletions

File tree

.agents/skills/emcn-design-review/SKILL.md

Lines changed: 71 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ import { Button } from '@/components/emcn/components/button/button'
4242

4343
## Design Tokens (CSS Variables)
4444

45-
Never use raw color values. Always use CSS variable tokens via Tailwind arbitrary values.
45+
Never use raw color values. Always use CSS variable tokens via Tailwind arbitrary values: `text-[var(--text-primary)]`, not `text-gray-500` or `#333`. The CSS variable pattern is canonical (1,700+ uses) — do not use Tailwind semantic classes like `text-muted-foreground`.
4646

4747
### Text hierarchy
4848
| Token | Use |
@@ -99,16 +99,18 @@ Use z-index tokens for layering:
9999
### Buttons
100100
Available variants: `default`, `primary`, `destructive`, `ghost`, `outline`, `active`, `secondary`, `tertiary`, `subtle`, `ghost-secondary`, `3d`
101101

102-
| Action type | Variant |
103-
|-------------|---------|
104-
| Primary action (create, save, submit) | `primary` |
105-
| Cancel, close, secondary action | `default` |
106-
| Delete, remove, destructive action | `destructive` |
107-
| Toolbar/icon-only button | `ghost` |
108-
| Toggle, mode switch | `ghost` or `outline` |
109-
| Active/selected state | `active` |
102+
| Action type | Variant | Frequency |
103+
|-------------|---------|-----------|
104+
| Toolbar, icon-only, utility actions | `ghost` | Most common (28%) |
105+
| Primary action (create, save, submit) | `primary` | Very common (24%) |
106+
| Cancel, close, secondary action | `default` | Common |
107+
| Delete, remove, destructive action | `destructive` | Targeted use only |
108+
| Active/selected state | `active` | Targeted use only |
109+
| Toggle, mode switch | `outline` | Moderate |
110110

111-
Sizes: `sm` (compact) or `md` (default). Never create custom button styles — use an existing variant.
111+
Sizes: `sm` (compact, 32% of buttons) or `md` (default, used when no size specified). Never create custom button styles — use an existing variant.
112+
113+
Buttons without an explicit variant prop get `default` styling. This is acceptable for cancel/secondary actions.
112114

113115
### Modals (Dialogs)
114116
Use `Modal` + subcomponents. Never build custom dialog overlays.
@@ -126,12 +128,12 @@ Use `Modal` + subcomponents. Never build custom dialog overlays.
126128
</Modal>
127129
```
128130

129-
Modal sizes: `sm` (440px, confirmations), `md` (500px, forms), `lg` (600px, content-heavy), `xl` (800px, complex editors), `full` (1200px, dashboards).
131+
Modal sizes by frequency: `sm` (440px, most common — confirmations and simple dialogs), `md` (500px, forms), `lg` (600px, content-heavy), `xl` (800px, rare), `full` (1200px, rare).
130132

131-
Footer buttons: Cancel on left, primary action on right.
133+
Footer buttons: Cancel on left (`variant="default"`), primary action on right. This pattern is followed 100% across the codebase.
132134

133-
### Delete Confirmations
134-
Always use Modal with this pattern:
135+
### Delete/Remove Confirmations
136+
Always use Modal with `size="sm"`. The established pattern:
135137

136138
```tsx
137139
<Modal open={open} onOpenChange={setOpen}>
@@ -140,7 +142,6 @@ Always use Modal with this pattern:
140142
<ModalBody>
141143
<p>Description of consequences</p>
142144
<p className="text-[var(--text-error)]">Warning about irreversibility</p>
143-
{/* Optional: confirmation text input for high-risk actions */}
144145
</ModalBody>
145146
<ModalFooter>
146147
<Button variant="default" onClick={() => setOpen(false)}>Cancel</Button>
@@ -153,15 +154,17 @@ Always use Modal with this pattern:
153154
```
154155

155156
Rules:
156-
- Title: "Delete {ItemType}"
157+
- Title: "Delete {ItemType}" or "Remove {ItemType}" (use "Remove" for membership/association changes)
157158
- Include consequence description
158-
- Use `text-[var(--text-error)]` for warning text
159-
- Destructive button on the right
159+
- Use `text-[var(--text-error)]` for warning text when the action is irreversible
160+
- `variant="destructive"` for the action button (100% compliance)
161+
- `variant="default"` for cancel (100% compliance)
162+
- Cancel left, destructive right (100% compliance)
160163
- For high-risk deletes (workspaces), require typing the name to confirm
161164
- Include recovery info if soft-delete: "You can restore it from Recently Deleted in Settings"
162165

163166
### Toast Notifications
164-
Use the imperative `toast` API. Never build custom notification UI.
167+
Use the imperative `toast` API from `@/components/emcn`. Never build custom notification UI.
165168

166169
```tsx
167170
import { toast } from '@/components/emcn'
@@ -171,21 +174,26 @@ toast.error('Something went wrong')
171174
toast.success('Deleted', { action: { label: 'Undo', onClick: handleUndo } })
172175
```
173176

174-
Variants: `default`, `success`, `error`. Auto-dismiss after 5s.
177+
Variants: `default`, `success`, `error`. Auto-dismiss after 5s. Supports optional action buttons with callbacks.
175178

176179
### Badges
177180
Use semantic color variants for status:
178181

179-
| Status | Variant |
180-
|--------|---------|
181-
| Success, active, online | `green` |
182-
| Error, failed, offline | `red` |
183-
| Warning, processing | `amber` or `orange` |
184-
| Info, default | `blue` |
185-
| Neutral, draft, inactive | `gray` |
186-
| Type labels | `type` |
187-
188-
Use `dot` prop for status indicators. Use `icon` prop for icon badges.
182+
| Status | Variant | Usage |
183+
|--------|---------|-------|
184+
| Error, failed, disconnected | `red` | Most common (15 uses) |
185+
| Metadata, roles, auth types, scopes | `gray-secondary` | Very common (12 uses) |
186+
| Type annotations (TS types, field types) | `type` | Very common (12 uses) |
187+
| Success, active, enabled, running | `green` | Common (7 uses) |
188+
| Neutral, default, unknown | `gray` | Common (6 uses) |
189+
| Outline, parameters, public | `outline` | Moderate (6 uses) |
190+
| Warning, processing | `amber` | Moderate (5 uses) |
191+
| Paused, warning | `orange` | Occasional |
192+
| Info, queued | `blue` | Occasional |
193+
| Data types (arrays) | `purple` | Occasional |
194+
| Generic with border | `default` | Occasional |
195+
196+
Use `dot` prop for status indicators (19 instances in codebase). `icon` prop is available but rarely used.
189197

190198
### Tooltips
191199
Use `Tooltip` from emcn with namespace pattern:
@@ -226,7 +234,7 @@ Use for context menus and action menus:
226234
<DropdownMenu>
227235
<DropdownMenuTrigger asChild>
228236
<Button variant="ghost">
229-
<MoreHorizontal className="h-4 w-4" />
237+
<MoreHorizontal className="h-[14px] w-[14px]" />
230238
</Button>
231239
</DropdownMenuTrigger>
232240
<DropdownMenuContent align="end">
@@ -251,7 +259,7 @@ Use `FormField` wrapper for labeled inputs:
251259
```
252260

253261
Rules:
254-
- Use `Input` from emcn, never raw `<input>`
262+
- Use `Input` from emcn, never raw `<input>` (exception: hidden file inputs)
255263
- Use `Textarea` from emcn, never raw `<textarea>`
256264
- Use `FormField` for label + input + error layout
257265
- Mark optional fields with `optional` prop
@@ -273,43 +281,55 @@ Rules:
273281
- Stack multiple skeletons for lists
274282

275283
### Icons
276-
Standard sizing pattern:
284+
Standard sizing `h-[14px] w-[14px]` is the dominant pattern (400+ uses):
277285

278286
```tsx
279287
<Icon className="h-[14px] w-[14px] text-[var(--text-icon)]" />
280288
```
281289

282-
Common sizes: `h-3 w-3` (12px), `h-[14px] w-[14px]`, `h-4 w-4` (16px).
290+
Size scale by frequency:
291+
1. `h-[14px] w-[14px]` — default for inline icons (most common)
292+
2. `h-[16px] w-[16px]` — slightly larger inline icons
293+
3. `h-3 w-3` (12px) — compact/tight spaces
294+
4. `h-4 w-4` (16px) — Tailwind equivalent, also common
295+
5. `h-3.5 w-3.5` (14px) — Tailwind equivalent of 14px
296+
6. `h-5 w-5` (20px) — larger icons, section headers
297+
298+
Use `text-[var(--text-icon)]` for icon color (113+ uses in codebase).
283299

284300
---
285301

286302
## Styling Rules
287303

288-
1. **Use `cn()` for conditional classes**: `cn('base', condition && 'conditional')`
289-
2. **Never use inline styles**: Use Tailwind classes exclusively
290-
3. **Never hardcode colors**: Use CSS variable tokens (`var(--text-primary)`, not `#333`)
291-
4. **Never use global styles**: Keep all styling local to components
292-
5. **Hover states**: Use `hover-hover:` pseudo-class for hover-capable devices
293-
6. **Transitions**: Use `transition-colors` for color changes, `transition-colors duration-100` for fast hover
294-
7. **Border radius**: `rounded-lg` (large cards), `rounded-md` (medium), `rounded-sm` (small), `rounded-xs` (tiny)
295-
8. **Typography**: Use semantic sizes — `text-small` (13px), `text-caption` (12px), `text-xs` (11px), `text-micro` (10px)
296-
9. **Font weight**: Use `font-medium` for emphasis, avoid `font-bold` unless for headings
297-
10. **Spacing**: Use Tailwind gap/padding utilities. Common patterns: `gap-2`, `gap-3`, `px-4 py-2.5`
304+
1. **Use `cn()` for conditional classes**: `cn('base', condition && 'conditional')` — never template literal concatenation like `` `base ${condition ? 'active' : ''}` ``
305+
2. **Inline styles**: Avoid. Exception: dynamic values that can't be expressed as Tailwind classes (e.g., `style={{ width: dynamicVar }}` or CSS variable references). Never use inline styles for colors or static values.
306+
3. **Never hardcode colors**: Use CSS variable tokens. Never `text-gray-500`, `bg-red-100`, `#fff`, or `rgb()`. Always `text-[var(--text-*)]`, `bg-[var(--surface-*)]`, etc.
307+
4. **Never use Tailwind semantic color classes**: Use `text-[var(--text-muted)]` not `text-muted-foreground`. The CSS variable pattern is canonical.
308+
5. **Never use global styles**: Keep all styling local to components
309+
6. **Hover states**: Use `hover-hover:` pseudo-class for hover-capable devices
310+
7. **Transitions**: Use `transition-colors` for color changes, `transition-colors duration-100` for fast hover
311+
8. **Border radius**: `rounded-lg` (large cards), `rounded-md` (medium), `rounded-sm` (small), `rounded-xs` (tiny)
312+
9. **Typography**: Use semantic sizes — `text-small` (13px), `text-caption` (12px), `text-xs` (11px), `text-micro` (10px)
313+
10. **Font weight**: Use `font-medium` for emphasis, avoid `font-bold` unless for headings
314+
11. **Spacing**: Use Tailwind gap/padding utilities. Common patterns: `gap-2`, `gap-3`, `px-4 py-2.5`
298315

299316
---
300317

301318
## Anti-patterns to flag
302319

303-
- Raw HTML elements (`<button>`, `<input>`, `<dialog>`) instead of emcn components
304-
- Hardcoded color values (`#fff`, `rgb(0,0,0)`, `text-gray-500`)
320+
- Raw HTML `<button>` instead of Button component (exception: inside Radix primitives)
321+
- Raw HTML `<input>` instead of Input component (exception: hidden file inputs, read-only checkboxes in markdown)
322+
- Hardcoded Tailwind default colors (`text-gray-*`, `bg-red-*`, `text-blue-*`)
323+
- Hex values in className (`bg-[#fff]`, `text-[#333]`)
324+
- Tailwind semantic classes (`text-muted-foreground`) instead of CSS variables (`text-[var(--text-muted)]`)
305325
- Custom modal/dialog implementations instead of `Modal`
306326
- Custom toast/notification implementations instead of `toast`
307-
- Inline styles (`style={{ color: 'red' }}`)
308-
- Missing `cn()` for conditional classes (string concatenation instead)
327+
- Inline styles for colors or static values (dynamic values are acceptable)
328+
- Template literal className concatenation instead of `cn()`
309329
- Wrong button variant for the action type
310330
- Missing loading/skeleton states
311331
- Missing error states on forms
312-
- Using `@/components/ui/button` when `@/components/emcn` Button should be used
313-
- Importing from emcn subpaths instead of barrel
314-
- Using `z-50`, `z-[9999]` instead of z-index tokens
332+
- Importing from emcn subpaths instead of barrel export
333+
- Using arbitrary z-index (`z-50`, `z-[9999]`) instead of z-index tokens
315334
- Custom shadows instead of shadow tokens
335+
- Icon sizes that don't follow the established scale (default to `h-[14px] w-[14px]`)

.claude/commands/emcn-design-review.md

Lines changed: 36 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -13,88 +13,67 @@ User arguments: $ARGUMENTS
1313

1414
## Context
1515

16-
This codebase uses **emcn**, a custom component library built on Radix UI primitives with CVA (class-variance-authority) variants and CSS variable design tokens. All UI must use emcn components and tokens — never raw HTML elements or hardcoded colors.
16+
This codebase uses **emcn**, a custom component library built on Radix UI primitives with CVA variants and CSS variable design tokens. All UI must use emcn components and tokens.
1717

1818
## Steps
1919

2020
1. Read the emcn barrel export at `apps/sim/components/emcn/components/index.ts` to know what's available
21-
2. Read `apps/sim/app/_styles/globals.css` for the full set of CSS variable tokens
21+
2. Read `apps/sim/app/_styles/globals.css` for CSS variable tokens
2222
3. Analyze the specified scope against every rule below
2323
4. If fix=true, apply the fixes. If fix=false, propose the fixes without applying.
2424

2525
---
2626

2727
## Imports
2828

29-
- Import components from `@/components/emcn`, never from subpaths
30-
- Import icons from `@/components/emcn/icons` or `lucide-react`
31-
- Import `cn` from `@/lib/core/utils/cn` for conditional class merging
32-
- Import app-specific wrappers (Select, VerifiedBadge) from `@/components/ui`
29+
- Import from `@/components/emcn` barrel, never subpaths
30+
- Icons from `@/components/emcn/icons` or `lucide-react`
31+
- Use `cn` from `@/lib/core/utils/cn` for conditional classes
3332

34-
---
35-
36-
## Design Tokens (CSS Variables)
37-
38-
Never use raw color values. Always use CSS variable tokens via Tailwind arbitrary values.
39-
40-
### Text hierarchy
41-
- `text-[var(--text-primary)]` — Main content text
42-
- `text-[var(--text-secondary)]` — Secondary/supporting text
43-
- `text-[var(--text-tertiary)]` — Tertiary text
44-
- `text-[var(--text-muted)]` — Disabled, placeholder text
45-
- `text-[var(--text-icon)]` — Icon tinting
46-
- `text-[var(--text-error)]` — Error/warning messages
47-
48-
### Surfaces
49-
- `bg-[var(--bg)]` — Page background
50-
- `bg-[var(--surface-2)]` through `bg-[var(--surface-7)]` — Increasing elevation
51-
- `bg-[var(--surface-hover)]` — Hover backgrounds
52-
- `bg-[var(--surface-active)]` — Active/selected backgrounds
53-
54-
### Borders
55-
- `border-[var(--border)]` — Default borders
56-
- `border-[var(--border-1)]` — Stronger borders (inputs, cards)
33+
## Design Tokens
5734

58-
### Z-Index & Shadows
59-
- Use z-index tokens: `--z-dropdown` (100), `--z-modal` (200), `--z-popover` (300), `--z-tooltip` (400), `--z-toast` (500)
60-
- Use shadow tokens: `shadow-subtle`, `shadow-medium`, `shadow-overlay`, `shadow-card`
35+
Use CSS variable pattern (`text-[var(--text-primary)]`), never Tailwind semantics (`text-muted-foreground`) or hardcoded colors (`text-gray-500`, `#333`).
6136

62-
---
37+
**Text**: `--text-primary`, `--text-secondary`, `--text-tertiary`, `--text-muted`, `--text-icon`, `--text-inverse`, `--text-error`
38+
**Surfaces**: `--bg`, `--surface-2` through `--surface-7`, `--surface-hover`, `--surface-active`
39+
**Borders**: `--border`, `--border-1`, `--border-muted`
40+
**Z-Index**: `--z-dropdown` (100), `--z-modal` (200), `--z-popover` (300), `--z-tooltip` (400), `--z-toast` (500)
41+
**Shadows**: `shadow-subtle`, `shadow-medium`, `shadow-overlay`, `shadow-card`
6342

64-
## Component Usage Rules
43+
## Buttons
6544

66-
### Buttons
67-
| Action type | Variant |
68-
|-------------|---------|
69-
| Primary action (create, save) | `primary` |
45+
| Action | Variant |
46+
|--------|---------|
47+
| Toolbar, icon-only | `ghost` (most common, 28%) |
48+
| Create, save, submit | `primary` (24%) |
7049
| Cancel, close | `default` |
7150
| Delete, remove | `destructive` |
72-
| Toolbar/icon-only | `ghost` |
51+
| Selected state | `active` |
52+
| Toggle | `outline` |
7353

74-
### Delete Confirmations
75-
Always Modal with: title "Delete {ItemType}", consequence text, `text-[var(--text-error)]` warning, Cancel (default) + Delete (destructive) buttons.
54+
## Delete/Remove Confirmations
7655

77-
### Toast Notifications
78-
Use imperative API: `toast.success(msg)`, `toast.error(msg)`. Never build custom notification UI.
56+
Modal `size="sm"`, title "Delete/Remove {ItemType}", `variant="destructive"` action button, `variant="default"` cancel. Cancel left, action right (100% compliance). Use `text-[var(--text-error)]` for irreversible warnings.
7957

80-
### Badges
81-
Green=success, red=error, amber=warning, blue=info, gray=neutral. Use `dot` prop for status indicators.
58+
## Toast
8259

83-
### Forms
84-
Use `FormField` + `Input`/`Textarea` from emcn. Never raw HTML form elements.
60+
`toast.success()`, `toast.error()`, `toast()` from `@/components/emcn`. Never custom notification UI.
8561

86-
### Loading States
87-
Use `Skeleton` matching the actual UI structure dimensions.
62+
## Badges
8863

89-
---
64+
`red`=error/failed, `gray-secondary`=metadata/roles, `type`=type annotations, `green`=success/active, `gray`=neutral, `amber`=processing, `orange`=paused, `blue`=info. Use `dot` prop for status indicators.
65+
66+
## Icons
67+
68+
Default: `h-[14px] w-[14px]` (400+ uses). Color: `text-[var(--text-icon)]`. Scale: 14px > 16px > 12px > 20px.
9069

9170
## Anti-patterns to flag
9271

93-
- Raw HTML elements instead of emcn components
94-
- Hardcoded colors instead of CSS variable tokens
95-
- Custom modal/toast implementations
96-
- Inline styles
97-
- Missing `cn()` for conditional classes
98-
- Wrong button variant for action type
72+
- Raw `<button>`/`<input>` instead of emcn components
73+
- Hardcoded colors (`text-gray-*`, `#hex`, `rgb()`)
74+
- Tailwind semantics (`text-muted-foreground`) instead of CSS variables
75+
- Template literal className instead of `cn()`
76+
- Inline styles for colors/static values (dynamic values OK)
9977
- Importing from emcn subpaths instead of barrel
100-
- Using arbitrary z-index instead of tokens
78+
- Arbitrary z-index instead of tokens
79+
- Wrong button variant for action type

0 commit comments

Comments
 (0)