blob: 0d2b4f69b1505a5fea345a4310fd3d6f9f36c97b [file] [log] [blame]
Philip Zeyliger16fa8b42025-05-02 04:28:16 +00001import { State, AgentMessage } from "../types";
Sean McCulloughb29f8912025-04-20 15:39:11 -07002import { LitElement, css, html } from "lit";
Philip Zeyligere66db3e2025-04-27 15:40:39 +00003import { customElement, property, state } from "lit/decorators.js";
Josh Bleecher Snydera0801ad2025-04-25 19:34:53 +00004import { formatNumber } from "../utils";
Sean McCullough86b56862025-04-18 13:04:03 -07005
6@customElement("sketch-container-status")
7export class SketchContainerStatus extends LitElement {
8 // Header bar: Container status details
9
10 @property()
11 state: State;
12
Philip Zeyligere66db3e2025-04-27 15:40:39 +000013 @state()
14 showDetails: boolean = false;
15
Philip Zeyliger16fa8b42025-05-02 04:28:16 +000016 @state()
17 lastCommit: { hash: string; pushedBranch?: string } | null = null;
18
19 @state()
20 lastCommitCopied: boolean = false;
21
Sean McCullough86b56862025-04-18 13:04:03 -070022 // See https://lit.dev/docs/components/styles/ for how lit-element handles CSS.
23 // Note that these styles only apply to the scope of this web component's
24 // shadow DOM node, so they won't leak out or collide with CSS declared in
25 // other components or the containing web page (...unless you want it to do that).
26 static styles = css`
Philip Zeyliger16fa8b42025-05-02 04:28:16 +000027 /* Last commit display styling */
28 .last-commit {
29 display: flex;
30 flex-direction: column;
31 padding: 3px 8px;
32 cursor: pointer;
33 position: relative;
34 margin: 4px 0;
35 transition: color 0.2s ease;
36 }
37
38 .last-commit:hover {
39 color: #0366d6;
40 }
41
42 .last-commit-title {
43 color: #666;
44 font-family: system-ui, sans-serif;
45 font-size: 11px;
46 font-weight: 500;
47 line-height: 1.2;
48 }
49
50 .last-commit-hash {
51 font-family: monospace;
52 font-size: 12px;
53 white-space: nowrap;
54 overflow: hidden;
55 text-overflow: ellipsis;
56 }
57
58 /* Styles for the last commit in main grid */
59 .last-commit-column {
60 justify-content: flex-start;
61 }
62
63 .info-label {
64 color: #666;
65 font-family: system-ui, sans-serif;
66 font-size: 11px;
67 font-weight: 500;
68 }
69
70 .last-commit-main {
71 cursor: pointer;
72 position: relative;
73 padding-top: 0;
74 }
75
76 .last-commit-main:hover {
77 color: #0366d6;
78 }
79
80 .main-grid-commit {
81 font-family: monospace;
82 font-size: 12px;
83 white-space: nowrap;
84 overflow: hidden;
85 text-overflow: ellipsis;
86 }
87
88 .commit-hash-indicator {
89 color: #666;
90 }
91
92 .commit-branch-indicator {
93 color: #28a745;
94 }
95
96 .no-commit-indicator {
97 color: #999;
98 font-style: italic;
99 font-size: 12px;
100 }
101
Philip Zeyligere66db3e2025-04-27 15:40:39 +0000102 .info-container {
103 display: flex;
104 align-items: center;
105 position: relative;
Sean McCullough86b56862025-04-18 13:04:03 -0700106 }
107
108 .info-grid {
109 display: flex;
110 flex-wrap: wrap;
111 gap: 8px;
112 background: #f9f9f9;
113 border-radius: 4px;
114 padding: 4px 10px;
115 box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
116 flex: 1;
117 }
118
Philip Zeyligere66db3e2025-04-27 15:40:39 +0000119 .info-expanded {
120 position: absolute;
121 top: 100%;
122 right: 0;
123 z-index: 10;
Philip Zeyligerc72fff52025-04-29 20:17:54 +0000124 min-width: 400px;
Philip Zeyligere66db3e2025-04-27 15:40:39 +0000125 background: white;
126 border-radius: 8px;
127 padding: 10px 15px;
128 box-shadow: 0 6px 16px rgba(0, 0, 0, 0.1);
129 margin-top: 5px;
130 display: none;
131 }
132
133 .info-expanded.active {
134 display: block;
135 }
136
Sean McCullough86b56862025-04-18 13:04:03 -0700137 .info-item {
138 display: flex;
139 align-items: center;
140 white-space: nowrap;
141 margin-right: 10px;
142 font-size: 13px;
143 }
144
145 .info-label {
146 font-size: 11px;
147 color: #555;
148 margin-right: 3px;
149 font-weight: 500;
150 }
151
152 .info-value {
153 font-size: 11px;
154 font-weight: 600;
Philip Zeyliger16fa8b42025-05-02 04:28:16 +0000155 word-break: break-all;
Sean McCullough86b56862025-04-18 13:04:03 -0700156 }
157
Philip Zeyligerd1402952025-04-23 03:54:37 +0000158 [title] {
Philip Zeyligerbce3a132025-04-30 22:03:39 +0000159 cursor: default;
Philip Zeyligerd1402952025-04-23 03:54:37 +0000160 }
161
Sean McCullough86b56862025-04-18 13:04:03 -0700162 .cost {
163 color: #2e7d32;
164 }
165
166 .info-item a {
167 --tw-text-opacity: 1;
168 color: rgb(37 99 235 / var(--tw-text-opacity, 1));
169 text-decoration: inherit;
170 }
Philip Zeyligere66db3e2025-04-27 15:40:39 +0000171
172 .info-toggle {
173 margin-left: 8px;
174 width: 24px;
175 height: 24px;
176 border-radius: 50%;
177 display: flex;
178 align-items: center;
179 justify-content: center;
180 background: #f0f0f0;
181 border: 1px solid #ddd;
182 cursor: pointer;
183 font-weight: bold;
184 font-style: italic;
185 color: #555;
186 transition: all 0.2s ease;
187 }
188
189 .info-toggle:hover {
190 background: #e0e0e0;
191 }
192
193 .info-toggle.active {
194 background: #4a90e2;
195 color: white;
196 border-color: #3a80d2;
197 }
198
199 .main-info-grid {
Philip Zeyliger16fa8b42025-05-02 04:28:16 +0000200 display: grid;
201 grid-template-columns: 1fr 1fr 1fr;
202 gap: 10px;
203 width: 100%;
Philip Zeyligere66db3e2025-04-27 15:40:39 +0000204 }
205
206 .info-column {
207 display: flex;
208 flex-direction: column;
209 gap: 2px;
210 }
211
212 .detailed-info-grid {
213 display: grid;
214 grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
215 gap: 8px;
216 margin-top: 10px;
217 }
Philip Zeyligerc72fff52025-04-29 20:17:54 +0000218
219 .ssh-section {
220 margin-top: 10px;
221 padding-top: 10px;
222 border-top: 1px solid #eee;
223 }
224
225 .ssh-command {
226 display: flex;
227 align-items: center;
228 margin-bottom: 8px;
229 gap: 10px;
230 }
231
232 .ssh-command-text {
233 font-family: monospace;
234 font-size: 12px;
235 background: #f5f5f5;
236 padding: 4px 8px;
237 border-radius: 4px;
238 border: 1px solid #e0e0e0;
239 flex-grow: 1;
240 }
241
242 .copy-button {
243 background: #f0f0f0;
244 border: 1px solid #ddd;
245 border-radius: 4px;
246 padding: 3px 6px;
247 font-size: 11px;
248 cursor: pointer;
249 transition: all 0.2s;
250 }
251
252 .copy-button:hover {
253 background: #e0e0e0;
254 }
255
256 .ssh-warning {
257 background: #fff3e0;
258 border-left: 3px solid #ff9800;
259 padding: 8px 12px;
260 margin-top: 8px;
261 font-size: 12px;
262 color: #e65100;
263 }
264
265 .vscode-link {
Philip Zeyligerbce3a132025-04-30 22:03:39 +0000266 color: white;
267 text-decoration: none;
268 background-color: #0066b8;
269 padding: 4px 8px;
270 border-radius: 4px;
271 display: flex;
272 align-items: center;
273 gap: 6px;
274 font-size: 12px;
275 transition: all 0.2s ease;
276 }
277
278 .vscode-link:hover {
279 background-color: #005091;
280 }
281
282 .vscode-icon {
283 width: 16px;
284 height: 16px;
285 }
286
287 .github-link {
Philip Zeyligerc72fff52025-04-29 20:17:54 +0000288 color: #2962ff;
289 text-decoration: none;
290 }
291
Philip Zeyligerbce3a132025-04-30 22:03:39 +0000292 .github-link:hover {
Philip Zeyligerc72fff52025-04-29 20:17:54 +0000293 text-decoration: underline;
294 }
Sean McCullough86b56862025-04-18 13:04:03 -0700295 `;
296
297 constructor() {
298 super();
Philip Zeyligere66db3e2025-04-27 15:40:39 +0000299 this._toggleInfoDetails = this._toggleInfoDetails.bind(this);
300
301 // Close the info panel when clicking outside of it
302 document.addEventListener("click", (event) => {
303 if (this.showDetails && !this.contains(event.target as Node)) {
304 this.showDetails = false;
305 this.requestUpdate();
306 }
307 });
308 }
309
310 /**
311 * Toggle the display of detailed information
312 */
313 private _toggleInfoDetails(event: Event) {
314 event.stopPropagation();
315 this.showDetails = !this.showDetails;
316 this.requestUpdate();
Sean McCullough86b56862025-04-18 13:04:03 -0700317 }
318
Philip Zeyliger16fa8b42025-05-02 04:28:16 +0000319 /**
320 * Update the last commit information based on messages
321 */
322 public updateLastCommitInfo(newMessages: AgentMessage[]): void {
323 if (!newMessages || newMessages.length === 0) return;
324
325 // Process messages in chronological order (latest last)
326 for (const message of newMessages) {
327 if (
328 message.type === "commit" &&
329 message.commits &&
330 message.commits.length > 0
331 ) {
332 // Get the first commit from the list
333 const commit = message.commits[0];
334 if (commit) {
335 this.lastCommit = {
336 hash: commit.hash,
337 pushedBranch: commit.pushed_branch,
338 };
339 this.lastCommitCopied = false;
340 }
341 }
342 }
343 }
344
345 /**
346 * Copy commit info to clipboard when clicked
347 */
348 private copyCommitInfo(event: MouseEvent): void {
349 event.preventDefault();
350 event.stopPropagation();
351
352 if (!this.lastCommit) return;
353
354 const textToCopy =
355 this.lastCommit.pushedBranch || this.lastCommit.hash.substring(0, 8);
356
357 navigator.clipboard
358 .writeText(textToCopy)
359 .then(() => {
360 this.lastCommitCopied = true;
361 // Reset the copied state after 2 seconds
362 setTimeout(() => {
363 this.lastCommitCopied = false;
364 }, 2000);
365 })
366 .catch((err) => {
367 console.error("Failed to copy commit info:", err);
368 });
369 }
370
Philip Zeyligerd1402952025-04-23 03:54:37 +0000371 formatHostname() {
Philip Zeyligerbce3a132025-04-30 22:03:39 +0000372 // Only display outside hostname
Philip Zeyliger18532b22025-04-23 21:11:46 +0000373 const outsideHostname = this.state?.outside_hostname;
Philip Zeyligerd1402952025-04-23 03:54:37 +0000374
Philip Zeyligerbce3a132025-04-30 22:03:39 +0000375 if (!outsideHostname) {
Philip Zeyligerd1402952025-04-23 03:54:37 +0000376 return this.state?.hostname;
377 }
378
Philip Zeyligerbce3a132025-04-30 22:03:39 +0000379 return outsideHostname;
Philip Zeyligerd1402952025-04-23 03:54:37 +0000380 }
381
382 formatWorkingDir() {
Philip Zeyligerbce3a132025-04-30 22:03:39 +0000383 // Only display outside working directory
Philip Zeyliger18532b22025-04-23 21:11:46 +0000384 const outsideWorkingDir = this.state?.outside_working_dir;
Philip Zeyligerd1402952025-04-23 03:54:37 +0000385
Philip Zeyligerbce3a132025-04-30 22:03:39 +0000386 if (!outsideWorkingDir) {
Philip Zeyligerd1402952025-04-23 03:54:37 +0000387 return this.state?.working_dir;
388 }
389
Philip Zeyligerbce3a132025-04-30 22:03:39 +0000390 return outsideWorkingDir;
Philip Zeyligerd1402952025-04-23 03:54:37 +0000391 }
392
393 getHostnameTooltip() {
Philip Zeyliger18532b22025-04-23 21:11:46 +0000394 const outsideHostname = this.state?.outside_hostname;
395 const insideHostname = this.state?.inside_hostname;
Philip Zeyligerd1402952025-04-23 03:54:37 +0000396
397 if (
Philip Zeyliger18532b22025-04-23 21:11:46 +0000398 !outsideHostname ||
399 !insideHostname ||
400 outsideHostname === insideHostname
Philip Zeyligerd1402952025-04-23 03:54:37 +0000401 ) {
402 return "";
403 }
404
Philip Zeyliger18532b22025-04-23 21:11:46 +0000405 return `Outside: ${outsideHostname}, Inside: ${insideHostname}`;
406 }
407
408 getWorkingDirTooltip() {
409 const outsideWorkingDir = this.state?.outside_working_dir;
410 const insideWorkingDir = this.state?.inside_working_dir;
411
412 if (
413 !outsideWorkingDir ||
414 !insideWorkingDir ||
415 outsideWorkingDir === insideWorkingDir
416 ) {
417 return "";
418 }
419
420 return `Outside: ${outsideWorkingDir}, Inside: ${insideWorkingDir}`;
Philip Zeyligerd1402952025-04-23 03:54:37 +0000421 }
422
Sean McCullough86b56862025-04-18 13:04:03 -0700423 // See https://lit.dev/docs/components/lifecycle/
424 connectedCallback() {
425 super.connectedCallback();
426 // register event listeners
427 }
428
429 // See https://lit.dev/docs/components/lifecycle/
430 disconnectedCallback() {
431 super.disconnectedCallback();
432 // unregister event listeners
433 }
434
Philip Zeyligerc72fff52025-04-29 20:17:54 +0000435 copyToClipboard(text: string) {
436 navigator.clipboard
437 .writeText(text)
438 .then(() => {
439 // Could add a temporary success indicator here
440 })
441 .catch((err) => {
442 console.error("Could not copy text: ", err);
443 });
444 }
445
446 getSSHHostname() {
447 return `sketch-${this.state?.session_id}`;
448 }
449
Philip Zeyligerbce3a132025-04-30 22:03:39 +0000450 // Format GitHub repository URL to org/repo format
451 formatGitHubRepo(url) {
452 if (!url) return null;
453
454 // Common GitHub URL patterns
455 const patterns = [
456 // HTTPS URLs
457 /https:\/\/github\.com\/([^/]+)\/([^/\s.]+)(?:\.git)?/,
458 // SSH URLs
459 /git@github\.com:([^/]+)\/([^/\s.]+)(?:\.git)?/,
460 // Git protocol
461 /git:\/\/github\.com\/([^/]+)\/([^/\s.]+)(?:\.git)?/,
462 ];
463
464 for (const pattern of patterns) {
465 const match = url.match(pattern);
466 if (match) {
467 return {
468 formatted: `${match[1]}/${match[2]}`,
469 url: `https://github.com/${match[1]}/${match[2]}`,
470 };
471 }
472 }
473
474 return null;
475 }
476
Philip Zeyligerc72fff52025-04-29 20:17:54 +0000477 renderSSHSection() {
478 // Only show SSH section if we're in a Docker container and have session ID
479 if (!this.state?.session_id) {
480 return html``;
481 }
482
483 const sshHost = this.getSSHHostname();
484 const sshCommand = `ssh ${sshHost}`;
485 const vscodeCommand = `code --remote ssh-remote+root@${sshHost} /app -n`;
486 const vscodeURL = `vscode://vscode-remote/ssh-remote+root@${sshHost}/app?windowId=_blank`;
487
488 if (!this.state?.ssh_available) {
489 return html`
490 <div class="ssh-section">
Philip Zeyligerbce3a132025-04-30 22:03:39 +0000491 <h3>Connect to Container</h3>
Philip Zeyligerc72fff52025-04-29 20:17:54 +0000492 <div class="ssh-warning">
493 SSH connections are not available:
494 ${this.state?.ssh_error || "SSH configuration is missing"}
495 </div>
496 </div>
497 `;
498 }
499
500 return html`
501 <div class="ssh-section">
Philip Zeyligerbce3a132025-04-30 22:03:39 +0000502 <h3>Connect to Container</h3>
Philip Zeyligerc72fff52025-04-29 20:17:54 +0000503 <div class="ssh-command">
504 <div class="ssh-command-text">${sshCommand}</div>
505 <button
506 class="copy-button"
507 @click=${() => this.copyToClipboard(sshCommand)}
508 >
509 Copy
510 </button>
511 </div>
512 <div class="ssh-command">
513 <div class="ssh-command-text">${vscodeCommand}</div>
514 <button
515 class="copy-button"
516 @click=${() => this.copyToClipboard(vscodeCommand)}
517 >
518 Copy
519 </button>
520 </div>
521 <div class="ssh-command">
Philip Zeyligerbce3a132025-04-30 22:03:39 +0000522 <a href="${vscodeURL}" class="vscode-link" title="${vscodeURL}">
523 <svg
524 class="vscode-icon"
525 xmlns="http://www.w3.org/2000/svg"
526 viewBox="0 0 24 24"
527 fill="none"
528 stroke="white"
529 stroke-width="2"
530 stroke-linecap="round"
531 stroke-linejoin="round"
532 >
533 <path
534 d="M16.5 9.4 7.55 4.24a.35.35 0 0 0-.41.01l-1.23.93a.35.35 0 0 0-.14.29v13.04c0 .12.07.23.17.29l1.24.93c.13.1.31.09.43-.01L16.5 14.6l-6.39 4.82c-.16.12-.38.12-.55.01l-1.33-1.01a.35.35 0 0 1-.14-.28V5.88c0-.12.07-.23.18-.29l1.23-.93c.14-.1.32-.1.46 0l6.54 4.92-6.54 4.92c-.14.1-.32.1-.46 0l-1.23-.93a.35.35 0 0 1-.18-.29V5.88c0-.12.07-.23.17-.29l1.33-1.01c.16-.12.39-.11.55.01l6.39 4.81z"
535 />
536 </svg>
537 <span>Open in VSCode</span>
538 </a>
Philip Zeyligerc72fff52025-04-29 20:17:54 +0000539 </div>
540 </div>
541 `;
542 }
543
Sean McCullough86b56862025-04-18 13:04:03 -0700544 render() {
545 return html`
Philip Zeyligere66db3e2025-04-27 15:40:39 +0000546 <div class="info-container">
547 <!-- Main visible info in two columns - hostname/dir and repo/cost -->
548 <div class="main-info-grid">
549 <!-- First column: hostname and working dir -->
550 <div class="info-column">
551 <div class="info-item">
552 <span
553 id="hostname"
554 class="info-value"
555 title="${this.getHostnameTooltip()}"
556 >
557 ${this.formatHostname()}
558 </span>
559 </div>
560 <div class="info-item">
561 <span
562 id="workingDir"
563 class="info-value"
564 title="${this.getWorkingDirTooltip()}"
565 >
566 ${this.formatWorkingDir()}
567 </span>
568 </div>
569 </div>
570
Philip Zeyliger72318392025-05-14 02:56:07 +0000571 <!-- Second column: git repo, agent state, and cost -->
Philip Zeyligere66db3e2025-04-27 15:40:39 +0000572 <div class="info-column">
573 ${this.state?.git_origin
574 ? html`
575 <div class="info-item">
Philip Zeyligerbce3a132025-04-30 22:03:39 +0000576 ${(() => {
577 const github = this.formatGitHubRepo(
578 this.state?.git_origin,
579 );
580 if (github) {
581 return html`
582 <a
583 href="${github.url}"
584 target="_blank"
585 rel="noopener noreferrer"
586 class="github-link"
587 title="${this.state?.git_origin}"
588 >
589 ${github.formatted}
590 </a>
591 `;
592 } else {
593 return html`
594 <span id="gitOrigin" class="info-value"
595 >${this.state?.git_origin}</span
596 >
597 `;
598 }
599 })()}
Philip Zeyligere66db3e2025-04-27 15:40:39 +0000600 </div>
601 `
602 : ""}
Philip Zeyliger72318392025-05-14 02:56:07 +0000603
Philip Zeyligere66db3e2025-04-27 15:40:39 +0000604 <div class="info-item">
605 <span id="totalCost" class="info-value cost"
606 >$${(this.state?.total_usage?.total_cost_usd || 0).toFixed(
607 2,
608 )}</span
609 >
610 </div>
611 </div>
Philip Zeyliger16fa8b42025-05-02 04:28:16 +0000612
613 <!-- Third column: Last Commit -->
614 <div class="info-column last-commit-column">
615 <div class="info-item">
616 <span class="info-label">Last Commit</span>
617 </div>
618 <div
619 class="info-item last-commit-main"
620 @click=${(e: MouseEvent) => this.copyCommitInfo(e)}
621 title="Click to copy"
622 >
623 ${this.lastCommitCopied
624 ? html`<span class="copied-indicator">Copied!</span>`
625 : ""}
626 ${this.lastCommit
627 ? this.lastCommit.pushedBranch
628 ? html`<span class="commit-branch-indicator main-grid-commit"
629 >${this.lastCommit.pushedBranch}</span
630 >`
631 : html`<span class="commit-hash-indicator main-grid-commit"
632 >${this.lastCommit.hash.substring(0, 8)}</span
633 >`
634 : html`<span class="no-commit-indicator">N/A</span>`}
635 </div>
636 </div>
Sean McCullough86b56862025-04-18 13:04:03 -0700637 </div>
Philip Zeyligere66db3e2025-04-27 15:40:39 +0000638
639 <!-- Info toggle button -->
640 <button
641 class="info-toggle ${this.showDetails ? "active" : ""}"
642 @click=${this._toggleInfoDetails}
643 title="Show/hide details"
644 >
645 i
646 </button>
647
648 <!-- Expanded info panel -->
649 <div class="info-expanded ${this.showDetails ? "active" : ""}">
Philip Zeyliger16fa8b42025-05-02 04:28:16 +0000650 <!-- Last Commit section moved to main grid -->
651
Philip Zeyligere66db3e2025-04-27 15:40:39 +0000652 <div class="detailed-info-grid">
653 <div class="info-item">
654 <span class="info-label">Commit:</span>
655 <span id="initialCommit" class="info-value"
656 >${this.state?.initial_commit?.substring(0, 8)}</span
657 >
658 </div>
659 <div class="info-item">
660 <span class="info-label">Msgs:</span>
661 <span id="messageCount" class="info-value"
662 >${this.state?.message_count}</span
663 >
664 </div>
665 <div class="info-item">
Philip Zeyliger16fa8b42025-05-02 04:28:16 +0000666 <span class="info-label">Session ID:</span>
667 <span id="sessionId" class="info-value"
668 >${this.state?.session_id || "N/A"}</span
669 >
670 </div>
Philip Zeyliger72318392025-05-14 02:56:07 +0000671 ${this.state?.agent_state
672 ? html`
673 <div class="info-item">
674 <span class="info-label">Agent State:</span>
675 <span id="agentState" class="info-value"
676 >${this.state?.agent_state}</span
677 >
678 </div>
679 `
680 : ""}
Philip Zeyliger16fa8b42025-05-02 04:28:16 +0000681 <div class="info-item">
Philip Zeyligere66db3e2025-04-27 15:40:39 +0000682 <span class="info-label">Input tokens:</span>
683 <span id="inputTokens" class="info-value"
684 >${formatNumber(
685 (this.state?.total_usage?.input_tokens || 0) +
686 (this.state?.total_usage?.cache_read_input_tokens || 0) +
687 (this.state?.total_usage?.cache_creation_input_tokens || 0),
688 )}</span
689 >
690 </div>
691 <div class="info-item">
692 <span class="info-label">Output tokens:</span>
693 <span id="outputTokens" class="info-value"
694 >${formatNumber(this.state?.total_usage?.output_tokens)}</span
695 >
696 </div>
697 <div
698 class="info-item"
699 style="grid-column: 1 / -1; margin-top: 5px; border-top: 1px solid #eee; padding-top: 5px;"
700 >
701 <a href="logs">Logs</a> (<a href="download">Download</a>)
702 </div>
703 </div>
Philip Zeyligerc72fff52025-04-29 20:17:54 +0000704
705 <!-- SSH Connection Information -->
706 ${this.renderSSHSection()}
Sean McCullough86b56862025-04-18 13:04:03 -0700707 </div>
708 </div>
709 `;
710 }
711}
712
713declare global {
714 interface HTMLElementTagNameMap {
715 "sketch-container-status": SketchContainerStatus;
716 }
717}