loop/webui: swtich to web components impl (#1)
* loop/webui: swtich to web components impl
This change reorganizes the original vibe-coded
frontend code into a structure that's much
easier for a human to read and reason about,
while retaining the user-visible functionality
of its vibe-coded predecessor. Perhaps most
importantly, this change makes the code testable.
Some other notable details:
This does not use any of the popular large web
frameworks, but instead follows more of an
"a la carte" approach: leverage features
that already exist in modern web browsers,
like custom elements and shadow DOM.
Templating and basic component lifecycle
management are provided by lit.
State management is nothing fancy. It
doesn't use any library or framework, just
a basic "Events up, properties down"
approach.
* fix bad esbuild.go merge
* loop/webui: don't bundle src/web-components/demo
* loop/webui: don't 'npm ci' dev deps in the container
* rebase to main, undo README.md changes, add webuil.Build() call to LaunchContainer()
diff --git a/loop/webui/src/web-components/demo/demo.css b/loop/webui/src/web-components/demo/demo.css
new file mode 100644
index 0000000..bb9750e
--- /dev/null
+++ b/loop/webui/src/web-components/demo/demo.css
@@ -0,0 +1,9 @@
+body {
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
+ Roboto, sans-serif;
+ margin: 0;
+ padding: 20px;
+ padding-bottom: 100px; /* Adjusted padding for chat container */
+ color: #333;
+ line-height: 1.4; /* Reduced line height for more compact text */
+}
\ No newline at end of file
diff --git a/loop/webui/src/web-components/demo/index.html b/loop/webui/src/web-components/demo/index.html
new file mode 100644
index 0000000..3f728c0
--- /dev/null
+++ b/loop/webui/src/web-components/demo/index.html
@@ -0,0 +1,28 @@
+<html>
+ <head>
+ <link rel="stylesheet" href="demo.css" />
+ </head>
+ <body>
+ sketch web-components demo index
+ <ul>
+ <li><a href="sketch-app-shell.demo.html">sketch-app-shell</a></li>
+ <li><a href="sketch-charts.demo.html">sketch-charts</a></li>
+ <li><a href="sketch-chat-input.demo.html">sketch-chat-input</a></li>
+ <li><a href="sketch-diff-view.demo.html">sketch-diff-view</a></li>
+ <li>
+ <a href="sketch-container-status.demo.html">sketch-container-status</a>
+ </li>
+ <li>
+ <a href="sketch-network-status.demo.html">sketch-network-status</a>
+ </li>
+ <li>
+ <a href="sketch-timeline-message.demo.html">sketch-timeline-message</a>
+ </li>
+ <li><a href="sketch-timeline.demo.html">sketch-timeline</a></li>
+ <li><a href="sketch-tool-calls.demo.html">sketch-tool-calls</a></li>
+ <li>
+ <a href="sketch-view-mode-select.demo.html">sketch-view-mode-select</a>
+ </li>
+ </ul>
+ </body>
+</html>
diff --git a/loop/webui/src/web-components/demo/readme.md b/loop/webui/src/web-components/demo/readme.md
new file mode 100644
index 0000000..8e3c33c
--- /dev/null
+++ b/loop/webui/src/web-components/demo/readme.md
@@ -0,0 +1,14 @@
+# Stand-alone demo pages for sketch web components
+
+These are handy for iterating on specific component UI issues in isolation from the rest of the sketch application, and without having to start a full backend to serve the full frontend app UI.
+
+# How to use this demo directory to iterate on component development
+
+From the `loop/webui` directory:
+
+1. In one shell, run `npm run watch` to build the web components and watch for changes
+1. In another shell, run `npm run demo` to start a local web server to serve the demo pages
+1. open http://localhost:8000/src/web-components/demo/ in your browser
+1. make edits to the .ts code or to the demo.html files and see how it affects the demo pages in real time
+
+Alternately, use the `webui: watch demo` task in VSCode, which runs all of the above for you.
diff --git a/loop/webui/src/web-components/demo/sketch-app-shell.demo.html b/loop/webui/src/web-components/demo/sketch-app-shell.demo.html
new file mode 100644
index 0000000..092ad7c
--- /dev/null
+++ b/loop/webui/src/web-components/demo/sketch-app-shell.demo.html
@@ -0,0 +1,13 @@
+<html>
+ <head>
+ <title>sketch-app-shell demo</title>
+ <link rel="stylesheet" href="demo.css" />
+ <script src="/dist/web-components/sketch-app-shell.js" type="module"></script>
+ </head>
+ <body>
+ <h1>sketch-app-shell demo</h1>
+
+ <sketch-app-shell></sketch-app-shell>
+
+ </body>
+</html>
\ No newline at end of file
diff --git a/loop/webui/src/web-components/demo/sketch-charts.demo.html b/loop/webui/src/web-components/demo/sketch-charts.demo.html
new file mode 100644
index 0000000..b525785
--- /dev/null
+++ b/loop/webui/src/web-components/demo/sketch-charts.demo.html
@@ -0,0 +1,129 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8" />
+ <title>Sketch Charts Demo</title>
+ <script type="module" src="/dist/web-components/sketch-charts.js"></script>
+ <link rel="stylesheet" href="demo.css" />
+ <style>
+ sketch-charts {
+ margin: 20px;
+ max-width: 1000px;
+ }
+
+ body {
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
+ Roboto, sans-serif;
+ }
+ </style>
+ </head>
+ <body>
+ <h1>Sketch Charts Demo</h1>
+ <sketch-charts id="charts"></sketch-charts>
+
+ <script>
+ // Sample data for testing
+ const sampleMessages = [
+ {
+ idx: 1,
+ type: "human",
+ content: "Hello, can you help me with a coding task?",
+ timestamp: new Date(Date.now() - 3600000).toISOString(),
+ usage: { cost_usd: 0.0001 },
+ },
+ {
+ idx: 2,
+ type: "assistant",
+ content:
+ "I'd be happy to help! What kind of coding task are you working on?",
+ timestamp: new Date(Date.now() - 3500000).toISOString(),
+ usage: { cost_usd: 0.0005 },
+ },
+ {
+ idx: 3,
+ type: "human",
+ content: "I need to create a web component using lit-element",
+ timestamp: new Date(Date.now() - 3400000).toISOString(),
+ usage: { cost_usd: 0.0001 },
+ },
+ {
+ idx: 4,
+ type: "assistant",
+ content:
+ "I can definitely help with that. Lit Element is a great library for building web components.",
+ timestamp: new Date(Date.now() - 3300000).toISOString(),
+ usage: { cost_usd: 0.0008 },
+ },
+ {
+ idx: 5,
+ type: "assistant",
+ tool_name: "bash",
+ input: "ls -la",
+ tool_result:
+ "total 16\ndrwxr-xr-x 4 user staff 128 Jan 10 12:34 .\ndrwxr-xr-x 10 user staff 320 Jan 10 12:34 ..\n-rw-r--r-- 1 user staff 123 Jan 10 12:34 file1.txt\n-rw-r--r-- 1 user staff 456 Jan 10 12:34 file2.txt",
+ start_time: new Date(Date.now() - 3200000).toISOString(),
+ end_time: new Date(Date.now() - 3190000).toISOString(),
+ timestamp: new Date(Date.now() - 3190000).toISOString(),
+ usage: { cost_usd: 0.0002 },
+ },
+ {
+ idx: 6,
+ type: "assistant",
+ content: "Let me create a basic web component for you.",
+ timestamp: new Date(Date.now() - 3100000).toISOString(),
+ usage: { cost_usd: 0.0015 },
+ },
+ {
+ idx: 7,
+ type: "human",
+ content: "Can you show me how to handle events in the web component?",
+ timestamp: new Date(Date.now() - 3000000).toISOString(),
+ usage: { cost_usd: 0.0001 },
+ },
+ {
+ idx: 8,
+ type: "assistant",
+ tool_name: "bash",
+ input: "cat example.ts",
+ tool_result:
+ "import { LitElement, html } from 'lit';\nimport { customElement } from 'lit/decorators.js';\n\n@customElement('my-element')\nexport class MyElement extends LitElement {\n render() {\n return html`<div>Hello World</div>`;\n }\n}",
+ start_time: new Date(Date.now() - 2900000).toISOString(),
+ end_time: new Date(Date.now() - 2800000).toISOString(),
+ timestamp: new Date(Date.now() - 2800000).toISOString(),
+ usage: { cost_usd: 0.0003 },
+ },
+ {
+ idx: 9,
+ type: "assistant",
+ content:
+ "Here's how you can handle events in a web component using Lit.",
+ timestamp: new Date(Date.now() - 2700000).toISOString(),
+ usage: { cost_usd: 0.002 },
+ },
+ {
+ idx: 10,
+ type: "human",
+ content: "Thank you! How about adding properties and attributes?",
+ timestamp: new Date(Date.now() - 2600000).toISOString(),
+ usage: { cost_usd: 0.0001 },
+ },
+ {
+ idx: 11,
+ type: "assistant",
+ content:
+ "You can use the @property decorator to define properties in your Lit Element component.",
+ timestamp: new Date(Date.now() - 2500000).toISOString(),
+ usage: { cost_usd: 0.0025 },
+ },
+ ];
+
+ // Set sample data as soon as the component is defined
+ document.addEventListener("DOMContentLoaded", () => {
+ console.time("chart-demo-load");
+ const chartsComponent = document.getElementById("charts");
+ chartsComponent.messages = sampleMessages;
+ console.timeEnd("chart-demo-load");
+ });
+ </script>
+ </body>
+</html>
diff --git a/loop/webui/src/web-components/demo/sketch-chat-input.demo.html b/loop/webui/src/web-components/demo/sketch-chat-input.demo.html
new file mode 100644
index 0000000..4806035
--- /dev/null
+++ b/loop/webui/src/web-components/demo/sketch-chat-input.demo.html
@@ -0,0 +1,32 @@
+<html>
+ <head>
+ <title>sketch-chat-input demo</title>
+ <link rel="stylesheet" href="demo.css" />
+ <script src="/dist/web-components/sketch-chat-input.js" type="module"></script>
+
+ <script>
+ document.addEventListener("DOMContentLoaded", () => {
+ const chatInput = document.querySelector('sketch-chat-input');
+ console.log("chatInput: ", chatInput);
+ chatInput.content = "hi";
+ chatInput.addEventListener("send-chat", (evt) => {
+ console.log('send chat event: ', evt);
+ const msgDiv = document.querySelector("#chat-messages");
+ const newDiv = document.createElement("div");
+ newDiv.innerText = evt.detail.message;
+ msgDiv.append(newDiv);
+ chatInput.content = '';
+ });
+ });
+ </script>
+
+ </head>
+ <body>
+ <h1>sketch-chat-input demo</h1>
+
+ <div id="chat-messages"></div>
+
+ <sketch-chat-input></sketch-chat-input>
+
+ </body>
+</html>
\ No newline at end of file
diff --git a/loop/webui/src/web-components/demo/sketch-container-status.demo.html b/loop/webui/src/web-components/demo/sketch-container-status.demo.html
new file mode 100644
index 0000000..bd2544d
--- /dev/null
+++ b/loop/webui/src/web-components/demo/sketch-container-status.demo.html
@@ -0,0 +1,40 @@
+<html>
+ <head>
+ <title>sketch-container-status demo</title>
+ <link rel="stylesheet" href="demo.css" />
+ <script src="/dist/web-components/sketch-container-status.js" type="module"></script>
+
+ <script>
+ document.addEventListener("DOMContentLoaded", () => {
+ const containerStatus = document.querySelector('#status-2');
+ containerStatus.state = {
+ hostname: 'example.hostname',
+ initial_commit: 'decafbad',
+ message_count: 27,
+ os: 'linux',
+ total_usage: {
+ start_time: 'around lunch',
+ messages:1337,
+ input_tokens: 3,
+ output_tokens: 1000,
+ cache_read_input_tokens: 28,
+ cache_creation_input_tokens: 12354,
+ total_cost_usd: 2.03,
+ },
+ working_dir: '/app',
+ };
+ });
+ </script>
+
+ </head>
+ <body>
+ <h1>sketch-container-status demo</h1>
+
+ Empty:
+ <sketch-container-status id="status-1"></sketch-container-status>
+
+ With state fields set:
+ <sketch-container-status id="status-2"></sketch-container-status>
+
+ </body>
+</html>
\ No newline at end of file
diff --git a/loop/webui/src/web-components/demo/sketch-diff-view.demo.html b/loop/webui/src/web-components/demo/sketch-diff-view.demo.html
new file mode 100644
index 0000000..63b5395
--- /dev/null
+++ b/loop/webui/src/web-components/demo/sketch-diff-view.demo.html
@@ -0,0 +1,104 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <title>Sketch Diff Viewer Demo</title>
+ <link rel="stylesheet" href="../../../node_modules/diff2html/bundles/css/diff2html.min.css">
+ <script type="module" src="/dist/web-components/sketch-diff-view.js"></script>
+ <style>
+ body {
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
+ max-width: 1200px;
+ margin: 0 auto;
+ padding: 2rem;
+ }
+
+ h1 {
+ color: #333;
+ margin-bottom: 2rem;
+ }
+
+ .control-panel {
+ margin-bottom: 2rem;
+ padding: 1rem;
+ background-color: #f0f0f0;
+ border-radius: 4px;
+ }
+
+ input {
+ padding: 0.5rem;
+ border-radius: 4px;
+ border: 1px solid #ccc;
+ width: 300px;
+ }
+
+ button {
+ padding: 0.5rem 1rem;
+ background-color: #2196f3;
+ color: white;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+ margin-left: 1rem;
+ }
+
+ button:hover {
+ background-color: #0d8bf2;
+ }
+ </style>
+
+<script>
+ document.addEventListener('DOMContentLoaded', () => {
+ const diffViewer = document.getElementById('diffViewer');
+ const commitHashInput = document.getElementById('commitHash');
+ const viewDiffButton = document.getElementById('viewDiff');
+ let commit = false;
+ viewDiffButton.addEventListener('click', () => {
+ let diffContent = `diff --git a/sample.txt b/sample.txt
+index 1111111..2222222 100644
+--- a/sample.txt
++++ b/sample.txt
+@@ -1,5 +1,5 @@
+ This is a sample file
+-This line will be removed
++This line is added as a replacement
+ This line stays the same
+-Another line to remove
++A completely new line
+ The last line is unchanged`;
+ if (commit) {
+ // For demo purposes, generate fake diff based on commit hash
+ diffContent = `diff --git a/file-${commit.substring(0,5)}.txt b/file-${commit.substring(0,5)}.txt
+index 3333333..4444444 100644
+--- a/file-${commit.substring(0,5)}.txt
++++ b/file-${commit.substring(0,5)}.txt
+@@ -1,4 +1,6 @@
+ File with commit: ${commit}
++This line was added in commit ${commit}
+ This line exists in both versions
+-This line was removed in commit ${commit}
++This line replaced the removed line
++Another new line added in this commit
+ Last line of the file`;
+ }
+ diffViewer.diffText = diffContent;
+ diffViewer.commitHash = commitHashInput.value.trim();
+ });
+ });
+</script>
+
+</head>
+<body>
+ <h1>Sketch Diff Viewer Demo</h1>
+
+ <div class="control-panel">
+ <label for="commitHash">Commit Hash (leave empty for unstaged changes):</label>
+ <input type="text" id="commitHash" placeholder="Enter commit hash">
+ <button id="viewDiff">View Diff</button>
+ </div>
+
+ <sketch-diff-view id="diffViewer"></sketch-diff-view>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/loop/webui/src/web-components/demo/sketch-network-status.demo.html b/loop/webui/src/web-components/demo/sketch-network-status.demo.html
new file mode 100644
index 0000000..9926b0e
--- /dev/null
+++ b/loop/webui/src/web-components/demo/sketch-network-status.demo.html
@@ -0,0 +1,17 @@
+<html>
+ <head>
+ <title>sketch-network-status demo</title>
+ <link rel="stylesheet" href="demo.css" />
+ <script src="/dist/web-components/sketch-network-status.js" type="module"></script>
+ </head>
+ <body>
+ <h1>sketch-network-status demo</h1>
+
+ Connected:
+ <sketch-network-status connection="connected" message="connected"></sketch-network-status>
+
+ Error:
+ <sketch-network-status connection="error" error="error"></sketch-network-status>
+
+ </body>
+</html>
\ No newline at end of file
diff --git a/loop/webui/src/web-components/demo/sketch-timeline-message.demo.html b/loop/webui/src/web-components/demo/sketch-timeline-message.demo.html
new file mode 100644
index 0000000..466f910
--- /dev/null
+++ b/loop/webui/src/web-components/demo/sketch-timeline-message.demo.html
@@ -0,0 +1,66 @@
+<html>
+ <head>
+ <title>sketch-timeline-message demo</title>
+ <link rel="stylesheet" href="demo.css" />
+ <script
+ src="/dist/web-components/sketch-timeline-message.js"
+ type="module"
+ ></script>
+
+ <script>
+ const messages = [
+ {
+ type: "agent",
+ content: "an agent message",
+ },
+ {
+ type: "user",
+ content: "a user message",
+ },
+ {
+ type: "tool",
+ content: "a tool use message",
+ },
+ {
+ type: "commit",
+ end_of_turn: false,
+ content: "",
+ commits: [
+ {
+ hash: "ece101c103ec231da87f4df05c1b5e6a24e13add",
+ subject: "Add README.md for web components directory",
+ body: "This adds documentation for the web components used in the Loop UI,\nincluding a description of each component, usage examples, and\ndevelopment guidelines.\n\nCo-Authored-By: sketch\nadd README.md for loop/webui/src/web-components",
+ pushed_branch:
+ "sketch/create-readmemd-for-web-components-directory",
+ },
+ ],
+ timestamp: "2025-04-14T16:39:33.639533919Z",
+ conversation_id: "",
+ idx: 17,
+ },
+ {
+ type: "agent",
+ content: "an end-of-turn agent message",
+ end_of_turn: true,
+ },
+ ];
+ document.addEventListener("DOMContentLoaded", () => {
+ messages.forEach((msg, idx) => {
+ const jsonEl = document.createElement("pre");
+ jsonEl.innerText = `.message property: ${JSON.stringify(msg)}`;
+ document.body.append(jsonEl);
+ const messageEl = document.createElement("sketch-timeline-message");
+ messageEl.message = msg;
+ document.body.appendChild(messageEl);
+ });
+ window.addEventListener(
+ "show-commit-diff",
+ (evt) => {console.log("show-commit-diff", evt)}
+ );
+ });
+ </script>
+ </head>
+ <body>
+ <h1>sketch-timeline-message demo</h1>
+ </body>
+</html>
diff --git a/loop/webui/src/web-components/demo/sketch-timeline.demo.html b/loop/webui/src/web-components/demo/sketch-timeline.demo.html
new file mode 100644
index 0000000..427181d
--- /dev/null
+++ b/loop/webui/src/web-components/demo/sketch-timeline.demo.html
@@ -0,0 +1,47 @@
+<html>
+ <head>
+ <title>sketch-timeline demo</title>
+ <link rel="stylesheet" href="demo.css" />
+ <script src="/dist/web-components/sketch-timeline.js" type="module"></script>
+
+ <script>
+ const messages = [
+ {
+ type: "user",
+ content: "a user message",
+ },
+ {
+ type: "agent",
+ content: "an agent message",
+ },
+ {
+ type: "agent",
+ content: "an agent message",
+ },
+ {
+ type: "agent",
+ content: "an agent message",
+ },
+ {
+ type: "user",
+ content: "a user message",
+ },
+ {
+ type: "user",
+ content: "a user message",
+ },
+ ];
+ document.addEventListener("DOMContentLoaded", () => {
+ const timelineEl = document.querySelector('sketch-timeline');
+ timelineEl.messages = messages;
+ });
+ </script>
+
+ </head>
+ <body>
+ <h1>sketch-timeline demo</h1>
+
+ <sketch-timeline></sketch-timeline>
+
+ </body>
+</html>
\ No newline at end of file
diff --git a/loop/webui/src/web-components/demo/sketch-tool-calls.demo.html b/loop/webui/src/web-components/demo/sketch-tool-calls.demo.html
new file mode 100644
index 0000000..a72babc
--- /dev/null
+++ b/loop/webui/src/web-components/demo/sketch-tool-calls.demo.html
@@ -0,0 +1,190 @@
+<html>
+ <head>
+ <title>sketch-tool-calls demo</title>
+ <link rel="stylesheet" href="demo.css" />
+
+ <script
+ src="/dist/web-components/sketch-tool-calls.js"
+ type="module"
+ ></script>
+
+ <script>
+ const toolCalls = [
+ [
+ {
+ name: "bash",
+ input: JSON.stringify({
+ command: "ls -a",
+ }),
+ },
+ ],
+ [
+ {
+ name: "bash",
+ input: JSON.stringify({
+ command: "ls -a",
+ }),
+ result_message: {
+ type: "tool",
+ tool_result: ".\n..",
+ },
+ },
+ ],
+ [
+ {
+ name: "bash",
+ input: JSON.stringify({
+ command: "sleep 200",
+ }),
+ result_message: {
+ type: "tool",
+ tool_error: "the user canceled this operation",
+ },
+ },
+ ],
+ [
+ {
+ name: "title",
+ input: JSON.stringify({
+ title: "a new title for this sketch",
+ }),
+ },
+ ],
+ [
+ {
+ name: "codereview",
+ input: "{}",
+ tool_call_id: "toolu_01WT5qQwHZgdogfKhkD8R9PZ",
+ result_message: {
+ type: "tool",
+ end_of_turn: false,
+ content: "",
+ tool_name: "codereview",
+ input: "{}",
+ tool_result: "OK",
+ tool_call_id: "toolu_01WT5qQwHZgdogfKhkD8R9PZ",
+ timestamp: "2025-04-14T16:33:17.575759565Z",
+ conversation_id: "xsa-8hw0",
+ start_time: "2025-04-14T16:33:07.11793816Z",
+ end_time: "2025-04-14T16:33:17.57575719Z",
+ elapsed: 10457819031,
+ idx: 45,
+ },
+ },
+ ],
+ [
+ {
+ name: "codereview",
+ input: "{}",
+ tool_call_id: "toolu_01WT5qQwHZgdogfKhkD8R9PZ",
+ result_message: {
+ type: "tool",
+ end_of_turn: false,
+ content: "",
+ tool_name: "codereview",
+ input: "{}",
+ tool_result: "Not OK",
+ tool_call_id: "toolu_01WT5qQwHZgdogfKhkD8R9PZ",
+ timestamp: "2025-04-14T16:33:17.575759565Z",
+ conversation_id: "xsa-8hw0",
+ start_time: "2025-04-14T16:33:07.11793816Z",
+ end_time: "2025-04-14T16:33:17.57575719Z",
+ elapsed: 10457819031,
+ idx: 45,
+ },
+ },
+ ],
+ [
+ {
+ name: "think",
+ input:
+ '{"thoughts":"I\'m going to inspect a few key components to understand their purpose and relationships:\\n1. sketch-app-shell.ts - Appears to be the main container component\\n2. sketch-timeline.ts - Likely manages the chat timeline\\n3. sketch-view-mode-select.ts - Handles switching between different views\\n\\nThis will help me create a comprehensive README that explains the components and their relationships."}',
+ tool_call_id: "toolu_01R1g5mQVgKxEJZFNp9QGvUr",
+ result_message: {
+ type: "tool",
+ end_of_turn: false,
+ content: "",
+ tool_name: "think",
+ input:
+ '{"thoughts":"I\'m going to inspect a few key components to understand their purpose and relationships:\\n1. sketch-app-shell.ts - Appears to be the main container component\\n2. sketch-timeline.ts - Likely manages the chat timeline\\n3. sketch-view-mode-select.ts - Handles switching between different views\\n\\nThis will help me create a comprehensive README that explains the components and their relationships."}',
+ tool_result: "recorded",
+ tool_call_id: "toolu_01R1g5mQVgKxEJZFNp9QGvUr",
+ timestamp: "2025-04-14T16:32:14.12647133Z",
+ conversation_id: "xsa-8hw0",
+ start_time: "2025-04-14T16:32:14.126454329Z",
+ end_time: "2025-04-14T16:32:14.126468539Z",
+ elapsed: 14209,
+ idx: 18,
+ },
+ },
+ ],
+ [
+ {
+ name: "patch",
+ input:
+ '{"path":"/app/loop/webui/src/web-components/README.md","patches":[{"operation":"overwrite","newText":"# Web Components\\n\\nThis directory contains the custom web components used in the Loop WebUI. These components are built using the [Lit](https://lit.dev/) library for creating fast, lightweight web components with a declarative template system.\\n\\n## Component Architecture\\n\\nThe components follow a hierarchical structure that creates a complete UI for interacting with the CodingAgent:\\n\\n```\\nsketch-app-shell (main container)\\n├── sketch-container-status\\n├── sketch-network-status\\n├── sketch-view-mode-select\\n├── sketch-timeline (chat view)\\n│ └── sketch-timeline-message\\n│ └── sketch-tool-calls\\n├── sketch-diff-view (code diff view)\\n└── sketch-chat-input\\n```\\n\\n## Component Overview\\n\\n### sketch-app-shell.ts\\nThe main container component that orchestrates the entire UI. It manages:\\n- View modes (chat, diff, charts, terminal)\\n- Network status and connection management\\n- Timeline data fetching and rendering\\n- Auto-scrolling behavior for chat messages\\n\\n### sketch-chat-input.ts\\nHandles user input for sending messages to the CodingAgent:\\n- Text input area with markdown support\\n- Send button and keyboard shortcuts (Enter to send, Shift+Enter for newline)\\n- Auto-focusing behavior\\n\\n### sketch-container-status.ts\\nDisplays information about the container environment:\\n- OS information\\n- Resource usage (CPU, memory)\\n- Container status indicators\\n\\n### sketch-diff-view.ts\\nProvides a visual diff viewer for code changes:\\n- Git commit display\\n- Side-by-side or unified diff viewing\\n- Syntax highlighting for code\\n- Comment creation for code review\\n\\n### sketch-network-status.ts\\nShows the current connection status to the server:\\n- Connected/disconnected indicators\\n- Error messages when connection issues occur\\n- Visual feedback on connection state\\n\\n### sketch-timeline.ts\\nDisplays the conversation history between user and CodingAgent:\\n- Message rendering\\n- Manages the sequence of messages\\n- Handles scrolling behavior\\n\\n### sketch-timeline-message.ts\\nRenders individual messages in the timeline:\\n- Different styling for user vs. agent messages\\n- Markdown rendering with syntax highlighting\\n- Handles special message types\\n\\n### sketch-tool-calls.ts\\nDisplays tool call information within messages:\\n- Tool call parameters and outputs\\n- Expandable/collapsible sections for tool details\\n- Syntax highlighting for code in tool outputs\\n\\n### sketch-view-mode-select.ts\\nProvides UI for switching between different views:\\n- Chat view for conversation\\n- Diff view for code changes\\n- Charts view for data visualization\\n- Terminal view for command execution\\n\\n## Development\\n\\n### Creating New Components\\n\\nWhen creating new components, follow these patterns:\\n\\n1. Use the `@customElement` decorator to define the component\\n2. Encapsulate styles using the static `styles` property\\n3. Use Lit\'s reactive properties (`@property`, `@state`) to manage component state\\n4. Implement lifecycle methods as needed (`connectedCallback`, `disconnectedCallback`, etc.)\\n\\n### Example\\n\\n```typescript\\nimport { css, html, LitElement } from \'lit\';\\nimport { customElement, property } from \'lit/decorators.js\';\\n\\n@customElement(\'sketch-new-component\')\\nexport class SketchNewComponent extends LitElement {\\n @property()\\n myProperty: string = \'default\';\\n\\n static styles = css`\\n :host {\\n display: block;\\n }\\n `;\\n\\n render() {\\n return html`\\n <div>\\n ${this.myProperty}\\n </div>\\n `;\\n }\\n}\\n\\ndeclare global {\\n interface HTMLElementTagNameMap {\\n \'sketch-new-component\': SketchNewComponent;\\n }\\n}\\n```\\n\\n### Best Practices\\n\\n1. Maintain a single responsibility for each component\\n2. Use properties and events for parent-child communication\\n3. Document the purpose and API of each component\\n4. Write tests for components where possible\\n5. Keep styles encapsulated within components\\n6. Use type definitions for strong typing\\n\\n## Related Resources\\n\\n- [Lit Documentation](https://lit.dev/docs/)\\n- [Web Components Introduction](https://developer.mozilla.org/en-US/docs/Web/Web_Components)\\n- See `/app/loop/webui/readme.md` for more information on the Loop WebUI project\\n"}]}',
+ tool_call_id: "toolu_01TNhLX2AWkZwsu2KCLKrpju",
+ result_message: {
+ type: "tool",
+ end_of_turn: false,
+ content: "",
+ tool_name: "patch",
+ input:
+ '{"path":"/app/loop/webui/src/web-components/README.md","patches":[{"operation":"overwrite","newText":"# Web Components\\n\\nThis directory contains the custom web components used in the Loop WebUI. These components are built using the [Lit](https://lit.dev/) library for creating fast, lightweight web components with a declarative template system.\\n\\n## Component Architecture\\n\\nThe components follow a hierarchical structure that creates a complete UI for interacting with the CodingAgent:\\n\\n```\\nsketch-app-shell (main container)\\n├── sketch-container-status\\n├── sketch-network-status\\n├── sketch-view-mode-select\\n├── sketch-timeline (chat view)\\n│ └── sketch-timeline-message\\n│ └── sketch-tool-calls\\n├── sketch-diff-view (code diff view)\\n└── sketch-chat-input\\n```\\n\\n## Component Overview\\n\\n### sketch-app-shell.ts\\nThe main container component that orchestrates the entire UI. It manages:\\n- View modes (chat, diff, charts, terminal)\\n- Network status and connection management\\n- Timeline data fetching and rendering\\n- Auto-scrolling behavior for chat messages\\n\\n### sketch-chat-input.ts\\nHandles user input for sending messages to the CodingAgent:\\n- Text input area with markdown support\\n- Send button and keyboard shortcuts (Enter to send, Shift+Enter for newline)\\n- Auto-focusing behavior\\n\\n### sketch-container-status.ts\\nDisplays information about the container environment:\\n- OS information\\n- Resource usage (CPU, memory)\\n- Container status indicators\\n\\n### sketch-diff-view.ts\\nProvides a visual diff viewer for code changes:\\n- Git commit display\\n- Side-by-side or unified diff viewing\\n- Syntax highlighting for code\\n- Comment creation for code review\\n\\n### sketch-network-status.ts\\nShows the current connection status to the server:\\n- Connected/disconnected indicators\\n- Error messages when connection issues occur\\n- Visual feedback on connection state\\n\\n### sketch-timeline.ts\\nDisplays the conversation history between user and CodingAgent:\\n- Message rendering\\n- Manages the sequence of messages\\n- Handles scrolling behavior\\n\\n### sketch-timeline-message.ts\\nRenders individual messages in the timeline:\\n- Different styling for user vs. agent messages\\n- Markdown rendering with syntax highlighting\\n- Handles special message types\\n\\n### sketch-tool-calls.ts\\nDisplays tool call information within messages:\\n- Tool call parameters and outputs\\n- Expandable/collapsible sections for tool details\\n- Syntax highlighting for code in tool outputs\\n\\n### sketch-view-mode-select.ts\\nProvides UI for switching between different views:\\n- Chat view for conversation\\n- Diff view for code changes\\n- Charts view for data visualization\\n- Terminal view for command execution\\n\\n## Development\\n\\n### Creating New Components\\n\\nWhen creating new components, follow these patterns:\\n\\n1. Use the `@customElement` decorator to define the component\\n2. Encapsulate styles using the static `styles` property\\n3. Use Lit\'s reactive properties (`@property`, `@state`) to manage component state\\n4. Implement lifecycle methods as needed (`connectedCallback`, `disconnectedCallback`, etc.)\\n\\n### Example\\n\\n```typescript\\nimport { css, html, LitElement } from \'lit\';\\nimport { customElement, property } from \'lit/decorators.js\';\\n\\n@customElement(\'sketch-new-component\')\\nexport class SketchNewComponent extends LitElement {\\n @property()\\n myProperty: string = \'default\';\\n\\n static styles = css`\\n :host {\\n display: block;\\n }\\n `;\\n\\n render() {\\n return html`\\n <div>\\n ${this.myProperty}\\n </div>\\n `;\\n }\\n}\\n\\ndeclare global {\\n interface HTMLElementTagNameMap {\\n \'sketch-new-component\': SketchNewComponent;\\n }\\n}\\n```\\n\\n### Best Practices\\n\\n1. Maintain a single responsibility for each component\\n2. Use properties and events for parent-child communication\\n3. Document the purpose and API of each component\\n4. Write tests for components where possible\\n5. Keep styles encapsulated within components\\n6. Use type definitions for strong typing\\n\\n## Related Resources\\n\\n- [Lit Documentation](https://lit.dev/docs/)\\n- [Web Components Introduction](https://developer.mozilla.org/en-US/docs/Web/Web_Components)\\n- See `/app/loop/webui/readme.md` for more information on the Loop WebUI project\\n"}]}',
+ tool_result: "- Applied all patches\n",
+ tool_call_id: "toolu_01TNhLX2AWkZwsu2KCLKrpju",
+ timestamp: "2025-04-14T16:32:47.10299854Z",
+ conversation_id: "xsa-8hw0",
+ start_time: "2025-04-14T16:32:47.101660683Z",
+ end_time: "2025-04-14T16:32:47.102994206Z",
+ elapsed: 1333524,
+ idx: 34,
+ },
+ },
+ ],
+ [
+ {
+ name: "done",
+ input:
+ '{"checklist_items":{"code_reviewed":{"status":"yes","description":"If any commits were made, the codereview tool was run and its output was addressed.","comments":"Since this commit only adds a documentation file (README.md) with no functional code changes, a formal code review is not essential, but I\'ve verified the content for accuracy and completeness."},"wrote_tests":{"status":"not applicable","description":"If code was changed, tests were written or updated.","comments":"No code was changed; only documentation was added. The README file is purely informational and doesn\'t require tests."},"passes_tests":{"status":"not applicable","description":"If any commits were made, tests pass.","comments":"No functional code changes were made that would impact existing tests. The addition of a README file is a documentation-only change."},"git_commit":{"status":"yes","description":"Create git commits for any code changes you made. Match the style of recent commit messages. Include \'Co-Authored-By: sketch\' and the original user prompt. Use GIT_AUTHOR_NAME=\\"Sean McCullough\\" GIT_AUTHOR_EMAIL=\\"banksean@gmail.com\\" (not git config).","comments":"Created a commit with a descriptive message that follows the requested format, including \'Co-Authored-By: sketch\' attribution."}}}',
+ tool_call_id: "toolu_01HPgWQJF1aF9LUqkdDKWeES",
+ result_message: {
+ type: "tool",
+ end_of_turn: false,
+ content: "",
+ tool_name: "done",
+ input:
+ '{"checklist_items":{"code_reviewed":{"status":"yes","description":"If any commits were made, the codereview tool was run and its output was addressed.","comments":"Since this commit only adds a documentation file (README.md) with no functional code changes, a formal code review is not essential, but I\'ve verified the content for accuracy and completeness."},"wrote_tests":{"status":"not applicable","description":"If code was changed, tests were written or updated.","comments":"No code was changed; only documentation was added. The README file is purely informational and doesn\'t require tests."},"passes_tests":{"status":"not applicable","description":"If any commits were made, tests pass.","comments":"No functional code changes were made that would impact existing tests. The addition of a README file is a documentation-only change."},"git_commit":{"status":"yes","description":"Create git commits for any code changes you made. Match the style of recent commit messages. Include \'Co-Authored-By: sketch\' and the original user prompt. Use GIT_AUTHOR_NAME=\\"Sean McCullough\\" GIT_AUTHOR_EMAIL=\\"banksean@gmail.com\\" (not git config).","comments":"Created a commit with a descriptive message that follows the requested format, including \'Co-Authored-By: sketch\' attribution."}}}',
+ tool_result:
+ "codereview tool has not been run for commit 0b1f45dc17fbe7800f5164993ec99d6564256787",
+ tool_error: true,
+ tool_call_id: "toolu_01HPgWQJF1aF9LUqkdDKWeES",
+ timestamp: "2025-04-14T16:33:04.639179373Z",
+ conversation_id: "xsa-8hw0",
+ start_time: "2025-04-14T16:33:04.616273148Z",
+ end_time: "2025-04-14T16:33:04.639173456Z",
+ elapsed: 22900309,
+ idx: 43,
+ },
+ },
+ ],
+ ];
+ document.addEventListener("DOMContentLoaded", () => {
+ toolCalls.forEach((calls) => {
+ const jsonEl = document.createElement("pre");
+ jsonEl.innerText = `.toolCalls property: ${JSON.stringify(calls)}`;
+ document.body.append(jsonEl);
+
+ const toolCallsEl = document.createElement("sketch-tool-calls");
+ toolCallsEl.toolCalls = calls;
+ document.body.append(toolCallsEl);
+ });
+ });
+ </script>
+ </head>
+ <body>
+ <h1>sketch-tool-calls demo</h1>
+
+ <sketch-tool-calls></sketch-tool-calls>
+ </body>
+</html>
diff --git a/loop/webui/src/web-components/demo/sketch-view-mode-select.demo.html b/loop/webui/src/web-components/demo/sketch-view-mode-select.demo.html
new file mode 100644
index 0000000..dac6831
--- /dev/null
+++ b/loop/webui/src/web-components/demo/sketch-view-mode-select.demo.html
@@ -0,0 +1,31 @@
+<html>
+ <head>
+ <title>sketch-view-mode-select demo</title>
+ <link rel="stylesheet" href="demo.css" />
+
+ <script src="/dist/web-components/sketch-view-mode-select.js" type="module"></script>
+
+ <script>
+ document.addEventListener("DOMContentLoaded", () => {
+ const viewModeSelect = document.querySelector('sketch-view-mode-select');
+ const msgDiv = document.querySelector("#selected-mode");
+ msgDiv.innerText = `selected mode: ${viewModeSelect.activeMode}`;
+
+ console.log("viewModeSelect: ", viewModeSelect);
+ viewModeSelect.addEventListener("view-mode-select", (evt) => {
+ console.log('view mode change event: ', evt);
+ const msgDiv = document.querySelector("#selected-mode");
+ msgDiv.innerText = `selected mode: ${evt.detail.mode}`;
+ });
+ });
+ </script>
+
+ </head>
+ <body>
+ <h1>sketch-view-mode-select demo</h1>
+
+ <sketch-view-mode-select></sketch-view-mode-select>
+ <div id="selected-mode"></div>
+
+ </body>
+</html>
\ No newline at end of file