Commit graph

484 commits

Author SHA1 Message Date
Francisco Solla
7bdd7f91b1
Merge pull request #559 from fsolla/B187-sollinha-links-com-aparencia-de-link-nas-respostas
B187 — Sollinha: links com aparência de link nas respostas
2026-08-10 07:34:36 -03:00
Francisco Solla
1016668265 feat(B187): links com aparência de link nas respostas do Sollinha
Estilo de links no markdown do chat (primária + sublinhado + hover/foco),
externos abrem em aba nova; compõe com o next/link interno do B188.
Sem migration; e2e + unit cobrem a superfície.
2026-08-10 07:20:44 -03:00
Francisco Solla
1937688fc5
Merge pull request #555 from fsolla/C106-atualizacoes-mobile-feed-sem-moldura-omnibox-sticky-sem
C106 — Atualizações mobile: feed sem moldura (omnibox sticky sem label, criar no header, cards edge-to-edge)
2026-08-10 04:31:17 -03:00
Francisco Solla
74ccce432d
Merge pull request #554 from fsolla/B191-sollinha-acoes-rapidas-de-abertura-no-chat-chips-de-per
feat(B191): chips de pergunta de abertura no chat Sollinha
2026-08-10 04:15:38 -03:00
Francisco Solla
7eaa65ce03 feat(C106): atualizações mobile sem moldura — omnibox sticky, criar no header, cards edge-to-edge
- CSS de página (padrão C101): .campaign-updates-filter-strip sticky sob a
  top bar com linha separadora, :has() zera o padding-top do scroll container
- Chassis B127 ganha o hook estável data-slot=campaign-omnibox-field (sem
  mudança de comportamento)
- 'Nova atualização' vira icon no header via SetCampaignHeaderAction (slot
  C94/C95), gate de resolução 100% CSS (md:hidden); mesmo modal C89
- Cards edge-to-edge (-mx-4) sem borda, divisor de linha; corrigido o leak do
  @layer base editorial (ul my-6 ml-6 [&>li]:mt-2) que afetava a única lista
  campaign em <ul>
- Empty state com copy única descrevendo a ação
- E2E campaignUpdatesMobile (mobile + regressão desktop); impl plan + changelog
2026-08-10 04:12:23 -03:00
Francisco Solla
04951f6772 feat(B191): chips de pergunta de abertura no chat Sollinha (curados por papel e viewport)
- src/lib/sollinhaOpeningQuestions.ts: catálogo puro de chips de abertura
  (staff: 4 perguntas ancoradas nas tools atuais; leader: conjunto seguro
  sem tool eleitoral — fail-closed B180)
- ChatChipGroup: primitivo presentacional reutilizável (slot compartilhado
  com o B192)
- role do usuário flui do layout server pelo shell até o context do chat
- slot acima do input renderiza só com conversa vazia (messages 0 + ready);
  tocar envia via sendMessage e os chips somem no primeiro envio
- unit tests do catálogo + e2e (staff desktop 4 / mobile 3 / leader seguro)
2026-08-10 03:49:08 -03:00
fsolla
5abc0e0881 fix(C103): reaplica timeVisible/allDay do C104 perdidos no ciclo de rebase 2026-08-10 03:34:33 -03:00
Francisco Solla
acd522f035 feat(C103): agenda mobile — formulário de criação usável (sheet do topo, sem labels, salvar fixo, seletor em bottom sheet) 2026-08-10 03:34:33 -03:00
Francisco Solla
3ca200e7b0
Merge pull request #549 from fsolla/B184-municipios-mobile-lista-sem-moldura-omnibox-sticky-x-de
B184 — Municípios mobile: lista sem moldura (omnibox sticky, X de limpar no input, salvar filtro no header, cards edge-to-edge)
2026-08-10 03:30:25 -03:00
Francisco Solla
d60fe3867a
Merge pull request #552 from fsolla/C107-atualizacoes-mobile-sheet-de-criacao-usavel-altura-auto
C107 — Atualizações mobile: sheet de criação usável (altura auto até o topo, form sem labels com divisórias, placeholders, polaridade em toggle, checks à direita)
2026-08-10 03:29:43 -03:00
Francisco Solla
324e1c3c51 feat(B184): omnibox mobile sem moldura como padrão das listas (sticky no form, X de limpar no input, salvar filtro no header, cards edge-to-edge)
- Chassis CampaignListOmnibox (B127): abaixo de md a label some (input carrega
  aria-label), o campo perde borda/sombra (md: restaura), o form do filtro vira
  barra sticky com linha separadora (sticky no form porque não escapa do
  containing block — a coluna do input dentro do wrapper curto não grudava),
  o texto Limpar vira X circular dentro do input (chips OU busca; slot sempre
  renderizado com invisible; hit target 44px; live region de limpeza), botão
  texto Limpar desktop-only. Padrão para as 11 listas via
  campaignListOmniboxFormClassName.
