start-vibing-stacks 2.17.0 → 2.19.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "start-vibing-stacks",
3
- "version": "2.17.0",
3
+ "version": "2.19.0",
4
4
  "description": "AI-powered multi-stack dev workflow for Claude Code. Supports PHP, Node.js, Python and more.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,11 +1,14 @@
1
1
  ---
2
2
  name: preline-ui
3
- version: 1.0.0
3
+ version: 2.0.0
4
+ description: "Preline UI — semantic-token component system on top of Tailwind v4 (stable Jan 2025). 220+ design tokens covering background/foreground/primary/secondary/muted/destructive/border/surface/layer + per-component groups (navbar, sidebar, card, dropdown, select, overlay, tooltip, popover, scrollbar). 840+ free copy-paste blocks (hero, testimonials, pricing, dashboards, forms). Mandatory `HSStaticMethods.autoInit()` after client-side navigation in Next.js — without it, dropdowns/modals/accordions break. Theme generator CLI, custom palettes via `@theme inline { … }`, light + dark via `data-theme` + `.dark`. Chart tokens require HEX (no oklch in -hex). Pairs with React 19 (refs as props, document metadata). Invoke when using Preline components, creating themes, or customising tokens."
4
5
  ---
5
6
 
6
- # Preline UI — Component & Theme System for TailwindCSS 4
7
+ # Preline UI — Component & Theme System for Tailwind v4 (2026)
7
8
 
8
- **ALWAYS invoke when using Preline components, creating themes, or customizing design tokens.**
9
+ **ALWAYS invoke when using Preline components, creating themes, or customising design tokens.**
10
+
11
+ > Pairs with `tailwind-patterns` v2 (Tailwind v4 `@theme`, Oxide engine, OKLCH) and `react-standards` v2 (LABELS/STYLES const pattern). For shadcn-style primitives with Radix/Base UI underneath, see `shadcn-ui` v2 — pick **one** component system per project, don't mix Preline and shadcn.
9
12
 
10
13
  ## What is Preline
11
14
 
@@ -1,12 +1,23 @@
1
1
  ---
2
2
  name: react-patterns
3
- version: 1.0.0
3
+ version: 2.0.0
4
+ description: "Modern React 19 component architecture (stable Dec 5 2024; 19.1 in 2025). Covers the new hooks (`useActionState`, `useOptimistic`, `useFormStatus`, `use()` for promises and Context), refs-as-props (no more `forwardRef`), document metadata hoisting (write `<title>` and `<meta>` anywhere), Actions for async mutations, Server Components vs Client Components, compound + generic components, custom hooks, state-management decision matrix (useState → useReducer → Context → Zustand → server cache), Error Boundaries, performance via memo/useMemo/useCallback, and Suspense boundaries. Invoke for any new component, hook, state design, or React-architecture decision."
4
5
  ---
5
6
 
6
- # React Patterns — Modern Component Architecture
7
+ # React 19 Patterns — Modern Component Architecture
7
8
 
8
9
  **ALWAYS invoke when writing React components, hooks, or state management.**
9
10
 
11
+ ## React 19 essentials (stable Dec 5, 2024 → 19.1 in 2025)
12
+
13
+ - **Actions** — async functions passed straight to forms; `useActionState` manages pending/error/optimistic state automatically
14
+ - **`use()`** — read promises and Context **conditionally** during render (paired with Suspense + Error Boundaries)
15
+ - **Refs as props** — pass `ref` like any other prop. **`forwardRef` is no longer needed** for new components
16
+ - **Document metadata hoisting** — `<title>`, `<meta>`, `<link>` get auto-hoisted into `<head>` from anywhere
17
+ - **Form `action` prop** — submits via the action, auto-resets uncontrolled inputs on success
18
+ - **Stylesheet management** — `<link rel="stylesheet">` in components is awaited before the Suspense boundary reveals
19
+ - **Suspense sibling pre-warming** — fallback shows immediately and queues sibling requests in parallel
20
+
10
21
  ## Component Patterns
11
22
 
12
23
  ### Compound Components
@@ -109,52 +120,125 @@ const Heavy = lazy(() => import('./HeavyComponent'));
109
120
  <Suspense fallback={<Loading />}><Heavy /></Suspense>
