Conn2Flow
Navegação das docs

SDD

Decision Log (Archive: DEC-051 to DEC-060)

Painel Auxiliar de Formatação Visual (req-037 / BATCH-037). Decisões de design: 1. Estrutura de Duas Colunas: Dividir #html-editor-tailwind-styler em duas colunas: a da esquerda…

As decisões correntes mais recentes permanecem em sdd/decisions/DECISION-LOG.md. Este arquivo guarda o histórico movido para manter o log corrente enxuto (≤10 itens).

DEC-051 - 2026-06-13 - accepted

Painel Auxiliar de Formatação Visual (req-037 / BATCH-037). Decisões de design:

  1. Estrutura de Duas Colunas: Dividir #html-editor-tailwind-styler em duas colunas: a da esquerda mantendo a lista de tags e input autocomplete existentes; a da direita contendo o Visual Helper com botões interativos.

  2. Propriedades Mapeadas: O Visual Helper deve gerenciar alinhamento de texto (Esquerda, Centro, Direita, Justificado), espaçamento/padding (Nenhum, Pequeno, Médio, Grande), arredondamento de bordas (Reto, Leve, Médio, Redondo) e paletas de cores rápidas em formato circular para Texto e Fundo.

  3. Sincronização e Limpeza de Conflitos:

    • Cada grupo de controles de formatação visual deve ser mutuamente exclusivo (ex: aplicar bg-blue-500 limpa outras classes com prefixo bg-).

    • Ao carregar ou atualizar o selecionado, ler as classes vigentes e adicionar a classe .active aos botões visuais correspondentes.

    • Qualquer clique nos controles visuais recalcula as classes aplicadas, atualiza a lista de tags na esquerda e chama afterDomMutation().

  4. Empilhamento Responsivo: Se a largura do elemento selecionado for inferior a 400px, o painel do styler muda para o layout vertical .he-styler-stacked (flex-direction: column) para manter a usabilidade em botões e blocos pequenos.

Notas de execução (BATCH-037): a limpeza de conflitos NÃO usa prefixo cego (bg-/text-), e sim lista fechada do grupo + regex específica de cor — text-(<cor>)-<shade>|text-white|text-black e bg-(<cor>)-<shade>|bg-white|bg-black|bg-transparent. Isso evita dois falsos positivos: (a) trocar a cor do texto NÃO remove o alinhamento text-left/center/... nem o tamanho text-lg/...; (b) trocar a cor de fundo NÃO remove utilitários de imagem como bg-cover/bg-center/bg-no-repeat. Os grupos de alinhamento/padding/bordas usam lista fechada (+ regex ^p-\d+$ e ^rounded(-.+)?$ para variantes digitadas pelo operador). O painel (buildHelperPanelHtml()) é estático (gerado uma vez de tailwindHelperConfig()); renderStyler() só alterna .active via syncHelperButtons(). O botão "Nenhum" do padding mapeia para p-0 (classe real). Defaults destacados quando nada do grupo está presente: alinhamento→text-left, bordas→rounded-none, fundo→bg-transparent.

DEC-052 - 2026-06-13 - accepted

Inversão e Expansão do Painel Auxiliar de Formatação Visual (req-038 / BATCH-038). Decisões de design:

  1. Inversão de Colunas: Inverter a exibição das colunas do styler #html-editor-tailwind-styler. A Coluna Esquerda passa a exibir os controles visuais interativos (Visual Helper), enquanto a Coluna Direita exibe as tags de classes aplicadas e o input autocomplete.

  2. Propriedades Estendidas: Adicionar 7 novos grupos ao Visual Helper:

    • Tamanho do Texto (text-sm/base/lg/xl).

    • Espessura do Texto (font-normal/medium/bold).

    • Margem Externa (m-0/2/4/8).

    • Espessura da Borda (border-0/border/border-2/border-4).

    • Cor de Borda (Paleta circular de 8 cores).

    • Opacidade (opacity-100/75/50/25).

    • Layout / Display (block/inline-block/flex/grid).

  3. Limpeza e Sincronização Estendidas:

    • Ampliar applyHelperClass() para remover classes conflitantes do mesmo grupo usando regras específicas (ex: regex para margem ^m-\d+$ ou ^m-[a-z]+$, cor de borda ^border-(<cor>)-<shade>|border-white|border-black, e display/font-size/font-weight/opacity por lista fechada).

    • Estender syncHelperButtons() para detectar e destacar o estado ativo para todos os novos grupos.

  4. Deslocamento da Toolbar na Borda Inferior: Se a barra flutuante for desenhada na borda inferior do elemento (quando não há espaço no topo), a posição inicial de empilhamento vertical do breadcrumb (stackTop) é acrescida da altura da toolbar (toolbar.offsetHeight + 12), empurrando os elementos de navegação e formatação para baixo da barra e evitando sobreposições.

