webui: migrate sketch-monaco-view to tailind

Co-Authored-By: sketch <hello@sketch.dev>
Change-ID: s2b8f6e849aabb45fk
diff --git a/webui/src/web-components/demo/sketch-network-status.demo.ts b/webui/src/web-components/demo/sketch-network-status.demo.ts
deleted file mode 100644
index 2c808c7..0000000
--- a/webui/src/web-components/demo/sketch-network-status.demo.ts
+++ /dev/null
@@ -1,91 +0,0 @@
-/* eslint-disable @typescript-eslint/no-explicit-any */
-import { DemoModule } from "./demo-framework/types";
-import { demoUtils } from "./demo-fixtures/index";
-
-const demo: DemoModule = {
-  title: "Sketch Network Status Demo",
-  description:
-    "Status indicators showing different connection and activity states",
-  imports: ["../sketch-network-status.ts", "../sketch-call-status.ts"],
-
-  customStyles: `
-    .status-container {
-      margin: 20px 0;
-      padding: 10px;
-      border: 1px solid #ccc;
-      border-radius: 4px;
-    }
-    .label {
-      font-weight: bold;
-      margin-bottom: 5px;
-    }
-  `,
-
-  setup: async (container: HTMLElement) => {
-    const section = demoUtils.createDemoSection(
-      "Status Indicators",
-      "Demonstrates different connection and activity states using sketch-call-status component",
-    );
-
-    // Connected State
-    const connectedContainer = document.createElement("div");
-    connectedContainer.className = "status-container";
-
-    const connectedLabel = document.createElement("div");
-    connectedLabel.className = "label";
-    connectedLabel.textContent = "Connected State:";
-
-    const connectedStatus = document.createElement("sketch-call-status") as any;
-    connectedStatus.isDisconnected = false;
-    connectedStatus.isIdle = true;
-    connectedStatus.llmCalls = 0;
-    connectedStatus.toolCalls = [];
-
-    connectedContainer.appendChild(connectedLabel);
-    connectedContainer.appendChild(connectedStatus);
-
-    // Working State
-    const workingContainer = document.createElement("div");
-    workingContainer.className = "status-container";
-
-    const workingLabel = document.createElement("div");
-    workingLabel.className = "label";
-    workingLabel.textContent = "Working State:";
-
-    const workingStatus = document.createElement("sketch-call-status") as any;
-    workingStatus.isDisconnected = false;
-    workingStatus.isIdle = false;
-    workingStatus.llmCalls = 1;
-    workingStatus.toolCalls = ["bash"];
-
-    workingContainer.appendChild(workingLabel);
-    workingContainer.appendChild(workingStatus);
-
-    // Disconnected State
-    const disconnectedContainer = document.createElement("div");
-    disconnectedContainer.className = "status-container";
-
-    const disconnectedLabel = document.createElement("div");
-    disconnectedLabel.className = "label";
-    disconnectedLabel.textContent = "Disconnected State:";
-
-    const disconnectedStatus = document.createElement(
-      "sketch-call-status",
-    ) as any;
-    disconnectedStatus.isDisconnected = true;
-    disconnectedStatus.isIdle = true;
-    disconnectedStatus.llmCalls = 0;
-    disconnectedStatus.toolCalls = [];
-
-    disconnectedContainer.appendChild(disconnectedLabel);
-    disconnectedContainer.appendChild(disconnectedStatus);
-
-    // Add all containers to section
-    section.appendChild(connectedContainer);
-    section.appendChild(workingContainer);
-    section.appendChild(disconnectedContainer);
-    container.appendChild(section);
-  },
-};
-
-export default demo;
diff --git a/webui/src/web-components/sketch-monaco-view.ts b/webui/src/web-components/sketch-monaco-view.ts
index 2ab89fa..1e558f8 100644
--- a/webui/src/web-components/sketch-monaco-view.ts
+++ b/webui/src/web-components/sketch-monaco-view.ts
@@ -1,7 +1,8 @@
 /* eslint-disable @typescript-eslint/no-explicit-any, no-async-promise-executor, @typescript-eslint/ban-ts-comment */
