diff --git a/docs/TECH-DEBT.md b/docs/TECH-DEBT.md
index 7f01cf95..a99afe9f 100644
--- a/docs/TECH-DEBT.md
+++ b/docs/TECH-DEBT.md
@@ -6,7 +6,7 @@ Ledger of known engineering debt. Items large enough to be scheduled work become
| Item | Location | Type | Risk | Effort | Priority | Status | Ref |
| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | ----------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- | --------------------------- | ----------------------------------------------------------------------------------------- | -------------------- |
-| 164 unused exports + 60 unused types reported by knip (warn-level; 158 at Pass 1) | god modules, ui kit families | dead code | low | M | P2 | open — Pass 2 W4b flips `exports` to error after W4a kills the biggest offender | Pass 2 W4b |
+| 164 unused exports + 60 unused types reported by knip (warn-level; 158 at Pass 1) | god modules, ui kit families | dead code | low | M | P2 | closed 2026-07-25 (Pass 2 W4b): 0 findings; `exports`/`types`/`enumMembers` at **error** in CI; tests entered the knip graph; `duplicates` off (only hits were the 6 deliberate access aliases `canUpdateX = canReadX`) | Pass 2 W4b |
| 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 |
diff --git a/knip.json b/knip.json
index 3eb8e869..4d647cf9 100644
--- a/knip.json
+++ b/knip.json
@@ -1,21 +1,27 @@
{
"$schema": "https://unpkg.com/knip@5/schema.json",
"entry": [
- "src/payload.config.ts",
"src/migrations/index.ts",
+ "src/migrations/*.ts",
"src/app/(payload)/admin/importMap.js",
"scripts/*.mjs",
- "eslint-local-rules.mjs"
+ "eslint-local-rules.mjs",
+ "tests/**/*.spec.ts",
+ "tests/helpers/serverOnly.ts"
],
- "project": ["src/**/*.{ts,tsx,js}", "scripts/**/*.mjs"],
- "ignore": ["src/payload-types.ts", "src/migrations/**"],
+ "project": [
+ "src/**/*.{ts,tsx,js}",
+ "scripts/**/*.mjs",
+ "tests/**/*.ts"
+ ],
+ "ignore": [],
"paths": {
- "server-only": ["tests/helpers/serverOnly.ts"]
+ "server-only": [
+ "tests/helpers/serverOnly.ts"
+ ]
},
- "ignoreBinaries": ["playwright"],
+ "ignoreBinaries": [],
"ignoreDependencies": [
- "tsx",
- "sharp",
"tailwindcss-animate",
"tw-animate-css",
"shadcn",
@@ -23,9 +29,9 @@
"prettier"
],
"rules": {
- "exports": "warn",
- "types": "warn",
- "enumMembers": "warn",
- "duplicates": "warn"
+ "exports": "error",
+ "types": "error",
+ "enumMembers": "error",
+ "duplicates": "off"
}
-}
+}
\ No newline at end of file
diff --git a/src/app/(campaign)/campanha/(app)/apoiadores/novo/formActions.ts b/src/app/(campaign)/campanha/(app)/apoiadores/novo/formActions.ts
index da6a06a9..5716dfd4 100644
--- a/src/app/(campaign)/campanha/(app)/apoiadores/novo/formActions.ts
+++ b/src/app/(campaign)/campanha/(app)/apoiadores/novo/formActions.ts
@@ -21,7 +21,7 @@ export type SupporterFormState = {
revision?: number
}
-export type SupporterFormValues = {
+type SupporterFormValues = {
name?: string
phone?: string
email?: string
diff --git a/src/app/(campaign)/campanha/actions/actionPlan.ts b/src/app/(campaign)/campanha/actions/actionPlan.ts
index 011dff8a..a089d924 100644
--- a/src/app/(campaign)/campanha/actions/actionPlan.ts
+++ b/src/app/(campaign)/campanha/actions/actionPlan.ts
@@ -63,7 +63,7 @@ export const createActionPlanRecord = async (
)
}
-export const updateActionPlanRecord = async (
+const updateActionPlanRecord = async (
payload: Payload,
actor: CampaignUser,
input: ActionPlanUpdateInput,
@@ -134,7 +134,7 @@ const setActionPlanLifecycleStatusRecord = async (
{ beginFailureMessage: 'Não foi possível atualizar o status do plano.' },
)
-export const toggleActionPlanTaskRecord = async (
+const toggleActionPlanTaskRecord = async (
payload: Payload,
actor: CampaignUser,
planId: number,
@@ -176,7 +176,7 @@ export const toggleActionPlanTaskRecord = async (
{ beginFailureMessage: 'Não foi possível atualizar a tarefa do plano.' },
)
-export const appendActionPlanUpdateRecord = async (
+const appendActionPlanUpdateRecord = async (
payload: Payload,
actor: CampaignUser,
planId: number,
@@ -221,7 +221,7 @@ export const appendActionPlanUpdateRecord = async (
)
}
-export const registerActionPlanResult = async (
+const registerActionPlanResult = async (
payload: Payload,
actor: CampaignUser,
planId: number,
diff --git a/src/app/(campaign)/campanha/actions/demand.ts b/src/app/(campaign)/campanha/actions/demand.ts
index 68085ed3..25177f7f 100644
--- a/src/app/(campaign)/campanha/actions/demand.ts
+++ b/src/app/(campaign)/campanha/actions/demand.ts
@@ -5,11 +5,9 @@ import type { Payload } from 'payload'
import {
campaignDemandCostSchema,
campaignDemandCreateSchema,
- campaignDemandDetailsUpdateSchema,
campaignDemandTransitionSchema,
type CampaignDemandCostInput,
type CampaignDemandCreateInput,
- type CampaignDemandDetailsUpdateInput,
type CampaignDemandTransitionInput,
} from '@/lib/schemas/campaignDemandInput'
import type { CampaignUser } from '@/payload-types'
@@ -50,33 +48,6 @@ export const createCampaignDemandRecord = async (
)
}
-export const updateCampaignDemandDetailsRecord = async (
- payload: Payload,
- actor: CampaignUser,
- input: CampaignDemandDetailsUpdateInput,
-) => {
- const { id, ...data } = campaignDemandDetailsUpdateSchema.parse(input)
-
- return withPayloadTransaction(
- payload,
- async ({ req }) => {
- const currentActor = await reloadCampaignActor(payload, actor, req)
- await acquireTextAdvisoryLocks(payload, req, [`campaign-demand:${id}`])
-
- return payload.update({
- collection: 'campaignDemand',
- id,
- data,
- depth: 0,
- user: currentActor,
- overrideAccess: false,
- req,
- })
- },
- { beginFailureMessage: 'Não foi possível iniciar a atualização da demanda.' },
- )
-}
-
export const transitionCampaignDemandRecord = async (
payload: Payload,
actor: CampaignUser,
@@ -215,11 +186,6 @@ export const createCampaignDemand = async (input: CampaignDemandCreateInput) =>
return createCampaignDemandRecord(payload, actor, input)
}
-export const updateCampaignDemandDetails = async (input: CampaignDemandDetailsUpdateInput) => {
- const { payload, actor } = await getCampaignActionContext()
- return updateCampaignDemandDetailsRecord(payload, actor, input)
-}
-
export const transitionCampaignDemand = async (input: CampaignDemandTransitionInput) => {
const { payload, actor } = await getCampaignActionContext()
return transitionCampaignDemandRecord(payload, actor, input)
diff --git a/src/app/(campaign)/campanha/actions/leaderSupporter.ts b/src/app/(campaign)/campanha/actions/leaderSupporter.ts
index bf90c4ad..a398783e 100644
--- a/src/app/(campaign)/campanha/actions/leaderSupporter.ts
+++ b/src/app/(campaign)/campanha/actions/leaderSupporter.ts
@@ -28,7 +28,7 @@ export type LeaderSupporterFormState = {
revision?: number
}
-export type LeaderSupporterFormValues = {
+type LeaderSupporterFormValues = {
name?: string
phone?: string
city?: string
@@ -144,7 +144,7 @@ export const createLeaderSupporterRecord = async (
}
}
-export const createLeaderSupporter = async (input: unknown) => {
+const createLeaderSupporter = async (input: unknown) => {
const { payload, actor } = await getCampaignActionContext()
return createLeaderSupporterRecord(payload, actor, input)
}
diff --git a/src/app/(campaign)/campanha/actions/organization.ts b/src/app/(campaign)/campanha/actions/organization.ts
index 057a5120..058e766c 100644
--- a/src/app/(campaign)/campanha/actions/organization.ts
+++ b/src/app/(campaign)/campanha/actions/organization.ts
@@ -19,7 +19,7 @@ const organizationPolicy: StaffEntityPolicy = {
conflictMessage: 'Já existe uma organização com este nome.',
}
-export const createOrganizationRecord = async (
+const createOrganizationRecord = async (
payload: Payload,
actor: CampaignUser,
input: OrganizationCreateInput,
@@ -36,7 +36,7 @@ export const createOrganizationRecord = async (
)
}
-export const updateOrganizationRecord = async (
+const updateOrganizationRecord = async (
payload: Payload,
actor: CampaignUser,
input: OrganizationUpdateInput,
diff --git a/src/app/(campaign)/campanha/actions/stateDeputy.ts b/src/app/(campaign)/campanha/actions/stateDeputy.ts
index 6e19c58d..f153a8bd 100644
--- a/src/app/(campaign)/campanha/actions/stateDeputy.ts
+++ b/src/app/(campaign)/campanha/actions/stateDeputy.ts
@@ -19,7 +19,7 @@ const stateDeputyPolicy: StaffEntityPolicy = {
conflictMessage: 'Já existe uma dobradinha com este nome.',
}
-export const createStateDeputyRecord = async (
+const createStateDeputyRecord = async (
payload: Payload,
actor: CampaignUser,
input: StateDeputyCreateInput,
@@ -36,7 +36,7 @@ export const createStateDeputyRecord = async (
)
}
-export const updateStateDeputyRecord = async (
+const updateStateDeputyRecord = async (
payload: Payload,
actor: CampaignUser,
input: StateDeputyUpdateInput,
diff --git a/src/app/(campaign)/campanha/actions/supporter.ts b/src/app/(campaign)/campanha/actions/supporter.ts
index e5cc506e..b70ec936 100644
--- a/src/app/(campaign)/campanha/actions/supporter.ts
+++ b/src/app/(campaign)/campanha/actions/supporter.ts
@@ -27,12 +27,7 @@ import {
previewSupporterImportText as previewSupporterImportTextAction,
} from './supporterImport'
-export type {
- SupporterImportOkRow,
- SupporterImportPreviewResult,
- SupporterImportPreviewRow,
- SupporterImportRowStatus,
-} from '@/utilities/supporterImport'
+
const getFreshStaffActor = (
payload: Payload,
@@ -46,7 +41,7 @@ const getFreshStaffActor = (
req,
)
-export const assertMunicipalityManagement = async (
+const assertMunicipalityManagement = async (
payload: Payload,
actor: CampaignUser,
municipalityID: number,
diff --git a/src/components/campaign/actionPlan/ActionPlanForm.tsx b/src/components/campaign/actionPlan/ActionPlanForm.tsx
index d049801d..733dfcf0 100644
--- a/src/components/campaign/actionPlan/ActionPlanForm.tsx
+++ b/src/components/campaign/actionPlan/ActionPlanForm.tsx
@@ -64,7 +64,7 @@ export type ActionPlanFormFieldsProps = {
const editableStatuses = ['rascunho', 'planejado'] as const
-export const ActionPlanFormFields = ({
+const ActionPlanFormFields = ({
municipalityOptions,
organizationOptions,
advisorOptions = [],
diff --git a/src/components/campaign/leadership/SupportStatusBadge.tsx b/src/components/campaign/leadership/SupportStatusBadge.tsx
index 5458f4e0..17200f52 100644
--- a/src/components/campaign/leadership/SupportStatusBadge.tsx
+++ b/src/components/campaign/leadership/SupportStatusBadge.tsx
@@ -26,12 +26,6 @@ const statusPresentation = {
},
} as const
-export const supportStatusLabel = (status: SupportStatus): string =>
- statusPresentation[status].label
-
-export const supportStatusSummary = (status: SupportStatus): string =>
- statusPresentation[status].summary
-
export const SupportStatusBadge = ({ status }: { status: SupportStatus }) => {
const presentation = statusPresentation[status]
diff --git a/src/components/campaign/map/BahiaMap.tsx b/src/components/campaign/map/BahiaMap.tsx
index f54b71e5..8ed58798 100644
--- a/src/components/campaign/map/BahiaMap.tsx
+++ b/src/components/campaign/map/BahiaMap.tsx
@@ -26,7 +26,7 @@ import {
import { cn } from '@/lib/utils'
import { Spinner } from '@/components/ui/Spinner'
-export type { BahiaMapFillMode, ChoroplethValues }
+
const BAHIA_BOUNDS: L.LatLngBoundsExpression = [
[-18.5, -46.8],
diff --git a/src/components/campaign/shared/CampaignListPagination.tsx b/src/components/campaign/shared/CampaignListPagination.tsx
index 8a8fa034..2d2607de 100644
--- a/src/components/campaign/shared/CampaignListPagination.tsx
+++ b/src/components/campaign/shared/CampaignListPagination.tsx
@@ -10,7 +10,7 @@ import {
} from '@/components/ui/Pagination'
import { cn } from '@/lib/utils'
-export const getPaginationPages = (page: number, totalPages: number): number[] => {
+const getPaginationPages = (page: number, totalPages: number): number[] => {
const pages = new Set([1, totalPages, page - 1, page, page + 1])
return [...pages]
.filter((candidate) => candidate >= 1 && candidate <= totalPages)
diff --git a/src/components/campaign/shell/CampaignPageShell.tsx b/src/components/campaign/shell/CampaignPageShell.tsx
index 8d1ce2d7..346b2b25 100644
--- a/src/components/campaign/shell/CampaignPageShell.tsx
+++ b/src/components/campaign/shell/CampaignPageShell.tsx
@@ -2,7 +2,7 @@ import type { ComponentProps, ReactNode } from 'react'
import { cn } from '@/lib/utils'
-export const campaignPageShellClassName = 'mx-auto flex w-full max-w-screen-2xl flex-col gap-8'
+const campaignPageShellClassName = 'mx-auto flex w-full max-w-screen-2xl flex-col gap-8'
export const campaignPrioritySurfaceClassName =
'shadow-[0_4px_24px_rgb(28_25_23/0.08)] ring-1 ring-foreground/10'
diff --git a/src/components/ui/Alert.tsx b/src/components/ui/Alert.tsx
index 91b80268..2ce1de70 100644
--- a/src/components/ui/Alert.tsx
+++ b/src/components/ui/Alert.tsx
@@ -56,8 +56,4 @@ const AlertDescription = ({ className, ...props }: React.ComponentProps<'div'>)
/>
)
-const AlertAction = ({ className, ...props }: React.ComponentProps<'div'>) => (
-
-)
-
-export { Alert, AlertTitle, AlertDescription, AlertAction }
+export { Alert, AlertTitle, AlertDescription, }
diff --git a/src/components/ui/AlertDialog.tsx b/src/components/ui/AlertDialog.tsx
index 39adf001..f66273c1 100644
--- a/src/components/ui/AlertDialog.tsx
+++ b/src/components/ui/AlertDialog.tsx
@@ -14,11 +14,11 @@ export const AlertDialogTrigger = (
props: React.ComponentProps,
) =>
-export const AlertDialogPortal = (
+const AlertDialogPortal = (
props: React.ComponentProps,
) =>
-export const AlertDialogOverlay = ({
+const AlertDialogOverlay = ({
className,
...props
}: React.ComponentProps) => (
diff --git a/src/components/ui/Avatar.tsx b/src/components/ui/Avatar.tsx
index 2ef23ead..e59ab19d 100644
--- a/src/components/ui/Avatar.tsx
+++ b/src/components/ui/Avatar.tsx
@@ -48,40 +48,4 @@ const AvatarFallback = ({
/>
)
-const AvatarBadge = ({ className, ...props }: React.ComponentProps<'span'>) => (
- svg]:hidden',
- 'group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2',
- 'group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2',
- className,
- )}
- {...props}
- />
-)
-
-const AvatarGroup = ({ className, ...props }: React.ComponentProps<'div'>) => (
-
-)
-
-const AvatarGroupCount = ({ className, ...props }: React.ComponentProps<'div'>) => (
- svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3',
- className,
- )}
- {...props}
- />
-)
-
-export { Avatar, AvatarImage, AvatarFallback, AvatarGroup, AvatarGroupCount, AvatarBadge }
+export { Avatar, AvatarImage, AvatarFallback, }
diff --git a/src/components/ui/Command.tsx b/src/components/ui/Command.tsx
index 8fbfe083..bfc64f57 100644
--- a/src/components/ui/Command.tsx
+++ b/src/components/ui/Command.tsx
@@ -87,17 +87,6 @@ const CommandList = ({
/>
)
-const CommandEmpty = ({
- className,
- ...props
-}: React.ComponentProps
) => (
-
-)
-
const CommandGroup = ({
className,
...props
@@ -112,17 +101,6 @@ const CommandGroup = ({
/>
)
-const CommandSeparator = ({
- className,
- ...props
-}: React.ComponentProps) => (
-
-)
-
const CommandItem = ({
className,
children,
@@ -141,25 +119,14 @@ const CommandItem = ({
)
-const CommandShortcut = ({ className, ...props }: React.ComponentProps<'span'>) => (
-
-)
-
export {
Command,
CommandDialog,
CommandInput,
CommandList,
- CommandEmpty,
+
CommandGroup,
CommandItem,
- CommandShortcut,
- CommandSeparator,
+
+
}
diff --git a/src/components/ui/Drawer.tsx b/src/components/ui/Drawer.tsx
index 77697e23..242147db 100644
--- a/src/components/ui/Drawer.tsx
+++ b/src/components/ui/Drawer.tsx
@@ -54,10 +54,6 @@ const Drawer = ({
)
}
-const DrawerTrigger = ({ ...props }: DrawerPrimitive.Trigger.Props) => (
-
-)
-
const DrawerPortal = ({ ...props }: DrawerPrimitive.Portal.Props) => (
)
@@ -172,10 +168,10 @@ const DrawerDescription = ({ className, ...props }: DrawerPrimitive.Description.
export {
Drawer,
- DrawerPortal,
- DrawerOverlay,
- DrawerSwipeHandle,
- DrawerTrigger,
+
+
+
+
DrawerClose,
DrawerContent,
DrawerHeader,
diff --git a/src/components/ui/Pagination.tsx b/src/components/ui/Pagination.tsx
index 160d38af..058f09a2 100644
--- a/src/components/ui/Pagination.tsx
+++ b/src/components/ui/Pagination.tsx
@@ -1,8 +1,7 @@
import * as React from 'react'
import { cn } from '@/lib/utils'
-import { Button } from '@/components/ui/button'
-import { ChevronLeftIcon, ChevronRightIcon, MoreHorizontalIcon } from 'lucide-react'
+import { MoreHorizontalIcon } from 'lucide-react'
const Pagination = ({ className, ...props }: React.ComponentProps<'nav'>) => (