- Municípios: Salvar filtro vira icon no header mobile (C94/C95 slot,
  presentation='icon', nome de rename = contrato 2.5.3); cards edge-to-edge
  com separador de linha (border-b last:border-b-0, -mx-4, gate md:).
- Atualizações: Nova atualização vai para o header mobile (único trailing
  mobile-visible que grudaria na barra).
- campaignMobileHeaderIconClassName compartilhada (3º consumidor).
- E2E mobile novo (390x844) com pins: foco/aria-expanded após limpar, salvar
  -> renomear no header, cards, desktop invertido.
- D7 registrado no caminho: sticky dos headers da tabela desktop morto.
2026-08-10 03:11:43 -03:00
Francisco Solla
c3f10cce23 feat(C108): sino de notificações — abrir marca tudo como lido, painel limpo, modal central no desktop
openCampaignNotifications (lista + marca em 1 round trip) substitui
listCampaignNotifications + markAllCampaignNotificationsRead; markAllNotificationsRead
extraída (owner-scoped, idempotente, cap 200) com int test; bell vira Dialog
desktop / Drawer mobile (par C94), sem título/rodapé/botões, skeleton de
loading, safe-area, focus-visible, role=alert; readAt sai do contrato do item;
P4-D ledger atualizado (batch sem transação agora no hot path, self-healing).
2026-08-10 02:56:20 -03:00
Francisco Solla
07464b8810
Merge pull request #548 from fsolla/B188-sollinha-contexto-da-conversa-persiste-na-sessao-da-jan
B188 — Sollinha: contexto da conversa persiste na sessão da janela/tab
2026-08-10 02:53:54 -03:00
Francisco Solla
c8e009b47a
Merge pull request #537 from fsolla/C104-agenda-compromisso-todo-o-dia-na-criacao-rapida-so-data
C104 — Compromisso 'Todo o dia' na criação rápida da agenda (só datas, multi-dia)
2026-08-10 01:37:56 -03:00
Francisco Solla
5380154dca fix(B188): simplify — settle não reabre chat fechado no reload desktop; prune com cabeça de pergunta; node fora do Link; mock SSE v7 no B167
- P1: B167 settle forçava open=true no load desktop mesmo com sessão restaurada
  open:false (storage auto-curava para true); restore agora vence quando existe
- prune: cabeça assistant quebra convertToModelMessages; drop até ancorar em user
- MarkdownLink: node do react-markdown não vaza para <Link>/<a>
- isUIMessage: parts com shape inválido falham-closed no restore
- B167 mock: chunk legado {"type":"text"} falha schema v7 (status preso em erro);
  agora usa start/text-start/text-delta/text-end/finish
2026-08-10 01:34:53 -03:00
Francisco Solla
9c1d4839b2
Merge pull request #539 from fsolla/agent/C101-ux-agenda-mobile-affordance-do-hoje-no-titulo-navegacao
C101-ux — Agenda mobile: navegação por teclado + affordance do tap no título
2026-08-10 01:17:05 -03:00
Francisco Solla
ca1dbb80f7 feat(B188): contexto da conversa do Sollinha persiste na sessão da janela/tab
- sessionStorage por aba: mensagens + estado aberto, restaurados no reload;
  escrita só em estado settle (nunca mid-stream); guardrail 50 msgs / 256 KB
