@hyperframes/studio 0.7.99 → 0.7.101

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.
Files changed (35) hide show
  1. package/dist/assets/{hyperframes-player-BY5RLMBA.js → hyperframes-player-BxSY8bs4.js} +1 -1
  2. package/dist/assets/{index-CQ07_DLG.js → index-BlRPDw0A.js} +1 -1
  3. package/dist/assets/{index-DMSqmZM6.js → index-DF06yGPO.js} +200 -200
  4. package/dist/assets/{index-DeXLAktv.js → index-Dkz5RbFX.js} +1 -1
  5. package/dist/index.html +1 -1
  6. package/dist/index.js +473 -359
  7. package/dist/index.js.map +1 -1
  8. package/package.json +7 -7
  9. package/src/App.tsx +2 -2
  10. package/src/components/StudioHeader.test.ts +28 -0
  11. package/src/components/StudioHeader.tsx +21 -2
  12. package/src/components/StudioLeftSidebar.tsx +2 -2
  13. package/src/components/nle/NLEContext.tsx +16 -7
  14. package/src/components/nle/TimelineResizeDivider.tsx +4 -11
  15. package/src/components/sidebar/LeftSidebar.storage.test.ts +28 -0
  16. package/src/components/sidebar/LeftSidebar.tsx +18 -3
  17. package/src/contexts/PanelLayoutContext.tsx +6 -3
  18. package/src/hooks/gsapEditOutcome.ts +19 -1
  19. package/src/hooks/gsapKeyframeCacheHelpers.test.ts +47 -1
  20. package/src/hooks/gsapKeyframeCacheHelpers.ts +31 -3
  21. package/src/hooks/gsapResizeGeometrySweep.test.ts +289 -0
  22. package/src/hooks/gsapResizeIntercept.test.ts +2 -2
  23. package/src/hooks/gsapResizeIntercept.ts +37 -14
  24. package/src/hooks/gsapResizeMixedTween.test.ts +168 -0
  25. package/src/hooks/gsapResizeSweep.test.ts +274 -0
  26. package/src/hooks/useGsapAwareEditing.test.tsx +19 -2
  27. package/src/hooks/useGsapAwareEditing.ts +13 -5
  28. package/src/hooks/useGsapTweenCache.ts +16 -19
  29. package/src/hooks/usePanelLayout.test.ts +117 -0
  30. package/src/hooks/usePanelLayout.ts +121 -39
  31. package/src/player/components/Player.test.ts +13 -0
  32. package/src/player/components/Player.tsx +7 -1
  33. package/src/utils/clipboard.ts +1 -1
  34. package/src/utils/fitPanels.test.ts +164 -0
  35. package/src/utils/fitPanels.ts +151 -0
