@wangs-ui/skills 1.3.0-alpha.20 → 1.3.0-alpha.22

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 (56) hide show
  1. package/dist/bin.js +2 -2
  2. package/dist/index.js +1 -1
  3. package/dist/rules/wangs-ui/default-props-precedence.md +0 -1
  4. package/dist/rules/wangs-ui/forms-destructuring-lock.md +0 -1
  5. package/dist/rules/wangs-ui/no-raw-html.md +0 -1
  6. package/dist/rules/wangs-ui/no-redundant-wrappers.md +0 -1
  7. package/dist/rules/wangs-ui/react19-checklist-and-reference.md +0 -1
  8. package/dist/rules/wangs-ui/react19-compiler-render-patterns.md +0 -1
  9. package/dist/rules/wangs-ui/react19-naming-conventions.md +0 -1
  10. package/dist/rules/wangs-ui/react19-no-manual-memoization.md +0 -1
  11. package/dist/rules/wangs-ui/react19-primitives-typing.md +0 -1
  12. package/dist/rules/wangs-ui/react19-props-typing.md +0 -1
  13. package/dist/rules/wangs-ui/react19-purity-and-immutability.md +0 -1
  14. package/dist/rules/wangs-ui/react19-tooling-and-opt-out.md +0 -1
  15. package/dist/rules/wangs-ui/theme-variable-override-breaks-engine.md +0 -1
  16. package/dist/rules/wangs-ui/typescript-assertions-last-resort.md +0 -1
  17. package/dist/rules/wangs-ui/typescript-checklist-and-reference.md +0 -1
  18. package/dist/rules/wangs-ui/typescript-discriminated-unions.md +0 -1
  19. package/dist/rules/wangs-ui/typescript-explicit-return-types.md +0 -1
  20. package/dist/rules/wangs-ui/typescript-interface-vs-type.md +0 -1
  21. package/dist/rules/wangs-ui/typescript-literal-unions-vs-enums.md +0 -1
  22. package/dist/rules/wangs-ui/typescript-naming-conventions.md +0 -1
  23. package/dist/rules/wangs-ui/typescript-narrowing-over-casting.md +0 -1
  24. package/dist/rules/wangs-ui/typescript-readonly-by-default.md +0 -1
  25. package/dist/rules/wangs-ui/typescript-tsconfig-strictness.md +0 -1
  26. package/dist/rules/wangs-ui/typescript-zero-any.md +0 -1
  27. package/dist/skills/wangs-ui/craft-theme/SKILL.md +1 -1
  28. package/dist/skills/wangs-ui/custom-color-family/SKILL.md +122 -0
  29. package/dist/{src-C0l8elLu.js → src-CWGSuYNm.js} +33 -29
  30. package/package.json +1 -1
  31. package/rules/wangs-ui/default-props-precedence.md +0 -1
  32. package/rules/wangs-ui/forms-destructuring-lock.md +0 -1
  33. package/rules/wangs-ui/no-raw-html.md +0 -1
  34. package/rules/wangs-ui/no-redundant-wrappers.md +0 -1
  35. package/rules/wangs-ui/react19-checklist-and-reference.md +0 -1
  36. package/rules/wangs-ui/react19-compiler-render-patterns.md +0 -1
  37. package/rules/wangs-ui/react19-naming-conventions.md +0 -1
  38. package/rules/wangs-ui/react19-no-manual-memoization.md +0 -1
  39. package/rules/wangs-ui/react19-primitives-typing.md +0 -1
  40. package/rules/wangs-ui/react19-props-typing.md +0 -1
  41. package/rules/wangs-ui/react19-purity-and-immutability.md +0 -1
  42. package/rules/wangs-ui/react19-tooling-and-opt-out.md +0 -1
  43. package/rules/wangs-ui/theme-variable-override-breaks-engine.md +0 -1
  44. package/rules/wangs-ui/typescript-assertions-last-resort.md +0 -1
  45. package/rules/wangs-ui/typescript-checklist-and-reference.md +0 -1
  46. package/rules/wangs-ui/typescript-discriminated-unions.md +0 -1
  47. package/rules/wangs-ui/typescript-explicit-return-types.md +0 -1
  48. package/rules/wangs-ui/typescript-interface-vs-type.md +0 -1
  49. package/rules/wangs-ui/typescript-literal-unions-vs-enums.md +0 -1
  50. package/rules/wangs-ui/typescript-naming-conventions.md +0 -1
  51. package/rules/wangs-ui/typescript-narrowing-over-casting.md +0 -1
  52. package/rules/wangs-ui/typescript-readonly-by-default.md +0 -1
  53. package/rules/wangs-ui/typescript-tsconfig-strictness.md +0 -1
  54. package/rules/wangs-ui/typescript-zero-any.md +0 -1
  55. package/skills/wangs-ui/craft-theme/SKILL.md +1 -1
  56. package/skills/wangs-ui/custom-color-family/SKILL.md +122 -0
package/dist/bin.js CHANGED
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
- import { i as listSkills, n as updateSkills, r as addSkills, t as removeSkills } from "./src-C0l8elLu.js";
2
+ import { i as listSkills, n as updateSkills, r as addSkills, t as removeSkills } from "./src-CWGSuYNm.js";
3
3
  import path from "node:path";
4
4
  import { parseArgs } from "node:util";
5
5
  //#region bin.ts
