@hyperframes/parsers 0.8.2 → 0.8.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/composition.js.map +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/composition.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/types.ts","../src/fontAliases.ts","../src/utils/urlPath.ts","../src/compositionVariables.ts","../src/variableUsage.ts","../src/compositionContract.ts"],"sourcesContent":["// ── Composition data types ───────────────────────────────────────────────────\n// Moved from @hyperframes/core/core.types in the parsers extraction refactor.\n// These are the types produced and consumed by the parser pipeline.\n\nexport interface Asset {\n id: string;\n url: string;\n type: string;\n is_reference?: boolean;\n /** Duration in seconds for video/audio assets */\n duration?: number;\n}\n\n// ── Timeline types ──────────────────────────────────────────────────────────\n\nexport type TimelineElementType = \"video\" | \"image\" | \"text\" | \"audio\" | \"composition\";\nexport type MediaElementType = \"video\" | \"image\" | \"audio\";\n\nexport const CANVAS_DIMENSIONS = {\n landscape: { width: 1920, height: 1080 },\n portrait: { width: 1080, height: 1920 },\n \"landscape-4k\": { width: 3840, height: 2160 },\n \"portrait-4k\": { width: 2160, height: 3840 },\n square: { width: 1080, height: 1080 },\n \"square-4k\": { width: 2160, height: 2160 },\n} as const;\n\n// Single source of truth: derive the type from the table so adding a preset\n// extends the union automatically. Avoids the prior `as readonly CanvasResolution[]`\n// cast on `VALID_CANVAS_RESOLUTIONS` quietly drifting if the table grew but\n// the union didn't.\nexport type CanvasResolution = keyof typeof CANVAS_DIMENSIONS;\n\n// `Object.keys` ordering matches insertion order in `CANVAS_DIMENSIONS` on\n// every supported JS engine; tests pin the order in `index.test.ts`. Reorder\n// the table above with care.\nexport const VALID_CANVAS_RESOLUTIONS = Object.keys(\n CANVAS_DIMENSIONS,\n) as readonly CanvasResolution[];\n\nconst RESOLUTION_ALIASES: Record<string, CanvasResolution> = {\n \"1080p\": \"landscape\",\n hd: \"landscape\",\n \"1080p-portrait\": \"portrait\",\n \"portrait-1080p\": \"portrait\",\n \"4k\": \"landscape-4k\",\n uhd: \"landscape-4k\",\n \"4k-portrait\": \"portrait-4k\",\n \"1080p-square\": \"square\",\n \"square-1080p\": \"square\",\n \"4k-square\": \"square-4k\",\n};\n\n/**\n * Aliases that name a resolution *tier* (1080p / 4K) without also nailing an\n * orientation. Historically they all normalize to the `landscape` preset\n * (`normalizeResolutionFlag(\"1080p\") === \"landscape\"`), which then rejects\n * portrait / square compositions with a cryptic \"aspect ratio does not match\"\n * error deep inside the render pipeline. Consumers that know the composition's\n * dimensions can consult this set to decide whether to auto-adapt the preset's\n * orientation instead — see `adaptAspectAgnosticResolution` in the compile\n * stage (`@hyperframes/producer`) and `suggestMatchingPreset` here.\n *\n * Orientation-suffixed aliases (`1080p-portrait`, `4k-square`, …) are absent\n * on purpose: the user *did* pick an orientation, and honoring it is important\n * for the \"you asked for landscape but composed portrait\" error to still fire.\n */\nconst ASPECT_AGNOSTIC_RESOLUTION_ALIASES: ReadonlySet<string> = new Set([\n \"1080p\",\n \"hd\",\n \"4k\",\n \"uhd\",\n]);\n\n/**\n * Map a user-facing resolution string (canonical name or alias) to a\n * `CanvasResolution`. Returns undefined for unknown values so callers\n * can produce their own \"invalid\" UX (CLI exit, route validation, etc.).\n */\nexport function normalizeResolutionFlag(input: string | undefined): CanvasResolution | undefined {\n if (!input) return undefined;\n const lowered = input.toLowerCase();\n if ((VALID_CANVAS_RESOLUTIONS as readonly string[]).includes(lowered)) {\n return lowered as CanvasResolution;\n }\n return RESOLUTION_ALIASES[lowered];\n}\n\n/**\n * True when `input` names a resolution *tier* without nailing an orientation\n * (`1080p`, `hd`, `4k`, `uhd`). Case-insensitive.\n *\n * The `--resolution` CLI flag treats these as \"target this size; keep the\n * composition's orientation\" — a portrait 1080×1920 comp with `--resolution\n * 1080p` should land at 1080×1920, not blow up on aspect mismatch. Explicit\n * canonical presets (`landscape`, `portrait`, …) and orientation-suffixed\n * aliases (`1080p-portrait`) stay strict — the user picked an orientation.\n *\n * Consumers pair this signal with the composition's dimensions (in a\n * follow-up pass after HTML parse) to pick the right preset via\n * `suggestMatchingPreset` — see `adaptAspectAgnosticResolution` in the\n * compile stage (`@hyperframes/producer`) for the canonical remap.\n */\nexport function isAspectAgnosticResolutionAlias(input: string | undefined): boolean {\n if (!input) return false;\n return ASPECT_AGNOSTIC_RESOLUTION_ALIASES.has(input.toLowerCase());\n}\n\n/**\n * Public-boundary helper: given a raw `--resolution` / `--output-resolution`\n * flag value, return the pair every distributed render entrypoint needs to\n * forward end-to-end so the compile stage can adapt aspect-agnostic aliases\n * to the composition's orientation:\n *\n * - `outputResolution`: normalized {@link CanvasResolution} (or `undefined`\n * for unknown values — callers own their invalid-input UX).\n * - `outputResolutionAspectAgnostic`: `true` when the raw input was a\n * tier-only alias (`1080p` / `hd` / `4k` / `uhd`). Passes through to\n * `DistributedRenderConfig.outputResolutionAspectAgnostic` so the compile\n * stage remaps `landscape` → `portrait` / `square` when the composition\n * dimensions demand it.\n *\n * Exported to centralize the two-step pattern (`normalizeResolutionFlag` +\n * `isAspectAgnosticResolutionAlias`) that would otherwise be duplicated at\n * every entrypoint that emits a `DistributedRenderConfig` (`hyperframes\n * cloudrun render`, `hyperframes lambda render` / `render-batch`, the local\n * CLI). Divergence between those callers is what shipped the portrait-1080p\n * regression this helper prevents from recurring.\n */\nexport interface ResolvedResolutionFlag {\n outputResolution: CanvasResolution | undefined;\n outputResolutionAspectAgnostic: boolean;\n}\n\nexport function resolveResolutionFlagPair(input: string | undefined): ResolvedResolutionFlag {\n return {\n outputResolution: normalizeResolutionFlag(input),\n outputResolutionAspectAgnostic: isAspectAgnosticResolutionAlias(input),\n };\n}\n\nexport interface TimelineElementBase {\n id: string;\n type: TimelineElementType;\n name: string;\n startTime: number;\n duration: number;\n zIndex: number;\n x?: number;\n y?: number;\n scale?: number;\n opacity?: number;\n}\n\nexport interface TimelineMediaElement extends TimelineElementBase {\n type: MediaElementType;\n src: string;\n mediaStartTime?: number;\n sourceDuration?: number;\n isAroll?: boolean;\n sourceWidth?: number;\n sourceHeight?: number;\n volume?: number; // 0-1 (0% to 100%), default 1.0\n hasAudio?: boolean; // For videos - indicates if video has audio track\n}\n\nexport interface WaveformData {\n peaks: number[];\n duration: number;\n sampleRate?: number;\n}\n\nexport interface TimelineTextElement extends TimelineElementBase {\n type: \"text\";\n content: string;\n color?: string;\n fontSize?: number;\n textShadow?: boolean;\n fontFamily?: string;\n fontWeight?: number;\n textOutline?: boolean;\n textOutlineColor?: string;\n textOutlineWidth?: number;\n textHighlight?: boolean;\n textHighlightColor?: string;\n textHighlightPadding?: number;\n textHighlightRadius?: number;\n}\n\nexport interface TimelineCompositionElement extends TimelineElementBase {\n type: \"composition\";\n src: string;\n compositionId: string;\n scale?: number;\n sourceDuration?: number;\n variableValues?: Record<string, string | number | boolean>;\n sourceWidth?: number;\n sourceHeight?: number;\n}\n\n// Composition Variable Types\nexport type CompositionVariableType =\n | \"string\"\n | \"number\"\n | \"color\"\n | \"boolean\"\n | \"enum\"\n | \"font\"\n | \"image\";\n\n/**\n * Runtime list of every valid `CompositionVariableType`. Use this anywhere\n * a Set/array of valid type strings is needed (lint rules, validators).\n * The `satisfies` guard turns adding a new variant to the union without\n * also adding it here into a compile error.\n */\nexport const COMPOSITION_VARIABLE_TYPES = [\n \"string\",\n \"number\",\n \"color\",\n \"boolean\",\n \"enum\",\n \"font\",\n \"image\",\n] as const satisfies readonly CompositionVariableType[];\n\nexport interface CompositionVariableBase {\n id: string;\n type: CompositionVariableType;\n label: string;\n description?: string;\n}\n\nexport interface StringVariable extends CompositionVariableBase {\n type: \"string\";\n default: string;\n placeholder?: string;\n maxLength?: number;\n}\n\nexport interface NumberVariable extends CompositionVariableBase {\n type: \"number\";\n default: number;\n min?: number;\n max?: number;\n step?: number;\n unit?: string;\n}\n\nexport interface ColorVariable extends CompositionVariableBase {\n type: \"color\";\n default: string;\n /** Brand role identifier, e.g. \"color:primary\". */\n brandRole?: string;\n}\n\nexport interface BooleanVariable extends CompositionVariableBase {\n type: \"boolean\";\n default: boolean;\n}\n\nexport interface EnumVariable extends CompositionVariableBase {\n type: \"enum\";\n default: string;\n options: { value: string; label: string }[];\n}\n\n/**\n * Font variable — value is a `{name, source}` object (object-valued; LOCKED §7).\n * `default` is the fallback font-family name string.\n * `source` is the font stylesheet URL (e.g. Google Fonts CSS).\n * `default_name` / `default_source` are the CSS-level fallbacks when the\n * brand font is absent.\n */\nexport interface FontVariable extends CompositionVariableBase {\n type: \"font\";\n /** Fallback font-family name, e.g. \"Inter\". */\n default: string;\n /** Font stylesheet URL (e.g. Google Fonts CSS link). */\n source?: string;\n /** CSS font-family name to use when source is unavailable, e.g. \"sans-serif\". */\n default_name?: string;\n /** Fallback font stylesheet URL (empty string = system font). */\n default_source?: string;\n}\n\n/**\n * Image variable — value is a `{url, …}` object (object-valued; LOCKED §7).\n * `default` is the fallback image URL string.\n * `brandRole` is an optional semantic label, e.g. \"logo:primary\".\n */\nexport interface ImageVariable extends CompositionVariableBase {\n type: \"image\";\n /** Fallback image URL. */\n default: string;\n /** Brand role identifier, e.g. \"logo:primary\". */\n brandRole?: string;\n}\n\nexport type CompositionVariable =\n | StringVariable\n | NumberVariable\n | ColorVariable\n | BooleanVariable\n | EnumVariable\n | FontVariable\n | ImageVariable;\n\nexport interface CompositionSpec {\n id: string;\n duration: number;\n variables: CompositionVariable[];\n}\n\nexport type TimelineElement =\n | TimelineMediaElement\n | TimelineTextElement\n | TimelineCompositionElement;\n\nexport function isTextElement(el: TimelineElement): el is TimelineTextElement {\n return el.type === \"text\";\n}\n\nexport function isMediaElement(el: TimelineElement): el is TimelineMediaElement {\n return el.type === \"video\" || el.type === \"image\" || el.type === \"audio\";\n}\n\nexport function isCompositionElement(el: TimelineElement): el is TimelineCompositionElement {\n return el.type === \"composition\";\n}\n\nexport interface MediaFile {\n id: string;\n name: string;\n type: TimelineElementType;\n src: string;\n file?: File;\n duration?: number;\n compositionId?: string;\n sourceWidth?: number; // Intrinsic width for compositions\n sourceHeight?: number; // Intrinsic height for compositions\n}\n\nexport const TIMELINE_COLORS: Record<TimelineElementType, string> = {\n video: \"#ec4899\",\n image: \"#3b82f6\",\n text: \"#06b6d4\",\n audio: \"#10b981\",\n composition: \"#f97316\",\n};\n\nexport const DEFAULT_DURATIONS: Record<TimelineElementType, number> = {\n video: 5,\n image: 5,\n text: 2,\n audio: 5,\n composition: 5,\n};\n\nexport interface CompositionAPI {\n id: string;\n duration: number;\n seek(time: number): void;\n getTime(): number;\n getDuration(): number;\n}\n\n// ── Player API types (used by runtime) ────────────────────────────────────\n\nexport interface PlayerAPI {\n play(): void;\n pause(): void;\n seek(time: number, options?: { keepPlaying?: boolean }): void;\n getTime(): number;\n getDuration(): number;\n isPlaying(): boolean;\n getMainTimeline(): unknown;\n getElementBounds(elementId: string): void;\n getElementsAtPoint(x: number, y: number): void;\n setElementPosition(elementId: string, x: number, y: number): void;\n previewElementPosition(elementId: string, x: number, y: number): void;\n setElementKeyframes(\n elementId: string,\n keyframes: Array<{\n id: string;\n time: number;\n properties: { x?: number; y?: number };\n }> | null,\n ): void;\n setElementScale(elementId: string, scale: number): void;\n setElementFontSize(elementId: string, fontSize: number): void;\n setElementTextContent(elementId: string, content: string): void;\n setElementTextColor(elementId: string, color: string): void;\n setElementTextShadow(elementId: string, enabled: boolean): void;\n setElementTextFontWeight(elementId: string, weight: number): void;\n setElementTextFontFamily(elementId: string, fontFamily: string): void;\n setElementTextOutline(elementId: string, enabled: boolean, color?: string, width?: number): void;\n setElementTextHighlight(\n elementId: string,\n enabled: boolean,\n color?: string,\n padding?: number,\n radius?: number,\n ): void;\n setElementVolume(elementId: string, volume: number): void;\n setStageZoom(scale: number, focusX: number, focusY: number): void;\n getStageZoom(): { scale: number; focusX: number; focusY: number };\n setStageZoomKeyframes(\n keyframes: Array<{\n id: string;\n time: number;\n zoom: { scale: number; focusX: number; focusY: number };\n ease?: string;\n }> | null,\n ): void;\n getStageZoomKeyframes(): Array<{\n id: string;\n time: number;\n zoom: { scale: number; focusX: number; focusY: number };\n ease?: string;\n }>;\n addElement(data: AddElementData): boolean;\n removeElement(elementId: string): boolean;\n updateElementTiming(elementId: string, start?: number, end?: number): boolean;\n setElementTiming(\n elementId: string,\n startTime: number,\n duration: number,\n mediaStartTime?: number,\n ): void;\n updateElementSrc(elementId: string, src: string): boolean;\n updateElementLayer(elementId: string, zIndex: number): boolean;\n updateElementBasePosition(elementId: string, x?: number, y?: number, scale?: number): boolean;\n markTimelineDirty(): void;\n isTimelineDirty(): boolean;\n rebuildTimeline(): void;\n ensureTimeline(): void;\n enableRenderMode(): void;\n disableRenderMode(): void;\n renderSeek(time: number, options?: { suppressEvents?: boolean }): void;\n getElementVisibility(elementId: string): { visible: boolean; opacity?: number };\n getVisibleElements(): Array<{ id: string; tagName: string; start: number; end: number }>;\n getRenderState(): {\n time: number;\n duration: number;\n isPlaying: boolean;\n renderMode: boolean;\n timelineDirty: boolean;\n };\n}\n\nexport interface AddElementData {\n id: string;\n type: \"video\" | \"image\" | \"text\" | \"audio\" | \"composition\";\n name?: string;\n src?: string;\n content?: string;\n start: number;\n end: number;\n zIndex?: number;\n x?: number;\n y?: number;\n scale?: number;\n fontSize?: number;\n color?: string;\n textShadow?: boolean;\n fontWeight?: number;\n textOutline?: boolean;\n textOutlineColor?: string;\n textOutlineWidth?: number;\n textHighlight?: boolean;\n textHighlightColor?: string;\n textHighlightPadding?: number;\n textHighlightRadius?: number;\n compositionId?: string;\n sourceWidth?: number;\n sourceHeight?: number;\n isAroll?: boolean;\n}\n\nexport interface ValidationResult {\n valid: boolean;\n errors: string[];\n warnings: string[];\n}\n\nexport interface CompositionAsset {\n id: string;\n name: string;\n type: \"composition\";\n src: string;\n duration: number;\n compositionId: string;\n thumbnail?: string;\n}\n\nexport interface Keyframe {\n id: string;\n time: number;\n properties: Partial<KeyframeProperties>;\n ease?: string;\n}\n\nexport interface KeyframeProperties {\n x: number;\n y: number;\n opacity: number;\n scale: number;\n scaleX: number;\n scaleY: number;\n rotation: number;\n width: number;\n height: number;\n}\n\nexport interface ElementKeyframes {\n elementId: string;\n keyframes: Keyframe[];\n}\n\nexport interface StageZoom {\n scale: number;\n focusX: number;\n focusY: number;\n}\n\nexport interface StageZoomKeyframe {\n id: string;\n time: number;\n zoom: StageZoom;\n ease?: string;\n}\n\nexport function getDefaultStageZoom(resolution: CanvasResolution): StageZoom {\n const { width, height } = CANVAS_DIMENSIONS[resolution];\n return {\n scale: 1,\n focusX: width / 2,\n focusY: height / 2,\n };\n}\n","/**\n * Single source of truth for the deterministic font alias map. Both the\n * producer's @font-face injector and the core lint rules import from here,\n * eliminating manual drift between the two.\n *\n * Keys are lowercase font family names. Values are canonical font slugs\n * matching CANONICAL_FONTS keys in the producer's deterministicFonts module.\n */\nexport const FONT_ALIAS_MAP = {\n // ── Canonical bundled fonts (self-referencing) ────────────────────────\n inter: \"inter\",\n montserrat: \"montserrat\",\n outfit: \"outfit\",\n nunito: \"nunito\",\n oswald: \"oswald\",\n \"league gothic\": \"league-gothic\",\n \"archivo black\": \"archivo-black\",\n \"space mono\": \"space-mono\",\n \"ibm plex mono\": \"ibm-plex-mono\",\n \"jetbrains mono\": \"jetbrains-mono\",\n \"eb garamond\": \"eb-garamond\",\n \"playfair display\": \"playfair-display\",\n \"source code pro\": \"source-code-pro\",\n \"noto sans jp\": \"noto-sans-jp\",\n roboto: \"roboto\",\n \"open sans\": \"open-sans\",\n lato: \"lato\",\n poppins: \"poppins\",\n\n // ── Common aliases → nearest canonical ────────────────────────────────\n \"helvetica neue\": \"inter\",\n helvetica: \"inter\",\n arial: \"inter\",\n \"helvetica bold\": \"inter\",\n futura: \"montserrat\",\n \"din alternate\": \"montserrat\",\n \"arial black\": \"montserrat\",\n \"bebas neue\": \"league-gothic\",\n \"courier new\": \"jetbrains-mono\",\n courier: \"jetbrains-mono\",\n garamond: \"eb-garamond\",\n \"noto sans japanese\": \"noto-sans-jp\",\n \"segoe ui\": \"roboto\",\n\n // ── macOS sans-serif system fonts → inter ─────────────────────────────\n \"sf pro\": \"inter\",\n \"sf pro display\": \"inter\",\n \"sf pro text\": \"inter\",\n \"sf pro rounded\": \"inter\",\n avenir: \"inter\",\n \"avenir next\": \"inter\",\n \"lucida grande\": \"inter\",\n geneva: \"inter\",\n optima: \"inter\",\n\n // ── Windows sans-serif system fonts → inter ───────────────────────────\n verdana: \"inter\",\n tahoma: \"inter\",\n \"trebuchet ms\": \"inter\",\n calibri: \"inter\",\n candara: \"inter\",\n corbel: \"inter\",\n \"lucida sans\": \"inter\",\n \"lucida sans unicode\": \"inter\",\n\n // ── Linux sans-serif system fonts → inter ─────────────────────────────\n \"noto sans\": \"inter\",\n \"dejavu sans\": \"inter\",\n \"liberation sans\": \"inter\",\n\n // ── Monospace system fonts → jetbrains-mono ───────────────────────────\n \"sf mono\": \"jetbrains-mono\",\n menlo: \"jetbrains-mono\",\n monaco: \"jetbrains-mono\",\n consolas: \"jetbrains-mono\",\n \"lucida console\": \"jetbrains-mono\",\n \"lucida sans typewriter\": \"jetbrains-mono\",\n \"andale mono\": \"jetbrains-mono\",\n \"dejavu sans mono\": \"jetbrains-mono\",\n \"liberation mono\": \"jetbrains-mono\",\n\n // ── Serif system fonts → eb-garamond ──────────────────────────────────\n georgia: \"eb-garamond\",\n palatino: \"eb-garamond\",\n \"palatino linotype\": \"eb-garamond\",\n \"book antiqua\": \"eb-garamond\",\n cambria: \"eb-garamond\",\n times: \"eb-garamond\",\n \"times new roman\": \"eb-garamond\",\n \"dejavu serif\": \"eb-garamond\",\n \"liberation serif\": \"eb-garamond\",\n} satisfies Readonly<Record<string, string>>;\n\nexport const FONT_ALIAS_KEYS: ReadonlySet<string> = new Set(Object.keys(FONT_ALIAS_MAP));\n\n/**\n * Human-readable display names for canonical font slugs. Used by the lint\n * rule to tell authors what their aliased font will render as.\n */\nexport const CANONICAL_FONT_DISPLAY_NAMES: Readonly<Record<string, string>> = {\n inter: \"Inter\",\n montserrat: \"Montserrat\",\n outfit: \"Outfit\",\n nunito: \"Nunito\",\n oswald: \"Oswald\",\n \"league-gothic\": \"League Gothic\",\n \"archivo-black\": \"Archivo Black\",\n \"space-mono\": \"Space Mono\",\n \"ibm-plex-mono\": \"IBM Plex Mono\",\n \"jetbrains-mono\": \"JetBrains Mono\",\n \"eb-garamond\": \"EB Garamond\",\n \"playfair-display\": \"Playfair Display\",\n \"source-code-pro\": \"Source Code Pro\",\n \"noto-sans-jp\": \"Noto Sans JP\",\n roboto: \"Roboto\",\n \"open-sans\": \"Open Sans\",\n lato: \"Lato\",\n poppins: \"Poppins\",\n};\n\n/**\n * Resolve a font alias to its canonical display name, or undefined if the\n * alias is not in the map.\n */\nexport function resolveAliasDisplayName(alias: string): string | undefined {\n const slug = (FONT_ALIAS_MAP as Record<string, string>)[alias.toLowerCase()];\n if (!slug) return undefined;\n return CANONICAL_FONT_DISPLAY_NAMES[slug];\n}\n","export function decodeUrlPathVariants(path: string): string[] {\n const variants = [path];\n try {\n const decoded = decodeURIComponent(path);\n if (decoded !== path) variants.unshift(decoded);\n } catch {\n // Malformed percent sequences may be literal filesystem names.\n }\n\n return variants;\n}\n","/**\n * Browser-safe parser for the `data-composition-variables` schema attribute.\n * Lives outside htmlParser.ts so browser consumers (SDK, Studio, lint) can\n * import it via `@hyperframes/parsers/composition` without pulling the\n * linkedom/Node HTML-parser machinery from the main entry.\n */\n\nimport type { CompositionVariable, CompositionVariableType } from \"./types.js\";\n\n/**\n * Required typeof for each variable type's `default`. For font the default is\n * the font-family name string; for image it is the fallback URL string —\n * extra metadata fields on both are optional and not validated here.\n */\nconst DEFAULT_TYPEOF: Record<CompositionVariableType, \"string\" | \"number\" | \"boolean\"> = {\n string: \"string\",\n number: \"number\",\n color: \"string\",\n boolean: \"boolean\",\n enum: \"string\",\n font: \"string\",\n image: \"string\",\n};\n\n/**\n * Scalar variable values (string/number/boolean) are the ones that flow into\n * CSS custom props and text bindings; font/image values are object-shaped.\n * Shared so the SDK's CSS-compat writes, the runtime bindings, and Studio's\n * display logic can never disagree on what \"scalar\" means.\n */\nexport function isScalarVariableValue(value: unknown): value is string | number | boolean {\n return typeof value === \"string\" || typeof value === \"number\" || typeof value === \"boolean\";\n}\n\nfunction isRecord(v: unknown): v is Record<string, unknown> {\n return typeof v === \"object\" && v !== null;\n}\n\nfunction isVariableType(t: unknown): t is CompositionVariableType {\n return typeof t === \"string\" && t in DEFAULT_TYPEOF;\n}\n\n/**\n * True when the value is a structurally valid variable declaration: id, label,\n * a known type, a default matching that type, and options[] for enums. The\n * same predicate parseCompositionVariables filters with — exported so writers\n * (SDK declaration ops, Studio forms) can validate before persisting.\n */\nexport function isCompositionVariable(v: unknown): v is CompositionVariable {\n if (!isRecord(v)) return false;\n if (typeof v.id !== \"string\" || typeof v.label !== \"string\") return false;\n if (!isVariableType(v.type)) return false;\n if (typeof v.default !== DEFAULT_TYPEOF[v.type]) return false;\n if (v.type === \"enum\" && !Array.isArray(v.options)) return false;\n return true;\n}\n\n/**\n * Parse the typed variable declarations from an element's\n * `data-composition-variables` attribute. Malformed entries (wrong shape,\n * unknown type, default not matching the declared type) are dropped; an\n * absent attribute, invalid JSON, or a non-array payload yields `[]`.\n */\nexport function parseCompositionVariables(htmlEl: Element): CompositionVariable[] {\n const variablesAttr = htmlEl.getAttribute(\"data-composition-variables\");\n if (!variablesAttr) {\n return [];\n }\n\n try {\n const parsed = JSON.parse(variablesAttr);\n if (!Array.isArray(parsed)) {\n return [];\n }\n return parsed.filter(isCompositionVariable);\n } catch {\n return [];\n }\n}\n","/**\n * Browser-safe static scan for composition-variable reads in script text.\n *\n * Compositions read variables by calling the runtime API — `getVariables()`\n * bare (sub-comp scoped shadow) or via `__hyperframes.getVariables()` /\n * `window.__hyperframes.getVariables()` — and there is no DOM-attribute\n * binding to scan, so \"which variables does this composition use\" can only be\n * derived from the scripts. This is a best-effort static analysis: the\n * patterns agents actually write (destructuring, member access, a single\n * alias variable) resolve to ids; anything opaque flips `scanIncomplete`\n * so consumers can present usage as a lower bound instead of a fact.\n *\n * AST nodes are handled untyped (same convention as gsapParserAcorn.ts) —\n * acorn's structural types don't survive acorn-walk's visitor signatures.\n */\n\nimport * as acorn from \"acorn\";\nimport * as acornWalk from \"acorn-walk\";\n\nexport interface VariableUsageScan {\n /** Variable ids statically read by the script, in first-seen order. */\n usedIds: string[];\n /**\n * True when the script accesses variables in a way the scan cannot resolve\n * (computed keys, rest spreads, the values object escaping into a call…) or\n * when the script fails to parse — usedIds is then a lower bound.\n */\n scanIncomplete: boolean;\n}\n\ninterface Sink {\n use(id: string): void;\n incomplete(): void;\n}\n\n// oxlint-disable no-explicit-any -- untyped acorn AST traversal, see header\n\nfunction isGetVariablesCallee(callee: any): boolean {\n if (callee?.type === \"Identifier\") return callee.name === \"getVariables\";\n if (callee?.type === \"MemberExpression\" && !callee.computed) {\n return callee.property?.type === \"Identifier\" && callee.property.name === \"getVariables\";\n }\n return false;\n}\n\n/** Collect ids from an ObjectPattern destructuring of the values object. */\n// Exhaustive AST-node classification — branchy by nature, same as gsapParserAcorn.\n// fallow-ignore-next-line complexity\nfunction collectFromObjectPattern(pattern: any, out: Sink): void {\n for (const prop of pattern.properties ?? []) {\n if (prop?.type === \"RestElement\") {\n out.incomplete();\n continue;\n }\n if (prop?.type !== \"Property\") continue;\n if (prop.computed === true) {\n out.incomplete();\n } else if (prop.key?.type === \"Identifier\") {\n out.use(String(prop.key.name));\n } else if (prop.key?.type === \"Literal\" && typeof prop.key.value === \"string\") {\n out.use(prop.key.value);\n } else {\n out.incomplete();\n }\n }\n}\n\n/** Collect an id from a MemberExpression reading the values object. */\nfunction collectFromMemberAccess(member: any, out: Sink): void {\n if (member.computed !== true && member.property?.type === \"Identifier\") {\n out.use(String(member.property.name));\n } else if (\n member.computed === true &&\n member.property?.type === \"Literal\" &&\n typeof member.property.value === \"string\"\n ) {\n out.use(member.property.value);\n } else {\n out.incomplete();\n }\n}\n\n/**\n * Classify one read of the values object (a getVariables() call result or an\n * alias holding it) by its immediate syntactic context. Returns the alias\n * name when the value is bound to a plain variable (`const vars = …`).\n */\n// fallow-ignore-next-line complexity\nfunction classifyValueRead(parent: any, valueNode: any, out: Sink): string | null {\n if (!parent || parent.type === \"ExpressionStatement\") {\n // Bare statement — value unused, nothing read.\n return null;\n }\n if (parent.type === \"MemberExpression\" && parent.object === valueNode) {\n collectFromMemberAccess(parent, out);\n return null;\n }\n if (parent.type === \"VariableDeclarator\" && parent.init === valueNode) {\n if (parent.id?.type === \"ObjectPattern\") {\n collectFromObjectPattern(parent.id, out);\n return null;\n }\n if (parent.id?.type === \"Identifier\") return String(parent.id.name);\n out.incomplete();\n return null;\n }\n // The values object escapes (argument, return, spread, assignment…) —\n // reads beyond this point are invisible to the scan.\n out.incomplete();\n return null;\n}\n\nexport function scanVariableUsage(scriptText: string): VariableUsageScan {\n const usedIds: string[] = [];\n const seen = new Set<string>();\n let scanIncomplete = false;\n\n const sink: Sink = {\n use(id: string) {\n if (!seen.has(id)) {\n seen.add(id);\n usedIds.push(id);\n }\n },\n incomplete() {\n scanIncomplete = true;\n },\n };\n\n let ast: any;\n try {\n ast = acorn.parse(scriptText, { ecmaVersion: \"latest\", sourceType: \"script\" });\n } catch {\n return { usedIds: [], scanIncomplete: true };\n }\n\n const aliases = new Set<string>();\n\n // Pass 1: classify every getVariables() call by its parent context.\n acornWalk.ancestor(ast, {\n CallExpression(node: any, _: unknown, ancestors: any[]) {\n if (!isGetVariablesCallee(node.callee)) return;\n const parent = ancestors[ancestors.length - 2];\n const alias = classifyValueRead(parent, node, sink);\n if (alias) aliases.add(alias);\n },\n } as any);\n\n // Pass 2: classify every reference to an alias of the values object.\n // Scope-naive by design: an unrelated same-named identifier can only make\n // the scan report extra ids or flip scanIncomplete, never miss a read.\n if (aliases.size > 0) {\n acornWalk.ancestor(ast, {\n // fallow-ignore-next-line complexity\n Identifier(node: any, _: unknown, ancestors: any[]) {\n if (!aliases.has(String(node.name))) return;\n const parent = ancestors[ancestors.length - 2];\n if (!parent) return;\n // Skip the declarator that introduced the alias and property-position\n // identifiers that merely share the name.\n if (parent.type === \"VariableDeclarator\" && parent.id === node) return;\n if (parent.type === \"MemberExpression\" && parent.property === node) return;\n if (parent.type === \"Property\" && parent.key === node && parent.computed !== true) return;\n // Chained aliases (const v2 = vars) are not followed — flag instead\n // of silently missing reads through the second name.\n if (classifyValueRead(parent, node, sink)) sink.incomplete();\n },\n } as any);\n }\n\n return { usedIds, scanIncomplete };\n}\n","/**\n * Browser-safe authored composition contract.\n *\n * Source HTML is authored with data-start + data-duration + data-track-index.\n * data-end is compiler-derived and data-layer is legacy input only. Readers\n * accept legacy documents; writers always emit the canonical representation.\n */\n\nexport const COMPOSITION_CONTRACT_VERSION = 1 as const;\n\nexport const COMPOSITION_ATTRIBUTES = Object.freeze({\n start: \"data-start\",\n duration: \"data-duration\",\n trackIndex: \"data-track-index\",\n derivedEnd: \"data-end\",\n legacyTrack: \"data-layer\",\n} as const);\n\nexport const CANONICAL_AUTHORED_TIMING_ATTRIBUTES = Object.freeze([\n COMPOSITION_ATTRIBUTES.start,\n COMPOSITION_ATTRIBUTES.duration,\n COMPOSITION_ATTRIBUTES.trackIndex,\n] as const);\n\nexport const DERIVED_TIMING_ATTRIBUTES = Object.freeze([\n COMPOSITION_ATTRIBUTES.derivedEnd,\n] as const);\n\nexport const LEGACY_TIMING_ATTRIBUTES = Object.freeze([\n COMPOSITION_ATTRIBUTES.derivedEnd,\n COMPOSITION_ATTRIBUTES.legacyTrack,\n] as const);\n\nexport type ReferenceExpression =\n | { kind: \"absolute\"; value: number }\n | { kind: \"reference\"; refId: string; offset: number };\n\nexport type ClipTimingDiagnosticCode =\n | \"invalid-start\"\n | \"unresolved-start-reference\"\n | \"invalid-duration\"\n | \"invalid-end\"\n | \"end-before-start\"\n | \"deprecated-end\"\n | \"conflicting-end\"\n | \"invalid-track-index\"\n | \"deprecated-layer\"\n | \"conflicting-layer\";\n\nexport interface ClipTimingDiagnostic {\n code: ClipTimingDiagnosticCode;\n attribute: string;\n value: string | null;\n}\n\nexport interface ClipAttributeReader {\n getAttribute(name: string): string | null;\n}\n\nexport interface ClipAttributeWriter extends ClipAttributeReader {\n setAttribute(name: string, value: string): void;\n removeAttribute(name: string): void;\n}\n\nexport interface ReadClipTimingOptions {\n /** Resolve a reference to the referenced clip's absolute end time. */\n resolveReferenceEnd?: (refId: string) => number | null | undefined;\n /** Start used when data-start is absent. Defaults to zero. */\n defaultStart?: number | null;\n}\n\nexport interface ClipTiming {\n startExpression: ReferenceExpression | null;\n start: number | null;\n duration: number | null;\n end: number | null;\n trackIndex: number;\n durationSource: \"duration\" | \"legacy-end\" | \"missing\" | \"invalid\";\n trackSource: \"track-index\" | \"legacy-layer\" | \"default\" | \"invalid\";\n diagnostics: ClipTimingDiagnostic[];\n}\n\nexport interface ClipTimingUpdate {\n start?: number | string | ReferenceExpression;\n duration?: number | null;\n trackIndex?: number | null;\n}\n\nexport class ClipTimingWriteError extends Error {\n readonly code: \"invalid-start\" | \"invalid-duration\" | \"invalid-track-index\";\n\n constructor(code: ClipTimingWriteError[\"code\"], message: string) {\n super(message);\n this.name = \"ClipTimingWriteError\";\n this.code = code;\n }\n}\n\n/** Parse a value to a finite number, or null if it is absent/invalid. */\nexport function parseNumeric(value: string | null | undefined): number | null {\n if (value == null || value.trim() === \"\") return null;\n const parsed = Number(value);\n return Number.isFinite(parsed) ? parsed : null;\n}\n\nconst REFERENCE_ID_PATTERN = /^[A-Za-z0-9_.:-]+$/;\n\nfunction isAsciiDigitAt(value: string, index: number): boolean {\n const code = value.charCodeAt(index);\n return code >= 48 && code <= 57;\n}\n\nfunction skipDigitsLeft(value: string, start: number): number {\n let cursor = start;\n while (cursor >= 0 && isAsciiDigitAt(value, cursor)) cursor--;\n return cursor;\n}\n\nfunction skipWhitespaceLeft(value: string, start: number): number {\n let cursor = start;\n while (cursor >= 0 && (value[cursor] ?? \"\").trim() === \"\") cursor--;\n return cursor;\n}\n\nfunction findMagnitudeStart(value: string): number | null {\n const last = value.length - 1;\n if (!isAsciiDigitAt(value, last)) return null;\n let cursor = skipDigitsLeft(value, last);\n if (value[cursor] === \".\") cursor = skipDigitsLeft(value, cursor - 1);\n return cursor + 1;\n}\n\nfunction parseReferenceOffset(\n value: string,\n): { refId: string; operator: \"+\" | \"-\"; magnitude: number } | null {\n const magnitudeStart = findMagnitudeStart(value);\n if (magnitudeStart == null) return null;\n const operatorIndex = skipWhitespaceLeft(value, magnitudeStart - 1);\n const operator = value[operatorIndex];\n if (operator !== \"+\" && operator !== \"-\") return null;\n\n const refId = value.slice(0, operatorIndex).trim();\n if (!REFERENCE_ID_PATTERN.test(refId)) return null;\n const magnitude = Number(value.slice(magnitudeStart));\n if (!Number.isFinite(magnitude)) return null;\n return { refId, operator, magnitude };\n}\n\n/**\n * Parse the data-start grammar: absolute seconds, `clip-id`, or\n * `clip-id +/- offset`, where references resolve to the referenced clip's end.\n */\nexport function parseStartExpression(raw: string | null | undefined): ReferenceExpression | null {\n const normalized = (raw ?? \"\").trim();\n if (!normalized) return null;\n const absolute = parseNumeric(normalized);\n if (absolute != null) return { kind: \"absolute\", value: absolute };\n if (REFERENCE_ID_PATTERN.test(normalized)) {\n return { kind: \"reference\", refId: normalized, offset: 0 };\n }\n const reference = parseReferenceOffset(normalized);\n if (!reference) return null;\n return {\n kind: \"reference\",\n refId: reference.refId,\n offset: reference.operator === \"-\" ? -reference.magnitude : reference.magnitude,\n };\n}\n\nfunction pushDiagnostic(\n diagnostics: ClipTimingDiagnostic[],\n code: ClipTimingDiagnosticCode,\n attribute: string,\n value: string | null,\n): void {\n diagnostics.push({ code, attribute, value });\n}\n\nfunction resolveStart(\n expression: ReferenceExpression | null,\n rawStart: string | null,\n options: ReadClipTimingOptions,\n diagnostics: ClipTimingDiagnostic[],\n): number | null {\n if (rawStart == null || rawStart.trim() === \"\") {\n return options.defaultStart === undefined ? 0 : options.defaultStart;\n }\n if (!expression) {\n pushDiagnostic(diagnostics, \"invalid-start\", COMPOSITION_ATTRIBUTES.start, rawStart);\n return null;\n }\n if (expression.kind === \"absolute\") return Math.max(0, expression.value);\n const referencedEnd = options.resolveReferenceEnd?.(expression.refId);\n if (referencedEnd == null || !Number.isFinite(referencedEnd)) {\n pushDiagnostic(\n diagnostics,\n \"unresolved-start-reference\",\n COMPOSITION_ATTRIBUTES.start,\n rawStart,\n );\n return null;\n }\n return Math.max(0, referencedEnd + expression.offset);\n}\n\ntype DurationRead = Pick<ClipTiming, \"duration\" | \"end\" | \"durationSource\">;\ntype TrackRead = Pick<ClipTiming, \"trackIndex\" | \"trackSource\">;\n\n// Float pairs like `data-start=\"0.1\" + data-duration=\"0.2\"` add to\n// `0.30000000000000004`, so the compiler-emitted `data-end` string can be a few\n// ulps off the sum a reader computes from the canonical inputs. Any drift below\n// this ceiling is treated as equal for the derived-end reconciliation check —\n// well below one 60fps frame (~16.67 ms) and orders of magnitude above the\n// worst realistic IEEE-754 residual (~2e-16 s).\nconst DERIVED_END_EQUALITY_EPSILON_SECONDS = 1e-9;\n\nfunction derivedEndsAreConsistent(parsedEnd: number, canonicalEnd: number): boolean {\n return Math.abs(parsedEnd - canonicalEnd) <= DERIVED_END_EQUALITY_EPSILON_SECONDS;\n}\n\n// Reconcile a `data-end` attribute paired with canonical `data-duration`.\n//\n// The compiler (`compileTimingAttrs` in @hyperframes/core) writes\n// `data-end = data-start + data-duration` into the bundled HTML so the runtime\n// can key off a single attribute. That derived attribute is legitimate — it is\n// not a legacy authoring shape. Treating it as `deprecated-end` here made the\n// linter fire `deprecated_data_end` on the compiler's own output whenever\n// StaticGuard re-validated a bundled composition, producing a false-positive\n// cluster reported in the field (n=25+ across cli-feedback crons 61-68).\n//\n// We keep `deprecated-end` for the truly legacy shape (see `readLegacyEnd` —\n// `data-end` present without `data-duration`) and for the *conflicting* case\n// where an author left a stale `data-end` behind a canonical duration edit; a\n// stale end must not be silently overridden without a diagnostic.\nfunction diagnoseDerivedEnd(\n rawEnd: string,\n canonicalEnd: number | null,\n diagnostics: ClipTimingDiagnostic[],\n): void {\n const parsedEnd = parseNumeric(rawEnd);\n if (parsedEnd == null) {\n pushDiagnostic(diagnostics, \"deprecated-end\", COMPOSITION_ATTRIBUTES.derivedEnd, rawEnd);\n pushDiagnostic(diagnostics, \"invalid-end\", COMPOSITION_ATTRIBUTES.derivedEnd, rawEnd);\n return;\n }\n if (canonicalEnd == null) {\n // Canonical duration exists but resolved end is unknown (e.g. unresolved\n // reference start). Preserve prior behavior — flag as deprecated so authors\n // remove the stale companion attribute.\n pushDiagnostic(diagnostics, \"deprecated-end\", COMPOSITION_ATTRIBUTES.derivedEnd, rawEnd);\n return;\n }\n if (derivedEndsAreConsistent(parsedEnd, canonicalEnd)) {\n // Compiler-derived (or manually consistent) `data-end` alongside\n // `data-duration` — silent. No diagnostic.\n return;\n }\n pushDiagnostic(diagnostics, \"deprecated-end\", COMPOSITION_ATTRIBUTES.derivedEnd, rawEnd);\n pushDiagnostic(diagnostics, \"conflicting-end\", COMPOSITION_ATTRIBUTES.derivedEnd, rawEnd);\n}\n\nfunction readCanonicalDuration(\n rawDuration: string,\n start: number | null,\n diagnostics: ClipTimingDiagnostic[],\n): DurationRead {\n const duration = parseNumeric(rawDuration);\n if (duration == null || duration < 0) {\n pushDiagnostic(diagnostics, \"invalid-duration\", COMPOSITION_ATTRIBUTES.duration, rawDuration);\n return { duration: null, end: null, durationSource: \"invalid\" };\n }\n return {\n duration,\n end: start == null ? null : start + duration,\n durationSource: \"duration\",\n };\n}\n\nfunction readLegacyEnd(\n rawEnd: string,\n start: number | null,\n diagnostics: ClipTimingDiagnostic[],\n): DurationRead {\n pushDiagnostic(diagnostics, \"deprecated-end\", COMPOSITION_ATTRIBUTES.derivedEnd, rawEnd);\n const end = parseNumeric(rawEnd);\n if (end == null) {\n pushDiagnostic(diagnostics, \"invalid-end\", COMPOSITION_ATTRIBUTES.derivedEnd, rawEnd);\n return { duration: null, end: null, durationSource: \"invalid\" };\n }\n if (start == null) return { duration: null, end, durationSource: \"legacy-end\" };\n if (end < start) {\n pushDiagnostic(diagnostics, \"end-before-start\", COMPOSITION_ATTRIBUTES.derivedEnd, rawEnd);\n return { duration: null, end: null, durationSource: \"invalid\" };\n }\n return { duration: end - start, end, durationSource: \"legacy-end\" };\n}\n\nfunction readDuration(\n attributes: ClipAttributeReader,\n start: number | null,\n diagnostics: ClipTimingDiagnostic[],\n): DurationRead {\n const rawDuration = attributes.getAttribute(COMPOSITION_ATTRIBUTES.duration);\n const rawEnd = attributes.getAttribute(COMPOSITION_ATTRIBUTES.derivedEnd);\n if (rawDuration == null) {\n return rawEnd == null\n ? { duration: null, end: null, durationSource: \"missing\" }\n : readLegacyEnd(rawEnd, start, diagnostics);\n }\n const canonical = readCanonicalDuration(rawDuration, start, diagnostics);\n if (rawEnd != null) diagnoseDerivedEnd(rawEnd, canonical.end, diagnostics);\n return canonical;\n}\n\nfunction readTrackValue(\n rawValue: string,\n attribute: string,\n source: \"track-index\" | \"legacy-layer\",\n diagnostics: ClipTimingDiagnostic[],\n): TrackRead {\n const trackIndex = parseNumeric(rawValue);\n if (trackIndex == null || !Number.isInteger(trackIndex)) {\n pushDiagnostic(diagnostics, \"invalid-track-index\", attribute, rawValue);\n return { trackIndex: 0, trackSource: \"invalid\" };\n }\n return { trackIndex, trackSource: source };\n}\n\nfunction readTrack(\n attributes: ClipAttributeReader,\n diagnostics: ClipTimingDiagnostic[],\n): TrackRead {\n const rawTrack = attributes.getAttribute(COMPOSITION_ATTRIBUTES.trackIndex);\n const rawLayer = attributes.getAttribute(COMPOSITION_ATTRIBUTES.legacyTrack);\n if (rawTrack == null && rawLayer == null) return { trackIndex: 0, trackSource: \"default\" };\n if (rawTrack == null && rawLayer != null) {\n pushDiagnostic(diagnostics, \"deprecated-layer\", COMPOSITION_ATTRIBUTES.legacyTrack, rawLayer);\n return readTrackValue(\n rawLayer,\n COMPOSITION_ATTRIBUTES.legacyTrack,\n \"legacy-layer\",\n diagnostics,\n );\n }\n const canonical = readTrackValue(\n rawTrack ?? \"\",\n COMPOSITION_ATTRIBUTES.trackIndex,\n \"track-index\",\n diagnostics,\n );\n if (rawLayer == null) return canonical;\n pushDiagnostic(diagnostics, \"deprecated-layer\", COMPOSITION_ATTRIBUTES.legacyTrack, rawLayer);\n const parsedLayer = parseNumeric(rawLayer);\n if (parsedLayer != null && parsedLayer !== canonical.trackIndex) {\n pushDiagnostic(diagnostics, \"conflicting-layer\", COMPOSITION_ATTRIBUTES.legacyTrack, rawLayer);\n }\n return canonical;\n}\n\n/** Read canonical timing, accepting legacy attributes without preferring them. */\nexport function readClipTiming(\n attributes: ClipAttributeReader,\n options: ReadClipTimingOptions = {},\n): ClipTiming {\n const diagnostics: ClipTimingDiagnostic[] = [];\n const rawStart = attributes.getAttribute(COMPOSITION_ATTRIBUTES.start);\n const startExpression = parseStartExpression(rawStart);\n const start = resolveStart(startExpression, rawStart, options, diagnostics);\n const duration = readDuration(attributes, start, diagnostics);\n const track = readTrack(attributes, diagnostics);\n return { startExpression, start, ...duration, ...track, diagnostics };\n}\n\nfunction serializeStartNumber(start: number): string {\n if (!Number.isFinite(start)) {\n throw new ClipTimingWriteError(\"invalid-start\", \"start must be finite\");\n }\n return String(start);\n}\n\nfunction serializeStartReference(start: ReferenceExpression): string {\n if (start.kind === \"absolute\") return serializeStartNumber(start.value);\n if (!start.refId || !Number.isFinite(start.offset)) {\n throw new ClipTimingWriteError(\n \"invalid-start\",\n \"reference start must have an id and finite offset\",\n );\n }\n if (start.offset === 0) return start.refId;\n return `${start.refId} ${start.offset < 0 ? \"-\" : \"+\"} ${Math.abs(start.offset)}`;\n}\n\nfunction serializeStart(start: ClipTimingUpdate[\"start\"]): string {\n if (typeof start === \"number\") return serializeStartNumber(start);\n if (typeof start === \"object\" && start != null) return serializeStartReference(start);\n if (typeof start === \"string\" && parseStartExpression(start)) return start.trim();\n throw new ClipTimingWriteError(\"invalid-start\", `invalid start expression: ${start ?? \"\"}`);\n}\n\nfunction writeDuration(\n attributes: ClipAttributeWriter,\n duration: number | null | undefined,\n current: ClipTiming,\n): void {\n if (duration === null) {\n attributes.removeAttribute(COMPOSITION_ATTRIBUTES.duration);\n return;\n }\n if (duration === undefined) {\n if (\n attributes.getAttribute(COMPOSITION_ATTRIBUTES.duration) == null &&\n current.duration != null\n ) {\n attributes.setAttribute(COMPOSITION_ATTRIBUTES.duration, String(current.duration));\n }\n return;\n }\n if (!Number.isFinite(duration) || duration < 0) {\n throw new ClipTimingWriteError(\n \"invalid-duration\",\n \"duration must be a finite, non-negative number\",\n );\n }\n attributes.setAttribute(COMPOSITION_ATTRIBUTES.duration, String(duration));\n}\n\nfunction writeTrack(\n attributes: ClipAttributeWriter,\n trackIndex: number | null | undefined,\n current: ClipTiming,\n): void {\n if (trackIndex === null) {\n attributes.removeAttribute(COMPOSITION_ATTRIBUTES.trackIndex);\n return;\n }\n if (trackIndex === undefined) {\n if (\n attributes.getAttribute(COMPOSITION_ATTRIBUTES.trackIndex) == null &&\n current.trackSource === \"legacy-layer\"\n ) {\n attributes.setAttribute(COMPOSITION_ATTRIBUTES.trackIndex, String(current.trackIndex));\n }\n return;\n }\n if (!Number.isFinite(trackIndex) || !Number.isInteger(trackIndex)) {\n throw new ClipTimingWriteError(\"invalid-track-index\", \"trackIndex must be a finite integer\");\n }\n attributes.setAttribute(COMPOSITION_ATTRIBUTES.trackIndex, String(trackIndex));\n}\n\n/** Write only canonical authored timing and remove derived/legacy source attributes. */\nexport function writeClipTiming(\n attributes: ClipAttributeWriter,\n update: ClipTimingUpdate,\n): ClipTiming {\n const current = readClipTiming(attributes);\n if (update.start !== undefined) {\n attributes.setAttribute(COMPOSITION_ATTRIBUTES.start, serializeStart(update.start));\n }\n writeDuration(attributes, update.duration, current);\n writeTrack(attributes, update.trackIndex, current);\n // A legacy end paired with an unresolved reference start cannot be converted\n // to data-duration without a reference resolver. On a duration-omitting edit\n // (for example, moving only the track), preserve that sole duration source\n // instead of canonicalizing it into data loss.\n const preserveUnresolvedLegacyEnd =\n update.duration === undefined &&\n attributes.getAttribute(COMPOSITION_ATTRIBUTES.duration) == null &&\n attributes.getAttribute(COMPOSITION_ATTRIBUTES.derivedEnd) != null &&\n current.duration == null;\n if (!preserveUnresolvedLegacyEnd) {\n attributes.removeAttribute(COMPOSITION_ATTRIBUTES.derivedEnd);\n }\n attributes.removeAttribute(COMPOSITION_ATTRIBUTES.legacyTrack);\n return readClipTiming(attributes);\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/types.ts","../src/fontAliases.ts","../src/utils/urlPath.ts","../src/compositionVariables.ts","../src/variableUsage.ts","../src/compositionContract.ts"],"sourcesContent":["// ── Composition data types ───────────────────────────────────────────────────\n// Moved from @hyperframes/core/core.types in the parsers extraction refactor.\n// These are the types produced and consumed by the parser pipeline.\n\nexport interface Asset {\n id: string;\n url: string;\n type: string;\n is_reference?: boolean;\n /** Duration in seconds for video/audio assets */\n duration?: number;\n}\n\n// ── Timeline types ──────────────────────────────────────────────────────────\n\nexport type TimelineElementType = \"video\" | \"image\" | \"text\" | \"audio\" | \"composition\";\nexport type MediaElementType = \"video\" | \"image\" | \"audio\";\n\nexport const CANVAS_DIMENSIONS = {\n landscape: { width: 1920, height: 1080 },\n portrait: { width: 1080, height: 1920 },\n \"landscape-4k\": { width: 3840, height: 2160 },\n \"portrait-4k\": { width: 2160, height: 3840 },\n square: { width: 1080, height: 1080 },\n \"square-4k\": { width: 2160, height: 2160 },\n} as const;\n\n// Single source of truth: derive the type from the table so adding a preset\n// extends the union automatically. Avoids the prior `as readonly CanvasResolution[]`\n// cast on `VALID_CANVAS_RESOLUTIONS` quietly drifting if the table grew but\n// the union didn't.\nexport type CanvasResolution = keyof typeof CANVAS_DIMENSIONS;\n\n// `Object.keys` ordering matches insertion order in `CANVAS_DIMENSIONS` on\n// every supported JS engine; tests pin the order in `index.test.ts`. Reorder\n// the table above with care.\nexport const VALID_CANVAS_RESOLUTIONS = Object.keys(\n CANVAS_DIMENSIONS,\n) as readonly CanvasResolution[];\n\nconst RESOLUTION_ALIASES: Record<string, CanvasResolution> = {\n \"1080p\": \"landscape\",\n hd: \"landscape\",\n \"1080p-portrait\": \"portrait\",\n \"portrait-1080p\": \"portrait\",\n \"4k\": \"landscape-4k\",\n uhd: \"landscape-4k\",\n \"4k-portrait\": \"portrait-4k\",\n \"1080p-square\": \"square\",\n \"square-1080p\": \"square\",\n \"4k-square\": \"square-4k\",\n};\n\n/**\n * Aliases that name a resolution *tier* (1080p / 4K) without also nailing an\n * orientation. Historically they all normalize to the `landscape` preset\n * (`normalizeResolutionFlag(\"1080p\") === \"landscape\"`), which then rejects\n * portrait / square compositions with a cryptic \"aspect ratio does not match\"\n * error deep inside the render pipeline. Consumers that know the composition's\n * dimensions can consult this set to decide whether to auto-adapt the preset's\n * orientation instead — see `adaptAspectAgnosticResolution` in the compile\n * stage (`@hyperframes/producer`) and `suggestMatchingPreset` here.\n *\n * Orientation-suffixed aliases (`1080p-portrait`, `4k-square`, …) are absent\n * on purpose: the user *did* pick an orientation, and honoring it is important\n * for the \"you asked for landscape but composed portrait\" error to still fire.\n */\nconst ASPECT_AGNOSTIC_RESOLUTION_ALIASES: ReadonlySet<string> = new Set([\n \"1080p\",\n \"hd\",\n \"4k\",\n \"uhd\",\n]);\n\n/**\n * Map a user-facing resolution string (canonical name or alias) to a\n * `CanvasResolution`. Returns undefined for unknown values so callers\n * can produce their own \"invalid\" UX (CLI exit, route validation, etc.).\n */\nexport function normalizeResolutionFlag(input: string | undefined): CanvasResolution | undefined {\n if (!input) return undefined;\n const lowered = input.toLowerCase();\n if ((VALID_CANVAS_RESOLUTIONS as readonly string[]).includes(lowered)) {\n return lowered as CanvasResolution;\n }\n return RESOLUTION_ALIASES[lowered];\n}\n\n/**\n * True when `input` names a resolution *tier* without nailing an orientation\n * (`1080p`, `hd`, `4k`, `uhd`). Case-insensitive.\n *\n * The `--resolution` CLI flag treats these as \"target this size; keep the\n * composition's orientation\" — a portrait 1080×1920 comp with `--resolution\n * 1080p` should land at 1080×1920, not blow up on aspect mismatch. Explicit\n * canonical presets (`landscape`, `portrait`, …) and orientation-suffixed\n * aliases (`1080p-portrait`) stay strict — the user picked an orientation.\n *\n * Consumers pair this signal with the composition's dimensions (in a\n * follow-up pass after HTML parse) to pick the right preset via\n * `suggestMatchingPreset` — see `adaptAspectAgnosticResolution` in the\n * compile stage (`@hyperframes/producer`) for the canonical remap.\n */\nexport function isAspectAgnosticResolutionAlias(input: string | undefined): boolean {\n if (!input) return false;\n return ASPECT_AGNOSTIC_RESOLUTION_ALIASES.has(input.toLowerCase());\n}\n\n/**\n * Public-boundary helper: given a raw `--resolution` / `--output-resolution`\n * flag value, return the pair every distributed render entrypoint needs to\n * forward end-to-end so the compile stage can adapt aspect-agnostic aliases\n * to the composition's orientation:\n *\n * - `outputResolution`: normalized {@link CanvasResolution} (or `undefined`\n * for unknown values — callers own their invalid-input UX).\n * - `outputResolutionAspectAgnostic`: `true` when the raw input was a\n * tier-only alias (`1080p` / `hd` / `4k` / `uhd`). Passes through to\n * `DistributedRenderConfig.outputResolutionAspectAgnostic` so the compile\n * stage remaps `landscape` → `portrait` / `square` when the composition\n * dimensions demand it.\n *\n * Exported to centralize the two-step pattern (`normalizeResolutionFlag` +\n * `isAspectAgnosticResolutionAlias`) that would otherwise be duplicated at\n * every entrypoint that emits a `DistributedRenderConfig` (`hyperframes\n * cloudrun render`, `hyperframes lambda render` / `render-batch`, the local\n * CLI). Divergence between those callers is what shipped the portrait-1080p\n * regression this helper prevents from recurring.\n */\nexport interface ResolvedResolutionFlag {\n outputResolution: CanvasResolution | undefined;\n outputResolutionAspectAgnostic: boolean;\n}\n\nexport function resolveResolutionFlagPair(input: string | undefined): ResolvedResolutionFlag {\n return {\n outputResolution: normalizeResolutionFlag(input),\n outputResolutionAspectAgnostic: isAspectAgnosticResolutionAlias(input),\n };\n}\n\nexport interface TimelineElementBase {\n id: string;\n type: TimelineElementType;\n name: string;\n startTime: number;\n duration: number;\n zIndex: number;\n x?: number;\n y?: number;\n scale?: number;\n opacity?: number;\n}\n\nexport interface TimelineMediaElement extends TimelineElementBase {\n type: MediaElementType;\n src: string;\n mediaStartTime?: number;\n sourceDuration?: number;\n isAroll?: boolean;\n sourceWidth?: number;\n sourceHeight?: number;\n volume?: number; // linear gain; 0 is silent, 1 is 0 dB, values above 1 boost\n hasAudio?: boolean; // For videos - indicates if video has audio track\n}\n\nexport interface WaveformData {\n peaks: number[];\n duration: number;\n sampleRate?: number;\n}\n\nexport interface TimelineTextElement extends TimelineElementBase {\n type: \"text\";\n content: string;\n color?: string;\n fontSize?: number;\n textShadow?: boolean;\n fontFamily?: string;\n fontWeight?: number;\n textOutline?: boolean;\n textOutlineColor?: string;\n textOutlineWidth?: number;\n textHighlight?: boolean;\n textHighlightColor?: string;\n textHighlightPadding?: number;\n textHighlightRadius?: number;\n}\n\nexport interface TimelineCompositionElement extends TimelineElementBase {\n type: \"composition\";\n src: string;\n compositionId: string;\n scale?: number;\n sourceDuration?: number;\n variableValues?: Record<string, string | number | boolean>;\n sourceWidth?: number;\n sourceHeight?: number;\n}\n\n// Composition Variable Types\nexport type CompositionVariableType =\n | \"string\"\n | \"number\"\n | \"color\"\n | \"boolean\"\n | \"enum\"\n | \"font\"\n | \"image\";\n\n/**\n * Runtime list of every valid `CompositionVariableType`. Use this anywhere\n * a Set/array of valid type strings is needed (lint rules, validators).\n * The `satisfies` guard turns adding a new variant to the union without\n * also adding it here into a compile error.\n */\nexport const COMPOSITION_VARIABLE_TYPES = [\n \"string\",\n \"number\",\n \"color\",\n \"boolean\",\n \"enum\",\n \"font\",\n \"image\",\n] as const satisfies readonly CompositionVariableType[];\n\nexport interface CompositionVariableBase {\n id: string;\n type: CompositionVariableType;\n label: string;\n description?: string;\n}\n\nexport interface StringVariable extends CompositionVariableBase {\n type: \"string\";\n default: string;\n placeholder?: string;\n maxLength?: number;\n}\n\nexport interface NumberVariable extends CompositionVariableBase {\n type: \"number\";\n default: number;\n min?: number;\n max?: number;\n step?: number;\n unit?: string;\n}\n\nexport interface ColorVariable extends CompositionVariableBase {\n type: \"color\";\n default: string;\n /** Brand role identifier, e.g. \"color:primary\". */\n brandRole?: string;\n}\n\nexport interface BooleanVariable extends CompositionVariableBase {\n type: \"boolean\";\n default: boolean;\n}\n\nexport interface EnumVariable extends CompositionVariableBase {\n type: \"enum\";\n default: string;\n options: { value: string; label: string }[];\n}\n\n/**\n * Font variable — value is a `{name, source}` object (object-valued; LOCKED §7).\n * `default` is the fallback font-family name string.\n * `source` is the font stylesheet URL (e.g. Google Fonts CSS).\n * `default_name` / `default_source` are the CSS-level fallbacks when the\n * brand font is absent.\n */\nexport interface FontVariable extends CompositionVariableBase {\n type: \"font\";\n /** Fallback font-family name, e.g. \"Inter\". */\n default: string;\n /** Font stylesheet URL (e.g. Google Fonts CSS link). */\n source?: string;\n /** CSS font-family name to use when source is unavailable, e.g. \"sans-serif\". */\n default_name?: string;\n /** Fallback font stylesheet URL (empty string = system font). */\n default_source?: string;\n}\n\n/**\n * Image variable — value is a `{url, …}` object (object-valued; LOCKED §7).\n * `default` is the fallback image URL string.\n * `brandRole` is an optional semantic label, e.g. \"logo:primary\".\n */\nexport interface ImageVariable extends CompositionVariableBase {\n type: \"image\";\n /** Fallback image URL. */\n default: string;\n /** Brand role identifier, e.g. \"logo:primary\". */\n brandRole?: string;\n}\n\nexport type CompositionVariable =\n | StringVariable\n | NumberVariable\n | ColorVariable\n | BooleanVariable\n | EnumVariable\n | FontVariable\n | ImageVariable;\n\nexport interface CompositionSpec {\n id: string;\n duration: number;\n variables: CompositionVariable[];\n}\n\nexport type TimelineElement =\n | TimelineMediaElement\n | TimelineTextElement\n | TimelineCompositionElement;\n\nexport function isTextElement(el: TimelineElement): el is TimelineTextElement {\n return el.type === \"text\";\n}\n\nexport function isMediaElement(el: TimelineElement): el is TimelineMediaElement {\n return el.type === \"video\" || el.type === \"image\" || el.type === \"audio\";\n}\n\nexport function isCompositionElement(el: TimelineElement): el is TimelineCompositionElement {\n return el.type === \"composition\";\n}\n\nexport interface MediaFile {\n id: string;\n name: string;\n type: TimelineElementType;\n src: string;\n file?: File;\n duration?: number;\n compositionId?: string;\n sourceWidth?: number; // Intrinsic width for compositions\n sourceHeight?: number; // Intrinsic height for compositions\n}\n\nexport const TIMELINE_COLORS: Record<TimelineElementType, string> = {\n video: \"#ec4899\",\n image: \"#3b82f6\",\n text: \"#06b6d4\",\n audio: \"#10b981\",\n composition: \"#f97316\",\n};\n\nexport const DEFAULT_DURATIONS: Record<TimelineElementType, number> = {\n video: 5,\n image: 5,\n text: 2,\n audio: 5,\n composition: 5,\n};\n\nexport interface CompositionAPI {\n id: string;\n duration: number;\n seek(time: number): void;\n getTime(): number;\n getDuration(): number;\n}\n\n// ── Player API types (used by runtime) ────────────────────────────────────\n\nexport interface PlayerAPI {\n play(): void;\n pause(): void;\n seek(time: number, options?: { keepPlaying?: boolean }): void;\n getTime(): number;\n getDuration(): number;\n isPlaying(): boolean;\n getMainTimeline(): unknown;\n getElementBounds(elementId: string): void;\n getElementsAtPoint(x: number, y: number): void;\n setElementPosition(elementId: string, x: number, y: number): void;\n previewElementPosition(elementId: string, x: number, y: number): void;\n setElementKeyframes(\n elementId: string,\n keyframes: Array<{\n id: string;\n time: number;\n properties: { x?: number; y?: number };\n }> | null,\n ): void;\n setElementScale(elementId: string, scale: number): void;\n setElementFontSize(elementId: string, fontSize: number): void;\n setElementTextContent(elementId: string, content: string): void;\n setElementTextColor(elementId: string, color: string): void;\n setElementTextShadow(elementId: string, enabled: boolean): void;\n setElementTextFontWeight(elementId: string, weight: number): void;\n setElementTextFontFamily(elementId: string, fontFamily: string): void;\n setElementTextOutline(elementId: string, enabled: boolean, color?: string, width?: number): void;\n setElementTextHighlight(\n elementId: string,\n enabled: boolean,\n color?: string,\n padding?: number,\n radius?: number,\n ): void;\n setElementVolume(elementId: string, volume: number): void;\n setStageZoom(scale: number, focusX: number, focusY: number): void;\n getStageZoom(): { scale: number; focusX: number; focusY: number };\n setStageZoomKeyframes(\n keyframes: Array<{\n id: string;\n time: number;\n zoom: { scale: number; focusX: number; focusY: number };\n ease?: string;\n }> | null,\n ): void;\n getStageZoomKeyframes(): Array<{\n id: string;\n time: number;\n zoom: { scale: number; focusX: number; focusY: number };\n ease?: string;\n }>;\n addElement(data: AddElementData): boolean;\n removeElement(elementId: string): boolean;\n updateElementTiming(elementId: string, start?: number, end?: number): boolean;\n setElementTiming(\n elementId: string,\n startTime: number,\n duration: number,\n mediaStartTime?: number,\n ): void;\n updateElementSrc(elementId: string, src: string): boolean;\n updateElementLayer(elementId: string, zIndex: number): boolean;\n updateElementBasePosition(elementId: string, x?: number, y?: number, scale?: number): boolean;\n markTimelineDirty(): void;\n isTimelineDirty(): boolean;\n rebuildTimeline(): void;\n ensureTimeline(): void;\n enableRenderMode(): void;\n disableRenderMode(): void;\n renderSeek(time: number, options?: { suppressEvents?: boolean }): void;\n getElementVisibility(elementId: string): { visible: boolean; opacity?: number };\n getVisibleElements(): Array<{ id: string; tagName: string; start: number; end: number }>;\n getRenderState(): {\n time: number;\n duration: number;\n isPlaying: boolean;\n renderMode: boolean;\n timelineDirty: boolean;\n };\n}\n\nexport interface AddElementData {\n id: string;\n type: \"video\" | \"image\" | \"text\" | \"audio\" | \"composition\";\n name?: string;\n src?: string;\n content?: string;\n start: number;\n end: number;\n zIndex?: number;\n x?: number;\n y?: number;\n scale?: number;\n fontSize?: number;\n color?: string;\n textShadow?: boolean;\n fontWeight?: number;\n textOutline?: boolean;\n textOutlineColor?: string;\n textOutlineWidth?: number;\n textHighlight?: boolean;\n textHighlightColor?: string;\n textHighlightPadding?: number;\n textHighlightRadius?: number;\n compositionId?: string;\n sourceWidth?: number;\n sourceHeight?: number;\n isAroll?: boolean;\n}\n\nexport interface ValidationResult {\n valid: boolean;\n errors: string[];\n warnings: string[];\n}\n\nexport interface CompositionAsset {\n id: string;\n name: string;\n type: \"composition\";\n src: string;\n duration: number;\n compositionId: string;\n thumbnail?: string;\n}\n\nexport interface Keyframe {\n id: string;\n time: number;\n properties: Partial<KeyframeProperties>;\n ease?: string;\n}\n\nexport interface KeyframeProperties {\n x: number;\n y: number;\n opacity: number;\n scale: number;\n scaleX: number;\n scaleY: number;\n rotation: number;\n width: number;\n height: number;\n}\n\nexport interface ElementKeyframes {\n elementId: string;\n keyframes: Keyframe[];\n}\n\nexport interface StageZoom {\n scale: number;\n focusX: number;\n focusY: number;\n}\n\nexport interface StageZoomKeyframe {\n id: string;\n time: number;\n zoom: StageZoom;\n ease?: string;\n}\n\nexport function getDefaultStageZoom(resolution: CanvasResolution): StageZoom {\n const { width, height } = CANVAS_DIMENSIONS[resolution];\n return {\n scale: 1,\n focusX: width / 2,\n focusY: height / 2,\n };\n}\n","/**\n * Single source of truth for the deterministic font alias map. Both the\n * producer's @font-face injector and the core lint rules import from here,\n * eliminating manual drift between the two.\n *\n * Keys are lowercase font family names. Values are canonical font slugs\n * matching CANONICAL_FONTS keys in the producer's deterministicFonts module.\n */\nexport const FONT_ALIAS_MAP = {\n // ── Canonical bundled fonts (self-referencing) ────────────────────────\n inter: \"inter\",\n montserrat: \"montserrat\",\n outfit: \"outfit\",\n nunito: \"nunito\",\n oswald: \"oswald\",\n \"league gothic\": \"league-gothic\",\n \"archivo black\": \"archivo-black\",\n \"space mono\": \"space-mono\",\n \"ibm plex mono\": \"ibm-plex-mono\",\n \"jetbrains mono\": \"jetbrains-mono\",\n \"eb garamond\": \"eb-garamond\",\n \"playfair display\": \"playfair-display\",\n \"source code pro\": \"source-code-pro\",\n \"noto sans jp\": \"noto-sans-jp\",\n roboto: \"roboto\",\n \"open sans\": \"open-sans\",\n lato: \"lato\",\n poppins: \"poppins\",\n\n // ── Common aliases → nearest canonical ────────────────────────────────\n \"helvetica neue\": \"inter\",\n helvetica: \"inter\",\n arial: \"inter\",\n \"helvetica bold\": \"inter\",\n futura: \"montserrat\",\n \"din alternate\": \"montserrat\",\n \"arial black\": \"montserrat\",\n \"bebas neue\": \"league-gothic\",\n \"courier new\": \"jetbrains-mono\",\n courier: \"jetbrains-mono\",\n garamond: \"eb-garamond\",\n \"noto sans japanese\": \"noto-sans-jp\",\n \"segoe ui\": \"roboto\",\n\n // ── macOS sans-serif system fonts → inter ─────────────────────────────\n \"sf pro\": \"inter\",\n \"sf pro display\": \"inter\",\n \"sf pro text\": \"inter\",\n \"sf pro rounded\": \"inter\",\n avenir: \"inter\",\n \"avenir next\": \"inter\",\n \"lucida grande\": \"inter\",\n geneva: \"inter\",\n optima: \"inter\",\n\n // ── Windows sans-serif system fonts → inter ───────────────────────────\n verdana: \"inter\",\n tahoma: \"inter\",\n \"trebuchet ms\": \"inter\",\n calibri: \"inter\",\n candara: \"inter\",\n corbel: \"inter\",\n \"lucida sans\": \"inter\",\n \"lucida sans unicode\": \"inter\",\n\n // ── Linux sans-serif system fonts → inter ─────────────────────────────\n \"noto sans\": \"inter\",\n \"dejavu sans\": \"inter\",\n \"liberation sans\": \"inter\",\n\n // ── Monospace system fonts → jetbrains-mono ───────────────────────────\n \"sf mono\": \"jetbrains-mono\",\n menlo: \"jetbrains-mono\",\n monaco: \"jetbrains-mono\",\n consolas: \"jetbrains-mono\",\n \"lucida console\": \"jetbrains-mono\",\n \"lucida sans typewriter\": \"jetbrains-mono\",\n \"andale mono\": \"jetbrains-mono\",\n \"dejavu sans mono\": \"jetbrains-mono\",\n \"liberation mono\": \"jetbrains-mono\",\n\n // ── Serif system fonts → eb-garamond ──────────────────────────────────\n georgia: \"eb-garamond\",\n palatino: \"eb-garamond\",\n \"palatino linotype\": \"eb-garamond\",\n \"book antiqua\": \"eb-garamond\",\n cambria: \"eb-garamond\",\n times: \"eb-garamond\",\n \"times new roman\": \"eb-garamond\",\n \"dejavu serif\": \"eb-garamond\",\n \"liberation serif\": \"eb-garamond\",\n} satisfies Readonly<Record<string, string>>;\n\nexport const FONT_ALIAS_KEYS: ReadonlySet<string> = new Set(Object.keys(FONT_ALIAS_MAP));\n\n/**\n * Human-readable display names for canonical font slugs. Used by the lint\n * rule to tell authors what their aliased font will render as.\n */\nexport const CANONICAL_FONT_DISPLAY_NAMES: Readonly<Record<string, string>> = {\n inter: \"Inter\",\n montserrat: \"Montserrat\",\n outfit: \"Outfit\",\n nunito: \"Nunito\",\n oswald: \"Oswald\",\n \"league-gothic\": \"League Gothic\",\n \"archivo-black\": \"Archivo Black\",\n \"space-mono\": \"Space Mono\",\n \"ibm-plex-mono\": \"IBM Plex Mono\",\n \"jetbrains-mono\": \"JetBrains Mono\",\n \"eb-garamond\": \"EB Garamond\",\n \"playfair-display\": \"Playfair Display\",\n \"source-code-pro\": \"Source Code Pro\",\n \"noto-sans-jp\": \"Noto Sans JP\",\n roboto: \"Roboto\",\n \"open-sans\": \"Open Sans\",\n lato: \"Lato\",\n poppins: \"Poppins\",\n};\n\n/**\n * Resolve a font alias to its canonical display name, or undefined if the\n * alias is not in the map.\n */\nexport function resolveAliasDisplayName(alias: string): string | undefined {\n const slug = (FONT_ALIAS_MAP as Record<string, string>)[alias.toLowerCase()];\n if (!slug) return undefined;\n return CANONICAL_FONT_DISPLAY_NAMES[slug];\n}\n","export function decodeUrlPathVariants(path: string): string[] {\n const variants = [path];\n try {\n const decoded = decodeURIComponent(path);\n if (decoded !== path) variants.unshift(decoded);\n } catch {\n // Malformed percent sequences may be literal filesystem names.\n }\n\n return variants;\n}\n","/**\n * Browser-safe parser for the `data-composition-variables` schema attribute.\n * Lives outside htmlParser.ts so browser consumers (SDK, Studio, lint) can\n * import it via `@hyperframes/parsers/composition` without pulling the\n * linkedom/Node HTML-parser machinery from the main entry.\n */\n\nimport type { CompositionVariable, CompositionVariableType } from \"./types.js\";\n\n/**\n * Required typeof for each variable type's `default`. For font the default is\n * the font-family name string; for image it is the fallback URL string —\n * extra metadata fields on both are optional and not validated here.\n */\nconst DEFAULT_TYPEOF: Record<CompositionVariableType, \"string\" | \"number\" | \"boolean\"> = {\n string: \"string\",\n number: \"number\",\n color: \"string\",\n boolean: \"boolean\",\n enum: \"string\",\n font: \"string\",\n image: \"string\",\n};\n\n/**\n * Scalar variable values (string/number/boolean) are the ones that flow into\n * CSS custom props and text bindings; font/image values are object-shaped.\n * Shared so the SDK's CSS-compat writes, the runtime bindings, and Studio's\n * display logic can never disagree on what \"scalar\" means.\n */\nexport function isScalarVariableValue(value: unknown): value is string | number | boolean {\n return typeof value === \"string\" || typeof value === \"number\" || typeof value === \"boolean\";\n}\n\nfunction isRecord(v: unknown): v is Record<string, unknown> {\n return typeof v === \"object\" && v !== null;\n}\n\nfunction isVariableType(t: unknown): t is CompositionVariableType {\n return typeof t === \"string\" && t in DEFAULT_TYPEOF;\n}\n\n/**\n * True when the value is a structurally valid variable declaration: id, label,\n * a known type, a default matching that type, and options[] for enums. The\n * same predicate parseCompositionVariables filters with — exported so writers\n * (SDK declaration ops, Studio forms) can validate before persisting.\n */\nexport function isCompositionVariable(v: unknown): v is CompositionVariable {\n if (!isRecord(v)) return false;\n if (typeof v.id !== \"string\" || typeof v.label !== \"string\") return false;\n if (!isVariableType(v.type)) return false;\n if (typeof v.default !== DEFAULT_TYPEOF[v.type]) return false;\n if (v.type === \"enum\" && !Array.isArray(v.options)) return false;\n return true;\n}\n\n/**\n * Parse the typed variable declarations from an element's\n * `data-composition-variables` attribute. Malformed entries (wrong shape,\n * unknown type, default not matching the declared type) are dropped; an\n * absent attribute, invalid JSON, or a non-array payload yields `[]`.\n */\nexport function parseCompositionVariables(htmlEl: Element): CompositionVariable[] {\n const variablesAttr = htmlEl.getAttribute(\"data-composition-variables\");\n if (!variablesAttr) {\n return [];\n }\n\n try {\n const parsed = JSON.parse(variablesAttr);\n if (!Array.isArray(parsed)) {\n return [];\n }\n return parsed.filter(isCompositionVariable);\n } catch {\n return [];\n }\n}\n","/**\n * Browser-safe static scan for composition-variable reads in script text.\n *\n * Compositions read variables by calling the runtime API — `getVariables()`\n * bare (sub-comp scoped shadow) or via `__hyperframes.getVariables()` /\n * `window.__hyperframes.getVariables()` — and there is no DOM-attribute\n * binding to scan, so \"which variables does this composition use\" can only be\n * derived from the scripts. This is a best-effort static analysis: the\n * patterns agents actually write (destructuring, member access, a single\n * alias variable) resolve to ids; anything opaque flips `scanIncomplete`\n * so consumers can present usage as a lower bound instead of a fact.\n *\n * AST nodes are handled untyped (same convention as gsapParserAcorn.ts) —\n * acorn's structural types don't survive acorn-walk's visitor signatures.\n */\n\nimport * as acorn from \"acorn\";\nimport * as acornWalk from \"acorn-walk\";\n\nexport interface VariableUsageScan {\n /** Variable ids statically read by the script, in first-seen order. */\n usedIds: string[];\n /**\n * True when the script accesses variables in a way the scan cannot resolve\n * (computed keys, rest spreads, the values object escaping into a call…) or\n * when the script fails to parse — usedIds is then a lower bound.\n */\n scanIncomplete: boolean;\n}\n\ninterface Sink {\n use(id: string): void;\n incomplete(): void;\n}\n\n// oxlint-disable no-explicit-any -- untyped acorn AST traversal, see header\n\nfunction isGetVariablesCallee(callee: any): boolean {\n if (callee?.type === \"Identifier\") return callee.name === \"getVariables\";\n if (callee?.type === \"MemberExpression\" && !callee.computed) {\n return callee.property?.type === \"Identifier\" && callee.property.name === \"getVariables\";\n }\n return false;\n}\n\n/** Collect ids from an ObjectPattern destructuring of the values object. */\n// Exhaustive AST-node classification — branchy by nature, same as gsapParserAcorn.\n// fallow-ignore-next-line complexity\nfunction collectFromObjectPattern(pattern: any, out: Sink): void {\n for (const prop of pattern.properties ?? []) {\n if (prop?.type === \"RestElement\") {\n out.incomplete();\n continue;\n }\n if (prop?.type !== \"Property\") continue;\n if (prop.computed === true) {\n out.incomplete();\n } else if (prop.key?.type === \"Identifier\") {\n out.use(String(prop.key.name));\n } else if (prop.key?.type === \"Literal\" && typeof prop.key.value === \"string\") {\n out.use(prop.key.value);\n } else {\n out.incomplete();\n }\n }\n}\n\n/** Collect an id from a MemberExpression reading the values object. */\nfunction collectFromMemberAccess(member: any, out: Sink): void {\n if (member.computed !== true && member.property?.type === \"Identifier\") {\n out.use(String(member.property.name));\n } else if (\n member.computed === true &&\n member.property?.type === \"Literal\" &&\n typeof member.property.value === \"string\"\n ) {\n out.use(member.property.value);\n } else {\n out.incomplete();\n }\n}\n\n/**\n * Classify one read of the values object (a getVariables() call result or an\n * alias holding it) by its immediate syntactic context. Returns the alias\n * name when the value is bound to a plain variable (`const vars = …`).\n */\n// fallow-ignore-next-line complexity\nfunction classifyValueRead(parent: any, valueNode: any, out: Sink): string | null {\n if (!parent || parent.type === \"ExpressionStatement\") {\n // Bare statement — value unused, nothing read.\n return null;\n }\n if (parent.type === \"MemberExpression\" && parent.object === valueNode) {\n collectFromMemberAccess(parent, out);\n return null;\n }\n if (parent.type === \"VariableDeclarator\" && parent.init === valueNode) {\n if (parent.id?.type === \"ObjectPattern\") {\n collectFromObjectPattern(parent.id, out);\n return null;\n }\n if (parent.id?.type === \"Identifier\") return String(parent.id.name);\n out.incomplete();\n return null;\n }\n // The values object escapes (argument, return, spread, assignment…) —\n // reads beyond this point are invisible to the scan.\n out.incomplete();\n return null;\n}\n\nexport function scanVariableUsage(scriptText: string): VariableUsageScan {\n const usedIds: string[] = [];\n const seen = new Set<string>();\n let scanIncomplete = false;\n\n const sink: Sink = {\n use(id: string) {\n if (!seen.has(id)) {\n seen.add(id);\n usedIds.push(id);\n }\n },\n incomplete() {\n scanIncomplete = true;\n },\n };\n\n let ast: any;\n try {\n ast = acorn.parse(scriptText, { ecmaVersion: \"latest\", sourceType: \"script\" });\n } catch {\n return { usedIds: [], scanIncomplete: true };\n }\n\n const aliases = new Set<string>();\n\n // Pass 1: classify every getVariables() call by its parent context.\n acornWalk.ancestor(ast, {\n CallExpression(node: any, _: unknown, ancestors: any[]) {\n if (!isGetVariablesCallee(node.callee)) return;\n const parent = ancestors[ancestors.length - 2];\n const alias = classifyValueRead(parent, node, sink);\n if (alias) aliases.add(alias);\n },\n } as any);\n\n // Pass 2: classify every reference to an alias of the values object.\n // Scope-naive by design: an unrelated same-named identifier can only make\n // the scan report extra ids or flip scanIncomplete, never miss a read.\n if (aliases.size > 0) {\n acornWalk.ancestor(ast, {\n // fallow-ignore-next-line complexity\n Identifier(node: any, _: unknown, ancestors: any[]) {\n if (!aliases.has(String(node.name))) return;\n const parent = ancestors[ancestors.length - 2];\n if (!parent) return;\n // Skip the declarator that introduced the alias and property-position\n // identifiers that merely share the name.\n if (parent.type === \"VariableDeclarator\" && parent.id === node) return;\n if (parent.type === \"MemberExpression\" && parent.property === node) return;\n if (parent.type === \"Property\" && parent.key === node && parent.computed !== true) return;\n // Chained aliases (const v2 = vars) are not followed — flag instead\n // of silently missing reads through the second name.\n if (classifyValueRead(parent, node, sink)) sink.incomplete();\n },\n } as any);\n }\n\n return { usedIds, scanIncomplete };\n}\n","/**\n * Browser-safe authored composition contract.\n *\n * Source HTML is authored with data-start + data-duration + data-track-index.\n * data-end is compiler-derived and data-layer is legacy input only. Readers\n * accept legacy documents; writers always emit the canonical representation.\n */\n\nexport const COMPOSITION_CONTRACT_VERSION = 1 as const;\n\nexport const COMPOSITION_ATTRIBUTES = Object.freeze({\n start: \"data-start\",\n duration: \"data-duration\",\n trackIndex: \"data-track-index\",\n derivedEnd: \"data-end\",\n legacyTrack: \"data-layer\",\n} as const);\n\nexport const CANONICAL_AUTHORED_TIMING_ATTRIBUTES = Object.freeze([\n COMPOSITION_ATTRIBUTES.start,\n COMPOSITION_ATTRIBUTES.duration,\n COMPOSITION_ATTRIBUTES.trackIndex,\n] as const);\n\nexport const DERIVED_TIMING_ATTRIBUTES = Object.freeze([\n COMPOSITION_ATTRIBUTES.derivedEnd,\n] as const);\n\nexport const LEGACY_TIMING_ATTRIBUTES = Object.freeze([\n COMPOSITION_ATTRIBUTES.derivedEnd,\n COMPOSITION_ATTRIBUTES.legacyTrack,\n] as const);\n\nexport type ReferenceExpression =\n | { kind: \"absolute\"; value: number }\n | { kind: \"reference\"; refId: string; offset: number };\n\nexport type ClipTimingDiagnosticCode =\n | \"invalid-start\"\n | \"unresolved-start-reference\"\n | \"invalid-duration\"\n | \"invalid-end\"\n | \"end-before-start\"\n | \"deprecated-end\"\n | \"conflicting-end\"\n | \"invalid-track-index\"\n | \"deprecated-layer\"\n | \"conflicting-layer\";\n\nexport interface ClipTimingDiagnostic {\n code: ClipTimingDiagnosticCode;\n attribute: string;\n value: string | null;\n}\n\nexport interface ClipAttributeReader {\n getAttribute(name: string): string | null;\n}\n\nexport interface ClipAttributeWriter extends ClipAttributeReader {\n setAttribute(name: string, value: string): void;\n removeAttribute(name: string): void;\n}\n\nexport interface ReadClipTimingOptions {\n /** Resolve a reference to the referenced clip's absolute end time. */\n resolveReferenceEnd?: (refId: string) => number | null | undefined;\n /** Start used when data-start is absent. Defaults to zero. */\n defaultStart?: number | null;\n}\n\nexport interface ClipTiming {\n startExpression: ReferenceExpression | null;\n start: number | null;\n duration: number | null;\n end: number | null;\n trackIndex: number;\n durationSource: \"duration\" | \"legacy-end\" | \"missing\" | \"invalid\";\n trackSource: \"track-index\" | \"legacy-layer\" | \"default\" | \"invalid\";\n diagnostics: ClipTimingDiagnostic[];\n}\n\nexport interface ClipTimingUpdate {\n start?: number | string | ReferenceExpression;\n duration?: number | null;\n trackIndex?: number | null;\n}\n\nexport class ClipTimingWriteError extends Error {\n readonly code: \"invalid-start\" | \"invalid-duration\" | \"invalid-track-index\";\n\n constructor(code: ClipTimingWriteError[\"code\"], message: string) {\n super(message);\n this.name = \"ClipTimingWriteError\";\n this.code = code;\n }\n}\n\n/** Parse a value to a finite number, or null if it is absent/invalid. */\nexport function parseNumeric(value: string | null | undefined): number | null {\n if (value == null || value.trim() === \"\") return null;\n const parsed = Number(value);\n return Number.isFinite(parsed) ? parsed : null;\n}\n\nconst REFERENCE_ID_PATTERN = /^[A-Za-z0-9_.:-]+$/;\n\nfunction isAsciiDigitAt(value: string, index: number): boolean {\n const code = value.charCodeAt(index);\n return code >= 48 && code <= 57;\n}\n\nfunction skipDigitsLeft(value: string, start: number): number {\n let cursor = start;\n while (cursor >= 0 && isAsciiDigitAt(value, cursor)) cursor--;\n return cursor;\n}\n\nfunction skipWhitespaceLeft(value: string, start: number): number {\n let cursor = start;\n while (cursor >= 0 && (value[cursor] ?? \"\").trim() === \"\") cursor--;\n return cursor;\n}\n\nfunction findMagnitudeStart(value: string): number | null {\n const last = value.length - 1;\n if (!isAsciiDigitAt(value, last)) return null;\n let cursor = skipDigitsLeft(value, last);\n if (value[cursor] === \".\") cursor = skipDigitsLeft(value, cursor - 1);\n return cursor + 1;\n}\n\nfunction parseReferenceOffset(\n value: string,\n): { refId: string; operator: \"+\" | \"-\"; magnitude: number } | null {\n const magnitudeStart = findMagnitudeStart(value);\n if (magnitudeStart == null) return null;\n const operatorIndex = skipWhitespaceLeft(value, magnitudeStart - 1);\n const operator = value[operatorIndex];\n if (operator !== \"+\" && operator !== \"-\") return null;\n\n const refId = value.slice(0, operatorIndex).trim();\n if (!REFERENCE_ID_PATTERN.test(refId)) return null;\n const magnitude = Number(value.slice(magnitudeStart));\n if (!Number.isFinite(magnitude)) return null;\n return { refId, operator, magnitude };\n}\n\n/**\n * Parse the data-start grammar: absolute seconds, `clip-id`, or\n * `clip-id +/- offset`, where references resolve to the referenced clip's end.\n */\nexport function parseStartExpression(raw: string | null | undefined): ReferenceExpression | null {\n const normalized = (raw ?? \"\").trim();\n if (!normalized) return null;\n const absolute = parseNumeric(normalized);\n if (absolute != null) return { kind: \"absolute\", value: absolute };\n if (REFERENCE_ID_PATTERN.test(normalized)) {\n return { kind: \"reference\", refId: normalized, offset: 0 };\n }\n const reference = parseReferenceOffset(normalized);\n if (!reference) return null;\n return {\n kind: \"reference\",\n refId: reference.refId,\n offset: reference.operator === \"-\" ? -reference.magnitude : reference.magnitude,\n };\n}\n\nfunction pushDiagnostic(\n diagnostics: ClipTimingDiagnostic[],\n code: ClipTimingDiagnosticCode,\n attribute: string,\n value: string | null,\n): void {\n diagnostics.push({ code, attribute, value });\n}\n\nfunction resolveStart(\n expression: ReferenceExpression | null,\n rawStart: string | null,\n options: ReadClipTimingOptions,\n diagnostics: ClipTimingDiagnostic[],\n): number | null {\n if (rawStart == null || rawStart.trim() === \"\") {\n return options.defaultStart === undefined ? 0 : options.defaultStart;\n }\n if (!expression) {\n pushDiagnostic(diagnostics, \"invalid-start\", COMPOSITION_ATTRIBUTES.start, rawStart);\n return null;\n }\n if (expression.kind === \"absolute\") return Math.max(0, expression.value);\n const referencedEnd = options.resolveReferenceEnd?.(expression.refId);\n if (referencedEnd == null || !Number.isFinite(referencedEnd)) {\n pushDiagnostic(\n diagnostics,\n \"unresolved-start-reference\",\n COMPOSITION_ATTRIBUTES.start,\n rawStart,\n );\n return null;\n }\n return Math.max(0, referencedEnd + expression.offset);\n}\n\ntype DurationRead = Pick<ClipTiming, \"duration\" | \"end\" | \"durationSource\">;\ntype TrackRead = Pick<ClipTiming, \"trackIndex\" | \"trackSource\">;\n\n// Float pairs like `data-start=\"0.1\" + data-duration=\"0.2\"` add to\n// `0.30000000000000004`, so the compiler-emitted `data-end` string can be a few\n// ulps off the sum a reader computes from the canonical inputs. Any drift below\n// this ceiling is treated as equal for the derived-end reconciliation check —\n// well below one 60fps frame (~16.67 ms) and orders of magnitude above the\n// worst realistic IEEE-754 residual (~2e-16 s).\nconst DERIVED_END_EQUALITY_EPSILON_SECONDS = 1e-9;\n\nfunction derivedEndsAreConsistent(parsedEnd: number, canonicalEnd: number): boolean {\n return Math.abs(parsedEnd - canonicalEnd) <= DERIVED_END_EQUALITY_EPSILON_SECONDS;\n}\n\n// Reconcile a `data-end` attribute paired with canonical `data-duration`.\n//\n// The compiler (`compileTimingAttrs` in @hyperframes/core) writes\n// `data-end = data-start + data-duration` into the bundled HTML so the runtime\n// can key off a single attribute. That derived attribute is legitimate — it is\n// not a legacy authoring shape. Treating it as `deprecated-end` here made the\n// linter fire `deprecated_data_end` on the compiler's own output whenever\n// StaticGuard re-validated a bundled composition, producing a false-positive\n// cluster reported in the field (n=25+ across cli-feedback crons 61-68).\n//\n// We keep `deprecated-end` for the truly legacy shape (see `readLegacyEnd` —\n// `data-end` present without `data-duration`) and for the *conflicting* case\n// where an author left a stale `data-end` behind a canonical duration edit; a\n// stale end must not be silently overridden without a diagnostic.\nfunction diagnoseDerivedEnd(\n rawEnd: string,\n canonicalEnd: number | null,\n diagnostics: ClipTimingDiagnostic[],\n): void {\n const parsedEnd = parseNumeric(rawEnd);\n if (parsedEnd == null) {\n pushDiagnostic(diagnostics, \"deprecated-end\", COMPOSITION_ATTRIBUTES.derivedEnd, rawEnd);\n pushDiagnostic(diagnostics, \"invalid-end\", COMPOSITION_ATTRIBUTES.derivedEnd, rawEnd);\n return;\n }\n if (canonicalEnd == null) {\n // Canonical duration exists but resolved end is unknown (e.g. unresolved\n // reference start). Preserve prior behavior — flag as deprecated so authors\n // remove the stale companion attribute.\n pushDiagnostic(diagnostics, \"deprecated-end\", COMPOSITION_ATTRIBUTES.derivedEnd, rawEnd);\n return;\n }\n if (derivedEndsAreConsistent(parsedEnd, canonicalEnd)) {\n // Compiler-derived (or manually consistent) `data-end` alongside\n // `data-duration` — silent. No diagnostic.\n return;\n }\n pushDiagnostic(diagnostics, \"deprecated-end\", COMPOSITION_ATTRIBUTES.derivedEnd, rawEnd);\n pushDiagnostic(diagnostics, \"conflicting-end\", COMPOSITION_ATTRIBUTES.derivedEnd, rawEnd);\n}\n\nfunction readCanonicalDuration(\n rawDuration: string,\n start: number | null,\n diagnostics: ClipTimingDiagnostic[],\n): DurationRead {\n const duration = parseNumeric(rawDuration);\n if (duration == null || duration < 0) {\n pushDiagnostic(diagnostics, \"invalid-duration\", COMPOSITION_ATTRIBUTES.duration, rawDuration);\n return { duration: null, end: null, durationSource: \"invalid\" };\n }\n return {\n duration,\n end: start == null ? null : start + duration,\n durationSource: \"duration\",\n };\n}\n\nfunction readLegacyEnd(\n rawEnd: string,\n start: number | null,\n diagnostics: ClipTimingDiagnostic[],\n): DurationRead {\n pushDiagnostic(diagnostics, \"deprecated-end\", COMPOSITION_ATTRIBUTES.derivedEnd, rawEnd);\n const end = parseNumeric(rawEnd);\n if (end == null) {\n pushDiagnostic(diagnostics, \"invalid-end\", COMPOSITION_ATTRIBUTES.derivedEnd, rawEnd);\n return { duration: null, end: null, durationSource: \"invalid\" };\n }\n if (start == null) return { duration: null, end, durationSource: \"legacy-end\" };\n if (end < start) {\n pushDiagnostic(diagnostics, \"end-before-start\", COMPOSITION_ATTRIBUTES.derivedEnd, rawEnd);\n return { duration: null, end: null, durationSource: \"invalid\" };\n }\n return { duration: end - start, end, durationSource: \"legacy-end\" };\n}\n\nfunction readDuration(\n attributes: ClipAttributeReader,\n start: number | null,\n diagnostics: ClipTimingDiagnostic[],\n): DurationRead {\n const rawDuration = attributes.getAttribute(COMPOSITION_ATTRIBUTES.duration);\n const rawEnd = attributes.getAttribute(COMPOSITION_ATTRIBUTES.derivedEnd);\n if (rawDuration == null) {\n return rawEnd == null\n ? { duration: null, end: null, durationSource: \"missing\" }\n : readLegacyEnd(rawEnd, start, diagnostics);\n }\n const canonical = readCanonicalDuration(rawDuration, start, diagnostics);\n if (rawEnd != null) diagnoseDerivedEnd(rawEnd, canonical.end, diagnostics);\n return canonical;\n}\n\nfunction readTrackValue(\n rawValue: string,\n attribute: string,\n source: \"track-index\" | \"legacy-layer\",\n diagnostics: ClipTimingDiagnostic[],\n): TrackRead {\n const trackIndex = parseNumeric(rawValue);\n if (trackIndex == null || !Number.isInteger(trackIndex)) {\n pushDiagnostic(diagnostics, \"invalid-track-index\", attribute, rawValue);\n return { trackIndex: 0, trackSource: \"invalid\" };\n }\n return { trackIndex, trackSource: source };\n}\n\nfunction readTrack(\n attributes: ClipAttributeReader,\n diagnostics: ClipTimingDiagnostic[],\n): TrackRead {\n const rawTrack = attributes.getAttribute(COMPOSITION_ATTRIBUTES.trackIndex);\n const rawLayer = attributes.getAttribute(COMPOSITION_ATTRIBUTES.legacyTrack);\n if (rawTrack == null && rawLayer == null) return { trackIndex: 0, trackSource: \"default\" };\n if (rawTrack == null && rawLayer != null) {\n pushDiagnostic(diagnostics, \"deprecated-layer\", COMPOSITION_ATTRIBUTES.legacyTrack, rawLayer);\n return readTrackValue(\n rawLayer,\n COMPOSITION_ATTRIBUTES.legacyTrack,\n \"legacy-layer\",\n diagnostics,\n );\n }\n const canonical = readTrackValue(\n rawTrack ?? \"\",\n COMPOSITION_ATTRIBUTES.trackIndex,\n \"track-index\",\n diagnostics,\n );\n if (rawLayer == null) return canonical;\n pushDiagnostic(diagnostics, \"deprecated-layer\", COMPOSITION_ATTRIBUTES.legacyTrack, rawLayer);\n const parsedLayer = parseNumeric(rawLayer);\n if (parsedLayer != null && parsedLayer !== canonical.trackIndex) {\n pushDiagnostic(diagnostics, \"conflicting-layer\", COMPOSITION_ATTRIBUTES.legacyTrack, rawLayer);\n }\n return canonical;\n}\n\n/** Read canonical timing, accepting legacy attributes without preferring them. */\nexport function readClipTiming(\n attributes: ClipAttributeReader,\n options: ReadClipTimingOptions = {},\n): ClipTiming {\n const diagnostics: ClipTimingDiagnostic[] = [];\n const rawStart = attributes.getAttribute(COMPOSITION_ATTRIBUTES.start);\n const startExpression = parseStartExpression(rawStart);\n const start = resolveStart(startExpression, rawStart, options, diagnostics);\n const duration = readDuration(attributes, start, diagnostics);\n const track = readTrack(attributes, diagnostics);\n return { startExpression, start, ...duration, ...track, diagnostics };\n}\n\nfunction serializeStartNumber(start: number): string {\n if (!Number.isFinite(start)) {\n throw new ClipTimingWriteError(\"invalid-start\", \"start must be finite\");\n }\n return String(start);\n}\n\nfunction serializeStartReference(start: ReferenceExpression): string {\n if (start.kind === \"absolute\") return serializeStartNumber(start.value);\n if (!start.refId || !Number.isFinite(start.offset)) {\n throw new ClipTimingWriteError(\n \"invalid-start\",\n \"reference start must have an id and finite offset\",\n );\n }\n if (start.offset === 0) return start.refId;\n return `${start.refId} ${start.offset < 0 ? \"-\" : \"+\"} ${Math.abs(start.offset)}`;\n}\n\nfunction serializeStart(start: ClipTimingUpdate[\"start\"]): string {\n if (typeof start === \"number\") return serializeStartNumber(start);\n if (typeof start === \"object\" && start != null) return serializeStartReference(start);\n if (typeof start === \"string\" && parseStartExpression(start)) return start.trim();\n throw new ClipTimingWriteError(\"invalid-start\", `invalid start expression: ${start ?? \"\"}`);\n}\n\nfunction writeDuration(\n attributes: ClipAttributeWriter,\n duration: number | null | undefined,\n current: ClipTiming,\n): void {\n if (duration === null) {\n attributes.removeAttribute(COMPOSITION_ATTRIBUTES.duration);\n return;\n }\n if (duration === undefined) {\n if (\n attributes.getAttribute(COMPOSITION_ATTRIBUTES.duration) == null &&\n current.duration != null\n ) {\n attributes.setAttribute(COMPOSITION_ATTRIBUTES.duration, String(current.duration));\n }\n return;\n }\n if (!Number.isFinite(duration) || duration < 0) {\n throw new ClipTimingWriteError(\n \"invalid-duration\",\n \"duration must be a finite, non-negative number\",\n );\n }\n attributes.setAttribute(COMPOSITION_ATTRIBUTES.duration, String(duration));\n}\n\nfunction writeTrack(\n attributes: ClipAttributeWriter,\n trackIndex: number | null | undefined,\n current: ClipTiming,\n): void {\n if (trackIndex === null) {\n attributes.removeAttribute(COMPOSITION_ATTRIBUTES.trackIndex);\n return;\n }\n if (trackIndex === undefined) {\n if (\n attributes.getAttribute(COMPOSITION_ATTRIBUTES.trackIndex) == null &&\n current.trackSource === \"legacy-layer\"\n ) {\n attributes.setAttribute(COMPOSITION_ATTRIBUTES.trackIndex, String(current.trackIndex));\n }\n return;\n }\n if (!Number.isFinite(trackIndex) || !Number.isInteger(trackIndex)) {\n throw new ClipTimingWriteError(\"invalid-track-index\", \"trackIndex must be a finite integer\");\n }\n attributes.setAttribute(COMPOSITION_ATTRIBUTES.trackIndex, String(trackIndex));\n}\n\n/** Write only canonical authored timing and remove derived/legacy source attributes. */\nexport function writeClipTiming(\n attributes: ClipAttributeWriter,\n update: ClipTimingUpdate,\n): ClipTiming {\n const current = readClipTiming(attributes);\n if (update.start !== undefined) {\n attributes.setAttribute(COMPOSITION_ATTRIBUTES.start, serializeStart(update.start));\n }\n writeDuration(attributes, update.duration, current);\n writeTrack(attributes, update.trackIndex, current);\n // A legacy end paired with an unresolved reference start cannot be converted\n // to data-duration without a reference resolver. On a duration-omitting edit\n // (for example, moving only the track), preserve that sole duration source\n // instead of canonicalizing it into data loss.\n const preserveUnresolvedLegacyEnd =\n update.duration === undefined &&\n attributes.getAttribute(COMPOSITION_ATTRIBUTES.duration) == null &&\n attributes.getAttribute(COMPOSITION_ATTRIBUTES.derivedEnd) != null &&\n current.duration == null;\n if (!preserveUnresolvedLegacyEnd) {\n attributes.removeAttribute(COMPOSITION_ATTRIBUTES.derivedEnd);\n }\n attributes.removeAttribute(COMPOSITION_ATTRIBUTES.legacyTrack);\n return readClipTiming(attributes);\n}\n"],"mappings":"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","names":[]}
|