--- id: KB-PLUGIN-015 title: querySelector('.row') colide com elementos de outros plugins no Helpdesk portal domain: plugin-dev tags: - glpi - glpi11 - helpdesk - tilesections - dom - selectors - bootstrap - cross-plugin-interference status: active severity: high created_at: 2026-05-10 updated_at: 2026-05-10 applies_to: - GLPI 11.0.x --- # `querySelector('.row')` colide com outros plugins no Helpdesk portal ## Sintoma Plugin funciona perfeitamente no ambiente de desenvolvimento mas falha **silenciosamente** em produção — não há erro no console, não há AJAX falhando, mas o DOM não é manipulado. A causa é difícil de descobrir porque o servidor retorna os dados corretos e o JS chega a rodar. No caso real: `tilesections/public/js/helpdesk_home.js` em produção não reorganizava os tiles em categorias. Em dev funcionava. A diferença era a presença de **outro plugin** (`news_alert`) em produção que injetava um elemento com classe `row` antes dos tiles. ## Causa A `container-xl` do portal do Helpdesk pode conter múltiplos elementos com class `row`: ```html
...
``` Código defeituoso: ```js const container = tilesSection.querySelector('.container-xl'); const tilesRow = container.querySelector('.row'); // ❌ pega o do news_alert ``` `querySelector` faz busca em profundidade e retorna o **primeiro** match na árvore — incluindo elementos aninhados. O `plugin_news_alert-container` está dentro da `` que vem **antes** da row dos tiles, então é encontrado primeiro. Depois disso, `tilesRow.querySelectorAll(':scope > div')` retorna zero (o container do news plugin está vazio), e a função desiste sem fazer nada. ## Padrão correto Usar `:scope > .row` para garantir que apenas **filhos diretos** sejam considerados: ```js const tilesRow = container.querySelector(':scope > .row'); // ✅ só filhos diretos ``` `:scope` referencia o elemento de partida da query (no caso, `container`). `:scope > .row` significa "row que é filha direta de container". ## Lições - **Nunca confie que sua produção tem a mesma estrutura DOM do dev**. Plugins de terceiros (news, dashboards, customizações antigas) injetam elementos em pontos imprevisíveis. - Para qualquer seletor que dependa de hierarquia, **prefira `:scope >` ou caminhos específicos** ao invés de buscas globais com classes Bootstrap genéricas como `.row`, `.card`, `.col-*`. - Quando funcionar local e não em produção, **não assuma diferenças de container/Docker**. Inspecione o DOM real do cliente. O usuário pode rodar no console: ```js document.querySelector('.tiles-banner .container-xl').innerHTML.substring(0, 600) ``` e mandar o resultado. ## Hierarquia de especificidade recomendada Para seletores de containers em plugins GLPI, em ordem de robustez: 1. **`[data-glpi-*]` atributos** — quando GLPI fornece (ex: `[data-glpi-helpdesk-config-tiles]`). Mais estável. 2. **`:scope > .classe`** — quando só filhos diretos importam. 3. **Caminho específico** — ex: `.tiles-banner > .container-xl > .row`. 4. **`.row` solto** — **evitar**. Praticamente garante colisão com outros plugins. ## Onde isso pegou na vida real `docker/glpi/plugins/tilesections/public/js/helpdesk_home.js::reorganizeTiles()` — corrigido em `tilesections v1.1.2`. O bug não era reproduzível em dev porque o ambiente Docker não tinha o plugin `news_alert` instalado.