fix(runtime): composite keyframe wrapper geometry + transform (ADR 011 I7)#24
Merged
Merged
Conversation
…1 I7) `core.animation.play@1` rendered dead at the antenna — the box sat 100×100 pinned at (0,0), immobile, with no translateX and no fade (live frame-diff, tir I7 #2) — despite a correct render-bundle (keyframed `frame` wrapper with the target's geometry, animated transform/opacity, target nested beneath). Two distinct bugs in `@lumencast/runtime`'s keyframe path, both dropping the animated geometry: - The `KeyframePlayer` wrapped the subtree in a `display:contents` motion.div. `display:contents` generates no box, so the browser silently dropped the transform/opacity/filter framer-motion wrote — the wrapper geometry never composited and the nested target rendered at its default origin. The player is now a real compositing box (`position:absolute; inset:0`) and the containing block for the absolutely-positioned target, preserving its x/y. - `compileForFramer` emitted the authored `translateX`/`translateY` channels verbatim, but framer-motion animates transform via `x`/`y`; the translate was ignored even on a real box (only opacity survived). Mapped to framer keys. Both repair the EXISTING keyframe player (no new runtime primitive, ADR 011 §6 #6); shipped as a committed patch-package patch over @lumencast/runtime@0.6.0 since the code lives in the bundled dependency. Solar re-bundles the patched runtime into dist/. The wipe-cover degenerate cover is preserved (byte-pinned shape unchanged). New runtime test proves the wrapper composites translateX(400px)+opacity:1 with the nested 160×160 target at (80,360). Bumped to v0.2.9 so the release tgz carries the fix to Orion's static serve. Refs ADR 011 I7 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ClodoCapeo
added a commit
that referenced
this pull request
Jun 19, 2026
The prod dependency-audit CI job (npm audit --omit=dev --audit-level=high) fails on ws 8.0.0-8.20.1 (GHSA-58qx-3vcg-4xpx uninitialized memory disclosure + GHSA-96hv-2xvq-fx4p memory-exhaustion DoS), pulled transitively on the prod path via @lumencast/protocol/@lumencast/runtime (both pin ws ^8.18.0). The advisory was published after main's last green run, so it is environmental, not introduced by the runtime 0.7.0 bump (ws resolved identically under 0.6.0). Patched ws 8.21.0 is within the existing ^8.18.0 range; resolution-only bump (constraint -> ^8.21.0), no other deps drift. Prod audit now reports 0 vulnerabilities; lint/typecheck/test/build/npm ci all green. Refs #24 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ClodoCapeo
added a commit
that referenced
this pull request
Jun 19, 2026
* build(deps): bump @lumencast/runtime to 0.7.0 Resolve @lumencast/runtime ^0.6.0 -> ^0.7.0 (lockfile 0.7.0, transitive @lumencast/protocol 0.6.0 -> 0.7.0). Solar's adapter surface (mount, MountOptions, SolarError/SolarStatus) maps onto unchanged runtime types, so no adapter code change was needed. Re-target the ADR 011 I7 keyframe-compositing patch-package patch to 0.7.0: the fix is still not upstreamed (display:contents dead box + translateX/Y -> framer x/y mapping both absent in published 0.7.0), so the patch stays necessary. Regenerated against the 0.7.0 files (5 file diffs, same edits); obsolete 0.6.0 patch removed. npm ci re-applies it. Refs #24 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(test): adapt render test to runtime 0.7.0 asset host gate @lumencast/runtime 0.7.0 (LSML 1.2, ADR 002 #F - Bastion T1/T2) gates every image src through gateSrc() before the DOM, deny-by-default: T2 admits only https: schemes, T1 requires the URL host in the bundle's assets.allowedHosts. A bundle with no allowlist has its <img> omitted. The render-vocab test served http://x/logo.svg with no allowlist, so under 0.7 no <img> rendered and waitFor() timed out. Declare assets.allowedHosts: ["cdn.example"] and serve https://cdn.example/...; width/height/src assertions preserved. Legitimate runtime hardening, not a Solar regression. Document the bump in CHANGELOG (Unreleased). Refs #24 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * build(deps): bump ws to 8.21.0 to clear high advisory The prod dependency-audit CI job (npm audit --omit=dev --audit-level=high) fails on ws 8.0.0-8.20.1 (GHSA-58qx-3vcg-4xpx uninitialized memory disclosure + GHSA-96hv-2xvq-fx4p memory-exhaustion DoS), pulled transitively on the prod path via @lumencast/protocol/@lumencast/runtime (both pin ws ^8.18.0). The advisory was published after main's last green run, so it is environmental, not introduced by the runtime 0.7.0 bump (ws resolved identically under 0.6.0). Patched ws 8.21.0 is within the existing ^8.18.0 range; resolution-only bump (constraint -> ^8.21.0), no other deps drift. Prod audit now reports 0 vulnerabilities; lint/typecheck/test/build/npm ci all green. Refs #24 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Fixes the keyframe-animation runtime so
core.animation.play@1actually moves and fades at the antenna — the 3rd and last link of ADR 011 I7 (after the trigger fix Orion #163 and the lowering fix Orion #167 + oracle Solar #23).The render-bundle Orion serves was already correct (verified live): a keyframed
framewrapper carrying the target's static geometry (x:80,y:360, 160×160) + animatedtransform/opacitykeyframes, with the resolved target nested beneath. Yet the box rendered 100×100 pinned at (0,0), immobile, no translateX, no fade (live frame-diff, tir I7 #2). The leaf incremented and the bundle was sound — the pixel was dead. Root cause was entirely in the runtime renderer (@lumencast/runtime).Cause (runtime, fichier:ligne)
Two distinct bugs in
@lumencast/runtime@0.6.0's keyframe path, both silently dropping the animated geometry:src/render/keyframe-player.tsx:67(bundled indist/tree-…js) — theKeyframePlayerwrapped the played subtree in<motion.div style={{display:"contents"}}>. Adisplay:contentselement generates no box, so the browser never composites thetransform/opacity/filterframer-motion writes onto it. The wrapper's geometry never painted; the nested target rendered at its default origin.src/animate/keyframes.tspullTransform—compileForFrameremitted the authoredtranslateX/translateYkeys verbatim, but framer-motion animates transform via its shorthand motion keysx/y. So translateX was ignored even once the box composited (only the opacity fade survived — the second half of the live symptom).Fix
style={{position:"absolute", inset:0}}. It overlays the parent without disturbing sibling layout and, being positioned, becomes the containing block for the absolutely-positioned target nested beneath (Frame isposition:absolute; left:0; top:0), so the target's authored x/y are preserved while the animated channels composite onto live pixels.pullTransformmapstranslateX→x,translateY→yfor framer'sanimateobject.Both are repairs to the existing keyframe player (no new runtime primitive — ADR 011 §6 criterion #6). Shipped as a committed
patch-packagepatch over@lumencast/runtime@0.6.0(the buggy code is in the bundled dependency; Solar re-bundles the patched runtime intodist/). The patch is applied viapostinstall+pretest+prebuildso it lands deterministically (npm 11 may gatepostinstall;pretest/prebuildcover the CIcheckjob).Wipe-cover (degenerate cover) preserved: full-screen self-painting cover with no nested target still fills the screen and fades through the same keyframe path; its byte-pinned authored shape is unchanged.
Test added
tests/unit/animation-compositing.test.tsx— mounts Solar end-to-end over a fake LSDP/1.1 transport against a bundle shaped exactly like the live render-bundle, and proves on the actual DOM that the keyframe wrapper:display:contents(regression guard);translateX(400px)+opacity:1(both halves of the bug);The wipe-cover test's opacity helper reads off the new compositing box.
How the new build reaches the golive (étape pour Keeper)
The golive (
_keeper_ops/golive-solar.mjs) points Pulsar CEF at…/orion/static/solar/v{SOLAR_VERSION}/host/index.html, currently pinned to v0.2.8. Orion serves Solar fromORION_SOLAR_ROOT(/var/lib/orion/solar/v{N.N.N}/). This PR bumps Solar to v0.2.9 + CHANGELOG so:v0.2.9, Solar'srelease.ymlruns the full CI + E2E, buildsdist/(with the prebuild patch applied), and publishessolar-v0.2.9.tgz(contents ofdist/, incl.host/+ the patchedtree-…jschunk) to GitHub Releases. It also verifiespackage.jsonversion == tag.v0.2.9toSOLAR_VERSIONSin Orion's.github/workflows/ci.yml(Install Solar bundles step) — Orion's deploy extracts the tgz into$APP_PATH/solar/v0.2.9/(idempotent.installedmarker). Multiple versions are supported for canarying.SOLAR_VERSION = "0.2.9"in_keeper_ops/golive-solar.mjsso the next golive serves the patched runtime.CI
Local gate green and constated (not asserted): lint, typecheck,
vitest(37/37 incl. the new proof),npm run build,check:bundle(size budget + zero bare specifiers), prodnpm audit --omit=dev --audit-level=high(only 1 pre-existing moderatews, below threshold), lockfilenpm ci --ignore-scripts. E2E (host-bundle.spec.ts) not runnable locally (Chromium unavailable in this env) — it is a bundle-loadability smoke test unaffected by this change (module structure/import map unchanged); it runs green in CI vianpx playwright install --with-deps chromium.Surface sensible / écarts vs ADR
patch-package(+ transitive dev deps), not named in ADR 011. Justified: the buggy code is in the bundled@lumencast/runtimedependency with no Solar-side render seam; patch-package is the minimal, reversible mechanism to land the fix in the served bundle without forking the runtime. No runtime (prod) dependency added; no prod-high audit regression.Refs ADR 011 I7
🤖 Generated with Claude Code