@hyperframes/studio-server 0.8.75 → 0.8.76

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.
@@ -1,6 +1,7 @@
1
1
  import * as _hyperframes_core from '@hyperframes/core';
2
2
  import { RegistryItem } from '@hyperframes/core';
3
3
  import { CanvasResolution } from '@hyperframes/parsers';
4
+ import { BundleOptions } from '@hyperframes/core/compiler';
4
5
  import { MediaCodecProbeCache, HtmlSourceLike } from './helpers/mediaCodecMap.js';
5
6
 
6
7
  interface HistoryWho {
@@ -29,6 +30,8 @@ interface HistoryEntry {
29
30
  /** The point before the first kept entry. */
30
31
  declare const START = "start";
31
32
 
33
+ /** Where hosts keep project histories unless told otherwise: outside every project, so no tidy-up takes one away. */
34
+ declare const DEFAULT_HISTORY_ROOT: string;
32
35
  interface ProjectHistoryOptions {
33
36
  projectDir: string;
34
37
  /** Kept outside the project, so nothing tidying the project can take its history with it. */
@@ -43,6 +46,15 @@ interface ProjectHistoryOptions {
43
46
  /** A sweep or commit that a watcher or timer started failed. */
44
47
  onError?: (error: unknown) => void;
45
48
  }
49
+ interface ClaimOptions {
50
+ coalesceKey?: string;
51
+ idleMs?: number;
52
+ /** Per path, the version (fileContentVersion) the claimer overwrote: earlier changes stay their writer's. */
53
+ overwrote?: Readonly<Record<string, string>>;
54
+ }
55
+ interface Writing {
56
+ writeToken?: string;
57
+ }
46
58
  interface HistoryListItem extends HistoryEntry {
47
59
  pinned: boolean;
48
60
  undone: boolean;
@@ -65,37 +77,33 @@ interface HistoryWindow {
65
77
  }
66
78
  interface ProjectHistory {
67
79
  readonly projectId: string;
68
- /**
69
- * Writes until close() are this writer's, as one entry whose id is the window's. A window with no write for
70
- * `idleMs` (default maxGroupMs) ends by itself. ponytail: overlapping windows give a write to the newest.
71
- */
80
+ /** Writes until close() are one entry with the window's id; a claim cutting a file out commits that file's part
81
+ * first (fresh id), returned by close() if nothing else remained. Idle windows end; overlaps go to the newest. */
72
82
  beginWindow(who: HistoryWho, label: string, options?: {
73
83
  idleMs?: number;
74
84
  }): Promise<HistoryWindow>;
75
85
  /**
76
- * For a writer that records after writing (Studio): takes in every write so far and moves the not yet committed
77
- * outside changes to `paths` into one entry of `who`'s. Claims with the same coalesceKey merge until a claim with
78
- * another key, `idleMs` (default quietMs) without one, or any operation. Null when nothing was claimed, or
79
- * when a coalescing claim's writes net to nothing (a drag back to where it started).
86
+ * For a writer that records after writing (Studio): moves the uncommitted changes to `paths` into one entry of
87
+ * `who`'s, each cut at the `overwrote` version so earlier parts stay their writer's. Same-key claims merge until
88
+ * another key, `idleMs` idle, or any operation. Null when nothing was claimed or a drag nets to nothing.
80
89
  */
81
- claim(who: HistoryWho, label: string, paths: readonly string[], options?: {
82
- coalesceKey?: string;
83
- idleMs?: number;
84
- }): Promise<{
90
+ claim(who: HistoryWho, label: string, paths: readonly string[], options?: ClaimOptions): Promise<{
85
91
  id: string;
86
92
  } | null>;
87
93
  /** A watcher saw `path` change (project-relative or absolute). */
88
94
  noteChange(path: string): void;
89
95
  list(): HistoryListItem[];
90
- /** Cmd+Z (back) and Cmd+Shift+Z (forward), whoever made the change. */
91
- step(direction: "back" | "forward", who: HistoryWho): Promise<HistoryResult>;
96
+ /** Cmd+Z (back) and Cmd+Shift+Z (forward), whoever made the change; `writeToken` labels the writes' echo. */
97
+ step(direction: "back" | "forward", who: HistoryWho, options?: Writing): Promise<HistoryResult>;
98
+ /** The entry the next step reverts, as of the last scan (a step scans first). */
99
+ next(direction: "back" | "forward"): HistoryEntry | undefined;
92
100
  /** A conflict (a file changed since) returns the choice; pass `mode` to take one. */
93
101
  undo(id: string, options: {
94
102
  who: HistoryWho;
95
103
  mode?: "just-this" | "back-to-before";
96
- }): Promise<HistoryResult>;
104
+ } & Writing): Promise<HistoryResult>;
97
105
  /** Makes the files equal what they were right after `point` (an entry id, or START). */
98
- restore(point: string, who: HistoryWho): Promise<HistoryEntry | null>;
106
+ restore(point: string, who: HistoryWho, options?: Writing): Promise<HistoryEntry | null>;
99
107
  /** The files at `point` without writing anything: path to hash, read through readBlob. */
100
108
  peek(point: string): Record<string, string> | null;
101
109
  readBlob(hash: string): Promise<Buffer>;
@@ -216,8 +224,8 @@ interface StudioApiAdapter {
216
224
  * the history routes answer 404.
217
225
  */
218
226
  history?: (project: ResolvedProject) => Promise<ProjectHistory | null> | ProjectHistory | null;
219
- /** Bundle a project directory into a single HTML string. Returns null if unavailable. */
220
- bundle(projectDir: string): Promise<string | null>;
227
+ /** Bundle a project directory into a single HTML string, forwarding `options` over the host's own. */
228
+ bundle(projectDir: string, options?: Pick<BundleOptions, "stampHfIds">): Promise<string | null>;
221
229
  /** Optional: cached signature for project files that should invalidate preview frame caches. */
222
230
  getProjectSignature?: (projectDir: string) => string;
223
231
  /** Lint a single HTML string. */
@@ -351,7 +359,13 @@ interface StudioApiAdapter {
351
359
  type PreviewApiAdapter = StudioApiAdapter & {
352
360
  autoProxy?: boolean;
353
361
  mediaCodecProbeCache?: MediaCodecProbeCache;
362
+ /** Keeps built preview documents across restarts, keyed by project id and preview ETag. */
363
+ previewDocuments?: PreviewDocumentStore;
354
364
  };
365
+ interface PreviewDocumentStore {
366
+ read(key: string): string | null;
367
+ write(key: string, html: string): void;
368
+ }
355
369
  declare function isAutoProxyEnabled(adapter: PreviewApiAdapter): boolean;
356
370
  /** One probe cache per server instance — construct once in `registerPreviewRoutes`
357
371
  * and reuse across every request so the mtime-cache benefit in
@@ -393,4 +407,4 @@ declare function injectMediaCodecMapIntoHtml(html: string, projectDir: string, h
393
407
  */
394
408
  declare function injectMediaCodecMap(html: string, adapter: PreviewApiAdapter, projectDir: string, compSrcPath: string, probeCache: MediaCodecProbeCache): Promise<string>;
395
409
 
396
- export { type HistoryEntry as H, type LintResult as L, type MediaProcessingJobState as M, type PreviewApiAdapter as P, type RenderJobState as R, type StudioApiAdapter as S, START as a, type HistoryFileChange as b, type HistoryListItem as c, type HistoryResult as d, type HistoryWho as e, type HistoryWindow as f, type ProjectHistory as g, type ProjectHistoryOptions as h, type ResolvedProject as i, type StudioSelectionResponse as j, type StudioSelectionSnapshot as k, type StudioSelectionTextField as l, injectMediaCodecMap as m, injectMediaCodecMapIntoHtml as n, openProjectHistory as o, isAutoProxyEnabled as p, proxyEtagSalt as q, resolvePreviewMediaCodecProbeCache as r };
410
+ export { DEFAULT_HISTORY_ROOT as D, type HistoryEntry as H, type LintResult as L, type MediaProcessingJobState as M, type PreviewDocumentStore as P, type RenderJobState as R, type StudioApiAdapter as S, START as a, type HistoryFileChange as b, type HistoryListItem as c, type HistoryResult as d, type HistoryWho as e, type HistoryWindow as f, type PreviewApiAdapter as g, type ProjectHistory as h, type ProjectHistoryOptions as i, type ResolvedProject as j, type StudioSelectionResponse as k, type StudioSelectionSnapshot as l, type StudioSelectionTextField as m, injectMediaCodecMap as n, openProjectHistory as o, injectMediaCodecMapIntoHtml as p, isAutoProxyEnabled as q, proxyEtagSalt as r, resolvePreviewMediaCodecProbeCache as s };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperframes/studio-server",
3
- "version": "0.8.75",
3
+ "version": "0.8.76",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/heygen-com/hyperframes",
@@ -64,8 +64,8 @@
64
64
  "linkedom": "^0.18.12",
65
65
  "postcss": "^8.5.8",
66
66
  "postcss-selector-parser": "^7.1.2",
67
- "@hyperframes/core": "0.8.75",
68
- "@hyperframes/parsers": "0.8.75"
67
+ "@hyperframes/parsers": "0.8.76",
68
+ "@hyperframes/core": "0.8.76"
69
69
  },
70
70
  "devDependencies": {
71
71
  "@types/node": "^25.0.10",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/helpers/sourceMutation.ts","../src/helpers/sourceStyleMutation.ts"],"sourcesContent":["import { parseHTML } from \"linkedom\";\nimport { removeElementWithGsapCascade } from \"@hyperframes/parsers\";\nimport postcss from \"postcss\";\nimport selectorParser from \"postcss-selector-parser\";\nimport { isAllowedHtmlAttribute, isSafeAttributeValue } from \"@hyperframes/core/html-attr-safety\";\nimport { sanitizeRichTextChildren } from \"@hyperframes/core/rich-text-sanitize\";\nimport {\n EXCLUDED_TAGS,\n ensureHfIds,\n mintHfId,\n walkCompositionDescendants,\n} from \"@hyperframes/parsers/hf-ids\";\nimport { readClipTiming, writeClipTiming } from \"@hyperframes/core/composition-contract\";\nimport { parseStyleDecls, patchStyleAttrString } from \"./sourceStyleMutation.js\";\n\nexport interface SourceMutationTarget {\n id?: string | null;\n hfId?: string;\n selector?: string;\n selectorIndex?: number;\n}\n\nexport function parseSourceDocument(source: string): {\n document: Document;\n wrappedFragment: boolean;\n} {\n const hasDocumentShell = /<!doctype|<html[\\s>]/i.test(source);\n if (hasDocumentShell) {\n return { document: parseHTML(source).document, wrappedFragment: false };\n }\n return {\n document: parseHTML(`<!DOCTYPE html><html><head></head><body>${source}</body></html>`).document,\n wrappedFragment: true,\n };\n}\n\nfunction duplicateCssRulesForId(document: Document, originalId: string, newId: string): void {\n const idToken = `#${originalId}`;\n const transform = selectorParser((selectors) => {\n selectors.walkIds((node) => {\n if (node.value === originalId) node.value = newId;\n });\n });\n for (const styleEl of document.querySelectorAll(\"style\")) {\n const css = styleEl.textContent ?? \"\";\n let root: postcss.Root;\n try {\n root = postcss.parse(css);\n } catch {\n continue;\n }\n const clones: postcss.Rule[] = [];\n root.walkRules((rule) => {\n if (!rule.selector.includes(idToken)) return;\n const newSelector = transform.processSync(rule.selector);\n if (newSelector === rule.selector) return;\n const clone = rule.clone({ selector: newSelector });\n clones.push(clone);\n });\n if (clones.length > 0) {\n for (const c of clones) root.append(c);\n styleEl.textContent = root.toString();\n }\n }\n}\n\nfunction querySelectorAllWithTemplates(root: Document | Element, selector: string): Element[] {\n const matches = Array.from(root.querySelectorAll(selector));\n if (matches.length > 0) return matches;\n // querySelectorAll doesn't traverse <template> content in linkedom.\n // Search directly on each template element (NOT .content — removing from\n // .content's DocumentFragment doesn't update the serialized output).\n // Recurse so NESTED templates resolve too — ensureHfIds and the SDK's\n // querySelectorAllDeep descend nested composition templates, so ids exist at\n // any template depth; a one-level search here would silently no-op\n // server-side ops on those ids while the SDK resolves them.\n const templates = Array.from(root.querySelectorAll(\"template\"));\n for (const tmpl of templates) {\n const inner = querySelectorAllWithTemplates(tmpl, selector);\n if (inner.length > 0) return inner;\n }\n return [];\n}\n\n// Prevent CSS attribute-selector injection via a crafted hfId: escape\n// backslashes first, then double-quotes. Keeps a malformed/hostile value from\n// breaking out of the `[data-hf-id=\"…\"]` selector once callers beyond the\n// internal mint contract (R2+ user flows) pass values here.\nfunction escapeCssAttrValue(value: string): string {\n return value.replace(/\\\\/g, \"\\\\\\\\\").replace(/\"/g, '\\\\\"');\n}\n\nfunction findByHfId(document: Document, hfId: string): Element | null {\n try {\n const matches = querySelectorAllWithTemplates(\n document,\n `[data-hf-id=\"${escapeCssAttrValue(hfId)}\"]`,\n );\n if (matches.length > 1) {\n // The mint contract guarantees uniqueness; a duplicate means upstream\n // id drift. Don't silently patch an arbitrary one — surface it.\n // eslint-disable-next-line no-console\n console.warn(\n `sourceMutation: data-hf-id \"${hfId}\" matched ${matches.length} elements; using the first. ids must be unique per document.`,\n );\n }\n return matches[0] ?? null;\n } catch {\n // Malformed selector despite escaping — let the caller fall back.\n return null;\n }\n}\n\nexport function findTargetElement(\n document: Document,\n target: SourceMutationTarget,\n): Element | null {\n if (target.hfId) {\n const el = findByHfId(document, target.hfId);\n if (el) return el;\n }\n\n if (target.id) {\n const byId = document.getElementById(target.id);\n if (byId) return byId;\n }\n\n if (!target.selector) return null;\n try {\n const matches = querySelectorAllWithTemplates(document, target.selector);\n return matches[target.selectorIndex ?? 0] ?? null;\n } catch {\n return null;\n }\n}\n\nexport function removeElementFromHtml(source: string, target: SourceMutationTarget): string {\n const { document, wrappedFragment } = parseSourceDocument(source);\n const element = findTargetElement(document, target);\n if (!element) return source;\n\n removeElementWithGsapCascade(document, element);\n return wrappedFragment ? document.body.innerHTML || \"\" : document.toString();\n}\n\nexport function isHTMLElement(el: Node): el is HTMLElement {\n const HTMLEl = el.ownerDocument?.defaultView?.HTMLElement;\n return HTMLEl ? el instanceof HTMLEl : el.nodeType === 1 && \"style\" in el;\n}\n\nexport function dedupeClonedCompositionId(document: Document, clone: Element): void {\n const compositionId = clone.getAttribute(\"data-composition-id\");\n if (!compositionId) return;\n const usedCompositionIds = new Set(\n querySelectorAllWithTemplates(document, \"[data-composition-id]\").map((node) =>\n node.getAttribute(\"data-composition-id\"),\n ),\n );\n const base = `${compositionId}-split`;\n let nextCompositionId = base;\n let suffix = 2;\n while (usedCompositionIds.has(nextCompositionId)) nextCompositionId = `${base}-${suffix++}`;\n clone.setAttribute(\"data-composition-id\", nextCompositionId);\n}\n\nexport interface PatchOperation {\n type: \"inline-style\" | \"attribute\" | \"html-attribute\" | \"text-content\" | \"rich-text\";\n property: string;\n value: string | null;\n childSelector?: string;\n childIndex?: number;\n}\n\ninterface ResolvedPatchOperation {\n op: PatchOperation;\n target: HTMLElement;\n}\n\nfunction resolveOperationTarget(parent: HTMLElement, op: PatchOperation): HTMLElement | null {\n if (op.childSelector === undefined) return parent;\n try {\n const child = parent.querySelectorAll(op.childSelector)[op.childIndex ?? 0] ?? null;\n return child && isHTMLElement(child) ? child : null;\n } catch {\n return null;\n }\n}\n\n/**\n * Give the elements a rich-text patch just introduced their stable ids, here,\n * in the bytes about to be written and handed back.\n *\n * Otherwise the next preview request mints them and writes the file a second\n * time, after Studio has already recorded the edit in its history. The recorded\n * \"after\" stops matching disk, the content check refuses, and undo reports the\n * file as changed outside Studio — for every colour applied to a run of\n * characters and every text layer added. The clip split stamps its own clone\n * for exactly this reason.\n *\n * Minted one element at a time with the same function `ensureHfIds` uses, so\n * these ids are the ones the next pass would have assigned. Not `ensureHfIds`\n * itself: it takes a whole document, and handing it this element's markup would\n * put the markup back as one.\n */\nfunction stampNewChildIds(parent: Element): void {\n const assigned = new Set<string>();\n const root = parent.ownerDocument?.body ?? parent;\n walkCompositionDescendants(root, (el) => {\n const id = el.getAttribute(\"data-hf-id\");\n if (id) assigned.add(id);\n });\n for (const el of parent.querySelectorAll(\"*\")) {\n if (el.getAttribute(\"data-hf-id\")) continue;\n if (EXCLUDED_TAGS.has(el.tagName.toLowerCase())) continue;\n el.setAttribute(\"data-hf-id\", mintHfId(el, assigned));\n }\n}\n\n// fallow-ignore-next-line complexity\nexport function patchElementInHtml(\n source: string,\n target: SourceMutationTarget,\n operations: PatchOperation[],\n): { html: string; matched: boolean } {\n const { document, wrappedFragment } = parseSourceDocument(source);\n const el = findTargetElement(document, target);\n if (!el || !isHTMLElement(el)) return { html: source, matched: false };\n const htmlEl = el;\n const originalHtml = wrappedFragment ? document.body.innerHTML || \"\" : document.toString();\n\n const resolved: ResolvedPatchOperation[] = [];\n for (const op of operations) {\n const opTarget = resolveOperationTarget(htmlEl, op);\n if (!opTarget) return { html: source, matched: false };\n resolved.push({ op, target: opTarget });\n }\n\n for (const { op, target: opTarget } of resolved) {\n switch (op.type) {\n case \"inline-style\":\n // linkedom's CSSStyleDeclaration does not support CSS custom properties\n // (--foo) or newer individual transform properties (translate, rotate,\n // scale) via style.setProperty(). Manipulate the style attribute string\n // directly so all property names survive the round-trip.\n {\n const raw = opTarget.getAttribute(\"style\") ?? \"\";\n const patched = patchStyleAttrString(raw, op.property, op.value);\n opTarget.setAttribute(\"style\", patched);\n }\n break;\n case \"attribute\":\n {\n const fullAttr = op.property.startsWith(\"data-\") ? op.property : `data-${op.property}`;\n if (op.value != null) {\n opTarget.setAttribute(fullAttr, op.value);\n } else {\n opTarget.removeAttribute(fullAttr);\n }\n }\n break;\n case \"html-attribute\":\n if (!isAllowedHtmlAttribute(op.property)) break;\n if (op.value != null) {\n if (!isSafeAttributeValue(op.property, op.value)) break;\n opTarget.setAttribute(op.property, op.value);\n } else {\n opTarget.removeAttribute(op.property);\n }\n break;\n case \"text-content\":\n if (op.value != null) {\n const inner = opTarget.children.length === 1 ? opTarget.firstElementChild : null;\n const textTarget = inner && isHTMLElement(inner) ? inner : opTarget;\n textTarget.textContent = op.value;\n }\n break;\n // The one operation that can write markup, so the one that has to check\n // it. Assigned first and sanitised after, rather than sanitising a\n // string: parsing is what turns a payload into the tree the allowlist\n // can actually judge, and linkedom never runs anything it parses.\n case \"rich-text\":\n if (op.value != null) {\n opTarget.innerHTML = op.value;\n sanitizeRichTextChildren(opTarget);\n stampNewChildIds(opTarget);\n }\n break;\n }\n }\n\n const html = wrappedFragment ? document.body.innerHTML || \"\" : document.toString();\n if (html === originalHtml) return { html: source, matched: true };\n return { html: ensureHfIds(html), matched: true };\n}\n\nexport function probeElementInSource(source: string, target: SourceMutationTarget): boolean {\n if (!target.id && !target.hfId && !target.selector) return false;\n const { document } = parseSourceDocument(source);\n const el = findTargetElement(document, target);\n return el != null && isHTMLElement(el);\n}\n\nexport interface SplitElementResult {\n html: string;\n matched: boolean;\n newId: string | null;\n}\n\nfunction resolveElementTiming(el: Element): {\n start: number;\n duration: number;\n} {\n const timing = readClipTiming(el);\n return { start: timing.start ?? 0, duration: timing.duration ?? 0 };\n}\n\nfunction setElementDuration(\n el: Element,\n start: number,\n duration: number,\n trackIndex?: number,\n): void {\n writeClipTiming(el, {\n start: Math.round(start * 1000) / 1000,\n duration: Math.round(duration * 1000) / 1000,\n ...(trackIndex != null ? { trackIndex } : {}),\n });\n}\n\n// fallow-ignore-next-line complexity\nexport function splitElementInHtml(\n source: string,\n target: SourceMutationTarget,\n splitTime: number,\n newId: string,\n fallbackTiming?: {\n start: number;\n duration: number;\n playbackStart?: number;\n playbackRate?: number;\n stampPlaybackStart?: boolean;\n // The element's current resolved track (authored, or the runtime's\n // positional-index fallback when unauthored). Stamped onto both halves so\n // inserting the clone can't shift either one to a different row — see\n // parseAuthoredTrack's fallback in core/runtime/timeline.ts.\n track?: number;\n },\n): SplitElementResult {\n const { document, wrappedFragment } = parseSourceDocument(source);\n const el = findTargetElement(document, target);\n if (!el || !isHTMLElement(el)) return { html: source, matched: false, newId: null };\n\n const timing = resolveElementTiming(el);\n let { start, duration } = timing;\n // GSAP-animated elements carry their timing in the script, not in data-* attrs,\n // so the source has no authored duration. Fall back to the store's (GSAP-derived)\n // range — the runtime windows visibility off data-start/data-duration regardless\n // of class, so stamping both halves below makes each half show only in its window.\n if (duration <= 0 && fallbackTiming && fallbackTiming.duration > 0) {\n start = fallbackTiming.start;\n duration = fallbackTiming.duration;\n }\n if (duration <= 0 || splitTime <= start || splitTime >= start + duration) {\n return { html: source, matched: false, newId: null };\n }\n\n if (document.getElementById(newId)) {\n let suffix = 2;\n const base = newId;\n while (document.getElementById(newId)) {\n newId = `${base}-${suffix++}`;\n }\n }\n\n const firstDuration = splitTime - start;\n const secondDuration = duration - firstDuration;\n\n const clone = el.cloneNode(true);\n if (!isHTMLElement(clone)) return { html: source, matched: false, newId: null };\n clone.setAttribute(\"id\", newId);\n dedupeClonedCompositionId(document, clone);\n clone.removeAttribute(\"data-hf-id\");\n // Descendants carry their own data-hf-id; leaving them duplicates the id of\n // every nested node (e.g. an inner <span>), so strip them on the clone too.\n for (const node of clone.querySelectorAll(\"[data-hf-id]\")) node.removeAttribute(\"data-hf-id\");\n setElementDuration(clone, splitTime, secondDuration, fallbackTiming?.track);\n\n // Keep the \"clip\" class — the runtime uses it to control visibility\n // based on data-start/data-duration timing.\n\n // A split creates two views over the same media source. Even an untrimmed\n // audio/video element needs an explicit zero in-point stamped on the first\n // half so the second half can advance from it instead of restarting at zero.\n const playbackStartAttr = el.hasAttribute(\"data-playback-start\")\n ? \"data-playback-start\"\n : el.hasAttribute(\"data-media-start\")\n ? \"data-media-start\"\n : fallbackTiming?.stampPlaybackStart\n ? \"data-playback-start\"\n : el.matches(\"audio, video\")\n ? \"data-media-start\"\n : null;\n if (playbackStartAttr) {\n const currentTrim =\n parseFloat(el.getAttribute(playbackStartAttr) ?? \"\") || fallbackTiming?.playbackStart || 0;\n const rateRaw = parseFloat(el.getAttribute(\"data-playback-rate\") ?? \"\");\n const rate =\n Number.isFinite(rateRaw) && rateRaw > 0 ? rateRaw : (fallbackTiming?.playbackRate ?? 1);\n el.setAttribute(playbackStartAttr, String(Math.round(currentTrim * 1000) / 1000));\n clone.setAttribute(\n playbackStartAttr,\n String(Math.round((currentTrim + firstDuration * rate) * 1000) / 1000),\n );\n }\n\n // Duplicate CSS rules targeting the original ID so the clone inherits the same styles.\n const originalId = el.getAttribute(\"id\");\n if (originalId) {\n duplicateCssRulesForId(document, originalId, newId);\n }\n\n // Trim the original element's duration. A GSAP element had no data-start; stamp\n // it so the runtime windows the first half (visibility selects on [data-start]).\n setElementDuration(el, start, firstDuration, fallbackTiming?.track);\n\n // Insert clone after original\n if (el.nextSibling) {\n el.parentElement!.insertBefore(clone, el.nextSibling);\n } else {\n el.parentElement!.appendChild(clone);\n }\n\n const html = wrappedFragment ? document.body.innerHTML || \"\" : document.toString();\n return {\n // The split owns its new nodes' stable ids. Leaving the clone unstamped makes\n // the next preview request persist different bytes after history is recorded.\n html: ensureHfIds(html),\n matched: true,\n newId,\n };\n}\n\n// --- Element grouping -------------------------------------------------------\n// A group is a real `<div data-hf-group=\"…\">` wrapping its members in the DOM.\n// Wrapping rebases each member's left/top so its absolute position is unchanged:\n// the wrapper sits at the selection bbox top-left, and each child's new left/top\n// is its old left/top minus the wrapper origin (computed client-side, where live\n// layout is available, and passed in via `rebases`). GSAP x/y, CSS translate and\n// --hf-studio-offset vars are deltas relative to flow position and stay untouched.\n\nexport interface WrapElementsResult {\n html: string;\n matched: boolean;\n groupId: string | null;\n error?: string;\n}\n\nexport interface UnwrapElementsResult {\n html: string;\n unwrapped: boolean;\n /** The unwrapped wrapper's id, so callers can strip GSAP that targeted it\n * (the wrapper is gone; a leftover `gsap.set(\"#id\")` would throw at runtime). */\n unwrappedGroupId?: string;\n /** Members (id'd children) with their absolute layout centres (post un-rebase),\n * so the caller can BAKE the group's GSAP transform into each member before\n * stripping it — otherwise the group's moves are lost on ungroup. */\n members?: Array<{ id: string; cx: number; cy: number }>;\n /** The wrapper's layout centre — the pivot for baking the group's rotation/scale. */\n groupCenter?: { cx: number; cy: number };\n}\n\nexport interface ElementRebase {\n target: SourceMutationTarget;\n left: number;\n top: number;\n /** The member's current resolved track (authored, or the runtime's\n * positional-index fallback). Stamped explicitly so moving it into the\n * wrapper can't shift its computed row — same hazard split closes. */\n track?: number;\n}\n\nfunction getInlineStylePx(el: Element, property: string): number {\n const style = (isHTMLElement(el) ? el.getAttribute(\"style\") : null) ?? \"\";\n const { props } = parseStyleDecls(style);\n const raw = props.get(property);\n if (!raw) return 0;\n const n = parseFloat(raw);\n return Number.isFinite(n) ? n : 0;\n}\n\nfunction setInlineLeftTop(el: HTMLElement, left: number, top: number): void {\n let style = el.getAttribute(\"style\") ?? \"\";\n style = patchStyleAttrString(style, \"left\", `${left}px`);\n style = patchStyleAttrString(style, \"top\", `${top}px`);\n el.setAttribute(\"style\", style);\n}\n\n// Slug the group name (\"Group 1\" → \"group-1\") into a unique, valid element id.\nfunction uniqueGroupDomId(document: Document, groupId: string): string {\n const base =\n groupId\n .trim()\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n // Normalization above leaves at most one hyphen at either edge.\n .replace(/^-|-$/g, \"\") || \"group\";\n let id = base;\n let n = 2;\n while (document.getElementById(id)) {\n id = `${base}-${n}`;\n n += 1;\n }\n return id;\n}\n\n// fallow-ignore-next-line complexity\nexport function wrapElementsInHtml(\n source: string,\n targets: SourceMutationTarget[],\n groupId: string,\n bbox: { left: number; top: number; width: number; height: number },\n rebases: ElementRebase[],\n): WrapElementsResult {\n const { document, wrappedFragment } = parseSourceDocument(source);\n if (targets.length === 0) {\n return { html: source, matched: false, groupId: null, error: \"no targets\" };\n }\n\n // Resolve + dedupe by element ref (two targets may point at the same node).\n const els: HTMLElement[] = [];\n const seen = new Set<Element>();\n for (const target of targets) {\n const el = findTargetElement(document, target);\n if (!el || !isHTMLElement(el) || seen.has(el)) continue;\n seen.add(el);\n els.push(el);\n }\n if (els.length === 0) {\n return { html: source, matched: false, groupId: null, error: \"no targets matched\" };\n }\n\n // P1: require a single common parent (LCA multi-parent wrapping is P2).\n const parent = els[0]?.parentElement;\n if (!parent || els.some((el) => el.parentElement !== parent)) {\n return {\n html: source,\n matched: false,\n groupId: null,\n error: \"grouped elements must share a single parent\",\n };\n }\n\n // Order members by their position in the parent (= z-order / stacking order).\n const memberSet = new Set<Element>(els);\n const ordered = Array.from(parent.children).filter((c): c is HTMLElement => memberSet.has(c));\n\n // Map each member to its rebased left/top and track (resolved against the same document).\n const rebaseByEl = new Map<Element, { left: number; top: number; track?: number }>();\n for (const rebase of rebases) {\n const el = findTargetElement(document, rebase.target);\n if (el) rebaseByEl.set(el, { left: rebase.left, top: rebase.top, track: rebase.track });\n }\n\n const wrapper = document.createElement(\"div\");\n wrapper.setAttribute(\"data-hf-group\", groupId);\n // A real `id` (slug of the group name) makes the wrapper a first-class node in the\n // clip manifest / timeline parent-map (both keyed by id) and a clean GSAP target —\n // without it the wrapper is invisible to the timeline and breaks child enumeration.\n wrapper.setAttribute(\"id\", uniqueGroupDomId(document, groupId));\n // Adopt the topmost member's stacking level. A group is one stacking unit, so a\n // non-member interleaved between two selected members can't stay \"between\" them\n // once they unify. Matching Figma/Sketch, the group lifts to the topmost selected\n // layer: the wrapper goes at the LAST member's slot and carries the max member\n // z-index — so an interleaved non-member falls below the group instead of hoisting\n // above it, and explicit member z-indexes are honored.\n const memberZIndexes = ordered\n .map((el) =>\n Number.parseInt(\n parseStyleDecls(el.getAttribute(\"style\") ?? \"\").props.get(\"z-index\") ?? \"\",\n 10,\n ),\n )\n .filter((z) => Number.isFinite(z));\n const maxZ = memberZIndexes.length > 0 ? Math.max(...memberZIndexes) : null;\n wrapper.setAttribute(\n \"style\",\n `position: absolute; left: ${bbox.left}px; top: ${bbox.top}px; width: ${bbox.width}px; height: ${bbox.height}px` +\n (maxZ !== null ? `; z-index: ${maxZ}` : \"\"),\n );\n\n // Insert the wrapper at the topmost member's slot, then move members into it.\n parent.insertBefore(wrapper, ordered[ordered.length - 1] ?? null);\n for (const el of ordered) {\n const rebase = rebaseByEl.get(el);\n if (rebase) setInlineLeftTop(el, rebase.left, rebase.top);\n if (rebase?.track != null) writeClipTiming(el, { trackIndex: Math.round(rebase.track) });\n wrapper.appendChild(el); // appendChild moves the node, preserving order\n }\n\n return {\n html: wrappedFragment ? document.body.innerHTML || \"\" : document.toString(),\n matched: true,\n groupId,\n };\n}\n\nexport interface UnwrapChildTrack {\n target: SourceMutationTarget;\n /** The child's current resolved track, same hazard and fix as wrap's members. */\n track?: number;\n}\n\n// Only children actually inside the group and given a resolved track qualify —\n// same hazard and fix as wrap's members, scoped to this group's own children.\nfunction buildChildTrackMap(\n document: Document,\n group: Element,\n childTracks: UnwrapChildTrack[],\n): Map<Element, number> {\n const trackByEl = new Map<Element, number>();\n for (const entry of childTracks) {\n const el = findTargetElement(document, entry.target);\n if (el && group.contains(el) && entry.track != null) trackByEl.set(el, entry.track);\n }\n return trackByEl;\n}\n\n// Undoes the wrap-side rebase (child absolute = child rebased + wrapper\n// origin), stamps each child's resolved track where one was given, and moves\n// every child back into the parent ahead of the wrapper — preserving order.\nfunction relocateGroupChildren(\n group: Element,\n parent: Element,\n wLeft: number,\n wTop: number,\n trackByEl: Map<Element, number>,\n): Array<{ id: string; cx: number; cy: number }> {\n const members: Array<{ id: string; cx: number; cy: number }> = [];\n for (const child of Array.from(group.children)) {\n if (isHTMLElement(child)) {\n const newLeft = getInlineStylePx(child, \"left\") + wLeft;\n const newTop = getInlineStylePx(child, \"top\") + wTop;\n setInlineLeftTop(child, newLeft, newTop);\n const track = trackByEl.get(child);\n if (track != null) writeClipTiming(child, { trackIndex: Math.round(track) });\n if (child.id) {\n members.push({\n id: child.id,\n cx: newLeft + getInlineStylePx(child, \"width\") / 2,\n cy: newTop + getInlineStylePx(child, \"height\") / 2,\n });\n }\n }\n parent.insertBefore(child, group);\n }\n return members;\n}\n\nexport function unwrapElementsFromHtml(\n source: string,\n groupTarget: SourceMutationTarget,\n childTracks: UnwrapChildTrack[] = [],\n): UnwrapElementsResult {\n const { document, wrappedFragment } = parseSourceDocument(source);\n const group = findTargetElement(document, groupTarget);\n if (!group || !isHTMLElement(group)) return { html: source, unwrapped: false };\n // Shape guard mirroring the wrap-side contract: only ever dissolve an actual\n // group wrapper. A stale/desynced selection that resolves to a plain <div>\n // would otherwise be unwrapped — rebasing its children by the parent's origin\n // (silent corruption). Wrap enforces invariants; unwrap must too.\n if (!group.hasAttribute(\"data-hf-group\")) return { html: source, unwrapped: false };\n\n const parent = group.parentElement;\n if (!parent) return { html: source, unwrapped: false };\n\n const trackByEl = buildChildTrackMap(document, group, childTracks);\n\n // Undo the rebase: child absolute position = child (rebased) + wrapper origin.\n const wLeft = getInlineStylePx(group, \"left\");\n const wTop = getInlineStylePx(group, \"top\");\n const groupCenter = {\n cx: wLeft + getInlineStylePx(group, \"width\") / 2,\n cy: wTop + getInlineStylePx(group, \"height\") / 2,\n };\n\n const members = relocateGroupChildren(group, parent, wLeft, wTop, trackByEl);\n const groupId = group.id || undefined;\n group.remove();\n\n return {\n html: wrappedFragment ? document.body.innerHTML || \"\" : document.toString(),\n unwrapped: true,\n unwrappedGroupId: groupId,\n members,\n groupCenter,\n };\n}\n","// fallow-ignore-next-line complexity\nexport function parseStyleDecls(style: string): { props: Map<string, string>; order: string[] } {\n const props = new Map<string, string>();\n const order: string[] = [];\n let i = 0;\n while (i < style.length) {\n let depth = 0;\n let inSingle = false;\n let inDouble = false;\n const start = i;\n while (i < style.length) {\n const ch = style[i];\n if (ch === \"'\" && !inDouble) inSingle = !inSingle;\n else if (ch === '\"' && !inSingle) inDouble = !inDouble;\n else if (!inSingle && !inDouble) {\n if (ch === \"(\") depth++;\n else if (ch === \")\") depth = Math.max(0, depth - 1);\n else if (ch === \";\" && depth === 0) break;\n }\n i++;\n }\n const decl = style.slice(start, i).trim();\n i++;\n if (!decl) continue;\n const colon = decl.indexOf(\":\");\n if (colon < 0) continue;\n const key = decl.slice(0, colon).trim();\n const val = decl.slice(colon + 1).trim();\n if (!key) continue;\n if (!props.has(key)) order.push(key);\n props.set(key, val);\n }\n return { props, order };\n}\n\nfunction serializeStyleDecls(props: Map<string, string>, order: string[]): string {\n return order\n .map((k) => `${k}: ${props.get(k) ?? \"\"}`)\n .filter((d) => d.trim())\n .join(\"; \");\n}\n\nexport function patchStyleAttrString(\n style: string,\n property: string,\n value: string | null,\n): string {\n const { props, order } = parseStyleDecls(style);\n if (value === null) {\n props.delete(property);\n const idx = order.indexOf(property);\n if (idx >= 0) order.splice(idx, 1);\n } else {\n if (!props.has(property)) order.push(property);\n props.set(property, value);\n }\n return serializeStyleDecls(props, order);\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/helpers/mediaProxyPreview.ts"],"sourcesContent":["import { resolve } from \"node:path\";\nimport type { StudioApiAdapter } from \"../types.js\";\nimport {\n createMediaCodecProbeCache,\n proxyVariantFor,\n recordProxyPrewarm,\n scanProjectMediaCodecMap,\n shouldPrewarmProxy,\n type HtmlSourceLike,\n type MediaCodecMap,\n type MediaCodecProbeCache,\n} from \"./mediaCodecMap.js\";\nimport { resolveProxy, PROXY_PARAMS_VERSION } from \"./proxyTranscoder.js\";\n\n/**\n * Transparent-media-proxy wiring shared by `routes/preview.ts`\n * (docs/plans/2026-07-14-002-feat-transparent-media-proxies-plan.md, unit U3).\n * Split out of the route module to keep it under the repo's 600-line file cap.\n */\n\n/**\n * Preview-route-local adapter surface for the auto-proxy feature. Both\n * fields are optional so any existing `StudioApiAdapter` value remains\n * structurally assignable without editing the shared interface:\n * `autoProxy` defaults to true (on) when omitted — a later unit wires the\n * CLI `--no-proxy` flag / `hyperframes.json` setting through it;\n * `mediaCodecProbeCache` lets a host share one probe cache across\n * preview/play/static-server surfaces instead of each constructing its own.\n */\nexport type PreviewApiAdapter = StudioApiAdapter & {\n autoProxy?: boolean;\n mediaCodecProbeCache?: MediaCodecProbeCache;\n};\n\nexport function isAutoProxyEnabled(adapter: PreviewApiAdapter): boolean {\n return adapter.autoProxy !== false;\n}\n\n/** One probe cache per server instance — construct once in `registerPreviewRoutes`\n * and reuse across every request so the mtime-cache benefit in\n * `scanProjectMediaCodecMap` actually applies. A host that wants to share the\n * cache across other surfaces (play, static project server) can pass its own\n * via `adapter.mediaCodecProbeCache`. */\nexport function resolvePreviewMediaCodecProbeCache(\n adapter: PreviewApiAdapter,\n): MediaCodecProbeCache {\n return adapter.mediaCodecProbeCache ?? createMediaCodecProbeCache();\n}\n\n/**\n * ETag salt for `?hf-proxy=` asset requests, mirroring `variablesEtagSalt` in\n * preview.ts: salted by the raw param value plus the transcoder's params\n * version, so a future proxy-recipe change (which bumps `PROXY_PARAMS_VERSION`)\n * or a different proxy variant invalidates cached 304s without needing to\n * touch the proxy file itself.\n */\nexport function proxyEtagSalt(raw: string | undefined): string {\n if (raw === undefined) return \"\";\n return `:proxy:${raw}:${PROXY_PARAMS_VERSION}`;\n}\n\n// Mirrors `injectScriptTagIntoHead` in routes/preview.ts (kept local rather\n// than imported to avoid a helpers → routes dependency edge for one\n// two-line utility).\nfunction injectScriptTagIntoHead(html: string, scriptTag: string): string {\n if (html.includes(\"</head>\")) return html.replace(\"</head>\", `${scriptTag}\\n</head>`);\n return `${scriptTag}\\n${html}`;\n}\n\n/**\n * Injects `window.__HF_MEDIA_CODEC_MAP__` (the U1 codec-facts scan) into\n * served composition HTML, and fire-and-forget pre-warms `resolveProxy` for\n * every entry whose codec no browser decodes, so an element's proactive swap\n * usually hits a warm cache (KTD: protects the per-origin connection budget\n * under held responses). Conditionally hostile codecs are injected but NOT\n * pre-warmed: the requesting browser usually plays them, and the transcode\n * runs concurrently with its first layout. No second concurrency limiter here\n * — the transcoder's own global bound throttles both pre-warm and\n * element-triggered calls. Pre-warm failures are swallowed; an actual\n * `?hf-proxy=` request surfaces them as a 502 and transcodes lazily.\n * Alpha-bearing entries pre-warm their VP8/WebM variant.\n *\n * The single shared implementation for every auto-proxy surface — the studio\n * preview route (via `injectMediaCodecMap` below) and the CLI's composition /\n * static project servers (via the `./media-proxy-preview` subpath export).\n * Empty maps leave HTML untouched, preserving the normal no-hostile-media\n * preview path. On-demand proxy requests enforce the same eligibility gate.\n */\nexport async function injectMediaCodecMapIntoHtml(\n html: string,\n projectDir: string,\n htmlSources: HtmlSourceLike[],\n probeCache?: MediaCodecProbeCache,\n): Promise<string> {\n let map: MediaCodecMap;\n try {\n map = await scanProjectMediaCodecMap(\n projectDir,\n htmlSources,\n probeCache ? { cache: probeCache } : {},\n );\n } catch {\n // Best-effort: a scan failure must never block serving the page.\n return html;\n }\n if (Object.keys(map).length === 0) return html;\n for (const [rootRelativePathname, facts] of Object.entries(map)) {\n if (!shouldPrewarmProxy(facts)) continue;\n recordProxyPrewarm();\n resolveProxy(\n projectDir,\n resolve(projectDir, rootRelativePathname.replace(/^\\/+/, \"\")),\n proxyVariantFor(facts),\n ).catch(() => {\n // Swallowed: the pre-warm is best-effort. A real `?hf-proxy=` request\n // for this asset re-attempts the transcode and reports failure (502).\n });\n }\n // <-escape prevents a src path containing \"</script>\" from breaking out of\n // the injected tag, mirroring injectPreviewVariables in helpers/previewVariables.ts.\n const json = JSON.stringify(map)\n .replace(/</g, \"\\\\u003c\")\n .replace(/\\u2028/g, \"\\\\u2028\")\n .replace(/\\u2029/g, \"\\\\u2029\");\n const tag = `<script data-hf-media-codec-map>window.__HF_MEDIA_CODEC_MAP__=${json};</script>`;\n return injectScriptTagIntoHead(html, tag);\n}\n\n/**\n * Adapter-aware wrapper used by the studio preview routes: skipped entirely\n * (no scan, no injection) when auto-proxy is off for this adapter.\n */\nexport async function injectMediaCodecMap(\n html: string,\n adapter: PreviewApiAdapter,\n projectDir: string,\n compSrcPath: string,\n probeCache: MediaCodecProbeCache,\n): Promise<string> {\n if (!isAutoProxyEnabled(adapter)) return html;\n return injectMediaCodecMapIntoHtml(html, projectDir, [{ html, compSrcPath }], probeCache);\n}\n"],"mappings":";;;;;;;;;;;;;AAAA,SAAS,eAAe;AAkCjB,SAAS,mBAAmB,SAAqC;AACtE,SAAO,QAAQ,cAAc;AAC/B;AAOO,SAAS,mCACd,SACsB;AACtB,SAAO,QAAQ,wBAAwB,2BAA2B;AACpE;AASO,SAAS,cAAc,KAAiC;AAC7D,MAAI,QAAQ,OAAW,QAAO;AAC9B,SAAO,UAAU,GAAG,IAAI,oBAAoB;AAC9C;AAKA,SAAS,wBAAwB,MAAc,WAA2B;AACxE,MAAI,KAAK,SAAS,SAAS,EAAG,QAAO,KAAK,QAAQ,WAAW,GAAG,SAAS;AAAA,QAAW;AACpF,SAAO,GAAG,SAAS;AAAA,EAAK,IAAI;AAC9B;AAqBA,eAAsB,4BACpB,MACA,YACA,aACA,YACiB;AACjB,MAAI;AACJ,MAAI;AACF,UAAM,MAAM;AAAA,MACV;AAAA,MACA;AAAA,MACA,aAAa,EAAE,OAAO,WAAW,IAAI,CAAC;AAAA,IACxC;AAAA,EACF,QAAQ;AAEN,WAAO;AAAA,EACT;AACA,MAAI,OAAO,KAAK,GAAG,EAAE,WAAW,EAAG,QAAO;AAC1C,aAAW,CAAC,sBAAsB,KAAK,KAAK,OAAO,QAAQ,GAAG,GAAG;AAC/D,QAAI,CAAC,mBAAmB,KAAK,EAAG;AAChC,uBAAmB;AACnB;AAAA,MACE;AAAA,MACA,QAAQ,YAAY,qBAAqB,QAAQ,QAAQ,EAAE,CAAC;AAAA,MAC5D,gBAAgB,KAAK;AAAA,IACvB,EAAE,MAAM,MAAM;AAAA,IAGd,CAAC;AAAA,EACH;AAGA,QAAM,OAAO,KAAK,UAAU,GAAG,EAC5B,QAAQ,MAAM,SAAS,EACvB,QAAQ,WAAW,SAAS,EAC5B,QAAQ,WAAW,SAAS;AAC/B,QAAM,MAAM,iEAAiE,IAAI;AACjF,SAAO,wBAAwB,MAAM,GAAG;AAC1C;AAMA,eAAsB,oBACpB,MACA,SACA,YACA,aACA,YACiB;AACjB,MAAI,CAAC,mBAAmB,OAAO,EAAG,QAAO;AACzC,SAAO,4BAA4B,MAAM,YAAY,CAAC,EAAE,MAAM,YAAY,CAAC,GAAG,UAAU;AAC1F;","names":[]}