plans(C119,B198): status Lembrança em lideranças e Sollinha fecha o drawer ao navegar em mobile

This commit is contained in:
Francisco Solla 2026-08-11 10:44:53 -03:00
parent 62335084a1
commit d23433a39a
2 changed files with 161 additions and 0 deletions

View file

@ -0,0 +1,83 @@
# Status de apoio "Lembrança" em lideranças
Status: registrado
Atualizado em: 2026-08-11
Issue: #661
Priority: P1
Model: composer-2.5
Impeccable: B — encaixe no seletor/badge de status existentes
Canvas UI: /home/fsolla/.cursor/projects/home-fsolla-cursor-worktrees-teqo-plans-plan-issue-36/canvases/plan-c119-ui-draft.canvas.tsx
Appetite: ~1 dia eng; um estado novo nos lugares onde status já existe
Responsável: —
## Intenção
No campo existe um perfil de liderança que não cabe em nenhum dos quatro status atuais: ela **não se compromete** com a campanha — já está comprometida com outra campanha ou tem outro impedimento — mas é **favorável** e vai trazer alguns votos por respeito/admiração ao mandato.
Hoje o assessor que encontra esse caso só tem duas saídas ruins: marcar "Em disputa" (status pendente, que promete trabalho de abordagem que não existe) ou "Negativo" (que apaga a informação mais valiosa — ela é favorável e conta no planejamento). O status "Lembrança" é o registro de que a conversa terminou bem: não vamos conquistar o compromisso, mas podemos contar com os votos dela.
## Persona e fluxo
- **Persona / contexto:** assessor ou coordenador no campo (celular ou desktop), classificando lideranças após visitas e conversas.
- **Job principal:** registrar em um clique que a liderança não se compromete, mas é favorável — para a equipe parar de gastar esforço de abordagem e ainda contabilizá-la no planejamento.
- **Fluxo desejado:** abre a lista de lideranças → toca no badge de status da célula → escolhe "Lembrança" no popover → o badge muda e o valor salva sozinho (o auto-save da célula já existe) → a liderança sai dos "pendentes de abordagem" e segue visível como favorável nos filtros e nas leituras do Sollinha.
- **Anti-goals de produto:** não é um "negativo leve" — a liderança é favorável e os votos dela importam; não vira um estado que o assessor marque por dó. Não cria segundo eixo de classificação.
### Esboço de fluxo (B)
```text
[lista de lideranças — célula "Status"] → [toque no badge → popover com 5 opções]
→ [escolhe "Lembrança"] → [badge muda + salva sozinho]
→ [sai dos pendentes de abordagem] → [segue nos filtros/leituras como favorável]
```
## Objetivo e aceite
- O assessor consegue marcar "Lembrança" em **todas** as superfícies onde os quatro status atuais aparecem (célula da lista, ficha, wizard, admin, filtro da lista de pessoas).
- Liderança "Lembrança" **não** aparece nos pendentes de abordagem — nem na ferramenta do Sollinha ("quais lideranças precisam ser abordadas") nem no critério de pendência usado por ela. (Guardrail: mesmo padrão de exclusão do "Negativo".)
- O badge "Lembrança" é visualmente distinto dos quatro atuais.
- O filtro por status (lista de lideranças e lista de pessoas) inclui o novo valor.
- O Sollinha reconhece e consegue responder sobre o status (rótulo na leitura de lideranças; pendências declaram o critério sem o novo estado).
- Nenhuma leitura existente quebra: agregações, filtros e tools continuam cobrindo o novo valor sem comportamento acidental (ex.: vazar para pendências).
## Dados (intenção)
- **Vou apresentar dados?** Não — o item adiciona um valor de classificação que já flui pelas listas existentes (filtro/facets). Nenhuma superfície de dado nova neste item.
- **Decisões desbloqueadas:** assessor/coordenador decide, por liderança, que ela é "favorável sem compromisso" em vez de pendente ou negativa. Essa classificação alimenta leituras futuras (planejamento, pendências) — sem métrica nova aqui.
## Direção no codebase (hipótese)
- **Áreas prováveis:** `src/collections/Leadership.ts` (campo `supportStatus`, `type: 'select'`, enum no Postgres — precisa de migration); `src/lib/schemas/leadership.ts` (enum zod); `src/utilities/leadership/leadershipLabels.ts` (rótulos); `src/components/campaign/leadership/SupportStatusBadge.tsx` e `LeadershipListSupportStatusControl.tsx` (lista); formulários `LeadershipForm`/`LeadershipInternalForm`/`WizardLeadershipForm`; filtro de status em `src/utilities/people/peopleData.ts` (lista de pessoas); tools do Sollinha `src/utilities/ai/tools/getLeaderships.ts` (rótulos) e `getPendingLeaderships.ts` (critério de pendência — o novo valor não pode entrar no pré-filtro), além do texto do critério no `systemPrompt.ts`.
- **Precedente a olhar:** planos `simplificar-modelo-lideranca.md` e `autosave-status-lista-liderancas.md`; critério de pendência documentado em `sollinha-liderancas-pendentes-abordagem.md` (o critério "negativo nunca entra" é o guardrail a espelhar para "lembrança nunca entra").
- **Risco de acoplamento:** o status é um enum no banco (migration de `ALTER TYPE`), consumido por lista, formulários, pessoas, admin e tools do Sollinha — qualquer superfície que itere os valores por engano precisa ser encontrada. Liderança é campo staff-only; leader lockdown não é afetado.
## Dependências
- Nenhuma dura. Suave: concluir após o C116–C118 (pessoas) se a fila assim o der, para não disputar as mesmas superfícies de lista.
## Fora de escopo
- Mudança de semântica dos status existentes (ex.: renomear "A abordar", reordenar) — outro item.
- Fluxo ou mecanismo novo para votos de "Lembrança" — decidido: votos declarados vão no mesmo campo já existente, sem fluxo próprio.
- Mudanças na UI além do encaixe do novo valor (sem redesign do seletor/badge).
## Rabbit holes de produto
- **Segundo eixo de classificação.** Se alguém "só completar" e achar que "favorável sem compromisso" precisa de um campo extra (ex.: intensidade, motivo), explode para matriz de combinações. **Corte neste item:** um valor a mais no enum existente; motivo/nota já tem `notes`.
- **Lembrança nos pendentes.** Incluir o novo estado no critério de "precisa ser abordada" geraria fila de trabalho que não existe. **Corte:** espelhar a exclusão do "Negativo" (fail-closed na query, não só na UI).
- **Tratamento de votos especializado.** "Contar votos por admiração" pode puxar lógica de pledge nova. **Corte neste item:** decidido no gate — os votos de uma "Lembrança" usam o mesmo campo de votos declarados já existente, sem fluxo próprio; nada de mecanismo novo.
## Questões em aberto (produto)
- **"Lembrança" pode ter votos registrados (pledge) no planejamento?** **Decidido (gate 2026-08-11):** sim — os votos vão no **mesmo campo já existente para votos declarados**, sem fluxo próprio. A liderança "Lembrança" se comporta como qualquer outra quanto a votos; o status é só classificação.
- **Onde "Lembrança" entra na ordem do seletor?** **Decidido (gate 2026-08-11):** entre "Em disputa" e "Negativo" — estado resolvido, não estado de engajamento.
- **"Lembrança" deve aparecer como opção em filtros rápidos/contadores de status?** **Decidido (gate 2026-08-11):** sim — como qualquer outro valor; filtros derivam dos valores existentes.
- **"Lembrança" conta como pendente de abordagem?** **Decidido (gate 2026-08-11):** não — espelha a exclusão do "Negativo" (fail-closed na query, não só na UI).
## Referências
- GitHub Issue #661
- Canvas UI (gate): [plan-c119-ui-draft.canvas.tsx](/home/fsolla/.cursor/projects/home-fsolla-cursor-worktrees-teqo-plans-plan-issue-36/canvases/plan-c119-ui-draft.canvas.tsx)
- `src/collections/Leadership.ts` (campo `supportStatus`), `src/lib/schemas/leadership.ts`, `src/utilities/leadership/leadershipLabels.ts`
- `docs/plans/sollinha-liderancas-pendentes-abordagem.md` (critério de pendência), `docs/plans/autosave-status-lista-liderancas.md`
- `src/utilities/ai/tools/getPendingLeaderships.ts` (pré-filtro de pendência)

