diff --git a/chat-ui/bun.lock b/chat-ui/bun.lock index 1eacc3c..bf54d30 100644 --- a/chat-ui/bun.lock +++ b/chat-ui/bun.lock @@ -29,6 +29,7 @@ "@vitejs/plugin-react": "^4.4.0", "typescript": "^5.7.0", "vite": "^6.3.0", + "vitest": "^2.1.0", }, }, }, @@ -375,18 +376,38 @@ "@vitejs/plugin-react": ["@vitejs/plugin-react@4.7.0", "", { "dependencies": { "@babel/core": "^7.28.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.27", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA=="], + "@vitest/expect": ["@vitest/expect@2.1.9", "", { "dependencies": { "@vitest/spy": "2.1.9", "@vitest/utils": "2.1.9", "chai": "^5.1.2", "tinyrainbow": "^1.2.0" } }, "sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw=="], + + "@vitest/mocker": ["@vitest/mocker@2.1.9", "", { "dependencies": { "@vitest/spy": "2.1.9", "estree-walker": "^3.0.3", "magic-string": "^0.30.12" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^5.0.0" }, "optionalPeers": ["msw", "vite"] }, "sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg=="], + + "@vitest/pretty-format": ["@vitest/pretty-format@2.1.9", "", { "dependencies": { "tinyrainbow": "^1.2.0" } }, "sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ=="], + + "@vitest/runner": ["@vitest/runner@2.1.9", "", { "dependencies": { "@vitest/utils": "2.1.9", "pathe": "^1.1.2" } }, "sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g=="], + + "@vitest/snapshot": ["@vitest/snapshot@2.1.9", "", { "dependencies": { "@vitest/pretty-format": "2.1.9", "magic-string": "^0.30.12", "pathe": "^1.1.2" } }, "sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ=="], + + "@vitest/spy": ["@vitest/spy@2.1.9", "", { "dependencies": { "tinyspy": "^3.0.2" } }, "sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ=="], + + "@vitest/utils": ["@vitest/utils@2.1.9", "", { "dependencies": { "@vitest/pretty-format": "2.1.9", "loupe": "^3.1.2", "tinyrainbow": "^1.2.0" } }, "sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ=="], + "aria-hidden": ["aria-hidden@1.2.6", "", { "dependencies": { "tslib": "^2.0.0" } }, "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA=="], + "assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="], + "bail": ["bail@2.0.2", "", {}, "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="], "baseline-browser-mapping": ["baseline-browser-mapping@2.10.19", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-qCkNLi2sfBOn8XhZQ0FXsT1Ki/Yo5P90hrkRamVFRS7/KV9hpfA4HkoWNU152+8w0zPjnxo5psx5NL3PSGgv5g=="], "browserslist": ["browserslist@4.28.2", "", { "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", "electron-to-chromium": "^1.5.328", "node-releases": "^2.0.36", "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg=="], + "cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="], + "caniuse-lite": ["caniuse-lite@1.0.30001788", "", {}, "sha512-6q8HFp+lOQtcf7wBK+uEenxymVWkGKkjFpCvw5W25cmMwEDU45p1xQFBQv8JDlMMry7eNxyBaR+qxgmTUZkIRQ=="], "ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="], + "chai": ["chai@5.3.3", "", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw=="], + "character-entities": ["character-entities@2.0.2", "", {}, "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ=="], "character-entities-html4": ["character-entities-html4@2.1.0", "", {}, "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA=="], @@ -395,6 +416,8 @@ "character-reference-invalid": ["character-reference-invalid@2.0.1", "", {}, "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw=="], + "check-error": ["check-error@2.1.3", "", {}, "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA=="], + "class-variance-authority": ["class-variance-authority@0.7.1", "", { "dependencies": { "clsx": "^2.1.1" } }, "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg=="], "clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="], @@ -413,6 +436,8 @@ "decode-named-character-reference": ["decode-named-character-reference@1.3.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q=="], + "deep-eql": ["deep-eql@5.0.2", "", {}, "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q=="], + "dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="], "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], @@ -425,6 +450,8 @@ "enhanced-resolve": ["enhanced-resolve@5.20.1", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.0" } }, "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA=="], + "es-module-lexer": ["es-module-lexer@1.7.0", "", {}, "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="], + "esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="], "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], @@ -433,6 +460,10 @@ "estree-util-is-identifier-name": ["estree-util-is-identifier-name@3.0.0", "", {}, "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg=="], + "estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], + + "expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="], + "extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="], "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], @@ -499,6 +530,8 @@ "longest-streak": ["longest-streak@3.1.0", "", {}, "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g=="], + "loupe": ["loupe@3.2.1", "", {}, "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ=="], + "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], "lucide-react": ["lucide-react@1.8.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-WuvlsjngSk7TnTBJ1hsCy3ql9V9VOdcPkd3PKcSmM34vJD8KG6molxz7m7zbYFgICwsanQWmJ13JlYs4Zp7Arw=="], @@ -603,6 +636,10 @@ "parse-entities": ["parse-entities@4.0.2", "", { "dependencies": { "@types/unist": "^2.0.0", "character-entities-legacy": "^3.0.0", "character-reference-invalid": "^2.0.0", "decode-named-character-reference": "^1.0.0", "is-alphanumerical": "^2.0.0", "is-decimal": "^2.0.0", "is-hexadecimal": "^2.0.0" } }, "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw=="], + "pathe": ["pathe@1.1.2", "", {}, "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ=="], + + "pathval": ["pathval@2.0.1", "", {}, "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ=="], + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], "picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], @@ -649,12 +686,18 @@ "set-cookie-parser": ["set-cookie-parser@2.7.2", "", {}, "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw=="], + "siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="], + "sonner": ["sonner@2.0.7", "", { "peerDependencies": { "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w=="], "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], "space-separated-tokens": ["space-separated-tokens@2.0.2", "", {}, "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q=="], + "stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="], + + "std-env": ["std-env@3.10.0", "", {}, "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg=="], + "stringify-entities": ["stringify-entities@4.0.4", "", { "dependencies": { "character-entities-html4": "^2.0.0", "character-entities-legacy": "^3.0.0" } }, "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg=="], "style-to-js": ["style-to-js@1.1.21", "", { "dependencies": { "style-to-object": "1.0.14" } }, "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ=="], @@ -667,8 +710,18 @@ "tapable": ["tapable@2.3.2", "", {}, "sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA=="], + "tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="], + + "tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="], + "tinyglobby": ["tinyglobby@0.2.16", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg=="], + "tinypool": ["tinypool@1.1.1", "", {}, "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg=="], + + "tinyrainbow": ["tinyrainbow@1.2.0", "", {}, "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ=="], + + "tinyspy": ["tinyspy@3.0.2", "", {}, "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q=="], + "trim-lines": ["trim-lines@3.0.1", "", {}, "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg=="], "trough": ["trough@2.2.0", "", {}, "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw=="], @@ -705,6 +758,12 @@ "vite": ["vite@6.4.2", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ=="], + "vite-node": ["vite-node@2.1.9", "", { "dependencies": { "cac": "^6.7.14", "debug": "^4.3.7", "es-module-lexer": "^1.5.4", "pathe": "^1.1.2", "vite": "^5.0.0" }, "bin": { "vite-node": "vite-node.mjs" } }, "sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA=="], + + "vitest": ["vitest@2.1.9", "", { "dependencies": { "@vitest/expect": "2.1.9", "@vitest/mocker": "2.1.9", "@vitest/pretty-format": "^2.1.9", "@vitest/runner": "2.1.9", "@vitest/snapshot": "2.1.9", "@vitest/spy": "2.1.9", "@vitest/utils": "2.1.9", "chai": "^5.1.2", "debug": "^4.3.7", "expect-type": "^1.1.0", "magic-string": "^0.30.12", "pathe": "^1.1.2", "std-env": "^3.8.0", "tinybench": "^2.9.0", "tinyexec": "^0.3.1", "tinypool": "^1.0.1", "tinyrainbow": "^1.2.0", "vite": "^5.0.0", "vite-node": "2.1.9", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@types/node": "^18.0.0 || >=20.0.0", "@vitest/browser": "2.1.9", "@vitest/ui": "2.1.9", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@types/node", "@vitest/browser", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q=="], + + "why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="], + "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], "zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="], @@ -722,5 +781,105 @@ "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], "parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="], + + "vite-node/vite": ["vite@5.4.21", "", { "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", "rollup": "^4.20.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || >=20.0.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.4.0" }, "optionalPeers": ["@types/node", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser"], "bin": { "vite": "bin/vite.js" } }, "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw=="], + + "vitest/vite": ["vite@5.4.21", "", { "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", "rollup": "^4.20.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || >=20.0.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.4.0" }, "optionalPeers": ["@types/node", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser"], "bin": { "vite": "bin/vite.js" } }, "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw=="], + + "vite-node/vite/esbuild": ["esbuild@0.21.5", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.21.5", "@esbuild/android-arm": "0.21.5", "@esbuild/android-arm64": "0.21.5", "@esbuild/android-x64": "0.21.5", "@esbuild/darwin-arm64": "0.21.5", "@esbuild/darwin-x64": "0.21.5", "@esbuild/freebsd-arm64": "0.21.5", "@esbuild/freebsd-x64": "0.21.5", "@esbuild/linux-arm": "0.21.5", "@esbuild/linux-arm64": "0.21.5", "@esbuild/linux-ia32": "0.21.5", "@esbuild/linux-loong64": "0.21.5", "@esbuild/linux-mips64el": "0.21.5", "@esbuild/linux-ppc64": "0.21.5", "@esbuild/linux-riscv64": "0.21.5", "@esbuild/linux-s390x": "0.21.5", "@esbuild/linux-x64": "0.21.5", "@esbuild/netbsd-x64": "0.21.5", "@esbuild/openbsd-x64": "0.21.5", "@esbuild/sunos-x64": "0.21.5", "@esbuild/win32-arm64": "0.21.5", "@esbuild/win32-ia32": "0.21.5", "@esbuild/win32-x64": "0.21.5" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw=="], + + "vitest/vite/esbuild": ["esbuild@0.21.5", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.21.5", "@esbuild/android-arm": "0.21.5", "@esbuild/android-arm64": "0.21.5", "@esbuild/android-x64": "0.21.5", "@esbuild/darwin-arm64": "0.21.5", "@esbuild/darwin-x64": "0.21.5", "@esbuild/freebsd-arm64": "0.21.5", "@esbuild/freebsd-x64": "0.21.5", "@esbuild/linux-arm": "0.21.5", "@esbuild/linux-arm64": "0.21.5", "@esbuild/linux-ia32": "0.21.5", "@esbuild/linux-loong64": "0.21.5", "@esbuild/linux-mips64el": "0.21.5", "@esbuild/linux-ppc64": "0.21.5", "@esbuild/linux-riscv64": "0.21.5", "@esbuild/linux-s390x": "0.21.5", "@esbuild/linux-x64": "0.21.5", "@esbuild/netbsd-x64": "0.21.5", "@esbuild/openbsd-x64": "0.21.5", "@esbuild/sunos-x64": "0.21.5", "@esbuild/win32-arm64": "0.21.5", "@esbuild/win32-ia32": "0.21.5", "@esbuild/win32-x64": "0.21.5" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw=="], + + "vite-node/vite/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.21.5", "", { "os": "aix", "cpu": "ppc64" }, "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ=="], + + "vite-node/vite/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.21.5", "", { "os": "android", "cpu": "arm" }, "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg=="], + + "vite-node/vite/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.21.5", "", { "os": "android", "cpu": "arm64" }, "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A=="], + + "vite-node/vite/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.21.5", "", { "os": "android", "cpu": "x64" }, "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA=="], + + "vite-node/vite/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.21.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ=="], + + "vite-node/vite/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.21.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw=="], + + "vite-node/vite/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.21.5", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g=="], + + "vite-node/vite/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.21.5", "", { "os": "freebsd", "cpu": "x64" }, "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ=="], + + "vite-node/vite/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.21.5", "", { "os": "linux", "cpu": "arm" }, "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA=="], + + "vite-node/vite/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.21.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q=="], + + "vite-node/vite/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.21.5", "", { "os": "linux", "cpu": "ia32" }, "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg=="], + + "vite-node/vite/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.21.5", "", { "os": "linux", "cpu": "none" }, "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg=="], + + "vite-node/vite/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.21.5", "", { "os": "linux", "cpu": "none" }, "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg=="], + + "vite-node/vite/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.21.5", "", { "os": "linux", "cpu": "ppc64" }, "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w=="], + + "vite-node/vite/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.21.5", "", { "os": "linux", "cpu": "none" }, "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA=="], + + "vite-node/vite/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.21.5", "", { "os": "linux", "cpu": "s390x" }, "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A=="], + + "vite-node/vite/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.21.5", "", { "os": "linux", "cpu": "x64" }, "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ=="], + + "vite-node/vite/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.21.5", "", { "os": "none", "cpu": "x64" }, "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg=="], + + "vite-node/vite/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.21.5", "", { "os": "openbsd", "cpu": "x64" }, "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow=="], + + "vite-node/vite/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.21.5", "", { "os": "sunos", "cpu": "x64" }, "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg=="], + + "vite-node/vite/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.21.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A=="], + + "vite-node/vite/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.21.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA=="], + + "vite-node/vite/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.21.5", "", { "os": "win32", "cpu": "x64" }, "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw=="], + + "vitest/vite/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.21.5", "", { "os": "aix", "cpu": "ppc64" }, "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ=="], + + "vitest/vite/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.21.5", "", { "os": "android", "cpu": "arm" }, "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg=="], + + "vitest/vite/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.21.5", "", { "os": "android", "cpu": "arm64" }, "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A=="], + + "vitest/vite/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.21.5", "", { "os": "android", "cpu": "x64" }, "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA=="], + + "vitest/vite/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.21.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ=="], + + "vitest/vite/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.21.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw=="], + + "vitest/vite/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.21.5", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g=="], + + "vitest/vite/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.21.5", "", { "os": "freebsd", "cpu": "x64" }, "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ=="], + + "vitest/vite/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.21.5", "", { "os": "linux", "cpu": "arm" }, "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA=="], + + "vitest/vite/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.21.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q=="], + + "vitest/vite/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.21.5", "", { "os": "linux", "cpu": "ia32" }, "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg=="], + + "vitest/vite/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.21.5", "", { "os": "linux", "cpu": "none" }, "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg=="], + + "vitest/vite/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.21.5", "", { "os": "linux", "cpu": "none" }, "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg=="], + + "vitest/vite/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.21.5", "", { "os": "linux", "cpu": "ppc64" }, "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w=="], + + "vitest/vite/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.21.5", "", { "os": "linux", "cpu": "none" }, "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA=="], + + "vitest/vite/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.21.5", "", { "os": "linux", "cpu": "s390x" }, "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A=="], + + "vitest/vite/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.21.5", "", { "os": "linux", "cpu": "x64" }, "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ=="], + + "vitest/vite/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.21.5", "", { "os": "none", "cpu": "x64" }, "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg=="], + + "vitest/vite/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.21.5", "", { "os": "openbsd", "cpu": "x64" }, "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow=="], + + "vitest/vite/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.21.5", "", { "os": "sunos", "cpu": "x64" }, "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg=="], + + "vitest/vite/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.21.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A=="], + + "vitest/vite/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.21.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA=="], + + "vitest/vite/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.21.5", "", { "os": "win32", "cpu": "x64" }, "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw=="], } } diff --git a/chat-ui/package.json b/chat-ui/package.json index 9e83bca..6df69bf 100644 --- a/chat-ui/package.json +++ b/chat-ui/package.json @@ -7,7 +7,8 @@ "dev": "vite", "build": "tsc -b && vite build", "preview": "vite preview", - "typecheck": "tsc -b" + "typecheck": "tsc -b", + "test": "vitest run" }, "dependencies": { "@tailwindcss/vite": "^4.1.0", @@ -33,6 +34,7 @@ "typescript": "^5.7.0", "@types/react": "^19.1.0", "@types/react-dom": "^19.1.0", - "@types/node": "^22.15.0" + "@types/node": "^22.15.0", + "vitest": "^2.1.0" } } diff --git a/chat-ui/src/lib/__tests__/chat-store.test.ts b/chat-ui/src/lib/__tests__/chat-store.test.ts new file mode 100644 index 0000000..efc9904 --- /dev/null +++ b/chat-ui/src/lib/__tests__/chat-store.test.ts @@ -0,0 +1,109 @@ +import { describe, expect, it } from "vitest"; +import { createChatStore, dispatchFrame } from "../chat-store"; + +function send( + store: ReturnType, + event: string, + data: Record, +): void { + dispatchFrame(store, event, JSON.stringify(data)); +} + +describe("chat-store reducer: text block lifecycle", () => { + it("accumulates text_delta into a single content block for one text_start", () => { + const store = createChatStore(); + send(store, "message.assistant_start", { message_id: "a1" }); + send(store, "message.text_start", { + message_id: "a1", + text_block_id: "tb_0_0", + index: 0, + }); + send(store, "message.text_delta", { + text_block_id: "tb_0_0", + delta: "Hello", + }); + send(store, "message.text_delta", { + text_block_id: "tb_0_0", + delta: " world", + }); + send(store, "message.text_end", { text_block_id: "tb_0_0" }); + send(store, "message.assistant_end", { + message_id: "a1", + interrupted: false, + }); + + const state = store.getState(); + const last = state.messages[state.messages.length - 1]; + expect(last?.role).toBe("assistant"); + expect(last?.content.length).toBe(1); + expect(last?.content[0]?.type).toBe("text"); + expect(last?.content[0]?.text).toBe("Hello world"); + expect(last?.content[0]?.blockId).toBe("tb_0_0"); + }); + + it("text_reconcile replaces accumulated delta text instead of appending", () => { + const store = createChatStore(); + send(store, "message.assistant_start", { message_id: "a1" }); + send(store, "message.text_start", { + message_id: "a1", + text_block_id: "tb_0_0", + index: 0, + }); + send(store, "message.text_delta", { + text_block_id: "tb_0_0", + delta: "Hello world", + }); + send(store, "message.text_end", { text_block_id: "tb_0_0" }); + send(store, "message.assistant_end", { + message_id: "a1", + interrupted: false, + }); + send(store, "message.text_reconcile", { + text_block_id: "tb_0_0", + full_text: "Hello world", + }); + + const state = store.getState(); + const last = state.messages[state.messages.length - 1]; + expect(last?.content.length).toBe(1); + expect(last?.content[0]?.text).toBe("Hello world"); + }); + + it("text_reconcile with divergent canonical text snaps the block to the final value", () => { + const store = createChatStore(); + send(store, "message.assistant_start", { message_id: "a1" }); + send(store, "message.text_start", { + message_id: "a1", + text_block_id: "tb_0_0", + index: 0, + }); + send(store, "message.text_delta", { + text_block_id: "tb_0_0", + delta: "Hello wrold", + }); + send(store, "message.text_end", { text_block_id: "tb_0_0" }); + send(store, "message.text_reconcile", { + text_block_id: "tb_0_0", + full_text: "Hello world", + }); + + const state = store.getState(); + const last = state.messages[state.messages.length - 1]; + expect(last?.content.length).toBe(1); + expect(last?.content[0]?.text).toBe("Hello world"); + }); + + it("text_reconcile for a block that was never started is a no-op", () => { + const store = createChatStore(); + send(store, "message.assistant_start", { message_id: "a1" }); + send(store, "message.text_reconcile", { + text_block_id: "tb_0_0", + full_text: "Hello world", + }); + + const state = store.getState(); + const last = state.messages[state.messages.length - 1]; + expect(last?.role).toBe("assistant"); + expect(last?.content.length).toBe(0); + }); +}); diff --git a/chat-ui/tsconfig.app.tsbuildinfo b/chat-ui/tsconfig.app.tsbuildinfo index b044cc9..ff46a89 100644 --- a/chat-ui/tsconfig.app.tsbuildinfo +++ b/chat-ui/tsconfig.app.tsbuildinfo @@ -1 +1 @@ -{"root":["./src/app.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/components/app-shell.tsx","./src/components/assistant-message.tsx","./src/components/attachment-strip.tsx","./src/components/attachment-tile.tsx","./src/components/chat-input-toolbar.tsx","./src/components/chat-input.tsx","./src/components/code-block.tsx","./src/components/command-palette.tsx","./src/components/delete-session-dialog.tsx","./src/components/drop-overlay.tsx","./src/components/empty-state.tsx","./src/components/ios-install-banner.tsx","./src/components/keyboard-help-sheet.tsx","./src/components/markdown.tsx","./src/components/message-actions.tsx","./src/components/message-list.tsx","./src/components/message.tsx","./src/components/notification-banner.tsx","./src/components/sidebar-footer.tsx","./src/components/sidebar-panel.tsx","./src/components/sidebar-session-item.tsx","./src/components/sidebar-session-list.tsx","./src/components/theme-toggle.tsx","./src/components/thinking-block.tsx","./src/components/tool-call-card.tsx","./src/components/user-message.tsx","./src/hooks/use-attachments.ts","./src/hooks/use-auto-scroll.ts","./src/hooks/use-bootstrap.ts","./src/hooks/use-chat.ts","./src/hooks/use-drag-drop.ts","./src/hooks/use-focus-heartbeat.ts","./src/hooks/use-keyboard.ts","./src/hooks/use-mobile.ts","./src/hooks/use-notifications.ts","./src/hooks/use-paste.ts","./src/hooks/use-sessions.ts","./src/hooks/use-theme.ts","./src/lib/chat-dispatch-tools.ts","./src/lib/chat-store.ts","./src/lib/chat-types.ts","./src/lib/client.ts","./src/lib/keymap.ts","./src/lib/utils.ts","./src/routes/chat-route.tsx","./src/routes/new-chat-route.tsx","./src/routes/not-found-route.tsx","./src/routes/session-route.tsx","./src/ui/alert-dialog.tsx","./src/ui/avatar.tsx","./src/ui/badge.tsx","./src/ui/button.tsx","./src/ui/card.tsx","./src/ui/collapsible.tsx","./src/ui/command.tsx","./src/ui/dialog.tsx","./src/ui/dropdown-menu.tsx","./src/ui/input.tsx","./src/ui/label.tsx","./src/ui/popover.tsx","./src/ui/scroll-area.tsx","./src/ui/separator.tsx","./src/ui/sheet.tsx","./src/ui/sidebar.tsx","./src/ui/skeleton.tsx","./src/ui/sonner.tsx","./src/ui/tabs.tsx","./src/ui/textarea.tsx","./src/ui/tooltip.tsx"],"version":"5.9.3"} \ No newline at end of file +{"root":["./src/app.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/components/app-shell.tsx","./src/components/assistant-message.tsx","./src/components/attachment-strip.tsx","./src/components/attachment-tile.tsx","./src/components/chat-input-toolbar.tsx","./src/components/chat-input.tsx","./src/components/code-block.tsx","./src/components/command-palette.tsx","./src/components/delete-session-dialog.tsx","./src/components/drop-overlay.tsx","./src/components/empty-state.tsx","./src/components/ios-install-banner.tsx","./src/components/keyboard-help-sheet.tsx","./src/components/markdown.tsx","./src/components/message-actions.tsx","./src/components/message-list.tsx","./src/components/message.tsx","./src/components/notification-banner.tsx","./src/components/sidebar-footer.tsx","./src/components/sidebar-panel.tsx","./src/components/sidebar-session-item.tsx","./src/components/sidebar-session-list.tsx","./src/components/theme-toggle.tsx","./src/components/thinking-block.tsx","./src/components/tool-call-card.tsx","./src/components/user-message.tsx","./src/hooks/use-attachments.ts","./src/hooks/use-auto-scroll.ts","./src/hooks/use-bootstrap.ts","./src/hooks/use-chat.ts","./src/hooks/use-drag-drop.ts","./src/hooks/use-focus-heartbeat.ts","./src/hooks/use-keyboard.ts","./src/hooks/use-mobile.ts","./src/hooks/use-notifications.ts","./src/hooks/use-paste.ts","./src/hooks/use-sessions.ts","./src/hooks/use-theme.ts","./src/lib/chat-dispatch-tools.ts","./src/lib/chat-store.ts","./src/lib/chat-types.ts","./src/lib/client.ts","./src/lib/keymap.ts","./src/lib/utils.ts","./src/lib/__tests__/chat-store.test.ts","./src/routes/chat-route.tsx","./src/routes/new-chat-route.tsx","./src/routes/not-found-route.tsx","./src/routes/session-route.tsx","./src/ui/alert-dialog.tsx","./src/ui/avatar.tsx","./src/ui/badge.tsx","./src/ui/button.tsx","./src/ui/card.tsx","./src/ui/collapsible.tsx","./src/ui/command.tsx","./src/ui/dialog.tsx","./src/ui/dropdown-menu.tsx","./src/ui/input.tsx","./src/ui/label.tsx","./src/ui/popover.tsx","./src/ui/scroll-area.tsx","./src/ui/separator.tsx","./src/ui/sheet.tsx","./src/ui/sidebar.tsx","./src/ui/skeleton.tsx","./src/ui/sonner.tsx","./src/ui/tabs.tsx","./src/ui/textarea.tsx","./src/ui/tooltip.tsx"],"version":"5.9.3"} \ No newline at end of file diff --git a/chat-ui/tsconfig.node.json b/chat-ui/tsconfig.node.json index b3fc13e..b2205ef 100644 --- a/chat-ui/tsconfig.node.json +++ b/chat-ui/tsconfig.node.json @@ -16,5 +16,5 @@ "noUnusedParameters": true, "noFallthroughCasesInSwitch": true }, - "include": ["vite.config.ts"] + "include": ["vite.config.ts", "vitest.config.ts"] } diff --git a/chat-ui/tsconfig.node.tsbuildinfo b/chat-ui/tsconfig.node.tsbuildinfo index 62c7bf9..9130f00 100644 --- a/chat-ui/tsconfig.node.tsbuildinfo +++ b/chat-ui/tsconfig.node.tsbuildinfo @@ -1 +1 @@ -{"root":["./vite.config.ts"],"version":"5.9.3"} \ No newline at end of file +{"root":["./vite.config.ts","./vitest.config.ts"],"version":"5.9.3"} \ No newline at end of file diff --git a/chat-ui/vitest.config.ts b/chat-ui/vitest.config.ts new file mode 100644 index 0000000..a54ec22 --- /dev/null +++ b/chat-ui/vitest.config.ts @@ -0,0 +1,15 @@ +import path from "node:path"; +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + resolve: { + alias: { + "@": path.resolve(__dirname, "./src"), + }, + }, + test: { + environment: "node", + include: ["src/**/__tests__/**/*.test.ts"], + globals: false, + }, +}); diff --git a/public/dashboard/dashboard.css b/public/dashboard/dashboard.css index 96aab69..37ba134 100644 --- a/public/dashboard/dashboard.css +++ b/public/dashboard/dashboard.css @@ -2890,6 +2890,14 @@ body { padding: var(--space-3); border-bottom: 1px solid var(--color-base-300); } +/* .dash-filter-search was authored for a horizontal .dash-filter-bar. Inside + the Memory rail (flex column) its flex: 1 grows vertically and pushes the + first list row off-screen. Clamp it back to content height here. */ +.dash-split-pane-rail > .dash-filter-search { + flex: 0 0 auto; + max-width: none; + margin-left: 0; +} .dash-memory-list { overflow-y: auto; flex: 1; diff --git a/src/chat/__tests__/sdk-to-wire.test.ts b/src/chat/__tests__/sdk-to-wire.test.ts index 7fd5c1c..4bceb5c 100644 --- a/src/chat/__tests__/sdk-to-wire.test.ts +++ b/src/chat/__tests__/sdk-to-wire.test.ts @@ -430,6 +430,322 @@ describe("sdk-to-wire translator", () => { expect(frames.length).toBe(0); }); + // Regression tests for the v0.20.1 chat duplication fix. The SDK emits stream + // deltas and then re-asserts the final content as a redundant assistant + // message. Pre-fix, handleAssistant re-emitted text_start + text_delta, + // producing two text blocks with the same block id in the client reducer. + // Post-fix, streamed blocks reconcile via a single message.text_reconcile + // frame. + + test("stream_event + assistant combined: final assistant emits a single text_reconcile frame, not text_start/text_delta", () => { + const ctx = makeCtx(); + translateSdkMessage( + { + type: "stream_event", + event: { type: "content_block_start", content_block: { type: "text" }, index: 0 }, + parent_tool_use_id: null, + }, + ctx, + ); + translateSdkMessage( + { + type: "stream_event", + event: { type: "content_block_delta", delta: { type: "text_delta", text: "Hello" }, index: 0 }, + parent_tool_use_id: null, + }, + ctx, + ); + translateSdkMessage( + { + type: "stream_event", + event: { type: "content_block_delta", delta: { type: "text_delta", text: " world" }, index: 0 }, + parent_tool_use_id: null, + }, + ctx, + ); + translateSdkMessage( + { type: "stream_event", event: { type: "content_block_stop", index: 0 }, parent_tool_use_id: null }, + ctx, + ); + translateSdkMessage({ type: "stream_event", event: { type: "message_stop" }, parent_tool_use_id: null }, ctx); + const frames = translateSdkMessage( + { + type: "assistant", + message: { content: [{ type: "text", text: "Hello world" }] }, + parent_tool_use_id: null, + }, + ctx, + ); + expect(frames.length).toBe(1); + expect(frames[0].event).toBe("message.text_reconcile"); + if (frames[0].event === "message.text_reconcile") { + expect(frames[0].text_block_id).toBe("tb_0_0"); + expect(frames[0].full_text).toBe("Hello world"); + } + expect(frames.some((f) => f.event === "message.text_start")).toBe(false); + expect(frames.some((f) => f.event === "message.text_delta")).toBe(false); + }); + + test("stream_event + assistant combined: divergent final text is reconciled to canonical", () => { + const ctx = makeCtx(); + translateSdkMessage( + { + type: "stream_event", + event: { type: "content_block_start", content_block: { type: "text" }, index: 0 }, + parent_tool_use_id: null, + }, + ctx, + ); + translateSdkMessage( + { + type: "stream_event", + event: { type: "content_block_delta", delta: { type: "text_delta", text: "Hello world" }, index: 0 }, + parent_tool_use_id: null, + }, + ctx, + ); + const frames = translateSdkMessage( + { + type: "assistant", + message: { content: [{ type: "text", text: "Hello, world!" }] }, + parent_tool_use_id: null, + }, + ctx, + ); + expect(frames.length).toBe(1); + expect(frames[0].event).toBe("message.text_reconcile"); + if (frames[0].event === "message.text_reconcile") { + expect(frames[0].full_text).toBe("Hello, world!"); + } + }); + + test("assistant-only path (no prior stream_event): original diff emit is preserved", () => { + const ctx = makeCtx(); + const frames = translateSdkMessage( + { + type: "assistant", + message: { content: [{ type: "text", text: "Hello" }] }, + parent_tool_use_id: null, + }, + ctx, + ); + expect(frames.length).toBe(3); + expect(frames[0].event).toBe("message.assistant_start"); + expect(frames[1].event).toBe("message.text_start"); + expect(frames[2].event).toBe("message.text_delta"); + expect(frames.some((f) => f.event === "message.text_reconcile")).toBe(false); + }); + + test("stream_event + assistant combined: thinking final emits start+delta(fullText) which the Map-indirect client reducer replaces idempotently", () => { + const ctx = makeCtx(); + translateSdkMessage( + { + type: "stream_event", + event: { type: "content_block_start", content_block: { type: "thinking" }, index: 0 }, + parent_tool_use_id: null, + }, + ctx, + ); + translateSdkMessage( + { + type: "stream_event", + event: { type: "content_block_delta", delta: { type: "thinking_delta", thinking: "Let me think" }, index: 0 }, + parent_tool_use_id: null, + }, + ctx, + ); + translateSdkMessage( + { type: "stream_event", event: { type: "content_block_stop", index: 0 }, parent_tool_use_id: null }, + ctx, + ); + const frames = translateSdkMessage( + { + type: "assistant", + message: { content: [{ type: "thinking", thinking: "Let me think" }] }, + parent_tool_use_id: null, + }, + ctx, + ); + expect(frames.length).toBe(2); + expect(frames[0].event).toBe("message.thinking_start"); + expect(frames[1].event).toBe("message.thinking_delta"); + if (frames[1].event === "message.thinking_delta") { + expect(frames[1].delta).toBe("Let me think"); + } + }); + + test("stream_event + assistant combined: thinking final snaps to canonical when deltas diverge (stream='abc', final='abcd')", () => { + const ctx = makeCtx(); + translateSdkMessage( + { + type: "stream_event", + event: { type: "content_block_start", content_block: { type: "thinking" }, index: 0 }, + parent_tool_use_id: null, + }, + ctx, + ); + translateSdkMessage( + { + type: "stream_event", + event: { type: "content_block_delta", delta: { type: "thinking_delta", thinking: "abc" }, index: 0 }, + parent_tool_use_id: null, + }, + ctx, + ); + translateSdkMessage( + { type: "stream_event", event: { type: "content_block_stop", index: 0 }, parent_tool_use_id: null }, + ctx, + ); + const frames = translateSdkMessage( + { + type: "assistant", + message: { content: [{ type: "thinking", thinking: "abcd" }] }, + parent_tool_use_id: null, + }, + ctx, + ); + // thinking_start replaces the client's Map entry, thinking_delta fills + // with the canonical "abcd". The "d" missing from the stream is restored. + expect(frames.length).toBe(2); + expect(frames[0].event).toBe("message.thinking_start"); + expect(frames[1].event).toBe("message.thinking_delta"); + if (frames[1].event === "message.thinking_delta") { + expect(frames[1].delta).toBe("abcd"); + } + }); + + test("stream_event + assistant combined: tool_use block still guarded by startedToolIds", () => { + const ctx = makeCtx(); + translateSdkMessage( + { + type: "stream_event", + event: { + type: "content_block_start", + content_block: { type: "tool_use", id: "toolu_abc", name: "Read" }, + index: 0, + }, + parent_tool_use_id: null, + }, + ctx, + ); + const frames = translateSdkMessage( + { + type: "assistant", + message: { + content: [{ type: "tool_use", id: "toolu_abc", name: "Read", input: { file: "/tmp/x" } }], + }, + parent_tool_use_id: null, + }, + ctx, + ); + expect(frames.some((f) => f.event === "message.tool_call_start")).toBe(false); + expect(frames.some((f) => f.event === "message.tool_call_input_end")).toBe(false); + }); + + test("stream_event + assistant combined: interleaved [text, tool_use, text] reconciles each text block independently", () => { + const ctx = makeCtx(); + translateSdkMessage( + { + type: "stream_event", + event: { type: "content_block_start", content_block: { type: "text" }, index: 0 }, + parent_tool_use_id: null, + }, + ctx, + ); + translateSdkMessage( + { + type: "stream_event", + event: { type: "content_block_delta", delta: { type: "text_delta", text: "Part one" }, index: 0 }, + parent_tool_use_id: null, + }, + ctx, + ); + translateSdkMessage( + { type: "stream_event", event: { type: "content_block_stop", index: 0 }, parent_tool_use_id: null }, + ctx, + ); + translateSdkMessage( + { + type: "stream_event", + event: { + type: "content_block_start", + content_block: { type: "tool_use", id: "toolu_x", name: "Read" }, + index: 1, + }, + parent_tool_use_id: null, + }, + ctx, + ); + translateSdkMessage( + { type: "stream_event", event: { type: "content_block_stop", index: 1 }, parent_tool_use_id: null }, + ctx, + ); + translateSdkMessage( + { + type: "stream_event", + event: { type: "content_block_start", content_block: { type: "text" }, index: 2 }, + parent_tool_use_id: null, + }, + ctx, + ); + translateSdkMessage( + { + type: "stream_event", + event: { type: "content_block_delta", delta: { type: "text_delta", text: "Part two" }, index: 2 }, + parent_tool_use_id: null, + }, + ctx, + ); + translateSdkMessage( + { type: "stream_event", event: { type: "content_block_stop", index: 2 }, parent_tool_use_id: null }, + ctx, + ); + const frames = translateSdkMessage( + { + type: "assistant", + message: { + content: [ + { type: "text", text: "Part one" }, + { type: "tool_use", id: "toolu_x", name: "Read", input: { f: 1 } }, + { type: "text", text: "Part two" }, + ], + }, + parent_tool_use_id: null, + }, + ctx, + ); + const reconciles = frames.filter((f) => f.event === "message.text_reconcile"); + expect(reconciles.length).toBe(2); + if (reconciles[0].event === "message.text_reconcile") { + expect(reconciles[0].text_block_id).toBe("tb_0_0"); + expect(reconciles[0].full_text).toBe("Part one"); + } + if (reconciles[1].event === "message.text_reconcile") { + expect(reconciles[1].text_block_id).toBe("tb_0_2"); + expect(reconciles[1].full_text).toBe("Part two"); + } + expect(frames.some((f) => f.event === "message.text_start")).toBe(false); + expect(frames.some((f) => f.event === "message.text_delta")).toBe(false); + expect(frames.some((f) => f.event === "message.tool_call_start")).toBe(false); + }); + + test("assistant-only path: empty text block emits start but no delta", () => { + const ctx = makeCtx(); + const frames = translateSdkMessage( + { + type: "assistant", + message: { content: [{ type: "text", text: "" }] }, + parent_tool_use_id: null, + }, + ctx, + ); + expect(frames.length).toBe(2); + expect(frames[0].event).toBe("message.assistant_start"); + expect(frames[1].event).toBe("message.text_start"); + expect(frames.some((f) => f.event === "message.text_delta")).toBe(false); + expect(frames.some((f) => f.event === "message.text_reconcile")).toBe(false); + }); + test("input_json_delta uses real tool_call_id from content_block_start", () => { const ctx = makeCtx(); translateSdkMessage( diff --git a/src/chat/sdk-to-wire-handlers.ts b/src/chat/sdk-to-wire-handlers.ts index 2989022..2fcf19b 100644 --- a/src/chat/sdk-to-wire-handlers.ts +++ b/src/chat/sdk-to-wire-handlers.ts @@ -39,24 +39,45 @@ export function handleAssistant(msg: Record, ctx: TranslationCo if (blockType === "text") { const fullText = (block.text as string) ?? ""; - const prevLen = ctx.seenBlockLengths.get(i) ?? 0; - if (prevLen === 0) { - frames.push({ - event: "message.text_start", - message_id: ctx.messageId, - text_block_id: `tb_${ctx.turnIndex}_${i}`, - index: i, - }); - } - if (fullText.length > prevLen) { + if (ctx.blockTypes.get(i) === "text") { + // Stream deltas already shipped this block. Emit a single reconcile + // frame so the client replaces the accumulated text with the + // canonical final text. No-op at the UI if they already match. frames.push({ - event: "message.text_delta", + event: "message.text_reconcile", text_block_id: `tb_${ctx.turnIndex}_${i}`, - delta: fullText.slice(prevLen), + full_text: fullText, }); + ctx.seenBlockLengths.set(i, fullText.length); + } else { + const prevLen = ctx.seenBlockLengths.get(i) ?? 0; + if (prevLen === 0) { + frames.push({ + event: "message.text_start", + message_id: ctx.messageId, + text_block_id: `tb_${ctx.turnIndex}_${i}`, + index: i, + }); + } + if (fullText.length > prevLen) { + frames.push({ + event: "message.text_delta", + text_block_id: `tb_${ctx.turnIndex}_${i}`, + delta: fullText.slice(prevLen), + }); + } + ctx.seenBlockLengths.set(i, fullText.length); } - ctx.seenBlockLengths.set(i, fullText.length); } else if (blockType === "thinking" || blockType === "redacted_thinking") { + // Thinking blocks use a Map-indirect client reducer: `thinking_start` + // REPLACES the Map entry, and `thinking_delta` appends to it. That + // makes `thinking_start + thinking_delta(fullText)` idempotent across + // the streamed and non-streamed paths, AND correctly snaps the + // client's Map entry to the canonical text when the stream deltas + // diverged from the final assistant text (e.g. stream="abc", + // final="abcd"). So we always emit on the final pass. The text path + // above uses a dedicated reconcile frame because its reducer is + // array-of-blocks and that idempotence guarantee does not hold there. const thinkingText = (block.thinking as string) ?? ""; const prevLen = ctx.seenBlockLengths.get(i) ?? 0; const redacted = blockType === "redacted_thinking";