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/generate-index.ts b/webui/src/web-components/demo/generate-index.ts
new file mode 100644
index 0000000..015ef88
--- /dev/null
+++ b/webui/src/web-components/demo/generate-index.ts
@@ -0,0 +1,198 @@
+/**
+ * Build-time script to auto-generate demo index page
+ */
+
+import * as fs from "fs";
+import * as path from "path";
+
+interface DemoInfo {
+  name: string;
+  title: string;
+  description?: string;
+  fileName: string;
+}
+
+async function generateIndex() {
+  const demoDir = path.join(__dirname);
+  const files = await fs.promises.readdir(demoDir);
+
+  // Find all .demo.ts files
+  const demoFiles = files.filter((file) => file.endsWith(".demo.ts"));
+
+  const demos: DemoInfo[] = [];
+
+  for (const file of demoFiles) {
+    const componentName = file.replace(".demo.ts", "");
+    const filePath = path.join(demoDir, file);
+
+    try {
+      // Read the file content to extract title and description
+      const content = await fs.promises.readFile(filePath, "utf-8");
+
+      // Extract title from the demo module
+      const titleMatch = content.match(/title:\s*['"]([^'"]+)['"]/);
+      const descriptionMatch = content.match(/description:\s*['"]([^'"]+)['"]/);
+
+      demos.push({
+        name: componentName,
+        title: titleMatch ? titleMatch[1] : formatComponentName(componentName),
+        description: descriptionMatch ? descriptionMatch[1] : undefined,
+        fileName: file,
+      });
+    } catch (error) {
+      console.warn(`Failed to process demo file ${file}:`, error);
+    }
+  }
+
+  // Sort demos alphabetically
+  demos.sort((a, b) => a.title.localeCompare(b.title));
+
+  // Generate HTML index
+  const html = generateIndexHTML(demos);
+
+  // Write the generated index
+  const indexPath = path.join(demoDir, "index-generated.html");
+  await fs.promises.writeFile(indexPath, html, "utf-8");
+
+  console.log(`Generated demo index with ${demos.length} components`);
+  console.log("Available demos:", demos.map((d) => d.name).join(", "));
+}
+
+function formatComponentName(name: string): string {
+  return name
+    .replace(/^sketch-/, "")
+    .replace(/-/g, " ")
+    .replace(/\b\w/g, (l) => l.toUpperCase());
+}
+
+function generateIndexHTML(demos: DemoInfo[]): string {
+  const demoLinks = demos
+    .map((demo) => {
+      const href = `demo-runner.html#${demo.name}`;
+      const description = demo.description ? ` - ${demo.description}` : "";
+
+      return `      <li>
+        <a href="${href}">
+          <strong>${demo.title}</strong>${description}
+        </a>
+      </li>`;
+    })
+    .join("\n");
+
+  return `<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>Sketch Web Components - Demo Index</title>
+    <link rel="stylesheet" href="demo.css" />
+    <style>
+      body {
+        font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
+        max-width: 800px;
+        margin: 40px auto;
+        padding: 20px;
+        line-height: 1.6;
+      }
+      
+      h1 {
+        color: #24292f;
+        border-bottom: 1px solid #d1d9e0;
+        padding-bottom: 10px;
+      }
+      
+      .demo-list {
+        list-style: none;
+        padding: 0;
+      }
+      
+      .demo-list li {
+        margin: 15px 0;
+        padding: 15px;
+        border: 1px solid #d1d9e0;
+        border-radius: 6px;
+        background: #f6f8fa;
+        transition: background-color 0.2s;
+      }
+      
+      .demo-list li:hover {
+        background: #ffffff;
+      }
+      
+      .demo-list a {
+        text-decoration: none;
+        color: #0969da;
+        display: block;
+      }
+      
+      .demo-list a:hover {
+        text-decoration: underline;
+      }
+      
+      .demo-list strong {
+        font-size: 16px;
+        display: block;
+        margin-bottom: 5px;
+      }
+      
+      .stats {
+        background: #fff8dc;
+        padding: 15px;
+        border-radius: 6px;
+        margin: 20px 0;
+        border-left: 4px solid #f9c23c;
+      }
+      
+      .runner-link {
+        display: inline-block;
+        padding: 10px 20px;
+        background: #0969da;
+        color: white;
+        text-decoration: none;
+        border-radius: 6px;
+        margin-top: 20px;
+      }
+      
+      .runner-link:hover {
+        background: #0860ca;
+      }
+    </style>
+  </head>
+  <body>
+    <h1>Sketch Web Components Demo Index</h1>
+    
+    <div class="stats">
+      <strong>Auto-generated index</strong><br>
+      Found ${demos.length} demo component${demos.length === 1 ? "" : "s"} • Last updated: ${new Date().toLocaleString()}
+    </div>
+    
+    <p>
+      This page provides an overview of all available component demos.
+      Click on any component below to view its interactive demo.
+    </p>
+    
+    <a href="demo-runner.html" class="runner-link">🚀 Launch Demo Runner</a>
+    
+    <h2>Available Component Demos</h2>
+    
+    <ul class="demo-list">
+${demoLinks}
+    </ul>
+    
+    <hr style="margin: 40px 0; border: none; border-top: 1px solid #d1d9e0;">
+    
+    <p>
+      <em>This index is automatically generated from available <code>*.demo.ts</code> files.</em><br>
+      To add a new demo, create a <code>component-name.demo.ts</code> file in this directory.
+    </p>
+  </body>
+</html>
+`;
+}
+
+// Run the generator if this script is executed directly
+if (require.main === module) {
+  generateIndex().catch(console.error);
+}
+
+export { generateIndex };