maestro-bundle 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +91 -0
- package/package.json +25 -0
- package/src/cli.mjs +212 -0
- package/templates/bundle-ai-agents/.spec/constitution.md +33 -0
- package/templates/bundle-ai-agents/AGENTS.md +140 -0
- package/templates/bundle-ai-agents/skills/agent-orchestration/SKILL.md +132 -0
- package/templates/bundle-ai-agents/skills/api-design/SKILL.md +100 -0
- package/templates/bundle-ai-agents/skills/clean-architecture/SKILL.md +99 -0
- package/templates/bundle-ai-agents/skills/context-engineering/SKILL.md +98 -0
- package/templates/bundle-ai-agents/skills/database-modeling/SKILL.md +59 -0
- package/templates/bundle-ai-agents/skills/docker-containerization/SKILL.md +114 -0
- package/templates/bundle-ai-agents/skills/eval-testing/SKILL.md +115 -0
- package/templates/bundle-ai-agents/skills/memory-management/SKILL.md +106 -0
- package/templates/bundle-ai-agents/skills/prompt-engineering/SKILL.md +66 -0
- package/templates/bundle-ai-agents/skills/rag-pipeline/SKILL.md +128 -0
- package/templates/bundle-ai-agents/skills/testing-strategy/SKILL.md +95 -0
- package/templates/bundle-base/AGENTS.md +118 -0
- package/templates/bundle-base/skills/branch-strategy/SKILL.md +42 -0
- package/templates/bundle-base/skills/code-review/SKILL.md +54 -0
- package/templates/bundle-base/skills/commit-pattern/SKILL.md +58 -0
- package/templates/bundle-data-pipeline/.spec/constitution.md +32 -0
- package/templates/bundle-data-pipeline/AGENTS.md +115 -0
- package/templates/bundle-data-pipeline/skills/data-preprocessing/SKILL.md +75 -0
- package/templates/bundle-data-pipeline/skills/docker-containerization/SKILL.md +114 -0
- package/templates/bundle-data-pipeline/skills/feature-engineering/SKILL.md +76 -0
- package/templates/bundle-data-pipeline/skills/mlops-pipeline/SKILL.md +77 -0
- package/templates/bundle-data-pipeline/skills/model-training/SKILL.md +68 -0
- package/templates/bundle-data-pipeline/skills/rag-pipeline/SKILL.md +128 -0
- package/templates/bundle-frontend-spa/.spec/constitution.md +32 -0
- package/templates/bundle-frontend-spa/AGENTS.md +107 -0
- package/templates/bundle-frontend-spa/skills/authentication/SKILL.md +90 -0
- package/templates/bundle-frontend-spa/skills/component-design/SKILL.md +115 -0
- package/templates/bundle-frontend-spa/skills/e2e-testing/SKILL.md +101 -0
- package/templates/bundle-frontend-spa/skills/integration-api/SKILL.md +95 -0
- package/templates/bundle-frontend-spa/skills/react-patterns/SKILL.md +130 -0
- package/templates/bundle-frontend-spa/skills/responsive-layout/SKILL.md +65 -0
- package/templates/bundle-frontend-spa/skills/state-management/SKILL.md +86 -0
- package/templates/bundle-jhipster-microservices/.spec/constitution.md +37 -0
- package/templates/bundle-jhipster-microservices/AGENTS.md +307 -0
- package/templates/bundle-jhipster-microservices/skills/ci-cd-pipeline/SKILL.md +112 -0
- package/templates/bundle-jhipster-microservices/skills/clean-architecture/SKILL.md +99 -0
- package/templates/bundle-jhipster-microservices/skills/ddd-tactical/SKILL.md +138 -0
- package/templates/bundle-jhipster-microservices/skills/jhipster-angular/SKILL.md +97 -0
- package/templates/bundle-jhipster-microservices/skills/jhipster-docker-k8s/SKILL.md +183 -0
- package/templates/bundle-jhipster-microservices/skills/jhipster-entities/SKILL.md +87 -0
- package/templates/bundle-jhipster-microservices/skills/jhipster-gateway/SKILL.md +96 -0
- package/templates/bundle-jhipster-microservices/skills/jhipster-kafka/SKILL.md +145 -0
- package/templates/bundle-jhipster-microservices/skills/jhipster-registry/SKILL.md +83 -0
- package/templates/bundle-jhipster-microservices/skills/jhipster-service/SKILL.md +131 -0
- package/templates/bundle-jhipster-microservices/skills/testing-strategy/SKILL.md +95 -0
- package/templates/bundle-jhipster-monorepo/.spec/constitution.md +32 -0
- package/templates/bundle-jhipster-monorepo/AGENTS.md +227 -0
- package/templates/bundle-jhipster-monorepo/skills/clean-architecture/SKILL.md +99 -0
- package/templates/bundle-jhipster-monorepo/skills/ddd-tactical/SKILL.md +138 -0
- package/templates/bundle-jhipster-monorepo/skills/jhipster-angular/SKILL.md +166 -0
- package/templates/bundle-jhipster-monorepo/skills/jhipster-entities/SKILL.md +141 -0
- package/templates/bundle-jhipster-monorepo/skills/jhipster-liquibase/SKILL.md +95 -0
- package/templates/bundle-jhipster-monorepo/skills/jhipster-security/SKILL.md +89 -0
- package/templates/bundle-jhipster-monorepo/skills/jhipster-spring/SKILL.md +155 -0
- package/templates/bundle-jhipster-monorepo/skills/testing-strategy/SKILL.md +95 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# Projeto: Frontend SPA (Single Page Application)
|
|
2
|
+
|
|
3
|
+
Você está construindo uma aplicação frontend moderna com React, TypeScript e Tailwind CSS que consome APIs REST ou GraphQL.
|
|
4
|
+
|
|
5
|
+
## Specification-Driven Development (SDD)
|
|
6
|
+
|
|
7
|
+
Este projeto usa **GitHub Spec Kit** para governança. Antes de implementar qualquer demanda:
|
|
8
|
+
|
|
9
|
+
1. Rodar `/speckit.constitution` — se `.spec/constitution.md` não existir
|
|
10
|
+
2. Rodar `/speckit.specify` — descrever O QUE e POR QUÊ (não como)
|
|
11
|
+
3. Rodar `/speckit.plan` — arquitetura e decisões técnicas
|
|
12
|
+
4. Rodar `/speckit.tasks` — quebrar em tasks atômicas
|
|
13
|
+
5. Rodar `/speckit.implement` — executar as tasks
|
|
14
|
+
|
|
15
|
+
Nunca pular direto para código. Spec primeiro, código depois.
|
|
16
|
+
|
|
17
|
+
## References
|
|
18
|
+
|
|
19
|
+
Documentos de referência que o agente deve consultar quando necessário:
|
|
20
|
+
|
|
21
|
+
- `references/react-component-patterns.md` — Padrões de componentes React
|
|
22
|
+
- `references/tailwind-design-system.md` — Design system com Tailwind
|
|
23
|
+
- `references/testing-library-guide.md` — Guia de testes com Testing Library
|
|
24
|
+
|
|
25
|
+
## Stack do projeto
|
|
26
|
+
|
|
27
|
+
- **Framework:** React 18+ com TypeScript (strict mode)
|
|
28
|
+
- **Bundler:** Vite
|
|
29
|
+
- **Estilização:** Tailwind CSS + Shadcn/UI
|
|
30
|
+
- **Estado:** Zustand (global) + React Query (server state)
|
|
31
|
+
- **Roteamento:** React Router v6+
|
|
32
|
+
- **Forms:** React Hook Form + Zod
|
|
33
|
+
- **HTTP:** Axios
|
|
34
|
+
- **WebSocket:** Socket.io-client (se real-time)
|
|
35
|
+
- **Testes:** Vitest + Testing Library + Playwright (E2E)
|
|
36
|
+
|
|
37
|
+
## Estrutura do projeto
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
src/
|
|
41
|
+
├── features/ # Organizado por feature/domínio
|
|
42
|
+
│ ├── demands/
|
|
43
|
+
│ │ ├── components/ # Componentes da feature
|
|
44
|
+
│ │ │ ├── DemandList.tsx
|
|
45
|
+
│ │ │ ├── DemandCard.tsx
|
|
46
|
+
│ │ │ └── DemandForm.tsx
|
|
47
|
+
│ │ ├── hooks/ # Custom hooks
|
|
48
|
+
│ │ │ └── useDemands.ts
|
|
49
|
+
│ │ ├── services/ # Chamadas API
|
|
50
|
+
│ │ │ └── demandApi.ts
|
|
51
|
+
│ │ ├── types.ts # Types da feature
|
|
52
|
+
│ │ └── index.ts # Barrel export
|
|
53
|
+
│ ├── dashboard/
|
|
54
|
+
│ └── auth/
|
|
55
|
+
├── shared/ # Compartilhado entre features
|
|
56
|
+
│ ├── components/ # Button, Modal, Table, etc.
|
|
57
|
+
│ ├── hooks/ # useDebounce, useLocalStorage, etc.
|
|
58
|
+
│ ├── lib/ # api.ts (axios instance), utils
|
|
59
|
+
│ └── types/ # Types globais
|
|
60
|
+
├── layouts/ # AppLayout, AuthLayout
|
|
61
|
+
├── routes/ # Configuração de rotas
|
|
62
|
+
├── config/ # Constantes, env vars
|
|
63
|
+
├── styles/ # Globals CSS
|
|
64
|
+
└── tests/
|
|
65
|
+
├── e2e/ # Playwright
|
|
66
|
+
└── setup.ts # Vitest setup
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Padrões de código
|
|
70
|
+
|
|
71
|
+
- Máximo 200 linhas por componente
|
|
72
|
+
- Props tipadas com interface (não type)
|
|
73
|
+
- Um componente = uma responsabilidade
|
|
74
|
+
- Custom hooks para lógica reutilizável
|
|
75
|
+
- Server state no React Query, UI state no Zustand
|
|
76
|
+
- React Hook Form + Zod para formulários
|
|
77
|
+
- Lazy loading por rota
|
|
78
|
+
|
|
79
|
+
## Padrões de componentes
|
|
80
|
+
|
|
81
|
+
- Composição > configuração (slots > props booleanas)
|
|
82
|
+
- Container/Presenter para componentes complexos
|
|
83
|
+
- Loading/Error/Empty states em todo componente async
|
|
84
|
+
- Acessibilidade: semantic HTML, aria-labels, keyboard nav
|
|
85
|
+
|
|
86
|
+
## Git
|
|
87
|
+
|
|
88
|
+
- Commits: `feat(demands): adicionar filtro por status`
|
|
89
|
+
- Branches: `feature/<feature>-<descricao>`
|
|
90
|
+
- Nunca commitar node_modules, .env, dist/
|
|
91
|
+
|
|
92
|
+
## Testes
|
|
93
|
+
|
|
94
|
+
- Vitest + Testing Library: componentes e hooks
|
|
95
|
+
- Playwright: fluxos E2E (login, CRUD, navegação)
|
|
96
|
+
- Cobertura mínima: 70%
|
|
97
|
+
- Testar comportamento, não implementação
|
|
98
|
+
|
|
99
|
+
## O que NÃO fazer
|
|
100
|
+
|
|
101
|
+
- Não usar `any` em TypeScript
|
|
102
|
+
- Não fazer fetch dentro do componente (usar React Query)
|
|
103
|
+
- Não duplicar dados da API no estado global
|
|
104
|
+
- Não usar index como key em listas dinâmicas
|
|
105
|
+
- Não criar mega-componentes de 500+ linhas
|
|
106
|
+
- Não ignorar loading/error states
|
|
107
|
+
- Não estilizar com CSS inline quando tem Tailwind
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: authentication
|
|
3
|
+
description: Implementar autenticação JWT com login, refresh token e proteção de rotas no frontend e backend. Use quando precisar implementar login, auth, JWT, ou proteção de endpoints.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Authentication
|
|
7
|
+
|
|
8
|
+
## Backend — JWT com FastAPI
|
|
9
|
+
|
|
10
|
+
```python
|
|
11
|
+
from fastapi import Depends, HTTPException, status
|
|
12
|
+
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
|
13
|
+
from jose import jwt, JWTError
|
|
14
|
+
from datetime import datetime, timedelta
|
|
15
|
+
|
|
16
|
+
security = HTTPBearer()
|
|
17
|
+
SECRET_KEY = os.environ["JWT_SECRET"]
|
|
18
|
+
ALGORITHM = "HS256"
|
|
19
|
+
ACCESS_TOKEN_EXPIRE = timedelta(hours=1)
|
|
20
|
+
REFRESH_TOKEN_EXPIRE = timedelta(days=7)
|
|
21
|
+
|
|
22
|
+
def create_access_token(user_id: str) -> str:
|
|
23
|
+
payload = {
|
|
24
|
+
"sub": user_id,
|
|
25
|
+
"exp": datetime.utcnow() + ACCESS_TOKEN_EXPIRE,
|
|
26
|
+
"type": "access"
|
|
27
|
+
}
|
|
28
|
+
return jwt.encode(payload, SECRET_KEY, algorithm=ALGORITHM)
|
|
29
|
+
|
|
30
|
+
def create_refresh_token(user_id: str) -> str:
|
|
31
|
+
payload = {
|
|
32
|
+
"sub": user_id,
|
|
33
|
+
"exp": datetime.utcnow() + REFRESH_TOKEN_EXPIRE,
|
|
34
|
+
"type": "refresh"
|
|
35
|
+
}
|
|
36
|
+
return jwt.encode(payload, SECRET_KEY, algorithm=ALGORITHM)
|
|
37
|
+
|
|
38
|
+
async def get_current_user(credentials: HTTPAuthorizationCredentials = Depends(security)) -> User:
|
|
39
|
+
try:
|
|
40
|
+
payload = jwt.decode(credentials.credentials, SECRET_KEY, algorithms=[ALGORITHM])
|
|
41
|
+
if payload.get("type") != "access":
|
|
42
|
+
raise HTTPException(status_code=401, detail="Invalid token type")
|
|
43
|
+
user = await user_repo.find_by_id(payload["sub"])
|
|
44
|
+
if not user:
|
|
45
|
+
raise HTTPException(status_code=401, detail="User not found")
|
|
46
|
+
return user
|
|
47
|
+
except JWTError:
|
|
48
|
+
raise HTTPException(status_code=401, detail="Invalid token")
|
|
49
|
+
|
|
50
|
+
# Endpoint protegido
|
|
51
|
+
@router.get("/me")
|
|
52
|
+
async def get_me(user: User = Depends(get_current_user)):
|
|
53
|
+
return UserResponse.from_entity(user)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Frontend — Auth Context
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
interface AuthState {
|
|
60
|
+
token: string | null;
|
|
61
|
+
user: User | null;
|
|
62
|
+
login: (email: string, password: string) => Promise<void>;
|
|
63
|
+
logout: () => void;
|
|
64
|
+
isAuthenticated: boolean;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export const useAuthStore = create<AuthState>((set) => ({
|
|
68
|
+
token: localStorage.getItem('token'),
|
|
69
|
+
user: null,
|
|
70
|
+
isAuthenticated: !!localStorage.getItem('token'),
|
|
71
|
+
|
|
72
|
+
login: async (email, password) => {
|
|
73
|
+
const { access_token, user } = await authApi.login(email, password);
|
|
74
|
+
localStorage.setItem('token', access_token);
|
|
75
|
+
set({ token: access_token, user, isAuthenticated: true });
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
logout: () => {
|
|
79
|
+
localStorage.removeItem('token');
|
|
80
|
+
set({ token: null, user: null, isAuthenticated: false });
|
|
81
|
+
},
|
|
82
|
+
}));
|
|
83
|
+
|
|
84
|
+
// Axios interceptor
|
|
85
|
+
api.interceptors.request.use((config) => {
|
|
86
|
+
const token = localStorage.getItem('token');
|
|
87
|
+
if (token) config.headers.Authorization = `Bearer ${token}`;
|
|
88
|
+
return config;
|
|
89
|
+
});
|
|
90
|
+
```
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: component-design
|
|
3
|
+
description: Criar componentes UI reutilizáveis com Tailwind + Shadcn/UI seguindo design system. Use quando for criar componentes de interface, botões, cards, modais, tabelas, ou elementos de UI.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Component Design
|
|
7
|
+
|
|
8
|
+
## Princípios
|
|
9
|
+
|
|
10
|
+
1. **Um componente = uma responsabilidade**
|
|
11
|
+
2. **Props tipadas** com interface TypeScript
|
|
12
|
+
3. **Composição** sobre configuração (slots > props booleanas)
|
|
13
|
+
4. **Acessibilidade** built-in (aria-labels, keyboard nav)
|
|
14
|
+
|
|
15
|
+
## Componente Base — Template
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
19
|
+
variant?: 'primary' | 'secondary' | 'danger';
|
|
20
|
+
size?: 'sm' | 'md' | 'lg';
|
|
21
|
+
loading?: boolean;
|
|
22
|
+
children: React.ReactNode;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function Button({
|
|
26
|
+
variant = 'primary',
|
|
27
|
+
size = 'md',
|
|
28
|
+
loading = false,
|
|
29
|
+
children,
|
|
30
|
+
disabled,
|
|
31
|
+
...props
|
|
32
|
+
}: ButtonProps) {
|
|
33
|
+
return (
|
|
34
|
+
<button
|
|
35
|
+
className={cn(
|
|
36
|
+
'rounded font-medium transition-colors',
|
|
37
|
+
variants[variant],
|
|
38
|
+
sizes[size],
|
|
39
|
+
(disabled || loading) && 'opacity-50 cursor-not-allowed'
|
|
40
|
+
)}
|
|
41
|
+
disabled={disabled || loading}
|
|
42
|
+
{...props}
|
|
43
|
+
>
|
|
44
|
+
{loading ? <Spinner size={size} /> : children}
|
|
45
|
+
</button>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Status Badge — Para tasks e demandas
|
|
51
|
+
|
|
52
|
+
```tsx
|
|
53
|
+
const statusConfig = {
|
|
54
|
+
pending: { label: 'Pendente', className: 'bg-gray-100 text-gray-700' },
|
|
55
|
+
in_progress: { label: 'Em andamento', className: 'bg-blue-100 text-blue-700' },
|
|
56
|
+
completed: { label: 'Concluído', className: 'bg-green-100 text-green-700' },
|
|
57
|
+
failed: { label: 'Falhou', className: 'bg-red-100 text-red-700' },
|
|
58
|
+
} as const;
|
|
59
|
+
|
|
60
|
+
export function StatusBadge({ status }: { status: keyof typeof statusConfig }) {
|
|
61
|
+
const config = statusConfig[status];
|
|
62
|
+
return (
|
|
63
|
+
<span className={cn('px-2 py-1 rounded-full text-xs font-medium', config.className)}>
|
|
64
|
+
{config.label}
|
|
65
|
+
</span>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Data Table — Com paginação
|
|
71
|
+
|
|
72
|
+
```tsx
|
|
73
|
+
interface DataTableProps<T> {
|
|
74
|
+
data: T[];
|
|
75
|
+
columns: ColumnDef<T>[];
|
|
76
|
+
pagination: { page: number; size: number; total: number };
|
|
77
|
+
onPageChange: (page: number) => void;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function DataTable<T>({ data, columns, pagination, onPageChange }: DataTableProps<T>) {
|
|
81
|
+
const table = useReactTable({
|
|
82
|
+
data,
|
|
83
|
+
columns,
|
|
84
|
+
getCoreRowModel: getCoreRowModel(),
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
<div>
|
|
89
|
+
<Table>
|
|
90
|
+
<TableHeader>{/* ... */}</TableHeader>
|
|
91
|
+
<TableBody>{/* ... */}</TableBody>
|
|
92
|
+
</Table>
|
|
93
|
+
<Pagination
|
|
94
|
+
page={pagination.page}
|
|
95
|
+
total={pagination.total}
|
|
96
|
+
size={pagination.size}
|
|
97
|
+
onChange={onPageChange}
|
|
98
|
+
/>
|
|
99
|
+
</div>
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Loading States
|
|
105
|
+
|
|
106
|
+
Sempre mostrar skeleton enquanto carrega, error state quando falha:
|
|
107
|
+
|
|
108
|
+
```tsx
|
|
109
|
+
function AsyncContent<T>({ query, children }: { query: UseQueryResult<T>, children: (data: T) => ReactNode }) {
|
|
110
|
+
if (query.isLoading) return <Skeleton />;
|
|
111
|
+
if (query.error) return <ErrorState message={query.error.message} />;
|
|
112
|
+
if (!query.data) return <EmptyState />;
|
|
113
|
+
return <>{children(query.data)}</>;
|
|
114
|
+
}
|
|
115
|
+
```
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: e2e-testing
|
|
3
|
+
description: Criar testes end-to-end com Playwright para fluxos completos da aplicação web. Use quando precisar testar fluxos de usuário, validar integração frontend-backend, ou automatizar testes de interface.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# E2E Testing com Playwright
|
|
7
|
+
|
|
8
|
+
## Setup
|
|
9
|
+
|
|
10
|
+
```python
|
|
11
|
+
# tests/e2e/conftest.py
|
|
12
|
+
import pytest
|
|
13
|
+
from playwright.sync_api import sync_playwright
|
|
14
|
+
|
|
15
|
+
@pytest.fixture(scope="session")
|
|
16
|
+
def browser():
|
|
17
|
+
with sync_playwright() as p:
|
|
18
|
+
browser = p.chromium.launch(headless=True)
|
|
19
|
+
yield browser
|
|
20
|
+
browser.close()
|
|
21
|
+
|
|
22
|
+
@pytest.fixture
|
|
23
|
+
def page(browser):
|
|
24
|
+
context = browser.new_context()
|
|
25
|
+
page = context.new_page()
|
|
26
|
+
yield page
|
|
27
|
+
context.close()
|
|
28
|
+
|
|
29
|
+
@pytest.fixture
|
|
30
|
+
def authenticated_page(page):
|
|
31
|
+
page.goto("http://localhost:3000/login")
|
|
32
|
+
page.fill('[name="email"]', "admin@maestro.local")
|
|
33
|
+
page.fill('[name="password"]', "admin123")
|
|
34
|
+
page.click('button[type="submit"]')
|
|
35
|
+
page.wait_for_url("**/dashboard")
|
|
36
|
+
return page
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Testes de fluxo
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
class TestCreateDemandFlow:
|
|
43
|
+
def test_should_create_demand_from_dashboard(self, authenticated_page):
|
|
44
|
+
page = authenticated_page
|
|
45
|
+
|
|
46
|
+
# Navegar para demands
|
|
47
|
+
page.click('a[href="/demands"]')
|
|
48
|
+
page.wait_for_selector('h1:has-text("Demandas")')
|
|
49
|
+
|
|
50
|
+
# Clicar em "Nova"
|
|
51
|
+
page.click('button:has-text("Nova")')
|
|
52
|
+
page.wait_for_selector('dialog')
|
|
53
|
+
|
|
54
|
+
# Preencher formulário
|
|
55
|
+
page.fill('[name="title"]', "Criar CRUD de usuários")
|
|
56
|
+
page.fill('[name="description"]', "Implementar CRUD completo com API e frontend")
|
|
57
|
+
page.select_option('[name="priority"]', "high")
|
|
58
|
+
|
|
59
|
+
# Submeter
|
|
60
|
+
page.click('button:has-text("Criar")')
|
|
61
|
+
|
|
62
|
+
# Verificar criação
|
|
63
|
+
page.wait_for_selector('text=Demanda criada')
|
|
64
|
+
assert page.locator('text=Criar CRUD de usuários').is_visible()
|
|
65
|
+
|
|
66
|
+
def test_should_show_agent_activity_in_realtime(self, authenticated_page):
|
|
67
|
+
page = authenticated_page
|
|
68
|
+
page.goto("http://localhost:3000/dashboard")
|
|
69
|
+
|
|
70
|
+
# Verificar feed de atividades
|
|
71
|
+
feed = page.locator('[data-testid="activity-feed"]')
|
|
72
|
+
assert feed.is_visible()
|
|
73
|
+
|
|
74
|
+
# Verificar métricas
|
|
75
|
+
assert page.locator('[data-testid="metric-active-agents"]').is_visible()
|
|
76
|
+
assert page.locator('[data-testid="metric-compliance-rate"]').is_visible()
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Page Objects (para testes complexos)
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
class DemandPage:
|
|
83
|
+
def __init__(self, page):
|
|
84
|
+
self.page = page
|
|
85
|
+
|
|
86
|
+
def navigate(self):
|
|
87
|
+
self.page.goto("http://localhost:3000/demands")
|
|
88
|
+
self.page.wait_for_selector('h1:has-text("Demandas")')
|
|
89
|
+
|
|
90
|
+
def create(self, title: str, description: str, priority: str = "medium"):
|
|
91
|
+
self.page.click('button:has-text("Nova")')
|
|
92
|
+
self.page.fill('[name="title"]', title)
|
|
93
|
+
self.page.fill('[name="description"]', description)
|
|
94
|
+
self.page.select_option('[name="priority"]', priority)
|
|
95
|
+
self.page.click('button:has-text("Criar")')
|
|
96
|
+
self.page.wait_for_selector('text=Demanda criada')
|
|
97
|
+
|
|
98
|
+
def search(self, query: str):
|
|
99
|
+
self.page.fill('[data-testid="search"]', query)
|
|
100
|
+
self.page.press('[data-testid="search"]', 'Enter')
|
|
101
|
+
```
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: integration-api
|
|
3
|
+
description: Integrar frontend React com backend API usando React Query, Axios e WebSocket. Use quando precisar conectar frontend com backend, consumir APIs, ou implementar real-time.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Integração Frontend ↔ Backend
|
|
7
|
+
|
|
8
|
+
## HTTP Client (Axios)
|
|
9
|
+
|
|
10
|
+
```typescript
|
|
11
|
+
// shared/lib/api.ts
|
|
12
|
+
import axios from 'axios';
|
|
13
|
+
|
|
14
|
+
export const api = axios.create({
|
|
15
|
+
baseURL: import.meta.env.VITE_API_URL || 'http://localhost:8000/api/v1',
|
|
16
|
+
timeout: 10000,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
api.interceptors.response.use(
|
|
20
|
+
(response) => response.data,
|
|
21
|
+
(error) => {
|
|
22
|
+
if (error.response?.status === 401) {
|
|
23
|
+
useAuthStore.getState().logout();
|
|
24
|
+
window.location.href = '/login';
|
|
25
|
+
}
|
|
26
|
+
return Promise.reject(error.response?.data || error);
|
|
27
|
+
}
|
|
28
|
+
);
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Service Layer
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
// features/demands/services/demandApi.ts
|
|
35
|
+
export const demandApi = {
|
|
36
|
+
list: (params?: { page?: number; size?: number; status?: string }) =>
|
|
37
|
+
api.get<PaginatedResponse<Demand>>('/demands', { params }),
|
|
38
|
+
|
|
39
|
+
get: (id: string) =>
|
|
40
|
+
api.get<Demand>(`/demands/${id}`),
|
|
41
|
+
|
|
42
|
+
create: (data: CreateDemandDto) =>
|
|
43
|
+
api.post<Demand>('/demands', data),
|
|
44
|
+
|
|
45
|
+
update: (id: string, data: UpdateDemandDto) =>
|
|
46
|
+
api.put<Demand>(`/demands/${id}`, data),
|
|
47
|
+
|
|
48
|
+
delete: (id: string) =>
|
|
49
|
+
api.delete(`/demands/${id}`),
|
|
50
|
+
};
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## React Query Hooks
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
export function useDemands(params?: DemandFilters) {
|
|
57
|
+
return useQuery({
|
|
58
|
+
queryKey: ['demands', params],
|
|
59
|
+
queryFn: () => demandApi.list(params),
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function useDemand(id: string) {
|
|
64
|
+
return useQuery({
|
|
65
|
+
queryKey: ['demands', id],
|
|
66
|
+
queryFn: () => demandApi.get(id),
|
|
67
|
+
enabled: !!id,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function useCreateDemand() {
|
|
72
|
+
const qc = useQueryClient();
|
|
73
|
+
return useMutation({
|
|
74
|
+
mutationFn: demandApi.create,
|
|
75
|
+
onSuccess: () => qc.invalidateQueries({ queryKey: ['demands'] }),
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## WebSocket Real-time
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
// shared/hooks/useWebSocket.ts
|
|
84
|
+
import { io, Socket } from 'socket.io-client';
|
|
85
|
+
|
|
86
|
+
let socket: Socket | null = null;
|
|
87
|
+
|
|
88
|
+
export function useAgentEvents(onEvent: (event: TrackingEvent) => void) {
|
|
89
|
+
useEffect(() => {
|
|
90
|
+
socket = io(import.meta.env.VITE_WS_URL);
|
|
91
|
+
socket.on('tracking:event', onEvent);
|
|
92
|
+
return () => { socket?.disconnect(); };
|
|
93
|
+
}, [onEvent]);
|
|
94
|
+
}
|
|
95
|
+
```
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: react-patterns
|
|
3
|
+
description: Implementar padrões React com TypeScript incluindo hooks customizados, composição, render props e HOCs. Use quando for criar componentes React, estruturar features, ou resolver problemas de arquitetura frontend.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# React Patterns
|
|
7
|
+
|
|
8
|
+
## 1. Custom Hooks — Extrair lógica reutilizável
|
|
9
|
+
|
|
10
|
+
```tsx
|
|
11
|
+
// hooks/useDemands.ts
|
|
12
|
+
export function useDemands(filters?: DemandFilters) {
|
|
13
|
+
return useQuery({
|
|
14
|
+
queryKey: ['demands', filters],
|
|
15
|
+
queryFn: () => demandApi.list(filters),
|
|
16
|
+
staleTime: 30_000,
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function useCreateDemand() {
|
|
21
|
+
const queryClient = useQueryClient();
|
|
22
|
+
return useMutation({
|
|
23
|
+
mutationFn: demandApi.create,
|
|
24
|
+
onSuccess: () => {
|
|
25
|
+
queryClient.invalidateQueries({ queryKey: ['demands'] });
|
|
26
|
+
toast.success('Demanda criada');
|
|
27
|
+
},
|
|
28
|
+
onError: (err) => toast.error(err.message),
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## 2. Composição > Herança
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
// Compor componentes pequenos
|
|
37
|
+
function DemandCard({ demand }: { demand: Demand }) {
|
|
38
|
+
return (
|
|
39
|
+
<Card>
|
|
40
|
+
<Card.Header>
|
|
41
|
+
<Card.Title>{demand.title}</Card.Title>
|
|
42
|
+
<StatusBadge status={demand.status} />
|
|
43
|
+
</Card.Header>
|
|
44
|
+
<Card.Content>
|
|
45
|
+
<AgentTeamList agents={demand.assignedAgents} />
|
|
46
|
+
</Card.Content>
|
|
47
|
+
<Card.Footer>
|
|
48
|
+
<TaskProgress tasks={demand.tasks} />
|
|
49
|
+
</Card.Footer>
|
|
50
|
+
</Card>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## 3. Container/Presenter
|
|
56
|
+
|
|
57
|
+
```tsx
|
|
58
|
+
// Container — lógica e estado
|
|
59
|
+
function DemandListContainer() {
|
|
60
|
+
const { data, isLoading, error } = useDemands();
|
|
61
|
+
const [selected, setSelected] = useState<string | null>(null);
|
|
62
|
+
|
|
63
|
+
if (isLoading) return <Skeleton />;
|
|
64
|
+
if (error) return <ErrorState message={error.message} />;
|
|
65
|
+
|
|
66
|
+
return <DemandList demands={data!} selected={selected} onSelect={setSelected} />;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Presenter — apenas renderização
|
|
70
|
+
function DemandList({ demands, selected, onSelect }: DemandListProps) {
|
|
71
|
+
return (
|
|
72
|
+
<div className="grid gap-4">
|
|
73
|
+
{demands.map(d => (
|
|
74
|
+
<DemandCard
|
|
75
|
+
key={d.id}
|
|
76
|
+
demand={d}
|
|
77
|
+
isSelected={d.id === selected}
|
|
78
|
+
onClick={() => onSelect(d.id)}
|
|
79
|
+
/>
|
|
80
|
+
))}
|
|
81
|
+
</div>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## 4. Error Boundary
|
|
87
|
+
|
|
88
|
+
```tsx
|
|
89
|
+
function AppErrorBoundary({ children }: { children: React.ReactNode }) {
|
|
90
|
+
return (
|
|
91
|
+
<ErrorBoundary
|
|
92
|
+
fallback={({ error, resetErrorBoundary }) => (
|
|
93
|
+
<div className="p-8 text-center">
|
|
94
|
+
<h2>Algo deu errado</h2>
|
|
95
|
+
<p>{error.message}</p>
|
|
96
|
+
<Button onClick={resetErrorBoundary}>Tentar novamente</Button>
|
|
97
|
+
</div>
|
|
98
|
+
)}
|
|
99
|
+
>
|
|
100
|
+
{children}
|
|
101
|
+
</ErrorBoundary>
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## 5. Formulários com React Hook Form + Zod
|
|
107
|
+
|
|
108
|
+
```tsx
|
|
109
|
+
const demandSchema = z.object({
|
|
110
|
+
title: z.string().min(3, 'Mínimo 3 caracteres'),
|
|
111
|
+
description: z.string().min(10),
|
|
112
|
+
priority: z.enum(['low', 'medium', 'high']),
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
function CreateDemandForm() {
|
|
116
|
+
const { register, handleSubmit, formState: { errors } } = useForm({
|
|
117
|
+
resolver: zodResolver(demandSchema),
|
|
118
|
+
});
|
|
119
|
+
const createDemand = useCreateDemand();
|
|
120
|
+
|
|
121
|
+
return (
|
|
122
|
+
<form onSubmit={handleSubmit(data => createDemand.mutate(data))}>
|
|
123
|
+
<Input {...register('title')} error={errors.title?.message} />
|
|
124
|
+
<Textarea {...register('description')} error={errors.description?.message} />
|
|
125
|
+
<Select {...register('priority')} options={['low', 'medium', 'high']} />
|
|
126
|
+
<Button type="submit" loading={createDemand.isPending}>Criar</Button>
|
|
127
|
+
</form>
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
```
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: responsive-layout
|
|
3
|
+
description: Criar layouts responsivos com Tailwind CSS incluindo sidebar, dashboard grid e mobile-first design. Use quando for criar layouts de página, dashboards, ou interfaces responsivas.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Responsive Layout
|
|
7
|
+
|
|
8
|
+
## Layout Principal do Maestro
|
|
9
|
+
|
|
10
|
+
```tsx
|
|
11
|
+
function AppLayout({ children }: { children: React.ReactNode }) {
|
|
12
|
+
const { sidebarOpen } = useAppStore();
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<div className="flex h-screen bg-gray-50">
|
|
16
|
+
<Sidebar open={sidebarOpen} />
|
|
17
|
+
<div className="flex-1 flex flex-col overflow-hidden">
|
|
18
|
+
<Header />
|
|
19
|
+
<main className="flex-1 overflow-y-auto p-4 md:p-6 lg:p-8">
|
|
20
|
+
{children}
|
|
21
|
+
</main>
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Dashboard Grid
|
|
29
|
+
|
|
30
|
+
```tsx
|
|
31
|
+
function Dashboard() {
|
|
32
|
+
return (
|
|
33
|
+
<div className="space-y-6">
|
|
34
|
+
{/* Métricas top-level */}
|
|
35
|
+
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
|
36
|
+
<MetricCard title="Demands Ativas" value={12} icon={<Activity />} />
|
|
37
|
+
<MetricCard title="Agentes Online" value={8} icon={<Bot />} />
|
|
38
|
+
<MetricCard title="Compliance Rate" value="94%" icon={<Shield />} />
|
|
39
|
+
<MetricCard title="Tasks Hoje" value={47} icon={<CheckCircle />} />
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
{/* Conteúdo principal */}
|
|
43
|
+
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
|
44
|
+
<div className="lg:col-span-2">
|
|
45
|
+
<AgentActivityFeed />
|
|
46
|
+
</div>
|
|
47
|
+
<div>
|
|
48
|
+
<ActiveDemandsList />
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Breakpoints Tailwind
|
|
57
|
+
|
|
58
|
+
| Prefix | Min-width | Uso |
|
|
59
|
+
|---|---|---|
|
|
60
|
+
| `sm:` | 640px | Mobile landscape |
|
|
61
|
+
| `md:` | 768px | Tablet |
|
|
62
|
+
| `lg:` | 1024px | Desktop |
|
|
63
|
+
| `xl:` | 1280px | Desktop wide |
|
|
64
|
+
|
|
65
|
+
Sempre **mobile-first**: escrever para mobile, depois adicionar breakpoints maiores.
|