package/dist/index.js CHANGED
@@ -32,7 +32,7 @@ import {
32
32
  import { useCallback as useCallback50 } from "react";
33
33
 
34
34
  // src/components/nle/PreviewPane.tsx
35
- import { useCallback as useCallback35, useRef as useRef38, useSyncExternalStore as useSyncExternalStore3 } from "react";
35
+ import { useCallback as useCallback34, useRef as useRef37, useSyncExternalStore as useSyncExternalStore3 } from "react";
36
36
 
37
37
  // src/player/components/Player.tsx
38
38
  import { forwardRef as forwardRef2, useEffect as useEffect3, useRef as useRef2, useState as useState2 } from "react";
@@ -621,7 +621,7 @@ var Player = forwardRef2(
621
621
  player.removeEventListener("error", handleError);
622
622
  if (assetPollRef.current) clearInterval(assetPollRef.current);
623
623
  assetPollRef.current = null;
624
- container.removeChild(player);
624
+ player.remove();
625
625
  if (retryPreviewRef.current === retryPreview) retryPreviewRef.current = null;
626
626
  if (typeof ref === "function") {
627
627
  } else if (ref) {
@@ -15465,7 +15465,7 @@ function copyWithSelection(text) {
15465
15465
  } catch {
15466
15466
  return false;
15467
15467
  } finally {
15468
- document.body.removeChild(textarea);
15468
+ textarea.remove();
15469
15469
  }
15470
15470
  }
15471
15471
  function shouldCopyWithSelectionFirst() {
@@ -21608,8 +21608,8 @@ import {
21608
21608
  createContext as createContext5,
21609
21609
  useContext as useContext5,
21610
21610
  useState as useState23,
21611
- useCallback as useCallback33,
21612
- useRef as useRef37,
21611
+ useCallback as useCallback32,
21612
+ useRef as useRef36,
21613
21613
  useEffect as useEffect26
21614
21614
  } from "react";
21615
21615
 
@@ -21710,94 +21710,57 @@ function useCompositionStack({
21710
21710
  };
21711
21711
  }
21712
21712
 
21713
- // src/components/nle/TimelineResizeDivider.tsx
21714
- import { useCallback as useCallback32, useRef as useRef36 } from "react";
21715
- import { jsx as jsx49, jsxs as jsxs37 } from "react/jsx-runtime";
21713
+ // src/utils/fitPanels.ts
21714
+ var MIN_PREVIEW_W = 360;
21715
+ var MIN_PREVIEW_H = 200;
21716
+ var MIN_LEFT = 200;
21717
+ var MIN_RIGHT = 280;
21718
+ var RAIL_W = 42;
21716
21719
  var MIN_TIMELINE_H = 100;
21717
- var MIN_PREVIEW_H = 120;
21718
- function TimelineResizeDivider({
21719
- timelineH,
21720
- setTimelineH,
21721
- persistTimelineH,
21722
- containerRef,
21723
- disabled
21724
- }) {
21725
- const isDragging = useRef36(false);
21726
- const timelineHRef = useRef36(timelineH);
21727
- timelineHRef.current = timelineH;
21728
- const handlePointerDown = useCallback32(
21729
- (e) => {
21730
- if (disabled) return;
21731
- e.preventDefault();
21732
- isDragging.current = true;
21733
- e.target.setPointerCapture(e.pointerId);
21734
- },
21735
- [disabled]
21736
- );
21737
- const handlePointerMove = useCallback32(
21738
- (e) => {
21739
- if (disabled) return;
21740
- if (!isDragging.current || !containerRef.current) return;
21741
- const rect = containerRef.current.getBoundingClientRect();
21742
- const mouseY = e.clientY - rect.top;
21743
- const containerH = rect.height;
21744
- const newTimelineH = Math.max(
21745
- MIN_TIMELINE_H,
21746
- Math.min(containerH - MIN_PREVIEW_H, containerH - mouseY)
21747
- );
21748
- setTimelineH(newTimelineH);
21749
- },
21750
- [disabled, containerRef, setTimelineH]
21751
- );
21752
- const handlePointerUp = useCallback32(() => {
21753
- if (isDragging.current) persistTimelineH(timelineHRef.current);
21754
- isDragging.current = false;
21755
- }, [persistTimelineH]);
21756
- const handleKeyDown = useCallback32(
21757
- (e) => {
21758
- if (disabled) return;
21759
- if (e.key !== "ArrowUp" && e.key !== "ArrowDown") return;
21760
- e.preventDefault();
21761
- const containerH = containerRef.current?.getBoundingClientRect().height ?? Infinity;
21762
- const delta = e.key === "ArrowUp" ? 16 : -16;
21763
- setTimelineH((prev) => {
21764
- const next = Math.max(MIN_TIMELINE_H, Math.min(containerH - MIN_PREVIEW_H, prev + delta));
21765
- persistTimelineH(next);
21766
- return next;
21767
- });
21768
- },
21769
- [disabled, containerRef, setTimelineH, persistTimelineH]
21770
- );
21771
- return (
21772
- // Horizontal resize divider: 3px visible seam (h-[3px]), 10px pointer-capture
21773
- // zone via the absolutely-positioned inner hit area so the layout gap stays
21774
- // at 3px while draggability is preserved over the full band.
21775
- /* @__PURE__ */ jsxs37(
21776
- "div",
21777
- {
21778
- role: "separator",
21779
- "aria-orientation": "horizontal",
21780
- "aria-label": "Resize timeline (arrow keys)",
21781
- "aria-valuenow": Math.round(timelineH),
21782
- "aria-valuemin": MIN_TIMELINE_H,
21783
- "aria-valuemax": Math.round(
21784
- (containerRef.current?.getBoundingClientRect().height ?? 600) - MIN_PREVIEW_H
21785
- ),
21786
- tabIndex: 0,
21787
- className: "group relative h-[3px] flex-shrink-0 cursor-row-resize z-10 outline-none focus-visible:bg-studio-accent/20",
21788
- style: { touchAction: "none" },
21789
- onPointerDown: handlePointerDown,
21790
- onPointerMove: handlePointerMove,
21791
- onPointerUp: handlePointerUp,
21792
- onPointerCancel: handlePointerUp,
21793
- onKeyDown: handleKeyDown,
21794
- children: [
21795
- /* @__PURE__ */ jsx49("div", { className: "absolute inset-x-0 -top-[4px] h-[10px]" }),
21796
- /* @__PURE__ */ jsx49("div", { className: "h-[3px] w-full bg-transparent transition-colors group-hover:bg-white/12 group-active:bg-white/18 group-focus-visible:bg-studio-accent/60" })
21797
- ]
21798
- }
21799
- )
21800
- );
21720
+ var PANEL_SEAM_W = 6;
21721
+ var PANEL_MAX_RATIO = 0.4;
21722
+ var RAIL_LEFT_BELOW = 860;
21723
+ var RAIL_RIGHT_BELOW = 700;
21724
+ function railsEngaged(viewportWidth) {
21725
+ return viewportWidth < RAIL_LEFT_BELOW;
21726
+ }
21727
+ function clamp5(value, min, max) {
21728
+ return Math.max(min, Math.min(max, value));
21729
+ }
21730
+ function defaultPanelWidths(viewportWidth) {
21731
+ return {
21732
+ left: Math.max(MIN_LEFT, Math.min(384, Math.round(viewportWidth * 0.257))),
21733
+ right: Math.max(MIN_RIGHT, Math.min(424, Math.round(viewportWidth * 0.284)))
21734
+ };
21735
+ }
21736
+ function fitPanelWidths(viewportWidth, preferred, reopened = { left: false, right: false }) {
21737
+ const vw = Number.isFinite(viewportWidth) ? Math.max(0, viewportWidth) : 0;
21738
+ if (vw <= 0) {
21739
+ return { ...preferred, preview: 0, autoCollapseLeft: false, autoCollapseRight: false };
21740
+ }
21741
+ const autoCollapseLeft = vw < RAIL_LEFT_BELOW && !reopened.left;
21742
+ const autoCollapseRight = vw < RAIL_RIGHT_BELOW && !reopened.right;
21743
+ const cap = Math.floor(vw * PANEL_MAX_RATIO);
21744
+ const leftFloor = autoCollapseLeft ? RAIL_W : MIN_LEFT;
21745
+ const rightFloor = autoCollapseRight ? 0 : MIN_RIGHT;
21746
+ let left = autoCollapseLeft ? RAIL_W : clamp5(preferred.left, MIN_LEFT, Math.max(MIN_LEFT, cap));
21747
+ let right = autoCollapseRight ? 0 : clamp5(preferred.right, MIN_RIGHT, Math.max(MIN_RIGHT, cap));
21748
+ const budget = vw - MIN_PREVIEW_W - PANEL_SEAM_W;
21749
+ if (left + right > budget) right = Math.max(rightFloor, budget - left);
21750
+ if (left + right > budget) left = Math.max(leftFloor, budget - right);
21751
+ return {
21752
+ left,
21753
+ right,
21754
+ preview: Math.max(0, vw - left - right - PANEL_SEAM_W),
21755
+ autoCollapseLeft,
21756
+ autoCollapseRight
21757
+ };
21758
+ }
21759
+ function fitTimelineHeight(containerHeight, preferred) {
21760
+ const h = Number.isFinite(containerHeight) ? containerHeight : 0;
21761
+ const wanted = Number.isFinite(preferred) ? preferred : MIN_TIMELINE_H;
21762
+ if (h <= 0) return Math.max(MIN_TIMELINE_H, wanted);
21763
+ return clamp5(wanted, MIN_TIMELINE_H, Math.max(MIN_TIMELINE_H, h - MIN_PREVIEW_H));
21801
21764
  }
21802
21765
 
21803
21766
  // src/utils/assetPreviewStore.ts
@@ -21810,7 +21773,7 @@ var useAssetPreviewStore = create4((set) => ({
21810
21773
  }));
21811
21774
 
21812
21775
  // src/components/nle/NLEContext.tsx
21813
- import { jsx as jsx50 } from "react/jsx-runtime";
21776
+ import { jsx as jsx49 } from "react/jsx-runtime";
21814
21777
  var DEFAULT_TIMELINE_H = 360;
21815
21778
  function shouldDisableTimelineWhileCompositionLoading(compositionLoading) {
21816
21779
  return compositionLoading;
@@ -21843,13 +21806,13 @@ function NLEProvider({
21843
21806
  useAssetPreviewStore.getState().clearPreviewAsset();
21844
21807
  }, [projectId]);
21845
21808
  const [previewCompositionSize, setPreviewCompositionSize] = useState23(null);
21846
- const prevRefreshKeyRef = useRef37(refreshKey);
21809
+ const prevRefreshKeyRef = useRef36(refreshKey);
21847
21810
  useEffect26(() => {
21848
21811
  if (refreshKey === prevRefreshKeyRef.current) return;
21849
21812
  prevRefreshKeyRef.current = refreshKey;
21850
21813
  refreshPlayer();
21851
21814
  }, [refreshKey, refreshPlayer]);
21852
- const onIframeLoad = useCallback33(() => {
21815
+ const onIframeLoad = useCallback32(() => {
21853
21816
  baseOnIframeLoad();
21854
21817
  ensureMotionPathPluginLoaded(iframeRef.current);
21855
21818
  onIframeRef?.(iframeRef.current);
@@ -21867,7 +21830,7 @@ function NLEProvider({
21867
21830
  onCompositionChange
21868
21831
  });
21869
21832
  const iframeRef_ = iframeRef;
21870
- const handleDrillDown = useCallback33(
21833
+ const handleDrillDown = useCallback32(
21871
21834
  (element) => {
21872
21835
  if (!element.compositionSrc) return;
21873
21836
  usePlayerStore.getState().setSelectedElementId(null);
@@ -21894,9 +21857,9 @@ function NLEProvider({
21894
21857
  },
21895
21858
  [compIdToSrc, drillDown, iframeRef_]
21896
21859
  );
21897
- const compIdToSrcRef = useRef37(compIdToSrc);
21860
+ const compIdToSrcRef = useRef36(compIdToSrc);
21898
21861
  compIdToSrcRef.current = compIdToSrc;
21899
- const onCompIdToSrcChangeRef = useRef37(onCompIdToSrcChange);
21862
+ const onCompIdToSrcChangeRef = useRef36(onCompIdToSrcChange);
21900
21863
  onCompIdToSrcChangeRef.current = onCompIdToSrcChange;
21901
21864
  useEffect26(() => {
21902
21865
  const controller = new AbortController();
@@ -21967,19 +21930,26 @@ function NLEProvider({
21967
21930
  const stored = readStudioUiPreferences().timelineHeight;
21968
21931
  return stored !== void 0 && stored >= MIN_TIMELINE_H ? stored : DEFAULT_TIMELINE_H;
21969
21932
  });
21970
- const persistTimelineH = useCallback33((height) => {
21933
+ const persistTimelineH = useCallback32((height) => {
21971
21934
  writeStudioUiPreferences({ timelineHeight: Math.round(height) });
21972
21935
  }, []);
21973
- const containerRef = useRef37(null);
21936
+ const containerRef = useRef36(null);
21974
21937
  useEffect26(() => {
21975
- const containerH = containerRef.current?.getBoundingClientRect().height;
21976
- if (!containerH) return;
21977
- const max = containerH - MIN_PREVIEW_H;
21978
- setTimelineH((prev) => prev > max ? Math.max(MIN_TIMELINE_H, max) : prev);
21938
+ const element = containerRef.current;
21939
+ if (!element || typeof ResizeObserver === "undefined") return;
21940
+ const reconcile = () => {
21941
+ const containerH = element.getBoundingClientRect().height;
21942
+ if (!containerH) return;
21943
+ setTimelineH((prev) => fitTimelineHeight(containerH, prev));
21944
+ };
21945
+ reconcile();
21946
+ const observer = new ResizeObserver(reconcile);
21947
+ observer.observe(element);
21948
+ return () => observer.disconnect();
21979
21949
  }, []);
21980
- const hasLoadedOnceRef = useRef37(false);
21950
+ const hasLoadedOnceRef = useRef36(false);
21981
21951
  const [compositionLoading, setCompositionLoadingRaw] = useState23(true);
21982
- const setCompositionLoading = useCallback33((loading) => {
21952
+ const setCompositionLoading = useCallback32((loading) => {
21983
21953
  if (!loading) hasLoadedOnceRef.current = true;
21984
21954
  if (loading && hasLoadedOnceRef.current) return;
21985
21955
  setCompositionLoadingRaw(loading);
@@ -21989,7 +21959,7 @@ function NLEProvider({
21989
21959
  useEffect26(() => {
21990
21960
  onCompositionLoadingChange?.(compositionLoading);
21991
21961
  }, [compositionLoading, onCompositionLoadingChange]);
21992
- const onIframeRefStable = useRef37(onIframeRef);
21962
+ const onIframeRefStable = useRef36(onIframeRef);
21993
21963
  onIframeRefStable.current = onIframeRef;
21994
21964
  useEffect26(() => {
21995
21965
  onIframeRefStable.current?.(iframeRef.current);
@@ -22018,11 +21988,11 @@ function NLEProvider({
22018
21988
  previewCompositionSize,
22019
21989
  setPreviewCompositionSize
22020
21990
  };
22021
- return /* @__PURE__ */ jsx50(NLEContext.Provider, { value, children });
21991
+ return /* @__PURE__ */ jsx49(NLEContext.Provider, { value, children });
22022
21992
  }
22023
21993
 
22024
21994
  // src/components/nle/AssetPreviewOverlay.tsx
22025
- import { useEffect as useEffect27, useCallback as useCallback34 } from "react";
21995
+ import { useEffect as useEffect27, useCallback as useCallback33 } from "react";
22026
21996
 
22027
21997
  // src/utils/assetPreviewDismiss.ts
22028
21998
  var TIME_EPSILON_S = 1e-6;
@@ -22033,7 +22003,7 @@ function shouldDismissAssetPreview(openedTime, snapshot) {
22033
22003
  }
22034
22004
 
22035
22005
  // src/components/nle/AssetPreviewOverlay.tsx
22036
- import { jsx as jsx51, jsxs as jsxs38 } from "react/jsx-runtime";
22006
+ import { jsx as jsx50, jsxs as jsxs37 } from "react/jsx-runtime";
22037
22007
  function basename(path) {
22038
22008
  return path.split("/").pop() ?? path;
22039
22009
  }
@@ -22048,10 +22018,10 @@ function AssetPreviewMedia({
22048
22018
  name
22049
22019
  }) {
22050
22020
  if (kind === "image") {
22051
- return /* @__PURE__ */ jsx51("img", { src: serveUrl, alt: name, className: "max-w-full max-h-[40vh] rounded object-contain" });
22021
+ return /* @__PURE__ */ jsx50("img", { src: serveUrl, alt: name, className: "max-w-full max-h-[40vh] rounded object-contain" });
22052
22022
  }
22053
22023
  if (kind === "video") {
22054
- return /* @__PURE__ */ jsx51(
22024
+ return /* @__PURE__ */ jsx50(
22055
22025
  "video",
22056
22026
  {
22057
22027
  src: serveUrl,
@@ -22063,8 +22033,8 @@ function AssetPreviewMedia({
22063
22033
  }
22064
22034
  );
22065
22035
  }
22066
- return /* @__PURE__ */ jsxs38("div", { className: "flex flex-col items-center gap-3 px-6 py-4", children: [
22067
- /* @__PURE__ */ jsxs38(
22036
+ return /* @__PURE__ */ jsxs37("div", { className: "flex flex-col items-center gap-3 px-6 py-4", children: [
22037
+ /* @__PURE__ */ jsxs37(
22068
22038
  "svg",
22069
22039
  {
22070
22040
  width: "40",
@@ -22075,20 +22045,20 @@ function AssetPreviewMedia({
22075
22045
  strokeWidth: "1.5",
22076
22046
  className: "text-neutral-500",
22077
22047
  children: [
22078
- /* @__PURE__ */ jsx51("path", { d: "M9 18V5l12-2v13", strokeLinecap: "round", strokeLinejoin: "round" }),
22079
- /* @__PURE__ */ jsx51("circle", { cx: "6", cy: "18", r: "3" }),
22080
- /* @__PURE__ */ jsx51("circle", { cx: "18", cy: "16", r: "3" })
22048
+ /* @__PURE__ */ jsx50("path", { d: "M9 18V5l12-2v13", strokeLinecap: "round", strokeLinejoin: "round" }),
22049
+ /* @__PURE__ */ jsx50("circle", { cx: "6", cy: "18", r: "3" }),
22050
+ /* @__PURE__ */ jsx50("circle", { cx: "18", cy: "16", r: "3" })
22081
22051
  ]
22082
22052
  }
22083
22053
  ),
22084
- /* @__PURE__ */ jsx51("audio", { src: serveUrl, controls: true, className: "w-64" })
22054
+ /* @__PURE__ */ jsx50("audio", { src: serveUrl, controls: true, className: "w-64" })
22085
22055
  ] });
22086
22056
  }
22087
22057
  function AssetPreviewOverlay() {
22088
22058
  const previewAsset = useAssetPreviewStore((s) => s.previewAsset);
22089
22059
  const previewProjectId = useAssetPreviewStore((s) => s.previewProjectId);
22090
22060
  const clearPreviewAsset = useAssetPreviewStore((s) => s.clearPreviewAsset);
22091
- const handleKeyDown = useCallback34(
22061
+ const handleKeyDown = useCallback33(
22092
22062
  (e) => {
22093
22063
  if (e.key === "Escape") clearPreviewAsset();
22094
22064
  },
@@ -22114,20 +22084,20 @@ function AssetPreviewOverlay() {
22114
22084
  if (!previewAsset || !previewProjectId) return null;
22115
22085
  const serveUrl = resolveMediaPreviewUrl(previewAsset, previewProjectId);
22116
22086
  const name = basename(previewAsset);
22117
- return /* @__PURE__ */ jsx51(
22087
+ return /* @__PURE__ */ jsx50(
22118
22088
  "div",
22119
22089
  {
22120
22090
  className: "absolute inset-0 z-50 flex flex-col items-center justify-center bg-black/20",
22121
22091
  onClick: clearPreviewAsset,
22122
22092
  role: "dialog",
22123
22093
  "aria-label": `Preview: ${name}`,
22124
- children: /* @__PURE__ */ jsxs38(
22094
+ children: /* @__PURE__ */ jsxs37(
22125
22095
  "div",
22126
22096
  {
22127
22097
  className: "relative flex flex-col items-center gap-2 max-w-[58%] rounded-lg border border-neutral-800 bg-neutral-950/95 p-2 pt-8 shadow-2xl",
22128
22098
  onClick: (e) => e.stopPropagation(),
22129
22099
  children: [
22130
- /* @__PURE__ */ jsx51(
22100
+ /* @__PURE__ */ jsx50(
22131
22101
  "button",
22132
22102
  {
22133
22103
  className: "absolute top-2 right-2 w-6 h-6 rounded-full bg-neutral-800 hover:bg-neutral-700 text-neutral-300 hover:text-white flex items-center justify-center transition-colors z-10",
@@ -22136,7 +22106,7 @@ function AssetPreviewOverlay() {
22136
22106
  clearPreviewAsset();
22137
22107
  },
22138
22108
  "aria-label": "Close preview",
22139
- children: /* @__PURE__ */ jsxs38(
22109
+ children: /* @__PURE__ */ jsxs37(
22140
22110
  "svg",
22141
22111
  {
22142
22112
  width: "12",
@@ -22147,15 +22117,15 @@ function AssetPreviewOverlay() {
22147
22117
  fill: "none",
22148
22118
  strokeLinecap: "round",
22149
22119
  children: [
22150
- /* @__PURE__ */ jsx51("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
22151
- /* @__PURE__ */ jsx51("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
22120
+ /* @__PURE__ */ jsx50("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
22121
+ /* @__PURE__ */ jsx50("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
22152
22122
  ]
22153
22123
  }
22154
22124
  )
22155
22125
  }
22156
22126
  ),
22157
- /* @__PURE__ */ jsx51(AssetPreviewMedia, { kind: resolveAssetKind(previewAsset), serveUrl, name }),
22158
- /* @__PURE__ */ jsx51("span", { className: "text-[12px] text-neutral-400 truncate max-w-full px-2 text-center", children: name })
22127
+ /* @__PURE__ */ jsx50(AssetPreviewMedia, { kind: resolveAssetKind(previewAsset), serveUrl, name }),
22128
+ /* @__PURE__ */ jsx50("span", { className: "text-[12px] text-neutral-400 truncate max-w-full px-2 text-center", children: name })
22159
22129
  ]
22160
22130
  }
22161
22131
  )
@@ -22164,7 +22134,7 @@ function AssetPreviewOverlay() {
22164
22134
  }
22165
22135
 
22166
22136
  // src/components/nle/PreviewPane.tsx
22167
- import { jsx as jsx52, jsxs as jsxs39 } from "react/jsx-runtime";
22137
+ import { jsx as jsx51, jsxs as jsxs38 } from "react/jsx-runtime";
22168
22138
  function subscribeFullscreen(cb) {
22169
22139
  document.addEventListener("fullscreenchange", cb);
22170
22140
  return () => document.removeEventListener("fullscreenchange", cb);
@@ -22199,8 +22169,8 @@ function PreviewPane({
22199
22169
  previewCompositionSize,
22200
22170
  setPreviewCompositionSize
22201
22171
  } = useNLEContext();
22202
- const stageRefForDrop = useRef38(null);
22203
- const handleStageRef = useCallback35((ref) => {
22172
+ const stageRefForDrop = useRef37(null);
22173
+ const handleStageRef = useCallback34((ref) => {
22204
22174
  stageRefForDrop.current = ref.current;
22205
22175
  }, []);
22206
22176
  const {
@@ -22215,10 +22185,10 @@ function PreviewPane({
22215
22185
  stageRef: stageRefForDrop,
22216
22186
  onBlockDrop: onPreviewBlockDrop
22217
22187
  });
22218
- const containerRef = useRef38(null);
22188
+ const containerRef = useRef37(null);
22219
22189
  const fullscreenElement = useSyncExternalStore3(subscribeFullscreen, getFullscreenElement);
22220
22190
  const isFullscreen = fullscreenElement === containerRef.current && fullscreenElement != null;
22221
- const toggleFullscreen = useCallback35(() => {
22191
+ const toggleFullscreen = useCallback34(() => {
22222
22192
  if (!containerRef.current) return;
22223
22193
  if (document.fullscreenElement) {
22224
22194
  void document.exitFullscreen();
@@ -22228,14 +22198,14 @@ function PreviewPane({
22228
22198
  }, []);
22229
22199
  const currentLevel = compositionStack[compositionStack.length - 1];
22230
22200
  const directUrl = compositionStack.length > 1 ? currentLevel.previewUrl : void 0;
22231
- return /* @__PURE__ */ jsxs39(
22201
+ return /* @__PURE__ */ jsxs38(
22232
22202
  "div",
22233
22203
  {
22234
22204
  ref: containerRef,
22235
22205
  className: `flex-1 min-h-0 flex flex-col overflow-hidden bg-neutral-950 ${isFullscreen ? "" : "rounded-lg border border-neutral-800/50"}`,
22236
22206
  "data-studio-fullscreen-target": "",
22237
22207
  children: [
22238
- /* @__PURE__ */ jsxs39(
22208
+ /* @__PURE__ */ jsxs38(
22239
22209
  "div",
22240
22210
  {
22241
22211
  className: "flex-1 min-h-0 relative overflow-hidden",
@@ -22246,8 +22216,8 @@ function PreviewPane({
22246
22216
  onDragLeave: handlePreviewDragLeave,
22247
22217
  onDrop: handlePreviewDrop,
22248
22218
  children: [
22249
- /* @__PURE__ */ jsxs39("div", { className: "absolute inset-0 overflow-hidden", children: [
22250
- /* @__PURE__ */ jsx52(
22219
+ /* @__PURE__ */ jsxs38("div", { className: "absolute inset-0 overflow-hidden", children: [
22220
+ /* @__PURE__ */ jsx51(
22251
22221
  NLEPreview,
22252
22222
  {
22253
22223
  projectId,
@@ -22261,16 +22231,16 @@ function PreviewPane({
22261
22231
  onCompositionSizeChange: setPreviewCompositionSize
22262
22232
  }
22263
22233
  ),
22264
- previewDragOver && /* @__PURE__ */ jsx52("div", { className: "absolute inset-2 z-40 rounded-lg border-2 border-dashed border-studio-accent/50 bg-studio-accent/[0.04] pointer-events-none" }),
22265
- /* @__PURE__ */ jsx52(AssetPreviewOverlay, {})
22234
+ previewDragOver && /* @__PURE__ */ jsx51("div", { className: "absolute inset-2 z-40 rounded-lg border-2 border-dashed border-studio-accent/50 bg-studio-accent/[0.04] pointer-events-none" }),
22235
+ /* @__PURE__ */ jsx51(AssetPreviewOverlay, {})
22266
22236
  ] }),
22267
22237
  !isFullscreen && previewOverlay
22268
22238
  ]
22269
22239
  }
22270
22240
  ),
22271
- /* @__PURE__ */ jsxs39("div", { className: "flex-shrink-0", children: [
22272
- !isFullscreen && compositionStack.length > 1 && /* @__PURE__ */ jsx52(CompositionBreadcrumb, { stack: compositionStack, onNavigate: handleNavigateComposition }),
22273
- /* @__PURE__ */ jsx52(
22241
+ /* @__PURE__ */ jsxs38("div", { className: "flex-shrink-0", children: [
22242
+ !isFullscreen && compositionStack.length > 1 && /* @__PURE__ */ jsx51(CompositionBreadcrumb, { stack: compositionStack, onNavigate: handleNavigateComposition }),
22243
+ /* @__PURE__ */ jsx51(
22274
22244
  PlayerControls,
22275
22245
  {
22276
22246
  onTogglePlay: togglePlay,
@@ -22288,6 +22258,91 @@ function PreviewPane({
22288
22258
 
22289
22259
  // src/components/nle/TimelinePane.tsx
22290
22260
  import { useCallback as useCallback36 } from "react";
22261
+
22262
+ // src/components/nle/TimelineResizeDivider.tsx
22263
+ import { useCallback as useCallback35, useRef as useRef38 } from "react";
22264
+ import { jsx as jsx52, jsxs as jsxs39 } from "react/jsx-runtime";
22265
+ function TimelineResizeDivider({
22266
+ timelineH,
22267
+ setTimelineH,
22268
+ persistTimelineH,
22269
+ containerRef,
22270
+ disabled
22271
+ }) {
22272
+ const isDragging = useRef38(false);
22273
+ const timelineHRef = useRef38(timelineH);
22274
+ timelineHRef.current = timelineH;
22275
+ const handlePointerDown = useCallback35(
22276
+ (e) => {
22277
+ if (disabled) return;
22278
+ e.preventDefault();
22279
+ isDragging.current = true;
22280
+ e.target.setPointerCapture(e.pointerId);
22281
+ },
22282
+ [disabled]
22283
+ );
22284
+ const handlePointerMove = useCallback35(
22285
+ (e) => {
22286
+ if (disabled) return;
22287
+ if (!isDragging.current || !containerRef.current) return;
22288
+ const rect = containerRef.current.getBoundingClientRect();
22289
+ const mouseY = e.clientY - rect.top;
22290
+ setTimelineH(fitTimelineHeight(rect.height, rect.height - mouseY));
22291
+ },
22292
+ [disabled, containerRef, setTimelineH]
22293
+ );
22294
+ const handlePointerUp = useCallback35(() => {
22295
+ if (isDragging.current) persistTimelineH(timelineHRef.current);
22296
+ isDragging.current = false;
22297
+ }, [persistTimelineH]);
22298
+ const handleKeyDown = useCallback35(
22299
+ (e) => {
22300
+ if (disabled) return;
22301
+ if (e.key !== "ArrowUp" && e.key !== "ArrowDown") return;
22302
+ e.preventDefault();
22303
+ const containerH = containerRef.current?.getBoundingClientRect().height ?? 0;
22304
+ const delta = e.key === "ArrowUp" ? 16 : -16;
22305
+ setTimelineH((prev) => {
22306
+ const next = fitTimelineHeight(containerH, prev + delta);
22307
+ persistTimelineH(next);
22308
+ return next;
22309
+ });
22310
+ },
22311
+ [disabled, containerRef, setTimelineH, persistTimelineH]
22312
+ );
22313
+ return (
22314
+ // Horizontal resize divider: 3px visible seam (h-[3px]), 10px pointer-capture
22315
+ // zone via the absolutely-positioned inner hit area so the layout gap stays
22316
+ // at 3px while draggability is preserved over the full band.
22317
+ /* @__PURE__ */ jsxs39(
22318
+ "div",
22319
+ {
22320
+ role: "separator",
22321
+ "aria-orientation": "horizontal",
22322
+ "aria-label": "Resize timeline (arrow keys)",
22323
+ "aria-valuenow": Math.round(timelineH),
22324
+ "aria-valuemin": MIN_TIMELINE_H,
22325
+ "aria-valuemax": Math.round(
22326
+ (containerRef.current?.getBoundingClientRect().height ?? 600) - MIN_PREVIEW_H
22327
+ ),
22328
+ tabIndex: 0,
22329
+ className: "group relative h-[3px] flex-shrink-0 cursor-row-resize z-10 outline-none focus-visible:bg-studio-accent/20",
22330
+ style: { touchAction: "none" },
22331
+ onPointerDown: handlePointerDown,
22332
+ onPointerMove: handlePointerMove,
22333
+ onPointerUp: handlePointerUp,
22334
+ onPointerCancel: handlePointerUp,
22335
+ onKeyDown: handleKeyDown,
22336
+ children: [
22337
+ /* @__PURE__ */ jsx52("div", { className: "absolute inset-x-0 -top-[4px] h-[10px]" }),
22338
+ /* @__PURE__ */ jsx52("div", { className: "h-[3px] w-full bg-transparent transition-colors group-hover:bg-white/12 group-active:bg-white/18 group-focus-visible:bg-studio-accent/60" })
22339
+ ]
22340
+ }
22341
+ )
22342
+ );
22343
+ }
22344
+
22345
+ // src/components/nle/TimelinePane.tsx
22291
22346
  import { Fragment as Fragment15, jsx as jsx53, jsxs as jsxs40 } from "react/jsx-runtime";
22292
22347
  function forwardRebasedTimelineMoveElements(edits, coalesceKey, operation, onMoveElements, coalesceMs) {
22293
22348
  return onMoveElements(
@@ -28696,7 +28751,19 @@ function scopedElementKey(element) {
28696
28751
  return `${element.sourceFile || "index.html"}#${element.id}`;
28697
28752
  }
28698
28753
  function elementCacheKeys(sourceFile, elementId2) {
28699
- return sourceFile === "index.html" ? [`index.html#${elementId2}`, elementId2] : [`${sourceFile}#${elementId2}`, `index.html#${elementId2}`, elementId2];
28754
+ const id = typeof elementId2 === "string" ? elementId2 : coerceCacheKeyId(elementId2, sourceFile);
28755
+ return sourceFile === "index.html" ? [`index.html#${id}`, id] : [`${sourceFile}#${id}`, `index.html#${id}`, id];
28756
+ }
28757
+ function coerceCacheKeyId(elementId2, sourceFile) {
28758
+ trackStudioEvent("cache_key_non_string", {
28759
+ value_type: typeof elementId2,
28760
+ // An Element lands here as "HTMLDivElement", a boxed id as "Number" — enough
28761
+ // to name the producer without shipping user content to telemetry.
28762
+ constructor_name: elementId2?.constructor?.name ?? null,
28763
+ is_array: Array.isArray(elementId2),
28764
+ source_file: sourceFile
28765
+ });
28766
+ return String(elementId2);
28700
28767
  }
28701
28768
  function replaceKeyframeCacheForFile(sourceFile, entries, animationsByElement) {
28702
28769
  publishKeyframeCache((draft) => {
@@ -33452,7 +33519,9 @@ function useGsapAnimationsForElement(projectId, sourceFile, target, version, ifr
33452
33519
  }
33453
33520
  if (allKeyframes.length === 0) {
33454
33521
  const { keyframeCache } = usePlayerStore.getState();
33455
- const hasCached = keyframeCache.has(`${sourceFile}#${elementId2}`) || keyframeCache.has(elementId2);
33522
+ const hasCached = elementCacheKeys(sourceFile, elementId2).some(
33523
+ (key2) => keyframeCache.has(key2)
33524
+ );
33456
33525
  if (!hasCached) clearKeyframeCacheForElement(sourceFile, elementId2);
33457
33526
  return;
33458
33527
  }
@@ -33464,8 +33533,9 @@ function useGsapAnimationsForElement(projectId, sourceFile, target, version, ifr
33464
33533
  ...easeEach ? { easeEach } : {}
33465
33534
  };
33466
33535
  publishKeyframeCache((draft) => {
33467
- draft.keyframeCache.set(`${sourceFile}#${elementId2}`, merged);
33468
- draft.keyframeCache.set(elementId2, merged);
33536
+ for (const key2 of elementCacheKeys(sourceFile, elementId2)) {
33537
+ draft.keyframeCache.set(key2, merged);
33538
+ }
33469
33539
  });
33470
33540
  }, [elementId2, sourceFile, animations, domClipChildrenKey]);
33471
33541
  return { animations, multipleTimelines, unsupportedTimelinePattern };
@@ -33519,10 +33589,8 @@ function usePopulateKeyframeCacheForFile(projectId, sourceFile, version, iframeR
33519
33589
  if (scanned.size === 0) return false;
33520
33590
  publishKeyframeCache((draft) => {
33521
33591
  for (const [id, data] of scanned) {
33522
- const cacheKey = `${sf}#${id}`;
33523
- const fallbackKey = `index.html#${id}`;
33524
- const alreadyCached = draft.keyframeCache.has(cacheKey) || draft.keyframeCache.has(fallbackKey) || draft.keyframeCache.has(id);
33525
- if (alreadyCached) continue;
33592
+ const keys = elementCacheKeys(sf, id);
33593
+ if (keys.some((key2) => draft.keyframeCache.has(key2))) continue;
33526
33594
  const isPosOnly = data.keyframes.length === 1 && Object.keys(data.keyframes[0].properties).every((k) => k === "x" || k === "y");
33527
33595
  if (isPosOnly) {
33528
33596
  continue;
@@ -33532,9 +33600,7 @@ function usePopulateKeyframeCacheForFile(projectId, sourceFile, version, iframeR
33532
33600
  keyframes: data.keyframes,
33533
33601
  ...data.easeEach ? { easeEach: data.easeEach } : {}
33534
33602
  };
33535
- draft.keyframeCache.set(cacheKey, entry);
33536
- if (sf !== "index.html") draft.keyframeCache.set(fallbackKey, entry);
33537
- draft.keyframeCache.set(id, entry);
33603
+ for (const key2 of keys) draft.keyframeCache.set(key2, entry);
33538
33604
  }
33539
33605
  });
33540
33606
  runtimeScanDoneRef.current = `kf-cache:${projectId}:${sf}:${version}`;
@@ -33554,7 +33620,7 @@ var NOOP_EPSILON_PCT2 = 0.1;
33554
33620
  var EPSILON_TIME = 1e-4;
33555
33621
  var MIN_TWEEN_DURATION = 0.01;
33556
33622
  var round33 = (n) => Math.round(n * 1e3) / 1e3;
33557
- var clamp5 = (n, lo, hi) => Math.max(lo, Math.min(hi, n));
33623
+ var clamp6 = (n, lo, hi) => Math.max(lo, Math.min(hi, n));
33558
33624
  function resolveFlatTweenBoundaryRetime(opts) {
33559
33625
  const { keyframeCount, draggedTweenPct, tweenStart, tweenEnd, dropAbsTime } = opts;
33560
33626
  if (keyframeCount > 0) return null;
@@ -33585,7 +33651,7 @@ function resolveKeyframeRetime(opts) {
33585
33651
  });
33586
33652
  if (flatBoundary) return flatBoundary;
33587
33653
  if (dropAbsTime >= tweenStart - EPSILON_TIME && dropAbsTime <= tweenEnd + EPSILON_TIME) {
33588
- const toTweenPct = round33(clamp5((dropAbsTime - tweenStart) / tweenDuration * 100, 0, 100));
33654
+ const toTweenPct = round33(clamp6((dropAbsTime - tweenStart) / tweenDuration * 100, 0, 100));
33589
33655
  if (Math.abs(toTweenPct - draggedTweenPct) < NOOP_EPSILON_PCT2) return { kind: "noop" };
33590
33656
  return { kind: "move", toTweenPct };
33591
33657
  }
@@ -39622,7 +39688,7 @@ function hasCssWord(value, word) {
39622
39688
  function parsePercentToken(value, fallback) {
39623
39689
  if (!value?.endsWith("%")) return fallback;
39624
39690
  const parsed = parseCssNumber(value.slice(0, -1));
39625
- return parsed == null ? fallback : clamp6(parsed, 0, 100);
39691
+ return parsed == null ? fallback : clamp7(parsed, 0, 100);
39626
39692
  }
39627
39693
  function parseAngleToken(value) {
39628
39694
  const trimmed = value?.trim().toLowerCase();
@@ -39644,13 +39710,13 @@ function trailingPercentStart(value) {
39644
39710
  if (start === 0 || !isWhitespace(withoutUnit[start - 1])) return null;
39645
39711
  return start;
39646
39712
  }
39647
- function clamp6(value, min, max) {
39713
+ function clamp7(value, min, max) {
39648
39714
  return Math.min(max, Math.max(min, value));
39649
39715
  }
39650
39716
  var round = roundToCenti;
39651
39717
  function parsePercent(value, fallback) {
39652
39718
  const parsed = parseCssNumber(value);
39653
- return parsed == null ? fallback : clamp6(parsed, 0, 100);
39719
+ return parsed == null ? fallback : clamp7(parsed, 0, 100);
39654
39720
  }
39655
39721
  function parseColorStop(raw) {
39656
39722
  const trimmed = raw.trim();
@@ -39681,7 +39747,7 @@ function normalizeStops(stops) {
39681
39747
  }));
39682
39748
  return result.map((stop) => ({
39683
39749
  color: stop.color,
39684
- position: round(clamp6(stop.position, 0, 100))
39750
+ position: round(clamp7(stop.position, 0, 100))
39685
39751
  }));
39686
39752
  }
39687
39753
  function splitGradientArgs(value) {
@@ -39844,7 +39910,7 @@ function formatHex(channel) {
39844
39910
  return channel.toString(16).padStart(2, "0");
39845
39911
  }
39846
39912
  function interpolateGradientStopColor(model, position) {
39847
- const clampedPosition = clamp6(position, 0, 100);
39913
+ const clampedPosition = clamp7(position, 0, 100);
39848
39914
  const sortedStops = [...model.stops].sort((a, b) => a.position - b.position);
39849
39915
  const exact = sortedStops.find((stop) => Math.abs(stop.position - clampedPosition) < 1e-3);
39850
39916
  if (exact) return exact.color;
@@ -39868,7 +39934,7 @@ function interpolateGradientStopColor(model, position) {
39868
39934
  return `rgba(${red}, ${green}, ${blue}, ${alpha})`;
39869
39935
  }
39870
39936
  function insertGradientStop(model, position) {
39871
- const clampedPosition = round(clamp6(position, 0, 100));
39937
+ const clampedPosition = round(clamp7(position, 0, 100));
39872
39938
  const color = interpolateGradientStopColor(model, clampedPosition);
39873
39939
  const nextStops = [...model.stops, { color, position: clampedPosition }].sort(
39874
39940
  (a, b) => a.position - b.position
@@ -50734,7 +50800,7 @@ var FileTree = memo33(function FileTree2({
50734
50800
  });
50735
50801
 
50736
50802
  // src/App.tsx
50737
- import { useState as useState133, useCallback as useCallback152, useRef as useRef142, useMemo as useMemo66, useEffect as useEffect121, useLayoutEffect as useLayoutEffect9 } from "react";
50803
+ import { useState as useState133, useCallback as useCallback152, useRef as useRef142, useMemo as useMemo66, useEffect as useEffect122, useLayoutEffect as useLayoutEffect9 } from "react";
50738
50804
 
50739
50805
  // src/components/renders/useRenderQueue.ts
50740
50806
  import { useState as useState77, useEffect as useEffect71, useCallback as useCallback65, useRef as useRef83, useMemo as useMemo46 } from "react";
@@ -52315,33 +52381,26 @@ function usePersistentEditHistory(options) {
52315
52381
  }
52316
52382
 
52317
52383
  // src/hooks/usePanelLayout.ts
52318
- import { useState as useState82, useCallback as useCallback69, useRef as useRef90 } from "react";
52384
+ import { useState as useState82, useCallback as useCallback69, useRef as useRef90, useEffect as useEffect74 } from "react";
52385
+ var NO_OVERRIDE = { left: false, right: false };
52319
52386
  function getInitialRightInspectorPanes(tab) {
52320
52387
  if (tab === "layers") return { layers: true, design: false };
52321
52388
  return { layers: false, design: true };
52322
52389
  }
52323
- function getInitialPanelWidths() {
52324
- const viewportWidth = typeof window === "undefined" ? 1496 : window.innerWidth;
52390
+ function readViewportWidth() {
52391
+ return typeof window === "undefined" ? 1496 : window.innerWidth;
52392
+ }
52393
+ function getPreferredPanelWidths() {
52325
52394
  const preferences = readStudioUiPreferences();
52326
- const leftDefault = Math.max(240, Math.min(384, Math.round(viewportWidth * 0.257)));
52327
- const rightDefault = Math.max(320, Math.min(424, Math.round(viewportWidth * 0.284)));
52395
+ const defaults = defaultPanelWidths(readViewportWidth());
52328
52396
  return {
52329
- left: Math.max(
52330
- 160,
52331
- Math.min(Math.floor(viewportWidth * 0.5), preferences.leftWidth ?? leftDefault)
52332
- ),
52333
- right: Math.max(160, Math.min(600, preferences.rightWidth ?? rightDefault))
52397
+ left: preferences.leftWidth ?? defaults.left,
52398
+ right: preferences.rightWidth ?? defaults.right
52334
52399
  };
52335
52400
  }
52336
- function clampPanelWidth(side, width) {
52337
- const max = side === "left" ? Math.floor(window.innerWidth * 0.5) : 600;
52338
- return Math.max(160, Math.min(max, width));
52339
- }
52340
52401
  function usePanelLayout(initialState2) {
52341
- const [initialPanelWidths] = useState82(getInitialPanelWidths);
52342
- const [leftWidth, setLeftWidth] = useState82(initialPanelWidths.left);
52343
- const [rightWidth, setRightWidth] = useState82(initialPanelWidths.right);
52344
- const panelWidthsRef = useRef90(initialPanelWidths);
52402
+ const [preferredWidths, setPreferredWidths] = useState82(getPreferredPanelWidths);
52403
+ const [viewportWidth, setViewportWidth] = useState82(readViewportWidth);
52345
52404
  const [leftCollapsed, setLeftCollapsed] = useState82(
52346
52405
  () => readStudioUiPreferences().leftCollapsed ?? false
52347
52406
  );
@@ -52352,33 +52411,65 @@ function usePanelLayout(initialState2) {
52352
52411
  const [rightInspectorPanes, setRightInspectorPanes] = useState82(
52353
52412
  () => getInitialRightInspectorPanes(initialState2?.rightPanelTab)
52354
52413
  );
52414
+ const [autoCollapseOverride, setAutoCollapseOverride] = useState82(NO_OVERRIDE);
52415
+ useEffect74(() => {
52416
+ if (typeof window === "undefined") return;
52417
+ const handleResize = () => {
52418
+ const width = window.innerWidth;
52419
+ setViewportWidth(width);
52420
+ if (!railsEngaged(width)) {
52421
+ setAutoCollapseOverride((prev) => prev.left || prev.right ? NO_OVERRIDE : prev);
52422
+ }
52423
+ };
52424
+ window.addEventListener("resize", handleResize);
52425
+ return () => window.removeEventListener("resize", handleResize);
52426
+ }, []);
52427
+ const fitted = fitPanelWidths(viewportWidth, preferredWidths, autoCollapseOverride);
52428
+ const leftCollapsedByWidth = fitted.autoCollapseLeft;
52429
+ const rightCollapsedByWidth = fitted.autoCollapseRight;
52430
+ const fittedRef = useRef90(fitted);
52431
+ fittedRef.current = fitted;
52355
52432
  const panelDragRef = useRef90(null);
52356
- const updatePanelWidth = useCallback69((side, width) => {
52357
- const next = clampPanelWidth(side, width);
52358
- panelWidthsRef.current[side] = next;
52359
- if (side === "left") setLeftWidth(next);
52360
- else setRightWidth(next);
52433
+ const preferredRef = useRef90(preferredWidths);
52434
+ const setPreferred = useCallback69((side, width) => {
52435
+ const next = Math.max(0, Math.round(width));
52436
+ preferredRef.current = { ...preferredRef.current, [side]: next };
52437
+ setPreferredWidths(preferredRef.current);
52361
52438
  return next;
52362
52439
  }, []);
52440
+ const updatePanelWidth = useCallback69(
52441
+ (side, width) => {
52442
+ setPreferred(side, width);
52443
+ },
52444
+ [setPreferred]
52445
+ );
52363
52446
  const commitPanelWidth = useCallback69(
52364
52447
  (side, width) => {
52365
- const next = updatePanelWidth(side, Math.round(width));
52366
- writeStudioUiPreferences(side === "left" ? { leftWidth: next } : { rightWidth: next });
52448
+ const candidate = { ...preferredRef.current, [side]: Math.max(0, Math.round(width)) };
52449
+ const settled = fitPanelWidths(readViewportWidth(), candidate)[side];
52450
+ setPreferred(side, settled);
52451
+ writeStudioUiPreferences(side === "left" ? { leftWidth: settled } : { rightWidth: settled });
52367
52452
  },
52368
- [updatePanelWidth]
52453
+ [setPreferred]
52369
52454
  );
52370
52455
  const adjustPanelWidth = useCallback69(
52371
52456
  (side, delta) => {
52372
- commitPanelWidth(side, panelWidthsRef.current[side] + delta);
52457
+ commitPanelWidth(side, preferredRef.current[side] + delta);
52373
52458
  },
52374
52459
  [commitPanelWidth]
52375
52460
  );
52461
+ const effectiveLeftCollapsedRef = useRef90(false);
52462
+ effectiveLeftCollapsedRef.current = leftCollapsed || leftCollapsedByWidth;
52376
52463
  const toggleLeftSidebar = useCallback69(() => {
52377
- setLeftCollapsed((collapsed) => {
52378
- writeStudioUiPreferences({ leftCollapsed: !collapsed });
52379
- trackStudioEvent("panel_toggle", { panel: "left_sidebar", collapsed: !collapsed });
52380
- return !collapsed;
52381
- });
52464
+ const next = !effectiveLeftCollapsedRef.current;
52465
+ setLeftCollapsed(next);
52466
+ writeStudioUiPreferences({ leftCollapsed: next });
52467
+ trackStudioEvent("panel_toggle", { panel: "left_sidebar", collapsed: next });
52468
+ if (!next) setAutoCollapseOverride((prev) => ({ ...prev, left: true }));
52469
+ }, []);
52470
+ const setRightCollapsedWithOverride = useCallback69((collapsed) => {
52471
+ setRightCollapsed(collapsed);
52472
+ if (!collapsed) setAutoCollapseOverride((prev) => ({ ...prev, right: true }));
52382
52473
  }, []);
52383
52474
  const handlePanelResizeStart = useCallback69((side, e) => {
52384
52475
  e.preventDefault();
@@ -52386,7 +52477,7 @@ function usePanelLayout(initialState2) {
52386
52477
  panelDragRef.current = {
52387
52478
  side,
52388
52479
  startX: e.clientX,
52389
- startW: panelWidthsRef.current[side]
52480
+ startW: fittedRef.current[side]
52390
52481
  };
52391
52482
  }, []);
52392
52483
  const handlePanelResizeMove = useCallback69(
@@ -52400,7 +52491,7 @@ function usePanelLayout(initialState2) {
52400
52491
  );
52401
52492
  const handlePanelResizeEnd = useCallback69(() => {
52402
52493
  const side = panelDragRef.current?.side;
52403
- if (side) commitPanelWidth(side, panelWidthsRef.current[side]);
52494
+ if (side) commitPanelWidth(side, preferredRef.current[side]);
52404
52495
  panelDragRef.current = null;
52405
52496
  }, [commitPanelWidth]);
52406
52497
  const trackedSetRightPanelTab = useCallback69(
@@ -52426,13 +52517,22 @@ function usePanelLayout(initialState2) {
52426
52517
  setRightInspectorPanes({ design: pane === "design", layers: pane === "layers" });
52427
52518
  }, []);
52428
52519
  return {
52429
- leftWidth,
52430
- rightWidth,
52520
+ leftWidth: fitted.left,
52521
+ rightWidth: fitted.right,
52431
52522
  adjustPanelWidth,
52523
+ /**
52524
+ * User intent. Persisted to localStorage; never written by auto-collapse.
52525
+ * Deliberately read-only outside this hook: `toggleLeftSidebar` is the only
52526
+ * writer, so it cannot be flipped without also clearing the rail override
52527
+ * (which would open the sidebar and then immediately rail it again).
52528
+ */
52432
52529
  leftCollapsed,
52433
- setLeftCollapsed,
52530
+ /** User intent. Synced into the shareable URL; never written by auto-collapse. */
52434
52531
  rightCollapsed,
52435
- setRightCollapsed,
52532
+ setRightCollapsed: setRightCollapsedWithOverride,
52533
+ /** What the shell actually renders: intent OR the window forcing a rail. */
52534
+ effectiveLeftCollapsed: leftCollapsed || leftCollapsedByWidth,
52535
+ effectiveRightCollapsed: rightCollapsed || rightCollapsedByWidth,
52436
52536
  rightPanelTab,
52437
52537
  setRightPanelTab: trackedSetRightPanelTab,
52438
52538
  rightInspectorPanes,
@@ -52483,13 +52583,13 @@ function createStudioWriteToken() {
52483
52583
  }
52484
52584
 
52485
52585
  // src/hooks/useFileTree.ts
52486
- import { useState as useState83, useCallback as useCallback70, useEffect as useEffect74, useMemo as useMemo48 } from "react";
52586
+ import { useState as useState83, useCallback as useCallback70, useEffect as useEffect75, useMemo as useMemo48 } from "react";
52487
52587
  function useFileTree({ projectId, projectIdRef }) {
52488
52588
  const [projectDir, setProjectDir] = useState83(null);
52489
52589
  const [fileTree, setFileTree] = useState83([]);
52490
52590
  const [compositionPaths, setCompositionPaths] = useState83([]);
52491
52591
  const [fileTreeLoaded, setFileTreeLoaded] = useState83(false);
52492
- useEffect74(() => {
52592
+ useEffect75(() => {
52493
52593
  if (!projectId) {
52494
52594
  setFileTreeLoaded(false);
52495
52595
  return;
@@ -53476,7 +53576,7 @@ function usePreviewPersistence({
53476
53576
  }
53477
53577
 
53478
53578
  // src/hooks/usePreviewDocumentVersion.ts
53479
- import { useCallback as useCallback74, useEffect as useEffect75, useRef as useRef94, useState as useState86 } from "react";
53579
+ import { useCallback as useCallback74, useEffect as useEffect76, useRef as useRef94, useState as useState86 } from "react";
53480
53580
  function usePreviewDocumentVersion() {
53481
53581
  const [previewDocumentVersion, setPreviewDocumentVersion] = useState86(0);
53482
53582
  const refreshTimersRef = useRef94([]);
@@ -53489,7 +53589,7 @@ function usePreviewDocumentVersion() {
53489
53589
  window.setTimeout(() => setPreviewDocumentVersion((v) => v + 1), 300)
53490
53590
  );
53491
53591
  }, []);
53492
- useEffect75(
53592
+ useEffect76(
53493
53593
  () => () => {
53494
53594
  for (const id of refreshTimersRef.current) clearTimeout(id);
53495
53595
  },
@@ -55034,7 +55134,7 @@ function persistTimelineMoveEditsAtomically(edits, coalesceKey, operation, deps,
55034
55134
  import { useCallback as useCallback102 } from "react";
55035
55135
 
55036
55136
  // src/hooks/useAskAgentModal.ts
55037
- import { useState as useState87, useCallback as useCallback80, useRef as useRef97, useEffect as useEffect76 } from "react";
55137
+ import { useState as useState87, useCallback as useCallback80, useRef as useRef97, useEffect as useEffect77 } from "react";
55038
55138
  function useAskAgentModal({
55039
55139
  activeCompPath,
55040
55140
  projectDir,
@@ -55115,13 +55215,13 @@ function useAskAgentModal({
55115
55215
  showToast
55116
55216
  ]
55117
55217
  );
55118
- useEffect76(() => {
55218
+ useEffect77(() => {
55119
55219
  setAgentPromptTagSnippet(void 0);
55120
55220
  setAgentPromptSelectionContext(void 0);
55121
55221
  setAgentModalAnchorPoint(null);
55122
55222
  setCopiedAgentPrompt(false);
55123
55223
  }, [domEditSelection]);
55124
- useEffect76(
55224
+ useEffect77(
55125
55225
  () => () => {
55126
55226
  if (copiedAgentTimerRef.current) clearTimeout(copiedAgentTimerRef.current);
55127
55227
  },
@@ -55145,7 +55245,7 @@ function useAskAgentModal({
55145
55245
  }
55146
55246
 
55147
55247
  // src/hooks/useDomSelection.ts
55148
- import { useState as useState88, useCallback as useCallback81, useRef as useRef98, useEffect as useEffect78 } from "react";
55248
+ import { useState as useState88, useCallback as useCallback81, useRef as useRef98, useEffect as useEffect79 } from "react";
55149
55249
 
55150
55250
  // src/utils/domEditHelpers.ts
55151
55251
  function domEditSelectionsTargetSame(a, b) {
@@ -55178,7 +55278,7 @@ function seedDomEditGroupWithSelection(group, selection) {
55178
55278
  }
55179
55279
 
55180
55280
  // src/hooks/useStudioTestHooks.ts
55181
- import { useEffect as useEffect77 } from "react";
55281
+ import { useEffect as useEffect78 } from "react";
55182
55282
 
55183
55283
  // src/player/lib/timelinePerformanceDiagnostics.ts
55184
55284
  function readNonNegativeNumber(value) {
@@ -55261,7 +55361,7 @@ function useStudioTestHooks({
55261
55361
  buildDomSelectionFromTarget,
55262
55362
  applyDomSelection
55263
55363
  }) {
55264
- useEffect77(() => {
55364
+ useEffect78(() => {
55265
55365
  if (!STUDIO_TEST_HOOKS_ENABLED || typeof window === "undefined") return;
55266
55366
  const api = {
55267
55367
  runtimeMode: STUDIO_RUNTIME_MODE,
@@ -55562,10 +55662,10 @@ function useDomSelection({
55562
55662
  previewIframeRef
55563
55663
  ]
55564
55664
  );
55565
- useEffect78(() => {
55665
+ useEffect79(() => {
55566
55666
  updateDomEditHoverSelection(null);
55567
55667
  }, [activeCompPath, projectId, previewIframe, refreshKey, updateDomEditHoverSelection]);
55568
- useEffect78(() => {
55668
+ useEffect79(() => {
55569
55669
  const previous = compositionIdentityRef.current;
55570
55670
  if (previous.activeCompPath === activeCompPath && previous.projectId === projectId) return;
55571
55671
  compositionIdentityRef.current = { activeCompPath, projectId };
@@ -55573,7 +55673,7 @@ function useDomSelection({
55573
55673
  setActiveGroupElementState(null);
55574
55674
  applyDomSelection(null, { revealPanel: false });
55575
55675
  }, [activeCompPath, projectId, applyDomSelection]);
55576
- useEffect78(() => {
55676
+ useEffect79(() => {
55577
55677
  if (!domEditHoverSelection) return;
55578
55678
  const shouldClear = captionEditMode || domEditSelectionsTargetSame(domEditHoverSelection, domEditSelection) || domEditSelectionInGroup(domEditGroupSelections, domEditHoverSelection) || !domEditHoverSelection.element.isConnected;
55579
55679
  if (shouldClear) updateDomEditHoverSelection(null);
@@ -55584,7 +55684,7 @@ function useDomSelection({
55584
55684
  domEditGroupSelections,
55585
55685
  updateDomEditHoverSelection
55586
55686
  ]);
55587
- useEffect78(() => {
55687
+ useEffect79(() => {
55588
55688
  if (!captionEditMode) return;
55589
55689
  applyDomSelection(null, { revealPanel: false });
55590
55690
  }, [applyDomSelection, captionEditMode]);
@@ -58330,7 +58430,7 @@ function useGsapKeyframeOps({
58330
58430
  }
58331
58431
 
58332
58432
  // src/hooks/useGsapPropertyDebounce.ts
58333
- import { useCallback as useCallback92, useEffect as useEffect79, useRef as useRef103 } from "react";
58433
+ import { useCallback as useCallback92, useEffect as useEffect80, useRef as useRef103 } from "react";
58334
58434
  import { parseGsapScriptAcorn } from "@hyperframes/core/gsap-parser-acorn";
58335
58435
  var DEBOUNCE_MS = 150;
58336
58436
  function mergeTweenProperties(sdkSession, animationId, edited, kind) {
@@ -58400,7 +58500,7 @@ function useGsapPropertyDebounce(commitMutationSafely, sdk) {
58400
58500
  },
58401
58501
  [flushPendingPropertyEdit]
58402
58502
  );
58403
- useEffect79(() => {
58503
+ useEffect80(() => {
58404
58504
  return () => {
58405
58505
  if (debounceTimerRef.current) clearTimeout(debounceTimerRef.current);
58406
58506
  void flushPendingPropertyEdit();
@@ -58941,10 +59041,10 @@ function useGsapScriptCommits({ projectIdRef, activeCompPath, previewIframeRef,
58941
59041
  }
58942
59042
 
58943
59043
  // src/hooks/useDomEditWiring.ts
58944
- import { useCallback as useCallback98, useEffect as useEffect81, useRef as useRef107 } from "react";
59044
+ import { useCallback as useCallback98, useEffect as useEffect82, useRef as useRef107 } from "react";
58945
59045
 
58946
59046
  // src/hooks/useDomEditPreviewSync.ts
58947
- import { useEffect as useEffect80, useRef as useRef105 } from "react";
59047
+ import { useEffect as useEffect81, useRef as useRef105 } from "react";
58948
59048
  function useDomEditPreviewSync({
58949
59049
  previewIframe,
58950
59050
  activeCompPath,
@@ -58960,7 +59060,7 @@ function useDomEditPreviewSync({
58960
59060
  getSidebarTab,
58961
59061
  gsapCacheVersion
58962
59062
  }) {
58963
- useEffect80(() => {
59063
+ useEffect81(() => {
58964
59064
  if (!previewIframe) return;
58965
59065
  const syncSelectionFromDocument = async () => {
58966
59066
  if (captionEditMode) return;
@@ -59012,7 +59112,7 @@ function useDomEditPreviewSync({
59012
59112
  ]);
59013
59113
  const openSourceRef = useRef105(openSourceForSelection);
59014
59114
  openSourceRef.current = openSourceForSelection;
59015
- useEffect80(
59115
+ useEffect81(
59016
59116
  // fallow-ignore-next-line complexity
59017
59117
  () => {
59018
59118
  if (!domEditSelection || !openSourceRef.current || !getSidebarTab) return;
@@ -59449,7 +59549,7 @@ function useDomEditWiring({
59449
59549
  },
59450
59550
  [openSourceForSelection, selectSidebarTab]
59451
59551
  );
59452
- useEffect81(() => {
59552
+ useEffect82(() => {
59453
59553
  if (!domEditSelection?.id) return;
59454
59554
  const { selectedElementId, elements, setSelectedElementId } = usePlayerStore.getState();
59455
59555
  const matchKey = elements.find(
@@ -59459,7 +59559,7 @@ function useDomEditWiring({
59459
59559
  if (key2 && key2 !== selectedElementId) setSelectedElementId(key2);
59460
59560
  }, [domEditSelection?.id]);
59461
59561
  const prevRefreshKeyRef = useRef107(refreshKey);
59462
- useEffect81(() => {
59562
+ useEffect82(() => {
59463
59563
  if (refreshKey !== prevRefreshKeyRef.current) {
59464
59564
  prevRefreshKeyRef.current = refreshKey;
59465
59565
  bumpGsapCache();
@@ -59597,7 +59697,7 @@ async function tryGsapResizeIntercept(selection, size, animations, iframe, commi
59597
59697
  if (!anim || isInstantHold(anim)) {
59598
59698
  const sel = selectorFromSelection(selection) ?? writeTargetSelector(selection);
59599
59699
  if (!sel) return { status: "blocked", reason: "no-selector" };
59600
- const sizeSet = anim ?? findSizeSetAnimation(workingAnimations, sel, selection.element);
59700
+ const sizeSet = resizeGroup === "size" ? anim ?? findSizeSetAnimation(workingAnimations, sel, selection.element) : findSizeSetAnimation(workingAnimations, sel, selection.element);
59601
59701
  if (resizeGroup === "size") {
59602
59702
  const animatedTween = pickClosestToPlayhead(
59603
59703
  workingAnimations.filter((a) => !isInstantHold(a) && resolveTweenDuration(a) > 0)
@@ -59645,7 +59745,8 @@ async function tryGsapResizeIntercept(selection, size, animations, iframe, commi
59645
59745
  const liveScaleY = rawLiveScaleY > 0 ? rawLiveScaleY : 1;
59646
59746
  const newScaleX = roundTo3(size.width * liveScaleX / cssW);
59647
59747
  const newScaleY = roundTo3(size.height * liveScaleY / cssH);
59648
- nonUniformScale = Math.abs(newScaleX - newScaleY) > 0.01;
59748
+ const uniformDrift = Math.abs(newScaleX - newScaleY) * cssH;
59749
+ nonUniformScale = uniformDrift > 0.5;
59649
59750
  useScaleLonghands = nonUniformScale || tweenUsesScaleLonghands(anim);
59650
59751
  resizeProps = useScaleLonghands ? { scaleX: newScaleX, scaleY: newScaleY } : { scale: newScaleX };
59651
59752
  logResize("intercept-route", {
@@ -59671,9 +59772,9 @@ async function tryGsapResizeIntercept(selection, size, animations, iframe, commi
59671
59772
  };
59672
59773
  }
59673
59774
  const finalizeScaleResizeCommit = async () => {
59674
- if (!scaleDraftEl) return;
59775
+ if (!scaleDraftEl) return false;
59675
59776
  clearStudioBoxSize(scaleDraftEl);
59676
- if (!scaleDraftDropPoint || !selector) return;
59777
+ if (!scaleDraftDropPoint || !selector) return false;
59677
59778
  if (committedScale) {
59678
59779
  setElementGsapScale(scaleDraftEl, committedScale.x, committedScale.y);
59679
59780
  }
@@ -59687,10 +59788,10 @@ async function tryGsapResizeIntercept(selection, size, animations, iframe, commi
59687
59788
  setElementGsapPosition(scaleDraftEl, base2.x, base2.y);
59688
59789
  const post = scaleDraftEl.getBoundingClientRect();
59689
59790
  const residual = { x: scaleDraftDropPoint.x - post.x, y: scaleDraftDropPoint.y - post.y };
59690
- if (!Number.isFinite(residual.x) || !Number.isFinite(residual.y)) return;
59791
+ if (!Number.isFinite(residual.x) || !Number.isFinite(residual.y)) return false;
59691
59792
  if (Math.abs(residual.x) < 0.5 && Math.abs(residual.y) < 0.5) {
59692
59793
  logResize("scale-finalize", { skipped: "already-on-drop-point", residual, base: base2 });
59693
- return;
59794
+ return true;
59694
59795
  }
59695
59796
  const corrected = {
59696
59797
  x: Math.round(base2.x + residual.x),
@@ -59718,13 +59819,14 @@ async function tryGsapResizeIntercept(selection, size, animations, iframe, commi
59718
59819
  commitMutation,
59719
59820
  fetchAnimations: fetchFallbackAnimations
59720
59821
  });
59721
- return;
59822
+ return true;
59722
59823
  }
59723
59824
  const existingSet = findExistingPositionWrite(currentAnimations, selector, selection.element);
59724
59825
  await commitStaticGsapPosition(selection, delta, base2, selector, existingSet, {
59725
59826
  commitMutation,
59726
59827
  fetchAnimations: fetchFallbackAnimations
59727
59828
  });
59829
+ return true;
59728
59830
  };
59729
59831
  if (!usePlayerStore.getState().autoKeyframeEnabled) {
59730
59832
  if (activeKeyframePct != null) setActiveKeyframePct(null);
@@ -59737,8 +59839,7 @@ async function tryGsapResizeIntercept(selection, size, animations, iframe, commi
59737
59839
  { commitMutation, fetchAnimations: fetchFallbackAnimations },
59738
59840
  "Resize animation"
59739
59841
  );
59740
- await finalizeScaleResizeCommit();
59741
- return { status: "persisted" };
59842
+ return { status: "persisted", ownsDragOffset: await finalizeScaleResizeCommit() };
59742
59843
  }
59743
59844
  const ct = usePlayerStore.getState().currentTime;
59744
59845
  const ts = resolveTweenStart(anim);
@@ -59817,8 +59918,7 @@ async function tryGsapResizeIntercept(selection, size, animations, iframe, commi
59817
59918
  softReload: true
59818
59919
  }
59819
59920
  );
59820
- await finalizeScaleResizeCommit();
59821
- return { status: "persisted" };
59921
+ return { status: "persisted", ownsDragOffset: await finalizeScaleResizeCommit() };
59822
59922
  }
59823
59923
  const SIZE_PROPS = /* @__PURE__ */ new Set(["width", "height"]);
59824
59924
  const backfillDefaults = {};
@@ -59837,8 +59937,7 @@ async function tryGsapResizeIntercept(selection, size, animations, iframe, commi
59837
59937
  },
59838
59938
  { label: `Resize (keyframe ${pct}%)`, softReload: true }
59839
59939
  );
59840
- await finalizeScaleResizeCommit();
59841
- return { status: "persisted" };
59940
+ return { status: "persisted", ownsDragOffset: await finalizeScaleResizeCommit() };
59842
59941
  }
59843
59942
 
59844
59943
  // src/hooks/useAnimatedPropertyCommit.ts
@@ -60338,11 +60437,13 @@ function useGsapAwareEditing({
60338
60437
  makeFetchFallback(selection)
60339
60438
  );
60340
60439
  assertGsapEditPersisted(outcome);
60440
+ const ownsDragOffset = outcome.status === "persisted" && outcome.ownsDragOffset === true;
60341
60441
  logResize("intercept-handled", {
60342
60442
  scaleRoute,
60343
- willForwardOffset: !!(offset && !scaleRoute)
60443
+ ownsDragOffset,
60444
+ willForwardOffset: !!(offset && !ownsDragOffset)
60344
60445
  });
60345
- if (offset && !scaleRoute) {
60446
+ if (offset && !ownsDragOffset) {
60346
60447
  const dragOutcome = await tryGsapDragIntercept(
60347
60448
  selection,
60348
60449
  offset,
@@ -60353,7 +60454,7 @@ function useGsapAwareEditing({
60353
60454
  );
60354
60455
  assertGsapEditPersisted(dragOutcome);
60355
60456
  }
60356
- logResizeSettle(selection.element, scaleRoute ? "gsap-scale" : "gsap-size");
60457
+ logResizeSettle(selection.element, ownsDragOffset ? "gsap-scale" : "gsap-size");
60357
60458
  return;
60358
60459
  } catch (error) {
60359
60460
  trackGsapInteractionFailure(error, selection, "resize", "Resize animated layer");
@@ -60491,7 +60592,7 @@ function useGsapAwareEditing({
60491
60592
  }
60492
60593
 
60493
60594
  // src/hooks/useStudioSelectionPublisher.ts
60494
- import { useEffect as useEffect82, useRef as useRef108 } from "react";
60595
+ import { useEffect as useEffect83, useRef as useRef108 } from "react";
60495
60596
 
60496
60597
  // src/utils/studioSelectionSnapshot.ts
60497
60598
  function round34(value) {
@@ -60584,7 +60685,7 @@ function useStudioSelectionPublisher({
60584
60685
  }) {
60585
60686
  const lastSelectionRefreshKeyRef = useRef108(refreshKey);
60586
60687
  const pendingSelectionRefreshKeyRef = useRef108(null);
60587
- useEffect82(() => {
60688
+ useEffect83(() => {
60588
60689
  if (!projectId) return;
60589
60690
  const selection = domEditSelection?.element.isConnected ? buildStudioSelectionSnapshot({
60590
60691
  projectId,
@@ -60595,13 +60696,13 @@ function useStudioSelectionPublisher({
60595
60696
  void putSelection(projectId, selection, controller.signal).catch(reportSelectionPublishError);
60596
60697
  return () => controller.abort();
60597
60698
  }, [domEditSelection, projectId]);
60598
- useEffect82(() => {
60699
+ useEffect83(() => {
60599
60700
  if (!projectId) return;
60600
60701
  return () => {
60601
60702
  void putSelection(projectId, null).catch(reportSelectionPublishError);
60602
60703
  };
60603
60704
  }, [projectId]);
60604
- useEffect82(() => {
60705
+ useEffect83(() => {
60605
60706
  if (lastSelectionRefreshKeyRef.current === refreshKey) return;
60606
60707
  lastSelectionRefreshKeyRef.current = refreshKey;
60607
60708
  pendingSelectionRefreshKeyRef.current = domEditSelectionRef.current ? refreshKey : null;
@@ -60610,7 +60711,7 @@ function useStudioSelectionPublisher({
60610
60711
  void putSelection(projectId, null, controller.signal).catch(reportSelectionPublishError);
60611
60712
  return () => controller.abort();
60612
60713
  }, [domEditSelectionRef, projectId, refreshKey]);
60613
- useEffect82(() => {
60714
+ useEffect83(() => {
60614
60715
  if (pendingSelectionRefreshKeyRef.current === null) return;
60615
60716
  pendingSelectionRefreshKeyRef.current = null;
60616
60717
  const selection = domEditSelectionRef.current;
@@ -61127,23 +61228,23 @@ function useDomEditSession({
61127
61228
  }
61128
61229
 
61129
61230
  // src/hooks/useSdkSelectionSync.ts
61130
- import { useEffect as useEffect83 } from "react";
61231
+ import { useEffect as useEffect84 } from "react";
61131
61232
  function toHfIds(group, primary) {
61132
61233
  const source = group.length > 0 ? group : primary ? [primary] : [];
61133
61234
  return source.flatMap((s) => s.hfId ? [s.hfId] : []);
61134
61235
  }
61135
61236
  function useSdkSelectionSync(session, domEditSelection, domEditGroupSelections) {
61136
- useEffect83(() => {
61237
+ useEffect84(() => {
61137
61238
  if (!session) return;
61138
61239
  session.setSelection(toHfIds(domEditGroupSelections, domEditSelection));
61139
61240
  }, [session, domEditSelection, domEditGroupSelections]);
61140
61241
  }
61141
61242
 
61142
61243
  // src/hooks/useStudioSdkSessions.ts
61143
- import { useEffect as useEffect85 } from "react";
61244
+ import { useEffect as useEffect86 } from "react";
61144
61245
 
61145
61246
  // src/hooks/useSdkSession.ts
61146
- import { useState as useState89, useEffect as useEffect84, useCallback as useCallback103, useRef as useRef109 } from "react";
61247
+ import { useState as useState89, useEffect as useEffect85, useCallback as useCallback103, useRef as useRef109 } from "react";
61147
61248
  import { openComposition as openComposition3 } from "@hyperframes/sdk";
61148
61249
 
61149
61250
  // src/hooks/externalFileReloadBus.ts
@@ -61203,13 +61304,13 @@ function useSdkSession(projectId, activeCompPath) {
61203
61304
  const [reloadToken, setReloadToken] = useState89(0);
61204
61305
  const reloadTokenRef = useRef109(reloadToken);
61205
61306
  reloadTokenRef.current = reloadToken;
61206
- useEffect84(
61307
+ useEffect85(
61207
61308
  () => addExternalFileReloadListener((changedPath) => {
61208
61309
  if (changedPath === activeCompPathRef.current) setReloadToken((token) => token + 1);
61209
61310
  }),
61210
61311
  []
61211
61312
  );
61212
- useEffect84(() => {
61313
+ useEffect85(() => {
61213
61314
  const generation = ++generationRef.current;
61214
61315
  let cancelled = false;
61215
61316
  const previous = ownedSessionRef.current;
@@ -61293,7 +61394,7 @@ function useSdkSession(projectId, activeCompPath) {
61293
61394
  function useStudioSdkSessions(projectId, activeCompPath, masterCompPath) {
61294
61395
  const sdkHandle = useSdkSession(projectId, activeCompPath ?? masterCompPath);
61295
61396
  const editFlowSdkSession = activeCompPath ? sdkHandle.session : null;
61296
- useEffect85(() => {
61397
+ useEffect86(() => {
61297
61398
  usePreviewVariablesStore.getState().setValues(null);
61298
61399
  }, [projectId, activeCompPath]);
61299
61400
  return { sdkHandle, editFlowSdkSession };
@@ -61401,7 +61502,7 @@ async function deleteExternalConflictSnapshot(projectId, filePath) {
61401
61502
  }
61402
61503
 
61403
61504
  // src/hooks/useExternalFileChangeCoordinator.ts
61404
- import { useCallback as useCallback104, useEffect as useEffect86, useRef as useRef110, useState as useState90 } from "react";
61505
+ import { useCallback as useCallback104, useEffect as useEffect87, useRef as useRef110, useState as useState90 } from "react";
61405
61506
  function testHotAdapter() {
61406
61507
  const value = globalThis.__HF_STUDIO_HOT_TEST_ADAPTER__;
61407
61508
  if (!value || typeof value !== "object") return null;
@@ -61458,19 +61559,19 @@ function useExternalFileChangeCoordinator({
61458
61559
  const blockedRef = useRef110(blocked);
61459
61560
  const snapshotWriteTailRef = useRef110(Promise.resolve());
61460
61561
  blockedRef.current = blocked;
61461
- useEffect86(() => {
61562
+ useEffect87(() => {
61462
61563
  mountedRef.current = true;
61463
61564
  return () => {
61464
61565
  mountedRef.current = false;
61465
61566
  generationRef.current += 1;
61466
61567
  };
61467
61568
  }, []);
61468
- useEffect86(() => {
61569
+ useEffect87(() => {
61469
61570
  generationRef.current += 1;
61470
61571
  setBlocked(null);
61471
61572
  lastEventIdentityRef.current = null;
61472
61573
  }, [projectId, activeCompPath]);
61473
- useEffect86(() => {
61574
+ useEffect87(() => {
61474
61575
  if (!projectId || !recoveryFilePath || !loadConflictSnapshot) return;
61475
61576
  const generation = ++generationRef.current;
61476
61577
  let cancelled = false;
@@ -61621,7 +61722,7 @@ function useExternalFileChangeCoordinator({
61621
61722
  reloadAcceptedGeneration
61622
61723
  ]
61623
61724
  );
61624
- useEffect86(() => {
61725
+ useEffect87(() => {
61625
61726
  const handler = (payload) => processChange(payload);
61626
61727
  const adapter = testHotAdapter();
61627
61728
  if (adapter) {
@@ -62087,7 +62188,7 @@ function useBlockHandlers({
62087
62188
  }
62088
62189
 
62089
62190
  // src/hooks/useAppHotkeys.ts
62090
- import { useCallback as useCallback107, useEffect as useEffect87, useRef as useRef112 } from "react";
62191
+ import { useCallback as useCallback107, useEffect as useEffect88, useRef as useRef112 } from "react";
62091
62192
  function iframeContentWindow(iframe) {
62092
62193
  try {
62093
62194
  return iframe?.contentWindow ?? null;
@@ -62380,7 +62481,7 @@ function useAppHotkeys({
62380
62481
  }
62381
62482
  if (!isEditableTarget(event.target)) dispatchPlainKey(event, key2, cb);
62382
62483
  }, []);
62383
- useEffect87(() => {
62484
+ useEffect88(() => {
62384
62485
  window.addEventListener("keydown", handleAppKeyDown, true);
62385
62486
  return () => window.removeEventListener("keydown", handleAppKeyDown, true);
62386
62487
  }, [handleAppKeyDown]);
@@ -62398,7 +62499,7 @@ function useAppHotkeys({
62398
62499
  },
62399
62500
  [handleAppKeyDown]
62400
62501
  );
62401
- useEffect87(
62502
+ useEffect88(
62402
62503
  () => () => {
62403
62504
  safeRemoveListener(
62404
62505
  previewHotkeyWindowRef.current,
@@ -62439,7 +62540,7 @@ function useAppHotkeys({
62439
62540
  },
62440
62541
  [handleHistoryHotkey]
62441
62542
  );
62442
- useEffect87(
62543
+ useEffect88(
62443
62544
  () => () => {
62444
62545
  previewHistoryCleanupRef.current?.();
62445
62546
  previewHistoryCleanupRef.current = null;
@@ -62697,7 +62798,7 @@ function useClipboard({
62697
62798
  }
62698
62799
 
62699
62800
  // src/hooks/useCaptionDetection.ts
62700
- import { useEffect as useEffect88 } from "react";
62801
+ import { useEffect as useEffect89 } from "react";
62701
62802
 
62702
62803
  // src/captions/types.ts
62703
62804
  var DEFAULT_STYLE = {
@@ -62949,7 +63050,7 @@ function useCaptionDetection({
62949
63050
  captionSync,
62950
63051
  setRightCollapsed
62951
63052
  }) {
62952
- useEffect88(() => {
63053
+ useEffect89(() => {
62953
63054
  if (!projectId) return;
62954
63055
  let activating = false;
62955
63056
  const tryActivateCaptions = () => {
@@ -63029,7 +63130,7 @@ function useCaptionDetection({
63029
63130
  window.removeEventListener("message", handleMessage);
63030
63131
  };
63031
63132
  }, [activeCompPath, projectId, compIdToSrc, captionSync, previewIframeRef]);
63032
- useEffect88(() => {
63133
+ useEffect89(() => {
63033
63134
  if (captionEditMode) {
63034
63135
  setRightCollapsed(!captionHasSelection);
63035
63136
  }
@@ -63466,7 +63567,7 @@ function useRenderClipContent({
63466
63567
  }
63467
63568
 
63468
63569
  // src/hooks/useConsoleErrorCapture.ts
63469
- import { useCallback as useCallback112, useEffect as useEffect89, useRef as useRef116, useState as useState93 } from "react";
63570
+ import { useCallback as useCallback112, useEffect as useEffect90, useRef as useRef116, useState as useState93 } from "react";
63470
63571
  function useConsoleErrorCapture(previewIframe) {
63471
63572
  const [consoleErrors, setConsoleErrors] = useState93(null);
63472
63573
  const consoleErrorsRef = useRef116([]);
@@ -63474,7 +63575,7 @@ function useConsoleErrorCapture(previewIframe) {
63474
63575
  consoleErrorsRef.current = [];
63475
63576
  setConsoleErrors(null);
63476
63577
  }, []);
63477
- useEffect89(() => {
63578
+ useEffect90(() => {
63478
63579
  if (!previewIframe) return;
63479
63580
  let patchedWin = null;
63480
63581
  let origConsoleError = null;
@@ -63663,7 +63764,7 @@ function useFrameCapture({
63663
63764
  }
63664
63765
 
63665
63766
  // src/hooks/useLintModal.ts
63666
- import { useState as useState95, useCallback as useCallback114, useEffect as useEffect90, useRef as useRef118, useMemo as useMemo54 } from "react";
63767
+ import { useState as useState95, useCallback as useCallback114, useEffect as useEffect91, useRef as useRef118, useMemo as useMemo54 } from "react";
63667
63768
  function parseFinding(f) {
63668
63769
  return {
63669
63770
  severity: f.severity === "error" ? "error" : "warning",
@@ -63705,7 +63806,7 @@ function useLintModal(projectId, refreshKey) {
63705
63806
  );
63706
63807
  const handleLint = useCallback114(() => runLint(), [runLint]);
63707
63808
  const prevProjectIdRef = useRef118(projectId);
63708
- useEffect90(() => {
63809
+ useEffect91(() => {
63709
63810
  if (projectId !== prevProjectIdRef.current) {
63710
63811
  autoLintRanRef.current = false;
63711
63812
  prevProjectIdRef.current = projectId;
@@ -63714,7 +63815,7 @@ function useLintModal(projectId, refreshKey) {
63714
63815
  autoLintRanRef.current = true;
63715
63816
  void runLint({ background: true });
63716
63817
  }, [projectId, runLint]);
63717
- useEffect90(() => {
63818
+ useEffect91(() => {
63718
63819
  if (!projectId || !refreshKey) return;
63719
63820
  const timer = setTimeout(() => void runLint({ background: true }), 1e3);
63720
63821
  return () => clearTimeout(timer);
@@ -63737,7 +63838,7 @@ function useLintModal(projectId, refreshKey) {
63737
63838
  );
63738
63839
  const findingsByElement = useMemo54(() => groupFindings((f) => f.elementId), [groupFindings]);
63739
63840
  const findingsByFile = useMemo54(() => groupFindings((f) => f.file), [groupFindings]);
63740
- useEffect90(() => {
63841
+ useEffect91(() => {
63741
63842
  usePlayerStore.getState().setLintFindingsByElement(findingsByElement);
63742
63843
  }, [findingsByElement]);
63743
63844
  return {
@@ -63835,7 +63936,7 @@ function useCompositionContentLoader({
63835
63936
  }
63836
63937
 
63837
63938
  // src/hooks/useStudioUrlState.ts
63838
- import { useCallback as useCallback117, useEffect as useEffect91, useRef as useRef120, useState as useState97 } from "react";
63939
+ import { useCallback as useCallback117, useEffect as useEffect92, useRef as useRef120, useState as useState97 } from "react";
63839
63940
 
63840
63941
  // src/utils/studioUrlState.ts
63841
63942
  var VALID_TABS = ["layers", "design", "renders", "slideshow", "variables"];
@@ -64011,7 +64112,7 @@ function useStudioUrlState({
64011
64112
  },
64012
64113
  [activeCompPath, applyDomSelection, buildDomSelectionFromTarget, previewIframeRef]
64013
64114
  );
64014
- useEffect91(() => {
64115
+ useEffect92(() => {
64015
64116
  if (!projectId || hydratedSeekRef.current || compositionLoading) return;
64016
64117
  const nextTime = duration > 0 ? clampNumber(initialState2.currentTime ?? 0, 0, duration) : Math.max(0, initialState2.currentTime ?? 0);
64017
64118
  usePlayerStore.getState().requestSeek(nextTime);
@@ -64019,7 +64120,7 @@ function useStudioUrlState({
64019
64120
  hydratedSeekRef.current = true;
64020
64121
  }, [projectId, compositionLoading, duration, initialState2.currentTime]);
64021
64122
  const selectionHydrationTime = selectionHydrated ? 0 : currentTime;
64022
- useEffect91(() => {
64123
+ useEffect92(() => {
64023
64124
  if (!projectId || hydratedSelectionRef.current || compositionLoading) return;
64024
64125
  if (!hydratedSeekRef.current) return;
64025
64126
  const targetTime = initialState2.currentTime;
@@ -64046,7 +64147,7 @@ function useStudioUrlState({
64046
64147
  projectId,
64047
64148
  refreshKey
64048
64149
  ]);
64049
- useEffect91(() => {
64150
+ useEffect92(() => {
64050
64151
  if (hydratedInitialTimeRef.current) return;
64051
64152
  const targetTime = stableTimeRef.current;
64052
64153
  if (targetTime == null) {
@@ -64056,7 +64157,7 @@ function useStudioUrlState({
64056
64157
  if (Math.abs(currentTime - targetTime) > 0.05) return;
64057
64158
  hydratedInitialTimeRef.current = true;
64058
64159
  }, [currentTime]);
64059
- useEffect91(() => {
64160
+ useEffect92(() => {
64060
64161
  if (!activeCompPathHydrated) return;
64061
64162
  if (!hydratedSeekRef.current) return;
64062
64163
  if (!hydratedInitialTimeRef.current) return;
@@ -64067,12 +64168,12 @@ function useStudioUrlState({
64067
64168
  }, 200);
64068
64169
  return () => window.clearTimeout(handle);
64069
64170
  }, [activeCompPathHydrated, buildUrlState, currentTime, duration, isPlaying, projectId]);
64070
- useEffect91(() => {
64171
+ useEffect92(() => {
64071
64172
  if (!activeCompPathHydrated) return;
64072
64173
  if (!projectId) return;
64073
64174
  replaceHash(buildStudioHash(projectId, buildUrlState()));
64074
64175
  }, [activeCompPathHydrated, buildUrlState, projectId]);
64075
- useEffect91(() => {
64176
+ useEffect92(() => {
64076
64177
  if (!projectId) return;
64077
64178
  const onHashChange = () => {
64078
64179
  if (parseProjectIdFromHash(window.location.hash) !== projectId) return;
@@ -64205,9 +64306,10 @@ function PanelLayoutProvider({
64205
64306
  rightWidth,
64206
64307
  adjustPanelWidth,
64207
64308
  leftCollapsed,
64208
- setLeftCollapsed,
64209
64309
  rightCollapsed,
64210
64310
  setRightCollapsed,
64311
+ effectiveLeftCollapsed,
64312
+ effectiveRightCollapsed,
64211
64313
  rightPanelTab,
64212
64314
  setRightPanelTab,
64213
64315
  rightInspectorPanes,
@@ -64226,9 +64328,10 @@ function PanelLayoutProvider({
64226
64328
  rightWidth,
64227
64329
  adjustPanelWidth,
64228
64330
  leftCollapsed,
64229
- setLeftCollapsed,
64230
64331
  rightCollapsed,
64231
64332
  setRightCollapsed,
64333
+ effectiveLeftCollapsed,
64334
+ effectiveRightCollapsed,
64232
64335
  rightPanelTab,
64233
64336
  setRightPanelTab,
64234
64337
  rightInspectorPanes,
@@ -64244,9 +64347,10 @@ function PanelLayoutProvider({
64244
64347
  rightWidth,
64245
64348
  adjustPanelWidth,
64246
64349
  leftCollapsed,
64247
- setLeftCollapsed,
64248
64350
  rightCollapsed,
64249
64351
  setRightCollapsed,
64352
+ effectiveLeftCollapsed,
64353
+ effectiveRightCollapsed,
64250
64354
  rightPanelTab,
64251
64355
  setRightPanelTab,
64252
64356
  rightInspectorPanes,
@@ -64266,7 +64370,7 @@ import {
64266
64370
  createContext as createContext9,
64267
64371
  useCallback as useCallback119,
64268
64372
  useContext as useContext9,
64269
- useEffect as useEffect92,
64373
+ useEffect as useEffect93,
64270
64374
  useMemo as useMemo57,
64271
64375
  useRef as useRef122,
64272
64376
  useState as useState99
@@ -64304,7 +64408,7 @@ function useViewModeState() {
64304
64408
  }
64305
64409
  return true;
64306
64410
  }, []);
64307
- useEffect92(() => {
64411
+ useEffect93(() => {
64308
64412
  const onPopState = () => {
64309
64413
  const mode = readViewModeFromUrl();
64310
64414
  if (mode !== viewMode && !canSetViewMode(mode)) {
@@ -64586,6 +64690,9 @@ function ViewModeToggle() {
64586
64690
  }
64587
64691
  );
64588
64692
  }
64693
+ function shouldOpenInspector(effectiveRightCollapsed, inspectorPanelActive) {
64694
+ return effectiveRightCollapsed || !inspectorPanelActive;
64695
+ }
64589
64696
  function StudioHeader({
64590
64697
  captureFrameHref,
64591
64698
  captureFrameFilename,
@@ -64597,7 +64704,7 @@ function StudioHeader({
64597
64704
  onExport
64598
64705
  }) {
64599
64706
  const { projectId, editHistory, handleUndo, handleRedo, renderQueue } = useStudioShellContext();
64600
- const { rightCollapsed, setRightCollapsed, setRightPanelTab } = usePanelLayoutContext();
64707
+ const { effectiveRightCollapsed, setRightCollapsed, setRightPanelTab } = usePanelLayoutContext();
64601
64708
  const isRendering = renderQueue.isRendering;
64602
64709
  return /* @__PURE__ */ jsxs118("div", { className: "flex items-center justify-between h-10 px-3 bg-neutral-900 border-b border-neutral-800 flex-shrink-0", children: [
64603
64710
  /* @__PURE__ */ jsxs118("div", { className: "flex items-center gap-3", children: [
@@ -64707,7 +64814,7 @@ function StudioHeader({
64707
64814
  {
64708
64815
  type: "button",
64709
64816
  onClick: () => {
64710
- if (rightCollapsed || !inspectorPanelActive) {
64817
+ if (shouldOpenInspector(effectiveRightCollapsed, inspectorPanelActive)) {
64711
64818
  trackStudioEvent("panel_toggle", { panel: "inspector", collapsed: false });
64712
64819
  setRightPanelTab("design");
64713
64820
  setRightCollapsed(false);
@@ -64766,10 +64873,10 @@ function StudioHeader({
64766
64873
  }
64767
64874
 
64768
64875
  // src/hooks/useGestureCommit.ts
64769
- import { useState as useState101, useCallback as useCallback121, useRef as useRef125, useEffect as useEffect94 } from "react";
64876
+ import { useState as useState101, useCallback as useCallback121, useRef as useRef125, useEffect as useEffect95 } from "react";
64770
64877
 
64771
64878
  // src/hooks/useGestureRecording.ts
64772
- import { useCallback as useCallback120, useEffect as useEffect93, useRef as useRef124, useState as useState100 } from "react";
64879
+ import { useCallback as useCallback120, useEffect as useEffect94, useRef as useRef124, useState as useState100 } from "react";
64773
64880
  function readBasePosition(element, iframeEl) {
64774
64881
  let baseOpacity = 1;
64775
64882
  let baseScale = 1;
@@ -64918,7 +65025,7 @@ function useGestureRecording() {
64918
65025
  const refs = useRef124(createRecordingRefs());
64919
65026
  const samplesRef = useRef124(refs.current.samples);
64920
65027
  const trailRef = useRef124(refs.current.trail);
64921
- useEffect93(() => {
65028
+ useEffect94(() => {
64922
65029
  const r = refs.current;
64923
65030
  return () => {
64924
65031
  isRecordingRef.current = false;
@@ -65301,7 +65408,7 @@ function useGestureCommit({
65301
65408
  const recordingStartTimeRef = useRef125(0);
65302
65409
  const commitInFlightRef = useRef125(false);
65303
65410
  const capturedSelectionRef = useRef125(null);
65304
- useEffect94(() => () => clearInterval(recordingAutoStopRef.current), []);
65411
+ useEffect95(() => () => clearInterval(recordingAutoStopRef.current), []);
65305
65412
  const stopAndCommitRecording = useCallback121(async () => {
65306
65413
  clearInterval(recordingAutoStopRef.current);
65307
65414
  if (commitInFlightRef.current) {
@@ -65646,7 +65753,7 @@ import {
65646
65753
  } from "react";
65647
65754
 
65648
65755
  // src/components/sidebar/CompositionsTab.tsx
65649
- import { memo as memo37, useCallback as useCallback122, useEffect as useEffect95, useRef as useRef126, useState as useState102 } from "react";
65756
+ import { memo as memo37, useCallback as useCallback122, useEffect as useEffect96, useRef as useRef126, useState as useState102 } from "react";
65650
65757
  import { jsx as jsx144, jsxs as jsxs120 } from "react/jsx-runtime";
65651
65758
  var DEFAULT_PREVIEW_STAGE = { width: 1920, height: 1080 };
65652
65759
  var CARD_W = 80;
@@ -65764,10 +65871,10 @@ function CompCard({
65764
65871
  });
65765
65872
  const thumbnailOffsetX = (CARD_W - stageSize.width * previewScale) / 2;
65766
65873
  const thumbnailOffsetY = (CARD_H - stageSize.height * previewScale) / 2;
65767
- useEffect95(() => {
65874
+ useEffect96(() => {
65768
65875
  if (hovered) requestIframePlaybackSync(true);
65769
65876
  }, [hovered, requestIframePlaybackSync]);
65770
- useEffect95(() => {
65877
+ useEffect96(() => {
65771
65878
  return () => {
65772
65879
  if (hoverTimer.current) clearTimeout(hoverTimer.current);
65773
65880
  if (syncTimer.current) clearTimeout(syncTimer.current);
@@ -65943,7 +66050,7 @@ var CompositionsTab = memo37(function CompositionsTab2({
65943
66050
  });
65944
66051
 
65945
66052
  // src/components/sidebar/AssetsTab.tsx
65946
- import { memo as memo38, useState as useState107, useCallback as useCallback125, useRef as useRef129, useMemo as useMemo60, useEffect as useEffect100 } from "react";
66053
+ import { memo as memo38, useState as useState107, useCallback as useCallback125, useRef as useRef129, useMemo as useMemo60, useEffect as useEffect101 } from "react";
65947
66054
 
65948
66055
  // src/components/sidebar/assetHelpers.ts
65949
66056
  function getCategory(path) {
@@ -65993,7 +66100,7 @@ var CATEGORY_LABELS = {
65993
66100
  var FILTER_ORDER = ["audio", "images", "video", "fonts"];
65994
66101
 
65995
66102
  // src/components/sidebar/AudioRow.tsx
65996
- import { useState as useState103, useRef as useRef127, useEffect as useEffect96, useCallback as useCallback123 } from "react";
66103
+ import { useState as useState103, useRef as useRef127, useEffect as useEffect97, useCallback as useCallback123 } from "react";
65997
66104
 
65998
66105
  // src/components/sidebar/AssetContextMenu.tsx
65999
66106
  import { jsx as jsx145, jsxs as jsxs121 } from "react/jsx-runtime";
@@ -66168,14 +66275,14 @@ function AudioRow({
66168
66275
  clearPreviewAsset
66169
66276
  ]
66170
66277
  );
66171
- useEffect96(() => {
66278
+ useEffect97(() => {
66172
66279
  return () => {
66173
66280
  cancelAnimationFrame(animRef.current);
66174
66281
  audioRef.current?.pause();
66175
66282
  actxRef.current?.close();
66176
66283
  };
66177
66284
  }, []);
66178
- useEffect96(() => {
66285
+ useEffect97(() => {
66179
66286
  if (playing) {
66180
66287
  const barCount = 24;
66181
66288
  const loop = () => {
@@ -66313,7 +66420,7 @@ function AudioRow({
66313
66420
  }
66314
66421
 
66315
66422
  // src/components/sidebar/GlobalAssetsView.tsx
66316
- import { useEffect as useEffect97, useMemo as useMemo59, useState as useState104 } from "react";
66423
+ import { useEffect as useEffect98, useMemo as useMemo59, useState as useState104 } from "react";
66317
66424
  import { jsx as jsx147, jsxs as jsxs123 } from "react/jsx-runtime";
66318
66425
  function globalAssetRows(records, query = "") {
66319
66426
  const q = query.trim().toLowerCase();
@@ -66329,7 +66436,7 @@ function globalAssetRows(records, query = "") {
66329
66436
  }
66330
66437
  function GlobalAssetsView({ searchQuery }) {
66331
66438
  const [records, setRecords] = useState104(null);
66332
- useEffect97(() => {
66439
+ useEffect98(() => {
66333
66440
  let cancelled = false;
66334
66441
  fetch("/api/assets/global").then((r) => r.ok ? r.json() : { assets: [] }).then((d) => {
66335
66442
  if (!cancelled) setRecords(Array.isArray(d.assets) ? d.assets : []);
@@ -66372,10 +66479,10 @@ function GlobalAssetsView({ searchQuery }) {
66372
66479
  }
66373
66480
 
66374
66481
  // src/components/sidebar/AssetCard.tsx
66375
- import { useState as useState106, useEffect as useEffect99, useRef as useRef128, useCallback as useCallback124 } from "react";
66482
+ import { useState as useState106, useEffect as useEffect100, useRef as useRef128, useCallback as useCallback124 } from "react";
66376
66483
 
66377
66484
  // src/components/ui/VideoFrameThumbnail.tsx
66378
- import { useState as useState105, useEffect as useEffect98 } from "react";
66485
+ import { useState as useState105, useEffect as useEffect99 } from "react";
66379
66486
  import { jsx as jsx148 } from "react/jsx-runtime";
66380
66487
  function VideoFrameThumbnail({
66381
66488
  src,
@@ -66383,7 +66490,7 @@ function VideoFrameThumbnail({
66383
66490
  }) {
66384
66491
  const [frame, setFrame] = useState105(null);
66385
66492
  const [failed2, setFailed] = useState105(false);
66386
- useEffect98(() => {
66493
+ useEffect99(() => {
66387
66494
  setFailed(false);
66388
66495
  const video = document.createElement("video");
66389
66496
  video.crossOrigin = "anonymous";
@@ -66436,7 +66543,7 @@ function openAssetContextMenu(e, setContextMenu) {
66436
66543
  }
66437
66544
  function useProbedDuration(src, skip) {
66438
66545
  const [duration, setDuration] = useState106(void 0);
66439
- useEffect99(() => {
66546
+ useEffect100(() => {
66440
66547
  if (skip) return;
66441
66548
  let cancelled = false;
66442
66549
  let retryTimer;
@@ -66709,7 +66816,7 @@ var AssetsTab = memo38(function AssetsTab2({
66709
66816
  const [manifest, setManifest] = useState107(/* @__PURE__ */ new Map());
66710
66817
  const manifest404Ref = useRef129(/* @__PURE__ */ new Set());
66711
66818
  const assetsKey = assets.join("|");
66712
- useEffect100(() => {
66819
+ useEffect101(() => {
66713
66820
  if (manifest404Ref.current.has(projectId)) return;
66714
66821
  let cancelled = false;
66715
66822
  fetch(`/api/projects/${projectId}/preview/.media/manifest.jsonl`).then((r) => {
@@ -67028,7 +67135,7 @@ var AssetsTab = memo38(function AssetsTab2({
67028
67135
  });
67029
67136
 
67030
67137
  // src/components/sidebar/BlocksTab.tsx
67031
- import { memo as memo39, useState as useState108, useCallback as useCallback126, useRef as useRef130, useEffect as useEffect101 } from "react";
67138
+ import { memo as memo39, useState as useState108, useCallback as useCallback126, useRef as useRef130, useEffect as useEffect102 } from "react";
67032
67139
  import { createPortal as createPortal7 } from "react-dom";
67033
67140
  import { jsx as jsx151, jsxs as jsxs126 } from "react/jsx-runtime";
67034
67141
  var BlocksTab = memo39(function BlocksTab2({ onAddBlock, onPreviewBlock }) {
@@ -67257,7 +67364,7 @@ function BlockCard({
67257
67364
  setHovered(false);
67258
67365
  onPreview?.(null);
67259
67366
  }, [onPreview]);
67260
- useEffect101(() => {
67367
+ useEffect102(() => {
67261
67368
  return () => {
67262
67369
  if (hoverTimer.current) clearTimeout(hoverTimer.current);
67263
67370
  };
@@ -67419,7 +67526,7 @@ function PromptPreviewModal({
67419
67526
  const [value, setValue] = useState108(prompt);
67420
67527
  const [copied, setCopied] = useState108(false);
67421
67528
  const textareaRef = useRef130(null);
67422
- useEffect101(() => {
67529
+ useEffect102(() => {
67423
67530
  requestAnimationFrame(() => textareaRef.current?.focus());
67424
67531
  }, []);
67425
67532
  const handleCopy = useCallback126(() => {
@@ -67508,7 +67615,11 @@ function PromptPreviewModal({
67508
67615
  import { Fragment as Fragment47, jsx as jsx152, jsxs as jsxs127 } from "react/jsx-runtime";
67509
67616
  var STORAGE_KEY = "hf-studio-sidebar-tab";
67510
67617
  function getPersistedTab() {
67511
- const stored = localStorage.getItem(STORAGE_KEY);
67618
+ let stored = null;
67619
+ try {
67620
+ stored = safeLocalStorage()?.getItem(STORAGE_KEY) ?? null;
67621
+ } catch {
67622
+ }
67512
67623
  if (stored === "assets") return "assets";
67513
67624
  if (stored === "code") return "code";
67514
67625
  if (stored === "blocks") return "blocks";
@@ -67551,7 +67662,10 @@ var LeftSidebar = memo40(
67551
67662
  tabRef.current = tab;
67552
67663
  const selectTab = useCallback127((t) => {
67553
67664
  setTab(t);
67554
- localStorage.setItem(STORAGE_KEY, t);
67665
+ try {
67666
+ safeLocalStorage()?.setItem(STORAGE_KEY, t);
67667
+ } catch {
67668
+ }
67555
67669
  trackStudioEvent("tab_switch", { panel: "left_sidebar", tab: t });
67556
67670
  }, []);
67557
67671
  const getTab = useCallback127(() => tabRef.current, []);
@@ -67870,7 +67984,7 @@ function StudioLeftSidebar({
67870
67984
  onAddCompositionToTimeline
67871
67985
  }) {
67872
67986
  const {
67873
- leftCollapsed,
67987
+ effectiveLeftCollapsed,
67874
67988
  leftWidth,
67875
67989
  adjustPanelWidth,
67876
67990
  toggleLeftSidebar,
@@ -67903,7 +68017,7 @@ function StudioLeftSidebar({
67903
68017
  },
67904
68018
  [renderQueue, waitForPendingDomEditSaves]
67905
68019
  );
67906
- if (leftCollapsed) {
68020
+ if (effectiveLeftCollapsed) {
67907
68021
  return /* @__PURE__ */ jsx154("div", { className: "mr-0.5 flex w-10 flex-shrink-0 flex-col items-center rounded-lg border border-neutral-800/50 bg-neutral-950 pt-1", children: /* @__PURE__ */ jsx154(
67908
68022
  "button",
67909
68023
  {
@@ -68015,10 +68129,10 @@ function StudioLeftSidebar({
68015
68129
  }
68016
68130
 
68017
68131
  // src/components/StudioRightPanel.tsx
68018
- import { useCallback as useCallback144, useEffect as useEffect108, useRef as useRef137 } from "react";
68132
+ import { useCallback as useCallback144, useEffect as useEffect109, useRef as useRef137 } from "react";
68019
68133
 
68020
68134
  // src/components/editor/LayersPanel.tsx
68021
- import { memo as memo41, useState as useState112, useCallback as useCallback130, useEffect as useEffect102, useRef as useRef133 } from "react";
68135
+ import { memo as memo41, useState as useState112, useCallback as useCallback130, useEffect as useEffect103, useRef as useRef133 } from "react";
68022
68136
 
68023
68137
  // src/components/editor/useLayerDrag.ts
68024
68138
  import { useRef as useRef132, useState as useState111, useCallback as useCallback129 } from "react";
@@ -68321,10 +68435,10 @@ var LayersPanel = memo41(function LayersPanel2() {
68321
68435
  });
68322
68436
  setLayers(sortLayersByZIndex(items));
68323
68437
  }, [previewIframeRef, activeCompPath, isMasterView, activeGroupElement, setActiveGroupElement]);
68324
- useEffect102(() => {
68438
+ useEffect103(() => {
68325
68439
  collectLayers();
68326
68440
  }, [collectLayers, refreshKey, zEditVersion]);
68327
- useEffect102(() => {
68441
+ useEffect103(() => {
68328
68442
  const iframe = previewIframeRef.current;
68329
68443
  if (!iframe) return;
68330
68444
  const handleLoad = () => {
@@ -68334,13 +68448,13 @@ var LayersPanel = memo41(function LayersPanel2() {
68334
68448
  iframe.addEventListener("load", handleLoad);
68335
68449
  return () => iframe.removeEventListener("load", handleLoad);
68336
68450
  }, [previewIframeRef, collectLayers]);
68337
- useEffect102(() => {
68451
+ useEffect103(() => {
68338
68452
  if (!compositionLoading) {
68339
68453
  const timer = setTimeout(collectLayers, 100);
68340
68454
  return () => clearTimeout(timer);
68341
68455
  }
68342
68456
  }, [compositionLoading, collectLayers]);
68343
- useEffect102(() => {
68457
+ useEffect103(() => {
68344
68458
  const throttle = createRafThrottle(collectLayers, 100);
68345
68459
  const unsubscribe = liveTime.subscribe(throttle.invoke);
68346
68460
  return () => {
@@ -69186,7 +69300,7 @@ function ParamControl({
69186
69300
  }
69187
69301
 
69188
69302
  // src/components/renders/RenderQueue.tsx
69189
- import { memo as memo46, useState as useState116, useRef as useRef134, useEffect as useEffect103, useLayoutEffect as useLayoutEffect8, useId as useId6 } from "react";
69303
+ import { memo as memo46, useState as useState116, useRef as useRef134, useEffect as useEffect104, useLayoutEffect as useLayoutEffect8, useId as useId6 } from "react";
69190
69304
  import { createPortal as createPortal8 } from "react-dom";
69191
69305
  import { CANVAS_DIMENSIONS } from "@hyperframes/parsers";
69192
69306
 
@@ -69482,7 +69596,7 @@ function FormatInfoTooltip({ format }) {
69482
69596
  const hide = () => {
69483
69597
  timeoutRef.current = setTimeout(() => setOpen(false), 120);
69484
69598
  };
69485
- useEffect103(() => () => clearTimeout(timeoutRef.current), []);
69599
+ useEffect104(() => () => clearTimeout(timeoutRef.current), []);
69486
69600
  useLayoutEffect8(() => {
69487
69601
  if (!open) return;
69488
69602
  const el = triggerRef.current;
@@ -69496,7 +69610,7 @@ function FormatInfoTooltip({ format }) {
69496
69610
  )
69497
69611
  );
69498
69612
  }, [open]);
69499
- useEffect103(() => {
69613
+ useEffect104(() => {
69500
69614
  if (!open) return;
69501
69615
  const onKeyDown = (e) => {
69502
69616
  if (e.key === "Escape") setOpen(false);
@@ -69708,7 +69822,7 @@ var RenderQueue = memo46(function RenderQueue2({
69708
69822
  compositionDimensions
69709
69823
  }) {
69710
69824
  const listRef = useRef134(null);
69711
- useEffect103(() => {
69825
+ useEffect104(() => {
69712
69826
  if (listRef.current) {
69713
69827
  listRef.current.scrollTo({ top: listRef.current.scrollHeight, behavior: "smooth" });
69714
69828
  }
@@ -69816,7 +69930,7 @@ var RenderQueue = memo46(function RenderQueue2({
69816
69930
  });
69817
69931
 
69818
69932
  // src/components/panels/SlideshowPanel.tsx
69819
- import { useState as useState118, useEffect as useEffect104, useCallback as useCallback136, useRef as useRef135 } from "react";
69933
+ import { useState as useState118, useEffect as useEffect105, useCallback as useCallback136, useRef as useRef135 } from "react";
69820
69934
  import { parseSlideshowManifest } from "@hyperframes/core/slideshow";
69821
69935
 
69822
69936
  // src/components/panels/SlideshowSubPanels.tsx
@@ -70455,7 +70569,7 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
70455
70569
  const { domEditSelection } = useDomEditSelectionContext();
70456
70570
  const manifestRef = useRef135(manifest);
70457
70571
  const notesCtrlRef = useRef135(makeSlideshowNotesController());
70458
- useEffect104(() => {
70572
+ useEffect105(() => {
70459
70573
  if (!compHtml) {
70460
70574
  notesCtrlRef.current.flush();
70461
70575
  setManifest({ slides: [] });
@@ -70489,7 +70603,7 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
70489
70603
  },
70490
70604
  [onPersistNotes]
70491
70605
  );
70492
- useEffect104(() => {
70606
+ useEffect105(() => {
70493
70607
  const ctrl = notesCtrlRef.current;
70494
70608
  return () => {
70495
70609
  ctrl.flush();
@@ -70709,7 +70823,7 @@ function SlideshowPanel({ scenes, onPersist, onPersistNotes }) {
70709
70823
  }
70710
70824
 
70711
70825
  // src/components/panels/VariablesPanel.tsx
70712
- import { memo as memo47, useCallback as useCallback140, useEffect as useEffect106, useMemo as useMemo61, useState as useState122 } from "react";
70826
+ import { memo as memo47, useCallback as useCallback140, useEffect as useEffect107, useMemo as useMemo61, useState as useState122 } from "react";
70713
70827
 
70714
70828
  // src/hooks/useVariablesPersist.ts
70715
70829
  import { useCallback as useCallback137 } from "react";
@@ -70763,7 +70877,7 @@ function useVariablesPersist({
70763
70877
  import { useCallback as useCallback139, useState as useState121 } from "react";
70764
70878
 
70765
70879
  // src/hooks/useProjectCompositionVariables.ts
70766
- import { useCallback as useCallback138, useEffect as useEffect105, useState as useState119 } from "react";
70880
+ import { useCallback as useCallback138, useEffect as useEffect106, useState as useState119 } from "react";
70767
70881
  import { openComposition as openComposition4 } from "@hyperframes/sdk";
70768
70882
  async function readGroup(path, readProjectFile) {
70769
70883
  let content;
@@ -70787,7 +70901,7 @@ async function readGroup(path, readProjectFile) {
70787
70901
  }
70788
70902
  function useProjectCompositionVariables(fileTree, excludePath, readProjectFile, refreshKey) {
70789
70903
  const [groups, setGroups] = useState119([]);
70790
- useEffect105(() => {
70904
+ useEffect106(() => {
70791
70905
  let cancelled = false;
70792
70906
  const htmlFiles = fileTree.filter((p) => p.endsWith(".html") && p !== excludePath);
70793
70907
  void (async () => {
@@ -71397,7 +71511,7 @@ var VariablesPanel = memo47(function VariablesPanel2({
71397
71511
  const previewValues = usePreviewVariablesStore((s) => s.values);
71398
71512
  const setPreviewValues = usePreviewVariablesStore((s) => s.setValues);
71399
71513
  const [revision, setRevision] = useState122(0);
71400
- useEffect106(() => {
71514
+ useEffect107(() => {
71401
71515
  if (!sdkSession) return;
71402
71516
  return sdkSession.on("change", () => setRevision((r) => r + 1));
71403
71517
  }, [sdkSession]);
@@ -71767,7 +71881,7 @@ function useSlideshowPersist({
71767
71881
  }
71768
71882
 
71769
71883
  // src/hooks/useSlideshowTabState.ts
71770
- import { useEffect as useEffect107, useMemo as useMemo62 } from "react";
71884
+ import { useEffect as useEffect108, useMemo as useMemo62 } from "react";
71771
71885
  import { SLIDESHOW_ISLAND_TYPE as SLIDESHOW_ISLAND_TYPE2, slideshowIslandRegex as slideshowIslandRegex2 } from "@hyperframes/core/slideshow";
71772
71886
  function useSlideshowTabState(params) {
71773
71887
  const { editingFileContent, previewIframeRef, refreshKey, rightPanelTab, setRightPanelTab } = params;
@@ -71788,7 +71902,7 @@ function useSlideshowTabState(params) {
71788
71902
  return [];
71789
71903
  }
71790
71904
  }, [previewIframeRef, rightPanelTab, refreshKey]);
71791
- useEffect107(() => {
71905
+ useEffect108(() => {
71792
71906
  if (rightPanelTab === "slideshow" && !isSlideshowComposition) {
71793
71907
  setRightPanelTab("renders");
71794
71908
  }
@@ -72226,7 +72340,7 @@ function StudioRightPanel({
72226
72340
  handleInspectorSplitResizeEnd
72227
72341
  } = useInspectorSplitResize();
72228
72342
  const backgroundRemovalAbortRef = useRef137(null);
72229
- useEffect108(
72343
+ useEffect109(
72230
72344
  () => () => {
72231
72345
  backgroundRemovalAbortRef.current?.abort();
72232
72346
  },
@@ -72594,7 +72708,7 @@ function StudioRightPanel({
72594
72708
  }
72595
72709
 
72596
72710
  // src/components/TimelineToolbar.tsx
72597
- import { useEffect as useEffect110, useRef as useRef138 } from "react";
72711
+ import { useEffect as useEffect111, useRef as useRef138 } from "react";
72598
72712
  import { Image as Image2, Magnet, MagnifyingGlassMinus, MagnifyingGlassPlus } from "@phosphor-icons/react";
72599
72713
 
72600
72714
  // src/hooks/useEnableKeyframes.ts
@@ -72933,7 +73047,7 @@ function useEnableKeyframes(sessionRef) {
72933
73047
  }
72934
73048
 
72935
73049
  // src/hooks/useKeyframeKeyboard.ts
72936
- import { useEffect as useEffect109, useCallback as useCallback146 } from "react";
73050
+ import { useEffect as useEffect110, useCallback as useCallback146 } from "react";
72937
73051
  function isTextInput(el) {
72938
73052
  if (!el) return false;
72939
73053
  const tag = el.tagName;
@@ -73001,7 +73115,7 @@ function useKeyframeKeyboard({
73001
73115
  onNudgeKeyframe
73002
73116
  ]
73003
73117
  );
73004
- useEffect109(() => {
73118
+ useEffect110(() => {
73005
73119
  if (!enabled2) return;
73006
73120
  window.addEventListener("keydown", handler, { capture: true });
73007
73121
  return () => window.removeEventListener("keydown", handler, { capture: true });
@@ -73089,7 +73203,7 @@ function TimelineToolbar({ domEditSession, onSplitElement }) {
73089
73203
  enabled: Boolean(onToggleKeyframe),
73090
73204
  onAddKeyframe: onToggleKeyframe
73091
73205
  });
73092
- useEffect110(() => {
73206
+ useEffect111(() => {
73093
73207
  const onKeyDown = (e) => {
73094
73208
  if (e.key !== "n" && e.key !== "N") return;
73095
73209
  if (e.metaKey || e.ctrlKey || e.altKey) return;
@@ -73367,7 +73481,7 @@ import { useState as useState131 } from "react";
73367
73481
  import { Copy as Copy2, Check as Check2 } from "@phosphor-icons/react";
73368
73482
 
73369
73483
  // src/hooks/useStoryboard.ts
73370
- import { useCallback as useCallback147, useEffect as useEffect111, useRef as useRef139, useState as useState124 } from "react";
73484
+ import { useCallback as useCallback147, useEffect as useEffect112, useRef as useRef139, useState as useState124 } from "react";
73371
73485
  function useStoryboard(projectId) {
73372
73486
  const [data, setData] = useState124(null);
73373
73487
  const [loading, setLoading] = useState124(true);
@@ -73376,7 +73490,7 @@ function useStoryboard(projectId) {
73376
73490
  const hasDataRef = useRef139(false);
73377
73491
  const lastProjectRef = useRef139(null);
73378
73492
  const reload = useCallback147(() => setReloadKey((k) => k + 1), []);
73379
- useEffect111(() => {
73493
+ useEffect112(() => {
73380
73494
  if (!projectId) return;
73381
73495
  let cancelled = false;
73382
73496
  if (lastProjectRef.current !== projectId) {
@@ -73408,7 +73522,7 @@ function useStoryboard(projectId) {
73408
73522
  }
73409
73523
 
73410
73524
  // src/hooks/useProjectSignaturePoll.ts
73411
- import { useEffect as useEffect112, useRef as useRef140 } from "react";
73525
+ import { useEffect as useEffect113, useRef as useRef140 } from "react";
73412
73526
  var POLL_INTERVAL_MS = 2e3;
73413
73527
  async function fetchProjectSignature(projectId) {
73414
73528
  try {
@@ -73425,7 +73539,7 @@ function useProjectSignaturePoll(projectId, currentSignature, onChange) {
73425
73539
  const onChangeRef = useRef140(onChange);
73426
73540
  signatureRef.current = currentSignature;
73427
73541
  onChangeRef.current = onChange;
73428
- useEffect112(() => {
73542
+ useEffect113(() => {
73429
73543
  if (!projectId) return;
73430
73544
  let disposed = false;
73431
73545
  let inFlight = false;
@@ -73452,7 +73566,7 @@ function useProjectSignaturePoll(projectId, currentSignature, onChange) {
73452
73566
  }
73453
73567
 
73454
73568
  // src/components/storyboard/StoryboardLoaded.tsx
73455
- import { useEffect as useEffect118, useMemo as useMemo65, useState as useState130 } from "react";
73569
+ import { useEffect as useEffect119, useMemo as useMemo65, useState as useState130 } from "react";
73456
73570
 
73457
73571
  // src/components/storyboard/StoryboardDirection.tsx
73458
73572
  import { jsx as jsx172, jsxs as jsxs144 } from "react/jsx-runtime";
@@ -73475,7 +73589,7 @@ function StoryboardDirection({ globals, frameCount }) {
73475
73589
  }
73476
73590
 
73477
73591
  // src/components/storyboard/FramePoster.tsx
73478
- import { useEffect as useEffect113, useState as useState125 } from "react";
73592
+ import { useEffect as useEffect114, useState as useState125 } from "react";
73479
73593
  import { jsx as jsx173 } from "react/jsx-runtime";
73480
73594
  function FramePoster({
73481
73595
  projectId,
@@ -73486,7 +73600,7 @@ function FramePoster({
73486
73600
  posterVersion
73487
73601
  }) {
73488
73602
  const [failed2, setFailed] = useState125(false);
73489
- useEffect113(() => setFailed(false), [src, seconds, posterVersion]);
73603
+ useEffect114(() => setFailed(false), [src, seconds, posterVersion]);
73490
73604
  if (failed2) {
73491
73605
  return /* @__PURE__ */ jsx173("div", { className: "flex h-full w-full items-center justify-center text-[11px] text-neutral-600", children: "Preview unavailable" });
73492
73606
  }
@@ -73685,7 +73799,7 @@ function StoryboardScriptPanel({ script }) {
73685
73799
  }
73686
73800
 
73687
73801
  // src/components/storyboard/StoryboardSourceEditor.tsx
73688
- import { useCallback as useCallback148, useEffect as useEffect114, useMemo as useMemo63, useRef as useRef141, useState as useState126 } from "react";
73802
+ import { useCallback as useCallback148, useEffect as useEffect115, useMemo as useMemo63, useRef as useRef141, useState as useState126 } from "react";
73689
73803
  import { marked } from "marked";
73690
73804
  import DOMPurify from "dompurify";
73691
73805
  import { jsx as jsx177, jsxs as jsxs147 } from "react/jsx-runtime";
@@ -73697,7 +73811,7 @@ function useEditableFile(path, onSaved) {
73697
73811
  const [loading, setLoading] = useState126(true);
73698
73812
  const [saving, setSaving] = useState126(false);
73699
73813
  const [error, setError] = useState126(null);
73700
- useEffect114(() => {
73814
+ useEffect115(() => {
73701
73815
  if (!path) return;
73702
73816
  let cancelled = false;
73703
73817
  setLoading(true);
@@ -73735,7 +73849,7 @@ function hardenLinks(node) {
73735
73849
  function useMarkdownPreview(source) {
73736
73850
  const [debounced, setDebounced] = useState126(source);
73737
73851
  const primed = useRef141(false);
73738
- useEffect114(() => {
73852
+ useEffect115(() => {
73739
73853
  if (!primed.current && source !== "") {
73740
73854
  primed.current = true;
73741
73855
  setDebounced(source);
@@ -73767,8 +73881,8 @@ function StoryboardSourceEditor({
73767
73881
  const activePath = files.some((f) => f.path === selected) ? selected : files[0]?.path ?? "";
73768
73882
  const file = useEditableFile(activePath, onSaved);
73769
73883
  const previewHtml = useMarkdownPreview(file.content);
73770
- useEffect114(() => onDirtyChange?.(file.dirty), [onDirtyChange, file.dirty]);
73771
- useEffect114(() => {
73884
+ useEffect115(() => onDirtyChange?.(file.dirty), [onDirtyChange, file.dirty]);
73885
+ useEffect115(() => {
73772
73886
  if (!file.dirty) return;
73773
73887
  const onBeforeUnload = (event) => {
73774
73888
  event.preventDefault();
@@ -73840,11 +73954,11 @@ function StoryboardSourceEditor({
73840
73954
  }
73841
73955
 
73842
73956
  // src/components/storyboard/StoryboardFrameFocus.tsx
73843
- import { useCallback as useCallback149, useEffect as useEffect116, useState as useState128 } from "react";
73957
+ import { useCallback as useCallback149, useEffect as useEffect117, useState as useState128 } from "react";
73844
73958
  import { setFrameVoiceover } from "@hyperframes/core/storyboard";
73845
73959
 
73846
73960
  // src/components/storyboard/AgentChatMessageButton.tsx
73847
- import { useEffect as useEffect115, useState as useState127 } from "react";
73961
+ import { useEffect as useEffect116, useState as useState127 } from "react";
73848
73962
  import { jsx as jsx178 } from "react/jsx-runtime";
73849
73963
  var APPLY_STORYBOARD_FEEDBACK_MESSAGE = "Read the storyboard feedback I saved in .hyperframes/frame-comments.json and revise the frames.";
73850
73964
  function AgentChatMessageButton({
@@ -73853,7 +73967,7 @@ function AgentChatMessageButton({
73853
73967
  onCopied
73854
73968
  }) {
73855
73969
  const [copyState, setCopyState] = useState127("idle");
73856
- useEffect115(() => {
73970
+ useEffect116(() => {
73857
73971
  if (copyState !== "copied") return;
73858
73972
  const timeout = window.setTimeout(() => setCopyState("again"), 3e3);
73859
73973
  return () => window.clearTimeout(timeout);
@@ -73926,7 +74040,7 @@ function StoryboardFrameFocus({
73926
74040
  const saved = await applyEdit((src) => setFrameVoiceover(src, frame.index, draft));
73927
74041
  if (saved) setSavedVoiceover(draft);
73928
74042
  }, [applyEdit, frame.index, draft]);
73929
- useEffect116(() => {
74043
+ useEffect117(() => {
73930
74044
  if (!dirty) return;
73931
74045
  const onBeforeUnload = (e) => {
73932
74046
  e.preventDefault();
@@ -73938,14 +74052,14 @@ function StoryboardFrameFocus({
73938
74052
  () => !dirty || window.confirm("Discard unsaved voiceover changes?"),
73939
74053
  [dirty]
73940
74054
  );
73941
- useEffect116(() => registerViewModeGuard(confirmLeave), [confirmLeave, registerViewModeGuard]);
74055
+ useEffect117(() => registerViewModeGuard(confirmLeave), [confirmLeave, registerViewModeGuard]);
73942
74056
  const handleBack = () => {
73943
74057
  if (confirmLeave()) onBack();
73944
74058
  };
73945
74059
  const handleNavigate = (delta) => {
73946
74060
  if (confirmLeave()) onNavigate(delta);
73947
74061
  };
73948
- useEffect116(() => {
74062
+ useEffect117(() => {
73949
74063
  const onKey = (e) => {
73950
74064
  const el = document.activeElement;
73951
74065
  if (el instanceof HTMLTextAreaElement || el instanceof HTMLInputElement) return;
@@ -74417,7 +74531,7 @@ function progressLabel(summary) {
74417
74531
  }
74418
74532
 
74419
74533
  // src/components/storyboard/useFrameComments.ts
74420
- import { useCallback as useCallback150, useEffect as useEffect117, useMemo as useMemo64, useState as useState129 } from "react";
74534
+ import { useCallback as useCallback150, useEffect as useEffect118, useMemo as useMemo64, useState as useState129 } from "react";
74421
74535
 
74422
74536
  // src/components/storyboard/frameComments.ts
74423
74537
  var FRAME_COMMENTS_PATH = ".hyperframes/frame-comments.json";
@@ -74490,7 +74604,7 @@ function useFrameComments(frames) {
74490
74604
  } catch {
74491
74605
  }
74492
74606
  }, [readOptionalProjectFile]);
74493
- useEffect117(() => {
74607
+ useEffect118(() => {
74494
74608
  void refreshPending();
74495
74609
  const onFocus = () => void refreshPending();
74496
74610
  window.addEventListener("focus", onFocus);
@@ -74551,10 +74665,10 @@ function StoryboardLoaded({
74551
74665
  const [feedbackMessageCopied, setFeedbackMessageCopied] = useState130(false);
74552
74666
  const comments = useFrameComments(data.frames);
74553
74667
  const { refreshPending } = comments;
74554
- useEffect118(() => {
74668
+ useEffect119(() => {
74555
74669
  void refreshPending();
74556
74670
  }, [data.signature, refreshPending]);
74557
- useEffect118(() => {
74671
+ useEffect119(() => {
74558
74672
  if (comments.draftCount > 0) setFeedbackMessageCopied(false);
74559
74673
  }, [comments.draftCount]);
74560
74674
  const saveFeedbackAndCopyMessage = async () => {
@@ -74903,7 +75017,7 @@ function StudioSplash({ waiting }) {
74903
75017
  }
74904
75018
 
74905
75019
  // src/hooks/useServerConnection.ts
74906
- import { useEffect as useEffect119, useState as useState132 } from "react";
75020
+ import { useEffect as useEffect120, useState as useState132 } from "react";
74907
75021
  function useServerConnection() {
74908
75022
  const [projectId, setProjectId] = useState132(null);
74909
75023
  const [resolving, setResolving] = useState132(true);
@@ -74944,7 +75058,7 @@ function useServerConnection() {
74944
75058
  if (retryTimer !== null) clearTimeout(retryTimer);
74945
75059
  };
74946
75060
  });
74947
- useEffect119(() => {
75061
+ useEffect120(() => {
74948
75062
  const onHashChange = () => {
74949
75063
  const next = parseProjectIdFromHash(window.location.hash);
74950
75064
  if (next && next !== projectId) setProjectId(next);
@@ -74956,9 +75070,9 @@ function useServerConnection() {
74956
75070
  }
74957
75071
 
74958
75072
  // src/hooks/useStudioSessionStart.ts
74959
- import { useEffect as useEffect120 } from "react";
75073
+ import { useEffect as useEffect121 } from "react";
74960
75074
  function useStudioSessionStart(projectId, resolving, waitingForServer) {
74961
- useEffect120(() => {
75075
+ useEffect121(() => {
74962
75076
  if (resolving || waitingForServer || hasFiredSessionStart()) return;
74963
75077
  markSessionStartFired();
74964
75078
  trackStudioSessionStart({ has_project: projectId != null });
@@ -75039,7 +75153,7 @@ function StudioApp() {
75039
75153
  activeCompPath,
75040
75154
  masterCompPath
75041
75155
  );
75042
- useEffect121(() => {
75156
+ useEffect122(() => {
75043
75157
  if (activeCompPathHydrated) return;
75044
75158
  if (!fileManager.fileTreeLoaded) return;
75045
75159
  const nextCompPath = normalizeStudioCompositionPath(
@@ -75313,7 +75427,7 @@ function StudioApp() {
75313
75427
  } = useInspectorState(
75314
75428
  panelLayout.rightPanelTab,
75315
75429
  panelLayout.rightInspectorPanes,
75316
- panelLayout.rightCollapsed,
75430
+ panelLayout.effectiveRightCollapsed,
75317
75431
  isPlaying,
75318
75432
  domEditSession.domEditSelection,
75319
75433
  gestureState === "recording"
@@ -75429,7 +75543,7 @@ function StudioApp() {
75429
75543
  onAddCompositionToTimeline: handleAddCompositionAtPlayhead
75430
75544
  }
75431
75545
  ),
75432
- right: panelLayout.rightCollapsed ? null : /* @__PURE__ */ jsx184(
75546
+ right: panelLayout.effectiveRightCollapsed ? null : /* @__PURE__ */ jsx184(
75433
75547
  StudioRightPanel,
75434
75548
  {
75435
75549
  designPanelActive,