create-sdd-ai-stack 0.1.17
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/AGENTS.md +164 -0
- package/APP-STACK.md +31 -0
- package/APP.md +46 -0
- package/ARCHITECTURE.md +115 -0
- package/DESIGN.md +219 -0
- package/LICENSE +21 -0
- package/NEXT.md +345 -0
- package/NODE.md +107 -0
- package/REACT.md +92 -0
- package/README.md +260 -0
- package/SKILLS/check-docs/SKILL.md +24 -0
- package/SKILLS/check-docs/check-docs.mjs +56 -0
- package/SKILLS/create-feature/SKILL.md +40 -0
- package/SKILLS/create-feature/create-feature.mjs +128 -0
- package/SKILLS/create-feature.sh +112 -0
- package/SKILLS/install-submodule/SKILL.md +42 -0
- package/SKILLS/install-submodule/install-submodule.mjs +119 -0
- package/bin/create-sdd-ai-stack.mjs +93 -0
- package/docs/CHANGELOG.md +209 -0
- package/docs/PLANNING.md +68 -0
- package/docs/PRODUCT.md +76 -0
- package/docs/RELEASE.md +332 -0
- package/lib/check-links.mjs +42 -0
- package/lib/constants.mjs +60 -0
- package/lib/scaffold.mjs +253 -0
- package/package.json +80 -0
- package/specs/BACKLOG.md +24 -0
- package/specs/PLAN.md +57 -0
- package/specs/ROADMAP.md +35 -0
- package/specs/history/phases/phase-0-bootstrap.md +67 -0
- package/specs/tasks/TASK_TEMPLATE.md +46 -0
- package/src/cli.mjs +119 -0
- package/stacks/README.md +33 -0
- package/stacks/ai.md +52 -0
- package/stacks/ci.md +59 -0
- package/stacks/database.md +56 -0
- package/stacks/git.md +51 -0
- package/stacks/shadcn.md +52 -0
- package/stacks/tailwind.md +65 -0
- package/stacks/testing.md +74 -0
- package/stacks/typescript.md +58 -0
- package/template/next/.env.example +4 -0
- package/template/next/README.md +47 -0
- package/template/next/biome.json +36 -0
- package/template/next/gitignore +32 -0
- package/template/next/next.config.ts +12 -0
- package/template/next/package.json +47 -0
- package/template/next/playwright.config.ts +21 -0
- package/template/next/postcss.config.mjs +7 -0
- package/template/next/src/app/(app)/app/page.tsx +18 -0
- package/template/next/src/app/(app)/error.tsx +30 -0
- package/template/next/src/app/(app)/layout.tsx +25 -0
- package/template/next/src/app/(marketing)/page.tsx +73 -0
- package/template/next/src/app/globals.css +437 -0
- package/template/next/src/app/layout.tsx +41 -0
- package/template/next/src/app/not-found.tsx +11 -0
- package/template/next/src/features/example/actions.ts +43 -0
- package/template/next/src/features/example/application/create-example.usecase.ts +26 -0
- package/template/next/src/features/example/container.ts +16 -0
- package/template/next/src/features/example/domain/IExampleRepository.ts +11 -0
- package/template/next/src/features/example/domain/example.schema.ts +18 -0
- package/template/next/src/features/example/infrastructure/example.repository.ts +26 -0
- package/template/next/src/features/example/ui/create-example-form.tsx +56 -0
- package/template/next/src/proxy.ts +23 -0
- package/template/next/src/shared/lib/cn.ts +6 -0
- package/template/next/src/shared/server/auth.ts +32 -0
- package/template/next/src/shared/server/env.ts +14 -0
- package/template/next/src/shared/ui/index.ts +3 -0
- package/template/next/tests/e2e/routes.spec.ts +34 -0
- package/template/next/tests/unit/create-example.test.ts +55 -0
- package/template/next/tsconfig.json +37 -0
- package/template/next/vitest.config.ts +19 -0
package/stacks/git.md
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# 🌿 GIT
|
|
2
|
+
|
|
3
|
+
## 🚨 Regras não-negociáveis
|
|
4
|
+
|
|
5
|
+
1. **Identidade fixa:** `Marcelino Sandroni <marcelino.sandroni@gmail.com>`. Configure uma vez:
|
|
6
|
+
```bash
|
|
7
|
+
git config user.name "Marcelino Sandroni"
|
|
8
|
+
git config user.email "marcelino.sandroni@gmail.com"
|
|
9
|
+
```
|
|
10
|
+
2. **Conventional Commits obrigatório:**
|
|
11
|
+
```
|
|
12
|
+
<tipo>(<escopo>): descrição curta imperativa. (Agent: <Ferramenta> - <Modelo>)
|
|
13
|
+
```
|
|
14
|
+
Tipos: `feat`, `fix`, `refactor`, `test`, `docs`, `chore`, `perf`, `style`, `build`, `ci`.
|
|
15
|
+
3. **Escopo = a área** (`feat(chat)`, `fix(auth)`, `docs(sdd)`). Sem escopo, `feat:`.
|
|
16
|
+
4. **Uma task = um commit.** Não commite task de outra junto.
|
|
17
|
+
5. **Gitflow simplificado:** `main` = produção, `feat/`, `fix/`, `chore/` para trabalho. PR vai para `main`.
|
|
18
|
+
6. **Nunca commite:** `node_modules`, `.env`, `.env.local`, `.next`, build output, `*.log`, credencial.
|
|
19
|
+
7. **Antes de commitar:** `git status`, `git diff`, `git log --oneline -10`. Stage só o que é da task.
|
|
20
|
+
8. **Sem `--force`, sem `git config` hack, sem amend de commit já pushado.**
|
|
21
|
+
|
|
22
|
+
## 🏷️ Versionamento (SemVer + Tags)
|
|
23
|
+
|
|
24
|
+
- `MAJOR.MINOR.PATCH` — `feat`→MINOR, `fix`→PATCH, breaking→MAJOR.
|
|
25
|
+
- Tag no merge da fase: `git tag v1.2.0` (anotada).
|
|
26
|
+
- Fechou fase? Arquive em `specs/history/phases/`, gere tag, um commit de arquivamento.
|
|
27
|
+
|
|
28
|
+
## 🧭 Fluxo diário
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
git checkout -b feat/billing # 1. branch
|
|
32
|
+
# ... codar e testar ...
|
|
33
|
+
git add -A # 2. stage (só da task)
|
|
34
|
+
git commit -m "feat(billing): cria slice de assinatura. (Agent: Cursor - Claude)"
|
|
35
|
+
git push -u origin feat/billing # 3. push
|
|
36
|
+
# 4. abrir PR contra main
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## 🔁 Atualizando este core (submodule)
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
git submodule update --remote --merge SDD # puxa regras mais novas
|
|
43
|
+
git add SDD && git commit -m "chore(sdd): atualiza core para vX.Y.Z"
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## 🚫 Nunca
|
|
47
|
+
|
|
48
|
+
- Commitar com mensagem vaga ("update", "fix", ".").
|
|
49
|
+
- `git add -A` com lixo não relacionado na working tree.
|
|
50
|
+
- Commitar direto na `main` em trabalho em andamento.
|
|
51
|
+
- Reescrever histórico público.
|
package/stacks/shadcn.md
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# 🧩 SHADCN/UI
|
|
2
|
+
|
|
3
|
+
> **Biblioteca de UI OFICIAL do template.** Componentes vão para `src/shared/ui/`.
|
|
4
|
+
|
|
5
|
+
## 🚨 Regras não-negociáveis
|
|
6
|
+
|
|
7
|
+
1. **shadcn/ui é a única lib de UI.** Proibido MUI, AntD, Bootstrap, Chakra, styled-components.
|
|
8
|
+
2. **Componente novo entra na pasta, não no registry.** `npx shadcn@latest add <comp>` e o arquivo nasce em `src/shared/ui/`.
|
|
9
|
+
3. **Depois de adicionar, EDITE o componente local.** Ele é seu agora. Alinhe com [../DESIGN.md](../DESIGN.md) (tokens, raio, borda).
|
|
10
|
+
4. **Todo componente novo precisa de:** `aria-*` nos que têm função, foco visível, e estado `disabled` real (não só visual).
|
|
11
|
+
5. **Variante com `cva` (class-variance-authority).** Nunca `if/else` de className.
|
|
12
|
+
6. **Ícone:** `lucide-react`. Tamanho default 16, stroke 2. Nada de SVG colado na mão.
|
|
13
|
+
|
|
14
|
+
## 🧪 Fluxo para adicionar componente
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
# 1. Instala e cria o arquivo
|
|
18
|
+
npx shadcn@latest add dialog
|
|
19
|
+
|
|
20
|
+
# 2. Verifique onde caiu: src/shared/ui/dialog.tsx
|
|
21
|
+
|
|
22
|
+
# 3. Edite o arquivo: troque as classes pelo DESIGN (tokens, rounded, border)
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## 🎨 Sobreposição com o DESIGN
|
|
26
|
+
|
|
27
|
+
O shadcn traz um theme genérico. **O DESIGN.md manda.** Checklist ao tocar um componente:
|
|
28
|
+
|
|
29
|
+
- [ ] Cor de fundo/borda/text usando tokens do `globals.css` (`bg-surface-container`, `border-border-prominent`)
|
|
30
|
+
- [ ] Raio: `rounded-md` (0.375rem) em input/botão, `rounded-lg` (0.5rem) em card
|
|
31
|
+
- [ ] Borda hairline `1px`, **sem sombra pesada**
|
|
32
|
+
- [ ] Botão primário: `bg-primary text-on-primary hover:bg-primary/90` com glow lime sutil
|
|
33
|
+
- [ ] Foco: `focus-visible:ring-2 focus-visible:ring-primary`
|
|
34
|
+
|
|
35
|
+
## 📂 Onde cada coisa mora
|
|
36
|
+
|
|
37
|
+
| Tipo | Caminho |
|
|
38
|
+
| --- | --- |
|
|
39
|
+
| Primitive genérica (Button, Dialog, Input) | `src/shared/ui/` |
|
|
40
|
+
| Componente de domínio (UserCard, InvoiceRow) | `src/features/<x>/ui/` |
|
|
41
|
+
| Composição (form + action + validação) | `src/features/<x>/ui/` |
|
|
42
|
+
| Hook de UI (useToast, useMediaQuery) | `src/shared/hooks/` |
|
|
43
|
+
|
|
44
|
+
## 🚫 Anti-padrões
|
|
45
|
+
|
|
46
|
+
| ❌ Não faça | ✅ Faça |
|
|
47
|
+
| --- | --- |
|
|
48
|
+
| `<div onClick>` como botão | `<button type="button">` |
|
|
49
|
+
| Wrapper de `Dialog` com estado próprio espalhado | Estado no pai, ou `useActionState` |
|
|
50
|
+
| Criar componente que é 90% igual ao do shadcn | Editar o existente |
|
|
51
|
+
| Props customizadas com 8 booleanos | `variant` + `size` com cva |
|
|
52
|
+
| Acessibilidade ignorada em modal/drawer | `DialogTitle`, `DialogDescription`, foco preso |
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# 🎨 TAILWIND CSS (v4)
|
|
2
|
+
|
|
3
|
+
> Stack de estilo do template. **Dark mode only.** Tokens completos em [../DESIGN.md](../DESIGN.md).
|
|
4
|
+
|
|
5
|
+
## 🚨 Regras não-negociáveis
|
|
6
|
+
|
|
7
|
+
1. **Nunca escreva cor/rgba literal em classe.** Use os tokens: `bg-surface`, `text-text-secondary`, `border-border-subtle`.
|
|
8
|
+
2. **Nunca use `style={{}}` inline** (exceto variável de valor dinâmico do design).
|
|
9
|
+
3. **Tokens vivem em `src/app/globals.css` no bloco `@theme`.** Para mudar o design, muda **lá** — nunca em componente.
|
|
10
|
+
4. **Mobile-first.** Estilo base é mobile; breakpoint é o upgrade (`sm: md: lg:`).
|
|
11
|
+
5. **Grid de 12 colunas, max-width 1320px, gutter 1.5rem/2rem.** Não inventa outro grid.
|
|
12
|
+
6. **`<Image>` sempre com `sizes` explícito** em grid/lista (evita baixar imagem de 4k no celular).
|
|
13
|
+
7. **Ordem das classes = mobile → estado → breakpoint.** Ajuda a leitura e o diff.
|
|
14
|
+
|
|
15
|
+
## 🧱 Classes de componente (via `@layer components`)
|
|
16
|
+
|
|
17
|
+
O template define os primitivos. **Não repita a classe longa em 40 lugares** — use a variante.
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
<a className="btn-primary">Salvar</a>
|
|
21
|
+
<a className="btn-secondary">Cancelar</a>
|
|
22
|
+
<span className="chip">React 19</span> // chip inativo
|
|
23
|
+
<span className="chip chip-active">Ativo</span> // chip ativo (lime)
|
|
24
|
+
<div className="card">…</div> // card padrão
|
|
25
|
+
<div className="card-metric">…</div> // KPI card
|
|
26
|
+
<label className="field-label">E-mail</label>
|
|
27
|
+
<input className="field" />
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## 🎯 Padrões de uso
|
|
31
|
+
|
|
32
|
+
```tsx
|
|
33
|
+
// ✅ grid responsivo com 12 colunas no desktop
|
|
34
|
+
<div className="mx-auto w-full max-w-[1320px] px-6 lg:px-8">
|
|
35
|
+
<div className="grid grid-cols-4 gap-4 md:grid-cols-8 lg:grid-cols-12">
|
|
36
|
+
<div className="col-span-4 lg:col-span-6">…</div>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
// ✅ glassmorphism (nav/status bar)
|
|
41
|
+
<header className="sticky top-0 z-50 border-b border-border-subtle bg-surface-raised/80 backdrop-blur-xl">
|
|
42
|
+
|
|
43
|
+
// ✅ glow lime no KPI
|
|
44
|
+
<div className="card-metric hover:shadow-[0_0_24px_var(--kpi-accent-glow)]">
|
|
45
|
+
|
|
46
|
+
// ❌ proibido
|
|
47
|
+
<div style={{ background: "#BAF336" }} />
|
|
48
|
+
<div className="bg-[#BAF336] text-[#0A0D12]" />
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## 🚫 Proibido
|
|
52
|
+
|
|
53
|
+
| Padrão | Por quê |
|
|
54
|
+
| --- | --- |
|
|
55
|
+
| Cor literal (`#hex`, `rgb()`) em classe | Quebra o tema e o dark mode |
|
|
56
|
+
| `!important` | Esconde erro de especificidade |
|
|
57
|
+
| Classes arbitrárias sem necessidade | Cria CSS não rastreável |
|
|
58
|
+
| `tailwind.config.js` quando Tailwind v4 usa `@theme` no CSS | Duas fontes de verdade |
|
|
59
|
+
| Animações custom sem `prefers-reduced-motion` | Acessibilidade |
|
|
60
|
+
|
|
61
|
+
## ✅ Acessibilidade (obrigatório)
|
|
62
|
+
|
|
63
|
+
- Foco **sempre visível**: use o token `focus-visible:ring-primary` no padrão de input/botão.
|
|
64
|
+
- Alvo de toque mínimo **44px** em mobile.
|
|
65
|
+
- Contraste: texto secundário (`#94A3B8`) sobre surface (`#11151C`) passa; texto muted (`#56657A`) só em detalhe decorativo.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# 🧪 TESTES
|
|
2
|
+
|
|
3
|
+
> Meta: **100% de cobertura nos fluxos críticos** (use cases, actions, mutations). UI visual tem E2E obrigatório.
|
|
4
|
+
|
|
5
|
+
## 📐 Pirâmide do projeto
|
|
6
|
+
|
|
7
|
+
| Tipo | Ferramenta | Onde | Quantos |
|
|
8
|
+
| --- | --- | --- | --- |
|
|
9
|
+
| **Unit** | Vitest | `tests/unit` | use cases, helpers puros, validações |
|
|
10
|
+
| **Integração** | Vitest + DB mock | `tests/integration` | repositórios, actions, server components |
|
|
11
|
+
| **E2E** | Playwright | `tests/e2e` | fluxo real do usuário ponta a ponta |
|
|
12
|
+
|
|
13
|
+
## 🚨 Regras não-negociáveis
|
|
14
|
+
|
|
15
|
+
1. **Toda task precisa de teste.** Sem teste = task não está pronta. ([../specs/PLAN.md](../specs/PLAN.md) e Definition of Done)
|
|
16
|
+
2. **Mokar dados, nunca chamar API de terceiro real** em teste unitário/integração. E2E pode usar sandbox.
|
|
17
|
+
3. **Nome do teste descreve comportamento**, não implementação: `deve_negar_acesso_quando_usuario_bloqueado` > `teste2`.
|
|
18
|
+
4. **Teste de UI = E2E com Playwright.** Não teste componente por snapshot (snapshot é mentira).
|
|
19
|
+
5. **Prova de vida obrigatória:** pra marcar task `[x]`, cole o output verde do terminal na resposta.
|
|
20
|
+
6. **E2E precisa de evidência visual:** screenshot ou trace anexado.
|
|
21
|
+
|
|
22
|
+
## 🧩 Exemplo — unit de use case
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
// tests/unit/features/billing/create-subscription.test.ts
|
|
26
|
+
import { describe, it, expect, vi } from "vitest";
|
|
27
|
+
import { CreateSubscriptionUseCase } from "@/features/billing/application/create-subscription";
|
|
28
|
+
|
|
29
|
+
describe("CreateSubscriptionUseCase", () => {
|
|
30
|
+
it("deve_negar_acesso_quando_usuario_bloqueado", async () => {
|
|
31
|
+
const repo = { create: vi.fn() };
|
|
32
|
+
const useCase = new CreateSubscriptionUseCase(repo as never);
|
|
33
|
+
await expect(
|
|
34
|
+
useCase.execute({ userId: "u1", planId: "p1", blocked: true }),
|
|
35
|
+
).rejects.toThrow("Acesso negado");
|
|
36
|
+
expect(repo.create).not.toHaveBeenCalled();
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## 🧩 Exemplo — E2E Playwright
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
// tests/e2e/billing.spec.ts
|
|
45
|
+
import { test, expect } from "@playwright/test";
|
|
46
|
+
|
|
47
|
+
test("usuário assina um plano e vê o status", async ({ page }) => {
|
|
48
|
+
await page.goto("/billing");
|
|
49
|
+
await page.getByRole("button", { name: "Assinar Pro" }).click();
|
|
50
|
+
await expect(page.getByTestId("subscription-status")).toHaveText("Ativo");
|
|
51
|
+
await page.screenshot({ path: "tests/e2e/screenshots/billing-ativo.png" });
|
|
52
|
+
});
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## ▶️ Comandos
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
cd client # ou a raiz, dependendo do template
|
|
59
|
+
npm run test:unit # Vitest watch
|
|
60
|
+
npm run test:unit -- --run # uma passada
|
|
61
|
+
npm run test:coverage # cobertura
|
|
62
|
+
npm run test:e2e # Playwright
|
|
63
|
+
npm run test:e2e:ui # Playwright com UI
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## 🚫 Anti-padrões
|
|
67
|
+
|
|
68
|
+
| ❌ | ✅ |
|
|
69
|
+
| --- | --- |
|
|
70
|
+
| Testar implementação (mock demais) | Testar comportamento observável |
|
|
71
|
+
| Snapshot de componente inteiro | E2E com `getByRole` |
|
|
72
|
+
| sleep fixo (`waitForTimeout`) | `await expect(...).toBeVisible()` (auto-wait) |
|
|
73
|
+
| `test.skip` silencioso | Issue registrada |
|
|
74
|
+
| Sem mock de terceiro (gastando $$) | MSW / fake local |
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# 🔷 TYPESCRIPT
|
|
2
|
+
|
|
3
|
+
## 🚨 Regras não-negociáveis
|
|
4
|
+
|
|
5
|
+
1. **`strict: true` é obrigatório.** Sem exceção. `tsconfig.json` do template já vem assim.
|
|
6
|
+
2. **`any` é PROIBIDO** no código de produção. Se você não dá, use `unknown` + narrowing.
|
|
7
|
+
3. **Interface (`I*`) mora junto da implementação**, dentro do slice. Nada de pasta global de tipos.
|
|
8
|
+
4. **`type` para união/interseção, `interface` para contrato de classe.** Consistência acima de preferência.
|
|
9
|
+
5. **Funções que recebem `unknown` da fronteira validam com Zod dentro da mesma função.**
|
|
10
|
+
6. **Retorno sempre explícito** em função exportada quando não for óbvio.
|
|
11
|
+
7. **`as` só com justificativa em comentário.** Se precisa de `as`, o modelo está errado.
|
|
12
|
+
|
|
13
|
+
## 🧩 Padrões
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
// ✅ Contrato de domínio (dentro de features/x/domain/)
|
|
17
|
+
export interface IChatRepository {
|
|
18
|
+
save(userId: string, text: string): Promise<ChatMessage>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// ✅ Validação de fronteira
|
|
22
|
+
import { z } from "zod";
|
|
23
|
+
const CreateUserSchema = z.object({
|
|
24
|
+
email: z.email(),
|
|
25
|
+
name: z.string().min(1).max(120),
|
|
26
|
+
});
|
|
27
|
+
export type CreateUserInput = z.infer<typeof CreateUserSchema>;
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## 🚫 Proibido
|
|
31
|
+
|
|
32
|
+
| Padrão | Por quê | Use em vez disso |
|
|
33
|
+
| --- | --- | --- |
|
|
34
|
+
| `any` | some o type-check | `unknown` + guard |
|
|
35
|
+
| `enum` | complica serialização e narrow | `as const` + uniões literais |
|
|
36
|
+
| `!` non-null assertion | estouro em runtime | optional chaining + early return |
|
|
37
|
+
| `as unknown as T` | compilador não entendeu | reescreva o tipo |
|
|
38
|
+
| `namespace` | colisões + bundle | `module` ou escopo de pasta |
|
|
39
|
+
| `function` em vez de arrow | ruído | arrow function (exceto método de classe) |
|
|
40
|
+
| `@ts-ignore` | debt invisível | `@ts-expect-error` + comentário |
|
|
41
|
+
|
|
42
|
+
## 📐 Configuração mínima do template
|
|
43
|
+
|
|
44
|
+
```jsonc
|
|
45
|
+
// tsconfig.json
|
|
46
|
+
{
|
|
47
|
+
"compilerOptions": {
|
|
48
|
+
"strict": true,
|
|
49
|
+
"noUncheckedIndexedAccess": true, // index access seguro
|
|
50
|
+
"noImplicitOverride": true, // todo override explícito
|
|
51
|
+
"noFallthroughCasesInSwitch": true,
|
|
52
|
+
"verbatimModuleSyntax": true, // type-only import obrigatório
|
|
53
|
+
"paths": { "@/*": ["./src/*"] }
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
> `verbatimModuleSyntax` obriga `import type { X } from ...`. Isso é de propósito: mantém o bundle limpo.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# 🚀 [NOME DO APP]
|
|
2
|
+
|
|
3
|
+
> Projeto gerado com **SDD AI Stack** — `npx create-sdd-ai-stack`.
|
|
4
|
+
> As regras de desenvolvimento moram em [`SDD/`](./SDD). **Leia [`SDD/AGENTS.md`](./SDD/AGENTS.md) antes de codar.**
|
|
5
|
+
|
|
6
|
+
## Stack
|
|
7
|
+
|
|
8
|
+
- **Next.js 16** (App Router, React 19.2, Turbopack, Cache Components)
|
|
9
|
+
- **TypeScript** strict + path alias `@/*`
|
|
10
|
+
- **Tailwind CSS v4** (tokens no `src/app/globals.css`)
|
|
11
|
+
- **Biome** (lint + format)
|
|
12
|
+
- **Vitest** (unit/integr) + **Playwright** (E2E)
|
|
13
|
+
|
|
14
|
+
## Comandos
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm run dev # http://localhost:3000
|
|
18
|
+
npm run build # build de produção
|
|
19
|
+
npm run typecheck # tsc --noEmit
|
|
20
|
+
npm run lint # biome check
|
|
21
|
+
npm run test:unit # vitest (watch)
|
|
22
|
+
npm run test:coverage
|
|
23
|
+
npm run test:e2e # playwright
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Regras do projeto
|
|
27
|
+
|
|
28
|
+
Todas as regras vivem em `SDD/`:
|
|
29
|
+
|
|
30
|
+
| Documento | Escopo |
|
|
31
|
+
| --- | --- |
|
|
32
|
+
| [`SDD/AGENTS.md`](./SDD/AGENTS.md) | Leis do agente + fluxo de entrega |
|
|
33
|
+
| [`SDD/NEXT.md`](./SDD/NEXT.md) | Next.js 16 (padrão) |
|
|
34
|
+
| [`SDD/DESIGN.md`](./SDD/DESIGN.md) | Design system |
|
|
35
|
+
| [`SDD/NODE.md`](./SDD/NODE.md) | Node.js (workers, cron) |
|
|
36
|
+
| [`SDD/REACT.md`](./SDD/REACT.md) | React |
|
|
37
|
+
| [`SDD/ARCHITECTURE.md`](./SDD/ARCHITECTURE.md) | Arquitetura global |
|
|
38
|
+
| [`SDD/stacks/`](./SDD/stacks) | TypeScript, Tailwind, shadcn, testes, DB, IA, Git, CI |
|
|
39
|
+
| [`SDD/specs/PLAN.md`](./SDD/specs/PLAN.md) | Tarefa atual |
|
|
40
|
+
|
|
41
|
+
## Fluxo obrigatório
|
|
42
|
+
|
|
43
|
+
1. Pegue a próxima task em `SDD/specs/PLAN.md`
|
|
44
|
+
2. Implemente **um passo** por vez
|
|
45
|
+
3. Rode `typecheck` + `test:unit` + `test:e2e`
|
|
46
|
+
4. Cole a evidência verde no terminal
|
|
47
|
+
5. Marque `[x]` no PLAN e commite
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://biomejs.dev/schemas/2.5.14/schema.json",
|
|
3
|
+
"vcs": { "enabled": true, "clientKind": "git", "useIgnoreFile": true },
|
|
4
|
+
"files": {
|
|
5
|
+
"includes": ["**", "!**/node_modules", "!**/.next", "!**/SDD", "!**/coverage"]
|
|
6
|
+
},
|
|
7
|
+
"formatter": {
|
|
8
|
+
"enabled": true,
|
|
9
|
+
"indentStyle": "space",
|
|
10
|
+
"indentWidth": 2,
|
|
11
|
+
"lineWidth": 90
|
|
12
|
+
},
|
|
13
|
+
"linter": {
|
|
14
|
+
"enabled": true,
|
|
15
|
+
"rules": {
|
|
16
|
+
"preset": "recommended",
|
|
17
|
+
"correctness": {
|
|
18
|
+
"noUnusedVariables": "error"
|
|
19
|
+
},
|
|
20
|
+
"suspicious": {
|
|
21
|
+
"noExplicitAny": "error"
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"javascript": {
|
|
26
|
+
"formatter": {
|
|
27
|
+
"quoteStyle": "double",
|
|
28
|
+
"semicolons": "always"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"css": {
|
|
32
|
+
"parser": {
|
|
33
|
+
"tailwindDirectives": true
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
node_modules/
|
|
2
|
+
.next/
|
|
3
|
+
out/
|
|
4
|
+
build/
|
|
5
|
+
dist/
|
|
6
|
+
coverage/
|
|
7
|
+
test-results/
|
|
8
|
+
playwright-report/
|
|
9
|
+
blob-report/
|
|
10
|
+
.playwright/
|
|
11
|
+
|
|
12
|
+
.env
|
|
13
|
+
.env.local
|
|
14
|
+
.env*.local
|
|
15
|
+
!.env.example
|
|
16
|
+
|
|
17
|
+
*.log
|
|
18
|
+
npm-debug.log*
|
|
19
|
+
yarn-error.log*
|
|
20
|
+
|
|
21
|
+
.DS_Store
|
|
22
|
+
Thumbs.db
|
|
23
|
+
|
|
24
|
+
.vscode/*
|
|
25
|
+
!.vscode/extensions.json
|
|
26
|
+
.idea/
|
|
27
|
+
|
|
28
|
+
.vercel
|
|
29
|
+
*.tsbuildinfo
|
|
30
|
+
next-env.d.ts
|
|
31
|
+
|
|
32
|
+
SDD/node_modules/
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { NextConfig } from "next";
|
|
2
|
+
|
|
3
|
+
const nextConfig: NextConfig = {
|
|
4
|
+
// PPR + "use cache" + cacheLife. Ver SDD/NEXT.md §6
|
|
5
|
+
cacheComponents: true,
|
|
6
|
+
reactCompiler: true,
|
|
7
|
+
images: {
|
|
8
|
+
remotePatterns: [{ protocol: "https", hostname: "**" }],
|
|
9
|
+
},
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export default nextConfig;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "sdd-ai-stack-next",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=20.9.0"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"dev": "next dev",
|
|
11
|
+
"build": "next build",
|
|
12
|
+
"start": "next start",
|
|
13
|
+
"lint": "biome check .",
|
|
14
|
+
"lint:fix": "biome check --write .",
|
|
15
|
+
"format": "biome format --write .",
|
|
16
|
+
"typecheck": "tsc --noEmit",
|
|
17
|
+
"test": "vitest run",
|
|
18
|
+
"test:unit": "vitest",
|
|
19
|
+
"test:coverage": "vitest run --coverage",
|
|
20
|
+
"test:e2e": "playwright test",
|
|
21
|
+
"test:e2e:ui": "playwright test --ui"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"next": "^16.3.7",
|
|
25
|
+
"react": "^19.3.0",
|
|
26
|
+
"react-dom": "^19.3.0",
|
|
27
|
+
"server-only": "^0.0.1",
|
|
28
|
+
"zod": "^4.6.5",
|
|
29
|
+
"class-variance-authority": "^0.7.1",
|
|
30
|
+
"clsx": "^2.1.1",
|
|
31
|
+
"tailwind-merge": "^3.3.1",
|
|
32
|
+
"lucide-react": "^0.469.0"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@biomejs/biome": "^2.0.0",
|
|
36
|
+
"@playwright/test": "^1.63.0",
|
|
37
|
+
"@tailwindcss/postcss": "^4.3.3",
|
|
38
|
+
"@types/node": "^22.10.0",
|
|
39
|
+
"@types/react": "^19.0.0",
|
|
40
|
+
"@types/react-dom": "^19.0.0",
|
|
41
|
+
"@vitest/coverage-v8": "^5.0.2",
|
|
42
|
+
"babel-plugin-react-compiler": "^1.0.0",
|
|
43
|
+
"tailwindcss": "^4.3.3",
|
|
44
|
+
"typescript": "^7.0.2",
|
|
45
|
+
"vitest": "^5.0.2"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { defineConfig, devices } from "@playwright/test";
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
testDir: "./tests/e2e",
|
|
5
|
+
fullyParallel: true,
|
|
6
|
+
forbidOnly: !!process.env.CI,
|
|
7
|
+
retries: process.env.CI ? 2 : 0,
|
|
8
|
+
reporter: [["list"], ["html", { open: "never" }]],
|
|
9
|
+
use: {
|
|
10
|
+
baseURL: "http://localhost:3000",
|
|
11
|
+
trace: "on-first-retry",
|
|
12
|
+
screenshot: "only-on-failure",
|
|
13
|
+
},
|
|
14
|
+
projects: [{ name: "chromium", use: { ...devices["Desktop Chrome"] } }],
|
|
15
|
+
webServer: {
|
|
16
|
+
command: "npm run dev",
|
|
17
|
+
url: "http://localhost:3000",
|
|
18
|
+
reuseExistingServer: !process.env.CI,
|
|
19
|
+
timeout: 120_000,
|
|
20
|
+
},
|
|
21
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export default function AppPage() {
|
|
2
|
+
return (
|
|
3
|
+
<main className="container-grid py-16">
|
|
4
|
+
<h1 className="text-headline-lg">Área logada</h1>
|
|
5
|
+
<p className="mt-3 text-body-md text-text-secondary">
|
|
6
|
+
Crie aqui a primeira feature. Copie a estrutura de{" "}
|
|
7
|
+
<code className="text-code-inline text-primary">src/features/example</code>.
|
|
8
|
+
</p>
|
|
9
|
+
|
|
10
|
+
<div className="mt-8 flex flex-wrap gap-2">
|
|
11
|
+
<span className="chip chip-active">Next.js 16</span>
|
|
12
|
+
<span className="chip">React 19</span>
|
|
13
|
+
<span className="chip">TypeScript</span>
|
|
14
|
+
<span className="chip">Tailwind v4</span>
|
|
15
|
+
</div>
|
|
16
|
+
</main>
|
|
17
|
+
);
|
|
18
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { AlertTriangle } from "lucide-react";
|
|
3
|
+
|
|
4
|
+
export default function ErrorPage({
|
|
5
|
+
error,
|
|
6
|
+
reset,
|
|
7
|
+
}: {
|
|
8
|
+
error: Error & { digest?: string };
|
|
9
|
+
reset: () => void;
|
|
10
|
+
}) {
|
|
11
|
+
return (
|
|
12
|
+
<main className="container-grid flex min-h-screen flex-col items-start justify-center py-24">
|
|
13
|
+
<span className="chip" style={{ borderColor: "var(--color-error)" }}>
|
|
14
|
+
<AlertTriangle size={12} />
|
|
15
|
+
Erro
|
|
16
|
+
</span>
|
|
17
|
+
<h1 className="mt-6 text-headline-lg">Algo quebrou.</h1>
|
|
18
|
+
<p className="mt-3 max-w-lg text-body-md text-text-secondary">
|
|
19
|
+
O erro foi registrado. Tente novamente — se persistir, o digest abaixo identifica
|
|
20
|
+
a ocorrência.
|
|
21
|
+
</p>
|
|
22
|
+
{error.digest ? (
|
|
23
|
+
<p className="mt-4 text-code-inline text-text-muted">digest: {error.digest}</p>
|
|
24
|
+
) : null}
|
|
25
|
+
<button type="button" onClick={reset} className="btn-primary mt-8">
|
|
26
|
+
Tentar novamente
|
|
27
|
+
</button>
|
|
28
|
+
</main>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Metadata } from "next";
|
|
2
|
+
|
|
3
|
+
export const metadata: Metadata = {
|
|
4
|
+
title: "Home",
|
|
5
|
+
description: "Área logada da aplicação.",
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export default function AppLayout({ children }: Readonly<{ children: React.ReactNode }>) {
|
|
9
|
+
return (
|
|
10
|
+
<div className="min-h-screen">
|
|
11
|
+
{/* Nav rail: glassmorphism. Ver SDD/DESIGN.md §3 */}
|
|
12
|
+
<header className="sticky top-0 z-50 border-b border-border-subtle bg-surface-raised/80 backdrop-blur-xl">
|
|
13
|
+
<div className="container-grid flex h-14 items-center justify-between">
|
|
14
|
+
<span className="text-headline-sm font-semibold">[APP]</span>
|
|
15
|
+
<nav className="flex items-center gap-1">
|
|
16
|
+
<a href="/app" className="btn-ghost">
|
|
17
|
+
App
|
|
18
|
+
</a>
|
|
19
|
+
</nav>
|
|
20
|
+
</div>
|
|
21
|
+
</header>
|
|
22
|
+
{children}
|
|
23
|
+
</div>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
export default function HomePage() {
|
|
2
|
+
return (
|
|
3
|
+
<main className="container-grid py-16 lg:py-24">
|
|
4
|
+
<p className="label-mono">Executive Engineering</p>
|
|
5
|
+
|
|
6
|
+
<h1 className="mt-4 text-display-hero-mobile font-extrabold tracking-tight text-balance lg:text-display-hero">
|
|
7
|
+
[HERO HEADLINE AQUI]
|
|
8
|
+
</h1>
|
|
9
|
+
|
|
10
|
+
<p className="mt-6 max-w-2xl text-body-lg text-text-secondary">
|
|
11
|
+
[Subtítulo de uma linha explicando o impacto operacional e financeiro do produto.]
|
|
12
|
+
</p>
|
|
13
|
+
|
|
14
|
+
<div className="mt-10 flex flex-wrap gap-3">
|
|
15
|
+
<a href="#principal" className="btn-primary">
|
|
16
|
+
Ação Executiva
|
|
17
|
+
</a>
|
|
18
|
+
<a href="#detalhe" className="btn-secondary">
|
|
19
|
+
Deep Dive
|
|
20
|
+
</a>
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
<section
|
|
24
|
+
id="principal"
|
|
25
|
+
className="mt-24 grid grid-cols-4 gap-4 md:grid-cols-8 lg:mt-36 lg:grid-cols-12"
|
|
26
|
+
>
|
|
27
|
+
<KpiCard label="Economia anual" value="R$ 24M" sub="12 serviços migrados" />
|
|
28
|
+
<KpiCard label="Throughput" value="100M/dia" sub="msgs processadas" />
|
|
29
|
+
<KpiCard label="Latência p99" value="42ms" sub="região São Paulo" />
|
|
30
|
+
<KpiCard label="Assets sob custódia" value="R$ 100bi" sub="auditoria ok" />
|
|
31
|
+
</section>
|
|
32
|
+
|
|
33
|
+
<section id="detalhe" className="mt-20">
|
|
34
|
+
<p className="label-mono">Stack</p>
|
|
35
|
+
<h2 className="mt-3 text-headline-md">Tecnologia em produção</h2>
|
|
36
|
+
<div className="mt-6 flex flex-wrap gap-2">
|
|
37
|
+
{STACK.map((tech) => (
|
|
38
|
+
<span key={tech} className="chip">
|
|
39
|
+
{tech}
|
|
40
|
+
</span>
|
|
41
|
+
))}
|
|
42
|
+
</div>
|
|
43
|
+
</section>
|
|
44
|
+
</main>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const STACK = [
|
|
49
|
+
"Next.js 16",
|
|
50
|
+
"React 19",
|
|
51
|
+
"TypeScript",
|
|
52
|
+
"Tailwind v4",
|
|
53
|
+
"PostgreSQL",
|
|
54
|
+
"Prisma",
|
|
55
|
+
"Zod",
|
|
56
|
+
"Vitest",
|
|
57
|
+
"Playwright",
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
function KpiCard({ label, value, sub }: { label: string; value: string; sub: string }) {
|
|
61
|
+
return (
|
|
62
|
+
<div className="card-metric col-span-4 md:col-span-4 lg:col-span-3">
|
|
63
|
+
<p className="label-mono flex items-center gap-2">
|
|
64
|
+
<span className="status-dot" />
|
|
65
|
+
{label}
|
|
66
|
+
</p>
|
|
67
|
+
<p className="mt-3 text-metric-stat-mobile font-bold tracking-tight text-primary lg:text-metric-stat">
|
|
68
|
+
{value}
|
|
69
|
+
</p>
|
|
70
|
+
<p className="mt-2 text-body-sm text-text-secondary">{sub}</p>
|
|
71
|
+
</div>
|
|
72
|
+
);
|
|
73
|
+
}
|