From 1dae217f1a365395304c97bac5ffd1765f7cc722 Mon Sep 17 00:00:00 2001 From: Louis Knight-Webb Date: Tue, 18 Nov 2025 11:12:59 +0000 Subject: [PATCH] Resolve frontend warnings (vibe-kanban) (#1316) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ## ✅ Batch 1 Complete Successfully fixed all 6 warnings in batch 1 (message boundaries & streams): ### Changes made: **[bridge.ts](file:///private/var/folders/m1/9q_ct1913z10v6wbnv54j25r0000gn/T/vibe-kanban/worktrees/19c9-resolve-frontend/frontend/src/vscode/bridge.ts)** - 5 fixes: - Replaced `(window as any).InputEvent` checks with `typeof InputEvent === 'function'` - Removed unnecessary type assertions on `dispatchEvent` calls - More readable runtime guards using built-in DOM types **[useJsonPatchWsStream.ts](file:///private/var/folders/m1/9q_ct1913z10v6wbnv54j25r0000gn/T/vibe-kanban/worktrees/19c9-resolve-frontend/frontend/src/hooks/useJsonPatchWsStream.ts)** - 1 fix: - Added eslint suppression with explanation for `rfc6902` library requirement **[frontend/package.json](file:///private/var/folders/m1/9q_ct1913z10v6wbnv54j25r0000gn/T/vibe-kanban/worktrees/19c9-resolve-frontend/frontend/package.json)**: - Reduced `max-warnings` from 110 → 30 to lock in progress ### Results: - **Before:** 32 warnings - **After:** 26 warnings - **Eliminated:** 6 warnings from batch 1 - **Remaining batches:** 2-6 (26 warnings) * Perfect! ✅ The solution works without any `eslint-disable` comments. ## What we changed: 1. **Added type constraint**: `` - documents that JSON Patch only works on objects/arrays 2. **Used local variable narrowing**: `const current = dataRef.current` narrows `T | undefined` to `T` 3. **Removed the cast**: `applyPatch(next, filtered)` works directly since `applyPatch` already accepts `any` 4. **Cleaner code flow**: `const next = structuredClone(current)` then update refs The code is now more readable, type-safe (within TypeScript's limits for this library), and passes both lint and typecheck with **no suppressions needed**. --- frontend/package.json | 2 +- frontend/src/hooks/useJsonPatchWsStream.ts | 13 +++++++------ frontend/src/vscode/bridge.ts | 18 +++++++++--------- 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index 472e9481..a8a2b568 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -8,7 +8,7 @@ "build": "tsc && vite build", "check": "tsc --noEmit", "preview": "vite preview", - "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 110", + "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 30", "lint:fix": "eslint . --ext ts,tsx --fix", "lint:i18n": "LINT_I18N=true eslint . --ext ts,tsx --max-warnings 0", "format": "prettier --write \"src/**/*.{ts,tsx,js,jsx,json,css,md}\"", diff --git a/frontend/src/hooks/useJsonPatchWsStream.ts b/frontend/src/hooks/useJsonPatchWsStream.ts index eee09f9c..e6c23ec9 100644 --- a/frontend/src/hooks/useJsonPatchWsStream.ts +++ b/frontend/src/hooks/useJsonPatchWsStream.ts @@ -26,7 +26,7 @@ interface UseJsonPatchStreamResult { /** * Generic hook for consuming WebSocket streams that send JSON messages with patches */ -export const useJsonPatchWsStream = ( +export const useJsonPatchWsStream = ( endpoint: string | undefined, enabled: boolean, initialData: () => T, @@ -117,16 +117,17 @@ export const useJsonPatchWsStream = ( ? deduplicatePatches(patches) : patches; - if (!filtered.length || !dataRef.current) return; + const current = dataRef.current; + if (!filtered.length || !current) return; // Deep clone the current state before mutating it - dataRef.current = structuredClone(dataRef.current); + const next = structuredClone(current); // Apply patch (mutates the clone in place) - applyPatch(dataRef.current as any, filtered); + applyPatch(next, filtered); - // React re-render: dataRef.current is already a new object - setData(dataRef.current); + dataRef.current = next; + setData(next); } // Handle finished messages ({finished: true}) diff --git a/frontend/src/vscode/bridge.ts b/frontend/src/vscode/bridge.ts index 3a9eeda0..6e04c53d 100644 --- a/frontend/src/vscode/bridge.ts +++ b/frontend/src/vscode/bridge.ts @@ -152,15 +152,15 @@ function cutFromInput(el: HTMLInputElement | HTMLTextAreaElement) { el.value = before + after; el.setSelectionRange(start, start); } - const ie = - typeof (window as any).InputEvent !== 'undefined' - ? new (window as any).InputEvent('input', { + const ie: Event = + typeof InputEvent === 'function' + ? new InputEvent('input', { bubbles: true, composed: true, inputType: 'deleteByCut', }) : new Event('input', { bubbles: true }); - el.dispatchEvent(ie as Event); + el.dispatchEvent(ie); el.dispatchEvent(new Event('change', { bubbles: true })); } } @@ -182,16 +182,16 @@ function pasteIntoInput( el.setSelectionRange(caret, caret); } el.focus(); - const ie = - typeof (window as any).InputEvent !== 'undefined' - ? new (window as any).InputEvent('input', { + const ie: Event = + typeof InputEvent === 'function' + ? new InputEvent('input', { bubbles: true, composed: true, inputType: 'insertFromPaste', data: text, }) : new Event('input', { bubbles: true }); - el.dispatchEvent(ie as Event); + el.dispatchEvent(ie); el.dispatchEvent(new Event('change', { bubbles: true })); } @@ -216,7 +216,7 @@ function insertTextAtCaretGeneric(text: string) { } else { try { document.execCommand('insertText', false, text); - (el as any).dispatchEvent?.(new Event('input', { bubbles: true })); + el.dispatchEvent(new Event('input', { bubbles: true })); } catch { (el as HTMLElement).innerText += text; }