-import { css, html, LitElement } from "lit";
+import { html } from "lit";
 import { customElement, property, state } from "lit/decorators.js";
 import { createRef, Ref, ref } from "lit/directives/ref.js";
+import { SketchTailwindElement } from "./sketch-tailwind-element.js";
 
 // See https://rodydavis.com/posts/lit-monaco-editor for some ideas.
 
@@ -74,24 +75,24 @@
 const monacoStyles = `
   /* Import Monaco editor styles */
   @import url('./static/monaco/min/vs/editor/editor.main.css');
-  
+
   /* Codicon font is now defined globally in sketch-app-shell.css */
-  
+
   /* Custom Monaco styles */
   .monaco-editor {
     width: 100%;
     height: 100%;
   }
-  
+
   /* Ensure light theme colors */
   .monaco-editor, .monaco-editor-background, .monaco-editor .inputarea.ime-input {
     background-color: var(--monaco-editor-bg, #ffffff) !important;
   }
-  
+
   .monaco-editor .margin {
     background-color: var(--monaco-editor-margin, #f5f5f5) !important;
   }
-  
+
   /* Glyph decoration styles - only show on hover */
   .comment-glyph-decoration {
     width: 16px !important;
@@ -100,7 +101,7 @@
     opacity: 0;
     transition: opacity 0.2s ease;
   }
-  
+
   .comment-glyph-decoration:before {
     content: '💬';
     font-size: 12px;
@@ -110,7 +111,7 @@
     display: block;
     text-align: center;
   }
-  
+
   .comment-glyph-decoration.hover-visible {
     opacity: 1;
   }
@@ -138,7 +139,7 @@
 };
 
 @customElement("sketch-monaco-view")
-export class CodeDiffEditor extends LitElement {
+export class CodeDiffEditor extends SketchTailwindElement {
   // Editable state
   @property({ type: Boolean, attribute: "editable-right" })
   editableRight?: boolean;
@@ -275,192 +276,56 @@
     });
   }
 
-  static styles = css`
-    /* Save indicator styles */
-    .save-indicator {
-      position: absolute;
-      top: 4px;
-      right: 4px;
-      padding: 3px 8px;
-      border-radius: 3px;
-      font-size: 12px;
-      font-family: system-ui, sans-serif;
-      color: white;
-      z-index: 100;
-      opacity: 0.9;
-      pointer-events: none;
-      transition: opacity 0.3s ease;
-    }
-
-    .save-indicator.idle {
-      background-color: #6c757d;
-    }
-
-    .save-indicator.modified {
-      background-color: #f0ad4e;
-    }
-
-    .save-indicator.saving {
-      background-color: #5bc0de;
-    }
-
-    .save-indicator.saved {
-      background-color: #5cb85c;
-    }
-
-    /* Editor host styles */
-    :host {
+  render() {
+    // Set host element styles for layout (equivalent to :host styles)
+    this.style.cssText = `
       --editor-width: 100%;
       --editor-height: 100%;
       display: flex;
-      flex: none; /* Don't grow/shrink - size is determined by content */
-      min-height: 0; /* Critical for flex layout */
-      position: relative; /* Establish positioning context */
-      width: 100%; /* Take full width */
-      /* Height will be set dynamically by setupAutoSizing */
-    }
-    main {
-      width: 100%;
-      height: 100%; /* Fill the host element completely */
-      border: 1px solid #e0e0e0;
-      flex: none; /* Size determined by parent */
-      min-height: 200px; /* Ensure a minimum height for the editor */
-      /* Remove absolute positioning - use normal block layout */
+      flex: none;
+      min-height: 0;
       position: relative;
-      display: block;
-      box-sizing: border-box; /* Include border in width calculation */
-    }
-
-    /* Comment box styles */
-    .comment-box {
-      position: fixed;
-      background-color: white;
-      border: 1px solid #ddd;
-      border-radius: 4px;
-      box-shadow: 0 3px 10px rgba(0, 0, 0, 0.15);
-      padding: 12px;
-      z-index: 10001;
-      width: 600px;
-      animation: fadeIn 0.2s ease-in-out;
-      max-height: 80vh;
-      overflow-y: auto;
-    }
-
-    .comment-box-header {
-      display: flex;
-      justify-content: space-between;
-      align-items: center;
-      margin-bottom: 8px;
-    }
-
-    .comment-box-header h3 {
-      margin: 0;
-      font-size: 14px;
-      font-weight: 500;
-    }
-
-    .close-button {
-      background: none;
-      border: none;
-      cursor: pointer;
-      font-size: 16px;
-      color: #666;
-      padding: 2px 6px;
-    }
-
-    .close-button:hover {
-      color: #333;
-    }
-
-    .selected-text-preview {
-      background-color: #f5f5f5;
-      border: 1px solid #eee;
-      border-radius: 3px;
-      padding: 8px;
-      margin-bottom: 10px;
-      font-family: monospace;
-      font-size: 12px;
-      overflow-y: auto;
-      white-space: pre-wrap;
-      word-break: break-all;
-      line-height: 1.4;
-    }
-
-    .selected-text-preview.small-selection {
-      /* For selections of 10 lines or fewer, ensure all content is visible */
-      max-height: none;
-    }
-
-    .selected-text-preview.large-selection {
-      /* For selections larger than 10 lines, limit height with scroll */
-      max-height: 280px; /* Approximately 10 lines at 12px font with 1.4 line-height */
-    }
-
-    .comment-textarea {
       width: 100%;
-      min-height: 80px;
-      padding: 8px;
-      border: 1px solid #ddd;
-      border-radius: 3px;
-      resize: vertical;
-      font-family: inherit;
-      margin-bottom: 10px;
-      box-sizing: border-box;
-    }
+    `;
 
-    .comment-actions {
-      display: flex;
-      justify-content: flex-end;
-      gap: 8px;
-    }
-
-    .comment-actions button {
-      padding: 6px 12px;
-      border-radius: 3px;
-      cursor: pointer;
-      font-size: 12px;
-    }
-
-    .cancel-button {
-      background-color: transparent;
-      border: 1px solid #ddd;
-    }
-
-    .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;
-      }
-    }
-  `;
-
-  render() {
     return html`
       <style>
         ${monacoStyles}
