feat(sdk): stage 4 — canUndo/canRedo, removeElement GSAP cascade, override-set cleanup#1431
Conversation
edb9a98 to
d4d2899
Compare
04ba6aa to
ff4dca2
Compare
james-russo-rames-d-jusso
left a comment
There was a problem hiding this comment.
Verified at HEAD ff4dca2.
Three orthogonal features in one PR (canUndo/canRedo, GSAP cascade, override-set cleanup). PR body acknowledges this explicitly; each has its own test section; total surface is small (+373 / -3 across 5 source files + 1 stray report). Letting it through as one is reasonable given the override-set cleanup is logically tied to removeElement cascade — the cascade is incomplete without the override-set sweep.
GSAP cascade (handleRemoveElement in mutate.ts): the cascadeRemoveAnimations helper iterates the located animations and removes any whose targetSelector matches the removed id via the new selectorMatchesId helper. Selector match covers all three forms used in this codebase: [data-hf-id="X"], [data-hf-id='X'], #X. The helper is re-used in handleSetTiming to dedupe — good hygiene.
The patch shape is right: forward = [removeElement, replaceScript], inverse = [addElement, replaceScript-restore]. The test "applying inverse restores element and GSAP script to original" round-trips this cleanly. The "no GSAP script" path emits only the element patch (size-1), matching the test expectation. No orphan tweens — verified.
Override-set cleanup (session.ts _dispatch): the loop for (const p of forward) checks p.op === "remove" against the /elements/<id>$ path regex, then delete this.overrides[key] for every key starting with ${id}.. Correct shape. Behaviorally: the removal marker overrides["hf-title"] = null is set elsewhere in the existing pipeline (via the dispatch override-set update path that #1426 covers), and is preserved here because the regex anchors on ${id}. (with the dot — the bare marker hf-title is NOT touched). The test confirms overrides["hf-title"] is null AND hf-title.style.color is purged.
canUndo / canRedo: thin delegation to historyModule?.canUndo() ?? false. T3-embedded mode (no history module) returns false — correct, no spurious "undo available" on a fresh embedded composition. Test covers this.
Blocker
sdk-status-report.txt(+191 lines) at repo root. New file, not gitignored, not in a docs/ subtree, internal status report (dated June 13, names contacts, lists "stages not started"). This should not land in the source tree — it's a working artifact. Either move todocs/internal/(and probably gitignore the path), or drop the file from the diff. Trivial to fix; flagging as blocker because it pollutes the repo root with internal planning material.
Concerns
- Multi-element
removeElement+ override-set sweep ordering:handleRemoveElementacceptsHfId[](vector). If multiple ids are batched,_dispatchiteratesforwardonce and the regex-match path covers each/elements/<id>patch. Looks correct — but theforwardarray now has interleaved[remove_el_1, remove_el_2, ..., replace_script]; the regex only matches the^/elements/X$shape so the script patch is ignored. Tested for single removal; would be worth aremoveElement(["hf-a", "hf-b"])test confirming both override-sets get swept. - GSAP cascade in batched multi-remove:
currentScriptis threaded across the loop (if (currentScript) currentScript = cascadeRemoveAnimations(currentScript, id)), so successive removes operate on the previous result. Correct, but no test exercises this path either. - Cascade-remove of GSAP
addLabelreferences: the cascade removes animations targeting the element. If a label has a position computed from the removed element's animation, the label survives but its position may now reference a removed anim. Probably out of scope (labels are position-anchored, not element-anchored), but worth a thought. - No
Compositioninterface JSDoc update for canUndo/canRedo — types.ts only adds the signatures. Thecan()JSDoc in #1426 is detailed; matching tone on canUndo/canRedo would help host integrators.
Determinism: cascade and override-cleanup paths are pure data transforms — no Date.now() / Math.random() / fetch(). Author-time only. Good.
Verdict: blocker (sdk-status-report.txt removal). Concerns are non-blocking once that's resolved.
Review by Rames D Jusso
miguel-heygen
left a comment
There was a problem hiding this comment.
CI blocked by the root oxfmt failure in #1423. Code is correct and well-tested.
Strengths:
canUndo()/canRedo()correctly delegate tohistoryModule— no state duplication. The T3-embedded-mode test (returns false when no history) correctly verifies the null-history path.removeElementGSAP cascade — purging orphan property keys from the override-set on element removal is important for T3 embedded mode correctness; the test covers both the removal marker and the orphan key cleanup.- Override-set cleanup test verifies sibling elements are not disturbed — good cross-element isolation check.
Important:
packages/sdk/src/session.tsremoveElement— the GSAP cascade (handleGsapRemoveElement) is called inside the engine but is it covered by a test that actually checks a GSAP-scripted composition? The existing test usesBASE_HTMLwhich may not have a GSAP script block. IfhandleGsapRemoveElementis a no-op when no GSAP script is present, the cascade path itself is untested. Consider adding a fixture with a GSAP script where removing the element should also remove its tween.
Verdict: APPROVE (pending #1423 oxfmt fix)
Reasoning: canUndo/canRedo and override-set cleanup are correct; tests cover the core contracts.
— magi
ff4dca2 to
33aa492
Compare
d4d2899 to
23563d4
Compare
33aa492 to
43f39ef
Compare
23563d4 to
85f4ba6
Compare
|
@james-russo-rames-d-jusso — blocker resolved
On the other concerns:
|
ff19b27 to
f81b3c6
Compare
The base branch was changed.
…rride-set cleanup
… scoped ids Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Miguel Ángel <miguel07alm@protonmail.com>
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>
43f39ef to
7e6f442
Compare
miguel-heygen
left a comment
There was a problem hiding this comment.
Re-review of two fix commits since my prior APPROVE (ff4dca2):
ffc91ae — docs(sdk): document cascadeRemoveAnimations bare-id v1 limitation
7e6f442 — chore(sdk): remove sdk-status-report.txt from source tree
✅ Addressed
-
Important — cascade test coverage: I raised that the GSAP cascade path lacked a fixture with an actual GSAP script block. The fix commit adds a clear inline comment at
mutate.ts:509documenting the bare-id matching scope (v1 limitation: a bare-id match will affect all elements whose bare or scoped id matches, including sibling sub-compositions). This is an acceptable resolution for an Important-not-blocker finding — the limitation is now visible to future authors adding sub-composition fixtures. A GSAP-scripted test fixture remains a good follow-up but isn't required before merge. -
Chore:
sdk-status-report.txtcorrectly removed from the source tree. Good cleanup.
CI
All substantive checks pass (Preflight, SDK unit+contract+smoke, Test, Typecheck, Format, Lint, regression, player-perf, preview-regression). Windows runners are pending — pre-existing on this repo and not introduced by these commits.
Approval stands.
— magi

What
Stage 4 of the SDK — three features that complete the public history/editing API:
canUndo()/canRedo()onComposition— exposes the history module's existing state predicates on the public interface so hosts can enable/disable undo buttons without keeping their own patch counter.removeElementGSAP cascade — when removing an element, its targeting GSAP animations are also removed from the script and the change is folded into the same forward/inverse patch pair. Undo restores both the element and its animations atomically.Override-set orphan cleanup — after removing an element, its per-property override-set keys (
hf-x.style.fontSize,hf-x.text, etc.) are purged. Keeps T3 serialized override-sets compact and avoids stale key replay on future session opens.Also extracted
selectorMatchesId/cascadeRemoveAnimationshelpers to deduplicate the GSAP-target-matching pattern fromhandleSetTimingand reduce cognitive complexity.Why
canUndo/canRedoare needed for Studio toolbar state and any playground UI.Changes
src/types.tscanUndo(): boolean/canRedo(): booleantoCompositioninterfacesrc/session.tscanUndo/canRedodelegating tohistoryModule; add override-set orphan purge in_dispatchsrc/engine/mutate.tshandleRemoveElementGSAP cascade via newcascadeRemoveAnimations/selectorMatchesIdhelpers; same helpers used byhandleSetTimingto eliminate duplicationsrc/session.test.tscanUndo/canRedolifecycle tests (6 cases); override-set orphan cleanup tests (2 cases)src/engine/mutate.gsap.test.tsremoveElementGSAP cascade tests (5 cases)Test plan
bun run testinpackages/sdk— 156 tests passcomp.setStyle(id, ...)thencomp.removeElement(id); verifygetOverrides()shows noid.*keyscomp.canUndo()returns false before mutations, true after, false after undo back to start🤖 Generated with Claude Code