- links internos (/campanha…) do markdown navegam via next/link (sem reload)
- e2e novo + ajuste do B167 (visita mobile nova agora loga em viewport mobile)
2026-08-10 01:13:25 -03:00
Francisco Solla
d52d001613 style(C104): format (prettier) 2026-08-10 01:13:12 -03:00
Francisco Solla
ea700e3113 feat(C104): compromisso 'Todo o dia' na criação rápida da agenda (só datas, multi-dia) 2026-08-10 01:13:12 -03:00
Francisco Solla
4e920ee064 feat(C107): sheet de criação de atualização usável no mobile — altura auto até o topo, form sem labels com divisórias full-bleed, placeholders, polaridade em toggle, checks à direita
- MunicipalityUpdateFields ganha layout 'labeled'|'list'; polaridade vira ToggleGroup Ruim|Neutra|Boa nas duas resoluções (mesmo dado, hidden input)
- layout 'list': labels sr-only, textarea sem borda, divisórias divide-y full-bleed, Urgente/Sinalizar adversário como linha tocável com check à direita
- StrictCombobox: prop placeholder (backward-compatible)
- Sheet do feed: max-h-dvh (teto no topo, rola por dentro), título sr-only, form lista com placeholders descritivos; desktop preservado
- Quick-edit B42 herda o visual no sheet mobile via layout
- unit/e2e atualizados para o toggle (radio)
2026-08-10 01:11:24 -03:00
Francisco Solla
f3c41d3382 feat(C101-ux): agenda mobile — navegação por teclado (região focável) + affordance do tap no título
- useAgendaKeyboardNavigation: ArrowLeft/Right → prev/next no mobile
  (FullCalendar v7 não tem setas no grid — verificado no bundle); skips
  para modificadores/dialog/menu/inputs; restore de foco na região
- Região de teclado mobile-only: tabIndex/role/aria-label gateados por
  isMobile (desktop mantém a toolbar e sua árvore a11y intacta)
- onTitleClick ganha icon?: LucideIcon (glyph da página, aria-hidden);
  agenda passa RotateCcw como affordance do 'voltar para hoje'
- E2E: teste de teclado (dia e mês), glyph no título, hardening do swipe
  CDP (pacing 25ms — burst era classificado como flick pelo Chromium e
  disparava back-nav ~35% das runs)
- Unit: 14 testes do hook/pura + 4 do renderer do glyph; impl plan
  docs/plans/c101-ux-pos-critique-impl.md (aprovado pelo humano)

Closes #512
2026-08-10 00:53:19 -03:00
Francisco Solla
cd41b44de4 feat(C105): tags no sheet da agenda + rótulo no evento + prefill no "Mais detalhes"
O filtro por tag na omnibox já existia (C94); o C105 completa a superfície da
agenda: campo de tags no overlay de criação inline (chips, sugestões das tags
já usadas), rótulo leve #tag no evento do calendário (oculto no mês/mobile) e
tags via prefill do "Mais detalhes" (params repetidos, parse fail-closed).

- ActivityTagInput extraído do ActivityForm (compartilhado, aria-label novo)
- "Mais detalhes" espelha estado vivo (horários editados + tags)
- popover do sheet com max-h --radix-popper-available-height (shift-before-flip
  do Radix clipava os campos do topo em viewports baixos)
- submit lê tags do estado espelho (fonte única), 4 KB cap p/ responsáveisJson
- limites mantidos 20/80 (C14), sem migration/access/Consent

Impl: docs/plans/agenda-tags-eventos-impl.md (aprovado no gate humano)
2026-08-10 00:21:20 -03:00
Francisco Solla
a7dce2c716
Merge pull request #516 from fsolla/C101-agenda-mobile-calendario-com-cara-de-app-nativo-context
C101 — Agenda mobile: calendário com cara de app nativo (contexto no header, navegação por arrasto, filtro sticky, dia na hora atual)
2026-08-09 22:43:12 -03:00
Francisco Solla
51fb715ec7 feat(C101): agenda mobile com cara de app nativo — contexto no header, swipe, filtro sticky, dia na hora atual
- toolbar do FullCalendar some no mobile; título do header mostra o período
  ("9 Agosto", "3–9 Agosto", "Agosto", "Agenda") e tap volta ao hoje
- navegação por arrasto horizontal (useAgendaSwipeNavigation): limiar 48px,
  claim 12px com preventDefault, touchcancel sintético contra o long-press
  do FC, dateClick do dia suprimido (C91/C15 intactos)
