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/aggregateAgentMessages.ts b/webui/src/web-components/aggregateAgentMessages.ts
index 3dc11f8..366b690 100644
--- a/webui/src/web-components/aggregateAgentMessages.ts
+++ b/webui/src/web-components/aggregateAgentMessages.ts
@@ -8,7 +8,7 @@
   const seenIds = new Set<number>();
   const toolCallResults = new Map<string, AgentMessage>();
 
-  let ret: AgentMessage[] = mergedArray
+  const ret: AgentMessage[] = mergedArray
     .filter((msg) => {
       if (msg.type == "tool") {
         toolCallResults.set(msg.tool_call_id, msg);
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 = `
diff --git a/webui/src/web-components/mobile-chat-input.ts b/webui/src/web-components/mobile-chat-input.ts
index 94abdbc..c41cdff 100644
--- a/webui/src/web-components/mobile-chat-input.ts
+++ b/webui/src/web-components/mobile-chat-input.ts
@@ -1,3 +1,4 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
 import { css, html, LitElement } from "lit";
 import { customElement, property, state } from "lit/decorators.js";
 import { createRef, ref } from "lit/directives/ref.js";
diff --git a/webui/src/web-components/mobile-chat.ts b/webui/src/web-components/mobile-chat.ts
index 26ef963..bd3a9e3 100644
--- a/webui/src/web-components/mobile-chat.ts
+++ b/webui/src/web-components/mobile-chat.ts
@@ -1,3 +1,4 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
 import { css, html, LitElement } from "lit";
 import { customElement, property, state } from "lit/decorators.js";
 import { unsafeHTML } from "lit/directives/unsafe-html.js";
@@ -479,9 +480,7 @@
     return html`
       <div class="tool-calls">
         ${message.tool_calls.map((toolCall) => {
-          const statusIcon = this.getToolStatusIcon(toolCall);
           const summary = this.getToolSummary(toolCall);
-          const duration = this.getToolDuration(toolCall);
 
           return html`
             <div class="tool-call-item ${toolCall.name}">
@@ -494,7 +493,7 @@
     `;
   }
 
-  private getToolStatusIcon(toolCall: any): string {
+  private getToolStatusIcon(_toolCall: any): string {
     // Don't show status icons for mobile
     return "";
   }
@@ -503,6 +502,7 @@
     try {
       const input = JSON.parse(toolCall.input || "{}");
 
+      /* eslint-disable no-case-declarations */
       switch (toolCall.name) {
         case "bash":
           const command = input.command || "";
@@ -571,12 +571,13 @@
             ? inputStr.substring(0, 50) + "..."
             : inputStr;
       }
-    } catch (e) {
+      /* eslint-enable no-case-declarations */
+    } catch {
       return "Tool call";
     }
   }
 
-  private getToolDuration(toolCall: any): string {
+  private getToolDuration(_toolCall: any): string {
     // Don't show duration for mobile
     return "";
   }
@@ -605,7 +606,7 @@
           : displayMessages.map((message) => {
               const role = this.getMessageRole(message);
               const text = this.getMessageText(message);
-              const timestamp = message.timestamp;
+              // const timestamp = message.timestamp; // Unused for mobile layout
 
               return html`
                 <div class="message ${role}">
diff --git a/webui/src/web-components/mobile-diff.ts b/webui/src/web-components/mobile-diff.ts
index 9d74518..7511602 100644
--- a/webui/src/web-components/mobile-diff.ts
+++ b/webui/src/web-components/mobile-diff.ts
@@ -1,3 +1,4 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
 import { css, html, LitElement } from "lit";
 import { customElement, state } from "lit/decorators.js";
 import {
diff --git a/webui/src/web-components/mobile-shell.ts b/webui/src/web-components/mobile-shell.ts
index 1a27c05..dd59ad8 100644
--- a/webui/src/web-components/mobile-shell.ts
+++ b/webui/src/web-components/mobile-shell.ts
@@ -123,7 +123,7 @@
 
   private handleConnectionStatusChanged(
     status: ConnectionStatus,
-    errorMessage?: string,
+    _errorMessage?: string,
   ) {
     this.connectionStatus = status;
   }
diff --git a/webui/src/web-components/sketch-app-shell-base.ts b/webui/src/web-components/sketch-app-shell-base.ts
index 502b019..070ed21 100644
--- a/webui/src/web-components/sketch-app-shell-base.ts
+++ b/webui/src/web-components/sketch-app-shell-base.ts
@@ -1,7 +1,8 @@
-import { css, html, LitElement } from "lit";
-import { customElement, property, state } from "lit/decorators.js";
+/* eslint-disable @typescript-eslint/no-explicit-any */
+import { html } from "lit";
+import { property, state } from "lit/decorators.js";
 import { ConnectionStatus, DataManager } from "../data";
-import { AgentMessage, GitLogEntry, State } from "../types";
+import { AgentMessage, State } from "../types";
 import { aggregateAgentMessages } from "./aggregateAgentMessages";
 import { SketchTailwindElement } from "./sketch-tailwind-element";
 
@@ -19,7 +20,7 @@
 import "./sketch-view-mode-select";
 import "./sketch-todo-panel";
 
-import { createRef, ref } from "lit/directives/ref.js";
+import { createRef } from "lit/directives/ref.js";
 import { SketchChatInput } from "./sketch-chat-input";
 
 type ViewMode = "chat" | "diff2" | "terminal";
@@ -322,12 +323,7 @@
     }
   }
 
-  private _handleMultipleChoice(event: CustomEvent) {
-    window.console.log("_handleMultipleChoice", event);
-    this._sendChat;
-  }
-
-  private _handleDiffComment(event: CustomEvent) {
+  private _handleDiffComment(_event: CustomEvent) {
     // Empty stub required by the event binding in the template
     // Actual handling occurs at global level in sketch-chat-input component
   }
@@ -567,7 +563,7 @@
       try {
         const todoData = JSON.parse(latestTodoContent);
         hasTodos = todoData.items && todoData.items.length > 0;
-      } catch (error) {
+      } catch {
         // Invalid JSON, treat as no todos
         hasTodos = false;
       }
@@ -1102,12 +1098,6 @@
       }
     });
 
-    // Setup end button
-    const endButton = this.renderRoot?.querySelector(
-      "#endButton",
-    ) as HTMLButtonElement;
-    // We're already using the @click binding in the HTML, so manual event listener not needed here
-
     // Process any existing messages to find commit information
     if (this.messages && this.messages.length > 0) {
       // Update last commit info via container status component
diff --git a/webui/src/web-components/sketch-app-shell.test.ts b/webui/src/web-components/sketch-app-shell.test.ts
index 94e9a55..6c8d468 100644
--- a/webui/src/web-components/sketch-app-shell.test.ts
+++ b/webui/src/web-components/sketch-app-shell.test.ts
@@ -1,3 +1,4 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
 import { test, expect } from "@sand4rt/experimental-ct-web";
 import { SketchAppShell } from "./sketch-app-shell";
 import { initialMessages, initialState } from "../fixtures/dummy";
@@ -327,37 +328,6 @@
   page,
   mount,
 }) => {
-  // Create test messages where the last agent message doesn't have end_of_turn
-  const testMessages = [
-    {
-      idx: 0,
-      type: "user" as const,
-      content: "Please help me",
-      timestamp: "2023-05-15T12:00:00Z",
-      end_of_turn: true,
-      conversation_id: "conv123",
-      parent_conversation_id: null,
-    },
-    {
-      idx: 1,
-      type: "agent" as const,
-      content: "Working on it...",
-      timestamp: "2023-05-15T12:01:00Z",
-      end_of_turn: false, // Agent is still working
-      conversation_id: "conv123",
-      parent_conversation_id: null,
-    },
-    {
-      idx: 2,
-      type: "commit" as const,
-      content: "Commit detected: def456",
-      timestamp: "2023-05-15T12:02:00Z",
-      end_of_turn: false,
-      conversation_id: "conv123",
-      parent_conversation_id: null,
-    },
-  ];
-
   // Skip SSE mocking for this test - we'll set data directly
   await page.route("**/stream*", async (route) => {
     await route.abort();
diff --git a/webui/src/web-components/sketch-chat-input.test.ts b/webui/src/web-components/sketch-chat-input.test.ts
index 532ccf4..8c1a47a 100644
--- a/webui/src/web-components/sketch-chat-input.test.ts
+++ b/webui/src/web-components/sketch-chat-input.test.ts
@@ -1,3 +1,4 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
 import { test, expect } from "@sand4rt/experimental-ct-web";
 import { SketchChatInput } from "./sketch-chat-input";
 
diff --git a/webui/src/web-components/sketch-chat-input.ts b/webui/src/web-components/sketch-chat-input.ts
index 557e171..6ccdb32 100644
--- a/webui/src/web-components/sketch-chat-input.ts
+++ b/webui/src/web-components/sketch-chat-input.ts
@@ -1,5 +1,5 @@
 import { html } from "lit";
-import { customElement, property, state, query } from "lit/decorators.js";
+import { customElement, state, query } from "lit/decorators.js";
 import { SketchTailwindElement } from "./sketch-tailwind-element.js";
 
 @customElement("sketch-chat-input")
diff --git a/webui/src/web-components/sketch-container-status.ts b/webui/src/web-components/sketch-container-status.ts
index 09fe41e..5e276a8 100644
--- a/webui/src/web-components/sketch-container-status.ts
+++ b/webui/src/web-components/sketch-container-status.ts
@@ -270,7 +270,7 @@
       return html``;
     }
 
-    const sshHost = this.getSSHHostname();
+    const _sshHost = this.getSSHHostname();
     const sshConnectionString = this.getSSHConnectionString();
     const sshCommand = `ssh ${sshConnectionString}`;
     const vscodeCommand = `code --remote ssh-remote+${sshConnectionString} /app -n`;
@@ -604,9 +604,9 @@
                       >Total cost:</span
                     >
                     <span id="totalCost" class="text-xs font-semibold break-all"
-                      >$${(this.state?.total_usage?.total_cost_usd).toFixed(
-                        2,
-                      )}</span
+                      >$${(
+                        this.state?.total_usage?.total_cost_usd ?? 0
+                      ).toFixed(2)}</span
                     >
                   </div>
                 `
diff --git a/webui/src/web-components/sketch-diff-range-picker.ts b/webui/src/web-components/sketch-diff-range-picker.ts
index 127732a..6a656f3 100644
--- a/webui/src/web-components/sketch-diff-range-picker.ts
+++ b/webui/src/web-components/sketch-diff-range-picker.ts
@@ -3,7 +3,7 @@
 
 import { css, html, LitElement } from "lit";
 import { customElement, property, state } from "lit/decorators.js";
-import { GitDataService, DefaultGitDataService } from "./git-data-service";
+import { GitDataService } from "./git-data-service";
 import { GitLogEntry } from "../types";
 
 /**
@@ -494,7 +494,7 @@
   /**
    * Handle blur event on select button
    */
-  handleBlur(event: FocusEvent) {
+  handleBlur(_event: FocusEvent) {
     // Small delay to allow click events to process
     setTimeout(() => {
       if (!this.shadowRoot?.activeElement?.closest(".custom-select")) {
diff --git a/webui/src/web-components/sketch-diff2-view.ts b/webui/src/web-components/sketch-diff2-view.ts
index ccdfebc..650826c 100644
--- a/webui/src/web-components/sketch-diff2-view.ts
+++ b/webui/src/web-components/sketch-diff2-view.ts
@@ -1,13 +1,10 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
 import { css, html, LitElement } from "lit";
 import { customElement, property, state } from "lit/decorators.js";
 import "./sketch-monaco-view";
 import "./sketch-diff-range-picker";
 import "./sketch-diff-empty-view";
-import {
-  GitDiffFile,
-  GitDataService,
-  DefaultGitDataService,
-} from "./git-data-service";
+import { GitDiffFile, GitDataService } from "./git-data-service";
 import { DiffRange } from "./sketch-diff-range-picker";
 
 /**
@@ -694,13 +691,11 @@
     this.fileContents.clear();
 
     try {
-      let fromCommit: string;
-      let toCommit: string;
       let isUnstagedChanges = false;
 
       // Determine the commits to compare based on the current range
-      fromCommit = this.currentRange.from;
-      toCommit = this.currentRange.to;
+      const _fromCommit = this.currentRange.from;
+      const toCommit = this.currentRange.to;
       // Check if this is an unstaged changes view
       isUnstagedChanges = toCommit === "";
 
diff --git a/webui/src/web-components/sketch-monaco-view.test.ts b/webui/src/web-components/sketch-monaco-view.test.ts
index 7366153..d967cc9 100644
--- a/webui/src/web-components/sketch-monaco-view.test.ts
+++ b/webui/src/web-components/sketch-monaco-view.test.ts
@@ -1,3 +1,4 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
 import { test, expect } from "@sand4rt/experimental-ct-web";
 import { CodeDiffEditor } from "./sketch-monaco-view";
 
diff --git a/webui/src/web-components/sketch-monaco-view.ts b/webui/src/web-components/sketch-monaco-view.ts
index 917b816..fa657f9 100644
--- a/webui/src/web-components/sketch-monaco-view.ts
+++ b/webui/src/web-components/sketch-monaco-view.ts
@@ -1,3 +1,4 @@
+/* eslint-disable @typescript-eslint/no-explicit-any, no-async-promise-executor, @typescript-eslint/ban-ts-comment */
 import { css, html, LitElement } from "lit";
 import { customElement, property, state } from "lit/decorators.js";
 import { createRef, Ref, ref } from "lit/directives/ref.js";
@@ -1653,7 +1654,7 @@
           remeasureFonts: () => {
             window.monaco!.editor.remeasureFonts();
             (window as any).sketchDebug.editors.forEach(
-              (editor: any, index: number) => {
+              (editor: any, _index: number) => {
                 if (editor && editor.layout) {
                   editor.layout();
                 }
@@ -1662,7 +1663,7 @@
           },
           layoutAll: () => {
             (window as any).sketchDebug.editors.forEach(
-              (editor: any, index: number) => {
+              (editor: any, _index: number) => {
                 if (editor && editor.layout) {
                   editor.layout();
                 }
diff --git a/webui/src/web-components/sketch-terminal.ts b/webui/src/web-components/sketch-terminal.ts
index 0c3b9a7..38e7425 100644
--- a/webui/src/web-components/sketch-terminal.ts
+++ b/webui/src/web-components/sketch-terminal.ts
@@ -1,6 +1,7 @@
 import { Terminal } from "@xterm/xterm";
 import { FitAddon } from "@xterm/addon-fit";
 
+/* eslint-disable @typescript-eslint/ban-ts-comment */
 import { css, html, LitElement } from "lit";
 import { customElement } from "lit/decorators.js";
 import "./sketch-container-status";
diff --git a/webui/src/web-components/sketch-timeline-message.ts b/webui/src/web-components/sketch-timeline-message.ts
index 1fb8334..695ef79 100644
--- a/webui/src/web-components/sketch-timeline-message.ts
+++ b/webui/src/web-components/sketch-timeline-message.ts
@@ -1,3 +1,4 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
 import { css, html, LitElement, render } from "lit";
 import { unsafeHTML } from "lit/directives/unsafe-html.js";
 import { customElement, property, state } from "lit/decorators.js";
@@ -1124,7 +1125,7 @@
         second: "2-digit",
         hour12: true,
       });
-    } catch (e) {
+    } catch {
       return defaultValue;
     }
   }
@@ -1136,7 +1137,7 @@
     if (num === undefined || num === null) return defaultValue;
     try {
       return num.toLocaleString();
-    } catch (e) {
+    } catch {
       return String(num);
     }
   }
@@ -1150,7 +1151,7 @@
       // Use 4 decimal places for message-level costs, 2 for totals
       const decimalPlaces = isMessageLevel ? 4 : 2;
       return `$${parseFloat(String(num)).toFixed(decimalPlaces)}`;
-    } catch (e) {
+    } catch {
       return defaultValue;
     }
   }
diff --git a/webui/src/web-components/sketch-timeline.test.ts b/webui/src/web-components/sketch-timeline.test.ts
index 314c5a0..64323c0 100644
--- a/webui/src/web-components/sketch-timeline.test.ts
+++ b/webui/src/web-components/sketch-timeline.test.ts
@@ -1,3 +1,4 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
 import { test, expect } from "@sand4rt/experimental-ct-web";
 import { SketchTimeline } from "./sketch-timeline";
 import { AgentMessage } from "../types";
diff --git a/webui/src/web-components/sketch-timeline.ts b/webui/src/web-components/sketch-timeline.ts
index b0edb42..b0e6d4c 100644
--- a/webui/src/web-components/sketch-timeline.ts
+++ b/webui/src/web-components/sketch-timeline.ts
@@ -1,3 +1,4 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
 import { html } from "lit";
 import { PropertyValues } from "lit";
 import { repeat } from "lit/directives/repeat.js";
@@ -679,7 +680,7 @@
     }
   }
 
-  private _handleScroll(event) {
+  private _handleScroll(_event) {
     if (!this.scrollContainer.value) return;
 
     const container = this.scrollContainer.value;
@@ -898,13 +899,13 @@
               ? repeat(
                   this.visibleMessages,
                   this.messageKey,
-                  (message, index) => {
+                  (message, _index) => {
                     // Find the previous message in the full filtered messages array
                     const filteredMessages = this.filteredMessages;
                     const messageIndex = filteredMessages.findIndex(
                       (m) => m === message,
                     );
-                    let previousMessage =
+                    const previousMessage =
                       messageIndex > 0
                         ? filteredMessages[messageIndex - 1]
                         : undefined;
diff --git a/webui/src/web-components/sketch-todo-panel.ts b/webui/src/web-components/sketch-todo-panel.ts
index dd23276..7760faf 100644
--- a/webui/src/web-components/sketch-todo-panel.ts
+++ b/webui/src/web-components/sketch-todo-panel.ts
@@ -1,6 +1,6 @@
 import { css, html, LitElement } from "lit";
 import { customElement, property, state } from "lit/decorators.js";
-import { unsafeHTML } from "lit/directives/unsafe-html.js";
+// import { unsafeHTML } from "lit/directives/unsafe-html.js"; // Unused import
 import { TodoList, TodoItem } from "../types.js";
 
 @customElement("sketch-todo-panel")
@@ -441,7 +441,7 @@
       const completedCount = this.todoList.items.filter(
         (item) => item.status === "completed",
       ).length;
-      const inProgressCount = this.todoList.items.filter(
+      const _inProgressCount = this.todoList.items.filter(
         (item) => item.status === "in-progress",
       ).length;
 
@@ -521,7 +521,7 @@
     this.commentText = "";
   }
 
-  private handleOverlayClick(e: Event) {
+  private handleOverlayClick(_e: Event) {
     // Close when clicking outside the comment box
     this.closeCommentBox();
   }
diff --git a/webui/src/web-components/sketch-tool-card.ts b/webui/src/web-components/sketch-tool-card.ts
index c12f722..34e9020 100644
--- a/webui/src/web-components/sketch-tool-card.ts
+++ b/webui/src/web-components/sketch-tool-card.ts
@@ -7,7 +7,7 @@
   MultipleChoiceParams,
   State,
 } from "../types";
-import { marked, MarkedOptions, Renderer } from "marked";
+import { marked } from "marked";
 import DOMPurify from "dompurify";
 
 // Shared utility function for markdown rendering with DOMPurify sanitization
diff --git a/webui/src/web-components/sketch-view-mode-select.test.ts b/webui/src/web-components/sketch-view-mode-select.test.ts
index 3862393..da4d10c 100644
--- a/webui/src/web-components/sketch-view-mode-select.test.ts
+++ b/webui/src/web-components/sketch-view-mode-select.test.ts
@@ -1,3 +1,4 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
 import { test, expect } from "@sand4rt/experimental-ct-web";
 import { SketchViewModeSelect } from "./sketch-view-mode-select";