mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-28 06:51:16 +08:00
feat(tui): archive todos at turn end with incomplete hint
This commit is contained in:
32
ui-tui/babel.compiler.config.cjs
Normal file
32
ui-tui/babel.compiler.config.cjs
Normal file
@@ -0,0 +1,32 @@
|
||||
// React Compiler runs as a post-pass over tsc's `dist/` output.
|
||||
//
|
||||
// tsc emits JSX as _jsx() calls (jsx: "react-jsx"). babel-plugin-react-compiler
|
||||
// accepts that shape and auto-memoizes every component it recognizes via the
|
||||
// default `infer` compilation mode (PascalCase components + use-prefixed
|
||||
// hooks). The `sources` filter keeps it from walking node_modules files that
|
||||
// end up in source maps.
|
||||
//
|
||||
// target=19 matches our react ^19.2.4 dependency.
|
||||
module.exports = {
|
||||
assumptions: {
|
||||
setPublicClassFields: true
|
||||
},
|
||||
plugins: [
|
||||
[
|
||||
'babel-plugin-react-compiler',
|
||||
{
|
||||
target: '19',
|
||||
sources: (filename) => {
|
||||
if (!filename) return false
|
||||
if (filename.includes('node_modules')) return false
|
||||
return true
|
||||
}
|
||||
}
|
||||
]
|
||||
],
|
||||
// We feed already-compiled JS into babel; don't re-parse as TS/JSX.
|
||||
// @babel/preset-env etc. would over-transform — the compiler is our only
|
||||
// transform here.
|
||||
babelrc: false,
|
||||
configFile: false
|
||||
}
|
||||
601
ui-tui/package-lock.json
generated
601
ui-tui/package-lock.json
generated
@@ -16,14 +16,19 @@
|
||||
"unicode-animations": "^1.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "^7.28.6",
|
||||
"@babel/core": "^7.29.0",
|
||||
"@babel/plugin-syntax-jsx": "^7.28.6",
|
||||
"@eslint/js": "^9",
|
||||
"@types/node": "^25.5.0",
|
||||
"@types/react": "^19.2.14",
|
||||
"@typescript-eslint/eslint-plugin": "^8",
|
||||
"@typescript-eslint/parser": "^8",
|
||||
"babel-plugin-react-compiler": "^1.0.0",
|
||||
"eslint": "^9",
|
||||
"eslint-plugin-perfectionist": "^5",
|
||||
"eslint-plugin-react": "^7",
|
||||
"eslint-plugin-react-compiler": "^19.1.0-rc.2",
|
||||
"eslint-plugin-react-hooks": "^7",
|
||||
"eslint-plugin-unused-imports": "^4",
|
||||
"globals": "^16",
|
||||
@@ -58,6 +63,36 @@
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/cli": {
|
||||
"version": "7.28.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.28.6.tgz",
|
||||
"integrity": "sha512-6EUNcuBbNkj08Oj4gAZ+BUU8yLCgKzgVX4gaTh09Ya2C8ICM4P+G30g4m3akRxSYAp3A/gnWchrNst7px4/nUQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/trace-mapping": "^0.3.28",
|
||||
"commander": "^6.2.0",
|
||||
"convert-source-map": "^2.0.0",
|
||||
"fs-readdir-recursive": "^1.1.0",
|
||||
"glob": "^7.2.0",
|
||||
"make-dir": "^2.1.0",
|
||||
"slash": "^2.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"babel": "bin/babel.js",
|
||||
"babel-external-helpers": "bin/babel-external-helpers.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@nicolo-ribaudo/chokidar-2": "2.1.8-no-fsevents.3",
|
||||
"chokidar": "^3.6.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame": {
|
||||
"version": "7.29.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
|
||||
@@ -141,6 +176,19 @@
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-annotate-as-pure": {
|
||||
"version": "7.27.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz",
|
||||
"integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.27.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-compilation-targets": {
|
||||
"version": "7.28.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz",
|
||||
@@ -168,6 +216,38 @@
|
||||
"semver": "bin/semver.js"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-create-class-features-plugin": {
|
||||
"version": "7.28.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.6.tgz",
|
||||
"integrity": "sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-annotate-as-pure": "^7.27.3",
|
||||
"@babel/helper-member-expression-to-functions": "^7.28.5",
|
||||
"@babel/helper-optimise-call-expression": "^7.27.1",
|
||||
"@babel/helper-replace-supers": "^7.28.6",
|
||||
"@babel/helper-skip-transparent-expression-wrappers": "^7.27.1",
|
||||
"@babel/traverse": "^7.28.6",
|
||||
"semver": "^6.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": {
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
||||
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-globals": {
|
||||
"version": "7.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
|
||||
@@ -178,6 +258,20 @@
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-member-expression-to-functions": {
|
||||
"version": "7.28.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz",
|
||||
"integrity": "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/traverse": "^7.28.5",
|
||||
"@babel/types": "^7.28.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-module-imports": {
|
||||
"version": "7.28.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz",
|
||||
@@ -210,6 +304,61 @@
|
||||
"@babel/core": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-optimise-call-expression": {
|
||||
"version": "7.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz",
|
||||
"integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.27.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-plugin-utils": {
|
||||
"version": "7.28.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz",
|
||||
"integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-replace-supers": {
|
||||
"version": "7.28.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.28.6.tgz",
|
||||
"integrity": "sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-member-expression-to-functions": "^7.28.5",
|
||||
"@babel/helper-optimise-call-expression": "^7.27.1",
|
||||
"@babel/traverse": "^7.28.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-skip-transparent-expression-wrappers": {
|
||||
"version": "7.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz",
|
||||
"integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/traverse": "^7.27.1",
|
||||
"@babel/types": "^7.27.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-string-parser": {
|
||||
"version": "7.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
|
||||
@@ -270,6 +419,40 @@
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-proposal-private-methods": {
|
||||
"version": "7.18.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz",
|
||||
"integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==",
|
||||
"deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-methods instead.",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-create-class-features-plugin": "^7.18.6",
|
||||
"@babel/helper-plugin-utils": "^7.18.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-syntax-jsx": {
|
||||
"version": "7.28.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz",
|
||||
"integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.28.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/template": {
|
||||
"version": "7.28.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
|
||||
@@ -1156,6 +1339,14 @@
|
||||
"@emnapi/runtime": "^1.7.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@nicolo-ribaudo/chokidar-2": {
|
||||
"version": "2.1.8-no-fsevents.3",
|
||||
"resolved": "https://registry.npmjs.org/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.3.tgz",
|
||||
"integrity": "sha512-s88O1aVtXftvp5bCPB7WnmXc5IwOZZ7YPuwNPt+GtOOXpPvad1LfbmjYv+qII7zP6RU2QGnqve27dnLycEnyEQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/@oxc-project/types": {
|
||||
"version": "0.124.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.124.0.tgz",
|
||||
@@ -1952,6 +2143,35 @@
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/anymatch": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
|
||||
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"normalize-path": "^3.0.0",
|
||||
"picomatch": "^2.0.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/anymatch/node_modules/picomatch": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
|
||||
"integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=8.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/argparse": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
||||
@@ -2145,6 +2365,16 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/babel-plugin-react-compiler": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/babel-plugin-react-compiler/-/babel-plugin-react-compiler-1.0.0.tgz",
|
||||
"integrity": "sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.26.0"
|
||||
}
|
||||
},
|
||||
"node_modules/balanced-match": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
|
||||
@@ -2177,6 +2407,20 @@
|
||||
"require-from-string": "^2.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/binary-extensions": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
|
||||
"integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "5.0.5",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
|
||||
@@ -2190,6 +2434,20 @@
|
||||
"node": "18 || 20 || >=22"
|
||||
}
|
||||
},
|
||||
"node_modules/braces": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
||||
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"fill-range": "^7.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/browserslist": {
|
||||
"version": "4.28.2",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz",
|
||||
@@ -2332,6 +2590,46 @@
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/chokidar": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
||||
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"anymatch": "~3.1.2",
|
||||
"braces": "~3.0.2",
|
||||
"glob-parent": "~5.1.2",
|
||||
"is-binary-path": "~2.1.0",
|
||||
"is-glob": "~4.0.1",
|
||||
"normalize-path": "~3.0.0",
|
||||
"readdirp": "~3.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8.10.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/chokidar/node_modules/glob-parent": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
||||
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"is-glob": "^4.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/cli-boxes": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz",
|
||||
@@ -2407,6 +2705,16 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/commander": {
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz",
|
||||
"integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
@@ -2999,6 +3307,50 @@
|
||||
"eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-react-compiler": {
|
||||
"version": "19.1.0-rc.2",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-react-compiler/-/eslint-plugin-react-compiler-19.1.0-rc.2.tgz",
|
||||
"integrity": "sha512-oKalwDGcD+RX9mf3NEO4zOoUMeLvjSvcbbEOpquzmzqEEM2MQdp7/FY/Hx9NzmUwFzH1W9SKTz5fihfMldpEYw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.24.4",
|
||||
"@babel/parser": "^7.24.4",
|
||||
"@babel/plugin-proposal-private-methods": "^7.18.6",
|
||||
"hermes-parser": "^0.25.1",
|
||||
"zod": "^3.22.4",
|
||||
"zod-validation-error": "^3.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.17.0 || ^16.0.0 || >= 18.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": ">=7"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-react-compiler/node_modules/zod": {
|
||||
"version": "3.25.76",
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
|
||||
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-react-compiler/node_modules/zod-validation-error": {
|
||||
"version": "3.5.4",
|
||||
"resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-3.5.4.tgz",
|
||||
"integrity": "sha512-+hEiRIiPobgyuFlEojnqjJnhFvg4r/i3cqgcm67eehZf/WBaK3g6cD02YU9mtdVxZjv8CzCA9n/Rhrs3yAAvAw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"zod": "^3.24.4"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-react-hooks": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz",
|
||||
@@ -3309,6 +3661,20 @@
|
||||
"node": ">=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fill-range": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"to-regex-range": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/find-up": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
|
||||
@@ -3363,6 +3729,20 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/fs-readdir-recursive": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz",
|
||||
"integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||
@@ -3521,6 +3901,28 @@
|
||||
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/glob": {
|
||||
"version": "7.2.3",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
||||
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
|
||||
"deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
"inherits": "2",
|
||||
"minimatch": "^3.1.1",
|
||||
"once": "^1.3.0",
|
||||
"path-is-absolute": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/glob-parent": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
|
||||
@@ -3534,6 +3936,37 @@
|
||||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/glob/node_modules/balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/glob/node_modules/brace-expansion": {
|
||||
"version": "1.1.14",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
|
||||
"integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/glob/node_modules/minimatch": {
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
|
||||
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/globals": {
|
||||
"version": "16.5.0",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz",
|
||||
@@ -3736,6 +4169,25 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/inflight": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
|
||||
"deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"once": "^1.3.0",
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/ink": {
|
||||
"version": "6.8.0",
|
||||
"resolved": "https://registry.npmjs.org/ink/-/ink-6.8.0.tgz",
|
||||
@@ -3919,6 +4371,20 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/is-binary-path": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
|
||||
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"binary-extensions": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/is-boolean-object": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz",
|
||||
@@ -4115,6 +4581,17 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/is-number": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
||||
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=0.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-number-object": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz",
|
||||
@@ -4745,6 +5222,30 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.5.5"
|
||||
}
|
||||
},
|
||||
"node_modules/make-dir": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
|
||||
"integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"pify": "^4.0.1",
|
||||
"semver": "^5.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/make-dir/node_modules/semver": {
|
||||
"version": "5.7.2",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
|
||||
"integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver"
|
||||
}
|
||||
},
|
||||
"node_modules/math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
@@ -4875,6 +5376,17 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/normalize-path": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
||||
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
@@ -4994,6 +5506,16 @@
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/onetime": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
|
||||
@@ -5109,6 +5631,16 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/path-is-absolute": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/path-key": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
||||
@@ -5153,6 +5685,16 @@
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/pify": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
|
||||
"integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/possible-typed-array-names": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz",
|
||||
@@ -5271,6 +5813,34 @@
|
||||
"react": "^19.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/readdirp": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
|
||||
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"picomatch": "^2.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/readdirp/node_modules/picomatch": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
|
||||
"integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=8.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/reflect.getprototypeof": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz",
|
||||
@@ -5652,6 +6222,16 @@
|
||||
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/slash": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz",
|
||||
"integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/slice-ansi": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-8.0.0.tgz",
|
||||
@@ -5990,6 +6570,20 @@
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"is-number": "^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ts-api-utils": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz",
|
||||
@@ -6607,6 +7201,13 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "8.20.0",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz",
|
||||
|
||||
@@ -24,14 +24,19 @@
|
||||
"unicode-animations": "^1.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "^7.28.6",
|
||||
"@babel/core": "^7.29.0",
|
||||
"@babel/plugin-syntax-jsx": "^7.28.6",
|
||||
"@eslint/js": "^9",
|
||||
"@types/node": "^25.5.0",
|
||||
"@types/react": "^19.2.14",
|
||||
"@typescript-eslint/eslint-plugin": "^8",
|
||||
"@typescript-eslint/parser": "^8",
|
||||
"babel-plugin-react-compiler": "^1.0.0",
|
||||
"eslint": "^9",
|
||||
"eslint-plugin-perfectionist": "^5",
|
||||
"eslint-plugin-react": "^7",
|
||||
"eslint-plugin-react-compiler": "^19.1.0-rc.2",
|
||||
"eslint-plugin-react-hooks": "^7",
|
||||
"eslint-plugin-unused-imports": "^4",
|
||||
"globals": "^16",
|
||||
|
||||
@@ -59,7 +59,7 @@ describe('createGatewayEventHandler', () => {
|
||||
patchUiState({ showReasoning: true })
|
||||
})
|
||||
|
||||
it('keeps todo list visible after final assistant text completes', () => {
|
||||
it('archives incomplete todos into transcript flow at end of turn so they scroll up', () => {
|
||||
const appended: Msg[] = []
|
||||
|
||||
const todos = [
|
||||
@@ -76,8 +76,12 @@ describe('createGatewayEventHandler', () => {
|
||||
|
||||
onEvent({ payload: { text: 'Started a todo list.' }, type: 'message.complete' } as any)
|
||||
|
||||
expect(appended[appended.length - 1]).toMatchObject({ role: 'assistant', text: 'Started a todo list.' })
|
||||
expect(getTurnState().todos).toEqual(todos)
|
||||
const trail = appended.find(msg => msg.kind === 'trail' && msg.todos?.length)
|
||||
const finalText = appended.find(msg => msg.role === 'assistant' && msg.text === 'Started a todo list.')
|
||||
|
||||
expect(finalText).toBeDefined()
|
||||
expect(trail).toMatchObject({ kind: 'trail', role: 'system', todos, todoIncomplete: true })
|
||||
expect(getTurnState().todos).toEqual([])
|
||||
})
|
||||
|
||||
it('archives completed todos into transcript flow at end of turn', () => {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { beforeEach, describe, expect, it } from 'vitest'
|
||||
import {
|
||||
appendTurnSegment,
|
||||
archiveDoneTodos,
|
||||
archiveTodosAtTurnEnd,
|
||||
getTurnState,
|
||||
patchTurnState,
|
||||
resetTurnState,
|
||||
@@ -20,7 +21,7 @@ describe('turnStore live progress helpers', () => {
|
||||
]
|
||||
})
|
||||
|
||||
expect(archiveDoneTodos()).toEqual([
|
||||
expect(archiveTodosAtTurnEnd()).toEqual([
|
||||
{
|
||||
kind: 'trail',
|
||||
role: 'system',
|
||||
@@ -34,11 +35,25 @@ describe('turnStore live progress helpers', () => {
|
||||
expect(getTurnState().todos).toEqual([])
|
||||
})
|
||||
|
||||
it('does not archive active todos', () => {
|
||||
patchTurnState({ todos: [{ content: 'cook', id: 'cook', status: 'in_progress' }] })
|
||||
it('archives incomplete todos with an incomplete flag so the hint renders', () => {
|
||||
patchTurnState({
|
||||
todos: [
|
||||
{ content: 'cook', id: 'cook', status: 'completed' },
|
||||
{ content: 'serve', id: 'serve', status: 'in_progress' },
|
||||
{ content: 'eat', id: 'eat', status: 'pending' }
|
||||
]
|
||||
})
|
||||
|
||||
const archived = archiveTodosAtTurnEnd()
|
||||
expect(archived).toHaveLength(1)
|
||||
expect(archived[0]!.todoIncomplete).toBe(true)
|
||||
expect(archived[0]!.todos?.map(t => t.id)).toEqual(['cook', 'serve', 'eat'])
|
||||
expect(getTurnState().todos).toEqual([])
|
||||
})
|
||||
|
||||
it('returns nothing when there are no todos at turn end', () => {
|
||||
expect(archiveTodosAtTurnEnd()).toEqual([])
|
||||
expect(archiveDoneTodos()).toEqual([])
|
||||
expect(getTurnState().todos).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('tracks collapsed state independently of todo content', () => {
|
||||
|
||||
@@ -11,7 +11,7 @@ import { applyDelegationStatus, getDelegationState } from './delegationStore.js'
|
||||
import type { GatewayEventHandlerContext } from './interfaces.js'
|
||||
import { patchOverlayState } from './overlayStore.js'
|
||||
import { turnController } from './turnController.js'
|
||||
import { archiveDoneTodos } from './turnStore.js'
|
||||
import { archiveTodosAtTurnEnd } from './turnStore.js'
|
||||
import { getUiState, patchUiState } from './uiStore.js'
|
||||
|
||||
const NO_PROVIDER_RE = /\bNo (?:LLM|inference) provider configured\b/i
|
||||
@@ -539,7 +539,7 @@ export function createGatewayEventHandler(ctx: GatewayEventHandlerContext): (ev:
|
||||
if (!wasInterrupted) {
|
||||
const msgs: Msg[] = finalMessages.length ? finalMessages : [{ role: 'assistant', text: finalText }]
|
||||
msgs.forEach(appendMessage)
|
||||
archiveDoneTodos().forEach(appendMessage)
|
||||
archiveTodosAtTurnEnd().forEach(appendMessage)
|
||||
|
||||
if (bellOnComplete && stdout?.isTTY) {
|
||||
stdout.write('\x07')
|
||||
|
||||
@@ -40,14 +40,22 @@ export const patchTurnState = (next: Partial<TurnState> | ((state: TurnState) =>
|
||||
|
||||
export const toggleTodoCollapsed = () => patchTurnState(state => ({ ...state, todoCollapsed: !state.todoCollapsed }))
|
||||
|
||||
export const archiveDoneTodos = () => {
|
||||
export const archiveDoneTodos = () => archiveTodosAtTurnEnd()
|
||||
|
||||
export const archiveTodosAtTurnEnd = () => {
|
||||
const state = $turnState.get()
|
||||
|
||||
if (!isTodoDone(state.todos)) {
|
||||
if (!state.todos.length) {
|
||||
return []
|
||||
}
|
||||
|
||||
const msg: Msg = { kind: 'trail', role: 'system', text: '', todos: state.todos }
|
||||
const msg: Msg = {
|
||||
kind: 'trail',
|
||||
role: 'system',
|
||||
text: '',
|
||||
todos: state.todos,
|
||||
...(isTodoDone(state.todos) ? {} : { todoIncomplete: true })
|
||||
}
|
||||
|
||||
patchTurnState({ todoCollapsed: false, todos: [] })
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ export const MessageLine = memo(function MessageLine({
|
||||
const thinking = msg.thinking?.trim() ?? ''
|
||||
|
||||
if (msg.kind === 'trail' && msg.todos?.length) {
|
||||
return <TodoPanel t={t} todos={msg.todos} />
|
||||
return <TodoPanel incomplete={msg.todoIncomplete} t={t} todos={msg.todos} />
|
||||
}
|
||||
|
||||
if (msg.kind === 'trail' && (msg.tools?.length || tools.length || thinking)) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Box, Text } from '@hermes/ink'
|
||||
import { memo } from 'react'
|
||||
|
||||
import { countPendingTodos } from '../lib/liveProgress.js'
|
||||
import { todoGlyph, todoTone } from '../lib/todo.js'
|
||||
import type { Theme } from '../theme.js'
|
||||
import type { TodoItem } from '../types.js'
|
||||
@@ -13,11 +14,13 @@ const rowColor = (t: Theme, status: TodoItem['status']) => {
|
||||
|
||||
export const TodoPanel = memo(function TodoPanel({
|
||||
collapsed = false,
|
||||
incomplete = false,
|
||||
onToggle,
|
||||
t,
|
||||
todos
|
||||
}: {
|
||||
collapsed?: boolean
|
||||
incomplete?: boolean
|
||||
onToggle?: () => void
|
||||
t: Theme
|
||||
todos: TodoItem[]
|
||||
@@ -27,6 +30,7 @@ export const TodoPanel = memo(function TodoPanel({
|
||||
}
|
||||
|
||||
const done = todos.filter(todo => todo.status === 'completed').length
|
||||
const pending = countPendingTodos(todos)
|
||||
|
||||
return (
|
||||
<Box flexDirection="column" marginBottom={1}>
|
||||
@@ -39,6 +43,12 @@ export const TodoPanel = memo(function TodoPanel({
|
||||
<Text color={t.color.statusFg} dim>
|
||||
({done}/{todos.length})
|
||||
</Text>
|
||||
{incomplete && pending > 0 && (
|
||||
<Text color={t.color.dim} dim>
|
||||
{' '}
|
||||
· incomplete · {pending} still {pending === 1 ? 'pending' : 'pending/in_progress'}
|
||||
</Text>
|
||||
)}
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
|
||||
@@ -2,9 +2,9 @@ import type { ScrollBoxHandle } from '@hermes/ink'
|
||||
import {
|
||||
type RefObject,
|
||||
useCallback,
|
||||
useDeferredValue,
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
useSyncExternalStore
|
||||
@@ -14,8 +14,43 @@ const ESTIMATE = 4
|
||||
const OVERSCAN = 40
|
||||
const MAX_MOUNTED = 260
|
||||
const COLD_START = 40
|
||||
// Floor on unmeasured row height used when computing coverage — guarantees
|
||||
// the mounted span physically reaches the viewport bottom regardless of how
|
||||
// small items actually are (at the cost of over-mounting when items are
|
||||
// larger; overscan absorbs that).
|
||||
const PESSIMISTIC = 1
|
||||
// Tightest safe scrollTop bin for the useSyncExternalStore snapshot. Small
|
||||
// wheel ticks that don't cross a bin short-circuit React's commit entirely;
|
||||
// Ink keeps painting via ScrollBox.forceRender + direct scrollTop reads.
|
||||
// Half of OVERSCAN keeps ≥20 rows of cushion before the mounted range
|
||||
// would actually need to shift.
|
||||
const QUANTUM = OVERSCAN >> 1
|
||||
// Renders to keep the mount range frozen after width change (heights scaled
|
||||
// but not yet re-measured). Render #1 skips measurement so pre-resize Yoga
|
||||
// doesn't poison the scaled cache; render #2's useLayoutEffect captures
|
||||
// post-resize heights; render #3 recomputes range with accurate data.
|
||||
const FREEZE_RENDERS = 2
|
||||
// Cap on NEW items mounted per commit when scrolling fast. Without this,
|
||||
// a single PageUp into unmeasured territory mounts ~190 rows with
|
||||
// PESSIMISTIC=1 coverage — each row running marked lexer + syntax
|
||||
// highlighting for ~3ms = ~600ms sync block. Sliding toward the target
|
||||
// over several commits keeps per-commit mount cost bounded.
|
||||
const SLIDE_STEP = 25
|
||||
|
||||
const NOOP = () => {}
|
||||
|
||||
const upperBound = (arr: ArrayLike<number>, target: number) => {
|
||||
let lo = 0
|
||||
let hi = arr.length
|
||||
|
||||
while (lo < hi) {
|
||||
const mid = (lo + hi) >> 1
|
||||
|
||||
arr[mid]! <= target ? (lo = mid + 1) : (hi = mid)
|
||||
}
|
||||
|
||||
return lo
|
||||
}
|
||||
|
||||
export const shouldSetVirtualClamp = ({
|
||||
itemCount,
|
||||
@@ -29,19 +64,6 @@ export const shouldSetVirtualClamp = ({
|
||||
viewportHeight: number
|
||||
}) => itemCount > 0 && viewportHeight > 0 && !sticky && !liveTailActive
|
||||
|
||||
const upperBound = (arr: number[], target: number) => {
|
||||
let lo = 0
|
||||
let hi = arr.length
|
||||
|
||||
while (lo < hi) {
|
||||
const mid = (lo + hi) >> 1
|
||||
|
||||
arr[mid]! <= target ? (lo = mid + 1) : (hi = mid)
|
||||
}
|
||||
|
||||
return lo
|
||||
}
|
||||
|
||||
export function useVirtualHistory(
|
||||
scrollRef: RefObject<ScrollBoxHandle | null>,
|
||||
items: readonly { key: string }[],
|
||||
@@ -57,15 +79,28 @@ export function useVirtualHistory(
|
||||
const nodes = useRef(new Map<string, unknown>())
|
||||
const heights = useRef(new Map<string, number>())
|
||||
const refs = useRef(new Map<string, (el: unknown) => void>())
|
||||
const [ver, setVer] = useState(0)
|
||||
// Bump whenever heightCache mutates so offsets rebuild on next read.
|
||||
// Ref (not state) — checked during render phase, zero extra commits.
|
||||
const offsetVersion = useRef(0)
|
||||
// Cached offsets: reused Float64Array keyed on (itemCount, version) so we
|
||||
// only rebuild when something actually changed. Previous approach allocated
|
||||
// a fresh Array(n+1) every render — at n=10k that's ~80KB/render of GC
|
||||
// pressure during streaming.
|
||||
const offsetsCache = useRef<{ arr: Float64Array; n: number; version: number }>({
|
||||
arr: new Float64Array(0),
|
||||
n: -1,
|
||||
version: -1
|
||||
})
|
||||
const [hasScrollRef, setHasScrollRef] = useState(false)
|
||||
const metrics = useRef({ sticky: true, top: 0, vp: 0 })
|
||||
const lastScrollTopRef = useRef(0)
|
||||
|
||||
// Width change: scale cached heights (not clear — clearing forces a
|
||||
// pessimistic back-walk mounting ~190 rows at once, each a fresh
|
||||
// marked.lexer + syntax highlight ≈ 3ms). Freeze mount range for 2
|
||||
// renders so warm memos survive; skip one measurement so useLayoutEffect
|
||||
// doesn't poison the scaled cache with pre-resize Yoga heights.
|
||||
// Width change: scale cached heights by oldCols/newCols instead of clearing
|
||||
// (clearing forces a pessimistic back-walk mounting ~190 rows at once, each
|
||||
// a fresh marked.lexer + syntax highlight ≈ 3ms). Freeze the mount range
|
||||
// for 2 renders so warm memos survive; skip one measurement pass so
|
||||
// useLayoutEffect doesn't poison the scaled cache with pre-resize Yoga
|
||||
// heights.
|
||||
const prevColumns = useRef(columns)
|
||||
const skipMeasurement = useRef(false)
|
||||
const prevRange = useRef<null | readonly [number, number]>(null)
|
||||
@@ -80,6 +115,7 @@ export function useVirtualHistory(
|
||||
heights.current.set(k, Math.max(1, Math.round(h * ratio)))
|
||||
}
|
||||
|
||||
offsetVersion.current++
|
||||
skipMeasurement.current = true
|
||||
freezeRenders.current = FREEZE_RENDERS
|
||||
}
|
||||
@@ -88,11 +124,18 @@ export function useVirtualHistory(
|
||||
setHasScrollRef(Boolean(scrollRef.current))
|
||||
}, [scrollRef])
|
||||
|
||||
// Quantized snapshot: same-bin scrolls (most wheel ticks) produce the same
|
||||
// number → React.Object.is short-circuits the commit entirely. sticky state
|
||||
// is folded in via the sign bit so sticky→broken transitions also trigger.
|
||||
// Uses the TARGET (committed + pendingDelta), not committed scrollTop, so
|
||||
// scrollBy notifications immediately remount for the destination before
|
||||
// Ink's drain frames need the children.
|
||||
const subscribe = useCallback(
|
||||
(cb: () => void) => (hasScrollRef ? scrollRef.current?.subscribe(cb) : null) ?? NOOP,
|
||||
[hasScrollRef, scrollRef]
|
||||
)
|
||||
useSyncExternalStore(
|
||||
useCallback(
|
||||
(cb: () => void) => (hasScrollRef ? scrollRef.current?.subscribe(cb) : null) ?? (() => () => {}),
|
||||
[hasScrollRef, scrollRef]
|
||||
),
|
||||
subscribe,
|
||||
() => {
|
||||
const s = scrollRef.current
|
||||
|
||||
@@ -100,9 +143,10 @@ export function useVirtualHistory(
|
||||
return NaN
|
||||
}
|
||||
|
||||
const b = Math.floor((s.getScrollTop() + s.getPendingDelta()) / QUANTUM)
|
||||
const target = s.getScrollTop() + s.getPendingDelta()
|
||||
const bin = Math.floor(target / QUANTUM)
|
||||
|
||||
return s.isSticky() ? -b - 1 : b
|
||||
return s.isSticky() ? ~bin : bin
|
||||
},
|
||||
() => NaN
|
||||
)
|
||||
@@ -121,26 +165,33 @@ export function useVirtualHistory(
|
||||
}
|
||||
|
||||
if (dirty) {
|
||||
setVer(v => v + 1)
|
||||
offsetVersion.current++
|
||||
}
|
||||
}, [items])
|
||||
|
||||
const offsets = useMemo(() => {
|
||||
void ver
|
||||
const out = new Array<number>(items.length + 1).fill(0)
|
||||
// Offsets: Float64Array reused across renders, invalidated by offsetVersion
|
||||
// bumps from heightCache writers (measureRef, resize-scale, GC). Binary
|
||||
// search tolerates either monotone source, so no need to rebuild unless
|
||||
// something changed.
|
||||
const n = items.length
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
out[i + 1] = out[i]! + Math.max(1, Math.floor(heights.current.get(items[i]!.key) ?? estimate))
|
||||
if (offsetsCache.current.version !== offsetVersion.current || offsetsCache.current.n !== n) {
|
||||
const arr = offsetsCache.current.arr.length >= n + 1 ? offsetsCache.current.arr : new Float64Array(n + 1)
|
||||
|
||||
arr[0] = 0
|
||||
|
||||
for (let i = 0; i < n; i++) {
|
||||
arr[i + 1] = arr[i]! + Math.max(1, Math.floor(heights.current.get(items[i]!.key) ?? estimate))
|
||||
}
|
||||
|
||||
return out
|
||||
}, [estimate, items, ver])
|
||||
offsetsCache.current = { arr, n, version: offsetVersion.current }
|
||||
}
|
||||
|
||||
const n = items.length
|
||||
const offsets = offsetsCache.current.arr
|
||||
const total = offsets[n] ?? 0
|
||||
const top = Math.max(0, scrollRef.current?.getScrollTop() ?? 0)
|
||||
const pending = scrollRef.current?.getPendingDelta() ?? 0
|
||||
const target = Math.max(0, top + pending)
|
||||
const pendingDelta = scrollRef.current?.getPendingDelta() ?? 0
|
||||
const target = Math.max(0, top + pendingDelta)
|
||||
const vp = Math.max(0, scrollRef.current?.getViewportHeight() ?? 0)
|
||||
const sticky = scrollRef.current?.isSticky() ?? true
|
||||
const recentManual = Date.now() - (scrollRef.current?.getLastManualScrollAt() ?? 0) < 1200
|
||||
@@ -168,9 +219,22 @@ export function useVirtualHistory(
|
||||
start--
|
||||
}
|
||||
} else {
|
||||
const lo = Math.max(0, Math.min(top, target) - overscan)
|
||||
const hi = Math.max(top, target) + vp + overscan
|
||||
// User scrolled up. Span [committed..target] so every drain frame is
|
||||
// covered. Claude-code caps the span at 3×viewport so pendingDelta
|
||||
// growing unbounded (MX Master free-spin) doesn't blow the mount
|
||||
// budget; the clamp (setClampBounds) shows edge-of-mounted content
|
||||
// during catch-up.
|
||||
const MAX_SPAN = vp * 3
|
||||
const rawLo = Math.min(top, target)
|
||||
const rawHi = Math.max(top, target)
|
||||
const span = rawHi - rawLo
|
||||
const clampedLo = span > MAX_SPAN ? (pendingDelta < 0 ? rawHi - MAX_SPAN : rawLo) : rawLo
|
||||
const clampedHi = clampedLo + Math.min(span, MAX_SPAN)
|
||||
const lo = Math.max(0, clampedLo - overscan)
|
||||
const hi = clampedHi + vp + overscan
|
||||
|
||||
// Binary search — offsets is monotone. Linear walk was O(n) at n=10k+,
|
||||
// ~2ms per render during scroll.
|
||||
start = Math.max(0, Math.min(n - 1, upperBound(offsets, lo) - 1))
|
||||
end = Math.max(start + 1, Math.min(n, upperBound(offsets, hi)))
|
||||
}
|
||||
@@ -180,17 +244,144 @@ export function useVirtualHistory(
|
||||
sticky ? (start = Math.max(0, end - maxMounted)) : (end = Math.min(n, start + maxMounted))
|
||||
}
|
||||
|
||||
// Coverage guarantee: ensure sum(real or pessimistic heights) ≥
|
||||
// viewportH + 2*overscan so the viewport is physically covered even when
|
||||
// items are tiny. Pessimistic because uncached items use a floor of 1 —
|
||||
// over-mounts when items are large, never leaves blank spacer showing.
|
||||
if (n > 0 && vp > 0 && !frozenRange) {
|
||||
const needed = vp + 2 * overscan
|
||||
let coverage = 0
|
||||
|
||||
for (let i = start; i < end; i++) {
|
||||
coverage += heights.current.get(items[i]!.key) ?? PESSIMISTIC
|
||||
}
|
||||
|
||||
if (sticky) {
|
||||
const minStart = Math.max(0, end - maxMounted)
|
||||
|
||||
while (start > minStart && coverage < needed) {
|
||||
start--
|
||||
coverage += heights.current.get(items[start]!.key) ?? PESSIMISTIC
|
||||
}
|
||||
} else {
|
||||
const maxEnd = Math.min(n, start + maxMounted)
|
||||
|
||||
while (end < maxEnd && coverage < needed) {
|
||||
coverage += heights.current.get(items[end]!.key) ?? PESSIMISTIC
|
||||
end++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Slide cap: limit how many NEW items mount this commit. Gates on scroll
|
||||
// VELOCITY (|scrollTop delta since last commit| + |pendingDelta| >
|
||||
// 2×viewport — key-repeat PageUp moves ~viewport/2 per press). Covers
|
||||
// both scrollBy (pendingDelta) and scrollTo (direct write). Normal single
|
||||
// PageUp skips this; the clamp holds the viewport at the mounted edge
|
||||
// during catch-up so there's no blank screen. Only caps range GROWTH;
|
||||
// shrinking is unbounded.
|
||||
if (!frozenRange && prevRange.current && vp > 0) {
|
||||
const velocity = Math.abs(top - lastScrollTopRef.current) + Math.abs(pendingDelta)
|
||||
|
||||
if (velocity > vp * 2) {
|
||||
const [pS, pE] = prevRange.current
|
||||
|
||||
if (start < pS - SLIDE_STEP) {
|
||||
start = pS - SLIDE_STEP
|
||||
}
|
||||
|
||||
if (end > pE + SLIDE_STEP) {
|
||||
end = pE + SLIDE_STEP
|
||||
}
|
||||
|
||||
// A large jump past the capped end can invert (start > end); mount
|
||||
// SLIDE_STEP items from the new start so the viewport isn't blank
|
||||
// during catch-up.
|
||||
if (start > end) {
|
||||
end = Math.min(start + SLIDE_STEP, n)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lastScrollTopRef.current = top
|
||||
|
||||
if (freezeRenders.current > 0) {
|
||||
freezeRenders.current--
|
||||
} else {
|
||||
prevRange.current = [start, end]
|
||||
}
|
||||
|
||||
// Time-slice range growth via useDeferredValue. Urgent render keeps Ink
|
||||
// painting with the OLD range (all memo hits, fast); deferred render
|
||||
// transitions to the NEW range (fresh mounts: Md, syntax highlight) in a
|
||||
// non-blocking background commit. The clamp (setClampBounds) pins the
|
||||
// viewport to the mounted edge so there's no visual artifact from the
|
||||
// deferred range lagging briefly. Only deferral range GROWTH — shrinking
|
||||
// is cheap (unmount = remove fiber, no parse).
|
||||
const dStart = useDeferredValue(start)
|
||||
const dEnd = useDeferredValue(end)
|
||||
let effStart = start < dStart ? dStart : start
|
||||
let effEnd = end > dEnd ? dEnd : end
|
||||
|
||||
// Inverted range (large jump with deferred value lagging) or sticky snap
|
||||
// (scrollToBottom needs the tail mounted NOW so maxScroll lands on content,
|
||||
// not bottomSpacer) — skip deferral.
|
||||
if (effStart > effEnd || sticky) {
|
||||
effStart = start
|
||||
effEnd = end
|
||||
}
|
||||
|
||||
// Scrolling DOWN — bypass effEnd deferral so the tail mounts immediately.
|
||||
// Without this, the clamp holds scrollTop short of the real bottom and
|
||||
// the user feels "stuck before bottom". effStart stays deferred so scroll-
|
||||
// UP keeps time-slicing (older messages parse on mount).
|
||||
if (pendingDelta > 0) {
|
||||
effEnd = end
|
||||
}
|
||||
|
||||
// Final O(viewport) enforcement. Deferred+bypass combinations above can
|
||||
// leak: during sustained PageUp, concurrent mode interleaves dStart updates
|
||||
// with effEnd=end bypasses across commits and the effective window drifts
|
||||
// wider than either bound alone. Trim the far edge by viewport position
|
||||
// (not pendingDelta direction — that flips mid-settle under concurrent
|
||||
// scheduling and yanks scrollTop).
|
||||
if (effEnd - effStart > maxMounted && vp > 0) {
|
||||
const mid = (offsets[effStart]! + offsets[effEnd]!) / 2
|
||||
|
||||
if (top < mid) {
|
||||
effEnd = effStart + maxMounted
|
||||
} else {
|
||||
effStart = effEnd - maxMounted
|
||||
}
|
||||
}
|
||||
|
||||
const measureRef = useCallback((key: string) => {
|
||||
let fn = refs.current.get(key)
|
||||
|
||||
if (!fn) {
|
||||
fn = (el: unknown) => (el ? nodes.current.set(key, el) : nodes.current.delete(key))
|
||||
fn = (el: unknown) => {
|
||||
if (el) {
|
||||
nodes.current.set(key, el)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Measure-at-unmount: the yogaNode is still valid here (reconciler
|
||||
// calls ref(null) before removeChild → freeRecursive), so we grab
|
||||
// the final height before WASM release. Without this, items
|
||||
// scrolled out during fast pan keep a stale estimate in heightCache
|
||||
// and offset math drifts until the next mount/remount cycle.
|
||||
const existing = nodes.current.get(key) as MeasuredNode | undefined
|
||||
const h = Math.ceil(existing?.yogaNode?.getComputedHeight?.() ?? 0)
|
||||
|
||||
if (h > 0 && heights.current.get(key) !== h) {
|
||||
heights.current.set(key, h)
|
||||
offsetVersion.current++
|
||||
}
|
||||
|
||||
nodes.current.delete(key)
|
||||
}
|
||||
|
||||
refs.current.set(key, fn)
|
||||
}
|
||||
|
||||
@@ -202,25 +393,33 @@ export function useVirtualHistory(
|
||||
let dirty = false
|
||||
|
||||
// Give the renderer the mounted-row coverage for passive scroll clamping.
|
||||
// Without this, burst wheel/page scroll can race past the React commit that
|
||||
// updates the virtual range and paint spacer-only frames.
|
||||
// Clamp MUST use the EFFECTIVE (deferred) range, not the immediate one.
|
||||
// During fast scroll, immediate [start,end] may already cover the new
|
||||
// scrollTop position, but children still render at the deferred range.
|
||||
// If clamp used immediate bounds, render-node-to-output's drain-gate
|
||||
// would drain past the deferred children's span → viewport lands in
|
||||
// spacer → white flash.
|
||||
if (s && shouldSetVirtualClamp({ itemCount: n, liveTailActive, sticky, viewportHeight: vp })) {
|
||||
const min = offsets[start] ?? 0
|
||||
const max = Math.max(min, (offsets[end] ?? total) - vp)
|
||||
s.setClampBounds(min, max)
|
||||
const effTopSpacer = offsets[effStart] ?? 0
|
||||
const effBottom = offsets[effEnd] ?? total
|
||||
// At effEnd=n there's no bottomSpacer — use Infinity so render-node-
|
||||
// to-output's own Math.min(cur, maxScroll) governs. Using offsets[n]
|
||||
// here would bake in heightCache (one render behind Yoga), and during
|
||||
// streaming the tail item's cached height lags its real height —
|
||||
// sticky-break would then clamp below the real max and push
|
||||
// streaming text off-viewport.
|
||||
const clampMin = effStart === 0 ? 0 : effTopSpacer
|
||||
const clampMax = effEnd === n ? Infinity : Math.max(effTopSpacer, effBottom - vp)
|
||||
|
||||
s.setClampBounds(clampMin, clampMax)
|
||||
} else {
|
||||
// Sticky bottom often has live, non-virtualized tail content after the
|
||||
// virtual transcript (streaming answer / thinking / tools). A clamp based
|
||||
// only on virtual history would cap rendering before that tail and make
|
||||
// live thinking appear to vanish. No burst-scroll clamp is needed while
|
||||
// sticky anyway.
|
||||
s?.setClampBounds(undefined, undefined)
|
||||
}
|
||||
|
||||
if (skipMeasurement.current) {
|
||||
skipMeasurement.current = false
|
||||
} else {
|
||||
for (let i = start; i < end; i++) {
|
||||
for (let i = effStart; i < effEnd; i++) {
|
||||
const k = items[i]?.key
|
||||
|
||||
if (!k) {
|
||||
@@ -254,17 +453,17 @@ export function useVirtualHistory(
|
||||
}
|
||||
|
||||
if (dirty) {
|
||||
setVer(v => v + 1)
|
||||
offsetVersion.current++
|
||||
}
|
||||
}, [end, hasScrollRef, items, liveTailActive, n, offsets, recentManual, scrollRef, start, sticky, total, vp])
|
||||
})
|
||||
|
||||
return {
|
||||
bottomSpacer: Math.max(0, total - (offsets[end] ?? total)),
|
||||
end,
|
||||
bottomSpacer: Math.max(0, total - (offsets[effEnd] ?? total)),
|
||||
end: effEnd,
|
||||
measureRef,
|
||||
offsets,
|
||||
start,
|
||||
topSpacer: offsets[start] ?? 0
|
||||
start: effStart,
|
||||
topSpacer: offsets[effStart] ?? 0
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import type { Msg, TodoItem } from '../types.js'
|
||||
|
||||
export const countPendingTodos = (todos: readonly TodoItem[]) =>
|
||||
todos.filter(todo => todo.status === 'in_progress' || todo.status === 'pending').length
|
||||
|
||||
export const isTodoDone = (todos: readonly TodoItem[]) =>
|
||||
todos.length > 0 && todos.every(todo => todo.status === 'completed' || todo.status === 'cancelled')
|
||||
|
||||
|
||||
@@ -117,6 +117,7 @@ export interface Msg {
|
||||
toolTokens?: number
|
||||
tools?: string[]
|
||||
todos?: TodoItem[]
|
||||
todoIncomplete?: boolean
|
||||
}
|
||||
|
||||
export type Role = 'assistant' | 'system' | 'tool' | 'user'
|
||||
|
||||
Reference in New Issue
Block a user