- filtro vira strip edge-to-edge sem rótulo, sticky sob a barra do app
  (containing block via :has no padding do scroll container)
- visão de dia com scroll interno (height fixa), 08:00 na abertura,
  cabeçalho do dia fixo, scroll preservado ao trocar de dia
- desktop inalterado; impl plan + plano pós-critique (C101-ux)
2026-08-09 22:09:47 -03:00
Francisco Solla
457aad5a21
Merge pull request #510 from fsolla/C102-mobile-sem-botao-sheet-de-sidebar-navegacao-pela-barra-
C102 — Mobile: sem botão/sheet de sidebar — navegação pela barra inferior + Mais
2026-08-09 19:56:07 -03:00
Francisco Solla
f0dff330bd feat(C102): sem hamburger/sheet de sidebar no mobile staff — navegação pela barra + Mais
No mobile (<768px) staff perde o botão de abrir sidebar na top bar
(CampaignMobileTopBar role-gated via useCampaignPageChromeRole +
isStaffCampaignRole; leader mantém) e o sheet de navegação deixa de
montar (CampaignSidebar early-return quando isMobile && staff).
Filtros salvos de Municípios (B18) migram para o drawer Mais
(MunicipalityNavSavedFilters variant 'overflow', mesma store,
apagar/desfazer/sucessão de foco; drawer ganha overflow-y-auto).

Correção de bug pré-existente (D4): o Toaster vivia dentro do wrapper
[contain:layout_paint] do painel da AI, que vira containing block para
position:fixed e stacking context próprio — toasts com ação (ex.
Desfazer) ficavam inalcançáveis sob os portais z-50 de drawer/sheet.
Movido para o root layout (campaign).

Testes: unit top bar (staff sem trigger / leader com trigger) + e2e
(staff sem trigger/dialog, leader com sheet, B18 no drawer completo).
2026-08-09 19:13:43 -03:00
Francisco Solla
27daa1339e feat(C99): toda conta campaignUser aponta para uma ficha Contact; lideranças ganham assessores responsáveis 2026-08-09 19:12:35 -03:00
Francisco Solla
dff940681d fix(B182): PWA iOS — heal do zoom preso do visual viewport no standalone
Motor client-side (standalone-iOS only, no-op em aba/desktop):
heal imediato no install + launch/bfcache/resume/pós-teclado/resize,
scrollTo(0,0) + re-measure nudge, retry ≤3×250ms por episódio,
pinça preservada (janela renovada por gesture*/touch ≥2), debug
global para verificação em aparelho, fallback maximum-scale off.

40 testes unit (jsdom) pinam detecção, guards, retry e teardown.
2026-08-09 18:50:00 -03:00
Francisco Solla
fdedfa9ad4 feat(C97): seletor shadcn de dia+horário 24h no popover de criação rápida da agenda
Troca o datetime-local nativo (AM/PM no idioma do browser, horário pouco
confiável) por ActivityDateTimeField: trigger com rótulo civil
dd/mm/aaaa às hh:mm + popover com Calendar (react-day-picker 9.7,
timeZone America/Bahia, locale pt-BR) e selects Hora 00-23 / Minuto
em passos de 15. Contrato de valor e submit inalterados; prefill do
slot mantido. Novo ui/Calendar.tsx (variante radix-nova), helpers
puros em campaignTime.ts. Sem migration. Closes #482 (PR).
2026-08-09 15:13:15 -03:00
Francisco Solla
07b4509e42 feat(campaign): Tendência migra para Ações ao colapsar a coluna (B172)
Em /campanha/municipios (staff), abaixo de 60rem do container a coluna
Tendência some inteira (hidden @min-60:table-cell no th+td, invertendo a
decisão B158 'container query nunca duplica' só para Tendência) e o mesmo
editor monta como ícone compacto na coluna Ações, ao lado do de atualização;
acima de 60rem volta o badge completo e Ações é gated fechado quando nada
está oculto manualmente. Ações torna-se sempre montada para staff e pinada
por ocultação manual no B17. Uma instância visível do editor por linha por
faixa (dual-render CSS-gated, sem JS/resize). Sem migration. Closes #446.
2026-08-09 13:36:04 -03:00
Francisco Solla
dff3a9e8b1 feat(C95): seletor de modo de visualização no header do app (agenda)
- view=week|day|month|list no contrato URL da agenda (canônico, fail-closed)
- seletor compacto 'Semana ▾' no header desktop e mobile (slot C94/C95)
- FullCalendar headerToolbar sem os 4 botões de modo; escolha vence resize
- effect normaliza a vista também quando a URL perde o param (sem vista órfã)
- useNarrowMeasured(breakpoint) em hooks/use-mobile (settle de hidratação)
- e2e reescrito no seletor + persistência + escolha-vence-resize + estabilização
  dos waits de slot (grid lazy do FullCalendar substitui gridcells no load)