110
121
  ```
111
122
 
112
- ## React 19 Hooks
123
+ ## React 19 — Actions & form-state hooks
124
+
125
+ ### `useActionState` — pending + error + result for one form
113
126
 
114
127
  ```tsx
115
- // useActionState — form submission state
116
128
  import { useActionState } from 'react';
117
129
 
118
130
  function LoginForm() {
119
131
  const [state, formAction, isPending] = useActionState(
120
- async (prev, formData: FormData) => {
132
+ async (_prev, formData: FormData) => {
121
133
  const result = await login(formData);
122
134
  if (result.error) return { error: result.error };
123
135
  redirect('/dashboard');
124
136
  },
125
- { error: null }
137
+ { error: null as string | null }
126
138
  );
127
139
 
128
140
  return (
129
141
  <form action={formAction}>
130
- <input name="email" type="email" />
142
+ <input name="email" type="email" required />
131
143
  {state.error && <p className="text-destructive">{state.error}</p>}
132
- <button disabled={isPending}>
133
- {isPending ? 'Signing in...' : 'Sign In'}
134
- </button>
144
+ <button disabled={isPending}>{isPending ? 'Signing in…' : 'Sign In'}</button>
135
145
  </form>
136
146
  );
137
147
  }
148
+ ```
149
+
150
+ ### `useFormStatus` — child of a form reads its parent's submit state
151
+
152
+ ```tsx
153
+ import { useFormStatus } from 'react-dom';
154
+
155
+ function SubmitButton() {
156
+ const { pending } = useFormStatus(); // ← reads the enclosing <form action={…}>
157
+ return <button disabled={pending}>{pending ? 'Saving…' : 'Save'}</button>;
158
+ }
159
+ ```
138
160
 
139
- // useOptimisticinstant UI feedback
161
+ Drop `<SubmitButton />` inside any `<form action={…}>` no prop drilling, no context.
162
+
163
+ ### `useOptimistic` — instant UI feedback that auto-reverts on error
164
+
165
+ ```tsx
140
166
  import { useOptimistic } from 'react';
141
167
 
142
168
  function TodoList({ todos }: { todos: Todo[] }) {
143
169
  const [optimisticTodos, addOptimistic] = useOptimistic(
144
170
  todos,
145
- (state, newTodo: Todo) => [...state, newTodo]
171
+ (current, newTodo: Todo) => [...current, newTodo]
146
172
  );
147
173
 
148
- const addTodo = async (formData: FormData) => {
149
- const todo = { id: crypto.randomUUID(), title: formData.get('title') as string, done: false };
150
- addOptimistic(todo); // Instant UI
151
- await saveTodo(todo); // Server sync
152
- };
174
+ async function add(formData: FormData) {
175
+ const todo = { id: crypto.randomUUID(), title: String(formData.get('title')), done: false };
176
+ addOptimistic(todo); // instant
177
+ await saveTodo(todo); // server confirms (or throws → revert is automatic on rerender)
178
+ }
153
179
 
154
- return <ul>{optimisticTodos.map(t => <li key={t.id}>{t.title}</li>)}</ul>;
180
+ return (
181
+ <>
182
+ <form action={add}><input name="title" /></form>
183
+ <ul>{optimisticTodos.map(t => <li key={t.id}>{t.title}</li>)}</ul>
184
+ </>
185
+ );
155
186
  }
156
187
  ```
157
188
 
189
+ ### `use()` — read a promise or Context conditionally
190
+
191
+ ```tsx
192
+ import { use, Suspense } from 'react';
193
+
194
+ function UserName({ userPromise }: { userPromise: Promise<User> }) {
195
+ const user = use(userPromise); // suspends until resolved
196
+ return <span>{user.name}</span>;
197
+ }
198
+
199
+ // Parent
200
+ <Suspense fallback={<Skeleton />}>
201
+ <UserName userPromise={fetchUser(id)} />
202
+ </Suspense>
203
+ ```
204
+
205
+ `use()` is the only hook allowed inside conditionals — perfect for "render after this resource resolves" without restructuring components.
206
+
207
+ ## React 19 — refs as props (no `forwardRef`)
208
+
209
+ ```tsx
210
+ // React 19 — `ref` is just a prop
211
+ function Input({ ref, className, ...props }: React.InputHTMLAttributes<HTMLInputElement> & {
212
+ ref?: React.Ref<HTMLInputElement>;
213
+ }) {
214
+ return <input ref={ref} className={cn('h-10 px-3 …', className)} {...props} />;
215
+ }
216
+
217
+ // Usage — no different from before
218
+ const myRef = useRef<HTMLInputElement>(null);
219
+ <Input ref={myRef} />
220
+ ```
221
+
222
+ `forwardRef` keeps working — but new components should ditch it.
223
+
224
+ ## React 19 — document metadata anywhere
225
+
226
+ ```tsx
227
+ function Article({ post }: { post: Post }) {
228
+ return (
229
+ <article>
230
+ <title>{post.title} — My Site</title> {/* hoisted into <head> */}
231
+ <meta name="description" content={post.summary} />
232
+ <link rel="canonical" href={`/blog/${post.slug}`} />
233
+ <h1>{post.title}</h1>
234
+ {/* … */}
235
+ </article>
236
+ );
237
+ }
238
+ ```
239
+
240
+ No `next/head` / `react-helmet` needed — React handles it. Server-rendered metadata appears in the initial HTML (good for SEO).
241
+
158
242
  ## State Management Selection
159
243
 
160
244
  | Complexity | Solution | When |
@@ -219,11 +303,26 @@ class ErrorBoundary extends Component<
219
303
 
220
304
  ## FORBIDDEN
221
305
 
222
- 1. **Class components** function components only (except ErrorBoundary)
223
- 2. **Prop drilling** — use context or composition
224
- 3. **Inline objects/functions in JSX**causes re-renders
225
- 4. **useEffect for derived state** use useMemo
226
- 5. **Mutating state directly** always setState/dispatch
227
- 6. **Index as key** use stable unique ID (`uuid`, `id`)
228
- 7. **Premature optimization** profile first with React DevTools
229
- 8. **useEffect for everything** prefer server components for data
306
+ | Pattern | Why |
307
+ |---|---|
308
+ | `forwardRef` in new components | Refs are props in React 19 drop the wrapper |
309
+ | `useEffect` to fetch data | Use Server Components, `use()` + Suspense, or TanStack Query |
310
+ | `useEffect` for derived state | Compute it in render or via `useMemo` |
311
+ | `next/head` / `react-helmet` in R19 | Use plain `<title>` / `<meta>` — React hoists |
312
+ | Class components | Function components only (Error Boundaries are the rare exception) |
313
+ | Prop drilling > 2 levels | Composition or `Context` |
314
+ | Inline objects/functions in hot paths | Stable refs via `useMemo` / `useCallback` (only when profiler shows it matters) |
315
+ | Direct state mutation | Always set new state — React relies on referential change |
316
+ | Index as `key` | Use stable unique IDs (`crypto.randomUUID()`, server IDs) |
317
+ | Premature memoisation | Profile with React DevTools first |
318
+ | Manual `isPending` + `error` `useState` for forms | Use `useActionState` |
319
+ | Custom optimistic-state code | `useOptimistic` + Action |
320
+
321
+ ## See Also
322
+
323
+ - `react-standards` v2 — LABELS / STYLES const pattern
324
+ - `react-ui-patterns` v2 — loading/error/empty + TanStack Query v5
325
+ - `tailwind-patterns` v2 — `@theme`, container queries, OKLCH
326
+ - `shadcn-ui` v2 — primitives without `forwardRef`, `data-slot` styling
327
+ - `zod-validation` v2 — Zod 4 + RHF for form Actions
328
+ - `_shared/skills/playwright-automation` v2 — E2E coverage including R19 forms
@@ -1,14 +1,18 @@
1
1
  ---
2
2
  name: react-standards
3
- version: 1.0.0
3
+ version: 2.0.0
4
+ description: "Project conventions for React 19 + Tailwind v4 codebases. Mandates the LABELS / STYLES const pattern (defined at file top, before hooks, with `as const` for stable references — prevents re-renders, centralises styles, supports i18n drop-in), controlled debug logging (no raw console.log), semantic Tailwind tokens (no raw colours), separate-file SVG icons, mandatory loading/empty states, modal data-flow contract (`onUpdated` callback), and third-party chart integration (no useRef DOM mutation). Pairs with `tailwind-patterns` v2 for tokens and `react-ui-patterns` v2 for state choreography. Invoke at the start of any new React project or component file."
4
5
  ---
5
6
 
6
- # React 19+ Standards
7
+ # React 19 + Tailwind v4 — Project Standards
8
+
9
+ > Conventions that apply to **every** component in the codebase. Pairs with `react-patterns` (architecture), `tailwind-patterns` (tokens), `react-ui-patterns` (state), and `shadcn-ui` (primitives).
7
10
 
8
11
  ## Version Requirements
9
12
 
10
- - **ReactJS >= 19** — MANDATORY
11
- - **TailwindCSS >= 4** — MANDATORY
13
+ - **React 19.0** (stable Dec 5, 2024) — MANDATORY for new projects
14
+ - **TailwindCSS 4.0** (stable Jan 22, 2025) — MANDATORY (CSS-first `@theme`, Oxide engine)
15
+ - **TypeScript ≥ 5.6** strict mode
12
16
 
13
17
  ## Label Constants Pattern
14
18
 
@@ -279,3 +283,12 @@ const processedData = useMemo(() => {
279
283
  - Conditional rendering: `data && <Component />`
280
284
  - `useMemo` for expensive computations
281
285
  - Loading states before rendering charts
286
+
287
+ ## See Also
288
+
289
+ - `react-patterns` v2 — React 19 hooks, `use()`, refs as props
290
+ - `tailwind-patterns` v2 — `@theme`, Oxide engine, container queries, OKLCH
291
+ - `shadcn-ui` v2 — primitives without `forwardRef`, `data-slot` slots
292
+ - `react-ui-patterns` v2 — loading/error/empty + TanStack Query v5
293
+ - `zod-validation` v2 — Zod 4 schemas for forms + env vars
294
+ - `_shared/skills/ui-ux-audit` v2 — WCAG 2.2 AA validation
@@ -1,12 +1,26 @@
1
1
  ---
2
2
  name: react-ui-patterns
3
- version: 1.0.0
3
+ version: 2.0.0
4
+ description: "React 19 + TanStack Query v5 UI patterns for production async UI: loading/error/empty state hierarchy, skeleton vs spinner choice, ErrorState/EmptyState/LoadingState reusable components, button states with disabled+loading indicator, RHF + Zod 4 forms, optimistic updates via React 19 `useOptimistic` (built-in, simpler than TanStack `onMutate`/rollback) AND TanStack Query v5 onMutate pattern (when you need cache surgery), Suspense + useSuspenseQuery boundaries, signal threading for cancellation. TanStack Query v5 renamed `loading` → `pending` (note isPending vs isFetching vs isLoading). Invoke when handling any async UI state, form, mutation, or user feedback."
4
5
  ---
5
6
 
6
- # React UI Patterns — Loading, Errors, Empty States & Forms
7
+ # React UI Patterns — Loading, Errors, Empty States & Forms (R19 + TanStack v5)
7
8
 
8
9
  **ALWAYS invoke when handling async UI states, forms, or user feedback.**
9
10
 
11
+ ## Stack snapshot (2026)
12
+
13
+ | Concern | Pick |
14
+ |---|---|
15
+ | Server cache + refetch + dedup | **TanStack Query v5** (`useQuery`, `useMutation`, `useSuspenseQuery`) |
16
+ | Form state + validation | **react-hook-form** + **`@hookform/resolvers/zod`** (Zod 4) |
17
+ | Optimistic UI for **submit-then-confirm** flows | React 19 **`useOptimistic`** (no cache surgery needed) |
18
+ | Optimistic UI when you must mutate the cache | TanStack `onMutate` + `setQueryData` rollback |
19
+ | Suspense / streaming data | `useSuspenseQuery` + `<Suspense>` |
20
+ | Toasts | **Sonner** (`toast.success`, `toast.error`) |
21
+
22
+ > **TanStack Query v5 renamed `loading` → `pending`.** Use `isPending` (no data yet, no error), `isFetching` (any fetch in flight, including refetch), `isLoading` (legacy alias for `isPending && isFetching`). The render-tree decision below is unchanged.
23
+
10
24
  ## Core Principles
11
25
 
12
26
  1. **Never show stale UI** — loading only when actually loading
@@ -33,18 +47,40 @@ Has data?
33
47
  ```
34
48
 
35
49
  ```tsx
36
- // ✅ CORRECT — only loading when no data
37
- const { data, isLoading, error, refetch } = useQuery(...);
50
+ // ✅ CORRECT — TanStack v5: isPending = no data yet
51
+ const { data, isPending, error, refetch } = useQuery({
52
+ queryKey: ['items'],
53
+ queryFn: ({ signal }) => fetchItems({ signal }), // thread signal for cancellation
54
+ });
38
55
 
39
- if (error) return <ErrorState error={error} onRetry={refetch} />;
40
- if (isLoading && !data) return <Skeleton />;
56
+ if (error) return <ErrorState error={error} onRetry={refetch} />;
57
+ if (isPending) return <Skeleton />;
41
58
  if (!data?.items.length) return <EmptyState />;
42
59
  return <ItemList items={data.items} />;
43
60
 
44
- // ❌ WRONG — flashes spinner on refetch when cached data exists
61
+ // ❌ WRONG (v3 idiom) — flashes spinner on refetch when cached data exists
45
62
  if (isLoading) return <Spinner />;
46
63
  ```
47
64
 
65
+ ### Suspense alternative
66
+
67
+ ```tsx
68
+ // useSuspenseQuery — cleaner when you wrap with <Suspense> + <ErrorBoundary>
69
+ function ItemList() {
70
+ const { data } = useSuspenseQuery({
71
+ queryKey: ['items'],
72
+ queryFn: ({ signal }) => fetchItems({ signal }),
73
+ });
74
+ return <ul>{data.items.map(i => <li key={i.id}>{i.title}</li>)}</ul>;
75
+ }
76
+
77
+ <ErrorBoundary fallback={<ErrorState />}>
78
+ <Suspense fallback={<Skeleton />}>
79
+ <ItemList />
80
+ </Suspense>
81
+ </ErrorBoundary>
82
+ ```
83
+
48
84
  ### Skeleton vs Spinner
49
85
 
50
86
  | Use Skeleton | Use Spinner |
@@ -276,49 +312,83 @@ export default function CreateUser() {
276
312
  }
277
313
  ```
278
314
 
279
- ## Optimistic Updates (TanStack Query)
315
+ ## Optimistic Updates pick by use case
316
+
317
+ ### Option A — React 19 `useOptimistic` (simpler, NEW DEFAULT for forms)
318
+
319
+ When the optimistic update is **local to a component** and tied to a form Action, this is the cleanest option — no manual rollback, no cache surgery.
320
+
321
+ ```tsx
322
+ "use client";
323
+ import { useOptimistic } from "react";
324
+
325
+ function TodoList({ todos, addTodo }: { todos: Todo[]; addTodo: (t: Todo) => Promise<void> }) {
326
+ const [optimisticTodos, addOptimistic] = useOptimistic(
327
+ todos,
328
+ (current, newTodo: Todo) => [...current, newTodo],
329
+ );
330
+
331
+ async function action(formData: FormData) {
332
+ const todo = { id: crypto.randomUUID(), title: String(formData.get("title")), done: false };
333
+ addOptimistic(todo); // instant
334
+ await addTodo(todo); // server confirms — on error React reverts on next render
335
+ }
336
+
337
+ return (
338
+ <>
339
+ <form action={action}>
340
+ <input name="title" required />
341
+ <button>Add</button>
342
+ </form>
343
+ <ul>{optimisticTodos.map(t => <li key={t.id}>{t.title}</li>)}</ul>
344
+ </>
345
+ );
346
+ }
347
+ ```
348
+
349
+ ### Option B — TanStack Query `onMutate` + cache rollback
350
+
351
+ Reach for this when the mutation has to update a **shared server cache** that other components also read (toggling a favourite that appears in three lists, for instance).
280
352
 
281
353
  ```tsx
282
- import { useMutation, useQueryClient } from '@tanstack/react-query';
354
+ import { useMutation, useQueryClient } from "@tanstack/react-query";
283
355
 
284
356
  function ToggleFavorite({ item }: { item: Item }) {
285
- const queryClient = useQueryClient();
357
+ const qc = useQueryClient();
286
358
 
287
359
  const mutation = useMutation({
288
360
  mutationFn: () =>
289
- fetch(`/api/items/${item.id}/favorite`, { method: 'POST' }).then((res) => {
290
- if (!res.ok) throw new Error('Failed');
291
- return res.json();
361
+ fetch(`/api/items/${item.id}/favorite`, { method: "POST" }).then(r => {
362
+ if (!r.ok) throw new Error("Failed");
363
+ return r.json();
292
364
  }),
293
365
  onMutate: async () => {
294
- await queryClient.cancelQueries({ queryKey: ['items'] });
295
- const previous = queryClient.getQueryData<Item[]>(['items']);
296
-
297
- queryClient.setQueryData<Item[]>(['items'], (old) =>
298
- old?.map((i) =>
299
- i.id === item.id ? { ...i, isFavorite: !i.isFavorite } : i
300
- )
366
+ await qc.cancelQueries({ queryKey: ["items"] });
367
+ const previous = qc.getQueryData<Item[]>(["items"]);
368
+ qc.setQueryData<Item[]>(["items"], (old) =>
369
+ old?.map(i => i.id === item.id ? { ...i, isFavorite: !i.isFavorite } : i),
301
370
  );
302
-
303
371
  return { previous };
304
372
  },
305
373
  onError: (_err, _vars, context) => {
306
- queryClient.setQueryData(['items'], context?.previous);
307
- toast.error('Failed to update');
374
+ qc.setQueryData(["items"], context?.previous); // rollback
375
+ toast.error("Failed to update");
308
376
  },
309
377
  onSettled: () => {
310
- queryClient.invalidateQueries({ queryKey: ['items'] });
378
+ qc.invalidateQueries({ queryKey: ["items"] }); // truth from server
311
379
  },
312
380
  });
313
381
 
314
382
  return (
315
- <button onClick={() => mutation.mutate()} className="text-xl">
316
- {item.isFavorite ? '❤️' : '🤍'}
383
+ <button onClick={() => mutation.mutate()} className="text-xl" aria-label="Toggle favorite">
384
+ {item.isFavorite ? "❤️" : "🤍"}
317
385
  </button>
318
386
  );
319
387
  }
320
388
  ```
321
389
 
390
+ > Don't combine both for the same flow — `useOptimistic` for forms, TanStack `onMutate` for cross-cache updates. Mixing them creates invisible double rollbacks.
391
+
322
392
  ## Checklist — Before Shipping Any UI Component
323
393
 
324
394
  - [ ] Error state handled and shown to user
@@ -332,8 +402,13 @@ function ToggleFavorite({ item }: { item: Item }) {
332
402
 
333
403
  ## FORBIDDEN
334
404
 
335
- 1. **`if (loading) return <Spinner />`** — check `loading && !data` instead
336
- 2. **Silent catch** — always toast/display errors to user
337
- 3. **No empty state** every list needs one
338
- 4. **Clickable button during submit** always `disabled={isPending}` or `disabled={isSubmitting}`
339
- 5. **Console.log-only errors** user must see feedback
405
+ | Anti-pattern | Fix |
406
+ |---|---|
407
+ | `if (isLoading) return <Spinner />` (v3 idiom) | TanStack v5: check `isPending` (no data yet), keep cached data visible during refetch |
408
+ | Silent `catch (e) { console.log(e) }` | Always surface via toast / inline error |
409
+ | List without an empty state | Every collection needs `<EmptyState />` |
410
+ | Clickable button while a mutation is pending | `disabled={mutation.isPending}` + spinner |
411
+ | `console.log`-only errors | User must see feedback (toast / banner / inline) |
412
+ | `useOptimistic` AND TanStack `onMutate` for the same flow | Pick one — mixing produces double rollbacks |
413
+ | `mutationFn` that doesn't thread the `signal` | Add `({ signal }) => fetch(url, { signal })` so unmount cancels in-flight |
414
+ | `forwardRef` wrappers around shadcn primitives | React 19 — refs are plain props |