Notas de execução (BATCH-038): sob autorização do Engenheiro Chefe Humano para "ser criativo e criar mais ainda", o painel foi expandido para 20 grupos organizados em 4 seções (.he-helper-section): Texto (Alinhamento, Tamanho, Peso, Caixa de texto, Decoração, Cor), Layout (Exibição, Direção flex, Justificar, Alinhar itens, Gap), Caixa (Largura, Padding, Margem), Aparência (Fundo, Cantos, Borda, Cor da borda, Sombra, Opacidade) — os 7 pedidos + 8 extras. O styler ganhou max-height:72vh + scroll. A limpeza de conflitos combina três mecanismos em applyHelperClass(): lista fechada do grupo (derivada dos botões), cleanList (variantes de palavra isolada: displays/sombras/transform/decoração/flex-dir/justify/items/width) e cleanRe (cores, ^p-\d+$, ^m[xytblr]?-\d+$ que preserva mx-auto/min-h-*, ^gap(-[xy])?-\d+$, ^opacity-\d+$, ^border-\d+$). As cores de borda são renderizadas como anel (.he-helper-bordercolor, fundo branco + borda 3px) para distingui-las das cores de fundo. As classes de largura com barra (w-1/2) funcionam normalmente em classList/seletores de atributo. Ajuste pós-feedback: como 20 grupos deixaram o painel alto, as seções viraram um accordion (cabeçalho .he-helper-section clicável + .he-helper-section-body colapsável; uma seção aberta por vez, "Texto" por padrão), reduzindo a altura visual; o estado persiste entre seleções.

DEC-053 - 2026-06-14 - accepted

