@polymorphism-tech/morph-spec 7.4.0 → 7.6.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 +2 -2
- package/bin/morph-spec.js +18 -0
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/CLAUDE.md +9 -4
- package/framework/MORPH.md +4 -4
- package/framework/agents.json +1 -1
- package/framework/commands/create-umbraco-lp.md +35 -0
- package/framework/commands/migrate-lp-to-umbraco.md +33 -0
- package/framework/commands/morph-archive.md +17 -27
- package/framework/commands/morph-preflight.md +10 -5
- package/framework/hooks/claude-code/core/enforce-phase-writes.js +115 -90
- package/framework/hooks/claude-code/core/protect-spec-files.js +32 -30
- package/framework/hooks/claude-code/core/state-sync.js +16 -48
- package/framework/hooks/shared/state-reader.js +74 -8
- package/framework/hooks/shared/state-writer.js +124 -0
- package/framework/rules/infrastructure-standards.md +4 -4
- package/framework/rules/morph-task-tracking.md +69 -49
- package/framework/rules/morph-workflow.md +192 -191
- package/framework/skills/core/create-umbraco-lp/SKILL.md +272 -0
- package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +531 -0
- package/framework/skills/core/morph-apply/SKILL.md +15 -15
- package/framework/skills/core/morph-brainstorming/SKILL.md +5 -5
- package/framework/skills/core/morph-eval/SKILL.md +1 -1
- package/framework/skills/core/morph-implement/SKILL.md +5 -4
- package/framework/skills/core/morph-plan/SKILL.md +6 -6
- package/framework/skills/core/morph-proposal/SKILL.md +8 -7
- package/framework/skills/core/morph-review/SKILL.md +3 -3
- package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
- package/framework/skills/core/morph-standards/SKILL.md +1 -1
- package/framework/standards/STANDARDS.json +80 -8
- package/framework/standards/ai-agents/durable-workflows-hitl.md +191 -0
- package/framework/standards/ai-agents/rag-custom-pgvector.md +246 -243
- package/framework/standards/ai-agents/setup.md +3 -0
- package/framework/standards/ai-agents/vector-data-extensions.md +170 -0
- package/framework/standards/ai-agents/workflows.md +190 -187
- package/framework/standards/infrastructure/docker/{easypanel-deploy.md → coolify-deploy.md} +49 -20
- package/framework/templates/README.md +7 -7
- package/framework/templates/REGISTRY.json +15 -17
- package/framework/templates/context/CONTEXT-FEATURE.md +10 -17
- package/framework/templates/context/CONTEXT.md +181 -180
- package/framework/templates/docs/onboarding.md +122 -118
- package/framework/templates/docs/proposal.md +12 -2
- package/framework/templates/docs/spec.md +228 -228
- package/framework/templates/frontend/nextjs/Dockerfile.nextjs.hbs +1 -1
- package/framework/templates/infrastructure/docker/coolify.template.json +49 -0
- package/framework/templates/infrastructure/github/README.md +6 -6
- package/framework/templates/infrastructure/github/workflows/deploy-coolify.yml.hbs +33 -0
- package/framework/templates/meta-prompts/parallel-workers/parallel-worker.md +80 -80
- package/framework/templates/meta-prompts/squad-leaders/backend-squad.md +90 -89
- package/framework/templates/meta-prompts/squad-leaders/frontend-squad.md +2 -1
- package/framework/templates/meta-prompts/squad-leaders/squad-leader.md +44 -43
- package/framework/templates/meta-prompts/validators/checkpoint-validator.md +107 -107
- package/package.json +1 -1
- package/src/commands/project/update.js +415 -368
- package/src/commands/project/worktree.js +212 -210
- package/src/commands/state/advance.js +82 -0
- package/src/commands/state/archive.js +111 -0
- package/src/commands/validation/validate-feature.js +229 -105
- package/src/core/state/state-manager.js +238 -66
- package/src/dashboard/scanner/state-scanner.js +35 -8
- package/src/lib/generators/context-generator.js +12 -8
- package/src/utils/file-copier.js +7 -0
- package/framework/templates/infrastructure/docker/easypanel.template.json +0 -46
- package/framework/templates/infrastructure/github/workflows/deploy-easypanel.yml.hbs +0 -31
|
@@ -0,0 +1,531 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: migrate-lp-to-umbraco
|
|
3
|
+
description: Migra uma landing page existente (React/Vite, Next.js estático ou HTML) para o template Umbraco-headless MORPH_Umbraco_LP_Template entregando a LP EXATAMENTE igual à origem (mesmo CSS, imagens, fontes e comportamento) + Umbraco por cima — obtenção automática do template, inventário de seções, lift-and-rewire verbatim de cada seção para módulo editável, conexão do formulário ao backend de leads e gate de paridade determinístico. Repo-agnóstica: roda em qualquer lugar, sem template nem origem pré-baixados.
|
|
4
|
+
version: core
|
|
5
|
+
when-to-invoke: ao receber instrução "use /migrate-lp-to-umbraco {projeto}" do usuário, ou quando o usuário descrever necessidade de migrar uma LP para Umbraco
|
|
6
|
+
user-invokable: true
|
|
7
|
+
cliVersion: "7.6.0"
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# migrate-lp-to-umbraco
|
|
11
|
+
|
|
12
|
+
## Objetivo
|
|
13
|
+
|
|
14
|
+
Conduzir a migração de uma landing page de origem arbitrária para o stack Umbraco 17 / .NET 10 + Next.js do template `MORPH_Umbraco_LP_Template`, entregando uma LP **visual e funcionalmente idêntica à origem** (mesmo CSS, imagens, fontes, animações e comportamento) com o Umbraco embrulhando o conteúdo por cima — projeto funcional e idempotentemente seedável onde todo conteúdo editorial (textos, imagens, vídeos) fica editável no backoffice, e as credenciais de integração de leads nunca saem do backoffice.
|
|
15
|
+
|
|
16
|
+
**Princípio central — lift-and-rewire, não rebuild.** A paridade exata é garantida **por construção**: copia-se o markup/CSS/classes/assets de cada seção da origem **verbatim** para o componente do módulo, trocando apenas os literais de conteúdo (texto, `src` de mídia) por leituras do backoffice. O render é idêntico porque o markup é o mesmo; o gate de paridade (passo 6) é uma **confirmação** de que o lift foi limpo, não uma ferramenta para descobrir e remendar drift. Reescrever cada seção do zero ("rebuild-then-compare") nunca converge para "exatamente igual" — não fazer isso.
|
|
17
|
+
|
|
18
|
+
O processo é reproduzível para qualquer LP (React/Vite, Next.js estático ou HTML). A skill é **repo-agnóstica**: não depende de nenhum repo de exemplo nem do template estarem pré-baixados — ela obtém o template sozinha (passo 0).
|
|
19
|
+
|
|
20
|
+
## Quando invocar
|
|
21
|
+
|
|
22
|
+
- Usuário pede "use /migrate-lp-to-umbraco {projeto}" ou equivalente natural ("migrar LP para Umbraco", "portar site para Umbraco headless", "rebuild no template Umbraco").
|
|
23
|
+
- Projeto de origem já existe (brownfield) — LP em React/Vite/Next.js/HTML.
|
|
24
|
+
|
|
25
|
+
## Argumentos
|
|
26
|
+
|
|
27
|
+
- `{projeto}` — nome do projeto em kebab-case (ex.: `reserva-das-flores`, `lp-imobiliaria`). Se ausente, perguntar via `AskUserQuestion`.
|
|
28
|
+
|
|
29
|
+
## Pré-condições
|
|
30
|
+
|
|
31
|
+
- **Acesso ao template** — `git` + (preferencialmente) `gh` autenticado com acesso ao repo **privado** `polymorphism-tech/MORPH_Umbraco_LP_Template`. O template **não** precisa estar pré-baixado: o passo 0 o obtém. (Para clone via `git` direto, é preciso credencial HTTPS ou chave SSH com acesso à org.)
|
|
32
|
+
- **Origem acessível** — em uma das duas formas (a skill suporta ambas):
|
|
33
|
+
- **(a) código-fonte local** da LP (React/Vite, Next.js ou HTML) — caminho de **fidelidade máxima**, pois o CSS, o markup e os assets exatos estão disponíveis para o lift verbatim.
|
|
34
|
+
- **(b) apenas a URL** da LP em produção — fallback: scrapear o HTML renderizado, o CSS computado e baixar todos os assets. O mesmo gate de paridade (passo 6) se aplica.
|
|
35
|
+
- **Docker** disponível para boot local (`docker compose`), ou .NET 10 SDK + Node para boot sem container.
|
|
36
|
+
|
|
37
|
+
## Passo 0 — Obter o template (sempre)
|
|
38
|
+
|
|
39
|
+
A skill **sempre** garante uma cópia local do template antes de qualquer outro passo. Roda em qualquer diretório — não assume nada pré-baixado.
|
|
40
|
+
|
|
41
|
+
**Cache de referência (read-only).** Manter uma cópia canônica do template num cache estável por máquina, para leitura dos arquivos de referência (sinks, `SeedContentHandler`, `moduleRegistry`, e2e) e como base do scaffold:
|
|
42
|
+
|
|
43
|
+
- bash: `TPL="${MORPH_TEMPLATE_CACHE:-$HOME/.morph/templates}/MORPH_Umbraco_LP_Template"`
|
|
44
|
+
- PowerShell: `$Tpl = "$env:USERPROFILE\.morph\templates\MORPH_Umbraco_LP_Template"`
|
|
45
|
+
|
|
46
|
+
**Rotina check-local → clone → pull:**
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
# Bash — obtém ou atualiza o cache do template
|
|
50
|
+
TPL="${MORPH_TEMPLATE_CACHE:-$HOME/.morph/templates}/MORPH_Umbraco_LP_Template"
|
|
51
|
+
if [ -d "$TPL/.git" ]; then
|
|
52
|
+
git -C "$TPL" pull --ff-only || echo "offline — usando cache existente"
|
|
53
|
+
else
|
|
54
|
+
mkdir -p "$(dirname "$TPL")"
|
|
55
|
+
gh repo clone polymorphism-tech/MORPH_Umbraco_LP_Template "$TPL" \
|
|
56
|
+
|| git clone https://github.com/polymorphism-tech/MORPH_Umbraco_LP_Template.git "$TPL" \
|
|
57
|
+
|| git clone git@github.com:polymorphism-tech/MORPH_Umbraco_LP_Template.git "$TPL"
|
|
58
|
+
fi
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
```powershell
|
|
62
|
+
# PowerShell — obtém ou atualiza o cache do template
|
|
63
|
+
$Tpl = "$env:USERPROFILE\.morph\templates\MORPH_Umbraco_LP_Template"
|
|
64
|
+
if (Test-Path "$Tpl\.git") {
|
|
65
|
+
git -C "$Tpl" pull --ff-only
|
|
66
|
+
} else {
|
|
67
|
+
New-Item -ItemType Directory -Force (Split-Path $Tpl) | Out-Null
|
|
68
|
+
gh repo clone polymorphism-tech/MORPH_Umbraco_LP_Template "$Tpl"
|
|
69
|
+
if (-not (Test-Path "$Tpl\.git")) { git clone https://github.com/polymorphism-tech/MORPH_Umbraco_LP_Template.git "$Tpl" }
|
|
70
|
+
if (-not (Test-Path "$Tpl\.git")) { git clone git@github.com:polymorphism-tech/MORPH_Umbraco_LP_Template.git "$Tpl" }
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Notas:
|
|
75
|
+
- Branch padrão do template é **`master`** — **não** fazer `git checkout` de outra branch (o template já está generalizado: doctype raiz `lpHome`, scripts `new-lp`, sinks de lead, e2e).
|
|
76
|
+
- `gh repo clone` é o caminho primário por ser repo **privado** (usa o token já autenticado). HTTPS/SSH são fallback.
|
|
77
|
+
- O cache é **somente leitura**: nunca rodar `new-lp` nem editar dentro dele — o scaffold (passo 3) trabalha sobre uma **cópia fresca**.
|
|
78
|
+
|
|
79
|
+
## Fluxo
|
|
80
|
+
|
|
81
|
+
### 1. Analisar a origem — capturar PARA UM LIFT VERBATIM
|
|
82
|
+
|
|
83
|
+
O objetivo desta etapa não é "entender" a LP, é **capturar tudo o que é necessário para reproduzi-la byte-a-byte**. Cada item abaixo alimenta o lift do passo 4.
|
|
84
|
+
|
|
85
|
+
**Inventário de seções** — para cada bloco visual da página, documentar:
|
|
86
|
+
- Nome e ordem na página.
|
|
87
|
+
- **Markup e classes exatos**: a árvore DOM e os nomes de classe de cada seção, como estão (não normalizar nem "limpar").
|
|
88
|
+
- Conteúdo textual: títulos, subtítulos, corpo, CTAs, links.
|
|
89
|
+
- Mídia: imagens, vídeos, posters — registrar o caminho/URL de cada um para baixar abaixo.
|
|
90
|
+
- **Comportamento interativo**: animação de scroll/scroll-expand, carrossel, marquee, autoplay de vídeo, hovers, mapa, máscara/validação de formulário — documentar cada um com detalhe suficiente para reproduzir idêntico.
|
|
91
|
+
- Dados estruturados aninhados (ex.: lista de produtos com nome+imagem+descrição, lista de depoimentos).
|
|
92
|
+
- **Breakpoints responsivos** e o que muda em cada um (mobile/tablet/desktop).
|
|
93
|
+
|
|
94
|
+
**Tema e CSS — copiar VERBATIM, não reinterpretar:**
|
|
95
|
+
- Capturar o CSS da origem como está: bloco `@theme`/tokens (variáveis `--color-*`, `--font-*`), classes custom, `@layer`, `@keyframes`, media queries. Os valores serão **portados sem alteração** no passo 4 — não arredondar cores nem aproximar espaçamentos.
|
|
96
|
+
- **Config do Tailwind**: portar a config/tema da origem. Tailwind v4 é CSS-based (`@theme` em `globals.css`). ⚠️ A **mesma classe utilitária renderiza diferente** se a versão ou o tema do Tailwind divergir — portar o tema da origem, **nunca** assumir o do template.
|
|
97
|
+
|
|
98
|
+
**Assets — baixar byte-a-byte e registrar checksum:**
|
|
99
|
+
- Baixar/copiar **todos** os assets: imagens, vídeos, posters, ícones e **fontes** (arquivos `.woff2`/`.ttf` + famílias e pesos exatos). Web-fonts são parte da fidelidade — mesma família, mesmo peso, mesmo arquivo.
|
|
100
|
+
- Registrar o `sha256` de cada asset (vira a base da paridade de checksum no passo 6).
|
|
101
|
+
- **Não reencodar nem otimizar** nenhum asset — qualquer reencode quebra a paridade.
|
|
102
|
+
|
|
103
|
+
**SEO/GEO da origem — capturar para semear, não perder no lift:** a paridade visual não
|
|
104
|
+
cobre isso sozinha — um lift perfeito que esquece o SEO da origem entrega uma LP idêntica
|
|
105
|
+
visualmente mas invisível pra buscadores/IA no dia 1. Capturar da origem:
|
|
106
|
+
- `<title>`, `<meta name="description">`, `<link rel="canonical">`.
|
|
107
|
+
- Open Graph: `og:title`, `og:description`, `og:image` (baixar a imagem, idealmente 1200x630).
|
|
108
|
+
- JSON-LD (`<script type="application/ld+json">`) — schema completo, se existir.
|
|
109
|
+
- Favicon (todos os tamanhos/formatos disponíveis) e `apple-touch-icon`.
|
|
110
|
+
- `robots.txt` e `sitemap.xml` da origem — confirmar se há regras além do default (bots de IA bloqueados? outras URLs?).
|
|
111
|
+
- `llms.txt` da origem, se existir.
|
|
112
|
+
|
|
113
|
+
**Formulário de captação** — campos presentes, validação no frontend, endpoint atual, integração de CRM/leads.
|
|
114
|
+
|
|
115
|
+
**Origem apenas por URL (fallback):** se não houver código-fonte, scrapear o HTML renderizado, extrair o CSS computado dos elementos e baixar todos os assets referenciados (incluindo fontes). O lift parte do markup renderizado + CSS computado.
|
|
116
|
+
|
|
117
|
+
**Exemplo ilustrativo (LP fictícia):**
|
|
118
|
+
- Origem: `src/App.tsx` (React/Vite) — componentes `Navbar`, `Hero`, `HorizontalScroll`, `Manifesto`, `Galeria`, `Testimonial`, `CTA`, `Footer`.
|
|
119
|
+
- Tema extraído do bloco `@theme` em `src/index.css`: `--color-surface`, `--color-primary`, `--color-tertiary`, etc.; tipografia (ex.) Cormorant Garamond + Jost (baixar os arquivos de fonte).
|
|
120
|
+
- Formulário: campos nome, e-mail, telefone + POST para servidor com integração de leads.
|
|
121
|
+
|
|
122
|
+
Produzir `docs/origem-inventario.md` no repositório destino com esta análise (incluindo a tabela de assets com `sha256`).
|
|
123
|
+
|
|
124
|
+
### 2. Mapear origem para doctypes/módulos do template
|
|
125
|
+
|
|
126
|
+
Comparar cada seção inventariada com os módulos disponíveis no template:
|
|
127
|
+
|
|
128
|
+
| Módulo (alias uSync) | Propósito |
|
|
129
|
+
|---|---|
|
|
130
|
+
| `heroSection` | Hero genérico com título, subtítulo, imagem/vídeo de fundo e CTA |
|
|
131
|
+
| `diferencialSection` / `diferencialItem` | Lista de diferenciais/features com ícone e descrição |
|
|
132
|
+
| `testimonialSection` / `testimonialItem` | Depoimentos com nome, cargo e texto |
|
|
133
|
+
| `galeriaSection` | Galeria de imagens |
|
|
134
|
+
| `videoSection` | Bloco com vídeo embutido |
|
|
135
|
+
| `equipeSection` / `equipeMembro` | Equipe com foto, nome e cargo |
|
|
136
|
+
| `certificacoesSection` / `certificacaoItem` | Selos e certificações |
|
|
137
|
+
| `localizacaoSection` | Mapa ou endereço |
|
|
138
|
+
| `contatoSection` | Seção de formulário de captação |
|
|
139
|
+
| `customHtmlSection` | Bloco HTML livre (fallback para conteúdo sem módulo dedicado) |
|
|
140
|
+
|
|
141
|
+
Para cada seção da origem, determinar (na ótica do lift verbatim — fidelidade primeiro):
|
|
142
|
+
1. **Reuso direto de módulo do template**: **somente** quando o markup e o CSS renderizados do módulo do template já são idênticos aos da origem. Se houver qualquer diferença visual, **não** reusar — isso introduz drift. Na dúvida, prefira lift verbatim.
|
|
143
|
+
2. **Módulo do template com ajuste de campos**: o módulo serve mas precisa de propriedades extras — adicionar no uSync `.config` do módulo e no componente frontend. O markup permanece o da origem.
|
|
144
|
+
3. **Módulo novo via lift verbatim (caso padrão)**: a seção vira um novo doctype + componente que **reproduz o markup/CSS/classes da origem verbatim** (ver passo 4). Criar o doctype + componente + registro no `moduleRegistry.ts`. Nomear o alias em camelCase com sufixo `Section` (ex.: `manifestoSection`, `galeriaSection`).
|
|
145
|
+
|
|
146
|
+
> Regra prática: a decisão default é **lift verbatim** (opção 3). Reusar um módulo do template (opções 1/2) só quando a paridade visual é garantida — caso contrário a LP migrada deixa de ser "exatamente igual".
|
|
147
|
+
|
|
148
|
+
Documentar decisões e justificativas em `docs/mapeamento-modulos.md`.
|
|
149
|
+
|
|
150
|
+
**Exemplo ilustrativo — módulos lifted da origem:**
|
|
151
|
+
- `heroSection` (lift): Hero com efeito scroll-expand + vídeo de fundo expansível — markup e animação copiados verbatim da origem.
|
|
152
|
+
- `brandStorySection`: Bloco editorial de história da marca com imagem e texto em colunas.
|
|
153
|
+
- `galeriaSection` / `galeriaItem`: Grade de itens com imagem, nome e descrição.
|
|
154
|
+
- `processoSection` / `processoStep`: Processo em etapas (passo a passo numerado).
|
|
155
|
+
- `marqueeSection`: Faixa de texto em loop (palavras-chave da marca).
|
|
156
|
+
- `statsSection`: Contador de métricas (anos, projetos, etc.).
|
|
157
|
+
- Módulos reutilizados diretamente (só porque o render bate com a origem): `testimonialSection`, `contatoSection`.
|
|
158
|
+
|
|
159
|
+
### 3. Scaffold via new-lp
|
|
160
|
+
|
|
161
|
+
Criar o projeto destino a partir de uma **cópia fresca** do template (nunca dentro do cache do passo 0) e rodar o bootstrap nessa cópia:
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
# Bash (Linux/macOS/WSL) — cópia fresca do cache para o diretório do projeto
|
|
165
|
+
TPL="${MORPH_TEMPLATE_CACHE:-$HOME/.morph/templates}/MORPH_Umbraco_LP_Template"
|
|
166
|
+
cp -r "$TPL" ./{projeto} && cd ./{projeto}
|
|
167
|
+
rm -rf .git && git init # desacopla do histórico do template
|
|
168
|
+
# --lead-integration: none | ghl | prospectpro | webhook
|
|
169
|
+
# --reset-db: primeiro boot limpo de um scaffold novo
|
|
170
|
+
./scripts/new-lp.sh "Nome do Projeto" admin@projeto.com \
|
|
171
|
+
--lead-integration ghl \
|
|
172
|
+
--reset-db
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
```powershell
|
|
176
|
+
# PowerShell (Windows) — cópia fresca do cache para o diretório do projeto
|
|
177
|
+
$Tpl = "$env:USERPROFILE\.morph\templates\MORPH_Umbraco_LP_Template"
|
|
178
|
+
Copy-Item $Tpl ".\{projeto}" -Recurse; Set-Location ".\{projeto}"
|
|
179
|
+
Remove-Item .git -Recurse -Force; git init # desacopla do histórico do template
|
|
180
|
+
# new-lp.ps1 usa parâmetros NOMEADOS (não posicionais/--flag):
|
|
181
|
+
.\scripts\new-lp.ps1 -ProjectName "Nome do Projeto" -AdminEmail admin@projeto.com `
|
|
182
|
+
-LeadIntegration ghl -ResetDb
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
> Alternativa: em vez de copiar do cache, fazer um clone fresco direto no diretório do projeto (`gh repo clone polymorphism-tech/MORPH_Umbraco_LP_Template {projeto}`) e então `rm -rf .git && git init`.
|
|
186
|
+
|
|
187
|
+
O script:
|
|
188
|
+
- Gera `.env` com `UMBRACO_ADMIN_EMAIL`, `UMBRACO_ADMIN_PASSWORD` (senha aleatória), `UMBRACO_GLOBAL_ID`, `REVALIDATE_SECRET`, `PROJECT_NAME` e `LEAD_INTEGRATION`.
|
|
189
|
+
- Integração de lead selecionada em `--lead-integration` (`none`/`ghl`/`prospectpro`/`webhook`). Em modo não-interativo sem a flag, assume `none`.
|
|
190
|
+
|
|
191
|
+
Nunca commitar `.env`. O cache do passo 0 permanece intacto (read-only) — todo o trabalho acontece nesta cópia.
|
|
192
|
+
|
|
193
|
+
**Smoke-boot antes de qualquer customização** — boot limpo do template para confirmar que o ambiente funciona:
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
docker compose up --build
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Backend sobe em `http://localhost:5001`, frontend em `http://localhost:3000`. Acessar `http://localhost:3000` e confirmar que a página carrega sem erro 500. Isso elimina problemas de ambiente antes de introduzir customizações — falha precoce = horas economizadas.
|
|
200
|
+
|
|
201
|
+
**Atenção (gotcha Build do Next.js):** o Dockerfile do frontend executa `next build` na imagem — nesse momento o backend precisa estar acessível para o Next.js buscar conteúdo via Delivery API. Se o build rodar antes do backend estar pronto, o build falha com erro de fetch. O `docker compose` com `depends_on: backend` mitiga isso, mas em CI adicionar um `healthcheck` no serviço `backend` e `condition: service_healthy` no `depends_on` do `frontend`.
|
|
202
|
+
|
|
203
|
+
### 4. Lift verbatim — portar markup, mídia e tema
|
|
204
|
+
|
|
205
|
+
**Esta é a etapa que garante a paridade exata.** Para cada seção, o trabalho é um **lift-and-rewire**, não um rebuild:
|
|
206
|
+
|
|
207
|
+
1. **Copiar o componente da origem verbatim** para `frontend/src/components/modules/NomeSection.tsx` — mesmo JSX/HTML, mesmas classes, mesmo CSS, mesma estrutura. Não "limpar", não refatorar, não trocar libs de animação.
|
|
208
|
+
2. **Trocar apenas os literais de conteúdo** (texto, `src` de imagem/vídeo, labels) por leituras do `data` prop do Umbraco. Tudo o que é layout/estilo/markup permanece igual.
|
|
209
|
+
3. **Seedar** esses valores via `SeedContentHandler` para baterem com a origem (passo 4b) → o primeiro render é idêntico **por construção**; a editabilidade no backoffice fica por cima sem mudar o markup renderizado.
|
|
210
|
+
4. **Copiar os assets sem reencode** (passo 4b) e **portar o CSS/tema verbatim** (passo 4c).
|
|
211
|
+
|
|
212
|
+
Os passos 4a-4d abaixo detalham cada parte. A regra que atravessa todos: **se mudou o markup ou o CSS renderizado, não é mais "exatamente igual"**.
|
|
213
|
+
|
|
214
|
+
#### 4a. Módulos novos — uSync ContentType
|
|
215
|
+
|
|
216
|
+
Para cada módulo novo identificado no passo 2, criar o arquivo `.config` em `backend/uSync/v17/ContentTypes/`. Espelhar a estrutura dos módulos existentes (ex.: `heroSection.config`):
|
|
217
|
+
|
|
218
|
+
```xml
|
|
219
|
+
<!-- Exemplo: atelierSection.config -->
|
|
220
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
221
|
+
<ContentType Key="{novo-guid}" Alias="atelierSection" Level="2">
|
|
222
|
+
<Info>
|
|
223
|
+
<Name>Atelier Section</Name>
|
|
224
|
+
<Icon>icon-layers</Icon>
|
|
225
|
+
<Thumbnail>folder.png</Thumbnail>
|
|
226
|
+
<Description></Description>
|
|
227
|
+
<AllowAtRoot>False</AllowAtRoot>
|
|
228
|
+
<IsListView>False</IsListView>
|
|
229
|
+
<Variations>Nothing</Variations>
|
|
230
|
+
<IsElement>true</IsElement>
|
|
231
|
+
<Folder>Sections</Folder>
|
|
232
|
+
<Compositions />
|
|
233
|
+
<DefaultTemplate></DefaultTemplate>
|
|
234
|
+
<AllowedTemplates />
|
|
235
|
+
</Info>
|
|
236
|
+
<Structure />
|
|
237
|
+
<GenericProperties>
|
|
238
|
+
<GenericProperty>
|
|
239
|
+
<Key>{guid-prop}</Key>
|
|
240
|
+
<Name>Titulo</Name>
|
|
241
|
+
<Alias>titulo</Alias>
|
|
242
|
+
<Definition>0cc0eba1-9960-42c9-bf9b-60e150b429ae</Definition>
|
|
243
|
+
<Type>Umbraco.TextBox</Type>
|
|
244
|
+
<Mandatory>true</Mandatory>
|
|
245
|
+
<Description><![CDATA[]]></Description>
|
|
246
|
+
<SortOrder>0</SortOrder>
|
|
247
|
+
<Tab Alias="content/content">Content</Tab>
|
|
248
|
+
<Variations>Nothing</Variations>
|
|
249
|
+
</GenericProperty>
|
|
250
|
+
<!-- propriedades adicionais -->
|
|
251
|
+
</GenericProperties>
|
|
252
|
+
</ContentType>
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
Cada módulo carrega suas próprias propriedades diretamente — os módulos do template não usam compositions compartilhadas para campos de seção (o `<Compositions />` é vazio nos módulos existentes; as compositions em `lpHome.config` são para SEO, cookie e analytics — composições transversais da home, não dos módulos filhos).
|
|
256
|
+
|
|
257
|
+
Para que o novo módulo apareça como opção de bloco na home, registrá-lo no DataType de Block List (`modules` em `lpHome.config`). O `<Definition>649d20e8-e4d5-42a0-9f27-28a537f9ebda</Definition>` aponta para esse DataType — adicionar o novo `ContentTypeKey` no XML do DataType em `backend/uSync/v17/DataTypes/` (arquivo correspondente) e no `SeedContentHandler` para o bloco ser incluído no seed programático.
|
|
258
|
+
|
|
259
|
+
#### 4b. Seed de conteúdo — SeedContentHandler
|
|
260
|
+
|
|
261
|
+
O `SeedContentHandler` (`backend/Infrastructure/SeedContentHandler.cs`) roda na notificação `uSyncImportCompletedNotification` — após o Umbraco importar todos os schemas — e cria a home com conteúdo inicial se ainda não existir. É **idempotente**: verifica se já há um nó raiz do tipo `lpHome` (ou alias equivalente) antes de criar.
|
|
262
|
+
|
|
263
|
+
Padrão de seed de uma seção com mídia:
|
|
264
|
+
|
|
265
|
+
```csharp
|
|
266
|
+
// 1. Importar asset para a Media Library (idempotente por nome de arquivo)
|
|
267
|
+
var mediaItem = CreateOrGetMedia("hero-video.mp4", mediaFolder, "umbracoMediaVideo");
|
|
268
|
+
// mediaItem.Key é um Guid estável entre boots
|
|
269
|
+
|
|
270
|
+
// 2. Serializar referência como MediaPicker3 JSON
|
|
271
|
+
var mediaJson = JsonSerializer.Serialize(new[] {
|
|
272
|
+
new { key = Guid.NewGuid(), mediaKey = mediaItem.Key }
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
// 3. Setar na propriedade do nó de conteúdo
|
|
276
|
+
sectionNode.SetValue("videoFundo", mediaJson);
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
Coloque os assets de seed em `backend/seed-assets/` (imagens, vídeos, ícones da LP de origem).
|
|
280
|
+
|
|
281
|
+
**Semear também o SEO/GEO capturado no passo 1** — não deixar em branco pra "o editor preencher depois" (isso já causou um site indo pro ar sem `og:image`, com favicon genérico e apontando pro domínio errado no sitemap num projeto real):
|
|
282
|
+
|
|
283
|
+
```csharp
|
|
284
|
+
content.SetValue("pageTitleSeo", "<título da origem>");
|
|
285
|
+
content.SetValue("titleSeo", "<og:title da origem>");
|
|
286
|
+
content.SetValue("descriptionSeo", "<meta description da origem>");
|
|
287
|
+
content.SetValue("canonicalUrlSeo", "<URL canônica de produção>"); // só a URL, sem tag HTML
|
|
288
|
+
content.SetValue("noIndexSeo", false); // é Mandatory=false a partir do fix de 2026-07-02 — não deixar true
|
|
289
|
+
content.SetValue("jsonLDSchemaSeo", OrigemJsonLd); // JSON-LD capturado da origem, se existir
|
|
290
|
+
// imageSeo (og:image) e favicon: importar como media (mesmo padrão de CreateOrGetMedia)
|
|
291
|
+
// e setar via MediaPicker3, igual ao padrão de mídia acima.
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
`imageSeo` (1200x630) e `favicon` alimentam `app/icon.tsx`/`apple-icon.tsx` e o `<meta property="og:image">` automaticamente — não precisam de nenhum código extra no frontend, só o seed correto.
|
|
295
|
+
|
|
296
|
+
**O que vai no SeedContentHandler:** textos de inauguração, mídia local (arquivos em `seed-assets/`), ordem das seções, configuração inicial, **e o SEO/GEO capturado no passo 1**. O conteúdo fica editável no backoffice após o primeiro boot — o seed é apenas o estado inicial.
|
|
297
|
+
|
|
298
|
+
**O que NÃO vai no SeedContentHandler:** tokens de integração (GHL API Key, webhook URL). Esses ficam no nó `leadsConfig` que o operador preenche diretamente no backoffice após o boot (ver passo 5).
|
|
299
|
+
|
|
300
|
+
#### 4c. Tema — portabilidade do index.css
|
|
301
|
+
|
|
302
|
+
Copiar as variáveis de design da origem para `frontend/src/app/globals.css` no bloco `@theme`:
|
|
303
|
+
|
|
304
|
+
```css
|
|
305
|
+
/* Antes (origem — index.css/CSS da LP) */
|
|
306
|
+
@theme {
|
|
307
|
+
--color-surface: #F5F0EB;
|
|
308
|
+
--color-primary: #8B1A1A;
|
|
309
|
+
--color-primary-hover: #A82020;
|
|
310
|
+
--color-tertiary: #1A1A1A;
|
|
311
|
+
--font-display: 'Cormorant Garamond', serif;
|
|
312
|
+
--font-body: 'Jost', sans-serif;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/* Depois (destino — frontend/src/app/globals.css) */
|
|
316
|
+
@theme {
|
|
317
|
+
--color-surface: #F5F0EB; /* portado diretamente */
|
|
318
|
+
--color-primary: #8B1A1A;
|
|
319
|
+
--color-primary-hover: #A82020;
|
|
320
|
+
--color-tertiary: #1A1A1A;
|
|
321
|
+
--font-display: 'Cormorant Garamond', serif;
|
|
322
|
+
--font-body: 'Jost', sans-serif;
|
|
323
|
+
}
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
**Fontes — auto-hospedar, nunca `<link>` externo pro Google Fonts:** um `<link rel="stylesheet"
|
|
327
|
+
href="https://fonts.googleapis.com/css2?...">` no `<head>` é render-blocking (round-trip
|
|
328
|
+
externo antes de qualquer paint) e, combinado com `font-display: swap`, causa CLS quando a
|
|
329
|
+
fonte troca depois do primeiro paint — foi 1.680ms de render-blocking estimado e 0.221 de CLS
|
|
330
|
+
medidos num caso real (Barone_LP). **Não usar `next/font/google`** aqui (diferente do passo 4
|
|
331
|
+
de `create-umbraco-lp`, que pode usar): o nome de família que ele gera é escopado/diferente do
|
|
332
|
+
literal da origem, e o gate de paridade (passo 6b.2) compara `font-family` computado
|
|
333
|
+
byte-a-byte contra a origem — `next/font` quebra esse check.
|
|
334
|
+
|
|
335
|
+
O fix correto, que preserva a paridade:
|
|
336
|
+
1. Buscar a CSS real do Google pra descobrir as URLs `.woff2` (`curl` na mesma URL que iria no
|
|
337
|
+
`<link>`) — geralmente **um arquivo por família+estilo já cobre todos os pesos estáticos**
|
|
338
|
+
usados (Google costuma reaproveitar o mesmo arquivo pra weight 400/500/600/700 de uma fonte
|
|
339
|
+
não-variável). Baixar só o subset `latin` (`unicode-range: U+0000-00FF, ...`) — cobre toda
|
|
340
|
+
acentuação do português (á, ã, ç, é... estão em U+00C0-00FF).
|
|
341
|
+
2. Salvar os `.woff2` em `frontend/public/fonts/`.
|
|
342
|
+
3. Declarar `@font-face` em `globals.css` (ou no arquivo de tema) com o **mesmo `font-family`
|
|
343
|
+
literal da origem** (`'Cormorant'`, `'Jost'`, etc. — não o nome gerado por uma lib) e
|
|
344
|
+
`font-display: optional` (não `swap`) — `optional` só troca pro custom font se ele chegar
|
|
345
|
+
quase instantaneamente; senão fica no fallback pra aquela visita, evitando o reflow visível
|
|
346
|
+
que gerou o CLS.
|
|
347
|
+
4. `<link rel="preload" href="/fonts/arquivo.woff2" as="font" type="font/woff2" crossOrigin="anonymous">`
|
|
348
|
+
no `<head>` do `layout.tsx`, sem nenhum `<link>` pro domínio do Google.
|
|
349
|
+
|
|
350
|
+
Verificar ao final: `getComputedStyle(el).fontFamily` no elemento de heading do hero deve
|
|
351
|
+
retornar **a mesma string literal que a origem** (ex.: `"Cormorant, Georgia, serif"`) — só a
|
|
352
|
+
origem do arquivo mudou (de `fonts.gstatic.com` pra same-origin), não o nome da família.
|
|
353
|
+
|
|
354
|
+
#### 4d. Componentes frontend dos módulos novos
|
|
355
|
+
|
|
356
|
+
Para cada módulo novo, criar o componente em `frontend/src/components/modules/NomeSection.tsx` **a partir do componente da origem copiado verbatim** — colar o JSX/markup/classes/CSS da origem e trocar só os literais de conteúdo por `data.*`:
|
|
357
|
+
|
|
358
|
+
> **Exceção deliberada ao verbatim — imagens de mídia do Umbraco:** o componente `Picture`
|
|
359
|
+
> lifted da origem provavelmente emite um `<img>` cru. Pra qualquer `src` que vier do Media
|
|
360
|
+
> Picker (URL do backend, editável no backoffice — diferente de um asset estático em
|
|
361
|
+
> `/public`), usar `next/image` (`fill`, com o container já dimensionado) desde o primeiro
|
|
362
|
+
> lift, não um `<img>` cru. Isso **não** quebra o gate de paridade (passo 6b.3 só faz checksum
|
|
363
|
+
> de assets em `/public`, nunca de URLs de mídia) — pelo contrário, sem isso qualquer foto que
|
|
364
|
+
> o cliente trocar depois no backoffice chega crua (fotos de câmera/celular fácil 10+ MB) e
|
|
365
|
+
> vira lento ou 500 sob concorrência (ver seção "Mídia" no README do template).
|
|
366
|
+
>
|
|
367
|
+
> **Pitfall a checar nesse lift:** `next/image fill` aplica `position/height/width` **inline**
|
|
368
|
+
> no `<img>` renderizado — isso vence qualquer regra de classe (`.minha-grade img { height:
|
|
369
|
+
> 116%; margin-top: -8% }`, por exemplo) que a origem use pra efeitos de oversize/parallax/zoom
|
|
370
|
+
> em cima de um `<img>` cru, mesmo que a regra continue no CSS lifted verbatim. Se a origem tem
|
|
371
|
+
> algum truque desses (imagem deliberadamente maior que o container + margin negativo, comum
|
|
372
|
+
> em seções com parallax scroll-scrub), a troca pra `fill` abre um vão visível onde a "sobra"
|
|
373
|
+
> da imagem deveria estar. Fix: `!important` na propriedade de classe que está sendo vencida
|
|
374
|
+
> (normalmente só `height`) — é uma das poucas justificativas legítimas pra `!important`, já
|
|
375
|
+
> que é o único jeito de vencer um inline style que você não controla diretamente. Checar isso
|
|
376
|
+
> na hora do lift, não esperar aparecer como bug visual depois.
|
|
377
|
+
|
|
378
|
+
```tsx
|
|
379
|
+
// Padrão: componente com 'use client', recebe { data: SectionProps },
|
|
380
|
+
// faz cast para o tipo tipado, e lê props via data.property.
|
|
381
|
+
// O corpo do return é o MARKUP DA ORIGEM VERBATIM — mesmas tags, mesmas classes,
|
|
382
|
+
// mesma animação. Só os literais (texto, src) viram leituras de `d`.
|
|
383
|
+
'use client'
|
|
384
|
+
import type { SectionProps, NomeSectionProps } from '@/lib/types'
|
|
385
|
+
|
|
386
|
+
type Props = { data: SectionProps }
|
|
387
|
+
|
|
388
|
+
export default function NomeSection({ data }: Props) {
|
|
389
|
+
const d = data as NomeSectionProps
|
|
390
|
+
// return ( <markup da origem, idêntico, com {d.titulo} no lugar do texto fixo> )
|
|
391
|
+
}
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
Declarar o tipo correspondente em `frontend/src/lib/types.ts` com os aliases de propriedade do uSync.
|
|
395
|
+
|
|
396
|
+
Registrar o módulo em `frontend/src/components/modules/moduleRegistry.ts`:
|
|
397
|
+
|
|
398
|
+
```ts
|
|
399
|
+
nomeSection: () => import('./NomeSection'),
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
### 5. Conectar o formulário ao backend de leads
|
|
403
|
+
|
|
404
|
+
O template usa uma arquitetura de **multi-sink**: o backend seleciona automaticamente a integração correta pelo valor de `leadIntegration` configurado no nó `leadsConfig` do backoffice.
|
|
405
|
+
|
|
406
|
+
**Sinks disponíveis no template:**
|
|
407
|
+
|
|
408
|
+
| `Key` do sink | Classe | Integração |
|
|
409
|
+
|---|---|---|
|
|
410
|
+
| `ghl` | `GhlLeadSink` | GoHighLevel CRM |
|
|
411
|
+
| `prospectpro` | `ProspectProLeadSink` | ProspectPRO |
|
|
412
|
+
| `webhook` | `WebhookLeadSink` | Webhook HTTP genérico |
|
|
413
|
+
|
|
414
|
+
O `LeadController` (`backend/Controllers/LeadController.cs`) recebe `IEnumerable<ILeadSink>` via DI e despacha para `sinks.FirstOrDefault(s => s.Key == cfg.Integration)`. Adicionar uma nova integração = implementar `ILeadSink` + uma linha no `LeadComposer.cs`.
|
|
415
|
+
|
|
416
|
+
**Configuração pós-boot (nunca em código):**
|
|
417
|
+
|
|
418
|
+
1. Acessar o backoffice em `http://localhost:5001/umbraco`.
|
|
419
|
+
2. Navegar até o nó **Leads Config** (criado automaticamente pelo `SeedContentHandler`).
|
|
420
|
+
3. Preencher:
|
|
421
|
+
- `Lead Integration`: selecionar `ghl` / `prospectpro` / `webhook`.
|
|
422
|
+
- `API Key` / `Webhook URL` / campos específicos da integração.
|
|
423
|
+
4. Publicar o nó.
|
|
424
|
+
|
|
425
|
+
O token nunca aparece no código-fonte nem em variáveis de ambiente — lido do cache publicado pelo `ReadConfig()` do controller via `IUmbracoContextFactory`.
|
|
426
|
+
|
|
427
|
+
**Frontend:** o componente `ContactSection` (`frontend/src/components/modules/ContactSection.tsx`) faz POST para `/api/leads` (rota do Next.js que faz thin-proxy para `{NEXT_PUBLIC_UMBRACO_URL}/api/leads`). Nenhuma credencial no frontend.
|
|
428
|
+
|
|
429
|
+
**Campos mínimos esperados pelo LeadController:** `name`, `email`, `phone`. Campos extras são aceitos no payload como propriedades livres e repassados ao sink.
|
|
430
|
+
|
|
431
|
+
**Normalização de telefone:** aplicar máscara no frontend antes do POST para garantir o formato `+55XXXXXXXXXXX` — o GHL rejeita telefones sem DDI. O template tem o helper `LeadHelpers.NormalizePhoneBR()` no backend como fallback (idempotente: preserva o `+55` se já presente), mas normalizar no frontend evita ambiguidade.
|
|
432
|
+
|
|
433
|
+
### 6. Gate de paridade — confirmar que ficou EXATAMENTE igual
|
|
434
|
+
|
|
435
|
+
Objetivo: **provar** que a LP migrada é idêntica à origem. Com o lift verbatim (passo 4), os checks determinísticos abaixo passam **por construção** — se algum falha, é sinal de que o lift introduziu drift naquela seção; voltar e corrigir o lift, não "ajustar até parecer".
|
|
436
|
+
|
|
437
|
+
**Hierarquia do gate:** os checks **determinísticos** (6b) são o **bloqueio**. O **screenshot diff** (6c) é **corroboração secundária** — nunca o bloqueio único, porque font hinting/antialiasing diferem entre ambientes mesmo com CSS byte-idêntico.
|
|
438
|
+
|
|
439
|
+
> Os dois pontos do passo 4c (fontes auto-hospedadas) e 4d (`next/image fill` pra mídia do
|
|
440
|
+
> Umbraco) não são drift de paridade — são a paridade **certa** pedindo um detalhe extra de
|
|
441
|
+
> implementação. O checksum de assets (6b.3) só cobre `/public`; o estilo computado (6b.2)
|
|
442
|
+
> continua batendo porque `font-family` fica literal. Nenhum dos dois deveria fazer o gate
|
|
443
|
+
> falhar quando bem aplicados.
|
|
444
|
+
|
|
445
|
+
#### 6a. Boot + checklist manual (sanidade)
|
|
446
|
+
|
|
447
|
+
```bash
|
|
448
|
+
docker compose up --build
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
Percorrer a página em `http://localhost:3000` e conferir, lado a lado com a origem: todas as seções na ordem certa, textos/mídias corretos, formulário visível, navegação e footer funcionando. Isso é só uma sanidade rápida antes dos checks automáticos.
|
|
452
|
+
|
|
453
|
+
#### 6b. Checks determinísticos (o bloqueio)
|
|
454
|
+
|
|
455
|
+
A skill **scaffolda** o harness de paridade no projeto destino (o template **não** o traz): criar `frontend/e2e/parity.spec.ts` e `frontend/compare-sections.mjs`. O harness compara `http://localhost:3000` (destino) contra a origem (URL de produção ou um build local da origem) e **falha** se qualquer dimensão divergir:
|
|
456
|
+
|
|
457
|
+
1. **Estrutura DOM + nomes de classe** por seção — `compare-sections.mjs` extrai o esqueleto de tags+classes de cada `<section>`/`<nav>`/`<footer>` dos dois lados (agrupado por chave semântica/texto âncora) e aponta diferenças. Adaptar o array `KEYS` (`[nome, matchFn]`) aos textos âncora das seções do projeto.
|
|
458
|
+
2. **Estilo computado** em elementos amostrados — para cada seção, comparar `getComputedStyle` de elementos-chave (cor, `font-family`, `font-size`, `font-weight`, paddings/margins, `border-radius`) origem × destino. Divergência = falha.
|
|
459
|
+
3. **Checksum de assets** — o conjunto de `sha256` de todas as imagens/vídeos/fontes servidas pelo destino deve igualar o conjunto capturado da origem no passo 1. Asset reencode/otimizado quebra aqui (proposital).
|
|
460
|
+
4. **Comportamento por interação** — uma asserção Playwright por comportamento capturado no passo 1 (scroll-expand, carrossel, marquee, autoplay de vídeo, hover, máscara/validação de telefone, submit do formulário com `/api/leads` mockado retornando `{"ok":true}`).
|
|
461
|
+
|
|
462
|
+
```bash
|
|
463
|
+
cd frontend
|
|
464
|
+
npx playwright test # roda parity.spec.ts + smoke (home.spec.ts do template)
|
|
465
|
+
node compare-sections.mjs http://localhost:3000/ https://origem-em-producao.com.br/
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
#### 6c. Screenshot diff (corroboração secundária)
|
|
469
|
+
|
|
470
|
+
Captura full-page origem × destino em múltiplos viewports (mobile/tablet/desktop). Para não gerar ruído:
|
|
471
|
+
- **desabilitar animações** (`prefers-reduced-motion` / CSS de teste), **aguardar as web-fonts** (`document.fonts.ready`), **congelar vídeo/carrossel** num frame fixo;
|
|
472
|
+
- usar **tolerância ciente de antialiasing** (ex.: `maxDiffPixelRatio` pequeno, threshold de cor).
|
|
473
|
+
|
|
474
|
+
Um diff acima da tolerância é um **alerta para investigar** (provável seção com lift incompleto), não uma reprovação automática se os checks de 6b passam.
|
|
475
|
+
|
|
476
|
+
#### 6d. Critérios de aceite
|
|
477
|
+
|
|
478
|
+
A migração é "exata" quando **todos** são verdade:
|
|
479
|
+
- **6b determinístico passa**: DOM+classes, estilo computado, checksum de assets e comportamento batem com a origem em todas as seções.
|
|
480
|
+
- **6c screenshot diff** abaixo da tolerância em todos os breakpoints (mobile/tablet/desktop).
|
|
481
|
+
- Todas as seções renderizam com conteúdo real (não placeholders) e na ordem da origem.
|
|
482
|
+
- Formulário envia lead com sucesso para a integração configurada (testar com dado real em staging; `--demo-seed` popula a home com módulos de demonstração via `SEED_DEMO_CONTENT=true`, não envia lead de teste).
|
|
483
|
+
- Backoffice: editar um campo de texto, publicar — a mudança reflete na página **em produção, sem precisar de deploy manual** (o `RevalidateOnPublishHandler` do backend chama `/api/revalidate` sozinho; só funciona com `FRONTEND_URL`/`REVALIDATE_SECRET` setados) e **sem** alterar o markup/estilo renderizado.
|
|
484
|
+
- SEO/GEO da origem migrado, não perdido: `curl` a home de produção e conferir `<title>`, `<meta description>`, `og:image`, canonical e JSON-LD batendo com o que foi capturado no passo 1 — e que o favicon do browser não é o ícone genérico do template.
|
|
485
|
+
- `robots.txt`/`sitemap.xml` de produção apontam pro domínio real (não o placeholder `minha-landing-page.com.br` do template) — só acontece se `NEXT_PUBLIC_SITE_URL` foi setada como Build Variable no provedor.
|
|
486
|
+
- Nenhuma chave de API ou token aparece em arquivo versionado.
|
|
487
|
+
|
|
488
|
+
## Anti-padrões
|
|
489
|
+
|
|
490
|
+
- Não commitar `.env` nem nenhum arquivo com tokens ou senhas.
|
|
491
|
+
- Não colocar credenciais de integração em variáveis de ambiente do container — elas pertencem ao nó `leadsConfig` no backoffice.
|
|
492
|
+
- Não criar módulos novos com alias que conflite com os do template (verificar `lpHome.config` e os `.config` existentes antes de nomear).
|
|
493
|
+
- Não usar `Guid.NewGuid()` em `SeedContentHandler` para o `Key` de um `mediaKey` que deve ser estável entre boots — calcular o GUID deterministicamente a partir do nome do arquivo ou recuperar o existente com `GetByKey`.
|
|
494
|
+
- Não saltear o smoke-boot (passo 3) — problemas de ambiente devem ser detectados antes de portar conteúdo.
|
|
495
|
+
- Não reutilizar `customHtmlSection` para tudo — módulos dedicados permitem edição granular no backoffice e ISR por tag.
|
|
496
|
+
- **Não reinterpretar layout/CSS nem "limpar" o markup** — portar verbatim; o módulo Umbraco só embrulha o mesmo markup da origem. Rebuild = drift.
|
|
497
|
+
- **Não reencodar nem otimizar assets da origem** (o que vai pra `/public`/`seed-assets`) na migração — quebra a paridade de checksum (6b) e altera o render. Isso **não** se aplica a imagens que vêm do Media Picker do Umbraco (ver passo 4d) — essas não são asset da origem, não entram no checksum, e otimizá-las via `next/image` é o comportamento correto, não uma violação de paridade.
|
|
498
|
+
- **Não assumir o tema do template** — portar o Tailwind/`@theme` (e as fontes) da origem; a mesma classe utilitária renderiza diferente entre versões/temas.
|
|
499
|
+
- **Não tratar o screenshot diff como gate único** — o bloqueio são os checks determinísticos (6b); o diff visual é secundário.
|
|
500
|
+
|
|
501
|
+
## Output esperado
|
|
502
|
+
|
|
503
|
+
- `docs/origem-inventario.md` — inventário de seções, conteúdo, mídia e identidade visual da LP de origem.
|
|
504
|
+
- `docs/mapeamento-modulos.md` — tabela de mapeamento seção → doctype/módulo com decisão (reuso direto / ajuste / lift verbatim).
|
|
505
|
+
- `backend/uSync/v17/ContentTypes/*.config` — novos doctypes para os módulos lifted da origem.
|
|
506
|
+
- `backend/Infrastructure/SeedContentHandler.cs` — seed idempotente com textos e mídia iniciais.
|
|
507
|
+
- `backend/seed-assets/` — assets de seed (imagens, vídeos copiados da origem).
|
|
508
|
+
- `frontend/src/components/modules/*.tsx` — componentes dos módulos novos.
|
|
509
|
+
- `frontend/src/components/modules/moduleRegistry.ts` — atualizado com novos aliases.
|
|
510
|
+
- `frontend/src/lib/types.ts` — tipos TypeScript para as props dos novos módulos.
|
|
511
|
+
- `frontend/src/app/globals.css` — tema portado verbatim da origem (paleta + tipografia + fontes).
|
|
512
|
+
- `frontend/e2e/home.spec.ts` — smoke test adaptado para o projeto.
|
|
513
|
+
- `frontend/e2e/parity.spec.ts` — **gerado pela skill**: gate determinístico (DOM+classes, estilo computado, checksum de assets, comportamento) + screenshot diff.
|
|
514
|
+
- `frontend/compare-sections.mjs` — **gerado pela skill**: comparação de esqueleto estrutural origem × destino.
|
|
515
|
+
- `.env` gerado pelo `new-lp.sh` / `new-lp.ps1` — **não versionado** (`.gitignore`).
|
|
516
|
+
|
|
517
|
+
## Referências
|
|
518
|
+
|
|
519
|
+
Repo do template (privado, branch `master`): `polymorphism-tech/MORPH_Umbraco_LP_Template` — obtido automaticamente no passo 0.
|
|
520
|
+
|
|
521
|
+
- `MORPH_Umbraco_LP_Template/scripts/new-lp.sh` e `new-lp.ps1` — scaffold de nova LP
|
|
522
|
+
- `MORPH_Umbraco_LP_Template/backend/Lead/LeadComposer.cs` — registro dos sinks no DI
|
|
523
|
+
- `MORPH_Umbraco_LP_Template/backend/Lead/ILeadSink.cs` — interface de integração de leads
|
|
524
|
+
- `MORPH_Umbraco_LP_Template/backend/Controllers/LeadController.cs` — controller público + `ReadConfig()` + dispatch
|
|
525
|
+
- `MORPH_Umbraco_LP_Template/backend/Lead/GhlLeadSink.cs`, `ProspectProLeadSink.cs`, `WebhookLeadSink.cs` — sinks concretos
|
|
526
|
+
- `MORPH_Umbraco_LP_Template/backend/Infrastructure/SeedContentHandler.cs` — seed idempotente com mídia (MediaPicker3)
|
|
527
|
+
- `MORPH_Umbraco_LP_Template/backend/uSync/v17/ContentTypes/lpHome.config` — doctype raiz da home (Block List `modules`)
|
|
528
|
+
- `MORPH_Umbraco_LP_Template/frontend/src/components/modules/moduleRegistry.ts` — registro dos módulos
|
|
529
|
+
- `MORPH_Umbraco_LP_Template/frontend/src/app/globals.css` — bloco `@theme` para portar o tema da origem
|
|
530
|
+
- `MORPH_Umbraco_LP_Template/frontend/e2e/home.spec.ts` + `playwright.config.ts` — base de e2e (estender com `parity.spec.ts`)
|
|
531
|
+
- `MORPH_Umbraco_LP_Template/docker-compose.yml` — orquestração backend + frontend com variáveis de ambiente
|
|
@@ -4,7 +4,7 @@ description: Orquestra a execução autônoma de tasks de uma feature morph-spec
|
|
|
4
4
|
version: core
|
|
5
5
|
when-to-invoke: após Gate 2 aprovado, ao receber instrução "use /morph-apply" ou "implementar feature"
|
|
6
6
|
user-invokable: true
|
|
7
|
-
cliVersion: "7.
|
|
7
|
+
cliVersion: "7.6.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-apply
|
|
@@ -15,7 +15,7 @@ Executar todas as tasks pendentes de uma feature até `done`, aplicando `morph-e
|
|
|
15
15
|
|
|
16
16
|
## Quando invocar
|
|
17
17
|
|
|
18
|
-
- Após Gate 2 aprovado (verificar `.morph/
|
|
18
|
+
- Após Gate 2 aprovado (verificar `.morph/features/{feature}/feature.json → approvalGates.plan.approved === true`).
|
|
19
19
|
- Usuário pede "use /morph-apply {feature}" ou "implementar {feature}".
|
|
20
20
|
|
|
21
21
|
## Argumentos
|
|
@@ -26,7 +26,7 @@ Executar todas as tasks pendentes de uma feature até `done`, aplicando `morph-e
|
|
|
26
26
|
|
|
27
27
|
- `.morph/features/{feature}/2-plan/tasks.json` existe e é válido (schema check).
|
|
28
28
|
- `.morph/features/{feature}/2-plan/spec.md` e `mandate.md` existem.
|
|
29
|
-
- Gate 2 está marcado aprovado no
|
|
29
|
+
- Gate 2 está marcado aprovado no `feature.json` da feature (ou trust:auto está ativo).
|
|
30
30
|
|
|
31
31
|
## Fluxo
|
|
32
32
|
|
|
@@ -86,11 +86,11 @@ while (true):
|
|
|
86
86
|
(no attempt > 1: passar o feedback priorizado do morph-eval anterior como input)
|
|
87
87
|
result = invoke Skill(morph-eval) with implementation output
|
|
88
88
|
if result.score > best.score: best = result
|
|
89
|
-
// Persista o score no
|
|
90
|
-
// no MOMENTO da implementação (não no fim). O hook
|
|
91
|
-
// Edit em .morph/
|
|
92
|
-
//
|
|
93
|
-
edit .morph/
|
|
89
|
+
// Persista o score no feature.json autoritativo da feature, sob taskScores — torna o
|
|
90
|
+
// evalScore um signal de disco no MOMENTO da implementação (não no fim). O hook
|
|
91
|
+
// gate-guard.js e o Gate 3 leem dali. Edit em .morph/features/** é permitido. Grave:
|
|
92
|
+
// taskScores[task.id] = { lastScore: result.score, bestScore: best.score, attempts: attempt, updatedAt: <ISO> }
|
|
93
|
+
edit .morph/features/{feature}/feature.json com o taskScores acima
|
|
94
94
|
|
|
95
95
|
if result.score >= 9:
|
|
96
96
|
break
|
|
@@ -159,16 +159,16 @@ Sinais de risco para Gate 3:
|
|
|
159
159
|
|
|
160
160
|
Mesma lógica do Gate 1/Gate 2 via `shouldPauseGate(config, signals)`:
|
|
161
161
|
|
|
162
|
-
**Persista a decisão
|
|
163
|
-
`
|
|
164
|
-
(o `signals.evalScore` aqui é o score composto do review-report). O hook `gate-guard.js` confere depois se a ação bateu. Sem o registro o guard não verifica.
|
|
162
|
+
**Persista a decisão no `feature.json` autoritativo da feature ANTES de apresentar ou auto-aprovar** — grave
|
|
163
|
+
`gateDecisions.review = { pause, reason, signals, trust, decidedAt: <ISO> }` em `.morph/features/{feature}/feature.json`
|
|
164
|
+
(o `signals.evalScore` aqui é o score composto do review-report; Edit em `.morph/features/**` é permitido). O hook `gate-guard.js` confere depois se a ação bateu. Sem o registro o guard não verifica.
|
|
165
165
|
|
|
166
166
|
- Pause: `AskUserQuestion` com options `["aprovado, fechar feature", "preciso ajustar", "rollback"]`.
|
|
167
|
-
- Auto: exibir summary do review-report e prosseguir.
|
|
167
|
+
- Auto: exibir summary do review-report, aprovar via `morph-spec approve {feature} review --mode auto`, e prosseguir.
|
|
168
168
|
|
|
169
169
|
### 6. Pós Gate 3
|
|
170
170
|
|
|
171
|
-
-
|
|
171
|
+
- Aprovar o Gate 3: em auto, `morph-spec approve {feature} review --mode auto`; em manual, o hook state-sync registra a resposta "aprovado" em AskUserQuestion. O gate fica gravado no `feature.json` autoritativo da feature — nunca via Edit à mão de `state.json`.
|
|
172
172
|
- Sinalizar: "Feature {nome} concluída. Rode `/morph-archive {feature}` para arquivar."
|
|
173
173
|
- Não arquivar automaticamente.
|
|
174
174
|
|
|
@@ -184,8 +184,8 @@ Mesma lógica do Gate 1/Gate 2 via `shouldPauseGate(config, signals)`:
|
|
|
184
184
|
- Todas as tasks em `tasks.json` com status `done` ou `skipped`.
|
|
185
185
|
- `.morph/features/{feature}/3-implement/recap.md` produzido por morph-implement.
|
|
186
186
|
- `.morph/features/{feature}/4-review/review-report.md` produzido por morph-review.
|
|
187
|
-
- Gate 3
|
|
188
|
-
- `
|
|
187
|
+
- Gate 3 registrado aprovado em `.morph/features/{feature}/feature.json` (autoritativo) — via `morph-spec approve` em auto, ou pelo hook state-sync no caminho manual.
|
|
188
|
+
- Feature com Gate 3 aprovado e pronta para `morph-spec archive` (que carimba `status: "completed"` + `archivedAt`).
|
|
189
189
|
|
|
190
190
|
## Referências
|
|
191
191
|
|