Commit graph

334 commits

Author SHA1 Message Date
Francisco Ignácio Fontoura Solla
812e3a0bee B34: editable município chips on the leaderships list.
The "Municípios" column was dead text; it is now chips that navigate to the
município, with a territory chip when a leadership covers a whole TI, and it is
editable in place. What picks inline vs. Drawer is the `pointer:` media query,
not viewport width: on a fine pointer the editor is always mounted (the "×" sits
at the chip's corner, revealed on hover/focus) and on a coarse pointer a tap
anywhere in the cell opens a Drawer with 44 px targets.

`AdvisorMunicipalityCell` is promoted to `shared/MunicipalityPortfolioCell` and
/campanha/assessores migrates onto it in the same change, so the second call site
is born with the first. One batch action only; the single-id advisor twin is
deleted with its int tests migrated. The anti-goal on always-mounted list inputs
is amended for chip-based relations on fine pointers, not reopened.

Also fixes a latent bug: `CampaignTableHead`'s `description` seam broke any RSC
caller, because its two helpers lived in a 'use client' module.
2026-07-27 22:35:19 -03:00
Francisco Ignácio Fontoura Solla
0ee5136b3f Merge branch 'cursor/5fc89e32' 2026-07-27 22:25:10 -03:00
Francisco Ignácio Fontoura Solla
93b58a9b3c Register the E14 closing triage on B32+.
Three debts, no new ID. The fifth JSON control and route arrived without
touching F1 — the level movement is an explicit submit, not a debounce — which
is exactly the escape clause F2/F3 were parked behind, so they stop being
conditional. F2 also changes character: the same-origin check that
`isSameOriginRequest` documents itself as owning is copied into five routes, so
the one that forgets the line fails open. That is not boilerplate and is not
cuttable by appetite.

F5 is B42's own deferred trigger firing: it parked the footer slot on "a second
control needing explicit submit in a Drawer", and this is that control. What the
deferral could not know is that the shell pins "Fechar" while "Registrar
movimento" sits below the fold, so the action that discards a typed rationale is
the reachable one.

Discarded with reasons in the triage: `new Date()` during render (the server
re-runs the rules and a 409 is a designed state), and `levelNote` in the 435-row
select, already written down in E14's own plan next to `politicalTrend.note`,
which has the same shape and must be fixed with it.
2026-07-27 22:24:34 -03:00
Francisco Ignácio Fontoura Solla
adb1773be8 Harden the level control now that it also renders as a Drawer.
The migration put this control on a phone for the first time and it was still
mouse-sized: a 28px submit and checkbox rows with no minimum height, where the
siblings size both for touch. Closing the overlay mid-submit was worse — it
unmounts the Alert and the live region while nothing aborts the request, so a
failure went unreported; it now uses the same `reportFailure` the trend and
estimate cells do.

The rest is what the reviewers found around it: the trigger reports `aria-busy`
like every other overlay caller, `canSubmit` carries the override rule that was
stranded in the JSX, the two-level-jump check reads
`ENGAGEMENT_LEVEL_RULES.maxStepsWithoutShock` instead of a literal so E15 cannot
desync the checkbox from the rule behind it, and the route echoes
`updated.engagementLevel` rather than the requested one — with
`overrideAccess: false` Payload drops a field the actor cannot write instead of
refusing, so the request is not evidence the movement landed.

The cell's state stopped being nullable, which meant splitting it from the
route's success payload: a success always names a level, a row need not have
one. And the control joins `overlayCases`, the table that exists to catch
exactly the kind of container mistake this migration could have made.
2026-07-27 22:17:44 -03:00
Francisco Ignácio Fontoura Solla
8d6c8e3313 Move the level control onto B42's shared cell-edit overlay.
B42 landed on main during this session and extracted `CampaignCellEditOverlay`
— Popover on `md+`, Drawer below it, where a popover fights the virtual
keyboard. The rebase auto-merged E14's "Nível" block into what had become the
Drawer, which compiles and would have opened a popover inside a sheet, so the
control now takes `variant` like its three siblings. The container's Popover
branch is point for point what E14 had hand-written, so the desktop is
unchanged; on touch, where there is no hover to carry it, the trigger now
spells the level out instead of showing the bare numeral.
2026-07-27 21:24:45 -03:00
Francisco Ignácio Fontoura Solla
c7001b75f0 E14: engagement levels N0-N4 and the first allocationDecision writer.
`municipality.engagementLevel` (staff-only, no default) records what the
campaign decided to invest in a município, next to the E8/E10 metrics that
describe what it knows. Moving it is a reallocation decision, so only
unrestricted staff may, every move carries a rationale plus the signals that
would reverse it, and the write files an `allocationDecision` in the same
transaction under an advisory lock taken before the current level is read —
otherwise the snapshot's `from` is a guess. The enum gains `movimento`: a level
move is neither `aceita` nor `descarta`.

No backfill. Null is "Sem nível", and filtering by it is the triage queue.
That is also why the list sorts `nivel` in memory rather than in the query:
Postgres puts NULLs first on DESC, so a native sort would open "N4 primeiro"
on a page of municípios nobody has decided about yet. The filter stays native.

The hysteresis rules from research §6.8 are pure and client-safe, so the
Popover explains a block before submitting and the server re-runs them anyway;
a blocked move answers 409 with typed violations and the override is recorded
with exactly the rules it contradicted.
2026-07-27 21:19:33 -03:00
Francisco Ignácio Fontoura Solla
b839b924fb Merge branch 'cursor/55aaebf7' 2026-07-27 21:12:34 -03:00
Francisco Ignácio Fontoura Solla
a7feb6b26e Register the E13 review debts on the roadmap.
The one finding the /simplify skipped for the wrong reason becomes a fill-in:
the tour composer signals a território change only on the control, and the
pending pieces it dismissed as list-coupled are a Provider, a div and a hook
with a local fallback. The batched goal-account loader stays a trigger on the
E13 plan — the planner and the list diverge on missing data on purpose.
2026-07-27 21:12:05 -03:00
Francisco Ignácio Fontoura Solla
d5b4af2d98 E13: visit eligibility checklist and the tour composer.
Five conditions per município (volume, headroom, rede, janela, encaixe) with
their reason always visible, plus a staff-only composer at
/campanha/atividades/giros that turns an âncora + satélites + semente into draft
activities in one transaction. No `visita` kind and no `tour` entity:
`deputyPresent` already marks the candidate's own agenda.
2026-07-27 21:04:04 -03:00
Francisco Ignácio Fontoura Solla
c9fbdce471 Merge branch 'cursor/367431b3' 2026-07-27 20:54:52 -03:00
Francisco Ignácio Fontoura Solla
2983011bfe Register the B42 review debts on the roadmap.
The aria-live region that unmounts with the overlay becomes B32+ F4, and the
catalog bundle debt in B33+ stops being conditional now that B41 and B42 both
shipped on that route without paying it.
2026-07-27 20:54:12 -03:00
Francisco Ignácio Fontoura Solla
59d89ce287 B42: Drawer quick-edit and a tappable card on the mobile municipalities list.
Tendência, Assessores and Votos estimados now share one container
(CampaignCellEditOverlay: Popover on md+, bottom Drawer below), the card drops
to the table's density, and the whole card navigates through a stretched link
instead of a second "Abrir município" button.
2026-07-27 20:49:41 -03:00
Francisco Ignácio Fontoura Solla
1eb8590e99 Expand suggest-next-roadmap-items to list all parallel candidates.
Drop the 2–4 batch cap so every Pronto/Quase that clears the principal is listed by priority, with sibling collisions annotated instead of omitted.
2026-07-27 10:48:01 -03:00
Francisco Ignácio Fontoura Solla
983d266012 Merge branch 'cursor/eb1e2048' 2026-07-27 10:38:31 -03:00
Francisco Ignácio Fontoura Solla
6de807e288 Note B41 critique deferrals on the parent plan.
Edge fade and sticky-cell hover stay Adiado with explicit triggers.
2026-07-27 10:38:06 -03:00
Francisco Ignácio Fontoura Solla
c35e459e32 Tighten B41 comments after the second simplify pass.
Drop ticket noise and clarify that overflow-x-auto is already Table's default.
2026-07-27 10:37:25 -03:00
Francisco Ignácio Fontoura Solla
6c0c5e87ce B41: horizontal scroll and sticky Município on the municipalities list.
Mirror TerritoryList so wide staff columns can scroll on md+ without losing the row identity.
2026-07-27 10:16:16 -03:00
Francisco Ignácio Fontoura Solla
22135c0a31 Merge branch 'cursor/ab035786' 2026-07-27 10:04:52 -03:00
Francisco Ignácio Fontoura Solla
b6a8b64a66 Registra B32+ (débito de auto-save pós-B32)
capture-review-debts do fechamento do B32: MunicipalityListExpectedVotesControl
(B27), MunicipalityListTrendControl (B24) e LeadershipListSupportStatusControl
(B32) são o 3º call site do mesmo scaffold de auto-save por debounce, gatilho
já registrado nos três planos. Fill-in de engenharia com auditoria antes de
extrair (precedente B33+): docs/plans/escala-dry-pos-b32.md.

Outros achados do /simplify (flush-on-close, comentário/summary morto em
SupportStatusBadge) já foram corrigidos no commit do B32; null→a_abordar e o
findByID extra em updateLeadershipInternal são decisões já documentadas,
descartadas desta triage.
2026-07-27 10:04:30 -03:00
Francisco Ignácio Fontoura Solla
211276b4e4 B32: editar Status de apoio na célula da lista de lideranças
- Popover + NativeSelect sempre montado na coluna Status de
  /campanha/liderancas, sem toggle de modo de edição
- Auto-save (~150ms) via POST /campanha/liderancas/support-status,
  reusando isSameOriginRequest/campaignJsonMutationRoute (B24/B27),
  sem revalidatePath
- Extrai leadershipStaffEditSafeMessages (adotado também em
  liderancas/formActions.ts e nova/formActions.ts) e
  utilities/leadershipLabels.ts (3º call site de supportStatusLabels,
  agora fonte única também para SupportStatusBadge)
- Novo e2e tests/e2e/campaignLeaderships.e2e.spec.ts; setup.e2e.spec.ts
  prewarma as rotas novas
- Fecha o Adiado com gatilho do B29; docs/plans, notebook e roadmap
  atualizados

Gate: tsc/lint/format/knip(P3 pré-existente)/check:cycles/unit+int/e2e
verdes; two /simplify passes (labels/estado duplicado/isSupportStatus;
flush-on-close ao fechar o popover, comentário órfão de summary/
campos summary mortos removidos de SupportStatusBadge).
2026-07-27 09:56:09 -03:00
Francisco Ignácio Fontoura Solla
e712397241 Merge branch 'cursor/f95cf56d' 2026-07-27 09:39:42 -03:00
Francisco Ignácio Fontoura Solla
bcb58c2162 B33+: close the /simplify rounds on the shared list filter hook
Follows the URL when the committed query changes with no debounce pending, so
Back/Forward and the empty state's clear anchor update the search box without
overwriting text the user is still typing. That removed the last reason for
/campanha/municipios to remount its filters, so the key= (and its now orphaned
buildMunicipalityFiltersKey) is gone and the three lists share one mounting
policy — the dedup guard was dead code on municípios while it remounted.

Reuse: setSearch + scheduleSearch were always called as a pair, so they collapse
into onSearchChange (hook surface 8 -> 6 names); MunicipalityFilters reuses the
buildMunicipalityFilterHref it was reimplementing inline; dobradinhas gains the
sibling buildStateDeputyFilterHref for the four sites pinning page 1 by hand;
municípios gains the active-filter summary its mobile block was missing.

Also drops an export with no consumer that was failing knip in CI (seo.ts).
2026-07-27 09:39:15 -03:00
Francisco Ignácio Fontoura Solla
abe37cbc5c B33+: shared list filter navigation hook
Extract the debounce/dedup/transition scaffold that StateDeputyFilters,
TerritoryFilters and MunicipalityFilters each reimplemented into
useCampaignListFilterNavigation, and unify the two behaviors that had
silently diverged: touching a filter mid-typing used to discard the
pending search on dobradinhas and territorios, and the debounce timer
captured a stale state, so a sort clicked within its window was reverted
a second later. Also folds the six copies of the pending-transition
fallback into useCampaignListTransition and drops a redundant aria-live
that made the municipality summary the third live region on its page.
2026-07-27 09:05:08 -03:00
Francisco Ignácio Fontoura Solla
862b717250 Merge branch 'cursor/8ffd4f41' 2026-07-27 08:58:43 -03:00
Francisco Ignácio Fontoura Solla
3892c2e0a4 Add remembered campaign sessions
Keep campaign JWT and cookie lifetimes aligned while revoking the active server session on logout.
2026-07-27 08:57:26 -03:00
Francisco Ignácio Fontoura Solla
126b1abae8 Merge branch 'cursor/a1a74a92' 2026-07-27 08:35:23 -03:00
Francisco Ignácio Fontoura Solla
edd2172bce Format F4b plan and TECH-DEBT after Prettier check. 2026-07-27 08:34:57 -03:00
Francisco Ignácio Fontoura Solla
b9d697373c Document F4b simplify follow-ups and deferred form-action guard triggers. 2026-07-27 08:34:30 -03:00
Francisco Ignácio Fontoura Solla
98c334785e Tighten formActions guard: allowlist real exceptions and require call sites.
Also rethrow non-FormDataBoundaryError from the activity lifecycle id pre-parse.
2026-07-27 08:33:41 -03:00
Francisco Ignácio Fontoura Solla
3c162c5250 Fix leftover rebase conflict markers in roadmap and project notebook. 2026-07-27 08:33:41 -03:00
Francisco Ignácio Fontoura Solla
440c15f132 C8 F4b: widen formActions guard and migrate sibling ladders.
Cover *FormActions.ts (+ taskActions allowlist) so route siblings cannot drift past the shared wrappers; migrate activity/municipality ladders onto runCampaignFormAction.
2026-07-27 08:33:41 -03:00
Francisco Ignácio Fontoura Solla
fb2af739fc Merge branch 'cursor/6b8a5e62' 2026-07-27 08:30:58 -03:00
Francisco Ignácio Fontoura Solla
753eae388f Teach rebase-on-main to auto-handle dirty trees via commit or stash. 2026-07-27 08:30:49 -03:00
Francisco Ignácio Fontoura Solla
423aa42185 Merge branch 'cursor/7046b47e' 2026-07-27 08:21:48 -03:00
Francisco Ignácio Fontoura Solla
7eb3ca20a6 Format the metadata resilience plan for Prettier. 2026-07-27 08:21:25 -03:00
Francisco Ignácio Fontoura Solla
fa75c355a0 Make public SEO resilient when the metadata global is empty.
resolveSiteMetadata fails open on missing URL/text so next build no longer crashes on a fresh or poisoned metadata cache.
2026-07-27 08:20:33 -03:00
Francisco Ignácio Fontoura Solla
4bbd526dfd Merge branch 'cursor/58bad531' 2026-07-27 02:04:47 -03:00
Francisco Ignácio Fontoura Solla
9afa474879 Register the B8+ review debts from capture-review-debts.
Dos 20 achados do /simplify, 12 entraram na entrega e 3 ja estavam
cobertos (o modo `territory` pertence ao E12 por rabbit hole do plano, a
shoelace duplicada e descarte declarado, e o B5 F2 ja contava os 5 call
sites dos helpers de CLI). Sobraram dois fatos sem registro.

O revisor de perf mediu que `quantize` aplica um unico numero de digitos
aos dois eixos: a grade de ~103 m e exata em longitude e a latitude segue
o aspect ratio do bbox (102,8x109,2 m no estado, 102,9x80,1 m em
Salvador). Corrigir exige `{scale, translate}` explicito e reescrever os
tres artefatos, perdendo a prova de byte-identidade da malha estadual por
um erro de ~9% num eixo num mapa que para em maxZoom 10 — entao vira
gatilho no plano-pai, nao item de roadmap.

E o B8+ respondeu por precedente uma pergunta em aberto do B5 F2: com
`scripts/lib/topology.mjs`, o helper de cache da F2 pousa em
`scripts/lib/` em vez de abrir um segundo endereco. A recomendacao antiga
(`src/lib/cliCachedDownload.ts`) fica rejeitada: `src/lib/` e client-safe
por contrato e nao e lugar de codigo que faz mkdir/fetch/writeFile.

Sem novos IDs de roadmap.
2026-07-27 02:04:09 -03:00
Francisco Ignácio Fontoura Solla
2c30944593 B8+: escala das malhas em unidades reais, contrato do map key e degradacao
F1 — a tolerancia de simplificacao e os digitos de quantizacao viraram
politica em unidades de solo em scripts/lib/topology.mjs, consumida pelos
dois scripts de geometria. Os quantis 0.35 copiados cortavam as 19 ZE de
Salvador a ~0.6 m2 e os 417 municipios a ~1.78 km2; com tolerancia
absoluta e a grade de ~103 m do estado, a malha das zonas caiu de 480 para
30.2 pontos/feature. bahia-municipalities segue byte-identico e o tripwire
de bytes deu lugar a pontos/feature.

F2 — featureMapKey passou a ser asserido sobre features reais das tres
malhas comprometidas (417 codarea, 27 code, 19 slug), e o comentario de
MAP_KEY_PROPERTIES agora diz o que o codigo faz: as propriedades sao
mutuamente exclusivas, nao ordenadas por especificidade.

F3 — a malha das zonas e um detalhe sobre o mapa, nao o mapa: se o chunk
falha, Salvador cai no poligono municipal que ja esta desenhado embaixo e
o resto da Bahia continua funcionando, com o motivo logado. A ressalva de
aproximacao saiu do gate de comparacao e passou a depender de
bundle.hasZoneMunicipalities, derivado no servidor de municipality.kind.

F4 — orfaos deletados: os dois acessores sem chamador, isZoneMapKey (o
servidor ja sabe a regra), o wrapper simplifyTopology e o helper
polygonCount com um branch inalcancavel. MunicipalityMapNavigation
colapsou em string | undefined e MunicipalitiesByMapKey em
Record<string, string>.
2026-07-27 01:59:36 -03:00
Francisco Ignácio Fontoura Solla
dd8c4d1d27 Fix Prettier formatting in roadmap after users.roles docs update. 2026-07-27 01:54:58 -03:00
Francisco Ignácio Fontoura Solla
bb5ac362ed Add users.roles so Payload /admin can separate editors from admins.
Editors publish post/tag/media without PII or campaign access; existing users backfill as admin.
2026-07-27 01:54:29 -03:00
Francisco Ignácio Fontoura Solla
a5b6341ccb docs: plan fill-in to drop zone list from campaign home map
After B8 F2 polygons, the textual zone breakdown under the dashboard map is redundant; register the removal in the roadmap and align B8+/B8 plan notes.
2026-07-27 00:00:29 -03:00
Francisco Ignácio Fontoura Solla
75cdea38b4 Register the B8 F2 review debts as B8+.
The three /simplify reviewers reported after the merge, so their findings
became a fill-in instead of a fix. The lot is anchored on a measurement that
contradicts "small mesh, small artifact": the 19 Salvador zones carry 9,120 arc
points against 9,139 for ALL 417 Bahia municipalities, because both simplify
constants are relative to the file's own bbox and were copied verbatim from the
sibling script -- the same "1e4 grid" is ~103 m statewide and ~3.9 m in a city
26x narrower, on a map that stops at maxZoom 10.

That measurement inverts one reviewer's recommendation, which is why it is
written down rather than left to be re-derived: extracting the shared constants
as they stand would cement the defect and give it the authority of a policy, so
what gets shared between the two build scripts is the ground scale.

The other three: nothing runs featureMapKey over a real feature outside the
e2e, so the three spellings of the map key can drift and paint grey with no
error anywhere; Promise.all over both meshes plus the memoized rejection turned
a 19-polygon chunk into a single point of failure for the whole map, where the
municipal mesh alone used to suffice; and the "approximate, not the official TSE
boundary" caveat hangs off the zone LIST's render condition, so it vanishes in
comparison mode while the approximate mesh is still painted.

Also records what this delivery orphaned (getTerritoryFeature lost its last
production caller when fitMapToHighlights stopped consulting geometry modules,
and knip cannot see it because specs are entry points), and bumps B5 F2's
call-site counter to five.
2026-07-26 23:35:49 -03:00
Francisco Ignácio Fontoura Solla
1d3ec3c772 Merge branch 'cursor/a2a2fd95' 2026-07-26 23:26:15 -03:00
Francisco Ignácio Fontoura Solla
7b70322118 Document B8 F2 and apply the simplify pass findings.
Records the Salvador zone delivery where the next agent will look for it —
roadmap, plan as-built, notebook and the geometry/map sections of AGENTS.md —
including the two measurements that are easy to re-derive wrongly (the bay
water in Salvador's municipal polygon, and ZE 16 wrapping ZE 14, which is why
neither an area ratio nor a centroid check is a valid overlap test).

Simplify fixes: the build script re-indexes simplified geometries by a unique
id instead of neighbourhood name, so a repeated name in a future mesh cannot
silently collapse two polygons inside a committed artifact; a stale comment
claiming zone polygons do not exist; and two int assertions that the Fase 0
catalog hydration turned tautological.

Ledgers the Aikido outage: the scanner returns Opengrep exit 2 for any input,
so the mandatory per-change scan was skipped, not passed.
2026-07-26 23:25:42 -03:00
Francisco Ignácio Fontoura Solla
84e796ab49 B8 F2: polígonos das ZE de Salvador e re-keying do coroplético por map key
Salvador era um polígono só carregando os votos da cidade inteira, então o
coroplético não conseguia responder "qual pedaço de Salvador está fraco / é
meu". As 19 ZE ganham polígono próprio, dissolvido da malha de bairros do IBGE
(Censo 2022) pela lista de circunscrição da RA 02/2017 do TRE-BA, e o mapa passa
a pintar por map key (slug para as zonas, ibgeCode para o resto) em vez de
codarea. O polígono municipal de Salvador continua desenhado por baixo, sem dado
e sem interação, para não abrir buraco no contorno do estado.

Aproximação declarada na copy e no cabeçalho do script: o TRE publica a
circunscrição como lista de bairros, então o bairro é o átomo geométrico —
Periperi, que a resolução corta na Rua das Pedrinhas, vai inteiro para a ZE 17.
competitiveRank segue por cidade (o artefato TSE só tem federalRankByIbgeCode).

Sem migration, sem collection, sem action, sem Consent.
2026-07-26 23:08:09 -03:00
Francisco Ignácio Fontoura Solla
2ac3a3e512 Fix Prettier on B40/B41 plan docs. 2026-07-26 22:17:04 -03:00
Francisco Ignácio Fontoura Solla
6fc3bd04b2 Register B39–B42: session persistence, biometrics, and municipality list UX. 2026-07-26 22:15:39 -03:00
Francisco Ignácio Fontoura Solla
f4e7b4f1e0 Merge branch 'cursor/3907847e' 2026-07-26 21:27:45 -03:00
Francisco Ignácio Fontoura Solla
a911c7191f chore: format after E12 (pre-push gate). 2026-07-26 21:27:23 -03:00