Melhorias Visuais e Funcionais do Editor HTML Visual (req-039 / BATCH-039). Decisões de design:

  1. Nova Seção "Fundo" (Background) no Styler: Criar seção "Fundo" reposicionando a cor de fundo (bgColor) de "Aparência" para ela. Adicionar controles para imagem de fundo: botão de ImagePicker (enviando html-editor-imagepick-open para o pai), tratamento de retorno html-editor-imagepick-selected (aplicando estilo inline background-image e gerenciando preview local via flag imagePickerTarget = 'background'), e grupos de botões Tailwind para repetição (bg-repeat/bg-no-repeat etc.), tamanho (bg-auto/bg-cover etc.) e posição (bg-center/bg-top etc.).

  2. Deseleção e Toggle de Seleção: Inserir botão de deselecionar na toolbar flutuante (.he-tb-deselect, ícone times circle ou ban) com estilo destacado. Implementar toggle de seleção no clique global: se o elemento clicado for exatamente o selecionado ativo (el === this.selectedElement), deselecioná-lo via clearSelection(). Preservar tecla Esc.

  3. Preservação de Rolagem no Histórico (Scroll Sync): Armazenar a posição scrollTop do iframe do preview em cada snapshot da undoStack/redoStack (formato { html, scrollTop }). Ao realizar Undo (Ctrl+Z) ou Redo (Ctrl+Y), restaurar a rolagem vertical (window.scrollTo) correspondente.

  4. Alinhamento e Wrapping dos Breadcrumbs: Ajustar o CSS de #html-editor-selection-breadcrumb e #html-editor-selection-children para display: flex; flex-wrap: wrap; white-space: normal; para permitir quebra de linha. No método updateSelectionUI(), aplicar um clamp horizontal se left + offsetWidth ultrapassar os limites do viewport do iframe.

  5. Elemento Fantasma (Ghost Insert Cursor): No modo de inserção, desenhar e mover o elemento insertGhost com opacidade 0.6 e borda tracejada roxa/cinza seguindo o mouse. Destruir ao sair do modo de inserção.

  6. Highlight de Append Completo: Ao realizar arraste ou inserção, se não houver posição em linha (antes/depois) mapeada, mas o cursor estiver sobre um contêiner pai, destacar o contêiner de destino com uma borda amarela tracejada de 4 lados (#html-editor-parent-highlight-overlay).

  7. Renderização de Widgets com Esqueleto Real (AJAX): Criar o endpoint AJAX html-editor-widget-render no backend que aceita signature do widget, e chama widgets_get(['id' => signature]) com $_GESTOR['ajax'] = false para retornar o HTML real do widget. No visual editor, chamar este endpoint ao adicionar ou atualizar widgets e injetar o resultado no .conn2flow-widget-inner do wrapper virtual.

Notas de execução (BATCH-039): (a) O iframe (srcdoc) não faz o AJAX direto — pede ao pai por postMessage (c2f-he:widget-render), o html-editor-visual-controls.js consulta html-editor-widget-render e devolve c2f-he:widget-rendered; cada wrapper recebe um data-widget-id para casar a resposta. (b) O preview renderizado NÃO vaza no save: o mockup original é preservado em data-widget-mockup e é ele (not the .conn2flow-widget-inner com o preview) que volta entre os comentários <!-- widgets#... --> em extractUserHtml. O endpoint valida a assinatura por regex ^[\w\-]+->[\w\-]+\(.*\)$ antes de chamar widgets_get. (c) O bgImage é um grupo de kind:'bgimage' (sem classe Tailwind) que aplica background-image inline; a regex de limpeza de bgColor (bgColorRe) não atinge bg-cover/bg-center/bg-repeat, então cor de fundo e propriedades de imagem coexistem. (d) Highlight de contêiner e placeholder em linha são mutuamente exclusivos (showDropIndicator); a inserção real usa insertAtTarget (não depende mais do placeholder estar no DOM). (e) Após undo/redo, rerenderVisibleWidgets() repinta o esqueleto dos widgets sem mockup.

DEC-054 - 2026-06-14 - accepted

Ajustes Finais no Pré-visualizador de Widgets e Elemento Fantasma do Cursor (req-040 / BATCH-040). Decisões de design:

  1. Renderização de Widgets no Pré-visualizador: Injetar um script utilitário em previewHtmlConteudo() (em html-editor-interface.js) para processar e renderizar dinamicamente os widgets do preview no #iframe-visualizacao-pagina via AJAX para html-editor-widget-render (com os parâmetros de window.parent.gestor).

  2. Elemento Real no Cursor Fantasma: Em createInsertGhost(payload) (em html-editor.js), renderizar o elemento ou wrapper de widget real usando buildElement() ou buildWidgetWrapper() (com renderização assíncrona) e anexá-lo como filho de #html-editor-insert-ghost.

  3. Estilo Limpo do Cursor Fantasma: Ajustar a estilização de #html-editor-insert-ghost para remover restrições de layout estritas que impeçam ou quebrem a exibição e estruturação interna do elemento real renderizado.

Notas de execução (BATCH-040): (a) O script do preview é uma função nomeada widgetPreviewBootstrap() injetada por (${fn.toString()})() — isso preserva as regex literais (evita o problema de \s→s que ocorreria escrevendo o script como template string) e mantém o código autocontido (usa só globals do iframe + window.parent.gestor). Injetado nos dois caminhos de previewHtmlConteudo (layout e padrão), antes de </body>. O contêiner usa display:contents para não introduzir caixa extra no fluxo. (b) O nó de widget dentro do ghost recebe data-widget-id e dispara requestWidgetRender, mas não vaza no save/snapshot: o ghost (#html-editor-insert-ghost) é isEditorOwned, então seu conteúdo não é filho direto do body nem entra em getUserContentNodes. (c) pointer-events:none foi reforçado também nos descendentes do ghost (#html-editor-insert-ghost *) para a caixa flutuante jamais interceptar o cursor.

DEC-055 - 2026-06-15 - accepted

Correções e Melhorias no Módulo publisher-index (req-041 / BATCH-041). Decisões de design:

  1. Busca Tolerante a Acentuação:

    • Implementar a função utilitária publisher_index_widget_unicode_escape() que converte caracteres não-ASCII (acentos) do termo pesquisado para suas representações de escape Unicode (u00xx e \u00xx).

    • Modificar a query SQL para realizar buscas combinadas com OR cobrindo o termo original e as versões Unicode escapadas, permitindo que a busca encontre registros mesmo com acentuação corrompida no banco de dados.

  2. Decodificação de Unicode Corrompido:

    • Criar o helper publisher_index_widget_corrigir_unicode() que detecta padrões de escape Unicode (\?u[0-9a-fA-F]{4}) em strings e os re-decodifica para UTF-8 nativo usando a função pack('N', hexdec($m[1])) e mb_convert_encoding().

    • Aplicar esta decodificação ao título e campos dinâmicos ao buscar publicações do banco, garantindo exibição textual correta no widget renderizado.

  3. Filtro Estrito de Publicações (INNER JOIN):

    • Alterar o LEFT JOIN da consulta de publicações para INNER JOIN entre paginas e publisher_pages.

    • Isso elimina do widget a renderização indesejada da própria página de índice que renderiza o widget (e qualquer outra página comum que compartilhe o mesmo publisher_id mas não seja uma publicação cadastrada na tabela publisher_pages).

  4. Resolução de Métricas de Paginação:

    • Expor as variáveis globais de widget [[page_count]] e [[page_total]] no backend PHP.

    • Enviar a propriedade total com a contagem total de publicações filtradas no JSON do retorno AJAX.

    • No cliente JS (publisher-index.widget.js), contar fisicamente os filhos renderizados na listagem e preencher o texto de seletores [data-page-count] e [data-page-total] dinamicamente no término de cada chamada AJAX.

  5. Ajustes e Simplificação de Layout do CRUD:

    • Remover o input de quantidade máxima de itens (#count) por ser redundante com a paginação do índice.

    • Expandir os campos de Regra de Alimentação (rule) e Ordenação (order_by) para 8 colunas cada.

    • Mover #manual-items-wrapper para dentro do bloco de Regra de Curadoria, e corrigir os typos de barra invertida em suas tags HTML (div\ class e \div\).

  6. Integrações com Editor HTML Visual:

    • Declarar window.publisher_index_update_target_variables no interface JS do editor.

    • Atualizar alvoUsaItemVars() para suportar o alvo 'publisher-index'.

    • Atualizar a simulação interna de itens no editor para ler de #items_per_page (fallback 10) em vez de #count.

Notas de execução (BATCH-041): (a) banco_select mapeia a chave do array de retorno pela string literal de cada campo (via explode(',', ...)), então COUNT(*) AS total vira uma chave não-trivial — publisher_index_widget_contar_publicacoes() lê o valor com reset($row). (b) A cláusula de busca foi extraída para publisher_index_widget_clausula_busca() (reusada na listagem e na contagem) gerando p.nome LIKE para o termo literal + variantes u00xx/\u00xx; o banco_escape_field escapa a \ da variante com barra, casando o conteúdo corrompido no banco. (c) O bloco de métricas "Exibindo X de Y" foi adicionado aos templates físicos lista/grid (e aos novos) com [data-page-count]/[data-page-total] e os placeholders @[[page_count]]@/@[[page_total]]@; o JS conta os filhos da listagem ignorando .publisher-index-empty. (d) O branch de simulação do publisher-highlights foi unificado com publisher-index em publisherVariablesOrSimulation (conforme a origem do count: #count vs #items_per_page). (e) Pedido adicional do Engenheiro Chefe Humano nesta rodada: criados 2 novos modelos para publisher-index — publisher-index-timeline (Linha do Tempo, trilho vertical com marcadores) e publisher-index-agenda (cartões horizontais com data em destaque), em pt-br e en, usando exclusivamente variáveis garantidas + blocos do widget, registrados em publisher-index.json (version 1.0, checksums vazios para o pipeline). Versões dos recursos alterados incrementadas (módulo 1.0.0→1.1.0; templates/páginas 1.1→1.2); checksums recalculados pelo pipeline.

DEC-056 - 2026-06-15 - accepted

Controle de Métricas no publisher-index e suíte de testes MySQL (req-042 / BATCH-042). Decisões desta rodada:

  1. Controle show_metrics como flag retrocompatível de schema: O campo vive in fields_schema, com default true em adicionar/editar/clonar e no JavaScript administrativo. Registros antigos continuam exibindo métricas sem migração de dados.

  2. Bloco condicional próprio para métricas: As métricas usam os delimitadores <!-- metrics < --> ... <!-- metrics > -->, processados pelo mesmo helper publisher_index_widget_bloco_condicional() dos controles de busca, ordenação e carregar mais.

  3. Variável global show_metrics: O renderer passa a resolver [[show_metrics]]/@[[show_metrics]]@ como 'true' ou 'false', permitindo templates customizados que queiram refletir esse estado em atributos ou lógica visual.

  4. Teste integrado MySQL gated e banco dedicado: O teste de busca real só roda com CONN2FLOW_RUN_DB_TESTS=1 e exige CONN2FLOW_DB_DATABASE=conn2flow_test. O setup cria/dropa apenas tabelas mínimas nesse banco dedicado, evitando qualquer impacto no banco de desenvolvimento.

  5. Correção no LIKE para barra invertida literal: A validação integrada revelou que \ também é caractere de escape no padrão LIKE do MySQL. Para casar nomes gravados como T\u00edtulo, a variante com barra precisa ter a barra dobrada no padrão antes de banco_escape_field().

Notas de execução (BATCH-042): implementado em publisher-index.php, publisher-index.js, publisher-index.widget.php, seis views CRUD, oito templates físicos e tests/Integration/PublisherIndexWidgetTest.php. Validação: php -l nos PHPs alterados, node --check no JS administrativo, composer test OK e teste MySQL dedicado contra conn2flow_test OK.

DEC-057 - 2026-06-15 - accepted

Curadoria Manual no publisher-index, Novos Templates com Imagem, Variável de Widget no CRUD e Suporte Inline no Editor (req-043 / BATCH-043). Decisões desta rodada:

  1. Regra de alimentação manual resolvida em PHP: Quando rule === 'manual' no fields_schema, a busca dinâmica é substituída pela curadoria explícita. Uma nova função publisher_index_widget_lista_manual() consulta os itens com filtro restritivo AND p.id IN (...) (cada ID via banco_escape_field), sem ORDER BY nem LIMIT no SQL, mapeia os registros por page_id e reordena em PHP para respeitar exatamente a ordem de selected_items; a busca textual filtra em PHP (case-insensitive sobre título e campos custom, via publisher_index_widget_item_casa_busca()) e a paginação é aplicada por array_slice(offset, limit). buscar_publicacoes() e contar_publicacoes() ganham um dispatch no topo: sem busca, a contagem é count(selected_items) literal; com busca, conta os itens curados que casam; selected_items vazio retorna []/0. render_inline e render_ajax passam rule/selected_items adiante. A montagem de itens foi extraída para publisher_index_widget_montar_itens() (compartilhada com a busca dinâmica). Retrocompat: registros sem rule seguem em latest (dinâmico).

  2. Novos modelos com imagem destaque: 4 templates físicos (publisher-index-grid-imagem, publisher-index-lista-imagem, em pt-br e en), clonados dos modelos grid/lista existentes acrescentando <img src="@[[item#imagem]]@"> (campo custom mapeável) e um resumo @[[item#resumo]]@, preservando os blocos do widget (search-input/sort-select/metrics/item/no-item/load-more) e utilitários Tailwind padrão. Registrados em publisher-index.json com version 1.0 e checksums vazios (pipeline recalcula).

  3. Variável de Widget no CRUD dos 4 módulos: A aba data-tab="hep-widget" ganhou, entre o bloco informativo e o editor CodeMirror, um campo "Variável do Widget" (input readonly #hep-widget-val + botão #btn-copy-widget-val .teal com ícone copy) e um header "Código HTML". O JS de cada módulo (updateWidgetCodeTab) passa a popular #hep-widget-val com a assinatura em formato de variável [[widgets#MODULO->render({"grupo_slug":"SLUG"})]] e registra (uma vez, via delegação) o clique de copiar usando navigator.clipboard.writeText() com fallback por elemento temporário e feedback visual i18n.

  4. Variáveis de widget inline no editor e pré-visualizador: No renderWidgets() de widgetPreviewBootstrap (html-editor-interface.js) e em convertWidgetCommentsToWrappers() (html-editor.js), antes da varredura de comentários, ocorrências de [[widgets#...]]/@[[widgets#...]]@ no document.body.innerHTML são convertidas em pares de comentários <!-- widgets#$1 < --><!-- widgets#$1 > --> (regex literal /@?\[\[widgets#(.+?)\]\]@?/gi, preservada pela injeção via .toString()), tornando-as renderizáveis (preview) e editáveis (wrappers).

  5. Correção de teste (req §5): O stub jQuery do Vitest (tests/Unit/JS/helpers/jquery-stub.js) não implementava .children()/.not(), deixando publisher-index.widget.test.js (do BATCH-041/042) vermelho no baseline. Adicionados os dois métodos (adição pura, sem alterar comportamento existente) — formalizado no req-043 §5. As versões dos módulos foram incrementadas (publisher-index 1.1.0→1.2.0; menus 1.0.2→1.0.3; galleries 1.0.1→1.0.2; publisher-highlights 1.2.0→1.2.1); as version de páginas/templates ficam para o pipeline (que detecta mudança por checksum dos *Data.json, não pela version do manifest).

  6. Prefixagem dinâmica de url-raiz em campos de imagem (req §6): Os campos custom em publisher_pages.fields_values guardam caminhos relativos, mas os novos templates de imagem destaque precisam do caminho a partir da raiz. Como publisher_pages não declara tipos, o tipo de cada campo é lido do fields_schema da tabela publisher (fields[].{id,type}) por uma helper com cache estático por idioma+publicador (publisher_index_widget_tipos_campos_publicador / publisher_highlights_widget_tipos_campos_publicador). Em publisher-index, montar_itens($rows, $publisher_id = null) ganhou o 2º parâmetro (passado por buscar_publicacoes e lista_manual); em publisher-highlights, a prefixagem entra no loop de buscar_publicacoes. Campos cujo tipo é 'image' e valor não-vazio recebem $_GESTOR['url-raiz'] via *_widget_prefixar_url_raiz(), que preserva URLs já absolutas (http/https/protocol-relative/data:) e evita barra dupla (divergência defensável do req, que pedia prefixo incondicional).

Notas de execução (BATCH-043): (a) A reordenação manual usa um mapa page_id => item e itera selected_items, então IDs inexistentes, inativos ou sem correspondência em publisher_pages (INNER JOIN) são silenciosamente descartados — mas a contagem sem busca segue count(selected_items) literal por requisito (§1.2). (b) A inserção do campo "Variável do Widget" nos 24 HTMLs foi feita por script PowerShell (UTF-8 sem BOM, acentos via [char] para sobreviver ao PS 5.1) inserindo o bloco antes do <div style="border:1px solid #ccc;"> comum aos 24 arquivos. (c) Os 4 JS administrativos compartilham o mesmo handler de cópia (delegado, registrado uma vez), diferindo só no MODULO da assinatura. (d) A prefixagem de imagem (§6) lê o tipo do fields_schema da tabela publisher (não de publisher_pages); o teste integrado passou a criar/popular também a tabela publisher no seed (noticias com resumo=text, imagem=image) — necessário porque montar_itens agora a consulta em todos os testes integrados. (e) Validação: php -l/node --check/JSON OK; PHPUnit 40/40 (132 asserts) com MySQL conn2flow_test incluindo os testes da curadoria manual (ordem/slice/busca/contagem), item_casa_busca, prefixar_url_raiz (puro) e prefixagem de imagem (integrado, relativo→raiz / absoluto preservado / text intacto); Vitest 3/3 OK.

DEC-058 - 2026-06-15 - accepted

Prefixagem Dinâmica de URL Raiz para Campos de Imagem nos Widgets (req-043 / BATCH-043). Decisões de design:

  1. Recuperação de Tipos de Campo via Schema: A tabela publisher guarda o schema de campos no campo fields_schema (JSON). Para identificar quais campos customizados de uma publicação correspondem a imagens, o sistema deve recuperar este schema usando o publisher_id (natural key) e a linguagem corrente ($_GESTOR['linguagem-codigo']), mapeando os IDs de campos que possuem o tipo "image".

  2. Otimização por Cache em Memória: Para evitar que o sistema execute consultas repetitivas ao banco de dados ao processar múltiplos itens do mesmo publicador (no mesmo request/render), os schemas decodificados devem ser armazenados em cache estático por chave composta (publisher_id + language).

  3. Prefixagem Dinâmica nos Widgets:

    • No publisher-index: A assinatura da função helper de montagem de itens (publisher_index_widget_montar_itens()) passa a aceitar $publisher_id. Seus chamadores (buscar_publicacoes e lista_manual) fornecem o ID. Durante a conversão de campos customizados, caso o tipo do campo no schema do publicador seja 'image' e ele contenha valor, a URL raiz ($_GESTOR['url-raiz']) é concatenada automaticamente.

    • No publisher-highlights: Na rotina de busca de publicações (publisher_highlights_widget_buscar_publicacoes()), após decodificar o fields_values, realiza-se a mesma verificação baseada no schema do publicador para prependir a URL raiz aos campos do tipo 'image'.

Notas de execução (BATCH-043): A mudança visa garantir que os novos templates que utilizam @[[item#imagem]]@ (ou campos mapeados equivalentes de imagem) recebam o caminho absoluto ou a partir da raiz do site configurada na variável global $_GESTOR['url-raiz'] em tempo de execução.

DEC-059 - 2026-06-16 - accepted

Correção de Caracteres Especiais nos Widgets, Suporte AJAX no Preview e Refatoração de Módulos (req-044 / BATCH-044). Decisões de design:

  1. Assinatura de widget fora do DOM (mapa em memória): A causa-raiz da corrupção (menus-&gt;render/&amp;gt;/&quot;) era persistir a assinatura crua (->, aspas, chaves) no atributo data-widget-signature, que o navegador re-escapa ao serializar/reparsear o innerHTML. O atributo foi removido; a assinatura passa a viver apenas em this.widgetsMap (objeto em memória do HtmlEditor), chaveado por um data-widget-id limpo (widget-N, contador this.widgetCounter). No DOM ficam só atributos alfanuméricos (data-widget-id/data-widget-type/data-widget-slug/data-widget-variable). getWidgetSignature(wrapper) resolve pelo mapa (fallback reconstruindo de type/slug).

  2. Diferenciação variável vs comentário na carga: convertWidgetCommentsToWrappers() converte [[widgets#...]]/@[[widgets#...]]@ em comentários temporários rotulados widgets-var# (distintos dos comentários reais widgets#), marcando isVariable no mapa. No save (extractUserHtml), isVariable=true reconstrói a variável [[widgets#signature]] e isVariable=false reconstrói o par de comentários com o mockup — corrigindo a perda do formato de variável reportada no req §1.1.

  3. Unescape de entidades (incl. duplo escape): helper htmlUnescape (no html-editor.js) e unescapeEntities (interno ao widgetPreviewBootstrap, injetado via .toString()) usam <textarea> (RCDATA) em laço idempotente (até 3 passes) para decodificar &gt;/&quot;/&amp; e o duplo escape &amp;gt;, aplicado antes de processar/rotear a assinatura.

  4. Save de variável sem re-escape (token): como container.innerHTML re-escaparia >/& de um text node, o save substitui o wrapper-variável por um token alfanumérico (__C2F_WVAR_N__) e troca o token pela string [[widgets#signature]] na string final — preservando ->/aspas intactos.

  5. Edição/clonagem com novo id: editWidgetWrapper gera um novo data-widget-id e copia os metadados anteriores ao atualizar o slug, evitando que clones na tela compartilhem a mesma entrada do mapa.

  6. *Inclusão automática e desduplicada de `.widget.jsno preview**:previewHtmlConteudoextrai as assinaturas presentes (extrairAssinaturasWidgets) e injeta no<head>do iframe os scripts controladores{raiz}{modulo}/widget.js(mapa fixogalleries/publisher-index/menus), uma única tag por módulo. URL no padrão usado pormenus.js/galleries.js/publisher-index.js(gestor.raiz + modulo + '/widget.js'`).

  7. widgetsToAjax replicado no iframe: como o srcdoc é gerado estaticamente em JS (o PHP não cria a variável nesse contexto), montarWidgetAssetsHead injeta window.gestor = Object.assign({}, window.parent.gestor); window.gestor.widgetsToAjax = "SIG1<#;>SIG2…" com as assinaturas únicas. Contrato confirmado em widgets.php: cada item de widgetsToAjax (divisor <#;>) é a assinatura completa repassada a widgets_get como $id.

  8. Refatoração para html-editor-modules.js (divergência defensável do req §5.2): as 26 simulações de menus/galleries/publisher (+ MENUS_SIM_FALLBACK/GALLERIES_SIM_FALLBACK + a var de estado publisher_table_tr_skeleton) foram extraídas para o novo arquivo, anexadas ao window, carregado antes do interface (html-editor.php). Além das 3 variáveis pedidas pelo req (CodeMirrorHtml/CodeMirrorHtmlExtraHead/publisher_fields_schema), a análise de dependências mostrou que as funções movidas também usam as auxiliares frameworkCSS/previewHtml/regexVariaveisGlobal/alvoUsaItemVars (que permanecem no interface) — todas igualmente expostas no window para que as referências por nome nu resolvam em runtime. Os itens intercalados não-listados (getUpdatedHtmlWithValues, window.publisher_*_update_target_variables, addVariableSkeleton) permaneceram no interface.

Notas de execução (BATCH-044): (a) A extração do Slice 5 foi feita por script Node temporário (localização por nome no nível de indentação 4 + fim por linha }/];, mais comentários contíguos acima), preservando o código verbatim e removendo as exposições obsoletas (window.publisherValuesUpdate/window.publisherGetAllVariables, agora feitas pelo modules.js). (b) publisher_fields_schema é compartilhado por referência de objeto (mutação de .template_map no interface é visível no modules.js); nunca é reatribuído. (c) Validação: node --check em 5 JS, php -l no html-editor.php, Vitest 3/3 e PHPUnit 40/40 (112 asserts, 4 skip de DB gated). Deploy/runtime pendentes com o operador.

DEC-060 - 2026-06-16 - accepted

Correção de Temporal Dead Zone (TDZ) na inicialização do Editor HTML Visual (req-045 / BATCH-045). Slice corretivo do BATCH-044. Decisão:

  1. Causa-raiz: const/let no meio do arquivo lidas por init síncrona: html-editor-interface.js roda inteiro dentro de $(document).ready(...). A const WIDGET_SCRIPT_MODULES (introduzida no BATCH-044) e a let total_sessoes ficam no meio do arquivo (~L1544/~L2056). Como const/let não são hoisted (ao contrário de var/function), qualquer código de inicialização que rode ANTES dessas linhas e cascateie em previewHtml()→previewHtmlConteudo()→montarWidgetAssetsHead() (lê WIDGET_SCRIPT_MODULES) ou em pageModificationContainerMove() (lê total_sessoes num setTimeout) bate na Temporal Dead Zone → ReferenceError, que aborta o resto do ready.

  2. Dois gatilhos síncronos, não um: o req-045 atribuía o erro apenas à chamada síncrona contentPageTabHandler(). O runtime provou que mover só esse não bastou: o gatilho principal é o onLoad que o Fomantic dispara SÍNCRONAMENTE ao inicializar $('.menuContainerPagina .item').tab({...}) (a init do tab executa changeTab→onLoad para a aba ativa imediatamente). Esse onLoad chama previewHtml() e pageModificationContainerMove(). Logo, ambos (o contentPageTabHandler() e a init do .tab()) precisam rodar após as declarações.

  3. Solução: init do tab + kickoff no fim do ready: remover do meio do arquivo tanto a invocação contentPageTabHandler() quanto o bloco $('.menuContainerPagina .item').tab({onLoad...}), e colocá-los no fim do ready, após todas as declarações locais — preservando a ordem original (handler antes da init do tab; a chamada .tab('change tab') dentro do handler permanece no-op enquanto o tab não está inicializado, idêntico ao comportamento anterior). O outro tab (.menuPaginas, ~L688) não foi movido: seu onLoad só faz CodeMirror*.refresh() (não toca as consts tardias). Inits intermediárias (dropdowns .frameworkCSS/.publisher-design-mode-*, ~L830-849) apenas registram callbacks onChange (não disparam na carga). Alternativa rejeitada (mover só as 2 declarações para o topo / inline): mais frágil e pontual; mover os gatilhos para o fim é a invariante correta e cobre a classe inteira. versao da biblioteca html-editor bumpada pelo operador para cache-bust (1.3.x). Validação: node --check OK + verificação de que contentPageTabHandler() e a init do .tab() são as únicas execuções síncronas que cascateiam em previewHtml e estão ambas no fim; runtime (console sem ReferenceError, troca de abas e preview de widgets) pendente com o operador.

  4. Overlays do editor destruídos por body.innerHTML no caso de widget-variável (html-editor.js): bug colateral correlato, encontrado em runtime. Na init do HtmlEditor (dentro do iframe), createOverlays/createToolbar/createPlaceholder anexam os overlays/toolbar/breadcrumb/styler ao document.body ANTES de convertWidgetCommentsToWrappers(). Esse método convertia variáveis inline [[widgets#...]] em comentários via document.body.innerHTML = bodyHtml.replace(...), o que destrói todos os filhos do body — inclusive a UI do editor — quebrando a seleção de elementos. Só ocorria com widget em formato de variável (no formato de comentário o innerHTML não era reescrito). Solução: novo convertWidgetVariablesToComments() que faz a conversão de forma CIRÚRGICA, percorrendo só os text nodes do conteúdo (TreeWalker SHOW_TEXT, pulando a UI html-editor-*) e substituindo cada [[widgets#...]] por um par de comentários widgets-var# via replaceChild de um fragmento — sem nunca reescrever body.innerHTML. Preserva a correção de caracteres especiais do req-044 (a assinatura vem limpa do nodeValue). O widgetPreviewBootstrap (iframe de preview) mantém o reset de innerHTML pois lá não há UI persistente a preservar.

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