diff --git a/.changeset/tranche-b-3-1-0.md b/.changeset/tranche-b-3-1-0.md new file mode 100644 index 0000000..c3c529c --- /dev/null +++ b/.changeset/tranche-b-3-1-0.md @@ -0,0 +1,32 @@ +--- +"@mkbabb/keyframes.js": minor +--- + +Tranche B — the engine's debt transposed, the demo made true. + +**Engine (gestalt transposition, net-deletion).** A typed `Easing` +(`{ fn, css? }`) replaces the former Symbol-on-a-closure side channels; +`resolveEasing(name)` (async, fail-explicit) + `toEasing` replace the +`EasingResolvable` resolver — the light engines accept a callable +`TimingFunction` or a typed `Easing` only (a string name throws +`AnimationOptionError`; resolve it up front). One `RAFPlayback` driver +(`play`/`drive`/`loop`) owns every rAF loop; one `withReducedMotion` gate +drives every reduced-motion snap; one explicit rest-position/fill contract +(`settle()` pure teardown, `reset()` explicit rewind, `restPosition` from +`fillMode`). Option setters are fail-explicit: a malformed PRESENT value +throws a typed `AnimationOptionError` (genuine omission still defaults). +WAAPI delegation is restored (the prior renderer check was bind-broken) and +made faithful: it delegates only when the easing has a CSS twin, and +`stop()`/`reset()` cancel the compositor animation. New exports: `Easing`, +`resolveEasing`, `toEasing`, `AnimationOptionError`, `UnknownEasingError`, +`RAFPlaybackOptions`, `Tickable`. `getTimingFunction` now resolves CSS +`steps()`/`step-start`/`step-end` (Easing Level 1 complete). + +**Boundary gate widened.** `proof:boundary` now proves every light barrel +export (not just `SpringProgress`), the heavy engine's dynamic boundary, +and the absence of dormant static value.js specifiers. + +**Demo + CI** (not part of the published library): the production demo +build is repaired (it was shipping blank), the four blank scenes render, +the cube is no longer clipped, and CI gains demo-paint (inv γ) + occlusion +(inv δ) gates. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f584f92..5fe0cd8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,19 +1,25 @@ # ci — PR + push-to-master gate matrix for @mkbabb/keyframes.js. # -# LIBRARY-SCOPED gate (A inv β — the library build is glass-ui-free). Every -# step touches only the publishable surface (`src/` → `dist/`): +# TWO gate postures, by inv β: # -# check:lib — tsc --noEmit -p tsconfig.lib.json (src/ only; never demo) -# build:lib — vite build --mode production (library entry; externals -# value.js + glass-ui; demo graph untouched) -# test — vitest (test/, jsdom) (no glass-ui imports) -# proof:boundary — build a spring-only entry and count value.js bytes + -# static engine-* edges (A inv α — the boundary is gated) +# gates (LIBRARY) — glass-ui-FREE. Every step touches only the +# publishable surface (`src/` → `dist/`): +# check:lib — tsc --noEmit -p tsconfig.lib.json (src/ only; never demo) +# build:lib — vite build --mode production (library entry; externals +# value.js + glass-ui; demo graph untouched) +# test — vitest (test/, jsdom) (no glass-ui imports) +# proof:boundary — bundle EVERY light barrel entry and assert 0 value.js + +# 0 static engine edge per entry, the heavy engine dynamic-only, +# no dormant static specifier (A inv α, widened in B.W2) +# `@mkbabb/glass-ui` is an OPTIONAL `file:../glass-ui` dependency; a clean +# runner has no sibling checkout, so `npm ci` cleanly SKIPS it +# (`optional: true`) — verified by the /tmp clean-runner archive run. The +# library gate therefore runs glass-ui-free. # -# `@mkbabb/glass-ui` is an OPTIONAL `file:../glass-ui` dependency (demo-dev -# only). A clean runner has no sibling checkout, so npm skips it and the -# library gate runs glass-ui-free. The demo / gh-pages build is a separate -# dev-machine arm the release path does not require. +# demo-smoke (DEMO) — the demo legitimately needs glass-ui, so this is a +# SEPARATE job that installs the published package + a browser and asserts +# the BUILT gh-pages demo paints (B inv γ) and occludes nothing (B inv δ). +# A blank/tree-shaken build or an occluded page fails CI here. # # Cross-repo dependency-order note: keyframes.js resolves @mkbabb/value.js # via the npm registry (^0.10.0). A breaking value.js publish surfaces here @@ -29,11 +35,12 @@ on: jobs: gates: + name: library gate (glass-ui-free) runs-on: ubuntu-latest timeout-minutes: 10 steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 + - uses: actions/checkout@v5 + - uses: actions/setup-node@v5 with: node-version: 24 cache: "npm" @@ -42,7 +49,55 @@ jobs: run: npm run check:lib - name: build (library) run: npm run build:lib + - name: dts artefact is real (not a 12-byte empty stub) + run: | + test -f dist/keyframes.d.ts + bytes=$(wc -c < dist/keyframes.d.ts) + echo "dist/keyframes.d.ts = $bytes bytes" + test "$bytes" -gt 10000 + grep -q "class Animation" dist/keyframes.d.ts + grep -q "loadAnimationEngine" dist/keyframes.d.ts - name: test run: npm test -- --run - - name: proof:boundary (value.js static/dynamic boundary gate) + - name: proof:boundary (value.js static/dynamic boundary gate, all light entries) run: npm run proof:boundary + + demo-smoke: + name: demo gate (inv γ paints + inv δ occlusion-free) + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - uses: actions/checkout@v5 + - uses: actions/setup-node@v5 + with: + node-version: 24 + cache: "npm" + # The demo arm needs glass-ui (the library gate does not). The + # PUBLISHED tarball ships only `dist/styles/` while tailwind's CSS + # `@import "@mkbabb/glass-ui/styles"` resolves the SOURCE's root + # `styles/` (it does not honor the package `exports` map) — so the + # demo build resolves glass-ui ONLY against the sibling source the + # `file:../glass-ui` optional dep points at. Check out + build that + # sibling (it consumes keyframes/value.js from the REGISTRY — no + # circular sibling dep), so the demo build resolves it exactly as a + # dev machine does. + - name: check out + build the glass-ui sibling (file:../glass-ui) + run: | + git clone --depth 1 https://github.com/mkbabb/glass-ui.git "$GITHUB_WORKSPACE/../glass-ui" + cd "$GITHUB_WORKSPACE/../glass-ui" + npm ci + npm run build + - name: npm ci (picks up the built file:../glass-ui) + run: npm ci + - name: install playwright (demo-gate only; --no-save keeps the lib posture) + run: npm i --no-save @playwright/test + - name: install chromium + run: npx playwright install --with-deps chromium + - name: build the demo (gh-pages) + run: npm run gh-pages + - name: inv γ — the demo cannot ship blank + run: node scripts/demo-smoke.mjs + - name: inv δ — no page occludes on any viewport + run: node scripts/occlusion-gate.mjs + env: + KF_REQUIRE_BROWSER: "1" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a9ce959..93e4dbf 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -29,8 +29,8 @@ jobs: publish: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 + - uses: actions/checkout@v5 + - uses: actions/setup-node@v5 with: node-version: 24 registry-url: "https://registry.npmjs.org" diff --git a/demo/@/components/custom/animation-controls/AnimationControlsGroup.vue b/demo/@/components/custom/animation-controls/AnimationControlsGroup.vue index 0b8207d..9fddc8e 100644 --- a/demo/@/components/custom/animation-controls/AnimationControlsGroup.vue +++ b/demo/@/components/custom/animation-controls/AnimationControlsGroup.vue @@ -134,11 +134,18 @@ + stage at narrow widths (Qσ V1). Desktop: the stage spans the FULL + 3-col grid (col 1-4) so the subject centers in the viewport, NOT + in cols 2-3 — which, when the controls pane is closed/hidden, + collapsed the `1fr 1fr` tracks to zero width and jammed the cube + off the right edge (B.W3 BLOCKER: the cube was ~half-clipped at + 1280/1440). The controls-pane (col-1, z-controls, position: + relative) overlays the stage's left edge when open — its own + 400px backdrop sits above the centered stage, so an open pane + frames the subject without shifting it. -->