mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-13 15:13:07 +00:00
- Remove .claude/skills (symlinks) e a árvore .cursor (rules, hooks, skills) - Adiciona .agents/ com skills (+ references/), rules/, hooks/ e scripts - Adiciona .codex/hooks.json (hook do impeccable) - Corrige referências .cursor → .agents em environment.json, hooks.json e AGENTS.md
50 lines
6.8 KiB
Text
50 lines
6.8 KiB
Text
---
|
|
description: Mapa navegacional do repositório Teqo — onde cada coisa vive e em que direção o código depende. Atualize no MESMO PR que mover módulos (par com docs/ARCHITECTURE.md).
|
|
alwaysApply: true
|
|
---
|
|
|
|
# Codebase map (Teqo)
|
|
|
|
Conceitos e decisões: `docs/ARCHITECTURE.md`. Operação (setup/seeds/deploy/segurança Payload): `AGENTS.md`.
|
|
|
|
## Direção de dependência
|
|
|
|
`lib/` (puro, client-safe) → `utilities/` (Payload/Next; loaders com `import 'server-only'`) → `components/` → `app/`. `lib/` nunca importa `utilities/` — agora enforcement programático: ESLint `no-restricted-imports` em `src/lib`, `pnpm check:cycles` (madge, 0 ciclos) e `codebaseConventions.unit.spec.ts` (exige `server-only` em utilities acoplados ao Payload). Client components importam de módulos server só `import type` — valores compartilhados vivem em módulos de contrato (`municipalityMapContract`, `votePledgeViews`, `lib/campaignRoles`, `lib/campaignConsentKeys`).
|
|
|
|
## Onde vive o quê
|
|
|
|
- `src/app/(frontend)` — site público (ISR, tag `posts`); actions públicas em `(frontend)/actions/*`.
|
|
- `src/app/(payload)` — admin `/admin` (auth `users`).
|
|
- `src/app/(campaign)/campanha` — ferramenta interna (auth `campaignUser`, cookie `campaign-token`; barreira no layout de `(app)`). Server actions em `campanha/actions/*`; `formActions.ts` por rota são cascas sobre `runCampaignFormAction` / `runCampaignRedirectFormAction` (`utilities/campaignFormActionError.ts`).
|
|
- `src/collections/` + `src/globals/` — modelo de dados Payload; TODA collection declara `access`; schema muda só por migration (`pnpm migrate:create`).
|
|
- `src/utilities/access/` — RBAC por domínio; `campaignAccess.ts` é o barrel de re-export; predicados de role client-safe em `src/lib/campaignRoles.ts`.
|
|
- `src/utilities/<domínio>/` — subpastas de domínio (P3-J, gatilho D1): `municipality/` (incl. `campaignMunicipalityScope`, `goalCoverage`), `supporter/`, `territory/` (incl. `loadTerritoryOverview`), `leadership/`, `visit/`, `webauthn/` (`campaignWebAuthn*`). Imports diretos, **sem barrels**. O top-level de `src/utilities/` é **pinado** (`codebaseConventions.unit.spec.ts`): módulo novo top-level falha o build até ser registrado — a 3º módulo novo de um domínio no mês, o domínio ganha subpasta. Helpers puros que estavam na camada errada moram em `lib/` (`campaignTime`, `relationship`).
|
|
- `src/components/campaign/<domínio>/` — municipality, activity, supporter, leadership, advisor, demand, organization, stateDeputy, votePledge, invite, map, dashboard, auth, shell (sidebar/nav/PWA), shared (sistema de listas + widgets genéricos).
|
|
- `src/components/ui/` — kit shadcn da casa (sem peças mortas; `shadcn add` re-adiciona).
|
|
- `src/lib/schemas/` — zod de entrada; `src/lib/` também guarda catálogos estáticos e o artefato TSE (`electionAggregates/`).
|
|
- `src/migrations/` — história congelada; nunca editar retroativamente.
|
|
- `tests/` — `unit` (DATABASE_URL inválida), `int` (Payload real em `teqo_test`, fixtures em `tests/helpers/campaignFixtures.ts`), `e2e` (Playwright smoke).
|
|
|
|
## Sistema de listas (Pass 2 W1 — use, não reinvente)
|
|
|
|
- URL: `utilities/campaignListUrl.ts` (parse/canonicalize/`resolveListUrl`) + módulo por domínio (`municipalityListUrl.ts`, `supporterUi.ts`, `activityUi.ts`, parsers nos `*Data.ts`). Contrato de URL congelado (B18).
|
|
- Tabela: `shared/CampaignTable.tsx` — colunas como dado (`id`, `label` **obrigatório**, `head` opcional (default = `<CampaignTableHead>{label}</CampaignTableHead>`; declare só para alinhamento, `description` do B22 ou ilha de head), `cell`, `mandatory`); heads ricos = ilhas de domínio sobre `shared/CampaignSortableHead` + `shared/CampaignHeaderFilterPopover` (multi-seleção: opções marcadas sobem ao topo via `lib/listFilterOptions.ts` — snapshot na abertura; gate = `row.checkbox`; single-select intocado). Seletor de colunas (B17): passe `columnVisibility` — **um** valor, devolvido por `readCampaignColumnVisibility(listId)` (`utilities/campaignColumnVisibilityCookie.ts`, `server-only`) em quem renderiza a tabela — e ela monta o `CampaignColumnPicker` e filtra por `resolveVisibleColumns`; regra pura em `lib/campaignColumnVisibility.ts`. Só o conjunto de **ocultas** é guardado (`defaultVisible` não existe): coluna nova nasce visível.
|
|
- Shells: `CampaignSearchForm`, `CampaignFilterChips`, `CampaignListFooter`, `CampaignListEmptyState`, `CampaignListPagination`, pending compartilhado em `CampaignListPending.tsx` (dima o resultado, não o controle).
|
|
- Filtros salvos (B18) — só municípios: `utilities/municipalitySavedFilters.ts` (`localStorage`, identidade = href canônico, ordem alfabética) + hook `shared/useMunicipalitySavedFilters` + `municipality/SaveMunicipalityFilterControl` (barra) + `shell/MunicipalityNavSavedFilters` (submenu). O casamento URL↔salvo é `lib/listQueryMatch.ts` e **tem de continuar puro**: a sidebar está no layout de `(app)`, então importar o serializador de URL do domínio ali custa ~21 kB em toda rota `/campanha`.
|
|
- Relação por chips numa célula: `shared/MunicipalityPortfolioCell` (B34) — sempre montada em `pointer-fine`, Drawer em `pointer-coarse`; costuras `minItems`/`maxItems`/`addableIds`/`draft`/`commitAction`. Serve lideranças e assessores; não escrever uma terceira. Lógica pura em `lib/municipalityPortfolio.ts`, índice em `utilities/municipalityPortfolioIndex.ts`.
|
|
- Exceções documentadas: atividades = cards; comparação de candidatos, LeaderContacts e preview de import ficam fora.
|
|
- `src/lib/formData.ts` é o único módulo de `lib/` marcado `server-only` (FormData é fronteira server); todo o resto de `lib/` é client-safe.
|
|
- Detalhe por tabs: `utilities/detailTabUi.ts` (factory) + sections RSC colocadas na rota (`municipios/[slug]/MunicipalityDetailTabs.tsx`, `atividades/[slug]/ActivityOverviewTab.tsx`).
|
|
|
|
## Achar o dono de uma feature
|
|
|
|
1. Rota em `src/app/(campaign)/campanha/(app)/<área>/page.tsx` → 2. loader `utilities/<área>Data.ts` ou `*PageData.ts` → 3. view models (`*ViewModels`/`*Views`) → 4. componentes em `components/campaign/<domínio>/` → 5. escrita em `campanha/actions/<área>.ts` (+ `formActions.ts` da rota) → 6. acesso em `utilities/access/<domínio>.ts` → 7. pins em `tests/unit|int` com o mesmo prefixo.
|
|
|
|
## Invariantes que nunca se violam
|
|
|
|
- Local API com `user` sempre com `overrideAccess: false`; bypass admin só com comentário justificando.
|
|
- Escrita multi-collection = transação (`withPayloadTransaction`, `req: { transactionID }`).
|
|
- `leader` é lockdown; view models são por papel; liderança nunca vê `estimatedVotes`.
|
|
- Consent LGPD resolve por chave estável (`lib/campaignConsentKeys.ts`) e falha fechado.
|
|
- Copy pt-BR / identificadores em inglês; "Praça"/"Núcleo" não voltam.
|
|
- Gate por entrega: `tsc --noEmit`, `pnpm lint` (0 warnings), `pnpm format:check`, `pnpm exec knip` (exports em ERROR), `pnpm check:cycles` (0 ciclos), `pnpm test`, `pnpm build`.
|