mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 04:38:33 +00:00
0f287ee0b6275091bd6c34491b85ddf628deb00d
152
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
dd6fad6bb2 |
fix(sdk): code-review follow-ups (WS-B/C/3.C, #1569/#1570/#1572) (#1588)
Addresses the still-outstanding review concerns from merged PRs #1569 / #1570 / #1572 not already hoisted into #1573. WS-B (#1569): - validateVariables requires discriminant fields for object-valued font/image ({name,source} / {url}); a {name:42} font or {foo:42} image previously passed runtime validation and surfaced as a bogus font-family / missing image. - Dropped ImageValue's [key:string]:unknown index signature (let any {url}-shaped object through, swallowed typos); explicit alt?/fit? instead. - Documented the OverrideSet widening for SDK consumers. WS-C (#1570): - getElementTimings caches parsed GSAP labels by exact script text (avoids a full acorn re-parse per read; content-key invalidates on edit). - Documented end-inclusive label window + best-effort extractGsapLabels catch. WS-3.C (#1572): - Added typed Composition.addWithKeyframes / replaceWithKeyframes (was asymmetric with addGsapTween; Studio had to use raw dispatch). - Extracted shared KeyframeSpec type; documented position as seconds/number-only. Gates: build + core 18/18 + sdk 19/19 + oxlint + oxfmt + fallow + typecheck all green. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
418f198b33 |
feat(sdk): ws-3c — addWithKeyframes + replaceWithKeyframes SDK ops (acorn writer) (#1572)
* 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) <noreply@anthropic.com>
* 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) <noreply@anthropic.com>
* 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 <script> in html
- session.ts: typed addElement(parent, index, html) returns minted id
via result.meta.newId
- mutate.test.ts: 16 tests covering insert position, append semantics,
id uniqueness, content-collision rehash, nested fragments, forward/
inverse symmetry, undo, add/undo/redo stability, parent:null body
insertion, serialize round-trip, and all five validateOp rejection codes
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(sdk): ws-3c — addWithKeyframes + replaceWithKeyframes SDK ops (acorn writer)
Port add-with-keyframes / replace-with-keyframes from the server recast path
to the acorn/magic-string writer and expose them as typed SDK EditOps.
- gsapWriterAcorn.ts: extend buildKeyframeObjectCode and
addAnimationWithKeyframesToScript to accept `auto?: boolean` on keyframes
(emits `_auto: 1` matching the recast writer)
- types.ts: add `addWithKeyframes` and `replaceWithKeyframes` to EditOp union
- mutate.ts: add handleAddWithKeyframes, handleReplaceWithKeyframes, and
applyGsapWithKeyframesOp dispatch sub-function; validateOp cases for both
- sdkCutover.ts: add sdkAddWithKeyframesPersist + sdkReplaceWithKeyframesPersist
(shared via dispatchWithKeyframes to eliminate clone)
- useGsapAnimationOps.ts: wire addWithKeyframes + replaceWithKeyframes
callbacks with SDK-first / server fallback pattern
- gsapWriter.parity.test.ts: add parity tests for _auto endpoint round-trip and
replaceWithKeyframes (remove + addWithKeyframes) differential golden harness;
import removeAnimationFromScript from both writers
Landmine note: tween IDs are position-derived — replaceWithKeyframes removes the
old tween (renumbering survivors) then inserts the replacement at the end; the
MutationResult patch pair restores the whole GSAP script on undo, not a per-ID
inverse, so ID-held references in callers must re-parse after structural edits.
Gate: WS-3.F (retire recast / executeGsapMutation) — NOT started here.
Remaining Studio callers (gsapDragCommit, gsapRuntimeBridge, useGestureCommit,
useEnableKeyframes) remain on the server commitMutation path until WS-3.F.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
0ca01c88cf |
feat(sdk): addElement forward op — mint hf-id, inverse = removeElement (WS-D) (#1571)
* 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) <noreply@anthropic.com> * 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 <script> in html - session.ts: typed addElement(parent, index, html) returns minted id via result.meta.newId - mutate.test.ts: 16 tests covering insert position, append semantics, id uniqueness, content-collision rehash, nested fragments, forward/ inverse symmetry, undo, add/undo/redo stability, parent:null body insertion, serialize round-trip, and all five validateOp rejection codes Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
f65e229663 |
feat(sdk): ws-c elastic timing + word-alignment resolver (WS-C) (#1570)
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) <noreply@anthropic.com> |
||
|
|
d0e520dbd9 |
feat(sdk): ws-b variables/brand — object-valued font/image + B1 JSON model (#1569)
## WS-B — variables / brand, object-valued (end-to-end)
Part of the AI Studio (Pacific) SDK integration. **Base of the SDK-hotspot stack** (`main → ws-b → ws-c → ws-d → ws-3c → ws-3f`).
### Problem
The variable system was split-brained: SDK `setVariableValue` wrote a `--{id}` CSS custom prop, while the runtime `getVariables()` read a separate JSON model (`data-composition-variables` / `__hfVariables`). The two never connected, and there was no `--brand-*` convention. Variables were scalar-only.
### What this does
- **B1 — one source of truth.** `setVariableValue` now drives the runtime variable model (`data-composition-variables` / `__hfVariables`), with CSS compatibility emitted as explicit `stylePath`-based patches alongside the model patch. A brand kit is a variables JSON; a batch of `setVariableValue` re-skins in one frame.
- **B2 — object-valued variables.** The `CompositionVariable` union extends from scalar-only to typed objects: `font` (`{name, source}`) and `image` (`{url, …}`), end-to-end (core union → SDK op → runtime merge). Colors stay scalar (per §7 LOCKED decision).
### Implementation notes
CSS compatibility was moved out of `apply-patches.ts` (where it was incorrectly writing CSS props as a side-effect of model patches, breaking inverse/undo) and into explicit patches emitted in `mutate.ts`. Forward emits `[modelPatch, cssPatch]` for scalars; inverse correctly generates `patchRemove` for the CSS prop when there was no prior CSS prop. Font/image variables never become CSS props.
### Files (12 changed, +441 −32)
- `packages/core`: `core.types.ts`, `lint/rules/composition.ts`, `parsers/htmlParser.ts` (+test), `runtime/validateVariables.ts`
- `packages/sdk`: `engine/mutate.ts` (+test), `engine/apply-patches.ts`, `engine/patches.ts`, `index.ts`, `types.ts`
### Gates
- `bun run build` ✅
- `bun test` SDK 304/0 ✅ · `validateVariables.test.ts` 13/0 ✅
- `bunx oxlint` 0/0 ✅ · `bunx oxfmt --check` ✅
- `fallow audit --gate new-only` ✅ (complexity inherited only)
> The +8 new `htmlParser.test.ts` font/image tests fail under the pre-existing `DOMParser is not defined` happy-dom limitation (main already carries 425 such failures) — not a logic bug; the pure runtime logic is covered by `validateVariables.test.ts`.
### Deferred
Brand-kit picker UI and `batch(setVariableValue × N)` wiring are Pacific-side; per-composition variable scoping beyond `__hfVariablesByComp`.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
|
||
|
|
1bab79ef4c | chore: release v0.6.112 | ||
|
|
28bfe09f21 |
feat(sdk): ws-3 — reorderElements op (batch z-index update) (#1502)
* feat(sdk): ws-3 — reorderElements op (batch z-index update) Adds the reorderElements EditOp: each entry sets inline zIndex on one element. Last-write-wins per target so a duplicated target collapses to a single zIndex patch. Positioning is unchanged — z-index only takes effect on non-static elements, so the caller must ensure the target is positioned. Also fixes single-dispatch undo to reverse the inverse patch list (parity with batch()): an op emitting multiple patches whose undo order matters — a duplicated reorderElements target, an aliased multi-target, or a nested parent+child removeElement — must undo in reverse application order, or undo lands on an intermediate value / drops a subtree. validateOp resolves every entry target (E_TARGET_NOT_FOUND for unknown ids; empty entries is a clean no-op). Tests cover set/inverse/validate/duplicate-target. Rebuilt standalone on main (reorderElements only depends on handleSetStyle). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(sdk): remove unused HTTP persist adapter The HTTP PersistAdapter (createHttpAdapter) was dead weight after the studio cutover went single-writer (ws-4): Studio's writeProjectFile is the sole writer and useSdkSession opens with no persist queue, so the adapter's write/flush/ listVersions/loadFrom were never used — only read() was, to fetch the composition source. Replace those two read() calls with a direct optional fetch (GET /files/<path>?optional=1) and drop the adapter + its export-map entries. Saved for later re-introduction (when a non-Studio SDK host needs server-backed persist) at docs/hyperframes/plans/sdk-http-adapter/ (outside the repo). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(studio): resolver shadow for z-index reorder targets The z-index reorder commit takes the server path (no SDK persist), but the resolver-shadow tripwire is decoupled from cutover — so it should still record whether the SDK resolves each reordered element (reorderElements' targets), the same as timing/delete already do before their cutover gate. This gives wild resolver-parity telemetry on z-index targets before z-index reorder is cut over. Threads an onReorderShadow callback (sdkSession-bound, mirrors onTrySdkDelete) from useDomEditSession → useDomEditCommits → useElementLifecycleOps, called with the reordered elements' hf-ids in handleDomZIndexReorderCommit. Read-only, divergence-only, never throws — same contract as recordResolverParity elsewhere. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(studio): guard project-file read path against traversal (CodeQL CSRF) readProjectFileOptional interpolated a user-influenced composition path into the fetch URL, which CodeQL flagged as client-side request forgery. Reject NUL/`..` up front (mirrors the existing guard in timelineEditingHelpers) and encodeURIComponent the projectId too, so both values stay confined to single segments of the same-origin URL. Unsafe path → undefined (graceful for the optional read). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
de87f3932e | chore: release v0.6.111 | ||
|
|
e57e75b9b4 |
fix(sdk,studio): R5 cutover review fixes (on top of #1539) (#1545)
* fix(sdk,studio): R5 cutover review fixes — fromTo dest, timing sync, parity Confirmed correctness findings from the R5 review of the SDK cutover stack, applied on top of #1539: - fromTo add via cutover dropped its destination: handleAddGsapTween read only `toProperties`; now falls back to `properties` like every other method. - handleSetTiming GSAP sync: a clip with no data-start skipped the shift (now treats start as 0, matching the server path) and a blank/non-numeric data-start wrote position: NaN (now sanitized). - handleSetTiming no longer appends an absolute position to an auto-sequenced (implicit-position) tween, which collapsed staggers. - handleSetTiming keeps data-end in sync when a clip carries BOTH data-duration and data-end (a stale data-end inverted the clip). - string/relative tween positions ("+=0.5", "<") documented as a known ceiling. - opacity/autoAlpha property seed no longer falsy-zero (`|| 1`): an element at opacity 0 seeds 0, not 1. - optimistic add-keyframe cache tolerance aligned to the writer's PCT_TOLERANCE (2%) so a near-neighbour keyframe no longer shows then vanishes on reload. - DOM-patch finiteness validation runs before the SDK cutover path. - attribute ops mapping to a reserved data-* name decline the cutover up front instead of throwing inside dispatch. Regression tests added for each SDK-side fix. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(studio): close two gaps in the reserved-attr cutover gate - Lowercase the mapped attribute name before the reserved check, matching the SDK's validateSetAttribute (which lowercases), so a case-variant reserved name is declined up front instead of throwing inside dispatch. - Also gate `html-attribute` ops (raw, non-prefixed names), not just bare `attribute` ops. Both the emitter and the gate now derive the name via one shared `sdkAttrName` helper so they can't drift. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(studio): match keyframe remove-path tolerance to the writer (mirror of add) The optimistic remove-keyframe cache filtered with `> 0.001`, dropping only a near-exact match, while the writer removes within PCT_TOLERANCE (2). Removing at e.g. 49% dropped a 50% keyframe on disk but left it in the cache — a phantom that vanished on reload, the inverted twin of the add-path tolerance fix. Now filters with `> 2` to match. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
8c981a451a |
fix(studio): restore timeline move/resize fallback parity (review #1466) (#1539)
* fix(studio): restore timeline move/resize fallback parity (review #1466) The §3.2 sdkTimingPersist rewrite regressed the non-SDK fallback path vs the pre-cutover behavior. Restored, on both fallback entry points (no-session and sdkTimingPersist-returned-unhandled): - Resize live DOM patch dropped the conditional data-playback-start/media-start attr — restored so a start-trim updates the preview's in-point immediately. - Move/resize fallback dropped the GSAP-position sync (shift/scaleGsapPositions) + reloadPreview — restored so server-path edits keep GSAP tweens in sync and refresh the preview (the SDK path folds both into setTiming). - Undo-coalesce drift: fallback enqueueEdit carried no coalesceKey while the SDK branch did — plumbed coalesceKey through persistTimelineEdit so undo granularity is identical on either path. - Documented the hasPbsAdjustment second clause + sdkTimingPersist before-capture transition limitation. Flag-off (dark launch) so this lands as one fix PR at the stack tip rather than restacking the mid-stack §3.2 commit. #1500 review items: parity-harness gap already closed at the tip (arc/unroll recast-vs-acorn parity added); blockRemoveRange flagged 'potential' but verified correct (no comma residue on any block position). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(sdk): retire duplicate removeGsapKeyframe keyframeIndex variant (review #1498) EditOp had two removeGsapKeyframe members with the same discriminant but different shapes (keyframeIndex vs percentage) — TS can't discriminate them and a handler could get the wrong shape. Per both reviewers (option 2): retire the keyframeIndex variant. It had no production caller (Studio dispatches percentage only); removed the dead by-index handleRemoveGsapKeyframe + simplified the dispatcher. resolveKeyframe stays (setGsapKeyframe still uses keyframeIndex). Converted the one by-index test to the percentage API. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(studio): gate ALL cutover persist paths on the flag — true dark launch (review #1469 finding #6) Only sdkCutoverPersist (style/text/attr) checked STUDIO_SDK_CUTOVER_ENABLED. sdkTimingPersist, dispatchGsapOpAndPersist (every GSAP op) and sdkDeletePersist guarded only on `!sdkSession` — and useSdkSession opens a session by default for shadow/selection, so timing/GSAP/keyframe/delete cutover was ALWAYS live regardless of the flag. Flipping the flag OFF could not disable it, so the data-loss bugs in those paths (single-prop wipe, wrong-keyframe match, tween collapse, arc strip) ship LIVE on merge instead of being dark-launched. Added the flag guard at all three chokepoints → flag OFF returns false → callers fall back to the legacy server path. Makes the stack genuinely dark-launchable: merge is now a no-op in prod, and the remaining cutover correctness bugs become flip-prerequisites rather than merge-blockers. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(core,sdk): correct 8 GSAP write-path review findings (#1539) Eight correctness bugs from the SDK-cutover review. Several were cases where BOTH writers were identically wrong, so the recast-vs-acorn parity suite stayed green; the new tests assert the real-world-correct result, not agreement. - #2 findKfPropByPct: match the CLOSEST keyframe within tolerance, not the first within 2% — removing/updating 50% on 0/49/50/100 no longer hits 49%. - #3 handleSetTiming: shift each tween by the start DELTA and scale duration by the clip-duration RATIO per-tween, instead of writing absolute newStart/ newDuration onto every tween (which collapsed staggers and blew durations). - #4 enableArcPath: insert motionPath via appendRight at the object start so the insertion can't collide with the x/y remove-range end (which made MagicString discard the append and emit '{}'). - #5 splitAnimationsInScript: compute the inherited baseline in a forward pre-pass so the split-spanning midpoint sees earlier tweens (the reverse write loop is kept for stable count-suffixed ids). - #9 unrollDynamicAnimations: preserve non-target loop-body statements (e.g. tl.set initial-state) per iteration instead of overwriting the whole loop. - #10 buildMotionPathObjectCode (both writers): emit the cubic form when segment curviness varies so per-segment curviness survives, not just segments[0]. - #11 readLastWaypointXY: handle UnaryExpression so negative destination coords are recovered when disabling an arc path. - #15 no-bang: removed every `!` non-null assertion in the touched files, replaced with guards/fallbacks. Tests: gsapWriter.reviewFixes.test.ts (#2/#4/#5/#9/#10/#11) and mutate.gsap.test.ts setTiming GSAP-sync block (#3). All fail on the base and pass after the fix; tsc + full core/sdk suites + parity stay green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(studio): SDK cutover review fixes — merge tween props, stabilize debounce, serialize gsap writes, on-disk undo baseline, self-write identity Addresses 5 SDK-cutover review findings (studio-only): - #1 useGsapPropertyDebounce: editing one GSAP tween property no longer drops the tween's other animated props. setGsapTween REPLACES the property set, so merge the single edit into the tween's CURRENT properties (read from the SDK doc) before dispatching, mirroring the legacy server merge. - #7 useGsapPropertyDebounce: stabilize the flush callback by reading sdk deps from a ref instead of an unmemoized literal, so a parent re-render mid-edit no longer tears down + flushes the debounce (one commit/undo entry per render). - #8 sdkCutover/useGsapScriptCommits: route SDK gsap-write persists through the same per-file keyed serializer the legacy commitMutation uses, so concurrent same-file read-modify-writes can't interleave and lose an edit. - #12 sdkCutover/useTimelineEditing: capture the exact on-disk bytes as the undo 'before' for timing/GSAP persists (matching the style/delete paths) instead of a normalized SDK serialize() re-emit that reformatted the whole file on undo. - #14 useSdkSession/sdkSelfWriteRegistry: discriminate a cutover echo from an undo write by CONTENT identity (registered self-write hash), not just the 2 s timestamp window — an undo write always reloads the SDK session. Tests: useGsapPropertyDebounce(.test), useGsapPropertyDebounceFlush.test, sdkSelfWriteRegistry.test, and new sdkCutover.test cases; each reproduces the review scenario and asserts the corrected behavior (verified red before fix). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(core): extract split/collapse helpers to satisfy no-fallow-ignore rule The #5 (split) and #15 (no-bang guards) fixes pushed splitAnimationsInScript and removeAllKeyframesFromScript over fallow's complexity threshold, and a fallow-ignore had been added to splitAnimationsInScript. Per the hard rule (never ignore — fix), extracted buildSpanningSplit + applyTweenSplit (split) and buildCollapsedFlatVars (collapse), and removed the ignore. Both functions now under threshold; fallow new-only gate reports 0 new findings. Behavior unchanged — core 1811 green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(studio): pin dark-launch flag-gate contract (review #1539, Rames/Via) flag OFF ⇒ sdkTimingPersist / sdkGsapTweenPersist (GSAP-op chokepoint) / sdkDeletePersist all return false even with a valid session → legacy fallback. The prod flag-flip rests on this contract; sdkCutover.test.ts only mocks the flag TRUE, so a future gate refactor could silently re-enable cutover on flag-off without failing CI. This sibling file mocks it FALSE and locks the three guards. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(studio): leading flag-gate on sdkGsapTweenPersist (review #1539 nit, Via) The add-op getElement existence check ran before the inner gate, so flag-off did an SDK touch before falling back. Lead with the flag guard to match the other three chokepoints — flag-off is now a clean no-op at every entry point. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(core): unroll-preservation regressions — non-for loops + AST index substitution (review R2) The #9 unroll-preservation fix had two confirmed regressions: - Non-for loops (forEach/for-of/for-in/while): loopIndexVarName returns null, so substitution no-op'd and preserved siblings kept a now-undefined loop variable (e.g. `item`) → ReferenceError at render. Now returns null for those forms → caller falls back to the blanket loop overwrite (drops siblings, valid code). The #9 fixture only used `for(let i…)` so it never caught this. - substituteLoopIndex did a \bvar\b regex over raw source including string literals, corrupting selectors like ".row-i" → ".row-0". Now AST-based: substitutes only real Identifier uses, skipping string literals and non-computed member/key positions (extracted isIndexBindingPosition helper to stay under the fallow complexity threshold — no ignore added). Two regression tests added (forEach no-dangling-var; for-loop string-literal intact). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(sdk,core): unrollDynamicAnimations rejects empty element list (R1 #1501b) An empty `elements` array has no unrolled form — the writer would overwrite the loop/statement with zero tween calls, silently deleting the animation. - gsapWriterAcorn: unrollDynamicAnimations returns the script verbatim on an empty list (no-op instead of a destructive overwrite). - validateOp: reject unrollDynamicAnimations with empty elements as E_INVALID_ARGS so callers get a clean error rather than silent corruption. - Tests: writer no-op on []; validateOp E_INVALID_ARGS on []. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * perf(sdk): cache draft element in applyDraft, drop HTMLElement casts (R1 #1490a) applyDraft runs at 60fps during a drag but re-ran doc.querySelector on every call — the _draftEl/_draftId fields were only consumed by commit/cancel, never to skip the query. Reuse the tracked element when the id matches and the node is still connected; re-query only on id change or detach (iframe reload). Retypes _draftEl to HTMLElement | null (only ever set from querySelector<HTMLElement>), which removes the `as HTMLElement` casts in commitPreview / _clearDraft. Test asserts a repeated same-id drag queries once. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(sdk,core): round-3 correctness — unroll AST safety, single-dispatch undo, empty-arg guards, persist decouple Addresses the highest-severity round-3 review findings: - gsapWriterAcorn unroll (R3 #1/#2/#9): the round-2 AST-substitution fix emitted invalid GSAP for object shorthand `{ i }` (→ `{ 0 }`) and shadowed inner bindings (→ `for(let i=0;0<3;0++)`), and silently dropped sibling statements on non-`for` loops (forEach/for-of). The unroll now REFUSES (no-ops, leaving the dynamic loop intact) whenever siblings can't be safely reproduced — a non-`for` loop, an unmodeled statement, or an unsafe index use — instead of dropping or corrupting. Plain `for` loops with safe siblings still unroll. - session single-dispatch undo (R3 #5/#11): _dispatch now reverses the inverse patch list (parity with batch()). A single op emitting order-dependent inverse patches — a nested parent+child removeElement, an aliased multi-target — undid forward and dropped the child subtree / landed on an intermediate value. - materializeKeyframes empty-array (R3 #10): the unguarded twin of the just-fixed unrollDynamicAnimations. Writer no-ops on an empty keyframe list; validateOp rejects it as E_INVALID_ARGS (shared gsapScriptMissing helper). - history:false persist decouple (R3 #4): persist (auto-save) no longer lives inside the history-enable block, so opting out of SDK undo no longer silently disables all disk writes (data-loss trap for #1496's flag consumers). Tests: unroll refuse cases (shorthand/shadow/forEach) + safe-for-loop regression; nested removeElement undo; materializeKeyframes writer no-op + validateOp reject; history:false-still-persists. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(core): stripGsapForId re-parses per removal so all tweens for a deleted element are stripped (R3 #3) Animation ids are count-based (positional), so removing one tween renumbers the survivors. stripGsapForId captured every matching id from a single up-front parse then removed against the mutating script — after the first removal the later ids were stale and silently no-op'd, leaving an orphaned tl.to() referencing the just-deleted element. Now re-parse after each removal and strip the first still-matching animation until none remain. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(core): gsap writer — keyframe ease routing, convert preserves delay, addLabel dedup (R3 #7/#8/#12) - #7: updateAnimationInScript routes an ease update on a keyframe tween to keyframes.easeEach (per-keyframe), not a top-level ease that GSAP ignores — the user's keyframe-easing edit was silently a no-op. - #8: convertToKeyframesFromScript now preserves every non-editable vars key (delay/callbacks/stagger/yoyo/…) verbatim via preservedVarsEntries instead of rebuilding from the GsapAnimation object, which had no `delay` field and dropped it — shifting the tween's start time. - #12: addLabelToScript moves an existing same-named label (overwrites its position) instead of appending a duplicate; duplicates made removeLabel over-remove (it deletes every match, including a pre-existing label). Tests: easeEach routing, delay preservation, addLabel move-not-duplicate + hand-authored-dup removal. Updated the old "no dedup contract" corpus test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(sdk): handleSetTiming #domId + data-duration sync; validateOp resolves ids + arc/selector (R3 #6/#13, CF2 #15/#16) CF2 #15: handleSetTiming re-synced GSAP tweens only when the selector matched the element's hf-id. The common #domId-targeted tween (authored by the Studio panel) never matched, so moving/resizing a clip via the SDK timing path left its animations unsynced. Now match the tween selector against the DOM id too. CF2 #16: handleSetTiming read/wrote only data-end. Clips authored with data-duration (what the runtime prefers) got a fresh data-end beside a stale data-duration (no playback change) and oldDuration=null collapsed the GSAP duration-scale ratio to 1. Now read duration preferring data-duration, and write back to whichever attribute the clip uses (timingPath gains a "duration" field). R3 #13b: deleteAllForSelector compared selectors with strict === and missed the alternate quote style ([data-hf-id='x'] vs "x"); now quote-insensitive. R3 #6/#13a: validateOp now resolves the animationId for id-bearing GSAP ops (E_TARGET_NOT_FOUND instead of a misleading ok that no-ops at apply), and updateArcSegment validates the arc is enabled + the segment index is in range. Tests: #domId move sync, data-duration resize + scale, quote-insensitive delete, unresolved-id rejection, arc-segment preconditions. Updated the loose-can() test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(core,sdk): name the acorn-node type alias; keyToPath round-trips timing.duration (R3 #14) - gsapWriterAcorn: replace the bare `: any` AST-node annotations with the named `type Node = any` alias, matching the established convention in gsapParserAcorn.ts / gsapInline.ts ("acorn ESTree nodes are structurally untyped"). Documents intent and is greppable; type-identical (zero runtime change). A full ESTree typing is a deliberate architecture decision the codebase has not taken and is out of scope here. - patches: keyToPath/timingPath now include the "duration" timing field added for the data-duration resize fix, so a timing.duration override round-trips on T3 replay instead of being dropped. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(sdk): cascadeRemoveAnimations re-parses per removal (R4 — SDK twin of #3) cascadeRemoveAnimations captured every matching animation id from a single up-front parse, then removed against the mutating script — the SDK-side twin of the stripGsapForId bug (R3 #3). Animation ids are positional, so removing the first tween for an element renumbered the survivors and the stale later ids no-op'd, orphaning those tweens on the just-removed element. Now re-parse after each removal and strip the first still-matching animation until none remain. Also adds the reviewer's defense-in-depth test: an aliased multi-target setStyle (same id twice) undoes to the original, not the intermediate (exercises the single-dispatch inverse reversal from R3 #5/#11). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
09cefc1bb7 |
feat(sdk,core): ws-3 — unrollDynamicAnimations acorn port + SDK op (#1501)
* feat(sdk,core): ws-3 — unrollDynamicAnimations acorn port + SDK op Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Miguel Ángel <miguel07alm@protonmail.com> * test(core): recast-vs-acorn parity + acorn fixes for arc/unroll/keyframe-add/%-removeKeyframe/add-with-keyframes (WS-3.F gate) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(core): port shiftPositions/scalePositions to acorn writer (WS-3.F) shiftPositionsInScript + scalePositionsInScript were recast-only GSAP-script writers reachable from executeGsapMutation (shift-positions/scale-positions), called by Studio timeline clip move/resize — the last write ops blocking recast retirement. Ported to gsapWriterAcorn.ts mirroring recast's arithmetic (shift: max(0,pos+delta); scale: remap pos by duration ratio + scale duration), reusing a shared overwritePosition helper (also adopted by updateAnimationInScript). Adds 10 recast-vs-acorn parity tests. Closes the WS-3.F op-coverage gate. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Miguel Ángel <miguel07alm@protonmail.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
1612d18fdf |
feat(sdk): stage 6 — arc path ops (setArcPath, updateArcSegment, removeArcPath) (#1500)
Port arc path trio from recast to browser-safe acorn+MagicString writer. Add SDK op types and mutate.ts handlers for setArcPath / updateArcSegment / removeArcPath. Decompose buildMotionPathObjectCode into small sub-functions in gsapSerialize.ts to stay within fallow complexity thresholds. Tests verify acorn output re-parses to correct arcPath shape. Co-authored-by: Miguel Ángel <miguel07alm@protonmail.com> |
||
|
|
a746db6017 |
feat(sdk,core): ws-3 prerequisites — acorn keyframe-collapse foundation + removeAllKeyframes (#1499)
* feat(sdk,core): ws-3 prerequisites — acorn keyframe-collapse foundation + removeAllKeyframes
P1: gsapWriter.parity.test.ts — recast-vs-acorn parity harness (reparse-equivalence).
P2: move pure keyframe-conversion transforms (resolveConversionProps, cssIdentityValue)
to recast-free gsapSerialize.ts so the acorn/SDK path can share them.
P3: MagicString splice primitives in gsapWriterAcorn.ts (buildVarsObjectCode, overwriteVarsArg).
P4: reference vertical slice — removeAllKeyframesFromScript ported to acorn writer +
removeAllKeyframes SDK op (types/mutate/can) + Studio cutover (useGsapKeyframeOps),
replacing the server-authoritative ponytail stub.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(sdk,core): ws-3 — convertToKeyframes acorn port + SDK op + Studio cutover
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Miguel Ángel <miguel07alm@protonmail.com>
* feat(sdk,core): ws-3 — materializeKeyframes + splitIntoPropertyGroups acorn ports + SDK ops
- acorn: buildKeyframeObjectCode, materializeKeyframesFromScript, addAnimationWithKeyframesToScript
- acorn: splitIntoPropertyGroupsFromScript with filterGroupKeyframes/filterGroupProperties helpers
- parity tests: materialize (2 positive + 1 no-op) and split (2 positive + 2 no-op) suites
- SDK types: materializeKeyframes + splitIntoPropertyGroups EditOp variants
- mutate.ts: handlers + can() gates for both new ops
- mutate.gsap.test.ts: 6 new tests (53 total passing)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Miguel Ángel <miguel07alm@protonmail.com>
* feat(sdk,core): ws-3 — splitAnimationsInScript acorn port + SDK op
- acorn: updateAnimationSelectorInScript, insertInheritedStateSetInScript helpers
- acorn: splitAnimationsInScript exported (parity with recast version)
- parity: 4 new fixtures (3 cases + no-op) — 23 total parity tests
- SDK types: splitAnimations EditOp variant
- mutate.ts: handleSplitAnimations + can() gate
- mutate.gsap.test.ts: 3 new tests (56 total passing)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Miguel Ángel <miguel07alm@protonmail.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Miguel Ángel <miguel07alm@protonmail.com>
|
||
|
|
ceb815c318 |
feat(sdk,studio): ws-1.2 — percentage-based removeGsapKeyframe (#1498)
* feat(sdk,studio): ws-1.2 — percentage-based removeGsapKeyframe Co-authored-by: Miguel Ángel <miguel07alm@protonmail.com> * feat(sdk,studio): ws-1.3 — removeGsapProperty SDK op + Studio hook cutover Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Miguel Ángel <miguel07alm@protonmail.com> * feat(sdk,studio): ws-1.4 — deleteAllForSelector SDK op + Studio hook cutover Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Miguel Ángel <miguel07alm@protonmail.com> * fix(core): cascade-remove GSAP tweens in removeElementFromHtml (WS-2) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Miguel Ángel <miguel07alm@protonmail.com> --------- Co-authored-by: Miguel Ángel <miguel07alm@protonmail.com> |
||
|
|
a5016ed416 |
feat(sdk,studio): ws-1.1 — add set method to GsapTweenSpec; route addGsapAnimation(set) through sdk (#1497)
Co-authored-by: Miguel Ángel <miguel07alm@protonmail.com> |
||
|
|
e35846176e |
feat(sdk,studio): ws-4 — add history:false option; disable unused sdk undo in studio (#1496)
Co-authored-by: Miguel Ángel <miguel07alm@protonmail.com> |
||
|
|
53717a77f4 |
feat(sdk): ws-a2 — applyDraft/commitPreview/cancelPreview → moveElement op (#1490)
Co-authored-by: Miguel Ángel <miguel07alm@protonmail.com> |
||
|
|
b96e8a3072 |
feat(sdk): ws-a1 — iframe preview adapter (hit-test + selection) (#1489)
* feat(studio): stage 7 step 3c — sdk cutover for inline-style ops Introduces sdkCutoverPersist(): when STUDIO_SDK_CUTOVER_ENABLED is set, inline-style PatchOps are routed through the SDK session's in-memory document model instead of the server patch-element API. The SDK serialize() result is written back through the same writeProjectFile + editHistory.recordEdit path, so the on-disk output is identical to the legacy route. - packages/studio/src/utils/sdkCutover.ts (new): sdkCutoverPersist() + shouldUseSdkCutover() guard; domEditSaveTimestampRef.current is stamped on each write to suppress the echo file-change reload. - packages/studio/src/components/editor/manualEditingAvailability.ts: adds STUDIO_SDK_CUTOVER_ENABLED flag (default false); changes STUDIO_SDK_SHADOW_ENABLED default to false now that cutover is available. - packages/studio/src/hooks/useSdkSession.ts: adds optional domEditSaveTimestampRef param; self-write suppress window (SELF_WRITE_SUPPRESS_MS) gates file-change reloads so SDK writes don't echo back as external edits. - packages/studio/src/App.tsx: passes domEditSaveTimestampRef to useSdkSession so the suppress window can gate reloads triggered by SDK cutover writes. - Test coverage: sdkCutover.test.ts (new, 141 lines) + useDomEditSession.test.ts (new, 50 lines) — guard function + happy-path assertions. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(studio): force-reload sdk session after undo/redo bypasses suppress window writeHistoryFile arms the 2 s self-write suppress window, so the file-change event for an undo/redo write is swallowed and the SDK in-memory doc stays on pre-undo content. Expose forceReload() from useSdkSession (s7.4) and call it in useAppHotkeys after a successful undo/redo that touched the active composition path. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Miguel Ángel <miguel07alm@protonmail.com> * feat(studio): s7.5 — delete shadow scaffolding; keep cutover flag (dark launch) Removes the SDK shadow telemetry: STUDIO_SDK_SHADOW_ENABLED, sdkShadow.ts + sdkShadowGsapFidelity/GsapKeyframe/Numeric and their tests, the runShadow* call-sites across the GSAP/timeline hooks, and the onDomEditPersisted shadow callback in useDomEditSession. Moves patchOpsToSdkEditOps into sdkCutover.ts. KEEPS STUDIO_SDK_CUTOVER_ENABLED as a dark-launch kill-switch — default false, enable per-environment via VITE_STUDIO_SDK_CUTOVER_ENABLED=true. shouldUseSdkCutover stays flag-gated. The stack can merge with zero behavior change; cutover is validated by flipping the flag, not by removing it. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Miguel Ángel <miguel07alm@protonmail.com> * fix(studio): wire onTrySdkPersist to sdkCutoverPersist (cutover was unwired) Stage 7 s7.5 removed the feature flag and declared cutover 'always-on', but onTrySdkPersist was never actually passed to useDomEditCommits — the sdkCutoverPersist function was dead code in production. Thread sdkSession through useDomEditSession params, build the onTrySdkPersist closure there (all CutoverDeps are already in scope), and pass sdkSession from App.tsx. Style/text/attribute/html-attribute commits now route through SDK dispatch instead of the server patch path. Co-authored-by: Miguel Ángel <miguel07alm@protonmail.com> * feat(studio): route element delete through SDK removeElement (§3.1) Co-authored-by: Miguel Ángel <miguel07alm@protonmail.com> * feat(studio): route timeline trim/move through SDK setTiming (§3.2) Co-authored-by: Miguel Ángel <miguel07alm@protonmail.com> * chore(studio): document CSS-path position cut-over, GSAP-path intentionally deferred (§3.3) Co-authored-by: Miguel Ángel <miguel07alm@protonmail.com> * feat(studio): route GSAP tween add/update/delete through SDK (§3.5 PR1) Co-authored-by: Miguel Ángel <miguel07alm@protonmail.com> * feat(studio): route GSAP keyframe add through SDK (§3.5 PR2) Co-authored-by: Miguel Ángel <miguel07alm@protonmail.com> * fix(studio,core): resolve SDK-cutover review findings Co-authored-by: Miguel Ángel <miguel07alm@protonmail.com> * feat(sdk): ws-a1 — iframe preview adapter (hit-test + selection) Co-authored-by: Miguel Ángel <miguel07alm@protonmail.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Miguel Ángel <miguel07alm@protonmail.com> |
||
|
|
fcc7b314f0 | chore: release v0.6.110 | ||
|
|
6e32142334 |
fix(sdk): resolve composition-id targets + emit canonical data-hf-id for GSAP tweens (#1526)
A sub-composition ROOT is addressed by its data-composition-id, but the SDK's whole element<->tween attribution is data-hf-id based, so the prior fix's [data-composition-id] selector was invisible to three readers (validateOp/can, selectorMatchesId -> setTiming + removeElement cascade, buildAnimationIdMap -> getElement.animationIds), diverging can from apply and orphaning tweens. Root fix: make composition ids first-class resolvable addresses and emit the canonical selector everywhere. - resolveScoped (model.ts): for a bare id with no data-hf-id match, fall back to [data-composition-id]. data-hf-id keeps precedence; scoped-path and canonical behavior intact. Fixes validateOp gating, findById/getElement, and every op handler for comp-root targets in one place. - gsapTargetSelector (mutate.ts): resolve the target and emit [data-hf-id="<resolved host hf-id>"] (canonical). Normal targets unchanged; comp-root targets resolve via comp-id -> host -> host hf-id. Defensive [data-composition-id] only when the resolved element has no hf-id. - setTiming syncs the GSAP tween via the resolved element's data-hf-id so a comp-root target matches its host tween; removeElement cascade already covers the host hf-id via collectSubtreeHfIds. - export escapeHfId; escape both the querySelector probe and the emitted selector string. Tests: comp-id resolveScoped fallback + precedence (session.subcomp), canonical selector, validateOp accept, setTiming sync, removeElement cascade, and getElement.animationIds for comp-root tweens (mutate.gsap). The prior test only called applyOp, masking all of this. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
1028f52aa2 | chore: release v0.6.109 | ||
|
|
cc7c206e9c | chore: release v0.6.108 | ||
|
|
b5ad518957 |
fix(core,sdk): rebuild acorn GSAP keyframe writer for recast parity (#1520)
The acorn addKeyframeToScript mixed ms.overwrite + ms.appendLeft on the
same _auto endpoint node, crashing MagicString ("Cannot split a chunk
that has already been edited") whenever an interior keyframe adjacent to
an _auto 0/100 endpoint introduced a new backfilled prop — the common SDK
path. It also replaced (not merged) existing keyframes, dropped ease and
_auto markers, corrupted commas on multi-prop backfill into empty {},
used a <0.001 percentage tolerance instead of recast's PCT_TOLERANCE=2,
and silently no-op'd on flat (non-keyframe) tweens.
Rebuild the node model to mirror recast: compute the FINAL property record
for every changed keyframe value node (target merge, _auto endpoint sync,
backfilled siblings) against the original AST, then emit exactly one
ms.overwrite per changed node (one insert for a brand-new key). No node is
ever both overwritten and appended into, so splices can never overlap.
- Merge: re-touching an existing keyframe merges new props over the
existing record, preserving untouched props, existing ease, and _auto.
- Convert-flat: first keyframe-add on a flat to()/from()/fromTo() tween
rebuilds its vars object to percentage keyframes (ease->easeEach,
ease:"none", from/fromTo->to) matching recast, then re-locates via the
-from-/-fromTo- -> -to- id fallback.
- Tolerance: PCT_TOLERANCE=2 for existing-keyframe detection.
- Shared serializeValue/safeJsKey for keyframe values (recast parity); the
tween-statement path keeps its local serializer for object/boolean extras.
- keyframeBackfill: only backfill props with a real numeric default; skip
unknown/string props so color:0 / filter:0 are never emitted.
- setGsapKeyframe move-path threads the same backfill defaults as the add
path so both entry points behave identically.
Differential tests (acorn vs recast parsed keyframe arrays) cover the
crash (2-endpoint + 0/25/100), empty-{} multi-prop backfill, merge with
extra props + ease, flat to()/fromTo() convert, "50.0%" non-byte-equal
key, near-% tolerance, and _auto-marker preservation onto an endpoint.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
6778ad13ef | chore: release v0.6.107 | ||
|
|
cc055f318d |
fix(sdk): agree removeElement/getElement on duplicate bare ids (#1511)
* fix(sdk): setStyle removes hyphenated properties (was kebab/camel key mismatch) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(sdk): agree removeElement/getElement on duplicate bare ids A bare hf-id duplicated across a sub-composition element and a top-level element resolved to different instances: removeElement → resolveScoped → querySelector (document-order-first, the inner sub-comp dup) while getElement preferred the canonical match (scopedId === id, the top-level dup). So removeElement(bareId) removed the inner instance and getElement(bareId) still found the surviving top-level one — they disagreed. resolveScoped now resolves an ambiguous BARE id to the canonical (top-level) instance via isCanonicalScope (walks ancestors for isNewHostBoundary), falling back to document order when no canonical match exists — matching getElement. Fully-scoped paths (hf-host/hf-dup) and non-duplicated bare ids are unchanged. Surfaced by SDK shadow parity (op:delete expected removed, actual present). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
066ea798b4 |
fix(sdk): setStyle removes hyphenated properties (was kebab/camel key mismatch) (#1510)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
c0ac03cab2 | chore: release v0.6.106 | ||
|
|
2798b97ef1 | chore: release v0.6.105 | ||
|
|
479184ecf0 | chore: release v0.6.104 | ||
|
|
d9dc88ff60 | chore: release v0.6.103 | ||
|
|
d6a846300e | chore: release v0.6.102 | ||
|
|
7593aac5ef |
feat(sdk,studio): populate animationIds; shadow GSAP update/delete + value fidelity (#1474)
* feat(sdk,studio): populate animationIds; shadow GSAP update/delete Closes the GSAP shadow gaps. The server's animationId was assumed to live in a separate id-space — it does not: the studio-api read path (T6e) and the SDK both derive tween ids as targetSelector-method-position from the same acorn parser, so server ids are dispatchable in the SDK as-is. SDK: populate ElementSnapshot.animationIds (was a hardcoded stub) from parseGsapScriptAcornForWrite().located, resolving each tween's targetSelector to element hf-ids. Makes the snapshot truthful and enables real GSAP parity. Studio: shadow deleteGsapAnimation (removeGsapTween) and updateGsapMeta (setGsapTween) using the server animationId directly. GSAP add/remove parity now verifies via animationIds (present after add, gone after remove). set is existence-only — the SDK still has no per-tween property reader (value fidelity would need serialize()-script round-trip diffing). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(studio): GSAP value fidelity via serialize round-trip diff Closes the last shadow gap: GSAP value fidelity. Existence parity confirmed a tween was created/removed but not that its values (duration/ease/position/ properties) matched the server, since the SDK has no per-tween property reader. runShadowGsapFidelity opens a fresh SDK doc from the server's pre-op file (result.before), applies the same typed op, serializes, and structurally diffs the SDK's GSAP script against the server's resulting script (result.scriptText). Both are re-parsed via parseGsapScriptAcorn, so formatting/whitespace never produces false positives — only real value drift does. gsapFidelityMismatches reports per-field drift and tween presence/absence. Wired at the commitMutation chokepoint (the only place with the server's before+after scripts); handlers pass the typed ShadowGsapOp via CommitMutationOptions.shadowGsapOp. Emits sdk_shadow_dispatch op:gsap_fidelity. Complements the existing live existence shadow (op:gsap). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(studio,sdk): address shadow code-review findings - gsapFidelityMismatches: canonical comparison (sort property keys, numeric- coerce position/duration/values). Server (addAnimationToScript) and SDK (gsapWriterAcorn) are different writers; non-canonical compare flagged key-order / number-vs-string differences as false value drift. - document.ts buildAnimationIdMap: memoize the acorn parse by script text (single-entry). getElements() invalidates on every dispatch, so shadow's frequent dispatches were re-parsing the full GSAP AST each rebuild. Selector resolution still runs per-call (depends on live DOM). - runShadowGsapFidelity: early-bail when serverScript/beforeHtml is empty — skip the costly openComposition. - useSafeGsapCommitMutation: import the shared CommitMutationOptions/ CommitMutation instead of a stale local duplicate (was missing shadowGsapOp). - align extractGsapScript marker set across sdkShadow.ts and document.ts (gsap || __timelines || ScrollTrigger) so both pick the same script. Tests: +2 canonical-compare cases (key-order, number-vs-string → no drift). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(studio,sdk): fallow gate for #1474 (fidelity diff + test clones) - suppress moderate CRAP on gsapFidelityMismatches and the runShadowGsapTween parity arrow (comparison/parity functions are inherently branchy) - suppress two pre-existing test clones in session.test.ts surfaced by the added animationIds tests (TestPreviewAdapter stub, selectionchange setup) Rebased onto the updated #1473 (no-persist shadow session); inherits the persist-race fix and prior fallow suppressions. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(studio): extract GSAP fidelity to its own module (file-size gate) sdkShadow.ts hit 602 lines (CI File size check: max 600). Move the GSAP value-fidelity diff (gsapFidelityMismatches, runShadowGsapFidelity, and their private helpers) into sdkShadowGsapFidelity.ts; re-export from sdkShadow.ts so the import surface is unchanged. sdkShadow.ts now 430 lines. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(studio,sdk): address #1474 review feedback - CodeQL js/bad-tag-filter: the GSAP <script> extraction + test regexes now match </script\s*> (whitespace-before-close variant). 3 alerts resolved. - Wiring (Miguel): extract resolveGsapFidelityArgs — a pure, narrowing gate for the commitMutation chokepoint (no non-null assertions) — and unit-test the fire/skip conditions (session, op, before, scriptText). Replaces the inline guard so the wiring decision is covered without rendering the hook. - Property-handler scope (Rames): comment at the chokepoint documenting that only meta-level ops (add/update-meta/delete) carry shadowGsapOp today; per-property and keyframe handlers are a deliberate follow-up. Also why scriptText can be null. - Test coverage (Rames): multi-tween-per-element and shared-selector cross-element animationIds cases. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(studio): CodeQL js/bad-tag-filter — match </script[^>]*> close tags `</script\s*>` still tripped CodeQL on attribute-junk closes like `</script foo>` (HTML5 ignores junk before `>`). Widen the close-tag match to `</script[^>]*>` in the GSAP-script extraction and the test regexes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
78cce00c50 | chore: release v0.6.101 | ||
|
|
f03dfaa599 | chore: release v0.6.100 | ||
|
|
5fe87cc39b |
feat(sdk,studio): stage 7 step 3a — persistPath + SDK session reload-on-change (#1449)
* feat(sdk,studio): stage 7 step 3a — persistPath + SDK session reload-on-change Stage 7 Step 3a — SDK plumbing for routing Studio commits through the SDK session. No behavior change: the session stays idle (no op routed yet). SDK: - Add OpenCompositionOptions.persistPath; thread to createPersistQueue so the persist queue writes back to the composition's real path instead of the "composition.html" default (blocker A). Studio (useSdkSession): - Pass persistPath = activeCompPath so a future dispatch persists the right file. - Re-open the session when the active composition file changes on disk (HMR hf:file-change / SSE file-change), scoped to activeCompPath, so the in-memory linkedom document never goes stale under code-editor/agent/server edits (blocker C). Re-opening is additive while the session is idle; 3c must add self-write suppression once dispatch writes. Tests: SDK persistPath default + override; shouldReloadSdkSession path-match. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(sdk): document persistPath as immutable for session lifetime Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Miguel Ángel <miguel07alm@protonmail.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: Miguel Ángel <miguel07alm@protonmail.com> |
||
|
|
92e2c8ce6a |
feat(studio): stage 7 step 1 — wire SDK session into Studio (#1443)
* feat(studio): stage 7 step 1 — wire SDK session into Studio Creates useSdkSession hook: fetches active composition HTML, opens an SDK Composition backed by createHttpAdapter, disposes on comp/project change. Session is idle (no dispatch routed yet) — Step 3 wires edit ops through it. Also removes createFsAdapter from SDK main entry (Node-only; subpath-only: @hyperframes/sdk/adapters/fs). Required for Studio typecheck to pass when importing @hyperframes/sdk — fs.ts uses node:fs/promises which Studio's tsconfig does not include. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(studio): stage 7 step 2 — mirror canvas selection into SDK session useSdkSelectionSync: effect that calls session.setSelection(hfIds) whenever domEditSelection or domEditGroupSelections changes. Maps each entry's hfId; skips entries without one. Pure additive — no existing hook modified. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(studio): use adapter.read() in useSdkSession bootstrap Build the HttpAdapter first, then call adapter.read(activeCompPath) instead of duplicating URL construction with a raw fetch. Eliminates the /files/encode duplication already in HttpAdapter.read(). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(studio): flush in-flight http writes before disposing SDK session Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Miguel Ángel <miguel07alm@protonmail.com> * fix(studio): dispose SDK session if cleanup fires during openComposition Reviewer found a race: if the effect cleanup runs while openComposition is awaited, comp is null so cleanup is a no-op, but the composition is then set and never disposed. Add an explicit check after the await so any composition opened after cancellation is disposed immediately. Also wire the missing useSdkSession call in App.tsx (sdkSession was referenced but never declared — pre-existing typecheck failure), move the stableRenderQueue memo into useRenderQueue so App.tsx stays under the 600-line architecture gate. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Miguel Ángel <miguel07alm@protonmail.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Miguel Ángel <miguel07alm@protonmail.com> |
||
|
|
30ce1f35a2 |
feat(sdk): stage 7 step 2 — setSelection API (#1442)
* feat(sdk): stage 7 step 2 — setSelection API Adds setSelection(ids: string[]) to Composition interface and CompositionImpl. Fires selectionchange; does not touch undo stack or patch stream. 11 contract tests: get/set/clear, event firing, copy semantics, no undo/patch side-effects. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(sdk): guard setSelection against same-id no-ops Skip event dispatch when ids are identical (same length, same order) to prevent double-firing selectionchange from callers that call setSelection with the same list. Two new tests (RED→GREEN verified). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(sdk): de-duplicate ids in setSelection Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Miguel Ángel <miguel07alm@protonmail.com> * fix(sdk): document PreviewAdapter.on("selection") as stage 8 prep Reviewer noted it is dead surface in this stack — no caller uses it. Add comment explaining it is wired up in stage 8 when the preview host pushes selection events up to the SDK session. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Miguel Ángel <miguel07alm@protonmail.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Miguel Ángel <miguel07alm@protonmail.com> |
||
|
|
c19898e799 |
feat(sdk): stage 7 step 1 — http persist adapter (#1441)
## What
Adds `createHttpAdapter` — a browser-native `PersistAdapter` that reads and writes composition files through the Studio dev-server's `/api/projects/:id/files/...` endpoints using the Fetch API. Exported as a subpath: `@hyperframes/sdk/adapters/http`.
## Why
The SDK's `PersistAdapter` interface previously had filesystem (`fs`) and in-memory (`memory`) implementations, both Node-only. Studio runs in the browser and needs to persist compositions back to the dev server. This adapter is the browser-compatible plug that lets `openComposition` work in a Studio context without Node I/O.
## How
- `HttpAdapter` implements `PersistAdapter`: `read` → GET, `write` → PUT with per-path queue to serialize concurrent writes to the same file (at-most-once in-flight per path)
- `flush()` waits for all in-flight queues to drain
- `listVersions` / `loadFrom` proxy the server's version history endpoints
- `on('persist:error')` fires on network/non-2xx failures without throwing; callers can surface errors non-fatally
- Retry is caller's responsibility; the adapter does not retry
## Test plan
- `http.test.ts`: read/write round-trip with MSW, concurrent write serialization, persist:error event on 503, flush drains queue
- Contract suite (`persistAdapter.contract.test.ts`) passes for the http adapter against a mock server
|
||
|
|
e2e13f1e6c | chore: release v0.6.99 | ||
|
|
b158870d8f |
feat(sdk): stage 6 — sub-composition scoped ids (F9) (#1434)
* feat(sdk): stage 6 — sub-composition scoped ids (F9) Adds fully-qualified scoped ids for addressing elements inside inlined sub-compositions, so callers can target "hf-HOST/hf-LEAF" unambiguously even when bare hf-ids collide across sub-composition boundaries. Changes: - model.ts: resolveScoped() traverses id segments through nested subtrees; isNewHostBoundary() detects host boundaries (dcf ≠ parent dcf handles outerHTML innerRoot edge case) - types.ts: HyperFramesElement gains scopedId field - document.ts: buildElement carries scopePrefix, propagates childPrefix at host boundaries; buildRoots starts with "" - patches.ts: RFC 6902 escapeIdForPath / decodePathSegment for scoped ids containing "/"; all path builders and pathToKey/keyToPath updated - session.ts: getElement() matches by scopedId; find() returns scopedIds; orphan cleanup decodes RFC 6902 before key comparison, preserves removal markers, purges property sub-keys for both bare and scoped ids - mutate.ts: all element handlers use resolveScoped instead of findById; handleRemoveElement collects full subtree hf-ids before removal for complete GSAP animation cascade (Q3 fix); validateOp uses resolveScoped 20 new contract tests in session.subcomp.test.ts covering resolveScoped, scopedId propagation, dispatch to scoped targets, RFC 6902 patch encoding, override-set key format, orphan purge, and serialize stability. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(sdk): add find({ composition }) filter — Stage 6 WS-C completion Closes the last headless-testable Stage 6 gap (F9 workstream C). `find({ composition: "hf-host" })` returns all scopedIds whose prefix matches the given host id — i.e. every element mounted inside that sub-composition, at any depth. Combinable with other FindQuery fields (tag, text, name, track). 3 new contract tests in session.subcomp.test.ts. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(sdk): addGsapTween resolves scoped id to bare leaf; validateOp checks target exists - handleAddGsapTween: strip host prefix for scoped ids (hf-host/hf-leaf → selector [data-hf-id="hf-leaf"]) — DOM element carries only the leaf part - validateOp addGsapTween: call resolveScoped to surface E_TARGET_NOT_FOUND before the GSAP script checks (previously can() returned ok for missing targets) - patches.ts pathToKey: remove dead ?? null (decodePathSegment never returns undefined) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Miguel Ángel <miguel07alm@protonmail.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: Miguel Ángel <miguel07alm@protonmail.com> |
||
|
|
f10f3425a5 |
feat(sdk): stage 5 — export adapter factories from package root (#1432)
* feat(sdk): stage 4 — canUndo/canRedo, removeElement GSAP cascade, override-set cleanup * docs(sdk): document cascadeRemoveAnimations bare-id v1 limitation for scoped ids Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Miguel Ángel <miguel07alm@protonmail.com> * chore(sdk): remove sdk-status-report.txt from source tree Internal planning artifact should not be committed to the repo. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Miguel Ángel <miguel07alm@protonmail.com> * feat(sdk): stage 5 — export adapter factories from package root Expose the concrete adapter factories so consumers no longer reach into deep adapter paths: - createHeadlessAdapter — no-op PreviewAdapter for agents/CI/SSR (no browser) - createMemoryAdapter — in-memory PersistAdapter for tests/headless open - createFsAdapter (+ FsAdapterOptions) — node fs PersistAdapter for local dev Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Miguel Ángel <miguel07alm@protonmail.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
9627e03fa7 |
feat(sdk): stage 4 — canUndo/canRedo, removeElement GSAP cascade, override-set cleanup (#1431)
* feat(sdk): stage 4 — canUndo/canRedo, removeElement GSAP cascade, override-set cleanup * docs(sdk): document cascadeRemoveAnimations bare-id v1 limitation for scoped ids Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Miguel Ángel <miguel07alm@protonmail.com> * chore(sdk): remove sdk-status-report.txt from source tree Internal planning artifact should not be committed to the repo. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Miguel Ángel <miguel07alm@protonmail.com> --------- Co-authored-by: Miguel Ángel <miguel07alm@protonmail.com> |
||
|
|
5ecaac1fcb |
feat(sdk): can() returns CanResult; T4 dispatch-boundary tests (#1426)
* feat(sdk): can() returns CanResult; T4 dispatch-boundary tests
* fix(sdk): 8 code-review correctness fixes
- setGsapScript: remove element when newScript="" (fixes undo/redo duplicate-script bug)
- parseDeclarations: track quotes so ; inside CSS values (data URIs) doesn't split
- handleRemoveGsapKeyframe: guard against duplicate-percentage ambiguity (return EMPTY)
- resolveKeyframe: return kfs so callers can check uniqueness
- handleSetClassStyle: emit op:"add" (not "replace") when no prior <style> element
- FsAdapter listVersions: Number(f.split("_")[0]) — was NaN due to underscore in key
- FsAdapter doWrite: split try/catch so appendVersion failure doesn't fire error handlers
- FileAdapter playground: add content:"" field to satisfy PersistVersionEntry contract
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(sdk): export CanResult from package root so callers can switch on result.code
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Miguel Ángel <miguel07alm@protonmail.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Miguel Ángel <miguel07alm@protonmail.com>
|
||
|
|
0a30011abd |
fix(sdk): fs adapter flush() tracks in-flight writes; add to T13 contract suite (#1425)
* fix(sdk): fs adapter flush() tracks in-flight writes; add to T13 contract suite * fix(sdk): document flush() first-error rejection semantics Promise.all rejects on first write failure; errors also surface via persist:error event channel per write. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Miguel Ángel <miguel07alm@protonmail.com> --------- Co-authored-by: Miguel Ángel <miguel07alm@protonmail.com> |
||
|
|
577a689860 |
feat(sdk): file-backed fs adapter + setTiming GSAP sync; sdk-playground workspace (#1458)
* feat(sdk): file-backed fs adapter + setTiming GSAP-script sync; add sdk-playground * fix(sdk): address PR #1423 review — oxfmt, PersistVersionEntry contract, race, comments - bunx oxfmt packages/sdk-playground/index.html (unblocks CI) - PersistVersionEntry.content is now optional; HTTP adapter omits it for lazy-load - fs adapter: monotonic key (Date.now-NNNN) + per-path write serialization via promise chain - mutate.ts: fix wrong comment on GSAP sync reason; add caveat to "pre-parse once" note Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Miguel Ángel <miguel07alm@protonmail.com> * fix(core): oxfmt gsapSerialize.ts — unblocks Preflight across stack Pre-existing format issue on the base; fixing here to unblock CI. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Miguel Ángel <miguel07alm@protonmail.com> * chore: update bun.lock for sdk-playground workspace Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Miguel Ángel <miguel07alm@protonmail.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
b8fa4b5dd2 |
refactor(core): swap studio-api read path from recast to acorn parser (T6e) (#1392)
* refactor(core): swap studio-api read path from recast to acorn parser (T6e)
* fix(core,sdk): code-review findings — 5 correctness bugs + 2 cleanup
- gsapParserAcorn: top-level variable targets now resolved via program-scope
null-key fallback in lookupBindingFromAncestors (const el = querySelector...)
- gsapParserAcorn: fromTo guard requires args.length >= 3, preventing undefined
args[2]/args[3] access when fewer args supplied
- gsapWriterAcorn: remove fuzzing fallback in removeAnimationFromScript that
silently deleted the wrong animation (from→to ID conversion)
- gsapWriterAcorn: valueToCode guards NaN → "0" to avoid broken tween props;
safeKey regex aligned to ASCII-only (matching gsapSerialize)
- mutate: handleSetGsapTween now includes stagger in extras (was in addGsapTween
but missing from setGsapTween)
- apply-patches: script case now mirrors stylesheet — op=remove calls
setGsapScript("") instead of silently ignoring the patch
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* docs(core): add trust-model header to T6d parity suite
Documents the recast-baseline trust relationship and clarifies that
motionPath parity tests live in the Phase 3b commit (PR #1379) since
the acorn motionPath parser is also added there.
Addresses #1370 R1-N1 (Rames).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
|
||
|
|
6dcbb5530e | feat(sdk,core): phase 3b — 8 gsap/label ops + setClassStyle (#1379) | ||
|
|
84a56986c6 | fix(sdk): bridge preview selection into session state (#1362) | ||
|
|
ad5229af83 | fix(release): include sdk in fixed-version bump (#1363) | ||
|
|
a0ee97210b |
fix(sdk,core): css tokenizer, override-set replay, setattribute safety, persist errors (#1350)
* fix(sdk,core): css tokenizer, override-set replay, setattribute safety, persist errors * test(sdk,ci): smoke test + explicit sdk-tests CI gate Smoke test covers the full public surface: openComposition → setStyle/setText/dispatch(moveElement) → serialize applyPatches + ORIGIN_APPLY_PATCHES tagging batch() coalescing + transactional rollback on throw undo/redo round-trip persist adapter write + persist:error surfacing T3 embedded mode: override-set apply on open + getOverrides round-trip Adds sdk-tests CI job so SDK coverage is explicitly named and required — prevents a repeat of the demo-next vitest-never-ran incident. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(sdk): export adapter types, awaitable flush(), never-coalesce mode - Export PersistAdapter, PreviewAdapter, PersistVersionEntry from package root — callers can now write typed fakes without reaching into internals - Add flush(): Promise<void> to Composition interface + CompositionImpl — app-close handlers can await a clean drain of the persist queue - coalesceMs <= 0 disables coalescing entirely in createHistory — enables deterministic test scenarios without per-entry timestamp manipulation Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test(sdk): p2 edge cases — setText no-text-node, override-remove non-existent, flush in smoke - setText on element with no prior text node (firstTextIdx=-1 path) - applyOverrideSet null removal on non-existent prop is a no-op (no throw) - smoke persist test uses comp.flush() instead of setTimeout - can() JSDoc clarifies Phase 3b false-return is intentional feature-detection Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * ci: trigger regression suite * fix(ci): add packages/sdk/package.json to Dockerfile.test workspace copy bun install --frozen-lockfile fails in the regression Docker build because the lockfile references the sdk workspace member but its package.json was not copied into the image before the install step. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |