0015 — O kit de UI não tem slot de head¶
Status: Aprovado · Responsável: Gustavo Madruga · Atualizado em: 2026-07-16 · Decidido em: 2026-07-16
Contexto¶
A 0.21.0 deu ao layout.html um irmão do fragment head: o headComExtras(title, extras),
para a tela que precisa de markup próprio dentro do <head> — tipicamente o
<noscript><meta http-equiv="refresh"> de fallback sem JS, que fora do <head> é inválido.
O irmão nasceu com nome distinto de propósito (o Thymeleaf não faz overload por aridade —
lição da 0.19.0), e o changelog afirmou: "as telas que já chamam head('Título') seguem
intactas".
A afirmação era falsa. O headComExtras era um segundo elemento <head> no mesmo
documento, e um documento HTML tem um só: o parser funde o segundo no primeiro, e o
head('Título') passou a arrastar o ${extras} do irmão. Toda view do kit quebrou com
Error resolving fragment: "${extras}" (template: "layout" - line 53) — inclusive as que
não sabiam que o slot existia.
Números que decidiram o desfecho: 0 views nos apps usavam o slot, 126 chamavam
head(). Uma feature sem nenhum uso derrubou o kit inteiro. O caso que a motivou existe
(bi-transporte-xls tem o <noscript><meta refresh> numa tela de admin), mas aquele app
tem layout próprio e ainda não adotou o kit — a demanda era hipotética.
Reproduzido com Thymeleaf 3.1.5 antes de decidir, e três hipóteses caíram:
- não é colisão de nome (a regra A do
checa-viewsjá cobria isso): renomear o irmão mantendo o<head>continua quebrado; - não é "head dentro de body": dois
<head>irmãos, ambos fora do<body>, quebram igual. Basta serem dois; - a cura óbvia é uma armadilha: trocar o
<head>do irmão por um<div>deixa os dois fragments verdes e emite<div>onde devia ir<head>— parse limpo, HTML inválido.
Duas curas funcionam de verdade (verificadas verdes): dois arquivos com um <head> cada, e
a variante que ainda extrai os metas para um fragment comum, para não ter dois donos do
mesmo markup (§1.9).
Decisão¶
O kit tem um head(title) e ponto — sem variante com slot. As duas curas que funcionam
foram rejeitadas: ambas pagam complexidade permanente (arquivo extra, três fragments de
head) por uma feature com zero uso. YAGNI. Isto reverte a decisão de desenho da 0.21.0.
Tela que precise de markup próprio no <head> escreve o <head> à mão nela e traz o
caso ao central. O slot volta a ser desenhado quando existir tela real precisando dele —
com o caso na mão, não por antecipação.
Regra G no checa-views.py: <html>/<head>/<body> declarado duas vezes no mesmo
arquivo reprova. Vale mesmo com nomes distintos e mesmo fora do <body> — o que quebra é o
parser fundir elemento. Só declaração real conta: o cabeçalho do kit documenta o uso com
<head th:replace=...> dentro de um comentário, e os três apps Micronaut reais têm isso
— por isso a regra lê o parser, nunca regex.
O central não renderiza o kit. O gate honesto para "o kit renderiza" é JVM, e o central é Python/Node; montar um projeto Java aqui foi avaliado e recusado. Consequência aceita e declarada: defeito de render no kit segue chegando ao app — quem o pega é o CI do primeiro app que adotar o kit e tiver teste de render. O que o central garante é o shape, e que as duas classes já vividas não voltam.
Consequências¶
- O
checa-viewsreprova olayout.htmlreal da 0.21.0 na linha do segundo<head>, e passa nos 46 views reais dos três apps Micronaut. - A fixture
LAYOUT_OKdotesta-checa-viewsera o layout quebrado da 0.21.0 e se dizia conformante: o teste do checker atestava contra um defeito. Corrigida — é a "fixture inventada" da §6 dentro de casa, e o motivo de a suíte não ter pego nada. - Ninguém precisa migrar: nenhum app usava o slot. Quem copiou o kit 0.21.0 ou 0.22.x
re-deriva com
/xadm-docse volta a renderizar. - A classe "markup que parseia e mesmo assim está errado" (o
<div>) continua sem gate no central. Está escrita na receita e na docstring do check, para ninguém confiar no verde além do que ele afirma.