From 11f476539ddb1e2575499d56b1ec120c93ff6c56 Mon Sep 17 00:00:00 2001 From: P-Theo Date: Wed, 23 Sep 2026 13:46:26 +0300 Subject: [PATCH] fix(tui): use public theme hues for spinner colors @opencode/theme is an optional peer, so its missing types masked the invalid dynamic hue lookup under skipLibCheck. Pin it alongside @opencode/plugin, use only public semantic hues, and retain the categorical fallback without raising the runtime compatibility floor. --- docs/CONFIGURATION.md | 2 + docs/DEVELOPMENT.md | 4 +- docs/RELEASE.md | 2 +- package-lock.json | 481 +++++++++++++++++++++----------- package.json | 3 +- scripts/check-compatibility.mjs | 2 +- src/footer.tsx | 15 +- tests/footer.test.ts | 38 ++- 8 files changed, 369 insertions(+), 178 deletions(-) diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md index de1f13a..6dcd1cd 100644 --- a/docs/CONFIGURATION.md +++ b/docs/CONFIGURATION.md @@ -34,6 +34,8 @@ The session counters come from OpenCode's cumulative session token and cost fiel | `tps` | A live token-throughput estimate while output is observable, then the frozen average for the completed run. It appears only once a rate exists. | | `bgagent` | The number of running direct subagents of the session; it hides when the count is zero. | +> **Spinner color compatibility:** OpenCode 2.0.15 exposes only `accent`, `interactive`, and `neutral` hues in its public theme types. The spinner uses those hues or a hex agent color directly and falls back to the theme's categorical palette for other named colors. On older OpenCode 2 versions, the spinner color may not match the agent's color. Animation is unaffected. + While a run streams, TPS (tokens per second) is estimated from observed UTF-8 bytes using `bytesPerToken`. Once a step reports its output and reasoning usage, those exact tokens replace that step's estimate, and at run end the cumulative average stays frozen until the next prompt. The label always reads `~N t/s`, so TPS has no appearance setting. The tracker is the same engine as the standalone [opencode2-tps](https://github.com/P-Theo/opencode2-tps) plugin, minus its `display` option. See [TPS rate tuning](#tps-rate-tuning) for `refreshHz` and `bytesPerToken`. ## Appearance options diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index ce72ad0..78147e8 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -11,7 +11,7 @@ Load the checkout using [Run from source](../README.md#run-from-source). See [Ar | `npm run lint` | Run oxlint. | | `npm run check` | Check TypeScript types. | | `npm test` | Run the Bun tests. | -| `npm run check:compatibility` | Check the OpenCode SDK pin and README version floor. | +| `npm run check:compatibility` | Check that the OpenCode plugin and theme pins match and that the README minimum is no newer than the pin. | | `npm run build` | Rebuild `dist/`. | | `npm pack` | Run lint, type-checks, tests, and build, then create the publishable tarball. | @@ -21,6 +21,6 @@ Tests live in [tests/](../tests), mostly named after the source module. `layout- Local directory entries load the root `tui.tsx`. Published packages load `dist/tui.js`, built from `src/footer.tsx` by [scripts/build.mjs](../scripts/build.mjs). Test the packed artifact when changing runtime code or packaging. CI also imports it against the host's bundled runtime versions. -Keep `options.schema.json` aligned with `src/options.ts`. Update the pinned `@opencode/plugin` version and README compatibility floor together when a change requires a newer host. +Keep `options.schema.json` aligned with `src/options.ts`. Keep `@opencode/plugin` and `@opencode/theme` pinned to the same version so TypeScript checks the host's theme types. Raise the README minimum only when runtime behavior requires a newer host. A typings-only bump does not change it. The compatibility script checks these version labels, not runtime behavior. See [Release](RELEASE.md) for publishing. diff --git a/docs/RELEASE.md b/docs/RELEASE.md index b025dcb..511c9ad 100644 --- a/docs/RELEASE.md +++ b/docs/RELEASE.md @@ -4,7 +4,7 @@ Maintainer runbook. A path entry loads the `tui.tsx` source through the host's transform; an installed package loads the pre-built `dist/tui.js` through the `exports` subpath. Those are two different code paths, so the bundle gets tested before it goes out. -The pinned `@opencode/plugin` version is the build and test baseline. Move it in the same pull request as the fix or feature that needs it; CI validates the pin format and README compatibility floor, then checks GitHub for the matching official `anomalyco/opencode` release tag. +The pinned `@opencode/plugin` version is the build and test baseline. Pin `@opencode/theme` to the same version so theme types remain available to TypeScript. Move both pins in the same pull request as the change that needs them. CI checks their parity and the README compatibility floor, then looks up the matching official `anomalyco/opencode` release tag. A docs-only release skips steps 1 to 5. The bundle is unchanged, and CI already packs and imports the artifact on every push to `main`. Go straight to step 6. A README change that matters on the package page needs a patch release to reach it, because npmjs.com only refreshes the rendered README when a new version is published. diff --git a/package-lock.json b/package-lock.json index af3ca6b..e13524d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,8 @@ "devDependencies": { "@babel/core": "^7.29.7", "@babel/preset-typescript": "^7.29.7", - "@opencode/plugin": "2.0.12", + "@opencode/plugin": "2.0.15", + "@opencode/theme": "2.0.15", "@opentui/core": "^0.5.11", "@opentui/solid": "^0.5.10", "@oxlint/plugins": "^1.82.0", @@ -206,18 +207,18 @@ } }, "node_modules/@aws-sdk/core": { - "version": "3.978.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.978.0.tgz", - "integrity": "sha512-2yX9LUmxPklVjSGTb8dfnWRJSiFQ3TeH2nn7G1mdKHTfnabzF0+gfrS8rYfLWmZrQ8A3mEcxMJjRc51dL5KWaA==", + "version": "3.978.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.978.1.tgz", + "integrity": "sha512-LbY9aGsEiznDWmUc30Nwv3aIX/+dbwTx8KfS0yOC3NPYMO+O91e6jkT1azf34FwjOndq8/Q+RcVVZz5xnerwdg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.974.5", - "@aws-sdk/xml-builder": "^3.972.40", + "@aws-sdk/types": "^3.974.6", + "@aws-sdk/xml-builder": "^3.972.41", "@aws/lambda-invoke-store": "^0.3.0", - "@smithy/core": "^3.33.3", - "@smithy/signature-v4": "^5.6.12", - "@smithy/types": "^4.17.2", + "@smithy/core": "^3.35.0", + "@smithy/signature-v4": "^5.7.3", + "@smithy/types": "^4.19.0", "bowser": "^2.11.0", "tslib": "^2.6.2" }, @@ -226,16 +227,16 @@ } }, "node_modules/@aws-sdk/credential-provider-cognito-identity": { - "version": "3.972.70", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-cognito-identity/-/credential-provider-cognito-identity-3.972.70.tgz", - "integrity": "sha512-KlU89w6Hmb4oZB5zFz/MNIhPOBQGVE7KrDr3BTPCwC4W+q566YH8tGNsAML781LKATtqmNCGFry8XvsJ2XPusg==", + "version": "3.972.71", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-cognito-identity/-/credential-provider-cognito-identity-3.972.71.tgz", + "integrity": "sha512-rsxsi8tF3vMGdWdaHQNXceHBdb49442yR2Pe08K+Phoc/yICbptdsHwL25Wf0z5+hjhSS5a+hDJHP7Xul4zvHQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/nested-clients": "^3.997.45", - "@aws-sdk/types": "^3.974.5", - "@smithy/core": "^3.33.3", - "@smithy/types": "^4.17.2", + "@aws-sdk/nested-clients": "^3.997.46", + "@aws-sdk/types": "^3.974.6", + "@smithy/core": "^3.35.0", + "@smithy/types": "^4.19.0", "tslib": "^2.6.2" }, "engines": { @@ -243,16 +244,16 @@ } }, "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.972.71", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.71.tgz", - "integrity": "sha512-JN+JHruYZw3GUZB8YGAlDk4wTDPOEAEEdEzj5nS0xodWR4smzHsN7PnK2j6IeOsDIj2aqua5DSbhXl9Gtf90FQ==", + "version": "3.972.72", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.72.tgz", + "integrity": "sha512-xTKO/FWJPozTIXbozVnVGoNBhaGba8TBcx+KyUjRVeOlXE+dUc7GTR1cLvu0uTdIdmemzaFbqqCshXeZA1fZew==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.978.0", - "@aws-sdk/types": "^3.974.5", - "@smithy/core": "^3.33.3", - "@smithy/types": "^4.17.2", + "@aws-sdk/core": "^3.978.1", + "@aws-sdk/types": "^3.974.6", + "@smithy/core": "^3.35.0", + "@smithy/types": "^4.19.0", "tslib": "^2.6.2" }, "engines": { @@ -260,18 +261,18 @@ } }, "node_modules/@aws-sdk/credential-provider-http": { - "version": "3.972.73", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.73.tgz", - "integrity": "sha512-uyYYnJOnlis8uQzaYGPd7N1JoioCoNpXgnkXYixsWJXHXgXyYi8WXJSDfofxJeWfQIGWLe2Nwyq60Uc7MZdVOg==", + "version": "3.972.74", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.74.tgz", + "integrity": "sha512-u91E/hT8f4d1xy0Jl7VG4nVKJ3lxbrZkoBTeSVoJdWBiSEUMwMS/9+e0H/aJVQV//Lt5wuzP+E69v4aRSsNTmw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.978.0", - "@aws-sdk/types": "^3.974.5", - "@smithy/core": "^3.33.3", - "@smithy/fetch-http-handler": "^5.7.2", - "@smithy/node-http-handler": "^4.11.3", - "@smithy/types": "^4.17.2", + "@aws-sdk/core": "^3.978.1", + "@aws-sdk/types": "^3.974.6", + "@smithy/core": "^3.35.0", + "@smithy/fetch-http-handler": "^5.8.0", + "@smithy/node-http-handler": "^4.12.1", + "@smithy/types": "^4.19.0", "tslib": "^2.6.2" }, "engines": { @@ -279,24 +280,24 @@ } }, "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.973.16", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.973.16.tgz", - "integrity": "sha512-i++ly+0Uxa+u3ebSSyr0S/3CFhFJDxCXT3+Zj+mW2bXenEx5bKGCdTIKFu39SgXBNhWDjex/8cXUx9MUTMCrTw==", + "version": "3.973.17", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.973.17.tgz", + "integrity": "sha512-ged4KXdBkvIC81bLvNHHuQKdKak/VXhQTR1NWYTTqW0474nlmsxy9O/vlgTIohDDWH3xpBdtVMZRyjb+DnocDA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.978.0", - "@aws-sdk/credential-provider-env": "^3.972.71", - "@aws-sdk/credential-provider-http": "^3.972.73", - "@aws-sdk/credential-provider-login": "^3.972.78", - "@aws-sdk/credential-provider-process": "^3.972.71", - "@aws-sdk/credential-provider-sso": "^3.973.15", - "@aws-sdk/credential-provider-web-identity": "^3.972.77", - "@aws-sdk/nested-clients": "^3.997.45", - "@aws-sdk/types": "^3.974.5", - "@smithy/core": "^3.33.3", - "@smithy/credential-provider-imds": "^4.4.16", - "@smithy/types": "^4.17.2", + "@aws-sdk/core": "^3.978.1", + "@aws-sdk/credential-provider-env": "^3.972.72", + "@aws-sdk/credential-provider-http": "^3.972.74", + "@aws-sdk/credential-provider-login": "^3.972.79", + "@aws-sdk/credential-provider-process": "^3.972.72", + "@aws-sdk/credential-provider-sso": "^3.973.16", + "@aws-sdk/credential-provider-web-identity": "^3.972.78", + "@aws-sdk/nested-clients": "^3.997.46", + "@aws-sdk/types": "^3.974.6", + "@smithy/core": "^3.35.0", + "@smithy/credential-provider-imds": "^4.5.2", + "@smithy/types": "^4.19.0", "tslib": "^2.6.2" }, "engines": { @@ -304,17 +305,17 @@ } }, "node_modules/@aws-sdk/credential-provider-login": { - "version": "3.972.78", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.78.tgz", - "integrity": "sha512-eUtswnXu0+Ii9ieRK+0L7aPFV3Z/dnW2VntJzjBP9xs8s+8p5nBNuymIXtXwZ+5r5+XJP3e32nMkuZ/r0HozEA==", + "version": "3.972.79", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.79.tgz", + "integrity": "sha512-L+Z85anONJd8MaiuraO4wRxATCdEejBZ3K3eymzWI5JPXa9sOS9CkIm72PBKqXKX+Z9p9NGMX5AIMXm0LEflgw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.978.0", - "@aws-sdk/nested-clients": "^3.997.45", - "@aws-sdk/types": "^3.974.5", - "@smithy/core": "^3.33.3", - "@smithy/types": "^4.17.2", + "@aws-sdk/core": "^3.978.1", + "@aws-sdk/nested-clients": "^3.997.46", + "@aws-sdk/types": "^3.974.6", + "@smithy/core": "^3.35.0", + "@smithy/types": "^4.19.0", "tslib": "^2.6.2" }, "engines": { @@ -322,22 +323,22 @@ } }, "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.972.83", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.83.tgz", - "integrity": "sha512-jdso7ejzfRnatxMUZK4S/U6KbaDPCvfIV4XL+IQAPFDBt5rj5Fq595euqlK8Le4lNCMFR9oUpt+1l0aMgaayOQ==", + "version": "3.972.84", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.84.tgz", + "integrity": "sha512-oHt854odINVwzwsh+c5x69j0ajm4DbqqqVJ+O1ECsCIZeMDAbzFpXItaqP7UZstJj/ATdTk/KFSH0LaNAgV+kA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/credential-provider-env": "^3.972.71", - "@aws-sdk/credential-provider-http": "^3.972.73", - "@aws-sdk/credential-provider-ini": "^3.973.16", - "@aws-sdk/credential-provider-process": "^3.972.71", - "@aws-sdk/credential-provider-sso": "^3.973.15", - "@aws-sdk/credential-provider-web-identity": "^3.972.77", - "@aws-sdk/types": "^3.974.5", - "@smithy/core": "^3.33.3", - "@smithy/credential-provider-imds": "^4.4.16", - "@smithy/types": "^4.17.2", + "@aws-sdk/credential-provider-env": "^3.972.72", + "@aws-sdk/credential-provider-http": "^3.972.74", + "@aws-sdk/credential-provider-ini": "^3.973.17", + "@aws-sdk/credential-provider-process": "^3.972.72", + "@aws-sdk/credential-provider-sso": "^3.973.16", + "@aws-sdk/credential-provider-web-identity": "^3.972.78", + "@aws-sdk/types": "^3.974.6", + "@smithy/core": "^3.35.0", + "@smithy/credential-provider-imds": "^4.5.2", + "@smithy/types": "^4.19.0", "tslib": "^2.6.2" }, "engines": { @@ -345,16 +346,16 @@ } }, "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.972.71", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.71.tgz", - "integrity": "sha512-lYmXJa4gvq4xN1lrT5NiP5vIYYKcGWAdj8y+8o6dlcateB5eF3Dn8DtmjjHKfMBrTPAMr2pebIiX/UOj8c1/UA==", + "version": "3.972.72", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.72.tgz", + "integrity": "sha512-rLIp2xbMjX/k9/od7APpqq1ZgXXnV0pOL1Th3ZsL8Wu0TRtBsDTVS8iPqcfRFcHakFxPvR04OSTv2ka2qOb/2A==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.978.0", - "@aws-sdk/types": "^3.974.5", - "@smithy/core": "^3.33.3", - "@smithy/types": "^4.17.2", + "@aws-sdk/core": "^3.978.1", + "@aws-sdk/types": "^3.974.6", + "@smithy/core": "^3.35.0", + "@smithy/types": "^4.19.0", "tslib": "^2.6.2" }, "engines": { @@ -362,18 +363,18 @@ } }, "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.973.15", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.973.15.tgz", - "integrity": "sha512-6Jhcf4v0pSFdjk1EW2kvzuEBKD+UZ2uNcHUIglKKLndD20YhvkL2kdmDOV5/j4mYuWWwe/a1FQ1aomU86/Cg5Q==", + "version": "3.973.16", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.973.16.tgz", + "integrity": "sha512-IGihaJfFZYacJJr/odqILCoK7W/mvrZ7cuK7ECn3sAu4vLC6u0V8bS7mCGbdugJ8Aum2tnvqmx0F2MRFp2rn9g==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.978.0", - "@aws-sdk/nested-clients": "^3.997.45", - "@aws-sdk/token-providers": "3.1129.0", - "@aws-sdk/types": "^3.974.5", - "@smithy/core": "^3.33.3", - "@smithy/types": "^4.17.2", + "@aws-sdk/core": "^3.978.1", + "@aws-sdk/nested-clients": "^3.997.46", + "@aws-sdk/token-providers": "3.1138.0", + "@aws-sdk/types": "^3.974.6", + "@smithy/core": "^3.35.0", + "@smithy/types": "^4.19.0", "tslib": "^2.6.2" }, "engines": { @@ -381,17 +382,17 @@ } }, "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.972.77", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.77.tgz", - "integrity": "sha512-uylIQSUWpfLuH2LovxEEfwzJGM/SabLOfLMg6YXu/E8jJEKUdpdILCVCQCdFvHyu/7dLJOHPMfrSwduxO56NkQ==", + "version": "3.972.78", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.78.tgz", + "integrity": "sha512-/y9WvNtlcPBGLR0qc1a+9J/xtYZfVczvLUOuXaVWylzttH7ewsxwHtjmiJSolNrVSDorIxHGHMU61CbonRkmwA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.978.0", - "@aws-sdk/nested-clients": "^3.997.45", - "@aws-sdk/types": "^3.974.5", - "@smithy/core": "^3.33.3", - "@smithy/types": "^4.17.2", + "@aws-sdk/core": "^3.978.1", + "@aws-sdk/nested-clients": "^3.997.46", + "@aws-sdk/types": "^3.974.6", + "@smithy/core": "^3.35.0", + "@smithy/types": "^4.19.0", "tslib": "^2.6.2" }, "engines": { @@ -428,19 +429,19 @@ } }, "node_modules/@aws-sdk/nested-clients": { - "version": "3.997.45", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.45.tgz", - "integrity": "sha512-mooq9Q+jLa18VoM7HouczmslZU60iiB0aKc/Ztnq/luIL1ud0z4DnYprLR/ZO1gp331S9tJctM1HZr7u6YKBXQ==", + "version": "3.997.46", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.46.tgz", + "integrity": "sha512-oRxtBcka/JGHGs9l9p9IVajGoTP8vTPmoAzdHGy4Qcy9P5vPnDf6nhIeM/COQNY9k/OahImTRaLkHftoXvfcmQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.978.0", - "@aws-sdk/signature-v4-multi-region": "^3.996.46", - "@aws-sdk/types": "^3.974.5", - "@smithy/core": "^3.33.3", - "@smithy/fetch-http-handler": "^5.7.2", - "@smithy/node-http-handler": "^4.11.3", - "@smithy/types": "^4.17.2", + "@aws-sdk/core": "^3.978.1", + "@aws-sdk/signature-v4-multi-region": "^3.996.47", + "@aws-sdk/types": "^3.974.6", + "@smithy/core": "^3.35.0", + "@smithy/fetch-http-handler": "^5.8.0", + "@smithy/node-http-handler": "^4.12.1", + "@smithy/types": "^4.19.0", "tslib": "^2.6.2" }, "engines": { @@ -448,15 +449,15 @@ } }, "node_modules/@aws-sdk/signature-v4-multi-region": { - "version": "3.996.46", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.46.tgz", - "integrity": "sha512-L+2xZTye/2T96f3lwCws0Zw6GG2JHZW9e8FpVgGBeeExSKyeoZ6CWRpBml/7DNiK/O26jrgPM9F+Ay8VkgzUWQ==", + "version": "3.996.47", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.47.tgz", + "integrity": "sha512-Zk08macMvQTHzQJCLJVkOlviVoqwYMrpXv4lmLN7b7sAbiMoOK7Go0NYdR5UeF+MW8LIbRmwrNy9u/5VvX1U5g==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.974.5", - "@smithy/signature-v4": "^5.6.12", - "@smithy/types": "^4.17.2", + "@aws-sdk/types": "^3.974.6", + "@smithy/signature-v4": "^5.7.3", + "@smithy/types": "^4.19.0", "tslib": "^2.6.2" }, "engines": { @@ -464,17 +465,17 @@ } }, "node_modules/@aws-sdk/token-providers": { - "version": "3.1129.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1129.0.tgz", - "integrity": "sha512-Sbl3rpzQdsG4ZK2zh0JWUYyZPKKorJlVOddA2T0DVbKJFrsW8J6wgnslxxUH04+WaBMr4A1HzJZvZX0xUvkniA==", + "version": "3.1138.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1138.0.tgz", + "integrity": "sha512-GpyAr0DD63YOEmYFM6Df+gJuIgC92MMTiBK4FTKfxii5MJ9ge20epR7LyroulscYlG89J+ZB2ivFDPjvfQhzdw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.978.0", - "@aws-sdk/nested-clients": "^3.997.45", - "@aws-sdk/types": "^3.974.5", - "@smithy/core": "^3.33.3", - "@smithy/types": "^4.17.2", + "@aws-sdk/core": "^3.978.1", + "@aws-sdk/nested-clients": "^3.997.46", + "@aws-sdk/types": "^3.974.6", + "@smithy/core": "^3.35.0", + "@smithy/types": "^4.19.0", "tslib": "^2.6.2" }, "engines": { @@ -482,13 +483,13 @@ } }, "node_modules/@aws-sdk/types": { - "version": "3.974.5", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.974.5.tgz", - "integrity": "sha512-LkwLL2BLbC6wNNm4JaH9mbEqBMdOZCct6VAYqhdN4U1xrWM+fUJQEfbHwQgDypapOWTRtlk25akb5afM0P8CIQ==", + "version": "3.974.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.974.6.tgz", + "integrity": "sha512-v/clNZzZnDxGyvpHMOGpJKVXFAExJzUNAAjaWGdcx8QAcXLGwTaOkw33p5SHAi0YAioK32xB3hWwOekRVfmfKg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.17.2", + "@smithy/types": "^4.19.0", "tslib": "^2.6.2" }, "engines": { @@ -509,13 +510,13 @@ } }, "node_modules/@aws-sdk/xml-builder": { - "version": "3.972.40", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.40.tgz", - "integrity": "sha512-wlFmCIGUlwF4zx/kncw+bmxTQh1HeSJq4mYV/V5cZUSJadDP3kXvGW8Rn21cimj/7y9ju+47oYWXi97vF7czaA==", + "version": "3.972.41", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.41.tgz", + "integrity": "sha512-ctjVSyCMegrWfXlx6VqzSBFI6UqmQ5ZlnfMhdLIiWmhoH8UAQxSCP5N3OpG7X3k4LnS7ou74C4mt20+bfTW2aQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.17.2", + "@smithy/types": "^4.19.0", "tslib": "^2.6.2" }, "engines": { @@ -1625,14 +1626,14 @@ } }, "node_modules/@opencode/ai": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/@opencode/ai/-/ai-2.0.12.tgz", - "integrity": "sha512-3ODFv7a3mKHZ92uUgJDKUM8wY2akNwpvC6YDck0U2gwkc6zfXwNXXpWAEjhdlHPujmQub6dNb0FS5St4oKpSNQ==", + "version": "2.0.15", + "resolved": "https://registry.npmjs.org/@opencode/ai/-/ai-2.0.15.tgz", + "integrity": "sha512-dY3HG6bp+UMJOi+m1nfzvQaTPpCSmMNDswAOLQq/MJghGrD54WrcEC4IhAQ1hJY7b/hhQYOzPAMeJsvF4aRYYQ==", "dev": true, "license": "MIT", "dependencies": { "@aws-sdk/credential-providers": "3.1057.0", - "@opencode/schema": "2.0.12", + "@opencode/schema": "2.0.15", "@smithy/eventstream-codec": "4.2.14", "@smithy/util-utf8": "4.2.2", "aws4fetch": "1.0.20", @@ -1641,14 +1642,14 @@ } }, "node_modules/@opencode/client": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/@opencode/client/-/client-2.0.12.tgz", - "integrity": "sha512-WxZLWIv0Sx9NwoCDmkEqKPnn+NOpk3Kt9FWh5gMgrl1mDbCI+6EQtFOsgrhqJSrFZXMzFqgLMAiTTIYD7wZWpA==", + "version": "2.0.15", + "resolved": "https://registry.npmjs.org/@opencode/client/-/client-2.0.15.tgz", + "integrity": "sha512-Lu3Pb6Z/lJ797VryQrsdqEgVBXjtYZWOxLNZKyBUDgXhO/GpzOpXi1H1Uo4g+qWrH8LwUISDBdxHHOWJy5HcqQ==", "dev": true, "license": "MIT", "dependencies": { - "@opencode/protocol": "2.0.12", - "@opencode/schema": "2.0.12" + "@opencode/protocol": "2.0.15", + "@opencode/schema": "2.0.15" }, "peerDependencies": { "effect": "4.0.0-rc.112", @@ -1664,24 +1665,24 @@ } }, "node_modules/@opencode/plugin": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/@opencode/plugin/-/plugin-2.0.12.tgz", - "integrity": "sha512-eUO61AEruyxK6wrL82XhjRuZ5zvoWpLdhzAWbFgoLe3/8IgwrY/4SUKGEou5WgXtzh0zWNDQQqMpaVMe66n8cQ==", + "version": "2.0.15", + "resolved": "https://registry.npmjs.org/@opencode/plugin/-/plugin-2.0.15.tgz", + "integrity": "sha512-TsSXd2g/433De7ViY+5d/tlLtkZCSmYKo8ScnhtcmPVoM/U5ptQDFXEXx5psTlAfkKcISJh+5vMSOe4u3uCqUQ==", "dev": true, "license": "MIT", "dependencies": { "@ai-sdk/provider": "3.0.8", - "@opencode/ai": "2.0.12", - "@opencode/client": "2.0.12", - "@opencode/protocol": "2.0.12", - "@opencode/schema": "2.0.12", - "@opencode/util": "2.0.12", + "@opencode/ai": "2.0.15", + "@opencode/client": "2.0.15", + "@opencode/protocol": "2.0.15", + "@opencode/schema": "2.0.15", + "@opencode/util": "2.0.15", "@standard-schema/spec": "1.1.0", "effect": "4.0.0-rc.112", "zod": "4.1.8" }, "peerDependencies": { - "@opencode/theme": "2.0.12", + "@opencode/theme": "2.0.15", "@opentui/core": ">=0.5.10", "@opentui/solid": ">=0.5.10", "solid-js": ">=1.9.0" @@ -1702,20 +1703,20 @@ } }, "node_modules/@opencode/protocol": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/@opencode/protocol/-/protocol-2.0.12.tgz", - "integrity": "sha512-YYp9ElZMW8UG7cV9kSd9j3svPTEt0sTP7G4tXeQ2icbCIwFki28x+fCiuF8wBGcpYMEcLg1GrF3aEqxPAZOpWA==", + "version": "2.0.15", + "resolved": "https://registry.npmjs.org/@opencode/protocol/-/protocol-2.0.15.tgz", + "integrity": "sha512-fFi3VYdgNQzT4TswfKGul7DzseOyhA4b/hE/elUe3xxm5iki4tP/pjT8CU3rRWX8sK/75ICr36NAUhSayFtJuw==", "dev": true, "license": "MIT", "dependencies": { - "@opencode/schema": "2.0.12", + "@opencode/schema": "2.0.15", "effect": "4.0.0-rc.112" } }, "node_modules/@opencode/schema": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/@opencode/schema/-/schema-2.0.12.tgz", - "integrity": "sha512-T3RJb371gH813pdaWpAvXAgZ3GSHpPcHSkP8nRbPsSKYOlfmNId2s8Js9aLCJHeVfIKmW3+I9poa3Bx+aiiROQ==", + "version": "2.0.15", + "resolved": "https://registry.npmjs.org/@opencode/schema/-/schema-2.0.15.tgz", + "integrity": "sha512-D0JDwF4uTNmFA2MyiSnZ5DlT/t2PF1EEwVbRPulB/Ppcss/z5Fcn6VybHrapki2bKDVQu1Mnn8zBW3G6KP5I6g==", "dev": true, "license": "MIT", "dependencies": { @@ -1723,10 +1724,166 @@ "effect": "4.0.0-rc.112" } }, + "node_modules/@opencode/theme": { + "version": "2.0.15", + "resolved": "https://registry.npmjs.org/@opencode/theme/-/theme-2.0.15.tgz", + "integrity": "sha512-xmdf4xlm9IFX+8zE0Z6J9NuUdotkN27Tj1tbCmgicI3SVddHpRKTa7IATsaqzQMegKYZEVrYqNglnGRAyPTrCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@opentui/core": "0.5.10", + "effect": "4.0.0-rc.112" + } + }, + "node_modules/@opencode/theme/node_modules/@opentui/core": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/@opentui/core/-/core-0.5.10.tgz", + "integrity": "sha512-C3a2UbmefeAjIxAgm4BqjuSxKT4oqutfvYFwVvUgMxmGRHkNbBc/s7sukV0JgwcxFcV3uMFrXxo+E+BQtvuOiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "bun-ffi-structs": "0.3.1", + "diff": "9.0.0", + "marked": "17.0.1", + "string-width": "7.2.0", + "strip-ansi": "7.1.2" + }, + "optionalDependencies": { + "@opentui/core-darwin-arm64": "0.5.10", + "@opentui/core-darwin-x64": "0.5.10", + "@opentui/core-linux-arm64": "0.5.10", + "@opentui/core-linux-arm64-musl": "0.5.10", + "@opentui/core-linux-x64": "0.5.10", + "@opentui/core-linux-x64-musl": "0.5.10", + "@opentui/core-win32-arm64": "0.5.10", + "@opentui/core-win32-x64": "0.5.10" + }, + "peerDependencies": { + "web-tree-sitter": "0.25.10" + } + }, + "node_modules/@opencode/theme/node_modules/@opentui/core-darwin-arm64": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/@opentui/core-darwin-arm64/-/core-darwin-arm64-0.5.10.tgz", + "integrity": "sha512-Vyb+nTbhab8ZcRy5gg1loEEGwRcIbjAeVRIBfHBcbFDqmITBOg7x2gqJ+x/TnoOy4uwMhCmICUN2wiyREw3r1Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@opencode/theme/node_modules/@opentui/core-darwin-x64": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/@opentui/core-darwin-x64/-/core-darwin-x64-0.5.10.tgz", + "integrity": "sha512-tTFLcM7Oj1gTyhm/bUdAt3C6grZdCxPk6+/g2azcZBUlI3/62LwbeRS6HbQKFFmm+1fUmX8cq6kWrtul885mVg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@opencode/theme/node_modules/@opentui/core-linux-arm64": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/@opentui/core-linux-arm64/-/core-linux-arm64-0.5.10.tgz", + "integrity": "sha512-ncJXcgudhBf2GdJyF3xVQN/Ec+1F7GOL+pRrURmgBYSj2v1w6EyoDQFAACtPTK2c3R38W6fvZwL4JSLlm4EFXQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@opencode/theme/node_modules/@opentui/core-linux-arm64-musl": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/@opentui/core-linux-arm64-musl/-/core-linux-arm64-musl-0.5.10.tgz", + "integrity": "sha512-dGMphDKexSdeYqwl0wgoFBP88Ta/cdi1Zc1mk29/ENkSCGz+74zlCHgqTHRNGLmI8W5TfuUtCyktQH11/Z+TBQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@opencode/theme/node_modules/@opentui/core-linux-x64": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/@opentui/core-linux-x64/-/core-linux-x64-0.5.10.tgz", + "integrity": "sha512-5qtYaOgwVycZD1GaGshTRsi0rXPAmVExO03N1JQaHu+NYxK/vXSOc7Bu4QW0sPXx3Sp0SpzpP+FHjXABfoK66g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@opencode/theme/node_modules/@opentui/core-linux-x64-musl": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/@opentui/core-linux-x64-musl/-/core-linux-x64-musl-0.5.10.tgz", + "integrity": "sha512-Oj4H9hApuvuTKPWxh4SoZAgGJorR7vbvnrZA/cAkSMAk2VGSoHRRcqeXQbcH8IcdjVZ0KFpv8Zkl/D5Ye+2mew==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@opencode/theme/node_modules/@opentui/core-win32-arm64": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/@opentui/core-win32-arm64/-/core-win32-arm64-0.5.10.tgz", + "integrity": "sha512-A9VhgvTxQoUdZ+8LmUumEng1sQNbj9QQQT3NYG9mSxI54qTANi7vOWNSphMiY6RMVsr22pgm6nUvSSvJXv7Jog==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@opencode/theme/node_modules/@opentui/core-win32-x64": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/@opentui/core-win32-x64/-/core-win32-x64-0.5.10.tgz", + "integrity": "sha512-u3KHa7kEeWrmKVDRJYpxSGO+g5E9cMGlrmTsPN3GVPHUmQMiREUawLXUvsU8+IHaQnqG3Q5nuE1yf4fPBzS+Qw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@opencode/util": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/@opencode/util/-/util-2.0.12.tgz", - "integrity": "sha512-LgecrvN4dhsbdgG7Lt6+Cp/+0+VswyV8RJsE/mjjJ6Ns39EwFVTnZYMHT4bkR+vRL/w3IF0VCFg8Ohnfwkn3lw==", + "version": "2.0.15", + "resolved": "https://registry.npmjs.org/@opencode/util/-/util-2.0.15.tgz", + "integrity": "sha512-s0Z+2rxb+2Tif+ZV1EvTiGQ0tRiemIZq3WOOjBw2XPBSBt2Sfv9rWXJhTzSpkOZucfTyymuIVXslXPGFF7aaQA==", "dev": true, "license": "MIT", "dependencies": { @@ -2747,13 +2904,13 @@ } }, "node_modules/@smithy/core": { - "version": "3.34.1", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.34.1.tgz", - "integrity": "sha512-dLcOUxz8YCv1RZUMKq6GbyUf95pLbrqh34bPvpCZ1+CByFF31BEAFewZjsGCnVsZTKdThNENfGyAgk2TJqVwSw==", + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.35.0.tgz", + "integrity": "sha512-zRMhfkByhT2snNdr1si24vJitU6Cr9ix2MikUfWmkAgp4jrNP0GcKSP5YvwQ+TlI8AZXER5QOGJn3JsVtSD9/A==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.18.0", + "@smithy/types": "^4.19.0", "tslib": "^2.6.2" }, "engines": { @@ -2850,9 +3007,9 @@ } }, "node_modules/@smithy/types": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.18.0.tgz", - "integrity": "sha512-CgB6HHWer/vrKps24ulRIbpcpb7K4xAU7SkZ7YHzBPlwHsvsrCJFEXK421s+cJzX+ZrqtA/TuU5w1HzI7k9N8A==", + "version": "4.19.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.19.0.tgz", + "integrity": "sha512-r7jh49VJxGerfAcTQA6gXcKc+98zOp/tqRwzYjgOE+iSQsP6cEU1hq2QzbuipmP68QtYdY9wKEhiCQZIzHgZ4Q==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -5806,9 +5963,9 @@ } }, "node_modules/undici": { - "version": "8.10.2", - "resolved": "https://registry.npmjs.org/undici/-/undici-8.10.2.tgz", - "integrity": "sha512-/y4/bH9YNU5hi9NIrpOuvGXFcxrj3CMrV+/AYpowAYTpHn8gX/XPFjNy766FPoYY0miQhdW977JFWKGNhBdwyQ==", + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-8.11.0.tgz", + "integrity": "sha512-AdzuGcAkzhbha3GgCSoUBx2P0quwym6qdFNCQz/fDGZc8w9XJ741wHUbt2kTmfPz+ZIOMkySFkiY7RXmiDDC8w==", "dev": true, "license": "MIT", "engines": { diff --git a/package.json b/package.json index 14677b8..bf04fbb 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,8 @@ "devDependencies": { "@babel/core": "^7.29.7", "@babel/preset-typescript": "^7.29.7", - "@opencode/plugin": "2.0.12", + "@opencode/plugin": "2.0.15", + "@opencode/theme": "2.0.15", "@opentui/core": "^0.5.11", "@opentui/solid": "^0.5.10", "@oxlint/plugins": "^1.82.0", diff --git a/scripts/check-compatibility.mjs b/scripts/check-compatibility.mjs index bed4a6c..f17b681 100644 --- a/scripts/check-compatibility.mjs +++ b/scripts/check-compatibility.mjs @@ -6,7 +6,7 @@ const packageJson = JSON.parse(readFileSync(new URL("../package.json", import.me const readme = readFileSync(new URL("../README.md", import.meta.url), "utf8") -const packages = ["@opencode/plugin"] +const packages = ["@opencode/plugin", "@opencode/theme"] const versions = packages.map((name) => packageJson.devDependencies[name]) diff --git a/src/footer.tsx b/src/footer.tsx index fcb3fec..a408b78 100644 --- a/src/footer.tsx +++ b/src/footer.tsx @@ -414,7 +414,6 @@ const definition: Plugin.Definition = { readonly sessionID: string } - /** Mirrors the host's `local.agent.color`, including its palette fallback. */ function spinnerBaseColor(sessionID?: string): RGBA { const fallback: RGBA = context.theme.border.base const session = sessionID ? context.data.session.get(sessionID) : undefined @@ -431,19 +430,19 @@ const definition: Plugin.Definition = { if (!agent) return fallback + const step = context.themeMode === "light" ? 800 : 200 + if (agent.color) { - const step = context.themeMode === "light" ? 800 : 200 + if (/^#[0-9a-f]{6}$/iu.test(agent.color)) return RGBA.fromHex(agent.color) - // Hex from `cli.json`, or one of the config's theme color names. - const declared: RGBA | undefined = /^#[0-9a-f]{6}$/iu.test(agent.color) - ? RGBA.fromHex(agent.color) - : context.theme.hue?.[agent.color]?.[step] + if (agent.color === "accent" || agent.color === "interactive" || agent.color === "neutral") { + const declared = context.theme.hue?.[agent.color]?.[step] - if (declared) return declared + if (declared) return declared + } } const palette: RGBA[] = [] - const step = context.themeMode === "light" ? 800 : 200 for (const scale of context.theme.categorical ?? []) { const color: RGBA | undefined = scale?.[step] diff --git a/tests/footer.test.ts b/tests/footer.test.ts index 89c6d9d..749da17 100644 --- a/tests/footer.test.ts +++ b/tests/footer.test.ts @@ -222,6 +222,8 @@ interface FakeContext { readonly theme: { readonly text: { readonly muted: string } readonly border: { readonly base: RGBA } + readonly hue?: { readonly accent: { readonly 200: RGBA } } + readonly categorical?: readonly { readonly 200: RGBA }[] } readonly storage: { readonly memory: ( @@ -242,7 +244,7 @@ const setupWithFakeContext = definition.setup as unknown as ( context: FakeContext, ) => ReturnType -function createHarness(options: StatusOptionsInput & TpsOptionsInput = {}) { +function createHarness(options: StatusOptionsInput & TpsOptionsInput = {}, agentColor?: string) { const claims: CapturedClaim[] = [] const handlers = new Map void)[]>() const generation: Generation = { active: 0 } @@ -313,7 +315,10 @@ function createHarness(options: StatusOptionsInput & TpsOptionsInput = {}) { }, }, model: { list: () => state.models }, - agent: { list: () => [] }, + agent: { + list: () => + agentColor ? [{ id: "test", mode: "primary", hidden: false, color: agentColor }] : [], + }, }, } @@ -322,7 +327,12 @@ function createHarness(options: StatusOptionsInput & TpsOptionsInput = {}) { location: state.defaultLocation, app: { version: "test" }, renderer: { widthMethod: "unicode" }, - theme: { text: { muted: "#888888" }, border: { base: RGBA.fromHex("#ff8800") } }, + theme: { + text: { muted: "#888888" }, + border: { base: RGBA.fromHex("#ff8800") }, + hue: { accent: { 200: RGBA.fromHex("#3388ff") } }, + categorical: [{ 200: RGBA.fromHex("#e57837") }], + }, storage: { memory: () => [generation, (mutation: (draft: Generation) => void) => mutation(generation)] as const }, data, ui: { @@ -1386,4 +1396,26 @@ describe("built render", () => { h.restore() } }) + + test.each([ + ["#12ab34", "#12ab34"], + ["accent", "#3388ff"], + ["custom", "#e57837"], + ] satisfies ReadonlyArray)("uses agent color %s for the block spinner", async (agentColor, expected) => { + const h = createHarness({ appearance: { spinner: "blocks" } }, agentColor) + + seed(h, { status: "running" }) + + const app = await renderClaim(h, "composer", { sessionID: "ses_test" }, 60, 2) + + try { + const spans = app.captureSpans().lines[0]?.spans ?? [] + + expect(spans.some((span) => span.text.includes("■") && span.fg.equals(RGBA.fromHex(expected)))).toBe(true) + } finally { + app.renderer.destroy() + h.cleanup() + h.restore() + } + }) })