Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
caf50eb
chore: add agent skills config
idleberg Oct 31, 2026
0c8ca66
docs: add ADR for ESM + CJS only output
idleberg Nov 1, 2026
e1c5043
chore: switch agent issue tracker to local markdown
idleberg Nov 1, 2026
044bca7
docs: add modernize spec
idleberg Nov 1, 2026
ca4b343
docs: add modernize tickets
idleberg Nov 1, 2026
d22d0d9
chore: restore green baseline and move CI to GitHub Actions
idleberg Nov 1, 2026
0dd0d8f
docs: update ticket status
idleberg Nov 1, 2026
5456329
chore: upgrade to TypeScript 6 on the existing toolchain
idleberg Nov 1, 2026
e984298
refactor: replace lodash-es with native code
idleberg Nov 1, 2026
3943e0b
docs: update ticket status
idleberg Nov 1, 2026
62dd0c3
refactor: replace eventemitter3 with a minimal emitter and pin stats.js
idleberg Nov 1, 2026
182677a
docs: update ticket status
idleberg Nov 1, 2026
1e80fb4
build: replace pegjs with peggy and commit the generated parser
idleberg Nov 1, 2026
fd42925
docs: update ticket status
idleberg Nov 1, 2026
887f3ab
test: replace karma and mocha-webpack with vitest
idleberg Nov 1, 2026
d1acb80
docs: update ticket status
idleberg Nov 1, 2026
711e803
build: replace webpack with tsdown ESM + CJS output
idleberg Nov 1, 2026
92e67d6
docs: update ticket status
idleberg Nov 1, 2026
556c061
docs: serve examples via vite and document ESM CDN usage
idleberg Nov 1, 2026
2d915f4
docs: update ticket status
idleberg Nov 1, 2026
dcac87d
build: TypeScript 7 and oxlint replace TypeScript 6 and tslint
idleberg Nov 1, 2026
d458248
docs: update ticket status
idleberg Nov 1, 2026
cbe7887
build: pnpm replaces npm
idleberg Nov 1, 2026
6ff24a2
build: compile expression grammar at build time
idleberg Nov 1, 2026
8b33120
chore: fix spec smells
idleberg Nov 1, 2026
a67fe0c
chore: update tsconfig
idleberg Nov 1, 2026
341c745
fix: load builtin resources from relative resources/
idleberg Nov 1, 2026
814c185
chore: update ts target
idleberg Nov 1, 2026
d839d0b
docs: add cdn ticket
idleberg Nov 1, 2026
ed742a8
chore: update seedrandom to 3.0.5
idleberg Nov 1, 2026
816d13a
chore: update dependencies
idleberg Nov 1, 2026
b4f74fa
chore: rename fork, add contributors
idleberg Nov 1, 2026
edf6df1
docs: add badges, rename imports
idleberg Nov 1, 2026
956543e
fix: update imports
idleberg Nov 1, 2026
eea2dcc
ci: bump workflow actions
idleberg Nov 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 0 additions & 28 deletions .circleci/config.yml

This file was deleted.

27 changes: 27 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: CI

on:
push:
branches: [master]
pull_request:

jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [22, 24]
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: ${{ matrix.node-version }}
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm exec playwright install --with-deps chromium-headless-shell
- run: pnpm lint
- run: pnpm unit-test
- run: pnpm func-test
- run: pnpm build
- run: pnpm smoke-test
2 changes: 0 additions & 2 deletions .npmignore

This file was deleted.

8 changes: 8 additions & 0 deletions .oxlintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": ["typescript", "unicorn", "oxc"],
"rules": {
// unused params mostly match interface or override signatures
"no-unused-vars": ["warn", { "args": "none" }]
}
}
25 changes: 25 additions & 0 deletions .scratch/modernize/issues/01-baseline.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# 01: Baseline: green from a clean checkout, CI on GitHub Actions

**What to build:** A clean clone of `master` installs with one command and passes lint, unit and func tests on the existing webpack 3 / karma toolchain, and GitHub Actions checks every PR. See the spec's "PR 1: Baseline" and the Phase 0 baseline under Testing Decisions.

**Blocked by:** None (can start immediately)

**Status:** ready-for-human

