From e60350f0e0e47e542e8ce8d210d38adb69d55f71 Mon Sep 17 00:00:00 2001 From: Jake Jarvis Date: Thu, 3 Sep 2026 16:14:20 -0400 Subject: [PATCH] chore: refine oxlint rules and fix resulting fallout --- .github/workflows/ci.yml | 4 - .oxlintrc.json | 62 ++++++++++++++- AGENTS.md | 10 +-- apps/web/.oxlintrc.json | 21 +++++- .../@modal/(.)login/login-modal-client.tsx | 4 +- apps/web/app/api/avatar/[userId]/route.ts | 8 +- apps/web/app/api/screenshot/route.ts | 5 +- apps/web/app/api/trpc/[trpc]/route.ts | 9 ++- .../components/ai-elements/prompt-input.tsx | 2 +- apps/web/components/chat/chat-client.tsx | 2 +- .../components/chat/chat-mode-selector.tsx | 3 +- .../add-domain/add-domain-content.tsx | 2 +- .../components/dashboard/dashboard-banner.tsx | 2 +- .../components/dashboard/dashboard-client.tsx | 2 +- .../dashboard/dashboard-shell.test.tsx | 6 +- .../dashboard/dashboard-table-column-menu.tsx | 3 +- .../components/dashboard/dashboard-table.tsx | 2 +- .../dashboard/domain-health-badge.tsx | 4 +- .../dashboard/unverified-table-row.tsx | 2 +- .../dashboard/verified-table-row.tsx | 2 +- apps/web/components/domain/export-button.tsx | 4 +- .../domain/registration/raw-data-dialog.tsx | 2 + apps/web/components/domain/report-client.tsx | 2 +- apps/web/components/domain/screenshot.tsx | 15 +++- .../components/domain/seo/robots-summary.tsx | 4 +- apps/web/components/layout/app-footer.tsx | 2 + apps/web/components/layout/user-menu.tsx | 2 +- .../search/header-search-client.test.tsx | 4 +- .../components/search/search-client.test.tsx | 22 +++--- .../notifications/notification-matrix.tsx | 4 +- apps/web/components/ui/map.tsx | 6 +- apps/web/components/ui/sonner.tsx | 2 +- apps/web/hooks/use-dashboard-filters.ts | 18 ++--- apps/web/hooks/use-dashboard-mutations.ts | 33 +++----- apps/web/hooks/use-dashboard-pagination.ts | 6 +- apps/web/hooks/use-subscription.ts | 12 ++- apps/web/lib/chat/message-parts.ts | 2 +- apps/web/lib/constants/sections.ts | 12 +-- apps/web/lib/notification-utils.ts | 4 +- apps/web/lib/workflow/errors.ts | 2 +- apps/web/package.json | 5 +- apps/web/server/routers/domain.ts | 6 +- .../workflows/subscription-expiry/workflow.ts | 2 +- package.json | 2 +- packages/api/package.json | 1 - packages/api/src/wait-until.test.ts | 5 +- packages/auth/package.json | 1 - packages/auth/src/server.ts | 6 +- packages/blob/package.json | 1 - packages/constants/package.json | 1 - packages/db/package.json | 1 - packages/db/src/queries/favicons.ts | 4 +- packages/db/src/queries/headers.ts | 2 +- packages/db/src/queries/provider-logos.ts | 2 +- packages/db/src/queries/seo.ts | 18 ++--- packages/db/src/testing.ts | 4 +- packages/edge-config/package.json | 1 - packages/email/.oxlintrc.json | 7 +- packages/email/package.json | 1 - .../src/templates/certificate-expiry.tsx | 2 +- .../src/templates/delete-account-verify.tsx | 2 +- .../src/templates/pro-upgrade-success.tsx | 2 +- packages/email/src/templates/pro-welcome.tsx | 2 +- .../src/templates/subscription-canceling.tsx | 2 +- .../src/templates/subscription-expired.tsx | 2 +- .../templates/verification-instructions.tsx | 2 +- .../src/templates/verification-revoked.tsx | 2 +- packages/image/.oxlintrc.json | 4 + packages/image/package.json | 1 - packages/image/src/optimize.ts | 2 +- packages/logger/package.json | 1 - packages/logger/src/otel.ts | 3 +- packages/polar/package.json | 1 - packages/redis/package.json | 1 - packages/safe-fetch/package.json | 1 - packages/safe-fetch/src/safe-fetch.test.ts | 2 +- packages/safe-fetch/src/safe-fetch.ts | 2 +- packages/screenshot/package.json | 1 - packages/screenshot/src/browser.ts | 6 +- packages/server/package.json | 1 - packages/server/src/seo/utils.ts | 4 +- packages/server/src/tls/fetch.ts | 2 +- packages/server/src/whois/lookup.ts | 10 ++- packages/types/package.json | 1 - packages/ui/.oxlintrc.json | 16 +++- packages/ui/package.json | 1 - packages/ui/src/components/pagination.tsx | 2 + .../ui/src/components/responsive-tooltip.tsx | 7 +- packages/ui/src/components/stepper.tsx | 2 +- packages/ui/src/components/video-player.tsx | 2 + packages/utils/package.json | 1 - packages/utils/src/dns/query.test.ts | 6 +- pnpm-lock.yaml | 75 +++++++++++++++++-- turbo.json | 4 - 94 files changed, 357 insertions(+), 201 deletions(-) create mode 100644 packages/image/.oxlintrc.json diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0c502f36..88684727 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,10 +37,6 @@ jobs: continue-on-error: true run: pnpm lint - - name: Run typecheck - continue-on-error: true - run: pnpm check-types - - name: Run tests with coverage report continue-on-error: true run: pnpm test:coverage diff --git a/.oxlintrc.json b/.oxlintrc.json index 6f49b0a7..3edab5be 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -1,11 +1,48 @@ { "$schema": "./node_modules/oxlint/configuration_schema.json", - "plugins": ["oxc", "eslint", "typescript", "react", "nextjs", "import", "unicorn", "vitest"], + "plugins": [ + "oxc", + "eslint", + "typescript", + "unicorn", + "import", + "node", + "promise", + "jsdoc", + "vitest", + "react", + "jsx-a11y", + "nextjs" + ], "categories": { "correctness": "error", "suspicious": "warn", "perf": "warn" }, + "options": { + "typeAware": true, + "typeCheck": true, + "reportUnusedDisableDirectives": "error" + }, + "env": { + "builtin": true, + "node": true + }, + "settings": { + "next": { + "rootDir": "apps/web" + }, + "react": { + "version": "19.2.8", + "linkComponents": [{ "name": "Link", "attribute": "href" }] + }, + "jsx-a11y": { + "components": { + "Link": "a", + "Button": "button" + } + } + }, "rules": { "import/no-named-as-default-member": "off", "import/no-unassigned-import": "off", @@ -21,7 +58,28 @@ "unicorn/consistent-function-scoping": "off", "unicorn/filename-case": "off", "unicorn/no-array-sort": "off", - "unicorn/no-null": "off" + "unicorn/no-null": "off", + "typescript/no-unsafe-type-assertion": "off", + "typescript/consistent-return": "off", + "typescript/no-unnecessary-type-conversion": "off", + "typescript/no-unnecessary-type-parameters": "off", + "jsdoc/check-tag-names": "off", + "jsx-a11y/prefer-tag-over-role": "off", + "jsx-a11y/control-has-associated-label": "off", + "jsx-a11y/no-autofocus": "off", + "react/jsx-no-target-blank": "off", + "import/no-amd": "error", + "import/no-cycle": "error", + "import/no-duplicates": "error", + "node/no-new-require": "error", + "node/no-path-concat": "error", + "react/button-has-type": "error", + "react/rules-of-hooks": "error", + "typescript/no-misused-promises": ["error", { "checksVoidReturn": { "attributes": false } }], + "typescript/only-throw-error": "error", + "typescript/switch-exhaustiveness-check": "error", + "unicorn/no-abusive-eslint-disable": "error", + "unicorn/prefer-node-protocol": "error" }, "overrides": [ { diff --git a/AGENTS.md b/AGENTS.md index 7e65956e..95e871cf 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,14 +2,13 @@ ## Pre-Commit Checklist -**CRITICAL:** Before declaring victory on any task and before committing to git, the following four commands must pass with NO WARNINGS: +**CRITICAL:** Before declaring victory on any task and before committing to git, the following three commands must pass with NO WARNINGS: 1. `pnpm lint` — Must pass with zero warnings 2. `pnpm fmt:check` — Must pass with zero warnings -3. `pnpm check-types` — Must pass with zero warnings -4. `pnpm test` — Must pass with zero warnings +3. `pnpm test` — Must pass with zero warnings -Do not proceed with commits until all four checks are clean. +Do not proceed with commits until all three checks are clean. ## Skill Loading @@ -28,11 +27,10 @@ Before editing files for a substantial task: - `pnpm dev` — Start Next.js dev server at http://localhost:3000 - `pnpm build` — Compile production bundle -- `pnpm check-types` — Run `tsc --noEmit` for type diagnostics ### Linting & Formatting -- `pnpm lint` — Run oxlint lint +- `pnpm lint` — Run oxlint (includes type-aware linting and type checking) - `pnpm fmt` — Apply oxfmt formatting ### Testing diff --git a/apps/web/.oxlintrc.json b/apps/web/.oxlintrc.json index d38bbcf5..c17d33b9 100644 --- a/apps/web/.oxlintrc.json +++ b/apps/web/.oxlintrc.json @@ -1,4 +1,23 @@ { "$schema": "../../node_modules/oxlint/configuration_schema.json", - "extends": ["../../.oxlintrc.json"] + "extends": ["../../.oxlintrc.json"], + "env": { + "browser": true, + "node": true + }, + "settings": { + "next": { + "rootDir": "." + }, + "react": { + "version": "19.2.8", + "linkComponents": [{ "name": "Link", "attribute": "href" }] + }, + "jsx-a11y": { + "components": { + "Link": "a", + "Button": "button" + } + } + } } diff --git a/apps/web/app/@modal/(.)login/login-modal-client.tsx b/apps/web/app/@modal/(.)login/login-modal-client.tsx index d80c5f66..09d07de9 100644 --- a/apps/web/app/@modal/(.)login/login-modal-client.tsx +++ b/apps/web/app/@modal/(.)login/login-modal-client.tsx @@ -23,10 +23,10 @@ export function LoginModalClient() { function AuthorizedLoginContent({ onNavigate }: { onNavigate: () => void }) { const { data: session } = useSession(); - const { replace } = useRouter(); + const router = useRouter(); if (session?.user) { - replace("/dashboard"); + router.replace("/dashboard"); } return ; diff --git a/apps/web/app/api/avatar/[userId]/route.ts b/apps/web/app/api/avatar/[userId]/route.ts index 8c4db746..cb4ad8cc 100644 --- a/apps/web/app/api/avatar/[userId]/route.ts +++ b/apps/web/app/api/avatar/[userId]/route.ts @@ -86,7 +86,13 @@ export async function GET( return new NextResponse("Avatar host not allowed", { status: 403 }); case "size_exceeded": return new NextResponse("Avatar too large", { status: 413 }); - default: + case "connection_error": + case "dns_error": + case "invalid_response": + case "invalid_url": + case "protocol_not_allowed": + case "redirect_limit": + case "timeout": return new NextResponse("Failed to fetch avatar", { status: 502 }); } } diff --git a/apps/web/app/api/screenshot/route.ts b/apps/web/app/api/screenshot/route.ts index 4f3dc63d..8d972363 100644 --- a/apps/web/app/api/screenshot/route.ts +++ b/apps/web/app/api/screenshot/route.ts @@ -87,8 +87,7 @@ export async function POST( // Only treat as cache hit if we have a definitive result: // - url is present (string), OR // - url is null but marked as permanently not found - const isDefinitiveResult = - cachedScreenshot.url !== null || cachedScreenshot.notFound === true; + const isDefinitiveResult = cachedScreenshot.url !== null || cachedScreenshot.notFound; if (isDefinitiveResult) { // Check current block status dynamically @@ -186,7 +185,7 @@ export async function GET( cached: false, success: result.success, data: result.data, - ...(result.success === false && { error: result.error }), + ...(!result.success && { error: result.error }), } as ScreenshotStatusResponse, { headers: rateLimit.headers }, ); diff --git a/apps/web/app/api/trpc/[trpc]/route.ts b/apps/web/app/api/trpc/[trpc]/route.ts index 63fc3f9c..ee0524aa 100644 --- a/apps/web/app/api/trpc/[trpc]/route.ts +++ b/apps/web/app/api/trpc/[trpc]/route.ts @@ -11,10 +11,11 @@ const handler = async (req: Request) => { req, router: appRouter, createContext: () => ctx, - onError: async ({ path, error }) => { - // Use logger for unhandled errors - const { logger } = await import("@domainstack/logger"); - logger.error({ err: error, source: "trpc", path }); + onError: ({ path, error }) => { + void (async () => { + const { logger } = await import("@domainstack/logger"); + logger.error({ err: error, source: "trpc", path }); + })(); }, }); }; diff --git a/apps/web/components/ai-elements/prompt-input.tsx b/apps/web/components/ai-elements/prompt-input.tsx index fd5f7a34..dc205a31 100644 --- a/apps/web/components/ai-elements/prompt-input.tsx +++ b/apps/web/components/ai-elements/prompt-input.tsx @@ -51,7 +51,7 @@ export const PromptInput = ({ className, onSubmit, children, ...props }: PromptI form.reset(); try { - onSubmit({ text }, event); + void onSubmit({ text }, event); } catch (error) { console.warn("Message submission failed:", error); } diff --git a/apps/web/components/chat/chat-client.tsx b/apps/web/components/chat/chat-client.tsx index ffbaeb47..13bb8d4c 100644 --- a/apps/web/components/chat/chat-client.tsx +++ b/apps/web/components/chat/chat-client.tsx @@ -223,7 +223,7 @@ function CloudChatSession({ (msgParams: { text: string }) => { const text = msgParams.text.trim(); if (!text) return; - chat.sendMessage({ text }); + void chat.sendMessage({ text }); onActiveChange(true); }, [chat, onActiveChange], diff --git a/apps/web/components/chat/chat-mode-selector.tsx b/apps/web/components/chat/chat-mode-selector.tsx index 7af57ba4..81f43003 100644 --- a/apps/web/components/chat/chat-mode-selector.tsx +++ b/apps/web/components/chat/chat-mode-selector.tsx @@ -43,7 +43,8 @@ function getStatusLabel(status: BrowserAIStatus, downloadProgress?: number): str return `Downloading… ${Math.round((downloadProgress ?? 0) * 100)}%`; case "error": return "Error"; - default: + case "ready": + case "unavailable": return ""; } } diff --git a/apps/web/components/dashboard/add-domain/add-domain-content.tsx b/apps/web/components/dashboard/add-domain/add-domain-content.tsx index 10fce3e2..6c15ba51 100644 --- a/apps/web/components/dashboard/add-domain/add-domain-content.tsx +++ b/apps/web/components/dashboard/add-domain/add-domain-content.tsx @@ -125,7 +125,7 @@ export function AddDomainContent({