diff --git a/.cursor/rules/engineering-standards.mdc b/.cursor/rules/engineering-standards.mdc new file mode 100644 index 00000000..8d430645 --- /dev/null +++ b/.cursor/rules/engineering-standards.mdc @@ -0,0 +1,49 @@ +--- +description: Teqo engineering standards — dead-code hygiene, client-boundary composition, caching ladder, typed escape hatches, zero-warning gate. Read before and enforce after ANY code change. +alwaysApply: true +--- + +# Engineering standards (Teqo) + +Established 2026-07-23 by the engineering-standards hardening (tracker: `docs/IMPROVE-CODE-QUALITY-PLAN.md`; ledger: `docs/TECH-DEBT.md`; safety net: `docs/TESTING.md`). These are gates, not suggestions — CI enforces most of them. + +## After every change (non-negotiable) + +1. **Dead code dies immediately.** After your edits, check what your change orphaned and delete it in the same delivery: run `pnpm exec knip` (files/dependencies are CI-blocking errors; unused exports are warnings being driven to zero). Never leave commented-out code, unused exports, superseded components, or one-shot scripts behind — git history is the archive. +2. **Zero warnings.** `pnpm lint` runs with `--max-warnings=0`. Deliberate placeholders use a `_` prefix. Never silence a rule without an inline justification comment. +3. **Full gate before handoff:** `pnpm exec tsc --noEmit`, `pnpm lint`, `pnpm test` (unit + int, local DB only), `pnpm build`. CI (`.github/workflows/ci.yml`) runs the same on the GitHub mirror. + +## Type honesty + +- **`as never` is banned** (ESLint `no-restricted-syntax`). Use the typed escape hatches that keep property-level checking: `hookFilledCreateData()` for create data whose remaining required fields are filled by collection hooks; tests use `stub(partial)` (`tests/helpers/stub.ts`). Casting from `unknown` after a runtime check is acceptable; a justified `eslint-disable-next-line` is the last resort. +- Prefer fixing the type over casting. When a generic can't be proven, restructure so the caller keeps concrete types (see `runStaffEntityMutation` — policy in the helper, typed mutation in the caller). + +## Client boundary (Next.js composition) + +- **Server-first.** Pages and data assembly are Server Components; `'use client'` only where there is interactivity (hooks, handlers, browser APIs). Static subtrees of an interactive area are passed as `children`/slots from the server (`LeaderContactsPanel` + `LeaderContactForm`, `CampaignDashboard.mapSlot` are the references). +- **Client components must not import server data modules for values.** Types/constants shared with the client live in a client-safe contract module (`municipalityMapContract.ts` is the reference; `municipalityMapData.ts` is `server-only`). `import type` is always safe. +- **State lives where it is used.** Hover/selection in the leaf that renders it (`MunicipalityMapSelection`); context providers wrap only their consumers (estimate-scenario provider wraps results, not filters). Do not lift state so high that siblings re-render. +- Heavy view payloads: pass view models with `select`ed fields, never whole Payload docs. + +## Loading feedback (see also `campanha-action-feedback.mdc`) + +- Every navigation-triggering control participates in a transition; the RESULTS region dims (`CampaignListPendingBoundary`/`CampaignListResults`), not just the control. Optimistic UI must revalidate on success (`revalidatePath`) or it will lie by reverting. +- Heavy server subtrees stream behind `` with a local skeleton; heavy route segments get their own `loading.tsx`. + +## Caching ladder (decide in this order) + +1. **React `cache()`** for per-request dedup of repeated reads in one RSC tree (`loadMunicipalityScope`, `getCampaignUser`). +2. **`unstable_cache` + tag** for immutable/slow-changing cross-request data (election reads use tag `election-tse`; bust via `POST /api/revalidate?tag=...` — allowlist in `revalidateRequest.ts`). Auth checks stay OUTSIDE the cached core. +3. **Committed artifact via re-runnable script** for truly immutable derived data (`pnpm build:election-aggregates` → `src/lib/electionAggregates/`, pinned to the catalog by a snapshot test). Never compute artifacts during `pnpm build` — deploys must not depend on DB content. +4. Never long-cache live 2026 campaign data (pledges, expectedVotes) without a write-path invalidation. + +## Access control (see also `security-critical.mdc`) + +- **Every collection declares `access` explicitly.** Payload's default ("any authenticated user") is a bug here: campaign JWTs work against `/api/*`. Admin-only collections use `payloadAdminOnly`. +- Access helpers live in `src/utilities/access/` per domain, re-exported through `campaignAccess.ts`. New collections get helpers there plus int tests proving the denied paths. + +## Module organization + +- `src/lib/` = pure logic and static data (client-safe unless marked). `src/utilities/` = Payload/Next-coupled server code (mark `server-only` when server-bound). React components never live in `utilities/`. +- Follow the domain prefixes (`campaign*`, `municipality*`, `supporter*`, `actionPlan*`); the access domain has a real folder (`utilities/access/`). +- Abstractions need 3+ call sites or a policy worth naming (`runCampaignRedirectFormAction`, `runStaffEntityMutation`); don't build generic services for 2 uses. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..7e185a6d --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,68 @@ +# Continuous integration for the GitHub mirror (pushes go to Codeberg + GitHub). +# Runs the same gate as .husky/pre-push plus tests and dead-code analysis. +name: CI + +on: + push: + branches: [main] + pull_request: + +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + +jobs: + checks: + runs-on: ubuntu-latest + timeout-minutes: 20 + services: + postgres: + # Matches production Neon major (17) and docker-compose.yml. + image: postgres:17-alpine + env: + POSTGRES_USER: teqo + POSTGRES_PASSWORD: teqo + POSTGRES_DB: teqo_test + ports: + - 5432:5432 + options: >- + --health-cmd "pg_isready -U teqo" + --health-interval 5s + --health-timeout 5s + --health-retries 12 + env: + # Tests load .env.test themselves (database teqo_test on localhost:5432). + # These are only for the migrate step, which reads the process env. + DATABASE_URL: postgresql://teqo:teqo@localhost:5432/teqo_test + PAYLOAD_SECRET: test-only-secret-not-used-in-production + NEXT_PUBLIC_SITE_URL: http://localhost:3000 + steps: + - uses: actions/checkout@v4 + + - uses: pnpm/action-setup@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 24 + cache: pnpm + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Lint (zero warnings) + run: pnpm lint + + - name: Typecheck + run: pnpm typecheck + + - name: Dead code (knip) + run: pnpm exec knip + + - name: Unit tests + run: pnpm test:unit + + - name: Migrate test database + run: pnpm migrate + + - name: Integration tests + run: pnpm test:int diff --git a/.impeccable/config.json b/.impeccable/config.json index 5fc7123b..8e40de01 100644 --- a/.impeccable/config.json +++ b/.impeccable/config.json @@ -11,6 +11,15 @@ ], "createdAt": "2026-07-21T01:54:11.928Z", "reason": "Pre-existing inline hex in the password-reset EMAIL HTML template; transactional email cannot use CSS tokens" + }, + { + "rule": "broken-image", + "value": "*", + "files": [ + "src/components/MetaPixel.tsx" + ], + "createdAt": "2026-07-23T22:40:00.000Z", + "reason": "Meta's documented noscript tracking pixel: a hidden 1x1 whose src is a templated facebook.com/tr URL — not a rendered image" } ] } diff --git a/AGENTS.md b/AGENTS.md index 32dd698b..8dcf3687 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -38,7 +38,7 @@ Teqo is the digital platform for deputado federal Jorge Solla (PT-BA): public si Backlog consolidado (bloqueadores, site, campanha, white-label): [`docs/roadmap.md`](docs/roadmap.md). -1. **Payload admin RBAC is still absent.** The campaign auth collection now has `role`, but the `users` admin collection still has no roles; every admin user has full Payload admin access. Add admin roles before opening `/admin` to a broader team. +1. **Payload admin roles are still absent (access lockdown shipped 2026-07-23).** Every collection now declares explicit `access` — `users`, `signature`, `subscription`, `consent` are Payload-admin-only, and CMS writes (`post`/`tag`/`media`/`petition`) are admin-only — so campaign JWTs can no longer act on them via `/api`. But the `users` collection still has no `roles` field: every admin user remains omnipotent inside `/admin`. Add admin roles (schema migration) before opening `/admin` to a broader team. 2. **Legacy public consent flows still hardcode document IDs** (e.g. `consent: 2` in `submitWhatsapp.ts`). The campaign leadership flow does not: it requires `Consent.key = 'lideranca-autopreenchimento'` and fails closed when missing. Migrating the older public flows remains separate work. 3. **`Post`/`Tag` ship the news system, but there is still no `Pages` collection for institutional content.** Partly resolved: the `post`/`tag` collections back the news/publications system, and the home "Últimas notícias" list plus the `/[type]`, `/[type]/[category]`, and article routes are live (see "Posts & Tags" below). Still hardcoded/pending: the home hero heading + subtitle copy (`HomePage` global still exposes only a single `image` field — the text lives in `src/app/(frontend)/(home)/page.tsx`), and there is no `Pages` collection yet for institutional content such as the bio and propostas. 4. **Campaign LGPD consent texts are not yet configured in production.** Invites and self-service leadership flows fail closed until an admin creates `Consent.key = 'lideranca-autopreenchimento'` with counsel-approved copy. The C2 supporter registry (merged to `main`, production-blocked) additionally requires `Consent.key = 'apoiador-cadastro'` and `Consent.key = 'apoiador-intencao-voto'` (vote intention is sensitive data) — same fail-closed behavior. All four keys are bundled in the single legal batch of Onda 0 (see deploy checklist). @@ -59,6 +59,8 @@ Backlog consolidado (bloqueadores, site, campanha, white-label): [`docs/roadmap. **Recently resolved (2026-07-23):** **Export CSV de assinaturas e contatos** no admin Payload — `@payloadcms/plugin-import-export@3.82.0` habilitado em `signature` e `contact` (`import: false`, export CSV síncrono); flatten de `contact`/`petition` via `toCSV` em [`src/utilities/signatureExport.ts`](src/utilities/signatureExport.ts) (só assinaturas); `consent` excluído do export de assinaturas; collections `exports`/`imports` com access `isPayloadAdmin`; migration `20260723_025513_add_import_export_plugin`. Plano: [`docs/plans/exportar-csv-assinaturas.md`](docs/plans/exportar-csv-assinaturas.md). +**Recently resolved (2026-07-23):** **Engineering-standards hardening** (tracker: [`docs/IMPROVE-CODE-QUALITY-PLAN.md`](docs/IMPROVE-CODE-QUALITY-PLAN.md); ledger: [`docs/TECH-DEBT.md`](docs/TECH-DEBT.md); safety net: [`docs/TESTING.md`](docs/TESTING.md); standards rule: `.cursor/rules/engineering-standards.mdc` — READ IT before coding). Phases: (0) explicit `access` on every collection — `users`/`signature`/`subscription`/`consent` admin-only, CMS writes admin-only, leader route gates on `demandas*`/`planos*`, `createLeaderSupporter` scope fixed, `candidate` treated as unrestricted staff consistently; (1) tooling gate — knip in CI (`.github/workflows/ci.yml` on the GitHub mirror), `pnpm lint --max-warnings=0`, `as never` banned (typed helpers `hookFilledCreateData` / tests' `stub`), tsconfig include consolidated + `noUnusedLocals`, ~12 dead modules deleted; (2) caching — committed TSE aggregate artifact (`pnpm build:election-aggregates` → `src/lib/electionAggregates/`, loader `bahiaElectionAggregates.ts`), `loadMunicipalityScope` React `cache()` dedup, `unstable_cache` + `election-tse` tag on elections-tab loaders; (3) "Feel the action" gaps closed — task-toggle revalidation bug, shared `CampaignListPendingBoundary` dimming results, map-compare transition, dashboard map + detail tabs stream behind Suspense; (4) state scoping — map hover isolated in `MunicipalityMapSelection`, scenario provider narrowed, `LeaderContactsPanel` server-rendered with a client form island; (5) structure — `campaignAccess` split into `src/utilities/access/*` (re-exported), supporter actions split (`supporterImport.ts`), `runStaffEntityMutation` + `runCampaignRedirectFormAction` dedupe the org/dobradinha twins and 4 formActions ladders, Plaza→Municipality identifier rename finished. + **Recently resolved (2026-07-23):** **A10** Cenários de estimativa (pessimista/média/otimista) — `votePledge.estimatedVotes` e `municipality.expectedVotes` viram groups (`pessimistic|central|optimistic`); migration `20260723_124200_add_vote_estimate_scenarios` (backfill scalar→`central`); agregação por cenário em `votePledgeData` com default `central`; mapa 2026 com `values2026ByScenario` + seletor local; lista/overview/dashboard com média + faixa secundária; liderança continua com um `declaredVotes` sem ver estimativas. Plano: [`docs/plans/cenarios-estimativa-votos.md`](docs/plans/cenarios-estimativa-votos.md). Desbloqueia **E8**. ## Posts & Tags (news / publications) @@ -98,7 +100,7 @@ curl -X POST "https:///api/revalidate?tag=global_privacy-policy" \ - **Auth:** send the secret via `x-revalidate-secret` or `Authorization: Bearer `; it is compared to `REVALIDATE_SECRET` with a constant-time check. - `REVALIDATE_SECRET` must be set in the Vercel **production** env (and in your local env if you want to test the endpoint). It is documented in `.env.example`. -- **Tag:** optional `?tag=` query or JSON body `{ "tag": "..." }` (query wins). Allowlist: `posts` (default), `global_privacy-policy`. Unknown tag → `400`. +- **Tag:** optional `?tag=` query or JSON body `{ "tag": "..." }` (query wins). Allowlist: `posts` (default), `global_privacy-policy`, `election-tse` (bust after re-running `pnpm db:seed:tse` so the cached elections-tab reads refresh). Unknown tag → `400`. - **Responses:** `200 { revalidated: true, tag: '...' }` on success; `401` if the secret is missing/wrong; `500` if `REVALIDATE_SECRET` is not configured on the server. - This POST is the required last step of the post-seed / direct-DB-change runbook. @@ -152,7 +154,7 @@ Static Bahia map foundation for future Leaflet surfaces (roadmap B2; UI is B3). - **Artifacts (committed):** `src/lib/geometries/bahia-municipalities.topo.json` (~132 KB; Topology object `municipalities`, 417 features, `properties: { codarea, name }`) and `src/lib/geometries/bahia-identity-territories.topo.json` (~15 KB; object `territories`, 27 features, `properties: { code, name }`). Soft size budget in tests: ≤ ~600 KB each. - **Code table:** `src/lib/bahiaMunicipalityCodes.ts` — canonical municipality name → IBGE 7-digit `codarea`, plus `codeForMunicipality` / `municipalityForCode`. Fixture `tests/fixtures/bahia-municipality-codes.official.json` + int tests for bijection/coverage. -- **Runtime helpers:** `src/lib/bahiaGeometries.ts` — `getMunicipalityFeature` / `getTerritoryFeature`, typed topologies, feature arrays. Eagerly decodes TopoJSON→GeoJSON at module load (lazy split is B5 F1 — prefer with B3). +- **Runtime helpers:** `src/lib/bahiaGeometries.ts` — `getMunicipalityFeature` / `getTerritoryFeature`, typed topologies, feature arrays. TopoJSON→GeoJSON decode is LAZY (dynamic import via `loadMunicipalityGeometryModule`; B5 F1 done) — the default `/campanha` server graph never pays it. - **Territory polygons:** dissolved from IBGE municipality meshes via `bahiaIdentityTerritoryRecords` (`topojson` `merge`), not the IDE Bahia shapefile (IDE remains provenance/validation reference only). - **Rebuild:** `pnpm build:geometries` (`scripts/build-bahia-geometries.mjs`) downloads IBGE Malhas (qualidade intermediaria) + Localidades, reconciles names with `canonicalizeMunicipalityName`, simplifies/quantizes, emits the artifacts above. Cache under `data/geometries/` (gitignored; override with `GEOMETRIES_CACHE_DIR`). **Does not touch the database** — no `assertLocalDatabase` / no Neon risk. Not part of `pnpm build` or `pnpm dev`. Details: `docs/plans/mapa-bahia-geometrias.md`. Scale/DRY follow-ups from `/simplify`: `docs/plans/escala-dry-pos-b2.md` (B5). @@ -166,7 +168,7 @@ Public TSE 2022 results back the electoral baseline (roadmap items A3 + A4). Thr 1. Confirm `DATABASE_URL` is `postgresql://teqo:teqo@localhost:5432/teqo` and `.env.test` points to `teqo_test`; never use Neon for development, tests, E2E, or local builds. 2. Run `pnpm db:start`, `pnpm migrate:status`, and `DATABASE_URL=postgresql://teqo:teqo@localhost:5432/teqo_test pnpm migrate:status`; every migration must show `Ran: Yes`. -3. Run `pnpm generate:types`, `pnpm generate:importmap` when components changed, `pnpm exec tsc --noEmit`, `pnpm lint`, `pnpm test` (unit + int), `pnpm test:e2e` (or `pnpm test:all`), and `pnpm build` against the local database. Payload CLI scripts (`migrate`, `generate:*`, `payload`) and the build migrate step use `--conditions=react-server` so `server-only` modules load correctly outside Next.js. +3. Run `pnpm generate:types`, `pnpm generate:importmap` when components changed, `pnpm exec tsc --noEmit`, `pnpm lint` (zero warnings — the script enforces `--max-warnings=0`), `pnpm exec knip` (dead files/dependencies are CI-blocking; delete what your change orphaned), `pnpm test` (unit + int), `pnpm test:e2e` (or `pnpm test:all`), and `pnpm build` against the local database. Payload CLI scripts (`migrate`, `generate:*`, `payload`) and the build migrate step use `--conditions=react-server` so `server-only` modules load correctly outside Next.js. 4. Scan every edited runtime source file with Aikido and resolve new findings before handoff. Do not expand hardening work into unrelated pre-existing findings without an explicit scope change. 5. Review Vercel environment variables and migration SQL before deploy; `pnpm build` applies pending migrations to production automatically. 6. **Mandatory production blocker:** do not insert real leadership/support data or enable invites until electoral counsel documents the LGPD art. 11 basis, approves the specific versioned consent text, and an admin creates it with exactly `Consent.key = 'lideranca-autopreenchimento'`. The app intentionally fails closed when this key is absent. @@ -197,7 +199,7 @@ src/ │ ├── (frontend)/ # Frontend routes + server actions (src/app/(frontend)/actions/*.ts) │ ├── (payload)/ # Payload admin routes │ └── (campaign)/ # Internal campaign-management area (auth barrier, see "Campaign auth") -├── collections/ # Users, CampaignUser, CampaignInvite, ElectoralNucleus, Leadership, NucleusUpdate, Supporter, SupporterImportBatch, ActionPlan, ElectionTally, ElectionCandidateVote, ElectionCandidate, Media, Petition, Signature, Consent, Contact, Subscription, Post, Tag +├── collections/ # Users, CampaignUser, CampaignInvite, Municipality, Leadership, VotePledge, MunicipalityUpdate, Organization, StateDeputy, CampaignDemand, Supporter, SupporterImportBatch, ActionPlan, ElectionTally, ElectionCandidateVote, ElectionCandidate, Media, Petition, Signature, Consent, Contact, Subscription, Post, Tag ├── globals/ # Global configs (SiteSettings, HomePage, Metadata) ├── components/ # Custom React components (campaign/*, ui from shadcn) ├── lib/ # cities.ts, bahiaTerritories.ts, bahiaTseZones.ts, bahiaMunicipalityCodes.ts, bahiaGeometries.ts, geometries/*.topo.json, territorySuggestions.ts, formData.ts, zod schemas (lib/schemas/*) diff --git a/docs/IMPROVE-CODE-QUALITY-PLAN.md b/docs/IMPROVE-CODE-QUALITY-PLAN.md new file mode 100644 index 00000000..9a35d7d6 --- /dev/null +++ b/docs/IMPROVE-CODE-QUALITY-PLAN.md @@ -0,0 +1,51 @@ +# Improve Code Quality Plan + +## Context + +- **Started:** 2026-07-23 +- **Trigger:** full-codebase engineering-standards review (five parallel audits: structure/dead code, RBAC, client boundary/state, loading UX, caching) of a rapidly built ("vibe coded") codebase. +- **Scope:** whole repo; production is live (Neon Postgres with real citizens' PII), so security ordered first. +- **Method:** `improve-code-quality` journey, compressed into the seven delivery phases below (mapping to the skill's phases noted per row). The audits replaced a separate intake; the existing test suite (238 unit + 345 int + 5 e2e specs) is the safety net. +- **Companion docs:** [TECH-DEBT.md](TECH-DEBT.md) (ledger), [TESTING.md](TESTING.md) (safety-net map), [roadmap.md](roadmap.md) (canonical product/engineering backlog — larger debts become roadmap items per the `capture-review-debts` convention). + +## Phase Status + +| Phase | Content | Skill phases | Status | Date | +| ----- | ---------------------------------------------------------------------------------------------------------------------------- | -------------------------------------- | ------ | ---------- | +| 0 | Security lockdown: collection access defaults, leader route gates, broken leader-supporter create, candidate alignment | — (P0 pre-work) | done | 2026-07-23 | +| 1 | Tooling gate: knip + dead code, CI, tsconfig, zero lint warnings, `as never` ban, docs artifacts | 1 (net) + 2 (readability) + 6 (habits) | done | 2026-07-23 | +| 2 | Caching: React `cache()` dedup, committed election-aggregate artifact, `unstable_cache`, select/depth trims | 8 (sizing) | done | 2026-07-23 | +| 3 | Loading feedback: task-toggle revalidation bug, map compare transition, result-region pending, Suspense streaming | — (UX correctness) | done | 2026-07-23 | +| 4 | Client boundary/state: map hover isolation, provider narrowing, composition splits (bundle slimming deferred with rationale) | 4 (deep modules, UI) | done | 2026-07-23 | +| 5 | Structure: split campaignAccess + supporter actions, dedupe clones, utilities organization, finish rename | 3 (refactoring) + 4 (deep modules) | done | 2026-07-24 | +| 6 | Documentation: engineering-standards rule, AGENTS.md drift fixes, debt registration | 6 (habits) | done | 2026-07-24 | + +Statuses: pending · in-progress · awaiting-evidence · done · deferred: \ · skipped: \ + +## Key Decisions + +| Date | Phase | Decision | Rationale | +| ---------- | ----- | --------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | +| 2026-07-23 | 0 | Collection access lockdown before anything else | Payload defaults allowed any authenticated user (incl. campaign JWTs) to CRUD `users`/`signature`/`subscription`/`consent` via `/api` — live PII | +| 2026-07-23 | 0 | Keep pinned test contracts; stale tests updated, not silently changed code | Two leader-phone tests encoded the pre-remodel model; code was right, tests were stale | +| 2026-07-23 | 1 | `as never` banned repo-wide (ESLint `no-restricted-syntax`) | It silences all type checking; replaced by typed helpers (`hookFilledCreateData`, tests' `stub`) that keep property-level checking | +| 2026-07-23 | 1 | Lint gate is zero warnings (`--max-warnings=0`) | Warnings that never fail teach everyone to ignore the linter | +| 2026-07-23 | 1 | knip: files/dependencies = error; unused exports = warn (ledgered) | 158 export findings are dominated by god modules Phase 5 splits; failing CI on them day one would freeze the repo | +| 2026-07-23 | 1 | CI on the GitHub mirror (`.github/workflows/ci.yml`) | Pushes go to Codeberg + GitHub; GitHub Actions runs on the mirror with a Postgres 17 service | +| 2026-07-23 | 2 | Immutable TSE aggregates become a committed artifact (script-generated), not build-time computation | Vercel builds must not depend on prod DB content (TSE seeds are local-only); provenance stays explicit like `build:geometries` | +| 2026-07-23 | 2 | Salvador is the only zone-split municipality in the artifact | 2026-07-23 remodel: Camaçari is a single municipality (ZE 170/171 aggregated) | +| 2026-07-23 | 4 | Map-bundle slimming deferred | gzip crushes the repeating JSON keys to ~15-25KB on the wire; instant year/scenario switching is a core analysis flow (discovery kernel) | +| 2026-07-23 | 4 | Props→state effect mirrors accepted as correct | guarded-equality sync is the deliberate server-echo/local-draft reconciliation (A10 pattern), not derived state | +| 2026-07-23 | 5 | Entity twins deduped by POLICY, not by generic data plumbing | `runStaffEntityMutation` keeps callers' concrete Payload types; a generic slug-union factory could not be type-proven | + +## Next Actions + +- [x] Phase 0 shipped (`fe0fb1c`) +- [x] Phase 1 — tooling gate (`1f73626`) +- [x] Phase 2 — caching (`a9b111b`) +- [x] Phase 3 — loading feedback (`3323275`) +- [x] Phase 4 — client boundary (`c4ec15f`) +- [x] Phase 5 — structure (`524ae57`) +- [x] Phase 6 — documentation (`deca5c0`) +- [x] E2E-driven fixes: leader home Forbidden, leader contact visibility, duplicate nav keys, RSC function-prop contracts, fixed e2e dist dir (`cb6c152`) +- [ ] Remaining leftovers live in [TECH-DEBT.md](TECH-DEBT.md) Debt Ledger (drive knip `exports` warn→error, consent-window lease redesign, e2e local-latency flake, users roles migration) diff --git a/docs/TECH-DEBT.md b/docs/TECH-DEBT.md new file mode 100644 index 00000000..2d72224e --- /dev/null +++ b/docs/TECH-DEBT.md @@ -0,0 +1,57 @@ +# Tech Debt + +Ledger of known engineering debt. Items large enough to be scheduled work become entries in [roadmap.md](roadmap.md) (linked in the Ref column); this file tracks the full inventory so nothing is lost. Sourced initially from the 2026-07-23 engineering-standards audits. + +## Debt Ledger + +| Item | Location | Type | Risk | Effort | Priority | Status | Ref | +| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | ----------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- | --------------------------- | ----------------------------------------------------------------------------------------- | -------------------- | +| 158 unused exports reported by knip (warn-level) | god modules, ui kit families | dead code | low | M | P2 | open — drive `exports` rule to error after splits | Phase 5 | +| knip loader noise: `Error loading src/payload.config.ts` (server-only guard) | knip run output | tooling noise | none (exit 0, static graph resolves) | S | P3 | open | | +| Missing-consent window fixture releases its exclusive lease during the observation window — readers can interleave (rare flake). Full fix: session-level advisory lock spanning delete→op→restore + updated fault-path pins | `tests/helpers/testDatabaseLease.ts` (`withMissingInviteConsentFixture`) | test-infra race | low (ms-wide window; shared leases added 2026-07-23 narrow it) | M | P2 | open | | +| `users` collection has no roles (all admins omnipotent) | `src/collections/Users.ts` | RBAC | medium (small trusted admin set) | M (schema migration) | P1 before widening `/admin` | open | AGENTS Known Gaps #1 | +| Any campaign user can enumerate staff names/roles (`canReadCampaignUsers`) | `src/utilities/campaignAccess.ts` | info disclosure | low | S | P2 | open | | +| `removeSupporterData` deletes via `overrideAccess: true` while `canDeleteSupporter` is admin-only | `src/app/(campaign)/campanha/actions/supporter.ts` | access asymmetry (likely intentional LGPD path) | low | S | P3 | open — document or align | | +| Legacy public consent flows hardcode document IDs (`consent: 2`) | `src/app/(frontend)/actions/submitWhatsapp.ts` | correctness | medium | S | P1 | open | AGENTS Known Gaps #2 | +| E2E suite thin (5 specs; most campaign CRUD int-only) | `tests/e2e/` | coverage | medium | L | P2 | open | | +| CI runs on GitHub mirror only (no Codeberg/Woodpecker pipeline) | `.github/workflows/ci.yml` | process | low | M | P3 | open | | +| E2E campaign journeys latency-flake on loaded local machines (5s expect / 30s test budgets vs cold dev compiles; suite green with CI retries) | `tests/e2e/campaignMunicipalities.e2e.spec.ts` | test-infra | low | S | P3 | open — consider per-step budgets or a warmed dist dir | | +| Post listing cache holds all published posts, filters in memory | `src/utilities/posts.ts` | scale (fine at ~tens of posts) | low | M | P3 | open — watch corpus growth | | +| Petition `generateStaticParams` uses uncached `getDocuments` without `select`/`depth: 0` | `src/utilities/documents.ts` consumers | over-fetch (build-time) | low | S | P2 | planned (Phase 2) | | +| Compare mode (`?compare=`) still queries per candidate uncached cross-request | `src/utilities/municipalityMapData.ts` | perf | low | M | P3 | open — React `cache()` dedups in-request; add `unstable_cache` if profiling warrants | | +| Map bundle serializes all years × scenarios (~100KB raw) | `municipalityMapContract.ts` consumers | payload size | low — gzip crushes repeating keys to ~15-25KB on the wire; instant year/scenario switching is a core analysis flow worth the bytes | M | P3 | deferred 2026-07-23 with rationale (revisit only if RSC payload profiling says otherwise) | | +| Props→state `useEffect` mirrors in list controls/comboboxes | `MunicipalityListExpectedVotesControl`, `AsyncSearchCombobox`, `StrictCombobox` | pattern | none — reviewed 2026-07-23: guarded-equality sync is the correct "server echo vs local draft" reconciliation (A10 pattern), not derived state | — | — | closed (accepted as correct) | | + +## Smell Inventory + +| Smell | Location | Refactoring | Status | +| ---------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ | ---------------------------------------------- | ------------------- | +| God module: RBAC for every collection in one file (964 lines, ~80 exports) | `src/utilities/campaignAccess.ts` | split per domain under `src/utilities/access/` | planned (Phase 5) | +| God module: ~71 exports of electoral math | `src/lib/electionInsights.ts` | split by concern when next touched | ledgered | +| Multi-feature action bag: CRUD + CSV import pipeline + LGPD in one file (722 lines) | `src/app/(campaign)/campanha/actions/supporter.ts` | split CRUD vs import | planned (Phase 5) | +| Clone twins: create/update/staff-assert/unique-conflict skeleton | `actions/organization.ts` ↔ `actions/stateDeputy.ts` | extract shared factory | planned (Phase 5) | +| 13 near-identical `formActions.ts` try/catch→mapError→redirect ladders | `src/app/(campaign)/campanha/(app)/*/formActions.ts` | shared form-action wrapper | planned (Phase 5) | +| Flat 80+-file `src/utilities/` junk drawer; `lib/` vs `utilities/` boundary undocumented | `src/utilities/`, `src/lib/` | document convention + move misfiled modules | planned (Phase 5/6) | +| Praça→Município rename leftovers in identifiers (`PLAZA_MAP_YEARS`, `ACCESSIBLE_PLAZA_IDS_CONTEXT_KEY`, …) | map/list utilities | finish rename | planned (Phase 5) | +| Props→state `useEffect` mirrors | `MunicipalityListExpectedVotesControl`, comboboxes | key-remount or fully controlled | planned (Phase 4) | +| Detail-page RSCs mixing loading + tab UI + forms (300+ lines) | `municipios/[slug]/page.tsx`, `planos/[slug]/page.tsx` | extract tab loaders/sections | ledgered | + +## Sprout / Wrap Register + +| Host | Sprouted/Wrapped | Notes | +| ---------- | ---------------- | ----- | +| (none yet) | | | + +## Adopted Conventions + +- **Zero lint warnings.** `pnpm lint` runs with `--max-warnings=0`; deliberate placeholders use a `_` prefix (`argsIgnorePattern`). +- **`as never` is banned** (ESLint `no-restricted-syntax`). Production code uses typed helpers (e.g. `hookFilledCreateData` for hook-completed create data); tests use `stub(partial)` which keeps property-level checking. A justified `eslint-disable-next-line` is the only escape hatch. +- **Dead code is deleted, not commented.** knip runs in CI (files/dependencies at error level). After any change: `pnpm exec knip` and remove what died. +- **Collections always declare `access` explicitly.** Payload's default ("any authenticated user") is treated as a bug — see `payloadAdminOnly`. +- **Generated migration signatures are exempt** from unused-args linting (`src/migrations/**` override); never hand-edit migration content for lint cosmetics. +- **Shared consent fixture rows are leased, never owned** in integration tests (`tests/helpers/testDatabaseLease.ts`); deleting them steals from parallel spec files. + +## Debt Budget & Broken-Windows Policy + +- New warnings/`as never`/unused exports fail or get ledgered the same day — no untracked `// TODO`s. +- Each delivery that runs `/simplify` registers surviving debts here or in `docs/plans/escala-dry-pos-*.md` (existing convention), whichever the item's size warrants. diff --git a/docs/TESTING.md b/docs/TESTING.md new file mode 100644 index 00000000..b036969b --- /dev/null +++ b/docs/TESTING.md @@ -0,0 +1,32 @@ +# Testing + +## Test Strategy + +Three layers, all local-database-only (see `local-database` skill; tests refuse any database not ending in `_test`): + +- **Unit** (`tests/unit/**/*.unit.spec.ts`, 33+ specs): pure logic; runs with a deliberately invalid `DATABASE_URL` so nothing can touch a database. +- **Integration** (`tests/int/**/*.int.spec.ts`, 42+ specs): real Payload + Postgres (`teqo_test`); campaign fixtures allocate seeded municipalities via a Postgres sequence so parallel spec files never share one, and purge residue on claim. Shared consent rows with stable keys are leased (`tests/helpers/testDatabaseLease.ts`), never owned. +- **E2E** (`tests/e2e`, Playwright, 5 specs): smoke-level; most campaign behavior is pinned at the int layer. + +CI (`.github/workflows/ci.yml`) runs lint (zero warnings), typecheck, knip, unit, migrate, int against a Postgres 17 service. + +## Safety Net Map + +Modules the 2026-07 hardening phases touch, and where their behavior is pinned: + +| Module | Pinned behaviors | Test files | Gaps | +| ------ | ---------------- | ---------- | ---- | +| `campaignAccess.ts` (RBAC) | per-role municipality/leadership/pledge/supporter/demand/plan scoping; leader lockdown; staff field asymmetry | `campaignMunicipalityAccess`, `campaignVotePledge`, `campaignSupporter`, `campaignUserPhone`, `campaignActionPlan`, `campaignDemandWorkflow`, `collectionAccessLockdown` (int) | no dedicated per-export suite; covered through collection behavior | +| Collection access lockdown | campaign JWT denied on `users`/PII/CMS writes; anonymous read of public content; admin unaffected | `collectionAccessLockdown.int.spec.ts` | REST-transport variant untested (Local API with `overrideAccess: false` is equivalent) | +| `municipalityMapData.ts` | bundle scoping by URL filters + advisor access; `validVotesByYear` incl. 2026 reuse of 2022; null on empty scope | `municipalityMapData.int.spec.ts` | zone breakdown shape | +| `votePledgeData.ts` | `estimated[scenario] ?? declared` aggregation; role scoping; leader denial | `campaignVotePledge.int.spec.ts` | | +| Supporter import (token/bulk) | preview→confirm flow, dedup by phone, consent fail-closed | `campaignSupporter.int.spec.ts` | `supporterImportBulk`/`supporterImportToken` internals only via flow | +| Leader supporter create | linked-municipality scope; out-of-scope rejection; staff rejection | `collectionAccessLockdown.int.spec.ts` | | +| Posts visibility/caching | `isPostVisible` fail-closed; cached listing | (indirect via routes) | no dedicated `posts` spec — backlog | + +## Characterization Backlog + +- [ ] `src/utilities/posts.ts` — dedicated spec for `isPostVisible` fail-closed matrix and cache wrappers. +- [ ] `actionPlanViewModels.ts` + other `actionPlan*` utilities (~1k lines) — currently untested directly. +- [ ] `campaignDemandData.ts` / `leadershipData.ts` / `organizationData.ts` / `stateDeputyData.ts` list loaders. +- [ ] Election aggregate artifact (Phase 2): snapshot test pinning artifact ↔ `municipalityCatalog` key consistency. diff --git a/docs/roadmap.md b/docs/roadmap.md index a6d8bb15..41705641 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -246,9 +246,13 @@ Paralelizáveis agora: **E8** (pós-A10), fill-ins (O0+, RS+, …). **D3** (baix ## Admin Payload -- `roles` em `users` + access real antes de ampliar `/admin` +- `roles` em `users` + access real antes de ampliar `/admin` — access explícito por collection SHIPPED 2026-07-23 (hardening Fase 0); falta apenas o campo `roles` (migration) - Seed reproduzível de `Consent` por chave (não por ID) +## Débitos de engenharia (hardening 2026-07-23) + +- Ledger vivo em [`TECH-DEBT.md`](TECH-DEBT.md) (tracker: [`IMPROVE-CODE-QUALITY-PLAN.md`](IMPROVE-CODE-QUALITY-PLAN.md)); itens maiores: dirigir `knip` unused-exports de warn→error após os splits, redesenho do lease da janela de consent ausente nos testes, e2e fino, CI só no mirror GitHub + ## Plataforma white-label - Fase 2 do README (multi-tenant / marca por mandato) — **depois da eleição** diff --git a/eslint.config.mjs b/eslint.config.mjs index 45960655..cf8b4432 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -141,6 +141,40 @@ const eslintConfig = [ 'local/use-server-async-exports': 'error', }, }, + { + // Underscore-prefixed bindings are deliberate placeholders (unused action + // state, ignored tuple slots); everything else unused is dead code. + files: ['**/*.{js,mjs,jsx,ts,tsx}'], + rules: { + '@typescript-eslint/no-unused-vars': [ + 'warn', + { + args: 'all', + argsIgnorePattern: '^_', + caughtErrorsIgnorePattern: '^_', + varsIgnorePattern: '^_', + }, + ], + // `as never` silences every type check on the expression (worse than + // `any`). Fix the types instead; a justified eslint-disable comment is + // the only sanctioned escape hatch. + 'no-restricted-syntax': [ + 'error', + { + selector: 'TSAsExpression > TSNeverKeyword', + message: + 'Do not cast with `as never` — it disables type checking entirely. Fix the types or use a narrowly-typed helper.', + }, + ], + }, + }, + { + // Payload generates migration signatures with unused destructured args. + files: ['src/migrations/**/*.ts'], + rules: { + '@typescript-eslint/no-unused-vars': ['warn', { args: 'none' }], + }, + }, prettier, // Override default ignores of eslint-config-next. globalIgnores([ @@ -149,6 +183,9 @@ const eslintConfig = [ 'out/**', 'build/**', 'next-env.d.ts', + // Playwright artifacts (generated on e2e failures/reports): + 'playwright-report/**', + 'test-results/**', ]), ] diff --git a/knip.json b/knip.json new file mode 100644 index 00000000..3eb8e869 --- /dev/null +++ b/knip.json @@ -0,0 +1,31 @@ +{ + "$schema": "https://unpkg.com/knip@5/schema.json", + "entry": [ + "src/payload.config.ts", + "src/migrations/index.ts", + "src/app/(payload)/admin/importMap.js", + "scripts/*.mjs", + "eslint-local-rules.mjs" + ], + "project": ["src/**/*.{ts,tsx,js}", "scripts/**/*.mjs"], + "ignore": ["src/payload-types.ts", "src/migrations/**"], + "paths": { + "server-only": ["tests/helpers/serverOnly.ts"] + }, + "ignoreBinaries": ["playwright"], + "ignoreDependencies": [ + "tsx", + "sharp", + "tailwindcss-animate", + "tw-animate-css", + "shadcn", + "eslint-config-next", + "prettier" + ], + "rules": { + "exports": "warn", + "types": "warn", + "enumMembers": "warn", + "duplicates": "warn" + } +} diff --git a/package.json b/package.json index 337881df..c807c32a 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "db:seed:tse": "cross-env NODE_OPTIONS=\"--no-deprecation --import=tsx/esm --import=./scripts/seed-loader.mjs\" node scripts/seed-tse-results.mjs", "db:seed:consent": "cross-env NODE_OPTIONS=\"--no-deprecation --import=tsx/esm --import=./scripts/seed-loader.mjs\" node scripts/seed-consent.mjs", "build:demographics": "cross-env NODE_OPTIONS=\"--no-deprecation --import=tsx/esm\" node scripts/build-bahia-demographics.mjs", + "build:election-aggregates": "cross-env NODE_OPTIONS=\"--no-deprecation --import=tsx/esm --import=./scripts/seed-loader.mjs\" node scripts/build-election-aggregates.mjs", "build:geometries": "cross-env NODE_OPTIONS=\"--no-deprecation --import=tsx/esm\" node scripts/build-bahia-geometries.mjs", "build:tse-city-codes": "cross-env NODE_OPTIONS=\"--no-deprecation --import=tsx/esm\" node scripts/build-bahia-tse-city-codes.mjs", "dev": "node scripts/guard-dev-db.mjs && cross-env NODE_OPTIONS=--no-deprecation next dev", @@ -38,7 +39,9 @@ "migrate": "cross-env NODE_OPTIONS=\"--no-deprecation --conditions=react-server\" payload migrate", "migrate:create": "cross-env NODE_OPTIONS=\"--no-deprecation --conditions=react-server\" payload migrate:create", "migrate:status": "cross-env NODE_OPTIONS=\"--no-deprecation --conditions=react-server\" payload migrate:status", - "lint": "cross-env NODE_OPTIONS=--no-deprecation eslint --cache --cache-location .next/cache/eslint/", + "knip": "knip", + "knip:production": "knip --production", + "lint": "cross-env NODE_OPTIONS=--no-deprecation eslint --cache --cache-location .next/cache/eslint/ --max-warnings=0", "lint:fix": "cross-env NODE_OPTIONS=--no-deprecation eslint --fix", "payload": "cross-env NODE_OPTIONS=\"--no-deprecation --conditions=react-server\" payload", "start": "cross-env NODE_OPTIONS=--no-deprecation next start", @@ -62,10 +65,6 @@ "@payloadcms/richtext-lexical": "3.82.0", "@payloadcms/storage-vercel-blob": "3.82.0", "@payloadcms/ui": "3.82.0", - "@radix-ui/react-label": "^2.1.8", - "@radix-ui/react-select": "^2.2.6", - "@radix-ui/react-separator": "^1.1.8", - "@radix-ui/react-slot": "^1.2.4", "@vercel/analytics": "^1.6.1", "@vercel/speed-insights": "^1.3.1", "class-variance-authority": "^0.7.1", @@ -101,6 +100,7 @@ "@types/geojson": "^7946.0.16", "@types/leaflet": "^1.9.21", "@types/node": "22.19.9", + "@types/pg": "^8.10.2", "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", "@types/topojson-client": "^3.1.5", @@ -117,6 +117,8 @@ "eslint-plugin-check-file": "^3.3.1", "husky": "^9.1.7", "jsdom": "28.0.0", + "knip": "^6.27.0", + "pg": "^8.22.0", "postcss": "^8.5.6", "prettier": "^3.4.2", "schema-dts": "^2.0.0", @@ -126,7 +128,6 @@ "tsx": "4.21.0", "typescript": "npm:@typescript/typescript6@6.0.2", "typescript-7": "npm:typescript@7.0.2", - "typescript-eslint": "^8.64.0", "vite-tsconfig-paths": "6.0.5", "vitest": "4.0.18", "yauzl": "^3.4.0" diff --git a/playwright.config.ts b/playwright.config.ts index 81bfbe55..3d41b74e 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -9,6 +9,13 @@ import { assertTestDatabase } from './tests/helpers/assertTestDatabase' * `override: true` guarantees the test DB wins over anything in the shell. */ loadEnv({ path: '.env.test', override: true }) + +// Optional local override when the default Docker port is occupied (e.g. mapped +// to 5433) — same escape hatch vitest.setup.ts documents. +if (process.env.TEQO_TEST_DATABASE_URL) { + process.env.DATABASE_URL = process.env.TEQO_TEST_DATABASE_URL +} + assertTestDatabase(process.env.DATABASE_URL) const baseURL = process.env.PLAYWRIGHT_BASE_URL ?? 'http://localhost:3000' const webServerPort = new URL(baseURL).port || (baseURL.startsWith('https:') ? '443' : '80') @@ -77,9 +84,12 @@ export default defineConfig({ env: { DATABASE_URL: process.env.DATABASE_URL as string, PORT: webServerPort, - // A prior Playwright process can finish tearing down after the next starts. - // Per-process output prevents that cleanup from deleting the active server's files. - NEXT_DIST_DIR: `.next/e2e-${process.pid}`, + // Fixed dist dir: Next dev appends `/types/**/*.ts` to + // tsconfig.json's include with an EXACT-string check, so per-process + // names polluted tsconfig with one entry per run. A prior Playwright + // process tearing down late can, rarely, delete files of the next run's + // server — if a boot flakes, rerun (dev-only trade-off). + NEXT_DIST_DIR: '.next/e2e', PAYLOAD_SECRET: process.env.PAYLOAD_SECRET ?? 'test-only-secret-not-used-in-production', NEXT_PUBLIC_SITE_URL: process.env.NEXT_PUBLIC_SITE_URL ?? baseURL, }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5b2b9a23..dac67e68 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -38,18 +38,6 @@ importers: '@payloadcms/ui': specifier: 3.82.0 version: 3.82.0(@types/react@19.2.14)(@typescript/typescript6@6.0.2)(monaco-editor@0.55.1)(next@15.4.11(@babel/core@7.29.0)(@playwright/test@1.58.2)(babel-plugin-macros@3.1.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.77.4))(payload@3.82.0(@typescript/typescript6@6.0.2)(graphql@16.12.0))(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-label': - specifier: ^2.1.8 - version: 2.1.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-select': - specifier: ^2.2.6 - version: 2.2.6(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-separator': - specifier: ^1.1.8 - version: 1.1.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-slot': - specifier: ^1.2.4 - version: 1.2.4(@types/react@19.2.14)(react@19.2.4) '@vercel/analytics': specifier: ^1.6.1 version: 1.6.1(next@15.4.11(@babel/core@7.29.0)(@playwright/test@1.58.2)(babel-plugin-macros@3.1.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.77.4))(react@19.2.4) @@ -150,6 +138,9 @@ importers: '@types/node': specifier: 22.19.9 version: 22.19.9 + '@types/pg': + specifier: ^8.10.2 + version: 8.10.2 '@types/react': specifier: ^19.2.14 version: 19.2.14 @@ -176,28 +167,34 @@ importers: version: 2.3.1 '@vitejs/plugin-react': specifier: 4.5.2 - version: 4.5.2(vite@7.3.1(@types/node@22.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.77.4)(tsx@4.21.0)(yaml@2.8.2)) + version: 4.5.2(vite@7.3.1(@types/node@22.19.9)(jiti@2.7.0)(lightningcss@1.30.2)(sass@1.77.4)(tsx@4.21.0)(yaml@2.9.0)) csv-parse: specifier: ^7.0.1 version: 7.0.1 eslint: specifier: ^9.39.2 - version: 9.39.2(jiti@2.6.1) + version: 9.39.2(jiti@2.7.0) eslint-config-next: specifier: 15.4.11 - version: 15.4.11(@typescript/typescript6@6.0.2)(eslint@9.39.2(jiti@2.6.1)) + version: 15.4.11(@typescript/typescript6@6.0.2)(eslint@9.39.2(jiti@2.7.0)) eslint-config-prettier: specifier: ^10.1.8 - version: 10.1.8(eslint@9.39.2(jiti@2.6.1)) + version: 10.1.8(eslint@9.39.2(jiti@2.7.0)) eslint-plugin-check-file: specifier: ^3.3.1 - version: 3.3.1(eslint@9.39.2(jiti@2.6.1)) + version: 3.3.1(eslint@9.39.2(jiti@2.7.0)) husky: specifier: ^9.1.7 version: 9.1.7 jsdom: specifier: 28.0.0 version: 28.0.0(@noble/hashes@1.8.0) + knip: + specifier: ^6.27.0 + version: 6.27.0 + pg: + specifier: ^8.22.0 + version: 8.22.0 postcss: specifier: ^8.5.6 version: 8.5.6 @@ -225,15 +222,12 @@ importers: typescript-7: specifier: npm:typescript@7.0.2 version: typescript@7.0.2 - typescript-eslint: - specifier: ^8.64.0 - version: 8.64.0(@typescript/typescript6@6.0.2)(eslint@9.39.2(jiti@2.6.1)) vite-tsconfig-paths: specifier: 6.0.5 - version: 6.0.5(@typescript/typescript6@6.0.2)(vite@7.3.1(@types/node@22.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.77.4)(tsx@4.21.0)(yaml@2.8.2)) + version: 6.0.5(@typescript/typescript6@6.0.2)(vite@7.3.1(@types/node@22.19.9)(jiti@2.7.0)(lightningcss@1.30.2)(sass@1.77.4)(tsx@4.21.0)(yaml@2.9.0)) vitest: specifier: 4.0.18 - version: 4.0.18(@types/node@22.19.9)(happy-dom@20.8.3)(jiti@2.6.1)(jsdom@28.0.0(@noble/hashes@1.8.0))(lightningcss@1.30.2)(msw@2.12.10(@types/node@22.19.9)(@typescript/typescript6@6.0.2))(sass@1.77.4)(tsx@4.21.0)(yaml@2.8.2) + version: 4.0.18(@types/node@22.19.9)(happy-dom@20.8.3)(jiti@2.7.0)(jsdom@28.0.0(@noble/hashes@1.8.0))(lightningcss@1.30.2)(msw@2.12.10(@types/node@22.19.9)(@typescript/typescript6@6.0.2))(sass@1.77.4)(tsx@4.21.0)(yaml@2.9.0) yauzl: specifier: ^3.4.0 version: 3.4.0 @@ -518,15 +512,30 @@ packages: peerDependencies: '@noble/ciphers': ^1.0.0 + '@emnapi/core@1.11.0': + resolution: {integrity: sha512-l9Oo58x0HOP5znGzVhYW9U3e5wVuA4LAZU2AGezTmkhO1CgQRFDhDg4nneHsu/t3WniXg9QrG2nIXL/ZS8ln8Q==} + + '@emnapi/core@1.11.1': + resolution: {integrity: sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==} + '@emnapi/core@1.8.1': resolution: {integrity: sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==} + '@emnapi/runtime@1.11.0': + resolution: {integrity: sha512-55coeOFKHv1ywEcUXJtWU5f+Jr/W5tZDvZig8DLKSwUN1JpROQ4rk/SNOQiFWmaR/VKF4zuFyW1B8JduOSv6Pg==} + + '@emnapi/runtime@1.11.1': + resolution: {integrity: sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==} + '@emnapi/runtime@1.8.1': resolution: {integrity: sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==} '@emnapi/wasi-threads@1.1.0': resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==} + '@emnapi/wasi-threads@1.2.2': + resolution: {integrity: sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==} + '@emotion/babel-plugin@11.13.5': resolution: {integrity: sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==} @@ -1588,6 +1597,12 @@ packages: '@napi-rs/wasm-runtime@0.2.12': resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==} + '@napi-rs/wasm-runtime@1.1.6': + resolution: {integrity: sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==} + peerDependencies: + '@emnapi/core': ^1.7.1 + '@emnapi/runtime': ^1.7.1 + '@next/env@15.4.11': resolution: {integrity: sha512-mIYp/091eYfPFezKX7ZPTWqrmSXq+ih6+LcUyKvLmeLQGhlPtot33kuEOd4U+xAA7sFfj21+OtCpIZx0g5SpvQ==} @@ -1686,6 +1701,239 @@ packages: '@open-draft/until@2.1.0': resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==} + '@oxc-parser/binding-android-arm-eabi@0.137.0': + resolution: {integrity: sha512-KDs+0VPdEmasOkpuJHW9V5WCF+cvYdMQv2Jd+aJXt+cxIx12NToRQRbXaRwUEDsZw+/jMk81Ve8ZFbjUkJTOwA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@oxc-parser/binding-android-arm64@0.137.0': + resolution: {integrity: sha512-WhALNzfy3x/RfC6bsqX+csavuUY0yHHE7XfgPE5M542uhoBZUUoGTPG+nkMbGoG4+gcfss5s7urMyn5QBHu0sw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@oxc-parser/binding-darwin-arm64@0.137.0': + resolution: {integrity: sha512-bFPr5hgmNMOMoyPTGtdsK4Ug21RovIPojRMgDDhSp1LtCnc/DkLwGONKjgRjszg677RlGnkYSviQ8hHaUPOVYA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@oxc-parser/binding-darwin-x64@0.137.0': + resolution: {integrity: sha512-CL5dMm1asqXIDZHg14FLxj3Mc36w8PI7xCWh1uA4is6z8g2XrIILoTcQYOxDbwzuk34RDPX5IAGUxZr6LA9KAg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@oxc-parser/binding-freebsd-x64@0.137.0': + resolution: {integrity: sha512-79h8rYGnSlKPGWo7mHr2ixO6ea7aW8B0CT965SZ8SLbNnCOH5aOYBTeVXUY6eMvEaiLyWr8Skuiugr5pDYgLGw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@oxc-parser/binding-linux-arm-gnueabihf@0.137.0': + resolution: {integrity: sha512-ASgmlSimhGyr0lksgVIo6hibz1obnDq4qJbiMX/AzltfgPnanRrzG1Q+23g8ljOHOjv6dsznkUuCYL3gg0sY1Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxc-parser/binding-linux-arm-musleabihf@0.137.0': + resolution: {integrity: sha512-AU2J9aa22Sx32wRGnDjybOU9TQXXQUud5sdUi+ZB0XxwM8aToWLweV+yA0wlQm0yIUVqljquqoHCYEq9II8gJQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxc-parser/binding-linux-arm64-gnu@0.137.0': + resolution: {integrity: sha512-GdEtiG89yMr7XkUGxifgodXEEm2f+xW2f9CpDjlgAnBOwhTmrpQMvhOGobLVKUyzf/qHBXW16smk5zbF3nZU6w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-arm64-musl@0.137.0': + resolution: {integrity: sha512-EGJ+Bs8iXx8KBH8DQ5BLoEm5lnHaYjlh4/8j8vFhrr/6z4tqONy5BZDzLpKmmNWlN6Hlc5r8YOuBVHqZ9vRFEQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@oxc-parser/binding-linux-ppc64-gnu@0.137.0': + resolution: {integrity: sha512-vzFUQENy/fnbSe5DZWovq6tIBc1uhuMztanSW6rz1e9WdQE4gHwYuD7ZII6JnrJifd1R3RSoqiZbgRFlVL2tYQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-riscv64-gnu@0.137.0': + resolution: {integrity: sha512-SfVI14HBQs9gtLcUD5hTt5hsNbdrqSUNg9S8muN+LhVQ5nf1WwH3hAoK6B9NKgdYgWAQSXFXGiiBedQ4r/BKuw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-riscv64-musl@0.137.0': + resolution: {integrity: sha512-e7Ppy4FCIFNQxT/ikSeIWFoQ0l+N9vgtRBtLcyZXeolTzApyVoPqEXsYPrcdM/9i0Bwk8knvYd37vaEMxHyi6g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@oxc-parser/binding-linux-s390x-gnu@0.137.0': + resolution: {integrity: sha512-Bho5qFwdhqsIFR7gipYEUlqvi3SRrY8sugxXig380MIaakBB1PyU9+7dBiBVScfImTNWhijUxdBwqrprGdq5WA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-x64-gnu@0.137.0': + resolution: {integrity: sha512-36mGWtg7PyFzjJwGDkH6/F4o2nIDEoKXLPr/X/lwqklkomQwJJt1I5GJVmGhovUEmgPK5WAeAZMqlFCehwiy9Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-x64-musl@0.137.0': + resolution: {integrity: sha512-/Jqx6+N7A44n2BdvUr7pXhVr2vFjs6WGH3unZRczwrfiH0H1zY0QwKQMG/dtRiTlKGDKGukznPT8lx84/oEsZg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@oxc-parser/binding-openharmony-arm64@0.137.0': + resolution: {integrity: sha512-9Uj0qHNNl+OgT1UTGwF7ixIXU6T1u2SbMidmgPy/h1h/fl2gRS6YpAxxY1gwHofcWjoTwkoMFd8xs5Vuj6GOFA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@oxc-parser/binding-wasm32-wasi@0.137.0': + resolution: {integrity: sha512-gW2vfkytNGgMVADiuzdvOfw0mWG9za20F/1fCJsif5aBMAvWJTSbpIXbIe0XkOe0VENk+PadpQ7cZgUy2sUJcA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [wasm32] + + '@oxc-parser/binding-win32-arm64-msvc@0.137.0': + resolution: {integrity: sha512-x+pFANF0yL5uK/6T7lu6SlR5qid6sp//eZXKLq5iNsIE+EQg6EaS8/wsW7E91nXXjpnPhSoMOHXShSVhGRdn8w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@oxc-parser/binding-win32-ia32-msvc@0.137.0': + resolution: {integrity: sha512-sQUqym80PFi6McRsIqfJrSu2JrSClEZIXXD+/FjAFoULEKzOPsldIdFBG96xdX8aVMzCNQ9792FPx3MfkEIrFA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + + '@oxc-parser/binding-win32-x64-msvc@0.137.0': + resolution: {integrity: sha512-2AsevxlvNN4WKxpEn3RtqD5zbqMaXF+T7JXblsP4gVuY+vC9dXS4ED/PwfRCliFqoeisYS3Iro4DHzxr0TEvVA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@oxc-project/types@0.137.0': + resolution: {integrity: sha512-WT+Gb24i8hmvo85AIv2oEYouEXkRlKAlT9WaCa3TfLgNCN+GhrJOGZuIlMouAh38Qe4QOx26eUOVsq70qXrywA==} + + '@oxc-resolver/binding-android-arm-eabi@11.21.3': + resolution: {integrity: sha512-eNU11A2WNizh04v3uyaJCootrHIaS0B9aHYXvAvVnPNk4xYSjMUjHnhQ6dewPN2MRYDskV85d1N0Aw0WNWhcyg==} + cpu: [arm] + os: [android] + + '@oxc-resolver/binding-android-arm64@11.21.3': + resolution: {integrity: sha512-8Q+ZjTLvn2dIcWsrmhdrEihm7q+ag/k+mkry7Z+t0QbbHaVxXQfvH9AewyVMh/WrpEKhQ3DDgx9fYbqeCpeOEw==} + cpu: [arm64] + os: [android] + + '@oxc-resolver/binding-darwin-arm64@11.21.3': + resolution: {integrity: sha512-wkh0qKZGHXVUDxFw3oA1TXnU2BDYY/r775oJflGeIr8uDPPoN2pk8gijQIzYRT6hoql/lg3+Tx/SaTn9e2/aGg==} + cpu: [arm64] + os: [darwin] + + '@oxc-resolver/binding-darwin-x64@11.21.3': + resolution: {integrity: sha512-HbNc23FAQYbuyDV2vBWMez4u4mrsm5RAkniGZAWqr6lYZ3N4beeqIb776jzwRl8qL2zRhHVXpUj97X0QgogVzg==} + cpu: [x64] + os: [darwin] + + '@oxc-resolver/binding-freebsd-x64@11.21.3': + resolution: {integrity: sha512-K6xNsTUPEUdfrn0+kbMq5nOUB5w1C5pavPQngt4TM2FpN91lP0PBe2srSpamb4d69O7h86oAi/qWX/kZNRSjkw==} + cpu: [x64] + os: [freebsd] + + '@oxc-resolver/binding-linux-arm-gnueabihf@11.21.3': + resolution: {integrity: sha512-VcFmOpcpWX1zoEy8M58tR2M9YxM+Z9RuQhqAx5q0CTmrruaP7Gveejg75hzd/5sg5nk9G3aLALEa3hE2FsmmTQ==} + cpu: [arm] + os: [linux] + + '@oxc-resolver/binding-linux-arm-musleabihf@11.21.3': + resolution: {integrity: sha512-quVoxFLBy43hWaQbbDtQNRwAX5vX76mv7n64icAtQcJ3eNgVeblqmkupF/hAneNthdqSlnd1sTjb3aQSaDPaCQ==} + cpu: [arm] + os: [linux] + + '@oxc-resolver/binding-linux-arm64-gnu@11.21.3': + resolution: {integrity: sha512-X0AqNZgcD07Q4V3RDK18/vYOj/HQT/FnmEFGYS2jTWqY7JO13ryE3TEs3eAIgUJhBnNkpEaiXqz3VK8M7qQhWQ==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@oxc-resolver/binding-linux-arm64-musl@11.21.3': + resolution: {integrity: sha512-YkaQnaKYdbuaXvRt5Qd0GpbihzVnyfR6z1SpYfIUC6RTu4NF7lDKPjVkYb+jRI2gedVO2rVpN35Y6akG6ud4Lw==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@oxc-resolver/binding-linux-ppc64-gnu@11.21.3': + resolution: {integrity: sha512-gB9HwhrPiFqUzDeEq+y/CgAijz1YdI6BnXz5GaH2Pa9cWdutchlkGFAiAuGb/PjVQpiK6NFKzFuztxrweoit7A==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@oxc-resolver/binding-linux-riscv64-gnu@11.21.3': + resolution: {integrity: sha512-zjDWBlYk8QGv0H8dsPUWqkfjYIIjG2TvspGkzXL0eImbgxtZorA/klKeHyolevoT3Kvbi+1iMr9Lhrh7jf54Og==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@oxc-resolver/binding-linux-riscv64-musl@11.21.3': + resolution: {integrity: sha512-4UfsQvacV388y1zpXL7C1x1FNYaV52JtuNRiuzrfQA2z1z6ElVrsidkGsrvQ5EgeSq1Pj7kaKqrgGkvFuxJ/tw==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@oxc-resolver/binding-linux-s390x-gnu@11.21.3': + resolution: {integrity: sha512-b5uH+HKH0MP5mNBYaK75SKsJbw52URqrx2LavYdq6wb0l3ExAG5niYRP9DWUNHdKilpaBVM2bXk9HNWrH3ew7Q==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@oxc-resolver/binding-linux-x64-gnu@11.21.3': + resolution: {integrity: sha512-PjYlmilBpNRh2ntXNYAK3Am5w/nPfEpnU/96iNx7CI8EzAn12J4JRiec63wHJTH31nLoCNxBg/829pN+3CfG3Q==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@oxc-resolver/binding-linux-x64-musl@11.21.3': + resolution: {integrity: sha512-QTBAb7JuHlZ7JUEyM8UiQi2f7m/L4swBhP2TNpYIDc9Wp/wRw1G/8sl6i13aIzQAXH7LKIm294LeOHd0lQR8zA==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@oxc-resolver/binding-openharmony-arm64@11.21.3': + resolution: {integrity: sha512-4j1DFwjwv36ec9kds0jU/ucQ5Ha4ERO/H95BxR5JFf0kqUUAJ1kwII7XhTc1vZrkdJkvLGC9Q2MbpObpum8RBg==} + cpu: [arm64] + os: [openharmony] + + '@oxc-resolver/binding-wasm32-wasi@11.21.3': + resolution: {integrity: sha512-i8oluoel5kru/j1WNrjmQSiA3GQ7wvIYVR1IwIoZtKogAhya2iub+ZKIeSIkcJOrnzQ18Tzl/F+kL3fYOxZLvA==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@oxc-resolver/binding-win32-arm64-msvc@11.21.3': + resolution: {integrity: sha512-M/8dw8dD6aOs+NlPJax401CZB9I7Aut84isQLgALGGwke4Afvw+/7yYhZb94yXf6t2sPLhQLmSmtSV+2FhsOWg==} + cpu: [arm64] + os: [win32] + + '@oxc-resolver/binding-win32-x64-msvc@11.21.3': + resolution: {integrity: sha512-H7BCt/VnS9hnmMp42eGhZ99izSCRvlnWwy/N71K1/J8QoExwY4262Z8QiEkMDtduRJrztayDxETTckmUuAVL9Q==} + cpu: [x64] + os: [win32] + '@payloadcms/db-postgres@3.82.0': resolution: {integrity: sha512-FzIRFpytQc8j6UjMpCYKN5Vz+sjw6EgOgsLzVtae6ihbJcHnEWeLXsV9ELd/r1349nNNqyFycRbJVpXbb+rhhw==} peerDependencies: @@ -2051,19 +2299,6 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-label@2.1.8': - resolution: {integrity: sha512-FmXs37I6hSBVDlO4y764TNz1rLgKwjJMQ0EGte6F3Cb3f4bIuHB/iLa/8I9VKkmOy+gNHq8rql3j686ACVV21A==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - '@radix-ui/react-menu@2.1.16': resolution: {integrity: sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==} peerDependencies: @@ -2285,19 +2520,6 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-separator@1.1.8': - resolution: {integrity: sha512-sDvqVY4itsKwwSMEe0jtKgfTh+72Sy3gPmQpjqcQneqQ4PFmr/1I0YA+2/puilhggCe2gJcx5EBAYFkWkdpa5g==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - '@radix-ui/react-slider@1.3.6': resolution: {integrity: sha512-JPYb1GuM1bxfjMRlNLE+BcmBC8onfCi60Blk7OBqi2MLTFdS+8401U4uFjnwkOr49BLmXxLC6JHkvAsx5OJvHw==} peerDependencies: @@ -2791,6 +3013,9 @@ packages: '@tybys/wasm-util@0.10.1': resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} + '@tybys/wasm-util@0.10.3': + resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==} + '@types/acorn@4.0.6': resolution: {integrity: sha512-veQTnWP+1D/xbxVrPC3zHnCZRjSrKfhbMUlEA43iMZLu7EsnTtkJklIuwrCPbOi8YkvDQAiW05VQQFvvz9oieQ==} @@ -2920,14 +3145,6 @@ packages: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/eslint-plugin@8.64.0': - resolution: {integrity: sha512-CGvQPBxN3wZLu6Rz2kFUpZeoCm78xUic92ck39KPePkO1NPOwjCqdQnm5Q87tpWw9vcBvW8XLrDXjH9PWYtJ3Q==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - '@typescript-eslint/parser': ^8.64.0 - eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/parser@8.55.0': resolution: {integrity: sha512-4z2nCSBfVIMnbuu8uinj+f0o4qOeggYJLbjpPHka3KH1om7e+H9yLKTYgksTaHcGco+NClhhY2vyO3HsMH1RGw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -2935,45 +3152,22 @@ packages: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/parser@8.64.0': - resolution: {integrity: sha512-KA0OshtlcCCXmbfqyZkM5pV3/WNraJf7DkJRLpyrmwPtud57H5BDX7C3k0LPSPxpprfRL+cJDGabF10mvNCoCw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/project-service@8.55.0': resolution: {integrity: sha512-zRcVVPFUYWa3kNnjaZGXSu3xkKV1zXy8M4nO/pElzQhFweb7PPtluDLQtKArEOGmjXoRjnUZ29NjOiF0eCDkcQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/project-service@8.64.0': - resolution: {integrity: sha512-tk4WpOJ6IEbGrVHaNmM0YRrwAD3exZlIK3iadQNAxh4YKk6jvUQ4ecq18n+v7+meh+cJ3j+D8nbk8sRKhlwLQg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/scope-manager@8.55.0': resolution: {integrity: sha512-fVu5Omrd3jeqeQLiB9f1YsuK/iHFOwb04bCtY4BSCLgjNbOD33ZdV6KyEqplHr+IlpgT0QTZ/iJ+wT7hvTx49Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/scope-manager@8.64.0': - resolution: {integrity: sha512-CXEaFdYXjSTgKhisNkwCcJwTP8Pl+fmRrEQrri4nm3vU743bALrxzLmq7fHG/7e6a5xO0lDYeURpZmBuhHk54w==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.55.0': resolution: {integrity: sha512-1R9cXqY7RQd7WuqSN47PK9EDpgFUK3VqdmbYrvWJZYDd0cavROGn+74ktWBlmJ13NXUQKlZ/iAEQHI/V0kKe0Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/tsconfig-utils@8.64.0': - resolution: {integrity: sha512-2yo8rRNKuzbVWQp5kslhANqZ2uDAeROQHBRZNPu8JDsHmeFNj/XJJhX/FhNUWmkHHvoNsKa6+tHJiig87EzsQw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/type-utils@8.55.0': resolution: {integrity: sha512-x1iH2unH4qAt6I37I2CGlsNs+B9WGxurP2uyZLRz6UJoZWDBx9cJL1xVN/FiOmHEONEg6RIufdvyT0TEYIgC5g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -2981,33 +3175,16 @@ packages: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/type-utils@8.64.0': - resolution: {integrity: sha512-XWG4Fmmv/6SvyS9nH8jWrKs6terwJvE8cyRt1CzYYqzp9OrPhCT4cMc/f7C6RZCwG+qMmiffJS1/qJP8G1URtg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/types@8.55.0': resolution: {integrity: sha512-ujT0Je8GI5BJWi+/mMoR0wxwVEQaxM+pi30xuMiJETlX80OPovb2p9E8ss87gnSVtYXtJoU9U1Cowcr6w2FE0w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/types@8.64.0': - resolution: {integrity: sha512-qjhfuTfLXjA4IOzXvz0rTjT01BqEiIgPoUeMwiEjnaHKJMTNo8rH5pYW1a2L/0Dnux2fPC85AeyJoWaGa8WxTA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.55.0': resolution: {integrity: sha512-EwrH67bSWdx/3aRQhCoxDaHM+CrZjotc2UCCpEDVqfCE+7OjKAGWNY2HsCSTEVvWH2clYQK8pdeLp42EVs+xQw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/typescript-estree@8.64.0': - resolution: {integrity: sha512-Pztpsn1aCE1oWDvDEfUk31nngvvF7vUB5SwHFEaZIFpvw7WJtqUHHL4plBZDA9HfWJJjL13BdG0YrJInTUvoVA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/utils@8.55.0': resolution: {integrity: sha512-BqZEsnPGdYpgyEIkDC1BadNY8oMwckftxBT+C8W0g1iKPdeqKZBtTfnvcq0nf60u7MkjFO8RBvpRGZBPw4L2ow==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -3015,21 +3192,10 @@ packages: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/utils@8.64.0': - resolution: {integrity: sha512-aJUGVB3+U0htrrCjoA8qukw8cm8fNCGAxK/tVoS70k8aeb7DETKeFozRiVFIwEeN9WJLsjaP3ph8I60tY2XZoQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/visitor-keys@8.55.0': resolution: {integrity: sha512-AxNRwEie8Nn4eFS1FzDMJWIISMGoXMb037sgCBJ3UR6o0fQTzr2tqN9WT+DkWJPhIdQCfV7T6D387566VtnCJA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/visitor-keys@8.64.0': - resolution: {integrity: sha512-mrtuL8Nsn6gi2H4mo5KMTp823M+3Q19Ew/i+Zlikq20tIMm99C3Ez0dCmkWWnxut20esQvTg8aUSEhMcAOXhEw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript/typescript-aix-ppc64@7.0.2': resolution: {integrity: sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==} engines: {node: '>=16.20.0'} @@ -4223,10 +4389,6 @@ packages: resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - eslint-visitor-keys@5.0.1: - resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} - eslint@9.39.2: resolution: {integrity: sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -4338,6 +4500,9 @@ packages: fastq@1.20.1: resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} + fd-package-json@2.0.0: + resolution: {integrity: sha512-jKmm9YtsNXN789RS/0mSzOC1NUq9mkVd65vbSSVsKdjGvYXBuE4oWe2QOEoFeRmJg+lPuZxpmrfFclNhoRMneQ==} + fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} engines: {node: '>=12.0.0'} @@ -4399,6 +4564,11 @@ packages: resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} engines: {node: '>= 0.4'} + formatly@0.3.0: + resolution: {integrity: sha512-9XNj/o4wrRFyhSMJOvsuyMwy8aUfBaZ1VrqHVfohyXf0Sw0e+yfKG+xZaY3arGCOMdwFsqObtzVOc1gU9KiT9w==} + engines: {node: '>=18.3.0'} + hasBin: true + formdata-polyfill@4.0.10: resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} engines: {node: '>=12.20.0'} @@ -4488,6 +4658,9 @@ packages: get-tsconfig@4.13.6: resolution: {integrity: sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==} + get-tsconfig@4.14.0: + resolution: {integrity: sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==} + get-tsconfig@4.8.1: resolution: {integrity: sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==} @@ -4898,6 +5071,10 @@ packages: resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} hasBin: true + jiti@2.7.0: + resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} + hasBin: true + jose@5.10.0: resolution: {integrity: sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==} @@ -4987,6 +5164,11 @@ packages: resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} engines: {node: '>=6'} + knip@6.27.0: + resolution: {integrity: sha512-CngYEYrD0n20N06FXA8n3u/0Wnnugoa+B9k14OP+iKIgkCHuzvIdsP3nfwjhByoc1WfogpxfiriMboAXFETDUw==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + language-subtag-registry@0.3.23: resolution: {integrity: sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==} @@ -5453,6 +5635,13 @@ packages: resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} engines: {node: '>= 0.4'} + oxc-parser@0.137.0: + resolution: {integrity: sha512-yFImD+WLElJpLKy8llG1qe4DCmMsL18peRp8XP1JKfig/gISbJkglnpDtX2aTmAn10kZF7164HbN2H8QPsXxGg==} + engines: {node: ^20.19.0 || >=22.12.0} + + oxc-resolver@11.21.3: + resolution: {integrity: sha512-2Mx3fKQz7+xgrBONjsxOgCGtMHOn38/HxMzW1I5efwXB5a4lRN0Vp40gYUJFBWJslcrvwoofTrqoTnLbwTd3pA==} + p-limit@3.1.0: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} @@ -5534,9 +5723,15 @@ packages: pg-cloudflare@1.3.0: resolution: {integrity: sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ==} + pg-cloudflare@1.4.0: + resolution: {integrity: sha512-Vo7z/6rrQYxpNRylp4Tlob2elzbh+N/MOQbxFVWCxS7oEx6jF53GTJFxK2WWpKuBRkmiin4Mt+xofFDjx09R0A==} + pg-connection-string@2.11.0: resolution: {integrity: sha512-kecgoJwhOpxYU21rZjULrmrBJ698U2RxXofKVzOn5UDj61BPj/qMb7diYUR1nLScCDbrztQFl1TaQZT0t1EtzQ==} + pg-connection-string@2.14.0: + resolution: {integrity: sha512-XwWDGcLRGCXAR8F/AM5bG7Q+A3Wm2s6QeEjlOKZLlH3UYcguiqCWKyWXVag5TLTIjR7oOJUY8kcADaZgWPyLeg==} + pg-int8@1.0.1: resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} engines: {node: '>=4.0.0'} @@ -5550,9 +5745,17 @@ packages: peerDependencies: pg: '>=8.0' + pg-pool@3.14.0: + resolution: {integrity: sha512-gKtPkFdQPU3DksooVLi9LsjZxrsBUZIpa+7aVx+LV5pNh0KzP4Zleud2po+ConrxbuXGBJ6Hfer6hdgpIBpBaw==} + peerDependencies: + pg: '>=8.0' + pg-protocol@1.11.0: resolution: {integrity: sha512-pfsxk2M9M3BuGgDOfuy37VNRRX3jmKgMjcvAcWqNDpZSf4cUmv8HSOl5ViRQFsfARFn0KuUQTgLxVMbNq5NW3g==} + pg-protocol@1.15.0: + resolution: {integrity: sha512-cq9sECI5s0+uPUXjbz8ioyPJni6RzsRib0US67i5IoTZKw8fNeYlVE7u8F4dG7vEJJtc5wdD1K189lCCUwqWTQ==} + pg-types@2.2.0: resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} engines: {node: '>=4'} @@ -5570,6 +5773,15 @@ packages: pg-native: optional: true + pg@8.22.0: + resolution: {integrity: sha512-8wih1vVIBMxoUM2oB4soJsD9tDnDpLv4OXBJ+EJzFsvycD+lfyIreC2gGHq78f8jbLLt+bvlPTFdFZfJkOuzAA==} + engines: {node: '>= 16.0.0'} + peerDependencies: + pg-native: '>=3.0.1' + peerDependenciesMeta: + pg-native: + optional: true + pgpass@1.0.5: resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==} @@ -5584,6 +5796,10 @@ packages: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} + picomatch@4.0.5: + resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==} + engines: {node: '>=12'} + pino-abstract-transport@2.0.0: resolution: {integrity: sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==} @@ -6057,6 +6273,10 @@ packages: sisteransi@1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + smol-toml@1.7.0: + resolution: {integrity: sha512-aqVvWoyO21L23mb+drl4RmMXbf6N7FdHjAhTRA9ZBL7apWBgfWC16KjrASI+1p9GAroljyMHj6fK67i0UiTNvQ==} + engines: {node: '>= 18'} + sonic-boom@4.2.1: resolution: {integrity: sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==} @@ -6262,6 +6482,10 @@ packages: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + tinyrainbow@3.0.3: resolution: {integrity: sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==} engines: {node: '>=14.0.0'} @@ -6323,12 +6547,6 @@ packages: peerDependencies: typescript: '>=4.8.4' - ts-api-utils@2.5.0: - resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} - engines: {node: '>=18.12'} - peerDependencies: - typescript: '>=4.8.4' - ts-essentials@10.0.3: resolution: {integrity: sha512-/FrVAZ76JLTWxJOERk04fm8hYENDo0PWSP3YLQKxevLwWtxemGcl5JJEzN4iqfDlRve0ckyfFaOBu4xbNH/wZw==} peerDependencies: @@ -6396,13 +6614,6 @@ packages: resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} engines: {node: '>= 0.4'} - typescript-eslint@8.64.0: - resolution: {integrity: sha512-0qg+pDNMnqYzqH9AnNK+39tejHvsShUOUUoRUgtnTGE7QuMZhiFDnozq8nHJVq+Wae6NMLKNWLg5WmkcC/ndyQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.1.0' - typescript@6.0.3: resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} engines: {node: '>=14.17'} @@ -6417,6 +6628,10 @@ packages: resolution: {integrity: sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==} engines: {node: '>=18'} + unbash@4.0.3: + resolution: {integrity: sha512-3cudTErfToSc4Ggv8XGXVNVli/xHKUtUZvaY5UVwhOcUPbQGz7PeaEnT/SAVgNziZtX67KEN9swMUYkLghxA1w==} + engines: {node: '>=14'} + unbox-primitive@1.1.0: resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} engines: {node: '>= 0.4'} @@ -6626,6 +6841,10 @@ packages: resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} engines: {node: '>=18'} + walk-up-path@4.0.0: + resolution: {integrity: sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A==} + engines: {node: 20 || >=22} + web-streams-polyfill@3.3.3: resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} engines: {node: '>= 8'} @@ -6739,8 +6958,8 @@ packages: resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} engines: {node: '>= 6'} - yaml@2.8.2: - resolution: {integrity: sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==} + yaml@2.9.0: + resolution: {integrity: sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==} engines: {node: '>= 14.6'} hasBin: true @@ -7124,12 +7343,34 @@ snapshots: dependencies: '@noble/ciphers': 1.3.0 + '@emnapi/core@1.11.0': + dependencies: + '@emnapi/wasi-threads': 1.2.2 + tslib: 2.8.1 + optional: true + + '@emnapi/core@1.11.1': + dependencies: + '@emnapi/wasi-threads': 1.2.2 + tslib: 2.8.1 + optional: true + '@emnapi/core@1.8.1': dependencies: '@emnapi/wasi-threads': 1.1.0 tslib: 2.8.1 optional: true + '@emnapi/runtime@1.11.0': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.11.1': + dependencies: + tslib: 2.8.1 + optional: true + '@emnapi/runtime@1.8.1': dependencies: tslib: 2.8.1 @@ -7140,6 +7381,11 @@ snapshots: tslib: 2.8.1 optional: true + '@emnapi/wasi-threads@1.2.2': + dependencies: + tslib: 2.8.1 + optional: true + '@emotion/babel-plugin@11.13.5': dependencies: '@babel/helper-module-imports': 7.28.6 @@ -7436,9 +7682,9 @@ snapshots: '@esbuild/win32-x64@0.27.3': optional: true - '@eslint-community/eslint-utils@4.9.1(eslint@9.39.2(jiti@2.6.1))': + '@eslint-community/eslint-utils@4.9.1(eslint@9.39.2(jiti@2.7.0))': dependencies: - eslint: 9.39.2(jiti@2.6.1) + eslint: 9.39.2(jiti@2.7.0) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.2': {} @@ -8009,6 +8255,20 @@ snapshots: '@tybys/wasm-util': 0.10.1 optional: true + '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.11.0)(@emnapi/runtime@1.11.0)': + dependencies: + '@emnapi/core': 1.11.0 + '@emnapi/runtime': 1.11.0 + '@tybys/wasm-util': 0.10.3 + optional: true + + '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)': + dependencies: + '@emnapi/core': 1.11.1 + '@emnapi/runtime': 1.11.1 + '@tybys/wasm-util': 0.10.3 + optional: true + '@next/env@15.4.11': {} '@next/env@15.5.12': {} @@ -8072,6 +8332,133 @@ snapshots: '@open-draft/until@2.1.0': {} + '@oxc-parser/binding-android-arm-eabi@0.137.0': + optional: true + + '@oxc-parser/binding-android-arm64@0.137.0': + optional: true + + '@oxc-parser/binding-darwin-arm64@0.137.0': + optional: true + + '@oxc-parser/binding-darwin-x64@0.137.0': + optional: true + + '@oxc-parser/binding-freebsd-x64@0.137.0': + optional: true + + '@oxc-parser/binding-linux-arm-gnueabihf@0.137.0': + optional: true + + '@oxc-parser/binding-linux-arm-musleabihf@0.137.0': + optional: true + + '@oxc-parser/binding-linux-arm64-gnu@0.137.0': + optional: true + + '@oxc-parser/binding-linux-arm64-musl@0.137.0': + optional: true + + '@oxc-parser/binding-linux-ppc64-gnu@0.137.0': + optional: true + + '@oxc-parser/binding-linux-riscv64-gnu@0.137.0': + optional: true + + '@oxc-parser/binding-linux-riscv64-musl@0.137.0': + optional: true + + '@oxc-parser/binding-linux-s390x-gnu@0.137.0': + optional: true + + '@oxc-parser/binding-linux-x64-gnu@0.137.0': + optional: true + + '@oxc-parser/binding-linux-x64-musl@0.137.0': + optional: true + + '@oxc-parser/binding-openharmony-arm64@0.137.0': + optional: true + + '@oxc-parser/binding-wasm32-wasi@0.137.0': + dependencies: + '@emnapi/core': 1.11.1 + '@emnapi/runtime': 1.11.1 + '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1) + optional: true + + '@oxc-parser/binding-win32-arm64-msvc@0.137.0': + optional: true + + '@oxc-parser/binding-win32-ia32-msvc@0.137.0': + optional: true + + '@oxc-parser/binding-win32-x64-msvc@0.137.0': + optional: true + + '@oxc-project/types@0.137.0': {} + + '@oxc-resolver/binding-android-arm-eabi@11.21.3': + optional: true + + '@oxc-resolver/binding-android-arm64@11.21.3': + optional: true + + '@oxc-resolver/binding-darwin-arm64@11.21.3': + optional: true + + '@oxc-resolver/binding-darwin-x64@11.21.3': + optional: true + + '@oxc-resolver/binding-freebsd-x64@11.21.3': + optional: true + + '@oxc-resolver/binding-linux-arm-gnueabihf@11.21.3': + optional: true + + '@oxc-resolver/binding-linux-arm-musleabihf@11.21.3': + optional: true + + '@oxc-resolver/binding-linux-arm64-gnu@11.21.3': + optional: true + + '@oxc-resolver/binding-linux-arm64-musl@11.21.3': + optional: true + + '@oxc-resolver/binding-linux-ppc64-gnu@11.21.3': + optional: true + + '@oxc-resolver/binding-linux-riscv64-gnu@11.21.3': + optional: true + + '@oxc-resolver/binding-linux-riscv64-musl@11.21.3': + optional: true + + '@oxc-resolver/binding-linux-s390x-gnu@11.21.3': + optional: true + + '@oxc-resolver/binding-linux-x64-gnu@11.21.3': + optional: true + + '@oxc-resolver/binding-linux-x64-musl@11.21.3': + optional: true + + '@oxc-resolver/binding-openharmony-arm64@11.21.3': + optional: true + + '@oxc-resolver/binding-wasm32-wasi@11.21.3': + dependencies: + '@emnapi/core': 1.11.0 + '@emnapi/runtime': 1.11.0 + '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.11.0)(@emnapi/runtime@1.11.0) + optional: true + + '@oxc-resolver/binding-win32-arm64-msvc@11.21.3': + optional: true + + '@oxc-resolver/binding-win32-x64-msvc@11.21.3': + optional: true + '@payloadcms/db-postgres@3.82.0(payload@3.82.0(@typescript/typescript6@6.0.2)(graphql@16.12.0))': dependencies: '@payloadcms/drizzle': 3.82.0(@types/pg@8.10.2)(payload@3.82.0(@typescript/typescript6@6.0.2)(graphql@16.12.0))(pg@8.16.3) @@ -8607,15 +8994,6 @@ snapshots: '@types/react': 19.2.14 '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-label@2.1.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': - dependencies: - '@radix-ui/react-primitive': 2.1.4(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) - optionalDependencies: - '@types/react': 19.2.14 - '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-menu@2.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@radix-ui/primitive': 1.1.3 @@ -8897,15 +9275,6 @@ snapshots: '@types/react': 19.2.14 '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-separator@1.1.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': - dependencies: - '@radix-ui/react-primitive': 2.1.4(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) - optionalDependencies: - '@types/react': 19.2.14 - '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-slider@1.3.6(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@radix-ui/number': 1.1.1 @@ -9326,6 +9695,11 @@ snapshots: tslib: 2.8.1 optional: true + '@tybys/wasm-util@0.10.3': + dependencies: + tslib: 2.8.1 + optional: true + '@types/acorn@4.0.6': dependencies: '@types/estree': 1.0.8 @@ -9405,7 +9779,7 @@ snapshots: '@types/pg@8.10.2': dependencies: '@types/node': 22.19.9 - pg-protocol: 1.11.0 + pg-protocol: 1.15.0 pg-types: 4.1.0 '@types/react-dom@19.2.3(@types/react@19.2.14)': @@ -9462,15 +9836,15 @@ snapshots: dependencies: '@types/node': 22.19.9 - '@typescript-eslint/eslint-plugin@8.55.0(@typescript-eslint/parser@8.55.0(@typescript/typescript6@6.0.2)(eslint@9.39.2(jiti@2.6.1)))(@typescript/typescript6@6.0.2)(eslint@9.39.2(jiti@2.6.1))': + '@typescript-eslint/eslint-plugin@8.55.0(@typescript-eslint/parser@8.55.0(@typescript/typescript6@6.0.2)(eslint@9.39.2(jiti@2.7.0)))(@typescript/typescript6@6.0.2)(eslint@9.39.2(jiti@2.7.0))': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.55.0(@typescript/typescript6@6.0.2)(eslint@9.39.2(jiti@2.6.1)) + '@typescript-eslint/parser': 8.55.0(@typescript/typescript6@6.0.2)(eslint@9.39.2(jiti@2.7.0)) '@typescript-eslint/scope-manager': 8.55.0 - '@typescript-eslint/type-utils': 8.55.0(@typescript/typescript6@6.0.2)(eslint@9.39.2(jiti@2.6.1)) - '@typescript-eslint/utils': 8.55.0(@typescript/typescript6@6.0.2)(eslint@9.39.2(jiti@2.6.1)) + '@typescript-eslint/type-utils': 8.55.0(@typescript/typescript6@6.0.2)(eslint@9.39.2(jiti@2.7.0)) + '@typescript-eslint/utils': 8.55.0(@typescript/typescript6@6.0.2)(eslint@9.39.2(jiti@2.7.0)) '@typescript-eslint/visitor-keys': 8.55.0 - eslint: 9.39.2(jiti@2.6.1) + eslint: 9.39.2(jiti@2.7.0) ignore: 7.0.5 natural-compare: 1.4.0 ts-api-utils: 2.4.0(@typescript/typescript6@6.0.2) @@ -9478,42 +9852,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/eslint-plugin@8.64.0(@typescript-eslint/parser@8.64.0(@typescript/typescript6@6.0.2)(eslint@9.39.2(jiti@2.6.1)))(@typescript/typescript6@6.0.2)(eslint@9.39.2(jiti@2.6.1))': - dependencies: - '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.64.0(@typescript/typescript6@6.0.2)(eslint@9.39.2(jiti@2.6.1)) - '@typescript-eslint/scope-manager': 8.64.0 - '@typescript-eslint/type-utils': 8.64.0(@typescript/typescript6@6.0.2)(eslint@9.39.2(jiti@2.6.1)) - '@typescript-eslint/utils': 8.64.0(@typescript/typescript6@6.0.2)(eslint@9.39.2(jiti@2.6.1)) - '@typescript-eslint/visitor-keys': 8.64.0 - eslint: 9.39.2(jiti@2.6.1) - ignore: 7.0.5 - natural-compare: 1.4.0 - ts-api-utils: 2.5.0(@typescript/typescript6@6.0.2) - typescript: '@typescript/typescript6@6.0.2' - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/parser@8.55.0(@typescript/typescript6@6.0.2)(eslint@9.39.2(jiti@2.6.1))': + '@typescript-eslint/parser@8.55.0(@typescript/typescript6@6.0.2)(eslint@9.39.2(jiti@2.7.0))': dependencies: '@typescript-eslint/scope-manager': 8.55.0 '@typescript-eslint/types': 8.55.0 '@typescript-eslint/typescript-estree': 8.55.0(@typescript/typescript6@6.0.2) '@typescript-eslint/visitor-keys': 8.55.0 debug: 4.4.3 - eslint: 9.39.2(jiti@2.6.1) - typescript: '@typescript/typescript6@6.0.2' - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/parser@8.64.0(@typescript/typescript6@6.0.2)(eslint@9.39.2(jiti@2.6.1))': - dependencies: - '@typescript-eslint/scope-manager': 8.64.0 - '@typescript-eslint/types': 8.64.0 - '@typescript-eslint/typescript-estree': 8.64.0(@typescript/typescript6@6.0.2) - '@typescript-eslint/visitor-keys': 8.64.0 - debug: 4.4.3 - eslint: 9.39.2(jiti@2.6.1) + eslint: 9.39.2(jiti@2.7.0) typescript: '@typescript/typescript6@6.0.2' transitivePeerDependencies: - supports-color @@ -9527,61 +9873,29 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.64.0(@typescript/typescript6@6.0.2)': - dependencies: - '@typescript-eslint/tsconfig-utils': 8.64.0(@typescript/typescript6@6.0.2) - '@typescript-eslint/types': 8.64.0 - debug: 4.4.3 - typescript: '@typescript/typescript6@6.0.2' - transitivePeerDependencies: - - supports-color - '@typescript-eslint/scope-manager@8.55.0': dependencies: '@typescript-eslint/types': 8.55.0 '@typescript-eslint/visitor-keys': 8.55.0 - '@typescript-eslint/scope-manager@8.64.0': - dependencies: - '@typescript-eslint/types': 8.64.0 - '@typescript-eslint/visitor-keys': 8.64.0 - '@typescript-eslint/tsconfig-utils@8.55.0(@typescript/typescript6@6.0.2)': dependencies: typescript: '@typescript/typescript6@6.0.2' - '@typescript-eslint/tsconfig-utils@8.64.0(@typescript/typescript6@6.0.2)': - dependencies: - typescript: '@typescript/typescript6@6.0.2' - - '@typescript-eslint/type-utils@8.55.0(@typescript/typescript6@6.0.2)(eslint@9.39.2(jiti@2.6.1))': + '@typescript-eslint/type-utils@8.55.0(@typescript/typescript6@6.0.2)(eslint@9.39.2(jiti@2.7.0))': dependencies: '@typescript-eslint/types': 8.55.0 '@typescript-eslint/typescript-estree': 8.55.0(@typescript/typescript6@6.0.2) - '@typescript-eslint/utils': 8.55.0(@typescript/typescript6@6.0.2)(eslint@9.39.2(jiti@2.6.1)) + '@typescript-eslint/utils': 8.55.0(@typescript/typescript6@6.0.2)(eslint@9.39.2(jiti@2.7.0)) debug: 4.4.3 - eslint: 9.39.2(jiti@2.6.1) + eslint: 9.39.2(jiti@2.7.0) ts-api-utils: 2.4.0(@typescript/typescript6@6.0.2) typescript: '@typescript/typescript6@6.0.2' transitivePeerDependencies: - supports-color - '@typescript-eslint/type-utils@8.64.0(@typescript/typescript6@6.0.2)(eslint@9.39.2(jiti@2.6.1))': - dependencies: - '@typescript-eslint/types': 8.64.0 - '@typescript-eslint/typescript-estree': 8.64.0(@typescript/typescript6@6.0.2) - '@typescript-eslint/utils': 8.64.0(@typescript/typescript6@6.0.2)(eslint@9.39.2(jiti@2.6.1)) - debug: 4.4.3 - eslint: 9.39.2(jiti@2.6.1) - ts-api-utils: 2.5.0(@typescript/typescript6@6.0.2) - typescript: '@typescript/typescript6@6.0.2' - transitivePeerDependencies: - - supports-color - '@typescript-eslint/types@8.55.0': {} - '@typescript-eslint/types@8.64.0': {} - '@typescript-eslint/typescript-estree@8.55.0(@typescript/typescript6@6.0.2)': dependencies: '@typescript-eslint/project-service': 8.55.0(@typescript/typescript6@6.0.2) @@ -9597,39 +9911,13 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/typescript-estree@8.64.0(@typescript/typescript6@6.0.2)': + '@typescript-eslint/utils@8.55.0(@typescript/typescript6@6.0.2)(eslint@9.39.2(jiti@2.7.0))': dependencies: - '@typescript-eslint/project-service': 8.64.0(@typescript/typescript6@6.0.2) - '@typescript-eslint/tsconfig-utils': 8.64.0(@typescript/typescript6@6.0.2) - '@typescript-eslint/types': 8.64.0 - '@typescript-eslint/visitor-keys': 8.64.0 - debug: 4.4.3 - minimatch: 10.2.4 - semver: 7.7.4 - tinyglobby: 0.2.15 - ts-api-utils: 2.5.0(@typescript/typescript6@6.0.2) - typescript: '@typescript/typescript6@6.0.2' - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/utils@8.55.0(@typescript/typescript6@6.0.2)(eslint@9.39.2(jiti@2.6.1))': - dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@2.6.1)) + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@2.7.0)) '@typescript-eslint/scope-manager': 8.55.0 '@typescript-eslint/types': 8.55.0 '@typescript-eslint/typescript-estree': 8.55.0(@typescript/typescript6@6.0.2) - eslint: 9.39.2(jiti@2.6.1) - typescript: '@typescript/typescript6@6.0.2' - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/utils@8.64.0(@typescript/typescript6@6.0.2)(eslint@9.39.2(jiti@2.6.1))': - dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@2.6.1)) - '@typescript-eslint/scope-manager': 8.64.0 - '@typescript-eslint/types': 8.64.0 - '@typescript-eslint/typescript-estree': 8.64.0(@typescript/typescript6@6.0.2) - eslint: 9.39.2(jiti@2.6.1) + eslint: 9.39.2(jiti@2.7.0) typescript: '@typescript/typescript6@6.0.2' transitivePeerDependencies: - supports-color @@ -9639,11 +9927,6 @@ snapshots: '@typescript-eslint/types': 8.55.0 eslint-visitor-keys: 4.2.1 - '@typescript-eslint/visitor-keys@8.64.0': - dependencies: - '@typescript-eslint/types': 8.64.0 - eslint-visitor-keys: 5.0.1 - '@typescript/typescript-aix-ppc64@7.0.2': optional: true @@ -9785,7 +10068,7 @@ snapshots: next: 15.4.11(@babel/core@7.29.0)(@playwright/test@1.58.2)(babel-plugin-macros@3.1.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.77.4) react: 19.2.4 - '@vitejs/plugin-react@4.5.2(vite@7.3.1(@types/node@22.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.77.4)(tsx@4.21.0)(yaml@2.8.2))': + '@vitejs/plugin-react@4.5.2(vite@7.3.1(@types/node@22.19.9)(jiti@2.7.0)(lightningcss@1.30.2)(sass@1.77.4)(tsx@4.21.0)(yaml@2.9.0))': dependencies: '@babel/core': 7.29.0 '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.0) @@ -9793,7 +10076,7 @@ snapshots: '@rolldown/pluginutils': 1.0.0-beta.11 '@types/babel__core': 7.20.5 react-refresh: 0.17.0 - vite: 7.3.1(@types/node@22.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.77.4)(tsx@4.21.0)(yaml@2.8.2) + vite: 7.3.1(@types/node@22.19.9)(jiti@2.7.0)(lightningcss@1.30.2)(sass@1.77.4)(tsx@4.21.0)(yaml@2.9.0) transitivePeerDependencies: - supports-color @@ -9806,14 +10089,14 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.0.3 - '@vitest/mocker@4.0.18(msw@2.12.10(@types/node@22.19.9)(@typescript/typescript6@6.0.2))(vite@7.3.1(@types/node@22.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.77.4)(tsx@4.21.0)(yaml@2.8.2))': + '@vitest/mocker@4.0.18(msw@2.12.10(@types/node@22.19.9)(@typescript/typescript6@6.0.2))(vite@7.3.1(@types/node@22.19.9)(jiti@2.7.0)(lightningcss@1.30.2)(sass@1.77.4)(tsx@4.21.0)(yaml@2.9.0))': dependencies: '@vitest/spy': 4.0.18 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: msw: 2.12.10(@types/node@22.19.9)(@typescript/typescript6@6.0.2) - vite: 7.3.1(@types/node@22.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.77.4)(tsx@4.21.0)(yaml@2.8.2) + vite: 7.3.1(@types/node@22.19.9)(jiti@2.7.0)(lightningcss@1.30.2)(sass@1.77.4)(tsx@4.21.0)(yaml@2.9.0) '@vitest/pretty-format@4.0.18': dependencies: @@ -10647,19 +10930,19 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-config-next@15.4.11(@typescript/typescript6@6.0.2)(eslint@9.39.2(jiti@2.6.1)): + eslint-config-next@15.4.11(@typescript/typescript6@6.0.2)(eslint@9.39.2(jiti@2.7.0)): dependencies: '@next/eslint-plugin-next': 15.4.11 '@rushstack/eslint-patch': 1.16.1 - '@typescript-eslint/eslint-plugin': 8.55.0(@typescript-eslint/parser@8.55.0(@typescript/typescript6@6.0.2)(eslint@9.39.2(jiti@2.6.1)))(@typescript/typescript6@6.0.2)(eslint@9.39.2(jiti@2.6.1)) - '@typescript-eslint/parser': 8.55.0(@typescript/typescript6@6.0.2)(eslint@9.39.2(jiti@2.6.1)) - eslint: 9.39.2(jiti@2.6.1) + '@typescript-eslint/eslint-plugin': 8.55.0(@typescript-eslint/parser@8.55.0(@typescript/typescript6@6.0.2)(eslint@9.39.2(jiti@2.7.0)))(@typescript/typescript6@6.0.2)(eslint@9.39.2(jiti@2.7.0)) + '@typescript-eslint/parser': 8.55.0(@typescript/typescript6@6.0.2)(eslint@9.39.2(jiti@2.7.0)) + eslint: 9.39.2(jiti@2.7.0) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.2(jiti@2.6.1)) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.64.0(@typescript/typescript6@6.0.2)(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)) - eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.2(jiti@2.6.1)) - eslint-plugin-react: 7.37.5(eslint@9.39.2(jiti@2.6.1)) - eslint-plugin-react-hooks: 5.2.0(eslint@9.39.2(jiti@2.6.1)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.2(jiti@2.7.0)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.55.0(@typescript/typescript6@6.0.2)(eslint@9.39.2(jiti@2.7.0)))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@2.7.0)) + eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.2(jiti@2.7.0)) + eslint-plugin-react: 7.37.5(eslint@9.39.2(jiti@2.7.0)) + eslint-plugin-react-hooks: 5.2.0(eslint@9.39.2(jiti@2.7.0)) optionalDependencies: typescript: '@typescript/typescript6@6.0.2' transitivePeerDependencies: @@ -10667,9 +10950,9 @@ snapshots: - eslint-plugin-import-x - supports-color - eslint-config-prettier@10.1.8(eslint@9.39.2(jiti@2.6.1)): + eslint-config-prettier@10.1.8(eslint@9.39.2(jiti@2.7.0)): dependencies: - eslint: 9.39.2(jiti@2.6.1) + eslint: 9.39.2(jiti@2.7.0) eslint-import-resolver-node@0.3.9: dependencies: @@ -10679,38 +10962,39 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.2(jiti@2.6.1)): + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.2(jiti@2.7.0)): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.3 - eslint: 9.39.2(jiti@2.6.1) + eslint: 9.39.2(jiti@2.7.0) get-tsconfig: 4.13.6 is-bun-module: 2.0.0 stable-hash: 0.0.5 tinyglobby: 0.2.15 unrs-resolver: 1.11.1 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.64.0(@typescript/typescript6@6.0.2)(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.55.0(@typescript/typescript6@6.0.2)(eslint@9.39.2(jiti@2.7.0)))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@2.7.0)) transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.64.0(@typescript/typescript6@6.0.2)(eslint@9.39.2(jiti@2.6.1)))(eslint-import-resolver-node@0.3.9)(eslint@9.39.2(jiti@2.6.1)): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.55.0(@typescript/typescript6@6.0.2)(eslint@9.39.2(jiti@2.7.0)))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@2.7.0)): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 8.64.0(@typescript/typescript6@6.0.2)(eslint@9.39.2(jiti@2.6.1)) - eslint: 9.39.2(jiti@2.6.1) + '@typescript-eslint/parser': 8.55.0(@typescript/typescript6@6.0.2)(eslint@9.39.2(jiti@2.7.0)) + eslint: 9.39.2(jiti@2.7.0) eslint-import-resolver-node: 0.3.9 + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.2(jiti@2.7.0)) transitivePeerDependencies: - supports-color - eslint-plugin-check-file@3.3.1(eslint@9.39.2(jiti@2.6.1)): + eslint-plugin-check-file@3.3.1(eslint@9.39.2(jiti@2.7.0)): dependencies: - eslint: 9.39.2(jiti@2.6.1) + eslint: 9.39.2(jiti@2.7.0) is-glob: 4.0.3 micromatch: 4.0.8 - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.64.0(@typescript/typescript6@6.0.2)(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.55.0(@typescript/typescript6@6.0.2)(eslint@9.39.2(jiti@2.7.0)))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@2.7.0)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -10719,9 +11003,9 @@ snapshots: array.prototype.flatmap: 1.3.3 debug: 3.2.7 doctrine: 2.1.0 - eslint: 9.39.2(jiti@2.6.1) + eslint: 9.39.2(jiti@2.7.0) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.64.0(@typescript/typescript6@6.0.2)(eslint@9.39.2(jiti@2.6.1)))(eslint-import-resolver-node@0.3.9)(eslint@9.39.2(jiti@2.6.1)) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.55.0(@typescript/typescript6@6.0.2)(eslint@9.39.2(jiti@2.7.0)))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@2.7.0)) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -10733,13 +11017,13 @@ snapshots: string.prototype.trimend: 1.0.9 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 8.64.0(@typescript/typescript6@6.0.2)(eslint@9.39.2(jiti@2.6.1)) + '@typescript-eslint/parser': 8.55.0(@typescript/typescript6@6.0.2)(eslint@9.39.2(jiti@2.7.0)) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack - supports-color - eslint-plugin-jsx-a11y@6.10.2(eslint@9.39.2(jiti@2.6.1)): + eslint-plugin-jsx-a11y@6.10.2(eslint@9.39.2(jiti@2.7.0)): dependencies: aria-query: 5.3.2 array-includes: 3.1.9 @@ -10749,7 +11033,7 @@ snapshots: axobject-query: 4.1.0 damerau-levenshtein: 1.0.8 emoji-regex: 9.2.2 - eslint: 9.39.2(jiti@2.6.1) + eslint: 9.39.2(jiti@2.7.0) hasown: 2.0.2 jsx-ast-utils: 3.3.5 language-tags: 1.0.9 @@ -10758,11 +11042,11 @@ snapshots: safe-regex-test: 1.1.0 string.prototype.includes: 2.0.1 - eslint-plugin-react-hooks@5.2.0(eslint@9.39.2(jiti@2.6.1)): + eslint-plugin-react-hooks@5.2.0(eslint@9.39.2(jiti@2.7.0)): dependencies: - eslint: 9.39.2(jiti@2.6.1) + eslint: 9.39.2(jiti@2.7.0) - eslint-plugin-react@7.37.5(eslint@9.39.2(jiti@2.6.1)): + eslint-plugin-react@7.37.5(eslint@9.39.2(jiti@2.7.0)): dependencies: array-includes: 3.1.9 array.prototype.findlast: 1.2.5 @@ -10770,7 +11054,7 @@ snapshots: array.prototype.tosorted: 1.1.4 doctrine: 2.1.0 es-iterator-helpers: 1.2.2 - eslint: 9.39.2(jiti@2.6.1) + eslint: 9.39.2(jiti@2.7.0) estraverse: 5.3.0 hasown: 2.0.2 jsx-ast-utils: 3.3.5 @@ -10793,11 +11077,9 @@ snapshots: eslint-visitor-keys@4.2.1: {} - eslint-visitor-keys@5.0.1: {} - - eslint@9.39.2(jiti@2.6.1): + eslint@9.39.2(jiti@2.7.0): dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@2.6.1)) + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@2.7.0)) '@eslint-community/regexpp': 4.12.2 '@eslint/config-array': 0.21.1 '@eslint/config-helpers': 0.4.2 @@ -10832,7 +11114,7 @@ snapshots: natural-compare: 1.4.0 optionator: 0.9.4 optionalDependencies: - jiti: 2.6.1 + jiti: 2.7.0 transitivePeerDependencies: - supports-color @@ -10978,10 +11260,18 @@ snapshots: dependencies: reusify: 1.1.0 + fd-package-json@2.0.0: + dependencies: + walk-up-path: 4.0.0 + fdir@6.5.0(picomatch@4.0.3): optionalDependencies: picomatch: 4.0.3 + fdir@6.5.0(picomatch@4.0.5): + optionalDependencies: + picomatch: 4.0.5 + fetch-blob@3.2.0: dependencies: node-domexception: 1.0.0 @@ -11053,6 +11343,10 @@ snapshots: dependencies: is-callable: 1.2.7 + formatly@0.3.0: + dependencies: + fd-package-json: 2.0.0 + formdata-polyfill@4.0.10: dependencies: fetch-blob: 3.2.0 @@ -11137,6 +11431,10 @@ snapshots: dependencies: resolve-pkg-maps: 1.0.0 + get-tsconfig@4.14.0: + dependencies: + resolve-pkg-maps: 1.0.0 + get-tsconfig@4.8.1: dependencies: resolve-pkg-maps: 1.0.0 @@ -11504,6 +11802,8 @@ snapshots: jiti@2.6.1: {} + jiti@2.7.0: {} + jose@5.10.0: {} jose@6.2.1: {} @@ -11601,6 +11901,22 @@ snapshots: kleur@4.1.5: {} + knip@6.27.0: + dependencies: + fdir: 6.5.0(picomatch@4.0.5) + formatly: 0.3.0 + get-tsconfig: 4.14.0 + jiti: 2.7.0 + oxc-parser: 0.137.0 + oxc-resolver: 11.21.3 + picomatch: 4.0.5 + smol-toml: 1.7.0 + strip-json-comments: 5.0.3 + tinyglobby: 0.2.17 + unbash: 4.0.3 + yaml: 2.9.0 + zod: 4.3.6 + language-subtag-registry@0.3.23: {} language-tags@1.0.9: @@ -12186,6 +12502,53 @@ snapshots: object-keys: 1.1.1 safe-push-apply: 1.0.0 + oxc-parser@0.137.0: + dependencies: + '@oxc-project/types': 0.137.0 + optionalDependencies: + '@oxc-parser/binding-android-arm-eabi': 0.137.0 + '@oxc-parser/binding-android-arm64': 0.137.0 + '@oxc-parser/binding-darwin-arm64': 0.137.0 + '@oxc-parser/binding-darwin-x64': 0.137.0 + '@oxc-parser/binding-freebsd-x64': 0.137.0 + '@oxc-parser/binding-linux-arm-gnueabihf': 0.137.0 + '@oxc-parser/binding-linux-arm-musleabihf': 0.137.0 + '@oxc-parser/binding-linux-arm64-gnu': 0.137.0 + '@oxc-parser/binding-linux-arm64-musl': 0.137.0 + '@oxc-parser/binding-linux-ppc64-gnu': 0.137.0 + '@oxc-parser/binding-linux-riscv64-gnu': 0.137.0 + '@oxc-parser/binding-linux-riscv64-musl': 0.137.0 + '@oxc-parser/binding-linux-s390x-gnu': 0.137.0 + '@oxc-parser/binding-linux-x64-gnu': 0.137.0 + '@oxc-parser/binding-linux-x64-musl': 0.137.0 + '@oxc-parser/binding-openharmony-arm64': 0.137.0 + '@oxc-parser/binding-wasm32-wasi': 0.137.0 + '@oxc-parser/binding-win32-arm64-msvc': 0.137.0 + '@oxc-parser/binding-win32-ia32-msvc': 0.137.0 + '@oxc-parser/binding-win32-x64-msvc': 0.137.0 + + oxc-resolver@11.21.3: + optionalDependencies: + '@oxc-resolver/binding-android-arm-eabi': 11.21.3 + '@oxc-resolver/binding-android-arm64': 11.21.3 + '@oxc-resolver/binding-darwin-arm64': 11.21.3 + '@oxc-resolver/binding-darwin-x64': 11.21.3 + '@oxc-resolver/binding-freebsd-x64': 11.21.3 + '@oxc-resolver/binding-linux-arm-gnueabihf': 11.21.3 + '@oxc-resolver/binding-linux-arm-musleabihf': 11.21.3 + '@oxc-resolver/binding-linux-arm64-gnu': 11.21.3 + '@oxc-resolver/binding-linux-arm64-musl': 11.21.3 + '@oxc-resolver/binding-linux-ppc64-gnu': 11.21.3 + '@oxc-resolver/binding-linux-riscv64-gnu': 11.21.3 + '@oxc-resolver/binding-linux-riscv64-musl': 11.21.3 + '@oxc-resolver/binding-linux-s390x-gnu': 11.21.3 + '@oxc-resolver/binding-linux-x64-gnu': 11.21.3 + '@oxc-resolver/binding-linux-x64-musl': 11.21.3 + '@oxc-resolver/binding-openharmony-arm64': 11.21.3 + '@oxc-resolver/binding-wasm32-wasi': 11.21.3 + '@oxc-resolver/binding-win32-arm64-msvc': 11.21.3 + '@oxc-resolver/binding-win32-x64-msvc': 11.21.3 + p-limit@3.1.0: dependencies: yocto-queue: 0.1.0 @@ -12290,8 +12653,13 @@ snapshots: pg-cloudflare@1.3.0: optional: true + pg-cloudflare@1.4.0: + optional: true + pg-connection-string@2.11.0: {} + pg-connection-string@2.14.0: {} + pg-int8@1.0.1: {} pg-numeric@1.0.2: {} @@ -12300,8 +12668,14 @@ snapshots: dependencies: pg: 8.16.3 + pg-pool@3.14.0(pg@8.22.0): + dependencies: + pg: 8.22.0 + pg-protocol@1.11.0: {} + pg-protocol@1.15.0: {} + pg-types@2.2.0: dependencies: pg-int8: 1.0.1 @@ -12330,6 +12704,16 @@ snapshots: optionalDependencies: pg-cloudflare: 1.3.0 + pg@8.22.0: + dependencies: + pg-connection-string: 2.14.0 + pg-pool: 3.14.0(pg@8.22.0) + pg-protocol: 1.15.0 + pg-types: 2.2.0 + pgpass: 1.0.5 + optionalDependencies: + pg-cloudflare: 1.4.0 + pgpass@1.0.5: dependencies: split2: 4.2.0 @@ -12340,6 +12724,8 @@ snapshots: picomatch@4.0.3: {} + picomatch@4.0.5: {} + pino-abstract-transport@2.0.0: dependencies: split2: 4.2.0 @@ -13024,6 +13410,8 @@ snapshots: sisteransi@1.0.5: {} + smol-toml@1.7.0: {} + sonic-boom@4.2.1: dependencies: atomic-sleep: 1.0.0 @@ -13216,6 +13604,11 @@ snapshots: fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.5) + picomatch: 4.0.5 + tinyrainbow@3.0.3: {} tldts-core@7.0.23: {} @@ -13275,10 +13668,6 @@ snapshots: dependencies: typescript: '@typescript/typescript6@6.0.2' - ts-api-utils@2.5.0(@typescript/typescript6@6.0.2): - dependencies: - typescript: '@typescript/typescript6@6.0.2' - ts-essentials@10.0.3(@typescript/typescript6@6.0.2): optionalDependencies: typescript: '@typescript/typescript6@6.0.2' @@ -13363,17 +13752,6 @@ snapshots: possible-typed-array-names: 1.1.0 reflect.getprototypeof: 1.0.10 - typescript-eslint@8.64.0(@typescript/typescript6@6.0.2)(eslint@9.39.2(jiti@2.6.1)): - dependencies: - '@typescript-eslint/eslint-plugin': 8.64.0(@typescript-eslint/parser@8.64.0(@typescript/typescript6@6.0.2)(eslint@9.39.2(jiti@2.6.1)))(@typescript/typescript6@6.0.2)(eslint@9.39.2(jiti@2.6.1)) - '@typescript-eslint/parser': 8.64.0(@typescript/typescript6@6.0.2)(eslint@9.39.2(jiti@2.6.1)) - '@typescript-eslint/typescript-estree': 8.64.0(@typescript/typescript6@6.0.2) - '@typescript-eslint/utils': 8.64.0(@typescript/typescript6@6.0.2)(eslint@9.39.2(jiti@2.6.1)) - eslint: 9.39.2(jiti@2.6.1) - typescript: '@typescript/typescript6@6.0.2' - transitivePeerDependencies: - - supports-color - typescript@6.0.3: {} typescript@7.0.2: @@ -13401,6 +13779,8 @@ snapshots: uint8array-extras@1.5.0: {} + unbash@4.0.3: {} + unbox-primitive@1.1.0: dependencies: call-bound: 1.0.4 @@ -13528,17 +13908,17 @@ snapshots: '@types/unist': 3.0.3 unist-util-stringify-position: 4.0.0 - vite-tsconfig-paths@6.0.5(@typescript/typescript6@6.0.2)(vite@7.3.1(@types/node@22.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.77.4)(tsx@4.21.0)(yaml@2.8.2)): + vite-tsconfig-paths@6.0.5(@typescript/typescript6@6.0.2)(vite@7.3.1(@types/node@22.19.9)(jiti@2.7.0)(lightningcss@1.30.2)(sass@1.77.4)(tsx@4.21.0)(yaml@2.9.0)): dependencies: debug: 4.4.3 globrex: 0.1.2 tsconfck: 3.1.6(@typescript/typescript6@6.0.2) - vite: 7.3.1(@types/node@22.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.77.4)(tsx@4.21.0)(yaml@2.8.2) + vite: 7.3.1(@types/node@22.19.9)(jiti@2.7.0)(lightningcss@1.30.2)(sass@1.77.4)(tsx@4.21.0)(yaml@2.9.0) transitivePeerDependencies: - supports-color - typescript - vite@7.3.1(@types/node@22.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.77.4)(tsx@4.21.0)(yaml@2.8.2): + vite@7.3.1(@types/node@22.19.9)(jiti@2.7.0)(lightningcss@1.30.2)(sass@1.77.4)(tsx@4.21.0)(yaml@2.9.0): dependencies: esbuild: 0.27.3 fdir: 6.5.0(picomatch@4.0.3) @@ -13549,16 +13929,16 @@ snapshots: optionalDependencies: '@types/node': 22.19.9 fsevents: 2.3.3 - jiti: 2.6.1 + jiti: 2.7.0 lightningcss: 1.30.2 sass: 1.77.4 tsx: 4.21.0 - yaml: 2.8.2 + yaml: 2.9.0 - vitest@4.0.18(@types/node@22.19.9)(happy-dom@20.8.3)(jiti@2.6.1)(jsdom@28.0.0(@noble/hashes@1.8.0))(lightningcss@1.30.2)(msw@2.12.10(@types/node@22.19.9)(@typescript/typescript6@6.0.2))(sass@1.77.4)(tsx@4.21.0)(yaml@2.8.2): + vitest@4.0.18(@types/node@22.19.9)(happy-dom@20.8.3)(jiti@2.7.0)(jsdom@28.0.0(@noble/hashes@1.8.0))(lightningcss@1.30.2)(msw@2.12.10(@types/node@22.19.9)(@typescript/typescript6@6.0.2))(sass@1.77.4)(tsx@4.21.0)(yaml@2.9.0): dependencies: '@vitest/expect': 4.0.18 - '@vitest/mocker': 4.0.18(msw@2.12.10(@types/node@22.19.9)(@typescript/typescript6@6.0.2))(vite@7.3.1(@types/node@22.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.77.4)(tsx@4.21.0)(yaml@2.8.2)) + '@vitest/mocker': 4.0.18(msw@2.12.10(@types/node@22.19.9)(@typescript/typescript6@6.0.2))(vite@7.3.1(@types/node@22.19.9)(jiti@2.7.0)(lightningcss@1.30.2)(sass@1.77.4)(tsx@4.21.0)(yaml@2.9.0)) '@vitest/pretty-format': 4.0.18 '@vitest/runner': 4.0.18 '@vitest/snapshot': 4.0.18 @@ -13575,7 +13955,7 @@ snapshots: tinyexec: 1.0.2 tinyglobby: 0.2.15 tinyrainbow: 3.0.3 - vite: 7.3.1(@types/node@22.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.77.4)(tsx@4.21.0)(yaml@2.8.2) + vite: 7.3.1(@types/node@22.19.9)(jiti@2.7.0)(lightningcss@1.30.2)(sass@1.77.4)(tsx@4.21.0)(yaml@2.9.0) why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 22.19.9 @@ -13598,6 +13978,8 @@ snapshots: dependencies: xml-name-validator: 5.0.0 + walk-up-path@4.0.0: {} + web-streams-polyfill@3.3.3: {} webidl-conversions@8.0.1: {} @@ -13712,8 +14094,7 @@ snapshots: yaml@1.10.2: {} - yaml@2.8.2: - optional: true + yaml@2.9.0: {} yargs-parser@21.1.1: {} diff --git a/scripts/build-bahia-demographics.mjs b/scripts/build-bahia-demographics.mjs index 7133abb3..2a03dfcb 100644 --- a/scripts/build-bahia-demographics.mjs +++ b/scripts/build-bahia-demographics.mjs @@ -130,7 +130,7 @@ const buildDemographicsByCode = ({ ageCategoryRows, femaleRows, medianRows }) => current.medianAge = Number.isFinite(median) ? median : null } - for (const [code, record] of byCode.entries()) { + for (const record of byCode.values()) { record.population = Object.values(record.ageBands).reduce((sum, value) => sum + value, 0) record.sexShareFemale = record.population > 0 ? record.femalePopulation / record.population : 0 diff --git a/scripts/build-election-aggregates.mjs b/scripts/build-election-aggregates.mjs new file mode 100644 index 00000000..1000fe3d --- /dev/null +++ b/scripts/build-election-aggregates.mjs @@ -0,0 +1,111 @@ +/** + * Builds the committed per-municipality election baseline artifact consumed by + * the campaign map (src/lib/electionAggregates/bahia-federal-baseline.json). + * + * Why an artifact instead of runtime queries: the 2014/2018/2022 TSE results + * are immutable, but the map used to re-scan statewide city×zone slices on + * every dashboard visit. Precomputing the per-municipality sums (Solla nominal + * votes + federal T1 valid votes, cut by the same `municipalityCatalog` + * geography used at runtime — Salvador split into zone municipalities, every + * other municipality whole) removes all historical-year DB work. + * + * Why not compute during `pnpm build`: Vercel builds must not depend on the + * production database CONTENT (TSE seeds are local-only by policy), and the + * artifact should only change when the electoral scope deliberately changes. + * + * Prerequisite: `pnpm db:seed:tse` (2022) + `--year=2018` + `--year=2014` on + * the LOCAL database. + * + * Usage: + * pnpm build:election-aggregates + * + * Safety: read-only against the local database (assertLocalDatabase guard); + * writes only src/lib/electionAggregates/. + */ + +import { mkdir, writeFile } from 'node:fs/promises' +import { dirname, join } from 'node:path' +import { fileURLToPath } from 'node:url' +import { config as loadEnv } from 'dotenv' +import { getPayload } from 'payload' + +import { assertLocalDatabase } from './assert-local-database.mjs' + +loadEnv({ path: '.env.local' }) +loadEnv({ path: '.env' }) + +const payloadConfig = (await import('../src/payload.config.ts')).default +const { BASELINE_TICKET_2022, HISTORICAL_SERIES_YEARS } = await import( + '../src/lib/electionResults.ts' +) +const { municipalityCatalog } = await import('../src/lib/municipalityCatalog.ts') +const { municipalityElectionGeography } = await import( + '../src/utilities/municipalityElectionGeography.ts' +) +const { + loadCandidateVotesByCityZone, + loadValidVotesByCityZone, + sumVotesForGeography, +} = await import('../src/utilities/municipalityElectoralBaseline.ts') + +const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..') +const OUTPUT_DIR = join(ROOT, 'src', 'lib', 'electionAggregates') +const OUTPUT_FILE = join(OUTPUT_DIR, 'bahia-federal-baseline.json') + +assertLocalDatabase( + 'build:election-aggregates', + 'This script only reads the LOCAL seeded TSE data (pnpm db:seed:tse).', +) + +const payload = await getPayload({ config: payloadConfig }) + +/** CLI reader — satisfies the election-data read assertion (admin realm). */ +const cliReader = { collection: 'users' } + +const candidateNumber = BASELINE_TICKET_2022.candidate.candidateNumber +const municipalities = {} +for (const entry of municipalityCatalog) { + municipalities[entry.slug] = { votesByYear: {}, validVotesByYear: {} } +} + +for (const year of HISTORICAL_SERIES_YEARS) { + const [candidateVotes, validVotes] = await Promise.all([ + loadCandidateVotesByCityZone(payload, cliReader, { year, candidateNumber }), + loadValidVotesByCityZone(payload, cliReader, { year }), + ]) + + if (candidateVotes.size === 0) { + console.error( + `[build:election-aggregates] No nominal votes found for ${year}. ` + + `Seed the local database first: pnpm db:seed:tse -- --year=${year}`, + ) + process.exit(1) + } + + let yearTotal = 0 + for (const entry of municipalityCatalog) { + const geography = municipalityElectionGeography(entry) + const votes = sumVotesForGeography(candidateVotes, geography) + const valid = sumVotesForGeography(validVotes, geography) + municipalities[entry.slug].votesByYear[String(year)] = votes + municipalities[entry.slug].validVotesByYear[String(year)] = valid + yearTotal += votes + } + console.log(`[build:election-aggregates] ${year}: ${yearTotal} votes for #${candidateNumber}.`) +} + +const artifact = { + provenance: + 'Derived from TSE open data seeded by pnpm db:seed:tse; regenerate with pnpm build:election-aggregates.', + candidateNumber, + candidateName: BASELINE_TICKET_2022.candidate.name, + years: [...HISTORICAL_SERIES_YEARS], + municipalities, +} + +await mkdir(OUTPUT_DIR, { recursive: true }) +await writeFile(OUTPUT_FILE, `${JSON.stringify(artifact, null, 2)}\n`) +console.log( + `[build:election-aggregates] Wrote ${OUTPUT_FILE} (${municipalityCatalog.length} municipalities × ${HISTORICAL_SERIES_YEARS.length} years).`, +) +process.exit(0) diff --git a/scripts/rename-plaza-to-municipality.mjs b/scripts/rename-plaza-to-municipality.mjs deleted file mode 100644 index 1a937c48..00000000 --- a/scripts/rename-plaza-to-municipality.mjs +++ /dev/null @@ -1,214 +0,0 @@ -#!/usr/bin/env node -/** - * One-shot mechanical rename: plaza → municipality across src/ and tests/. - * Excludes frozen historical migration artifacts. - */ -import { mkdirSync, readdirSync, readFileSync, renameSync, statSync, writeFileSync } from 'fs' -import { dirname, join, relative } from 'path' - -const ROOT = join(import.meta.dirname, '..') - -const EXCLUDE_PATH_FRAGMENTS = [ - 'src/migrations/data/plazaCatalog20260721.ts', - 'src/migrations/20260721_020109_remodel_plazas.ts', - 'src/migrations/20260721_020109_remodel_plazas.json', - 'src/migrations/20260721_133444_add_plaza_expected_votes.ts', - 'src/migrations/20260721_133444_add_plaza_expected_votes.json', - 'node_modules/', - '.git/', - 'scripts/rename-plaza-to-municipality.mjs', -] - -const shouldExclude = (relPath) => EXCLUDE_PATH_FRAGMENTS.some((frag) => relPath.includes(frag)) - -const renamePathSegment = (segment) => - segment - .replace(/PlazaUpdate/g, 'MunicipalityUpdate') - .replace(/plazaUpdate/g, 'municipalityUpdate') - .replace(/Plaza/g, 'Municipality') - .replace(/plaza/g, 'municipality') - .replace(/^pracas$/, 'municipios') - -function walk(dir, files = []) { - for (const entry of readdirSync(dir)) { - const full = join(dir, entry) - const rel = relative(ROOT, full) - if (shouldExclude(rel)) continue - const st = statSync(full) - if (st.isDirectory()) { - if (entry === 'node_modules' || entry === '.git') continue - walk(full, files) - } else if (/\.(ts|tsx|js|jsx|json|md|mdc)$/.test(entry)) { - files.push(full) - } - } - return files -} - -// Rename directories bottom-up -function collectDirs(dir, dirs = []) { - for (const entry of readdirSync(dir)) { - const full = join(dir, entry) - const rel = relative(ROOT, full) - if (shouldExclude(rel)) continue - if (statSync(full).isDirectory()) { - collectDirs(full, dirs) - if (/plaza|Plaza|pracas/i.test(entry)) dirs.push(full) - } - } - return dirs -} - -const dirRenames = collectDirs(join(ROOT, 'src')) - .concat(collectDirs(join(ROOT, 'tests'))) - .sort((a, b) => b.length - a.length) - -for (const dir of dirRenames) { - const parent = dirname(dir) - const base = renamePathSegment(dir.split('/').pop()) - const target = join(parent, base) - if (dir !== target) { - mkdirSync(dirname(target), { recursive: true }) - try { - renameSync(dir, target) - console.log('DIR', relative(ROOT, dir), '->', relative(ROOT, target)) - } catch (e) { - // already renamed - } - } -} - -// Rename files -const allFiles = walk(join(ROOT, 'src')).concat(walk(join(ROOT, 'tests'))) -const fileRenames = allFiles - .filter((f) => /plaza|Plaza|pracas/i.test(f.split('/').pop())) - .sort((a, b) => b.length - a.length) - -for (const file of fileRenames) { - const parent = dirname(file) - const base = renamePathSegment(file.split('/').pop()) - const target = join(parent, base) - if (file !== target) { - try { - renameSync(file, target) - console.log('FILE', relative(ROOT, file), '->', relative(ROOT, target)) - } catch { - /* noop */ - } - } -} - -const REPLACEMENTS = [ - ['/campanha/pracas', '/campanha/municipios'], - ["collection: 'plazaUpdate'", "collection: 'municipalityUpdate'"], - ["collection: 'plaza'", "collection: 'municipality'"], - ["relationTo: 'plazaUpdate'", "relationTo: 'municipalityUpdate'"], - ["relationTo: 'plaza'", "relationTo: 'municipality'"], - ['slug: plazaUpdate', 'slug: municipalityUpdate'], - ['slug: plaza', 'slug: municipality'], - ["'plazaUpdate'", "'municipalityUpdate'"], - ["'plaza'", "'municipality'"], - ['PlazaUpdate', 'MunicipalityUpdate'], - ['plazaUpdate', 'municipalityUpdate'], - ['PlazaEstimateScenario', 'MunicipalityEstimateScenario'], - ['plazaEstimateScenario', 'municipalityEstimateScenario'], - ['PlazaZoneNeighborhoods', 'MunicipalityZoneNeighborhoods'], - ['plazaZoneNeighborhoods', 'municipalityZoneNeighborhoods'], - ['plaza-zone-neighborhoods', 'municipality-zone-neighborhoods'], - ['plaza-catalog', 'municipality-catalog'], - ['plazaStaffFormActions', 'municipalityStaffFormActions'], - ['plazaStaffEditMessages', 'municipalityStaffEditMessages'], - ['plazaStaffVoteTotal', 'municipalityStaffVoteTotal'], - ['StaffPlazaVotes', 'StaffMunicipalityVotes'], - ['aggregatePledgesByPlaza', 'aggregatePledgesByMunicipality'], - ['loadPlazaListPageBundle', 'loadMunicipalityListPageBundle'], - ['loadPlazaMapBundle', 'loadMunicipalityMapBundle'], - ['buildPlazaMapBundleFromPlazas', 'buildMunicipalityMapBundleFromMunicipalities'], - ['buildPlazaMapBundle', 'buildMunicipalityMapBundle'], - ['buildPlazaListWhere', 'buildMunicipalityListWhere'], - ['shouldUpdatePlazaSearchUrl', 'shouldUpdateMunicipalitySearchUrl'], - ['buildPlazaFiltersKey', 'buildMunicipalityFiltersKey'], - ['getAccessiblePlazaIds', 'getAccessibleMunicipalityIds'], - ['canAssignPlazaAdvisors', 'canAssignMunicipalityAdvisors'], - ['canManagePlazaAdvisors', 'canManageMunicipalityAdvisors'], - ['canCreatePlaza', 'canCreateMunicipality'], - ['canDeletePlaza', 'canDeleteMunicipality'], - ['canReadPlaza', 'canReadMunicipality'], - ['canUpdatePlaza', 'canUpdateMunicipality'], - ['validatePlazaAdvisors', 'validateMunicipalityAdvisors'], - ['plazaCatalogEntriesForCity', 'municipalityCatalogEntriesForCity'], - ['getPlazaCatalogEntry', 'getMunicipalityCatalogEntry'], - ['plazaCatalog', 'municipalityCatalog'], - ['isPlazaSlug', 'isMunicipalitySlug'], - ['PlazaCatalogEntry', 'MunicipalityCatalogEntry'], - ['PlazaKind', 'MunicipalityKind'], - ['ZONE_PLAZA_CITIES', 'ZONE_MUNICIPALITY_CITIES'], - ['zonePlazaCitySet', 'zoneMunicipalityCitySet'], - ['plazasByIbgeCode', 'municipalitiesByIbgeCode'], - ['resolvePlazaMapNavigation', 'resolveMunicipalityMapNavigation'], - ['plazaMapNavigation', 'municipalityMapNavigation'], - ['plazaRevalidation', 'municipalityRevalidation'], - ['plazaSlug', 'municipalitySlug'], - ['plazaPageData', 'municipalityPageData'], - ['plazaViewModels', 'municipalityViewModels'], - ['plazaMapData', 'municipalityMapData'], - ['plazaUi', 'municipalityUi'], - ['plazaElectoralBaseline', 'municipalityElectoralBaseline'], - ['plazaElectionGeography', 'municipalityElectionGeography'], - ['plazaCandidateComparison', 'municipalityCandidateComparison'], - ['plazaDetailTabUi', 'municipalityDetailTabUi'], - ['campaignPlazaAccess', 'campaignMunicipalityAccess'], - ['campaignPlazas', 'campaignMunicipalities'], - ['campaignPlazaUpdate', 'campaignMunicipalityUpdate'], - ['updatePlazaStrategy', 'updateMunicipalityStrategy'], - ['PlazaMapBundle', 'MunicipalityMapBundle'], - ['plazaMapBundle', 'municipalityMapBundle'], - ['PlazaList', 'MunicipalityList'], - ['PlazaFilters', 'MunicipalityFilters'], - ['PlazaMap', 'MunicipalityMap'], - ['PlazaStrategy', 'MunicipalityStrategy'], - ['PlazaBaseline', 'MunicipalityBaseline'], - ['PlazaCandidate', 'MunicipalityCandidate'], - ['PlazaAdvisor', 'MunicipalityAdvisor'], - ['PlazaLeadership', 'MunicipalityLeadership'], - ['PlazaPledges', 'MunicipalityPledges'], - ['PlazaTab', 'MunicipalityTab'], - ['PlazaUpdate', 'MunicipalityUpdate'], - ['plazas', 'municipalities'], - ['Plaza', 'Municipality'], - ['plaza', 'municipality'], -] - -const contentFiles = walk(join(ROOT, 'src')) - .concat(walk(join(ROOT, 'tests'))) - .concat( - [ - join(ROOT, 'AGENTS.md'), - join(ROOT, 'PRODUCT.md'), - join(ROOT, 'docs/roadmap.md'), - join(ROOT, '.cursor/rules/projects/nucleos-eleitorais.mdc'), - ].filter((f) => { - try { - statSync(f) - return true - } catch { - return false - } - }), - ) - -for (const file of contentFiles) { - const rel = relative(ROOT, file) - if (shouldExclude(rel)) continue - let content = readFileSync(file, 'utf8') - const original = content - for (const [from, to] of REPLACEMENTS) { - content = content.split(from).join(to) - } - if (content !== original) { - writeFileSync(file, content) - console.log('CONTENT', rel) - } -} - -console.log('Done.') diff --git a/src/app/(campaign)/campanha/(app)/apoiadores/[id]/page.tsx b/src/app/(campaign)/campanha/(app)/apoiadores/[id]/page.tsx index 3142cc6e..9460a666 100644 --- a/src/app/(campaign)/campanha/(app)/apoiadores/[id]/page.tsx +++ b/src/app/(campaign)/campanha/(app)/apoiadores/[id]/page.tsx @@ -13,7 +13,7 @@ import { VoteIntentionControl } from '@/components/campaign/VoteIntentionControl import { Badge } from '@/components/ui/Badge' import { Button } from '@/components/ui/button' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' -import { isCampaignCoordinator } from '@/utilities/campaignAccess' +import { campaignRoleLabels } from '@/utilities/campaignUserProfile' import { getCampaignUser } from '@/utilities/campaignAuth' import { loadSupporterDetailConsentData, @@ -69,9 +69,7 @@ export default async function SupporterDetailPage({ params }: SupporterDetailPag Voltar para apoiadores - - {isCampaignCoordinator(user) ? 'Coordenador Geral' : 'Assessor'} - + {campaignRoleLabels[user.role]}

