@polymorphism-tech/morph-spec 8.27.0 → 8.29.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.
Files changed (55) hide show
  1. package/CLAUDE.md +1 -0
  2. package/README.md +2 -2
  3. package/bin/morph-spec.js +9 -4
  4. package/claude-plugin.json +1 -1
  5. package/docs/CHEATSHEET.md +3 -2
  6. package/docs/QUICKSTART.md +1 -1
  7. package/framework/MORPH.md +8 -8
  8. package/framework/agents.json +14 -3
  9. package/framework/commands/create-umbraco-lp.md +1 -1
  10. package/framework/docs/CLI.md +6 -5
  11. package/framework/mcp/registry.json +59 -8
  12. package/framework/skills/core/create-umbraco-lp/SKILL.md +13 -2
  13. package/framework/skills/core/create-umbraco-lp/references/umbraco-lp-patterns.md +598 -0
  14. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +92 -7
  15. package/framework/skills/core/morph-apply/SKILL.md +1 -1
  16. package/framework/skills/core/morph-brainstorming/SKILL.md +1 -1
  17. package/framework/skills/core/morph-eval/SKILL.md +1 -1
  18. package/framework/skills/core/morph-hotfix/SKILL.md +1 -1
  19. package/framework/skills/core/morph-implement/SKILL.md +1 -1
  20. package/framework/skills/core/morph-plan/SKILL.md +1 -1
  21. package/framework/skills/core/morph-proposal/SKILL.md +1 -1
  22. package/framework/skills/core/morph-review/SKILL.md +1 -1
  23. package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
  24. package/framework/skills/core/morph-standards/SKILL.md +1 -1
  25. package/framework/skills/core/morph-uiux/SKILL.md +1 -1
  26. package/framework/standards/PROVENANCE.json +41 -0
  27. package/framework/standards/STANDARDS.json +218 -7
  28. package/framework/standards/frontend/design-system/ai-image-generation.md +43 -0
  29. package/framework/standards/frontend/design-system/ai-video-generation.md +113 -0
  30. package/framework/standards/frontend/design-system/premium-finish.md +160 -0
  31. package/framework/standards/frontend/gsap/gsap-core.md +1 -1
  32. package/framework/standards/frontend/gsap/gsap-plugins.md +1 -1
  33. package/framework/standards/frontend/gsap/gsap-scrolltrigger.md +3 -1
  34. package/framework/standards/frontend/nextjs/motion-patterns.md +3 -1
  35. package/framework/standards/frontend/scroll-driven/frame-scrub.md +287 -0
  36. package/framework/standards/frontend/scroll-driven/scroll-components.md +256 -0
  37. package/framework/standards/frontend/scroll-driven/smooth-scroll.md +148 -0
  38. package/framework/standards/frontend/seo/meta-schema-audit.md +134 -0
  39. package/package.json +1 -1
  40. package/src/commands/project/doctor.js +9 -4
  41. package/src/commands/project/worktree.js +45 -3
  42. package/src/commands/state/advance.js +12 -0
  43. package/src/commands/state/approve.js +20 -0
  44. package/src/commands/state/create.js +4 -1
  45. package/src/commands/state/delete.js +63 -4
  46. package/src/commands/state/finish.js +20 -2
  47. package/src/commands/state/gate-check.js +13 -0
  48. package/src/commands/state/gate-decision.js +9 -0
  49. package/src/commands/state/handoff.js +48 -6
  50. package/src/commands/state/list.js +124 -10
  51. package/src/commands/state/score.js +9 -0
  52. package/src/lib/generators/handoff-generator.js +11 -4
  53. package/src/lib/worktree/feature-truth.js +248 -0
  54. package/src/lib/worktree/fleet-scanner.js +1 -65
  55. package/src/lib/worktree/worktree-audit.js +103 -17
package/CLAUDE.md CHANGED
@@ -114,6 +114,7 @@ When modifying framework components, verify the full scope of related files.
114
114
  | `framework/templates/**` | REGISTRY.json (template registered), valid Handlebars helpers |
115
115
  | `src/core/paths/output-schema.js`| Run `npm run generate` to update phase-utils.js |
116
116
  | `framework/MORPH.md` | All core skills (cross-references), CLAUDE.md (slash command list) |
117
+ | `src/lib/worktree/feature-truth.js` | The four leaders reading the same "which tree owns this feature" predicate: `fleet-scanner.js`, `list.js`, `handoff.js`, and the mutator guard (`approve.js`/`advance.js`/`score.js`/`gate-decision.js`/`gate-check.js`) |
117
118
 
118
119
  ### Test Coverage
119
120
 
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  > Spec-driven development framework for multi-stack projects. Turns feature requests into implementation-ready code through structured, AI-orchestrated phases.
4
4
 
5
5
  **Package:** `@polymorphism-tech/morph-spec`
6
- **Version:** 8.27.0
6
+ **Version:** 8.29.0
7
7
  **Requires:** Node.js 22+, Claude Code
8
8
 
9
9
  ---
@@ -368,4 +368,4 @@ Code generated by morph-spec (contracts, templates, implementation output) belon
368
368
 
369
369
  ---
370
370
 
