Move webui from /loop/webui to /webui

Thanks, perl (and git mv):

	perl -pi -e s,loop/webui,webui,g $(git grep -l loop/webui)
diff --git a/loop/server/loophttp.go b/loop/server/loophttp.go
index 271375b..e1b76ad 100644
--- a/loop/server/loophttp.go
+++ b/loop/server/loophttp.go
@@ -25,7 +25,7 @@
 	"github.com/creack/pty"
 	"sketch.dev/ant"
 	"sketch.dev/loop"
-	"sketch.dev/loop/webui"
+	"sketch.dev/webui"
 )
 
 // terminalSession represents a terminal session with its PTY and the event channel
diff --git a/loop/webui/.gitignore b/loop/webui/.gitignore
deleted file mode 100644
index ee8e4bf..0000000
--- a/loop/webui/.gitignore
+++ /dev/null
@@ -1,7 +0,0 @@
-dist/
-coverage/
-node_modules/
-/test-results/
-/playwright-report/
-/blob-report/
-/playwright/.cache/
diff --git a/loop/webui/.prettierignore b/loop/webui/.prettierignore
deleted file mode 100644
index 77f3f9f..0000000
--- a/loop/webui/.prettierignore
+++ /dev/null
@@ -1,2 +0,0 @@
-*.min.*
-src/types.ts
\ No newline at end of file
diff --git a/loop/webui/Makefile b/loop/webui/Makefile
deleted file mode 100644
index 2ce27f5..0000000
--- a/loop/webui/Makefile
+++ /dev/null
@@ -1,15 +0,0 @@
-all: install check
-
-install:
-	npm ci
-
-demo:
-	npm run demo
-
-# TypeScript type checking
-# Note: The actual esbuild bundling happens in esbuild.go
-check:
-	npx tsc --noEmit
-
-clean:
-	rm -rf node_modules
diff --git a/loop/webui/esbuild.go b/loop/webui/esbuild.go
deleted file mode 100644
index b86b7d7..0000000
--- a/loop/webui/esbuild.go
+++ /dev/null
@@ -1,379 +0,0 @@
-// Package webui provides the web interface for the sketch loop.
-// It bundles typescript files into JavaScript using esbuild.
-package webui
-
-import (
-	"archive/zip"
-	"bytes"
-	"compress/gzip"
-	"crypto/sha256"
-	"embed"
-	"encoding/hex"
-	"fmt"
-	"io"
-	"io/fs"
-	"os"
-	"os/exec"
-	"path/filepath"
-	"strings"
-
-	esbuildcli "github.com/evanw/esbuild/pkg/cli"
-)
-
-//go:embed package.json package-lock.json src tsconfig.json
-var embedded embed.FS
-
-func embeddedHash() (string, error) {
-	h := sha256.New()
-	err := fs.WalkDir(embedded, ".", func(path string, d fs.DirEntry, err error) error {
-		if d.IsDir() {
-			return nil
-		}
-		f, err := embedded.Open(path)
-		if err != nil {
-			return err
-		}
-		defer f.Close()
-		if _, err := io.Copy(h, f); err != nil {
-			return fmt.Errorf("%s: %w", path, err)
-		}
-		return nil
-	})
-	if err != nil {
-		return "", fmt.Errorf("embedded hash: %w", err)
-	}
-	return hex.EncodeToString(h.Sum(nil))[:32], nil
-}
-
-func cleanBuildDir(buildDir string) error {
-	err := fs.WalkDir(os.DirFS(buildDir), ".", func(path string, d fs.DirEntry, err error) error {
-		if d.Name() == "." {
-			return nil
-		}
-		if d.Name() == "node_modules" {
-			return fs.SkipDir
-		}
-		osPath := filepath.Join(buildDir, path)
-		os.RemoveAll(osPath)
-		if d.IsDir() {
-			return fs.SkipDir
-		}
-		return nil
-	})
-	if err != nil {
-		return fmt.Errorf("clean build dir: %w", err)
-	}
-	return nil
-}
-
-func unpackFS(out string, srcFS fs.FS) error {
-	err := fs.WalkDir(srcFS, ".", func(path string, d fs.DirEntry, err error) error {
-		if d.Name() == "." {
-			return nil
-		}
-		if d.IsDir() {
-			if err := os.Mkdir(filepath.Join(out, path), 0o777); err != nil {
-				return err
-			}
-			return nil
-		}
-		f, err := srcFS.Open(path)
-		if err != nil {
-			return err
-		}
-		defer f.Close()
-		dst, err := os.Create(filepath.Join(out, path))
-		if err != nil {
-			return err
-		}
-		defer dst.Close()
-		if _, err := io.Copy(dst, f); err != nil {
-			return err
-		}
-		if err := dst.Close(); err != nil {
-			return err
-		}
-		return nil
-	})
-	if err != nil {
-		return fmt.Errorf("unpack fs into out dir %s: %w", out, err)
-	}
-	return nil
-}
-
-func ZipPath() (string, error) {
-	_, hashZip, err := zipPath()
-	return hashZip, err
-}
-
-func zipPath() (cacheDir, hashZip string, err error) {
-	homeDir, err := os.UserHomeDir()
-	if err != nil {
-		return "", "", err
-	}
-	hash, err := embeddedHash()
-	if err != nil {
-		return "", "", err
-	}
-	cacheDir = filepath.Join(homeDir, ".cache", "sketch", "webui")
-	return cacheDir, filepath.Join(cacheDir, "skui-"+hash+".zip"), nil
-}
-
-// Build unpacks and esbuild's all bundleTs typescript files
-func Build() (fs.FS, error) {
-	cacheDir, hashZip, err := zipPath()
-	if err != nil {
-		return nil, err
-	}
-	buildDir := filepath.Join(cacheDir, "build")
-	if err := os.MkdirAll(buildDir, 0o777); err != nil { // make sure .cache/sketch/build exists
-		return nil, err
-	}
-	if b, err := os.ReadFile(hashZip); err == nil {
-		// Build already done, serve it out.
-		return zip.NewReader(bytes.NewReader(b), int64(len(b)))
-	}
-
-	// TODO: try downloading "https://sketch.dev/webui/"+filepath.Base(hashZip)
-
-	// We need to do a build.
-
-	// Clear everything out of the build directory except node_modules.
-	if err := cleanBuildDir(buildDir); err != nil {
-		return nil, err
-	}
-	tmpHashDir := filepath.Join(buildDir, "out")
-	if err := os.Mkdir(tmpHashDir, 0o777); err != nil {
-		return nil, err
-	}
-
-	// Unpack everything from embedded into build dir.
-	if err := unpackFS(buildDir, embedded); err != nil {
-		return nil, err
-	}
-
-	// Do the build. Don't install dev dependencies, because they can be large
-	// and slow enough to install that the /init requests from the host process
-	// will run out of retries and the whole thing exits. We do need better health
-	// checking in general, but that's a separate issue. Don't do slow stuff here:
-	cmd := exec.Command("npm", "ci", "--omit", "dev")
-	cmd.Dir = buildDir
-	if out, err := cmd.CombinedOutput(); err != nil {
-		return nil, fmt.Errorf("npm ci: %s: %v", out, err)
-	}
-	bundleTs := []string{"src/web-components/sketch-app-shell.ts"}
-	for _, tsName := range bundleTs {
-		if err := esbuildBundle(tmpHashDir, filepath.Join(buildDir, tsName), ""); err != nil {
-			return nil, fmt.Errorf("esbuild: %s: %w", tsName, err)
-		}
-	}
-
-	// Copy src files used directly into the new hash output dir.
-	err = fs.WalkDir(embedded, "src", func(path string, d fs.DirEntry, err error) error {
-		if d.IsDir() {
-			if path == "src/web-components/demo" {
-				return fs.SkipDir
-			}
-			return nil
-		}
-		if strings.HasSuffix(path, ".html") || strings.HasSuffix(path, ".css") || strings.HasSuffix(path, ".js") {
-			b, err := embedded.ReadFile(path)
-			if err != nil {
-				return err
-			}
-			dstPath := filepath.Join(tmpHashDir, strings.TrimPrefix(path, "src/"))
-			if err := os.WriteFile(dstPath, b, 0o777); err != nil {
-				return err
-			}
-			return nil
-		}
-		return nil
-	})
-	if err != nil {
-		return nil, err
-	}
-
-	// Copy xterm.css from node_modules
-	const xtermCssPath = "node_modules/@xterm/xterm/css/xterm.css"
-	xtermCss, err := os.ReadFile(filepath.Join(buildDir, xtermCssPath))
-	if err != nil {
-		return nil, fmt.Errorf("failed to read xterm.css: %w", err)
-	}
-	if err := os.WriteFile(filepath.Join(tmpHashDir, "xterm.css"), xtermCss, 0o666); err != nil {
-		return nil, fmt.Errorf("failed to write xterm.css: %w", err)
-	}
-
-	// Compress all .js, .js.map, and .css files with gzip, leaving the originals in place
-	err = filepath.Walk(tmpHashDir, func(path string, info os.FileInfo, err error) error {
-		if err != nil {
-			return err
-		}
-		if info.IsDir() {
-			return nil
-		}
-		// Check if file is a .js or .js.map file
-		if !strings.HasSuffix(path, ".js") && !strings.HasSuffix(path, ".js.map") && !strings.HasSuffix(path, ".css") {
-			return nil
-		}
-
-		// Read the original file
-		origData, err := os.ReadFile(path)
-		if err != nil {
-			return fmt.Errorf("failed to read file %s: %w", path, err)
-		}
-
-		// Create a gzipped file
-		gzipPath := path + ".gz"
-		gzipFile, err := os.Create(gzipPath)
-		if err != nil {
-			return fmt.Errorf("failed to create gzip file %s: %w", gzipPath, err)
-		}
-		defer gzipFile.Close()
-
-		// Create a gzip writer
-		gzWriter := gzip.NewWriter(gzipFile)
-		defer gzWriter.Close()
-
-		// Write the original file content to the gzip writer
-		_, err = gzWriter.Write(origData)
-		if err != nil {
-			return fmt.Errorf("failed to write to gzip file %s: %w", gzipPath, err)
-		}
-
-		// Ensure we flush and close properly
-		if err := gzWriter.Close(); err != nil {
-			return fmt.Errorf("failed to close gzip writer for %s: %w", gzipPath, err)
-		}
-		if err := gzipFile.Close(); err != nil {
-			return fmt.Errorf("failed to close gzip file %s: %w", gzipPath, err)
-		}
-
-		return nil
-	})
-	if err != nil {
-		return nil, fmt.Errorf("failed to compress .js/.js.map/.css files: %w", err)
-	}
-
-	// Everything succeeded, so we write tmpHashDir to hashZip
-	buf := new(bytes.Buffer)
-	w := zip.NewWriter(buf)
-	if err := w.AddFS(os.DirFS(tmpHashDir)); err != nil {
-		return nil, err
-	}
-	if err := w.Close(); err != nil {
-		return nil, err
-	}
-	if err := os.WriteFile(hashZip, buf.Bytes(), 0o666); err != nil {
-		return nil, err
-	}
-	return zip.NewReader(bytes.NewReader(buf.Bytes()), int64(buf.Len()))
-}
-
-func esbuildBundle(outDir, src, metafilePath string) error {
-	args := []string{
-		src,
-		"--bundle",
-		"--sourcemap",
-		"--log-level=error",
-		// Disable minification for now
-		// "--minify",
-		"--outdir=" + outDir,
-	}
-
-	// Add metafile option if path is provided
-	if metafilePath != "" {
-		args = append(args, "--metafile="+metafilePath)
-	}
-
-	ret := esbuildcli.Run(args)
-	if ret != 0 {
-		return fmt.Errorf("esbuild %s failed: %d", filepath.Base(src), ret)
-	}
-	return nil
-}
-
-// unpackTS unpacks all the typescript-relevant files from the embedded filesystem into tmpDir.
-func unpackTS(outDir string, embedded fs.FS) error {
-	return fs.WalkDir(embedded, ".", func(path string, d fs.DirEntry, err error) error {
-		if err != nil {
-			return err
-		}
-		tgt := filepath.Join(outDir, path)
-		if d.IsDir() {
-			if err := os.MkdirAll(tgt, 0o777); err != nil {
-				return err
-			}
-			return nil
-		}
-		if strings.HasSuffix(path, ".html") || strings.HasSuffix(path, ".md") || strings.HasSuffix(path, ".css") {
-			return nil
-		}
-		data, err := fs.ReadFile(embedded, path)
-		if err != nil {
-			return err
-		}
-		if err := os.WriteFile(tgt, data, 0o666); err != nil {
-			return err
-		}
-		return nil
-	})
-}
-
-// GenerateBundleMetafile creates metafiles for bundle analysis with esbuild.
-//
-// The metafiles contain information about bundle size and dependencies
-// that can be visualized at https://esbuild.github.io/analyze/
-//
-// It takes the output directory where the metafiles will be written.
-// Returns the file path of the generated metafiles.
-func GenerateBundleMetafile(outputDir string) (string, error) {
-	tmpDir, err := os.MkdirTemp("", "bundle-analysis-")
-	if err != nil {
-		return "", err
-	}
-	defer os.RemoveAll(tmpDir)
-
-	// Create output directory if it doesn't exist
-	if err := os.MkdirAll(outputDir, 0o755); err != nil {
-		return "", err
-	}
-
-	cacheDir, _, err := zipPath()
-	if err != nil {
-		return "", err
-	}
-	buildDir := filepath.Join(cacheDir, "build")
-	if err := os.MkdirAll(buildDir, 0o777); err != nil { // make sure .cache/sketch/build exists
-		return "", err
-	}
-
-	// Ensure we have a source to bundle
-	if err := unpackTS(tmpDir, embedded); err != nil {
-		return "", err
-	}
-
-	// All bundles to analyze
-	bundleTs := []string{"src/web-components/sketch-app-shell.ts"}
-	metafiles := make([]string, len(bundleTs))
-
-	for i, tsName := range bundleTs {
-		// Create a metafile path for this bundle
-		baseFileName := filepath.Base(tsName)
-		metaFileName := strings.TrimSuffix(baseFileName, ".ts") + ".meta.json"
-		metafilePath := filepath.Join(outputDir, metaFileName)
-		metafiles[i] = metafilePath
-
-		// Bundle with metafile generation
-		outTmpDir, err := os.MkdirTemp("", "metafile-bundle-")
-		if err != nil {
-			return "", err
-		}
-		defer os.RemoveAll(outTmpDir)
-
-		if err := esbuildBundle(outTmpDir, filepath.Join(buildDir, tsName), metafilePath); err != nil {
-			return "", fmt.Errorf("failed to generate metafile for %s: %w", tsName, err)
-		}
-	}
-
-	return outputDir, nil
-}
diff --git a/loop/webui/memfs.go b/loop/webui/memfs.go
deleted file mode 100644
index 5431862..0000000
--- a/loop/webui/memfs.go
+++ /dev/null
@@ -1,53 +0,0 @@
-package webui
-
-import (
-	"bytes"
-	"fmt"
-	"io/fs"
-	"time"
-)
-
-// memFS implements fs.FS in-memory.
-type memFS struct {
-	m map[string][]byte
-}
-
-func (m memFS) Open(name string) (fs.File, error) {
-	b, found := m.m[name]
-	if !found {
-		return nil, fmt.Errorf("esbuild.memFS(%q): %w", name, fs.ErrNotExist)
-	}
-	return &memFile{name: name, Reader: *bytes.NewReader(b)}, nil
-}
-
-func (m memFS) ReadFile(name string) ([]byte, error) {
-	b, found := m.m[name]
-	if !found {
-		return nil, fmt.Errorf("esbuild.memFS.ReadFile(%q): %w", name, fs.ErrNotExist)
-	}
-	return append(make([]byte, 0, len(b)), b...), nil
-}
-
-// memFile implements fs.File in-memory.
-type memFile struct {
-	// embedding is very important here because need more than
-	// Read, we need Seek to make http.ServeContent happy.
-	bytes.Reader
-	name string
-}
-
-func (f *memFile) Stat() (fs.FileInfo, error) { return &memFileInfo{f: f}, nil }
-func (f *memFile) Close() error               { return nil }
-
-var start = time.Now()
-
-type memFileInfo struct {
-	f *memFile
-}
-
-func (i memFileInfo) Name() string       { return i.f.name }
-func (i memFileInfo) Size() int64        { return i.f.Reader.Size() }
-func (i memFileInfo) Mode() fs.FileMode  { return 0o444 }
-func (i memFileInfo) ModTime() time.Time { return start }
-func (i memFileInfo) IsDir() bool        { return false }
-func (i memFileInfo) Sys() any           { return nil }
diff --git a/loop/webui/package-lock.json b/loop/webui/package-lock.json
deleted file mode 100644
index 8abb565..0000000
--- a/loop/webui/package-lock.json
+++ /dev/null
@@ -1,7264 +0,0 @@
-{
-  "name": "webui",
-  "version": "1.0.0",
-  "lockfileVersion": 3,
-  "requires": true,
-  "packages": {
-    "": {
-      "name": "webui",
-      "version": "1.0.0",
-      "license": "ISC",
-      "dependencies": {
-        "@xterm/addon-fit": "^0.10.0",
-        "@xterm/xterm": "^5.5.0",
-        "diff2html": "3.4.51",
-        "lit": "^3.2.1",
-        "marked": "^15.0.7",
-        "sanitize-html": "^2.15.0",
-        "vega": "^5.33.0",
-        "vega-embed": "^6.29.0",
-        "vega-lite": "^5.23.0"
-      },
-      "devDependencies": {
-        "@sand4rt/experimental-ct-web": "^1.51.1",
-        "@types/marked": "^5.0.2",
-        "@types/mocha": "^10.0.7",
-        "@types/node": "^22.13.14",
-        "@web/test-runner": "^0.18.2",
-        "@web/test-runner-puppeteer": "^0.18.0",
-        "autoprefixer": "^10.4.21",
-        "esbuild": "^0.25.1",
-        "prettier": "3.5.3",
-        "typescript": "^5.8.2",
-        "vite": "^6.3.2",
-        "vite-plugin-web-components-hmr": "^0.1.3"
-      }
-    },
-    "node_modules/@ampproject/remapping": {
-      "version": "2.3.0",
-      "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
-      "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
-      "dev": true,
-      "license": "Apache-2.0",
-      "dependencies": {
-        "@jridgewell/gen-mapping": "^0.3.5",
-        "@jridgewell/trace-mapping": "^0.3.24"
-      },
-      "engines": {
-        "node": ">=6.0.0"
-      }
-    },
-    "node_modules/@babel/code-frame": {
-      "version": "7.26.2",
-      "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz",
-      "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==",
-      "dev": true,
-      "dependencies": {
-        "@babel/helper-validator-identifier": "^7.25.9",
-        "js-tokens": "^4.0.0",
-        "picocolors": "^1.0.0"
-      },
-      "engines": {
-        "node": ">=6.9.0"
-      }
-    },
-    "node_modules/@babel/compat-data": {
-      "version": "7.26.8",
-      "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.8.tgz",
-      "integrity": "sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==",
-      "dev": true,
-      "license": "MIT",
-      "engines": {
-        "node": ">=6.9.0"
-      }
-    },
-    "node_modules/@babel/core": {
-      "version": "7.26.10",
-      "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.10.tgz",
-      "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "@ampproject/remapping": "^2.2.0",
-        "@babel/code-frame": "^7.26.2",
-        "@babel/generator": "^7.26.10",
-        "@babel/helper-compilation-targets": "^7.26.5",
-        "@babel/helper-module-transforms": "^7.26.0",
-        "@babel/helpers": "^7.26.10",
-        "@babel/parser": "^7.26.10",
-        "@babel/template": "^7.26.9",
-        "@babel/traverse": "^7.26.10",
-        "@babel/types": "^7.26.10",
-        "convert-source-map": "^2.0.0",
-        "debug": "^4.1.0",
-        "gensync": "^1.0.0-beta.2",
-        "json5": "^2.2.3",
-        "semver": "^6.3.1"
-      },
-      "engines": {
-        "node": ">=6.9.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/babel"
-      }
-    },
-    "node_modules/@babel/core/node_modules/semver": {
-      "version": "6.3.1",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
-      "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
-      "dev": true,
-      "license": "ISC",
-      "bin": {
-        "semver": "bin/semver.js"
-      }
-    },
-    "node_modules/@babel/generator": {
-      "version": "7.27.0",
-      "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.0.tgz",
-      "integrity": "sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "@babel/parser": "^7.27.0",
-        "@babel/types": "^7.27.0",
-        "@jridgewell/gen-mapping": "^0.3.5",
-        "@jridgewell/trace-mapping": "^0.3.25",
-        "jsesc": "^3.0.2"
-      },
-      "engines": {
-        "node": ">=6.9.0"
-      }
-    },
-    "node_modules/@babel/helper-compilation-targets": {
-      "version": "7.27.0",
-      "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.0.tgz",
-      "integrity": "sha512-LVk7fbXml0H2xH34dFzKQ7TDZ2G4/rVTOrq9V+icbbadjbVxxeFeDsNHv2SrZeWoA+6ZiTyWYWtScEIW07EAcA==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "@babel/compat-data": "^7.26.8",
-        "@babel/helper-validator-option": "^7.25.9",
-        "browserslist": "^4.24.0",
-        "lru-cache": "^5.1.1",
-        "semver": "^6.3.1"
-      },
-      "engines": {
-        "node": ">=6.9.0"
-      }
-    },
-    "node_modules/@babel/helper-compilation-targets/node_modules/semver": {
-      "version": "6.3.1",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
-      "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
-      "dev": true,
-      "license": "ISC",
-      "bin": {
-        "semver": "bin/semver.js"
-      }
-    },
-    "node_modules/@babel/helper-module-imports": {
-      "version": "7.25.9",
-      "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz",
-      "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "@babel/traverse": "^7.25.9",
-        "@babel/types": "^7.25.9"
-      },
-      "engines": {
-        "node": ">=6.9.0"
-      }
-    },
-    "node_modules/@babel/helper-module-transforms": {
-      "version": "7.26.0",
-      "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz",
-      "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "@babel/helper-module-imports": "^7.25.9",
-        "@babel/helper-validator-identifier": "^7.25.9",
-        "@babel/traverse": "^7.25.9"
-      },
-      "engines": {
-        "node": ">=6.9.0"
-      },
-      "peerDependencies": {
-        "@babel/core": "^7.0.0"
-      }
-    },
-    "node_modules/@babel/helper-plugin-utils": {
-      "version": "7.26.5",
-      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz",
-      "integrity": "sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==",
-      "dev": true,
-      "license": "MIT",
-      "engines": {
-        "node": ">=6.9.0"
-      }
-    },
-    "node_modules/@babel/helper-string-parser": {
-      "version": "7.25.9",
-      "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz",
-      "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==",
-      "dev": true,
-      "license": "MIT",
-      "engines": {
-        "node": ">=6.9.0"
-      }
-    },
-    "node_modules/@babel/helper-validator-identifier": {
-      "version": "7.25.9",
-      "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz",
-      "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==",
-      "dev": true,
-      "engines": {
-        "node": ">=6.9.0"
-      }
-    },
-    "node_modules/@babel/helper-validator-option": {
-      "version": "7.25.9",
-      "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz",
-      "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==",
-      "dev": true,
-      "license": "MIT",
-      "engines": {
-        "node": ">=6.9.0"
-      }
-    },
-    "node_modules/@babel/helpers": {
-      "version": "7.27.0",
-      "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.0.tgz",
-      "integrity": "sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "@babel/template": "^7.27.0",
-        "@babel/types": "^7.27.0"
-      },
-      "engines": {
-        "node": ">=6.9.0"
-      }
-    },
-    "node_modules/@babel/parser": {
-      "version": "7.27.0",
-      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz",
-      "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "@babel/types": "^7.27.0"
-      },
-      "bin": {
-        "parser": "bin/babel-parser.js"
-      },
-      "engines": {
-        "node": ">=6.0.0"
-      }
-    },
-    "node_modules/@babel/plugin-syntax-class-properties": {
-      "version": "7.12.13",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz",
-      "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "@babel/helper-plugin-utils": "^7.12.13"
-      },
-      "peerDependencies": {
-        "@babel/core": "^7.0.0-0"
-      }
-    },
-    "node_modules/@babel/plugin-syntax-import-assertions": {
-      "version": "7.26.0",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.26.0.tgz",
-      "integrity": "sha512-QCWT5Hh830hK5EQa7XzuqIkQU9tT/whqbDz7kuaZMHFl1inRRg7JnuAEOQ0Ur0QUl0NufCk1msK2BeY79Aj/eg==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "@babel/helper-plugin-utils": "^7.25.9"
-      },
-      "engines": {
-        "node": ">=6.9.0"
-      },
-      "peerDependencies": {
-        "@babel/core": "^7.0.0-0"
-      }
-    },
-    "node_modules/@babel/plugin-syntax-top-level-await": {
-      "version": "7.14.5",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz",
-      "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "@babel/helper-plugin-utils": "^7.14.5"
-      },
-      "engines": {
-        "node": ">=6.9.0"
-      },
-      "peerDependencies": {
-        "@babel/core": "^7.0.0-0"
-      }
-    },
-    "node_modules/@babel/template": {
-      "version": "7.27.0",
-      "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.0.tgz",
-      "integrity": "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "@babel/code-frame": "^7.26.2",
-        "@babel/parser": "^7.27.0",
-        "@babel/types": "^7.27.0"
-      },
-      "engines": {
-        "node": ">=6.9.0"
-      }
-    },
-    "node_modules/@babel/traverse": {
-      "version": "7.27.0",
-      "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.0.tgz",
-      "integrity": "sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "@babel/code-frame": "^7.26.2",
-        "@babel/generator": "^7.27.0",
-        "@babel/parser": "^7.27.0",
-        "@babel/template": "^7.27.0",
-        "@babel/types": "^7.27.0",
-        "debug": "^4.3.1",
-        "globals": "^11.1.0"
-      },
-      "engines": {
-        "node": ">=6.9.0"
-      }
-    },
-    "node_modules/@babel/types": {
-      "version": "7.27.0",
-      "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz",
-      "integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "@babel/helper-string-parser": "^7.25.9",
-        "@babel/helper-validator-identifier": "^7.25.9"
-      },
-      "engines": {
-        "node": ">=6.9.0"
-      }
-    },
-    "node_modules/@esbuild/aix-ppc64": {
-      "version": "0.25.2",
-      "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.2.tgz",
-      "integrity": "sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag==",
-      "cpu": [
-        "ppc64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "aix"
-      ],
-      "engines": {
-        "node": ">=18"
-      }
-    },
-    "node_modules/@esbuild/android-arm": {
-      "version": "0.25.2",
-      "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.2.tgz",
-      "integrity": "sha512-NQhH7jFstVY5x8CKbcfa166GoV0EFkaPkCKBQkdPJFvo5u+nGXLEH/ooniLb3QI8Fk58YAx7nsPLozUWfCBOJA==",
-      "cpu": [
-        "arm"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "android"
-      ],
-      "engines": {
-        "node": ">=18"
-      }
-    },
-    "node_modules/@esbuild/android-arm64": {
-      "version": "0.25.2",
-      "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.2.tgz",
-      "integrity": "sha512-5ZAX5xOmTligeBaeNEPnPaeEuah53Id2tX4c2CVP3JaROTH+j4fnfHCkr1PjXMd78hMst+TlkfKcW/DlTq0i4w==",
-      "cpu": [
-        "arm64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "android"
-      ],
-      "engines": {
-        "node": ">=18"
-      }
-    },
-    "node_modules/@esbuild/android-x64": {
-      "version": "0.25.2",
-      "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.2.tgz",
-      "integrity": "sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "android"
-      ],
-      "engines": {
-        "node": ">=18"
-      }
-    },
-    "node_modules/@esbuild/darwin-arm64": {
-      "version": "0.25.2",
-      "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.2.tgz",
-      "integrity": "sha512-MpM6LUVTXAzOvN4KbjzU/q5smzryuoNjlriAIx+06RpecwCkL9JpenNzpKd2YMzLJFOdPqBpuub6eVRP5IgiSA==",
-      "cpu": [
-        "arm64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "darwin"
-      ],
-      "engines": {
-        "node": ">=18"
-      }
-    },
-    "node_modules/@esbuild/darwin-x64": {
-      "version": "0.25.2",
-      "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.2.tgz",
-      "integrity": "sha512-5eRPrTX7wFyuWe8FqEFPG2cU0+butQQVNcT4sVipqjLYQjjh8a8+vUTfgBKM88ObB85ahsnTwF7PSIt6PG+QkA==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "darwin"
-      ],
-      "engines": {
-        "node": ">=18"
-      }
-    },
-    "node_modules/@esbuild/freebsd-arm64": {
-      "version": "0.25.2",
-      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.2.tgz",
-      "integrity": "sha512-mLwm4vXKiQ2UTSX4+ImyiPdiHjiZhIaE9QvC7sw0tZ6HoNMjYAqQpGyui5VRIi5sGd+uWq940gdCbY3VLvsO1w==",
-      "cpu": [
-        "arm64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "freebsd"
-      ],
-      "engines": {
-        "node": ">=18"
-      }
-    },
-    "node_modules/@esbuild/freebsd-x64": {
-      "version": "0.25.2",
-      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.2.tgz",
-      "integrity": "sha512-6qyyn6TjayJSwGpm8J9QYYGQcRgc90nmfdUb0O7pp1s4lTY+9D0H9O02v5JqGApUyiHOtkz6+1hZNvNtEhbwRQ==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "freebsd"
-      ],
-      "engines": {
-        "node": ">=18"
-      }
-    },
-    "node_modules/@esbuild/linux-arm": {
-      "version": "0.25.2",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.2.tgz",
-      "integrity": "sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g==",
-      "cpu": [
-        "arm"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">=18"
-      }
-    },
-    "node_modules/@esbuild/linux-arm64": {
-      "version": "0.25.2",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.2.tgz",
-      "integrity": "sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g==",
-      "cpu": [
-        "arm64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">=18"
-      }
-    },
-    "node_modules/@esbuild/linux-ia32": {
-      "version": "0.25.2",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.2.tgz",
-      "integrity": "sha512-bBYCv9obgW2cBP+2ZWfjYTU+f5cxRoGGQ5SeDbYdFCAZpYWrfjjfYwvUpP8MlKbP0nwZ5gyOU/0aUzZ5HWPuvQ==",
-      "cpu": [
-        "ia32"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">=18"
-      }
-    },
-    "node_modules/@esbuild/linux-loong64": {
-      "version": "0.25.2",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.2.tgz",
-      "integrity": "sha512-SHNGiKtvnU2dBlM5D8CXRFdd+6etgZ9dXfaPCeJtz+37PIUlixvlIhI23L5khKXs3DIzAn9V8v+qb1TRKrgT5w==",
-      "cpu": [
-        "loong64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">=18"
-      }
-    },
-    "node_modules/@esbuild/linux-mips64el": {
-      "version": "0.25.2",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.2.tgz",
-      "integrity": "sha512-hDDRlzE6rPeoj+5fsADqdUZl1OzqDYow4TB4Y/3PlKBD0ph1e6uPHzIQcv2Z65u2K0kpeByIyAjCmjn1hJgG0Q==",
-      "cpu": [
-        "mips64el"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">=18"
-      }
-    },
-    "node_modules/@esbuild/linux-ppc64": {
-      "version": "0.25.2",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.2.tgz",
-      "integrity": "sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g==",
-      "cpu": [
-        "ppc64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">=18"
-      }
-    },
-    "node_modules/@esbuild/linux-riscv64": {
-      "version": "0.25.2",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.2.tgz",
-      "integrity": "sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw==",
-      "cpu": [
-        "riscv64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">=18"
-      }
-    },
-    "node_modules/@esbuild/linux-s390x": {
-      "version": "0.25.2",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.2.tgz",
-      "integrity": "sha512-GRa4IshOdvKY7M/rDpRR3gkiTNp34M0eLTaC1a08gNrh4u488aPhuZOCpkF6+2wl3zAN7L7XIpOFBhnaE3/Q8Q==",
-      "cpu": [
-        "s390x"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">=18"
-      }
-    },
-    "node_modules/@esbuild/linux-x64": {
-      "version": "0.25.2",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.2.tgz",
-      "integrity": "sha512-QInHERlqpTTZ4FRB0fROQWXcYRD64lAoiegezDunLpalZMjcUcld3YzZmVJ2H/Cp0wJRZ8Xtjtj0cEHhYc/uUg==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "linux"
-      ],
-      "engines": {
-        "node": ">=18"
-      }
-    },
-    "node_modules/@esbuild/netbsd-arm64": {
-      "version": "0.25.2",
-      "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.2.tgz",
-      "integrity": "sha512-talAIBoY5M8vHc6EeI2WW9d/CkiO9MQJ0IOWX8hrLhxGbro/vBXJvaQXefW2cP0z0nQVTdQ/eNyGFV1GSKrxfw==",
-      "cpu": [
-        "arm64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "netbsd"
-      ],
-      "engines": {
-        "node": ">=18"
-      }
-    },
-    "node_modules/@esbuild/netbsd-x64": {
-      "version": "0.25.2",
-      "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.2.tgz",
-      "integrity": "sha512-voZT9Z+tpOxrvfKFyfDYPc4DO4rk06qamv1a/fkuzHpiVBMOhpjK+vBmWM8J1eiB3OLSMFYNaOaBNLXGChf5tg==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "netbsd"
-      ],
-      "engines": {
-        "node": ">=18"
-      }
-    },
-    "node_modules/@esbuild/openbsd-arm64": {
-      "version": "0.25.2",
-      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.2.tgz",
-      "integrity": "sha512-dcXYOC6NXOqcykeDlwId9kB6OkPUxOEqU+rkrYVqJbK2hagWOMrsTGsMr8+rW02M+d5Op5NNlgMmjzecaRf7Tg==",
-      "cpu": [
-        "arm64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "openbsd"
-      ],
-      "engines": {
-        "node": ">=18"
-      }
-    },
-    "node_modules/@esbuild/openbsd-x64": {
-      "version": "0.25.2",
-      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.2.tgz",
-      "integrity": "sha512-t/TkWwahkH0Tsgoq1Ju7QfgGhArkGLkF1uYz8nQS/PPFlXbP5YgRpqQR3ARRiC2iXoLTWFxc6DJMSK10dVXluw==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "openbsd"
-      ],
-      "engines": {
-        "node": ">=18"
-      }
-    },
-    "node_modules/@esbuild/sunos-x64": {
-      "version": "0.25.2",
-      "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.2.tgz",
-      "integrity": "sha512-cfZH1co2+imVdWCjd+D1gf9NjkchVhhdpgb1q5y6Hcv9TP6Zi9ZG/beI3ig8TvwT9lH9dlxLq5MQBBgwuj4xvA==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "sunos"
-      ],
-      "engines": {
-        "node": ">=18"
-      }
-    },
-    "node_modules/@esbuild/win32-arm64": {
-      "version": "0.25.2",
-      "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.2.tgz",
-      "integrity": "sha512-7Loyjh+D/Nx/sOTzV8vfbB3GJuHdOQyrOryFdZvPHLf42Tk9ivBU5Aedi7iyX+x6rbn2Mh68T4qq1SDqJBQO5Q==",
-      "cpu": [
-        "arm64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "win32"
-      ],
-      "engines": {
-        "node": ">=18"
-      }
-    },
-    "node_modules/@esbuild/win32-ia32": {
-      "version": "0.25.2",
-      "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.2.tgz",
-      "integrity": "sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg==",
-      "cpu": [
-        "ia32"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "win32"
-      ],
-      "engines": {
-        "node": ">=18"
-      }
-    },
-    "node_modules/@esbuild/win32-x64": {
-      "version": "0.25.2",
-      "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.2.tgz",
-      "integrity": "sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "win32"
-      ],
-      "engines": {
-        "node": ">=18"
-      }
-    },
-    "node_modules/@hapi/bourne": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-3.0.0.tgz",
-      "integrity": "sha512-Waj1cwPXJDucOib4a3bAISsKJVb15MKi9IvmTI/7ssVEm6sywXGjVJDhl6/umt1pK1ZS7PacXU3A1PmFKHEZ2w==",
-      "dev": true
-    },
-    "node_modules/@jridgewell/gen-mapping": {
-      "version": "0.3.8",
-      "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
-      "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "@jridgewell/set-array": "^1.2.1",
-        "@jridgewell/sourcemap-codec": "^1.4.10",
-        "@jridgewell/trace-mapping": "^0.3.24"
-      },
-      "engines": {
-        "node": ">=6.0.0"
-      }
-    },
-    "node_modules/@jridgewell/resolve-uri": {
-      "version": "3.1.2",
-      "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
-      "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
-      "dev": true,
-      "license": "MIT",
-      "engines": {
-        "node": ">=6.0.0"
-      }
-    },
-    "node_modules/@jridgewell/set-array": {
-      "version": "1.2.1",
-      "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
-      "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
-      "dev": true,
-      "license": "MIT",
-      "engines": {
-        "node": ">=6.0.0"
-      }
-    },
-    "node_modules/@jridgewell/sourcemap-codec": {
-      "version": "1.5.0",
-      "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
-      "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
-      "dev": true,
-      "license": "MIT"
-    },
-    "node_modules/@jridgewell/trace-mapping": {
-      "version": "0.3.25",
-      "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
-      "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "@jridgewell/resolve-uri": "^3.1.0",
-        "@jridgewell/sourcemap-codec": "^1.4.14"
-      }
-    },
-    "node_modules/@lit-labs/ssr-dom-shim": {
-      "version": "1.3.0",
-      "resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.3.0.tgz",
-      "integrity": "sha512-nQIWonJ6eFAvUUrSlwyHDm/aE8PBDu5kRpL0vHMg6K8fK3Diq1xdPjTnsJSwxABhaZ+5eBi1btQB5ShUTKo4nQ==",
-      "license": "BSD-3-Clause"
-    },
-    "node_modules/@lit/reactive-element": {
-      "version": "2.0.4",
-      "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-2.0.4.tgz",
-      "integrity": "sha512-GFn91inaUa2oHLak8awSIigYz0cU0Payr1rcFsrkf5OJ5eSPxElyZfKh0f2p9FsTiZWXQdWGJeXZICEfXXYSXQ==",
-      "license": "BSD-3-Clause",
-      "dependencies": {
-        "@lit-labs/ssr-dom-shim": "^1.2.0"
-      }
-    },
-    "node_modules/@nodelib/fs.scandir": {
-      "version": "2.1.5",
-      "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
-      "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "@nodelib/fs.stat": "2.0.5",
-        "run-parallel": "^1.1.9"
-      },
-      "engines": {
-        "node": ">= 8"
-      }
-    },
-    "node_modules/@nodelib/fs.stat": {
-      "version": "2.0.5",
-      "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
-      "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
-      "dev": true,
-      "license": "MIT",
-      "engines": {
-        "node": ">= 8"
-      }
-    },
-    "node_modules/@nodelib/fs.walk": {
-      "version": "1.2.8",
-      "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
-      "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "@nodelib/fs.scandir": "2.1.5",
-        "fastq": "^1.6.0"
-      },
-      "engines": {
-        "node": ">= 8"
-      }
-    },
-    "node_modules/@playwright/experimental-ct-core": {
-      "version": "1.51.1",
-      "resolved": "https://registry.npmjs.org/@playwright/experimental-ct-core/-/experimental-ct-core-1.51.1.tgz",
-      "integrity": "sha512-kpRZWBT3SMukL1fx8BwEj385Pkgtp86bBKzmrmJU30lWlQiIDFNaIHosgxQC68c8y2mg3Una/lBSHNc2Fotgkw==",
-      "dev": true,
-      "license": "Apache-2.0",
-      "dependencies": {
-        "playwright": "1.51.1",
-        "playwright-core": "1.51.1",
-        "vite": "^5.4.14 || ^6.0.0"
-      },
-      "engines": {
-        "node": ">=18"
-      }
-    },
-    "node_modules/@puppeteer/browsers": {
-      "version": "2.3.0",
-      "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.3.0.tgz",
-      "integrity": "sha512-ioXoq9gPxkss4MYhD+SFaU9p1IHFUX0ILAWFPyjGaBdjLsYAlZw6j1iLA0N/m12uVHLFDfSYNF7EQccjinIMDA==",
-      "dev": true,
-      "dependencies": {
-        "debug": "^4.3.5",
-        "extract-zip": "^2.0.1",
-        "progress": "^2.0.3",
-        "proxy-agent": "^6.4.0",
-        "semver": "^7.6.3",
-        "tar-fs": "^3.0.6",
-        "unbzip2-stream": "^1.4.3",
-        "yargs": "^17.7.2"
-      },
-      "bin": {
-        "browsers": "lib/cjs/main-cli.js"
-      },
-      "engines": {
-        "node": ">=18"
-      }
-    },
-    "node_modules/@rollup/plugin-node-resolve": {
-      "version": "15.3.1",
-      "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.3.1.tgz",
-      "integrity": "sha512-tgg6b91pAybXHJQMAAwW9VuWBO6Thi+q7BCNARLwSqlmsHz0XYURtGvh/AuwSADXSI4h/2uHbs7s4FzlZDGSGA==",
-      "dev": true,
-      "dependencies": {
-        "@rollup/pluginutils": "^5.0.1",
-        "@types/resolve": "1.20.2",
-        "deepmerge": "^4.2.2",
-        "is-module": "^1.0.0",
-        "resolve": "^1.22.1"
-      },
-      "engines": {
-        "node": ">=14.0.0"
-      },
-      "peerDependencies": {
-        "rollup": "^2.78.0||^3.0.0||^4.0.0"
-      },
-      "peerDependenciesMeta": {
-        "rollup": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/@rollup/pluginutils": {
-      "version": "5.1.4",
-      "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.4.tgz",
-      "integrity": "sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==",
-      "dev": true,
-      "dependencies": {
-        "@types/estree": "^1.0.0",
-        "estree-walker": "^2.0.2",
-        "picomatch": "^4.0.2"
-      },
-      "engines": {
-        "node": ">=14.0.0"
-      },
-      "peerDependencies": {
-        "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
-      },
-      "peerDependenciesMeta": {
-        "rollup": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/@rollup/pluginutils/node_modules/picomatch": {
-      "version": "4.0.2",
-      "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
-      "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
-      "dev": true,
-      "engines": {
-        "node": ">=12"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/jonschlinkert"
-      }
-    },
-    "node_modules/@rollup/rollup-android-arm-eabi": {
-      "version": "4.39.0",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.39.0.tgz",
-      "integrity": "sha512-lGVys55Qb00Wvh8DMAocp5kIcaNzEFTmGhfFd88LfaogYTRKrdxgtlO5H6S49v2Nd8R2C6wLOal0qv6/kCkOwA==",
-      "cpu": [
-        "arm"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "android"
-      ]
-    },
-    "node_modules/@rollup/rollup-android-arm64": {
-      "version": "4.39.0",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.39.0.tgz",
-      "integrity": "sha512-It9+M1zE31KWfqh/0cJLrrsCPiF72PoJjIChLX+rEcujVRCb4NLQ5QzFkzIZW8Kn8FTbvGQBY5TkKBau3S8cCQ==",
-      "cpu": [
-        "arm64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "android"
-      ]
-    },
-    "node_modules/@rollup/rollup-darwin-arm64": {
-      "version": "4.39.0",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.39.0.tgz",
-      "integrity": "sha512-lXQnhpFDOKDXiGxsU9/l8UEGGM65comrQuZ+lDcGUx+9YQ9dKpF3rSEGepyeR5AHZ0b5RgiligsBhWZfSSQh8Q==",
-      "cpu": [
-        "arm64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "darwin"
-      ]
-    },
-    "node_modules/@rollup/rollup-darwin-x64": {
-      "version": "4.39.0",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.39.0.tgz",
-      "integrity": "sha512-mKXpNZLvtEbgu6WCkNij7CGycdw9cJi2k9v0noMb++Vab12GZjFgUXD69ilAbBh034Zwn95c2PNSz9xM7KYEAQ==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "darwin"
-      ]
-    },
-    "node_modules/@rollup/rollup-freebsd-arm64": {
-      "version": "4.39.0",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.39.0.tgz",
-      "integrity": "sha512-jivRRlh2Lod/KvDZx2zUR+I4iBfHcu2V/BA2vasUtdtTN2Uk3jfcZczLa81ESHZHPHy4ih3T/W5rPFZ/hX7RtQ==",
-      "cpu": [
-        "arm64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "freebsd"
-      ]
-    },
-    "node_modules/@rollup/rollup-freebsd-x64": {
-      "version": "4.39.0",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.39.0.tgz",
-      "integrity": "sha512-8RXIWvYIRK9nO+bhVz8DwLBepcptw633gv/QT4015CpJ0Ht8punmoHU/DuEd3iw9Hr8UwUV+t+VNNuZIWYeY7Q==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "freebsd"
-      ]
-    },
-    "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
-      "version": "4.39.0",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.39.0.tgz",
-      "integrity": "sha512-mz5POx5Zu58f2xAG5RaRRhp3IZDK7zXGk5sdEDj4o96HeaXhlUwmLFzNlc4hCQi5sGdR12VDgEUqVSHer0lI9g==",
-      "cpu": [
-        "arm"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "linux"
-      ]
-    },
-    "node_modules/@rollup/rollup-linux-arm-musleabihf": {
-      "version": "4.39.0",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.39.0.tgz",
-      "integrity": "sha512-+YDwhM6gUAyakl0CD+bMFpdmwIoRDzZYaTWV3SDRBGkMU/VpIBYXXEvkEcTagw/7VVkL2vA29zU4UVy1mP0/Yw==",
-      "cpu": [
-        "arm"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "linux"
-      ]
-    },
-    "node_modules/@rollup/rollup-linux-arm64-gnu": {
-      "version": "4.39.0",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.39.0.tgz",
-      "integrity": "sha512-EKf7iF7aK36eEChvlgxGnk7pdJfzfQbNvGV/+l98iiMwU23MwvmV0Ty3pJ0p5WQfm3JRHOytSIqD9LB7Bq7xdQ==",
-      "cpu": [
-        "arm64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "linux"
-      ]
-    },
-    "node_modules/@rollup/rollup-linux-arm64-musl": {
-      "version": "4.39.0",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.39.0.tgz",
-      "integrity": "sha512-vYanR6MtqC7Z2SNr8gzVnzUul09Wi1kZqJaek3KcIlI/wq5Xtq4ZPIZ0Mr/st/sv/NnaPwy/D4yXg5x0B3aUUA==",
-      "cpu": [
-        "arm64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "linux"
-      ]
-    },
-    "node_modules/@rollup/rollup-linux-loongarch64-gnu": {
-      "version": "4.39.0",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.39.0.tgz",
-      "integrity": "sha512-NMRUT40+h0FBa5fb+cpxtZoGAggRem16ocVKIv5gDB5uLDgBIwrIsXlGqYbLwW8YyO3WVTk1FkFDjMETYlDqiw==",
-      "cpu": [
-        "loong64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "linux"
-      ]
-    },
-    "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
-      "version": "4.39.0",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.39.0.tgz",
-      "integrity": "sha512-0pCNnmxgduJ3YRt+D+kJ6Ai/r+TaePu9ZLENl+ZDV/CdVczXl95CbIiwwswu4L+K7uOIGf6tMo2vm8uadRaICQ==",
-      "cpu": [
-        "ppc64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "linux"
-      ]
-    },
-    "node_modules/@rollup/rollup-linux-riscv64-gnu": {
-      "version": "4.39.0",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.39.0.tgz",
-      "integrity": "sha512-t7j5Zhr7S4bBtksT73bO6c3Qa2AV/HqiGlj9+KB3gNF5upcVkx+HLgxTm8DK4OkzsOYqbdqbLKwvGMhylJCPhQ==",
-      "cpu": [
-        "riscv64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "linux"
-      ]
-    },
-    "node_modules/@rollup/rollup-linux-riscv64-musl": {
-      "version": "4.39.0",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.39.0.tgz",
-      "integrity": "sha512-m6cwI86IvQ7M93MQ2RF5SP8tUjD39Y7rjb1qjHgYh28uAPVU8+k/xYWvxRO3/tBN2pZkSMa5RjnPuUIbrwVxeA==",
-      "cpu": [
-        "riscv64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "linux"
-      ]
-    },
-    "node_modules/@rollup/rollup-linux-s390x-gnu": {
-      "version": "4.39.0",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.39.0.tgz",
-      "integrity": "sha512-iRDJd2ebMunnk2rsSBYlsptCyuINvxUfGwOUldjv5M4tpa93K8tFMeYGpNk2+Nxl+OBJnBzy2/JCscGeO507kA==",
-      "cpu": [
-        "s390x"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "linux"
-      ]
-    },
-    "node_modules/@rollup/rollup-linux-x64-gnu": {
-      "version": "4.39.0",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.39.0.tgz",
-      "integrity": "sha512-t9jqYw27R6Lx0XKfEFe5vUeEJ5pF3SGIM6gTfONSMb7DuG6z6wfj2yjcoZxHg129veTqU7+wOhY6GX8wmf90dA==",
-      "cpu": [
-        "x64"
-      ],
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "linux"
-      ]
-    },
-    "node_modules/@rollup/rollup-linux-x64-musl": {
-      "version": "4.39.0",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.39.0.tgz",
-      "integrity": "sha512-ThFdkrFDP55AIsIZDKSBWEt/JcWlCzydbZHinZ0F/r1h83qbGeenCt/G/wG2O0reuENDD2tawfAj2s8VK7Bugg==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "linux"
-      ]
-    },
-    "node_modules/@rollup/rollup-win32-arm64-msvc": {
-      "version": "4.39.0",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.39.0.tgz",
-      "integrity": "sha512-jDrLm6yUtbOg2TYB3sBF3acUnAwsIksEYjLeHL+TJv9jg+TmTwdyjnDex27jqEMakNKf3RwwPahDIt7QXCSqRQ==",
-      "cpu": [
-        "arm64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "win32"
-      ]
-    },
-    "node_modules/@rollup/rollup-win32-ia32-msvc": {
-      "version": "4.39.0",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.39.0.tgz",
-      "integrity": "sha512-6w9uMuza+LbLCVoNKL5FSLE7yvYkq9laSd09bwS0tMjkwXrmib/4KmoJcrKhLWHvw19mwU+33ndC69T7weNNjQ==",
-      "cpu": [
-        "ia32"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "win32"
-      ]
-    },
-    "node_modules/@rollup/rollup-win32-x64-msvc": {
-      "version": "4.39.0",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.39.0.tgz",
-      "integrity": "sha512-yAkUOkIKZlK5dl7u6dg897doBgLXmUHhIINM2c+sND3DZwnrdQkkSiDh7N75Ll4mM4dxSkYfXqU9fW3lLkMFug==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "optional": true,
-      "os": [
-        "win32"
-      ]
-    },
-    "node_modules/@sand4rt/experimental-ct-web": {
-      "version": "1.51.1",
-      "resolved": "https://registry.npmjs.org/@sand4rt/experimental-ct-web/-/experimental-ct-web-1.51.1.tgz",
-      "integrity": "sha512-op81vdZY/WioUvwk1fqK9U2g8Fd/my+zTVSGaboQ5z2kGCfluZ49IcjNtgUWoNYjaTItoA2TLki4ryrhZOvDuA==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "@playwright/experimental-ct-core": "1.51.1"
-      },
-      "bin": {
-        "playwright": "cli.js"
-      },
-      "engines": {
-        "node": ">=18"
-      }
-    },
-    "node_modules/@tootallnate/quickjs-emscripten": {
-      "version": "0.23.0",
-      "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz",
-      "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==",
-      "dev": true
-    },
-    "node_modules/@types/accepts": {
-      "version": "1.3.7",
-      "resolved": "https://registry.npmjs.org/@types/accepts/-/accepts-1.3.7.tgz",
-      "integrity": "sha512-Pay9fq2lM2wXPWbteBsRAGiWH2hig4ZE2asK+mm7kUzlxRTfL961rj89I6zV/E3PcIkDqyuBEcMxFT7rccugeQ==",
-      "dev": true,
-      "dependencies": {
-        "@types/node": "*"
-      }
-    },
-    "node_modules/@types/babel__code-frame": {
-      "version": "7.0.6",
-      "resolved": "https://registry.npmjs.org/@types/babel__code-frame/-/babel__code-frame-7.0.6.tgz",
-      "integrity": "sha512-Anitqkl3+KrzcW2k77lRlg/GfLZLWXBuNgbEcIOU6M92yw42vsd3xV/Z/yAHEj8m+KUjL6bWOVOFqX8PFPJ4LA==",
-      "dev": true
-    },
-    "node_modules/@types/body-parser": {
-      "version": "1.19.5",
-      "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz",
-      "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==",
-      "dev": true,
-      "dependencies": {
-        "@types/connect": "*",
-        "@types/node": "*"
-      }
-    },
-    "node_modules/@types/co-body": {
-      "version": "6.1.3",
-      "resolved": "https://registry.npmjs.org/@types/co-body/-/co-body-6.1.3.tgz",
-      "integrity": "sha512-UhuhrQ5hclX6UJctv5m4Rfp52AfG9o9+d9/HwjxhVB5NjXxr5t9oKgJxN8xRHgr35oo8meUEHUPFWiKg6y71aA==",
-      "dev": true,
-      "dependencies": {
-        "@types/node": "*",
-        "@types/qs": "*"
-      }
-    },
-    "node_modules/@types/command-line-args": {
-      "version": "5.2.3",
-      "resolved": "https://registry.npmjs.org/@types/command-line-args/-/command-line-args-5.2.3.tgz",
-      "integrity": "sha512-uv0aG6R0Y8WHZLTamZwtfsDLVRnOa+n+n5rEvFWL5Na5gZ8V2Teab/duDPFzIIIhs9qizDpcavCusCLJZu62Kw==",
-      "dev": true
-    },
-    "node_modules/@types/connect": {
-      "version": "3.4.38",
-      "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
-      "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==",
-      "dev": true,
-      "dependencies": {
-        "@types/node": "*"
-      }
-    },
-    "node_modules/@types/content-disposition": {
-      "version": "0.5.8",
-      "resolved": "https://registry.npmjs.org/@types/content-disposition/-/content-disposition-0.5.8.tgz",
-      "integrity": "sha512-QVSSvno3dE0MgO76pJhmv4Qyi/j0Yk9pBp0Y7TJ2Tlj+KCgJWY6qX7nnxCOLkZ3VYRSIk1WTxCvwUSdx6CCLdg==",
-      "dev": true
-    },
-    "node_modules/@types/convert-source-map": {
-      "version": "2.0.3",
-      "resolved": "https://registry.npmjs.org/@types/convert-source-map/-/convert-source-map-2.0.3.tgz",
-      "integrity": "sha512-ag0BfJLZf6CQz8VIuRIEYQ5Ggwk/82uvTQf27RcpyDNbY0Vw49LIPqAxk5tqYfrCs9xDaIMvl4aj7ZopnYL8bA==",
-      "dev": true
-    },
-    "node_modules/@types/cookies": {
-      "version": "0.9.0",
-      "resolved": "https://registry.npmjs.org/@types/cookies/-/cookies-0.9.0.tgz",
-      "integrity": "sha512-40Zk8qR147RABiQ7NQnBzWzDcjKzNrntB5BAmeGCb2p/MIyOE+4BVvc17wumsUqUw00bJYqoXFHYygQnEFh4/Q==",
-      "dev": true,
-      "dependencies": {
-        "@types/connect": "*",
-        "@types/express": "*",
-        "@types/keygrip": "*",
-        "@types/node": "*"
-      }
-    },
-    "node_modules/@types/debounce": {
-      "version": "1.2.4",
-      "resolved": "https://registry.npmjs.org/@types/debounce/-/debounce-1.2.4.tgz",
-      "integrity": "sha512-jBqiORIzKDOToaF63Fm//haOCHuwQuLa2202RK4MozpA6lh93eCBc+/8+wZn5OzjJt3ySdc+74SXWXB55Ewtyw==",
-      "dev": true
-    },
-    "node_modules/@types/estree": {
-      "version": "1.0.7",
-      "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz",
-      "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==",
-      "license": "MIT"
-    },
-    "node_modules/@types/express": {
-      "version": "5.0.1",
-      "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.1.tgz",
-      "integrity": "sha512-UZUw8vjpWFXuDnjFTh7/5c2TWDlQqeXHi6hcN7F2XSVT5P+WmUnnbFS3KA6Jnc6IsEqI2qCVu2bK0R0J4A8ZQQ==",
-      "dev": true,
-      "dependencies": {
-        "@types/body-parser": "*",
-        "@types/express-serve-static-core": "^5.0.0",
-        "@types/serve-static": "*"
-      }
-    },
-    "node_modules/@types/express-serve-static-core": {
-      "version": "5.0.6",
-      "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.6.tgz",
-      "integrity": "sha512-3xhRnjJPkULekpSzgtoNYYcTWgEZkp4myc+Saevii5JPnHNvHMRlBSHDbs7Bh1iPPoVTERHEZXyhyLbMEsExsA==",
-      "dev": true,
-      "dependencies": {
-        "@types/node": "*",
-        "@types/qs": "*",
-        "@types/range-parser": "*",
-        "@types/send": "*"
-      }
-    },
-    "node_modules/@types/geojson": {
-      "version": "7946.0.4",
-      "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.4.tgz",
-      "integrity": "sha512-MHmwBtCb7OCv1DSivz2UNJXPGU/1btAWRKlqJ2saEhVJkpkvqHMMaOpKg0v4sAbDWSQekHGvPVMM8nQ+Jen03Q==",
-      "license": "MIT"
-    },
-    "node_modules/@types/http-assert": {
-      "version": "1.5.6",
-      "resolved": "https://registry.npmjs.org/@types/http-assert/-/http-assert-1.5.6.tgz",
-      "integrity": "sha512-TTEwmtjgVbYAzZYWyeHPrrtWnfVkm8tQkP8P21uQifPgMRgjrow3XDEYqucuC8SKZJT7pUnhU/JymvjggxO9vw==",
-      "dev": true
-    },
-    "node_modules/@types/http-errors": {
-      "version": "2.0.4",
-      "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz",
-      "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==",
-      "dev": true
-    },
-    "node_modules/@types/istanbul-lib-coverage": {
-      "version": "2.0.6",
-      "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
-      "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==",
-      "dev": true
-    },
-    "node_modules/@types/istanbul-lib-report": {
-      "version": "3.0.3",
-      "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz",
-      "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==",
-      "dev": true,
-      "dependencies": {
-        "@types/istanbul-lib-coverage": "*"
-      }
-    },
-    "node_modules/@types/istanbul-reports": {
-      "version": "3.0.4",
-      "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz",
-      "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==",
-      "dev": true,
-      "dependencies": {
-        "@types/istanbul-lib-report": "*"
-      }
-    },
-    "node_modules/@types/keygrip": {
-      "version": "1.0.6",
-      "resolved": "https://registry.npmjs.org/@types/keygrip/-/keygrip-1.0.6.tgz",
-      "integrity": "sha512-lZuNAY9xeJt7Bx4t4dx0rYCDqGPW8RXhQZK1td7d4H6E9zYbLoOtjBvfwdTKpsyxQI/2jv+armjX/RW+ZNpXOQ==",
-      "dev": true
-    },
-    "node_modules/@types/koa": {
-      "version": "2.15.0",
-      "resolved": "https://registry.npmjs.org/@types/koa/-/koa-2.15.0.tgz",
-      "integrity": "sha512-7QFsywoE5URbuVnG3loe03QXuGajrnotr3gQkXcEBShORai23MePfFYdhz90FEtBBpkyIYQbVD+evKtloCgX3g==",
-      "dev": true,
-      "dependencies": {
-        "@types/accepts": "*",
-        "@types/content-disposition": "*",
-        "@types/cookies": "*",
-        "@types/http-assert": "*",
-        "@types/http-errors": "*",
-        "@types/keygrip": "*",
-        "@types/koa-compose": "*",
-        "@types/node": "*"
-      }
-    },
-    "node_modules/@types/koa-compose": {
-      "version": "3.2.8",
-      "resolved": "https://registry.npmjs.org/@types/koa-compose/-/koa-compose-3.2.8.tgz",
-      "integrity": "sha512-4Olc63RY+MKvxMwVknCUDhRQX1pFQoBZ/lXcRLP69PQkEpze/0cr8LNqJQe5NFb/b19DWi2a5bTi2VAlQzhJuA==",
-      "dev": true,
-      "dependencies": {
-        "@types/koa": "*"
-      }
-    },
-    "node_modules/@types/marked": {
-      "version": "5.0.2",
-      "resolved": "https://registry.npmjs.org/@types/marked/-/marked-5.0.2.tgz",
-      "integrity": "sha512-OucS4KMHhFzhz27KxmWg7J+kIYqyqoW5kdIEI319hqARQQUTqhao3M/F+uFnDXD0Rg72iDDZxZNxq5gvctmLlg==",
-      "dev": true,
-      "license": "MIT"
-    },
-    "node_modules/@types/mime": {
-      "version": "1.3.5",
-      "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz",
-      "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==",
-      "dev": true
-    },
-    "node_modules/@types/mocha": {
-      "version": "10.0.10",
-      "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.10.tgz",
-      "integrity": "sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==",
-      "dev": true
-    },
-    "node_modules/@types/node": {
-      "version": "22.14.0",
-      "resolved": "https://registry.npmjs.org/@types/node/-/node-22.14.0.tgz",
-      "integrity": "sha512-Kmpl+z84ILoG+3T/zQFyAJsU6EPTmOCj8/2+83fSN6djd6I4o7uOuGIH6vq3PrjY5BGitSbFuMN18j3iknubbA==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "undici-types": "~6.21.0"
-      }
-    },
-    "node_modules/@types/parse5": {
-      "version": "6.0.3",
-      "resolved": "https://registry.npmjs.org/@types/parse5/-/parse5-6.0.3.tgz",
-      "integrity": "sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==",
-      "dev": true
-    },
-    "node_modules/@types/qs": {
-      "version": "6.9.18",
-      "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.18.tgz",
-      "integrity": "sha512-kK7dgTYDyGqS+e2Q4aK9X3D7q234CIZ1Bv0q/7Z5IwRDoADNU81xXJK/YVyLbLTZCoIwUoDoffFeF+p/eIklAA==",
-      "dev": true
-    },
-    "node_modules/@types/range-parser": {
-      "version": "1.2.7",
-      "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz",
-      "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==",
-      "dev": true
-    },
-    "node_modules/@types/resolve": {
-      "version": "1.20.2",
-      "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz",
-      "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==",
-      "dev": true
-    },
-    "node_modules/@types/send": {
-      "version": "0.17.4",
-      "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz",
-      "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==",
-      "dev": true,
-      "dependencies": {
-        "@types/mime": "^1",
-        "@types/node": "*"
-      }
-    },
-    "node_modules/@types/serve-static": {
-      "version": "1.15.7",
-      "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz",
-      "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==",
-      "dev": true,
-      "dependencies": {
-        "@types/http-errors": "*",
-        "@types/node": "*",
-        "@types/send": "*"
-      }
-    },
-    "node_modules/@types/trusted-types": {
-      "version": "2.0.7",
-      "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
-      "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
-      "license": "MIT"
-    },
-    "node_modules/@types/ws": {
-      "version": "7.4.7",
-      "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.4.7.tgz",
-      "integrity": "sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww==",
-      "dev": true,
-      "dependencies": {
-        "@types/node": "*"
-      }
-    },
-    "node_modules/@types/yauzl": {
-      "version": "2.10.3",
-      "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz",
-      "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==",
-      "dev": true,
-      "optional": true,
-      "dependencies": {
-        "@types/node": "*"
-      }
-    },
-    "node_modules/@web/browser-logs": {
-      "version": "0.4.1",
-      "resolved": "https://registry.npmjs.org/@web/browser-logs/-/browser-logs-0.4.1.tgz",
-      "integrity": "sha512-ypmMG+72ERm+LvP+loj9A64MTXvWMXHUOu773cPO4L1SV/VWg6xA9Pv7vkvkXQX+ItJtCJt+KQ+U6ui2HhSFUw==",
-      "dev": true,
-      "dependencies": {
-        "errorstacks": "^2.4.1"
-      },
-      "engines": {
-        "node": ">=18.0.0"
-      }
-    },
-    "node_modules/@web/config-loader": {
-      "version": "0.3.3",
-      "resolved": "https://registry.npmjs.org/@web/config-loader/-/config-loader-0.3.3.tgz",
-      "integrity": "sha512-ilzeQzrPpPLWZhzFCV+4doxKDGm7oKVfdKpW9wiUNVgive34NSzCw+WzXTvjE4Jgr5CkyTDIObEmMrqQEjhT0g==",
-      "dev": true,
-      "engines": {
-        "node": ">=18.0.0"
-      }
-    },
-    "node_modules/@web/dev-server": {
-      "version": "0.4.6",
-      "resolved": "https://registry.npmjs.org/@web/dev-server/-/dev-server-0.4.6.tgz",
-      "integrity": "sha512-jj/1bcElAy5EZet8m2CcUdzxT+CRvUjIXGh8Lt7vxtthkN9PzY9wlhWx/9WOs5iwlnG1oj0VGo6f/zvbPO0s9w==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "@babel/code-frame": "^7.12.11",
-        "@types/command-line-args": "^5.0.0",
-        "@web/config-loader": "^0.3.0",
-        "@web/dev-server-core": "^0.7.2",
-        "@web/dev-server-rollup": "^0.6.1",
-        "camelcase": "^6.2.0",
-        "command-line-args": "^5.1.1",
-        "command-line-usage": "^7.0.1",
-        "debounce": "^1.2.0",
-        "deepmerge": "^4.2.2",
-        "internal-ip": "^6.2.0",
-        "nanocolors": "^0.2.1",
-        "open": "^8.0.2",
-        "portfinder": "^1.0.32"
-      },
-      "bin": {
-        "wds": "dist/bin.js",
-        "web-dev-server": "dist/bin.js"
-      },
-      "engines": {
-        "node": ">=18.0.0"
-      }
-    },
-    "node_modules/@web/dev-server-core": {
-      "version": "0.7.5",
-      "resolved": "https://registry.npmjs.org/@web/dev-server-core/-/dev-server-core-0.7.5.tgz",
-      "integrity": "sha512-Da65zsiN6iZPMRuj4Oa6YPwvsmZmo5gtPWhW2lx3GTUf5CAEapjVpZVlUXnKPL7M7zRuk72jSsIl8lo+XpTCtw==",
-      "dev": true,
-      "dependencies": {
-        "@types/koa": "^2.11.6",
-        "@types/ws": "^7.4.0",
-        "@web/parse5-utils": "^2.1.0",
-        "chokidar": "^4.0.1",
-        "clone": "^2.1.2",
-        "es-module-lexer": "^1.0.0",
-        "get-stream": "^6.0.0",
-        "is-stream": "^2.0.0",
-        "isbinaryfile": "^5.0.0",
-        "koa": "^2.13.0",
-        "koa-etag": "^4.0.0",
-        "koa-send": "^5.0.1",
-        "koa-static": "^5.0.0",
-        "lru-cache": "^8.0.4",
-        "mime-types": "^2.1.27",
-        "parse5": "^6.0.1",
-        "picomatch": "^2.2.2",
-        "ws": "^7.5.10"
-      },
-      "engines": {
-        "node": ">=18.0.0"
-      }
-    },
-    "node_modules/@web/dev-server-core/node_modules/chokidar": {
-      "version": "4.0.3",
-      "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
-      "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
-      "dev": true,
-      "dependencies": {
-        "readdirp": "^4.0.1"
-      },
-      "engines": {
-        "node": ">= 14.16.0"
-      },
-      "funding": {
-        "url": "https://paulmillr.com/funding/"
-      }
-    },
-    "node_modules/@web/dev-server-core/node_modules/lru-cache": {
-      "version": "8.0.5",
-      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-8.0.5.tgz",
-      "integrity": "sha512-MhWWlVnuab1RG5/zMRRcVGXZLCXrZTgfwMikgzCegsPnG62yDQo5JnqKkrK4jO5iKqDAZGItAqN5CtKBCBWRUA==",
-      "dev": true,
-      "engines": {
-        "node": ">=16.14"
-      }
-    },
-    "node_modules/@web/dev-server-core/node_modules/readdirp": {
-      "version": "4.1.2",
-      "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
-      "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
-      "dev": true,
-      "engines": {
-        "node": ">= 14.18.0"
-      },
-      "funding": {
-        "type": "individual",
-        "url": "https://paulmillr.com/funding/"
-      }
-    },
-    "node_modules/@web/dev-server-rollup": {
-      "version": "0.6.4",
-      "resolved": "https://registry.npmjs.org/@web/dev-server-rollup/-/dev-server-rollup-0.6.4.tgz",
-      "integrity": "sha512-sJZfTGCCrdku5xYnQQG51odGI092hKY9YFM0X3Z0tRY3iXKXcYRaLZrErw5KfCxr6g0JRuhe4BBhqXTA5Q2I3Q==",
-      "dev": true,
-      "dependencies": {
-        "@rollup/plugin-node-resolve": "^15.0.1",
-        "@web/dev-server-core": "^0.7.2",
-        "nanocolors": "^0.2.1",
-        "parse5": "^6.0.1",
-        "rollup": "^4.4.0",
-        "whatwg-url": "^14.0.0"
-      },
-      "engines": {
-        "node": ">=18.0.0"
-      }
-    },
-    "node_modules/@web/dev-server-rollup/node_modules/tr46": {
-      "version": "5.1.0",
-      "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.0.tgz",
-      "integrity": "sha512-IUWnUK7ADYR5Sl1fZlO1INDUhVhatWl7BtJWsIhwJ0UAK7ilzzIa8uIqOO/aYVWHZPJkKbEL+362wrzoeRF7bw==",
-      "dev": true,
-      "dependencies": {
-        "punycode": "^2.3.1"
-      },
-      "engines": {
-        "node": ">=18"
-      }
-    },
-    "node_modules/@web/dev-server-rollup/node_modules/webidl-conversions": {
-      "version": "7.0.0",
-      "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
-      "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
-      "dev": true,
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/@web/dev-server-rollup/node_modules/whatwg-url": {
-      "version": "14.2.0",
-      "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz",
-      "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==",
-      "dev": true,
-      "dependencies": {
-        "tr46": "^5.1.0",
-        "webidl-conversions": "^7.0.0"
-      },
-      "engines": {
-        "node": ">=18"
-      }
-    },
-    "node_modules/@web/parse5-utils": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/@web/parse5-utils/-/parse5-utils-2.1.0.tgz",
-      "integrity": "sha512-GzfK5disEJ6wEjoPwx8AVNwUe9gYIiwc+x//QYxYDAFKUp4Xb1OJAGLc2l2gVrSQmtPGLKrTRcW90Hv4pEq1qA==",
-      "dev": true,
-      "dependencies": {
-        "@types/parse5": "^6.0.1",
-        "parse5": "^6.0.1"
-      },
-      "engines": {
-        "node": ">=18.0.0"
-      }
-    },
-    "node_modules/@web/test-runner": {
-      "version": "0.18.3",
-      "resolved": "https://registry.npmjs.org/@web/test-runner/-/test-runner-0.18.3.tgz",
-      "integrity": "sha512-QkVK8Qguw3Zhyu8SYR7F4VdcjyXBeJNr8W8L++s4zO/Ok7DR/Wu7+rLswn3H7OH3xYoCHRmwteehcFejefz6ew==",
-      "dev": true,
-      "dependencies": {
-        "@web/browser-logs": "^0.4.0",
-        "@web/config-loader": "^0.3.0",
-        "@web/dev-server": "^0.4.0",
-        "@web/test-runner-chrome": "^0.16.0",
-        "@web/test-runner-commands": "^0.9.0",
-        "@web/test-runner-core": "^0.13.0",
-        "@web/test-runner-mocha": "^0.9.0",
-        "camelcase": "^6.2.0",
-        "command-line-args": "^5.1.1",
-        "command-line-usage": "^7.0.1",
-        "convert-source-map": "^2.0.0",
-        "diff": "^5.0.0",
-        "globby": "^11.0.1",
-        "nanocolors": "^0.2.1",
-        "portfinder": "^1.0.32",
-        "source-map": "^0.7.3"
-      },
-      "bin": {
-        "web-test-runner": "dist/bin.js",
-        "wtr": "dist/bin.js"
-      },
-      "engines": {
-        "node": ">=18.0.0"
-      }
-    },
-    "node_modules/@web/test-runner-chrome": {
-      "version": "0.16.0",
-      "resolved": "https://registry.npmjs.org/@web/test-runner-chrome/-/test-runner-chrome-0.16.0.tgz",
-      "integrity": "sha512-Edc6Y49aVB6k18S5IOj9OCX3rEf8F3jptIu0p95+imqxmcutFEh1GNmlAk2bQGnXS0U6uVY7Xbf61fiaXUQqhg==",
-      "dev": true,
-      "dependencies": {
-        "@web/test-runner-core": "^0.13.0",
-        "@web/test-runner-coverage-v8": "^0.8.0",
-        "async-mutex": "0.4.0",
-        "chrome-launcher": "^0.15.0",
-        "puppeteer-core": "^22.0.0"
-      },
-      "engines": {
-        "node": ">=18.0.0"
-      }
-    },
-    "node_modules/@web/test-runner-commands": {
-      "version": "0.9.0",
-      "resolved": "https://registry.npmjs.org/@web/test-runner-commands/-/test-runner-commands-0.9.0.tgz",
-      "integrity": "sha512-zeLI6QdH0jzzJMDV5O42Pd8WLJtYqovgdt0JdytgHc0d1EpzXDsc7NTCJSImboc2NcayIsWAvvGGeRF69SMMYg==",
-      "dev": true,
-      "dependencies": {
-        "@web/test-runner-core": "^0.13.0",
-        "mkdirp": "^1.0.4"
-      },
-      "engines": {
-        "node": ">=18.0.0"
-      }
-    },
-    "node_modules/@web/test-runner-commands/node_modules/mkdirp": {
-      "version": "1.0.4",
-      "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
-      "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
-      "dev": true,
-      "bin": {
-        "mkdirp": "bin/cmd.js"
-      },
-      "engines": {
-        "node": ">=10"
-      }
-    },
-    "node_modules/@web/test-runner-core": {
-      "version": "0.13.4",
-      "resolved": "https://registry.npmjs.org/@web/test-runner-core/-/test-runner-core-0.13.4.tgz",
-      "integrity": "sha512-84E1025aUSjvZU1j17eCTwV7m5Zg3cZHErV3+CaJM9JPCesZwLraIa0ONIQ9w4KLgcDgJFw9UnJ0LbFf42h6tg==",
-      "dev": true,
-      "dependencies": {
-        "@babel/code-frame": "^7.12.11",
-        "@types/babel__code-frame": "^7.0.2",
-        "@types/co-body": "^6.1.0",
-        "@types/convert-source-map": "^2.0.0",
-        "@types/debounce": "^1.2.0",
-        "@types/istanbul-lib-coverage": "^2.0.3",
-        "@types/istanbul-reports": "^3.0.0",
-        "@web/browser-logs": "^0.4.0",
-        "@web/dev-server-core": "^0.7.3",
-        "chokidar": "^4.0.1",
-        "cli-cursor": "^3.1.0",
-        "co-body": "^6.1.0",
-        "convert-source-map": "^2.0.0",
-        "debounce": "^1.2.0",
-        "dependency-graph": "^0.11.0",
-        "globby": "^11.0.1",
-        "internal-ip": "^6.2.0",
-        "istanbul-lib-coverage": "^3.0.0",
-        "istanbul-lib-report": "^3.0.1",
-        "istanbul-reports": "^3.0.2",
-        "log-update": "^4.0.0",
-        "nanocolors": "^0.2.1",
-        "nanoid": "^3.1.25",
-        "open": "^8.0.2",
-        "picomatch": "^2.2.2",
-        "source-map": "^0.7.3"
-      },
-      "engines": {
-        "node": ">=18.0.0"
-      }
-    },
-    "node_modules/@web/test-runner-core/node_modules/chokidar": {
-      "version": "4.0.3",
-      "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
-      "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
-      "dev": true,
-      "dependencies": {
-        "readdirp": "^4.0.1"
-      },
-      "engines": {
-        "node": ">= 14.16.0"
-      },
-      "funding": {
-        "url": "https://paulmillr.com/funding/"
-      }
-    },
-    "node_modules/@web/test-runner-core/node_modules/dependency-graph": {
-      "version": "0.11.0",
-      "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.11.0.tgz",
-      "integrity": "sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==",
-      "dev": true,
-      "engines": {
-        "node": ">= 0.6.0"
-      }
-    },
-    "node_modules/@web/test-runner-core/node_modules/readdirp": {
-      "version": "4.1.2",
-      "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
-      "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
-      "dev": true,
-      "engines": {
-        "node": ">= 14.18.0"
-      },
-      "funding": {
-        "type": "individual",
-        "url": "https://paulmillr.com/funding/"
-      }
-    },
-    "node_modules/@web/test-runner-coverage-v8": {
-      "version": "0.8.0",
-      "resolved": "https://registry.npmjs.org/@web/test-runner-coverage-v8/-/test-runner-coverage-v8-0.8.0.tgz",
-      "integrity": "sha512-PskiucYpjUtgNfR2zF2AWqWwjXL7H3WW/SnCAYmzUrtob7X9o/+BjdyZ4wKbOxWWSbJO4lEdGIDLu+8X2Xw+lA==",
-      "dev": true,
-      "dependencies": {
-        "@web/test-runner-core": "^0.13.0",
-        "istanbul-lib-coverage": "^3.0.0",
-        "lru-cache": "^8.0.4",
-        "picomatch": "^2.2.2",
-        "v8-to-istanbul": "^9.0.1"
-      },
-      "engines": {
-        "node": ">=18.0.0"
-      }
-    },
-    "node_modules/@web/test-runner-coverage-v8/node_modules/lru-cache": {
-      "version": "8.0.5",
-      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-8.0.5.tgz",
-      "integrity": "sha512-MhWWlVnuab1RG5/zMRRcVGXZLCXrZTgfwMikgzCegsPnG62yDQo5JnqKkrK4jO5iKqDAZGItAqN5CtKBCBWRUA==",
-      "dev": true,
-      "engines": {
-        "node": ">=16.14"
-      }
-    },
-    "node_modules/@web/test-runner-mocha": {
-      "version": "0.9.0",
-      "resolved": "https://registry.npmjs.org/@web/test-runner-mocha/-/test-runner-mocha-0.9.0.tgz",
-      "integrity": "sha512-ZL9F6FXd0DBQvo/h/+mSfzFTSRVxzV9st/AHhpgABtUtV/AIpVE9to6+xdkpu6827kwjezdpuadPfg+PlrBWqQ==",
-      "dev": true,
-      "dependencies": {
-        "@web/test-runner-core": "^0.13.0"
-      },
-      "engines": {
-        "node": ">=18.0.0"
-      }
-    },
-    "node_modules/@web/test-runner-puppeteer": {
-      "version": "0.18.0",
-      "resolved": "https://registry.npmjs.org/@web/test-runner-puppeteer/-/test-runner-puppeteer-0.18.0.tgz",
-      "integrity": "sha512-pc0gADGjqflSRIZQehwD9INKUY1DZ92eWJwuVwZXzKxr5soniT/Bknv2hT5ttpJ5P7ILuCyjJyq5IjKzPBFUXg==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "@web/test-runner-chrome": "^0.18.0",
-        "@web/test-runner-core": "^0.13.0",
-        "puppeteer": "^24.0.0"
-      },
-      "engines": {
-        "node": ">=18.0.0"
-      }
-    },
-    "node_modules/@web/test-runner-puppeteer/node_modules/@puppeteer/browsers": {
-      "version": "2.10.0",
-      "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.10.0.tgz",
-      "integrity": "sha512-HdHF4rny4JCvIcm7V1dpvpctIGqM3/Me255CB44vW7hDG1zYMmcBMjpNqZEDxdCfXGLkx5kP0+Jz5DUS+ukqtA==",
-      "dev": true,
-      "license": "Apache-2.0",
-      "dependencies": {
-        "debug": "^4.4.0",
-        "extract-zip": "^2.0.1",
-        "progress": "^2.0.3",
-        "proxy-agent": "^6.5.0",
-        "semver": "^7.7.1",
-        "tar-fs": "^3.0.8",
-        "yargs": "^17.7.2"
-      },
-      "bin": {
-        "browsers": "lib/cjs/main-cli.js"
-      },
-      "engines": {
-        "node": ">=18"
-      }
-    },
-    "node_modules/@web/test-runner-puppeteer/node_modules/@web/test-runner-chrome": {
-      "version": "0.18.1",
-      "resolved": "https://registry.npmjs.org/@web/test-runner-chrome/-/test-runner-chrome-0.18.1.tgz",
-      "integrity": "sha512-eO6ctCaqSguGM6G3cFobGHnrEs9wlv9Juj/Akyr4XLjeEMTheNULdvOXw9Bygi+QC/ir/0snMmt+/YKnfy8rYA==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "@web/test-runner-core": "^0.13.0",
-        "@web/test-runner-coverage-v8": "^0.8.0",
-        "chrome-launcher": "^0.15.0",
-        "puppeteer-core": "^24.0.0"
-      },
-      "engines": {
-        "node": ">=18.0.0"
-      }
-    },
-    "node_modules/@web/test-runner-puppeteer/node_modules/chromium-bidi": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-3.0.0.tgz",
-      "integrity": "sha512-ZOGRDAhBMX1uxL2Cm2TDuhImbrsEz5A/tTcVU6RpXEWaTNUNwsHW6njUXizh51Ir6iqHbKAfhA2XK33uBcLo5A==",
-      "dev": true,
-      "license": "Apache-2.0",
-      "dependencies": {
-        "mitt": "^3.0.1",
-        "zod": "^3.24.1"
-      },
-      "peerDependencies": {
-        "devtools-protocol": "*"
-      }
-    },
-    "node_modules/@web/test-runner-puppeteer/node_modules/devtools-protocol": {
-      "version": "0.0.1425554",
-      "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1425554.tgz",
-      "integrity": "sha512-uRfxR6Nlzdzt0ihVIkV+sLztKgs7rgquY/Mhcv1YNCWDh5IZgl5mnn2aeEnW5stYTE0wwiF4RYVz8eMEpV1SEw==",
-      "dev": true,
-      "license": "BSD-3-Clause"
-    },
-    "node_modules/@web/test-runner-puppeteer/node_modules/puppeteer-core": {
-      "version": "24.6.1",
-      "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.6.1.tgz",
-      "integrity": "sha512-sMCxsY+OPWO2fecBrhIeCeJbWWXJ6UaN997sTid6whY0YT9XM0RnxEwLeUibluIS5/fRmuxe1efjb5RMBsky7g==",
-      "dev": true,
-      "license": "Apache-2.0",
-      "dependencies": {
-        "@puppeteer/browsers": "2.10.0",
-        "chromium-bidi": "3.0.0",
-        "debug": "^4.4.0",
-        "devtools-protocol": "0.0.1425554",
-        "typed-query-selector": "^2.12.0",
-        "ws": "^8.18.1"
-      },
-      "engines": {
-        "node": ">=18"
-      }
-    },
-    "node_modules/@web/test-runner-puppeteer/node_modules/ws": {
-      "version": "8.18.1",
-      "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.1.tgz",
-      "integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==",
-      "dev": true,
-      "license": "MIT",
-      "engines": {
-        "node": ">=10.0.0"
-      },
-      "peerDependencies": {
-        "bufferutil": "^4.0.1",
-        "utf-8-validate": ">=5.0.2"
-      },
-      "peerDependenciesMeta": {
-        "bufferutil": {
-          "optional": true
-        },
-        "utf-8-validate": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/@web/test-runner-puppeteer/node_modules/zod": {
-      "version": "3.24.2",
-      "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.2.tgz",
-      "integrity": "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==",
-      "dev": true,
-      "license": "MIT",
-      "funding": {
-        "url": "https://github.com/sponsors/colinhacks"
-      }
-    },
-    "node_modules/@web/test-runner/node_modules/diff": {
-      "version": "5.2.0",
-      "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz",
-      "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==",
-      "dev": true,
-      "engines": {
-        "node": ">=0.3.1"
-      }
-    },
-    "node_modules/@xterm/addon-fit": {
-      "version": "0.10.0",
-      "resolved": "https://registry.npmjs.org/@xterm/addon-fit/-/addon-fit-0.10.0.tgz",
-      "integrity": "sha512-UFYkDm4HUahf2lnEyHvio51TNGiLK66mqP2JoATy7hRZeXaGMRDr00JiSF7m63vR5WKATF605yEggJKsw0JpMQ==",
-      "license": "MIT",
-      "peerDependencies": {
-        "@xterm/xterm": "^5.0.0"
-      }
-    },
-    "node_modules/@xterm/xterm": {
-      "version": "5.5.0",
-      "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.5.0.tgz",
-      "integrity": "sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==",
-      "license": "MIT"
-    },
-    "node_modules/abbrev": {
-      "version": "1.1.1",
-      "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
-      "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
-      "license": "ISC"
-    },
-    "node_modules/accepts": {
-      "version": "1.3.8",
-      "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
-      "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
-      "dev": true,
-      "dependencies": {
-        "mime-types": "~2.1.34",
-        "negotiator": "0.6.3"
-      },
-      "engines": {
-        "node": ">= 0.6"
-      }
-    },
-    "node_modules/agent-base": {
-      "version": "7.1.3",
-      "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz",
-      "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==",
-      "dev": true,
-      "engines": {
-        "node": ">= 14"
-      }
-    },
-    "node_modules/ansi-escapes": {
-      "version": "4.3.2",
-      "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
-      "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==",
-      "dev": true,
-      "dependencies": {
-        "type-fest": "^0.21.3"
-      },
-      "engines": {
-        "node": ">=8"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
-      }
-    },
-    "node_modules/argparse": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
-      "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
-      "dev": true,
-      "license": "Python-2.0"
-    },
-    "node_modules/array-back": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz",
-      "integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==",
-      "dev": true,
-      "engines": {
-        "node": ">=6"
-      }
-    },
-    "node_modules/array-union": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
-      "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
-      "dev": true,
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/ast-types": {
-      "version": "0.13.4",
-      "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz",
-      "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==",
-      "dev": true,
-      "dependencies": {
-        "tslib": "^2.0.1"
-      },
-      "engines": {
-        "node": ">=4"
-      }
-    },
-    "node_modules/astral-regex": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz",
-      "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==",
-      "dev": true,
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/async": {
-      "version": "3.2.6",
-      "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
-      "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==",
-      "dev": true
-    },
-    "node_modules/async-mutex": {
-      "version": "0.4.0",
-      "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.4.0.tgz",
-      "integrity": "sha512-eJFZ1YhRR8UN8eBLoNzcDPcy/jqjsg6I1AP+KvWQX80BqOSW1oJPJXDylPUEeMr2ZQvHgnQ//Lp6f3RQ1zI7HA==",
-      "dev": true,
-      "dependencies": {
-        "tslib": "^2.4.0"
-      }
-    },
-    "node_modules/autoprefixer": {
-      "version": "10.4.21",
-      "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz",
-      "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==",
-      "dev": true,
-      "funding": [
-        {
-          "type": "opencollective",
-          "url": "https://opencollective.com/postcss/"
-        },
-        {
-          "type": "tidelift",
-          "url": "https://tidelift.com/funding/github/npm/autoprefixer"
-        },
-        {
-          "type": "github",
-          "url": "https://github.com/sponsors/ai"
-        }
-      ],
-      "license": "MIT",
-      "dependencies": {
-        "browserslist": "^4.24.4",
-        "caniuse-lite": "^1.0.30001702",
-        "fraction.js": "^4.3.7",
-        "normalize-range": "^0.1.2",
-        "picocolors": "^1.1.1",
-        "postcss-value-parser": "^4.2.0"
-      },
-      "bin": {
-        "autoprefixer": "bin/autoprefixer"
-      },
-      "engines": {
-        "node": "^10 || ^12 || >=14"
-      },
-      "peerDependencies": {
-        "postcss": "^8.1.0"
-      }
-    },
-    "node_modules/b4a": {
-      "version": "1.6.7",
-      "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.7.tgz",
-      "integrity": "sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==",
-      "dev": true
-    },
-    "node_modules/bare-events": {
-      "version": "2.5.4",
-      "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.5.4.tgz",
-      "integrity": "sha512-+gFfDkR8pj4/TrWCGUGWmJIkBwuxPS5F+a5yWjOHQt2hHvNZd5YLzadjmDUtFmMM4y429bnKLa8bYBMHcYdnQA==",
-      "dev": true,
-      "optional": true
-    },
-    "node_modules/bare-fs": {
-      "version": "4.1.2",
-      "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.1.2.tgz",
-      "integrity": "sha512-8wSeOia5B7LwD4+h465y73KOdj5QHsbbuoUfPBi+pXgFJIPuG7SsiOdJuijWMyfid49eD+WivpfY7KT8gbAzBA==",
-      "dev": true,
-      "optional": true,
-      "dependencies": {
-        "bare-events": "^2.5.4",
-        "bare-path": "^3.0.0",
-        "bare-stream": "^2.6.4"
-      },
-      "engines": {
-        "bare": ">=1.16.0"
-      },
-      "peerDependencies": {
-        "bare-buffer": "*"
-      },
-      "peerDependenciesMeta": {
-        "bare-buffer": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/bare-os": {
-      "version": "3.6.1",
-      "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.6.1.tgz",
-      "integrity": "sha512-uaIjxokhFidJP+bmmvKSgiMzj2sV5GPHaZVAIktcxcpCyBFFWO+YlikVAdhmUo2vYFvFhOXIAlldqV29L8126g==",
-      "dev": true,
-      "optional": true,
-      "engines": {
-        "bare": ">=1.14.0"
-      }
-    },
-    "node_modules/bare-path": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz",
-      "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==",
-      "dev": true,
-      "optional": true,
-      "dependencies": {
-        "bare-os": "^3.0.1"
-      }
-    },
-    "node_modules/bare-stream": {
-      "version": "2.6.5",
-      "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.6.5.tgz",
-      "integrity": "sha512-jSmxKJNJmHySi6hC42zlZnq00rga4jjxcgNZjY9N5WlOe/iOoGRtdwGsHzQv2RlH2KOYMwGUXhf2zXd32BA9RA==",
-      "dev": true,
-      "optional": true,
-      "dependencies": {
-        "streamx": "^2.21.0"
-      },
-      "peerDependencies": {
-        "bare-buffer": "*",
-        "bare-events": "*"
-      },
-      "peerDependenciesMeta": {
-        "bare-buffer": {
-          "optional": true
-        },
-        "bare-events": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/base64-js": {
-      "version": "1.5.1",
-      "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
-      "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
-      "dev": true,
-      "funding": [
-        {
-          "type": "github",
-          "url": "https://github.com/sponsors/feross"
-        },
-        {
-          "type": "patreon",
-          "url": "https://www.patreon.com/feross"
-        },
-        {
-          "type": "consulting",
-          "url": "https://feross.org/support"
-        }
-      ]
-    },
-    "node_modules/basic-ftp": {
-      "version": "5.0.5",
-      "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz",
-      "integrity": "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==",
-      "dev": true,
-      "engines": {
-        "node": ">=10.0.0"
-      }
-    },
-    "node_modules/braces": {
-      "version": "3.0.3",
-      "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
-      "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "fill-range": "^7.1.1"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/browserslist": {
-      "version": "4.24.4",
-      "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz",
-      "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==",
-      "dev": true,
-      "funding": [
-        {
-          "type": "opencollective",
-          "url": "https://opencollective.com/browserslist"
-        },
-        {
-          "type": "tidelift",
-          "url": "https://tidelift.com/funding/github/npm/browserslist"
-        },
-        {
-          "type": "github",
-          "url": "https://github.com/sponsors/ai"
-        }
-      ],
-      "license": "MIT",
-      "dependencies": {
-        "caniuse-lite": "^1.0.30001688",
-        "electron-to-chromium": "^1.5.73",
-        "node-releases": "^2.0.19",
-        "update-browserslist-db": "^1.1.1"
-      },
-      "bin": {
-        "browserslist": "cli.js"
-      },
-      "engines": {
-        "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
-      }
-    },
-    "node_modules/buffer": {
-      "version": "5.7.1",
-      "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
-      "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
-      "dev": true,
-      "funding": [
-        {
-          "type": "github",
-          "url": "https://github.com/sponsors/feross"
-        },
-        {
-          "type": "patreon",
-          "url": "https://www.patreon.com/feross"
-        },
-        {
-          "type": "consulting",
-          "url": "https://feross.org/support"
-        }
-      ],
-      "dependencies": {
-        "base64-js": "^1.3.1",
-        "ieee754": "^1.1.13"
-      }
-    },
-    "node_modules/buffer-crc32": {
-      "version": "0.2.13",
-      "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
-      "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==",
-      "dev": true,
-      "engines": {
-        "node": "*"
-      }
-    },
-    "node_modules/bytes": {
-      "version": "3.1.2",
-      "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
-      "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
-      "dev": true,
-      "engines": {
-        "node": ">= 0.8"
-      }
-    },
-    "node_modules/cache-content-type": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/cache-content-type/-/cache-content-type-1.0.1.tgz",
-      "integrity": "sha512-IKufZ1o4Ut42YUrZSo8+qnMTrFuKkvyoLXUywKz9GJ5BrhOFGhLdkx9sG4KAnVvbY6kEcSFjLQul+DVmBm2bgA==",
-      "dev": true,
-      "dependencies": {
-        "mime-types": "^2.1.18",
-        "ylru": "^1.2.0"
-      },
-      "engines": {
-        "node": ">= 6.0.0"
-      }
-    },
-    "node_modules/call-bind-apply-helpers": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
-      "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
-      "dev": true,
-      "dependencies": {
-        "es-errors": "^1.3.0",
-        "function-bind": "^1.1.2"
-      },
-      "engines": {
-        "node": ">= 0.4"
-      }
-    },
-    "node_modules/call-bound": {
-      "version": "1.0.4",
-      "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
-      "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
-      "dev": true,
-      "dependencies": {
-        "call-bind-apply-helpers": "^1.0.2",
-        "get-intrinsic": "^1.3.0"
-      },
-      "engines": {
-        "node": ">= 0.4"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/callsites": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
-      "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
-      "dev": true,
-      "license": "MIT",
-      "engines": {
-        "node": ">=6"
-      }
-    },
-    "node_modules/camelcase": {
-      "version": "6.3.0",
-      "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
-      "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
-      "dev": true,
-      "engines": {
-        "node": ">=10"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
-      }
-    },
-    "node_modules/caniuse-lite": {
-      "version": "1.0.30001712",
-      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001712.tgz",
-      "integrity": "sha512-MBqPpGYYdQ7/hfKiet9SCI+nmN5/hp4ZzveOJubl5DTAMa5oggjAuoi0Z4onBpKPFI2ePGnQuQIzF3VxDjDJig==",
-      "dev": true,
-      "funding": [
-        {
-          "type": "opencollective",
-          "url": "https://opencollective.com/browserslist"
-        },
-        {
-          "type": "tidelift",
-          "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
-        },
-        {
-          "type": "github",
-          "url": "https://github.com/sponsors/ai"
-        }
-      ],
-      "license": "CC-BY-4.0"
-    },
-    "node_modules/chalk": {
-      "version": "4.1.2",
-      "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
-      "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
-      "dev": true,
-      "dependencies": {
-        "ansi-styles": "^4.1.0",
-        "supports-color": "^7.1.0"
-      },
-      "engines": {
-        "node": ">=10"
-      },
-      "funding": {
-        "url": "https://github.com/chalk/chalk?sponsor=1"
-      }
-    },
-    "node_modules/chalk-template": {
-      "version": "0.4.0",
-      "resolved": "https://registry.npmjs.org/chalk-template/-/chalk-template-0.4.0.tgz",
-      "integrity": "sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg==",
-      "dev": true,
-      "dependencies": {
-        "chalk": "^4.1.2"
-      },
-      "engines": {
-        "node": ">=12"
-      },
-      "funding": {
-        "url": "https://github.com/chalk/chalk-template?sponsor=1"
-      }
-    },
-    "node_modules/chalk/node_modules/ansi-styles": {
-      "version": "4.3.0",
-      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
-      "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
-      "dev": true,
-      "dependencies": {
-        "color-convert": "^2.0.1"
-      },
-      "engines": {
-        "node": ">=8"
-      },
-      "funding": {
-        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
-      }
-    },
-    "node_modules/chrome-launcher": {
-      "version": "0.15.2",
-      "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.15.2.tgz",
-      "integrity": "sha512-zdLEwNo3aUVzIhKhTtXfxhdvZhUghrnmkvcAq2NoDd+LeOHKf03H5jwZ8T/STsAlzyALkBVK552iaG1fGf1xVQ==",
-      "dev": true,
-      "dependencies": {
-        "@types/node": "*",
-        "escape-string-regexp": "^4.0.0",
-        "is-wsl": "^2.2.0",
-        "lighthouse-logger": "^1.0.0"
-      },
-      "bin": {
-        "print-chrome-path": "bin/print-chrome-path.js"
-      },
-      "engines": {
-        "node": ">=12.13.0"
-      }
-    },
-    "node_modules/chromium-bidi": {
-      "version": "0.6.3",
-      "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.6.3.tgz",
-      "integrity": "sha512-qXlsCmpCZJAnoTYI83Iu6EdYQpMYdVkCfq08KDh2pmlVqK5t5IA9mGs4/LwCwp4fqisSOMXZxP3HIh8w8aRn0A==",
-      "dev": true,
-      "dependencies": {
-        "mitt": "3.0.1",
-        "urlpattern-polyfill": "10.0.0",
-        "zod": "3.23.8"
-      },
-      "peerDependencies": {
-        "devtools-protocol": "*"
-      }
-    },
-    "node_modules/cli-cursor": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz",
-      "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==",
-      "dev": true,
-      "dependencies": {
-        "restore-cursor": "^3.1.0"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/cliui": {
-      "version": "8.0.1",
-      "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
-      "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
-      "license": "ISC",
-      "dependencies": {
-        "string-width": "^4.2.0",
-        "strip-ansi": "^6.0.1",
-        "wrap-ansi": "^7.0.0"
-      },
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/cliui/node_modules/ansi-regex": {
-      "version": "5.0.1",
-      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
-      "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
-      "license": "MIT",
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/cliui/node_modules/ansi-styles": {
-      "version": "4.3.0",
-      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
-      "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
-      "license": "MIT",
-      "dependencies": {
-        "color-convert": "^2.0.1"
-      },
-      "engines": {
-        "node": ">=8"
-      },
-      "funding": {
-        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
-      }
-    },
-    "node_modules/cliui/node_modules/emoji-regex": {
-      "version": "8.0.0",
-      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
-      "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
-      "license": "MIT"
-    },
-    "node_modules/cliui/node_modules/string-width": {
-      "version": "4.2.3",
-      "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
-      "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
-      "license": "MIT",
-      "dependencies": {
-        "emoji-regex": "^8.0.0",
-        "is-fullwidth-code-point": "^3.0.0",
-        "strip-ansi": "^6.0.1"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/cliui/node_modules/strip-ansi": {
-      "version": "6.0.1",
-      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
-      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
-      "license": "MIT",
-      "dependencies": {
-        "ansi-regex": "^5.0.1"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/cliui/node_modules/wrap-ansi": {
-      "version": "7.0.0",
-      "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
-      "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
-      "license": "MIT",
-      "dependencies": {
-        "ansi-styles": "^4.0.0",
-        "string-width": "^4.1.0",
-        "strip-ansi": "^6.0.0"
-      },
-      "engines": {
-        "node": ">=10"
-      },
-      "funding": {
-        "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
-      }
-    },
-    "node_modules/clone": {
-      "version": "2.1.2",
-      "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
-      "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==",
-      "dev": true,
-      "engines": {
-        "node": ">=0.8"
-      }
-    },
-    "node_modules/co": {
-      "version": "4.6.0",
-      "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
-      "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==",
-      "dev": true,
-      "engines": {
-        "iojs": ">= 1.0.0",
-        "node": ">= 0.12.0"
-      }
-    },
-    "node_modules/co-body": {
-      "version": "6.2.0",
-      "resolved": "https://registry.npmjs.org/co-body/-/co-body-6.2.0.tgz",
-      "integrity": "sha512-Kbpv2Yd1NdL1V/V4cwLVxraHDV6K8ayohr2rmH0J87Er8+zJjcTa6dAn9QMPC9CRgU8+aNajKbSf1TzDB1yKPA==",
-      "dev": true,
-      "dependencies": {
-        "@hapi/bourne": "^3.0.0",
-        "inflation": "^2.0.0",
-        "qs": "^6.5.2",
-        "raw-body": "^2.3.3",
-        "type-is": "^1.6.16"
-      },
-      "engines": {
-        "node": ">=8.0.0"
-      }
-    },
-    "node_modules/color-convert": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
-      "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
-      "license": "MIT",
-      "dependencies": {
-        "color-name": "~1.1.4"
-      },
-      "engines": {
-        "node": ">=7.0.0"
-      }
-    },
-    "node_modules/color-name": {
-      "version": "1.1.4",
-      "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
-      "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
-      "license": "MIT"
-    },
-    "node_modules/command-line-args": {
-      "version": "5.2.1",
-      "resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-5.2.1.tgz",
-      "integrity": "sha512-H4UfQhZyakIjC74I9d34fGYDwk3XpSr17QhEd0Q3I9Xq1CETHo4Hcuo87WyWHpAF1aSLjLRf5lD9ZGX2qStUvg==",
-      "dev": true,
-      "dependencies": {
-        "array-back": "^3.1.0",
-        "find-replace": "^3.0.0",
-        "lodash.camelcase": "^4.3.0",
-        "typical": "^4.0.0"
-      },
-      "engines": {
-        "node": ">=4.0.0"
-      }
-    },
-    "node_modules/command-line-usage": {
-      "version": "7.0.3",
-      "resolved": "https://registry.npmjs.org/command-line-usage/-/command-line-usage-7.0.3.tgz",
-      "integrity": "sha512-PqMLy5+YGwhMh1wS04mVG44oqDsgyLRSKJBdOo1bnYhMKBW65gZF1dRp2OZRhiTjgUHljy99qkO7bsctLaw35Q==",
-      "dev": true,
-      "dependencies": {
-        "array-back": "^6.2.2",
-        "chalk-template": "^0.4.0",
-        "table-layout": "^4.1.0",
-        "typical": "^7.1.1"
-      },
-      "engines": {
-        "node": ">=12.20.0"
-      }
-    },
-    "node_modules/command-line-usage/node_modules/array-back": {
-      "version": "6.2.2",
-      "resolved": "https://registry.npmjs.org/array-back/-/array-back-6.2.2.tgz",
-      "integrity": "sha512-gUAZ7HPyb4SJczXAMUXMGAvI976JoK3qEx9v1FTmeYuJj0IBiaKttG1ydtGKdkfqWkIkouke7nG8ufGy77+Cvw==",
-      "dev": true,
-      "engines": {
-        "node": ">=12.17"
-      }
-    },
-    "node_modules/command-line-usage/node_modules/typical": {
-      "version": "7.3.0",
-      "resolved": "https://registry.npmjs.org/typical/-/typical-7.3.0.tgz",
-      "integrity": "sha512-ya4mg/30vm+DOWfBg4YK3j2WD6TWtRkCbasOJr40CseYENzCUby/7rIvXA99JGsQHeNxLbnXdyLLxKSv3tauFw==",
-      "dev": true,
-      "engines": {
-        "node": ">=12.17"
-      }
-    },
-    "node_modules/content-disposition": {
-      "version": "0.5.4",
-      "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
-      "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
-      "dev": true,
-      "dependencies": {
-        "safe-buffer": "5.2.1"
-      },
-      "engines": {
-        "node": ">= 0.6"
-      }
-    },
-    "node_modules/content-type": {
-      "version": "1.0.5",
-      "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
-      "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
-      "dev": true,
-      "engines": {
-        "node": ">= 0.6"
-      }
-    },
-    "node_modules/convert-source-map": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
-      "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
-      "dev": true
-    },
-    "node_modules/cookies": {
-      "version": "0.9.1",
-      "resolved": "https://registry.npmjs.org/cookies/-/cookies-0.9.1.tgz",
-      "integrity": "sha512-TG2hpqe4ELx54QER/S3HQ9SRVnQnGBtKUz5bLQWtYAQ+o6GpgMs6sYUvaiJjVxb+UXwhRhAEP3m7LbsIZ77Hmw==",
-      "dev": true,
-      "dependencies": {
-        "depd": "~2.0.0",
-        "keygrip": "~1.1.0"
-      },
-      "engines": {
-        "node": ">= 0.8"
-      }
-    },
-    "node_modules/cosmiconfig": {
-      "version": "9.0.0",
-      "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz",
-      "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "env-paths": "^2.2.1",
-        "import-fresh": "^3.3.0",
-        "js-yaml": "^4.1.0",
-        "parse-json": "^5.2.0"
-      },
-      "engines": {
-        "node": ">=14"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/d-fischer"
-      },
-      "peerDependencies": {
-        "typescript": ">=4.9.5"
-      },
-      "peerDependenciesMeta": {
-        "typescript": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/cross-spawn": {
-      "version": "7.0.6",
-      "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
-      "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "path-key": "^3.1.0",
-        "shebang-command": "^2.0.0",
-        "which": "^2.0.1"
-      },
-      "engines": {
-        "node": ">= 8"
-      }
-    },
-    "node_modules/d3-array": {
-      "version": "3.2.4",
-      "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
-      "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
-      "license": "ISC",
-      "dependencies": {
-        "internmap": "1 - 2"
-      },
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/d3-color": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
-      "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==",
-      "license": "ISC",
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/d3-delaunay": {
-      "version": "6.0.4",
-      "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz",
-      "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==",
-      "license": "ISC",
-      "dependencies": {
-        "delaunator": "5"
-      },
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/d3-dispatch": {
-      "version": "3.0.1",
-      "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz",
-      "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==",
-      "license": "ISC",
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/d3-dsv": {
-      "version": "3.0.1",
-      "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz",
-      "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==",
-      "license": "ISC",
-      "dependencies": {
-        "commander": "7",
-        "iconv-lite": "0.6",
-        "rw": "1"
-      },
-      "bin": {
-        "csv2json": "bin/dsv2json.js",
-        "csv2tsv": "bin/dsv2dsv.js",
-        "dsv2dsv": "bin/dsv2dsv.js",
-        "dsv2json": "bin/dsv2json.js",
-        "json2csv": "bin/json2dsv.js",
-        "json2dsv": "bin/json2dsv.js",
-        "json2tsv": "bin/json2dsv.js",
-        "tsv2csv": "bin/dsv2dsv.js",
-        "tsv2json": "bin/dsv2json.js"
-      },
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/d3-dsv/node_modules/commander": {
-      "version": "7.2.0",
-      "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
-      "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
-      "license": "MIT",
-      "engines": {
-        "node": ">= 10"
-      }
-    },
-    "node_modules/d3-force": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz",
-      "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==",
-      "license": "ISC",
-      "dependencies": {
-        "d3-dispatch": "1 - 3",
-        "d3-quadtree": "1 - 3",
-        "d3-timer": "1 - 3"
-      },
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/d3-format": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz",
-      "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==",
-      "license": "ISC",
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/d3-geo": {
-      "version": "3.1.1",
-      "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz",
-      "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==",
-      "license": "ISC",
-      "dependencies": {
-        "d3-array": "2.5.0 - 3"
-      },
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/d3-geo-projection": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/d3-geo-projection/-/d3-geo-projection-4.0.0.tgz",
-      "integrity": "sha512-p0bK60CEzph1iqmnxut7d/1kyTmm3UWtPlwdkM31AU+LW+BXazd5zJdoCn7VFxNCHXRngPHRnsNn5uGjLRGndg==",
-      "license": "ISC",
-      "dependencies": {
-        "commander": "7",
-        "d3-array": "1 - 3",
-        "d3-geo": "1.12.0 - 3"
-      },
-      "bin": {
-        "geo2svg": "bin/geo2svg.js",
-        "geograticule": "bin/geograticule.js",
-        "geoproject": "bin/geoproject.js",
-        "geoquantize": "bin/geoquantize.js",
-        "geostitch": "bin/geostitch.js"
-      },
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/d3-geo-projection/node_modules/commander": {
-      "version": "7.2.0",
-      "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
-      "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
-      "license": "MIT",
-      "engines": {
-        "node": ">= 10"
-      }
-    },
-    "node_modules/d3-hierarchy": {
-      "version": "3.1.2",
-      "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz",
-      "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==",
-      "license": "ISC",
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/d3-interpolate": {
-      "version": "3.0.1",
-      "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
-      "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
-      "license": "ISC",
-      "dependencies": {
-        "d3-color": "1 - 3"
-      },
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/d3-path": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz",
-      "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==",
-      "license": "ISC",
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/d3-quadtree": {
-      "version": "3.0.1",
-      "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz",
-      "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==",
-      "license": "ISC",
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/d3-scale": {
-      "version": "4.0.2",
-      "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz",
-      "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==",
-      "license": "ISC",
-      "dependencies": {
-        "d3-array": "2.10.0 - 3",
-        "d3-format": "1 - 3",
-        "d3-interpolate": "1.2.0 - 3",
-        "d3-time": "2.1.1 - 3",
-        "d3-time-format": "2 - 4"
-      },
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/d3-scale-chromatic": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz",
-      "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==",
-      "license": "ISC",
-      "dependencies": {
-        "d3-color": "1 - 3",
-        "d3-interpolate": "1 - 3"
-      },
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/d3-shape": {
-      "version": "3.2.0",
-      "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz",
-      "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==",
-      "license": "ISC",
-      "dependencies": {
-        "d3-path": "^3.1.0"
-      },
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/d3-time": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz",
-      "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==",
-      "license": "ISC",
-      "dependencies": {
-        "d3-array": "2 - 3"
-      },
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/d3-time-format": {
-      "version": "4.1.0",
-      "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz",
-      "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==",
-      "license": "ISC",
-      "dependencies": {
-        "d3-time": "1 - 3"
-      },
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/d3-timer": {
-      "version": "3.0.1",
-      "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz",
-      "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==",
-      "license": "ISC",
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/data-uri-to-buffer": {
-      "version": "6.0.2",
-      "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz",
-      "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==",
-      "dev": true,
-      "engines": {
-        "node": ">= 14"
-      }
-    },
-    "node_modules/debounce": {
-      "version": "1.2.1",
-      "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz",
-      "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==",
-      "dev": true
-    },
-    "node_modules/debug": {
-      "version": "4.4.0",
-      "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
-      "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
-      "dev": true,
-      "dependencies": {
-        "ms": "^2.1.3"
-      },
-      "engines": {
-        "node": ">=6.0"
-      },
-      "peerDependenciesMeta": {
-        "supports-color": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/deep-equal": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz",
-      "integrity": "sha512-bHtC0iYvWhyaTzvV3CZgPeZQqCOBGyGsVV7v4eevpdkLHfiSrXUdBG+qAuSz4RI70sszvjQ1QSZ98An1yNwpSw==",
-      "dev": true
-    },
-    "node_modules/deepmerge": {
-      "version": "4.3.1",
-      "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
-      "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
-      "license": "MIT",
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
-    "node_modules/default-gateway": {
-      "version": "6.0.3",
-      "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz",
-      "integrity": "sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==",
-      "dev": true,
-      "dependencies": {
-        "execa": "^5.0.0"
-      },
-      "engines": {
-        "node": ">= 10"
-      }
-    },
-    "node_modules/define-lazy-prop": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz",
-      "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==",
-      "dev": true,
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/degenerator": {
-      "version": "5.0.1",
-      "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz",
-      "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==",
-      "dev": true,
-      "dependencies": {
-        "ast-types": "^0.13.4",
-        "escodegen": "^2.1.0",
-        "esprima": "^4.0.1"
-      },
-      "engines": {
-        "node": ">= 14"
-      }
-    },
-    "node_modules/delaunator": {
-      "version": "5.0.1",
-      "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz",
-      "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==",
-      "license": "ISC",
-      "dependencies": {
-        "robust-predicates": "^3.0.2"
-      }
-    },
-    "node_modules/delegates": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
-      "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==",
-      "dev": true
-    },
-    "node_modules/depd": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
-      "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
-      "dev": true,
-      "engines": {
-        "node": ">= 0.8"
-      }
-    },
-    "node_modules/destroy": {
-      "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
-      "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
-      "dev": true,
-      "engines": {
-        "node": ">= 0.8",
-        "npm": "1.2.8000 || >= 1.4.16"
-      }
-    },
-    "node_modules/devtools-protocol": {
-      "version": "0.0.1312386",
-      "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1312386.tgz",
-      "integrity": "sha512-DPnhUXvmvKT2dFA/j7B+riVLUt9Q6RKJlcppojL5CoRywJJKLDYnRlw0gTFKfgDPHP5E04UoB71SxoJlVZy8FA==",
-      "dev": true
-    },
-    "node_modules/diff": {
-      "version": "7.0.0",
-      "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz",
-      "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==",
-      "license": "BSD-3-Clause",
-      "engines": {
-        "node": ">=0.3.1"
-      }
-    },
-    "node_modules/diff2html": {
-      "version": "3.4.51",
-      "resolved": "https://registry.npmjs.org/diff2html/-/diff2html-3.4.51.tgz",
-      "integrity": "sha512-/rVCSDyokkzSCEGaGjkkElXtIRwyNDRzIa3S8VUhR6pjk25p6+AMnb1s2zGmhjl66D5m/HnV3IeZoxnWsvTy+w==",
-      "license": "MIT",
-      "dependencies": {
-        "diff": "^7.0.0",
-        "hogan.js": "3.0.2"
-      },
-      "engines": {
-        "node": ">=12"
-      },
-      "optionalDependencies": {
-        "highlight.js": "11.9.0"
-      }
-    },
-    "node_modules/dir-glob": {
-      "version": "3.0.1",
-      "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
-      "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
-      "dev": true,
-      "dependencies": {
-        "path-type": "^4.0.0"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/dom-serializer": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
-      "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
-      "license": "MIT",
-      "dependencies": {
-        "domelementtype": "^2.3.0",
-        "domhandler": "^5.0.2",
-        "entities": "^4.2.0"
-      },
-      "funding": {
-        "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
-      }
-    },
-    "node_modules/domelementtype": {
-      "version": "2.3.0",
-      "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
-      "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
-      "funding": [
-        {
-          "type": "github",
-          "url": "https://github.com/sponsors/fb55"
-        }
-      ],
-      "license": "BSD-2-Clause"
-    },
-    "node_modules/domhandler": {
-      "version": "5.0.3",
-      "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
-      "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
-      "license": "BSD-2-Clause",
-      "dependencies": {
-        "domelementtype": "^2.3.0"
-      },
-      "engines": {
-        "node": ">= 4"
-      },
-      "funding": {
-        "url": "https://github.com/fb55/domhandler?sponsor=1"
-      }
-    },
-    "node_modules/domutils": {
-      "version": "3.2.2",
-      "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz",
-      "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==",
-      "license": "BSD-2-Clause",
-      "dependencies": {
-        "dom-serializer": "^2.0.0",
-        "domelementtype": "^2.3.0",
-        "domhandler": "^5.0.3"
-      },
-      "funding": {
-        "url": "https://github.com/fb55/domutils?sponsor=1"
-      }
-    },
-    "node_modules/dunder-proto": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
-      "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
-      "dev": true,
-      "dependencies": {
-        "call-bind-apply-helpers": "^1.0.1",
-        "es-errors": "^1.3.0",
-        "gopd": "^1.2.0"
-      },
-      "engines": {
-        "node": ">= 0.4"
-      }
-    },
-    "node_modules/ee-first": {
-      "version": "1.1.1",
-      "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
-      "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
-      "dev": true
-    },
-    "node_modules/electron-to-chromium": {
-      "version": "1.5.135",
-      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.135.tgz",
-      "integrity": "sha512-8gXUdEmvb+WCaYUhA0Svr08uSeRjM2w3x5uHOc1QbaEVzJXB8rgm5eptieXzyKoVEtinLvW6MtTcurA65PeS1Q==",
-      "dev": true,
-      "license": "ISC"
-    },
-    "node_modules/encodeurl": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
-      "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
-      "dev": true,
-      "engines": {
-        "node": ">= 0.8"
-      }
-    },
-    "node_modules/end-of-stream": {
-      "version": "1.4.4",
-      "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
-      "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
-      "dev": true,
-      "dependencies": {
-        "once": "^1.4.0"
-      }
-    },
-    "node_modules/entities": {
-      "version": "4.5.0",
-      "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
-      "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
-      "license": "BSD-2-Clause",
-      "engines": {
-        "node": ">=0.12"
-      },
-      "funding": {
-        "url": "https://github.com/fb55/entities?sponsor=1"
-      }
-    },
-    "node_modules/env-paths": {
-      "version": "2.2.1",
-      "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz",
-      "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==",
-      "dev": true,
-      "license": "MIT",
-      "engines": {
-        "node": ">=6"
-      }
-    },
-    "node_modules/error-ex": {
-      "version": "1.3.2",
-      "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
-      "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "is-arrayish": "^0.2.1"
-      }
-    },
-    "node_modules/errorstacks": {
-      "version": "2.4.1",
-      "resolved": "https://registry.npmjs.org/errorstacks/-/errorstacks-2.4.1.tgz",
-      "integrity": "sha512-jE4i0SMYevwu/xxAuzhly/KTwtj0xDhbzB6m1xPImxTkw8wcCbgarOQPfCVMi5JKVyW7in29pNJCCJrry3Ynnw==",
-      "dev": true
-    },
-    "node_modules/es-define-property": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
-      "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
-      "dev": true,
-      "engines": {
-        "node": ">= 0.4"
-      }
-    },
-    "node_modules/es-errors": {
-      "version": "1.3.0",
-      "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
-      "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
-      "dev": true,
-      "engines": {
-        "node": ">= 0.4"
-      }
-    },
-    "node_modules/es-module-lexer": {
-      "version": "1.6.0",
-      "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.6.0.tgz",
-      "integrity": "sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==",
-      "dev": true
-    },
-    "node_modules/es-object-atoms": {
-      "version": "1.1.1",
-      "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
-      "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
-      "dev": true,
-      "dependencies": {
-        "es-errors": "^1.3.0"
-      },
-      "engines": {
-        "node": ">= 0.4"
-      }
-    },
-    "node_modules/esbuild": {
-      "version": "0.25.2",
-      "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.2.tgz",
-      "integrity": "sha512-16854zccKPnC+toMywC+uKNeYSv+/eXkevRAfwRD/G9Cleq66m8XFIrigkbvauLLlCfDL45Q2cWegSg53gGBnQ==",
-      "dev": true,
-      "hasInstallScript": true,
-      "license": "MIT",
-      "bin": {
-        "esbuild": "bin/esbuild"
-      },
-      "engines": {
-        "node": ">=18"
-      },
-      "optionalDependencies": {
-        "@esbuild/aix-ppc64": "0.25.2",
-        "@esbuild/android-arm": "0.25.2",
-        "@esbuild/android-arm64": "0.25.2",
-        "@esbuild/android-x64": "0.25.2",
-        "@esbuild/darwin-arm64": "0.25.2",
-        "@esbuild/darwin-x64": "0.25.2",
-        "@esbuild/freebsd-arm64": "0.25.2",
-        "@esbuild/freebsd-x64": "0.25.2",
-        "@esbuild/linux-arm": "0.25.2",
-        "@esbuild/linux-arm64": "0.25.2",
-        "@esbuild/linux-ia32": "0.25.2",
-        "@esbuild/linux-loong64": "0.25.2",
-        "@esbuild/linux-mips64el": "0.25.2",
-        "@esbuild/linux-ppc64": "0.25.2",
-        "@esbuild/linux-riscv64": "0.25.2",
-        "@esbuild/linux-s390x": "0.25.2",
-        "@esbuild/linux-x64": "0.25.2",
-        "@esbuild/netbsd-arm64": "0.25.2",
-        "@esbuild/netbsd-x64": "0.25.2",
-        "@esbuild/openbsd-arm64": "0.25.2",
-        "@esbuild/openbsd-x64": "0.25.2",
-        "@esbuild/sunos-x64": "0.25.2",
-        "@esbuild/win32-arm64": "0.25.2",
-        "@esbuild/win32-ia32": "0.25.2",
-        "@esbuild/win32-x64": "0.25.2"
-      }
-    },
-    "node_modules/escalade": {
-      "version": "3.2.0",
-      "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
-      "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
-      "license": "MIT",
-      "engines": {
-        "node": ">=6"
-      }
-    },
-    "node_modules/escape-html": {
-      "version": "1.0.3",
-      "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
-      "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
-      "dev": true
-    },
-    "node_modules/escape-string-regexp": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
-      "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
-      "license": "MIT",
-      "engines": {
-        "node": ">=10"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
-      }
-    },
-    "node_modules/escodegen": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz",
-      "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==",
-      "dev": true,
-      "dependencies": {
-        "esprima": "^4.0.1",
-        "estraverse": "^5.2.0",
-        "esutils": "^2.0.2"
-      },
-      "bin": {
-        "escodegen": "bin/escodegen.js",
-        "esgenerate": "bin/esgenerate.js"
-      },
-      "engines": {
-        "node": ">=6.0"
-      },
-      "optionalDependencies": {
-        "source-map": "~0.6.1"
-      }
-    },
-    "node_modules/escodegen/node_modules/source-map": {
-      "version": "0.6.1",
-      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
-      "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
-      "dev": true,
-      "optional": true,
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
-    "node_modules/esprima": {
-      "version": "4.0.1",
-      "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
-      "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
-      "dev": true,
-      "bin": {
-        "esparse": "bin/esparse.js",
-        "esvalidate": "bin/esvalidate.js"
-      },
-      "engines": {
-        "node": ">=4"
-      }
-    },
-    "node_modules/estraverse": {
-      "version": "5.3.0",
-      "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
-      "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
-      "dev": true,
-      "engines": {
-        "node": ">=4.0"
-      }
-    },
-    "node_modules/estree-walker": {
-      "version": "2.0.2",
-      "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
-      "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
-      "dev": true
-    },
-    "node_modules/esutils": {
-      "version": "2.0.3",
-      "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
-      "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
-      "dev": true,
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
-    "node_modules/etag": {
-      "version": "1.8.1",
-      "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
-      "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
-      "dev": true,
-      "engines": {
-        "node": ">= 0.6"
-      }
-    },
-    "node_modules/execa": {
-      "version": "5.1.1",
-      "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
-      "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==",
-      "dev": true,
-      "dependencies": {
-        "cross-spawn": "^7.0.3",
-        "get-stream": "^6.0.0",
-        "human-signals": "^2.1.0",
-        "is-stream": "^2.0.0",
-        "merge-stream": "^2.0.0",
-        "npm-run-path": "^4.0.1",
-        "onetime": "^5.1.2",
-        "signal-exit": "^3.0.3",
-        "strip-final-newline": "^2.0.0"
-      },
-      "engines": {
-        "node": ">=10"
-      },
-      "funding": {
-        "url": "https://github.com/sindresorhus/execa?sponsor=1"
-      }
-    },
-    "node_modules/execa/node_modules/signal-exit": {
-      "version": "3.0.7",
-      "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
-      "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
-      "dev": true
-    },
-    "node_modules/extract-zip": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz",
-      "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==",
-      "dev": true,
-      "dependencies": {
-        "debug": "^4.1.1",
-        "get-stream": "^5.1.0",
-        "yauzl": "^2.10.0"
-      },
-      "bin": {
-        "extract-zip": "cli.js"
-      },
-      "engines": {
-        "node": ">= 10.17.0"
-      },
-      "optionalDependencies": {
-        "@types/yauzl": "^2.9.1"
-      }
-    },
-    "node_modules/extract-zip/node_modules/get-stream": {
-      "version": "5.2.0",
-      "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
-      "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==",
-      "dev": true,
-      "dependencies": {
-        "pump": "^3.0.0"
-      },
-      "engines": {
-        "node": ">=8"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
-      }
-    },
-    "node_modules/fast-fifo": {
-      "version": "1.3.2",
-      "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz",
-      "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==",
-      "dev": true
-    },
-    "node_modules/fast-glob": {
-      "version": "3.3.3",
-      "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
-      "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "@nodelib/fs.stat": "^2.0.2",
-        "@nodelib/fs.walk": "^1.2.3",
-        "glob-parent": "^5.1.2",
-        "merge2": "^1.3.0",
-        "micromatch": "^4.0.8"
-      },
-      "engines": {
-        "node": ">=8.6.0"
-      }
-    },
-    "node_modules/fast-json-patch": {
-      "version": "3.1.1",
-      "resolved": "https://registry.npmjs.org/fast-json-patch/-/fast-json-patch-3.1.1.tgz",
-      "integrity": "sha512-vf6IHUX2SBcA+5/+4883dsIjpBTqmfBjmYiWK1savxQmFk4JfBMLa7ynTYOs1Rolp/T1betJxHiGD3g1Mn8lUQ==",
-      "license": "MIT"
-    },
-    "node_modules/fastq": {
-      "version": "1.19.1",
-      "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz",
-      "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==",
-      "dev": true,
-      "license": "ISC",
-      "dependencies": {
-        "reusify": "^1.0.4"
-      }
-    },
-    "node_modules/fd-slicer": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
-      "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==",
-      "dev": true,
-      "dependencies": {
-        "pend": "~1.2.0"
-      }
-    },
-    "node_modules/fill-range": {
-      "version": "7.1.1",
-      "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
-      "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "to-regex-range": "^5.0.1"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/find-replace": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-3.0.0.tgz",
-      "integrity": "sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==",
-      "dev": true,
-      "dependencies": {
-        "array-back": "^3.0.1"
-      },
-      "engines": {
-        "node": ">=4.0.0"
-      }
-    },
-    "node_modules/fraction.js": {
-      "version": "4.3.7",
-      "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz",
-      "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==",
-      "dev": true,
-      "license": "MIT",
-      "engines": {
-        "node": "*"
-      },
-      "funding": {
-        "type": "patreon",
-        "url": "https://github.com/sponsors/rawify"
-      }
-    },
-    "node_modules/fresh": {
-      "version": "0.5.2",
-      "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
-      "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
-      "dev": true,
-      "engines": {
-        "node": ">= 0.6"
-      }
-    },
-    "node_modules/fsevents": {
-      "version": "2.3.3",
-      "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
-      "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
-      "dev": true,
-      "hasInstallScript": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "darwin"
-      ],
-      "engines": {
-        "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
-      }
-    },
-    "node_modules/function-bind": {
-      "version": "1.1.2",
-      "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
-      "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
-      "dev": true,
-      "license": "MIT",
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/gensync": {
-      "version": "1.0.0-beta.2",
-      "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
-      "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
-      "dev": true,
-      "license": "MIT",
-      "engines": {
-        "node": ">=6.9.0"
-      }
-    },
-    "node_modules/get-caller-file": {
-      "version": "2.0.5",
-      "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
-      "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
-      "license": "ISC",
-      "engines": {
-        "node": "6.* || 8.* || >= 10.*"
-      }
-    },
-    "node_modules/get-intrinsic": {
-      "version": "1.3.0",
-      "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
-      "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
-      "dev": true,
-      "dependencies": {
-        "call-bind-apply-helpers": "^1.0.2",
-        "es-define-property": "^1.0.1",
-        "es-errors": "^1.3.0",
-        "es-object-atoms": "^1.1.1",
-        "function-bind": "^1.1.2",
-        "get-proto": "^1.0.1",
-        "gopd": "^1.2.0",
-        "has-symbols": "^1.1.0",
-        "hasown": "^2.0.2",
-        "math-intrinsics": "^1.1.0"
-      },
-      "engines": {
-        "node": ">= 0.4"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/get-proto": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
-      "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
-      "dev": true,
-      "dependencies": {
-        "dunder-proto": "^1.0.1",
-        "es-object-atoms": "^1.0.0"
-      },
-      "engines": {
-        "node": ">= 0.4"
-      }
-    },
-    "node_modules/get-stream": {
-      "version": "6.0.1",
-      "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
-      "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
-      "dev": true,
-      "engines": {
-        "node": ">=10"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
-      }
-    },
-    "node_modules/get-uri": {
-      "version": "6.0.4",
-      "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.4.tgz",
-      "integrity": "sha512-E1b1lFFLvLgak2whF2xDBcOy6NLVGZBqqjJjsIhvopKfWWEi64pLVTWWehV8KlLerZkfNTA95sTe2OdJKm1OzQ==",
-      "dev": true,
-      "dependencies": {
-        "basic-ftp": "^5.0.2",
-        "data-uri-to-buffer": "^6.0.2",
-        "debug": "^4.3.4"
-      },
-      "engines": {
-        "node": ">= 14"
-      }
-    },
-    "node_modules/glob-parent": {
-      "version": "5.1.2",
-      "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
-      "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
-      "dev": true,
-      "license": "ISC",
-      "dependencies": {
-        "is-glob": "^4.0.1"
-      },
-      "engines": {
-        "node": ">= 6"
-      }
-    },
-    "node_modules/globals": {
-      "version": "11.12.0",
-      "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
-      "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
-      "dev": true,
-      "license": "MIT",
-      "engines": {
-        "node": ">=4"
-      }
-    },
-    "node_modules/globby": {
-      "version": "11.1.0",
-      "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz",
-      "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==",
-      "dev": true,
-      "dependencies": {
-        "array-union": "^2.1.0",
-        "dir-glob": "^3.0.1",
-        "fast-glob": "^3.2.9",
-        "ignore": "^5.2.0",
-        "merge2": "^1.4.1",
-        "slash": "^3.0.0"
-      },
-      "engines": {
-        "node": ">=10"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
-      }
-    },
-    "node_modules/globby/node_modules/slash": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
-      "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
-      "dev": true,
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/gopd": {
-      "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
-      "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
-      "dev": true,
-      "engines": {
-        "node": ">= 0.4"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/has-flag": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
-      "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
-      "dev": true,
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/has-symbols": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
-      "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
-      "dev": true,
-      "engines": {
-        "node": ">= 0.4"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/has-tostringtag": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
-      "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
-      "dev": true,
-      "dependencies": {
-        "has-symbols": "^1.0.3"
-      },
-      "engines": {
-        "node": ">= 0.4"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/hasown": {
-      "version": "2.0.2",
-      "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
-      "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "function-bind": "^1.1.2"
-      },
-      "engines": {
-        "node": ">= 0.4"
-      }
-    },
-    "node_modules/highlight.js": {
-      "version": "11.9.0",
-      "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.9.0.tgz",
-      "integrity": "sha512-fJ7cW7fQGCYAkgv4CPfwFHrfd/cLS4Hau96JuJ+ZTOWhjnhoeN1ub1tFmALm/+lW5z4WCAuAV9bm05AP0mS6Gw==",
-      "license": "BSD-3-Clause",
-      "optional": true,
-      "engines": {
-        "node": ">=12.0.0"
-      }
-    },
-    "node_modules/hogan.js": {
-      "version": "3.0.2",
-      "resolved": "https://registry.npmjs.org/hogan.js/-/hogan.js-3.0.2.tgz",
-      "integrity": "sha512-RqGs4wavGYJWE07t35JQccByczmNUXQT0E12ZYV1VKYu5UiAU9lsos/yBAcf840+zrUQQxgVduCR5/B8nNtibg==",
-      "dependencies": {
-        "mkdirp": "0.3.0",
-        "nopt": "1.0.10"
-      },
-      "bin": {
-        "hulk": "bin/hulk"
-      }
-    },
-    "node_modules/html-escaper": {
-      "version": "2.0.2",
-      "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
-      "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
-      "dev": true
-    },
-    "node_modules/htmlparser2": {
-      "version": "8.0.2",
-      "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz",
-      "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==",
-      "funding": [
-        "https://github.com/fb55/htmlparser2?sponsor=1",
-        {
-          "type": "github",
-          "url": "https://github.com/sponsors/fb55"
-        }
-      ],
-      "license": "MIT",
-      "dependencies": {
-        "domelementtype": "^2.3.0",
-        "domhandler": "^5.0.3",
-        "domutils": "^3.0.1",
-        "entities": "^4.4.0"
-      }
-    },
-    "node_modules/http-assert": {
-      "version": "1.5.0",
-      "resolved": "https://registry.npmjs.org/http-assert/-/http-assert-1.5.0.tgz",
-      "integrity": "sha512-uPpH7OKX4H25hBmU6G1jWNaqJGpTXxey+YOUizJUAgu0AjLUeC8D73hTrhvDS5D+GJN1DN1+hhc/eF/wpxtp0w==",
-      "dev": true,
-      "dependencies": {
-        "deep-equal": "~1.0.1",
-        "http-errors": "~1.8.0"
-      },
-      "engines": {
-        "node": ">= 0.8"
-      }
-    },
-    "node_modules/http-errors": {
-      "version": "1.8.1",
-      "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz",
-      "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==",
-      "dev": true,
-      "dependencies": {
-        "depd": "~1.1.2",
-        "inherits": "2.0.4",
-        "setprototypeof": "1.2.0",
-        "statuses": ">= 1.5.0 < 2",
-        "toidentifier": "1.0.1"
-      },
-      "engines": {
-        "node": ">= 0.6"
-      }
-    },
-    "node_modules/http-errors/node_modules/depd": {
-      "version": "1.1.2",
-      "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
-      "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==",
-      "dev": true,
-      "engines": {
-        "node": ">= 0.6"
-      }
-    },
-    "node_modules/http-proxy-agent": {
-      "version": "7.0.2",
-      "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
-      "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
-      "dev": true,
-      "dependencies": {
-        "agent-base": "^7.1.0",
-        "debug": "^4.3.4"
-      },
-      "engines": {
-        "node": ">= 14"
-      }
-    },
-    "node_modules/https-proxy-agent": {
-      "version": "7.0.6",
-      "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
-      "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
-      "dev": true,
-      "dependencies": {
-        "agent-base": "^7.1.2",
-        "debug": "4"
-      },
-      "engines": {
-        "node": ">= 14"
-      }
-    },
-    "node_modules/human-signals": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
-      "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==",
-      "dev": true,
-      "engines": {
-        "node": ">=10.17.0"
-      }
-    },
-    "node_modules/iconv-lite": {
-      "version": "0.6.3",
-      "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
-      "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
-      "license": "MIT",
-      "dependencies": {
-        "safer-buffer": ">= 2.1.2 < 3.0.0"
-      },
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
-    "node_modules/ieee754": {
-      "version": "1.2.1",
-      "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
-      "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
-      "dev": true,
-      "funding": [
-        {
-          "type": "github",
-          "url": "https://github.com/sponsors/feross"
-        },
-        {
-          "type": "patreon",
-          "url": "https://www.patreon.com/feross"
-        },
-        {
-          "type": "consulting",
-          "url": "https://feross.org/support"
-        }
-      ]
-    },
-    "node_modules/ignore": {
-      "version": "5.3.2",
-      "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
-      "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
-      "dev": true,
-      "engines": {
-        "node": ">= 4"
-      }
-    },
-    "node_modules/import-fresh": {
-      "version": "3.3.1",
-      "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
-      "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "parent-module": "^1.0.0",
-        "resolve-from": "^4.0.0"
-      },
-      "engines": {
-        "node": ">=6"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
-      }
-    },
-    "node_modules/inflation": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/inflation/-/inflation-2.1.0.tgz",
-      "integrity": "sha512-t54PPJHG1Pp7VQvxyVCJ9mBbjG3Hqryges9bXoOO6GExCPa+//i/d5GSuFtpx3ALLd7lgIAur6zrIlBQyJuMlQ==",
-      "dev": true,
-      "engines": {
-        "node": ">= 0.8.0"
-      }
-    },
-    "node_modules/inherits": {
-      "version": "2.0.4",
-      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
-      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
-      "dev": true
-    },
-    "node_modules/internal-ip": {
-      "version": "6.2.0",
-      "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-6.2.0.tgz",
-      "integrity": "sha512-D8WGsR6yDt8uq7vDMu7mjcR+yRMm3dW8yufyChmszWRjcSHuxLBkR3GdS2HZAjodsaGuCvXeEJpueisXJULghg==",
-      "dev": true,
-      "dependencies": {
-        "default-gateway": "^6.0.0",
-        "ipaddr.js": "^1.9.1",
-        "is-ip": "^3.1.0",
-        "p-event": "^4.2.0"
-      },
-      "engines": {
-        "node": ">=10"
-      },
-      "funding": {
-        "url": "https://github.com/sindresorhus/internal-ip?sponsor=1"
-      }
-    },
-    "node_modules/internmap": {
-      "version": "2.0.3",
-      "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz",
-      "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==",
-      "license": "ISC",
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/ip-address": {
-      "version": "9.0.5",
-      "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz",
-      "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==",
-      "dev": true,
-      "dependencies": {
-        "jsbn": "1.1.0",
-        "sprintf-js": "^1.1.3"
-      },
-      "engines": {
-        "node": ">= 12"
-      }
-    },
-    "node_modules/ip-regex": {
-      "version": "4.3.0",
-      "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-4.3.0.tgz",
-      "integrity": "sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q==",
-      "dev": true,
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/ipaddr.js": {
-      "version": "1.9.1",
-      "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
-      "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
-      "dev": true,
-      "engines": {
-        "node": ">= 0.10"
-      }
-    },
-    "node_modules/is-arrayish": {
-      "version": "0.2.1",
-      "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
-      "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
-      "dev": true,
-      "license": "MIT"
-    },
-    "node_modules/is-core-module": {
-      "version": "2.16.1",
-      "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
-      "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "hasown": "^2.0.2"
-      },
-      "engines": {
-        "node": ">= 0.4"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/is-docker": {
-      "version": "2.2.1",
-      "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz",
-      "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==",
-      "dev": true,
-      "bin": {
-        "is-docker": "cli.js"
-      },
-      "engines": {
-        "node": ">=8"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
-      }
-    },
-    "node_modules/is-extglob": {
-      "version": "2.1.1",
-      "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
-      "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
-      "dev": true,
-      "license": "MIT",
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
-    "node_modules/is-fullwidth-code-point": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
-      "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
-      "license": "MIT",
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/is-generator-function": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz",
-      "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==",
-      "dev": true,
-      "dependencies": {
-        "call-bound": "^1.0.3",
-        "get-proto": "^1.0.0",
-        "has-tostringtag": "^1.0.2",
-        "safe-regex-test": "^1.1.0"
-      },
-      "engines": {
-        "node": ">= 0.4"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/is-glob": {
-      "version": "4.0.3",
-      "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
-      "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "is-extglob": "^2.1.1"
-      },
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
-    "node_modules/is-ip": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmjs.org/is-ip/-/is-ip-3.1.0.tgz",
-      "integrity": "sha512-35vd5necO7IitFPjd/YBeqwWnyDWbuLH9ZXQdMfDA8TEo7pv5X8yfrvVO3xbJbLUlERCMvf6X0hTUamQxCYJ9Q==",
-      "dev": true,
-      "dependencies": {
-        "ip-regex": "^4.0.0"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/is-module": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz",
-      "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==",
-      "dev": true
-    },
-    "node_modules/is-number": {
-      "version": "7.0.0",
-      "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
-      "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
-      "dev": true,
-      "license": "MIT",
-      "engines": {
-        "node": ">=0.12.0"
-      }
-    },
-    "node_modules/is-plain-object": {
-      "version": "5.0.0",
-      "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
-      "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==",
-      "license": "MIT",
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
-    "node_modules/is-regex": {
-      "version": "1.2.1",
-      "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz",
-      "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==",
-      "dev": true,
-      "dependencies": {
-        "call-bound": "^1.0.2",
-        "gopd": "^1.2.0",
-        "has-tostringtag": "^1.0.2",
-        "hasown": "^2.0.2"
-      },
-      "engines": {
-        "node": ">= 0.4"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/is-stream": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
-      "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
-      "dev": true,
-      "engines": {
-        "node": ">=8"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
-      }
-    },
-    "node_modules/is-wsl": {
-      "version": "2.2.0",
-      "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
-      "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==",
-      "dev": true,
-      "dependencies": {
-        "is-docker": "^2.0.0"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/isbinaryfile": {
-      "version": "5.0.4",
-      "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-5.0.4.tgz",
-      "integrity": "sha512-YKBKVkKhty7s8rxddb40oOkuP0NbaeXrQvLin6QMHL7Ypiy2RW9LwOVrVgZRyOrhQlayMd9t+D8yDy8MKFTSDQ==",
-      "dev": true,
-      "engines": {
-        "node": ">= 18.0.0"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/gjtorikian/"
-      }
-    },
-    "node_modules/isexe": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
-      "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
-      "dev": true,
-      "license": "ISC"
-    },
-    "node_modules/istanbul-lib-coverage": {
-      "version": "3.2.2",
-      "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz",
-      "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==",
-      "dev": true,
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/istanbul-lib-report": {
-      "version": "3.0.1",
-      "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz",
-      "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==",
-      "dev": true,
-      "dependencies": {
-        "istanbul-lib-coverage": "^3.0.0",
-        "make-dir": "^4.0.0",
-        "supports-color": "^7.1.0"
-      },
-      "engines": {
-        "node": ">=10"
-      }
-    },
-    "node_modules/istanbul-reports": {
-      "version": "3.1.7",
-      "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz",
-      "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==",
-      "dev": true,
-      "dependencies": {
-        "html-escaper": "^2.0.0",
-        "istanbul-lib-report": "^3.0.0"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/js-tokens": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
-      "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
-      "dev": true
-    },
-    "node_modules/js-yaml": {
-      "version": "4.1.0",
-      "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
-      "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "argparse": "^2.0.1"
-      },
-      "bin": {
-        "js-yaml": "bin/js-yaml.js"
-      }
-    },
-    "node_modules/jsbn": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz",
-      "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==",
-      "dev": true
-    },
-    "node_modules/jsesc": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
-      "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
-      "dev": true,
-      "license": "MIT",
-      "bin": {
-        "jsesc": "bin/jsesc"
-      },
-      "engines": {
-        "node": ">=6"
-      }
-    },
-    "node_modules/json-parse-even-better-errors": {
-      "version": "2.3.1",
-      "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
-      "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
-      "dev": true,
-      "license": "MIT"
-    },
-    "node_modules/json-stringify-pretty-compact": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/json-stringify-pretty-compact/-/json-stringify-pretty-compact-4.0.0.tgz",
-      "integrity": "sha512-3CNZ2DnrpByG9Nqj6Xo8vqbjT4F6N+tb4Gb28ESAZjYZ5yqvmc56J+/kuIwkaAMOyblTQhUW7PxMkUb8Q36N3Q==",
-      "license": "MIT"
-    },
-    "node_modules/json5": {
-      "version": "2.2.3",
-      "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
-      "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
-      "dev": true,
-      "license": "MIT",
-      "bin": {
-        "json5": "lib/cli.js"
-      },
-      "engines": {
-        "node": ">=6"
-      }
-    },
-    "node_modules/keygrip": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz",
-      "integrity": "sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==",
-      "dev": true,
-      "dependencies": {
-        "tsscmp": "1.0.6"
-      },
-      "engines": {
-        "node": ">= 0.6"
-      }
-    },
-    "node_modules/koa": {
-      "version": "2.16.1",
-      "resolved": "https://registry.npmjs.org/koa/-/koa-2.16.1.tgz",
-      "integrity": "sha512-umfX9d3iuSxTQP4pnzLOz0HKnPg0FaUUIKcye2lOiz3KPu1Y3M3xlz76dISdFPQs37P9eJz1wUpcTS6KDPn9fA==",
-      "dev": true,
-      "dependencies": {
-        "accepts": "^1.3.5",
-        "cache-content-type": "^1.0.0",
-        "content-disposition": "~0.5.2",
-        "content-type": "^1.0.4",
-        "cookies": "~0.9.0",
-        "debug": "^4.3.2",
-        "delegates": "^1.0.0",
-        "depd": "^2.0.0",
-        "destroy": "^1.0.4",
-        "encodeurl": "^1.0.2",
-        "escape-html": "^1.0.3",
-        "fresh": "~0.5.2",
-        "http-assert": "^1.3.0",
-        "http-errors": "^1.6.3",
-        "is-generator-function": "^1.0.7",
-        "koa-compose": "^4.1.0",
-        "koa-convert": "^2.0.0",
-        "on-finished": "^2.3.0",
-        "only": "~0.0.2",
-        "parseurl": "^1.3.2",
-        "statuses": "^1.5.0",
-        "type-is": "^1.6.16",
-        "vary": "^1.1.2"
-      },
-      "engines": {
-        "node": "^4.8.4 || ^6.10.1 || ^7.10.1 || >= 8.1.4"
-      }
-    },
-    "node_modules/koa-compose": {
-      "version": "4.1.0",
-      "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-4.1.0.tgz",
-      "integrity": "sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==",
-      "dev": true
-    },
-    "node_modules/koa-convert": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/koa-convert/-/koa-convert-2.0.0.tgz",
-      "integrity": "sha512-asOvN6bFlSnxewce2e/DK3p4tltyfC4VM7ZwuTuepI7dEQVcvpyFuBcEARu1+Hxg8DIwytce2n7jrZtRlPrARA==",
-      "dev": true,
-      "dependencies": {
-        "co": "^4.6.0",
-        "koa-compose": "^4.1.0"
-      },
-      "engines": {
-        "node": ">= 10"
-      }
-    },
-    "node_modules/koa-etag": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/koa-etag/-/koa-etag-4.0.0.tgz",
-      "integrity": "sha512-1cSdezCkBWlyuB9l6c/IFoe1ANCDdPBxkDkRiaIup40xpUub6U/wwRXoKBZw/O5BifX9OlqAjYnDyzM6+l+TAg==",
-      "dev": true,
-      "dependencies": {
-        "etag": "^1.8.1"
-      }
-    },
-    "node_modules/koa-send": {
-      "version": "5.0.1",
-      "resolved": "https://registry.npmjs.org/koa-send/-/koa-send-5.0.1.tgz",
-      "integrity": "sha512-tmcyQ/wXXuxpDxyNXv5yNNkdAMdFRqwtegBXUaowiQzUKqJehttS0x2j0eOZDQAyloAth5w6wwBImnFzkUz3pQ==",
-      "dev": true,
-      "dependencies": {
-        "debug": "^4.1.1",
-        "http-errors": "^1.7.3",
-        "resolve-path": "^1.4.0"
-      },
-      "engines": {
-        "node": ">= 8"
-      }
-    },
-    "node_modules/koa-static": {
-      "version": "5.0.0",
-      "resolved": "https://registry.npmjs.org/koa-static/-/koa-static-5.0.0.tgz",
-      "integrity": "sha512-UqyYyH5YEXaJrf9S8E23GoJFQZXkBVJ9zYYMPGz919MSX1KuvAcycIuS0ci150HCoPf4XQVhQ84Qf8xRPWxFaQ==",
-      "dev": true,
-      "dependencies": {
-        "debug": "^3.1.0",
-        "koa-send": "^5.0.0"
-      },
-      "engines": {
-        "node": ">= 7.6.0"
-      }
-    },
-    "node_modules/koa-static/node_modules/debug": {
-      "version": "3.2.7",
-      "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
-      "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
-      "dev": true,
-      "dependencies": {
-        "ms": "^2.1.1"
-      }
-    },
-    "node_modules/lighthouse-logger": {
-      "version": "1.4.2",
-      "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-1.4.2.tgz",
-      "integrity": "sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g==",
-      "dev": true,
-      "dependencies": {
-        "debug": "^2.6.9",
-        "marky": "^1.2.2"
-      }
-    },
-    "node_modules/lighthouse-logger/node_modules/debug": {
-      "version": "2.6.9",
-      "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
-      "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
-      "dev": true,
-      "dependencies": {
-        "ms": "2.0.0"
-      }
-    },
-    "node_modules/lighthouse-logger/node_modules/ms": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
-      "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
-      "dev": true
-    },
-    "node_modules/lines-and-columns": {
-      "version": "1.2.4",
-      "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
-      "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
-      "dev": true,
-      "license": "MIT"
-    },
-    "node_modules/lit": {
-      "version": "3.2.1",
-      "resolved": "https://registry.npmjs.org/lit/-/lit-3.2.1.tgz",
-      "integrity": "sha512-1BBa1E/z0O9ye5fZprPtdqnc0BFzxIxTTOO/tQFmyC/hj1O3jL4TfmLBw0WEwjAokdLwpclkvGgDJwTIh0/22w==",
-      "license": "BSD-3-Clause",
-      "dependencies": {
-        "@lit/reactive-element": "^2.0.4",
-        "lit-element": "^4.1.0",
-        "lit-html": "^3.2.0"
-      }
-    },
-    "node_modules/lit-element": {
-      "version": "4.1.1",
-      "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-4.1.1.tgz",
-      "integrity": "sha512-HO9Tkkh34QkTeUmEdNYhMT8hzLid7YlMlATSi1q4q17HE5d9mrrEHJ/o8O2D0cMi182zK1F3v7x0PWFjrhXFew==",
-      "license": "BSD-3-Clause",
-      "dependencies": {
-        "@lit-labs/ssr-dom-shim": "^1.2.0",
-        "@lit/reactive-element": "^2.0.4",
-        "lit-html": "^3.2.0"
-      }
-    },
-    "node_modules/lit-html": {
-      "version": "3.2.1",
-      "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-3.2.1.tgz",
-      "integrity": "sha512-qI/3lziaPMSKsrwlxH/xMgikhQ0EGOX2ICU73Bi/YHFvz2j/yMCIrw4+puF2IpQ4+upd3EWbvnHM9+PnJn48YA==",
-      "license": "BSD-3-Clause",
-      "dependencies": {
-        "@types/trusted-types": "^2.0.2"
-      }
-    },
-    "node_modules/lodash.camelcase": {
-      "version": "4.3.0",
-      "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
-      "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==",
-      "dev": true
-    },
-    "node_modules/log-update": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz",
-      "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==",
-      "dev": true,
-      "dependencies": {
-        "ansi-escapes": "^4.3.0",
-        "cli-cursor": "^3.1.0",
-        "slice-ansi": "^4.0.0",
-        "wrap-ansi": "^6.2.0"
-      },
-      "engines": {
-        "node": ">=10"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
-      }
-    },
-    "node_modules/log-update/node_modules/ansi-regex": {
-      "version": "5.0.1",
-      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
-      "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
-      "dev": true,
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/log-update/node_modules/ansi-styles": {
-      "version": "4.3.0",
-      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
-      "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
-      "dev": true,
-      "dependencies": {
-        "color-convert": "^2.0.1"
-      },
-      "engines": {
-        "node": ">=8"
-      },
-      "funding": {
-        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
-      }
-    },
-    "node_modules/log-update/node_modules/emoji-regex": {
-      "version": "8.0.0",
-      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
-      "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
-      "dev": true
-    },
-    "node_modules/log-update/node_modules/string-width": {
-      "version": "4.2.3",
-      "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
-      "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
-      "dev": true,
-      "dependencies": {
-        "emoji-regex": "^8.0.0",
-        "is-fullwidth-code-point": "^3.0.0",
-        "strip-ansi": "^6.0.1"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/log-update/node_modules/strip-ansi": {
-      "version": "6.0.1",
-      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
-      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
-      "dev": true,
-      "dependencies": {
-        "ansi-regex": "^5.0.1"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/log-update/node_modules/wrap-ansi": {
-      "version": "6.2.0",
-      "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
-      "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
-      "dev": true,
-      "dependencies": {
-        "ansi-styles": "^4.0.0",
-        "string-width": "^4.1.0",
-        "strip-ansi": "^6.0.0"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/lru-cache": {
-      "version": "5.1.1",
-      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
-      "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
-      "dev": true,
-      "license": "ISC",
-      "dependencies": {
-        "yallist": "^3.0.2"
-      }
-    },
-    "node_modules/make-dir": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz",
-      "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==",
-      "dev": true,
-      "dependencies": {
-        "semver": "^7.5.3"
-      },
-      "engines": {
-        "node": ">=10"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
-      }
-    },
-    "node_modules/marked": {
-      "version": "15.0.8",
-      "resolved": "https://registry.npmjs.org/marked/-/marked-15.0.8.tgz",
-      "integrity": "sha512-rli4l2LyZqpQuRve5C0rkn6pj3hT8EWPC+zkAxFTAJLxRbENfTAhEQq9itrmf1Y81QtAX5D/MYlGlIomNgj9lA==",
-      "license": "MIT",
-      "bin": {
-        "marked": "bin/marked.js"
-      },
-      "engines": {
-        "node": ">= 18"
-      }
-    },
-    "node_modules/marky": {
-      "version": "1.2.5",
-      "resolved": "https://registry.npmjs.org/marky/-/marky-1.2.5.tgz",
-      "integrity": "sha512-q9JtQJKjpsVxCRVgQ+WapguSbKC3SQ5HEzFGPAJMStgh3QjCawp00UKv3MTTAArTmGmmPUvllHZoNbZ3gs0I+Q==",
-      "dev": true
-    },
-    "node_modules/math-intrinsics": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
-      "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
-      "dev": true,
-      "engines": {
-        "node": ">= 0.4"
-      }
-    },
-    "node_modules/media-typer": {
-      "version": "0.3.0",
-      "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
-      "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
-      "dev": true,
-      "engines": {
-        "node": ">= 0.6"
-      }
-    },
-    "node_modules/merge-stream": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
-      "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
-      "dev": true
-    },
-    "node_modules/merge2": {
-      "version": "1.4.1",
-      "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
-      "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
-      "dev": true,
-      "license": "MIT",
-      "engines": {
-        "node": ">= 8"
-      }
-    },
-    "node_modules/micromatch": {
-      "version": "4.0.8",
-      "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
-      "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "braces": "^3.0.3",
-        "picomatch": "^2.3.1"
-      },
-      "engines": {
-        "node": ">=8.6"
-      }
-    },
-    "node_modules/mime-db": {
-      "version": "1.52.0",
-      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
-      "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
-      "dev": true,
-      "engines": {
-        "node": ">= 0.6"
-      }
-    },
-    "node_modules/mime-types": {
-      "version": "2.1.35",
-      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
-      "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
-      "dev": true,
-      "dependencies": {
-        "mime-db": "1.52.0"
-      },
-      "engines": {
-        "node": ">= 0.6"
-      }
-    },
-    "node_modules/mimic-fn": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
-      "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
-      "dev": true,
-      "engines": {
-        "node": ">=6"
-      }
-    },
-    "node_modules/mitt": {
-      "version": "3.0.1",
-      "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz",
-      "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==",
-      "dev": true
-    },
-    "node_modules/mkdirp": {
-      "version": "0.3.0",
-      "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz",
-      "integrity": "sha512-OHsdUcVAQ6pOtg5JYWpCBo9W/GySVuwvP9hueRMW7UqshC0tbfzLv8wjySTPm3tfUZ/21CE9E1pJagOA91Pxew==",
-      "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)",
-      "license": "MIT/X11",
-      "engines": {
-        "node": "*"
-      }
-    },
-    "node_modules/ms": {
-      "version": "2.1.3",
-      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
-      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
-      "dev": true
-    },
-    "node_modules/nanocolors": {
-      "version": "0.2.13",
-      "resolved": "https://registry.npmjs.org/nanocolors/-/nanocolors-0.2.13.tgz",
-      "integrity": "sha512-0n3mSAQLPpGLV9ORXT5+C/D4mwew7Ebws69Hx4E2sgz2ZA5+32Q80B9tL8PbL7XHnRDiAxH/pnrUJ9a4fkTNTA==",
-      "dev": true
-    },
-    "node_modules/nanoid": {
-      "version": "3.3.11",
-      "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
-      "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
-      "funding": [
-        {
-          "type": "github",
-          "url": "https://github.com/sponsors/ai"
-        }
-      ],
-      "license": "MIT",
-      "bin": {
-        "nanoid": "bin/nanoid.cjs"
-      },
-      "engines": {
-        "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
-      }
-    },
-    "node_modules/negotiator": {
-      "version": "0.6.3",
-      "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
-      "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
-      "dev": true,
-      "engines": {
-        "node": ">= 0.6"
-      }
-    },
-    "node_modules/netmask": {
-      "version": "2.0.2",
-      "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz",
-      "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==",
-      "dev": true,
-      "engines": {
-        "node": ">= 0.4.0"
-      }
-    },
-    "node_modules/node-fetch": {
-      "version": "2.7.0",
-      "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
-      "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
-      "license": "MIT",
-      "dependencies": {
-        "whatwg-url": "^5.0.0"
-      },
-      "engines": {
-        "node": "4.x || >=6.0.0"
-      },
-      "peerDependencies": {
-        "encoding": "^0.1.0"
-      },
-      "peerDependenciesMeta": {
-        "encoding": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/node-releases": {
-      "version": "2.0.19",
-      "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
-      "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==",
-      "dev": true,
-      "license": "MIT"
-    },
-    "node_modules/nopt": {
-      "version": "1.0.10",
-      "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz",
-      "integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==",
-      "license": "MIT",
-      "dependencies": {
-        "abbrev": "1"
-      },
-      "bin": {
-        "nopt": "bin/nopt.js"
-      },
-      "engines": {
-        "node": "*"
-      }
-    },
-    "node_modules/normalize-range": {
-      "version": "0.1.2",
-      "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz",
-      "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==",
-      "dev": true,
-      "license": "MIT",
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
-    "node_modules/npm-run-path": {
-      "version": "4.0.1",
-      "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
-      "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==",
-      "dev": true,
-      "dependencies": {
-        "path-key": "^3.0.0"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/object-inspect": {
-      "version": "1.13.4",
-      "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
-      "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
-      "dev": true,
-      "engines": {
-        "node": ">= 0.4"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/on-finished": {
-      "version": "2.4.1",
-      "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
-      "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
-      "dev": true,
-      "dependencies": {
-        "ee-first": "1.1.1"
-      },
-      "engines": {
-        "node": ">= 0.8"
-      }
-    },
-    "node_modules/once": {
-      "version": "1.4.0",
-      "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
-      "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
-      "dev": true,
-      "dependencies": {
-        "wrappy": "1"
-      }
-    },
-    "node_modules/onetime": {
-      "version": "5.1.2",
-      "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
-      "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
-      "dev": true,
-      "dependencies": {
-        "mimic-fn": "^2.1.0"
-      },
-      "engines": {
-        "node": ">=6"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
-      }
-    },
-    "node_modules/only": {
-      "version": "0.0.2",
-      "resolved": "https://registry.npmjs.org/only/-/only-0.0.2.tgz",
-      "integrity": "sha512-Fvw+Jemq5fjjyWz6CpKx6w9s7xxqo3+JCyM0WXWeCSOboZ8ABkyvP8ID4CZuChA/wxSx+XSJmdOm8rGVyJ1hdQ==",
-      "dev": true
-    },
-    "node_modules/open": {
-      "version": "8.4.2",
-      "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz",
-      "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==",
-      "dev": true,
-      "dependencies": {
-        "define-lazy-prop": "^2.0.0",
-        "is-docker": "^2.1.1",
-        "is-wsl": "^2.2.0"
-      },
-      "engines": {
-        "node": ">=12"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
-      }
-    },
-    "node_modules/p-event": {
-      "version": "4.2.0",
-      "resolved": "https://registry.npmjs.org/p-event/-/p-event-4.2.0.tgz",
-      "integrity": "sha512-KXatOjCRXXkSePPb1Nbi0p0m+gQAwdlbhi4wQKJPI1HsMQS9g+Sqp2o+QHziPr7eYJyOZet836KoHEVM1mwOrQ==",
-      "dev": true,
-      "dependencies": {
-        "p-timeout": "^3.1.0"
-      },
-      "engines": {
-        "node": ">=8"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
-      }
-    },
-    "node_modules/p-finally": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
-      "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==",
-      "dev": true,
-      "engines": {
-        "node": ">=4"
-      }
-    },
-    "node_modules/p-timeout": {
-      "version": "3.2.0",
-      "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz",
-      "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==",
-      "dev": true,
-      "dependencies": {
-        "p-finally": "^1.0.0"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/pac-proxy-agent": {
-      "version": "7.2.0",
-      "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz",
-      "integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==",
-      "dev": true,
-      "dependencies": {
-        "@tootallnate/quickjs-emscripten": "^0.23.0",
-        "agent-base": "^7.1.2",
-        "debug": "^4.3.4",
-        "get-uri": "^6.0.1",
-        "http-proxy-agent": "^7.0.0",
-        "https-proxy-agent": "^7.0.6",
-        "pac-resolver": "^7.0.1",
-        "socks-proxy-agent": "^8.0.5"
-      },
-      "engines": {
-        "node": ">= 14"
-      }
-    },
-    "node_modules/pac-resolver": {
-      "version": "7.0.1",
-      "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz",
-      "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==",
-      "dev": true,
-      "dependencies": {
-        "degenerator": "^5.0.0",
-        "netmask": "^2.0.2"
-      },
-      "engines": {
-        "node": ">= 14"
-      }
-    },
-    "node_modules/parent-module": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
-      "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "callsites": "^3.0.0"
-      },
-      "engines": {
-        "node": ">=6"
-      }
-    },
-    "node_modules/parse-json": {
-      "version": "5.2.0",
-      "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
-      "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "@babel/code-frame": "^7.0.0",
-        "error-ex": "^1.3.1",
-        "json-parse-even-better-errors": "^2.3.0",
-        "lines-and-columns": "^1.1.6"
-      },
-      "engines": {
-        "node": ">=8"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
-      }
-    },
-    "node_modules/parse-srcset": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/parse-srcset/-/parse-srcset-1.0.2.tgz",
-      "integrity": "sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==",
-      "license": "MIT"
-    },
-    "node_modules/parse5": {
-      "version": "6.0.1",
-      "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz",
-      "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==",
-      "dev": true
-    },
-    "node_modules/parseurl": {
-      "version": "1.3.3",
-      "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
-      "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
-      "dev": true,
-      "engines": {
-        "node": ">= 0.8"
-      }
-    },
-    "node_modules/path-is-absolute": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
-      "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
-      "dev": true,
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
-    "node_modules/path-key": {
-      "version": "3.1.1",
-      "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
-      "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
-      "dev": true,
-      "license": "MIT",
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/path-parse": {
-      "version": "1.0.7",
-      "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
-      "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
-      "dev": true,
-      "license": "MIT"
-    },
-    "node_modules/path-type": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
-      "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
-      "dev": true,
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/pend": {
-      "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
-      "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==",
-      "dev": true
-    },
-    "node_modules/picocolors": {
-      "version": "1.1.1",
-      "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
-      "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
-      "license": "ISC"
-    },
-    "node_modules/picomatch": {
-      "version": "2.3.1",
-      "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
-      "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
-      "dev": true,
-      "license": "MIT",
-      "engines": {
-        "node": ">=8.6"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/jonschlinkert"
-      }
-    },
-    "node_modules/playwright": {
-      "version": "1.51.1",
-      "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.51.1.tgz",
-      "integrity": "sha512-kkx+MB2KQRkyxjYPc3a0wLZZoDczmppyGJIvQ43l+aZihkaVvmu/21kiyaHeHjiFxjxNNFnUncKmcGIyOojsaw==",
-      "dev": true,
-      "license": "Apache-2.0",
-      "dependencies": {
-        "playwright-core": "1.51.1"
-      },
-      "bin": {
-        "playwright": "cli.js"
-      },
-      "engines": {
-        "node": ">=18"
-      },
-      "optionalDependencies": {
-        "fsevents": "2.3.2"
-      }
-    },
-    "node_modules/playwright-core": {
-      "version": "1.51.1",
-      "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.51.1.tgz",
-      "integrity": "sha512-/crRMj8+j/Nq5s8QcvegseuyeZPxpQCZb6HNk3Sos3BlZyAknRjoyJPFWkpNn8v0+P3WiwqFF8P+zQo4eqiNuw==",
-      "dev": true,
-      "license": "Apache-2.0",
-      "bin": {
-        "playwright-core": "cli.js"
-      },
-      "engines": {
-        "node": ">=18"
-      }
-    },
-    "node_modules/playwright/node_modules/fsevents": {
-      "version": "2.3.2",
-      "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
-      "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
-      "dev": true,
-      "hasInstallScript": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "darwin"
-      ],
-      "engines": {
-        "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
-      }
-    },
-    "node_modules/portfinder": {
-      "version": "1.0.35",
-      "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.35.tgz",
-      "integrity": "sha512-73JaFg4NwYNAufDtS5FsFu/PdM49ahJrO1i44aCRsDWju1z5wuGDaqyFUQWR6aJoK2JPDWlaYYAGFNIGTSUHSw==",
-      "dev": true,
-      "dependencies": {
-        "async": "^3.2.6",
-        "debug": "^4.3.6"
-      },
-      "engines": {
-        "node": ">= 10.12"
-      }
-    },
-    "node_modules/postcss": {
-      "version": "8.5.3",
-      "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz",
-      "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==",
-      "funding": [
-        {
-          "type": "opencollective",
-          "url": "https://opencollective.com/postcss/"
-        },
-        {
-          "type": "tidelift",
-          "url": "https://tidelift.com/funding/github/npm/postcss"
-        },
-        {
-          "type": "github",
-          "url": "https://github.com/sponsors/ai"
-        }
-      ],
-      "license": "MIT",
-      "dependencies": {
-        "nanoid": "^3.3.8",
-        "picocolors": "^1.1.1",
-        "source-map-js": "^1.2.1"
-      },
-      "engines": {
-        "node": "^10 || ^12 || >=14"
-      }
-    },
-    "node_modules/postcss-value-parser": {
-      "version": "4.2.0",
-      "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
-      "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
-      "dev": true,
-      "license": "MIT"
-    },
-    "node_modules/prettier": {
-      "version": "3.5.3",
-      "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz",
-      "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==",
-      "dev": true,
-      "license": "MIT",
-      "bin": {
-        "prettier": "bin/prettier.cjs"
-      },
-      "engines": {
-        "node": ">=14"
-      },
-      "funding": {
-        "url": "https://github.com/prettier/prettier?sponsor=1"
-      }
-    },
-    "node_modules/progress": {
-      "version": "2.0.3",
-      "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
-      "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
-      "dev": true,
-      "engines": {
-        "node": ">=0.4.0"
-      }
-    },
-    "node_modules/proxy-agent": {
-      "version": "6.5.0",
-      "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz",
-      "integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==",
-      "dev": true,
-      "dependencies": {
-        "agent-base": "^7.1.2",
-        "debug": "^4.3.4",
-        "http-proxy-agent": "^7.0.1",
-        "https-proxy-agent": "^7.0.6",
-        "lru-cache": "^7.14.1",
-        "pac-proxy-agent": "^7.1.0",
-        "proxy-from-env": "^1.1.0",
-        "socks-proxy-agent": "^8.0.5"
-      },
-      "engines": {
-        "node": ">= 14"
-      }
-    },
-    "node_modules/proxy-agent/node_modules/lru-cache": {
-      "version": "7.18.3",
-      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz",
-      "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==",
-      "dev": true,
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/proxy-from-env": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
-      "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
-      "dev": true
-    },
-    "node_modules/pump": {
-      "version": "3.0.2",
-      "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz",
-      "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==",
-      "dev": true,
-      "dependencies": {
-        "end-of-stream": "^1.1.0",
-        "once": "^1.3.1"
-      }
-    },
-    "node_modules/punycode": {
-      "version": "2.3.1",
-      "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
-      "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
-      "dev": true,
-      "engines": {
-        "node": ">=6"
-      }
-    },
-    "node_modules/puppeteer": {
-      "version": "24.6.1",
-      "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-24.6.1.tgz",
-      "integrity": "sha512-/4ocGfu8LNvDbWUqJZV2VmwEWpbOdJa69y2Jivd213tV0ekAtUh/bgT1hhW63SDN/CtrEucOPwoomZ+9M+eBEg==",
-      "dev": true,
-      "hasInstallScript": true,
-      "license": "Apache-2.0",
-      "dependencies": {
-        "@puppeteer/browsers": "2.10.0",
-        "chromium-bidi": "3.0.0",
-        "cosmiconfig": "^9.0.0",
-        "devtools-protocol": "0.0.1425554",
-        "puppeteer-core": "24.6.1",
-        "typed-query-selector": "^2.12.0"
-      },
-      "bin": {
-        "puppeteer": "lib/cjs/puppeteer/node/cli.js"
-      },
-      "engines": {
-        "node": ">=18"
-      }
-    },
-    "node_modules/puppeteer-core": {
-      "version": "22.15.0",
-      "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-22.15.0.tgz",
-      "integrity": "sha512-cHArnywCiAAVXa3t4GGL2vttNxh7GqXtIYGym99egkNJ3oG//wL9LkvO4WE8W1TJe95t1F1ocu9X4xWaGsOKOA==",
-      "dev": true,
-      "dependencies": {
-        "@puppeteer/browsers": "2.3.0",
-        "chromium-bidi": "0.6.3",
-        "debug": "^4.3.6",
-        "devtools-protocol": "0.0.1312386",
-        "ws": "^8.18.0"
-      },
-      "engines": {
-        "node": ">=18"
-      }
-    },
-    "node_modules/puppeteer-core/node_modules/ws": {
-      "version": "8.18.1",
-      "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.1.tgz",
-      "integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==",
-      "dev": true,
-      "engines": {
-        "node": ">=10.0.0"
-      },
-      "peerDependencies": {
-        "bufferutil": "^4.0.1",
-        "utf-8-validate": ">=5.0.2"
-      },
-      "peerDependenciesMeta": {
-        "bufferutil": {
-          "optional": true
-        },
-        "utf-8-validate": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/puppeteer/node_modules/@puppeteer/browsers": {
-      "version": "2.10.0",
-      "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.10.0.tgz",
-      "integrity": "sha512-HdHF4rny4JCvIcm7V1dpvpctIGqM3/Me255CB44vW7hDG1zYMmcBMjpNqZEDxdCfXGLkx5kP0+Jz5DUS+ukqtA==",
-      "dev": true,
-      "license": "Apache-2.0",
-      "dependencies": {
-        "debug": "^4.4.0",
-        "extract-zip": "^2.0.1",
-        "progress": "^2.0.3",
-        "proxy-agent": "^6.5.0",
-        "semver": "^7.7.1",
-        "tar-fs": "^3.0.8",
-        "yargs": "^17.7.2"
-      },
-      "bin": {
-        "browsers": "lib/cjs/main-cli.js"
-      },
-      "engines": {
-        "node": ">=18"
-      }
-    },
-    "node_modules/puppeteer/node_modules/chromium-bidi": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-3.0.0.tgz",
-      "integrity": "sha512-ZOGRDAhBMX1uxL2Cm2TDuhImbrsEz5A/tTcVU6RpXEWaTNUNwsHW6njUXizh51Ir6iqHbKAfhA2XK33uBcLo5A==",
-      "dev": true,
-      "license": "Apache-2.0",
-      "dependencies": {
-        "mitt": "^3.0.1",
-        "zod": "^3.24.1"
-      },
-      "peerDependencies": {
-        "devtools-protocol": "*"
-      }
-    },
-    "node_modules/puppeteer/node_modules/devtools-protocol": {
-      "version": "0.0.1425554",
-      "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1425554.tgz",
-      "integrity": "sha512-uRfxR6Nlzdzt0ihVIkV+sLztKgs7rgquY/Mhcv1YNCWDh5IZgl5mnn2aeEnW5stYTE0wwiF4RYVz8eMEpV1SEw==",
-      "dev": true,
-      "license": "BSD-3-Clause"
-    },
-    "node_modules/puppeteer/node_modules/puppeteer-core": {
-      "version": "24.6.1",
-      "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.6.1.tgz",
-      "integrity": "sha512-sMCxsY+OPWO2fecBrhIeCeJbWWXJ6UaN997sTid6whY0YT9XM0RnxEwLeUibluIS5/fRmuxe1efjb5RMBsky7g==",
-      "dev": true,
-      "license": "Apache-2.0",
-      "dependencies": {
-        "@puppeteer/browsers": "2.10.0",
-        "chromium-bidi": "3.0.0",
-        "debug": "^4.4.0",
-        "devtools-protocol": "0.0.1425554",
-        "typed-query-selector": "^2.12.0",
-        "ws": "^8.18.1"
-      },
-      "engines": {
-        "node": ">=18"
-      }
-    },
-    "node_modules/puppeteer/node_modules/ws": {
-      "version": "8.18.1",
-      "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.1.tgz",
-      "integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==",
-      "dev": true,
-      "license": "MIT",
-      "engines": {
-        "node": ">=10.0.0"
-      },
-      "peerDependencies": {
-        "bufferutil": "^4.0.1",
-        "utf-8-validate": ">=5.0.2"
-      },
-      "peerDependenciesMeta": {
-        "bufferutil": {
-          "optional": true
-        },
-        "utf-8-validate": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/puppeteer/node_modules/zod": {
-      "version": "3.24.2",
-      "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.2.tgz",
-      "integrity": "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==",
-      "dev": true,
-      "license": "MIT",
-      "funding": {
-        "url": "https://github.com/sponsors/colinhacks"
-      }
-    },
-    "node_modules/qs": {
-      "version": "6.14.0",
-      "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
-      "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==",
-      "dev": true,
-      "dependencies": {
-        "side-channel": "^1.1.0"
-      },
-      "engines": {
-        "node": ">=0.6"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/queue-microtask": {
-      "version": "1.2.3",
-      "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
-      "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
-      "dev": true,
-      "funding": [
-        {
-          "type": "github",
-          "url": "https://github.com/sponsors/feross"
-        },
-        {
-          "type": "patreon",
-          "url": "https://www.patreon.com/feross"
-        },
-        {
-          "type": "consulting",
-          "url": "https://feross.org/support"
-        }
-      ],
-      "license": "MIT"
-    },
-    "node_modules/raw-body": {
-      "version": "2.5.2",
-      "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
-      "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
-      "dev": true,
-      "dependencies": {
-        "bytes": "3.1.2",
-        "http-errors": "2.0.0",
-        "iconv-lite": "0.4.24",
-        "unpipe": "1.0.0"
-      },
-      "engines": {
-        "node": ">= 0.8"
-      }
-    },
-    "node_modules/raw-body/node_modules/http-errors": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
-      "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
-      "dev": true,
-      "dependencies": {
-        "depd": "2.0.0",
-        "inherits": "2.0.4",
-        "setprototypeof": "1.2.0",
-        "statuses": "2.0.1",
-        "toidentifier": "1.0.1"
-      },
-      "engines": {
-        "node": ">= 0.8"
-      }
-    },
-    "node_modules/raw-body/node_modules/iconv-lite": {
-      "version": "0.4.24",
-      "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
-      "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
-      "dev": true,
-      "dependencies": {
-        "safer-buffer": ">= 2.1.2 < 3"
-      },
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
-    "node_modules/raw-body/node_modules/statuses": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
-      "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
-      "dev": true,
-      "engines": {
-        "node": ">= 0.8"
-      }
-    },
-    "node_modules/require-directory": {
-      "version": "2.1.1",
-      "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
-      "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
-      "license": "MIT",
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
-    "node_modules/resolve": {
-      "version": "1.22.10",
-      "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
-      "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "is-core-module": "^2.16.0",
-        "path-parse": "^1.0.7",
-        "supports-preserve-symlinks-flag": "^1.0.0"
-      },
-      "bin": {
-        "resolve": "bin/resolve"
-      },
-      "engines": {
-        "node": ">= 0.4"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/resolve-from": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
-      "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
-      "dev": true,
-      "license": "MIT",
-      "engines": {
-        "node": ">=4"
-      }
-    },
-    "node_modules/resolve-path": {
-      "version": "1.4.0",
-      "resolved": "https://registry.npmjs.org/resolve-path/-/resolve-path-1.4.0.tgz",
-      "integrity": "sha512-i1xevIst/Qa+nA9olDxLWnLk8YZbi8R/7JPbCMcgyWaFR6bKWaexgJgEB5oc2PKMjYdrHynyz0NY+if+H98t1w==",
-      "dev": true,
-      "dependencies": {
-        "http-errors": "~1.6.2",
-        "path-is-absolute": "1.0.1"
-      },
-      "engines": {
-        "node": ">= 0.8"
-      }
-    },
-    "node_modules/resolve-path/node_modules/depd": {
-      "version": "1.1.2",
-      "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
-      "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==",
-      "dev": true,
-      "engines": {
-        "node": ">= 0.6"
-      }
-    },
-    "node_modules/resolve-path/node_modules/http-errors": {
-      "version": "1.6.3",
-      "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz",
-      "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==",
-      "dev": true,
-      "dependencies": {
-        "depd": "~1.1.2",
-        "inherits": "2.0.3",
-        "setprototypeof": "1.1.0",
-        "statuses": ">= 1.4.0 < 2"
-      },
-      "engines": {
-        "node": ">= 0.6"
-      }
-    },
-    "node_modules/resolve-path/node_modules/inherits": {
-      "version": "2.0.3",
-      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
-      "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==",
-      "dev": true
-    },
-    "node_modules/resolve-path/node_modules/setprototypeof": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz",
-      "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==",
-      "dev": true
-    },
-    "node_modules/restore-cursor": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz",
-      "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==",
-      "dev": true,
-      "dependencies": {
-        "onetime": "^5.1.0",
-        "signal-exit": "^3.0.2"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/restore-cursor/node_modules/signal-exit": {
-      "version": "3.0.7",
-      "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
-      "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
-      "dev": true
-    },
-    "node_modules/reusify": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
-      "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
-      "dev": true,
-      "license": "MIT",
-      "engines": {
-        "iojs": ">=1.0.0",
-        "node": ">=0.10.0"
-      }
-    },
-    "node_modules/robust-predicates": {
-      "version": "3.0.2",
-      "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz",
-      "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==",
-      "license": "Unlicense"
-    },
-    "node_modules/rollup": {
-      "version": "4.39.0",
-      "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.39.0.tgz",
-      "integrity": "sha512-thI8kNc02yNvnmJp8dr3fNWJ9tCONDhp6TV35X6HkKGGs9E6q7YWCHbe5vKiTa7TAiNcFEmXKj3X/pG2b3ci0g==",
-      "dev": true,
-      "dependencies": {
-        "@types/estree": "1.0.7"
-      },
-      "bin": {
-        "rollup": "dist/bin/rollup"
-      },
-      "engines": {
-        "node": ">=18.0.0",
-        "npm": ">=8.0.0"
-      },
-      "optionalDependencies": {
-        "@rollup/rollup-android-arm-eabi": "4.39.0",
-        "@rollup/rollup-android-arm64": "4.39.0",
-        "@rollup/rollup-darwin-arm64": "4.39.0",
-        "@rollup/rollup-darwin-x64": "4.39.0",
-        "@rollup/rollup-freebsd-arm64": "4.39.0",
-        "@rollup/rollup-freebsd-x64": "4.39.0",
-        "@rollup/rollup-linux-arm-gnueabihf": "4.39.0",
-        "@rollup/rollup-linux-arm-musleabihf": "4.39.0",
-        "@rollup/rollup-linux-arm64-gnu": "4.39.0",
-        "@rollup/rollup-linux-arm64-musl": "4.39.0",
-        "@rollup/rollup-linux-loongarch64-gnu": "4.39.0",
-        "@rollup/rollup-linux-powerpc64le-gnu": "4.39.0",
-        "@rollup/rollup-linux-riscv64-gnu": "4.39.0",
-        "@rollup/rollup-linux-riscv64-musl": "4.39.0",
-        "@rollup/rollup-linux-s390x-gnu": "4.39.0",
-        "@rollup/rollup-linux-x64-gnu": "4.39.0",
-        "@rollup/rollup-linux-x64-musl": "4.39.0",
-        "@rollup/rollup-win32-arm64-msvc": "4.39.0",
-        "@rollup/rollup-win32-ia32-msvc": "4.39.0",
-        "@rollup/rollup-win32-x64-msvc": "4.39.0",
-        "fsevents": "~2.3.2"
-      }
-    },
-    "node_modules/run-parallel": {
-      "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
-      "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
-      "dev": true,
-      "funding": [
-        {
-          "type": "github",
-          "url": "https://github.com/sponsors/feross"
-        },
-        {
-          "type": "patreon",
-          "url": "https://www.patreon.com/feross"
-        },
-        {
-          "type": "consulting",
-          "url": "https://feross.org/support"
-        }
-      ],
-      "license": "MIT",
-      "dependencies": {
-        "queue-microtask": "^1.2.2"
-      }
-    },
-    "node_modules/rw": {
-      "version": "1.3.3",
-      "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz",
-      "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==",
-      "license": "BSD-3-Clause"
-    },
-    "node_modules/safe-buffer": {
-      "version": "5.2.1",
-      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
-      "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
-      "dev": true,
-      "funding": [
-        {
-          "type": "github",
-          "url": "https://github.com/sponsors/feross"
-        },
-        {
-          "type": "patreon",
-          "url": "https://www.patreon.com/feross"
-        },
-        {
-          "type": "consulting",
-          "url": "https://feross.org/support"
-        }
-      ]
-    },
-    "node_modules/safe-regex-test": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz",
-      "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==",
-      "dev": true,
-      "dependencies": {
-        "call-bound": "^1.0.2",
-        "es-errors": "^1.3.0",
-        "is-regex": "^1.2.1"
-      },
-      "engines": {
-        "node": ">= 0.4"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/safer-buffer": {
-      "version": "2.1.2",
-      "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
-      "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
-      "license": "MIT"
-    },
-    "node_modules/sanitize-html": {
-      "version": "2.15.0",
-      "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.15.0.tgz",
-      "integrity": "sha512-wIjst57vJGpLyBP8ioUbg6ThwJie5SuSIjHxJg53v5Fg+kUK+AXlb7bK3RNXpp315MvwM+0OBGCV6h5pPHsVhA==",
-      "license": "MIT",
-      "dependencies": {
-        "deepmerge": "^4.2.2",
-        "escape-string-regexp": "^4.0.0",
-        "htmlparser2": "^8.0.0",
-        "is-plain-object": "^5.0.0",
-        "parse-srcset": "^1.0.2",
-        "postcss": "^8.3.11"
-      }
-    },
-    "node_modules/semver": {
-      "version": "7.7.1",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
-      "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
-      "license": "ISC",
-      "bin": {
-        "semver": "bin/semver.js"
-      },
-      "engines": {
-        "node": ">=10"
-      }
-    },
-    "node_modules/setprototypeof": {
-      "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
-      "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
-      "dev": true
-    },
-    "node_modules/shebang-command": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
-      "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "shebang-regex": "^3.0.0"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/shebang-regex": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
-      "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
-      "dev": true,
-      "license": "MIT",
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/side-channel": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
-      "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
-      "dev": true,
-      "dependencies": {
-        "es-errors": "^1.3.0",
-        "object-inspect": "^1.13.3",
-        "side-channel-list": "^1.0.0",
-        "side-channel-map": "^1.0.1",
-        "side-channel-weakmap": "^1.0.2"
-      },
-      "engines": {
-        "node": ">= 0.4"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/side-channel-list": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
-      "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
-      "dev": true,
-      "dependencies": {
-        "es-errors": "^1.3.0",
-        "object-inspect": "^1.13.3"
-      },
-      "engines": {
-        "node": ">= 0.4"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/side-channel-map": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
-      "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
-      "dev": true,
-      "dependencies": {
-        "call-bound": "^1.0.2",
-        "es-errors": "^1.3.0",
-        "get-intrinsic": "^1.2.5",
-        "object-inspect": "^1.13.3"
-      },
-      "engines": {
-        "node": ">= 0.4"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/side-channel-weakmap": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
-      "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
-      "dev": true,
-      "dependencies": {
-        "call-bound": "^1.0.2",
-        "es-errors": "^1.3.0",
-        "get-intrinsic": "^1.2.5",
-        "object-inspect": "^1.13.3",
-        "side-channel-map": "^1.0.1"
-      },
-      "engines": {
-        "node": ">= 0.4"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/slice-ansi": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz",
-      "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==",
-      "dev": true,
-      "dependencies": {
-        "ansi-styles": "^4.0.0",
-        "astral-regex": "^2.0.0",
-        "is-fullwidth-code-point": "^3.0.0"
-      },
-      "engines": {
-        "node": ">=10"
-      },
-      "funding": {
-        "url": "https://github.com/chalk/slice-ansi?sponsor=1"
-      }
-    },
-    "node_modules/slice-ansi/node_modules/ansi-styles": {
-      "version": "4.3.0",
-      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
-      "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
-      "dev": true,
-      "dependencies": {
-        "color-convert": "^2.0.1"
-      },
-      "engines": {
-        "node": ">=8"
-      },
-      "funding": {
-        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
-      }
-    },
-    "node_modules/smart-buffer": {
-      "version": "4.2.0",
-      "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
-      "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==",
-      "dev": true,
-      "engines": {
-        "node": ">= 6.0.0",
-        "npm": ">= 3.0.0"
-      }
-    },
-    "node_modules/socks": {
-      "version": "2.8.4",
-      "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.4.tgz",
-      "integrity": "sha512-D3YaD0aRxR3mEcqnidIs7ReYJFVzWdd6fXJYUM8ixcQcJRGTka/b3saV0KflYhyVJXKhb947GndU35SxYNResQ==",
-      "dev": true,
-      "dependencies": {
-        "ip-address": "^9.0.5",
-        "smart-buffer": "^4.2.0"
-      },
-      "engines": {
-        "node": ">= 10.0.0",
-        "npm": ">= 3.0.0"
-      }
-    },
-    "node_modules/socks-proxy-agent": {
-      "version": "8.0.5",
-      "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz",
-      "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==",
-      "dev": true,
-      "dependencies": {
-        "agent-base": "^7.1.2",
-        "debug": "^4.3.4",
-        "socks": "^2.8.3"
-      },
-      "engines": {
-        "node": ">= 14"
-      }
-    },
-    "node_modules/source-map": {
-      "version": "0.7.4",
-      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz",
-      "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==",
-      "dev": true,
-      "engines": {
-        "node": ">= 8"
-      }
-    },
-    "node_modules/source-map-js": {
-      "version": "1.2.1",
-      "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
-      "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
-      "license": "BSD-3-Clause",
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
-    "node_modules/sprintf-js": {
-      "version": "1.1.3",
-      "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz",
-      "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==",
-      "dev": true
-    },
-    "node_modules/statuses": {
-      "version": "1.5.0",
-      "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
-      "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==",
-      "dev": true,
-      "engines": {
-        "node": ">= 0.6"
-      }
-    },
-    "node_modules/streamx": {
-      "version": "2.22.0",
-      "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.22.0.tgz",
-      "integrity": "sha512-sLh1evHOzBy/iWRiR6d1zRcLao4gGZr3C1kzNz4fopCOKJb6xD9ub8Mpi9Mr1R6id5o43S+d93fI48UC5uM9aw==",
-      "dev": true,
-      "dependencies": {
-        "fast-fifo": "^1.3.2",
-        "text-decoder": "^1.1.0"
-      },
-      "optionalDependencies": {
-        "bare-events": "^2.2.0"
-      }
-    },
-    "node_modules/strip-final-newline": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
-      "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==",
-      "dev": true,
-      "engines": {
-        "node": ">=6"
-      }
-    },
-    "node_modules/supports-color": {
-      "version": "7.2.0",
-      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
-      "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
-      "dev": true,
-      "dependencies": {
-        "has-flag": "^4.0.0"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/supports-preserve-symlinks-flag": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
-      "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
-      "dev": true,
-      "license": "MIT",
-      "engines": {
-        "node": ">= 0.4"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/table-layout": {
-      "version": "4.1.1",
-      "resolved": "https://registry.npmjs.org/table-layout/-/table-layout-4.1.1.tgz",
-      "integrity": "sha512-iK5/YhZxq5GO5z8wb0bY1317uDF3Zjpha0QFFLA8/trAoiLbQD0HUbMesEaxyzUgDxi2QlcbM8IvqOlEjgoXBA==",
-      "dev": true,
-      "dependencies": {
-        "array-back": "^6.2.2",
-        "wordwrapjs": "^5.1.0"
-      },
-      "engines": {
-        "node": ">=12.17"
-      }
-    },
-    "node_modules/table-layout/node_modules/array-back": {
-      "version": "6.2.2",
-      "resolved": "https://registry.npmjs.org/array-back/-/array-back-6.2.2.tgz",
-      "integrity": "sha512-gUAZ7HPyb4SJczXAMUXMGAvI976JoK3qEx9v1FTmeYuJj0IBiaKttG1ydtGKdkfqWkIkouke7nG8ufGy77+Cvw==",
-      "dev": true,
-      "engines": {
-        "node": ">=12.17"
-      }
-    },
-    "node_modules/tar-fs": {
-      "version": "3.0.8",
-      "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.8.tgz",
-      "integrity": "sha512-ZoROL70jptorGAlgAYiLoBLItEKw/fUxg9BSYK/dF/GAGYFJOJJJMvjPAKDJraCXFwadD456FCuvLWgfhMsPwg==",
-      "dev": true,
-      "dependencies": {
-        "pump": "^3.0.0",
-        "tar-stream": "^3.1.5"
-      },
-      "optionalDependencies": {
-        "bare-fs": "^4.0.1",
-        "bare-path": "^3.0.0"
-      }
-    },
-    "node_modules/tar-stream": {
-      "version": "3.1.7",
-      "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz",
-      "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==",
-      "dev": true,
-      "dependencies": {
-        "b4a": "^1.6.4",
-        "fast-fifo": "^1.2.0",
-        "streamx": "^2.15.0"
-      }
-    },
-    "node_modules/text-decoder": {
-      "version": "1.2.3",
-      "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz",
-      "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==",
-      "dev": true,
-      "dependencies": {
-        "b4a": "^1.6.4"
-      }
-    },
-    "node_modules/through": {
-      "version": "2.3.8",
-      "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
-      "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==",
-      "dev": true
-    },
-    "node_modules/tinyglobby": {
-      "version": "0.2.13",
-      "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz",
-      "integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "fdir": "^6.4.4",
-        "picomatch": "^4.0.2"
-      },
-      "engines": {
-        "node": ">=12.0.0"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/SuperchupuDev"
-      }
-    },
-    "node_modules/tinyglobby/node_modules/fdir": {
-      "version": "6.4.4",
-      "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz",
-      "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==",
-      "dev": true,
-      "license": "MIT",
-      "peerDependencies": {
-        "picomatch": "^3 || ^4"
-      },
-      "peerDependenciesMeta": {
-        "picomatch": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/tinyglobby/node_modules/picomatch": {
-      "version": "4.0.2",
-      "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
-      "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
-      "dev": true,
-      "license": "MIT",
-      "engines": {
-        "node": ">=12"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/jonschlinkert"
-      }
-    },
-    "node_modules/to-regex-range": {
-      "version": "5.0.1",
-      "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
-      "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "is-number": "^7.0.0"
-      },
-      "engines": {
-        "node": ">=8.0"
-      }
-    },
-    "node_modules/toidentifier": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
-      "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
-      "dev": true,
-      "engines": {
-        "node": ">=0.6"
-      }
-    },
-    "node_modules/topojson-client": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmjs.org/topojson-client/-/topojson-client-3.1.0.tgz",
-      "integrity": "sha512-605uxS6bcYxGXw9qi62XyrV6Q3xwbndjachmNxu8HWTtVPxZfEJN9fd/SZS1Q54Sn2y0TMyMxFj/cJINqGHrKw==",
-      "license": "ISC",
-      "dependencies": {
-        "commander": "2"
-      },
-      "bin": {
-        "topo2geo": "bin/topo2geo",
-        "topomerge": "bin/topomerge",
-        "topoquantize": "bin/topoquantize"
-      }
-    },
-    "node_modules/topojson-client/node_modules/commander": {
-      "version": "2.20.3",
-      "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
-      "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
-      "license": "MIT"
-    },
-    "node_modules/tr46": {
-      "version": "0.0.3",
-      "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
-      "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
-      "license": "MIT"
-    },
-    "node_modules/tslib": {
-      "version": "2.8.1",
-      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
-      "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
-      "license": "0BSD"
-    },
-    "node_modules/tsscmp": {
-      "version": "1.0.6",
-      "resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz",
-      "integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==",
-      "dev": true,
-      "engines": {
-        "node": ">=0.6.x"
-      }
-    },
-    "node_modules/type-fest": {
-      "version": "0.21.3",
-      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz",
-      "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==",
-      "dev": true,
-      "engines": {
-        "node": ">=10"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
-      }
-    },
-    "node_modules/type-is": {
-      "version": "1.6.18",
-      "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
-      "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
-      "dev": true,
-      "dependencies": {
-        "media-typer": "0.3.0",
-        "mime-types": "~2.1.24"
-      },
-      "engines": {
-        "node": ">= 0.6"
-      }
-    },
-    "node_modules/typed-query-selector": {
-      "version": "2.12.0",
-      "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.0.tgz",
-      "integrity": "sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg==",
-      "dev": true,
-      "license": "MIT"
-    },
-    "node_modules/typescript": {
-      "version": "5.8.3",
-      "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
-      "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
-      "dev": true,
-      "license": "Apache-2.0",
-      "bin": {
-        "tsc": "bin/tsc",
-        "tsserver": "bin/tsserver"
-      },
-      "engines": {
-        "node": ">=14.17"
-      }
-    },
-    "node_modules/typical": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/typical/-/typical-4.0.0.tgz",
-      "integrity": "sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==",
-      "dev": true,
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/unbzip2-stream": {
-      "version": "1.4.3",
-      "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz",
-      "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==",
-      "dev": true,
-      "dependencies": {
-        "buffer": "^5.2.1",
-        "through": "^2.3.8"
-      }
-    },
-    "node_modules/undici-types": {
-      "version": "6.21.0",
-      "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
-      "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
-      "dev": true,
-      "license": "MIT"
-    },
-    "node_modules/unpipe": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
-      "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
-      "dev": true,
-      "engines": {
-        "node": ">= 0.8"
-      }
-    },
-    "node_modules/update-browserslist-db": {
-      "version": "1.1.3",
-      "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz",
-      "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==",
-      "dev": true,
-      "funding": [
-        {
-          "type": "opencollective",
-          "url": "https://opencollective.com/browserslist"
-        },
-        {
-          "type": "tidelift",
-          "url": "https://tidelift.com/funding/github/npm/browserslist"
-        },
-        {
-          "type": "github",
-          "url": "https://github.com/sponsors/ai"
-        }
-      ],
-      "license": "MIT",
-      "dependencies": {
-        "escalade": "^3.2.0",
-        "picocolors": "^1.1.1"
-      },
-      "bin": {
-        "update-browserslist-db": "cli.js"
-      },
-      "peerDependencies": {
-        "browserslist": ">= 4.21.0"
-      }
-    },
-    "node_modules/urlpattern-polyfill": {
-      "version": "10.0.0",
-      "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.0.0.tgz",
-      "integrity": "sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==",
-      "dev": true
-    },
-    "node_modules/v8-to-istanbul": {
-      "version": "9.3.0",
-      "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz",
-      "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==",
-      "dev": true,
-      "dependencies": {
-        "@jridgewell/trace-mapping": "^0.3.12",
-        "@types/istanbul-lib-coverage": "^2.0.1",
-        "convert-source-map": "^2.0.0"
-      },
-      "engines": {
-        "node": ">=10.12.0"
-      }
-    },
-    "node_modules/vary": {
-      "version": "1.1.2",
-      "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
-      "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
-      "dev": true,
-      "engines": {
-        "node": ">= 0.8"
-      }
-    },
-    "node_modules/vega": {
-      "version": "5.33.0",
-      "resolved": "https://registry.npmjs.org/vega/-/vega-5.33.0.tgz",
-      "integrity": "sha512-jNAGa7TxLojOpMMMrKMXXBos4K6AaLJbCgGDOw1YEkLRjUkh12pcf65J2lMSdEHjcEK47XXjKiOUVZ8L+MniBA==",
-      "license": "BSD-3-Clause",
-      "dependencies": {
-        "vega-crossfilter": "~4.1.3",
-        "vega-dataflow": "~5.7.7",
-        "vega-encode": "~4.10.2",
-        "vega-event-selector": "~3.0.1",
-        "vega-expression": "~5.2.0",
-        "vega-force": "~4.2.2",
-        "vega-format": "~1.1.3",
-        "vega-functions": "~5.18.0",
-        "vega-geo": "~4.4.3",
-        "vega-hierarchy": "~4.1.3",
-        "vega-label": "~1.3.1",
-        "vega-loader": "~4.5.3",
-        "vega-parser": "~6.6.0",
-        "vega-projection": "~1.6.2",
-        "vega-regression": "~1.3.1",
-        "vega-runtime": "~6.2.1",
-        "vega-scale": "~7.4.2",
-        "vega-scenegraph": "~4.13.1",
-        "vega-statistics": "~1.9.0",
-        "vega-time": "~2.1.3",
-        "vega-transforms": "~4.12.1",
-        "vega-typings": "~1.5.0",
-        "vega-util": "~1.17.2",
-        "vega-view": "~5.16.0",
-        "vega-view-transforms": "~4.6.1",
-        "vega-voronoi": "~4.2.4",
-        "vega-wordcloud": "~4.1.6"
-      }
-    },
-    "node_modules/vega-canvas": {
-      "version": "1.2.7",
-      "resolved": "https://registry.npmjs.org/vega-canvas/-/vega-canvas-1.2.7.tgz",
-      "integrity": "sha512-OkJ9CACVcN9R5Pi9uF6MZBF06pO6qFpDYHWSKBJsdHP5o724KrsgR6UvbnXFH82FdsiTOff/HqjuaG8C7FL+9Q==",
-      "license": "BSD-3-Clause"
-    },
-    "node_modules/vega-crossfilter": {
-      "version": "4.1.3",
-      "resolved": "https://registry.npmjs.org/vega-crossfilter/-/vega-crossfilter-4.1.3.tgz",
-      "integrity": "sha512-nyPJAXAUABc3EocUXvAL1J/IWotZVsApIcvOeZaUdEQEtZ7bt8VtP2nj3CLbHBA8FZZVV+K6SmdwvCOaAD4wFQ==",
-      "license": "BSD-3-Clause",
-      "dependencies": {
-        "d3-array": "^3.2.2",
-        "vega-dataflow": "^5.7.7",
-        "vega-util": "^1.17.3"
-      }
-    },
-    "node_modules/vega-dataflow": {
-      "version": "5.7.7",
-      "resolved": "https://registry.npmjs.org/vega-dataflow/-/vega-dataflow-5.7.7.tgz",
-      "integrity": "sha512-R2NX2HvgXL+u4E6u+L5lKvvRiCtnE6N6l+umgojfi53suhhkFP+zB+2UAQo4syxuZ4763H1csfkKc4xpqLzKnw==",
-      "license": "BSD-3-Clause",
-      "dependencies": {
-        "vega-format": "^1.1.3",
-        "vega-loader": "^4.5.3",
-        "vega-util": "^1.17.3"
-      }
-    },
-    "node_modules/vega-embed": {
-      "version": "6.29.0",
-      "resolved": "https://registry.npmjs.org/vega-embed/-/vega-embed-6.29.0.tgz",
-      "integrity": "sha512-PmlshTLtLFLgWtF/b23T1OwX53AugJ9RZ3qPE2c01VFAbgt3/GSNI/etzA/GzdrkceXFma+FDHNXUppKuM0U6Q==",
-      "license": "BSD-3-Clause",
-      "dependencies": {
-        "fast-json-patch": "^3.1.1",
-        "json-stringify-pretty-compact": "^4.0.0",
-        "semver": "^7.6.3",
-        "tslib": "^2.8.1",
-        "vega-interpreter": "^1.0.5",
-        "vega-schema-url-parser": "^2.2.0",
-        "vega-themes": "^2.15.0",
-        "vega-tooltip": "^0.35.2"
-      },
-      "peerDependencies": {
-        "vega": "^5.21.0",
-        "vega-lite": "*"
-      }
-    },
-    "node_modules/vega-encode": {
-      "version": "4.10.2",
-      "resolved": "https://registry.npmjs.org/vega-encode/-/vega-encode-4.10.2.tgz",
-      "integrity": "sha512-fsjEY1VaBAmqwt7Jlpz0dpPtfQFiBdP9igEefvumSpy7XUxOJmDQcRDnT3Qh9ctkv3itfPfI9g8FSnGcv2b4jQ==",
-      "license": "BSD-3-Clause",
-      "dependencies": {
-        "d3-array": "^3.2.2",
-        "d3-interpolate": "^3.0.1",
-        "vega-dataflow": "^5.7.7",
-        "vega-scale": "^7.4.2",
-        "vega-util": "^1.17.3"
-      }
-    },
-    "node_modules/vega-event-selector": {
-      "version": "3.0.1",
-      "resolved": "https://registry.npmjs.org/vega-event-selector/-/vega-event-selector-3.0.1.tgz",
-      "integrity": "sha512-K5zd7s5tjr1LiOOkjGpcVls8GsH/f2CWCrWcpKy74gTCp+llCdwz0Enqo013ZlGaRNjfgD/o1caJRt3GSaec4A==",
-      "license": "BSD-3-Clause"
-    },
-    "node_modules/vega-expression": {
-      "version": "5.2.0",
-      "resolved": "https://registry.npmjs.org/vega-expression/-/vega-expression-5.2.0.tgz",
-      "integrity": "sha512-WRMa4ny3iZIVAzDlBh3ipY2QUuLk2hnJJbfbncPgvTF7BUgbIbKq947z+JicWksYbokl8n1JHXJoqi3XvpG0Zw==",
-      "license": "BSD-3-Clause",
-      "dependencies": {
-        "@types/estree": "^1.0.0",
-        "vega-util": "^1.17.3"
-      }
-    },
-    "node_modules/vega-force": {
-      "version": "4.2.2",
-      "resolved": "https://registry.npmjs.org/vega-force/-/vega-force-4.2.2.tgz",
-      "integrity": "sha512-cHZVaY2VNNIG2RyihhSiWniPd2W9R9kJq0znxzV602CgUVgxEfTKtx/lxnVCn8nNrdKAYrGiqIsBzIeKG1GWHw==",
-      "license": "BSD-3-Clause",
-      "dependencies": {
-        "d3-force": "^3.0.0",
-        "vega-dataflow": "^5.7.7",
-        "vega-util": "^1.17.3"
-      }
-    },
-    "node_modules/vega-format": {
-      "version": "1.1.3",
-      "resolved": "https://registry.npmjs.org/vega-format/-/vega-format-1.1.3.tgz",
-      "integrity": "sha512-wQhw7KR46wKJAip28FF/CicW+oiJaPAwMKdrxlnTA0Nv8Bf7bloRlc+O3kON4b4H1iALLr9KgRcYTOeXNs2MOA==",
-      "license": "BSD-3-Clause",
-      "dependencies": {
-        "d3-array": "^3.2.2",
-        "d3-format": "^3.1.0",
-        "d3-time-format": "^4.1.0",
-        "vega-time": "^2.1.3",
-        "vega-util": "^1.17.3"
-      }
-    },
-    "node_modules/vega-functions": {
-      "version": "5.18.0",
-      "resolved": "https://registry.npmjs.org/vega-functions/-/vega-functions-5.18.0.tgz",
-      "integrity": "sha512-+D+ey4bDAhZA2CChh7bRZrcqRUDevv05kd2z8xH+il7PbYQLrhi6g1zwvf8z3KpgGInFf5O13WuFK5DQGkz5lQ==",
-      "license": "BSD-3-Clause",
-      "dependencies": {
-        "d3-array": "^3.2.2",
-        "d3-color": "^3.1.0",
-        "d3-geo": "^3.1.0",
-        "vega-dataflow": "^5.7.7",
-        "vega-expression": "^5.2.0",
-        "vega-scale": "^7.4.2",
-        "vega-scenegraph": "^4.13.1",
-        "vega-selections": "^5.6.0",
-        "vega-statistics": "^1.9.0",
-        "vega-time": "^2.1.3",
-        "vega-util": "^1.17.3"
-      }
-    },
-    "node_modules/vega-geo": {
-      "version": "4.4.3",
-      "resolved": "https://registry.npmjs.org/vega-geo/-/vega-geo-4.4.3.tgz",
-      "integrity": "sha512-+WnnzEPKIU1/xTFUK3EMu2htN35gp9usNZcC0ZFg2up1/Vqu6JyZsX0PIO51oXSIeXn9bwk6VgzlOmJUcx92tA==",
-      "license": "BSD-3-Clause",
-      "dependencies": {
-        "d3-array": "^3.2.2",
-        "d3-color": "^3.1.0",
-        "d3-geo": "^3.1.0",
-        "vega-canvas": "^1.2.7",
-        "vega-dataflow": "^5.7.7",
-        "vega-projection": "^1.6.2",
-        "vega-statistics": "^1.9.0",
-        "vega-util": "^1.17.3"
-      }
-    },
-    "node_modules/vega-hierarchy": {
-      "version": "4.1.3",
-      "resolved": "https://registry.npmjs.org/vega-hierarchy/-/vega-hierarchy-4.1.3.tgz",
-      "integrity": "sha512-0Z+TYKRgOEo8XYXnJc2HWg1EGpcbNAhJ9Wpi9ubIbEyEHqIgjCIyFVN8d4nSfsJOcWDzsSmRqohBztxAhOCSaw==",
-      "license": "BSD-3-Clause",
-      "dependencies": {
-        "d3-hierarchy": "^3.1.2",
-        "vega-dataflow": "^5.7.7",
-        "vega-util": "^1.17.3"
-      }
-    },
-    "node_modules/vega-interpreter": {
-      "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/vega-interpreter/-/vega-interpreter-1.2.0.tgz",
-      "integrity": "sha512-p408/0IPevyR/bIKdXGNzOixkTYCkH83zNhGypRqDxd/qVrdJVrh9RcECOYx1MwEc6JTB1BeK2lArHiGGuG7Hw==",
-      "license": "BSD-3-Clause",
-      "dependencies": {
-        "vega-util": "^1.17.3"
-      }
-    },
-    "node_modules/vega-label": {
-      "version": "1.3.1",
-      "resolved": "https://registry.npmjs.org/vega-label/-/vega-label-1.3.1.tgz",
-      "integrity": "sha512-Emx4b5s7pvuRj3fBkAJ/E2snCoZACfKAwxVId7f/4kYVlAYLb5Swq6W8KZHrH4M9Qds1XJRUYW9/Y3cceqzEFA==",
-      "license": "BSD-3-Clause",
-      "dependencies": {
-        "vega-canvas": "^1.2.7",
-        "vega-dataflow": "^5.7.7",
-        "vega-scenegraph": "^4.13.1",
-        "vega-util": "^1.17.3"
-      }
-    },
-    "node_modules/vega-lite": {
-      "version": "5.23.0",
-      "resolved": "https://registry.npmjs.org/vega-lite/-/vega-lite-5.23.0.tgz",
-      "integrity": "sha512-l4J6+AWE3DIjvovEoHl2LdtCUkfm4zs8Xxx7INwZEAv+XVb6kR6vIN1gt3t2gN2gs/y4DYTs/RPoTeYAuEg6mA==",
-      "license": "BSD-3-Clause",
-      "dependencies": {
-        "json-stringify-pretty-compact": "~4.0.0",
-        "tslib": "~2.8.1",
-        "vega-event-selector": "~3.0.1",
-        "vega-expression": "~5.1.1",
-        "vega-util": "~1.17.2",
-        "yargs": "~17.7.2"
-      },
-      "bin": {
-        "vl2pdf": "bin/vl2pdf",
-        "vl2png": "bin/vl2png",
-        "vl2svg": "bin/vl2svg",
-        "vl2vg": "bin/vl2vg"
-      },
-      "engines": {
-        "node": ">=18"
-      },
-      "peerDependencies": {
-        "vega": "^5.24.0"
-      }
-    },
-    "node_modules/vega-lite/node_modules/vega-expression": {
-      "version": "5.1.2",
-      "resolved": "https://registry.npmjs.org/vega-expression/-/vega-expression-5.1.2.tgz",
-      "integrity": "sha512-fFeDTh4UtOxlZWL54jf1ZqJHinyerWq/ROiqrQxqLkNJRJ86RmxYTgXwt65UoZ/l4VUv9eAd2qoJeDEf610Umw==",
-      "license": "BSD-3-Clause",
-      "dependencies": {
-        "@types/estree": "^1.0.0",
-        "vega-util": "^1.17.3"
-      }
-    },
-    "node_modules/vega-loader": {
-      "version": "4.5.3",
-      "resolved": "https://registry.npmjs.org/vega-loader/-/vega-loader-4.5.3.tgz",
-      "integrity": "sha512-dUfIpxTLF2magoMaur+jXGvwMxjtdlDZaIS8lFj6N7IhUST6nIvBzuUlRM+zLYepI5GHtCLOnqdKU4XV0NggCA==",
-      "license": "BSD-3-Clause",
-      "dependencies": {
-        "d3-dsv": "^3.0.1",
-        "node-fetch": "^2.6.7",
-        "topojson-client": "^3.1.0",
-        "vega-format": "^1.1.3",
-        "vega-util": "^1.17.3"
-      }
-    },
-    "node_modules/vega-parser": {
-      "version": "6.6.0",
-      "resolved": "https://registry.npmjs.org/vega-parser/-/vega-parser-6.6.0.tgz",
-      "integrity": "sha512-jltyrwCTtWeidi/6VotLCybhIl+ehwnzvFWYOdWNUP0z/EskdB64YmawNwjCjzTBMemeiQtY6sJPPbewYqe3Vg==",
-      "license": "BSD-3-Clause",
-      "dependencies": {
-        "vega-dataflow": "^5.7.7",
-        "vega-event-selector": "^3.0.1",
-        "vega-functions": "^5.18.0",
-        "vega-scale": "^7.4.2",
-        "vega-util": "^1.17.3"
-      }
-    },
-    "node_modules/vega-projection": {
-      "version": "1.6.2",
-      "resolved": "https://registry.npmjs.org/vega-projection/-/vega-projection-1.6.2.tgz",
-      "integrity": "sha512-3pcVaQL9R3Zfk6PzopLX6awzrQUeYOXJzlfLGP2Xd93mqUepBa6m/reVrTUoSFXA3v9lfK4W/PS2AcVzD/MIcQ==",
-      "license": "BSD-3-Clause",
-      "dependencies": {
-        "d3-geo": "^3.1.0",
-        "d3-geo-projection": "^4.0.0",
-        "vega-scale": "^7.4.2"
-      }
-    },
-    "node_modules/vega-regression": {
-      "version": "1.3.1",
-      "resolved": "https://registry.npmjs.org/vega-regression/-/vega-regression-1.3.1.tgz",
-      "integrity": "sha512-AmccF++Z9uw4HNZC/gmkQGe6JsRxTG/R4QpbcSepyMvQN1Rj5KtVqMcmVFP1r3ivM4dYGFuPlzMWvuqp0iKMkQ==",
-      "license": "BSD-3-Clause",
-      "dependencies": {
-        "d3-array": "^3.2.2",
-        "vega-dataflow": "^5.7.7",
-        "vega-statistics": "^1.9.0",
-        "vega-util": "^1.17.3"
-      }
-    },
-    "node_modules/vega-runtime": {
-      "version": "6.2.1",
-      "resolved": "https://registry.npmjs.org/vega-runtime/-/vega-runtime-6.2.1.tgz",
-      "integrity": "sha512-b4eot3tWKCk++INWqot+6sLn3wDTj/HE+tRSbiaf8aecuniPMlwJEK7wWuhVGeW2Ae5n8fI/8TeTViaC94bNHA==",
-      "license": "BSD-3-Clause",
-      "dependencies": {
-        "vega-dataflow": "^5.7.7",
-        "vega-util": "^1.17.3"
-      }
-    },
-    "node_modules/vega-scale": {
-      "version": "7.4.2",
-      "resolved": "https://registry.npmjs.org/vega-scale/-/vega-scale-7.4.2.tgz",
-      "integrity": "sha512-o6Hl76aU1jlCK7Q8DPYZ8OGsp4PtzLdzI6nGpLt8rxoE78QuB3GBGEwGAQJitp4IF7Lb2rL5oAXEl3ZP6xf9jg==",
-      "license": "BSD-3-Clause",
-      "dependencies": {
-        "d3-array": "^3.2.2",
-        "d3-interpolate": "^3.0.1",
-        "d3-scale": "^4.0.2",
-        "d3-scale-chromatic": "^3.1.0",
-        "vega-time": "^2.1.3",
-        "vega-util": "^1.17.3"
-      }
-    },
-    "node_modules/vega-scenegraph": {
-      "version": "4.13.1",
-      "resolved": "https://registry.npmjs.org/vega-scenegraph/-/vega-scenegraph-4.13.1.tgz",
-      "integrity": "sha512-LFY9+sLIxRfdDI9ZTKjLoijMkIAzPLBWHpPkwv4NPYgdyx+0qFmv+puBpAUGUY9VZqAZ736Uj5NJY9zw+/M3yQ==",
-      "license": "BSD-3-Clause",
-      "dependencies": {
-        "d3-path": "^3.1.0",
-        "d3-shape": "^3.2.0",
-        "vega-canvas": "^1.2.7",
-        "vega-loader": "^4.5.3",
-        "vega-scale": "^7.4.2",
-        "vega-util": "^1.17.3"
-      }
-    },
-    "node_modules/vega-schema-url-parser": {
-      "version": "2.2.0",
-      "resolved": "https://registry.npmjs.org/vega-schema-url-parser/-/vega-schema-url-parser-2.2.0.tgz",
-      "integrity": "sha512-yAtdBnfYOhECv9YC70H2gEiqfIbVkq09aaE4y/9V/ovEFmH9gPKaEgzIZqgT7PSPQjKhsNkb6jk6XvSoboxOBw==",
-      "license": "BSD-3-Clause"
-    },
-    "node_modules/vega-selections": {
-      "version": "5.6.0",
-      "resolved": "https://registry.npmjs.org/vega-selections/-/vega-selections-5.6.0.tgz",
-      "integrity": "sha512-UE2w78rUUbaV3Ph+vQbQDwh8eywIJYRxBiZdxEG/Tr/KtFMLdy2BDgNZuuDO1Nv8jImPJwONmqjNhNDYwM0VJQ==",
-      "license": "BSD-3-Clause",
-      "dependencies": {
-        "d3-array": "3.2.4",
-        "vega-expression": "^5.2.0",
-        "vega-util": "^1.17.3"
-      }
-    },
-    "node_modules/vega-statistics": {
-      "version": "1.9.0",
-      "resolved": "https://registry.npmjs.org/vega-statistics/-/vega-statistics-1.9.0.tgz",
-      "integrity": "sha512-GAqS7mkatpXcMCQKWtFu1eMUKLUymjInU0O8kXshWaQrVWjPIO2lllZ1VNhdgE0qGj4oOIRRS11kzuijLshGXQ==",
-      "license": "BSD-3-Clause",
-      "dependencies": {
-        "d3-array": "^3.2.2"
-      }
-    },
-    "node_modules/vega-themes": {
-      "version": "2.15.0",
-      "resolved": "https://registry.npmjs.org/vega-themes/-/vega-themes-2.15.0.tgz",
-      "integrity": "sha512-DicRAKG9z+23A+rH/3w3QjJvKnlGhSbbUXGjBvYGseZ1lvj9KQ0BXZ2NS/+MKns59LNpFNHGi9us/wMlci4TOA==",
-      "license": "BSD-3-Clause",
-      "peerDependencies": {
-        "vega": "*",
-        "vega-lite": "*"
-      }
-    },
-    "node_modules/vega-time": {
-      "version": "2.1.3",
-      "resolved": "https://registry.npmjs.org/vega-time/-/vega-time-2.1.3.tgz",
-      "integrity": "sha512-hFcWPdTV844IiY0m97+WUoMLADCp+8yUQR1NStWhzBzwDDA7QEGGwYGxALhdMOaDTwkyoNj3V/nox2rQAJD/vQ==",
-      "license": "BSD-3-Clause",
-      "dependencies": {
-        "d3-array": "^3.2.2",
-        "d3-time": "^3.1.0",
-        "vega-util": "^1.17.3"
-      }
-    },
-    "node_modules/vega-tooltip": {
-      "version": "0.35.2",
-      "resolved": "https://registry.npmjs.org/vega-tooltip/-/vega-tooltip-0.35.2.tgz",
-      "integrity": "sha512-kuYcsAAKYn39ye5wKf2fq1BAxVcjoz0alvKp/G+7BWfIb94J0PHmwrJ5+okGefeStZnbXxINZEOKo7INHaj9GA==",
-      "license": "BSD-3-Clause",
-      "dependencies": {
-        "vega-util": "^1.17.2"
-      },
-      "optionalDependencies": {
-        "@rollup/rollup-linux-x64-gnu": "^4.24.4"
-      }
-    },
-    "node_modules/vega-transforms": {
-      "version": "4.12.1",
-      "resolved": "https://registry.npmjs.org/vega-transforms/-/vega-transforms-4.12.1.tgz",
-      "integrity": "sha512-Qxo+xeEEftY1jYyKgzOGc9NuW4/MqGm1YPZ5WrL9eXg2G0410Ne+xL/MFIjHF4hRX+3mgFF4Io2hPpfy/thjLg==",
-      "license": "BSD-3-Clause",
-      "dependencies": {
-        "d3-array": "^3.2.2",
-        "vega-dataflow": "^5.7.7",
-        "vega-statistics": "^1.9.0",
-        "vega-time": "^2.1.3",
-        "vega-util": "^1.17.3"
-      }
-    },
-    "node_modules/vega-typings": {
-      "version": "1.5.0",
-      "resolved": "https://registry.npmjs.org/vega-typings/-/vega-typings-1.5.0.tgz",
-      "integrity": "sha512-tcZ2HwmiQEOXIGyBMP8sdCnoFoVqHn4KQ4H0MQiHwzFU1hb1EXURhfc+Uamthewk4h/9BICtAM3AFQMjBGpjQA==",
-      "license": "BSD-3-Clause",
-      "dependencies": {
-        "@types/geojson": "7946.0.4",
-        "vega-event-selector": "^3.0.1",
-        "vega-expression": "^5.2.0",
-        "vega-util": "^1.17.3"
-      }
-    },
-    "node_modules/vega-util": {
-      "version": "1.17.3",
-      "resolved": "https://registry.npmjs.org/vega-util/-/vega-util-1.17.3.tgz",
-      "integrity": "sha512-nSNpZLUrRvFo46M5OK4O6x6f08WD1yOcEzHNlqivF+sDLSsVpstaF6fdJYwrbf/debFi2L9Tkp4gZQtssup9iQ==",
-      "license": "BSD-3-Clause"
-    },
-    "node_modules/vega-view": {
-      "version": "5.16.0",
-      "resolved": "https://registry.npmjs.org/vega-view/-/vega-view-5.16.0.tgz",
-      "integrity": "sha512-Nxp1MEAY+8bphIm+7BeGFzWPoJnX9+hgvze6wqCAPoM69YiyVR0o0VK8M2EESIL+22+Owr0Fdy94hWHnmon5tQ==",
-      "license": "BSD-3-Clause",
-      "dependencies": {
-        "d3-array": "^3.2.2",
-        "d3-timer": "^3.0.1",
-        "vega-dataflow": "^5.7.7",
-        "vega-format": "^1.1.3",
-        "vega-functions": "^5.18.0",
-        "vega-runtime": "^6.2.1",
-        "vega-scenegraph": "^4.13.1",
-        "vega-util": "^1.17.3"
-      }
-    },
-    "node_modules/vega-view-transforms": {
-      "version": "4.6.1",
-      "resolved": "https://registry.npmjs.org/vega-view-transforms/-/vega-view-transforms-4.6.1.tgz",
-      "integrity": "sha512-RYlyMJu5kZV4XXjmyTQKADJWDB25SMHsiF+B1rbE1p+pmdQPlp5tGdPl9r5dUJOp3p8mSt/NGI8GPGucmPMxtw==",
-      "license": "BSD-3-Clause",
-      "dependencies": {
-        "vega-dataflow": "^5.7.7",
-        "vega-scenegraph": "^4.13.1",
-        "vega-util": "^1.17.3"
-      }
-    },
-    "node_modules/vega-voronoi": {
-      "version": "4.2.4",
-      "resolved": "https://registry.npmjs.org/vega-voronoi/-/vega-voronoi-4.2.4.tgz",
-      "integrity": "sha512-lWNimgJAXGeRFu2Pz8axOUqVf1moYhD+5yhBzDSmckE9I5jLOyZc/XvgFTXwFnsVkMd1QW1vxJa+y9yfUblzYw==",
-      "license": "BSD-3-Clause",
-      "dependencies": {
-        "d3-delaunay": "^6.0.2",
-        "vega-dataflow": "^5.7.7",
-        "vega-util": "^1.17.3"
-      }
-    },
-    "node_modules/vega-wordcloud": {
-      "version": "4.1.6",
-      "resolved": "https://registry.npmjs.org/vega-wordcloud/-/vega-wordcloud-4.1.6.tgz",
-      "integrity": "sha512-lFmF3u9/ozU0P+WqPjeThQfZm0PigdbXDwpIUCxczrCXKYJLYFmZuZLZR7cxtmpZ0/yuvRvAJ4g123LXbSZF8A==",
-      "license": "BSD-3-Clause",
-      "dependencies": {
-        "vega-canvas": "^1.2.7",
-        "vega-dataflow": "^5.7.7",
-        "vega-scale": "^7.4.2",
-        "vega-statistics": "^1.9.0",
-        "vega-util": "^1.17.3"
-      }
-    },
-    "node_modules/vite": {
-      "version": "6.3.2",
-      "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.2.tgz",
-      "integrity": "sha512-ZSvGOXKGceizRQIZSz7TGJ0pS3QLlVY/9hwxVh17W3re67je1RKYzFHivZ/t0tubU78Vkyb9WnHPENSBCzbckg==",
-      "dev": true,
-      "dependencies": {
-        "esbuild": "^0.25.0",
-        "fdir": "^6.4.3",
-        "picomatch": "^4.0.2",
-        "postcss": "^8.5.3",
-        "rollup": "^4.34.9",
-        "tinyglobby": "^0.2.12"
-      },
-      "bin": {
-        "vite": "bin/vite.js"
-      },
-      "engines": {
-        "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
-      },
-      "funding": {
-        "url": "https://github.com/vitejs/vite?sponsor=1"
-      },
-      "optionalDependencies": {
-        "fsevents": "~2.3.3"
-      },
-      "peerDependencies": {
-        "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
-        "jiti": ">=1.21.0",
-        "less": "*",
-        "lightningcss": "^1.21.0",
-        "sass": "*",
-        "sass-embedded": "*",
-        "stylus": "*",
-        "sugarss": "*",
-        "terser": "^5.16.0",
-        "tsx": "^4.8.1",
-        "yaml": "^2.4.2"
-      },
-      "peerDependenciesMeta": {
-        "@types/node": {
-          "optional": true
-        },
-        "jiti": {
-          "optional": true
-        },
-        "less": {
-          "optional": true
-        },
-        "lightningcss": {
-          "optional": true
-        },
-        "sass": {
-          "optional": true
-        },
-        "sass-embedded": {
-          "optional": true
-        },
-        "stylus": {
-          "optional": true
-        },
-        "sugarss": {
-          "optional": true
-        },
-        "terser": {
-          "optional": true
-        },
-        "tsx": {
-          "optional": true
-        },
-        "yaml": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/vite-plugin-web-components-hmr": {
-      "version": "0.1.3",
-      "resolved": "https://registry.npmjs.org/vite-plugin-web-components-hmr/-/vite-plugin-web-components-hmr-0.1.3.tgz",
-      "integrity": "sha512-UF+YYOFyaie6cT7XPatRz2Xo1Fh3TdTwOdCq1Z0EjzMfdAlUiUdF3crkN0DdEhctWBvq18CHxJZUTKw7x1zSRQ==",
-      "dev": true,
-      "dependencies": {
-        "@babel/core": "^7.12.3",
-        "@babel/plugin-syntax-class-properties": "^7.12.13",
-        "@babel/plugin-syntax-import-assertions": "^7.12.1",
-        "@babel/plugin-syntax-top-level-await": "^7.12.1",
-        "picomatch": "^2.2.2"
-      },
-      "peerDependencies": {
-        "vite": ">=2"
-      }
-    },
-    "node_modules/vite/node_modules/fdir": {
-      "version": "6.4.4",
-      "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz",
-      "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==",
-      "dev": true,
-      "license": "MIT",
-      "peerDependencies": {
-        "picomatch": "^3 || ^4"
-      },
-      "peerDependenciesMeta": {
-        "picomatch": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/vite/node_modules/picomatch": {
-      "version": "4.0.2",
-      "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
-      "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
-      "dev": true,
-      "license": "MIT",
-      "engines": {
-        "node": ">=12"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/jonschlinkert"
-      }
-    },
-    "node_modules/webidl-conversions": {
-      "version": "3.0.1",
-      "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
-      "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
-      "license": "BSD-2-Clause"
-    },
-    "node_modules/whatwg-url": {
-      "version": "5.0.0",
-      "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
-      "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
-      "license": "MIT",
-      "dependencies": {
-        "tr46": "~0.0.3",
-        "webidl-conversions": "^3.0.0"
-      }
-    },
-    "node_modules/which": {
-      "version": "2.0.2",
-      "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
-      "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
-      "dev": true,
-      "license": "ISC",
-      "dependencies": {
-        "isexe": "^2.0.0"
-      },
-      "bin": {
-        "node-which": "bin/node-which"
-      },
-      "engines": {
-        "node": ">= 8"
-      }
-    },
-    "node_modules/wordwrapjs": {
-      "version": "5.1.0",
-      "resolved": "https://registry.npmjs.org/wordwrapjs/-/wordwrapjs-5.1.0.tgz",
-      "integrity": "sha512-JNjcULU2e4KJwUNv6CHgI46UvDGitb6dGryHajXTDiLgg1/RiGoPSDw4kZfYnwGtEXf2ZMeIewDQgFGzkCB2Sg==",
-      "dev": true,
-      "engines": {
-        "node": ">=12.17"
-      }
-    },
-    "node_modules/wrappy": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
-      "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
-      "dev": true
-    },
-    "node_modules/ws": {
-      "version": "7.5.10",
-      "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz",
-      "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==",
-      "dev": true,
-      "engines": {
-        "node": ">=8.3.0"
-      },
-      "peerDependencies": {
-        "bufferutil": "^4.0.1",
-        "utf-8-validate": "^5.0.2"
-      },
-      "peerDependenciesMeta": {
-        "bufferutil": {
-          "optional": true
-        },
-        "utf-8-validate": {
-          "optional": true
-        }
-      }
-    },
-    "node_modules/y18n": {
-      "version": "5.0.8",
-      "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
-      "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
-      "license": "ISC",
-      "engines": {
-        "node": ">=10"
-      }
-    },
-    "node_modules/yallist": {
-      "version": "3.1.1",
-      "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
-      "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
-      "dev": true,
-      "license": "ISC"
-    },
-    "node_modules/yargs": {
-      "version": "17.7.2",
-      "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
-      "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
-      "license": "MIT",
-      "dependencies": {
-        "cliui": "^8.0.1",
-        "escalade": "^3.1.1",
-        "get-caller-file": "^2.0.5",
-        "require-directory": "^2.1.1",
-        "string-width": "^4.2.3",
-        "y18n": "^5.0.5",
-        "yargs-parser": "^21.1.1"
-      },
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/yargs-parser": {
-      "version": "21.1.1",
-      "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
-      "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
-      "license": "ISC",
-      "engines": {
-        "node": ">=12"
-      }
-    },
-    "node_modules/yargs/node_modules/ansi-regex": {
-      "version": "5.0.1",
-      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
-      "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
-      "license": "MIT",
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/yargs/node_modules/emoji-regex": {
-      "version": "8.0.0",
-      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
-      "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
-      "license": "MIT"
-    },
-    "node_modules/yargs/node_modules/string-width": {
-      "version": "4.2.3",
-      "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
-      "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
-      "license": "MIT",
-      "dependencies": {
-        "emoji-regex": "^8.0.0",
-        "is-fullwidth-code-point": "^3.0.0",
-        "strip-ansi": "^6.0.1"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/yargs/node_modules/strip-ansi": {
-      "version": "6.0.1",
-      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
-      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
-      "license": "MIT",
-      "dependencies": {
-        "ansi-regex": "^5.0.1"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/yauzl": {
-      "version": "2.10.0",
-      "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
-      "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==",
-      "dev": true,
-      "dependencies": {
-        "buffer-crc32": "~0.2.3",
-        "fd-slicer": "~1.1.0"
-      }
-    },
-    "node_modules/ylru": {
-      "version": "1.4.0",
-      "resolved": "https://registry.npmjs.org/ylru/-/ylru-1.4.0.tgz",
-      "integrity": "sha512-2OQsPNEmBCvXuFlIni/a+Rn+R2pHW9INm0BxXJ4hVDA8TirqMj+J/Rp9ItLatT/5pZqWwefVrTQcHpixsxnVlA==",
-      "dev": true,
-      "engines": {
-        "node": ">= 4.0.0"
-      }
-    },
-    "node_modules/zod": {
-      "version": "3.23.8",
-      "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz",
-      "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==",
-      "dev": true,
-      "funding": {
-        "url": "https://github.com/sponsors/colinhacks"
-      }
-    }
-  }
-}
diff --git a/loop/webui/package.json b/loop/webui/package.json
deleted file mode 100644
index 262a4fc..0000000
--- a/loop/webui/package.json
+++ /dev/null
@@ -1,48 +0,0 @@
-{
-  "name": "webui",
-  "description": "Web UI for CodingAgent.",
-  "license": "ISC",
-  "author": "",
-  "version": "1.0.0",
-  "main": "dist/index.js",
-  "exports": {
-    ".": "./dist/index.js",
-    "./sketch-app-shell.js": "./dist/sketch-app-shell.js"
-  },
-  "scripts": {
-    "check": "tsc --noEmit",
-    "demo": "vite --open src/web-components/demo/index.html",
-    "dev": "vite",
-    "format": "prettier ./src --write",
-    "gentypes": "go run ../../cmd/go2ts -o src/types.ts",
-    "build": "go run ../../cmd/go2ts -o src/types.ts && tsc",
-    "watch": "tsc --watch",
-    "test": "tsc && playwright test -c playwright-ct.config.ts"
-  },
-  "dependencies": {
-    "@xterm/addon-fit": "^0.10.0",
-    "@xterm/xterm": "^5.5.0",
-    "diff2html": "3.4.51",
-    "lit": "^3.2.1",
-    "marked": "^15.0.7",
-    "sanitize-html": "^2.15.0",
-    "vega": "^5.33.0",
-    "vega-embed": "^6.29.0",
-    "vega-lite": "^5.23.0"
-  },
-  "devDependencies": {
-    "@sand4rt/experimental-ct-web": "^1.51.1",
-    "@types/marked": "^5.0.2",
-    "@types/mocha": "^10.0.7",
-    "@types/node": "^22.13.14",
-    "@web/test-runner": "^0.18.2",
-    "@web/test-runner-puppeteer": "^0.18.0",
-    "autoprefixer": "^10.4.21",
-    "esbuild": "^0.25.1",
-    "prettier": "3.5.3",
-    "typescript": "^5.8.2",
-    "vite": "^6.3.2",
-    "vite-plugin-web-components-hmr": "^0.1.3"
-  },
-  "keywords": []
-}
diff --git a/loop/webui/playwright-ct.config.ts b/loop/webui/playwright-ct.config.ts
deleted file mode 100644
index 0acbc5e..0000000
--- a/loop/webui/playwright-ct.config.ts
+++ /dev/null
@@ -1,46 +0,0 @@
-import { defineConfig, devices } from "@sand4rt/experimental-ct-web";
-
-/**
- * See https://playwright.dev/docs/test-configuration.
- */
-export default defineConfig({
-  testDir: "./src",
-  /* The base directory, relative to the config file, for snapshot files created with toMatchSnapshot and toHaveScreenshot. */
-  snapshotDir: "./__snapshots__",
-  /* Maximum time one test can run for. */
-  timeout: 10 * 1000,
-  /* Run tests in files in parallel */
-  fullyParallel: true,
-  /* Fail the build on CI if you accidentally left test.only in the source code. */
-  forbidOnly: !!process.env.CI,
-  /* Retry on CI only */
-  retries: process.env.CI ? 2 : 0,
-  /* Opt out of parallel tests on CI. */
-  workers: process.env.CI ? 1 : undefined,
-  /* Reporter to use. See https://playwright.dev/docs/test-reporters */
-  reporter: [["html", { open: process.env.CI ? "never" : "on-failure" }]],
-  /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
-  use: {
-    /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
-    trace: "on-first-retry",
-
-    /* Port to use for Playwright component endpoint. */
-    ctPort: 3100,
-  },
-
-  /* Configure projects for major browsers */
-  projects: [
-    {
-      name: "chromium",
-      use: { ...devices["Desktop Chrome"] },
-    },
-    // {
-    //   name: 'firefox',
-    //   use: { ...devices['Desktop Firefox'] },
-    // },
-    // {
-    //   name: 'webkit',
-    //   use: { ...devices['Desktop Safari'] },
-    // },
-  ],
-});
diff --git a/loop/webui/playwright/index.html b/loop/webui/playwright/index.html
deleted file mode 100644
index 56da040..0000000
--- a/loop/webui/playwright/index.html
+++ /dev/null
@@ -1,12 +0,0 @@
-<!doctype html>
-<html lang="en">
-  <head>
-    <meta charset="UTF-8" />
-    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
-    <title>Testing Page</title>
-  </head>
-  <body>
-    <div id="root"></div>
-    <script type="module" src="./index.ts"></script>
-  </body>
-</html>
diff --git a/loop/webui/playwright/index.ts b/loop/webui/playwright/index.ts
deleted file mode 100644
index ac6de14..0000000
--- a/loop/webui/playwright/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-// Import styles, initialize component theme here.
-// import '../src/common.css';
diff --git a/loop/webui/readme.md b/loop/webui/readme.md
deleted file mode 100644
index 49ca0e8..0000000
--- a/loop/webui/readme.md
+++ /dev/null
@@ -1,64 +0,0 @@
-# Loop WebUI
-
-A modern web interface for the CodingAgent loop.
-
-The server in the sibling directory (../server) exposes an HTTP API for
-the CodingAgent.
-
-## Development
-
-This module contains a TypeScript-based web UI for the Loop service. The TypeScript code is compiled into JavaScript using esbuild, and the resulting bundle is served by the Go server.
-
-### Prerequisites
-
-- Node.js and npm
-- Go 1.20 or later
-
-### Setup
-
-```bash
-# Install dependencies
-make install
-
-# Type checking only
-make check
-```
-
-### Development Mode
-
-For development, you can use watch mode:
-
-```bash
-make demo
-```
-
-This will launch a local web server that serves the demo pages for the web components. You can edit the TypeScript files, and the changes will be reflected in real-time.
-p
-
-#### VSCode
-
-If you are using Visual Studio Code, you can use the `Launch Chrome against localhost` launch configuration to run the demo server. This configuration is set up to automatically open a sketch page with dummy data in Chrome when you start debugging, supporting hot module reloading and breakpoints.
-
-## Integration with Go Server
-
-The TypeScript code is bundled into JavaScript using esbuild and then served by the Go HTTP server. The integration happens through the `webui` package, which provides a function to retrieve the built bundle.
-
-The server code accesses the built web UI through the `webui.GetBundle()` function, which returns a filesystem that can be used to serve the files.
-
-## File Structure
-
-- `src/`: TypeScript source files
-- `dist/`: Generated JavaScript bundle
-- `esbuild.go`: Go code for bundling TypeScript files
-- `Makefile`: Build tasks
-
-## Bundle Analysis
-
-You can analyze the size and dependency structure of the TypeScript bundles:
-
-```bash
-# Generate bundle metafiles in a temporary directory
-go run sketch.dev/cmd/bundle-analyzer
-```
-
-The tool generates metafiles that can be analyzed by dragging them onto the esbuild analyzer at https://esbuild.github.io/analyze/
diff --git a/loop/webui/src/data.ts b/loop/webui/src/data.ts
deleted file mode 100644
index 9b5aca9..0000000
--- a/loop/webui/src/data.ts
+++ /dev/null
@@ -1,400 +0,0 @@
-import { AgentMessage } from "./types";
-import { formatNumber } from "./utils";
-
-/**
- * Event types for data manager
- */
-export type DataManagerEventType = "dataChanged" | "connectionStatusChanged";
-
-/**
- * Connection status types
- */
-export type ConnectionStatus = "connected" | "disconnected" | "disabled";
-
-/**
- * State interface
- */
-export interface TimelineState {
-  hostname?: string;
-  working_dir?: string;
-  initial_commit?: string;
-  message_count?: number;
-  title?: string;
-  total_usage?: {
-    input_tokens: number;
-    output_tokens: number;
-    cache_read_input_tokens: number;
-    cache_creation_input_tokens: number;
-    total_cost_usd: number;
-  };
-}
-
-/**
- * DataManager - Class to manage timeline data, fetching, and polling
- */
-export class DataManager {
-  // State variables
-  private lastMessageCount: number = 0;
-  private nextFetchIndex: number = 0;
-  private currentFetchStartIndex: number = 0;
-  private currentPollController: AbortController | null = null;
-  private isFetchingMessages: boolean = false;
-  private isPollingEnabled: boolean = true;
-  private isFirstLoad: boolean = true;
-  private connectionStatus: ConnectionStatus = "disabled";
-  private messages: AgentMessage[] = [];
-  private timelineState: TimelineState | null = null;
-
-  // Event listeners
-  private eventListeners: Map<
-    DataManagerEventType,
-    Array<(...args: any[]) => void>
-  > = new Map();
-
-  constructor() {
-    // Initialize empty arrays for each event type
-    this.eventListeners.set("dataChanged", []);
-    this.eventListeners.set("connectionStatusChanged", []);
-  }
-
-  /**
-   * Initialize the data manager and fetch initial data
-   */
-  public async initialize(): Promise<void> {
-    try {
-      // Initial data fetch
-      await this.fetchData();
-      // Start polling for updates only if initial fetch succeeds
-      this.startPolling();
-    } catch (error) {
-      console.error("Initial data fetch failed, will retry via polling", error);
-      // Still start polling to recover
-      this.startPolling();
-    }
-  }
-
-  /**
-   * Get all messages
-   */
-  public getMessages(): AgentMessage[] {
-    return this.messages;
-  }
-
-  /**
-   * Get the current state
-   */
-  public getState(): TimelineState | null {
-    return this.timelineState;
-  }
-
-  /**
-   * Get the connection status
-   */
-  public getConnectionStatus(): ConnectionStatus {
-    return this.connectionStatus;
-  }
-
-  /**
-   * Get the isFirstLoad flag
-   */
-  public getIsFirstLoad(): boolean {
-    return this.isFirstLoad;
-  }
-
-  /**
-   * Get the currentFetchStartIndex
-   */
-  public getCurrentFetchStartIndex(): number {
-    return this.currentFetchStartIndex;
-  }
-
-  /**
-   * Add an event listener
-   */
-  public addEventListener(
-    event: DataManagerEventType,
-    callback: (...args: any[]) => void,
-  ): void {
-    const listeners = this.eventListeners.get(event) || [];
-    listeners.push(callback);
-    this.eventListeners.set(event, listeners);
-  }
-
-  /**
-   * Remove an event listener
-   */
-  public removeEventListener(
-    event: DataManagerEventType,
-    callback: (...args: any[]) => void,
-  ): void {
-    const listeners = this.eventListeners.get(event) || [];
-    const index = listeners.indexOf(callback);
-    if (index !== -1) {
-      listeners.splice(index, 1);
-      this.eventListeners.set(event, listeners);
-    }
-  }
-
-  /**
-   * Emit an event
-   */
-  private emitEvent(event: DataManagerEventType, ...args: any[]): void {
-    const listeners = this.eventListeners.get(event) || [];
-    listeners.forEach((callback) => callback(...args));
-  }
-
-  /**
-   * Set polling enabled/disabled state
-   */
-  public setPollingEnabled(enabled: boolean): void {
-    this.isPollingEnabled = enabled;
-
-    if (enabled) {
-      this.startPolling();
-    } else {
-      this.stopPolling();
-    }
-  }
-
-  /**
-   * Start polling for updates
-   */
-  public startPolling(): void {
-    this.stopPolling(); // Stop any existing polling
-
-    // Start long polling
-    this.longPoll();
-  }
-
-  /**
-   * Stop polling for updates
-   */
-  public stopPolling(): void {
-    // Abort any ongoing long poll request
-    if (this.currentPollController) {
-      this.currentPollController.abort();
-      this.currentPollController = null;
-    }
-
-    // If polling is disabled by user, set connection status to disabled
-    if (!this.isPollingEnabled) {
-      this.updateConnectionStatus("disabled");
-    }
-  }
-
-  /**
-   * Update the connection status
-   */
-  private updateConnectionStatus(status: ConnectionStatus): void {
-    if (this.connectionStatus !== status) {
-      this.connectionStatus = status;
-      this.emitEvent("connectionStatusChanged", status);
-    }
-  }
-
-  /**
-   * Long poll for updates
-   */
-  private async longPoll(): Promise<void> {
-    // Abort any existing poll request
-    if (this.currentPollController) {
-      this.currentPollController.abort();
-      this.currentPollController = null;
-    }
-
-    // If polling is disabled, don't start a new poll
-    if (!this.isPollingEnabled) {
-      return;
-    }
-
-    let timeoutId: number | undefined;
-
-    try {
-      // Create a new abort controller for this request
-      this.currentPollController = new AbortController();
-      const signal = this.currentPollController.signal;
-
-      // Get the URL with the current message count
-      const pollUrl = `state?poll=true&seen=${this.lastMessageCount}`;
-
-      // Make the long poll request
-      // Use explicit timeout to handle stalled connections (120s)
-      const controller = new AbortController();
-      timeoutId = window.setTimeout(() => controller.abort(), 120000);
-
-      interface CustomFetchOptions extends RequestInit {
-        [Symbol.toStringTag]?: unknown;
-      }
-
-      const fetchOptions: CustomFetchOptions = {
-        signal: controller.signal,
-        // Use the original signal to allow manual cancellation too
-        get [Symbol.toStringTag]() {
-          if (signal.aborted) controller.abort();
-          return "";
-        },
-      };
-
-      try {
-        const response = await fetch(pollUrl, fetchOptions);
-        // Clear the timeout since we got a response
-        clearTimeout(timeoutId);
-
-        // Parse the JSON response
-        const _data = await response.json();
-
-        // If we got here, data has changed, so fetch the latest data
-        await this.fetchData();
-
-        // Start a new long poll (if polling is still enabled)
-        if (this.isPollingEnabled) {
-          this.longPoll();
-        }
-      } catch (error) {
-        // Handle fetch errors inside the inner try block
-        clearTimeout(timeoutId);
-        throw error; // Re-throw to be caught by the outer catch block
-      }
-    } catch (error: unknown) {
-      // Clean up timeout if we're handling an error
-      if (timeoutId) clearTimeout(timeoutId);
-
-      // Don't log or treat manual cancellations as errors
-      const isErrorWithName = (
-        err: unknown,
-      ): err is { name: string; message?: string } =>
-        typeof err === "object" && err !== null && "name" in err;
-
-      if (
-        isErrorWithName(error) &&
-        error.name === "AbortError" &&
-        this.currentPollController?.signal.aborted
-      ) {
-        console.log("Polling cancelled by user");
-        return;
-      }
-
-      // Handle different types of errors with specific messages
-      let errorMessage = "Not connected";
-
-      if (isErrorWithName(error)) {
-        if (error.name === "AbortError") {
-          // This was our timeout abort
-          errorMessage = "Connection timeout - not connected";
-          console.error("Long polling timeout");
-        } else if (error.name === "SyntaxError") {
-          // JSON parsing error
-          errorMessage = "Invalid response from server - not connected";
-          console.error("JSON parsing error:", error);
-        } else if (
-          error.name === "TypeError" &&
-          error.message?.includes("NetworkError")
-        ) {
-          // Network connectivity issues
-          errorMessage = "Network connection lost - not connected";
-          console.error("Network error during polling:", error);
-        } else {
-          // Generic error
-          console.error("Long polling error:", error);
-        }
-      }
-
-      // Disable polling on error
-      this.isPollingEnabled = false;
-
-      // Update connection status to disconnected
-      this.updateConnectionStatus("disconnected");
-
-      // Emit an event that we're disconnected with the error message
-      this.emitEvent(
-        "connectionStatusChanged",
-        this.connectionStatus,
-        errorMessage,
-      );
-    }
-  }
-
-  /**
-   * Fetch timeline data
-   */
-  public async fetchData(): Promise<void> {
-    // If we're already fetching messages, don't start another fetch
-    if (this.isFetchingMessages) {
-      console.log("Already fetching messages, skipping request");
-      return;
-    }
-
-    this.isFetchingMessages = true;
-
-    try {
-      // Fetch state first
-      const stateResponse = await fetch("state");
-      const state = await stateResponse.json();
-      this.timelineState = state;
-
-      // Check if new messages are available
-      if (
-        state.message_count === this.lastMessageCount &&
-        this.lastMessageCount > 0
-      ) {
-        // No new messages, early return
-        this.isFetchingMessages = false;
-        this.emitEvent("dataChanged", { state, newMessages: [] });
-        return;
-      }
-
-      // Fetch messages with a start parameter
-      this.currentFetchStartIndex = this.nextFetchIndex;
-      const messagesResponse = await fetch(
-        `messages?start=${this.nextFetchIndex}`,
-      );
-      const newMessages = (await messagesResponse.json()) || [];
-
-      // Store messages in our array
-      if (this.nextFetchIndex === 0) {
-        // If this is the first fetch, replace the entire array
-        this.messages = [...newMessages];
-      } else {
-        // Otherwise append the new messages
-        this.messages = [...this.messages, ...newMessages];
-      }
-
-      // Update connection status to connected
-      this.updateConnectionStatus("connected");
-
-      // Update the last message index for next fetch
-      if (newMessages && newMessages.length > 0) {
-        this.nextFetchIndex += newMessages.length;
-      }
-
-      // Update the message count
-      this.lastMessageCount = state?.message_count ?? 0;
-
-      // Mark that we've completed first load
-      if (this.isFirstLoad) {
-        this.isFirstLoad = false;
-      }
-
-      // Emit an event that data has changed
-      this.emitEvent("dataChanged", {
-        state,
-        newMessages,
-        isFirstFetch: this.nextFetchIndex === newMessages.length,
-      });
-    } catch (error) {
-      console.error("Error fetching data:", error);
-
-      // Update connection status to disconnected
-      this.updateConnectionStatus("disconnected");
-
-      // Emit an event that we're disconnected
-      this.emitEvent(
-        "connectionStatusChanged",
-        this.connectionStatus,
-        "Not connected",
-      );
-    } finally {
-      this.isFetchingMessages = false;
-    }
-  }
-}
diff --git a/loop/webui/src/diff2.css b/loop/webui/src/diff2.css
deleted file mode 100644
index f716a01..0000000
--- a/loop/webui/src/diff2.css
+++ /dev/null
@@ -1,142 +0,0 @@
-/* Custom styles for diff2 view */
-
-/* Override container max-width for diff2 view */
-#diff2View .diff-container {
-  max-width: 100%;
-  width: 100%;
-}
-
-/* When diff2 view is active, allow container to expand to full width */
-.container.diff2-active,
-.timeline-container.diff-active {
-  max-width: 100%;
-  padding-left: 20px;
-  padding-right: 20px;
-}
-
-/* Fix line-height inheritance issue */
-.d2h-code-line,
-.d2h-code-line-ctn,
-.d2h-code-linenumber {
-  line-height: 1.4 !important;
-}
-
-/* Make diff2 file container use the full width */
-.d2h-file-wrapper {
-  width: 100%;
-  margin-bottom: 20px;
-}
-
-/* Make side-by-side view use the full width */
-.d2h-file-side-diff {
-  width: 50% !important;
-}
-
-/* Style for diff lines - for both side-by-side and unified views */
-.d2h-code-line,
-.d2h-code-side-line {
-  transition: background-color 0.2s;
-  position: relative;
-}
-
-.d2h-code-line:hover,
-.d2h-code-side-line:hover {
-  background-color: #e6f7ff !important;
-}
-
-/* Plus button styles for commenting */
-.d2h-gutter-comment-button {
-  display: none;
-  position: absolute;
-  right: 0; /* Adjusted from -11px to prevent layout shifts */
-  top: 50%;
-  transform: translateY(-50%);
-  width: 22px;
-  height: 22px;
-  background-color: #0366d6;
-  color: white;
-  border-radius: 50%;
-  text-align: center;
-  line-height: 20px;
-  font-size: 16px;
-  font-weight: bold;
-  cursor: pointer;
-  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
-  opacity: 0.9;
-  z-index: 100;
-  user-select: none;
-}
-
-.d2h-gutter-comment-button:hover {
-  background-color: #0256bd;
-  opacity: 1;
-}
-
-/* Show the plus button on row hover (including line number and code) and when hovering over the button itself */
-tr:hover .d2h-gutter-comment-button,
-.d2h-gutter-comment-button:hover {
-  display: block;
-}
-
-/* Ensure diff2html content uses all available space */
-.diff2html-content {
-  width: 100%;
-  overflow-x: auto;
-}
-
-/* Diff view controls */
-#diff-view-controls {
-  display: flex;
-  justify-content: flex-end;
-  padding: 10px;
-  background-color: #f5f5f5;
-  border-bottom: 1px solid #ddd;
-}
-
-.diff-view-format {
-  display: flex;
-  gap: 15px;
-}
-
-.diff-view-format label {
-  display: flex;
-  align-items: center;
-  gap: 5px;
-  cursor: pointer;
-  font-size: 14px;
-  user-select: none;
-}
-
-.diff-view-format input[type="radio"] {
-  margin: 0;
-  cursor: pointer;
-}
-
-/* Adjust code line padding to make room for the gutter button */
-.d2h-code-line-ctn {
-  position: relative;
-  padding-left: 14px !important;
-}
-
-/* Ensure gutter is wide enough for the plus button */
-.d2h-code-linenumber,
-.d2h-code-side-linenumber {
-  position: relative;
-  min-width: 60px !important; /* Increased from 45px to accommodate 3-digit line numbers plus button */
-  padding-right: 15px !important; /* Ensure space for the button */
-  overflow: visible !important; /* Prevent button from being clipped */
-  text-align: right; /* Ensure consistent text alignment */
-  box-sizing: border-box; /* Ensure padding is included in width calculation */
-}
-
-/* Ensure table rows and cells don't clip the button */
-.d2h-diff-table tr,
-.d2h-diff-table td {
-  overflow: visible !important;
-}
-
-/* Add a bit of padding between line number and code content for visual separation */
-.d2h-code-line-ctn,
-.d2h-code-side-line-ctn {
-  padding-left: 8px !important;
-}
diff --git a/loop/webui/src/diff2html.min.css b/loop/webui/src/diff2html.min.css
deleted file mode 100644
index 8014a13..0000000
--- a/loop/webui/src/diff2html.min.css
+++ /dev/null
@@ -1 +0,0 @@
-:host,:root{--d2h-bg-color:#fff;--d2h-border-color:#ddd;--d2h-dim-color:rgba(0,0,0,.3);--d2h-line-border-color:#eee;--d2h-file-header-bg-color:#f7f7f7;--d2h-file-header-border-color:#d8d8d8;--d2h-empty-placeholder-bg-color:#f1f1f1;--d2h-empty-placeholder-border-color:#e1e1e1;--d2h-selected-color:#c8e1ff;--d2h-ins-bg-color:#dfd;--d2h-ins-border-color:#b4e2b4;--d2h-ins-highlight-bg-color:#97f295;--d2h-ins-label-color:#399839;--d2h-del-bg-color:#fee8e9;--d2h-del-border-color:#e9aeae;--d2h-del-highlight-bg-color:#ffb6ba;--d2h-del-label-color:#c33;--d2h-change-del-color:#fdf2d0;--d2h-change-ins-color:#ded;--d2h-info-bg-color:#f8fafd;--d2h-info-border-color:#d5e4f2;--d2h-change-label-color:#d0b44c;--d2h-moved-label-color:#3572b0;--d2h-dark-color:#e6edf3;--d2h-dark-bg-color:#0d1117;--d2h-dark-border-color:#30363d;--d2h-dark-dim-color:#6e7681;--d2h-dark-line-border-color:#21262d;--d2h-dark-file-header-bg-color:#161b22;--d2h-dark-file-header-border-color:#30363d;--d2h-dark-empty-placeholder-bg-color:hsla(215,8%,47%,.1);--d2h-dark-empty-placeholder-border-color:#30363d;--d2h-dark-selected-color:rgba(56,139,253,.1);--d2h-dark-ins-bg-color:rgba(46,160,67,.15);--d2h-dark-ins-border-color:rgba(46,160,67,.4);--d2h-dark-ins-highlight-bg-color:rgba(46,160,67,.4);--d2h-dark-ins-label-color:#3fb950;--d2h-dark-del-bg-color:rgba(248,81,73,.1);--d2h-dark-del-border-color:rgba(248,81,73,.4);--d2h-dark-del-highlight-bg-color:rgba(248,81,73,.4);--d2h-dark-del-label-color:#f85149;--d2h-dark-change-del-color:rgba(210,153,34,.2);--d2h-dark-change-ins-color:rgba(46,160,67,.25);--d2h-dark-info-bg-color:rgba(56,139,253,.1);--d2h-dark-info-border-color:rgba(56,139,253,.4);--d2h-dark-change-label-color:#d29922;--d2h-dark-moved-label-color:#3572b0}.d2h-wrapper{text-align:left}.d2h-file-header{background-color:#f7f7f7;background-color:var(--d2h-file-header-bg-color);border-bottom:1px solid #d8d8d8;border-bottom:1px solid var(--d2h-file-header-border-color);display:-webkit-box;display:-ms-flexbox;display:flex;font-family:Source Sans Pro,Helvetica Neue,Helvetica,Arial,sans-serif;height:35px;padding:5px 10px}.d2h-file-header.d2h-sticky-header{position:sticky;top:0;z-index:1}.d2h-file-stats{display:-webkit-box;display:-ms-flexbox;display:flex;font-size:14px;margin-left:auto}.d2h-lines-added{border:1px solid #b4e2b4;border:1px solid var(--d2h-ins-border-color);border-radius:5px 0 0 5px;color:#399839;color:var(--d2h-ins-label-color);padding:2px;text-align:right;vertical-align:middle}.d2h-lines-deleted{border:1px solid #e9aeae;border:1px solid var(--d2h-del-border-color);border-radius:0 5px 5px 0;color:#c33;color:var(--d2h-del-label-color);margin-left:1px;padding:2px;text-align:left;vertical-align:middle}.d2h-file-name-wrapper{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;font-size:15px;width:100%}.d2h-file-name{overflow-x:hidden;text-overflow:ellipsis;white-space:nowrap}.d2h-file-wrapper{border:1px solid #ddd;border:1px solid var(--d2h-border-color);border-radius:3px;margin-bottom:1em}.d2h-file-collapse{-webkit-box-pack:end;-ms-flex-pack:end;cursor:pointer;display:none;font-size:12px;justify-content:flex-end;-webkit-box-align:center;-ms-flex-align:center;align-items:center;border:1px solid #ddd;border:1px solid var(--d2h-border-color);border-radius:3px;padding:4px 8px}.d2h-file-collapse.d2h-selected{background-color:#c8e1ff;background-color:var(--d2h-selected-color)}.d2h-file-collapse-input{margin:0 4px 0 0}.d2h-diff-table{border-collapse:collapse;font-family:Menlo,Consolas,monospace;font-size:13px;width:100%}.d2h-files-diff{display:-webkit-box;display:-ms-flexbox;display:flex;width:100%}.d2h-file-diff{overflow-y:hidden}.d2h-file-diff.d2h-d-none,.d2h-files-diff.d2h-d-none{display:none}.d2h-file-side-diff{display:inline-block;overflow-x:scroll;overflow-y:hidden;width:50%}.d2h-code-line{padding:0 8em;width:calc(100% - 16em)}.d2h-code-line,.d2h-code-side-line{display:inline-block;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;white-space:nowrap}.d2h-code-side-line{padding:0 4.5em;width:calc(100% - 9em)}.d2h-code-line-ctn{background:none;display:inline-block;padding:0;word-wrap:normal;-webkit-user-select:text;-moz-user-select:text;-ms-user-select:text;user-select:text;vertical-align:middle;white-space:pre;width:100%}.d2h-code-line del,.d2h-code-side-line del{background-color:#ffb6ba;background-color:var(--d2h-del-highlight-bg-color)}.d2h-code-line del,.d2h-code-line ins,.d2h-code-side-line del,.d2h-code-side-line ins{border-radius:.2em;display:inline-block;margin-top:-1px;-webkit-text-decoration:none;text-decoration:none}.d2h-code-line ins,.d2h-code-side-line ins{background-color:#97f295;background-color:var(--d2h-ins-highlight-bg-color);text-align:left}.d2h-code-line-prefix{background:none;display:inline;padding:0;word-wrap:normal;white-space:pre}.line-num1{float:left}.line-num1,.line-num2{-webkit-box-sizing:border-box;box-sizing:border-box;overflow:hidden;padding:0 .5em;text-overflow:ellipsis;width:3.5em}.line-num2{float:right}.d2h-code-linenumber{background-color:#fff;background-color:var(--d2h-bg-color);border:solid #eee;border:solid var(--d2h-line-border-color);border-width:0 1px;-webkit-box-sizing:border-box;box-sizing:border-box;color:rgba(0,0,0,.3);color:var(--d2h-dim-color);cursor:pointer;display:inline-block;position:absolute;text-align:right;width:7.5em}.d2h-code-linenumber:after{content:"\200b"}.d2h-code-side-linenumber{background-color:#fff;background-color:var(--d2h-bg-color);border:solid #eee;border:solid var(--d2h-line-border-color);border-width:0 1px;-webkit-box-sizing:border-box;box-sizing:border-box;color:rgba(0,0,0,.3);color:var(--d2h-dim-color);cursor:pointer;display:inline-block;overflow:hidden;padding:0 .5em;position:absolute;text-align:right;text-overflow:ellipsis;width:4em}.d2h-code-side-linenumber:after{content:"\200b"}.d2h-code-side-emptyplaceholder,.d2h-emptyplaceholder{background-color:#f1f1f1;background-color:var(--d2h-empty-placeholder-bg-color);border-color:#e1e1e1;border-color:var(--d2h-empty-placeholder-border-color)}.d2h-code-line-prefix,.d2h-code-linenumber,.d2h-code-side-linenumber,.d2h-emptyplaceholder{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.d2h-code-linenumber,.d2h-code-side-linenumber{direction:rtl}.d2h-del{background-color:#fee8e9;background-color:var(--d2h-del-bg-color);border-color:#e9aeae;border-color:var(--d2h-del-border-color)}.d2h-ins{background-color:#dfd;background-color:var(--d2h-ins-bg-color);border-color:#b4e2b4;border-color:var(--d2h-ins-border-color)}.d2h-info{background-color:#f8fafd;background-color:var(--d2h-info-bg-color);border-color:#d5e4f2;border-color:var(--d2h-info-border-color);color:rgba(0,0,0,.3);color:var(--d2h-dim-color)}.d2h-file-diff .d2h-del.d2h-change{background-color:#fdf2d0;background-color:var(--d2h-change-del-color)}.d2h-file-diff .d2h-ins.d2h-change{background-color:#ded;background-color:var(--d2h-change-ins-color)}.d2h-file-list-wrapper{margin-bottom:10px}.d2h-file-list-wrapper a{-webkit-text-decoration:none;text-decoration:none}.d2h-file-list-wrapper a,.d2h-file-list-wrapper a:visited{color:#3572b0;color:var(--d2h-moved-label-color)}.d2h-file-list-header{text-align:left}.d2h-file-list-title{font-weight:700}.d2h-file-list-line{display:-webkit-box;display:-ms-flexbox;display:flex;text-align:left}.d2h-file-list{display:block;list-style:none;margin:0;padding:0}.d2h-file-list>li{border-bottom:1px solid #ddd;border-bottom:1px solid var(--d2h-border-color);margin:0;padding:5px 10px}.d2h-file-list>li:last-child{border-bottom:none}.d2h-file-switch{cursor:pointer;display:none;font-size:10px}.d2h-icon{margin-right:10px;vertical-align:middle;fill:currentColor}.d2h-deleted{color:#c33;color:var(--d2h-del-label-color)}.d2h-added{color:#399839;color:var(--d2h-ins-label-color)}.d2h-changed{color:#d0b44c;color:var(--d2h-change-label-color)}.d2h-moved{color:#3572b0;color:var(--d2h-moved-label-color)}.d2h-tag{background-color:#fff;background-color:var(--d2h-bg-color);display:-webkit-box;display:-ms-flexbox;display:flex;font-size:10px;margin-left:5px;padding:0 2px}.d2h-deleted-tag{border:1px solid #c33;border:1px solid var(--d2h-del-label-color)}.d2h-added-tag{border:1px solid #399839;border:1px solid var(--d2h-ins-label-color)}.d2h-changed-tag{border:1px solid #d0b44c;border:1px solid var(--d2h-change-label-color)}.d2h-moved-tag{border:1px solid #3572b0;border:1px solid var(--d2h-moved-label-color)}.d2h-dark-color-scheme{background-color:#0d1117;background-color:var(--d2h-dark-bg-color);color:#e6edf3;color:var(--d2h-dark-color)}.d2h-dark-color-scheme .d2h-file-header{background-color:#161b22;background-color:var(--d2h-dark-file-header-bg-color);border-bottom:#30363d;border-bottom:var(--d2h-dark-file-header-border-color)}.d2h-dark-color-scheme .d2h-lines-added{border:1px solid rgba(46,160,67,.4);border:1px solid var(--d2h-dark-ins-border-color);color:#3fb950;color:var(--d2h-dark-ins-label-color)}.d2h-dark-color-scheme .d2h-lines-deleted{border:1px solid rgba(248,81,73,.4);border:1px solid var(--d2h-dark-del-border-color);color:#f85149;color:var(--d2h-dark-del-label-color)}.d2h-dark-color-scheme .d2h-code-line del,.d2h-dark-color-scheme .d2h-code-side-line del{background-color:rgba(248,81,73,.4);background-color:var(--d2h-dark-del-highlight-bg-color)}.d2h-dark-color-scheme .d2h-code-line ins,.d2h-dark-color-scheme .d2h-code-side-line ins{background-color:rgba(46,160,67,.4);background-color:var(--d2h-dark-ins-highlight-bg-color)}.d2h-dark-color-scheme .d2h-diff-tbody{border-color:#30363d;border-color:var(--d2h-dark-border-color)}.d2h-dark-color-scheme .d2h-code-side-linenumber{background-color:#0d1117;background-color:var(--d2h-dark-bg-color);border-color:#21262d;border-color:var(--d2h-dark-line-border-color);color:#6e7681;color:var(--d2h-dark-dim-color)}.d2h-dark-color-scheme .d2h-files-diff .d2h-code-side-emptyplaceholder,.d2h-dark-color-scheme .d2h-files-diff .d2h-emptyplaceholder{background-color:hsla(215,8%,47%,.1);background-color:var(--d2h-dark-empty-placeholder-bg-color);border-color:#30363d;border-color:var(--d2h-dark-empty-placeholder-border-color)}.d2h-dark-color-scheme .d2h-code-linenumber{background-color:#0d1117;background-color:var(--d2h-dark-bg-color);border-color:#21262d;border-color:var(--d2h-dark-line-border-color);color:#6e7681;color:var(--d2h-dark-dim-color)}.d2h-dark-color-scheme .d2h-del{background-color:rgba(248,81,73,.1);background-color:var(--d2h-dark-del-bg-color);border-color:rgba(248,81,73,.4);border-color:var(--d2h-dark-del-border-color)}.d2h-dark-color-scheme .d2h-ins{background-color:rgba(46,160,67,.15);background-color:var(--d2h-dark-ins-bg-color);border-color:rgba(46,160,67,.4);border-color:var(--d2h-dark-ins-border-color)}.d2h-dark-color-scheme .d2h-info{background-color:rgba(56,139,253,.1);background-color:var(--d2h-dark-info-bg-color);border-color:rgba(56,139,253,.4);border-color:var(--d2h-dark-info-border-color);color:#6e7681;color:var(--d2h-dark-dim-color)}.d2h-dark-color-scheme .d2h-file-diff .d2h-del.d2h-change{background-color:rgba(210,153,34,.2);background-color:var(--d2h-dark-change-del-color)}.d2h-dark-color-scheme .d2h-file-diff .d2h-ins.d2h-change{background-color:rgba(46,160,67,.25);background-color:var(--d2h-dark-change-ins-color)}.d2h-dark-color-scheme .d2h-file-wrapper{border:1px solid #30363d;border:1px solid var(--d2h-dark-border-color)}.d2h-dark-color-scheme .d2h-file-collapse{border:1px solid #0d1117;border:1px solid var(--d2h-dark-bg-color)}.d2h-dark-color-scheme .d2h-file-collapse.d2h-selected{background-color:rgba(56,139,253,.1);background-color:var(--d2h-dark-selected-color)}.d2h-dark-color-scheme .d2h-file-list-wrapper a,.d2h-dark-color-scheme .d2h-file-list-wrapper a:visited{color:#3572b0;color:var(--d2h-dark-moved-label-color)}.d2h-dark-color-scheme .d2h-file-list>li{border-bottom:1px solid #0d1117;border-bottom:1px solid var(--d2h-dark-bg-color)}.d2h-dark-color-scheme .d2h-deleted{color:#f85149;color:var(--d2h-dark-del-label-color)}.d2h-dark-color-scheme .d2h-added{color:#3fb950;color:var(--d2h-dark-ins-label-color)}.d2h-dark-color-scheme .d2h-changed{color:#d29922;color:var(--d2h-dark-change-label-color)}.d2h-dark-color-scheme .d2h-moved{color:#3572b0;color:var(--d2h-dark-moved-label-color)}.d2h-dark-color-scheme .d2h-tag{background-color:#0d1117;background-color:var(--d2h-dark-bg-color)}.d2h-dark-color-scheme .d2h-deleted-tag{border:1px solid #f85149;border:1px solid var(--d2h-dark-del-label-color)}.d2h-dark-color-scheme .d2h-added-tag{border:1px solid #3fb950;border:1px solid var(--d2h-dark-ins-label-color)}.d2h-dark-color-scheme .d2h-changed-tag{border:1px solid #d29922;border:1px solid var(--d2h-dark-change-label-color)}.d2h-dark-color-scheme .d2h-moved-tag{border:1px solid #3572b0;border:1px solid var(--d2h-dark-moved-label-color)}@media (prefers-color-scheme:dark){.d2h-auto-color-scheme{background-color:#0d1117;background-color:var(--d2h-dark-bg-color);color:#e6edf3;color:var(--d2h-dark-color)}.d2h-auto-color-scheme .d2h-file-header{background-color:#161b22;background-color:var(--d2h-dark-file-header-bg-color);border-bottom:#30363d;border-bottom:var(--d2h-dark-file-header-border-color)}.d2h-auto-color-scheme .d2h-lines-added{border:1px solid rgba(46,160,67,.4);border:1px solid var(--d2h-dark-ins-border-color);color:#3fb950;color:var(--d2h-dark-ins-label-color)}.d2h-auto-color-scheme .d2h-lines-deleted{border:1px solid rgba(248,81,73,.4);border:1px solid var(--d2h-dark-del-border-color);color:#f85149;color:var(--d2h-dark-del-label-color)}.d2h-auto-color-scheme .d2h-code-line del,.d2h-auto-color-scheme .d2h-code-side-line del{background-color:rgba(248,81,73,.4);background-color:var(--d2h-dark-del-highlight-bg-color)}.d2h-auto-color-scheme .d2h-code-line ins,.d2h-auto-color-scheme .d2h-code-side-line ins{background-color:rgba(46,160,67,.4);background-color:var(--d2h-dark-ins-highlight-bg-color)}.d2h-auto-color-scheme .d2h-diff-tbody{border-color:#30363d;border-color:var(--d2h-dark-border-color)}.d2h-auto-color-scheme .d2h-code-side-linenumber{background-color:#0d1117;background-color:var(--d2h-dark-bg-color);border-color:#21262d;border-color:var(--d2h-dark-line-border-color);color:#6e7681;color:var(--d2h-dark-dim-color)}.d2h-auto-color-scheme .d2h-files-diff .d2h-code-side-emptyplaceholder,.d2h-auto-color-scheme .d2h-files-diff .d2h-emptyplaceholder{background-color:hsla(215,8%,47%,.1);background-color:var(--d2h-dark-empty-placeholder-bg-color);border-color:#30363d;border-color:var(--d2h-dark-empty-placeholder-border-color)}.d2h-auto-color-scheme .d2h-code-linenumber{background-color:#0d1117;background-color:var(--d2h-dark-bg-color);border-color:#21262d;border-color:var(--d2h-dark-line-border-color);color:#6e7681;color:var(--d2h-dark-dim-color)}.d2h-auto-color-scheme .d2h-del{background-color:rgba(248,81,73,.1);background-color:var(--d2h-dark-del-bg-color);border-color:rgba(248,81,73,.4);border-color:var(--d2h-dark-del-border-color)}.d2h-auto-color-scheme .d2h-ins{background-color:rgba(46,160,67,.15);background-color:var(--d2h-dark-ins-bg-color);border-color:rgba(46,160,67,.4);border-color:var(--d2h-dark-ins-border-color)}.d2h-auto-color-scheme .d2h-info{background-color:rgba(56,139,253,.1);background-color:var(--d2h-dark-info-bg-color);border-color:rgba(56,139,253,.4);border-color:var(--d2h-dark-info-border-color);color:#6e7681;color:var(--d2h-dark-dim-color)}.d2h-auto-color-scheme .d2h-file-diff .d2h-del.d2h-change{background-color:rgba(210,153,34,.2);background-color:var(--d2h-dark-change-del-color)}.d2h-auto-color-scheme .d2h-file-diff .d2h-ins.d2h-change{background-color:rgba(46,160,67,.25);background-color:var(--d2h-dark-change-ins-color)}.d2h-auto-color-scheme .d2h-file-wrapper{border:1px solid #30363d;border:1px solid var(--d2h-dark-border-color)}.d2h-auto-color-scheme .d2h-file-collapse{border:1px solid #0d1117;border:1px solid var(--d2h-dark-bg-color)}.d2h-auto-color-scheme .d2h-file-collapse.d2h-selected{background-color:rgba(56,139,253,.1);background-color:var(--d2h-dark-selected-color)}.d2h-auto-color-scheme .d2h-file-list-wrapper a,.d2h-auto-color-scheme .d2h-file-list-wrapper a:visited{color:#3572b0;color:var(--d2h-dark-moved-label-color)}.d2h-auto-color-scheme .d2h-file-list>li{border-bottom:1px solid #0d1117;border-bottom:1px solid var(--d2h-dark-bg-color)}.d2h-dark-color-scheme .d2h-deleted{color:#f85149;color:var(--d2h-dark-del-label-color)}.d2h-auto-color-scheme .d2h-added{color:#3fb950;color:var(--d2h-dark-ins-label-color)}.d2h-auto-color-scheme .d2h-changed{color:#d29922;color:var(--d2h-dark-change-label-color)}.d2h-auto-color-scheme .d2h-moved{color:#3572b0;color:var(--d2h-dark-moved-label-color)}.d2h-auto-color-scheme .d2h-tag{background-color:#0d1117;background-color:var(--d2h-dark-bg-color)}.d2h-auto-color-scheme .d2h-deleted-tag{border:1px solid #f85149;border:1px solid var(--d2h-dark-del-label-color)}.d2h-auto-color-scheme .d2h-added-tag{border:1px solid #3fb950;border:1px solid var(--d2h-dark-ins-label-color)}.d2h-auto-color-scheme .d2h-changed-tag{border:1px solid #d29922;border:1px solid var(--d2h-dark-change-label-color)}.d2h-auto-color-scheme .d2h-moved-tag{border:1px solid #3572b0;border:1px solid var(--d2h-dark-moved-label-color)}}
\ No newline at end of file
diff --git a/loop/webui/src/sketch-app-shell.css b/loop/webui/src/sketch-app-shell.css
deleted file mode 100644
index 57c96df..0000000
--- a/loop/webui/src/sketch-app-shell.css
+++ /dev/null
@@ -1,22 +0,0 @@
-html,
-body {
-  height: 100%;
-  overflow-y: auto;
-}
-
-body {
-  font-family:
-    system-ui,
-    -apple-system,
-    BlinkMacSystemFont,
-    "Segoe UI",
-    Roboto,
-    sans-serif;
-  margin: 0;
-  padding: 0;
-  color: #333;
-  line-height: 1.4;
-  overflow-x: hidden; /* Prevent horizontal scrolling */
-  display: flex;
-  flex-direction: column;
-}
diff --git a/loop/webui/src/sketch-app-shell.html b/loop/webui/src/sketch-app-shell.html
deleted file mode 100644
index c12ce8c..0000000
--- a/loop/webui/src/sketch-app-shell.html
+++ /dev/null
@@ -1,13 +0,0 @@
-<!doctype html>
-<html lang="en">
-  <head>
-    <meta charset="UTF-8" />
-    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
-    <title>sketch coding assistant</title>
-    <link rel="stylesheet" href="sketch-app-shell.css" />
-    <script src="static/sketch-app-shell.js" async type="module"></script>
-  </head>
-  <body>
-    <sketch-app-shell></sketch-app-shell>
-  </body>
-</html>
diff --git a/loop/webui/src/types.ts b/loop/webui/src/types.ts
deleted file mode 100644
index 7874a3b..0000000
--- a/loop/webui/src/types.ts
+++ /dev/null
@@ -1,81 +0,0 @@
-// Auto-generated by sketch.dev/cmd/go2ts.go
-// DO NOT EDIT. This file is automatically generated.
-
-export interface ToolCall {
-	name: string;
-	input: string;
-	tool_call_id: string;
-	result_message?: AgentMessage | null;
-	args?: string;
-	result?: string;
-}
-
-export interface GitCommit {
-	hash: string;
-	subject: string;
-	body: string;
-	pushed_branch?: string;
-}
-
-export interface Usage {
-	input_tokens: number;
-	cache_creation_input_tokens: number;
-	cache_read_input_tokens: number;
-	output_tokens: number;
-	cost_usd: number;
-}
-
-export interface AgentMessage {
-	type: CodingAgentMessageType;
-	end_of_turn: boolean;
-	content: string;
-	tool_name?: string;
-	input?: string;
-	tool_result?: string;
-	tool_error?: boolean;
-	tool_call_id?: string;
-	tool_calls?: ToolCall[] | null;
-	toolResponses?: AgentMessage[] | null;
-	commits?: (GitCommit | null)[] | null;
-	timestamp: string;
-	conversation_id: string;
-	parent_conversation_id?: string | null;
-	usage?: Usage | null;
-	start_time?: string | null;
-	end_time?: string | null;
-	elapsed?: Duration | null;
-	turnDuration?: Duration | null;
-	idx: number;
-}
-
-export interface CumulativeUsage {
-	start_time: string;
-	messages: number;
-	input_tokens: number;
-	output_tokens: number;
-	cache_read_input_tokens: number;
-	cache_creation_input_tokens: number;
-	total_cost_usd: number;
-	tool_uses: { [key: string]: number } | null;
-}
-
-export interface State {
-	message_count: number;
-	total_usage?: CumulativeUsage | null;
-	hostname: string;
-	working_dir: string;
-	initial_commit: string;
-	title: string;
-	os: string;
-	outside_hostname?: string;
-	inside_hostname?: string;
-	outside_os?: string;
-	inside_os?: string;
-	outside_working_dir?: string;
-	inside_working_dir?: string;
-	git_origin?: string;
-}
-
-export type CodingAgentMessageType = 'user' | 'agent' | 'error' | 'budget' | 'tool' | 'commit' | 'auto';
-
-export type Duration = number;
diff --git a/loop/webui/src/utils.ts b/loop/webui/src/utils.ts
deleted file mode 100644
index b60a0fa..0000000
--- a/loop/webui/src/utils.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-/**
- * Escapes HTML special characters in a string
- */
-export function escapeHTML(str: string): string {
-  return str
-    .replace(/&/g, "&amp;")
-    .replace(/</g, "&lt;")
-    .replace(/>/g, "&gt;")
-    .replace(/"/g, "&quot;")
-    .replace(/'/g, "&#039;");
-}
-
-/**
- * Formats a number with locale-specific formatting
- */
-export function formatNumber(
-  num: number | null | undefined,
-  defaultValue: string = "0",
-): string {
-  if (num === undefined || num === null) return defaultValue;
-  try {
-    return num.toLocaleString();
-  } catch (e) {
-    return String(num);
-  }
-}
-
-/**
- * Generates a consistent color based on an ID string
- */
-export function generateColorFromId(id: string | null | undefined): string {
-  if (!id) return "#7c7c7c"; // Default color for null/undefined
-
-  // Generate a hash from the ID
-  let hash = 0;
-  for (let i = 0; i < id.length; i++) {
-    hash = id.charCodeAt(i) + ((hash << 5) - hash);
-  }
-
-  // Convert hash to a hex color
-  let color = "#";
-  for (let i = 0; i < 3; i++) {
-    // Generate more muted colors by using only part of the range
-    // and adding a base value to avoid very dark colors
-    const value = (hash >> (i * 8)) & 0xff;
-    const scaledValue = Math.floor(100 + (value * 100) / 255); // Range 100-200 for more muted colors
-    color += scaledValue.toString(16).padStart(2, "0");
-  }
-  return color;
-}
diff --git a/loop/webui/src/vega-types.d.ts b/loop/webui/src/vega-types.d.ts
deleted file mode 100644
index 97a4655..0000000
--- a/loop/webui/src/vega-types.d.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-// Type definitions for Vega-Lite and related modules
-declare module "fast-json-patch/index.mjs";
-
-// Add any interface augmentations for TimelineMessage and ToolCall
-interface ToolCall {
-  name: string;
-  args?: string;
-  result?: string;
-  input?: string; // Add missing property
-}
-
-interface TimelineMessage {
-  type: string;
-  content?: string;
-  timestamp?: string | number | Date;
-  elapsed?: number;
-  end_of_turn?: boolean;
-  conversation_id?: string;
-  parent_conversation_id?: string;
-  tool_calls?: ToolCall[];
-  tool_name?: string;
-  tool_error?: boolean;
-  tool_result?: string;
-  input?: string;
-  start_time?: string | number | Date; // Add start time
-  end_time?: string | number | Date; // Add end time
-  usage?: {
-    input_tokens?: number;
-    output_tokens?: number;
-    cache_read_input_tokens?: number;
-    cache_creation_input_tokens?: number;
-    cost_usd?: number;
-  };
-}
diff --git a/loop/webui/src/web-components/aggregateAgentMessages.ts b/loop/webui/src/web-components/aggregateAgentMessages.ts
deleted file mode 100644
index 3dc11f8..0000000
--- a/loop/webui/src/web-components/aggregateAgentMessages.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-import { AgentMessage } from "../types";
-
-export function aggregateAgentMessages(
-  arr1: AgentMessage[],
-  arr2: AgentMessage[],
-): AgentMessage[] {
-  const mergedArray = [...arr1, ...arr2];
-  const seenIds = new Set<number>();
-  const toolCallResults = new Map<string, AgentMessage>();
-
-  let ret: AgentMessage[] = mergedArray
-    .filter((msg) => {
-      if (msg.type == "tool") {
-        toolCallResults.set(msg.tool_call_id, msg);
-        return false;
-      }
-      if (seenIds.has(msg.idx)) {
-        return false; // Skip if idx is already seen
-      }
-
-      seenIds.add(msg.idx);
-      return true;
-    })
-    .sort((a: AgentMessage, b: AgentMessage) => a.idx - b.idx);
-
-  // Attach any tool_call result messages to the original message's tool_call object.
-  ret.forEach((msg) => {
-    msg.tool_calls?.forEach((toolCall) => {
-      if (toolCallResults.has(toolCall.tool_call_id)) {
-        toolCall.result_message = toolCallResults.get(toolCall.tool_call_id);
-      }
-    });
-  });
-  return ret;
-}
diff --git a/loop/webui/src/web-components/demo/demo.css b/loop/webui/src/web-components/demo/demo.css
deleted file mode 100644
index 08e02a2..0000000
--- a/loop/webui/src/web-components/demo/demo.css
+++ /dev/null
@@ -1,18 +0,0 @@
-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 */
-}
-
-pre {
-  white-space: normal;
-}
diff --git a/loop/webui/src/web-components/demo/index.html b/loop/webui/src/web-components/demo/index.html
deleted file mode 100644
index 77df51e..0000000
--- a/loop/webui/src/web-components/demo/index.html
+++ /dev/null
@@ -1,29 +0,0 @@
-<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-tool-card.demo.html">sketch-tool-card</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
deleted file mode 100644
index 324d077..0000000
--- a/loop/webui/src/web-components/demo/readme.md
+++ /dev/null
@@ -1,5 +0,0 @@
-# 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.
-
-See [README](../../../readme.md#development-mode) for more information on how to run the demo pages.
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
deleted file mode 100644
index 48fc100..0000000
--- a/loop/webui/src/web-components/demo/sketch-app-shell.demo.html
+++ /dev/null
@@ -1,13 +0,0 @@
-<!doctype html>
-<html lang="en">
-  <head>
-    <meta charset="UTF-8" />
-    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
-    <title>sketch coding assistant</title>
-    <link rel="stylesheet" href="sketch-app-shell.css" />
-    <script src="../sketch-app-shell.ts" type="module"></script>
-  </head>
-  <body>
-    <sketch-app-shell></sketch-app-shell>
-  </body>
-</html>
diff --git a/loop/webui/src/web-components/demo/sketch-charts.demo.html b/loop/webui/src/web-components/demo/sketch-charts.demo.html
deleted file mode 100644
index 64a9bd2..0000000
--- a/loop/webui/src/web-components/demo/sketch-charts.demo.html
+++ /dev/null
@@ -1,134 +0,0 @@
-<!doctype html>
-<html>
-  <head>
-    <meta charset="utf-8" />
-    <title>Sketch Charts Demo</title>
-    <script type="module" src="../sketch-charts.ts"></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
deleted file mode 100644
index afc79fb..0000000
--- a/loop/webui/src/web-components/demo/sketch-chat-input.demo.html
+++ /dev/null
@@ -1,30 +0,0 @@
-<html>
-  <head>
-    <title>sketch-chat-input demo</title>
-    <link rel="stylesheet" href="demo.css" />
-    <script type="module" src="../sketch-chat-input.ts"></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>
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
deleted file mode 100644
index 0945d70..0000000
--- a/loop/webui/src/web-components/demo/sketch-container-status.demo.html
+++ /dev/null
@@ -1,38 +0,0 @@
-<html>
-  <head>
-    <title>sketch-container-status demo</title>
-    <link rel="stylesheet" href="demo.css" />
-    <script type="module" src="../sketch-container-status.ts"></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>
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
deleted file mode 100644
index 6ab6e62..0000000
--- a/loop/webui/src/web-components/demo/sketch-diff-view.demo.html
+++ /dev/null
@@ -1,109 +0,0 @@
-<!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="../sketch-diff-view.ts"></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>
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
deleted file mode 100644
index f248a5d..0000000
--- a/loop/webui/src/web-components/demo/sketch-network-status.demo.html
+++ /dev/null
@@ -1,22 +0,0 @@
-<html>
-  <head>
-    <title>sketch-network-status demo</title>
-    <link rel="stylesheet" href="demo.css" />
-    <script type="module" src="../sketch-network-status.ts"></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>
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
deleted file mode 100644
index a8f35d4..0000000
--- a/loop/webui/src/web-components/demo/sketch-timeline-message.demo.html
+++ /dev/null
@@ -1,62 +0,0 @@
-<html>
-  <head>
-    <title>sketch-timeline-message demo</title>
-    <link rel="stylesheet" href="demo.css" />
-    <script type="module" src="../sketch-timeline-message.ts"></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
deleted file mode 100644
index ddec327..0000000
--- a/loop/webui/src/web-components/demo/sketch-timeline.demo.html
+++ /dev/null
@@ -1,149 +0,0 @@
-<html>
-  <head>
-    <title>sketch-timeline demo</title>
-    <link rel="stylesheet" href="demo.css" />
-    <script type="module" src="../sketch-timeline.ts"></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",
-        },
-        {
-          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", () => {
-        const appShell = document.querySelector(".app-shell");
-        const timelineEl = document.querySelector("sketch-timeline");
-        timelineEl.messages = messages;
-        timelineEl.scrollContainer = appShell;
-        const addMessagesCheckbox = document.querySelector("#addMessages");
-        addMessagesCheckbox.addEventListener("change", toggleAddMessages);
-
-        let addingMessages = false;
-        const addNewMessagesInterval = 1000;
-
-        function addNewMessages() {
-          if (!addingMessages) {
-            return;
-          }
-          const n = new Date().getMilliseconds() % messages.length;
-          const msgToDup = messages[n];
-          const dup = JSON.parse(JSON.stringify(msgToDup));
-          dup.idx = messages.length;
-          dup.timestamp = new Date().toISOString();
-          messages.push(dup);
-          timelineEl.messages = messages.concat();
-          timelineEl.prop;
-          timelineEl.requestUpdate();
-        }
-
-        let addMessagesHandler = setInterval(
-          addNewMessages,
-          addNewMessagesInterval,
-        );
-
-        function toggleAddMessages() {
-          addingMessages = !addingMessages;
-          if (addingMessages) {
-          } else {
-          }
-        }
-      });
-    </script>
-    <style>
-      .app-shell {
-        display: block;
-        font-family:
-          system-ui,
-          -apple-system,
-          BlinkMacSystemFont,
-          "Segoe UI",
-          Roboto,
-          sans-serif;
-        color: rgb(51, 51, 51);
-        line-height: 1.4;
-        min-height: 100vh;
-        width: 100%;
-        position: relative;
-        overflow-x: hidden;
-      }
-      .app-header {
-        flex-grow: 0;
-      }
-      .view-container {
-        flex-grow: 2;
-      }
-    </style>
-  </head>
-  <body>
-    <div class="app-shell">
-      <div class="app-header">
-        <h1>sketch-timeline demo</h1>
-        <input
-          type="checkbox"
-          id="addMessages"
-          title="Automatically add new messages"
-        /><label for="addMessages">Automatically add new messages</label>
-      </div>
-      <div class="view-container">
-        <div class="chat-view view-active">
-          <sketch-timeline></sketch-timeline>
-        </div>
-      </div>
-    </div>
-  </body>
-</html>
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
deleted file mode 100644
index e6ea669..0000000
--- a/loop/webui/src/web-components/demo/sketch-tool-calls.demo.html
+++ /dev/null
@@ -1,184 +0,0 @@
-<html>
-  <head>
-    <title>sketch-tool-calls demo</title>
-    <link rel="stylesheet" href="demo.css" />
-
-    <script type="module" src="../sketch-tool-calls.ts"></script>
-
-    <script>
-      const toolCalls = [
-        [
-          {
-            name: "bash",
-            input: JSON.stringify({
-              command:
-                "docker ps -a --format '{{.ID}} {{.Image }} {{.Names}}' | grep sketch | awk '{print $1 }' | xargs -I {} docker rm {} && docker image prune -af",
-            }),
-          },
-        ],
-        [
-          {
-            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 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-tool-card.demo.html b/loop/webui/src/web-components/demo/sketch-tool-card.demo.html
deleted file mode 100644
index 2f887df..0000000
--- a/loop/webui/src/web-components/demo/sketch-tool-card.demo.html
+++ /dev/null
@@ -1,254 +0,0 @@
-<html>
-  <head>
-    <title>sketch-tool-card demo</title>
-    <link rel="stylesheet" href="demo.css" />
-
-    <script type="module" src="../sketch-tool-card.ts"></script>
-
-    <script>
-      const toolCalls = [
-        {
-          name: "bash",
-          input: JSON.stringify({
-            command:
-              "docker ps -a --format '{{.ID}} {{.Image }} {{.Names}}' | grep sketch | awk '{print $1 }' | xargs -I {} docker rm {} && docker image prune -af",
-          }),
-          result_message: {
-            type: "tool",
-            tool_result: `Deleted Images:
-deleted: sha256:110d4aed8bcc76cb7327412504af8aef31670b816453a3088d834bbeefd11a2c
-deleted: sha256:042622460c913078901555a8a72de18e95228fca98b9ac388503b3baafafb683
-deleted: sha256:04ccf3d087e258ffd5f940f378c2aab3c0ed646fb2fb283f90e65397db304694
-deleted: sha256:877120aa3efd02b6afdad181c1cd75bbdc67e41a75dd770fbf781e4fe9c95fc7
-deleted: sha256:d96824c284e594acacc631458818d07842fd4cfa3a1037668a1b23abce077d7b
-deleted: sha256:d90eef6007f5782b59643eecb3edab38af6399d4142f0bb306742efa0e1cf6a4
-deleted: sha256:66b006b0d7570ccf7e2afa15e7b6e6385debba0e60e76eb314383215e480a664
-deleted: sha256:834ff90a57edf5c3987a3f21713310d189f209cec7b002a863c75a22e24cc114
-deleted: sha256:735be867a9939611842099b1131e23096fbde47bb326416382ff7a90a86ab687
-deleted: sha256:986792e96058cabe4452eab0fda2694fe2d5f0b951c446c9c1f94d86614f7bc6
-deleted: sha256:01539d19a06b87dd7a2268677c6beb06bc5aed3cde0c52691a684f4d085bc437
-deleted: sha256:d03b7602a43340d6d1e53ad1d7daa5b55740613ad969c360e1377b7af7597eba
-deleted: sha256:5a7310817c5fa3e29ebfe5b17031fdc5789543460c790ae2e1039226044a6109
-deleted: sha256:def65005e4b1e48e9531ce6ca6bea682bd8285e32b0748212fb8ace12976f920
-deleted: sha256:3b17b8e4e349ac09bac24da27ec4d65e3dec359645f73bd9a38bf015ca5f8a98
-deleted: sha256:1bef4e5c965c2fa2658954096dbe64dae8f3b1d7d595bdb370d54f4027a95603
-deleted: sha256:16e6b5b274b06916833d3f040ca045a12fe1a6a10bebf5f92338fe6b4c7dbbf7
-deleted: sha256:d90588879cc818bc3b3b575a291a3c4088d0ea1c61fad2c4a2f34160bdc86db6
-deleted: sha256:85903960027c7b9baf8bd0ee662571758ce8ffe83526839377284e2fccac558f
-untagged: sketch-94924d08c163:latest
-deleted: sha256:7c7c3957d3ba526a351d21e52a1aee0e72bb4a62d0422a0eb3a0e2b53391824f
-deleted: sha256:e4a1fe6a3369ca8f24baaba277bc9d97353992e9e051020c5a25e588a702e634
-deleted: sha256:28ccbe834ee66199498458f500b10cc9ea69460216982a537ea3294d6dfb0b63
-deleted: sha256:95c7d2956020039d92b546d6824c5d7fac163a6247be599160483d263094c047
-deleted: sha256:f87bc9eb655a06edd50d5a34e016175006c430ad129146b9b755169a3c318a57
-deleted: sha256:b455829fdcd5fe238567af2370f9fc021eb416ec2140f98b0ab59478febcfb2e
-deleted: sha256:ed64271d223807308a391a733fc556a6c16bfb87e6f9aed6d4ce394fcbb77ba6
-deleted: sha256:a5ce6521003bca24abcb4a0021837e789349fb3f44f7ceb00ef4af33ca01f84f
-deleted: sha256:57e05db1ff95deab5f5c3f38f9607a1c3bb21518133f4e0c137ffe6bb9cbfde9
-deleted: sha256:540194db01e12f59d19f7795ec9c8a1bb753df2de935469b21a10fc7ca1d25a5
-deleted: sha256:97519dae495c256597a9b7975a332e67edb21f93e306b72132ed2c30bb01b8aa
-deleted: sha256:162c7a942156fd5f16616c6fea4a26f2bfa01a53e499d59fdb8c68e815f5350e
-deleted: sha256:51b9d76df1fbcb277e4f22496ff661d4d748f499453a27a012629f78bb61107e
-deleted: sha256:7a1a595c3015a6b2f5e996988d094bcaca328ebeaafe37403e78322e10d6b859
-deleted: sha256:27631f63a84d9a524381a95168f24deb89612fb468e03bce724f352bb5ef7b3b
-deleted: sha256:58746669dff4a4051d05542e05109d57c94f867981b47bdb5800d62567a6280f
-untagged: golang:1.24.2-alpine3.21
-untagged: golang@sha256:7772cb5322baa875edd74705556d08f0eeca7b9c4b5367754ce3f2f00041ccee
-untagged: sketch-3c262c60c42c:latest
-deleted: sha256:fadf166900e61610d77d613ce52ca1c03711ce2a7bcd31f1f634529791c0c107
-deleted: sha256:8b719162dad84cddd630e1e943520041947ca91b3794417c0d2a03b3726ebaa4
-deleted: sha256:444f0e44dcaff517142f8aab35d35f08536d886a746f6858dac7052977ee2cff
-deleted: sha256:a95a3660958ed25a27ae7b0622b5426e046d4c5587693aa7c0098e050e057311
-deleted: sha256:edb781114acb505bbde5e4a3db68b7ab6f4a3c0da92ceed2d10f02c6278b93c8
-deleted: sha256:1429402020a73b7d5c1de32f9451c68e22508cc4238750f5a500e1d9737eedae
-deleted: sha256:3f749e03b0f5ef2dfc538581c92230f2cd6b844fe3c734c728fd3775865ed24c
-deleted: sha256:f62c6ba2d4f4b94796d4c4c111031fbbbaf22df24623a2d6729277dc1eaf8da8
-deleted: sha256:504579f990b8894755910252d3b401f86a589709efafb30b9ded67cb3edad80e
-deleted: sha256:2e22f953ef8cc5fac95fb0babc5042f5e2a7fefc9d5ec444429c490d54acb1ab
-deleted: sha256:afa0c23676c039532a39faa1f1506b19f34507b586796ea070dcaee30e6228ef
-deleted: sha256:5f176f397253734bdc726a505c84448f9b00e5652d9a28ef59de0581a2e8e923
-deleted: sha256:253afbfd579bc6daf71e42b0f1e369d2b6c9015028191af4478da0b77b8a85ed
-deleted: sha256:81f79e13183887f93db52268f00975f43613abc520c88e1090a1dbb3d09094e9
-deleted: sha256:3c0b6f56bdbec5bf995b818e8a67d2d6c3bd9aa3698c403b6dabc01a81a4cb52
-deleted: sha256:635f4ba57c6445e69cf8c6fba61c3690f76901e17334f6d2d165979b2d387dfa
-
-Total reclaimed space: 1.426GB`,
-          },
-        },
-        {
-          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((toolCall) => {
-          const h2El = document.createElement("h2");
-          h2El.innerText = toolCall.name;
-          document.body.append(h2El);
-
-          let toolCardEl = document.createElement("sketch-tool-card-generic");
-          switch (toolCall.name) {
-            case "bash":
-              toolCardEl = document.createElement("sketch-tool-card-bash");
-              break;
-            case "codereview":
-              toolCardEl = document.createElement(
-                "sketch-tool-card-codereview",
-              );
-              break;
-            case "done":
-              toolCardEl = document.createElement("sketch-tool-card-done");
-              break;
-            case "patch":
-              toolCardEl = document.createElement("sketch-tool-card-patch");
-              break;
-            case "think":
-              toolCardEl = document.createElement("sketch-tool-card-think");
-              break;
-            case "title":
-              toolCardEl = document.createElement("sketch-tool-card-title");
-              break;
-          }
-          toolCardEl.toolCall = toolCall;
-          toolCardEl.open = true;
-          document.body.append(toolCardEl);
-        });
-      });
-    </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
deleted file mode 100644
index 0068616..0000000
--- a/loop/webui/src/web-components/demo/sketch-view-mode-select.demo.html
+++ /dev/null
@@ -1,32 +0,0 @@
-<html>
-  <head>
-    <title>sketch-view-mode-select demo</title>
-    <link rel="stylesheet" href="demo.css" />
-
-    <script type="module" src="../sketch-view-mode-select.ts"></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}`;
-          viewModeSelect.activeMode = 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>
diff --git a/loop/webui/src/web-components/sketch-app-shell.ts b/loop/webui/src/web-components/sketch-app-shell.ts
deleted file mode 100644
index 1dd3b6f..0000000
--- a/loop/webui/src/web-components/sketch-app-shell.ts
+++ /dev/null
@@ -1,603 +0,0 @@
-import { css, html, LitElement } from "lit";
-import { customElement, property, state } from "lit/decorators.js";
-import { DataManager, ConnectionStatus } from "../data";
-import { State, AgentMessage } from "../types";
-import "./sketch-container-status";
-import "./sketch-view-mode-select";
-import "./sketch-network-status";
-import "./sketch-timeline";
-import "./sketch-chat-input";
-import "./sketch-diff-view";
-import "./sketch-charts";
-import "./sketch-terminal";
-import { SketchDiffView } from "./sketch-diff-view";
-import { aggregateAgentMessages } from "./aggregateAgentMessages";
-
-type ViewMode = "chat" | "diff" | "charts" | "terminal";
-
-@customElement("sketch-app-shell")
-export class SketchAppShell extends LitElement {
-  // Current view mode (chat, diff, charts, terminal)
-  @state()
-  viewMode: "chat" | "diff" | "charts" | "terminal" = "chat";
-
-  // Current commit hash for diff view
-  @state()
-  currentCommitHash: 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`
-    :host {
-      display: block;
-      font-family:
-        system-ui,
-        -apple-system,
-        BlinkMacSystemFont,
-        "Segoe UI",
-        Roboto,
-        sans-serif;
-      color: #333;
-      line-height: 1.4;
-      min-height: 100vh;
-      width: 100%;
-      position: relative;
-      overflow-x: hidden;
-    }
-
-    /* Top banner with combined elements */
-    .top-banner {
-      display: flex;
-      justify-content: space-between;
-      align-items: center;
-      padding: 5px 20px;
-      margin-bottom: 0;
-      border-bottom: 1px solid #eee;
-      gap: 10px;
-      position: fixed;
-      top: 0;
-      left: 0;
-      right: 0;
-      background: white;
-      z-index: 100;
-      box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
-      max-width: 100%;
-    }
-
-    .banner-title {
-      font-size: 18px;
-      font-weight: 600;
-      margin: 0;
-      min-width: 6em;
-      white-space: nowrap;
-      overflow: hidden;
-      text-overflow: ellipsis;
-    }
-
-    .chat-title {
-      margin: 0;
-      padding: 0;
-      color: rgba(82, 82, 82, 0.85);
-      font-size: 16px;
-      font-weight: normal;
-      font-style: italic;
-      white-space: nowrap;
-      overflow: hidden;
-      text-overflow: ellipsis;
-    }
-
-    /* View mode container styles - mirroring timeline.css structure */
-    .view-container {
-      max-width: 1200px;
-      margin: 0 auto;
-      margin-top: 65px; /* Space for the top banner */
-      margin-bottom: 90px; /* Increased space for the chat input */
-      position: relative;
-      padding-bottom: 15px; /* Additional padding to prevent clipping */
-      padding-top: 15px; /* Add padding at top to prevent content touching the header */
-    }
-
-    /* Allow the container to expand to full width in diff mode */
-    .view-container.diff-active {
-      max-width: 100%;
-    }
-
-    /* Individual view styles */
-    .chat-view,
-    .diff-view,
-    .chart-view,
-    .terminal-view {
-      display: none; /* Hidden by default */
-      width: 100%;
-    }
-
-    /* Active view styles - these will be applied via JavaScript */
-    .view-active {
-      display: flex;
-      flex-direction: column;
-    }
-
-    .title-container {
-      display: flex;
-      flex-direction: column;
-      white-space: nowrap;
-      overflow: hidden;
-      text-overflow: ellipsis;
-      max-width: 33%;
-    }
-
-    .refresh-control {
-      display: flex;
-      align-items: center;
-      margin-bottom: 0;
-      flex-wrap: nowrap;
-      white-space: nowrap;
-      flex-shrink: 0;
-    }
-
-    .refresh-button {
-      background: #4caf50;
-      color: white;
-      border: none;
-      padding: 4px 10px;
-      border-radius: 4px;
-      cursor: pointer;
-      font-size: 12px;
-      margin-right: 5px;
-    }
-
-    .stop-button:hover {
-      background-color: #c82333 !important;
-    }
-
-    .poll-updates {
-      display: flex;
-      align-items: center;
-      margin: 0 5px;
-      font-size: 12px;
-    }
-  `;
-
-  // Header bar: Network connection status details
-  @property()
-  connectionStatus: ConnectionStatus = "disconnected";
-
-  @property()
-  connectionErrorMessage: string = "";
-
-  @property()
-  messageStatus: string = "";
-
-  // Chat messages
-  @property({ attribute: false })
-  messages: AgentMessage[] = [];
-
-  @property()
-  title: string = "";
-
-  private dataManager = new DataManager();
-
-  @property({ attribute: false })
-  containerState: State = {
-    title: "",
-    os: "",
-    message_count: 0,
-    hostname: "",
-    working_dir: "",
-    initial_commit: "",
-  };
-
-  // Mutation observer to detect when new messages are added
-  private mutationObserver: MutationObserver | null = null;
-
-  constructor() {
-    super();
-    console.log("Hello!");
-
-    // Binding methods to this
-    this._handleViewModeSelect = this._handleViewModeSelect.bind(this);
-    this._handleShowCommitDiff = this._handleShowCommitDiff.bind(this);
-    this._handlePopState = this._handlePopState.bind(this);
-  }
-
-  // See https://lit.dev/docs/components/lifecycle/
-  connectedCallback() {
-    super.connectedCallback();
-
-    // Initialize client-side nav history.
-    const url = new URL(window.location.href);
-    const mode = url.searchParams.get("view") || "chat";
-    window.history.replaceState({ mode }, "", url.toString());
-
-    this.toggleViewMode(mode as ViewMode, false);
-    // Add popstate event listener to handle browser back/forward navigation
-    window.addEventListener("popstate", this._handlePopState);
-
-    // Add event listeners
-    window.addEventListener("view-mode-select", this._handleViewModeSelect);
-    window.addEventListener("show-commit-diff", this._handleShowCommitDiff);
-
-    // register event listeners
-    this.dataManager.addEventListener(
-      "dataChanged",
-      this.handleDataChanged.bind(this),
-    );
-    this.dataManager.addEventListener(
-      "connectionStatusChanged",
-      this.handleConnectionStatusChanged.bind(this),
-    );
-
-    // Initialize the data manager
-    this.dataManager.initialize();
-  }
-
-  // See https://lit.dev/docs/components/lifecycle/
-  disconnectedCallback() {
-    super.disconnectedCallback();
-    window.removeEventListener("popstate", this._handlePopState);
-
-    // Remove event listeners
-    window.removeEventListener("view-mode-select", this._handleViewModeSelect);
-    window.removeEventListener("show-commit-diff", this._handleShowCommitDiff);
-
-    // unregister data manager event listeners
-    this.dataManager.removeEventListener(
-      "dataChanged",
-      this.handleDataChanged.bind(this),
-    );
-    this.dataManager.removeEventListener(
-      "connectionStatusChanged",
-      this.handleConnectionStatusChanged.bind(this),
-    );
-
-    // Disconnect mutation observer if it exists
-    if (this.mutationObserver) {
-      console.log("Auto-scroll: Disconnecting mutation observer");
-      this.mutationObserver.disconnect();
-      this.mutationObserver = null;
-    }
-  }
-
-  updateUrlForViewMode(mode: "chat" | "diff" | "charts" | "terminal"): void {
-    // Get the current URL without search parameters
-    const url = new URL(window.location.href);
-
-    // Clear existing parameters
-    url.search = "";
-
-    // Only add view parameter if not in default chat view
-    if (mode !== "chat") {
-      url.searchParams.set("view", mode);
-      const diffView = this.shadowRoot?.querySelector(
-        ".diff-view",
-      ) as SketchDiffView;
-
-      // If in diff view and there's a commit hash, include that too
-      if (mode === "diff" && diffView.commitHash) {
-        url.searchParams.set("commit", diffView.commitHash);
-      }
-    }
-
-    // Update the browser history without reloading the page
-    window.history.pushState({ mode }, "", url.toString());
-  }
-
-  private _handlePopState(event: PopStateEvent) {
-    if (event.state && event.state.mode) {
-      this.toggleViewMode(event.state.mode, false);
-    } else {
-      this.toggleViewMode("chat", false);
-    }
-  }
-
-  /**
-   * Handle view mode selection event
-   */
-  private _handleViewModeSelect(event: CustomEvent) {
-    const mode = event.detail.mode as "chat" | "diff" | "charts" | "terminal";
-    this.toggleViewMode(mode, true);
-  }
-
-  /**
-   * Handle show commit diff event
-   */
-  private _handleShowCommitDiff(event: CustomEvent) {
-    const { commitHash } = event.detail;
-    if (commitHash) {
-      this.showCommitDiff(commitHash);
-    }
-  }
-
-  /**
-   * Listen for commit diff event
-   * @param commitHash The commit hash to show diff for
-   */
-  private showCommitDiff(commitHash: string): void {
-    // Store the commit hash
-    this.currentCommitHash = commitHash;
-
-    // Switch to diff view
-    this.toggleViewMode("diff", true);
-
-    // Wait for DOM update to complete
-    this.updateComplete.then(() => {
-      // Get the diff view component
-      const diffView = this.shadowRoot?.querySelector("sketch-diff-view");
-      if (diffView) {
-        // Call the showCommitDiff method
-        (diffView as any).showCommitDiff(commitHash);
-      }
-    });
-  }
-
-  /**
-   * Toggle between different view modes: chat, diff, charts, terminal
-   */
-  private toggleViewMode(mode: ViewMode, updateHistory: boolean): void {
-    // Don't do anything if the mode is already active
-    if (this.viewMode === mode) return;
-
-    // Update the view mode
-    this.viewMode = mode;
-
-    if (updateHistory) {
-      // Update URL with the current view mode
-      this.updateUrlForViewMode(mode);
-    }
-
-    // Wait for DOM update to complete
-    this.updateComplete.then(() => {
-      // Update active view
-      const viewContainer = this.shadowRoot?.querySelector(".view-container");
-      const chatView = this.shadowRoot?.querySelector(".chat-view");
-      const diffView = this.shadowRoot?.querySelector(".diff-view");
-      const chartView = this.shadowRoot?.querySelector(".chart-view");
-      const terminalView = this.shadowRoot?.querySelector(".terminal-view");
-
-      // Remove active class from all views
-      chatView?.classList.remove("view-active");
-      diffView?.classList.remove("view-active");
-      chartView?.classList.remove("view-active");
-      terminalView?.classList.remove("view-active");
-
-      // Add/remove diff-active class on view container
-      if (mode === "diff") {
-        viewContainer?.classList.add("diff-active");
-      } else {
-        viewContainer?.classList.remove("diff-active");
-      }
-
-      // Add active class to the selected view
-      switch (mode) {
-        case "chat":
-          chatView?.classList.add("view-active");
-          break;
-        case "diff":
-          diffView?.classList.add("view-active");
-          // Load diff content if we have a diff view
-          const diffViewComp =
-            this.shadowRoot?.querySelector("sketch-diff-view");
-          if (diffViewComp && this.currentCommitHash) {
-            (diffViewComp as any).showCommitDiff(this.currentCommitHash);
-          } else if (diffViewComp) {
-            (diffViewComp as any).loadDiffContent();
-          }
-          break;
-        case "charts":
-          chartView?.classList.add("view-active");
-          break;
-        case "terminal":
-          terminalView?.classList.add("view-active");
-          break;
-      }
-
-      // Update view mode buttons
-      const viewModeSelect = this.shadowRoot?.querySelector(
-        "sketch-view-mode-select",
-      );
-      if (viewModeSelect) {
-        const event = new CustomEvent("update-active-mode", {
-          detail: { mode },
-          bubbles: true,
-          composed: true,
-        });
-        viewModeSelect.dispatchEvent(event);
-      }
-
-      // FIXME: This is a hack to get vega chart in sketch-charts.ts to work properly
-      // When the chart is in the background, its container has a width of 0, so vega
-      // renders width 0 and only changes that width on a resize event.
-      // See https://github.com/vega/react-vega/issues/85#issuecomment-1826421132
-      window.dispatchEvent(new Event("resize"));
-    });
-  }
-
-  private handleDataChanged(eventData: {
-    state: State;
-    newMessages: AgentMessage[];
-    isFirstFetch?: boolean;
-  }): void {
-    const { state, newMessages, isFirstFetch } = eventData;
-
-    // Check if this is the first data fetch or if there are new messages
-    if (isFirstFetch) {
-      this.messageStatus = "Initial messages loaded";
-    } else if (newMessages && newMessages.length > 0) {
-      this.messageStatus = "Updated just now";
-    } else {
-      this.messageStatus = "No new messages";
-    }
-
-    // Update state if we received it
-    if (state) {
-      this.containerState = state;
-      this.title = state.title;
-    }
-
-    // Create a copy of the current messages before updating
-    const oldMessageCount = this.messages.length;
-
-    // Update messages
-    this.messages = aggregateAgentMessages(this.messages, newMessages);
-
-    // Log information about the message update
-    if (this.messages.length > oldMessageCount) {
-      console.log(
-        `Auto-scroll: Messages updated from ${oldMessageCount} to ${this.messages.length}`,
-      );
-    }
-  }
-
-  private handleConnectionStatusChanged(
-    status: ConnectionStatus,
-    errorMessage?: string,
-  ): void {
-    this.connectionStatus = status;
-    this.connectionErrorMessage = errorMessage || "";
-  }
-
-  async _sendChat(e: CustomEvent) {
-    console.log("app shell: _sendChat", e);
-    const message = e.detail.message?.trim();
-    if (message == "") {
-      return;
-    }
-    try {
-      // Send the message to the server
-      const response = await fetch("chat", {
-        method: "POST",
-        headers: {
-          "Content-Type": "application/json",
-        },
-        body: JSON.stringify({ message }),
-      });
-
-      if (!response.ok) {
-        const errorData = await response.text();
-        throw new Error(`Server error: ${response.status} - ${errorData}`);
-      }
-
-      // TOOD(philip): If the data manager is getting messages out of order, there's a bug?
-      // Reset data manager state to force a full refresh after sending a message
-      // This ensures we get all messages in the correct order
-      // Use private API for now - TODO: add a resetState() method to DataManager
-      (this.dataManager as any).nextFetchIndex = 0;
-      (this.dataManager as any).currentFetchStartIndex = 0;
-
-      // // If in diff view, switch to conversation view
-      // if (this.viewMode === "diff") {
-      //   await this.toggleViewMode("chat");
-      // }
-
-      // Refresh the timeline data to show the new message
-      await this.dataManager.fetchData();
-    } catch (error) {
-      console.error("Error sending chat message:", error);
-      const statusText = document.getElementById("statusText");
-      if (statusText) {
-        statusText.textContent = "Error sending message";
-      }
-    }
-  }
-
-  render() {
-    return html`
-      <div class="top-banner">
-        <div class="title-container">
-          <h1 class="banner-title">sketch</h1>
-          <h2 id="chatTitle" class="chat-title">${this.title}</h2>
-        </div>
-
-        <sketch-container-status
-          .state=${this.containerState}
-        ></sketch-container-status>
-
-        <div class="refresh-control">
-          <sketch-view-mode-select></sketch-view-mode-select>
-
-          <button id="stopButton" class="refresh-button stop-button">
-            Stop
-          </button>
-
-          <div class="poll-updates">
-            <input type="checkbox" id="pollToggle" checked />
-            <label for="pollToggle">Poll</label>
-          </div>
-
-          <sketch-network-status
-            message=${this.messageStatus}
-            connection=${this.connectionStatus}
-            error=${this.connectionErrorMessage}
-          ></sketch-network-status>
-        </div>
-      </div>
-
-      <div class="view-container">
-        <div class="chat-view ${this.viewMode === "chat" ? "view-active" : ""}">
-          <sketch-timeline
-            .messages=${this.messages}
-            .scrollContainer=${this}
-          ></sketch-timeline>
-        </div>
-
-        <div class="diff-view ${this.viewMode === "diff" ? "view-active" : ""}">
-          <sketch-diff-view
-            .commitHash=${this.currentCommitHash}
-          ></sketch-diff-view>
-        </div>
-
-        <div
-          class="chart-view ${this.viewMode === "charts" ? "view-active" : ""}"
-        >
-          <sketch-charts .messages=${this.messages}></sketch-charts>
-        </div>
-
-        <div
-          class="terminal-view ${this.viewMode === "terminal"
-            ? "view-active"
-            : ""}"
-        >
-          <sketch-terminal></sketch-terminal>
-        </div>
-      </div>
-
-      <sketch-chat-input @send-chat="${this._sendChat}"></sketch-chat-input>
-    `;
-  }
-
-  /**
-   * Lifecycle callback when component is first connected to DOM
-   */
-  firstUpdated(): void {
-    if (this.viewMode !== "chat") {
-      return;
-    }
-
-    // Initial scroll to bottom when component is first rendered
-    setTimeout(
-      () => this.scrollTo({ top: this.scrollHeight, behavior: "smooth" }),
-      50,
-    );
-
-    const pollToggleCheckbox = this.renderRoot?.querySelector(
-      "#pollToggle",
-    ) as HTMLInputElement;
-    pollToggleCheckbox?.addEventListener("change", () => {
-      this.dataManager.setPollingEnabled(pollToggleCheckbox.checked);
-      if (!pollToggleCheckbox.checked) {
-        this.connectionStatus = "disabled";
-        this.messageStatus = "Polling stopped";
-      } else {
-        this.messageStatus = "Polling for updates...";
-      }
-    });
-  }
-}
-
-declare global {
-  interface HTMLElementTagNameMap {
-    "sketch-app-shell": SketchAppShell;
-  }
-}
diff --git a/loop/webui/src/web-components/sketch-charts.ts b/loop/webui/src/web-components/sketch-charts.ts
deleted file mode 100644
index 8cf2606..0000000
--- a/loop/webui/src/web-components/sketch-charts.ts
+++ /dev/null
@@ -1,498 +0,0 @@
-import "./vega-embed";
-import { css, html, LitElement, PropertyValues } from "lit";
-import { customElement, property, state } from "lit/decorators.js";
-import { TopLevelSpec } from "vega-lite";
-import type { AgentMessage } from "../types";
-import "vega-embed";
-import { VisualizationSpec } from "vega-embed";
-
-/**
- * Web component for rendering charts related to the timeline data
- * Displays cumulative cost over time and message timing visualization
- */
-@customElement("sketch-charts")
-export class SketchCharts extends LitElement {
-  @property({ type: Array })
-  messages: AgentMessage[] = [];
-
-  @state()
-  private chartData: { timestamp: Date; cost: number }[] = [];
-
-  // We need to make the styles available to Vega-Embed when it's rendered
-  static styles = css`
-    :host {
-      display: block;
-      width: 100%;
-    }
-
-    .chart-container {
-      padding: 20px;
-      background-color: #fff;
-      border-radius: 8px;
-      box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
-      margin-bottom: 20px;
-    }
-
-    .chart-section {
-      margin-bottom: 30px;
-    }
-
-    .chart-section h3 {
-      margin-top: 0;
-      margin-bottom: 15px;
-      font-size: 18px;
-      color: #333;
-      border-bottom: 1px solid #eee;
-      padding-bottom: 8px;
-    }
-
-    .chart-content {
-      width: 100%;
-      min-height: 300px;
-    }
-
-    .loader {
-      border: 4px solid #f3f3f3;
-      border-radius: 50%;
-      border-top: 4px solid #3498db;
-      width: 40px;
-      height: 40px;
-      margin: 20px auto;
-      animation: spin 2s linear infinite;
-    }
-
-    @keyframes spin {
-      0% {
-        transform: rotate(0deg);
-      }
-      100% {
-        transform: rotate(360deg);
-      }
-    }
-  `;
-
-  constructor() {
-    super();
-    this.chartData = [];
-  }
-
-  private calculateCumulativeCostData(
-    messages: AgentMessage[],
-  ): { timestamp: Date; cost: number }[] {
-    if (!messages || messages.length === 0) {
-      return [];
-    }
-
-    let cumulativeCost = 0;
-    const data: { timestamp: Date; cost: number }[] = [];
-
-    for (const message of messages) {
-      if (message.timestamp && message.usage && message.usage.cost_usd) {
-        const timestamp = new Date(message.timestamp);
-        cumulativeCost += message.usage.cost_usd;
-
-        data.push({
-          timestamp,
-          cost: cumulativeCost,
-        });
-      }
-    }
-
-    return data;
-  }
-
-  protected willUpdate(changedProperties: PropertyValues): void {
-    if (changedProperties.has("messages")) {
-      this.chartData = this.calculateCumulativeCostData(this.messages);
-    }
-  }
-
-  private getMessagesChartSpec(): VisualizationSpec {
-    try {
-      const allMessages = this.messages;
-      if (!Array.isArray(allMessages) || allMessages.length === 0) {
-        return null;
-      }
-
-      // Sort messages chronologically
-      allMessages.sort((a, b) => {
-        const dateA = a.timestamp ? new Date(a.timestamp).getTime() : 0;
-        const dateB = b.timestamp ? new Date(b.timestamp).getTime() : 0;
-        return dateA - dateB;
-      });
-
-      // Create unique indexes for all messages
-      const messageIndexMap = new Map<string, number>();
-      let messageIdx = 0;
-
-      // First pass: Process parent messages
-      allMessages.forEach((msg, index) => {
-        // Create a unique ID for each message to track its position
-        const msgId = msg.timestamp ? msg.timestamp.toString() : `msg-${index}`;
-        messageIndexMap.set(msgId, messageIdx++);
-      });
-
-      // Process tool calls from messages to account for filtered out tool messages
-      const toolCallData: any[] = [];
-      allMessages.forEach((msg) => {
-        if (msg.tool_calls && msg.tool_calls.length > 0) {
-          msg.tool_calls.forEach((toolCall) => {
-            if (toolCall.result_message) {
-              // Add this tool result message to our data
-              const resultMsg = toolCall.result_message;
-
-              // Important: use the original message's idx to maintain the correct order
-              // The original message idx value is what we want to show in the chart
-              if (resultMsg.idx !== undefined) {
-                // If the tool call has start/end times, add it to bar data, otherwise to point data
-                if (resultMsg.start_time && resultMsg.end_time) {
-                  toolCallData.push({
-                    type: "bar",
-                    index: resultMsg.idx, // Use actual idx from message
-                    message_type: "tool",
-                    content: resultMsg.content || "",
-                    tool_name: resultMsg.tool_name || toolCall.name || "",
-                    tool_input: toolCall.input || "",
-                    tool_result: resultMsg.tool_result || "",
-                    start_time: new Date(resultMsg.start_time).toISOString(),
-                    end_time: new Date(resultMsg.end_time).toISOString(),
-                    message: JSON.stringify(resultMsg, null, 2),
-                  });
-                } else if (resultMsg.timestamp) {
-                  toolCallData.push({
-                    type: "point",
-                    index: resultMsg.idx, // Use actual idx from message
-                    message_type: "tool",
-                    content: resultMsg.content || "",
-                    tool_name: resultMsg.tool_name || toolCall.name || "",
-                    tool_input: toolCall.input || "",
-                    tool_result: resultMsg.tool_result || "",
-                    time: new Date(resultMsg.timestamp).toISOString(),
-                    message: JSON.stringify(resultMsg, null, 2),
-                  });
-                }
-              }
-            }
-          });
-        }
-      });
-
-      // Prepare data for messages with start_time and end_time (bar marks)
-      const barData = allMessages
-        .filter((msg) => msg.start_time && msg.end_time) // Only include messages with explicit start and end times
-        .map((msg) => {
-          // Parse start and end times
-          const startTime = new Date(msg.start_time!);
-          const endTime = new Date(msg.end_time!);
-
-          // Use the message idx directly for consistent ordering
-          const index = msg.idx;
-
-          // Truncate content for tooltip readability
-          const displayContent = msg.content
-            ? msg.content.length > 100
-              ? msg.content.substring(0, 100) + "..."
-              : msg.content
-            : "No content";
-
-          // Prepare tool input and output for tooltip if applicable
-          const toolInput = msg.input
-            ? msg.input.length > 100
-              ? msg.input.substring(0, 100) + "..."
-              : msg.input
-            : "";
-
-          const toolResult = msg.tool_result
-            ? msg.tool_result.length > 100
-              ? msg.tool_result.substring(0, 100) + "..."
-              : msg.tool_result
-            : "";
-
-          return {
-            index: index,
-            message_type: msg.type,
-            content: displayContent,
-            tool_name: msg.tool_name || "",
-            tool_input: toolInput,
-            tool_result: toolResult,
-            start_time: startTime.toISOString(),
-            end_time: endTime.toISOString(),
-            message: JSON.stringify(msg, null, 2), // Full message for detailed inspection
-          };
-        });
-
-      // Prepare data for messages with timestamps only (point marks)
-      const pointData = allMessages
-        .filter((msg) => msg.timestamp && !(msg.start_time && msg.end_time)) // Only messages with timestamp but without start/end times
-        .map((msg) => {
-          // Get the timestamp
-          const timestamp = new Date(msg.timestamp!);
-
-          // Use the message idx directly for consistent ordering
-          const index = msg.idx;
-
-          // Truncate content for tooltip readability
-          const displayContent = msg.content
-            ? msg.content.length > 100
-              ? msg.content.substring(0, 100) + "..."
-              : msg.content
-            : "No content";
-
-          // Prepare tool input and output for tooltip if applicable
-          const toolInput = msg.input
-            ? msg.input.length > 100
-              ? msg.input.substring(0, 100) + "..."
-              : msg.input
-            : "";
-
-          const toolResult = msg.tool_result
-            ? msg.tool_result.length > 100
-              ? msg.tool_result.substring(0, 100) + "..."
-              : msg.tool_result
-            : "";
-
-          return {
-            index: index,
-            message_type: msg.type,
-            content: displayContent,
-            tool_name: msg.tool_name || "",
-            tool_input: toolInput,
-            tool_result: toolResult,
-            time: timestamp.toISOString(),
-            message: JSON.stringify(msg, null, 2), // Full message for detailed inspection
-          };
-        });
-
-      // Add tool call data to the appropriate arrays
-      const toolBarData = toolCallData
-        .filter((d) => d.type === "bar")
-        .map((d) => {
-          delete d.type;
-          return d;
-        });
-
-      const toolPointData = toolCallData
-        .filter((d) => d.type === "point")
-        .map((d) => {
-          delete d.type;
-          return d;
-        });
-
-      // Check if we have any data to display
-      if (
-        barData.length === 0 &&
-        pointData.length === 0 &&
-        toolBarData.length === 0 &&
-        toolPointData.length === 0
-      ) {
-        return null;
-      }
-
-      // Calculate height based on number of unique messages
-      const chartHeight = 20 * Math.min(allMessages.length, 25); // Max 25 visible at once
-
-      // Create a layered Vega-Lite spec combining bars and points
-      const messagesSpec: TopLevelSpec = {
-        $schema: "https://vega.github.io/schema/vega-lite/v5.json",
-        description: "Message Timeline",
-        width: "container",
-        height: chartHeight,
-        layer: [],
-      };
-
-      // Add bar layer if we have bar data
-      if (barData.length > 0 || toolBarData.length > 0) {
-        const combinedBarData = [...barData, ...toolBarData];
-        messagesSpec.layer.push({
-          data: { values: combinedBarData },
-          mark: {
-            type: "bar",
-            height: 16,
-          },
-          encoding: {
-            x: {
-              field: "start_time",
-              type: "temporal",
-              title: "Time",
-              axis: {
-                format: "%H:%M:%S",
-                title: "Time",
-                labelAngle: -45,
-              },
-            },
-            x2: { field: "end_time" },
-            y: {
-              field: "index",
-              type: "ordinal",
-              title: "Message Index",
-              axis: {
-                grid: true,
-              },
-            },
-            color: {
-              field: "message_type",
-              type: "nominal",
-              title: "Message Type",
-              legend: {},
-            },
-            tooltip: [
-              { field: "message_type", type: "nominal", title: "Type" },
-              { field: "tool_name", type: "nominal", title: "Tool" },
-              {
-                field: "start_time",
-                type: "temporal",
-                title: "Start Time",
-                format: "%H:%M:%S.%L",
-              },
-              {
-                field: "end_time",
-                type: "temporal",
-                title: "End Time",
-                format: "%H:%M:%S.%L",
-              },
-              { field: "content", type: "nominal", title: "Content" },
-              { field: "tool_input", type: "nominal", title: "Tool Input" },
-              { field: "tool_result", type: "nominal", title: "Tool Result" },
-            ],
-          },
-        });
-      }
-
-      // Add point layer if we have point data
-      if (pointData.length > 0 || toolPointData.length > 0) {
-        const combinedPointData = [...pointData, ...toolPointData];
-        messagesSpec.layer.push({
-          data: { values: combinedPointData },
-          mark: {
-            type: "point",
-            size: 100,
-            filled: true,
-          },
-          encoding: {
-            x: {
-              field: "time",
-              type: "temporal",
-              title: "Time",
-              axis: {
-                format: "%H:%M:%S",
-                title: "Time",
-                labelAngle: -45,
-              },
-            },
-            y: {
-              field: "index",
-              type: "ordinal",
-              title: "Message Index",
-            },
-            color: {
-              field: "message_type",
-              type: "nominal",
-              title: "Message Type",
-            },
-            tooltip: [
-              { field: "message_type", type: "nominal", title: "Type" },
-              { field: "tool_name", type: "nominal", title: "Tool" },
-              {
-                field: "time",
-                type: "temporal",
-                title: "Timestamp",
-                format: "%H:%M:%S.%L",
-              },
-              { field: "content", type: "nominal", title: "Content" },
-              { field: "tool_input", type: "nominal", title: "Tool Input" },
-              { field: "tool_result", type: "nominal", title: "Tool Result" },
-            ],
-          },
-        });
-      }
-      return messagesSpec;
-    } catch (error) {
-      console.error("Error rendering messages chart:", error);
-    }
-  }
-
-  render() {
-    const costSpec = this.createCostChartSpec();
-    const messagesSpec = this.getMessagesChartSpec();
-
-    return html`
-      <div class="chart-container" id="chartContainer">
-        <div class="chart-section">
-          <h3>Dollar Usage Over Time</h3>
-          <div class="chart-content">
-            ${this.chartData.length > 0
-              ? html`<vega-embed .spec=${costSpec}></vega-embed>`
-              : html`<p>No cost data available to display.</p>`}
-          </div>
-        </div>
-        <div class="chart-section">
-          <h3>Message Timeline</h3>
-          <div class="chart-content">
-            ${messagesSpec?.data
-              ? html`<vega-embed .spec=${messagesSpec}></vega-embed>`
-              : html`<p>No messages available to display.</p>`}
-          </div>
-        </div>
-      </div>
-    `;
-  }
-
-  private createCostChartSpec(): VisualizationSpec {
-    return {
-      $schema: "https://vega.github.io/schema/vega-lite/v5.json",
-      description: "Cumulative cost over time",
-      width: "container",
-      height: 300,
-      data: {
-        values: this.chartData.map((d) => ({
-          timestamp: d.timestamp.toISOString(),
-          cost: d.cost,
-        })),
-      },
-      mark: {
-        type: "line",
-        point: true,
-      },
-      encoding: {
-        x: {
-          field: "timestamp",
-          type: "temporal",
-          title: "Time",
-          axis: {
-            format: "%H:%M:%S",
-            title: "Time",
-            labelAngle: -45,
-          },
-        },
-        y: {
-          field: "cost",
-          type: "quantitative",
-          title: "Cumulative Cost (USD)",
-          axis: {
-            format: "$,.4f",
-          },
-        },
-        tooltip: [
-          {
-            field: "timestamp",
-            type: "temporal",
-            title: "Time",
-            format: "%Y-%m-%d %H:%M:%S",
-          },
-          {
-            field: "cost",
-            type: "quantitative",
-            title: "Cumulative Cost",
-            format: "$,.4f",
-          },
-        ],
-      },
-    };
-  }
-}
-
-declare global {
-  interface HTMLElementTagNameMap {
-    "sketch-charts": SketchCharts;
-  }
-}
diff --git a/loop/webui/src/web-components/sketch-chat-input.test.ts b/loop/webui/src/web-components/sketch-chat-input.test.ts
deleted file mode 100644
index efb303f..0000000
--- a/loop/webui/src/web-components/sketch-chat-input.test.ts
+++ /dev/null
@@ -1,163 +0,0 @@
-import { test, expect } from "@sand4rt/experimental-ct-web";
-import { SketchChatInput } from "./sketch-chat-input";
-
-test("initializes with empty content by default", async ({ mount }) => {
-  const component = await mount(SketchChatInput, {});
-
-  // Check public property via component's evaluate method
-  const content = await component.evaluate((el: SketchChatInput) => el.content);
-  expect(content).toBe("");
-
-  // Check textarea value
-  await expect(component.locator("#chatInput")).toHaveValue("");
-});
-
-test("initializes with provided content", async ({ mount }) => {
-  const testContent = "Hello, world!";
-  const component = await mount(SketchChatInput, {
-    props: {
-      content: testContent,
-    },
-  });
-
-  // Check public property via component's evaluate method
-  const content = await component.evaluate((el: SketchChatInput) => el.content);
-  expect(content).toBe(testContent);
-
-  // Check textarea value
-  await expect(component.locator("#chatInput")).toHaveValue(testContent);
-});
-
-test("updates content when typing in the textarea", async ({ mount }) => {
-  const component = await mount(SketchChatInput, {});
-  const newValue = "New message";
-
-  // Fill the textarea with new content
-  await component.locator("#chatInput").fill(newValue);
-
-  // Check that the content property was updated
-  const content = await component.evaluate((el: SketchChatInput) => el.content);
-  expect(content).toBe(newValue);
-});
-
-test("sends message when clicking the send button", async ({ mount }) => {
-  const testContent = "Test message";
-  const component = await mount(SketchChatInput, {
-    props: {
-      content: testContent,
-    },
-  });
-
-  // Set up promise to wait for the event
-  const eventPromise = component.evaluate((el) => {
-    return new Promise((resolve) => {
-      el.addEventListener(
-        "send-chat",
-        (event) => {
-          resolve((event as CustomEvent).detail);
-        },
-        { once: true },
-      );
-    });
-  });
-
-  // Click the send button
-  await component.locator("#sendChatButton").click();
-
-  // Wait for the event and check its details
-  const detail: any = await eventPromise;
-  expect(detail.message).toBe(testContent);
-});
-
-test.skip("sends message when pressing Enter (without shift)", async ({
-  mount,
-}) => {
-  const testContent = "Test message";
-  const component = await mount(SketchChatInput, {
-    props: {
-      content: testContent,
-    },
-  });
-
-  // Set up promise to wait for the event
-  const eventPromise = component.evaluate((el) => {
-    return new Promise((resolve) => {
-      el.addEventListener(
-        "send-chat",
-        (event) => {
-          resolve((event as CustomEvent).detail);
-        },
-        { once: true },
-      );
-    });
-  });
-
-  // Press Enter in the textarea
-  await component.locator("#chatInput").press("Enter");
-
-  // Wait for the event and check its details
-  const detail: any = await eventPromise;
-  expect(detail.message).toBe(testContent);
-
-  // Check that content was cleared
-  const content = await component.evaluate((el: SketchChatInput) => el.content);
-  expect(content).toBe("");
-});
-
-test.skip("does not send message when pressing Shift+Enter", async ({
-  mount,
-}) => {
-  const testContent = "Test message";
-  const component = await mount(SketchChatInput, {
-    props: {
-      content: testContent,
-    },
-  });
-
-  // Set up to track if event fires
-  let eventFired = false;
-  await component.evaluate((el) => {
-    el.addEventListener("send-chat", () => {
-      (window as any).__eventFired = true;
-    });
-    (window as any).__eventFired = false;
-  });
-
-  // Press Shift+Enter in the textarea
-  await component.locator("#chatInput").press("Shift+Enter");
-
-  // Wait a short time and check if event fired
-  await new Promise((resolve) => setTimeout(resolve, 50));
-  eventFired = await component.evaluate(() => (window as any).__eventFired);
-  expect(eventFired).toBe(false);
-
-  // Check that content was not cleared
-  const content = await component.evaluate((el: SketchChatInput) => el.content);
-  expect(content).toBe(testContent);
-});
-
-test("resizes when user enters more text than will fit", async ({ mount }) => {
-  const testContent = "Test message\n\n\n\n\n\n\n\n\n\n\n\n\nends here.";
-  const component = await mount(SketchChatInput, {
-    props: {
-      content: "",
-    },
-  });
-  const origHeight = await component.evaluate(
-    (el: SketchChatInput) => el.chatInput.style.height,
-  );
-
-  // Enter very tall text in the textarea
-  await component.locator("#chatInput").fill(testContent);
-
-  // Wait for the requestAnimationFrame to complete
-  await component.evaluate(() => new Promise(requestAnimationFrame));
-
-  // Check that textarea resized
-  const newHeight = await component.evaluate(
-    (el: SketchChatInput) => el.chatInput.style.height,
-  );
-  expect(Number.parseInt(newHeight)).toBeGreaterThan(
-    Number.parseInt(origHeight),
-  );
-});
diff --git a/loop/webui/src/web-components/sketch-chat-input.ts b/loop/webui/src/web-components/sketch-chat-input.ts
deleted file mode 100644
index 74e462f..0000000
--- a/loop/webui/src/web-components/sketch-chat-input.ts
+++ /dev/null
@@ -1,176 +0,0 @@
-import { css, html, LitElement, PropertyValues } from "lit";
-import { customElement, property, state, query } from "lit/decorators.js";
-
-@customElement("sketch-chat-input")
-export class SketchChatInput extends LitElement {
-  @state()
-  content: 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`
-    /* Chat styles - exactly matching timeline.css */
-    .chat-container {
-      position: fixed;
-      bottom: 0;
-      left: 0;
-      width: 100%;
-      background: #f0f0f0;
-      padding: 15px;
-      box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1);
-      z-index: 1000;
-      min-height: 40px; /* Ensure minimum height */
-    }
-
-    .chat-input-wrapper {
-      display: flex;
-      max-width: 1200px;
-      margin: 0 auto;
-      gap: 10px;
-    }
-
-    #chatInput {
-      flex: 1;
-      padding: 12px;
-      border: 1px solid #ddd;
-      border-radius: 4px;
-      resize: vertical;
-      font-family: monospace;
-      font-size: 12px;
-      min-height: 40px;
-      max-height: 300px;
-      background: #f7f7f7;
-      overflow-y: auto;
-      box-sizing: border-box; /* Ensure padding is included in height calculation */
-      line-height: 1.4; /* Consistent line height for better height calculation */
-    }
-
-    #sendChatButton {
-      background-color: #2196f3;
-      color: white;
-      border: none;
-      border-radius: 4px;
-      padding: 0 20px;
-      cursor: pointer;
-      font-weight: 600;
-      align-self: center;
-      height: 40px;
-    }
-
-    #sendChatButton:hover {
-      background-color: #0d8bf2;
-    }
-  `;
-
-  constructor() {
-    super();
-    this._handleDiffComment = this._handleDiffComment.bind(this);
-  }
-
-  connectedCallback() {
-    super.connectedCallback();
-    window.addEventListener("diff-comment", this._handleDiffComment);
-  }
-
-  private _handleDiffComment(event: CustomEvent) {
-    const { comment } = event.detail;
-    if (!comment) return;
-
-    if (this.content != "") {
-      this.content += "\n\n";
-    }
-    this.content += comment;
-    requestAnimationFrame(() => this.adjustChatSpacing());
-  }
-
-  // See https://lit.dev/docs/components/lifecycle/
-  disconnectedCallback() {
-    super.disconnectedCallback();
-    window.removeEventListener("diff-comment", this._handleDiffComment);
-  }
-
-  sendChatMessage() {
-    const event = new CustomEvent("send-chat", {
-      detail: { message: this.content },
-      bubbles: true,
-      composed: true,
-    });
-    this.dispatchEvent(event);
-
-    // TODO(philip?): Ideally we only clear the content if the send is successful.
-    this.content = ""; // Clear content after sending
-  }
-
-  adjustChatSpacing() {
-    if (!this.chatInput) return;
-
-    // Reset height to minimal value to correctly calculate scrollHeight
-    this.chatInput.style.height = "auto";
-
-    // Get the scroll height (content height)
-    const scrollHeight = this.chatInput.scrollHeight;
-
-    // Set the height to match content (up to max-height which is handled by CSS)
-    this.chatInput.style.height = `${scrollHeight}px`;
-  }
-
-  async _sendChatClicked() {
-    this.sendChatMessage();
-    this.chatInput.focus(); // Refocus the input after sending
-    // Reset height after sending a message
-    requestAnimationFrame(() => this.adjustChatSpacing());
-  }
-
-  _chatInputKeyDown(event: KeyboardEvent) {
-    // Send message if Enter is pressed without Shift key
-    if (event.key === "Enter" && !event.shiftKey) {
-      event.preventDefault(); // Prevent default newline
-      this.sendChatMessage();
-    }
-  }
-
-  _chatInputChanged(event) {
-    this.content = event.target.value;
-    // Use requestAnimationFrame to ensure DOM updates have completed
-    requestAnimationFrame(() => this.adjustChatSpacing());
-  }
-
-  @query("#chatInput")
-  chatInput: HTMLTextAreaElement;
-
-  protected firstUpdated(): void {
-    if (this.chatInput) {
-      this.chatInput.focus();
-      // Initialize the input height
-      this.adjustChatSpacing();
-    }
-  }
-
-  render() {
-    return html`
-      <div class="chat-container">
-        <div class="chat-input-wrapper">
-          <textarea
-            id="chatInput"
-            placeholder="Type your message here and press Enter to send..."
-            autofocus
-            @keydown="${this._chatInputKeyDown}"
-            @input="${this._chatInputChanged}"
-            .value=${this.content || ""}
-          ></textarea>
-          <button @click="${this._sendChatClicked}" id="sendChatButton">
-            Send
-          </button>
-        </div>
-      </div>
-    `;
-  }
-}
-
-declare global {
-  interface HTMLElementTagNameMap {
-    "sketch-chat-input": SketchChatInput;
-  }
-}
diff --git a/loop/webui/src/web-components/sketch-container-status.test.ts b/loop/webui/src/web-components/sketch-container-status.test.ts
deleted file mode 100644
index db11a4e..0000000
--- a/loop/webui/src/web-components/sketch-container-status.test.ts
+++ /dev/null
@@ -1,171 +0,0 @@
-import { test, expect } from "@sand4rt/experimental-ct-web";
-import { SketchContainerStatus } from "./sketch-container-status";
-import { State } from "../types";
-
-// Mock complete state for testing
-const mockCompleteState: State = {
-  hostname: "test-host",
-  working_dir: "/test/dir",
-  initial_commit: "abcdef1234567890",
-  message_count: 42,
-  os: "linux",
-  title: "Test Session",
-  total_usage: {
-    input_tokens: 1000,
-    output_tokens: 2000,
-    cache_read_input_tokens: 300,
-    cache_creation_input_tokens: 400,
-    total_cost_usd: 0.25,
-    start_time: "",
-    messages: 0,
-    tool_uses: {},
-  },
-};
-
-test("render props", async ({ mount }) => {
-  const component = await mount(SketchContainerStatus, {
-    props: {
-      state: mockCompleteState,
-    },
-  });
-  await expect(component.locator("#hostname")).toContainText(
-    mockCompleteState.hostname,
-  );
-  // Check that all expected elements exist
-  await expect(component.locator("#workingDir")).toContainText(
-    mockCompleteState.working_dir,
-  );
-  await expect(component.locator("#initialCommit")).toContainText(
-    mockCompleteState.initial_commit.substring(0, 8),
-  );
-
-  await expect(component.locator("#messageCount")).toContainText(
-    mockCompleteState.message_count + "",
-  );
-  await expect(component.locator("#inputTokens")).toContainText(
-    mockCompleteState.total_usage.input_tokens + "",
-  );
-  await expect(component.locator("#outputTokens")).toContainText(
-    mockCompleteState.total_usage.output_tokens + "",
-  );
-
-  await expect(component.locator("#cacheReadInputTokens")).toContainText(
-    mockCompleteState.total_usage.cache_read_input_tokens + "",
-  );
-  await expect(component.locator("#cacheCreationInputTokens")).toContainText(
-    mockCompleteState.total_usage.cache_creation_input_tokens + "",
-  );
-  await expect(component.locator("#totalCost")).toContainText(
-    "$" + mockCompleteState.total_usage.total_cost_usd.toFixed(2),
-  );
-});
-
-test("renders with undefined state", async ({ mount }) => {
-  const component = await mount(SketchContainerStatus, {});
-
-  // Elements should exist but be empty
-  await expect(component.locator("#hostname")).toContainText("");
-  await expect(component.locator("#workingDir")).toContainText("");
-  await expect(component.locator("#initialCommit")).toContainText("");
-  await expect(component.locator("#messageCount")).toContainText("");
-  await expect(component.locator("#inputTokens")).toContainText("");
-  await expect(component.locator("#outputTokens")).toContainText("");
-  await expect(component.locator("#totalCost")).toContainText("$0.00");
-});
-
-test("renders with partial state data", async ({ mount }) => {
-  const partialState: Partial<State> = {
-    hostname: "partial-host",
-    message_count: 10,
-    os: "linux",
-    title: "Partial Test",
-    total_usage: {
-      input_tokens: 500,
-      start_time: "",
-      messages: 0,
-      output_tokens: 0,
-      cache_read_input_tokens: 0,
-      cache_creation_input_tokens: 0,
-      total_cost_usd: 0,
-      tool_uses: {},
-    },
-  };
-
-  const component = await mount(SketchContainerStatus, {
-    props: {
-      state: partialState as State,
-    },
-  });
-
-  // Check that elements with data are properly populated
-  await expect(component.locator("#hostname")).toContainText("partial-host");
-  await expect(component.locator("#messageCount")).toContainText("10");
-  await expect(component.locator("#inputTokens")).toContainText("500");
-
-  // Check that elements without data are empty
-  await expect(component.locator("#workingDir")).toContainText("");
-  await expect(component.locator("#initialCommit")).toContainText("");
-  await expect(component.locator("#outputTokens")).toContainText("");
-  await expect(component.locator("#totalCost")).toContainText("$0.00");
-});
-
-test("handles cost formatting correctly", async ({ mount }) => {
-  // Test with different cost values
-  const testCases = [
-    { cost: 0, expected: "$0.00" },
-    { cost: 0.1, expected: "$0.10" },
-    { cost: 1.234, expected: "$1.23" },
-    { cost: 10.009, expected: "$10.01" },
-  ];
-
-  for (const testCase of testCases) {
-    const stateWithCost = {
-      ...mockCompleteState,
-      total_usage: {
-        ...mockCompleteState.total_usage,
-        total_cost_usd: testCase.cost,
-      },
-    };
-
-    const component = await mount(SketchContainerStatus, {
-      props: {
-        state: stateWithCost,
-      },
-    });
-    await expect(component.locator("#totalCost")).toContainText(
-      testCase.expected,
-    );
-    await component.unmount();
-  }
-});
-
-test("truncates commit hash to 8 characters", async ({ mount }) => {
-  const stateWithLongCommit = {
-    ...mockCompleteState,
-    initial_commit: "1234567890abcdef1234567890abcdef12345678",
-  };
-
-  const component = await mount(SketchContainerStatus, {
-    props: {
-      state: stateWithLongCommit,
-    },
-  });
-
-  await expect(component.locator("#initialCommit")).toContainText("12345678");
-});
-
-test("has correct link elements", async ({ mount }) => {
-  const component = await mount(SketchContainerStatus, {
-    props: {
-      state: mockCompleteState,
-    },
-  });
-
-  // Check for logs link
-  const logsLink = component.locator("a").filter({ hasText: "Logs" });
-  await expect(logsLink).toHaveAttribute("href", "logs");
-
-  // Check for download link
-  const downloadLink = component.locator("a").filter({ hasText: "Download" });
-  await expect(downloadLink).toHaveAttribute("href", "download");
-});
diff --git a/loop/webui/src/web-components/sketch-container-status.ts b/loop/webui/src/web-components/sketch-container-status.ts
deleted file mode 100644
index 9e542cb..0000000
--- a/loop/webui/src/web-components/sketch-container-status.ts
+++ /dev/null
@@ -1,237 +0,0 @@
-import { State } from "../types";
-import { LitElement, css, html } from "lit";
-import { customElement, property } from "lit/decorators.js";
-
-@customElement("sketch-container-status")
-export class SketchContainerStatus extends LitElement {
-  // Header bar: Container status details
-
-  @property()
-  state: State;
-
-  // 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`
-    .info-card {
-      background: #f9f9f9;
-      border-radius: 8px;
-      padding: 15px;
-      margin-bottom: 20px;
-      box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
-      display: none; /* Hidden in the combined layout */
-    }
-
-    .info-grid {
-      display: flex;
-      flex-wrap: wrap;
-      gap: 8px;
-      background: #f9f9f9;
-      border-radius: 4px;
-      padding: 4px 10px;
-      box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
-      flex: 1;
-    }
-
-    .info-item {
-      display: flex;
-      align-items: center;
-      white-space: nowrap;
-      margin-right: 10px;
-      font-size: 13px;
-    }
-
-    .info-label {
-      font-size: 11px;
-      color: #555;
-      margin-right: 3px;
-      font-weight: 500;
-    }
-
-    .info-value {
-      font-size: 11px;
-      font-weight: 600;
-    }
-
-    [title] {
-      cursor: help;
-      text-decoration: underline dotted;
-    }
-
-    .cost {
-      color: #2e7d32;
-    }
-
-    .info-item a {
-      --tw-text-opacity: 1;
-      color: rgb(37 99 235 / var(--tw-text-opacity, 1));
-      text-decoration: inherit;
-    }
-  `;
-
-  constructor() {
-    super();
-  }
-
-  formatHostname() {
-    const outsideHostname = this.state?.outside_hostname;
-    const insideHostname = this.state?.inside_hostname;
-
-    if (!outsideHostname || !insideHostname) {
-      return this.state?.hostname;
-    }
-
-    if (outsideHostname === insideHostname) {
-      return outsideHostname;
-    }
-
-    return `${outsideHostname}:${insideHostname}`;
-  }
-
-  formatWorkingDir() {
-    const outsideWorkingDir = this.state?.outside_working_dir;
-    const insideWorkingDir = this.state?.inside_working_dir;
-
-    if (!outsideWorkingDir || !insideWorkingDir) {
-      return this.state?.working_dir;
-    }
-
-    if (outsideWorkingDir === insideWorkingDir) {
-      return outsideWorkingDir;
-    }
-
-    return `${outsideWorkingDir}:${insideWorkingDir}`;
-  }
-
-  getHostnameTooltip() {
-    const outsideHostname = this.state?.outside_hostname;
-    const insideHostname = this.state?.inside_hostname;
-
-    if (
-      !outsideHostname ||
-      !insideHostname ||
-      outsideHostname === insideHostname
-    ) {
-      return "";
-    }
-
-    return `Outside: ${outsideHostname}, Inside: ${insideHostname}`;
-  }
-
-  getWorkingDirTooltip() {
-    const outsideWorkingDir = this.state?.outside_working_dir;
-    const insideWorkingDir = this.state?.inside_working_dir;
-
-    if (
-      !outsideWorkingDir ||
-      !insideWorkingDir ||
-      outsideWorkingDir === insideWorkingDir
-    ) {
-      return "";
-    }
-
-    return `Outside: ${outsideWorkingDir}, Inside: ${insideWorkingDir}`;
-  }
-
-  // See https://lit.dev/docs/components/lifecycle/
-  connectedCallback() {
-    super.connectedCallback();
-    // register event listeners
-  }
-
-  // See https://lit.dev/docs/components/lifecycle/
-  disconnectedCallback() {
-    super.disconnectedCallback();
-    // unregister event listeners
-  }
-
-  render() {
-    return html`
-      <div class="info-grid">
-        <div class="info-item">
-          <a href="logs">Logs</a>
-        </div>
-        <div class="info-item">
-          <a href="download">Download</a>
-        </div>
-        <div class="info-item">
-          <span
-            id="hostname"
-            class="info-value"
-            title="${this.getHostnameTooltip()}"
-          >
-            ${this.formatHostname()}
-          </span>
-        </div>
-        <div class="info-item">
-          <span
-            id="workingDir"
-            class="info-value"
-            title="${this.getWorkingDirTooltip()}"
-          >
-            ${this.formatWorkingDir()}
-          </span>
-        </div>
-        ${this.state?.git_origin
-          ? html`
-              <div class="info-item">
-                <span class="info-label">Origin:</span>
-                <span id="gitOrigin" class="info-value"
-                  >${this.state?.git_origin}</span
-                >
-              </div>
-            `
-          : ""}
-        <div class="info-item">
-          <span class="info-label">Commit:</span>
-          <span id="initialCommit" class="info-value"
-            >${this.state?.initial_commit?.substring(0, 8)}</span
-          >
-        </div>
-        <div class="info-item">
-          <span class="info-label">Msgs:</span>
-          <span id="messageCount" class="info-value"
-            >${this.state?.message_count}</span
-          >
-        </div>
-        <div class="info-item">
-          <span class="info-label">In:</span>
-          <span id="inputTokens" class="info-value"
-            >${this.state?.total_usage?.input_tokens}</span
-          >
-        </div>
-        <div class="info-item">
-          <span class="info-label">Cache Read:</span>
-          <span id="cacheReadInputTokens" class="info-value"
-            >${this.state?.total_usage?.cache_read_input_tokens}</span
-          >
-        </div>
-        <div class="info-item">
-          <span class="info-label">Cache Create:</span>
-          <span id="cacheCreationInputTokens" class="info-value"
-            >${this.state?.total_usage?.cache_creation_input_tokens}</span
-          >
-        </div>
-        <div class="info-item">
-          <span class="info-label">Out:</span>
-          <span id="outputTokens" class="info-value"
-            >${this.state?.total_usage?.output_tokens}</span
-          >
-        </div>
-        <div class="info-item">
-          <span class="info-label">Cost:</span>
-          <span id="totalCost" class="info-value cost"
-            >$${(this.state?.total_usage?.total_cost_usd || 0).toFixed(2)}</span
-          >
-        </div>
-      </div>
-    `;
-  }
-}
-
-declare global {
-  interface HTMLElementTagNameMap {
-    "sketch-container-status": SketchContainerStatus;
-  }
-}
diff --git a/loop/webui/src/web-components/sketch-diff-view.ts b/loop/webui/src/web-components/sketch-diff-view.ts
deleted file mode 100644
index 47c14f3..0000000
--- a/loop/webui/src/web-components/sketch-diff-view.ts
+++ /dev/null
@@ -1,615 +0,0 @@
-import { css, html, LitElement, unsafeCSS } from "lit";
-import { customElement, property, state } from "lit/decorators.js";
-import * as Diff2Html from "diff2html";
-
-@customElement("sketch-diff-view")
-export class SketchDiffView extends LitElement {
-  // Current commit hash being viewed
-  @property({ type: String })
-  commitHash: string = "";
-
-  // Selected line in the diff for commenting
-  @state()
-  private selectedDiffLine: string | null = null;
-
-  // The clicked button element used for positioning the comment box
-  @state()
-  private clickedElement: HTMLElement | null = null;
-
-  // View format (side-by-side or line-by-line)
-  @state()
-  private viewFormat: "side-by-side" | "line-by-line" = "side-by-side";
-
-  static styles = css`
-    .diff-view {
-      flex: 1;
-      display: flex;
-      flex-direction: column;
-      overflow: hidden;
-      height: 100%;
-    }
-
-    .diff-container {
-      height: 100%;
-      overflow: auto;
-      flex: 1;
-      padding: 0 1rem;
-    }
-
-    #diff-view-controls {
-      display: flex;
-      justify-content: flex-end;
-      padding: 10px;
-      background: #f8f8f8;
-      border-bottom: 1px solid #eee;
-    }
-
-    .diff-view-format {
-      display: flex;
-      gap: 10px;
-    }
-
-    .diff-view-format label {
-      cursor: pointer;
-    }
-
-    .diff2html-content {
-      font-family: var(--monospace-font);
-      position: relative;
-    }
-
-    /* Comment box styles */
-    .diff-comment-box {
-      position: absolute;
-      width: 400px;
-      background-color: white;
-      border: 1px solid #ddd;
-      border-radius: 4px;
-      box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
-      padding: 16px;
-      z-index: 1000;
-      margin-top: 10px;
-    }
-
-    .diff-comment-box h3 {
-      margin-top: 0;
-      margin-bottom: 10px;
-      font-size: 16px;
-    }
-
-    .selected-line {
-      margin-bottom: 10px;
-      font-size: 14px;
-    }
-
-    .selected-line pre {
-      padding: 6px;
-      background: #f5f5f5;
-      border: 1px solid #eee;
-      border-radius: 3px;
-      margin: 5px 0;
-      max-height: 100px;
-      overflow: auto;
-      font-family: var(--monospace-font);
-      font-size: 13px;
-      white-space: pre-wrap;
-    }
-
-    #diffCommentInput {
-      width: 100%;
-      height: 100px;
-      padding: 8px;
-      border: 1px solid #ddd;
-      border-radius: 4px;
-      resize: vertical;
-      font-family: inherit;
-      margin-bottom: 10px;
-    }
-
-    .diff-comment-buttons {
-      display: flex;
-      justify-content: flex-end;
-      gap: 8px;
-    }
-
-    .diff-comment-buttons button {
-      padding: 6px 12px;
-      border-radius: 4px;
-      border: 1px solid #ddd;
-      background: white;
-      cursor: pointer;
-    }
-
-    .diff-comment-buttons button:hover {
-      background: #f5f5f5;
-    }
-
-    .diff-comment-buttons button#submitDiffComment {
-      background: #1a73e8;
-      color: white;
-      border-color: #1a73e8;
-    }
-
-    .diff-comment-buttons button#submitDiffComment:hover {
-      background: #1967d2;
-    }
-
-    /* Styles for the comment button on diff lines */
-    .d2h-gutter-comment-button {
-      position: absolute;
-      right: 0;
-      top: 50%;
-      transform: translateY(-50%);
-      visibility: hidden;
-      background: rgba(255, 255, 255, 0.8);
-      border-radius: 50%;
-      width: 16px;
-      height: 16px;
-      line-height: 13px;
-      text-align: center;
-      font-size: 14px;
-      cursor: pointer;
-      color: #666;
-      box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
-    }
-
-    tr:hover .d2h-gutter-comment-button {
-      visibility: visible;
-    }
-
-    .d2h-gutter-comment-button:hover {
-      background: white;
-      color: #333;
-      box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
-    }
-  `;
-
-  constructor() {
-    super();
-  }
-
-  // See https://lit.dev/docs/components/lifecycle/
-  connectedCallback() {
-    super.connectedCallback();
-
-    // Load the diff2html CSS if needed
-    this.loadDiff2HtmlCSS();
-  }
-
-  // Load diff2html CSS into the shadow DOM
-  private async loadDiff2HtmlCSS() {
-    try {
-      // Check if diff2html styles are already loaded
-      const styleId = "diff2html-styles";
-      if (this.shadowRoot?.getElementById(styleId)) {
-        return; // Already loaded
-      }
-
-      // Fetch the diff2html CSS
-      const response = await fetch("static/diff2html.min.css");
-
-      if (!response.ok) {
-        console.error(
-          `Failed to load diff2html CSS: ${response.status} ${response.statusText}`,
-        );
-        return;
-      }
-
-      const cssText = await response.text();
-
-      // Create a style element and append to shadow DOM
-      const style = document.createElement("style");
-      style.id = styleId;
-      style.textContent = cssText;
-      this.shadowRoot?.appendChild(style);
-
-      console.log("diff2html CSS loaded into shadow DOM");
-    } catch (error) {
-      console.error("Error loading diff2html CSS:", error);
-    }
-  }
-
-  // See https://lit.dev/docs/components/lifecycle/
-  disconnectedCallback() {
-    super.disconnectedCallback();
-  }
-
-  // Method called to load diff content
-  async loadDiffContent() {
-    // Wait for the component to be rendered
-    await this.updateComplete;
-
-    const diff2htmlContent =
-      this.shadowRoot?.getElementById("diff2htmlContent");
-    if (!diff2htmlContent) return;
-
-    try {
-      // Build the diff URL - include commit hash if specified
-      const diffUrl = this.commitHash
-        ? `diff?commit=${this.commitHash}`
-        : "diff";
-
-      if (this.commitHash) {
-        diff2htmlContent.innerHTML = `Loading diff for commit <strong>${this.commitHash}</strong>...`;
-      } else {
-        diff2htmlContent.innerHTML = "Loading diff...";
-      }
-
-      // Fetch the diff from the server
-      const response = await fetch(diffUrl);
-
-      if (!response.ok) {
-        throw new Error(
-          `Server returned ${response.status}: ${response.statusText}`,
-        );
-      }
-
-      const diffText = await response.text();
-
-      if (!diffText || diffText.trim() === "") {
-        diff2htmlContent.innerHTML =
-          "<span style='color: #666; font-style: italic;'>No changes detected since conversation started.</span>";
-        return;
-      }
-
-      // Render the diff using diff2html
-      const diffHtml = Diff2Html.html(diffText, {
-        outputFormat: this.viewFormat,
-        drawFileList: true,
-        matching: "lines",
-        renderNothingWhenEmpty: false,
-        colorScheme: "light" as any, // Force light mode to match the rest of the UI
-      });
-
-      // Insert the generated HTML
-      diff2htmlContent.innerHTML = diffHtml;
-
-      // Add CSS styles to ensure we don't have double scrollbars
-      const d2hFiles = diff2htmlContent.querySelectorAll(".d2h-file-wrapper");
-      d2hFiles.forEach((file) => {
-        const contentElem = file.querySelector(".d2h-files-diff");
-        if (contentElem) {
-          // Remove internal scrollbar - the outer container will handle scrolling
-          (contentElem as HTMLElement).style.overflow = "visible";
-          (contentElem as HTMLElement).style.maxHeight = "none";
-        }
-      });
-
-      // Add click event handlers to each code line for commenting
-      this.setupDiffLineComments();
-    } catch (error) {
-      console.error("Error loading diff2html content:", error);
-      const errorMessage =
-        error instanceof Error ? error.message : "Unknown error";
-      diff2htmlContent.innerHTML = `<span style='color: #dc3545;'>Error loading enhanced diff: ${errorMessage}</span>`;
-    }
-  }
-
-  // Handle view format changes
-  private handleViewFormatChange(event: Event) {
-    const input = event.target as HTMLInputElement;
-    if (input.checked) {
-      this.viewFormat = input.value as "side-by-side" | "line-by-line";
-      this.loadDiffContent();
-    }
-  }
-
-  /**
-   * Setup handlers for diff code lines to enable commenting
-   */
-  private setupDiffLineComments(): void {
-    const diff2htmlContent =
-      this.shadowRoot?.getElementById("diff2htmlContent");
-    if (!diff2htmlContent) return;
-
-    // Add plus buttons to each code line
-    this.addCommentButtonsToCodeLines();
-
-    // Use event delegation for handling clicks on plus buttons
-    diff2htmlContent.addEventListener("click", (event) => {
-      const target = event.target as HTMLElement;
-
-      // Only respond to clicks on the plus button
-      if (target.classList.contains("d2h-gutter-comment-button")) {
-        // Find the parent row first
-        const row = target.closest("tr");
-        if (!row) return;
-
-        // Then find the code line in that row
-        const codeLine =
-          row.querySelector(".d2h-code-side-line") ||
-          row.querySelector(".d2h-code-line");
-        if (!codeLine) return;
-
-        // Get the line text content
-        const lineContent = codeLine.querySelector(".d2h-code-line-ctn");
-        if (!lineContent) return;
-
-        const lineText = lineContent.textContent?.trim() || "";
-
-        // Get file name to add context
-        const fileHeader = codeLine
-          .closest(".d2h-file-wrapper")
-          ?.querySelector(".d2h-file-name");
-        const fileName = fileHeader
-          ? fileHeader.textContent?.trim()
-          : "Unknown file";
-
-        // Get line number if available
-        const lineNumElem = codeLine
-          .closest("tr")
-          ?.querySelector(".d2h-code-side-linenumber");
-        const lineNum = lineNumElem ? lineNumElem.textContent?.trim() : "";
-        const lineInfo = lineNum ? `Line ${lineNum}: ` : "";
-
-        // Format the line for the comment box with file context and line number
-        const formattedLine = `${fileName} ${lineInfo}${lineText}`;
-
-        console.log("Comment button clicked for line: ", formattedLine);
-
-        // Open the comment box with this line and store the clicked element for positioning
-        this.clickedElement = target;
-        this.openDiffCommentBox(formattedLine);
-
-        // Prevent event from bubbling up
-        event.stopPropagation();
-      }
-    });
-  }
-
-  /**
-   * Add plus buttons to each table row in the diff for commenting
-   */
-  private addCommentButtonsToCodeLines(): void {
-    const diff2htmlContent =
-      this.shadowRoot?.getElementById("diff2htmlContent");
-    if (!diff2htmlContent) return;
-
-    // Target code lines first, then find their parent rows
-    const codeLines = diff2htmlContent.querySelectorAll(
-      ".d2h-code-side-line, .d2h-code-line",
-    );
-
-    // Create a Set to store unique rows to avoid duplicates
-    const rowsSet = new Set<HTMLElement>();
-
-    // Get all rows that contain code lines
-    codeLines.forEach((line) => {
-      const row = line.closest("tr");
-      if (row) rowsSet.add(row as HTMLElement);
-    });
-
-    // Convert Set back to array for processing
-    const codeRows = Array.from(rowsSet);
-
-    codeRows.forEach((row) => {
-      const rowElem = row as HTMLElement;
-
-      // Skip info lines without actual code (e.g., "file added")
-      if (rowElem.querySelector(".d2h-info")) {
-        return;
-      }
-
-      // Find the code line number element (first TD in the row)
-      const lineNumberCell = rowElem.querySelector(
-        ".d2h-code-side-linenumber, .d2h-code-linenumber",
-      );
-
-      if (!lineNumberCell) return;
-
-      // Create the plus button
-      const plusButton = document.createElement("span");
-      plusButton.className = "d2h-gutter-comment-button";
-      plusButton.innerHTML = "+";
-      plusButton.title = "Add a comment on this line";
-
-      // Add button to the line number cell for proper positioning
-      (lineNumberCell as HTMLElement).style.position = "relative"; // Ensure positioning context
-      lineNumberCell.appendChild(plusButton);
-    });
-  }
-
-  /**
-   * Open the comment box for a selected diff line
-   */
-  private openDiffCommentBox(lineText: string): void {
-    // Make sure the comment box div exists
-    const commentBoxId = "diffCommentBox";
-    let commentBox = this.shadowRoot?.getElementById(commentBoxId);
-
-    // If it doesn't exist, create it
-    if (!commentBox) {
-      commentBox = document.createElement("div");
-      commentBox.id = commentBoxId;
-      commentBox.className = "diff-comment-box";
-
-      // Create the comment box contents
-      commentBox.innerHTML = `
-        <h3>Add a comment</h3>
-        <div class="selected-line">
-          Line:
-          <pre id="selectedLine"></pre>
-        </div>
-        <textarea
-          id="diffCommentInput"
-          placeholder="Enter your comment about this line..."
-        ></textarea>
-        <div class="diff-comment-buttons">
-          <button id="cancelDiffComment">Cancel</button>
-          <button id="submitDiffComment">Add Comment</button>
-        </div>
-      `;
-
-      // Append the comment box to the diff container to ensure proper positioning
-      const diffContainer = this.shadowRoot?.querySelector(".diff-container");
-      if (diffContainer) {
-        diffContainer.appendChild(commentBox);
-      } else {
-        this.shadowRoot?.appendChild(commentBox);
-      }
-    }
-
-    // Store the selected line
-    this.selectedDiffLine = lineText;
-
-    // Display the line in the comment box
-    const selectedLine = this.shadowRoot?.getElementById("selectedLine");
-    if (selectedLine) {
-      selectedLine.textContent = lineText;
-    }
-
-    // Reset the comment input
-    const commentInput = this.shadowRoot?.getElementById(
-      "diffCommentInput",
-    ) as HTMLTextAreaElement;
-    if (commentInput) {
-      commentInput.value = "";
-    }
-
-    // Show the comment box and position it below the clicked line
-    if (commentBox && this.clickedElement) {
-      // Get the row that contains the clicked button
-      const row = this.clickedElement.closest("tr");
-      if (row) {
-        // Get the position of the row
-        const rowRect = row.getBoundingClientRect();
-        const diffContainerRect = this.shadowRoot
-          ?.querySelector(".diff-container")
-          ?.getBoundingClientRect();
-
-        if (diffContainerRect) {
-          // Position the comment box below the row
-          const topPosition =
-            rowRect.bottom -
-            diffContainerRect.top +
-            this.shadowRoot!.querySelector(".diff-container")!.scrollTop;
-          const leftPosition = rowRect.left - diffContainerRect.left;
-
-          commentBox.style.top = `${topPosition}px`;
-          commentBox.style.left = `${leftPosition}px`;
-          commentBox.style.display = "block";
-        }
-      } else {
-        // Fallback if we can't find the row
-        commentBox.style.display = "block";
-      }
-    } else if (commentBox) {
-      // Fallback if we don't have clickedElement
-      commentBox.style.display = "block";
-    }
-
-    // Add event listeners for submit and cancel buttons
-    const submitButton = this.shadowRoot?.getElementById("submitDiffComment");
-    if (submitButton) {
-      submitButton.onclick = () => this.submitDiffComment();
-    }
-
-    const cancelButton = this.shadowRoot?.getElementById("cancelDiffComment");
-    if (cancelButton) {
-      cancelButton.onclick = () => this.closeDiffCommentBox();
-    }
-
-    // Focus on the comment input
-    if (commentInput) {
-      commentInput.focus();
-    }
-  }
-
-  /**
-   * Close the diff comment box without submitting
-   */
-  private closeDiffCommentBox(): void {
-    const commentBox = this.shadowRoot?.getElementById("diffCommentBox");
-    if (commentBox) {
-      commentBox.style.display = "none";
-    }
-    this.selectedDiffLine = null;
-    this.clickedElement = null;
-  }
-
-  /**
-   * Submit a comment on a diff line
-   */
-  private submitDiffComment(): void {
-    const commentInput = this.shadowRoot?.getElementById(
-      "diffCommentInput",
-    ) as HTMLTextAreaElement;
-
-    if (!commentInput) return;
-
-    const comment = commentInput.value.trim();
-
-    // Validate inputs
-    if (!this.selectedDiffLine || !comment) {
-      alert("Please select a line and enter a comment.");
-      return;
-    }
-
-    // Format the comment in a readable way
-    const formattedComment = `\`\`\`\n${this.selectedDiffLine}\n\`\`\`\n\n${comment}`;
-
-    // Dispatch a custom event with the formatted comment
-    const event = new CustomEvent("diff-comment", {
-      detail: { comment: formattedComment },
-      bubbles: true,
-      composed: true,
-    });
-    this.dispatchEvent(event);
-
-    // Close only the comment box but keep the diff view open
-    this.closeDiffCommentBox();
-  }
-
-  // Clear the current state
-  public clearState(): void {
-    this.commitHash = "";
-  }
-
-  // Show diff for a specific commit
-  public showCommitDiff(commitHash: string): void {
-    // Store the commit hash
-    this.commitHash = commitHash;
-    // Load the diff content
-    this.loadDiffContent();
-  }
-
-  render() {
-    return html`
-      <div class="diff-view">
-        <div class="diff-container">
-          <div id="diff-view-controls">
-            <div class="diff-view-format">
-              <label>
-                <input
-                  type="radio"
-                  name="diffViewFormat"
-                  value="side-by-side"
-                  ?checked=${this.viewFormat === "side-by-side"}
-                  @change=${this.handleViewFormatChange}
-                />
-                Side-by-side
-              </label>
-              <label>
-                <input
-                  type="radio"
-                  name="diffViewFormat"
-                  value="line-by-line"
-                  ?checked=${this.viewFormat === "line-by-line"}
-                  @change=${this.handleViewFormatChange}
-                />
-                Line-by-line
-              </label>
-            </div>
-          </div>
-          <div id="diff2htmlContent" class="diff2html-content"></div>
-        </div>
-      </div>
-    `;
-  }
-}
-
-declare global {
-  interface HTMLElementTagNameMap {
-    "sketch-diff-view": SketchDiffView;
-  }
-}
diff --git a/loop/webui/src/web-components/sketch-network-status.test.ts b/loop/webui/src/web-components/sketch-network-status.test.ts
deleted file mode 100644
index 45882a0..0000000
--- a/loop/webui/src/web-components/sketch-network-status.test.ts
+++ /dev/null
@@ -1,65 +0,0 @@
-import { test, expect } from "@sand4rt/experimental-ct-web";
-import { SketchNetworkStatus } from "./sketch-network-status";
-
-test("displays the correct connection status when connected", async ({
-  mount,
-}) => {
-  const component = await mount(SketchNetworkStatus, {
-    props: {
-      connection: "connected",
-      message: "Connected to server",
-    },
-  });
-
-  await expect(component.locator(".polling-indicator")).toBeVisible();
-  await expect(component.locator(".status-text")).toBeVisible();
-  await expect(component.locator(".polling-indicator.active")).toBeVisible();
-  await expect(component.locator(".status-text")).toContainText(
-    "Connected to server",
-  );
-});
-
-test("displays the correct connection status when disconnected", async ({
-  mount,
-}) => {
-  const component = await mount(SketchNetworkStatus, {
-    props: {
-      connection: "disconnected",
-      message: "Disconnected",
-    },
-  });
-
-  await expect(component.locator(".polling-indicator")).toBeVisible();
-  await expect(component.locator(".polling-indicator.error")).toBeVisible();
-});
-
-test("displays the correct connection status when disabled", async ({
-  mount,
-}) => {
-  const component = await mount(SketchNetworkStatus, {
-    props: {
-      connection: "disabled",
-      message: "Disabled",
-    },
-  });
-
-  await expect(component.locator(".polling-indicator")).toBeVisible();
-  await expect(component.locator(".polling-indicator.error")).not.toBeVisible();
-  await expect(
-    component.locator(".polling-indicator.active"),
-  ).not.toBeVisible();
-});
-
-test("displays error message when provided", async ({ mount }) => {
-  const errorMsg = "Connection error";
-  const component = await mount(SketchNetworkStatus, {
-    props: {
-      connection: "disconnected",
-      message: "Disconnected",
-      error: errorMsg,
-    },
-  });
-
-  await expect(component.locator(".status-text")).toBeVisible();
-  await expect(component.locator(".status-text")).toContainText(errorMsg);
-});
diff --git a/loop/webui/src/web-components/sketch-network-status.ts b/loop/webui/src/web-components/sketch-network-status.ts
deleted file mode 100644
index 2a0e455..0000000
--- a/loop/webui/src/web-components/sketch-network-status.ts
+++ /dev/null
@@ -1,103 +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()
-  message: 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: flex;
-      align-items: center;
-    }
-
-    .polling-indicator {
-      display: inline-block;
-      width: 8px;
-      height: 8px;
-      border-radius: 50%;
-      margin-right: 4px;
-      background-color: #ccc;
-    }
-
-    .polling-indicator.active {
-      background-color: #4caf50;
-      animation: pulse 1.5s infinite;
-    }
-
-    .polling-indicator.error {
-      background-color: #f44336;
-      animation: pulse 1.5s infinite;
-    }
-
-    @keyframes pulse {
-      0% {
-        opacity: 1;
-      }
-      50% {
-        opacity: 0.5;
-      }
-      100% {
-        opacity: 1;
-      }
-    }
-
-    .status-text {
-      font-size: 11px;
-      color: #666;
-    }
-  `;
-
-  constructor() {
-    super();
-  }
-
-  // See https://lit.dev/docs/components/lifecycle/
-  connectedCallback() {
-    super.connectedCallback();
-  }
-
-  // See https://lit.dev/docs/components/lifecycle/
-  disconnectedCallback() {
-    super.disconnectedCallback();
-  }
-
-  indicator() {
-    if (this.connection === "disabled") {
-      return "";
-    }
-    return this.connection === "connected" ? "active" : "error";
-  }
-
-  render() {
-    return html`
-      <div class="status-container">
-        <span
-          id="pollingIndicator"
-          class="polling-indicator ${this.indicator()}"
-        ></span>
-        <span id="statusText" class="status-text"
-          >${this.error || this.message}</span
-        >
-      </div>
-    `;
-  }
-}
-
-declare global {
-  interface HTMLElementTagNameMap {
-    "sketch-network-status": SketchNetworkStatus;
-  }
-}
diff --git a/loop/webui/src/web-components/sketch-terminal.ts b/loop/webui/src/web-components/sketch-terminal.ts
deleted file mode 100644
index 4ffccfd..0000000
--- a/loop/webui/src/web-components/sketch-terminal.ts
+++ /dev/null
@@ -1,365 +0,0 @@
-import { Terminal } from "@xterm/xterm";
-import { FitAddon } from "@xterm/addon-fit";
-
-import { css, html, LitElement } from "lit";
-import { customElement } from "lit/decorators.js";
-import "./sketch-container-status";
-
-@customElement("sketch-terminal")
-export class SketchTerminal extends LitElement {
-  // Terminal instance
-  private terminal: Terminal | null = null;
-  // Terminal fit addon for handling resize
-  private fitAddon: FitAddon | null = null;
-  // Terminal EventSource for SSE
-  private terminalEventSource: EventSource | null = null;
-  // Terminal ID (always 1 for now, will support 1-9 later)
-  private terminalId: string = "1";
-  // Queue for serializing terminal inputs
-  private terminalInputQueue: string[] = [];
-  // Flag to track if we're currently processing a terminal input
-  private processingTerminalInput: boolean = false;
-
-  static styles = css`
-    /* Terminal View Styles */
-    .terminal-view {
-      width: 100%;
-      background-color: #f5f5f5;
-      border-radius: 8px;
-      overflow: hidden;
-      margin-bottom: 20px;
-      box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
-      padding: 15px;
-      height: 70vh;
-    }
-
-    .terminal-container {
-      width: 100%;
-      height: 100%;
-      overflow: hidden;
-    }
-  `;
-
-  constructor() {
-    super();
-    this._resizeHandler = this._resizeHandler.bind(this);
-  }
-
-  connectedCallback() {
-    super.connectedCallback();
-    this.loadXtermlCSS();
-    // Setup resize handler
-    window.addEventListener("resize", this._resizeHandler);
-  }
-
-  disconnectedCallback() {
-    super.disconnectedCallback();
-
-    window.removeEventListener("resize", this._resizeHandler);
-
-    this.closeTerminalConnections();
-
-    if (this.terminal) {
-      this.terminal.dispose();
-      this.terminal = null;
-    }
-    this.fitAddon = null;
-  }
-
-  firstUpdated() {
-    this.initializeTerminal();
-  }
-
-  _resizeHandler() {
-    if (this.fitAddon) {
-      this.fitAddon.fit();
-      // Send resize information to server
-      this.sendTerminalResize();
-    }
-  }
-
-  // Load xterm CSS into the shadow DOM
-  private async loadXtermlCSS() {
-    try {
-      // Check if diff2html styles are already loaded
-      const styleId = "xterm-styles";
-      if (this.shadowRoot?.getElementById(styleId)) {
-        return; // Already loaded
-      }
-
-      // Fetch the diff2html CSS
-      const response = await fetch("static/xterm.css");
-
-      if (!response.ok) {
-        console.error(
-          `Failed to load xterm CSS: ${response.status} ${response.statusText}`,
-        );
-        return;
-      }
-
-      const cssText = await response.text();
-
-      // Create a style element and append to shadow DOM
-      const style = document.createElement("style");
-      style.id = styleId;
-      style.textContent = cssText;
-      this.renderRoot?.appendChild(style);
-
-      console.log("xterm CSS loaded into shadow DOM");
-    } catch (error) {
-      console.error("Error loading xterm CSS:", error);
-    }
-  }
-
-  /**
-   * Initialize the terminal component
-   * @param terminalContainer The DOM element to contain the terminal
-   */
-  public async initializeTerminal(): Promise<void> {
-    const terminalContainer = this.renderRoot.querySelector(
-      "#terminalContainer",
-    ) as HTMLElement;
-
-    if (!terminalContainer) {
-      console.error("Terminal container not found");
-      return;
-    }
-
-    // If terminal is already initialized, just focus it
-    if (this.terminal) {
-      this.terminal.focus();
-      if (this.fitAddon) {
-        this.fitAddon.fit();
-      }
-      return;
-    }
-
-    // Clear the terminal container
-    terminalContainer.innerHTML = "";
-
-    // Create new terminal instance
-    this.terminal = new Terminal({
-      cursorBlink: true,
-      theme: {
-        background: "#f5f5f5",
-        foreground: "#333333",
-        cursor: "#0078d7",
-        selectionBackground: "rgba(0, 120, 215, 0.4)",
-      },
-    });
-
-    // Add fit addon to handle terminal resizing
-    this.fitAddon = new FitAddon();
-    this.terminal.loadAddon(this.fitAddon);
-
-    // Open the terminal in the container
-    this.terminal.open(terminalContainer);
-
-    // Connect to WebSocket
-    await this.connectTerminal();
-
-    // Fit the terminal to the container
-    this.fitAddon.fit();
-
-    // Focus the terminal
-    this.terminal.focus();
-  }
-
-  /**
-   * Connect to terminal events stream
-   */
-  private async connectTerminal(): Promise<void> {
-    if (!this.terminal) {
-      return;
-    }
-
-    // Close existing connections if any
-    this.closeTerminalConnections();
-
-    try {
-      // Connect directly to the SSE endpoint for terminal 1
-      // Use relative URL based on current location
-      const baseUrl = window.location.pathname.endsWith("/") ? "." : ".";
-      const eventsUrl = `${baseUrl}/terminal/events/${this.terminalId}`;
-      this.terminalEventSource = new EventSource(eventsUrl);
-
-      // Handle SSE events
-      this.terminalEventSource.onopen = () => {
-        console.log("Terminal SSE connection opened");
-        this.sendTerminalResize();
-      };
-
-      this.terminalEventSource.onmessage = (event) => {
-        if (this.terminal) {
-          // Decode base64 data before writing to terminal
-          try {
-            // @ts-ignore This isn't in the type definitions yet; it's pretty new?!?
-            const decoded = base64ToUint8Array(event.data);
-            this.terminal.write(decoded);
-          } catch (e) {
-            console.error("Error decoding terminal data:", e);
-          }
-        }
-      };
-
-      this.terminalEventSource.onerror = (error) => {
-        console.error("Terminal SSE error:", error);
-        if (this.terminal) {
-          this.terminal.write("\r\n\x1b[1;31mConnection error\x1b[0m\r\n");
-        }
-        // Attempt to reconnect if the connection was lost
-        if (this.terminalEventSource?.readyState === EventSource.CLOSED) {
-          this.closeTerminalConnections();
-        }
-      };
-
-      // Send key inputs to the server via POST requests
-      if (this.terminal) {
-        this.terminal.onData((data) => {
-          this.sendTerminalInput(data);
-        });
-      }
-    } catch (error) {
-      console.error("Failed to connect to terminal:", error);
-      if (this.terminal) {
-        this.terminal.write(
-          `\r\n\x1b[1;31mFailed to connect: ${error}\x1b[0m\r\n`,
-        );
-      }
-    }
-  }
-
-  /**
-   * Close any active terminal connections
-   */
-  private closeTerminalConnections(): void {
-    if (this.terminalEventSource) {
-      this.terminalEventSource.close();
-      this.terminalEventSource = null;
-    }
-  }
-
-  /**
-   * Send input to the terminal
-   * @param data The input data to send
-   */
-  private async sendTerminalInput(data: string): Promise<void> {
-    // Add the data to the queue
-    this.terminalInputQueue.push(data);
-
-    // If we're not already processing inputs, start processing
-    if (!this.processingTerminalInput) {
-      await this.processTerminalInputQueue();
-    }
-  }
-
-  /**
-   * Process the terminal input queue in order
-   */
-  private async processTerminalInputQueue(): Promise<void> {
-    if (this.terminalInputQueue.length === 0) {
-      this.processingTerminalInput = false;
-      return;
-    }
-
-    this.processingTerminalInput = true;
-
-    // Concatenate all available inputs from the queue into a single request
-    let combinedData = "";
-
-    // Take all currently available items from the queue
-    while (this.terminalInputQueue.length > 0) {
-      combinedData += this.terminalInputQueue.shift()!;
-    }
-
-    try {
-      // Use relative URL based on current location
-      const baseUrl = window.location.pathname.endsWith("/") ? "." : ".";
-      const response = await fetch(
-        `${baseUrl}/terminal/input/${this.terminalId}`,
-        {
-          method: "POST",
-          body: combinedData,
-          headers: {
-            "Content-Type": "text/plain",
-          },
-        },
-      );
-
-      if (!response.ok) {
-        console.error(
-          `Failed to send terminal input: ${response.status} ${response.statusText}`,
-        );
-      }
-    } catch (error) {
-      console.error("Error sending terminal input:", error);
-    }
-
-    // Continue processing the queue (for any new items that may have been added)
-    await this.processTerminalInputQueue();
-  }
-
-  /**
-   * Send terminal resize information to the server
-   */
-  private async sendTerminalResize(): Promise<void> {
-    if (!this.terminal || !this.fitAddon) {
-      return;
-    }
-
-    // Get terminal dimensions
-    try {
-      // Send resize message in a format the server can understand
-      // Use relative URL based on current location
-      const baseUrl = window.location.pathname.endsWith("/") ? "." : ".";
-      const response = await fetch(
-        `${baseUrl}/terminal/input/${this.terminalId}`,
-        {
-          method: "POST",
-          body: JSON.stringify({
-            type: "resize",
-            cols: this.terminal.cols || 80, // Default to 80 if undefined
-            rows: this.terminal.rows || 24, // Default to 24 if undefined
-          }),
-          headers: {
-            "Content-Type": "application/json",
-          },
-        },
-      );
-
-      if (!response.ok) {
-        console.error(
-          `Failed to send terminal resize: ${response.status} ${response.statusText}`,
-        );
-      }
-    } catch (error) {
-      console.error("Error sending terminal resize:", error);
-    }
-  }
-
-  render() {
-    return html`
-      <div id="terminalView" class="terminal-view">
-        <div id="terminalContainer" class="terminal-container"></div>
-      </div>
-    `;
-  }
-}
-
-function base64ToUint8Array(base64String) {
-  // This isn't yet available in Chrome, but Safari has it!
-  // @ts-ignore
-  if (Uint8Array.fromBase64) {
-    // @ts-ignore
-    return Uint8Array.fromBase64(base64String);
-  }
-
-  const binaryString = atob(base64String);
-  return Uint8Array.from(binaryString, (char) => char.charCodeAt(0));
-}
-
-declare global {
-  interface HTMLElementTagNameMap {
-    "sketch-terminal": SketchTerminal;
-  }
-}
diff --git a/loop/webui/src/web-components/sketch-timeline-message.test.ts b/loop/webui/src/web-components/sketch-timeline-message.test.ts
deleted file mode 100644
index bc74202..0000000
--- a/loop/webui/src/web-components/sketch-timeline-message.test.ts
+++ /dev/null
@@ -1,311 +0,0 @@
-import { test, expect } from "@sand4rt/experimental-ct-web";
-import { SketchTimelineMessage } from "./sketch-timeline-message";
-import {
-  AgentMessage,
-  CodingAgentMessageType,
-  GitCommit,
-  Usage,
-} from "../types";
-
-// Helper function to create mock timeline messages
-function createMockMessage(props: Partial<AgentMessage> = {}): AgentMessage {
-  return {
-    idx: props.idx || 0,
-    type: props.type || "agent",
-    content: props.content || "Hello world",
-    timestamp: props.timestamp || "2023-05-15T12:00:00Z",
-    elapsed: props.elapsed || 1500000000, // 1.5 seconds in nanoseconds
-    end_of_turn: props.end_of_turn || false,
-    conversation_id: props.conversation_id || "conv123",
-    tool_calls: props.tool_calls || [],
-    commits: props.commits || [],
-    usage: props.usage,
-    ...props,
-  };
-}
-
-test("renders with basic message content", async ({ mount }) => {
-  const message = createMockMessage({
-    type: "agent",
-    content: "This is a test message",
-  });
-
-  const component = await mount(SketchTimelineMessage, {
-    props: {
-      message: message,
-    },
-  });
-
-  await expect(component.locator(".message-text")).toBeVisible();
-  await expect(component.locator(".message-text")).toContainText(
-    "This is a test message",
-  );
-});
-
-test.skip("renders with correct message type classes", async ({ mount }) => {
-  const messageTypes: CodingAgentMessageType[] = [
-    "user",
-    "agent",
-    "error",
-    "budget",
-    "tool",
-    "commit",
-    "auto",
-  ];
-
-  for (const type of messageTypes) {
-    const message = createMockMessage({ type });
-
-    const component = await mount(SketchTimelineMessage, {
-      props: {
-        message: message,
-      },
-    });
-
-    await expect(component.locator(".message")).toBeVisible();
-    await expect(component.locator(`.message.${type}`)).toBeVisible();
-  }
-});
-
-test("renders end-of-turn marker correctly", async ({ mount }) => {
-  const message = createMockMessage({
-    end_of_turn: true,
-  });
-
-  const component = await mount(SketchTimelineMessage, {
-    props: {
-      message: message,
-    },
-  });
-
-  await expect(component.locator(".message")).toBeVisible();
-  await expect(component.locator(".message.end-of-turn")).toBeVisible();
-});
-
-test("formats timestamps correctly", async ({ mount }) => {
-  const message = createMockMessage({
-    timestamp: "2023-05-15T12:00:00Z",
-  });
-
-  const component = await mount(SketchTimelineMessage, {
-    props: {
-      message: message,
-    },
-  });
-
-  await expect(component.locator(".message-timestamp")).toBeVisible();
-  // Should include a formatted date like "May 15, 2023"
-  await expect(component.locator(".message-timestamp")).toContainText(
-    "May 15, 2023",
-  );
-  // Should include elapsed time
-  await expect(component.locator(".message-timestamp")).toContainText(
-    "(1.50s)",
-  );
-});
-
-test("renders markdown content correctly", async ({ mount }) => {
-  const markdownContent =
-    "# Heading\n\n- List item 1\n- List item 2\n\n`code block`";
-  const message = createMockMessage({
-    content: markdownContent,
-  });
-
-  const component = await mount(SketchTimelineMessage, {
-    props: {
-      message: message,
-    },
-  });
-
-  await expect(component.locator(".markdown-content")).toBeVisible();
-
-  // Check HTML content
-  const html = await component
-    .locator(".markdown-content")
-    .evaluate((element) => element.innerHTML);
-  expect(html).toContain("<h1>Heading</h1>");
-  expect(html).toContain("<ul>");
-  expect(html).toContain("<li>List item 1</li>");
-  expect(html).toContain("<code>code block</code>");
-});
-
-test("displays usage information when available", async ({ mount }) => {
-  const usage: Usage = {
-    input_tokens: 150,
-    output_tokens: 300,
-    cost_usd: 0.025,
-    cache_read_input_tokens: 50,
-    cache_creation_input_tokens: 0,
-  };
-
-  const message = createMockMessage({
-    usage,
-  });
-
-  const component = await mount(SketchTimelineMessage, {
-    props: {
-      message: message,
-    },
-  });
-
-  await expect(component.locator(".message-usage")).toBeVisible();
-  await expect(component.locator(".message-usage")).toContainText("150"); // In
-  await expect(component.locator(".message-usage")).toContainText("300"); // Out
-  await expect(component.locator(".message-usage")).toContainText("50"); // Cache
-  await expect(component.locator(".message-usage")).toContainText("$0.03"); // Cost
-});
-
-test("renders commit information correctly", async ({ mount }) => {
-  const commits: GitCommit[] = [
-    {
-      hash: "1234567890abcdef",
-      subject: "Fix bug in application",
-      body: "This fixes a major bug in the application\n\nSigned-off-by: Developer",
-      pushed_branch: "main",
-    },
-  ];
-
-  const message = createMockMessage({
-    commits,
-  });
-
-  const component = await mount(SketchTimelineMessage, {
-    props: {
-      message: message,
-    },
-  });
-
-  await expect(component.locator(".commits-container")).toBeVisible();
-  await expect(component.locator(".commits-header")).toBeVisible();
-  await expect(component.locator(".commits-header")).toContainText("1 new");
-
-  await expect(component.locator(".commit-hash")).toBeVisible();
-  await expect(component.locator(".commit-hash")).toHaveText("12345678"); // First 8 chars
-
-  await expect(component.locator(".pushed-branch")).toBeVisible();
-  await expect(component.locator(".pushed-branch")).toContainText("main");
-});
-
-test("dispatches show-commit-diff event when commit diff button is clicked", async ({
-  mount,
-}) => {
-  const commits: GitCommit[] = [
-    {
-      hash: "1234567890abcdef",
-      subject: "Fix bug in application",
-      body: "This fixes a major bug in the application",
-      pushed_branch: "main",
-    },
-  ];
-
-  const message = createMockMessage({
-    commits,
-  });
-
-  const component = await mount(SketchTimelineMessage, {
-    props: {
-      message: message,
-    },
-  });
-
-  await expect(component.locator(".commit-diff-button")).toBeVisible();
-
-  // Set up promise to wait for the event
-  const eventPromise = component.evaluate((el) => {
-    return new Promise((resolve) => {
-      el.addEventListener(
-        "show-commit-diff",
-        (event) => {
-          resolve((event as CustomEvent).detail);
-        },
-        { once: true },
-      );
-    });
-  });
-
-  // Click the diff button
-  await component.locator(".commit-diff-button").click();
-
-  // Wait for the event and check its details
-  const detail = await eventPromise;
-  expect(detail["commitHash"]).toBe("1234567890abcdef");
-});
-
-test.skip("handles message type icon display correctly", async ({ mount }) => {
-  // First message of a type should show icon
-  const firstMessage = createMockMessage({
-    type: "user",
-    idx: 0,
-  });
-
-  // Second message of same type should not show icon
-  const secondMessage = createMockMessage({
-    type: "user",
-    idx: 1,
-  });
-
-  // Test first message (should show icon)
-  const firstComponent = await mount(SketchTimelineMessage, {
-    props: {
-      message: firstMessage,
-    },
-  });
-
-  await expect(firstComponent.locator(".message-icon")).toBeVisible();
-  await expect(firstComponent.locator(".message-icon")).toHaveText("U");
-
-  // Test second message with previous message of same type
-  const secondComponent = await mount(SketchTimelineMessage, {
-    props: {
-      message: secondMessage,
-      previousMessage: firstMessage,
-    },
-  });
-
-  await expect(secondComponent.locator(".message-icon")).not.toBeVisible();
-});
-
-test("formats numbers correctly", async ({ mount }) => {
-  const component = await mount(SketchTimelineMessage, {});
-
-  // Test accessing public method via evaluate
-  const result1 = await component.evaluate((el: SketchTimelineMessage) =>
-    el.formatNumber(1000),
-  );
-  expect(result1).toBe("1,000");
-
-  const result2 = await component.evaluate((el: SketchTimelineMessage) =>
-    el.formatNumber(null, "N/A"),
-  );
-  expect(result2).toBe("N/A");
-
-  const result3 = await component.evaluate((el: SketchTimelineMessage) =>
-    el.formatNumber(undefined, "--"),
-  );
-  expect(result3).toBe("--");
-});
-
-test("formats currency values correctly", async ({ mount }) => {
-  const component = await mount(SketchTimelineMessage, {});
-
-  // Test with different precisions
-  const result1 = await component.evaluate((el: SketchTimelineMessage) =>
-    el.formatCurrency(10.12345, "$0.00", true),
-  );
-  expect(result1).toBe("$10.1235"); // message level (4 decimals)
-
-  const result2 = await component.evaluate((el: SketchTimelineMessage) =>
-    el.formatCurrency(10.12345, "$0.00", false),
-  );
-  expect(result2).toBe("$10.12"); // total level (2 decimals)
-
-  const result3 = await component.evaluate((el: SketchTimelineMessage) =>
-    el.formatCurrency(null, "N/A"),
-  );
-  expect(result3).toBe("N/A");
-
-  const result4 = await component.evaluate((el: SketchTimelineMessage) =>
-    el.formatCurrency(undefined, "--"),
-  );
-  expect(result4).toBe("--");
-});
diff --git a/loop/webui/src/web-components/sketch-timeline-message.ts b/loop/webui/src/web-components/sketch-timeline-message.ts
deleted file mode 100644
index 36f1640..0000000
--- a/loop/webui/src/web-components/sketch-timeline-message.ts
+++ /dev/null
@@ -1,765 +0,0 @@
-import { css, html, LitElement } from "lit";
-import { unsafeHTML } from "lit/directives/unsafe-html.js";
-import { customElement, property } from "lit/decorators.js";
-import { AgentMessage } from "../types";
-import { marked, MarkedOptions } from "marked";
-import "./sketch-tool-calls";
-@customElement("sketch-timeline-message")
-export class SketchTimelineMessage extends LitElement {
-  @property()
-  message: AgentMessage;
-
-  @property()
-  previousMessage: AgentMessage;
-
-  @property()
-  open: boolean = false;
-
-  // 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`
-    .message {
-      position: relative;
-      margin-bottom: 5px;
-      padding-left: 30px;
-    }
-
-    .message-icon {
-      position: absolute;
-      left: 10px;
-      top: 0;
-      transform: translateX(-50%);
-      width: 16px;
-      height: 16px;
-      border-radius: 3px;
-      text-align: center;
-      line-height: 16px;
-      color: #fff;
-      font-size: 10px;
-    }
-
-    .message-content {
-      position: relative;
-      padding: 5px 10px;
-      background: #fff;
-      border-radius: 3px;
-      box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
-      border-left: 3px solid transparent;
-    }
-
-    /* Copy button styles */
-    .message-text-container,
-    .tool-result-container {
-      position: relative;
-    }
-
-    .message-actions {
-      position: absolute;
-      top: 5px;
-      right: 5px;
-      z-index: 10;
-      opacity: 0;
-      transition: opacity 0.2s ease;
-    }
-
-    .message-text-container:hover .message-actions,
-    .tool-result-container:hover .message-actions {
-      opacity: 1;
-    }
-
-    .copy-button {
-      background-color: rgba(255, 255, 255, 0.9);
-      border: 1px solid #ddd;
-      border-radius: 4px;
-      color: #555;
-      cursor: pointer;
-      font-size: 12px;
-      padding: 2px 8px;
-      transition: all 0.2s ease;
-    }
-
-    .copy-button:hover {
-      background-color: #f0f0f0;
-      color: #333;
-    }
-
-    /* Removed arrow decoration for a more compact look */
-
-    .message-header {
-      display: flex;
-      flex-wrap: wrap;
-      gap: 5px;
-      margin-bottom: 3px;
-      font-size: 12px;
-    }
-
-    .message-timestamp {
-      font-size: 10px;
-      color: #888;
-      font-style: italic;
-      margin-left: 3px;
-    }
-
-    .message-usage {
-      font-size: 10px;
-      color: #888;
-      margin-left: 3px;
-    }
-
-    .conversation-id {
-      font-family: monospace;
-      font-size: 12px;
-      padding: 2px 4px;
-      background-color: #f0f0f0;
-      border-radius: 3px;
-      margin-left: auto;
-    }
-
-    .parent-info {
-      font-size: 11px;
-      opacity: 0.8;
-    }
-
-    .subconversation {
-      border-left: 2px solid transparent;
-      padding-left: 5px;
-      margin-left: 20px;
-      transition: margin-left 0.3s ease;
-    }
-
-    .message-text {
-      overflow-x: auto;
-      margin-bottom: 3px;
-      font-family: monospace;
-      padding: 3px 5px;
-      background: rgb(236, 236, 236);
-      border-radius: 6px;
-      user-select: text;
-      cursor: text;
-      -webkit-user-select: text;
-      -moz-user-select: text;
-      -ms-user-select: text;
-      font-size: 13px;
-      line-height: 1.3;
-    }
-
-    .tool-details {
-      margin-top: 3px;
-      padding-top: 3px;
-      border-top: 1px dashed #e0e0e0;
-      font-size: 12px;
-    }
-
-    .tool-name {
-      font-size: 12px;
-      font-weight: bold;
-      margin-bottom: 2px;
-      background: #f0f0f0;
-      padding: 2px 4px;
-      border-radius: 2px;
-      display: flex;
-      align-items: center;
-      gap: 3px;
-    }
-
-    .tool-input,
-    .tool-result {
-      margin-top: 2px;
-      padding: 3px 5px;
-      background: #f7f7f7;
-      border-radius: 2px;
-      font-family: monospace;
-      font-size: 12px;
-      overflow-x: auto;
-      white-space: pre;
-      line-height: 1.3;
-      user-select: text;
-      cursor: text;
-      -webkit-user-select: text;
-      -moz-user-select: text;
-      -ms-user-select: text;
-    }
-
-    .tool-result {
-      max-height: 300px;
-      overflow-y: auto;
-    }
-
-    .usage-info {
-      margin-top: 10px;
-      padding-top: 10px;
-      border-top: 1px dashed #e0e0e0;
-      font-size: 12px;
-      color: #666;
-    }
-
-    /* Custom styles for IRC-like experience */
-    .user .message-content {
-      border-left-color: #2196f3;
-    }
-
-    .agent .message-content {
-      border-left-color: #4caf50;
-    }
-
-    .tool .message-content {
-      border-left-color: #ff9800;
-    }
-
-    .error .message-content {
-      border-left-color: #f44336;
-    }
-
-    /* Make message type display bold but without the IRC-style markers */
-    .message-type {
-      font-weight: bold;
-    }
-
-    /* Commit message styling */
-    .message.commit {
-      background-color: #f0f7ff;
-      border-left: 4px solid #0366d6;
-    }
-
-    .commits-container {
-      margin-top: 10px;
-      padding: 5px;
-    }
-
-    .commits-header {
-      font-weight: bold;
-      margin-bottom: 5px;
-      color: #24292e;
-      display: flex;
-      justify-content: space-between;
-      align-items: center;
-    }
-
-    .commit-boxes-row {
-      display: flex;
-      flex-wrap: wrap;
-      gap: 8px;
-      margin-top: 8px;
-    }
-
-    .commit-box {
-      border: 1px solid #d1d5da;
-      border-radius: 4px;
-      overflow: hidden;
-      background-color: #ffffff;
-      box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
-      max-width: 100%;
-      display: flex;
-      flex-direction: column;
-    }
-
-    .commit-preview {
-      padding: 8px 12px;
-      font-family: monospace;
-      background-color: #f6f8fa;
-      border-bottom: 1px dashed #d1d5da;
-      display: flex;
-      align-items: center;
-      flex-wrap: wrap;
-      gap: 4px;
-    }
-
-    .commit-preview:hover {
-      background-color: #eef2f6;
-    }
-
-    .commit-hash {
-      color: #0366d6;
-      font-weight: bold;
-      cursor: pointer;
-      margin-right: 8px;
-      text-decoration: none;
-      position: relative;
-    }
-
-    .commit-hash:hover {
-      text-decoration: underline;
-    }
-
-    .commit-hash:hover::after {
-      content: "📋";
-      font-size: 10px;
-      position: absolute;
-      top: -8px;
-      right: -12px;
-      opacity: 0.7;
-    }
-
-    .branch-wrapper {
-      margin-right: 8px;
-      color: #555;
-    }
-
-    .commit-branch {
-      color: #28a745;
-      font-weight: 500;
-      cursor: pointer;
-      text-decoration: none;
-      position: relative;
-    }
-
-    .commit-branch:hover {
-      text-decoration: underline;
-    }
-
-    .commit-branch:hover::after {
-      content: "📋";
-      font-size: 10px;
-      position: absolute;
-      top: -8px;
-      right: -12px;
-      opacity: 0.7;
-    }
-
-    .commit-preview {
-      display: flex;
-      align-items: center;
-      flex-wrap: wrap;
-      gap: 4px;
-    }
-
-    .commit-details {
-      padding: 8px 12px;
-      max-height: 200px;
-      overflow-y: auto;
-    }
-
-    .commit-details pre {
-      margin: 0;
-      white-space: pre-wrap;
-      word-break: break-word;
-    }
-
-    .commit-details.is-hidden {
-      display: none;
-    }
-
-    .pushed-branch {
-      color: #28a745;
-      font-weight: 500;
-      margin-left: 6px;
-    }
-
-    .commit-diff-button {
-      padding: 3px 6px;
-      border: 1px solid #ccc;
-      border-radius: 3px;
-      background-color: #f7f7f7;
-      color: #24292e;
-      font-size: 11px;
-      cursor: pointer;
-      transition: all 0.2s ease;
-      margin-left: auto;
-    }
-
-    .commit-diff-button:hover {
-      background-color: #e7e7e7;
-      border-color: #aaa;
-    }
-
-    /* Tool call cards */
-    .tool-call-cards-container {
-      display: flex;
-      flex-direction: column;
-      gap: 8px;
-      margin-top: 8px;
-    }
-
-    /* Message type styles */
-
-    .user .message-icon {
-      background-color: #2196f3;
-    }
-
-    .agent .message-icon {
-      background-color: #4caf50;
-    }
-
-    .tool .message-icon {
-      background-color: #ff9800;
-    }
-
-    .error .message-icon {
-      background-color: #f44336;
-    }
-
-    .end-of-turn {
-      margin-bottom: 15px;
-    }
-
-    .end-of-turn::after {
-      content: "End of Turn";
-      position: absolute;
-      left: 15px;
-      bottom: -10px;
-      transform: translateX(-50%);
-      font-size: 10px;
-      color: #666;
-      background: #f0f0f0;
-      padding: 1px 4px;
-      border-radius: 3px;
-    }
-
-    .markdown-content {
-      box-sizing: border-box;
-      min-width: 200px;
-      margin: 0 auto;
-    }
-
-    .markdown-content p {
-      margin-block-start: 0.5em;
-      margin-block-end: 0.5em;
-    }
-  `;
-
-  constructor() {
-    super();
-  }
-
-  // See https://lit.dev/docs/components/lifecycle/
-  connectedCallback() {
-    super.connectedCallback();
-  }
-
-  // See https://lit.dev/docs/components/lifecycle/
-  disconnectedCallback() {
-    super.disconnectedCallback();
-  }
-
-  renderMarkdown(markdownContent: string): string {
-    try {
-      // Set markdown options for proper code block highlighting and safety
-      const markedOptions: MarkedOptions = {
-        gfm: true, // GitHub Flavored Markdown
-        breaks: true, // Convert newlines to <br>
-        async: false,
-        // DOMPurify is recommended for production, but not included in this implementation
-      };
-      return marked.parse(markdownContent, markedOptions) as string;
-    } catch (error) {
-      console.error("Error rendering markdown:", error);
-      // Fallback to plain text if markdown parsing fails
-      return markdownContent;
-    }
-  }
-
-  /**
-   * Format timestamp for display
-   */
-  formatTimestamp(
-    timestamp: string | number | Date | null | undefined,
-    defaultValue: string = "",
-  ): string {
-    if (!timestamp) return defaultValue;
-    try {
-      const date = new Date(timestamp);
-      if (isNaN(date.getTime())) return defaultValue;
-
-      // Format: Mar 13, 2025 09:53:25 AM
-      return date.toLocaleString("en-US", {
-        month: "short",
-        day: "numeric",
-        year: "numeric",
-        hour: "numeric",
-        minute: "2-digit",
-        second: "2-digit",
-        hour12: true,
-      });
-    } catch (e) {
-      return defaultValue;
-    }
-  }
-
-  formatNumber(
-    num: number | null | undefined,
-    defaultValue: string = "0",
-  ): string {
-    if (num === undefined || num === null) return defaultValue;
-    try {
-      return num.toLocaleString();
-    } catch (e) {
-      return String(num);
-    }
-  }
-  formatCurrency(
-    num: number | string | null | undefined,
-    defaultValue: string = "$0.00",
-    isMessageLevel: boolean = false,
-  ): string {
-    if (num === undefined || num === null) return defaultValue;
-    try {
-      // Use 4 decimal places for message-level costs, 2 for totals
-      const decimalPlaces = isMessageLevel ? 4 : 2;
-      return `$${parseFloat(String(num)).toFixed(decimalPlaces)}`;
-    } catch (e) {
-      return defaultValue;
-    }
-  }
-
-  showCommit(commitHash: string) {
-    this.dispatchEvent(
-      new CustomEvent("show-commit-diff", {
-        bubbles: true,
-        composed: true,
-        detail: { commitHash },
-      }),
-    );
-  }
-
-  copyToClipboard(text: string, event: Event) {
-    const element = event.currentTarget as HTMLElement;
-    const rect = element.getBoundingClientRect();
-
-    navigator.clipboard
-      .writeText(text)
-      .then(() => {
-        this.showFloatingMessage("Copied!", rect, "success");
-      })
-      .catch((err) => {
-        console.error("Failed to copy text: ", err);
-        this.showFloatingMessage("Failed to copy!", rect, "error");
-      });
-  }
-
-  showFloatingMessage(
-    message: string,
-    targetRect: DOMRect,
-    type: "success" | "error",
-  ) {
-    // Create floating message element
-    const floatingMsg = document.createElement("div");
-    floatingMsg.textContent = message;
-    floatingMsg.className = `floating-message ${type}`;
-
-    // Position it near the clicked element
-    // Position just above the element
-    const top = targetRect.top - 30;
-    const left = targetRect.left + targetRect.width / 2 - 40;
-
-    floatingMsg.style.position = "fixed";
-    floatingMsg.style.top = `${top}px`;
-    floatingMsg.style.left = `${left}px`;
-    floatingMsg.style.zIndex = "9999";
-
-    // Add to document body
-    document.body.appendChild(floatingMsg);
-
-    // Animate in
-    floatingMsg.style.opacity = "0";
-    floatingMsg.style.transform = "translateY(10px)";
-
-    setTimeout(() => {
-      floatingMsg.style.opacity = "1";
-      floatingMsg.style.transform = "translateY(0)";
-    }, 10);
-
-    // Remove after animation
-    setTimeout(() => {
-      floatingMsg.style.opacity = "0";
-      floatingMsg.style.transform = "translateY(-10px)";
-
-      setTimeout(() => {
-        document.body.removeChild(floatingMsg);
-      }, 300);
-    }, 1500);
-  }
-
-  render() {
-    return html`
-      <div
-        class="message ${this.message?.type} ${this.message?.end_of_turn
-          ? "end-of-turn"
-          : ""}"
-      >
-        ${this.previousMessage?.type != this.message?.type
-          ? html`<div class="message-icon">
-              ${this.message?.type.toUpperCase()[0]}
-            </div>`
-          : ""}
-        <div class="message-content">
-          <div class="message-header">
-            <span class="message-type">${this.message?.type}</span>
-            <span class="message-timestamp"
-              >${this.formatTimestamp(this.message?.timestamp)}
-              ${this.message?.elapsed
-                ? html`(${(this.message?.elapsed / 1e9).toFixed(2)}s)`
-                : ""}</span
-            >
-            ${this.message?.usage
-              ? html` <span class="message-usage">
-                  <span title="Input tokens"
-                    >In: ${this.message?.usage?.input_tokens}</span
-                  >
-                  ${this.message?.usage?.cache_read_input_tokens > 0
-                    ? html`<span title="Cache tokens"
-                        >[Cache:
-                        ${this.formatNumber(
-                          this.message?.usage?.cache_read_input_tokens,
-                        )}]</span
-                      >`
-                    : ""}
-                  <span title="Output tokens"
-                    >Out: ${this.message?.usage?.output_tokens}</span
-                  >
-                  <span title="Message cost"
-                    >(${this.formatCurrency(
-                      this.message?.usage?.cost_usd,
-                    )})</span
-                  >
-                </span>`
-              : ""}
-          </div>
-          <div class="message-text-container">
-            <div class="message-actions">
-              ${copyButton(this.message?.content)}
-            </div>
-            ${this.message?.content
-              ? html`
-                  <div class="message-text markdown-content">
-                    ${unsafeHTML(this.renderMarkdown(this.message?.content))}
-                  </div>
-                `
-              : ""}
-          </div>
-          <sketch-tool-calls
-            .toolCalls=${this.message?.tool_calls}
-            .open=${this.open}
-          ></sketch-tool-calls>
-          ${this.message?.commits
-            ? html`
-                <div class="commits-container">
-                  <div class="commits-header">
-                    ${this.message.commits.length} new
-                    commit${this.message.commits.length > 1 ? "s" : ""} detected
-                  </div>
-                  ${this.message.commits.map((commit) => {
-                    return html`
-                      <div class="commit-boxes-row">
-                        <div class="commit-box">
-                          <div class="commit-preview">
-                            <span
-                              class="commit-hash"
-                              title="Click to copy: ${commit.hash}"
-                              @click=${(e) =>
-                                this.copyToClipboard(
-                                  commit.hash.substring(0, 8),
-                                  e,
-                                )}
-                            >
-                              ${commit.hash.substring(0, 8)}
-                            </span>
-                            ${commit.pushed_branch
-                              ? html`
-                                  <span class="branch-wrapper">
-                                    (<span
-                                      class="commit-branch pushed-branch"
-                                      title="Click to copy: ${commit.pushed_branch}"
-                                      @click=${(e) =>
-                                        this.copyToClipboard(
-                                          commit.pushed_branch,
-                                          e,
-                                        )}
-                                      >${commit.pushed_branch}</span
-                                    >)
-                                  </span>
-                                `
-                              : ``}
-                            <span class="commit-subject"
-                              >${commit.subject}</span
-                            >
-                            <button
-                              class="commit-diff-button"
-                              @click=${() => this.showCommit(commit.hash)}
-                            >
-                              View Diff
-                            </button>
-                          </div>
-                          <div class="commit-details is-hidden">
-                            <pre>${commit.body}</pre>
-                          </div>
-                        </div>
-                      </div>
-                    `;
-                  })}
-                </div>
-              `
-            : ""}
-        </div>
-      </div>
-    `;
-  }
-}
-
-function copyButton(textToCopy: string) {
-  // Add click event listener to handle copying
-  const buttonClass = "copy-button";
-  const buttonContent = "Copy";
-  const successContent = "Copied!";
-  const failureContent = "Failed";
-
-  const ret = html`<button
-    class="${buttonClass}"
-    title="Copy to clipboard"
-    @click=${(e: Event) => {
-      e.stopPropagation();
-      const copyButton = e.currentTarget as HTMLButtonElement;
-      navigator.clipboard
-        .writeText(textToCopy)
-        .then(() => {
-          copyButton.textContent = successContent;
-          setTimeout(() => {
-            copyButton.textContent = buttonContent;
-          }, 2000);
-        })
-        .catch((err) => {
-          console.error("Failed to copy text: ", err);
-          copyButton.textContent = failureContent;
-          setTimeout(() => {
-            copyButton.textContent = buttonContent;
-          }, 2000);
-        });
-    }}
-  >
-    ${buttonContent}
-  </button>`;
-
-  return ret;
-}
-
-// Create global styles for floating messages
-const floatingMessageStyles = document.createElement("style");
-floatingMessageStyles.textContent = `
-  .floating-message {
-    background-color: rgba(0, 0, 0, 0.8);
-    color: white;
-    padding: 5px 10px;
-    border-radius: 4px;
-    font-size: 12px;
-    font-family: system-ui, sans-serif;
-    box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
-    pointer-events: none;
-    transition: opacity 0.3s ease, transform 0.3s ease;
-  }
-  
-  .floating-message.success {
-    background-color: rgba(40, 167, 69, 0.9);
-  }
-  
-  .floating-message.error {
-    background-color: rgba(220, 53, 69, 0.9);
-  }
-`;
-document.head.appendChild(floatingMessageStyles);
-
-declare global {
-  interface HTMLElementTagNameMap {
-    "sketch-timeline-message": SketchTimelineMessage;
-  }
-}
diff --git a/loop/webui/src/web-components/sketch-timeline.ts b/loop/webui/src/web-components/sketch-timeline.ts
deleted file mode 100644
index e2c8ee7..0000000
--- a/loop/webui/src/web-components/sketch-timeline.ts
+++ /dev/null
@@ -1,224 +0,0 @@
-import { css, html, LitElement } from "lit";
-import { PropertyValues } from "lit";
-import { repeat } from "lit/directives/repeat.js";
-import { customElement, property, state } from "lit/decorators.js";
-import { AgentMessage } from "../types";
-import "./sketch-timeline-message";
-
-@customElement("sketch-timeline")
-export class SketchTimeline extends LitElement {
-  @property({ attribute: false })
-  messages: AgentMessage[] = [];
-
-  // Track if we should scroll to the bottom
-  @state()
-  private scrollingState: "pinToLatest" | "floating" = "pinToLatest";
-
-  @property({ attribute: false })
-  scrollContainer: HTMLElement;
-
-  static styles = css`
-    /* Hide views initially to prevent flash of content */
-    .timeline-container .timeline,
-    .timeline-container .diff-view,
-    .timeline-container .chart-view,
-    .timeline-container .terminal-view {
-      visibility: hidden;
-    }
-
-    /* Will be set by JavaScript once we know which view to display */
-    .timeline-container.view-initialized .timeline,
-    .timeline-container.view-initialized .diff-view,
-    .timeline-container.view-initialized .chart-view,
-    .timeline-container.view-initialized .terminal-view {
-      visibility: visible;
-    }
-
-    .timeline-container {
-      width: 100%;
-      position: relative;
-    }
-
-    /* Timeline styles that should remain unchanged */
-    .timeline {
-      position: relative;
-      margin: 10px 0;
-      scroll-behavior: smooth;
-    }
-
-    .timeline::before {
-      content: "";
-      position: absolute;
-      top: 0;
-      bottom: 0;
-      left: 15px;
-      width: 2px;
-      background: #e0e0e0;
-      border-radius: 1px;
-    }
-
-    /* Hide the timeline vertical line when there are no messages */
-    .timeline.empty::before {
-      display: none;
-    }
-
-    #scroll-container {
-      overflow: auto;
-      padding-left: 1em;
-    }
-    #jump-to-latest {
-      display: none;
-      position: fixed;
-      bottom: 100px;
-      right: 0;
-      background: rgb(33, 150, 243);
-      color: white;
-      border-radius: 8px;
-      padding: 0.5em;
-      margin: 0.5em;
-      font-size: x-large;
-      opacity: 0.5;
-      cursor: pointer;
-    }
-    #jump-to-latest:hover {
-      opacity: 1;
-    }
-    #jump-to-latest.floating {
-      display: block;
-    }
-  `;
-
-  constructor() {
-    super();
-
-    // Binding methods
-    this._handleShowCommitDiff = this._handleShowCommitDiff.bind(this);
-    this._handleScroll = this._handleScroll.bind(this);
-  }
-
-  /**
-   * Scroll to the bottom of the timeline
-   */
-  private scrollToBottom(): void {
-    this.scrollContainer?.scrollTo({
-      top: this.scrollContainer?.scrollHeight,
-      behavior: "smooth",
-    });
-  }
-
-  /**
-   * Called after the component's properties have been updated
-   */
-  updated(changedProperties: PropertyValues): void {
-    // If messages have changed, scroll to bottom if needed
-    if (changedProperties.has("messages") && this.messages.length > 0) {
-      if (this.scrollingState == "pinToLatest") {
-        setTimeout(() => this.scrollToBottom(), 50);
-      }
-    }
-    if (changedProperties.has("scrollContainer")) {
-      this.scrollContainer?.addEventListener("scroll", this._handleScroll);
-    }
-  }
-
-  /**
-   * Handle showCommitDiff event
-   */
-  private _handleShowCommitDiff(event: CustomEvent) {
-    const { commitHash } = event.detail;
-    if (commitHash) {
-      // Bubble up the event to the app shell
-      const newEvent = new CustomEvent("show-commit-diff", {
-        detail: { commitHash },
-        bubbles: true,
-        composed: true,
-      });
-      this.dispatchEvent(newEvent);
-    }
-  }
-
-  private _handleScroll(event) {
-    const isAtBottom =
-      Math.abs(
-        this.scrollContainer.scrollHeight -
-          this.scrollContainer.clientHeight -
-          this.scrollContainer.scrollTop,
-      ) <= 1;
-    if (isAtBottom) {
-      this.scrollingState = "pinToLatest";
-    } else {
-      // TODO: does scroll direction matter here?
-      this.scrollingState = "floating";
-    }
-  }
-
-  // See https://lit.dev/docs/components/lifecycle/
-  connectedCallback() {
-    super.connectedCallback();
-
-    // Listen for showCommitDiff events from the renderer
-    document.addEventListener(
-      "showCommitDiff",
-      this._handleShowCommitDiff as EventListener,
-    );
-    this.scrollContainer?.addEventListener("scroll", this._handleScroll);
-  }
-
-  // See https://lit.dev/docs/components/lifecycle/
-  disconnectedCallback() {
-    super.disconnectedCallback();
-
-    // Remove event listeners
-    document.removeEventListener(
-      "showCommitDiff",
-      this._handleShowCommitDiff as EventListener,
-    );
-
-    this.scrollContainer?.removeEventListener("scroll", this._handleScroll);
-  }
-
-  // messageKey uniquely identifes a AgentMessage based on its ID and tool_calls, so
-  // that we only re-render <sketch-message> elements that we need to re-render.
-  messageKey(message: AgentMessage): string {
-    // If the message has tool calls, and any of the tool_calls get a response, we need to
-    // re-render that message.
-    const toolCallResponses = message.tool_calls
-      ?.filter((tc) => tc.result_message)
-      .map((tc) => tc.tool_call_id)
-      .join("-");
-    return `message-${message.idx}-${toolCallResponses}`;
-  }
-
-  render() {
-    return html`
-      <div id="scroll-container">
-        <div class="timeline-container">
-          ${repeat(this.messages, this.messageKey, (message, index) => {
-            let previousMessage: AgentMessage;
-            if (index > 0) {
-              previousMessage = this.messages[index - 1];
-            }
-            return html`<sketch-timeline-message
-              .message=${message}
-              .previousMessage=${previousMessage}
-              .open=${index == this.messages.length - 1}
-            ></sketch-timeline-message>`;
-          })}
-        </div>
-      </div>
-      <div
-        id="jump-to-latest"
-        class="${this.scrollingState}"
-        @click=${this.scrollToBottom}
-      >
-        ⇩
-      </div>
-    `;
-  }
-}
-
-declare global {
-  interface HTMLElementTagNameMap {
-    "sketch-timeline": SketchTimeline;
-  }
-}
diff --git a/loop/webui/src/web-components/sketch-tool-calls.ts b/loop/webui/src/web-components/sketch-tool-calls.ts
deleted file mode 100644
index 3f036c2..0000000
--- a/loop/webui/src/web-components/sketch-tool-calls.ts
+++ /dev/null
@@ -1,148 +0,0 @@
-import { css, html, LitElement } from "lit";
-import { customElement, property } from "lit/decorators.js";
-import { repeat } from "lit/directives/repeat.js";
-import { ToolCall } from "../types";
-import "./sketch-tool-card";
-
-@customElement("sketch-tool-calls")
-export class SketchToolCalls extends LitElement {
-  @property()
-  toolCalls: ToolCall[] = [];
-
-  @property()
-  open: boolean = false;
-
-  static styles = css`
-    /* Tool calls container styles */
-    .tool-calls-container {
-      /* Container for all tool calls */
-    }
-
-    /* Header for tool calls section */
-    .tool-calls-header {
-      /* Empty header - just small spacing */
-    }
-
-    /* Card container */
-    .tool-call-card {
-      display: flex;
-      flex-direction: column;
-      background-color: white;
-      overflow: hidden;
-      cursor: pointer;
-    }
-
-    /* Status indicators for tool calls */
-    .tool-call-status {
-      margin-right: 4px;
-      text-align: center;
-    }
-
-    .tool-call-status.spinner {
-      animation: spin 1s infinite linear;
-      display: inline-block;
-      width: 1em;
-    }
-
-    @keyframes spin {
-      0% {
-        transform: rotate(0deg);
-      }
-      100% {
-        transform: rotate(360deg);
-      }
-    }
-  `;
-
-  constructor() {
-    super();
-  }
-
-  connectedCallback() {
-    super.connectedCallback();
-  }
-
-  disconnectedCallback() {
-    super.disconnectedCallback();
-  }
-
-  cardForToolCall(toolCall: ToolCall, open: boolean) {
-    switch (toolCall.name) {
-      case "bash":
-        return html`<sketch-tool-card-bash
-          .open=${open}
-          .toolCall=${toolCall}
-        ></sketch-tool-card-bash>`;
-      case "codereview":
-        return html`<sketch-tool-card-codereview
-          .open=${open}
-          .toolCall=${toolCall}
-        ></sketch-tool-card-codereview>`;
-      case "done":
-        return html`<sketch-tool-card-done
-          .open=${open}
-          .toolCall=${toolCall}
-        ></sketch-tool-card-done>`;
-      case "patch":
-        return html`<sketch-tool-card-patch
-          .open=${open}
-          .toolCall=${toolCall}
-        ></sketch-tool-card-patch>`;
-      case "think":
-        return html`<sketch-tool-card-think
-          .open=${open}
-          .toolCall=${toolCall}
-        ></sketch-tool-card-think>`;
-      case "title":
-        return html`<sketch-tool-card-title
-          .open=${open}
-          .toolCall=${toolCall}
-        ></sketch-tool-card-title>`;
-    }
-    return html`<sketch-tool-card-generic
-      .open=${open}
-      .toolCall=${toolCall}
-    ></sketch-tool-card-generic>`;
-  }
-
-  // toolUseKey return value should change, if the toolCall gets a response.
-  toolUseKey(toolCall: ToolCall): string {
-    console.log(
-      "toolUseKey",
-      toolCall.tool_call_id,
-      toolCall.result_message?.idx,
-    );
-    if (!toolCall.result_message) {
-      return toolCall.tool_call_id;
-    }
-    return `${toolCall.tool_call_id}-${toolCall.result_message.idx}`;
-  }
-
-  render() {
-    return html`<div class="tool-calls-container">
-      <div class="tool-calls-header"></div>
-      <div class="tool-call-cards-container">
-        ${this.toolCalls
-          ? repeat(this.toolCalls, this.toolUseKey, (toolCall, idx) => {
-              let lastCall = false;
-              if (idx == this.toolCalls?.length - 1) {
-                lastCall = true;
-              }
-              return html`<div
-                id="${toolCall.tool_call_id}"
-                class="tool-call-card ${toolCall.name}"
-              >
-                ${this.cardForToolCall(toolCall, lastCall && this.open)}
-              </div>`;
-            })
-          : ""}
-      </div>
-    </div>`;
-  }
-}
-
-declare global {
-  interface HTMLElementTagNameMap {
-    "sketch-tool-calls": SketchToolCalls;
-  }
-}
diff --git a/loop/webui/src/web-components/sketch-tool-card.ts b/loop/webui/src/web-components/sketch-tool-card.ts
deleted file mode 100644
index dbb09ae..0000000
--- a/loop/webui/src/web-components/sketch-tool-card.ts
+++ /dev/null
@@ -1,630 +0,0 @@
-import { css, html, LitElement } from "lit";
-import { unsafeHTML } from "lit/directives/unsafe-html.js";
-import { customElement, property } from "lit/decorators.js";
-import { ToolCall } from "../types";
-import { marked, MarkedOptions } from "marked";
-
-function renderMarkdown(markdownContent: string): string {
-  try {
-    // Set markdown options for proper code block highlighting and safety
-    const markedOptions: MarkedOptions = {
-      gfm: true, // GitHub Flavored Markdown
-      breaks: true, // Convert newlines to <br>
-      async: false,
-      // DOMPurify is recommended for production, but not included in this implementation
-    };
-    return marked.parse(markdownContent, markedOptions) as string;
-  } catch (error) {
-    console.error("Error rendering markdown:", error);
-    // Fallback to plain text if markdown parsing fails
-    return markdownContent;
-  }
-}
-
-@customElement("sketch-tool-card")
-export class SketchToolCard extends LitElement {
-  @property()
-  toolCall: ToolCall;
-
-  @property()
-  open: boolean;
-
-  static styles = css`
-    .tool-call {
-      display: flex;
-      align-items: center;
-      gap: 8px;
-      white-space: nowrap;
-    }
-
-    .tool-call-status {
-      margin-right: 4px;
-      text-align: center;
-    }
-
-    .tool-call-status.spinner {
-      animation: spin 1s infinite linear;
-      display: inline-block;
-      width: 1em;
-    }
-
-    @keyframes spin {
-      0% {
-        transform: rotate(0deg);
-      }
-      100% {
-        transform: rotate(360deg);
-      }
-    }
-
-    .title {
-      font-style: italic;
-    }
-
-    .cancel-button {
-      background: rgb(76, 175, 80);
-      color: white;
-      border: none;
-      padding: 4px 10px;
-      border-radius: 4px;
-      cursor: pointer;
-      font-size: 12px;
-      margin: 5px;
-    }
-
-    .cancel-button:hover {
-      background: rgb(200, 35, 51) !important;
-    }
-
-    .codereview-OK {
-      color: green;
-    }
-
-    details {
-      border-radius: 4px;
-      padding: 0.25em;
-      margin: 0.25em;
-      display: flex;
-      flex-direction: column;
-      align-items: start;
-    }
-
-    details summary {
-      list-style: none;
-      &::before {
-        cursor: hand;
-        font-family: monospace;
-        content: "+";
-        color: white;
-        background-color: darkgray;
-        border-radius: 1em;
-        padding-left: 0.5em;
-        margin: 0.25em;
-        min-width: 1em;
-      }
-      [open] &::before {
-        content: "-";
-      }
-    }
-
-    details summary:hover {
-      list-style: none;
-      &::before {
-        background-color: gray;
-      }
-    }
-    summary {
-      display: flex;
-      flex-direction: row;
-      flex-wrap: nowrap;
-      justify-content: flex-start;
-      align-items: baseline;
-    }
-
-    summary .tool-name {
-      font-family: monospace;
-      color: white;
-      background: rgb(124 145 160);
-      border-radius: 4px;
-      padding: 0.25em;
-      margin: 0.25em;
-      white-space: pre;
-    }
-
-    .summary-text {
-      padding: 0.25em;
-      display: flex;
-      max-width: 50%;
-      overflow: hidden;
-      text-overflow: ellipsis;
-    }
-
-    details[open] .summary-text {
-      /*display: none;*/
-    }
-
-    .tool-error-message {
-      font-style: italic;
-      color: #aa0909;
-    }
-
-    .elapsed {
-      font-size: 10px;
-      color: #888;
-      font-style: italic;
-      margin-left: 3px;
-    }
-  `;
-
-  constructor() {
-    super();
-  }
-
-  connectedCallback() {
-    super.connectedCallback();
-  }
-
-  disconnectedCallback() {
-    super.disconnectedCallback();
-  }
-
-  _cancelToolCall = async (tool_call_id: string, button: HTMLButtonElement) => {
-    console.log("cancelToolCall", tool_call_id, button);
-    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) {
-        console.log("cancel", tool_call_id, response);
-        button.parentElement.removeChild(button);
-      } else {
-        button.innerText = "Cancel";
-        console.log(`error trying to cancel ${tool_call_id}: `, response);
-      }
-    } catch (e) {
-      console.error("cancel", tool_call_id, e);
-    }
-  };
-
-  render() {
-    const toolCallStatus = this.toolCall?.result_message
-      ? this.toolCall?.result_message.tool_error
-        ? html`❌
-            <span class="tool-error-message"
-              >${this.toolCall?.result_message.tool_result}</span
-            >`
-        : ""
-      : "⏳";
-
-    const cancelButton = this.toolCall?.result_message
-      ? ""
-      : html`<button
-          class="cancel-button"
-          title="Cancel this operation"
-          @click=${(e: Event) => {
-            e.stopPropagation();
-            const button = e.target as HTMLButtonElement;
-            this._cancelToolCall(this.toolCall?.tool_call_id, button);
-          }}
-        >
-          Cancel
-        </button>`;
-
-    const status = html`<span
-      class="tool-call-status ${this.toolCall?.result_message ? "" : "spinner"}"
-      >${toolCallStatus}</span
-    >`;
-
-    const elapsed = html`${this.toolCall?.result_message?.elapsed
-      ? html`<span class="elapsed"
-          >${(this.toolCall?.result_message?.elapsed / 1e9).toFixed(2)}s
-          elapsed</span
-        >`
-      : ""}`;
-
-    const ret = html`<div class="tool-call">
-      <details ?open=${this.open}>
-        <summary>
-          <span class="tool-name">${this.toolCall?.name}</span>
-          <span class="summary-text"><slot name="summary"></slot></span>
-          ${status} ${cancelButton} ${elapsed}
-        </summary>
-        <slot name="input"></slot>
-        <slot name="result"></slot>
-      </details>
-    </div> `;
-    if (true) {
-      return ret;
-    }
-  }
-}
-
-@customElement("sketch-tool-card-bash")
-export class SketchToolCardBash extends LitElement {
-  @property()
-  toolCall: ToolCall;
-
-  @property()
-  open: boolean;
-
-  static styles = css`
-    pre {
-      background: rgb(236, 236, 236);
-      color: black;
-      padding: 0.5em;
-      border-radius: 4px;
-    }
-    .summary-text {
-      overflow: hidden;
-      text-overflow: ellipsis;
-      font-family: monospace;
-    }
-    .input {
-      display: flex;
-    }
-    .input pre {
-      width: 100%;
-      margin-bottom: 0;
-      border-radius: 4px 4px 0 0;
-    }
-    .result pre {
-      margin-top: 0;
-      color: #555;
-      border-radius: 0 0 4px 4px;
-    }
-    .background-badge {
-      display: inline-block;
-      background-color: #6200ea;
-      color: white;
-      font-size: 10px;
-      font-weight: bold;
-      padding: 2px 6px;
-      border-radius: 10px;
-      margin-left: 8px;
-      vertical-align: middle;
-    }
-    .command-wrapper {
-      display: flex;
-      align-items: center;
-    }
-  `;
-
-  constructor() {
-    super();
-  }
-
-  connectedCallback() {
-    super.connectedCallback();
-  }
-
-  disconnectedCallback() {
-    super.disconnectedCallback();
-  }
-
-  render() {
-    const inputData = JSON.parse(this.toolCall?.input || "{}");
-    const isBackground = inputData?.background === true;
-    const backgroundIcon = isBackground ? "🔄 " : "";
-
-    return html`
-    <sketch-tool-card .open=${this.open} .toolCall=${this.toolCall}>
-    <span slot="summary" class="summary-text">
-      <div class="command-wrapper">
-        🖥️ ${backgroundIcon}${inputData?.command}
-      </div>
-    </span>
-    <div slot="input" class="input">
-      <pre>🖥️ ${backgroundIcon}${inputData?.command}</pre>
-    </div>
-    ${
-      this.toolCall?.result_message
-        ? html` ${this.toolCall?.result_message.tool_result
-            ? html`<div slot="result" class="result">
-                <pre class="tool-call-result">
-${this.toolCall?.result_message.tool_result}</pre
-                >
-              </div>`
-            : ""}`
-        : ""
-    }</div>
-    </sketch-tool-card>`;
-  }
-}
-
-@customElement("sketch-tool-card-codereview")
-export class SketchToolCardCodeReview extends LitElement {
-  @property()
-  toolCall: ToolCall;
-
-  @property()
-  open: boolean;
-
-  static styles = css``;
-
-  constructor() {
-    super();
-  }
-
-  connectedCallback() {
-    super.connectedCallback();
-  }
-
-  disconnectedCallback() {
-    super.disconnectedCallback();
-  }
-  render() {
-    return html` <sketch-tool-card
-      .open=${this.open}
-      .toolCall=${this.toolCall}
-    >
-      <span slot="summary" class="summary-text">
-        ${this.toolCall?.result_message?.tool_result == "OK" ? "✔️" : "⛔"}
-      </span>
-      <div slot="result">
-        <pre>${this.toolCall?.result_message?.tool_result}</pre>
-      </div>
-    </sketch-tool-card>`;
-  }
-}
-
-@customElement("sketch-tool-card-done")
-export class SketchToolCardDone extends LitElement {
-  @property()
-  toolCall: ToolCall;
-
-  @property()
-  open: boolean;
-
-  static styles = css``;
-
-  constructor() {
-    super();
-  }
-
-  connectedCallback() {
-    super.connectedCallback();
-  }
-
-  disconnectedCallback() {
-    super.disconnectedCallback();
-  }
-
-  render() {
-    const doneInput = JSON.parse(this.toolCall.input);
-    return html` <sketch-tool-card
-      .open=${this.open}
-      .toolCall=${this.toolCall}
-    >
-      <span slot="summary" class="summary-text"> </span>
-      <div slot="result">
-        ${Object.keys(doneInput.checklist_items).map((key) => {
-          const item = doneInput.checklist_items[key];
-          let statusIcon = "⛔";
-          if (item.status == "yes") {
-            statusIcon = "👍";
-          } else if (item.status == "not applicable") {
-            statusIcon = "🤷‍♂️";
-          }
-          return html`<div>
-            <span>${statusIcon}</span> ${key}:${item.status}
-          </div>`;
-        })}
-      </div>
-    </sketch-tool-card>`;
-  }
-}
-
-@customElement("sketch-tool-card-patch")
-export class SketchToolCardPatch extends LitElement {
-  @property()
-  toolCall: ToolCall;
-
-  @property()
-  open: boolean;
-
-  static styles = css`
-    .summary-text {
-      color: #555;
-      font-family: monospace;
-      overflow: hidden;
-      text-overflow: ellipsis;
-      white-space: nowrap;
-      border-radius: 3px;
-    }
-  `;
-
-  constructor() {
-    super();
-  }
-
-  connectedCallback() {
-    super.connectedCallback();
-  }
-
-  disconnectedCallback() {
-    super.disconnectedCallback();
-  }
-
-  render() {
-    const patchInput = JSON.parse(this.toolCall?.input);
-    return html` <sketch-tool-card
-      .open=${this.open}
-      .toolCall=${this.toolCall}
-    >
-      <span slot="summary" class="summary-text">
-        ${patchInput?.path}: ${patchInput.patches.length}
-        edit${patchInput.patches.length > 1 ? "s" : ""}
-      </span>
-      <div slot="input">
-        ${patchInput.patches.map((patch) => {
-          return html` Patch operation: <b>${patch.operation}</b>
-            <pre>${patch.newText}</pre>`;
-        })}
-      </div>
-      <div slot="result">
-        <pre>${this.toolCall?.result_message?.tool_result}</pre>
-      </div>
-    </sketch-tool-card>`;
-  }
-}
-
-@customElement("sketch-tool-card-think")
-export class SketchToolCardThink extends LitElement {
-  @property()
-  toolCall: ToolCall;
-
-  @property()
-  open: boolean;
-
-  static styles = css`
-    .thought-bubble {
-      overflow-x: auto;
-      margin-bottom: 3px;
-      font-family: monospace;
-      padding: 3px 5px;
-      background: rgb(236, 236, 236);
-      border-radius: 6px;
-      user-select: text;
-      cursor: text;
-      -webkit-user-select: text;
-      -moz-user-select: text;
-      -ms-user-select: text;
-      font-size: 13px;
-      line-height: 1.3;
-    }
-    .summary-text {
-      overflow: hidden;
-      text-overflow: ellipsis;
-      font-family: monospace;
-      max-width: 50%;
-    }
-  `;
-
-  constructor() {
-    super();
-  }
-
-  connectedCallback() {
-    super.connectedCallback();
-  }
-
-  disconnectedCallback() {
-    super.disconnectedCallback();
-  }
-
-  render() {
-    return html`
-      <sketch-tool-card .open=${this.open} .toolCall=${this.toolCall}>
-        <span slot="summary" class="summary-text"
-          >${JSON.parse(this.toolCall?.input)?.thoughts}</span
-        >
-        <div slot="input" class="thought-bubble">
-          <div class="markdown-content">
-            ${unsafeHTML(
-              renderMarkdown(JSON.parse(this.toolCall?.input)?.thoughts),
-            )}
-          </div>
-        </div>
-      </sketch-tool-card>
-    `;
-  }
-}
-
-@customElement("sketch-tool-card-title")
-export class SketchToolCardTitle extends LitElement {
-  @property()
-  toolCall: ToolCall;
-
-  @property()
-  open: boolean;
-
-  static styles = css`
-    .summary-text {
-      font-style: italic;
-    }
-  `;
-  constructor() {
-    super();
-  }
-
-  connectedCallback() {
-    super.connectedCallback();
-  }
-
-  disconnectedCallback() {
-    super.disconnectedCallback();
-  }
-
-  render() {
-    return html`
-      <span class="summary-text"
-        >I've set the title of this sketch to
-        <b>"${JSON.parse(this.toolCall?.input)?.title}"</b></span
-      >
-    `;
-  }
-}
-
-@customElement("sketch-tool-card-generic")
-export class SketchToolCardGeneric extends LitElement {
-  @property()
-  toolCall: ToolCall;
-
-  @property()
-  open: boolean;
-
-  constructor() {
-    super();
-  }
-
-  connectedCallback() {
-    super.connectedCallback();
-  }
-
-  disconnectedCallback() {
-    super.disconnectedCallback();
-  }
-
-  render() {
-    return html` <sketch-tool-card
-      .open=${this.open}
-      .toolCall=${this.toolCall}
-    >
-      <span slot="summary" class="summary-text">${this.toolCall?.input}</span>
-      <div slot="input">
-        Input:
-        <pre>${this.toolCall?.input}</pre>
-      </div>
-      <div slot="result">
-        Result:
-        ${this.toolCall?.result_message
-          ? html` ${this.toolCall?.result_message.tool_result
-              ? html`<pre>${this.toolCall?.result_message.tool_result}</pre>`
-              : ""}`
-          : ""}
-      </div>
-    </sketch-tool-card>`;
-  }
-}
-
-declare global {
-  interface HTMLElementTagNameMap {
-    "sketch-tool-card": SketchToolCard;
-    "sketch-tool-card-generic": SketchToolCardGeneric;
-    "sketch-tool-card-bash": SketchToolCardBash;
-    "sketch-tool-card-codereview": SketchToolCardCodeReview;
-    "sketch-tool-card-done": SketchToolCardDone;
-    "sketch-tool-card-patch": SketchToolCardPatch;
-    "sketch-tool-card-think": SketchToolCardThink;
-    "sketch-tool-card-title": SketchToolCardTitle;
-  }
-}
diff --git a/loop/webui/src/web-components/sketch-view-mode-select.test.ts b/loop/webui/src/web-components/sketch-view-mode-select.test.ts
deleted file mode 100644
index 6db790b..0000000
--- a/loop/webui/src/web-components/sketch-view-mode-select.test.ts
+++ /dev/null
@@ -1,119 +0,0 @@
-import { test, expect } from "@sand4rt/experimental-ct-web";
-import { SketchViewModeSelect } from "./sketch-view-mode-select";
-
-test("initializes with 'chat' as the default mode", async ({ mount }) => {
-  const component = await mount(SketchViewModeSelect, {});
-
-  // Check the activeMode property
-  const activeMode = await component.evaluate(
-    (el: SketchViewModeSelect) => el.activeMode,
-  );
-  expect(activeMode).toBe("chat");
-
-  // Check that the chat button has the active class
-  await expect(
-    component.locator("#showConversationButton.active"),
-  ).toBeVisible();
-});
-
-test("displays all four view mode buttons", async ({ mount }) => {
-  const component = await mount(SketchViewModeSelect, {});
-
-  // Count the number of buttons
-  const buttonCount = await component.locator(".emoji-button").count();
-  expect(buttonCount).toBe(4);
-
-  // Check that each button exists
-  await expect(component.locator("#showConversationButton")).toBeVisible();
-  await expect(component.locator("#showDiffButton")).toBeVisible();
-  await expect(component.locator("#showChartsButton")).toBeVisible();
-  await expect(component.locator("#showTerminalButton")).toBeVisible();
-
-  // Check the title attributes
-  expect(
-    await component.locator("#showConversationButton").getAttribute("title"),
-  ).toBe("Conversation View");
-  expect(await component.locator("#showDiffButton").getAttribute("title")).toBe(
-    "Diff View",
-  );
-  expect(
-    await component.locator("#showChartsButton").getAttribute("title"),
-  ).toBe("Charts View");
-  expect(
-    await component.locator("#showTerminalButton").getAttribute("title"),
-  ).toBe("Terminal View");
-});
-
-test("dispatches view-mode-select event when clicking a mode button", async ({
-  mount,
-}) => {
-  const component = await mount(SketchViewModeSelect, {});
-
-  // Set up promise to wait for the event
-  const eventPromise = component.evaluate((el) => {
-    return new Promise((resolve) => {
-      el.addEventListener(
-        "view-mode-select",
-        (event) => {
-          resolve((event as CustomEvent).detail);
-        },
-        { once: true },
-      );
-    });
-  });
-
-  // Click the diff button
-  await component.locator("#showDiffButton").click();
-
-  // Wait for the event and check its details
-  const detail: any = await eventPromise;
-  expect(detail.mode).toBe("diff");
-});
-
-test("updates the active mode when receiving update-active-mode event", async ({
-  mount,
-}) => {
-  const component = await mount(SketchViewModeSelect, {});
-
-  // Initially should be in chat mode
-  let activeMode = await component.evaluate(
-    (el: SketchViewModeSelect) => el.activeMode,
-  );
-  expect(activeMode).toBe("chat");
-
-  // Dispatch the update-active-mode event
-  await component.evaluate((el) => {
-    const updateEvent = new CustomEvent("update-active-mode", {
-      detail: { mode: "diff" },
-      bubbles: true,
-    });
-    el.dispatchEvent(updateEvent);
-  });
-
-  // Check that the mode was updated
-  activeMode = await component.evaluate(
-    (el: SketchViewModeSelect) => el.activeMode,
-  );
-  expect(activeMode).toBe("diff");
-
-  // Check that the diff button is now active
-  await expect(component.locator("#showDiffButton.active")).toBeVisible();
-});
-
-test("correctly marks the active button based on mode", async ({ mount }) => {
-  const component = await mount(SketchViewModeSelect, {
-    props: {
-      activeMode: "terminal",
-    },
-  });
-
-  // Terminal button should be active
-  await expect(component.locator("#showTerminalButton.active")).toBeVisible();
-
-  // Other buttons should not be active
-  await expect(
-    component.locator("#showConversationButton.active"),
-  ).not.toBeVisible();
-  await expect(component.locator("#showDiffButton.active")).not.toBeVisible();
-  await expect(component.locator("#showChartsButton.active")).not.toBeVisible();
-});
diff --git a/loop/webui/src/web-components/sketch-view-mode-select.ts b/loop/webui/src/web-components/sketch-view-mode-select.ts
deleted file mode 100644
index 52f8a4e..0000000
--- a/loop/webui/src/web-components/sketch-view-mode-select.ts
+++ /dev/null
@@ -1,146 +0,0 @@
-import { css, html, LitElement } from "lit";
-import { customElement, property } from "lit/decorators.js";
-import "./sketch-container-status";
-
-@customElement("sketch-view-mode-select")
-export class SketchViewModeSelect extends LitElement {
-  // Current active mode
-  @property()
-  activeMode: "chat" | "diff" | "charts" | "terminal" = "chat";
-  // Header bar: view mode buttons
-
-  static styles = css`
-    /* View Mode Button Styles */
-    .view-mode-buttons {
-      display: flex;
-      gap: 8px;
-      margin-right: 10px;
-    }
-
-    .emoji-button {
-      font-size: 18px;
-      width: 32px;
-      height: 32px;
-      display: flex;
-      align-items: center;
-      justify-content: center;
-      background: white;
-      border: 1px solid #ddd;
-      border-radius: 4px;
-      cursor: pointer;
-      transition: all 0.2s ease;
-      padding: 0;
-      line-height: 1;
-    }
-
-    .emoji-button:hover {
-      background-color: #f0f0f0;
-      transform: translateY(-2px);
-      box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
-    }
-
-    .emoji-button.active {
-      background-color: #e6f7ff;
-      border-color: #1890ff;
-      color: #1890ff;
-    }
-  `;
-
-  constructor() {
-    super();
-
-    // Binding methods
-    this._handleViewModeClick = this._handleViewModeClick.bind(this);
-    this._handleUpdateActiveMode = this._handleUpdateActiveMode.bind(this);
-  }
-
-  // See https://lit.dev/docs/components/lifecycle/
-  connectedCallback() {
-    super.connectedCallback();
-
-    // Listen for update-active-mode events
-    this.addEventListener(
-      "update-active-mode",
-      this._handleUpdateActiveMode as EventListener,
-    );
-  }
-
-  /**
-   * Handle view mode button clicks
-   */
-  private _handleViewModeClick(mode: "chat" | "diff" | "charts" | "terminal") {
-    // Dispatch a custom event to notify the app shell to change the view
-    const event = new CustomEvent("view-mode-select", {
-      detail: { mode },
-      bubbles: true,
-      composed: true,
-    });
-    this.dispatchEvent(event);
-  }
-
-  /**
-   * Handle updates to the active mode
-   */
-  private _handleUpdateActiveMode(event: CustomEvent) {
-    const { mode } = event.detail;
-    if (mode) {
-      this.activeMode = mode;
-    }
-  }
-
-  // See https://lit.dev/docs/components/lifecycle/
-  disconnectedCallback() {
-    super.disconnectedCallback();
-
-    // Remove event listeners
-    this.removeEventListener(
-      "update-active-mode",
-      this._handleUpdateActiveMode as EventListener,
-    );
-  }
-
-  render() {
-    return html`
-      <div class="view-mode-buttons">
-        <button
-          id="showConversationButton"
-          class="emoji-button ${this.activeMode === "chat" ? "active" : ""}"
-          title="Conversation View"
-          @click=${() => this._handleViewModeClick("chat")}
-        >
-          💬
-        </button>
-        <button
-          id="showDiffButton"
-          class="emoji-button ${this.activeMode === "diff" ? "active" : ""}"
-          title="Diff View"
-          @click=${() => this._handleViewModeClick("diff")}
-        >
-          ±
-        </button>
-        <button
-          id="showChartsButton"
-          class="emoji-button ${this.activeMode === "charts" ? "active" : ""}"
-          title="Charts View"
-          @click=${() => this._handleViewModeClick("charts")}
-        >
-          📈
-        </button>
-        <button
-          id="showTerminalButton"
-          class="emoji-button ${this.activeMode === "terminal" ? "active" : ""}"
-          title="Terminal View"
-          @click=${() => this._handleViewModeClick("terminal")}
-        >
-          💻
-        </button>
-      </div>
-    `;
-  }
-}
-
-declare global {
-  interface HTMLElementTagNameMap {
-    "sketch-view-mode-select": SketchViewModeSelect;
-  }
-}
diff --git a/loop/webui/src/web-components/vega-embed.ts b/loop/webui/src/web-components/vega-embed.ts
deleted file mode 100644
index 04f0087..0000000
--- a/loop/webui/src/web-components/vega-embed.ts
+++ /dev/null
@@ -1,86 +0,0 @@
-import { css, html, LitElement } from "lit";
-import { customElement, property, query } from "lit/decorators.js";
-import vegaEmbed from "vega-embed";
-import { VisualizationSpec } from "vega-embed";
-
-/**
- * A web component wrapper for vega-embed.
- * Renders Vega and Vega-Lite visualizations.
- *
- * Usage:
- * <vega-embed .spec="${yourVegaLiteSpec}"></vega-embed>
- */
-@customElement("vega-embed")
-export class VegaEmbed extends LitElement {
-  /**
-   * The Vega or Vega-Lite specification to render
-   */
-  @property({ type: Object })
-  spec?: VisualizationSpec;
-
-  static styles = css`
-    :host {
-      display: block;
-      width: 100%;
-      height: 100%;
-    }
-
-    #vega-container {
-      width: 100%;
-      height: 100%;
-      min-height: 200px;
-    }
-  `;
-
-  @query("#vega-container")
-  protected container?: HTMLElement;
-
-  protected firstUpdated() {
-    this.renderVegaVisualization();
-  }
-
-  protected updated() {
-    this.renderVegaVisualization();
-  }
-
-  /**
-   * Renders the Vega/Vega-Lite visualization using vega-embed
-   */
-  private async renderVegaVisualization() {
-    if (!this.spec) {
-      return;
-    }
-
-    if (!this.container) {
-      return;
-    }
-
-    try {
-      // Clear previous visualization if any
-      this.container.innerHTML = "";
-
-      // Render new visualization
-      await vegaEmbed(this.container, this.spec, {
-        actions: true,
-        renderer: "svg",
-      });
-    } catch (error) {
-      console.error("Error rendering Vega visualization:", error);
-      this.container.innerHTML = `<div style="color: red; padding: 10px;">
-        Error rendering visualization: ${
-          error instanceof Error ? error.message : String(error)
-        }
-      </div>`;
-    }
-  }
-
-  render() {
-    return html`<div id="vega-container"></div> `;
-  }
-}
-
-declare global {
-  interface HTMLElementTagNameMap {
-    "vega-embed": VegaEmbed;
-  }
-}
diff --git a/loop/webui/tsconfig.json b/loop/webui/tsconfig.json
deleted file mode 100644
index 8b79a51..0000000
--- a/loop/webui/tsconfig.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
-  "compilerOptions": {
-    "target": "ES2020",
-    "module": "ESNext",
-    "moduleResolution": "node",
-    "esModuleInterop": true,
-    "experimentalDecorators": true,
-    "strict": false,
-    "sourceMap": true,
-    "outDir": "./dist",
-    "declaration": true,
-    "lib": ["DOM", "ES2020"],
-    "skipLibCheck": true,
-    "noImplicitAny": false
-  },
-  "include": ["src/**/*", "web-dev-server.config.mjs"],
-  "exclude": ["node_modules", "dist"]
-}
diff --git a/loop/webui/vite.config.mts b/loop/webui/vite.config.mts
deleted file mode 100644
index 74508a5..0000000
--- a/loop/webui/vite.config.mts
+++ /dev/null
@@ -1,15 +0,0 @@
-import { dirname, resolve } from "node:path";
-import { fileURLToPath } from "node:url";
-import { hmrPlugin, presets } from "vite-plugin-web-components-hmr";
-import { defineConfig } from "vite";
-
-const __dirname = dirname(fileURLToPath(import.meta.url));
-
-export default defineConfig({
-  plugins: [
-    hmrPlugin({
-      include: ["./src/**/*.ts"],
-      presets: [presets.lit],
-    }),
-  ],
-});
diff --git a/loop/webui/web-test-runner.config.mjs b/loop/webui/web-test-runner.config.mjs
deleted file mode 100644
index d5f34a0..0000000
--- a/loop/webui/web-test-runner.config.mjs
+++ /dev/null
@@ -1,27 +0,0 @@
-import { puppeteerLauncher } from "@web/test-runner-puppeteer";
-
-const filteredLogs = ["Running in dev mode", "Lit is in dev mode"];
-
-export default /** @type {import("@web/test-runner").TestRunnerConfig} */ ({
-  /** Test files to run */
-  files: "dist/**/*.test.js",
-  browsers: [puppeteerLauncher({ concurrency: 1 })],
-
-  /** Resolve bare module imports */
-  nodeResolve: {
-    exportConditions: ["browser", "development"],
-  },
-
-  /** Filter out lit dev mode logs */
-  filterBrowserLogs(log) {
-    for (const arg of log.args) {
-      if (
-        typeof arg === "string" &&
-        filteredLogs.some((l) => arg.includes(l))
-      ) {
-        return false;
-      }
-    }
-    return true;
-  },
-});