picasso-skill 1.0.0 → 1.2.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.
@@ -2,12 +2,14 @@
2
2
 
3
3
  ## Table of Contents
4
4
  1. Component Architecture
5
- 2. State Management
6
- 3. Performance
7
- 4. Composition Patterns
8
- 5. Data Fetching
9
- 6. Styling
10
- 7. Common Mistakes
5
+ 2. React 19 Features
6
+ 3. State Management
7
+ 4. Performance
8
+ 5. Composition Patterns
9
+ 6. Data Fetching & Mutations
10
+ 7. Styling & Tailwind v4
11
+ 8. Dark Mode Toggle
12
+ 9. Common Mistakes
11
13
 
12
14
  ---
13
15
 
@@ -27,7 +29,6 @@ components/
27
29
  user-card/
28
30
  user-card.tsx
29
31
  user-card.test.tsx
30
- user-card.module.css
31
32
  types.ts
32
33
  ```
33
34
 
@@ -44,11 +45,67 @@ components/
44
45
 
45
46
  ---
46
47
 
47
- ## 2. State Management
48
+ ## 2. React 19 Features
49
+
50
+ ### The `use` Hook
51
+ Read promises and context directly in render. Works inside conditionals and loops unlike other hooks:
52
+ ```tsx
53
+ function UserProfile({ userPromise }: { userPromise: Promise<User> }) {
54
+ const user = use(userPromise); // suspends until resolved
55
+ return <h1>{user.name}</h1>;
56
+ }
57
+ ```
58
+
59
+ ### `useActionState` for Form Actions
60
+ Manages form state, pending status, and server action results in one hook:
61
+ ```tsx
62
+ 'use client';
63
+ import { useActionState } from 'react';
64
+ import { submitForm } from './actions';
65
+
66
+ function ContactForm() {
67
+ const [state, formAction, isPending] = useActionState(submitForm, { message: '' });
68
+ return (
69
+ <form action={formAction}>
70
+ <input name="email" type="email" required />
71
+ <button disabled={isPending}>{isPending ? 'Sending...' : 'Submit'}</button>
72
+ </form>
73
+ );
74
+ }
75
+ ```
76
+
77
+ ### `useFormStatus` for Submission State
78
+ Access the parent form's pending state from within child components:
79
+ ```tsx
80
+ 'use client';
81
+ import { useFormStatus } from 'react-dom';
82
+
83
+ function SubmitButton() {
84
+ const { pending } = useFormStatus();
85
+ return <button disabled={pending}>{pending ? 'Saving...' : 'Save'}</button>;
86
+ }
87
+ ```
88
+
89
+ ### `useOptimistic` for Instant Feedback
90
+ Show optimistic state while an async action completes:
91
+ ```tsx
92
+ const [optimistic, addOptimistic] = useOptimistic(
93
+ messages,
94
+ (current, newMsg: string) => [...current, { text: newMsg, pending: true }]
95
+ );
96
+ // Call addOptimistic(value) before await -- renders instantly, reverts on error
97
+ ```
98
+
99
+ ### React Compiler
100
+ React 19 ships with an opt-in compiler that auto-memoizes components, hooks, and expressions. When the compiler is enabled, remove manual `React.memo`, `useMemo`, and `useCallback` calls -- the compiler handles it. Check your project's `react-compiler` config before deciding whether to memoize manually.
101
+
102
+ ---
103
+
104
+ ## 3. State Management
48
105
 
49
106
  ### Where State Lives
50
107
  1. **URL state**: filters, pagination, search queries (use `searchParams`)
51
- 2. **Server state**: data from APIs (use React Query, SWR, or server components)
108
+ 2. **Server state**: data from APIs (use server components or React Query/SWR for client)
52
109
  3. **Local state**: form inputs, UI toggles, hover/focus state (use `useState`)
53
110
  4. **Shared local state**: state needed by siblings (lift to parent, or use context)
54
111
  5. **Global state**: rarely needed (auth user, theme preference, feature flags)
@@ -59,94 +116,49 @@ components/
59
116
  - Prefer `useReducer` over `useState` when the next state depends on the previous state or when managing more than 3 related state variables.
60
117
 
61
118
  ```tsx