371
- *morph-spec v8.27.0 by [Polymorphism Tech](https://polymorphism.tech)*
371
+ *morph-spec v8.29.0 by [Polymorphism Tech](https://polymorphism.tech)*
package/bin/morph-spec.js CHANGED
@@ -126,7 +126,7 @@ program
126
126
  .option('--full', 'Run full health check')
127
127
  .option('--mcp', 'Check MCP server configuration')
128
128
  .option('--worktrees', "Reconcile git's worktree registry × the directories on disk × known features")
129
- .option('--prune', 'With --worktrees: drop git records for worktree directories that are already gone')
129
+ .option('--prune', "With --worktrees: drop git records for worktree directories that are already gone, and clear any feature's root worktree registration pointing at one of them")
130
130
  .option('--json', 'With --worktrees: output as JSON')
131
131
  .option('--reset', 'Remove morph-managed entries from .claude/settings.local.json')
132
132
  .action(doctorCommand);
@@ -144,6 +144,7 @@ program
144
144
  .description('List every feature × status × gates (G1/G2/G3) × workType — the audit board')
145
145
  .option('--all', 'Include archived features (.morph/archive)')
146
146
  .option('--archived', 'Include archived features (alias of --all)')
147
+ .option('--no-branch', 'Skip reading morph/{feature} for truth — report only what is on disk in this tree')
147
148
  .option('--json', 'Output as JSON')
148
149
  .action((options) => listCommand(options));
149
150
 
@@ -187,9 +188,12 @@ program
187
188
 
188
189
  program
189
190
  .command('delete <feature>')
190
- .description('Delete a feature (hard by default; --archive marks it abandoned). Refuses if a gate is approved unless --force')
191
+ .description('Delete a feature (hard by default; --archive marks it abandoned). Refuses if a gate is approved unless --force. Also refuses if a worktree is alive elsewhere, unless --with-worktree')
191
192
  .option('--archive', 'Soft delete: move to .morph/archive with status "abandoned" instead of removing')
192
- .option('--force', 'Delete even when a gate is approved')
193
+ .option('--force', 'Delete even when a gate is approved (does NOT bypass the live-worktree refusal — see --with-worktree)')
194
+ .option('--with-worktree', 'Tear down the live worktree first (PROVE-or-DECLINE); only deletes if that succeeds. Branch survives unless --delete-branch')
195
+ .option('--delete-branch', 'With --with-worktree, also delete the feature branch (default: kept — it is the work)')
196
+ .option('--discard-ignored', 'With --with-worktree, discard gitignored files unique to the worktree instead of declining removal')
193
197
  .option('--no-git', 'Skip git staging of the removal')
194
198
  .option('--json', 'Output as JSON')
195
199
  .action((feature, options) => deleteCommand(feature, options));
@@ -255,8 +259,9 @@ program
255
259
 
256
260
  program
257
261
  .command('handoff <feature>')
258
- .description('Generate the compressed session-handoff document (.morph/features/{f}/handoff.md) — pointers, not copies')
262
+ .description('Generate the compressed session-handoff document (.morph/features/{f}/handoff.md) — pointers, not copies. Resolves the tree that owns the feature\'s state (worktree, or morph/{feature} if only committed) and writes there — not necessarily cwd.')
259
263
  .option('--json', 'Output as JSON')
264
+ .option('--here', 'Force building/writing in the current directory, even if another tree owns this feature')
260
265
  .action((feature, options) => handoffCommand(feature, options));
261
266
 
262
267
  // ─── Use cases ────────────────────────────────────────────────────────────
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "morph-spec",
3
- "version": "8.27.0",
3
+ "version": "8.29.0",
4
4
  "displayName": "MORPH-SPEC Framework",
5
5
  "description": "Spec-driven development with 38 agents and 8-phase workflow for .NET/Blazor/Next.js/Azure",
6
6
  "publisher": "polymorphism-tech",
@@ -121,7 +121,8 @@ morph-spec worktree setup {feature} # create worktrees/{feature}/ on bran
121
121
  morph-spec worktree link [--all] # relink shared infra (node_modules[, .claude, .morph/framework])
122
122
  morph-spec worktree list [--json] # active feature worktrees with phase/gate status
123
123
  # Close a worktree feature with `morph-spec finish {feature} --pr|--merge` (see above) — it absorbed `worktree finish`.
124
- morph-spec handoff {feature} [--json] # regenerate the compressed session-handoff doc (pointers, not copies)
124
+ morph-spec handoff {feature} [--json] [--here] # regenerate the handoff doc — resolves the owning tree and writes THERE, safe to run from the root
125
+ morph-spec doctor --worktrees [--prune] # reconcile git's registry × disk × features; --prune also clears a dangling root worktree.path pointer
125
126
  morph-spec fleet [--json] # every active feature (root + worktrees) with phase/gates/ports/handoff/activity
126
127
  ```
127
128
 
@@ -185,4 +186,4 @@ Never edited directly — go through CLI commands or `morph-spec update` instead
185
186
 
186
187
  ---
187
188
 
188
- *morph-spec v8.27.0 by Polymorphism Tech*
189
+ *morph-spec v8.29.0 by Polymorphism Tech*
@@ -249,4 +249,4 @@ morph-spec doctor
249
249
 
250
250
  ---
251
251
 
252
- *morph-spec v8.27.0 by Polymorphism Tech*
252
+ *morph-spec v8.29.0 by Polymorphism Tech*
@@ -113,18 +113,18 @@ Nem todo trabalho merece o pipeline completo. Cada feature nasce com **identidad
113
113
 
114
114
  ### 2.2 Desenvolvimento paralelo por worktree (frota)
115
115
 
116
- Features independentes podem ser desenvolvidas **em paralelo, cada uma no seu git worktree**, uma instância de Claude Code por worktree. `morph-spec create <f> --description "<o quê/porquê>" --worktree` (ou `morph-spec worktree setup <f>`) cria `worktrees/{f}/` na branch `morph/{f}`. Contrato:
116
+ Features independentes podem ser desenvolvidas **em paralelo, cada uma no seu git worktree**, uma instância de Claude Code por worktree. `morph-spec create <f> --description "<o quê/porquê>" --worktree` (ou `worktree setup <f>`) cria `worktrees/{f}/` na branch `morph/{f}`. Contrato:
117
117
 
118
118
  - **Uma feature = um worktree = uma instância.** Nunca abra duas instâncias na mesma feature (setup recusa, exit 3).
119
- - `feature.json` viaja **commitado na branch** `morph/{f}`. `state.json` local do worktree é índice derivável — materializado por self-heal, nunca sincronizado à mão.
119
+ - `feature.json` viaja **commitado na branch** `morph/{f}`; a cópia da raiz é esqueleto do nascimento, nunca mais atualizado — leitores (`list`/`handoff`/`fleet`) seguem a branch, mutadores recusam gravar fora da árvore dona (detalhes: CLI.md). `state.json` do worktree é índice derivável — self-heal, nunca sincronizado à mão.
120
120
  - Infra compartilhada (`node_modules`, `.claude`, `.morph/framework`) linkada por junction; `memory/`/`logs/` são locais de cada worktree. Faltou infra (worktree aberto com `git worktree add` puro) → `morph-spec worktree link --all`.
121
121
  - **Só o morph remove worktree.** Remoção genérica — `git worktree remove` (com OU sem `--force`), `rm -rf`, `Remove-Item -Recurse`, explorador/IDE — **atravessa a junction** e apaga o conteúdo real compartilhado da raiz (`.claude`/`node_modules`/`.morph/framework`), não só o link, saindo com sucesso. Foi assim que uma raiz teve `.claude/` e `.morph/framework/` esvaziados. Só `morph-spec` desmonta a junction ANTES de remover (PROVE-or-DECLINE): **fechar a feature** = `morph-spec finish <f> --pr|--merge`; **só desmontar o worktree** (feature segue aberta) = `morph-spec worktree remove <f>` — ou `--target <path>` para worktree sem feature. O hook `block-destructive-worktree-removal` barra a versão crua, mas a regra vale antes dele.
122
- - **Ambiente separado (portas):** o setup aloca um **bloco de portas determinístico** por feature em `.morph/worktree.env` (`PORT`, `MORPH_PORT_RANGE`; `ASPNETCORE_URLS` se dotnet). **Suba dev servers SEMPRE nas portas do bloco** — as portas padrão (3000/5000) pertencem à raiz. Arquivo local, git-invisível; SessionStart injeta as portas. Overrides em `config.json → fleet.basePort/blockSize`.
123
- - **Banco de dados:** compartilhado por padrão. Isolamento real (Neon MCP): branch de DB `morph/{f}` + connection string no `.morph/worktree.env`; sem Neon, prefixe schema/DB local pelo nome da feature. Migration destrutiva compartilhando banco → integre antes de abrir worktrees irmãos.
124
- - **Limitação conhecida:** `node_modules` compartilhado (junction) — mudança de dependência afeta os irmãos. Mudou dependência: conclua e integre (`finish`) antes de seguir em paralelo.
125
- - **Handoff entre sessões:** `.morph/features/{f}/handoff.md` — documento de retomada comprimido, regenerável (`morph-spec handoff <f>`), **aponta** para os artefatos em vez de copiá-los. Regenerado em `worktree setup`/`create --worktree`/`advance`/`approve`. **Leia-o antes de re-derivar contexto à mão.** Nunca edite manualmente.
126
- - **Visão da frota:** `morph-spec fleet` agrega raiz + worktrees (fase, gates, tasks, portas, handoff, último commit, sujeira). `morph-spec worktree list` é a visão enxuta por worktree.
127
- - **Ownership de arquivos:** sem lock em runtime — colisão se resolve **em plan-time** (`morph-spec dag` detecta overlap de `outputs`, recomenda `isolation: "worktree"`) e **por construção** (uma feature = um worktree = um escritor). Arquivos compartilhados convergem no merge do `finish`; conflito ali é escopo sobreposto, não bug.
122
+ - **Ambiente separado (portas):** setup aloca **bloco de portas determinístico** por feature em `.morph/worktree.env` (`PORT`, `MORPH_PORT_RANGE`; `ASPNETCORE_URLS` se dotnet). **Suba dev servers SEMPRE nas portas do bloco** — as padrão (3000/5000) pertencem à raiz. Local, git-invisível; SessionStart injeta as portas. Override: `config.json → fleet.basePort/blockSize`.
123
+ - **Banco de dados:** compartilhado por padrão; isolar via Neon MCP: branch de DB `morph/{f}` + connection string em `.morph/worktree.env` (sem Neon, prefixe schema/DB pelo nome da feature). Migration destrutiva → integre antes de abrir worktrees irmãos.
124
+ - **Limitação conhecida:** `node_modules` compartilhado (junction) — dependência mudada afeta os irmãos; integre (`finish`) antes de seguir em paralelo.
125
+ - **Handoff entre sessões:** `.morph/features/{f}/handoff.md` — retomada comprimida, regenerável (`morph-spec handoff <f>`, resolve a árvore dona sozinho), **aponta** para os artefatos em vez de copiá-los. Regenerado em `setup`/`create --worktree`/`advance`/`approve`. **Leia-o antes de re-derivar contexto à mão** — nunca edite manualmente.
126
+ - **Visão da frota:** `fleet` agrega raiz + worktrees (fase, gates, tasks, portas, handoff, commit, sujeira); `worktree list` é a visão enxuta por worktree.
127
+ - **Ownership de arquivos:** sem lock runtime — colisão resolve **em plan-time** (`dag` detecta overlap de `outputs`, recomenda `isolation: "worktree"`) e **por construção** (uma feature = um worktree = um escritor). Arquivos compartilhados convergem no merge do `finish`; conflito ali é escopo sobreposto, não bug.
128
128
  - **Fechamento:** aprove o Gate 3, depois **`morph-spec finish <f> --pr|--merge`** — encerramento único e simétrico. **Arquiva** commitando `features/{f} → archive/{f}` **dentro de `morph/{f}`** e integra: `--merge` faz `--no-ff` **local** (sem push; recusa se sujo; aborta em conflito); `--pr` faz `push` + `gh pr create` (exige `origin`+`gh`, checados **antes** de arquivar). Worktree desmontado (PROVE-or-DECLINE), portas liberadas; no `--pr` a branch é **preservada**. `morph-spec archive` fica para arquivar-sem-integrar (features legadas, faxina).
129
129
 
130
130
  ---
@@ -101,16 +101,27 @@
101
101
  "domains": ["ui", "ux", "design-system", "accessibility"],
102
102
  "keywords": [
103
103
  "design", "ui", "ux", "design system", "tokens", "wcag",
104
- "accessibility", "mockup", "flow", "shadcn", "tailwind"
104
+ "accessibility", "mockup", "flow", "shadcn", "tailwind",
105
+ "motion", "scroll", "scrub", "gsap", "lenis", "seo"
105
106
  ],
106
107
  "stack": ["*"],
107
108
  "standards": [
108
109
  "frontend-design-system-naming",
109
110
  "frontend-design-system-animations",
110
- "frontend-nextjs-motion-patterns"
111
+ "frontend-nextjs-motion-patterns",
112
+ "frontend-design-system-premium-finish",
113
+ "frontend-design-system-ai-image-generation",
114
+ "frontend-design-system-ai-video-generation",
115
+ "frontend-scroll-driven-frame-scrub",
116
+ "frontend-scroll-driven-smooth-scroll",
117
+ "frontend-scroll-driven-scroll-components",
118
+ "frontend-seo-meta-schema-audit",
119
+ "frontend-gsap-core",
120
+ "frontend-gsap-scrolltrigger",
121
+ "frontend-gsap-plugins"
111
122
  ],
112
123
  "tools": "Read, Write, Edit, Glob, Grep",
113
- "mcp": ["context7", "stitch", "gemini-image", "playwright"],
124
+ "mcp": ["context7", "stitch", "gemini-image", "playwright", "seedance"],
114
125
  "model": "sonnet",
115
126
  "persona": "Você desenha sistemas, não telas isoladas. Pensa em tokens (cor, espaço, tipografia), componentes reutilizáveis, estados (default/hover/focus/disabled/loading/error), acessibilidade WCAG AA. Mockups em Tailwind direto, sem Figma intermediário."
116
127
  },
@@ -30,6 +30,6 @@ Cria um projeto de landing page novo a partir do template canônico `MORPH_Umbra
30
30
  ## Referências
31
31
 
32
32
  - `.claude/skills/create-umbraco-lp/SKILL.md`
33
- - `docs/umbraco-lp-patterns.md` (em `main`)
33
+ - `references/umbraco-lp-patterns.md` (dentro da skill `create-umbraco-lp`)
34
34
 
35
35
  **Projeto:** $ARGUMENTS
@@ -11,22 +11,23 @@
11
11
  - **`morph-spec score <feature> <task> <score> [--dims a,c,q,t]`** — grava `taskScores` no feature.json com timestamp server-side; **nunca edite taskScores à mão**. Em PowerShell, cite o valor de `--dims` entre aspas (`--dims "9.5,9,9,9"`) — sem aspas, a lista pode chegar como tokens separados; o parser aceita ambas as formas, mas citar evita qualquer ambiguidade.
12
12
  - **`morph-spec gate-check <feature> <gate> [--signals <json>] [--trust <mode>] [--json]`** — o caminho **default**: coleta do disco os sinais que `shouldPauseGate` (risk-detector.js) já sabe avaliar (`workType`, `taskCount`, verificação, sha/diffstat), decide a pausa e persiste `gateDecisions.{gate}`. `--signals` aceita só as chaves que o disco genuinamente não expõe sozinho — `designSystemDivergence`, `standardsViolations`, `evalScore` — qualquer outra chave é ignorada com aviso. No **review**, o `evalScore` real vem do token `**Composite-Score:** {n.n}` do `evaluator-report.md` (o avaliador o grava por contrato). **Score que não venha desse token PAUSA o Gate 3**, qualquer que seja o valor: o proxy (média das `taskScores`) e o override de `--signals` medem auto-avaliação de quem implementou, não qualidade da feature — em campo, 7 tasks com auto-score 9.0-9.3 auto-passaram um produto que o avaliador independente pontuou 7.2 e reprovou com 3 P1. `evalScoreSource` registra a procedência (`evaluator-report` / `taskScores-proxy` / `signals-override`). Para forçar a passagem mesmo assim, use `gate-decision --pause false` — explícito e registrado. Fora do review a regra não vale: antes de haver implementação não existe avaliador independente. Um stamp de `verification` com status `fail` (último `morph-spec verify` vermelho) **força pausa** em qualquer gate. Um stamp `pass` mas **stale** (sha diferente do HEAD atual — código mudou depois do último verify) força pausa **só no ship** (`review`); em `plan`/`uiux`/`proposal` a staleness é computada e reportada em `signals`, mas não pausa nada — é regra de ship, não de gate genérico. Stamp **ausente** nunca pausa por isso (features legadas sem stamp).
13
13
  - **`morph-spec gate-decision <feature> <gate> --pause <bool> [--reason <txt>] [--signals <json>]`** — o **escape manual**: registra uma decisão já tomada pela sessão (ex.: `discoveryMode: "interview"`, sinal que `shouldPauseGate` nem representa). Mesmo write path do `gate-check` — grava `gateDecisions` no feature.json com timestamp server-side (shape em `framework/schemas/feature.schema.json`); **nunca edite gateDecisions à mão** — o Edit manual colide com o hook state-sync.
14
+ - **Guarda de árvore dona:** `approve`/`unapprove`/`advance`/`score`/`gate-decision`/`gate-check` recusam (exit 1) quando um worktree vivo é dono da feature e a sessão está em outra árvore — sem isso o comando carimbaria o gate/score/decisão na cópia congelada da raiz, criando o drift em vez de só herdá-lo. A recusa nomeia o worktree e o comando exato para rodar lá; `--force` **não** dispensa (é sobre gate order, não sobre árvore). Rodando de dentro do worktree dono, ou numa feature sem worktree algum, nada muda.
14
15
 
15
16
  ## Features
16
17
 
17
18
  - **`morph-spec create <feature> --description "<o quê/porquê>" --type <feature|bug|chore|hotfix> [--request "<txt>"] [--name "<nome>"] [--worktree]`** — cria a estrutura da feature + `feature.json` canônico explicitamente (nascimento explícito no início da proposal; o hook state-sync continua cobrindo o caminho implícito como fallback). **Identidade no nascimento:** `name` (default = slug) e `description` são carimbados; a `--description` é **obrigatória** numa feature nova (ou `--request`, que a semeia) — sem nenhuma das duas o `create` recusa (fim dos scaffolds anônimos). `--type` classifica direto; `--request "<pedido>"` roda o detector determinístico (empate/sem sinal sai com exit 2 e candidatos — a LLM decide e re-roda com `--type` + `--request`/`--description`); `--worktree` já nasce a feature no seu worktree próprio. **Toda feature nasce numa branch `morph/{f}`** (ramificada da default) mesmo sem `--worktree` — por isso o fechamento por `morph-spec finish` é idêntico nos dois fluxos.
18
- - **`morph-spec list [--all] [--json]`** — o quadro de auditoria: uma linha por feature × workType × status × fase × gates G1/G2/G3 × descrição, com `⚠` para inconsistências (sem descrição, ordem de gate). `--all` inclui as arquivadas; `--json` para scripting. É o complemento do `delete` (enxergar o que está morto antes de remover).
19
- - **`morph-spec delete <feature> [--archive] [--force]`** — remove uma feature. Hard por padrão (pasta apagada, git-aware, + entrada podada do índice raiz para não ressuscitar); `--archive` move para `.morph/archive` com status `abandoned` em vez de apagar. Recusa se algum gate estiver aprovado, a menos que `--force`. Use para faxina de scaffolds mortos e drafts abandonados (`archive` é para trabalho concluído; `delete` é para trabalho morto).
19
+ - **`morph-spec list [--all] [--json] [--no-branch]`** — o quadro de auditoria: uma linha por feature × workType × status × fase × gates G1/G2/G3 × descrição, com `⚠` para inconsistências (sem descrição, ordem de gate). Uma feature com worktree/branch ativos tem a cópia da RAIZ congelada no instante do nascimento (`create --worktree` grava lá um esqueleto completo e nunca mais atualiza) — por padrão o `list` lê a verdade commitada em `morph/{f}` quando ela diverge do disco desta árvore (mesma leitura do `fleet`), reportando `source: 'branch'|'disk'` e uma coluna **WHERE** (`raiz` / `branch morph/{f}` / `worktree {dir}` / `⚠ worktree ausente`). `--no-branch` volta ao disco puro, sem custo de git. Um ponteiro `worktree.path` que não existe mais aparece com o flag `worktree-missing` (repare com `doctor --worktrees --prune`). Uma feature já arquivada em outra árvore/branch, cujo esqueleto sobrevive aqui, ganha o flag `archived-elsewhere` em vez de sumir da lista (read-only: nunca ressuscita nem poda). `--all` inclui as arquivadas; `--json` para scripting. É o complemento do `delete` (enxergar o que está morto antes de remover).
20
+ - **`morph-spec delete <feature> [--archive] [--force] [--with-worktree]`** — remove uma feature. Hard por padrão (pasta apagada, git-aware, + entrada podada do índice raiz para não ressuscitar); `--archive` move para `.morph/archive` com status `abandoned` em vez de apagar. Recusa se algum gate estiver aprovado, a menos que `--force`. Recusa TAMBÉM se um worktree vivo estiver em outra árvore (`feature-has-worktree` — apagar o registro da raiz órfão o worktree+branch em silêncio) — `--force` **não** dispensa isto (é consentimento sobre gate, não sobre filesystem); feche primeiro com `finish`/`worktree remove`, ou passe `--with-worktree` para desmontar o worktree (PROVE-or-DECLINE) e apagar num passo só — a branch sobrevive por padrão (`--delete-branch` para descartá-la também). Use para faxina de scaffolds mortos e drafts abandonados (`archive` é para trabalho concluído; `delete` é para trabalho morto).
20
21
  - **`morph-spec retype <feature> <newType>`** — muda o tipo de trabalho mid-flight; desfaz só os gates carimbados por política, preserva aprovações humanas; bloqueado após o Gate 3.
21
22
 
22
23
  ## Worktree / Fleet
23
24
 
24
- - **`morph-spec finish <feature> --pr|--merge`** — o **encerramento único** da feature (worktree ou comum): **arquiva** commitando `features/{f} → archive/{f}` dentro de `morph/{f}` — promovendo junto, no mesmo commit, os use cases da feature para o acervo permanente `.morph/specs/usecases/` com `status: implemented` (fail-open: sem `2-plan/usecases/`, no-op silencioso) — depois integra. Um `UC-{nn}` cuja promoção colide com outra feature já publicada sob o mesmo número (`ucCollisions` no output; ver `usecase reserve` abaixo) **não é promovido**, mas isso nunca recusa o `finish` — só aquele use case fica de fora, com a mensagem apontando onde o arquivo ficou e como recuperar. `--merge` faz merge `--no-ff` **local** na default (sem push); `--pr` faz `push` + `gh pr create` (exige `origin` + `gh`, checados **antes** de arquivar — um `--pr` recusado não deixa meio-estado). Desmonta o worktree (PROVE-or-DECLINE) e libera o bloco de portas; no `--pr` **preserva** a branch (o PR precisa dela) e fica em `morph/{f}`. Flags: `--keep` mantém o worktree, `--force` dispensa o Gate 3, `--discard-ignored` consente descartar gitignored no teardown. Substitui o antigo `worktree finish` + `archive` separados. **Retomável:** conflito de merge, checkout ou push falham *depois* do archive (é o commit do archive que viaja com a branch) — re-executar o `finish` retoma na integração e sai com `resumed: true`, em vez de recusar por feature ausente. Se a integração já tiver acontecido, recusa com `already-finished` — e o que conta como "já integrada" depende do modo: no `--merge`, branch contida na default; no `--pr`, PR aberto para `morph/{f}` (a branch de um `--pr` concluído fica não-mergeada de propósito, então só o teste de ancestralidade deixaria passar um segundo `gh pr create`). Sem conseguir provar nem uma coisa nem outra, recusa com `resume-undecidable` em vez de retomar às cegas.
25
+ - **`morph-spec finish <feature> --pr|--merge`** — o **encerramento único** da feature (worktree ou comum): **arquiva** commitando `features/{f} → archive/{f}` dentro de `morph/{f}` — promovendo junto, no mesmo commit, os use cases da feature para o acervo permanente `.morph/specs/usecases/` com `status: implemented` (fail-open: sem `2-plan/usecases/`, no-op silencioso) — depois integra. Um `UC-{nn}` cuja promoção colide com outra feature já publicada sob o mesmo número (`ucCollisions` no output; ver `usecase reserve` abaixo) **não é promovido**, mas isso nunca recusa o `finish` — só aquele use case fica de fora, com a mensagem apontando onde o arquivo ficou e como recuperar. `--merge` faz merge `--no-ff` **local** na default (sem push); `--pr` faz `push` + `gh pr create` (exige `origin` + `gh`, checados **antes** de arquivar — um `--pr` recusado não deixa meio-estado). Desmonta o worktree (PROVE-or-DECLINE) e libera o bloco de portas; no `--pr` **preserva** a branch (o PR precisa dela) e fica em `morph/{f}`. Flags: `--keep` mantém o worktree **e** o registro dele na raiz (válido enquanto o diretório existir) — o output reporta `worktree: {path, branch}` e um `hint` lembrando `worktree remove <f>` para fechar depois; `--force` dispensa o Gate 3; `--discard-ignored` consente descartar gitignored no teardown. Substitui o antigo `worktree finish` + `archive` separados. **Retomável:** conflito de merge, checkout ou push falham *depois* do archive (é o commit do archive que viaja com a branch) — re-executar o `finish` retoma na integração e sai com `resumed: true`, em vez de recusar por feature ausente. Se a integração já tiver acontecido, recusa com `already-finished` — e o que conta como "já integrada" depende do modo: no `--merge`, branch contida na default; no `--pr`, PR aberto para `morph/{f}` (a branch de um `--pr` concluído fica não-mergeada de propósito, então só o teste de ancestralidade deixaria passar um segundo `gh pr create`). Sem conseguir provar nem uma coisa nem outra, recusa com `resume-undecidable` em vez de retomar às cegas.
25
26
  - **`morph-spec usecase reserve [--count <n>] [--feature <name>] [--json]`** — reserva atomicamente os próximos N números `UC-{nn}`, compartilhado por **todo worktree** deste repo (`<git-common-dir>/morph-spec/uc-registry.json`, mesmo mecanismo do `claim`/env-registry do fleet). Substitui o scan manual "leia o acervo + as features da árvore e some 1": um scan só enxerga a ÁRVORE CORRENTE, então dois worktrees planejando ao mesmo tempo mintavam o MESMO número — colisão que a promoção do `finish` resolvia sobrescrevendo por ID, apagando o use case do primeiro a chegar (já custou perda de dados em produção). O contador nunca regride (semeado do maior número já visto no acervo + nas features da árvore + no próprio registro). `--feature` é mais que auditoria: é o sinal que o guarda de colisão do `finish`/`archive` usa para distinguir uma correção legítima (`bug`, nunca reserva — reaproveita um número publicado) de uma colisão real (a feature reservou o número e o acervo já tem outra dona nele); sem `--feature`, infere a única feature ativa se for inequívoco. Fora de um repositório git: cai para o scan local (avisa no output, `shared: false`) — ainda evita autocolisão dentro da mesma árvore, mas sem a garantia entre worktrees.
26
27
  - **`morph-spec archive <feature>`** — arquiva-**sem-integrar** (atômico, git-aware, commita o move escopado por pathspec): reservado a features legadas (sem branch `morph/{f}`) e faxina. O fechamento normal (arquiva **e** integra num passo) é `morph-spec finish`. É o que `/morph-archive` chama.
27
28
  - **`morph-spec worktree setup|link|remove|list`** — gerencia o ciclo do worktree por feature (`setup` cria `worktrees/{f}/` na branch `morph/{f}`, aloca o bloco de portas e escreve `.morph/worktree.env`; `link` religa infra compartilhada; `list` mostra fase/gate de cada worktree ativo). **Fechar a feature** continua sendo `morph-spec finish` (acima).
28
29
  - **`morph-spec worktree remove [feature] [--target <path>] [--discard-ignored] [--delete-branch]`** — a remoção **segura** de um worktree, sem fechar a feature. Desmonta as junctions de infra ANTES de deixar o git chegar no diretório e recusa se não conseguir **provar** que é seguro (PROVE-or-DECLINE) — `git worktree remove` cru, com ou sem `--force`, atravessa a junction e apaga o `.claude/`/`.morph/framework/`/`node_modules/` **reais da raiz**, saindo com sucesso. Use `--target` para worktree sem feature registrada (os que o `isolation: "worktree"` cria). A branch **sobrevive** por padrão: remover o worktree não é descartar o trabalho.
29
- - **`morph-spec handoff <feature>`** — regenera o documento de retomada (`.morph/features/{f}/handoff.md`): comprimido, derivado, aponta para os artefatos em vez de copiá-los. Gerado automaticamente em `setup`/`create --worktree`/`advance`/`approve`.
30
+ - **`morph-spec handoff <feature> [--here]`** — regenera o documento de retomada (`.morph/features/{f}/handoff.md`): comprimido, derivado, aponta para os artefatos em vez de copiá-los. **Resolve a árvore dona** antes de gerar (mesma lógica do `list`/`fleet`) e escreve LÁ, não necessariamente no cwd — rodar da raiz com o worktree vivo é seguro: o handoff sai correto e no lugar certo, em vez do esqueleto de nascimento da raiz. Sem árvore nenhuma materializada (só existe commitado em `morph/{f}`) recusa com `feature-not-in-tree`, nomeando a branch, em vez de gerar um documento apontando para arquivos inexistentes. `--here` força construir/escrever no cwd mesmo assim. Gerado automaticamente em `setup`/`create --worktree`/`advance`/`approve`.
30
31
  - **`morph-spec fleet`** — visão da frota: toda feature ativa (raiz + worktrees) com fase, gates pendentes, tasks, **remoto** (`local`/`pushed`/`PR #N`), **claim** (que sessão está na feature), portas, handoff e atividade. Use entre sessões para decidir onde atuar.
31
32
  - **`morph-spec claim [feature] [--release] [--list] [--all] [--json]`** — posse **advisory** de feature. Cada sessão reclama sua feature ativa automaticamente (hook SessionStart) e renova o heartbeat a cada tool call; o registro vive em `<git-common-dir>/morph-spec/claims.json`, compartilhado por todos os worktrees. **`morph-spec claim <feature>` (sem flags) ADQUIRE** — se esta árvore já tinha claim vivo em outra feature, ele é liberado no mesmo passo (o CLI reporta "movido de X para Y"); é o jeito de trocar de feature no meio da tarefa sem a dança de dois passos `--release` + reclamar. `--list` força a leitura sem adquirir mesmo passando uma feature. **Claims avisam, nunca bloqueiam** — mas se o SessionStart avisar que outra sessão tem claim vivo, **confirme antes de `finish`/`archive`/`delete`**: duas sessões na mesma feature já custaram um recap.md inteiro e um sub-agent (o worktree sumiu debaixo dele). Claim órfão de sessão morta: `morph-spec claim <f> --release`. **O board mostra só claims VIVOS** (dentro do TTL de 30 min): um claim morto não avisa mais nada, e deixá-lo listado transformava o quadro em ruído (features encerradas há 20h e 73h ainda aparecendo). `--all` mostra o registro inteiro; a contagem dos omitidos sai no rodapé, nunca em silêncio.
32
33
 
@@ -35,7 +36,7 @@
35
36
  - **`morph-spec dag <feature>`** — análise do DAG de tasks.json com decisão sequencial vs paralelo **ponderada por effort** (raízes S vão inline, nunca ganham agente próprio). Também cruza `tasks.json` com os use cases: `usecase` apontando para um `UC-{nn}` inexistente é **erro** (exit 1, não despacha); pós-condição órfã e `doneCriteria` que parafraseou em vez de copiar são avisos. `--mermaid` nunca é bloqueado.
36
37
  - **`morph-spec verify <feature> [task] [--json] [--skip-e2e] [--fresh-e2e]`** — cadeia determinística build+testes+validadores+**e2e** (código antes do juiz LLM); escopo de task quando informado, senão a feature inteira. O nó `e2e` é o único que **executa** a aplicação: sobe o `docker compose` do projeto, espera todo serviço ficar saudável e roda as specs Playwright quando existem (sem specs, ainda vale como smoke — "a app sobe" já pega o serviço não registrado que derruba o startup e passa batido no build). Aplicabilidade: escopo de feature (Gate 3) **sempre** roda; escopo de task roda para `group` `frontend`/`backend`/`infra` e dispensa `tests`/`docs`. **Dispensa legítima × setup faltando:** projeto sem compose e sem a integração `docker` é `skip` com razão; projeto que **declara** `docker` e não tem compose é `fail` — é verificação que nunca foi montada, não projeto que não containeriza.
37
38
  - **`morph-spec e2e up|status|down <feature>`** — ciclo de vida do stack de verificação, compartilhando toda a lógica com o nó `e2e` do verify. `up [--fresh]` reusa um stack quente **só** quando ele provadamente serve o código atual: o carimbo compara `HEAD` + `git status --porcelain` + `git diff HEAD`, então qualquer edição, commitada ou não, invalida o reuso (a exceção é o serviço com bind mount do código, que serve o disco por construção). Na dúvida, rebuild — o layer cache do Docker faz disso barato. `status [--json]` responde a pergunta que decide se um verde vale alguma coisa (no ar? saudável? construído do código atual?) e sai 0 só com os três; o `baseUrl` do payload é o endereço real da app — é dele que a `morph-review` tira a URL que vai no prompt do avaliador, em vez de deixá-lo adivinhar `localhost:3000`. `down [--volumes]` é best-effort e nunca bloqueia. Isolamento entre worktrees: `COMPOSE_PROJECT_NAME=morph-{feature}` separa os containers e o bloco de portas do fleet (`env-allocator`) separa as portas — um compose com porta fixa não pode ser isolado, e isso é avisado. O bloco vale nos **dois** caminhos (este comando e o nó do `verify`): `PORT`, `MORPH_PORT_BASE`, `ASPNETCORE_URLS` em dotnet, mais `PLAYWRIGHT_BASE_URL` apontando para a porta alocada. Por isso o `playwright.config.ts` precisa ler a base URL do ambiente em vez de cravá-la — com a URL cravada, a suíte bate numa porta vazia e o `ECONNREFUSED` se disfarça de app quebrada.
38
- - **`morph-spec doctor --worktrees [--prune] [--json]`** — reconcilia o registro do git × as pastas em disco × as features conhecidas. Worktrees órfãos acumulavam **invisíveis**: o `fleet` só reporta worktrees em branch `morph/*`, então um worktree em outra branch (resíduo de finish anterior) não aparecia em lugar nenhum. `--prune` roda o único reparo seguro (`git worktree prune` — registros de pastas que já sumiram); apagar pasta que ainda existe é reportado com o comando exato, nunca executado.
39
+ - **`morph-spec doctor --worktrees [--prune] [--json]`** — reconcilia o registro do git × as pastas em disco × as features conhecidas. Worktrees órfãos acumulavam **invisíveis**: o `fleet` só reporta worktrees em branch `morph/*`, então um worktree em outra branch (resíduo de finish anterior) não aparecia em lugar nenhum. `--prune` roda o único reparo seguro (`git worktree prune` — registros de pastas que já sumiram) **e** limpa o registro `worktree` de qualquer feature cujo `.path` aponte para um desses diretórios já sumidos (`dangling-registration` — a direção simétrica do `no-feature`: não é o worktree sem feature, é a feature com um ponteiro morto na raiz); apagar pasta que ainda existe é reportado com o comando exato, nunca executado.
39
40
  - **`morph-spec report-check <feature> [task] [--json]`** — audita os relatórios de sub-agent contra o trabalho que realmente entrou. **Relatório ausente NÃO quer dizer que o agente falhou** — ele pode ter feito tudo e perdido o meio de gravar. E relatório PRESENTE não basta: `report-stale` é o relatório completo porém gravado ANTES do fim do trabalho (o agente relatou cedo e continuou escrevendo) — completo e mentindo. O comando responde a pergunta certa (`work-landed-report-missing` → aceite degradado, NÃO re-despache; `no-work-no-report` → re-despache uma vez; `report-stale` → atualize a partir do campo `observed`, NÃO re-despache). A evidência de `observed` vem do `events.jsonl` (todo `Write`/`Edit` observado na janela da task, cruzada com `task-times.json`), não de `outputs`, que é auto-declarado pelo próprio agente — writes do orquestrador sob `.morph/` e o do próprio relatório são excluídos. Sem telemetria no projeto, o comando volta ao comportamento anterior (fail-open). Protocolo completo em MORPH.md §6.
40
41
 
41
42
  ## Observabilidade
@@ -176,15 +176,15 @@
176
176
  "testCall": "Call get_configuration_status to verify API key and server health",
177
177
  "testParams": {},
178
178
  "successIndicator": "Returns status with valid API key configured",
179
- "failureHint": "Gemini Image MCP not responding. Ensure GEMINI_API_KEY is set in MCP server configuration. Install: npx gemini-image-mcp@latest"
179
+ "failureHint": "Gemini Image MCP not responding. Check that GEMINI_API_KEY is set in the MCP env config and that MORPH_HUB points at a built morph-HUB checkout."
180
180
  },
181
181
  "install": {
182
182
  "autoInstall": false,
183
183
  "transport": "stdio",
184
184
  "config": {
185
185
  "type": "stdio",
186
- "command": "cmd",
187
- "args": ["/c", "npx", "-y", "gemini-image-mcp@latest"]
186
+ "command": "node",
187
+ "args": ["${MORPH_HUB}/mcps/gemini-image/dist/index.js"]
188
188
  },
189
189
  "credentials": [
190
190
  {
@@ -194,19 +194,70 @@
194
194
  "helpUrl": "https://aistudio.google.com/apikey"
195
195
  }
196
196
  ],
197
- "prerequisites": ["Google AI Studio API key"],
197
+ "prerequisites": [
198
+ "Google AI Studio API key",
199
+ "Local morph-HUB checkout with mcps/gemini-image built, exposed via the MORPH_HUB env var"
200
+ ],
198
201
  "stackRelevance": ["nextjs", "next"],
199
- "warnings": ["Requires GEMINI_API_KEY environment variable. Image generation costs apply per Google AI pricing."],
200
- "setupGuide": "1. Get API key from https://aistudio.google.com/apikey\n2. Run: claude mcp add gemini-image-mcp -- npx gemini-image-mcp@latest\n3. Set GEMINI_API_KEY in MCP env config"
202
+ "warnings": [
203
+ "Internal Polymorphism MCP, not published to npm. The public gemini-image-mcp package was unpublished in 2025-04, so npx installs no longer work.",
204
+ "Requires GEMINI_API_KEY environment variable. Image generation costs apply per Google AI pricing."
205
+ ],
206
+ "setupGuide": "1. Get an API key at https://aistudio.google.com/apikey\n2. Build the MCP: cd $MORPH_HUB/mcps/gemini-image && npm install && npm run build\n3. Run: claude mcp add gemini-image-mcp -e GEMINI_API_KEY=<key> -- node $MORPH_HUB/mcps/gemini-image/dist/index.js\n4. Verify with get_configuration_status"
207
+ }
208
+ },
209
+ "seedance": {
210
+ "phases": ["uiux", "implement"],
211
+ "tools": ["generate_video", "generate_loop_video", "get_video", "list_videos"],
212
+ "detection": "mcpServers includes seedance",
213
+ "fallback": "Use client-provided footage or a static poster image; canvas frame-scrub works from any frame sequence, AI-generated or not",
214
+ "usage": "AI video generation for scroll-driven heroes and ambient backgrounds. generate_video with endImage produces the directional footage that frame-scrub needs (start frame differs from end frame); generate_loop_video is only for seamless ambient loops",
215
+ "healthCheck": {
216
+ "toolPattern": "seedance",
217
+ "testCall": "Call list_videos with limit 1 to verify the server responds (reads local history, spends no credits)",
218
+ "testParams": {
219
+ "limit": 1
220
+ },
221
+ "successIndicator": "Returns the local job history, or 'No videos in history yet'",
222
+ "failureHint": "Seedance MCP not responding. Check that REPLICATE_API_TOKEN is set in the MCP env config and that MORPH_HUB points at a built morph-HUB checkout."
223
+ },
224
+ "install": {
225
+ "autoInstall": false,
226
+ "transport": "stdio",
227
+ "config": {
228
+ "type": "stdio",
229
+ "command": "node",
230
+ "args": ["${MORPH_HUB}/mcps/seedance/dist/index.js"]
231
+ },
232
+ "credentials": [
233
+ {
234
+ "name": "Replicate API Token",
235
+ "envVar": "REPLICATE_API_TOKEN",
236
+ "secret": true,
237
+ "helpUrl": "https://replicate.com/account/api-tokens"
238
+ }
239
+ ],
240
+ "prerequisites": [
241
+ "Replicate account with billing enabled",
242
+ "Local morph-HUB checkout with mcps/seedance built, exposed via the MORPH_HUB env var",
243
+ "ffmpeg on PATH to extract frames for scroll-scrubbing"
244
+ ],
245
+ "stackRelevance": ["next", "react", "vite", "astro"],
246
+ "warnings": [
247
+ "Internal Polymorphism MCP, not published to npm. Requires a local morph-HUB checkout.",
248
+ "Paid per generation. Draft at fast/480p/4s first, render 1080p only after the take is approved.",
249
+ "Generation is stochastic and needs a human approval gate. Do not wire it into an unattended phase."
250
+ ],
251
+ "setupGuide": "1. Get a token at https://replicate.com/account/api-tokens\n2. Build the MCP: cd $MORPH_HUB/mcps/seedance && npm install && npm run build\n3. Run: claude mcp add seedance -e REPLICATE_API_TOKEN=<token> -- node $MORPH_HUB/mcps/seedance/dist/index.js\n4. Verify with list_videos (reads local history, spends no credits)"
201
252
  }
202
253
  }
