feat: adiciona 12 plugins Descomplicar ao marketplace
Plugins: automacao, crm-ops, design-media, dev-tools, gestao, infraestrutura, marketing, negocio, perfex-dev, project-manager, wordpress + hello-plugin (existente). Totais: 83 skills, 44 agents, 12 datasets.json Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
250
dev-tools/skills/db-design/SKILL.md
Normal file
250
dev-tools/skills/db-design/SKILL.md
Normal file
@@ -0,0 +1,250 @@
|
||||
---
|
||||
name: db-design
|
||||
description: Database schema design and optimization. Creates normalized schemas,
|
||||
indexes, and migration plans. Use when user mentions "database design", "schema",
|
||||
"modelo dados", "database optimization", "sql design".
|
||||
author: Descomplicar® Crescimento Digital
|
||||
version: 1.0.0
|
||||
quality_score: 75
|
||||
user_invocable: true
|
||||
desk_task: 1469
|
||||
---
|
||||
|
||||
# Database Design Specialist
|
||||
|
||||
Skill para design e optimização de bases de dados seguindo padrões Descomplicar®.
|
||||
|
||||
## Quando Usar
|
||||
|
||||
- Desenhar schema para novo sistema
|
||||
- Optimizar queries lentas
|
||||
- Criar migrations (Laravel, Doctrine, Raw SQL)
|
||||
- Auditar e optimizar índices
|
||||
- Planear partitioning/sharding
|
||||
|
||||
## Protocolo Obrigatório
|
||||
|
||||
### 1. Pesquisa Inicial
|
||||
```
|
||||
mcp__memory-supabase__search_memories "database [projecto]"
|
||||
mcp__wikijs__search_pages "database schema [sistema]"
|
||||
```
|
||||
|
||||
### 2. Verificar Schema Existente
|
||||
Antes de propor alterações, SEMPRE verificar estrutura actual.
|
||||
|
||||
## Princípios de Design
|
||||
|
||||
### Normalização
|
||||
- **OLTP**: Mínimo 3NF (Third Normal Form)
|
||||
- **OLAP/Reporting**: Desnormalização justificada
|
||||
- **Híbrido**: Tabelas normalizadas + views materializadas
|
||||
|
||||
### Naming Conventions
|
||||
```sql
|
||||
-- Tabelas: snake_case, plural
|
||||
users, order_items, product_categories
|
||||
|
||||
-- Colunas: snake_case, singular
|
||||
created_at, user_id, is_active
|
||||
|
||||
-- Primary keys
|
||||
id (auto-increment ou UUID)
|
||||
|
||||
-- Foreign keys: {tabela_singular}_id
|
||||
user_id, order_id, category_id
|
||||
|
||||
-- Índices: idx_{tabela}_{coluna(s)}
|
||||
idx_orders_customer_id
|
||||
idx_orders_created_at_status
|
||||
```
|
||||
|
||||
## Performance
|
||||
|
||||
### Índices Estratégicos
|
||||
```sql
|
||||
-- WHERE frequente
|
||||
CREATE INDEX idx_users_email ON users(email);
|
||||
|
||||
-- JOIN
|
||||
CREATE INDEX idx_orders_customer_id ON orders(customer_id);
|
||||
|
||||
-- ORDER BY
|
||||
CREATE INDEX idx_products_created_at ON products(created_at DESC);
|
||||
|
||||
-- Composto para queries específicas
|
||||
CREATE INDEX idx_orders_status_date ON orders(status, created_at);
|
||||
```
|
||||
|
||||
### Análise de Queries
|
||||
```sql
|
||||
-- SEMPRE usar EXPLAIN antes de optimizar
|
||||
EXPLAIN ANALYZE
|
||||
SELECT c.name, COUNT(o.id) as total
|
||||
FROM customers c
|
||||
LEFT JOIN orders o ON o.customer_id = c.id
|
||||
WHERE o.created_at >= '2025-01-01'
|
||||
GROUP BY c.id
|
||||
ORDER BY total DESC
|
||||
LIMIT 100;
|
||||
```
|
||||
|
||||
### Evitar Anti-patterns
|
||||
```sql
|
||||
-- ❌ EVITAR
|
||||
SELECT * FROM orders; -- Usar colunas específicas
|
||||
SELECT ... WHERE YEAR(created_at) = 2025; -- Função impede uso de índice
|
||||
|
||||
-- ✅ PREFERIR
|
||||
SELECT id, customer_id, total FROM orders;
|
||||
SELECT ... WHERE created_at >= '2025-01-01' AND created_at < '2026-01-01';
|
||||
```
|
||||
|
||||
## Template Migration Laravel
|
||||
|
||||
```php
|
||||
<?php
|
||||
/**
|
||||
* Migration: Create subscriptions table
|
||||
*
|
||||
* @author Descomplicar® Crescimento Digital
|
||||
* @link https://descomplicar.pt
|
||||
* @copyright 2025 Descomplicar®
|
||||
*/
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('subscriptions', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
|
||||
$table->foreignId('plan_id')->constrained();
|
||||
$table->enum('status', ['active', 'cancelled', 'expired', 'trial']);
|
||||
$table->timestamp('trial_ends_at')->nullable();
|
||||
$table->timestamp('current_period_start');
|
||||
$table->timestamp('current_period_end');
|
||||
$table->timestamp('cancelled_at')->nullable();
|
||||
$table->timestamps();
|
||||
|
||||
// Índices
|
||||
$table->index(['user_id', 'status']);
|
||||
$table->index('current_period_end');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('subscriptions');
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## Segurança
|
||||
|
||||
- **Prepared statements**: SEMPRE para queries com input
|
||||
- **Least privilege**: Utilizadores DB com permissões mínimas
|
||||
- **Audit logging**: Para tabelas com dados sensíveis
|
||||
- **Encriptação**: Colunas com PII devem ser encriptadas
|
||||
|
||||
## Checklist Schema
|
||||
|
||||
### Design
|
||||
- [ ] Normalização adequada (sem redundância desnecessária)
|
||||
- [ ] Foreign keys definidas
|
||||
- [ ] Tipos de dados apropriados
|
||||
- [ ] Constraints (NOT NULL, UNIQUE, CHECK)
|
||||
- [ ] Soft deletes onde apropriado (deleted_at)
|
||||
|
||||
### Performance
|
||||
- [ ] Índices em colunas de WHERE
|
||||
- [ ] Índices em colunas de JOIN
|
||||
- [ ] Índices compostos onde necessário
|
||||
- [ ] Evitados índices redundantes
|
||||
- [ ] EXPLAIN em queries críticas
|
||||
|
||||
### Segurança
|
||||
- [ ] Dados sensíveis identificados
|
||||
- [ ] Audit trail se necessário
|
||||
- [ ] Prepared statements em todo código
|
||||
|
||||
## Entregáveis Standard
|
||||
|
||||
1. DDL completo (CREATE TABLE, INDEX)
|
||||
2. Diagrama ER se schema complexo
|
||||
3. Migrations se framework especificado
|
||||
4. Queries exemplo para operações comuns
|
||||
5. EXPLAIN de queries críticas
|
||||
|
||||
---
|
||||
|
||||
## Datasets Dify (Consulta Obrigatória)
|
||||
|
||||
Em caso de dúvidas ou para aprofundar conhecimento, consultar os seguintes datasets via MCP:
|
||||
|
||||
| Dataset | ID | Prioridade |
|
||||
|---------|----|-----------:|
|
||||
| **TI (Tecnologia da Informação)** | `7f63ec0c-6321-488c-b107-980140199850` | 1 |
|
||||
| **Desenvolvimento de Software** | `e7c7decc-0ded-4351-ab14-b110b3c38ec9` | 1 |
|
||||
| **AWS (Amazon Web Services)** | `cc7f000a-ad86-49b6-b59b-179e65f8a229` | 2 |
|
||||
|
||||
### Como Consultar
|
||||
|
||||
```javascript
|
||||
// Pesquisar optimização MySQL
|
||||
mcp__dify-kb__dify_kb_retrieve_segments({
|
||||
dataset_id: "7f63ec0c-6321-488c-b107-980140199850",
|
||||
query: "mysql index optimization slow query"
|
||||
})
|
||||
|
||||
// Padrões de design de bases de dados
|
||||
mcp__dify-kb__dify_kb_retrieve_segments({
|
||||
dataset_id: "e7c7decc-0ded-4351-ab14-b110b3c38ec9",
|
||||
query: "database schema design patterns"
|
||||
})
|
||||
|
||||
// RDS e bases de dados AWS
|
||||
mcp__dify-kb__dify_kb_retrieve_segments({
|
||||
dataset_id: "cc7f000a-ad86-49b6-b59b-179e65f8a229",
|
||||
query: "RDS aurora postgresql"
|
||||
})
|
||||
```
|
||||
|
||||
### Quando Consultar
|
||||
|
||||
- Antes de desenhar novo schema
|
||||
- Ao optimizar queries lentas
|
||||
- Para escolher tipo de índice adequado
|
||||
- Decidir entre MySQL/PostgreSQL
|
||||
- Configurar bases de dados em cloud
|
||||
|
||||
---
|
||||
**Versão**: 1.0.0 | **Autor**: Descomplicar®
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Quando NÃO Usar
|
||||
|
||||
- Para tarefas fora do domínio de especialização desta skill
|
||||
- Quando outra skill mais específica está disponível
|
||||
- Para operações que requerem confirmação manual do utilizador
|
||||
|
||||
|
||||
## Exemplos
|
||||
|
||||
### Exemplo 1: Uso Básico
|
||||
```
|
||||
Input: [descrição da tarefa]
|
||||
Output: [resultado esperado]
|
||||
```
|
||||
|
||||
### Exemplo 2: Uso Avançado
|
||||
```
|
||||
Input: [caso complexo]
|
||||
Output: [resultado detalhado]
|
||||
```
|
||||
1
dev-tools/skills/frontend-design
Symbolic link
1
dev-tools/skills/frontend-design
Symbolic link
@@ -0,0 +1 @@
|
||||
/home/ealmeida/.claude/plugins/anthropic-claude-code/plugins/frontend-design/skills/frontend-design
|
||||
722
dev-tools/skills/nextjs/SKILL.md
Normal file
722
dev-tools/skills/nextjs/SKILL.md
Normal file
@@ -0,0 +1,722 @@
|
||||
---
|
||||
name: nextjs
|
||||
description: Next.js development best practices and patterns. App router, server components,
|
||||
API routes, and deployment. Use when user mentions "nextjs", "next.js", "react server",
|
||||
"app router", "next deployment".
|
||||
author: Descomplicar® Crescimento Digital
|
||||
version: 2.0.0
|
||||
quality_score: 75
|
||||
user_invocable: true
|
||||
allowed-tools: Glob
|
||||
---
|
||||
|
||||
# /nextjs - Next.js Development
|
||||
|
||||
Desenvolvimento Next.js moderno (13+) com App Router e Server Components.
|
||||
|
||||
## Quando Usar
|
||||
|
||||
- Criar aplicações Next.js
|
||||
- Migrar de Pages para App Router
|
||||
- Implementar Server Components
|
||||
- Configurar Server Actions
|
||||
- Optimizar SEO e performance
|
||||
|
||||
## App Router Structure
|
||||
|
||||
```
|
||||
app/
|
||||
├── layout.tsx # Root layout (obrigatório)
|
||||
├── page.tsx # Home page
|
||||
├── loading.tsx # Loading UI (Suspense)
|
||||
├── error.tsx # Error boundary
|
||||
├── not-found.tsx # 404 page
|
||||
├── globals.css # Global styles
|
||||
├── (auth)/ # Route group (não afecta URL)
|
||||
│ ├── login/page.tsx
|
||||
│ └── register/page.tsx
|
||||
├── dashboard/
|
||||
│ ├── layout.tsx # Nested layout
|
||||
│ ├── page.tsx
|
||||
│ └── [id]/
|
||||
│ └── page.tsx # Dynamic route
|
||||
└── api/
|
||||
└── route.ts # API route
|
||||
```
|
||||
|
||||
## Server vs Client Components
|
||||
|
||||
### Server Component (default)
|
||||
|
||||
```tsx
|
||||
// ✅ PADRÃO - Runs on server
|
||||
async function ProductsPage() {
|
||||
// Pode fazer fetch directo
|
||||
const products = await db.product.findMany();
|
||||
|
||||
return (
|
||||
<div>
|
||||
{products.map(p => (
|
||||
<ProductCard key={p.id} product={p} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ProductsPage;
|
||||
```
|
||||
|
||||
**Vantagens:**
|
||||
- Acesso directo a BD
|
||||
- Menos JavaScript no cliente
|
||||
- SEO melhor
|
||||
- Dados sempre frescos
|
||||
|
||||
### Client Component
|
||||
|
||||
```tsx
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
|
||||
// ✅ Para interactividade
|
||||
function Counter() {
|
||||
const [count, setCount] = useState(0);
|
||||
|
||||
return (
|
||||
<button onClick={() => setCount(c => c + 1)}>
|
||||
{count}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
**Quando usar:**
|
||||
- useState, useEffect, useContext
|
||||
- Event listeners (onClick, onChange)
|
||||
- Browser APIs (localStorage, window)
|
||||
- Custom hooks
|
||||
|
||||
## Data Fetching Patterns
|
||||
|
||||
### 1. Static (SSG)
|
||||
|
||||
```tsx
|
||||
// Default behaviour - cache infinito
|
||||
async function Page() {
|
||||
const data = await fetch('https://api.example.com/data', {
|
||||
cache: 'force-cache' // Explícito (mas é default)
|
||||
});
|
||||
|
||||
return <div>{data.title}</div>;
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Dynamic (SSR)
|
||||
|
||||
```tsx
|
||||
// Sempre fetch fresco
|
||||
async function Page() {
|
||||
const data = await fetch('https://api.example.com/data', {
|
||||
cache: 'no-store' // NUNCA cache
|
||||
});
|
||||
|
||||
return <div>{data.title}</div>;
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Revalidate (ISR)
|
||||
|
||||
```tsx
|
||||
// Cache com revalidação automática
|
||||
async function Page() {
|
||||
const data = await fetch('https://api.example.com/data', {
|
||||
next: { revalidate: 60 } // Revalida a cada 60s
|
||||
});
|
||||
|
||||
return <div>{data.title}</div>;
|
||||
}
|
||||
```
|
||||
|
||||
## Server Actions
|
||||
|
||||
```tsx
|
||||
// app/actions.ts
|
||||
'use server';
|
||||
|
||||
import { revalidatePath } from 'next/cache';
|
||||
import { db } from '@/lib/db';
|
||||
|
||||
export async function createPost(formData: FormData) {
|
||||
const title = formData.get('title') as string;
|
||||
const content = formData.get('content') as string;
|
||||
|
||||
await db.post.create({
|
||||
data: { title, content }
|
||||
});
|
||||
|
||||
revalidatePath('/posts');
|
||||
}
|
||||
|
||||
// app/create-post/page.tsx
|
||||
import { createPost } from '@/app/actions';
|
||||
|
||||
export default function CreatePost() {
|
||||
return (
|
||||
<form action={createPost}>
|
||||
<input name="title" required />
|
||||
<textarea name="content" required />
|
||||
<button type="submit">Create</button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Layouts e Loading States
|
||||
|
||||
### Root Layout
|
||||
|
||||
```tsx
|
||||
// app/layout.tsx
|
||||
import './globals.css';
|
||||
import { Inter } from 'next/font/google';
|
||||
|
||||
const inter = Inter({ subsets: ['latin'] });
|
||||
|
||||
export const metadata = {
|
||||
title: 'My App',
|
||||
description: 'Description'
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<html lang="pt">
|
||||
<body className={inter.className}>
|
||||
<nav>...</nav>
|
||||
{children}
|
||||
<footer>...</footer>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Loading UI
|
||||
|
||||
```tsx
|
||||
// app/dashboard/loading.tsx
|
||||
export default function Loading() {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
|
||||
// Wrapper automático em <Suspense>
|
||||
```
|
||||
|
||||
### Error Handling
|
||||
|
||||
```tsx
|
||||
// app/error.tsx
|
||||
'use client';
|
||||
|
||||
export default function Error({
|
||||
error,
|
||||
reset
|
||||
}: {
|
||||
error: Error;
|
||||
reset: () => void;
|
||||
}) {
|
||||
return (
|
||||
<div>
|
||||
<h2>Something went wrong!</h2>
|
||||
<button onClick={reset}>Try again</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Routing Avançado
|
||||
|
||||
### Parallel Routes
|
||||
|
||||
```tsx
|
||||
// app/layout.tsx
|
||||
export default function Layout({
|
||||
children,
|
||||
modal,
|
||||
sidebar
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
modal: React.ReactNode;
|
||||
sidebar: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
{sidebar}
|
||||
{children}
|
||||
{modal}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
// app/@sidebar/page.tsx
|
||||
// app/@modal/page.tsx
|
||||
```
|
||||
|
||||
### Intercepting Routes
|
||||
|
||||
```tsx
|
||||
// app/feed/page.tsx - Lista de fotos
|
||||
// app/photo/[id]/page.tsx - Página completa da foto
|
||||
// app/@modal/(.)photo/[id]/page.tsx - Modal da foto (intercepta)
|
||||
|
||||
// Navegar via <Link> abre modal
|
||||
// Refresh ou URL directo abre página completa
|
||||
```
|
||||
|
||||
## Middleware
|
||||
|
||||
```tsx
|
||||
// middleware.ts
|
||||
import { NextResponse } from 'next/server';
|
||||
import type { NextRequest } from 'next/server';
|
||||
|
||||
export function middleware(request: NextRequest) {
|
||||
const token = request.cookies.get('token');
|
||||
|
||||
// Auth guard
|
||||
if (!token && request.nextUrl.pathname.startsWith('/dashboard')) {
|
||||
return NextResponse.redirect(new URL('/login', request.url));
|
||||
}
|
||||
|
||||
// Custom header
|
||||
const response = NextResponse.next();
|
||||
response.headers.set('x-custom-header', 'value');
|
||||
return response;
|
||||
}
|
||||
|
||||
export const config = {
|
||||
matcher: ['/dashboard/:path*', '/api/:path*']
|
||||
};
|
||||
```
|
||||
|
||||
## Performance Rules (Vercel Engineering)
|
||||
|
||||
57 regras priorizadas por impacto. Fonte: Vercel Engineering react-best-practices.
|
||||
|
||||
### 1. Eliminating Waterfalls (CRITICAL)
|
||||
|
||||
```tsx
|
||||
// ❌ ERRADO: Awaits sequenciais (waterfall)
|
||||
const session = await getSession()
|
||||
const config = await getConfig()
|
||||
const user = await getUser(session.userId)
|
||||
|
||||
// ✅ CORRECTO: Paralelo com Promise.all()
|
||||
const [session, config] = await Promise.all([
|
||||
getSession(),
|
||||
getConfig()
|
||||
])
|
||||
const user = await getUser(session.userId) // depende de session
|
||||
|
||||
// ✅ CORRECTO: Suspense boundaries estratégicos
|
||||
async function Page() {
|
||||
return (
|
||||
<Layout>
|
||||
<Suspense fallback={<HeaderSkeleton />}>
|
||||
<Header />
|
||||
</Suspense>
|
||||
<Suspense fallback={<ContentSkeleton />}>
|
||||
<SlowContent />
|
||||
</Suspense>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
|
||||
// ✅ CORRECTO: Defer await até necessário
|
||||
async function handler(req) {
|
||||
const dataPromise = fetchData() // inicia imediatamente
|
||||
if (req.method === 'HEAD') return new Response() // early return sem await
|
||||
const data = await dataPromise // só espera quando precisa
|
||||
return Response.json(data)
|
||||
}
|
||||
```
|
||||
|
||||
**Regras:**
|
||||
- `async-defer-await`: Mover await para onde é realmente necessário
|
||||
- `async-dependency-parallel`: Usar `better-all` para dependências parciais
|
||||
- `async-prevent-waterfall`: Iniciar promises antes de awaitar
|
||||
- `async-promise-all`: `Promise.all()` para operações independentes
|
||||
- `async-suspense-boundaries`: Suspense para UI progressiva
|
||||
|
||||
### 2. Bundle Size (CRITICAL)
|
||||
|
||||
```tsx
|
||||
// ❌ ERRADO: Barrel file imports
|
||||
import { Button, Icon } from '@mui/material' // carrega tudo
|
||||
|
||||
// ✅ CORRECTO: Import directo
|
||||
import Button from '@mui/material/Button'
|
||||
|
||||
// ✅ CORRECTO: optimizePackageImports (Next.js 13.5+)
|
||||
// next.config.js
|
||||
module.exports = {
|
||||
experimental: {
|
||||
optimizePackageImports: ['lucide-react', '@mui/material', 'lodash']
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ CORRECTO: Dynamic imports para componentes pesados
|
||||
const MonacoEditor = dynamic(() => import('./MonacoEditor'), {
|
||||
ssr: false,
|
||||
loading: () => <EditorSkeleton />
|
||||
})
|
||||
|
||||
// ✅ CORRECTO: Preload em hover/focus
|
||||
function FeatureButton() {
|
||||
return (
|
||||
<button
|
||||
onMouseEnter={() => import('./HeavyFeature')}
|
||||
onClick={openFeature}
|
||||
>
|
||||
Open Feature
|
||||
</button>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
**Regras:**
|
||||
- `bundle-barrel-imports`: Evitar barrel files (index.js re-exports)
|
||||
- `bundle-conditional-loading`: Carregar módulos só quando feature activa
|
||||
- `bundle-defer-third-party`: `next/dynamic` com `ssr: false` para analytics
|
||||
- `bundle-dynamic-imports`: Lazy-load componentes pesados (>50KB)
|
||||
- `bundle-preload-intent`: Preload em hover para UX instantânea
|
||||
|
||||
### 3. Server-Side Performance (HIGH)
|
||||
|
||||
```tsx
|
||||
// ✅ Server Actions: autenticar SEMPRE (são endpoints públicos!)
|
||||
'use server'
|
||||
async function updateProfile(formData: FormData) {
|
||||
const session = await auth() // 1. Autenticar
|
||||
if (!session) throw new Error('Unauthorized')
|
||||
const data = schema.parse(formData) // 2. Validar input
|
||||
await db.user.update({ where: { id: session.userId }, data }) // 3. Executar
|
||||
revalidatePath('/profile')
|
||||
}
|
||||
|
||||
// ✅ Minimizar serialização RSC - só campos necessários
|
||||
async function UserCard({ userId }: { userId: string }) {
|
||||
const user = await db.user.findUnique({ where: { id: userId } })
|
||||
// Passa só o necessário, não o objecto inteiro com 50+ campos
|
||||
return <ClientCard name={user.name} avatar={user.avatar} />
|
||||
}
|
||||
|
||||
// ✅ React.cache() para deduplicação per-request
|
||||
const getUser = cache(async (id: string) => {
|
||||
return db.user.findUnique({ where: { id } })
|
||||
})
|
||||
|
||||
// ✅ after() para operações não-bloqueantes
|
||||
import { after } from 'next/server'
|
||||
async function submitForm(data: FormData) {
|
||||
const result = await processForm(data)
|
||||
after(async () => {
|
||||
await logAnalytics(result) // executa APÓS resposta enviada
|
||||
})
|
||||
return result
|
||||
}
|
||||
```
|
||||
|
||||
**Regras:**
|
||||
- `server-auth-actions`: Autenticar Server Actions como API routes
|
||||
- `server-avoid-duplicate-serialization`: Transformar dados no cliente
|
||||
- `server-lru-cache`: LRUCache para dados cross-request
|
||||
- `server-minimize-serialization`: Só campos necessários em RSC props
|
||||
- `server-parallel-composition`: Composição paralela de Server Components
|
||||
- `server-react-cache`: `React.cache()` para deduplicação per-request
|
||||
- `server-after`: `after()` para logging/analytics não-bloqueante
|
||||
|
||||
### 4. Client-Side Data Fetching (MEDIUM-HIGH)
|
||||
|
||||
```tsx
|
||||
// ✅ SWR para deduplicação automática
|
||||
const { data, isLoading } = useSWR('/api/user', fetcher)
|
||||
|
||||
// ✅ Passive event listeners para scroll performance
|
||||
element.addEventListener('scroll', handler, { passive: true })
|
||||
|
||||
// ✅ localStorage versionado com try-catch
|
||||
const key = 'userConfig:v2'
|
||||
try {
|
||||
const data = JSON.parse(localStorage.getItem(key) ?? '{}')
|
||||
} catch { /* incognito mode, quota exceeded */ }
|
||||
```
|
||||
|
||||
### 5. Re-render Optimization (MEDIUM)
|
||||
|
||||
```tsx
|
||||
// ✅ Derived state: calcular durante render, NÃO em useEffect
|
||||
function FilteredList({ items, query }: Props) {
|
||||
const filtered = items.filter(i => i.name.includes(query)) // derive!
|
||||
return <List items={filtered} />
|
||||
}
|
||||
|
||||
// ✅ Functional setState para evitar stale closures
|
||||
setItems(curr => [...curr, ...newItems]) // ✅
|
||||
// setItems([...items, ...newItems]) // ❌ stale closure
|
||||
|
||||
// ✅ Lazy state initialization
|
||||
const [index] = useState(() => buildSearchIndex(items)) // computa 1x
|
||||
|
||||
// ✅ useTransition para updates não-urgentes
|
||||
const [isPending, startTransition] = useTransition()
|
||||
startTransition(() => setSearchResults(results))
|
||||
|
||||
// ✅ Extrair defaults não-primitivos para module scope
|
||||
const EMPTY_ARRAY: string[] = [] // ✅ fora do componente
|
||||
const NOOP = () => {} // ✅ fora do componente
|
||||
function Component({ items = EMPTY_ARRAY, onClick = NOOP }) { ... }
|
||||
|
||||
// ✅ Dependências de effect: primitivos, não objectos
|
||||
useEffect(() => { fetchUser(userId) }, [userId]) // ✅ primitivo
|
||||
// useEffect(() => { ... }, [user]) // ❌ objecto = nova ref cada render
|
||||
```
|
||||
|
||||
### 6. Rendering Performance (MEDIUM)
|
||||
|
||||
```tsx
|
||||
// ✅ CSS content-visibility para listas longas
|
||||
// .message { content-visibility: auto; contain-intrinsic-size: 0 80px; }
|
||||
|
||||
// ✅ Hoist static JSX fora do componente
|
||||
const skeleton = <div className="skeleton h-4 w-full" /> // module scope
|
||||
|
||||
// ✅ Conditional rendering explícito (evitar && com falsy)
|
||||
{count > 0 ? <Badge>{count}</Badge> : null} // ✅
|
||||
// {count && <Badge>{count}</Badge>} // ❌ renderiza "0"
|
||||
|
||||
// ✅ useTransition em vez de useState manual para loading
|
||||
const [isPending, startTransition] = useTransition()
|
||||
// const [isLoading, setIsLoading] = useState(false) // ❌ manual
|
||||
|
||||
// ✅ Hydration: inline script para evitar flicker
|
||||
// <script dangerouslySetInnerHTML={{ __html: `
|
||||
// document.documentElement.classList.add(
|
||||
// localStorage.getItem('theme') === 'dark' ? 'dark' : 'light'
|
||||
// )
|
||||
// `}} />
|
||||
```
|
||||
|
||||
### 7. JavaScript Performance (LOW-MEDIUM)
|
||||
|
||||
```tsx
|
||||
// ✅ Index Maps para lookups repetidos O(1) vs O(n)
|
||||
const userMap = new Map(users.map(u => [u.id, u]))
|
||||
orders.map(o => ({ ...o, user: userMap.get(o.userId) }))
|
||||
|
||||
// ✅ Set para membership checks
|
||||
const allowedIds = new Set(['a', 'b', 'c'])
|
||||
if (allowedIds.has(id)) { ... } // O(1) vs .includes() O(n)
|
||||
|
||||
// ✅ toSorted() para imutabilidade (React-safe)
|
||||
const sorted = items.toSorted((a, b) => a.name.localeCompare(b.name))
|
||||
// items.sort() // ❌ muta o array original
|
||||
|
||||
// ✅ Early return para evitar processamento desnecessário
|
||||
function validate(items: Item[]) {
|
||||
for (const item of items) {
|
||||
if (!item.valid) return { error: item.id } // sai cedo
|
||||
}
|
||||
return { success: true }
|
||||
}
|
||||
|
||||
// ✅ Combinar iterações de array
|
||||
const { active, expired } = items.reduce((acc, item) => {
|
||||
if (item.active) acc.active.push(item)
|
||||
else acc.expired.push(item)
|
||||
return acc
|
||||
}, { active: [], expired: [] })
|
||||
// Em vez de items.filter(active) + items.filter(expired) = 2 iterações
|
||||
```
|
||||
|
||||
### 8. Advanced Patterns (LOW)
|
||||
|
||||
```tsx
|
||||
// ✅ Init uma vez, não por mount (Strict Mode remonta)
|
||||
let didInit = false
|
||||
function App() {
|
||||
useEffect(() => {
|
||||
if (didInit) return
|
||||
didInit = true
|
||||
initializeAnalytics()
|
||||
}, [])
|
||||
}
|
||||
|
||||
// ✅ useEffectEvent para callbacks estáveis em effects
|
||||
const onTick = useEffectEvent((value) => {
|
||||
console.log(value) // sempre valor mais recente
|
||||
})
|
||||
useEffect(() => {
|
||||
const id = setInterval(() => onTick(count), 1000)
|
||||
return () => clearInterval(id)
|
||||
}, []) // sem dependência de count!
|
||||
```
|
||||
|
||||
### Resumo de Impacto
|
||||
|
||||
| Prioridade | Categoria | Regras | Ganho |
|
||||
|---|---|---|---|
|
||||
| CRITICAL | Waterfalls | 5 | 2-10x latência |
|
||||
| CRITICAL | Bundle Size | 5 | 200-800ms load |
|
||||
| HIGH | Server-Side | 7 | RSC payload, TTI |
|
||||
| MEDIUM-HIGH | Client Fetch | 4 | Request dedup |
|
||||
| MEDIUM | Re-renders | 12 | UI responsiveness |
|
||||
| MEDIUM | Rendering | 9 | Paint performance |
|
||||
| LOW-MEDIUM | JS Perf | 12 | CPU cycles |
|
||||
| LOW | Advanced | 3 | Init correctness |
|
||||
|
||||
---
|
||||
|
||||
## Optimizações
|
||||
|
||||
### Images
|
||||
|
||||
```tsx
|
||||
import Image from 'next/image';
|
||||
|
||||
<Image
|
||||
src="/photo.jpg"
|
||||
width={500}
|
||||
height={300}
|
||||
alt="Photo"
|
||||
priority // LCP image
|
||||
placeholder="blur"
|
||||
blurDataURL="data:image/..."
|
||||
/>
|
||||
```
|
||||
|
||||
### Fonts
|
||||
|
||||
```tsx
|
||||
import { Inter, Roboto_Mono } from 'next/font/google';
|
||||
|
||||
const inter = Inter({
|
||||
subsets: ['latin'],
|
||||
display: 'swap',
|
||||
});
|
||||
|
||||
const robotoMono = Roboto_Mono({
|
||||
subsets: ['latin'],
|
||||
weight: ['400', '700'],
|
||||
});
|
||||
```
|
||||
|
||||
### Metadata (SEO)
|
||||
|
||||
```tsx
|
||||
// app/page.tsx
|
||||
export const metadata = {
|
||||
title: 'Home',
|
||||
description: 'Home page',
|
||||
openGraph: {
|
||||
title: 'Home',
|
||||
description: 'Home page',
|
||||
images: ['/og-image.jpg'],
|
||||
},
|
||||
twitter: {
|
||||
card: 'summary_large_image',
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
## Deployment
|
||||
|
||||
### Vercel (recomendado)
|
||||
|
||||
```bash
|
||||
npm install -g vercel
|
||||
vercel
|
||||
```
|
||||
|
||||
### Docker
|
||||
|
||||
```dockerfile
|
||||
FROM node:18-alpine AS builder
|
||||
WORKDIR /app
|
||||
COPY package*.json ./
|
||||
RUN npm ci
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
|
||||
FROM node:18-alpine
|
||||
WORKDIR /app
|
||||
COPY --from=builder /app/.next ./.next
|
||||
COPY --from=builder /app/public ./public
|
||||
COPY --from=builder /app/package*.json ./
|
||||
RUN npm ci --production
|
||||
EXPOSE 3000
|
||||
CMD ["npm", "start"]
|
||||
```
|
||||
|
||||
### Static Export
|
||||
|
||||
```js
|
||||
// next.config.js
|
||||
module.exports = {
|
||||
output: 'export',
|
||||
trailingSlash: true,
|
||||
};
|
||||
```
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
# Output: out/
|
||||
```
|
||||
|
||||
## Datasets Dify
|
||||
|
||||
| Dataset | ID | Prioridade |
|
||||
|---------|----|-----------:|
|
||||
| **Desenvolvimento de Software** | `e7c7decc-0ded-4351-ab14-b110b3c38ec9` | 1 |
|
||||
| **TI (Tecnologia da Informação)** | `7f63ec0c-6321-488c-b107-980140199850` | 1 |
|
||||
|
||||
---
|
||||
|
||||
**Versão**: 1.0.0 | **Autor**: Descomplicar®
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Quando NÃO Usar
|
||||
|
||||
- Para tarefas fora do domínio de especialização desta skill
|
||||
- Quando outra skill mais específica está disponível
|
||||
- Para operações que requerem confirmação manual do utilizador
|
||||
|
||||
|
||||
## Protocolo
|
||||
|
||||
1. Analisar requisitos da tarefa
|
||||
2. Verificar disponibilidade de ferramentas necessárias
|
||||
3. Executar operações de forma incremental
|
||||
4. Validar resultados antes de concluir
|
||||
5. Reportar status e próximos passos
|
||||
|
||||
|
||||
## Exemplos
|
||||
|
||||
### Exemplo 1: Uso Básico
|
||||
```
|
||||
Input: [descrição da tarefa]
|
||||
Output: [resultado esperado]
|
||||
```
|
||||
|
||||
### Exemplo 2: Uso Avançado
|
||||
```
|
||||
Input: [caso complexo]
|
||||
Output: [resultado detalhado]
|
||||
```
|
||||
370
dev-tools/skills/php-dev/SKILL.md
Normal file
370
dev-tools/skills/php-dev/SKILL.md
Normal file
@@ -0,0 +1,370 @@
|
||||
---
|
||||
name: php-dev
|
||||
description: >
|
||||
Modern PHP fullstack development with Laravel, Symfony, RESTful APIs and backend architecture. Creates APIs, implements authentication (JWT, OAuth, Sanctum), develops service classes and refactors legacy code following PSR standards.
|
||||
Use when developing PHP applications, creating REST APIs, implementing authentication, refactoring legacy code, or when user mentions
|
||||
"php", "laravel", "symfony", "rest api", "jwt", "oauth", "backend", "service layer", "repository pattern", "php 8".
|
||||
author: Descomplicar® Crescimento Digital
|
||||
version: 1.2.0
|
||||
user_invocable: true
|
||||
tags: [php, laravel, symfony, api, backend, jwt, oauth, rest]
|
||||
desk_task: 1477
|
||||
allowed-tools: Read, Write, Edit, Bash, mcp__memory-supabase__search_memories, mcp__context7__get-library-docs, mcp__dify-kb__dify_kb_retrieve_segments
|
||||
category: dev
|
||||
quality_score: 80
|
||||
updated: "2026-02-04T18:00:00Z"
|
||||
---
|
||||
|
||||
# PHP Fullstack Engineer
|
||||
|
||||
Skill para desenvolvimento PHP moderno seguindo padrões Descomplicar®.
|
||||
|
||||
## Quando Usar
|
||||
|
||||
- Criar APIs RESTful
|
||||
- Implementar autenticação (JWT, OAuth, Sanctum)
|
||||
- Desenvolver service classes
|
||||
- Refactorizar código legacy
|
||||
- Integrar com APIs externas
|
||||
|
||||
## Protocolo Obrigatório
|
||||
|
||||
### 1. Pesquisa Inicial
|
||||
```
|
||||
mcp__memory-supabase__search_memories "[keywords php/laravel]"
|
||||
mcp__wikijs__search_pages "[framework] best practices"
|
||||
mcp__context7__get-library-docs para documentação actualizada
|
||||
```
|
||||
|
||||
### 2. Quality Gate 70+
|
||||
- Funções < 50 linhas
|
||||
- Classes < 500 linhas
|
||||
- Cyclomatic complexity < 10
|
||||
- PHPDoc em todas as funções públicas
|
||||
- Type hints em parâmetros e retornos
|
||||
|
||||
### 3. Assinatura Obrigatória
|
||||
```php
|
||||
<?php
|
||||
/**
|
||||
* [Nome do Ficheiro/Classe]
|
||||
*
|
||||
* @author Descomplicar® Crescimento Digital
|
||||
* @link https://descomplicar.pt
|
||||
* @copyright 2025 Descomplicar®
|
||||
*/
|
||||
```
|
||||
|
||||
## Padrões de Código
|
||||
|
||||
### PHP 8.1+ Features
|
||||
```php
|
||||
// Typed properties
|
||||
private readonly string $name;
|
||||
|
||||
// Constructor promotion
|
||||
public function __construct(
|
||||
private readonly ProductService $service
|
||||
) {}
|
||||
|
||||
// Enums
|
||||
enum Status: string {
|
||||
case Active = 'active';
|
||||
case Inactive = 'inactive';
|
||||
}
|
||||
|
||||
// Match expression
|
||||
$result = match($status) {
|
||||
Status::Active => 'Activo',
|
||||
Status::Inactive => 'Inactivo',
|
||||
};
|
||||
|
||||
// Named arguments
|
||||
$this->service->create(
|
||||
name: $name,
|
||||
price: $price
|
||||
);
|
||||
```
|
||||
|
||||
### PSR Standards
|
||||
- **PSR-1**: Basic Coding Standard
|
||||
- **PSR-4**: Autoloading
|
||||
- **PSR-12**: Extended Coding Style
|
||||
- **PSR-7**: HTTP Message Interface
|
||||
- **PSR-15**: HTTP Handlers
|
||||
|
||||
## Segurança (OBRIGATÓRIO)
|
||||
|
||||
```php
|
||||
// SQL - SEMPRE prepared statements
|
||||
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
|
||||
// Input validation
|
||||
$email = filter_var($input, FILTER_VALIDATE_EMAIL);
|
||||
|
||||
// Output escaping
|
||||
echo htmlspecialchars($output, ENT_QUOTES, 'UTF-8');
|
||||
|
||||
// Password hashing
|
||||
$hash = password_hash($password, PASSWORD_ARGON2ID);
|
||||
|
||||
// CSRF em forms
|
||||
<input type="hidden" name="_token" value="<?= csrf_token() ?>">
|
||||
```
|
||||
|
||||
## Padrão Laravel
|
||||
|
||||
### Controller
|
||||
```php
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\StoreProductRequest;
|
||||
use App\Http\Resources\ProductResource;
|
||||
use App\Services\ProductService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
/**
|
||||
* ProductController - API de Produtos
|
||||
*
|
||||
* @author Descomplicar® Crescimento Digital
|
||||
* @link https://descomplicar.pt
|
||||
* @copyright 2025 Descomplicar®
|
||||
*/
|
||||
class ProductController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ProductService $productService
|
||||
) {}
|
||||
|
||||
public function index(): JsonResponse
|
||||
{
|
||||
$products = $this->productService->paginate();
|
||||
return ProductResource::collection($products)->response();
|
||||
}
|
||||
|
||||
public function store(StoreProductRequest $request): JsonResponse
|
||||
{
|
||||
$product = $this->productService->create($request->validated());
|
||||
return (new ProductResource($product))
|
||||
->response()
|
||||
->setStatusCode(201);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Service
|
||||
```php
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Product;
|
||||
use App\Repositories\ProductRepository;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
|
||||
/**
|
||||
* ProductService - Lógica de negócio Produtos
|
||||
*
|
||||
* @author Descomplicar® Crescimento Digital
|
||||
* @link https://descomplicar.pt
|
||||
* @copyright 2025 Descomplicar®
|
||||
*/
|
||||
class ProductService
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ProductRepository $repository
|
||||
) {}
|
||||
|
||||
public function paginate(array $filters = []): LengthAwarePaginator
|
||||
{
|
||||
return $this->repository->paginate($filters);
|
||||
}
|
||||
|
||||
public function create(array $data): Product
|
||||
{
|
||||
return $this->repository->create($data);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Repository
|
||||
```php
|
||||
<?php
|
||||
|
||||
namespace App\Repositories;
|
||||
|
||||
use App\Models\Product;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
|
||||
/**
|
||||
* ProductRepository - Acesso a dados Produtos
|
||||
*
|
||||
* @author Descomplicar® Crescimento Digital
|
||||
* @link https://descomplicar.pt
|
||||
* @copyright 2025 Descomplicar®
|
||||
*/
|
||||
class ProductRepository
|
||||
{
|
||||
public function paginate(array $filters = []): LengthAwarePaginator
|
||||
{
|
||||
return Product::query()
|
||||
->when($filters['category'] ?? null, fn($q, $cat) => $q->where('category_id', $cat))
|
||||
->when($filters['active'] ?? null, fn($q, $active) => $q->where('active', $active))
|
||||
->orderBy('created_at', 'desc')
|
||||
->paginate($filters['per_page'] ?? 15);
|
||||
}
|
||||
|
||||
public function create(array $data): Product
|
||||
{
|
||||
return Product::create($data);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Debugging PHP
|
||||
|
||||
### Xdebug
|
||||
|
||||
```php
|
||||
// php.ini
|
||||
zend_extension=xdebug.so
|
||||
xdebug.mode=debug
|
||||
xdebug.start_with_request=yes
|
||||
xdebug.client_host=127.0.0.1
|
||||
xdebug.client_port=9003
|
||||
```
|
||||
|
||||
### Logging
|
||||
|
||||
```php
|
||||
// Monolog exemplo
|
||||
use Monolog\Logger;
|
||||
use Monolog\Handler\StreamHandler;
|
||||
|
||||
$log = new Logger('app');
|
||||
$log->pushHandler(new StreamHandler('logs/app.log', Logger::DEBUG));
|
||||
|
||||
$log->info('User logged in', ['user_id' => $userId]);
|
||||
$log->error('Database error', ['exception' => $e->getMessage()]);
|
||||
```
|
||||
|
||||
### Profiling
|
||||
|
||||
```bash
|
||||
# Com Blackfire
|
||||
blackfire run php script.php
|
||||
|
||||
# Com Xhprof
|
||||
php -d extension=xhprof.so script.php
|
||||
```
|
||||
|
||||
## Testes Unitários
|
||||
|
||||
### PHPUnit
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class ProductServiceTest extends TestCase
|
||||
{
|
||||
private ProductService $service;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->service = new ProductService(
|
||||
new InMemoryProductRepository()
|
||||
);
|
||||
}
|
||||
|
||||
public function test_create_product(): void
|
||||
{
|
||||
$product = $this->service->create([
|
||||
'name' => 'Test Product',
|
||||
'price' => 99.99
|
||||
]);
|
||||
|
||||
$this->assertInstanceOf(Product::class, $product);
|
||||
$this->assertEquals('Test Product', $product->name);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Pest (alternativa moderna)
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
test('creates product', function () {
|
||||
$product = app(ProductService::class)->create([
|
||||
'name' => 'Test',
|
||||
'price' => 99.99
|
||||
]);
|
||||
|
||||
expect($product)
|
||||
->toBeInstanceOf(Product::class)
|
||||
->name->toBe('Test');
|
||||
});
|
||||
```
|
||||
|
||||
## Checklist Entrega
|
||||
|
||||
- [ ] Assinatura Descomplicar® em todos os ficheiros
|
||||
- [ ] php -l sem erros
|
||||
- [ ] PHPDoc em funções públicas
|
||||
- [ ] Type hints completos
|
||||
- [ ] PSR-12 compliant
|
||||
- [ ] Segurança validada (injection, XSS)
|
||||
- [ ] Se API: documentação endpoints
|
||||
- [ ] Testes unitários (cobertura >70%)
|
||||
|
||||
---
|
||||
|
||||
## Datasets Dify (Consulta Obrigatória)
|
||||
|
||||
Em caso de dúvidas ou para aprofundar conhecimento, consultar os seguintes datasets via MCP:
|
||||
|
||||
| Dataset | ID | Prioridade |
|
||||
|---------|----|-----------:|
|
||||
| **TI (Tecnologia da Informação)** | `7f63ec0c-6321-488c-b107-980140199850` | 1 |
|
||||
| **Desenvolvimento de Software** | `e7c7decc-0ded-4351-ab14-b110b3c38ec9` | 1 |
|
||||
| **Wordpress** | `9da0b2b9-5051-4b99-b9f6-20bf35067092` | 2 |
|
||||
| **PerfexCRM** | `43354eb6-f0b2-40cc-aa53-44e375ab347c` | 3 |
|
||||
|
||||
### Como Consultar
|
||||
|
||||
```javascript
|
||||
// Pesquisar padrões Laravel
|
||||
mcp__dify-kb__dify_kb_retrieve_segments({
|
||||
dataset_id: "e7c7decc-0ded-4351-ab14-b110b3c38ec9",
|
||||
query: "laravel service repository pattern"
|
||||
})
|
||||
|
||||
// APIs e autenticação
|
||||
mcp__dify-kb__dify_kb_retrieve_segments({
|
||||
dataset_id: "7f63ec0c-6321-488c-b107-980140199850",
|
||||
query: "api rest jwt sanctum"
|
||||
})
|
||||
|
||||
// Desenvolvimento Perfex
|
||||
mcp__dify-kb__dify_kb_retrieve_segments({
|
||||
dataset_id: "43354eb6-f0b2-40cc-aa53-44e375ab347c",
|
||||
query: "module development hooks"
|
||||
})
|
||||
```
|
||||
|
||||
### Quando Consultar
|
||||
|
||||
- Implementar padrões de arquitectura
|
||||
- Desenvolver APIs RESTful
|
||||
- Integrar sistemas de autenticação
|
||||
- Criar módulos Perfex CRM
|
||||
- Refactorizar código legacy
|
||||
|
||||
---
|
||||
**Versão**: 1.0.0 | **Autor**: Descomplicar®
|
||||
586
dev-tools/skills/react-patterns/SKILL.md
Normal file
586
dev-tools/skills/react-patterns/SKILL.md
Normal file
@@ -0,0 +1,586 @@
|
||||
---
|
||||
name: react-patterns
|
||||
description: Modern React patterns and best practices. Implements hooks, context,
|
||||
composition, and performance optimization patterns. Use when user mentions "react
|
||||
patterns", "hooks", "react best practices", "component design", "react optimization".
|
||||
author: Descomplicar® Crescimento Digital
|
||||
version: 2.0.0
|
||||
quality_score: 80
|
||||
user_invocable: true
|
||||
desk_task: 1478
|
||||
---
|
||||
|
||||
# React Patterns
|
||||
|
||||
Skill para desenvolvimento React seguindo padrões modernos (React 18+/19).
|
||||
|
||||
## Quando Usar
|
||||
|
||||
- Desenvolver componentes React
|
||||
- Implementar state management
|
||||
- Optimizar performance (memoization, lazy loading)
|
||||
- Migrar de class components para hooks
|
||||
- Usar Server Components (Next.js App Router)
|
||||
|
||||
## Regras Core
|
||||
|
||||
### Hooks Fundamentais
|
||||
|
||||
```jsx
|
||||
// ✅ useState - estado simples
|
||||
const [count, setCount] = useState(0)
|
||||
const [user, setUser] = useState<User | null>(null)
|
||||
|
||||
// ✅ useEffect - side effects
|
||||
useEffect(() => {
|
||||
fetchData()
|
||||
return () => cleanup() // cleanup function
|
||||
}, [dependency]) // array dependências
|
||||
|
||||
// ✅ useCallback - memoizar funções
|
||||
const handleClick = useCallback(() => {
|
||||
doSomething(id)
|
||||
}, [id])
|
||||
|
||||
// ✅ useMemo - memoizar valores computados
|
||||
const expensiveValue = useMemo(() => {
|
||||
return computeExpensive(data)
|
||||
}, [data])
|
||||
|
||||
// ✅ useRef - referências mutáveis
|
||||
const inputRef = useRef<HTMLInputElement>(null)
|
||||
```
|
||||
|
||||
### Component Patterns
|
||||
|
||||
```jsx
|
||||
// ✅ CORRECTO: Functional components
|
||||
function UserCard({ user, onSelect }: Props) {
|
||||
return (
|
||||
<div onClick={() => onSelect(user.id)}>
|
||||
{user.name}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// ✅ CORRECTO: Composition over props drilling
|
||||
function Layout({ children }) {
|
||||
return <main className="container">{children}</main>
|
||||
}
|
||||
|
||||
// ❌ ERRADO: Class components (legacy)
|
||||
class UserCard extends React.Component { ... }
|
||||
```
|
||||
|
||||
### State Management
|
||||
|
||||
```jsx
|
||||
// ✅ Local state: useState
|
||||
// Para estado de um componente
|
||||
|
||||
// ✅ Shared state: Context + useReducer
|
||||
const AppContext = createContext<AppState | null>(null)
|
||||
|
||||
function AppProvider({ children }) {
|
||||
const [state, dispatch] = useReducer(reducer, initialState)
|
||||
return (
|
||||
<AppContext.Provider value={{ state, dispatch }}>
|
||||
{children}
|
||||
</AppContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
// ✅ Server state: React Query / SWR
|
||||
const { data, isLoading } = useQuery({
|
||||
queryKey: ['users'],
|
||||
queryFn: fetchUsers,
|
||||
})
|
||||
|
||||
// ❌ ERRADO: Redux para tudo
|
||||
// ❌ ERRADO: Estado global para estado local
|
||||
```
|
||||
|
||||
### Server Components (Next.js 13+)
|
||||
|
||||
```jsx
|
||||
// ✅ Server Component (default) - async, data fetching
|
||||
async function UserList() {
|
||||
const users = await db.users.findMany() // Direct DB access
|
||||
return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>
|
||||
}
|
||||
|
||||
// ✅ Client Component - interactividade
|
||||
'use client'
|
||||
function Counter() {
|
||||
const [count, setCount] = useState(0)
|
||||
return <button onClick={() => setCount(c => c + 1)}>{count}</button>
|
||||
}
|
||||
|
||||
// ✅ Padrão: Server parent + Client children
|
||||
// page.tsx (Server) → InteractiveSection.tsx ('use client')
|
||||
```
|
||||
|
||||
### Performance
|
||||
|
||||
```jsx
|
||||
// ✅ Lazy loading componentes
|
||||
const HeavyComponent = lazy(() => import('./HeavyComponent'))
|
||||
|
||||
<Suspense fallback={<Loading />}>
|
||||
<HeavyComponent />
|
||||
</Suspense>
|
||||
|
||||
// ✅ React.memo para componentes puros
|
||||
const MemoizedCard = memo(function Card({ data }) {
|
||||
return <div>{data.title}</div>
|
||||
})
|
||||
|
||||
// ✅ Virtualização para listas grandes
|
||||
import { useVirtualizer } from '@tanstack/react-virtual'
|
||||
```
|
||||
|
||||
## Anti-Patterns
|
||||
|
||||
| Anti-Pattern | Problema | Solução |
|
||||
|--------------|----------|---------|
|
||||
| useEffect para tudo | Complexidade | Server Components / React Query |
|
||||
| Props drilling | Manutenção | Context ou Composition |
|
||||
| Inline functions em JSX | Re-renders | useCallback |
|
||||
| State em URL | Perda estado | URL state com hooks |
|
||||
| Class components | Legacy | Functional + Hooks |
|
||||
| Index como key | Bugs listas | ID único |
|
||||
|
||||
## Checklist Componente
|
||||
|
||||
- [ ] Functional component (não class)
|
||||
- [ ] TypeScript types/interfaces
|
||||
- [ ] Props destructuring com defaults
|
||||
- [ ] Hooks no topo (não condicionais)
|
||||
- [ ] Keys únicos em listas
|
||||
- [ ] Error boundaries para erros
|
||||
- [ ] Loading states considerados
|
||||
|
||||
## Estrutura Ficheiros
|
||||
|
||||
```
|
||||
components/
|
||||
├── ui/ # Componentes base (Button, Input, Card)
|
||||
├── features/ # Componentes feature-specific
|
||||
├── layouts/ # Layout components
|
||||
└── providers/ # Context providers
|
||||
|
||||
hooks/
|
||||
├── useAuth.ts
|
||||
├── useLocalStorage.ts
|
||||
└── useDebounce.ts
|
||||
```
|
||||
|
||||
## Custom Hooks Úteis
|
||||
|
||||
### useDebounce
|
||||
|
||||
```tsx
|
||||
function useDebounce<T>(value: T, delay: number): T {
|
||||
const [debouncedValue, setDebouncedValue] = useState(value);
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => setDebouncedValue(value), delay);
|
||||
return () => clearTimeout(timer);
|
||||
}, [value, delay]);
|
||||
|
||||
return debouncedValue;
|
||||
}
|
||||
|
||||
// Uso
|
||||
const searchTerm = useDebounce(input, 500);
|
||||
```
|
||||
|
||||
### useLocalStorage
|
||||
|
||||
```tsx
|
||||
function useLocalStorage<T>(key: string, initialValue: T) {
|
||||
const [storedValue, setStoredValue] = useState<T>(() => {
|
||||
try {
|
||||
const item = window.localStorage.getItem(key);
|
||||
return item ? JSON.parse(item) : initialValue;
|
||||
} catch {
|
||||
return initialValue;
|
||||
}
|
||||
});
|
||||
|
||||
const setValue = (value: T | ((val: T) => T)) => {
|
||||
const valueToStore = value instanceof Function ? value(storedValue) : value;
|
||||
setStoredValue(valueToStore);
|
||||
window.localStorage.setItem(key, JSON.stringify(valueToStore));
|
||||
};
|
||||
|
||||
return [storedValue, setValue] as const;
|
||||
}
|
||||
```
|
||||
|
||||
### useFetch
|
||||
|
||||
```tsx
|
||||
function useFetch<T>(url: string) {
|
||||
const [data, setData] = useState<T | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<Error | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
fetch(url)
|
||||
.then(res => res.json())
|
||||
.then(setData)
|
||||
.catch(setError)
|
||||
.finally(() => setLoading(false));
|
||||
}, [url]);
|
||||
|
||||
return { data, loading, error };
|
||||
}
|
||||
```
|
||||
|
||||
## Padrões Avançados
|
||||
|
||||
### Compound Components
|
||||
|
||||
```tsx
|
||||
// Tab.tsx
|
||||
const TabsContext = createContext<{
|
||||
activeTab: string;
|
||||
setActiveTab: (id: string) => void;
|
||||
} | null>(null);
|
||||
|
||||
function Tabs({ children, defaultTab }: Props) {
|
||||
const [activeTab, setActiveTab] = useState(defaultTab);
|
||||
return (
|
||||
<TabsContext.Provider value={{ activeTab, setActiveTab }}>
|
||||
{children}
|
||||
</TabsContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
Tabs.List = function TabList({ children }: Props) {
|
||||
return <div className="tab-list">{children}</div>;
|
||||
};
|
||||
|
||||
Tabs.Tab = function Tab({ id, children }: Props) {
|
||||
const { activeTab, setActiveTab } = useContext(TabsContext)!;
|
||||
return (
|
||||
<button
|
||||
className={activeTab === id ? 'active' : ''}
|
||||
onClick={() => setActiveTab(id)}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
Tabs.Panel = function TabPanel({ id, children }: Props) {
|
||||
const { activeTab } = useContext(TabsContext)!;
|
||||
return activeTab === id ? <div>{children}</div> : null;
|
||||
};
|
||||
|
||||
// Uso
|
||||
<Tabs defaultTab="home">
|
||||
<Tabs.List>
|
||||
<Tabs.Tab id="home">Home</Tabs.Tab>
|
||||
<Tabs.Tab id="profile">Profile</Tabs.Tab>
|
||||
</Tabs.List>
|
||||
<Tabs.Panel id="home">Home content</Tabs.Panel>
|
||||
<Tabs.Panel id="profile">Profile content</Tabs.Panel>
|
||||
</Tabs>
|
||||
```
|
||||
|
||||
### Render Props (legacy, preferir hooks)
|
||||
|
||||
```tsx
|
||||
// Evitar - usar custom hook
|
||||
<DataFetcher url="/api/users" render={data => <UserList users={data} />} />
|
||||
|
||||
// Preferir
|
||||
const { data } = useFetch('/api/users');
|
||||
return <UserList users={data} />;
|
||||
```
|
||||
|
||||
## Composition Patterns (Vercel Engineering)
|
||||
|
||||
Padrões de composição para componentes React escaláveis. Fonte: Vercel composition-patterns.
|
||||
|
||||
### Regra #1: Evitar Boolean Props (CRITICAL)
|
||||
|
||||
```tsx
|
||||
// ❌ ERRADO: Booleans duplicam estados possíveis
|
||||
<Composer isThread isEditing={false} showAttachments showFormatting={false} />
|
||||
|
||||
// ✅ CORRECTO: Variantes explícitas
|
||||
<ThreadComposer channelId="abc" />
|
||||
<EditMessageComposer messageId="xyz" />
|
||||
<ForwardMessageComposer messageId="123" />
|
||||
```
|
||||
|
||||
Cada boolean duplica os estados possíveis. 5 booleans = 32 combinações, maioria impossível.
|
||||
|
||||
### Regra #2: Compound Components com Context (HIGH)
|
||||
|
||||
```tsx
|
||||
// ✅ Compound Components: context partilhado, composição explícita
|
||||
const ComposerContext = createContext<ComposerContextValue | null>(null)
|
||||
|
||||
function ComposerFrame({ children }: { children: React.ReactNode }) {
|
||||
return <form>{children}</form>
|
||||
}
|
||||
|
||||
function ComposerInput() {
|
||||
const { state, actions: { update }, meta } = use(ComposerContext)
|
||||
return (
|
||||
<TextInput
|
||||
ref={meta.inputRef}
|
||||
value={state.input}
|
||||
onChangeText={(text) => update(s => ({ ...s, input: text }))}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
// Exportar como compound
|
||||
const Composer = {
|
||||
Frame: ComposerFrame,
|
||||
Input: ComposerInput,
|
||||
Submit: ComposerSubmit,
|
||||
Footer: ComposerFooter,
|
||||
}
|
||||
|
||||
// Uso: composição clara do que cada variante renderiza
|
||||
<Composer.Frame>
|
||||
<Composer.Input />
|
||||
<Composer.Footer>
|
||||
<Composer.Submit />
|
||||
</Composer.Footer>
|
||||
</Composer.Frame>
|
||||
```
|
||||
|
||||
### Regra #3: State em Providers (HIGH)
|
||||
|
||||
```tsx
|
||||
// ❌ ERRADO: Estado preso dentro do componente
|
||||
function ForwardComposer() {
|
||||
const [state, setState] = useState(initialState)
|
||||
return <Composer.Frame>...</Composer.Frame>
|
||||
}
|
||||
// Como ForwardButton acede ao state? useEffect sync? Refs? Prop drilling?
|
||||
|
||||
// ✅ CORRECTO: State lifted para Provider
|
||||
function ForwardProvider({ children }: { children: React.ReactNode }) {
|
||||
const [state, setState] = useState(initialState)
|
||||
const submit = useForwardMessage()
|
||||
const inputRef = useRef(null)
|
||||
return (
|
||||
<Composer.Provider
|
||||
state={state}
|
||||
actions={{ update: setState, submit }}
|
||||
meta={{ inputRef }}
|
||||
>
|
||||
{children}
|
||||
</Composer.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
// Agora QUALQUER componente dentro do Provider acede ao state
|
||||
function ForwardDialog() {
|
||||
return (
|
||||
<ForwardProvider>
|
||||
<Dialog>
|
||||
<ForwardComposer />
|
||||
<MessagePreview /> {/* acede state via context */}
|
||||
<ForwardButton /> {/* acede submit via context */}
|
||||
</Dialog>
|
||||
</ForwardProvider>
|
||||
)
|
||||
}
|
||||
|
||||
// Botão FORA do Composer.Frame mas DENTRO do Provider
|
||||
function ForwardButton() {
|
||||
const { actions } = use(ComposerContext)
|
||||
return <Button onPress={actions.submit}>Forward</Button>
|
||||
}
|
||||
```
|
||||
|
||||
**Princípio chave:** O boundary do Provider é o que importa, não o nesting visual.
|
||||
|
||||
### Regra #4: Interface Genérica para Dependency Injection (HIGH)
|
||||
|
||||
```tsx
|
||||
// ✅ Definir interface genérica: state + actions + meta
|
||||
interface ComposerContextValue {
|
||||
state: { input: string; attachments: Attachment[]; isSubmitting: boolean }
|
||||
actions: { update: (fn: (s: State) => State) => void; submit: () => void }
|
||||
meta: { inputRef: React.RefObject<TextInput> }
|
||||
}
|
||||
|
||||
// ✅ Múltiplos providers implementam a MESMA interface
|
||||
function ChannelProvider({ channelId, children }) {
|
||||
const { state, update, submit } = useGlobalChannel(channelId)
|
||||
return <Composer.Provider state={state} actions={{ update, submit }}>{children}</Composer.Provider>
|
||||
}
|
||||
|
||||
function ForwardProvider({ children }) {
|
||||
const [state, setState] = useState(initialState)
|
||||
return <Composer.Provider state={state} actions={{ update: setState, submit: forward }}>{children}</Composer.Provider>
|
||||
}
|
||||
|
||||
// O mesmo Composer.Input funciona com AMBOS os providers!
|
||||
```
|
||||
|
||||
**Princípio:** UI = pedaços reutilizáveis compostos. State = injectado pelo provider. Trocar provider, manter UI.
|
||||
|
||||
### Regra #5: Children > Render Props (MEDIUM)
|
||||
|
||||
```tsx
|
||||
// ❌ Render props (inflexível, difícil de ler)
|
||||
<Composer
|
||||
renderHeader={() => <Header />}
|
||||
renderFooter={() => <><Formatting /><Emojis /></>}
|
||||
/>
|
||||
|
||||
// ✅ Children (composição natural)
|
||||
<Composer.Frame>
|
||||
<Header />
|
||||
<Composer.Input />
|
||||
<Composer.Footer>
|
||||
<Formatting />
|
||||
<Emojis />
|
||||
</Composer.Footer>
|
||||
</Composer.Frame>
|
||||
|
||||
// Excepção: render props quando parent passa dados
|
||||
<List data={items} renderItem={({ item }) => <Item item={item} />} />
|
||||
```
|
||||
|
||||
### Regra #6: React 19 APIs (MEDIUM)
|
||||
|
||||
```tsx
|
||||
// ✅ React 19: ref como prop normal (sem forwardRef)
|
||||
function Input({ ref, ...props }: Props & { ref?: React.Ref<HTMLInputElement> }) {
|
||||
return <input ref={ref} {...props} />
|
||||
}
|
||||
|
||||
// ✅ React 19: use() em vez de useContext()
|
||||
const value = use(MyContext) // pode ser chamado condicionalmente!
|
||||
```
|
||||
|
||||
### Checklist Composição
|
||||
|
||||
- [ ] Zero boolean props para variantes (usar componentes explícitos)
|
||||
- [ ] Compound components com context partilhado
|
||||
- [ ] State em Provider (não dentro do componente UI)
|
||||
- [ ] Interface genérica (state/actions/meta)
|
||||
- [ ] Children para composição, render props só para data passthrough
|
||||
- [ ] React 19: `use()` e ref como prop
|
||||
|
||||
---
|
||||
|
||||
## Datasets Dify (Consulta Obrigatória)
|
||||
|
||||
Em caso de dúvidas ou para aprofundar conhecimento, consultar os seguintes datasets via MCP:
|
||||
|
||||
| Dataset | ID | Prioridade |
|
||||
|---------|----|-----------:|
|
||||
| **TI (Tecnologia da Informação)** | `7f63ec0c-6321-488c-b107-980140199850` | 1 |
|
||||
| **Desenvolvimento de Software** | `e7c7decc-0ded-4351-ab14-b110b3c38ec9` | 1 |
|
||||
| **Desenvolvimento de WebSites** | `c8489151-de94-42b2-8cee-c0b961cfac6d` | 2 |
|
||||
| **UX e Usabilidade** | `e14ab89e-8910-43b6-becf-d57c78afd62d` | 3 |
|
||||
|
||||
### Como Consultar
|
||||
|
||||
```javascript
|
||||
// Pesquisar padrões React
|
||||
mcp__dify-kb__dify_kb_retrieve_segments({
|
||||
dataset_id: "e7c7decc-0ded-4351-ab14-b110b3c38ec9",
|
||||
query: "react hooks state management"
|
||||
})
|
||||
|
||||
// Server Components Next.js
|
||||
mcp__dify-kb__dify_kb_retrieve_segments({
|
||||
dataset_id: "7f63ec0c-6321-488c-b107-980140199850",
|
||||
query: "nextjs server components app router"
|
||||
})
|
||||
|
||||
// UX e performance
|
||||
mcp__dify-kb__dify_kb_retrieve_segments({
|
||||
dataset_id: "e14ab89e-8910-43b6-becf-d57c78afd62d",
|
||||
query: "performance loading states"
|
||||
})
|
||||
```
|
||||
|
||||
### Quando Consultar
|
||||
|
||||
- Implementar hooks customizados
|
||||
- Escolher state management
|
||||
- Optimizar performance (memoization, lazy loading)
|
||||
- Server Components vs Client Components
|
||||
- Padrões de UX em React
|
||||
|
||||
---
|
||||
**Versão**: 1.0.0 | **Autor**: Descomplicar®
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Quando NÃO Usar
|
||||
|
||||
- Para tarefas fora do domínio de especialização desta skill
|
||||
- Quando outra skill mais específica está disponível
|
||||
- Para operações que requerem aprovação manual obrigatória
|
||||
- Quando os requisitos não estão claramente definidos
|
||||
|
||||
|
||||
## Protocolo de Execução
|
||||
|
||||
1. **Análise Inicial**
|
||||
- Verificar requisitos e contexto
|
||||
- Identificar ferramentas necessárias
|
||||
|
||||
2. **Preparação**
|
||||
- Validar acesso a recursos
|
||||
- Preparar ambiente de trabalho
|
||||
|
||||
3. **Execução**
|
||||
- Executar operações de forma incremental
|
||||
- Validar cada passo antes de prosseguir
|
||||
|
||||
4. **Validação**
|
||||
- Verificar resultados obtidos
|
||||
- Confirmar sucesso da operação
|
||||
|
||||
5. **Conclusão**
|
||||
- Documentar alterações realizadas
|
||||
- Reportar status final e próximos passos
|
||||
|
||||
|
||||
## Exemplos de Uso
|
||||
|
||||
### Exemplo 1: Caso Básico
|
||||
```
|
||||
User: [requisição simples relacionada com react-patterns]
|
||||
Skill: [execução directa com validação]
|
||||
Output: [resultado conciso e accionável]
|
||||
```
|
||||
|
||||
### Exemplo 2: Caso Complexo
|
||||
```
|
||||
User: [requisição multi-passo ou complexa]
|
||||
Skill:
|
||||
1. Análise dos requisitos
|
||||
2. Planeamento da abordagem
|
||||
3. Execução faseada
|
||||
4. Validação contínua
|
||||
Output: [resultado detalhado com próximos passos]
|
||||
```
|
||||
|
||||
### Exemplo 3: Caso com Dependências
|
||||
```
|
||||
User: [requisição que depende de outros sistemas]
|
||||
Skill:
|
||||
1. Verificar dependências disponíveis
|
||||
2. Coordenar com skills/MCPs necessários
|
||||
3. Executar workflow integrado
|
||||
Output: [resultado completo com referências]
|
||||
```
|
||||
Reference in New Issue
Block a user