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