webui: convert sketch-todo-panel to SketchTailwindElement with comprehensive test suite

Convert sketch-todo-panel component from LitElement with CSS-in-JS to SketchTailwindElement
inheritance using Tailwind utility classes, and add complete testing infrastructure with
TypeScript demo module and comprehensive test coverage.

Component Conversion:
- Replace LitElement with SketchTailwindElement inheritance to disable shadow DOM
- Remove 200+ lines of CSS-in-JS styles in favor of Tailwind utility classes
- Convert all styling to Tailwind class compositions while maintaining visual parity
- Add inline fadeIn animation using <style> tag following established patterns
- Preserve all existing functionality: todo rendering, comment system, loading states

CSS-to-Tailwind Mapping:
- Main container: flex flex-col h-full bg-transparent overflow-hidden
- Header section: py-2 px-3 border-b border-gray-300 bg-gray-100 font-semibold text-xs
- Content area: flex-1 overflow-y-auto p-2 pb-5 text-xs leading-relaxed min-h-0
- Todo items: flex items-start p-2 mb-1.5 rounded bg-white border border-gray-300 gap-2
- Loading state: animate-spin with proper Tailwind spinner classes
- Comment modal: fixed inset-0 bg-black bg-opacity-30 z-[10000] with centered content
- Status icons: ✅ completed, 🦉 in-progress, ⚪ queued with proper sizing
- Interactive buttons: hover states and transitions using Tailwind utility classes

Test Infrastructure:
- Create sketch-todo-panel.test.ts with 14 comprehensive test cases
- Test initialization, visibility, state management (loading/error/empty states)
- Test todo rendering: status icons, task descriptions, progress counts
- Test comment system: button visibility, modal interactions, event dispatch
- Test error handling: invalid JSON parsing, empty content scenarios
- Test Tailwind integration: proper class usage, shadow DOM disabled
- Test scrollable interface: large todo lists render and scroll correctly
- Use @sand4rt/experimental-ct-web framework following established patterns
- Include helper functions for mock TodoItem creation and test utilities

Demo Module Integration:
- Create sketch-todo-panel.demo.ts following established TypeScript demo pattern
- Add comprehensive demo scenarios: basic usage, loading/error/empty states
- Include large scrollable list demonstration with multiple todo items
- Add interactive comment functionality testing with event logging
- Add sketch-todo-panel to demo-runner.ts knownComponents registry
- Demonstrate all component states and user interactions comprehensively

TypeScript Compatibility:
- Fix property access for private @state() properties using component.evaluate()
- Use type assertions for addEventListener/removeEventListener on SketchTailwindElement
- Address interface compatibility issues with proper TypeScript patterns
- Remove unused imports and helper functions to maintain ESLint compliance
- Follow established patterns from other SketchTailwindElement components

Files Modified:
- sketch/webui/src/web-components/sketch-todo-panel.ts: TailwindElement conversion with complete CSS removal
- sketch/webui/src/web-components/demo/demo-framework/demo-runner.ts: Added component to registry

Files Added:
- sketch/webui/src/web-components/sketch-todo-panel.test.ts: Comprehensive test suite with 14 test cases
- sketch/webui/src/web-components/demo/sketch-todo-panel.demo.ts: Interactive TypeScript demo module

The conversion maintains complete functional parity while enabling consistent
Tailwind-based styling, comprehensive test coverage, and improved development
experience through integrated demo infrastructure.

Co-Authored-By: sketch <hello@sketch.dev>
Change-ID: seada6841c7c375e5k
diff --git a/webui/src/web-components/sketch-todo-panel.ts b/webui/src/web-components/sketch-todo-panel.ts
index 7760faf..e76de46 100644
--- a/webui/src/web-components/sketch-todo-panel.ts
+++ b/webui/src/web-components/sketch-todo-panel.ts
@@ -1,10 +1,10 @@
-import { css, html, LitElement } from "lit";
+import { html } from "lit";
 import { customElement, property, state } from "lit/decorators.js";
-// import { unsafeHTML } from "lit/directives/unsafe-html.js"; // Unused import
 import { TodoList, TodoItem } from "../types.js";
+import { SketchTailwindElement } from "./sketch-tailwind-element.js";
 
 @customElement("sketch-todo-panel")
