diff --git a/.filesize-allowlist b/.filesize-allowlist index b4dbc51b4..917916749 100644 --- a/.filesize-allowlist +++ b/.filesize-allowlist @@ -1,5 +1,8 @@ packages/studio/src/player/hooks/useTimelinePlayer.ts packages/studio/src/hooks/useManifestPersistence.ts packages/studio/src/player/components/PlayerControls.tsx +packages/studio/src/player/components/Timeline.tsx +packages/studio/src/App.tsx packages/studio/src/components/editor/manualEdits.test.ts +packages/studio/src/player/components/timelineEditing.test.ts packages/studio/src/components/editor/manualEditsDom.ts diff --git a/bun.lock b/bun.lock index a5441b7d7..f7b934f4f 100644 --- a/bun.lock +++ b/bun.lock @@ -21,7 +21,7 @@ }, "packages/aws-lambda": { "name": "@hyperframes/aws-lambda", - "version": "0.0.1", + "version": "0.6.21", "dependencies": { "@aws-sdk/client-s3": "^3.700.0", "@aws-sdk/client-sfn": "^3.700.0", @@ -53,12 +53,13 @@ }, "packages/cli": { "name": "@hyperframes/cli", - "version": "0.6.15", + "version": "0.6.21", "bin": { "hyperframes": "./dist/cli.js", }, "dependencies": { "@hono/node-server": "^1.8.0", + "@nimrobo/superconnector": "^0.2.1", "@puppeteer/browsers": "^2.13.0", "adm-zip": "^0.5.16", "citty": "^0.2.1", @@ -97,9 +98,10 @@ }, "packages/core": { "name": "@hyperframes/core", - "version": "0.6.15", + "version": "0.6.21", "dependencies": { "@chenglou/pretext": "^0.0.5", + "@nimrobo/superconnector": "^0.2.1", "postcss": "^8.5.8", }, "devDependencies": { @@ -124,7 +126,7 @@ }, "packages/engine": { "name": "@hyperframes/engine", - "version": "0.6.15", + "version": "0.6.21", "dependencies": { "@hono/node-server": "^1.13.0", "@hyperframes/core": "workspace:^", @@ -142,7 +144,7 @@ }, "packages/player": { "name": "@hyperframes/player", - "version": "0.6.15", + "version": "0.6.21", "devDependencies": { "@types/bun": "^1.1.0", "gsap": "^3.12.5", @@ -154,7 +156,7 @@ }, "packages/producer": { "name": "@hyperframes/producer", - "version": "0.6.15", + "version": "0.6.21", "dependencies": { "@fontsource/archivo-black": "^5.2.8", "@fontsource/eb-garamond": "^5.2.7", @@ -194,7 +196,7 @@ }, "packages/shader-transitions": { "name": "@hyperframes/shader-transitions", - "version": "0.6.15", + "version": "0.6.21", "dependencies": { "html2canvas": "^1.4.1", }, @@ -206,7 +208,7 @@ }, "packages/studio": { "name": "@hyperframes/studio", - "version": "0.6.15", + "version": "0.6.21", "dependencies": { "@codemirror/autocomplete": "^6.20.1", "@codemirror/commands": "^6.10.3", @@ -384,9 +386,9 @@ "@chenglou/pretext": ["@chenglou/pretext@0.0.5", "", {}, "sha512-A8GZN10REdFGsyuiUgLV8jjPDDFMg5GmgxGWV0I3igxBOnzj+jgz2VMmVD7g+SFyoctfeqHFxbNatKSzVRWtRg=="], - "@clack/core": ["@clack/core@1.3.0", "", { "dependencies": { "fast-wrap-ansi": "^0.2.0", "sisteransi": "^1.0.5" } }, "sha512-xJPHpAmEQUBrXSLx0gF+q5K/IyihXpsHZcha+jB+tyahsKRK3Dxo4D0coZDewHo12NhiuzC3dTtMPbm53GEAAA=="], + "@clack/core": ["@clack/core@1.3.1", "", { "dependencies": { "fast-wrap-ansi": "^0.2.0", "sisteransi": "^1.0.5" } }, "sha512-fT1qHVGAag4IEkrupZ6lRRbNCs1vS9P01KB/sG8zKgvUztbYtFBtQpjSITNwooDZ83tpsPzP0mRNs1/KVszCRA=="], - "@clack/prompts": ["@clack/prompts@1.3.0", "", { "dependencies": { "@clack/core": "1.3.0", "fast-string-width": "^3.0.2", "fast-wrap-ansi": "^0.2.0", "sisteransi": "^1.0.5" } }, "sha512-GgcWwRCs/xPtaqlMy8qRhPnZf9vlWcWZNHAitnVQ3yk7JmSralSiq5q07yaffYE8SogtDm7zFeKccx1QNVARpw=="], + "@clack/prompts": ["@clack/prompts@1.4.0", "", { "dependencies": { "@clack/core": "1.3.1", "fast-string-width": "^3.0.2", "fast-wrap-ansi": "^0.2.0", "sisteransi": "^1.0.5" } }, "sha512-S0My7XPGIgpRWMDG8uRqalbgT+a6FmCUdOW+HaIOVVpUPHOb7RrpvjTjiODadKp06fsrVDJZlIzc6yCTp4AnxA=="], "@codemirror/autocomplete": ["@codemirror/autocomplete@6.20.2", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.17.0", "@lezer/common": "^1.0.0" } }, "sha512-G5FPkgIiLjOgZMjqVjvuKQ1rGPtHogLldJr33eFJdVLtmwY+giGrlv/ewljLz6b9BSQLkjxuwBc6g6omDM+YxQ=="], @@ -448,13 +450,13 @@ "@csstools/color-helpers": ["@csstools/color-helpers@6.0.2", "", {}, "sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q=="], - "@csstools/css-calc": ["@csstools/css-calc@3.2.0", "", { "peerDependencies": { "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-tokenizer": "^4.0.0" } }, "sha512-bR9e6o2BDB12jzN/gIbjHa5wLJ4UjD1CB9pM7ehlc0ddk6EBz+yYS1EV2MF55/HUxrHcB/hehAyt5vhsA3hx7w=="], + "@csstools/css-calc": ["@csstools/css-calc@3.2.1", "", { "peerDependencies": { "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-tokenizer": "^4.0.0" } }, "sha512-DtdHlgXh5ZkA43cwBcAm+huzgJiwx3ZTWVjBs94kwz2xKqSimDA3lBgCjphYgwgVUMWatSM0pDd8TILB1yrVVg=="], - "@csstools/css-color-parser": ["@csstools/css-color-parser@4.1.0", "", { "dependencies": { "@csstools/color-helpers": "^6.0.2", "@csstools/css-calc": "^3.2.0" }, "peerDependencies": { "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-tokenizer": "^4.0.0" } }, "sha512-U0KhLYmy2GVj6q4T3WaAe6NPuFYCPQoE3b0dRGxejWDgcPp8TP7S5rVdM5ZrFaqu4N67X8YaPBw14dQSYx3IyQ=="], + "@csstools/css-color-parser": ["@csstools/css-color-parser@4.1.1", "", { "dependencies": { "@csstools/color-helpers": "^6.0.2", "@csstools/css-calc": "^3.2.1" }, "peerDependencies": { "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-tokenizer": "^4.0.0" } }, "sha512-eZ5XOtyhK+mggRafYUWzA0tvaYOFgdY8AkgQiCJF9qNAePnUo/zmsqqYubBBb3sQ8uNUaSKTY9s9klfRaAXL0g=="], "@csstools/css-parser-algorithms": ["@csstools/css-parser-algorithms@4.0.0", "", { "peerDependencies": { "@csstools/css-tokenizer": "^4.0.0" } }, "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w=="], - "@csstools/css-syntax-patches-for-csstree": ["@csstools/css-syntax-patches-for-csstree@1.1.3", "", { "peerDependencies": { "css-tree": "^3.2.1" }, "optionalPeers": ["css-tree"] }, "sha512-SH60bMfrRCJF3morcdk57WklujF4Jr/EsQUzqkarfHXEFcAR1gg7fS/chAE922Sehgzc1/+Tz5H3Ypa1HiEKrg=="], + "@csstools/css-syntax-patches-for-csstree": ["@csstools/css-syntax-patches-for-csstree@1.1.4", "", { "peerDependencies": { "css-tree": "^3.2.1" }, "optionalPeers": ["css-tree"] }, "sha512-wgsqt92b7C7tQhIdPNxj0n9zuUbQlvAuI1exyzeNrOKOi62SD7ren8zqszmpVREjAOqg8cD2FqYhQfAuKjk4sw=="], "@csstools/css-tokenizer": ["@csstools/css-tokenizer@4.0.0", "", {}, "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA=="], @@ -658,6 +660,8 @@ "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.4", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow=="], + "@nimrobo/superconnector": ["@nimrobo/superconnector@0.2.1", "", {}, "sha512-R/ZbpubawgDXYRA5uRmyYoeOV3uR7V9udYVFuVXEMd7+VcGWfEZWyR6CwpcrK1C9L3ccwQ8XKLfoRyK9T1jwaw=="], + "@nodable/entities": ["@nodable/entities@2.1.0", "", {}, "sha512-nyT7T3nbMyBI/lvr6L5TyWbFJAI9FTgVRakNoBqCD+PmID8DzFrrNdLLtHMwMszOtqZa8PAOV24ZqDnQrhQINA=="], "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], @@ -666,47 +670,47 @@ "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], - "@oxc-parser/binding-android-arm-eabi": ["@oxc-parser/binding-android-arm-eabi@0.128.0", "", { "os": "android", "cpu": "arm" }, "sha512-aca6ZvzmCBUGOANQRiRQRZuRKYI3ENhcit6GisnknOOmcezfQc7xJ4dxlPU7MV7mOvrC7RNR1u3LAD7xyaiCxA=="], + "@oxc-parser/binding-android-arm-eabi": ["@oxc-parser/binding-android-arm-eabi@0.130.0", "", { "os": "android", "cpu": "arm" }, "sha512-h/xYU8/7ADWzVSf5I+YalLpj33LOy9CI/zgbJNIZ5eunRBG+Czqa3lZsvuPHHf3rOt6z1c5+UzoxjbAzAvhwVw=="], - "@oxc-parser/binding-android-arm64": ["@oxc-parser/binding-android-arm64@0.128.0", "", { "os": "android", "cpu": "arm64" }, "sha512-BbeDmuohoJ7Rz/it5wnkj69i/OsCPS3Z51nLEzwO/Y6YshtC4JU+15oNwhY8v4LRKRYclRc7ggOikwrsJ/eOEQ=="], + "@oxc-parser/binding-android-arm64": ["@oxc-parser/binding-android-arm64@0.130.0", "", { "os": "android", "cpu": "arm64" }, "sha512-oFWFJrsGv9siFM4HjMqKNB7IuIZD/SMmZdCXl8xyx7lDplGvPKyewpOo272rSWgMXe2Wx7bWI0Yj+gkHv4qbeg=="], - "@oxc-parser/binding-darwin-arm64": ["@oxc-parser/binding-darwin-arm64@0.128.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-tRUHPt80417QmvNpoSslJT1VY8NUbWdrWR+L14Zn+RbOTcaqB8E6PYE/ZGN8jjWBzqporiA/H4MfO50ew/NCNA=="], + "@oxc-parser/binding-darwin-arm64": ["@oxc-parser/binding-darwin-arm64@0.130.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-sGUzupdTplK9jQg7eJZ878HfEgQjJNBc6dAYVWJ9W5aU+J8rLfRJhTVsKThiu1pNwm6Y1qKCcbC6WhNWSXR3Ig=="], - "@oxc-parser/binding-darwin-x64": ["@oxc-parser/binding-darwin-x64@0.128.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-rWI2Hb1Nt3U/vKsjyNvZzDC8i/l144U20DKjhzaTmwIhIiSRGeroPWWiImwypmKLqrw8GuIixbWJkpGWLbkzrQ=="], + "@oxc-parser/binding-darwin-x64": ["@oxc-parser/binding-darwin-x64@0.130.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-PsB4cdCISbC00Uy8eiD8bc2AkGWjZqrSrJnkBFuG2ptrrf6mZ2F5gLFSjOAVMMgZPg8B1D7OydJwLWSfyI2Plg=="], - "@oxc-parser/binding-freebsd-x64": ["@oxc-parser/binding-freebsd-x64@0.128.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-hhpdVMaNCLgQxjgNPeeFzSeJMmZPc5lKfv0NGSI3egZq9EdnEGqeC8JsYsQjK7PoQgbvZ17xlj0SO5ziH5Obkg=="], + "@oxc-parser/binding-freebsd-x64": ["@oxc-parser/binding-freebsd-x64@0.130.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-DgABp3l38hS77JbXCV4qk1+n6DPym5u8zzwuweokezm2tX194nDSJDENbDRECxVsiNbprKATLbk+Z5wlHT0OHw=="], - "@oxc-parser/binding-linux-arm-gnueabihf": ["@oxc-parser/binding-linux-arm-gnueabihf@0.128.0", "", { "os": "linux", "cpu": "arm" }, "sha512-093zNw0zZ/e/obML+rhlSdmnzR0mVZluPcAkxunEc5E3F0yBVsFn24Y1ILfsEte11Ud041qn/gp2OJ1jxNqUng=="], + "@oxc-parser/binding-linux-arm-gnueabihf": ["@oxc-parser/binding-linux-arm-gnueabihf@0.130.0", "", { "os": "linux", "cpu": "arm" }, "sha512-4Kn3CTEmwFrzhTSC/JuUW16qovmaMdX7jeSKbL8w0pLtLww7To1a2XJi9Z5uD8QWUkfUHhqfV+VD6dVzBnWzoA=="], - "@oxc-parser/binding-linux-arm-musleabihf": ["@oxc-parser/binding-linux-arm-musleabihf@0.128.0", "", { "os": "linux", "cpu": "arm" }, "sha512-fq7DmKmfC+dvD97IXrgbph6Jzwe0EDu+PYMofmzZ6fv5X1k9vtaqLpDGMuICO9MmUnyKAQmVl+wIv2RNy4Dz8g=="], + "@oxc-parser/binding-linux-arm-musleabihf": ["@oxc-parser/binding-linux-arm-musleabihf@0.130.0", "", { "os": "linux", "cpu": "arm" }, "sha512-D35KZM3F4rRu1uAFKyBlg3Gaf/ybCjyaPR1hfgvk5ex8NtcTmRgc0JgSighEyNg96TPrFhemFba68SZuxaha8w=="], - "@oxc-parser/binding-linux-arm64-gnu": ["@oxc-parser/binding-linux-arm64-gnu@0.128.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-Xvm48jJah8TlIrURIjNOP/gNiGe6aKvCB+r06VliflFo8Kq7VOLE8PxtgShJzZIqubrgdMdYfvuPPozn7F6MbQ=="], + "@oxc-parser/binding-linux-arm64-gnu": ["@oxc-parser/binding-linux-arm64-gnu@0.130.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-Q9o7oVlo955KHwS8l1u0bCzIx+JsZUA3XToLXC+MsMhye/9LeBQbt84nh120cl2XLy+TEzvugYDiHShg5yaX6Q=="], - "@oxc-parser/binding-linux-arm64-musl": ["@oxc-parser/binding-linux-arm64-musl@0.128.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-M7iwBGmYJTx+pKOYFjI0buop4gJvlmcVzFGaXPt21DKpQkbQZG1f63Yg7LloIYT/t9yLxCw0Lhfx/RFlAlMSjA=="], + "@oxc-parser/binding-linux-arm64-musl": ["@oxc-parser/binding-linux-arm64-musl@0.130.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-EiJ/gC0ljbcwVpycC8YWw6ggMbtsPX8XMOt0mPx0aqWeMsNR+L9m05Flbvd5T+GlivG+GkSWQL7tM9SRFpM/dw=="], - "@oxc-parser/binding-linux-ppc64-gnu": ["@oxc-parser/binding-linux-ppc64-gnu@0.128.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-21LGNIZb1Pcfk5/EGsqabrxv4yqQOWis1407JJrClS7XpFCrbvr74YAB1V+m54cYbwvO6UWwQqS4WecxiyfCRg=="], + "@oxc-parser/binding-linux-ppc64-gnu": ["@oxc-parser/binding-linux-ppc64-gnu@0.130.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-b+h/lsLLurp756dMGizNs5uPaJfyEdWrTcV5t8M609jWm1DEHB1StpRXCkyvwtkJx3m+qL5BNQ0dEKan/4yGFA=="], - "@oxc-parser/binding-linux-riscv64-gnu": ["@oxc-parser/binding-linux-riscv64-gnu@0.128.0", "", { "os": "linux", "cpu": "none" }, "sha512-gyHjOTFpg9bTTYjxPmQirvufb89+VdZwVfcMtAUyPr6F5H8ZswvCQshK4qOW+Q+2Xyb33hduRgY/eFHJQjU/vQ=="], + "@oxc-parser/binding-linux-riscv64-gnu": ["@oxc-parser/binding-linux-riscv64-gnu@0.130.0", "", { "os": "linux", "cpu": "none" }, "sha512-O19Cil83XAyjEFfo8WhkMwY58ALqZ7ckjGL+25mjMIuF84urWBeANH0FC8B8BsSSygWU3/1aY3ADdDbp+wlBnw=="], - "@oxc-parser/binding-linux-riscv64-musl": ["@oxc-parser/binding-linux-riscv64-musl@0.128.0", "", { "os": "linux", "cpu": "none" }, "sha512-X6Q2oKUrP5GyDd2xniuEBLk6aFQCZ97W2+aVXGgJXdjx5t4/oFuA9ri0wLOUrBIX+qdSuK581snMBio4z910eA=="], + "@oxc-parser/binding-linux-riscv64-musl": ["@oxc-parser/binding-linux-riscv64-musl@0.130.0", "", { "os": "linux", "cpu": "none" }, "sha512-BgXRVC0+83n3YzCscLQjj6nbyeBIVeZYPTI4fFMAE4WNm2+4RXhWp03IVizL7esIz36kgmT48aebk1iM+cs8sw=="], - "@oxc-parser/binding-linux-s390x-gnu": ["@oxc-parser/binding-linux-s390x-gnu@0.128.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-BdzTmqxfxoYkpgokoLaSnOX6T+R3/goL42klre2tnG+kHbG2TXS0VN+P5BPofH1axdKOHy5ei4ENZrjmCOt2lA=="], + "@oxc-parser/binding-linux-s390x-gnu": ["@oxc-parser/binding-linux-s390x-gnu@0.130.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-6tJz0xvnGhsokE7N1WlUSBXibpYmT9xSJFS1Ce41Km/+8gQvdlW8MLhRv8PD0L7ix8vRG0FDDepp3jdOFzdVdw=="], - "@oxc-parser/binding-linux-x64-gnu": ["@oxc-parser/binding-linux-x64-gnu@0.128.0", "", { "os": "linux", "cpu": "x64" }, "sha512-OO1nW2Q7sSYYvJZpDHdvyFSdRaVcQqRijZSSmWVMqFxPYy8cEF45zJ9fcdIYuzIT3jYq6YRhEFm/VMWNWhE22Q=="], + "@oxc-parser/binding-linux-x64-gnu": ["@oxc-parser/binding-linux-x64-gnu@0.130.0", "", { "os": "linux", "cpu": "x64" }, "sha512-9aCWj83dp3heTQGmGnZGdIWgxjZrr/7VQ0TGFHH5PKByxJKF2Hcr4qvaSUHhhGEa3MSsDjTL1YDP8RAgdL5/Cg=="], - "@oxc-parser/binding-linux-x64-musl": ["@oxc-parser/binding-linux-x64-musl@0.128.0", "", { "os": "linux", "cpu": "x64" }, "sha512-4NehAe404MRdoZVS9DW8C5XbJwbXIc/KfVlYdpi5vE4081zc9Y0YzKVqyOYj/Puye7/Do+ohaONBFWlEHYl9hw=="], + "@oxc-parser/binding-linux-x64-musl": ["@oxc-parser/binding-linux-x64-musl@0.130.0", "", { "os": "linux", "cpu": "x64" }, "sha512-afXt87aZBqrUVli8TB/I8H1G50RDWcwirjWtXGXYqJ2ZqWEiErH7V72j3LUSDZaivmtu2OLX0KQ/mbhP81mr7A=="], - "@oxc-parser/binding-openharmony-arm64": ["@oxc-parser/binding-openharmony-arm64@0.128.0", "", { "os": "none", "cpu": "arm64" }, "sha512-kVbqgW9xLL8bh8oc7aYOJilRKXE5G33+tE0jan+duo/9OriaFRpijcCwT2waWs2oqYROYq0GlE7/p3ywoshVeg=="], + "@oxc-parser/binding-openharmony-arm64": ["@oxc-parser/binding-openharmony-arm64@0.130.0", "", { "os": "none", "cpu": "arm64" }, "sha512-I0NCrZV/YZuCGWgqwNN/GO/iXlLF2z+Wgc7u+Aa9N4P51oYeIa0XT+zVBUne4csO9GqxskXgI4g8JzzWGRpfOw=="], - "@oxc-parser/binding-wasm32-wasi": ["@oxc-parser/binding-wasm32-wasi@0.128.0", "", { "dependencies": { "@emnapi/core": "1.10.0", "@emnapi/runtime": "1.10.0", "@napi-rs/wasm-runtime": "^1.1.4" }, "cpu": "none" }, "sha512-L38ojghJYHmgiz6fJd7jwLB/ESDBpB02NdFxh+smqVM6P2anCEvHn0jhaSrt5eVNR1Ak8+moOeftUlofeyvniA=="], + "@oxc-parser/binding-wasm32-wasi": ["@oxc-parser/binding-wasm32-wasi@0.130.0", "", { "dependencies": { "@emnapi/core": "1.10.0", "@emnapi/runtime": "1.10.0", "@napi-rs/wasm-runtime": "^1.1.4" }, "cpu": "none" }, "sha512-sJgQkGaBX0WJvPUDfwciex6IcTk5O5NLQ1bhEb6f3nBruh1GshKMRSMt2bxZlYrgBzjyBbJzsnO+InPG0bg+fA=="], - "@oxc-parser/binding-win32-arm64-msvc": ["@oxc-parser/binding-win32-arm64-msvc@0.128.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-xgvO35GyHBtjlQ5AEpaYr7Rll1rvY7zqIhT6ty8E3ezBW2J1SFLjIDEvI/tcgDg6oaseDAqVcM+jU1HuCekgZw=="], + "@oxc-parser/binding-win32-arm64-msvc": ["@oxc-parser/binding-win32-arm64-msvc@0.130.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-bjcma99sQrNh6RY4mPO9yTkfxql6TDFoN3HWdK31RCKXwNhcDgJXW/l8PUtzKNiQ+9vpKJfJtQq+LklBuxSOBA=="], - "@oxc-parser/binding-win32-ia32-msvc": ["@oxc-parser/binding-win32-ia32-msvc@0.128.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-OY+3eM2SN72prHKRB22mPz8o5A/7dJ+f5DFLBVvggyZhEaNDAH9IB+ElMjmOkOIwf5MDCUAowCK7pAncNxzpBA=="], + "@oxc-parser/binding-win32-ia32-msvc": ["@oxc-parser/binding-win32-ia32-msvc@0.130.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-hRYbv6HhpSTzT4xTiIkadLI7upLQxuOdLPR/9nL1fTjwhgutBTPXrwaAPb/jTFVx6/8C7Jb5HcUKhmNwloTbFA=="], - "@oxc-parser/binding-win32-x64-msvc": ["@oxc-parser/binding-win32-x64-msvc@0.128.0", "", { "os": "win32", "cpu": "x64" }, "sha512-NE9ny+cPUCCObXa0IKLfj0tCdPd7pe/dz9ZpkxpUOymB3miNeMPybdlYYTBSGJUalMWeBM85/4JcCErCNTqOXw=="], + "@oxc-parser/binding-win32-x64-msvc": ["@oxc-parser/binding-win32-x64-msvc@0.130.0", "", { "os": "win32", "cpu": "x64" }, "sha512-RBpA9TsRucJq6HNVNCFF1iKg+QeTkLdZf7hi4xaOGCPvMZWvDHjQgSOEZMUpuW4JNciHbxNhLEYmz5CVygjVGQ=="], - "@oxc-project/types": ["@oxc-project/types@0.128.0", "", {}, "sha512-huv1Y/LzBJkBVHt3OlC7u0zHBW9qXf1FdD7sGmc1rXc2P1mTwHssYv7jyGx5KAACSCH+9B3Bhn6Z9luHRvf7pQ=="], + "@oxc-project/types": ["@oxc-project/types@0.130.0", "", {}, "sha512-ibD2usx9JRu7f5pu2tMKMI4cpA4NgXJQoYRP4pQ7Pxmn1l6k/53qWtQWZayhYy3X4QZkt90Ot+mJEaeXouio6Q=="], "@oxc-resolver/binding-android-arm-eabi": ["@oxc-resolver/binding-android-arm-eabi@11.19.1", "", { "os": "android", "cpu": "arm" }, "sha512-aUs47y+xyXHUKlbhqHUjBABjvycq6YSD7bpxSW7vplUmdzAlJ93yXY6ZR0c1o1x5A/QKbENCvs3+NlY8IpIVzg=="], @@ -786,43 +790,43 @@ "@oxfmt/binding-win32-x64-msvc": ["@oxfmt/binding-win32-x64-msvc@0.41.0", "", { "os": "win32", "cpu": "x64" }, "sha512-49ZSpbZ1noozyPapE8SUOSm3IN0Ze4b5nkO+4+7fq6oEYQQJFhE0saj5k/Gg4oewVPdjn0L3ZFeWk2Vehjcw7A=="], - "@oxlint/binding-android-arm-eabi": ["@oxlint/binding-android-arm-eabi@1.64.0", "", { "os": "android", "cpu": "arm" }, "sha512-2r6Nq3XXGLHEXKkSj8JtmJ6N4gDw431DPFOg0ZoJHlNjnG6HVMm/ksQ10m0HJ8WBvwgMe1L50UHPaYZutCRPCw=="], + "@oxlint/binding-android-arm-eabi": ["@oxlint/binding-android-arm-eabi@1.65.0", "", { "os": "android", "cpu": "arm" }, "sha512-jDVaGNURT5pEA9qcabh6WusIoBNybOMMDPCx+EFt+gxo6rVvoUf0+73Xy5x81+ZrxU+ewk5uRBYifjy5pgkcnA=="], - "@oxlint/binding-android-arm64": ["@oxlint/binding-android-arm64@1.64.0", "", { "os": "android", "cpu": "arm64" }, "sha512-ePJMpePgg7fBv+L/hVx1xXRU5/5gd5m0obLA6hPEfLXF3GjpR8idIDbY1dhQYhyz1ms2wdTccSboo6KEd2Oxtg=="], + "@oxlint/binding-android-arm64": ["@oxlint/binding-android-arm64@1.65.0", "", { "os": "android", "cpu": "arm64" }, "sha512-v0z80IWNA7c9RhUydq9YprBxCVZrQ6Ixls2tdxUC1F/1FFqSfa7xTX+EJf0mj6+BKRg2zWXqWfcbJUnETlLlIw=="], - "@oxlint/binding-darwin-arm64": ["@oxlint/binding-darwin-arm64@1.64.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-U4DMLQd10gJLuoSTLSGbfv3bGjTlUNsScm9Dgb8wwBqmCzidf1pE1pXV4doGNxqwH3KtVng1AGTINA0NvkGLvQ=="], + "@oxlint/binding-darwin-arm64": ["@oxlint/binding-darwin-arm64@1.65.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-pL/mG/5gMzBwp1gdc5+Cwi87F9j3XRnPxHGyVj5Zd+dCEV5YkKt0L70PB3EGmEEHxgn4H+jnMS3xLuXs6mZW/Q=="], - "@oxlint/binding-darwin-x64": ["@oxlint/binding-darwin-x64@1.64.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-GoRIL48QWm4/TAvjN8pB1nAG+1/uqc9EdnWT9zqHeb6wsmjZtywj8VRe5aGW47Fdb64YtLOsdLqVxOvQuz98Wg=="], + "@oxlint/binding-darwin-x64": ["@oxlint/binding-darwin-x64@1.65.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-jVTneaeuHtqTrKYnhrdH1buhnSorinvpy1sv43ayclfWx/e/DfdRWv+h1fopJcHQbYr5WMcZMmDvnfEBkPZ+1A=="], - "@oxlint/binding-freebsd-x64": ["@oxlint/binding-freebsd-x64@1.64.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-5dFkv4tkg7PxJJGS9/OjrJwjhuHczrd3OQOkRE0wHcLM+ncUnULtzEPWjqGOxTXxZnLWcB91bGiIznx89TVXyQ=="], + "@oxlint/binding-freebsd-x64": ["@oxlint/binding-freebsd-x64@1.65.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-8lJQ7B6RloYDUhwVdbSpwT2eKsCN5KP1Scn18ly1tytCuhXhbs0nkfKHT4jWWZBJqmynWuzd+78bF7wILrj6pw=="], - "@oxlint/binding-linux-arm-gnueabihf": ["@oxlint/binding-linux-arm-gnueabihf@1.64.0", "", { "os": "linux", "cpu": "arm" }, "sha512-jsBqMLl/uOL5+Kq/+BtK9FrmiNGUbx8SiyZXv+WlUxA45KuwcLu9BfiSIL3I3DBDgWM3yZizDITnTK9BcqNBQg=="], + "@oxlint/binding-linux-arm-gnueabihf": ["@oxlint/binding-linux-arm-gnueabihf@1.65.0", "", { "os": "linux", "cpu": "arm" }, "sha512-EgmZY+DeWhLLEnNl70/49j3ltA8I6X9kxMfexupWi2Vwfp6RonGsBaHtGoedLolaU37ne7eDUgoxa3CFB95GZA=="], - "@oxlint/binding-linux-arm-musleabihf": ["@oxlint/binding-linux-arm-musleabihf@1.64.0", "", { "os": "linux", "cpu": "arm" }, "sha512-1lrj8At/Uuc9GhjrVFBQo0NEjfBrTkzpmtHIGAhNnIXqn1CAyGL+qrztUsXb2GIluJrpl9Q7qRLJOb/NqydacQ=="], + "@oxlint/binding-linux-arm-musleabihf": ["@oxlint/binding-linux-arm-musleabihf@1.65.0", "", { "os": "linux", "cpu": "arm" }, "sha512-OJMWmAYRVBCPPxnYr3j5sXRwHPh1bAuMlTStGco1Z8q3HkvSH4h+A10E9MiRNYmLhUuli5a2P5wmfj8cagiF5Q=="], - "@oxlint/binding-linux-arm64-gnu": ["@oxlint/binding-linux-arm64-gnu@1.64.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-HpSQbubwh03mMhAdy2BYtad/fsY8vDFHDAb6bUwuCYg2VD3xCQgn6ArKcO0oZyLCheacKTv4PrF3Mfu5hgoE2g=="], + "@oxlint/binding-linux-arm64-gnu": ["@oxlint/binding-linux-arm64-gnu@1.65.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-D8uNi50LsYKgS0vGARZDRx05TBZeSxAVdLGddSEqQLSU7xsiqdImHPEw55xq8sKA5rCc/4au/5uS7FQALWdLCg=="], - "@oxlint/binding-linux-arm64-musl": ["@oxlint/binding-linux-arm64-musl@1.64.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-00QQ0h0Y7u0G69BgiH3+ky2aaq/QvkDL6DYok8htIuJHxybiux5aQ8jwmg8qIk9wha6UagUP2BAwAzbemcJbpg=="], + "@oxlint/binding-linux-arm64-musl": ["@oxlint/binding-linux-arm64-musl@1.65.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-IpbA8QGbwFehQhO+YaHwmoI81f93xvywpspf8HrdPCWOIeKwYfM1dhVhO4YKfZewTRRQEPY/JFjTOXTgkwhKrA=="], - "@oxlint/binding-linux-ppc64-gnu": ["@oxlint/binding-linux-ppc64-gnu@1.64.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-2GaimTV6EMW+s5HS0An3oGbQme3BgHswvfVdGk3EB57Xe9+/gyT+Qd7lNVzb3rtir52vbIPzXfaYArzs5b5zcw=="], + "@oxlint/binding-linux-ppc64-gnu": ["@oxlint/binding-linux-ppc64-gnu@1.65.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-ZSe8HgaZdgyHSv2+/pTG68z10+OarB18CkFKQOhRs3lmmP/p2vuigedK2e9d0ztoG2DU/duJzhxXBSjy/492HQ=="], - "@oxlint/binding-linux-riscv64-gnu": ["@oxlint/binding-linux-riscv64-gnu@1.64.0", "", { "os": "linux", "cpu": "none" }, "sha512-H46AtFb9wypjoVwGdlxrm0DsD809NGmtiK9HiyPKTxkSte2YjhC4S+00rOIrwCaxcyPiGid3Y3OMXp5KMAkGZw=="], + "@oxlint/binding-linux-riscv64-gnu": ["@oxlint/binding-linux-riscv64-gnu@1.65.0", "", { "os": "linux", "cpu": "none" }, "sha512-DcTERf++v6HyPHukKAr0JFTRqB+YeDEvqzRgNDMaz7jITPf+tlJIwRxodlAqoXMYhNVEZhXdQM5RAAYH8/oPuw=="], - "@oxlint/binding-linux-riscv64-musl": ["@oxlint/binding-linux-riscv64-musl@1.64.0", "", { "os": "linux", "cpu": "none" }, "sha512-HEgsidjjvvyzdg82icYkuFCf7REDV7B9JFwbIMbVwrKLBY0MrXX+bku3POn/hduZ2yW91IyVDUMq0Bf02KwXQw=="], + "@oxlint/binding-linux-riscv64-musl": ["@oxlint/binding-linux-riscv64-musl@1.65.0", "", { "os": "linux", "cpu": "none" }, "sha512-xjhMwuFJwRh40NOBzol4gM5gqAa0xPCJU+GQLM6BydV8TbfkIA7JeyCFNhyfbE9Q/5EWcKYTx62R0cRcjP7DAA=="], - "@oxlint/binding-linux-s390x-gnu": ["@oxlint/binding-linux-s390x-gnu@1.64.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-Axvm8qryotmKN00P5w4JapaSjvP2LOSbdbBJiX+2SuHd3QzhW7TUc8skqgw+ahQZ5DmzEYeHCqauvW8f32Ns6Q=="], + "@oxlint/binding-linux-s390x-gnu": ["@oxlint/binding-linux-s390x-gnu@1.65.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-lrWSXb8JzboPWYBG6Kunt/eemvjo2oCFXktShsm3yMToY7HjzKLjxh7CljSvGnnZH9oohNFHOKc9xYpGKCPm6w=="], - "@oxlint/binding-linux-x64-gnu": ["@oxlint/binding-linux-x64-gnu@1.64.0", "", { "os": "linux", "cpu": "x64" }, "sha512-cR60vSd7+m+KRZ3GQGfDxWwahW5RMXg0qlGvAluZr0fTUYvw0H9N9AXAF/M/PMqgytyqvVNmBAkJG9l7U30Y1g=="], + "@oxlint/binding-linux-x64-gnu": ["@oxlint/binding-linux-x64-gnu@1.65.0", "", { "os": "linux", "cpu": "x64" }, "sha512-A7xfghw250m4a1sPV+q44Mow2G5bhiC9FBvhAuIhJS6QovWnqzuL5AFQPEuwOB+PM4DhABkqxVa3Iwe3Y/nFlQ=="], - "@oxlint/binding-linux-x64-musl": ["@oxlint/binding-linux-x64-musl@1.64.0", "", { "os": "linux", "cpu": "x64" }, "sha512-2u/aPZ9pEg7HnvZPDsHxUGNnrpr4qaHi+mCgLgpt+LYRzPrS4Px4wPfkIdRdr2GvKnaYyt+XSlto0Vm5sbStTg=="], + "@oxlint/binding-linux-x64-musl": ["@oxlint/binding-linux-x64-musl@1.65.0", "", { "os": "linux", "cpu": "x64" }, "sha512-reqOun1+pWO3fW6cv7bsa8hHG0TN3t/82qPdaoJo90FwugXiMjKhZMChmH5Z01cFNRHmxN4+543Fy8478cM/iA=="], - "@oxlint/binding-openharmony-arm64": ["@oxlint/binding-openharmony-arm64@1.64.0", "", { "os": "none", "cpu": "arm64" }, "sha512-kfhkGfCdoXLSxEkrhDlJrvBYajGmq+ma4EMc53dsOWTq+rIBOlI0vTBmpZNnM5oH2LY/K/w1HAK+UQEgjgpVUg=="], + "@oxlint/binding-openharmony-arm64": ["@oxlint/binding-openharmony-arm64@1.65.0", "", { "os": "none", "cpu": "arm64" }, "sha512-KQpqOb/juDBO0xyloDkVDhOVxDUgAfZ2OAAVq99TJScJDzT319xry1QzB9LQohV9QGnA7p6m/XATZkMXc84lwA=="], - "@oxlint/binding-win32-arm64-msvc": ["@oxlint/binding-win32-arm64-msvc@1.64.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-r/cNKBFieONoVu2bb1KkVouq9W+edDUgHumXJGphCRRj+U0xaD4nanrw8ZOqo0IsutPkEM4vCcGBpak6x5aXMg=="], + "@oxlint/binding-win32-arm64-msvc": ["@oxlint/binding-win32-arm64-msvc@1.65.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-xfqcOc3nJFeAd1kDY4T9d3XeJIhr00twaaW0kOAzGPyUHkruXtNJv6zz1Ra9fRtSek5VpW2Yoj5AcwPIlT0ZiQ=="], - "@oxlint/binding-win32-ia32-msvc": ["@oxlint/binding-win32-ia32-msvc@1.64.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-tUw0xUUwEFVZbpJoeCblkv8SJA4Xz3CdXCJbAnBsiNLyxDrk2tLcxEAS6M73Q7hHHDg3OtwI8vZVK3t5RJt4Gw=="], + "@oxlint/binding-win32-ia32-msvc": ["@oxlint/binding-win32-ia32-msvc@1.65.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-JV+pXm45p8sdgs3c7LOPAohW23optCNZETFOXUcjn6cS4PYZhEU/RI54Z5dHdMudab3nw7T48PZILthM+Q0COQ=="], - "@oxlint/binding-win32-x64-msvc": ["@oxlint/binding-win32-x64-msvc@1.64.0", "", { "os": "win32", "cpu": "x64" }, "sha512-9CBR+LO0JVST87fNTzzNxS5I29jIUO5gxT9i9+M3SDHHALElj9sY1Prf12tad3vIRC6OD7Ehtvvh+sn13vSwHw=="], + "@oxlint/binding-win32-x64-msvc": ["@oxlint/binding-win32-x64-msvc@1.65.0", "", { "os": "win32", "cpu": "x64" }, "sha512-D7L/oBbskLss21bYrRbFuIs81AiSQV+wRzwck54dOkHIlq2qu1xjLz8u6jCqGH8Fltk8bB5DLBpVhE7v/fA8XQ=="], "@phosphor-icons/react": ["@phosphor-icons/react@2.1.10", "", { "peerDependencies": { "react": ">= 16.8", "react-dom": ">= 16.8" } }, "sha512-vt8Tvq8GLjheAZZYa+YG/pW7HDbov8El/MANW8pOAz4eGxrwhnbfrQZq0Cp4q8zBEu8NIhHdnr+r8thnfRSNYA=="], @@ -836,11 +840,11 @@ "@protobufjs/eventemitter": ["@protobufjs/eventemitter@1.1.0", "", {}, "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q=="], - "@protobufjs/fetch": ["@protobufjs/fetch@1.1.0", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.1", "@protobufjs/inquire": "^1.1.0" } }, "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ=="], + "@protobufjs/fetch": ["@protobufjs/fetch@1.1.1", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.1" } }, "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw=="], "@protobufjs/float": ["@protobufjs/float@1.0.2", "", {}, "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ=="], - "@protobufjs/inquire": ["@protobufjs/inquire@1.1.1", "", {}, "sha512-mnzgDV26ueAvk7rsbt9L7bE0SuAoqyuys/sMMrmVcN5x9VsxpcG3rqAUSgDyLp0UZlmNfIbQ4fHfCtreVBk8Ew=="], + "@protobufjs/inquire": ["@protobufjs/inquire@1.1.2", "", {}, "sha512-pa0vFRuws4wkvaXKK1uXZMAwAX4/t8ANaJo45iw/oQHNQ9q5xUzwgFmVJGXiga2BeN+zpX7Vf9vmsiIa2J+MUw=="], "@protobufjs/path": ["@protobufjs/path@1.1.2", "", {}, "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA=="], @@ -852,55 +856,55 @@ "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.27", "", {}, "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA=="], - "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.60.3", "", { "os": "android", "cpu": "arm" }, "sha512-x35CNW/ANXG3hE/EZpRU8MXX1JDN86hBb2wMGAtltkz7pc6cxgjpy1OMMfDosOQ+2hWqIkag/fGok1Yady9nGw=="], + "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.60.4", "", { "os": "android", "cpu": "arm" }, "sha512-F5QXMSiFebS9hKZj02XhWLLnRpJ3B3AROP0tWbFBSj+6kCbg5m9j5JoHKd4mmSVy5mS/IMQloYgYxCuJC0fxEQ=="], - "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.60.3", "", { "os": "android", "cpu": "arm64" }, "sha512-xw3xtkDApIOGayehp2+Rz4zimfkaX65r4t47iy+ymQB2G4iJCBBfj0ogVg5jpvjpn8UWn/+q9tprxleYeNp3Hw=="], + "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.60.4", "", { "os": "android", "cpu": "arm64" }, "sha512-GxxTKApUpzRhof7poWvCJHRF51C67u1R7D6DiluBE8wKU1u5GWE8t+v81JvJYtbawoBFX1hLv5Ei4eVjkWokaw=="], - "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.60.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-vo6Y5Qfpx7/5EaamIwi0WqW2+zfiusVihKatLvtN1VFVy3D13uERk/6gZLU1UiHRL6fDXqj/ELIeVRGnvcTE1g=="], + "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.60.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-tua0TaJxMOB1R0V0RS1jFZ/RpURFDJIOR2A6jWwQeawuFyS4gBW+rntLRaQd0EQ4bd6Vp44Z2rXW+YYDBsj6IA=="], - "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.60.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-D+0QGcZhBzTN82weOnsSlY7V7+RMmPuF1CkbxyMAGE8+ZHeUjyb76ZiWmBlCu//AQQONvxcqRbwZTajZKqjuOw=="], + "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.60.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-CSKq7MsP+5PFIcydhAiR1K0UhEI1A2jWXVKHPCBZ151yOutENwvnPocgVHkivu2kviURtCEB6zUQw0vs8RrhMg=="], - "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.60.3", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-6HnvHCT7fDyj6R0Ph7A6x8dQS/S38MClRWeDLqc0MdfWkxjiu1HSDYrdPhqSILzjTIC/pnXbbJbo+ft+gy/9hQ=="], + "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.60.4", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-+O8OkVdyvXMtJEciu2wS/pzm1IxntEEQx3z5TAVy4l32G0etZn+RsA48ARRrFm6Ri8fvqPQfgrvNxSjKAbnd3g=="], - "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.60.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-KHLgC3WKlUYW3ShFKnnosZDOJ0xjg9zp7au3sIm2bs/tGBeC2ipmvRh/N7JKi0t9Ue20C0dpEshi8WUubg+cnA=="], + "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.60.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-Iw3oMskH3AfNuhU0MSN7vNbdi4me/NiYo2azqPz/Le16zHSa+3RRmliCMWWQmh4lcndccU40xcJuTYJZxNo/lw=="], - "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.60.3", "", { "os": "linux", "cpu": "arm" }, "sha512-DV6fJoxEYWJOvaZIsok7KrYl0tPvga5OZ2yvKHNNYyk/2roMLqQAbGhr78EQ5YhHpnhLKJD3S1WFusAkmUuV5g=="], + "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.60.4", "", { "os": "linux", "cpu": "arm" }, "sha512-EIPRXTVQpHyF8WOo219AD2yEltPehLTcTMz2fn6JsatLYSzQf00hj3rulF+yauOlF9/FtM2WpkT/hJh/KJFGhA=="], - "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.60.3", "", { "os": "linux", "cpu": "arm" }, "sha512-mQKoJAzvuOs6F+TZybQO4GOTSMUu7v0WdxEk24krQ/uUxXoPTtHjuaUuPmFhtBcM4K0ons8nrE3JyhTuCFtT/w=="], + "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.60.4", "", { "os": "linux", "cpu": "arm" }, "sha512-J3Yh9PzzF1Ovah2At+lHiGQdsYgArxBbXv/zHfSyaiFQEqvNv7DcW98pCrmdjCZBrqBiKrKKe2V+aaSGWuBe/w=="], - "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.60.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-Whjj2qoiJ6+OOJMGptTYazaJvjOJm+iKHpXQM1P3LzGjt7Ff++Tp7nH4N8J/BUA7R9IHfDyx4DJIflifwnbmIA=="], + "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.60.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-BFDEZMYfUvLn37ONE1yMBojPxnMlTFsdyNoqncT0qFq1mAfllL+ATMMJd8TeuVMiX84s1KbcxcZbXInmcO2mRg=="], - "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.60.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-4YTNHKqGng5+yiZt3mg77nmyuCfmNfX4fPmyUapBcIk+BdwSwmCWGXOUxhXbBEkFHtoN5boLj/5NON+u5QC9tg=="], + "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.60.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-pc9EYOSlOgdQ2uPl1o9PF6/kLSgaUosia7gOuS8mB69IxJvlclko1MECXysjs5ryez1/5zjYqx3+xYU0TU6R1A=="], - "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.60.3", "", { "os": "linux", "cpu": "none" }, "sha512-SU3kNlhkpI4UqlUc2VXPGK9o886ZsSeGfMAX2ba2b8DKmMXq4AL7KUrkSWVbb7koVqx41Yczx6dx5PNargIrEA=="], + "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.60.4", "", { "os": "linux", "cpu": "none" }, "sha512-NxnomyxYerDh5n4iLrNa+sH+Z+U4BMEE46V2PgQ/hoB909i8gV1M5wPojWg9fk1jWpO3IQnOs20K4wyZuFLEFQ=="], - "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.60.3", "", { "os": "linux", "cpu": "none" }, "sha512-6lDLl5h4TXpB1mTf2rQWnAk/LcXrx9vBfu/DT5TIPhvMhRWaZ5MxkIc8u4lJAmBo6klTe1ywXIUHFjylW505sg=="], + "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.60.4", "", { "os": "linux", "cpu": "none" }, "sha512-nbJnQ8a3z1mtmrwImCYhc6BGpThAyYVRQxw9uKSKG4wR6aAYno9sVjJ0zaZcW9BPJX1GbrDPf+SvdWjgTuDmnw=="], - "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.60.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-BMo8bOw8evlup/8G+cj5xWtPyp93xPdyoSN16Zy90Q2QZ0ZYRhCt6ZJSwbrRzG9HApFabjwj2p25TUPDWrhzqQ=="], + "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.60.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-2EU6acNrQLd8tYvo/LXW535wupT3m6fo7HKo6lr7ktQoItxTyOL1ZCR/GfGCuXl2vR+zmfI6eRXkSemafv+iVg=="], - "@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.60.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-E0L8X1dZN1/Rph+5VPF6Xj2G7JJvMACVXtamTJIDrVI44Y3K+G8gQaMEAavbqCGTa16InptiVrX6eM6pmJ+7qA=="], + "@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.60.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-WeBtoMuaMxiiIrO2IYP3xs6GMWkJP2C0EoT8beTLkUPmzV1i/UcOSVw1d5r9KBODtHKilG5yFxsGRnBbK3wJ4A=="], - "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.60.3", "", { "os": "linux", "cpu": "none" }, "sha512-oZJ/WHaVfHUiRAtmTAeo3DcevNsVvH8mbvodjZy7D5QKvCefO371SiKRpxoDcCxB3PTRTLayWBkvmDQKTcX/sw=="], + "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.60.4", "", { "os": "linux", "cpu": "none" }, "sha512-FJHFfqpKUI3A10WrWKiFbBZ7yVbGT4q4B5o1qKFFojqpaYoh9LrQgqWCmmcxQzVSXYtyB5bzkXrYzlHTs21MYA=="], - "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.60.3", "", { "os": "linux", "cpu": "none" }, "sha512-Dhbyh7j9FybM3YaTgaHmVALwA8AkUwTPccyCQ79TG9AJUsMQqgN1DDEZNr4+QUfwiWvLDumW5vdwzoeUF+TNxQ=="], + "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.60.4", "", { "os": "linux", "cpu": "none" }, "sha512-mcEl6CUT5IAUmQf1m9FYSmVqCJlpQ8r8eyftFUHG8i9OhY7BkBXSUdnLH5DOf0wCOjcP9v/QO93zpmF1SptCCw=="], - "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.60.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-cJd1X5XhHHlltkaypz1UcWLA8AcoIi1aWhsvaWDskD1oz2eKCypnqvTQ8ykMNI0RSmm7NkTdSqSSD7zM0xa6Ig=="], + "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.60.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-ynt3JxVd2w2buzoKDWIyiV1pJW93xlQic1THVLXilz429oijRpSHivZAgp65KBu+cMcgf1eVVjdnTLvPxgCuoQ=="], - "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.60.3", "", { "os": "linux", "cpu": "x64" }, "sha512-DAZDBHQfG2oQuhY7mc6I3/qB4LU2fQCjRvxbDwd/Jdvb9fypP4IJ4qmtu6lNjes6B531AI8cg1aKC2di97bUxA=="], + "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.60.4", "", { "os": "linux", "cpu": "x64" }, "sha512-Boiz5+MsaROEWDf+GGEwF8VMHGhlUoQMtIPjOgA5fv4osupqTVnJteQNKJwUcnUog2G55jYXH7KZFFiJe0TEzQ=="], - "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.60.3", "", { "os": "linux", "cpu": "x64" }, "sha512-cRxsE8c13mZOh3vP+wLDxpQBRrOHDIGOWyDL93Sy0Ga8y515fBcC2pjUfFwUe5T7tqvTvWbCpg1URM/AXdWIXA=="], + "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.60.4", "", { "os": "linux", "cpu": "x64" }, "sha512-+qfSY27qIrFfI/Hom04KYFw3GKZSGU4lXus51wsb5EuySfFlWRwjkKWoE9emgRw/ukoT4Udsj4W/+xxG8VbPKg=="], - "@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.60.3", "", { "os": "openbsd", "cpu": "x64" }, "sha512-QaWcIgRxqEdQdhJqW4DJctsH6HCmo5vHxY0krHSX4jMtOqfzC+dqDGuHM87bu4H8JBeibWx7jFz+h6/4C8wA5Q=="], + "@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.60.4", "", { "os": "openbsd", "cpu": "x64" }, "sha512-VpTfOPHgVXEBeeR8hZ2O0F3aSso+JDWqTWmTmzcQKted54IAdUVbxE+j/MVxUsKa8L20HJhv3vUezVPoquqWjA=="], - "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.60.3", "", { "os": "none", "cpu": "arm64" }, "sha512-AaXwSvUi3QIPtroAUw1t5yHGIyqKEXwH54WUocFolZhpGDruJcs8c+xPNDRn4XiQsS7MEwnYsHW2l0MBLDMkWg=="], + "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.60.4", "", { "os": "none", "cpu": "arm64" }, "sha512-IPOsh5aRYuLv/nkU51X10Bf75Bsf6+gZdx1X+QP5QM6lIJFHHqbHLG0uJn/hWthzo13UAc2umiUorqZy3axoZg=="], - "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.60.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-65LAKM/bAWDqKNEelHlcHvm2V+Vfb8C6INFxQXRHCvaVN1rJfwr4NvdP4FyzUaLqWfaCGaadf6UbTm8xJeYfEg=="], + "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.60.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-4QzE9E81OohJ/HKzHhsqU+zcYYojVOXlFMs1DdyMT6qXl/niOH7AVElmmEdUNHHS/oRkc++d5k6Vy85zFs0DEw=="], - "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.60.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-EEM2gyhBF5MFnI6vMKdX1LAosE627RGBzIoGMdLloPZkXrUN0Ckqgr2Qi8+J3zip/8NVVro3/FjB+tjhZUgUHA=="], + "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.60.4", "", { "os": "win32", "cpu": "ia32" }, "sha512-zTPgT1YuHHcd+Tmx7h8aml0FWFVelV5N54oHow9SLj+GfoDy/huQ+UV396N/C7KpMDMiPspRktzM1/0r1usYEA=="], - "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.60.3", "", { "os": "win32", "cpu": "x64" }, "sha512-E5Eb5H/DpxaoXH++Qkv28RcUJboMopmdDUALBczvHMf7hNIxaDZqwY5lK12UK1BHacSmvupoEWGu+n993Z0y1A=="], + "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.60.4", "", { "os": "win32", "cpu": "x64" }, "sha512-DRS4G7mi9lJxqEDezIkKCaUIKCrLUUDCUaCsTPCi/rtqaC6D/jjwslMQyiDU50Ka0JKpeXeRBFBAXwArY52vBw=="], - "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.60.3", "", { "os": "win32", "cpu": "x64" }, "sha512-hPt/bgL5cE+Qp+/TPHBqptcAgPzgj46mPcg/16zNUmbQk0j+mOEQV/+Lqu8QRtDV3Ek95Q6FeFITpuhl6OTsAA=="], + "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.60.4", "", { "os": "win32", "cpu": "x64" }, "sha512-QVTUovf40zgTqlFVrKA1uXMVvU2QWEFWfAH8Wdc48IxLvrJMQVMBRjuQyUpzZCDkakImib9eVazbWlC6ksWtJw=="], "@simple-libs/child-process-utils": ["@simple-libs/child-process-utils@1.0.2", "", { "dependencies": { "@simple-libs/stream-utils": "^1.2.0" } }, "sha512-/4R8QKnd/8agJynkNdJmNw2MBxuFTRcNFnE5Sg/G+jkSsV8/UBgULMzhizWWW42p8L5H7flImV2ATi79Ove2Tw=="], @@ -942,7 +946,7 @@ "@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="], - "@types/bun": ["@types/bun@1.3.13", "", { "dependencies": { "bun-types": "1.3.13" } }, "sha512-9fqXWk5YIHGGnUau9TEi+qdlTYDAnOj+xLCmSTwXfAIqXr2x4tytJb43E9uCvt09zJURKXwAtkoH4nLQfzeTXw=="], + "@types/bun": ["@types/bun@1.3.14", "", { "dependencies": { "bun-types": "1.3.14" } }, "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw=="], "@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="], @@ -950,11 +954,11 @@ "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], - "@types/jsdom": ["@types/jsdom@28.0.2", "", { "dependencies": { "@types/node": "*", "@types/tough-cookie": "*", "parse5": "^8.0.0", "undici-types": "^7.21.0" } }, "sha512-zZYItekplnGirFhVDrcB0+103TMakXfKfIp7uECxaFzFG3Ws5kYQSwVb1d4pQfJMMjQda6pfuZxueAv9CMiJbw=="], + "@types/jsdom": ["@types/jsdom@28.0.3", "", { "dependencies": { "@types/node": "*", "@types/tough-cookie": "*", "parse5": "^8.0.0", "undici-types": "^7.21.0" } }, "sha512-/HQ2uFoetFTXuye8vzIcHw2z6Fwi7Hi/qcgC+RoS9NCyewiqxhVGqlG+ViGB6lkax481R6dmhf1I7lIGlzJStQ=="], "@types/mime-types": ["@types/mime-types@3.0.1", "", {}, "sha512-xRMsfuQbnRq1Ef+C+RKaENOxXX87Ygl38W1vDfPHRku02TgQr+Qd8iivLtAMcR0KF5/29xlnFihkTlbqFrGOVQ=="], - "@types/node": ["@types/node@25.7.0", "", { "dependencies": { "undici-types": "~7.21.0" } }, "sha512-z+pdZyxE+RTQE9AcboAZCb4otwcrvgHD+GlBpPgn0emDVt0ohrTMhAwlr2Wd9nZ+nihhYFxO2pThz3C5qSu2Eg=="], + "@types/node": ["@types/node@25.9.0", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-AOQwYUNolgy3VosiRqXrACUXTN8nJUtPl7FJXMqZVyxiiCLhQuG3jXKvCS1ALr+Y2OmZhzzLVlYPEqJaiqkaJQ=="], "@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="], @@ -1030,7 +1034,7 @@ "balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], - "bare-events": ["bare-events@2.8.2", "", { "peerDependencies": { "bare-abort-controller": "*" }, "optionalPeers": ["bare-abort-controller"] }, "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ=="], + "bare-events": ["bare-events@2.8.3", "", { "peerDependencies": { "bare-abort-controller": "*" }, "optionalPeers": ["bare-abort-controller"] }, "sha512-HdUm8EMQBLaJvGUdidNNbqpA1kYkwNcb+MYxkxCLAPJGQzlv9J0C24h8V65Z4c5GLd/JEALDvpFCQgpLJqc0zw=="], "bare-fs": ["bare-fs@4.7.1", "", { "dependencies": { "bare-events": "^2.5.4", "bare-path": "^3.0.0", "bare-stream": "^2.6.4", "bare-url": "^2.2.2", "fast-fifo": "^1.3.2" }, "peerDependencies": { "bare-buffer": "*" }, "optionalPeers": ["bare-buffer"] }, "sha512-WDRsyVN52eAx/lBamKD6uyw8H4228h/x0sGGGegOamM2cd7Pag88GfMQalobXI+HaEUxpCkbKQUDOQqt9wawRw=="], @@ -1046,7 +1050,7 @@ "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], - "baseline-browser-mapping": ["baseline-browser-mapping@2.10.29", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-Asa2krT+XTPZINCS+2QcyS8WTkObE77RwkydwF7h6DmnKqbvlalz93m/dnphUyCa6SWSP51VgtEUf2FN+gelFQ=="], + "baseline-browser-mapping": ["baseline-browser-mapping@2.10.30", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-xjOFN16Ha1+Rz4nFYKqHU/LSB+gx/Vi3yQLX7r7sAW+Wa+8hhF2h4pvqTrTMc8+WcDBEunnUurr46Jvv0jk3Vg=="], "basic-ftp": ["basic-ftp@5.3.1", "", {}, "sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw=="], @@ -1072,7 +1076,7 @@ "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], - "bun-types": ["bun-types@1.3.13", "", { "dependencies": { "@types/node": "*" } }, "sha512-QXKeHLlOLqQX9LgYaHJfzdBaV21T63HhFJnvuRCcjZiaUDpbs5ED1MgxbMra71CsryN/1dAoXuJJJwIv/2drVA=="], + "bun-types": ["bun-types@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="], "bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="], @@ -1084,7 +1088,7 @@ "camelcase-css": ["camelcase-css@2.0.1", "", {}, "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA=="], - "caniuse-lite": ["caniuse-lite@1.0.30001792", "", {}, "sha512-hVLMUZFgR4JJ6ACt1uEESvQN1/dBVqPAKY0hgrV70eN3391K6juAfTjKZLKvOMsx8PxA7gsY1/tLMMTcfFLLpw=="], + "caniuse-lite": ["caniuse-lite@1.0.30001793", "", {}, "sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA=="], "case": ["case@1.6.3", "", {}, "sha512-mzDSXIPaFwVDvZAHqZ9VlbyF4yyXRuX6IvB06WvPYkqJVO24kX1PPhv9bfpKNFZyxYFmmgo03HUiD8iklmJYRQ=="], @@ -1204,7 +1208,7 @@ "ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="], - "electron-to-chromium": ["electron-to-chromium@1.5.353", "", {}, "sha512-kOrWphBi8TOZyiJZqsgqIle0lw+tzmnQK83pV9dZUd01Nm2POECSyFQMAuarzZdYqQW7FH9RaYOuaRo3h+bQ3w=="], + "electron-to-chromium": ["electron-to-chromium@1.5.357", "", {}, "sha512-NHlTIQDK8fmVwHwuIzmXYEJ1Ewq3D9wDNc0cWXxDGysP6Pb21giwGNkxiTifyKy/4SoPuN5l6GLP1W9Sv7zB2g=="], "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], @@ -1292,7 +1296,7 @@ "fraction.js": ["fraction.js@5.3.4", "", {}, "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ=="], - "framer-motion": ["framer-motion@12.38.0", "", { "dependencies": { "motion-dom": "^12.38.0", "motion-utils": "^12.36.0", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-rFYkY/pigbcswl1XQSb7q424kSTQ8q6eAC+YUsSKooHQYuLdzdHjrt6uxUC+PRAO++q5IS7+TamgIw1AphxR+g=="], + "framer-motion": ["framer-motion@12.39.0", "", { "dependencies": { "motion-dom": "^12.39.0", "motion-utils": "^12.39.0", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-+vnLfzrv0MzjLzNl+nvNvR7jdg3q4cxxjz/YvzfifHl0TREtL00cs1RoMTxs+1PzLiEqZGV6gYsBY0oEAYZ24w=="], "fs-extra": ["fs-extra@11.3.5", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-eKpRKAovdpZtR1WopLHxlBWvAgPny3c4gX1G5Jhwmmw4XJj0ifSD5qB5TOo8hmA0wlRKDAOAhEE1yVPgs6Fgcg=="], @@ -1346,7 +1350,7 @@ "hasown": ["hasown@2.0.3", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg=="], - "hono": ["hono@4.12.18", "", {}, "sha512-RWzP96k/yv0PQfyXnWjs6zot20TqfpfsNXhOnev8d1InAxubW93L11/oNUc3tQqn2G0bSdAOBpX+2uDFHV7kdQ=="], + "hono": ["hono@4.12.19", "", {}, "sha512-xa3eYXYXx68XTT4hZ7dRzsXBhaq85ToSrlUJNoR0gwz/1Ap/CNwX47wfvV7pc/xWhjKVVkLT7zBJy8chhNguqQ=="], "html-encoding-sniffer": ["html-encoding-sniffer@6.0.0", "", { "dependencies": { "@exodus/bytes": "^1.6.0" } }, "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg=="], @@ -1440,7 +1444,7 @@ "jws": ["jws@4.0.1", "", { "dependencies": { "jwa": "^2.0.1", "safe-buffer": "^5.0.1" } }, "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA=="], - "knip": ["knip@6.12.2", "", { "dependencies": { "fdir": "^6.5.0", "formatly": "^0.3.0", "get-tsconfig": "4.14.0", "jiti": "^2.7.0", "minimist": "^1.2.8", "oxc-parser": "^0.128.0", "oxc-resolver": "^11.19.1", "picomatch": "^4.0.4", "smol-toml": "^1.6.1", "strip-json-comments": "5.0.3", "tinyglobby": "^0.2.16", "unbash": "^3.0.0", "yaml": "^2.8.2", "zod": "^4.1.11" }, "bin": { "knip": "bin/knip.js", "knip-bun": "bin/knip-bun.js" } }, "sha512-RcZpT1sVziKZgDk1F0hAcp+bq71VJAF8vg1Y9ZLXc1+UXQaMm1rjiUqpJQTIj+lqwmiBQT19/u7ikgazs23cvA=="], + "knip": ["knip@6.14.1", "", { "dependencies": { "fdir": "^6.5.0", "formatly": "^0.3.0", "get-tsconfig": "4.14.0", "jiti": "^2.7.0", "minimist": "^1.2.8", "oxc-parser": "^0.130.0", "oxc-resolver": "^11.19.1", "picomatch": "^4.0.4", "smol-toml": "^1.6.1", "strip-json-comments": "5.0.3", "tinyglobby": "^0.2.16", "unbash": "^3.0.0", "yaml": "^2.9.0", "zod": "^4.1.11" }, "bin": { "knip": "bin/knip.js", "knip-bun": "bin/knip-bun.js" } }, "sha512-SN3Ly0ixzj5CQkY/rc4OPHpWrCC0XRIIjgdP76G9Cni5k72ur5jBYOyvJuF5oPTM14v8eHcMUgPbElHa+lnR0g=="], "lefthook": ["lefthook@2.1.6", "", { "optionalDependencies": { "lefthook-darwin-arm64": "2.1.6", "lefthook-darwin-x64": "2.1.6", "lefthook-freebsd-arm64": "2.1.6", "lefthook-freebsd-x64": "2.1.6", "lefthook-linux-arm64": "2.1.6", "lefthook-linux-x64": "2.1.6", "lefthook-openbsd-arm64": "2.1.6", "lefthook-openbsd-x64": "2.1.6", "lefthook-windows-arm64": "2.1.6", "lefthook-windows-x64": "2.1.6" }, "bin": { "lefthook": "bin/index.js" } }, "sha512-w9sBoR0mdN+kJc3SB85VzpiAAl451/rxdCRcZlwW71QLjkeH3EBQFgc4VMj5apePychYDHAlqEWTB8J8JK/j1Q=="], @@ -1480,7 +1484,7 @@ "loupe": ["loupe@3.2.1", "", {}, "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ=="], - "lru-cache": ["lru-cache@11.3.6", "", {}, "sha512-Gf/KoL3C/MlI7Bt0PGI9I+TeTC/I6r/csU58N4BSNc4lppLBeKsOdFYkK+dX0ABDUMJNfCHTyPpzwwO21Awd3A=="], + "lru-cache": ["lru-cache@11.4.0", "", {}, "sha512-W+R+kFL4HgVxONq2bhXPi3bGpzGe/yEhVOp233qw9wCRtgncJ15P3bC+e4zZMu4Cq7d+WAJjXGW0uUkifhcatA=="], "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], @@ -1514,11 +1518,11 @@ "mlly": ["mlly@1.8.2", "", { "dependencies": { "acorn": "^8.16.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.3" } }, "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA=="], - "motion": ["motion@12.38.0", "", { "dependencies": { "framer-motion": "^12.38.0", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-uYfXzeHlgThchzwz5Te47dlv5JOUC7OB4rjJ/7XTUgtBZD8CchMN8qEJ4ZVsUmTyYA44zjV0fBwsiktRuFnn+w=="], + "motion": ["motion@12.39.0", "", { "dependencies": { "framer-motion": "^12.39.0", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-H4a+Ze+a9j+/NTla5ezfb/g9vmIOxC+viDj++NGDZyTZkdRKjiOz3kSv6TalRWM8ZmD2y/CfC6TkQc97ybyqSA=="], - "motion-dom": ["motion-dom@12.38.0", "", { "dependencies": { "motion-utils": "^12.36.0" } }, "sha512-pdkHLD8QYRp8VfiNLb8xIBJis1byQ9gPT3Jnh2jqfFtAsWUA3dEepDlsWe/xMpO8McV+VdpKVcp+E+TGJEtOoA=="], + "motion-dom": ["motion-dom@12.39.0", "", { "dependencies": { "motion-utils": "^12.39.0" } }, "sha512-Xn7aAcGDhco/JZTXOub64UmaYn73C6J1Po7Fk+8EvkJsNGTqfhon6UJY53vJKXW5v5Zl8HrYsVxv6oPXeGoGLQ=="], - "motion-utils": ["motion-utils@12.36.0", "", {}, "sha512-eHWisygbiwVvf6PZ1vhaHCLamvkSbPIeAYxWUuL3a2PD/TROgE7FvfHWTIH4vMl798QLfMw15nRqIaRDXTlYRg=="], + "motion-utils": ["motion-utils@12.39.0", "", {}, "sha512-8nadJAJjTtqRkmRF36FoJTrywK9nnFmnPwnSMyxaOCU7GDjN9RTMJIxx9De8ErM+vpPhMccr/6fo5WciyQLnMQ=="], "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], @@ -1552,13 +1556,13 @@ "open": ["open@10.2.0", "", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "wsl-utils": "^0.1.0" } }, "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA=="], - "oxc-parser": ["oxc-parser@0.128.0", "", { "dependencies": { "@oxc-project/types": "^0.128.0" }, "optionalDependencies": { "@oxc-parser/binding-android-arm-eabi": "0.128.0", "@oxc-parser/binding-android-arm64": "0.128.0", "@oxc-parser/binding-darwin-arm64": "0.128.0", "@oxc-parser/binding-darwin-x64": "0.128.0", "@oxc-parser/binding-freebsd-x64": "0.128.0", "@oxc-parser/binding-linux-arm-gnueabihf": "0.128.0", "@oxc-parser/binding-linux-arm-musleabihf": "0.128.0", "@oxc-parser/binding-linux-arm64-gnu": "0.128.0", "@oxc-parser/binding-linux-arm64-musl": "0.128.0", "@oxc-parser/binding-linux-ppc64-gnu": "0.128.0", "@oxc-parser/binding-linux-riscv64-gnu": "0.128.0", "@oxc-parser/binding-linux-riscv64-musl": "0.128.0", "@oxc-parser/binding-linux-s390x-gnu": "0.128.0", "@oxc-parser/binding-linux-x64-gnu": "0.128.0", "@oxc-parser/binding-linux-x64-musl": "0.128.0", "@oxc-parser/binding-openharmony-arm64": "0.128.0", "@oxc-parser/binding-wasm32-wasi": "0.128.0", "@oxc-parser/binding-win32-arm64-msvc": "0.128.0", "@oxc-parser/binding-win32-ia32-msvc": "0.128.0", "@oxc-parser/binding-win32-x64-msvc": "0.128.0" } }, "sha512-XkOw3eiIxAgQ19WRew/Bq9wc5Ga/guaWIzDBzq80z1PyuDNGvWBpPby9k6YGwV8A8uMw+Nlq3xqlzuDYmUFYUw=="], + "oxc-parser": ["oxc-parser@0.130.0", "", { "dependencies": { "@oxc-project/types": "^0.130.0" }, "optionalDependencies": { "@oxc-parser/binding-android-arm-eabi": "0.130.0", "@oxc-parser/binding-android-arm64": "0.130.0", "@oxc-parser/binding-darwin-arm64": "0.130.0", "@oxc-parser/binding-darwin-x64": "0.130.0", "@oxc-parser/binding-freebsd-x64": "0.130.0", "@oxc-parser/binding-linux-arm-gnueabihf": "0.130.0", "@oxc-parser/binding-linux-arm-musleabihf": "0.130.0", "@oxc-parser/binding-linux-arm64-gnu": "0.130.0", "@oxc-parser/binding-linux-arm64-musl": "0.130.0", "@oxc-parser/binding-linux-ppc64-gnu": "0.130.0", "@oxc-parser/binding-linux-riscv64-gnu": "0.130.0", "@oxc-parser/binding-linux-riscv64-musl": "0.130.0", "@oxc-parser/binding-linux-s390x-gnu": "0.130.0", "@oxc-parser/binding-linux-x64-gnu": "0.130.0", "@oxc-parser/binding-linux-x64-musl": "0.130.0", "@oxc-parser/binding-openharmony-arm64": "0.130.0", "@oxc-parser/binding-wasm32-wasi": "0.130.0", "@oxc-parser/binding-win32-arm64-msvc": "0.130.0", "@oxc-parser/binding-win32-ia32-msvc": "0.130.0", "@oxc-parser/binding-win32-x64-msvc": "0.130.0" } }, "sha512-X0PJ+NmOok8qP3vK9uaW431ngkdM9UPEK7KG466urtIL2+EYTEgbZK2yqe2MWKJKBjRlFweP/pJPx0x9muMEVw=="], "oxc-resolver": ["oxc-resolver@11.19.1", "", { "optionalDependencies": { "@oxc-resolver/binding-android-arm-eabi": "11.19.1", "@oxc-resolver/binding-android-arm64": "11.19.1", "@oxc-resolver/binding-darwin-arm64": "11.19.1", "@oxc-resolver/binding-darwin-x64": "11.19.1", "@oxc-resolver/binding-freebsd-x64": "11.19.1", "@oxc-resolver/binding-linux-arm-gnueabihf": "11.19.1", "@oxc-resolver/binding-linux-arm-musleabihf": "11.19.1", "@oxc-resolver/binding-linux-arm64-gnu": "11.19.1", "@oxc-resolver/binding-linux-arm64-musl": "11.19.1", "@oxc-resolver/binding-linux-ppc64-gnu": "11.19.1", "@oxc-resolver/binding-linux-riscv64-gnu": "11.19.1", "@oxc-resolver/binding-linux-riscv64-musl": "11.19.1", "@oxc-resolver/binding-linux-s390x-gnu": "11.19.1", "@oxc-resolver/binding-linux-x64-gnu": "11.19.1", "@oxc-resolver/binding-linux-x64-musl": "11.19.1", "@oxc-resolver/binding-openharmony-arm64": "11.19.1", "@oxc-resolver/binding-wasm32-wasi": "11.19.1", "@oxc-resolver/binding-win32-arm64-msvc": "11.19.1", "@oxc-resolver/binding-win32-ia32-msvc": "11.19.1", "@oxc-resolver/binding-win32-x64-msvc": "11.19.1" } }, "sha512-qE/CIg/spwrTBFt5aKmwe3ifeDdLfA2NESN30E42X/lII5ClF8V7Wt6WIJhcGZjp0/Q+nQ+9vgxGk//xZNX2hg=="], "oxfmt": ["oxfmt@0.41.0", "", { "dependencies": { "tinypool": "2.1.0" }, "optionalDependencies": { "@oxfmt/binding-android-arm-eabi": "0.41.0", "@oxfmt/binding-android-arm64": "0.41.0", "@oxfmt/binding-darwin-arm64": "0.41.0", "@oxfmt/binding-darwin-x64": "0.41.0", "@oxfmt/binding-freebsd-x64": "0.41.0", "@oxfmt/binding-linux-arm-gnueabihf": "0.41.0", "@oxfmt/binding-linux-arm-musleabihf": "0.41.0", "@oxfmt/binding-linux-arm64-gnu": "0.41.0", "@oxfmt/binding-linux-arm64-musl": "0.41.0", "@oxfmt/binding-linux-ppc64-gnu": "0.41.0", "@oxfmt/binding-linux-riscv64-gnu": "0.41.0", "@oxfmt/binding-linux-riscv64-musl": "0.41.0", "@oxfmt/binding-linux-s390x-gnu": "0.41.0", "@oxfmt/binding-linux-x64-gnu": "0.41.0", "@oxfmt/binding-linux-x64-musl": "0.41.0", "@oxfmt/binding-openharmony-arm64": "0.41.0", "@oxfmt/binding-win32-arm64-msvc": "0.41.0", "@oxfmt/binding-win32-ia32-msvc": "0.41.0", "@oxfmt/binding-win32-x64-msvc": "0.41.0" }, "bin": { "oxfmt": "bin/oxfmt" } }, "sha512-sKLdJZdQ3bw6x9qKiT7+eID4MNEXlDHf5ZacfIircrq6Qwjk0L6t2/JQlZZrVHTXJawK3KaMuBoJnEJPcqCEdg=="], - "oxlint": ["oxlint@1.64.0", "", { "optionalDependencies": { "@oxlint/binding-android-arm-eabi": "1.64.0", "@oxlint/binding-android-arm64": "1.64.0", "@oxlint/binding-darwin-arm64": "1.64.0", "@oxlint/binding-darwin-x64": "1.64.0", "@oxlint/binding-freebsd-x64": "1.64.0", "@oxlint/binding-linux-arm-gnueabihf": "1.64.0", "@oxlint/binding-linux-arm-musleabihf": "1.64.0", "@oxlint/binding-linux-arm64-gnu": "1.64.0", "@oxlint/binding-linux-arm64-musl": "1.64.0", "@oxlint/binding-linux-ppc64-gnu": "1.64.0", "@oxlint/binding-linux-riscv64-gnu": "1.64.0", "@oxlint/binding-linux-riscv64-musl": "1.64.0", "@oxlint/binding-linux-s390x-gnu": "1.64.0", "@oxlint/binding-linux-x64-gnu": "1.64.0", "@oxlint/binding-linux-x64-musl": "1.64.0", "@oxlint/binding-openharmony-arm64": "1.64.0", "@oxlint/binding-win32-arm64-msvc": "1.64.0", "@oxlint/binding-win32-ia32-msvc": "1.64.0", "@oxlint/binding-win32-x64-msvc": "1.64.0" }, "peerDependencies": { "oxlint-tsgolint": ">=0.22.1" }, "optionalPeers": ["oxlint-tsgolint"], "bin": { "oxlint": "bin/oxlint" } }, "sha512-Star3SNpWPeWFPw7kRXIhXUSn6fdiAl25q15CQzH/9WaOtG6e9CWTc25vNZOCr4PE1yEP1GtKJKIKglhj3OmEQ=="], + "oxlint": ["oxlint@1.65.0", "", { "optionalDependencies": { "@oxlint/binding-android-arm-eabi": "1.65.0", "@oxlint/binding-android-arm64": "1.65.0", "@oxlint/binding-darwin-arm64": "1.65.0", "@oxlint/binding-darwin-x64": "1.65.0", "@oxlint/binding-freebsd-x64": "1.65.0", "@oxlint/binding-linux-arm-gnueabihf": "1.65.0", "@oxlint/binding-linux-arm-musleabihf": "1.65.0", "@oxlint/binding-linux-arm64-gnu": "1.65.0", "@oxlint/binding-linux-arm64-musl": "1.65.0", "@oxlint/binding-linux-ppc64-gnu": "1.65.0", "@oxlint/binding-linux-riscv64-gnu": "1.65.0", "@oxlint/binding-linux-riscv64-musl": "1.65.0", "@oxlint/binding-linux-s390x-gnu": "1.65.0", "@oxlint/binding-linux-x64-gnu": "1.65.0", "@oxlint/binding-linux-x64-musl": "1.65.0", "@oxlint/binding-openharmony-arm64": "1.65.0", "@oxlint/binding-win32-arm64-msvc": "1.65.0", "@oxlint/binding-win32-ia32-msvc": "1.65.0", "@oxlint/binding-win32-x64-msvc": "1.65.0" }, "peerDependencies": { "oxlint-tsgolint": ">=0.22.1" }, "optionalPeers": ["oxlint-tsgolint"], "bin": { "oxlint": "bin/oxlint" } }, "sha512-ChUuE3Q7XnAbscvT4XLMsH7HFJmLgLVv9lu+RRgFL5wSXnDqUOzTp5IS8qWDBGd/ZDSzQ2tbX8fjAmijlGLC7A=="], "p-retry": ["p-retry@4.6.2", "", { "dependencies": { "@types/retry": "0.12.0", "retry": "^0.13.1" } }, "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ=="], @@ -1618,7 +1622,7 @@ "progress": ["progress@2.0.3", "", {}, "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA=="], - "protobufjs": ["protobufjs@7.5.7", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.5", "@protobufjs/eventemitter": "^1.1.0", "@protobufjs/fetch": "^1.1.0", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.1", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.1", "@types/node": ">=13.7.0", "long": "^5.0.0" } }, "sha512-NGnrxS/nLKUo5nkbVQxlC71sB4hdfImdYIbFeSCidxtwATx0AHRPcANSLd0q5Bb2BkoSWo2iisQhGg5/r+ihbA=="], + "protobufjs": ["protobufjs@7.5.9", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.5", "@protobufjs/eventemitter": "^1.1.0", "@protobufjs/fetch": "^1.1.1", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.2", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.1", "@types/node": ">=13.7.0", "long": "^5.0.0" } }, "sha512-Od4muIm3HW1AouyHF5lONOf1FWo3hY1NbFDoy191X9GzhpgW1clCoaFjfVs2rKJNFYpTNJbje4cbAIDBZJ63ZA=="], "proxy-agent": ["proxy-agent@6.5.0", "", { "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" } }, "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A=="], @@ -1660,7 +1664,7 @@ "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], - "rollup": ["rollup@4.60.3", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.60.3", "@rollup/rollup-android-arm64": "4.60.3", "@rollup/rollup-darwin-arm64": "4.60.3", "@rollup/rollup-darwin-x64": "4.60.3", "@rollup/rollup-freebsd-arm64": "4.60.3", "@rollup/rollup-freebsd-x64": "4.60.3", "@rollup/rollup-linux-arm-gnueabihf": "4.60.3", "@rollup/rollup-linux-arm-musleabihf": "4.60.3", "@rollup/rollup-linux-arm64-gnu": "4.60.3", "@rollup/rollup-linux-arm64-musl": "4.60.3", "@rollup/rollup-linux-loong64-gnu": "4.60.3", "@rollup/rollup-linux-loong64-musl": "4.60.3", "@rollup/rollup-linux-ppc64-gnu": "4.60.3", "@rollup/rollup-linux-ppc64-musl": "4.60.3", "@rollup/rollup-linux-riscv64-gnu": "4.60.3", "@rollup/rollup-linux-riscv64-musl": "4.60.3", "@rollup/rollup-linux-s390x-gnu": "4.60.3", "@rollup/rollup-linux-x64-gnu": "4.60.3", "@rollup/rollup-linux-x64-musl": "4.60.3", "@rollup/rollup-openbsd-x64": "4.60.3", "@rollup/rollup-openharmony-arm64": "4.60.3", "@rollup/rollup-win32-arm64-msvc": "4.60.3", "@rollup/rollup-win32-ia32-msvc": "4.60.3", "@rollup/rollup-win32-x64-gnu": "4.60.3", "@rollup/rollup-win32-x64-msvc": "4.60.3", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-pAQK9HalE84QSm4Po3EmWIZPd3FnjkShVkiMlz1iligWYkWQ7wHYd1PF/T7QZ5TVSD6uSTon5gBVMSM4JfBV+A=="], + "rollup": ["rollup@4.60.4", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.60.4", "@rollup/rollup-android-arm64": "4.60.4", "@rollup/rollup-darwin-arm64": "4.60.4", "@rollup/rollup-darwin-x64": "4.60.4", "@rollup/rollup-freebsd-arm64": "4.60.4", "@rollup/rollup-freebsd-x64": "4.60.4", "@rollup/rollup-linux-arm-gnueabihf": "4.60.4", "@rollup/rollup-linux-arm-musleabihf": "4.60.4", "@rollup/rollup-linux-arm64-gnu": "4.60.4", "@rollup/rollup-linux-arm64-musl": "4.60.4", "@rollup/rollup-linux-loong64-gnu": "4.60.4", "@rollup/rollup-linux-loong64-musl": "4.60.4", "@rollup/rollup-linux-ppc64-gnu": "4.60.4", "@rollup/rollup-linux-ppc64-musl": "4.60.4", "@rollup/rollup-linux-riscv64-gnu": "4.60.4", "@rollup/rollup-linux-riscv64-musl": "4.60.4", "@rollup/rollup-linux-s390x-gnu": "4.60.4", "@rollup/rollup-linux-x64-gnu": "4.60.4", "@rollup/rollup-linux-x64-musl": "4.60.4", "@rollup/rollup-openbsd-x64": "4.60.4", "@rollup/rollup-openharmony-arm64": "4.60.4", "@rollup/rollup-win32-arm64-msvc": "4.60.4", "@rollup/rollup-win32-ia32-msvc": "4.60.4", "@rollup/rollup-win32-x64-gnu": "4.60.4", "@rollup/rollup-win32-x64-msvc": "4.60.4", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-WHeFSbZYsPu3+bLoNRUuAO+wavNlocOPf3wSHTP7hcFKVnJeWsYlCDbr3mTS14FCizf9ccIxXA8sGL8zKeQN3g=="], "run-applescript": ["run-applescript@7.1.0", "", {}, "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q=="], @@ -1792,7 +1796,7 @@ "tsup": ["tsup@8.5.1", "", { "dependencies": { "bundle-require": "^5.1.0", "cac": "^6.7.14", "chokidar": "^4.0.3", "consola": "^3.4.0", "debug": "^4.4.0", "esbuild": "^0.27.0", "fix-dts-default-cjs-exports": "^1.0.0", "joycon": "^3.1.1", "picocolors": "^1.1.1", "postcss-load-config": "^6.0.1", "resolve-from": "^5.0.0", "rollup": "^4.34.8", "source-map": "^0.7.6", "sucrase": "^3.35.0", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.11", "tree-kill": "^1.2.2" }, "peerDependencies": { "@microsoft/api-extractor": "^7.36.0", "@swc/core": "^1", "postcss": "^8.4.12", "typescript": ">=4.5.0" }, "optionalPeers": ["@microsoft/api-extractor", "@swc/core", "postcss", "typescript"], "bin": { "tsup": "dist/cli-default.js", "tsup-node": "dist/cli-node.js" } }, "sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing=="], - "tsx": ["tsx@4.21.0", "", { "dependencies": { "esbuild": "~0.27.0", "get-tsconfig": "^4.7.5" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw=="], + "tsx": ["tsx@4.22.2", "", { "dependencies": { "esbuild": "~0.28.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-6w9FwtT8WQqRAyTNR+Z+86kghRqpmOLjXUrBlBT6T+CQGDuIMm0VmAqaFUFBIeKDTGobE6/YSigZYLeomzBaRg=="], "type-fest": ["type-fest@0.20.2", "", {}, "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ=="], @@ -1810,7 +1814,7 @@ "undici": ["undici@7.25.0", "", {}, "sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ=="], - "undici-types": ["undici-types@7.21.0", "", {}, "sha512-w9IMgQrz4O0YN1LtB7K5P63vhlIOvC7opSmouCJ+ZywlPAlO9gIkJ+otk6LvGpAs2wg4econaCz3TvQ9xPoyuQ=="], + "undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="], "universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="], @@ -1852,7 +1856,7 @@ "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], - "ws": ["ws@8.20.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA=="], + "ws": ["ws@8.20.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w=="], "wsl-utils": ["wsl-utils@0.1.0", "", { "dependencies": { "is-wsl": "^3.1.0" } }, "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw=="], @@ -1892,7 +1896,7 @@ "@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], - "@codemirror/lint/@codemirror/view": ["@codemirror/view@6.42.1", "", { "dependencies": { "@codemirror/state": "^6.6.0", "crelt": "^1.0.6", "style-mod": "^4.1.0", "w3c-keyname": "^2.2.4" } }, "sha512-ToN3oFc0nsxNUYVF5P0ztLgbC4UPPjPtA9aKYhkOKQaZASpOUo6ISXyQLP66ctVwlDc+j6Jv0uK5IFALkiXztg=="], + "@codemirror/lint/@codemirror/view": ["@codemirror/view@6.43.0", "", { "dependencies": { "@codemirror/state": "^6.6.0", "crelt": "^1.0.6", "style-mod": "^4.1.0", "w3c-keyname": "^2.2.4" } }, "sha512-V7ZCLQO3Jus9hzh2jVCCPW3mO4IBMr43O37PqSUYautJSnnJF41YlgLw21x0fLJTYvJ+Vkm6Gp+qKGH9pltgXA=="], "@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], @@ -1902,6 +1906,8 @@ "@isaacs/fs-minipass/minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="], + "@types/jsdom/undici-types": ["undici-types@7.25.0", "", {}, "sha512-AXNgS1Byr27fTI+2bsPEkV9CxkT8H6xNyRI68b3TatlZo3RkzlqQBLL+w7SmGPVpokjHbcuNVQUWE7FRTg+LRA=="], + "anymatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], "aws-cdk-lib/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], @@ -1976,7 +1982,7 @@ "tsup/tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="], - "tsx/esbuild": ["esbuild@0.27.7", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.7", "@esbuild/android-arm": "0.27.7", "@esbuild/android-arm64": "0.27.7", "@esbuild/android-x64": "0.27.7", "@esbuild/darwin-arm64": "0.27.7", "@esbuild/darwin-x64": "0.27.7", "@esbuild/freebsd-arm64": "0.27.7", "@esbuild/freebsd-x64": "0.27.7", "@esbuild/linux-arm": "0.27.7", "@esbuild/linux-arm64": "0.27.7", "@esbuild/linux-ia32": "0.27.7", "@esbuild/linux-loong64": "0.27.7", "@esbuild/linux-mips64el": "0.27.7", "@esbuild/linux-ppc64": "0.27.7", "@esbuild/linux-riscv64": "0.27.7", "@esbuild/linux-s390x": "0.27.7", "@esbuild/linux-x64": "0.27.7", "@esbuild/netbsd-arm64": "0.27.7", "@esbuild/netbsd-x64": "0.27.7", "@esbuild/openbsd-arm64": "0.27.7", "@esbuild/openbsd-x64": "0.27.7", "@esbuild/openharmony-arm64": "0.27.7", "@esbuild/sunos-x64": "0.27.7", "@esbuild/win32-arm64": "0.27.7", "@esbuild/win32-ia32": "0.27.7", "@esbuild/win32-x64": "0.27.7" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w=="], + "tsx/esbuild": ["esbuild@0.28.0", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.0", "@esbuild/android-arm": "0.28.0", "@esbuild/android-arm64": "0.28.0", "@esbuild/android-x64": "0.28.0", "@esbuild/darwin-arm64": "0.28.0", "@esbuild/darwin-x64": "0.28.0", "@esbuild/freebsd-arm64": "0.28.0", "@esbuild/freebsd-x64": "0.28.0", "@esbuild/linux-arm": "0.28.0", "@esbuild/linux-arm64": "0.28.0", "@esbuild/linux-ia32": "0.28.0", "@esbuild/linux-loong64": "0.28.0", "@esbuild/linux-mips64el": "0.28.0", "@esbuild/linux-ppc64": "0.28.0", "@esbuild/linux-riscv64": "0.28.0", "@esbuild/linux-s390x": "0.28.0", "@esbuild/linux-x64": "0.28.0", "@esbuild/netbsd-arm64": "0.28.0", "@esbuild/netbsd-x64": "0.28.0", "@esbuild/openbsd-arm64": "0.28.0", "@esbuild/openbsd-x64": "0.28.0", "@esbuild/openharmony-arm64": "0.28.0", "@esbuild/sunos-x64": "0.28.0", "@esbuild/win32-arm64": "0.28.0", "@esbuild/win32-ia32": "0.28.0", "@esbuild/win32-x64": "0.28.0" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw=="], "vitest/tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="], @@ -2052,57 +2058,57 @@ "tsup/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.7", "", { "os": "win32", "cpu": "x64" }, "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg=="], - "tsx/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.7", "", { "os": "aix", "cpu": "ppc64" }, "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg=="], + "tsx/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.0", "", { "os": "aix", "cpu": "ppc64" }, "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA=="], - "tsx/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.27.7", "", { "os": "android", "cpu": "arm" }, "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ=="], + "tsx/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.28.0", "", { "os": "android", "cpu": "arm" }, "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ=="], - "tsx/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.7", "", { "os": "android", "cpu": "arm64" }, "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ=="], + "tsx/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.28.0", "", { "os": "android", "cpu": "arm64" }, "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw=="], - "tsx/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.27.7", "", { "os": "android", "cpu": "x64" }, "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg=="], + "tsx/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.28.0", "", { "os": "android", "cpu": "x64" }, "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA=="], - "tsx/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.7", "", { "os": "darwin", "cpu": "arm64" }, "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw=="], + "tsx/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.28.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q=="], - "tsx/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.7", "", { "os": "darwin", "cpu": "x64" }, "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ=="], + "tsx/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.28.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ=="], - "tsx/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.7", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w=="], + "tsx/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.28.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q=="], - "tsx/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.7", "", { "os": "freebsd", "cpu": "x64" }, "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ=="], + "tsx/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.28.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw=="], - "tsx/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.7", "", { "os": "linux", "cpu": "arm" }, "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA=="], + "tsx/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.28.0", "", { "os": "linux", "cpu": "arm" }, "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw=="], - "tsx/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A=="], + "tsx/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.28.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A=="], - "tsx/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.7", "", { "os": "linux", "cpu": "ia32" }, "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg=="], + "tsx/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.28.0", "", { "os": "linux", "cpu": "ia32" }, "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ=="], - "tsx/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q=="], + "tsx/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.28.0", "", { "os": "linux", "cpu": "none" }, "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg=="], - "tsx/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw=="], + "tsx/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.28.0", "", { "os": "linux", "cpu": "none" }, "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w=="], - "tsx/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.7", "", { "os": "linux", "cpu": "ppc64" }, "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ=="], + "tsx/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.28.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg=="], - "tsx/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ=="], + "tsx/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.28.0", "", { "os": "linux", "cpu": "none" }, "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ=="], - "tsx/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.7", "", { "os": "linux", "cpu": "s390x" }, "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw=="], + "tsx/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.28.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q=="], - "tsx/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.7", "", { "os": "linux", "cpu": "x64" }, "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA=="], + "tsx/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.28.0", "", { "os": "linux", "cpu": "x64" }, "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ=="], - "tsx/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.7", "", { "os": "none", "cpu": "arm64" }, "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w=="], + "tsx/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.28.0", "", { "os": "none", "cpu": "arm64" }, "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw=="], - "tsx/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.7", "", { "os": "none", "cpu": "x64" }, "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw=="], + "tsx/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.28.0", "", { "os": "none", "cpu": "x64" }, "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw=="], - "tsx/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.7", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A=="], + "tsx/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.28.0", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g=="], - "tsx/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.7", "", { "os": "openbsd", "cpu": "x64" }, "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg=="], + "tsx/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.28.0", "", { "os": "openbsd", "cpu": "x64" }, "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA=="], - "tsx/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.7", "", { "os": "none", "cpu": "arm64" }, "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw=="], + "tsx/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.28.0", "", { "os": "none", "cpu": "arm64" }, "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w=="], - "tsx/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.7", "", { "os": "sunos", "cpu": "x64" }, "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA=="], + "tsx/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.28.0", "", { "os": "sunos", "cpu": "x64" }, "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw=="], - "tsx/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.7", "", { "os": "win32", "cpu": "arm64" }, "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA=="], + "tsx/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.28.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA=="], - "tsx/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.7", "", { "os": "win32", "cpu": "ia32" }, "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw=="], + "tsx/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.28.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA=="], - "tsx/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.7", "", { "os": "win32", "cpu": "x64" }, "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg=="], + "tsx/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.28.0", "", { "os": "win32", "cpu": "x64" }, "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw=="], "glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], diff --git a/packages/cli/package.json b/packages/cli/package.json index 3d0aec309..88481419d 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -25,6 +25,7 @@ }, "dependencies": { "@hono/node-server": "^1.8.0", + "@nimrobo/superconnector": "^0.2.1", "@puppeteer/browsers": "^2.13.0", "adm-zip": "^0.5.16", "citty": "^0.2.1", diff --git a/packages/cli/src/server/fileWatcher.test.ts b/packages/cli/src/server/fileWatcher.test.ts index 62412fcb9..e047fa974 100644 --- a/packages/cli/src/server/fileWatcher.test.ts +++ b/packages/cli/src/server/fileWatcher.test.ts @@ -15,5 +15,6 @@ describe("shouldWatchProjectFile", () => { expect(shouldWatchProjectFile("renders/output.mp4")).toBe(false); expect(shouldWatchProjectFile("dist/index.html")).toBe(false); expect(shouldWatchProjectFile(".hyperframes/cache.json")).toBe(false); + expect(shouldWatchProjectFile(".superconnector/config.json")).toBe(false); }); }); diff --git a/packages/cli/src/server/fileWatcher.ts b/packages/cli/src/server/fileWatcher.ts index 050f009ea..30191bf15 100644 --- a/packages/cli/src/server/fileWatcher.ts +++ b/packages/cli/src/server/fileWatcher.ts @@ -13,6 +13,7 @@ const WATCHER_EXCLUDED_DIRS = new Set([ ".git", ".hyperframes", ".next", + ".superconnector", ".vite", "build", "coverage", diff --git a/packages/core/package.json b/packages/core/package.json index 831be72bc..7a207adb5 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -135,6 +135,7 @@ }, "dependencies": { "@chenglou/pretext": "^0.0.5", + "@nimrobo/superconnector": "^0.2.1", "postcss": "^8.5.8" }, "devDependencies": { diff --git a/packages/core/src/studio-api/createStudioApi.ts b/packages/core/src/studio-api/createStudioApi.ts index 04492aad4..bd4f1e80c 100644 --- a/packages/core/src/studio-api/createStudioApi.ts +++ b/packages/core/src/studio-api/createStudioApi.ts @@ -8,6 +8,7 @@ import { registerRenderRoutes } from "./routes/render.js"; import { registerThumbnailRoutes } from "./routes/thumbnail.js"; import { registerWaveformRoutes } from "./routes/waveform.js"; import { registerFontRoutes } from "./routes/fonts.js"; +import { registerTimelineCommentRoutes } from "./routes/timelineComments.js"; /** * Create a Hono sub-app with all studio API routes. @@ -26,6 +27,7 @@ export function createStudioApi(adapter: StudioApiAdapter): Hono { registerThumbnailRoutes(api, adapter); registerWaveformRoutes(api, adapter); registerFontRoutes(api); + registerTimelineCommentRoutes(api, adapter); return api; } diff --git a/packages/core/src/studio-api/helpers/timelineComments.test.ts b/packages/core/src/studio-api/helpers/timelineComments.test.ts new file mode 100644 index 000000000..a4774af97 --- /dev/null +++ b/packages/core/src/studio-api/helpers/timelineComments.test.ts @@ -0,0 +1,129 @@ +import { afterEach, describe, expect, it } from "vitest"; +import { parseHTML } from "linkedom"; +import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { + insertTimelineComment, + parseTimelineCommentsFromHtml, + removeTimelineComment, + removeTimelineCommentFromProject, + writeTimelineCommentToProject, +} from "./timelineComments"; + +const tempDirs: string[] = []; + +afterEach(() => { + for (const dir of tempDirs.splice(0)) { + rmSync(dir, { recursive: true, force: true }); + } +}); + +function createProjectDir(): string { + const dir = mkdtempSync(join(tmpdir(), "hf-comments-helper-test-")); + tempDirs.push(dir); + return dir; +} + +describe("timeline comments", () => { + it("inserts a source comment before a target id", () => { + const html = '
Hero
'; + const result = insertTimelineComment(html, { + id: "hfc_test", + filePath: "index.html", + rangeStart: 1, + rangeEnd: 4, + prompt: "Make hero faster", + elements: [{ id: "hero", tag: "section", start: 1, duration: 3, track: 0 }], + target: { id: "hero" }, + }); + + expect(result.indexOf("hyperframes-comment")).toBeLessThan(result.indexOf('id="hero"')); + expect(result).toContain(""); + expect(parseTimelineCommentsFromHtml(result, "index.html")).toMatchObject([ + { + id: "hfc_test", + filePath: "index.html", + rangeStart: 1, + rangeEnd: 4, + prompt: "Make hero faster", + }, + ]); + }); + + it("removes only the matching source comment", () => { + const html = insertTimelineComment( + insertTimelineComment("
Body
", { + id: "hfc_keep", + filePath: "index.html", + rangeStart: 0, + rangeEnd: 1, + prompt: "Keep", + elements: [], + }), + { + id: "hfc_remove", + filePath: "index.html", + rangeStart: 2, + rangeEnd: 3, + prompt: "Remove", + elements: [], + }, + ); + + const result = removeTimelineComment(html, "hfc_remove"); + + expect(result).toContain("hfc_keep"); + expect(result).not.toContain("hfc_remove"); + }); + + it("removes a comment id from every .html file that contains it", () => { + const projectDir = createProjectDir(); + writeFileSync(join(projectDir, "a.html"), "
A
", "utf-8"); + writeFileSync(join(projectDir, "b.html"), "
B
", "utf-8"); + + writeTimelineCommentToProject(projectDir, { + id: "hfc_dup", + filePath: "a.html", + rangeStart: 0, + rangeEnd: 1, + prompt: "dup", + elements: [], + }); + writeTimelineCommentToProject(projectDir, { + id: "hfc_dup", + filePath: "b.html", + rangeStart: 0, + rangeEnd: 1, + prompt: "dup", + elements: [], + }); + + const result = removeTimelineCommentFromProject(projectDir, "hfc_dup"); + + expect(result.changed).toBe(true); + expect(readFileSync(join(projectDir, "a.html"), "utf-8")).not.toContain("hfc_dup"); + expect(readFileSync(join(projectDir, "b.html"), "utf-8")).not.toContain("hfc_dup"); + }); + + it("does not create visible text nodes in the rendered document", () => { + const html = insertTimelineComment( + '
Hero
', + { + id: "hfc_hidden", + filePath: "index.html", + rangeStart: 1, + rangeEnd: 4, + prompt: "Make hero faster", + elements: [], + target: { id: "hero" }, + }, + ); + + const { document } = parseHTML(html); + + expect(document.body?.textContent ?? document.textContent).not.toContain("Make hero faster"); + expect(document.body?.textContent ?? document.textContent).not.toContain("hfc_hidden"); + }); +}); diff --git a/packages/core/src/studio-api/helpers/timelineComments.ts b/packages/core/src/studio-api/helpers/timelineComments.ts new file mode 100644 index 000000000..0578bc8f2 --- /dev/null +++ b/packages/core/src/studio-api/helpers/timelineComments.ts @@ -0,0 +1,269 @@ +import { readFileSync, writeFileSync } from "node:fs"; +import { join, resolve } from "node:path"; +import { isSafePath, walkDir } from "./safePath.js"; + +const COMMENT_RE = //g; + +export interface TimelineCommentElement { + id: string; + tag: string; + start: number; + duration: number; + track: number; + sourceFile?: string; + selector?: string; + selectorIndex?: number; + domId?: string; + compositionSrc?: string; +} + +export interface TimelineCommentRecord { + id: string; + status: "open"; + filePath: string; + rangeStart: number; + rangeEnd: number; + target?: string; + prompt: string; + elements: TimelineCommentElement[]; +} + +export interface CreateTimelineCommentInput { + id?: string; + filePath: string; + rangeStart: number; + rangeEnd: number; + prompt: string; + elements?: TimelineCommentElement[]; + target?: { + id?: string | null; + selector?: string; + selectorIndex?: number; + }; +} + +interface SerializedTimelineComment { + id: string; + status: "open"; + rangeStart: number; + rangeEnd: number; + target?: string; + prompt: string; + elements: TimelineCommentElement[]; +} + +export function isCreateTimelineCommentInput(value: unknown): value is CreateTimelineCommentInput { + if (!value || typeof value !== "object") return false; + const input = value as Partial; + return ( + typeof input.filePath === "string" && + typeof input.prompt === "string" && + typeof input.rangeStart === "number" && + typeof input.rangeEnd === "number" + ); +} + +export function createTimelineCommentId(): string { + return `hfc_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`; +} + +function serializeCommentPayload(input: SerializedTimelineComment): string { + return JSON.stringify(input) + .replaceAll("<", "\\u003c") + .replaceAll(">", "\\u003e") + .replaceAll("--", "\\u002d\\u002d"); +} + +function parseCommentPayload(body: string): SerializedTimelineComment | null { + try { + const parsed = JSON.parse(body) as { + id?: unknown; + status?: unknown; + rangeStart?: unknown; + rangeEnd?: unknown; + target?: unknown; + prompt?: unknown; + elements?: unknown; + }; + if (typeof parsed.id !== "string") return null; + if (parsed.status !== "open") return null; + if (typeof parsed.rangeStart !== "number" || typeof parsed.rangeEnd !== "number") return null; + return { + id: parsed.id, + status: "open", + rangeStart: parsed.rangeStart, + rangeEnd: parsed.rangeEnd, + target: typeof parsed.target === "string" ? parsed.target : undefined, + prompt: typeof parsed.prompt === "string" ? parsed.prompt : "", + elements: Array.isArray(parsed.elements) + ? parsed.elements.filter(isTimelineCommentElement) + : [], + }; + } catch { + return null; + } +} + +export function isTimelineCommentElement(value: unknown): value is TimelineCommentElement { + if (!value || typeof value !== "object") return false; + const candidate = value as Partial; + return ( + typeof candidate.id === "string" && + typeof candidate.tag === "string" && + typeof candidate.start === "number" && + typeof candidate.duration === "number" && + typeof candidate.track === "number" + ); +} + +function buildCommentBlock(input: CreateTimelineCommentInput & { id: string }): string { + const start = Math.min(input.rangeStart, input.rangeEnd); + const end = Math.max(input.rangeStart, input.rangeEnd); + const payload = serializeCommentPayload({ + id: input.id, + status: "open", + rangeStart: start, + rangeEnd: end, + target: input.target?.id || input.target?.selector || input.filePath, + prompt: input.prompt, + elements: input.elements ?? [], + }); + + return `\n`; +} + +function findInsertionIndex(html: string, target?: CreateTimelineCommentInput["target"]): number { + if (target?.id) { + const re = new RegExp(`<[^>]+\\bid=["']${escapeRegExp(target.id)}["'][^>]*>`, "i"); + const match = re.exec(html); + if (match?.index != null) return match.index; + } + + if (target?.selector) { + const targetIndex = Math.max(0, target.selectorIndex ?? 0); + for (const re of selectorToTagRegex(target.selector.trim())) { + const match = Array.from(html.matchAll(re))[targetIndex]; + if (match?.index != null) return match.index; + } + } + + const bodyMatch = /]*>/i.exec(html); + return bodyMatch?.index != null ? bodyMatch.index + bodyMatch[0].length : 0; +} + +function selectorToTagRegex(selector: string): RegExp[] { + if (selector.startsWith("#")) { + const id = selector.slice(1); + return [new RegExp(`<[^>]+\\bid=["']${escapeRegExp(id)}["'][^>]*>`, "gi")]; + } + if (selector.startsWith(".")) { + const cls = selector.slice(1); + return [ + new RegExp( + `<[^>]+\\bclass=["'][^"']*(?:^|\\s)${escapeRegExp(cls)}(?:\\s|$)[^"']*["'][^>]*>`, + "gi", + ), + ]; + } + if (/^[a-zA-Z][a-zA-Z0-9-]*$/.test(selector)) { + return [new RegExp(`<${escapeRegExp(selector)}\\b[^>]*>`, "gi")]; + } + return []; +} + +function escapeRegExp(value: string): string { + return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); +} + +function resolveProjectFile(projectDir: string, filePath: string): string { + if (filePath.includes("\0")) throw new Error("forbidden"); + const absPath = resolve(projectDir, filePath); + if (!isSafePath(projectDir, absPath)) throw new Error("forbidden"); + return absPath; +} + +export function insertTimelineComment( + html: string, + input: CreateTimelineCommentInput & { id: string }, +): string { + const block = buildCommentBlock(input); + const index = findInsertionIndex(html, input.target); + const prefix = html.slice(0, index); + const suffix = html.slice(index); + const needsLeadingNewline = prefix.length > 0 && !prefix.endsWith("\n"); + const needsTrailingNewline = suffix.length > 0 && !suffix.startsWith("\n"); + return `${prefix}${needsLeadingNewline ? "\n" : ""}${block}${needsTrailingNewline ? "\n" : ""}${suffix}`; +} + +export function removeTimelineComment(html: string, commentId: string): string { + COMMENT_RE.lastIndex = 0; + return html.replace(COMMENT_RE, (full, payload: string) => { + const parsed = parseCommentPayload(payload); + return parsed?.id === commentId ? "" : full; + }); +} + +export function parseTimelineCommentsFromHtml( + html: string, + filePath: string, +): TimelineCommentRecord[] { + const comments: TimelineCommentRecord[] = []; + COMMENT_RE.lastIndex = 0; + for (const match of html.matchAll(COMMENT_RE)) { + const parsed = parseCommentPayload(match[1] ?? ""); + if (!parsed) continue; + comments.push({ + id: parsed.id, + status: "open", + filePath, + rangeStart: parsed.rangeStart, + rangeEnd: parsed.rangeEnd, + target: parsed.target, + prompt: parsed.prompt, + elements: parsed.elements, + }); + } + return comments; +} + +export function listTimelineComments(projectDir: string): TimelineCommentRecord[] { + return walkDir(projectDir) + .filter((file) => file.endsWith(".html")) + .flatMap((file) => { + const html = readFileSync(join(projectDir, file), "utf-8"); + return parseTimelineCommentsFromHtml(html, file); + }) + .sort((a, b) => a.rangeStart - b.rangeStart || a.filePath.localeCompare(b.filePath)); +} + +export function writeTimelineCommentToProject( + projectDir: string, + input: CreateTimelineCommentInput, +): { comment: TimelineCommentRecord; content: string } { + const id = input.id ?? createTimelineCommentId(); + const absPath = resolveProjectFile(projectDir, input.filePath); + const original = readFileSync(absPath, "utf-8"); + const content = insertTimelineComment(original, { ...input, id }); + writeFileSync(absPath, content, "utf-8"); + const [comment] = parseTimelineCommentsFromHtml(content, input.filePath).filter( + (candidate) => candidate.id === id, + ); + if (!comment) throw new Error("failed to create timeline comment"); + return { comment, content }; +} + +export function removeTimelineCommentFromProject( + projectDir: string, + commentId: string, +): { changed: boolean; filePath?: string; content?: string } { + let first: { filePath: string; content: string } | null = null; + for (const filePath of walkDir(projectDir).filter((file) => file.endsWith(".html"))) { + const absPath = resolveProjectFile(projectDir, filePath); + const original = readFileSync(absPath, "utf-8"); + const content = removeTimelineComment(original, commentId); + if (content === original) continue; + writeFileSync(absPath, content, "utf-8"); + if (!first) first = { filePath, content }; + } + return first ? { changed: true, ...first } : { changed: false }; +} diff --git a/packages/core/src/studio-api/routes/timelineComments.test.ts b/packages/core/src/studio-api/routes/timelineComments.test.ts new file mode 100644 index 000000000..8d38c6593 --- /dev/null +++ b/packages/core/src/studio-api/routes/timelineComments.test.ts @@ -0,0 +1,144 @@ +import { afterEach, describe, expect, it } from "vitest"; +import { Hono } from "hono"; +import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { registerTimelineCommentRoutes } from "./timelineComments"; +import type { StudioApiAdapter } from "../types"; + +const tempDirs: string[] = []; + +afterEach(() => { + for (const dir of tempDirs.splice(0)) { + rmSync(dir, { recursive: true, force: true }); + } +}); + +function createProjectDir(): string { + const projectDir = mkdtempSync(join(tmpdir(), "hf-comments-test-")); + tempDirs.push(projectDir); + writeFileSync( + join(projectDir, "index.html"), + '
Hero
', + ); + return projectDir; +} + +function createAdapter(projectDir: string): StudioApiAdapter { + return { + listProjects: () => [], + resolveProject: async (id: string) => ({ id, dir: projectDir }), + bundle: async () => null, + lint: async () => ({ findings: [] }), + runtimeUrl: "/api/runtime.js", + rendersDir: () => "/tmp/renders", + startRender: () => ({ + id: "job-1", + status: "rendering", + progress: 0, + outputPath: "/tmp/out.mp4", + }), + }; +} + +describe("registerTimelineCommentRoutes", () => { + it("creates, lists, and clears source-backed comments", async () => { + const projectDir = createProjectDir(); + const app = new Hono(); + registerTimelineCommentRoutes(app, createAdapter(projectDir)); + + const createResponse = await app.request("http://localhost/projects/demo/timeline-comments", { + method: "POST", + body: JSON.stringify({ + id: "hfc_route", + filePath: "index.html", + rangeStart: 1, + rangeEnd: 4, + prompt: "Make hero faster", + elements: [{ id: "hero", tag: "section", start: 1, duration: 3, track: 0 }], + target: { id: "hero" }, + }), + }); + expect(createResponse.status).toBe(201); + expect(readFileSync(join(projectDir, "index.html"), "utf-8")).toContain("hfc_route"); + + const listResponse = await app.request("http://localhost/projects/demo/timeline-comments"); + const listed = (await listResponse.json()) as { comments: Array<{ id: string }> }; + expect(listed.comments.map((comment) => comment.id)).toEqual(["hfc_route"]); + + const deleteResponse = await app.request( + "http://localhost/projects/demo/timeline-comments/hfc_route", + { method: "DELETE" }, + ); + expect(deleteResponse.status).toBe(200); + expect(readFileSync(join(projectDir, "index.html"), "utf-8")).not.toContain("hfc_route"); + }); + + it("rejects agent-run payloads with malformed elements", async () => { + const projectDir = createProjectDir(); + const app = new Hono(); + registerTimelineCommentRoutes(app, createAdapter(projectDir)); + + const response = await app.request("http://localhost/projects/demo/agent/resolve-comment", { + method: "POST", + body: JSON.stringify({ + commentId: "hfc_route", + filePath: "index.html", + prompt: "do thing", + rangeStart: 0, + rangeEnd: 1, + elements: [{ id: "hero" }], + }), + }); + + expect(response.status).toBe(400); + }); + + it("reports no active runs when none are in flight", async () => { + const projectDir = createProjectDir(); + const app = new Hono(); + registerTimelineCommentRoutes(app, createAdapter(projectDir)); + + const response = await app.request("http://localhost/projects/demo/agent/active-run"); + expect(response.status).toBe(200); + expect(await response.json()).toEqual({ active: false, run: null }); + }); + + it("cancel endpoint is a no-op when nothing is running for the given comment", async () => { + const projectDir = createProjectDir(); + const app = new Hono(); + registerTimelineCommentRoutes(app, createAdapter(projectDir)); + + const response = await app.request("http://localhost/projects/demo/agent/cancel", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ commentId: "hfc_missing" }), + }); + expect(response.status).toBe(200); + expect(await response.json()).toEqual({ ok: true, cancelled: false }); + }); + + it("cancel endpoint rejects missing commentId", async () => { + const projectDir = createProjectDir(); + const app = new Hono(); + registerTimelineCommentRoutes(app, createAdapter(projectDir)); + + const response = await app.request("http://localhost/projects/demo/agent/cancel", { + method: "POST", + }); + expect(response.status).toBe(400); + }); + + it("rejects invalid comment payloads", async () => { + const projectDir = createProjectDir(); + const app = new Hono(); + registerTimelineCommentRoutes(app, createAdapter(projectDir)); + + const response = await app.request("http://localhost/projects/demo/timeline-comments", { + method: "POST", + body: JSON.stringify({ filePath: "index.html" }), + }); + + expect(response.status).toBe(400); + }); +}); diff --git a/packages/core/src/studio-api/routes/timelineComments.ts b/packages/core/src/studio-api/routes/timelineComments.ts new file mode 100644 index 000000000..7b6e3144d --- /dev/null +++ b/packages/core/src/studio-api/routes/timelineComments.ts @@ -0,0 +1,354 @@ +import { realpathSync } from "node:fs"; +import type { Hono } from "hono"; +import { + isCreateTimelineCommentInput, + isTimelineCommentElement, + listTimelineComments, + removeTimelineCommentFromProject, + type TimelineCommentElement, + writeTimelineCommentToProject, +} from "../helpers/timelineComments.js"; +import type { ResolvedProject, StudioApiAdapter } from "../types.js"; + +interface StudioAgentRunInput { + commentId: string; + filePath: string; + prompt: string; + rangeStart: number; + rangeEnd: number; + elements: TimelineCommentElement[]; +} + +interface StudioAgentRunEvent { + type: string; + sessionId?: string; + content?: unknown; + error?: string; +} + +type AdapterInfo = { kind: string; detected: boolean; selected: boolean }; + +type SuperconnectorModule = { + createSuperconnector: (opts?: { cwd?: string }) => { + whichAdapterWillRun: (opts: { + appId: string; + sessionSelector?: string; + resumeLastCreatedSession?: boolean; + }) => { + ready: boolean; + adapter: string | null; + action?: string; + source?: string; + reason?: string; + session?: { sessionId?: string } | null; + }; + listAdapters: () => AdapterInfo[]; + spawn: (opts: { + prompt: string; + appId: string; + sessionSelector?: string; + resumeLastCreatedSession?: boolean; + permissionMode?: "acceptEdits"; + signal?: AbortSignal; + }) => AsyncIterable<{ type: string; sessionId?: string; content?: unknown }>; + }; +}; + +const TIMELINE_COMMENT_AGENT_APP_ID = "hyperframes"; + +interface ActiveAgentRun { + commentId: string; + abortController: AbortController; + startedAt: number; +} + +const activeAgentRuns = new Map(); + +function encodeNdjson(event: StudioAgentRunEvent): Uint8Array { + return new TextEncoder().encode(`${JSON.stringify(event)}\n`); +} + +function isAbortError(err: unknown): boolean { + return ( + err instanceof Error && + (err.name === "AbortError" || err.message.toLowerCase().includes("abort")) + ); +} + +function isStudioAgentRunInput(value: unknown): value is StudioAgentRunInput { + if (!value || typeof value !== "object") return false; + const input = value as Partial; + return ( + typeof input.commentId === "string" && + typeof input.filePath === "string" && + typeof input.prompt === "string" && + typeof input.rangeStart === "number" && + typeof input.rangeEnd === "number" && + Array.isArray(input.elements) && + input.elements.every(isTimelineCommentElement) + ); +} + +function createSuperconnectorOptions(projectDir: string): { cwd: string } { + return { cwd: realpathSync(projectDir) }; +} + +async function loadSuperconnector(): Promise { + const dynamicImport = new Function("specifier", "return import(specifier)") as ( + specifier: string, + ) => Promise; + return dynamicImport("@nimrobo/superconnector"); +} + +type SuperconnectorConfig = { preferredAdapter?: string; [key: string]: unknown }; + +type SuperconnectorConfigModule = { + localConfigPath: (cwd: string) => string; + readConfig: (path: string) => SuperconnectorConfig | null; + writeConfig: (path: string, cfg: SuperconnectorConfig) => void; +}; + +async function loadSuperconnectorConfig(): Promise { + const dynamicImport = new Function("specifier", "return import(specifier)") as ( + specifier: string, + ) => Promise; + return dynamicImport("@nimrobo/superconnector/config"); +} + +function buildTimelineCommentAgentPrompt( + project: ResolvedProject, + input: StudioAgentRunInput, +): string { + const elements = input.elements + .map((element) => { + const source = element.sourceFile ? `, source ${element.sourceFile}` : ""; + const selector = element.selector ? `, selector ${element.selector}` : ""; + return `- ${element.id} (${element.tag}) ${element.start}-${element.start + element.duration}s, track ${element.track}${source}${selector}`; + }) + .join("\n"); + + return `You are resolving a Hyperframes Studio timeline comment. + +Project: ${project.title ?? project.id} +Comment id: ${input.commentId} +Comment file: ${input.filePath} +Time range: ${input.rangeStart}-${input.rangeEnd}s + +Affected timeline elements: +${elements || "(none)"} + +User request: +${input.prompt} + +Instructions: +- Edit the project files directly to satisfy the request. +- Follow Hyperframes conventions: HTML compositions, data-start/data-duration/data-track-index, paused GSAP timelines registered on window.__timelines, deterministic rendering. +- Keep the change scoped to this timeline request. +- When the request is resolved, remove the matching "" comment with id "${input.commentId}" from ${input.filePath}. +- If you edit any .html composition, run npx hyperframes lint and npx hyperframes validate before finishing.`; +} + +async function previewAgentRun(project: ResolvedProject) { + const { createSuperconnector } = await loadSuperconnector(); + const superconnectorOptions = createSuperconnectorOptions(project.dir); + const sc = createSuperconnector(superconnectorOptions); + const preview = sc.whichAdapterWillRun({ + appId: TIMELINE_COMMENT_AGENT_APP_ID, + sessionSelector: project.id, + resumeLastCreatedSession: false, + }); + let adapters: { kind: string; detected: boolean }[] = []; + try { + adapters = sc.listAdapters().map((a) => ({ kind: a.kind, detected: a.detected })); + } catch { + // listAdapters is advisory; preview still works without the adapter list. + } + return { + ready: preview.ready, + agent: preview.adapter, + path: superconnectorOptions.cwd, + action: preview.action, + source: preview.source, + reason: preview.reason, + adapters, + }; +} + +async function* runAgentCommentResolution( + project: ResolvedProject, + input: StudioAgentRunInput, + signal?: AbortSignal, +): AsyncIterable { + const { createSuperconnector } = await loadSuperconnector(); + const sc = createSuperconnector(createSuperconnectorOptions(project.dir)); + for await (const msg of sc.spawn({ + prompt: buildTimelineCommentAgentPrompt(project, input), + appId: TIMELINE_COMMENT_AGENT_APP_ID, + sessionSelector: project.id, + resumeLastCreatedSession: false, + permissionMode: "acceptEdits", + signal, + })) { + yield { + type: msg.type, + sessionId: msg.sessionId, + content: msg.content, + }; + } +} + +export function registerTimelineCommentRoutes(api: Hono, adapter: StudioApiAdapter): void { + // Warm the Superconnector modules at startup so the first agent preview and + // the first adapter change don't pay the cold dynamic-import cost. + void loadSuperconnector().catch(() => {}); + void loadSuperconnectorConfig().catch(() => {}); + + api.get("/projects/:id/timeline-comments", async (c) => { + const project = await adapter.resolveProject(c.req.param("id")); + if (!project) return c.json({ error: "not found" }, 404); + return c.json({ comments: listTimelineComments(project.dir) }); + }); + + api.post("/projects/:id/timeline-comments", async (c) => { + const project = await adapter.resolveProject(c.req.param("id")); + if (!project) return c.json({ error: "not found" }, 404); + + const body = await c.req.json().catch(() => null); + if (!isCreateTimelineCommentInput(body)) { + return c.json({ error: "filePath and prompt required" }, 400); + } + + try { + const result = writeTimelineCommentToProject(project.dir, body); + return c.json(result, 201); + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + const status = message === "forbidden" ? 403 : 500; + return c.json({ error: message }, status); + } + }); + + api.delete("/projects/:id/timeline-comments/:commentId", async (c) => { + const project = await adapter.resolveProject(c.req.param("id")); + if (!project) return c.json({ error: "not found" }, 404); + + const result = removeTimelineCommentFromProject(project.dir, c.req.param("commentId")); + return c.json({ ok: true, ...result }); + }); + + api.get("/projects/:id/agent/preview", async (c) => { + const project = await adapter.resolveProject(c.req.param("id")); + if (!project) return c.json({ error: "not found" }, 404); + return c.json(await previewAgentRun(project)); + }); + + api.put("/projects/:id/agent/adapter", async (c) => { + const project = await adapter.resolveProject(c.req.param("id")); + if (!project) return c.json({ error: "not found" }, 404); + + const body = (await c.req.json().catch(() => null)) as { adapter?: unknown } | null; + if (!body || typeof body.adapter !== "string") { + return c.json({ error: "adapter required" }, 400); + } + + const { createSuperconnector } = await loadSuperconnector(); + const options = createSuperconnectorOptions(project.dir); + const sc = createSuperconnector(options); + const known = new Set(sc.listAdapters().map((a) => a.kind)); + if (!known.has(body.adapter)) { + return c.json({ error: `unknown adapter: ${body.adapter}` }, 400); + } + + const { localConfigPath, readConfig, writeConfig } = await loadSuperconnectorConfig(); + const configPath = localConfigPath(options.cwd); + const current = readConfig(configPath) ?? {}; + writeConfig(configPath, { ...current, preferredAdapter: body.adapter }); + + return c.json(await previewAgentRun(project)); + }); + + api.post("/projects/:id/agent/resolve-comment", async (c) => { + const project = await adapter.resolveProject(c.req.param("id")); + if (!project) return c.json({ error: "not found" }, 404); + + const input = await c.req.json().catch(() => null); + if (!isStudioAgentRunInput(input)) { + return c.json({ error: "commentId, filePath, and prompt required" }, 400); + } + + if (activeAgentRuns.has(project.id)) { + return c.json({ error: "busy", commentId: input.commentId }, 409); + } + + const controller = new AbortController(); + c.req.raw.signal.addEventListener("abort", () => controller.abort(), { once: true }); + + activeAgentRuns.set(project.id, { + commentId: input.commentId, + abortController: controller, + startedAt: Date.now(), + }); + + const stream = new ReadableStream({ + async start(streamController) { + try { + for await (const event of runAgentCommentResolution(project, input, controller.signal)) { + streamController.enqueue(encodeNdjson(event)); + } + streamController.enqueue(encodeNdjson({ type: "done" })); + } catch (err) { + if (controller.signal.aborted || isAbortError(err)) { + // client disconnected or cancelled — close the stream silently + } else { + streamController.enqueue( + encodeNdjson({ + type: "error", + error: err instanceof Error ? err.message : String(err), + }), + ); + } + } finally { + activeAgentRuns.delete(project.id); + streamController.close(); + } + }, + cancel() { + controller.abort(); + }, + }); + + return new Response(stream, { + headers: { + "Content-Type": "application/x-ndjson", + "Cache-Control": "no-store", + }, + }); + }); + + api.get("/projects/:id/agent/active-run", async (c) => { + const project = await adapter.resolveProject(c.req.param("id")); + if (!project) return c.json({ error: "not found" }, 404); + const active = activeAgentRuns.get(project.id); + return c.json({ + active: !!active, + run: active ? { commentId: active.commentId, startedAt: active.startedAt } : null, + }); + }); + + api.post("/projects/:id/agent/cancel", async (c) => { + const project = await adapter.resolveProject(c.req.param("id")); + if (!project) return c.json({ error: "not found" }, 404); + const body = (await c.req.json().catch(() => null)) as { commentId?: unknown } | null; + if (!body || typeof body.commentId !== "string") { + return c.json({ error: "commentId required" }, 400); + } + const active = activeAgentRuns.get(project.id); + if (active && active.commentId === body.commentId) { + active.abortController.abort(); + activeAgentRuns.delete(project.id); + return c.json({ ok: true, cancelled: "running", commentId: active.commentId }); + } + + return c.json({ ok: true, cancelled: false }); + }); +} diff --git a/packages/studio/src/App.tsx b/packages/studio/src/App.tsx index 1cf24b2c5..0ee6e373e 100644 --- a/packages/studio/src/App.tsx +++ b/packages/studio/src/App.tsx @@ -45,6 +45,8 @@ import { normalizeStudioCompositionPath, readStudioUrlStateFromWindow, } from "./utils/studioUrlState"; +import { TimelineCommentsTray } from "./timeline-comments/TimelineCommentsTray"; +import { useTimelineComments } from "./timeline-comments/useTimelineComments"; export function StudioApp() { const { projectId, resolving, waitingForServer } = useServerConnection(); @@ -379,11 +381,26 @@ export function StudioApp() { toggleTimelineVisibility, }; + const timelineAgent = useTimelineComments({ + projectId, + activeCompPath, + editingPathRef: fileManager.editingPathRef, + refreshKey, + setEditingFile: fileManager.setEditingFile, + onProjectContentChanged: () => setRefreshKey((key) => key + 1), + onNotify: showToast, + }); + if (resolving || waitingForServer || !projectId) { return ; } - const timelineToolbar = ; + const timelineToolbar = ( + } + /> + ); return ( @@ -439,6 +456,10 @@ export function StudioApp() { handleTimelineElementMove={timelineEditing.handleTimelineElementMove} handleTimelineElementResize={timelineEditing.handleTimelineElementResize} handleBlockedTimelineEdit={timelineEditing.handleBlockedTimelineEdit} + onSendPromptToAgent={timelineAgent.sendPromptToAgent} + agentRunPreview={timelineAgent.agentRunPreview} + onSelectAgent={timelineAgent.setPreferredAdapter} + timelineComments={timelineAgent.comments} setCompIdToSrc={setCompIdToSrc} setCompositionLoading={setCompositionLoading} shouldShowSelectedDomBounds={shouldShowSelectedDomBounds} diff --git a/packages/studio/src/components/StudioPreviewArea.tsx b/packages/studio/src/components/StudioPreviewArea.tsx index 74911d067..e80a88d6d 100644 --- a/packages/studio/src/components/StudioPreviewArea.tsx +++ b/packages/studio/src/components/StudioPreviewArea.tsx @@ -5,6 +5,11 @@ import { CaptionTimeline } from "../captions/components/CaptionTimeline"; import { DomEditOverlay } from "./editor/DomEditOverlay"; import type { TimelineElement } from "../player"; import type { BlockedTimelineEditIntent } from "../player/components/timelineEditing"; +import type { + AgentRunPreview, + SendPromptToAgentInput, + TimelineComment, +} from "../timeline-comments/types"; import { STUDIO_INSPECTOR_PANELS_ENABLED, STUDIO_PREVIEW_MANUAL_EDITING_ENABLED, @@ -38,6 +43,10 @@ export interface StudioPreviewAreaProps { updates: Pick, ) => Promise | void; handleBlockedTimelineEdit: (element: TimelineElement, intent: BlockedTimelineEditIntent) => void; + onSendPromptToAgent: (input: SendPromptToAgentInput) => Promise | void; + agentRunPreview: AgentRunPreview | null; + onSelectAgent: (adapterKind: string) => Promise | void; + timelineComments: Pick[]; setCompIdToSrc: (map: Map) => void; setCompositionLoading: (loading: boolean) => void; shouldShowSelectedDomBounds: boolean; @@ -52,6 +61,10 @@ export function StudioPreviewArea({ handleTimelineElementMove, handleTimelineElementResize, handleBlockedTimelineEdit, + onSendPromptToAgent, + agentRunPreview, + onSelectAgent, + timelineComments, setCompIdToSrc, setCompositionLoading, shouldShowSelectedDomBounds, @@ -103,6 +116,10 @@ export function StudioPreviewArea({ onResizeElement={handleTimelineElementResize} onBlockedEditAttempt={handleBlockedTimelineEdit} onSelectTimelineElement={handleTimelineElementSelect} + onSendPromptToAgent={onSendPromptToAgent} + agentRunPreview={agentRunPreview} + onSelectAgent={onSelectAgent} + timelineComments={timelineComments} onCompIdToSrcChange={setCompIdToSrc} onCompositionLoadingChange={setCompositionLoading} onCompositionChange={(compPath) => { diff --git a/packages/studio/src/components/TimelineToolbar.tsx b/packages/studio/src/components/TimelineToolbar.tsx index f0866a479..5ccf5658a 100644 --- a/packages/studio/src/components/TimelineToolbar.tsx +++ b/packages/studio/src/components/TimelineToolbar.tsx @@ -4,12 +4,14 @@ import { } from "../player/components/timelineZoom"; import { getTimelineToggleTitle } from "../utils/timelineDiscovery"; import { usePlayerStore } from "../player"; +import type { ReactNode } from "react"; interface TimelineToolbarProps { toggleTimelineVisibility: () => void; + traySlot?: ReactNode; } -export function TimelineToolbar({ toggleTimelineVisibility }: TimelineToolbarProps) { +export function TimelineToolbar({ toggleTimelineVisibility, traySlot }: TimelineToolbarProps) { const zoomMode = usePlayerStore((s) => s.zoomMode); const manualZoomPercent = usePlayerStore((s) => s.manualZoomPercent); const setZoomMode = usePlayerStore((s) => s.setZoomMode); @@ -17,12 +19,13 @@ export function TimelineToolbar({ toggleTimelineVisibility }: TimelineToolbarPro const displayedTimelineZoomPercent = getTimelineZoomPercent(zoomMode, manualZoomPercent); return ( -
+
Timeline
+ {traySlot} + +
+ + {/* Agent target */} +
+
+ Agent + {agentRunPreview?.adapters?.some((a) => a.detected) ? ( + + ) : ( + + {agentRunPreview?.agent ?? "unavailable"} + + )} +
+
+ Path + + {agentRunPreview?.path ?? "unavailable"} + +
diff --git a/packages/studio/src/player/components/Timeline.tsx b/packages/studio/src/player/components/Timeline.tsx index 30ca7205f..fd1df23da 100644 --- a/packages/studio/src/player/components/Timeline.tsx +++ b/packages/studio/src/player/components/Timeline.tsx @@ -20,6 +20,11 @@ import { shouldShowTimelineShortcutHint, resolveTimelineAssetDrop, } from "./timelineLayout"; +import type { + AgentRunPreview, + SendPromptToAgentInput, + TimelineComment, +} from "../../timeline-comments/types"; // Re-export pure utilities so existing imports from "./Timeline" still resolve. export { @@ -63,6 +68,10 @@ interface TimelineProps { ) => Promise | void; onBlockedEditAttempt?: (element: TimelineElement, intent: BlockedTimelineEditIntent) => void; onSelectElement?: (element: TimelineElement | null) => void; + onSendPromptToAgent?: (input: SendPromptToAgentInput) => Promise | void; + agentRunPreview?: AgentRunPreview | null; + onSelectAgent?: (adapterKind: string) => Promise | void; + comments?: Pick[]; theme?: Partial; } @@ -78,6 +87,10 @@ export const Timeline = memo(function Timeline({ onResizeElement, onBlockedEditAttempt, onSelectElement, + onSendPromptToAgent, + agentRunPreview, + onSelectAgent, + comments = [], theme: themeOverrides, }: TimelineProps = {}) { const theme = useMemo(() => ({ ...defaultTimelineTheme, ...themeOverrides }), [themeOverrides]); @@ -422,6 +435,7 @@ export const Timeline = memo(function Timeline({ majorTickInterval={majorTickInterval} shiftHeld={shiftHeld} rangeSelection={rangeSelection} + comments={comments} theme={theme} displayTrackOrder={displayTrackOrder} trackOrder={trackOrder} @@ -483,6 +497,9 @@ export const Timeline = memo(function Timeline({ setShowPopover(false); setRangeSelection(null); }} + onSendPromptToAgent={onSendPromptToAgent} + agentRunPreview={agentRunPreview} + onSelectAgent={onSelectAgent} /> )}
diff --git a/packages/studio/src/player/components/TimelineCanvas.tsx b/packages/studio/src/player/components/TimelineCanvas.tsx index ce9119331..1e95637a4 100644 --- a/packages/studio/src/player/components/TimelineCanvas.tsx +++ b/packages/studio/src/player/components/TimelineCanvas.tsx @@ -12,6 +12,7 @@ import type { TimelineElement } from "../store/playerStore"; import type { DraggedClipState, ResizingClipState, BlockedClipState } from "./useTimelineClipDrag"; import { formatTime } from "../lib/time"; import type { TrackVisualStyle } from "./timelineIcons"; +import type { TimelineComment } from "../../timeline-comments/types"; interface TimelineCanvasProps { major: number[]; @@ -23,6 +24,7 @@ interface TimelineCanvasProps { majorTickInterval: number; shiftHeld: boolean; rangeSelection: TimelineRangeSelection | null; + comments?: Pick[]; theme: TimelineTheme; displayTrackOrder: number[]; trackOrder: number[]; @@ -71,6 +73,7 @@ export const TimelineCanvas = memo(function TimelineCanvas({ majorTickInterval, shiftHeld, rangeSelection, + comments, theme, displayTrackOrder, trackOrder, @@ -173,6 +176,7 @@ export const TimelineCanvas = memo(function TimelineCanvas({ majorTickInterval={majorTickInterval} shiftHeld={shiftHeld} rangeSelection={rangeSelection} + comments={comments} theme={theme} /> diff --git a/packages/studio/src/player/components/TimelineRuler.tsx b/packages/studio/src/player/components/TimelineRuler.tsx index be4eaed1d..ad44182d1 100644 --- a/packages/studio/src/player/components/TimelineRuler.tsx +++ b/packages/studio/src/player/components/TimelineRuler.tsx @@ -2,6 +2,7 @@ import { memo } from "react"; import type { TimelineTheme } from "./timelineTheme"; import type { TimelineRangeSelection } from "./timelineEditing"; import { GUTTER, RULER_H, formatTimelineTickLabel } from "./timelineLayout"; +import type { TimelineComment } from "../../timeline-comments/types"; interface TimelineRulerProps { major: number[]; @@ -13,6 +14,7 @@ interface TimelineRulerProps { majorTickInterval: number; shiftHeld: boolean; rangeSelection: TimelineRangeSelection | null; + comments?: Pick[]; theme: TimelineTheme; } @@ -26,6 +28,7 @@ export const TimelineRuler = memo(function TimelineRuler({ majorTickInterval, shiftHeld, rangeSelection, + comments = [], theme, }: TimelineRulerProps) { return ( @@ -84,6 +87,19 @@ export const TimelineRuler = memo(function TimelineRuler({
))} + {comments.map((comment) => { + const start = Math.max(0, Math.min(effectiveDuration, comment.rangeStart)); + const end = Math.max(start, Math.min(effectiveDuration, comment.rangeEnd)); + const width = Math.max(6, (end - start) * pps); + return ( +
+ ); + })}
); diff --git a/packages/studio/src/timeline-comments/TimelineCommentsTray.tsx b/packages/studio/src/timeline-comments/TimelineCommentsTray.tsx new file mode 100644 index 000000000..0dce01e3b --- /dev/null +++ b/packages/studio/src/timeline-comments/TimelineCommentsTray.tsx @@ -0,0 +1,160 @@ +import { formatTime } from "../player"; +import type { AgentCommentStatusMap, TimelineComment } from "./types"; + +const DISPLAY_PROMPT_MAX = 160; + +function truncatePrompt(prompt: string): string { + if (!prompt) return "(no prompt provided)"; + if (prompt.length <= DISPLAY_PROMPT_MAX) return prompt; + return `${prompt.slice(0, DISPLAY_PROMPT_MAX).trimEnd()}…`; +} + +interface TimelineCommentsTrayProps { + comments: TimelineComment[]; + open: boolean; + status: AgentCommentStatusMap; + onToggleOpen: () => void; + onClose: () => void; + onRunAgent: (comment: TimelineComment) => void; + onCopyPrompt: (comment: TimelineComment) => void; + onClear: (commentId: string) => void; + onCancelAgentRun: (commentId: string) => void; +} + +export function TimelineCommentsTray({ + comments, + open, + status, + onToggleOpen, + onClose, + onRunAgent, + onCopyPrompt, + onClear, + onCancelAgentRun, +}: TimelineCommentsTrayProps) { + return ( + <> + + {open && ( +
+
+
Timeline Comments
+ +
+
+ {comments.length === 0 ? ( +
+ No open comments. +
+ ) : ( + comments.map((comment) => { + const entry = status[comment.id]; + const isRunning = entry?.status === "running"; + return ( +
+
+
+
+ {isRunning && ( + + )} + {formatTime(comment.rangeStart)} - {formatTime(comment.rangeEnd)} + {comment.filePath} +
+
+ {truncatePrompt(comment.prompt)} +
+
+
+ {isRunning ? ( + + ) : ( + + )} + + {!isRunning && ( + + )} +
+
+ {status[comment.id]?.status === "stopped" && ( +
+ {status[comment.id]?.message ?? "Stopped."} +
+ )} + {status[comment.id]?.status === "error" && ( +
+ {status[comment.id]?.message ?? "Agent run failed."} +
+ )} + {isRunning && entry.lastMessage && ( +
+ {entry.lastMessage} +
+ )} +
+ ); + }) + )} +
+
+ )} + + ); +} diff --git a/packages/studio/src/timeline-comments/agentRunController.test.ts b/packages/studio/src/timeline-comments/agentRunController.test.ts new file mode 100644 index 000000000..996c7ab77 --- /dev/null +++ b/packages/studio/src/timeline-comments/agentRunController.test.ts @@ -0,0 +1,295 @@ +import { describe, expect, it, vi } from "vitest"; +import { + createAgentRunController, + shouldCompleteActive, + type AgentRunState, +} from "./agentRunController"; +import type { TimelineComment } from "./types"; + +function comment(id: string): TimelineComment { + return { + id, + status: "open", + filePath: "index.html", + rangeStart: 0, + rangeEnd: 1, + prompt: `prompt-${id}`, + elements: [], + }; +} + +function deferred() { + let resolve!: (v: T) => void; + let reject!: (e: unknown) => void; + const promise = new Promise((res, rej) => { + resolve = res; + reject = rej; + }); + return { promise, resolve, reject }; +} + +describe("createAgentRunController", () => { + it("runs a single comment to completion", async () => { + const statuses: Array<[string, AgentRunState | null]> = []; + const settled = vi.fn(); + const d = deferred(); + const controller = createAgentRunController({ + run: () => d.promise, + onStatus: (id, s) => statuses.push([id, s]), + onSettled: settled, + }); + + expect(controller.run(comment("a"))).toBe(true); + expect(controller.activeId()).toBe("a"); + expect(statuses).toEqual([["a", { status: "running" }]]); + + d.resolve(); + await new Promise((r) => setTimeout(r, 0)); + + expect(statuses).toEqual([ + ["a", { status: "running" }], + ["a", { status: "stopped", message: "Done." }], + ]); + expect(settled).toHaveBeenCalledWith("a"); + expect(controller.activeId()).toBeNull(); + }); + + it("does not start another comment while one is active", async () => { + const statuses: Array<[string, AgentRunState | null]> = []; + const runs: string[] = []; + const d = deferred(); + const controller = createAgentRunController({ + run: (c) => { + runs.push(c.id); + return d.promise; + }, + onStatus: (id, s) => statuses.push([id, s]), + }); + + expect(controller.run(comment("a"))).toBe(true); + expect(controller.run(comment("b"))).toBe(false); + + expect(controller.activeId()).toBe("a"); + expect(runs).toEqual(["a"]); + expect(statuses).toEqual([["a", { status: "running" }]]); + + d.resolve(); + await new Promise((r) => setTimeout(r, 0)); + expect(controller.activeId()).toBeNull(); + }); + + it("allows a new run after the active run settles", async () => { + const statuses: Array<[string, AgentRunState | null]> = []; + const deferreds = [deferred(), deferred()]; + let runCount = 0; + const controller = createAgentRunController({ + run: () => deferreds[runCount++]!.promise, + onStatus: (id, s) => statuses.push([id, s]), + }); + + expect(controller.run(comment("a"))).toBe(true); + deferreds[0]!.resolve(); + await new Promise((r) => setTimeout(r, 0)); + + expect(controller.run(comment("b"))).toBe(true); + expect(controller.activeId()).toBe("b"); + expect(statuses.find(([id, s]) => id === "b" && s?.status === "running")).toBeTruthy(); + + deferreds[1]!.resolve(); + await new Promise((r) => setTimeout(r, 0)); + expect(controller.activeId()).toBeNull(); + }); + + it("cancels the active run via abort", async () => { + const statuses: Array<[string, AgentRunState | null]> = []; + const d = deferred(); + const signals: AbortSignal[] = []; + const controller = createAgentRunController({ + run: (_c, signal) => { + signals.push(signal); + signal.addEventListener("abort", () => d.reject(new Error("aborted"))); + return d.promise; + }, + onStatus: (id, s) => statuses.push([id, s]), + }); + + controller.run(comment("a")); + controller.cancel("a"); + await new Promise((r) => setTimeout(r, 0)); + + expect(signals[0]!.aborted).toBe(true); + expect(statuses).toEqual([ + ["a", { status: "running" }], + ["a", { status: "stopped", message: "Stopped." }], + ]); + expect(controller.activeId()).toBeNull(); + }); + + it("reports a completed run via abort as Done.", async () => { + const statuses: Array<[string, AgentRunState | null]> = []; + const d = deferred(); + const signals: AbortSignal[] = []; + const controller = createAgentRunController({ + run: (_c, signal) => { + signals.push(signal); + signal.addEventListener("abort", () => d.reject(new Error("aborted"))); + return d.promise; + }, + onStatus: (id, s) => statuses.push([id, s]), + }); + + controller.run(comment("a")); + controller.complete("a"); + await new Promise((r) => setTimeout(r, 0)); + + expect(signals[0]!.aborted).toBe(true); + expect(statuses).toEqual([ + ["a", { status: "running" }], + ["a", { status: "stopped", message: "Done." }], + ]); + expect(controller.activeId()).toBeNull(); + }); + + it("ignores complete for an inactive comment", async () => { + const statuses: Array<[string, AgentRunState | null]> = []; + const d = deferred(); + const controller = createAgentRunController({ + run: () => d.promise, + onStatus: (id, s) => statuses.push([id, s]), + }); + + controller.run(comment("a")); + controller.complete("b"); + + expect(controller.activeId()).toBe("a"); + expect(statuses).toEqual([["a", { status: "running" }]]); + + d.resolve(); + await new Promise((r) => setTimeout(r, 0)); + }); + + it("does not carry completingId into a later cancelled run", async () => { + const statuses: Array<[string, AgentRunState | null]> = []; + const deferreds = [deferred(), deferred()]; + let runCount = 0; + const controller = createAgentRunController({ + run: (_c, signal) => { + const d = deferreds[runCount++]!; + signal.addEventListener("abort", () => d.reject(new Error("aborted"))); + return d.promise; + }, + onStatus: (id, s) => statuses.push([id, s]), + }); + + controller.run(comment("a")); + controller.complete("a"); + await new Promise((r) => setTimeout(r, 0)); + + controller.run(comment("b")); + controller.cancel("b"); + await new Promise((r) => setTimeout(r, 0)); + + expect(statuses).toEqual([ + ["a", { status: "running" }], + ["a", { status: "stopped", message: "Done." }], + ["b", { status: "running" }], + ["b", { status: "stopped", message: "Stopped." }], + ]); + }); + + it("ignores cancel for an inactive comment", async () => { + const statuses: Array<[string, AgentRunState | null]> = []; + const d = deferred(); + const controller = createAgentRunController({ + run: () => d.promise, + onStatus: (id, s) => statuses.push([id, s]), + }); + + controller.run(comment("a")); + controller.cancel("b"); + + expect(controller.activeId()).toBe("a"); + expect(statuses).toEqual([["a", { status: "running" }]]); + + d.resolve(); + await new Promise((r) => setTimeout(r, 0)); + }); + + it("reports errors without starting another run", async () => { + const statuses: Array<[string, AgentRunState | null]> = []; + const d = deferred(); + const controller = createAgentRunController({ + run: () => d.promise, + onStatus: (id, s) => statuses.push([id, s]), + }); + + controller.run(comment("a")); + expect(controller.run(comment("b"))).toBe(false); + + d.reject(new Error("boom")); + await new Promise((r) => setTimeout(r, 0)); + + expect( + statuses.find(([id, s]) => id === "a" && s?.status === "error" && s.message === "boom"), + ).toBeTruthy(); + expect(statuses.some(([id]) => id === "b")).toBe(false); + expect(controller.activeId()).toBeNull(); + }); + + it("clears status and notifies when a run is blocked by the backend", async () => { + class BlockedError extends Error {} + + const statuses: Array<[string, AgentRunState | null]> = []; + const blocked = vi.fn(); + const d = deferred(); + const controller = createAgentRunController({ + run: () => d.promise, + onStatus: (id, s) => statuses.push([id, s]), + isBlockedError: (err) => err instanceof BlockedError, + onBlocked: blocked, + }); + + controller.run(comment("a")); + d.reject(new BlockedError("busy")); + await new Promise((r) => setTimeout(r, 0)); + + expect(statuses).toEqual([ + ["a", { status: "running" }], + ["a", null], + ]); + expect(blocked).toHaveBeenCalledOnce(); + expect(controller.activeId()).toBeNull(); + }); + + it("releases the active slot even when onSettled throws", async () => { + const d = deferred(); + const controller = createAgentRunController({ + run: () => d.promise, + onStatus: () => null, + onSettled: () => { + throw new Error("boom from settled"); + }, + }); + + controller.run(comment("a")); + d.resolve(); + await new Promise((r) => setTimeout(r, 0)); + + expect(controller.activeId()).toBeNull(); + expect(controller.run(comment("b"))).toBe(true); + }); +}); + +describe("shouldCompleteActive", () => { + it("returns true when the active id is no longer in the comments list", () => { + expect(shouldCompleteActive("a", [{ id: "b" }, { id: "c" }])).toBe(true); + }); + + it("returns false when the active id is still in the comments list", () => { + expect(shouldCompleteActive("a", [{ id: "a" }, { id: "b" }])).toBe(false); + }); + + it("returns false when no run is active", () => { + expect(shouldCompleteActive(null, [{ id: "a" }])).toBe(false); + }); +}); diff --git a/packages/studio/src/timeline-comments/agentRunController.ts b/packages/studio/src/timeline-comments/agentRunController.ts new file mode 100644 index 000000000..4ded27020 --- /dev/null +++ b/packages/studio/src/timeline-comments/agentRunController.ts @@ -0,0 +1,107 @@ +import type { TimelineComment } from "./types"; + +/** + * Returns true if the active run should be force-completed because its + * source-of-truth marker has disappeared from the comments list. The agent + * is instructed to remove the `` marker as + * its completion signal, so an active id that's no longer in the list means + * the work is done even if the network stream hasn't closed. + */ +export function shouldCompleteActive( + activeId: string | null, + comments: ReadonlyArray<{ id: string }>, +): boolean { + return !!activeId && !comments.some((c) => c.id === activeId); +} + +export interface AgentRunMeta { + agent?: string | null; + path?: string | null; +} + +export type AgentRunState = + | ({ status: "running" } & AgentRunMeta & { lastMessage?: string }) + | { status: "stopped"; message: string } + | { status: "error"; message: string }; + +export interface AgentRunControllerOptions { + run: (comment: TimelineComment, signal: AbortSignal) => Promise; + onStatus: (commentId: string, state: AgentRunState | null) => void; + onSettled?: (commentId: string) => void; + isBlockedError?: (err: unknown) => boolean; + onBlocked?: (commentId: string, err: unknown) => void; +} + +export interface AgentRunController { + run: (comment: TimelineComment, meta?: AgentRunMeta) => boolean; + cancel: (commentId: string) => void; + complete: (commentId: string) => void; + activeId: () => string | null; + abortAll: () => void; +} + +export function createAgentRunController({ + run, + onStatus, + onSettled, + isBlockedError, + onBlocked, +}: AgentRunControllerOptions): AgentRunController { + let activeId: string | null = null; + let activeAbort: AbortController | null = null; + // Set when a run is aborted because its work succeeded (marker removed) + // rather than because the user cancelled it — drives "Done." vs "Stopped." + let completingId: string | null = null; + + return { + run(comment, meta = {}) { + if (activeId) return false; + activeId = comment.id; + const controller = new AbortController(); + activeAbort = controller; + onStatus(comment.id, { status: "running", ...meta }); + run(comment, controller.signal) + .then(() => { + onStatus(comment.id, { status: "stopped", message: "Done." }); + }) + .catch((err: unknown) => { + if (isBlockedError?.(err)) { + onStatus(comment.id, null); + onBlocked?.(comment.id, err); + } else if (controller.signal.aborted) { + const message = completingId === comment.id ? "Done." : "Stopped."; + onStatus(comment.id, { status: "stopped", message }); + } else { + const message = err instanceof Error ? err.message : String(err); + onStatus(comment.id, { status: "error", message }); + } + }) + .finally(() => { + activeId = null; + activeAbort = null; + completingId = null; + try { + onSettled?.(comment.id); + } catch { + // user callback must not strand future runs + } + }); + return true; + }, + cancel(commentId) { + if (activeId === commentId) { + activeAbort?.abort(); + } + }, + complete(commentId) { + if (activeId === commentId) { + completingId = commentId; + activeAbort?.abort(); + } + }, + activeId: () => activeId, + abortAll() { + activeAbort?.abort(); + }, + }; +} diff --git a/packages/studio/src/timeline-comments/types.ts b/packages/studio/src/timeline-comments/types.ts new file mode 100644 index 000000000..e73f6fd87 --- /dev/null +++ b/packages/studio/src/timeline-comments/types.ts @@ -0,0 +1,43 @@ +import type { TimelineElement } from "../player/store/playerStore"; + +export interface TimelineComment { + id: string; + status: "open"; + filePath: string; + rangeStart: number; + rangeEnd: number; + target?: string; + prompt: string; + elements: TimelineElement[]; +} + +export interface SendPromptToAgentInput { + rangeStart: number; + rangeEnd: number; + elements: TimelineElement[]; + prompt: string; +} + +export interface AgentAdapterInfo { + kind: string; + detected: boolean; +} + +export interface AgentRunPreview { + ready: boolean; + agent: string | null; + path: string | null; + reason?: string; + adapters?: AgentAdapterInfo[]; +} + +export type AgentCommentStatusMap = Record< + string, + { + status: "running" | "stopped" | "error"; + message?: string; + agent?: string | null; + path?: string | null; + lastMessage?: string; + } +>; diff --git a/packages/studio/src/timeline-comments/useTimelineComments.ts b/packages/studio/src/timeline-comments/useTimelineComments.ts new file mode 100644 index 000000000..4fcce4982 --- /dev/null +++ b/packages/studio/src/timeline-comments/useTimelineComments.ts @@ -0,0 +1,406 @@ +import { + useCallback, + useEffect, + useMemo, + useRef, + useState, + type Dispatch, + type RefObject, + type SetStateAction, +} from "react"; +import { + createAgentRunController, + shouldCompleteActive, + type AgentRunState, +} from "./agentRunController"; +import type { + AgentCommentStatusMap, + AgentRunPreview, + SendPromptToAgentInput, + TimelineComment, +} from "./types"; + +interface EditingFile { + path: string; + content: string | null; +} + +class AgentBusyError extends Error { + constructor() { + super("Agent is already running. Try again when it finishes."); + this.name = "AgentBusyError"; + } +} + +interface UseTimelineCommentsOptions { + projectId: string | null; + activeCompPath: string | null; + editingPathRef: RefObject; + refreshKey: number; + setEditingFile: Dispatch>; + onProjectContentChanged: () => void; + onNotify?: (message: string, tone?: "error" | "info") => void; +} + +async function fetchTimelineComments(projectId: string): Promise { + const response = await fetch(`/api/projects/${projectId}/timeline-comments`); + if (!response.ok) return []; + const data = (await response.json()) as { comments?: TimelineComment[] }; + return data.comments ?? []; +} + +function parseAgentRunPreview(data: Partial): AgentRunPreview { + return { + ready: data.ready === true, + agent: typeof data.agent === "string" ? data.agent : null, + path: typeof data.path === "string" ? data.path : null, + reason: typeof data.reason === "string" ? data.reason : undefined, + adapters: Array.isArray(data.adapters) + ? data.adapters + .filter((a): a is { kind: string; detected: boolean } => typeof a?.kind === "string") + .map((a) => ({ kind: a.kind, detected: a.detected === true })) + : undefined, + }; +} + +async function fetchAgentRunPreview(projectId: string): Promise { + const response = await fetch(`/api/projects/${projectId}/agent/preview`); + if (!response.ok) return null; + return parseAgentRunPreview((await response.json()) as Partial); +} + +async function fetchActiveCommentId(projectId: string): Promise { + const response = await fetch(`/api/projects/${projectId}/agent/active-run`); + if (!response.ok) return null; + const data = (await response.json()) as { run?: { commentId?: unknown } | null }; + const commentId = data.run?.commentId; + return typeof commentId === "string" ? commentId : null; +} + +async function createTimelineComment( + projectId: string, + input: SendPromptToAgentInput & { filePath: string }, +): Promise<{ comment?: TimelineComment; content?: string }> { + const targetElement = input.elements[0]; + const response = await fetch(`/api/projects/${projectId}/timeline-comments`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + filePath: input.filePath, + rangeStart: input.rangeStart, + rangeEnd: input.rangeEnd, + prompt: input.prompt, + elements: input.elements, + target: targetElement + ? { + id: targetElement.domId, + selector: targetElement.selector, + selectorIndex: targetElement.selectorIndex, + } + : undefined, + }), + }); + if (!response.ok) { + throw new Error(`Failed to create timeline comment (${response.status})`); + } + return (await response.json()) as { comment?: TimelineComment; content?: string }; +} + +async function streamAgentRun( + projectId: string, + comment: TimelineComment, + signal: AbortSignal, + onEvent?: (rawLine: string) => void, +): Promise { + const response = await fetch(`/api/projects/${projectId}/agent/resolve-comment`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + signal, + body: JSON.stringify({ + commentId: comment.id, + filePath: comment.filePath, + prompt: comment.prompt, + rangeStart: comment.rangeStart, + rangeEnd: comment.rangeEnd, + elements: comment.elements, + }), + }); + if (response.status === 409) { + throw new AgentBusyError(); + } + if (!response.ok || !response.body) { + throw new Error(`Agent request failed (${response.status})`); + } + + const reader = response.body.getReader(); + const decoder = new TextDecoder(); + let buffer = ""; + while (true) { + const { done, value } = await reader.read(); + if (done) break; + buffer += decoder.decode(value, { stream: true }); + const lines = buffer.split("\n"); + buffer = lines.pop() ?? ""; + for (const line of lines) { + if (!line.trim()) continue; + onEvent?.(line); + const message = JSON.parse(line) as { type?: string; error?: string }; + if (message.type === "error" && message.error) { + throw new Error(message.error); + } + } + } +} + +export function useTimelineComments({ + projectId, + activeCompPath, + editingPathRef, + refreshKey, + setEditingFile, + onProjectContentChanged, + onNotify, +}: UseTimelineCommentsOptions) { + const [timelineComments, setTimelineComments] = useState([]); + const [commentsTrayOpen, setCommentsTrayOpen] = useState(false); + const [agentStatus, setAgentStatus] = useState({}); + const [agentRunPreview, setAgentRunPreview] = useState(null); + + const projectIdRef = useRef(projectId); + projectIdRef.current = projectId; + const onProjectContentChangedRef = useRef(onProjectContentChanged); + onProjectContentChangedRef.current = onProjectContentChanged; + + const refreshTimelineComments = useCallback(async () => { + if (!projectId) return; + setTimelineComments(await fetchTimelineComments(projectId)); + }, [projectId]); + + const setStatus = useCallback((commentId: string, state: AgentRunState | null) => { + setAgentStatus((prev) => { + if (state === null) { + if (!(commentId in prev)) return prev; + const next = { ...prev }; + delete next[commentId]; + return next; + } + return { ...prev, [commentId]: state }; + }); + }, []); + + const agentRunPreviewRef = useRef(null); + agentRunPreviewRef.current = agentRunPreview; + + const previewMeta = useCallback( + () => ({ + agent: agentRunPreviewRef.current?.agent ?? null, + path: agentRunPreviewRef.current?.path ?? null, + }), + [], + ); + + const agentRunController = useMemo( + () => + createAgentRunController({ + run: async (comment, signal) => { + const pid = projectIdRef.current; + if (!pid) throw new Error("no project"); + await streamAgentRun(pid, comment, signal, (line) => { + setStatus(comment.id, { status: "running", ...previewMeta(), lastMessage: line }); + }); + }, + onStatus: setStatus, + onSettled: () => { + void refreshTimelineComments(); + onProjectContentChangedRef.current(); + }, + isBlockedError: (err) => err instanceof AgentBusyError, + onBlocked: () => { + onNotify?.("Agent is already running. Try again when it finishes.", "info"); + }, + }), + [onNotify, previewMeta, refreshTimelineComments, setStatus], + ); + + const runAgentForComment = useCallback( + (comment: TimelineComment) => { + const started = agentRunController.run(comment, previewMeta()); + if (!started) { + onNotify?.("Agent is already running. Try again when it finishes.", "info"); + } + }, + [agentRunController, onNotify, previewMeta], + ); + + const cancelAgentRun = useCallback( + (commentId: string) => { + // Owned by this client: aborting the fetch fires the controller's + // .catch, which sets the "Stopped." status. A run reattached from a + // previous page load (see fetchActiveCommentId) has no local fetch, so + // cancel() is a no-op there — we clear its stale status explicitly. + const wasActive = agentRunController.activeId() === commentId; + agentRunController.cancel(commentId); + if (projectId) { + // The server cancels any run matching commentId regardless of which + // client started it, so always POST. + void fetch(`/api/projects/${projectId}/agent/cancel`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ commentId }), + }).catch(() => null); + } + if (!wasActive) { + setStatus(commentId, null); + } + }, + [agentRunController, projectId, setStatus], + ); + + const clearTimelineComment = useCallback( + async (commentId: string) => { + if (!projectId) return; + agentRunController.cancel(commentId); + await fetch(`/api/projects/${projectId}/timeline-comments/${commentId}`, { + method: "DELETE", + }); + setStatus(commentId, null); + await refreshTimelineComments(); + onProjectContentChanged(); + }, + [agentRunController, onProjectContentChanged, projectId, refreshTimelineComments, setStatus], + ); + + const setPreferredAdapter = useCallback( + async (adapterKind: string) => { + if (!projectId) return; + try { + const response = await fetch(`/api/projects/${projectId}/agent/adapter`, { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ adapter: adapterKind }), + }); + if (!response.ok) { + onNotify?.(`Failed to set agent (${response.status})`, "error"); + return; + } + const data = (await response.json()) as Partial; + setAgentRunPreview(parseAgentRunPreview(data)); + } catch (err) { + onNotify?.(err instanceof Error ? err.message : String(err), "error"); + } + }, + [onNotify, projectId], + ); + + const copyTimelineCommentPrompt = useCallback(async (comment: TimelineComment) => { + try { + await navigator.clipboard.writeText(comment.prompt); + } catch { + const ta = document.createElement("textarea"); + ta.value = comment.prompt; + document.body.appendChild(ta); + ta.select(); + document.execCommand("copy"); + document.body.removeChild(ta); + } + }, []); + + const sendPromptToAgent = useCallback( + async (input: SendPromptToAgentInput) => { + if (!projectId) return; + const targetElement = input.elements[0]; + const filePath = targetElement?.sourceFile || activeCompPath || "index.html"; + const data = await createTimelineComment(projectId, { ...input, filePath }); + if (data.content && editingPathRef.current === filePath) { + setEditingFile({ path: filePath, content: data.content }); + } + await refreshTimelineComments(); + onProjectContentChanged(); + setCommentsTrayOpen(true); + if (data.comment) { + const started = agentRunController.run(data.comment, previewMeta()); + if (!started) { + onNotify?.("Agent is already running. Run this comment when it finishes.", "info"); + } + } + }, + [ + agentRunController, + activeCompPath, + editingPathRef, + onNotify, + onProjectContentChanged, + previewMeta, + projectId, + refreshTimelineComments, + setEditingFile, + ], + ); + + useEffect(() => { + void refreshTimelineComments(); + }, [refreshKey, refreshTimelineComments]); + + useEffect(() => { + if (!projectId) { + setAgentRunPreview(null); + return; + } + let cancelled = false; + void fetchAgentRunPreview(projectId) + .then((preview) => { + if (!cancelled) setAgentRunPreview(preview); + }) + .catch(() => { + if (!cancelled) setAgentRunPreview(null); + }); + return () => { + cancelled = true; + }; + }, [projectId]); + + useEffect(() => { + if (!projectId) return; + let cancelled = false; + void fetchActiveCommentId(projectId).then((commentId) => { + if (!cancelled && commentId) { + setStatus(commentId, { status: "running", ...previewMeta() }); + } + }); + return () => { + cancelled = true; + }; + }, [previewMeta, projectId, setStatus]); + + useEffect(() => () => agentRunController.abortAll(), [agentRunController]); + + // The agent is instructed to remove the comment marker when its work is done. + // If the stream itself doesn't terminate (some agents keep their session + // alive after completing the task), the marker-gone state is our authoritative + // completion signal — complete() aborts the open fetch to release the active + // slot while reporting the run as "Done." rather than "Stopped." + useEffect(() => { + const active = agentRunController.activeId(); + if (shouldCompleteActive(active, timelineComments)) { + agentRunController.complete(active!); + } + }, [agentRunController, timelineComments]); + + return { + comments: timelineComments, + trayProps: { + comments: timelineComments, + open: commentsTrayOpen, + status: agentStatus, + onToggleOpen: () => setCommentsTrayOpen((open) => !open), + onClose: () => setCommentsTrayOpen(false), + onRunAgent: runAgentForComment, + onCopyPrompt: copyTimelineCommentPrompt, + onClear: clearTimelineComment, + onCancelAgentRun: cancelAgentRun, + }, + sendPromptToAgent, + agentRunPreview, + setPreferredAdapter, + }; +}