DEC-071 - 2026-06-29 - accepted
Alinhamento Visual do CRUD e Painel de Conteúdo de Formulários com Menus (req-068 / BATCH-068). Decisões desta rodada:
-
Unificação Visual: Para harmonizar a interface administrativa de formulários com o módulo de menus, a aba principal "Template" foi removida dos 6 HTMLs de CRUD (
adicionar/editar/clonarempt-br/en). O seletor de modelo (#template_id) e a seção de abas do editor/prévia/widget foram movidos para a raiz da página sob o cabeçalho<h4 class="ui dividing header">Conteúdo do Formulário</h4>(en:Form Content). -
Prevenção de Conflito de Abas via Contexto: Com a coexistência de dois menus de abas na mesma página (
menuFormsemenuConteudoForm), a inicialização padrão de abas do Fomantic-UI entrava em conflito, ocultando segmentos incorretos. A decisão técnica para isolar os escopos foi envolver os blocos no HTML em containers dedicados (.forms-main-tabse.forms-content-tabs) e configurar a inicialização noforms.jspassando a propriedadecontextapontando para esses seletores. -
Persistência de Aba Ativa: As abas do conteúdo do formulário passaram a persistir a aba ativa sob a chave
tabFormContentActive(no localStorage do respectivo módulo), disparando a sincronização correta ao carregar a página e integrando-se comwindow.contentPageTabHandler()para sincronizar os CodeMirrors.
DEC-072 - 2026-06-29 - accepted
Integração do Módulo de Formulários com o Controlador de Submissão da Biblioteca de Formulários e Suporte a Múltiplos Formulários Concorrentes (req-069 / BATCH-069). Decisões desta rodada:
-
formulario_controlador()aceita string OU array de IDs: a função normaliza$formIdem$formIds(is_array($formId) ? array_values($formId) : [$formId]) e processa a consulta na tabelaforms, a verificação de reCAPTCHA, o controle de acesso (formulario_acesso_verificar) e a preparação de parâmetros numforeach ($formIds as $fid), com reset explícito das variáveis condicionais por iteração (evita vazamento de config entre formulários). A extração do componenteform-ui(prompts/ui-texts/ui-components/block-wrapper) é feita uma única vez antes do laço. A query usabanco_escape_field($fid)(hardening; o controlador original interpolava o ID cru). -
Injeção indexada por ID + retrocompat plana: as configurações de cada formulário são armazenadas em
$forms_js_vars[$fid]e injetadas comgestor_js_variavel_incluir('form', $forms_js_vars)→gestor.form[fid]. Comogestor_js_variavel_incluirfazarray_merge_recursive, múltiplas chamadas com IDs distintos (dois widgets na mesma página) acumulam chaves indexadas distintas sem colisão. Quando a chamada usa ID único (string), uma segunda injeção replica as variáveis em formato plano no mesmo namespace para preservar a retrocompatibilidade do controlador legado. Versão da bibliotecabiblioteca-formulariobumpada1.1.0→1.2.0(cache-bust doformulario.js). -
Resolução de instância no JS por
data-form-id(divergência do intake): oformulario.jspassa a iterar sobre$('._forms-submissions-controller, .conn2flow-form')e resolve a config de cada<form>lendo o ID porform.attr('id') || form.attr('data-form-id'); se o ID existir como chave emgestor.form, usa a config isolada (gestor.form[formId]), caso contrário faz fallback ao objeto plano. A prosa do req-069 §2 indicavaform.attr('id'), mas todos os templates (clássicocontact.htmle os 5 de widget) usamdata-form-ide nenhum defineidno<form>— seguiu-se o código real (precedente "seguir o código, não a prosa do intake").showErrorpassou a inserir o erro na própria instância (form.prepend) em vez do seletor global._forms-submissions-controller, isolando mensagens por formulário. -
Widget delega à biblioteca e remove o script redundante:
forms_render()(forms.widget.php) substitui a inclusão deforms.widget.jsporgestor_incluir_biblioteca('formulario')+formulario_controlador(['formId' => $registro['id']]), herdando honeypot, reCAPTCHA v2/v3, rate limiting por IP, validações e mensagens centralizadas do backend. O arquivogestor/modulos/forms/forms.widget.jsfoi deletado (sem referências remanescentes no manifesto/código). Ogestor_pagina_recursos_incluir()(CSS/head) e oforms_widget_render_inline()foram preservados.
DEC-073 - 2026-06-30 - accepted
Parametrização de Scripts de Widget no Editor HTML e Dinamização do Mapeamento do Módulo Subscriptions (req-070 / BATCH-070). Três frentes (conn2flow + [REDACTED]):
-
widget_js_includeparametriza a injeção de scripts de widget no preview do Editor HTML (conn2flow):html_editor_componente()aceita a chave'widget_js_include'e a injeta emgestor.html_editor.widget_js_include. No frontend,WIDGET_SCRIPT_MODULESpassa a lê-la dinamicamente (fallback aos 4 módulos do core quando ausente). Mudança-chave emmontarWidgetAssetsHead: quandowidget_js_includeé explicitamente informado pelo backend, os scripts daqueles módulos são injetados mesmo sem assinatura[[widgets#...]]presente — necessário porque o conteúdo do preview do editor de forms É o próprio.conn2flow-form(não um wrapper de widget). Editores de página/layout/componente não informam a chave (injetada comonull) e mantêm a injeção apenas por assinatura (retrocompatibilidade). Cada módulo passa o seu: forms['forms'=>true], menus['menus'=>true], galleries['galleries'=>true], publisher-index['publisher-index'=>true]. -
Recriação do
forms.widget.jspara o preview, via AJAX dedicado (conn2flow): comoformulario_controladorinjetagestor.form[id]apenas no page load do site real (não no iframesrcdocestático do preview), o preview do editor de forms ficou sem as variáveis dinâmicas após a remoção doforms.widget.jsno BATCH-069. O novoforms.widget.jsroda dentro do iframe, varre.conn2flow-form/._forms-submissions-controller, recupera as variáveis via rota AJAXforms-render-editor-html(no switch deforms.php), populawindow.gestor.form[formId]e carrega assíncronamenteinterface.js+formulario.jspara ligar a submissão. Reuso sem duplicação: o laço de montagem das variáveis por formulário foi extraído deformulario_controladorpara um builder puroformulario_montar_js_vars($formIds, $formAjaxOpcao)emformulario.php, consumido tanto pelo controlador (site real) quanto porforms_render_editor_html()(forms.widget.php, preview) — evitando ~170 linhas duplicadas e mantendo a estrutura idêntica degestor.form[id]. -
Dinamização de Planos de Cadastro no módulo Subscriptions ([REDACTED]): o mapeamento formulário↔plano e as regras de signup deixam de ser hardcoded (
$formToPlanemsubscriptions.ajax.public.php,$planMappingemsubscriptions.php) e migram para a tabelasubscriptions_plans(migração20260710120000: colunasis_signup,signup_field_email,signup_field_password,signup_field_password_confirm,signup_id_usuarios_perfis). O CRUD de planos ganha checkbox "Plano de Cadastro", autocomplete de perfil (data-target="perfis"→ endpointbuscar-perfis) e 3 selects de mapeamento de campos populados pelo endpointbuscar-form-campos; o JS alterna o container e pré-seleciona o mapeamento salvo. No checkout (subscriptions_checkout), oform_idvem desubscriptions_planspor slug+idioma (mantida só a normalização de aliases pt-braceleracao/mentoria/personalizado). No processamento (subscriptions_ajax_forms_process),is_signupe os nomes dos campos de e-mail/senha vêm do plano vinculado ao formulário;subscriptions_criar_usuariorecebeid_usuarios_perfisdireto do plano (signup_id_usuarios_perfis), com fallback ao slug quando nulo. Migração com colunas default 0/null: planos previamente de signup precisam teris_signupativado e os campos mapeados pelo administrador (passo runtime).
DEC-074 - 2026-06-30 - accepted
Novos Tipos de Campos, Limites via Campo "Opções" e Melhoria Progressiva no Módulo de Formulários (req-071 / BATCH-071). Decisões desta rodada:
-
Tipos
password/date/url/hiddenreaproveitam o blocotype-inputsem novos templates: oitem#typedo renderer já emite o tipo nativo para qualquer tipo que não sejaselect/textarea/radio/checkbox, então os 4 novos tipos caem automaticamente no<input type="...">existente. O construtor (forms.js) adiciona as 4 opções ao dropdown e expande a textarea de "Opções" (helpertypeUsesOptions) para também aceitarhidden/text/textarea/number/date, com placeholders dinâmicos por tipo (optionsPlaceholder). -
Reuso do campo "Opções" para limites/diretivas, sem novas colunas: linhas
min:X/max:Y/step:Z(parserforms_widget_parse_limitsno widget eformulario_parse_limitsna biblioteca) configuramminlength/maxlength(text/textarea),min/max/step(number) emin/max(date). Parahidden, o texto de "Opções" é o valor padrão constante.fields_schemapermanece o contrato único (sem migração). -
Injeção de atributos em PHP em vez de novos placeholders nos templates (divergência do intake §2.1): o req sugeria o placeholder
[[item#value]]e "adicionar atributos". Para robustez com formulários legados (cujo HTML salvo no banco não conteria placeholders novos), os atributos extras são injetados diretamente na tag renderizada (forms_widget_inject_tag_attrs/forms_widget_add_tag_class) e ovaluedo hidden viaforms_widget_inject_value_if_absent; o placeholder[[item#value]]continua sendo resolvido quando presente. Segue o precedente "seguir o código/robustez, não a prosa do intake". Decisão extra: camposhiddenrenderizam apenas a<input type="hidden">(descarta label/wrapper), evitando linha visual vazia com rótulo. -
Toggle de senha Vanilla + Melhoria Progressiva (Progressive Enhancement):
passwordé envolto em.forms-password-wrappercom botão.forms-password-toggle(íconeeye), com estilos inline para funcionar em Tailwind/Fomantic/CSS puro sem depender do framework. Noformulario.js, o date picker Fomantic (.forms-date-picker) e o dropdown Fomantic só são ativados se$.fn.calendar/$.fn.dropdownexistirem (caso contrário,<input type="date">/<select>nativos); o toggle de senha é listener delegado vanilla. As validações de limite são duplicadas no cliente (validateField) e no servidor (formulario_processador), sendo o backend a autoridade. -
Mensagens i18n no componente
form-ui, com fallback: novos prompts (client) e ajax-messages (server) adicionados em pt-br/en (prompt-min-length/-max-length/-min-value/-max-value/-min-date/-max-date/-urle os respectivosajax-message-*, incluindoajax-message-max-lengthque faltava eajax-message-min-lengthagora genérico com#min#). PHP e JS usam o valor do componente quando presente e caem em literais quando vazio (pré-deploy). O componente é populado no banco pela esteira (Update => Core), então as mensagens novas dependem do deploy (passo runtime); os fallbacks cobrem o intervalo. -
Cobertura de teste: novo
tests/Unit/PHP/FormsWidgetFieldTypesTest.php(9 testes, 32 assertions) blinda as funções puras do renderer (parse de limites, valor do hidden input-only, injeção de minlength/maxlength/min/max/step, classeforms-date-picker,type="url", wrapper/toggle de senha), sem banco. Versões cache-bust: móduloforms1.0.0→1.1.0 e bibliotecaformulario1.2.0→1.3.0.
DEC-075 - 2026-06-30 - accepted
Remoção do Campo e Metadado "hosting_plan" no Módulo de Planos de Assinatura (req-072 / BATCH-072). Decisões desta rodada:
-
Remoção da coluna por migração reversível: Criada a nova migração Phinx
20260710130000_remove_hosting_plan_from_subscriptions_plans.phppara remover a colunahosting_planda tabelasubscriptions_planscom segurança, incluindo o guardhasColumnnoup()e recriação nodown(). -
Saneamento do CRUD: O controlador
subscriptions-plans.phpe os 4 templates HTML de adicionar/editar planos (pt-br/en) foram limpos para remover a captura, validação, atribuição, placeholders e exibição visual do campo. -
Saneamento do Hooks de Hidratação: A função
subscriptions_hidratar_config_bdemsubscriptions.hooks.phpfoi atualizada para removerhosting_planda consulta do banco de dados na tabelasubscriptions_planse remover a injeção condicional da chave no array$planEntry.
DEC-076 - 2026-06-30 - accepted
Parametrização Dinâmica de E-mails e Integração de Estágios de Serviço em Planos (req-073 / BATCH-073). Decisões desta rodada:
-
Dinamização de E-mail de Proposta/Pedido: Em vez de carregar templates e assuntos de e-mail estáticos com base no formId (por exemplo,
form-pro-custom), a funçãosubscriptions_ajax_forms_process()no checkout lêmessage_componentesubjectdiretamente defields_schema->emailcom fallbacks. Os placeholders#name#,#code#,#formName#são substituídos dinamicamente no assunto configurado. -
Status Inicial do E-mail por Flag: Adicionada a flag
is_freeemsubscriptions_plans. O status do pedido exibido no e-mail de confirmação é dinâmico: seis_freefor verdadeiro, mostra "Ativo - Plano Gratuito" (verde); caso contrário, "Novo - Aguardando Pagamento" (azul). -
Autocomplete de Componentes de Mensagem no Forms: O campo
email_message_componentno construtor de formulários virou autocomplete AJAX buscando componentes na tabelacomponentes(via novo endpointbuscar-componentesemforms.php). O JS de autocomplete no admin foi integrado noforms.js, sincronizando as alterações com ofields_schema. -
Reorganização do CRUD e Carga Dinâmica de Estágio Inicial: O formulário de planos de assinatura foi segmentado nos templates HTML em blocos lógicos (
Geral,Integração,Serviço,Pagamentos / Gateways,Mapeamento de Cadastro). Oservice_stage_typee o estágio inicialinitial_stagesão integrados no banco de dados e interface. Ao selecionar o tipo de serviço no admin, o JS dispara uma chamada AJAX ao novo endpointbuscar-estagiosemsubscriptions-plans.phppara repopular dinamicamente as opções de estágio inicial com os estágios de serviço correspondentes da tabelasubscriptions_service_stages.
DEC-077 - 2026-06-30 - accepted
Dinamização Completa de Planos e Saneamento de Hardcodes no Módulo Subscriptions (req-074 / BATCH-074). Duas frentes ([REDACTED] + conn2flow):
-
Nome do plano via banco, sem dicionários ([REDACTED] §1): novo helper
subscriptions_obter_nome_plano($slug, $lang)emsubscriptions.hooks.phpconsultasubscriptions_plans.nameporid+language(status≠'D'), substituindo os 5 dicionários estáticos$paypalPlanNames/$planDisplayNames/$planNames(2 nos hooks, 3 no controlador). Divergência do intake: o fallback éucfirst(str_replace(['-','_'],' ',$slug))(humanizacloud-nano→Cloud Nano) em vez doucfirst($slug)literal — preserva o comportamento já existente em um dos sites. -
Filtro de submissões por planos ativos, sem prefixo chumbado ([REDACTED] §2): helpers
subscriptions_obter_form_ids_planos()(lista deform_iddistintos de planosstatus='A') esubscriptions_obter_form_ids_clausula($coluna='form_id')(geraform_id IN ('a','b',...)escapado, com fallbackLIKE 'form-pro-%'quando o banco ainda não tem planos — retrocompatibilidade). Aplicados nos 3 filtros de webhook/IPN (2 hooks + 1 controlador) que antes usavamform_id LIKE 'form-pro-%'. -
Checkout dinâmico e agnóstico ao plano ([REDACTED] §3 + §7):
subscriptions_checkout()resolve o plano direto emsubscriptions_planspor slug (subscriptions_checkout_carregar_plano), com dicionário mínimo de aliases legados (aceleracao→acceleration,mentoria→mentorship,personalizado→custom) antes de marcar inválido — removendo os mapas estáticos$plans/$planCelMape o loop de 6 blocos. O cabeçalho usa#plan_name#/#plan_description#do banco; o card de preço (#plan_price_card#, helpersubscriptions_checkout_price_card) é montado desubscriptions_plans_payments(nota da parcela principal, ou mensagem de gratuito/sob medida). Decisão-chave (orientação do usuário humano): em vez de dois conjuntos fixos de campos (signup/custom) propostos no intake, o formulário do plano é renderizado dinamicamente viaforms_render(['form_id'=>...]), que lê o campoforms.htmlda tabelaformsporform_ide injeta o controlador de submissão, o JS público e o CSS — preservando os campos específicos de cada plano e cobrindo qualquer plano futuro sem tocar no código. Osubscription-checkout.html(pt-br/en) caiu de ~990 para ~135 linhas, mantendo os stepsstep_form/step_payment/step_successevalid_plan/invalid_plan. O gate de criação de pagamento PayPal passou deactivePlanCel !== 'custom'paratotal_payments > 0. -
Landing e callback enxutos ([REDACTED] §4 + §5):
subscription.html(pt-br/en) reduzido (~690→~55 linhas) removendo o marketing herdado do antigopro-manager(Hero/Filosofia/VPS/“Por que pagar”/FAQ), preservando os marcadores que o PHP consome (custom_layout/plans_grid/plan_card+ placeholders).subscription-start.html(pt-br/en) teve os textos saneados, trocando jargão específico (“hospedagem”, “cloud hosting”, “seu projeto”, “our subscription platform”) por linguagem neutra de assinatura, sem alterar marcadores de status nem placeholders dinâmicos (#plan_display_name#/#plan_name#/#customer_name#). -
Proteção de campos
passwordem submissões (conn2flow §6): emgestor/bibliotecas/formulario.php, ao montar$fieldsValuespara gravarforms_submissions.fields_values, valores de campostype === 'password'são gravados como''(chave preservada por integridade estrutural). Extensão de defesa em profundidade: o mesmo campo é omitido (continue) no laço que monta o e-mail de notificação/confirmação, evitando vazar a credencial por e-mail. O valor em texto plano permanece em$_POSTdurante a requisição para o hook/controller de signup criar/vincular o usuário. Sem teste unitário dedicado (guard de 1 linha embutido noformulario_processador, acoplado a banco/POST/reCAPTCHA — segue o precedente de DEC-072/DEC-073 de slices de fluxo não isoláveis); validado porphp -l+ suítecomposer test76/76 sem regressão.
DEC-078 - 2026-07-09 - accepted
Dashboard Site Toolbar, Agendamento de Páginas e Extensões do Editor HTML (req-075 / BATCH-075). Lote consolidado de 6 metas, implementado em slices sequenciais sobre esqueletos scaffoldados pelo operador. Decisões desta rodada:
-
Botão "Acessar Site" (Meta 1): âncora
#menu-site-btn(íconeexternal alternate,href="@[[pagina#url-raiz]]@", tooltip i18n) no grupoui icon buttonsda.menu-controlsdo layout admin (pt-br/en). O operador recoloriu paraui green button. -
Dashboard Site Toolbar em iframe (Meta 2): rota
dashboard-site-toolbar/(layoutlayout-pagina-simples, Tailwind), controladordashboard_site_toolbar()/_menu(), e injeçãogestor_dashboard_toolbar()no fim do roteador de páginas degestor.php— gate único cacheadogestor_dashboard_toolbar_ativo()(editor logado, permissãoeditar/admin-paginas, página pública, não-iframe, não o próprio toolbar), reusado pela injeção E pelo wrapper de conteúdo. Iframe fixo 30px +dashboard.toolbar.jsincluído viagestor_pagina_javascript_incluir(tipo=toolbar, modulo_id=dashboard)(chamado ANTES do flush dejavascript-fim). UX (aval do Chefe): menu de módulos e barra de edição redimensionam o iframe viac2f-toolbar:resize(padrão do seletor de linguagens emglobal.js), com transparência; a barra de edição EMPURRA a página (offset dinâmico 30↔74px), o dropdown apenas sobrepõe. -
Redirect avançado inteligente (Meta 4):
admin-paginasepublisher-pageseditam a tabelapaginaskeyed porid; divergência do intake — o publicador precisa TAMBÉM dopublisher_id(distinto do id da página) para fixar o contexto:publisher-pages/editar/?id=<paginas.id>&publisher_id=<paginas.publisher_id>; senãoadmin-paginas/editar/?id=<paginas.id>. -
Edição visual in-place marker-based (Meta 3, aval do Chefe): o editor mostra a página RENDERIZADA (fiel), mas cada conteúdo vindo de
@[[var]]@/widget é envolvido numa caixa de destaque protegida (.c2f-dyn-box,data-c2f-marker= base64 do marcador). O usuário edita tudo menos as caixas; no salvar, a reconstrução é determinística (caixa→marcador, resto→HTML editado), sem diff difuso — preservando vars/widgets sem "queimar" o dinamismo. Endpoints AJAXdashboardsite-toolbar-render/site-toolbar-save; wrapper#c2f-page-content(mesmo gate); frontendcontenteditablein-place + reload. Não se edita o DOM renderizado diretamente (queimaria o dinamismo); não se usa o overlay de editor separado (revela controles abaixo da toolbar). -
Agendamento e datas retroativas (Meta 5): migração
20260712100000adicionadata_publicacao_inicio/_fim(datetime null) apaginas. Regra 404 no roteador (gestor.php): a query de página ativa (principal + fallback de idioma, não a de idiomas disponíveis) filtraNOW()dentro da janela — fora = 404; páginas de sistema (janela NULL) passam. Helpers globais de data (gestor_parse_datetime_br/gestor_datetime_para_input). CRUD deadmin-paginasepublisher-pages(adicionar/clonar/editar) grava janela +data_criacao/data_modificacaoretroativas; 12 forms ganham a seção "Agendamento e datas" (datetime-local). Limitações registradas (sem pré-preenchimento no editar; alteração só-de-agendamento pode não disparar save). -
Atalho de widget no editor HTML (Meta 6): botão
he-tb-widget-adminno#html-editor-floating-toolbar(html-editor.js), visível só com um.conn2flow-widget-wrapperselecionado, abrindo<raiz><data-widget-type>/editar/?id=<data-widget-slug>em nova aba. Cache-bustbiblioteca-html-editor1.3.27→1.3.28.
Validação: php -l (6 arquivos) OK, node --check (2 JS) OK, composer test 76/76 (287 assertions, 4 skipped) sem regressão. Runtime pendente com o operador (deploy Update => Core + migração + validação visual). BATCH-076 (req-076) é paralelo e fora deste escopo. Nenhum git commit/git push executado.
Adendo — Rodada de correções (2026-07-09, feedback do Chefe pós-teste) (Slice 5 no VALIDATION-CHECKLIST). Revisões sobre os pontos acima:
-
Ponto 4 (Meta 3) — editor visual REAL, não
contenteditable: ocontenteditablein-place foi substituído pela reutilização do editor visual admin. Ohtml-editor.jsaceitacontentRoot(defaultdocument.body, mantendo o clássico intacto), com auto-init guardado porwindow.__c2fHtmlEditorNoAutoInitewindow.HtmlEditorClassexposto. Odashboard.toolbar.jsinstancianew HtmlEditorClass({contentRoot:#c2f-page-content})— caixas tracejadas de seleção, floating toolbar, styler, DnD reais. Modal fallback vanilla (ensureFallbackModal) e imagepick porwindow.promptquando não há Fomantic/config. CodeMirror inicializado in-place no#element-code. -
Ponto 5 (Meta 5) — datas via
formato.php: os helpers globaisgestor_parse_datetime_br/gestor_datetime_para_inputforam removidos dogestor.phpe substituídos porformato_data_hora_br_para_datetime()(aceita BR e ISO —datetime-localmantido) eformato_data_hora_datetime_para_input()na bibliotecaformato.php. -
Novo — edição do LAYOUT (via
layout_id): além do conteúdo (paginas), o save edita o body-inner do registrolayoutsdo banco (dashboard_site_toolbar_salvar_layout), com o#c2f-page-contentrecolocado como@[[pagina#corpo]]@(safeguard exige o marcador) e vars de atributo mantidas literais ($preservar_atributos=true). Só o body-inner (head não). -
Novo — histórico/backup + restauração: o save grava
interface_backup_campo_incluir+interface_historico_incluirno módulo-dono; o editbar ganhou dropdown de restauração (#c2f-backups-btn→site-toolbar-backups/site-toolbar-backup-get→openBackupPanel). -
Novo —
user_modified=1: o save marcauser_modified=1(+versao+1) empaginase emlayouts(quando o layout muda no mesmo salvamento). -
Novo — controles no editbar + painel de widgets:
screenPagina(desktop/tablet/mobile), undo/redo e add-btn com painel de inserção de elementos e widgets (endpointssite-toolbar-widget-types/site-toolbar-widgets-listreusandohtml_editor_ajax_widget_types/_widgets_list).
Validação do adendo: node --check dashboard.toolbar.js OK; php -l (dashboard.php, gestor.php, formato.php) OK; composer test 76/76 (287 assertions, 4 skipped) sem regressão. Cache-bust biblioteca-html-editor até 1.3.30. Nenhum git commit/git push executado.
DEC-079 - 2026-07-10 - accepted
Desacoplamento de Scripts do Iframe e Mapeamento Inteligente de Variáveis no Live Editor (req-077 / BATCH-077). Correções pós-teste visual do BATCH-075. Decisões desta rodada:
-
Script da toolbar como arquivo estático (não inline): a lógica JS embutida no
<script>do templatedashboard-site-toolbar.html(pt-br/en) foi extraída paragestor/modulos/dashboard/dashboard.iframe-toolbar.jse passou a ser injetada porgestor_pagina_javascript_incluir(Array('tipo'=>'iframe-toolbar','modulo_id'=>'dashboard'))emdashboard_site_toolbar(). O separador módulo→tipo do incluidor é o ponto (dashboard+iframe-toolbar→ URLdashboard/iframe-toolbar.js), e o hífen dentro do tipo (iframe-toolbar) casa a regex/^[A-Za-z0-9-]+$/do roteadorarquivo-estatico.php, que mapeia ao físicodashboard.iframe-toolbar.js. Um único arquivo serve pt-br e en (o JS não tem strings i18n — os rótulos ficam no HTML). O placeholder órfão vaziodashboard.iframe.toolbar.js(0 bytes, sem referências) foi removido. -
Preservação do DOM VIVO em vez de swap destrutivo de
innerHTML(mudança-chave): o motor do BATCH-075 substituíaroot.innerHTMLpelo HTML re-renderizado com caixas, o que quebrava scripts ativos, CSS e estado JS do site. O motor novo não toca o DOM vivo: guarda o HTML ORIGINAL (cru, com marcadores intactos) num container oculto#paginaHTMLAntesEdicaoe anota a árvore viva comparando-a com a original (mapTree, varredura paralela alinhada por assinatura estrutural). O backenddashboard_ajax_site_toolbar_renderpassou a devolvercontent_raw(=paginas.html) elayout_raw(=body-inner do layout com o slot@[[pagina#corpo]]@preenchido por#c2f-raw-content, espelhando a estrutura viva#c2f-layout-root⊃#c2f-page-content); os campos legados com caixas (html/layout_html) foram mantidos para retrocompat (painel de backups). -
Mapeamento por tipo de ocorrência da variável:
-
Atributo (caso de aceite:
@[[pagina#url-raiz]]@emsrc/href): a tag viva é marcada comdata-c2f-variable="ID_VAR_N"e o mapavarMap[ID]={param, variable(template cru do atributo), valor(resolvido)}guarda a correspondência. O valor resolvido continua visível no editor (visual correto); no save, o atributo volta à notação@[[…]]@só quando o valor não foi alterado pelo usuário (senão a edição prevalece). Isso resolve o dilema do motor antigo, que no modo conteúdo queimava as variáveis de atributo no banco. -
Texto:
annotateTextVarsderiva os valores por regex do template cru e envolve cada variável numspan.c2f-var-box(contenteditable=false) com o marcador em base64 (data-c2f-marker). -
Widget: como o fluxo vivo (
gestor_pagina_widgets) substitui o bloco<!-- widgets#… -->pelo HTML renderizado sem deixar âncora,mapTreelocaliza a expansão viva por âncora estrutural (a 1ª assinatura do cru após o bloco de widget) e a envolve numdiv.c2f-widget-boxatômico, guardando o marcador cru completo (open+mockup+close). Best-effort: em páginas muito complexas com múltiplos widgets/expansões o alinhamento pode ser parcial; degrada sem corromper (nós não mapeados seguem editáveis).
-
-
Save separa
paginasdelayouts(orientação do Engenheiro Chefe): o body vivo mistura HTML do registrolayouts(body-inner) e do registropaginas(conteúdo).saveEdit/reconstructOriginalextraem#c2f-page-content→ grava empaginas; o restante do#c2f-layout-root, com o#c2f-page-contenttrocado pelo marcador@[[pagina#corpo]]@, → grava emlayouts(mantido o mecanismo de save duplo do BATCH-075/adendo P4, agora sobre o DOM vivo anotado).
Validação: node --check (2 JS) OK, php -l (dashboard.php) OK, <div> 5/5 nos 2 HTMLs. Teste de lógica do motor com happy-dom carregando o dashboard.toolbar.js real e exercitando edit-start/edit-save via message-bus: 16/16 (atributos do aceite, variável de texto e widget mapeados/reconstruídos, DOM vivo preservado). Runtime (deploy Update => Core + edição visual real) pendente com o operador. Nenhum git commit/git push executado.
DEC-080 - 2026-07-10 - accepted
Correções no Live Editor: Trava de Widgets, Submenu do Painel "+" e Isolamento de Estilos (req-078 / BATCH-078). Rodada de correções visuais/interação pós-homologação do BATCH-075/BATCH-077, todas pontuais no motor compartilhado html-editor.js (o Live Editor reutiliza o editor visual admin escopado a contentRoot, rodando na própria página hospedeira). Decisões desta rodada:
-
Widgets/variáveis do live editor são blocos atômicos protegidos (correção 1): no live editor os widgets renderizados são envolvidos por
.c2f-widget-box/.c2f-dyn-box(mapeamento do BATCH-077), e não por.conn2flow-widget-wrapper— por isso a proteção atômica do editor clássico não os alcançava e o conteúdo interno seguia editável/selecionável.resolveEditablepassa a reconhecer.c2f-dyn-box(element.closest('.c2f-dyn-box')→ retorna a caixa), dando paridade com o wrapper clássico; o CSS injetado (injectStyles) adiciona.c2f-dyn-box{user-select:none}e.c2f-dyn-box *{pointer-events:none!important;user-select:none!important}, deixando o conteúdo interno (texto/imagem/links do widget) inerte a clique/seleção. O usuário só interage com a caixa externa. Seguro para o admin (as classesc2f-*não existem lá). -
Painéis do live editor entram em
isEditorOwned(correção 2 — causa-raiz):#c2f-add-panelnão estava listado emisEditorOwned, então o listener de clique em capture dohtml-editor.js(que usaelementsFromPoint) atravessava o painel, encontrava o conteúdo por trás, selecionava-o e chamavastopPropagation()— matando otoggleWidgetGroupdo painel e produzindo o sintoma "a categoria fecha o painel / age como inserção".isEditorOwnedpassa a reconhecer#c2f-add-panele#c2f-backup-panel(viaclosest), dando early-return no clique/hover; o clique propaga normal para o handler do painel (que já fazia o toggle correto nodashboard.toolbar.js). Nenhuma mudança na lógica do painel foi necessária. -
Blindagem de CSS dos controles injetados na página hospedeira (correção 3): bloco de hardening em
injectStylesforçando com!importantcolor/background/-webkit-text-fill-color/font/caret-colorem#html-editor-floating-toolbar .he-tb-btn(+ ícones<i>e ohe-tb-deselectrosa preservado),#html-editor-tailwind-styler(+ seusinput) e#html-editor-modal textarea,input[type="text"],label. Isso corrige ícones apagados no floating toolbar,textarea/inputilegíveis (texto cinza herdado do site) no modal e o styler desconfigurado. Nota técnica:color:...!importantno container do styler não sobrescreve os filhos com cor própria (tags roxas, títulos cinza) porque a herança CSS não propaga!important— apenas os elementos sem cor própria são blindados; o visual intencional é preservado. -
Cache-bust duplo: o live carrega
interface/html-editor.js?v=c2f1→?v=c2f2(query-string hardcoded nodashboard.toolbar.js); o admin bumpabiblioteca-html-editor1.3.30→1.3.31(html-editor.php). Como o motor é compartilhado, os dois contextos precisam recarregar.
Validação: node --check (html-editor.js, dashboard.toolbar.js) OK; php -l (html-editor.php, dashboard.php) OK; composer test 76/76 sem regressão.
-
Adendo — Rodada 2 (R2) — Refinamentos Visuais e Funcionais do Live Editor (2026-07-10):
-
Widgets Sem Wrapper Div (R2-P4): A introdução de uma tag
<div>de wrapper (.c2f-widget-box) no BATCH-077 quebrava seletores CSS estritos do site hospedeiro (como.parent > .child). O novo modelo de mapeamento marca diretamente os elementos-raiz do render do widget com os atributosdata-c2f-widget-id(grupo),data-c2f-widget-root="1"(primeiro elemento), edata-c2f-marker(marcador cru). -
Reconstrução Sem Wrapper:
reconstructOriginalagrupa elementos pordata-c2f-widget-id, substitui o primeiro elemento com o marcador cru original e exclui os elementos irmãos pertencentes ao mesmo widget. Isso permite o salvamento e restauração determinística sem introduzir nenhuma tag de wrapper no HTML final. -
Proteção Interna de Widgets (R2-P1): No
resolveEditable, reconhecer elementos com[data-c2f-widget-id]como blocos atômicos protegidos. O CSS injetado desabilita seleção e eventos em elementos filhos ([data-c2f-widget-id] * { pointer-events: none!important; user-select: none!important; }). -
Aparência Tracejada e Label (R2-P1): Exibição visual do widget via
outline: 1px dashed #fbbf24!importante um label absoluto no pseudo-elemento::before(exibindo o nome e slug do widget) ancorado no elemento-raiz (position: relative). -
Atalho de Widget Unificado (R2-P2): O botão
#he-tb-widget-adminna toolbar flutuante verifica se o elemento possuidata-widget-type, compatibilizando o atalho para wrappers clássicos e os novos elementos-raiz do live editor. -
Ícones SVG Autônomos (R2-P3): As tags
<i class="... icon">(Fomantic UI) da toolbar flutuante e do styler foram substituídas por SVGs inline comstroke="currentColor". Isso garante o funcionamento e exibição correta dos ícones em páginas públicas que não carregam fontes ou folhas de estilo da Fomantic UI. -
Cache-Bust: Atualizada a query-string para
?v=c2f3no carregador dehtml-editor.jse a versão do componentebiblioteca-html-editorpara1.4.0emhtml-editor.php.
-
Validação da Rodada 2:
-
Executado
node _smoke_batch078_r2.mjsno ambiente de testeshappy-domvalidando a round-trip completa (marcação de elementos sem wrapper, preservação de variáveis de atributo no DOM e reconstrução determinística) com 18/18 checks aprovados. -
node --check(JS) ephp -l(PHP) OK. -
composer test76/76 aprovados sem regressões.