203
254
  },
204
255
  "_phaseMatrixNote": "Keys must be the v5 phases from framework/phases.json. The v4 keys 'design' and 'tasks' both collapsed into 'plan'; 'review' was absent entirely, so a project in review resolved to undefined. Stack-scoped MCPs (asaas) stay out of this matrix on purpose — only universal ones are listed.",
205
256
  "phaseMatrix": {
206
257
  "proposal": ["context7"],
207
- "uiux": ["playwright", "context7", "stitch", "gemini-image"],
258
+ "uiux": ["playwright", "context7", "stitch", "gemini-image", "seedance"],
208
259
  "plan": ["context7"],
209
- "implement": ["context7", "playwright", "vercel", "gemini-image"],
260
+ "implement": ["context7", "playwright", "vercel", "gemini-image", "seedance"],
210
261
  "review": ["playwright", "context7"]
211
262
  }
212
263
  }
@@ -4,7 +4,7 @@ description: Guia a criação de uma landing page nova ponta-a-ponta a partir do
4
4
  version: core
5
5
  when-to-invoke: ao iniciar um novo projeto de LP com o template Umbraco genérico
6
6
  user-invokable: true
7
- cliVersion: "8.27.0"
7
+ cliVersion: "8.29.0"
8
8
  stacks:
9
9
  - umbraco
10
10
  ---
@@ -199,6 +199,17 @@ Edite `frontend/src/app/globals.css`. As variáveis CSS de base do tema são def
199
199
 
200
200
  Para tipografias customizadas, importe via `next/font` em `frontend/src/app/layout.tsx` e exponha como variável CSS.
201
201
 
202
+ > **Se a LP nasce cinematográfica (scroll-driven):** o template não traz `gsap` nem `lenis`; adicione ao
203
+ > `frontend/package.json` e monte o runtime de motion como componente `'use client'` montado uma vez na
204
+ > page, com guard de SSR no registro do ScrollTrigger. O conhecimento está nos standards
205
+ > `frontend/scroll-driven/smooth-scroll.md` (wiring Lenis com o ticker do GSAP, `window.__lenis`,
206
+ > armadilha de HMR), `frame-scrub.md` (canvas quadro a quadro, extração ffmpeg, poster no mobile) e
207
+ > `scroll-components.md` (pin, modal, parallax, reveal). Regra de assets que vale desde o primeiro dia:
208
+ > **sequência de frames é asset estrutural em `frontend/public/` com cache-bust `?v=N` manual, e só o
209
+ > poster vira mídia editável no backoffice.** Frames na Media Library perdem o webp (o ImageSharp do
210
+ > Umbraco 17 exige URL assinada por HMAC, não relaxável por config) e viram 145 requests assinadas
211
+ > por visitante.
212
+
202
213
  **4.2 — Módulos disponíveis (Content Types *Section):**