+
+        /* Custom animation for comment box fade-in */
+        @keyframes fadeIn {
+          from {
+            opacity: 0;
+          }
+          to {
+            opacity: 1;
+          }
+        }
+        .animate-fade-in {
+          animation: fadeIn 0.2s ease-in-out;
+        }
       </style>
-      <main ${ref(this.container)}></main>
+
+      <main
+        ${ref(this.container)}
+        class="w-full h-full border border-gray-300 flex-none min-h-[200px] relative block box-border"
+      ></main>
 
       <!-- Save indicator - shown when editing -->
       ${this.editableRight
         ? html`
-            <div class="save-indicator ${this.saveState}">
+            <div
+              class="absolute top-1 right-1 px-2 py-0.5 rounded text-xs font-sans text-white z-[100] opacity-90 pointer-events-none transition-opacity duration-300 ${this
+                .saveState === "idle"
+                ? "bg-gray-500"
+                : this.saveState === "modified"
+                  ? "bg-yellow-500"
+                  : this.saveState === "saving"
+                    ? "bg-blue-400"
+                    : this.saveState === "saved"
+                      ? "bg-green-500"
+                      : "bg-gray-500"}"
+            >
               ${this.saveState === "idle"
                 ? "Editable"
                 : this.saveState === "modified"
@@ -478,36 +343,48 @@
       ${this.showCommentBox
         ? html`
             <div
-              class="comment-box"
+              class="fixed bg-white border border-gray-300 rounded shadow-lg p-3 z-[10001] w-[600px] animate-fade-in max-h-[80vh] overflow-y-auto"
               style="top: ${this.commentBoxPosition.top}px; left: ${this
                 .commentBoxPosition.left}px;"
             >
