From 53d0466f890657bdeac3d11f6d3e651824288e5b Mon Sep 17 00:00:00 2001 From: kevinccbsg Date: Sun, 6 Sep 2026 23:02:07 +0200 Subject: [PATCH 1/2] chore(release): 1.10.0 --- CHANGELOG.md | 29 +++++++++++++++++++++++++++++ package-lock.json | 29 +++++++++++++++++++++++++---- package.json | 2 +- src/constants/version.ts | 2 +- src/constants/version_cli.js | 2 +- 5 files changed, 57 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d5c9b472..fffa2dda 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,32 @@ +## 1.10.0 (2026-09-06) + +* feat(matchLayout): layout snapshots, in the browser, with no SaaS account and no containers (#337) ([8ab683e](https://github.com/BRIKEV/twd/commit/8ab683e)), closes [#337](https://github.com/BRIKEV/twd/issues/337) +* feat(matchLayout): `twd.matchLayout(el, name)` captures a DOM node to an anchored grid of bits and compares it against a committed `.snap` reference, throwing when the geometry moved. It watches **geometry, not appearance**: a changed string or a shifted colour is invisible to it on purpose, because `twd.should` already covers content +* feat(matchLayout): the reference is a text file you commit, with an ASCII preview inside it, so a layout change is reviewable in a pull request without opening an image +* feat(matchLayout): a new `twdSnapshot()` Vite plugin does the file I/O, since a browser cannot. It holds no snapshot policy: every decision lives in `matchLayout` +* feat(matchLayout): snapshots are **skipped in the sidebar by default**. The sidebar resizes the page and a developer's window is an arbitrary size, so a reference created there would fail for everyone else. `twdSnapshot({ debug: true })` opts in locally, and `twd-cli` 1.6.0 or newer is what actually decides a snapshot +* feat(sidebar): an execution speed selector, and pacing state moved somewhere every bundle can see it. `pace.ts` kept the value in module scope, but the module ships in more than one bundle, so a pace set from the sidebar never reached the copy the runner reads (#340) ([1e701be](https://github.com/BRIKEV/twd/commit/1e701be)), closes [#340](https://github.com/BRIKEV/twd/issues/340) +* feat(diagnostics): a failing test now reports the route it was on and which mock rules never fired, as plain data the reporter renders (#335) ([a43bd9d](https://github.com/BRIKEV/twd/commit/a43bd9d)), closes [#335](https://github.com/BRIKEV/twd/issues/335) +* fix(matchLayout): externalise `node:` prefixed builtins so `twdSnapshot` loads at all. `rollupOptions.external` listed only the unprefixed `fs` and `path`, so rolldown bundled them and emitted broken interop, and any app using the plugin died on startup with `l.default.resolve is not a function` (#339) ([6470a11](https://github.com/BRIKEV/twd/commit/6470a11)), closes [#339](https://github.com/BRIKEV/twd/issues/339) +* fix(matchLayout): the failure message says where the layout moved again, with the diff map restored +* feat: **`twd.viewport()` and `twd.resetViewport()` are removed** (#338) ([e475acf](https://github.com/BRIKEV/twd/commit/e475acf)), closes [#338](https://github.com/BRIKEV/twd/issues/338) +* docs: a Layout Snapshots (beta) page, and the API reference entry for `matchLayout` +* chore: dependencies (14 dependabot bumps) + +**Removal, and why this is not a major.** `twd.viewport()` shipped in 1.6.0 as a +beta and did not survive contact with real users. It simulated a viewport by +constraining the body, overriding `innerWidth`, `innerHeight` and `matchMedia`, +and rewriting CSS `@media` rules by hand at runtime, and that machinery did not +hold up in practice. Anyone calling `twd.viewport()` or `twd.resetViewport()` +has to drop those calls. We are not cutting a major for it because usage of the +command is effectively nil, and we would rather start the responsive story from +a clean slate than keep a method that makes people think the problem is solved. + +**Layout snapshots ship as a beta feature.** They are strictly additive: without +`matchLayout` in a test nothing changes, and in the sidebar they are skipped +unless you ask for them. What may still change is the `.snap` format and the +API. Deciding a snapshot needs `twd-cli` 1.6.0 or newer. + ## 1.9.0 (2026-07-28) * feat(pace): command pacing for recorded runs (#316) ([3aeceb4](https://github.com/BRIKEV/twd/commit/3aeceb4)), closes [#316](https://github.com/BRIKEV/twd/issues/316) diff --git a/package-lock.json b/package-lock.json index 4c16bcab..eb64000b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "twd-js", - "version": "1.9.0", + "version": "1.10.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "twd-js", - "version": "1.9.0", + "version": "1.10.0", "license": "MIT", "dependencies": { "@testing-library/dom": "^10.4.1", @@ -140,6 +140,7 @@ "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.7", @@ -593,6 +594,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=20.19.0" }, @@ -641,6 +643,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=20.19.0" } @@ -1979,6 +1982,7 @@ "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", @@ -2158,6 +2162,7 @@ "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -2168,6 +2173,7 @@ "integrity": "sha512-fMPwH9v7r/pp43yUd2/Mbiex5KouJwwR3dzHkhLREUC6764VyDsqxhAxv6OFEYR1RhjOyD1naqba8ECDBe7ZQg==", "dev": true, "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -2192,6 +2198,7 @@ "integrity": "sha512-WASHDpCm6qO5jj9g1a+8NiW5+GCkAyLReR56/4VruYmNgfUmqpxOfZ2Yfb8xGfJPWv5Qi6LSD8sXdces3vbp/Q==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.68.0", @@ -2231,6 +2238,7 @@ "integrity": "sha512-fHq2VC1kpyYfvEcbiMjOpySY4WS7voEp89yAThrHRX5sm9j2lzYppCb2umFMEed4fWcyeLjHxrz0mpjNBaBxMQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.68.0", "@typescript-eslint/types": "8.68.0", @@ -2485,6 +2493,7 @@ "integrity": "sha512-IM49HmthevbgAO4anp1hwtoT9wYe59w0LR00gr+eagHE+ZJ5lK4sLPeO0ubgoJcwLk6dehU3R24N+FbEEKDc8g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@bcoe/v8-coverage": "^1.0.2", "@vitest/utils": "4.1.10", @@ -2957,6 +2966,7 @@ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -3148,6 +3158,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.11.20", "caniuse-lite": "^1.0.30001810", @@ -3323,6 +3334,7 @@ "integrity": "sha512-cs+LadpH7Kpw0M3k8wurk+sOVVDAENA0iK4OBOrkL94j5lEVYRJ4j3zd2bhY9qgzyrPqthdcYT3axzRN7AliMg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=22" } @@ -3333,6 +3345,7 @@ "integrity": "sha512-O+x4N2yH+ijvqWlIyTHsXTAP+algNWgGbjY2duCe8w2vUMvUB95cLRslCPfTMQyLAKlet3bhZTdu6ozn4M+QJQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@simple-libs/stream-utils": "^2.0.0", "argue-cli": "^3.1.0" @@ -3615,6 +3628,7 @@ "dev": true, "hasInstallScript": true, "license": "MIT", + "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -3679,6 +3693,7 @@ "integrity": "sha512-wqA7W2jbsC/BnV9Iv1UZpKVFkO1AdNoSmYW8NWG4HNOBbkAMvIqDZ27pI2f07dqn583NcIC44ckjAcOXDL1QbQ==", "dev": true, "license": "MIT", + "peer": true, "workspaces": [ "packages/*" ], @@ -4014,6 +4029,7 @@ "integrity": "sha512-qV0g8hRYBqgACcFOH3f9wXc4zPKhr/0z9RI2a6ZijZ72EeBi4g8oBy8zAWuUR1TsMpOzwpUMFvjdasrC41Joug==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "tabbable": "^6.5.0" } @@ -5265,6 +5281,7 @@ "resolved": "https://registry.npmjs.org/preact/-/preact-10.29.8.tgz", "integrity": "sha512-ej2aVZ+vZ8WO7tvlQWRM9N63A0KzF9q4mWJfDUHgYaIofWY9hu74QdnQrjoPMmZi2/nZ5gN0bJCQF49xQqx09Q==", "license": "MIT", + "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/preact" @@ -5442,6 +5459,7 @@ "integrity": "sha512-rn9wpmxplLf7NLNyCk9FyWh3FM43DbY8jOzCdEPzH7uflhTftRbCEpqi6Ly2osgoU8OwObtmavMbWLaWy4LX7A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@oxc-project/types": "=0.143.0", "@rolldown/pluginutils": "^1.0.0" @@ -5486,8 +5504,7 @@ "version": "0.27.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/semver": { "version": "6.3.1", @@ -5802,6 +5819,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -6071,6 +6089,7 @@ "integrity": "sha512-EU/eS7BH3XROHh2YnBefjM6DBKA6ZeMZEYQbj7NLWg5wHYlhB8B/Mayd5XsgWq+NFYccDOTemRpdETWR6Ka/lw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "lightningcss": "^1.33.0", "picomatch": "^4.0.5", @@ -6246,6 +6265,7 @@ "integrity": "sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@vitest/expect": "4.1.10", "@vitest/mocker": "4.1.10", @@ -6343,6 +6363,7 @@ "integrity": "sha512-2laE0p+aK+/AOPG/XL/WepOs/GlK755LJ1XECi9kDUrz1FKNw8rb2Xzlw9JS1rqEV55nb0ttsKxVlTCcd+R5cg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@vue/compiler-dom": "3.5.41", "@vue/compiler-sfc": "3.5.41", diff --git a/package.json b/package.json index c7dc9567..2f9e7fc1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "twd-js", - "version": "1.9.0", + "version": "1.10.0", "description": "Test While Developing (TWD): in-browser test runner with a live sidebar UI. Works with React, Vue, Angular, Solid, Astro, Nuxt, HTMX and vanilla JS, on Vite, Webpack, or a CDN. Built-in API and component mocking.", "type": "module", "license": "MIT", diff --git a/src/constants/version.ts b/src/constants/version.ts index 95a3aa15..d2f72e44 100644 --- a/src/constants/version.ts +++ b/src/constants/version.ts @@ -1 +1 @@ -export const TWD_VERSION = '1.9.0'; +export const TWD_VERSION = '1.10.0'; diff --git a/src/constants/version_cli.js b/src/constants/version_cli.js index 95a3aa15..d2f72e44 100644 --- a/src/constants/version_cli.js +++ b/src/constants/version_cli.js @@ -1 +1 @@ -export const TWD_VERSION = '1.9.0'; +export const TWD_VERSION = '1.10.0'; From caf2ceb49671267713f216746827184650c6aa11 Mon Sep 17 00:00:00 2001 From: kevinccbsg Date: Sun, 6 Sep 2026 23:02:10 +0200 Subject: [PATCH 2/2] ci: publish to npm with provenance Attaches a signed attestation linking each published tarball to the exact commit and workflow run that built it, which npm surfaces as a Provenance badge on the package page. The value is not that it prevents a bad publish. It is that it makes one visible: a version pushed from a stolen NPM_TOKEN on someone's laptop carries no attestation, and the gap is public. Needs id-token: write, since the attestation is signed with a GitHub OIDC token. Without that permission the publish fails outright rather than quietly shipping unsigned, which is the behaviour we want. --- .github/workflows/publish.yml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 9d2191dd..50ec8d5a 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -4,6 +4,13 @@ on: release: types: [published] +# npm provenance is signed with a GitHub OIDC token, so the job has to be +# allowed to mint one. Without id-token: write the publish fails rather than +# quietly falling back to an unsigned package. +permissions: + contents: read + id-token: write + jobs: publish: runs-on: ubuntu-latest @@ -42,9 +49,9 @@ jobs: - name: Publish to npm run: | if [ "${{ github.event.release.prerelease }}" = "true" ]; then - npm publish --access public --tag beta + npm publish --access public --tag beta --provenance else - npm publish --access public + npm publish --access public --provenance fi env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}