203
214
 
204
215
  O template já inclui os seguintes módulos versionados em `backend/uSync/v17/ContentTypes/`:
@@ -268,7 +279,7 @@ Antes de fazer deploy ou entregar o projeto ao cliente:
268
279
  ## Referências
269
280
 
270
281
  - `polymorphism-tech/MORPH_Umbraco_LP_Template` (privado, branch `master`) — template canônico: `scripts/new-lp.ps1`, `scripts/new-lp.sh`, `backend/Infrastructure/SeedContentHandler.cs`, `backend/Controllers/LeadController.cs`, `backend/uSync/v17/`.
271
- - `docs/umbraco-lp-patterns.md` (em `main` neste repo) — padrões e gotchas detalhados: controller público (seção 1), DI multi-sink (seção 2), leitura server-side de config oculta (seção 3), segurança Delivery API (seção 4), uSync config-as-code (seção 5), dropdown gotcha (seção 6), seed idempotente (seção 7), endpoint Ping (seção 10), skip silencioso em dev (seção 11), build Next.js exige backend (seção 12).
282
+ - `references/umbraco-lp-patterns.md` (carregue sob demanda) — padrões e gotchas detalhados: controller público (seção 1), DI multi-sink (seção 2), leitura server-side de config oculta (seção 3), segurança Delivery API (seção 4), uSync config-as-code (seção 5), dropdown gotcha (seção 6), seed idempotente (seção 7), endpoint Ping (seção 10), skip silencioso em dev (seção 11), build Next.js exige backend (seção 12).
272
283
  - `backend/appsettings.json` — `Umbraco:CMS:DeliveryApi:DisallowedContentTypeAliases` e `uSync:Settings:ImportAtStartup`.
273
284
  - `backend/Properties/launchSettings.json` — portas de dev: HTTP `:19223`, HTTPS `:44324`.
274
285
  - `docker-compose.yml` — orquestração de produção (backend porta `5001`, frontend `3000`).