O commit OPS33 (f0f5b73f) entrou com o arquivo já conflitado na região do
bullet 'Per-worktree environments' (<<<<<<< HEAD indentado + lado HEAD
pré-OPS33 com artefato '- # ' + fechamento corrompido '> > > > > > >'; o
======= foi removido no próprio commit) e o OPS37 herdou o estado.
Resolução perda zero validada por diff: manter o lado superset (OPS28 +
OPS31 + OPS33 — claim determinístico, --issue N, prompt citado) e
descartar o lado HEAD; -3 linhas no total; scan de todos os trackeados
confirmou AGENTS.md como único arquivo afetado.
Guardrail: describe 'no conflict markers in committed files' em
codebaseConventions.unit.spec.ts — varre git ls-files e falha em
<<<<<<< / >>>>>>> (tolerante a indentação) e na forma corrompida
^\s*(> ){7}; validado negativo contra o estado sujo (AGENTS.md:37, 40).
next dev resolve a porta antes de @next/env carregar env-files (commander
.env('PORT') no bin/next), então o PORT=3100+slot do provisionador de
worktree era ignorado e todo dev server subia em 3000. pnpm dev/devsafe
passam por scripts/dev.mjs: preflight do guard-dev-db inline, env com a
mesma precedência (real > .env.local > .env, override:false) e porta como
flag CLI -p (resolveDevPort/nextDevArgs em scripts/lib/cli.mjs,
unit-testados). Sem PORT → nenhum -p → default 3000 + allowRetry intactos.
Claim extraído para scripts/lib/agent-github.mjs (claimIssue/claimBriefLines/
claimQueueEntry/claimTargetVerdict), agent-claim.mjs vira CLI fino com mesma
saída; branch validada antes do claim (sem claim órfão); hint de falha
pós-claim; guard -B contra reset de commits de sessão; diretiva launch com
prompt citado + tokenização xargs no worktree.sh; --issue sem valor/posicional
morrem com motivo (sem misclaim); unit novos (directive com issueNumber,
claimQueueEntry, claimTargetVerdict); docs/textos (AGENTS.md, /worktree, skill
worktree-next-issue, AGENT-OPS, CHANGELOG).
- scripts/worktree.mjs: provision() aplica migrate + db:seed:minimal nos
bancos dev e test (paridade com a CI, que roda o par antes do e2e);
--no-migrate pula os dois (o seed depende do catálogo migrado); fallback
sem Docker também migra+seeda o teqo/teqo_test compartilhado (com warning
de sobrescrita dos pins).
- Guard de regressão tests/e2e/seedParity.setup.e2e.spec.ts no projeto setup:
falha cedo com mensagem clara se o banco de teste não estiver seedado;
evidência = users do seed (MINIMAL_CAMPAIGN_USERS), não os pins de priority
— o cleanup dos fixtures e2e reseta priority por round-robin persistente.
- scripts/lib/test-affected-core.mjs: scripts/worktree.mjs em HIGH_RISK_EXACT.
- Docs: AGENTS.md, skill local-database, CHANGELOG-AGENTS.md, impl plan.
Closes#584
next/plan imprimem 'cd <dir>' na última linha por padrão; --stay suprime;
--go segue aceito como no-op. kill imprime 'cd <main>' como última linha
(depois do drop dos bancos) e recusa --stay. Superfícies: worktree.sh
aplica o cd por padrão; comando /worktree do opencode idem. Docs
(AGENTS.md, skills, CHANGELOG) atualizadas.
Production feed generation failed with a generic message because
NEXT_PUBLIC_SITE_URL was missing on Vercel production: buildFeedUrl calls
getCampaignInviteBaseURL(), which throws in NODE_ENV=production without the
env (intentional fail-closed), so the action catch returned 'Não foi
possível criar o feed de calendário.' and nothing was created. The local
real-flow verification (login -> agenda without filters -> header icon ->
name -> generate -> link /campanha/agenda/ical/<secret> -> iCal 200 with
the activity) proved C92/C93 work; the divergence was the environment.
Configured NEXT_PUBLIC_SITE_URL=https://pt.jorgesolla.com.br in Production
(+ checked Preview) and redeployed.
New e2e campaignAgendaFeed pins the real flow: desktop (header -> dialog
-> generate -> link responds iCal with the fixture activity) and mobile
(FAB -> sheet -> generate). The e2e fixture now owns calendarFeed rows
(createdBy NOT NULL made the campaignUser cleanup transaction abort).
AGENTS.md deploy checklist notes NEXT_PUBLIC_SITE_URL as mandatory in
Production.
Closes#483
Cada 'pnpm worktree plan' agora deriva um branch próprio: plans/plan-issue-<bag>
quando um bag é passado (sufixo -2/-3 em colisão) ou o próximo sequencial
plans/plan-issue-<n> quando não. O prefixo minúsculo plans/… nunca colide com o
branch <Code>-<slug> de 'next'. planBranchName (scripts/lib/worktree.mjs)
unit-testado; docs e shell/command atualizados.
Novo subcomando `pnpm worktree plan [--go] [--no-migrate]` cria/reutiliza o
worktree de planejamento no branch fixo `plans/plan-issue`
(`PLAN_WORKTREE_BRANCH`), sem vínculo com a fila de claim: um `next` posterior
da próxima Issue nunca colide em branch nem em slot (slot derivado por hash do
branch, bumpado entre worktrees vivos). Mesmo provisionamento isolado do
`next` — porta 3100+slot, bancos teqo_wt<slot>/teqo_wt<slot>_test, migrations.
- scripts/worktree.mjs: cmdPlan + provision() com purpose/issue opcionais
- scripts/lib/worktree.mjs: constante PLAN_WORKTREE_BRANCH
- tests/unit/worktree.unit.spec.ts: derivação de slot do plano
- docs: .opencode/commands/worktree.md, shell, skills, AGENTS.md, changelog
pnpm worktree next [--go] [--no-migrate] agora provisiona um ambiente
determinístico por worktree: slot = parte numérica do código da Issue
(C15→15; hash sem dígitos; bump em colisão entre worktrees vivos), dev
server em 3100+slot, bancos teqo_wt<slot> / teqo_wt<slot>_test no
container compartilhado (-p teqo) com a cadeia de migrations aplicada;
escreve .env.local + .env.test.local (gitignored, marcador gerado; envs
manuais nunca sobrescritos) e copia PAYLOAD_SECRET/Blob/VAPID do main.
kill remove o worktree e dropa os bancos gerados (WITH FORCE, best-effort).
Suites leem .env.test.local depois de .env.test (vitest.setup /
playwright / db-doctor / gate-ci espelham o layering) e
assertTestDatabase aceita teqo_test ou teqo_<worktree>_test em localhost
só (fail-closed preservado). db:start/db:stop fixam -p teqo (um
container de qualquer diretório); max_connections 200→400. Sem Docker
(Cursor Cloud): degrada ao compartilhado com aviso.
Derivação pura em scripts/lib/worktree-env.mjs (unit-tested); fluxo
validado end-to-end: provision → migrate → 598/598 int no banco do
worktree → kill sem resíduo.
Agents were treating "locked decisions" and the 3+ call-site rule as a ban on changing prior code. Reframe product constraints, prefer editing the owner over twins, and mark the campaign notebook as changelog-only.
Remove stage/promote/Neon smoke. PR CI targets main; ci.yml full-suite
verifier deploys via vercel CLI and requeues when HEAD moves. Pool uses
POOL_GITHUB_TOKEN and POOL_BASE_REF=main. Docs/skills/rules retargeted.
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.
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.
Bookmarks the canonical list recorte beside Limpar, syncs via useSyncExternalStore, and matches active URLs with a pure href comparator so the app layout never imports the heavy URL serializer.
WebAuthn platform passkeys: opt-in enrollment after a password login, a
password-less sign-in that mints the same 14-day campaign-token, and
revocation from the profile. The session is minted through Payload's public
pieces (checkLoginPermission, addSessionToUser, getFieldsToSign, jwtSign)
inside a transaction with an advisory lock, so a passkey login neither
bypasses a lockout nor evicts the account's other sessions. The rpID is
derived from NEXT_PUBLIC_SITE_URL and resolves to null when the request host
disagrees, so previews never offer a ceremony the browser would refuse.
The /simplify triage produced two live debts, and both are about the same pair
of functions the B34+ plan already names in F3, so they landed as F10 of that
plan rather than as an escala-dry-pos-b37: the município-side batch actions
write one município at a time (findByID + update each — ~870 round trips at the
435 ceiling B37 itself raised, but ~52 at the largest real TI, and no test above
a batch of 2), and the two are twins at 2 call sites, one short of the bar. The
common trigger is a "select all"/multi-TI affordance or a third batch on that
side, and F10 records why the bulk-SQL rewrite is not obviously the answer: it
would hand back the scope check that overrideAccess: false gives for free.
This delivery also closed B34+ phases outright, which is worth reading in the
plan rather than inferring from a diff. F2 is done and its prediction was wrong
in a way worth keeping: it stated "o B37 não retira este débito", and B37 read
the same evidence the other way — the ~165 measured lines were not deduplicated
into useChipRowOverflow, they were deleted when both cells became wrappers of
one engine. F9 is done via scopedPortfolioIndex. Half of F3 is done (the three
single-id deltas), and the half that stayed out — the set-with-floor form —
stayed out because seeing the collapsed code confirmed the plan's own inverted
recommendation. F5 and two F6 bullets shrank for the same reason: "written twice
across the two surfaces" stopped having two surfaces.
Also corrects a comment I got wrong in the previous commit: the batch's sort
does not carry the deadlock-avoidance guarantee, since acquireTextAdvisoryLocks
sorts its own keys (C8 batched them into one round trip); it only makes the
apply order stable.
The "Municípios" column of /campanha/dobradinhas reuses
MunicipalityPortfolioCell unchanged, writing the inverted side of the
relation (municipality.stateDeputies) through a single transactional
batch action (setStateDeputyMunicipalitiesBatch): per-município advisory
lock, all-or-nothing on a partial-scope batch instead of a silent partial
apply, no floor (a dobradinha can hold zero municípios). stateDeputiesArraySchema's
cap moved 20 → 435 to fit a whole território batch from the município side.
The post-implementation critique found two P1s, and both were fixed instead
of deferred. First: MunicipalityPortfolioCell and LeadershipStateDeputyRelationCell
had grown the same chip-editing machine twice — optimistic delta, undo, ARIA
1.2 combobox, floor/ceiling, pointer-fine/coarse split — with incompatible
contracts. This was the 3rd call site B34+ was waiting for, so RelationChipCell
is now the single engine and both cells shrank to thin domain wrappers (chip/
search building, FormData, copy — nothing interaction-specific left in either).
LeadershipStateDeputyRelationCell also gained an ownerName prop and its first
dedicated test suite, which it shipped without before this.
Second: an advisor could see, and attempt to remove, a município chip outside
the portfolio they administer, failing silently at the document level.
MunicipalityPortfolioCell now filters the visible chips by addableIds instead
of only gating what can be added, so an advisor sees and can only touch
municípios they administer.
No migration, collection, or Consent. Docs updated: plan as-built, roadmap,
AGENTS.md, project notebook.
The four *SortableHead islands already fall back to their sort-label record,
so B17's new `label` made the header text a third copy instead of removing the
second one — 16 redundant `children` deleted, leaving only `frescor`, which
diverges on purpose and now reads the record too.
The 3 s "safety net" timer was committing like a close, refresh included, which
recreated the per-toggle route render the 400 ms debounce was rejected for: at a
reading pace every checkbox landed outside the window. The timer now writes the
cookie only; closing repaints. A session that ends where it started refreshes
nothing, and a menu open when the route unmounts saves from the cleanup, since
Radix reports no close event for a sidebar navigation. Both pinned red first.
Also: `columnVisibility` required rather than optional for the tests' benefit,
the redundant per-list cap dropped, `resolveVisibleColumns` no longer copies,
and B29's unused `LeadershipListAccessFilter` export un-exported — knip was
failing on main too.
The picker shipped as a capability of CampaignTable rather than a municípios
feature, so every list that already uses the shared table got it: pass
`columnVisibility` and the table mounts the menu and filters the columns.
Persistence is a cookie, not localStorage, and the reason is a type rather
than a preference: CampaignTableColumn.cell is a function, so the column array
never crosses the server/client boundary and the client cannot rebuild the
table without it. The server filters, so a hidden column does not reach the RSC
payload either. `campaign_columns` carries a listId -> hidden ids map in a
compact form with no JSON and no percent-encoding, because document.cookie and
Next's cookies() disagree about decoding.
Closing the menu is the commit. The first cut debounced at 400 ms, which
batches a burst rather than a reading pace, so unchecking four columns while
reading their labels was four full-route refreshes — on /campanha/liderancas
each one re-pays the ~229 ms / 371-statement render already ledgered P1. The
timer survives at 3 s as a safety net for a menu left open; the price is that
the table behind the menu no longer updates per checkbox.
`defaultVisible` is gone: it was true on 10 columns and false on none, and
storing only the hidden set is what makes a newly shipped column visible to
everyone who never touched the picker. In exchange `label` is required, which
made `head` optional — 24 columns had a head that only restated the new label.
Each surface declares one mandatory column, re-filtered on the server so a
stale cookie cannot leave a row without its only linked cell.
The bug review caught is this codebase's recurring one: the RSC payload of
toggle A arrives inside the window of toggle B, and adopting the server there
undid B and then committed the set without it. Reconciliation now runs only
when no write is pending, pinned red against the previous code.
Municipality column ids are typed against MunicipalityListColumnId now, so the
id, the B22 description and the picker label are one key; the union member
`nivel` was renamed `level` to match the id and the ?level= filter. The
picker's own content comparison is sameIdSet, widened to string ids.
`campaignJsonMutationRoute` now builds every JSON POST route under /campanha —
`isSameOriginRequest` had been copied into five of them, so the CSRF policy was
failing open by omission; a convention guard sweeps all of `src/app` and names
its three exceptions. `postCampaignJson` is the only place that spells
`credentials: 'same-origin'`, and `useCampaignCellAutosave` is the debounce /
abort / revert / flush-on-close machine the quick-edit cells shipped three
times.
The shell keeps the `aria-live` region outside the Popover/Drawer, since closing
is what commits and it was unmounting the node that would announce the result;
`variant="sheet"` took a `footer` slot, which let the signal control drop the
overlay it only kept for one button.
Two bugs the extraction made visible: a revert issued while a different payload
was in flight was dropped silently, and `change()` never cleared a past failure,
which outranks everything the live region would otherwise say.
The Drawer half of MunicipalityPortfolioCell now goes through
CampaignCellEditOverlay: the container switches on a `variant` prop, not on
viewport, so the pointer policy stays in CSS while the cell inherits
`initialFocus` (the hand-rolled sheet focused the first chip, a link out of the
row being edited) and the ARIA trigger attributes every other cell has.
The optimistic baseline bug was back in a shape that reads as correct. Applying
the delta functionally is not enough when the fallback is not: `current ??
municipalityIds` still read the render that created the closure, which is the
branch an undo always takes, since the server reconcile clears the optimistic
value first. Undo after a later add rebuilt the row from a set that predated it,
permanently. The baseline is a ref now, advanced synchronously in the draft
branch, and pinned by a spec verified red against the old code.
Also: revert the Drawer body gate (its saving measured as zero, and it animated
an empty box out of view), collapse the word-start matcher to two string tests
now that both sides are normalized, copy the memoized território ids before
handing them to a caller, and revalidate every changed município of an advisor
batch instead of only the last.
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.
`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.
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.
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>.
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.