@mind-fold/open-flow 0.1.17 → 0.2.2
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/dist/configurators/templates.d.ts.map +1 -1
- package/dist/configurators/templates.js +17 -6
- package/dist/configurators/templates.js.map +1 -1
- package/dist/configurators/workflow.d.ts.map +1 -1
- package/dist/configurators/workflow.js +82 -6
- package/dist/configurators/workflow.js.map +1 -1
- package/dist/templates/commands/break-loop.txt +107 -0
- package/dist/templates/commands/check-cross-layer.txt +153 -0
- package/dist/templates/commands/finish-work.txt +129 -0
- package/dist/templates/commands/index.d.ts +9 -5
- package/dist/templates/commands/index.d.ts.map +1 -1
- package/dist/templates/commands/index.js +16 -5
- package/dist/templates/commands/index.js.map +1 -1
- package/dist/templates/commands/init-agent.txt +100 -9
- package/dist/templates/commands/sync-from-runtime.txt +140 -0
- package/dist/templates/markdown/flow.md.txt +96 -84
- package/dist/templates/markdown/index.d.ts +21 -4
- package/dist/templates/markdown/index.d.ts.map +1 -1
- package/dist/templates/markdown/index.js +27 -4
- package/dist/templates/markdown/index.js.map +1 -1
- package/dist/templates/markdown/structure/backend/database-guidelines.md.txt +247 -0
- package/dist/templates/markdown/structure/backend/directory-structure.md.txt +153 -0
- package/dist/templates/markdown/structure/backend/error-handling.md.txt +257 -0
- package/dist/templates/markdown/structure/backend/index.md.txt +88 -0
- package/dist/templates/markdown/structure/backend/logging-guidelines.md.txt +212 -0
- package/dist/templates/markdown/structure/backend/quality-guidelines.md.txt +219 -0
- package/dist/templates/markdown/structure/backend/type-safety.md.txt +192 -0
- package/dist/templates/markdown/structure/flows/code-reuse-thinking-guide.md.txt +343 -0
- package/dist/templates/markdown/structure/flows/cross-layer-thinking-guide.md.txt +283 -0
- package/dist/templates/markdown/structure/flows/index.md.txt +133 -0
- package/dist/templates/markdown/structure/flows/pre-implementation-checklist.md.txt +182 -0
- package/dist/templates/markdown/structure/flows/spec-flow-template.md.txt +145 -0
- package/dist/templates/markdown/structure/frontend/component-guidelines.md.txt +335 -0
- package/dist/templates/markdown/structure/frontend/directory-structure.md.txt +172 -0
- package/dist/templates/markdown/structure/frontend/hook-guidelines.md.txt +287 -0
- package/dist/templates/markdown/structure/frontend/index.md.txt +91 -0
- package/dist/templates/markdown/structure/frontend/quality-guidelines.md.txt +274 -0
- package/dist/templates/markdown/structure/frontend/state-management.md.txt +293 -0
- package/dist/templates/markdown/structure/frontend/type-safety.md.txt +275 -0
- package/package.json +2 -2
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
# State Management
|
|
2
|
+
|
|
3
|
+
> Local state, server state, and global state patterns
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## State Types
|
|
8
|
+
|
|
9
|
+
| Type | When to Use | Tool |
|
|
10
|
+
|------|-------------|------|
|
|
11
|
+
| **Local State** | UI state, form inputs | `useState` |
|
|
12
|
+
| **Server State** | Data from API | React Query |
|
|
13
|
+
| **Global State** | App-wide state, auth | Context / Zustand |
|
|
14
|
+
| **URL State** | Filters, pagination | `useSearchParams` |
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Local State
|
|
19
|
+
|
|
20
|
+
For component-specific UI state.
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
function Dialog() {
|
|
24
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<>
|
|
28
|
+
<button onClick={() => setIsOpen(true)}>Open</button>
|
|
29
|
+
{isOpen && <Modal onClose={() => setIsOpen(false)} />}
|
|
30
|
+
</>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Complex Local State
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
// For related state, use useReducer
|
|
39
|
+
type State = {
|
|
40
|
+
status: 'idle' | 'loading' | 'success' | 'error';
|
|
41
|
+
data: User | null;
|
|
42
|
+
error: string | null;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
type Action =
|
|
46
|
+
| { type: 'loading' }
|
|
47
|
+
| { type: 'success'; data: User }
|
|
48
|
+
| { type: 'error'; error: string };
|
|
49
|
+
|
|
50
|
+
function reducer(state: State, action: Action): State {
|
|
51
|
+
switch (action.type) {
|
|
52
|
+
case 'loading':
|
|
53
|
+
return { ...state, status: 'loading', error: null };
|
|
54
|
+
case 'success':
|
|
55
|
+
return { status: 'success', data: action.data, error: null };
|
|
56
|
+
case 'error':
|
|
57
|
+
return { status: 'error', data: null, error: action.error };
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Server State (React Query)
|
|
65
|
+
|
|
66
|
+
For data fetched from APIs.
|
|
67
|
+
|
|
68
|
+
### Fetching Data
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
function UserList() {
|
|
72
|
+
const { data, isLoading, error } = useQuery({
|
|
73
|
+
queryKey: ['users'],
|
|
74
|
+
queryFn: () => api.users.list(),
|
|
75
|
+
staleTime: 5 * 60 * 1000, // Consider fresh for 5 minutes
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
if (isLoading) return <Skeleton />;
|
|
79
|
+
if (error) return <Error message={error.message} />;
|
|
80
|
+
|
|
81
|
+
return <List items={data} />;
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Mutating Data
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
function CreateUserForm() {
|
|
89
|
+
const queryClient = useQueryClient();
|
|
90
|
+
|
|
91
|
+
const createUser = useMutation({
|
|
92
|
+
mutationFn: api.users.create,
|
|
93
|
+
onSuccess: () => {
|
|
94
|
+
queryClient.invalidateQueries({ queryKey: ['users'] });
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
const handleSubmit = (data: CreateUserInput) => {
|
|
99
|
+
createUser.mutate(data);
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
return <form onSubmit={handleSubmit}>...</form>;
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Global State (Context)
|
|
109
|
+
|
|
110
|
+
For app-wide state like auth, theme, or locale.
|
|
111
|
+
|
|
112
|
+
### Auth Context Example
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
// context/auth-context.tsx
|
|
116
|
+
interface AuthState {
|
|
117
|
+
user: User | null;
|
|
118
|
+
isLoading: boolean;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
interface AuthContextType extends AuthState {
|
|
122
|
+
login: (credentials: Credentials) => Promise<void>;
|
|
123
|
+
logout: () => Promise<void>;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const AuthContext = createContext<AuthContextType | null>(null);
|
|
127
|
+
|
|
128
|
+
export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|
129
|
+
const [state, setState] = useState<AuthState>({
|
|
130
|
+
user: null,
|
|
131
|
+
isLoading: true,
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
useEffect(() => {
|
|
135
|
+
// Check session on mount
|
|
136
|
+
checkSession().then(user => {
|
|
137
|
+
setState({ user, isLoading: false });
|
|
138
|
+
});
|
|
139
|
+
}, []);
|
|
140
|
+
|
|
141
|
+
const login = async (credentials: Credentials) => {
|
|
142
|
+
const user = await api.auth.login(credentials);
|
|
143
|
+
setState({ user, isLoading: false });
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const logout = async () => {
|
|
147
|
+
await api.auth.logout();
|
|
148
|
+
setState({ user: null, isLoading: false });
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
return (
|
|
152
|
+
<AuthContext.Provider value={{ ...state, login, logout }}>
|
|
153
|
+
{children}
|
|
154
|
+
</AuthContext.Provider>
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function useAuth() {
|
|
159
|
+
const context = useContext(AuthContext);
|
|
160
|
+
if (!context) {
|
|
161
|
+
throw new Error('useAuth must be used within AuthProvider');
|
|
162
|
+
}
|
|
163
|
+
return context;
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Usage
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
function LoginButton() {
|
|
171
|
+
const { user, login, logout } = useAuth();
|
|
172
|
+
|
|
173
|
+
if (user) {
|
|
174
|
+
return <button onClick={logout}>Logout</button>;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return <button onClick={() => login(credentials)}>Login</button>;
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## URL State
|
|
184
|
+
|
|
185
|
+
For state that should persist in URL (filters, pagination, search).
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
import { useSearchParams } from 'next/navigation';
|
|
189
|
+
|
|
190
|
+
function ProductList() {
|
|
191
|
+
const searchParams = useSearchParams();
|
|
192
|
+
const router = useRouter();
|
|
193
|
+
|
|
194
|
+
// Read from URL
|
|
195
|
+
const page = Number(searchParams.get('page')) || 1;
|
|
196
|
+
const category = searchParams.get('category') ?? 'all';
|
|
197
|
+
|
|
198
|
+
// Update URL
|
|
199
|
+
const setPage = (newPage: number) => {
|
|
200
|
+
const params = new URLSearchParams(searchParams);
|
|
201
|
+
params.set('page', String(newPage));
|
|
202
|
+
router.push(`?${params.toString()}`);
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
const { data } = useProducts({ page, category });
|
|
206
|
+
|
|
207
|
+
return (
|
|
208
|
+
<>
|
|
209
|
+
<CategoryFilter value={category} />
|
|
210
|
+
<ProductGrid items={data?.items} />
|
|
211
|
+
<Pagination page={page} onPageChange={setPage} />
|
|
212
|
+
</>
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
## State Location Decision Tree
|
|
220
|
+
|
|
221
|
+
```
|
|
222
|
+
Is the state needed by multiple components?
|
|
223
|
+
├─ No → Local state (useState)
|
|
224
|
+
└─ Yes → Is it from the server?
|
|
225
|
+
├─ Yes → React Query
|
|
226
|
+
└─ No → Should it persist in URL?
|
|
227
|
+
├─ Yes → URL state (searchParams)
|
|
228
|
+
└─ No → Context or Zustand
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## Anti-Patterns
|
|
234
|
+
|
|
235
|
+
### Don't Sync Server State to Local State
|
|
236
|
+
|
|
237
|
+
```typescript
|
|
238
|
+
// ❌ BAD: Duplicating server state
|
|
239
|
+
const { data: users } = useUsers();
|
|
240
|
+
const [localUsers, setLocalUsers] = useState<User[]>([]);
|
|
241
|
+
|
|
242
|
+
useEffect(() => {
|
|
243
|
+
if (users) setLocalUsers(users);
|
|
244
|
+
}, [users]);
|
|
245
|
+
|
|
246
|
+
// ✅ GOOD: Use server state directly
|
|
247
|
+
const { data: users } = useUsers();
|
|
248
|
+
// Use users directly, mutate with mutations
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### Don't Overuse Global State
|
|
252
|
+
|
|
253
|
+
```typescript
|
|
254
|
+
// ❌ BAD: Everything in global state
|
|
255
|
+
const { isModalOpen, setModalOpen } = useGlobalStore();
|
|
256
|
+
|
|
257
|
+
// ✅ GOOD: Local state for local UI
|
|
258
|
+
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### Don't Put Derived State in State
|
|
262
|
+
|
|
263
|
+
```typescript
|
|
264
|
+
// ❌ BAD: Storing derived value
|
|
265
|
+
const [items, setItems] = useState([]);
|
|
266
|
+
const [filteredItems, setFilteredItems] = useState([]);
|
|
267
|
+
|
|
268
|
+
useEffect(() => {
|
|
269
|
+
setFilteredItems(items.filter(i => i.active));
|
|
270
|
+
}, [items]);
|
|
271
|
+
|
|
272
|
+
// ✅ GOOD: Compute derived value
|
|
273
|
+
const [items, setItems] = useState([]);
|
|
274
|
+
const filteredItems = items.filter(i => i.active);
|
|
275
|
+
// Or useMemo for expensive computations
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
---
|
|
279
|
+
|
|
280
|
+
## Summary
|
|
281
|
+
|
|
282
|
+
| State Type | Tool | When to Use |
|
|
283
|
+
|------------|------|-------------|
|
|
284
|
+
| UI toggles | `useState` | Modal, dropdown, tabs |
|
|
285
|
+
| Form input | `useState` or form library | Input values |
|
|
286
|
+
| API data | React Query | Fetched data |
|
|
287
|
+
| Auth/User | Context | App-wide user state |
|
|
288
|
+
| Theme | Context | App-wide theme |
|
|
289
|
+
| Filters | URL params | Shareable filters |
|
|
290
|
+
|
|
291
|
+
---
|
|
292
|
+
|
|
293
|
+
**Language**: All documentation must be written in **English**.
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
# Type Safety
|
|
2
|
+
|
|
3
|
+
> TypeScript patterns and type inference for frontend
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Core Principles
|
|
8
|
+
|
|
9
|
+
1. **Import from API** - Don't redefine backend types
|
|
10
|
+
2. **Infer when possible** - Let TypeScript do the work
|
|
11
|
+
3. **No `any`** - Use `unknown` with type guards
|
|
12
|
+
4. **No assertions** - Avoid `as` and `!`
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Import Types from Backend
|
|
17
|
+
|
|
18
|
+
Never redefine types that exist in your API layer.
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
// ✅ GOOD: Import from API package
|
|
22
|
+
import type { User, CreateUserInput } from '@/api/types';
|
|
23
|
+
|
|
24
|
+
function UserForm({ onSubmit }: { onSubmit: (data: CreateUserInput) => void }) {
|
|
25
|
+
// ...
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// ❌ BAD: Redefining types
|
|
29
|
+
interface User {
|
|
30
|
+
id: string;
|
|
31
|
+
name: string;
|
|
32
|
+
email: string;
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Why?
|
|
37
|
+
|
|
38
|
+
- Single source of truth
|
|
39
|
+
- Types stay in sync with API
|
|
40
|
+
- Less code to maintain
|
|
41
|
+
- Compile-time errors if API changes
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Type Inference
|
|
46
|
+
|
|
47
|
+
Let TypeScript infer types when it can.
|
|
48
|
+
|
|
49
|
+
### State
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
// ✅ GOOD: Inferred type
|
|
53
|
+
const [count, setCount] = useState(0); // number
|
|
54
|
+
const [name, setName] = useState(''); // string
|
|
55
|
+
const [user, setUser] = useState<User | null>(null); // explicit when needed
|
|
56
|
+
|
|
57
|
+
// ❌ BAD: Redundant type annotation
|
|
58
|
+
const [count, setCount] = useState<number>(0);
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Functions
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
// ✅ GOOD: Inferred return type
|
|
65
|
+
function formatDate(date: Date) {
|
|
66
|
+
return date.toISOString(); // returns string
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ✅ GOOD: Explicit when complex
|
|
70
|
+
function getUser(id: string): Promise<User | null> {
|
|
71
|
+
return api.users.get(id);
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Props
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
// ✅ GOOD: Props interface
|
|
79
|
+
interface ButtonProps {
|
|
80
|
+
children: React.ReactNode;
|
|
81
|
+
onClick: () => void;
|
|
82
|
+
disabled?: boolean;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function Button({ children, onClick, disabled }: ButtonProps) {
|
|
86
|
+
return <button onClick={onClick} disabled={disabled}>{children}</button>;
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Handling Nullable Values
|
|
93
|
+
|
|
94
|
+
### Null Checks
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
// ✅ GOOD: Explicit null check
|
|
98
|
+
function UserProfile({ userId }: { userId: string }) {
|
|
99
|
+
const { data: user } = useUser(userId);
|
|
100
|
+
|
|
101
|
+
if (!user) {
|
|
102
|
+
return <Skeleton />;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// TypeScript knows user is not null
|
|
106
|
+
return <div>{user.name}</div>;
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Optional Chaining
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
// ✅ GOOD: Optional chaining for nested access
|
|
114
|
+
const userName = user?.profile?.name ?? 'Anonymous';
|
|
115
|
+
|
|
116
|
+
// ❌ BAD: Non-null assertion
|
|
117
|
+
const userName = user!.profile!.name;
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Default Values
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
// ✅ GOOD: Default values with nullish coalescing
|
|
124
|
+
const displayName = user.nickname ?? user.name ?? 'Unknown';
|
|
125
|
+
const items = response.data ?? [];
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## Type Guards
|
|
131
|
+
|
|
132
|
+
Use type guards for runtime type checking.
|
|
133
|
+
|
|
134
|
+
### Type Predicates
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
function isUser(value: unknown): value is User {
|
|
138
|
+
return (
|
|
139
|
+
typeof value === 'object' &&
|
|
140
|
+
value !== null &&
|
|
141
|
+
'id' in value &&
|
|
142
|
+
'email' in value
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Usage
|
|
147
|
+
function handleData(data: unknown) {
|
|
148
|
+
if (isUser(data)) {
|
|
149
|
+
// TypeScript knows data is User
|
|
150
|
+
console.log(data.email);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Discriminated Unions
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
type Result<T> =
|
|
159
|
+
| { success: true; data: T }
|
|
160
|
+
| { success: false; error: string };
|
|
161
|
+
|
|
162
|
+
function handleResult(result: Result<User>) {
|
|
163
|
+
if (result.success) {
|
|
164
|
+
// TypeScript knows result.data exists
|
|
165
|
+
console.log(result.data.name);
|
|
166
|
+
} else {
|
|
167
|
+
// TypeScript knows result.error exists
|
|
168
|
+
console.error(result.error);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## Component Props
|
|
176
|
+
|
|
177
|
+
### Extending HTML Elements
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
import { type ComponentProps } from 'react';
|
|
181
|
+
|
|
182
|
+
// Extend button props
|
|
183
|
+
interface ButtonProps extends ComponentProps<'button'> {
|
|
184
|
+
variant?: 'primary' | 'secondary';
|
|
185
|
+
isLoading?: boolean;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function Button({ variant = 'primary', isLoading, children, ...props }: ButtonProps) {
|
|
189
|
+
return (
|
|
190
|
+
<button className={variant} disabled={isLoading} {...props}>
|
|
191
|
+
{isLoading ? 'Loading...' : children}
|
|
192
|
+
</button>
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Polymorphic Components
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
type AsProp<C extends React.ElementType> = {
|
|
201
|
+
as?: C;
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
type PropsToOmit<C extends React.ElementType, P> = keyof (AsProp<C> & P);
|
|
205
|
+
|
|
206
|
+
type PolymorphicProps<C extends React.ElementType, Props = {}> =
|
|
207
|
+
React.PropsWithChildren<Props & AsProp<C>> &
|
|
208
|
+
Omit<React.ComponentPropsWithoutRef<C>, PropsToOmit<C, Props>>;
|
|
209
|
+
|
|
210
|
+
// Usage
|
|
211
|
+
function Text<C extends React.ElementType = 'span'>({
|
|
212
|
+
as,
|
|
213
|
+
children,
|
|
214
|
+
...props
|
|
215
|
+
}: PolymorphicProps<C>) {
|
|
216
|
+
const Component = as || 'span';
|
|
217
|
+
return <Component {...props}>{children}</Component>;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Can render as different elements
|
|
221
|
+
<Text>Span text</Text>
|
|
222
|
+
<Text as="p">Paragraph text</Text>
|
|
223
|
+
<Text as="h1">Heading text</Text>
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## Forbidden Patterns
|
|
229
|
+
|
|
230
|
+
| Pattern | Problem | Solution |
|
|
231
|
+
|---------|---------|----------|
|
|
232
|
+
| `any` | Disables type checking | Use `unknown` + guards |
|
|
233
|
+
| `as User` | Unsafe cast | Use type guards |
|
|
234
|
+
| `user!.name` | Runtime crash | Null check first |
|
|
235
|
+
| Redefine API types | Type drift | Import from API |
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
## Best Practices
|
|
240
|
+
|
|
241
|
+
### DO
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
// Import types from API
|
|
245
|
+
import type { User } from '@/api/types';
|
|
246
|
+
|
|
247
|
+
// Use type guards
|
|
248
|
+
if (isUser(data)) { ... }
|
|
249
|
+
|
|
250
|
+
// Handle nullable explicitly
|
|
251
|
+
if (!user) return null;
|
|
252
|
+
|
|
253
|
+
// Infer when possible
|
|
254
|
+
const [value, setValue] = useState(0);
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### DON'T
|
|
258
|
+
|
|
259
|
+
```typescript
|
|
260
|
+
// Use any
|
|
261
|
+
const data: any = await fetch(...);
|
|
262
|
+
|
|
263
|
+
// Cast without validation
|
|
264
|
+
const user = data as User;
|
|
265
|
+
|
|
266
|
+
// Use non-null assertion
|
|
267
|
+
user!.name;
|
|
268
|
+
|
|
269
|
+
// Redefine API types locally
|
|
270
|
+
interface User { ... }
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
---
|
|
274
|
+
|
|
275
|
+
**Language**: All documentation must be written in **English**.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mind-fold/open-flow",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "AI-assisted development workflow initializer for Cursor, Claude Code and more",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
},
|
|
15
15
|
"scripts": {
|
|
16
16
|
"build": "tsc && npm run copy-templates",
|
|
17
|
-
"copy-templates": "cp -r src/templates/commands/*.txt dist/templates/commands/ && cp -r src/templates/scripts/*.txt dist/templates/scripts/ && cp -r src/templates/markdown/*.txt dist/templates/markdown/",
|
|
17
|
+
"copy-templates": "cp -r src/templates/commands/*.txt dist/templates/commands/ && cp -r src/templates/scripts/*.txt dist/templates/scripts/ && cp -r src/templates/markdown/*.txt dist/templates/markdown/ && cp -r src/templates/markdown/structure dist/templates/markdown/",
|
|
18
18
|
"dev": "tsc --watch",
|
|
19
19
|
"start": "node ./dist/cli/index.js",
|
|
20
20
|
"prepublishOnly": "npm run build"
|