From b467e9825586108a0710433f774115847e5d6d69 Mon Sep 17 00:00:00 2001 From: Titouan Mathis Date: Wed, 26 Aug 2026 21:45:01 +0200 Subject: [PATCH 1/2] ci: release 4.0.0-alpha.0 from main The publish workflow needed three changes for the release commit to prove what it ships. - It ran v3's Node suite with coverage. That directory now holds v4, whose specs run in a real browser, so the job installs Chromium and runs `npm test`. Coverage is dropped with it: this suite writes no clover report, and the pull-request job is where coverage is measured. - It publishes `npm run check:package` before the tarball goes out. That script packs the package and runs the result through a Node, a browser and a TypeScript consumer with the workspace aliases removed, so what passes is what a consumer installs. Nothing gated the tarball before. - It needs no version gate, and a comment now says why: a tag publishes the packages on the branch it points at, and the workflow runs from the ref of the tag. A `4.x` tag here publishes v4; a `3.x` tag on the `3.x` branch publishes v3 from that branch's copy of this file. `@studiometa/eslint-plugin-js-toolkit` moves to 4.0.0-alpha.0 with the framework. Its rule set already targets v4, and both packages publish from this branch to the same `next` dist-tag. The changelog gets a 4.0.0-alpha.0 section. The `Unreleased` entries that belonged to the 3.x line stay on the `3.x` branch, which carries the same file; what is left here describes v4 and points at the migration guide, since a rewrite has no meaningful line-by-line diff against 3.9.0. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_011izFBQT4AsFcD4tVZz1f7R --- .github/workflows/publish.yml | 32 ++++++++++++++------ CHANGELOG.md | 46 +++++++++++++++++++---------- package-lock.json | 2 +- packages/eslint-plugin/package.json | 2 +- 4 files changed, 55 insertions(+), 27 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 8cb1eaa22..3425b5637 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,5 +1,13 @@ name: publish-and-release +# A tag publishes the packages that live on the branch it points at, so this +# file needs no version gate: a `4.x` tag is created here and publishes v4, +# while a `3.x` tag is created on the `3.x` branch, whose copy of this file +# publishes v3. The workflow always runs from the ref of the tag. +# +# A prerelease goes to the `next` dist-tag, which is what keeps `latest` on +# 3.x while 4.x is an alpha. + on: push: tags: @@ -39,18 +47,24 @@ jobs: - name: Build the published packages run: npm run build + # The specs run in a real browser, so the runner needs one. They cover + # native view transitions, frame-aligned scheduling and DOM lifecycle, + # which is what makes them worth running again on the release commit + # rather than trusting the pull request that produced it. + - name: Install Playwright Chromium + run: npx playwright install --with-deps chromium + working-directory: packages/js-toolkit + - name: Test js-toolkit - run: npm test -- --retry=3 --coverage.enabled + run: npm test working-directory: packages/js-toolkit - - name: Upload coverage to Codecov - uses: codecov/codecov-action@v4 - with: - token: ${{ secrets.CODECOV_TOKEN }} - files: packages/js-toolkit/coverage/clover.xml - flags: js-toolkit - fail_ci_if_error: false - verbose: true + # The last gate before the tarball goes out: packs the package and runs + # the result through a Node, a browser and a TypeScript consumer, without + # the workspace aliases. What passes here is what a consumer installs. + - name: Check the packed package + run: npm run check:package + working-directory: packages/js-toolkit - name: Test oxlint-plugin run: npm test -- --retry=3 --coverage.enabled diff --git a/CHANGELOG.md b/CHANGELOG.md index cb8d5620a..c6451c792 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,28 +4,42 @@ All notable changes to this project will be documented in this file. The format ## [Unreleased] -### Fixed +## [4.0.0-alpha.0](https://github.com/studiometa/js-toolkit/compare/3.9.0..4.0.0-alpha.0) (2026-08-26) -- Fix v4 services being identified by how their options were written rather than by what they observe: `useDrag(el, { axis, inertia })` and `useDrag(el, { inertia, axis })` describe one drag and bound two services, and a mixin's own `manual`, `immediate` and `target` options entered the key, so `withDrag(Base, { manual: true })` bound a second drag service — a second pointer listener set and a second `touch-action` claim — on an element the plain `withDrag(Base)` already owned ([#839](https://github.com/studiometa/js-toolkit/pull/839)) -- Fix a v4 handler stacking `@on` with `@read` or `@write` binding twice, and firing twice, whenever the phase decorator was written above the `@on`: the magic `on<…>` name scan now skips a decorated handler by its method name instead of by the function it was declared with, which the phase decorators replace ([#840](https://github.com/studiometa/js-toolkit/pull/840)) -- Fix `Queue` and `SmartQueue` draining in quadratic time, which cost a page of 5 000 components several seconds of mounting: 5 548 ms becomes 294 ms ([#833](https://github.com/studiometa/js-toolkit/pull/833)) +**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. -### Removed +Read [the migration guide](https://js-toolkit-v4.studiometa.dev/guide/migration/v3-to-v4.html) before upgrading. `DESIGN.md` states what v4 does and `RATIONALE.md` states why, which options were refused, and what the measurements are. + +One sentence carries the change: **the registry is the framework, and the DOM is the component tree.** An instance exists because its element is in the document and its class is registered. In 3.x a parent constructed its children and a child had to find its parent; in 4.0 neither happens. + +### Added -- Drop the dedicated subpaths of the 89 type-only symbols, such as `@studiometa/js-toolkit/BaseConfig`. Import types from `@studiometa/js-toolkit` or `@studiometa/js-toolkit/utils` instead ([#777](https://github.com/studiometa/js-toolkit/pull/777)) -- **Breaking:** drop `createLocalStorageProvider` and `createSessionStorageProvider` from the published surface of `@studiometa/js-toolkit`, root entry and subpaths alike. Both returned an adapter behaving exactly like the `localStorageProvider` and `sessionStorageProvider` instances the package already exports — the adapter holds no state — so use those instances instead ([#841](https://github.com/studiometa/js-toolkit/pull/841)) +- Provide/inject in core, with the shape of Vue and the mechanics of the WICG context protocol: `$provide`, `$inject`, `$injectSync`, `createContext`, `subscribeContext`, and a `signal` primitive whose delivery is reentrancy-safe +- `$watchChildren()` and `@children()`, live DOM-ordered collections that answer for a child appearing after mount, matched by class so a named subclass is included +- Delegated `on`, `onWindow` and `onDocument` handlers, resolved by name and bound once per mount cycle +- Mount strategies on one attribute — `data-mount="visible[:]"`, `in-view`, `idle`, `interaction`, `media` — replacing the decorators that wrapped a constructor +- Lazy manifests: `defineManifest()`, `registerManifest()`, `fromMetaGlob()` and `fromWebpackContext()` +- A service layer that is lazy and reference-counted, with `toggle()` to suspend a subscription inside a mount cycle, plus `useInView`, `useScrollProgress`, `useDrag`, `useKey`, `useMediaQuery` and `perTarget` +- Test helpers on `@studiometa/js-toolkit/test`: `mount`, `settle`, `frames`, `waitFor`, `recordEvents`, `countRequestedFrames` and `resetDom` +- A cancelable diagnostic channel with stable codes, plus `$warn()` and `$error()` on every instance +- `swap()` for DOM content replacement, native view transitions, `$id`, and a `utils` surface chosen by what consumers needed ### Changed -- Schedule every v4 component through one algorithm: a registered class and a lazy manifest entry now share one controller per element/component pair, and `mount-strategies` answers whether a strategy is reversible or eager instead of the registry re-deriving it from strategy names ([#843](https://github.com/studiometa/js-toolkit/pull/843)) -- Give the v4 attribute vocabulary one owner, `attributes.ts`: the framework attribute names, the `data-option-` spelling and the batch coalescing rule were each written down in several modules, and the mutation engine kept them as string literals to avoid an import cycle. The engine's relevance test and the observer's `attributeFilter` are now the same set, so a framework attribute added to one can no longer be dropped by the other. Internal to the v4 prototype; no public API changes ([#842](https://github.com/studiometa/js-toolkit/pull/842)) -- Assemble the published package in `packages/js-toolkit/` instead of a repository-level `dist/`. The subpaths a consumer imports are unchanged ([#777](https://github.com/studiometa/js-toolkit/pull/777)) -- Build with tsdown (rolldown) instead of esbuild plus a separate `tsgo --build` pass, in every package ([#777](https://github.com/studiometa/js-toolkit/pull/777)) -- Format with `oxfmt` instead of Prettier, across the whole tree; the demo keeps Prettier for its Twig templates ([#777](https://github.com/studiometa/js-toolkit/pull/777)) -- Move the toolchain to the released `typescript@7` (`tsc`) instead of `@typescript/native-preview` (`tsgo`), and `oxlint` to 1.77.0 ([#777](https://github.com/studiometa/js-toolkit/pull/777)) -- Move the test runner to Vitest 4.1.10, with each package owning its runner config ([#777](https://github.com/studiometa/js-toolkit/pull/777)) -- Run v3's benchmarks in a real Chromium instead of Node with happy-dom, add the v3-against-v4 mounting comparison, and track regressions with `.github/actions/bench-diff` instead of CodSpeed, which is removed ([#830](https://github.com/studiometa/js-toolkit/pull/830)) -- Commit the subpath stubs instead of regenerating them before every build, test and lint run. `npm run subpaths` rewrites them and the `exports` map together ([#777](https://github.com/studiometa/js-toolkit/pull/777)) +- **Breaking:** `mounted()` returns its cleanup function instead of pairing with `destroyed()`, and the lifecycle vocabulary is `mount`/`unmount` only — both reversible +- **Breaking:** `$options` is a read-only view over the attributes, typed `Readonly<…>`. Every option is responsive +- **Breaking:** `config` merges along the prototype chain, so a subclass no longer respreads its parent's refs and components +- **Breaking:** a boolean option reads presence, and `data-option-no-` turns a true-default off +- **Breaking:** an `Object` or `Array` option default must be a factory function + +### Removed + +- **Breaking:** `$children`, `$parent`, `$root`, `$update()`, `$terminate()`, `terminated()`, `updated()`, `config.emits`, `$services.enable()`/`.disable()`, and the constructor-wrapping mount decorators. `@studiometa/eslint-plugin-js-toolkit` flags every one of them through `configs.v4` + +### Notes + +- The 3.x line continues on the [`3.x` branch](https://github.com/studiometa/js-toolkit/tree/3.x), which keeps `latest`, its documentation at [js-toolkit.studiometa.dev](https://js-toolkit.studiometa.dev), and its own entries in this file +- Two v4 fixes landed after the documentation was written: a service is now identified by what it observes rather than by the order its options were written ([#839](https://github.com/studiometa/js-toolkit/pull/839)), and a handler stacking `@on` with `@read` or `@write` no longer binds twice ([#840](https://github.com/studiometa/js-toolkit/pull/840)) ## [v3.9.0](https://github.com/studiometa/js-toolkit/compare/3.8.2..3.9.0) (2026-08-10) diff --git a/package-lock.json b/package-lock.json index c4e664ce1..ed124d661 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9604,7 +9604,7 @@ }, "packages/eslint-plugin": { "name": "@studiometa/eslint-plugin-js-toolkit", - "version": "3.9.0", + "version": "4.0.0-alpha.0", "license": "MIT", "dependencies": { "@oxlint/plugins": "1.77.0" diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index a3cf7045d..0f19f85a2 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@studiometa/eslint-plugin-js-toolkit", - "version": "3.9.0", + "version": "4.0.0-alpha.0", "description": "Oxlint/ESLint plugin for @studiometa/js-toolkit best practices", "publishConfig": { "access": "public" From 0cccdde6db27299975c32b6767b1f14dc3d851ec Mon Sep 17 00:00:00 2001 From: Titouan Mathis Date: Wed, 26 Aug 2026 21:48:02 +0200 Subject: [PATCH 2/2] ci: run the release job on Ubuntu MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `playwright install --with-deps` installs system dependencies through `apt`, so it only does its job on Ubuntu — and the release job, which now runs the browser suite, was on `macos-latest`. Ubuntu also makes the release run the suite in the same environment `tests.yml` verifies every pull request in, which is what a release job should be doing. Reported by @weareikko/code-review on #872. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_011izFBQT4AsFcD4tVZz1f7R --- .github/workflows/publish.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 3425b5637..c74eed714 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -15,7 +15,11 @@ on: jobs: publish-and-release: - runs-on: macos-latest + # Ubuntu, not macOS: the specs run in a real Chromium, and `playwright + # install --with-deps` only installs system dependencies on Ubuntu. It also + # makes the release run the suite in the same environment `tests.yml` + # verifies every pull request in. + runs-on: ubuntu-latest permissions: contents: write id-token: write