webui: implement modular demo system with TypeScript and shared fixtures

Replace hand-written HTML demo pages with TypeScript demo modules and
automated infrastructure to reduce maintenance overhead and improve
developer experience with type safety and shared code.

Problems Solved:

Demo Maintenance Overhead:
- Hand-written HTML demo pages contained extensive boilerplate duplication
- No type checking for demo setup code or component data
- Manual maintenance of demo/index.html with available demos
- Difficult to share common fake data between demo pages
- No hot module replacement for demo development

Code Quality and Consistency:
- Demo setup code written in plain JavaScript without type safety
- No validation that demo data matches component interfaces
- Inconsistent styling and structure across demo pages
- Duplicated fake data declarations in each demo file

Solution Architecture:

TypeScript Demo Module System:
- Created DemoModule interface for standardized demo structure
- Demo modules export title, description, imports, and setup functions
- Full TypeScript compilation with type checking for demo code
- Dynamic import system for on-demand demo loading with Vite integration

Shared Demo Infrastructure:
- demo-framework/ with types.ts and demo-runner.ts for core functionality
- DemoRunner class handles dynamic loading, cleanup, and error handling
- Single demo-runner.html page loads any demo module dynamically
- Supports URL hash routing for direct demo links

Centralized Fake Data:
- demo-fixtures/ directory with shared TypeScript data files
- sampleToolCalls, sampleTimelineMessages, and sampleContainerState
- Type-safe imports ensure demo data matches component interfaces
- demoUtils with helper functions for consistent demo UI creation

Auto-generated Index Page:
- generate-index.ts scans for *.demo.ts files and extracts metadata
- Creates index-generated.html with links to all available demos
- Automatically includes demo titles and descriptions
- Eliminates manual maintenance of demo listing

Implementation Details:

Demo Framework:
- DemoRunner.loadDemo() uses dynamic imports with Vite ignore comments
- Automatic component import based on demo module configuration
- Support for demo-specific CSS and cleanup functions
- Error handling with detailed error display for debugging

Demo Module Structure:
- sketch-chat-input.demo.ts: Interactive chat with message history
- sketch-container-status.demo.ts: Status variations with real-time updates
- sketch-tool-calls.demo.ts: Multiple tool call examples with progressive loading
- All use shared fixtures and utilities for consistent experience

Vite Integration:
- Hot Module Replacement works for demo modules and shared fixtures
- TypeScript compilation on-the-fly for immediate feedback
- Dynamic imports work seamlessly with Vite's module system
- @vite-ignore comments prevent import analysis warnings

Testing and Validation:
- Tested demo runner loads and displays available components
- Verified component discovery and dynamic import functionality
- Confirmed shared fixture imports work correctly
- Validated auto-generated index creation and content

Files Modified:
- demo-framework/types.ts: TypeScript interfaces for demo system
- demo-framework/demo-runner.ts: Core demo loading and execution logic
- demo-fixtures/: Shared fake data (tool-calls.ts, timeline-messages.ts, container-status.ts, index.ts)
- demo-runner.html: Interactive demo browser with sidebar navigation
- generate-index.ts: Auto-generation script for demo index
- sketch-chat-input.demo.ts: Converted chat input demo to TypeScript
- sketch-container-status.demo.ts: Container status demo with variations
- sketch-tool-calls.demo.ts: Tool calls demo with interactive examples
- readme.md: Comprehensive documentation for new demo system

Benefits:
- Developers get full TypeScript type checking for demo code
- Shared fake data ensures consistency and reduces duplication
- Hot module replacement provides instant feedback during development
- Auto-generated index eliminates manual maintenance
- Modular architecture makes it easy to add new demos
- Vite integration provides fast development iteration

The new system reduces demo maintenance overhead while providing
better developer experience through TypeScript, shared code, and
automated infrastructure.