- [x] Lockfile regenerated so a fresh install succeeds without `--no-package-lock`. Dependencies pinned where Phase 0 found drift: early-2018 @types set, pegjs-loader 0.5.4, tslint 5.9.1
- [x] Func tests default to a headless Chrome launcher with the SwiftShader flags and get WebGL
- [x] Mocha timeout for func tests is 10s
- [x] Locally from a clean clone: lint passes, unit 6/6, func 60/60. Thresholds and reference PNGs unchanged
- [ ] A GitHub Actions workflow runs lint, unit and func on Node 16 with Chrome and passes on the PR
- [x] CircleCI config deleted
- [x] Hand-written `gitHead` removed from package metadata

## Comments

**Agent, 2026-11-01:** Implemented; only the CI checkbox is open, it needs a push and a green PR run.

- Transitive @types pins (node, lodash, glob, normalize-package-data) are done via npm `overrides`.
- Added `browserNoActivityTimeout: 60000` in karma.conf.js: karma's 10s default killed the browser during the slow DynamicMovement cases. Not in the spec.
- Local func: 60/60 on 2 of 3 runs. The failing run was DynamicMovement hitting its own `this.timeout(10000)` at load average ~30 on 10 cores.
- CI risks: headless Chrome on ubuntu-24.04 may need `--no-sandbox`; DynamicMovement (500 frames on SwiftShader) may be slow on the runner.
- Convolution.test.ts sets `this.timeout(5000)`, which overrides the new 10s default. Left unchanged.
19 changes: 19 additions & 0 deletions .scratch/modernize/issues/02-typescript-6.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# 02: TypeScript 6 on the existing toolchain

**What to build:** The codebase compiles with TypeScript 6 and an es2022 lib on the existing webpack 3 / karma toolchain, so later tickets can use modern native methods. See the spec's "PR 2: TypeScript 6".

**Blocked by:** 01 (Baseline)

**Status:** resolved

- [x] TypeScript 6 installed. The TS config sets explicitly: `strict` off, lib dom + es2022, emit target es2017, module commonjs, types mocha + node
- [x] eventemitter3 imported as a default import, so the base model's emitter type-checks under always-on interop
- [x] The image-loading error callback in the resource manager is typed as returning a boolean, matching how it's used
- [x] Unit-test mocks injected via inject-loader are shaped as ES modules, so default imports resolve
- [x] Type check reports 0 errors
- [x] Lint, unit 6/6 and func 60/60 pass locally and in CI. Thresholds and PNGs unchanged
- [x] Dev and CI stay on Node 16. The legacy OpenSSL flag is added only if the toolchain needs it

## Comments

