@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
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
trigger: model_decision
|
|
3
|
+
description: "Apply when doing a final TypeScript review pass on a file."
|
|
4
|
+
metadata:
|
|
5
|
+
owner: wangs-ui
|
|
6
|
+
---
|
|
7
|
+
# Rule: TypeScript — Review Checklist & Quick Reference
|
|
8
|
+
|
|
9
|
+
## Review Checklist
|
|
10
|
+
|
|
11
|
+
Before considering TypeScript code "done," verify:
|
|
12
|
+
|
|
13
|
+
- [ ] No `any` anywhere (including implicit `any` from missing annotations)
|
|
14
|
+
- [ ] External/uncertain data enters as `unknown` and is narrowed before use
|
|
15
|
+
- [ ] Variant state is a discriminated union, not optional fields + booleans
|
|
16
|
+
- [ ] `interface` used for object/entity shapes; `type` used for unions/aliases/intersections
|
|
17
|
+
- [ ] No stray `I` prefixes on interfaces
|
|
18
|
+
- [ ] Naming follows casing conventions consistently
|
|
19
|
+
- [ ] `as` / `!` are rare, justified, and can't be replaced by a guard or null check
|
|
20
|
+
- [ ] Exported functions/methods have explicit return types
|
|
21
|
+
- [ ] Switch statements over unions have an exhaustiveness (`never`) check
|
|
22
|
+
- [ ] `tsconfig.json` includes the strictness baseline
|
|
23
|
+
|
|
24
|
+
## Quick Reference
|
|
25
|
+
|
|
26
|
+
| Situation | Use |
|
|
27
|
+
| ---------------------------------------------- | ------------------------------------------------------------- |
|
|
28
|
+
| External/uncertain data | `unknown` + narrowing |
|
|
29
|
+
| "This value is definitely one of these shapes" | Discriminated union (`type`) |
|
|
30
|
+
| Object with identity, may be extended | `interface` |
|
|
31
|
+
| Union, intersection, tuple, mapped type | `type` |
|
|
32
|
+
| Need to prove a type through logic | Type guard / narrowing |
|
|
33
|
+
| Tempted to write `any` | Stop — use `unknown`, a generic, or a local interface instead |
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
---
|
|
2
|
+
trigger: model_decision
|
|
3
|
+
description: "Apply when modeling a variant or multi-shape state in TypeScript."
|
|
4
|
+
metadata:
|
|
5
|
+
owner: wangs-ui
|
|
6
|
+
---
|
|
7
|
+
# Rule: TypeScript — Discriminated Unions for Variant State
|
|
8
|
+
|
|
9
|
+
Whenever 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.
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
// ❌ Bad — booleans can contradict each other; unclear which fields are valid together
|
|
13
|
+
interface FetchState {
|
|
14
|
+
isLoading: boolean;
|
|
15
|
+
isError: boolean;
|
|
16
|
+
data?: User;
|
|
17
|
+
error?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// ✅ Good — only one shape is possible at a time, and the compiler enforces it
|
|
21
|
+
type FetchState =
|
|
22
|
+
| { status: 'idle' }
|
|
23
|
+
| { status: 'loading' }
|
|
24
|
+
| { status: 'success'; data: User }
|
|
25
|
+
| { status: 'error'; error: string };
|
|
26
|
+
|
|
27
|
+
function render(state: FetchState) {
|
|
28
|
+
switch (state.status) {
|
|
29
|
+
case 'success':
|
|
30
|
+
return state.data.name; // `data` is guaranteed to exist here
|
|
31
|
+
case 'error':
|
|
32
|
+
return state.error; // `error` is guaranteed to exist here
|
|
33
|
+
default:
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Use a consistent tag field name across a codebase (`kind`, `type`, or `status` — pick one and stick with it) so narrowing patterns stay predictable.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
---
|
|
2
|
+
trigger: model_decision
|
|
3
|
+
description: "Apply when writing or reviewing an exported function, class method, or public API surface in TypeScript."
|
|
4
|
+
metadata:
|
|
5
|
+
owner: wangs-ui
|
|
6
|
+
---
|
|
7
|
+
# Rule: TypeScript — Explicit Return Types on Exported Functions
|
|
8
|
+
|
|
9
|
+
Inference 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.
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
// ❌ Return type is inferred and can silently drift
|
|
13
|
+
export function getActiveUsers(users: User[]) {
|
|
14
|
+
return users.filter((u) => u.active);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// ✅ Explicit, intentional contract
|
|
18
|
+
export function getActiveUsers(users: User[]): User[] {
|
|
19
|
+
return users.filter((u) => u.active);
|
|
20
|
+
}
|
|
21
|
+
```
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
---
|
|
2
|
+
trigger: model_decision
|
|
3
|
+
description: "Apply when deciding between `interface` and `type` for a new TypeScript declaration."
|
|
4
|
+
metadata:
|
|
5
|
+
owner: wangs-ui
|
|
6
|
+
---
|
|
7
|
+
# Rule: TypeScript — `interface` vs `type`
|
|
8
|
+
|
|
9
|
+
Both can describe object shapes, but they signal different intent. Default rule:
|
|
10
|
+
|
|
11
|
+
| Use `interface` for... | Use `type` for... |
|
|
12
|
+
| -------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
|
|
13
|
+
| Object / entity shapes (a `User`, a `Product`, a component's `Props`) | Unions (`"a" \| "b"`) and discriminated unions |
|
|
14
|
+
| Public API contracts meant to be `implements`-ed by classes | Intersections (`A & B`) |
|
|
15
|
+
| Shapes that consumers may want to **extend/augment** (declaration merging) | Tuples (`[string, number]`) |
|
|
16
|
+
| | Function types / callback signatures |
|
|
17
|
+
| | Mapped, conditional, or utility-derived types (`Partial<T>`, `Pick<T, K>`) |
|
|
18
|
+
| | Aliasing a primitive or another type for readability |
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
// ✅ interface — an entity with identity, extendable
|
|
22
|
+
interface User {
|
|
23
|
+
id: string;
|
|
24
|
+
email: string;
|
|
25
|
+
role: UserRole;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface AdminUser extends User {
|
|
29
|
+
permissions: Permission[];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// ✅ type — union, alias, derived shape
|
|
33
|
+
type UserRole = 'admin' | 'editor' | 'viewer';
|
|
34
|
+
type UserId = User['id'];
|
|
35
|
+
type PartialUser = Partial<User>;
|
|
36
|
+
type Callback<T> = (value: T) => void;
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Don'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`.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
---
|
|
2
|
+
trigger: model_decision
|
|
3
|
+
description: "Apply when modeling a fixed set of string or numeric variants in TypeScript."
|
|
4
|
+
metadata:
|
|
5
|
+
owner: wangs-ui
|
|
6
|
+
---
|
|
7
|
+
# Rule: TypeScript — Prefer Literal Unions Over Numeric Enums
|
|
8
|
+
|
|
9
|
+
String 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.
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
// ✅ Preferred
|
|
13
|
+
type OrderStatus = 'pending' | 'shipped' | 'delivered' | 'cancelled';
|
|
14
|
+
|
|
15
|
+
// Acceptable when a namespaced runtime object is actually needed
|
|
16
|
+
const OrderStatus = {
|
|
17
|
+
Pending: 'pending',
|
|
18
|
+
Shipped: 'shipped',
|
|
19
|
+
} as const;
|
|
20
|
+
type OrderStatus = (typeof OrderStatus)[keyof typeof OrderStatus];
|
|
21
|
+
```
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
---
|
|
2
|
+
trigger: model_decision
|
|
3
|
+
description: "Apply when naming a TypeScript type, variable, or constant."
|
|
4
|
+
metadata:
|
|
5
|
+
owner: wangs-ui
|
|
6
|
+
---
|
|
7
|
+
# Rule: TypeScript — Naming Conventions
|
|
8
|
+
|
|
9
|
+
| Kind | Convention | Example |
|
|
10
|
+
| ---------------------------------------------------------- | ------------------------------------------- | ----------------------------------- |
|
|
11
|
+
| Types, interfaces, classes, enums | `PascalCase` | `UserProfile`, `OrderStatus` |
|
|
12
|
+
| Interfaces | `PascalCase`, **no `I` prefix** | `User`, not `IUser` |
|
|
13
|
+
| Type aliases | `PascalCase` | `type ApiResponse<T> = ...` |
|
|
14
|
+
| Variables, functions, methods, properties | `camelCase` | `getUserById`, `isValid` |
|
|
15
|
+
| Booleans | `camelCase` with `is/has/should/can` prefix | `isLoading`, `hasPermission` |
|
|
16
|
+
| True constants (module-level, never reassigned, primitive) | `UPPER_SNAKE_CASE` | `MAX_RETRIES`, `DEFAULT_TIMEOUT_MS` |
|
|
17
|
+
| Enum members | `PascalCase` | `enum Status { Active, Archived }` |
|
|
18
|
+
| Generic type parameters (simple, single-purpose) | Single uppercase letter | `T`, `K`, `V`, `E` for errors |
|
|
19
|
+
| Generic type parameters (multiple / non-obvious) | Descriptive, prefixed with `T` | `TInput`, `TOutput`, `TContext` |
|
|
20
|
+
| Discriminated union tag field | Consistent across the codebase | `kind`, `type`, or `status` |
|
|
21
|
+
| Files with a single exported entity | Match the entity name | `UserProfile.ts`, `useAuth.ts` |
|
|
22
|
+
|
|
23
|
+
Naming should describe **intent**, not implementation — `fetchUser` not `getUserFromApiEndpoint`; `retryCount` not `numRetries2`.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
---
|
|
2
|
+
trigger: model_decision
|
|
3
|
+
description: "Apply when narrowing an `unknown` or union-typed value in TypeScript."
|
|
4
|
+
metadata:
|
|
5
|
+
owner: wangs-ui
|
|
6
|
+
---
|
|
7
|
+
# Rule: TypeScript — `unknown` + Narrowing, Not Casting
|
|
8
|
+
|
|
9
|
+
Prefer proving a type through control flow over asserting it with `as`.
|
|
10
|
+
|
|
11
|
+
## Narrowing techniques, in order of preference:
|
|
12
|
+
|
|
13
|
+
1. **`typeof`** — primitives (`string`, `number`, `boolean`, `undefined`, `function`)
|
|
14
|
+
2. **`instanceof`** — class instances, `Error`, `Date`, custom classes
|
|
15
|
+
3. **`in`** — checking a property exists before accessing it on a union/unknown
|
|
16
|
+
4. **User-defined type guards** — `function isUser(x: unknown): x is User`
|
|
17
|
+
5. **Discriminated union tag checks** — `switch (value.kind) { ... }`
|
|
18
|
+
6. **Exhaustiveness checks** — a `never`-typed default branch so adding a new variant is a compile error until every switch/if-chain handles it
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
// ✅ Type guard
|
|
22
|
+
function isUser(value: unknown): value is User {
|
|
23
|
+
return typeof value === 'object' && value !== null && 'id' in value && 'email' in value;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// ✅ Exhaustiveness check
|
|
27
|
+
function assertNever(x: never): never {
|
|
28
|
+
throw new Error(`Unhandled case: ${JSON.stringify(x)}`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function area(shape: Shape): number {
|
|
32
|
+
switch (shape.kind) {
|
|
33
|
+
case 'circle':
|
|
34
|
+
return Math.PI * shape.radius ** 2;
|
|
35
|
+
case 'square':
|
|
36
|
+
return shape.side ** 2;
|
|
37
|
+
default:
|
|
38
|
+
return assertNever(shape); // compile error if a variant is missed
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Type assertions (`as X`) and the non-null assertion (`!`) bypass this entirely — treat them as a last resort, not a shortcut.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
---
|
|
2
|
+
trigger: model_decision
|
|
3
|
+
description: "Apply when declaring a TypeScript collection or object shape."
|
|
4
|
+
metadata:
|
|
5
|
+
owner: wangs-ui
|
|
6
|
+
---
|
|
7
|
+
# Rule: TypeScript — Readonly by Default
|
|
8
|
+
|
|
9
|
+
Prefer immutable shapes unless mutation is intentional and localized.
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
interface Point {
|
|
13
|
+
readonly x: number;
|
|
14
|
+
readonly y: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function config(values: readonly string[]) {
|
|
18
|
+
/* ... */
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const ROLES = ['admin', 'editor', 'viewer'] as const;
|
|
22
|
+
type UserRole = (typeof ROLES)[number];
|
|
23
|
+
```
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
---
|
|
2
|
+
trigger: model_decision
|
|
3
|
+
description: "Apply when configuring or reviewing tsconfig.json compiler strictness options."
|
|
4
|
+
metadata:
|
|
5
|
+
owner: wangs-ui
|
|
6
|
+
---
|
|
7
|
+
# Rule: TypeScript — Baseline `tsconfig.json` Strictness
|
|
8
|
+
|
|
9
|
+
Treat these as the non-negotiable floor for any project this rule touches:
|
|
10
|
+
|
|
11
|
+
```json
|
|
12
|
+
{
|
|
13
|
+
"compilerOptions": {
|
|
14
|
+
"strict": true,
|
|
15
|
+
"noImplicitAny": true,
|
|
16
|
+
"strictNullChecks": true,
|
|
17
|
+
"strictFunctionTypes": true,
|
|
18
|
+
"strictPropertyInitialization": true,
|
|
19
|
+
"noUncheckedIndexedAccess": true,
|
|
20
|
+
"exactOptionalPropertyTypes": true,
|
|
21
|
+
"noImplicitOverride": true,
|
|
22
|
+
"noFallthroughCasesInSwitch": true,
|
|
23
|
+
"noUnusedLocals": true,
|
|
24
|
+
"noUnusedParameters": true,
|
|
25
|
+
"forceConsistentCasingInFileNames": true
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
`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).
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
trigger: model_decision
|
|
3
|
+
description: "Apply when writing or reviewing any TypeScript type annotation."
|
|
4
|
+
metadata:
|
|
5
|
+
owner: wangs-ui
|
|
6
|
+
---
|
|
7
|
+
# Rule: TypeScript — Never Use `any`
|
|
8
|
+
|
|
9
|
+
`any` is not "unknown type," it's "type checking off." It's contagious — once a value is `any`, everything it touches becomes unchecked too.
|
|
10
|
+
|
|
11
|
+
- Never write `any` for parameters, return types, variables, or generics.
|
|
12
|
+
- Use `unknown` for genuinely unknown external data (API responses, `JSON.parse`, catch clauses, third-party callbacks) and narrow it before use.
|
|
13
|
+
- Use generics (`<T>`) when a function needs to work across types but preserve the relationship between input and output.
|
|
14
|
+
- If a library ships untyped, write a minimal local type/interface for the surface area you actually use instead of reaching for `any`.
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
// ❌ Bad
|
|
18
|
+
function parseConfig(json: any) {
|
|
19
|
+
return json.settings.theme; // no safety, no autocomplete, silent runtime crash
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// ✅ Good
|
|
23
|
+
function parseConfig(json: unknown): string {
|
|
24
|
+
if (
|
|
25
|
+
typeof json === 'object' &&
|
|
26
|
+
json !== null &&
|
|
27
|
+
'settings' in json &&
|
|
28
|
+
typeof (json as { settings: unknown }).settings === 'object'
|
|
29
|
+
) {
|
|
30
|
+
// still narrow further or validate with a schema library (zod, valibot, etc.)
|
|
31
|
+
}
|
|
32
|
+
throw new Error('Invalid config shape');
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The only acceptable `any` is a well-justified, isolated, and commented one (e.g. interfacing with a genuinely untyped legacy module) — never a default.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: craft-theme
|
|
3
|
+
description: 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.
|
|
4
|
+
metadata:
|
|
5
|
+
owner: wangs-ui
|
|
6
|
+
---
|
|
7
|
+
# Skill: Craft Theme
|
|
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.
|
|
10
|
+
|
|
11
|
+
## The rule this skill exists to enforce
|
|
12
|
+
|
|
13
|
+
**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.
|
|
14
|
+
|
|
15
|
+
## 1. Generate the palette
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pnpm exec wangs-ui-generate-palette --name=<id> --primary=<#hex> [options] --out=<path>
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
- `--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`).
|
|
22
|
+
- `--primary` (required): the brand/key hex color, e.g. `#ff6b35`.
|
|
23
|
+
- 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):
|
|
24
|
+
`--secondary=#hex --tertiary=#hex --general=#hex --success=#hex --danger=#hex --warning=#hex --info=#hex`
|
|
25
|
+
- `--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.
|
|
26
|
+
|
|
27
|
+
If `@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`.
|
|
28
|
+
|
|
29
|
+
**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.
|
|
30
|
+
|
|
31
|
+
## 2. Wire it into the app
|
|
32
|
+
|
|
33
|
+
The generated file exports a plain `Palette` object — pass it directly to `WangsUiProvider`'s `theme.palette` (or `theme.defaultPalette` for uncontrolled mode):
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
import { WangsUiProvider } from '@wangs-ui/react-core/api';
|
|
37
|
+
import preset from '@wangs-ui/react-presets/fixedasset'; // or whichever preset the app uses
|
|
38
|
+
import { sunsetPalette } from './theme/sunset';
|
|
39
|
+
|
|
40
|
+
const App = () => (
|
|
41
|
+
<WangsUiProvider configOptions={{ preset }} theme={{ palette: sunsetPalette, mode: 'light' }}>
|
|
42
|
+
<YourApp />
|
|
43
|
+
</WangsUiProvider>
|
|
44
|
+
);
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
`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.
|
|
48
|
+
|
|
49
|
+
## 3. Regenerating / evolving an existing custom palette
|
|
50
|
+
|
|
51
|
+
To 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:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
pnpm exec wangs-ui-generate-palette --name=sunset --primary=#ff6b35 --tertiary=#2a9d8f --out=src/theme/sunset.ts
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## 4. If you're extending Wangs UI itself (contributing a new official palette)
|
|
58
|
+
|
|
59
|
+
This 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:
|
|
60
|
+
|
|
61
|
+
1. Run the generator with `--out` pointing at `packages/foundation/theme/tokens/palettes/<name>.ts`.
|
|
62
|
+
2. 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`.
|
|
63
|
+
3. 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.
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: create-form
|
|
3
3
|
description: 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.
|
|
4
|
+
metadata:
|
|
5
|
+
owner: wangs-ui
|
|
4
6
|
---
|
|
5
|
-
|
|
6
7
|
# Skill: Form Architecture & Validation Workflows
|
|
7
8
|
|
|
8
9
|
Use this skill when building forms, data entry panels, modal forms, settings pages, or multipart forms in Wangs UI applications.
|
|
@@ -13,28 +14,36 @@ Use this skill when building forms, data entry panels, modal forms, settings pag
|
|
|
13
14
|
|
|
14
15
|
Do **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:
|
|
15
16
|
|
|
16
|
-
### Component & Form
|
|
17
|
+
### Component & Form API Protocol:
|
|
18
|
+
|
|
19
|
+
```json
|
|
20
|
+
get_component_api({ "component": "form" })
|
|
21
|
+
get_component_api({ "component": "field" })
|
|
22
|
+
get_component_api({ "component": "dialogform" })
|
|
23
|
+
get_component_api({ "component": "input" })
|
|
24
|
+
get_component_api({ "component": "numberinput" })
|
|
25
|
+
get_component_api({ "component": "select" })
|
|
26
|
+
get_component_api({ "component": "multiselect" })
|
|
27
|
+
get_component_api({ "component": "datepicker" })
|
|
28
|
+
get_component_api({ "component": "fileupload" })
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Curated Form Documentation:
|
|
17
32
|
|
|
18
33
|
```json
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
get-documentation({ "id": "input" })
|
|
23
|
-
get-documentation({ "id": "numberinput" })
|
|
24
|
-
get-documentation({ "id": "select" })
|
|
25
|
-
get-documentation({ "id": "multiselect" })
|
|
26
|
-
get-documentation({ "id": "datepicker" })
|
|
27
|
-
get-documentation({ "id": "fileupload" })
|
|
34
|
+
get_documentation({ "id": "form" })
|
|
35
|
+
get_documentation({ "id": "dialogform" })
|
|
36
|
+
get_documentation({ "id": "field" })
|
|
28
37
|
```
|
|
29
38
|
|
|
30
39
|
### Live Storybook & Interactive Behavior Protocol:
|
|
31
40
|
|
|
32
41
|
```json
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
42
|
+
get_component_examples({ "component": "form", "variant": "Default" })
|
|
43
|
+
get_component_examples({ "component": "form", "variant": "AsyncInitialValues" })
|
|
44
|
+
get_component_examples({ "component": "form", "variant": "ConditionalFields" })
|
|
45
|
+
get_component_examples({ "component": "form", "variant": "CascadingOptions" })
|
|
46
|
+
get_component_examples({ "component": "dialogform", "variant": "Default" })
|
|
38
47
|
```
|
|
39
48
|
|
|
40
49
|
### Knowledge Graph & Symbol Usages:
|
|
@@ -74,6 +83,8 @@ query_graph({ "query": "useWatchField" })
|
|
|
74
83
|
- **Children Render Callback**: `Field` yields `{ fieldProps, fieldState }`.
|
|
75
84
|
- **`fieldProps`**: Pass directly to primitive inputs (`<Input {...fieldProps} />`). Contains `name`, `value`, `ref`, `onChange`.
|
|
76
85
|
- **`fieldState`**: Provides `invalid`, `error`, `isDirty`, `isPending`. Pass `invalid={fieldState.invalid}` to primitive components for accessibility and validation styling.
|
|
86
|
+
- **⚠️ 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.
|
|
87
|
+
- **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.
|
|
77
88
|
|
|
78
89
|
---
|
|
79
90
|
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: data-table
|
|
3
3
|
description: Architecture, workflows, and MCP discovery protocol for building DataTables with sorting, pagination, filtering, selection, and export.
|
|
4
|
+
metadata:
|
|
5
|
+
owner: wangs-ui
|
|
4
6
|
---
|
|
5
|
-
|
|
6
7
|
# Skill: DataTable Architecture & Integration Workflows
|
|
7
8
|
|
|
8
9
|
Use this skill when implementing data grids, server-paginated tables, filterable listing views, or batch management interfaces with `@wangs-ui/react-core`.
|
|
@@ -13,24 +14,31 @@ Use this skill when implementing data grids, server-paginated tables, filterable
|
|
|
13
14
|
|
|
14
15
|
Do **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:
|
|
15
16
|
|
|
16
|
-
### Inspect Component Contracts:
|
|
17
|
+
### Inspect Component Contracts (`component` parameter):
|
|
18
|
+
|
|
19
|
+
```json
|
|
20
|
+
get_component_api({ "component": "datatable" })
|
|
21
|
+
get_component_api({ "component": "exportbutton" })
|
|
22
|
+
get_component_api({ "component": "filtercontainer" })
|
|
23
|
+
get_component_api({ "component": "bulkactionbutton" })
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Read Curated Documentation:
|
|
17
27
|
|
|
18
28
|
```json
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
get-documentation({ "id": "filtercontainer" })
|
|
22
|
-
get-documentation({ "id": "bulkactionbutton" })
|
|
29
|
+
get_documentation({ "id": "datatable" })
|
|
30
|
+
get_documentation({ "id": "exportbutton" })
|
|
23
31
|
```
|
|
24
32
|
|
|
25
33
|
### Inspect Live Story Implementations:
|
|
26
34
|
|
|
27
35
|
```json
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
36
|
+
get_component_examples({ "component": "datatable", "variant": "Basic" })
|
|
37
|
+
get_component_examples({ "component": "datatable", "variant": "CursorPagination" })
|
|
38
|
+
get_component_examples({ "component": "datatable", "variant": "Sortable" })
|
|
39
|
+
get_component_examples({ "component": "datatable", "variant": "MultipleSelection" })
|
|
40
|
+
get_component_examples({ "component": "datatable", "variant": "CustomColumn" })
|
|
41
|
+
get_component_examples({ "component": "exportbutton", "variant": "WithTable" })
|
|
34
42
|
```
|
|
35
43
|
|
|
36
44
|
### Inspect Knowledge Graph & Usages:
|
|
@@ -47,7 +55,7 @@ query_graph({ "query": "useDataTableFetch" })
|
|
|
47
55
|
The Wangs UI `DataTable` is built on a modular, headless-first architecture:
|
|
48
56
|
|
|
49
57
|
1. **Declarative Column Definitions (`TableColumn<T>[]`)**:
|
|
50
|
-
Columns are configured as typed array objects, not as JSX children. Check `
|
|
58
|
+
Columns are configured as typed array objects, not as JSX children. Check `get_component_api({ "component": "datatable" })` for column field types.
|
|
51
59
|
2. **Table Instance Hook (`useDataTable`)**:
|
|
52
60
|
Coordinates table state (sorting, pagination, selection, column ordering, pinning, visibility).
|
|
53
61
|
3. **Data Fetching Hook (`useDataTableFetch`)**:
|
|
@@ -62,7 +70,8 @@ The Wangs UI `DataTable` is built on a modular, headless-first architecture:
|
|
|
62
70
|
|
|
63
71
|
## 3. Mandatory Implementation Rules
|
|
64
72
|
|
|
65
|
-
1. **Query MCP for Current Code Patterns**: Always run `
|
|
73
|
+
1. **Query MCP for Current Code Patterns**: Always run `get_component_examples` for `datatable` before drafting code.
|
|
66
74
|
2. **Strict Subpath Imports**: Import via `@wangs-ui/react-core/primitive/datatable` and companion primitive paths.
|
|
67
75
|
3. **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`.
|
|
68
|
-
4. **Stable Row Identity**: Always configure a unique key identifier for stable selection and row identity.
|
|
76
|
+
4. **Stable Row Identity**: Always configure a unique key identifier (`dataKey` / `rowId`) for stable selection and row identity.
|
|
77
|
+
5. **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.
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: dialog-modal
|
|
3
3
|
description: Patterns, overlay selection criteria, and MCP discovery protocol for Dialog, Modal, and DialogForm components in Wangs UI.
|
|
4
|
+
metadata:
|
|
5
|
+
owner: wangs-ui
|
|
4
6
|
---
|
|
5
|
-
|
|
6
7
|
# Skill: Dialog, Modal & Overlay Workflows
|
|
7
8
|
|
|
8
9
|
Use this skill when building interactive modals, create/edit dialog forms, destructive action confirmations, or slide-in overlay panels.
|
|
@@ -13,21 +14,29 @@ Use this skill when building interactive modals, create/edit dialog forms, destr
|
|
|
13
14
|
|
|
14
15
|
Do **NOT** guess overlay props, event names, or footer slots. Query the MCP server dynamically to inspect exact contracts and live story implementations:
|
|
15
16
|
|
|
16
|
-
### Inspect Overlay Contracts:
|
|
17
|
+
### Inspect Overlay Contracts (`component` parameter):
|
|
18
|
+
|
|
19
|
+
```json
|
|
20
|
+
get_component_api({ "component": "dialog" })
|
|
21
|
+
get_component_api({ "component": "dialogform" })
|
|
22
|
+
get_component_api({ "component": "modal" })
|
|
23
|
+
get_component_api({ "component": "toast" })
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Read Curated Overlay Documentation:
|
|
17
27
|
|
|
18
28
|
```json
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
get-documentation({ "id": "toast" })
|
|
29
|
+
get_documentation({ "id": "dialog" })
|
|
30
|
+
get_documentation({ "id": "dialogform" })
|
|
31
|
+
get_documentation({ "id": "modal" })
|
|
23
32
|
```
|
|
24
33
|
|
|
25
34
|
### Inspect Live Story Implementations:
|
|
26
35
|
|
|
27
36
|
```json
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
37
|
+
get_component_examples({ "component": "dialog", "variant": "RichHeaderFooter" })
|
|
38
|
+
get_component_examples({ "component": "dialogform", "variant": "Default" })
|
|
39
|
+
get_component_examples({ "component": "modal", "variant": "Default" })
|
|
31
40
|
```
|
|
32
41
|
|
|
33
42
|
### Inspect Knowledge Graph & Usages:
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: i18n-usage
|
|
3
3
|
description: Comprehensive guidelines for application internationalization, JIT translations (t), ICU formatting, and locale-aware formatting with @wangs-ui/react-i18n.
|
|
4
|
+
metadata:
|
|
5
|
+
owner: wangs-ui
|
|
4
6
|
---
|
|
5
|
-
|
|
6
7
|
# Skill: Application Internationalization & Formatting Protocol
|
|
7
8
|
|
|
8
9
|
Use this skill when implementing multi-language interfaces, translating user-facing text, formatting dates, times, currencies, or numbers in React applications built with Wangs UI and `@wangs-ui/react-i18n`.
|
|
@@ -13,22 +14,29 @@ Use this skill when implementing multi-language interfaces, translating user-fac
|
|
|
13
14
|
|
|
14
15
|
Do **NOT** guess component localization contracts, language switcher variants, or datepicker props. Query the MCP server dynamically to inspect exact props and live story implementations:
|
|
15
16
|
|
|
16
|
-
### Inspect Localized Component Contracts:
|
|
17
|
+
### Inspect Localized Component Contracts (`component` parameter):
|
|
18
|
+
|
|
19
|
+
```json
|
|
20
|
+
get_component_api({ "component": "languageswitcher" })
|
|
21
|
+
get_component_api({ "component": "currencyinput" })
|
|
22
|
+
get_component_api({ "component": "datepicker" })
|
|
23
|
+
get_component_api({ "component": "select" })
|
|
24
|
+
get_component_api({ "component": "datatable" })
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Read Curated Localization Documentation:
|
|
17
28
|
|
|
18
29
|
```json
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
get-documentation({ "id": "datepicker" })
|
|
22
|
-
get-documentation({ "id": "select" })
|
|
23
|
-
get-documentation({ "id": "datatable" })
|
|
30
|
+
get_documentation({ "id": "languageswitcher" })
|
|
31
|
+
get_documentation({ "id": "currencyinput" })
|
|
24
32
|
```
|
|
25
33
|
|
|
26
34
|
### Inspect Live Story Implementations:
|
|
27
35
|
|
|
28
36
|
```json
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
37
|
+
get_component_examples({ "component": "languageswitcher", "variant": "Default" })
|
|
38
|
+
get_component_examples({ "component": "currencyinput", "variant": "Default" })
|
|
39
|
+
get_component_examples({ "component": "datepicker", "variant": "Default" })
|
|
32
40
|
```
|
|
33
41
|
|
|
34
42
|
---
|
|
@@ -132,7 +140,7 @@ selectedCount === 0
|
|
|
132
140
|
Use standard supported HTML tags (`<a>`, `<b>`, `<i>`, `<u>`, `<s>`, `<br/>`, `<sub>`, `<sup>`, `<code>`, `<mark>`) for inline styling. Tags are automatically parsed into React elements without custom regex or string manipulation:
|
|
133
141
|
|
|
134
142
|
```tsx
|
|
135
|
-
import Link from '@wangs-ui/foundation/theme
|
|
143
|
+
import { Link } from '@wangs-ui/foundation/theme';
|
|
136
144
|
|
|
137
145
|
t('You have selected <b>{count} items</b>. Click <a>here</a> to review.', {
|
|
138
146
|
count: selectedCount,
|