Co-Authored-By: sketch <hello@sketch.dev>
Change-ID: s3d91894eb7c4a79fk
diff --git a/webui/src/web-components/demo/sketch-container-status.demo.ts b/webui/src/web-components/demo/sketch-container-status.demo.ts
new file mode 100644
index 0000000..20d6a47
--- /dev/null
+++ b/webui/src/web-components/demo/sketch-container-status.demo.ts
@@ -0,0 +1,147 @@
+/**
+ * Demo module for sketch-container-status component
+ */
+
+import { DemoModule } from "./demo-framework/types";
+import {
+  demoUtils,
+  sampleContainerState,
+  lightUsageState,
+  heavyUsageState,
+} from "./demo-fixtures/index";
+
+const demo: DemoModule = {
+  title: "Container Status Demo",
+  description: "Display container status information with usage statistics",
+  imports: ["../sketch-container-status"],
+  styles: ["/dist/tailwind.css"],
+
+  setup: async (container: HTMLElement) => {
+    // Create demo sections
+    const basicSection = demoUtils.createDemoSection(
+      "Basic Container Status",
+      "Shows current container state with usage information",
+    );
+
+    const variationsSection = demoUtils.createDemoSection(
+      "Usage Variations",
+      "Different usage levels and states",
+    );
+
+    // Basic status component
+    const basicStatus = document.createElement(
+      "sketch-container-status",
+    ) as any;
+    basicStatus.id = "basic-status";
+    basicStatus.state = sampleContainerState;
+
+    // Light usage status
+    const lightStatus = document.createElement(
+      "sketch-container-status",
+    ) as any;
+    lightStatus.id = "light-status";
+    lightStatus.state = lightUsageState;
+
+    const lightLabel = document.createElement("h4");
+    lightLabel.textContent = "Light Usage";
+    lightLabel.style.cssText = "margin: 20px 0 10px 0; color: #24292f;";
+
+    // Heavy usage status
+    const heavyStatus = document.createElement(
+      "sketch-container-status",
+    ) as any;
+    heavyStatus.id = "heavy-status";
+    heavyStatus.state = heavyUsageState;
+
+    const heavyLabel = document.createElement("h4");
+    heavyLabel.textContent = "Heavy Usage";
+    heavyLabel.style.cssText = "margin: 20px 0 10px 0; color: #24292f;";
+
+    // Control buttons for interaction
+    const controlsDiv = document.createElement("div");
+    controlsDiv.style.cssText = "margin-top: 20px;";
+
+    const updateBasicButton = demoUtils.createButton(
+      "Update Basic Status",
+      () => {
+        const updatedState = {
+          ...sampleContainerState,
+          message_count: sampleContainerState.message_count + 1,
+          total_usage: {
+            ...sampleContainerState.total_usage!,
+            messages: sampleContainerState.total_usage!.messages + 1,
+            total_cost_usd: Number(
+              (sampleContainerState.total_usage!.total_cost_usd + 0.05).toFixed(
+                2,
+              ),
+            ),
+          },
+        };
+        basicStatus.state = updatedState;
+      },
+    );
+
+    const toggleSSHButton = demoUtils.createButton("Toggle SSH Status", () => {
+      const currentState = basicStatus.state;
+      basicStatus.state = {
+        ...currentState,
+        ssh_available: !currentState.ssh_available,
+        ssh_error: currentState.ssh_available ? "Connection failed" : undefined,
+      };
+    });
+
+    const resetButton = demoUtils.createButton("Reset to Defaults", () => {
+      basicStatus.state = sampleContainerState;
+      lightStatus.state = lightUsageState;
+      heavyStatus.state = heavyUsageState;
+    });
+
+    controlsDiv.appendChild(updateBasicButton);
+    controlsDiv.appendChild(toggleSSHButton);
+    controlsDiv.appendChild(resetButton);
+
+    // Assemble the demo
+    basicSection.appendChild(basicStatus);
+    basicSection.appendChild(controlsDiv);
+
+    variationsSection.appendChild(lightLabel);
+    variationsSection.appendChild(lightStatus);
+    variationsSection.appendChild(heavyLabel);
+    variationsSection.appendChild(heavyStatus);
+
+    container.appendChild(basicSection);
+    container.appendChild(variationsSection);
+
+    // Add some real-time updates
+    const updateInterval = setInterval(() => {
+      const states = [basicStatus, lightStatus, heavyStatus];
+      states.forEach((status) => {
+        if (status.state) {
+          const updatedState = {
+            ...status.state,
+            message_count:
+              status.state.message_count + Math.floor(Math.random() * 2),
+          };
+          if (Math.random() > 0.7) {
+            // 30% chance to update
+            status.state = updatedState;
+          }
+        }
+      });
+    }, 3000);
+
+    // Store interval for cleanup
+    (container as any).demoInterval = updateInterval;
+  },
+
+  cleanup: async () => {
+    // Clear any intervals
+    const container = document.getElementById("demo-container");
+    if (container && (container as any).demoInterval) {
+      clearInterval((container as any).demoInterval);
+      delete (container as any).demoInterval;
+    }
+  },
+};
+
+export default demo;