From efa63aab8a685081c8b08e88d5f7f33a73869b4a Mon Sep 17 00:00:00 2001 From: Andrey Yamanov Date: Fri, 28 Aug 2026 17:23:27 +0200 Subject: [PATCH 1/2] chore(deps): update @tenphi/tasty to 3.5.0 Co-Authored-By: Claude Opus 5 --- .changeset/tasty-3-5-0.md | 11 +++++++++++ .size-limit.cjs | 19 +++++++++++++++++-- package.json | 2 +- pnpm-lock.yaml | 16 ++++++++-------- 4 files changed, 37 insertions(+), 11 deletions(-) create mode 100644 .changeset/tasty-3-5-0.md diff --git a/.changeset/tasty-3-5-0.md b/.changeset/tasty-3-5-0.md new file mode 100644 index 000000000..65ce55822 --- /dev/null +++ b/.changeset/tasty-3-5-0.md @@ -0,0 +1,11 @@ +--- +'@cube-dev/ui-kit': minor +--- + +Update Tasty to 3.5.0 (from 3.3.1). No changes were needed in the kit; two things matter to apps built on it. + +**Injected CSS is garbage-collected again.** Once Tasty's render path became hook-free it stopped disposing the classes it injected, so their reference counts never fell back to zero and `gc()`, `cleanup()` and `tastyDebug.cleanup()` all read a non-zero count as "still in use". Nothing was ever evicted, and **a long-lived page accumulated injected rules for its entire lifetime** — the failure mode is a session that gets slower the longer it stays open, worst in a UI that mounts many short-lived styled subtrees (dialogs, popovers, table cells). What a style is worth keeping is now decided by the DOM: a sweep scans for the classes actually on the page, and only classes that are both absent from it and cold for longer than `gc.grace` (new, default 10s) beyond `gc.capacity` are deleted. Nothing is tracked per class while rendering, so collection costs nothing on the render path. `touch()` and the `StyleUsage` type are deprecated — both still exported, and `touch()`'s class-name argument is now ignored — and `tastyDebug` no longer reports `Unused: 0 classes` for a page holding hundreds of stale rules. + +**Local `@keyframes` are content-addressed and owned by the classes that use them.** A `@keyframes` block written inside a styles object is emitted as `fade-` rather than `fade`, resolved identically by the client, the SSR collector and the RSC pass, and folded into the chunk's cache key. Two components that each author `animation: fade 1s` over _different_ steps therefore get two rules and two classes instead of one definition silently winning — which previously also let the server and the client disagree about which class to use. **If your own CSS or a test refers to a local keyframes name literally, it no longer resolves**; `keyframes()`-registered global animations are unaffected and keep their authored names. `holdKeyframes()` and `ownKeyframes()` come with it, re-exported from `@cube-dev/ui-kit`. + +Also in: styling overhead drops (from 3.4.0) — LRU cache reads are 1.79x faster in their steady-state shape, and a repeat SSR render of one component is 1.8x faster end to end now that `tasty()` factories memoize their chunk cache keys. A custom property declared with a whitespace-only value (`--brand-color: ;`) is skipped rather than read as an empty value, so it no longer auto-registers an `@property` off the strength of its name. Bundle: +1.21 kB on the full import and +1.18 kB on a `{ Button }`-only import, both still inside budget. diff --git a/.size-limit.cjs b/.size-limit.cjs index 91049df41..a0b6b0952 100644 --- a/.size-limit.cjs +++ b/.size-limit.cjs @@ -20,7 +20,17 @@ module.exports = [ }), ); }, - // Still 515 kB on `@tenphi/tasty` 3.3.1: 508.53 kB locally against 509.94 kB + // Still 515 kB on `@tenphi/tasty` 3.5.0: 510.16 kB locally against 508.95 kB + // on 3.3.1 with both sides rebuilt here, i.e. +1.21 kB. The Button entry + // below moved by +1.18 kB, and matching deltas on both entries is the + // signature of a change inside Tasty's always-included core — here the + // DOM-scanning GC sweep that replaced the reference counting, keyframe + // ownership, content-addressed local `@keyframes` names, and the fuller + // `tastyDebug.summary()` accounting. 3.4.0 is pure performance (faster LRU + // reads, memoized chunk cache keys) and does not show up in either reading. + // That leaves 4.84 kB of headroom, which is enough not to raise this. + // + // Before this: 515 kB on 3.3.1 too — 508.53 kB locally against 509.94 kB // on 3.1.0 with both sides rebuilt here, i.e. -1.41 kB across the two // releases. (508.95 kB after merging `main`'s cols x rows Board matrix, // which is that feature's 0.42 kB and not the dependency's.) 3.2.0 added 0.69 kB for batched injection (the write queue and @@ -146,7 +156,12 @@ module.exports = [ path: './dist/index.js', webpack: true, import: '{ Button }', - // Still 125 kB on `@tenphi/tasty` 3.3.1: 121.76 kB locally against 124.03 kB + // Still 125 kB on `@tenphi/tasty` 3.5.0: 122.94 kB locally against 121.76 kB + // on 3.3.1, both sides rebuilt here — +1.18 kB, matched by the +1.21 kB on + // `All` above, so all of it is Tasty's always-included core and none of it + // ours. See that entry for what the release changed there. 2.06 kB left. + // + // Before this: 125 kB on 3.3.1 too — 121.76 kB locally against 124.03 kB // on 3.1.0, both sides rebuilt here — -2.27 kB, matched by the -1.41 kB on // `All` above, so all of it is Tasty's always-included core and none of it // ours. 3.2.0's batching cost this entry 0.68 kB; 3.3.0 then removed the diff --git a/package.json b/package.json index 877ea8d94..216c417dd 100644 --- a/package.json +++ b/package.json @@ -93,7 +93,7 @@ "@tabler/icons-react": "^3.31.0", "@tanstack/react-virtual": "^3.13.12", "@tenphi/glaze": "2.0.0", - "@tenphi/tasty": "^3.3.1", + "@tenphi/tasty": "^3.5.0", "clipboard-copy": "^4.0.1", "clsx": "^1.1.1", "diff": "^8.0.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6e1152735..b2e5906d6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -51,8 +51,8 @@ importers: specifier: 2.0.0 version: 2.0.0 '@tenphi/tasty': - specifier: ^3.3.1 - version: 3.3.1(@babel/core@7.28.5)(@babel/helper-plugin-utils@7.25.7)(@babel/types@7.29.0)(jiti@2.6.1)(react@19.1.1) + specifier: ^3.5.0 + version: 3.5.0(@babel/core@7.28.5)(@babel/helper-plugin-utils@7.25.7)(@babel/types@7.29.0)(jiti@2.6.1)(react@19.1.1) clipboard-copy: specifier: ^4.0.1 version: 4.0.1 @@ -158,7 +158,7 @@ importers: version: 10.3.6(esbuild@0.27.3)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(storybook@10.3.6(@testing-library/dom@10.4.1)(prettier@3.2.5)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(typescript@5.6.3)(vite@8.0.0(@types/node@24.13.3)(esbuild@0.27.3)(jiti@2.6.1)(terser@5.31.1)(yaml@2.9.0))(webpack@5.76.1(esbuild@0.27.3)) '@tenphi/eslint-plugin-tasty': specifier: ^1.0.3 - version: 1.0.3(@tenphi/tasty@3.3.1(@babel/core@7.28.5)(@babel/helper-plugin-utils@7.25.7)(@babel/types@7.29.0)(jiti@2.6.1)(react@19.1.1))(eslint@10.8.0(jiti@2.6.1))(typescript@5.6.3) + version: 1.0.3(@tenphi/tasty@3.5.0(@babel/core@7.28.5)(@babel/helper-plugin-utils@7.25.7)(@babel/types@7.29.0)(jiti@2.6.1)(react@19.1.1))(eslint@10.8.0(jiti@2.6.1))(typescript@5.6.3) '@testing-library/dom': specifier: ^10.4.1 version: 10.4.1 @@ -2186,8 +2186,8 @@ packages: resolution: {integrity: sha512-entP+7nsScY87maev/rA6HS/qQy3YwHeywAsWTT9LGhovMp3/cj/Zb1jctlc37FWLim+mkYMwTGOk7ZfJBLLWg==} engines: {node: '>=20'} - '@tenphi/tasty@3.3.1': - resolution: {integrity: sha512-zCMwMu79R0VMcSA4M4UcAjRNrm+fLSOQsvHnpYk//IlnLizTpTAjpy+AIV908us7X1Iskj7ssuVgp0UyQ9e98Q==} + '@tenphi/tasty@3.5.0': + resolution: {integrity: sha512-qNdeeiMR1RnQY+yVGR3U1AGqlJBcRYFIGeoemiDZnDXdwVL4NdvTSEw864sj89az0o+47lfWZ2OTwkwOrdy0dw==} engines: {node: '>=20'} peerDependencies: '@babel/core': ^7.24.0 @@ -8282,20 +8282,20 @@ snapshots: '@tanstack/virtual-core@3.13.12': {} - '@tenphi/eslint-plugin-tasty@1.0.3(@tenphi/tasty@3.3.1(@babel/core@7.28.5)(@babel/helper-plugin-utils@7.25.7)(@babel/types@7.29.0)(jiti@2.6.1)(react@19.1.1))(eslint@10.8.0(jiti@2.6.1))(typescript@5.6.3)': + '@tenphi/eslint-plugin-tasty@1.0.3(@tenphi/tasty@3.5.0(@babel/core@7.28.5)(@babel/helper-plugin-utils@7.25.7)(@babel/types@7.29.0)(jiti@2.6.1)(react@19.1.1))(eslint@10.8.0(jiti@2.6.1))(typescript@5.6.3)': dependencies: '@typescript-eslint/utils': 8.56.1(eslint@10.8.0(jiti@2.6.1))(typescript@5.6.3) eslint: 10.8.0(jiti@2.6.1) jiti: 2.6.1 optionalDependencies: - '@tenphi/tasty': 3.3.1(@babel/core@7.28.5)(@babel/helper-plugin-utils@7.25.7)(@babel/types@7.29.0)(jiti@2.6.1)(react@19.1.1) + '@tenphi/tasty': 3.5.0(@babel/core@7.28.5)(@babel/helper-plugin-utils@7.25.7)(@babel/types@7.29.0)(jiti@2.6.1)(react@19.1.1) transitivePeerDependencies: - supports-color - typescript '@tenphi/glaze@2.0.0': {} - '@tenphi/tasty@3.3.1(@babel/core@7.28.5)(@babel/helper-plugin-utils@7.25.7)(@babel/types@7.29.0)(jiti@2.6.1)(react@19.1.1)': + '@tenphi/tasty@3.5.0(@babel/core@7.28.5)(@babel/helper-plugin-utils@7.25.7)(@babel/types@7.29.0)(jiti@2.6.1)(react@19.1.1)': dependencies: csstype: 3.1.2 optionalDependencies: From 5b27db081a0e7f64a964a3783a962a903f13689d Mon Sep 17 00:00:00 2001 From: Andrey Yamanov Date: Fri, 28 Aug 2026 19:54:26 +0200 Subject: [PATCH 2/2] chore: record the confirmed CI size reading for the Tasty bump CI measured this branch at exactly the local readings the budgets were checked against, so the macOS/Linux caveat did not apply here. Co-Authored-By: Claude Opus 5 --- .size-limit.cjs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/.size-limit.cjs b/.size-limit.cjs index 68f1d6120..85e7e68ea 100644 --- a/.size-limit.cjs +++ b/.size-limit.cjs @@ -42,11 +42,13 @@ module.exports = [ // machine that measured this bump, the local reading matched CI EXACTLY on // both entries. CI reported `main` at 500.38 KB and 118.96 KB binary, which // is 512.39 kB and 121.81 kB decimal, and the local rebuild of the same - // commit read 512.39 kB and 121.81 kB. So the ~1.6 kB gap that warning - // describes did not apply here. Two entries on one machine do not retire - // the warning — still prefer CI's number — but a local reading is not - // automatically 1.6 kB light either, and assuming it is would have raised - // this budget for no reason. + // commit read 512.39 kB and 121.81 kB. CI then measured this branch at + // 501.56 KB and 120.13 KB binary — 513.60 kB and 123.02 kB — matching the + // local readings above that the budgets were checked against. So the + // ~1.6 kB gap that warning describes did not apply here. Two entries on one + // machine do not retire the warning — still prefer CI's number — but a + // local reading is not automatically 1.6 kB light either, and assuming it + // is would have raised this budget for no reason. // // Before this: 515 kB on 3.3.1 too — 508.53 kB locally against 509.94 kB // on 3.1.0 with both sides rebuilt here, i.e. -1.41 kB across the two