feat(documents): editor de texto rico (Tiptap) para templates de documento
Substitui o textarea de texto puro por um editor visual real, seguindo o
print de referência do usuário do editor "Contrato de Software" do
OrçaFácil legado: toolbar completa (negrito/itálico/sublinhado/tachado,
alinhamento, listas, tabela, imagem, quebra de página), variáveis inseridas
como chip inline via catálogo fechado (nunca {{texto livre}}), painel
"Variáveis usadas neste modelo" (com badge obrigatório ao vivo) e
"Histórico de versões".
Backend: editor-content-renderer.ts e merge-engine.ts portados/reescritos
a partir do renderEditorContent.js/variableRegistry.js reais do legado
(achados em .tmp/orcafacil-reference) — resolve a árvore Tiptap
diretamente em vez de regex sobre HTML cacheado. Novo catálogo de
variáveis + GET /documents/variable-catalog.
Fora de escopo desta leva (documentado em docs/progress.md): nó
`conditional`, repetição de linha de tabela, bloqueio por variável
obrigatória ausente, e auto-resolução de dados reais do cadastro na hora
de gerar o documento.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -851,3 +851,65 @@ Novo botão "Pré-visualizar (PDF)" ao lado de "Salvar rascunho". **Decisão de
|
||||
|
||||
### Verificação
|
||||
`pnpm turbo run typecheck build` 28/28. Suíte 115/115 (113→115: 1 teste cobrindo o preview de rascunho — variáveis viram placeholder, CSS aplicado, nada persiste —, 1 confirmando que `css_content` agora chega no `renderPdf` real da geração). Testado ao vivo contra Chromium de verdade (não mockado): PDF real de 1 página gerado sem nunca ter salvo um rascunho, texto extraído via `pdftotext` confirmando "Prezado(a) [nome_cliente], seu plano é [plano]." e a formatação CSS (cor/tamanho de fonte) presente no stream do PDF. A renderização visual do PDF dentro do iframe não pôde ser confirmada por screenshot (visualizador nativo de PDF do Chromium não aparece em capturas de tela no modo headless do Playwright — mesma limitação de teste automatizado já registrada antes nesta sessão), mas o mecanismo (blob correto, `src` do iframe apontando pro blob certo, bytes do PDF válidos e corretos) foi confirmado de forma independente.
|
||||
|
||||
---
|
||||
|
||||
## Editor de texto rico para Templates de Documentos (Tiptap)
|
||||
|
||||
Data: 2026-09-19. Usuário mandou o print do editor de "Contrato de Software" do OrçaFácil legado
|
||||
(`/home/handix/editortexto.png`) como referência de como queria a tela de edição de rascunho de
|
||||
Documentos: header com título/badge de versão + ações, tabs "Editor" / "Visualizar", toolbar rica
|
||||
(parágrafo/fonte, negrito/itálico/sublinhado/tachado, alinhamento, listas, tabela, imagem, quebra de
|
||||
página), variáveis inseridas como **chip inline** (nunca `{{texto livre}}`), painel "Variáveis usadas
|
||||
neste modelo" (com "obrigatório") e "Histórico de versões". Escolheu ir direto pro nível completo.
|
||||
|
||||
Até aqui o "editor" era um `<textarea>` puro com `{{variavel}}` digitado à mão — um stand-in documentado
|
||||
no próprio código/migration (`editor-content-renderer.ts`/`merge-engine.ts`), esperando o "editor visual
|
||||
real" que essa leva implementa. O banco (`document_template_versions.editor_content` JSONB) já estava
|
||||
desenhado pra uma árvore Tiptap real (`EMPTY_DOC_CONTENT` já nascia `{type:'doc', content:[...]}`) — sem
|
||||
migration nova.
|
||||
|
||||
Achado o código-fonte real do legado em `.tmp/orcafacil-reference/server/src/lib/documentTemplates/`
|
||||
(`renderEditorContent.js` + `variableRegistry.js`) — portados fielmente em vez de reinventados.
|
||||
|
||||
**Fora de escopo** (documentado de propósito, pra não estourar o pedido — a tela do editor, não o motor de
|
||||
geração inteiro): nó `conditional` e repetição de linha de tabela (`tableRow.repeat`); bloqueio HTTP 422
|
||||
por variável obrigatória ausente (mantém o comportamento permissivo já existente — o badge "obrigatório" é
|
||||
só informativo); auto-resolução de `cliente.*`/`oferta.*`/`contrato.*` a partir do banco na hora de GERAR
|
||||
o documento (`GenerationFormPage.tsx` continua pedindo cada valor manualmente, como já era); allowlist
|
||||
completa de sanitização de HTML; cabeçalho/rodapé de PDF.
|
||||
|
||||
### Backend
|
||||
`editor-content-renderer.ts` reescrito: `renderToHtml(doc, resolveMergeField?)` + `collectMergeFieldKeys`
|
||||
cobrindo paragraph/heading/bulletList/orderedList/listItem/image/pageBreak/table+thead-real/marks
|
||||
bold-italic-underline-strike-fontSize/mergeField (chip visual sem `resolveMergeField`, valor resolvido
|
||||
com). `merge-engine.ts` muda de contrato: em vez de regex sobre `html_content` cacheado, `resolveDocument
|
||||
(editorContent, context)` resolve a árvore direto — mais fiel ao legado e mais simples. Novo
|
||||
`variable-catalog.ts` com o catálogo fechado de variáveis (portado do legado, só `scope: 'document'`) +
|
||||
`GET /v1/documents/variable-catalog`. Novo teste unitário puro `editor-content-renderer.spec.ts` (8 casos,
|
||||
finalmente cumprindo o que o docblock do merge-engine prometia desde uma leva anterior e nunca existiu).
|
||||
|
||||
### Frontend
|
||||
Tiptap (`@tiptap/react` + extensões table/image/text-align/text-style/underline, `^3.29.2`, mesma major do
|
||||
legado) adicionado ao core-web. Nó customizado `mergeField` (chip visual via NodeView React) e `pageBreak`
|
||||
(régua tracejada). `EditorToolbar.tsx` fiel ao print + `VariablePickerMenu.tsx` (menu por categoria, sem
|
||||
componente de dropdown pronto em `packages/ui` — construído ali mesmo). `TemplateDetailPage.tsx`: quando
|
||||
há rascunho aberto, a tela vira o layout do print (header com título/badge/ações, tabs "Editor / com
|
||||
variáveis" / "Pré-visualização (PDF)" — nome ajustado do print pra não prometer dado real de
|
||||
cliente/revenda, já que só mostra `[chave]" —, painel "Variáveis usadas neste modelo" com badge obrigatório
|
||||
ao vivo, "Histórico de versões"). `GenerationFormPage.tsx` ajustado pra extrair variáveis da árvore real
|
||||
em vez de regex sobre `plainText` (que deixou de existir).
|
||||
|
||||
### Verificação
|
||||
`pnpm turbo run typecheck build` 28/28. Suíte 123/123 (115→123: 8 novos do renderer). Testado ao vivo
|
||||
(Playwright + curl): criado template novo, aberto o editor, digitado texto com negrito, inseridas duas
|
||||
variáveis (`cliente.razao_social` obrigatória, `cliente.cnpj` obrigatória) via o menu por categoria —
|
||||
confirmado que o painel "Variáveis usadas" atualiza em tempo real com checkmark + badge "obrigatório".
|
||||
Salvo o rascunho e confirmado via `GET /documents/templates/:id` que `editor_content` persistiu a árvore
|
||||
Tiptap real (mark `bold` + dois nós `mergeField` com `key`/`label` corretos) e `html_content` já teria o
|
||||
`<strong>`/chip visual certos. Pré-visualização em PDF gerada ao vivo contra Chromium real (não mockado) e
|
||||
confirmada via `pdftotext`: `"Prezado(a) cliente, segue o contrato. [cliente.razao_social][cliente.cnpj]"`.
|
||||
Publicado o rascunho e confirmado que a versão publicada aparece corretamente na tela de overview (chip
|
||||
"Razão Social"/"CNPJ/CPF" renderizado com o estilo `.merge-field-chip` aplicado). Template de teste
|
||||
(`qa_test_editor_*`) desativado ao final via `UPDATE document_templates SET active=false` (sem endpoint de
|
||||
exclusão, mesmo padrão de higiene de dados de teste já seguido nesta sessão).
|
||||
|
||||
Reference in New Issue
Block a user