@minhduydev/mdpi 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/dist/index.js +1 -1
  2. package/dist/template/.pi/VERSION +1 -1
  3. package/dist/template/.pi/extensions/templates-injector.ts +34 -6
  4. package/dist/template/.pi/prompts/INDEX.md +3 -9
  5. package/dist/template/.pi/skills/INDEX.md +81 -19
  6. package/dist/template/.pi/skills/accessibility-audit/SKILL.md +8 -2
  7. package/dist/template/.pi/skills/baseline-ui/SKILL.md +211 -0
  8. package/dist/template/.pi/skills/dcp-hygiene/SKILL.md +1 -1
  9. package/dist/template/.pi/skills/design-taste-frontend/SKILL.md +53 -42
  10. package/dist/template/.pi/skills/fixing-accessibility/SKILL.md +509 -0
  11. package/dist/template/.pi/skills/frontend-design/SKILL.md +60 -47
  12. package/dist/template/.pi/skills/frontend-design/references/animation/motion-advanced.md +88 -15
  13. package/dist/template/.pi/skills/frontend-design/references/animation/motion-core.md +148 -13
  14. package/dist/template/.pi/skills/frontend-design/references/shadcn/setup.md +127 -20
  15. package/dist/template/.pi/skills/frontend-ui-engineering/SKILL.md +21 -27
  16. package/dist/template/.pi/skills/nextjs-app-router/SKILL.md +334 -0
  17. package/dist/template/.pi/skills/nextjs-cache/SKILL.md +262 -0
  18. package/dist/template/.pi/skills/oklch-color-workflow/SKILL.md +426 -0
  19. package/dist/template/.pi/skills/production-hardening/SKILL.md +652 -0
  20. package/dist/template/.pi/skills/react-best-practices/SKILL.md +79 -1
  21. package/dist/template/.pi/skills/react-compiler/SKILL.md +237 -0
  22. package/dist/template/.pi/skills/react-hook-form/SKILL.md +374 -0
  23. package/dist/template/.pi/skills/react-server-actions/SKILL.md +299 -0
  24. package/dist/template/.pi/skills/shadcn-ui/SKILL.md +404 -0
  25. package/dist/template/.pi/skills/tanstack-query/SKILL.md +330 -0
  26. package/dist/template/.pi/skills/ui-craft-principles/SKILL.md +564 -0
  27. package/dist/template/.pi/skills/ui-quality-audit/SKILL.md +329 -0
  28. package/dist/template/.pi/skills/v0/SKILL.md +264 -0
  29. package/dist/template/.pi/skills/zustand/SKILL.md +333 -0
  30. package/dist/template/.pi/templates/DESIGN.md +76 -0
  31. package/dist/template/.pi/workflows/INDEX.md +2 -1
  32. package/dist/template/.pi/workflows/frontend-feature-workflow.md +343 -0
  33. package/dist/template/.pi/workflows/quality-loop.md +1 -1
  34. package/package.json +1 -1
  35. package/dist/template/.pi/prompts/loop-check.md +0 -87
  36. package/dist/template/.pi/prompts/loop-init.md +0 -157
  37. package/dist/template/.pi/prompts/loop-review.md +0 -90
  38. package/dist/template/.pi/skills/loop-audit/SKILL.md +0 -141
  39. package/dist/template/.pi/skills/loop-cost/SKILL.md +0 -130
  40. package/dist/template/.pi/skills/loop-engineering/SKILL.md +0 -175
  41. package/dist/template/.pi/templates/loop-github-action.yml +0 -162
  42. package/dist/template/.pi/templates/loop-orchestrator.sh +0 -514
  43. package/dist/template/.pi/templates/loop-orchestrator.test.ts +0 -332
  44. package/dist/template/.pi/templates/loop-orchestrator.ts +0 -936
  45. package/dist/template/.pi/templates/loop-state.json +0 -24
  46. package/dist/template/.pi/templates/loop-state.md +0 -98
  47. package/dist/template/.pi/templates/loop-vision.md +0 -110
  48. /package/dist/template/.pi/templates/{design.md → feature-design.md} +0 -0
