webui: add ESLint and eslint-typescript

I've historically found this stuff worthwhile, so let's swallow the pill.

Fortunately, sketch was happy to oblige.

- Install ESLint, @eslint/js, and typescript-eslint packages
- Configure eslint.config.mjs with recommended TypeScript ESLint rules
- Add browser globals support for DOM and web APIs
- Integrate ESLint into npm test workflow via package.json scripts
- Update Makefile to run lint checks as part of test suite
- Fix 249+ linting issues including:
  * Remove unused imports and variables (with _ prefix convention)
  * Fix case declaration issues with eslint-disable blocks
  * Remove unnecessary escape characters
  * Address prefer-const violations
  * Handle unused function parameters appropriately
- Configure ignore patterns to exclude dist/ and node_modules/
- Set rules to allow explicit 'any' types temporarily
- Convert warnings for ts-ignore, async promise executors, and unsafe optional chaining

Results:
- ESLint now runs successfully with 0 errors and only 6 warnings
- TypeScript compilation continues to work correctly
- Linting integrated into test workflow for continuous quality enforcement
- Codebase follows consistent ESLint TypeScript recommended practices

Co-Authored-By: sketch <hello@sketch.dev>
Change-ID: sd7b538be0a28d294k
diff --git a/webui/src/web-components/demo/chat-input.ts b/webui/src/web-components/demo/chat-input.ts
index 7ddcc7e..746d9ce 100644
--- a/webui/src/web-components/demo/chat-input.ts
+++ b/webui/src/web-components/demo/chat-input.ts
@@ -1,3 +1,4 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
 /**
  * Demo fixture for sketch-chat-input component
  */
diff --git a/webui/src/web-components/demo/demo-fixtures/view-mode-select.ts b/webui/src/web-components/demo/demo-fixtures/view-mode-select.ts
index 5e2df2d..8747e6e 100644
--- a/webui/src/web-components/demo/demo-fixtures/view-mode-select.ts
+++ b/webui/src/web-components/demo/demo-fixtures/view-mode-select.ts
@@ -1,3 +1,4 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
 /**
  * Shared demo fixtures for SketchViewModeSelect component
  */
diff --git a/webui/src/web-components/demo/demo-framework/demo-runner.ts b/webui/src/web-components/demo/demo-framework/demo-runner.ts
index a547c0a..1c9160c 100644
--- a/webui/src/web-components/demo/demo-framework/demo-runner.ts
+++ b/webui/src/web-components/demo/demo-framework/demo-runner.ts
@@ -1,13 +1,9 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
 /**
  * Demo runner that dynamically loads and executes demo modules
  */
 
-import {
-  DemoModule,
-  DemoRegistry,
-  DemoRunnerOptions,
-  DemoNavigationEvent,
-} from "./types";
+import { DemoModule, DemoRunnerOptions, DemoNavigationEvent } from "./types";
 
 export class DemoRunner {
   private container: HTMLElement;
diff --git a/webui/src/web-components/demo/mocks/browser.ts b/webui/src/web-components/demo/mocks/browser.ts
index 0e05730..30e2210 100644
--- a/webui/src/web-components/demo/mocks/browser.ts
+++ b/webui/src/web-components/demo/mocks/browser.ts
@@ -1,3 +1,4 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
 import { setupWorker } from "msw/browser";
 import { handlers } from "./handlers";
 
diff --git a/webui/src/web-components/demo/mocks/handlers.ts b/webui/src/web-components/demo/mocks/handlers.ts
index 29f0710..b5753a1 100644
--- a/webui/src/web-components/demo/mocks/handlers.ts
+++ b/webui/src/web-components/demo/mocks/handlers.ts
@@ -1,6 +1,5 @@
-import { http, HttpResponse, delay } from "msw";
+import { http, HttpResponse } from "msw";
 import { initialState, initialMessages } from "../../../fixtures/dummy";
-import { AgentMessage, State } from "../../../types";
 
 // Mock state updates for SSE simulation
 const EMPTY_CONVERSATION =
@@ -43,11 +42,10 @@
         }
 
         // Simulate heartbeats and new messages
