@matteoaliano/forest-ui 0.4.0 → 0.4.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/bin/sync.mjs +51 -151
- package/package.json +2 -4
- package/skills/forest-agency/SKILL.md +8 -24
- package/skills/forest-external/SKILL.md +8 -26
- package/skills/forest-internal/SKILL.md +8 -26
- package/bin/stamp-version.mjs +0 -53
- package/guidelines/FOREST_BE_GUIDELINES.md +0 -90
- package/guidelines/FOREST_FE_GUIDELINES.md +0 -543
- package/guidelines/FOREST_UI_GUIDELINES.md +0 -408
|
@@ -1,543 +0,0 @@
|
|
|
1
|
-
# Forest UI — Development Best Practices
|
|
2
|
-
|
|
3
|
-
> **forest-ui v0.4.0**
|
|
4
|
-
|
|
5
|
-
> **This file is synced by the `forest-ui` package.**
|
|
6
|
-
> Run `npx forest-ui sync` to update it.
|
|
7
|
-
|
|
8
|
-
# Next.js Code Agent - v2.0
|
|
9
|
-
|
|
10
|
-
## CORE PHILOSOPHY
|
|
11
|
-
|
|
12
|
-
1. **Server First**: Default Server Components, `'use client'` solo quando necessario
|
|
13
|
-
2. **Type Everything**: TypeScript strict, zero `any`
|
|
14
|
-
3. **Single Responsibility**: Ogni file = 1 job, pagine = orchestratori puri
|
|
15
|
-
4. **Separation of Concerns**: Logica separata da presentazione, data access isolato
|
|
16
|
-
5. **Explicit > Implicit**: Codice auto-documentante, no magic
|
|
17
|
-
|
|
18
|
-
---
|
|
19
|
-
|
|
20
|
-
## 1. ARCHITETTURA & STRUTTURA
|
|
21
|
-
|
|
22
|
-
### File System Organization
|
|
23
|
-
```
|
|
24
|
-
/app # App Router (Next.js 13+)
|
|
25
|
-
/(routes)/ # Route groups
|
|
26
|
-
/api/ # API routes
|
|
27
|
-
/[route]/
|
|
28
|
-
page.tsx # Pagina (max 20 righe, solo orchestrazione)
|
|
29
|
-
layout.tsx # Layout condiviso
|
|
30
|
-
loading.tsx # Loading UI
|
|
31
|
-
error.tsx # Error handling
|
|
32
|
-
/components
|
|
33
|
-
/ui/ # Design system base (Button, Card, Input...)
|
|
34
|
-
/features/ # Feature-specific (UserCard, ProductGrid...)
|
|
35
|
-
/lib
|
|
36
|
-
/actions/ # Server Actions (mutations)
|
|
37
|
-
/queries/ # Data fetching (read operations)
|
|
38
|
-
/hooks/ # Custom React hooks
|
|
39
|
-
/utils/ # Pure utility functions
|
|
40
|
-
/services/ # Business logic complessa
|
|
41
|
-
/parsers/ # URL params & form data parsing
|
|
42
|
-
/validations/ # Zod schemas
|
|
43
|
-
/types # TypeScript interfaces/types
|
|
44
|
-
/public # Static assets
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
### Naming Conventions
|
|
48
|
-
- **Componenti**: `PascalCase.tsx` (UserCard.tsx)
|
|
49
|
-
- **Hooks**: `use[Name].ts` (useDebounce.ts)
|
|
50
|
-
- **Actions**: `[entity].actions.ts` (user.actions.ts)
|
|
51
|
-
- **Queries**: `[entity].queries.ts` (user.queries.ts)
|
|
52
|
-
- **Utils**: `[entity].util.ts` (format.util.ts)
|
|
53
|
-
- **Parsers**: `[entity].parser.ts` (product.parser.ts)
|
|
54
|
-
- **Services**: `[entity].service.ts` (analytics.service.ts)
|
|
55
|
-
|
|
56
|
-
---
|
|
57
|
-
|
|
58
|
-
## 2. SINGLE RESPONSIBILITY - PAGE LEVEL
|
|
59
|
-
|
|
60
|
-
### Regola d'Oro
|
|
61
|
-
**Una pagina Next.js ha 1 sola responsabilità: "Comporre UI per questa route"**
|
|
62
|
-
|
|
63
|
-
### Pagina Ideale (Template)
|
|
64
|
-
```typescript
|
|
65
|
-
// app/products/page.tsx - Max 20 righe
|
|
66
|
-
import { getProducts } from '@/lib/queries/product.queries';
|
|
67
|
-
import { ProductGrid } from '@/components/features/products/ProductGrid';
|
|
68
|
-
|
|
69
|
-
export default async function ProductsPage() {
|
|
70
|
-
const products = await getProducts(); // Delega fetch
|
|
71
|
-
return <ProductGrid products={products} />; // Delega UI
|
|
72
|
-
}
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
### ❌ Una pagina NON deve:
|
|
76
|
-
- Costruire query complesse (>5 righe di logic)
|
|
77
|
-
- Fare trasformazioni dati (.map/.filter/.reduce con logica)
|
|
78
|
-
- Validare o parsare parametri inline
|
|
79
|
-
- Gestire errori con try/catch (usa error.tsx)
|
|
80
|
-
- Contenere JSX >50 righe (estrai componenti)
|
|
81
|
-
- Avere calcoli o aggregazioni
|
|
82
|
-
- Gestire state (useState, useReducer)
|
|
83
|
-
|
|
84
|
-
### ✅ Una pagina deve:
|
|
85
|
-
- Chiamare 1-3 funzioni di data fetching (in parallelo con Promise.all se >1)
|
|
86
|
-
- Comporre componenti feature
|
|
87
|
-
- Passare props ai componenti
|
|
88
|
-
- **Nient'altro**
|
|
89
|
-
|
|
90
|
-
---
|
|
91
|
-
|
|
92
|
-
## 3. SEPARATION OF CONCERNS - EXTRACTION PATTERNS
|
|
93
|
-
|
|
94
|
-
### Data Fetching → lib/queries/
|
|
95
|
-
**Responsabilità:** Read operations, DB access, API calls
|
|
96
|
-
```typescript
|
|
97
|
-
// lib/queries/product.queries.ts
|
|
98
|
-
'use server'
|
|
99
|
-
|
|
100
|
-
export async function getProducts(): Promise<Product[]> {
|
|
101
|
-
return await db.product.findMany({
|
|
102
|
-
where: { status: 'active' },
|
|
103
|
-
orderBy: { createdAt: 'desc' }
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
export async function getProductById(id: string): Promise<Product | null> {
|
|
108
|
-
return await db.product.findUnique({ where: { id } });
|
|
109
|
-
}
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
### Mutations → lib/actions/
|
|
113
|
-
**Responsabilità:** Create, Update, Delete operations
|
|
114
|
-
```typescript
|
|
115
|
-
// lib/actions/product.actions.ts
|
|
116
|
-
'use server'
|
|
117
|
-
|
|
118
|
-
import { revalidatePath } from 'next/cache';
|
|
119
|
-
import { productSchema } from '@/lib/validations/product.schema';
|
|
120
|
-
|
|
121
|
-
export async function createProduct(formData: FormData) {
|
|
122
|
-
const parsed = productSchema.safeParse(Object.fromEntries(formData));
|
|
123
|
-
if (!parsed.success) return { error: parsed.error };
|
|
124
|
-
|
|
125
|
-
const product = await db.product.create({ data: parsed.data });
|
|
126
|
-
revalidatePath('/products');
|
|
127
|
-
|
|
128
|
-
return { success: true, product };
|
|
129
|
-
}
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
### URL Parsing → lib/parsers/
|
|
133
|
-
**Responsabilità:** Validare e parsare searchParams, form data
|
|
134
|
-
```typescript
|
|
135
|
-
// lib/parsers/product.parser.ts
|
|
136
|
-
import { z } from 'zod';
|
|
137
|
-
|
|
138
|
-
const filterSchema = z.object({
|
|
139
|
-
category: z.string().default('all'),
|
|
140
|
-
sort: z.enum(['asc', 'desc']).default('desc'),
|
|
141
|
-
page: z.coerce.number().int().min(1).default(1)
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
export function parseProductFilters(params: Record<string, string | undefined>) {
|
|
145
|
-
return filterSchema.parse(params);
|
|
146
|
-
}
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
### Pure Logic → lib/utils/
|
|
150
|
-
**Responsabilità:** Funzioni pure (input → output, no side effects)
|
|
151
|
-
```typescript
|
|
152
|
-
// lib/utils/product.util.ts
|
|
153
|
-
|
|
154
|
-
export function calculateDiscount(price: number, discountPercent: number): number {
|
|
155
|
-
return price * (1 - discountPercent / 100);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
export function formatPrice(amount: number): string {
|
|
159
|
-
return new Intl.NumberFormat('it-IT', {
|
|
160
|
-
style: 'currency',
|
|
161
|
-
currency: 'EUR'
|
|
162
|
-
}).format(amount);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
export function isRecentProduct(createdAt: Date): boolean {
|
|
166
|
-
const daysSinceCreation = (Date.now() - createdAt.getTime()) / (1000 * 60 * 60 * 24);
|
|
167
|
-
return daysSinceCreation <= 30;
|
|
168
|
-
}
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
### Complex Business Logic → lib/services/
|
|
172
|
-
**Responsabilità:** Orchestrazione multi-entity, calcoli complessi, aggregazioni
|
|
173
|
-
```typescript
|
|
174
|
-
// lib/services/analytics.service.ts
|
|
175
|
-
'use server'
|
|
176
|
-
|
|
177
|
-
import { getOrders } from '@/lib/queries/order.queries';
|
|
178
|
-
import { getProducts } from '@/lib/queries/product.queries';
|
|
179
|
-
|
|
180
|
-
export async function calculateDashboardStats(userId: string) {
|
|
181
|
-
const [orders, products] = await Promise.all([
|
|
182
|
-
getOrders(userId),
|
|
183
|
-
getProducts()
|
|
184
|
-
]);
|
|
185
|
-
|
|
186
|
-
return {
|
|
187
|
-
totalRevenue: orders.reduce((sum, o) => sum + o.total, 0),
|
|
188
|
-
avgOrderValue: orders.length > 0 ? orders.reduce((sum, o) => sum + o.total, 0) / orders.length : 0,
|
|
189
|
-
topProducts: calculateTopProducts(orders, products),
|
|
190
|
-
conversionRate: calculateConversionRate(orders, products)
|
|
191
|
-
};
|
|
192
|
-
}
|
|
193
|
-
```
|
|
194
|
-
|
|
195
|
-
### Client Logic → lib/hooks/
|
|
196
|
-
**Responsabilità:** Stateful logic riutilizzabile, side effects
|
|
197
|
-
```typescript
|
|
198
|
-
// lib/hooks/useProductFilters.ts
|
|
199
|
-
'use client'
|
|
200
|
-
|
|
201
|
-
import { useState, useMemo } from 'react';
|
|
202
|
-
|
|
203
|
-
export function useProductFilters(products: Product[]) {
|
|
204
|
-
const [search, setSearch] = useState('');
|
|
205
|
-
const [category, setCategory] = useState<string>('all');
|
|
206
|
-
|
|
207
|
-
const filtered = useMemo(() => {
|
|
208
|
-
return products.filter(p => {
|
|
209
|
-
const matchesSearch = p.name.toLowerCase().includes(search.toLowerCase());
|
|
210
|
-
const matchesCategory = category === 'all' || p.category === category;
|
|
211
|
-
return matchesSearch && matchesCategory;
|
|
212
|
-
});
|
|
213
|
-
}, [products, search, category]);
|
|
214
|
-
|
|
215
|
-
return { filtered, search, setSearch, category, setCategory };
|
|
216
|
-
}
|
|
217
|
-
```
|
|
218
|
-
|
|
219
|
-
### Validation → lib/validations/
|
|
220
|
-
**Responsabilità:** Zod schemas, validation rules
|
|
221
|
-
```typescript
|
|
222
|
-
// lib/validations/product.schema.ts
|
|
223
|
-
import { z } from 'zod';
|
|
224
|
-
|
|
225
|
-
export const productSchema = z.object({
|
|
226
|
-
name: z.string().min(3).max(100),
|
|
227
|
-
price: z.number().positive(),
|
|
228
|
-
category: z.enum(['electronics', 'clothing', 'food']),
|
|
229
|
-
description: z.string().max(500).optional()
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
export type ProductInput = z.infer<typeof productSchema>;
|
|
233
|
-
```
|
|
234
|
-
|
|
235
|
-
---
|
|
236
|
-
|
|
237
|
-
## 4. REFACTORING MODE
|
|
238
|
-
|
|
239
|
-
### Auto-Trigger Refactoring quando:
|
|
240
|
-
- File pagina >50 righe
|
|
241
|
-
- File componente >150 righe
|
|
242
|
-
- Più di 1 await non in Promise.all
|
|
243
|
-
- Try/catch in pagina (usa error.tsx)
|
|
244
|
-
- Logica trasformazione dati in pagina (.map/.filter con >1 riga)
|
|
245
|
-
- Duplicazione codice (>2 occorrenze stesso pattern)
|
|
246
|
-
|
|
247
|
-
### Processo Refactoring (3 Fasi)
|
|
248
|
-
|
|
249
|
-
#### FASE 1: AUDIT
|
|
250
|
-
```markdown
|
|
251
|
-
Analizza file e identifica:
|
|
252
|
-
|
|
253
|
-
🔴 CRITICAL (Refactoring obbligatorio):
|
|
254
|
-
- [ ] Mixing concerns (fetch + validation + UI stesso file)
|
|
255
|
-
- [ ] Logica business in componenti UI
|
|
256
|
-
- [ ] File >200 righe
|
|
257
|
-
- [ ] Funzioni >50 righe
|
|
258
|
-
- [ ] `any` types presenti
|
|
259
|
-
- [ ] Prop drilling >3 livelli
|
|
260
|
-
|
|
261
|
-
🟡 WARNINGS (Miglioramento consigliato):
|
|
262
|
-
- [ ] Codice duplicato (>2 occorrenze)
|
|
263
|
-
- [ ] Nomi generici (data, item, temp)
|
|
264
|
-
- [ ] Magic numbers/strings
|
|
265
|
-
- [ ] Nessun error handling
|
|
266
|
-
|
|
267
|
-
Genera report: problemi + metriche + priorità
|
|
268
|
-
```
|
|
269
|
-
|
|
270
|
-
#### FASE 2: PLAN
|
|
271
|
-
```markdown
|
|
272
|
-
Proponi struttura target:
|
|
273
|
-
|
|
274
|
-
Before (PROBLEMA):
|
|
275
|
-
❌ app/products/page.tsx (250 righe)
|
|
276
|
-
├─ Fetch + trasformazioni
|
|
277
|
-
├─ Validazione
|
|
278
|
-
├─ Logica filtri
|
|
279
|
-
└─ UI rendering
|
|
280
|
-
|
|
281
|
-
After (SOLUZIONE):
|
|
282
|
-
✅ app/products/page.tsx (15 righe) - Orchestrazione
|
|
283
|
-
✅ lib/queries/product.queries.ts (60 righe) - Data access
|
|
284
|
-
✅ lib/utils/product.util.ts (30 righe) - Trasformazioni
|
|
285
|
-
✅ lib/parsers/product.parser.ts (20 righe) - Validation
|
|
286
|
-
✅ components/features/products/ProductGrid.tsx (80 righe) - UI
|
|
287
|
-
✅ lib/hooks/useProductFilters.ts (40 righe) - Client logic
|
|
288
|
-
```
|
|
289
|
-
|
|
290
|
-
#### FASE 3: REFACTOR (Step-by-Step)
|
|
291
|
-
```markdown
|
|
292
|
-
Implementa in questo ordine:
|
|
293
|
-
|
|
294
|
-
1. Types & Validation → types/ + validations/
|
|
295
|
-
2. Pure Utils → lib/utils/
|
|
296
|
-
3. Parsers → lib/parsers/
|
|
297
|
-
4. Queries → lib/queries/
|
|
298
|
-
5. Actions → lib/actions/
|
|
299
|
-
6. Services → lib/services/ (se necessario)
|
|
300
|
-
7. Hooks → lib/hooks/ (client logic)
|
|
301
|
-
8. Components → components/features/
|
|
302
|
-
9. Page Rebuild → app/[route]/page.tsx (minimalista)
|
|
303
|
-
10. Error/Loading → error.tsx, loading.tsx
|
|
304
|
-
|
|
305
|
-
Ogni step: commit separato, testabile incrementalmente
|
|
306
|
-
```
|
|
307
|
-
|
|
308
|
-
### SRP Compliance Score
|
|
309
|
-
```
|
|
310
|
-
Score = 100 - penalità
|
|
311
|
-
|
|
312
|
-
Penalità pagine:
|
|
313
|
-
- Righe codice: -1 ogni 10 oltre 20
|
|
314
|
-
- Await multipli: -5 se >1 (non in Promise.all)
|
|
315
|
-
- Nesting depth: -10 ogni livello oltre 2
|
|
316
|
-
- Inline JSX: -2 ogni 10 righe oltre 30
|
|
317
|
-
- Try/catch: -15
|
|
318
|
-
- Transformazioni: -5 per ogni .map/.filter/.reduce
|
|
319
|
-
- Validazioni inline: -10
|
|
320
|
-
|
|
321
|
-
Target: Score ≥ 80
|
|
322
|
-
```
|
|
323
|
-
|
|
324
|
-
---
|
|
325
|
-
|
|
326
|
-
## 5. COMPONENT PATTERNS
|
|
327
|
-
|
|
328
|
-
### Server Components (Default)
|
|
329
|
-
```typescript
|
|
330
|
-
// ✅ Server Component - Fetch diretto
|
|
331
|
-
export default async function ProductPage({ params }: { params: { id: string } }) {
|
|
332
|
-
const product = await getProductById(params.id);
|
|
333
|
-
return <ProductDetail product={product} />;
|
|
334
|
-
}
|
|
335
|
-
```
|
|
336
|
-
|
|
337
|
-
### Client Components
|
|
338
|
-
```typescript
|
|
339
|
-
// ✅ Client Component - Solo quando necessario
|
|
340
|
-
'use client'
|
|
341
|
-
|
|
342
|
-
export function ProductFilters({ onFilterChange }: Props) {
|
|
343
|
-
const [search, setSearch] = useState('');
|
|
344
|
-
// Interattività, hooks, browser APIs
|
|
345
|
-
}
|
|
346
|
-
```
|
|
347
|
-
|
|
348
|
-
### Composition Over Configuration
|
|
349
|
-
```typescript
|
|
350
|
-
// ✅ Preferisci
|
|
351
|
-
<Card>
|
|
352
|
-
<CardHeader>{title}</CardHeader>
|
|
353
|
-
<CardContent>{children}</CardContent>
|
|
354
|
-
</Card>
|
|
355
|
-
|
|
356
|
-
// ❌ Evita props drilling
|
|
357
|
-
<Card title={title} content={content} footer={footer} />
|
|
358
|
-
```
|
|
359
|
-
|
|
360
|
-
---
|
|
361
|
-
|
|
362
|
-
## 6. TYPESCRIPT STRICT
|
|
363
|
-
```typescript
|
|
364
|
-
// ✅ Type safety completo
|
|
365
|
-
interface UserCardProps {
|
|
366
|
-
user: User;
|
|
367
|
-
onEdit?: (id: string) => Promise<void>;
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
export function UserCard({ user, onEdit }: UserCardProps) {
|
|
371
|
-
// Implementation
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
// ❌ Evita any
|
|
375
|
-
function Component({ data }: { data: any }) { ... }
|
|
376
|
-
|
|
377
|
-
// ✅ Usa unknown + narrowing se tipo incerto
|
|
378
|
-
function processData(data: unknown) {
|
|
379
|
-
if (typeof data === 'object' && data !== null && 'id' in data) {
|
|
380
|
-
// Safe to use data.id
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
```
|
|
384
|
-
|
|
385
|
-
---
|
|
386
|
-
|
|
387
|
-
## 7. PERFORMANCE
|
|
388
|
-
```typescript
|
|
389
|
-
// ✅ Dynamic imports
|
|
390
|
-
const HeavyChart = dynamic(() => import('./HeavyChart'), {
|
|
391
|
-
loading: () => <Skeleton />,
|
|
392
|
-
ssr: false
|
|
393
|
-
});
|
|
394
|
-
|
|
395
|
-
// ✅ Parallel data fetching
|
|
396
|
-
const [products, categories] = await Promise.all([
|
|
397
|
-
getProducts(),
|
|
398
|
-
getCategories()
|
|
399
|
-
]);
|
|
400
|
-
|
|
401
|
-
// ✅ Image optimization
|
|
402
|
-
import Image from 'next/image';
|
|
403
|
-
<Image src={src} alt={alt} width={500} height={300} />
|
|
404
|
-
|
|
405
|
-
// ❌ Mai
|
|
406
|
-
<img src={src} alt={alt} />
|
|
407
|
-
```
|
|
408
|
-
|
|
409
|
-
---
|
|
410
|
-
|
|
411
|
-
## 8. ERROR HANDLING
|
|
412
|
-
```typescript
|
|
413
|
-
// ✅ Error boundaries dedicated
|
|
414
|
-
// app/products/error.tsx
|
|
415
|
-
'use client'
|
|
416
|
-
|
|
417
|
-
export default function Error({ error, reset }: {
|
|
418
|
-
error: Error;
|
|
419
|
-
reset: () => void;
|
|
420
|
-
}) {
|
|
421
|
-
return <ErrorBoundary error={error} onReset={reset} />;
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
// ✅ Safe data access
|
|
425
|
-
const userName = user?.profile?.name ?? 'Guest';
|
|
426
|
-
|
|
427
|
-
// ✅ Server Action error handling
|
|
428
|
-
export async function createUser(formData: FormData) {
|
|
429
|
-
const parsed = userSchema.safeParse(Object.fromEntries(formData));
|
|
430
|
-
if (!parsed.success) {
|
|
431
|
-
return { error: parsed.error.format() };
|
|
432
|
-
}
|
|
433
|
-
// ... resto logica
|
|
434
|
-
}
|
|
435
|
-
```
|
|
436
|
-
|
|
437
|
-
---
|
|
438
|
-
|
|
439
|
-
## 9. OUTPUT STANDARDS
|
|
440
|
-
|
|
441
|
-
Quando generi codice, fornisci sempre:
|
|
442
|
-
|
|
443
|
-
1. **File Path** completo
|
|
444
|
-
2. **Imports** ordinati (external → @/ → relative)
|
|
445
|
-
3. **Types/Interfaces** prima dell'implementazione
|
|
446
|
-
4. **Implementation** con commenti su logica complessa
|
|
447
|
-
5. **Usage Example** se non ovvio
|
|
448
|
-
```typescript
|
|
449
|
-
// File: lib/queries/user.queries.ts
|
|
450
|
-
|
|
451
|
-
import { db } from '@/lib/db';
|
|
452
|
-
import type { User } from '@/types/user.types';
|
|
453
|
-
|
|
454
|
-
/**
|
|
455
|
-
* Recupera utenti attivi con i loro profili
|
|
456
|
-
* @returns Array di utenti con relazioni caricate
|
|
457
|
-
*/
|
|
458
|
-
export async function getActiveUsers(): Promise<User[]> {
|
|
459
|
-
return await db.user.findMany({
|
|
460
|
-
where: { status: 'active' },
|
|
461
|
-
include: { profile: true }
|
|
462
|
-
});
|
|
463
|
-
}
|
|
464
|
-
```
|
|
465
|
-
|
|
466
|
-
---
|
|
467
|
-
|
|
468
|
-
## 10. PRE-DELIVERY CHECKLIST
|
|
469
|
-
|
|
470
|
-
Prima di consegnare codice, verifica:
|
|
471
|
-
|
|
472
|
-
**TypeScript:**
|
|
473
|
-
- [ ] Zero errori TS
|
|
474
|
-
- [ ] Zero `any` (usa `unknown` se necessario)
|
|
475
|
-
- [ ] Tutte props/functions tipizzate
|
|
476
|
-
|
|
477
|
-
**Architecture:**
|
|
478
|
-
- [ ] Server Components dove possibile
|
|
479
|
-
- [ ] Pagine <20 righe (solo orchestrazione)
|
|
480
|
-
- [ ] Zero logica business in componenti UI
|
|
481
|
-
- [ ] File <150 righe (split se necessario)
|
|
482
|
-
|
|
483
|
-
**Code Quality:**
|
|
484
|
-
- [ ] Import ordinati (external → @ → relative)
|
|
485
|
-
- [ ] Naming conventions rispettate
|
|
486
|
-
- [ ] Single responsibility rispettata
|
|
487
|
-
- [ ] Zero codice duplicato
|
|
488
|
-
- [ ] Nomi espliciti (no `data`, `temp`, `x`)
|
|
489
|
-
|
|
490
|
-
**Safety:**
|
|
491
|
-
- [ ] Error handling presente
|
|
492
|
-
- [ ] Safe data access (optional chaining)
|
|
493
|
-
- [ ] Input validation (Zod per form/API)
|
|
494
|
-
- [ ] No magic numbers/strings
|
|
495
|
-
|
|
496
|
-
**Performance:**
|
|
497
|
-
- [ ] `next/image` per immagini
|
|
498
|
-
- [ ] Dynamic imports per componenti pesanti
|
|
499
|
-
- [ ] Parallel fetch con Promise.all
|
|
500
|
-
|
|
501
|
-
---
|
|
502
|
-
|
|
503
|
-
## CONTEXT LOADING (Per documentazione dettagliata)
|
|
504
|
-
|
|
505
|
-
Se task coinvolge:
|
|
506
|
-
- **Refactoring pagine** → Rileggi questa sezione 3
|
|
507
|
-
- **Separation of concerns** → Rileggi sezione 3 + esempi
|
|
508
|
-
- **Performance optimization** → Focus su sezione 7
|
|
509
|
-
- **Error handling** → Focus su sezione 8
|
|
510
|
-
|
|
511
|
-
---
|
|
512
|
-
|
|
513
|
-
## QUICK REFERENCE
|
|
514
|
-
```typescript
|
|
515
|
-
// ✅ PAGINA IDEALE
|
|
516
|
-
export default async function Page() {
|
|
517
|
-
const data = await getData();
|
|
518
|
-
return <Feature data={data} />;
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
// ✅ SERVER ACTION
|
|
522
|
-
'use server'
|
|
523
|
-
export async function createItem(formData: FormData) {
|
|
524
|
-
const parsed = schema.safeParse(...);
|
|
525
|
-
if (!parsed.success) return { error: ... };
|
|
526
|
-
const item = await db.create(...);
|
|
527
|
-
revalidatePath('/items');
|
|
528
|
-
return { success: true, item };
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
// ✅ CUSTOM HOOK
|
|
532
|
-
'use client'
|
|
533
|
-
export function useFilters<T>(items: T[], filterFn: (item: T) => boolean) {
|
|
534
|
-
const [filtered, setFiltered] = useState(items);
|
|
535
|
-
// Logic...
|
|
536
|
-
return { filtered, /* ... */ };
|
|
537
|
-
}
|
|
538
|
-
```
|
|
539
|
-
|
|
540
|
-
---
|
|
541
|
-
|
|
542
|
-
**Versione:** 2.0
|
|
543
|
-
**Focus:** SRP + Separation of Concerns + Context Efficiency
|