62
- // Bad: storing derived state
63
- const [items, setItems] = useState(data);
64
- const [filteredItems, setFilteredItems] = useState([]);
65
- useEffect(() => {
66
- setFilteredItems(items.filter(i => i.active));
67
- }, [items]);
68
-
69
- // Good: compute during render
70
- const [items, setItems] = useState(data);
71
- const filteredItems = items.filter(i => i.active);
119
+ // Bad: derived state in useEffect // Good: compute during render
120
+ const [filtered, setFiltered] = useState([]); const filtered = items.filter(i => i.active);
121
+ useEffect(() => setFiltered(items.filter(i => i.active)), [items]);
72
122
  ```
73
123
 
74
124
  ---
75
125
 
76
- ## 3. Performance
126
+ ## 4. Performance
77
127
 
78
128
  ### Rendering
79
- - Use `React.memo` only for components that re-render often with the same props
80
- - Use `useMemo` for expensive computations, not for every variable
81
- - Use `useCallback` for callbacks passed to memoized children
129
+ - With React Compiler enabled: skip manual `React.memo`, `useMemo`, `useCallback`
130
+ - Without the compiler: use `React.memo` only for components that re-render often with the same props; use `useMemo` for expensive computations only; use `useCallback` for callbacks passed to memoized children
82
131
  - Use `key` props correctly (stable, unique identifiers, never array indices for reorderable lists)
83
132
 
84
133
  ### Code Splitting
134
+ Use `React.lazy` + `Suspense` for client components (works in Next.js App Router and plain React):
85
135
  ```tsx
86
- import dynamic from 'next/dynamic';
87
-
88
- const Chart = dynamic(() => import('./chart'), {
89
- loading: () => <ChartSkeleton />,
90
- ssr: false,
91
- });
136
+ const Chart = lazy(() => import('./chart'));
137
+ // Wrap in <Suspense fallback={<ChartSkeleton />}><Chart /></Suspense>
92
138
  ```
139
+ Next.js App Router also provides automatic route-level splitting per `page.tsx`/`layout.tsx`.
93
140
 
94
141
  ### Virtualization
95
- For lists with 100+ items, use `react-window` or `@tanstack/virtual`:
96
- ```tsx
97
- import { FixedSizeList } from 'react-window';
98
-
99
- <FixedSizeList height={600} itemCount={items.length} itemSize={48} width="100%">
100
- {({ index, style }) => <Row style={style} item={items[index]} />}
101
- </FixedSizeList>
102
- ```
142
+ For lists with 100+ items, use `@tanstack/virtual` or `react-window`.
103
143
 
104
144
  ### Image Optimization
105
- Use `next/image` in Next.js or `loading="lazy"` with explicit `width`/`height` attributes. Always set `aspect-ratio` to prevent layout shift.
145
+ Use `next/image` in Next.js or `loading="lazy"` with explicit `width`/`height`. Always set `aspect-ratio` to prevent layout shift.
106
146
 
107
147
  ---
108
148
 
109
- ## 4. Composition Patterns
149
+ ## 5. Composition Patterns
110
150
 
111
151
  ### Compound Components
112
- Components that share implicit state through context:
113
- ```tsx
114
- <Select value={value} onChange={setValue}>
115
- <Select.Trigger>Choose a fruit</Select.Trigger>
116
- <Select.Content>
117
- <Select.Item value="apple">Apple</Select.Item>
118
- <Select.Item value="banana">Banana</Select.Item>
119
- </Select.Content>
120
- </Select>
121
- ```
152
+ Components that share implicit state through context (e.g., `<Select>`, `<Select.Trigger>`, `<Select.Item>` pattern). Parent holds state, children consume via context.
122
153
 
123
154
  ### Slot Pattern
124
- Flexible component composition through named children:
125
- ```tsx
126
- function Card({ header, children, footer }) {
127
- return (
128
- <div className="card">
129
- {header && <div className="card-header">{header}</div>}
130
- <div className="card-body">{children}</div>
131
- {footer && <div className="card-footer">{footer}</div>}
132
- </div>
133
- );
134
- }
135
- ```
136
-
137
- ### Render Props (when needed)
138
- For components that need to share behavior, not UI:
139
- ```tsx
140
- <DataFetcher url="/api/users">
141
- {({ data, isLoading }) => isLoading ? <Skeleton /> : <UserList users={data} />}
142
- </DataFetcher>
143
- ```
155
+ Flexible composition through named children (`header`, `children`, `footer` as props). Avoids rigid component trees and enables flexible layouts without render props.
144
156
 
145
157
  ---
146
158
 
147
- ## 5. Data Fetching
159
+ ## 6. Data Fetching & Mutations
148
160
 
149
- ### Server Components (preferred)
161
+ ### Server Components (preferred for reads)
150
162
  ```tsx
