Resolve frontend warnings (vibe-kanban) (#1316)

* ##  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**: `<T extends object>` - 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**.
This commit is contained in:
Louis Knight-Webb
2025-11-18 11:12:59 +00:00
committed by GitHub
parent 9d8c0b286f
commit 1dae217f1a
3 changed files with 17 additions and 16 deletions

View File

@@ -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}\"",

View File

@@ -26,7 +26,7 @@ interface UseJsonPatchStreamResult<T> {
/**
* Generic hook for consuming WebSocket streams that send JSON messages with patches
*/
export const useJsonPatchWsStream = <T>(
export const useJsonPatchWsStream = <T extends object>(
endpoint: string | undefined,
enabled: boolean,
initialData: () => T,
@@ -117,16 +117,17 @@ export const useJsonPatchWsStream = <T>(
? 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})

View File

@@ -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;
}