webui: Add End button to shutdown container

This change adds a new button to the sketch-app-shell UI alongside the
existing Restart and Stop buttons. When clicked, the End button shows a
confirmation dialog and then sends a request to a new /end endpoint.

The /end endpoint gracefully shuts down the inner sketch container.

Fixes #88
diff --git a/webui/src/web-components/sketch-app-shell.ts b/webui/src/web-components/sketch-app-shell.ts
index e5b9108..7b95bdd 100644
--- a/webui/src/web-components/sketch-app-shell.ts
+++ b/webui/src/web-components/sketch-app-shell.ts
@@ -237,7 +237,8 @@
     }
 
     .restart-button,
-    .stop-button {
+    .stop-button,
+    .end-button {
       background: #2196f3;
       color: white;
       border: none;
@@ -280,6 +281,21 @@
       background-color: #e9a8ad;
     }
 
+    .end-button {
+      background: #6c757d;
+      color: white;
+    }
+
+    .end-button:hover:not(:disabled) {
+      background-color: #5a6268;
+    }
+
+    .end-button:disabled {
+      background-color: #a9acaf;
+      cursor: not-allowed;
+      opacity: 0.7;
+    }
+
     .button-icon {
       width: 16px;
       height: 16px;
@@ -403,6 +419,7 @@
     this._handleMutlipleChoiceSelected =
       this._handleMutlipleChoiceSelected.bind(this);
     this._handleStopClick = this._handleStopClick.bind(this);
+    this._handleEndClick = this._handleEndClick.bind(this);
     this._handleNotificationsToggle =
       this._handleNotificationsToggle.bind(this);
     this._handleWindowFocus = this._handleWindowFocus.bind(this);
@@ -869,6 +886,61 @@
     }
   }
 
+  private async _handleEndClick(event?: Event): Promise<void> {
+    if (event) {
+      event.preventDefault();
+      event.stopPropagation();
+    }
+    // Show confirmation dialog
+    const confirmed = window.confirm(
+      "Ending the session will shut down the underlying container. Are you sure?",
+    );
+    if (!confirmed) return;
+
+    try {
+      const response = await fetch("end", {
+        method: "POST",
+        headers: {
+          "Content-Type": "application/json",
+        },
+        body: JSON.stringify({ reason: "user requested end of session" }),
+      });
+
+      if (!response.ok) {
+        const errorData = await response.text();
+        throw new Error(
+          `Failed to end session: ${response.status} - ${errorData}`,
+        );
+      }
+
+      // After successful response, redirect to messages view
+      // Extract the session ID from the URL
+      const currentUrl = window.location.href;
+      // The URL pattern should be like https://sketch.dev/s/cs71-8qa6-1124-aw79/
+      const urlParts = currentUrl.split("/");
+      let sessionId = "";
+
+      // Find the session ID in the URL (should be after /s/)
+      for (let i = 0; i < urlParts.length; i++) {
+        if (urlParts[i] === "s" && i + 1 < urlParts.length) {
+          sessionId = urlParts[i + 1];
+          break;
+        }
+      }
+
+      if (sessionId) {
+        // Create the messages URL
+        const messagesUrl = `/messages/${sessionId}`;
+        // Redirect to messages view
+        window.location.href = messagesUrl;
+      }
+
+      // End request sent - connection will be closed by server
+    } catch (error) {
+      console.error("Error ending session:", error);
+    }
+  }
+
   openRestartModal() {
     this.restartModalOpen = true;
   }
@@ -987,6 +1059,26 @@
             </svg>
             <span class="button-text">Stop</span>
           </button>
+          <button
+            id="endButton"
+            class="end-button"
+            @click=${this._handleEndClick}
+          >
+            <svg
+              class="button-icon"
+              xmlns="http://www.w3.org/2000/svg"
+              viewBox="0 0 24 24"
+              fill="none"
+              stroke="currentColor"
+              stroke-width="2"
+              stroke-linecap="round"
+              stroke-linejoin="round"
+            >
+              <path d="M18 6L6 18" />
+              <path d="M6 6l12 12" />
+            </svg>
+            <span class="button-text">End</span>
+          </button>
 
           <div
             class="notifications-toggle"
@@ -1122,6 +1214,12 @@
       }
     });
 
+    // Setup end button
+    const endButton = this.renderRoot?.querySelector(
+      "#endButton",
+    ) as HTMLButtonElement;
+    // We're already using the @click binding in the HTML, so manual event listener not needed here
+
     // Process any existing messages to find commit information
     if (this.messages && this.messages.length > 0) {
       // Update last commit info via container status component