diff --git a/.gitignore b/.gitignore index 9299475b7..b470644a6 100644 --- a/.gitignore +++ b/.gitignore @@ -87,5 +87,10 @@ next-env.d.ts .audit-*.mjs -# Isolated development worktrees +# Isolated development worktrees. `/.wt-*/` is the convention actually in use; +# each one is a full copy of the tree with its own node_modules, so leaving it +# untracked-but-visible means one `git add -A` commits the repository eleven +# times over. The same two names are excluded from eslint and vitest, for the +# same reason in a different tool. +/.wt-*/ /.worktrees/ \ No newline at end of file diff --git a/eslint.config.mjs b/eslint.config.mjs index 82fa8cfae..9f0e9130f 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -16,6 +16,10 @@ const eslintConfig = defineConfig([ // v1.31.4 — ignore local tool worktrees so a worktree on another // commit cannot poison `pnpm lint` locally. // CI never sees these paths; the rules protect the dev environment. + // `.wt-*` is the convention actually in use; the two names below predate + // it. Without the first pattern `pnpm lint` walks every worktree in the + // root, which is both slow and exactly the poisoning this list prevents. + ".wt-*/**", ".claude/**", ".worktrees/**", // The project-local ESLint rule plugin is CommonJS (require/module @@ -32,6 +36,21 @@ const eslintConfig = defineConfig([ { plugins: { healthlog: healthlogPlugin }, rules: { + // The codebase already marks a deliberately unused binding by prefixing + // it with an underscore — a required-but-ignored route handler argument, + // a mock signature that has to match the real one. The rule did not know + // that convention, so seven such bindings warned and `pnpm lint` could + // not be read as pass/fail. Teaching it the convention is what makes a + // real warning visible again. + "@typescript-eslint/no-unused-vars": [ + "warn", + { + argsIgnorePattern: "^_", + varsIgnorePattern: "^_", + caughtErrorsIgnorePattern: "^_", + destructuredArrayIgnorePattern: "^_", + }, + ], "healthlog/queryKey-factory": "error", // v1.5.6 — every outbound fetch under src/lib + src/app must route // through the safeFetch wrapper (manual-redirect + timeout, and the diff --git a/src/app/api/feature-flags/route.ts b/src/app/api/feature-flags/route.ts index e92422307..a8d9928db 100644 --- a/src/app/api/feature-flags/route.ts +++ b/src/app/api/feature-flags/route.ts @@ -40,7 +40,6 @@ import { apiSuccess } from "@/lib/api-response"; import { getAssistantFlags } from "@/lib/feature-flags"; import { annotate } from "@/lib/logging/context"; -// eslint-disable-next-line @typescript-eslint/no-unused-vars export const GET = apiHandler(async (_request: NextRequest) => { await requireAuth(); annotate({ action: { name: "feature-flags.read" } }); diff --git a/src/app/api/measurement-categories/route.ts b/src/app/api/measurement-categories/route.ts index ef7f55f56..597322014 100644 --- a/src/app/api/measurement-categories/route.ts +++ b/src/app/api/measurement-categories/route.ts @@ -62,7 +62,6 @@ const CATEGORY_ORDER: readonly MeasurementCategory[] = [ "metabolic", ] as const; -// eslint-disable-next-line @typescript-eslint/no-unused-vars export const GET = apiHandler(async (_request: NextRequest) => { await requireAuth(); diff --git a/src/app/api/sync/state/route.ts b/src/app/api/sync/state/route.ts index dd9ce0a4e..b45e269a1 100644 --- a/src/app/api/sync/state/route.ts +++ b/src/app/api/sync/state/route.ts @@ -43,7 +43,6 @@ import { TOMBSTONE_RETENTION_DAYS, } from "@/lib/auth/native-client"; -// eslint-disable-next-line @typescript-eslint/no-unused-vars export const GET = apiHandler(async (_request: NextRequest) => { const { user } = await requireAuth(); diff --git a/src/components/onboarding/goals-chip-picker.tsx b/src/components/onboarding/goals-chip-picker.tsx index a637858a9..a1a64d06a 100644 --- a/src/components/onboarding/goals-chip-picker.tsx +++ b/src/components/onboarding/goals-chip-picker.tsx @@ -97,7 +97,6 @@ export interface GoalsChipPickerProps { userId: string; } -// eslint-disable-next-line @typescript-eslint/no-unused-vars export function GoalsChipPicker({ userId: _userId }: GoalsChipPickerProps) { const { t } = useTranslations(); const router = useRouter(); diff --git a/src/lib/cycle/gate.ts b/src/lib/cycle/gate.ts index 839ff1e02..c198a7361 100644 --- a/src/lib/cycle/gate.ts +++ b/src/lib/cycle/gate.ts @@ -88,7 +88,6 @@ export type CycleGateResult = */ export async function requireCycleEnabled( userId: string, - // eslint-disable-next-line @typescript-eslint/no-unused-vars _gender: string | null | undefined, ): Promise { const [profile, available] = await Promise.all([ diff --git a/vitest.config.mts b/vitest.config.mts index 777007d6f..e19110a15 100644 --- a/vitest.config.mts +++ b/vitest.config.mts @@ -31,8 +31,13 @@ export default defineConfig({ "tests/integration/**", // Playwright E2E suite — driven separately via `pnpm e2e`. "e2e/**", - // Local agent worktrees create copies of `src/` under `.claude/` - // or `.worktrees/`; vitest must not collect stale duplicate suites. + // Local tool worktrees create full copies of `src/` in the repo root; + // vitest must not collect stale duplicate suites from them. The copies + // also carry their own `node_modules`, so a collected duplicate drags in + // a SECOND React instance and fails in ways that have nothing to do with + // the working tree. `.wt-*` is the convention actually in use — the two + // names below predate it and are kept because both still occur. + ".wt-*/**", ".claude/worktrees/**", ".worktrees/**", ],