From 663652e39d544dfcd6fd9817045543657a45906d Mon Sep 17 00:00:00 2001 From: Titouan Mathis Date: Thu, 27 Aug 2026 00:29:57 +0200 Subject: [PATCH 1/2] fix(v4): export SmoothToRecord, the type the record overload returns MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `smoothTo()` has two overloads. The scalar one returns `SmoothTo`, the record one returns `SmoothToRecord`. The utils barrel named the first two interfaces of `smoothTo.ts` and stopped there, so the third never reached `dist` and a consumer could call the record overload but not write down what it got back: // before import { smoothTo, type SmoothToRecord } from '@studiometa/js-toolkit/utils'; // ^ has no exported member 'SmoothToRecord' That is not a cosmetic gap. The return value of a public overload is part of the public signature: anything holding it in a class field, a function parameter or a return position has to name it. The `@studiometa/ui` v2 port hit exactly this and copied the interface into its own source, which is a duplicate that will drift from ours the first time either moves. Nothing else in the package surface reacts to the line. A type-only symbol gets no subpath — `enumerate()` filters `isType` out, because a subpath exists to keep a runtime import from dragging in a barrel's graph and a type import is erased before anything runs — so the stubs, the `exports` map and `check-doc-links.js`, which walks those stubs, are all unchanged. Types stay reachable through `.` and `./utils`, which is the entry point this fixes. The two specs that assert the surface now say so, since nothing else would have caught it: `utils/index.spec.ts` compares runtime keys and is blind to types by construction, and `exports.spec.ts` reads the type through the published `./utils` condition rather than through a relative path, which is the way the consumer sees it. Every module under `src/utils/` was checked against the barrel; this was the only symbol missing. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_011izFBQT4AsFcD4tVZz1f7R --- packages/js-toolkit/src/exports.spec.ts | 9 ++++++++- packages/js-toolkit/src/utils/index.spec.ts | 7 ++++++- packages/js-toolkit/src/utils/index.ts | 2 +- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/packages/js-toolkit/src/exports.spec.ts b/packages/js-toolkit/src/exports.spec.ts index 21690619..2be5cad6 100644 --- a/packages/js-toolkit/src/exports.spec.ts +++ b/packages/js-toolkit/src/exports.spec.ts @@ -1,6 +1,6 @@ import { describe, expect, expectTypeOf, it } from 'vitest'; import packageManifest from '../package.json' with { type: 'json' }; -import { clamp, smoothTo } from '@studiometa/js-toolkit/utils'; +import { clamp, smoothTo, type SmoothToRecord } from '@studiometa/js-toolkit/utils'; import { Base, DIAGNOSTICS, @@ -173,6 +173,13 @@ describe('the package entry points', () => { x.destroy(); }); + it('names the return type of the record overload of smoothTo', () => { + // The overload is public, so what it returns has to be nameable from the + // entry point — a consumer holding the record in a field or a signature + // cannot write its type otherwise. + expectTypeOf(smoothTo({ x: 0, y: 0 })).toEqualTypeOf>(); + }); + it('keeps the framework on the root entry, without the utils or removed exports', async () => { expect(typeof Base).toBe('function'); const root = (await import('@studiometa/js-toolkit')) as Record; diff --git a/packages/js-toolkit/src/utils/index.spec.ts b/packages/js-toolkit/src/utils/index.spec.ts index 9d997812..7ad3bc2e 100644 --- a/packages/js-toolkit/src/utils/index.spec.ts +++ b/packages/js-toolkit/src/utils/index.spec.ts @@ -19,7 +19,7 @@ import * as strings from './strings.js'; import * as timing from './timing.js'; import * as transformModule from './transform.js'; import * as transitionModule from './transition.js'; -import type { Memo, SmoothTo, SmoothToOptions, SpringOptions } from './index.js'; +import type { Memo, SmoothTo, SmoothToOptions, SmoothToRecord, SpringOptions } from './index.js'; describe('the utils barrel', () => { it('names every runtime export of every module it fronts', () => { @@ -52,11 +52,16 @@ describe('the utils barrel', () => { const spring: SpringOptions = { stiffness: 0.2, damping: 0.6, mass: 1 }; const options: SmoothToOptions = { ...spring, spring: true, precision: 0.01 }; const value: SmoothTo = barrel.smoothTo(0, options); + // The record overload returns a type of its own, so a consumer holding what + // it returns has to be able to name it from the barrel. + const record: SmoothToRecord<'x' | 'y'> = barrel.smoothTo({ x: 0, y: 0 }, options); const upper: Memo<[key: string], string> = barrel.memo((key: string) => key.toUpperCase()); expect(upper('a')).toBe('A'); expect(value()).toBe(0); + expect(record()).toEqual({ x: 0, y: 0 }); value.destroy(); + record.destroy(); }); it('exports nothing the framework barrel also exports', () => { diff --git a/packages/js-toolkit/src/utils/index.ts b/packages/js-toolkit/src/utils/index.ts index f4b32872..fa9a3d9f 100644 --- a/packages/js-toolkit/src/utils/index.ts +++ b/packages/js-toolkit/src/utils/index.ts @@ -85,7 +85,7 @@ export { } from './scrollTo.js'; export { lockScroll } from './scroll-lock.js'; export { selectorFor } from './selectors.js'; -export { smoothTo, type SmoothTo, type SmoothToOptions } from './smoothTo.js'; +export { smoothTo, type SmoothTo, type SmoothToOptions, type SmoothToRecord } from './smoothTo.js'; export { camelCase, capitalize, From 90ec4b192747240b2e9ab2fbd296f31e1f06f8aa Mon Sep 17 00:00:00 2001 From: Titouan Mathis Date: Thu, 27 Aug 2026 00:30:42 +0200 Subject: [PATCH 2/2] ci: release 4.0.0-alpha.1 from main A patch release carrying one fix: `SmoothToRecord` is exported from `@studiometa/js-toolkit/utils`, so the return type of the record overload of `smoothTo()` is nameable from the published package. `@studiometa/eslint-plugin-js-toolkit` moves with the framework, as it did at alpha.0. Nothing in it changed; the two packages publish from this branch to the same `next` dist-tag and are easier to reason about when their versions match. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_011izFBQT4AsFcD4tVZz1f7R --- CHANGELOG.md | 6 ++++++ package-lock.json | 8 ++++---- package.json | 2 +- packages/eslint-plugin/package.json | 2 +- packages/js-toolkit/package.json | 2 +- 5 files changed, 13 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c6451c79..45720956 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file. The format ## [Unreleased] +## [4.0.0-alpha.1](https://github.com/studiometa/js-toolkit/compare/4.0.0-alpha.0..4.0.0-alpha.1) (2026-08-27) + +### Fixed + +- Export `SmoothToRecord` from `@studiometa/js-toolkit/utils`. It is the return type of the record overload of `smoothTo()`, so it is part of the public signature, but the barrel named only `SmoothTo` and `SmoothToOptions` and the type never reached `dist` — a consumer holding the record in a field or a signature could not write its type. Found by the `@studiometa/ui` v2 port, which had to copy the interface into its own source ([#873](https://github.com/studiometa/js-toolkit/pull/873)) + ## [4.0.0-alpha.0](https://github.com/studiometa/js-toolkit/compare/3.9.0..4.0.0-alpha.0) (2026-08-26) **4.0 is a rewrite, and this is its first published release.** It goes to the `next` dist-tag, so `latest` still installs 3.x. The API is stable and documented; the alpha label says it has not yet run on a production project. diff --git a/package-lock.json b/package-lock.json index ed124d66..e4c1466d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@studiometa/js-toolkit-workspace", - "version": "4.0.0-alpha.0", + "version": "4.0.0-alpha.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@studiometa/js-toolkit-workspace", - "version": "4.0.0-alpha.0", + "version": "4.0.0-alpha.1", "hasInstallScript": true, "workspaces": [ "packages/*" @@ -9604,7 +9604,7 @@ }, "packages/eslint-plugin": { "name": "@studiometa/eslint-plugin-js-toolkit", - "version": "4.0.0-alpha.0", + "version": "4.0.0-alpha.1", "license": "MIT", "dependencies": { "@oxlint/plugins": "1.77.0" @@ -9619,7 +9619,7 @@ }, "packages/js-toolkit": { "name": "@studiometa/js-toolkit", - "version": "4.0.0-alpha.0", + "version": "4.0.0-alpha.1", "license": "MIT", "dependencies": { "morphdom": "^2.7.8" diff --git a/package.json b/package.json index b70cc434..fb33fed2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@studiometa/js-toolkit-workspace", - "version": "4.0.0-alpha.0", + "version": "4.0.0-alpha.1", "private": true, "type": "module", "workspaces": [ diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index 0f19f85a..5066a269 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@studiometa/eslint-plugin-js-toolkit", - "version": "4.0.0-alpha.0", + "version": "4.0.0-alpha.1", "description": "Oxlint/ESLint plugin for @studiometa/js-toolkit best practices", "publishConfig": { "access": "public" diff --git a/packages/js-toolkit/package.json b/packages/js-toolkit/package.json index 2d47f9b6..d4d08beb 100644 --- a/packages/js-toolkit/package.json +++ b/packages/js-toolkit/package.json @@ -1,6 +1,6 @@ { "name": "@studiometa/js-toolkit", - "version": "4.0.0-alpha.0", + "version": "4.0.0-alpha.1", "type": "module", "sideEffects": [ "./dist/responsive-options.js"