ADR-010 — Atomic Rendering & Anti-Flash
| Campo | Valor |
|---|---|
| Status | ✅ Aceito |
| Data | 2026-04-30 |
| Autores | Equipe OLP |
| Implementação | src/contexts/auth-context.tsx, src/hooks/useMyMenu.ts, src/hooks/useMyPermissions.ts, me/index.ts (Edge Function), docs/development/ATOMIC_RENDERING.md |
Contexto
A plataforma tem multi-papel (um usuário pode ser admin de uma escola e diretor de outra) e multi-escola. Trocar de papel/escola precisa:
- Re-assinar JWT (Edge Function
select-roleretorna novo cookie) - Limpar cache de servidor da escola anterior (ADR-007)
- Re-renderizar sidebar com novas permissões
- Mostrar dados da nova escola
Soluções ingênuas causam flash:
window.location.reload()→ tela branca de 800ms a 2s- Atualização parcial → mostra sidebar nova com dados antigos
- "Skeletons everywhere" → tela cintila múltiplas vezes
Adicionalmente, no bootstrap (carregamento inicial), o frontend não pode ler o cookie HttpOnly (ADR-005) — precisa de outro sinal para evitar piscar tela de login antes do /me retornar.
Decisão
1. Proibido window.location.reload() para sincronizar sessão
Toda mudança de sessão (login, logout, troca de papel) usa startTransition() + applySnapshot(). Memória atomic-state-and-hydration-standard.
2. Sinal de bootstrap = localStorage.olp_last_activity
Frontend não pode ler olp_auth (HttpOnly). Em vez disso:
- Toda Edge Function autenticada bem-sucedida atualiza
olp_last_activityno front - Bootstrap: se
olp_last_activityexiste e é recente (< 8h) → renderiza skeleton autenticado e dispara/me - Se ausente ou expirado → renderiza tela de login
Isso evita o flash "login → app → login" durante o /me.
3. Endpoint /me é orquestrador único
/me retorna em uma única chamada:
- Usuário autenticado
- Papel principal e papéis secundários
- Escola ativa
- Menu/sidebar resolvido (com permissões aplicadas)
- Feature flags do contexto
Frontend hidrata tudo em applySnapshot envolto por startTransition. Memória me-endpoint-orchestrator-standard, unified-sidebar-and-menu-resolution.
4. Anti-flash em queries
Padrão obrigatório:
if (isLoading && !data) return <Skeleton />;
return <Content data={data} />;Memória anti-flash-rendering-standard. Quando data existe (stale), renderizar imediatamente em vez de skeleton — permite stale-while-revalidate sem cintilar.
5. Safety net em troca de escola
AuthProvider.applySnapshot compara snapshot anterior com novo:
const trocou = anterior && (anterior.escolaId !== nova.escolaId
|| anterior.role !== nova.role);
if (trocou) queryClient.removeQueries(); // limpa cache órfãoResolve incidente cross-school documentado em query-key-context-isolation.
6. Lazy mount once para tabs pesadas
Tabs com tabelas grandes/charts são montadas uma vez e ocultadas via CSS (hidden class) em vez de desmontadas. Memória performance/lazy-mount-once-tabs-strategy.
Alternativas consideradas
A. window.location.reload() em troca de papel
Pros: trivial. Cons: 1–2s de tela branca; perde scroll position; perde estado de modais; cara em métricas Web Vitals. Veredicto: explicitamente proibido.
B. Service Worker para gerir sessão
Pros: cache offline. Cons: complexidade desproporcional; problemas conhecidos com cookies cross-domain. Veredicto: rejeitado por ora.
C. Múltiplas chamadas paralelas no bootstrap (/me, /permissions, /feature-flags)
Pros: separação de responsabilidades. Cons: 3 spinners diferentes em momentos diferentes = cintilação. Veredicto: rejeitado em favor do orquestrador /me.
Consequências
Positivas
- Troca de papel em <300ms, sem reload
- Cross-school safe (incidente fechado)
- Bootstrap sem flash login → app
- LCP (Largest Contentful Paint) significativamente melhor
Negativas
/meé um ponto único — se ficar lento, todo bootstrap fica- Mais regras para devs novos absorverem (ATOMIC_RENDERING.md cobre)
localStorage.olp_last_activitynão é prova de auth — apenas heurística de UX (auth real continua sendo cookie)
Trade-offs
| Eixo | Custo | Benefício |
|---|---|---|
| Endpoint pesado | /me consolida 4 queries | 1 round-trip no bootstrap |
| Disciplina | Devs precisam saber "não reload" | UX coesa |
| Cache mgmt | removeQueries em pontos certos | Sem vazamento cross-school |
Conformidade
Como verificar:
rg "window\.location\.reload" src --type ts --type tsx # deve ser 0
rg "document\.cookie\.includes\('olp_auth" src --type ts # deve ser 0Sinais de violação:
location.reload()em qualquer fluxo de sessão- Tela com >1 skeleton sequencial no carregamento inicial
- Hook escolar sem
escola_idna queryKey
Débito técnico conhecido
Varredura em 2026-04-30: limpa. location.reload aparece zero vezes em código de aplicação.
Referências
docs/development/ATOMIC_RENDERING.mdsrc/contexts/auth-context.tsxsupabase/functions/me/index.tsmem://architecture/atomic-state-and-hydration-standardmem://architecture/me-endpoint-orchestrator-standardmem://architecture/performance/anti-flash-rendering-standardmem://architecture/performance/lazy-mount-once-tabs-strategy- ADR-005 — porque não dá pra ler cookie
- ADR-007 —
removeQueriesna troca