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({