From 6760b9994263f018358a0ee98b1c9a068d155fee Mon Sep 17 00:00:00 2001 From: Francisco Solla Date: Sun, 9 Aug 2026 09:35:27 -0300 Subject: [PATCH] =?UTF-8?q?feat(B179):=20Sollinha=20persona=20no=20g=C3=AA?= =?UTF-8?q?nero=20masculino=20(prompt=20+=20placeholder=20+=20docs)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/CHANGELOG-AGENTS.md | 2 + docs/plans/sollinha-genero-masculino-impl.md | 64 +++++++++++++++++++ docs/plans/sollinha-genero-masculino.md | 4 +- .../campaign/shell/ai/CampaignAIChat.tsx | 2 +- src/utilities/ai/systemPrompt.ts | 3 +- tests/e2e/campaignAiChatResize.e2e.spec.ts | 2 +- tests/e2e/campaignAiTranscribe.e2e.spec.ts | 4 +- 7 files changed, 74 insertions(+), 7 deletions(-) create mode 100644 docs/plans/sollinha-genero-masculino-impl.md diff --git a/docs/CHANGELOG-AGENTS.md b/docs/CHANGELOG-AGENTS.md index 2a5f61ba..35ec81f0 100644 --- a/docs/CHANGELOG-AGENTS.md +++ b/docs/CHANGELOG-AGENTS.md @@ -4,6 +4,8 @@ Conteúdo movido do `AGENTS.md` em 2026-07-30 (fatiamento do paradigma de agente --- +**Recently resolved (2026-08-09):** **B179** (Issue #462) — o Sollinha passou a falar de si **no masculino**: o system prompt (`src/utilities/ai/systemPrompt.ts`) abre com "Você é **o** Sollinha" e ganhou regra explícita (auto-referência masculina em primeira pessoa, nunca flexões femininas sobre si) — o welcome da UI já era masculino; o placeholder do chat virou "Pergunte para o Sollinha..." (`CampaignAIChat.tsx`) com os seletores e2e acompanhando (2 specs). Sem migration, access, Consent ou guarda automatizada (decisão do gate: só édito de texto). Os planos entregues da família (B162/B166/B167/B173/B174/B177) permanecem frozen com "a Sollinha" histórico — o que vale é a voz no runtime e o que se escreve daqui pra frente. Intenção [`sollinha-genero-masculino.md`](plans/sollinha-genero-masculino.md); impl [`sollinha-genero-masculino-impl.md`](plans/sollinha-genero-masculino-impl.md). + **Recently resolved (2026-08-09):** **B181** (Issue #480) — a `/campanha/agenda` não exibe mais o bloco "Nenhum compromisso nesta janela e neste filtro" + botão "Criar atividade" acima do calendário em janela vazia (só ocupava espaço, redundante com slot-click C91, botão "Nova atividade" dos filtros e FAB C94). Remoção cirúrgica em `ActivityAgenda.tsx` (bloco condicional + órfãos `emptyHref`/imports `Link` e `buildActivityCreateHref`); `buildActivityCreateHref` permanece com 2 consumidores ("Mais detalhes" no inline create, "Nova atividade" nos filtros); loading/erro e `noEventsText` do FullCalendar (vista "Lista") intactos. Teste unitário de regressão: janela vazia não renderiza aviso nem link de criação. Sem migration, access, Consent ou schema. Intenção [`agenda-remover-aviso-vazio.md`](plans/agenda-remover-aviso-vazio.md); impl [`agenda-remover-aviso-vazio-impl.md`](plans/agenda-remover-aviso-vazio-impl.md). **Recently resolved (2026-08-09):** **C93** (Issue #437) — botão "Link de import" da agenda destravado **sem filtros** (sucessor direto do C16): com zero filtros ativos, staff gera o link do feed **completo dentro do escopo de leitura do criador** — coordinator/candidate = tudo; advisor = portfólio dos municípios que administra — com fail-closed/revogação de C16 intactos. O servidor já cobria o caso (vazio = escopo do criador em `buildFeedWhere`/`resolveFeedCreatorAccess`; descrição do campo `filterMunicipality`); a entrega removeu o gate `hasFilters → disabled` de `CalendarFeedButton` (prop morto `state` incluído), manteve `hasFilters` só no "Limpar filtros" e neutralizou a copy do diálogo ("este recorte" → "a agenda"). Testes: 3 int de conteúdo sem filtro (coordinator = A+B; advisor = só o portfólio, nada fora vaza; advisor sem município → feed vazio fail-closed) + unit component (botão habilitado com zero filtros e fluxo nomear→criar). Sem migration, access, Consent ou schema. Intenção [`c93-gerar-link-import-sem-filtros.md`](plans/c93-gerar-link-import-sem-filtros.md); impl [`c93-gerar-link-import-sem-filtros-impl.md`](plans/c93-gerar-link-import-sem-filtros-impl.md). diff --git a/docs/plans/sollinha-genero-masculino-impl.md b/docs/plans/sollinha-genero-masculino-impl.md new file mode 100644 index 00000000..d0ad1352 --- /dev/null +++ b/docs/plans/sollinha-genero-masculino-impl.md @@ -0,0 +1,64 @@ +# Impl: Sollinha: persona no gênero masculino (prompt + referências .md) + +Status: aprovado +Atualizado em: 2026-08-09 +Issue: #462 +Intenção: docs/plans/sollinha-genero-masculino.md +Appetite restante: ~0,25 dia (édito de texto; herdado) + +## Leitura da intenção + +- **Outcome:** o Sollinha se apresenta e se refere a si **no masculino** ("sou o Sollinha", "estou à disposição"), sem renomear o produto nem mudar tom/formalidade. +- **O que NÃO negociar:** sem guarda automatizada (só édito de texto); não reescrever planos entregues/frozen; nome "Sollinha" permanece; nenhuma mudança em tools/RBAC/rate limit/fluxo. +- **O que reavaliar:** a hipótese de "referências .md editáveis" da intenção (ex.: `sollinha-tool-urls-navegacao.md`, `sollinha-cidades-mais-votado.md`) está **desatualizada**: B162 (#383) e B177 (#460) já estão `done/in-prod` → planos entregues → **frozen**. O surface de docs editáveis é efetivamente vazio — a entrega é runtime-first. + +## Abordagem recomendada + +```mermaid +flowchart LR + A[systemPrompt.ts
linha 1 + regra explícita] --> B[Voz masculina no runtime] + C[CampaignAIChat.tsx
placeholder opcional] --> D[Consistência UI] + E[CHANGELOG + plano próprio] --> F[Registro da entrega] +``` + +**Opções consideradas:** A (só prompt) | B (prompt + placeholder UI) | C (prompt + placeholder + docs não-frozen) +**Recomendação:** **B** — o prompt é o núcleo (aceite); o placeholder `Pergunte para a Sollinha...` usa artigo feminino no texto visível ao usuário e é a única outra superfície runtime de gênero; custo zero e consistente com o welcome já masculino ("Olá! Eu sou o Sollinha"). +**Rejeitadas:** + +- **C** — verificado no repo: os únicos docs da família com "a Sollinha" são **entregues/frozen** (B162, B166, B167, B173, B174, B177 + seus `-impl.md`) ou de Issue **in-progress** de outro agente (`salvador-pagina-detalhe-cidade.md`, B178 — proibido tocar). O único plano não-frozen da família (`sollinha-tools-eleitorais-leader-lockdown.md`, B180) já usa "no Sollinha" (masculino). Regra do repo: entregue = imutável; nada a editar. +- **Guarda automatizada / teste de convenção** — decisão explícita do gate da intenção ("sem guarda automatizada"). +- **Renomear produto / mudar tom** — anti-goal da intenção. + +### Componentes / mudanças + +- **`AI_SYSTEM_PROMPT`** (`src/utilities/ai/systemPrompt.ts`): linha 1 `Você é a Sollinha` → `Você é o Sollinha`; adicionar regra explícita na seção "Quem você é" (flexões masculinas de primeira pessoa; nunca adjetivos/particípio no feminino sobre si — ex.: "obrigado", não "obrigada"). +- **`CampaignAIChat.tsx`** (placeholder): `Pergunte para a Sollinha...` → `Pergunte para o Sollinha...` (único caso com artigo feminino na UI; welcome já é masculino). +- **E2E existentes** (manutenção de seletor, não guarda nova): 3 referências ao placeholder nos specs `campaignAiChatResize.e2e.spec.ts:23` e `campaignAiTranscribe.e2e.spec.ts:84,128` acompanham o texto. +- **Docs:** sem edição de planos entregues (frozen). Edita-se apenas o plano desta entrega (`sollinha-genero-masculino.md` — status no fim, quote da referência corrigida pós-fix) e a entrada curta em `docs/CHANGELOG-AGENTS.md` (redigida já no masculino). +- **Migration:** sem migration. **Access / Consent:** nenhum (sem schema, sem RBAC, sem PII). **UI:** Impeccable A — N/A (só string literal). + +## Fases verificáveis + +1. **Runtime** — editar `systemPrompt.ts` (linha 1 + regra). (quota: 0,1 dia) +2. **UI + specs** — placeholder `CampaignAIChat.tsx` + 3 seletores e2e. (quota: 0,05 dia) +3. **Docs/registro** — entrada `docs/CHANGELOG-AGENTS.md` + atualizar plano da entrega. (quota: 0,05 dia) +4. **Gates** — `pnpm gate:fast`; e2e dos 2 specs tocados; `pnpm push` entrega. + +## Rabbit holes / Não escopo (engenharia) + +- Caçar "a Sollinha"/"da Sollinha" em planos entregues ou no `CHANGELOG-AGENTS.md` (histórico congelado — fica como referência de época). +- Editar `salvador-pagina-detalhe-cidade.md` (B178, in-progress de outro agente) — mesmo tendo "da Sollinha". +- Teste de convenção / guarda automatizada de gênero (decisão do gate). +- Ajustar modelo/empresa de LLM (voz vem do prompt; reavaliação só com exemplo gravado, registro no plano sem projeto). + +## Riscos e mitigação + +- **Deriva do modelo apesar do prompt** (regra explícita não elimina 100%): risco conhecido e aceito pela intenção — reavaliar com exemplo gravado se recorrer; sem projeto agora. +- **E2E quebrado pelo seletor novo**: mitigado — os specs tocam o placeholder exatamente 3 vezes; rodar os 2 arquivos antes do push. +- **Divergência da hipótese da intenção (docs "editáveis")**: mitigado por este plano — o aceite (prompt define "o Sollinha" + regra explícita) permanece integralmente coberto; o surface .md não exige edição. + +## Aceite de engenharia + +- [x] Aceite de produto da intenção ainda coberto (prompt define "Você é o Sollinha" + regra explícita; docs novos/no futuro usam "o Sollinha") +- [x] Invariantes AGENTS/engineering-standards (sem migration, sem access, sem Consent; nada de histórico frozen tocado) +- [x] Sem testes de domínio novos — decisão de produto: édito de texto sem guarda automatizada; só manutenção de seletores e2e existentes diff --git a/docs/plans/sollinha-genero-masculino.md b/docs/plans/sollinha-genero-masculino.md index b770e962..2faa8dda 100644 --- a/docs/plans/sollinha-genero-masculino.md +++ b/docs/plans/sollinha-genero-masculino.md @@ -1,6 +1,6 @@ # B179 — Sollinha: persona no gênero masculino (prompt + referências .md) -Status: plano — registrado (blocked até plano em main) +Status: entregue (2026-08-09 — prompt + UI no masculino) Atualizado em: 2026-08-09 Issue: #462 Priority: P2 @@ -70,6 +70,6 @@ Resolvidas no gate (2026-08-09): ## Referências - Canvas UI (gate): N/A -- `src/utilities/ai/systemPrompt.ts` (linha 1: “Você é a Sollinha…”) +- `src/utilities/ai/systemPrompt.ts` (linha 1: “Você é o Sollinha…” — corrigido no B179) - Planos da família: [`ai-chat-sollinha.md`](ai-chat-sollinha.md) (frozen), [`sollinha-tool-urls-navegacao.md`](sollinha-tool-urls-navegacao.md) (ready/editable), [`sollinha-cidades-mais-votado.md`](sollinha-cidades-mais-votado.md) (novo) - AGENTS.md — convenções de naming/idioma (rótulos visíveis em pt, identificadores em inglês); regra de planos frozen diff --git a/src/components/campaign/shell/ai/CampaignAIChat.tsx b/src/components/campaign/shell/ai/CampaignAIChat.tsx index df9901b1..a72d7697 100644 --- a/src/components/campaign/shell/ai/CampaignAIChat.tsx +++ b/src/components/campaign/shell/ai/CampaignAIChat.tsx @@ -152,7 +152,7 @@ export const CampaignAIChat = ({ className }: { className?: string }) => { handleSubmit(e) } }} - placeholder={isRecording ? 'Ouvindo...' : 'Pergunte para a Sollinha...'} + placeholder={isRecording ? 'Ouvindo...' : 'Pergunte para o Sollinha...'} disabled={busy} rows={1} className="min-h-0 flex-1 resize-none border-0 bg-transparent px-3 py-2 text-sm outline-none placeholder:text-muted-foreground disabled:opacity-50" diff --git a/src/utilities/ai/systemPrompt.ts b/src/utilities/ai/systemPrompt.ts index 3a81de08..64fcbd82 100644 --- a/src/utilities/ai/systemPrompt.ts +++ b/src/utilities/ai/systemPrompt.ts @@ -1,7 +1,8 @@ -export const AI_SYSTEM_PROMPT = `Você é a Sollinha, assistente virtual da campanha de Jorge Solla (PT-BA) para deputado federal. +export const AI_SYSTEM_PROMPT = `Você é o Sollinha, assistente virtual da campanha de Jorge Solla (PT-BA) para deputado federal. ## Quem você é - Você é prestativo, direto e conhece profundamente os dados da campanha. +- Você se refere a si mesmo sempre no masculino, em primeira pessoa: "sou o Sollinha", "estou à disposição", "obrigado". Nunca flexione no feminino adjetivos ou particípios sobre você (ex.: "sou a Sollinha", "obrigada", "estou disponível" — não "disponível" no feminino). - Você fala português do Brasil, em tom profissional mas acolhedor. - Você NUNCA inventa dados. Se uma ferramenta retornar "não encontrado", você diz isso claramente. - Quando uma ferramenta retorna dados numéricos, você os apresenta de forma clara, com contexto. diff --git a/tests/e2e/campaignAiChatResize.e2e.spec.ts b/tests/e2e/campaignAiChatResize.e2e.spec.ts index 6c40297c..d3af8c49 100644 --- a/tests/e2e/campaignAiChatResize.e2e.spec.ts +++ b/tests/e2e/campaignAiChatResize.e2e.spec.ts @@ -20,7 +20,7 @@ const MESSAGE = 'Mensagem que migra entre as superficies' const openChatAndSend = async (page: Page) => { await expect(page.getByText('Olá! Eu sou o Sollinha')).toBeVisible({ timeout: 20_000 }) - const input = page.getByRole('textbox', { name: 'Pergunte para a Sollinha...' }) + const input = page.getByRole('textbox', { name: 'Pergunte para o Sollinha...' }) await input.fill(MESSAGE) await input.press('Enter') await expect(page.getByText(MESSAGE)).toBeVisible({ timeout: 20_000 }) diff --git a/tests/e2e/campaignAiTranscribe.e2e.spec.ts b/tests/e2e/campaignAiTranscribe.e2e.spec.ts index c9d8a87e..a58edfe3 100644 --- a/tests/e2e/campaignAiTranscribe.e2e.spec.ts +++ b/tests/e2e/campaignAiTranscribe.e2e.spec.ts @@ -81,7 +81,7 @@ test.describe('B173 — consulta por voz no chat da Sollinha', () => { await mockAiChat(page) await openChat(page) - const input = page.getByRole('textbox', { name: 'Pergunte para a Sollinha...' }) + const input = page.getByRole('textbox', { name: 'Pergunte para o Sollinha...' }) // Tap the mic → the bar enters "Ouvindo..." while we "speak". await page.getByRole('button', { name: 'Falar pergunta (voz)' }).click() @@ -125,7 +125,7 @@ test.describe('B173 — consulta por voz no chat da Sollinha', () => { }) // Text chat still works. - const input = page.getByRole('textbox', { name: 'Pergunte para a Sollinha...' }) + const input = page.getByRole('textbox', { name: 'Pergunte para o Sollinha...' }) await input.fill('Foco no texto') await input.press('Enter') await expect(page.getByText('Foco no texto')).toBeVisible({ timeout: 20_000 })