**Agent, 2026-11-01:** Implemented. Local on Node 16.20.1: tsc 0 errors, lint passes, unit 6/6, func 60/60, no OpenSSL flag needed. Also needed beyond the spec: `@types/mocha` 5.2.7 and the `@types/node` override bumped to 16.18.126 (the 2018 typings don't parse under TS 6), `require.context` cast in the func test index, a seedrandom default import in the Mirror test (it failed at runtime under interop), and the WebGL-only `preserveDrawingBuffer` dropped from a 2d `getContext` call. CI passed after the push.
13 changes: 13 additions & 0 deletions .scratch/modernize/issues/03-replace-lodash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# 03: Replace lodash-es with native code

**What to build:** webvs no longer depends on lodash-es. Every call, including the ones inside the expression grammar, becomes plain native JavaScript, and rendering stays identical. See the spec's "PR 3: Replace lodash-es".

**Blocked by:** 02 (TypeScript 6)

**Status:** resolved

- [x] No lodash import or require remains in source, grammar or tests
- [x] Replacements are native equivalents, not a home-grown lodash-style utility layer. `uniqueId` becomes a module-local counter, `flow`/`partialRight` become plain composition
- [x] lodash-es and its @types removed from package metadata and lockfile
- [x] Func 60/60 with thresholds and PNGs unchanged. Unit 6/6. Lint passes
- [x] CI green
14 changes: 14 additions & 0 deletions .scratch/modernize/issues/04-emitter-and-stats.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# 04: Minimal emitter and pinned stats.js

**What to build:** eventemitter3 is replaced by a minimal emitter in the base model, so consumers keep `on`, `addListener`, `removeListener` and `emit` with the same signatures. stats.js is pinned to a real version range. See the spec's "PR 4: Emitter and stats.js".

**Blocked by:** 02 (TypeScript 6)

**Status:** resolved

- [x] The base model provides exactly `on`, `addListener`, `removeListener` and `emit`, backed by a map of event name to a set of callbacks. Signatures are compatible with eventemitter3 for those four
- [x] `listenTo` and its cleanup counterpart work against the new emitter. `Main`, `Container`, `Component` and `ResourceManager` need no API change
- [x] eventemitter3 removed from package metadata and lockfile
- [x] stats.js pinned to `^0.17.0`
- [x] Func 60/60 with thresholds and PNGs unchanged (this covers resize and resource wait/ready events end to end). Unit 6/6. Lint passes
- [x] CI green
13 changes: 13 additions & 0 deletions .scratch/modernize/issues/05-peggy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# 05: peggy with a committed parser

**What to build:** The expression grammar is compiled by peggy instead of pegjs. The generated parser is committed and imported as a plain module, so no build step needs a grammar loader. See the spec's "PR 6" notes on peggy, pulled forward.

**Blocked by:** 03 (Replace lodash-es), because the grammar must be lodash-free first

**Status:** resolved

- [x] pegjs and pegjs-loader removed. peggy added as a dev dependency
- [x] Generated parser committed. The expression compiler imports it directly, with no loader rule
- [x] A `gen:grammar` script regenerates the parser from the grammar. Re-running it on an unchanged grammar produces no diff
- [x] Still builds and tests on webpack 3: func 60/60, unit 6/6, lint passes
- [x] CI green
18 changes: 18 additions & 0 deletions .scratch/modernize/issues/06-vitest.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# 06: vitest for unit and func tests

**What to build:** Unit and func tests run on vitest. The WebGL pixel tests run in browser mode on headless Playwright Chromium with SwiftShader, the old karma / mocha-webpack stack is gone, and development moves to a current Node. See the spec's "PR 5: vitest".

**Blocked by:** 03 (Replace lodash-es), 04 (Minimal emitter and pinned stats.js), to keep the code-first order

**Status:** resolved

- [x] Unit tests run under vitest with `vi.fn` / `vi.mock`. typemoq, ts-mockito, inject-loader, chai and mocha-webpack removed
- [x] Func tests run in vitest browser mode on Playwright Chromium, headless, with the SwiftShader flags. Reference PNGs and resources served as static files. Test discovery uses vitest globs, not the webpack context index
- [x] `testTimeout` 10s
- [x] Func 60/60 with thresholds and reference PNGs unchanged. Any change needs a written reason in the PR
- [x] karma and its plugins removed
- [x] Dev on Node ≥22. CI runs lint, unit and func on Node 22 and 24, and passes

## Comments

**Agent, 2026-11-01:** Implemented. Local on Node 24.20.0: tsc 0 errors outside the stale, gitignored `dist/`, lint passes, unit 9/9, func 60/60. Thresholds and PNGs unchanged. vitest 5.0.5, @vitest/browser-playwright 5.0.5, playwright 1.64.1. Also needed beyond the spec: `@types/node` 22.20.5 replaces the Node 16 override (vitest's peer range), the config is `vitest.config.mts` because the package isn't ESM yet, mocha's `before`/`after` became `beforeAll`/`afterAll`, and the per-suite `this.timeout` calls were dropped in favour of the global 10s (Convolution goes from 5s to 10s). Unit mocks use a small Proxy `autoMock` in place of typemoq's dynamic mocks. webpack 3 still builds on Node 24 without the legacy OpenSSL flag. vitest 5 requires Node ≥22.12, which enforces "dev on Node ≥22" until ticket 07 adds `engines`. CI passed on Node 22 and 24 after the push.
18 changes: 18 additions & 0 deletions .scratch/modernize/issues/07-tsdown-esm-cjs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# 07: tsdown build: ESM + CJS only (4.0.0 break)

**What to build:** The package is built by tsdown as ESM + CJS with type declarations, and the `window.Webvs` global build is gone, per the ADR on ESM + CJS only. npm consumers keep `import Webvs from 'webvs'` and can also use named imports. See the spec's "PR 6".

**Blocked by:** 05 (peggy with a committed parser), 06 (vitest for unit and func tests)

**Status:** resolved

- [x] tsdown builds the public entry point to ESM and CJS with type declarations, on a neutral platform. The resource pack URL (defaulting to unpkg for the matching version) and the library version are injected via define
- [x] The default-export object (`Main`, `Component`, `ShaderProgram`, `WebAudioAnalyser`, `AnalyserAdapter`) is unchanged. The same five are also named exports
- [x] Package metadata: `"type": "module"`, an `exports` map (import / require / types), `files` limited to build output plus resources, `engines` node ≥22. Old `main` and `readmeFilename` removed. `.npmignore` deleted
- [x] webpack, ts-loader, webpack-serve, cross-env, shx, npm-run-all and typedoc (with its docs script) removed
- [x] Package smoke check passes: `require('webvs')` and `import('webvs')` both expose `Main` via default and named exports, type declarations resolve, and the pack listing contains only build output and resources
- [x] Func 60/60 with thresholds and PNGs unchanged. Unit 6/6. Lint passes. CI green, including the smoke check

## Comments

**Agent, 2026-11-01:** Implemented. CI passed on Node 22 and 24 after the push. Local on Node 24.20.0: tsc 0 errors, lint passes, unit 9/9, func 60/60, thresholds and PNGs unchanged. tsdown 0.23.1 pinned (newest release at least two weeks old). The build emits `dist/index.{js,cjs,d.ts,d.cts}`, with `stats.js` left external. `outputOptions.exports: "named"` stops the CJS mixed-exports warning; `require('webvs')` exposes both `.default` and the named exports. The smoke check (`npm run smoke-test`, run after `npm run build`) resolves the package through its own exports map (self-reference) for both `require` and `import`. It type-checks an ESM fixture and a CJS `.cts` fixture against `index.d.ts` and `index.d.cts`, and asserts the `npm pack` listing has only `dist/`, `resources/` and the files npm always includes. CI gains build + smoke steps. The `dev` and `clean` scripts went with webpack-serve and shx. Ticket 08 restores a dev server via vite. `version` is still 3.0.0, so the baked-in resource URL points at `webvs@3.0.0` until the release bump.
14 changes: 14 additions & 0 deletions .scratch/modernize/issues/08-examples-readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# 08: Examples and README on the module build

**What to build:** The examples page runs straight from source through a vite dev server, and the README shows CDN users how to load webvs as an ES module now that the global build is gone.

**Blocked by:** 07 (tsdown build: ESM + CJS only)

**Status:** resolved

- [x] The examples' test page is an ES module importing the library from source. A dev script serves it via vite, and the preset files stay plain scripts
- [x] Manual check: a preset renders in the browser and the `rsrcMan.on('ready')` listener fires (record the result in the PR)
- [x] The README's CDN section uses a `<script type="module">` import from unpkg. No reference to `window.Webvs`, `dist/webvs.js` or `webvs.min.js` remains
- [x] The README's npm usage section is still accurate (default import, plus a named-import example)

**Agent, 2026-11-01:** Implemented. CI passed after the push. Local: tsc 0 errors, unit 9/9, func 60/60, smoke check passes. `npm run dev` runs vite with `vitest.config.mts`, reusing its defines, and opens `/examples/`. `test-page.js` is a module importing `../index.ts`. The preset scripts are plain `defer` scripts, so they register before `initPage` runs on `DOMContentLoaded`. `vite` 8.3.5 is now a declared devDependency. Manual check in Chrome: Silk Strings renders. Loading "pillow fight" fetched three `.bmp` files from `/resources/` (200), and the overlay went block→none, so the `rsrcMan` `wait` and `ready` listeners fired. README CDN: unpkg's `?module` no longer rewrites bare imports, and stats.js has no ESM build. So the snippet adds an import map that sends `stats.js` to jsDelivr's `+esm`, and it was tested against a locally served `dist/index.js`. The `webvs@4` URL resolves only after the 4.0.0 publish.
18 changes: 18 additions & 0 deletions .scratch/modernize/issues/09-typescript-7.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# 09: TypeScript 7

**What to build:** The project compiles with TypeScript 7, the native compiler, and drops the TS config workarounds that only existed for webpack 3 and inject-loader.

**Blocked by:** 07 (tsdown build: ESM + CJS only)

**Status:** resolved

- [x] TypeScript 7 replaces 6
- [x] TS config: es2017 emit target and commonjs module workarounds removed. Module resolution is bundler style. `strict` stays off
- [x] Type check 0 errors. Type declarations still generate
- [x] Package smoke check, func 60/60 (thresholds and PNGs unchanged), unit and lint pass. CI green

## Comments

**Agent, 2026-11-01:** Implemented. CI green after the push. TypeScript 7.0.2. Local on Node 24: tsc 0 errors, lint passes, unit 9/9, func 60/60 with thresholds and PNGs unchanged, build emits `.d.ts`/`.d.cts`, smoke check passes. The TS config drops `target` and `module: commonjs` for `module: preserve` + `moduleResolution: bundler`. Two things beyond the spec:
- `useDefineForClassFields: false` is now explicit. Before, it followed from the es2017 target. TS 7's ESNext default switches on define semantics. `Component`'s constructor calls `init()`, which sets subclass fields, so define semantics would reset every uninitialized subclass field to `undefined` after `super()`. tsc flagged it as TS2612 on 18 `opts` redeclarations.
- tslint 5.9 can't run on TS 7 because TS 7 ships no JS API, and an npm override can't nest the peer dependency. With the maintainer's OK, ticket 11 (oxlint) is folded into this one. oxlint 1.88.0, `.oxlintrc.json` with the default plugins (typescript, unicorn, oxc), and lint runs with `--deny-warnings`. The only default rule that fired was `no-unused-vars`. Unused imports, dead `lastR/G/B` in SuperScope, unused test bindings and dead `tslint:` directives are removed. Unused-parameter checks are turned off (`args: none`) because those parameters match interface and override signatures. The generated parser is ignored.
12 changes: 12 additions & 0 deletions .scratch/modernize/issues/10-pnpm.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# 10: pnpm

**What to build:** The repo uses pnpm instead of npm, locally and in CI. Every dependency the code uses is declared explicitly.

**Blocked by:** 08 (Examples and README on the module build), 09 (TypeScript 7)

**Status:** ready-for-agent

- [x] pnpm lockfile replaces the npm lockfile. A `packageManager` field pins the pnpm version
- [x] Any undeclared dependency exposed by strict `node_modules` is added explicitly
- [ ] CI installs with pnpm and runs lint, unit, func and the smoke check, all passing
- [x] README and scripts don't refer to npm-specific commands for development
17 changes: 17 additions & 0 deletions .scratch/modernize/issues/11-oxlint.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# 11: oxlint

**What to build:** Linting runs on oxlint instead of tslint, without turning the change into a refactor. See the spec's "PR 8: oxlint".

**Blocked by:** 10 (pnpm)

**Status:** resolved

- [x] oxlint with default rules plus the TypeScript plugin replaces tslint. tslint and its config removed
- [x] Small mechanical findings fixed
- [x] Rules that would need real code changes are turned off in config, each with a one-line comment giving the reason
- [x] No formatter added
- [x] Lint, unit, func 60/60 and the smoke check pass. CI green

## Comments

**Agent, 2026-11-01:** Folded into 09: TS 7 broke tslint, see the comment there. All checklist items are covered by that change.
17 changes: 17 additions & 0 deletions .scratch/modernize/issues/12-build-time-grammar.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# 12: Compile the expression grammar at build time

**What to build:** The expression grammar is imported directly and compiled by a small inline peggy plugin that the build, the tests and the dev server all share. They always use the current grammar, and there's no separate regeneration step. This replaces ticket 05's committed parser; the spec has been amended to match.

**Blocked by:** None (can start immediately)

**Status:** ready-for-agent

- [x] Grammar compiled at build time by an inline plugin, with no new dependency. rollup-plugin-peggy was rejected: it only has a default export, has a rollup peer dependency, and hasn't been verified under rolldown
- [x] The parser still exposes a named `parse` export, typed by a short ambient module declaration
- [x] The committed generated parser and its types, the `gen:grammar` script and the oxlint ignore for the generated file are removed
- [x] Build output (ESM + CJS + types) still contains the parser
- [ ] Lint, unit, func and the smoke check pass, the dev server examples run, CI green

## Comments

**Agent, 2026-11-01:** Plugin lives in `peggy.plugin.ts` (a `load` hook returning `moduleType: "js"`), registered in `tsdown.config.ts` and `vitest.config.mts`. tsdown loads its config through Node's native import, so the plugin is imported with an explicit `.ts` extension, which needs `allowImportingTsExtensions` in tsconfig. Lint, unit 9/9, func 60/60, build and smoke pass locally, and the dev server serves the compiled grammar. CI pending.
Loading
Loading