2026-08-09 12:34:04 -03:00
Francisco Solla
6c34da4a47
Merge pull request #488 from fsolla/B178-salvador-cidade-agregada-no-campanha-pagina-virtual-lin
fix(B178): review do simplify — ordenação de nulls nativos, mapa sem slug da cidade, a11y e testes de borda
2026-08-09 10:53:46 -03:00
Francisco Solla
d8ee2dbafa fix(B178): review do simplify — nulls nativos em DESC, mapa sem slug da cidade, sr-only morto, sinal mobile, paginação/edges testados 2026-08-09 10:40:18 -03:00
Francisco Solla
6cf29b3810
Merge pull request #486 from fsolla/B179-sollinha-persona-no-genero-masculino-prompt-referencias
B179 — Sollinha: persona no gênero masculino (prompt + referências .md)
2026-08-09 10:22:35 -03:00
Francisco Solla
f1a8332835
Merge pull request #487 from fsolla/B178-salvador-cidade-agregada-no-campanha-pagina-virtual-lin
feat(B178): Salvador como cidade no /campanha — página virtual, linha comum na lista e busca, sem dupla contagem
2026-08-09 10:21:16 -03:00
Francisco Solla
6760b99942 feat(B179): Sollinha persona no gênero masculino (prompt + placeholder + docs) 2026-08-09 10:07:21 -03:00
Francisco Solla
de66fe3e3f feat(B178): Salvador como cidade no /campanha — página virtual, linha comum na lista e busca, sem dupla contagem 2026-08-09 10:07:05 -03:00
Francisco Solla
d5d1d0c30e chore(B181): remove o aviso de janela vazia da agenda
Sem bloco 'Nenhum compromisso nesta janela e neste filtro' nem botão
'Criar atividade' acima do calendário: a criação segue pelo slot click
(C91), botão dos filtros e FAB. buildActivityCreateHref permanece com
2 consumidores. Teste unitário de regressão + impl plan + changelog.
2026-08-09 09:34:25 -03:00
Francisco Solla
f60a0bafee style(C93): formata (prettier) 2026-08-09 09:04:51 -03:00
Francisco Solla
2ba7a88531 feat(C93): allow generating calendar import link without filters
The 'Link de import' button was disabled whenever no agenda filter was
active, forcing a filter to subscribe to the full campaign agenda. The
server already supported the empty-filter case (filterMunicipality empty
= entire creator scope via buildFeedWhere/resolveFeedCreatorAccess), so
this unlocks the button and drops the now-dead state/hasFilters props.
The dialog copy is neutralized ('recorte' -> 'agenda') to cover both the
filtered and filterless shapes.

Int tests pin the content acceptance: coordinator filterless feed covers
all municipalities; advisor filterless feed is scoped to the administered
portfolio (nothing out-of-scope leaks); an advisor with no assigned
municipality serves an empty feed (fail-closed). A new component test
asserts the button is enabled with zero filters and the create flow works.