{supporter.name}

{supporter.city ?? 'Município não informado'} diff --git a/src/app/(campaign)/campanha/(app)/apoiadores/loading.tsx b/src/app/(campaign)/campanha/(app)/apoiadores/loading.tsx new file mode 100644 index 00000000..5550cdf6 --- /dev/null +++ b/src/app/(campaign)/campanha/(app)/apoiadores/loading.tsx @@ -0,0 +1,20 @@ +import { CampaignPageShell } from '@/components/campaign/CampaignPageShell' +import { Skeleton } from '@/components/ui/skeleton' + +/** List-shaped skeleton for /apoiadores and its detail routes. */ +export default function SupportersLoading() { + return ( + +

+ + +
+
+ + +
+ + + + ) +} diff --git a/src/app/(campaign)/campanha/(app)/apoiadores/novo/formActions.ts b/src/app/(campaign)/campanha/(app)/apoiadores/novo/formActions.ts index 4a43e2c4..1fdfc67e 100644 --- a/src/app/(campaign)/campanha/(app)/apoiadores/novo/formActions.ts +++ b/src/app/(campaign)/campanha/(app)/apoiadores/novo/formActions.ts @@ -34,7 +34,7 @@ const safeActionMessages = [ 'Esta pessoa já está cadastrada como apoiador nesta Praça.', 'Existe mais de um contato com este celular. Resolva a duplicidade no admin antes de continuar.', 'Somente a coordenação e a assessoria podem gerenciar apoiadores.', - 'Somente o Coordenador Geral pode cadastrar apoiadores sem Praça.', + 'Somente o Coordenador Geral pode cadastrar apoiadores sem município.', 'Consentimento de cadastro de apoiador ainda não configurado.', 'Consentimento de intenção de voto ainda não configurado.', ] as const diff --git a/src/app/(campaign)/campanha/(app)/apoiadores/novo/page.tsx b/src/app/(campaign)/campanha/(app)/apoiadores/novo/page.tsx index baeeeb9f..73785b44 100644 --- a/src/app/(campaign)/campanha/(app)/apoiadores/novo/page.tsx +++ b/src/app/(campaign)/campanha/(app)/apoiadores/novo/page.tsx @@ -7,7 +7,7 @@ import { createSupporterFormAction } from '@/app/(campaign)/campanha/(app)/apoia import { CampaignScopeBadge } from '@/components/campaign/CampaignScopeBadge' import { SupporterForm } from '@/components/campaign/SupporterForm' import { Button } from '@/components/ui/button' -import { isCampaignCoordinator } from '@/utilities/campaignAccess' +import { campaignRoleLabels } from '@/utilities/campaignUserProfile' import { getCampaignUser } from '@/utilities/campaignAuth' import { loadSupporterCreatePageData } from '@/utilities/supporterPageData' import { canAccessSupporterArea } from '@/utilities/supporterUi' @@ -26,9 +26,7 @@ export default async function NewSupporterPage() { - - {isCampaignCoordinator(user) ? 'Coordenador Geral' : 'Assessor'} - + {campaignRoleLabels[user.role]}

