From 4db9fc1b8ad486c45fa2f2f8a78fe0f1f64d4639 Mon Sep 17 00:00:00 2001 From: Julien Herr Date: Sat, 23 May 2026 10:38:01 +0200 Subject: [PATCH] fix(lint): close type-check gaps in client scripts and tooling Remove unused import flagged by CI lint, then harden the toolchain so such issues are caught before push: - lint-staged now also matches .tsx/.jsx (previously .tsx files skipped the pre-commit eslint pass, which is how the error reached CI) - eslint ignores generated client bundles (gitignored, not worth linting) - typecheck now also runs the client tsconfig; the hand-written browser source was excluded from the root config and never type-checked - consolidate the window global augmentations (showToast, parseJsonResponseOrThrow) into a single client globals.d.ts; the inline declare-global blocks failed (non-module files) and masked real errors Co-Authored-By: Claude Opus 4.7 --- eslint.config.mjs | 2 +- package.json | 5 +++-- src/routes/admin/feeds.tsx | 16 ++++++++++++---- src/scripts/client/dashboard.ts | 15 --------------- src/scripts/client/emails-page.ts | 15 --------------- src/scripts/client/globals.d.ts | 32 +++++++++++++++++++++++++++++++ 6 files changed, 48 insertions(+), 37 deletions(-) create mode 100644 src/scripts/client/globals.d.ts diff --git a/eslint.config.mjs b/eslint.config.mjs index 0d39a07..7f4d7a0 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -2,7 +2,7 @@ import prettier from "eslint-config-prettier"; import tseslint from "typescript-eslint"; export default tseslint.config( - { ignores: ["dist/", "coverage/"] }, + { ignores: ["dist/", "coverage/", "src/scripts/generated/"] }, ...tseslint.configs.recommended, prettier, { diff --git a/package.json b/package.json index 2e39ce7..4ccd6b0 100644 --- a/package.json +++ b/package.json @@ -16,11 +16,12 @@ "test": "vitest run", "test:watch": "vitest", "test:coverage": "vitest run --coverage", - "typecheck": "tsc --noEmit", + "typecheck": "tsc --noEmit && npm run typecheck:client", + "typecheck:client": "tsc -p src/scripts/client/tsconfig.json --noEmit", "prepare": "husky && npm run build:client" }, "lint-staged": { - "*.{ts,js}": [ + "*.{ts,tsx,js,jsx}": [ "eslint --fix", "prettier --write" ], diff --git a/src/routes/admin/feeds.tsx b/src/routes/admin/feeds.tsx index 98fcdd1..d6be12b 100644 --- a/src/routes/admin/feeds.tsx +++ b/src/routes/admin/feeds.tsx @@ -12,7 +12,6 @@ import { updateFeedInList, removeFeedFromList, removeFeedsFromListBulk, - deleteKeysWithConcurrency, purgeFeedKeysStep, } from "./helpers"; @@ -45,7 +44,12 @@ const updateFeedSchema = z.object({ }); const senderFilterSchema = z.object({ - action: z.enum(["allow_sender", "allow_domain", "block_sender", "block_domain"]), + action: z.enum([ + "allow_sender", + "allow_domain", + "block_sender", + "block_domain", + ]), value: z.string().min(1), }); @@ -668,7 +672,9 @@ feedsRouter.post("/bulk-delete", async (c) => { const deletedFeedIds = await removeFeedsFromListBulk(emailStorage, okIds); if (deletedFeedIds.length > 0) { - await bumpCounters(emailStorage, { feeds_deleted: deletedFeedIds.length }); + await bumpCounters(emailStorage, { + feeds_deleted: deletedFeedIds.length, + }); } const removed = new Set(deletedFeedIds); @@ -720,7 +726,9 @@ feedsRouter.post("/bulk-delete", async (c) => { const deletedFeedIds = await removeFeedsFromListBulk(emailStorage, okIds); if (deletedFeedIds.length > 0) { - await bumpCounters(emailStorage, { feeds_deleted: deletedFeedIds.length }); + await bumpCounters(emailStorage, { + feeds_deleted: deletedFeedIds.length, + }); } return c.redirect( diff --git a/src/scripts/client/dashboard.ts b/src/scripts/client/dashboard.ts index fe96eac..0356b39 100644 --- a/src/scripts/client/dashboard.ts +++ b/src/scripts/client/dashboard.ts @@ -341,21 +341,6 @@ function refreshFeedRowCache(): void { updateFeedSelectionState(); } -interface ToastHandle { - update?: (msg: string, opts?: Record) => void; - dismiss?: () => void; -} - -declare global { - interface Window { - showToast?: (msg: string, opts?: Record) => ToastHandle; - parseJsonResponseOrThrow?: ( - res: Response, - opts?: Record, - ) => Promise>; - } -} - function setupFeedDeleteButtons(): void { const buttons = Array.from( document.querySelectorAll( diff --git a/src/scripts/client/emails-page.ts b/src/scripts/client/emails-page.ts index 67a8a84..d2ebbe3 100644 --- a/src/scripts/client/emails-page.ts +++ b/src/scripts/client/emails-page.ts @@ -321,21 +321,6 @@ function refreshEmailRowCache(): void { updateEmailSelectionState(); } -interface ToastHandle { - update?: (msg: string, opts?: Record) => void; - dismiss?: () => void; -} - -declare global { - interface Window { - showToast?: (msg: string, opts?: Record) => ToastHandle; - parseJsonResponseOrThrow?: ( - res: Response, - opts?: Record, - ) => Promise>; - } -} - function setupEmailDeleteButtons(): void { Array.from( document.querySelectorAll( diff --git a/src/scripts/client/globals.d.ts b/src/scripts/client/globals.d.ts new file mode 100644 index 0000000..c6cc1a0 --- /dev/null +++ b/src/scripts/client/globals.d.ts @@ -0,0 +1,32 @@ +// Ambient declarations for browser globals injected at runtime via inline +//