Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 30 additions & 1 deletion .fallowrc.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,26 @@
"createFailedCaptureCalibrationEstimate",
],
},
// gsapParser.ts is a public-API barrel that re-exports constants, types,
// and utilities from gsapConstants, gsapSerialize, and springEase. The
// re-exports are intentional public API consumed by callers outside the
// changed-file set (e.g. studio, aws-lambda) and therefore appear unused
// to fallow's static analysis of the PR diff.
{
"file": "packages/core/src/parsers/gsapParser.ts",
"exports": [
"PROPERTY_GROUPS",
"classifyPropertyGroup",
"classifyTweenPropertyGroup",
"SPRING_PRESETS",
"generateSpringEaseData",
"GsapMethod",
"GsapKeyframesData",
"GsapKeyframeFormat",
"PropertyGroupName",
"SpringPreset",
],
},
// Shared test helpers consumed by gsapParser.test.ts (same file,
// fallow doesn't trace intra-file test consumption).
{
Expand Down Expand Up @@ -231,6 +251,15 @@
// and runtime-scan effects, the per-element animations memo) whose
// complexity pre-dates the computed-timeline work. Exempted at file level
// for the same reason as files.ts rather than refactored as scope creep.
"ignore": ["packages/core/src/studio-api/routes/files.ts"],
//
// gsapParser.ts: the recast/babel GSAP writer is a 2500-line legacy parser
// restored as the default server writer by WS-3.F rework (acorn is now
// flag-gated behind STUDIO_SDK_CUTOVER_ENABLED). Its complexity pre-dates
// this PR and was present on all ancestor branches; the file-level exemption
// avoids the line-shift fingerprint problem for inherited findings.
"ignore": [
"packages/core/src/studio-api/routes/files.ts",
"packages/core/src/parsers/gsapParser.ts",
],
},
}
20 changes: 10 additions & 10 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,8 @@
"types": "./src/parsers/hfIds.ts"
},
"./gsap-parser": {
"import": "./src/parsers/gsapParser.ts",
"types": "./src/parsers/gsapParser.ts"
"import": "./src/parsers/gsapParserExports.ts",
"types": "./src/parsers/gsapParserExports.ts"
},
"./gsap-parser-acorn": {
"import": "./src/parsers/gsapParserAcorn.ts",
Expand Down Expand Up @@ -195,8 +195,8 @@
"types": "./dist/parsers/hfIds.d.ts"
},
"./gsap-parser": {
"import": "./dist/parsers/gsapParser.js",
"types": "./dist/parsers/gsapParser.d.ts"
"import": "./dist/parsers/gsapParserExports.js",
"types": "./dist/parsers/gsapParserExports.d.ts"
},
"./gsap-parser-acorn": {
"import": "./dist/parsers/gsapParserAcorn.js",
Expand Down
11 changes: 8 additions & 3 deletions packages/core/src/compiler/timingResolver.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,8 +179,12 @@ describe("resolveTimings — determinism", () => {
// (smart-seek) is exercised by timingCompiler.test.ts (Node.js) separately.
// The purity of resolveTimings() means this test fully covers resolver logic.

describe("preview == render parity golden test", () => {
it("resolver output is identical for preview and render call sites (shared single impl)", () => {
// NOTE: this asserts a PROPERTY of the pure resolver (same input → same output),
// not a guarantee about the two live paths — neither preview nor render calls
// resolveTimings yet (see timingResolver.ts header). It is a forward fixture for
// when they do, not proof they currently agree.
describe("resolveTimings — determinism fixture for future preview/render wiring", () => {
it("resolver output is identical for identical input (one impl → no drift once wired)", () => {
// Golden fixture: 3 elements, 2 words, 1 anchored, 2 free.
const elements: AuthoredTiming[] = [
authored("hf-title", 0, 2.0), // anchored
Expand All @@ -198,7 +202,8 @@ describe("preview == render parity golden test", () => {
// Simulate render call (same input as would arrive from timingCompiler)
const renderResult = resolveTimings({ elements, wordTimings, anchors });

// They must be identical — this is the "preview == render" guarantee.
// Identical because it is one pure function — this becomes the live
// "preview == render" guarantee only once both paths actually call it.
expect(previewResult).toEqual(renderResult);

// Spot-check the anchored element's resolved values:
Expand Down
16 changes: 10 additions & 6 deletions packages/core/src/compiler/timingResolver.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
/**
* Shared pure timing resolver — WS-C.
*
* resolveTimings() is the SINGLE implementation of word-anchored elastic timing.
* It is consumed by both:
* 1. The preview path (session layer in @hyperframes/sdk)
* 2. The render path (timingCompiler.ts + htmlBundler in @hyperframes/core)
* resolveTimings() is the single intended implementation of word-anchored
* elastic timing, designed to be the one code path that BOTH the preview
* (session layer in @hyperframes/sdk) and render (timingCompiler.ts +
* htmlBundler) sides call so they cannot drift apart.
*
* "preview == render" guarantee: there is exactly one code path for anchor
* resolution so both environments produce identical enter/exit times.
* NOT YET WIRED: neither path consumes it yet — the anchor-producing inputs
* (TTS word timings) arrive on the Pacific/backend side, which is deferred.
* Until a real caller lands, the "preview == render" parity below is a property
* of the resolver (one pure function) rather than a guarantee the two live
* paths currently share. Wire it into timingCompiler and session before
* relying on it for parity.
*
* Constraints:
* - Deterministic + pure: no Date.now(), no Math.random(), no DOM, no I/O.
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/generators/hyperframes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import {
isMediaElement,
isCompositionElement,
} from "../core.types";
import type { GsapAnimation } from "../parsers/gsapParser";
import { serializeGsapAnimations, keyframesToGsapAnimations } from "../parsers/gsapParser";
import type { GsapAnimation } from "../parsers/gsapSerialize";
import { serializeGsapAnimations, keyframesToGsapAnimations } from "../parsers/gsapSerialize";
import { GSAP_CDN, BASE_STYLES, ZOOM_CONTAINER_STYLES } from "../templates/constants";

const GOOGLE_FONTS_BASE = "https://fonts.googleapis.com/css2";
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,8 @@ describe("@hyperframes/core public API exports", () => {

describe("parser exports", () => {
it("does NOT re-export GSAP parser functions from barrel (available via gsap-parser subpath)", () => {
// GSAP parser uses recast (Node.js fs), so it's excluded from the barrel
// to keep browser bundles clean. Use @hyperframes/core/gsap-parser instead.
// GSAP AST parser functions are not re-exported from the barrel
// use the acorn parser (gsapParserAcorn) or writer (gsapWriterAcorn) directly.
expect(typeof (core as Record<string, unknown>).parseGsapScript).toBe("undefined");
});

Expand Down
6 changes: 2 additions & 4 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,8 @@ export {
ZOOM_CONTAINER_STYLES,
} from "./templates/constants";

// Parsers — recast-free GSAP helpers only. The AST parser (parseGsapScript and
// the script-mutation helpers) depends on recast/@babel/parser, which break in
// browser/SSR bundles; it is reachable only via the Node-only
// `@hyperframes/core/gsap-parser` subpath.
// Parsers — GSAP helpers. The AST parser (parseGsapScriptAcorn and write ops)
// is browser-safe; mutation helpers are in gsapWriterAcorn.
export type { GsapAnimation, GsapMethod, ParsedGsap } from "./parsers/gsapSerialize";

export {
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/parsers/gsapParser.golden.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
import { beforeAll, describe, expect, it } from "vitest";
import { join } from "node:path";
import { fileURLToPath } from "node:url";
import { parseGsapScript, serializeGsapAnimations } from "./gsapParser.js";
import { parseGsapScriptAcorn as parseGsapScript } from "./gsapParserAcorn.js";
import { serializeGsapAnimations } from "./gsapSerialize.js";

const __goldens__ = join(fileURLToPath(import.meta.url), "..", "__goldens__");
const g = (name: string) => join(__goldens__, name);
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/parsers/gsapParserAcorn.full.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
*/
import { describe, it, expect } from "vitest";
import { parseGsapScriptAcorn } from "./gsapParserAcorn.js";
import { serializeGsapAnimations } from "./gsapParser.js";
import type { GsapAnimation, GsapPercentageKeyframe } from "./gsapParser.js";
import { serializeGsapAnimations } from "./gsapSerialize.js";
import type { GsapAnimation, GsapPercentageKeyframe } from "./gsapSerialize.js";
import { classifyPropertyGroup, classifyTweenPropertyGroup } from "./gsapConstants.js";

const parseGsapScript = parseGsapScriptAcorn;
Expand Down
43 changes: 43 additions & 0 deletions packages/core/src/parsers/gsapParserExports.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/**
* @hyperframes/core/gsap-parser subpath entry.
*
* Re-exports all public types and helpers that external packages (studio, sdk,
* registry) import via the `@hyperframes/core/gsap-parser` subpath.
*
* The recast-based AST parser (gsapParser.ts) was retired in WS-3.F. The read
* path now uses `parseGsapScriptAcorn` from gsapParserAcorn; the write path
* uses gsapWriterAcorn. This file remains the stable public surface for types
* and serialize helpers.
*/
export type {
GsapAnimation,
GsapMethod,
GsapKeyframesData,
GsapPercentageKeyframe,
ParsedGsap,
ArcPathConfig,
ArcPathSegment,
GsapProvenanceKind,
GsapProvenance,
KeyframeEditability,
} from "./gsapSerialize.js";
export {
serializeGsapAnimations,
getAnimationsForElementId,
validateCompositionGsap,
keyframesToGsapAnimations,
gsapAnimationsToKeyframes,
editabilityForProvenance,
SUPPORTED_PROPS,
SUPPORTED_EASES,
} from "./gsapSerialize.js";
export type { PropertyGroupName } from "./gsapConstants.js";
export {
PROPERTY_GROUPS,
classifyPropertyGroup,
classifyTweenPropertyGroup,
} from "./gsapConstants.js";
export { generateSpringEaseData, SPRING_PRESETS } from "./springEase.js";
export type { SpringPreset } from "./springEase.js";
export { parseGsapScriptAcorn as parseGsapScript } from "./gsapParserAcorn.js";
export type { SplitAnimationsOptions, SplitAnimationsResult } from "./gsapSerialize.js";
16 changes: 16 additions & 0 deletions packages/core/src/parsers/gsapSerialize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,22 @@ export interface ParsedGsap {

export { SUPPORTED_PROPS, SUPPORTED_EASES } from "./gsapConstants";

// ── Split-animation types (used by gsapWriterAcorn) ─────────────────────────

export interface SplitAnimationsOptions {
originalId: string;
newId: string;
splitTime: number;
elementStart: number;
elementDuration: number;
}

export interface SplitAnimationsResult {
script: string;
/** Non-ID-selector animations that the engine cannot safely retarget. */
skippedSelectors: string[];
}

// ── Serialization ───────────────────────────────────────────────────────────

export function serializeGsapAnimations(
Expand Down
Loading
Loading