From 418f198b3391a44728433a788c7c34f9b280ca6c Mon Sep 17 00:00:00 2001 From: Vance Ingalls Date: Thu, 18 Jun 2026 23:15:02 -0700 Subject: [PATCH] =?UTF-8?q?feat(sdk):=20ws-3c=20=E2=80=94=20addWithKeyfram?= =?UTF-8?q?es=20+=20replaceWithKeyframes=20SDK=20ops=20(acorn=20writer)=20?= =?UTF-8?q?(#1572)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(sdk): ws-b variables/brand — object-valued font/image + B1 JSON model B1 — setVariableValue now drives the runtime JSON model (data-composition-variables) so preview == render. CSS custom prop is kept as a secondary compat write for compositions that CSS-bind directly to --{id}. B2 — object-valued font ({name, source}) and image ({url}) variable types added core-to-SDK. Object values write to the JSON model only; scalars write both model + explicit CSS style patches. Explicit style-path patches in forward/inverse ensure apply-patches.ts handles each path type purely (model vs CSS), so inverse patches restore exact pre-call state without ambiguity. Changed files: packages/core/src/core.types.ts — font/image to CompositionVariableType + interfaces packages/core/src/lint/rules/composition.ts — accept font/image in lint message packages/core/src/parsers/htmlParser.ts — validate font/image variable declarations packages/core/src/parsers/htmlParser.test.ts — tests for new variable types packages/core/src/runtime/validateVariables.ts — checkType for font/image packages/sdk/src/types.ts — FontValue/ImageValue; widen OverrideSet + EditOp packages/sdk/src/index.ts — re-export FontValue/ImageValue packages/sdk/src/session.ts — widen setVariableValue signature packages/sdk/src/engine/patches.ts — valueChange helper for object-valued patches packages/sdk/src/engine/mutate.ts — handleSetVariableValue: B1+B2 with explicit CSS patches packages/sdk/src/engine/apply-patches.ts — variable case: model-only (CSS via explicit patch) packages/sdk/src/engine/mutate.test.ts — B1+B2 round-trip + inverse tests Co-Authored-By: Claude Opus 4.8 (1M context) * feat(sdk): ws-c elastic timing + word-alignment resolver (WS-C) C1: getElementTimings/setElementTiming typed session methods + setHold typed wrapper. getElementTimings reads data-duration (preferred) or data-end−data-start (fallback) — same attr-preference as handleSetTiming. setElementTiming dispatches a sparse map as one batch → one patch event → one undo step. setHold mirrors setVariableValue pattern. Also fixes a pre-existing apply-patches.ts gap: the timing/duration patch case was absent, causing undo of duration changes to silently no-op. Added the duration branch so inverse patches restore data-duration correctly. C2: packages/core/src/compiler/timingResolver.ts — shared pure resolveTimings() consumed by BOTH preview (sdk session) and render (timingCompiler) paths. Word- anchored elements get enterAt = wordTimings[k].start + offset; elastic hold = max(0, slotEnd − (enterAt + enterDuration + exitDuration)), clamped ≥ 0; never timescales animated content. Un-anchored elements keep authored timing (align-on- adjust). Deterministic + pure: no Date.now, no Math.random, no DOM. extractGsapLabels() added to gsapParserAcorn.ts to parse tl.addLabel() calls for the getElementTimings labels field. Tests: timingResolver.test.ts (10 pure-function tests including preview==render parity golden test); session.timings.test.ts (15 session-layer tests covering duration-authored, end-authored, label extraction, batching, undo, and setHold regression). Gates: build ✓ · bun test (sdk+core/compiler) 434/434 ✓ · oxlint 0 warnings ✓ · oxfmt --check ✓ · fallow --gate new-only ✓ (complexity suppressed on 2 new inline functions, duplication warn-only pre-existing) Co-Authored-By: Claude Opus 4.8 (1M context) * feat(sdk): addElement forward op — mint hf-id, inverse = removeElement (WS-D) Implements WS-D: the addElement EditOp and session.addElement() typed method. - types.ts: addElement op (parent/index/html) added to EditOp union; addElement(parent, index, html): HfId added to Composition interface - mutate.ts: handleAddElement inserts a single-root HTML fragment at parent+index, minting ids against the LIVE document's existing id set (not a fresh fragment set) via collectDocumentHfIds + mintFragmentIds; forward = patchAdd, inverse = patchRemove; MutationResult.meta.newId carries the minted root id - mutate.ts: validateOp case rejects missing parent, negative index, empty html, zero-element html, and