webui: convert sketch-app-shell to use tailwind
Doing the conversion from shadowDOM to tailwind starting with the
outermost elements in the page because shadowDOM hides dom nodes from
ancestor styles, which means tailwind styles don't apply to an element
if its ancestors use shadowDOM.
- add new SketchTailwindElement that disables shadowDOM for tailwind
- convert sketch-app-shell to inherit from SketchTailwindElement
- convert sketch-app-shell's CSS from shadowDOM to tailwind classes
diff --git a/webui/package-lock.json b/webui/package-lock.json
index daae2d1..21b15b4 100644
--- a/webui/package-lock.json
+++ b/webui/package-lock.json
@@ -21,6 +21,7 @@
},
"devDependencies": {
"@sand4rt/experimental-ct-web": "^1.51.1",
+ "@tailwindcss/cli": "^4.1.10",
"@types/marked": "^5.0.2",
"@types/mocha": "^10.0.7",
"@types/node": "^22.13.14",
@@ -30,6 +31,7 @@
"esbuild": "^0.25.1",
"msw": "^2.7.5",
"prettier": "3.5.3",
+ "tailwindcss": "^4.1.10",
"typescript": "^5.8.3",
"vite": "^6.3.4",
"vite-plugin-web-components-hmr": "^0.1.3"
@@ -1117,6 +1119,19 @@
}
}
},
+ "node_modules/@isaacs/fs-minipass": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz",
+ "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "minipass": "^7.0.4"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.8",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
@@ -1271,6 +1286,315 @@
"integrity": "sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==",
"dev": true
},
+ "node_modules/@parcel/watcher": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz",
+ "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "dependencies": {
+ "detect-libc": "^1.0.3",
+ "is-glob": "^4.0.3",
+ "micromatch": "^4.0.5",
+ "node-addon-api": "^7.0.0"
+ },
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ },
+ "optionalDependencies": {
+ "@parcel/watcher-android-arm64": "2.5.1",
+ "@parcel/watcher-darwin-arm64": "2.5.1",
+ "@parcel/watcher-darwin-x64": "2.5.1",
+ "@parcel/watcher-freebsd-x64": "2.5.1",
+ "@parcel/watcher-linux-arm-glibc": "2.5.1",
+ "@parcel/watcher-linux-arm-musl": "2.5.1",
+ "@parcel/watcher-linux-arm64-glibc": "2.5.1",
+ "@parcel/watcher-linux-arm64-musl": "2.5.1",
+ "@parcel/watcher-linux-x64-glibc": "2.5.1",
+ "@parcel/watcher-linux-x64-musl": "2.5.1",
+ "@parcel/watcher-win32-arm64": "2.5.1",
+ "@parcel/watcher-win32-ia32": "2.5.1",
+ "@parcel/watcher-win32-x64": "2.5.1"
+ }
+ },
+ "node_modules/@parcel/watcher-android-arm64": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz",
+ "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-darwin-arm64": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz",
+ "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-darwin-x64": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz",
+ "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-freebsd-x64": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz",
+ "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-linux-arm-glibc": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz",
+ "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-linux-arm-musl": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz",
+ "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-linux-arm64-glibc": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz",
+ "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-linux-arm64-musl": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz",
+ "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-linux-x64-glibc": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz",
+ "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-linux-x64-musl": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz",
+ "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-win32-arm64": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz",
+ "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-win32-ia32": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz",
+ "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-win32-x64": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz",
+ "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
"node_modules/@playwright/experimental-ct-core": {
"version": "1.51.1",
"resolved": "https://registry.npmjs.org/@playwright/experimental-ct-core/-/experimental-ct-core-1.51.1.tgz",
@@ -1643,6 +1967,297 @@
"node": ">=18"
}
},
+ "node_modules/@tailwindcss/cli": {
+ "version": "4.1.10",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/cli/-/cli-4.1.10.tgz",
+ "integrity": "sha512-TuO7IOUpTG1JeqtMQbQXjR4RIhfZ43mor/vpCp3S5X9h0WxUom5NYgxfNO0PiFoLMJ6/eYCelC7KGvUOmqqK6A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@parcel/watcher": "^2.5.1",
+ "@tailwindcss/node": "4.1.10",
+ "@tailwindcss/oxide": "4.1.10",
+ "enhanced-resolve": "^5.18.1",
+ "mri": "^1.2.0",
+ "picocolors": "^1.1.1",
+ "tailwindcss": "4.1.10"
+ },
+ "bin": {
+ "tailwindcss": "dist/index.mjs"
+ }
+ },
+ "node_modules/@tailwindcss/node": {
+ "version": "4.1.10",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.10.tgz",
+ "integrity": "sha512-2ACf1znY5fpRBwRhMgj9ZXvb2XZW8qs+oTfotJ2C5xR0/WNL7UHZ7zXl6s+rUqedL1mNi+0O+WQr5awGowS3PQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@ampproject/remapping": "^2.3.0",
+ "enhanced-resolve": "^5.18.1",
+ "jiti": "^2.4.2",
+ "lightningcss": "1.30.1",
+ "magic-string": "^0.30.17",
+ "source-map-js": "^1.2.1",
+ "tailwindcss": "4.1.10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide": {
+ "version": "4.1.10",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.10.tgz",
+ "integrity": "sha512-v0C43s7Pjw+B9w21htrQwuFObSkio2aV/qPx/mhrRldbqxbWJK6KizM+q7BF1/1CmuLqZqX3CeYF7s7P9fbA8Q==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "dependencies": {
+ "detect-libc": "^2.0.4",
+ "tar": "^7.4.3"
+ },
+ "engines": {
+ "node": ">= 10"
+ },
+ "optionalDependencies": {
+ "@tailwindcss/oxide-android-arm64": "4.1.10",
+ "@tailwindcss/oxide-darwin-arm64": "4.1.10",
+ "@tailwindcss/oxide-darwin-x64": "4.1.10",
+ "@tailwindcss/oxide-freebsd-x64": "4.1.10",
+ "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.10",
+ "@tailwindcss/oxide-linux-arm64-gnu": "4.1.10",
+ "@tailwindcss/oxide-linux-arm64-musl": "4.1.10",
+ "@tailwindcss/oxide-linux-x64-gnu": "4.1.10",
+ "@tailwindcss/oxide-linux-x64-musl": "4.1.10",
+ "@tailwindcss/oxide-wasm32-wasi": "4.1.10",
+ "@tailwindcss/oxide-win32-arm64-msvc": "4.1.10",
+ "@tailwindcss/oxide-win32-x64-msvc": "4.1.10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-android-arm64": {
+ "version": "4.1.10",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.10.tgz",
+ "integrity": "sha512-VGLazCoRQ7rtsCzThaI1UyDu/XRYVyH4/EWiaSX6tFglE+xZB5cvtC5Omt0OQ+FfiIVP98su16jDVHDEIuH4iQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-darwin-arm64": {
+ "version": "4.1.10",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.10.tgz",
+ "integrity": "sha512-ZIFqvR1irX2yNjWJzKCqTCcHZbgkSkSkZKbRM3BPzhDL/18idA8uWCoopYA2CSDdSGFlDAxYdU2yBHwAwx8euQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-darwin-x64": {
+ "version": "4.1.10",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.10.tgz",
+ "integrity": "sha512-eCA4zbIhWUFDXoamNztmS0MjXHSEJYlvATzWnRiTqJkcUteSjO94PoRHJy1Xbwp9bptjeIxxBHh+zBWFhttbrQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-freebsd-x64": {
+ "version": "4.1.10",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.10.tgz",
+ "integrity": "sha512-8/392Xu12R0cc93DpiJvNpJ4wYVSiciUlkiOHOSOQNH3adq9Gi/dtySK7dVQjXIOzlpSHjeCL89RUUI8/GTI6g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
+ "version": "4.1.10",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.10.tgz",
+ "integrity": "sha512-t9rhmLT6EqeuPT+MXhWhlRYIMSfh5LZ6kBrC4FS6/+M1yXwfCtp24UumgCWOAJVyjQwG+lYva6wWZxrfvB+NhQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
+ "version": "4.1.10",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.10.tgz",
+ "integrity": "sha512-3oWrlNlxLRxXejQ8zImzrVLuZ/9Z2SeKoLhtCu0hpo38hTO2iL86eFOu4sVR8cZc6n3z7eRXXqtHJECa6mFOvA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm64-musl": {
+ "version": "4.1.10",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.10.tgz",
+ "integrity": "sha512-saScU0cmWvg/Ez4gUmQWr9pvY9Kssxt+Xenfx1LG7LmqjcrvBnw4r9VjkFcqmbBb7GCBwYNcZi9X3/oMda9sqQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-x64-gnu": {
+ "version": "4.1.10",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.10.tgz",
+ "integrity": "sha512-/G3ao/ybV9YEEgAXeEg28dyH6gs1QG8tvdN9c2MNZdUXYBaIY/Gx0N6RlJzfLy/7Nkdok4kaxKPHKJUlAaoTdA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-x64-musl": {
+ "version": "4.1.10",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.10.tgz",
+ "integrity": "sha512-LNr7X8fTiKGRtQGOerSayc2pWJp/9ptRYAa4G+U+cjw9kJZvkopav1AQc5HHD+U364f71tZv6XamaHKgrIoVzA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-wasm32-wasi": {
+ "version": "4.1.10",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.10.tgz",
+ "integrity": "sha512-d6ekQpopFQJAcIK2i7ZzWOYGZ+A6NzzvQ3ozBvWFdeyqfOZdYHU66g5yr+/HC4ipP1ZgWsqa80+ISNILk+ae/Q==",
+ "bundleDependencies": [
+ "@napi-rs/wasm-runtime",
+ "@emnapi/core",
+ "@emnapi/runtime",
+ "@tybys/wasm-util",
+ "@emnapi/wasi-threads",
+ "tslib"
+ ],
+ "cpu": [
+ "wasm32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/core": "^1.4.3",
+ "@emnapi/runtime": "^1.4.3",
+ "@emnapi/wasi-threads": "^1.0.2",
+ "@napi-rs/wasm-runtime": "^0.2.10",
+ "@tybys/wasm-util": "^0.9.0",
+ "tslib": "^2.8.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
+ "version": "4.1.10",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.10.tgz",
+ "integrity": "sha512-i1Iwg9gRbwNVOCYmnigWCCgow8nDWSFmeTUU5nbNx3rqbe4p0kRbEqLwLJbYZKmSSp23g4N6rCDmm7OuPBXhDA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-win32-x64-msvc": {
+ "version": "4.1.10",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.10.tgz",
+ "integrity": "sha512-sGiJTjcBSfGq2DVRtaSljq5ZgZS2SDHSIfhOylkBvHVjwOsodBhnb3HdmiKkVuUGKD0I7G63abMOVaskj1KpOA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide/node_modules/detect-libc": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz",
+ "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/@tootallnate/quickjs-emscripten": {
"version": "0.23.0",
"resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz",
@@ -3188,6 +3803,16 @@
"chevrotain": "^11.0.0"
}
},
+ "node_modules/chownr": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz",
+ "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/chrome-launcher": {
"version": "0.15.2",
"resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.15.2.tgz",
@@ -4150,6 +4775,19 @@
"npm": "1.2.8000 || >= 1.4.16"
}
},
+ "node_modules/detect-libc": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
+ "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "detect-libc": "bin/detect-libc.js"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
"node_modules/devtools-protocol": {
"version": "0.0.1312386",
"resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1312386.tgz",
@@ -4282,6 +4920,20 @@
"once": "^1.4.0"
}
},
+ "node_modules/enhanced-resolve": {
+ "version": "5.18.1",
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz",
+ "integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.4",
+ "tapable": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
"node_modules/entities": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
@@ -4834,6 +5486,13 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "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,
+ "license": "ISC"
+ },
"node_modules/graphql": {
"version": "16.10.0",
"resolved": "https://registry.npmjs.org/graphql/-/graphql-16.10.0.tgz",
@@ -5376,6 +6035,16 @@
"node": ">=8"
}
},
+ "node_modules/jiti": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz",
+ "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jiti": "lib/jiti-cli.mjs"
+ }
+ },
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -5709,6 +6378,255 @@
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"dev": true
},
+ "node_modules/lightningcss": {
+ "version": "1.30.1",
+ "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz",
+ "integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==",
+ "dev": true,
+ "license": "MPL-2.0",
+ "dependencies": {
+ "detect-libc": "^2.0.3"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ },
+ "optionalDependencies": {
+ "lightningcss-darwin-arm64": "1.30.1",
+ "lightningcss-darwin-x64": "1.30.1",
+ "lightningcss-freebsd-x64": "1.30.1",
+ "lightningcss-linux-arm-gnueabihf": "1.30.1",
+ "lightningcss-linux-arm64-gnu": "1.30.1",
+ "lightningcss-linux-arm64-musl": "1.30.1",
+ "lightningcss-linux-x64-gnu": "1.30.1",
+ "lightningcss-linux-x64-musl": "1.30.1",
+ "lightningcss-win32-arm64-msvc": "1.30.1",
+ "lightningcss-win32-x64-msvc": "1.30.1"
+ }
+ },
+ "node_modules/lightningcss-darwin-arm64": {
+ "version": "1.30.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.1.tgz",
+ "integrity": "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-x64": {
+ "version": "1.30.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.1.tgz",
+ "integrity": "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-freebsd-x64": {
+ "version": "1.30.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.1.tgz",
+ "integrity": "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm-gnueabihf": {
+ "version": "1.30.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.1.tgz",
+ "integrity": "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-gnu": {
+ "version": "1.30.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.1.tgz",
+ "integrity": "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-musl": {
+ "version": "1.30.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.1.tgz",
+ "integrity": "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-gnu": {
+ "version": "1.30.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.1.tgz",
+ "integrity": "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-musl": {
+ "version": "1.30.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.1.tgz",
+ "integrity": "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-arm64-msvc": {
+ "version": "1.30.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.1.tgz",
+ "integrity": "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-x64-msvc": {
+ "version": "1.30.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.1.tgz",
+ "integrity": "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss/node_modules/detect-libc": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz",
+ "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/lines-and-columns": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
@@ -5804,6 +6722,16 @@
"yallist": "^3.0.2"
}
},
+ "node_modules/magic-string": {
+ "version": "0.30.17",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
+ "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0"
+ }
+ },
"node_modules/make-dir": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz",
@@ -5943,12 +6871,51 @@
"node": ">=6"
}
},
+ "node_modules/minipass": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
+ "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ }
+ },
+ "node_modules/minizlib": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz",
+ "integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "minipass": "^7.1.2"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
"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": "3.0.1",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz",
+ "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "mkdirp": "dist/cjs/src/bin.js"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
"node_modules/mlly": {
"version": "1.7.4",
"resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.4.tgz",
@@ -5983,6 +6950,16 @@
"resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.52.2.tgz",
"integrity": "sha512-GEQWEZmfkOGLdd3XK8ryrfWz3AIP8YymVXiPHEdewrUq7mh0qrKrfHLNCXcbB6sTnMLnOZ3ztSiKcciFUkIJwQ=="
},
+ "node_modules/mri": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",
+ "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@@ -6095,6 +7072,13 @@
"node": ">= 0.4.0"
}
},
+ "node_modules/node-addon-api": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
+ "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/node-releases": {
"version": "2.0.19",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
@@ -7499,6 +8483,41 @@
"node": ">=12.17"
}
},
+ "node_modules/tailwindcss": {
+ "version": "4.1.10",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.10.tgz",
+ "integrity": "sha512-P3nr6WkvKV/ONsTzj6Gb57sWPMX29EPNPopo7+FcpkQaNsrNpZ1pv8QmrYI2RqEKD7mlGqLnGovlcYnBK0IqUA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tapable": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz",
+ "integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/tar": {
+ "version": "7.4.3",
+ "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz",
+ "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "@isaacs/fs-minipass": "^4.0.0",
+ "chownr": "^3.0.0",
+ "minipass": "^7.1.2",
+ "minizlib": "^3.0.1",
+ "mkdirp": "^3.0.1",
+ "yallist": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/tar-fs": {
"version": "3.0.9",
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.9.tgz",
@@ -7525,6 +8544,16 @@
"streamx": "^2.15.0"
}
},
+ "node_modules/tar/node_modules/yallist": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz",
+ "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/text-decoder": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz",
diff --git a/webui/package.json b/webui/package.json
index 14f940f..55e0a4b 100644
--- a/webui/package.json
+++ b/webui/package.json
@@ -21,6 +21,7 @@
"gentypes": "go run ../cmd/go2ts -o src/types.ts",
"build": "go run ../cmd/go2ts -o src/types.ts && tsc",
"watch": "tsc --watch",
+ "tailwind": "tailwindcss -i ./src/global.css -o ./src/tailwind.css",
"test": "tsc && npm run test:playwright",
"test:playwright": "playwright test -c playwright-ct.config.ts"
},
@@ -37,6 +38,7 @@
},
"devDependencies": {
"@sand4rt/experimental-ct-web": "^1.51.1",
+ "@tailwindcss/cli": "^4.1.10",
"@types/marked": "^5.0.2",
"@types/mocha": "^10.0.7",
"@types/node": "^22.13.14",
@@ -46,6 +48,7 @@
"esbuild": "^0.25.1",
"msw": "^2.7.5",
"prettier": "3.5.3",
+ "tailwindcss": "^4.1.10",
"typescript": "^5.8.3",
"vite": "^6.3.4",
"vite-plugin-web-components-hmr": "^0.1.3"
diff --git a/webui/src/global.css b/webui/src/global.css
new file mode 100644
index 0000000..f1d8c73
--- /dev/null
+++ b/webui/src/global.css
@@ -0,0 +1 @@
+@import "tailwindcss";
diff --git a/webui/src/sketch-app-shell.html b/webui/src/sketch-app-shell.html
index 8127fa2..bf1fd13 100644
--- a/webui/src/sketch-app-shell.html
+++ b/webui/src/sketch-app-shell.html
@@ -7,6 +7,7 @@
<link rel="stylesheet" href="static/sketch-app-shell.css" />
<script src="static/sketch-app-shell.js" async type="module"></script>
<script src="static/interface-detection.js"></script>
+ <link rel="stylesheet" href="static/tailwind.css" />
</head>
<body>
<sketch-app-shell></sketch-app-shell>
diff --git a/webui/src/tailwind.css b/webui/src/tailwind.css
new file mode 100644
index 0000000..05a41c8
--- /dev/null
+++ b/webui/src/tailwind.css
@@ -0,0 +1,1332 @@
+/*! tailwindcss v4.1.8 | MIT License | https://tailwindcss.com */
+@layer properties;
+@layer theme, base, components, utilities;
+@layer theme {
+ :root,
+ :host {
+ --font-sans:
+ ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji",
+ "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
+ --font-mono:
+ ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono",
+ "Courier New", monospace;
+ --color-red-300: oklch(80.8% 0.114 19.571);
+ --color-red-600: oklch(57.7% 0.245 27.325);
+ --color-red-700: oklch(50.5% 0.213 27.518);
+ --color-orange-50: oklch(98% 0.016 73.684);
+ --color-orange-500: oklch(70.5% 0.213 47.604);
+ --color-orange-800: oklch(47% 0.157 37.304);
+ --color-green-600: oklch(62.7% 0.194 149.214);
+ --color-blue-50: oklch(97% 0.014 254.604);
+ --color-blue-100: oklch(93.2% 0.032 255.585);
+ --color-blue-400: oklch(70.7% 0.165 254.624);
+ --color-blue-500: oklch(62.3% 0.214 259.815);
+ --color-blue-600: oklch(54.6% 0.245 262.881);
+ --color-blue-700: oklch(48.8% 0.243 264.376);
+ --color-blue-800: oklch(42.4% 0.199 265.638);
+ --color-gray-50: oklch(98.5% 0.002 247.839);
+ --color-gray-100: oklch(96.7% 0.003 264.542);
+ --color-gray-200: oklch(92.8% 0.006 264.531);
+ --color-gray-300: oklch(87.2% 0.01 258.338);
+ --color-gray-400: oklch(70.7% 0.022 261.325);
+ --color-gray-500: oklch(55.1% 0.027 264.364);
+ --color-gray-600: oklch(44.6% 0.03 256.802);
+ --color-gray-700: oklch(37.3% 0.034 259.733);
+ --color-gray-800: oklch(27.8% 0.033 256.848);
+ --color-white: #fff;
+ --spacing: 0.25rem;
+ --container-6xl: 72rem;
+ --text-xs: 0.75rem;
+ --text-xs--line-height: calc(1 / 0.75);
+ --text-sm: 0.875rem;
+ --text-sm--line-height: calc(1.25 / 0.875);
+ --text-base: 1rem;
+ --text-base--line-height: calc(1.5 / 1);
+ --text-lg: 1.125rem;
+ --text-lg--line-height: calc(1.75 / 1.125);
+ --font-weight-normal: 400;
+ --font-weight-medium: 500;
+ --font-weight-semibold: 600;
+ --font-weight-bold: 700;
+ --leading-relaxed: 1.625;
+ --radius-lg: 0.5rem;
+ --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
+ --animate-pulse: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
+ --default-transition-duration: 150ms;
+ --default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+ --default-font-family: var(--font-sans);
+ --default-mono-font-family: var(--font-mono);
+ }
+}
+@layer base {
+ *,
+ ::after,
+ ::before,
+ ::backdrop,
+ ::file-selector-button {
+ box-sizing: border-box;
+ margin: 0;
+ padding: 0;
+ border: 0 solid;
+ }
+ html,
+ :host {
+ line-height: 1.5;
+ -webkit-text-size-adjust: 100%;
+ tab-size: 4;
+ font-family: var(
+ --default-font-family,
+ ui-sans-serif,
+ system-ui,
+ sans-serif,
+ "Apple Color Emoji",
+ "Segoe UI Emoji",
+ "Segoe UI Symbol",
+ "Noto Color Emoji"
+ );
+ font-feature-settings: var(--default-font-feature-settings, normal);
+ font-variation-settings: var(--default-font-variation-settings, normal);
+ -webkit-tap-highlight-color: transparent;
+ }
+ hr {
+ height: 0;
+ color: inherit;
+ border-top-width: 1px;
+ }
+ abbr:where([title]) {
+ -webkit-text-decoration: underline dotted;
+ text-decoration: underline dotted;
+ }
+ h1,
+ h2,
+ h3,
+ h4,
+ h5,
+ h6 {
+ font-size: inherit;
+ font-weight: inherit;
+ }
+ a {
+ color: inherit;
+ -webkit-text-decoration: inherit;
+ text-decoration: inherit;
+ }
+ b,
+ strong {
+ font-weight: bolder;
+ }
+ code,
+ kbd,
+ samp,
+ pre {
+ font-family: var(
+ --default-mono-font-family,
+ ui-monospace,
+ SFMono-Regular,
+ Menlo,
+ Monaco,
+ Consolas,
+ "Liberation Mono",
+ "Courier New",
+ monospace
+ );
+ font-feature-settings: var(--default-mono-font-feature-settings, normal);
+ font-variation-settings: var(
+ --default-mono-font-variation-settings,
+ normal
+ );
+ font-size: 1em;
+ }
+ small {
+ font-size: 80%;
+ }
+ sub,
+ sup {
+ font-size: 75%;
+ line-height: 0;
+ position: relative;
+ vertical-align: baseline;
+ }
+ sub {
+ bottom: -0.25em;
+ }
+ sup {
+ top: -0.5em;
+ }
+ table {
+ text-indent: 0;
+ border-color: inherit;
+ border-collapse: collapse;
+ }
+ :-moz-focusring {
+ outline: auto;
+ }
+ progress {
+ vertical-align: baseline;
+ }
+ summary {
+ display: list-item;
+ }
+ ol,
+ ul,
+ menu {
+ list-style: none;
+ }
+ img,
+ svg,
+ video,
+ canvas,
+ audio,
+ iframe,
+ embed,
+ object {
+ display: block;
+ vertical-align: middle;
+ }
+ img,
+ video {
+ max-width: 100%;
+ height: auto;
+ }
+ button,
+ input,
+ select,
+ optgroup,
+ textarea,
+ ::file-selector-button {
+ font: inherit;
+ font-feature-settings: inherit;
+ font-variation-settings: inherit;
+ letter-spacing: inherit;
+ color: inherit;
+ border-radius: 0;
+ background-color: transparent;
+ opacity: 1;
+ }
+ :where(select:is([multiple], [size])) optgroup {
+ font-weight: bolder;
+ }
+ :where(select:is([multiple], [size])) optgroup option {
+ padding-inline-start: 20px;
+ }
+ ::file-selector-button {
+ margin-inline-end: 4px;
+ }
+ ::placeholder {
+ opacity: 1;
+ }
+ @supports (not (-webkit-appearance: -apple-pay-button)) or
+ (contain-intrinsic-size: 1px) {
+ ::placeholder {
+ color: currentcolor;
+ @supports (color: color-mix(in lab, red, red)) {
+ color: color-mix(in oklab, currentcolor 50%, transparent);
+ }
+ }
+ }
+ textarea {
+ resize: vertical;
+ }
+ ::-webkit-search-decoration {
+ -webkit-appearance: none;
+ }
+ ::-webkit-date-and-time-value {
+ min-height: 1lh;
+ text-align: inherit;
+ }
+ ::-webkit-datetime-edit {
+ display: inline-flex;
+ }
+ ::-webkit-datetime-edit-fields-wrapper {
+ padding: 0;
+ }
+ ::-webkit-datetime-edit,
+ ::-webkit-datetime-edit-year-field,
+ ::-webkit-datetime-edit-month-field,
+ ::-webkit-datetime-edit-day-field,
+ ::-webkit-datetime-edit-hour-field,
+ ::-webkit-datetime-edit-minute-field,
+ ::-webkit-datetime-edit-second-field,
+ ::-webkit-datetime-edit-millisecond-field,
+ ::-webkit-datetime-edit-meridiem-field {
+ padding-block: 0;
+ }
+ :-moz-ui-invalid {
+ box-shadow: none;
+ }
+ button,
+ input:where([type="button"], [type="reset"], [type="submit"]),
+ ::file-selector-button {
+ appearance: button;
+ }
+ ::-webkit-inner-spin-button,
+ ::-webkit-outer-spin-button {
+ height: auto;
+ }
+ [hidden]:where(:not([hidden="until-found"])) {
+ display: none !important;
+ }
+}
+@layer utilities {
+ .collapse {
+ visibility: collapse;
+ }
+ .invisible {
+ visibility: hidden;
+ }
+ .visible {
+ visibility: visible;
+ }
+ .absolute {
+ position: absolute;
+ }
+ .fixed {
+ position: fixed;
+ }
+ .relative {
+ position: relative;
+ }
+ .static {
+ position: static;
+ }
+ .top-12 {
+ top: calc(var(--spacing) * 12);
+ }
+ .top-full {
+ top: 100%;
+ }
+ .right-0 {
+ right: calc(var(--spacing) * 0);
+ }
+ .right-4 {
+ right: calc(var(--spacing) * 4);
+ }
+ .z-10 {
+ z-index: 10;
+ }
+ .z-\[100\] {
+ z-index: 100;
+ }
+ .col-span-full {
+ grid-column: 1 / -1;
+ }
+ .container {
+ width: 100%;
+ @media (width >= 40rem) {
+ max-width: 40rem;
+ }
+ @media (width >= 48rem) {
+ max-width: 48rem;
+ }
+ @media (width >= 64rem) {
+ max-width: 64rem;
+ }
+ @media (width >= 80rem) {
+ max-width: 80rem;
+ }
+ @media (width >= 96rem) {
+ max-width: 96rem;
+ }
+ }
+ .m-0 {
+ margin: calc(var(--spacing) * 0);
+ }
+ .mx-auto {
+ margin-inline: auto;
+ }
+ .mt-1\.5 {
+ margin-top: calc(var(--spacing) * 1.5);
+ }
+ .mt-2 {
+ margin-top: calc(var(--spacing) * 2);
+ }
+ .mt-2\.5 {
+ margin-top: calc(var(--spacing) * 2.5);
+ }
+ .mr-0 {
+ margin-right: calc(var(--spacing) * 0);
+ }
+ .mr-1 {
+ margin-right: calc(var(--spacing) * 1);
+ }
+ .mr-1\.5 {
+ margin-right: calc(var(--spacing) * 1.5);
+ }
+ .mr-2\.5 {
+ margin-right: calc(var(--spacing) * 2.5);
+ }
+ .mr-12 {
+ margin-right: calc(var(--spacing) * 12);
+ }
+ .mr-96 {
+ margin-right: calc(var(--spacing) * 96);
+ }
+ .mb-0 {
+ margin-bottom: calc(var(--spacing) * 0);
+ }
+ .mb-2 {
+ margin-bottom: calc(var(--spacing) * 2);
+ }
+ .ml-1 {
+ margin-left: calc(var(--spacing) * 1);
+ }
+ .ml-2 {
+ margin-left: calc(var(--spacing) * 2);
+ }
+ .block {
+ display: block;
+ }
+ .flex {
+ display: flex;
+ }
+ .grid {
+ display: grid;
+ }
+ .hidden {
+ display: none;
+ }
+ .inline-flex {
+ display: inline-flex;
+ }
+ .h-4 {
+ height: calc(var(--spacing) * 4);
+ }
+ .h-5 {
+ height: calc(var(--spacing) * 5);
+ }
+ .h-6 {
+ height: calc(var(--spacing) * 6);
+ }
+ .h-12 {
+ height: calc(var(--spacing) * 12);
+ }
+ .h-full {
+ height: 100%;
+ }
+ .h-screen {
+ height: 100vh;
+ }
+ .min-h-0 {
+ min-height: calc(var(--spacing) * 0);
+ }
+ .w-4 {
+ width: calc(var(--spacing) * 4);
+ }
+ .w-5 {
+ width: calc(var(--spacing) * 5);
+ }
+ .w-6 {
+ width: calc(var(--spacing) * 6);
+ }
+ .w-96 {
+ width: calc(var(--spacing) * 96);
+ }
+ .w-\[calc\(100\%-2\.5rem\)\] {
+ width: calc(100% - 2.5rem);
+ }
+ .w-\[calc\(100\%-24rem\)\] {
+ width: calc(100% - 24rem);
+ }
+ .w-full {
+ width: 100%;
+ }
+ .max-w-6xl {
+ max-width: var(--container-6xl);
+ }
+ .max-w-\[30\%\] {
+ max-width: 30%;
+ }
+ .max-w-full {
+ max-width: 100%;
+ }
+ .max-w-none {
+ max-width: none;
+ }
+ .min-w-24 {
+ min-width: calc(var(--spacing) * 24);
+ }
+ .min-w-96 {
+ min-width: calc(var(--spacing) * 96);
+ }
+ .flex-1 {
+ flex: 1;
+ }
+ .flex-shrink {
+ flex-shrink: 1;
+ }
+ .flex-shrink-0 {
+ flex-shrink: 0;
+ }
+ .flex-grow {
+ flex-grow: 1;
+ }
+ .transform {
+ transform: var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,)
+ var(--tw-skew-x,) var(--tw-skew-y,);
+ }
+ .animate-pulse {
+ animation: var(--animate-pulse);
+ }
+ .cursor-default {
+ cursor: default;
+ }
+ .cursor-pointer {
+ cursor: pointer;
+ }
+ .resize {
+ resize: both;
+ }
+ .grid-cols-2 {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ }
+ .grid-cols-\[repeat\(auto-fill\,minmax\(150px\,1fr\)\)\] {
+ grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
+ }
+ .flex-col {
+ flex-direction: column;
+ }
+ .flex-nowrap {
+ flex-wrap: nowrap;
+ }
+ .flex-wrap {
+ flex-wrap: wrap;
+ }
+ .items-center {
+ align-items: center;
+ }
+ .justify-between {
+ justify-content: space-between;
+ }
+ .justify-center {
+ justify-content: center;
+ }
+ .justify-start {
+ justify-content: flex-start;
+ }
+ .gap-0\.5 {
+ gap: calc(var(--spacing) * 0.5);
+ }
+ .gap-1\.5 {
+ gap: calc(var(--spacing) * 1.5);
+ }
+ .gap-2 {
+ gap: calc(var(--spacing) * 2);
+ }
+ .gap-2\.5 {
+ gap: calc(var(--spacing) * 2.5);
+ }
+ .gap-4 {
+ gap: calc(var(--spacing) * 4);
+ }
+ .gap-5 {
+ gap: calc(var(--spacing) * 5);
+ }
+ .self-end {
+ align-self: flex-end;
+ }
+ .self-stretch {
+ align-self: stretch;
+ }
+ .overflow-hidden {
+ overflow: hidden;
+ }
+ .overflow-x-hidden {
+ overflow-x: hidden;
+ }
+ .overflow-y-auto {
+ overflow-y: auto;
+ }
+ .rounded {
+ border-radius: 0.25rem;
+ }
+ .rounded-full {
+ border-radius: calc(infinity * 1px);
+ }
+ .rounded-lg {
+ border-radius: var(--radius-lg);
+ }
+ .border {
+ border-style: var(--tw-border-style);
+ border-width: 1px;
+ }
+ .border-t {
+ border-top-style: var(--tw-border-style);
+ border-top-width: 1px;
+ }
+ .border-r {
+ border-right-style: var(--tw-border-style);
+ border-right-width: 1px;
+ }
+ .border-b {
+ border-bottom-style: var(--tw-border-style);
+ border-bottom-width: 1px;
+ }
+ .border-b-2 {
+ border-bottom-style: var(--tw-border-style);
+ border-bottom-width: 2px;
+ }
+ .border-l {
+ border-left-style: var(--tw-border-style);
+ border-left-width: 1px;
+ }
+ .border-l-4 {
+ border-left-style: var(--tw-border-style);
+ border-left-width: 4px;
+ }
+ .border-none {
+ --tw-border-style: none;
+ border-style: none;
+ }
+ .border-blue-400 {
+ border-color: var(--color-blue-400);
+ }
+ .border-gray-200 {
+ border-color: var(--color-gray-200);
+ }
+ .border-gray-300 {
+ border-color: var(--color-gray-300);
+ }
+ .border-orange-500 {
+ border-color: var(--color-orange-500);
+ }
+ .border-transparent {
+ border-color: transparent;
+ }
+ .border-b-blue-500 {
+ border-bottom-color: var(--color-blue-500);
+ }
+ .bg-blue-50 {
+ background-color: var(--color-blue-50);
+ }
+ .bg-blue-100 {
+ background-color: var(--color-blue-100);
+ }
+ .bg-blue-500 {
+ background-color: var(--color-blue-500);
+ }
+ .bg-blue-700 {
+ background-color: var(--color-blue-700);
+ }
+ .bg-gray-50 {
+ background-color: var(--color-gray-50);
+ }
+ .bg-gray-100 {
+ background-color: var(--color-gray-100);
+ }
+ .bg-gray-600 {
+ background-color: var(--color-gray-600);
+ }
+ .bg-orange-50 {
+ background-color: var(--color-orange-50);
+ }
+ .bg-red-600 {
+ background-color: var(--color-red-600);
+ }
+ .bg-transparent {
+ background-color: transparent;
+ }
+ .bg-white {
+ background-color: var(--color-white);
+ }
+ .p-0 {
+ padding: calc(var(--spacing) * 0);
+ }
+ .px-1\.5 {
+ padding-inline: calc(var(--spacing) * 1.5);
+ }
+ .px-2 {
+ padding-inline: calc(var(--spacing) * 2);
+ }
+ .px-2\.5 {
+ padding-inline: calc(var(--spacing) * 2.5);
+ }
+ .px-3 {
+ padding-inline: calc(var(--spacing) * 3);
+ }
+ .px-4 {
+ padding-inline: calc(var(--spacing) * 4);
+ }
+ .px-5 {
+ padding-inline: calc(var(--spacing) * 5);
+ }
+ .py-0\.5 {
+ padding-block: calc(var(--spacing) * 0.5);
+ }
+ .py-1 {
+ padding-block: calc(var(--spacing) * 1);
+ }
+ .py-1\.5 {
+ padding-block: calc(var(--spacing) * 1.5);
+ }
+ .py-2 {
+ padding-block: calc(var(--spacing) * 2);
+ }
+ .py-2\.5 {
+ padding-block: calc(var(--spacing) * 2.5);
+ }
+ .pt-0 {
+ padding-top: calc(var(--spacing) * 0);
+ }
+ .pt-1\.5 {
+ padding-top: calc(var(--spacing) * 1.5);
+ }
+ .pt-2\.5 {
+ padding-top: calc(var(--spacing) * 2.5);
+ }
+ .pr-8 {
+ padding-right: calc(var(--spacing) * 8);
+ }
+ .pb-2\.5 {
+ padding-bottom: calc(var(--spacing) * 2.5);
+ }
+ .pl-4 {
+ padding-left: calc(var(--spacing) * 4);
+ }
+ .align-middle {
+ vertical-align: middle;
+ }
+ .font-mono {
+ font-family: var(--font-mono);
+ }
+ .font-sans {
+ font-family: var(--font-sans);
+ }
+ .text-base {
+ font-size: var(--text-base);
+ line-height: var(--tw-leading, var(--text-base--line-height));
+ }
+ .text-lg {
+ font-size: var(--text-lg);
+ line-height: var(--tw-leading, var(--text-lg--line-height));
+ }
+ .text-sm {
+ font-size: var(--text-sm);
+ line-height: var(--tw-leading, var(--text-sm--line-height));
+ }
+ .text-xs {
+ font-size: var(--text-xs);
+ line-height: var(--tw-leading, var(--text-xs--line-height));
+ }
+ .leading-relaxed {
+ --tw-leading: var(--leading-relaxed);
+ line-height: var(--leading-relaxed);
+ }
+ .font-bold {
+ --tw-font-weight: var(--font-weight-bold);
+ font-weight: var(--font-weight-bold);
+ }
+ .font-medium {
+ --tw-font-weight: var(--font-weight-medium);
+ font-weight: var(--font-weight-medium);
+ }
+ .font-normal {
+ --tw-font-weight: var(--font-weight-normal);
+ font-weight: var(--font-weight-normal);
+ }
+ .font-semibold {
+ --tw-font-weight: var(--font-weight-semibold);
+ font-weight: var(--font-weight-semibold);
+ }
+ .break-all {
+ word-break: break-all;
+ }
+ .text-ellipsis {
+ text-overflow: ellipsis;
+ }
+ .whitespace-nowrap {
+ white-space: nowrap;
+ }
+ .text-blue-500 {
+ color: var(--color-blue-500);
+ }
+ .text-blue-600 {
+ color: var(--color-blue-600);
+ }
+ .text-gray-400 {
+ color: var(--color-gray-400);
+ }
+ .text-gray-500 {
+ color: var(--color-gray-500);
+ }
+ .text-gray-600 {
+ color: var(--color-gray-600);
+ }
+ .text-gray-600\/85 {
+ color: color-mix(in srgb, oklch(44.6% 0.03 256.802) 85%, transparent);
+ @supports (color: color-mix(in lab, red, red)) {
+ color: color-mix(in oklab, var(--color-gray-600) 85%, transparent);
+ }
+ }
+ .text-gray-800 {
+ color: var(--color-gray-800);
+ }
+ .text-green-600 {
+ color: var(--color-green-600);
+ }
+ .text-orange-800 {
+ color: var(--color-orange-800);
+ }
+ .text-white {
+ color: var(--color-white);
+ }
+ .italic {
+ font-style: italic;
+ }
+ .no-underline {
+ text-decoration-line: none;
+ }
+ .opacity-70 {
+ opacity: 70%;
+ }
+ .shadow {
+ --tw-shadow:
+ 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)),
+ 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1));
+ box-shadow:
+ var(--tw-inset-shadow), var(--tw-inset-ring-shadow),
+ var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
+ }
+ .shadow-\[0_-2px_10px_rgba\(0\,0\,0\,0\.1\)\] {
+ --tw-shadow: 0 -2px 10px var(--tw-shadow-color, rgba(0, 0, 0, 0.1));
+ box-shadow:
+ var(--tw-inset-shadow), var(--tw-inset-ring-shadow),
+ var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
+ }
+ .shadow-lg {
+ --tw-shadow:
+ 0 10px 15px -3px var(--tw-shadow-color, rgb(0 0 0 / 0.1)),
+ 0 4px 6px -4px var(--tw-shadow-color, rgb(0 0 0 / 0.1));
+ box-shadow:
+ var(--tw-inset-shadow), var(--tw-inset-ring-shadow),
+ var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
+ }
+ .shadow-sm {
+ --tw-shadow:
+ 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)),
+ 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1));
+ box-shadow:
+ var(--tw-inset-shadow), var(--tw-inset-ring-shadow),
+ var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
+ }
+ .outline {
+ outline-style: var(--tw-outline-style);
+ outline-width: 1px;
+ }
+ .blur {
+ --tw-blur: blur(8px);
+ filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,)
+ var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,)
+ var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);
+ }
+ .backdrop-filter {
+ -webkit-backdrop-filter: var(--tw-backdrop-blur,)
+ var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,)
+ var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,)
+ var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,)
+ var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);
+ backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,)
+ var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,)
+ var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,)
+ var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,)
+ var(--tw-backdrop-sepia,);
+ }
+ .transition {
+ transition-property:
+ color,
+ background-color,
+ border-color,
+ outline-color,
+ text-decoration-color,
+ fill,
+ stroke,
+ --tw-gradient-from,
+ --tw-gradient-via,
+ --tw-gradient-to,
+ opacity,
+ box-shadow,
+ transform,
+ translate,
+ scale,
+ rotate,
+ filter,
+ -webkit-backdrop-filter,
+ backdrop-filter,
+ display,
+ visibility,
+ content-visibility,
+ overlay,
+ pointer-events;
+ transition-timing-function: var(
+ --tw-ease,
+ var(--default-transition-timing-function)
+ );
+ transition-duration: var(--tw-duration, var(--default-transition-duration));
+ }
+ .transition-\[bottom\] {
+ transition-property: bottom;
+ transition-timing-function: var(
+ --tw-ease,
+ var(--default-transition-timing-function)
+ );
+ transition-duration: var(--tw-duration, var(--default-transition-duration));
+ }
+ .transition-\[margin-right\] {
+ transition-property: margin-right;
+ transition-timing-function: var(
+ --tw-ease,
+ var(--default-transition-timing-function)
+ );
+ transition-duration: var(--tw-duration, var(--default-transition-duration));
+ }
+ .transition-all {
+ transition-property: all;
+ transition-timing-function: var(
+ --tw-ease,
+ var(--default-transition-timing-function)
+ );
+ transition-duration: var(--tw-duration, var(--default-transition-duration));
+ }
+ .duration-200 {
+ --tw-duration: 200ms;
+ transition-duration: 200ms;
+ }
+ .ease-in-out {
+ --tw-ease: var(--ease-in-out);
+ transition-timing-function: var(--ease-in-out);
+ }
+ .group-hover\:opacity-100 {
+ &:is(:where(.group):hover *) {
+ @media (hover: hover) {
+ opacity: 100%;
+ }
+ }
+ }
+ .hover\:bg-blue-800 {
+ &:hover {
+ @media (hover: hover) {
+ background-color: var(--color-blue-800);
+ }
+ }
+ }
+ .hover\:bg-gray-200 {
+ &:hover {
+ @media (hover: hover) {
+ background-color: var(--color-gray-200);
+ }
+ }
+ }
+ .hover\:bg-gray-700 {
+ &:hover {
+ @media (hover: hover) {
+ background-color: var(--color-gray-700);
+ }
+ }
+ }
+ .hover\:bg-red-700 {
+ &:hover {
+ @media (hover: hover) {
+ background-color: var(--color-red-700);
+ }
+ }
+ }
+ .hover\:text-blue-600 {
+ &:hover {
+ @media (hover: hover) {
+ color: var(--color-blue-600);
+ }
+ }
+ }
+ .hover\:underline {
+ &:hover {
+ @media (hover: hover) {
+ text-decoration-line: underline;
+ }
+ }
+ }
+ .disabled\:cursor-not-allowed {
+ &:disabled {
+ cursor: not-allowed;
+ }
+ }
+ .disabled\:bg-gray-400 {
+ &:disabled {
+ background-color: var(--color-gray-400);
+ }
+ }
+ .disabled\:bg-red-300 {
+ &:disabled {
+ background-color: var(--color-red-300);
+ }
+ }
+ .disabled\:opacity-70 {
+ &:disabled {
+ opacity: 70%;
+ }
+ }
+ .disabled\:hover\:bg-red-300 {
+ &:disabled {
+ &:hover {
+ @media (hover: hover) {
+ background-color: var(--color-red-300);
+ }
+ }
+ }
+ }
+ .max-xl\:hidden {
+ @media (width < 80rem) {
+ display: none;
+ }
+ }
+ .max-xl\:px-1\.5 {
+ @media (width < 80rem) {
+ padding-inline: calc(var(--spacing) * 1.5);
+ }
+ }
+ .max-xl\:px-2\.5 {
+ @media (width < 80rem) {
+ padding-inline: calc(var(--spacing) * 2.5);
+ }
+ }
+ .max-xl\:py-1\.5 {
+ @media (width < 80rem) {
+ padding-block: calc(var(--spacing) * 1.5);
+ }
+ }
+ .max-md\:mr-0 {
+ @media (width < 48rem) {
+ margin-right: calc(var(--spacing) * 0);
+ }
+ }
+ .max-md\:hidden {
+ @media (width < 48rem) {
+ display: none;
+ }
+ }
+ .max-md\:w-full {
+ @media (width < 48rem) {
+ width: 100%;
+ }
+ }
+ .md\:mr-72 {
+ @media (width >= 48rem) {
+ margin-right: calc(var(--spacing) * 72);
+ }
+ }
+ .md\:w-72 {
+ @media (width >= 48rem) {
+ width: calc(var(--spacing) * 72);
+ }
+ }
+ .md\:w-\[calc\(100\%-18rem\)\] {
+ @media (width >= 48rem) {
+ width: calc(100% - 18rem);
+ }
+ }
+ .lg\:mr-80 {
+ @media (width >= 64rem) {
+ margin-right: calc(var(--spacing) * 80);
+ }
+ }
+ .lg\:w-80 {
+ @media (width >= 64rem) {
+ width: calc(var(--spacing) * 80);
+ }
+ }
+ .lg\:w-\[calc\(100\%-20rem\)\] {
+ @media (width >= 64rem) {
+ width: calc(100% - 20rem);
+ }
+ }
+ .xl\:mr-96 {
+ @media (width >= 80rem) {
+ margin-right: calc(var(--spacing) * 96);
+ }
+ }
+ .xl\:inline {
+ @media (width >= 80rem) {
+ display: inline;
+ }
+ }
+ .xl\:w-96 {
+ @media (width >= 80rem) {
+ width: calc(var(--spacing) * 96);
+ }
+ }
+ .xl\:w-\[calc\(100\%-24rem\)\] {
+ @media (width >= 80rem) {
+ width: calc(100% - 24rem);
+ }
+ }
+ .xl\:px-2\.5 {
+ @media (width >= 80rem) {
+ padding-inline: calc(var(--spacing) * 2.5);
+ }
+ }
+ .xl\:py-1 {
+ @media (width >= 80rem) {
+ padding-block: calc(var(--spacing) * 1);
+ }
+ }
+}
+@property --tw-rotate-x {
+ syntax: "*";
+ inherits: false;
+}
+@property --tw-rotate-y {
+ syntax: "*";
+ inherits: false;
+}
+@property --tw-rotate-z {
+ syntax: "*";
+ inherits: false;
+}
+@property --tw-skew-x {
+ syntax: "*";
+ inherits: false;
+}
+@property --tw-skew-y {
+ syntax: "*";
+ inherits: false;
+}
+@property --tw-border-style {
+ syntax: "*";
+ inherits: false;
+ initial-value: solid;
+}
+@property --tw-leading {
+ syntax: "*";
+ inherits: false;
+}
+@property --tw-font-weight {
+ syntax: "*";
+ inherits: false;
+}
+@property --tw-shadow {
+ syntax: "*";
+ inherits: false;
+ initial-value: 0 0 #0000;
+}
+@property --tw-shadow-color {
+ syntax: "*";
+ inherits: false;
+}
+@property --tw-shadow-alpha {
+ syntax: "<percentage>";
+ inherits: false;
+ initial-value: 100%;
+}
+@property --tw-inset-shadow {
+ syntax: "*";
+ inherits: false;
+ initial-value: 0 0 #0000;
+}
+@property --tw-inset-shadow-color {
+ syntax: "*";
+ inherits: false;
+}
+@property --tw-inset-shadow-alpha {
+ syntax: "<percentage>";
+ inherits: false;
+ initial-value: 100%;
+}
+@property --tw-ring-color {
+ syntax: "*";
+ inherits: false;
+}
+@property --tw-ring-shadow {
+ syntax: "*";
+ inherits: false;
+ initial-value: 0 0 #0000;
+}
+@property --tw-inset-ring-color {
+ syntax: "*";
+ inherits: false;
+}
+@property --tw-inset-ring-shadow {
+ syntax: "*";
+ inherits: false;
+ initial-value: 0 0 #0000;
+}
+@property --tw-ring-inset {
+ syntax: "*";
+ inherits: false;
+}
+@property --tw-ring-offset-width {
+ syntax: "<length>";
+ inherits: false;
+ initial-value: 0px;
+}
+@property --tw-ring-offset-color {
+ syntax: "*";
+ inherits: false;
+ initial-value: #fff;
+}
+@property --tw-ring-offset-shadow {
+ syntax: "*";
+ inherits: false;
+ initial-value: 0 0 #0000;
+}
+@property --tw-outline-style {
+ syntax: "*";
+ inherits: false;
+ initial-value: solid;
+}
+@property --tw-blur {
+ syntax: "*";
+ inherits: false;
+}
+@property --tw-brightness {
+ syntax: "*";
+ inherits: false;
+}
+@property --tw-contrast {
+ syntax: "*";
+ inherits: false;
+}
+@property --tw-grayscale {
+ syntax: "*";
+ inherits: false;
+}
+@property --tw-hue-rotate {
+ syntax: "*";
+ inherits: false;
+}
+@property --tw-invert {
+ syntax: "*";
+ inherits: false;
+}
+@property --tw-opacity {
+ syntax: "*";
+ inherits: false;
+}
+@property --tw-saturate {
+ syntax: "*";
+ inherits: false;
+}
+@property --tw-sepia {
+ syntax: "*";
+ inherits: false;
+}
+@property --tw-drop-shadow {
+ syntax: "*";
+ inherits: false;
+}
+@property --tw-drop-shadow-color {
+ syntax: "*";
+ inherits: false;
+}
+@property --tw-drop-shadow-alpha {
+ syntax: "<percentage>";
+ inherits: false;
+ initial-value: 100%;
+}
+@property --tw-drop-shadow-size {
+ syntax: "*";
+ inherits: false;
+}
+@property --tw-backdrop-blur {
+ syntax: "*";
+ inherits: false;
+}
+@property --tw-backdrop-brightness {
+ syntax: "*";
+ inherits: false;
+}
+@property --tw-backdrop-contrast {
+ syntax: "*";
+ inherits: false;
+}
+@property --tw-backdrop-grayscale {
+ syntax: "*";
+ inherits: false;
+}
+@property --tw-backdrop-hue-rotate {
+ syntax: "*";
+ inherits: false;
+}
+@property --tw-backdrop-invert {
+ syntax: "*";
+ inherits: false;
+}
+@property --tw-backdrop-opacity {
+ syntax: "*";
+ inherits: false;
+}
+@property --tw-backdrop-saturate {
+ syntax: "*";
+ inherits: false;
+}
+@property --tw-backdrop-sepia {
+ syntax: "*";
+ inherits: false;
+}
+@property --tw-duration {
+ syntax: "*";
+ inherits: false;
+}
+@property --tw-ease {
+ syntax: "*";
+ inherits: false;
+}
+@keyframes pulse {
+ 50% {
+ opacity: 0.5;
+ }
+}
+@layer properties {
+ @supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or
+ ((-moz-orient: inline) and (not (color: rgb(from red r g b)))) {
+ *,
+ ::before,
+ ::after,
+ ::backdrop {
+ --tw-rotate-x: initial;
+ --tw-rotate-y: initial;
+ --tw-rotate-z: initial;
+ --tw-skew-x: initial;
+ --tw-skew-y: initial;
+ --tw-border-style: solid;
+ --tw-leading: initial;
+ --tw-font-weight: initial;
+ --tw-shadow: 0 0 #0000;
+ --tw-shadow-color: initial;
+ --tw-shadow-alpha: 100%;
+ --tw-inset-shadow: 0 0 #0000;
+ --tw-inset-shadow-color: initial;
+ --tw-inset-shadow-alpha: 100%;
+ --tw-ring-color: initial;
+ --tw-ring-shadow: 0 0 #0000;
+ --tw-inset-ring-color: initial;
+ --tw-inset-ring-shadow: 0 0 #0000;
+ --tw-ring-inset: initial;
+ --tw-ring-offset-width: 0px;
+ --tw-ring-offset-color: #fff;
+ --tw-ring-offset-shadow: 0 0 #0000;
+ --tw-outline-style: solid;
+ --tw-blur: initial;
+ --tw-brightness: initial;
+ --tw-contrast: initial;
+ --tw-grayscale: initial;
+ --tw-hue-rotate: initial;
+ --tw-invert: initial;
+ --tw-opacity: initial;
+ --tw-saturate: initial;
+ --tw-sepia: initial;
+ --tw-drop-shadow: initial;
+ --tw-drop-shadow-color: initial;
+ --tw-drop-shadow-alpha: 100%;
+ --tw-drop-shadow-size: initial;
+ --tw-backdrop-blur: initial;
+ --tw-backdrop-brightness: initial;
+ --tw-backdrop-contrast: initial;
+ --tw-backdrop-grayscale: initial;
+ --tw-backdrop-hue-rotate: initial;
+ --tw-backdrop-invert: initial;
+ --tw-backdrop-opacity: initial;
+ --tw-backdrop-saturate: initial;
+ --tw-backdrop-sepia: initial;
+ --tw-duration: initial;
+ --tw-ease: initial;
+ }
+ }
+}
diff --git a/webui/src/web-components/sketch-app-shell.ts b/webui/src/web-components/sketch-app-shell.ts
index 7f51576..f37d9c9 100644
--- a/webui/src/web-components/sketch-app-shell.ts
+++ b/webui/src/web-components/sketch-app-shell.ts
@@ -3,6 +3,7 @@
import { ConnectionStatus, DataManager } from "../data";
import { AgentMessage, GitLogEntry, State } from "../types";
import { aggregateAgentMessages } from "./aggregateAgentMessages";
+import { SketchTailwindElement } from "./sketch-tailwind-element";
import "./sketch-chat-input";
import "./sketch-container-status";
@@ -24,7 +25,7 @@
type ViewMode = "chat" | "diff2" | "terminal";
@customElement("sketch-app-shell")
-export class SketchAppShell extends LitElement {
+export class SketchAppShell extends SketchTailwindElement {
// Current view mode (chat, diff, terminal)
@state()
viewMode: ViewMode = "chat";
@@ -39,512 +40,32 @@
// Reference to the container status element
containerStatusElement: any = null;
- // 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`
- .copied-indicator {
- position: absolute;
- top: -20px;
- left: 50%;
- transform: translateX(-50%);
- background: rgba(40, 167, 69, 0.9);
- color: white;
- padding: 2px 6px;
- border-radius: 3px;
- font-size: 10px;
- font-family: system-ui, sans-serif;
- animation: fadeInOut 2s ease;
- pointer-events: none;
- }
+ // Note: CSS styles have been converted to Tailwind classes applied directly to HTML elements
+ // since this component now extends SketchTailwindElement which disables shadow DOM
- @keyframes fadeInOut {
- 0% {
- opacity: 0;
- }
- 20% {
- opacity: 1;
- }
- 80% {
- opacity: 1;
- }
- 100% {
- opacity: 0;
- }
- }
-
- .commit-branch-indicator {
- color: #28a745;
- }
-
- .commit-hash-indicator {
- color: #0366d6;
- }
- :host {
- display: block;
- font-family:
- system-ui,
- -apple-system,
- BlinkMacSystemFont,
- "Segoe UI",
- Roboto,
- sans-serif;
- color: #333;
- line-height: 1.4;
- height: 100vh;
- width: 100%;
- position: relative;
- overflow-x: hidden;
- display: flex;
- flex-direction: column;
- }
-
- /* Top banner with combined elements */
- #top-banner {
- display: flex;
- align-self: stretch;
- justify-content: space-between;
- align-items: center;
- padding: 0 20px;
- margin-bottom: 0;
- border-bottom: 1px solid #eee;
- gap: 20px;
- background: white;
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
- width: 100%;
- height: 48px;
- padding-right: 30px; /* Extra padding on the right to prevent elements from hitting the edge */
- }
-
- /* View mode container styles - mirroring timeline.css structure */
- #view-container {
- align-self: stretch;
- overflow-y: auto;
- flex: 1;
- display: flex;
- flex-direction: column;
- min-height: 0; /* Critical for proper flex child behavior */
- }
-
- #view-container-inner {
- max-width: 1200px;
- width: calc(100% - 40px);
- margin: 0 auto;
- position: relative;
- padding-bottom: 10px;
- padding-top: 10px;
- display: flex;
- flex-direction: column;
- height: 100%; /* Ensure it takes full height of parent */
- }
-
- /* Adjust view container when todo panel is visible in chat mode */
- #view-container-inner.with-todo-panel {
- max-width: none;
- width: 100%;
- margin: 0;
- padding-left: 20px;
- padding-right: 20px;
- }
-
- #chat-input {
- align-self: flex-end;
- width: 100%;
- box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1);
- }
-
- .banner-title {
- font-size: 18px;
- font-weight: 600;
- margin: 0;
- min-width: 6em;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- }
-
- .banner-title a {
- color: inherit;
- text-decoration: none;
- transition: opacity 0.2s ease;
- display: flex;
- align-items: center;
- gap: 8px;
- }
-
- .banner-title a:hover {
- opacity: 0.8;
- text-decoration: underline;
- }
-
- .banner-title img {
- width: 20px;
- height: 20px;
- border-radius: 3px;
- }
-
- /* Mobile-specific styles for banner link */
- @media (max-width: 768px) {
- .title-container {
- max-width: 50%; /* Allow more space on mobile */
- }
-
- .banner-title {
- font-size: 16px; /* Slightly smaller on mobile */
- }
-
- .banner-title img {
- width: 18px;
- height: 18px;
- }
- }
-
- @media (max-width: 480px) {
- .title-container {
- max-width: 60%; /* Even more space on very small screens */
- }
-
- .banner-title {
- font-size: 14px;
- }
-
- .banner-title img {
- width: 16px;
- height: 16px;
- }
- }
-
- .slug-title {
- margin: 0;
- padding: 0;
- color: rgba(82, 82, 82, 0.85);
- font-size: 14px;
- font-weight: normal;
- font-style: italic;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- }
-
- /* Allow the container to expand to full width and height in diff mode */
- #view-container-inner.diff2-active {
- max-width: 100%;
- width: 100%;
- height: 100%;
- padding: 0; /* Remove padding for more space */
- display: flex;
- flex-direction: column;
- flex: 1;
- min-height: 0; /* Critical for flex behavior */
- }
-
- /* Individual view styles */
- .chat-view,
- .diff2-view,
- .terminal-view {
- display: none; /* Hidden by default */
- width: 100%;
- height: 100%;
- }
-
- /* Make chat view take full width available */
- .chat-view.view-active {
- display: flex;
- flex-direction: column;
- width: 100%;
- height: 100%;
- }
-
- /* Chat timeline container - takes full width, memory panel will be positioned separately */
- .chat-timeline-container {
- flex: 1;
- display: flex;
- flex-direction: column;
- width: 100%;
- height: 100%;
- margin-right: 0; /* Default - no memory panel */
- transition: margin-right 0.2s ease; /* Smooth transition */
- }
-
- /* Adjust chat timeline container when todo panel is visible */
- .chat-timeline-container.with-todo-panel {
- margin-right: 400px; /* Make space for fixed todo panel */
- width: calc(100% - 400px); /* Explicitly set width to prevent overlap */
- }
-
- /* Todo panel container - fixed to right side */
- .todo-panel-container {
- position: fixed;
- top: 48px; /* Below top banner */
- right: 15px; /* Leave space for scroll bar */
- width: 400px;
- bottom: var(
- --chat-input-height,
- 90px
- ); /* Dynamic height based on chat input size */
- background-color: #fafafa;
- border-left: 1px solid #e0e0e0;
- z-index: 100;
- display: none; /* Hidden by default */
- transition: bottom 0.2s ease; /* Smooth transition when height changes */
- /* Add fuzzy gradient at bottom to blend with text entry */
- background: linear-gradient(
- to bottom,
- #fafafa 0%,
- #fafafa 90%,
- rgba(250, 250, 250, 0.5) 95%,
- rgba(250, 250, 250, 0.2) 100%
- );
- }
-
- .todo-panel-container.visible {
- display: block;
- }
-
- /* Responsive adjustments for todo panel */
- @media (max-width: 1200px) {
- .todo-panel-container {
- width: 350px;
- /* bottom is still controlled by --chat-input-height CSS variable */
- }
- .chat-timeline-container.with-todo-panel {
- margin-right: 350px;
- width: calc(100% - 350px);
- }
- }
-
- @media (max-width: 900px) {
- .todo-panel-container {
- width: 300px;
- /* bottom is still controlled by --chat-input-height CSS variable */
- }
- .chat-timeline-container.with-todo-panel {
- margin-right: 300px;
- width: calc(100% - 300px);
- }
- }
-
- /* On very small screens, hide todo panel or make it overlay */
- @media (max-width: 768px) {
- .todo-panel-container.visible {
- display: none; /* Hide on mobile */
- }
- .chat-timeline-container.with-todo-panel {
- margin-right: 0;
+ // Override createRenderRoot to apply host styles for proper sizing while still using light DOM
+ createRenderRoot() {
+ // Use light DOM like SketchTailwindElement but still apply host styles
+ const style = document.createElement("style");
+ style.textContent = `
+ sketch-app-shell {
+ display: block;
width: 100%;
+ height: 100vh;
+ max-width: 100%;
+ box-sizing: border-box;
+ overflow: hidden;
}
+ `;
+
+ // Add the style to the document head if not already present
+ if (!document.head.querySelector("style[data-sketch-app-shell]")) {
+ style.setAttribute("data-sketch-app-shell", "");
+ document.head.appendChild(style);
}
- /* Monaco diff2 view needs to take all available space */
- .diff2-view.view-active {
- flex: 1;
- overflow: hidden;
- min-height: 0; /* Required for proper flex child behavior */
- display: flex;
- flex-direction: column;
- height: 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: 30%;
- padding: 6px 0;
- }
-
- .refresh-control {
- display: flex;
- align-items: center;
- margin-bottom: 0;
- flex-wrap: nowrap;
- white-space: nowrap;
- flex-shrink: 0;
- gap: 15px;
- padding-left: 15px;
- margin-right: 50px;
- }
-
- .stop-button,
- .end-button {
- background: #2196f3;
- color: white;
- border: none;
- padding: 4px 10px;
- border-radius: 4px;
- cursor: pointer;
- font-size: 12px;
- margin-right: 5px;
- display: flex;
- align-items: center;
- gap: 6px;
- }
-
- .stop-button {
- background: #dc3545;
- color: white;
- }
-
- .stop-button:hover:not(:disabled) {
- background-color: #c82333;
- }
-
- .stop-button:disabled {
- background-color: #e9a8ad;
- cursor: not-allowed;
- opacity: 0.7;
- }
-
- .stop-button:disabled:hover {
- background-color: #e9a8ad;
- }
-
- .end-button {
- background: #6c757d;
- color: white;
- }
-
- .end-button:hover:not(:disabled) {
- background-color: #5a6268;
- }
-
- .end-button:disabled {
- background-color: #a9acaf;
- cursor: not-allowed;
- opacity: 0.7;
- }
-
- .button-icon {
- width: 16px;
- height: 16px;
- }
-
- @media (max-width: 1400px) {
- .button-text {
- display: none;
- }
-
- .stop-button {
- padding: 6px;
- }
- }
-
- /* Removed poll-updates class */
-
- .notifications-toggle {
- display: flex;
- align-items: center;
- font-size: 12px;
- margin-right: 10px;
- cursor: pointer;
- }
-
- .bell-icon {
- width: 20px;
- height: 20px;
- position: relative;
- display: inline-flex;
- align-items: center;
- justify-content: center;
- }
-
- .bell-disabled::before {
- content: "";
- position: absolute;
- width: 2px;
- height: 24px;
- background-color: #dc3545;
- transform: rotate(45deg);
- transform-origin: center center;
- }
-
- /* Print styles for full chat printing */
- @media print {
- :host {
- height: auto !important;
- overflow: visible !important;
- display: block !important;
- }
-
- /* Hide non-essential UI elements during printing */
- #top-banner {
- border-bottom: 1px solid #000;
- box-shadow: none;
- page-break-inside: avoid;
- }
-
- /* Hide interactive elements that aren't useful in print */
- .stop-button,
- .end-button,
- .notifications-toggle,
- sketch-call-status,
- sketch-network-status,
- sketch-view-mode-select {
- display: none !important;
- }
-
- /* Ensure view container can expand to full content */
- #view-container {
- overflow: visible !important;
- flex: none !important;
- height: auto !important;
- max-height: none !important;
- }
-
- #view-container-inner {
- height: auto !important;
- max-height: none !important;
- overflow: visible !important;
- }
-
- /* Remove todo panel from print to avoid layout issues */
- .todo-panel-container {
- display: none !important;
- }
-
- /* Ensure chat timeline container takes full width in print */
- .chat-timeline-container {
- margin-right: 0 !important;
- width: 100% !important;
- height: auto !important;
- overflow: visible !important;
- }
-
- /* Make chat view fully visible */
- .chat-view {
- height: auto !important;
- overflow: visible !important;
- }
-
- /* Hide chat input during printing */
- #chat-input {
- display: none !important;
- }
-
- /* Adjust diff2 and terminal views for print */
- .diff2-view,
- .terminal-view {
- height: auto !important;
- overflow: visible !important;
- }
-
- /* Ensure only active view is shown in print */
- .chat-view:not(.view-active),
- .diff2-view:not(.view-active),
- .terminal-view:not(.view-active) {
- display: none !important;
- }
- }
- `;
+ return this;
+ }
// Header bar: Network connection status details
@property()
@@ -859,68 +380,39 @@
// Wait for DOM update to complete
this.updateComplete.then(() => {
- // Update active view
- const viewContainerInner = this.shadowRoot?.querySelector(
- "#view-container-inner",
- );
- const chatView = this.shadowRoot?.querySelector(".chat-view");
- const diff2View = this.shadowRoot?.querySelector(".diff2-view");
- const terminalView = this.shadowRoot?.querySelector(".terminal-view");
-
- // Remove active class from all views
- chatView?.classList.remove("view-active");
- diff2View?.classList.remove("view-active");
- terminalView?.classList.remove("view-active");
-
- // Add/remove diff2-active class on view container
- if (mode === "diff2") {
- viewContainerInner?.classList.add("diff2-active");
- } else {
- viewContainerInner?.classList.remove("diff2-active");
+ // Handle scroll position restoration for chat view
+ if (
+ mode === "chat" &&
+ this.scrollContainerRef.value &&
+ this._chatScrollPosition > 0
+ ) {
+ // Use requestAnimationFrame to ensure DOM is ready
+ requestAnimationFrame(() => {
+ if (this.scrollContainerRef.value) {
+ // Double-check that we're still in chat mode and the container is available
+ if (
+ this.viewMode === "chat" &&
+ this.scrollContainerRef.value.isConnected
+ ) {
+ this.scrollContainerRef.value.scrollTop =
+ this._chatScrollPosition;
+ }
+ }
+ });
}
- // Add active class to the selected view
- switch (mode) {
- case "chat":
- chatView?.classList.add("view-active");
- // Restore scroll position if we're switching back to chat
- if (this.scrollContainerRef.value && this._chatScrollPosition > 0) {
- // Use requestAnimationFrame to ensure DOM is ready
- requestAnimationFrame(() => {
- if (this.scrollContainerRef.value) {
- // Double-check that we're still in chat mode and the container is available
- if (
- this.viewMode === "chat" &&
- this.scrollContainerRef.value.isConnected
- ) {
- this.scrollContainerRef.value.scrollTop =
- this._chatScrollPosition;
- }
- }
- });
- }
- break;
-
- case "diff2":
- diff2View?.classList.add("view-active");
- // Refresh git/recentlog when Monaco diff view is opened
- // This ensures branch information is always up-to-date, as branches can change frequently
- const diff2ViewComp =
- this.shadowRoot?.querySelector("sketch-diff2-view");
- if (diff2ViewComp) {
- (diff2ViewComp as SketchDiff2View).refreshDiffView();
- }
- break;
-
- case "terminal":
- terminalView?.classList.add("view-active");
- break;
+ // Handle diff2 view specific logic
+ if (mode === "diff2") {
+ // Refresh git/recentlog when Monaco diff view is opened
+ // This ensures branch information is always up-to-date, as branches can change frequently
+ const diff2ViewComp = this.querySelector("sketch-diff2-view");
+ if (diff2ViewComp) {
+ (diff2ViewComp as SketchDiff2View).refreshDiffView();
+ }
}
// Update view mode buttons
- const viewModeSelect = this.shadowRoot?.querySelector(
- "sketch-view-mode-select",
- );
+ const viewModeSelect = this.querySelector("sketch-view-mode-select");
if (viewModeSelect) {
const event = new CustomEvent("update-active-mode", {
detail: { mode },
@@ -1352,194 +844,239 @@
render() {
return html`
- <div id="top-banner">
- <div class="title-container">
- <h1 class="banner-title">
- ${this.containerState?.skaband_addr
- ? html`<a
- href="${this.containerState.skaband_addr}"
- target="_blank"
- rel="noopener noreferrer"
- >
- <img
- src="${this.containerState.skaband_addr}/sketch.dev.png"
- alt="sketch"
- />
- sketch
- </a>`
- : html`sketch`}
- </h1>
- <h2 class="slug-title">${this.slug}</h2>
- </div>
-
- <!-- Container status info moved above tabs -->
- <sketch-container-status
- .state=${this.containerState}
- id="container-status"
- ></sketch-container-status>
-
- <!-- Last Commit section moved to sketch-container-status -->
-
- <!-- Views section with tabs -->
- <sketch-view-mode-select
- .diffLinesAdded=${this.containerState?.diff_lines_added || 0}
- .diffLinesRemoved=${this.containerState?.diff_lines_removed || 0}
- ></sketch-view-mode-select>
-
- <div class="refresh-control">
- <button
- id="stopButton"
- class="stop-button"
- ?disabled=${(this.containerState?.outstanding_llm_calls || 0) ===
- 0 &&
- (this.containerState?.outstanding_tool_calls || []).length === 0}
- >
- <svg
- class="button-icon"
- xmlns="http://www.w3.org/2000/svg"
- viewBox="0 0 24 24"
- fill="none"
- stroke="currentColor"
- stroke-width="2"
- stroke-linecap="round"
- stroke-linejoin="round"
- >
- <rect x="6" y="6" width="12" height="12" />
- </svg>
- <span class="button-text">Stop</span>
- </button>
- <button
- id="endButton"
- class="end-button"
- @click=${this._handleEndClick}
- >
- <svg
- class="button-icon"
- xmlns="http://www.w3.org/2000/svg"
- viewBox="0 0 24 24"
- fill="none"
- stroke="currentColor"
- stroke-width="2"
- stroke-linecap="round"
- stroke-linejoin="round"
- >
- <path d="M18 6L6 18" />
- <path d="M6 6l12 12" />
- </svg>
- <span class="button-text">End</span>
- </button>
-
+ <!-- Main container: flex column, full height, system font, hidden overflow-x -->
+ <div
+ class="block font-sans text-gray-800 leading-relaxed h-screen w-full relative overflow-x-hidden flex flex-col"
+ >
+ <!-- Top banner: flex row, space between, border bottom, shadow -->
+ <div
+ id="top-banner"
+ class="flex self-stretch justify-between items-center px-5 pr-8 mb-0 border-b border-gray-200 gap-5 bg-white shadow-md w-full h-12"
+ >
+ <!-- Title container -->
<div
- class="notifications-toggle"
- @click=${this._handleNotificationsToggle}
- title="${this.notificationsEnabled
- ? "Disable"
- : "Enable"} notifications when the agent completes its turn"
+ class="flex flex-col whitespace-nowrap overflow-hidden text-ellipsis max-w-[30%] md:max-w-1/2 sm:max-w-[60%] py-1.5"
>
- <div
- class="bell-icon ${!this.notificationsEnabled
- ? "bell-disabled"
- : ""}"
+ <h1
+ class="text-lg md:text-base sm:text-sm font-semibold m-0 min-w-24 whitespace-nowrap overflow-hidden text-ellipsis"
>
- <!-- Bell SVG icon -->
- <svg
- xmlns="http://www.w3.org/2000/svg"
- width="16"
- height="16"
- fill="currentColor"
- viewBox="0 0 16 16"
- >
- <path
- d="M8 16a2 2 0 0 0 2-2H6a2 2 0 0 0 2 2zM8 1.918l-.797.161A4.002 4.002 0 0 0 4 6c0 .628-.134 2.197-.459 3.742-.16.767-.376 1.566-.663 2.258h10.244c-.287-.692-.502-1.49-.663-2.258C12.134 8.197 12 6.628 12 6a4.002 4.002 0 0 0-3.203-3.92L8 1.917zM14.22 12c.223.447.481.801.78 1H1c.299-.199.557-.553.78-1C2.68 10.2 3 6.88 3 6c0-2.42 1.72-4.44 4.005-4.901a1 1 0 1 1 1.99 0A5.002 5.002 0 0 1 13 6c0 .88.32 4.2 1.22 6z"
- />
- </svg>
- </div>
+ ${this.containerState?.skaband_addr
+ ? html`<a
+ href="${this.containerState.skaband_addr}"
+ target="_blank"
+ rel="noopener noreferrer"
+ class="text-inherit no-underline transition-opacity duration-200 ease-in-out flex items-center gap-2 hover:opacity-80 hover:underline"
+ >
+ <img
+ src="${this.containerState.skaband_addr}/sketch.dev.png"
+ alt="sketch"
+ class="w-5 h-5 md:w-[18px] md:h-[18px] sm:w-4 sm:h-4 rounded-sm"
+ />
+ sketch
+ </a>`
+ : html`sketch`}
+ </h1>
+ <h2
+ class="m-0 p-0 text-gray-600 text-sm font-normal italic whitespace-nowrap overflow-hidden text-ellipsis"
+ >
+ ${this.slug}
+ </h2>
</div>
- <sketch-call-status
- .agentState=${this.containerState?.agent_state}
- .llmCalls=${this.containerState?.outstanding_llm_calls || 0}
- .toolCalls=${this.containerState?.outstanding_tool_calls || []}
- .isIdle=${(() => {
- const lastUserOrAgentMessage = this.getLastUserOrAgentMessage();
- return lastUserOrAgentMessage
- ? lastUserOrAgentMessage.end_of_turn &&
- !lastUserOrAgentMessage.parent_conversation_id
- : true;
- })()}
- .isDisconnected=${this.connectionStatus === "disconnected"}
- ></sketch-call-status>
+ <!-- Container status info moved above tabs -->
+ <sketch-container-status
+ .state=${this.containerState}
+ id="container-status"
+ ></sketch-container-status>
- <sketch-network-status
- connection=${this.connectionStatus}
- error=${this.connectionErrorMessage}
- ></sketch-network-status>
+ <!-- Last Commit section moved to sketch-container-status -->
+
+ <!-- Views section with tabs -->
+ <sketch-view-mode-select
+ .diffLinesAdded=${this.containerState?.diff_lines_added || 0}
+ .diffLinesRemoved=${this.containerState?.diff_lines_removed || 0}
+ ></sketch-view-mode-select>
+
+ <!-- Control buttons and status -->
+ <div
+ class="flex items-center mb-0 flex-nowrap whitespace-nowrap flex-shrink-0 gap-4 pl-4 mr-12"
+ >
+ <button
+ id="stopButton"
+ class="bg-red-600 hover:bg-red-700 disabled:bg-red-300 disabled:cursor-not-allowed disabled:opacity-70 text-white border-none px-2.5 py-1 xl:px-1.5 rounded cursor-pointer text-xs mr-1.5 flex items-center gap-1.5 transition-colors"
+ ?disabled=${(this.containerState?.outstanding_llm_calls || 0) ===
+ 0 &&
+ (this.containerState?.outstanding_tool_calls || []).length === 0}
+ >
+ <svg
+ class="w-4 h-4"
+ xmlns="http://www.w3.org/2000/svg"
+ viewBox="0 0 24 24"
+ fill="none"
+ stroke="currentColor"
+ stroke-width="2"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ >
+ <rect x="6" y="6" width="12" height="12" />
+ </svg>
+ <span class="xl:hidden">Stop</span>
+ </button>
+ <button
+ id="endButton"
+ class="bg-gray-600 hover:bg-gray-700 disabled:bg-gray-400 disabled:cursor-not-allowed disabled:opacity-70 text-white border-none px-2.5 py-1 xl:px-1.5 rounded cursor-pointer text-xs mr-1.5 flex items-center gap-1.5 transition-colors"
+ @click=${this._handleEndClick}
+ >
+ <svg
+ class="w-4 h-4"
+ xmlns="http://www.w3.org/2000/svg"
+ viewBox="0 0 24 24"
+ fill="none"
+ stroke="currentColor"
+ stroke-width="2"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ >
+ <path d="M18 6L6 18" />
+ <path d="M6 6l12 12" />
+ </svg>
+ <span class="xl:hidden">End</span>
+ </button>
+
+ <div
+ class="flex items-center text-xs mr-2.5 cursor-pointer"
+ @click=${this._handleNotificationsToggle}
+ title="${this.notificationsEnabled
+ ? "Disable"
+ : "Enable"} notifications when the agent completes its turn"
+ >
+ <div
+ class="w-5 h-5 relative inline-flex items-center justify-center"
+ >
+ <!-- Bell SVG icon -->
+ <svg
+ xmlns="http://www.w3.org/2000/svg"
+ width="16"
+ height="16"
+ fill="currentColor"
+ viewBox="0 0 16 16"
+ class="${!this.notificationsEnabled ? "relative z-10" : ""}"
+ >
+ <path
+ d="M8 16a2 2 0 0 0 2-2H6a2 2 0 0 0 2 2zM8 1.918l-.797.161A4.002 4.002 0 0 0 4 6c0 .628-.134 2.197-.459 3.742-.16.767-.376 1.566-.663 2.258h10.244c-.287-.692-.502-1.49-.663-2.258C12.134 8.197 12 6.628 12 6a4.002 4.002 0 0 0-3.203-3.92L8 1.917zM14.22 12c.223.447.481.801.78 1H1c.299-.199.557-.553.78-1C2.68 10.2 3 6.88 3 6c0-2.42 1.72-4.44 4.005-4.901a1 1 0 1 1 1.99 0A5.002 5.002 0 0 1 13 6c0 .88.32 4.2 1.22 6z"
+ />
+ </svg>
+ ${!this.notificationsEnabled
+ ? html`<div
+ class="absolute w-0.5 h-6 bg-red-600 rotate-45 origin-center"
+ ></div>`
+ : ""}
+ </div>
+ </div>
+
+ <sketch-call-status
+ .agentState=${this.containerState?.agent_state}
+ .llmCalls=${this.containerState?.outstanding_llm_calls || 0}
+ .toolCalls=${this.containerState?.outstanding_tool_calls || []}
+ .isIdle=${(() => {
+ const lastUserOrAgentMessage = this.getLastUserOrAgentMessage();
+ return lastUserOrAgentMessage
+ ? lastUserOrAgentMessage.end_of_turn &&
+ !lastUserOrAgentMessage.parent_conversation_id
+ : true;
+ })()}
+ .isDisconnected=${this.connectionStatus === "disconnected"}
+ ></sketch-call-status>
+
+ <sketch-network-status
+ connection=${this.connectionStatus}
+ error=${this.connectionErrorMessage}
+ ></sketch-network-status>
+ </div>
</div>
- </div>
- <div id="view-container" ${ref(this.scrollContainerRef)}>
+ <!-- Main content area: scrollable, flex-1 -->
<div
- id="view-container-inner"
- class="${this._todoPanelVisible && this.viewMode === "chat"
- ? "with-todo-panel"
- : ""}"
+ id="view-container"
+ ${ref(this.scrollContainerRef)}
+ class="self-stretch overflow-y-auto flex-1 flex flex-col min-h-0"
>
<div
- class="chat-view ${this.viewMode === "chat" ? "view-active" : ""}"
+ id="view-container-inner"
+ class="${this.viewMode === "diff2"
+ ? "max-w-full w-full h-full p-0 flex flex-col flex-1 min-h-0"
+ : this._todoPanelVisible && this.viewMode === "chat"
+ ? "max-w-none w-full m-0 px-5"
+ : "max-w-6xl w-[calc(100%-40px)] mx-auto"} relative pb-2.5 pt-2.5 flex flex-col h-full"
>
+ <!-- Chat View -->
<div
- class="chat-timeline-container ${this._todoPanelVisible &&
- this.viewMode === "chat"
- ? "with-todo-panel"
- : ""}"
+ class="chat-view ${this.viewMode === "chat"
+ ? "view-active flex flex-col"
+ : "hidden"} w-full h-full"
>
- <sketch-timeline
- .messages=${this.messages}
- .scrollContainer=${this.scrollContainerRef}
- .agentState=${this.containerState?.agent_state}
- .llmCalls=${this.containerState?.outstanding_llm_calls || 0}
- .toolCalls=${this.containerState?.outstanding_tool_calls || []}
- .firstMessageIndex=${this.containerState?.first_message_index ||
- 0}
- .state=${this.containerState}
- .dataManager=${this.dataManager}
- ></sketch-timeline>
+ <div
+ class="${this._todoPanelVisible && this.viewMode === "chat"
+ ? "mr-[400px] xl:mr-[350px] lg:mr-[300px] md:mr-0 w-[calc(100%-400px)] xl:w-[calc(100%-350px)] lg:w-[calc(100%-300px)] md:w-full"
+ : "mr-0"} flex-1 flex flex-col w-full h-full transition-[margin-right] duration-200 ease-in-out"
+ >
+ <sketch-timeline
+ .messages=${this.messages}
+ .scrollContainer=${this.scrollContainerRef}
+ .agentState=${this.containerState?.agent_state}
+ .llmCalls=${this.containerState?.outstanding_llm_calls || 0}
+ .toolCalls=${this.containerState?.outstanding_tool_calls ||
+ []}
+ .firstMessageIndex=${this.containerState
+ ?.first_message_index || 0}
+ .state=${this.containerState}
+ .dataManager=${this.dataManager}
+ ></sketch-timeline>
+ </div>
+ </div>
+
+ <!-- Todo panel positioned outside the main flow - only visible in chat view -->
+ <div
+ class="${this._todoPanelVisible && this.viewMode === "chat"
+ ? "block"
+ : "hidden"} fixed top-12 right-4 w-[400px] xl:w-[350px] lg:w-[300px] md:hidden z-[100] transition-[bottom] duration-200 ease-in-out"
+ style="bottom: var(--chat-input-height, 90px); background: linear-gradient(to bottom, #fafafa 0%, #fafafa 90%, rgba(250, 250, 250, 0.5) 95%, rgba(250, 250, 250, 0.2) 100%); border-left: 1px solid #e0e0e0;"
+ >
+ <sketch-todo-panel
+ .visible=${this._todoPanelVisible && this.viewMode === "chat"}
+ ></sketch-todo-panel>
+ </div>
+ <!-- Diff2 View -->
+ <div
+ class="diff2-view ${this.viewMode === "diff2"
+ ? "view-active flex-1 overflow-hidden min-h-0 flex flex-col h-full"
+ : "hidden"} w-full h-full"
+ >
+ <sketch-diff2-view
+ .commit=${this.currentCommitHash}
+ .gitService=${new DefaultGitDataService()}
+ @diff-comment="${this._handleDiffComment}"
+ ></sketch-diff2-view>
+ </div>
+
+ <!-- Terminal View -->
+ <div
+ class="terminal-view ${this.viewMode === "terminal"
+ ? "view-active flex flex-col"
+ : "hidden"} w-full h-full"
+ >
+ <sketch-terminal></sketch-terminal>
</div>
</div>
-
- <!-- Todo panel positioned outside the main flow - only visible in chat view -->
- <div
- class="todo-panel-container ${this._todoPanelVisible &&
- this.viewMode === "chat"
- ? "visible"
- : ""}"
- >
- <sketch-todo-panel
- .visible=${this._todoPanelVisible && this.viewMode === "chat"}
- ></sketch-todo-panel>
- </div>
- <div
- class="diff2-view ${this.viewMode === "diff2" ? "view-active" : ""}"
- >
- <sketch-diff2-view
- .commit=${this.currentCommitHash}
- .gitService=${new DefaultGitDataService()}
- @diff-comment="${this._handleDiffComment}"
- ></sketch-diff2-view>
- </div>
-
- <div
- class="terminal-view ${this.viewMode === "terminal"
- ? "view-active"
- : ""}"
- >
- <sketch-terminal></sketch-terminal>
- </div>
</div>
- </div>
- <div id="chat-input">
- <sketch-chat-input @send-chat="${this._sendChat}"></sketch-chat-input>
+ <!-- Chat input fixed at bottom -->
+ <div
+ id="chat-input"
+ class="self-end w-full shadow-[0_-2px_10px_rgba(0,0,0,0.1)]"
+ >
+ <sketch-chat-input @send-chat="${this._sendChat}"></sketch-chat-input>
+ </div>
</div>
`;
}
diff --git a/webui/src/web-components/sketch-tailwind-element.ts b/webui/src/web-components/sketch-tailwind-element.ts
new file mode 100644
index 0000000..85f380b
--- /dev/null
+++ b/webui/src/web-components/sketch-tailwind-element.ts
@@ -0,0 +1,11 @@
+import { LitElement } from "lit";
+
+export class SketchTailwindElement extends LitElement {
+ // Disable shadow DOM for better integration with tailwind.
+ // Inspired by:
+ // https://lengrand.fr/a-simple-setup-to-use-litelement-with-tailwindcss-for-small-projects/
+
+ createRenderRoot() {
+ return this;
+ }
+}
diff --git a/webui/tailwind.config.js b/webui/tailwind.config.js
new file mode 100644
index 0000000..577ab81
--- /dev/null
+++ b/webui/tailwind.config.js
@@ -0,0 +1,8 @@
+/** @type {import('tailwindcss').Config} */
+export default {
+ content: ["./src/**/*.{js,ts,jsx,tsx,html}"],
+ theme: {
+ extend: {},
+ },
+ plugins: [],
+};