View file

@ -0,0 +1,78 @@
# Sollinha mobile: fechar o drawer ao navegar por link
Status: registrado
Atualizado em: 2026-08-11
Issue: #660
Priority: P2
Model: composer-2.5
Impeccable: B — comportamento do drawer existente, sem nova superfície
Canvas UI: /home/fsolla/.cursor/projects/home-fsolla-cursor-worktrees-teqo-plans-plan-issue-36/canvases/plan-b198-ui-draft.canvas.tsx
Appetite: ~0,5 dia eng; um encaixe no renderer de links do chat
Responsável: —
## Intenção
No celular, o Sollinha abre como drawer de 90% da tela. Quando o usuário toca num link entregue na resposta (ex.: "ver liderança", "ver município"), a navegação acontece — mas o drawer continua aberto cobrindo o destino. O usuário chega na página sem ver nada dela, tendo que fechar o drawer manualmente para só então olhar o conteúdo que o link prometia.
A conversa já é preservada (o layout fica montado na navegação interna e o contexto vive na sessão da janela) — o que falta é só o drawer sair do caminho no momento do toque.
## Persona e fluxo
- **Persona / contexto:** assessor/liderança no celular usando o Sollinha em campo; toca num link interno para ir ver o recurso citado.
- **Job principal:** navegar para o destino do link com o drawer fechado, sem perder a conversa.
- **Fluxo desejado:** Sollinha responde com um link interno → toque no link → drawer fecha e a navegação segue no mesmo tab → página de destino visível → abrir o Sollinha de novo (FAB/header) → conversa e estado exatamente onde estavam (comportamento atual de restauração, inalterado).
- **Anti-goals de produto:** fechar o drawer não pode descartar a conversa (o contexto deve continuar salvo); o desktop não muda; link externo (nova aba) não fecha nada na aba de origem.
### Esboço de fluxo (B)
```text
[chat aberto no drawer — resposta com link /campanha…] → [toque no link]
→ [drawer fecha + navegação no mesmo tab] → [destino visível]
→ [abrir Sollinha de novo] → [conversa restaurada no estado anterior]
```
## Objetivo e aceite
- Em mobile, tocar em um link interno (`/campanha…`) na resposta do Sollinha fecha o drawer enquanto navega — o usuário aterrissa no destino com a página visível.
- Reabrir o Sollinha na mesma janela restaura a conversa exatamente onde estava (comportamento atual preservado — nada de contexto perdido).
- Desktop inalterado: o painel lateral continua aberto ao navegar por link (comportamento atual).
- Link externo (http(s), abre em nova aba) não fecha o drawer da aba de origem.
- O estado persistido de "aberto/fechado" continua correto após o fechamento por navegação (reload não reabre o drawer sozinho).
## Dados (intenção)
Dados: N/A — sem superfície de dados; é comportamento de navegação de UI.
## Direção no codebase (hipótese)
- **Áreas prováveis:** `src/components/campaign/shell/ai/CampaignAIChat.tsx` — o renderer de markdown (`markdownComponents.a`) que hoje decide link interno (`next/link`) vs externo (`externalLinkTarget`); hoje é um módulo estático sem acesso ao contexto do chat. O drawer (`CampaignAIDrawer.tsx`) reage ao `open` do `CampaignAISidebarContext` — fechar é chamar `setOpen(false)` no toque (provavelmente só quando `isMobile`). A persistência (`src/lib/sollinhaChatSession.ts`) e a restauração (`CampaignAISidebarContext.tsx`) não devem precisar mudar.
- **Precedente a olhar:** planos `sollinha-links-como-links.md` (B187/B188 — decisão de link interno vs externo) e `sollinha-contexto-sessao-janela.md` (B188 — persistência da conversa).
- **Risco de acoplamento:** o contexto `open` é compartilhado entre drawer (mobile) e painel (desktop) — o fechamento precisa ser condicionado ao viewport mobile para não fechar o painel no desktop. A marcação de intenção de usuário (`userToggledOpenRef`) distingue abertura por gesto de abertura por settle — o fechamento por navegação não deve se passar por decisão do usuário de forma que reabra em reload (o restore só reabre se `open: true` persistido, então fechar programaticamente já é seguro — validar na implementação).
## Dependências
- Nenhuma.
## Fora de escopo
- Fechar o drawer ao navegar por chips/sugestões ou pela busca global — só os links das respostas do Sollinha.
- Alterar a persistência da conversa ou o comportamento do painel desktop.
- Animar/fazer transição do fechamento — o drawer já fecha com o mecanismo existente.
## Rabbit holes de produto
- **Fechar para tudo.** Se alguém "só completar" e fechar o drawer em qualquer link (inclusive externos, que abrem em nova aba), a aba de origem perde o lugar de retomada da conversa. **Corte:** só links internos (navegação no mesmo tab) fecham; externos deixam o chat montado.
- **Reescrever a restauração.** A conversa já persiste e restaura; mexer no mecanismo de sessão para "resolver" o fechamento seria reabrir a raça de restore (OPS22/B188). **Corte:** fechar é só `setOpen(false)` no toque; sessão intocada.
## Questões em aberto (produto)
- **Links externos também devem fechar o drawer?** **Decidido (gate 2026-08-11):** não — o destino abre em nova aba; a aba de origem fica com o chat aberto para retomar a conversa. Só links internos (`/campanha…`) fecham.
- **O fechamento deve acontecer no clique?** **Decidido (gate 2026-08-11):** sim — o drawer sai do caminho no toque, antes de a página carregar.
## Referências
- GitHub Issue #660
- Canvas UI (gate): [plan-b198-ui-draft.canvas.tsx](/home/fsolla/.cursor/projects/home-fsolla-cursor-worktrees-teqo-plans-plan-issue-36/canvases/plan-b198-ui-draft.canvas.tsx)
- `src/components/campaign/shell/ai/CampaignAIChat.tsx` (markdownComponents.a), `CampaignAIDrawer.tsx`, `CampaignAISidebarContext.tsx`
- `src/lib/ai/markdownLinks.ts` (decisão link interno/externo), `src/lib/sollinhaChatSession.ts`
- `docs/plans/sollinha-links-como-links.md`, `docs/plans/sollinha-contexto-sessao-janela.md`