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-tool-calls.demo.ts b/webui/src/web-components/demo/sketch-tool-calls.demo.ts
new file mode 100644
index 0000000..f279657
--- /dev/null
+++ b/webui/src/web-components/demo/sketch-tool-calls.demo.ts
@@ -0,0 +1,138 @@
+/**
+ * Demo module for sketch-tool-calls component
+ */
+
+import { DemoModule } from "./demo-framework/types";
+import {
+ demoUtils,
+ sampleToolCalls,
+ multipleToolCallGroups,
+ longBashCommand,
+} from "./demo-fixtures/index";
+
+const demo: DemoModule = {
+ title: "Tool Calls Demo",
+ description: "Interactive tool call display with various tool types",
+ imports: ["../sketch-tool-calls"],
+
+ setup: async (container: HTMLElement) => {
+ // Create demo sections
+ const basicSection = demoUtils.createDemoSection(
+ "Basic Tool Calls",
+ "Various types of tool calls with results",
+ );
+
+ const interactiveSection = demoUtils.createDemoSection(
+ "Interactive Examples",
+ "Tool calls that can be modified and updated",
+ );
+
+ const groupsSection = demoUtils.createDemoSection(
+ "Tool Call Groups",
+ "Multiple tool calls grouped together",
+ );
+
+ // Basic tool calls component
+ const basicToolCalls = document.createElement("sketch-tool-calls") as any;
+ basicToolCalls.toolCalls = sampleToolCalls.slice(0, 3);
+
+ // Interactive tool calls component
+ const interactiveToolCalls = document.createElement(
+ "sketch-tool-calls",
+ ) as any;
+ interactiveToolCalls.toolCalls = [sampleToolCalls[0]];
+
+ // Control buttons for interaction
+ const controlsDiv = document.createElement("div");
+ controlsDiv.style.cssText = "margin-top: 15px;";
+
+ const addBashButton = demoUtils.createButton("Add Bash Command", () => {
+ const currentCalls = interactiveToolCalls.toolCalls || [];
+ interactiveToolCalls.toolCalls = [...currentCalls, sampleToolCalls[2]];
+ });
+
+ const addLongCommandButton = demoUtils.createButton(
+ "Add Long Command",
+ () => {
+ const currentCalls = interactiveToolCalls.toolCalls || [];
+ interactiveToolCalls.toolCalls = [...currentCalls, longBashCommand];
+ },
+ );
+
+ const clearButton = demoUtils.createButton("Clear Tool Calls", () => {
+ interactiveToolCalls.toolCalls = [];
+ });
+
+ const resetButton = demoUtils.createButton("Reset to Default", () => {
+ interactiveToolCalls.toolCalls = [sampleToolCalls[0]];
+ });
+
+ controlsDiv.appendChild(addBashButton);
+ controlsDiv.appendChild(addLongCommandButton);
+ controlsDiv.appendChild(clearButton);
+ controlsDiv.appendChild(resetButton);
+
+ // Tool call groups
+ const groupsContainer = document.createElement("div");
+ multipleToolCallGroups.forEach((group, index) => {
+ const groupHeader = document.createElement("h4");
+ groupHeader.textContent = `Group ${index + 1}`;
+ groupHeader.style.cssText = "margin: 20px 0 10px 0; color: #24292f;";
+
+ const groupToolCalls = document.createElement("sketch-tool-calls") as any;
+ groupToolCalls.toolCalls = group;
+
+ groupsContainer.appendChild(groupHeader);
+ groupsContainer.appendChild(groupToolCalls);
+ });
+
+ // Progressive loading demo
+ const progressiveSection = demoUtils.createDemoSection(
+ "Progressive Loading Demo",
+ "Tool calls that appear one by one",
+ );
+
+ const progressiveToolCalls = document.createElement(
+ "sketch-tool-calls",
+ ) as any;
+ progressiveToolCalls.toolCalls = [];
+
+ const startProgressiveButton = demoUtils.createButton(
+ "Start Progressive Load",
+ async () => {
+ progressiveToolCalls.toolCalls = [];
+
+ for (let i = 0; i < sampleToolCalls.length; i++) {
+ await demoUtils.delay(1000);
+ const currentCalls = progressiveToolCalls.toolCalls || [];
+ progressiveToolCalls.toolCalls = [
+ ...currentCalls,
+ sampleToolCalls[i],
+ ];
+ }
+ },
+ );
+
+ const progressiveControls = document.createElement("div");
+ progressiveControls.style.cssText = "margin-top: 15px;";
+ progressiveControls.appendChild(startProgressiveButton);
+
+ // Assemble the demo
+ basicSection.appendChild(basicToolCalls);
+
+ interactiveSection.appendChild(interactiveToolCalls);
+ interactiveSection.appendChild(controlsDiv);
+
+ groupsSection.appendChild(groupsContainer);
+
+ progressiveSection.appendChild(progressiveToolCalls);
+ progressiveSection.appendChild(progressiveControls);
+
+ container.appendChild(basicSection);
+ container.appendChild(interactiveSection);
+ container.appendChild(groupsSection);
+ container.appendChild(progressiveSection);
+ },
+};
+
+export default demo;