-        let heartbeatInterval;
         let messageInterval;
 
         // Send heartbeats every 30 seconds
-        heartbeatInterval = setInterval(() => {
+        const heartbeatInterval = setInterval(() => {
           controller.enqueue(
             encoder.encode(
               formatSSE("heartbeat", { timestamp: new Date().toISOString() }),
diff --git a/webui/src/web-components/demo/sketch-app-shell.demo.ts b/webui/src/web-components/demo/sketch-app-shell.demo.ts
index d0ee53c..332b0fb 100644
--- a/webui/src/web-components/demo/sketch-app-shell.demo.ts
+++ b/webui/src/web-components/demo/sketch-app-shell.demo.ts
@@ -1,3 +1,4 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
 /**
  * Demo module for sketch-app-shell component
  */
diff --git a/webui/src/web-components/demo/sketch-call-status.demo.ts b/webui/src/web-components/demo/sketch-call-status.demo.ts
index 44b8c21..f01b190 100644
--- a/webui/src/web-components/demo/sketch-call-status.demo.ts
+++ b/webui/src/web-components/demo/sketch-call-status.demo.ts
@@ -1,3 +1,4 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
 /**
  * Demo module for sketch-call-status component
  */
diff --git a/webui/src/web-components/demo/sketch-chat-input.demo.ts b/webui/src/web-components/demo/sketch-chat-input.demo.ts
index f18c0b9..ac5f756 100644
--- a/webui/src/web-components/demo/sketch-chat-input.demo.ts
+++ b/webui/src/web-components/demo/sketch-chat-input.demo.ts
@@ -1,3 +1,4 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
 /**
  * Demo module for sketch-chat-input component
  */
diff --git a/webui/src/web-components/demo/sketch-container-status.demo.ts b/webui/src/web-components/demo/sketch-container-status.demo.ts
index b57412d..da0e435 100644
--- a/webui/src/web-components/demo/sketch-container-status.demo.ts
+++ b/webui/src/web-components/demo/sketch-container-status.demo.ts
@@ -1,3 +1,4 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
 /**
  * Demo module for sketch-container-status component
  */
diff --git a/webui/src/web-components/demo/sketch-timeline.demo.ts b/webui/src/web-components/demo/sketch-timeline.demo.ts
index 0a41250..845967b 100644
--- a/webui/src/web-components/demo/sketch-timeline.demo.ts
+++ b/webui/src/web-components/demo/sketch-timeline.demo.ts
@@ -1,3 +1,4 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
 /**
  * Demo module for sketch-timeline component
  */
@@ -38,7 +39,7 @@
                 name: "bash",
                 input: `echo "Tool call example ${i}"`,
                 tool_call_id: `call_${i}`,
-                args: `{"command": "echo \"Tool call example ${i}\""}`,
+                args: `{"command": "echo 'Tool call example ${i}'"}`,
                 result: `Tool call example ${i}`,
               },
             ]
diff --git a/webui/src/web-components/demo/sketch-tool-calls.demo.ts b/webui/src/web-components/demo/sketch-tool-calls.demo.ts
index f279657..80eda26 100644
--- a/webui/src/web-components/demo/sketch-tool-calls.demo.ts
+++ b/webui/src/web-components/demo/sketch-tool-calls.demo.ts
@@ -1,3 +1,4 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
 /**
  * Demo module for sketch-tool-calls component
  */
diff --git a/webui/src/web-components/demo/sketch-view-mode-select.demo.ts b/webui/src/web-components/demo/sketch-view-mode-select.demo.ts
index c187fbd..523f371 100644
--- a/webui/src/web-components/demo/sketch-view-mode-select.demo.ts
+++ b/webui/src/web-components/demo/sketch-view-mode-select.demo.ts
@@ -1,3 +1,4 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
 /**
  * Demo module for sketch-view-mode-select component
  */
@@ -253,7 +254,7 @@
     examplesContainer.style.cssText =
       "display: flex; flex-direction: column; gap: 20px; margin: 20px 0;";
 
-    containerExamples.forEach((example, index) => {
+    containerExamples.forEach((example) => {
       // Create container wrapper
       const wrapper = document.createElement("div");
       wrapper.style.cssText = `