From ddd6ad43f01839bd6de2e3dd2d1dc1042714a7c4 Mon Sep 17 00:00:00 2001 From: Harry Yep Date: Wed, 24 Jun 2026 11:40:36 +0800 Subject: [PATCH 01/10] chore: upgrade @assistant-ui/react 0.12 to 0.14 and react-streamdown 0.1 to 0.3 bumps @assistant-ui/react from ^0.12.28 to ^0.14.23 and @assistant-ui/react-streamdown from ^0.1.11 to ^0.3.4. this crosses two minor bumps on each package and unlocks the built-in defer, smooth, and tail-bounded remend primitives for PR 2. breaking change from core 0.2.x: MessageRepository.appendOptimisticMessage was removed (assistant-ui#4162). inline the three steps it did (generateId + fromThreadMessageLike + addOrUpdateMessage) in incremental-external-store-runtime.ts, and set metadata.isOptimistic so the new off-branch eviction logic cleans up the placeholder correctly. fromThreadMessageLike and generateId graduated to the public API in 0.14.22 (assistant-ui#4414), so they now import from @assistant-ui/react instead of @assistant-ui/core/internal. ExportedMessageRepository in the test file moves to the public import for the same reason. the remaining internal imports (AssistantRuntimeImpl, BaseAssistantRuntimeCore, ExternalStoreThreadListRuntimeCore, ExternalStoreThreadRuntimeCore, hasUpcomingMessage) are runtime construction internals with no public equivalent and stay on @assistant-ui/core/internal. the @assistant-ui/store npm override is removed: all transitive ranges now resolve to 0.2.18 without it. verified: tsc --noEmit passes, vitest shows zero new failures (15 pre-existing, 792 passing, identical to baseline before the upgrade). --- apps/desktop/package.json | 4 +- .../thread/user-message-edit.test.tsx | 2 +- .../lib/incremental-external-store-runtime.ts | 16 +- package-lock.json | 846 ++++-------------- package.json | 1 - 5 files changed, 167 insertions(+), 702 deletions(-) diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 0a4fd7f4e9cc..556a05b6d897 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -50,8 +50,8 @@ "check": "npm run typecheck && npm run test && npm run test:desktop:all && npm run build" }, "dependencies": { - "@assistant-ui/react": "^0.12.28", - "@assistant-ui/react-streamdown": "^0.1.11", + "@assistant-ui/react": "^0.14.23", + "@assistant-ui/react-streamdown": "^0.3.4", "@audiowave/react": "^0.6.2", "@chenglou/pretext": "^0.0.6", "@codemirror/commands": "^6.10.4", diff --git a/apps/desktop/src/components/assistant-ui/thread/user-message-edit.test.tsx b/apps/desktop/src/components/assistant-ui/thread/user-message-edit.test.tsx index 3d1e7a69b809..f81bd08ec57f 100644 --- a/apps/desktop/src/components/assistant-ui/thread/user-message-edit.test.tsx +++ b/apps/desktop/src/components/assistant-ui/thread/user-message-edit.test.tsx @@ -1,4 +1,4 @@ -import { ExportedMessageRepository } from '@assistant-ui/core/internal' +import { ExportedMessageRepository } from '@assistant-ui/react' // Clicking a user bubble must open the inline edit composer — through the // app's incremental external-store runtime (which reimplements capability // resolution, incl. `edit: onEdit !== undefined`) and the stock runtime. diff --git a/apps/desktop/src/lib/incremental-external-store-runtime.ts b/apps/desktop/src/lib/incremental-external-store-runtime.ts index c055175091dd..e8d2033c4dbd 100644 --- a/apps/desktop/src/lib/incremental-external-store-runtime.ts +++ b/apps/desktop/src/lib/incremental-external-store-runtime.ts @@ -8,6 +8,8 @@ import { import { type AssistantRuntime, type ExternalStoreAdapter, + fromThreadMessageLike, + generateId, type ThreadMessage, useRuntimeAdapters } from '@assistant-ui/react' @@ -135,10 +137,16 @@ class IncrementalExternalStoreThreadRuntimeCore extends ExternalStoreThreadRunti } if (hasUpcomingMessage(isRunning, messages)) { - self._assistantOptimisticId = this.repository.appendOptimisticMessage(messages.at(-1)?.id ?? null, { - role: 'assistant', - content: [] - }) + const optimisticId = generateId() + this.repository.addOrUpdateMessage( + messages.at(-1)?.id ?? null, + fromThreadMessageLike( + { role: 'assistant', content: [], metadata: { isOptimistic: true } }, + optimisticId, + { type: 'running' } + ) + ) + self._assistantOptimisticId = optimisticId } this.repository.resetHead(self._assistantOptimisticId ?? messages.at(-1)?.id ?? null) diff --git a/package-lock.json b/package-lock.json index 92b7699b03c2..da998b92864e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -61,8 +61,8 @@ "name": "hermes", "version": "0.17.0", "dependencies": { - "@assistant-ui/react": "^0.12.28", - "@assistant-ui/react-streamdown": "^0.1.11", + "@assistant-ui/react": "^0.14.23", + "@assistant-ui/react-streamdown": "^0.3.4", "@audiowave/react": "^0.6.2", "@chenglou/pretext": "^0.0.6", "@codemirror/commands": "^6.10.4", @@ -167,6 +167,150 @@ "node": "^20.19.0 || >=22.12.0" } }, + "apps/desktop/node_modules/@assistant-ui/core": { + "version": "0.2.19", + "resolved": "https://registry.npmjs.org/@assistant-ui/core/-/core-0.2.19.tgz", + "integrity": "sha512-QYwIy+l21rvVsyuc19doblCJ3bfhBzqbuNa/xdts8CIDlw+5LWg1uVGCiVhTRpkysHuJyONDF4TVBAxdMXR7yw==", + "license": "MIT", + "dependencies": { + "assistant-stream": "^0.3.24", + "nanoid": "^5.1.15" + }, + "peerDependencies": { + "@assistant-ui/store": "^0.2.13", + "@assistant-ui/tap": "^0.9.0", + "@types/react": "*", + "assistant-cloud": "^0.1.31", + "react": "^18 || ^19", + "zustand": "^5.0.11" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "assistant-cloud": { + "optional": true + }, + "react": { + "optional": true + }, + "zustand": { + "optional": true + } + } + }, + "apps/desktop/node_modules/@assistant-ui/react": { + "version": "0.14.24", + "resolved": "https://registry.npmjs.org/@assistant-ui/react/-/react-0.14.24.tgz", + "integrity": "sha512-DHUEbJfn3EeApiLXJp6pZfwzGUwQ7aAoGeUfs8bmo1G9uAkRlxF1RKr7MwM/oVSUt+ixSsfWey+OuHJi5gtKCQ==", + "license": "MIT", + "dependencies": { + "@assistant-ui/core": "^0.2.19", + "@assistant-ui/store": "^0.2.19", + "@assistant-ui/tap": "^0.9.3", + "@radix-ui/primitive": "^1.1.4", + "@radix-ui/react-compose-refs": "^1.1.3", + "@radix-ui/react-context": "^1.1.4", + "@radix-ui/react-primitive": "^2.1.6", + "@radix-ui/react-use-callback-ref": "^1.1.2", + "@radix-ui/react-use-escape-keydown": "^1.1.2", + "assistant-cloud": "^0.1.34", + "assistant-stream": "^0.3.24", + "nanoid": "^5.1.15", + "radix-ui": "^1.6.0", + "react-textarea-autosize": "^8.5.9", + "safe-content-frame": "^0.0.21", + "zod": "^4.4.3", + "zustand": "^5.0.14" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^18 || ^19", + "react-dom": "^18 || ^19" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "apps/desktop/node_modules/@assistant-ui/react-streamdown": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@assistant-ui/react-streamdown/-/react-streamdown-0.3.5.tgz", + "integrity": "sha512-xl4UoFGtCfeUk6T/F9d94djE4Vc2KPQ8I3vy7cV0fb8IGN63yHt/rVzehxMSX4XT81Qrpsa0KUQhJOo/8zxdkw==", + "license": "MIT", + "dependencies": { + "rehype-harden": "^1.1.8", + "rehype-raw": "^7.0.0", + "rehype-sanitize": "^6.0.0", + "remend": "^1.3.0", + "streamdown": "^2.5.0" + }, + "peerDependencies": { + "@assistant-ui/react": "^0.14.18", + "@streamdown/cjk": "^1.0.0", + "@streamdown/code": "^1.0.0", + "@streamdown/math": "^1.0.0", + "@streamdown/mermaid": "^1.0.0", + "@types/react": "*", + "react": "^18 || ^19" + }, + "peerDependenciesMeta": { + "@streamdown/cjk": { + "optional": true + }, + "@streamdown/code": { + "optional": true + }, + "@streamdown/math": { + "optional": true + }, + "@streamdown/mermaid": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "apps/desktop/node_modules/@assistant-ui/store": { + "version": "0.2.19", + "resolved": "https://registry.npmjs.org/@assistant-ui/store/-/store-0.2.19.tgz", + "integrity": "sha512-7GMEoK+H4iINquteLFXqqDB5SAzB19YHBy4KKQprxHseAtDrpkC8w9pTrPfJ1yLvbn3FqjXk645bCt5vDf1sTg==", + "license": "MIT", + "dependencies": { + "use-effect-event": "^2.0.3" + }, + "peerDependencies": { + "@assistant-ui/tap": "^0.9.0", + "@types/react": "*", + "react": "^18 || ^19" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "apps/desktop/node_modules/@assistant-ui/tap": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/@assistant-ui/tap/-/tap-0.9.3.tgz", + "integrity": "sha512-IKIgDbaKUPvVt2hMKL+WU2E8oru5J3muO9iSjL/vEVf6TNz5H07wrOKwD+1xhitqR1Nc4WtHK986jqoeGmWRDw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^18 || ^19" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "apps/desktop/node_modules/@nous-research/ui": { "version": "0.13.2", "resolved": "https://registry.npmjs.org/@nous-research/ui/-/ui-0.13.2.tgz", @@ -311,151 +455,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@assistant-ui/core": { - "version": "0.1.17", - "resolved": "https://registry.npmjs.org/@assistant-ui/core/-/core-0.1.17.tgz", - "integrity": "sha512-IWIP98UVQ9W+oF0yz8XqFRtaX8HtozWVUWt6D/BSV6cyKwLfJ8niHtLG74bSnllTnGcreU2El3GR/tIodR1XuA==", - "license": "MIT", - "dependencies": { - "assistant-stream": "^0.3.12", - "nanoid": "^5.1.9" - }, - "peerDependencies": { - "@assistant-ui/store": "^0.2.9", - "@assistant-ui/tap": "^0.5.10", - "@types/react": "*", - "assistant-cloud": "^0.1.27", - "react": "^18 || ^19", - "zustand": "^5.0.11" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "assistant-cloud": { - "optional": true - }, - "react": { - "optional": true - }, - "zustand": { - "optional": true - } - } - }, - "node_modules/@assistant-ui/react": { - "version": "0.12.28", - "resolved": "https://registry.npmjs.org/@assistant-ui/react/-/react-0.12.28.tgz", - "integrity": "sha512-czjpexLK1lKnNDNM1YMJi8SufeKUWBICqiVUtiHMV+86PYGCwJykOZKkchI8MVbSQ62xZ8A1LfPO5W2IDjed3A==", - "license": "MIT", - "dependencies": { - "@assistant-ui/core": "^0.1.17", - "@assistant-ui/store": "^0.2.9", - "@assistant-ui/tap": "^0.5.10", - "@radix-ui/primitive": "^1.1.3", - "@radix-ui/react-compose-refs": "^1.1.2", - "@radix-ui/react-context": "^1.1.3", - "@radix-ui/react-primitive": "^2.1.4", - "@radix-ui/react-use-callback-ref": "^1.1.1", - "@radix-ui/react-use-escape-keydown": "^1.1.1", - "assistant-cloud": "^0.1.27", - "assistant-stream": "^0.3.12", - "nanoid": "^5.1.9", - "radix-ui": "^1.4.3", - "react-textarea-autosize": "^8.5.9", - "zod": "^4.3.6", - "zustand": "^5.0.12" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^18 || ^19", - "react-dom": "^18 || ^19" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@assistant-ui/react-streamdown": { - "version": "0.1.11", - "resolved": "https://registry.npmjs.org/@assistant-ui/react-streamdown/-/react-streamdown-0.1.11.tgz", - "integrity": "sha512-9y+89ZxotYSt81hChSVjK2kwUYRKq7UW/r5qoqZTpcb7119gc0NOj0dx9xxuyXE2QfR6EY8rW6yBz3g+Y7RrhQ==", - "license": "MIT", - "dependencies": { - "rehype-harden": "^1.1.8", - "rehype-raw": "^7.0.0", - "rehype-sanitize": "^6.0.0", - "streamdown": "^2.5.0" - }, - "peerDependencies": { - "@assistant-ui/react": "^0.12.26", - "@streamdown/cjk": "^1.0.0", - "@streamdown/code": "^1.0.0", - "@streamdown/math": "^1.0.0", - "@streamdown/mermaid": "^1.0.0", - "@types/react": "*", - "react": "^18 || ^19" - }, - "peerDependenciesMeta": { - "@streamdown/cjk": { - "optional": true - }, - "@streamdown/code": { - "optional": true - }, - "@streamdown/math": { - "optional": true - }, - "@streamdown/mermaid": { - "optional": true - }, - "@types/react": { - "optional": true - } - } - }, - "node_modules/@assistant-ui/store": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/@assistant-ui/store/-/store-0.2.13.tgz", - "integrity": "sha512-7NL6HWMBxe1ndLWO4kHkjQ0Syyc0D/Aj+zxdpcy4yrplG71X04CzFimMBBSQAk+AnGBf+d96D7cuUZdjHkTavg==", - "license": "MIT", - "dependencies": { - "use-effect-event": "^2.0.3" - }, - "peerDependencies": { - "@assistant-ui/tap": "^0.5.14", - "@types/react": "*", - "react": "^18 || ^19" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@assistant-ui/tap": { - "version": "0.5.16", - "resolved": "https://registry.npmjs.org/@assistant-ui/tap/-/tap-0.5.16.tgz", - "integrity": "sha512-6f3RxJdE+5NCndmf8i8SJYq7C5qzrH4olyOw3Nzer7pLy4uB6ZYkV2fi2UR7W44NIxfg7ur9UCT56krjZKXrSw==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^18 || ^19" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "react": { - "optional": true - } - } - }, "node_modules/@audiowave/core": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/@audiowave/core/-/core-0.3.1.tgz", @@ -1787,72 +1786,6 @@ "node": ">= 10.0.0" } }, - "node_modules/@electron/windows-sign": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@electron/windows-sign/-/windows-sign-1.2.2.tgz", - "integrity": "sha512-dfZeox66AvdPtb2lD8OsIIQh12Tp0GNCRUDfBHIKGpbmopZto2/A8nSpYYLoedPIHpqkeblZ/k8OV0Gy7PYuyQ==", - "dev": true, - "license": "BSD-2-Clause", - "optional": true, - "peer": true, - "dependencies": { - "cross-dirname": "^0.1.0", - "debug": "^4.3.4", - "fs-extra": "^11.1.1", - "minimist": "^1.2.8", - "postject": "^1.0.0-alpha.6" - }, - "bin": { - "electron-windows-sign": "bin/electron-windows-sign.js" - }, - "engines": { - "node": ">=14.14" - } - }, - "node_modules/@electron/windows-sign/node_modules/fs-extra": { - "version": "11.3.5", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.5.tgz", - "integrity": "sha512-eKpRKAovdpZtR1WopLHxlBWvAgPny3c4gX1G5Jhwmmw4XJj0ifSD5qB5TOo8hmA0wlRKDAOAhEE1yVPgs6Fgcg==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=14.14" - } - }, - "node_modules/@electron/windows-sign/node_modules/jsonfile": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", - "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/@electron/windows-sign/node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">= 10.0.0" - } - }, "node_modules/@emnapi/core": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", @@ -1891,448 +1824,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", - "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", - "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", - "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", - "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", - "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", - "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", - "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", - "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", - "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", - "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", - "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", - "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", - "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", - "cpu": [ - "mips64el" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", - "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", - "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", - "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", - "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", - "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", - "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", - "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", - "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", - "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", - "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", - "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", - "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", - "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, "node_modules/@eslint-community/eslint-utils": { "version": "4.9.1", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", @@ -8439,15 +7930,6 @@ "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==", "license": "MIT" }, - "node_modules/cross-dirname": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/cross-dirname/-/cross-dirname-0.1.0.tgz", - "integrity": "sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true - }, "node_modules/cross-env": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-10.1.0.tgz", @@ -15581,36 +15063,6 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/postject": { - "version": "1.0.0-alpha.6", - "resolved": "https://registry.npmjs.org/postject/-/postject-1.0.0-alpha.6.tgz", - "integrity": "sha512-b9Eb8h2eVqNE8edvKdwqkrY6O7kAwmI8kcnBv1NScolYJbo59XUF0noFq+lxbC1yN20bmC0WBEbDC5H/7ASb0A==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "commander": "^9.4.0" - }, - "bin": { - "postject": "dist/cli.js" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/postject/node_modules/commander": { - "version": "9.5.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", - "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": "^12.20.0 || >=14" - } - }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -16927,6 +16379,12 @@ "dev": true, "license": "MIT" }, + "node_modules/safe-content-frame": { + "version": "0.0.21", + "resolved": "https://registry.npmjs.org/safe-content-frame/-/safe-content-frame-0.0.21.tgz", + "integrity": "sha512-4LgYcX0lESOg9zVi6QCcqHaNGjZuc86w0IGRJij7lA4YG/6QHBblL2Jwh5OJBtkVSnhDOeARRpD3jvFNGiIWiw==", + "license": "MIT" + }, "node_modules/safe-push-apply": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", diff --git a/package.json b/package.json index 5eb1e6b63714..fb78d87411e3 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,6 @@ }, "overrides": { "lodash": "4.18.1", - "@assistant-ui/store": "0.2.13", "yauzl": "^3.3.1" }, "engines": { From 6c95740d9eaed10d214d4a35c2960ee6a08c066a Mon Sep 17 00:00:00 2001 From: Harry Yep Date: Wed, 24 Jun 2026 11:57:48 +0800 Subject: [PATCH 02/10] feat: replace custom streaming wrappers with built-in defer and smooth props MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit delete SmoothStreamingText, DeferStreamingText, and useSmoothReveal (~174 lines) from markdown-text.tsx. the built-in defer and smooth props on StreamdownTextPrimitive now handle the same work: - defer: routes streaming text through useDeferredValue so markdown re-parsing runs at lower priority (typing/scrolling stay responsive) - smooth: typewriter-style reveal via useSmooth with SmoothOptions { drainMs: 500, maxCharsPerFrame: 30, minCommitMs: 33 }, matching the old useSmoothReveal constants exactly MarkdownTextContent (reasoning text) gets both defer and smooth. MarkdownText (assistant text) gets defer only, matching the previous behavior where text messages had no typewriter effect. the internal pipeline order changes from smooth → defer → preprocess to preprocess → smooth → defer (the built-in primitive runs preprocess first). this is functionally equivalent: the tail-bounded remend repair runs once on the full text instead of per revealed prefix, and the smooth reveal operates on already-repaired markdown. end result is identical. verified: tsc 0 errors, eslint clean, vitest 0 new failures (15 pre-existing, 792 passing), manual verification of 6 streaming scenarios (defer, smooth reveal, typing-while-streaming, code blocks, math, long text performance). --- .../components/assistant-ui/markdown-text.tsx | 85 +++++-------------- 1 file changed, 20 insertions(+), 65 deletions(-) diff --git a/apps/desktop/src/components/assistant-ui/markdown-text.tsx b/apps/desktop/src/components/assistant-ui/markdown-text.tsx index 694a94e78c75..841e91fe4c4b 100644 --- a/apps/desktop/src/components/assistant-ui/markdown-text.tsx +++ b/apps/desktop/src/components/assistant-ui/markdown-text.tsx @@ -1,6 +1,6 @@ 'use client' -import { TextMessagePartProvider, useMessagePartText } from '@assistant-ui/react' +import { type SmoothOptions, TextMessagePartProvider, useMessagePartText } from '@assistant-ui/react' import { parseMarkdownIntoBlocks, type StreamdownTextComponents, @@ -8,15 +8,7 @@ import { type SyntaxHighlighterProps } from '@assistant-ui/react-streamdown' import { code } from '@streamdown/code' -import { - type ComponentProps, - memo, - type ReactNode, - useDeferredValue, - useEffect, - useMemo, - useState -} from 'react' +import { type ComponentProps, memo, useEffect, useMemo, useState } from 'react' import { ExpandableBlock } from '@/components/chat/expandable-block' import { PreviewAttachment } from '@/components/chat/preview-attachment' @@ -71,7 +63,7 @@ function preprocessWithTailRepair(text: string): string { // `marked` lex of the entire message, ~1.6ms per 28KB) inside a useMemo keyed // on the text — but the same text is re-lexed every time a message REMOUNTS // (virtualizer scroll, session switch) and whenever multiple surfaces render -// the same content (deferred + smooth reveal republish). A small module-level +// the same content (remount, session switch). A small module-level // LRU keyed by the exact source string removes all of those repeat parses // with zero correctness risk (same input → same output). Streaming tail // growth misses the cache by design (every flush is a new string) — that @@ -347,44 +339,11 @@ function MarkdownImage({ className, src, alt, ...props }: ComponentProps<'img'>) ) } -/** - * Re-publish the active message-part context with React's `useDeferredValue` - * applied to the streaming text and status. The outer wrapper still re-renders - * on every token, but the work it does is trivial (one hook, one provider). - * - * The expensive subtree (Streamdown → micromark → mdast → hast → React) lives - * inside `` and reads the deferred text via the - * normal `useMessagePartText` hook. React's concurrent scheduler then has - * permission to: - * - skip intermediate token states when the next token arrives mid-render - * (it abandons the in-flight deferred render and starts over) - * - deprioritize the markdown render when the main thread is busy with an - * urgent task (typing, scrolling, layout work elsewhere) - * - * Net effect: per-token CPU is unchanged but the *blocking* part of that work - * goes away — typing-while-streaming stays a single-frame paint, scroll - * stutter disappears, and the longtask histogram tightens because long - * commits can be interrupted and discarded. - * - * Industry standard (Streamdown's own block-array setState already uses - * `useTransition`); this just lifts the deferral up to the consumer text - * boundary so it covers the whole pipeline, not just the inner setState. - */ -function DeferStreamingText({ children }: { children: ReactNode }) { - const { text, status } = useMessagePartText() - const deferredText = useDeferredValue(text) - const isRunning = status.type === 'running' - - return ( - - {children} - - ) -} - interface MarkdownTextSurfaceProps { containerClassName?: string containerProps?: ComponentProps<'div'> + defer?: boolean + smooth?: boolean | SmoothOptions } // Headings shrink to chat scale rather than the prose default (h1≈xl). Kept @@ -433,7 +392,7 @@ function HugeTextFallback({ containerClassName, text }: { containerClassName?: s ) } -function MarkdownTextSurface({ containerClassName, containerProps }: MarkdownTextSurfaceProps) { +function MarkdownTextSurface({ containerClassName, containerProps, defer, smooth }: MarkdownTextSurfaceProps) { const { status, text } = useMessagePartText() const isStreaming = status.type === 'running' @@ -561,26 +520,28 @@ function MarkdownTextSurface({ containerClassName, containerProps }: MarkdownTex components={components} containerClassName={cn(MARKDOWN_CONTAINER_CLASS_NAME, containerClassName)} containerProps={containerProps} + defer={defer} lineNumbers={false} mode="streaming" - // Incomplete-markdown repair is handled by `preprocessWithTailRepair` - // below (tail-bounded remend) instead of Streamdown's built-in pass, - // which re-runs remend over the ENTIRE message on every flush — ~18% - // of streaming script time on 50KB+ messages. The repair itself stays - // always-on (even between flushes / for completed messages): an - // unclosed ```python ... ``` whose body contains `$` (shell snippets, - // JS template strings, dollar amounts) would otherwise leak those - // dollars to the math parser and render broken inline math. Shiki is - // independently deferred via `defer={isStreaming}` on the - // SyntaxHighlighter component. + // The built-in tail-bounded remend is disabled when a custom + // parseMarkdownIntoBlocksFn is supplied, so repair runs in + // preprocessWithTailRepair instead. parseIncompleteMarkdown stays + // false to avoid a second full-text remend pass. parseIncompleteMarkdown={false} parseMarkdownIntoBlocksFn={parseMarkdownIntoBlocksCached} plugins={plugins} preprocess={preprocessWithTailRepair} + smooth={smooth} /> ) } +const SMOOTH_OPTIONS: SmoothOptions = { + drainMs: 500, + maxCharsPerFrame: 30, + minCommitMs: 33 +} + interface MarkdownTextContentProps extends MarkdownTextSurfaceProps { isRunning: boolean text: string @@ -592,19 +553,13 @@ export function MarkdownTextContent({ isRunning, text, ...surfaceProps }: Markdo // the whole message), blanking the Thinking widget. return ( - - - + ) } const MarkdownTextImpl = () => { - return ( - - - - ) + return } export const MarkdownText = memo(MarkdownTextImpl) From 4e10a38c9e14d00e34ddb419bf33c3a0b0c42ad0 Mon Sep 17 00:00:00 2001 From: Harry Yep Date: Wed, 24 Jun 2026 12:05:03 +0800 Subject: [PATCH 03/10] refactor: replace custom lib/remend-tail.ts with built-in tailBoundedRemend MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit delete lib/remend-tail.ts (108 lines) and lib/remend-tail.test.ts (105 lines). the tailBoundedRemend export from @assistant-ui/react-streamdown 0.3.4 is algorithmically identical — same findRemendWindowStart boundary scan, same fence/math tracking, same slice-and-repair strategy. the only differences are improvements: the built-in handles \r (CR) in line endings for Windows compatibility, and accepts an optional RemendOptions parameter passed through to remend. the import in markdown-text.tsx moves from @/lib/remend-tail to @assistant-ui/react-streamdown. the call site (preprocessWithTailRepair) is unchanged. verified: tsc 0 errors, eslint clean, vitest 0 new failures (15 pre-existing, 786 passing — 6 fewer than before because the deleted remend-tail.test.ts had 6 cases), manual verification of incomplete markdown repair during streaming. --- .../components/assistant-ui/markdown-text.tsx | 8 +- apps/desktop/src/lib/remend-tail.test.ts | 105 ----------------- apps/desktop/src/lib/remend-tail.ts | 108 ------------------ 3 files changed, 4 insertions(+), 217 deletions(-) delete mode 100644 apps/desktop/src/lib/remend-tail.test.ts delete mode 100644 apps/desktop/src/lib/remend-tail.ts diff --git a/apps/desktop/src/components/assistant-ui/markdown-text.tsx b/apps/desktop/src/components/assistant-ui/markdown-text.tsx index 841e91fe4c4b..d6ee2a433570 100644 --- a/apps/desktop/src/components/assistant-ui/markdown-text.tsx +++ b/apps/desktop/src/components/assistant-ui/markdown-text.tsx @@ -5,7 +5,8 @@ import { parseMarkdownIntoBlocks, type StreamdownTextComponents, StreamdownTextPrimitive, - type SyntaxHighlighterProps + type SyntaxHighlighterProps, + tailBoundedRemend } from '@assistant-ui/react-streamdown' import { code } from '@streamdown/code' import { type ComponentProps, memo, useEffect, useMemo, useState } from 'react' @@ -29,7 +30,6 @@ import { mediaStreamUrl } from '@/lib/media' import { previewTargetFromMarkdownHref } from '@/lib/preview-targets' -import { tailBoundedRemend } from '@/lib/remend-tail' import { cn } from '@/lib/utils' import { detectEmbed, extractAlert, MarkdownAlert, RichCodeBlock, UrlEmbed } from './embeds' @@ -49,8 +49,8 @@ import { detectEmbed, extractAlert, MarkdownAlert, RichCodeBlock, UrlEmbed } fro const mathPlugin = createMemoizedMathPlugin({ singleDollarTextMath: true }) // Replaces Streamdown's `parseIncompleteMarkdown` (full-text remend per -// flush) with a tail-bounded repair — see lib/remend-tail.ts. Must stay -// module-scope so the prop identity is stable across renders. +// flush) with a tail-bounded repair. Must stay module-scope so the prop +// identity is stable across renders. function preprocessWithTailRepair(text: string): string { try { return tailBoundedRemend(preprocessMarkdown(text)) diff --git a/apps/desktop/src/lib/remend-tail.test.ts b/apps/desktop/src/lib/remend-tail.test.ts deleted file mode 100644 index c730937356d5..000000000000 --- a/apps/desktop/src/lib/remend-tail.test.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { parseMarkdownIntoBlocks } from '@assistant-ui/react-streamdown' -import remend from 'remend' -import { describe, expect, it } from 'vitest' - -import { findRemendWindowStart, tailBoundedRemend } from './remend-tail' - -const CORPUS = `# Heading one - -Intro paragraph with **bold**, *italic*, \`inline code\`, and a [link](https://example.com). - -## Code - -\`\`\`python -def main(): - cost = "$5" - print(f"total: $\{cost}") -\`\`\` - -Some text after the fence with $x^2 + y^2$ inline math. - -$$ -\\int_0^1 f(x) dx -$$ - -- list item one with **bold** -- list item two - -| col a | col b | -| ----- | ----- | -| 1 | 2 | - -~~~js -const s = \`template \${value}\` -~~~ - -Final paragraph with ~~strike~~ and unfinished [link text](https://exa -` - -/** - * Render-equivalence oracle: full-text remend and tail-bounded remend may - * differ in raw string output ONLY in ways that cannot affect rendering — - * i.e. after block splitting, every block must be identical. (Streamdown - * renders blocks independently, so block-level equality IS render equality.) - */ -function blocksOf(text: string): string[] { - return parseMarkdownIntoBlocks(text) -} - -describe('tailBoundedRemend', () => { - it('matches full remend block output at every streaming prefix', () => { - for (let end = 1; end <= CORPUS.length; end++) { - const prefix = CORPUS.slice(0, end) - const full = blocksOf(remend(prefix)) - const tail = blocksOf(tailBoundedRemend(prefix)) - - expect(tail, `prefix length ${end}: ${JSON.stringify(prefix.slice(-60))}`).toEqual(full) - } - }) - - it('repairs an unclosed fence opened early in a long message', () => { - const text = `intro\n\n\`\`\`python\n${'x = 1\n'.repeat(500)}print("$dollar")` - const repaired = tailBoundedRemend(text) - - expect(blocksOf(repaired)).toEqual(blocksOf(remend(text))) - // the window must reach back to the fence opener - expect(findRemendWindowStart(text)).toBe(text.indexOf('```python')) - }) - - it('bounds the window to the tail paragraph when no fence is open', () => { - const text = `para one\n\npara two\n\npara three with **bold` - const start = findRemendWindowStart(text) - - expect(start).toBe(text.indexOf('para three')) - expect(tailBoundedRemend(text)).toBe(remend(text)) - }) - - it('widens the window across an open $$ math block', () => { - const text = `before\n\n$$\n\\frac{a}{b}` - const start = findRemendWindowStart(text) - - expect(start).toBeLessThanOrEqual(text.indexOf('$$')) - expect(blocksOf(tailBoundedRemend(text))).toEqual(blocksOf(remend(text))) - }) - - it('handles closed constructs without modification', () => { - const text = `done **bold** and \`code\`\n\n\`\`\`js\nconst a = 1\n\`\`\`\n\nlast line.` - - expect(tailBoundedRemend(text)).toBe(text) - }) - - it('intentionally diverges from full remend on cross-block dangling openers', () => { - // Full remend scans the whole document and appends `**` for an opener - // left dangling in an EARLIER block, dumping stray asterisks into the - // unrelated tail block ("|**"). Because Streamdown splits into blocks - // after the repair, that opener never renders as bold either way — the - // tail-bounded result is the cleaner of the two. This test documents - // the divergence so a future remend upgrade that changes the behavior - // gets noticed. - const text = `- item with **dangling\n- item two\n\n|` - - expect(remend(text).endsWith('|**')).toBe(true) - expect(tailBoundedRemend(text).endsWith('|')).toBe(true) - expect(tailBoundedRemend(text).endsWith('|**')).toBe(false) - }) -}) diff --git a/apps/desktop/src/lib/remend-tail.ts b/apps/desktop/src/lib/remend-tail.ts deleted file mode 100644 index 683f7dc193e0..000000000000 --- a/apps/desktop/src/lib/remend-tail.ts +++ /dev/null @@ -1,108 +0,0 @@ -import remend from 'remend' - -// Tail-bounded incomplete-markdown repair. -// -// Streamdown's built-in `parseIncompleteMarkdown` runs `remend` over the whole -// accumulated message on every streaming flush (~18% of script time on 50KB+ -// messages). But repairs only ever matter in the trailing block: inline -// constructs can't cross a blank line, and Streamdown splits into blocks AFTER -// the repair, so a dangling opener in an earlier block can't reach the tail. -// We run `remend` on just that block instead. - -const BACKTICK = 96 // ` -const TILDE = 126 // ~ -const SPACE = 32 -const TAB = 9 -const BACKSLASH = 92 - -const isSpace = (c: number) => c === SPACE || c === TAB - -/** - * Index of the last top-level block start — the char after the most recent - * blank line that sits outside any open code fence or `$$` math block. An - * unclosed fence/math always begins after that blank, so it stays wholly - * inside the window without separate tracking. One cheap char pass, no regex. - */ -export function findRemendWindowStart(text: string): number { - const n = text.length - let inFence = false - let fenceChar = 0 - let fenceRun = 0 - let inMath = false - let boundary = 0 - let pending = -1 // a blank line, committed to `boundary` once content follows - - for (let lineStart = 0; lineStart <= n; ) { - let lineEnd = text.indexOf('\n', lineStart) - - if (lineEnd === -1) { - lineEnd = n - } - - let i = lineStart - - while (i < lineEnd && isSpace(text.charCodeAt(i))) { - i += 1 - } - - const first = i < lineEnd ? text.charCodeAt(i) : -1 - let marker = false - - // Fence open/close (``` or ~~~, ≤3 spaces indent). - if ((first === BACKTICK || first === TILDE) && i - lineStart <= 3) { - let run = i - - while (run < lineEnd && text.charCodeAt(run) === first) { - run += 1 - } - - if (run - i >= 3) { - marker = true - - if (!inFence) { - inFence = true - fenceChar = first - fenceRun = run - i - } else if (first === fenceChar && run - i >= fenceRun && onlyWhitespace(text, run, lineEnd)) { - inFence = false - } - } - } - - // Toggle `$$` math state on plain lines ($$ inside a fence is literal). - if (!inFence && !marker) { - for (let s = text.indexOf('$$', lineStart); s !== -1 && s < lineEnd - 1; s = text.indexOf('$$', s + 2)) { - if (s === 0 || text.charCodeAt(s - 1) !== BACKSLASH) { - inMath = !inMath - } - } - } - - if (first === -1 && !inFence && !inMath) { - pending = lineEnd + 1 - } else if (pending !== -1) { - boundary = pending - pending = -1 - } - - lineStart = lineEnd + 1 - } - - return boundary -} - -function onlyWhitespace(text: string, from: number, to: number): boolean { - for (let i = from; i < to; i += 1) { - if (!isSpace(text.charCodeAt(i))) { - return false - } - } - - return true -} - -export function tailBoundedRemend(text: string): string { - const start = findRemendWindowStart(text) - - return start <= 0 ? remend(text) : text.slice(0, start) + remend(text.slice(start)) -} From 102d2d5676a265c969f0c7b0bf55b5d2817ebb25 Mon Sep 17 00:00:00 2001 From: Harry Yep Date: Wed, 24 Jun 2026 12:11:28 +0800 Subject: [PATCH 04/10] refactor: replace custom math delimiter helpers with built-in normalizeMathDelimiters and escapeCurrencyDollars MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit delete the custom rewriteLatexBracketDelimiters and escapeCurrencyDollars implementations from markdown-preprocess.ts (~40 lines). the built-in exports from @assistant-ui/react-streamdown 0.3.4 are strict improvements: - normalizeMathDelimiters combines rewriteLatexBracketDelimiters (now handles double backslashes and trims body whitespace) with rewriteCustomMathTags (handles [/math]...[/math] and [/inline]...[/inline] tags that some models emit — new capability HA didn't have before) - escapeCurrencyDollars excludes $ as a preceding character, so display math $$5 is no longer incorrectly escaped (bugfix) the call site in preprocessMarkdown changes from rewriteLatexBracketDelimiters(escapeCurrencyDollars(part)) to normalizeMathDelimiters(escapeCurrencyDollars(part)). verified: tsc 0 errors, eslint clean, all 16 preprocessMarkdown tests pass (including currency dollar escaping), vitest 0 new failures, manual verification of currency amounts, LaTeX bracket delimiters, display math, and dollar signs inside code blocks. --- apps/desktop/src/lib/markdown-preprocess.ts | 47 +++------------------ 1 file changed, 7 insertions(+), 40 deletions(-) diff --git a/apps/desktop/src/lib/markdown-preprocess.ts b/apps/desktop/src/lib/markdown-preprocess.ts index 5fd08453b26e..6137a7d61e2d 100644 --- a/apps/desktop/src/lib/markdown-preprocess.ts +++ b/apps/desktop/src/lib/markdown-preprocess.ts @@ -1,3 +1,5 @@ +import { escapeCurrencyDollars, normalizeMathDelimiters } from '@assistant-ui/react-streamdown' + import { isLikelyProseFence, sanitizeLanguageTag } from '@/lib/markdown-code' import { stripPreviewTargets } from '@/lib/preview-targets' @@ -310,41 +312,6 @@ function normalizeFenceBlocks(text: string): string { return out.join('\n') } -// Convert LaTeX bracket delimiters to remark-math's dollar-sign syntax. -// Models often emit `\(...\)` for inline math and `\[...\]` for display -// math (the standard LaTeX convention) instead of `$...$` / `$$...$$`. -// remark-math only natively recognizes the dollar form, so we rewrite at -// preprocess time. Done with simple non-greedy matches keyed on the -// escaped-bracket sequences — these are rare enough in non-math content -// (you'd have to write a literal `\(` followed eventually by a literal -// `\)` with NO interleaving newline-paragraph-break) that false positives -// are extremely unlikely. -const LATEX_INLINE_RE = /\\\(([^\n]+?)\\\)/g -const LATEX_DISPLAY_RE = /\\\[([\s\S]+?)\\\]/g - -function rewriteLatexBracketDelimiters(text: string): string { - return text - .replace(LATEX_INLINE_RE, (_, body: string) => `$${body}$`) - .replace(LATEX_DISPLAY_RE, (_, body: string) => `$$${body}$$`) -} - -// Escape `$` patterns so they don't get eaten as math delimiters. -// Models commonly write currency amounts ($5, $19.99, $1,299) in prose. -// With `singleDollarTextMath: true`, remark-math is greedy and matches -// EVERY pair of `$`s — including the open of `$5` to the next `$10`, -// rendering "5 in my pocket and you have " as italicized math text. -// The de-facto convention across math-supporting LLM UIs is to treat -// `$` followed by a digit as currency rather than math, since math -// expressions almost always start with a letter or `\command`. Trade- -// off: a math expression like `$5x = 10$` would have its leading 5 -// escaped — annoying but rare. The escape `\$` survives to render as -// a literal `$` in the final output. -const CURRENCY_DOLLAR_RE = /(^|[^\\])\$(?=\d)/g - -function escapeCurrencyDollars(text: string): string { - return text.replace(CURRENCY_DOLLAR_RE, '$1\\$') -} - export function preprocessMarkdown(text: string): string { const cleaned = text.replace(REASONING_BLOCK_RE, '').replace(PREVIEW_MARKER_RE, '') const scrubbed = scrubBacktickNoise(cleaned) @@ -377,12 +344,12 @@ export function preprocessMarkdown(text: string): string { const leading = part.match(/^\s*/)?.[0] ?? '' const trailing = part.match(/\s*$/)?.[0] ?? '' - // rewriteLatexBracketDelimiters runs only on prose segments so - // we don't accidentally touch `\(` inside a code block. - // escapeCurrencyDollars likewise only runs on prose, so legit - // `$5` literals inside fenced code stay intact. + // normalizeMathDelimiters rewrites LaTeX bracket delimiters and + // custom math tags to dollar form. escapeCurrencyDollars escapes + // `$` so currency isn't eaten as math. Both run only on + // prose segments so code blocks stay untouched. const transformed = normalizeVisibleProse( - stripPreviewTargets(rewriteLatexBracketDelimiters(escapeCurrencyDollars(part))) + stripPreviewTargets(normalizeMathDelimiters(escapeCurrencyDollars(part))) ) return leading + transformed + trailing From 673a61edc82d9545366f3fe50788295af13b5ce1 Mon Sep 17 00:00:00 2001 From: Harry Yep Date: Tue, 30 Jun 2026 17:24:24 +0800 Subject: [PATCH 05/10] fix(desktop): match renamed useClientLookup out-of-bounds throw in MessageRenderBoundary @assistant-ui/store renamed its index-out-of-bounds throw from tapClientLookup/tapClientResource to useClientLookup in the 0.14 upgrade, so the boundary's /tapClient.../ filter stopped matching and re-threw the transient session-switch and reconnect race to root, blanking the app. broaden the regex to accept the new prefix (keeping the old one for older store versions) and point the test at the real message so it exercises the live path instead of the dead string. --- .../components/assistant-ui/message-render-boundary.test.tsx | 4 ++-- .../src/components/assistant-ui/message-render-boundary.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/desktop/src/components/assistant-ui/message-render-boundary.test.tsx b/apps/desktop/src/components/assistant-ui/message-render-boundary.test.tsx index 934f3babd1c0..8e1b70f7934f 100644 --- a/apps/desktop/src/components/assistant-ui/message-render-boundary.test.tsx +++ b/apps/desktop/src/components/assistant-ui/message-render-boundary.test.tsx @@ -13,7 +13,7 @@ function Boom({ error }: { error: Error | null }): null { return null } -const lookupError = new Error('tapClientLookup: Index 2 out of bounds (length: 2)') +const lookupError = new Error('useClientLookup: Index 2 out of bounds (length: 2)') describe('MessageRenderBoundary', () => { it('renders children when nothing throws', () => { @@ -26,7 +26,7 @@ describe('MessageRenderBoundary', () => { expect(screen.getByText('content')).toBeTruthy() }) - it('swallows the transient tapClientLookup out-of-bounds store race', () => { + it('swallows the transient useClientLookup out-of-bounds store race', () => { const spy = vi.spyOn(console, 'error').mockImplementation(() => undefined) const { container } = render( diff --git a/apps/desktop/src/components/assistant-ui/message-render-boundary.tsx b/apps/desktop/src/components/assistant-ui/message-render-boundary.tsx index 990f8c6072e4..f0fafbc4f18f 100644 --- a/apps/desktop/src/components/assistant-ui/message-render-boundary.tsx +++ b/apps/desktop/src/components/assistant-ui/message-render-boundary.tsx @@ -1,6 +1,6 @@ import { Component, type ReactNode } from 'react' -// `@assistant-ui/store`'s index-keyed child-scope lookup (`tapClientLookup`) +// `@assistant-ui/store`'s index-keyed child-scope lookup (`useClientLookup`) // throws — rather than returning undefined — when a subscriber reads an index // that the message/parts list no longer has. This races during high-frequency // store replacement (session switch mid-stream, gateway reconnect replay): a @@ -10,7 +10,7 @@ import { Component, type ReactNode } from 'react' // without a local boundary it unwinds to the root and blanks the whole app. // Upstream-tracked: assistant-ui/assistant-ui#4051, #3652. const isTransientLookupError = (error: unknown): boolean => - error instanceof Error && /tapClient(Lookup|Resource).*out of bounds/.test(error.message) + error instanceof Error && /(useClientLookup|tapClient(Lookup|Resource)).*out of bounds/.test(error.message) interface Props { // Changes whenever the message list mutates; remounting clears the caught From 9eb89b8a12448ed987b3b88983e6f0da02c6bb34 Mon Sep 17 00:00:00 2001 From: Harry Yep Date: Tue, 30 Jun 2026 17:24:58 +0800 Subject: [PATCH 06/10] docs(desktop): correct streaming-repair comments after the defer/smooth swap the parseIncompleteMarkdown comment implied the reveal frontier is repaired; repair runs on the full accumulated text, so reword it to say that. drop the now-dead "multiple surfaces render the same content" clause from the block-cache comment (the smooth and defer wrappers that caused it were removed), and trim the math-preprocess comment to the load-bearing prose-only constraint. --- .../src/components/assistant-ui/markdown-text.tsx | 12 ++++++------ apps/desktop/src/lib/markdown-preprocess.ts | 6 ++---- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/apps/desktop/src/components/assistant-ui/markdown-text.tsx b/apps/desktop/src/components/assistant-ui/markdown-text.tsx index d6ee2a433570..f9eeae55b63e 100644 --- a/apps/desktop/src/components/assistant-ui/markdown-text.tsx +++ b/apps/desktop/src/components/assistant-ui/markdown-text.tsx @@ -62,8 +62,7 @@ function preprocessWithTailRepair(text: string): string { // Memoized block splitter. Streamdown calls `parseMarkdownIntoBlocks` (a full // `marked` lex of the entire message, ~1.6ms per 28KB) inside a useMemo keyed // on the text — but the same text is re-lexed every time a message REMOUNTS -// (virtualizer scroll, session switch) and whenever multiple surfaces render -// the same content (remount, session switch). A small module-level +// (virtualizer scroll, session switch). A small module-level // LRU keyed by the exact source string removes all of those repeat parses // with zero correctness risk (same input → same output). Streaming tail // growth misses the cache by design (every flush is a new string) — that @@ -523,10 +522,11 @@ function MarkdownTextSurface({ containerClassName, containerProps, defer, smooth defer={defer} lineNumbers={false} mode="streaming" - // The built-in tail-bounded remend is disabled when a custom - // parseMarkdownIntoBlocksFn is supplied, so repair runs in - // preprocessWithTailRepair instead. parseIncompleteMarkdown stays - // false to avoid a second full-text remend pass. + // Incomplete-markdown repair runs in preprocessWithTailRepair on the + // full accumulated text; the built-in tail-bounded remend is disabled + // because a custom parseMarkdownIntoBlocksFn is supplied, and + // parseIncompleteMarkdown stays false to avoid a second full-text + // remend pass. parseIncompleteMarkdown={false} parseMarkdownIntoBlocksFn={parseMarkdownIntoBlocksCached} plugins={plugins} diff --git a/apps/desktop/src/lib/markdown-preprocess.ts b/apps/desktop/src/lib/markdown-preprocess.ts index 6137a7d61e2d..63768a710d14 100644 --- a/apps/desktop/src/lib/markdown-preprocess.ts +++ b/apps/desktop/src/lib/markdown-preprocess.ts @@ -344,10 +344,8 @@ export function preprocessMarkdown(text: string): string { const leading = part.match(/^\s*/)?.[0] ?? '' const trailing = part.match(/\s*$/)?.[0] ?? '' - // normalizeMathDelimiters rewrites LaTeX bracket delimiters and - // custom math tags to dollar form. escapeCurrencyDollars escapes - // `$` so currency isn't eaten as math. Both run only on - // prose segments so code blocks stay untouched. + // Run only on prose segments so `$5` literals and `\(` inside code + // blocks stay intact. const transformed = normalizeVisibleProse( stripPreviewTargets(normalizeMathDelimiters(escapeCurrencyDollars(part))) ) From b50b83fbd827e9307a3a2041be48e11762482876 Mon Sep 17 00:00:00 2001 From: Harry Yep Date: Tue, 30 Jun 2026 17:38:44 +0800 Subject: [PATCH 07/10] test(desktop): add math-delimiter fixtures for the react-streamdown preprocess swap lock the four behaviors the built-in normalizeMathDelimiters/escapeCurrencyDollars introduce over the deleted custom helpers: $$$$ display math stays intact, double-backslash brackets and [/math]/[/inline] tag pairs rewrite to dollar delimiters, and currency dollars in prose are escaped. the existing preprocessMarkdown suite had no math cases. --- .../assistant-ui/markdown-text.test.ts | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/apps/desktop/src/components/assistant-ui/markdown-text.test.ts b/apps/desktop/src/components/assistant-ui/markdown-text.test.ts index b3ea416d0664..748bef09e288 100644 --- a/apps/desktop/src/components/assistant-ui/markdown-text.test.ts +++ b/apps/desktop/src/components/assistant-ui/markdown-text.test.ts @@ -210,4 +210,29 @@ describe('preprocessMarkdown', () => { expect(() => preprocessMarkdown(input)).not.toThrow() }) + + it('keeps $$$$ display math intact instead of escaping it as currency', () => { + const output = preprocessMarkdown('$$5x = 10$$') + + expect(output).toContain('$$5x = 10$$') + expect(output).not.toContain('\\$') + }) + + it('rewrites double-backslash bracket math to dollar delimiters', () => { + const output = preprocessMarkdown('\\\\(x^2\\\\)') + + expect(output).toContain('$x^2$') + }) + + it('rewrites [/math] and [/inline] tag pairs to dollar delimiters', () => { + expect(preprocessMarkdown('[/math]a+b[/math]')).toContain('$$a+b$$') + expect(preprocessMarkdown('[/inline]x[/inline]')).toContain('$x$') + }) + + it('escapes currency dollars in prose so they are not parsed as math', () => { + const output = preprocessMarkdown('$5 and $10') + + expect(output).toContain('\\$5') + expect(output).toContain('\\$10') + }) }) From abd7458e77aa99840f2ac456a2acf0ca330ddb95 Mon Sep 17 00:00:00 2001 From: Harry Yep Date: Tue, 30 Jun 2026 17:50:53 +0800 Subject: [PATCH 08/10] docs(desktop): note the load-bearing isOptimistic invariant on the optimistic placeholder a reader of this subclass can't recover from hermes code alone that the metadata.isOptimistic flag drives core's off-branch eviction and export() omission, so a future core change to it would silently break placeholder cleanup. flagged in the upgrade review. --- apps/desktop/src/lib/incremental-external-store-runtime.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/desktop/src/lib/incremental-external-store-runtime.ts b/apps/desktop/src/lib/incremental-external-store-runtime.ts index e8d2033c4dbd..48c0c424dfd2 100644 --- a/apps/desktop/src/lib/incremental-external-store-runtime.ts +++ b/apps/desktop/src/lib/incremental-external-store-runtime.ts @@ -136,6 +136,8 @@ class IncrementalExternalStoreThreadRuntimeCore extends ExternalStoreThreadRunti self._notifyEventSubscribers(store.isRunning ? 'runStart' : 'runEnd', {}) } + // metadata.isOptimistic keeps this placeholder ephemeral: core evicts + // off-branch optimistic messages on head moves and omits them from export(). if (hasUpcomingMessage(isRunning, messages)) { const optimisticId = generateId() this.repository.addOrUpdateMessage( From 6fc24651f74ca465ec490d25a63da2f57adf40be Mon Sep 17 00:00:00 2001 From: Brooklyn Nicholson Date: Mon, 13 Jul 2026 16:48:43 -0400 Subject: [PATCH 09/10] fix(desktop): add respondToApproval to clarify tool-part test mock @assistant-ui/react 0.14 makes respondToApproval required on ToolCallMessagePartProps; the settledClarifyProps helper still lacked it after the upgrade cherry-picks, so tsc failed on clarify-tool.test.tsx. --- apps/desktop/src/components/assistant-ui/clarify-tool.test.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/desktop/src/components/assistant-ui/clarify-tool.test.tsx b/apps/desktop/src/components/assistant-ui/clarify-tool.test.tsx index a508b8471c51..8938435c0f60 100644 --- a/apps/desktop/src/components/assistant-ui/clarify-tool.test.tsx +++ b/apps/desktop/src/components/assistant-ui/clarify-tool.test.tsx @@ -29,6 +29,7 @@ function settledClarifyProps( args, argsText: JSON.stringify(args), isError: false, + respondToApproval: vi.fn(), result, resume: vi.fn(), status: { type: 'complete' }, From c91e651009a036f520119655341d3bcc8931a6a5 Mon Sep 17 00:00:00 2001 From: Brooklyn Nicholson Date: Wed, 15 Jul 2026 01:58:06 -0400 Subject: [PATCH 10/10] fix(desktop): restore @esbuild platform entries in lockfile for CI The assistant-ui upgrade lockfile omitted standalone @esbuild/* packages. CI runs npm ci --ignore-scripts, so esbuild's postinstall never runs and desktop/ui-tui check (build:ink, bundle-electron-main) fail without them. Graft the 26 platform packages from main's lockfile. --- package-lock.json | 442 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 442 insertions(+) diff --git a/package-lock.json b/package-lock.json index da998b92864e..2909052cd360 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19198,6 +19198,448 @@ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "dev": true, "license": "MIT" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", + "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", + "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", + "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", + "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", + "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", + "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", + "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", + "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", + "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", + "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", + "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", + "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", + "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", + "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", + "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", + "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", + "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", + "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", + "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", + "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", + "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", + "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", + "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", + "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", + "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", + "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=18" + } } } }