-export class SketchTodoPanel extends LitElement {
+export class SketchTodoPanel extends SketchTailwindElement {
   @property()
   visible: boolean = false;
 
@@ -26,322 +26,6 @@
   @state()
   private commentText: string = "";
 
-  static styles = css`
-    :host {
-      display: flex;
-      flex-direction: column;
-      height: 100%;
-      background-color: transparent; /* Let parent handle background */
-      overflow: hidden; /* Ensure proper clipping */
-    }
-
-    .todo-header {
-      padding: 8px 12px;
-      border-bottom: 1px solid #e0e0e0;
-      background-color: #f5f5f5;
-      font-weight: 600;
-      font-size: 13px;
-      color: #333;
-      display: flex;
-      align-items: center;
-      gap: 6px;
-    }
-
-    .todo-icon {
-      width: 14px;
-      height: 14px;
-      color: #666;
-    }
-
-    .todo-content {
-      flex: 1;
-      overflow-y: auto;
-      padding: 8px;
-      padding-bottom: 20px; /* Extra bottom padding for better scrolling */
-      font-family:
-        system-ui,
-        -apple-system,
-        BlinkMacSystemFont,
-        "Segoe UI",
-        sans-serif;
-      font-size: 12px;
-      line-height: 1.4;
-      /* Ensure scrollbar is always accessible */
-      min-height: 0;
-    }
-
-    .todo-content.loading {
-      display: flex;
-      align-items: center;
-      justify-content: center;
-      color: #666;
-    }
-
-    .todo-content.error {
-      color: #d32f2f;
-      display: flex;
-      align-items: center;
-      justify-content: center;
-    }
-
-    .todo-content.empty {
-      color: #999;
-      font-style: italic;
-      display: flex;
-      align-items: center;
-      justify-content: center;
-    }
-
-    /* Todo item styling */
-    .todo-item {
-      display: flex;
-      align-items: flex-start;
-      padding: 8px;
-      margin-bottom: 6px;
-      border-radius: 4px;
-      background-color: #fff;
-      border: 1px solid #e0e0e0;
-      gap: 8px;
-      min-height: 24px; /* Ensure consistent height */
-    }
-
-    .todo-item.queued {
-      border-left: 3px solid #e0e0e0;
-    }
-
-    .todo-item.in-progress {
-      border-left: 3px solid #e0e0e0;
-    }
-
-    .todo-item.completed {
-      border-left: 3px solid #e0e0e0;
-    }
-
-    .todo-status-icon {
-      font-size: 14px;
-      margin-top: 1px;
-      flex-shrink: 0;
-    }
-
-    .todo-main {
-      flex: 1;
-      min-width: 0;
-    }
-
-    .todo-content-text {
-      font-size: 12px;
-      line-height: 1.3;
-      color: #333;
-      word-wrap: break-word;
-    }
-
-    .todo-item-content {
-      display: flex;
-      align-items: flex-start;
-      justify-content: space-between;
-      width: 100%;
-      min-height: 20px; /* Ensure consistent height */
-    }
-
-    .todo-text-section {
-      flex: 1;
-      min-width: 0;
-      padding-right: 8px; /* Space between text and button column */
-    }
-
-    .todo-actions-column {
-      flex-shrink: 0;
-      display: flex;
-      align-items: flex-start;
-      width: 24px; /* Fixed width for button column */
-      justify-content: center;
-    }
-
-    .comment-button {
-      background: none;
-      border: none;
-      cursor: pointer;
-      font-size: 14px;
-      padding: 2px;
-      color: #666;
-      opacity: 0.7;
-      transition: opacity 0.2s ease;
-      width: 20px;
-      height: 20px;
-      display: flex;
-      align-items: center;
-      justify-content: center;
-    }
-
-    .comment-button:hover {
-      opacity: 1;
-      background-color: rgba(0, 0, 0, 0.05);
-      border-radius: 3px;
-    }
-
-    /* Comment box overlay */
-    .comment-overlay {
-      position: fixed;
-      top: 0;
-      left: 0;
-      right: 0;
-      bottom: 0;
-      background-color: rgba(0, 0, 0, 0.3);
-      z-index: 10000;
-      display: flex;
-      align-items: center;
-      justify-content: center;
-      animation: fadeIn 0.2s ease-in-out;
-    }
-
-    .comment-box {
-      background-color: white;
-      border: 1px solid #ddd;
-      border-radius: 6px;
-      box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
-      padding: 16px;
-      width: 400px;
-      max-width: 90vw;
-      max-height: 80vh;
-      overflow-y: auto;
-    }
-
-    .comment-box-header {
-      display: flex;
-      justify-content: space-between;
-      align-items: center;
-      margin-bottom: 12px;
-    }
-
-    .comment-box-header h3 {
-      margin: 0;
-      font-size: 14px;
-      font-weight: 500;
-    }
-
-    .close-button {
-      background: none;
-      border: none;
-      cursor: pointer;
-      font-size: 18px;
-      color: #666;
-      padding: 2px 6px;
-    }
-
-    .close-button:hover {
-      color: #333;
-    }
-
-    .todo-context {
-      background-color: #f8f9fa;
-      border: 1px solid #e9ecef;
-      border-radius: 4px;
-      padding: 8px;
-      margin-bottom: 12px;
-      font-size: 12px;
-    }
-
-    .todo-context-status {
-      font-weight: 500;
-      color: #666;
-      margin-bottom: 4px;
-    }
-
-    .todo-context-task {
-      color: #333;
-    }
-
-    .comment-textarea {
-      width: 100%;
-      min-height: 80px;
-      padding: 8px;
-      border: 1px solid #ddd;
-      border-radius: 4px;
-      resize: vertical;
-      font-family: inherit;
-      font-size: 12px;
-      margin-bottom: 12px;
-      box-sizing: border-box;
-    }
-
-    .comment-actions {
-      display: flex;
-      justify-content: flex-end;
-      gap: 8px;
-    }
-
-    .comment-actions button {
-      padding: 6px 12px;
-      border-radius: 4px;
-      cursor: pointer;
-      font-size: 12px;
-    }
-
-    .cancel-button {
-      background-color: transparent;
-      border: 1px solid #ddd;
-      color: #666;
-    }
-
-    .cancel-button:hover {
-      background-color: #f5f5f5;
-    }
-
-    .submit-button {
-      background-color: #4285f4;
-      color: white;
-      border: none;
-    }
-
-    .submit-button:hover {
-      background-color: #3367d6;
-    }
-
-    @keyframes fadeIn {
-      from {
-        opacity: 0;
-      }
-      to {
-        opacity: 1;
-      }
-    }
-
-    .todo-header-text {
-      display: flex;
-      align-items: center;
-      gap: 6px;
-    }
-
-    .todo-count {
-      background-color: #e0e0e0;
-      color: #666;
-      padding: 2px 6px;
-      border-radius: 10px;
-      font-size: 10px;
-      font-weight: normal;
-    }
-
-    /* Loading spinner */
-    .spinner {
-      width: 20px;
-      height: 20px;
-      border: 2px solid #f3f3f3;
-      border-top: 2px solid #3498db;
-      border-radius: 50%;
-      animation: spin 1s linear infinite;
-      margin-right: 8px;
-    }
-
-    @keyframes spin {
-      0% {
-        transform: rotate(0deg);
-      }
-      100% {
-        transform: rotate(360deg);
-      }
-    }
-  `;
-
   updateTodoContent(content: string) {
     try {
       if (!content.trim()) {
@@ -371,17 +55,21 @@
     const showCommentButton = item.status !== "completed";
 
     return html`
-      <div class="todo-item ${item.status}">
-        <div class="todo-status-icon">${statusIcon}</div>
-        <div class="todo-item-content">
-          <div class="todo-text-section">
-            <div class="todo-content-text">${item.task}</div>
+      <div
+        class="flex items-start p-2 mb-1.5 rounded bg-white border border-gray-300 gap-2 min-h-6 border-l-[3px] border-l-gray-300"
+      >
+        <div class="text-sm mt-0.5 flex-shrink-0">${statusIcon}</div>
+        <div class="flex items-start justify-between w-full min-h-5">
+          <div class="flex-1 min-w-0 pr-2">
+            <div class="text-xs leading-snug text-gray-800 break-words">
+              ${item.task}
+            </div>
           </div>
-          <div class="todo-actions-column">
+          <div class="flex-shrink-0 flex items-start w-6 justify-center">
             ${showCommentButton
               ? html`
                   <button
-                    class="comment-button"
+                    class="bg-transparent border-none cursor-pointer text-sm p-0.5 text-gray-500 opacity-70 transition-opacity duration-200 w-5 h-5 flex items-center justify-center hover:opacity-100 hover:bg-black/5 hover:bg-opacity-5 hover:rounded-sm"
                     @click="${() => this.openCommentBox(item)}"
                     title="Add comment about this TODO item"
                   >
@@ -402,7 +90,7 @@
 
     const todoIcon = html`
       <svg
-        class="todo-icon"
+        class="w-3.5 h-3.5 text-gray-500"
         xmlns="http://www.w3.org/2000/svg"
         viewBox="0 0 24 24"
         fill="none"
@@ -419,14 +107,22 @@
     let contentElement;
     if (this.loading) {
       contentElement = html`
-        <div class="todo-content loading">
-          <div class="spinner"></div>
+        <div
+          class="flex-1 overflow-y-auto p-2 pb-5 text-xs leading-relaxed min-h-0 flex items-center justify-center text-gray-500"
+        >
+          <div
+            class="w-5 h-5 border-2 border-gray-200 border-t-blue-500 rounded-full animate-spin mr-2"
+          ></div>
           Loading todos...
         </div>
       `;
     } else if (this.error) {
       contentElement = html`
-        <div class="todo-content error">Error: ${this.error}</div>
+        <div
+          class="flex-1 overflow-y-auto p-2 pb-5 text-xs leading-relaxed min-h-0 text-red-600 flex items-center justify-center"
+        >
+          Error: ${this.error}
+        </div>
       `;
     } else if (
       !this.todoList ||
@@ -434,7 +130,11 @@
       this.todoList.items.length === 0
     ) {
       contentElement = html`
-        <div class="todo-content empty">No todos available</div>
+        <div
+          class="flex-1 overflow-y-auto p-2 pb-5 text-xs leading-relaxed min-h-0 text-gray-400 italic flex items-center justify-center"
+        >
+          No todos available
+        </div>
       `;
     } else {
       const totalCount = this.todoList.items.length;
@@ -446,21 +146,31 @@
       ).length;
 
       contentElement = html`
-        <div class="todo-header">
-          <div class="todo-header-text">
+        <div
+          class="py-2 px-3 border-b border-gray-300 bg-gray-100 font-semibold text-xs text-gray-800 flex items-center gap-1.5"
+        >
+          <div class="flex items-center gap-1.5">
             ${todoIcon}
             <span>Sketching...</span>
-            <span class="todo-count">${completedCount}/${totalCount}</span>
+            <span
+              class="bg-gray-300 text-gray-500 px-1.5 py-0.5 rounded-full text-xs font-normal"
+              >${completedCount}/${totalCount}</span
+            >
           </div>
         </div>
-        <div class="todo-content">
+        <div
+          class="flex-1 overflow-y-auto p-2 pb-5 text-xs leading-relaxed min-h-0"
+        >
           ${this.todoList.items.map((item) => this.renderTodoItem(item))}
         </div>
       `;
     }
 
     return html`
-      ${contentElement} ${this.showCommentBox ? this.renderCommentBox() : ""}
+      <div class="flex flex-col h-full bg-transparent overflow-hidden">
+        ${contentElement}
+      </div>
+      ${this.showCommentBox ? this.renderCommentBox() : ""}
     `;
   }
 
@@ -475,32 +185,64 @@
       }[this.commentingItem.status] || this.commentingItem.status;
 
     return html`
-      <div class="comment-overlay" @click="${this.handleOverlayClick}">
-        <div class="comment-box" @click="${this.stopPropagation}">
-          <div class="comment-box-header">
-            <h3>Comment on TODO Item</h3>
-            <button class="close-button" @click="${this.closeCommentBox}">
+      <style>
+        @keyframes fadeIn {
+          from {
+            opacity: 0;
+          }
+          to {
+            opacity: 1;
+          }
+        }
+        .animate-fade-in {
+          animation: fadeIn 0.2s ease-in-out;
+        }
+      </style>
+      <div
+        class="fixed inset-0 bg-black/30 z-[10000] flex items-center justify-center animate-fade-in"
+        @click="${this.handleOverlayClick}"
+      >
+        <div
+          class="bg-white border border-gray-300 rounded-md shadow-lg p-4 w-96 max-w-[90vw] max-h-[80vh] overflow-y-auto"
+          @click="${this.stopPropagation}"
+        >
+          <div class="flex justify-between items-center mb-3">
+            <h3 class="m-0 text-sm font-medium">Comment on TODO Item</h3>
+            <button
+              class="bg-transparent border-none cursor-pointer text-lg text-gray-500 px-1.5 py-0.5 hover:text-gray-800"
+              @click="${this.closeCommentBox}"
+            >
               ×
             </button>
           </div>
 
-          <div class="todo-context">
-            <div class="todo-context-status">Status: ${statusText}</div>
-            <div class="todo-context-task">${this.commentingItem.task}</div>
+          <div
+            class="bg-gray-50 border border-gray-200 rounded p-2 mb-3 text-xs"
+          >
+            <div class="font-medium text-gray-500 mb-1">
+              Status: ${statusText}
+            </div>
+            <div class="text-gray-800">${this.commentingItem.task}</div>
           </div>
 
           <textarea
-            class="comment-textarea"
+            class="w-full min-h-20 p-2 border border-gray-300 rounded resize-y text-xs mb-3 box-border"
             placeholder="Type your comment about this TODO item..."
             .value="${this.commentText}"
             @input="${this.handleCommentInput}"
           ></textarea>
 
-          <div class="comment-actions">
-            <button class="cancel-button" @click="${this.closeCommentBox}">
+          <div class="flex justify-end gap-2">
+            <button
+              class="px-3 py-1.5 rounded cursor-pointer text-xs bg-transparent border border-gray-300 text-gray-500 hover:bg-gray-100"
+              @click="${this.closeCommentBox}"
+            >
               Cancel
             </button>
-            <button class="submit-button" @click="${this.submitComment}">
+            <button
+              class="px-3 py-1.5 rounded cursor-pointer text-xs bg-blue-500 text-white border-none hover:bg-blue-600"
+              @click="${this.submitComment}"
+            >
               Add Comment
             </button>
           </div>