Skip to content

ADR-010 — Atomic Rendering & Anti-Flash

CampoValor
Status✅ Aceito
Data2026-04-30
AutoresEquipe OLP
Implementaçãosrc/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:

  1. Re-assinar JWT (Edge Function select-role retorna novo cookie)
  2. Limpar cache de servidor da escola anterior (ADR-007)
  3. Re-renderizar sidebar com novas permissões
  4. 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_activity no front
  • Bootstrap: se olp_last_activity existe 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:

tsx
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:

tsx
const trocou = anterior && (anterior.escolaId !== nova.escolaId
                         || anterior.role     !== nova.role);
if (trocou) queryClient.removeQueries();   // limpa cache órfão

Resolve 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_activity não é prova de auth — apenas heurística de UX (auth real continua sendo cookie)

Trade-offs

EixoCustoBenefício
Endpoint pesado/me consolida 4 queries1 round-trip no bootstrap
DisciplinaDevs precisam saber "não reload"UX coesa
Cache mgmtremoveQueries em pontos certosSem vazamento cross-school

Conformidade

Como verificar:

bash
rg "window\.location\.reload" src --type ts --type tsx     # deve ser 0
rg "document\.cookie\.includes\('olp_auth" src --type ts   # deve ser 0

Sinais de violação:

  • location.reload() em qualquer fluxo de sessão
  • Tela com >1 skeleton sequencial no carregamento inicial
  • Hook escolar sem escola_id na 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.md
  • src/contexts/auth-context.tsx
  • supabase/functions/me/index.ts
  • mem://architecture/atomic-state-and-hydration-standard
  • mem://architecture/me-endpoint-orchestrator-standard
  • mem://architecture/performance/anti-flash-rendering-standard
  • mem://architecture/performance/lazy-mount-once-tabs-strategy
  • ADR-005 — porque não dá pra ler cookie
  • ADR-007removeQueries na troca