-              <div class="comment-box-header">
-                <h3>Add comment</h3>
-                <button class="close-button" @click="${this.closeCommentBox}">
+              <div class="flex justify-between items-center mb-2">
+                <h3 class="m-0 text-sm font-medium">Add comment</h3>
+                <button
+                  class="bg-none border-none cursor-pointer text-base text-gray-600 px-1.5 py-0.5 hover:text-gray-800"
+                  @click="${this.closeCommentBox}"
+                >
                   ×
                 </button>
               </div>
               ${this.selectedLines
                 ? html`
                     <div
-                      class="selected-text-preview ${this.getPreviewCssClass()}"
+                      class="bg-gray-100 border border-gray-200 rounded p-2 mb-2.5 font-mono text-xs overflow-y-auto whitespace-pre-wrap break-all leading-relaxed ${this.getPreviewCssClass() ===
+                      "small-selection"
+                        ? ""
+                        : "max-h-[280px]"}"
                     >
                       ${this.selectedLines.text}
                     </div>
                   `
                 : ""}
               <textarea
-                class="comment-textarea"
+                class="w-full min-h-[80px] p-2 border border-gray-300 rounded resize-y font-inherit mb-2.5 box-border"
                 placeholder="Type your comment here..."
                 .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 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-600 text-white border-none hover:bg-blue-700"
+                  @click="${this.submitComment}"
+                >
                   Add
                 </button>
               </div>
diff --git a/webui/src/web-components/sketch-network-status.test.ts b/webui/src/web-components/sketch-network-status.test.ts
deleted file mode 100644
index b15fbfd..0000000
--- a/webui/src/web-components/sketch-network-status.test.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-import { test, expect } from "@sand4rt/experimental-ct-web";
-import { SketchNetworkStatus } from "./sketch-network-status";
-
-// Test that the network status component doesn't display visible content
-// since we've removed the green dot indicator
-test("network status component is not visible", async ({ mount }) => {
-  const component = await mount(SketchNetworkStatus, {
-    props: {
-      connection: "connected",
-    },
-  });
-
-  // The status container should exist but be hidden with display: none
-  await expect(component.locator(".status-container")).toHaveCSS(
-    "display",
-    "none",
-  );
-});
-
-// Test that the network status component remains invisible regardless of connection state
-test("network status component is not visible when disconnected", async ({
-  mount,
-}) => {
-  const component = await mount(SketchNetworkStatus, {
-    props: {
-      connection: "disconnected",
-      error: "Connection error",
-    },
-  });
-
-  // The status container should exist but be hidden with display: none
-  await expect(component.locator(".status-container")).toHaveCSS(
-    "display",
-    "none",
-  );
-});
diff --git a/webui/src/web-components/sketch-network-status.ts b/webui/src/web-components/sketch-network-status.ts
deleted file mode 100644
index f604bd5..0000000
--- a/webui/src/web-components/sketch-network-status.ts
+++ /dev/null
@@ -1,48 +0,0 @@
-import { css, html, LitElement } from "lit";
-import { customElement, property } from "lit/decorators.js";
-
-@customElement("sketch-network-status")
-export class SketchNetworkStatus extends LitElement {
-  @property()
-  connection: string;
-
-  @property()
-  error: string;
-
-  // See https://lit.dev/docs/components/styles/ for how lit-element handles CSS.
-  // Note that these styles only apply to the scope of this web component's
-  // shadow DOM node, so they won't leak out or collide with CSS declared in
-  // other components or the containing web page (...unless you want it to do that).
-
-  static styles = css`
-    .status-container {
-      display: none; /* Hide by default - we're removing the dot */
-    }
-  `;
-
-  constructor() {
-    super();
-  }
-
-  // See https://lit.dev/docs/components/lifecycle/
-  connectedCallback() {
-    super.connectedCallback();
-  }
-
-  // See https://lit.dev/docs/components/lifecycle/
-  disconnectedCallback() {
-    super.disconnectedCallback();
-  }
-
-  render() {
-    // We no longer show any content as the dot is being removed
-    // The connection status will now be handled by the call-status component
-    return html` <div class="status-container"></div> `;
-  }
-}
-
-declare global {
-  interface HTMLElementTagNameMap {
-    "sketch-network-status": SketchNetworkStatus;
-  }
-}