@@ -0,0 +1,330 @@
1
+ ---
2
+ name: tanstack-query
3
+ description: Use when implementing data fetching, caching, or mutations with TanStack Query v5. Covers useQuery, useMutation, optimistic updates, infinite queries, prefetching, SSR patterns, query keys. MUST load before any data fetching implementation.
4
+ ---
5
+
6
+ # TanStack Query v5
7
+
8
+ ## When to Use
9
+
10
+ - Fetching data from APIs in React applications
11
+ - Managing server state with automatic caching and background refetching
12
+ - Implementing optimistic updates for mutations
13
+ - Handling pagination and infinite scrolling
14
+ - Prefetching data for faster navigation
15
+ - Server-side rendering with client hydration
16
+
17
+ ## When NOT to Use
18
+
19
+ - Server Components with direct DB access (use `use cache` instead)
20
+ - WebSocket-only real-time data (use SWR subscription or custom hook)
21
+ - Form state management (use React Hook Form)
22
+ - Global client-only state (use Zustand)
23
+
24
+ ## Setup
25
+
26
+ ```tsx
27
+ // app/providers.tsx
28
+ 'use client'
29
+
30
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
31
+ import { useState } from 'react'
32
+
33
+ export function Providers({ children }: { children: React.ReactNode }) {
34
+ const [queryClient] = useState(
35
+ () => new QueryClient({
36
+ defaultOptions: {
37
+ queries: {
38
+ staleTime: 60 * 1000, // 1 min before considered stale
39
+ gcTime: 5 * 60 * 1000, // 5 min garbage collection
40
+ retry: 1, // One retry on failure
41
+ refetchOnWindowFocus: false,
42
+ },
43
+ },
44
+ })
45
+ )
46
+
47
+ return (
48
+ <QueryClientProvider client={queryClient}>
49
+ {children}
50
+ </QueryClientProvider>
51
+ )
52
+ }
53
+ ```
54
+
55
+ ```tsx
56
+ // app/layout.tsx
57
+ import { Providers } from './providers'
58
+
59
+ export default function RootLayout({ children }) {
60
+ return (
61
+ <html>
62
+ <body>
63
+ <Providers>{children}</Providers>
64
+ </body>
65
+ </html>
66
+ )
67
+ }
68
+ ```
69
+
70
+ ## useQuery — Basic Data Fetching
71
+
72
+ ```tsx
73
+ 'use client'
74
+
75
+ import { useQuery } from '@tanstack/react-query'
76
+
77
+ function PostsList() {
78
+ const { data, isLoading, error } = useQuery({
79
+ queryKey: ['posts'],
80
+ queryFn: () => fetch('/api/posts').then(res => res.json()),
81
+ })
82
+
83
+ if (isLoading) return <PostsSkeleton />
84
+ if (error) return <ErrorDisplay error={error} />
85
+
86
+ return data.map(post => <PostCard key={post.id} post={post} />)
87
+ }
88
+ ```
89
+
90
+ ## Query Key Design
91
+
92
+ ```tsx
93
+ // Flat keys — simple
94
+ useQuery({ queryKey: ['posts'], queryFn: fetchPosts })
95
+
96
+ // Hierarchical keys — filterable
97
+ useQuery({ queryKey: ['posts', { status: 'published' }], queryFn: () => fetchPosts('published') })
98
+
99
+ // Detail queries — id-based
100
+ useQuery({ queryKey: ['posts', postId], queryFn: () => fetchPost(postId) })
101
+
102
+ // Factory pattern — recommended for large apps
103
+ const postKeys = {
104
+ all: ['posts'] as const,
105
+ lists: () => [...postKeys.all, 'list'] as const,
106
+ list: (filters: Filters) => [...postKeys.lists(), filters] as const,
107
+ details: () => [...postKeys.all, 'detail'] as const,
108
+ detail: (id: string) => [...postKeys.details(), id] as const,
109
+ }
110
+
111
+ // Usage:
112
+ useQuery({ queryKey: postKeys.lists({ status: 'published' }), ... })
113
+ useQuery({ queryKey: postKeys.detail(id), ... })
114
+
115
+ // Invalidation
116
+ queryClient.invalidateQueries({ queryKey: postKeys.lists() }) // All lists
117
+ queryClient.invalidateQueries({ queryKey: postKeys.detail(id) }) // Specific post
118
+ ```
119
+
120
+ ## useMutation
121
+
122
+ ```tsx
123
+ import { useMutation, useQueryClient } from '@tanstack/react-query'
124
+
125
+ function CreatePost() {
126
+ const queryClient = useQueryClient()
127
+
128
+ const mutation = useMutation({
129
+ mutationFn: (newPost: PostInput) =>
130
+ fetch('/api/posts', {
131
+ method: 'POST',
132
+ body: JSON.stringify(newPost),
133
+ }),
134
+
135
+ // Invalidate and refetch after success
136
+ onSuccess: () => {
137
+ queryClient.invalidateQueries({ queryKey: ['posts'] })
138
+ },
139
+ })
140
+
141
+ return (
142
+ <button
143
+ disabled={mutation.isPending}
144
+ onClick={() => mutation.mutate({ title: 'New Post' })}
145
+ >
146
+ {mutation.isPending ? 'Creating...' : 'Create Post'}
147
+ </button>
148
+ )
149
+ }
150
+ ```
151
+
152
+ ## Optimistic Updates
153
+
154
+ ```tsx
155
+ const mutation = useMutation({
156
+ mutationFn: toggleTodoStatus,
157
+
158
+ onMutate: async (todoId) => {
159
+ // Cancel outgoing refetches
160
+ await queryClient.cancelQueries({ queryKey: ['todos'] })
161
+
162
+ // Snapshot previous value
163
+ const previousTodos = queryClient.getQueryData(['todos'])
164
+
165
+ // Optimistically update
166
+ queryClient.setQueryData(['todos'], (old: Todo[]) =>
167
+ old.map(t => t.id === todoId ? { ...t, done: !t.done } : t)
168
+ )
169
+
170
+ // Return context for rollback
171
+ return { previousTodos }
172
+ },
173
+
174
+ onError: (err, todoId, context) => {
175
+ // Rollback on failure
176
+ queryClient.setQueryData(['todos'], context?.previousTodos)
177
+ },
178
+
179
+ onSettled: () => {
180
+ // Refetch to sync with server
181
+ queryClient.invalidateQueries({ queryKey: ['todos'] })
182
+ },
183
+ })
184
+ ```
185
+
186
+ ## Infinite Queries
187
+
188
+ ```tsx
189
+ import { useInfiniteQuery } from '@tanstack/react-query'
190
+
191
+ function PostFeed() {
192
+ const {
193
+ data,
194
+ fetchNextPage,
195
+ hasNextPage,
196
+ isFetchingNextPage,
197
+ } = useInfiniteQuery({
198
+ queryKey: ['posts', 'infinite'],
199
+ queryFn: ({ pageParam }) =>
200
+ fetch(`/api/posts?cursor=${pageParam}`).then(r => r.json()),
201
+ initialPageParam: 0,
202
+ getNextPageParam: (lastPage) => lastPage.nextCursor ?? undefined,
203
+ })
204
+
205
+ return (
206
+ <div>
207
+ {data.pages.map(page =>
208
+ page.posts.map(post => <PostCard key={post.id} post={post} />)
209
+ )}
210
+ {hasNextPage && (
211
+ <button onClick={() => fetchNextPage()} disabled={isFetchingNextPage}>
212
+ Load more
213
+ </button>
214
+ )}
215
+ </div>
216
+ )
217
+ }
218
+ ```
219
+
220
+ ## Prefetching (Next.js)
221
+
222
+ ```tsx
223
+ // app/posts/page.tsx — Server Component
224
+ import { dehydrate, HydrationBoundary, QueryClient } from '@tanstack/react-query'
225
+ import { PostsList } from './PostsList'
226
+
227
+ export default async function PostsPage() {
228
+ const queryClient = new QueryClient()
229
+
230
+ // Prefetch on server
231
+ await queryClient.prefetchQuery({
232
+ queryKey: ['posts'],
233
+ queryFn: fetchPosts,
234
+ })
235
+
236
+ return (
237
+ <HydrationBoundary state={dehydrate(queryClient)}>
238
+ <PostsList />
239
+ </HydrationBoundary>
240
+ )
241
+ }
242
+ ```
243
+
244
+ ```tsx
245
+ // app/posts/PostsList.tsx — Client Component
246
+ 'use client'
247
+
248
+ import { useQuery } from '@tanstack/react-query'
249
+
250
+ export function PostsList() {
251
+ // Uses prefetched data — no loading state on first render
252
+ const { data } = useQuery({ queryKey: ['posts'], queryFn: fetchPosts })
253
+ return data.map(post => <PostCard key={post.id} post={post} />)
254
+ }
255
+ ```
256
+
257
+ ## Combining with Server Actions
258
+
259
+ ```tsx
260
+ // Use Server Actions for mutations, TanStack Query for reads:
261
+
262
+ 'use client'
263
+ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
264
+ import { createPost } from './actions' // Server Action
265
+
266
+ function PostsPage() {
267
+ const queryClient = useQueryClient()
268
+
269
+ // Read — TanStack Query
270
+ const posts = useQuery({
271
+ queryKey: ['posts'],
272
+ queryFn: () => fetch('/api/posts').then(r => r.json()),
273
+ })
274
+
275
+ // Write — Server Action + cache revalidation
276
+ const mutation = useMutation({
277
+ mutationFn: (data: FormData) => createPost(null, data),
278
+ onSuccess: () => queryClient.invalidateQueries({ queryKey: ['posts'] }),
279
+ })
280
+
281
+ return (
282
+ <form action={mutation.mutate}>
283
+ <input name="title" />
284
+ <button>Create</button>
285
+ </form>
286
+ )
287
+ }
288
+ ```
289
+
290
+ ## staleTime vs gcTime
291
+
292
+ | Setting | What it controls | Recommended |
293
+ |---------|-----------------|-------------|
294
+ | `staleTime` | How long before data is considered stale and refetched | 30s–5min depending on update frequency |
295
+ | `gcTime` | How long inactive data stays in cache before garbage collection | 5–30min (longer than staleTime) |
296
+
297
+ ```tsx
298
+ // Static data — rarely changes
299
+ staleTime: Infinity, gcTime: 30 * 60 * 1000,
300
+
301
+ // Dashboard — updates every few minutes
302
+ staleTime: 5 * 60 * 1000, gcTime: 30 * 60 * 1000,
303
+
304
+ // Real-time feed — updates frequently
305
+ staleTime: 30 * 1000, gcTime: 5 * 60 * 1000,
306
+ ```
307
+
308
+ ## Common Pitfalls
309
+
310
+ | Pitfall | Fix |
311
+ |---------|-----|
312
+ | Using TanStack Query in Server Components | Move to `'use client'` component |
313
+ | `queryKey` as `['posts']` everywhere — no granularity | Use structured keys: `['posts', 'list', filters]` |
314
+ | Not setting `staleTime` — defaults to 0 | Set sensible `staleTime` — default 0 refetches too often |
315
+ | Mixing `isLoading` and `isFetching` | `isLoading` = first load; `isFetching` = any fetch including background |
316
+ | `useQuery` for mutations | `useQuery` is for reads; use `useMutation` for writes |
317
+ | Mutating cache directly without rollback | Always implement `onMutate` snapshot + `onError` rollback |
318
+ | Missing `HydrationBoundary` for SSR | Server-prefetched data won't hydrate without it |
319
+ | `queryClient` recreated every render | Wrap in `useState(() => new QueryClient(...))` |
320
+
321
+ ## Verification
322
+
323
+ - [ ] `QueryClientProvider` wraps the app in root layout
324
+ - [ ] `staleTime` and `gcTime` configured globally (not per-query unless needed)
325
+ - [ ] Query keys follow a structured pattern (all → lists → detail)
326
+ - [ ] Mutations call `invalidateQueries` or use optimistic updates
327
+ - [ ] Optimistic updates have rollback via `onError` + `onMutate` snapshot
328
+ - [ ] SSR pages use `HydrationBoundary` with `dehydrate`
329
+ - [ ] No `useQuery` in Server Components
330
+ - [ ] `isLoading` used for first-load skeleton; `isFetching` for background updates