@hyperframes/studio 0.6.25 → 0.6.27

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/index.html CHANGED
@@ -5,8 +5,8 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
6
6
  <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
7
7
  <title>HyperFrames Studio</title>
8
- <script type="module" crossorigin src="/assets/index-BA9LlfxA.js"></script>
9
- <link rel="stylesheet" crossorigin href="/assets/index-Cqq4uPvL.css">
8
+ <script type="module" crossorigin src="/assets/index-DYjmgXgg.js"></script>
9
+ <link rel="stylesheet" crossorigin href="/assets/index-DVpLGNHi.css">
10
10
  </head>
11
11
  <body>
12
12
  <div id="root"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperframes/studio",
3
- "version": "0.6.25",
3
+ "version": "0.6.27",
4
4
  "description": "",
5
5
  "repository": {
6
6
  "type": "git",
@@ -30,8 +30,8 @@
30
30
  "@codemirror/theme-one-dark": "^6.1.2",
31
31
  "@codemirror/view": "6.40.0",
32
32
  "@phosphor-icons/react": "^2.1.10",
33
- "@hyperframes/player": "0.6.25",
34
- "@hyperframes/core": "0.6.25"
33
+ "@hyperframes/core": "0.6.27",
34
+ "@hyperframes/player": "0.6.27"
35
35
  },
36
36
  "devDependencies": {
37
37
  "@types/react": "19",
@@ -45,7 +45,7 @@
45
45
  "vite": "^6.4.2",
46
46
  "vitest": "^3.2.4",
47
47
  "zustand": "^5.0.0",
48
- "@hyperframes/producer": "0.6.25"
48
+ "@hyperframes/producer": "0.6.27"
49
49
  },
