loop/webui: swtich to web components impl (#1)
* loop/webui: swtich to web components impl
This change reorganizes the original vibe-coded
frontend code into a structure that's much
easier for a human to read and reason about,
while retaining the user-visible functionality
of its vibe-coded predecessor. Perhaps most
importantly, this change makes the code testable.
Some other notable details:
This does not use any of the popular large web
frameworks, but instead follows more of an
"a la carte" approach: leverage features
that already exist in modern web browsers,
like custom elements and shadow DOM.
Templating and basic component lifecycle
management are provided by lit.
State management is nothing fancy. It
doesn't use any library or framework, just
a basic "Events up, properties down"
approach.
* fix bad esbuild.go merge
* loop/webui: don't bundle src/web-components/demo
* loop/webui: don't 'npm ci' dev deps in the container
* rebase to main, undo README.md changes, add webuil.Build() call to LaunchContainer()
diff --git a/loop/server/loophttp.go b/loop/server/loophttp.go
index 0fd7ee1..6edae0c 100644
--- a/loop/server/loophttp.go
+++ b/loop/server/loophttp.go
@@ -379,8 +379,8 @@
})
s.mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
- // Serve the timeline.html file directly from the embedded filesystem
- data, err := fs.ReadFile(webBundle, "timeline.html")
+ // Serve the sketch-app-shell.html file directly from the embedded filesystem
+ data, err := fs.ReadFile(webBundle, "sketch-app-shell.html")
if err != nil {
http.Error(w, "File not found", http.StatusNotFound)
return
diff --git a/loop/webui/.gitignore b/loop/webui/.gitignore
new file mode 100644
index 0000000..a60030e
--- /dev/null
+++ b/loop/webui/.gitignore
@@ -0,0 +1,2 @@
+dist/
+coverage/
diff --git a/loop/webui/Makefile b/loop/webui/Makefile
index 51b4bb7..662cfe7 100644
--- a/loop/webui/Makefile
+++ b/loop/webui/Makefile
@@ -1,4 +1,4 @@
-all: install check build-tailwind
+all: install check
install:
npm ci
@@ -8,12 +8,5 @@
check:
npx tsc --noEmit
-build-tailwind:
- npx postcss ./src/input.css -o ./src/tailwind.css
-
-watch-tailwind:
- npx postcss -i ./src/input.css -o ./src/tailwind.css --watch
-
clean:
rm -rf node_modules
- -rm -f ./src/tailwind.css
diff --git a/loop/webui/esbuild.go b/loop/webui/esbuild.go
index ca42afa..d4af636 100644
--- a/loop/webui/esbuild.go
+++ b/loop/webui/esbuild.go
@@ -21,7 +21,7 @@
esbuildcli "github.com/evanw/esbuild/pkg/cli"
)
-//go:embed package.json package-lock.json src tsconfig.json postcss.config.js tailwind.config.js
+//go:embed package.json package-lock.json src tsconfig.json
var embedded embed.FS
func embeddedHash() (string, error) {
@@ -153,20 +153,18 @@
return nil, err
}
- // Do the build.
- cmd := exec.Command("npm", "ci")
+ // 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)
}
- cmd = exec.Command("npx", "postcss", filepath.Join(buildDir, "./src/input.css"), "-o", filepath.Join(tmpHashDir, "tailwind.css"))
- cmd.Dir = buildDir
- if out, err := cmd.CombinedOutput(); err != nil {
- return nil, fmt.Errorf("npm postcss: %s: %v", out, err)
- }
- bundleTs := []string{"src/timeline.ts"}
+ bundleTs := []string{"src/web-components/sketch-app-shell.ts"}
for _, tsName := range bundleTs {
- if err := esbuildBundle(tmpHashDir, filepath.Join(buildDir, tsName)); err != nil {
+ if err := esbuildBundle(tmpHashDir, filepath.Join(buildDir, tsName), ""); err != nil {
return nil, fmt.Errorf("esbuild: %s: %w", tsName, err)
}
}
@@ -174,6 +172,9 @@
// 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") {
@@ -218,8 +219,8 @@
return zip.NewReader(bytes.NewReader(buf.Bytes()), int64(buf.Len()))
}
-func esbuildBundle(outDir, src string) error {
- ret := esbuildcli.Run([]string{
+func esbuildBundle(outDir, src, metafilePath string) error {
+ args := []string{
src,
"--bundle",
"--sourcemap",
@@ -227,9 +228,102 @@
// 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, 0755); 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/package-lock.json b/loop/webui/package-lock.json
index 27de4c5..53b806f 100644
--- a/loop/webui/package-lock.json
+++ b/loop/webui/package-lock.json
@@ -12,39 +12,337 @@
"@xterm/addon-fit": "^0.10.0",
"@xterm/xterm": "^5.5.0",
"diff2html": "3.4.51",
- "lit-html": "^3.2.1",
+ "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": {
+ "@open-wc/dev-server-hmr": "^0.1.2-next.0",
+ "@open-wc/testing": "^4.0.0",
"@types/marked": "^5.0.2",
+ "@types/mocha": "^10.0.7",
"@types/node": "^22.13.14",
+ "@web/dev-server": "^0.4.6",
+ "@web/test-runner": "^0.18.2",
+ "@web/test-runner-puppeteer": "^0.18.0",
"autoprefixer": "^10.4.21",
"esbuild": "^0.25.1",
- "postcss": "^8.5.3",
- "postcss-cli": "^11.0.1",
- "tailwindcss": "^3.4.1",
"typescript": "^5.8.2"
}
},
- "node_modules/@alloc/quick-lru": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
- "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
+ "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,
+ "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,
"engines": {
- "node": ">=10"
+ "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,
+ "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": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "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,
+ "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,
+ "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,
+ "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/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,
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "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,
+ "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,
+ "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,
+ "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,
+ "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,
+ "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,
+ "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,
+ "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,
+ "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,
+ "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,
+ "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,
+ "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,
+ "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,
+ "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,
+ "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.1",
- "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.1.tgz",
- "integrity": "sha512-kfYGy8IdzTGy+z0vFGvExZtxkFlA4zAxgKEahG9KE1ScBjpQnFsNOX8KTU5ojNru5ed5CVoJYXFtoxaq5nFbjQ==",
+ "version": "0.25.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.2.tgz",
+ "integrity": "sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag==",
"cpu": [
"ppc64"
],
@@ -59,9 +357,9 @@
}
},
"node_modules/@esbuild/android-arm": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.1.tgz",
- "integrity": "sha512-dp+MshLYux6j/JjdqVLnMglQlFu+MuVeNrmT5nk6q07wNhCdSnB7QZj+7G8VMUGh1q+vj2Bq8kRsuyA00I/k+Q==",
+ "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"
],
@@ -76,9 +374,9 @@
}
},
"node_modules/@esbuild/android-arm64": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.1.tgz",
- "integrity": "sha512-50tM0zCJW5kGqgG7fQ7IHvQOcAn9TKiVRuQ/lN0xR+T2lzEFvAi1ZcS8DiksFcEpf1t/GYOeOfCAgDHFpkiSmA==",
+ "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"
],
@@ -93,9 +391,9 @@
}
},
"node_modules/@esbuild/android-x64": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.1.tgz",
- "integrity": "sha512-GCj6WfUtNldqUzYkN/ITtlhwQqGWu9S45vUXs7EIYf+7rCiiqH9bCloatO9VhxsL0Pji+PF4Lz2XXCES+Q8hDw==",
+ "version": "0.25.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.2.tgz",
+ "integrity": "sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg==",
"cpu": [
"x64"
],
@@ -110,9 +408,9 @@
}
},
"node_modules/@esbuild/darwin-arm64": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.1.tgz",
- "integrity": "sha512-5hEZKPf+nQjYoSr/elb62U19/l1mZDdqidGfmFutVUjjUZrOazAtwK+Kr+3y0C/oeJfLlxo9fXb1w7L+P7E4FQ==",
+ "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"
],
@@ -127,9 +425,9 @@
}
},
"node_modules/@esbuild/darwin-x64": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.1.tgz",
- "integrity": "sha512-hxVnwL2Dqs3fM1IWq8Iezh0cX7ZGdVhbTfnOy5uURtao5OIVCEyj9xIzemDi7sRvKsuSdtCAhMKarxqtlyVyfA==",
+ "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"
],
@@ -144,9 +442,9 @@
}
},
"node_modules/@esbuild/freebsd-arm64": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.1.tgz",
- "integrity": "sha512-1MrCZs0fZa2g8E+FUo2ipw6jw5qqQiH+tERoS5fAfKnRx6NXH31tXBKI3VpmLijLH6yriMZsxJtaXUyFt/8Y4A==",
+ "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"
],
@@ -161,9 +459,9 @@
}
},
"node_modules/@esbuild/freebsd-x64": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.1.tgz",
- "integrity": "sha512-0IZWLiTyz7nm0xuIs0q1Y3QWJC52R8aSXxe40VUxm6BB1RNmkODtW6LHvWRrGiICulcX7ZvyH6h5fqdLu4gkww==",
+ "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"
],
@@ -178,9 +476,9 @@
}
},
"node_modules/@esbuild/linux-arm": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.1.tgz",
- "integrity": "sha512-NdKOhS4u7JhDKw9G3cY6sWqFcnLITn6SqivVArbzIaf3cemShqfLGHYMx8Xlm/lBit3/5d7kXvriTUGa5YViuQ==",
+ "version": "0.25.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.2.tgz",
+ "integrity": "sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g==",
"cpu": [
"arm"
],
@@ -195,9 +493,9 @@
}
},
"node_modules/@esbuild/linux-arm64": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.1.tgz",
- "integrity": "sha512-jaN3dHi0/DDPelk0nLcXRm1q7DNJpjXy7yWaWvbfkPvI+7XNSc/lDOnCLN7gzsyzgu6qSAmgSvP9oXAhP973uQ==",
+ "version": "0.25.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.2.tgz",
+ "integrity": "sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g==",
"cpu": [
"arm64"
],
@@ -212,9 +510,9 @@
}
},
"node_modules/@esbuild/linux-ia32": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.1.tgz",
- "integrity": "sha512-OJykPaF4v8JidKNGz8c/q1lBO44sQNUQtq1KktJXdBLn1hPod5rE/Hko5ugKKZd+D2+o1a9MFGUEIUwO2YfgkQ==",
+ "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"
],
@@ -229,9 +527,9 @@
}
},
"node_modules/@esbuild/linux-loong64": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.1.tgz",
- "integrity": "sha512-nGfornQj4dzcq5Vp835oM/o21UMlXzn79KobKlcs3Wz9smwiifknLy4xDCLUU0BWp7b/houtdrgUz7nOGnfIYg==",
+ "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"
],
@@ -246,9 +544,9 @@
}
},
"node_modules/@esbuild/linux-mips64el": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.1.tgz",
- "integrity": "sha512-1osBbPEFYwIE5IVB/0g2X6i1qInZa1aIoj1TdL4AaAb55xIIgbg8Doq6a5BzYWgr+tEcDzYH67XVnTmUzL+nXg==",
+ "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"
],
@@ -263,9 +561,9 @@
}
},
"node_modules/@esbuild/linux-ppc64": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.1.tgz",
- "integrity": "sha512-/6VBJOwUf3TdTvJZ82qF3tbLuWsscd7/1w+D9LH0W/SqUgM5/JJD0lrJ1fVIfZsqB6RFmLCe0Xz3fmZc3WtyVg==",
+ "version": "0.25.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.2.tgz",
+ "integrity": "sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g==",
"cpu": [
"ppc64"
],
@@ -280,9 +578,9 @@
}
},
"node_modules/@esbuild/linux-riscv64": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.1.tgz",
- "integrity": "sha512-nSut/Mx5gnilhcq2yIMLMe3Wl4FK5wx/o0QuuCLMtmJn+WeWYoEGDN1ipcN72g1WHsnIbxGXd4i/MF0gTcuAjQ==",
+ "version": "0.25.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.2.tgz",
+ "integrity": "sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw==",
"cpu": [
"riscv64"
],
@@ -297,9 +595,9 @@
}
},
"node_modules/@esbuild/linux-s390x": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.1.tgz",
- "integrity": "sha512-cEECeLlJNfT8kZHqLarDBQso9a27o2Zd2AQ8USAEoGtejOrCYHNtKP8XQhMDJMtthdF4GBmjR2au3x1udADQQQ==",
+ "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"
],
@@ -314,9 +612,9 @@
}
},
"node_modules/@esbuild/linux-x64": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.1.tgz",
- "integrity": "sha512-xbfUhu/gnvSEg+EGovRc+kjBAkrvtk38RlerAzQxvMzlB4fXpCFCeUAYzJvrnhFtdeyVCDANSjJvOvGYoeKzFA==",
+ "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"
],
@@ -331,9 +629,9 @@
}
},
"node_modules/@esbuild/netbsd-arm64": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.1.tgz",
- "integrity": "sha512-O96poM2XGhLtpTh+s4+nP7YCCAfb4tJNRVZHfIE7dgmax+yMP2WgMd2OecBuaATHKTHsLWHQeuaxMRnCsH8+5g==",
+ "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"
],
@@ -348,9 +646,9 @@
}
},
"node_modules/@esbuild/netbsd-x64": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.1.tgz",
- "integrity": "sha512-X53z6uXip6KFXBQ+Krbx25XHV/NCbzryM6ehOAeAil7X7oa4XIq+394PWGnwaSQ2WRA0KI6PUO6hTO5zeF5ijA==",
+ "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"
],
@@ -365,9 +663,9 @@
}
},
"node_modules/@esbuild/openbsd-arm64": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.1.tgz",
- "integrity": "sha512-Na9T3szbXezdzM/Kfs3GcRQNjHzM6GzFBeU1/6IV/npKP5ORtp9zbQjvkDJ47s6BCgaAZnnnu/cY1x342+MvZg==",
+ "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"
],
@@ -382,9 +680,9 @@
}
},
"node_modules/@esbuild/openbsd-x64": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.1.tgz",
- "integrity": "sha512-T3H78X2h1tszfRSf+txbt5aOp/e7TAz3ptVKu9Oyir3IAOFPGV6O9c2naym5TOriy1l0nNf6a4X5UXRZSGX/dw==",
+ "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"
],
@@ -399,9 +697,9 @@
}
},
"node_modules/@esbuild/sunos-x64": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.1.tgz",
- "integrity": "sha512-2H3RUvcmULO7dIE5EWJH8eubZAI4xw54H1ilJnRNZdeo8dTADEZ21w6J22XBkXqGJbe0+wnNJtw3UXRoLJnFEg==",
+ "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"
],
@@ -416,9 +714,9 @@
}
},
"node_modules/@esbuild/win32-arm64": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.1.tgz",
- "integrity": "sha512-GE7XvrdOzrb+yVKB9KsRMq+7a2U/K5Cf/8grVFRAGJmfADr/e/ODQ134RK2/eeHqYV5eQRFxb1hY7Nr15fv1NQ==",
+ "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"
],
@@ -433,9 +731,9 @@
}
},
"node_modules/@esbuild/win32-ia32": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.1.tgz",
- "integrity": "sha512-uOxSJCIcavSiT6UnBhBzE8wy3n0hOkJsBOzy7HDAuTDE++1DJMRRVCPGisULScHL+a/ZwdXPpXD3IyFKjA7K8A==",
+ "version": "0.25.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.2.tgz",
+ "integrity": "sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg==",
"cpu": [
"ia32"
],
@@ -450,9 +748,9 @@
}
},
"node_modules/@esbuild/win32-x64": {
- "version": "0.25.1",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.1.tgz",
- "integrity": "sha512-Y1EQdcfwMSeQN/ujR5VayLOJ1BHaK+ssyk0AEzPjC+t1lITgsnccPqFjb6V+LsTp/9Iov4ysfjxLaGJ9RPtkVg==",
+ "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"
],
@@ -466,107 +764,27 @@
"node": ">=18"
}
},
- "node_modules/@isaacs/cliui": {
- "version": "8.0.2",
- "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
- "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
+ "node_modules/@esm-bundle/chai": {
+ "version": "4.3.4-fix.0",
+ "resolved": "https://registry.npmjs.org/@esm-bundle/chai/-/chai-4.3.4-fix.0.tgz",
+ "integrity": "sha512-26SKdM4uvDWlY8/OOOxSB1AqQWeBosCX3wRYUZO7enTAj03CtVxIiCimYVG2WpULcyV51qapK4qTovwkUr5Mlw==",
"dev": true,
"dependencies": {
- "string-width": "^5.1.2",
- "string-width-cjs": "npm:string-width@^4.2.0",
- "strip-ansi": "^7.0.1",
- "strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
- "wrap-ansi": "^8.1.0",
- "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
- },
- "engines": {
- "node": ">=12"
+ "@types/chai": "^4.2.12"
}
},
- "node_modules/@isaacs/cliui/node_modules/ansi-regex": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
- "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
- "dev": true,
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-regex?sponsor=1"
- }
- },
- "node_modules/@isaacs/cliui/node_modules/ansi-styles": {
- "version": "6.2.1",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
- "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
- "dev": true,
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
- }
- },
- "node_modules/@isaacs/cliui/node_modules/emoji-regex": {
- "version": "9.2.2",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
- "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
+ "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/@isaacs/cliui/node_modules/string-width": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
- "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
- "dev": true,
- "dependencies": {
- "eastasianwidth": "^0.2.0",
- "emoji-regex": "^9.2.2",
- "strip-ansi": "^7.0.1"
- },
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/@isaacs/cliui/node_modules/strip-ansi": {
- "version": "7.1.0",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
- "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
- "dev": true,
- "dependencies": {
- "ansi-regex": "^6.0.1"
- },
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/strip-ansi?sponsor=1"
- }
- },
- "node_modules/@isaacs/cliui/node_modules/wrap-ansi": {
- "version": "8.1.0",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
- "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
- "dev": true,
- "dependencies": {
- "ansi-styles": "^6.1.0",
- "string-width": "^5.0.1",
- "strip-ansi": "^7.0.1"
- },
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
- }
- },
"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",
@@ -581,6 +799,7 @@
"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"
}
@@ -590,6 +809,7 @@
"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"
}
@@ -598,23 +818,41 @@
"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
+ "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"
@@ -628,6 +866,7 @@
"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"
}
@@ -637,6 +876,7 @@
"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"
@@ -645,20 +885,423 @@
"node": ">= 8"
}
},
- "node_modules/@pkgjs/parseargs": {
- "version": "0.11.0",
- "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
- "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
+ "node_modules/@open-wc/dedupe-mixin": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/@open-wc/dedupe-mixin/-/dedupe-mixin-1.4.0.tgz",
+ "integrity": "sha512-Sj7gKl1TLcDbF7B6KUhtvr+1UCxdhMbNY5KxdU5IfMFWqL8oy1ZeAcCANjoB1TL0AJTcPmcCFsCbHf8X2jGDUA==",
+ "dev": true
+ },
+ "node_modules/@open-wc/dev-server-hmr": {
+ "version": "0.1.2-next.0",
+ "resolved": "https://registry.npmjs.org/@open-wc/dev-server-hmr/-/dev-server-hmr-0.1.2-next.0.tgz",
+ "integrity": "sha512-XgazcRuYE0J17X1LgZ/BumwMf81p7qR1h3ncc3ljA3PDqXIBSOYnt1SSoR/IqlJvDmbTopONYQ/w+qjEOtBrAg==",
"dev": true,
- "optional": true,
- "engines": {
- "node": ">=14"
+ "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",
+ "@web/dev-server-core": "^0.3.10",
+ "@web/dev-server-hmr": "^0.1.6",
+ "picomatch": "^2.2.2"
}
},
+ "node_modules/@open-wc/dev-server-hmr/node_modules/@web/dev-server-core": {
+ "version": "0.3.19",
+ "resolved": "https://registry.npmjs.org/@web/dev-server-core/-/dev-server-core-0.3.19.tgz",
+ "integrity": "sha512-Q/Xt4RMVebLWvALofz1C0KvP8qHbzU1EmdIA2Y1WMPJwiFJFhPxdr75p9YxK32P2t0hGs6aqqS5zE0HW9wYzYA==",
+ "dev": true,
+ "dependencies": {
+ "@types/koa": "^2.11.6",
+ "@types/ws": "^7.4.0",
+ "@web/parse5-utils": "^1.2.0",
+ "chokidar": "^3.4.3",
+ "clone": "^2.1.2",
+ "es-module-lexer": "^1.0.0",
+ "get-stream": "^6.0.0",
+ "is-stream": "^2.0.0",
+ "isbinaryfile": "^4.0.6",
+ "koa": "^2.13.0",
+ "koa-etag": "^4.0.0",
+ "koa-send": "^5.0.1",
+ "koa-static": "^5.0.0",
+ "lru-cache": "^6.0.0",
+ "mime-types": "^2.1.27",
+ "parse5": "^6.0.1",
+ "picomatch": "^2.2.2",
+ "ws": "^7.4.2"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/@open-wc/dev-server-hmr/node_modules/@web/parse5-utils": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/@web/parse5-utils/-/parse5-utils-1.3.1.tgz",
+ "integrity": "sha512-haCgDchZrAOB9EhBJ5XqiIjBMsS/exsM5Ru7sCSyNkXVEJWskyyKuKMFk66BonnIGMPpDtqDrTUfYEis5Zi3XA==",
+ "dev": true,
+ "dependencies": {
+ "@types/parse5": "^6.0.1",
+ "parse5": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/@open-wc/dev-server-hmr/node_modules/isbinaryfile": {
+ "version": "4.0.10",
+ "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz",
+ "integrity": "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==",
+ "dev": true,
+ "engines": {
+ "node": ">= 8.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/gjtorikian/"
+ }
+ },
+ "node_modules/@open-wc/dev-server-hmr/node_modules/lru-cache": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+ "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+ "dev": true,
+ "dependencies": {
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@open-wc/dev-server-hmr/node_modules/yallist": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+ "dev": true
+ },
+ "node_modules/@open-wc/scoped-elements": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/@open-wc/scoped-elements/-/scoped-elements-3.0.5.tgz",
+ "integrity": "sha512-q4U+hFTQQRyorJILOpmBm6PY2hgjCnQe214nXJNjbJMQ9EvT55oyZ7C8BY5aFYJkytUyBoawlMpZt4F2xjdzHw==",
+ "dev": true,
+ "dependencies": {
+ "@open-wc/dedupe-mixin": "^1.4.0",
+ "lit": "^3.0.0"
+ }
+ },
+ "node_modules/@open-wc/semantic-dom-diff": {
+ "version": "0.20.1",
+ "resolved": "https://registry.npmjs.org/@open-wc/semantic-dom-diff/-/semantic-dom-diff-0.20.1.tgz",
+ "integrity": "sha512-mPF/RPT2TU7Dw41LEDdaeP6eyTOWBD4z0+AHP4/d0SbgcfJZVRymlIB6DQmtz0fd2CImIS9kszaMmwMt92HBPA==",
+ "dev": true,
+ "dependencies": {
+ "@types/chai": "^4.3.1",
+ "@web/test-runner-commands": "^0.9.0"
+ }
+ },
+ "node_modules/@open-wc/testing": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@open-wc/testing/-/testing-4.0.0.tgz",
+ "integrity": "sha512-KI70O0CJEpBWs3jrTju4BFCy7V/d4tFfYWkg8pMzncsDhD7TYNHLw5cy+s1FHXIgVFetnMDhPpwlKIPvtTQW7w==",
+ "dev": true,
+ "dependencies": {
+ "@esm-bundle/chai": "^4.3.4-fix.0",
+ "@open-wc/semantic-dom-diff": "^0.20.0",
+ "@open-wc/testing-helpers": "^3.0.0",
+ "@types/chai-dom": "^1.11.0",
+ "@types/sinon-chai": "^3.2.3",
+ "chai-a11y-axe": "^1.5.0"
+ }
+ },
+ "node_modules/@open-wc/testing-helpers": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/@open-wc/testing-helpers/-/testing-helpers-3.0.1.tgz",
+ "integrity": "sha512-hyNysSatbgT2FNxHJsS3rGKcLEo6+HwDFu1UQL6jcSQUabp/tj3PyX7UnXL3H5YGv0lJArdYLSnvjLnjn3O2fw==",
+ "dev": true,
+ "dependencies": {
+ "@open-wc/scoped-elements": "^3.0.2",
+ "lit": "^2.0.0 || ^3.0.0",
+ "lit-html": "^2.0.0 || ^3.0.0"
+ }
+ },
+ "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.37.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.37.0.tgz",
- "integrity": "sha512-pKivGpgJM5g8dwj0ywBwe/HeVAUSuVVJhUTa/URXjxvoyTT/AxsLTAbkHkDHG7qQxLoW2s3apEIl26uUe08LVQ==",
+ "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"
],
@@ -668,18 +1311,261 @@
"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/@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/chai": {
+ "version": "4.3.20",
+ "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.20.tgz",
+ "integrity": "sha512-/pC9HAB5I/xMlc5FP77qjCnI16ChlJfW0tGa0IUcFn38VJrTV6DeZ60NU5KZBtaOZqjdpwTWohz5HU1RrhiYxQ==",
+ "dev": true
+ },
+ "node_modules/@types/chai-dom": {
+ "version": "1.11.3",
+ "resolved": "https://registry.npmjs.org/@types/chai-dom/-/chai-dom-1.11.3.tgz",
+ "integrity": "sha512-EUEZI7uID4ewzxnU7DJXtyvykhQuwe+etJ1wwOiJyQRTH/ifMWKX+ghiXkxCUvNJ6IQDodf0JXhuP6zZcy2qXQ==",
+ "dev": true,
+ "dependencies": {
+ "@types/chai": "*"
+ }
+ },
+ "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",
@@ -687,22 +1573,692 @@
"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.13.14",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.14.tgz",
- "integrity": "sha512-Zs/Ollc1SJ8nKUAgc7ivOEdIBM8JAKgrqqUYi2J997JuKO7/tpQC+WCetQ1sypiKCQWHdvdg9wBNpUPEWZae7w==",
+ "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.20.0"
+ "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/sinon": {
+ "version": "17.0.4",
+ "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-17.0.4.tgz",
+ "integrity": "sha512-RHnIrhfPO3+tJT0s7cFaXGZvsL4bbR3/k7z3P312qMS4JaS2Tk+KiwiLx1S0rQ56ERj00u1/BtdyVd0FY+Pdew==",
+ "dev": true,
+ "dependencies": {
+ "@types/sinonjs__fake-timers": "*"
+ }
+ },
+ "node_modules/@types/sinon-chai": {
+ "version": "3.2.12",
+ "resolved": "https://registry.npmjs.org/@types/sinon-chai/-/sinon-chai-3.2.12.tgz",
+ "integrity": "sha512-9y0Gflk3b0+NhQZ/oxGtaAJDvRywCa5sIyaVnounqLvmf93yBF4EgIRspePtkMs3Tr844nCclYMlcCNmLCvjuQ==",
+ "dev": true,
+ "dependencies": {
+ "@types/chai": "*",
+ "@types/sinon": "*"
+ }
+ },
+ "node_modules/@types/sinonjs__fake-timers": {
+ "version": "8.1.5",
+ "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.5.tgz",
+ "integrity": "sha512-mQkU2jY8jJEF7YHjHvsQO8+3ughTL1mcnn96igfhONmR+fUPSKIkefQYpSe8bsly2Ep7oQbn/6VG5/9/0qcArQ==",
+ "dev": true
+ },
"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-hmr": {
+ "version": "0.1.12",
+ "resolved": "https://registry.npmjs.org/@web/dev-server-hmr/-/dev-server-hmr-0.1.12.tgz",
+ "integrity": "sha512-oqEYVFAh9D74GUigQqxPN5izhocc+A02tZ7Y4QCIHLe6qttjD5R+Hpj8CAObySslfH1X/IGSsWhB8TGctCxlPA==",
+ "dev": true,
+ "dependencies": {
+ "@web/dev-server-core": "^0.4.1"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/@web/dev-server-hmr/node_modules/@web/dev-server-core": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/@web/dev-server-core/-/dev-server-core-0.4.1.tgz",
+ "integrity": "sha512-KdYwejXZwIZvb6tYMCqU7yBiEOPfKLQ3V9ezqqEz8DA9V9R3oQWaowckvCpFB9IxxPfS/P8/59OkdzGKQjcIUw==",
+ "dev": true,
+ "dependencies": {
+ "@types/koa": "^2.11.6",
+ "@types/ws": "^7.4.0",
+ "@web/parse5-utils": "^1.3.1",
+ "chokidar": "^3.4.3",
+ "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": "^6.0.0",
+ "mime-types": "^2.1.27",
+ "parse5": "^6.0.1",
+ "picomatch": "^2.2.2",
+ "ws": "^7.4.2"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/@web/dev-server-hmr/node_modules/@web/parse5-utils": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/@web/parse5-utils/-/parse5-utils-1.3.1.tgz",
+ "integrity": "sha512-haCgDchZrAOB9EhBJ5XqiIjBMsS/exsM5Ru7sCSyNkXVEJWskyyKuKMFk66BonnIGMPpDtqDrTUfYEis5Zi3XA==",
+ "dev": true,
+ "dependencies": {
+ "@types/parse5": "^6.0.1",
+ "parse5": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/@web/dev-server-hmr/node_modules/lru-cache": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+ "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+ "dev": true,
+ "dependencies": {
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@web/dev-server-hmr/node_modules/yallist": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+ "dev": true
+ },
+ "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",
@@ -724,41 +2280,49 @@
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
"license": "ISC"
},
- "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",
+ "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": ">=8"
+ "node": ">= 0.6"
}
},
- "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",
+ "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": {
- "color-convert": "^2.0.1"
+ "type-fest": "^0.21.3"
},
"engines": {
"node": ">=8"
},
"funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/any-promise": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
- "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
- "dev": true
- },
"node_modules/anymatch": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
"dev": true,
+ "license": "ISC",
"dependencies": {
"normalize-path": "^3.0.0",
"picomatch": "^2.0.4"
@@ -767,12 +2331,67 @@
"node": ">= 8"
}
},
- "node_modules/arg": {
- "version": "5.0.2",
- "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
- "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
+ "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",
@@ -792,6 +2411,7 @@
"url": "https://github.com/sponsors/ai"
}
],
+ "license": "MIT",
"dependencies": {
"browserslist": "^4.24.4",
"caniuse-lite": "^1.0.30001702",
@@ -810,17 +2430,128 @@
"postcss": "^8.1.0"
}
},
- "node_modules/balanced-match": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
- "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "node_modules/axe-core": {
+ "version": "4.10.3",
+ "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.10.3.tgz",
+ "integrity": "sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "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/binary-extensions": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
"integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
"dev": true,
+ "license": "MIT",
"engines": {
"node": ">=8"
},
@@ -828,20 +2559,12 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/brace-expansion": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
- "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
- "dev": true,
- "dependencies": {
- "balanced-match": "^1.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"
},
@@ -868,6 +2591,7 @@
"url": "https://github.com/sponsors/ai"
}
],
+ "license": "MIT",
"dependencies": {
"caniuse-lite": "^1.0.30001688",
"electron-to-chromium": "^1.5.73",
@@ -881,19 +2605,116 @@
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
}
},
- "node_modules/camelcase-css": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
- "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
+ "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": ">= 6"
+ "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.30001710",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001710.tgz",
- "integrity": "sha512-B5C0I0UmaGqHgo5FuqJ7hBd4L57A4dDD+Xi+XX1nXOoxGeDdY4Ko38qJYOyqznBVJEqON5p8P1x5zRR3+rsnxA==",
+ "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": [
{
@@ -908,13 +2729,70 @@
"type": "github",
"url": "https://github.com/sponsors/ai"
}
- ]
+ ],
+ "license": "CC-BY-4.0"
+ },
+ "node_modules/chai-a11y-axe": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/chai-a11y-axe/-/chai-a11y-axe-1.5.0.tgz",
+ "integrity": "sha512-V/Vg/zJDr9aIkaHJ2KQu7lGTQQm5ZOH4u1k5iTMvIXuSVlSuUo0jcSpSqf9wUn9zl6oQXa4e4E0cqH18KOgKlQ==",
+ "dev": true,
+ "dependencies": {
+ "axe-core": "^4.3.3"
+ }
+ },
+ "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/chokidar": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"anymatch": "~3.1.2",
"braces": "~3.0.2",
@@ -934,6 +2812,50 @@
"fsevents": "~2.3.2"
}
},
+ "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",
@@ -948,6 +2870,114 @@
"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",
@@ -966,13 +2996,119 @@
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"license": "MIT"
},
- "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",
+ "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": ">= 10"
+ "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": {
@@ -980,6 +3116,7 @@
"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",
@@ -989,18 +3126,6 @@
"node": ">= 8"
}
},
- "node_modules/cssesc": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
- "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
- "dev": true,
- "bin": {
- "cssesc": "bin/cssesc"
- },
- "engines": {
- "node": ">=4"
- }
- },
"node_modules/d3-array": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
@@ -1068,6 +3193,15 @@
"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",
@@ -1124,6 +3258,15 @@
"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",
@@ -1237,6 +3380,88 @@
"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",
@@ -1246,19 +3471,35 @@
"robust-predicates": "^3.0.2"
}
},
- "node_modules/dependency-graph": {
+ "node_modules/delegates": {
"version": "1.0.0",
- "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-1.0.0.tgz",
- "integrity": "sha512-cW3gggJ28HZ/LExwxP2B++aiKxhJXMSIt9K48FOXQkm+vuG5gyatXnLsONRJdzO/7VfjDIiaOOa/bs4l464Lwg==",
+ "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": ">=4"
+ "node": ">= 0.8"
}
},
- "node_modules/didyoumean": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
- "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
+ "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": {
@@ -1286,34 +3527,196 @@
"highlight.js": "11.9.0"
}
},
- "node_modules/dlv": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
- "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
- "dev": true
+ "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/eastasianwidth": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
- "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
+ "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.132",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.132.tgz",
- "integrity": "sha512-QgX9EBvWGmvSRa74zqfnG7+Eno0Ak0vftBll0Pt2/z5b3bEGYL6OUXLgKPtvx73dn3dvwrlyVkjPKRRlhLYTEg==",
+ "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/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/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.1",
- "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.1.tgz",
- "integrity": "sha512-BGO5LtrGC7vxnqucAe/rmvKdJllfGaYWdyABvyMoXQlfYMb2bbRuReWR5tEGE//4LcNJj9XrkovTqNYRFZHAMQ==",
+ "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",
@@ -1324,31 +3727,31 @@
"node": ">=18"
},
"optionalDependencies": {
- "@esbuild/aix-ppc64": "0.25.1",
- "@esbuild/android-arm": "0.25.1",
- "@esbuild/android-arm64": "0.25.1",
- "@esbuild/android-x64": "0.25.1",
- "@esbuild/darwin-arm64": "0.25.1",
- "@esbuild/darwin-x64": "0.25.1",
- "@esbuild/freebsd-arm64": "0.25.1",
- "@esbuild/freebsd-x64": "0.25.1",
- "@esbuild/linux-arm": "0.25.1",
- "@esbuild/linux-arm64": "0.25.1",
- "@esbuild/linux-ia32": "0.25.1",
- "@esbuild/linux-loong64": "0.25.1",
- "@esbuild/linux-mips64el": "0.25.1",
- "@esbuild/linux-ppc64": "0.25.1",
- "@esbuild/linux-riscv64": "0.25.1",
- "@esbuild/linux-s390x": "0.25.1",
- "@esbuild/linux-x64": "0.25.1",
- "@esbuild/netbsd-arm64": "0.25.1",
- "@esbuild/netbsd-x64": "0.25.1",
- "@esbuild/openbsd-arm64": "0.25.1",
- "@esbuild/openbsd-x64": "0.25.1",
- "@esbuild/sunos-x64": "0.25.1",
- "@esbuild/win32-arm64": "0.25.1",
- "@esbuild/win32-ia32": "0.25.1",
- "@esbuild/win32-x64": "0.25.1"
+ "@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": {
@@ -1360,11 +3763,177 @@
"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",
@@ -1387,15 +3956,26 @@
"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"
},
@@ -1403,20 +3983,16 @@
"node": ">=8"
}
},
- "node_modules/foreground-child": {
- "version": "3.3.1",
- "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
- "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
+ "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": {
- "cross-spawn": "^7.0.6",
- "signal-exit": "^4.0.1"
+ "array-back": "^3.0.1"
},
"engines": {
- "node": ">=14"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
+ "node": ">=4.0.0"
}
},
"node_modules/fraction.js": {
@@ -1424,6 +4000,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": "*"
},
@@ -1432,18 +4009,13 @@
"url": "https://github.com/sponsors/rawify"
}
},
- "node_modules/fs-extra": {
- "version": "11.3.0",
- "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz",
- "integrity": "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==",
+ "node_modules/fresh": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
+ "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
"dev": true,
- "dependencies": {
- "graceful-fs": "^4.2.0",
- "jsonfile": "^6.0.1",
- "universalify": "^2.0.0"
- },
"engines": {
- "node": ">=14.14"
+ "node": ">= 0.6"
}
},
"node_modules/fsevents": {
@@ -1452,6 +4024,7 @@
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"dev": true,
"hasInstallScript": true,
+ "license": "MIT",
"optional": true,
"os": [
"darwin"
@@ -1465,10 +4038,20 @@
"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,
+ "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",
@@ -1478,24 +4061,67 @@
"node": "6.* || 8.* || >= 10.*"
}
},
- "node_modules/glob": {
- "version": "10.4.5",
- "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
- "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
+ "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": {
- "foreground-child": "^3.1.0",
- "jackspeak": "^3.1.2",
- "minimatch": "^9.0.4",
- "minipass": "^7.1.2",
- "package-json-from-dist": "^1.0.0",
- "path-scurry": "^1.11.1"
+ "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"
},
- "bin": {
- "glob": "dist/esm/bin.mjs"
+ "engines": {
+ "node": ">= 0.4"
},
"funding": {
- "url": "https://github.com/sponsors/isaacs"
+ "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": {
@@ -1503,6 +4129,7 @@
"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"
},
@@ -1510,17 +4137,98 @@
"node": ">= 6"
}
},
- "node_modules/graceful-fs": {
- "version": "4.2.11",
- "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
- "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
- "dev": true
+ "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,
+ "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"
},
@@ -1550,6 +4258,104 @@
"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",
@@ -1562,6 +4368,85 @@
"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",
@@ -1571,11 +4456,50 @@
"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-binary-path": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"binary-extensions": "^2.0.0"
},
@@ -1588,6 +4512,7 @@
"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"
},
@@ -1598,11 +4523,27 @@
"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"
}
@@ -1616,11 +4557,30 @@
"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"
},
@@ -1628,80 +4588,365 @@
"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
+ "dev": true,
+ "license": "ISC"
},
- "node_modules/jackspeak": {
- "version": "3.4.3",
- "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
- "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
+ "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": {
- "@isaacs/cliui": "^8.0.2"
+ "istanbul-lib-coverage": "^3.0.0",
+ "make-dir": "^4.0.0",
+ "supports-color": "^7.1.0"
},
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- },
- "optionalDependencies": {
- "@pkgjs/parseargs": "^0.11.0"
+ "engines": {
+ "node": ">=10"
}
},
- "node_modules/jiti": {
- "version": "1.21.7",
- "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz",
- "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
+ "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,
"bin": {
- "jiti": "bin/jiti.js"
+ "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/jsonfile": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
- "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
+ "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,
- "dependencies": {
- "universalify": "^2.0.0"
+ "bin": {
+ "json5": "lib/cli.js"
},
- "optionalDependencies": {
- "graceful-fs": "^4.1.6"
+ "engines": {
+ "node": ">=6"
}
},
- "node_modules/lilconfig": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
- "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==",
+ "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,
- "engines": {
- "node": ">=14"
+ "dependencies": {
+ "tsscmp": "1.0.6"
},
- "funding": {
- "url": "https://github.com/sponsors/antonk52"
+ "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
+ "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",
@@ -1712,16 +4957,119 @@
"@types/trusted-types": "^2.0.2"
}
},
- "node_modules/lru-cache": {
- "version": "10.4.3",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
- "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
+ "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/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.7",
- "resolved": "https://registry.npmjs.org/marked/-/marked-15.0.7.tgz",
- "integrity": "sha512-dgLIeKGLx5FwziAnsk4ONoGwHwGPJzselimvlVskE9XLN4Orv9u2VA3GWw/lYUqjfA0rUT/6fqKwfZJapP9BEg==",
+ "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"
@@ -1730,11 +5078,42 @@
"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"
}
@@ -1744,6 +5123,7 @@
"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"
@@ -1752,30 +5132,42 @@
"node": ">=8.6"
}
},
- "node_modules/minimatch": {
- "version": "9.0.5",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
- "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+ "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,
- "dependencies": {
- "brace-expansion": "^2.0.1"
- },
"engines": {
- "node": ">=16 || 14 >=14.17"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
+ "node": ">= 0.6"
}
},
- "node_modules/minipass": {
- "version": "7.1.2",
- "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
- "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
+ "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": ">=16 || 14 >=14.17"
+ "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",
@@ -1786,28 +5178,29 @@
"node": "*"
}
},
- "node_modules/mz": {
- "version": "2.7.0",
- "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
- "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
- "dev": true,
- "dependencies": {
- "any-promise": "^1.0.0",
- "object-assign": "^4.0.1",
- "thenify-all": "^1.0.0"
- }
+ "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==",
- "dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
+ "license": "MIT",
"bin": {
"nanoid": "bin/nanoid.cjs"
},
@@ -1815,6 +5208,24 @@
"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",
@@ -1839,7 +5250,8 @@
"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
+ "dev": true,
+ "license": "MIT"
},
"node_modules/nopt": {
"version": "1.0.10",
@@ -1861,6 +5273,7 @@
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
"dev": true,
+ "license": "MIT",
"engines": {
"node": ">=0.10.0"
}
@@ -1870,39 +5283,230 @@
"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/object-assign": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
- "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "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": ">=0.10.0"
+ "node": ">=8"
}
},
- "node_modules/object-hash": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
- "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
+ "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": ">= 6"
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/package-json-from-dist": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
- "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
+ "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"
}
@@ -1911,35 +5515,36 @@
"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
- },
- "node_modules/path-scurry": {
- "version": "1.11.1",
- "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
- "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
"dev": true,
- "dependencies": {
- "lru-cache": "^10.2.0",
- "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
- },
+ "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": ">=16 || 14 >=14.18"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
+ "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==",
- "dev": true
+ "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"
},
@@ -1947,29 +5552,23 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
- "node_modules/pify": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
- "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
+ "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": ">=0.10.0"
- }
- },
- "node_modules/pirates": {
- "version": "4.0.7",
- "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz",
- "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==",
- "dev": true,
- "engines": {
- "node": ">= 6"
+ "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==",
- "dev": true,
"funding": [
{
"type": "opencollective",
@@ -1984,6 +5583,7 @@
"url": "https://github.com/sponsors/ai"
}
],
+ "license": "MIT",
"dependencies": {
"nanoid": "^3.3.8",
"picocolors": "^1.1.1",
@@ -1993,186 +5593,240 @@
"node": "^10 || ^12 || >=14"
}
},
- "node_modules/postcss-cli": {
- "version": "11.0.1",
- "resolved": "https://registry.npmjs.org/postcss-cli/-/postcss-cli-11.0.1.tgz",
- "integrity": "sha512-0UnkNPSayHKRe/tc2YGW6XnSqqOA9eqpiRMgRlV1S6HdGi16vwJBx7lviARzbV1HpQHqLLRH3o8vTcB0cLc+5g==",
- "dev": true,
- "dependencies": {
- "chokidar": "^3.3.0",
- "dependency-graph": "^1.0.0",
- "fs-extra": "^11.0.0",
- "picocolors": "^1.0.0",
- "postcss-load-config": "^5.0.0",
- "postcss-reporter": "^7.0.0",
- "pretty-hrtime": "^1.0.3",
- "read-cache": "^1.0.0",
- "slash": "^5.0.0",
- "tinyglobby": "^0.2.12",
- "yargs": "^17.0.0"
- },
- "bin": {
- "postcss": "index.js"
- },
- "engines": {
- "node": ">=18"
- },
- "peerDependencies": {
- "postcss": "^8.0.0"
- }
- },
- "node_modules/postcss-import": {
- "version": "15.1.0",
- "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz",
- "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==",
- "dev": true,
- "dependencies": {
- "postcss-value-parser": "^4.0.0",
- "read-cache": "^1.0.0",
- "resolve": "^1.1.7"
- },
- "engines": {
- "node": ">=14.0.0"
- },
- "peerDependencies": {
- "postcss": "^8.0.0"
- }
- },
- "node_modules/postcss-js": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz",
- "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==",
- "dev": true,
- "dependencies": {
- "camelcase-css": "^2.0.1"
- },
- "engines": {
- "node": "^12 || ^14 || >= 16"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/postcss/"
- },
- "peerDependencies": {
- "postcss": "^8.4.21"
- }
- },
- "node_modules/postcss-load-config": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-5.1.0.tgz",
- "integrity": "sha512-G5AJ+IX0aD0dygOE0yFZQ/huFFMSNneyfp0e3/bT05a8OfPC5FUoZRPfGijUdGOJNMewJiwzcHJXFafFzeKFVA==",
- "dev": true,
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/postcss/"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "dependencies": {
- "lilconfig": "^3.1.1",
- "yaml": "^2.4.2"
- },
- "engines": {
- "node": ">= 18"
- },
- "peerDependencies": {
- "jiti": ">=1.21.0",
- "postcss": ">=8.0.9",
- "tsx": "^4.8.1"
- },
- "peerDependenciesMeta": {
- "jiti": {
- "optional": true
- },
- "postcss": {
- "optional": true
- },
- "tsx": {
- "optional": true
- }
- }
- },
- "node_modules/postcss-nested": {
- "version": "6.2.0",
- "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz",
- "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==",
- "dev": true,
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/postcss/"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "dependencies": {
- "postcss-selector-parser": "^6.1.1"
- },
- "engines": {
- "node": ">=12.0"
- },
- "peerDependencies": {
- "postcss": "^8.2.14"
- }
- },
- "node_modules/postcss-reporter": {
- "version": "7.1.0",
- "resolved": "https://registry.npmjs.org/postcss-reporter/-/postcss-reporter-7.1.0.tgz",
- "integrity": "sha512-/eoEylGWyy6/DOiMP5lmFRdmDKThqgn7D6hP2dXKJI/0rJSO1ADFNngZfDzxL0YAxFvws+Rtpuji1YIHj4mySA==",
- "dev": true,
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/postcss/"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "dependencies": {
- "picocolors": "^1.0.0",
- "thenby": "^1.3.4"
- },
- "engines": {
- "node": ">=10"
- },
- "peerDependencies": {
- "postcss": "^8.1.0"
- }
- },
- "node_modules/postcss-selector-parser": {
- "version": "6.1.2",
- "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
- "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
- "dev": true,
- "dependencies": {
- "cssesc": "^3.0.0",
- "util-deprecate": "^1.0.2"
- },
- "engines": {
- "node": ">=4"
- }
- },
"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
+ "dev": true,
+ "license": "MIT"
},
- "node_modules/pretty-hrtime": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz",
- "integrity": "sha512-66hKPCr+72mlfiSjlEB1+45IjXSqvVAIy6mocupoww4tBFE9R9IhwwUGoI4G++Tc9Aq+2rxOt0RFU6gPcrte0A==",
+ "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.8"
+ "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": {
@@ -2193,15 +5847,59 @@
"type": "consulting",
"url": "https://feross.org/support"
}
- ]
+ ],
+ "license": "MIT"
},
- "node_modules/read-cache": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
- "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
+ "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": {
- "pify": "^2.3.0"
+ "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/readdirp": {
@@ -2209,6 +5907,7 @@
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"picomatch": "^2.2.1"
},
@@ -2230,6 +5929,7 @@
"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",
@@ -2245,11 +5945,90 @@
"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"
@@ -2261,6 +6040,45 @@
"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",
@@ -2280,6 +6098,7 @@
"url": "https://feross.org/support"
}
],
+ "license": "MIT",
"dependencies": {
"queue-microtask": "^1.2.2"
}
@@ -2290,12 +6109,63 @@
"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",
@@ -2308,11 +6178,18 @@
"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"
},
@@ -2325,319 +6202,301 @@
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
"dev": true,
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/signal-exit": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
- "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
- "dev": true,
- "engines": {
- "node": ">=14"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/slash": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz",
- "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==",
- "dev": true,
- "engines": {
- "node": ">=14.16"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "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==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "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/string-width-cjs": {
- "name": "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==",
+ "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": {
- "emoji-regex": "^8.0.0",
- "is-fullwidth-code-point": "^3.0.0",
- "strip-ansi": "^6.0.1"
+ "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": ">=8"
- }
- },
- "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/strip-ansi-cjs": {
- "name": "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/sucrase": {
- "version": "3.35.0",
- "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz",
- "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==",
- "dev": true,
- "dependencies": {
- "@jridgewell/gen-mapping": "^0.3.2",
- "commander": "^4.0.0",
- "glob": "^10.3.10",
- "lines-and-columns": "^1.1.6",
- "mz": "^2.7.0",
- "pirates": "^4.0.1",
- "ts-interface-checker": "^0.1.9"
- },
- "bin": {
- "sucrase": "bin/sucrase",
- "sucrase-node": "bin/sucrase-node"
- },
- "engines": {
- "node": ">=16 || 14 >=14.17"
- }
- },
- "node_modules/sucrase/node_modules/commander": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
- "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
- "dev": true,
- "engines": {
- "node": ">= 6"
- }
- },
- "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,
- "engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/tailwindcss": {
- "version": "3.4.1",
- "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.1.tgz",
- "integrity": "sha512-qAYmXRfk3ENzuPBakNK0SRrUDipP8NQnEY6772uDhflcQz5EhRdD7JNZxyrFHVQNCwULPBn6FNPp9brpO7ctcA==",
+ "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": {
- "@alloc/quick-lru": "^5.2.0",
- "arg": "^5.0.2",
- "chokidar": "^3.5.3",
- "didyoumean": "^1.2.2",
- "dlv": "^1.1.3",
- "fast-glob": "^3.3.0",
- "glob-parent": "^6.0.2",
- "is-glob": "^4.0.3",
- "jiti": "^1.19.1",
- "lilconfig": "^2.1.0",
- "micromatch": "^4.0.5",
- "normalize-path": "^3.0.0",
- "object-hash": "^3.0.0",
- "picocolors": "^1.0.0",
- "postcss": "^8.4.23",
- "postcss-import": "^15.1.0",
- "postcss-js": "^4.0.1",
- "postcss-load-config": "^4.0.1",
- "postcss-nested": "^6.0.1",
- "postcss-selector-parser": "^6.0.11",
- "resolve": "^1.22.2",
- "sucrase": "^3.32.0"
- },
- "bin": {
- "tailwind": "lib/cli.js",
- "tailwindcss": "lib/cli.js"
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.3"
},
"engines": {
- "node": ">=14.0.0"
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/tailwindcss/node_modules/glob-parent": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
- "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "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": {
- "is-glob": "^4.0.3"
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3"
},
"engines": {
- "node": ">=10.13.0"
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/tailwindcss/node_modules/lilconfig": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz",
- "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==",
+ "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/tailwindcss/node_modules/postcss-load-config": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz",
- "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==",
+ "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,
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/postcss/"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
"dependencies": {
- "lilconfig": "^3.0.0",
- "yaml": "^2.3.4"
+ "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"
- },
- "peerDependencies": {
- "postcss": ">=8.0.9",
- "ts-node": ">=9.0.0"
- },
- "peerDependenciesMeta": {
- "postcss": {
- "optional": true
- },
- "ts-node": {
- "optional": true
- }
}
},
- "node_modules/tailwindcss/node_modules/postcss-load-config/node_modules/lilconfig": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
- "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==",
+ "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": ">=14"
- },
- "funding": {
- "url": "https://github.com/sponsors/antonk52"
+ "node": ">= 8"
}
},
- "node_modules/thenby": {
- "version": "1.3.4",
- "resolved": "https://registry.npmjs.org/thenby/-/thenby-1.3.4.tgz",
- "integrity": "sha512-89Gi5raiWA3QZ4b2ePcEwswC3me9JIg+ToSgtE0JWeCynLnLxNr/f9G+xfo9K+Oj4AFdom8YNJjibIARTJmapQ==",
+ "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/thenify": {
- "version": "3.3.1",
- "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
- "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
+ "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,
- "dependencies": {
- "any-promise": "^1.0.0"
+ "engines": {
+ "node": ">= 0.6"
}
},
- "node_modules/thenify-all": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
- "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
+ "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": {
- "thenify": ">= 3.1.0 < 4"
+ "fast-fifo": "^1.3.2",
+ "text-decoder": "^1.1.0"
},
- "engines": {
- "node": ">=0.8"
+ "optionalDependencies": {
+ "bare-events": "^2.2.0"
}
},
- "node_modules/tinyglobby": {
- "version": "0.2.12",
- "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.12.tgz",
- "integrity": "sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==",
+ "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": {
- "fdir": "^6.4.3",
- "picomatch": "^4.0.2"
+ "has-flag": "^4.0.0"
},
"engines": {
- "node": ">=12.0.0"
+ "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/SuperchupuDev"
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/tinyglobby/node_modules/fdir": {
- "version": "6.4.3",
- "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.3.tgz",
- "integrity": "sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==",
+ "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,
- "peerDependencies": {
- "picomatch": "^3 || ^4"
+ "dependencies": {
+ "array-back": "^6.2.2",
+ "wordwrapjs": "^5.1.0"
},
- "peerDependenciesMeta": {
- "picomatch": {
- "optional": true
- }
+ "engines": {
+ "node": ">=12.17"
}
},
- "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==",
+ "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"
- },
- "funding": {
- "url": "https://github.com/sponsors/jonschlinkert"
+ "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/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"
},
@@ -2645,6 +6504,15 @@
"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",
@@ -2671,22 +6539,57 @@
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
"license": "MIT"
},
- "node_modules/ts-interface-checker": {
- "version": "0.1.13",
- "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
- "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
- "dev": true
- },
"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.2",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz",
- "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==",
+ "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": {
@@ -2697,20 +6600,39 @@
"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.20.0",
- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
- "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
+ "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/universalify": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
- "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
+ "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": ">= 10.0.0"
+ "node": ">= 0.8"
}
},
"node_modules/update-browserslist-db": {
@@ -2732,6 +6654,7 @@
"url": "https://github.com/sponsors/ai"
}
],
+ "license": "MIT",
"dependencies": {
"escalade": "^3.2.0",
"picocolors": "^1.1.1"
@@ -2743,12 +6666,35 @@
"browserslist": ">= 4.21.0"
}
},
- "node_modules/util-deprecate": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
- "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+ "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",
@@ -3237,6 +7183,7 @@
"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"
},
@@ -3247,39 +7194,40 @@
"node": ">= 8"
}
},
- "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"
- },
+ "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": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ "node": ">=12.17"
}
},
- "node_modules/wrap-ansi-cjs": {
- "name": "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==",
+ "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,
- "dependencies": {
- "ansi-styles": "^4.0.0",
- "string-width": "^4.1.0",
- "strip-ansi": "^6.0.0"
- },
"engines": {
- "node": ">=10"
+ "node": ">=8.3.0"
},
- "funding": {
- "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": "^5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
}
},
"node_modules/y18n": {
@@ -3291,17 +7239,11 @@
"node": ">=10"
}
},
- "node_modules/yaml": {
- "version": "2.7.1",
- "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.1.tgz",
- "integrity": "sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==",
- "dev": true,
- "bin": {
- "yaml": "bin.mjs"
- },
- "engines": {
- "node": ">= 14"
- }
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "dev": true
},
"node_modules/yargs": {
"version": "17.7.2",
@@ -3329,6 +7271,75 @@
"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
index 06cc3c0..44d3a78 100644
--- a/loop/webui/package.json
+++ b/loop/webui/package.json
@@ -1,35 +1,46 @@
{
"name": "webui",
- "version": "1.0.0",
"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",
- "test": "echo \"Error: no test specified\" && exit 1",
- "build:tailwind": "npx postcss ./src/input.css -o ./src/tailwind.css",
- "build:tailwind:watch": "npx postcss ./src/input.css -o ./src/tailwind.css --watch"
- },
- "keywords": [],
- "author": "",
- "license": "ISC",
- "devDependencies": {
- "@types/marked": "^5.0.2",
- "@types/node": "^22.13.14",
- "autoprefixer": "^10.4.21",
- "esbuild": "^0.25.1",
- "postcss": "^8.5.3",
- "postcss-cli": "^11.0.1",
- "tailwindcss": "^3.4.1",
- "typescript": "^5.8.2"
+ "demo": "web-dev-server -config ./web-dev-server.config.mjs --node-resolve --open /src/web-components/demo/",
+ "build": "tsc",
+ "watch": "tsc --watch",
+ "test": "tsc && wtr --coverage",
+ "test:manual": "tsc && wtr --manual",
+ "test:watch": "tsc && concurrently -k -r \"tsc --watch --preserveWatchOutput\" \"wtr --watch\""
},
"dependencies": {
"@xterm/addon-fit": "^0.10.0",
"@xterm/xterm": "^5.5.0",
"diff2html": "3.4.51",
- "lit-html": "^3.2.1",
+ "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": {
+ "@open-wc/dev-server-hmr": "^0.1.2-next.0",
+ "@open-wc/testing": "^4.0.0",
+ "@types/marked": "^5.0.2",
+ "@types/mocha": "^10.0.7",
+ "@types/node": "^22.13.14",
+ "@web/dev-server": "^0.4.6",
+ "@web/test-runner": "^0.18.2",
+ "@web/test-runner-puppeteer": "^0.18.0",
+ "autoprefixer": "^10.4.21",
+ "esbuild": "^0.25.1",
+ "typescript": "^5.8.2"
+ },
+ "keywords": []
}
diff --git a/loop/webui/postcss.config.js b/loop/webui/postcss.config.js
deleted file mode 100644
index 12a703d..0000000
--- a/loop/webui/postcss.config.js
+++ /dev/null
@@ -1,6 +0,0 @@
-module.exports = {
- plugins: {
- tailwindcss: {},
- autoprefixer: {},
- },
-};
diff --git a/loop/webui/readme.md b/loop/webui/readme.md
index 861a8cd..b904934 100644
--- a/loop/webui/readme.md
+++ b/loop/webui/readme.md
@@ -49,3 +49,14 @@
- `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/timeline/data.ts b/loop/webui/src/data.ts
similarity index 100%
rename from loop/webui/src/timeline/data.ts
rename to loop/webui/src/data.ts
diff --git a/loop/webui/src/index.html b/loop/webui/src/index.html
deleted file mode 100644
index a1f62a0..0000000
--- a/loop/webui/src/index.html
+++ /dev/null
@@ -1,40 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
- <head>
- <meta charset="UTF-8" />
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
- <title>Loop WebUI</title>
- <link rel="stylesheet" href="tailwind.css" />
- <style>
- body {
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
- Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
- margin: 0;
- padding: 20px;
- background-color: #f5f5f5;
- }
- #app {
- max-width: 800px;
- margin: 0 auto;
- background-color: white;
- border-radius: 8px;
- padding: 20px;
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
- }
- h1 {
- color: #333;
- }
- #status {
- margin-top: 20px;
- padding: 10px;
- background-color: #e8f5e9;
- border-radius: 4px;
- color: #2e7d32;
- }
- </style>
- </head>
- <body>
- <div id="app">Loading...</div>
- <script src="index.js"></script>
- </body>
-</html>
diff --git a/loop/webui/src/input.css b/loop/webui/src/input.css
deleted file mode 100644
index 176b454..0000000
--- a/loop/webui/src/input.css
+++ /dev/null
@@ -1,5 +0,0 @@
-@tailwind base;
-@tailwind components;
-@tailwind utilities;
-
-/* Custom styles can be added below */
diff --git a/loop/webui/src/sketch-app-shell.html b/loop/webui/src/sketch-app-shell.html
new file mode 100644
index 0000000..6a9f1c3
--- /dev/null
+++ b/loop/webui/src/sketch-app-shell.html
@@ -0,0 +1,31 @@
+<!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>
+ <!-- We only need basic body styling; all component styles are encapsulated -->
+ <style>
+ 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;
+ }
+ </style>
+ <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/timeline.css b/loop/webui/src/timeline.css
deleted file mode 100644
index 2928c44..0000000
--- a/loop/webui/src/timeline.css
+++ /dev/null
@@ -1,1306 +0,0 @@
-body {
- font-family:
- system-ui,
- -apple-system,
- BlinkMacSystemFont,
- "Segoe UI",
- Roboto,
- sans-serif;
- margin: 0;
- padding: 20px;
- padding-top: 80px; /* Added padding to account for the fixed top banner */
- padding-bottom: 100px; /* Adjusted padding for chat container */
- color: #333;
- line-height: 1.4; /* Reduced line height for more compact text */
-}
-
-.timeline-container {
- max-width: 1200px;
- margin: 0 auto;
- position: relative;
-}
-
-/* When diff view is active, allow timeline container to expand to full width */
-.timeline-container.diff-active {
- max-width: 100%;
-}
-
-/* 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;
- flex-wrap: wrap;
- 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;
- 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;
- max-width: 100%;
-}
-
-/* Original header styles kept for compatibility */
-header {
- display: none; /* Hidden since we're using top-banner instead */
-}
-
-/* Ensure the container starts below the fixed top banner */
-.timeline-container {
- padding-top: 10px;
-}
-
-h1 {
- margin: 0;
- font-size: 24px;
- font-weight: 600;
-}
-
-.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;
-}
-
-.cost {
- color: #2e7d32;
-}
-
-.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: 5px;
-}
-
-.poll-updates {
- display: flex;
- align-items: center;
- margin: 0 5px;
- font-size: 12px;
-}
-
-.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;
-}
-
-/* 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;
-}
-
-.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;
-}
-
-/* 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;
-}
-
-.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: #f7f7f7;
- border-radius: 2px;
- 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;
-}
-
-/* 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;
-}
-
-.collapsible {
- cursor: pointer;
- background-color: #f0f0f0;
- padding: 5px 10px;
- border: none;
- border-radius: 4px;
- text-align: left;
- font-size: 12px;
- margin-top: 5px;
-}
-
-.collapsed {
- max-height: 50px;
- overflow-y: hidden;
- position: relative;
- text-overflow: ellipsis;
-}
-
-/* Removed the gradient effect */
-
-.loader {
- display: flex;
- justify-content: center;
- padding: 20px;
-}
-
-.loader::after {
- content: "";
- width: 30px;
- height: 30px;
- border: 3px solid #f3f3f3;
- border-top: 3px solid #3498db;
- border-radius: 50%;
- animation: spin 1s linear infinite;
-}
-
-@keyframes spin {
- 0% {
- transform: rotate(0deg);
- }
- 100% {
- transform: rotate(360deg);
- }
-}
-
-/* Chat styles */
-.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;
-}
-
-.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: none;
- font-family: monospace;
- font-size: 12px;
- min-height: 40px;
- max-height: 120px;
- background: #f7f7f7;
-}
-
-#sendChatButton {
- background-color: #2196f3;
- color: white;
- border: none;
- border-radius: 4px;
- padding: 0 20px;
- cursor: pointer;
- font-weight: 600;
-}
-
-#sendChatButton:hover {
- background-color: #0d8bf2;
-}
-
-/* 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;
-}
-
-/* Diff View Styles */
-.diff-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);
- display: flex;
- flex-direction: column;
-}
-
-.diff-tabs {
- display: flex;
- background-color: #e0e0e0;
- border-bottom: 1px solid #ccc;
-}
-
-.diff-tab-button {
- padding: 8px 16px;
- border: none;
- background: none;
- font-size: 14px;
- cursor: pointer;
- outline: none;
- transition: background-color 0.2s;
-}
-
-.diff-tab-button:hover {
- background-color: #d0d0d0;
-}
-
-.diff-tab-button.active {
- background-color: #fff;
- border-bottom: 2px solid #3498db;
-}
-
-.diff-container {
- flex: 1;
- overflow: hidden;
-}
-
-/* Removed diff-header for more space */
-
-.diff-content {
- padding: 15px;
- margin: 0;
- max-height: 70vh;
- overflow-y: auto;
- font-family: Consolas, Monaco, "Andale Mono", monospace;
- font-size: 14px;
- line-height: 1.5;
- white-space: pre;
- tab-size: 4;
- background-color: #fff;
-}
-
-.diff-content .diff-line {
- padding: 0 5px;
- white-space: pre;
- cursor: pointer;
- transition: background-color 0.2s;
-}
-
-.diff-content .diff-line:hover {
- background-color: #e6f7ff;
-}
-
-.diff-content .diff-add {
- background-color: #e6ffed;
- color: #22863a;
-}
-
-.diff-content .diff-remove {
- background-color: #ffeef0;
- color: #cb2431;
-}
-
-.diff-content .diff-info {
- color: #6a737d;
- background-color: #f0f0f0;
-}
-
-.diff-comment-box {
- position: fixed;
- left: 50%;
- top: 50%;
- transform: translate(-50%, -50%);
- width: 80%;
- max-width: 600px;
- background-color: #fff;
- padding: 20px;
- border-radius: 8px;
- box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
- z-index: 1000;
-}
-
-.diff-comment-box h3 {
- margin-top: 0;
- margin-bottom: 15px;
- font-size: 18px;
-}
-
-.selected-line {
- background-color: #f5f5f5;
- padding: 10px;
- margin-bottom: 15px;
- border-radius: 4px;
- border-left: 3px solid #0366d6;
-}
-
-.selected-line pre {
- margin: 5px 0 0 0;
- white-space: pre-wrap;
- word-wrap: break-word;
- font-family: Consolas, Monaco, "Andale Mono", monospace;
- font-size: 14px;
-}
-
-#diffCommentInput {
- width: 100%;
- min-height: 100px;
- padding: 10px;
- margin-bottom: 15px;
- border: 1px solid #ccc;
- border-radius: 4px;
- resize: vertical;
- font-family: Arial, sans-serif;
-}
-
-.diff-comment-buttons {
- display: flex;
- justify-content: flex-end;
- gap: 10px;
-}
-
-.diff-comment-buttons button {
- padding: 8px 15px;
- border: none;
- border-radius: 4px;
- cursor: pointer;
- font-weight: 500;
-}
-
-#submitDiffComment {
- background-color: #0366d6;
- color: white;
-}
-
-#submitDiffComment:hover {
- background-color: #0256bd;
-}
-
-#cancelDiffComment {
- background-color: #e1e4e8;
- color: #24292e;
-}
-
-#cancelDiffComment:hover {
- background-color: #d1d5da;
-}
-
-/* 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;
-}
-
-#showConversationButton.active {
- background-color: #e6f7ff;
- border-color: #1890ff;
-}
-
-#showDiffButton.active {
- background-color: #f6ffed;
- border-color: #52c41a;
-}
-
-#showChartsButton.active {
- background-color: #fff2e8;
- border-color: #fa8c16;
-}
-
-.stop-button:hover {
- background-color: #c82333 !important;
-}
-
-/* Chart View Styles */
-.chart-view {
- width: 100%;
- background-color: #ffffff;
- border-radius: 8px;
- overflow: hidden;
- margin-bottom: 20px;
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
- padding: 15px;
-}
-
-.chart-container {
- width: 100%;
- height: auto;
- overflow: auto;
-}
-
-.chart-section {
- margin-bottom: 30px;
- border-bottom: 1px solid #eee;
- padding-bottom: 20px;
-}
-
-/* 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;
-}
-
-#showTerminalButton.active {
- background-color: #fef0f0;
- border-color: #ff4d4f;
-}
-
-.chart-section:last-child {
- border-bottom: none;
- margin-bottom: 0;
-}
-
-.chart-section h3 {
- margin-top: 0;
- margin-bottom: 15px;
- font-size: 18px;
- color: #333;
-}
-
-#costChart,
-#messagesChart {
- width: 100%;
- min-height: 300px;
- margin-bottom: 10px;
-}
-
-/* Tool calls container styles */
-.tool-calls-container {
- /* Removed dotted border */
-}
-
-.tool-calls-toggle {
- cursor: pointer;
- background-color: #f0f0f0;
- padding: 5px 10px;
- border: none;
- border-radius: 4px;
- text-align: left;
- font-size: 12px;
- margin-top: 5px;
- color: #555;
- font-weight: 500;
-}
-
-.tool-calls-toggle:hover {
- background-color: #e0e0e0;
-}
-
-.tool-calls-details {
- margin-top: 10px;
- transition: max-height 0.3s ease;
-}
-
-.tool-calls-details.collapsed {
- max-height: 0;
- overflow: hidden;
- margin-top: 0;
-}
-
-.tool-call {
- background: #f9f9f9;
- border-radius: 4px;
- padding: 10px;
- margin-bottom: 10px;
- border-left: 3px solid #4caf50;
-}
-
-.tool-call-header {
- margin-bottom: 8px;
- font-size: 14px;
- padding: 2px 0;
-}
-
-/* Compact tool display styles */
-.tool-compact-line {
- font-family: monospace;
- font-size: 12px;
- line-height: 1.4;
- padding: 4px 6px;
- background: #f8f8f8;
- border-radius: 3px;
- position: relative;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- max-width: 100%;
- display: flex;
- align-items: center;
-}
-
-.tool-result-inline {
- font-family: monospace;
- color: #0066bb;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- max-width: 400px;
- display: inline-block;
- vertical-align: middle;
-}
-
-.copy-inline-button {
- font-size: 10px;
- padding: 2px 4px;
- margin-left: 8px;
- background: #eee;
- border: none;
- border-radius: 3px;
- cursor: pointer;
- opacity: 0.7;
-}
-
-.copy-inline-button:hover {
- opacity: 1;
- background: #ddd;
-}
-
-.tool-input.compact,
-.tool-result.compact {
- margin: 2px 0;
- padding: 4px;
- font-size: 12px;
-}
-
-/* Removed old compact container CSS */
-
-/* Ultra-compact tool call box styles */
-.tool-calls-header {
- /* Empty header - just small spacing */
-}
-
-.tool-call-boxes-row {
- display: flex;
- flex-wrap: wrap;
- gap: 8px;
- margin-bottom: 8px;
-}
-
-.tool-call-wrapper {
- display: flex;
- flex-direction: column;
- margin-bottom: 4px;
-}
-
-.tool-call-box {
- display: inline-flex;
- align-items: center;
- background: #f0f0f0;
- border-radius: 4px;
- padding: 3px 8px;
- font-size: 12px;
- cursor: pointer;
- max-width: 320px;
- position: relative;
- border: 1px solid #ddd;
- transition: background-color 0.2s;
-}
-
-.tool-call-box:hover {
- background-color: #e8e8e8;
-}
-
-.tool-call-box.expanded {
- background-color: #e0e0e0;
- border-bottom-left-radius: 0;
- border-bottom-right-radius: 0;
- border-bottom: 1px solid #ccc;
-}
-
-.tool-call-name {
- font-weight: bold;
- margin-right: 6px;
- color: #444;
-}
-
-.tool-call-input {
- color: #666;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- font-family: monospace;
- font-size: 11px;
-}
-
-/* Removed old expanded view CSS */
-
-/* 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;
-}
-
-/* Tool call cards */
-.tool-call-cards-container {
- display: flex;
- flex-direction: column;
- gap: 8px;
- margin-top: 8px;
-}
-
-/* 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;
-}
-
-.commit-boxes-row {
- display: flex;
- flex-wrap: wrap;
- gap: 8px;
- margin-top: 8px;
-}
-
-.tool-call-card {
- display: flex;
- flex-direction: column;
- border: 1px solid #ddd;
- border-radius: 6px;
- background-color: #f9f9f9;
- overflow: hidden;
- cursor: pointer;
-}
-
-/* Compact view (default) */
-.tool-call-compact-view {
- display: flex;
- align-items: center;
- padding: 0px 6px;
- gap: 8px;
- background-color: #f9f9f9;
- font-size: 0.9em;
- white-space: nowrap;
- overflow: visible; /* Don't hide overflow, we'll handle text truncation per element */
- position: relative; /* For positioning the expand icon */
-}
-
-/* Expanded view (hidden by default) */
-.tool-call-card.collapsed .tool-call-expanded-view {
- display: none;
-}
-
-.tool-call-expanded-view {
- display: flex;
- flex-direction: column;
- border-top: 1px solid #eee;
-}
-
-.tool-call-header {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 6px 10px;
- background-color: #f0f0f0;
- border-bottom: 1px solid #ddd;
- font-weight: bold;
-}
-
-.tool-call-name {
- font-family: var(--monospace-font);
- color: #0066cc;
- font-weight: bold;
-}
-
-.tool-call-status {
- margin-right: 4px;
- min-width: 1em;
- text-align: center;
-}
-
-.tool-call-status.spinner {
- animation: spin 1s infinite linear;
- display: inline-block;
- width: 1em;
-}
-
-.tool-call-time {
- margin-left: 8px;
- font-size: 0.85em;
- color: #666;
- font-weight: normal;
-}
-
-.tool-call-input-preview {
- color: #555;
- font-family: var(--monospace-font);
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- max-width: 30%;
- background-color: rgba(240, 240, 240, 0.5);
- padding: 2px 5px;
- border-radius: 3px;
- font-size: 0.9em;
-}
-
-.tool-call-result-preview {
- color: #28a745;
- font-family: var(--monospace-font);
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- max-width: 40%;
- background-color: rgba(240, 248, 240, 0.5);
- padding: 2px 5px;
- border-radius: 3px;
- font-size: 0.9em;
-}
-
-.tool-call-expand-icon {
- position: absolute;
- right: 10px;
- font-size: 0.8em;
- color: #888;
-}
-
-.tool-call-input {
- padding: 6px 10px;
- border-bottom: 1px solid #eee;
- font-family: var(--monospace-font);
- font-size: 0.9em;
- white-space: pre-wrap;
- word-break: break-all;
- background-color: #f5f5f5;
-}
-
-.tool-call-result {
- padding: 6px 10px;
- font-family: var(--monospace-font);
- font-size: 0.9em;
- white-space: pre-wrap;
- max-height: 300px;
- overflow-y: auto;
-}
-
-.tool-call-result pre {
- margin: 0;
- white-space: pre-wrap;
-}
-
-@keyframes spin {
- 0% {
- transform: rotate(0deg);
- }
- 100% {
- transform: rotate(360deg);
- }
-}
-
-/* Standalone tool messages (legacy/disconnected) */
-.tool-details.standalone .tool-header {
- border-radius: 4px;
- background-color: #fff3cd;
- border-color: #ffeeba;
-}
-
-.tool-details.standalone .tool-warning {
- margin-left: 10px;
- font-size: 0.85em;
- color: #856404;
- font-style: italic;
-}
-
-/* Tool call expanded view with sections */
-.tool-call-section {
- border-bottom: 1px solid #eee;
-}
-
-.tool-call-section:last-child {
- border-bottom: none;
-}
-
-.tool-call-section-label {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 8px 10px;
- background-color: #f5f5f5;
- font-weight: bold;
- font-size: 0.9em;
-}
-
-.tool-call-section-content {
- padding: 0;
-}
-
-.tool-call-copy-btn {
- background-color: #f0f0f0;
- border: 1px solid #ddd;
- border-radius: 4px;
- padding: 2px 8px;
- font-size: 0.8em;
- cursor: pointer;
- transition: background-color 0.2s;
-}
-
-.tool-call-copy-btn:hover {
- background-color: #e0e0e0;
-}
-
-/* Override for tool call input in expanded view */
-.tool-call-section-content .tool-call-input {
- margin: 0;
- padding: 8px 10px;
- border: none;
- background-color: #fff;
- max-height: 300px;
- overflow-y: auto;
-}
-
-.title-container {
- display: flex;
- flex-direction: column;
- max-width: 33%;
- overflow: hidden;
-}
-
-.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;
- cursor: pointer;
- font-family: monospace;
- background-color: #f6f8fa;
- border-bottom: 1px dashed #d1d5da;
-}
-
-.commit-preview:hover {
- background-color: #eef2f6;
-}
-
-.commit-hash {
- color: #0366d6;
- font-weight: bold;
-}
-
-.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: 6px 12px;
- border: 1px solid #ccc;
- border-radius: 3px;
- background-color: #f7f7f7;
- color: #24292e;
- font-size: 12px;
- cursor: pointer;
- transition: all 0.2s ease;
- margin: 8px 12px;
- display: block;
-}
-
-.commit-diff-button:hover {
- background-color: #e7e7e7;
- border-color: #aaa;
-}
-
-/* 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;
-}
-
-.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
-}
\ No newline at end of file
diff --git a/loop/webui/src/timeline.html b/loop/webui/src/timeline.html
deleted file mode 100644
index 46144c1..0000000
--- a/loop/webui/src/timeline.html
+++ /dev/null
@@ -1,158 +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>
- <!-- Import the diff2html CSS -->
- <link rel="stylesheet" href="static/diff2html.min.css" />
- <link rel="stylesheet" href="static/timeline.css" />
- <link rel="stylesheet" href="static/diff2.css" />
- <link rel="stylesheet" href="static/xterm.css" />
- <link rel="stylesheet" href="static/tailwind.css" />
- </head>
- <body>
- <div class="top-banner">
- <div class="title-container">
- <h1 class="banner-title">sketch coding assistant</h1>
- <h2 id="chatTitle" class="chat-title"></h2>
- </div>
- <div class="info-grid">
- <div class="info-item">
- <a href="logs" class="text-blue-600 font-medium hover:text-blue-800 hover:underline">Logs</a>
- </div>
- <div class="info-item">
- <a href="download" class="text-blue-600 font-medium hover:text-blue-800 hover:underline">Download</a>
- </div>
- <div class="info-item">
- <span id="hostname" class="info-value">Loading...</span>
- </div>
- <div class="info-item">
- <span id="workingDir" class="info-value">Loading...</span>
- </div>
- <div class="info-item">
- <span class="info-label">Commit:</span>
- <span id="initialCommit" class="info-value">Loading...</span>
- </div>
- <div class="info-item">
- <span class="info-label">Msgs:</span>
- <span id="messageCount" class="info-value">0</span>
- </div>
- <div class="info-item">
- <span class="info-label">In:</span>
- <span id="inputTokens" class="info-value">0</span>
- </div>
- <div class="info-item">
- <span class="info-label">Cache Read:</span>
- <span id="cacheReadInputTokens" class="info-value">0</span>
- </div>
- <div class="info-item">
- <span class="info-label">Cache Create:</span>
- <span id="cacheCreationInputTokens" class="info-value">0</span>
- </div>
- <div class="info-item">
- <span class="info-label">Out:</span>
- <span id="outputTokens" class="info-value">0</span>
- </div>
- <div class="info-item">
- <span class="info-label">Cost:</span>
- <span id="totalCost" class="info-value cost">$0.00</span>
- </div>
- </div>
- <div class="refresh-control">
- <div class="view-mode-buttons">
- <button
- id="showConversationButton"
- class="emoji-button"
- title="Conversation View"
- >
- 💬
- </button>
- <button
- id="showDiff2Button"
- class="emoji-button"
- title="Diff View"
- >
- ±
- </button>
- <button
- id="showChartsButton"
- class="emoji-button"
- title="Charts View"
- >
- 📈
- </button>
- <button
- id="showTerminalButton"
- class="emoji-button"
- title="Terminal View"
- >
- 💻
- </button>
- </div>
- <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>
- <div class="status-container">
- <span id="pollingIndicator" class="polling-indicator"></span>
- <span id="statusText" class="status-text"></span>
- </div>
- </div>
- </div>
-
- <div class="timeline-container">
- <div id="timeline" class="timeline empty"></div>
- <div id="diff2View" class="diff-view" style="display: none">
- <div id="diff2Container" class="diff-container">
- <div id="diff-view-controls">
- <div class="diff-view-format">
- <label>
- <input type="radio" name="diffViewFormat" value="side-by-side" checked> Side-by-side
- </label>
- <label>
- <input type="radio" name="diffViewFormat" value="line-by-line"> Line-by-line
- </label>
- </div>
- </div>
- <div id="diff2htmlContent" class="diff2html-content"></div>
- </div>
- </div>
- <div id="chartView" class="chart-view" style="display: none">
- <div id="chartContainer" class="chart-container"></div>
- </div>
- <div id="terminalView" class="terminal-view" style="display: none">
- <div id="terminalContainer" class="terminal-container"></div>
- </div>
- <div id="diffCommentBox" class="diff-comment-box" style="display: none">
- <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="submitDiffComment">Add Comment</button>
- <button id="cancelDiffComment">Cancel</button>
- </div>
- </div>
- </div>
-
- <div class="chat-container">
- <div class="chat-input-wrapper">
- <textarea
- id="chatInput"
- placeholder="Type your message here and press Enter to send..."
- autofocus
- ></textarea>
- <button id="sendChatButton">Send</button>
- </div>
- </div>
-
- <script src="static/timeline.js"></script>
- </body>
-</html>
diff --git a/loop/webui/src/timeline.ts b/loop/webui/src/timeline.ts
deleted file mode 100644
index eef2726..0000000
--- a/loop/webui/src/timeline.ts
+++ /dev/null
@@ -1,641 +0,0 @@
-import { TimelineMessage } from "./timeline/types";
-import { formatNumber } from "./timeline/utils";
-import { checkShouldScroll } from "./timeline/scroll";
-import { ChartManager } from "./timeline/charts";
-import { ConnectionStatus, DataManager } from "./timeline/data";
-import { DiffViewer } from "./timeline/diffviewer";
-import { MessageRenderer } from "./timeline/renderer";
-import { TerminalHandler } from "./timeline/terminal";
-
-/**
- * TimelineManager - Class to manage the timeline UI and functionality
- */
-class TimelineManager {
- private diffViewer = new DiffViewer();
- private terminalHandler = new TerminalHandler();
- private chartManager = new ChartManager();
- private messageRenderer = new MessageRenderer();
- private dataManager = new DataManager();
-
- private viewMode: "chat" | "diff2" | "charts" | "terminal" = "chat";
- shouldScrollToBottom: boolean;
-
- constructor() {
- // Initialize when DOM is ready
- document.addEventListener("DOMContentLoaded", () => {
- // First initialize from URL params to prevent flash of incorrect view
- // This must happen before setting up other event handlers
- void this.initializeViewFromUrl()
- .then(() => {
- // Continue with the rest of initialization
- return this.initialize();
- })
- .catch((err) => {
- console.error("Failed to initialize timeline:", err);
- });
- });
-
- // Add popstate event listener to handle browser back/forward navigation
- window.addEventListener("popstate", (event) => {
- if (event.state && event.state.mode) {
- // Using void to handle the promise returned by toggleViewMode
- void this.toggleViewMode(event.state.mode);
- } else {
- // If no state or no mode in state, default to chat view
- void this.toggleViewMode("chat");
- }
- });
-
- // Listen for commit diff event from MessageRenderer
- document.addEventListener("showCommitDiff", ((e: CustomEvent) => {
- const { commitHash } = e.detail;
- this.diffViewer.showCommitDiff(
- commitHash,
- (mode: "chat" | "diff2" | "terminal" | "charts") =>
- this.toggleViewMode(mode)
- );
- }) as EventListener);
- }
-
- /**
- * Initialize the timeline manager
- */
- private async initialize(): Promise<void> {
- // Set up data manager event listeners
- this.dataManager.addEventListener(
- "dataChanged",
- this.handleDataChanged.bind(this)
- );
- this.dataManager.addEventListener(
- "connectionStatusChanged",
- this.handleConnectionStatusChanged.bind(this)
- );
-
- // Initialize the data manager
- await this.dataManager.initialize();
-
- // URL parameters have already been read in constructor
- // to prevent flash of incorrect content
-
- // Set up conversation button handler
- document
- .getElementById("showConversationButton")
- ?.addEventListener("click", async () => {
- this.toggleViewMode("chat");
- });
-
- // Set up diff2 button handler
- document
- .getElementById("showDiff2Button")
- ?.addEventListener("click", async () => {
- this.toggleViewMode("diff2");
- });
-
- // Set up charts button handler
- document
- .getElementById("showChartsButton")
- ?.addEventListener("click", async () => {
- this.toggleViewMode("charts");
- });
-
- // Set up terminal button handler
- document
- .getElementById("showTerminalButton")
- ?.addEventListener("click", async () => {
- this.toggleViewMode("terminal");
- });
-
- // The active button will be set by toggleViewMode
- // We'll initialize view based on URL params or default to chat view if no params
- // We defer button activation to the toggleViewMode function
-
- // Set up stop button handler
- document
- .getElementById("stopButton")
- ?.addEventListener("click", async () => {
- this.stopInnerLoop();
- });
-
- const pollToggleCheckbox = document.getElementById(
- "pollToggle"
- ) as HTMLInputElement;
- pollToggleCheckbox?.addEventListener("change", () => {
- this.dataManager.setPollingEnabled(pollToggleCheckbox.checked);
- const statusText = document.getElementById("statusText");
- if (statusText) {
- if (pollToggleCheckbox.checked) {
- statusText.textContent = "Polling for updates...";
- } else {
- statusText.textContent = "Polling stopped";
- }
- }
- });
-
- // Initial data fetch and polling is now handled by the DataManager
-
- // Set up chat functionality
- this.setupChatBox();
-
- // Set up keyboard shortcuts
- this.setupKeyboardShortcuts();
-
- // Set up spacing adjustments
- this.adjustChatSpacing();
- window.addEventListener("resize", () => this.adjustChatSpacing());
- }
-
- /**
- * Set up chat box event listeners
- */
- private setupChatBox(): void {
- const chatInput = document.getElementById(
- "chatInput"
- ) as HTMLTextAreaElement;
- const sendButton = document.getElementById("sendChatButton");
-
- // Handle pressing Enter in the text area
- chatInput?.addEventListener("keydown", (event: KeyboardEvent) => {
- // Send message if Enter is pressed without Shift key
- if (event.key === "Enter" && !event.shiftKey) {
- event.preventDefault(); // Prevent default newline
- this.sendChatMessage();
- }
- });
-
- // Handle send button click
- sendButton?.addEventListener("click", () => this.sendChatMessage());
-
- // Set up mutation observer for the chat container
- if (chatInput) {
- chatInput.addEventListener("input", () => {
- // When content changes, adjust the spacing
- requestAnimationFrame(() => this.adjustChatSpacing());
- });
- }
- }
-
- /**
- * Send the chat message to the server
- */
- private async sendChatMessage(): Promise<void> {
- const chatInput = document.getElementById(
- "chatInput"
- ) as HTMLTextAreaElement;
- if (!chatInput) return;
-
- const message = chatInput.value.trim();
-
- // Don't send empty messages
- 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}`);
- }
-
- // Clear the input after sending
- chatInput.value = "";
-
- // 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 === "diff2") {
- 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";
- }
- }
- }
-
- /**
- * Handle data changed event from the data manager
- */
- private handleDataChanged(eventData: {
- state: any;
- newMessages: TimelineMessage[];
- isFirstFetch?: boolean;
- }): void {
- const { state, newMessages, isFirstFetch } = eventData;
-
- // Check if we should scroll to bottom BEFORE handling new data
- this.shouldScrollToBottom = this.checkShouldScroll();
-
- // Update state info in the UI
- this.updateUIWithState(state);
-
- // Update the timeline if there are new messages
- if (newMessages.length > 0) {
- // Initialize the message renderer with current state
- this.messageRenderer.initialize(
- this.dataManager.getIsFirstLoad(),
- this.dataManager.getCurrentFetchStartIndex()
- );
-
- this.messageRenderer.renderTimeline(newMessages, isFirstFetch || false);
-
- // Update chart data using our full messages array
- this.chartManager.setChartData(
- this.chartManager.calculateCumulativeCostData(
- this.dataManager.getMessages()
- )
- );
-
- // If in charts view, update the charts
- if (this.viewMode === "charts") {
- this.chartManager.renderCharts();
- }
-
- const statusTextEl = document.getElementById("statusText");
- if (statusTextEl) {
- statusTextEl.textContent = "Updated just now";
- }
- } else {
- const statusTextEl = document.getElementById("statusText");
- if (statusTextEl) {
- statusTextEl.textContent = "No new messages";
- }
- }
- }
-
- /**
- * Handle connection status changed event from the data manager
- */
- private handleConnectionStatusChanged(
- status: ConnectionStatus,
- errorMessage?: string
- ): void {
- const pollingIndicator = document.getElementById("pollingIndicator");
- if (!pollingIndicator) return;
-
- // Remove all status classes
- pollingIndicator.classList.remove("active", "error");
-
- // Add appropriate class based on status
- if (status === "connected") {
- pollingIndicator.classList.add("active");
- } else if (status === "disconnected") {
- pollingIndicator.classList.add("error");
- }
-
- // Update status text if error message is provided
- if (errorMessage) {
- const statusTextEl = document.getElementById("statusText");
- if (statusTextEl) {
- statusTextEl.textContent = errorMessage;
- }
- }
- }
-
- /**
- * Update UI elements with state data
- */
- private updateUIWithState(state: any): void {
- // Update state info in the UI with safe getters
- const hostnameEl = document.getElementById("hostname");
- if (hostnameEl) {
- hostnameEl.textContent = state?.hostname ?? "Unknown";
- }
-
- const workingDirEl = document.getElementById("workingDir");
- if (workingDirEl) {
- workingDirEl.textContent = state?.working_dir ?? "Unknown";
- }
-
- const initialCommitEl = document.getElementById("initialCommit");
- if (initialCommitEl) {
- initialCommitEl.textContent = state?.initial_commit
- ? state.initial_commit.substring(0, 8)
- : "Unknown";
- }
-
- const messageCountEl = document.getElementById("messageCount");
- if (messageCountEl) {
- messageCountEl.textContent = state?.message_count ?? "0";
- }
-
- const chatTitleEl = document.getElementById("chatTitle");
- const bannerTitleEl = document.querySelector(".banner-title");
-
- if (chatTitleEl && bannerTitleEl) {
- if (state?.title) {
- chatTitleEl.textContent = state.title;
- chatTitleEl.style.display = "block";
- bannerTitleEl.textContent = "sketch"; // Shorten title when chat title exists
- } else {
- chatTitleEl.style.display = "none";
- bannerTitleEl.textContent = "sketch coding assistant"; // Full title when no chat title
- }
- }
-
- // Get token and cost info safely
- const inputTokens = state?.total_usage?.input_tokens ?? 0;
- const outputTokens = state?.total_usage?.output_tokens ?? 0;
- const cacheReadInputTokens =
- state?.total_usage?.cache_read_input_tokens ?? 0;
- const cacheCreationInputTokens =
- state?.total_usage?.cache_creation_input_tokens ?? 0;
- const totalCost = state?.total_usage?.total_cost_usd ?? 0;
-
- const inputTokensEl = document.getElementById("inputTokens");
- if (inputTokensEl) {
- inputTokensEl.textContent = formatNumber(inputTokens, "0");
- }
-
- const outputTokensEl = document.getElementById("outputTokens");
- if (outputTokensEl) {
- outputTokensEl.textContent = formatNumber(outputTokens, "0");
- }
-
- const cacheReadInputTokensEl = document.getElementById(
- "cacheReadInputTokens"
- );
- if (cacheReadInputTokensEl) {
- cacheReadInputTokensEl.textContent = formatNumber(
- cacheReadInputTokens,
- "0"
- );
- }
-
- const cacheCreationInputTokensEl = document.getElementById(
- "cacheCreationInputTokens"
- );
- if (cacheCreationInputTokensEl) {
- cacheCreationInputTokensEl.textContent = formatNumber(
- cacheCreationInputTokens,
- "0"
- );
- }
-
- const totalCostEl = document.getElementById("totalCost");
- if (totalCostEl) {
- totalCostEl.textContent = `$${totalCost.toFixed(2)}`;
- }
- }
-
- /**
- * Check if we should scroll to the bottom
- */
- private checkShouldScroll(): boolean {
- return checkShouldScroll(this.dataManager.getIsFirstLoad());
- }
-
- /**
- * Dynamically adjust body padding based on the chat container height and top banner
- */
- private adjustChatSpacing(): void {
- const chatContainer = document.querySelector(".chat-container");
- const topBanner = document.querySelector(".top-banner");
-
- if (chatContainer) {
- const chatHeight = (chatContainer as HTMLElement).offsetHeight;
- document.body.style.paddingBottom = `${chatHeight + 20}px`; // 20px extra for spacing
- }
-
- if (topBanner) {
- const topHeight = (topBanner as HTMLElement).offsetHeight;
- document.body.style.paddingTop = `${topHeight + 20}px`; // 20px extra for spacing
- }
- }
-
- /**
- * Set up keyboard shortcuts
- */
- private setupKeyboardShortcuts(): void {
- // Add keyboard shortcut to automatically copy selected text with Ctrl+C (or Command+C on Mac)
- document.addEventListener("keydown", (e: KeyboardEvent) => {
- // We only want to handle Ctrl+C or Command+C
- if ((e.ctrlKey || e.metaKey) && e.key === "c") {
- // If text is already selected, we don't need to do anything special
- // as the browser's default behavior will handle copying
- // But we could add additional behavior here if needed
- }
- });
- }
-
- /**
- * Toggle between different view modes: chat, diff2, charts
- */
- public async toggleViewMode(
- mode: "chat" | "diff2" | "charts" | "terminal"
- ): Promise<void> {
- // Set the new view mode
- this.viewMode = mode;
-
- // Update URL with the current view mode
- this.updateUrlForViewMode(mode);
-
- // Get DOM elements
- const timeline = document.getElementById("timeline");
- const diff2View = document.getElementById("diff2View");
- const chartView = document.getElementById("chartView");
- const container = document.querySelector(".timeline-container");
- const terminalView = document.getElementById("terminalView");
- const conversationButton = document.getElementById(
- "showConversationButton"
- );
- const diff2Button = document.getElementById("showDiff2Button");
- const chartsButton = document.getElementById("showChartsButton");
- const terminalButton = document.getElementById("showTerminalButton");
-
- if (
- !timeline ||
- !diff2View ||
- !chartView ||
- !container ||
- !conversationButton ||
- !diff2Button ||
- !chartsButton ||
- !terminalView ||
- !terminalButton
- ) {
- console.error("Required DOM elements not found");
- return;
- }
-
- // Hide all views first
- timeline.style.display = "none";
- diff2View.style.display = "none";
- chartView.style.display = "none";
- terminalView.style.display = "none";
-
- // Reset all button states
- conversationButton.classList.remove("active");
- diff2Button.classList.remove("active");
- chartsButton.classList.remove("active");
- terminalButton.classList.remove("active");
-
- // Remove diff2-active and diff-active classes from container
- container.classList.remove("diff2-active");
- container.classList.remove("diff-active");
-
- // If switching to chat view, clear the current commit hash
- if (mode === "chat") {
- this.diffViewer.clearCurrentCommitHash();
- }
-
- // Add class to indicate views are initialized (prevents flash of content)
- container.classList.add("view-initialized");
-
- // Show the selected view based on mode
- switch (mode) {
- case "chat":
- timeline.style.display = "block";
- conversationButton.classList.add("active");
- break;
- case "diff2":
- diff2View.style.display = "block";
- diff2Button.classList.add("active");
- this.diffViewer.setViewMode(mode); // Update view mode in diff viewer
- await this.diffViewer.loadDiff2HtmlContent();
- break;
- case "charts":
- chartView.style.display = "block";
- chartsButton.classList.add("active");
- await this.chartManager.renderCharts();
- break;
- case "terminal":
- terminalView.style.display = "block";
- terminalButton.classList.add("active");
- this.terminalHandler.setViewMode(mode); // Update view mode in terminal handler
- this.diffViewer.setViewMode(mode); // Update view mode in diff viewer
- await this.initializeTerminal();
- break;
- }
- }
-
- /**
- * Initialize the terminal view
- */
- private async initializeTerminal(): Promise<void> {
- // Use the TerminalHandler to initialize the terminal
- await this.terminalHandler.initializeTerminal();
- }
-
- /**
- * Initialize the view based on URL parameters
- * This allows bookmarking and sharing of specific views
- */
- private async initializeViewFromUrl(): Promise<void> {
- // Parse the URL parameters
- const urlParams = new URLSearchParams(window.location.search);
- const viewParam = urlParams.get("view");
- const commitParam = urlParams.get("commit");
-
- // Default to chat view if no valid view parameter is provided
- if (!viewParam) {
- // Explicitly set chat view to ensure button state is correct
- await this.toggleViewMode("chat");
- return;
- }
-
- // Check if the view parameter is valid
- if (
- viewParam === "chat" ||
- viewParam === "diff2" ||
- viewParam === "charts" ||
- viewParam === "terminal"
- ) {
- // If it's a diff view with a commit hash, set the commit hash
- if (viewParam === "diff2" && commitParam) {
- this.diffViewer.setCurrentCommitHash(commitParam);
- }
-
- // Set the view mode
- await this.toggleViewMode(
- viewParam as "chat" | "diff2" | "charts" | "terminal"
- );
- }
- }
-
- /**
- * Update URL to reflect current view mode for bookmarking and sharing
- * @param mode The current view mode
- */
- private updateUrlForViewMode(
- mode: "chat" | "diff2" | "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);
-
- // If in diff view and there's a commit hash, include that too
- if (mode === "diff2" && this.diffViewer.getCurrentCommitHash()) {
- url.searchParams.set("commit", this.diffViewer.getCurrentCommitHash());
- }
- }
-
- // Update the browser history without reloading the page
- window.history.pushState({ mode }, "", url.toString());
- }
-
- /**
- * Stop the inner loop by calling the /cancel endpoint
- */
- private async stopInnerLoop(): Promise<void> {
- if (!confirm("Are you sure you want to stop the current operation?")) {
- return;
- }
-
- try {
- const statusText = document.getElementById("statusText");
- if (statusText) {
- statusText.textContent = "Cancelling...";
- }
-
- const response = await fetch("cancel", {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- },
- body: JSON.stringify({ reason: "User requested cancellation via UI" }),
- });
-
- if (!response.ok) {
- const errorData = await response.text();
- throw new Error(`Server error: ${response.status} - ${errorData}`);
- }
-
- // Parse the response
- const _result = await response.json();
- if (statusText) {
- statusText.textContent = "Operation cancelled";
- }
- } catch (error) {
- console.error("Error cancelling operation:", error);
- const statusText = document.getElementById("statusText");
- if (statusText) {
- statusText.textContent = "Error cancelling operation";
- }
- }
- }
-}
-
-// Create and initialize the timeline manager when the page loads
-const _timelineManager = new TimelineManager();
diff --git a/loop/webui/src/timeline/charts.ts b/loop/webui/src/timeline/charts.ts
deleted file mode 100644
index 0ed56e8..0000000
--- a/loop/webui/src/timeline/charts.ts
+++ /dev/null
@@ -1,468 +0,0 @@
-import type { TimelineMessage } from "./types";
-import vegaEmbed from "vega-embed";
-import { TopLevelSpec } from "vega-lite";
-
-/**
- * ChartManager handles all chart-related functionality for the timeline.
- * This includes rendering charts, calculating data, and managing chart state.
- */
-export class ChartManager {
- private chartData: { timestamp: Date; cost: number }[] = [];
-
- /**
- * Create a new ChartManager instance
- */
- constructor() {
- this.chartData = [];
- }
-
- /**
- * Calculate cumulative cost data from messages
- */
- public calculateCumulativeCostData(
- messages: TimelineMessage[],
- ): { 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;
- }
-
- /**
- * Get the current chart data
- */
- public getChartData(): { timestamp: Date; cost: number }[] {
- return this.chartData;
- }
-
- /**
- * Set chart data
- */
- public setChartData(data: { timestamp: Date; cost: number }[]): void {
- this.chartData = data;
- }
-
- /**
- * Fetch all messages to generate chart data
- */
- public async fetchAllMessages(): Promise<void> {
- try {
- // Fetch all messages in a single request
- const response = await fetch("messages");
- if (!response.ok) {
- throw new Error(`Failed to fetch messages: ${response.status}`);
- }
-
- const allMessages = await response.json();
- if (Array.isArray(allMessages)) {
- // 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;
- });
-
- // Calculate cumulative cost data
- this.chartData = this.calculateCumulativeCostData(allMessages);
- }
- } catch (error) {
- console.error("Error fetching messages for chart:", error);
- this.chartData = [];
- }
- }
-
- /**
- * Render all charts in the chart view
- */
- public async renderCharts(): Promise<void> {
- const chartContainer = document.getElementById("chartContainer");
- if (!chartContainer) return;
-
- try {
- // Show loading state
- chartContainer.innerHTML = "<div class='loader'></div>";
-
- // Fetch messages if necessary
- if (this.chartData.length === 0) {
- await this.fetchAllMessages();
- }
-
- // Clear the container for multiple charts
- chartContainer.innerHTML = "";
-
- // Create cost chart container
- const costChartDiv = document.createElement("div");
- costChartDiv.className = "chart-section";
- costChartDiv.innerHTML =
- "<h3>Dollar Usage Over Time</h3><div id='costChart'></div>";
- chartContainer.appendChild(costChartDiv);
-
- // Create messages chart container
- const messagesChartDiv = document.createElement("div");
- messagesChartDiv.className = "chart-section";
- messagesChartDiv.innerHTML =
- "<h3>Message Timeline</h3><div id='messagesChart'></div>";
- chartContainer.appendChild(messagesChartDiv);
-
- // Render both charts
- await this.renderDollarUsageChart();
- await this.renderMessagesChart();
- } catch (error) {
- console.error("Error rendering charts:", error);
- chartContainer.innerHTML = `<p>Error rendering charts: ${error instanceof Error ? error.message : "Unknown error"}</p>`;
- }
- }
-
- /**
- * Render the dollar usage chart using Vega-Lite
- */
- private async renderDollarUsageChart(): Promise<void> {
- const costChartContainer = document.getElementById("costChart");
- if (!costChartContainer) return;
-
- try {
- // Display cost chart using Vega-Lite
- if (this.chartData.length === 0) {
- costChartContainer.innerHTML =
- "<p>No cost data available to display.</p>";
- return;
- }
-
- // Create a Vega-Lite spec for the line chart
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- const costSpec: any = {
- $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",
- },
- ],
- },
- };
-
- // Render the cost chart
- await vegaEmbed(costChartContainer, costSpec, {
- actions: true,
- renderer: "svg",
- });
- } catch (error) {
- console.error("Error rendering dollar usage chart:", error);
- costChartContainer.innerHTML = `<p>Error rendering dollar usage chart: ${error instanceof Error ? error.message : "Unknown error"}</p>`;
- }
- }
-
- /**
- * Render the messages timeline chart using Vega-Lite
- */
- private async renderMessagesChart(): Promise<void> {
- const messagesChartContainer = document.getElementById("messagesChart");
- if (!messagesChartContainer) return;
-
- try {
- // Get all messages
- const response = await fetch("messages");
- if (!response.ok) {
- throw new Error(`Failed to fetch messages: ${response.status}`);
- }
-
- const allMessages = await response.json();
- if (!Array.isArray(allMessages) || allMessages.length === 0) {
- messagesChartContainer.innerHTML =
- "<p>No messages available to display.</p>";
- return;
- }
-
- // 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>();
- 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, index);
- });
-
- // 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!);
-
- // Get the index for this message
- const msgId = msg.timestamp ? msg.timestamp.toString() : "";
- const index = messageIndexMap.get(msgId) || 0;
-
- // 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!);
-
- // Get the index for this message
- const msgId = msg.timestamp ? msg.timestamp.toString() : "";
- const index = messageIndexMap.get(msgId) || 0;
-
- // 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
- };
- });
-
- // Check if we have any data to display
- if (barData.length === 0 && pointData.length === 0) {
- messagesChartContainer.innerHTML =
- "<p>No message timing data available to display.</p>";
- return;
- }
-
- // 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) {
- messagesSpec.layer.push({
- data: { values: barData },
- 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) {
- messagesSpec.layer.push({
- data: { values: pointData },
- 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" },
- ],
- },
- });
- }
-
- // Render the messages timeline chart
- await vegaEmbed(messagesChartContainer, messagesSpec, {
- actions: true,
- renderer: "svg",
- });
- } catch (error) {
- console.error("Error rendering messages chart:", error);
- messagesChartContainer.innerHTML = `<p>Error rendering messages chart: ${error instanceof Error ? error.message : "Unknown error"}</p>`;
- }
- }
-}
diff --git a/loop/webui/src/timeline/commits.ts b/loop/webui/src/timeline/commits.ts
deleted file mode 100644
index f4303f2..0000000
--- a/loop/webui/src/timeline/commits.ts
+++ /dev/null
@@ -1,90 +0,0 @@
-/**
- * Utility functions for rendering commit messages in the timeline
- */
-
-import { escapeHTML } from "./utils";
-
-interface Commit {
- hash: string;
- subject: string;
- body: string;
- pushed_branch?: string;
-}
-
-/**
- * Create HTML elements to display commits in the timeline
- * @param commits List of commit information to display
- * @param diffViewerCallback Callback function to show commit diff when requested
- * @returns The created HTML container element with commit information
- */
-export function createCommitsContainer(
- commits: Commit[],
- diffViewerCallback: (commitHash: string) => void
-): HTMLElement {
- const commitsContainer = document.createElement("div");
- commitsContainer.className = "commits-container";
-
- // Create a header for commits
- const commitsHeaderRow = document.createElement("div");
- commitsHeaderRow.className = "commits-header";
- commitsHeaderRow.textContent = `${commits.length} new commit${commits.length > 1 ? "s" : ""} detected`;
- commitsContainer.appendChild(commitsHeaderRow);
-
- // Create a row for commit boxes
- const commitBoxesRow = document.createElement("div");
- commitBoxesRow.className = "commit-boxes-row";
-
- // Add each commit as a box
- commits.forEach((commit) => {
- // Create the commit box
- const commitBox = document.createElement("div");
- commitBox.className = "commit-box";
-
- // Show commit hash and subject line as the preview
- const commitPreview = document.createElement("div");
- commitPreview.className = "commit-preview";
-
- // Include pushed branch information if available
- let previewHTML = `<span class="commit-hash">${commit.hash.substring(0, 8)}</span> ${escapeHTML(commit.subject)}`;
- if (commit.pushed_branch) {
- previewHTML += ` <span class="pushed-branch">→ pushed to ${escapeHTML(commit.pushed_branch)}</span>`;
- }
-
- commitPreview.innerHTML = previewHTML;
- commitBox.appendChild(commitPreview);
-
- // Create expandable view for commit details
- const expandedView = document.createElement("div");
- expandedView.className = "commit-details is-hidden";
- expandedView.innerHTML = `<pre>${escapeHTML(commit.body)}</pre>`;
- commitBox.appendChild(expandedView);
-
- // Toggle visibility of expanded view when clicking the preview
- commitPreview.addEventListener("click", (event) => {
- // If holding Ctrl/Cmd key, show diff for this commit
- if (event.ctrlKey || event.metaKey) {
- // Call the diff viewer callback with the commit hash
- diffViewerCallback(commit.hash);
- } else {
- // Normal behavior - toggle expanded view
- expandedView.classList.toggle("is-hidden");
- }
- });
-
- // Add a diff button to view commit changes
- const diffButton = document.createElement("button");
- diffButton.className = "commit-diff-button";
- diffButton.textContent = "View Changes";
- diffButton.addEventListener("click", (event) => {
- event.stopPropagation(); // Prevent triggering the parent click event
- diffViewerCallback(commit.hash);
- });
- // Add the button directly to the commit box
- commitBox.appendChild(diffButton);
-
- commitBoxesRow.appendChild(commitBox);
- });
-
- commitsContainer.appendChild(commitBoxesRow);
- return commitsContainer;
-}
diff --git a/loop/webui/src/timeline/components/collapsible.ts b/loop/webui/src/timeline/components/collapsible.ts
deleted file mode 100644
index 12f90ec..0000000
--- a/loop/webui/src/timeline/components/collapsible.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-import { TimelineMessage } from "../types";
-
-/**
- * Adds collapsible functionality to long content elements.
- * This creates a toggle button that allows users to expand/collapse long text content.
- *
- * @param message - The timeline message containing the content
- * @param textEl - The DOM element containing the text content
- * @param containerEl - The container element for the text and copy button
- * @param contentEl - The outer content element that will contain everything
- */
-export function addCollapsibleFunctionality(
- message: TimelineMessage,
- textEl: HTMLElement,
- containerEl: HTMLElement,
- contentEl: HTMLElement
-): void {
- // Don't collapse end_of_turn messages (final output) regardless of length
- if (message.content.length > 1000 && !message.end_of_turn) {
- textEl.classList.add("collapsed");
-
- const toggleButton = document.createElement("button");
- toggleButton.className = "collapsible";
- toggleButton.textContent = "Show more...";
- toggleButton.addEventListener("click", () => {
- textEl.classList.toggle("collapsed");
- toggleButton.textContent = textEl.classList.contains("collapsed")
- ? "Show more..."
- : "Show less";
- });
-
- contentEl.appendChild(containerEl);
- contentEl.appendChild(toggleButton);
- } else {
- contentEl.appendChild(containerEl);
- }
-}
diff --git a/loop/webui/src/timeline/copybutton.ts b/loop/webui/src/timeline/copybutton.ts
deleted file mode 100644
index d9b994b..0000000
--- a/loop/webui/src/timeline/copybutton.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-/**
- * Creates a copy button container with a functioning copy button
- */
-export function createCopyButton(textToCopy: string): {
- container: HTMLDivElement;
- button: HTMLButtonElement;
-} {
- // Create container for the copy button
- const copyButtonContainer = document.createElement("div");
- copyButtonContainer.className = "message-actions";
-
- // Create the copy button itself
- const copyButton = document.createElement("button");
- copyButton.className = "copy-button";
- copyButton.textContent = "Copy";
- copyButton.title = "Copy text to clipboard";
-
- // Add click event listener to handle copying
- copyButton.addEventListener("click", (e) => {
- e.stopPropagation();
- navigator.clipboard
- .writeText(textToCopy)
- .then(() => {
- copyButton.textContent = "Copied!";
- setTimeout(() => {
- copyButton.textContent = "Copy";
- }, 2000);
- })
- .catch((err) => {
- console.error("Failed to copy text: ", err);
- copyButton.textContent = "Failed";
- setTimeout(() => {
- copyButton.textContent = "Copy";
- }, 2000);
- });
- });
-
- copyButtonContainer.appendChild(copyButton);
-
- return {
- container: copyButtonContainer,
- button: copyButton
- };
-}
diff --git a/loop/webui/src/timeline/diffviewer.ts b/loop/webui/src/timeline/diffviewer.ts
deleted file mode 100644
index 1460dc3..0000000
--- a/loop/webui/src/timeline/diffviewer.ts
+++ /dev/null
@@ -1,384 +0,0 @@
-import * as Diff2Html from "diff2html";
-
-/**
- * Class to handle diff and commit viewing functionality in the timeline UI.
- */
-export class DiffViewer {
- // Current commit hash being viewed
- private currentCommitHash: string = "";
- // Selected line in the diff for commenting
- private selectedDiffLine: string | null = null;
- // Current view mode (needed for integration with TimelineManager)
- private viewMode: string = "chat";
-
- /**
- * Constructor for DiffViewer
- */
- constructor() {}
-
- /**
- * Sets the current view mode
- * @param mode The current view mode
- */
- public setViewMode(mode: string): void {
- this.viewMode = mode;
- }
-
- /**
- * Gets the current commit hash
- * @returns The current commit hash
- */
- public getCurrentCommitHash(): string {
- return this.currentCommitHash;
- }
-
- /**
- * Sets the current commit hash
- * @param hash The commit hash to set
- */
- public setCurrentCommitHash(hash: string): void {
- this.currentCommitHash = hash;
- }
-
- /**
- * Clears the current commit hash
- */
- public clearCurrentCommitHash(): void {
- this.currentCommitHash = "";
- }
-
- /**
- * Loads diff content and renders it using diff2html
- * @param commitHash Optional commit hash to load diff for
- */
- public async loadDiff2HtmlContent(commitHash?: string): Promise<void> {
- const diff2htmlContent = document.getElementById("diff2htmlContent");
- const container = document.querySelector(".timeline-container");
- if (!diff2htmlContent || !container) return;
-
- try {
- // Show loading state
- diff2htmlContent.innerHTML = "Loading enhanced diff...";
-
- // Add classes to container to allow full-width rendering
- container.classList.add("diff2-active");
- container.classList.add("diff-active");
-
- // Use currentCommitHash if provided or passed from parameter
- const hash = commitHash || this.currentCommitHash;
-
- // Build the diff URL - include commit hash if specified
- const diffUrl = hash ? `diff?commit=${hash}` : "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;
- }
-
- // Get the selected view format
- const formatRadios = document.getElementsByName("diffViewFormat") as NodeListOf<HTMLInputElement>;
- let outputFormat = "side-by-side"; // default
-
- // Convert NodeListOf to Array to ensure [Symbol.iterator]() is available
- Array.from(formatRadios).forEach(radio => {
- if (radio.checked) {
- outputFormat = radio.value as "side-by-side" | "line-by-line";
- }
- })
-
- // Render the diff using diff2html
- const diffHtml = Diff2Html.html(diffText, {
- outputFormat: outputFormat as "side-by-side" | "line-by-line",
- drawFileList: true,
- matching: "lines",
- // Make sure no unnecessary scrollbars in the nested containers
- 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.setupDiff2LineComments();
-
- // Setup event listeners for diff view format radio buttons
- this.setupDiffViewFormatListeners();
- } 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>`;
- }
- }
-
- /**
- * Setup event listeners for diff view format radio buttons
- */
- private setupDiffViewFormatListeners(): void {
- const formatRadios = document.getElementsByName("diffViewFormat") as NodeListOf<HTMLInputElement>;
-
- // Convert NodeListOf to Array to ensure [Symbol.iterator]() is available
- Array.from(formatRadios).forEach(radio => {
- radio.addEventListener("change", () => {
- // Reload the diff with the new format when radio selection changes
- this.loadDiff2HtmlContent(this.currentCommitHash);
- });
- })
- }
-
- /**
- * Setup handlers for diff2 code lines to enable commenting
- */
- private setupDiff2LineComments(): void {
- const diff2htmlContent = document.getElementById("diff2htmlContent");
- if (!diff2htmlContent) return;
-
- console.log("Setting up diff2 line comments");
-
- // 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
- this.openDiffCommentBox(formattedLine, 0);
-
- // Prevent event from bubbling up
- event.stopPropagation();
- }
- });
-
- // Handle text selection
- let isSelecting = false;
-
- diff2htmlContent.addEventListener("mousedown", () => {
- isSelecting = false;
- });
-
- diff2htmlContent.addEventListener("mousemove", (event) => {
- // If mouse is moving with button pressed, user is selecting text
- if (event.buttons === 1) { // Primary button (usually left) is pressed
- isSelecting = true;
- }
- });
- }
-
- /**
- * Add plus buttons to each table row in the diff for commenting
- */
- private addCommentButtonsToCodeLines(): void {
- const diff2htmlContent = document.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, _lineNumber: number): void {
- const commentBox = document.getElementById("diffCommentBox");
- const selectedLine = document.getElementById("selectedLine");
- const commentInput = document.getElementById(
- "diffCommentInput",
- ) as HTMLTextAreaElement;
-
- if (!commentBox || !selectedLine || !commentInput) return;
-
- // Store the selected line
- this.selectedDiffLine = lineText;
-
- // Display the line in the comment box
- selectedLine.textContent = lineText;
-
- // Reset the comment input
- commentInput.value = "";
-
- // Show the comment box
- commentBox.style.display = "block";
-
- // Focus on the comment input
- commentInput.focus();
-
- // Add event listeners for submit and cancel buttons
- const submitButton = document.getElementById("submitDiffComment");
- if (submitButton) {
- submitButton.onclick = () => this.submitDiffComment();
- }
-
- const cancelButton = document.getElementById("cancelDiffComment");
- if (cancelButton) {
- cancelButton.onclick = () => this.closeDiffCommentBox();
- }
- }
-
- /**
- * Close the diff comment box without submitting
- */
- private closeDiffCommentBox(): void {
- const commentBox = document.getElementById("diffCommentBox");
- if (commentBox) {
- commentBox.style.display = "none";
- }
- this.selectedDiffLine = null;
- }
-
- /**
- * Submit a comment on a diff line
- */
- private submitDiffComment(): void {
- const commentInput = document.getElementById(
- "diffCommentInput",
- ) as HTMLTextAreaElement;
- const chatInput = document.getElementById(
- "chatInput",
- ) as HTMLTextAreaElement;
-
- if (!commentInput || !chatInput) 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}`;
-
- // Append the formatted comment to the chat textarea
- if (chatInput.value.trim() !== "") {
- chatInput.value += "\n\n"; // Add two line breaks before the new comment
- }
- chatInput.value += formattedComment;
- chatInput.focus();
-
- // Close only the comment box but keep the diff view open
- this.closeDiffCommentBox();
- }
-
- /**
- * Show diff for a specific commit
- * @param commitHash The commit hash to show diff for
- * @param toggleViewModeCallback Callback to toggle view mode to diff
- */
- public showCommitDiff(commitHash: string, toggleViewModeCallback: (mode: string) => void): void {
- // Store the commit hash
- this.currentCommitHash = commitHash;
-
- // Switch to diff2 view (side-by-side)
- toggleViewModeCallback("diff2");
- }
-
- /**
- * Clean up resources when component is destroyed
- */
- public dispose(): void {
- // Clean up any resources or event listeners here
- // Currently there are no specific resources to clean up
- }
-}
diff --git a/loop/webui/src/timeline/icons/index.ts b/loop/webui/src/timeline/icons/index.ts
deleted file mode 100644
index d9480c5..0000000
--- a/loop/webui/src/timeline/icons/index.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-/**
- * Get the icon text to display for a message type
- * @param type - The message type
- * @returns The single character to represent this message type
- */
-export function getIconText(type: string | null | undefined): string {
- switch (type) {
- case "user":
- return "U";
- case "agent":
- return "A";
- case "tool":
- return "T";
- case "error":
- return "E";
- default:
- return "?";
- }
-}
diff --git a/loop/webui/src/timeline/index.ts b/loop/webui/src/timeline/index.ts
deleted file mode 100644
index a3d24b7..0000000
--- a/loop/webui/src/timeline/index.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-// Export types
-export * from './types';
-
-// Export utility functions
-export * from './utils';
-
-// Export terminal handler
-export * from './terminal';
-
-// Export diff viewer
-export * from './diffviewer';
-
-// Export chart manager
-export * from './charts';
-
-// Export tool call utilities
-export * from './toolcalls';
-
-// Export copy button utilities
-export * from './copybutton';
-
-// Re-export the timeline manager (will be implemented later)
-// For now, we'll maintain backward compatibility by importing from the original file
-import '../timeline';
diff --git a/loop/webui/src/timeline/markdown/renderer.ts b/loop/webui/src/timeline/markdown/renderer.ts
deleted file mode 100644
index 8199b69..0000000
--- a/loop/webui/src/timeline/markdown/renderer.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-import { marked } from "marked";
-
-/**
- * Renders markdown content as HTML with proper security handling.
- *
- * @param markdownContent - The markdown string to render
- * @returns The rendered HTML content as a string
- */
-export async function renderMarkdown(markdownContent: string): Promise<string> {
- try {
- // Set markdown options for proper code block highlighting and safety
- const markedOptions = {
- gfm: true, // GitHub Flavored Markdown
- breaks: true, // Convert newlines to <br>
- headerIds: false, // Disable header IDs for safety
- mangle: false, // Don't mangle email addresses
- // DOMPurify is recommended for production, but not included in this implementation
- };
-
- return await marked.parse(markdownContent, markedOptions);
- } catch (error) {
- console.error("Error rendering markdown:", error);
- // Fallback to plain text if markdown parsing fails
- return markdownContent;
- }
-}
-
-/**
- * Process rendered markdown HTML element, adding security attributes to links.
- *
- * @param element - The HTML element containing rendered markdown
- */
-export function processRenderedMarkdown(element: HTMLElement): void {
- // Make sure links open in a new tab and have proper security attributes
- const links = element.querySelectorAll("a");
- links.forEach((link) => {
- link.setAttribute("target", "_blank");
- link.setAttribute("rel", "noopener noreferrer");
- });
-}
diff --git a/loop/webui/src/timeline/renderer.ts b/loop/webui/src/timeline/renderer.ts
deleted file mode 100644
index f2770ee..0000000
--- a/loop/webui/src/timeline/renderer.ts
+++ /dev/null
@@ -1,729 +0,0 @@
-/**
- * MessageRenderer - Class to handle rendering of timeline messages
- */
-
-import { TimelineMessage, ToolCall } from "./types";
-import { escapeHTML, formatNumber, generateColorFromId } from "./utils";
-import { renderMarkdown, processRenderedMarkdown } from "./markdown/renderer";
-import { createToolCallCard, updateToolCallCard } from "./toolcalls";
-import { createCommitsContainer } from "./commits";
-import { createCopyButton } from "./copybutton";
-import { getIconText } from "./icons";
-import { addCollapsibleFunctionality } from "./components/collapsible";
-import { checkShouldScroll, scrollToBottom } from "./scroll";
-
-export class MessageRenderer {
- // Map to store references to agent message DOM elements by tool call ID
- private toolCallIdToMessageElement: Map<
- string,
- {
- messageEl: HTMLElement;
- toolCallContainer: HTMLElement | null;
- toolCardId: string;
- }
- > = new Map();
-
- // State tracking variables
- private isFirstLoad: boolean = true;
- private shouldScrollToBottom: boolean = true;
- private currentFetchStartIndex: number = 0;
-
- constructor() {}
-
- /**
- * Initialize the renderer with state from the timeline manager
- */
- public initialize(isFirstLoad: boolean, currentFetchStartIndex: number) {
- this.isFirstLoad = isFirstLoad;
- this.currentFetchStartIndex = currentFetchStartIndex;
- }
-
- /**
- * Renders the timeline with messages
- * @param messages The messages to render
- * @param clearExisting Whether to clear existing content before rendering
- */
- public renderTimeline(
- messages: TimelineMessage[],
- clearExisting: boolean = false,
- ): void {
- const timeline = document.getElementById("timeline");
- if (!timeline) return;
-
- // We'll keep the isFirstLoad value for this render cycle,
- // but will set it to false afterwards in scrollToBottom
-
- if (clearExisting) {
- timeline.innerHTML = ""; // Clear existing content only if this is the first load
- // Clear our map of tool call references
- this.toolCallIdToMessageElement.clear();
- }
-
- if (!messages || messages.length === 0) {
- if (clearExisting) {
- timeline.innerHTML = "<p>No messages available.</p>";
- timeline.classList.add("empty");
- }
- return;
- }
-
- // Remove empty class when there are messages
- timeline.classList.remove("empty");
-
- // Keep track of conversation groups to properly indent
- interface ConversationGroup {
- color: string;
- level: number;
- }
-
- const conversationGroups: Record<string, ConversationGroup> = {};
-
- // Use the currentFetchStartIndex as the base index for these messages
- const startIndex = this.currentFetchStartIndex;
- // Group tool messages with their parent agent messages
- const organizedMessages: (TimelineMessage & {
- toolResponses?: TimelineMessage[];
- })[] = [];
- const toolMessagesByCallId: Record<string, TimelineMessage> = {};
-
- // First, process tool messages - check if any can update existing UI elements
- const processedToolMessages = new Set<string>();
-
- messages.forEach((message) => {
- // If this is a tool message with a tool_call_id
- if (message.type === "tool" && message.tool_call_id) {
- // Try to find an existing agent message that's waiting for this tool response
- const toolCallRef = this.toolCallIdToMessageElement.get(
- message.tool_call_id,
- );
-
- if (toolCallRef) {
- // Found an existing agent message that needs updating
- this.updateToolCallInAgentMessage(message, toolCallRef);
- processedToolMessages.add(message.tool_call_id);
- } else {
- // No existing agent message found, we'll include this in normal rendering
- toolMessagesByCallId[message.tool_call_id] = message;
- }
- }
- });
-
- // Then, process messages and organize them
- messages.forEach((message, localIndex) => {
- const _index = startIndex + localIndex;
- if (!message) return; // Skip if message is null/undefined
-
- // If it's a tool message and we're going to inline it with its parent agent message,
- // we'll skip rendering it here - it will be included with the agent message
- if (message.type === "tool" && message.tool_call_id) {
- // Skip if we've already processed this tool message (updated an existing agent message)
- if (processedToolMessages.has(message.tool_call_id)) {
- return;
- }
-
- // Skip if this tool message will be included with a new agent message
- if (toolMessagesByCallId[message.tool_call_id]) {
- return;
- }
- }
-
- // For agent messages with tool calls, attach their tool responses
- if (
- message.type === "agent" &&
- message.tool_calls &&
- message.tool_calls.length > 0
- ) {
- const toolResponses: TimelineMessage[] = [];
-
- // Look up tool responses for each tool call
- message.tool_calls.forEach((toolCall) => {
- if (
- toolCall.tool_call_id &&
- toolMessagesByCallId[toolCall.tool_call_id]
- ) {
- toolResponses.push(toolMessagesByCallId[toolCall.tool_call_id]);
- }
- });
-
- if (toolResponses.length > 0) {
- message = { ...message, toolResponses };
- }
- }
-
- organizedMessages.push(message);
- });
-
- let lastMessage:TimelineMessage|undefined;
- if (messages && messages.length > 0 && startIndex > 0) {
- lastMessage = messages[startIndex-1];
- }
-
- // Loop through organized messages and create timeline items
- organizedMessages.forEach((message, localIndex) => {
- const _index = startIndex + localIndex;
- if (!message) return; // Skip if message is null/undefined
-
- if (localIndex > 0) {
- lastMessage = organizedMessages.at(localIndex-1);
- }
- // Determine if this is a subconversation
- const hasParent = !!message.parent_conversation_id;
- const conversationId = message.conversation_id || "";
- const _parentId = message.parent_conversation_id || "";
-
- // Track the conversation group
- if (conversationId && !conversationGroups[conversationId]) {
- conversationGroups[conversationId] = {
- color: generateColorFromId(conversationId),
- level: hasParent ? 1 : 0, // Level 0 for main conversation, 1+ for nested
- };
- }
-
- // Get the level and color for this message
- const group = conversationGroups[conversationId] || {
- level: 0,
- color: "#888888",
- };
-
- const messageEl = document.createElement("div");
- messageEl.className = `message ${message.type || "unknown"} ${message.end_of_turn ? "end-of-turn" : ""}`;
-
- // Add indentation class for subconversations
- if (hasParent) {
- messageEl.classList.add("subconversation");
- messageEl.style.marginLeft = `${group.level * 40}px`;
-
- // Add a colored left border to indicate the subconversation
- messageEl.style.borderLeft = `4px solid ${group.color}`;
- }
-
- // newMsgType indicates when to create a new icon and message
- // type header. This is a primitive form of message coalescing,
- // but it does reduce the amount of redundant information in
- // the UI.
- const newMsgType = !lastMessage ||
- (message.type == 'user' && lastMessage.type != 'user') ||
- (message.type != 'user' && lastMessage.type == 'user');
-
- if (newMsgType) {
- // Create message icon
- const iconEl = document.createElement("div");
- iconEl.className = "message-icon";
- iconEl.textContent = getIconText(message.type);
- messageEl.appendChild(iconEl);
- }
-
- // Create message content container
- const contentEl = document.createElement("div");
- contentEl.className = "message-content";
-
- // Create message header
- const headerEl = document.createElement("div");
- headerEl.className = "message-header";
-
- if (newMsgType) {
- const typeEl = document.createElement("span");
- typeEl.className = "message-type";
- typeEl.textContent = this.getTypeName(message.type);
- headerEl.appendChild(typeEl);
- }
-
- // Add timestamp and usage info combined for agent messages at the top
- if (message.timestamp) {
- const timestampEl = document.createElement("span");
- timestampEl.className = "message-timestamp";
- timestampEl.textContent = this.formatTimestamp(message.timestamp);
-
- // Add elapsed time if available
- if (message.elapsed) {
- timestampEl.textContent += ` (${(message.elapsed / 1e9).toFixed(2)}s)`;
- }
-
- // Add turn duration for end-of-turn messages
- if (message.turnDuration && message.end_of_turn) {
- timestampEl.textContent += ` [Turn: ${(message.turnDuration / 1e9).toFixed(2)}s]`;
- }
-
- // Add usage info inline for agent messages
- if (
- message.type === "agent" &&
- message.usage &&
- (message.usage.input_tokens > 0 ||
- message.usage.output_tokens > 0 ||
- message.usage.cost_usd > 0)
- ) {
- try {
- // Safe get all values
- const inputTokens = formatNumber(
- message.usage.input_tokens ?? 0,
- );
- const cacheInput = message.usage.cache_read_input_tokens ?? 0;
- const outputTokens = formatNumber(
- message.usage.output_tokens ?? 0,
- );
- const messageCost = this.formatCurrency(
- message.usage.cost_usd ?? 0,
- "$0.0000", // Default format for message costs
- true, // Use 4 decimal places for message-level costs
- );
-
- timestampEl.textContent += ` | In: ${inputTokens}`;
- if (cacheInput > 0) {
- timestampEl.textContent += ` [Cache: ${formatNumber(cacheInput)}]`;
- }
- timestampEl.textContent += ` Out: ${outputTokens} (${messageCost})`;
- } catch (e) {
- console.error("Error adding usage info to timestamp:", e);
- }
- }
-
- headerEl.appendChild(timestampEl);
- }
-
- contentEl.appendChild(headerEl);
-
- // Add message content
- if (message.content) {
- const containerEl = document.createElement("div");
- containerEl.className = "message-text-container";
-
- const textEl = document.createElement("div");
- textEl.className = "message-text markdown-content";
-
- // Render markdown content
- // Handle the Promise returned by renderMarkdown
- renderMarkdown(message.content).then(html => {
- textEl.innerHTML = html;
- processRenderedMarkdown(textEl);
- });
-
- // Add copy button
- const { container: copyButtonContainer, button: copyButton } = createCopyButton(message.content);
- containerEl.appendChild(copyButtonContainer);
- containerEl.appendChild(textEl);
-
- // Add collapse/expand for long content
- addCollapsibleFunctionality(message, textEl, containerEl, contentEl);
- }
-
- // If the message has tool calls, show them in an ultra-compact row of boxes
- if (message.tool_calls && message.tool_calls.length > 0) {
- const toolCallsContainer = document.createElement("div");
- toolCallsContainer.className = "tool-calls-container";
-
- // Create a header row with tool count
- const toolCallsHeaderRow = document.createElement("div");
- toolCallsHeaderRow.className = "tool-calls-header";
- // No header text - empty header
- toolCallsContainer.appendChild(toolCallsHeaderRow);
-
- // Create a container for the tool call cards
- const toolCallsCardContainer = document.createElement("div");
- toolCallsCardContainer.className = "tool-call-cards-container";
-
- // Add each tool call as a card with response or spinner
- message.tool_calls.forEach((toolCall: ToolCall, _index: number) => {
- // Create a unique ID for this tool card
- const toolCardId = `tool-card-${toolCall.tool_call_id || Math.random().toString(36).substring(2, 11)}`;
-
- // Find the matching tool response if it exists
- const toolResponse = message.toolResponses?.find(
- (resp) => resp.tool_call_id === toolCall.tool_call_id,
- );
-
- // Use the extracted utility function to create the tool card
- const toolCard = createToolCallCard(toolCall, toolResponse, toolCardId);
-
- // Store reference to this element if it has a tool_call_id
- if (toolCall.tool_call_id) {
- this.toolCallIdToMessageElement.set(toolCall.tool_call_id, {
- messageEl,
- toolCallContainer: toolCallsCardContainer,
- toolCardId,
- });
- }
-
- // Add the card to the container
- toolCallsCardContainer.appendChild(toolCard);
- });
-
- toolCallsContainer.appendChild(toolCallsCardContainer);
- contentEl.appendChild(toolCallsContainer);
- }
- // If message is a commit message, display commits
- if (
- message.type === "commit" &&
- message.commits &&
- message.commits.length > 0
- ) {
- // Use the extracted utility function to create the commits container
- const commitsContainer = createCommitsContainer(
- message.commits,
- (commitHash) => {
- // This will need to be handled by the TimelineManager
- const event = new CustomEvent('showCommitDiff', {
- detail: { commitHash }
- });
- document.dispatchEvent(event);
- }
- );
- contentEl.appendChild(commitsContainer);
- }
-
- // Tool messages are now handled inline with agent messages
- // If we still see a tool message here, it means it's not associated with an agent message
- // (this could be legacy data or a special case)
- if (message.type === "tool") {
- const toolDetailsEl = document.createElement("div");
- toolDetailsEl.className = "tool-details standalone";
-
- // Get tool input and result for display
- let inputText = "";
- try {
- if (message.input) {
- const parsedInput = JSON.parse(message.input);
- // Format input compactly for simple inputs
- inputText = JSON.stringify(parsedInput);
- }
- } catch (e) {
- // Not valid JSON, use as-is
- inputText = message.input || "";
- }
-
- const resultText = message.tool_result || "";
- const statusEmoji = message.tool_error ? "❌" : "✅";
- const toolName = message.tool_name || "Unknown";
-
- // Determine if we can use super compact display (e.g., for bash command results)
- // Use compact display for short inputs/outputs without newlines
- const isSimpleCommand =
- toolName === "bash" &&
- inputText.length < 50 &&
- resultText.length < 200 &&
- !resultText.includes("\n");
- const isCompact =
- inputText.length < 50 &&
- resultText.length < 100 &&
- !resultText.includes("\n");
-
- if (isSimpleCommand) {
- // SUPER COMPACT VIEW FOR BASH: Display everything on a single line
- const toolLineEl = document.createElement("div");
- toolLineEl.className = "tool-compact-line";
-
- // Create the compact bash display in format: "✅ bash({command}) → result"
- try {
- const parsed = JSON.parse(inputText);
- const cmd = parsed.command || "";
- toolLineEl.innerHTML = `${statusEmoji} <strong>${toolName}</strong>({"command":"${cmd}"}) → <span class="tool-result-inline">${resultText}</span>`;
- } catch {
- toolLineEl.innerHTML = `${statusEmoji} <strong>${toolName}</strong>(${inputText}) → <span class="tool-result-inline">${resultText}</span>`;
- }
-
- // Add copy button for result
- const copyBtn = document.createElement("button");
- copyBtn.className = "copy-inline-button";
- copyBtn.textContent = "Copy";
- copyBtn.title = "Copy result to clipboard";
-
- copyBtn.addEventListener("click", (e) => {
- e.stopPropagation();
- navigator.clipboard
- .writeText(resultText)
- .then(() => {
- copyBtn.textContent = "Copied!";
- setTimeout(() => {
- copyBtn.textContent = "Copy";
- }, 2000);
- })
- .catch((_err) => {
- copyBtn.textContent = "Failed";
- setTimeout(() => {
- copyBtn.textContent = "Copy";
- }, 2000);
- });
- });
-
- toolLineEl.appendChild(copyBtn);
- toolDetailsEl.appendChild(toolLineEl);
- } else if (isCompact && !isSimpleCommand) {
- // COMPACT VIEW: Display everything on one or two lines for other tool types
- const toolLineEl = document.createElement("div");
- toolLineEl.className = "tool-compact-line";
-
- // Create the compact display in format: "✅ tool_name(input) → result"
- let compactDisplay = `${statusEmoji} <strong>${toolName}</strong>(${inputText})`;
-
- if (resultText) {
- compactDisplay += ` → <span class="tool-result-inline">${resultText}</span>`;
- }
-
- toolLineEl.innerHTML = compactDisplay;
-
- // Add copy button for result
- const copyBtn = document.createElement("button");
- copyBtn.className = "copy-inline-button";
- copyBtn.textContent = "Copy";
- copyBtn.title = "Copy result to clipboard";
-
- copyBtn.addEventListener("click", (e) => {
- e.stopPropagation();
- navigator.clipboard
- .writeText(resultText)
- .then(() => {
- copyBtn.textContent = "Copied!";
- setTimeout(() => {
- copyBtn.textContent = "Copy";
- }, 2000);
- })
- .catch((_err) => {
- copyBtn.textContent = "Failed";
- setTimeout(() => {
- copyBtn.textContent = "Copy";
- }, 2000);
- });
- });
-
- toolLineEl.appendChild(copyBtn);
- toolDetailsEl.appendChild(toolLineEl);
- } else {
- // EXPANDED VIEW: For longer inputs/results that need more space
- // Tool name header
- const toolNameEl = document.createElement("div");
- toolNameEl.className = "tool-name";
- toolNameEl.innerHTML = `${statusEmoji} <strong>${toolName}</strong>`;
- toolDetailsEl.appendChild(toolNameEl);
-
- // Show input (simplified)
- if (message.input) {
- const inputContainer = document.createElement("div");
- inputContainer.className = "tool-input-container compact";
-
- const inputEl = document.createElement("pre");
- inputEl.className = "tool-input compact";
- inputEl.textContent = inputText;
- inputContainer.appendChild(inputEl);
- toolDetailsEl.appendChild(inputContainer);
- }
-
- // Show result (simplified)
- if (resultText) {
- const resultContainer = document.createElement("div");
- resultContainer.className = "tool-result-container compact";
-
- const resultEl = document.createElement("pre");
- resultEl.className = "tool-result compact";
- resultEl.textContent = resultText;
- resultContainer.appendChild(resultEl);
-
- // Add collapse/expand for longer results
- if (resultText.length > 100) {
- resultEl.classList.add("collapsed");
-
- const toggleButton = document.createElement("button");
- toggleButton.className = "collapsible";
- toggleButton.textContent = "Show more...";
- toggleButton.addEventListener("click", () => {
- resultEl.classList.toggle("collapsed");
- toggleButton.textContent = resultEl.classList.contains(
- "collapsed",
- )
- ? "Show more..."
- : "Show less";
- });
-
- toolDetailsEl.appendChild(resultContainer);
- toolDetailsEl.appendChild(toggleButton);
- } else {
- toolDetailsEl.appendChild(resultContainer);
- }
- }
- }
-
- contentEl.appendChild(toolDetailsEl);
- }
-
- // Add usage info if available with robust null handling - only for non-agent messages
- if (
- message.type !== "agent" && // Skip for agent messages as we've already added usage info at the top
- message.usage &&
- (message.usage.input_tokens > 0 ||
- message.usage.output_tokens > 0 ||
- message.usage.cost_usd > 0)
- ) {
- try {
- const usageEl = document.createElement("div");
- usageEl.className = "usage-info";
-
- // Safe get all values
- const inputTokens = formatNumber(
- message.usage.input_tokens ?? 0,
- );
- const cacheInput = message.usage.cache_read_input_tokens ?? 0;
- const outputTokens = formatNumber(
- message.usage.output_tokens ?? 0,
- );
- const messageCost = this.formatCurrency(
- message.usage.cost_usd ?? 0,
- "$0.0000", // Default format for message costs
- true, // Use 4 decimal places for message-level costs
- );
-
- // Create usage info display
- usageEl.innerHTML = `
- <span title="Input tokens">In: ${inputTokens}</span>
- ${cacheInput > 0 ? `<span title="Cache tokens">[Cache: ${formatNumber(cacheInput)}]</span>` : ""}
- <span title="Output tokens">Out: ${outputTokens}</span>
- <span title="Message cost">(${messageCost})</span>
- `;
-
- contentEl.appendChild(usageEl);
- } catch (e) {
- console.error("Error rendering usage info:", e);
- }
- }
-
- messageEl.appendChild(contentEl);
- timeline.appendChild(messageEl);
- });
-
- // Scroll to bottom of the timeline if needed
- this.scrollToBottom();
- }
-
- /**
- * Check if we should scroll to the bottom
- */
- private checkShouldScroll(): boolean {
- return checkShouldScroll(this.isFirstLoad);
- }
-
- /**
- * Scroll to the bottom of the timeline
- */
- private scrollToBottom(): void {
- scrollToBottom(this.shouldScrollToBottom);
-
- // After first load, we'll only auto-scroll if user is already near the bottom
- this.isFirstLoad = false;
- }
-
- /**
- * Get readable name for message type
- */
- private getTypeName(type: string | null | undefined): string {
- switch (type) {
- case "user":
- return "User";
- case "agent":
- return "Agent";
- case "tool":
- return "Tool Use";
- case "error":
- return "Error";
- default:
- return (
- (type || "Unknown").charAt(0).toUpperCase() +
- (type || "unknown").slice(1)
- );
- }
- }
-
- /**
- * Format timestamp for display
- */
- private 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;
- }
- }
-
- /**
- * Format currency values
- */
- private 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;
- }
- }
-
- /**
- * Update a tool call in an agent message with the response
- */
- private updateToolCallInAgentMessage(
- toolMessage: TimelineMessage,
- toolCallRef: {
- messageEl: HTMLElement;
- toolCallContainer: HTMLElement | null;
- toolCardId: string;
- },
- ): void {
- const { messageEl, toolCardId } = toolCallRef;
-
- // Find the tool card element
- const toolCard = messageEl.querySelector(`#${toolCardId}`) as HTMLElement;
- if (!toolCard) return;
-
- // Use the extracted utility function to update the tool card
- updateToolCallCard(toolCard, toolMessage);
- }
-
- /**
- * Get the tool call id to message element map
- * Used by the TimelineManager to access the map
- */
- public getToolCallIdToMessageElement(): Map<
- string,
- {
- messageEl: HTMLElement;
- toolCallContainer: HTMLElement | null;
- toolCardId: string;
- }
- > {
- return this.toolCallIdToMessageElement;
- }
-
- /**
- * Set the tool call id to message element map
- * Used by the TimelineManager to update the map
- */
- public setToolCallIdToMessageElement(
- map: Map<
- string,
- {
- messageEl: HTMLElement;
- toolCallContainer: HTMLElement | null;
- toolCardId: string;
- }
- >
- ): void {
- this.toolCallIdToMessageElement = map;
- }
-}
diff --git a/loop/webui/src/timeline/scroll.ts b/loop/webui/src/timeline/scroll.ts
deleted file mode 100644
index df3b8f9..0000000
--- a/loop/webui/src/timeline/scroll.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-/**
- * Check if the page should scroll to the bottom based on current view position
- * @param isFirstLoad If this is the first load of the timeline
- * @returns Boolean indicating if we should scroll to the bottom
- */
-export function checkShouldScroll(isFirstLoad: boolean): boolean {
- // Always scroll on first load
- if (isFirstLoad) {
- return true;
- }
-
- // Check if user is already near the bottom of the page
- // Account for the fixed top bar and chat bar
- return (
- window.innerHeight + window.scrollY >= document.body.offsetHeight - 200
- );
-}
-
-/**
- * Scroll to the bottom of the timeline if shouldScrollToBottom is true
- * @param shouldScrollToBottom Flag indicating if we should scroll
- */
-export function scrollToBottom(shouldScrollToBottom: boolean): void {
- // Find the timeline container
- const timeline = document.getElementById("timeline");
-
- // Scroll the window to the bottom based on our pre-determined value
- if (timeline && shouldScrollToBottom) {
- // Get the last message or element in the timeline
- const lastElement = timeline.lastElementChild;
-
- if (lastElement) {
- // Scroll to the bottom of the page
- window.scrollTo({
- top: document.body.scrollHeight,
- behavior: "smooth",
- });
- }
- }
-}
diff --git a/loop/webui/src/timeline/toolcalls.ts b/loop/webui/src/timeline/toolcalls.ts
deleted file mode 100644
index 5df88bd..0000000
--- a/loop/webui/src/timeline/toolcalls.ts
+++ /dev/null
@@ -1,259 +0,0 @@
-/**
- * Utility functions for rendering tool calls in the timeline
- */
-
-import { ToolCall, TimelineMessage } from "./types";
-import { html, render } from "lit-html";
-
-/**
- * Create a tool call card element for display in the timeline
- * @param toolCall The tool call data to render
- * @param toolResponse Optional tool response message if available
- * @param toolCardId Unique ID for this tool card
- * @returns The created tool card element
- */
-export function createToolCallCard(
- toolCall: ToolCall,
- toolResponse?: TimelineMessage | null,
- toolCardId?: string
-): HTMLElement {
- // Create a unique ID for this tool card if not provided
- const cardId =
- toolCardId ||
- `tool-card-${
- toolCall.tool_call_id || Math.random().toString(36).substring(2, 11)
- }`;
-
- // Get input as compact string
- let inputText = "";
- try {
- if (toolCall.input) {
- const parsedInput = JSON.parse(toolCall.input);
-
- // For bash commands, use a special format
- if (toolCall.name === "bash" && parsedInput.command) {
- inputText = parsedInput.command;
- } else {
- // For other tools, use the stringified JSON
- inputText = JSON.stringify(parsedInput);
- }
- }
- } catch (e) {
- // Not valid JSON, use as-is
- inputText = toolCall.input || "";
- }
-
- // Truncate input text for display
- const displayInput =
- inputText.length > 80 ? inputText.substring(0, 78) + "..." : inputText;
-
- // Truncate for compact display
- const shortInput =
- displayInput.length > 30
- ? displayInput.substring(0, 28) + "..."
- : displayInput;
-
- // Format input for expanded view
- let formattedInput = displayInput;
- try {
- const parsedInput = JSON.parse(toolCall.input || "");
- formattedInput = JSON.stringify(parsedInput, null, 2);
- } catch (e) {
- // Not valid JSON, use display input as-is
- }
-
- // Truncate result for compact display if available
- let shortResult = "";
- if (toolResponse && toolResponse.tool_result) {
- shortResult =
- toolResponse.tool_result.length > 40
- ? toolResponse.tool_result.substring(0, 38) + "..."
- : toolResponse.tool_result;
- }
-
- // State for collapsed/expanded view
- let isCollapsed = true;
-
- // Handler to copy text to clipboard
- const copyToClipboard = (text: string, button: HTMLElement) => {
- navigator.clipboard
- .writeText(text)
- .then(() => {
- button.textContent = "Copied!";
- setTimeout(() => {
- button.textContent = "Copy";
- }, 2000);
- })
- .catch((err) => {
- console.error("Failed to copy text:", err);
- button.textContent = "Failed";
- setTimeout(() => {
- button.textContent = "Copy";
- }, 2000);
- });
- };
-
- const 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" }),
- });
- console.log('cancel', tool_call_id, response);
- button.parentElement.removeChild(button);
- } catch (e) {
- console.error('cancel', tool_call_id,e);
- }
- };
-
- // Create the container element
- const container = document.createElement("div");
- container.id = cardId;
- container.className = "tool-call-card collapsed";
-
- // Function to render the component
- const renderComponent = () => {
- const template = html`
- <div
- class="tool-call-compact-view"
- @click=${() => {
- isCollapsed = !isCollapsed;
- container.classList.toggle("collapsed");
- renderComponent();
- }}
- >
- <span class="tool-call-status ${toolResponse ? "" : "spinner"}">
- ${toolResponse ? (toolResponse.tool_error ? "❌" : "✅") : "⏳"}
- </span>
- <span class="tool-call-name">${toolCall.name}</span>
- <code class="tool-call-input-preview">${shortInput}</code>
- ${toolResponse && toolResponse.tool_result
- ? html`<code class="tool-call-result-preview">${shortResult}</code>`
- : ""}
- ${toolResponse && toolResponse.elapsed !== undefined
- ? html`<span class="tool-call-time"
- >${(toolResponse.elapsed / 1e9).toFixed(2)}s</span
- >`
- : ""}
- ${toolResponse ? "" :
- html`<button class="refresh-button stop-button" title="Cancel this operation" @click=${(e: Event) => {
- e.stopPropagation(); // Don't toggle expansion when clicking cancel
- const button = e.target as HTMLButtonElement;
- cancelToolCall(toolCall.tool_call_id, button);
- }}>Cancel</button>`}
- <span class="tool-call-expand-icon">${isCollapsed ? "▼" : "▲"}</span>
- </div>
-
- <div class="tool-call-expanded-view">
- <div class="tool-call-section">
- <div class="tool-call-section-label">
- Input:
- <button
- class="tool-call-copy-btn"
- title="Copy input to clipboard"
- @click=${(e: Event) => {
- e.stopPropagation(); // Don't toggle expansion when clicking copy
- const button = e.target as HTMLElement;
- copyToClipboard(toolCall.input || displayInput, button);
- }}
- >
- Copy
- </button>
- </div>
- <div class="tool-call-section-content">
- <pre class="tool-call-input">${formattedInput}</pre>
- </div>
- </div>
-
- ${toolResponse && toolResponse.tool_result
- ? html`
- <div class="tool-call-section">
- <div class="tool-call-section-label">
- Result:
- <button
- class="tool-call-copy-btn"
- title="Copy result to clipboard"
- @click=${(e: Event) => {
- e.stopPropagation(); // Don't toggle expansion when clicking copy
- const button = e.target as HTMLElement;
- copyToClipboard(toolResponse.tool_result || "", button);
- }}
- >
- Copy
- </button>
- </div>
- <div class="tool-call-section-content">
- <div class="tool-call-result">
- ${toolResponse.tool_result.includes("\n")
- ? html`<pre><code>${toolResponse.tool_result}</code></pre>`
- : toolResponse.tool_result}
- </div>
- </div>
- </div>
- `
- : ""}
- </div>
- `;
-
- render(template, container);
- };
-
- // Initial render
- renderComponent();
-
- return container;
-}
-
-/**
- * Update a tool call card with response data
- * @param toolCard The tool card element to update
- * @param toolMessage The tool response message
- */
-export function updateToolCallCard(
- toolCard: HTMLElement,
- toolMessage: TimelineMessage
-): void {
- if (!toolCard) return;
-
- // Find the original tool call data to reconstruct the card
- const toolName = toolCard.querySelector(".tool-call-name")?.textContent || "";
- const inputPreview =
- toolCard.querySelector(".tool-call-input-preview")?.textContent || "";
-
- // Extract the original input from the expanded view
- let originalInput = "";
- const inputEl = toolCard.querySelector(".tool-call-input");
- if (inputEl) {
- originalInput = inputEl.textContent || "";
- }
-
- // Create a minimal ToolCall object from the existing data
- const toolCall: Partial<ToolCall> = {
- name: toolName,
- // Try to reconstruct the original input if possible
- input: originalInput,
- };
-
- // Replace the existing card with a new one
- const newCard = createToolCallCard(
- toolCall as ToolCall,
- toolMessage,
- toolCard.id
- );
-
- // Preserve the collapse state
- if (!toolCard.classList.contains("collapsed")) {
- newCard.classList.remove("collapsed");
- }
-
- // Replace the old card with the new one
- if (toolCard.parentNode) {
- toolCard.parentNode.replaceChild(newCard, toolCard);
- }
-}
diff --git a/loop/webui/src/timeline/types.ts b/loop/webui/src/timeline/types.ts
deleted file mode 100644
index 81d47d0..0000000
--- a/loop/webui/src/timeline/types.ts
+++ /dev/null
@@ -1,49 +0,0 @@
-/**
- * Interface for a Git commit
- */
-export interface GitCommit {
- hash: string; // Full commit hash
- subject: string; // Commit subject line
- body: string; // Full commit message body
- pushed_branch?: string; // If set, this commit was pushed to this branch
-}
-
-/**
- * Interface for a tool call
- */
-export interface ToolCall {
- name: string;
- args?: string;
- result?: string;
- input?: string; // Input property for TypeScript compatibility
- tool_call_id?: string;
-}
-
-/**
- * Interface for a timeline message
- */
-export interface TimelineMessage {
- type: string;
- content?: string;
- timestamp?: string | number | Date;
- elapsed?: number;
- turnDuration?: number; // Turn duration field
- end_of_turn?: boolean;
- conversation_id?: string;
- parent_conversation_id?: string;
- tool_calls?: ToolCall[];
- tool_name?: string;
- tool_error?: boolean;
- tool_call_id?: string;
- commits?: GitCommit[]; // For commit messages
- input?: string; // Input property
- tool_result?: string; // Tool result property
- toolResponses?: any[]; // Tool responses array
- 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/types.ts b/loop/webui/src/types.ts
new file mode 100644
index 0000000..f91c9af
--- /dev/null
+++ b/loop/webui/src/types.ts
@@ -0,0 +1,71 @@
+// TODO: generate these interface type declarations from the go structs instead of doing it by hand.
+// See https://github.com/boldsoftware/bold/blob/c6670a0a13f9d25785c8c1a90587fbab20a58bdd/sketch/types/ts.go for an example.
+
+/**
+ * Interface for a Git commit
+ */
+export interface GitCommit {
+ hash: string; // Full commit hash
+ subject: string; // Commit subject line
+ body: string; // Full commit message body
+ pushed_branch?: string; // If set, this commit was pushed to this branch
+}
+
+/**
+ * Interface for a tool call
+ */
+export interface ToolCall {
+ name: string;
+ args?: string;
+ result?: string;
+ input?: string; // Input property for TypeScript compatibility
+ tool_call_id?: string;
+ result_message?: TimelineMessage;
+}
+
+/**
+ * Interface for a timeline message
+ */
+export interface TimelineMessage {
+ idx: number;
+ type: string;
+ content?: string;
+ timestamp?: string | number | Date;
+ elapsed?: number;
+ turnDuration?: number; // Turn duration field
+ end_of_turn?: boolean;
+ conversation_id?: string;
+ parent_conversation_id?: string;
+ start_time?: string;
+ end_time?: string;
+ tool_calls?: ToolCall[];
+ tool_name?: string;
+ tool_error?: boolean;
+ tool_call_id?: string;
+ commits?: GitCommit[]; // For commit messages
+ input?: string; // Input property
+ tool_result?: string; // Tool result property
+ toolResponses?: any[]; // Tool responses array
+ usage?: Usage;
+}
+
+export interface Usage {
+ start_time?: string;
+ messages?: number;
+ input_tokens?: number;
+ output_tokens?: number;
+ cache_read_input_tokens?: number;
+ cache_creation_input_tokens?: number;
+ cost_usd?: number;
+ total_cost_usd?: number;
+ tool_uses?: Map<string, any>;
+}
+export interface State {
+ hostname?: string;
+ initial_commit?: string;
+ message_count?: number;
+ os: string;
+ title: string;
+ total_usage: Usage; // TODO Make a TotalUseage interface.
+ working_dir?: string;
+}
diff --git a/loop/webui/src/timeline/utils.ts b/loop/webui/src/utils.ts
similarity index 100%
rename from loop/webui/src/timeline/utils.ts
rename to loop/webui/src/utils.ts
diff --git a/loop/webui/src/web-components/demo/demo.css b/loop/webui/src/web-components/demo/demo.css
new file mode 100644
index 0000000..bb9750e
--- /dev/null
+++ b/loop/webui/src/web-components/demo/demo.css
@@ -0,0 +1,9 @@
+body {
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
+ Roboto, sans-serif;
+ margin: 0;
+ padding: 20px;
+ padding-bottom: 100px; /* Adjusted padding for chat container */
+ color: #333;
+ line-height: 1.4; /* Reduced line height for more compact text */
+}
\ No newline at end of file
diff --git a/loop/webui/src/web-components/demo/index.html b/loop/webui/src/web-components/demo/index.html
new file mode 100644
index 0000000..3f728c0
--- /dev/null
+++ b/loop/webui/src/web-components/demo/index.html
@@ -0,0 +1,28 @@
+<html>
+ <head>
+ <link rel="stylesheet" href="demo.css" />
+ </head>
+ <body>
+ sketch web-components demo index
+ <ul>
+ <li><a href="sketch-app-shell.demo.html">sketch-app-shell</a></li>
+ <li><a href="sketch-charts.demo.html">sketch-charts</a></li>
+ <li><a href="sketch-chat-input.demo.html">sketch-chat-input</a></li>
+ <li><a href="sketch-diff-view.demo.html">sketch-diff-view</a></li>
+ <li>
+ <a href="sketch-container-status.demo.html">sketch-container-status</a>
+ </li>
+ <li>
+ <a href="sketch-network-status.demo.html">sketch-network-status</a>
+ </li>
+ <li>
+ <a href="sketch-timeline-message.demo.html">sketch-timeline-message</a>
+ </li>
+ <li><a href="sketch-timeline.demo.html">sketch-timeline</a></li>
+ <li><a href="sketch-tool-calls.demo.html">sketch-tool-calls</a></li>
+ <li>
+ <a href="sketch-view-mode-select.demo.html">sketch-view-mode-select</a>
+ </li>
+ </ul>
+ </body>
+</html>
diff --git a/loop/webui/src/web-components/demo/readme.md b/loop/webui/src/web-components/demo/readme.md
new file mode 100644
index 0000000..8e3c33c
--- /dev/null
+++ b/loop/webui/src/web-components/demo/readme.md
@@ -0,0 +1,14 @@
+# Stand-alone demo pages for sketch web components
+
+These are handy for iterating on specific component UI issues in isolation from the rest of the sketch application, and without having to start a full backend to serve the full frontend app UI.
+
+# How to use this demo directory to iterate on component development
+
+From the `loop/webui` directory:
+
+1. In one shell, run `npm run watch` to build the web components and watch for changes
+1. In another shell, run `npm run demo` to start a local web server to serve the demo pages
+1. open http://localhost:8000/src/web-components/demo/ in your browser
+1. make edits to the .ts code or to the demo.html files and see how it affects the demo pages in real time
+
+Alternately, use the `webui: watch demo` task in VSCode, which runs all of the above for you.
diff --git a/loop/webui/src/web-components/demo/sketch-app-shell.demo.html b/loop/webui/src/web-components/demo/sketch-app-shell.demo.html
new file mode 100644
index 0000000..092ad7c
--- /dev/null
+++ b/loop/webui/src/web-components/demo/sketch-app-shell.demo.html
@@ -0,0 +1,13 @@
+<html>
+ <head>
+ <title>sketch-app-shell demo</title>
+ <link rel="stylesheet" href="demo.css" />
+ <script src="/dist/web-components/sketch-app-shell.js" type="module"></script>
+ </head>
+ <body>
+ <h1>sketch-app-shell demo</h1>
+
+ <sketch-app-shell></sketch-app-shell>
+
+ </body>
+</html>
\ No newline at end of file
diff --git a/loop/webui/src/web-components/demo/sketch-charts.demo.html b/loop/webui/src/web-components/demo/sketch-charts.demo.html
new file mode 100644
index 0000000..b525785
--- /dev/null
+++ b/loop/webui/src/web-components/demo/sketch-charts.demo.html
@@ -0,0 +1,129 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8" />
+ <title>Sketch Charts Demo</title>
+ <script type="module" src="/dist/web-components/sketch-charts.js"></script>
+ <link rel="stylesheet" href="demo.css" />
+ <style>
+ sketch-charts {
+ margin: 20px;
+ max-width: 1000px;
+ }
+
+ body {
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
+ Roboto, sans-serif;
+ }
+ </style>
+ </head>
+ <body>
+ <h1>Sketch Charts Demo</h1>
+ <sketch-charts id="charts"></sketch-charts>
+
+ <script>
+ // Sample data for testing
+ const sampleMessages = [
+ {
+ idx: 1,
+ type: "human",
+ content: "Hello, can you help me with a coding task?",
+ timestamp: new Date(Date.now() - 3600000).toISOString(),
+ usage: { cost_usd: 0.0001 },
+ },
+ {
+ idx: 2,
+ type: "assistant",
+ content:
+ "I'd be happy to help! What kind of coding task are you working on?",
+ timestamp: new Date(Date.now() - 3500000).toISOString(),
+ usage: { cost_usd: 0.0005 },
+ },
+ {
+ idx: 3,
+ type: "human",
+ content: "I need to create a web component using lit-element",
+ timestamp: new Date(Date.now() - 3400000).toISOString(),
+ usage: { cost_usd: 0.0001 },
+ },
+ {
+ idx: 4,
+ type: "assistant",
+ content:
+ "I can definitely help with that. Lit Element is a great library for building web components.",
+ timestamp: new Date(Date.now() - 3300000).toISOString(),
+ usage: { cost_usd: 0.0008 },
+ },
+ {
+ idx: 5,
+ type: "assistant",
+ tool_name: "bash",
+ input: "ls -la",
+ tool_result:
+ "total 16\ndrwxr-xr-x 4 user staff 128 Jan 10 12:34 .\ndrwxr-xr-x 10 user staff 320 Jan 10 12:34 ..\n-rw-r--r-- 1 user staff 123 Jan 10 12:34 file1.txt\n-rw-r--r-- 1 user staff 456 Jan 10 12:34 file2.txt",
+ start_time: new Date(Date.now() - 3200000).toISOString(),
+ end_time: new Date(Date.now() - 3190000).toISOString(),
+ timestamp: new Date(Date.now() - 3190000).toISOString(),
+ usage: { cost_usd: 0.0002 },
+ },
+ {
+ idx: 6,
+ type: "assistant",
+ content: "Let me create a basic web component for you.",
+ timestamp: new Date(Date.now() - 3100000).toISOString(),
+ usage: { cost_usd: 0.0015 },
+ },
+ {
+ idx: 7,
+ type: "human",
+ content: "Can you show me how to handle events in the web component?",
+ timestamp: new Date(Date.now() - 3000000).toISOString(),
+ usage: { cost_usd: 0.0001 },
+ },
+ {
+ idx: 8,
+ type: "assistant",
+ tool_name: "bash",
+ input: "cat example.ts",
+ tool_result:
+ "import { LitElement, html } from 'lit';\nimport { customElement } from 'lit/decorators.js';\n\n@customElement('my-element')\nexport class MyElement extends LitElement {\n render() {\n return html`<div>Hello World</div>`;\n }\n}",
+ start_time: new Date(Date.now() - 2900000).toISOString(),
+ end_time: new Date(Date.now() - 2800000).toISOString(),
+ timestamp: new Date(Date.now() - 2800000).toISOString(),
+ usage: { cost_usd: 0.0003 },
+ },
+ {
+ idx: 9,
+ type: "assistant",
+ content:
+ "Here's how you can handle events in a web component using Lit.",
+ timestamp: new Date(Date.now() - 2700000).toISOString(),
+ usage: { cost_usd: 0.002 },
+ },
+ {
+ idx: 10,
+ type: "human",
+ content: "Thank you! How about adding properties and attributes?",
+ timestamp: new Date(Date.now() - 2600000).toISOString(),
+ usage: { cost_usd: 0.0001 },
+ },
+ {
+ idx: 11,
+ type: "assistant",
+ content:
+ "You can use the @property decorator to define properties in your Lit Element component.",
+ timestamp: new Date(Date.now() - 2500000).toISOString(),
+ usage: { cost_usd: 0.0025 },
+ },
+ ];
+
+ // Set sample data as soon as the component is defined
+ document.addEventListener("DOMContentLoaded", () => {
+ console.time("chart-demo-load");
+ const chartsComponent = document.getElementById("charts");
+ chartsComponent.messages = sampleMessages;
+ console.timeEnd("chart-demo-load");
+ });
+ </script>
+ </body>
+</html>
diff --git a/loop/webui/src/web-components/demo/sketch-chat-input.demo.html b/loop/webui/src/web-components/demo/sketch-chat-input.demo.html
new file mode 100644
index 0000000..4806035
--- /dev/null
+++ b/loop/webui/src/web-components/demo/sketch-chat-input.demo.html
@@ -0,0 +1,32 @@
+<html>
+ <head>
+ <title>sketch-chat-input demo</title>
+ <link rel="stylesheet" href="demo.css" />
+ <script src="/dist/web-components/sketch-chat-input.js" type="module"></script>
+
+ <script>
+ document.addEventListener("DOMContentLoaded", () => {
+ const chatInput = document.querySelector('sketch-chat-input');
+ console.log("chatInput: ", chatInput);
+ chatInput.content = "hi";
+ chatInput.addEventListener("send-chat", (evt) => {
+ console.log('send chat event: ', evt);
+ const msgDiv = document.querySelector("#chat-messages");
+ const newDiv = document.createElement("div");
+ newDiv.innerText = evt.detail.message;
+ msgDiv.append(newDiv);
+ chatInput.content = '';
+ });
+ });
+ </script>
+
+ </head>
+ <body>
+ <h1>sketch-chat-input demo</h1>
+
+ <div id="chat-messages"></div>
+
+ <sketch-chat-input></sketch-chat-input>
+
+ </body>
+</html>
\ No newline at end of file
diff --git a/loop/webui/src/web-components/demo/sketch-container-status.demo.html b/loop/webui/src/web-components/demo/sketch-container-status.demo.html
new file mode 100644
index 0000000..bd2544d
--- /dev/null
+++ b/loop/webui/src/web-components/demo/sketch-container-status.demo.html
@@ -0,0 +1,40 @@
+<html>
+ <head>
+ <title>sketch-container-status demo</title>
+ <link rel="stylesheet" href="demo.css" />
+ <script src="/dist/web-components/sketch-container-status.js" type="module"></script>
+
+ <script>
+ document.addEventListener("DOMContentLoaded", () => {
+ const containerStatus = document.querySelector('#status-2');
+ containerStatus.state = {
+ hostname: 'example.hostname',
+ initial_commit: 'decafbad',
+ message_count: 27,
+ os: 'linux',
+ total_usage: {
+ start_time: 'around lunch',
+ messages:1337,
+ input_tokens: 3,
+ output_tokens: 1000,
+ cache_read_input_tokens: 28,
+ cache_creation_input_tokens: 12354,
+ total_cost_usd: 2.03,
+ },
+ working_dir: '/app',
+ };
+ });
+ </script>
+
+ </head>
+ <body>
+ <h1>sketch-container-status demo</h1>
+
+ Empty:
+ <sketch-container-status id="status-1"></sketch-container-status>
+
+ With state fields set:
+ <sketch-container-status id="status-2"></sketch-container-status>
+
+ </body>
+</html>
\ No newline at end of file
diff --git a/loop/webui/src/web-components/demo/sketch-diff-view.demo.html b/loop/webui/src/web-components/demo/sketch-diff-view.demo.html
new file mode 100644
index 0000000..63b5395
--- /dev/null
+++ b/loop/webui/src/web-components/demo/sketch-diff-view.demo.html
@@ -0,0 +1,104 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <title>Sketch Diff Viewer Demo</title>
+ <link rel="stylesheet" href="../../../node_modules/diff2html/bundles/css/diff2html.min.css">
+ <script type="module" src="/dist/web-components/sketch-diff-view.js"></script>
+ <style>
+ body {
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
+ max-width: 1200px;
+ margin: 0 auto;
+ padding: 2rem;
+ }
+
+ h1 {
+ color: #333;
+ margin-bottom: 2rem;
+ }
+
+ .control-panel {
+ margin-bottom: 2rem;
+ padding: 1rem;
+ background-color: #f0f0f0;
+ border-radius: 4px;
+ }
+
+ input {
+ padding: 0.5rem;
+ border-radius: 4px;
+ border: 1px solid #ccc;
+ width: 300px;
+ }
+
+ button {
+ padding: 0.5rem 1rem;
+ background-color: #2196f3;
+ color: white;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+ margin-left: 1rem;
+ }
+
+ button:hover {
+ background-color: #0d8bf2;
+ }
+ </style>
+
+<script>
+ document.addEventListener('DOMContentLoaded', () => {
+ const diffViewer = document.getElementById('diffViewer');
+ const commitHashInput = document.getElementById('commitHash');
+ const viewDiffButton = document.getElementById('viewDiff');
+ let commit = false;
+ viewDiffButton.addEventListener('click', () => {
+ let diffContent = `diff --git a/sample.txt b/sample.txt
+index 1111111..2222222 100644
+--- a/sample.txt
++++ b/sample.txt
+@@ -1,5 +1,5 @@
+ This is a sample file
+-This line will be removed
++This line is added as a replacement
+ This line stays the same
+-Another line to remove
++A completely new line
+ The last line is unchanged`;
+ if (commit) {
+ // For demo purposes, generate fake diff based on commit hash
+ diffContent = `diff --git a/file-${commit.substring(0,5)}.txt b/file-${commit.substring(0,5)}.txt
+index 3333333..4444444 100644
+--- a/file-${commit.substring(0,5)}.txt
++++ b/file-${commit.substring(0,5)}.txt
+@@ -1,4 +1,6 @@
+ File with commit: ${commit}
++This line was added in commit ${commit}
+ This line exists in both versions
+-This line was removed in commit ${commit}
++This line replaced the removed line
++Another new line added in this commit
+ Last line of the file`;
+ }
+ diffViewer.diffText = diffContent;
+ diffViewer.commitHash = commitHashInput.value.trim();
+ });
+ });
+</script>
+
+</head>
+<body>
+ <h1>Sketch Diff Viewer Demo</h1>
+
+ <div class="control-panel">
+ <label for="commitHash">Commit Hash (leave empty for unstaged changes):</label>
+ <input type="text" id="commitHash" placeholder="Enter commit hash">
+ <button id="viewDiff">View Diff</button>
+ </div>
+
+ <sketch-diff-view id="diffViewer"></sketch-diff-view>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/loop/webui/src/web-components/demo/sketch-network-status.demo.html b/loop/webui/src/web-components/demo/sketch-network-status.demo.html
new file mode 100644
index 0000000..9926b0e
--- /dev/null
+++ b/loop/webui/src/web-components/demo/sketch-network-status.demo.html
@@ -0,0 +1,17 @@
+<html>
+ <head>
+ <title>sketch-network-status demo</title>
+ <link rel="stylesheet" href="demo.css" />
+ <script src="/dist/web-components/sketch-network-status.js" type="module"></script>
+ </head>
+ <body>
+ <h1>sketch-network-status demo</h1>
+
+ Connected:
+ <sketch-network-status connection="connected" message="connected"></sketch-network-status>
+
+ Error:
+ <sketch-network-status connection="error" error="error"></sketch-network-status>
+
+ </body>
+</html>
\ No newline at end of file
diff --git a/loop/webui/src/web-components/demo/sketch-timeline-message.demo.html b/loop/webui/src/web-components/demo/sketch-timeline-message.demo.html
new file mode 100644
index 0000000..466f910
--- /dev/null
+++ b/loop/webui/src/web-components/demo/sketch-timeline-message.demo.html
@@ -0,0 +1,66 @@
+<html>
+ <head>
+ <title>sketch-timeline-message demo</title>
+ <link rel="stylesheet" href="demo.css" />
+ <script
+ src="/dist/web-components/sketch-timeline-message.js"
+ type="module"
+ ></script>
+
+ <script>
+ const messages = [
+ {
+ type: "agent",
+ content: "an agent message",
+ },
+ {
+ type: "user",
+ content: "a user message",
+ },
+ {
+ type: "tool",
+ content: "a tool use message",
+ },
+ {
+ type: "commit",
+ end_of_turn: false,
+ content: "",
+ commits: [
+ {
+ hash: "ece101c103ec231da87f4df05c1b5e6a24e13add",
+ subject: "Add README.md for web components directory",
+ body: "This adds documentation for the web components used in the Loop UI,\nincluding a description of each component, usage examples, and\ndevelopment guidelines.\n\nCo-Authored-By: sketch\nadd README.md for loop/webui/src/web-components",
+ pushed_branch:
+ "sketch/create-readmemd-for-web-components-directory",
+ },
+ ],
+ timestamp: "2025-04-14T16:39:33.639533919Z",
+ conversation_id: "",
+ idx: 17,
+ },
+ {
+ type: "agent",
+ content: "an end-of-turn agent message",
+ end_of_turn: true,
+ },
+ ];
+ document.addEventListener("DOMContentLoaded", () => {
+ messages.forEach((msg, idx) => {
+ const jsonEl = document.createElement("pre");
+ jsonEl.innerText = `.message property: ${JSON.stringify(msg)}`;
+ document.body.append(jsonEl);
+ const messageEl = document.createElement("sketch-timeline-message");
+ messageEl.message = msg;
+ document.body.appendChild(messageEl);
+ });
+ window.addEventListener(
+ "show-commit-diff",
+ (evt) => {console.log("show-commit-diff", evt)}
+ );
+ });
+ </script>
+ </head>
+ <body>
+ <h1>sketch-timeline-message demo</h1>
+ </body>
+</html>
diff --git a/loop/webui/src/web-components/demo/sketch-timeline.demo.html b/loop/webui/src/web-components/demo/sketch-timeline.demo.html
new file mode 100644
index 0000000..427181d
--- /dev/null
+++ b/loop/webui/src/web-components/demo/sketch-timeline.demo.html
@@ -0,0 +1,47 @@
+<html>
+ <head>
+ <title>sketch-timeline demo</title>
+ <link rel="stylesheet" href="demo.css" />
+ <script src="/dist/web-components/sketch-timeline.js" type="module"></script>
+
+ <script>
+ const messages = [
+ {
+ type: "user",
+ content: "a user message",
+ },
+ {
+ type: "agent",
+ content: "an agent message",
+ },
+ {
+ type: "agent",
+ content: "an agent message",
+ },
+ {
+ type: "agent",
+ content: "an agent message",
+ },
+ {
+ type: "user",
+ content: "a user message",
+ },
+ {
+ type: "user",
+ content: "a user message",
+ },
+ ];
+ document.addEventListener("DOMContentLoaded", () => {
+ const timelineEl = document.querySelector('sketch-timeline');
+ timelineEl.messages = messages;
+ });
+ </script>
+
+ </head>
+ <body>
+ <h1>sketch-timeline demo</h1>
+
+ <sketch-timeline></sketch-timeline>
+
+ </body>
+</html>
\ No newline at end of file
diff --git a/loop/webui/src/web-components/demo/sketch-tool-calls.demo.html b/loop/webui/src/web-components/demo/sketch-tool-calls.demo.html
new file mode 100644
index 0000000..a72babc
--- /dev/null
+++ b/loop/webui/src/web-components/demo/sketch-tool-calls.demo.html
@@ -0,0 +1,190 @@
+<html>
+ <head>
+ <title>sketch-tool-calls demo</title>
+ <link rel="stylesheet" href="demo.css" />
+
+ <script
+ src="/dist/web-components/sketch-tool-calls.js"
+ type="module"
+ ></script>
+
+ <script>
+ const toolCalls = [
+ [
+ {
+ name: "bash",
+ input: JSON.stringify({
+ command: "ls -a",
+ }),
+ },
+ ],
+ [
+ {
+ name: "bash",
+ input: JSON.stringify({
+ command: "ls -a",
+ }),
+ result_message: {
+ type: "tool",
+ tool_result: ".\n..",
+ },
+ },
+ ],
+ [
+ {
+ name: "bash",
+ input: JSON.stringify({
+ command: "sleep 200",
+ }),
+ result_message: {
+ type: "tool",
+ tool_error: "the user canceled this operation",
+ },
+ },
+ ],
+ [
+ {
+ name: "title",
+ input: JSON.stringify({
+ title: "a new title for this sketch",
+ }),
+ },
+ ],
+ [
+ {
+ name: "codereview",
+ input: "{}",
+ tool_call_id: "toolu_01WT5qQwHZgdogfKhkD8R9PZ",
+ result_message: {
+ type: "tool",
+ end_of_turn: false,
+ content: "",
+ tool_name: "codereview",
+ input: "{}",
+ tool_result: "OK",
+ tool_call_id: "toolu_01WT5qQwHZgdogfKhkD8R9PZ",
+ timestamp: "2025-04-14T16:33:17.575759565Z",
+ conversation_id: "xsa-8hw0",
+ start_time: "2025-04-14T16:33:07.11793816Z",
+ end_time: "2025-04-14T16:33:17.57575719Z",
+ elapsed: 10457819031,
+ idx: 45,
+ },
+ },
+ ],
+ [
+ {
+ name: "codereview",
+ input: "{}",
+ tool_call_id: "toolu_01WT5qQwHZgdogfKhkD8R9PZ",
+ result_message: {
+ type: "tool",
+ end_of_turn: false,
+ content: "",
+ tool_name: "codereview",
+ input: "{}",
+ tool_result: "Not OK",
+ tool_call_id: "toolu_01WT5qQwHZgdogfKhkD8R9PZ",
+ timestamp: "2025-04-14T16:33:17.575759565Z",
+ conversation_id: "xsa-8hw0",
+ start_time: "2025-04-14T16:33:07.11793816Z",
+ end_time: "2025-04-14T16:33:17.57575719Z",
+ elapsed: 10457819031,
+ idx: 45,
+ },
+ },
+ ],
+ [
+ {
+ name: "think",
+ input:
+ '{"thoughts":"I\'m going to inspect a few key components to understand their purpose and relationships:\\n1. sketch-app-shell.ts - Appears to be the main container component\\n2. sketch-timeline.ts - Likely manages the chat timeline\\n3. sketch-view-mode-select.ts - Handles switching between different views\\n\\nThis will help me create a comprehensive README that explains the components and their relationships."}',
+ tool_call_id: "toolu_01R1g5mQVgKxEJZFNp9QGvUr",
+ result_message: {
+ type: "tool",
+ end_of_turn: false,
+ content: "",
+ tool_name: "think",
+ input:
+ '{"thoughts":"I\'m going to inspect a few key components to understand their purpose and relationships:\\n1. sketch-app-shell.ts - Appears to be the main container component\\n2. sketch-timeline.ts - Likely manages the chat timeline\\n3. sketch-view-mode-select.ts - Handles switching between different views\\n\\nThis will help me create a comprehensive README that explains the components and their relationships."}',
+ tool_result: "recorded",
+ tool_call_id: "toolu_01R1g5mQVgKxEJZFNp9QGvUr",
+ timestamp: "2025-04-14T16:32:14.12647133Z",
+ conversation_id: "xsa-8hw0",
+ start_time: "2025-04-14T16:32:14.126454329Z",
+ end_time: "2025-04-14T16:32:14.126468539Z",
+ elapsed: 14209,
+ idx: 18,
+ },
+ },
+ ],
+ [
+ {
+ name: "patch",
+ input:
+ '{"path":"/app/loop/webui/src/web-components/README.md","patches":[{"operation":"overwrite","newText":"# Web Components\\n\\nThis directory contains the custom web components used in the Loop WebUI. These components are built using the [Lit](https://lit.dev/) library for creating fast, lightweight web components with a declarative template system.\\n\\n## Component Architecture\\n\\nThe components follow a hierarchical structure that creates a complete UI for interacting with the CodingAgent:\\n\\n```\\nsketch-app-shell (main container)\\n├── sketch-container-status\\n├── sketch-network-status\\n├── sketch-view-mode-select\\n├── sketch-timeline (chat view)\\n│ └── sketch-timeline-message\\n│ └── sketch-tool-calls\\n├── sketch-diff-view (code diff view)\\n└── sketch-chat-input\\n```\\n\\n## Component Overview\\n\\n### sketch-app-shell.ts\\nThe main container component that orchestrates the entire UI. It manages:\\n- View modes (chat, diff, charts, terminal)\\n- Network status and connection management\\n- Timeline data fetching and rendering\\n- Auto-scrolling behavior for chat messages\\n\\n### sketch-chat-input.ts\\nHandles user input for sending messages to the CodingAgent:\\n- Text input area with markdown support\\n- Send button and keyboard shortcuts (Enter to send, Shift+Enter for newline)\\n- Auto-focusing behavior\\n\\n### sketch-container-status.ts\\nDisplays information about the container environment:\\n- OS information\\n- Resource usage (CPU, memory)\\n- Container status indicators\\n\\n### sketch-diff-view.ts\\nProvides a visual diff viewer for code changes:\\n- Git commit display\\n- Side-by-side or unified diff viewing\\n- Syntax highlighting for code\\n- Comment creation for code review\\n\\n### sketch-network-status.ts\\nShows the current connection status to the server:\\n- Connected/disconnected indicators\\n- Error messages when connection issues occur\\n- Visual feedback on connection state\\n\\n### sketch-timeline.ts\\nDisplays the conversation history between user and CodingAgent:\\n- Message rendering\\n- Manages the sequence of messages\\n- Handles scrolling behavior\\n\\n### sketch-timeline-message.ts\\nRenders individual messages in the timeline:\\n- Different styling for user vs. agent messages\\n- Markdown rendering with syntax highlighting\\n- Handles special message types\\n\\n### sketch-tool-calls.ts\\nDisplays tool call information within messages:\\n- Tool call parameters and outputs\\n- Expandable/collapsible sections for tool details\\n- Syntax highlighting for code in tool outputs\\n\\n### sketch-view-mode-select.ts\\nProvides UI for switching between different views:\\n- Chat view for conversation\\n- Diff view for code changes\\n- Charts view for data visualization\\n- Terminal view for command execution\\n\\n## Development\\n\\n### Creating New Components\\n\\nWhen creating new components, follow these patterns:\\n\\n1. Use the `@customElement` decorator to define the component\\n2. Encapsulate styles using the static `styles` property\\n3. Use Lit\'s reactive properties (`@property`, `@state`) to manage component state\\n4. Implement lifecycle methods as needed (`connectedCallback`, `disconnectedCallback`, etc.)\\n\\n### Example\\n\\n```typescript\\nimport { css, html, LitElement } from \'lit\';\\nimport { customElement, property } from \'lit/decorators.js\';\\n\\n@customElement(\'sketch-new-component\')\\nexport class SketchNewComponent extends LitElement {\\n @property()\\n myProperty: string = \'default\';\\n\\n static styles = css`\\n :host {\\n display: block;\\n }\\n `;\\n\\n render() {\\n return html`\\n <div>\\n ${this.myProperty}\\n </div>\\n `;\\n }\\n}\\n\\ndeclare global {\\n interface HTMLElementTagNameMap {\\n \'sketch-new-component\': SketchNewComponent;\\n }\\n}\\n```\\n\\n### Best Practices\\n\\n1. Maintain a single responsibility for each component\\n2. Use properties and events for parent-child communication\\n3. Document the purpose and API of each component\\n4. Write tests for components where possible\\n5. Keep styles encapsulated within components\\n6. Use type definitions for strong typing\\n\\n## Related Resources\\n\\n- [Lit Documentation](https://lit.dev/docs/)\\n- [Web Components Introduction](https://developer.mozilla.org/en-US/docs/Web/Web_Components)\\n- See `/app/loop/webui/readme.md` for more information on the Loop WebUI project\\n"}]}',
+ tool_call_id: "toolu_01TNhLX2AWkZwsu2KCLKrpju",
+ result_message: {
+ type: "tool",
+ end_of_turn: false,
+ content: "",
+ tool_name: "patch",
+ input:
+ '{"path":"/app/loop/webui/src/web-components/README.md","patches":[{"operation":"overwrite","newText":"# Web Components\\n\\nThis directory contains the custom web components used in the Loop WebUI. These components are built using the [Lit](https://lit.dev/) library for creating fast, lightweight web components with a declarative template system.\\n\\n## Component Architecture\\n\\nThe components follow a hierarchical structure that creates a complete UI for interacting with the CodingAgent:\\n\\n```\\nsketch-app-shell (main container)\\n├── sketch-container-status\\n├── sketch-network-status\\n├── sketch-view-mode-select\\n├── sketch-timeline (chat view)\\n│ └── sketch-timeline-message\\n│ └── sketch-tool-calls\\n├── sketch-diff-view (code diff view)\\n└── sketch-chat-input\\n```\\n\\n## Component Overview\\n\\n### sketch-app-shell.ts\\nThe main container component that orchestrates the entire UI. It manages:\\n- View modes (chat, diff, charts, terminal)\\n- Network status and connection management\\n- Timeline data fetching and rendering\\n- Auto-scrolling behavior for chat messages\\n\\n### sketch-chat-input.ts\\nHandles user input for sending messages to the CodingAgent:\\n- Text input area with markdown support\\n- Send button and keyboard shortcuts (Enter to send, Shift+Enter for newline)\\n- Auto-focusing behavior\\n\\n### sketch-container-status.ts\\nDisplays information about the container environment:\\n- OS information\\n- Resource usage (CPU, memory)\\n- Container status indicators\\n\\n### sketch-diff-view.ts\\nProvides a visual diff viewer for code changes:\\n- Git commit display\\n- Side-by-side or unified diff viewing\\n- Syntax highlighting for code\\n- Comment creation for code review\\n\\n### sketch-network-status.ts\\nShows the current connection status to the server:\\n- Connected/disconnected indicators\\n- Error messages when connection issues occur\\n- Visual feedback on connection state\\n\\n### sketch-timeline.ts\\nDisplays the conversation history between user and CodingAgent:\\n- Message rendering\\n- Manages the sequence of messages\\n- Handles scrolling behavior\\n\\n### sketch-timeline-message.ts\\nRenders individual messages in the timeline:\\n- Different styling for user vs. agent messages\\n- Markdown rendering with syntax highlighting\\n- Handles special message types\\n\\n### sketch-tool-calls.ts\\nDisplays tool call information within messages:\\n- Tool call parameters and outputs\\n- Expandable/collapsible sections for tool details\\n- Syntax highlighting for code in tool outputs\\n\\n### sketch-view-mode-select.ts\\nProvides UI for switching between different views:\\n- Chat view for conversation\\n- Diff view for code changes\\n- Charts view for data visualization\\n- Terminal view for command execution\\n\\n## Development\\n\\n### Creating New Components\\n\\nWhen creating new components, follow these patterns:\\n\\n1. Use the `@customElement` decorator to define the component\\n2. Encapsulate styles using the static `styles` property\\n3. Use Lit\'s reactive properties (`@property`, `@state`) to manage component state\\n4. Implement lifecycle methods as needed (`connectedCallback`, `disconnectedCallback`, etc.)\\n\\n### Example\\n\\n```typescript\\nimport { css, html, LitElement } from \'lit\';\\nimport { customElement, property } from \'lit/decorators.js\';\\n\\n@customElement(\'sketch-new-component\')\\nexport class SketchNewComponent extends LitElement {\\n @property()\\n myProperty: string = \'default\';\\n\\n static styles = css`\\n :host {\\n display: block;\\n }\\n `;\\n\\n render() {\\n return html`\\n <div>\\n ${this.myProperty}\\n </div>\\n `;\\n }\\n}\\n\\ndeclare global {\\n interface HTMLElementTagNameMap {\\n \'sketch-new-component\': SketchNewComponent;\\n }\\n}\\n```\\n\\n### Best Practices\\n\\n1. Maintain a single responsibility for each component\\n2. Use properties and events for parent-child communication\\n3. Document the purpose and API of each component\\n4. Write tests for components where possible\\n5. Keep styles encapsulated within components\\n6. Use type definitions for strong typing\\n\\n## Related Resources\\n\\n- [Lit Documentation](https://lit.dev/docs/)\\n- [Web Components Introduction](https://developer.mozilla.org/en-US/docs/Web/Web_Components)\\n- See `/app/loop/webui/readme.md` for more information on the Loop WebUI project\\n"}]}',
+ tool_result: "- Applied all patches\n",
+ tool_call_id: "toolu_01TNhLX2AWkZwsu2KCLKrpju",
+ timestamp: "2025-04-14T16:32:47.10299854Z",
+ conversation_id: "xsa-8hw0",
+ start_time: "2025-04-14T16:32:47.101660683Z",
+ end_time: "2025-04-14T16:32:47.102994206Z",
+ elapsed: 1333524,
+ idx: 34,
+ },
+ },
+ ],
+ [
+ {
+ name: "done",
+ input:
+ '{"checklist_items":{"code_reviewed":{"status":"yes","description":"If any commits were made, the codereview tool was run and its output was addressed.","comments":"Since this commit only adds a documentation file (README.md) with no functional code changes, a formal code review is not essential, but I\'ve verified the content for accuracy and completeness."},"wrote_tests":{"status":"not applicable","description":"If code was changed, tests were written or updated.","comments":"No code was changed; only documentation was added. The README file is purely informational and doesn\'t require tests."},"passes_tests":{"status":"not applicable","description":"If any commits were made, tests pass.","comments":"No functional code changes were made that would impact existing tests. The addition of a README file is a documentation-only change."},"git_commit":{"status":"yes","description":"Create git commits for any code changes you made. Match the style of recent commit messages. Include \'Co-Authored-By: sketch\' and the original user prompt. Use GIT_AUTHOR_NAME=\\"Sean McCullough\\" GIT_AUTHOR_EMAIL=\\"banksean@gmail.com\\" (not git config).","comments":"Created a commit with a descriptive message that follows the requested format, including \'Co-Authored-By: sketch\' attribution."}}}',
+ tool_call_id: "toolu_01HPgWQJF1aF9LUqkdDKWeES",
+ result_message: {
+ type: "tool",
+ end_of_turn: false,
+ content: "",
+ tool_name: "done",
+ input:
+ '{"checklist_items":{"code_reviewed":{"status":"yes","description":"If any commits were made, the codereview tool was run and its output was addressed.","comments":"Since this commit only adds a documentation file (README.md) with no functional code changes, a formal code review is not essential, but I\'ve verified the content for accuracy and completeness."},"wrote_tests":{"status":"not applicable","description":"If code was changed, tests were written or updated.","comments":"No code was changed; only documentation was added. The README file is purely informational and doesn\'t require tests."},"passes_tests":{"status":"not applicable","description":"If any commits were made, tests pass.","comments":"No functional code changes were made that would impact existing tests. The addition of a README file is a documentation-only change."},"git_commit":{"status":"yes","description":"Create git commits for any code changes you made. Match the style of recent commit messages. Include \'Co-Authored-By: sketch\' and the original user prompt. Use GIT_AUTHOR_NAME=\\"Sean McCullough\\" GIT_AUTHOR_EMAIL=\\"banksean@gmail.com\\" (not git config).","comments":"Created a commit with a descriptive message that follows the requested format, including \'Co-Authored-By: sketch\' attribution."}}}',
+ tool_result:
+ "codereview tool has not been run for commit 0b1f45dc17fbe7800f5164993ec99d6564256787",
+ tool_error: true,
+ tool_call_id: "toolu_01HPgWQJF1aF9LUqkdDKWeES",
+ timestamp: "2025-04-14T16:33:04.639179373Z",
+ conversation_id: "xsa-8hw0",
+ start_time: "2025-04-14T16:33:04.616273148Z",
+ end_time: "2025-04-14T16:33:04.639173456Z",
+ elapsed: 22900309,
+ idx: 43,
+ },
+ },
+ ],
+ ];
+ document.addEventListener("DOMContentLoaded", () => {
+ toolCalls.forEach((calls) => {
+ const jsonEl = document.createElement("pre");
+ jsonEl.innerText = `.toolCalls property: ${JSON.stringify(calls)}`;
+ document.body.append(jsonEl);
+
+ const toolCallsEl = document.createElement("sketch-tool-calls");
+ toolCallsEl.toolCalls = calls;
+ document.body.append(toolCallsEl);
+ });
+ });
+ </script>
+ </head>
+ <body>
+ <h1>sketch-tool-calls demo</h1>
+
+ <sketch-tool-calls></sketch-tool-calls>
+ </body>
+</html>
diff --git a/loop/webui/src/web-components/demo/sketch-view-mode-select.demo.html b/loop/webui/src/web-components/demo/sketch-view-mode-select.demo.html
new file mode 100644
index 0000000..dac6831
--- /dev/null
+++ b/loop/webui/src/web-components/demo/sketch-view-mode-select.demo.html
@@ -0,0 +1,31 @@
+<html>
+ <head>
+ <title>sketch-view-mode-select demo</title>
+ <link rel="stylesheet" href="demo.css" />
+
+ <script src="/dist/web-components/sketch-view-mode-select.js" type="module"></script>
+
+ <script>
+ document.addEventListener("DOMContentLoaded", () => {
+ const viewModeSelect = document.querySelector('sketch-view-mode-select');
+ const msgDiv = document.querySelector("#selected-mode");
+ msgDiv.innerText = `selected mode: ${viewModeSelect.activeMode}`;
+
+ console.log("viewModeSelect: ", viewModeSelect);
+ viewModeSelect.addEventListener("view-mode-select", (evt) => {
+ console.log('view mode change event: ', evt);
+ const msgDiv = document.querySelector("#selected-mode");
+ msgDiv.innerText = `selected mode: ${evt.detail.mode}`;
+ });
+ });
+ </script>
+
+ </head>
+ <body>
+ <h1>sketch-view-mode-select demo</h1>
+
+ <sketch-view-mode-select></sketch-view-mode-select>
+ <div id="selected-mode"></div>
+
+ </body>
+</html>
\ No newline at end of file
diff --git a/loop/webui/src/web-components/sketch-app-shell.ts b/loop/webui/src/web-components/sketch-app-shell.ts
new file mode 100644
index 0000000..2c7b111
--- /dev/null
+++ b/loop/webui/src/web-components/sketch-app-shell.ts
@@ -0,0 +1,769 @@
+import { css, html, LitElement } from "lit";
+import { customElement, property, state } from "lit/decorators.js";
+import { PropertyValues } from "lit";
+import { DataManager, ConnectionStatus } from "../data";
+import { State, TimelineMessage, ToolCall } 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 { View } from "vega";
+
+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 = "";
+
+ // Reference to the diff view component
+ private diffViewRef?: HTMLElement;
+
+ // 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()
+ messages: TimelineMessage[] = [];
+
+ @property()
+ chatMessageText: string = "";
+
+ @property()
+ title: string = "";
+
+ private dataManager = new DataManager();
+
+ @property()
+ containerState: State = { title: "", os: "", total_usage: {} };
+
+ // Track if this is the first load of messages
+ @state()
+ private isFirstLoad: boolean = true;
+
+ // Track if we should scroll to the bottom
+ @state()
+ private shouldScrollToBottom: boolean = true;
+
+ // Mutation observer to detect when new messages are added
+ private mutationObserver: MutationObserver | null = null;
+
+ constructor() {
+ super();
+
+ // Binding methods to this
+ this._handleViewModeSelect = this._handleViewModeSelect.bind(this);
+ this._handleDiffComment = this._handleDiffComment.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 as EventListener);
+
+ // Add event listeners
+ window.addEventListener(
+ "view-mode-select",
+ this._handleViewModeSelect as EventListener
+ );
+ window.addEventListener(
+ "diff-comment",
+ this._handleDiffComment as EventListener
+ );
+ window.addEventListener(
+ "show-commit-diff",
+ this._handleShowCommitDiff as EventListener
+ );
+
+ // 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 as EventListener);
+
+ // Remove event listeners
+ window.removeEventListener(
+ "view-mode-select",
+ this._handleViewModeSelect as EventListener
+ );
+ window.removeEventListener(
+ "diff-comment",
+ this._handleDiffComment as EventListener
+ );
+ window.removeEventListener(
+ "show-commit-diff",
+ this._handleShowCommitDiff as EventListener
+ );
+
+ // 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());
+ }
+
+ _handlePopState(event) {
+ 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);
+ }
+ }
+
+ /**
+ * Handle diff comment event
+ */
+ private _handleDiffComment(event: CustomEvent) {
+ const { comment } = event.detail;
+ if (!comment) return;
+
+ // Find the chat input textarea
+ const chatInput = this.shadowRoot?.querySelector("sketch-chat-input");
+ if (chatInput) {
+ // Update the chat input content using property
+ const currentContent = chatInput.getAttribute("content") || "";
+ const newContent = currentContent
+ ? `${currentContent}\n\n${comment}`
+ : comment;
+ chatInput.setAttribute("content", newContent);
+
+ // Dispatch an event to update the textarea value in the chat input component
+ const updateEvent = new CustomEvent("update-content", {
+ detail: { content: newContent },
+ bubbles: true,
+ composed: true,
+ });
+ chatInput.dispatchEvent(updateEvent);
+
+ // Switch back to chat view
+ this.toggleViewMode("chat", true);
+ }
+ }
+
+ /**
+ * Listen for commit diff event
+ * @param commitHash The commit hash to show diff for
+ */
+ public 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
+ */
+ public 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"));
+ });
+ }
+
+ mergeAndDedupe(
+ arr1: TimelineMessage[],
+ arr2: TimelineMessage[]
+ ): TimelineMessage[] {
+ const mergedArray = [...arr1, ...arr2];
+ const seenIds = new Set<number>();
+ const toolCallResults = new Map<string, TimelineMessage>();
+
+ let ret: TimelineMessage[] = 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: TimelineMessage, b: TimelineMessage) => 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;
+ }
+
+ private handleDataChanged(eventData: {
+ state: State;
+ newMessages: TimelineMessage[];
+ isFirstFetch?: boolean;
+ }): void {
+ const { state, newMessages, isFirstFetch } = eventData;
+
+ // Check if this is the first data fetch or if there are new messages
+ if (isFirstFetch) {
+ console.log("Auto-scroll: First data fetch, will scroll to bottom");
+ this.isFirstLoad = true;
+ this.shouldScrollToBottom = true;
+ this.messageStatus = "Initial messages loaded";
+ } else if (newMessages && newMessages.length > 0) {
+ console.log(`Auto-scroll: Received ${newMessages.length} new messages`);
+ this.messageStatus = "Updated just now";
+ // Check if we should scroll before updating messages
+ this.shouldScrollToBottom = this.checkShouldScroll();
+ } 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 = this.mergeAndDedupe(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}, shouldScroll=${this.shouldScrollToBottom}`
+ );
+ }
+ }
+
+ 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}`);
+ }
+ // Clear the input after successfully sending the message.
+ this.chatMessageText = "";
+
+ // 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;
+
+ // Always scroll to bottom after sending a message
+ console.log("Auto-scroll: User sent a message, forcing scroll to bottom");
+ this.shouldScrollToBottom = true;
+
+ // // 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();
+
+ // Force multiple scroll attempts to ensure the user message is visible
+ // This addresses potential timing issues with DOM updates
+ const forceScrollAttempts = () => {
+ console.log("Auto-scroll: Forcing scroll after user message");
+ this.shouldScrollToBottom = true;
+
+ // Update the timeline component's scroll state
+ const timeline = this.shadowRoot?.querySelector(
+ "sketch-timeline"
+ ) as any;
+ if (timeline && timeline.setShouldScrollToLatest) {
+ timeline.setShouldScrollToLatest(true);
+ timeline.scrollToLatest();
+ } else {
+ this.scrollToBottom();
+ }
+ };
+
+ // Make multiple scroll attempts with different timings
+ // This ensures we catch the DOM after various update stages
+ setTimeout(forceScrollAttempts, 100);
+ setTimeout(forceScrollAttempts, 300);
+ setTimeout(forceScrollAttempts, 600);
+ } 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}></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
+ .content=${this.chatMessageText}
+ @send-chat="${this._sendChat}"
+ ></sketch-chat-input>
+ `;
+ }
+
+ /**
+ * Check if the page should scroll to the bottom based on current view position
+ * @returns Boolean indicating if we should scroll to the bottom
+ */
+ private checkShouldScroll(): boolean {
+ // If we're not in chat view, don't auto-scroll
+ if (this.viewMode !== "chat") {
+ return false;
+ }
+
+ // More generous threshold - if we're within 500px of the bottom, auto-scroll
+ // This ensures we start scrolling sooner when new messages appear
+ const scrollPosition = window.scrollY;
+ const windowHeight = window.innerHeight;
+ const documentHeight = document.body.scrollHeight;
+ const distanceFromBottom = documentHeight - (scrollPosition + windowHeight);
+ const threshold = 500; // Increased threshold to be more responsive
+
+ return distanceFromBottom <= threshold;
+ }
+
+ /**
+ * Scroll to the bottom of the timeline
+ */
+ private scrollToBottom(): void {
+ if (!this.checkShouldScroll()) {
+ return;
+ }
+
+ this.scrollTo({ top: this.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) {
+ setTimeout(() => this.scrollToBottom(), 50);
+ }
+ }
+
+ /**
+ * 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
new file mode 100644
index 0000000..3bde418
--- /dev/null
+++ b/loop/webui/src/web-components/sketch-charts.ts
@@ -0,0 +1,490 @@
+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 { TimelineMessage } 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: TimelineMessage[] = [];
+
+ @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: TimelineMessage[]
+ ): { 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
new file mode 100644
index 0000000..7d93c17
--- /dev/null
+++ b/loop/webui/src/web-components/sketch-chat-input.test.ts
@@ -0,0 +1,137 @@
+import { html, fixture, expect, oneEvent, elementUpdated, fixtureCleanup } from "@open-wc/testing";
+import "./sketch-chat-input";
+import { SketchChatInput } from "./sketch-chat-input";
+
+describe("SketchChatInput", () => {
+ afterEach(() => {
+ fixtureCleanup();
+ });
+
+ it("initializes with empty content by default", async () => {
+ const el: SketchChatInput = await fixture(html`
+ <sketch-chat-input></sketch-chat-input>
+ `);
+
+ expect(el.content).to.equal("");
+ const textarea = el.shadowRoot!.querySelector("#chatInput") as HTMLTextAreaElement;
+ expect(textarea.value).to.equal("");
+ });
+
+ it("initializes with provided content", async () => {
+ const testContent = "Hello, world!";
+ const el: SketchChatInput = await fixture(html`
+ <sketch-chat-input .content=${testContent}></sketch-chat-input>
+ `);
+
+ expect(el.content).to.equal(testContent);
+ const textarea = el.shadowRoot!.querySelector("#chatInput") as HTMLTextAreaElement;
+ expect(textarea.value).to.equal(testContent);
+ });
+
+ it("updates content when typing in the textarea", async () => {
+ const el: SketchChatInput = await fixture(html`
+ <sketch-chat-input></sketch-chat-input>
+ `);
+
+ const textarea = el.shadowRoot!.querySelector("#chatInput") as HTMLTextAreaElement;
+ const newValue = "New message";
+
+ textarea.value = newValue;
+ textarea.dispatchEvent(new Event("input"));
+
+ expect(el.content).to.equal(newValue);
+ });
+
+ it("sends message when clicking the send button", async () => {
+ const testContent = "Test message";
+ const el: SketchChatInput = await fixture(html`
+ <sketch-chat-input .content=${testContent}></sketch-chat-input>
+ `);
+
+ const button = el.shadowRoot!.querySelector("#sendChatButton") as HTMLButtonElement;
+
+ // Setup listener for the send-chat event
+ setTimeout(() => button.click());
+ const { detail } = await oneEvent(el, "send-chat");
+
+ expect(detail.message).to.equal(testContent);
+ expect(el.content).to.equal("");
+ });
+
+ it("sends message when pressing Enter (without shift)", async () => {
+ const testContent = "Test message";
+ const el: SketchChatInput = await fixture(html`
+ <sketch-chat-input .content=${testContent}></sketch-chat-input>
+ `);
+
+ const textarea = el.shadowRoot!.querySelector("#chatInput") as HTMLTextAreaElement;
+
+ // Setup listener for the send-chat event
+ setTimeout(() => {
+ const enterEvent = new KeyboardEvent("keydown", {
+ key: "Enter",
+ bubbles: true,
+ cancelable: true,
+ shiftKey: false
+ });
+ textarea.dispatchEvent(enterEvent);
+ });
+
+ const { detail } = await oneEvent(el, "send-chat");
+
+ expect(detail.message).to.equal(testContent);
+ expect(el.content).to.equal("");
+ });
+
+ it("does not send message when pressing Shift+Enter", async () => {
+ const testContent = "Test message";
+ const el: SketchChatInput = await fixture(html`
+ <sketch-chat-input .content=${testContent}></sketch-chat-input>
+ `);
+
+ const textarea = el.shadowRoot!.querySelector("#chatInput") as HTMLTextAreaElement;
+
+ // Create a flag to track if the event was fired
+ let eventFired = false;
+ el.addEventListener("send-chat", () => {
+ eventFired = true;
+ });
+
+ // Dispatch the shift+enter keydown event
+ const shiftEnterEvent = new KeyboardEvent("keydown", {
+ key: "Enter",
+ bubbles: true,
+ cancelable: true,
+ shiftKey: true
+ });
+ textarea.dispatchEvent(shiftEnterEvent);
+
+ // Wait a short time to verify no event was fired
+ await new Promise(resolve => setTimeout(resolve, 10));
+
+ expect(eventFired).to.be.false;
+ expect(el.content).to.equal(testContent);
+ });
+
+ it("updates content when receiving update-content event", async () => {
+ const el: SketchChatInput = await fixture(html`
+ <sketch-chat-input></sketch-chat-input>
+ `);
+
+ const newContent = "Updated content";
+
+ // Dispatch the update-content event
+ const updateEvent = new CustomEvent("update-content", {
+ detail: { content: newContent },
+ bubbles: true
+ });
+ el.dispatchEvent(updateEvent);
+
+ // Wait for the component to update
+ await elementUpdated(el);
+
+ expect(el.content).to.equal(newContent);
+ const textarea = el.shadowRoot!.querySelector("#chatInput") as HTMLTextAreaElement;
+ expect(textarea.value).to.equal(newContent);
+ });
+});
diff --git a/loop/webui/src/web-components/sketch-chat-input.ts b/loop/webui/src/web-components/sketch-chat-input.ts
new file mode 100644
index 0000000..3e75b52
--- /dev/null
+++ b/loop/webui/src/web-components/sketch-chat-input.ts
@@ -0,0 +1,178 @@
+import { css, html, LitElement, PropertyValues } from "lit";
+import { customElement, property, query } from "lit/decorators.js";
+import { DataManager, ConnectionStatus } from "../data";
+import { State, TimelineMessage } from "../types";
+import "./sketch-container-status";
+
+@customElement("sketch-chat-input")
+export class SketchChatInput extends LitElement {
+ @property()
+ 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: none;
+ font-family: monospace;
+ font-size: 12px;
+ min-height: 40px;
+ max-height: 120px;
+ background: #f7f7f7;
+ }
+
+ #sendChatButton {
+ background-color: #2196f3;
+ color: white;
+ border: none;
+ border-radius: 4px;
+ padding: 0 20px;
+ cursor: pointer;
+ font-weight: 600;
+ }
+
+ #sendChatButton:hover {
+ background-color: #0d8bf2;
+ }
+ `;
+
+ constructor() {
+ super();
+
+ // Binding methods
+ this._handleUpdateContent = this._handleUpdateContent.bind(this);
+ }
+
+ /**
+ * Handle update-content event
+ */
+ private _handleUpdateContent(event: CustomEvent) {
+ const { content } = event.detail;
+ if (content !== undefined) {
+ this.content = content;
+
+ // Update the textarea value directly, otherwise it won't update until next render
+ const textarea = this.shadowRoot?.querySelector(
+ "#chatInput"
+ ) as HTMLTextAreaElement;
+ if (textarea) {
+ textarea.value = content;
+ }
+ }
+ }
+
+ // See https://lit.dev/docs/components/lifecycle/
+ connectedCallback() {
+ super.connectedCallback();
+
+ // Listen for update-content events
+ this.addEventListener(
+ "update-content",
+ this._handleUpdateContent as EventListener
+ );
+ }
+
+ // See https://lit.dev/docs/components/lifecycle/
+ disconnectedCallback() {
+ super.disconnectedCallback();
+
+ // Remove event listeners
+ this.removeEventListener(
+ "update-content",
+ this._handleUpdateContent as EventListener
+ );
+ }
+
+ sendChatMessage() {
+ const event = new CustomEvent("send-chat", {
+ detail: { message: this.content },
+ bubbles: true,
+ composed: true,
+ });
+ this.dispatchEvent(event);
+ this.content = ""; // Clear the input after sending
+ }
+
+ adjustChatSpacing() {
+ console.log("TODO: adjustChatSpacing");
+ }
+
+ _sendChatClicked() {
+ this.sendChatMessage();
+ this.chatInput.focus(); // Refocus the input after sending
+ }
+
+ _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;
+ requestAnimationFrame(() => this.adjustChatSpacing());
+ }
+
+ @query("#chatInput")
+ private chatInput: HTMLTextAreaElement;
+
+ protected firstUpdated(): void {
+ if (this.chatInput) {
+ this.chatInput.focus();
+ }
+ }
+
+ 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
new file mode 100644
index 0000000..3a898ee
--- /dev/null
+++ b/loop/webui/src/web-components/sketch-container-status.test.ts
@@ -0,0 +1,151 @@
+import { html, fixture, expect } from "@open-wc/testing";
+import "./sketch-container-status";
+import type { SketchContainerStatus } from "./sketch-container-status";
+import { State } from "../types";
+
+describe("SketchContainerStatus", () => {
+ // 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
+ }
+ };
+
+ it("renders with complete state data", async () => {
+ const el: SketchContainerStatus = await fixture(html`
+ <sketch-container-status .state=${mockCompleteState}></sketch-container-status>
+ `);
+
+ // Check that all expected elements exist
+ expect(el.shadowRoot!.querySelector("#hostname")).to.exist;
+ expect(el.shadowRoot!.querySelector("#workingDir")).to.exist;
+ expect(el.shadowRoot!.querySelector("#initialCommit")).to.exist;
+ expect(el.shadowRoot!.querySelector("#messageCount")).to.exist;
+ expect(el.shadowRoot!.querySelector("#inputTokens")).to.exist;
+ expect(el.shadowRoot!.querySelector("#outputTokens")).to.exist;
+ expect(el.shadowRoot!.querySelector("#cacheReadInputTokens")).to.exist;
+ expect(el.shadowRoot!.querySelector("#cacheCreationInputTokens")).to.exist;
+ expect(el.shadowRoot!.querySelector("#totalCost")).to.exist;
+
+ // Verify content of displayed elements
+ expect(el.shadowRoot!.querySelector("#hostname")!.textContent).to.equal("test-host");
+ expect(el.shadowRoot!.querySelector("#workingDir")!.textContent).to.equal("/test/dir");
+ expect(el.shadowRoot!.querySelector("#initialCommit")!.textContent).to.equal("abcdef12"); // Only first 8 chars
+ expect(el.shadowRoot!.querySelector("#messageCount")!.textContent).to.equal("42");
+ expect(el.shadowRoot!.querySelector("#inputTokens")!.textContent).to.equal("1000");
+ expect(el.shadowRoot!.querySelector("#outputTokens")!.textContent).to.equal("2000");
+ expect(el.shadowRoot!.querySelector("#cacheReadInputTokens")!.textContent).to.equal("300");
+ expect(el.shadowRoot!.querySelector("#cacheCreationInputTokens")!.textContent).to.equal("400");
+ expect(el.shadowRoot!.querySelector("#totalCost")!.textContent).to.equal("$0.25");
+ });
+
+ it("renders with undefined state", async () => {
+ const el: SketchContainerStatus = await fixture(html`
+ <sketch-container-status></sketch-container-status>
+ `);
+
+ // Elements should exist but be empty
+ expect(el.shadowRoot!.querySelector("#hostname")!.textContent).to.equal("");
+ expect(el.shadowRoot!.querySelector("#workingDir")!.textContent).to.equal("");
+ expect(el.shadowRoot!.querySelector("#initialCommit")!.textContent).to.equal("");
+ expect(el.shadowRoot!.querySelector("#messageCount")!.textContent).to.equal("");
+ expect(el.shadowRoot!.querySelector("#inputTokens")!.textContent).to.equal("");
+ expect(el.shadowRoot!.querySelector("#outputTokens")!.textContent).to.equal("");
+ expect(el.shadowRoot!.querySelector("#totalCost")!.textContent).to.equal("$0.00");
+ });
+
+ it("renders with partial state data", async () => {
+ const partialState: Partial<State> = {
+ hostname: "partial-host",
+ message_count: 10,
+ os: "linux",
+ title: "Partial Test",
+ total_usage: {
+ input_tokens: 500
+ }
+ };
+
+ const el: SketchContainerStatus = await fixture(html`
+ <sketch-container-status .state=${partialState as State}></sketch-container-status>
+ `);
+
+ // Check that elements with data are properly populated
+ expect(el.shadowRoot!.querySelector("#hostname")!.textContent).to.equal("partial-host");
+ expect(el.shadowRoot!.querySelector("#messageCount")!.textContent).to.equal("10");
+ expect(el.shadowRoot!.querySelector("#inputTokens")!.textContent).to.equal("500");
+
+ // Check that elements without data are empty
+ expect(el.shadowRoot!.querySelector("#workingDir")!.textContent).to.equal("");
+ expect(el.shadowRoot!.querySelector("#initialCommit")!.textContent).to.equal("");
+ expect(el.shadowRoot!.querySelector("#outputTokens")!.textContent).to.equal("");
+ expect(el.shadowRoot!.querySelector("#totalCost")!.textContent).to.equal("$0.00");
+ });
+
+ it("handles cost formatting correctly", async () => {
+ // 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 el: SketchContainerStatus = await fixture(html`
+ <sketch-container-status .state=${stateWithCost}></sketch-container-status>
+ `);
+
+ expect(el.shadowRoot!.querySelector("#totalCost")!.textContent).to.equal(testCase.expected);
+ }
+ });
+
+ it("truncates commit hash to 8 characters", async () => {
+ const stateWithLongCommit = {
+ ...mockCompleteState,
+ initial_commit: "1234567890abcdef1234567890abcdef12345678"
+ };
+
+ const el: SketchContainerStatus = await fixture(html`
+ <sketch-container-status .state=${stateWithLongCommit}></sketch-container-status>
+ `);
+
+ expect(el.shadowRoot!.querySelector("#initialCommit")!.textContent).to.equal("12345678");
+ });
+
+ it("has correct link elements", async () => {
+ const el: SketchContainerStatus = await fixture(html`
+ <sketch-container-status .state=${mockCompleteState}></sketch-container-status>
+ `);
+
+ const links = Array.from(el.shadowRoot!.querySelectorAll('a'));
+ expect(links.length).to.equal(2);
+
+ // Check for logs link
+ const logsLink = links.find(link => link.textContent === 'Logs');
+ expect(logsLink).to.exist;
+ expect(logsLink!.getAttribute('href')).to.equal('logs');
+
+ // Check for download link
+ const downloadLink = links.find(link => link.textContent === 'Download');
+ expect(downloadLink).to.exist;
+ expect(downloadLink!.getAttribute('href')).to.equal('download');
+ });
+});
diff --git a/loop/webui/src/web-components/sketch-container-status.ts b/loop/webui/src/web-components/sketch-container-status.ts
new file mode 100644
index 0000000..c0f9626
--- /dev/null
+++ b/loop/webui/src/web-components/sketch-container-status.ts
@@ -0,0 +1,150 @@
+import { css, html, LitElement } from "lit";
+import { customElement, property } from "lit/decorators.js";
+import { State } from "../types";
+
+@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;
+ }
+
+ .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();
+ }
+
+ // 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">${this.state?.hostname}</span>
+ </div>
+ <div class="info-item">
+ <span id="workingDir" class="info-value"
+ >${this.state?.working_dir}</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
new file mode 100644
index 0000000..562eb1e
--- /dev/null
+++ b/loop/webui/src/web-components/sketch-diff-view.ts
@@ -0,0 +1,566 @@
+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;
+
+ // 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: fixed;
+ bottom: 80px;
+ right: 20px;
+ 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;
+ }
+
+ .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 {
+ // Show loading state
+ diff2htmlContent.innerHTML = "Loading enhanced diff...";
+
+ // Build the diff URL - include commit hash if specified
+ const diffUrl = this.commitHash ? `diff?commit=${this.commitHash}` : "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;
+
+ console.log("Setting up diff line comments");
+
+ // 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
+ 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>
+ `;
+
+ 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
+ if (commentBox) {
+ 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;
+ }
+
+ /**
+ * 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
new file mode 100644
index 0000000..a580a8f
--- /dev/null
+++ b/loop/webui/src/web-components/sketch-network-status.test.ts
@@ -0,0 +1,68 @@
+import { html, fixture, expect } from "@open-wc/testing";
+import "./sketch-network-status";
+import type { SketchNetworkStatus } from "./sketch-network-status";
+
+describe("SketchNetworkStatus", () => {
+ it("displays the correct connection status when connected", async () => {
+ const el: SketchNetworkStatus = await fixture(html`
+ <sketch-network-status
+ connection="connected"
+ message="Connected to server"
+ ></sketch-network-status>
+ `);
+
+ const indicator = el.shadowRoot!.querySelector(".polling-indicator");
+ const statusText = el.shadowRoot!.querySelector(".status-text");
+
+ expect(indicator).to.exist;
+ expect(statusText).to.exist;
+ expect(indicator!.classList.contains("active")).to.be.true;
+ expect(statusText!.textContent).to.equal("Connected to server");
+ });
+
+ it("displays the correct connection status when disconnected", async () => {
+ const el: SketchNetworkStatus = await fixture(html`
+ <sketch-network-status
+ connection="disconnected"
+ message="Disconnected"
+ ></sketch-network-status>
+ `);
+
+ const indicator = el.shadowRoot!.querySelector(".polling-indicator");
+
+ expect(indicator).to.exist;
+ expect(indicator!.classList.contains("error")).to.be.true;
+ });
+
+
+ it("displays the correct connection status when disabled", async () => {
+ const el: SketchNetworkStatus = await fixture(html`
+ <sketch-network-status
+ connection="disabled"
+ message="Disabled"
+ ></sketch-network-status>
+ `);
+
+ const indicator = el.shadowRoot!.querySelector(".polling-indicator");
+
+ expect(indicator).to.exist;
+ expect(indicator!.classList.contains("error")).to.be.false;
+ expect(indicator!.classList.contains("active")).to.be.false;
+ });
+
+ it("displays error message when provided", async () => {
+ const errorMsg = "Connection error";
+ const el: SketchNetworkStatus = await fixture(html`
+ <sketch-network-status
+ connection="disconnected"
+ message="Disconnected"
+ error="${errorMsg}"
+ ></sketch-network-status>
+ `);
+
+ const statusText = el.shadowRoot!.querySelector(".status-text");
+
+ expect(statusText).to.exist;
+ expect(statusText!.textContent).to.equal(errorMsg);
+ });
+});
diff --git a/loop/webui/src/web-components/sketch-network-status.ts b/loop/webui/src/web-components/sketch-network-status.ts
new file mode 100644
index 0000000..4b01e5e
--- /dev/null
+++ b/loop/webui/src/web-components/sketch-network-status.ts
@@ -0,0 +1,101 @@
+import {css, html, LitElement} from 'lit';
+import {customElement, property} from 'lit/decorators.js';
+import {DataManager, ConnectionStatus} from '../data';
+import {State, TimelineMessage} from '../types';
+import './sketch-container-status';
+
+@customElement('sketch-network-status')
+export class SketchNetworkStatus extends LitElement {
+ // Header bar: view mode buttons
+
+ @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;
+ }
+}
\ No newline at end of file
diff --git a/loop/webui/src/timeline/terminal.ts b/loop/webui/src/web-components/sketch-terminal.ts
similarity index 73%
rename from loop/webui/src/timeline/terminal.ts
rename to loop/webui/src/web-components/sketch-terminal.ts
index fbe9a7d..788521d 100644
--- a/loop/webui/src/timeline/terminal.ts
+++ b/loop/webui/src/web-components/sketch-terminal.ts
@@ -1,10 +1,14 @@
import { Terminal } from "@xterm/xterm";
import { FitAddon } from "@xterm/addon-fit";
-/**
- * Class to handle terminal functionality in the timeline UI.
- */
-export class TerminalHandler {
+import {css, html, LitElement} from 'lit';
+import {customElement, property, state} from 'lit/decorators.js';
+import {DataManager, ConnectionStatus} from '../data';
+import {State, TimelineMessage} from '../types';
+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
@@ -17,20 +21,94 @@
private terminalInputQueue: string[] = [];
// Flag to track if we're currently processing a terminal input
private processingTerminalInput: boolean = false;
- // Current view mode (needed for resize handling)
- private viewMode: string = "chat";
- /**
- * Constructor for TerminalHandler
- */
- constructor() {}
+ 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;
+}
- /**
- * Sets the current view mode
- * @param mode The current view mode
- */
- public setViewMode(mode: string): void {
- this.viewMode = mode;
+.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);
+ }
}
/**
@@ -38,7 +116,7 @@
* @param terminalContainer The DOM element to contain the terminal
*/
public async initializeTerminal(): Promise<void> {
- const terminalContainer = document.getElementById("terminalContainer");
+ const terminalContainer = this.renderRoot.querySelector("#terminalContainer") as HTMLElement;
if (!terminalContainer) {
console.error("Terminal container not found");
@@ -81,15 +159,6 @@
// Fit the terminal to the container
this.fitAddon.fit();
- // Setup resize handler
- window.addEventListener("resize", () => {
- if (this.viewMode === "terminal" && this.fitAddon) {
- this.fitAddon.fit();
- // Send resize information to server
- this.sendTerminalResize();
- }
- });
-
// Focus the terminal
this.terminal.focus();
}
@@ -111,13 +180,13 @@
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
@@ -131,7 +200,7 @@
}
}
};
-
+
this.terminalEventSource.onerror = (error) => {
console.error("Terminal SSE error:", error);
if (this.terminal) {
@@ -142,7 +211,7 @@
this.closeTerminalConnections();
}
};
-
+
// Send key inputs to the server via POST requests
if (this.terminal) {
this.terminal.onData((data) => {
@@ -174,7 +243,7 @@
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();
@@ -189,17 +258,17 @@
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('/') ? '.' : '.';
@@ -210,14 +279,14 @@
'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();
}
@@ -246,7 +315,7 @@
'Content-Type': 'application/json'
}
});
-
+
if (!response.ok) {
console.error(`Failed to send terminal resize: ${response.status} ${response.statusText}`);
}
@@ -255,15 +324,18 @@
}
}
- /**
- * Clean up resources when component is destroyed
- */
- public dispose(): void {
- this.closeTerminalConnections();
- if (this.terminal) {
- this.terminal.dispose();
- this.terminal = null;
- }
- this.fitAddon = null;
+
+ render() {
+ return html`
+ <div id="terminalView" class="terminal-view">
+ <div id="terminalContainer" class="terminal-container"></div>
+ </div>
+ `;
}
}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "sketch-terminal": SketchTerminal;
+ }
+}
\ No newline at end of file
diff --git a/loop/webui/src/web-components/sketch-timeline-message.test.ts b/loop/webui/src/web-components/sketch-timeline-message.test.ts
new file mode 100644
index 0000000..3f30696
--- /dev/null
+++ b/loop/webui/src/web-components/sketch-timeline-message.test.ts
@@ -0,0 +1,265 @@
+import { html, fixture, expect, oneEvent } from "@open-wc/testing";
+import "./sketch-timeline-message";
+import type { SketchTimelineMessage } from "./sketch-timeline-message";
+import { TimelineMessage, ToolCall, GitCommit, Usage } from "../types";
+
+describe("SketchTimelineMessage", () => {
+ // Helper function to create mock timeline messages
+ function createMockMessage(props: Partial<TimelineMessage> = {}): TimelineMessage {
+ 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
+ };
+ }
+
+ it("renders with basic message content", async () => {
+ const message = createMockMessage({
+ type: "agent",
+ content: "This is a test message"
+ });
+
+ const el: SketchTimelineMessage = await fixture(html`
+ <sketch-timeline-message
+ .message=${message}
+ ></sketch-timeline-message>
+ `);
+
+ const messageContent = el.shadowRoot!.querySelector(".message-text");
+ expect(messageContent).to.exist;
+ expect(messageContent!.textContent!.trim()).to.include("This is a test message");
+ });
+
+ it("renders with correct message type classes", async () => {
+ const messageTypes = ["user", "agent", "tool", "error"];
+
+ for (const type of messageTypes) {
+ const message = createMockMessage({ type });
+
+ const el: SketchTimelineMessage = await fixture(html`
+ <sketch-timeline-message
+ .message=${message}
+ ></sketch-timeline-message>
+ `);
+
+ const messageElement = el.shadowRoot!.querySelector(".message");
+ expect(messageElement).to.exist;
+ expect(messageElement!.classList.contains(type)).to.be.true;
+ }
+ });
+
+ it("renders end-of-turn marker correctly", async () => {
+ const message = createMockMessage({
+ end_of_turn: true
+ });
+
+ const el: SketchTimelineMessage = await fixture(html`
+ <sketch-timeline-message
+ .message=${message}
+ ></sketch-timeline-message>
+ `);
+
+ const messageElement = el.shadowRoot!.querySelector(".message");
+ expect(messageElement).to.exist;
+ expect(messageElement!.classList.contains("end-of-turn")).to.be.true;
+ });
+
+ it("formats timestamps correctly", async () => {
+ const message = createMockMessage({
+ timestamp: "2023-05-15T12:00:00Z"
+ });
+
+ const el: SketchTimelineMessage = await fixture(html`
+ <sketch-timeline-message
+ .message=${message}
+ ></sketch-timeline-message>
+ `);
+
+ const timestamp = el.shadowRoot!.querySelector(".message-timestamp");
+ expect(timestamp).to.exist;
+ // Should include a formatted date like "May 15, 2023"
+ expect(timestamp!.textContent).to.include("May 15, 2023");
+ // Should include elapsed time
+ expect(timestamp!.textContent).to.include("(1.50s)");
+ });
+
+ it("renders markdown content correctly", async () => {
+ const markdownContent = "# Heading\n\n- List item 1\n- List item 2\n\n`code block`";
+ const message = createMockMessage({
+ content: markdownContent
+ });
+
+ const el: SketchTimelineMessage = await fixture(html`
+ <sketch-timeline-message
+ .message=${message}
+ ></sketch-timeline-message>
+ `);
+
+ const contentElement = el.shadowRoot!.querySelector(".markdown-content");
+ expect(contentElement).to.exist;
+ expect(contentElement!.innerHTML).to.include("<h1>Heading</h1>");
+ expect(contentElement!.innerHTML).to.include("<ul>");
+ expect(contentElement!.innerHTML).to.include("<li>List item 1</li>");
+ expect(contentElement!.innerHTML).to.include("<code>code block</code>");
+ });
+
+ it("displays usage information when available", async () => {
+ const usage: Usage = {
+ input_tokens: 150,
+ output_tokens: 300,
+ cost_usd: 0.025,
+ cache_read_input_tokens: 50
+ };
+
+ const message = createMockMessage({
+ usage
+ });
+
+ const el: SketchTimelineMessage = await fixture(html`
+ <sketch-timeline-message
+ .message=${message}
+ ></sketch-timeline-message>
+ `);
+
+ const usageElement = el.shadowRoot!.querySelector(".message-usage");
+ expect(usageElement).to.exist;
+ expect(usageElement!.textContent).to.include("In: 150");
+ expect(usageElement!.textContent).to.include("Out: 300");
+ expect(usageElement!.textContent).to.include("Cache: 50");
+ expect(usageElement!.textContent).to.include("$0.03");
+ });
+
+ it("renders commit information correctly", async () => {
+ 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 el: SketchTimelineMessage = await fixture(html`
+ <sketch-timeline-message
+ .message=${message}
+ ></sketch-timeline-message>
+ `);
+
+ const commitsContainer = el.shadowRoot!.querySelector(".commits-container");
+ expect(commitsContainer).to.exist;
+
+ const commitHeader = commitsContainer!.querySelector(".commits-header");
+ expect(commitHeader).to.exist;
+ expect(commitHeader!.textContent).to.include("1 new commit");
+
+ const commitHash = commitsContainer!.querySelector(".commit-hash");
+ expect(commitHash).to.exist;
+ expect(commitHash!.textContent).to.equal("12345678"); // First 8 chars
+
+ const pushedBranch = commitsContainer!.querySelector(".pushed-branch");
+ expect(pushedBranch).to.exist;
+ expect(pushedBranch!.textContent).to.include("main");
+ });
+
+ it("dispatches show-commit-diff event when commit diff button is clicked", async () => {
+ 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 el: SketchTimelineMessage = await fixture(html`
+ <sketch-timeline-message
+ .message=${message}
+ ></sketch-timeline-message>
+ `);
+
+ const diffButton = el.shadowRoot!.querySelector(".commit-diff-button") as HTMLButtonElement;
+ expect(diffButton).to.exist;
+
+ // Set up listener for the event
+ setTimeout(() => diffButton!.click());
+ const { detail } = await oneEvent(el, "show-commit-diff");
+
+ expect(detail).to.exist;
+ expect(detail.commitHash).to.equal("1234567890abcdef");
+ });
+
+ it("handles message type icon display correctly", async () => {
+ // 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 firstEl: SketchTimelineMessage = await fixture(html`
+ <sketch-timeline-message
+ .message=${firstMessage}
+ ></sketch-timeline-message>
+ `);
+
+ const firstIcon = firstEl.shadowRoot!.querySelector(".message-icon");
+ expect(firstIcon).to.exist;
+ expect(firstIcon!.textContent!.trim()).to.equal("U");
+
+ // Test second message with previous message of same type
+ const secondEl: SketchTimelineMessage = await fixture(html`
+ <sketch-timeline-message
+ .message=${secondMessage}
+ .previousMessage=${firstMessage}
+ ></sketch-timeline-message>
+ `);
+
+ const secondIcon = secondEl.shadowRoot!.querySelector(".message-icon");
+ expect(secondIcon).to.not.exist;
+ });
+
+ it("formats numbers correctly", async () => {
+ const el: SketchTimelineMessage = await fixture(html`
+ <sketch-timeline-message></sketch-timeline-message>
+ `);
+
+ // Test accessing private method via the component instance
+ expect(el.formatNumber(1000)).to.equal("1,000");
+ expect(el.formatNumber(null, "N/A")).to.equal("N/A");
+ expect(el.formatNumber(undefined, "--")).to.equal("--");
+ });
+
+ it("formats currency values correctly", async () => {
+ const el: SketchTimelineMessage = await fixture(html`
+ <sketch-timeline-message></sketch-timeline-message>
+ `);
+
+ // Test with different precisions
+ expect(el.formatCurrency(10.12345, "$0.00", true)).to.equal("$10.1235"); // message level (4 decimals)
+ expect(el.formatCurrency(10.12345, "$0.00", false)).to.equal("$10.12"); // total level (2 decimals)
+ expect(el.formatCurrency(null, "N/A")).to.equal("N/A");
+ expect(el.formatCurrency(undefined, "--")).to.equal("--");
+ });
+});
diff --git a/loop/webui/src/web-components/sketch-timeline-message.ts b/loop/webui/src/web-components/sketch-timeline-message.ts
new file mode 100644
index 0000000..cd2985a
--- /dev/null
+++ b/loop/webui/src/web-components/sketch-timeline-message.ts
@@ -0,0 +1,553 @@
+import { css, html, LitElement } from "lit";
+import { unsafeHTML } from "lit/directives/unsafe-html.js";
+import { customElement, property } from "lit/decorators.js";
+import { State, TimelineMessage } from "../types";
+import { marked, MarkedOptions } from "marked";
+import "./sketch-tool-calls";
+@customElement("sketch-timeline-message")
+export class SketchTimelineMessage extends LitElement {
+ @property()
+ message: TimelineMessage;
+
+ @property()
+ previousMessage: TimelineMessage;
+
+ // 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;
+ }
+
+ .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;
+ cursor: pointer;
+ font-family: monospace;
+ background-color: #f6f8fa;
+ border-bottom: 1px dashed #d1d5da;
+ }
+
+ .commit-preview:hover {
+ background-color: #eef2f6;
+ }
+
+ .commit-hash {
+ color: #0366d6;
+ font-weight: bold;
+ }
+
+ .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: 6px 12px;
+ border: 1px solid #ccc;
+ border-radius: 3px;
+ background-color: #f7f7f7;
+ color: #24292e;
+ font-size: 12px;
+ cursor: pointer;
+ transition: all 0.2s ease;
+ margin: 8px 12px;
+ display: block;
+ }
+
+ .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}}))
+ }
+
+ 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}
+ ></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">${commit.hash.substring(0, 8)}</span>
+ ${commit.subject}
+ <span class="pushed-branch"
+ >→ pushed to ${commit.pushed_branch}</span>
+ </div>
+ <div class="commit-details is-hidden">
+ <pre>${commit.body}</pre>
+ </div>
+ <button class="commit-diff-button" @click=${() => this.showCommit(commit.hash)}>View Changes</button>
+ </div>
+ </div>
+ `;
+ })}
+ </div>
+ `
+ : ""}
+ </div>
+ </div>
+ `;
+ }
+}
+
+function copyButton(textToCopy: string) {
+ // Add click event listener to handle copying
+ const ret = html`<button class="copy-button" title="Copy text to clipboard" @click=${(e: Event) => {
+ e.stopPropagation();
+ const copyButton = e.currentTarget as HTMLButtonElement;
+ navigator.clipboard
+ .writeText(textToCopy)
+ .then(() => {
+ copyButton.textContent = "Copied!";
+ setTimeout(() => {
+ copyButton.textContent = "Copy";
+ }, 2000);
+ })
+ .catch((err) => {
+ console.error("Failed to copy text: ", err);
+ copyButton.textContent = "Failed";
+ setTimeout(() => {
+ copyButton.textContent = "Copy";
+ }, 2000);
+ });
+ }}>Copy</button`;
+
+ return ret
+}
+
+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
new file mode 100644
index 0000000..8122db7
--- /dev/null
+++ b/loop/webui/src/web-components/sketch-timeline.ts
@@ -0,0 +1,127 @@
+import {css, html, LitElement} from 'lit';
+import {repeat} from 'lit/directives/repeat.js';
+import {customElement, property} from 'lit/decorators.js';
+import {State, TimelineMessage} from '../types';
+import './sketch-timeline-message'
+
+@customElement('sketch-timeline')
+export class SketchTimeline extends LitElement {
+ @property()
+ messages: TimelineMessage[] = [];
+
+ // 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`
+ /* 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;
+ }
+ `;
+
+ constructor() {
+ super();
+
+ // Binding methods
+ this._handleShowCommitDiff = this._handleShowCommitDiff.bind(this);
+ }
+
+ /**
+ * 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);
+ }
+ }
+
+ // See https://lit.dev/docs/components/lifecycle/
+ connectedCallback() {
+ super.connectedCallback();
+
+ // Listen for showCommitDiff events from the renderer
+ document.addEventListener('showCommitDiff', this._handleShowCommitDiff as EventListener);
+ }
+
+ // See https://lit.dev/docs/components/lifecycle/
+ disconnectedCallback() {
+ super.disconnectedCallback();
+
+ // Remove event listeners
+ document.removeEventListener('showCommitDiff', this._handleShowCommitDiff as EventListener);
+ }
+
+ messageKey(message: TimelineMessage): 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 class="timeline-container">
+ ${repeat(this.messages, this.messageKey, (message, index) => {
+ let previousMessage: TimelineMessage;
+ if (index > 0) {
+ previousMessage = this.messages[index-1];
+ }
+ return html`<sketch-timeline-message .message=${message} .previousMessage=${previousMessage}></sketch-timeline-message>`;
+ })}
+ </div>
+ `;
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "sketch-timeline": SketchTimeline;
+ }
+}
\ No newline at end of file
diff --git a/loop/webui/src/web-components/sketch-tool-calls.ts b/loop/webui/src/web-components/sketch-tool-calls.ts
new file mode 100644
index 0000000..a8d0acc
--- /dev/null
+++ b/loop/webui/src/web-components/sketch-tool-calls.ts
@@ -0,0 +1,639 @@
+import { css, html, LitElement } from "lit";
+import { unsafeHTML } from "lit/directives/unsafe-html.js";
+import { repeat } from "lit/directives/repeat.js";
+import { customElement, property } from "lit/decorators.js";
+import { State, ToolCall } from "../types";
+import { marked, MarkedOptions } from "marked";
+
+@customElement("sketch-tool-calls")
+export class SketchToolCalls extends LitElement {
+ @property()
+ toolCalls: ToolCall[] = [];
+
+ // 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`
+ /* Tool calls container styles */
+ .tool-calls-container {
+ /* Removed dotted border */
+ }
+
+ .tool-calls-toggle {
+ cursor: pointer;
+ background-color: #f0f0f0;
+ padding: 5px 10px;
+ border: none;
+ border-radius: 4px;
+ text-align: left;
+ font-size: 12px;
+ margin-top: 5px;
+ color: #555;
+ font-weight: 500;
+ }
+
+ .tool-calls-toggle:hover {
+ background-color: #e0e0e0;
+ }
+
+ .tool-calls-details {
+ margin-top: 10px;
+ transition: max-height 0.3s ease;
+ }
+
+ .tool-calls-details.collapsed {
+ max-height: 0;
+ overflow: hidden;
+ margin-top: 0;
+ }
+
+ .tool-call {
+ background: #f9f9f9;
+ border-radius: 4px;
+ padding: 10px;
+ margin-bottom: 10px;
+ border-left: 3px solid #4caf50;
+ }
+
+ .tool-call-header {
+ margin-bottom: 8px;
+ font-size: 14px;
+ padding: 2px 0;
+ }
+
+ /* Compact tool display styles */
+ .tool-compact-line {
+ font-family: monospace;
+ font-size: 12px;
+ line-height: 1.4;
+ padding: 4px 6px;
+ background: #f8f8f8;
+ border-radius: 3px;
+ position: relative;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ max-width: 100%;
+ display: flex;
+ align-items: center;
+ }
+
+ .tool-result-inline {
+ font-family: monospace;
+ color: #0066bb;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ max-width: 400px;
+ display: inline-block;
+ vertical-align: middle;
+ }
+
+ .copy-inline-button {
+ font-size: 10px;
+ padding: 2px 4px;
+ margin-left: 8px;
+ background: #eee;
+ border: none;
+ border-radius: 3px;
+ cursor: pointer;
+ opacity: 0.7;
+ }
+
+ .copy-inline-button:hover {
+ opacity: 1;
+ background: #ddd;
+ }
+
+ .tool-input.compact,
+ .tool-result.compact {
+ margin: 2px 0;
+ padding: 4px;
+ font-size: 12px;
+ }
+
+ /* Removed old compact container CSS */
+
+ /* Ultra-compact tool call box styles */
+ .tool-calls-header {
+ /* Empty header - just small spacing */
+ }
+
+ .tool-call-boxes-row {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+ margin-bottom: 8px;
+ }
+
+ .tool-call-wrapper {
+ display: flex;
+ flex-direction: column;
+ margin-bottom: 4px;
+ }
+
+ .tool-call-box {
+ display: inline-flex;
+ align-items: center;
+ background: #f0f0f0;
+ border-radius: 4px;
+ padding: 3px 8px;
+ font-size: 12px;
+ cursor: pointer;
+ max-width: 320px;
+ position: relative;
+ border: 1px solid #ddd;
+ transition: background-color 0.2s;
+ }
+
+ .tool-call-box:hover {
+ background-color: #e8e8e8;
+ }
+
+ .tool-call-box.expanded {
+ background-color: #e0e0e0;
+ border-bottom-left-radius: 0;
+ border-bottom-right-radius: 0;
+ border-bottom: 1px solid #ccc;
+ }
+
+ .tool-call-input {
+ color: #666;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ font-family: monospace;
+ font-size: 11px;
+ }
+
+ .tool-call-card {
+ display: flex;
+ flex-direction: column;
+ background-color: white;
+ overflow: hidden;
+ cursor: pointer;
+ }
+
+ /* Compact view (default) */
+ .tool-call-compact-view {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ font-size: 0.9em;
+ white-space: nowrap;
+ overflow: visible; /* Don't hide overflow, we'll handle text truncation per element */
+ position: relative; /* For positioning the expand icon */
+ }
+
+ /* Expanded view (hidden by default) */
+ .tool-call-card.collapsed .tool-call-expanded-view {
+ display: none;
+ }
+
+ .tool-call-expanded-view {
+ display: flex;
+ flex-direction: column;
+ border-top: 1px solid #eee;
+ }
+
+ .tool-call-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 6px 10px;
+ background-color: #f0f0f0;
+ border-bottom: 1px solid #ddd;
+ font-weight: bold;
+ }
+
+ .tool-call-name {
+ color: gray;
+ }
+
+ .tool-call-status {
+ margin-right: 4px;
+ text-align: center;
+ }
+
+ .tool-call-status.spinner {
+ animation: spin 1s infinite linear;
+ display: inline-block;
+ width: 1em;
+ }
+
+ .tool-call-time {
+ margin-left: 8px;
+ font-size: 0.85em;
+ color: #666;
+ font-weight: normal;
+ }
+
+ .tool-call-input-preview {
+ color: #555;
+ font-family: var(--monospace-font);
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ max-width: 30%;
+ background-color: rgba(240, 240, 240, 0.5);
+ padding: 2px 5px;
+ border-radius: 3px;
+ font-size: 0.9em;
+ }
+
+ .tool-call-result-preview {
+ color: #28a745;
+ font-family: var(--monospace-font);
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ max-width: 40%;
+ background-color: rgba(240, 248, 240, 0.5);
+ padding: 2px 5px;
+ border-radius: 3px;
+ font-size: 0.9em;
+ }
+
+ .tool-call-expand-icon {
+ position: absolute;
+ right: 10px;
+ font-size: 0.8em;
+ color: #888;
+ }
+
+ .tool-call-input {
+ padding: 6px 10px;
+ border-bottom: 1px solid #eee;
+ font-family: var(--monospace-font);
+ font-size: 0.9em;
+ white-space: pre-wrap;
+ word-break: break-all;
+ background-color: #f5f5f5;
+ }
+
+ .tool-call-result {
+ padding: 6px 10px;
+ font-family: var(--monospace-font);
+ font-size: 0.9em;
+ white-space: pre-wrap;
+ max-height: 300px;
+ overflow-y: auto;
+ }
+
+ .tool-call-result pre {
+ margin: 0;
+ white-space: pre-wrap;
+ }
+
+ @keyframes spin {
+ 0% {
+ transform: rotate(0deg);
+ }
+ 100% {
+ transform: rotate(360deg);
+ }
+ }
+
+ /* Standalone tool messages (legacy/disconnected) */
+ .tool-details.standalone .tool-header {
+ border-radius: 4px;
+ background-color: #fff3cd;
+ border-color: #ffeeba;
+ }
+
+ .tool-details.standalone .tool-warning {
+ margin-left: 10px;
+ font-size: 0.85em;
+ color: #856404;
+ font-style: italic;
+ }
+
+ /* Tool call expanded view with sections */
+ .tool-call-section {
+ border-bottom: 1px solid #eee;
+ }
+
+ .tool-call-section:last-child {
+ border-bottom: none;
+ }
+
+ .tool-call-section-label {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 8px 10px;
+ background-color: #f5f5f5;
+ font-weight: bold;
+ font-size: 0.9em;
+ }
+
+ .tool-call-section-content {
+ padding: 0;
+ }
+
+ .tool-call-copy-btn {
+ background-color: #f0f0f0;
+ border: 1px solid #ddd;
+ border-radius: 4px;
+ padding: 2px 8px;
+ font-size: 0.8em;
+ cursor: pointer;
+ transition: background-color 0.2s;
+ }
+
+ .tool-call-copy-btn:hover {
+ background-color: #e0e0e0;
+ }
+
+ /* Override for tool call input in expanded view */
+ .tool-call-section-content .tool-call-input {
+ margin: 0;
+ padding: 8px 10px;
+ border: none;
+ background-color: #fff;
+ max-height: 300px;
+ overflow-y: auto;
+ }
+
+ .tool-call-card .tool-call-input-preview,
+ .tool-call-card .tool-call-result-preview {
+ font-family: monospace;
+ background: black;
+ padding: 1em;
+ }
+ .tool-call-input-preview {
+ color: white;
+ }
+ .tool-call-result-preview {
+ color: gray;
+ }
+
+ .tool-call-card.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;
+ }
+
+ .thought-bubble {
+ position: relative;
+ background-color: #eee;
+ border-radius: 8px;
+ padding: 0.5em;
+ box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
+ margin-left: 24px;
+ margin-top: 24px;
+ margin-bottom: 12px;
+ max-width: 30%;
+ white-space: pre;
+ }
+
+ .thought-bubble .preview {
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ }
+
+ .thought-bubble:before {
+ content: '';
+ position: absolute;
+ top: -8px;
+ left: -8px;
+ width: 15px;
+ height: 15px;
+ background-color: #eee;
+ border-radius: 50%;
+ box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
+ }
+
+ .thought-bubble:after {
+ content: '';
+ position: absolute;
+ top: -16px;
+ left: -16px;
+ width: 8px;
+ height: 8px;
+ background-color: #eee;
+ border-radius: 50%;
+ box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
+ }
+
+
+ .patch-input-preview {
+ color: #555;
+ font-family: monospace;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ max-width: 30%;
+ background-color: rgba(240, 240, 240, 0.5);
+ padding: 2px 5px;
+ border-radius: 3px;
+ font-size: 0.9em;
+ }
+
+ .codereview-OK {
+ color: green;
+ }
+ `;
+
+ 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;
+ }
+ }
+
+ _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);
+ }
+ };
+
+ toolCard(toolCall: ToolCall) {
+ const toolCallStatus = toolCall.result_message
+ ? toolCall.result_message.tool_error
+ ? "❌"
+ : ""
+ : "⏳";
+
+ const cancelButton = 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(toolCall.tool_call_id, button);
+ }}
+ >
+ Cancel
+ </button>`;
+
+ const status = html`<span
+ class="tool-call-status ${toolCall.result_message ? "" : "spinner"}"
+ >${toolCallStatus}</span
+ >`;
+
+ switch (toolCall.name) {
+ case "title":
+ const titleInput = JSON.parse(toolCall.input);
+ return html`
+ <div class="tool-call-compact-view">
+ I've set the title of this sketch to <b>"${titleInput.title}"</b>
+ </div>`;
+ case "bash":
+ const bashInput = JSON.parse(toolCall.input);
+ return html`
+ <div class="tool-call-compact-view">
+ ${status}
+ <span class="tool-call-name">${toolCall.name}</span>
+ <pre class="tool-call-input-preview">${bashInput.command}</pre>
+ ${toolCall.result_message
+ ? html`
+ ${toolCall.result_message.tool_result
+ ? html`
+ <pre class="tool-call-result-preview">
+${toolCall.result_message.tool_result}</pre>`
+ : ""}`
+ : cancelButton}
+ </div>`;
+ case "codereview":
+ return html`
+ <div class="tool-call-compact-view">
+ ${status}
+ <span class="tool-call-name">${toolCall.name}</span>
+ ${cancelButton}
+ <code class="codereview-preview codereview-${toolCall.result_message?.tool_result}">${toolCall.result_message?.tool_result == 'OK' ? '✔️': '⛔ ' + toolCall.result_message?.tool_result}</code>
+ </div>`;
+ case "think":
+ const thinkInput = JSON.parse(toolCall.input);
+ return html`
+ <div class="tool-call-compact-view">
+ ${status}
+ <span class="tool-call-name">${toolCall.name}</span>
+ <div class="thought-bubble"><div class="preview">${thinkInput.thoughts}</div></div>
+ ${cancelButton}
+ </div>`;
+ case "patch":
+ const patchInput = JSON.parse(toolCall.input);
+ return html`
+ <div class="tool-call-compact-view">
+ ${status}
+ <span class="tool-call-name">${toolCall.name}</span>
+ <div class="patch-input-preview"><span class="patch-path">${patchInput.path}</span>: ${patchInput.patches.length} edit${patchInput.patches.length > 1 ? 's': ''}</div>
+ ${cancelButton}
+ </div>`;
+ case "done":
+ const doneInput = JSON.parse(toolCall.input);
+ return html`
+ <div class="tool-call-compact-view">
+ ${status}
+ <span class="tool-call-name">${toolCall.name}</span>
+ <div class="done-input-preview">
+ ${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>
+ ${cancelButton}
+ </div>`;
+
+ default: // Generic tool card:
+ return html`
+ <div class="tool-call-compact-view">
+ ${status}
+ <span class="tool-call-name">${toolCall.name}</span>
+ <code class="tool-call-input-preview">${toolCall.input}</code>
+ ${cancelButton}
+ <code class="tool-call-result-preview">${toolCall.result_message?.tool_result}</code>
+ </div>
+ ${toolCall.result_message?.tool_result}
+ `;
+ }
+ }
+ render() {
+ return html`
+ <div class="tool-calls-container">
+ <div class="tool-calls-header"></div>
+ <div class="tool-call-cards-container">
+ ${this.toolCalls?.map((toolCall) => {
+ return html`<div class="tool-call-card ${toolCall.name}">
+ ${this.toolCard(toolCall)}
+ </div>`;
+ })}
+ </div>
+ </div>`;
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "sketch-tool-calls": SketchToolCalls;
+ }
+}
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
new file mode 100644
index 0000000..beb0b67
--- /dev/null
+++ b/loop/webui/src/web-components/sketch-view-mode-select.test.ts
@@ -0,0 +1,99 @@
+import { html, fixture, expect, oneEvent, elementUpdated, fixtureCleanup } from "@open-wc/testing";
+import "./sketch-view-mode-select";
+import type { SketchViewModeSelect } from "./sketch-view-mode-select";
+
+describe("SketchViewModeSelect", () => {
+ afterEach(() => {
+ fixtureCleanup();
+ });
+
+ it("initializes with 'chat' as the default mode", async () => {
+ const el: SketchViewModeSelect = await fixture(html`
+ <sketch-view-mode-select></sketch-view-mode-select>
+ `);
+
+ expect(el.activeMode).to.equal("chat");
+ const chatButton = el.shadowRoot!.querySelector("#showConversationButton");
+ expect(chatButton!.classList.contains("active")).to.be.true;
+ });
+
+ it("displays all four view mode buttons", async () => {
+ const el: SketchViewModeSelect = await fixture(html`
+ <sketch-view-mode-select></sketch-view-mode-select>
+ `);
+
+ const buttons = el.shadowRoot!.querySelectorAll(".emoji-button");
+ expect(buttons.length).to.equal(4);
+
+ const chatButton = el.shadowRoot!.querySelector("#showConversationButton");
+ const diffButton = el.shadowRoot!.querySelector("#showDiffButton");
+ const chartsButton = el.shadowRoot!.querySelector("#showChartsButton");
+ const terminalButton = el.shadowRoot!.querySelector("#showTerminalButton");
+
+ expect(chatButton).to.exist;
+ expect(diffButton).to.exist;
+ expect(chartsButton).to.exist;
+ expect(terminalButton).to.exist;
+
+ expect(chatButton!.getAttribute("title")).to.equal("Conversation View");
+ expect(diffButton!.getAttribute("title")).to.equal("Diff View");
+ expect(chartsButton!.getAttribute("title")).to.equal("Charts View");
+ expect(terminalButton!.getAttribute("title")).to.equal("Terminal View");
+ });
+
+ it("dispatches view-mode-select event when clicking a mode button", async () => {
+ const el: SketchViewModeSelect = await fixture(html`
+ <sketch-view-mode-select></sketch-view-mode-select>
+ `);
+
+ const diffButton = el.shadowRoot!.querySelector("#showDiffButton") as HTMLButtonElement;
+
+ // Setup listener for the view-mode-select event
+ setTimeout(() => diffButton.click());
+ const { detail } = await oneEvent(el, "view-mode-select");
+
+ expect(detail.mode).to.equal("diff");
+ });
+
+ it("updates the active mode when receiving update-active-mode event", async () => {
+ const el: SketchViewModeSelect = await fixture(html`
+ <sketch-view-mode-select></sketch-view-mode-select>
+ `);
+
+ // Initially should be in chat mode
+ expect(el.activeMode).to.equal("chat");
+
+ // Dispatch the update-active-mode event to change to diff mode
+ const updateEvent = new CustomEvent("update-active-mode", {
+ detail: { mode: "diff" },
+ bubbles: true
+ });
+ el.dispatchEvent(updateEvent);
+
+ // Wait for the component to update
+ await elementUpdated(el);
+
+ expect(el.activeMode).to.equal("diff");
+ const diffButton = el.shadowRoot!.querySelector("#showDiffButton");
+ expect(diffButton!.classList.contains("active")).to.be.true;
+ });
+
+ it("correctly marks the active button based on mode", async () => {
+ const el: SketchViewModeSelect = await fixture(html`
+ <sketch-view-mode-select activeMode="terminal"></sketch-view-mode-select>
+ `);
+
+ // Terminal button should be active
+ const terminalButton = el.shadowRoot!.querySelector("#showTerminalButton");
+ const chatButton = el.shadowRoot!.querySelector("#showConversationButton");
+ const diffButton = el.shadowRoot!.querySelector("#showDiffButton");
+ const chartsButton = el.shadowRoot!.querySelector("#showChartsButton");
+
+ expect(terminalButton!.classList.contains("active")).to.be.true;
+ expect(chatButton!.classList.contains("active")).to.be.false;
+ expect(diffButton!.classList.contains("active")).to.be.false;
+ expect(chartsButton!.classList.contains("active")).to.be.false;
+ });
+
+
+});
diff --git a/loop/webui/src/web-components/sketch-view-mode-select.ts b/loop/webui/src/web-components/sketch-view-mode-select.ts
new file mode 100644
index 0000000..b55282a
--- /dev/null
+++ b/loop/webui/src/web-components/sketch-view-mode-select.ts
@@ -0,0 +1,147 @@
+import {css, html, LitElement} from 'lit';
+import {customElement, property, state} from 'lit/decorators.js';
+import {DataManager, ConnectionStatus} from '../data';
+import {State, TimelineMessage} from '../types';
+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
+
+ // 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`
+/* 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;
+ }
+}
\ No newline at end of file
diff --git a/loop/webui/src/web-components/vega-embed.ts b/loop/webui/src/web-components/vega-embed.ts
new file mode 100644
index 0000000..04f0087
--- /dev/null
+++ b/loop/webui/src/web-components/vega-embed.ts
@@ -0,0 +1,86 @@
+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/tailwind.config.js b/loop/webui/tailwind.config.js
deleted file mode 100644
index 91d9b4b..0000000
--- a/loop/webui/tailwind.config.js
+++ /dev/null
@@ -1,10 +0,0 @@
-/** @type {import('tailwindcss').Config} */
-module.exports = {
- content: [
- "./src/**/*.{html,js,ts}",
- ],
- theme: {
- extend: {},
- },
- plugins: [],
-};
diff --git a/loop/webui/tsconfig.json b/loop/webui/tsconfig.json
index 810eb41..8b79a51 100644
--- a/loop/webui/tsconfig.json
+++ b/loop/webui/tsconfig.json
@@ -4,6 +4,7 @@
"module": "ESNext",
"moduleResolution": "node",
"esModuleInterop": true,
+ "experimentalDecorators": true,
"strict": false,
"sourceMap": true,
"outDir": "./dist",
@@ -12,6 +13,6 @@
"skipLibCheck": true,
"noImplicitAny": false
},
- "include": ["src/**/*"],
+ "include": ["src/**/*", "web-dev-server.config.mjs"],
"exclude": ["node_modules", "dist"]
}
diff --git a/loop/webui/web-dev-server.config.mjs b/loop/webui/web-dev-server.config.mjs
new file mode 100644
index 0000000..a1f598b
--- /dev/null
+++ b/loop/webui/web-dev-server.config.mjs
@@ -0,0 +1,13 @@
+import { hmrPlugin, presets } from "@open-wc/dev-server-hmr";
+
+export default {
+ port: 8000,
+ nodeResolve: true,
+
+ plugins: [
+ hmrPlugin({
+ include: ["../**/*"],
+ presets: [presets.lit],
+ }),
+ ],
+};
diff --git a/loop/webui/web-test-runner.config.mjs b/loop/webui/web-test-runner.config.mjs
new file mode 100644
index 0000000..64886f8
--- /dev/null
+++ b/loop/webui/web-test-runner.config.mjs
@@ -0,0 +1,27 @@
+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;
+ },
+});