sketch: remove shadowDOM dependency from tool card components

Replace shadowDOM-based slot system with property-based composition in all
sketch-tool-card-[TOOL_NAME] components to support shadowDOM-free architecture.

Problem Analysis:
- sketch-tool-card component relied on HTML5 template slots which require shadowDOM
- 13 tool card components used sketch-tool-card as composition base via slots
- shadowDOM dependency blocked broader effort to reduce shadowDOM usage
- Need to preserve all existing functionality while removing slot dependency

Solution Implementation:
- Created sketch-tool-card-base component with property-based content injection
- Replaced slot system with summaryContent, inputContent, resultContent properties
- Maintained all existing styling, behavior, and expand/collapse functionality
- Migrated all 13 existing tool card components to use new base component

Components Migrated:
- sketch-tool-card-about-sketch
- sketch-tool-card-browser-clear-console-logs
- sketch-tool-card-browser-click
- sketch-tool-card-browser-eval
- sketch-tool-card-browser-get-text
- sketch-tool-card-browser-navigate
- sketch-tool-card-browser-recent-console-logs
- sketch-tool-card-browser-resize
- sketch-tool-card-browser-scroll-into-view
- sketch-tool-card-browser-type
- sketch-tool-card-browser-wait-for
- sketch-tool-card-read-image
- sketch-tool-card-take-screenshot

Migration Pattern:
- Changed from: <slot name="summary">content</slot>
- Changed to: .summaryContent=html content
- Preserved all component-specific styling and logic
- Maintained existing API surface for parent components

Architecture Benefits:
- Removes shadowDOM requirement from 13+ components
- Enables future shadowDOM-free component development
- Maintains backward compatibility during migration
- Preserves all existing tool card functionality

Files Added:
- sketch/webui/src/web-components/sketch-tool-card-base.ts (new shadowDOM-free base)

Files Modified:
- All 13 sketch-tool-card-[TOOL_NAME].ts components migrated to use new base

Verification:
- TypeScript compilation passes without errors
- Demo pages render correctly with consistent styling
- Expand/collapse behavior preserved across all tool types

Co-Authored-By: sketch <hello@sketch.dev>
Change-ID: sa3288c1d986356e5k
diff --git a/webui/src/web-components/sketch-tool-card-base.ts b/webui/src/web-components/sketch-tool-card-base.ts
new file mode 100644
index 0000000..1bfdbe1
--- /dev/null
+++ b/webui/src/web-components/sketch-tool-card-base.ts
@@ -0,0 +1,133 @@
+import { html, TemplateResult } from "lit";
+import { customElement, property, state } from "lit/decorators.js";
+import { ToolCall } from "../types";
+import { SketchTailwindElement } from "./sketch-tailwind-element";
+
+@customElement("sketch-tool-card-base")
+export class SketchToolCardBase extends SketchTailwindElement {
+  @property() toolCall: ToolCall;
+  @property() open: boolean;
+  @property() summaryContent: TemplateResult | string = "";
+  @property() inputContent: TemplateResult | string = "";
+  @property() resultContent: TemplateResult | string = "";
+  @state() detailsVisible: boolean = false;
+
+  _cancelToolCall = async (tool_call_id: string, button: HTMLButtonElement) => {
+    button.innerText = "Cancelling";
+    button.disabled = true;
+    try {
+      const response = await fetch("cancel", {
+        method: "POST",
+        headers: { "Content-Type": "application/json" },
+        body: JSON.stringify({
+          tool_call_id: tool_call_id,
+          reason: "user requested cancellation",
+        }),
+      });
+      if (response.ok) {
+        button.parentElement.removeChild(button);
+      } else {
+        button.innerText = "Cancel";
+      }
+    } catch (e) {
+      console.error("cancel", tool_call_id, e);
+    }
+  };
+
+  _toggleDetails(e: Event) {
+    e.stopPropagation();
+    this.detailsVisible = !this.detailsVisible;
+  }
+
+  render() {
+    // Status indicator based on result
+    let statusIcon = html`<span
+      class="flex items-center justify-center text-sm text-yellow-500 animate-spin"
+      >⏳</span
+    >`;
+    if (this.toolCall?.result_message) {
+      statusIcon = this.toolCall?.result_message.tool_error
+        ? html`<span
+            class="flex items-center justify-center text-sm text-gray-500"
+            >〰️</span
+          >`
+        : html`<span
+            class="flex items-center justify-center text-sm text-green-600"
+            >✓</span
+          >`;
+    }
+
+    // Cancel button for pending operations
+    const cancelButton = this.toolCall?.result_message
+      ? ""
+      : html`<button
+          class="cursor-pointer text-white bg-red-600 hover:bg-red-700 disabled:bg-gray-400 disabled:cursor-not-allowed border-none rounded text-xs px-1.5 py-0.5 whitespace-nowrap min-w-[50px]"
+          title="Cancel this operation"
+          @click=${(e: Event) => {
+            e.stopPropagation();
+            this._cancelToolCall(
+              this.toolCall?.tool_call_id,
+              e.target as HTMLButtonElement,
+            );
+          }}
+        >
+          Cancel
+        </button>`;
+
+    // Elapsed time display
+    const elapsed = this.toolCall?.result_message?.elapsed
+      ? html`<span
+          class="text-xs text-gray-600 whitespace-nowrap min-w-[40px] text-right"
+          >${(this.toolCall?.result_message?.elapsed / 1e9).toFixed(1)}s</span
+        >`
+      : html`<span
+          class="text-xs text-gray-600 whitespace-nowrap min-w-[40px] text-right"
+        ></span>`;
+
+    // Initialize details visibility based on open property
+    if (this.open && !this.detailsVisible) {
+      this.detailsVisible = true;
+    }
+
+    return html`<div class="block max-w-full w-full box-border overflow-hidden">
+      <div class="flex flex-col w-full">
+        <div
+          class="flex w-full box-border py-1.5 px-2 pl-3 items-center gap-2 cursor-pointer rounded relative overflow-hidden flex-wrap hover:bg-black/[0.02]"
+          @click=${this._toggleDetails}
+        >
+          <span
+            class="font-mono font-medium text-gray-700 bg-black/[0.05] rounded px-1.5 py-0.5 flex-shrink-0 min-w-[45px] text-xs text-center whitespace-nowrap"
+            >${this.toolCall?.name}</span
+          >
+          <span
+            class="whitespace-normal break-words flex-grow flex-shrink text-gray-700 font-mono text-xs px-1 min-w-[50px] max-w-[calc(100%-150px)] inline-block"
+            >${this.summaryContent}</span
+          >
+          <div
+            class="flex items-center gap-3 ml-auto flex-shrink-0 min-w-[120px] justify-end pr-2"
+          >
+            ${statusIcon} ${elapsed} ${cancelButton}
+          </div>
+        </div>
+        <div
+          class="${this.detailsVisible
+            ? "block"
+            : "hidden"} p-2 bg-black/[0.02] mt-px border-t border-black/[0.05] font-mono text-xs text-gray-800 rounded-b max-w-full w-full box-border overflow-hidden"
+        >
+          ${this.inputContent
+            ? html`<div class="mb-2">${this.inputContent}</div>`
+            : ""}
+          ${this.resultContent
+            ? html`<div class="mt-2">${this.resultContent}</div>`
+            : ""}
+        </div>
+      </div>
+    </div>`;
+  }
+}
+
+declare global {
+  interface HTMLElementTagNameMap {
+    "sketch-tool-card-base": SketchToolCardBase;
+  }
+}