50
50
  "peerDependencies": {
51
51
  "react": "19",
@@ -1,14 +1,5 @@
1
- import { memo } from "react";
2
1
  import type { DomEditLayerItem } from "./domEditing";
3
2
 
4
- interface TimelineLayerPanelProps {
5
- clipLabel: string;
6
- layers: DomEditLayerItem[];
7
- selectedLayerKey: string | null;
8
- onSelectLayer: (layer: DomEditLayerItem) => void;
9
- onClose: () => void;
10
- }
11
-
12
3
  const MEDIA_LAYER_TAGS = new Set(["audio", "canvas", "img", "picture", "svg", "video"]);
13
4
 
14
5
  export function getTimelineLayerPanelSummary(layers: readonly DomEditLayerItem[]): string {
@@ -22,92 +13,3 @@ export function getTimelineLayerPanelSummary(layers: readonly DomEditLayerItem[]
22
13
  ? "Single selectable media layer"
23
14
  : "Single selectable layer";
24
15
  }
25
-
26
- export const TimelineLayerPanel = memo(function TimelineLayerPanel({
27
- clipLabel,
28
- layers,
29
- selectedLayerKey,
30
- onSelectLayer,
31
- onClose,
32
- }: TimelineLayerPanelProps) {
33
- return (
34
- <div className="flex h-full min-h-0 flex-col overflow-hidden bg-neutral-950">
35
- <div className="flex items-start justify-between gap-3 border-b border-white/10 px-3 py-3">
36
- <div className="min-w-0">
37
- <div className="text-[9px] font-semibold uppercase tracking-[0.18em] text-neutral-500">
38
- Clip layers
39
- </div>
40
- <div className="mt-1 truncate text-sm font-semibold text-neutral-100">{clipLabel}</div>
41
- </div>
42
- <button
43
- type="button"
44
- onPointerDown={(event) => {
45
- event.stopPropagation();
46
- }}
47
- onClick={onClose}
48
- className="flex h-7 w-7 flex-shrink-0 items-center justify-center rounded-md border border-white/10 bg-black/20 text-neutral-500 transition-colors hover:border-white/20 hover:text-neutral-200"
49
- aria-label="Close clip layers"
50
- >
51
- <svg
52
- width="14"
53
- height="14"
54
- viewBox="0 0 24 24"
55
- fill="none"
56
- stroke="currentColor"
57
- strokeWidth="1.8"
58
- strokeLinecap="round"
59
- aria-hidden="true"
60
- >
61
- <path d="M18 6 6 18" />
62
- <path d="m6 6 12 12" />
63
- </svg>
64
- </button>
65
- </div>
66
- <div className="border-b border-white/10 px-3 py-2 text-[11px] text-neutral-500">
67
- {getTimelineLayerPanelSummary(layers)}
68
- </div>
69
- <div className="min-h-0 flex-1 overflow-y-auto py-1">
70
- {layers.map((layer) => {
71
- const selected = layer.key === selectedLayerKey;
72
- return (
73
- <button
74
- key={layer.key}
75
- type="button"
76
- data-timeline-layer-row={layer.key}
77
- onPointerDown={(event) => {
78
- event.stopPropagation();
79
- onSelectLayer(layer);
80
- }}
81
- onClick={(event) => {
82
- event.stopPropagation();
83
- onSelectLayer(layer);
84
- }}
85
- className={`group flex w-full items-center gap-2 px-2.5 py-1.5 text-left transition-colors ${
86
- selected
87
- ? "bg-studio-accent/14 text-studio-accent"
88
- : "text-neutral-300 hover:bg-white/[0.04] hover:text-neutral-100"
89
- }`}
90
- style={{ paddingLeft: 10 + layer.depth * 14 }}
91
- >
92
- <span
93
- className={`flex h-5 w-5 flex-shrink-0 items-center justify-center rounded-md border text-[9px] font-bold uppercase ${
94
- selected
95
- ? "border-studio-accent/50 bg-studio-accent/18"
96
- : "border-white/10 bg-black/20 text-neutral-500 group-hover:text-neutral-300"
97
- }`}
98
- >
99
- {layer.tagName.slice(0, 2)}
100
- </span>
101
- <span className="min-w-0 flex-1 truncate text-xs font-medium">{layer.label}</span>
102
- {layer.childCount > 0 && (
103
- <span className="rounded-full border border-white/10 bg-black/25 px-1.5 py-0.5 text-[9px] font-semibold tabular-nums text-neutral-500">
104
- {layer.childCount}
105
- </span>
106
- )}
107
- </button>
108
- );
109
- })}
110
- </div>
111
- </div>
112
- );
113
- });
@@ -321,6 +321,29 @@ describe("resolveVisualDomEditSelectionTarget", () => {
321
321
  expect(visualTarget).toBe(headline);
322
322
  expect(explicitSelection?.id).toBe("container");
323
323
  });
324
+
325
+ it("prefers the visually-on-top sibling over a deeper element in a separate visual layer", () => {
326
+ const document = createDocument(`
327
+ <div id="comp-root">
328
+ <div id="sub-comp" class="sub-comp">
329
+ <img id="sf-chrome" class="sf-chrome" style="width:100%;height:100%" />
330
+ </div>
331
+ <video id="pip-studio" class="pip-studio" style="position:absolute;z-index:15" />
332
+ </div>
333
+ `);
334
+ const pipStudio = document.getElementById("pip-studio") as HTMLElement;
335
+ const sfChrome = document.getElementById("sf-chrome") as HTMLElement;
336
+ const subComp = document.getElementById("sub-comp") as HTMLElement;
337
+ setElementRect(pipStudio, { left: 50, top: 50, width: 320, height: 320 });
338
+ setElementRect(sfChrome, { left: 0, top: 0, width: 1920, height: 1080 });
339
+ setElementRect(subComp, { left: 0, top: 0, width: 1920, height: 1080 });
340
+
341
+ expect(
342
+ resolveVisualDomEditSelectionTarget([pipStudio, subComp, sfChrome], {
343
+ activeCompositionPath: "index.html",
344
+ }),
345
+ ).toBe(pipStudio);
346
+ });
324
347
  });
325
348
 
