O cache-hit do DeepSeek-V4-Flash custa $0.0028/M contra $0.018/M na
Deep Infra (~6,4x mais barato), e a Sollinha tem prefixo estável
(system prompt + tools + historico), entao o custo efetivo cai. Mesmo
modelo (deepseek-v4-flash), mesmas tools. @ai-sdk/deepseek pinado em
3.0.19 para ficar na ferroviaria provider 4.0.4 / provider-utils
5.0.18 do ai@7.0.47 (o ^ flutuaria a 3.0.26 e duplicaria 4.0.7).
STT (B173) permanece na Deep Infra: whisper-large-v3 a $0.00045/min
de audio e o mais barato verificado (Groq $0.0019, OpenAI $0.006).
.env.example documenta as duas chaves: DEEPSEEK_API_KEY (LLM) e
DEEPINFRA_API_KEY (STT).
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
Troca o provider da Sollinha de @ai-sdk/deepseek (api.deepseek.com) para
@ai-sdk/deepinfra (API OpenAI-compatível da Deep Infra), mantendo o mesmo
modelo deepseek-ai/DeepSeek-V4-Flash e as mesmas tools. Uma chave só
(DEEPINFRA_API_KEY), a mesma dos agentes; rollback = git revert.
Closes#449
- 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
createCalendarFeedLinkRecord failed with `overrideAccess:false` +
`user: campaignUser` because Payload stripped the required
`secretSlug`/`createdBy` fields (create access was admin-only) -> 400
behind the generic message; revokeCalendarFeedRecord silently no-opped
(revokedAt update access also admin-only).
- canCreateCalendarFeed now enforces advisor filterMunicipality scope
on data (canCreateSupporter precedent) + canSetCalendarFeedSecret /
canSetCalendarFeedRevocation permit staff to mint the secret on create
and mark a feed revoked; secret read stays `() => false` and update
admin-only.
- CalendarFeed stamps createdBy via stampCampaignCreatedBy; secret
create + revokedAt update use the new staff field accesses.
- Action refactored to `*Record` (payload, actor) cores + thin wrappers;
assertFilterMunicipalityInScope (tour precedent); console.error keeps
the real error on the server while the friendly message stays.
- 10 int tests for create/scope/revoke/leader/secret; fixture tracks
calendarFeed cleanup.
Closes#436
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.
- Converse (useChat) lifts to the provider: survives desktop panel <-> mobile
drawer swaps, internal /campanha navigation and resize (new tab still resets).
- Chat Panel/Separator always mounted (RRP panel-count change forces a full
navigation); footprint hidden via elementRef + .b167-ai-chat-hidden
(display:none !important), avoiding RRP's imperative collapse/expand races.
- Single 'open' flag drives both surfaces (open && viewport); reconciled with
the panel at desktop settle and on user drag-collapse (gated by isUserInteraction).
- Mobile Drawer extracted to CampaignAIDrawer; FAB/setOpen unified; focus moves
into the chat on the mobile->desktop swap.
- e2e campaignAiChatResize: desktop->mobile, mobile->desktop, closed stays
closed, fresh mobile visit closed, navigation survival, new tab resets.
- Impl plan: docs/plans/migrar-chat-painel-drawer-resize-impl.md
Closes#416
Ações de ajuste (votos, tendência, atualização, liderança) agora retornam à
origem ao salvar (toast + from allowlist B110 / Início), sem avançar sozinhas
para outro wizard. Removidos fila de encadeamento (wizardActionChain.ts),
param entry, skip/Pular (chrome mobile sempre X) e prefill de nota sem
produtor. Liderança: botão pós-save 'Continuar' → 'Concluir' (origem).
Sem migration.
Mobile bottom nav (staff only): 5 items (Início, Municípios, Atualizações,
Agenda, Mais) with Drawer overflow for secondary destinations. Leader and
desktop remain sidebar-only. FAB + scroll padding adjusted for safe-area.
Resolves#400
- Add calendarFeed collection with secret slug credential
- Add iCal feed endpoint at /campanha/agenda/ical/[secret] (unauthenticated)
- Add server actions for creating and revoking calendar feeds
- Add CalendarFeedButton UI component in agenda filters bar
- Add unit tests for iCal generation
- Fail-closed access: feed re-evaluates creator's municipality scope on each read
- Leader lockdown preserved; no PII in feed output
Substitui o modelo de MunicipalityUpdate (kind/semanal/urgente/nota/sinal + signalType)
por um modelo unificado: body obrigatório + polarity (boa/neutra/ruim) + urgent + adversarySignal.
- Schema: polarity enum obrigatório, urgent boolean default false, adversarySignal boolean
- Wizard: novo WizardUpdateBodyStep com campo de texto + polaridade + urgente
- Lista: controle unificado de atualização com polaridade inline
- Feed: exibe polaridade como badge colorido + indicador de urgência
- V2 status strip: atualização rápida de polaridade
- E11: queries atualizadas para polarity + adversarySignal
- Migração: 20260807_030000_unify_municipality_update (backfill polarity=neutra, urgent de kind, adversarySignal de kind+signalType)
- Testes: 18/18 integração C87, 593/593 total, 1399/1399 unitários
- Fix: testes de state deputy usavam nomes fixos que colidiam entre execuções
Issue #390 — entrada operacional staff com FullCalendar v7 route-local
(semana/mês/lista), filtros compartilháveis por município/deputado/tag,
criação pelo slot, detalhe por URL e remarcação drag/resize sob RBAC,
transação e lock por atividade.
- /campanha/atividades e giros permanecem como lista/fluxos legados
- Sem migration/collection nova; reutiliza activity de C14
- Fuso Bahia, range exclusivo, erro/revert, loading/empty testados
- Leader negado, advisor limitado pelo access atual
- E2E cobre filtros, drag, mobile 390x844 e slot prefill
- Int specs cobrem RBAC, concorrência e defense-in-depth
Impl: docs/plans/fullcalendar-agenda-campanha-impl.md
- Replace kind enum with free-form tags (text[] hasMany)
- Remove origin and deadline fields
- Status: remove rascunho/planejado, keep confirmado/realizado/cancelado
- startAt required for user-facing creates (optional for tour drafts)
- Add deputyPresent time gate (only coordinator/candidate can reschedule)
- UI: calendar-like form with tag input, no origin/deadline, no draft status
- E13 giros: tourStopRoleActivityKind → tourStopRoleActivityTags
- Migration: kind→tags with label mapping, status remap, enum recreation
- Activity texts table creation (was missing from migration history)
Closes#389
* fix(campanha): unblock dobradinhas list SSR for Assessores column
Drop CampaignTableHead description on Assessores — its hover tooltip plus
StateDeputyAdvisorRelationCell breaks production SSR with an undefined element.
* fix(test): keep int gate under Postgres connection ceiling
Cap vitest workers and raise Docker max_connections so high-core laptops
stop failing the suite with cascading "too many clients" / QueryError noise.
* fix(test): do not force eight int workers on CI
An explicit maxWorkers: 8 raised parallelism on 2–4 vCPU runners and
starved shared invite/consent advisory leases (15s timeouts). Cap only
on local high-core hosts; leave CI on Vitest's CPU-relative default.
* style(test): format vitest.config.mts
Espelho da coluna de Assessores: chips com nome do contato, popover/sheet
com Command + delta otimista e criação inline (Nome + Celular). Sem
migration — leitura reversa de leadership.municipalities no bundle e
escrita reusando as actions B34/wizard; rota POST
/campanha/municipios/leaderships via campaignJsonMutationRoute.
Closes#352
- POST /campanha/municipios/advisors aceita body de criação (name) na union;
createMunicipalityAdvisorRecord cria campaignUser advisor (stub
<slug>@criado.invalid, senha aleatória) e atribui ao município na mesma
transação (cap 10 com rollback, lock municipality-advisors:{id})
- MunicipalityAdvisorCreateProvider compartilha o assessor criado entre linhas;
opção "+ Criar assessor" e chip otimista com id temporário no control
- advisorNameSchema unificado em primitives.ts; predicado de placeholder cobre
@criado.invalid (reset de senha e célula de e-mail tratam stubs inline)
- Destrava o spec e2e de municipios (falhas pré-existentes em main): key
warning do fragment trailing de MunicipalityFilters; settle-wait da animação
do Drawer no teste B136
- leadershipContactUpdateSchema + updateLeadershipContactRecord
- LeadershipContactFieldControl (lápis → input → blur-save)
- Lista e bloco Contato no detalhe
- Int tests para update, conflito de telefone e escopo
Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
- loadMunicipalityV2ContaData + MunicipalityV2LocalAccountSection
- Inline edit-in-place com cenário ativo local para cobertura
- Extract MunicipalityTerritorialClassRow do card E8
- Integra com B149 rede e B150 agora na mesma página v2
- Impl plan docs/plans/municipio-v2-conta-local-impl.md
- Catálogo v2 de ações rápidas: dossiê, eleições, nova liderança, giro
- Integra no chassis B126 via registry + context sync na rota v2
- Omitido registrar sinal (já na faixa de status B147)
Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
- Loader e seção Agora na rota /campanha/municipio/[slug]/v2
- Encaminhamento editável com auto-save via POST /municipios/next-steps
- Até 2 sugestões E11 com resolve/dispensa existente; silêncio explícito
- Visita E13 condensada em uma linha + link ao compositor de giros
- Revalidação v2 ao resolver sugestão; helper compartilhado de empty state E11
Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
- Seção Rede na rota v2 com tabela/lista densa e CTAs Ver todas / Nova
- Células autosave (B9) para declarado e estimado via JSON routes
- Loader + view puro com ordenação por estimado/declarado
- Revalidate v2 em writes de pledge; unit tests de merge/sort
Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
Move CampaignColumnPicker to omnibox trailing via CampaignColumnPickerTrailing
on all seven B17 list surfaces (+ organizações). Remove the picker row from
CampaignTable and drop visible sort-summary spans on territórios, lideranças
and dobradinhas (sr-only table captions keep sort context).
Closes#304
Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
Allow empty motivo on sinal through collection beforeValidate; distinguish
warm-typeless vs cold in the v2 signal select; reuse list signal form action.
Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
Rota paralela /campanha/municipio/<slug>/v2 com selects de nível, tendência e
sinal (modal de motivo opcional), classe territorial e agregado. Soft-dep B134:
motivo opcional no movimento de nível e remoção do campo “voltar atrás” do
write path/lista.
Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
Adiciona dimensão municipality na URL e omnibox da lista de assessores,
espelhando o precedente de lideranças: chips removíveis, busca tipada no
catálogo e filtro server OR via municipality.advisors.
Closes#310
Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
- URL state: organization / stateDeputy (OR inclusivo por dimensão)
- Facets cross-filtered em leadershipData
- Adapter omnibox + wiring na página
- Testes unit/int e plano de implementação
Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
- Make engagement level note optional in schema and UI
- Remove reversalSignals field from popover, action snapshot and DB
- Add data migration to strip reversalSignals from movimento snapshots
- Wizard trend note step no longer requires text; labels show (opcional)
Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
Replace the stacked município filter controls with a shared omnibox
(chips + suggestions) backed by the frozen list URL contract; scenario
and non-default sort appear as presentation chips.
Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
Remove MunicipalityListOverview from /campanha/municipios and drop the
overview field/computation from loadMunicipalityListPageBundle. Filters,
scenario selector, and per-row goal coverage for sorting remain unchanged.
Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
Remove o bottom drawer não-modal (snap/peek/scroll-coupling) e introduz
um FAB em todas as larguras fora do Início e wizards. Abrir o FAB mostra
overlay modal: Dialog no desktop (busca em cima) e Drawer no mobile
(ações em cima), reutilizando strip e busca globais do Início.
Closes#260
Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
Replace the W4d filename-based convention spec with a sweep of every
src/app/(campaign) file that calls mapCampaignFormActionError without
runCampaignFormAction/Redirect. Migrate profile, leaderSupporter, and
convite-login to the shared wrappers; allowlist auth/password and the
existing bespoke formActions exceptions with documented reasons.
Closes#241
Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
Propagate return path from quick actions through the B98 wizard chain so
dismiss, skip, and chain end navigate back to the originating page instead
of always redirecting to Início.
Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
- CampaignHomeSearchChromeContext + publisher no Início
- CampaignMobileTopBar troca SidebarTrigger por Voltar quando uiFocused
- useHomeSearchFocusHistory com pushState/popstate para back do SO
- HomeSearchSuggestSkeleton enquanto isFetching sem hits
- Unit tests para history helper, top bar e skeleton
Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
Lift TooltipProvider in the campaign app layout so it wraps
CampaignAppScrollChrome (scroll + quick-actions drawer). Focus→suggest
with priority-alta hits mounted CampaignHoverTooltip outside the old
provider and crashed the page. Unit pins cover focus→suggest and type→search.
Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
Após salvar (ou Pular) votos/tendência/sinal/liderança, navega ao
próximo elo da matriz v1 no mesmo município via entryAction, em vez
de voltar ao Início. Helper puro wizardActionChain + skips alinhados.
Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
Após salvar a nota, o RSC refresh via server action via
redirecionava à etapa de escolha. Agora manda
para Início nesse caso; o client usa no sucesso.
Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
No idle do passo de busca de município, resolve client-side o município
mais próximo no escopo do ator quando a permissão de geo já está granted
(reuso B14: campaignGeolocation + municipalityProximity + malha lazy).
- Prop RSC accessibleMunicipalities na rota acoes/[slug]
- Hook useWizardNearestMunicipality (fail-soft, só inScope)
- Merge geo no topo da lista B92 via wizardMunicipalitySuggestMerge
- Unit tests do merge/format
Closes#94
Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
Merge client-side recent visits and last-acted municipality storage into
wizard municipality suggest idle list ahead of B92 server rankings.
- Add campaignLastActedMunicipality + wizardMunicipalitySuggestMerge libs
- Extend wizard-municipality-suggest with scopeMunicipalities for hit lookup
- Record last-acted on wizard write success (votes, signal, trend, leadership)
- Clear last-acted on logout alongside recent visits
Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
Add wizard-municipality-suggest mode to home-search with cold-first
ranking (signal → E14 → trend → E10 class → 2022 votes). Wizard
municipality step now fetches suggestions when query is idle.
Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
- Substitui loadActivityOptions (pagination: false) por AsyncSearchCombobox
com busca server-backed filtrada por município (searchActivityRelationOptions).
- Pagina card Demandas vinculadas no detalhe da atividade (10/página) com
soma de custo em query separada e link Ver todas na lista filtrada.
- Adiciona filtro ?activity= na lista /campanha/demandas.
Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
Registra catálogo contextual no drawer B79 para /campanha/liderancas:
lista com update-leadership; detalhe com cinco wizards e prefill de
município quando N=1. Adiciona query leadershipId no wizard A4 e
CampaignQuickActionContextSync na ficha.
Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
Registry B79 para /campanha/assessores (lista + detalhe): catálogo
new-advisor com deep link ?criar=1; mount gate só unrestricted;
/novo redireciona para o deep link; busca global permanece na lista
(B91 no drawer).
Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
Registra catálogo contextual no chassis B79:
- Lista: um launcher register-demand → /demandas/nova (wizard A5 ainda stub)
- Detalhe: A1–A5 com prefill de município via context sync
- Expõe municipalityId no view model para prefill do form de criação
Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
Registra catálogo B84 no registry B79: Nova atividade e Planejar giro na
lista; wizards A1–A5 com ?municipio= no detalhe quando há município, mais
atalhos Editar/Tarefas/Atualizações. Context bridge no detalhe; drawer
omitido em /atividades/giros (compositor E13 já tem CTAs).
Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
Registra catálogo staff no drawer B79 para /campanha/municipios e
/campanha/municipios/[slug]: lista espelha Início (sem prefill); detalhe
prefill ?municipio= nos wizards A1–A5 e omite Ver esquecidos.
Closes#16
Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
countUnreadNotifications/loadNotificationList took a caller-supplied
recipientID with module-level admin bypass — one misuse away from reading
someone else's inbox. They now take the actor (CampaignUser) and read
with overrideAccess: false under canReadOwnNotifications, keeping the
explicit recipient filter as a second fence. Pin: owner isolation at the
loader level in campaignNotifications.int.spec.ts.
Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
The B42 fix reached the municípios mobile cards, but the three
chip-relation surfaces still mounted a Drawer root per OPENED cell on
coarse pointers (lideranças 50 cells, dobradinhas 50, assessores 25):
the exact hundreds-of-hidden-instances class that tripped React #130 in
prod. Each now wraps its table in CampaignListSheetProvider — cell
bodies portal into the single host Drawer, fine-pointer popover path
untouched (behavior delta: after opening N cells sequentially on touch,
N drawer roots -> 1).
* docs(A6): freshness audit da #27 no plano de dobradinhas 2026
electionInsights.ts foi deletado no Pass 2 W4a (modulo puro novo),
electionAlliances.ts nao nasce (campoParties + electionPartySpectrum ja
carregam a taxonomia), questoes em aberto resolvidas (tiers, score,
proporcionais, lockdown de lideranca), card na aba Eleicoes, overview da
lista fora de escopo, cache election-tse com bust na conciliacao Fase 5.
Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
* feat(A6): insight de dobradinhas 2026 na aba Eleicoes do municipio
Lista ranqueada de candidatos proporcionais que concorrem de novo em 2026
(runningAgain2026=sim) por geografia do municipio: score 0,6 alinhamento
(campo 2026 + espectro Bolognesi) + 0,4 forca local normalizada pelo maximo
do conjunto. Estado pendente ate a conciliacao pos-15/08; Solla excluido
(referencia, nao parceiro); leitura derivada sem escrita; loader cacheado
sob a tag election-tse como os irmaos; link para /campanha/dobradinhas
como caminho de registro operacional.
Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
* test(A6): cobertura unit/int do insight + lease das colecoes eleitorais
Unit: tiers (campo/espectro/falha-para-o-meio), score, ordenacao, empates,
limite, zero votos. Int: pending2026 sem conciliacao (mesmo com votos
locais), so adversarios, misto com exclusao de Solla/nao e geografia
alheia. Lease advisory election-collections serializa os dois specs que
tratam as colecoes eleitorais como test-owned (o import wipe-all apagaria
as fixtures do novo spec no meio do assert, 48-way paralelo).
Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
* refactor(A6): fixes do /simplify (3 reviewers paralelos)
P1: campo coalition morto removido do input/select/mapping (sem consumidor).
P1: hooks do lease com timeout explicito de 120s nos dois specs eleitorais
(o wait advisory passa dos 10s default do Vitest sob contencao — flake em
ambas as direcoes). P2: sonda de conciliacao via payload.count (idioma da
casa), desempate final por candidateNumber (ordem deterministica), geography
como prop na secao (sem lookup duplicado), casing Dep. Federal/Estadual
alinhado, marcador 1313 via BASELINE_TICKET_2022, entrada no CHANGELOG.
Declinados com justificativa: zones como tupla nao-vazia (churn em tipo
compartilhado por assert provada no catalogo) e early-start da promise
antes do Promise.all (loader-dentro-do-Suspense e o idioma da casa; ganho
so em cache frio).
Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
Monta CampaignQuickActionsDrawer não-modal no layout (app) para staff fora
do Início e de /acoes, com snap collapsed/expanded, padding de peek no scroll,
registry vazio para catálogos B80+, e busca global reutilizada via
CampaignStaffGlobalSearch.
Closes#15
Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
- Collections Notification e PushSubscription com migration
- Sino no header (mobile + desktop) com drawer de avisos lidos/não lidos
- Hooks em municipalityUpdate, supporter, activity e convite aceito
- Push Web via web-push + handlers no service worker /campanha/sw.js
- Opt-in LGPD em /campanha/perfil (chave campanha-notificacoes-push)
- Testes unit/int de contrato e access
Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
Reusa CampaignListPendingBoundary no layout autenticado para compartilhar
useTransition entre o header mobile e o conteúdo do passo. Voltar, X, Pular,
seleção de município e tiles de passo passam por CampaignWizardNavLink;
o corpo dima com aria-busy e o header mostra skeleton nos slots de título.
CampaignTransitionAnchor agora retorna a promise do router para manter o
pending honesto até a navegação terminar.
Closes#14
Remove CampaignBottomNav and getCampaignBottomNav — mobile navigation
is now sidebar-only (Sheet via SidebarTrigger). Drop the pb-24 padding
that reserved space for the fixed bar; scroll uses symmetric p-4 on mobile.
Impeccable: N/A (shell chrome removal, class B per plan).
Plan: docs/plans/remover-bottom-nav-mobile.md
* feat(campanha): responsive grid and viewport cap for home search results (B54)
Adds HomeSearchResultsLayout with 1/2/3-column breakpoints and allocateHitBudget so Municípios keep priority while total hits stay within per-viewport limits.
Closes#8
* fix(campanha): un-export knip-unused home search budget helpers
CI e2e serves a production build on localhost; Next inlines NODE_ENV so
WebAuthn RP resolution now treats localhost requests as test mode. Saved
filters avoids a racing bare goto. Analytics/SpeedInsights render only on Vercel.
Two-step UX-1 flow on the B59 chassis with URL contract ?signalType= and optional ?entryAction= for embedded skip; reuses createMunicipalityListSignalFormAction.
Unify violation highlights with getWizardVoteViolation, share the expected-votes save error constant, and drop dead parseWizardVoteDraft plus unused municipalitySlug prop.
Organization stays by product decision (political branching, e.g. SindMed);
the leadership gains `exclusive` (default true) with list badge and form
checkbox, and sector/sectorNotes/consentNote leave the collection, zod,
forms, list filters/sort, invite autofill and redemption.
Staff can adjust municipality expected votes through média→pessimista→otimista steps with shortcuts, coherence warnings, and a single POST on completion.
Drops signalSource/triangulated from municipalityUpdate (migration
simplify_municipality_signal_fields), rewrites signal-type labels for the
field team, and treats any adversary signal as confirmed in E11 (P1
triage 1). Also fixes the stale post-B43 leader-home e2e and lowers a
drifted bypass pin.
- F1: loadMunicipalityScopeFromDocs evita segunda query em municipalityPageData
- F2: computeAllMunicipalityPotentials memoizado por processo
- F3: TooltipProvider movido para o layout de (app)
- F4: DRY nos filtros multi do mobile em MunicipalityFilters
The B46 flex spacer never filled the scrollport because min-h-full did not propagate; h-full min-h-0 plus home-dock groups the thumb zone above bottom nav.
Staff see role-ranked shortcuts (advisor portfolio by signal age; CG/candidate by alta priority deficit) via POST mode suggest, without treating empty query as search.
Staff get six launchers with uncovered-municipalities linking to the sem_assessor list; leaders get two with contacts wired to the shared route constant.
Fold duplicated campanha access, loaders, list/filter/form shells, and scripts into shared modules; reorganize utilities by domain; stabilize e2e; and document prevention-first audit workflow so the same smells cannot creep back in.