Closes #437
2026-08-09 09:02:06 -03:00
Francisco Solla
f36cc244b7 fix(C94): draft mirror p/ picks cumulativos no omnibox + simplificações do /simplify 2026-08-09 04:36:02 -03:00
Francisco Solla
ad39602d04 feat(C94): toolbar omnibox + e2e agenda hardening + changelog 2026-08-09 04:36:02 -03:00
Francisco Solla
e0530a3a90 feat(C94): agenda omnibox + feed no header/FAB 2026-08-09 04:35:39 -03:00
Francisco Solla
14bbee6e00 feat(B176): filtrar municípios por Dobradinha/Liderança/Partido + coluna Dobradinhas ao assessor
Omnibox de /campanha/municipios ganha as dimensões Dobradinha (nome do
contato), Liderança (nome do contato) e Partido (sigla), com recortes
"Sem dobradinha"/"Sem liderança"/"Sem partido" no precedente de
"Sem nível". Params aditivos stateDeputy/leadership/party na URL
congelada; buildMunicipalityListWhere ganha catálogo request-scoped
fail-closed (municipalityRelationSets) que vira as leituras reversa e
cruzada em where — sem migration, paginação nativa mantida; facets
estendidas e o mapa compartilha o catálogo. Coluna Dobradinhas liberada
ao staff (servidor já scoped B37/B157; só o gate de UI mudou).
Extrato do par sentinela+exists:false em campaignListUrl (5 call sites).
2026-08-09 03:19:54 -03:00
Francisco Solla
177803a54a
Merge pull request #470 from fsolla/B173-sollinha-consulta-por-voz-no-chat
B173 — Sollinha: consulta por voz no chat
2026-08-09 02:16:53 -03:00
Francisco Solla
9f997aa232
Merge pull request #471 from fsolla/B175-repintar-a-lista-de-territorios-no-padrao-da-lista-de-m
B175 — Repintar a lista de Territórios no padrão da lista de Municípios
2026-08-09 01:58:06 -03:00
Francisco Solla
4668c018bf feat(B173): consulta por voz no chat da Sollinha (STT → rascunho)
Adiciona entrada por voz ao chat: botão de mic no InputGroup grava no
browser (getUserMedia + MediaRecorder), envia o clip multipart para a nova
rota POST /campanha/api/ai-transcribe (auth cookie + rate limit no mesmo
balde + same-origin) e injeta a transcrição como rascunho editável — o
envio segue o fluxo normal do chat. O áudio nunca é persistido.

STT via Deep Infra whisper-large-v3 (DEEPINFRA_API_KEY, mesma do B174);
a costura de rede vive em utilities/ai/deepInfraTranscribe.ts; a máquina
de captura em useMicTranscript. Falha de permissão/provedor degrada a
mensagem em pt-BR sem quebrar o chat por texto.

Closes #448
2026-08-09 01:47:03 -03:00
Francisco Solla
e75bb1c2f3 feat(campaignTerritories): repintar a lista de Territórios no padrão da lista de Municípios (B175)
- Colunas responsivas ao painel via @container/territory-list (P0→P3), sem scroll horizontal
- Contagem de municípios no nome ("Irecê (20)"); % e votos absorvidos na célula 2022 (hover = válidos)
- Célula 2026 no detalhe de Municípios (valor central + %, faixa de cenários no hover, leitura)
- Colunas de rede só leitura: Assessor, Liderança, Dobradinha (para todo o staff)
- Cobertura oculta por padrão no seletor; labels curtos (2022/2026/Captura/Cobertura)
- loadTerritoryOverview devolve { rows, references }; rollup puro com soma por cenário, hasEstimate e uniões de rede
2026-08-08 23:58:54 -03:00
Francisco Solla
5a51d60c9e style(C89): formata (prettier) 2026-08-08 23:11:05 -03:00
Francisco Solla
7343f15053 feat(C89): página Atualizações — feed de cards + filtro omnibox + criar (modal/bottom sheet)
Substitui o placeholder B164 por um feed operacional da carteira (staff):
- Cards com avatar do autor, polaridade/urgente/adversário, município e data
- Filtro omnibox (município via dotted-path, polaridade, urgente, autor, busca)
  no padrão das listas (URL canônica + adapter puro)
- Criação in-place: modal desktop / bottom sheet mobile, reusando o registro
  unificado (C87) com prefill do município do filtro ativo
- Leader lockdown intacto (gate staff + canReadMunicipalityUpdate fail-closed)
- parseMunicipalityUpdateFormData único para os 3 forms de atualização
- Loader cruzando a carteira sem cláusula de município (access por ator)
- Int/unit specs (19 unit + 5 int) + convenções

Fix real: o modal de criação não pode ser filho React do <form> de filtros —
React 19 não liga progressive enhancement em action-form aninhado.
2026-08-08 23:07:23 -03:00