Sidebar Pending Badges¶
DEV-1101 · Feature em implementação no
cadencia-apppara destacar pendências operacionais direto na navegação principal.
Por que foi construído assim¶
Antes desta feature, o usuário precisava abrir Lara, Cadências, Oportunidades, Nutrição e Tráfego para descobrir se existia algo pendente. Isso escondia trabalho operacional e atrasava resposta em pontos sensíveis do funil.
A solução concentra a leitura em um endpoint server-side por tenant e entrega um contrato pequeno para a UI renderizar badges. Assim, o menu vira um painel de atenção operacional sem expor service role, tokens ou queries sensíveis no client.
Stack¶
| Camada | Tecnologia |
|---|---|
| Linguagem | TypeScript |
| Framework | Next.js App Router |
| Banco | Supabase PostgreSQL |
| UI | React client components no layout/sidebar |
| Integrações | Lara, Cadências, Growth/Oportunidades, Nutrição/Resend, Tráfego/Meta |
| Onde roda | Cadencia App em Vercel |
Como funciona¶
flowchart LR
A[Usuário abre Cadencia App] --> B[AppLayoutWrapper]
B --> C[GET /api/app/pending-counts]
C --> D[resolveTenant]
D --> E[collectPendingCounts]
E --> F[Lara]
E --> G[Cadências]
E --> H[Oportunidades]
E --> I[Nutrição]
E --> J[Tráfego]
F --> K[counts + errors]
G --> K
H --> K
I --> K
J --> K
K --> L[Sidebar/TabBar]
L --> M[Badges apenas quando count > 0] O layout client-side chama uma API privada com cache: no-store. A rota autentica o usuário, resolve o tenant efetivo com suporte a impersonation de super_admin e chama um agregador server-side.
O agregador consulta as fontes em paralelo e normaliza tudo para { count, title }. Falhas por fonte não derrubam o menu inteiro: a fonte entra em errors, gera console.warn e retorna count 0 para aquele item.
Componentes¶
| Arquivo | Papel |
|---|---|
src/lib/pending-counts.ts | Contrato compartilhado de chaves, labels e helpers de count. |
src/lib/pending-counts-server.ts | Agregador server-side multi-fonte com timeout/fallback por fonte. |
src/app/api/app/pending-counts/route.ts | Endpoint autenticado que resolve tenant e retorna o payload. |
src/app/(app)/AppLayoutWrapper.tsx | Client wrapper que busca contadores e propaga para o layout. |
src/components/layout/Sidebar/Sidebar.tsx | Renderiza badges nos itens mapeados da navegação. |
Decisões técnicas¶
- Endpoint único server-side — mantém service role e integrações fora do bundle client.
- Contrato compartilhado pequeno — evita o layout conhecer detalhes de cada módulo operacional.
- Timeout por fonte — uma integração lenta não pode travar a navegação inteira.
errorsseparado decounts— diferencia “sem pendência” de “não consegui consultar”.- Tenant efetivo via
resolveTenant— respeita impersonation e evita contagem no tenant errado.
Gotchas & armadilhas¶
- Não importar
pending-counts-server.tsem componente client. Esse arquivo pertence ao servidor. - Não esconder falha como zero silencioso. O fallback para 0 precisa preencher
errors. - Não contar registros pausados/inativos como vencidos. Badge é sinal de ação operacional real.
- Não cachear globalmente. O payload é privado por usuário/tenant.
- Não bloquear o layout por integração externa. Fonte lenta deve estourar timeout e degradar localmente.
Como operar¶
# testar endpoint em dev depois de logado
curl -i http://localhost:3000/api/app/pending-counts
# gates esperados no cadencia-app
npm run lint
npm run test -- --runInBand
npm run build
FAQ¶
Por que não buscar direto no Sidebar?
Porque o Sidebar é client component. As consultas precisam de service role e integrações server-side.
Por que o endpoint retorna errors?
Porque count 0 pode significar ausência real de pendência ou falha de fonte. errors preserva a diferença para logs e UI futura.
Quais itens recebem badge?
Lara, Cadências, Oportunidades, Nutrição e Tráfego, conforme o mapa PENDING_COUNT_BY_NAV_KEY.
Referências¶
- Linear: DEV-1101
- Repo source:
cadencia-app/docs/features/sidebar-pending-badges.md - Branch app:
feature/dev-1101-sidebar-pending-badges-20260822