326
349
  describe("isLargeRasterDomEditSelection", () => {
@@ -12,13 +12,9 @@ import type {
12
12
  import {
13
13
  buildStableSelector,
14
14
  escapeCssString,
15
- findClosestByAttribute,
16
- getElementDepth,
17
- getPreferredClassSelector,
18
15
  getSelectorIndex,
19
16
  getSourceFileForElement,
20
17
  isHtmlElement,
21
- isTextBearingTag,
22
18
  normalizeTimelineCompositionSource,
23
19
  querySelectorAllSafely,
24
20
  } from "./domEditingDom";
@@ -60,7 +56,7 @@ function isEmptyVisualContainer(el: HTMLElement): boolean {
60
56
  return true;
61
57
  }
62
58
 
63
- export function hasRenderedBox(el: HTMLElement): boolean {
59
+ function hasRenderedBox(el: HTMLElement): boolean {
64
60
  const rect = el.getBoundingClientRect();
65
61
  if (rect.width <= 1 || rect.height <= 1) return false;
66
62
  if (!isElementComputedVisible(el)) return false;
@@ -70,23 +66,6 @@ export function hasRenderedBox(el: HTMLElement): boolean {
70
66
 
71
67
  // ─── Visual scoring ──────────────────────────────────────────────────────────
72
68
 
73
- function isEditableTextLeafForScoring(el: HTMLElement): boolean {
74
- return isTextBearingTag(el.tagName.toLowerCase()) && el.children.length === 0;
75
- }
76
-
77
- function getVisualElementScore(el: HTMLElement, pointerStackIndex: number): number {
78
- const tagName = el.tagName.toLowerCase();
79
- const rect = el.getBoundingClientRect();
80
- const area = Math.max(1, rect.width * rect.height);
81
- const smallerElementBonus = Math.max(0, 1_000_000 - Math.min(area, 1_000_000)) / 1_000;
82
- const visualLeafBonus =
83
- isEditableTextLeafForScoring(el) || ["img", "video", "canvas", "svg"].includes(tagName)
84
- ? 2_000
85
- : 0;
86
-
87
- return getElementDepth(el) * 10_000 + visualLeafBonus + smallerElementBonus - pointerStackIndex;
88
- }
89
-
90
69
  // ─── Layer patch target ──────────────────────────────────────────────────────
91
70
 
92
71
  const DOM_LAYER_IGNORED_TAGS = new Set([
@@ -174,25 +153,31 @@ export function resolveVisualDomEditSelectionTarget(
174
153
  elementsFromPoint: Iterable<Element | null | undefined>,
175
154
  options: Pick<DomEditContextOptions, "activeCompositionPath">,
176
155
  ): HTMLElement | null {
177
- let best: { element: HTMLElement; score: number } | null = null;
178
- let pointerStackIndex = 0;
156
+ const candidates: HTMLElement[] = [];
179
157
 
180
158
  for (const entry of elementsFromPoint) {
181
- if (!isHtmlElement(entry)) {
182
- pointerStackIndex += 1;
183
- continue;
159
+ if (!isHtmlElement(entry)) continue;
160
+ if (hasRenderedBox(entry) && getDomLayerPatchTarget(entry, options.activeCompositionPath)) {
161
+ candidates.push(entry);
184
162
  }
163
+ }
185
164
 
186
- if (hasRenderedBox(entry) && getDomLayerPatchTarget(entry, options.activeCompositionPath)) {
187
- const score = getVisualElementScore(entry, pointerStackIndex);
188
- if (!best || score > best.score) {
189
- best = { element: entry, score };
190
- }
165
+ if (candidates.length === 0) return null;
166
+
167
+ // candidates are in visual stacking order (topmost first, from elementsFromPoint).
168
+ // Start with the topmost and only replace with a descendant that is more
169
+ // specific within the same visual subtree. Never jump to an unrelated
170
+ // element that happens to be painted behind the current pick.
171
+ let best = candidates[0];
172
+
173
+ for (let i = 1; i < candidates.length; i++) {
174
+ const candidate = candidates[i];
175
+ if (best.contains(candidate)) {
176
+ best = candidate;
191
177
  }
192
- pointerStackIndex += 1;
193
178
  }
194
179
 
195
- return best?.element ?? null;
180
+ return best;
196
181
  }
197
182
 
198
183
  // ─── Raster detection ────────────────────────────────────────────────────────
@@ -324,7 +309,3 @@ export function getDirectLayerChildren(
324
309
  isHtmlElement(child) && getDomLayerPatchTarget(child, options.activeCompositionPath) !== null,
325
310
  );
326
311
  }
327
-
328
- // ─── Composition source helpers ───────────────────────────────────────────────
329
-
330
- export { findClosestByAttribute, getPreferredClassSelector, getSourceFileForElement };
@@ -23,37 +23,15 @@ describe("manual editing availability", () => {
23
23
  expect(availability.STUDIO_PREVIEW_SELECTION_ENABLED).toBe(true);
24
24
  expect(availability.STUDIO_INSPECTOR_PANELS_ENABLED).toBe(true);
25
25
  expect(availability.STUDIO_MOTION_PANEL_ENABLED).toBe(false);
26
- expect(availability.STUDIO_TIMELINE_LAYER_INSPECTOR_ENABLED).toBe(true);
27
26
  });
28
27
 
29
- it("keeps explicit truthy inspector env flags enabled", async () => {
30
- const availability = await loadAvailabilityWithEnv({
31
- VITE_STUDIO_ENABLE_INSPECTOR_PANELS: "1",
32
- VITE_STUDIO_ENABLE_TIMELINE_LAYER_INSPECTOR: "true",
33
- });
34
-
35
- expect(availability.STUDIO_INSPECTOR_PANELS_ENABLED).toBe(true);
36
- expect(availability.STUDIO_TIMELINE_LAYER_INSPECTOR_ENABLED).toBe(true);
37
- });
38
-
39
- it("allows explicit env flags to disable default-on inspector layers", async () => {
40
- const availability = await loadAvailabilityWithEnv({
41
- VITE_STUDIO_ENABLE_TIMELINE_LAYER_INSPECTOR: "off",
42
- });
43
-
44
- expect(availability.STUDIO_INSPECTOR_PANELS_ENABLED).toBe(true);
45
- expect(availability.STUDIO_TIMELINE_LAYER_INSPECTOR_ENABLED).toBe(false);
46
- });
47
-
48
- it("keeps timeline layer inspection off when the parent inspector flag is off", async () => {
28
+ it("disables preview selection when the inspector panel flag is explicitly off", async () => {
49
29
  const availability = await loadAvailabilityWithEnv({
50
30
  VITE_STUDIO_ENABLE_INSPECTOR_PANELS: "0",
51
- VITE_STUDIO_ENABLE_TIMELINE_LAYER_INSPECTOR: "true",
52
31
  });
53
32
 
54
33
  expect(availability.STUDIO_INSPECTOR_PANELS_ENABLED).toBe(false);
55
34
  expect(availability.STUDIO_PREVIEW_SELECTION_ENABLED).toBe(false);
56
- expect(availability.STUDIO_TIMELINE_LAYER_INSPECTOR_ENABLED).toBe(false);
57
35
  });
58
36
 
59
37
  it("enables feature flags with explicit truthy env values", () => {
@@ -1,10 +1,8 @@
1
1
  export type StudioFeatureFlagEnv = Record<string, boolean | string | undefined>;
2
2
 
3
- export const STUDIO_PREVIEW_MANUAL_DRAGGING_ENV = "VITE_STUDIO_ENABLE_PREVIEW_MANUAL_DRAGGING";
4
- export const STUDIO_INSPECTOR_PANELS_ENV = "VITE_STUDIO_ENABLE_INSPECTOR_PANELS";
5
- export const STUDIO_MOTION_PANEL_ENV = "VITE_STUDIO_ENABLE_MOTION_PANEL";
6
- export const STUDIO_TIMELINE_LAYER_INSPECTOR_ENV = "VITE_STUDIO_ENABLE_TIMELINE_LAYER_INSPECTOR";
7
-
3
+ const STUDIO_PREVIEW_MANUAL_DRAGGING_ENV = "VITE_STUDIO_ENABLE_PREVIEW_MANUAL_DRAGGING";
4
+ const STUDIO_INSPECTOR_PANELS_ENV = "VITE_STUDIO_ENABLE_INSPECTOR_PANELS";
5
+ const STUDIO_MOTION_PANEL_ENV = "VITE_STUDIO_ENABLE_MOTION_PANEL";
8
6
  const TRUTHY_ENV_VALUES = new Set(["1", "true", "yes", "on", "enabled"]);
9
7
  const FALSY_ENV_VALUES = new Set(["0", "false", "no", "off", "disabled"]);
10
8
 
@@ -52,14 +50,6 @@ export const STUDIO_MOTION_PANEL_ENABLED = resolveStudioBooleanEnvFlag(
52
50
  false,
53
51
  );
54
52
 
55
- export const STUDIO_TIMELINE_LAYER_INSPECTOR_ENABLED =
56
- STUDIO_INSPECTOR_PANELS_ENABLED &&
57
- resolveStudioBooleanEnvFlag(
58
- env,
59
- [STUDIO_TIMELINE_LAYER_INSPECTOR_ENV, "VITE_STUDIO_TIMELINE_LAYER_INSPECTOR_ENABLED"],
60
- true,
61
- );
62
-
63
53
  export const STUDIO_BLOCKS_PANEL_ENABLED = resolveStudioBooleanEnvFlag(
64
54
  env,
65
55
  ["VITE_STUDIO_ENABLE_BLOCKS_PANEL", "VITE_STUDIO_BLOCKS_PANEL_ENABLED"],
@@ -68,6 +58,4 @@ export const STUDIO_BLOCKS_PANEL_ENABLED = resolveStudioBooleanEnvFlag(
68
58
 
69
59
  export const STUDIO_PREVIEW_SELECTION_ENABLED = STUDIO_INSPECTOR_PANELS_ENABLED;
70
60
 
71
- export const STUDIO_MANUAL_EDITING_ENABLED = STUDIO_PREVIEW_MANUAL_EDITING_ENABLED;
72
-
73
61
  export const STUDIO_MANUAL_EDITING_DISABLED_TITLE = "Manual editing is temporarily disabled";
@@ -11,7 +11,6 @@ import {
11
11
  STUDIO_HEIGHT_PROP,
12
12
  STUDIO_ROTATION_PROP,
13
13
  STUDIO_PATH_OFFSET_ATTR,
14
- STUDIO_MANUAL_EDIT_GESTURE_ATTR,
15
14
  STUDIO_BOX_SIZE_ATTR,
16
15
  STUDIO_ROTATION_ATTR,
17
16
  STUDIO_ORIGINAL_TRANSLATE_ATTR,
@@ -33,7 +32,6 @@ import {
33
32
  STUDIO_ORIGINAL_INLINE_ROTATE_ATTR,
34
33
  STUDIO_ORIGINAL_ROTATION_TRANSFORM_ORIGIN_ATTR,
35
34
  STUDIO_ROTATION_DRAFT_ATTR,
36
- STUDIO_ORIGINAL_TRANSFORM_DISPLAY_ATTR,
37
35
  } from "./manualEditsTypes";
38
36
  import type {
39
37
  StudioBoxSizeSnapshot,
@@ -187,38 +185,6 @@ export function restoreStudioPathOffset(
187
185
  );
188
186
  }
189
187
 
190
- /* ── DOM element collection ───────────────────────────────────────── */
191
- export function collectStudioManualEditElements(doc: Document): HTMLElement[] {
192
- const htmlElement = doc.defaultView?.HTMLElement;
193
- if (!htmlElement) return [];
194
-
195
- const elements = [doc.documentElement, ...Array.from(doc.getElementsByTagName("*"))].filter(
196
- (element): element is HTMLElement => element instanceof htmlElement,
197
- );
198
-
199
- return elements.filter(
200
- (element) =>
201
- element.hasAttribute(STUDIO_PATH_OFFSET_ATTR) ||
202
- element.hasAttribute(STUDIO_MANUAL_EDIT_GESTURE_ATTR) ||
203
- element.hasAttribute(STUDIO_BOX_SIZE_ATTR) ||
204
- element.hasAttribute(STUDIO_ROTATION_ATTR) ||
205
- element.hasAttribute(STUDIO_ROTATION_DRAFT_ATTR) ||
206
- element.hasAttribute(STUDIO_ORIGINAL_TRANSLATE_ATTR) ||
207
- element.hasAttribute(STUDIO_ORIGINAL_INLINE_TRANSLATE_ATTR) ||
208
- element.hasAttribute(STUDIO_ORIGINAL_TRANSFORM_DISPLAY_ATTR) ||
209
- element.hasAttribute(STUDIO_ORIGINAL_MIN_WIDTH_ATTR) ||
210
- element.hasAttribute(STUDIO_ORIGINAL_FLEX_BASIS_ATTR) ||
211
- element.hasAttribute(STUDIO_ORIGINAL_SCALE_ATTR) ||
212
- element.hasAttribute(STUDIO_ORIGINAL_ROTATE_ATTR) ||
213
- element.hasAttribute(STUDIO_ORIGINAL_INLINE_ROTATE_ATTR) ||
214
- Boolean(element.style.getPropertyValue(STUDIO_OFFSET_X_PROP)) ||
215
- Boolean(element.style.getPropertyValue(STUDIO_OFFSET_Y_PROP)) ||
216
- Boolean(element.style.getPropertyValue(STUDIO_WIDTH_PROP)) ||
217
- Boolean(element.style.getPropertyValue(STUDIO_HEIGHT_PROP)) ||
218
- Boolean(element.style.getPropertyValue(STUDIO_ROTATION_PROP)),
219
- );
220
- }
221
-
222
188
  /* ── Clear functions ──────────────────────────────────────────────── */
223
189
  type BoxSizeProperty =
224
190
  | "width"
@@ -151,7 +151,6 @@ export function useDomEditSession({
151
151
  setAgentModalOpen,
152
152
  setAgentPromptSelectionContext,
153
153
  setAgentModalAnchorPoint,
154
- preloadAgentPromptSnippet,
155
154
  handleAskAgent,
156
155
  handleAgentModalSubmit,
157
156
  } = useAskAgentModal({
@@ -181,10 +180,6 @@ export function useDomEditSession({
181
180
  applyDomSelection,
182
181
  resolveDomSelectionFromPreviewPoint,
183
182
  updateDomEditHoverSelection,
184
- preloadAgentPromptSnippet,
185
- setAgentPromptSelectionContext,
186
- setAgentModalAnchorPoint,
187
- setAgentModalOpen,
188
183
  onClickToSource,
189
184
  });
190
185
 
@@ -1,16 +1,8 @@
1
1
  import { useCallback } from "react";
2
2
  import { liveTime, usePlayerStore } from "../player";
3
- import {
4
- getPreviewLocalPointer,
5
- buildRasterClickSelectionContext,
6
- pauseStudioPreviewPlayback,
7
- } from "../utils/studioPreviewHelpers";
3
+ import { pauseStudioPreviewPlayback } from "../utils/studioPreviewHelpers";
8
4
  import { STUDIO_PREVIEW_SELECTION_ENABLED } from "../components/editor/manualEditingAvailability";
9
- import {
10
- isLargeRasterDomEditSelection,
11
- type DomEditSelection,
12
- } from "../components/editor/domEditing";
13
- import type { AgentModalAnchorPoint } from "../utils/studioHelpers";
5
+ import { type DomEditSelection } from "../components/editor/domEditing";
14
6
 
15
7
  // ── Types ──
16
8
 
@@ -32,12 +24,6 @@ export interface UsePreviewInteractionParams {
32
24
  ) => DomEditSelection | null;
33
25
  updateDomEditHoverSelection: (selection: DomEditSelection | null) => void;
34
26
 
35
- // From useAskAgentModal
36
- preloadAgentPromptSnippet: (selection: DomEditSelection) => Promise<void>;
37
- setAgentPromptSelectionContext: (context: string | undefined) => void;
38
- setAgentModalAnchorPoint: (point: AgentModalAnchorPoint | null) => void;
39
- setAgentModalOpen: (open: boolean) => void;
40
-
41
27
  onClickToSource?: (selection: DomEditSelection) => void;
42
28
  }
43
29
 
@@ -51,10 +37,6 @@ export function usePreviewInteraction({
51
37
  applyDomSelection,
52
38
  resolveDomSelectionFromPreviewPoint,
53
39
  updateDomEditHoverSelection,
54
- preloadAgentPromptSnippet,
55
- setAgentPromptSelectionContext,
56
- setAgentModalAnchorPoint,
57
- setAgentModalOpen,
58
40
  onClickToSource,
59
41
  }: UsePreviewInteractionParams) {
60
42
  const handlePreviewCanvasMouseDown = useCallback(
@@ -69,37 +51,17 @@ export function usePreviewInteraction({
69
51
  }
70
52
  e.preventDefault();
71
53
  e.stopPropagation();
72
- const localPointer = previewIframeRef.current
73
- ? getPreviewLocalPointer(previewIframeRef.current, e.clientX, e.clientY)
74
- : null;
75
54
  applyDomSelection(nextSelection, { additive: e.shiftKey });
76
55
  if (!e.shiftKey && e.altKey && onClickToSource) {
77
56
  onClickToSource(nextSelection);
78
57
  }
79
- if (
80
- !e.shiftKey &&
81
- localPointer &&
82
- isLargeRasterDomEditSelection(nextSelection, localPointer.viewport)
83
- ) {
84
- setAgentPromptSelectionContext(
85
- buildRasterClickSelectionContext(nextSelection, localPointer),
86
- );
87
- setAgentModalAnchorPoint({ x: e.clientX, y: e.clientY });
88
- void preloadAgentPromptSnippet(nextSelection);
89
- setAgentModalOpen(true);
90
- }
91
58
  },
92
59
  [
93
60
  applyDomSelection,
94
61
  captionEditMode,
95
62
  compositionLoading,
96
63
  onClickToSource,
97
- preloadAgentPromptSnippet,
98
64
  resolveDomSelectionFromPreviewPoint,
99
- previewIframeRef,
100
- setAgentModalAnchorPoint,
101
- setAgentModalOpen,
102
- setAgentPromptSelectionContext,
103
65
  ],
104
66
  );
105
67
 
@@ -2,10 +2,7 @@ import { useCallback, useRef } from "react";
2
2
  import type { TimelineElement } from "../player";
3
3
  import { usePlayerStore } from "../player";
4
4
  import { applyPatchByTarget, readAttributeByTarget } from "../utils/sourcePatcher";
5
- import {
6
- buildTrackZIndexMap,
7
- formatTimelineAttributeNumber,
8
- } from "../player/components/timelineEditing";
5
+ import { formatTimelineAttributeNumber } from "../player/components/timelineEditing";
9
6
  import {
10
7
  buildTimelineAssetId,
11
8
  buildTimelineAssetInsertHtml,
@@ -101,16 +98,6 @@ export function useTimelineEditing({
101
98
  throw new Error(`Timeline element ${element.id} is missing a patchable target`);
102
99
  }
103
100
 
104
- const resolvedTargetPath = targetPath || "index.html";
105
- const relevantElements = timelineElements
106
- .map((te) =>
107
- (te.key ?? te.id) === (element.key ?? element.id)
108
- ? { ...te, start: updates.start, track: updates.track }
109
- : te,
110
- )
111
- .filter((te) => (te.sourceFile || activeCompPath || "index.html") === resolvedTargetPath);
112
- const trackZIndices = buildTrackZIndexMap(relevantElements.map((te) => te.track));
113
-
114
101
  let patchedContent = applyPatchByTarget(originalContent, patchTarget, {
115
102
  type: "attribute",
116
103
  property: "start",
@@ -121,17 +108,6 @@ export function useTimelineEditing({
121
108
  property: "track-index",
122
109
  value: String(updates.track),
123
110
  });
124
- for (const te of relevantElements) {
125
- const elementTarget = buildPatchTarget(te);
126
- if (!elementTarget) continue;
127
- const nextZIndex = trackZIndices.get(te.track);
128
- if (nextZIndex == null) continue;
129
- patchedContent = applyPatchByTarget(patchedContent, elementTarget, {
130
- type: "inline-style",
131
- property: "z-index",
132
- value: String(nextZIndex),
133
- });
134
- }
135
111
 
136
112
  if (patchedContent === originalContent) {
137
113
  throw new Error(`Unable to patch timeline element ${element.id} in ${targetPath}`);
@@ -150,14 +126,7 @@ export function useTimelineEditing({
150
126
 
151
127
  reloadPreview();
152
128
  },
153
- [
154
- activeCompPath,
155
- recordEdit,
156
- timelineElements,
157
- writeProjectFile,
158
- domEditSaveTimestampRef,
159
- reloadPreview,
160
- ],
129
+ [activeCompPath, recordEdit, writeProjectFile, domEditSaveTimestampRef, reloadPreview],
161
130
  );
162
131
 
163
132
  const handleTimelineElementResize = useCallback(
@@ -247,14 +216,6 @@ export function useTimelineEditing({
247
216
  throw new Error(`Timeline element ${element.id} is missing a patchable target`);
248
217
  }
249
218
 
250
- const resolvedTargetPath = targetPath || "index.html";
251
- const remainingElements = timelineElements.filter(
252
- (te) =>
253
- (te.key ?? te.id) !== (element.key ?? element.id) &&
254
- (te.sourceFile || activeCompPath || "index.html") === resolvedTargetPath,
255
- );
256
- const trackZIndices = buildTrackZIndexMap(remainingElements.map((te) => te.track));
257
-
258
219
  const removeResponse = await fetch(
259
220
  `/api/projects/${pid}/file-mutations/remove-element/${encodeURIComponent(targetPath)}`,
260
221
  {
@@ -271,19 +232,8 @@ export function useTimelineEditing({
271
232
  changed?: boolean;
272
233
  content?: string;
273
234
  };
274
- let patchedContent =
235
+ const patchedContent =
275
236
  typeof removeData.content === "string" ? removeData.content : originalContent;
276
- for (const te of remainingElements) {
277
- const elementTarget = buildPatchTarget(te);
278
- if (!elementTarget) continue;
279
- const nextZIndex = trackZIndices.get(te.track);
280
- if (nextZIndex == null) continue;
281
- patchedContent = applyPatchByTarget(patchedContent, elementTarget, {
282
- type: "inline-style",
283
- property: "z-index",
284
- value: String(nextZIndex),
285
- });
286
- }
287
237
 
288
238
  domEditSaveTimestampRef.current = Date.now();
289
239
  await saveProjectFilesWithHistory({
@@ -352,26 +302,10 @@ export function useTimelineEditing({
352
302
  const relevantElements = timelineElements.filter(
353
303
  (te) => (te.sourceFile || activeCompPath || "index.html") === resolvedTargetPath,
354
304
  );
355
- const trackZIndices = buildTrackZIndexMap([
356
- ...relevantElements.map((te) => te.track),
357
- placement.track,
358
- ]);
359
-
360
- let patchedContent = originalContent;
361
- for (const te of relevantElements) {
362
- const elementTarget = buildPatchTarget(te);
363
- if (!elementTarget) continue;
364
- const nextZIndex = trackZIndices.get(te.track);
365
- if (nextZIndex == null) continue;
366
- patchedContent = applyPatchByTarget(patchedContent, elementTarget, {
367
- type: "inline-style",
368
- property: "z-index",
369
- value: String(nextZIndex),
370
- });
371
- }
305
+ const newElementZIndex = Math.max(1, relevantElements.length + 1);
372
306
 
373
- patchedContent = insertTimelineAssetIntoSource(
374
- patchedContent,
307
+ const patchedContent = insertTimelineAssetIntoSource(
308
+ originalContent,
375
309
  buildTimelineAssetInsertHtml({
376
310
  id: newId,
377
311
  assetPath: resolvedAssetSrc,
@@ -379,7 +313,7 @@ export function useTimelineEditing({
379
313
  start: normalizedStart,
380
314
  duration: normalizedDuration,
381
315
  track: placement.track,
382
- zIndex: trackZIndices.get(placement.track) ?? 1,
316
+ zIndex: newElementZIndex,
383
317
  geometry: resolveTimelineAssetInitialGeometry(originalContent),
384
318
  }),
385
319
  );