151
163
  async function UserList() {
152
164
  const users = await fetch('/api/users').then(r => r.json());
@@ -154,19 +166,30 @@ async function UserList() {
154
166
  }
155
167
  ```
156
168
 
157
- ### Client-Side with Suspense
169
+ ### Server Actions (preferred for mutations)
170
+ Define actions in a `'use server'` file and call from client components via form `action`:
158
171
  ```tsx
159
- function Dashboard() {
160
- return (
161
- <Suspense fallback={<DashboardSkeleton />}>
162
- <DashboardContent />
163
- </Suspense>
164
- );
172
+ // actions.ts
173
+ 'use server';
174
+ import { revalidatePath } from 'next/cache';
175
+
176
+ export async function createTodo(formData: FormData) {
177
+ await db.todo.create({ data: { title: formData.get('title') as string } });
178
+ revalidatePath('/todos');
179
+ }
180
+ ```
181
+ ```tsx
182
+ // todo-form.tsx -- 'use client'
183
+ import { createTodo } from './actions';
184
+ export default function TodoForm() {
185
+ return <form action={createTodo}><input name="title" required /><button>Add</button></form>;
165
186
  }
166
187
  ```
167
188
 
168
- ### Error Boundaries
169
- Wrap data-fetching components in error boundaries:
189
+ Server Actions handle serialization, error boundaries, and progressive enhancement. Prefer them over API route handlers for mutations.
190
+
191
+ ### Client-Side with Suspense + Error Boundaries
192
+ Always wrap data-fetching components:
170
193
  ```tsx
171
194
  <ErrorBoundary fallback={<ErrorMessage />}>
172
195
  <Suspense fallback={<Skeleton />}>
@@ -177,7 +200,29 @@ Wrap data-fetching components in error boundaries:
177
200
 
178
201
  ---
179
202
 
180
- ## 6. Styling
203
+ ## 7. Styling & Tailwind v4
204
+
205
+ ### Tailwind v4 Changes
206
+ Tailwind v4 uses a CSS-first configuration model. Key differences from v3:
207
+
208
+ - **No `tailwind.config.js`**: configuration lives in CSS using the `@theme` directive
209
+ - **New import**: use `@import "tailwindcss"` instead of `@tailwind base/components/utilities` directives
210
+ - **CSS variables for all tokens**: every design token is automatically exposed as a CSS custom property (`--color-blue-500`, `--spacing-4`, etc.)
211
+
212
+ ```css
213
+ /* app/globals.css */
214
+ @import "tailwindcss";
215
+
216
+ @theme {
217
+ --color-brand: #6366f1;
218
+ --color-surface: #ffffff;
219
+ --color-surface-dark: #0f172a;
220
+ --font-display: "Inter", sans-serif;
221
+ --breakpoint-3xl: 1920px;
222
+ }
223
+ ```
224
+
225
+ You can reference these tokens anywhere in CSS or JS via `var(--color-brand)` without any build-step workaround.
181
226
 
182
227
  ### Tailwind Best Practices
183
228
  - Use Tailwind's core utility classes (pre-defined classes only in Claude artifacts)
@@ -193,24 +238,81 @@ import styles from './button.module.css';
193
238
  ```
194
239
 
195
240
  ### Semantic HTML
196
- Use the right element, not just `div` with classes:
197
- - `<nav>` for navigation
198
- - `<main>` for primary content
199
- - `<section>` for thematic grouping
200
- - `<article>` for self-contained content
201
- - `<aside>` for tangentially related content
202
- - `<header>` and `<footer>` for their semantic purpose
203
- - `<button>` for clickable actions, `<a>` for navigation
241
+ Use the right element: `<nav>` for navigation, `<main>` for primary content, `<section>` for thematic grouping, `<article>` for self-contained content, `<button>` for actions, `<a>` for navigation. Do not use bare `div` where semantic elements apply.
242
+
243
+ ---
244
+
245
+ ## 8. Dark Mode Toggle
246
+
247
+ Complete dark mode implementation with localStorage persistence, system preference detection, and flash prevention.
248
+
249
+ ### Blocking Script (prevents flash of wrong theme)
250
+ Place inline in `<head>` before stylesheets (in Next.js, use `<script dangerouslySetInnerHTML>` in `app/layout.tsx`):
251
+ ```html
252
+ <script>
253
+ (function() {
254
+ var s = localStorage.getItem('theme');
255
+ var theme = s || (matchMedia('(prefers-color-scheme:dark)').matches ? 'dark' : 'light');
256
+ document.documentElement.setAttribute('data-theme', theme);
257
+ })();
258
+ </script>
259
+ ```
260
+
261
+ ### Theme Toggle Hook
262
+ ```tsx
263
+ 'use client';
264
+ function useTheme() {
265
+ const [theme, setThemeState] = useState<'light' | 'dark'>(() =>
266
+ typeof window === 'undefined' ? 'light'
267
+ : (document.documentElement.getAttribute('data-theme') as 'light' | 'dark') || 'light'
268
+ );
269
+ const setTheme = useCallback((next: 'light' | 'dark') => {
270
+ document.documentElement.setAttribute('data-theme', next);
271
+ localStorage.setItem('theme', next);
272
+ setThemeState(next);
273
+ }, []);
274
+ const toggle = useCallback(() => setTheme(theme === 'dark' ? 'light' : 'dark'), [theme, setTheme]);
275
+ // Listen for system preference changes when no explicit choice is stored
276
+ useEffect(() => {
277
+ const mq = matchMedia('(prefers-color-scheme: dark)');
278
+ const h = (e: MediaQueryListEvent) => { if (!localStorage.getItem('theme')) setTheme(e.matches ? 'dark' : 'light'); };
279
+ mq.addEventListener('change', h);
280
+ return () => mq.removeEventListener('change', h);
281
+ }, [setTheme]);
282
+ return { theme, setTheme, toggle };
283
+ }
284
+ ```
285
+
286
+ ### CSS Setup with Tailwind v4
287
+ ```css
288
+ @import "tailwindcss";
289
+
290
+ @theme {
291
+ --color-bg: #ffffff;
292
+ --color-text: #0f172a;
293
+ --color-surface: #f8fafc;
294
+ }
295
+
296
+ [data-theme="dark"] {
297
+ --color-bg: #0f172a;
298
+ --color-text: #f8fafc;
299
+ --color-surface: #1e293b;
300
+ }
301
+ ```
204
302
 
205
303
  ---
206
304
 
207
- ## 7. Common Mistakes
305
+ ## 9. Common Mistakes
208
306
 
209
307
  - Using `useEffect` for derived state (compute during render instead)
210
308
  - Putting everything in global state (most state should be local or server-derived)
211
309
  - Using `index` as `key` for dynamic lists
212
- - Wrapping every component in `React.memo`
310
+ - Wrapping every component in `React.memo` (let the React Compiler handle this)
213
311
  - Using `any` in TypeScript (defeats the purpose of type safety)
214
312
  - Fetching data in `useEffect` when a server component would suffice
215
313
  - Not using Suspense boundaries (the whole page flashes instead of parts loading independently)
216
314
  - Prop drilling through 5+ levels (use composition or context)
315
+ - Using API route handlers for mutations when Server Actions are simpler
316
+ - Not using `useOptimistic` for actions that need instant visual feedback
317
+ - Manual memoization when the React Compiler is enabled (causes unnecessary code noise)
318
+ - Using `@tailwind` directives or `tailwind.config.js` in Tailwind v4 projects (use `@import "tailwindcss"` and `@theme` instead)