Conn2Flow
Navegação das docs

SDD

Decision Log (Archive: DEC-091 to DEC-095)

Renderização de Variáveis/Widgets no Editor HTML Clássico e Preview, igual à Editbar (req-093 / BATCH-093 — Fase base). Decisões desta rodada (escopo confirmado com o Chefe…

DEC-091 - 2026-07-20 - accepted

Renderização de Variáveis/Widgets no Editor HTML Clássico e Preview, igual à Editbar (req-093 / BATCH-093 — Fase base). Decisões desta rodada (escopo confirmado com o Chefe: DUPLICAR a infra no clássico mantendo a Editbar intacta; itens 1+2, executados tecnicamente faseados):

  1. Backend cria as caixas → frontend só reverte (simplificação-chave): diferente da Editbar (que mapeia DOM vivo × cru via mapTree), o Editor Clássico monta iframes srcdoc a partir do HTML do CodeMirror. Como o BACKEND pode renderizar as variáveis já em caixas com o marcador embutido (data-c2f-marker), o frontend NÃO precisa portar o mapTree inteiro — basta reverter as caixas no save. Novas funções em html-editor.php (duplicadas/adaptadas das da Editbar): html_editor_boxes_variaveis/_boxes_widgets/resolver_var/resolver_variaveis/var_box/render_widget_signature + rota html-editor-render-vars ({boxes, values}). Regex TOLERANTE (@?[[...]]@?): o clássico usa [[var]] (o template-load remove o cerco), então o marcador guarda o texto EXATO de entrada e a reversão restitui o mesmo — round-trip perfeito seja [[var]] ou @[[var]]@.

  2. Resolução só das GLOBAIS; LOCAIS preservadas: html_editor_resolver_var resolve pagina#url-raiz/url-full-http/gestor#versao/pagina#menu + gestor_variaveis_globais; retorna null para variáveis desconhecidas (locais/simulação de módulo), que ficam literais para o frontend resolver (harmonização com publisherVariablesOrSimulation fica para a próxima rodada). Widgets inline são pulados na resolução de variáveis (tratados por _boxes_widgets).

  3. Integração ADITIVA e segura no frontend (html-editor-interface.js): htmlEditorRenderVars(html,cb) (POST à rota; falha → cb(null) → HTML cru, fluxo antigo intacto); htmlEditorReconstructVars(html) (reverte [data-c2f-marker] ao marcador; early-return sem caixas = no-op). editorHtmlVisual para páginas/componentes passou a renderizar as caixas (async) antes de abrir o editor visual; o save (previsualizarConfirmar) aplica a reversão em bodyContent antes de gravar no CodeMirror → o banco recebe a variável, não o valor. Layouts seguem síncronos (sem caixas por ora). Reversão testada com a função REAL extraída do arquivo (round-trip, no-op, cerco exato, múltiplas caixas).

Cache-bust: biblioteca-html-editor 1.4.10→1.4.11. Validação: php -l OK + smoke das funções de caixas; node --check OK; vitest reversão 4/4; suíte 29/29 + composer test 110/110 sem regressão. Nenhum git commit/git push executado.

Adendo DEC-091 (Fase 2 — preview + publisher-pages, mesma rodada)

  1. htmlEditorRenderVars generalizado (cb({boxes, values})): o editor visual consome boxes (globais em caixas), o preview consome values (globais resolvidas para valor). previewHtml passou a assíncrono: aplica a simulação LOCAL (como antes) e resolve as GLOBAIS via values, com fallback ao HTML local (o preview nunca quebra).

  2. publisher-pages coberto pela infra genérica (sem tocar publisher-pages.php): a rota resolve as GLOBAIS para qualquer módulo (roteada por html_editor_ajax_interface) e preserva as LOCAIS. No preview, publisherVariablesOrSimulation/OrValues (locais, simulação replicada) + backend (globais) coexistem. No editor visual, as GLOBAIS viram caixas e as LOCAIS ficam como marcadores ([[item#X]]), preservadas no save. Decisão: as locais NÃO recebem a simulação REPLICADA no editor visual — replicar o bloco item N vezes quebraria a reversão 1:1 (gravaria N marcadores onde havia 1); o usuário edita o TEMPLATE, não a replicação. A simulação replicada permanece exclusiva do preview.

  3. Refinamento futuro (opcional): envolver as variáveis LOCAIS em caixas protegidas 1:1 no editor visual (anotação/proteção sem replicar) — melhoria de UX; o comportamento atual (locais como marcadores, preservadas no save) já é seguro. Status do batch: complete (itens 1+2 atendidos).

DEC-092 - 2026-07-29 - accepted

Mapeamento visual de mídia/documento embutido, proteção de eventos, suporte híbrido a PDF e modal estruturado (req-096 / BATCH-096). Decisões desta rodada:

  1. Invólucro é UI de RUNTIME, nunca persistência: object/iframe/embed/video/audio (e o contêiner .conn2flow-pdfjs) recebem em runtime o invólucro .conn2flow-embed-wrapper (badge de tipo + escudo .c2f-embed-shield + 4 alças de resize + .conn2flow-embed-inner com o conteúdo inerte a ponteiro). A remoção acontece no ponto ÚNICO de saída de HTML do motor — extractUserHtml — que roda tanto no save (getCleanHtml) quanto nos snapshots de undo; init/applyState/afterDomMutation reconstroem o invólucro de forma idempotente a partir da tag limpa. Consequência prática: o banco nunca vê o invólucro, o reconstructOriginal da Editbar segue vendo apenas variáveis e widgets, e o histórico compara sempre HTML limpo (sem oscilação de snapshot). Alternativa descartada: remover o invólucro só no save — deixaria o wrapper vazar para os snapshots e o undo aninharia invólucros.

  2. Escudo transparente em vez de pointer-events:none no embed: <iframe>/<object> entregam o ponteiro ao documento interno antes da página, matando hover/seleção/arraste do editor. A solução é um escudo absoluto sobre o bloco (que recebe hover, clique e o duplo clique de edição) + pointer-events:none no conteúdo embutido. resolveEditable faz escudo, alça e o próprio embed resolverem para o invólucro, tornando o conjunto UM bloco atômico (mesmo padrão dos widgets).

  3. Embeds dentro de blocos dinâmicos NÃO recebem invólucro: widget renderizado (.conn2flow-widget-wrapper/[data-c2f-widget-id]) e caixa de variável (.c2f-dyn-box) voltam ao marcador no save — permitir editar um <iframe> interno criaria uma alteração descartada silenciosamente. Também ficam de fora a UI do editor e os iframes de sistema do Live Editor (#c2f-site-toolbar, #c2f-device-preview).

  4. Dimensões no style, atributos legados removidos: largura/altura (com unidade px/%/vh no modal e px no drag-resize) são gravadas em style.width/height e os atributos width/height são removidos, evitando tamanho inconsistente entre navegadores quando os dois competem.

  5. 3 motores de PDF como GERADORES DE MARKUP puros (buildPdfNativeMarkup/buildPdfJsMarkup/buildPdfGoogleMarkup, roteados por buildEmbedMarkup): funções que recebem a configuração e devolvem string HTML — testáveis sem DOM de editor e reaproveitáveis. Motor A = <object type="application/pdf"> com fallback amigável interno; motor B = <div class="conn2flow-pdfjs" data-pdf-src|zoom|toolbar|page|scroll>; motor C = <iframe src="[REDACTED]> com a URL absolutizada (o Google precisa de URL pública absoluta).

  6. Fallback do motor A com estilos INLINE: esse HTML é persistido e renderizado no site publicado, onde o CSS do editor não existe. Os estilos vão inline (classes preservadas para customização do projeto), garantindo a "mensagem + botão estilizado" pedida pelo intake em qualquer página — sem obrigar o projeto a incluir CSS novo.

  7. *PDF.js: contrato por atributos `data-+ runtime de core auto-suficiente**: o novo assetgestor/assets/interface/pdf-viewer.jsvarre.conn2flow-pdfjs, injeta seu próprio CSS, monta barra de ferramentas (páginas/zoom), renderiza em canvas nos modosvertical(todas as páginas) epage, e **carrega a lib do PDF.js da CDN sob demanda** quandowindow.pdfjsLibnão existe. Expõewindow.conn2flowPdfViewerInit()(idempotente viadata-c2f-pdfjs-ready), chamado pelo motor visual após aplicar/desfazer alterações. Versão fixada em 3.11.174 (build UMD/legada, compatível com Safari/iOS antigo) — a build ESM.mjsexigiriatype=modulenosrcdoc` e no site.

  8. Injeção de assets condicionada à página, com helper PURO no core: gestor_pdf_viewer_detectar($html) compara TOKEN por TOKEN do atributo class (uma checagem por substring, ou com \b, daria falso positivo em conn2flow-pdfjs-legacy) e gestor_pdf_viewer_assets($urlRaiz,$versao) monta as tags; ambas na biblioteca gestor (carregada pelo bootstrap do PHPUnit → testáveis sem banco). O pipeline gestor_pagina_pdf_viewer() (em gestor/gestor.php) roda DEPOIS de gestor_pagina_widgets() (o contêiner pode vir de um widget) e ANTES de gestor_pagina_extra_head_e_javascript() (que serializa as inclusões). No editor clássico, o srcdoc é montado por JS e não passa pelo pipeline PHP — daí montarPdfViewerHead() no html-editor-interface.js (preview e editor visual, página e layout).

Cache-bust: biblioteca-html-editor 1.4.12→1.5.0, dashboard.json 1.0.11→1.0.12, motor no Live Editor ?v=c2f10→?v=c2f11. Validação: php -l 4/4, node --check 4/4, npx vitest run 50/50 (novo html-editor-embed.test.js 19/19), composer test 158/158 (novo PdfViewerAssetsTest 5/5). Homologação runtime (deploy Update => Core, 3 motores em desktop e mobile) pendente com o operador. Nenhum git commit/git push executado.

DEC-093 - 2026-07-30 - accepted

Correções de homologação do BATCH-096, separação da edição avançada e embeds no painel "+" (req-097 / BATCH-097). Decisões desta rodada:

  1. A duplicação era o embed indo para o LAYOUT, não uma cópia extra: a evidência (temp/html-output.html) mostra um <object> dentro de #c2f-page-content e outro como último filho de #c2f-layout-root. No Live Editor a raiz editável é o layout (a edição cobre layout + conteúdo) e o performSave separa pelo #c2f-page-content; o fallback de inserção usava contentRoot.appendChild(), então o elemento nascia fora do conteúdo e era gravado em layouts.html. Como o layout é compartilhado por todas as páginas e pela página dashboard-site-toolbar renderizada dentro do iframe da barra, o mesmo embed aparecia 2–3 vezes e "vazava" para dentro da Editbar. Correção: insertionRoot() — havendo #c2f-page-content na raiz editável, ele é o destino padrão de qualquer inserção sem alvo (código customizado e painel "+"); o fallback do modo de inserção deixou de cair em document.body (o que colocava o elemento fora do que é salvo). Inserir no layout continua possível de forma explícita: basta selecionar um elemento do layout e inserir relativo a ele.

  2. Isolamento dos elementos de sistema centralizado em isEditorOwned: #c2f-site-toolbar, #c2f-device-preview (e descendentes) e #c2f-save-loader passaram a ser reconhecidos ali, o que blinda de uma só vez wrapEmbeds, resolveEditable, findEditableFromPoint e isUserContentNode/extractUserHtml — em vez de repetir a exceção em cada varredura. A extração também remove esses ids do clone, como defesa em profundidade contra um HTML já salvo com o iframe da barra dentro.

  3. Extração estritamente idempotente: além do invólucro, extractUserHtml agora (a) limpa o contêiner do PDF.js (o runtime desenha toolbar + canvas dentro dele e marca data-c2f-pdfjs-ready; o contrato salvo é o <div> VAZIO com os data-pdf-*) e (b) remove resíduos órfãos de invólucro (.c2f-embed-shield, .c2f-embed-handle, .conn2flow-embed-label) que tenham sido persistidos por uma versão anterior — o save "cura" o HTML sujo em vez de propagá-lo. Complementarmente, o pdf-viewer.js deixou de escrever display/flex-direction inline no contêiner (foi para CSS via [data-c2f-pdfjs-ready]), para o style persistido não ganhar sujeira de runtime.

  4. Escudo e pointer-events são exclusivos do modo de edição: disable() desembrulha os embeds no DOM VIVO e enable() reconstrói. Assim, sair da edição, entrar no preview de dispositivo ou salvar devolve imediatamente a interatividade (rolar/ampliar o PDF, tocar o vídeo), sem depender de reload. O invólucro continua sendo removido na extração — as duas garantias são independentes.

  5. Embeds nascem posicionados (position:relative;z-index:1): a causa da "imagem estática travada" no site publicado não é só o escudo — templates Tailwind usam camadas decorativas absolutas (absolute inset-0 z-0) dentro da seção, e um embed com posição estática fica ABAIXO delas e não recebe o ponteiro. O markup gerado passa a nascer com contexto de empilhamento próprio; se o usuário definir position/z-index nos estilos extras do modal, os dele vencem (a detecção é por regex no styleExtra).

  6. Z-index: seletor de arquivos acima do modal: #c2f-he-embed-modal em 1000000 e #c2f-he-imagepick-overlay em 1000060 (antes 1000004 × 1000002, com o gerenciador abrindo atrás da caixa). Faixa reservada: painéis do editor ≤ 1000003, modais ≤ 1000001, seletor de arquivos no topo.

  7. PDF.js sob demanda no editor: o runtime só é injetado no page load quando a página JÁ tem um leitor (gestor_pagina_pdf_viewer). Ao escolher o Motor B durante a edição, ensurePdfViewer() carrega interface/pdf-viewer.js (que por sua vez busca a lib na CDN) e refreshPdfJsViewers() dispara conn2flowPdfViewerInit() — no applyEmbedModal, no wrapEmbeds e no applyState. Antes, o usuário via um bloco cinza estático.

  8. Dois links de edição avançada, em vez de um link que troca de módulo: "Editar Avançado" aponta SEMPRE para admin-paginas/editar/?id=… (a página crua existe para qualquer página) e o novo "Editar Publicação Avançado" aponta para publisher-pages/editar/?id=…&publisher_id=…, exibido apenas quando há publicação vinculada. O bloco extra vive nos templates (<!-- dropdown-page-publisher < --> … > -->, pt-br e en) e é removido por modelo_tag_del quando $ehPublisher é falso — mesmo padrão já usado no <!-- dropdown-page -->.

  9. Os 5 tipos de embed no painel "+": object, iframe, embed, video e audio entram nas listas de elementos do Live Editor (dashboard.toolbar.js) e do editor clássico (html-editor-visual-controls.js); buildElement cria a tag com dimensões padrão e SEM fonte, e o onInsertClick seleciona o invólucro recém-criado e abre o modal na aba "Atributos" — a fonte é o primeiro dado que falta.

Cache-bust: biblioteca-html-editor 1.5.0→1.5.1, dashboard.json 1.0.12→1.0.13, motor no Live Editor ?v=c2f11→?v=c2f12. Validação: php -l OK, node --check 4/4, npx vitest run 58/58 (html-editor-embed.test.js 19→27 casos), composer test 158/158 sem regressão. Homologação runtime pendente com o operador. Nenhum git commit/git push executado.

DEC-094 - 2026-07-30 - accepted

Área de transferência persistente do Editor Visual (demanda direta do Engenheiro Chefe na sessão de 2026-07-30, complementando o req-097 / BATCH-098). Decisões desta rodada:

  1. localStorage como meio de persistência, com HTML como formato: a cópia deixa de ser um nó em memória (clipboardElement) e passa a ser uma STRING de HTML guardada em localStorage['c2f-he-clipboard'] ({html, ts, origem}). O localStorage é por origem, então a mesma cópia atravessa páginas do site, o Live Editor e o editor clássico (o srcdoc herda a origem) e sobrevive ao reload que acontece ao sair do modo de edição — que era exatamente o ponto onde a cópia se perdia. Guardar HTML (e não o nó) evita depender do DOM da página de origem e torna a colagem idêntica na mesma sessão ou em outra.

  2. Sanitização obrigatória no COPIAR, não no COLAR: buildClipboardMarkup() remove o invólucro de embed e o leitor PDF.js renderizado (UI de runtime) e descarta data-c2f-variable/contenteditable, que só fazem sentido junto do varMap da página de ORIGEM — mantê-los faria a Editbar reconstruir a variável errada na página de destino. Os data-c2f-marker (base64 do marcador) são PRESERVADOS porque são autossuficientes: é o que faz um widget colado continuar sendo um widget onde quer que seja colado. Sanear na cópia mantém o que está guardado sempre válido.

  3. Renumeração dos ids ao colar: remapPastedIds() troca data-c2f-widget-id por ids novos e reregistra wrappers clássicos no widgetsMap da sessão. Sem isso, colar na MESMA página do original criaria dois grupos com o mesmo id e o reconstructOriginal da Editbar — que agrupa por esse id e remove os "irmãos" do grupo — descartaria parte do conteúdo no salvamento.

  4. Colar sem seleção passa a ser válido: o caso de uso é "abri outra página e quero colar", quando não há nada selecionado. Nesse caso o bloco vai para o fim do conteúdo editável via insertionRoot() (BATCH-097) — nunca na raiz do layout, que reapareceria em todas as páginas. O Ctrl+V acompanhou (hasClipboard() no lugar de clipboardElement).

  5. Falha de storage é silenciosa: modo privado, storage negado ou cota estourada apenas mantêm a cópia em memória (try/catch em leitura e escrita) — o editor nunca quebra por causa da área de transferência.

Cache-bust: biblioteca-html-editor 1.5.1→1.5.2, motor no Live Editor ?v=c2f12→?v=c2f13. Validação: node --check OK, npx vitest run 64/64 (6 casos novos), composer test 158/158. Homologação runtime pendente com o operador. Nenhum git commit/git push executado.

DEC-095 - 2026-07-30 - accepted

Upload e gestão de pastas liberados no modo picker do admin-arquivos (demanda direta do Engenheiro Chefe na sessão de 2026-07-30 / BATCH-099). Decisões desta rodada:

  1. O picker é o mesmo gerenciador, não uma versão reduzida: todos os pontos de seleção de arquivo do sistema (campo de imagem, seletor de fonte do embed, image-picker do styler) abrem admin-arquivos/?paginaIframe=sim. Esconder ali as ferramentas de gestão (<!-- normal-tools -->: Adicionar, Nova pasta, Selecionar todos) obrigava o usuário a abrir o módulo em outra aba só para subir o arquivo que ele estava tentando escolher. A ocultação foi removida.

  2. A ocultação contradizia o resto do código: o próprio template já emitia o href do botão "Adicionar" com o placeholder #paginaIframe# (resolvido para ?paginaIframe=sim) e o JS já mantinha esse href atualizado com a pasta corrente acrescentando &paginaIframe=sim (atualizarAddHref()). O fluxo de upload dentro do iframe estava implementado — apenas inacessível. Nenhuma outra funcionalidade dependia da ausência desses controles (o JS não condiciona "Nova pasta", renomear, excluir nem a barra de seleção ao modo picker).

  3. O botão voltar precisa existir no picker: com o upload liberado, admin-arquivos-adicionar passou a ser alcançável dentro do iframe; o botão voltar (destino já preparado: admin-arquivos/?paginaIframe=sim) é o caminho de retorno à listagem para escolher o arquivo recém-enviado. Antes ele era removido no picker justamente porque esse fluxo não existia — mantê-lo removido deixaria o usuário preso na tela de envio.

  4. O que continua diferente no picker (de propósito): a célula de arquivo mostra "Selecionar" em vez de "Copiar URL" (a ação do picker é devolver o arquivo ao campo que o chamou), a galeria segue a mesma regra e a listagem não usa o cache de última pasta (localStorage), começando na pasta pedida por quem abriu o seletor.

Validação: php -l OK, composer test 158/158, npx vitest run 64/64. A mudança é de renderização condicional de template (sem cobertura unitária viável sem banco), então a verificação é manual/runtime, registrada no checklist. Homologação pendente com o operador. Nenhum git commit/git push executado.

O Futuro é Híbrido: Humano + IA.

O Conn2Flow é a ferramenta. Se você precisa do Arquiteto para desenhar a estratégia da sua empresa usando essa tecnologia...

Contrate Consultoria Especializada