Novo apoiador

Cadastre um apoiador com telefone obrigatório e consentimento LGPD. diff --git a/src/app/(campaign)/campanha/(app)/apoiadores/page.tsx b/src/app/(campaign)/campanha/(app)/apoiadores/page.tsx index 737119f4..c2324581 100644 --- a/src/app/(campaign)/campanha/(app)/apoiadores/page.tsx +++ b/src/app/(campaign)/campanha/(app)/apoiadores/page.tsx @@ -1,3 +1,7 @@ +import { + CampaignListPendingBoundary, + CampaignListResults, +} from '@/components/campaign/CampaignListPending' import config from '@payload-config' import { FileUpIcon, PlusIcon, SearchXIcon } from 'lucide-react' import Link from 'next/link' @@ -19,8 +23,9 @@ import { EmptyMedia, EmptyTitle, } from '@/components/ui/Empty' -import { isCampaignCoordinator } from '@/utilities/campaignAccess' +import { isCampaignCoordinator, isCampaignUnrestricted } from '@/utilities/campaignAccess' import { getCampaignUser } from '@/utilities/campaignAuth' +import { campaignRoleLabels } from '@/utilities/campaignUserProfile' import { loadSupportersPageData } from '@/utilities/supporterPageData' import { buildSupporterFiltersKey, @@ -42,13 +47,47 @@ export default async function SupportersPage({ searchParams }: SupportersPagePro const rawSearchParams = await searchParams const now = new Date() - const { result, state, redirectHref, municipalityOptions, overview } = await loadSupportersPageData( - payload, - user, - rawSearchParams, - ) + const { result, state, redirectHref, municipalityOptions, overview } = + await loadSupportersPageData(payload, user, rawSearchParams) if (redirectHref) redirect(redirectHref) + const listBody = result.docs.length ? ( + <> + {overview ? : null} + toSupporterListItemViewModel(supporter))} + /> +

+

+ {result.totalDocs}{' '} + {result.totalDocs === 1 ? 'apoiador encontrado' : 'apoiadores encontrados'} +

+ buildSupporterListHref(state, page)} + /> +
+ + ) : ( + + + + + Nenhum apoiador encontrado + + Ajuste a busca ou os filtros. Você só vê apoiadores dentro do seu escopo. + + + + + + + ) + return (
@@ -58,8 +97,8 @@ export default async function SupportersPage({ searchParams }: SupportersPagePro Base nominal de apoio com intenção de voto e vínculo opcional a Praças.

- {isCampaignCoordinator(user) - ? 'Coordenador Geral · todos os apoiadores' + {isCampaignUnrestricted(user) + ? `${campaignRoleLabels[user.role]} · todos os apoiadores` : getSupporterScopeLabel(result.totalDocs)} @@ -81,48 +120,15 @@ export default async function SupportersPage({ searchParams }: SupportersPagePro
- + + - {result.docs.length ? ( - <> - {overview ? : null} - toSupporterListItemViewModel(supporter))} - /> -
-

- {result.totalDocs}{' '} - {result.totalDocs === 1 ? 'apoiador encontrado' : 'apoiadores encontrados'} -

- buildSupporterListHref(state, page)} - /> -
- - ) : ( - - - - - Nenhum apoiador encontrado - - Ajuste a busca ou os filtros. Você só vê apoiadores dentro do seu escopo. - - - - - - - )} + {listBody} +
) } diff --git a/src/app/(campaign)/campanha/(app)/demandas/[slug]/page.tsx b/src/app/(campaign)/campanha/(app)/demandas/[slug]/page.tsx index 19c15e86..daf83c54 100644 --- a/src/app/(campaign)/campanha/(app)/demandas/[slug]/page.tsx +++ b/src/app/(campaign)/campanha/(app)/demandas/[slug]/page.tsx @@ -13,7 +13,7 @@ import { campaignDemandStatusLabels, type CampaignDemandStatus, } from '@/lib/schemas/campaignDemand' -import { isCampaignStaff } from '@/utilities/campaignAccess' +import { isCampaignStaff, isCampaignUnrestricted } from '@/utilities/campaignAccess' import { getCampaignUser } from '@/utilities/campaignAuth' import { loadDemandDetail } from '@/utilities/campaignDemandData' import { @@ -51,12 +51,11 @@ export default async function DemandDetailPage({ params }: DemandDetailPageProps const { slug } = await params const [user, payload] = await Promise.all([getCampaignUser(), getPayload({ config })]) if (!user) redirect('/campanha/login') + if (!isCampaignStaff(user)) redirect('/campanha') const demand = await loadDemandDetail(payload, user, slug) if (!demand) notFound() - const isStaffView = isCampaignStaff(user) - return (
@@ -105,82 +104,67 @@ export default async function DemandDetailPage({ params }: DemandDetailPageProps ) : null} - {isStaffView ? ( - <> - + -
-

- Controle interno -

-

- Custo:{' '} - {demand.cost != null ? currencyFormatter.format(demand.cost) : 'não registrado'} -

- {demand.receipts.length ? ( - - ) : ( -

Nenhum comprovante anexado.

- )} - {demand.statusHistory.length ? ( -
-

Histórico

-
    - {demand.statusHistory.map((entry, index) => ( -
  • - {campaignDemandStatusLabels[entry.status]} - {entry.authorName ? ` — ${entry.authorName}` : ''} - {entry.createdAt - ? ` · ${dateTimeFormatter.format(new Date(entry.createdAt))}` - : ''} - {entry.note ? ` · ${entry.note}` : ''} -
  • - ))} -
-
- ) : null} -
- - ) : ( -
-

- {demand.status === 'aberta' - ? 'Sua demanda foi registrada e aguarda análise da assessoria.' - : demand.status === 'em_analise' || demand.status === 'escalada' - ? 'Sua demanda está em análise pela coordenação.' - : 'Sua demanda foi decidida — veja a nota acima.'} -

-
- )} +
+

+ Controle interno +

+

+ Custo: {demand.cost != null ? currencyFormatter.format(demand.cost) : 'não registrado'} +

+ {demand.receipts.length ? ( + + ) : ( +

Nenhum comprovante anexado.

+ )} + {demand.statusHistory.length ? ( +
+

Histórico

+
    + {demand.statusHistory.map((entry, index) => ( +
  • + {campaignDemandStatusLabels[entry.status]} + {entry.authorName ? ` — ${entry.authorName}` : ''} + {entry.createdAt + ? ` · ${dateTimeFormatter.format(new Date(entry.createdAt))}` + : ''} + {entry.note ? ` · ${entry.note}` : ''} +
  • + ))} +
+
+ ) : null} +
) } diff --git a/src/app/(campaign)/campanha/(app)/demandas/nova/formActions.ts b/src/app/(campaign)/campanha/(app)/demandas/nova/formActions.ts index 267bd299..7c1da364 100644 --- a/src/app/(campaign)/campanha/(app)/demandas/nova/formActions.ts +++ b/src/app/(campaign)/campanha/(app)/demandas/nova/formActions.ts @@ -1,38 +1,30 @@ 'use server' -import { redirect } from 'next/navigation' - import { createCampaignDemand } from '@/app/(campaign)/campanha/actions/demand' import { optionalFormText, requiredFormText, requiredRelationshipFormValue } from '@/lib/formData' import { campaignDemandKinds, type CampaignDemandKind } from '@/lib/schemas/campaignDemand' import { - mapCampaignFormActionError, + runCampaignRedirectFormAction, type CampaignFormActionState, } from '@/utilities/campaignFormActionError' export const createDemandFormAction = async ( _state: CampaignFormActionState, formData: FormData, -): Promise => { - let createdSlug: string | null = null - try { - const rawKind = requiredFormText(formData, 'kind') - const demand = await createCampaignDemand({ - title: requiredFormText(formData, 'title'), - kind: campaignDemandKinds.includes(rawKind as CampaignDemandKind) - ? (rawKind as CampaignDemandKind) - : 'outro', - description: optionalFormText(formData, 'description'), - municipality: requiredRelationshipFormValue(formData, 'municipalityId'), - }) - createdSlug = demand.slug - } catch (error) { - return mapCampaignFormActionError({ - error, - safeMessages: ['Somente a coordenação e a assessoria podem abrir demandas.'], - genericMessage: 'Não foi possível abrir a demanda. Verifique os dados e tente novamente.', - }) - } - - redirect(`/campanha/demandas/${createdSlug}`) -} +): Promise => + runCampaignRedirectFormAction({ + execute: () => { + const rawKind = requiredFormText(formData, 'kind') + return createCampaignDemand({ + title: requiredFormText(formData, 'title'), + kind: campaignDemandKinds.includes(rawKind as CampaignDemandKind) + ? (rawKind as CampaignDemandKind) + : 'outro', + description: optionalFormText(formData, 'description'), + municipality: requiredRelationshipFormValue(formData, 'municipalityId'), + }) + }, + redirectTo: (demand) => `/campanha/demandas/${demand.slug}`, + safeMessages: ['Somente a coordenação e a assessoria podem abrir demandas.'], + genericMessage: 'Não foi possível abrir a demanda. Verifique os dados e tente novamente.', + }) diff --git a/src/app/(campaign)/campanha/(app)/demandas/nova/page.tsx b/src/app/(campaign)/campanha/(app)/demandas/nova/page.tsx index 3821e9c7..435ea8ba 100644 --- a/src/app/(campaign)/campanha/(app)/demandas/nova/page.tsx +++ b/src/app/(campaign)/campanha/(app)/demandas/nova/page.tsx @@ -7,6 +7,7 @@ import { getPayload } from 'payload' import { CampaignPageShell } from '@/components/campaign/CampaignPageShell' import { DemandForm } from '@/components/campaign/DemandForm' import { Button } from '@/components/ui/button' +import { isCampaignStaff } from '@/utilities/campaignAccess' import { getCampaignUser } from '@/utilities/campaignAuth' import { loadMunicipalityOptions } from '@/utilities/campaignRelationOptions' import { createDemandFormAction } from './formActions' @@ -14,6 +15,7 @@ import { createDemandFormAction } from './formActions' export default async function NewDemandPage() { const [user, payload] = await Promise.all([getCampaignUser(), getPayload({ config })]) if (!user) redirect('/campanha/login') + if (!isCampaignStaff(user)) redirect('/campanha') const municipalityOptions = await loadMunicipalityOptions(payload, user) diff --git a/src/app/(campaign)/campanha/(app)/demandas/page.tsx b/src/app/(campaign)/campanha/(app)/demandas/page.tsx index a7fc2d5c..60de0edc 100644 --- a/src/app/(campaign)/campanha/(app)/demandas/page.tsx +++ b/src/app/(campaign)/campanha/(app)/demandas/page.tsx @@ -4,7 +4,12 @@ import Link from 'next/link' import { redirect } from 'next/navigation' import { getPayload } from 'payload' +import { CampaignFilterChips } from '@/components/campaign/CampaignFilterChips' import { CampaignListPagination } from '@/components/campaign/CampaignListPagination' +import { + CampaignListPendingBoundary, + CampaignListResults, +} from '@/components/campaign/CampaignListPending' import { CampaignPageShell } from '@/components/campaign/CampaignPageShell' import { Badge } from '@/components/ui/Badge' import { Button } from '@/components/ui/button' @@ -30,6 +35,7 @@ import { campaignDemandStatuses, type CampaignDemandStatus, } from '@/lib/schemas/campaignDemand' +import { isCampaignStaff } from '@/utilities/campaignAccess' import { getCampaignUser } from '@/utilities/campaignAuth' import { loadDemandListPageData, parseDemandListParams } from '@/utilities/campaignDemandData' @@ -54,6 +60,7 @@ export default async function DemandsPage({ searchParams }: DemandsPageProps) { const rawSearchParams = await searchParams const [user, payload] = await Promise.all([getCampaignUser(), getPayload({ config })]) if (!user) redirect('/campanha/login') + if (!isCampaignStaff(user)) redirect('/campanha') const state = parseDemandListParams(rawSearchParams) const { rows, totalDocs, totalPages, openCount } = await loadDemandListPageData( @@ -71,6 +78,78 @@ export default async function DemandsPage({ searchParams }: DemandsPageProps) { return query ? `/campanha/demandas?${query}` : '/campanha/demandas' } + const listBody = rows.length ? ( + <> +
+ + + + Demanda + Tipo + Praça + Solicitante + Status + Aberta em + + + + {rows.map((row) => ( + + + + {row.title} + + + {campaignDemandKindLabels[row.kind]} + {row.municipalityName} + {row.requesterName ?? '—'} + + + {campaignDemandStatusLabels[row.status]} + + + {dateFormatter.format(new Date(row.createdAt))} + + ))} + +
+
+
+

+ {totalDocs} {totalDocs === 1 ? 'demanda' : 'demandas'} +

+ hrefFor(state.status, page)} + /> +
+ + ) : ( + + + + + Nenhuma demanda por aqui + + Abra uma demanda quando precisar de material, transporte, espaço ou apoio para uma ação. + + + + + + + ) + return (
@@ -92,100 +171,21 @@ export default async function DemandsPage({ searchParams }: DemandsPageProps) {
- + + ({ + href: hrefFor(status), + label: campaignDemandStatusLabels[status], + active: state.status === status, + })), + ]} + /> - {rows.length ? ( - <> -
- - - - Demanda - Tipo - Praça - Solicitante - Status - Aberta em - - - - {rows.map((row) => ( - - - - {row.title} - - - {campaignDemandKindLabels[row.kind]} - {row.municipalityName} - - {row.requesterName ?? '—'} - - - - {campaignDemandStatusLabels[row.status]} - - - {dateFormatter.format(new Date(row.createdAt))} - - ))} - -
-
-
-

- {totalDocs} {totalDocs === 1 ? 'demanda' : 'demandas'} -

- hrefFor(state.status, page)} - /> -
- - ) : ( - - - - - Nenhuma demanda por aqui - - Abra uma demanda quando precisar de material, transporte, espaço ou apoio para uma - ação. - - - - - - - )} + {listBody} +
) } diff --git a/src/app/(campaign)/campanha/(app)/dobradinhas/nova/formActions.ts b/src/app/(campaign)/campanha/(app)/dobradinhas/nova/formActions.ts index a47c540a..8949ca92 100644 --- a/src/app/(campaign)/campanha/(app)/dobradinhas/nova/formActions.ts +++ b/src/app/(campaign)/campanha/(app)/dobradinhas/nova/formActions.ts @@ -1,37 +1,28 @@ 'use server' -import { redirect } from 'next/navigation' - import { createStateDeputy } from '@/app/(campaign)/campanha/actions/stateDeputy' -import { nullableFormText, optionalFormText, requiredFormText } from '@/lib/formData' +import { optionalFormText, requiredFormText } from '@/lib/formData' import { - mapCampaignFormActionError, + runCampaignRedirectFormAction, type CampaignFormActionState, } from '@/utilities/campaignFormActionError' export const createStateDeputyFormAction = async ( _state: CampaignFormActionState, formData: FormData, -): Promise => { - let createdSlug: string | null = null - try { - const stateDeputy = await createStateDeputy({ - name: requiredFormText(formData, 'name'), - party: optionalFormText(formData, 'party'), - notes: optionalFormText(formData, 'notes'), - }) - createdSlug = stateDeputy.slug - } catch (error) { - return mapCampaignFormActionError({ - error, - safeMessages: [ - 'Já existe uma dobradinha com este nome.', - 'Somente a coordenação e a assessoria gerenciam dobradinhas.', - ], - genericMessage: - 'Não foi possível cadastrar a dobradinha. Verifique os dados e tente novamente.', - }) - } - - redirect(`/campanha/dobradinhas/${createdSlug}`) -} +): Promise => + runCampaignRedirectFormAction({ + execute: () => + createStateDeputy({ + name: requiredFormText(formData, 'name'), + party: optionalFormText(formData, 'party'), + notes: optionalFormText(formData, 'notes'), + }), + redirectTo: (stateDeputy) => `/campanha/dobradinhas/${stateDeputy.slug}`, + safeMessages: [ + 'Já existe uma dobradinha com este nome.', + 'Somente a coordenação e a assessoria gerenciam dobradinhas.', + ], + genericMessage: + 'Não foi possível cadastrar a dobradinha. Verifique os dados e tente novamente.', + }) diff --git a/src/app/(campaign)/campanha/(app)/dobradinhas/nova/page.tsx b/src/app/(campaign)/campanha/(app)/dobradinhas/nova/page.tsx index 29fac025..86df6742 100644 --- a/src/app/(campaign)/campanha/(app)/dobradinhas/nova/page.tsx +++ b/src/app/(campaign)/campanha/(app)/dobradinhas/nova/page.tsx @@ -1,4 +1,3 @@ -import config from '@payload-config' import { ArrowLeftIcon } from 'lucide-react' import Link from 'next/link' import { redirect } from 'next/navigation' diff --git a/src/app/(campaign)/campanha/(app)/dobradinhas/page.tsx b/src/app/(campaign)/campanha/(app)/dobradinhas/page.tsx index da285424..321525ef 100644 --- a/src/app/(campaign)/campanha/(app)/dobradinhas/page.tsx +++ b/src/app/(campaign)/campanha/(app)/dobradinhas/page.tsx @@ -1,3 +1,8 @@ +import { + CampaignListPendingBoundary, + CampaignListResults, +} from '@/components/campaign/CampaignListPending' +import { CampaignSearchForm } from '@/components/campaign/CampaignSearchForm' import config from '@payload-config' import { PlusIcon, SearchXIcon } from 'lucide-react' import Link from 'next/link' @@ -6,7 +11,6 @@ import { getPayload } from 'payload' import { CampaignListPagination } from '@/components/campaign/CampaignListPagination' import { CampaignPageShell } from '@/components/campaign/CampaignPageShell' -import { Badge } from '@/components/ui/Badge' import { Button } from '@/components/ui/button' import { Empty, @@ -70,83 +74,80 @@ export default async function StateDeputiesPage({ searchParams }: StateDeputiesP
-
- + - - - {rows.length ? ( - <> -
- - - - Nome - Partido - Municípios - Lideranças - - - - {rows.map((row) => ( - - - - {row.name} - - - {row.party ?? '—'} - {row.municipalityCount} - {row.leadershipCount} - - ))} - -
-
-
-

- {totalDocs} {totalDocs === 1 ? 'dobradinha' : 'dobradinhas'} -

- -
- - ) : ( - - - - - Nenhuma dobradinha cadastrada - - Cadastre deputados estaduais parceiros para vincular a municípios e lideranças. - - - - - - - )} + + {rows.length ? ( + <> +
+ + + + Nome + Partido + Municípios + Lideranças + + + + {rows.map((row) => ( + + + + {row.name} + + + {row.party ?? '—'} + {row.municipalityCount} + {row.leadershipCount} + + ))} + +
+
+
+

+ {totalDocs} {totalDocs === 1 ? 'dobradinha' : 'dobradinhas'} +

+ +
+ + ) : ( + + + + + Nenhuma dobradinha cadastrada + + Cadastre deputados estaduais parceiros para vincular a municípios e lideranças. + + + + + + + )} +
+
) } diff --git a/src/app/(campaign)/campanha/(app)/liderancas/nova/formActions.ts b/src/app/(campaign)/campanha/(app)/liderancas/nova/formActions.ts index a4d54625..2bfbc536 100644 --- a/src/app/(campaign)/campanha/(app)/liderancas/nova/formActions.ts +++ b/src/app/(campaign)/campanha/(app)/liderancas/nova/formActions.ts @@ -1,12 +1,10 @@ 'use server' -import { redirect } from 'next/navigation' - import { createLeadership } from '@/app/(campaign)/campanha/actions/leadership' import { optionalFormText, repeatedRelationshipFormValues, requiredFormText } from '@/lib/formData' import { leadershipSectors, leadershipSupportStatuses } from '@/lib/schemas/leadership' import { - mapCampaignFormActionError, + runCampaignRedirectFormAction, type CampaignFormActionState, } from '@/utilities/campaignFormActionError' @@ -20,39 +18,32 @@ const safeMessages = [ export const createLeadershipFormAction = async ( _state: CampaignFormActionState, formData: FormData, -): Promise => { - let createdId: number | null = null - try { - const sector = optionalFormText(formData, 'sector') - const supportStatus = optionalFormText(formData, 'supportStatus') +): Promise => + runCampaignRedirectFormAction({ + execute: () => { + const sector = optionalFormText(formData, 'sector') + const supportStatus = optionalFormText(formData, 'supportStatus') - const leadership = await createLeadership({ - name: requiredFormText(formData, 'name'), - phone: requiredFormText(formData, 'phone'), - email: optionalFormText(formData, 'email'), - municipalities: repeatedRelationshipFormValues(formData, 'municipalities'), - organizations: repeatedRelationshipFormValues(formData, 'organizations'), - stateDeputies: repeatedRelationshipFormValues(formData, 'stateDeputies'), - sector: leadershipSectors.includes(sector as (typeof leadershipSectors)[number]) - ? (sector as (typeof leadershipSectors)[number]) - : undefined, - supportStatus: leadershipSupportStatuses.includes( - supportStatus as (typeof leadershipSupportStatuses)[number], - ) - ? (supportStatus as (typeof leadershipSupportStatuses)[number]) - : 'a_abordar', - notes: optionalFormText(formData, 'notes'), - consentNote: optionalFormText(formData, 'consentNote'), - }) - createdId = leadership.id - } catch (error) { - return mapCampaignFormActionError({ - error, - safeMessages, - genericMessage: - 'Não foi possível cadastrar a liderança. Verifique os dados e tente novamente.', - }) - } - - redirect(`/campanha/liderancas/${createdId}`) -} + return createLeadership({ + name: requiredFormText(formData, 'name'), + phone: requiredFormText(formData, 'phone'), + email: optionalFormText(formData, 'email'), + municipalities: repeatedRelationshipFormValues(formData, 'municipalities'), + organizations: repeatedRelationshipFormValues(formData, 'organizations'), + stateDeputies: repeatedRelationshipFormValues(formData, 'stateDeputies'), + sector: leadershipSectors.includes(sector as (typeof leadershipSectors)[number]) + ? (sector as (typeof leadershipSectors)[number]) + : undefined, + supportStatus: leadershipSupportStatuses.includes( + supportStatus as (typeof leadershipSupportStatuses)[number], + ) + ? (supportStatus as (typeof leadershipSupportStatuses)[number]) + : 'a_abordar', + notes: optionalFormText(formData, 'notes'), + consentNote: optionalFormText(formData, 'consentNote'), + }) + }, + redirectTo: (leadership) => `/campanha/liderancas/${leadership.id}`, + safeMessages, + genericMessage: 'Não foi possível cadastrar a liderança. Verifique os dados e tente novamente.', + }) diff --git a/src/app/(campaign)/campanha/(app)/liderancas/page.tsx b/src/app/(campaign)/campanha/(app)/liderancas/page.tsx index 3dc13fa3..f13a69b5 100644 --- a/src/app/(campaign)/campanha/(app)/liderancas/page.tsx +++ b/src/app/(campaign)/campanha/(app)/liderancas/page.tsx @@ -1,3 +1,8 @@ +import { + CampaignListPendingBoundary, + CampaignListResults, +} from '@/components/campaign/CampaignListPending' +import { CampaignSearchForm } from '@/components/campaign/CampaignSearchForm' import config from '@payload-config' import { PlusIcon, SearchXIcon } from 'lucide-react' import Link from 'next/link' @@ -67,96 +72,97 @@ export default async function LeadershipsPage({ searchParams }: LeadershipsPageP -
- + - - - {rows.length ? ( - <> -
- - - - Nome - Status - Praças - Organizações - Acesso ao app - - - - {rows.map((row) => ( - - - - {row.name} - - - - {row.supportStatus ? : '—'} - - - {row.municipalityNames.join(', ') || '—'} - - - {row.organizationNames.join(', ') || '—'} - - - - {row.hasAppAccess ? 'Com acesso' : 'Sem acesso'} - - - - ))} - -
-
-
-

- {totalDocs} {totalDocs === 1 ? 'liderança' : 'lideranças'} -

- -
- - ) : ( - - - - - Nenhuma liderança encontrada - - Cadastre a primeira liderança ou ajuste a busca. Você só vê lideranças das suas - Praças. - - - - - - - )} + + {rows.length ? ( + <> +
+ + + + Nome + Status + Praças + Organizações + Acesso ao app + + + + {rows.map((row) => ( + + + + {row.name} + + + + {row.supportStatus ? ( + + ) : ( + '—' + )} + + + {row.municipalityNames.join(', ') || '—'} + + + {row.organizationNames.join(', ') || '—'} + + + + {row.hasAppAccess ? 'Com acesso' : 'Sem acesso'} + + + + ))} + +
+
+
+

+ {totalDocs} {totalDocs === 1 ? 'liderança' : 'lideranças'} +

+ +
+ + ) : ( + + + + + Nenhuma liderança encontrada + + Cadastre a primeira liderança ou ajuste a busca. Você só vê lideranças das suas + Praças. + + + + + + + )} +
+ ) } diff --git a/src/app/(campaign)/campanha/(app)/municipios/[slug]/page.tsx b/src/app/(campaign)/campanha/(app)/municipios/[slug]/page.tsx index 4dec6cc8..1049b98e 100644 --- a/src/app/(campaign)/campanha/(app)/municipios/[slug]/page.tsx +++ b/src/app/(campaign)/campanha/(app)/municipios/[slug]/page.tsx @@ -1,3 +1,4 @@ +import { Suspense } from 'react' import config from '@payload-config' import Link from 'next/link' import { notFound, redirect } from 'next/navigation' @@ -111,24 +112,32 @@ export default async function MunicipalityDetailPage({ ) : null} {activeTab === 'elections' ? ( - + // Streams below the header/tab chrome — the TSE baselines and the + // comparison table are the slowest reads on this page. + }> + + ) : null} {activeTab === 'leaderships' ? ( - + }> + + ) : null} {activeTab === 'updates' ? ( - + }> + + ) : null} {activeTab === 'demands' ? ( @@ -193,10 +202,17 @@ const OverviewTab = async ({ ) } +const MunicipalityTabFallback = () => ( +