@wangs-ui/skills 1.0.1

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.
@@ -0,0 +1,317 @@
1
+ ---
2
+ name: typescript-strict-typing
3
+ description: Enforce strict TypeScript typing discipline and naming conventions whenever writing, generating, reviewing, or refactoring TypeScript/TSX code. Use this any time code contains `any`, loose/implicit types, untyped catch blocks, unchecked type assertions, boolean-flag state instead of variants, or inconsistent naming — even if the user didn't explicitly ask for a "strict" pass. Governs `any` vs `unknown`, narrowing, discriminated unions, `interface` vs `type` usage, naming conventions, and tsconfig strictness baseline.
4
+ ---
5
+
6
+ # TypeScript Strict Typing Enforcer
7
+
8
+ ## Why this matters
9
+
10
+ TypeScript's type system is only as strong as its weakest escape hatch. A single `any`,
11
+ an un-narrowed `unknown`, or a lazy `as` assertion silently turns off the compiler for
12
+ everything downstream of it — the bug doesn't disappear, it just moves to runtime where
13
+ it's more expensive to find. The goal of this skill is not "add types for the sake of
14
+ it," it's **make illegal states unrepresentable** and **make the compiler prove
15
+ correctness wherever possible**, so bugs surface at build time instead of in production.
16
+
17
+ Apply these rules by default whenever writing or editing TypeScript, without waiting for
18
+ the user to ask for "strict mode" explicitly. If a rule would need to be broken (e.g. a
19
+ third-party type is genuinely untyped), say so explicitly and isolate the escape hatch
20
+ rather than letting it leak.
21
+
22
+ ## Core principle
23
+
24
+ > Narrow, don't cast. Model states, don't flag them. Let the compiler do the checking.
25
+
26
+ ---
27
+
28
+ ## 1. Never use `any`
29
+
30
+ `any` is not "unknown type," it's "type checking off." It's contagious — once a value is
31
+ `any`, everything it touches becomes unchecked too.
32
+
33
+ - Never write `any` for parameters, return types, variables, or generics.
34
+ - Use `unknown` for genuinely unknown external data (API responses, `JSON.parse`, catch
35
+ clauses, third-party callbacks) and narrow it before use.
36
+ - Use generics (`<T>`) when a function needs to work across types but preserve the
37
+ relationship between input and output.
38
+ - If a library ships untyped, write a minimal local type/interface for the surface area
39
+ you actually use instead of reaching for `any`.
40
+
41
+ ```ts
42
+ // ❌ Bad
43
+ function parseConfig(json: any) {
44
+ return json.settings.theme; // no safety, no autocomplete, silent runtime crash
45
+ }
46
+
47
+ // ✅ Good
48
+ function parseConfig(json: unknown): string {
49
+ if (
50
+ typeof json === 'object' &&
51
+ json !== null &&
52
+ 'settings' in json &&
53
+ typeof (json as { settings: unknown }).settings === 'object'
54
+ ) {
55
+ // still narrow further or validate with a schema library (zod, valibot, etc.)
56
+ }
57
+ throw new Error('Invalid config shape');
58
+ }
59
+ ```
60
+
61
+ The only acceptable `any` is a well-justified, isolated, and commented one (e.g.
62
+ interfacing with a genuinely untyped legacy module) — never a default.
63
+
64
+ ## 2. `unknown` + narrowing, not casting
65
+
66
+ Prefer proving a type through control flow over asserting it with `as`.
67
+
68
+ **Narrowing techniques, in order of preference:**
69
+
70
+ 1. **`typeof`** — primitives (`string`, `number`, `boolean`, `undefined`, `function`)
71
+ 2. **`instanceof`** — class instances, `Error`, `Date`, custom classes
72
+ 3. **`in`** — checking a property exists before accessing it on a union/unknown
73
+ 4. **User-defined type guards** — `function isUser(x: unknown): x is User`
74
+ 5. **Discriminated union tag checks** — `switch (value.kind) { ... }` (see §3)
75
+ 6. **Exhaustiveness checks** — a `never`-typed default branch so adding a new variant is
76
+ a compile error until every switch/if-chain handles it
77
+
78
+ ```ts
79
+ // ✅ Type guard
80
+ function isUser(value: unknown): value is User {
81
+ return typeof value === 'object' && value !== null && 'id' in value && 'email' in value;
82
+ }
83
+
84
+ // ✅ Exhaustiveness check
85
+ function assertNever(x: never): never {
86
+ throw new Error(`Unhandled case: ${JSON.stringify(x)}`);
87
+ }
88
+
89
+ function area(shape: Shape): number {
90
+ switch (shape.kind) {
91
+ case 'circle':
92
+ return Math.PI * shape.radius ** 2;
93
+ case 'square':
94
+ return shape.side ** 2;
95
+ default:
96
+ return assertNever(shape); // compile error if a variant is missed
97
+ }
98
+ }
99
+ ```
100
+
101
+ Type assertions (`as X`) and the non-null assertion (`!`) bypass this entirely — treat
102
+ them as a last resort (see §7), not a shortcut.
103
+
104
+ ## 3. Discriminated unions for variant state
105
+
106
+ Whenever a value can be one of several distinct "shapes" (loading/success/error states,
107
+ event types, API response variants), model it as a **discriminated union** with a
108
+ literal tag field — never as a loose object with optional fields or boolean flags.
109
+
110
+ ```ts
111
+ // ❌ Bad — booleans can contradict each other; unclear which fields are valid together
112
+ interface FetchState {
113
+ isLoading: boolean;
114
+ isError: boolean;
115
+ data?: User;
116
+ error?: string;
117
+ }
118
+
119
+ // ✅ Good — only one shape is possible at a time, and the compiler enforces it
120
+ type FetchState =
121
+ | { status: 'idle' }
122
+ | { status: 'loading' }
123
+ | { status: 'success'; data: User }
124
+ | { status: 'error'; error: string };
125
+
126
+ function render(state: FetchState) {
127
+ switch (state.status) {
128
+ case 'success':
129
+ return state.data.name; // `data` is guaranteed to exist here
130
+ case 'error':
131
+ return state.error; // `error` is guaranteed to exist here
132
+ default:
133
+ return null;
134
+ }
135
+ }
136
+ ```
137
+
138
+ Use a consistent tag field name across a codebase (`kind`, `type`, or `status` — pick
139
+ one and stick with it) so narrowing patterns stay predictable.
140
+
141
+ ## 4. `interface` vs `type` — pick by intent, not habit
142
+
143
+ Both can describe object shapes, but they signal different intent. Default rule:
144
+
145
+ | Use `interface` for... | Use `type` for... |
146
+ | -------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
147
+ | Object / entity shapes (a `User`, a `Product`, a component's `Props`) | Unions (`"a" \| "b"`) and discriminated unions |
148
+ | Public API contracts meant to be `implements`-ed by classes | Intersections (`A & B`) |
149
+ | Shapes that consumers may want to **extend/augment** (declaration merging) | Tuples (`[string, number]`) |
150
+ | | Function types / callback signatures |
151
+ | | Mapped, conditional, or utility-derived types (`Partial<T>`, `Pick<T, K>`) |
152
+ | | Aliasing a primitive or another type for readability |
153
+
154
+ ```ts
155
+ // ✅ interface — an entity with identity, extendable
156
+ interface User {
157
+ id: string;
158
+ email: string;
159
+ role: UserRole;
160
+ }
161
+
162
+ interface AdminUser extends User {
163
+ permissions: Permission[];
164
+ }
165
+
166
+ // ✅ type — union, alias, derived shape
167
+ type UserRole = 'admin' | 'editor' | 'viewer';
168
+ type UserId = User['id'];
169
+ type PartialUser = Partial<User>;
170
+ type Callback<T> = (value: T) => void;
171
+ ```
172
+
173
+ Don't mix conventions arbitrarily within one file — if a shape is a plain data object
174
+ that will never need a union/intersection, `interface` is the default; the moment it
175
+ needs to express "one of several shapes," reach for `type`.
176
+
177
+ ## 5. Naming conventions
178
+
179
+ | Kind | Convention | Example |
180
+ | ---------------------------------------------------------- | ------------------------------------------- | ----------------------------------- |
181
+ | Types, interfaces, classes, enums | `PascalCase` | `UserProfile`, `OrderStatus` |
182
+ | Interfaces | `PascalCase`, **no `I` prefix** | `User`, not `IUser` |
183
+ | Type aliases | `PascalCase` | `type ApiResponse<T> = ...` |
184
+ | Variables, functions, methods, properties | `camelCase` | `getUserById`, `isValid` |
185
+ | Booleans | `camelCase` with `is/has/should/can` prefix | `isLoading`, `hasPermission` |
186
+ | True constants (module-level, never reassigned, primitive) | `UPPER_SNAKE_CASE` | `MAX_RETRIES`, `DEFAULT_TIMEOUT_MS` |
187
+ | Enum members | `PascalCase` | `enum Status { Active, Archived }` |
188
+ | Generic type parameters (simple, single-purpose) | Single uppercase letter | `T`, `K`, `V`, `E` for errors |
189
+ | Generic type parameters (multiple / non-obvious) | Descriptive, prefixed with `T` | `TInput`, `TOutput`, `TContext` |
190
+ | Discriminated union tag field | Consistent across the codebase | `kind`, `type`, or `status` |
191
+ | Files with a single exported entity | Match the entity name | `UserProfile.ts`, `useAuth.ts` |
192
+
193
+ Naming should describe **intent**, not implementation — `fetchUser` not
194
+ `getUserFromApiEndpoint`; `retryCount` not `numRetries2`.
195
+
196
+ ## 6. Baseline `tsconfig.json` strictness
197
+
198
+ Treat these as the non-negotiable floor for any project this skill touches:
199
+
200
+ ```json
201
+ {
202
+ "compilerOptions": {
203
+ "strict": true,
204
+ "noImplicitAny": true,
205
+ "strictNullChecks": true,
206
+ "strictFunctionTypes": true,
207
+ "strictPropertyInitialization": true,
208
+ "noUncheckedIndexedAccess": true,
209
+ "exactOptionalPropertyTypes": true,
210
+ "noImplicitOverride": true,
211
+ "noFallthroughCasesInSwitch": true,
212
+ "noUnusedLocals": true,
213
+ "noUnusedParameters": true,
214
+ "forceConsistentCasingInFileNames": true
215
+ }
216
+ }
217
+ ```
218
+
219
+ `strict: true` alone enables the core group (`noImplicitAny`, `strictNullChecks`, etc.),
220
+ but `noUncheckedIndexedAccess` and `exactOptionalPropertyTypes` are commonly missed and
221
+ close real gaps (array/object index access returning `T` instead of `T | undefined`;
222
+ optional properties silently accepting `undefined` as an explicit value).
223
+
224
+ ## 7. Type assertions and non-null assertions are a last resort
225
+
226
+ - `as X` and `x!` tell the compiler "trust me" — they produce zero runtime safety and
227
+ actively hide bugs if wrong.
228
+ - Acceptable only when the compiler genuinely cannot know something you do (e.g. a DOM
229
+ query you've already null-checked, or narrowing a third-party type at a well-tested
230
+ boundary) — and even then, prefer a type guard or a runtime check over a bare
231
+ assertion.
232
+ - Never use `as any` or `as unknown as X` to force an incompatible cast — that's `any`
233
+ wearing a disguise.
234
+ - `x!` should almost always be replaceable by an actual null check or optional chaining
235
+ (`x?.y`) plus a real fallback.
236
+
237
+ ## 8. Readonly by default
238
+
239
+ Prefer immutable shapes unless mutation is intentional and localized.
240
+
241
+ ```ts
242
+ interface Point {
243
+ readonly x: number;
244
+ readonly y: number;
245
+ }
246
+
247
+ function config(values: readonly string[]) {
248
+ /* ... */
249
+ }
250
+
251
+ const ROLES = ['admin', 'editor', 'viewer'] as const;
252
+ type UserRole = (typeof ROLES)[number];
253
+ ```
254
+
255
+ ## 9. Explicit return types on exported/public functions
256
+
257
+ Inference is fine for local, private helpers, but exported functions, class methods, and
258
+ anything forming a public API should declare an explicit return type. This prevents an
259
+ internal implementation change from silently widening/narrowing the public contract.
260
+
261
+ ```ts
262
+ // ❌ Return type is inferred and can silently drift
263
+ export function getActiveUsers(users: User[]) {
264
+ return users.filter((u) => u.active);
265
+ }
266
+
267
+ // ✅ Explicit, intentional contract
268
+ export function getActiveUsers(users: User[]): User[] {
269
+ return users.filter((u) => u.active);
270
+ }
271
+ ```
272
+
273
+ ## 10. Prefer literal unions over numeric enums
274
+
275
+ String literal unions are simpler, tree-shake better, and produce clearer error
276
+ messages than TypeScript `enum`. Reserve `enum` (or `as const` object maps) for cases
277
+ that need reverse lookup or genuinely benefit from a namespaced runtime value.
278
+
279
+ ```ts
280
+ // ✅ Preferred
281
+ type OrderStatus = 'pending' | 'shipped' | 'delivered' | 'cancelled';
282
+
283
+ // Acceptable when a namespaced runtime object is actually needed
284
+ const OrderStatus = {
285
+ Pending: 'pending',
286
+ Shipped: 'shipped',
287
+ } as const;
288
+ type OrderStatus = (typeof OrderStatus)[keyof typeof OrderStatus];
289
+ ```
290
+
291
+ ---
292
+
293
+ ## Review checklist
294
+
295
+ Before considering TypeScript code "done," verify:
296
+
297
+ - [ ] No `any` anywhere (including implicit `any` from missing annotations)
298
+ - [ ] External/uncertain data enters as `unknown` and is narrowed before use
299
+ - [ ] Variant state is a discriminated union, not optional fields + booleans
300
+ - [ ] `interface` used for object/entity shapes; `type` used for unions/aliases/intersections
301
+ - [ ] No stray `I` prefixes on interfaces
302
+ - [ ] Naming follows the casing table in §5 consistently
303
+ - [ ] `as` / `!` are rare, justified, and can't be replaced by a guard or null check
304
+ - [ ] Exported functions/methods have explicit return types
305
+ - [ ] Switch statements over unions have an exhaustiveness (`never`) check
306
+ - [ ] `tsconfig.json` includes the strictness baseline in §6
307
+
308
+ ## Quick reference
309
+
310
+ | Situation | Use |
311
+ | ---------------------------------------------- | ------------------------------------------------------------- |
312
+ | External/uncertain data | `unknown` + narrowing |
313
+ | "This value is definitely one of these shapes" | Discriminated union (`type`) |
314
+ | Object with identity, may be extended | `interface` |
315
+ | Union, intersection, tuple, mapped type | `type` |
316
+ | Need to prove a type through logic | Type guard / narrowing |
317
+ | Tempted to write `any` | Stop — use `unknown`, a generic, or a local interface instead |
@@ -0,0 +1,108 @@
1
+ ---
2
+ name: wangs-ui-components
3
+ description: Foundational rules, subpath imports, design tokens, and the MCP Discovery Protocol for building React apps with Wangs UI.
4
+ ---
5
+
6
+ # Skill: Wangs UI Component Fundamentals & MCP Protocol
7
+
8
+ Use this skill whenever you write or modify UI components using Wangs UI (`@wangs-ui/react-core`, `@wangs-ui/react-icons`, `@wangs-ui/react-presets`, `@wangs-ui/foundation`).
9
+
10
+ ---
11
+
12
+ ## 1. The MCP Discovery Protocol (Mandatory Single Source of Truth)
13
+
14
+ Do **NOT** guess component props, Pass-Through (`pt`) slots, or event names. Always query the MCP server dynamically to retrieve the current API signatures and live story implementations:
15
+
16
+ ```mermaid
17
+ graph TD
18
+ A[Identify Component Needed] --> B[Call get-documentation id]
19
+ B --> C{Need live story / variant code?}
20
+ C -->|Yes| D[Call get-documentation-for-story]
21
+ C -->|No| E[Check Graphify: query_graph]
22
+ D --> E
23
+ E --> F[Implement Component with Subpath Imports]
24
+ ```
25
+
26
+ ### Discovery Steps:
27
+
28
+ 1. **Inspect Component Contract & Props**:
29
+ ```json
30
+ get-documentation({ "id": "button" })
31
+ get-documentation({ "id": "input" })
32
+ get-documentation({ "id": "datatable" })
33
+ ```
34
+ 2. **Inspect Live Usage & Story Variants**:
35
+ ```json
36
+ get-documentation-for-story({ "id": "button", "storyName": "Default" })
37
+ get-documentation-for-story({ "id": "datatable", "storyName": "ServerPagination" })
38
+ ```
39
+ 3. **Inspect Relationships & Real Usages in Graph**:
40
+ ```json
41
+ query_graph({ "query": "DataTable" })
42
+ query_graph({ "query": "usePT" })
43
+ ```
44
+
45
+ ---
46
+
47
+ ## 2. Subpath Modular Imports (Mandatory)
48
+
49
+ Always import via specific subpaths to guarantee tree-shaking and avoid bundling entire packages:
50
+
51
+ ```tsx
52
+ // Primitives (@wangs-ui/react-core/primitive/*)
53
+ import Button from '@wangs-ui/react-core/primitive/button';
54
+ import Input from '@wangs-ui/react-core/primitive/input';
55
+ import NumberInput from '@wangs-ui/react-core/primitive/numberinput';
56
+ import Select from '@wangs-ui/react-core/primitive/select';
57
+ import Badge from '@wangs-ui/react-core/primitive/badge';
58
+ import Card from '@wangs-ui/react-core/primitive/card';
59
+ import DataTable from '@wangs-ui/react-core/primitive/datatable';
60
+
61
+ // Blocks (@wangs-ui/react-core/blocks/*)
62
+ import AppLayout from '@wangs-ui/react-core/blocks/applayout';
63
+ import Sidebar from '@wangs-ui/react-core/blocks/sidebar';
64
+
65
+ // Providers & System Hooks
66
+ import { WangsUiProvider } from '@wangs-ui/react-core/api';
67
+ import { useI18n } from '@wangs-ui/react-i18n';
68
+ import { useTheme } from '@wangs-ui/foundation/theme';
69
+
70
+ // Icons (@wangs-ui/react-icons)
71
+ import { SearchLine, AddLine, DeleteBin6Line, CheckLine } from '@wangs-ui/react-icons';
72
+ ```
73
+
74
+ ---
75
+
76
+ ## 3. Strict Primitive Substitution Rule
77
+
78
+ Never write raw HTML elements when a Wangs UI primitive exists:
79
+
80
+ | Forbidden Raw HTML | Mandatory Wangs UI Component | Subpath Import | MCP Documentation ID |
81
+ | :------------------------ | :--------------------------- | :------------------------------------------- | :------------------- |
82
+ | `<button>` | `Button` | `@wangs-ui/react-core/primitive/button` | `button` |
83
+ | `<input type="text">` | `Input` | `@wangs-ui/react-core/primitive/input` | `input` |
84
+ | `<input type="number">` | `NumberInput` | `@wangs-ui/react-core/primitive/numberinput` | `numberinput` |
85
+ | `<input type="checkbox">` | `Checkbox` | `@wangs-ui/react-core/primitive/checkbox` | `checkbox` |
86
+ | `<select>` | `Select` | `@wangs-ui/react-core/primitive/select` | `select` |
87
+ | `<dialog>` / modal | `Dialog` / `Modal` | `@wangs-ui/react-core/primitive/dialog` | `dialog`, `modal` |
88
+ | `<table>` | `DataTable` | `@wangs-ui/react-core/primitive/datatable` | `datatable` |
89
+ | Container box | `Card` | `@wangs-ui/react-core/primitive/card` | `card` |
90
+ | Pill badge / status | `Badge` | `@wangs-ui/react-core/primitive/badge` | `badge` |
91
+
92
+ ---
93
+
94
+ ## 4. Typography Scale & 4px Spacing Tokens
95
+
96
+ ### Typography Helper Classes
97
+
98
+ - `.heading-1` — Page title (22px, 600)
99
+ - `.heading-2` — Section / Card title (18px, 600)
100
+ - `.heading-3` — Sub-header (16px, 500)
101
+ - `.heading-4` — Field label (14px, 500)
102
+ - `.heading-5` — Small group header (12px, 600)
103
+ - `.p` — Body copy (12px, 500)
104
+
105
+ ### 4px Spacing Tokens
106
+
107
+ - Gap: `gap-xs` (4px), `gap-s` (6px), `gap-md` (8px), `gap-m` (12px), `gap-l` (16px), `gap-xl` (20px), `gap-xxl` (24px)
108
+ - Padding: `p-xs`, `p-s`, `p-md`, `p-m`, `p-l`, `p-xl`, `p-xxl`