6
- var VERSION = "1.3.0-alpha.12";
6
+ var VERSION = "1.3.0-alpha.21";
7
7
  var HELP_TEXT = `
8
8
  \x1b[1m\x1b[36m🚀 Wangs UI Skills & Rules CLI\x1b[0m
9
9
  Install, update, and manage modular AI agent skills and rules for Wangs UI React applications.
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- import { _ as loadAllRules, a as getAgentRuleDirs, c as getInstalledSkills, d as isRuleInstalled, f as isSkillInstalled, g as getSkill, h as getRule, i as listSkills, l as installRule, m as removeSkill, n as updateSkills, o as getAgentSkillDirs, p as removeRule, r as addSkills, s as getInstalledRules, t as removeSkills, u as installSkill, v as loadAllSkills } from "./src-C0l8elLu.js";
1
+ import { _ as loadAllRules, a as getAgentRuleDirs, c as getInstalledSkills, d as isRuleInstalled, f as isSkillInstalled, g as getSkill, h as getRule, i as listSkills, l as installRule, m as removeSkill, n as updateSkills, o as getAgentSkillDirs, p as removeRule, r as addSkills, s as getInstalledRules, t as removeSkills, u as installSkill, v as loadAllSkills } from "./src-CWGSuYNm.js";
2
2
  export { addSkills, getAgentRuleDirs, getAgentSkillDirs, getInstalledRules, getInstalledSkills, getRule, getSkill, installRule, installSkill, isRuleInstalled, isSkillInstalled, listSkills, loadAllRules, loadAllSkills, removeRule, removeSkill, removeSkills, updateSkills };
@@ -1,7 +1,6 @@
1
1
  ---
2
2
  trigger: model_decision
3
3
  description: "Apply when deciding whether a prop belongs in global defaultProps config or per-instance JSX."
4
- owner: wangs-ui
5
4
  metadata:
6
5
  owner: wangs-ui
7
6
  ---
@@ -1,7 +1,6 @@
1
1
  ---
2
2
  trigger: model_decision
3
3
  description: "Apply when writing a <Field> render-prop callback in a Wangs UI Form/DialogForm."
4
- owner: wangs-ui
5
4
  metadata:
6
5
  owner: wangs-ui
7
6
  ---
@@ -1,7 +1,6 @@
1
1
  ---
2
2
  trigger: model_decision
3
3
  description: "Apply when writing JSX markup — enforce Wangs UI primitives over raw HTML elements."
4
- owner: wangs-ui
5
4
  metadata:
6
5
  owner: wangs-ui
7
6
  ---
@@ -1,7 +1,6 @@
1
1
  ---
2
2
  trigger: model_decision
3
3
  description: "Apply when writing or reviewing JSX layout for redundant wrapper containers."
4
- owner: wangs-ui
5
4
  metadata:
6
5
  owner: wangs-ui
7
6
  ---
@@ -1,7 +1,6 @@
1
1
  ---
2
2
  trigger: model_decision
3
3
  description: "Apply when doing a final review pass on React 19 component or hook code for compiler-optimization compliance."
4
- owner: wangs-ui
5
4
  metadata:
6
5
  owner: wangs-ui
7
6
  ---
@@ -1,7 +1,6 @@
1
1
  ---
2
2
  trigger: model_decision
3
3
  description: "Apply when writing or reviewing render logic in a React 19 component."
4
- owner: wangs-ui
5
4
  metadata:
6
5
  owner: wangs-ui
7
6
  ---
@@ -1,7 +1,6 @@
1
1
  ---
2
2
  trigger: model_decision
3
3
  description: "Apply when naming a React 19 component, hook, or prop."
4
- owner: wangs-ui
5
4
  metadata:
6
5
  owner: wangs-ui
7
6
  ---
@@ -1,7 +1,6 @@
1
1
  ---
2
2
  trigger: model_decision
3
3
  description: "Apply when writing or reviewing a React 19 component or hook that uses or is tempted to use useMemo/useCallback/React.memo."
4
- owner: wangs-ui
5
4
  metadata:
6
5
  owner: wangs-ui
7
6
  ---
@@ -1,7 +1,6 @@
1
1
  ---
2
2
  trigger: model_decision
3
3
  description: "Apply when typing React 19 primitives — ref props, useActionState, useOptimistic, use(), useEffectEvent."
4
- owner: wangs-ui
5
4
  metadata:
6
5
  owner: wangs-ui
7
6
  ---
@@ -1,7 +1,6 @@
1
1
  ---
2
2
  trigger: model_decision
3
3
  description: "Apply when defining a component's Props type or interface."
4
- owner: wangs-ui
5
4
  metadata:
6
5
  owner: wangs-ui
7
6
  ---
@@ -1,7 +1,6 @@
1
1
  ---
2
2
  trigger: model_decision
3
3
  description: "Apply when writing or reviewing a React 19 component or hook body for purity and immutability."
4
- owner: wangs-ui
5
4
  metadata:
6
5
  owner: wangs-ui
7
6
  ---
@@ -1,7 +1,6 @@
1
1
  ---
2
2
  trigger: model_decision
3
3
  description: 'Apply when configuring React Compiler tooling or opting a component out via "use no memo".'
4
- owner: wangs-ui
5
4
  metadata:
6
5
  owner: wangs-ui
7
6
  ---
@@ -1,7 +1,6 @@
1
1
  ---
2
2
  trigger: model_decision
3
3
  description: "Apply when tempted to override a Wangs UI theme CSS variable directly."
4
- owner: wangs-ui
5
4
  metadata:
6
5
  owner: wangs-ui
7
6
  ---
@@ -1,7 +1,6 @@
1
1
  ---
2
2
  trigger: model_decision
3
3
  description: "Apply when writing or reviewing a type assertion (`as X`) or non-null assertion (`x!`)."
4
- owner: wangs-ui
5
4
  metadata:
6
5
  owner: wangs-ui
7
6
  ---
@@ -1,7 +1,6 @@
1
1
  ---
2
2
  trigger: model_decision
3
3
  description: "Apply when doing a final TypeScript review pass on a file."
4
- owner: wangs-ui
5
4
  metadata:
6
5
  owner: wangs-ui
7
6
  ---
@@ -1,7 +1,6 @@
1
1
  ---
2
2
  trigger: model_decision
3
3
  description: "Apply when modeling a variant or multi-shape state in TypeScript."
4
- owner: wangs-ui
5
4
  metadata:
6
5
  owner: wangs-ui
7
6
  ---
@@ -1,7 +1,6 @@
1
1
  ---
2
2
  trigger: model_decision
3
3
  description: "Apply when writing or reviewing an exported function, class method, or public API surface in TypeScript."
4
- owner: wangs-ui
5
4
  metadata:
6
5
  owner: wangs-ui
7
6
  ---
@@ -1,7 +1,6 @@
1
1
  ---
2
2
  trigger: model_decision
3
3
  description: "Apply when deciding between `interface` and `type` for a new TypeScript declaration."
4
- owner: wangs-ui
5
4
  metadata:
6
5
  owner: wangs-ui
7
6
  ---
@@ -1,7 +1,6 @@
1
1
  ---
2
2
  trigger: model_decision
3
3
  description: "Apply when modeling a fixed set of string or numeric variants in TypeScript."
4
- owner: wangs-ui
5
4
  metadata:
6
5
  owner: wangs-ui
7
6
  ---
@@ -1,7 +1,6 @@
1
1
  ---
2
2
  trigger: model_decision
3
3
  description: "Apply when naming a TypeScript type, variable, or constant."
4
- owner: wangs-ui
5
4
  metadata:
6
5
  owner: wangs-ui
7
6
  ---
@@ -1,7 +1,6 @@
1
1
  ---
2
2
  trigger: model_decision
3
3
  description: "Apply when narrowing an `unknown` or union-typed value in TypeScript."
4
- owner: wangs-ui
5
4
  metadata:
6
5
  owner: wangs-ui
7
6
  ---
@@ -1,7 +1,6 @@
1
1
  ---
2
2
  trigger: model_decision
3
3
  description: "Apply when declaring a TypeScript collection or object shape."
4
- owner: wangs-ui
5
4
  metadata:
6
5
  owner: wangs-ui
7
6
  ---
@@ -1,7 +1,6 @@
1
1
  ---
2
2
  trigger: model_decision
3
3
  description: "Apply when configuring or reviewing tsconfig.json compiler strictness options."
4
- owner: wangs-ui
5
4
  metadata:
6
5
  owner: wangs-ui
7
6
  ---
@@ -1,7 +1,6 @@
1
1
  ---
2
2
  trigger: model_decision
3
3
  description: "Apply when writing or reviewing any TypeScript type annotation."
4
- owner: wangs-ui
5
4
  metadata:
6
5
  owner: wangs-ui
7
6
  ---
@@ -6,7 +6,7 @@ metadata:
6
6
  ---
7
7
  # Skill: Craft Theme
8
8
 
9
- Use this skill whenever the user asks to create, customize, or rebrand the color theme of an app built on `@wangs-ui/react-core` / `@wangs-ui/foundation` — "make the app's theme orange", "use our brand color #ff6b35", "add a new palette called sunset", etc.
9
+ Use this skill whenever the user asks to create, customize, or rebrand the color theme of an app built on `@wangs-ui/react-core` / `@wangs-ui/foundation` — "make the app's theme orange", "use our brand color #ff6b35", "add a new palette called sunset", etc. This only covers the 8 existing families (primary/secondary/tertiary/success/danger/warning/info/general) — for an entirely new semantic color group (e.g. a "premium" tier badge), use the `custom-color-family` skill instead.
10
10
 
11
11
  ## The rule this skill exists to enforce
12
12
 
@@ -0,0 +1,122 @@
1
+ ---
2
+ name: custom-color-family
3
+ description: Generate a new accessible custom color family (fill/on, container/on-container, emphasized/hover/pressed, fixed) for a Wangs UI app via the `wangs-ui-generate-color-family` CLI — for a semantic group beyond the 8 built-in families (e.g. "premium", "verified", "beta"). Never hand-write or hand-pick these hexes.
4
+ metadata:
5
+ owner: wangs-ui
6
+ ---
7
+ # Skill: Custom Color Family
8
+
9
+ Use this skill whenever the app needs a new semantic color group that isn't one of the 8 built-in families (`primary`/`secondary`/`tertiary`/`danger`/`success`/`warning`/`info`/`general`) — a product tier badge ("premium"), a verification status ("verified"), a draft/beta tag, a data-visualization category color, etc. For rebranding the existing 8 families instead, use the `craft-theme` skill.
10
+
11
+ ## The rule this skill exists to enforce
12
+
13
+ **Never hand-pick which color goes on top of your fill, how light the container tint should be, or what percentage a hover/pressed state should mix at — and never recompute any of it at render time.** Wangs UI's own built-in families hit the first mistake before: a fixed luminance threshold put white text on a brand color at 2.54:1 contrast (fails AA, looked fine "by eye"). The CLI below runs the same search-based derivation the built-in families use, **once**, and writes the result to a static file — exactly like `successContainer`/`onSuccessContainer` are plain static fields you reuse everywhere, not something recomputed per component.
14
+
15
+ ## 1. Generate the family
16
+
17
+ ```bash
18
+ pnpm exec wangs-ui-generate-color-family --name=<id> --color=<#hex> [--general=#hex] --out=<path>
19
+ ```
20
+
21
+ - `--name` (required): identifier for the family, e.g. `premium`. Used for the exported const name (`premiumTokens`) and, unless `--out` is given, the output filename (`premium.ts`).
22
+ - `--color` (required): the family's reference hex, e.g. `#7c5cff`.
23
+ - `--general` (optional): your theme's neutral/general base (`Palette.general['500']` — the same hex `craft-theme`'s generator used for your app's `general` family, or one of the built-in palettes' `general['500']`). Improves how the Fill/On pairing reads against your actual neutral surfaces. Defaults to `#808080` if omitted.
24
+ - `--out=<path>` (optional): where to write the file. Defaults to `./<name>.ts` in the current working directory — always pass an explicit `--out` pointing into the app's own theme directory (e.g. `src/theme/premium.ts`), don't rely on the default.
25
+
26
+ If `@wangs-ui/foundation` isn't already a dependency of the project, install it first — the CLI ships as its `bin`.
27
+
28
+ **The written file is chmod'd read-only (0o444) and headed with an AUTO-GENERATED / DO NOT EDIT BY HAND comment.** If the color needs to change, re-run the command — never hand-edit a role in the output.
29
+
30
+ This writes **two** files — `<out>.ts` (the computed hex values) and `<out>.css` (a static Tailwind `@theme` registration, same mechanism `theme.css` itself uses for `success`/`danger`/etc.):
31
+
32
+ ```ts
33
+ // premium.ts
34
+ export const premiumTokens = {
35
+ light: {
36
+ fill: '#7c5cff',
37
+ onFill: '#000000',
38
+ container: '#dfdfff',
39
+ onContainer: '#471fa9',
40
+ emphasized: '#7959f8',
41
+ hover: '#7959f8',
42
+ pressed: '#7959f8',
43
+ fixed: '#dfdfff',
44
+ fixedDim: '#c2bfff',
45
+ onFixed: '#1b0d48',
46
+ onFixedVariant: '#471fa9',
47
+ },
48
+ dark: {
49
+ /* … same 11 roles, derived for dark mode … */
50
+ },
51
+ } as const;
52
+ ```
53
+
54
+ ```css
55
+ /* premium.css */
56
+ @theme {
57
+ --color-premium: var(--color-premium);
58
+ --color-on-premium: var(--color-on-premium);
59
+ --color-premium-container: var(--color-premium-container);
60
+ --color-on-premium-container: var(--color-on-premium-container);
61
+ /* … the rest of the 11 roles … */
62
+ }
63
+ ```
64
+
65
+ The `.css` file only declares **names** (self-referential, no hardcoded value) so Tailwind can generate the matching utilities — it never needs regenerating even when you change the color with a new CLI run; only the `.ts` file does.
66
+
67
+ ## 2. Wire it up once (web)
68
+
69
+ Two one-time steps, done once for the whole app — not per component, and no wrapper component to write:
70
+
71
+ **a. Import the generated CSS** anywhere in your app's normal global stylesheet import chain (wherever your `@import "tailwindcss"` already lives):
72
+
73
+ ```css
74
+ @import './theme/premium.css';
75
+ ```
76
+
77
+ **b. Pass the generated tokens straight to `WangsUiProvider`'s `theme.customColorFamilies`** — this is a real `ThemeProviderProps` field, not a convention you assemble yourself. The provider resolves it to the current mode and injects the CSS variables internally, the exact same pass the built-in families already get (`CssVariablesInjector`) — adding a family is purely additive data, no new component in your tree:
78
+
79
+ ```tsx
80
+ import { WangsUiProvider } from '@wangs-ui/react-core/api';
81
+ import { premiumTokens } from './theme/premium';
82
+
83
+ <WangsUiProvider
84
+ configOptions={{ preset }}
85
+ theme={{ palette, mode, customColorFamilies: { premium: premiumTokens } }}
86
+ >
87
+ <App />
88
+ </WangsUiProvider>;
89
+ ```
90
+
91
+ A second custom family is one more entry in the same object (`{ premium: premiumTokens, verified: verifiedTokens }`) — never a second wrapper.
92
+
93
+ ## 3. Use it — same as any built-in family
94
+
95
+ From here on, every component just uses ordinary Tailwind utility classes, exactly like `bg-success-container`/`text-on-success-container`:
96
+
97
+ ```tsx
98
+ function PremiumBadge() {
99
+ return <span className="bg-premium-container text-on-premium-container">Premium</span>;
100
+ }
101
+ ```
102
+
103
+ No import, no hook, no per-component wiring — any component anywhere in the tree can reach for `bg-premium` / `text-on-premium` / `bg-premium-emphasized` / `hover:bg-premium-hover` / etc. the same way it already reaches for the built-in families.
104
+
105
+ If a component needs the plain hex value instead of a class (a canvas draw call, an SVG fill, a chart library prop), `useTheme().customColors.premium` is already resolved to the current mode — no manual `premiumTokens[mode]` indexing:
106
+
107
+ ```tsx
108
+ const { customColors } = useTheme();
109
+ customColors.premium.fill; // already the right mode's hex
110
+ ```
111
+
112
+ **React Native** has no `.css`/Tailwind step: skip step **a** in section 2, but still pass `customColorFamilies` to `theme` in step **b** — then read `useTheme().customColors.premium` (etc.) the same way, directly in `style`.
113
+
114
+ ## 4. Regenerating / evolving a custom family
115
+
116
+ To change the color, re-run the same command with the new hex:
117
+
118
+ ```bash
119
+ pnpm exec wangs-ui-generate-color-family --name=premium --color=#9333ea --out=src/theme/premium.ts
120
+ ```
121
+
122
+ This rewrites `premium.ts` with the new values and rewrites `premium.css` too (harmless — it's the same static names every time, nothing in your app needs to change because of it).
@@ -4,10 +4,13 @@ import { fileURLToPath } from "node:url";
4
4
  import os from "node:os";
5
5
  import { cancel, intro, isCancel, multiselect, outro } from "@clack/prompts";
6
6
  //#region skills/wangs-ui/craft-theme/SKILL.md?raw
7
- var SKILL_default$8 = "---\nname: craft-theme\ndescription: Generate a brand-color theme (13-shade tonal palette + semantic tokens) for a Wangs UI app via the `wangs-ui-generate-palette` CLI — never hand-write hex shade ramps.\nmetadata:\n owner: wangs-ui\n---\n# Skill: Craft Theme\n\nUse this skill whenever the user asks to create, customize, or rebrand the color theme of an app built on `@wangs-ui/react-core` / `@wangs-ui/foundation` — \"make the app's theme orange\", \"use our brand color #ff6b35\", \"add a new palette called sunset\", etc.\n\n## The rule this skill exists to enforce\n\n**Never hand-write a 13-shade tonal ramp, and never hand-pick which shade pairs with which as a text/foreground color.** Wangs UI's own design system shipped with exactly that mistake for a while: hand-tuned palette files drifted from what the perceptual (OKLCH) generator would produce, which caused white text to render at 2.54:1 contrast on one brand's primary color — invisible-adjacent, and only caught by a dedicated audit. The generator and the contrast math it's paired with exist specifically so this class of bug can't happen again. Always reach for the CLI below instead of writing hex values yourself.\n\n## 1. Generate the palette\n\n```bash\npnpm exec wangs-ui-generate-palette --name=<id> --primary=<#hex> [options] --out=<path>\n```\n\n- `--name` (required): identifier for the palette, e.g. `sunset`. Used for the exported const name (`sunsetPalette`) and, unless `--out` is given, the output filename (`sunset.ts`).\n- `--primary` (required): the brand/key hex color, e.g. `#ff6b35`.\n- Optional per-family overrides — omit any of these to let the generator harmoniously auto-derive it from `--primary` (secondary: boosted-lightness variant of primary's hue; tertiary: +60° hue rotation; general: near-neutral variant of primary's hue) or fall back to the generator's calibrated defaults (success/danger/warning/info):\n `--secondary=#hex --tertiary=#hex --general=#hex --success=#hex --danger=#hex --warning=#hex --info=#hex`\n- `--out=<path>` (optional): where to write the file. Defaults to `./<name>.ts` in the current working directory — always pass an explicit `--out` pointing into the consumer app's own theme directory (e.g. `src/theme/sunset.ts`), don't rely on the default.\n\nIf `@wangs-ui/foundation` isn't already a dependency of the project you're working in, install it first (`pnpm add @wangs-ui/foundation` or the project's equivalent) — the CLI ships as its `bin`.\n\n**The written file is chmod'd read-only (0o444) and headed with an AUTO-GENERATED / DO NOT EDIT BY HAND comment.** If a color needs to change, re-run the command (it clears the read-only bit, rewrites, and re-locks it) — never hand-edit a shade in the output, and never `chmod` it writable to bypass this. Note the read-only bit is a local filesystem attribute only; it is not preserved by git across clones, so it is a deterrent for the person/agent working in this checkout right now, not a hard guarantee for every future contributor.\n\n## 2. Wire it into the app\n\nThe generated file exports a plain `Palette` object — pass it directly to `WangsUiProvider`'s `theme.palette` (or `theme.defaultPalette` for uncontrolled mode):\n\n```tsx\nimport { WangsUiProvider } from '@wangs-ui/react-core/api';\nimport preset from '@wangs-ui/react-presets/fixedasset'; // or whichever preset the app uses\nimport { sunsetPalette } from './theme/sunset';\n\nconst App = () => (\n <WangsUiProvider configOptions={{ preset }} theme={{ palette: sunsetPalette, mode: 'light' }}>\n <YourApp />\n </WangsUiProvider>\n);\n```\n\n`theme.palette` also accepts a built-in palette name (`'blue' | 'emerald' | 'crimson' | 'carbon' | 'gold'`) or a raw hex string — a generated `Palette` object is the right choice once you have brand-specific secondary/tertiary/status colors to preserve, not just a single key color.\n\n## 3. Regenerating / evolving an existing custom palette\n\nTo change a color, re-run the same command with a new hex for the family that changed — **always pass every family you want to keep**, not just the one changing, since each run is a full regeneration from the anchors you give it:\n\n```bash\npnpm exec wangs-ui-generate-palette --name=sunset --primary=#ff6b35 --tertiary=#2a9d8f --out=src/theme/sunset.ts\n```\n\n## 4. If you're extending Wangs UI itself (contributing a new official palette)\n\nThis is a different, rarer case than theming a consumer app — only relevant if you're working inside the `wangs-ui-react` monorepo itself and adding a 6th built-in palette alongside blue/emerald/crimson/carbon/gold:\n\n1. Run the generator with `--out` pointing at `packages/foundation/theme/tokens/palettes/<name>.ts`.\n2. Register it: add `'<name>'` to the `PaletteName` union and `paletteLoaders` map in `packages/foundation/theme/context/ThemeContext.tsx`, and re-export it from `packages/foundation/theme/tokens/palettes/index.ts`.\n3. Run the contrast regression suite before considering it done: `pnpm exec vitest run --config packages/foundation/vitest.config.ts` — it checks every semantic token pairing (including the new palette) against WCAG AA across both light and dark mode. See `packages/foundation/theme/tokens/COLOR_TOKEN_CONTRACT.md` for the full rule set this is checked against.\n";
7
+ var SKILL_default$9 = "---\nname: craft-theme\ndescription: Generate a brand-color theme (13-shade tonal palette + semantic tokens) for a Wangs UI app via the `wangs-ui-generate-palette` CLI — never hand-write hex shade ramps.\nmetadata:\n owner: wangs-ui\n---\n# Skill: Craft Theme\n\nUse this skill whenever the user asks to create, customize, or rebrand the color theme of an app built on `@wangs-ui/react-core` / `@wangs-ui/foundation` — \"make the app's theme orange\", \"use our brand color #ff6b35\", \"add a new palette called sunset\", etc. This only covers the 8 existing families (primary/secondary/tertiary/success/danger/warning/info/general) — for an entirely new semantic color group (e.g. a \"premium\" tier badge), use the `custom-color-family` skill instead.\n\n## The rule this skill exists to enforce\n\n**Never hand-write a 13-shade tonal ramp, and never hand-pick which shade pairs with which as a text/foreground color.** Wangs UI's own design system shipped with exactly that mistake for a while: hand-tuned palette files drifted from what the perceptual (OKLCH) generator would produce, which caused white text to render at 2.54:1 contrast on one brand's primary color — invisible-adjacent, and only caught by a dedicated audit. The generator and the contrast math it's paired with exist specifically so this class of bug can't happen again. Always reach for the CLI below instead of writing hex values yourself.\n\n## 1. Generate the palette\n\n```bash\npnpm exec wangs-ui-generate-palette --name=<id> --primary=<#hex> [options] --out=<path>\n```\n\n- `--name` (required): identifier for the palette, e.g. `sunset`. Used for the exported const name (`sunsetPalette`) and, unless `--out` is given, the output filename (`sunset.ts`).\n- `--primary` (required): the brand/key hex color, e.g. `#ff6b35`.\n- Optional per-family overrides — omit any of these to let the generator harmoniously auto-derive it from `--primary` (secondary: boosted-lightness variant of primary's hue; tertiary: +60° hue rotation; general: near-neutral variant of primary's hue) or fall back to the generator's calibrated defaults (success/danger/warning/info):\n `--secondary=#hex --tertiary=#hex --general=#hex --success=#hex --danger=#hex --warning=#hex --info=#hex`\n- `--out=<path>` (optional): where to write the file. Defaults to `./<name>.ts` in the current working directory — always pass an explicit `--out` pointing into the consumer app's own theme directory (e.g. `src/theme/sunset.ts`), don't rely on the default.\n\nIf `@wangs-ui/foundation` isn't already a dependency of the project you're working in, install it first (`pnpm add @wangs-ui/foundation` or the project's equivalent) — the CLI ships as its `bin`.\n\n**The written file is chmod'd read-only (0o444) and headed with an AUTO-GENERATED / DO NOT EDIT BY HAND comment.** If a color needs to change, re-run the command (it clears the read-only bit, rewrites, and re-locks it) — never hand-edit a shade in the output, and never `chmod` it writable to bypass this. Note the read-only bit is a local filesystem attribute only; it is not preserved by git across clones, so it is a deterrent for the person/agent working in this checkout right now, not a hard guarantee for every future contributor.\n\n## 2. Wire it into the app\n\nThe generated file exports a plain `Palette` object — pass it directly to `WangsUiProvider`'s `theme.palette` (or `theme.defaultPalette` for uncontrolled mode):\n\n```tsx\nimport { WangsUiProvider } from '@wangs-ui/react-core/api';\nimport preset from '@wangs-ui/react-presets/fixedasset'; // or whichever preset the app uses\nimport { sunsetPalette } from './theme/sunset';\n\nconst App = () => (\n <WangsUiProvider configOptions={{ preset }} theme={{ palette: sunsetPalette, mode: 'light' }}>\n <YourApp />\n </WangsUiProvider>\n);\n```\n\n`theme.palette` also accepts a built-in palette name (`'blue' | 'emerald' | 'crimson' | 'carbon' | 'gold'`) or a raw hex string — a generated `Palette` object is the right choice once you have brand-specific secondary/tertiary/status colors to preserve, not just a single key color.\n\n## 3. Regenerating / evolving an existing custom palette\n\nTo change a color, re-run the same command with a new hex for the family that changed — **always pass every family you want to keep**, not just the one changing, since each run is a full regeneration from the anchors you give it:\n\n```bash\npnpm exec wangs-ui-generate-palette --name=sunset --primary=#ff6b35 --tertiary=#2a9d8f --out=src/theme/sunset.ts\n```\n\n## 4. If you're extending Wangs UI itself (contributing a new official palette)\n\nThis is a different, rarer case than theming a consumer app — only relevant if you're working inside the `wangs-ui-react` monorepo itself and adding a 6th built-in palette alongside blue/emerald/crimson/carbon/gold:\n\n1. Run the generator with `--out` pointing at `packages/foundation/theme/tokens/palettes/<name>.ts`.\n2. Register it: add `'<name>'` to the `PaletteName` union and `paletteLoaders` map in `packages/foundation/theme/context/ThemeContext.tsx`, and re-export it from `packages/foundation/theme/tokens/palettes/index.ts`.\n3. Run the contrast regression suite before considering it done: `pnpm exec vitest run --config packages/foundation/vitest.config.ts` — it checks every semantic token pairing (including the new palette) against WCAG AA across both light and dark mode. See `packages/foundation/theme/tokens/COLOR_TOKEN_CONTRACT.md` for the full rule set this is checked against.\n";
8
8
  //#endregion
9
9
  //#region skills/wangs-ui/create-form/SKILL.md?raw
10
- var SKILL_default$7 = "---\nname: create-form\ndescription: Form architecture, validation workflows, strongly-typed forms (useForm, useDialogForm, useWatchField), initialValues/reset lifecycle, and MCP discovery protocol for building forms and input controls with @wangs-ui/react-core.\nmetadata:\n owner: wangs-ui\n---\n# Skill: Form Architecture & Validation Workflows\n\nUse this skill when building forms, data entry panels, modal forms, settings pages, or multipart forms in Wangs UI applications.\n\n---\n\n## 1. MCP Protocol & Component Rules (Mandatory Single Source of Truth)\n\nDo **NOT** hardcode or guess prop names, component options, preset variations, or Storybook patterns in this document. Always retrieve component definitions, active props, and live Storybook implementations directly via MCP:\n\n### Component & Form API Protocol:\n\n```json\nget_component_api({ \"component\": \"form\" })\nget_component_api({ \"component\": \"field\" })\nget_component_api({ \"component\": \"dialogform\" })\nget_component_api({ \"component\": \"input\" })\nget_component_api({ \"component\": \"numberinput\" })\nget_component_api({ \"component\": \"select\" })\nget_component_api({ \"component\": \"multiselect\" })\nget_component_api({ \"component\": \"datepicker\" })\nget_component_api({ \"component\": \"fileupload\" })\n```\n\n### Curated Form Documentation:\n\n```json\nget_documentation({ \"id\": \"form\" })\nget_documentation({ \"id\": \"dialogform\" })\nget_documentation({ \"id\": \"field\" })\n```\n\n### Live Storybook & Interactive Behavior Protocol:\n\n```json\nget_component_examples({ \"component\": \"form\", \"variant\": \"Default\" })\nget_component_examples({ \"component\": \"form\", \"variant\": \"AsyncInitialValues\" })\nget_component_examples({ \"component\": \"form\", \"variant\": \"ConditionalFields\" })\nget_component_examples({ \"component\": \"form\", \"variant\": \"CascadingOptions\" })\nget_component_examples({ \"component\": \"dialogform\", \"variant\": \"Default\" })\n```\n\n### Knowledge Graph & Symbol Usages:\n\n```json\nquery_graph({ \"query\": \"useForm\" })\nquery_graph({ \"query\": \"useDialogForm\" })\nquery_graph({ \"query\": \"useWatchField\" })\n```\n\n---\n\n## 2. Core Form Concepts & Lifecycle Mechanics\n\n### A. Strongly Typed Form Instance (`useForm<TForm>()`)\n\n`useForm<TForm>()` instantiates a `FormControl` natively bound to model type `TForm`.\n\n- `Field`: `name` is strictly typed to `Path<TForm>` dot-paths.\n- `useWatchField`: `name` is strictly typed to `Path<TForm>`.\n- `control`: Provides `setInitialValues`, `setValues`, `setFieldError`, `setErrors`, and `reset`.\n\n### B. Dynamic Initial Values & Baseline Reset (`setInitialValues` vs `setValues`)\n\n1. **Async Initial Values (`control.setInitialValues(values)`)**:\n - Accepts a `Partial<TForm>` JSON object (e.g. fetched from an API).\n - Establishes an **immutable baseline** for registered fields. Once set for a field path, subsequent calls to `setInitialValues` for that path are ignored.\n2. **Batch Value Updates (`control.setValues(values)`)**:\n - Accepts a `Partial<TForm>` JSON object to update current input values without altering the initial baseline.\n3. **Reset Behavior (`control.reset()`)**:\n - Restores all fields back to their registered initial baseline values (set via `setInitialValues` or field `initialValue`) and clears all field-level validation errors.\n\n### C. Primitive Component Integration Architecture\n\n`Field` serves as the form integration wrapper for primitive UI input components (`Input`, `Select`, `MultiSelect`, `DatePicker`, `NumberInput`, `FileUpload`, `Calendar`, etc.):\n\n- **Children Render Callback**: `Field` yields `{ fieldProps, fieldState }`.\n- **`fieldProps`**: Pass directly to primitive inputs (`<Input {...fieldProps} />`). Contains `name`, `value`, `ref`, `onChange`.\n- **`fieldState`**: Provides `invalid`, `error`, `isDirty`, `isPending`. Pass `invalid={fieldState.invalid}` to primitive components for accessibility and validation styling.\n- **⚠️ Mandatory Destructuring (Never `(field) => ...`)**: `<Field>`'s callback render prop must destructure as `{({ fieldProps, fieldState })}`. Passing a single parameter instead (`{(field) => ...}`) produces `undefined` `value`/`onChange` and permanently locks the input.\n- **Pass `fieldProps.onChange` directly**: `<Field>` is generic and `fieldProps.onChange` is already typed to the field's value type. Do not wrap it in unnecessary `e?.target?.value` extractions.\n\n---\n\n## 3. High-Level Form Architecture & Usage Patterns\n\n### Pattern 1: Page Forms (`useForm<T>()`)\n\n```tsx\nimport Button from '@wangs-ui/react-core/primitive/button';\nimport { useForm } from '@wangs-ui/react-core/primitive/form';\nimport Input from '@wangs-ui/react-core/primitive/input';\nimport { useI18n } from '@wangs-ui/react-i18n';\nimport { useEffect } from 'react';\n\ninterface UserProfile {\n name: string;\n email: string;\n}\n\nexport function UserProfilePage({ userId }: { userId: string }) {\n const { t } = useI18n();\n const { Form, Field, control } = useForm<UserProfile>();\n\n useEffect(() => {\n async function loadData() {\n const data = await fetchUserData(userId);\n // Establish immutable initial baseline from async response\n control.setInitialValues(data);\n }\n loadData();\n }, [userId, control]);\n\n return (\n <Form control={control} onSubmit={(values) => saveUserData(values)}>\n <Field required label={t('Full Name')} name=\"name\">\n {({ fieldProps, fieldState }) => (\n <Input {...fieldProps} invalid={fieldState.invalid} placeholder={t('Enter full name')} />\n )}\n </Field>\n\n <div className=\"flex gap-2\">\n <Button\n label={t('Reset')}\n type=\"button\"\n variant=\"outlined\"\n onClick={() => control.reset()}\n />\n <Button label={t('Save')} type=\"submit\" />\n </div>\n </Form>\n );\n}\n```\n\n### Pattern 2: Modal Forms (`useDialogForm<T>()`)\n\n```tsx\nimport Button from '@wangs-ui/react-core/primitive/button';\nimport { useDialogForm } from '@wangs-ui/react-core/primitive/dialogform';\nimport Input from '@wangs-ui/react-core/primitive/input';\nimport { useI18n } from '@wangs-ui/react-i18n';\nimport { useState } from 'react';\n\ninterface EditUserForm {\n name: string;\n}\n\nexport function EditUserModal() {\n const { t } = useI18n();\n const [open, setOpen] = useState(false);\n const { DialogForm, Field, control } = useDialogForm<EditUserForm>();\n\n return (\n <>\n <Button label={t('Edit')} onClick={() => setOpen(true)} />\n <DialogForm\n closeOnSubmit\n control={control}\n header={t('Edit User')}\n open={open}\n onOpenChange={setOpen}\n onSubmit={(values) => handleSave(values)}\n >\n <Field required label={t('Full Name')} name=\"name\">\n {({ fieldProps, fieldState }) => <Input {...fieldProps} invalid={fieldState.invalid} />}\n </Field>\n </DialogForm>\n </>\n );\n}\n```\n\n---\n\n## 4. Mandatory Implementation Guidelines\n\n1. **Query MCP First**: Never guess component props or story examples — inspect via MCP tools.\n2. **Granular Primitive Subpaths**: Import primitives via exact subpath modules (`@wangs-ui/react-core/primitive/form`, `@wangs-ui/react-core/primitive/dialogform`, `@wangs-ui/react-core/primitive/input`).\n3. **i18n Localization**: Wrap all user-visible labels, placeholders, and error strings in `t('...')` from `@wangs-ui/react-i18n`.\n4. **Server Error Mapping**: Map HTTP validation errors (e.g. 422 response) into the form using `control.setErrors(apiErrors)`.\n";
10
+ var SKILL_default$8 = "---\nname: create-form\ndescription: Form architecture, validation workflows, strongly-typed forms (useForm, useDialogForm, useWatchField), initialValues/reset lifecycle, and MCP discovery protocol for building forms and input controls with @wangs-ui/react-core.\nmetadata:\n owner: wangs-ui\n---\n# Skill: Form Architecture & Validation Workflows\n\nUse this skill when building forms, data entry panels, modal forms, settings pages, or multipart forms in Wangs UI applications.\n\n---\n\n## 1. MCP Protocol & Component Rules (Mandatory Single Source of Truth)\n\nDo **NOT** hardcode or guess prop names, component options, preset variations, or Storybook patterns in this document. Always retrieve component definitions, active props, and live Storybook implementations directly via MCP:\n\n### Component & Form API Protocol:\n\n```json\nget_component_api({ \"component\": \"form\" })\nget_component_api({ \"component\": \"field\" })\nget_component_api({ \"component\": \"dialogform\" })\nget_component_api({ \"component\": \"input\" })\nget_component_api({ \"component\": \"numberinput\" })\nget_component_api({ \"component\": \"select\" })\nget_component_api({ \"component\": \"multiselect\" })\nget_component_api({ \"component\": \"datepicker\" })\nget_component_api({ \"component\": \"fileupload\" })\n```\n\n### Curated Form Documentation:\n\n```json\nget_documentation({ \"id\": \"form\" })\nget_documentation({ \"id\": \"dialogform\" })\nget_documentation({ \"id\": \"field\" })\n```\n\n### Live Storybook & Interactive Behavior Protocol:\n\n```json\nget_component_examples({ \"component\": \"form\", \"variant\": \"Default\" })\nget_component_examples({ \"component\": \"form\", \"variant\": \"AsyncInitialValues\" })\nget_component_examples({ \"component\": \"form\", \"variant\": \"ConditionalFields\" })\nget_component_examples({ \"component\": \"form\", \"variant\": \"CascadingOptions\" })\nget_component_examples({ \"component\": \"dialogform\", \"variant\": \"Default\" })\n```\n\n### Knowledge Graph & Symbol Usages:\n\n```json\nquery_graph({ \"query\": \"useForm\" })\nquery_graph({ \"query\": \"useDialogForm\" })\nquery_graph({ \"query\": \"useWatchField\" })\n```\n\n---\n\n## 2. Core Form Concepts & Lifecycle Mechanics\n\n### A. Strongly Typed Form Instance (`useForm<TForm>()`)\n\n`useForm<TForm>()` instantiates a `FormControl` natively bound to model type `TForm`.\n\n- `Field`: `name` is strictly typed to `Path<TForm>` dot-paths.\n- `useWatchField`: `name` is strictly typed to `Path<TForm>`.\n- `control`: Provides `setInitialValues`, `setValues`, `setFieldError`, `setErrors`, and `reset`.\n\n### B. Dynamic Initial Values & Baseline Reset (`setInitialValues` vs `setValues`)\n\n1. **Async Initial Values (`control.setInitialValues(values)`)**:\n - Accepts a `Partial<TForm>` JSON object (e.g. fetched from an API).\n - Establishes an **immutable baseline** for registered fields. Once set for a field path, subsequent calls to `setInitialValues` for that path are ignored.\n2. **Batch Value Updates (`control.setValues(values)`)**:\n - Accepts a `Partial<TForm>` JSON object to update current input values without altering the initial baseline.\n3. **Reset Behavior (`control.reset()`)**:\n - Restores all fields back to their registered initial baseline values (set via `setInitialValues` or field `initialValue`) and clears all field-level validation errors.\n\n### C. Primitive Component Integration Architecture\n\n`Field` serves as the form integration wrapper for primitive UI input components (`Input`, `Select`, `MultiSelect`, `DatePicker`, `NumberInput`, `FileUpload`, `Calendar`, etc.):\n\n- **Children Render Callback**: `Field` yields `{ fieldProps, fieldState }`.\n- **`fieldProps`**: Pass directly to primitive inputs (`<Input {...fieldProps} />`). Contains `name`, `value`, `ref`, `onChange`.\n- **`fieldState`**: Provides `invalid`, `error`, `isDirty`, `isPending`. Pass `invalid={fieldState.invalid}` to primitive components for accessibility and validation styling.\n- **⚠️ Mandatory Destructuring (Never `(field) => ...`)**: `<Field>`'s callback render prop must destructure as `{({ fieldProps, fieldState })}`. Passing a single parameter instead (`{(field) => ...}`) produces `undefined` `value`/`onChange` and permanently locks the input.\n- **Pass `fieldProps.onChange` directly**: `<Field>` is generic and `fieldProps.onChange` is already typed to the field's value type. Do not wrap it in unnecessary `e?.target?.value` extractions.\n\n---\n\n## 3. High-Level Form Architecture & Usage Patterns\n\n### Pattern 1: Page Forms (`useForm<T>()`)\n\n```tsx\nimport Button from '@wangs-ui/react-core/primitive/button';\nimport { useForm } from '@wangs-ui/react-core/primitive/form';\nimport Input from '@wangs-ui/react-core/primitive/input';\nimport { useI18n } from '@wangs-ui/react-i18n';\nimport { useEffect } from 'react';\n\ninterface UserProfile {\n name: string;\n email: string;\n}\n\nexport function UserProfilePage({ userId }: { userId: string }) {\n const { t } = useI18n();\n const { Form, Field, control } = useForm<UserProfile>();\n\n useEffect(() => {\n async function loadData() {\n const data = await fetchUserData(userId);\n // Establish immutable initial baseline from async response\n control.setInitialValues(data);\n }\n loadData();\n }, [userId, control]);\n\n return (\n <Form control={control} onSubmit={(values) => saveUserData(values)}>\n <Field required label={t('Full Name')} name=\"name\">\n {({ fieldProps, fieldState }) => (\n <Input {...fieldProps} invalid={fieldState.invalid} placeholder={t('Enter full name')} />\n )}\n </Field>\n\n <div className=\"flex gap-2\">\n <Button\n label={t('Reset')}\n type=\"button\"\n variant=\"outlined\"\n onClick={() => control.reset()}\n />\n <Button label={t('Save')} type=\"submit\" />\n </div>\n </Form>\n );\n}\n```\n\n### Pattern 2: Modal Forms (`useDialogForm<T>()`)\n\n```tsx\nimport Button from '@wangs-ui/react-core/primitive/button';\nimport { useDialogForm } from '@wangs-ui/react-core/primitive/dialogform';\nimport Input from '@wangs-ui/react-core/primitive/input';\nimport { useI18n } from '@wangs-ui/react-i18n';\nimport { useState } from 'react';\n\ninterface EditUserForm {\n name: string;\n}\n\nexport function EditUserModal() {\n const { t } = useI18n();\n const [open, setOpen] = useState(false);\n const { DialogForm, Field, control } = useDialogForm<EditUserForm>();\n\n return (\n <>\n <Button label={t('Edit')} onClick={() => setOpen(true)} />\n <DialogForm\n closeOnSubmit\n control={control}\n header={t('Edit User')}\n open={open}\n onOpenChange={setOpen}\n onSubmit={(values) => handleSave(values)}\n >\n <Field required label={t('Full Name')} name=\"name\">\n {({ fieldProps, fieldState }) => <Input {...fieldProps} invalid={fieldState.invalid} />}\n </Field>\n </DialogForm>\n </>\n );\n}\n```\n\n---\n\n## 4. Mandatory Implementation Guidelines\n\n1. **Query MCP First**: Never guess component props or story examples — inspect via MCP tools.\n2. **Granular Primitive Subpaths**: Import primitives via exact subpath modules (`@wangs-ui/react-core/primitive/form`, `@wangs-ui/react-core/primitive/dialogform`, `@wangs-ui/react-core/primitive/input`).\n3. **i18n Localization**: Wrap all user-visible labels, placeholders, and error strings in `t('...')` from `@wangs-ui/react-i18n`.\n4. **Server Error Mapping**: Map HTTP validation errors (e.g. 422 response) into the form using `control.setErrors(apiErrors)`.\n";
11
+ //#endregion
12
+ //#region skills/wangs-ui/custom-color-family/SKILL.md?raw
13
+ var SKILL_default$7 = "---\nname: custom-color-family\ndescription: Generate a new accessible custom color family (fill/on, container/on-container, emphasized/hover/pressed, fixed) for a Wangs UI app via the `wangs-ui-generate-color-family` CLI — for a semantic group beyond the 8 built-in families (e.g. \"premium\", \"verified\", \"beta\"). Never hand-write or hand-pick these hexes.\nmetadata:\n owner: wangs-ui\n---\n# Skill: Custom Color Family\n\nUse this skill whenever the app needs a new semantic color group that isn't one of the 8 built-in families (`primary`/`secondary`/`tertiary`/`danger`/`success`/`warning`/`info`/`general`) — a product tier badge (\"premium\"), a verification status (\"verified\"), a draft/beta tag, a data-visualization category color, etc. For rebranding the existing 8 families instead, use the `craft-theme` skill.\n\n## The rule this skill exists to enforce\n\n**Never hand-pick which color goes on top of your fill, how light the container tint should be, or what percentage a hover/pressed state should mix at — and never recompute any of it at render time.** Wangs UI's own built-in families hit the first mistake before: a fixed luminance threshold put white text on a brand color at 2.54:1 contrast (fails AA, looked fine \"by eye\"). The CLI below runs the same search-based derivation the built-in families use, **once**, and writes the result to a static file — exactly like `successContainer`/`onSuccessContainer` are plain static fields you reuse everywhere, not something recomputed per component.\n\n## 1. Generate the family\n\n```bash\npnpm exec wangs-ui-generate-color-family --name=<id> --color=<#hex> [--general=#hex] --out=<path>\n```\n\n- `--name` (required): identifier for the family, e.g. `premium`. Used for the exported const name (`premiumTokens`) and, unless `--out` is given, the output filename (`premium.ts`).\n- `--color` (required): the family's reference hex, e.g. `#7c5cff`.\n- `--general` (optional): your theme's neutral/general base (`Palette.general['500']` — the same hex `craft-theme`'s generator used for your app's `general` family, or one of the built-in palettes' `general['500']`). Improves how the Fill/On pairing reads against your actual neutral surfaces. Defaults to `#808080` if omitted.\n- `--out=<path>` (optional): where to write the file. Defaults to `./<name>.ts` in the current working directory — always pass an explicit `--out` pointing into the app's own theme directory (e.g. `src/theme/premium.ts`), don't rely on the default.\n\nIf `@wangs-ui/foundation` isn't already a dependency of the project, install it first — the CLI ships as its `bin`.\n\n**The written file is chmod'd read-only (0o444) and headed with an AUTO-GENERATED / DO NOT EDIT BY HAND comment.** If the color needs to change, re-run the command — never hand-edit a role in the output.\n\nThis writes **two** files — `<out>.ts` (the computed hex values) and `<out>.css` (a static Tailwind `@theme` registration, same mechanism `theme.css` itself uses for `success`/`danger`/etc.):\n\n```ts\n// premium.ts\nexport const premiumTokens = {\n light: {\n fill: '#7c5cff',\n onFill: '#000000',\n container: '#dfdfff',\n onContainer: '#471fa9',\n emphasized: '#7959f8',\n hover: '#7959f8',\n pressed: '#7959f8',\n fixed: '#dfdfff',\n fixedDim: '#c2bfff',\n onFixed: '#1b0d48',\n onFixedVariant: '#471fa9',\n },\n dark: {\n /* … same 11 roles, derived for dark mode … */\n },\n} as const;\n```\n\n```css\n/* premium.css */\n@theme {\n --color-premium: var(--color-premium);\n --color-on-premium: var(--color-on-premium);\n --color-premium-container: var(--color-premium-container);\n --color-on-premium-container: var(--color-on-premium-container);\n /* … the rest of the 11 roles … */\n}\n```\n\nThe `.css` file only declares **names** (self-referential, no hardcoded value) so Tailwind can generate the matching utilities — it never needs regenerating even when you change the color with a new CLI run; only the `.ts` file does.\n\n## 2. Wire it up once (web)\n\nTwo one-time steps, done once for the whole app — not per component, and no wrapper component to write:\n\n**a. Import the generated CSS** anywhere in your app's normal global stylesheet import chain (wherever your `@import \"tailwindcss\"` already lives):\n\n```css\n@import './theme/premium.css';\n```\n\n**b. Pass the generated tokens straight to `WangsUiProvider`'s `theme.customColorFamilies`** — this is a real `ThemeProviderProps` field, not a convention you assemble yourself. The provider resolves it to the current mode and injects the CSS variables internally, the exact same pass the built-in families already get (`CssVariablesInjector`) — adding a family is purely additive data, no new component in your tree:\n\n```tsx\nimport { WangsUiProvider } from '@wangs-ui/react-core/api';\nimport { premiumTokens } from './theme/premium';\n\n<WangsUiProvider\n configOptions={{ preset }}\n theme={{ palette, mode, customColorFamilies: { premium: premiumTokens } }}\n>\n <App />\n</WangsUiProvider>;\n```\n\nA second custom family is one more entry in the same object (`{ premium: premiumTokens, verified: verifiedTokens }`) — never a second wrapper.\n\n## 3. Use it — same as any built-in family\n\nFrom here on, every component just uses ordinary Tailwind utility classes, exactly like `bg-success-container`/`text-on-success-container`:\n\n```tsx\nfunction PremiumBadge() {\n return <span className=\"bg-premium-container text-on-premium-container\">Premium</span>;\n}\n```\n\nNo import, no hook, no per-component wiring — any component anywhere in the tree can reach for `bg-premium` / `text-on-premium` / `bg-premium-emphasized` / `hover:bg-premium-hover` / etc. the same way it already reaches for the built-in families.\n\nIf a component needs the plain hex value instead of a class (a canvas draw call, an SVG fill, a chart library prop), `useTheme().customColors.premium` is already resolved to the current mode — no manual `premiumTokens[mode]` indexing:\n\n```tsx\nconst { customColors } = useTheme();\ncustomColors.premium.fill; // already the right mode's hex\n```\n\n**React Native** has no `.css`/Tailwind step: skip step **a** in section 2, but still pass `customColorFamilies` to `theme` in step **b** — then read `useTheme().customColors.premium` (etc.) the same way, directly in `style`.\n\n## 4. Regenerating / evolving a custom family\n\nTo change the color, re-run the same command with the new hex:\n\n```bash\npnpm exec wangs-ui-generate-color-family --name=premium --color=#9333ea --out=src/theme/premium.ts\n```\n\nThis rewrites `premium.ts` with the new values and rewrites `premium.css` too (harmless — it's the same static names every time, nothing in your app needs to change because of it).\n";
11
14
  //#endregion
12
15
  //#region skills/wangs-ui/data-table/SKILL.md?raw
13
16
  var SKILL_default$6 = "---\nname: data-table\ndescription: Architecture, workflows, and MCP discovery protocol for building DataTables with sorting, pagination, filtering, selection, and export.\nmetadata:\n owner: wangs-ui\n---\n# Skill: DataTable Architecture & Integration Workflows\n\nUse this skill when implementing data grids, server-paginated tables, filterable listing views, or batch management interfaces with `@wangs-ui/react-core`.\n\n---\n\n## 1. MCP Inspection Protocol (Mandatory Single Source of Truth)\n\nDo **NOT** guess table prop names or hardcode table structures. Query the MCP server dynamically to inspect exact TypeScript signatures, live story implementations, and companion controls:\n\n### Inspect Component Contracts (`component` parameter):\n\n```json\nget_component_api({ \"component\": \"datatable\" })\nget_component_api({ \"component\": \"exportbutton\" })\nget_component_api({ \"component\": \"filtercontainer\" })\nget_component_api({ \"component\": \"bulkactionbutton\" })\n```\n\n### Read Curated Documentation:\n\n```json\nget_documentation({ \"id\": \"datatable\" })\nget_documentation({ \"id\": \"exportbutton\" })\n```\n\n### Inspect Live Story Implementations:\n\n```json\nget_component_examples({ \"component\": \"datatable\", \"variant\": \"Basic\" })\nget_component_examples({ \"component\": \"datatable\", \"variant\": \"CursorPagination\" })\nget_component_examples({ \"component\": \"datatable\", \"variant\": \"Sortable\" })\nget_component_examples({ \"component\": \"datatable\", \"variant\": \"MultipleSelection\" })\nget_component_examples({ \"component\": \"datatable\", \"variant\": \"CustomColumn\" })\nget_component_examples({ \"component\": \"exportbutton\", \"variant\": \"WithTable\" })\n```\n\n### Inspect Knowledge Graph & Usages:\n\n```json\nquery_graph({ \"query\": \"DataTable\" })\nquery_graph({ \"query\": \"useDataTableFetch\" })\n```\n\n---\n\n## 2. Core Architecture & Mental Model\n\nThe Wangs UI `DataTable` is built on a modular, headless-first architecture:\n\n1. **Declarative Column Definitions (`TableColumn<T>[]`)**:\n Columns are configured as typed array objects, not as JSX children. Check `get_component_api({ \"component\": \"datatable\" })` for column field types.\n2. **Table Instance Hook (`useDataTable`)**:\n Coordinates table state (sorting, pagination, selection, column ordering, pinning, visibility).\n3. **Data Fetching Hook (`useDataTableFetch`)**:\n Feeds server-side data, handles loading indicators, manages query parameters (`search`, `filter`, `sort`, `page`, `limit`), and debounces requests automatically.\n4. **Ecosystem Companions**:\n - `FilterContainer` & `FilterToggleButton`: Filter popovers and faceted search.\n - `ExportButton`: Client/server export to Excel, CSV, PDF, or Print.\n - `BulkActionButton`: Contextual batch actions triggered when rows are selected.\n - `CustomColumn`: User-controlled column ordering, visibility toggling, and pinning.\n\n---\n\n## 3. Mandatory Implementation Rules\n\n1. **Query MCP for Current Code Patterns**: Always run `get_component_examples` for `datatable` before drafting code.\n2. **Strict Subpath Imports**: Import via `@wangs-ui/react-core/primitive/datatable` and companion primitive paths.\n3. **Always Translate Visible Copy**: All column header labels, empty state messages, and action button labels must be wrapped in `t('...')` from `@wangs-ui/react-i18n`.\n4. **Stable Row Identity**: Always configure a unique key identifier (`dataKey` / `rowId`) for stable selection and row identity.\n5. **Zero Redundant Table Wrappers**: Never wrap `<DataTable />` in an isolated `<div>` or `<Box>` solely to set width or margin. Wangs UI DataTable manages its own container scroll and layout dimensions out of the box.\n";
@@ -31,82 +34,83 @@ var SKILL_default$1 = "---\nname: universal-layout\ndescription: Universal layou
31
34
  var SKILL_default = "---\nname: wangs-ui-components\ndescription: MCP Discovery Protocol, modular subpath import map, and primitive substitution map for Wangs UI applications.\nmetadata:\n owner: wangs-ui\n---\n\n# Skill: Wangs UI Component Fundamentals & MCP Protocol\n\nUse this skill to navigate the Wangs UI ecosystem. **MCP is the single source of truth** for all component APIs, props, variants, slots, and examples. Do not hardcode or assume props — always query MCP dynamically.\n\n---\n\n## 1. The MCP Discovery Protocol (Single Source of Truth)\n\nDo **NOT** guess component props, Pass-Through (`pt`) slots, or event names. Always query the MCP server dynamically:\n\n```mermaid\ngraph TD\n A[Identify Component / Primitive Needed] --> B[list_catalog to confirm name/id]\n B --> C[get_component_api for typed contract]\n B --> D[get_documentation for curated guidance]\n C --> E{Need live story / variant code?}\n D --> E\n E -->|Yes| F[get_component_examples]\n E -->|No| G[Implement with Subpath Import Map]\n F --> G\n```\n\n### Discovery Steps:\n\n1. **Discover Catalog Entries**:\n ```json\n list_catalog({ \"query\": \"button\" })\n list_catalog({ \"category\": \"component\" })\n ```\n2. **Inspect Typed Contracts & Props** (`component` parameter):\n ```json\n get_component_api({ \"component\": \"button\" })\n get_component_api({ \"component\": \"datatable\" })\n get_component_api({ \"component\": \"field\" })\n ```\n3. **Read Curated Narrative Documentation** (`id` parameter):\n ```json\n get_documentation({ \"id\": \"button\" })\n get_documentation({ \"id\": \"foundation\" })\n get_documentation({ \"id\": \"form\" })\n ```\n4. **Fetch Live Storybook TSX Examples**:\n ```json\n get_component_examples({ \"component\": \"button\", \"variant\": \"Sizes\" })\n get_component_examples({ \"component\": \"datatable\", \"variant\": \"Basic\" })\n ```\n5. **Explore Knowledge Graph Relationships**:\n ```json\n query_graph({ \"query\": \"DataTable\" })\n query_graph({ \"query\": \"useForm\" })\n ```\n\n---\n\n## 2. Modular Subpath Import Map\n\nAlways import via specific subpath modules to guarantee tree-shaking and avoid bundling entire packages:\n\n| Ecosystem Layer | Subpath Pattern | Example Imports |\n| :--- | :--- | :--- |\n| **Core Primitives** | `@wangs-ui/react-core/primitive/<name>` | `Button`, `Input`, `Select`, `DataTable`, `Dialog`, `Modal`, `Field`, `Form`, `Card`, `Badge` |\n| **Layout Primitives** | `@wangs-ui/foundation/layout` | `Stack`, `HStack`, `VStack`, `Flex`, `Grid`, `Box`, `Container`, `Section`, `ScrollArea` |\n| **Typography Primitives** | `@wangs-ui/foundation/theme` | `Text`, `Code`, `Kbd`, `Link`, `Blockquote`, `List`, `Mark` |\n| **Blocks & Shells** | `@wangs-ui/react-core/blocks/<name>` | `AppLayout`, `Sidebar`, `TableToolbar` |\n| **Form Engine** | `@wangs-ui/form/core`, `@wangs-ui/form/react` | Headless form state, hooks, validation adapters |\n| **Universal Navigation** | `@wangs-ui/react-navigation/web`, `@wangs-ui/react-navigation/native` | `buildGraph`, `paramRoute`, platform router bridges |\n| **Theme & Providers** | `@wangs-ui/react-core/api`, `@wangs-ui/foundation/theme` | `WangsUiProvider`, `ThemeProvider`, `useTheme` |\n| **Vector Icons** | `@wangs-ui/react-icons` | `SearchLine`, `AddLine`, `DeleteBin6Line`, `CheckLine` |\n| **i18n & Localization** | `@wangs-ui/react-i18n` | `useI18n`, `t`, `WangsUiI18nProvider` |\n\n---\n\n## 3. Primitive Substitution Map\n\nZero raw HTML elements. Never write raw HTML tags when a Wangs UI primitive exists:\n\n| Forbidden Raw HTML | Mandatory Wangs UI Primitive | Import Path | MCP Catalog / Doc ID |\n| :--- | :--- | :--- | :--- |\n| `<button>` | `Button` | `@wangs-ui/react-core/primitive/button` | `button` |\n| `<input type=\"text\">` | `Input` | `@wangs-ui/react-core/primitive/input` | `input` |\n| `<input type=\"number\">` | `NumberInput` | `@wangs-ui/react-core/primitive/numberinput` | `numberinput` |\n| `<input type=\"checkbox\">` | `Checkbox` | `@wangs-ui/react-core/primitive/checkbox` | `checkbox` |\n| `<select>` | `Select` / `MultiSelect` | `@wangs-ui/react-core/primitive/select` | `select`, `multiselect` |\n| `<textarea>` | `Textarea` | `@wangs-ui/react-core/primitive/textarea` | `textarea` |\n| `<form>` | `Form` / `DialogForm` | `@wangs-ui/react-core/primitive/form` | `form`, `dialogform` |\n| `<dialog>` / alert modal | `Modal` / `Dialog` | `@wangs-ui/react-core/primitive/modal` | `modal`, `dialog` |\n| `<table>` | `DataTable` | `@wangs-ui/react-core/primitive/datatable` | `datatable` |\n| `<div>` (layout / flex / grid) | `Stack`, `HStack`, `Flex`, `Grid`, `Box` | `@wangs-ui/foundation/layout` | `universal-layout` |\n| `<div>` (surface card) | `Card` | `@wangs-ui/react-core/primitive/card` | `card` |\n| Pill / status chip | `Badge` | `@wangs-ui/react-core/primitive/badge` | `badge` |\n| `<h1>` - `<h6>` | `<Text variant=\"...\">` | `@wangs-ui/foundation/theme` | `foundation` |\n| `<p>`, `<span>` (body text) | `<Text variant=\"...\">` | `@wangs-ui/foundation/theme` | `foundation` |\n| `<a>` | `Link` | `@wangs-ui/foundation/theme` | `foundation` |\n| `<code>` | `Code` | `@wangs-ui/foundation/theme` | `foundation` |\n| `<kbd>` | `Kbd` | `@wangs-ui/foundation/theme` | `foundation` |\n| `<blockquote>` | `Blockquote` | `@wangs-ui/foundation/theme` | `foundation` |\n| `<ul>`, `<ol>` | `List` | `@wangs-ui/foundation/theme` | `foundation` |\n";
32
35
  //#endregion
33
36
  //#region rules/wangs-ui/default-props-precedence.md?raw
34
- var default_props_precedence_default = "---\ntrigger: model_decision\ndescription: \"Apply when deciding whether a prop belongs in global defaultProps config or per-instance JSX.\"\nowner: wangs-ui\nmetadata:\n owner: wangs-ui\n---\n# Wangs UI Fact: `configOptions.defaultProps` Precedence\n\n`WangsUiProvider.configOptions.defaultProps` lets any project set global\ndefault prop values per component. Per-instance JSX props always win over\nthat global default — the merge order is global-default-then-instance, never\nthe reverse. This is a `WangsUiProvider` mechanism, true in any project that\nuses it, independent of what values a given project actually configures.\n";
37
+ var default_props_precedence_default = "---\ntrigger: model_decision\ndescription: \"Apply when deciding whether a prop belongs in global defaultProps config or per-instance JSX.\"\nmetadata:\n owner: wangs-ui\n---\n# Wangs UI Fact: `configOptions.defaultProps` Precedence\n\n`WangsUiProvider.configOptions.defaultProps` lets any project set global\ndefault prop values per component. Per-instance JSX props always win over\nthat global default — the merge order is global-default-then-instance, never\nthe reverse. This is a `WangsUiProvider` mechanism, true in any project that\nuses it, independent of what values a given project actually configures.\n";
35
38
  //#endregion
36
39
  //#region rules/wangs-ui/forms-destructuring-lock.md?raw
37
- var forms_destructuring_lock_default = "---\ntrigger: model_decision\ndescription: \"Apply when writing a <Field> render-prop callback in a Wangs UI Form/DialogForm.\"\nowner: wangs-ui\nmetadata:\n owner: wangs-ui\n---\n# Wangs UI Fact: `<Field>` Render-Prop Must Destructure `{ fieldProps, fieldState }`\n\n`<Field>`'s callback render prop must destructure as `{({ fieldProps, fieldState })}`.\nPassing a single parameter instead (`{(field) => ...}`) produces `undefined`\n`value`/`onChange` and permanently locks the input — the field never becomes\ninteractive, even on initial render. This is a real `@wangs-ui/form` quirk,\nnot a project convention; it reproduces the same way in any consumer.\n\n`<Field>` is generic — `fieldProps.onChange` is already typed to the field's\nactual value type. Pass it straight through (`fieldProps.onChange`); don't\nwrap it in a defensive `typeof`/`e?.target?.value` extraction, that's fighting\na type the field already guarantees.\n";
40
+ var forms_destructuring_lock_default = "---\ntrigger: model_decision\ndescription: \"Apply when writing a <Field> render-prop callback in a Wangs UI Form/DialogForm.\"\nmetadata:\n owner: wangs-ui\n---\n# Wangs UI Fact: `<Field>` Render-Prop Must Destructure `{ fieldProps, fieldState }`\n\n`<Field>`'s callback render prop must destructure as `{({ fieldProps, fieldState })}`.\nPassing a single parameter instead (`{(field) => ...}`) produces `undefined`\n`value`/`onChange` and permanently locks the input — the field never becomes\ninteractive, even on initial render. This is a real `@wangs-ui/form` quirk,\nnot a project convention; it reproduces the same way in any consumer.\n\n`<Field>` is generic — `fieldProps.onChange` is already typed to the field's\nactual value type. Pass it straight through (`fieldProps.onChange`); don't\nwrap it in a defensive `typeof`/`e?.target?.value` extraction, that's fighting\na type the field already guarantees.\n";
38
41
  //#endregion
39
42
  //#region rules/wangs-ui/no-raw-html.md?raw
40
- var no_raw_html_default = "---\ntrigger: model_decision\ndescription: \"Apply when writing JSX markup — enforce Wangs UI primitives over raw HTML elements.\"\nowner: wangs-ui\nmetadata:\n owner: wangs-ui\n---\n# Rule: No Raw HTML Controls\n\nZero raw HTML elements. Use Wangs UI primitives and Foundation theme typography.\n\n## Prohibitions\n\n- No `<button>`, `<input>`, `<select>`, `<textarea>`, `<form>`, `<table>`, `<dialog>`.\n- No `<h1>`-`<h6>`, `<p>`, `<span>` for text typography without Wangs UI theme primitives.\n\n## Mandatory Imports\n\n- Primitives via subpath:\n - `@wangs-ui/react-core/primitive/button`\n - `@wangs-ui/react-core/primitive/input`\n - `@wangs-ui/react-core/primitive/datatable`\n - `@wangs-ui/react-core/primitive/modal`\n - `@wangs-ui/react-core/primitive/dialogform`\n - `@wangs-ui/react-core/primitive/card`\n - `@wangs-ui/react-core/primitive/badge`\n- Typography via Foundation theme:\n - `@wangs-ui/foundation/theme` (`Text`, `Code`, `Kbd`, `Link`, `Mark`, `Blockquote`, `List`)\n - Use variants: `display*`, `headline*`, `title*`, `body*`, `label*`.\n";
43
+ var no_raw_html_default = "---\ntrigger: model_decision\ndescription: \"Apply when writing JSX markup — enforce Wangs UI primitives over raw HTML elements.\"\nmetadata:\n owner: wangs-ui\n---\n# Rule: No Raw HTML Controls\n\nZero raw HTML elements. Use Wangs UI primitives and Foundation theme typography.\n\n## Prohibitions\n\n- No `<button>`, `<input>`, `<select>`, `<textarea>`, `<form>`, `<table>`, `<dialog>`.\n- No `<h1>`-`<h6>`, `<p>`, `<span>` for text typography without Wangs UI theme primitives.\n\n## Mandatory Imports\n\n- Primitives via subpath:\n - `@wangs-ui/react-core/primitive/button`\n - `@wangs-ui/react-core/primitive/input`\n - `@wangs-ui/react-core/primitive/datatable`\n - `@wangs-ui/react-core/primitive/modal`\n - `@wangs-ui/react-core/primitive/dialogform`\n - `@wangs-ui/react-core/primitive/card`\n - `@wangs-ui/react-core/primitive/badge`\n- Typography via Foundation theme:\n - `@wangs-ui/foundation/theme` (`Text`, `Code`, `Kbd`, `Link`, `Mark`, `Blockquote`, `List`)\n - Use variants: `display*`, `headline*`, `title*`, `body*`, `label*`.\n";
41
44
  //#endregion
42
45
  //#region rules/wangs-ui/no-redundant-wrappers.md?raw
43
- var no_redundant_wrappers_default = "---\ntrigger: model_decision\ndescription: \"Apply when writing or reviewing JSX layout for redundant wrapper containers.\"\nowner: wangs-ui\nmetadata:\n owner: wangs-ui\n---\n# Rule: No Redundant Layout & Container Wrappers\n\n> **Severity**: **ARCHITECTURAL CODE SMELL / LINT FAILURE**.\n\nComponents must not be wrapped in an unnecessary layout element. A `<div>` (or `Box`/`Stack`) wrapping a single component without active layout coordination is strictly prohibited — see `.agents/skills/universal-layout` and `.agents/rules/no-inline-component-styling.md` §2 for which primitive replaces a raw `<div>` when layout coordination is genuinely needed.\n\n---\n\n## 1. Prohibited Anti-Patterns\n\n1. **Single-Child Wrapper `<div>`**:\n Never wrap a single component (e.g. `<DataTable />`, `<Card />`, `<Tabs />`, `<Input />`, `<Button />`) inside an isolated `<div>` that has no layout siblings.\n2. **Intermediate Tab/Card Wrappers**:\n Do not insert pass-through `<div>` wrappers between `<Card>`/`<Tabs>` and feature content:\n ```tsx\n // ❌ Bad — Redundant intermediate div wrapping a single tab component\n function TabbedPage() {\n return (\n <Card>\n <Tabs ... />\n <div>\n {activeTab === 'general' ? <GeneralTab /> : <AdvancedTab />}\n </div>\n </Card>\n );\n }\n\n // ✅ Good — Render active tab directly as Card child\n function TabbedPage() {\n return (\n <Card>\n <Tabs ... />\n {activeTab === 'general' ? <GeneralTab /> : <AdvancedTab />}\n </Card>\n );\n }\n ```\n3. **Redundant Table Wrappers**:\n Never wrap `<DataTable />` in a `Box`/`div` solely to set width or margin. Wangs UI DataTable manages its own container scroll and dimensions out of the box.\n\n---\n\n## 2. When a Layout Wrapper Is Permitted\n\nA layout wrapper is **ONLY** warranted when coordinating **2 or more siblings** in a structural layout — and even then it's a `universal-layout` primitive, not a raw `<div>`:\n\n1. **Alignment bars**: header title + search + action buttons → `<HStack justify=\"between\">`.\n2. **Multi-column/card grids**: responsive multi-card or multi-field layouts → `<Grid columns={{ compact: 1, medium: 2, expanded: 3 }}>`.\n3. **Fragment Alternative**: when returning multiple adjacent elements without layout coordination, use React Fragment (`<>...</>`) — not an empty wrapper.\n";
46
+ var no_redundant_wrappers_default = "---\ntrigger: model_decision\ndescription: \"Apply when writing or reviewing JSX layout for redundant wrapper containers.\"\nmetadata:\n owner: wangs-ui\n---\n# Rule: No Redundant Layout & Container Wrappers\n\n> **Severity**: **ARCHITECTURAL CODE SMELL / LINT FAILURE**.\n\nComponents must not be wrapped in an unnecessary layout element. A `<div>` (or `Box`/`Stack`) wrapping a single component without active layout coordination is strictly prohibited — see `.agents/skills/universal-layout` and `.agents/rules/no-inline-component-styling.md` §2 for which primitive replaces a raw `<div>` when layout coordination is genuinely needed.\n\n---\n\n## 1. Prohibited Anti-Patterns\n\n1. **Single-Child Wrapper `<div>`**:\n Never wrap a single component (e.g. `<DataTable />`, `<Card />`, `<Tabs />`, `<Input />`, `<Button />`) inside an isolated `<div>` that has no layout siblings.\n2. **Intermediate Tab/Card Wrappers**:\n Do not insert pass-through `<div>` wrappers between `<Card>`/`<Tabs>` and feature content:\n ```tsx\n // ❌ Bad — Redundant intermediate div wrapping a single tab component\n function TabbedPage() {\n return (\n <Card>\n <Tabs ... />\n <div>\n {activeTab === 'general' ? <GeneralTab /> : <AdvancedTab />}\n </div>\n </Card>\n );\n }\n\n // ✅ Good — Render active tab directly as Card child\n function TabbedPage() {\n return (\n <Card>\n <Tabs ... />\n {activeTab === 'general' ? <GeneralTab /> : <AdvancedTab />}\n </Card>\n );\n }\n ```\n3. **Redundant Table Wrappers**:\n Never wrap `<DataTable />` in a `Box`/`div` solely to set width or margin. Wangs UI DataTable manages its own container scroll and dimensions out of the box.\n\n---\n\n## 2. When a Layout Wrapper Is Permitted\n\nA layout wrapper is **ONLY** warranted when coordinating **2 or more siblings** in a structural layout — and even then it's a `universal-layout` primitive, not a raw `<div>`:\n\n1. **Alignment bars**: header title + search + action buttons → `<HStack justify=\"between\">`.\n2. **Multi-column/card grids**: responsive multi-card or multi-field layouts → `<Grid columns={{ compact: 1, medium: 2, expanded: 3 }}>`.\n3. **Fragment Alternative**: when returning multiple adjacent elements without layout coordination, use React Fragment (`<>...</>`) — not an empty wrapper.\n";
44
47
  //#endregion
45
48
  //#region rules/wangs-ui/react19-checklist-and-reference.md?raw
46
- var react19_checklist_and_reference_default = "---\ntrigger: model_decision\ndescription: \"Apply when doing a final review pass on React 19 component or hook code for compiler-optimization compliance.\"\nowner: wangs-ui\nmetadata:\n owner: wangs-ui\n---\n# Rule: React 19 — Review Checklist & Quick Reference\n\n## Checking Compiler Optimization\n\n- **Build output** — compiled files import `react/compiler-runtime` and contain `Symbol.for(\"react.memo_cache_sentinel\")`; if absent, the compiler never ran.\n- **ESLint / Oxlint** — compiler's recommended rules flag Rules-of-React violations at lint time.\n\n## Review Checklist\n\n- [ ] Compiler is wired up and confirmed active — set it up if missing (see `react19-tooling-and-opt-out.md`); never treat a missing compiler as licence to hand-roll memoization\n- [ ] Compiler confirmed active (build output imports `react/compiler-runtime`) before removing any _existing_ manual memoization\n- [ ] No new `useMemo`/`useCallback`/`React.memo` added without documented reason (confirmed bail-out, or external boundary)\n- [ ] No prop/state/context mutation anywhere in render\n- [ ] All hooks called unconditionally at top level, same order every render\n- [ ] Side effects live in `useEffect`/event handlers, never during render\n- [ ] Components are `PascalCase`; hooks are `camelCase` and prefixed `use`\n- [ ] `ref` accepted as normal prop instead of `forwardRef`\n- [ ] Action/optimistic-update state modeled as discriminated union, not optional fields\n- [ ] Mutually exclusive prop combinations modeled as discriminated union `Props` type\n- [ ] Any `\"use no memo\"` usage has a comment explaining why\n\n## Quick Reference\n\n| Situation | Do |\n| ----------------------------------------------------------- | ------------------------------------------------------------- |\n| Tempted to write `useMemo`/`useCallback` | Don't — write plain expression, let compiler decide |\n| Need a ref on a function component | Accept `ref` as a prop, skip `forwardRef` |\n| Form/async state with distinct outcomes | Discriminated union via `useActionState`, not optional fields |\n| Callback needs latest props/state without re-running effect | `useEffectEvent` |\n| A hook/library is known-incompatible with compiler | `\"use no memo\"` at top of that function, with comment |\n| Checking if optimization is happening | Build output imports `react/compiler-runtime` (has `react.memo_cache_sentinel`) + compiler lint rules |\n";
49
+ var react19_checklist_and_reference_default = "---\ntrigger: model_decision\ndescription: \"Apply when doing a final review pass on React 19 component or hook code for compiler-optimization compliance.\"\nmetadata:\n owner: wangs-ui\n---\n# Rule: React 19 — Review Checklist & Quick Reference\n\n## Checking Compiler Optimization\n\n- **Build output** — compiled files import `react/compiler-runtime` and contain `Symbol.for(\"react.memo_cache_sentinel\")`; if absent, the compiler never ran.\n- **ESLint / Oxlint** — compiler's recommended rules flag Rules-of-React violations at lint time.\n\n## Review Checklist\n\n- [ ] Compiler is wired up and confirmed active — set it up if missing (see `react19-tooling-and-opt-out.md`); never treat a missing compiler as licence to hand-roll memoization\n- [ ] Compiler confirmed active (build output imports `react/compiler-runtime`) before removing any _existing_ manual memoization\n- [ ] No new `useMemo`/`useCallback`/`React.memo` added without documented reason (confirmed bail-out, or external boundary)\n- [ ] No prop/state/context mutation anywhere in render\n- [ ] All hooks called unconditionally at top level, same order every render\n- [ ] Side effects live in `useEffect`/event handlers, never during render\n- [ ] Components are `PascalCase`; hooks are `camelCase` and prefixed `use`\n- [ ] `ref` accepted as normal prop instead of `forwardRef`\n- [ ] Action/optimistic-update state modeled as discriminated union, not optional fields\n- [ ] Mutually exclusive prop combinations modeled as discriminated union `Props` type\n- [ ] Any `\"use no memo\"` usage has a comment explaining why\n\n## Quick Reference\n\n| Situation | Do |\n| ----------------------------------------------------------- | ------------------------------------------------------------- |\n| Tempted to write `useMemo`/`useCallback` | Don't — write plain expression, let compiler decide |\n| Need a ref on a function component | Accept `ref` as a prop, skip `forwardRef` |\n| Form/async state with distinct outcomes | Discriminated union via `useActionState`, not optional fields |\n| Callback needs latest props/state without re-running effect | `useEffectEvent` |\n| A hook/library is known-incompatible with compiler | `\"use no memo\"` at top of that function, with comment |\n| Checking if optimization is happening | Build output imports `react/compiler-runtime` (has `react.memo_cache_sentinel`) + compiler lint rules |\n";
47
50
  //#endregion
48
51
  //#region rules/wangs-ui/react19-compiler-render-patterns.md?raw
49
- var react19_compiler_render_patterns_default = "---\ntrigger: model_decision\ndescription: \"Apply when writing or reviewing render logic in a React 19 component.\"\nowner: wangs-ui\nmetadata:\n owner: wangs-ui\n---\n# Rule: React 19 — Compiler-Friendly Render Patterns\n\n- Creating new object/array/function literals inline in render (`style={{ color }}`, `onClick={() => ...}`) is fine — stop manually hoisting or `useMemo`-wrapping these preemptively; the compiler memoizes them if it determines it's worthwhile.\n- Avoid module-level mutable variables read or written during render — that state is invisible to the compiler and breaks idempotence.\n- Don't use `useRef` to store a value that should trigger a re-render when it changes — refs are an imperative escape hatch, not state, and the compiler treats them as such.\n- Keep components small and composable. The compiler optimizes per component/hook boundary, so a single 300-line component gives it far less to work with than several focused ones.\n";
52
+ var react19_compiler_render_patterns_default = "---\ntrigger: model_decision\ndescription: \"Apply when writing or reviewing render logic in a React 19 component.\"\nmetadata:\n owner: wangs-ui\n---\n# Rule: React 19 — Compiler-Friendly Render Patterns\n\n- Creating new object/array/function literals inline in render (`style={{ color }}`, `onClick={() => ...}`) is fine — stop manually hoisting or `useMemo`-wrapping these preemptively; the compiler memoizes them if it determines it's worthwhile.\n- Avoid module-level mutable variables read or written during render — that state is invisible to the compiler and breaks idempotence.\n- Don't use `useRef` to store a value that should trigger a re-render when it changes — refs are an imperative escape hatch, not state, and the compiler treats them as such.\n- Keep components small and composable. The compiler optimizes per component/hook boundary, so a single 300-line component gives it far less to work with than several focused ones.\n";
50
53
  //#endregion
51
54
  //#region rules/wangs-ui/react19-naming-conventions.md?raw
52
- var react19_naming_conventions_default = "---\ntrigger: model_decision\ndescription: \"Apply when naming a React 19 component, hook, or prop.\"\nowner: wangs-ui\nmetadata:\n owner: wangs-ui\n---\n# Rule: React 19 — Naming Conventions\n\nThe compiler identifies what to optimize by naming heuristics, same as the Rules of Hooks linter:\n\n| Kind | Convention | Notes |\n| ------------------------------------------------------------------------ | ------------------------------- | ----------------------------------------------------------------------------- |\n| Components | `PascalCase`, returns JSX | Compiler treats it as a component to optimize |\n| Custom hooks | `camelCase`, prefixed `use` | Required for both Rules-of-Hooks lint and compiler analysis |\n| Plain helper functions that return JSX-like values but aren't components | Avoid `PascalCase`/`use` naming | Prevents the compiler (and other devs) from mistaking it for a component/hook |\n";
55
+ var react19_naming_conventions_default = "---\ntrigger: model_decision\ndescription: \"Apply when naming a React 19 component, hook, or prop.\"\nmetadata:\n owner: wangs-ui\n---\n# Rule: React 19 — Naming Conventions\n\nThe compiler identifies what to optimize by naming heuristics, same as the Rules of Hooks linter:\n\n| Kind | Convention | Notes |\n| ------------------------------------------------------------------------ | ------------------------------- | ----------------------------------------------------------------------------- |\n| Components | `PascalCase`, returns JSX | Compiler treats it as a component to optimize |\n| Custom hooks | `camelCase`, prefixed `use` | Required for both Rules-of-Hooks lint and compiler analysis |\n| Plain helper functions that return JSX-like values but aren't components | Avoid `PascalCase`/`use` naming | Prevents the compiler (and other devs) from mistaking it for a component/hook |\n";
53
56
  //#endregion
54
57
  //#region rules/wangs-ui/react19-no-manual-memoization.md?raw
55
- var react19_no_manual_memoization_default = "---\ntrigger: model_decision\ndescription: \"Apply when writing or reviewing a React 19 component or hook that uses or is tempted to use useMemo/useCallback/React.memo.\"\nowner: wangs-ui\nmetadata:\n owner: wangs-ui\n---\n# Rule: React 19 — Stop Hand-Rolling Memoization\n\n> **Prerequisite:** this rule holds only once the React Compiler is enabled. If the project isn't wired up yet, set it up first (see `react19-tooling-and-opt-out.md`) — the absence of the compiler is never a reason to hand-roll memoization.\n\nDon't reach for `useMemo`, `useCallback`, or `React.memo` — the compiler adds this automatically wherever it determines it helps. This is a requirement, not a stylistic preference: manual memoization is the exception and must be justified.\n\n```tsx\n// ❌ Old habit — noisy, and a mismatched dependency array is a whole class of bugs\nconst filteredUsers = useMemo(() => users.filter((u) => u.isActive), [users]);\nconst handleClick = useCallback(() => onSelect(user.id), [onSelect, user.id]);\n\n// ✅ New default — just write the logic; the compiler memoizes what's worth memoizing\nconst filteredUsers = users.filter((u) => u.isActive);\nconst handleClick = () => onSelect(user.id);\n```\n\n## When manual memoization is still justified:\n\n- You've **confirmed a compiler bail-out** on a genuine hot path via profiling, and fixing the underlying Rules-of-React violation isn't possible right now.\n- A value must have **stable referential identity across a boundary the compiler can't see** — e.g. passed into a non-React library, a WebSocket subscription, or a third-party hook incompatible with the compiler (`react-hook-form`'s `useForm`, `@tanstack/react-table`'s `useReactTable` are known cases).\n- Keep any manual memoization it produces isolated and commented with _why_, so it doesn't silently rot into a bail-out later when the code around it changes.\n";
58
+ var react19_no_manual_memoization_default = "---\ntrigger: model_decision\ndescription: \"Apply when writing or reviewing a React 19 component or hook that uses or is tempted to use useMemo/useCallback/React.memo.\"\nmetadata:\n owner: wangs-ui\n---\n# Rule: React 19 — Stop Hand-Rolling Memoization\n\n> **Prerequisite:** this rule holds only once the React Compiler is enabled. If the project isn't wired up yet, set it up first (see `react19-tooling-and-opt-out.md`) — the absence of the compiler is never a reason to hand-roll memoization.\n\nDon't reach for `useMemo`, `useCallback`, or `React.memo` — the compiler adds this automatically wherever it determines it helps. This is a requirement, not a stylistic preference: manual memoization is the exception and must be justified.\n\n```tsx\n// ❌ Old habit — noisy, and a mismatched dependency array is a whole class of bugs\nconst filteredUsers = useMemo(() => users.filter((u) => u.isActive), [users]);\nconst handleClick = useCallback(() => onSelect(user.id), [onSelect, user.id]);\n\n// ✅ New default — just write the logic; the compiler memoizes what's worth memoizing\nconst filteredUsers = users.filter((u) => u.isActive);\nconst handleClick = () => onSelect(user.id);\n```\n\n## When manual memoization is still justified:\n\n- You've **confirmed a compiler bail-out** on a genuine hot path via profiling, and fixing the underlying Rules-of-React violation isn't possible right now.\n- A value must have **stable referential identity across a boundary the compiler can't see** — e.g. passed into a non-React library, a WebSocket subscription, or a third-party hook incompatible with the compiler (`react-hook-form`'s `useForm`, `@tanstack/react-table`'s `useReactTable` are known cases).\n- Keep any manual memoization it produces isolated and commented with _why_, so it doesn't silently rot into a bail-out later when the code around it changes.\n";
56
59
  //#endregion
57
60
  //#region rules/wangs-ui/react19-primitives-typing.md?raw
58
- var react19_primitives_typing_default = "---\ntrigger: model_decision\ndescription: \"Apply when typing React 19 primitives — ref props, useActionState, useOptimistic, use(), useEffectEvent.\"\nowner: wangs-ui\nmetadata:\n owner: wangs-ui\n---\n# Rule: React 19 — Typing React 19 Primitives\n\n## `ref` as a Normal Prop\n\n`forwardRef` is no longer required for most cases; function components accept `ref` directly.\n\n```tsx\ntype InputProps = {\n ref?: React.Ref<HTMLInputElement>;\n placeholder?: string;\n};\n\nfunction TextInput({ ref, placeholder }: InputProps) {\n return <input ref={ref} placeholder={placeholder} />;\n}\n```\n\n## Actions with `useActionState`\n\nType the state and payload as generics; model the result as a discriminated union rather than optional fields.\n\n```tsx\ntype FormState = { status: 'idle' } | { status: 'error'; message: string } | { status: 'success' };\n\nconst [state, formAction, isPending] = useActionState<FormState, FormData>(\n async (_previous, formData) => {\n const email = formData.get('email');\n if (typeof email !== 'string' || !email.includes('@')) {\n return { status: 'error', message: 'Invalid email' };\n }\n await submit(email);\n return { status: 'success' };\n },\n { status: 'idle' },\n);\n```\n\n## Optimistic Updates with `useOptimistic`\n\nType both the state and the update shape.\n\n```tsx\nconst [optimisticTodos, addOptimisticTodo] = useOptimistic<Todo[], Todo>(\n todos,\n (state, newTodo) => [...state, newTodo],\n);\n```\n\n## Reading Promises or Context with `use()`\n\nType the resolved value, not the promise wrapper; `use()` is not a hook and may be called conditionally.\n\n```tsx\nfunction Comments({ commentsPromise }: { commentsPromise: Promise<Comment[]> }) {\n const comments = use(commentsPromise); // suspends until resolved\n return (\n <ul>\n {comments.map((c) => (\n <li key={c.id}>{c.text}</li>\n ))}\n </ul>\n );\n}\n```\n\n## Stable Event Callbacks with `useEffectEvent` (React 19.2+)\n\nSeparates \"event\" logic from \"reactive\" effect logic so the callback always sees latest props/state without being listed as an effect dependency.\n\n```tsx\nconst onVisit = useEffectEvent((url: string) => {\n logVisit(url, theme); // always fresh `theme`, never re-triggers the effect\n});\n\nuseEffect(() => {\n onVisit(url);\n}, [url]); // `theme` intentionally omitted — onVisit is stable\n```\n";
61
+ var react19_primitives_typing_default = "---\ntrigger: model_decision\ndescription: \"Apply when typing React 19 primitives — ref props, useActionState, useOptimistic, use(), useEffectEvent.\"\nmetadata:\n owner: wangs-ui\n---\n# Rule: React 19 — Typing React 19 Primitives\n\n## `ref` as a Normal Prop\n\n`forwardRef` is no longer required for most cases; function components accept `ref` directly.\n\n```tsx\ntype InputProps = {\n ref?: React.Ref<HTMLInputElement>;\n placeholder?: string;\n};\n\nfunction TextInput({ ref, placeholder }: InputProps) {\n return <input ref={ref} placeholder={placeholder} />;\n}\n```\n\n## Actions with `useActionState`\n\nType the state and payload as generics; model the result as a discriminated union rather than optional fields.\n\n```tsx\ntype FormState = { status: 'idle' } | { status: 'error'; message: string } | { status: 'success' };\n\nconst [state, formAction, isPending] = useActionState<FormState, FormData>(\n async (_previous, formData) => {\n const email = formData.get('email');\n if (typeof email !== 'string' || !email.includes('@')) {\n return { status: 'error', message: 'Invalid email' };\n }\n await submit(email);\n return { status: 'success' };\n },\n { status: 'idle' },\n);\n```\n\n## Optimistic Updates with `useOptimistic`\n\nType both the state and the update shape.\n\n```tsx\nconst [optimisticTodos, addOptimisticTodo] = useOptimistic<Todo[], Todo>(\n todos,\n (state, newTodo) => [...state, newTodo],\n);\n```\n\n## Reading Promises or Context with `use()`\n\nType the resolved value, not the promise wrapper; `use()` is not a hook and may be called conditionally.\n\n```tsx\nfunction Comments({ commentsPromise }: { commentsPromise: Promise<Comment[]> }) {\n const comments = use(commentsPromise); // suspends until resolved\n return (\n <ul>\n {comments.map((c) => (\n <li key={c.id}>{c.text}</li>\n ))}\n </ul>\n );\n}\n```\n\n## Stable Event Callbacks with `useEffectEvent` (React 19.2+)\n\nSeparates \"event\" logic from \"reactive\" effect logic so the callback always sees latest props/state without being listed as an effect dependency.\n\n```tsx\nconst onVisit = useEffectEvent((url: string) => {\n logVisit(url, theme); // always fresh `theme`, never re-triggers the effect\n});\n\nuseEffect(() => {\n onVisit(url);\n}, [url]); // `theme` intentionally omitted — onVisit is stable\n```\n";
59
62
  //#endregion
60
63
  //#region rules/wangs-ui/react19-props-typing.md?raw
61
- var react19_props_typing_default = "---\ntrigger: model_decision\ndescription: \"Apply when defining a component's Props type or interface.\"\nowner: wangs-ui\nmetadata:\n owner: wangs-ui\n---\n# Rule: React 19 — Typing Props\n\n- `interface` for a component's `Props` — it's an entity shape, often extended.\n- A discriminated union when a component has mutually exclusive prop combinations, instead of a pile of optional props that can contradict each other.\n\n```tsx\n// ❌ Bad — nothing stops passing both `href` and `onClick` incoherently\ninterface ButtonProps {\n label: string;\n href?: string;\n onClick?: () => void;\n}\n\n// ✅ Good — the two variants can't be mixed\ntype ButtonProps =\n | { variant: 'link'; label: string; href: string }\n | { variant: 'action'; label: string; onClick: () => void };\n```\n";
64
+ var react19_props_typing_default = "---\ntrigger: model_decision\ndescription: \"Apply when defining a component's Props type or interface.\"\nmetadata:\n owner: wangs-ui\n---\n# Rule: React 19 — Typing Props\n\n- `interface` for a component's `Props` — it's an entity shape, often extended.\n- A discriminated union when a component has mutually exclusive prop combinations, instead of a pile of optional props that can contradict each other.\n\n```tsx\n// ❌ Bad — nothing stops passing both `href` and `onClick` incoherently\ninterface ButtonProps {\n label: string;\n href?: string;\n onClick?: () => void;\n}\n\n// ✅ Good — the two variants can't be mixed\ntype ButtonProps =\n | { variant: 'link'; label: string; href: string }\n | { variant: 'action'; label: string; onClick: () => void };\n```\n";
62
65
  //#endregion
63
66
  //#region rules/wangs-ui/react19-purity-and-immutability.md?raw
64
- var react19_purity_and_immutability_default = "---\ntrigger: model_decision\ndescription: \"Apply when writing or reviewing a React 19 component or hook body for purity and immutability.\"\nowner: wangs-ui\nmetadata:\n owner: wangs-ui\n---\n# Rule: React 19 — Purity and Immutability (Load-Bearing)\n\nThe compiler assumes your components and hooks are pure. Violating these rules causes the compiler to silently skip optimizing that component:\n\n- **Idempotent renders** — given the same props/state/context, a component must return the same output. No random values, no `Date.now()`, no side effects during render.\n- **Immutability** — never mutate props, state, or context directly. Always create new objects/arrays for changes.\n- **Side effects only in effects or event handlers** — never during render.\n- **Hooks called unconditionally, top-level, same order every render** — no hooks inside conditionals, loops, or nested functions.\n\n```tsx\n// ❌ Mutates a prop — breaks purity and the compiler can't safely memoize this\nfunction TodoList({ todos }: { todos: Todo[] }) {\n todos.sort((a, b) => a.priority - b.priority); // mutates caller's array\n return (\n <ul>\n {todos.map((t) => (\n <li key={t.id}>{t.title}</li>\n ))}\n </ul>\n );\n}\n\n// ✅ Creates a new array — pure, compiler-safe\nfunction TodoList({ todos }: { todos: Todo[] }) {\n const sorted = [...todos].sort((a, b) => a.priority - b.priority);\n return (\n <ul>\n {sorted.map((t) => (\n <li key={t.id}>{t.title}</li>\n ))}\n </ul>\n );\n}\n```\n";
67
+ var react19_purity_and_immutability_default = "---\ntrigger: model_decision\ndescription: \"Apply when writing or reviewing a React 19 component or hook body for purity and immutability.\"\nmetadata:\n owner: wangs-ui\n---\n# Rule: React 19 — Purity and Immutability (Load-Bearing)\n\nThe compiler assumes your components and hooks are pure. Violating these rules causes the compiler to silently skip optimizing that component:\n\n- **Idempotent renders** — given the same props/state/context, a component must return the same output. No random values, no `Date.now()`, no side effects during render.\n- **Immutability** — never mutate props, state, or context directly. Always create new objects/arrays for changes.\n- **Side effects only in effects or event handlers** — never during render.\n- **Hooks called unconditionally, top-level, same order every render** — no hooks inside conditionals, loops, or nested functions.\n\n```tsx\n// ❌ Mutates a prop — breaks purity and the compiler can't safely memoize this\nfunction TodoList({ todos }: { todos: Todo[] }) {\n todos.sort((a, b) => a.priority - b.priority); // mutates caller's array\n return (\n <ul>\n {todos.map((t) => (\n <li key={t.id}>{t.title}</li>\n ))}\n </ul>\n );\n}\n\n// ✅ Creates a new array — pure, compiler-safe\nfunction TodoList({ todos }: { todos: Todo[] }) {\n const sorted = [...todos].sort((a, b) => a.priority - b.priority);\n return (\n <ul>\n {sorted.map((t) => (\n <li key={t.id}>{t.title}</li>\n ))}\n </ul>\n );\n}\n```\n";
65
68
  //#endregion
66
69
  //#region rules/wangs-ui/react19-tooling-and-opt-out.md?raw
67
- var react19_tooling_and_opt_out_default = "---\ntrigger: model_decision\ndescription: 'Apply when configuring React Compiler tooling or opting a component out via \"use no memo\".'\nowner: wangs-ui\nmetadata:\n owner: wangs-ui\n---\n# Rule: React 19 — Tooling Setup & Compiler Opt-Out\n\n## Compiler Setup Is Mandatory\n\n**The compiler is required, not opt-in.** Every Wangs UI project must run it. The entire \"write plain code, no manual memoization\" rule set only holds once the compiler is actually active, so wiring it up is a prerequisite — not an optional enhancement.\n\nPlain `@vitejs/plugin-react` (`react()`), plain Next.js, plain Babel/webpack config, etc. do **not** run the compiler on their own. **Verify it is active before assuming manual memoization can be dropped — and if it isn't wired up, set it up first** instead of falling back to hand-rolled `useMemo`/`useCallback`/`React.memo`.\n\n### Verify it's active\n\n- The build passes the compiler preset through Babel, and `babel-plugin-react-compiler` is installed (e.g. `@vitejs/plugin-react`'s `reactCompilerPreset()` via `@rolldown/plugin-babel`).\n- The build output actually contains compiler artifacts — `import { c as _c } from \"react/compiler-runtime\"` and `Symbol.for(\"react.memo_cache_sentinel\")` in `dist`. Config being present is **not** proof it ran; the emitted output is.\n- The `react/react-compiler` lint rule is enabled.\n\n### Setup (when missing)\n\n```bash\n# Compiler (build-time transform)\npnpm add -D --save-exact babel-plugin-react-compiler@latest\n```\n\n### Lint rules — oxlint\n\nOxlint ships a **native, Rust-based** `react/react-compiler` rule:\n\n```json\n// .oxlintrc.json\n{\n \"plugins\": [\"react\"],\n \"rules\": {\n \"react/react-compiler\": \"error\"\n }\n}\n```\n\nThis single rule reports:\n\n- **Rules-of-React violations** (conditional hooks, reading a ref during render, mutating props) — must-fix bugs.\n- **Compiler bail-outs** — places compiler declined to optimize without rule violation.\n\n### Wiring into Vite 8\n\n```ts\n// vite.config.ts\nimport { defineConfig } from 'vite';\nimport react, { reactCompilerPreset } from '@vitejs/plugin-react';\nimport babel from '@rolldown/plugin-babel';\n\nexport default defineConfig({\n plugins: [\n babel({ presets: [reactCompilerPreset()] }), // must run before react()\n react(),\n ],\n});\n```\n\n```bash\npnpm add -D @rolldown/plugin-babel @babel/core babel-plugin-react-compiler\n```\n\n## Incompatibility Escape Hatch (`\"use no memo\"`)\n\nIf a specific function is genuinely incompatible with compiler (e.g. calls `useForm` from `react-hook-form`), opt out with `\"use no memo\"` directive as **first line of function body** — leave a comment explaining why:\n\n```tsx\nfunction LegacyForm() {\n 'use no memo';\n const form = useForm(); // incompatible with the compiler today\n // ...\n}\n```\n";
70
+ var react19_tooling_and_opt_out_default = "---\ntrigger: model_decision\ndescription: 'Apply when configuring React Compiler tooling or opting a component out via \"use no memo\".'\nmetadata:\n owner: wangs-ui\n---\n# Rule: React 19 — Tooling Setup & Compiler Opt-Out\n\n## Compiler Setup Is Mandatory\n\n**The compiler is required, not opt-in.** Every Wangs UI project must run it. The entire \"write plain code, no manual memoization\" rule set only holds once the compiler is actually active, so wiring it up is a prerequisite — not an optional enhancement.\n\nPlain `@vitejs/plugin-react` (`react()`), plain Next.js, plain Babel/webpack config, etc. do **not** run the compiler on their own. **Verify it is active before assuming manual memoization can be dropped — and if it isn't wired up, set it up first** instead of falling back to hand-rolled `useMemo`/`useCallback`/`React.memo`.\n\n### Verify it's active\n\n- The build passes the compiler preset through Babel, and `babel-plugin-react-compiler` is installed (e.g. `@vitejs/plugin-react`'s `reactCompilerPreset()` via `@rolldown/plugin-babel`).\n- The build output actually contains compiler artifacts — `import { c as _c } from \"react/compiler-runtime\"` and `Symbol.for(\"react.memo_cache_sentinel\")` in `dist`. Config being present is **not** proof it ran; the emitted output is.\n- The `react/react-compiler` lint rule is enabled.\n\n### Setup (when missing)\n\n```bash\n# Compiler (build-time transform)\npnpm add -D --save-exact babel-plugin-react-compiler@latest\n```\n\n### Lint rules — oxlint\n\nOxlint ships a **native, Rust-based** `react/react-compiler` rule:\n\n```json\n// .oxlintrc.json\n{\n \"plugins\": [\"react\"],\n \"rules\": {\n \"react/react-compiler\": \"error\"\n }\n}\n```\n\nThis single rule reports:\n\n- **Rules-of-React violations** (conditional hooks, reading a ref during render, mutating props) — must-fix bugs.\n- **Compiler bail-outs** — places compiler declined to optimize without rule violation.\n\n### Wiring into Vite 8\n\n```ts\n// vite.config.ts\nimport { defineConfig } from 'vite';\nimport react, { reactCompilerPreset } from '@vitejs/plugin-react';\nimport babel from '@rolldown/plugin-babel';\n\nexport default defineConfig({\n plugins: [\n babel({ presets: [reactCompilerPreset()] }), // must run before react()\n react(),\n ],\n});\n```\n\n```bash\npnpm add -D @rolldown/plugin-babel @babel/core babel-plugin-react-compiler\n```\n\n## Incompatibility Escape Hatch (`\"use no memo\"`)\n\nIf a specific function is genuinely incompatible with compiler (e.g. calls `useForm` from `react-hook-form`), opt out with `\"use no memo\"` directive as **first line of function body** — leave a comment explaining why:\n\n```tsx\nfunction LegacyForm() {\n 'use no memo';\n const form = useForm(); // incompatible with the compiler today\n // ...\n}\n```\n";
68
71
  //#endregion
69
72
  //#region rules/wangs-ui/theme-variable-override-breaks-engine.md?raw
70
- var theme_variable_override_breaks_engine_default = "---\ntrigger: model_decision\ndescription: \"Apply when tempted to override a Wangs UI theme CSS variable directly.\"\nowner: wangs-ui\nmetadata:\n owner: wangs-ui\n---\n# Wangs UI Fact: Theme Variables Are Engine-Internal, Never Override Directly\n\nManually defining or overriding `:root`, `[data-mode='dark']`, or `.dark` custom\nproperties owned by `@wangs-ui/foundation/theme` (`--color-*`, `--z-index-*`,\n`--spacing-*`, `--size-*`, `--shadow-*`, `--radius-*`) breaks the theme token\nengine: it destroys automatic light/dark mode transitions, corrupts sub-tree\ndensity overrides made via `<ThemeProvider>`, and causes cross-module visual\nbugs that don't reproduce consistently. This is true for any project consuming\n`@wangs-ui/foundation`, not specific to one app's setup.\n\nThe supported customization surface is `WangsUiProvider.configOptions.preset`\n— discover it via the `design-system`/`wangs-ui-components` skills' MCP calls,\nnever by reading or guessing the CSS variable names.\n";
73
+ var theme_variable_override_breaks_engine_default = "---\ntrigger: model_decision\ndescription: \"Apply when tempted to override a Wangs UI theme CSS variable directly.\"\nmetadata:\n owner: wangs-ui\n---\n# Wangs UI Fact: Theme Variables Are Engine-Internal, Never Override Directly\n\nManually defining or overriding `:root`, `[data-mode='dark']`, or `.dark` custom\nproperties owned by `@wangs-ui/foundation/theme` (`--color-*`, `--z-index-*`,\n`--spacing-*`, `--size-*`, `--shadow-*`, `--radius-*`) breaks the theme token\nengine: it destroys automatic light/dark mode transitions, corrupts sub-tree\ndensity overrides made via `<ThemeProvider>`, and causes cross-module visual\nbugs that don't reproduce consistently. This is true for any project consuming\n`@wangs-ui/foundation`, not specific to one app's setup.\n\nThe supported customization surface is `WangsUiProvider.configOptions.preset`\n— discover it via the `design-system`/`wangs-ui-components` skills' MCP calls,\nnever by reading or guessing the CSS variable names.\n";
71
74
  //#endregion
72
75
  //#region rules/wangs-ui/typescript-assertions-last-resort.md?raw
73
- var typescript_assertions_last_resort_default = "---\ntrigger: model_decision\ndescription: \"Apply when writing or reviewing a type assertion (`as X`) or non-null assertion (`x!`).\"\nowner: wangs-ui\nmetadata:\n owner: wangs-ui\n---\n# Rule: TypeScript — Type Assertions & Non-Null Assertions are a Last Resort\n\n- `as X` and `x!` tell the compiler \"trust me\" — they produce zero runtime safety and actively hide bugs if wrong.\n- Acceptable only when the compiler genuinely cannot know something you do (e.g. a DOM query you've already null-checked, or narrowing a third-party type at a well-tested boundary) — and even then, prefer a type guard or a runtime check over a bare assertion.\n- Never use `as any` or `as unknown as X` to force an incompatible cast — that's `any` wearing a disguise.\n- `x!` should almost always be replaceable by an actual null check or optional chaining (`x?.y`) plus a real fallback.\n";
76
+ var typescript_assertions_last_resort_default = "---\ntrigger: model_decision\ndescription: \"Apply when writing or reviewing a type assertion (`as X`) or non-null assertion (`x!`).\"\nmetadata:\n owner: wangs-ui\n---\n# Rule: TypeScript — Type Assertions & Non-Null Assertions are a Last Resort\n\n- `as X` and `x!` tell the compiler \"trust me\" — they produce zero runtime safety and actively hide bugs if wrong.\n- Acceptable only when the compiler genuinely cannot know something you do (e.g. a DOM query you've already null-checked, or narrowing a third-party type at a well-tested boundary) — and even then, prefer a type guard or a runtime check over a bare assertion.\n- Never use `as any` or `as unknown as X` to force an incompatible cast — that's `any` wearing a disguise.\n- `x!` should almost always be replaceable by an actual null check or optional chaining (`x?.y`) plus a real fallback.\n";
74
77
  //#endregion
75
78
  //#region rules/wangs-ui/typescript-checklist-and-reference.md?raw
76
- var typescript_checklist_and_reference_default = "---\ntrigger: model_decision\ndescription: \"Apply when doing a final TypeScript review pass on a file.\"\nowner: wangs-ui\nmetadata:\n owner: wangs-ui\n---\n# Rule: TypeScript — Review Checklist & Quick Reference\n\n## Review Checklist\n\nBefore considering TypeScript code \"done,\" verify:\n\n- [ ] No `any` anywhere (including implicit `any` from missing annotations)\n- [ ] External/uncertain data enters as `unknown` and is narrowed before use\n- [ ] Variant state is a discriminated union, not optional fields + booleans\n- [ ] `interface` used for object/entity shapes; `type` used for unions/aliases/intersections\n- [ ] No stray `I` prefixes on interfaces\n- [ ] Naming follows casing conventions consistently\n- [ ] `as` / `!` are rare, justified, and can't be replaced by a guard or null check\n- [ ] Exported functions/methods have explicit return types\n- [ ] Switch statements over unions have an exhaustiveness (`never`) check\n- [ ] `tsconfig.json` includes the strictness baseline\n\n## Quick Reference\n\n| Situation | Use |\n| ---------------------------------------------- | ------------------------------------------------------------- |\n| External/uncertain data | `unknown` + narrowing |\n| \"This value is definitely one of these shapes\" | Discriminated union (`type`) |\n| Object with identity, may be extended | `interface` |\n| Union, intersection, tuple, mapped type | `type` |\n| Need to prove a type through logic | Type guard / narrowing |\n| Tempted to write `any` | Stop — use `unknown`, a generic, or a local interface instead |\n";
79
+ var typescript_checklist_and_reference_default = "---\ntrigger: model_decision\ndescription: \"Apply when doing a final TypeScript review pass on a file.\"\nmetadata:\n owner: wangs-ui\n---\n# Rule: TypeScript — Review Checklist & Quick Reference\n\n## Review Checklist\n\nBefore considering TypeScript code \"done,\" verify:\n\n- [ ] No `any` anywhere (including implicit `any` from missing annotations)\n- [ ] External/uncertain data enters as `unknown` and is narrowed before use\n- [ ] Variant state is a discriminated union, not optional fields + booleans\n- [ ] `interface` used for object/entity shapes; `type` used for unions/aliases/intersections\n- [ ] No stray `I` prefixes on interfaces\n- [ ] Naming follows casing conventions consistently\n- [ ] `as` / `!` are rare, justified, and can't be replaced by a guard or null check\n- [ ] Exported functions/methods have explicit return types\n- [ ] Switch statements over unions have an exhaustiveness (`never`) check\n- [ ] `tsconfig.json` includes the strictness baseline\n\n## Quick Reference\n\n| Situation | Use |\n| ---------------------------------------------- | ------------------------------------------------------------- |\n| External/uncertain data | `unknown` + narrowing |\n| \"This value is definitely one of these shapes\" | Discriminated union (`type`) |\n| Object with identity, may be extended | `interface` |\n| Union, intersection, tuple, mapped type | `type` |\n| Need to prove a type through logic | Type guard / narrowing |\n| Tempted to write `any` | Stop — use `unknown`, a generic, or a local interface instead |\n";
77
80
  //#endregion
78
81
  //#region rules/wangs-ui/typescript-discriminated-unions.md?raw
79
- var typescript_discriminated_unions_default = "---\ntrigger: model_decision\ndescription: \"Apply when modeling a variant or multi-shape state in TypeScript.\"\nowner: wangs-ui\nmetadata:\n owner: wangs-ui\n---\n# Rule: TypeScript — Discriminated Unions for Variant State\n\nWhenever a value can be one of several distinct \"shapes\" (loading/success/error states, event types, API response variants), model it as a **discriminated union** with a literal tag field — never as a loose object with optional fields or boolean flags.\n\n```ts\n// ❌ Bad — booleans can contradict each other; unclear which fields are valid together\ninterface FetchState {\n isLoading: boolean;\n isError: boolean;\n data?: User;\n error?: string;\n}\n\n// ✅ Good — only one shape is possible at a time, and the compiler enforces it\ntype FetchState =\n | { status: 'idle' }\n | { status: 'loading' }\n | { status: 'success'; data: User }\n | { status: 'error'; error: string };\n\nfunction render(state: FetchState) {\n switch (state.status) {\n case 'success':\n return state.data.name; // `data` is guaranteed to exist here\n case 'error':\n return state.error; // `error` is guaranteed to exist here\n default:\n return null;\n }\n}\n```\n\nUse a consistent tag field name across a codebase (`kind`, `type`, or `status` — pick one and stick with it) so narrowing patterns stay predictable.\n";
82
+ var typescript_discriminated_unions_default = "---\ntrigger: model_decision\ndescription: \"Apply when modeling a variant or multi-shape state in TypeScript.\"\nmetadata:\n owner: wangs-ui\n---\n# Rule: TypeScript — Discriminated Unions for Variant State\n\nWhenever a value can be one of several distinct \"shapes\" (loading/success/error states, event types, API response variants), model it as a **discriminated union** with a literal tag field — never as a loose object with optional fields or boolean flags.\n\n```ts\n// ❌ Bad — booleans can contradict each other; unclear which fields are valid together\ninterface FetchState {\n isLoading: boolean;\n isError: boolean;\n data?: User;\n error?: string;\n}\n\n// ✅ Good — only one shape is possible at a time, and the compiler enforces it\ntype FetchState =\n | { status: 'idle' }\n | { status: 'loading' }\n | { status: 'success'; data: User }\n | { status: 'error'; error: string };\n\nfunction render(state: FetchState) {\n switch (state.status) {\n case 'success':\n return state.data.name; // `data` is guaranteed to exist here\n case 'error':\n return state.error; // `error` is guaranteed to exist here\n default:\n return null;\n }\n}\n```\n\nUse a consistent tag field name across a codebase (`kind`, `type`, or `status` — pick one and stick with it) so narrowing patterns stay predictable.\n";
80
83
  //#endregion
81
84
  //#region rules/wangs-ui/typescript-explicit-return-types.md?raw
82
- var typescript_explicit_return_types_default = "---\ntrigger: model_decision\ndescription: \"Apply when writing or reviewing an exported function, class method, or public API surface in TypeScript.\"\nowner: wangs-ui\nmetadata:\n owner: wangs-ui\n---\n# Rule: TypeScript — Explicit Return Types on Exported Functions\n\nInference is fine for local, private helpers, but exported functions, class methods, and anything forming a public API should declare an explicit return type. This prevents an internal implementation change from silently widening/narrowing the public contract.\n\n```ts\n// ❌ Return type is inferred and can silently drift\nexport function getActiveUsers(users: User[]) {\n return users.filter((u) => u.active);\n}\n\n// ✅ Explicit, intentional contract\nexport function getActiveUsers(users: User[]): User[] {\n return users.filter((u) => u.active);\n}\n```\n";
85
+ var typescript_explicit_return_types_default = "---\ntrigger: model_decision\ndescription: \"Apply when writing or reviewing an exported function, class method, or public API surface in TypeScript.\"\nmetadata:\n owner: wangs-ui\n---\n# Rule: TypeScript — Explicit Return Types on Exported Functions\n\nInference is fine for local, private helpers, but exported functions, class methods, and anything forming a public API should declare an explicit return type. This prevents an internal implementation change from silently widening/narrowing the public contract.\n\n```ts\n// ❌ Return type is inferred and can silently drift\nexport function getActiveUsers(users: User[]) {\n return users.filter((u) => u.active);\n}\n\n// ✅ Explicit, intentional contract\nexport function getActiveUsers(users: User[]): User[] {\n return users.filter((u) => u.active);\n}\n```\n";
83
86
  //#endregion
84
87
  //#region rules/wangs-ui/typescript-interface-vs-type.md?raw
85
- var typescript_interface_vs_type_default = "---\ntrigger: model_decision\ndescription: \"Apply when deciding between `interface` and `type` for a new TypeScript declaration.\"\nowner: wangs-ui\nmetadata:\n owner: wangs-ui\n---\n# Rule: TypeScript — `interface` vs `type`\n\nBoth can describe object shapes, but they signal different intent. Default rule:\n\n| Use `interface` for... | Use `type` for... |\n| -------------------------------------------------------------------------- | -------------------------------------------------------------------------- |\n| Object / entity shapes (a `User`, a `Product`, a component's `Props`) | Unions (`\"a\" \\| \"b\"`) and discriminated unions |\n| Public API contracts meant to be `implements`-ed by classes | Intersections (`A & B`) |\n| Shapes that consumers may want to **extend/augment** (declaration merging) | Tuples (`[string, number]`) |\n| | Function types / callback signatures |\n| | Mapped, conditional, or utility-derived types (`Partial<T>`, `Pick<T, K>`) |\n| | Aliasing a primitive or another type for readability |\n\n```ts\n// ✅ interface — an entity with identity, extendable\ninterface User {\n id: string;\n email: string;\n role: UserRole;\n}\n\ninterface AdminUser extends User {\n permissions: Permission[];\n}\n\n// ✅ type — union, alias, derived shape\ntype UserRole = 'admin' | 'editor' | 'viewer';\ntype UserId = User['id'];\ntype PartialUser = Partial<User>;\ntype Callback<T> = (value: T) => void;\n```\n\nDon't mix conventions arbitrarily within one file — if a shape is a plain data object that will never need a union/intersection, `interface` is the default; the moment it needs to express \"one of several shapes,\" reach for `type`.\n";
88
+ var typescript_interface_vs_type_default = "---\ntrigger: model_decision\ndescription: \"Apply when deciding between `interface` and `type` for a new TypeScript declaration.\"\nmetadata:\n owner: wangs-ui\n---\n# Rule: TypeScript — `interface` vs `type`\n\nBoth can describe object shapes, but they signal different intent. Default rule:\n\n| Use `interface` for... | Use `type` for... |\n| -------------------------------------------------------------------------- | -------------------------------------------------------------------------- |\n| Object / entity shapes (a `User`, a `Product`, a component's `Props`) | Unions (`\"a\" \\| \"b\"`) and discriminated unions |\n| Public API contracts meant to be `implements`-ed by classes | Intersections (`A & B`) |\n| Shapes that consumers may want to **extend/augment** (declaration merging) | Tuples (`[string, number]`) |\n| | Function types / callback signatures |\n| | Mapped, conditional, or utility-derived types (`Partial<T>`, `Pick<T, K>`) |\n| | Aliasing a primitive or another type for readability |\n\n```ts\n// ✅ interface — an entity with identity, extendable\ninterface User {\n id: string;\n email: string;\n role: UserRole;\n}\n\ninterface AdminUser extends User {\n permissions: Permission[];\n}\n\n// ✅ type — union, alias, derived shape\ntype UserRole = 'admin' | 'editor' | 'viewer';\ntype UserId = User['id'];\ntype PartialUser = Partial<User>;\ntype Callback<T> = (value: T) => void;\n```\n\nDon't mix conventions arbitrarily within one file — if a shape is a plain data object that will never need a union/intersection, `interface` is the default; the moment it needs to express \"one of several shapes,\" reach for `type`.\n";
86
89
  //#endregion
87
90
  //#region rules/wangs-ui/typescript-literal-unions-vs-enums.md?raw
88
- var typescript_literal_unions_vs_enums_default = "---\ntrigger: model_decision\ndescription: \"Apply when modeling a fixed set of string or numeric variants in TypeScript.\"\nowner: wangs-ui\nmetadata:\n owner: wangs-ui\n---\n# Rule: TypeScript — Prefer Literal Unions Over Numeric Enums\n\nString literal unions are simpler, tree-shake better, and produce clearer error messages than TypeScript `enum`. Reserve `enum` (or `as const` object maps) for cases that need reverse lookup or genuinely benefit from a namespaced runtime value.\n\n```ts\n// ✅ Preferred\ntype OrderStatus = 'pending' | 'shipped' | 'delivered' | 'cancelled';\n\n// Acceptable when a namespaced runtime object is actually needed\nconst OrderStatus = {\n Pending: 'pending',\n Shipped: 'shipped',\n} as const;\ntype OrderStatus = (typeof OrderStatus)[keyof typeof OrderStatus];\n```\n";
91
+ var typescript_literal_unions_vs_enums_default = "---\ntrigger: model_decision\ndescription: \"Apply when modeling a fixed set of string or numeric variants in TypeScript.\"\nmetadata:\n owner: wangs-ui\n---\n# Rule: TypeScript — Prefer Literal Unions Over Numeric Enums\n\nString literal unions are simpler, tree-shake better, and produce clearer error messages than TypeScript `enum`. Reserve `enum` (or `as const` object maps) for cases that need reverse lookup or genuinely benefit from a namespaced runtime value.\n\n```ts\n// ✅ Preferred\ntype OrderStatus = 'pending' | 'shipped' | 'delivered' | 'cancelled';\n\n// Acceptable when a namespaced runtime object is actually needed\nconst OrderStatus = {\n Pending: 'pending',\n Shipped: 'shipped',\n} as const;\ntype OrderStatus = (typeof OrderStatus)[keyof typeof OrderStatus];\n```\n";
89
92
  //#endregion
90
93
  //#region rules/wangs-ui/typescript-naming-conventions.md?raw
91
- var typescript_naming_conventions_default = "---\ntrigger: model_decision\ndescription: \"Apply when naming a TypeScript type, variable, or constant.\"\nowner: wangs-ui\nmetadata:\n owner: wangs-ui\n---\n# Rule: TypeScript — Naming Conventions\n\n| Kind | Convention | Example |\n| ---------------------------------------------------------- | ------------------------------------------- | ----------------------------------- |\n| Types, interfaces, classes, enums | `PascalCase` | `UserProfile`, `OrderStatus` |\n| Interfaces | `PascalCase`, **no `I` prefix** | `User`, not `IUser` |\n| Type aliases | `PascalCase` | `type ApiResponse<T> = ...` |\n| Variables, functions, methods, properties | `camelCase` | `getUserById`, `isValid` |\n| Booleans | `camelCase` with `is/has/should/can` prefix | `isLoading`, `hasPermission` |\n| True constants (module-level, never reassigned, primitive) | `UPPER_SNAKE_CASE` | `MAX_RETRIES`, `DEFAULT_TIMEOUT_MS` |\n| Enum members | `PascalCase` | `enum Status { Active, Archived }` |\n| Generic type parameters (simple, single-purpose) | Single uppercase letter | `T`, `K`, `V`, `E` for errors |\n| Generic type parameters (multiple / non-obvious) | Descriptive, prefixed with `T` | `TInput`, `TOutput`, `TContext` |\n| Discriminated union tag field | Consistent across the codebase | `kind`, `type`, or `status` |\n| Files with a single exported entity | Match the entity name | `UserProfile.ts`, `useAuth.ts` |\n\nNaming should describe **intent**, not implementation — `fetchUser` not `getUserFromApiEndpoint`; `retryCount` not `numRetries2`.\n";
94
+ var typescript_naming_conventions_default = "---\ntrigger: model_decision\ndescription: \"Apply when naming a TypeScript type, variable, or constant.\"\nmetadata:\n owner: wangs-ui\n---\n# Rule: TypeScript — Naming Conventions\n\n| Kind | Convention | Example |\n| ---------------------------------------------------------- | ------------------------------------------- | ----------------------------------- |\n| Types, interfaces, classes, enums | `PascalCase` | `UserProfile`, `OrderStatus` |\n| Interfaces | `PascalCase`, **no `I` prefix** | `User`, not `IUser` |\n| Type aliases | `PascalCase` | `type ApiResponse<T> = ...` |\n| Variables, functions, methods, properties | `camelCase` | `getUserById`, `isValid` |\n| Booleans | `camelCase` with `is/has/should/can` prefix | `isLoading`, `hasPermission` |\n| True constants (module-level, never reassigned, primitive) | `UPPER_SNAKE_CASE` | `MAX_RETRIES`, `DEFAULT_TIMEOUT_MS` |\n| Enum members | `PascalCase` | `enum Status { Active, Archived }` |\n| Generic type parameters (simple, single-purpose) | Single uppercase letter | `T`, `K`, `V`, `E` for errors |\n| Generic type parameters (multiple / non-obvious) | Descriptive, prefixed with `T` | `TInput`, `TOutput`, `TContext` |\n| Discriminated union tag field | Consistent across the codebase | `kind`, `type`, or `status` |\n| Files with a single exported entity | Match the entity name | `UserProfile.ts`, `useAuth.ts` |\n\nNaming should describe **intent**, not implementation — `fetchUser` not `getUserFromApiEndpoint`; `retryCount` not `numRetries2`.\n";
92
95
  //#endregion
93
96
  //#region rules/wangs-ui/typescript-narrowing-over-casting.md?raw
94
- var typescript_narrowing_over_casting_default = "---\ntrigger: model_decision\ndescription: \"Apply when narrowing an `unknown` or union-typed value in TypeScript.\"\nowner: wangs-ui\nmetadata:\n owner: wangs-ui\n---\n# Rule: TypeScript — `unknown` + Narrowing, Not Casting\n\nPrefer proving a type through control flow over asserting it with `as`.\n\n## Narrowing techniques, in order of preference:\n\n1. **`typeof`** — primitives (`string`, `number`, `boolean`, `undefined`, `function`)\n2. **`instanceof`** — class instances, `Error`, `Date`, custom classes\n3. **`in`** — checking a property exists before accessing it on a union/unknown\n4. **User-defined type guards** — `function isUser(x: unknown): x is User`\n5. **Discriminated union tag checks** — `switch (value.kind) { ... }`\n6. **Exhaustiveness checks** — a `never`-typed default branch so adding a new variant is a compile error until every switch/if-chain handles it\n\n```ts\n// ✅ Type guard\nfunction isUser(value: unknown): value is User {\n return typeof value === 'object' && value !== null && 'id' in value && 'email' in value;\n}\n\n// ✅ Exhaustiveness check\nfunction assertNever(x: never): never {\n throw new Error(`Unhandled case: ${JSON.stringify(x)}`);\n}\n\nfunction area(shape: Shape): number {\n switch (shape.kind) {\n case 'circle':\n return Math.PI * shape.radius ** 2;\n case 'square':\n return shape.side ** 2;\n default:\n return assertNever(shape); // compile error if a variant is missed\n }\n}\n```\n\nType assertions (`as X`) and the non-null assertion (`!`) bypass this entirely — treat them as a last resort, not a shortcut.\n";
97
+ var typescript_narrowing_over_casting_default = "---\ntrigger: model_decision\ndescription: \"Apply when narrowing an `unknown` or union-typed value in TypeScript.\"\nmetadata:\n owner: wangs-ui\n---\n# Rule: TypeScript — `unknown` + Narrowing, Not Casting\n\nPrefer proving a type through control flow over asserting it with `as`.\n\n## Narrowing techniques, in order of preference:\n\n1. **`typeof`** — primitives (`string`, `number`, `boolean`, `undefined`, `function`)\n2. **`instanceof`** — class instances, `Error`, `Date`, custom classes\n3. **`in`** — checking a property exists before accessing it on a union/unknown\n4. **User-defined type guards** — `function isUser(x: unknown): x is User`\n5. **Discriminated union tag checks** — `switch (value.kind) { ... }`\n6. **Exhaustiveness checks** — a `never`-typed default branch so adding a new variant is a compile error until every switch/if-chain handles it\n\n```ts\n// ✅ Type guard\nfunction isUser(value: unknown): value is User {\n return typeof value === 'object' && value !== null && 'id' in value && 'email' in value;\n}\n\n// ✅ Exhaustiveness check\nfunction assertNever(x: never): never {\n throw new Error(`Unhandled case: ${JSON.stringify(x)}`);\n}\n\nfunction area(shape: Shape): number {\n switch (shape.kind) {\n case 'circle':\n return Math.PI * shape.radius ** 2;\n case 'square':\n return shape.side ** 2;\n default:\n return assertNever(shape); // compile error if a variant is missed\n }\n}\n```\n\nType assertions (`as X`) and the non-null assertion (`!`) bypass this entirely — treat them as a last resort, not a shortcut.\n";
95
98
  //#endregion
96
99
  //#region rules/wangs-ui/typescript-readonly-by-default.md?raw
97
- var typescript_readonly_by_default_default = "---\ntrigger: model_decision\ndescription: \"Apply when declaring a TypeScript collection or object shape.\"\nowner: wangs-ui\nmetadata:\n owner: wangs-ui\n---\n# Rule: TypeScript — Readonly by Default\n\nPrefer immutable shapes unless mutation is intentional and localized.\n\n```ts\ninterface Point {\n readonly x: number;\n readonly y: number;\n}\n\nfunction config(values: readonly string[]) {\n /* ... */\n}\n\nconst ROLES = ['admin', 'editor', 'viewer'] as const;\ntype UserRole = (typeof ROLES)[number];\n```\n";
100
+ var typescript_readonly_by_default_default = "---\ntrigger: model_decision\ndescription: \"Apply when declaring a TypeScript collection or object shape.\"\nmetadata:\n owner: wangs-ui\n---\n# Rule: TypeScript — Readonly by Default\n\nPrefer immutable shapes unless mutation is intentional and localized.\n\n```ts\ninterface Point {\n readonly x: number;\n readonly y: number;\n}\n\nfunction config(values: readonly string[]) {\n /* ... */\n}\n\nconst ROLES = ['admin', 'editor', 'viewer'] as const;\ntype UserRole = (typeof ROLES)[number];\n```\n";
98
101
  //#endregion
99
102
  //#region rules/wangs-ui/typescript-tsconfig-strictness.md?raw
100
- var typescript_tsconfig_strictness_default = "---\ntrigger: model_decision\ndescription: \"Apply when configuring or reviewing tsconfig.json compiler strictness options.\"\nowner: wangs-ui\nmetadata:\n owner: wangs-ui\n---\n# Rule: TypeScript — Baseline `tsconfig.json` Strictness\n\nTreat these as the non-negotiable floor for any project this rule touches:\n\n```json\n{\n \"compilerOptions\": {\n \"strict\": true,\n \"noImplicitAny\": true,\n \"strictNullChecks\": true,\n \"strictFunctionTypes\": true,\n \"strictPropertyInitialization\": true,\n \"noUncheckedIndexedAccess\": true,\n \"exactOptionalPropertyTypes\": true,\n \"noImplicitOverride\": true,\n \"noFallthroughCasesInSwitch\": true,\n \"noUnusedLocals\": true,\n \"noUnusedParameters\": true,\n \"forceConsistentCasingInFileNames\": true\n }\n}\n```\n\n`strict: true` alone enables the core group (`noImplicitAny`, `strictNullChecks`, etc.), but `noUncheckedIndexedAccess` and `exactOptionalPropertyTypes` are commonly missed and close real gaps (array/object index access returning `T` instead of `T | undefined`; optional properties silently accepting `undefined` as an explicit value).\n";
103
+ var typescript_tsconfig_strictness_default = "---\ntrigger: model_decision\ndescription: \"Apply when configuring or reviewing tsconfig.json compiler strictness options.\"\nmetadata:\n owner: wangs-ui\n---\n# Rule: TypeScript — Baseline `tsconfig.json` Strictness\n\nTreat these as the non-negotiable floor for any project this rule touches:\n\n```json\n{\n \"compilerOptions\": {\n \"strict\": true,\n \"noImplicitAny\": true,\n \"strictNullChecks\": true,\n \"strictFunctionTypes\": true,\n \"strictPropertyInitialization\": true,\n \"noUncheckedIndexedAccess\": true,\n \"exactOptionalPropertyTypes\": true,\n \"noImplicitOverride\": true,\n \"noFallthroughCasesInSwitch\": true,\n \"noUnusedLocals\": true,\n \"noUnusedParameters\": true,\n \"forceConsistentCasingInFileNames\": true\n }\n}\n```\n\n`strict: true` alone enables the core group (`noImplicitAny`, `strictNullChecks`, etc.), but `noUncheckedIndexedAccess` and `exactOptionalPropertyTypes` are commonly missed and close real gaps (array/object index access returning `T` instead of `T | undefined`; optional properties silently accepting `undefined` as an explicit value).\n";
101
104
  //#endregion
102
105
  //#region rules/wangs-ui/typescript-zero-any.md?raw
103
- var typescript_zero_any_default = "---\ntrigger: model_decision\ndescription: \"Apply when writing or reviewing any TypeScript type annotation.\"\nowner: wangs-ui\nmetadata:\n owner: wangs-ui\n---\n# Rule: TypeScript — Never Use `any`\n\n`any` is not \"unknown type,\" it's \"type checking off.\" It's contagious — once a value is `any`, everything it touches becomes unchecked too.\n\n- Never write `any` for parameters, return types, variables, or generics.\n- Use `unknown` for genuinely unknown external data (API responses, `JSON.parse`, catch clauses, third-party callbacks) and narrow it before use.\n- Use generics (`<T>`) when a function needs to work across types but preserve the relationship between input and output.\n- If a library ships untyped, write a minimal local type/interface for the surface area you actually use instead of reaching for `any`.\n\n```ts\n// ❌ Bad\nfunction parseConfig(json: any) {\n return json.settings.theme; // no safety, no autocomplete, silent runtime crash\n}\n\n// ✅ Good\nfunction parseConfig(json: unknown): string {\n if (\n typeof json === 'object' &&\n json !== null &&\n 'settings' in json &&\n typeof (json as { settings: unknown }).settings === 'object'\n ) {\n // still narrow further or validate with a schema library (zod, valibot, etc.)\n }\n throw new Error('Invalid config shape');\n}\n```\n\nThe only acceptable `any` is a well-justified, isolated, and commented one (e.g. interfacing with a genuinely untyped legacy module) — never a default.\n";
106
+ var typescript_zero_any_default = "---\ntrigger: model_decision\ndescription: \"Apply when writing or reviewing any TypeScript type annotation.\"\nmetadata:\n owner: wangs-ui\n---\n# Rule: TypeScript — Never Use `any`\n\n`any` is not \"unknown type,\" it's \"type checking off.\" It's contagious — once a value is `any`, everything it touches becomes unchecked too.\n\n- Never write `any` for parameters, return types, variables, or generics.\n- Use `unknown` for genuinely unknown external data (API responses, `JSON.parse`, catch clauses, third-party callbacks) and narrow it before use.\n- Use generics (`<T>`) when a function needs to work across types but preserve the relationship between input and output.\n- If a library ships untyped, write a minimal local type/interface for the surface area you actually use instead of reaching for `any`.\n\n```ts\n// ❌ Bad\nfunction parseConfig(json: any) {\n return json.settings.theme; // no safety, no autocomplete, silent runtime crash\n}\n\n// ✅ Good\nfunction parseConfig(json: unknown): string {\n if (\n typeof json === 'object' &&\n json !== null &&\n 'settings' in json &&\n typeof (json as { settings: unknown }).settings === 'object'\n ) {\n // still narrow further or validate with a schema library (zod, valibot, etc.)\n }\n throw new Error('Invalid config shape');\n}\n```\n\nThe only acceptable `any` is a well-justified, isolated, and commented one (e.g. interfacing with a genuinely untyped legacy module) — never a default.\n";
104
107
  //#endregion
105
108
  //#region src/registry.ts
106
109
  var __dirname = path.dirname(fileURLToPath(import.meta.url));
107
110
  var EMBEDDED_SKILLS_RAW = /* #__PURE__ */ Object.assign({
108
- "../skills/wangs-ui/craft-theme/SKILL.md": SKILL_default$8,
109
- "../skills/wangs-ui/create-form/SKILL.md": SKILL_default$7,
111
+ "../skills/wangs-ui/craft-theme/SKILL.md": SKILL_default$9,
112
+ "../skills/wangs-ui/create-form/SKILL.md": SKILL_default$8,
113
+ "../skills/wangs-ui/custom-color-family/SKILL.md": SKILL_default$7,
110
114
  "../skills/wangs-ui/data-table/SKILL.md": SKILL_default$6,
111
115
  "../skills/wangs-ui/dialog-modal/SKILL.md": SKILL_default$5,
112
116
  "../skills/wangs-ui/i18n-usage/SKILL.md": SKILL_default$4,
@@ -415,7 +419,7 @@ function removeRule(ruleRelativePath, baseDir = process.cwd()) {
415
419
  //#endregion
416
420
  //#region src/commands/list.ts
417
421
  function listSkills(baseDir = process.cwd()) {
418
- intro(`\x1b[1m\x1b[36m📦 Wangs UI Skills & Rules Registry\x1b[0m (v1.3.0-alpha.12)`);
422
+ intro(`\x1b[1m\x1b[36m📦 Wangs UI Skills & Rules Registry\x1b[0m (v1.3.0-alpha.21)`);
419
423
  const allSkills = loadAllSkills();
420
424
  const allRules = loadAllRules();
421
425
  const skillDirs = getAgentSkillDirs(baseDir);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wangs-ui/skills",
3
- "version": "1.3.0-alpha.20",
3
+ "version": "1.3.0-alpha.22",
4
4
  "description": "CLI to install, update, and manage modular AI agent skills for Wangs UI React applications",
5
5
  "keywords": [
6
6
  "agents",
@@ -1,7 +1,6 @@
1
1
  ---
2
2
  trigger: model_decision
3
3
  description: "Apply when deciding whether a prop belongs in global defaultProps config or per-instance JSX."
4
- owner: wangs-ui
5
4
  metadata:
6
5
  owner: wangs-ui
7
6
  ---
@@ -1,7 +1,6 @@
1
1
  ---
2
2
  trigger: model_decision
3
3
  description: "Apply when writing a <Field> render-prop callback in a Wangs UI Form/DialogForm."
4
- owner: wangs-ui
5
4
  metadata:
6
5
  owner: wangs-ui
7
6
  ---
@@ -1,7 +1,6 @@
1
1
  ---
2
2
  trigger: model_decision
3
3
  description: "Apply when writing JSX markup — enforce Wangs UI primitives over raw HTML elements."
4
- owner: wangs-ui
5
4
  metadata:
6
5
  owner: wangs-ui
7
6
  ---
@@ -1,7 +1,6 @@
1
1
  ---
2
2
  trigger: model_decision
3
3
  description: "Apply when writing or reviewing JSX layout for redundant wrapper containers."
4
- owner: wangs-ui
5
4
  metadata:
6
5
  owner: wangs-ui
7
6
  ---
@@ -1,7 +1,6 @@
1
1
  ---
2
2
  trigger: model_decision
3
3
  description: "Apply when doing a final review pass on React 19 component or hook code for compiler-optimization compliance."
4
- owner: wangs-ui
5
4
  metadata:
6
5
  owner: wangs-ui
7
6
  ---
@@ -1,7 +1,6 @@
1
1
  ---
2
2
  trigger: model_decision
3
3
  description: "Apply when writing or reviewing render logic in a React 19 component."
4
- owner: wangs-ui
5
4
  metadata:
6
5
  owner: wangs-ui
7
6
  ---
@@ -1,7 +1,6 @@
1
1
  ---
2
2
  trigger: model_decision
3
3
  description: "Apply when naming a React 19 component, hook, or prop."
4
- owner: wangs-ui
5
4
  metadata:
6
5
  owner: wangs-ui
7
6
  ---
@@ -1,7 +1,6 @@
1
1
  ---
2
2
  trigger: model_decision
3
3
  description: "Apply when writing or reviewing a React 19 component or hook that uses or is tempted to use useMemo/useCallback/React.memo."
4
- owner: wangs-ui
5
4
  metadata:
6
5
  owner: wangs-ui
7
6
  ---
@@ -1,7 +1,6 @@
1
1
  ---
2
2
  trigger: model_decision
3
3
  description: "Apply when typing React 19 primitives — ref props, useActionState, useOptimistic, use(), useEffectEvent."
4
- owner: wangs-ui
5
4
  metadata:
6
5
  owner: wangs-ui
7
6
  ---
@@ -1,7 +1,6 @@
1
1
  ---
2
2
  trigger: model_decision
3
3
  description: "Apply when defining a component's Props type or interface."
4
- owner: wangs-ui
5
4
  metadata:
6
5
  owner: wangs-ui
7
6
  ---
@@ -1,7 +1,6 @@
1
1
  ---
2
2
  trigger: model_decision
3
3
  description: "Apply when writing or reviewing a React 19 component or hook body for purity and immutability."
4
- owner: wangs-ui
5
4
  metadata:
6
5
  owner: wangs-ui
7
6
  ---
@@ -1,7 +1,6 @@
1
1
  ---
2
2
  trigger: model_decision
3
3
  description: 'Apply when configuring React Compiler tooling or opting a component out via "use no memo".'
4
- owner: wangs-ui
5
4
  metadata:
6
5
  owner: wangs-ui
7
6
  ---
@@ -1,7 +1,6 @@
1
1
  ---
2
2
  trigger: model_decision
3
3
  description: "Apply when tempted to override a Wangs UI theme CSS variable directly."
4
- owner: wangs-ui
5
4
  metadata:
6
5
  owner: wangs-ui
7
6
  ---
@@ -1,7 +1,6 @@
1
1
  ---
2
2
  trigger: model_decision
3
3
  description: "Apply when writing or reviewing a type assertion (`as X`) or non-null assertion (`x!`)."
4
- owner: wangs-ui
5
4
  metadata:
6
5
  owner: wangs-ui
7
6
  ---
@@ -1,7 +1,6 @@
1
1
  ---
2
2
  trigger: model_decision
3
3
  description: "Apply when doing a final TypeScript review pass on a file."
4
- owner: wangs-ui
5
4
  metadata:
6
5
  owner: wangs-ui
7
6
  ---
@@ -1,7 +1,6 @@
1
1
  ---
2
2
  trigger: model_decision
3
3
  description: "Apply when modeling a variant or multi-shape state in TypeScript."
4
- owner: wangs-ui
5
4
  metadata:
6
5
  owner: wangs-ui
7
6
  ---
@@ -1,7 +1,6 @@
1
1
  ---
2
2
  trigger: model_decision
3
3
  description: "Apply when writing or reviewing an exported function, class method, or public API surface in TypeScript."
4
- owner: wangs-ui
5
4
  metadata:
6
5
  owner: wangs-ui
7
6
  ---
@@ -1,7 +1,6 @@
1
1
  ---
2
2
  trigger: model_decision
3
3
  description: "Apply when deciding between `interface` and `type` for a new TypeScript declaration."
4
- owner: wangs-ui
5
4
  metadata:
6
5
  owner: wangs-ui
7
6
  ---
@@ -1,7 +1,6 @@
1
1
  ---
2
2
  trigger: model_decision
3
3
  description: "Apply when modeling a fixed set of string or numeric variants in TypeScript."
4
- owner: wangs-ui
5
4
  metadata:
6
5
  owner: wangs-ui
7
6
  ---
@@ -1,7 +1,6 @@
1
1
  ---
2
2
  trigger: model_decision
3
3
  description: "Apply when naming a TypeScript type, variable, or constant."
4
- owner: wangs-ui
5
4
  metadata:
6
5
  owner: wangs-ui
7
6
  ---
@@ -1,7 +1,6 @@
1
1
  ---
2
2
  trigger: model_decision
3
3
  description: "Apply when narrowing an `unknown` or union-typed value in TypeScript."
4
- owner: wangs-ui
5
4
  metadata:
6
5
  owner: wangs-ui
7
6
  ---
@@ -1,7 +1,6 @@
1
1
  ---
2
2
  trigger: model_decision
3
3
  description: "Apply when declaring a TypeScript collection or object shape."
4
- owner: wangs-ui
5
4
  metadata:
6
5
  owner: wangs-ui
7
6
  ---
@@ -1,7 +1,6 @@
1
1
  ---
2
2
  trigger: model_decision
3
3
  description: "Apply when configuring or reviewing tsconfig.json compiler strictness options."
4
- owner: wangs-ui
5
4
  metadata:
6
5
  owner: wangs-ui
7
6
  ---
@@ -1,7 +1,6 @@
1
1
  ---
2
2
  trigger: model_decision
3
3
  description: "Apply when writing or reviewing any TypeScript type annotation."
4
- owner: wangs-ui
5
4
  metadata:
6
5
  owner: wangs-ui
7
6
  ---
@@ -6,7 +6,7 @@ metadata:
6
6
  ---
7
7
  # Skill: Craft Theme
8
8
 
9
- Use this skill whenever the user asks to create, customize, or rebrand the color theme of an app built on `@wangs-ui/react-core` / `@wangs-ui/foundation` — "make the app's theme orange", "use our brand color #ff6b35", "add a new palette called sunset", etc.
9
+ Use this skill whenever the user asks to create, customize, or rebrand the color theme of an app built on `@wangs-ui/react-core` / `@wangs-ui/foundation` — "make the app's theme orange", "use our brand color #ff6b35", "add a new palette called sunset", etc. This only covers the 8 existing families (primary/secondary/tertiary/success/danger/warning/info/general) — for an entirely new semantic color group (e.g. a "premium" tier badge), use the `custom-color-family` skill instead.
10
10
 
11
11
  ## The rule this skill exists to enforce
12
12
 
@@ -0,0 +1,122 @@
1
+ ---
2
+ name: custom-color-family
3
+ description: Generate a new accessible custom color family (fill/on, container/on-container, emphasized/hover/pressed, fixed) for a Wangs UI app via the `wangs-ui-generate-color-family` CLI — for a semantic group beyond the 8 built-in families (e.g. "premium", "verified", "beta"). Never hand-write or hand-pick these hexes.
4
+ metadata:
5
+ owner: wangs-ui
6
+ ---
7
+ # Skill: Custom Color Family
8
+
9
+ Use this skill whenever the app needs a new semantic color group that isn't one of the 8 built-in families (`primary`/`secondary`/`tertiary`/`danger`/`success`/`warning`/`info`/`general`) — a product tier badge ("premium"), a verification status ("verified"), a draft/beta tag, a data-visualization category color, etc. For rebranding the existing 8 families instead, use the `craft-theme` skill.
10
+
11
+ ## The rule this skill exists to enforce
12
+
13
+ **Never hand-pick which color goes on top of your fill, how light the container tint should be, or what percentage a hover/pressed state should mix at — and never recompute any of it at render time.** Wangs UI's own built-in families hit the first mistake before: a fixed luminance threshold put white text on a brand color at 2.54:1 contrast (fails AA, looked fine "by eye"). The CLI below runs the same search-based derivation the built-in families use, **once**, and writes the result to a static file — exactly like `successContainer`/`onSuccessContainer` are plain static fields you reuse everywhere, not something recomputed per component.
14
+
15
+ ## 1. Generate the family
16
+
17
+ ```bash
18
+ pnpm exec wangs-ui-generate-color-family --name=<id> --color=<#hex> [--general=#hex] --out=<path>
19
+ ```
20
+
21
+ - `--name` (required): identifier for the family, e.g. `premium`. Used for the exported const name (`premiumTokens`) and, unless `--out` is given, the output filename (`premium.ts`).
22
+ - `--color` (required): the family's reference hex, e.g. `#7c5cff`.
23
+ - `--general` (optional): your theme's neutral/general base (`Palette.general['500']` — the same hex `craft-theme`'s generator used for your app's `general` family, or one of the built-in palettes' `general['500']`). Improves how the Fill/On pairing reads against your actual neutral surfaces. Defaults to `#808080` if omitted.
24
+ - `--out=<path>` (optional): where to write the file. Defaults to `./<name>.ts` in the current working directory — always pass an explicit `--out` pointing into the app's own theme directory (e.g. `src/theme/premium.ts`), don't rely on the default.
25
+
26
+ If `@wangs-ui/foundation` isn't already a dependency of the project, install it first — the CLI ships as its `bin`.
27
+
28
+ **The written file is chmod'd read-only (0o444) and headed with an AUTO-GENERATED / DO NOT EDIT BY HAND comment.** If the color needs to change, re-run the command — never hand-edit a role in the output.
29
+
30
+ This writes **two** files — `<out>.ts` (the computed hex values) and `<out>.css` (a static Tailwind `@theme` registration, same mechanism `theme.css` itself uses for `success`/`danger`/etc.):
31
+
32
+ ```ts
33
+ // premium.ts
34
+ export const premiumTokens = {
35
+ light: {
36
+ fill: '#7c5cff',
37
+ onFill: '#000000',
38
+ container: '#dfdfff',
39
+ onContainer: '#471fa9',
40
+ emphasized: '#7959f8',
41
+ hover: '#7959f8',
42
+ pressed: '#7959f8',
43
+ fixed: '#dfdfff',
44
+ fixedDim: '#c2bfff',
45
+ onFixed: '#1b0d48',
46
+ onFixedVariant: '#471fa9',
47
+ },
48
+ dark: {
49
+ /* … same 11 roles, derived for dark mode … */
50
+ },
51
+ } as const;
52
+ ```
53
+
54
+ ```css
55
+ /* premium.css */
56
+ @theme {
57
+ --color-premium: var(--color-premium);
58
+ --color-on-premium: var(--color-on-premium);
59
+ --color-premium-container: var(--color-premium-container);
60
+ --color-on-premium-container: var(--color-on-premium-container);
61
+ /* … the rest of the 11 roles … */
62
+ }
63
+ ```
64
+
65
+ The `.css` file only declares **names** (self-referential, no hardcoded value) so Tailwind can generate the matching utilities — it never needs regenerating even when you change the color with a new CLI run; only the `.ts` file does.
66
+
67
+ ## 2. Wire it up once (web)
68
+
69
+ Two one-time steps, done once for the whole app — not per component, and no wrapper component to write:
70
+
71
+ **a. Import the generated CSS** anywhere in your app's normal global stylesheet import chain (wherever your `@import "tailwindcss"` already lives):
72
+
73
+ ```css
74
+ @import './theme/premium.css';
75
+ ```
76
+
77
+ **b. Pass the generated tokens straight to `WangsUiProvider`'s `theme.customColorFamilies`** — this is a real `ThemeProviderProps` field, not a convention you assemble yourself. The provider resolves it to the current mode and injects the CSS variables internally, the exact same pass the built-in families already get (`CssVariablesInjector`) — adding a family is purely additive data, no new component in your tree:
78
+
79
+ ```tsx
80
+ import { WangsUiProvider } from '@wangs-ui/react-core/api';
81
+ import { premiumTokens } from './theme/premium';
82
+
83
+ <WangsUiProvider
84
+ configOptions={{ preset }}
85
+ theme={{ palette, mode, customColorFamilies: { premium: premiumTokens } }}
86
+ >
87
+ <App />
88
+ </WangsUiProvider>;
89
+ ```
90
+
91
+ A second custom family is one more entry in the same object (`{ premium: premiumTokens, verified: verifiedTokens }`) — never a second wrapper.
92
+
93
+ ## 3. Use it — same as any built-in family
94
+
95
+ From here on, every component just uses ordinary Tailwind utility classes, exactly like `bg-success-container`/`text-on-success-container`:
96
+
97
+ ```tsx
98
+ function PremiumBadge() {
99
+ return <span className="bg-premium-container text-on-premium-container">Premium</span>;
100
+ }
101
+ ```
102
+
103
+ No import, no hook, no per-component wiring — any component anywhere in the tree can reach for `bg-premium` / `text-on-premium` / `bg-premium-emphasized` / `hover:bg-premium-hover` / etc. the same way it already reaches for the built-in families.
104
+
105
+ If a component needs the plain hex value instead of a class (a canvas draw call, an SVG fill, a chart library prop), `useTheme().customColors.premium` is already resolved to the current mode — no manual `premiumTokens[mode]` indexing:
106
+
107
+ ```tsx
108
+ const { customColors } = useTheme();
109
+ customColors.premium.fill; // already the right mode's hex
110
+ ```
111
+
112
+ **React Native** has no `.css`/Tailwind step: skip step **a** in section 2, but still pass `customColorFamilies` to `theme` in step **b** — then read `useTheme().customColors.premium` (etc.) the same way, directly in `style`.
113
+
114
+ ## 4. Regenerating / evolving a custom family
115
+
116
+ To change the color, re-run the same command with the new hex:
117
+
118
+ ```bash
119
+ pnpm exec wangs-ui-generate-color-family --name=premium --color=#9333ea --out=src/theme/premium.ts
120
+ ```
121
+
122
+ This rewrites `premium.ts` with the new values and rewrites `premium.css` too (harmless — it's the same static names every time, nothing in your app needs to change because of it).