@matteoaliano/forest-ui 0.4.0 → 0.4.1

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.
@@ -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