@wangs-ui/skills 1.3.0-alpha.2 → 1.3.0-alpha.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -11
- package/dist/bin.js +20 -15
- package/dist/index.js +2 -2
- package/dist/rules/wangs-ui/default-props-precedence.md +13 -0
- package/dist/rules/wangs-ui/forms-destructuring-lock.md +18 -0
- package/dist/rules/wangs-ui/no-raw-html.md +28 -0
- package/dist/rules/wangs-ui/no-redundant-wrappers.md +55 -0
- package/dist/rules/wangs-ui/react19-checklist-and-reference.md +37 -0
- package/dist/rules/wangs-ui/react19-compiler-render-patterns.md +12 -0
- package/dist/rules/wangs-ui/react19-naming-conventions.md +15 -0
- package/dist/rules/wangs-ui/react19-no-manual-memoization.md +27 -0
- package/dist/rules/wangs-ui/react19-primitives-typing.md +84 -0
- package/dist/rules/wangs-ui/react19-props-typing.md +24 -0
- package/dist/rules/wangs-ui/react19-purity-and-immutability.md +40 -0
- package/dist/rules/wangs-ui/react19-tooling-and-opt-out.md +77 -0
- package/dist/rules/wangs-ui/theme-variable-override-breaks-engine.md +19 -0
- package/dist/rules/wangs-ui/typescript-assertions-last-resort.md +12 -0
- package/dist/rules/wangs-ui/typescript-checklist-and-reference.md +33 -0
- package/dist/rules/wangs-ui/typescript-discriminated-unions.md +39 -0
- package/dist/rules/wangs-ui/typescript-explicit-return-types.md +21 -0
- package/dist/rules/wangs-ui/typescript-interface-vs-type.md +39 -0
- package/dist/rules/wangs-ui/typescript-literal-unions-vs-enums.md +21 -0
- package/dist/rules/wangs-ui/typescript-naming-conventions.md +23 -0
- package/dist/rules/wangs-ui/typescript-narrowing-over-casting.md +43 -0
- package/dist/rules/wangs-ui/typescript-readonly-by-default.md +23 -0
- package/dist/rules/wangs-ui/typescript-tsconfig-strictness.md +30 -0
- package/dist/rules/wangs-ui/typescript-zero-any.md +36 -0
- package/dist/skills/wangs-ui/craft-theme/SKILL.md +63 -0
- package/dist/skills/{create-form → wangs-ui/create-form}/SKILL.md +27 -16
- package/{skills → dist/skills/wangs-ui}/data-table/SKILL.md +24 -15
- package/{skills → dist/skills/wangs-ui}/dialog-modal/SKILL.md +18 -9
- package/{skills → dist/skills/wangs-ui}/i18n-usage/SKILL.md +19 -11
- package/dist/skills/wangs-ui/layout-navigation/SKILL.md +156 -0
- package/dist/skills/wangs-ui/responsive-design/SKILL.md +107 -0
- package/dist/skills/wangs-ui/universal-layout/SKILL.md +80 -0
- package/dist/skills/wangs-ui/wangs-ui-components/SKILL.md +104 -0
- package/dist/src-BFwMaU8I.js +663 -0
- package/package.json +3 -2
- package/rules/wangs-ui/default-props-precedence.md +13 -0
- package/rules/wangs-ui/forms-destructuring-lock.md +18 -0
- package/rules/wangs-ui/no-raw-html.md +28 -0
- package/rules/wangs-ui/no-redundant-wrappers.md +55 -0
- package/rules/wangs-ui/react19-checklist-and-reference.md +37 -0
- package/rules/wangs-ui/react19-compiler-render-patterns.md +12 -0
- package/rules/wangs-ui/react19-naming-conventions.md +15 -0
- package/rules/wangs-ui/react19-no-manual-memoization.md +27 -0
- package/rules/wangs-ui/react19-primitives-typing.md +84 -0
- package/rules/wangs-ui/react19-props-typing.md +24 -0
- package/rules/wangs-ui/react19-purity-and-immutability.md +40 -0
- package/rules/wangs-ui/react19-tooling-and-opt-out.md +77 -0
- package/rules/wangs-ui/theme-variable-override-breaks-engine.md +19 -0
- package/rules/wangs-ui/typescript-assertions-last-resort.md +12 -0
- package/rules/wangs-ui/typescript-checklist-and-reference.md +33 -0
- package/rules/wangs-ui/typescript-discriminated-unions.md +39 -0
- package/rules/wangs-ui/typescript-explicit-return-types.md +21 -0
- package/rules/wangs-ui/typescript-interface-vs-type.md +39 -0
- package/rules/wangs-ui/typescript-literal-unions-vs-enums.md +21 -0
- package/rules/wangs-ui/typescript-naming-conventions.md +23 -0
- package/rules/wangs-ui/typescript-narrowing-over-casting.md +43 -0
- package/rules/wangs-ui/typescript-readonly-by-default.md +23 -0
- package/rules/wangs-ui/typescript-tsconfig-strictness.md +30 -0
- package/rules/wangs-ui/typescript-zero-any.md +36 -0
- package/skills/wangs-ui/craft-theme/SKILL.md +63 -0
- package/skills/{create-form → wangs-ui/create-form}/SKILL.md +27 -16
- package/{dist/skills → skills/wangs-ui}/data-table/SKILL.md +24 -15
- package/{dist/skills → skills/wangs-ui}/dialog-modal/SKILL.md +18 -9
- package/{dist/skills → skills/wangs-ui}/i18n-usage/SKILL.md +19 -11
- package/skills/wangs-ui/layout-navigation/SKILL.md +156 -0
- package/skills/wangs-ui/responsive-design/SKILL.md +107 -0
- package/skills/wangs-ui/universal-layout/SKILL.md +80 -0
- package/skills/wangs-ui/wangs-ui-components/SKILL.md +104 -0
- package/dist/skills/layout-navigation/SKILL.md +0 -62
- package/dist/skills/react19-compiler-typescript/SKILL.md +0 -388
- package/dist/skills/typescript-strict-typing/SKILL.md +0 -317
- package/dist/skills/wangs-ui-components/SKILL.md +0 -108
- package/dist/src-DDi-O7tk.js +0 -246
- package/skills/layout-navigation/SKILL.md +0 -62
- package/skills/react19-compiler-typescript/SKILL.md +0 -388
- package/skills/typescript-strict-typing/SKILL.md +0 -317
- package/skills/wangs-ui-components/SKILL.md +0 -108
package/README.md
CHANGED
|
@@ -1,31 +1,33 @@
|
|
|
1
1
|
# @wangs-ui/skills
|
|
2
2
|
|
|
3
|
-
Modular AI agent skills installer and manager for **Wangs UI** React components and design patterns.
|
|
3
|
+
Modular AI agent skills and guardrail rules installer and manager for **Wangs UI** React components and design patterns.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
## 📚 Dedicated Storybook Guide
|
|
8
|
-
|
|
9
|
-
For a step-by-step guide on using `@wangs-ui/skills` independently with any Storybook project, see the [Dedicated Storybook Guide](../../docs/storybook-mcp-skills.md).
|
|
5
|
+
Skills and rules are standalone: they work in any Storybook or React project without requiring project scaffolding tools. They pair with [`@wangs-ui/mcp`](../mcp/README.md), which supplies the live component catalog, typed APIs, and story implementations the skills tell agents to verify against.
|
|
10
6
|
|
|
11
7
|
---
|
|
12
8
|
|
|
13
9
|
## ⚡ Quick Start
|
|
14
10
|
|
|
15
11
|
```bash
|
|
16
|
-
# List available skills
|
|
12
|
+
# List available skills & rules
|
|
17
13
|
npx @wangs-ui/skills list
|
|
18
14
|
|
|
19
|
-
#
|
|
15
|
+
# Interactive installer (select skills and rules via multiselect menu)
|
|
20
16
|
npx @wangs-ui/skills add
|
|
21
17
|
|
|
18
|
+
# Install all rules (Wangs UI invariants, React 19 compiler-first, TypeScript strict)
|
|
19
|
+
npx @wangs-ui/skills add --rules
|
|
20
|
+
|
|
22
21
|
# Install specific skills directly by name
|
|
23
22
|
npx @wangs-ui/skills add wangs-ui-components data-table create-form
|
|
24
23
|
|
|
25
|
-
#
|
|
24
|
+
# Install specific rules directly by name
|
|
25
|
+
npx @wangs-ui/skills add forms-destructuring-lock no-raw-html
|
|
26
|
+
|
|
27
|
+
# Update installed skills and rules
|
|
26
28
|
npx @wangs-ui/skills update
|
|
27
29
|
|
|
28
|
-
# Remove a skill
|
|
30
|
+
# Remove a skill or rule
|
|
29
31
|
npx @wangs-ui/skills remove create-form
|
|
30
32
|
```
|
|
31
33
|
|
|
@@ -33,6 +35,8 @@ npx @wangs-ui/skills remove create-form
|
|
|
33
35
|
|
|
34
36
|
## 🛠️ Features
|
|
35
37
|
|
|
36
|
-
- **
|
|
38
|
+
- **Procedural Skills**: Installs specialized step-by-step guidelines and workflows into your agent environment (`.agents/skills/`, `.claude/skills/`, etc.).
|
|
39
|
+
- **Declarative Guardrail Rules**: Installs high-signal invariants and pitfalls (`.agents/rules/`, `.claude/rules/`) covering Wangs UI core facts, React 19 compiler patterns, and strict TypeScript discipline.
|
|
40
|
+
- **MCP-Verified Guidance**: Skills mandate querying the MCP server for live contracts instead of hardcoding props, so guidance never drifts from the installed package version.
|
|
37
41
|
- **Cross-Platform & Multi-Agent**: Supports Antigravity IDE, Claude Code, OpenCode, Kilo, and universal agent directories on Mac and Windows.
|
|
38
42
|
- **Independent Usage**: Works in any Storybook or React project without requiring project scaffolding tools.
|
package/dist/bin.js
CHANGED
|
@@ -1,21 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { i as listSkills, n as updateSkills, r as addSkills, t as removeSkills } from "./src-
|
|
2
|
+
import { i as listSkills, n as updateSkills, r as addSkills, t as removeSkills } from "./src-BFwMaU8I.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.20";
|
|
6
7
|
var HELP_TEXT = `
|
|
7
|
-
\x1b[1m\x1b[36m🚀 Wangs UI Skills CLI\x1b[0m
|
|
8
|
-
Install, update, and manage modular AI agent skills for Wangs UI React applications.
|
|
8
|
+
\x1b[1m\x1b[36m🚀 Wangs UI Skills & Rules CLI\x1b[0m
|
|
9
|
+
Install, update, and manage modular AI agent skills and rules for Wangs UI React applications.
|
|
9
10
|
|
|
10
11
|
\x1b[1mUsage:\x1b[0m
|
|
11
12
|
npx @wangs-ui/skills [command] [options]
|
|
12
13
|
wangs-ui-skills [command] [options]
|
|
13
14
|
|
|
14
15
|
\x1b[1mCommands:\x1b[0m
|
|
15
|
-
\x1b[36mlist\x1b[0m List all available and installed skills
|
|
16
|
-
\x1b[36madd\x1b[0m [
|
|
17
|
-
\x1b[36mupdate\x1b[0m [
|
|
18
|
-
\x1b[36mremove\x1b[0m <
|
|
16
|
+
\x1b[36mlist\x1b[0m List all available and installed skills & rules
|
|
17
|
+
\x1b[36madd\x1b[0m [items...] Install specified skills or rules (interactive if none provided)
|
|
18
|
+
\x1b[36mupdate\x1b[0m [items...] Update installed skills and rules to latest versions
|
|
19
|
+
\x1b[36mremove\x1b[0m <items...> Remove specified skills or rules from agent environment
|
|
19
20
|
|
|
20
21
|
\x1b[1mOptions:\x1b[0m
|
|
21
22
|
--target <dir> Target directory (default: current working directory)
|
|
@@ -24,8 +25,9 @@ Install, update, and manage modular AI agent skills for Wangs UI React applicati
|
|
|
24
25
|
|
|
25
26
|
\x1b[1mExamples:\x1b[0m
|
|
26
27
|
npx @wangs-ui/skills list
|
|
27
|
-
npx @wangs-ui/skills add
|
|
28
|
-
npx @wangs-ui/skills add
|
|
28
|
+
npx @wangs-ui/skills add --rules
|
|
29
|
+
npx @wangs-ui/skills add forms-destructuring-lock
|
|
30
|
+
npx @wangs-ui/skills add create-form data-table
|
|
29
31
|
npx @wangs-ui/skills update
|
|
30
32
|
npx @wangs-ui/skills remove create-form
|
|
31
33
|
`;
|
|
@@ -36,7 +38,7 @@ async function main() {
|
|
|
36
38
|
return;
|
|
37
39
|
}
|
|
38
40
|
if (args.includes("--version") || args.includes("-v")) {
|
|
39
|
-
console.log(
|
|
41
|
+
console.log(VERSION);
|
|
40
42
|
return;
|
|
41
43
|
}
|
|
42
44
|
const parsed = parseArgs({
|
|
@@ -45,10 +47,13 @@ async function main() {
|
|
|
45
47
|
allowPositionals: true,
|
|
46
48
|
strict: false
|
|
47
49
|
});
|
|
48
|
-
const
|
|
50
|
+
const targetVal = parsed.values.target;
|
|
51
|
+
const baseDir = typeof targetVal === "string" ? path.resolve(targetVal) : process.cwd();
|
|
49
52
|
const { positionals } = parsed;
|
|
50
53
|
const command = positionals[0] || "list";
|
|
51
|
-
const
|
|
54
|
+
const itemArgs = positionals.slice(1);
|
|
55
|
+
if (args.includes("--rules")) itemArgs.push("--rules");
|
|
56
|
+
if (args.includes("--skills")) itemArgs.push("--skills");
|
|
52
57
|
switch (command) {
|
|
53
58
|
case "list":
|
|
54
59
|
case "ls":
|
|
@@ -57,16 +62,16 @@ async function main() {
|
|
|
57
62
|
case "add":
|
|
58
63
|
case "install":
|
|
59
64
|
case "i":
|
|
60
|
-
await addSkills(
|
|
65
|
+
await addSkills(itemArgs, baseDir);
|
|
61
66
|
break;
|
|
62
67
|
case "update":
|
|
63
68
|
case "up":
|
|
64
|
-
updateSkills(
|
|
69
|
+
updateSkills(itemArgs, baseDir);
|
|
65
70
|
break;
|
|
66
71
|
case "remove":
|
|
67
72
|
case "rm":
|
|
68
73
|
case "delete":
|
|
69
|
-
removeSkills(
|
|
74
|
+
removeSkills(itemArgs, baseDir);
|
|
70
75
|
break;
|
|
71
76
|
default:
|
|
72
77
|
console.log(`\x1b[31mUnknown command: "${command}"\x1b[0m`);
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
export { addSkills, getAgentSkillDirs, getInstalledSkills, getSkill, installSkill, isSkillInstalled, listSkills, loadAllSkills, removeSkill, removeSkills, updateSkills };
|
|
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-BFwMaU8I.js";
|
|
2
|
+
export { addSkills, getAgentRuleDirs, getAgentSkillDirs, getInstalledRules, getInstalledSkills, getRule, getSkill, installRule, installSkill, isRuleInstalled, isSkillInstalled, listSkills, loadAllRules, loadAllSkills, removeRule, removeSkill, removeSkills, updateSkills };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
---
|
|
2
|
+
trigger: model_decision
|
|
3
|
+
description: "Apply when deciding whether a prop belongs in global defaultProps config or per-instance JSX."
|
|
4
|
+
metadata:
|
|
5
|
+
owner: wangs-ui
|
|
6
|
+
---
|
|
7
|
+
# Wangs UI Fact: `configOptions.defaultProps` Precedence
|
|
8
|
+
|
|
9
|
+
`WangsUiProvider.configOptions.defaultProps` lets any project set global
|
|
10
|
+
default prop values per component. Per-instance JSX props always win over
|
|
11
|
+
that global default — the merge order is global-default-then-instance, never
|
|
12
|
+
the reverse. This is a `WangsUiProvider` mechanism, true in any project that
|
|
13
|
+
uses it, independent of what values a given project actually configures.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
trigger: model_decision
|
|
3
|
+
description: "Apply when writing a <Field> render-prop callback in a Wangs UI Form/DialogForm."
|
|
4
|
+
metadata:
|
|
5
|
+
owner: wangs-ui
|
|
6
|
+
---
|
|
7
|
+
# Wangs UI Fact: `<Field>` Render-Prop Must Destructure `{ fieldProps, fieldState }`
|
|
8
|
+
|
|
9
|
+
`<Field>`'s callback render prop must destructure as `{({ fieldProps, fieldState })}`.
|
|
10
|
+
Passing a single parameter instead (`{(field) => ...}`) produces `undefined`
|
|
11
|
+
`value`/`onChange` and permanently locks the input — the field never becomes
|
|
12
|
+
interactive, even on initial render. This is a real `@wangs-ui/form` quirk,
|
|
13
|
+
not a project convention; it reproduces the same way in any consumer.
|
|
14
|
+
|
|
15
|
+
`<Field>` is generic — `fieldProps.onChange` is already typed to the field's
|
|
16
|
+
actual value type. Pass it straight through (`fieldProps.onChange`); don't
|
|
17
|
+
wrap it in a defensive `typeof`/`e?.target?.value` extraction, that's fighting
|
|
18
|
+
a type the field already guarantees.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
trigger: model_decision
|
|
3
|
+
description: "Apply when writing JSX markup — enforce Wangs UI primitives over raw HTML elements."
|
|
4
|
+
metadata:
|
|
5
|
+
owner: wangs-ui
|
|
6
|
+
---
|
|
7
|
+
# Rule: No Raw HTML Controls
|
|
8
|
+
|
|
9
|
+
Zero raw HTML elements. Use Wangs UI primitives and Foundation theme typography.
|
|
10
|
+
|
|
11
|
+
## Prohibitions
|
|
12
|
+
|
|
13
|
+
- No `<button>`, `<input>`, `<select>`, `<textarea>`, `<form>`, `<table>`, `<dialog>`.
|
|
14
|
+
- No `<h1>`-`<h6>`, `<p>`, `<span>` for text typography without Wangs UI theme primitives.
|
|
15
|
+
|
|
16
|
+
## Mandatory Imports
|
|
17
|
+
|
|
18
|
+
- Primitives via subpath:
|
|
19
|
+
- `@wangs-ui/react-core/primitive/button`
|
|
20
|
+
- `@wangs-ui/react-core/primitive/input`
|
|
21
|
+
- `@wangs-ui/react-core/primitive/datatable`
|
|
22
|
+
- `@wangs-ui/react-core/primitive/modal`
|
|
23
|
+
- `@wangs-ui/react-core/primitive/dialogform`
|
|
24
|
+
- `@wangs-ui/react-core/primitive/card`
|
|
25
|
+
- `@wangs-ui/react-core/primitive/badge`
|
|
26
|
+
- Typography via Foundation theme:
|
|
27
|
+
- `@wangs-ui/foundation/theme` (`Text`, `Code`, `Kbd`, `Link`, `Mark`, `Blockquote`, `List`)
|
|
28
|
+
- Use variants: `display*`, `headline*`, `title*`, `body*`, `label*`.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
---
|
|
2
|
+
trigger: model_decision
|
|
3
|
+
description: "Apply when writing or reviewing JSX layout for redundant wrapper containers."
|
|
4
|
+
metadata:
|
|
5
|
+
owner: wangs-ui
|
|
6
|
+
---
|
|
7
|
+
# Rule: No Redundant Layout & Container Wrappers
|
|
8
|
+
|
|
9
|
+
> **Severity**: **ARCHITECTURAL CODE SMELL / LINT FAILURE**.
|
|
10
|
+
|
|
11
|
+
Components 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.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 1. Prohibited Anti-Patterns
|
|
16
|
+
|
|
17
|
+
1. **Single-Child Wrapper `<div>`**:
|
|
18
|
+
Never wrap a single component (e.g. `<DataTable />`, `<Card />`, `<Tabs />`, `<Input />`, `<Button />`) inside an isolated `<div>` that has no layout siblings.
|
|
19
|
+
2. **Intermediate Tab/Card Wrappers**:
|
|
20
|
+
Do not insert pass-through `<div>` wrappers between `<Card>`/`<Tabs>` and feature content:
|
|
21
|
+
```tsx
|
|
22
|
+
// ❌ Bad — Redundant intermediate div wrapping a single tab component
|
|
23
|
+
function TabbedPage() {
|
|
24
|
+
return (
|
|
25
|
+
<Card>
|
|
26
|
+
<Tabs ... />
|
|
27
|
+
<div>
|
|
28
|
+
{activeTab === 'general' ? <GeneralTab /> : <AdvancedTab />}
|
|
29
|
+
</div>
|
|
30
|
+
</Card>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// ✅ Good — Render active tab directly as Card child
|
|
35
|
+
function TabbedPage() {
|
|
36
|
+
return (
|
|
37
|
+
<Card>
|
|
38
|
+
<Tabs ... />
|
|
39
|
+
{activeTab === 'general' ? <GeneralTab /> : <AdvancedTab />}
|
|
40
|
+
</Card>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
3. **Redundant Table Wrappers**:
|
|
45
|
+
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.
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## 2. When a Layout Wrapper Is Permitted
|
|
50
|
+
|
|
51
|
+
A 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>`:
|
|
52
|
+
|
|
53
|
+
1. **Alignment bars**: header title + search + action buttons → `<HStack justify="between">`.
|
|
54
|
+
2. **Multi-column/card grids**: responsive multi-card or multi-field layouts → `<Grid columns={{ compact: 1, medium: 2, expanded: 3 }}>`.
|
|
55
|
+
3. **Fragment Alternative**: when returning multiple adjacent elements without layout coordination, use React Fragment (`<>...</>`) — not an empty wrapper.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
trigger: model_decision
|
|
3
|
+
description: "Apply when doing a final review pass on React 19 component or hook code for compiler-optimization compliance."
|
|
4
|
+
metadata:
|
|
5
|
+
owner: wangs-ui
|
|
6
|
+
---
|
|
7
|
+
# Rule: React 19 — Review Checklist & Quick Reference
|
|
8
|
+
|
|
9
|
+
## Checking Compiler Optimization
|
|
10
|
+
|
|
11
|
+
- **Build output** — compiled files import `react/compiler-runtime` and contain `Symbol.for("react.memo_cache_sentinel")`; if absent, the compiler never ran.
|
|
12
|
+
- **ESLint / Oxlint** — compiler's recommended rules flag Rules-of-React violations at lint time.
|
|
13
|
+
|
|
14
|
+
## Review Checklist
|
|
15
|
+
|
|
16
|
+
- [ ] 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
|
|
17
|
+
- [ ] Compiler confirmed active (build output imports `react/compiler-runtime`) before removing any _existing_ manual memoization
|
|
18
|
+
- [ ] No new `useMemo`/`useCallback`/`React.memo` added without documented reason (confirmed bail-out, or external boundary)
|
|
19
|
+
- [ ] No prop/state/context mutation anywhere in render
|
|
20
|
+
- [ ] All hooks called unconditionally at top level, same order every render
|
|
21
|
+
- [ ] Side effects live in `useEffect`/event handlers, never during render
|
|
22
|
+
- [ ] Components are `PascalCase`; hooks are `camelCase` and prefixed `use`
|
|
23
|
+
- [ ] `ref` accepted as normal prop instead of `forwardRef`
|
|
24
|
+
- [ ] Action/optimistic-update state modeled as discriminated union, not optional fields
|
|
25
|
+
- [ ] Mutually exclusive prop combinations modeled as discriminated union `Props` type
|
|
26
|
+
- [ ] Any `"use no memo"` usage has a comment explaining why
|
|
27
|
+
|
|
28
|
+
## Quick Reference
|
|
29
|
+
|
|
30
|
+
| Situation | Do |
|
|
31
|
+
| ----------------------------------------------------------- | ------------------------------------------------------------- |
|
|
32
|
+
| Tempted to write `useMemo`/`useCallback` | Don't — write plain expression, let compiler decide |
|
|
33
|
+
| Need a ref on a function component | Accept `ref` as a prop, skip `forwardRef` |
|
|
34
|
+
| Form/async state with distinct outcomes | Discriminated union via `useActionState`, not optional fields |
|
|
35
|
+
| Callback needs latest props/state without re-running effect | `useEffectEvent` |
|
|
36
|
+
| A hook/library is known-incompatible with compiler | `"use no memo"` at top of that function, with comment |
|
|
37
|
+
| Checking if optimization is happening | Build output imports `react/compiler-runtime` (has `react.memo_cache_sentinel`) + compiler lint rules |
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
trigger: model_decision
|
|
3
|
+
description: "Apply when writing or reviewing render logic in a React 19 component."
|
|
4
|
+
metadata:
|
|
5
|
+
owner: wangs-ui
|
|
6
|
+
---
|
|
7
|
+
# Rule: React 19 — Compiler-Friendly Render Patterns
|
|
8
|
+
|
|
9
|
+
- 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.
|
|
10
|
+
- Avoid module-level mutable variables read or written during render — that state is invisible to the compiler and breaks idempotence.
|
|
11
|
+
- 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.
|
|
12
|
+
- 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.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
trigger: model_decision
|
|
3
|
+
description: "Apply when naming a React 19 component, hook, or prop."
|
|
4
|
+
metadata:
|
|
5
|
+
owner: wangs-ui
|
|
6
|
+
---
|
|
7
|
+
# Rule: React 19 — Naming Conventions
|
|
8
|
+
|
|
9
|
+
The compiler identifies what to optimize by naming heuristics, same as the Rules of Hooks linter:
|
|
10
|
+
|
|
11
|
+
| Kind | Convention | Notes |
|
|
12
|
+
| ------------------------------------------------------------------------ | ------------------------------- | ----------------------------------------------------------------------------- |
|
|
13
|
+
| Components | `PascalCase`, returns JSX | Compiler treats it as a component to optimize |
|
|
14
|
+
| Custom hooks | `camelCase`, prefixed `use` | Required for both Rules-of-Hooks lint and compiler analysis |
|
|
15
|
+
| 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 |
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
---
|
|
2
|
+
trigger: model_decision
|
|
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
|
+
metadata:
|
|
5
|
+
owner: wangs-ui
|
|
6
|
+
---
|
|
7
|
+
# Rule: React 19 — Stop Hand-Rolling Memoization
|
|
8
|
+
|
|
9
|
+
> **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.
|
|
10
|
+
|
|
11
|
+
Don'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.
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
// ❌ Old habit — noisy, and a mismatched dependency array is a whole class of bugs
|
|
15
|
+
const filteredUsers = useMemo(() => users.filter((u) => u.isActive), [users]);
|
|
16
|
+
const handleClick = useCallback(() => onSelect(user.id), [onSelect, user.id]);
|
|
17
|
+
|
|
18
|
+
// ✅ New default — just write the logic; the compiler memoizes what's worth memoizing
|
|
19
|
+
const filteredUsers = users.filter((u) => u.isActive);
|
|
20
|
+
const handleClick = () => onSelect(user.id);
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## When manual memoization is still justified:
|
|
24
|
+
|
|
25
|
+
- 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.
|
|
26
|
+
- 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).
|
|
27
|
+
- 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.
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
---
|
|
2
|
+
trigger: model_decision
|
|
3
|
+
description: "Apply when typing React 19 primitives — ref props, useActionState, useOptimistic, use(), useEffectEvent."
|
|
4
|
+
metadata:
|
|
5
|
+
owner: wangs-ui
|
|
6
|
+
---
|
|
7
|
+
# Rule: React 19 — Typing React 19 Primitives
|
|
8
|
+
|
|
9
|
+
## `ref` as a Normal Prop
|
|
10
|
+
|
|
11
|
+
`forwardRef` is no longer required for most cases; function components accept `ref` directly.
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
type InputProps = {
|
|
15
|
+
ref?: React.Ref<HTMLInputElement>;
|
|
16
|
+
placeholder?: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
function TextInput({ ref, placeholder }: InputProps) {
|
|
20
|
+
return <input ref={ref} placeholder={placeholder} />;
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Actions with `useActionState`
|
|
25
|
+
|
|
26
|
+
Type the state and payload as generics; model the result as a discriminated union rather than optional fields.
|
|
27
|
+
|
|
28
|
+
```tsx
|
|
29
|
+
type FormState = { status: 'idle' } | { status: 'error'; message: string } | { status: 'success' };
|
|
30
|
+
|
|
31
|
+
const [state, formAction, isPending] = useActionState<FormState, FormData>(
|
|
32
|
+
async (_previous, formData) => {
|
|
33
|
+
const email = formData.get('email');
|
|
34
|
+
if (typeof email !== 'string' || !email.includes('@')) {
|
|
35
|
+
return { status: 'error', message: 'Invalid email' };
|
|
36
|
+
}
|
|
37
|
+
await submit(email);
|
|
38
|
+
return { status: 'success' };
|
|
39
|
+
},
|
|
40
|
+
{ status: 'idle' },
|
|
41
|
+
);
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Optimistic Updates with `useOptimistic`
|
|
45
|
+
|
|
46
|
+
Type both the state and the update shape.
|
|
47
|
+
|
|
48
|
+
```tsx
|
|
49
|
+
const [optimisticTodos, addOptimisticTodo] = useOptimistic<Todo[], Todo>(
|
|
50
|
+
todos,
|
|
51
|
+
(state, newTodo) => [...state, newTodo],
|
|
52
|
+
);
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Reading Promises or Context with `use()`
|
|
56
|
+
|
|
57
|
+
Type the resolved value, not the promise wrapper; `use()` is not a hook and may be called conditionally.
|
|
58
|
+
|
|
59
|
+
```tsx
|
|
60
|
+
function Comments({ commentsPromise }: { commentsPromise: Promise<Comment[]> }) {
|
|
61
|
+
const comments = use(commentsPromise); // suspends until resolved
|
|
62
|
+
return (
|
|
63
|
+
<ul>
|
|
64
|
+
{comments.map((c) => (
|
|
65
|
+
<li key={c.id}>{c.text}</li>
|
|
66
|
+
))}
|
|
67
|
+
</ul>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Stable Event Callbacks with `useEffectEvent` (React 19.2+)
|
|
73
|
+
|
|
74
|
+
Separates "event" logic from "reactive" effect logic so the callback always sees latest props/state without being listed as an effect dependency.
|
|
75
|
+
|
|
76
|
+
```tsx
|
|
77
|
+
const onVisit = useEffectEvent((url: string) => {
|
|
78
|
+
logVisit(url, theme); // always fresh `theme`, never re-triggers the effect
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
onVisit(url);
|
|
83
|
+
}, [url]); // `theme` intentionally omitted — onVisit is stable
|
|
84
|
+
```
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
---
|
|
2
|
+
trigger: model_decision
|
|
3
|
+
description: "Apply when defining a component's Props type or interface."
|
|
4
|
+
metadata:
|
|
5
|
+
owner: wangs-ui
|
|
6
|
+
---
|
|
7
|
+
# Rule: React 19 — Typing Props
|
|
8
|
+
|
|
9
|
+
- `interface` for a component's `Props` — it's an entity shape, often extended.
|
|
10
|
+
- A discriminated union when a component has mutually exclusive prop combinations, instead of a pile of optional props that can contradict each other.
|
|
11
|
+
|
|
12
|
+
```tsx
|
|
13
|
+
// ❌ Bad — nothing stops passing both `href` and `onClick` incoherently
|
|
14
|
+
interface ButtonProps {
|
|
15
|
+
label: string;
|
|
16
|
+
href?: string;
|
|
17
|
+
onClick?: () => void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// ✅ Good — the two variants can't be mixed
|
|
21
|
+
type ButtonProps =
|
|
22
|
+
| { variant: 'link'; label: string; href: string }
|
|
23
|
+
| { variant: 'action'; label: string; onClick: () => void };
|
|
24
|
+
```
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
trigger: model_decision
|
|
3
|
+
description: "Apply when writing or reviewing a React 19 component or hook body for purity and immutability."
|
|
4
|
+
metadata:
|
|
5
|
+
owner: wangs-ui
|
|
6
|
+
---
|
|
7
|
+
# Rule: React 19 — Purity and Immutability (Load-Bearing)
|
|
8
|
+
|
|
9
|
+
The compiler assumes your components and hooks are pure. Violating these rules causes the compiler to silently skip optimizing that component:
|
|
10
|
+
|
|
11
|
+
- **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.
|
|
12
|
+
- **Immutability** — never mutate props, state, or context directly. Always create new objects/arrays for changes.
|
|
13
|
+
- **Side effects only in effects or event handlers** — never during render.
|
|
14
|
+
- **Hooks called unconditionally, top-level, same order every render** — no hooks inside conditionals, loops, or nested functions.
|
|
15
|
+
|
|
16
|
+
```tsx
|
|
17
|
+
// ❌ Mutates a prop — breaks purity and the compiler can't safely memoize this
|
|
18
|
+
function TodoList({ todos }: { todos: Todo[] }) {
|
|
19
|
+
todos.sort((a, b) => a.priority - b.priority); // mutates caller's array
|
|
20
|
+
return (
|
|
21
|
+
<ul>
|
|
22
|
+
{todos.map((t) => (
|
|
23
|
+
<li key={t.id}>{t.title}</li>
|
|
24
|
+
))}
|
|
25
|
+
</ul>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// ✅ Creates a new array — pure, compiler-safe
|
|
30
|
+
function TodoList({ todos }: { todos: Todo[] }) {
|
|
31
|
+
const sorted = [...todos].sort((a, b) => a.priority - b.priority);
|
|
32
|
+
return (
|
|
33
|
+
<ul>
|
|
34
|
+
{sorted.map((t) => (
|
|
35
|
+
<li key={t.id}>{t.title}</li>
|
|
36
|
+
))}
|
|
37
|
+
</ul>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
```
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
---
|
|
2
|
+
trigger: model_decision
|
|
3
|
+
description: 'Apply when configuring React Compiler tooling or opting a component out via "use no memo".'
|
|
4
|
+
metadata:
|
|
5
|
+
owner: wangs-ui
|
|
6
|
+
---
|
|
7
|
+
# Rule: React 19 — Tooling Setup & Compiler Opt-Out
|
|
8
|
+
|
|
9
|
+
## Compiler Setup Is Mandatory
|
|
10
|
+
|
|
11
|
+
**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.
|
|
12
|
+
|
|
13
|
+
Plain `@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`.
|
|
14
|
+
|
|
15
|
+
### Verify it's active
|
|
16
|
+
|
|
17
|
+
- 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`).
|
|
18
|
+
- 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.
|
|
19
|
+
- The `react/react-compiler` lint rule is enabled.
|
|
20
|
+
|
|
21
|
+
### Setup (when missing)
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
# Compiler (build-time transform)
|
|
25
|
+
pnpm add -D --save-exact babel-plugin-react-compiler@latest
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Lint rules — oxlint
|
|
29
|
+
|
|
30
|
+
Oxlint ships a **native, Rust-based** `react/react-compiler` rule:
|
|
31
|
+
|
|
32
|
+
```json
|
|
33
|
+
// .oxlintrc.json
|
|
34
|
+
{
|
|
35
|
+
"plugins": ["react"],
|
|
36
|
+
"rules": {
|
|
37
|
+
"react/react-compiler": "error"
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
This single rule reports:
|
|
43
|
+
|
|
44
|
+
- **Rules-of-React violations** (conditional hooks, reading a ref during render, mutating props) — must-fix bugs.
|
|
45
|
+
- **Compiler bail-outs** — places compiler declined to optimize without rule violation.
|
|
46
|
+
|
|
47
|
+
### Wiring into Vite 8
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
// vite.config.ts
|
|
51
|
+
import { defineConfig } from 'vite';
|
|
52
|
+
import react, { reactCompilerPreset } from '@vitejs/plugin-react';
|
|
53
|
+
import babel from '@rolldown/plugin-babel';
|
|
54
|
+
|
|
55
|
+
export default defineConfig({
|
|
56
|
+
plugins: [
|
|
57
|
+
babel({ presets: [reactCompilerPreset()] }), // must run before react()
|
|
58
|
+
react(),
|
|
59
|
+
],
|
|
60
|
+
});
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
pnpm add -D @rolldown/plugin-babel @babel/core babel-plugin-react-compiler
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Incompatibility Escape Hatch (`"use no memo"`)
|
|
68
|
+
|
|
69
|
+
If 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:
|
|
70
|
+
|
|
71
|
+
```tsx
|
|
72
|
+
function LegacyForm() {
|
|
73
|
+
'use no memo';
|
|
74
|
+
const form = useForm(); // incompatible with the compiler today
|
|
75
|
+
// ...
|
|
76
|
+
}
|
|
77
|
+
```
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
---
|
|
2
|
+
trigger: model_decision
|
|
3
|
+
description: "Apply when tempted to override a Wangs UI theme CSS variable directly."
|
|
4
|
+
metadata:
|
|
5
|
+
owner: wangs-ui
|
|
6
|
+
---
|
|
7
|
+
# Wangs UI Fact: Theme Variables Are Engine-Internal, Never Override Directly
|
|
8
|
+
|
|
9
|
+
Manually defining or overriding `:root`, `[data-mode='dark']`, or `.dark` custom
|
|
10
|
+
properties owned by `@wangs-ui/foundation/theme` (`--color-*`, `--z-index-*`,
|
|
11
|
+
`--spacing-*`, `--size-*`, `--shadow-*`, `--radius-*`) breaks the theme token
|
|
12
|
+
engine: it destroys automatic light/dark mode transitions, corrupts sub-tree
|
|
13
|
+
density overrides made via `<ThemeProvider>`, and causes cross-module visual
|
|
14
|
+
bugs that don't reproduce consistently. This is true for any project consuming
|
|
15
|
+
`@wangs-ui/foundation`, not specific to one app's setup.
|
|
16
|
+
|
|
17
|
+
The supported customization surface is `WangsUiProvider.configOptions.preset`
|
|
18
|
+
— discover it via the `design-system`/`wangs-ui-components` skills' MCP calls,
|
|
19
|
+
never by reading or guessing the CSS variable names.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
trigger: model_decision
|
|
3
|
+
description: "Apply when writing or reviewing a type assertion (`as X`) or non-null assertion (`x!`)."
|
|
4
|
+
metadata:
|
|
5
|
+
owner: wangs-ui
|
|
6
|
+
---
|
|
7
|
+
# Rule: TypeScript — Type Assertions & Non-Null Assertions are a Last Resort
|
|
8
|
+
|
|
9
|
+
- `as X` and `x!` tell the compiler "trust me" — they produce zero runtime safety and actively hide bugs if wrong.
|
|
10
|
+
- 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.
|
|
11
|
+
- Never use `as any` or `as unknown as X` to force an incompatible cast — that's `any` wearing a disguise.
|
|
12
|
+
- `x!` should almost always be replaceable by an actual null check or optional chaining (`x?.y`) plus a real fallback.
|